Discover projects
1@1island/Public project

Dog

A live Fractal execution graph shared by 1island.

19 graph tasks Updated 7/28/2026, 1:44:37 PM

Execution

Project graph

.fractal v1
Live execution graphdog.fractal
main
Voice promptBuild an app about a dog.
Wave 0sequential
0.1done
Lead PRD and architecture approvedruns after dependencies

The lead-authored .fractal/lead-prd.json and validated task DAG are ready.

Wave 1sequential
1.1done
Scaffold Vite + React + TypeScript project with full toolchain and test configruns after dependencies

In the current workspace root, create the complete project scaffold for a Vite + React 18 + TypeScript app named 'pawfolio'. Create: package.json (name 'pawfolio', private, scripts: 'dev': 'vite', 'build': 'tsc -b && vite build', 'test': 'vitest run', 'test:watch': 'vitest'; dependencies: react ^18.3.0, react-dom ^18.3.0; devDependencies: typescript ~5.5, vite ^5.4, @vitejs/plugin-react ^4.3, vitest ^2.1, jsdom ^25, @testing-library/react ^16, @testing-library/user-event ^14, @testing-library/jest-dom ^6, @types/react ^18, @types/react-dom ^18); vite.config.ts using @vitejs/plugin-react and a `test` block with environment 'jsdom', globals true, setupFiles ['src/test/setup.ts']; tsconfig.json (strict true, jsx 'react-jsx', module/target ESNext, moduleResolution 'bundler', types ['vitest/globals', '@testing-library/jest-dom']); index.html with <div id="root"></div> loading /src/main.tsx; src/main.tsx that renders <App /> from './App' into #root with React.StrictMode; a placeholder src/App.tsx exporting a default App component rendering <h1>Pawfolio</h1> (will be replaced later); src/test/setup.ts importing '@testing-library/jest-dom/vitest' and adding an afterEach that calls window.localStorage.clear(); and a .gitignore for node_modules and dist. Then run `npm install`, create a trivial smoke test src/test/smoke.test.ts asserting 1+1===2, and verify `npx vitest run` exits 0. All later tasks rely on these devDependencies, so the dependency list must be complete now; do not leave any TODOs.

Wave 2parallel
2.1done
Create the app stylesheet and visual design tokenswave-2 · runs together

