Files
allmy.work/docs/plans/2026-03-06-portfolio-foundation.md

1976 lines
57 KiB
Markdown
Raw Permalink Normal View History

# Portfolio Foundation Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Bootstrap the allmywork SvelteKit portfolio with a CLAUDE.md, a brutalist design system extracted from the Figma AI prototype, shared/ui components converted from React to Svelte 5, and Storybook stories for each component.
**Architecture:** Tailwind CSS v4 custom properties carry the design tokens (colors, typography, spacing, shadows). All UI primitives live in `src/shared/ui/`, each as a PascalCase folder with `ComponentName.svelte`, `types.ts`, `index.ts`, and `ComponentName.stories.svelte`. The FSD directory structure is already in place; this plan fills in the foundation layer only.
**Tech Stack:** SvelteKit 2 + Svelte 5 (runes), Tailwind CSS v4, Biome (lint/format), Vitest (unit tests), Storybook 10 with `@storybook/addon-svelte-csf`, Bun as package manager and runtime.
---
## Task 0: Write CLAUDE.md for the project
**Files:**
- Modify: `/home/ilia/Documents/Projects/allmywork/CLAUDE.md`
The existing CLAUDE.md covers project purpose and tooling. We need to add the code-style section that mirrors glyphdiff's conventions.
**Step 1: Read the existing CLAUDE.md**
Read `/home/ilia/Documents/Projects/allmywork/CLAUDE.md` in full.
**Step 2: Append the Code Style section**
Add the following section to the end of the existing CLAUDE.md (before any trailing lines):
```markdown
---
## Code Style
### Formatting (Biome)
- Indent: tabs
- Quote style: double quotes for JS/TS, double quotes for Svelte attributes
- Line width: 120 characters
- Semicolons: always
- Trailing commas: all
### TypeScript
- Strict mode: on
- Prefer `interface` over `type` for object shapes
- Always type component props via an `interface Props { ... }` block
- Use `$props()` rune — never `export let`
- Use `$derived()` and `$effect()` — never Svelte 4 `$:` syntax
- Use `$state()` for all reactive primitives
### Component Conventions
- File name: PascalCase (`Button.svelte`, `ProjectCard.svelte`)
- Store files: camelCase with `.svelte.ts` suffix (`sidebarStore.svelte.ts`)
- Story files: `.stories.svelte` suffix (Storybook CSF with Svelte addon)
- Test files: `.test.ts` for unit, `.svelte.test.ts` for component tests
- Each slice exports a public API via `index.ts`
### Svelte 5 Patterns
```svelte
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { Snippet } from 'svelte';
import type { HTMLButtonAttributes } from 'svelte/elements';
interface Props extends HTMLButtonAttributes {
variant?: 'primary' | 'secondary';
children: Snippet;
class?: string;
}
let { variant = 'primary', children, class: className, ...rest }: Props = $props();
const classes = $derived(cn(baseStyles, variantStyles[variant], className));
</script>
<button class={classes} {...rest}>
{@render children()}
</button>
```
### Comments
- Multiline JSDoc (`/** ... */`) for exported functions and component description headers
- Single-line (`//`) for inline logic remarks
- Principle: "less is more" — explain the *why*, not the *what*
### Import Order (Biome organizeImports)
1. Svelte/SvelteKit built-ins (`svelte`, `$app/...`)
2. FSD layer aliases (`$shared/...`, `$entities/...`, etc.)
3. Relative imports (`./`, `../`)
### Commit Messages
Format: `prefix(scope): short description`
Prefixes: `feat`, `fix`, `chore`, `refactor`, `docs`, `style`, `test`, `perf`, `ci`, `build`, `revert`
Example: `feat(shared/ui): add Button component with brutalist variants`
```
**Step 3: Verify the file looks correct**
Read the first and last 20 lines of the updated CLAUDE.md to confirm the edit landed cleanly.
**Step 4: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add CLAUDE.md
git commit -m "docs(CLAUDE.md): add code style section aligned with glyphdiff conventions"
```
---
## Task 1: Install Storybook and required dev dependencies
The project currently has no Storybook. This task wires it up before any stories are written.
**Files:**
- Modify: `/home/ilia/Documents/Projects/allmywork/package.json` (via bun add)
- Create: `/home/ilia/Documents/Projects/allmywork/.storybook/main.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/.storybook/preview.ts`
**Step 1: Install Storybook and addons**
```bash
cd /home/ilia/Documents/Projects/allmywork
bunx storybook@latest init --skip-install
```
Expected output: Storybook scaffolds `.storybook/` and adds scripts to package.json. Answer "yes" to any prompts.
**Step 2: Verify generated .storybook/main.ts**
Read `/home/ilia/Documents/Projects/allmywork/.storybook/main.ts`.
It should reference `@storybook/svelte-vite` as the framework.
If the `init` command did not produce a valid Svelte-Vite config, write `.storybook/main.ts` manually:
```typescript
import type { StorybookConfig } from '@storybook/svelte-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.svelte'],
addons: [
'@storybook/addon-svelte-csf',
'@storybook/addon-a11y',
'@storybook/addon-docs',
],
framework: {
name: '@storybook/svelte-vite',
options: {},
},
};
export default config;
```
**Step 3: Install missing addons if needed**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun add -D @storybook/addon-svelte-csf @storybook/addon-a11y @storybook/addon-docs
```
**Step 4: Add Storybook scripts to package.json (if not already present)**
Open `/home/ilia/Documents/Projects/allmywork/package.json` and confirm the `scripts` section contains:
```json
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
```
If missing, add them.
**Step 5: Run Storybook to confirm it starts**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run storybook &
```
Expected: Storybook starts on http://localhost:6006 with no fatal errors.
Kill it after confirming: `kill %1` or `Ctrl+C`.
**Step 6: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add .storybook package.json
git commit -m "chore(storybook): install and configure Storybook 10 with svelte-vite"
```
---
## Task 2: Set up the design system — CSS custom properties and Tailwind v4 tokens
Extract the design tokens from the Figma prototype's `theme.css` into the project's `app.css` and register them as Tailwind v4 theme values.
**Files:**
- Modify: `/home/ilia/Documents/Projects/allmywork/src/app/styles/app.css`
**Reference:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/styles/theme.css` and `fonts.css`
**Step 1: Open app.css and replace its contents**
The file currently only contains `@import "tailwindcss";`. Replace it with the following:
```css
@import "tailwindcss";
/* ============================================================
FONTS
============================================================ */
@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,100..900,0..100,0..1;1,9..144,100..900,0..100,0..1&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,100..900;1,100..900&display=swap');
/* ============================================================
DESIGN TOKENS
============================================================ */
:root {
/* Typography scale — Augmented Fourth (×1.414) */
--font-size-base: 16px;
--text-xs: 0.707rem; /* 11.31px */
--text-sm: 0.840rem; /* 13.44px */
--text-base: 1rem; /* 16px */
--text-lg: 1.414rem; /* 22.62px */
--text-xl: 2rem; /* 32px */
--text-2xl: 2.828rem; /* 45.25px */
--text-3xl: 4rem; /* 64px */
--text-4xl: 5.657rem; /* 90.51px */
--text-5xl: 8rem; /* 128px */
/* Font families */
--font-heading: 'Fraunces', serif;
--font-body: 'Public Sans', sans-serif;
/* Font weights */
--font-weight-heading: 900;
--font-weight-body: 600;
--font-weight-normal: 400;
/* Line heights */
--line-height-tight: 1.2;
--line-height-normal: 1.5;
/* Fraunces variable font axes */
--fraunces-wonk: 1;
--fraunces-soft: 0;
/* Palette — Sanzo Wada inspired */
--ochre-clay: #D9B48F;
--slate-indigo: #3B4A59;
--burnt-oxide: #A64B35;
--carbon-black: #121212;
/* Semantic color aliases */
--background: var(--ochre-clay);
--foreground: var(--carbon-black);
--card: var(--ochre-clay);
--card-foreground: var(--carbon-black);
--primary: var(--burnt-oxide);
--primary-foreground: var(--ochre-clay);
--secondary: var(--slate-indigo);
--secondary-foreground: var(--ochre-clay);
--muted: var(--slate-indigo);
--muted-foreground: var(--ochre-clay);
--accent: var(--burnt-oxide);
--accent-foreground: var(--ochre-clay);
--destructive: #d4183d;
--destructive-foreground:#ffffff;
--border: var(--carbon-black);
--ring: var(--carbon-black);
/* Spacing — 8pt linear scale */
--space-1: 0.5rem; /* 8px */
--space-2: 1rem; /* 16px */
--space-3: 1.5rem; /* 24px */
--space-4: 2rem; /* 32px */
--space-5: 2.5rem; /* 40px */
--space-6: 3rem; /* 48px */
--space-8: 4rem; /* 64px */
--space-10: 5rem; /* 80px */
--space-12: 6rem; /* 96px */
--space-16: 8rem; /* 128px */
--space-20: 10rem; /* 160px */
/* Borders */
--border-width: 3px;
--radius: 0px;
/* Brutalist hard shadows */
--shadow-brutal-sm: 4px 4px 0 var(--carbon-black);
--shadow-brutal: 8px 8px 0 var(--carbon-black);
--shadow-brutal-lg: 12px 12px 0 var(--carbon-black);
}
/* ============================================================
TAILWIND v4 THEME REGISTRATION
============================================================ */
@theme inline {
/* Colors */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-ring: var(--ring);
/* Palette direct access */
--color-ochre-clay: var(--ochre-clay);
--color-slate-indigo: var(--slate-indigo);
--color-burnt-oxide: var(--burnt-oxide);
--color-carbon-black: var(--carbon-black);
/* Border radius (zero for brutalist aesthetic) */
--radius-sm: var(--radius);
--radius-md: var(--radius);
--radius-lg: var(--radius);
/* Box shadows */
--shadow-brutal-sm: var(--shadow-brutal-sm);
--shadow-brutal: var(--shadow-brutal);
--shadow-brutal-lg: var(--shadow-brutal-lg);
/* Font families */
--font-heading: var(--font-heading);
--font-body: var(--font-body);
}
/* ============================================================
BASE LAYER — GLOBAL ELEMENT DEFAULTS
============================================================ */
@layer base {
html {
font-size: var(--font-size-base);
}
body {
background-color: var(--background);
color: var(--foreground);
font-family: var(--font-body);
font-weight: var(--font-weight-body);
line-height: var(--line-height-tight);
overflow-x: hidden;
position: relative;
}
/* Paper grain texture overlay */
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(90deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px);
opacity: .4;
pointer-events: none;
z-index: 1;
}
/* Ensure page content renders above texture */
body > * {
position: relative;
z-index: 2;
}
h1, .h1 { font-size: var(--text-4xl); }
h2, .h2 { font-size: var(--text-3xl); }
h3, .h3 { font-size: var(--text-2xl); }
h4, .h4 { font-size: var(--text-xl); }
h5, .h5 { font-size: var(--text-lg); }
h1, h2, h3, h4, h5,
.h1, .h2, .h3, .h4, .h5 {
font-family: var(--font-heading);
font-weight: var(--font-weight-heading);
line-height: var(--line-height-tight);
font-variation-settings: 'WONK' var(--fraunces-wonk), 'SOFT' var(--fraunces-soft);
color: var(--carbon-black);
margin: 0;
}
p {
font-family: var(--font-body);
font-size: var(--text-base);
font-weight: var(--font-weight-body);
line-height: var(--line-height-tight);
color: var(--carbon-black);
margin: 0;
}
label {
font-family: var(--font-body);
font-size: var(--text-sm);
font-weight: var(--font-weight-body);
text-transform: uppercase;
letter-spacing: .05em;
line-height: var(--line-height-tight);
}
button {
font-family: var(--font-body);
font-size: var(--text-base);
font-weight: var(--font-weight-body);
line-height: var(--line-height-tight);
cursor: pointer;
}
input, textarea, select {
font-family: var(--font-body);
font-size: var(--text-base);
font-weight: var(--font-weight-body);
line-height: var(--line-height-tight);
}
blockquote {
font-family: var(--font-heading);
font-size: var(--text-xl);
font-weight: var(--font-weight-heading);
font-variation-settings: 'WONK' var(--fraunces-wonk), 'SOFT' var(--fraunces-soft);
line-height: var(--line-height-tight);
border-left: var(--border-width) solid var(--carbon-black);
padding-left: var(--space-4);
margin: var(--space-6) 0;
}
a {
color: var(--burnt-oxide);
text-decoration: none;
border-bottom: 2px solid var(--carbon-black);
transition: border-bottom-width .15s ease;
}
a:hover {
border-bottom-width: 4px;
}
}
/* ============================================================
BRUTALIST UTILITY CLASSES
============================================================ */
.brutal-border { border: var(--border-width) solid var(--carbon-black); }
.brutal-border-top { border-top: var(--border-width) solid var(--carbon-black); }
.brutal-border-bottom { border-bottom: var(--border-width) solid var(--carbon-black); }
.brutal-border-left { border-left: var(--border-width) solid var(--carbon-black); }
.brutal-border-right { border-right: var(--border-width) solid var(--carbon-black); }
.brutal-shadow { box-shadow: var(--shadow-brutal); }
.brutal-shadow-sm { box-shadow: var(--shadow-brutal-sm); }
.brutal-shadow-lg { box-shadow: var(--shadow-brutal-lg); }
/* ============================================================
GRID UTILITIES
============================================================ */
.grid-muller {
display: grid;
gap: var(--space-3);
grid-template-columns: repeat(4, 1fr);
}
@media (min-width: 1024px) {
.grid-muller {
grid-template-columns: repeat(12, 1fr);
}
}
/* ============================================================
ANIMATION
============================================================ */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in-up {
animation: fadeInUp .5s cubic-bezier(.4, 0, .2, 1);
}
```
**Step 2: Verify dev server still compiles**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run dev &
```
Expected: No CSS errors in the terminal. Visit http://localhost:5173 — the page background should be ochre-clay (#D9B48F).
Kill dev server: `kill %1`
**Step 3: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/app/styles/app.css
git commit -m "feat(design-system): add brutalist design tokens and Tailwind v4 theme from Figma prototype"
```
---
## Task 3: Add the `cn` utility to shared/lib
Many components will use `cn` (clsx + tailwind-merge) to compose class names. This task adds the helper before any component uses it.
**Files:**
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/lib/cn.ts`
- Modify: `/home/ilia/Documents/Projects/allmywork/src/shared/lib/index.ts`
**Step 1: Install clsx and tailwind-merge**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun add clsx tailwind-merge
```
Expected: Both packages appear in `package.json` under `dependencies`.
**Step 2: Write failing test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/lib/cn.test.ts`:
```typescript
import { describe, expect, it } from 'vitest';
import { cn } from './cn';
describe('cn', () => {
it('merges class strings', () => {
expect(cn('foo', 'bar')).toBe('foo bar');
});
it('deduplicates conflicting Tailwind classes (last wins)', () => {
expect(cn('p-4', 'p-8')).toBe('p-8');
});
it('ignores falsy values', () => {
expect(cn('foo', false, undefined, null, 'bar')).toBe('foo bar');
});
});
```
**Step 3: Run test to confirm it fails**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/lib/cn.test.ts
```
Expected: FAIL — `Cannot find module './cn'`
**Step 4: Implement cn.ts**
```typescript
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
/** Merge Tailwind classes without conflicts. Later classes win on collision. */
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}
```
**Step 5: Run test to confirm it passes**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/lib/cn.test.ts
```
Expected: PASS (3 tests)
**Step 6: Export from shared/lib/index.ts**
Open `/home/ilia/Documents/Projects/allmywork/src/shared/lib/index.ts` and add:
```typescript
export { cn } from './cn';
```
**Step 7: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/lib/cn.ts src/shared/lib/cn.test.ts src/shared/lib/index.ts package.json
git commit -m "feat(shared/lib): add cn utility (clsx + tailwind-merge)"
```
---
## Task 4: Implement `Badge` component
**Files:**
- Modify: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/` — replace flat files with folder-per-component layout
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Badge/types.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Badge/Badge.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Badge/index.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Badge/Badge.stories.svelte`
**Reference prototype:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/Badge.tsx`
**Step 1: Create types.ts**
```typescript
export type BadgeVariant = 'default' | 'primary' | 'secondary' | 'outline';
```
**Step 2: Write failing component test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Badge/Badge.test.ts`:
```typescript
import { render } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import Badge from './Badge.svelte';
describe('Badge', () => {
it('renders children text', () => {
const { getByText } = render(Badge, { props: { children: () => 'hello' } });
expect(getByText('hello')).toBeTruthy();
});
it('applies default variant class', () => {
const { container } = render(Badge, { props: { variant: 'default' } });
expect(container.querySelector('span')?.className).toContain('bg-carbon-black');
});
});
```
**Step 3: Run test to confirm failure**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Badge/Badge.test.ts
```
Expected: FAIL — component file not found.
**Step 4: Implement Badge.svelte**
```svelte
<!--
Component: Badge
Inline label for tags and status indicators. Brutalist border, uppercase, no radius.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { Snippet } from 'svelte';
import type { BadgeVariant } from './types';
interface Props {
/** Visual style variant @default 'default' */
variant?: BadgeVariant;
children?: Snippet;
class?: string;
}
let { variant = 'default', children, class: className }: Props = $props();
const variantStyles: Record<BadgeVariant, string> = {
default: 'brutal-border bg-carbon-black text-ochre-clay',
primary: 'brutal-border bg-burnt-oxide text-ochre-clay',
secondary: 'brutal-border bg-slate-indigo text-ochre-clay',
outline: 'brutal-border bg-transparent text-carbon-black',
};
const classes = $derived(cn(
'inline-block px-3 py-1 text-xs uppercase tracking-wider',
variantStyles[variant],
className,
));
</script>
<span class={classes}>
{#if children}
{@render children()}
{/if}
</span>
```
**Step 5: Create index.ts**
```typescript
export { default as Badge } from './Badge.svelte';
export type { BadgeVariant } from './types';
```
**Step 6: Run test to confirm it passes**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Badge/Badge.test.ts
```
Expected: PASS
**Step 7: Write Badge.stories.svelte**
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Badge from './Badge.svelte';
const { Story } = defineMeta({
title: 'Shared/Badge',
component: Badge,
tags: ['autodocs'],
parameters: {
layout: 'centered',
docs: { description: { component: 'Brutalist inline label. Uppercase, 3px border, zero radius.' } },
},
argTypes: {
variant: {
control: 'select',
options: ['default', 'primary', 'secondary', 'outline'],
},
},
});
</script>
<Story name="Default">
{#snippet template(args)}
<Badge {...args}>Tag</Badge>
{/snippet}
</Story>
<Story name="Primary" args={{ variant: 'primary' }}>
{#snippet template(args)}
<Badge {...args}>Primary</Badge>
{/snippet}
</Story>
<Story name="Secondary" args={{ variant: 'secondary' }}>
{#snippet template(args)}
<Badge {...args}>Secondary</Badge>
{/snippet}
</Story>
<Story name="Outline" args={{ variant: 'outline' }}>
{#snippet template(args)}
<Badge {...args}>Outline</Badge>
{/snippet}
</Story>
<Story name="All variants">
{#snippet template()}
<div class="flex gap-3 flex-wrap">
<Badge variant="default">Default</Badge>
<Badge variant="primary">Primary</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
</div>
{/snippet}
</Story>
```
**Step 8: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/ui/Badge/
git commit -m "feat(shared/ui): add Badge component with brutalist variants and Storybook story"
```
---
## Task 5: Implement `Button` component (replacing the placeholder)
**Files:**
- Delete: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Button.svelte` (placeholder)
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Button/types.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Button/Button.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Button/index.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Button/Button.stories.svelte`
- Modify: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/index.ts`
**Reference prototype:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/Button.tsx`
**Step 1: Create types.ts**
```typescript
export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost';
export type ButtonSize = 'sm' | 'md' | 'lg';
```
**Step 2: Write failing test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Button/Button.test.ts`:
```typescript
import { render } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import Button from './Button.svelte';
describe('Button', () => {
it('renders children', () => {
const { getByText } = render(Button, { props: { children: () => 'Click me' } });
expect(getByText('Click me')).toBeTruthy();
});
it('is disabled when disabled prop is true', () => {
const { container } = render(Button, { props: { disabled: true } });
expect(container.querySelector('button')?.disabled).toBe(true);
});
it('applies primary variant styles', () => {
const { container } = render(Button, { props: { variant: 'primary' } });
expect(container.querySelector('button')?.className).toContain('bg-burnt-oxide');
});
});
```
**Step 3: Run test to confirm failure**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Button/Button.test.ts
```
Expected: FAIL
**Step 4: Implement Button.svelte**
```svelte
<!--
Component: Button
Brutalist button. 3px border, hard shadow on hover, uppercase, zero radius.
Hover: translates 2px down-right with reduced shadow for press feel.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { Snippet } from 'svelte';
import type { HTMLButtonAttributes } from 'svelte/elements';
import type { ButtonSize, ButtonVariant } from './types';
interface Props extends HTMLButtonAttributes {
/** Visual style @default 'primary' */
variant?: ButtonVariant;
/** Size preset @default 'md' */
size?: ButtonSize;
children?: Snippet;
class?: string;
}
let {
variant = 'primary',
size = 'md',
children,
class: className,
type = 'button',
disabled,
...rest
}: Props = $props();
const base =
'brutal-border transition-all duration-200 ' +
'hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[6px_6px_0_var(--carbon-black)] ' +
'active:translate-x-[4px] active:translate-y-[4px] active:shadow-[4px_4px_0_var(--carbon-black)] ' +
'uppercase tracking-wider inline-flex items-center justify-center ' +
'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none';
const variantStyles: Record<ButtonVariant, string> = {
primary: 'brutal-shadow bg-burnt-oxide text-ochre-clay',
secondary: 'brutal-shadow bg-slate-indigo text-ochre-clay',
outline: 'brutal-shadow bg-transparent text-carbon-black',
ghost: 'bg-ochre-clay text-carbon-black shadow-none hover:shadow-[6px_6px_0_var(--carbon-black)]',
};
const sizeStyles: Record<ButtonSize, string> = {
sm: 'px-4 py-2 text-sm',
md: 'px-6 py-3 text-base',
lg: 'px-8 py-4 text-lg',
};
const classes = $derived(cn(base, variantStyles[variant], sizeStyles[size], className));
</script>
<button {type} {disabled} class={classes} {...rest}>
{#if children}
{@render children()}
{/if}
</button>
```
**Step 5: Create index.ts**
```typescript
export { default as Button } from './Button.svelte';
export type { ButtonVariant, ButtonSize } from './types';
```
**Step 6: Run test to confirm pass**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Button/Button.test.ts
```
Expected: PASS (3 tests)
**Step 7: Update shared/ui/index.ts**
Open `/home/ilia/Documents/Projects/allmywork/src/shared/ui/index.ts`. Replace the existing content that referenced the flat `Button.svelte` and `Input.svelte` with:
```typescript
export * from './Badge';
export * from './Button';
```
(More exports will be appended as components are added in subsequent tasks.)
**Step 8: Remove the placeholder flat files**
```bash
rm /home/ilia/Documents/Projects/allmywork/src/shared/ui/Button.svelte
rm /home/ilia/Documents/Projects/allmywork/src/shared/ui/Input.svelte
```
Note: `Input.svelte` is removed here because Task 8 will add a proper `Input/` folder.
**Step 9: Fix import in HomePage.svelte**
Open `/home/ilia/Documents/Projects/allmywork/src/pages/home/ui/HomePage.svelte`.
The existing import `import { Button } from '$shared/ui';` should still work because index.ts now re-exports Button. Verify the dev server compiles:
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run dev &
# visit http://localhost:5173 — page should load
kill %1
```
**Step 10: Write Button.stories.svelte**
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Button from './Button.svelte';
const { Story } = defineMeta({
title: 'Shared/Button',
component: Button,
tags: ['autodocs'],
parameters: {
layout: 'centered',
docs: { description: { component: 'Brutalist CTA button. Hard shadow, uppercase, hover translates 2px.' } },
},
argTypes: {
variant: { control: 'select', options: ['primary', 'secondary', 'outline', 'ghost'] },
size: { control: 'select', options: ['sm', 'md', 'lg'] },
},
});
</script>
<Story name="Primary" args={{ variant: 'primary', size: 'md' }}>
{#snippet template(args)}
<Button {...args}>Primary</Button>
{/snippet}
</Story>
<Story name="Secondary" args={{ variant: 'secondary', size: 'md' }}>
{#snippet template(args)}
<Button {...args}>Secondary</Button>
{/snippet}
</Story>
<Story name="Outline" args={{ variant: 'outline', size: 'md' }}>
{#snippet template(args)}
<Button {...args}>Outline</Button>
{/snippet}
</Story>
<Story name="Ghost" args={{ variant: 'ghost', size: 'md' }}>
{#snippet template(args)}
<Button {...args}>Ghost</Button>
{/snippet}
</Story>
<Story name="All sizes">
{#snippet template()}
<div class="flex gap-4 items-center">
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</div>
{/snippet}
</Story>
<Story name="Disabled">
{#snippet template()}
<Button disabled>Disabled</Button>
{/snippet}
</Story>
```
**Step 11: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/ui/Button/ src/shared/ui/index.ts
git commit -m "feat(shared/ui): implement brutalist Button component replacing placeholder"
```
---
## Task 6: Implement `Card` component
**Files:**
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Card/types.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Card/Card.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Card/index.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Card/Card.stories.svelte`
**Reference prototype:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/Card.tsx`
**Step 1: Create types.ts**
```typescript
export type CardBackground = 'ochre' | 'slate' | 'white';
```
**Step 2: Write failing test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Card/Card.test.ts`:
```typescript
import { render } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import Card from './Card.svelte';
describe('Card', () => {
it('renders with default ochre background', () => {
const { container } = render(Card);
expect(container.querySelector('div')?.className).toContain('bg-ochre-clay');
});
it('renders with slate background', () => {
const { container } = render(Card, { props: { background: 'slate' } });
expect(container.querySelector('div')?.className).toContain('bg-slate-indigo');
});
it('renders with white background', () => {
const { container } = render(Card, { props: { background: 'white' } });
expect(container.querySelector('div')?.className).toContain('bg-white');
});
});
```
**Step 3: Run test to confirm failure**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Card/Card.test.ts
```
Expected: FAIL
**Step 4: Implement Card.svelte**
```svelte
<!--
Component: Card
Brutalist content container. 3px border, 8px hard shadow, zero radius.
Backgrounds: ochre (default), slate (dark), white.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { Snippet } from 'svelte';
import type { CardBackground } from './types';
interface Props {
/** Background color preset @default 'ochre' */
background?: CardBackground;
/** Remove default padding @default false */
noPadding?: boolean;
children?: Snippet;
class?: string;
}
let { background = 'ochre', noPadding = false, children, class: className }: Props = $props();
const backgroundStyles: Record<CardBackground, string> = {
ochre: 'bg-ochre-clay text-carbon-black',
slate: 'bg-slate-indigo text-ochre-clay',
white: 'bg-white text-carbon-black',
};
const classes = $derived(cn(
'brutal-border brutal-shadow',
backgroundStyles[background],
!noPadding && 'p-6 md:p-8',
className,
));
</script>
<div class={classes}>
{#if children}
{@render children()}
{/if}
</div>
```
**Step 5: Create index.ts**
```typescript
export { default as Card } from './Card.svelte';
export type { CardBackground } from './types';
```
**Step 6: Run test to confirm pass**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Card/Card.test.ts
```
Expected: PASS (3 tests)
**Step 7: Update shared/ui/index.ts** — append:
```typescript
export * from './Card';
```
**Step 8: Write Card.stories.svelte**
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Card from './Card.svelte';
const { Story } = defineMeta({
title: 'Shared/Card',
component: Card,
tags: ['autodocs'],
parameters: {
layout: 'padded',
docs: { description: { component: 'Brutalist card. 3px border, hard shadow, three background presets.' } },
},
argTypes: {
background: { control: 'select', options: ['ochre', 'slate', 'white'] },
noPadding: { control: 'boolean' },
},
});
</script>
<Story name="Ochre (default)" args={{ background: 'ochre' }}>
{#snippet template(args)}
<Card {...args}>Card content goes here</Card>
{/snippet}
</Story>
<Story name="Slate" args={{ background: 'slate' }}>
{#snippet template(args)}
<Card {...args}>Card content goes here</Card>
{/snippet}
</Story>
<Story name="White" args={{ background: 'white' }}>
{#snippet template(args)}
<Card {...args}>Card content goes here</Card>
{/snippet}
</Story>
<Story name="No Padding" args={{ noPadding: true }}>
{#snippet template(args)}
<Card {...args}>
<div class="p-4 brutal-border-bottom">Header</div>
<div class="p-4">Body</div>
</Card>
{/snippet}
</Story>
```
**Step 9: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/ui/Card/ src/shared/ui/index.ts
git commit -m "feat(shared/ui): add Card component with background variants and Storybook story"
```
---
## Task 7: Implement `Section` and `Container` components
**Files:**
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Section/types.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Section/Section.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Section/Container.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Section/index.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Section/Section.stories.svelte`
**Reference prototype:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/Section.tsx`
**Step 1: Create types.ts**
```typescript
export type SectionBackground = 'ochre' | 'slate' | 'white';
export type ContainerSize = 'default' | 'wide' | 'ultra-wide';
```
**Step 2: Write failing test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Section/Section.test.ts`:
```typescript
import { render } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import Section from './Section.svelte';
describe('Section', () => {
it('renders a <section> element', () => {
const { container } = render(Section);
expect(container.querySelector('section')).toBeTruthy();
});
it('applies ochre background by default', () => {
const { container } = render(Section);
expect(container.querySelector('section')?.className).toContain('bg-ochre-clay');
});
it('applies brutal border when bordered=true', () => {
const { container } = render(Section, { props: { bordered: true } });
const cls = container.querySelector('section')?.className ?? '';
expect(cls).toContain('brutal-border-top');
expect(cls).toContain('brutal-border-bottom');
});
});
```
**Step 3: Run test — expect FAIL**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Section/Section.test.ts
```
**Step 4: Implement Section.svelte**
```svelte
<!--
Component: Section
Full-width page section. Controls background and optional top/bottom brutal borders.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { Snippet } from 'svelte';
import type { SectionBackground } from './types';
interface Props {
background?: SectionBackground;
/** Add brutal border on top and bottom @default false */
bordered?: boolean;
children?: Snippet;
class?: string;
}
let { background = 'ochre', bordered = false, children, class: className }: Props = $props();
const backgroundStyles: Record<SectionBackground, string> = {
ochre: 'bg-ochre-clay text-carbon-black',
slate: 'bg-slate-indigo text-ochre-clay',
white: 'bg-white text-carbon-black',
};
const classes = $derived(cn(
backgroundStyles[background],
bordered && 'brutal-border-top brutal-border-bottom',
className,
));
</script>
<section class={classes}>
{#if children}
{@render children()}
{/if}
</section>
```
**Step 5: Implement Container.svelte**
```svelte
<!--
Component: Container
Centered max-width wrapper. Three size presets for responsive layouts up to 4K.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { Snippet } from 'svelte';
import type { ContainerSize } from './types';
interface Props {
/** Max-width preset @default 'default' */
size?: ContainerSize;
children?: Snippet;
class?: string;
}
let { size = 'default', children, class: className }: Props = $props();
const sizeStyles: Record<ContainerSize, string> = {
'default': 'max-w-7xl',
'wide': 'max-w-[1920px]',
'ultra-wide': 'max-w-[2560px]',
};
const classes = $derived(cn('mx-auto px-6 md:px-12 lg:px-16', sizeStyles[size], className));
</script>
<div class={classes}>
{#if children}
{@render children()}
{/if}
</div>
```
**Step 6: Create index.ts**
```typescript
export { default as Section } from './Section.svelte';
export { default as Container } from './Container.svelte';
export type { SectionBackground, ContainerSize } from './types';
```
**Step 7: Run test — expect PASS**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Section/Section.test.ts
```
**Step 8: Update shared/ui/index.ts** — append:
```typescript
export * from './Section';
```
**Step 9: Write Section.stories.svelte**
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Container from './Container.svelte';
import Section from './Section.svelte';
const { Story } = defineMeta({
title: 'Shared/Section',
component: Section,
tags: ['autodocs'],
parameters: { layout: 'fullscreen' },
});
</script>
<Story name="Ochre (default)">
{#snippet template()}
<Section>
<Container>
<p class="py-16">Section content</p>
</Container>
</Section>
{/snippet}
</Story>
<Story name="Slate">
{#snippet template()}
<Section background="slate">
<Container>
<p class="py-16">Section content on slate</p>
</Container>
</Section>
{/snippet}
</Story>
<Story name="Bordered">
{#snippet template()}
<Section bordered>
<Container>
<p class="py-16">Section with brutal top and bottom borders</p>
</Container>
</Section>
{/snippet}
</Story>
```
**Step 10: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/ui/Section/ src/shared/ui/index.ts
git commit -m "feat(shared/ui): add Section and Container components with layout presets"
```
---
## Task 8: Implement `Input` and `Textarea` components
**Files:**
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Input/types.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Input/Input.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Input/Textarea.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Input/index.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Input/Input.stories.svelte`
**Reference prototype:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/Input.tsx`
**Step 1: Create types.ts**
```typescript
// No custom types needed beyond HTML attributes; file reserved for future extension.
export type InputSize = 'sm' | 'md' | 'lg';
```
**Step 2: Write failing test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Input/Input.test.ts`:
```typescript
import { render } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import Input from './Input.svelte';
describe('Input', () => {
it('renders an <input> element', () => {
const { container } = render(Input);
expect(container.querySelector('input')).toBeTruthy();
});
it('renders label when label prop is set', () => {
const { getByText } = render(Input, { props: { label: 'Email' } });
expect(getByText('Email')).toBeTruthy();
});
it('renders error message when error prop is set', () => {
const { getByText } = render(Input, { props: { error: 'Required' } });
expect(getByText('Required')).toBeTruthy();
});
});
```
**Step 3: Run test — expect FAIL**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Input/Input.test.ts
```
**Step 4: Implement Input.svelte**
```svelte
<!--
Component: Input
Brutalist text input. 3px border, white background, burnt-oxide focus ring.
Optional label (uppercase) and inline error message.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { HTMLInputAttributes } from 'svelte/elements';
interface Props extends HTMLInputAttributes {
/** Uppercase label rendered above the input */
label?: string;
/** Inline error message rendered below the input */
error?: string;
class?: string;
}
let { label, error, class: className, ...rest }: Props = $props();
const inputClasses = $derived(cn(
'brutal-border bg-white px-4 py-3 text-carbon-black w-full',
'focus:outline-none focus:ring-2 focus:ring-burnt-oxide focus:ring-offset-2 focus:ring-offset-ochre-clay',
'transition-all duration-150',
'disabled:opacity-50 disabled:cursor-not-allowed',
className,
));
</script>
<div class="flex flex-col gap-2">
{#if label}
<label class="text-carbon-black">{label}</label>
{/if}
<input class={inputClasses} {...rest} />
{#if error}
<span class="text-sm text-burnt-oxide">{error}</span>
{/if}
</div>
```
**Step 5: Implement Textarea.svelte**
```svelte
<!--
Component: Textarea
Brutalist multi-line input. Same styling as Input but <textarea>.
Non-resizable by default (matches prototype).
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { HTMLTextareaAttributes } from 'svelte/elements';
interface Props extends HTMLTextareaAttributes {
label?: string;
error?: string;
rows?: number;
class?: string;
}
let { label, error, rows = 4, class: className, ...rest }: Props = $props();
const textareaClasses = $derived(cn(
'brutal-border bg-white px-4 py-3 text-carbon-black w-full resize-none',
'focus:outline-none focus:ring-2 focus:ring-burnt-oxide focus:ring-offset-2 focus:ring-offset-ochre-clay',
'transition-all duration-150',
'disabled:opacity-50 disabled:cursor-not-allowed',
className,
));
</script>
<div class="flex flex-col gap-2">
{#if label}
<label class="text-carbon-black">{label}</label>
{/if}
<textarea {rows} class={textareaClasses} {...rest}></textarea>
{#if error}
<span class="text-sm text-burnt-oxide">{error}</span>
{/if}
</div>
```
**Step 6: Create index.ts**
```typescript
export { default as Input } from './Input.svelte';
export { default as Textarea } from './Textarea.svelte';
export type { InputSize } from './types';
```
**Step 7: Run test — expect PASS**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Input/Input.test.ts
```
**Step 8: Update shared/ui/index.ts** — append:
```typescript
export * from './Input';
```
**Step 9: Write Input.stories.svelte**
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Input from './Input.svelte';
import Textarea from './Textarea.svelte';
const { Story } = defineMeta({
title: 'Shared/Input',
component: Input,
tags: ['autodocs'],
parameters: {
layout: 'padded',
docs: { description: { component: 'Brutalist text input with optional label and error state.' } },
},
});
</script>
<Story name="Default">
{#snippet template()}
<Input placeholder="Enter text..." />
{/snippet}
</Story>
<Story name="With Label">
{#snippet template()}
<Input label="Email address" type="email" placeholder="you@example.com" />
{/snippet}
</Story>
<Story name="With Error">
{#snippet template()}
<Input label="Email address" type="email" value="bad-email" error="Please enter a valid email address." />
{/snippet}
</Story>
<Story name="Disabled">
{#snippet template()}
<Input label="Read-only field" disabled value="Cannot edit this" />
{/snippet}
</Story>
<Story name="Textarea">
{#snippet template()}
<Textarea label="Message" placeholder="Write your message..." rows={5} />
{/snippet}
</Story>
<Story name="Textarea with Error">
{#snippet template()}
<Textarea label="Message" error="Message is required." />
{/snippet}
</Story>
```
**Step 10: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/ui/Input/ src/shared/ui/index.ts
git commit -m "feat(shared/ui): add Input and Textarea components with label and error states"
```
---
## Task 9: Implement `TechStackBrick` component
**Files:**
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/TechStackBrick/TechStackBrick.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/TechStackBrick/TechStackGrid.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/TechStackBrick/index.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/TechStackBrick/TechStackBrick.stories.svelte`
**Reference prototype:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/TechStackBrick.tsx`
**Step 1: Write failing test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/ui/TechStackBrick/TechStackBrick.test.ts`:
```typescript
import { render } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import TechStackBrick from './TechStackBrick.svelte';
describe('TechStackBrick', () => {
it('renders the skill name', () => {
const { getByText } = render(TechStackBrick, { props: { name: 'SvelteKit' } });
expect(getByText('SvelteKit')).toBeTruthy();
});
it('uppercases the skill name via CSS class', () => {
const { container } = render(TechStackBrick, { props: { name: 'React' } });
expect(container.querySelector('span')?.className).toContain('uppercase');
});
});
```
**Step 2: Run test — expect FAIL**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/TechStackBrick/TechStackBrick.test.ts
```
**Step 3: Implement TechStackBrick.svelte**
```svelte
<!--
Component: TechStackBrick
Single skill pill. Brutal border, white bg, hard shadow that collapses on hover (lifted press feel).
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
interface Props {
name: string;
class?: string;
}
let { name, class: className }: Props = $props();
const classes = $derived(cn(
'brutal-border brutal-shadow bg-white px-4 py-3 text-center',
'transition-all duration-200',
'hover:shadow-none hover:translate-x-[2px] hover:translate-y-[2px]',
className,
));
</script>
<div class={classes}>
<span class="text-sm uppercase tracking-wide">{name}</span>
</div>
```
**Step 4: Implement TechStackGrid.svelte**
```svelte
<!--
Component: TechStackGrid
Responsive grid of TechStackBrick items. Adjusts columns from 2 to 6 across breakpoints.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import TechStackBrick from './TechStackBrick.svelte';
interface Props {
skills: string[];
class?: string;
}
let { skills, class: className }: Props = $props();
const classes = $derived(cn(
'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4',
className,
));
</script>
<div class={classes}>
{#each skills as skill (skill)}
<TechStackBrick name={skill} />
{/each}
</div>
```
**Step 5: Create index.ts**
```typescript
export { default as TechStackBrick } from './TechStackBrick.svelte';
export { default as TechStackGrid } from './TechStackGrid.svelte';
```
**Step 6: Run test — expect PASS**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/TechStackBrick/TechStackBrick.test.ts
```
**Step 7: Update shared/ui/index.ts** — append:
```typescript
export * from './TechStackBrick';
```
**Step 8: Write TechStackBrick.stories.svelte**
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import TechStackBrick from './TechStackBrick.svelte';
import TechStackGrid from './TechStackGrid.svelte';
const { Story } = defineMeta({
title: 'Shared/TechStackBrick',
component: TechStackBrick,
tags: ['autodocs'],
parameters: {
layout: 'padded',
docs: { description: { component: 'Skill badge for the capabilities section. Hover removes shadow for a tactile press.' } },
},
});
</script>
<Story name="Single Brick">
{#snippet template()}
<TechStackBrick name="SvelteKit" />
{/snippet}
</Story>
<Story name="Grid">
{#snippet template()}
<TechStackGrid skills={['SvelteKit', 'TypeScript', 'Tailwind', 'Bun', 'Figma', 'Storybook', 'Vitest', 'Playwright']} />
{/snippet}
</Story>
```
**Step 9: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/ui/TechStackBrick/ src/shared/ui/index.ts
git commit -m "feat(shared/ui): add TechStackBrick and TechStackGrid components"
```
---
## Task 10: Run all tests and validate Storybook
This is a verification gate — no new code is written, only validation.
**Step 1: Run full unit test suite**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test
```
Expected: All tests pass. Note any failures and fix them in the relevant component task before continuing.
**Step 2: Type-check**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run check
```
Expected: Zero errors. If `svelte-check` reports errors in `.stories.svelte` files related to `defineMeta`, confirm `@storybook/addon-svelte-csf` types are installed:
```bash
bun add -D @storybook/addon-svelte-csf
```
**Step 3: Lint all files**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run lint
```
Expected: Zero errors. Fix any Biome lint warnings (unused imports, etc.) before continuing.
**Step 4: Start Storybook and verify all stories render**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run storybook
```
Visit http://localhost:6006. Confirm all stories appear in the sidebar:
- Shared/Badge — 5 stories
- Shared/Button — 6 stories
- Shared/Card — 4 stories
- Shared/Section — 3 stories
- Shared/Input — 6 stories
- Shared/TechStackBrick — 2 stories
**Step 5: Commit any fixes**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add -A
git commit -m "fix(shared/ui): resolve type errors and lint warnings from verification pass"
```
---
## Task 11: Update HomePage to use the design system
Replace the placeholder `HomePage.svelte` with a layout that demonstrates the design system is wired up and functional. This is not the final portfolio content — it's a foundation smoke test.
**Files:**
- Modify: `/home/ilia/Documents/Projects/allmywork/src/pages/home/ui/HomePage.svelte`
**Step 1: Write HomePage.svelte**
```svelte
<!--
Page: HomePage
Smoke test page that renders key shared/ui components to verify the design system.
To be replaced with real portfolio content in a future plan.
-->
<script lang="ts">
import { Badge } from '$shared/ui/Badge';
import { Button } from '$shared/ui/Button';
import { Card } from '$shared/ui/Card';
import { Container, Section } from '$shared/ui/Section';
import { TechStackGrid } from '$shared/ui/TechStackBrick';
</script>
<Section>
<Container>
<div class="py-20 space-y-12">
<!-- Heading -->
<div>
<h1>allmy.work</h1>
<div class="brutal-border-top pt-4 mt-4">
<p class="text-sm uppercase tracking-wider opacity-60">Portfolio Foundation — Design System Active</p>
</div>
</div>
<!-- Cards row -->
<div class="grid md:grid-cols-3 gap-6">
<Card background="slate" noPadding class="p-6">
<p class="text-sm opacity-60 mb-2">Stack</p>
<p>SvelteKit + Bun</p>
</Card>
<Card background="slate" noPadding class="p-6">
<p class="text-sm opacity-60 mb-2">Styling</p>
<p>Tailwind CSS v4</p>
</Card>
<Card background="slate" noPadding class="p-6">
<p class="text-sm opacity-60 mb-2">Components</p>
<p>Shared UI ready</p>
</Card>
</div>
<!-- Badges -->
<div class="flex gap-3 flex-wrap">
<Badge variant="default">SvelteKit</Badge>
<Badge variant="primary">Brutalist</Badge>
<Badge variant="secondary">FSD</Badge>
<Badge variant="outline">Open Source</Badge>
</div>
<!-- Buttons -->
<div class="flex gap-4 flex-wrap">
<Button variant="primary">View Work</Button>
<Button variant="secondary">About</Button>
<Button variant="outline">Contact</Button>
<Button variant="ghost">Learn More</Button>
</div>
<!-- Tech stack -->
<Card background="white">
<h4 class="mb-6">Technologies</h4>
<TechStackGrid skills={['SvelteKit', 'TypeScript', 'Tailwind', 'Bun', 'Storybook', 'Vitest']} />
</Card>
</div>
</Container>
</Section>
```
**Step 2: Start dev server and verify**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run dev
```
Visit http://localhost:5173. Confirm:
- Ochre-clay background renders
- Fraunces font loads for the h1
- Badge, Button, Card, TechStackGrid components render correctly
- No console errors
**Step 3: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/pages/home/ui/HomePage.svelte
git commit -m "feat(pages/home): wire up design system components in HomePage smoke test"
```
---
## Completion Checklist
Before declaring this plan complete, verify every item:
- [ ] `CLAUDE.md` includes code style section
- [ ] Storybook starts without errors
- [ ] `bun run test` — all tests pass
- [ ] `bun run check` — zero TypeScript errors
- [ ] `bun run lint` — zero Biome errors
- [ ] `bun run dev` — dev server compiles and page renders with ochre background and Fraunces font
- [ ] Storybook shows stories for: Badge, Button, Card, Section, Input, TechStackBrick
- [ ] All components use Svelte 5 runes (`$props`, `$derived`) — no `export let`
- [ ] All components import via FSD aliases (`$shared/...`)
- [ ] All commits follow `prefix(scope): description` convention
---
## Reference Paths
| Resource | Path |
|---|---|
| Figma prototype root | `/home/ilia/Downloads/High-End Portfolio Design System(1)/` |
| Prototype theme.css | `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/styles/theme.css` |
| Prototype components | `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/` |
| glyphdiff Button (reference) | `/home/ilia/Documents/Projects/glyphdiff/src/shared/ui/Button/Button.svelte` |
| glyphdiff stories (reference) | `/home/ilia/Documents/Projects/glyphdiff/src/shared/ui/Button/Button.stories.svelte` |
| Project root | `/home/ilia/Documents/Projects/allmywork/` |
| Shared UI | `/home/ilia/Documents/Projects/allmywork/src/shared/ui/` |
| App CSS | `/home/ilia/Documents/Projects/allmywork/src/app/styles/app.css` |
| Svelte config | `/home/ilia/Documents/Projects/allmywork/svelte.config.js` |
| FSD aliases | `$shared`, `$pages`, `$features`, `$entities`, `$widgets` |