···2627## Testing Guidelines
2829-- There is no dedicated test runner yet. Use `pnpm check` and `pnpm lint` before submitting changes.
000030- For UI changes, verify key flows manually (login, card editing, save/load, and route navigation across `[handle]` pages).
3132## Commit & Pull Request Guidelines
···2627## Testing Guidelines
2829+- There is no dedicated test runner yet.
30+- **Before submitting changes, you must:**
31+ 1. Run `pnpm check` - Must complete with **0 errors and 0 warnings**
32+ 2. Run `pnpm format` - Format all code with Prettier
33+ 3. Run `pnpm lint` - Ensure no linting errors
34- For UI changes, verify key flows manually (login, card editing, save/load, and route navigation across `[handle]` pages).
3536## Commit & Pull Request Guidelines
+7
CLAUDE.md
···16- `pnpm format` - Format code with Prettier
17- `pnpm deploy` - Build and deploy to Cloudflare Workers
18000000019## Architecture
2021### Tech Stack
···16- `pnpm format` - Format code with Prettier
17- `pnpm deploy` - Build and deploy to Cloudflare Workers
1819+## Code Quality Requirements
20+21+Before submitting changes:
22+23+1. **Run `pnpm check`** - Must complete with 0 errors and 0 warnings
24+2. **Run `pnpm format`** - Format all code with Prettier
25+26## Architecture
2728### Tech Stack
···1<script lang="ts">
2- import type { ContentComponentProps } from '../../types';
3 import { onMount, onDestroy } from 'svelte';
4 import Tetris8BitMusic from './Tetris8Bit.mp3';
5-6- let { item }: ContentComponentProps = $props();
78 let canvas: HTMLCanvasElement;
9 let container: HTMLDivElement;
···252253 oscillator.start(audioCtx.currentTime);
254 oscillator.stop(audioCtx.currentTime + duration);
255- } catch (e) {
256 // Audio not supported
257 }
258 }
···1<script lang="ts">
02 import { onMount, onDestroy } from 'svelte';
3 import Tetris8BitMusic from './Tetris8Bit.mp3';
0045 let canvas: HTMLCanvasElement;
6 let container: HTMLDivElement;
···249250 oscillator.start(audioCtx.currentTime);
251 oscillator.stop(audioCtx.currentTime + duration);
252+ } catch {
253 // Audio not supported
254 }
255 }
+3-2
src/lib/cards/GameCards/TetrisCard/index.ts
···1//Music by DJARTMUSIC - The Return Of The 8-bit Era
2//https://pixabay.com/de/music/videospiele-the-return-of-the-8-bit-era-301292/
34-import type { CardDefinition } from '../../types';
5import TetrisCard from './TetrisCard.svelte';
6import SidebarItemTetrisCard from './SidebarItemTetrisCard.svelte';
078export const TetrisCardDefinition = {
9 type: 'tetris',
10- contentComponent: TetrisCard,
11 sidebarComponent: SidebarItemTetrisCard,
12 allowSetColor: true,
13 defaultColor: 'accent',
···1//Music by DJARTMUSIC - The Return Of The 8-bit Era
2//https://pixabay.com/de/music/videospiele-the-return-of-the-8-bit-era-301292/
34+import type { CardDefinition, ContentComponentProps } from '../../types';
5import TetrisCard from './TetrisCard.svelte';
6import SidebarItemTetrisCard from './SidebarItemTetrisCard.svelte';
7+import type { Component } from 'svelte';
89export const TetrisCardDefinition = {
10 type: 'tetris',
11+ contentComponent: TetrisCard as unknown as Component<ContentComponentProps>,
12 sidebarComponent: SidebarItemTetrisCard,
13 allowSetColor: true,
14 defaultColor: 'accent',
···9 const data = getAdditionalUserData();
10 // svelte-ignore state_referenced_locally
11 let record = $state(data[item.cardType] as any);
12-13- let animated = $derived(emojiToNotoAnimatedWebp(record.value.status));
14</script>
1516<EmojiPicker
···9 const data = getAdditionalUserData();
10 // svelte-ignore state_referenced_locally
11 let record = $state(data[item.cardType] as any);
0012</script>
1314<EmojiPicker
···61 embed: post.embed
62 ? ({
63 type: blueskyEmbedTypeToEmbedType(post.embed?.$type),
64+ // Cast to any to handle union type - properties are conditionally accessed
65+ images: (post.embed as any)?.images?.map((image: any) => ({
66 alt: image.alt,
67 thumb: image.thumb,
68 aspectRatio: image.aspectRatio,
69 fullsize: image.fullsize
70 })),
71+ external: (post.embed as any)?.external
72 ? {
73+ href: (post.embed as any).external.uri,
74+ title: (post.embed as any).external.title,
75+ description: (post.embed as any).external.description,
76+ thumb: (post.embed as any).external.thumb
77 }
78 : undefined,
79+ video: (post.embed as any)?.playlist
80 ? {
81+ playlist: (post.embed as any).playlist,
82+ thumb: (post.embed as any).thumbnail,
83+ alt: (post.embed as any).alt,
84+ aspectRatio: (post.embed as any).aspectRatio
85 }
86 : undefined
87 } as PostEmbed)
···91 };
92}
9394+interface MentionFeature {
95+ $type: 'app.bsky.richtext.facet#mention';
96+ did: string;
97+}
98+99+interface LinkFeature {
100+ $type: 'app.bsky.richtext.facet#link';
101+ uri: string;
102+}
103+104+interface TagFeature {
105+ $type: 'app.bsky.richtext.facet#tag';
106+ tag: string;
107+}
108+109+type Feature = MentionFeature | LinkFeature | TagFeature;
110+111const renderSegment = (segment: RichtextSegment, baseUrl: string) => {
112 const { text, features } = segment;
113···116 }
117118 // segments can have multiple features, use the first one
119+ const feature = features[0] as Feature;
120121 const createLink = (href: string, text: string) => {
122 return `<a target="_blank" rel="noopener noreferrer nofollow" href="${encodeURI(href)}">${text}</a>`;
···124125 switch (feature.$type) {
126 case 'app.bsky.richtext.facet#mention':
127+ return createLink(`${baseUrl}/profile/${feature.did}`, segment.text);
128 case 'app.bsky.richtext.facet#link':
129 return createLink(feature.uri, segment.text);
130 case 'app.bsky.richtext.facet#tag':
131+ return createLink(`${baseUrl}/hashtag/${feature.tag}`, segment.text);
132 default:
133 return `<span>${text}</span>`;
134 }
+1-1
src/lib/components/post/Post.svelte
···1<script lang="ts">
2 import Embed from './embeds/Embed.svelte';
3- import { cn, Avatar, Prose } from '@foxui/core';
4 import type { WithChildren, WithElementRef } from 'bits-ui';
5 import type { HTMLAttributes } from 'svelte/elements';
6 import type { PostData } from '.';
···1<script lang="ts">
2 import Embed from './embeds/Embed.svelte';
3+ import { cn, Prose } from '@foxui/core';
4 import type { WithChildren, WithElementRef } from 'bits-ui';
5 import type { HTMLAttributes } from 'svelte/elements';
6 import type { PostData } from '.';