Writing Experience — A1 design (author-facing manuscript editor)
Status: design / inventory complete (2026-07-02). Workstream A of COMPETITIVE_LANDSCAPE_STORY_SOFTWARE.md §5. Directive (user): best-in-class writing experience, built off the existing HiveJournal writing surfaces as much as possible — not a greenfield editor. Audience (D3, resolved): external creators (polished, self-serve).
The one-sentence thesis
We don't need a new editor app — we need to give the season/episode model (which already carries the bible, chapters, versioned prose, and AI refinement, and feeds the entire audiobook/screenplay/video pipeline) a creator-facing editor, and graft onto it the writing environment the journal composer already nails. Then we beat Sudowrite/Novelcrafter not on prose polish but on "consistency is free" — the bible is at hand, inline AI is bible-grounded, and JQ can answer questions about the open manuscript.
What the inventory found (the surfaces we build on)
Full inventory is in the PR that introduced this doc; the load-bearing facts:
- No rich-text editor library exists. Every writing surface is a plain
<textarea>(journal composer, rehearsal room, chat) or a custom JSON editor (outline tree, graphic-novel panels).react-markdownis render-only. There is no TipTap/Lexical/Slate/ProseMirror dependency. → An editor-tech decision is required (see below). - The journal entry composer (
apps/frontend/src/app/dashboard/entries/new/page.tsx) is the best environment substrate: proven autosave (POST /api/journalat ≥10 chars), localStorage draft persistence, focus-mode chrome (auto-hide header while typing), font/size picker, tags,@mentions, and the write.cafe backdrop/word-count integration. All plain-textarea based. - Chapter prose is VIEW-ONLY today. In the seasons admin, prose is produced
by generate-take → critique → promote (
ChapterTakesModal.tsx,ProseVersionsModal.tsx); there is no direct prose editing and no creator-facing surface at all. This is the core gap A1 fills. - The refinement + history machinery already exists and is reusable wholesale:
chapter_takes(draft/live/archived generations), the critique loop (episode-take-critiques), andprose_versions(migration 321 — full before/after audit withoperationincl. amanual_editvalue already defined).prose_versionsis a ready-made manuscript edit-history spine. - Bible + canon intelligence is done —
StoryBibleModal.tsxwith the Current/Editor/History tabs plus the new 📍 Mentions / 📈 Progression / 🔗 Relationships read-layer tabs, and JQ canon mode (ask/edit canon conversationally, prose-groundedcanon_health). This is the "bible at hand." - Shared plumbing:
apiRequest+ Supabase bearer auth (lib/api.ts); Tailwind with the ember/emberkiln palette;ui/Textarea,ui/Button, etc.
The two real decisions — RESOLVED (2026-07-02)
- D-A → Season/episode model. A manuscript is a season; chapters are
story_episodes.chapter_prose. - D-B → Adopt TipTap now (not textarea-v1). The user chose best-in-class
from the start. TipTap (ProseMirror) is the editor substrate. Constraint:
the pipeline (audio/screenplay/scene) reads
chapter_proseas plain text, so the editor must serialize to plain text / lightweight markdown on save — TipTap is the editing UX,chapter_prosestays plain-text canonical. - Sequencing: external-creator ownership — ✅ shipped (A1a-auth). The
investigation found
story_seasons.owner_user_idalready exists (migration 134) AND the studio/manuscript create path already sets it to the uploader (manuscript.ts:359) — so no migration was needed. The gap was only that the new prose endpoints (:episodeId/:seasonIdparams) bypassed the existingrouter.param('id')ownership gate. Fixed withseason-access.ts(canEditSeasonProse, strict own-or-super, golden-tested) + anassertSeasonProseAccessguard on GET/PATCH prose + prose-assist. RLS-policy rewrite deferred — it's dormant + not the enforcement layer (frontend never hits these tables directly; backend uses the service-role client), so it's defense-in-depth for a future direct-client path, not a blocker.
D-A. Data home — season/episode model (chosen) vs. journal notebook
- Recommendation: the season/episode model. A manuscript IS a season;
chapters ARE
story_episodes.chapter_prose. It already has the bible, the version history (prose_versions), the AI refinement (takes/critique), and — critically — it is what the audiobook/screenplay/scene pipeline reads. Writing here means the creator's words flow straight into the moat. The journal notebook path (amanuscriptnotebook type) is simpler but a dead end: it doesn't connect to the bible or the production pipeline, so we'd rebuild all of that later. - Consequence: A1 needs creator ownership + RLS on seasons (today seasons are super-admin/creator-gated). External creators own their seasons. This is the main net-new backend work.
D-B. Editor tech — TipTap (chosen)
- Chosen: TipTap now. ProseMirror-based, React bindings, extensible (the selection-toolbar + inline-AI extensions in A2 hang off it cleanly, and ghost-text autocomplete becomes possible later). Reuse the journal composer's environment (autosave cadence, focus-mode chrome, fonts) around it.
- Serialization constraint:
chapter_prosestays plain text (the audio/screenplay/scene pipeline reads it). Configure TipTap with a minimal StarterKit and serialize to plain text / lightweight markdown on save; hydrate the editor from that text on load. The rich layer is editing-time only.
A1 architecture (reuse map)
| Capability | Reuse | Net-new |
|---|---|---|
| Writing environment (autosave, draft, focus chrome, fonts, word count) | journal composer patterns + write.cafe pill | lift into a creator editor route |
| Manuscript + chapters data | story_seasons / story_episodes.chapter_prose | creator ownership + RLS |
| Direct prose editing (the gap) | PATCH prose + prose_versions (manual_edit) | a save endpoint that writes prose + a version row |
| Edit history / restore | prose_versions + ProseVersionsModal patterns | surface it creator-side |
| Bible at hand | StoryBibleModal + Mentions/Progression/Relationships tabs | dock it as an editor side panel |
| Inline AI actions (rewrite/expand/continue/describe) | the LLM shim + bible context (buildBiblePromptBlock) | selection→action endpoint + floating toolbar |
| Deeper refinement | chapter_takes + critique loop | creator-facing entry points |
| Chat-with-your-manuscript | JQ canon mode (already season-scoped!) | scope JQ to the open chapter + a "ask about this" affordance |
| Auth/API/design system | apiRequest, Tailwind ember palette, ui/* | — |
Phasing
- A1a — Direct prose editing (TipTap). ✅ Shipped (first slice): a
TipTap editor (
ProseEditorModal.tsx) bound to a chapter's live prose, opened via "✏ Edit prose" inChapterTakesModal.GET/PATCH /api/story-seasons/episodes/:id/prose— the PATCH snapshots a versionedmanual_edit(mirrors promote-take, revertable from Prose History), writeschapter_prose, and best-effort rebuilds audio segments. Serializes TipTap → plain text on save so the pipeline stays happy. Gated to current creator/super-admin access. ✅ Creator route shipped: external creators reach the editor on their own stories via/studio/write(writer hub — lists the caller's seasons viaGET /api/story-seasons/mine) →/studio/write/[seasonId](chapter list via the owner-gatedGET /api/story-seasons/:seasonId/editor-chapters) → opensProseEditorModalper chapter. All owner-gated (assertSeasonProseAccess, own-or-super) — no/dashboard/adminneeded. A "Write" nav item lives in the Studio chrome. (External-creator ownership/RLS on seasons already shipped as A1a-auth.) ✅ Composer environment shipped (A1a-env): debounced autosave (1.5s idle; client-side unchanged check skips redundant PATCH/version snapshots; close flushes dirty content; ⌘/Ctrl-S force-saves) + a save-status pill · a distraction-free focus mode (hides bible + Voice/Ask JQ/Bible chrome, narrows to a centered measure; Esc exits) · serif/sans + size controls persisted in localStorage. A1a is complete. ✅ Chapter management shipped: the creator surface can add / rename / reorder / delete chapters (not just edit imported ones) — in-editor ‹ N of M › navigation (flush-saves before switching) + owner-gated endpointsPOST /:seasonId/chapters,PATCH /episodes/:episodeId/chapter-meta,DELETE /episodes/:episodeId/chapter(delete + contiguous renumber; FK children cascade),PATCH /:seasonId/chapters/order(two-pass renumber). New chapters use the manuscript-import convention (triggered+public) so they flow through render/publish uniformly. No migration. - A1b — Bible side panel. ✅ Shipped:
BibleReferencePanel.tsxdocked in the editor (toggle "📖 Bible") — a scannable Characters/Settings reference (name, physical, current state, voice tics, relationships) with a filter and click-a-name-to-insert-at-cursor (spelling consistency). The Novelcrafter "codex at hand" feature. Reference-only; the full audit tabs (Mentions / Progression / Relationships) + editing stay in the Story Bible modal + JQ canon mode. - A2 — Inline AI actions. ✅ Shipped:
InlineAiToolbar.tsx— select a passage → a floating toolbar offers Rewrite / Expand / Describe / Continue; Rewrite/Expand/Describe replace the selection, Continue appends. BackendPOST /api/story-seasons/:seasonId/prose-assistgrounds every action in the bible viabuildBiblePromptBlock(claude-haiku-4-5,feature_key prose_assist.<action>). Nothing persists until the writer saves. The inline, low-latency counterpart to the chapter critique loop. - A3 — Chat-with-your-manuscript. ✅ Shipped: a "💬 Ask JQ" button in the
editor dispatches the existing
jq:open-canonevent with this story'sseasonId→ JQ opens in canon mode (bible-grounded), above the editor (z-1001), so the writer can chat while writing. If a passage is selected, it's passed as a seed message (Chatbot pre-fills the input). ✅ Deepened + creator-unlocked: canon mode was super-admin-only (Phase 1); it's now own-or-super, so external creators get JQ grounded in their own story — read, the canon edit/health tools, AND the open chapter's live prose injected into context (canon_episode_idscope, migration 447; validated to belong to the scoped season so no cross-story leak). "What did Evelyn know here?" now reads the actual chapter, not just the bible. Gating flows through a newcanAccessCanonScope(chat create +generateChatResponse+executeCanonTool); universe canon stays super-admin-only. - A4 — Prose-quality bar. ✅ Investigated — see PROSE_QUALITY_BENCHMARK.md. Decision: don't chase a "beat Muse on one paragraph" engine rewrite (a losing bet vs a fiction-tuned model); instead ship Style Examples (voice exemplar) + a sentence-rhythm critic, stand up an eval harness to choose the draft-model tier from data, and keep the consistency + pipeline moat. Recommendations filed as tasks.
Competitive framing
Don't try to out-prose Sudowrite's Muse head-on (their whole company). Win on what our architecture makes free: the bible is live, inline AI is bible-grounded, and JQ answers questions about the open manuscript — because we generate from canon rather than bolting it on. That is a writing-experience advantage the drafting tools structurally can't match. See the competitive doc §1.
Open questions / risks
- Creator ownership model. Seasons are creator/super-admin gated today. External-creator ownership + RLS + per-user quotas is the biggest net-new piece and should be specced before A1a. (Ties to the BYOK/model-choice item in §4.)
- Migration surface. Direct-edit + versions reuse existing tables; ownership likely needs a migration (owner_user_id on seasons if absent + RLS).
- Two front doors. Journaling (consumer) and manuscript drafting (creator) are different products sharing an environment. Keep the creator editor its own route; don't overload the journal composer.
- TipTap v2 trigger. Reassess after A2 — if selection-UX or ghost-text autocomplete is limiting, adopt TipTap (plain-text/markdown serialization keeps data compatible; no migration).