Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Writer Studio (standalone writing editor)

Writer Studio is Omniscio's standalone writing surface — a full-screen markdown editor with a documents list, writing guidance, a creativity dial and four AI capabilities, plus how the native surface and the AI Writer plugin relate and how the feature is gated on.

What it is

Writer Studio is a standalone writing surface inside Omniscio, surfaced as a virtual project in the sidebar. It gives you a full-screen Tiptap-powered markdown editor with a saved-documents list, a Writing Guidance panel (instructions that will guide AI editing), and a Creativity Dial (controls how conservative or inventive AI suggestions are).

The foundation ships the core document management loop: create, open, rename, delete (soft-delete with Undo), and auto-save. All four AI capabilities are now shipped: selection edits, options variants, inline autocomplete, and chat assistant. The Writer Studio feature is complete.

AI works out of the box — no API key required. The default model for edits, options, and autocomplete is Luna (GPT-5.6 via OpenRouter), which uses a bundled company-paid key. Chat defaults to Sonnet and resolves credentials API-key-first, then falls back to your signed-in Claude account (run through claude -p on your subscription — the same sanctioned path the Council uses). See "Credentials — three-step cascade" below.

Where to find it

How it is gated

Writer Studio is an in-development feature registered as 'ai-writer' in src/shared/unreleased-features.ts (settingKey: 'aiWriterEnabled', envVar: AMC_SHOW_AI_WRITER, status: 'in-development').

It is visible when any one of the following is true:

  • The feature is marked 'shipped' in the registry (releases to everyone).
  • Omniscio is launched with AMC_SHOW_AI_WRITER=1.
  • The user flips the Settings → Lab → Writer Studio toggle on.

No code reads settings.aiWriterEnabled directly to make a visibility decision — all gates go through isUnreleasedFeatureVisible (main) / isUnreleasedFeatureVisibleInRenderer (renderer). Doing otherwise bypasses the env reveal path and will fail the gating lint test.

Choosing the model

Two dropdowns pick the AI model. Since 2026-07-24 they live in Settings → Plugins → AI Writer, declared in the plugin's own manifest.json:

  • AI model (writerModel) — chat, selection edits and options variants together. Default Automatic (chat uses Sonnet; edits and options use Luna).
  • Autocomplete model (writerAutocompleteModel) — inline autocomplete only. Default Automatic (Luna). Kept separate so a pricier main-model choice cannot silently make the always-firing autocomplete expensive.

They moved out of the Lab settings list to follow the convention every other plugin with settings already uses: a plugin declares its settings in its manifest and AMC renders them in that plugin's own card, storing values under pluginSettings.writer. RepoGuard, prdstack, virtual-pets and daily-spend-report all do the same, and none reaches into the app's general settings pages.

resolveWriterModel reads pluginSettings.writer and falls back to the legacy top-level AppSettings.writerModel / writerAutocompleteModel, so a choice made before the migration still applies; that fallback goes when the native surface retires. An empty value is the manifest default meaning "Automatic" and is treated as ABSENT rather than as an override, so the default cannot wipe an existing choice.

Available models: Luna / Haiku / Sonnet / Opus (the WRITER_AI_MODELS catalog in src/shared/types/writer.ts).

Setup wizard (first-run onboarding)

A 7-step setup wizard opens automatically on the first Writer Studio visit — once the writer-setup-wizard feature is turned on and writerSetupComplete is false. It follows the same shared Wizard component pattern as KMS Quick Reference — portal overlay, progress dots, keyboard nav, focus trapping, cinematic welcome.

