--- 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" --- 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. @/home/caleb/.config/opencode/get-shit-done/workflows/execute-plan.md @/home/caleb/.config/opencode/get-shit-done/templates/summary.md @.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 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` Task 1: Create event codegen tool backend/events/cmd/genevents/main.go backend/events/events.go 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. 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" - 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 Task 2: Wire codegen-check pre-commit hook lefthook.yml 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. go generate ./... && test -z "$(git diff --name-only)" && echo "codegen-check would pass" - `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 ```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 ./... ``` - 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 After completion, create `.planning/phases/06-sql-consolidation-code-quality/06-02-SUMMARY.md`