Phase 02: Core CRUD - 4 plans in 4 waves - 3 parallel-ready (Wave 1-3 sequential), 1 checkpoint - Ready for execution
12 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-core-crud | 02 | execute | 2 |
|
|
true |
|
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.
<execution_context> @/home/tho/.claude/get-shit-done/workflows/execute-plan.md @/home/tho/.claude/get-shit-done/templates/summary.md </execution_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 Task 1: Install dependencies and create preferences store package.json, src/lib/stores/preferences.svelte.ts Install svelte-persisted-store for sticky preferences:npm install svelte-persisted-store
Create preferences store at src/lib/stores/preferences.svelte.ts:
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.
npm install completes without errors.
TypeScript check: npm run check passes.
svelte-persisted-store installed, preferences store created with lastEntryType and showCompleted
EntryCard.svelte - Single entry display:
<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:
<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)
TypeScript check:
npm run checkpasses. Files exist: src/lib/components/EntryCard.svelte, src/lib/components/EntryList.svelte EntryCard shows entry with type indicator and completion toggle. EntryList renders all entries.
<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:
<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
Start dev server:
npm run devOpen 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 Quick capture bar creates entries. Main page shows entry list with proper bottom padding. Type preference persists.
- TypeScript:
npm run checkpasses - Dev server:
npm run devstarts without errors - UI visible: Entry list renders, quick capture bar at bottom
- Create entry: Fill in content, click Add - entry appears in list
- Type indicator: Tasks show checkbox, thoughts show purple badge
- Task toggle: Clicking checkbox marks task complete (strikethrough)
- 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 ✓
<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>