Steps: Welcome (cinematic fullBleed) → Writing Defaults (creativity dial + tone) → AI Editing → Rewrite & Autocomplete → AI Assistant → Integrations → Get Started.

  • Writing Defaults (step 2) persists writerDefaultCreativity and writerDefaultTone to settings. New documents inherit these defaults via createDocument in writer-store.ts.
  • AI Editing / AI Assistant (steps 3, 5) have a "Jump in and try it" button that creates a sample document and exits the wizard early.
  • Rewrite & Autocomplete (step 4) has an interactive toggle for writerAutocompleteEnabled (ghost-text autocomplete).
  • Get Started (step 7) action cards dispatch via onComplete({ action }); WriterWizard.handleFinish reads the step result and creates a blank doc, a sample doc, or triggers file/Drive import via callbacks from WriterView.
  • Early dismissal (X / Escape / backdrop) sets writerSetupComplete = true AND writerOnboardingCompleted = true, preventing both the wizard and the legacy welcome screen from re-appearing.
  • Welcome coordination — shouldShowWriterWelcome() returns false when writerSetupComplete === false (wizard takes priority); after the wizard completes, writerOnboardingCompleted = true suppresses the welcome too.
  • Replay — Settings → Setup Wizards → "Writer Studio Setup" card navigates to the Writer view and reopens the wizard via useWriterWizardStore.
  • Feature-gated via writer-setup-wizard in UNRELEASED_FEATURES — off by default (in-development), with parent: 'ai-writer' so it is hidden wholesale while Writer Studio is. Its settingKey is the dedicated writerSetupWizardEnabled, NOT writerSetupComplete: the completion marker means the opposite of an enable flag, and pointing the gate at it inverted the feature's own Settings toggle.

Source: src/renderer/src/features/writer/wizard/.

How it behaves

Two surfaces today: the native Writer and the AI Writer plugin

There are currently two Writer UIs in the repo, and they coexist on purpose.

  • The native surface — src/renderer/src/features/writer/, opened from the "Writer" virtual project in the sidebar, talking to the writer:* IPC channels. This is the one users get, and everything else on this page describes it. The native editor now has full extension parity with the plugin: InlineAutocomplete (with Ctrl+Right word-by-word accept), FindReplace (Ctrl+F), and SlashMenu (/). Both the "More formal" and "Punchier" tone presets are in the native SelectionMenu.
  • The AI Writer plugin — src/plugins/writer/, a first-party plugin whose UI is a standalone React app rendered inside an isolated <webview>. It is an in-progress port of the same feature, not a replacement, and not the shipped surface.

Which one to edit: unless the task explicitly says "the plugin", edit the native surface.

What the plugin can do so far: a searchable document list, a document title header with a save indicator, a Tiptap writing surface with the same 500 ms autosave, an empty state, and a delete that asks for confirmation first and then offers a working undo. It also has a working Assistant: open it from the button in the document header, ask a question or request a change, and the reply comes back rendered as proper formatted text rather than raw markdown. A conversation belongs to one document, so switching documents shows that document's own history, and reopening a document brings its past chat back. If a message fails, the panel says why in plain words and offers a Try again that resends without duplicating what you typed.

The plugin can also edit a selection. Highlight some text and a small menu appears with Improve, Fix grammar, Shorten and Expand, a box to type your own instruction, and three alternative phrasings it can generate for you. Whatever you pick, nothing is rewritten until you see it: every change opens a review showing exactly what would be added and removed, and you Accept or Reject it (Escape rejects). Accepting counts as one edit, so a single undo takes it back. The Assistant's "Apply suggested edit" now works the same way, finding where the suggestion belongs in the document and opening the same review. When the assistant replies with general content rather than a targeted edit, an "Insert into document" button appears so you can add that text with one click. There is also an Auto-insert AI text toggle in the plugin's settings: when on, the assistant's suggested changes are applied to the document automatically without the review step.

Since then the plugin has reached broad parity. It also has inline autocomplete and the writing-guidance panel with the creativity dial; folders with drag-and-drop filing and pinning; version history with restore; a formatting toolbar (bold, italic, strikethrough, inline code, bullet and numbered lists, quote, a paragraph-style dropdown, links, divider and clear formatting); import from Google Drive, which lists your Google Docs and turns the one you pick into a new Writer document; and publish to Google Docs, which creates a real document in your Drive and links to it.

Publishing is a paid call, so it goes through one shared core (publishMarkdownCore) that applies the export feature gate, a rate limit placed before the paid call, and a content-hash idempotency guard — publishing the same text twice returns the first document instead of minting a second. The renderer IPC handler calls that same core, so the two transports cannot drift.

The plugin also supports multi-select with bulk operations (checkbox selection, select-all, bulk delete), inline document rename (double-click a title or use the context menu), document cloning ("Duplicate" in the context menu — atomically copies content, metadata, and tags with a "Copy of" title prefix), tagging (add/remove tags via the context menu; tag chips on document rows; a tag filter bar above the list), font size and line spacing controls (in the Appearance menu, 12–24px font sizes and compact/normal/relaxed spacing applied via CSS custom properties), and Save as local file export (Markdown or HTML via a system save dialog, available from both the File menu and the document row context menu).

