Skip to content

URL: https://mkdocs.justinsforge.com/memory/general/reference_creatortrack_editor_shortcuts/

CreatorTrack editor shortcuts, /help page, page-link icons

Shipped 2026-07-12 (merge 86487fe on main), plan: memory/plans/creatortrack-editor-shortcuts-help-icons-2026-07-12.md, ledger: comms/results/plan-ledgers/creatortrack-editor-shortcuts-help-icons.md.

Shortcut registry (single source of truth)

lib/editorShortcuts.ts exports EDITOR_SHORTCUTS (grouped, mac+win key arrays). The public page app/(marketing)/help/page.tsx (creatortrack.ai/help, linked from marketing Nav/Footer and Settings > Get help) renders it. When adding/changing a shortcut in the editor, update the registry in the same commit; the help page then stays correct with no further work.

Where handlers live

  • Per-block onKeyDown in components/workspace/BlockList.tsx: inline marks (mod+B/I/U, mod+Shift+S, mod+E, mod+Shift+H), turn-into tables TURN_INTO_MOD_SHIFT (mod+Shift+7/8/9) and TURN_INTO_MOD_ALT (mod+Alt+0..3, guarded with !e.getModifierState("AltGraph") for European layouts), mod+D duplicate, mod+Enter to_do toggle, mod+Shift+V plain-paste flag arming.
  • Capture-phase document listener (same file): mod+Shift+ArrowUp/Down block move (must live there, an existing Shift+Arrow branch preventDefaults first), mod+F find, mod+Shift+M comment, Ctrl+A escalation (block content, then all VISIBLE blocks via visibleBlockIds(); materializes a native range via selectBlocksAsRange so Ctrl+C works).
  • Ctrl+K: components/workspace/InlineToolbar.tsx (pre-existing; needs a selection).
  • Match on e.code (Digit7 etc.) for shifted/alt digits, e.key for letters.

Plain paste (mod+Shift+V)

Keydown arms a one-shot pastePlainRef (500ms TTL, cleared by plain mod+V) because paste events carry no modifier state. onPaste then forces html="" and threads literal: true through pasteBlocksAfter -> parseClipboardToBlocks (components/workspace/editorSelection.ts), which skips the markdown-ish restyle so # x / - x stay literal text. deleteSelection() returns {handled, done}; onPaste awaits done before resolving the anchor (select-all-then-paste used to lose content to the async delete race).

components/workspace/entityLinkIcons.ts: hydrator decorates a[data-entity="page"][data-entity-id] with data-icon-* attrs (CSS ::before, decorative attrs stripped on save). adoptBareInternalLinks also adopts typed/pasted internal URLs, but only stamps entity attrs after /api/node/:id confirms a PAGE_LIKE type (tasks/projects share the same URL shape; stamping blind corrupted saved content with data-entity="page" on task links). Negative cache is a 5-min TTL Map. VersionHistoryModal runs the same client hydrator on its preview; there is deliberately NO SSR stamping layer (was dark code, removed).

Gotchas

  • Worktree next build fails on the node_modules symlink (Turbopack); build via the deploy script in the main checkout.
  • Playwright for CT QA: /home/justinwieb/.venvs/browser/bin/python3 with chromium.launch(executable_path="/opt/google/chrome/chrome", args=["--no-sandbox"]); use domcontentloaded + selector waits (networkidle never settles on next dev). No node playwright package exists anywhere.
  • Marketing hero: .ctl-hero-visual { order:-1 } in the 920px media query is scoped :not(.ctl-hero-inner--stack); the stacked landing hero keeps copy above the screenshot on mobile.