Skip to content

feat(drafts): foundation — top-level Drafts page + draft entity (slice 0) - #219

Merged
prellr merged 2 commits into
mainfrom
feat/drafts-foundation
Jun 29, 2026
Merged

prellr merged 2 commits into
mainfrom
feat/drafts-foundation

Conversation

@prellr

@prellr prellr commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Slice 0 of Drafts — the foundation. A new top-level Drafts page where a user creates, lists, opens, edits (title + markdown body, autosave), and deletes a draft. No agent, annotations, or co-editing yet — those are slices 1–4; clean seams left for them.

Drafts is the incubation home for Multica's flagship lead agent ("Jane") — a surface where you co-author a living plan with an agent by editing + annotating. This PR is just the skeleton everything hangs off.

What's here (mirrors the Tasks feature end-to-end)

  • Backend: migration 122_draft (own table — workspace_id + owner_user_id [forward-compat naming for future multi-author], title, body, open status enum [default draft, no CHECK so it survives enum drift], timestamps, compound index); sqlc CRUD; draft.go handler (loadDraftForUser loader + the UUID-parsing convention; open-enum default branch); /api/drafts routes; draft:* WS events.
  • Frontend: types + zod schema (parseWithFallback + 5 malformed-response tests) + client; packages/core/drafts/ query/mutation/cache/ws hooks (optimistic, autosave-friendly, wsId-keyed); packages/views/drafts/ DraftsPage master-detail (?draft=<id> search param, matching Tasks' live mechanism) + DraftEditor (ContentEditor + debounced autosave); sidebar nav (NotebookPen, personal nav); web + desktop routes; drafts i18n namespace (en + zh-Hans).

Decisions (from the implementer, all reasonable)

  • ?draft=<id> search param, no [id] route (Tasks' [id] is only a legacy redirect; Drafts has no legacy links to preserve).
  • draft is its own table (modeled on project), not piggybacked on issue like task.
  • Sidebar in personalNav next to Tasks (drafts are owner-scoped).
  • Open status downgrades unknown→draft (enum-drift rule); no DB CHECK.

Test plan

  • make sqlc clean; go build/vet/go test -run Draft pass; typecheck all 6 packages; core 328 + views 804 vitest (incl. 5 new schema + 6 new DraftsPage tests); lint 0 errors. Independently re-verified.
  • Browser: /drafts — create, edit (autosave), switch, delete a Draft.

🤖 Generated with Claude Code

prellr and others added 2 commits June 28, 2026 11:49
Drafts is a new top-level feature: a living markdown document a user
co-authors with an agent. Slice 0 ships only the standalone entity and
single-user CRUD; later slices add the annotation surface, the agent,
Yjs co-editing, and graduation.

Backend mirrors the project/task handler patterns:
- migration 122: standalone `draft` table (workspace_id, owner_user_id,
  title, body, open `status` enum defaulting to 'draft', timestamps) +
  (workspace_id, owner_user_id, updated_at DESC) index
- draft.sql: List/Get/Create/Update(COALESCE narg)/Delete, every query
  scoped to workspace_id + owner_user_id
- draft.go: DraftResponse + CRUD handlers, loadDraftForUser loader
  (UUID-parsing convention), open-enum status downgrade with a default
  branch
- /api/drafts routes next to /api/tasks (GET/POST collection; GET/PUT/
  PATCH/DELETE item) behind RequireWorkspaceMember
- draft:created/updated/deleted WS events
- draft_test.go: CRUD round-trip, partial-patch preservation, bad-UUID
  guards (404 not silent no-op), enum-drift downgrade, cross-owner denial

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirrors the Tasks feature end-to-end. Slice 0 is single-user CRUD: a
top-level Drafts page where a user can create, list, open, edit (title +
markdown body with autosave), and delete a draft. No annotations, agent,
or co-editing yet — those land in later slices and slot into the editor
pane without changing this shell.

Core:
- types/draft.ts: Draft, DraftStatus (open enum), CreateDraftRequest,
  UpdateDraftRequest, ListDraftsResponse
- api/schemas.ts: DraftSchema + ListDraftsResponseSchema + EMPTY fallbacks;
  schemas.test.ts adds malformed-response coverage (missing field, wrong
  type, null array, unknown status downgrade)
- api/client.ts: listDrafts/getDraft/createDraft/updateDraft/deleteDraft,
  every read through parseWithFallback (no bare `as`)
- drafts/: draftListOptions/draftDetailOptions, useCreateDraft/
  useUpdateDraft (optimistic, autosave-friendly)/useDeleteDraft, flat-list
  cache helpers, and onDraft{Created,Updated,Deleted} WS updaters
- realtime/use-realtime-sync.ts: subscribe draft:created/updated/deleted →
  invalidate via the updaters (never write stores)
- paths.ts: drafts() + draftDetail() (?draft=<id>, mirroring taskDetail)

Views:
- drafts/: DraftsPage master-detail (list left, editor right; selection on
  ?draft=<id>), DraftEditor (TitleEditor + ContentEditor autosave, 1s
  debounce) — mirrors the memory artifact editor
- layout/app-sidebar.tsx: Drafts nav entry (NotebookPen) in personalNav
- i18n: drafts namespace (en + zh-Hans) registered in locales bundle +
  resources types; layout nav.drafts label
- web + desktop /drafts routes; @multica/views/drafts + @multica/core/drafts
  package exports
- drafts-page.test.tsx: render/select/create/empty-state coverage

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@prellr
prellr merged commit 6258b3a into main Jun 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant