docs(02): create phase plan
Phase 02: Core CRUD - 4 plans in 4 waves - 3 parallel-ready (Wave 1-3 sequential), 1 checkpoint - Ready for execution
This commit is contained in:
368
.planning/phases/02-core-crud/02-02-PLAN.md
Normal file
368
.planning/phases/02-core-crud/02-02-PLAN.md
Normal file
@@ -0,0 +1,368 @@
|
||||
---
|
||||
phase: 02-core-crud
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: [02-01]
|
||||
files_modified:
|
||||
- src/lib/components/EntryList.svelte
|
||||
- src/lib/components/EntryCard.svelte
|
||||
- src/lib/components/QuickCapture.svelte
|
||||
- src/lib/stores/preferences.svelte.ts
|
||||
- src/routes/+page.svelte
|
||||
- package.json
|
||||
autonomous: true
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "User sees a list of entries on the main page"
|
||||
- "User can type in quick capture bar and create an entry"
|
||||
- "Entries show type indicator (task vs thought)"
|
||||
- "Quick capture bar is fixed at bottom of screen"
|
||||
artifacts:
|
||||
- path: "src/lib/components/EntryList.svelte"
|
||||
provides: "Entry list rendering with ordering"
|
||||
min_lines: 20
|
||||
- path: "src/lib/components/EntryCard.svelte"
|
||||
provides: "Single entry display with type indicator"
|
||||
min_lines: 30
|
||||
- path: "src/lib/components/QuickCapture.svelte"
|
||||
provides: "Bottom capture bar with form"
|
||||
min_lines: 40
|
||||
- path: "src/lib/stores/preferences.svelte.ts"
|
||||
provides: "Persisted user preferences store"
|
||||
contains: "lastEntryType"
|
||||
- path: "src/routes/+page.svelte"
|
||||
provides: "Main page composing components"
|
||||
contains: "EntryList"
|
||||
key_links:
|
||||
- from: "src/routes/+page.svelte"
|
||||
to: "src/lib/components/EntryList.svelte"
|
||||
via: "component import"
|
||||
pattern: "import EntryList"
|
||||
- from: "src/lib/components/QuickCapture.svelte"
|
||||
to: "src/routes/+page.server.ts"
|
||||
via: "form action ?/create"
|
||||
pattern: "action=.*\\?/create"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Build the main UI components: entry list, entry cards, and quick capture bar.
|
||||
|
||||
Purpose: Users can see their entries and create new ones via the quick capture bar fixed at the bottom of the screen.
|
||||
|
||||
Output: EntryList, EntryCard, QuickCapture components plus preferences store. Main page renders all entries with quick capture.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/tho/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/tho/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/02-core-crud/02-CONTEXT.md
|
||||
@.planning/phases/02-core-crud/02-RESEARCH.md
|
||||
@.planning/phases/02-core-crud/02-01-SUMMARY.md
|
||||
@src/routes/+page.server.ts
|
||||
@src/lib/server/db/schema.ts
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Install dependencies and create preferences store</name>
|
||||
<files>package.json, src/lib/stores/preferences.svelte.ts</files>
|
||||
<action>
|
||||
Install svelte-persisted-store for sticky preferences:
|
||||
|
||||
```bash
|
||||
npm install svelte-persisted-store
|
||||
```
|
||||
|
||||
Create preferences store at src/lib/stores/preferences.svelte.ts:
|
||||
|
||||
```typescript
|
||||
import { persisted } from 'svelte-persisted-store';
|
||||
|
||||
export const preferences = persisted('taskplaner-preferences', {
|
||||
lastEntryType: 'thought' as 'task' | 'thought',
|
||||
showCompleted: false
|
||||
});
|
||||
```
|
||||
|
||||
This store:
|
||||
- Persists to localStorage automatically
|
||||
- Remembers last used entry type for quick capture
|
||||
- Tracks show/hide completed preference
|
||||
|
||||
Create the stores directory if it doesn't exist.
|
||||
</action>
|
||||
<verify>
|
||||
npm install completes without errors.
|
||||
TypeScript check: `npm run check` passes.
|
||||
</verify>
|
||||
<done>svelte-persisted-store installed, preferences store created with lastEntryType and showCompleted</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Create EntryCard and EntryList components</name>
|
||||
<files>src/lib/components/EntryCard.svelte, src/lib/components/EntryList.svelte</files>
|
||||
<action>
|
||||
Create src/lib/components directory if needed.
|
||||
|
||||
**EntryCard.svelte** - Single entry display:
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import type { Entry } from '$lib/server/db/schema';
|
||||
|
||||
interface Props {
|
||||
entry: Entry;
|
||||
}
|
||||
|
||||
let { entry }: Props = $props();
|
||||
</script>
|
||||
|
||||
<article class="p-4 border-b border-gray-100 md:border md:rounded-lg md:shadow-sm md:mb-3">
|
||||
<div class="flex items-start gap-3">
|
||||
{#if entry.type === 'task'}
|
||||
<form method="POST" action="?/toggleComplete" class="flex items-center">
|
||||
<input type="hidden" name="id" value={entry.id} />
|
||||
<button
|
||||
type="submit"
|
||||
class="w-6 h-6 rounded border-2 border-gray-300 flex items-center justify-center touch-target
|
||||
{entry.status === 'done' ? 'bg-green-500 border-green-500' : 'hover:border-gray-400'}"
|
||||
aria-label={entry.status === 'done' ? 'Mark as incomplete' : 'Mark as complete'}
|
||||
>
|
||||
{#if entry.status === 'done'}
|
||||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</form>
|
||||
{:else}
|
||||
<span class="w-6 h-6 rounded-full bg-purple-100 flex items-center justify-center flex-shrink-0">
|
||||
<span class="text-purple-600 text-xs font-medium">T</span>
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-medium text-gray-900 text-base md:text-lg {entry.status === 'done' ? 'line-through text-gray-400' : ''}">
|
||||
{entry.title || 'Untitled'}
|
||||
</h3>
|
||||
<p class="text-gray-600 text-sm md:text-base line-clamp-2 {entry.status === 'done' ? 'text-gray-400' : ''}">
|
||||
{entry.content}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<span class="text-xs px-2 py-1 rounded-full {entry.type === 'task' ? 'bg-blue-100 text-blue-700' : 'bg-purple-100 text-purple-700'}">
|
||||
{entry.type}
|
||||
</span>
|
||||
</div>
|
||||
</article>
|
||||
```
|
||||
|
||||
**EntryList.svelte** - List of entries:
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import type { Entry } from '$lib/server/db/schema';
|
||||
import EntryCard from './EntryCard.svelte';
|
||||
|
||||
interface Props {
|
||||
entries: Entry[];
|
||||
}
|
||||
|
||||
let { entries }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if entries.length === 0}
|
||||
<div class="text-center py-12 text-gray-500">
|
||||
<p class="text-lg">No entries yet</p>
|
||||
<p class="text-sm mt-1">Use the capture bar below to add your first entry</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="divide-y divide-gray-100 md:divide-y-0 md:space-y-3">
|
||||
{#each entries as entry (entry.id)}
|
||||
<EntryCard {entry} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
```
|
||||
|
||||
Key features:
|
||||
- Type indicator: checkbox for tasks, purple "T" badge for thoughts
|
||||
- Type badge in corner (blue for task, purple for thought)
|
||||
- Completed tasks have strikethrough
|
||||
- Mobile compact (border-b), desktop cards (rounded, shadow)
|
||||
- Touch-friendly checkbox (44px touch target via touch-target class)
|
||||
</action>
|
||||
<verify>
|
||||
TypeScript check: `npm run check` passes.
|
||||
Files exist: src/lib/components/EntryCard.svelte, src/lib/components/EntryList.svelte
|
||||
</verify>
|
||||
<done>EntryCard shows entry with type indicator and completion toggle. EntryList renders all entries.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Create QuickCapture component and integrate into main page</name>
|
||||
<files>src/lib/components/QuickCapture.svelte, src/routes/+page.svelte</files>
|
||||
<action>
|
||||
**QuickCapture.svelte** - Bottom capture bar:
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { preferences } from '$lib/stores/preferences.svelte';
|
||||
|
||||
let title = $state('');
|
||||
let content = $state('');
|
||||
let type = $state<'task' | 'thought'>('thought');
|
||||
|
||||
// Initialize from preferences (client-side only)
|
||||
$effect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
type = $preferences.lastEntryType;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<form
|
||||
method="POST"
|
||||
action="?/create"
|
||||
use:enhance={() => {
|
||||
return async ({ result, update }) => {
|
||||
if (result.type === 'success') {
|
||||
// Update preference
|
||||
$preferences.lastEntryType = type;
|
||||
// Clear form
|
||||
title = '';
|
||||
content = '';
|
||||
// Let SvelteKit refresh data
|
||||
}
|
||||
await update();
|
||||
};
|
||||
}}
|
||||
class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg safe-bottom"
|
||||
>
|
||||
<div class="max-w-2xl mx-auto p-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<input
|
||||
name="title"
|
||||
bind:value={title}
|
||||
placeholder="Title (optional)"
|
||||
class="w-full px-3 py-2 border border-gray-200 rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<textarea
|
||||
name="content"
|
||||
bind:value={content}
|
||||
placeholder="What's on your mind?"
|
||||
required
|
||||
rows="1"
|
||||
class="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-base resize-none focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
></textarea>
|
||||
<div class="flex flex-col gap-1">
|
||||
<select
|
||||
name="type"
|
||||
bind:value={type}
|
||||
class="px-2 py-1 border border-gray-200 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="thought">Thought</option>
|
||||
<option value="task">Task</option>
|
||||
</select>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded-lg touch-target font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
|
||||
**+page.svelte** - Main page:
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import EntryList from '$lib/components/EntryList.svelte';
|
||||
import QuickCapture from '$lib/components/QuickCapture.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>TaskPlaner</title>
|
||||
</svelte:head>
|
||||
|
||||
<main class="min-h-screen bg-gray-50 pb-40">
|
||||
<header class="bg-white border-b border-gray-200 sticky top-0 z-10">
|
||||
<div class="max-w-2xl mx-auto px-4 py-4">
|
||||
<h1 class="text-xl font-bold text-gray-900">TaskPlaner</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="max-w-2xl mx-auto px-4 py-4">
|
||||
<EntryList entries={data.entries} />
|
||||
</div>
|
||||
|
||||
<QuickCapture />
|
||||
</main>
|
||||
```
|
||||
|
||||
Key features:
|
||||
- Fixed bottom capture bar with safe area insets
|
||||
- Type selector remembers last used type
|
||||
- Clear form on successful submit
|
||||
- Adequate padding at bottom (pb-40) so entries aren't hidden by capture bar
|
||||
- Progressive enhancement: works without JS, enhanced with use:enhance
|
||||
</action>
|
||||
<verify>
|
||||
Start dev server: `npm run dev`
|
||||
Open http://localhost:5173
|
||||
- Entry list should display (may be empty)
|
||||
- Quick capture bar should be fixed at bottom
|
||||
- Creating an entry should work and appear in list
|
||||
- Type preference should stick across page refreshes
|
||||
</verify>
|
||||
<done>Quick capture bar creates entries. Main page shows entry list with proper bottom padding. Type preference persists.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
After all tasks complete:
|
||||
|
||||
1. **TypeScript**: `npm run check` passes
|
||||
2. **Dev server**: `npm run dev` starts without errors
|
||||
3. **UI visible**: Entry list renders, quick capture bar at bottom
|
||||
4. **Create entry**: Fill in content, click Add - entry appears in list
|
||||
5. **Type indicator**: Tasks show checkbox, thoughts show purple badge
|
||||
6. **Task toggle**: Clicking checkbox marks task complete (strikethrough)
|
||||
7. **Type preference**: Select "task", create entry, refresh page - type selector still shows "task"
|
||||
|
||||
Requirements satisfied:
|
||||
- CORE-01: Can create entry ✓
|
||||
- CORE-04: Can specify type ✓
|
||||
- CORE-05: Can mark task complete ✓
|
||||
- CAPT-01, CAPT-02, CAPT-03: Quick capture works ✓
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- EntryList and EntryCard components render entries with type indicators
|
||||
- QuickCapture component creates entries via form action
|
||||
- Type preference persists in localStorage
|
||||
- Main page composes all components with proper layout
|
||||
- TypeScript compilation passes
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-core-crud/02-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user