Create src/styles/app.css containing the full visual system for Pawfolio: CSS custom properties on :root for colors (warm cream background #FAF6F0, ink text #2B2320, accent terracotta #C96F4A, positive green #4A8F5C, warning amber #C9932B, card surface #FFFFFF, border #E5DCD2), spacing scale, radius (12px cards), and a system-ui font stack; base reset (box-sizing border-box, margin 0); layout classes: .app-shell (max-width 720px, centered, padding), .tabbar (horizontal tab buttons with an active state using the accent color), .card (surface, radius, subtle shadow, padding), .tile-grid (responsive grid of stat tiles, minmax(150px, 1fr)), .tile (centered stat with large value and small label); form classes: .field (label above input, gap), .field input/select (full width, padded, border, focus ring in accent), .field-error (small warning-amber text), .btn (accent background, white text, radius, hover darken) and .btn-danger (muted red); list classes: .entry-list (unstyled ul), .entry-row (flex row with kind badge, note, timestamp, delete button), .badge (small pill, per-kind background via .badge-walk/.badge-meal/.badge-meds/.badge-play); and an .empty-state class (centered, muted, large emoji). Include a prefers-color-scheme: dark override adjusting the custom properties to a dark warm palette with sufficient contrast. This file must be self-contained — components will only reference these class names, so name them exactly as listed.

2.2done
Implement domain types, validation, and shared fixtureswave-2 · runs together

Create src/types/dog.ts exporting: interface FeedingSchedule { times: string[] } where times are 'HH:MM' 24h strings; interface DogProfile { name: string; breed: string; birthdate: string /* ISO yyyy-mm-dd */; avatar: string /* emoji */; feeding: FeedingSchedule }; type ActivityKind = 'walk' | 'meal' | 'meds' | 'play'; interface ActivityEntry { id: string; kind: ActivityKind; note: string; timestamp: string /* ISO datetime */ }; interface WeightEntry { id: string; kg: number; date: string /* ISO yyyy-mm-dd */ }; interface AppState { dog: DogProfile | null; activities: ActivityEntry[]; weights: WeightEntry[] }; const EMPTY_STATE: AppState = { dog: null, activities: [], weights: [] }; and function validateDogProfile(input: { name: string; breed: string; birthdate: string; avatar: string; feeding: FeedingSchedule }, now: Date): { ok: true; value: DogProfile } | { ok: false; errors: { field: string; message: string }[] } which rejects: empty/whitespace-only name ('Name is required'), birthdate that is not a valid date or is after `now` ('Birthdate must be in the past'), any feeding time not matching /^([01]\d|2[0-3]):[0-5]\d$/ ('Feeding times must be HH:MM'), and empty feeding.times array ('At least one feeding time is required'); trim name and breed on success. Also create src/fixtures/sampleDog.ts exporting sampleDog (a valid DogProfile named 'Biscuit', breed 'Corgi', birthdate '2024-03-15', avatar '🐶', feeding times ['08:00','18:00']), sampleActivities (3 ActivityEntry items with distinct kinds and ISO timestamps), and sampleWeights (3 WeightEntry items with increasing kg). Pure TypeScript only — no React imports, no Date.now() calls inside validation (use the `now` parameter).

2.3done
Verify.Scaffold Project.Harnesswave-2 · runs together

Re-run the acceptance suite against the produced artifact; fail if any test fails.

Wave 3parallel
3.1done
Implement pure care-logic engine (age, weight trend, feeding status, activity summary)wave-3 · runs together

Create src/lib/care.ts importing DogProfile, ActivityEntry, WeightEntry, FeedingSchedule, ActivityKind from '../types/dog'. Export these pure functions, every one taking an explicit `now: Date` where time matters (never call Date.now() or new Date() with no args): (1) computeAgeString(birthdate: string, now: Date): string — whole years and remaining whole months between birthdate and now, formatted '2 years, 3 months', with singulars ('1 year, 1 month'), months-only under a year ('7 months'), and 'Less than a month old' under one month; account for day-of-month so the month count does not overcount before the monthly anniversary. (2) weightTrend(entries: WeightEntry[], windowSize?: number): { direction: 'gaining' | 'losing' | 'stable' | 'insufficient'; deltaKg: number } — sort entries by date ascending, compare the latest entry to the previous one (or to the entry windowSize back when provided); 'insufficient' with deltaKg 0 when fewer than 2 entries; 'stable' when |delta| < 0.1 kg; round deltaKg to 1 decimal. (3) nextFeedingStatus(feeding: FeedingSchedule, now: Date): { label: string; overdue: boolean } — sort feeding.times ascending and compare against now's local HH:MM: if the most recently passed time today is at most 30 minutes in the past, return { label: 'Overdue: HH:MM meal', overdue: true } for that time; otherwise return { label: 'Next meal at HH:MM', overdue: false } using the next upcoming time today, or { label: 'Next meal at HH:MM tomorrow', overdue: false } with the first scheduled time when every time today has passed by more than 30 minutes; empty times array returns { label: 'No feeding schedule', overdue: false }. (4) activitySummary(entries: ActivityEntry[], now: Date): { total: number; byKind: Record<ActivityKind, number> } — counts only entries whose timestamp is within the last 7 * 24 hours before (inclusive) now; byKind always contains all four kinds with 0 defaults. (5) formatTimestamp(iso: string): string — 'Mon DD, HH:MM' local-time display string. Document each function with a short JSDoc block stating its edge-case behavior exactly as specified above so tests can be written against it.

3.2done
Write unit tests for domain types and validationwave-3 · runs together

Create src/types/dog.test.ts using Vitest (describe/it/expect, globals enabled) testing validateDogProfile from './dog' with a fixed `now` of new Date('2026-07-28T12:00:00'): valid input (use fields from src/fixtures/sampleDog) returns ok:true with name and breed trimmed when given padded strings; empty and whitespace-only name returns ok:false containing an error with field 'name' and message 'Name is required'; birthdate '2027-01-01' (future) and 'not-a-date' each return ok:false with field 'birthdate'; feeding time '25:00' and '8:00' (missing leading zero) each return ok:false with field matching feeding; empty feeding.times returns ok:false with message 'At least one feeding time is required'; multiple simultaneous violations return all corresponding errors in one errors array; birthdate exactly equal to today ('2026-07-28') is accepted (not in the future). Also assert EMPTY_STATE has dog null and empty arrays. Do not test storage or care logic here.

3.3done
Implement versioned localStorage repositorywave-3 · runs together

Create src/lib/storage.ts importing AppState and EMPTY_STATE from '../types/dog'. Export: const STORAGE_KEY = 'pawfolio.v1'; function saveState(state: AppState, storage: Storage = window.localStorage): void — serializes { version: 1, state } as JSON under STORAGE_KEY; function loadState(storage: Storage = window.localStorage): AppState — reads STORAGE_KEY and returns the parsed state only if JSON.parse succeeds, version === 1, and the payload structurally matches AppState (dog is null or has string name/breed/birthdate/avatar and feeding.times array of strings; activities and weights are arrays whose items have the required fields with correct primitive types — validate with a local isAppState type guard, filtering out malformed array items rather than rejecting the whole state when the top-level shape is valid); on any failure (missing key, invalid JSON, wrong version, wrong shape) return a fresh deep copy of EMPTY_STATE and never throw; function clearState(storage: Storage = window.localStorage): void — removes STORAGE_KEY; function exportStateJson(storage: Storage = window.localStorage): string — returns the pretty-printed JSON of loadState's result. Always return deep copies (structuredClone) so callers cannot mutate stored references. No React imports.

3.4done
Verify.Design System.Harnesswave-3 · runs together

Re-run the acceptance suite against the produced artifact; fail if any test fails.

Wave 4parallel
4.1done
Write unit tests for the care-logic enginewave-4 · runs together

Create src/lib/care.test.ts using Vitest testing computeAgeString, weightTrend, nextFeedingStatus, activitySummary from './care', constructing all Date values explicitly (no vi.useFakeTimers needed since every function takes `now`). computeAgeString with now = new Date('2026-07-28T12:00:00'): birthdate '2024-03-15' → '2 years, 4 months'; '2025-07-28' (anniversary today) → '1 year'-style output containing '1 year'; '2026-01-10' → months-only string; '2026-07-20' → 'Less than a month old'; '2024-08-05' (monthly anniversary not yet reached) must NOT overcount to '2 years'. weightTrend: [] and single entry → direction 'insufficient'; entries kg 10.0 then 10.05 (dates ascending) → 'stable'; 10.0 → 10.4 → 'gaining' with deltaKg 0.4; 10.4 → 10.0 → 'losing' with deltaKg -0.4; entries provided out of date order must still compare the two latest by date. nextFeedingStatus with feeding times ['08:00','18:00']: now 07:30 → label 'Next meal at 08:00', overdue false; now 08:15 (within 30 min after 08:00) → overdue true and label containing '08:00'; now 12:00 → 'Next meal at 18:00', overdue false; now 19:30 (all passed by >30 min) → label containing '08:00' and 'tomorrow', overdue false; empty times → 'No feeding schedule'. activitySummary with now = new Date('2026-07-28T12:00:00'): an entry timestamped 6 days ago is counted, one 8 days ago is excluded, byKind contains all four kinds with correct counts and zero defaults, and total equals the sum of byKind values.

4.2done
Write unit tests for the localStorage repositorywave-4 · runs together

Create src/lib/storage.test.ts using Vitest testing saveState, loadState, clearState, STORAGE_KEY from './storage' with fixtures from '../fixtures/sampleDog' (jsdom provides window.localStorage; the shared setup file clears it after each test, but also call localStorage.clear() in a beforeEach for independence). Tests: (1) round-trip — saveState of an AppState built from sampleDog/sampleActivities/sampleWeights then loadState returns a deeply equal state; (2) mutation safety — mutating the object returned by loadState (e.g. push into activities) then calling loadState again returns the original unmutated data; (3) missing key — loadState on empty storage returns { dog: null, activities: [], weights: [] }; (4) corrupt JSON — localStorage.setItem(STORAGE_KEY, '{not json!!') then loadState returns the empty state and does not throw; (5) wrong version — a stored payload { version: 99, state: {...valid...} } yields the empty state; (6) malformed items filtered — a stored version-1 payload whose activities array contains one valid entry and one junk object ({ id: 5 }) loads with only the valid entry present and dog intact; (7) clearState removes the key so a subsequent loadState is empty.

4.3done
Build ActivityLog component (activities + weight entries)wave-4 · runs together

Create src/components/ActivityLog.tsx exporting default function ActivityLog({ activities, weights, onAddActivity, onDeleteActivity, onAddWeight }: { activities: ActivityEntry[]; weights: WeightEntry[]; onAddActivity: (kind: ActivityKind, note: string) => void; onDeleteActivity: (id: string) => void; onAddWeight: (kg: number) => void }) using types from '../types/dog' and formatTimestamp from '../lib/care'. Render two .card sections: (1) 'Log activity' — a select labeled 'Activity type' with options walk/meal/meds/play, a text input labeled 'Note', and a .btn 'Add activity' that calls onAddActivity and clears the note; below it a .entry-list of activities sorted newest-first, each .entry-row showing a .badge with the kind (class badge-walk etc.), the note, formatTimestamp(entry.timestamp), and a .btn-danger button with accessible name 'Delete' + the entry note (aria-label `Delete ${note || kind}`) calling onDeleteActivity(entry.id); when activities is empty render an .empty-state with '🐾 No activities yet'. (2) 'Record weight' — a number input labeled 'Weight (kg)' (min 0.1, step 0.1) and a .btn 'Add weight' that calls onAddWeight with the parsed number only when it is a finite number > 0 (otherwise show a .field-error 'Enter a weight above 0'); list existing weights newest-first as 'DATE — X kg'. ID generation for new entries is the parent's job (props only pass kind/note/kg), so this component stays presentational. All controls must have proper label associations for Testing Library.

4.4done
Build Dashboard component with computed care statswave-4 · runs together

Create src/components/Dashboard.tsx exporting default function Dashboard({ dog, activities, weights, now }: { dog: DogProfile; activities: ActivityEntry[]; weights: WeightEntry[]; now: Date }) using computeAgeString, weightTrend, nextFeedingStatus, activitySummary from '../lib/care'. Render: a header .card with the dog's avatar emoji (large), name as an <h2>, and breed; then a .tile-grid of .tile elements: (1) 'Age' tile showing computeAgeString(dog.birthdate, now); (2) 'Weight trend' tile — if weightTrend(weights).direction is 'insufficient' show 'Add 2+ weigh-ins', else show the direction word capitalized plus signed delta e.g. 'Gaining (+0.4 kg)' with the value colored via inline class trend-gaining/trend-losing/trend-stable; (3) 'Feeding' tile showing nextFeedingStatus(dog.feeding, now).label, with a data-overdue attribute set to the overdue boolean; (4) 'Last 7 days' tile showing activitySummary(activities, now).total followed by 'activities', with a small per-kind breakdown line 'walks W · meals M · meds D · play P' using the byKind counts. Accept `now` strictly as a prop (never call new Date() inside this component) so tests control time. Use only class names from src/styles/app.css plus the three trend-* classes (add those three classes to nothing — inline the color with style={} using var(--positive)/var(--warning)/inherit instead, to avoid editing the css file).

4.5done
Build DogProfile form componentwave-4 · runs together

Create src/components/DogProfile.tsx exporting default function DogProfile({ dog, onSave }: { dog: DogProfile | null; onSave: (dog: DogProfile) => void }) using types from '../types/dog'. Render a .card containing a form (aria-label 'Dog profile form') with labeled controls: text input 'Name', text input 'Breed', date input 'Birthdate', a select 'Avatar' offering the emojis 🐶 🐕 🦮 🐩 🐕‍🦺, and a feeding-times editor: a list of time inputs (one per feeding time, default ['08:00','18:00'] for a new dog) with an 'Add feeding time' button and a per-row 'Remove' button (Remove disabled when only one row remains). Initialize fields from the `dog` prop when present (edit mode, submit button label 'Save changes') else blank/new-dog defaults (label 'Create profile'). On submit, call validateDogProfile from '../types/dog' with new Date() as now; if ok, call onSave(result.value) and render a status line 'Profile saved' (role='status'); if not ok, render each error message in a .field-error element adjacent to its field and do NOT call onSave. Every input must be reachable by accessible label text (htmlFor/id pairs) so Testing Library's getByLabelText works. Use only class names defined in src/styles/app.css (.card, .field, .field-error, .btn).

Wave 5sequential
5.1released
Wire App shell: state ownership, persistence, tab navigationruns after dependencies

Replace the placeholder src/App.tsx with the full application shell. Import './styles/app.css', types and EMPTY_STATE from './types/dog', loadState/saveState from './lib/storage', and the three components from './components/DogProfile', './components/ActivityLog', './components/Dashboard'. App holds state via useState<AppState>(() => loadState()) and a tab state 'dashboard' | 'log' | 'profile' (default 'dashboard' when a dog exists, 'profile' otherwise). Persist by calling saveState(state) inside a useEffect that runs on every state change. Render an .app-shell with an <h1>Pawfolio 🐾</h1>, a .tabbar with three buttons ('Dashboard', 'Activity log', 'Profile') that set the active tab (aria-current='page' on the active one); when state.dog is null and tab is not 'profile', render an .empty-state card '🐶 Welcome! Create your dog's profile to get started.' with a .btn 'Create profile' that switches to the profile tab. Handlers passed down: onSave(dog) sets state.dog and switches to the dashboard tab; onAddActivity(kind, note) prepends { id: crypto.randomUUID(), kind, note, timestamp: new Date().toISOString() }; onDeleteActivity(id) filters it out; onAddWeight(kg) appends { id: crypto.randomUUID(), kg, date: new Date().toISOString().slice(0, 10) }. Dashboard receives now={new Date()} constructed at render time in App (the single place real time enters the UI). Delete the scaffold's src/test/smoke.test.ts (superseded by real suites). Also update index.html's <title> to 'Pawfolio'. Verify the app compiles by running `npx tsc -b` and fix any type errors across the files you integrate (do not change the public signatures of the domain/lib modules).

Wave 6parallel
6.1queued
Write App-level integration tests (create → log → dashboard → persistence)wave-6 · runs together

Create src/App.test.tsx using Vitest + @testing-library/react + @testing-library/user-event testing the default export of './App'. Tests: (1) empty state — render <App />; assert the welcome empty-state text about creating a profile is shown. (2) validation surface — navigate to the Profile tab, submit the profile form with an empty name, assert 'Name is required' appears and no dog header is rendered. (3) create profile happy path — fill Name 'Biscuit', Breed 'Corgi', Birthdate '2024-03-15' via getByLabelText, submit, and assert the app switches to the dashboard showing 'Biscuit' and an Age tile. (4) log activity round-trip — after creating the profile, go to 'Activity log', choose type 'walk', type note 'Morning loop', click 'Add activity'; assert the entry row with 'Morning loop' appears; go to Dashboard and assert the 'Last 7 days' tile shows 1; return to the log, click the delete button whose accessible name includes 'Morning loop', and assert the row is removed. (5) persistence across reload — create the profile, then unmount() the rendered App, render a fresh <App /> without clearing localStorage, and assert 'Biscuit' is displayed on the dashboard (loadState restored it); then simulate corruption by localStorage.setItem('pawfolio.v1', 'garbage'), render a fresh <App />, and assert the welcome empty state renders instead of throwing. Use userEvent.setup() per test and findBy* queries for post-interaction assertions to avoid act warnings. Note the shared setup file clears localStorage after each test — within test 5 do all reload steps inside the single test.

6.2queued
Write README with product overview, run and test instructionswave-6 · runs together

Create README.md at the workspace root documenting Pawfolio: a one-paragraph product description (offline dog-care companion: profile, activity log, weight tracking, care dashboard); a 'Getting started' section with the exact commands `npm install`, `npm run dev` (Vite dev server), `npm run build`, and `npm test` (Vitest); a 'Features' bullet list mapping to the acceptance criteria (validated dog profile, persistent localStorage data that survives reload and tolerates corruption, dashboard with computed age / weight trend / next-feeding status / 7-day activity summary, activity logging with delete); a short 'Architecture' section naming the layers (pure domain in src/types and src/lib, presentational components in src/components, state-owning shell in src/App.tsx, versioned storage under key 'pawfolio.v1') and noting that all time-dependent logic takes an explicit `now: Date` for deterministic testing; and a 'Non-goals' section (no backend/accounts/sync, single dog only, no medical advice). Match the actual file layout and script names from package.json exactly.

Wave 7sequential
7.1queued
Run the full native test suite and type check; fix any failuresruns after dependencies

From the workspace root run `npm install` (ensure dependencies are present) then `npx tsc -b` and `npx vitest run`. All test files (src/types/dog.test.ts, src/lib/storage.test.ts, src/lib/care.test.ts, src/App.test.tsx) must pass and the type check must emit no errors — this verifies acceptance criteria AC-1 through AC-5. If any test or type error fails, diagnose and fix the implementation or, only when a test contradicts the specified behavior in fractal-plan.json's acceptance criteria, correct the test to match the specified behavior; then rerun until `npx vitest run` exits 0. Finish by running `npm run build` and confirming it produces a dist/ directory without errors. Report the final vitest summary (files/tests passed) as the task output.

Wave 8sequential
8.1queued
Lead acceptance review and closeoutruns after dependencies

Review the finished implementation against .fractal/lead-prd.json. Inspect the changes and verification evidence, run any final checks needed, then write .fractal/closeout.json with schema fractal.closeout.v1, status approved, a non-empty summary, an acceptance array containing every PRD acceptance id with passed=true and concrete evidence, and a risks array. Do not approve if any criterion is unsupported.

Contribute

Open work

5 visible tasks
app_integration
Wire App shell: state ownership, persistence, tab navigationWave 5 · sequentialReplace the placeholder src/App.tsx with the full application shell. Import './styles/app.css', types and EMPTY_STATE from './types/dog', loadState/saveState from './lib/storage', and the three components from './components/DogProfile', './components/ActivityLog', './components/Dashboard'. App holds state via useState<AppState>(() => loadState()) and a tab state 'dashboard' | 'log' | 'profile' (default 'dashboard' when a dog exists, 'profile' otherwise). Persist by calling saveState(state) inside a useEffect that runs on every state change. Render an .app-shell with an <h1>Pawfolio 🐾</h1>, a .tabbar with three buttons ('Dashboard', 'Activity log', 'Profile') that set the active tab (aria-current='page' on the active one); when state.dog is null and tab is not 'profile', render an .empty-state card '🐶 Welcome! Create your dog's profile to get started.' with a .btn 'Create profile' that switches to the profile tab. Handlers passed down: onSave(dog) sets state.dog and switches to the dashboard tab; onAddActivity(kind, note) prepends { id: crypto.randomUUID(), kind, note, timestamp: new Date().toISOString() }; onDeleteActivity(id) filters it out; onAddWeight(kg) appends { id: crypto.randomUUID(), kg, date: new Date().toISOString().slice(0, 10) }. Dashboard receives now={new Date()} constructed at render time in App (the single place real time enters the UI). Delete the scaffold's src/test/smoke.test.ts (superseded by real suites). Also update index.html's <title> to 'Pawfolio'. Verify the app compiles by running `npx tsc -b` and fix any type errors across the files you integrate (do not change the public signatures of the domain/lib modules).
ReleasedClaim task
app_integration_tests
Write App-level integration tests (create → log → dashboard → persistence)Wave 6 · parallelCreate src/App.test.tsx using Vitest + @testing-library/react + @testing-library/user-event testing the default export of './App'. Tests: (1) empty state — render <App />; assert the welcome empty-state text about creating a profile is shown. (2) validation surface — navigate to the Profile tab, submit the profile form with an empty name, assert 'Name is required' appears and no dog header is rendered. (3) create profile happy path — fill Name 'Biscuit', Breed 'Corgi', Birthdate '2024-03-15' via getByLabelText, submit, and assert the app switches to the dashboard showing 'Biscuit' and an Age tile. (4) log activity round-trip — after creating the profile, go to 'Activity log', choose type 'walk', type note 'Morning loop', click 'Add activity'; assert the entry row with 'Morning loop' appears; go to Dashboard and assert the 'Last 7 days' tile shows 1; return to the log, click the delete button whose accessible name includes 'Morning loop', and assert the row is removed. (5) persistence across reload — create the profile, then unmount() the rendered App, render a fresh <App /> without clearing localStorage, and assert 'Biscuit' is displayed on the dashboard (loadState restored it); then simulate corruption by localStorage.setItem('pawfolio.v1', 'garbage'), render a fresh <App />, and assert the welcome empty state renders instead of throwing. Use userEvent.setup() per test and findBy* queries for post-interaction assertions to avoid act warnings. Note the shared setup file clears localStorage after each test — within test 5 do all reload steps inside the single test.
ReadyClaim task
lead_closeout
Lead acceptance review and closeoutWave 8 · sequentialReview the finished implementation against .fractal/lead-prd.json. Inspect the changes and verification evidence, run any final checks needed, then write .fractal/closeout.json with schema fractal.closeout.v1, status approved, a non-empty summary, an acceptance array containing every PRD acceptance id with passed=true and concrete evidence, and a risks array. Do not approve if any criterion is unsupported.
ReadyClaim task
project_docs
Write README with product overview, run and test instructionsWave 6 · parallelCreate README.md at the workspace root documenting Pawfolio: a one-paragraph product description (offline dog-care companion: profile, activity log, weight tracking, care dashboard); a 'Getting started' section with the exact commands `npm install`, `npm run dev` (Vite dev server), `npm run build`, and `npm test` (Vitest); a 'Features' bullet list mapping to the acceptance criteria (validated dog profile, persistent localStorage data that survives reload and tolerates corruption, dashboard with computed age / weight trend / next-feeding status / 7-day activity summary, activity logging with delete); a short 'Architecture' section naming the layers (pure domain in src/types and src/lib, presentational components in src/components, state-owning shell in src/App.tsx, versioned storage under key 'pawfolio.v1') and noting that all time-dependent logic takes an explicit `now: Date` for deterministic testing; and a 'Non-goals' section (no backend/accounts/sync, single dog only, no medical advice). Match the actual file layout and script names from package.json exactly.
ReadyClaim task
run_full_test_suite
Run the full native test suite and type check; fix any failuresWave 7 · sequentialFrom the workspace root run `npm install` (ensure dependencies are present) then `npx tsc -b` and `npx vitest run`. All test files (src/types/dog.test.ts, src/lib/storage.test.ts, src/lib/care.test.ts, src/App.test.tsx) must pass and the type check must emit no errors — this verifies acceptance criteria AC-1 through AC-5. If any test or type error fails, diagnose and fix the implementation or, only when a test contradicts the specified behavior in fractal-plan.json's acceptance criteria, correct the test to match the specified behavior; then rerun until `npx vitest run` exits 0. Finish by running `npm run build` and confirming it produces a dist/ directory without errors. Report the final vitest summary (files/tests passed) as the task output.
ReadyClaim task