Files
yellowjacket/.planning/phases/06-sql-consolidation-code-quality/06-02-PLAN.md
T

254 lines
10 KiB
Markdown

---
phase: 06-sql-consolidation-code-quality
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
- backend/events/events.go
- backend/events/cmd/genevents/main.go
- frontend/src/events.ts
- lefthook.yml
autonomous: true
requirements: [QUAL-02]
must_haves:
truths:
- "Running `go generate ./backend/events/...` produces frontend/src/events.ts that exactly matches the Go constants"
- "The generated events.ts includes LibraryConfigChanged (currently missing from hand-maintained TS file)"
- "The codegen-check pre-commit hook detects stale events.ts and fails"
- "Output is deterministic — running the generator twice produces identical output"
artifacts:
- path: "backend/events/cmd/genevents/main.go"
provides: "Go→TypeScript event constant generator"
contains: "go/ast"
- path: "backend/events/events.go"
provides: "go:generate directive for event codegen"
contains: "go:generate"
- path: "frontend/src/events.ts"
provides: "Generated TypeScript event constants"
contains: "LibraryConfigChanged"
key_links:
- from: "backend/events/events.go"
to: "frontend/src/events.ts"
via: "go:generate directive running genevents"
pattern: "go:generate go run"
- from: "lefthook.yml"
to: "go generate"
via: "codegen-check pre-commit hook"
pattern: "go generate"
---
<objective>
Build a Go code generator that reads event constants from `backend/events/events.go` using `go/ast` and produces `frontend/src/events.ts`, then wire it into `go generate` and the pre-commit hook.
Purpose: Eliminate manual synchronization of event names between Go and TypeScript. The generator automatically catches drift (like the missing `LibraryConfigChanged`) and the pre-commit hook prevents stale files from being committed.
Output: Generator tool, `//go:generate` directive, updated events.ts with missing constant, working codegen-check hook.
</objective>
<execution_context>
@/home/caleb/.config/opencode/get-shit-done/workflows/execute-plan.md
@/home/caleb/.config/opencode/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/06-sql-consolidation-code-quality/06-RESEARCH.md
@backend/events/events.go
@frontend/src/events.ts
@lefthook.yml
<interfaces>
<!-- Current event constants for reference -->
From backend/events/events.go (21 constants in 5 groups):
```go
// Playback events (backend → frontend push).
const (
PlaybackStateChanged = "PlaybackStateChanged"
PlaybackFinished = "PlaybackFinished"
TrackChanged = "TrackChanged"
SeekFailed = "SeekFailed"
VolumeChanged = "VolumeChanged"
)
// Queue events (backend → frontend push).
const (
QueueChanged = "QueueChanged"
QueueIndexChanged = "QueueIndexChanged"
QueueModeChanged = "QueueModeChanged"
QueueTracksModified = "QueueTracksModified"
)
// Config events.
const (
LibraryConfigChanged = "LibraryConfigChanged" // <-- MISSING from TS
ThemeConfigChanged = "ThemeConfigChanged"
TrackListConfigChanged = "TrackListConfigChanged"
FavoritesConfigChanged = "FavoritesConfigChanged"
)
// Playlist events.
const (
PlaylistCreated = "PlaylistCreated"
PlaylistDeleted = "PlaylistDeleted"
PlaylistRenamed = "PlaylistRenamed"
PlaylistTracksChanged = "PlaylistTracksChanged"
PlaylistsRestored = "PlaylistsRestored"
DefaultPlaylistChanged = "DefaultPlaylistChanged"
)
// Library events.
const (
LibraryScanStarted = "LibraryScanStarted"
LibraryScanComplete = "LibraryScanComplete"
)
```
From frontend/src/events.ts (20 constants — missing LibraryConfigChanged):
- Format: `export const Events = { ... } as const;`
- Followed by: `export type EventName = (typeof Events)[keyof typeof Events];`
- Comment groups match Go groups (Playback, Queue, Playlist, Config, Library)
From lefthook.yml:
- codegen-check hook runs `go generate ./...` then checks `git diff --name-only`
- Hook currently hangs per STATE.md but research shows `go generate ./...` now completes in <1s
Existing go:generate directives:
- `backend/app.go:4``//go:generate go tool templ generate`
- `backend/database/database.go:21``//go:generate go tool sqlc generate`
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create event codegen tool</name>
<files>
backend/events/cmd/genevents/main.go
backend/events/events.go
</files>
<action>
1. Create `backend/events/cmd/genevents/main.go` — a standalone Go program (package main) that:
- Uses `go/ast`, `go/parser`, `go/token` to parse `events.go` in the same directory as the source
- Accepts a `-source` flag (path to events.go, default: the events.go file relative to the generator location) and an `-output` flag (path to output .ts file)
- Walks the AST in declaration order (NOT map iteration — deterministic output is critical)
- For each `const` block: extracts the doc comment above the block (e.g., "// Playback events (backend → frontend push).") and each constant name + string value
- Generates TypeScript output matching the current `events.ts` format exactly:
```typescript
// Code generated by genevents from backend/events/events.go. DO NOT EDIT.
export const Events = {
// Playback events (backend → frontend push)
PlaybackStateChanged: "PlaybackStateChanged",
...
} as const;
export type EventName = (typeof Events)[keyof typeof Events];
```
- Preserves comment group separation with blank lines between groups
- Strips the trailing period from Go doc comments (Go convention) for TypeScript comments
- Writes output atomically (write to temp file, then rename)
2. Add `//go:generate` directive to `backend/events/events.go`:
```go
//go:generate go run ./cmd/genevents -source events.go -output ../../frontend/src/events.ts
```
Place it after the package doc comment and before the first const block. Use a relative path from the events package directory to the frontend output.
3. Run `go generate ./backend/events/...` and verify the output matches the expected format.
4. Verify the generated events.ts now includes `LibraryConfigChanged` (the constant missing from the hand-maintained file).
**Key constraint:** AST iteration must be in source declaration order (iterate `f.Decls` directly, NOT collect into a map). This ensures deterministic output so the codegen-check hook doesn't produce false diffs.
</action>
<verify>
<automated>go generate ./backend/events/... && diff <(cat frontend/src/events.ts) <(go run ./backend/events/cmd/genevents -source backend/events/events.go -output /dev/stdout) && echo "Deterministic OK" && grep -q "LibraryConfigChanged" frontend/src/events.ts && echo "Missing constant fixed"</automated>
</verify>
<done>
- Generator exists at backend/events/cmd/genevents/main.go
- `//go:generate` directive added to events.go
- Running `go generate ./backend/events/...` produces valid events.ts
- Output includes all 21 constants (including LibraryConfigChanged)
- Output is deterministic (running twice produces identical files)
- Comment groups match Go source ordering
</done>
</task>
<task type="auto">
<name>Task 2: Wire codegen-check pre-commit hook</name>
<files>
lefthook.yml
</files>
<action>
1. The existing `codegen-check` hook in `lefthook.yml` already runs `go generate ./...` and diffs. Per research, `go generate ./...` now completes in <1 second (previous hanging appears resolved). The hook structure should work as-is with the new event generator wired in.
2. Test the hook end-to-end:
- Run `go generate ./...` and verify it completes quickly (<5 seconds)
- Verify no unstaged changes exist after generation (all generated code is up-to-date)
- Manually introduce a drift: add a test constant to events.go, verify `go generate` updates events.ts, then verify the hook would detect the diff
3. If the hook still hangs (unlikely per research): narrow the `codegen-check` glob to only trigger on event-related files, or split into a separate event-specific check. Update lefthook.yml accordingly.
4. Run the full pre-commit hook to verify all hooks pass:
```bash
LEFTHOOK=1 lefthook run pre-commit
```
Note: If the hook takes >10 seconds, investigate and optimize. Expected: <5s total.
5. Clean up any test changes (remove test constant if added).
**Important:** The hook runs `go generate ./...` which triggers ALL generators (templ, sqlc, events). This is the correct behavior — it ensures all generated code is fresh. The <1s completion time makes this acceptable.
</action>
<verify>
<automated>go generate ./... && test -z "$(git diff --name-only)" && echo "codegen-check would pass"</automated>
</verify>
<done>
- `go generate ./...` completes in <5 seconds
- codegen-check hook detects stale events.ts (adding Go constant without regenerating TS fails the hook)
- All existing pre-commit hooks still pass
- No leftover test changes in the working tree
</done>
</task>
</tasks>
<verification>
```bash
# 1. Generator produces valid output
go generate ./backend/events/...
# 2. Output includes all 21 constants
grep -c ":" frontend/src/events.ts # Should be 21+ (constants + type line)
# 3. LibraryConfigChanged is present
grep "LibraryConfigChanged" frontend/src/events.ts
# 4. Deterministic output
go generate ./backend/events/...
git diff --name-only # Should be empty (no changes on second run)
# 5. Full generate works
go generate ./...
# 6. Frontend typecheck passes with new events.ts
cd frontend && ./node_modules/.bin/tsc --noEmit
# 7. Full build
go build -tags webkit2_41 ./...
```
</verification>
<success_criteria>
- Event codegen tool parses Go constants and generates matching TypeScript
- LibraryConfigChanged gap is automatically fixed
- `go generate` directive wired into events.go
- codegen-check hook works end-to-end (detects drift, passes when clean)
- Frontend TypeScript compiles with generated events.ts
- Output is deterministic across runs
</success_criteria>
<output>
After completion, create `.planning/phases/06-sql-consolidation-code-quality/06-02-SUMMARY.md`
</output>