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) (part 2)

Part 2 of the Writer Studio page: the formatting toolbar, find and replace, the slash menu, document templates, tags, cloning, bulk operations, rename, font and spacing controls, Save as, the editor theme and colour palettes, and the two writer data-model tables.

What it is

This is part 2 of the Writer Studio (standalone writing editor) page. It covers the editor surface and the document list around it — the formatting toolbar, the plugin’s find bar, slash menu and template picker, the row-level document operations, the font, spacing, theme and palette controls, Save as, and the two data-model tables behind all of it.

Where to find it

Writer Studio opens from the Writer virtual project in the sidebar, and the parent page covers how the feature is gated, which of its two surfaces you are looking at, and the first-run wizard. Within the writer view the formatting toolbar sits between the document title and the editor body, the document list occupies the left sidebar, and the theme, palette, font and spacing controls live under the Appearance gear icon in the document header.

How it behaves

Formatting toolbar

A Word-style formatting toolbar sits between the document title and the editor body (FormatToolbar.tsx). Left to right: Undo · Redo, a paragraph-style dropdown (Normal text / Heading 1–3), Bold · Italic · Strikethrough · Code, Bullet list · Numbered list · Quote, and Link · Divider · Clear formatting.

  • Undo / Redo are the leftmost two buttons; they reflect the Tiptap editor history state (can().undo() / can().redo()) and disable when no action is available. They are keyboard-equivalent to Ctrl+Z / Ctrl+Y.
  • Buttons reflect the cursor's context — a toggle shows aria-pressed / the accent style when the selection is inside that mark or block; the dropdown shows the current block's style.
  • Link opens a small inline box to type a URL (auto-prefixed with https:// when no scheme is given; only http/https/mailto are accepted), with a Remove option on an existing link.
  • Clear formatting runs unsetAllMarks() + clearNodes() (matches Word's "Clear All Formatting").

The toolbar is markdown-clean: every action maps to a command StarterKit already serializes to markdown, so the stored format stays markdown (no HTML). There are deliberately no Underline, text-color, or alignment buttons — markdown has no standard representation for them, and adding them would require embedding HTML in the stored content. Clicking a button, pressing the keyboard shortcut (Ctrl+B…), and typing markdown all drive the same Tiptap commands.

Find & replace (plugin)

Cmd/Ctrl+F opens an in-document find bar (FindBar) over the editor — query + replace inputs, case-sensitive and whole-word toggles, an "N of M" count, and next / previous / Replace / Replace-all. The key is intercepted (preventDefault) so the webview's own native find doesn't hijack it; Esc closes and clears the search.

Matching runs in a custom ProseMirror extension (editor/extensions/FindReplace.ts, the InlineAutocomplete pattern — permitted by I20's Wave-2b clarification): all matches are highlighted via inline decorations (.writer-find-match; the current one also .writer-find-current), and it edits PLAIN TEXT only so the document stays markdown-clean (I3). Match ranges come from the pure findAllMatches (lib/find-matches.ts — exact substring, case / whole-word, non-overlapping) over collectTextBlocks (the SAME text→position map the chat "apply edit" flow uses). Replace-all runs last match → first so an earlier match's position never shifts under a later edit.

Slash "/" menu (plugin)

Type / at a line start (or after whitespace) to open a floating command menu at the caret, filtered by what you keep typing; up/down move, Enter runs, Esc dismisses. Commands: Heading 1-3, Bulleted / Numbered list, Quote, Divider (pure StarterKit chains -> clean markdown, I3/I20), From template (opens the TemplatePicker), and the AI commands Continue writing (via the existing aiAutocomplete, stale-guarded), Summarize, Explain, and Improve writing (the last three open the Assistant + send via sendChat). The AI commands reuse the existing cost-cap + humanized-error flow; no new bridge.

Detection is a pure matchSlashQuery (lib/slash-trigger.ts) over the text before the caret, wrapped in the SlashMenu extension (editor/extensions/SlashMenu.ts) that computes { active, query, from } PURELY in the plugin's apply (no dispatch-from-view) and SKIPS code blocks. The command list is lib/slash-commands.ts; the floating UI + keyboard live in components/SlashMenu.tsx -- the keyboard is a capture-phase listener mounted ONLY while the menu is open. On pick it deleteRanges exactly the /query before running the command.

Document templates (plugin)

Creating a new document in the plugin (the sidebar + or the empty-state create button) opens a template picker (TemplatePicker, reusing the guest Modal) with a Blank option first, then any user-defined templates (accent-colored border), then the built-in starter templates (lib/writer-templates.ts) — the same set Writer Studio ships, kept identical by a drift check. Picking one seeds structured markdown (and, where it helps, a writing-guidance seed) via the existing createDocument → setContent → updateGuidance path (the same flow as a Drive import). The picker ONLY ever seeds a fresh document — there is no "apply to the open document" path, so it can never overwrite an existing draft, and Blank preserves the one-click create.

User-defined templates

Users can save any open document as a reusable template via the BookmarkPlus button in the document header. The template captures the document's title, markdown content, and writing guidance (goal, audience, tone). Templates are stored in the writer_templates table (see "Data model" below) with soft-delete and are global per install (no account_id).

User templates appear ABOVE the built-in templates in the picker with an accent-colored border. Each user template has a hover-visible delete button (Trash2 icon) that soft-deletes it. The picker fetches user templates via writerApi.templateList() on every open.

Both the IPC handlers and the plugin bridge support full CRUD:

Surface List Get Create Delete
IPC (native) WRITER_TEMPLATE_LIST WRITER_TEMPLATE_GET WRITER_TEMPLATE_CREATE WRITER_TEMPLATE_DELETE
Plugin bridge templateList templateGet templateCreate templateDelete

Template operations do NOT emit writer:documents-changed pushes (templates are a separate entity from documents).

Document clone

The document row context menu includes a Duplicate action that atomically copies a document. cloneWriterDocument reads the source row, creates a new document with a "Copy of [title]" prefix, copies all metadata fields (goal, audience, tone, creativity, folder), and duplicates the document's tags. The clone is a fully independent document with its own id and timestamps. Cloning a nonexistent or deleted document returns undefined (the handler shows a humanized error). The WRITER_DOCUMENTS_CHANGED push fires after clone so the sidebar refreshes.

Document tags

Documents can be tagged with freeform text labels for organization and filtering. Tags are stored in a junction table writer_document_tags (created by migration 20260903023357):

Column Type Notes
document_id TEXT FK references writer_documents.id
tag TEXT the tag label (case-sensitive)
created_at TEXT ISO timestamp

Primary key is (document_id, tag). Adding a duplicate tag is a no-op (INSERT OR IGNORE). Tags are loaded alongside documents via loadAllDocumentTags (a single query that batch-loads all tags grouped by document id, avoiding N+1).

In the plugin DocumentList, each row shows up to 3 tag chips with an overflow count. A tag filter bar appears above the document list when any tags exist; clicking a tag filters the list to documents carrying that tag. The row context menu includes a "Tags" item that opens a tag management popover with an input to add new tags and buttons to remove existing ones.

Templates, tags, copies and export in Writer Studio (native)

Writer Studio has the same five document tools as the plugin, over the same tables and channels. Contract: writer-library-contract.

  • Start from a template. New from template in the create menu, or the From template item in the / menu, opens a picker: a Blank card, then your saved templates, then the built-in ones, with one search box over all three. Picking one creates the document, saves the template's text, then its writing guidance, and only then opens it. If the text cannot be saved the new document is removed; if only the guidance fails, the document is kept and you are told.
  • Save as template. An item in the document's File menu. It stores the title, text and guidance as a snapshot; later edits do not reach it. An empty document is refused with a plain sentence. The template's name is the title cut to 200 characters. A saved template's delete button is always visible, not hover-only, and asks for confirmation.
  • Tags. Each list row shows up to three tag chips, then +N. Manage tags in the row menu opens a small dialog: the tags as removable chips, a box where Enter adds one, and suggestions from every tag in use. Adding or removing a tag does not change the document's last-edited time, so it never reorders the list or makes the next autosave conflict.
  • Filter by tag. A row of tag pills appears above the list once any document is tagged. Picking one narrows the whole list, folders included, and works together with a search. Picking it again clears it, and it clears itself if no document carries that tag any more.
  • Duplicate. Makes a "Copy of" document with the text, guidance and tags, and opens it. The copy carries none of the original's share page, board card, knowledge-base note, project item or chat links.
  • Export from the list. Export as Markdown and Export as HTML in the row menu use the same export as the File menu. A failure is reported in words; cancelling the save dialog is silent.
  • Translate. A preset in the selection popup, beside the other AI edit presets, reviewed before it applies like any AI edit.

The four row actions work on one document at a time; with several selected they show as disabled, with a tooltip saying why. None of these actions has a keyboard shortcut: the / menu is already the keyboard path to templates, and the rest are one click away in a menu.

  • In the web client (a phone or browser linked to the app) the link refuses templates, tag editing, copies and export, so those menu items and the / template command are hidden there rather than shown only to fail. Tags still show on each row, and the tag filter still works.
  • From the command line, the same actions are routes on the local control server: POST /writer/documents/:id/clone, POST /writer/documents/:id/tags (body { tag }), DELETE /writer/documents/:id/tags/:tag, GET and POST /writer/templates, and DELETE /writer/templates/:id. They use the same saving code as the app, so a document copied or tagged from the command line shows up in the open Writer. Copy and template create accept an X-Client-Request-Id header, so a retried request never makes a second copy.

Multi-select and bulk operations

The plugin DocumentList supports multi-select: click the checkbox on any row to toggle selection (checkboxes appear on hover, or always when items are selected). A bulk toolbar appears above the list showing the selection count, with actions for Select all, Delete selected, and Clear selection. Bulk delete calls the per-document delete for each selected id and clears the selection.

Inline document rename

Double-clicking a document title in the sidebar, or choosing "Rename" from the row context menu, turns the title into an inline text input. Commit with Enter or blur, cancel with Escape. The cancelDocRenameRef pattern prevents a blur-after-Escape from committing the cancelled value (same pattern as folder rename).

Font size and line spacing

The Appearance menu (gear icon in the document header) includes two new submenus:

  • Font size — 7 options from 12px to 24px (default 16px). Stored as writerFontSize in appearance-settings.ts.
  • Line spacing — 3 options: Compact (1.4), Normal (1.7, default), Relaxed (2.0). Stored as writerLineSpacing (WriterLineSpacing enum).

Both are applied to the editor via CSS custom properties (--writer-font-size, --writer-line-height) set on the prose container's inline style. The plugin reads them on mount via the writer.getAppearanceSettings bridge method. The CSS variables have fallback defaults in index.css so the editor renders correctly before settings load.

Save as (local file export)

The File menu includes a Save as submenu with two options:

  • Markdown (.md) — writes the raw markdown content to the chosen path.
  • HTML (.html) — converts the markdown to HTML via markdownToExportHtml (the same converter used for Google Docs export) and writes it.

Both open a system save dialog (dialog.showSaveDialog) with a default filename derived from the document title (sanitized via sanitizeTitleForPath). Empty documents cannot be exported (the handler returns a humanized error). The same export options are also available in the document row context menu in the sidebar.

Editor theme and color palette

The Writer editor can use a different light/dark theme from the rest of the app, and an optional color palette that restyles the writing surface. Both live under the Appearance menu (gear icon) in the document header (WriterAppearanceMenu):

  • Theme (Sun/Moon/SunMoon icon) — a menu item that cycles between match-app (default), light, and dark. When set to an explicit theme that differs from the app's current mode, the Writer root div gets a .writer-light or .writer-dark CSS class that overrides the --color-surface-*-rgb custom properties. All existing surface-* Tailwind utilities within the scope resolve to the overridden values automatically, so zero child components needed refactoring.

  • Color palette (Palette icon) — a submenu offering 8 curated palettes (Parchment, Midnight Ink, Candlelight, Noir, Velvet, Frost, Sage, Rose) plus None. Each palette adds a .writer-palette-{name} class to the WRITER_PAGE element with custom gradients, borders, and shadows. Parchment and Candlelight include SVG feTurbulence noise texture and are light-only (skipped in dark mode). All other palettes have both light and dark variants.

  • Toolbar layout (LayoutGrid icon) — a submenu offering 4 toolbar button styles: Icons + labels (icon + full text), Icons only (icon with rich tooltip, default), Compact (icon + shortened label), and All icons (everything icon-only, including File/Share/Send-to menus). The default is Icons only because it matches other Omniscio toolbars and Writer Studio is a breadcrumbed tool for experienced users.

Settings: writerTheme ('match-app' | 'light' | 'dark', default 'match-app'), writerPalette (enum, default 'none'), and writerToolbarLayout ('icons-text' | 'icons-only' | 'compact' | 'full-icon', default 'icons-only') in appearance-settings.ts. The Appearance gear icon shows accent highlighting when palette is set away from its default. The theme logic is in WriterView.tsx (writerThemeClass memo, paletteClass memo); the CSS classes are in globals.css. Each palette tints the page with a single hairline inset shadow in the palette's own hue. The class name is built in WriterView.tsx (the paletteClass memo) as writer-palette-{name}, and the palette rules themselves are written out as literals in src/renderer/src/styles/globals.css: Tailwind purges a rule whose class name it never sees spelled out, which is how the light palettes once rendered nothing. The two light-only palettes are listed once, as LIGHT_ONLY_PALETTES in src/shared/types/settings/appearance-settings.ts, and the renderer skips their class in dark mode. That file is also where the WriterPalette, WriterLineSpacing and WriterToolbarLayout types and their WRITER_PALETTE_VALUES / WRITER_LINE_SPACING_VALUES / WRITER_TOOLBAR_LAYOUT_VALUES lists live.

The pop-out Writer window (WriterWindowApp) renders the same WriterView, so both settings work identically in the standalone window.

The writing surface

The writing surface is a portrait sheet — WRITER_PAGE in src/renderer/src/features/writer/styles.ts caps the line length at 70 characters and gives the panel its rounded border and generous padding, so it reads like a page rather than a strip. Under it, the editor status strip in WriterEditorHost.tsx shows the word count and an estimated reading time.

There are no page controls: neither an "Add page" page break nor a two-column "book view" exists in the app today, in this editor or in the Writer plugin. The editor's extensions are FindReplace, InlineAutocomplete and SlashMenu only.

For agents

Data model — writer_documents

Created by migration src/main/db/migrations/20260603162951-create-writer-documents-table.ts.

Column Type Notes
id TEXT PK randomUUID()
title TEXT display name (≤ 255 chars, Zod-enforced)
content TEXT canonical markdown body (stored as markdown, never HTML)
goal TEXT what the document is for (writing guidance for AI)
audience TEXT who the document is for (writing guidance for AI)
tone TEXT desired tone, e.g. warm, formal, punchy (guidance for AI)
creativity INTEGER 0–100 creativity dial value
created_at / updated_at TEXT ISO timestamps, bound in JS
is_deleted INTEGER 0/1 soft-delete — hard-deleted by the retention sweep
folder_id TEXT FK to writer_folders; NULL = Unfiled
pinned INTEGER 0/1 pin within folder
share_token TEXT Shares republish token (set on publish)
share_url TEXT published Shares URL
mc_item_id TEXT linked Mission Control item id
linked_kms_note_id TEXT linked KMS note id (vault export bridge)
linked_kms_vault_id TEXT linked KMS vault id
external_url TEXT URL of the linked external document (e.g. Google Doc)
external_provider TEXT provider name for the external link (e.g. google-docs)
external_id TEXT provider-specific document id
source_session_id TEXT id of the Claude session this document was created from

Documents are global per install — the table has no account_id column. Every document is visible regardless of which Anthropic account is currently active.

Every READ query filters AND is_deleted = 0. Delete flips is_deleted = 1; a toast action can restore it (is_deleted = 0) within the standard Undo window.

A deleted document is destroyed for good once the retention window passes. The daily retention tick calls purgeDeletedWriterDocuments(db, retentionDays) (src/main/db/queries-writer.ts), which runs DELETE FROM writer_documents WHERE is_deleted = 1 AND updated_at < ?. Every version of the document goes with it (purgeDeletedWriterDocumentVersions, the same rule on created_at), so a deleted draft and its history become unrecoverable at the cutoff — dataRetentionDays, 30 days by default. The Undo window is the only recovery path; after it closes, do not treat a deleted document as still being in the database.

Data model — writer_templates

Created by migration src/main/db/migrations/20260903023722-create-writer-templates-table.ts.

Column Type Notes
id TEXT PK randomUUID()
label TEXT display name (Zod: trimmed, 1–200 chars)
description TEXT optional description (Zod: max 500 chars, default '')
title TEXT seeded document title (Zod: trimmed, 1–500 chars)
markdown TEXT seeded document content (Zod: 1–500k chars)
goal TEXT writing guidance goal (nullable)
audience TEXT writing guidance audience (nullable)
tone TEXT writing guidance tone (nullable)
is_deleted INTEGER 0/1 soft-delete
created_at / updated_at TEXT ISO timestamps, bound in JS

Templates are global per install — no account_id column. Same soft-delete pattern as documents. Query layer: src/main/db/queries-writer-templates.ts.

Content format

Content is always stored as markdown. The Tiptap editor uses the @tiptap/markdown extension: getMarkdown() serialises on save; setContent(md, { contentType: 'markdown' }) hydrates on open. Never store or persist HTML.

Related

The surfaces that frame these controls — the document-list view modes, search, folders, version history and autosave — are on the Writer Studio (standalone writing editor) page. The AI capabilities that read this document and its guidance are in part 3, sharing and integrations in part 4, and the IPC reference in part 5.

Last verified 2026-10-06