Still native-only: the pop-out Writer window and the share / unpublish artifact flow.

Removed rather than ported (2026-07-24): the native "Send to Writer Studio" actions in the session context menu and under a Council verdict. A handoff that only works if you happen to have the plugin installed is a worse experience than not offering it, so both were deleted rather than duplicated.

How the plugin reaches data: a plugin webview cannot use IPC. Every call goes through window.AgentMC.writer.*, an identity-gated plugin-bridge namespace (allowed because the calling plugin's id is writer, NOT because the ai-writer flag is on) that reads and writes writer_documents rows through the query functions in src/main/db/queries-writer.ts, and writer_templates rows through src/main/db/queries-writer-templates.ts.

How the plugin reaches the AI: through the same bridge, via writer.aiEdit, aiOptions, aiAutocomplete, aiChat and chatHistoryGet. These call the SAME writer-ai-service the native Writer uses, completely unchanged — which is the point. That service tries an API-key account first and falls back to the user's Claude subscription, so someone signed in without an API key keeps working AI, and the models stay the cheaper Luna ones (GPT-5.6 via OpenRouter). Routing the plugin through the generic ai bridge instead would have meant API-key-only, Sonnet-priced AI, and no AI at all for subscription-only users.

Two behaviours differ from the rest of the bridge, deliberately. A failed edit, options or chat call arrives in the plugin as a thrown Error carrying the same plain sentence the native Writer shows — never a raw error code. Inline autocomplete never fails loudly at all: any problem simply produces an empty suggestion, because ghost text appearing after every typing pause must not raise an error message. Spend is unchanged: the same cost tracking and the same daily Writer budget apply, because it is the same engine.

Theme: the plugin is a separate document and cannot read Omniscio's stylesheet, so the host injects resolved theme values onto the guest page as CSS custom properties and the plugin maps them into its own Tailwind theme. It follows light/dark, the accent colour, the font stack and the app text size with no per-mode CSS of its own.

Build and tooling: the guest app lives in src/plugins/writer/web/ with its own package.json and TypeScript project, and compiles to the committed src/plugins/writer/ui/ bundle — rebuild it with pnpm --prefix src/plugins/writer/web build and commit the output in the same change, or the webview keeps serving stale code. Root npm run typecheck and npm run lint deliberately exclude web/ and ui/; run npm run typecheck:writer from the project root to typecheck the plugin separately.

The plugin loads as an ordinary built-in for now (it is not in the plugin loader's BUILTIN_EXCLUDE set). Making it marketplace-only, and deciding what happens to the native surface, belongs to a later phase.

The plugin surface's own test-locked invariants are I27-I32 in .claude/memory/contracts/writer-contract.md.

Save state indicator

A Saving… / Saved pill appears in the document header whenever a write is in flight or has just completed. It uses the savingState field in the writer Zustand store ('idle' | 'saving' | 'saved'). 'saved' requires the writer:save-content call to resolve AND to come back carrying the saved row — a call can resolve having written nothing, because the save is refused when another window saved first (a conflict) or when the document has since been deleted. Both of those return the pill to 'idle' and say what happened rather than flashing a "Saved" the text never earned. 'saved' reverts to 'idle' after 2 s, and the pill is hidden in 'idle' so it does not distract while you type.

Share / send action indicator

The same header slot carries a spinner + label while a longer share or send action is running — "Sending…" for Start a Session, "Exporting…" for Save to Vault, "Publishing…" for Publish, Republish and Unpublish. It exists because every one of those actions is started from an item in the Share menu, and that menu closes in the same click: a busy state drawn inside it is never seen, so the window between the click and the confirmation toast showed nothing at all. The indicator is derived once in WriterView and rendered by the document header, which stays mounted for the whole operation.

Loading states

Three load paths show a shape-matching skeleton rather than a centered spinner, so the layout does not jump when the real content arrives:

  • the document list, while it is being fetched;
  • the editor column, while the active document's content is still unavailable (a first open, or any open that was not warmed by a hover);
  • the editor chunk itself, while the lazily-loaded Tiptap bundle resolves.

The Send to PM dialog likewise draws a skeleton of its dropdown fields, and each level of the ClickUp workspace → space → folder → list cascade shows "Loading…" in its own field rather than "No spaces" / "No folders" / "No lists" — an empty-looking dropdown that is really still loading reads as missing data.

Assistant history caching

Each document has its own Assistant conversation, and the history is fetched by a separate call from the document body. Hovering a document in the list warms both — the body and the conversation — so opening it paints immediately instead of showing an empty panel until the round-trip lands. The warmed conversation is kept in a small bounded cache (the 10 most recent documents), revalidated from the server on every open, and dropped as soon as you send a message, so it can never show a conversation missing its newest turn.

AI status banner

When an AI operation fails with a credential or cost-cap error, a persistent banner appears below the document header (above the toolbar), naming what to fix. It persists until dismissed (×) or the page reloads. In the plugin (Writer Studio) it is a calm neutral strip with an accent icon — the guest webview theme exposes no amber "needs-you" token, and this is setup guidance, not an error (native renders its own amber variant).

  • "AI features need an Anthropic API key" (aiStatus: 'no-key') — no usable credential (neither an API-key account NOR a signed-in Claude account). The banner names the path in TEXT — "Add one in AMC Settings → Accounts" — NOT a button, because a sandboxed guest webview cannot navigate the host to open Settings. Rarely seen for edits/options/chat, since any signed-in Claude account satisfies them via the subscription fallback.
  • "Today's AI budget is used up" (aiStatus: 'cost-cap') — the per-day writer cost cap was hit; it resets tomorrow.

The status is derived reactively from the AI error sentence via toAiStatus() ('API key' → 'no-key', 'budget' → 'cost-cap'; lib/ai-status.ts). Whether to SHOW the banner is the pure resolveAiBannerStatus(aiStatus, dismissedAiStatus): dismissing records the CURRENT status, so a later, DIFFERENT problem still re-surfaces it. It is reactive by design — aiStatus is null until the first AI attempt fails, so a browsing no-key user sees the banner only after they try AI (a proactive check is a later wave). Rendered by AiStatusBanner (App.tsx); before Wave 1a the plugin computed aiStatus on every AI outcome but rendered nothing, so this common first-run wall showed only a transient toast.

Word count status strip

A word count + reading time strip sits below the editor, shown only while a document is open (hidden on the empty-state landing screen). It shows:

  • Word count: N word / N words
  • Reading time: ~N min read at 238 wpm. Native shows ~1 min down to an empty doc; the plugin omits the reading time over an empty document (0 words).

Words are counted by Intl.Segmenter at word granularity, NOT by splitting on whitespace. A whitespace split cannot count a language that does not space its words, so a whole Chinese or Japanese document counted as one word; it also counted markdown punctuation, making - a and # Title two words each. One extra rule is load-bearing: the segmenter splits e-mail into e / - / mail, so a lone - between two word-like segments continues the same compound word — without it state-of-the-art would count 4.

Native computes it in WriterEditorHost.tsx (off a useDeferredValue copy of the content) via countWords in src/renderer/src/features/writer/lib/word-count.ts — the CANONICAL implementation. The plugin (Writer Studio) has the same strip (WordCount.tsx over lib/word-count.ts), and because the island cannot import across the src/ boundary it carries a deliberate PORT of that function.

The two are held in agreement by tests/unit/plugins/writer/writer-word-count-parity.test.ts, which runs BOTH implementations over one corpus, so editing either file alone fails the gate. Parity used to rest on a comment asking the next person to change both files — and it did not hold. Note the reading time still differs by design: native shows ~1 min down to an empty doc, the plugin omits it at 0 words.

Document list view modes

The document sidebar supports three view modes, toggled via icon buttons in the section header: List (default — one row per document with title and relative timestamp), Grid (card layout with title, timestamp, and pin/share badges), and Compact (dense single-line rows with title only). All three modes share the same interaction behavior: click, drag-and-drop, multi-select, context menu, folder grouping, and keyboard navigation. The choice persists in the writerDocViewMode setting ('list' | 'grid' | 'compact'). The toggle uses the WAI-ARIA tablist pattern (useTabKeyboard) for keyboard accessibility.

Document list timestamps

Each document row in the sidebar shows a relative timestamp (formatRelativeTime(doc.updatedAt)) below the title — e.g. "just now", "3 min ago", "yesterday". The delete button is hidden until the row is hovered or focused (opacity-0 → opacity-100 via a CSS group class), keeping the list uncluttered.

Search documents

A search box at the top of the document sidebar filters the document list as you type. It matches against the document title AND the full text inside the document, so you can find a document by a phrase it contains, not just by name. The search is debounced (~200 ms) so it does not fire on every keystroke. Clearing the box (or pressing the ×) restores the full document list immediately, with no IPC call. Empty or whitespace input is treated as no search.

Folders & pin

Documents can be organized into folders, shown as collapsible groups in the document sidebar (each document also sits under an always-present Unfiled group for anything not filed away). A document lives in exactly one folder at a time, or stays Unfiled — there's no filing the same document into two folders. Use the row's ⋮ (actions) menu to move a document into a folder, and the "New folder" button next to the + menu to create one. The + menu itself is a dropdown with four items: "New document" (blank), "Upload" (local file import), "Import from Google Docs", and "New from sample" (tutorial content). You can also drag a document row onto a folder to file it there — this works even if the folder is collapsed — or drag it onto the Unfiled group to take it back out of its folder; the ⋮ "Move to folder" menu still works too, and is the option on phones since dragging is desktop-only. The ⋮ menu also has Edit title, which turns the document's title into an inline text input right in the sidebar row — commit with Enter or blur, cancel with Escape (same pattern as folder rename). Deleting a folder never deletes the documents inside it — they're moved back to Unfiled instead, and the delete itself can be undone like any other delete. Within a folder (or Unfiled), you can pin a document — via the star/pin control on its row — to keep it sorted at the top of the group regardless of when it was last edited. While you're searching (see "Search documents" above), the sidebar temporarily shows a flat list of matches instead of the folder groups; it returns to folders once you clear the search.

Version history

Writer Studio automatically saves version snapshots as you work — no manual "save version" step needed. A snapshot is taken when you pause typing (at most about once every few minutes, so it won't spam a snapshot per keystroke), whenever you switch away from or close a document, and right before any AI change is applied to your text (a selection edit accepted in the ReviewPanel, an options-mode replacement, or an applied chat suggested edit).

A History button in the document header opens a panel listing past versions newest-first, each labeled by why it was taken ("Auto-saved", "Before AI edit", "Before restore") with a relative time (e.g. "5 min ago"). Selecting a version previews it; a "Show changes" toggle highlights what's different from your current document. Clicking "Restore this version" swaps it into the editor and shows an Undo — your current text is saved to history first, so restoring is always reversible.

Each document keeps its most recent ~50 versions; older ones are trimmed automatically.

Editor prose measure

The editor body is constrained to 70 characters wide (max-w-[70ch], centred) to match comfortable reading and writing line lengths.

Writing Guidance panel

The GuidancePanel occupies the bottom of the left sidebar, below the document list. It contains four controls that the AI reads on every edit:

  • Goal — a text field where you describe what the document is for (placeholder: "What is this document for?").

  • Audience — a text field describing who the document is for (placeholder: "Who is it for?").

  • Tone — a text field for the desired writing tone (placeholder: "e.g. warm, formal, punchy").

  • Creativity Dial — a four-option segmented control with plain-language labels:

    Level Label Meaning
    1 Careful Minimal changes, preserves your voice
    2 Balanced Tightens structure and phrasing
    3 Creative Reworks flow and adds depth
    4 Bold Reinvents to hit the goal harder

    A footer confirms "Applies to all AI edits in this document."

All four values are stored in the writer_documents table and sent in every writer:ai-edit, writer:ai-options, and writer:ai-chat call.

Autosave

The editor auto-saves 500 ms after you stop typing. The timer lives in a module-private map (pendingWrites) outside Zustand state. Switching to another document flushes the pending write for the current document first (flushPendingWrite), so no content is lost on quick switches.

Cross-window safety. The same document can be open in two places at once (the popped-out Writer window, or a paired phone). Each save carries the version the window last saw, so if the other window saved in between, this window's save is refused instead of silently overwriting it — your in-progress text is kept, a small "This document changed in another window — your version will save on your next edit." toast appears once, and your very next edit saves cleanly on top of the newer version.

Related

Writer Studio opens from the Writer virtual project in the sidebar — Projects sidebar explains how virtual projects sit there, and App tour walks the surfaces around it. Its model dropdowns and first-run wizard live in Settings, indexed by Settings reference. This page is split across part 2 (the editor, document list and data model), part 3 (the four AI capabilities), part 4 (sharing, exports and integrations) and part 5 (IPC channels and key files).

Last verified 2026-09-28