Files
taskplaner/.planning/phases/02-core-crud/02-02-PLAN.md
Thomas Richter 457cd407a8 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
2026-01-29 05:27:31 +01:00

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
02-01
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
true
truths artifacts key_links
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
path provides min_lines
src/lib/components/EntryList.svelte Entry list rendering with ordering 20
path provides min_lines
src/lib/components/EntryCard.svelte Single entry display with type indicator 30
path provides min_lines
src/lib/components/QuickCapture.svelte Bottom capture bar with form 40
path provides contains
src/lib/stores/preferences.svelte.ts Persisted user preferences store lastEntryType
path provides contains
src/routes/+page.svelte Main page composing components EntryList
from to via pattern
src/routes/+page.svelte src/lib/components/EntryList.svelte component import import EntryList
from to via pattern
src/lib/components/QuickCapture.svelte src/routes/+page.server.ts form action ?/create action=.*?/create
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.

<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

Task 2: Create EntryCard and EntryList components src/lib/components/EntryCard.svelte, src/lib/components/EntryList.svelte Create src/lib/components directory if needed.

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 check passes. 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.
Task 3: Create QuickCapture component and integrate into main page src/lib/components/QuickCapture.svelte, src/routes/+page.svelte **QuickCapture.svelte** - Bottom capture bar:
<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 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 Quick capture bar creates entries. Main page shows entry list with proper bottom padding. Type preference persists.
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 ✓

<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>
After completion, create `.planning/phases/02-core-crud/02-02-SUMMARY.md`