1976 lines
57 KiB
Markdown
1976 lines
57 KiB
Markdown
# 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` |
|