Skip to content

feat(drafts): backfill Aye into existing workspaces + Cmd/Ctrl+Enter to send - #223

Merged
prellr merged 2 commits into
mainfrom
feat/drafts-aye-backfill-send-key
Jun 29, 2026
Merged

prellr merged 2 commits into
mainfrom
feat/drafts-aye-backfill-send-key

Conversation

@prellr

@prellr prellr commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Two small, related Drafts improvements so the Send-turn works in existing (pre-Drafts-deploy) workspaces AND can be triggered by keyboard.

Task 1 — Idempotent boot-time Aye backfill

Aye (the Drafts lead agent) is seeded only on workspace creation (seedAyeAgent, inside the create txn). Workspaces created before the Drafts deploy have no Aye row, so StartDraftTurn derives AyeAgentID(wsId), finds nothing, and 409s with "could not start a turn" (confirmed on prod: 3 existing workspaces, zero Aye agents).

  • New BackfillAyeAgents (in package handler, so it reuses the unexported seedAyeAgent/AyeAgentID verbatim — no duplicated persona/skill content), modeled on the existing one-shot migrateShipHubSecrets boot migrator.
  • Wired as a goroutine in main.go alongside the other boot tasks — runs once on boot, never blocks startup.
  • Owner sourcing: new ListWorkspacesWithOwner query joins each workspace to its owner-role member (DISTINCT ON (w.id), earliest owner). The skill's created_by references "user", so the owner-member's user_id is the right attribution. Workspaces with no owner member are skipped.
  • Idempotent: for each workspace, probe GetAgent(AyeAgentID(wsId)); only seed (in its own transaction) when she's missing. A re-run on every boot is a cheap no-op. Logs the count seeded.

Test: a workspace without Aye gets a correctly-seeded Aye (agent at AyeAgentID + the Drafts skill + the agent_skill link, attributed to the owner); a second run is a no-op (still exactly one agent + one skill link).

Task 2 — Cmd/Ctrl+Enter sends the draft turn

Pressing Cmd+Enter (mac) / Ctrl+Enter triggers the same action as the Send button.

  • The HardBreak gotcha: StarterKit's HardBreak binds Mod-Enter to insert a line break, so a plain window listener would BOTH break AND send. Intercepted at the editor level with a drafts-local Tiptap extension (createDraftSendShortcutExtension) bound to Mod-Enter + Ctrl-Enter; it returns true to consume the event before HardBreak sees it, and runs at priority 1000 so its keymap plugin is ordered ahead of HardBreak's in ProseMirror's stack. The shared createEditorExtensions factory is untouched — the keymap is added only to the drafts editor.
  • Lifted send action: useStartDraftTurn + handleSend (with its in-flight guard) move up to AnnotationDraftEditor (which already owns activeTurnTaskId). DraftSendControl is now presentational (onSend/working/pending props). The same handleSend backs the button, the editor keymap, and a window-level fallback for when focus is outside the editor (e.g. the thread panel) — guarded on editor.isFocused so it never double-fires.
  • The Send button's tooltip now hints the shortcut (⌘↵ / Ctrl+Enter) via the shared platform formatShortcut helper (the "Send" word stays localized; the shortcut glyphs are platform-derived, so no new locale string).

Test: a dedicated send-shortcut unit test asserts both bindings fire onSend and return true (so no hard break is inserted, even when onSend no-ops mid-turn) and that priority > HardBreak's default; the narration test is updated for the presentational DraftSendControl.

Verification

  • Go: go build ./... ✓ · go vet ./... ✓ · gofmt clean ✓ · go test ./internal/handler ✓ (new backfill test + all existing Aye/draft-turn tests pass)
  • TS: pnpm typecheck ✓ (6/6) · pnpm --filter @multica/views exec vitest run ✓ (851 passed) · pnpm --filter @multica/core exec vitest run ✓ (364 passed) · eslint clean on changed files (only the two pre-existing Yjs exhaustive-deps warnings, unchanged)

🤖 Generated with Claude Code

prellr and others added 2 commits June 29, 2026 15:22
Workspaces created before Drafts shipped have no Aye row, so
StartDraftTurn can't resolve AyeAgentID and the Send-turn 409s with
"could not start a turn". Add an idempotent boot-time backfill, modeled
on the migrateShipHubSecrets one-shot migrator: it lists every workspace
with its owner, and for any missing Aye at her deterministic id, runs the
existing seedAyeAgent (verbatim — no duplicated persona/skill content) in
its own transaction.

- New ListWorkspacesWithOwner query sources the owner from the owner-role
  member (the skill's created_by references "user"); workspaces with no
  owner are skipped.
- BackfillAyeAgents lives in package handler so it reuses the unexported
  seedAyeAgent/AyeAgentID; wired as a goroutine in main.go alongside the
  other boot tasks so it never blocks startup.
- Idempotent: a workspace that already has Aye is skipped after one probe,
  so it's safe to run on every boot. Logs the count seeded.

Test: a workspace without Aye gets a correctly-seeded Aye (agent +
skill + agent_skill link, attributed to the owner) after the backfill;
a second run is a no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pressing Cmd+Enter (mac) / Ctrl+Enter triggers the same Send action as
the header button.

The gotcha: StarterKit's HardBreak binds Mod-Enter to insert a line
break, so a plain window listener would BOTH break AND send. Intercept
at the editor level with a drafts-local Tiptap extension
(createDraftSendShortcutExtension) bound to Mod-Enter + Ctrl-Enter; it
returns true to consume the event before HardBreak sees it, and runs at
priority 1000 so its keymap plugin is ordered ahead of HardBreak's. The
shared createEditorExtensions factory is untouched — the keymap is added
only to the drafts editor.

Lift the send action: useStartDraftTurn + handleSend (with its in-flight
guard) move up to AnnotationDraftEditor, which already owns
activeTurnTaskId. DraftSendControl becomes presentational
(onSend/working/pending props). The same handleSend backs the button,
the editor keymap, and a window-level fallback for when focus is outside
the editor (guarded on editor.isFocused to avoid a double-fire).

The Send button's tooltip now hints the shortcut (⌘↵ / Ctrl+Enter) via
the shared platform formatShortcut helper.

Tests: a dedicated send-shortcut unit test asserts both bindings fire
onSend and return true (so no hard break is inserted, even when onSend
no-ops mid-turn) and that priority > HardBreak's default; the narration
test is updated for the presentational DraftSendControl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@prellr
prellr merged commit a01b103 into main Jun 29, 2026
2 checks passed
@prellr
prellr deleted the feat/drafts-aye-backfill-send-key branch June 29, 2026 20:50
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