docs(01): create phase 1 foundation plans

Phase 01: Foundation
- 2 plans in 2 waves
- Wave 1: Project setup with SvelteKit + Drizzle schema
- Wave 2: Repository layer + verification page
- Ready for execution

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Thomas Richter
2026-01-29 04:26:48 +01:00
parent f7634d9956
commit 92e0d5188a
3 changed files with 586 additions and 5 deletions

View File

@@ -31,12 +31,11 @@ Decimal phases appear between their surrounding integers in numeric order.
3. Repository layer provides typed CRUD operations for entries
4. Filesystem storage directory structure exists for future images
5. Development server starts and serves a basic page
**Plans**: TBD
**Plans**: 2 plans
Plans:
- [ ] 01-01: TBD
- [ ] 01-02: TBD
- [ ] 01-03: TBD
- [ ] 01-01-PLAN.md — SvelteKit project setup with Drizzle schema and unified entries table
- [ ] 01-02-PLAN.md — Repository layer with typed CRUD and verification page
### Phase 2: Core CRUD
**Goal**: Users can create, manage, and view entries with a responsive, accessible UI
@@ -135,7 +134,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. Foundation | 0/3 | Not started | - |
| 1. Foundation | 0/2 | Planned | - |
| 2. Core CRUD | 0/4 | Not started | - |
| 3. Images | 0/3 | Not started | - |
| 4. Tags & Organization | 0/3 | Not started | - |

View File

@@ -0,0 +1,226 @@
---
phase: 01-foundation
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- package.json
- tsconfig.json
- svelte.config.js
- vite.config.ts
- src/app.css
- src/lib/server/db/schema.ts
- src/lib/server/db/index.ts
- drizzle.config.ts
autonomous: true
must_haves:
truths:
- "npm run dev starts without errors"
- "TypeScript compilation succeeds"
- "Tailwind CSS classes are processed"
- "Database schema defines unified entries table with type discriminator"
artifacts:
- path: "package.json"
provides: "Project dependencies"
contains: "better-sqlite3"
- path: "src/lib/server/db/schema.ts"
provides: "Drizzle schema with entries table"
contains: "entries"
- path: "src/lib/server/db/index.ts"
provides: "Database connection and initialization"
exports: ["db"]
key_links:
- from: "src/lib/server/db/index.ts"
to: "src/lib/server/db/schema.ts"
via: "import schema"
pattern: "import.*schema"
---
<objective>
Create the SvelteKit project with all core dependencies and establish the database schema using Drizzle ORM.
Purpose: Establishes the foundation for all subsequent development - project structure, tooling, and data model.
Output: Working SvelteKit project with Drizzle schema defining the unified entries table.
</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/research/STACK.md
@.planning/research/ARCHITECTURE.md
@.planning/phases/01-foundation/01-CONTEXT.md
</context>
<tasks>
<task type="auto">
<name>Task 1: Create SvelteKit project with dependencies</name>
<files>
package.json
tsconfig.json
svelte.config.js
vite.config.ts
src/app.html
src/app.css
src/routes/+page.svelte
</files>
<action>
Create a new SvelteKit project using `npx sv create` with the following options:
- Template: SvelteKit minimal
- TypeScript: Yes
- Tailwind CSS: Yes (v4 with @tailwindcss/vite)
- ESLint + Prettier: Yes
After project creation, install additional dependencies:
```bash
npm install better-sqlite3 drizzle-orm nanoid zod
npm install -D drizzle-kit @types/better-sqlite3
```
Configure Tailwind in `src/app.css`:
```css
@import 'tailwindcss';
```
Verify `vite.config.ts` includes Tailwind plugin (sv create should handle this).
Update `src/routes/+page.svelte` to include a simple Tailwind-styled element to verify CSS processing:
```svelte
<h1 class="text-2xl font-bold text-blue-600">TaskPlanner</h1>
<p class="text-gray-600">Foundation setup complete.</p>
```
</action>
<verify>
Run `npm run dev` - server should start on localhost:5173 without errors.
Visit the page - should see styled "TaskPlanner" heading.
Run `npm run check` - TypeScript should pass.
</verify>
<done>
SvelteKit dev server runs, Tailwind processes classes, all dependencies installed.
</done>
</task>
<task type="auto">
<name>Task 2: Configure Drizzle schema with entries table</name>
<files>
src/lib/server/db/schema.ts
src/lib/server/db/index.ts
drizzle.config.ts
</files>
<action>
Create `src/lib/server/db/schema.ts` with the unified entries table:
```typescript
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
// Entry types: 'task' (actionable) or 'thought' (reference)
export const entries = sqliteTable('entries', {
id: text('id').primaryKey(), // nanoid
title: text('title'),
content: text('content').notNull(),
type: text('type', { enum: ['task', 'thought'] }).notNull().default('thought'),
status: text('status', { enum: ['open', 'done', 'archived'] }).default('open'), // for tasks
pinned: integer('pinned', { mode: 'boolean' }).default(false),
dueDate: text('due_date'), // ISO date string, nullable
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
updatedAt: text('updated_at').notNull().$defaultFn(() => new Date().toISOString()),
});
// Type inference for TypeScript
export type Entry = typeof entries.$inferSelect;
export type NewEntry = typeof entries.$inferInsert;
```
Create `src/lib/server/db/index.ts` for database connection:
```typescript
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import * as schema from './schema';
import { existsSync, mkdirSync } from 'fs';
import { dirname } from 'path';
const DB_PATH = process.env.DATABASE_PATH || './data/taskplaner.db';
// Ensure data directory exists
const dbDir = dirname(DB_PATH);
if (!existsSync(dbDir)) {
mkdirSync(dbDir, { recursive: true });
}
const sqlite = new Database(DB_PATH);
// Enable WAL mode for better concurrent read performance
sqlite.pragma('journal_mode = WAL');
export const db = drizzle(sqlite, { schema });
export { schema };
```
Create `drizzle.config.ts` for migrations:
```typescript
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/lib/server/db/schema.ts',
out: './drizzle',
dialect: 'sqlite',
dbCredentials: {
url: process.env.DATABASE_PATH || './data/taskplaner.db',
},
});
```
Add npm scripts to `package.json`:
```json
{
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio"
}
}
```
</action>
<verify>
Run `npm run db:push` - should create database file at `./data/taskplaner.db` with entries table.
Run `sqlite3 ./data/taskplaner.db ".schema"` - should show entries table with all columns.
Import db in a test: create a simple test file that imports and logs `db` without errors.
</verify>
<done>
Drizzle schema defines entries table with id, title, content, type, status, pinned, dueDate, createdAt, updatedAt.
Database file created with schema applied. WAL mode enabled.
</done>
</task>
</tasks>
<verification>
1. `npm run dev` starts without errors
2. `npm run check` passes TypeScript validation
3. `./data/taskplaner.db` exists after `npm run db:push`
4. Database has entries table with correct schema
5. Tailwind classes render correctly on the test page
</verification>
<success_criteria>
- SvelteKit project created with TypeScript and Tailwind 4
- All dependencies installed (better-sqlite3, drizzle-orm, nanoid, zod)
- Drizzle schema defines unified entries table with type discriminator
- Database initializes with WAL mode
- Dev server starts and serves styled page
</success_criteria>
<output>
After completion, create `.planning/phases/01-foundation/01-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,356 @@
---
phase: 01-foundation
plan: 02
type: execute
wave: 2
depends_on: ["01-01"]
files_modified:
- src/lib/server/db/repository.ts
- src/hooks.server.ts
- src/routes/+page.server.ts
- src/routes/+page.svelte
- data/.gitkeep
autonomous: true
must_haves:
truths:
- "Repository provides create, read, update, delete operations for entries"
- "Database initializes automatically on first server request"
- "Data directory structure exists with .gitkeep"
- "Basic page shows database connection status"
artifacts:
- path: "src/lib/server/db/repository.ts"
provides: "EntryRepository with typed CRUD operations"
exports: ["entryRepository", "EntryRepository"]
- path: "src/hooks.server.ts"
provides: "Server hooks for database initialization"
contains: "handle"
- path: "data/.gitkeep"
provides: "Data directory placeholder for git"
key_links:
- from: "src/lib/server/db/repository.ts"
to: "src/lib/server/db/index.ts"
via: "import db"
pattern: "import.*db.*from"
- from: "src/hooks.server.ts"
to: "src/lib/server/db/index.ts"
via: "import db for init"
pattern: "import.*db"
- from: "src/routes/+page.server.ts"
to: "src/lib/server/db/repository.ts"
via: "import repository"
pattern: "import.*repository"
---
<objective>
Create the repository layer for typed CRUD operations and wire up database initialization with a verification page.
Purpose: Provides the data access layer that all future features will use, and verifies the foundation works end-to-end.
Output: Working repository with CRUD operations, auto-initializing database, and a verification page proving everything connects.
</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/phases/01-foundation/01-01-SUMMARY.md
@src/lib/server/db/schema.ts
@src/lib/server/db/index.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: Create EntryRepository with typed CRUD operations</name>
<files>
src/lib/server/db/repository.ts
</files>
<action>
Create `src/lib/server/db/repository.ts` implementing the repository pattern:
```typescript
import { eq, desc } from 'drizzle-orm';
import { db } from './index';
import { entries, type Entry, type NewEntry } from './schema';
import { nanoid } from 'nanoid';
export interface EntryRepository {
create(entry: Omit<NewEntry, 'id' | 'createdAt' | 'updatedAt'>): Entry;
getById(id: string): Entry | undefined;
getAll(options?: { limit?: number; offset?: number }): Entry[];
update(id: string, updates: Partial<Omit<Entry, 'id' | 'createdAt'>>): Entry | undefined;
delete(id: string): boolean;
count(): number;
}
class SQLiteEntryRepository implements EntryRepository {
create(entry: Omit<NewEntry, 'id' | 'createdAt' | 'updatedAt'>): Entry {
const now = new Date().toISOString();
const newEntry: NewEntry = {
...entry,
id: nanoid(),
createdAt: now,
updatedAt: now,
};
db.insert(entries).values(newEntry).run();
return this.getById(newEntry.id)!;
}
getById(id: string): Entry | undefined {
return db.select().from(entries).where(eq(entries.id, id)).get();
}
getAll(options: { limit?: number; offset?: number } = {}): Entry[] {
const { limit = 100, offset = 0 } = options;
return db
.select()
.from(entries)
.orderBy(desc(entries.createdAt))
.limit(limit)
.offset(offset)
.all();
}
update(id: string, updates: Partial<Omit<Entry, 'id' | 'createdAt'>>): Entry | undefined {
const existing = this.getById(id);
if (!existing) return undefined;
db.update(entries)
.set({
...updates,
updatedAt: new Date().toISOString(),
})
.where(eq(entries.id, id))
.run();
return this.getById(id);
}
delete(id: string): boolean {
const existing = this.getById(id);
if (!existing) return false;
db.delete(entries).where(eq(entries.id, id)).run();
return true;
}
count(): number {
const result = db.select().from(entries).all();
return result.length;
}
}
// Singleton instance
export const entryRepository: EntryRepository = new SQLiteEntryRepository();
```
This provides:
- Type-safe CRUD operations using Drizzle ORM
- Automatic ID generation with nanoid
- Automatic timestamp management (createdAt, updatedAt)
- Pagination support via limit/offset
- Singleton pattern for consistent access
</action>
<verify>
Create a quick test by importing the repository:
```typescript
import { entryRepository } from './repository';
const entry = entryRepository.create({ content: 'Test', type: 'thought' });
console.log(entry); // Should have id, createdAt, updatedAt
const retrieved = entryRepository.getById(entry.id);
console.log(retrieved); // Should match entry
entryRepository.delete(entry.id);
```
TypeScript compilation should pass with `npm run check`.
</verify>
<done>
EntryRepository interface and implementation exist with create, getById, getAll, update, delete, count methods.
All methods are type-safe with Drizzle schema types.
</done>
</task>
<task type="auto">
<name>Task 2: Create data directory, server hooks, and verification page</name>
<files>
data/.gitkeep
data/attachments/.gitkeep
src/hooks.server.ts
src/routes/+page.server.ts
src/routes/+page.svelte
</files>
<action>
1. Create data directory structure for persistence:
```bash
mkdir -p data/attachments
touch data/.gitkeep
touch data/attachments/.gitkeep
```
Add to `.gitignore`:
```
# Data directory (persistent data)
data/*.db
data/*.db-wal
data/*.db-shm
data/attachments/*
!data/.gitkeep
!data/attachments/.gitkeep
```
2. Create `src/hooks.server.ts` for database initialization:
```typescript
import type { Handle } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { entries } from '$lib/server/db/schema';
import { sql } from 'drizzle-orm';
// Ensure database tables exist on first request
let initialized = false;
export const handle: Handle = async ({ event, resolve }) => {
if (!initialized) {
// Run a simple query to ensure connection works
// Drizzle with push handles schema creation, but this verifies connectivity
try {
db.select().from(entries).limit(1).all();
initialized = true;
console.log('Database initialized successfully');
} catch (error) {
console.error('Database initialization failed:', error);
// Don't block - let the error propagate to the request
}
}
return resolve(event);
};
```
3. Create `src/routes/+page.server.ts` for verification data:
```typescript
import type { PageServerLoad } from './$types';
import { entryRepository } from '$lib/server/db/repository';
export const load: PageServerLoad = async () => {
const count = entryRepository.count();
// Create a test entry if none exist (for verification)
let testEntry = null;
if (count === 0) {
testEntry = entryRepository.create({
title: 'Foundation Test',
content: 'This entry was created to verify the foundation is working.',
type: 'thought',
});
}
const entries = entryRepository.getAll({ limit: 5 });
return {
dbStatus: 'connected',
entryCount: testEntry ? count + 1 : count,
recentEntries: entries,
};
};
```
4. Update `src/routes/+page.svelte` to show verification status:
```svelte
<script lang="ts">
let { data } = $props();
</script>
<main class="min-h-screen bg-gray-50 p-8">
<div class="max-w-2xl mx-auto">
<h1 class="text-3xl font-bold text-gray-900 mb-2">TaskPlanner</h1>
<p class="text-gray-600 mb-8">Foundation Phase Complete</p>
<div class="bg-white rounded-lg shadow p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">System Status</h2>
<div class="space-y-3">
<div class="flex items-center gap-2">
<span class="w-3 h-3 rounded-full bg-green-500"></span>
<span class="text-gray-700">Database: {data.dbStatus}</span>
</div>
<div class="flex items-center gap-2">
<span class="w-3 h-3 rounded-full bg-green-500"></span>
<span class="text-gray-700">Entries in database: {data.entryCount}</span>
</div>
<div class="flex items-center gap-2">
<span class="w-3 h-3 rounded-full bg-green-500"></span>
<span class="text-gray-700">Repository layer: operational</span>
</div>
</div>
</div>
{#if data.recentEntries.length > 0}
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">Recent Entries</h2>
<ul class="space-y-2">
{#each data.recentEntries as entry}
<li class="p-3 bg-gray-50 rounded">
<div class="font-medium text-gray-900">{entry.title || '(no title)'}</div>
<div class="text-sm text-gray-600">{entry.content}</div>
<div class="text-xs text-gray-400 mt-1">
Type: {entry.type} | Created: {new Date(entry.createdAt).toLocaleString()}
</div>
</li>
{/each}
</ul>
</div>
{/if}
</div>
</main>
```
</action>
<verify>
1. `ls data/` - should show .gitkeep and attachments/
2. `npm run dev` - server starts without errors
3. Visit http://localhost:5173 - page shows:
- "Database: connected"
- "Entries in database: 1" (or more)
- "Repository layer: operational"
- The test entry displayed in "Recent Entries"
4. Check `data/taskplaner.db` exists
5. Run `npm run check` - no TypeScript errors
</verify>
<done>
Data directory structure created with .gitkeep files.
Server hooks initialize database on first request.
Verification page displays database status and entry count.
Full end-to-end data flow works: hooks -> repository -> page.
</done>
</task>
</tasks>
<verification>
1. Repository CRUD operations work (create, read, update, delete)
2. Database auto-initializes on first server request
3. Data directory exists with attachments subdirectory
4. Verification page shows green status for all systems
5. Test entry appears in recent entries list
6. `npm run check` passes
7. `npm run dev` serves the page without errors
</verification>
<success_criteria>
- EntryRepository provides typed create, getById, getAll, update, delete, count operations
- Database initializes automatically via server hooks
- Data directory structure exists (data/, data/attachments/)
- Verification page confirms database connected, entry count, repository operational
- Test entry created and displayed proving end-to-end data flow
</success_criteria>
<output>
After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md`
</output>