Writer Studio (standalone writing editor) (part 4)
Part 4 of the Writer Studio page: publishing a document to Shares and sending it by email, SMS or Slack, PDF and Word export, Google Docs import and export, local file import, and the Session, Mission Control, KMS vault, Team Chat and PM-tool bridges.
What it is
This is part 4 of the Writer Studio (standalone writing editor) page. It covers everything a document can leave through — publishing to Shares and sending by email, SMS or Slack, PDF and Word export, Google Docs round-trips, local file import, and the Session, Mission Control, KMS vault, Team Chat and PM-tool bridges.
Where to find it
Writer Studio opens from the Writer virtual project in the sidebar, and the parent page covers gating, surfaces and first-run setup. Inside the writer view these actions live in the document header — the Publish, Export to Docs, Send to MC and Send to... buttons — and in the File and Share menus; local file import is reached from the Welcome screen, the empty-state button and the sidebar + menu.
How it behaves
Publish to Shares
A Publish button in the document header publishes the current document's markdown to Omniscio Shares, generating a shareable URL anyone can open — no Omniscio account needed to view it. The document sidebar shows a small share icon on published documents.
- Publish — calls
writer:publish, which pushes the markdown content to Shares and stores the resulting share URL and token on the document row. The link is copied to the clipboard automatically (best-effort — a clipboard failure does not affect the publish itself). - Republish — appears when a document is already published. Updates the existing
share in place (same URL, new content) by passing the stored
republishTokentopublishArtifact. The old share is revoked only if the token changed. - Unpublish — revokes the published link so it stops working. This is a destructive action (anyone with the old link loses access), so it opens a confirmation dialog first.
- Published indicator — when a document has a live share link, the header shows an accent-colored "Published" button that copies the link on click, plus Republish and Unpublish options.
Deleting a document automatically revokes its share link if one exists. Empty documents cannot be published.
Send via channels
Published documents can be sent directly to recipients via email, SMS, or Slack from the Share menu. When a document is published, the menu shows "Send via Email", "Send via SMS", and "Send via Slack" items (hidden when not published).
Clicking any channel opens WriterSendViaDialog — a modal with a recipient field
(channel-appropriate placeholder from the CHANNELS catalog in shares-detail-helpers.ts)
and an optional message textarea. The dialog resolves the document's shareToken to a
shareId via the share store, then calls IPC.SHARE_SEND with { shareId, channelKind, recipient, message }. Success shows a toast; errors are humanized via humanizeError.
This reuses the existing share-send infrastructure (src/main/ipc/share/send.ts) — no new
IPC channels or backend changes. The SHARE_SEND handler validates recipients per channel
(email format, phone E.164, Slack channel name) and applies a 10/minute rate limit.
File export (PDF / Word)
Writer documents can be exported as PDF or Word (DOCX) files from both the File menu and
the Share menu. Both entry points call the shared useExportMarkdownToFile hook
(src/renderer/src/hooks/useExportMarkdownToFile.ts) with the document's markdown content,
format ('pdf' or 'docx'), title, and surface 'standalone'.
- PDF export is always available.
- Word export is gated by the
wordExportEnabledsetting (matching the ExportMenu pattern used elsewhere in the app). - Both items show a disabled state while an export is in progress.
- The hook handles empty-document warnings, file save dialogs, and error toasts internally.
Import & export with Google Docs
Writer Studio can pull a Google Doc in and push a document out, reusing Omniscio's existing Google plumbing — no Writer Studio-specific Google services.
- Import from Drive — the sidebar
+menu "Import from Google Docs" item (and empty-state "Import" button) opensWriterDriveImportDialog, aDialogShellpicker that supports two import methods: pasting a direct Google Doc/Sheet/Slides URL, or searching by keyword. The URL input sits above a divider; the keyword search lists the user's Google Docs, Sheets, and Slides (three paralleldrive:list-filesIPC calls, one per MIME type, merged and sorted bymodifiedTime). Docs import viaGOOGLE_DOC_FETCH_MARKDOWN(markdown export). Sheets import viawriter:import-google-sheet(XLSX export →extractOfficeText→ markdown table). Slides import viawriter:import-google-slides(PPTX export →extractOfficeText→ text outline). All three create a new Writer Studio document viawriter:create+writer:save-content. The original file is untouched; import never overwrites the open document. Picker state lives in the dedicatedwriter-drive-import-store.tsslice (separate from the standalone Drive browser'sdrive-store.ts). File type labels ("Sheet", "Slides") appear next to each file in the picker viagoogle-apps-mime-labels.ts. Not-connected / expired Google sessions show a "Connect Google" prompt routing to Settings → Google Workspace. - Export to Docs — the header "Export to Docs" button publishes the open document's markdown to
a fresh Google Doc via the shared
usePublishMarkdownToGoogleDochook (surface'writer'). Re-exporting creates another new Doc — no linked/two-way sync. Export is gated by the existinggoogleDocsExportEnabledsetting; when off, the hook shows a "turn it on in Settings" prompt.
Both entry points live inside the gated ai-writer surface. For offline E2E, AMC_GOOGLE_FAKE=1
returns deterministic Drive/Doc data without real OAuth.
File import (local files)
Writer Studio can import local document files, converting them to markdown and creating a new Writer document. Supported formats:
| Format | Extension(s) | Conversion method |
|---|---|---|
| DOCX | .docx |
mammoth → turndown (HTML → markdown) |
| Markdown | .md, .markdown |
Direct read (with UTF-8 BOM stripping) |
| Text | .txt |
Direct read (with UTF-8 BOM stripping) |
| RTF | .rtf |
Pure-JS control-word parser (extracts text + \u Unicode) |
| ODT | .odt |
adm-zip → content.xml XML tag walking |
| EPUB | .epub |
adm-zip → container.xml → OPF → spine → turndown |
The import service (src/main/services/writer/writer-file-import-service.ts) is
self-contained — it does NOT extend the generic conversion engine. No new npm dependencies;
mammoth, turndown, and adm-zip were already in the tree.
- Entry points — the Welcome screen (2×2 grid "Upload a file" card), the empty-state
"Upload a file" button, and the sidebar
+menu "Upload" item all open a native file picker (DIALOG_OPEN_FILE) filtered to the supported extensions, then callwriter:import-file→handleImported({ title, markdown })(the same callback Google Docs import uses, which creates a new document + navigates to it). - Safety — ZIP-based formats (DOCX, ODT, EPUB) run through
assertArchiveNotBomb()(total uncompressed XML capped atMAX_DOCX_XML_BYTES). File size capped at 200 MB (MAX_FILE_BYTES); converted markdown capped at 10 MB (MAX_MARKDOWN_BYTES). Image references are stripped (Writer is text-only). DRM-protected EPUBs are detected and rejected with a humanized error. - Desktop-only —
writer:import-filereads a local file path, so it is inBLOCKED_CHANNELSfor web-access WS and exempt from CLI parity (feature-scoped-surface). - Tests —
tests/integration/writer-file-import-service.test.ts(63 tests: all formats + error cases + image stripping + DRM detection + BOM + size limits).
Session bridge (Phase B2)
Writer Studio connects bi-directionally with Claude Code sessions:
- Session → Writer Studio: right-click a session in the dashboard → "Send to Writer
Studio". This assembles the session's visible-only transcript as markdown, creates a new
Writer document with that content, and navigates to it in Writer Studio. The action appears
only when
ai-writeris enabled and exactly one session is selected. - Writer Studio → Session: click "Send to Session" in the Writer toolbar. This opens a new Claude Code session with the document's content pre-filled in the composer (not auto-sent), so you can continue working on it with Claude.
- CLI:
POST /writer/create-from-sessionaccepts{ sessionId, title? }and returns the new document metadata. Returns 404 for unknown sessions, 403 when the feature is disabled. Agents can use this to programmatically create Writer documents from session transcripts.
Both UI actions are gated by the ai-writer unreleased feature. The IPC channel
(writer:create-from-session) is blocked for mobile/web access (desktop-only — it reads
the local session database).
Mission Control bridge
Writer documents can be linked to Mission Control (PM) tasks. The bridge requires BOTH ai-writer
AND settings.missionControlEnabled to be active (dual gate).
- "Send to MC" button in the Writer header opens a dialog (
SendToMCDialog) where the user creates a new MC task (with board picker) or links to an existing one (viaPmItemPicker). - Linked task chip replaces the button once linked, showing the task name. Clicking navigates to Mission Control; the unlink button removes the association after a confirm dialog.
- Dual-write pattern — linking stores both a forward FK (
writer_documents.mc_item_id) and a reverse row inpm_item_links(entity_typewriter_document), transactionally. - Content sync — when a linked Writer document is saved (non-conflict),
syncWriterContentToMcIteminwriter-mc-bridge.tscopies the document content to the MC item'sdescriptioncolumn (truncated at 5,000 graphemes with a…(truncated)suffix). The sync is fire-and-forget (try/catch, non-blocking) so a sync failure never blocks the document save. Initial sync also runs at link-creation time viacreateMcItemAndLink. - MC LinksTab shows linked Writer documents with click-through navigation back to the Writer
surface (via
navigateToEntityon the session bridge). - CLI routes —
GET/POST/DELETE /writer/documents/:id/mc-linkfor headless linking;POST /writer/documents/:id/mc-create-and-linkto atomically create a new MC item and link it. - Hook:
useWriterMcLink(docId)manages link state with auto-refresh.
Vault (KMS) bridge
Writer Studio documents can be exported to and imported from the KMS knowledge base (vault), creating a bi-directional markdown bridge between the two surfaces.
Export to Vault — the Writer header's "Export to Vault" button saves the current document's markdown content as a KMS note. On first export, a new note is created in the user's default vault (path derived from the document title via
sanitizeTitleForPathpickAvailableNoteRelativePath), and the Writer document stores the link (linked_kms_note_id+linked_kms_vault_id). Subsequent exports update the existing linked note's body rather than creating a duplicate. If the link is lost (e.g. after a DB restore), a path-based reconciliation re-links an existing note at the expected path before creating a new one. The handler dual-gates on bothai-writerand KMS being enabled.
Open in Writer Studio — the KMS file tree's context menu offers "Open in Writer Studio" on any note. If a Writer document already links to that KMS note, it is opened directly (no duplicate). Otherwise a new Writer document is created with the note's body as content, and the link is stored. Navigation dispatches
open-ai-writerto switch to the Writer surface.Link tracking — the
linked_kms_note_idcolumn has a unique partial index (WHERE NOT NULL) preventing two Writer documents from linking to the same KMS note. Soft-deleted documents do not block the index. Both directions emitKMS_NOTE_CHANGEDviaemitPushso the KMS file tree refreshes without polling.CLI:
POST /writer/documents/:id/export-to-vaultmirrors the IPC handler but additionally tags the note withwriter-studioandexported(merged with any content-derived tags). Idempotent viaX-Client-Request-Id. Returns 403 when KMS is disabled, 409 when no vault is configured.
Both entry points are gated behind ai-writer AND KMS being enabled. The vault bridge
invariants are I34-I38 in writer-contract.md.
Send to Team Chat
Writer documents can be shared to Team Chat channels from the Share menu. The dialog
(SendToTeamChatDialog) posts the document content as a message with an
omniscio://writer/<docId> deep-link appended. Messages longer than 8,000 characters are
truncated with a "(truncated; full document in Writer)" suffix.
- Threading — the first send stores the
clientMsgIdandchannelIdon the document (writer_documents.team_chat_msg_id/team_chat_channel_id). Subsequent sends to the same channel post as a threaded reply viasendReply(using the stored msgId asparentId). The button label changes to "Update in Thread" when a reference exists. - Deep-links — the
writerhost is registered insrc/main/services/deep-link.ts, which parses anomniscio://writer/<docId>link into anopen-writer-docaction. The renderer'sdeep-link-router.tshandles that action withhandleOpenWriterDoc, which navigates to the document in Writer Studio. - Deleted-channel recovery — if the reply fails (e.g. the channel was deleted), the reference
is cleared via
writer:clear-team-chat-linkand the message is re-sent as a fresh message with a "thread unavailable" warning toast. - IPC channels —
writer:set-team-chat-linkandwriter:clear-team-chat-linkstore/clear the reference. Both are desktop-only.
Bidirectional sync (F001)
Linked Writer documents support bidirectional sync with their vault notes:
- Sync on Save — a global toggle (
writerKmsSyncOnSaveinkmsSettingsSchema, default off) that auto-pushes the document's content to the linked vault note on every save. The sync is fire-and-forget (does not block the save response) and is gated on KMS being enabled, the setting being on, and the document having a link. If the vault note was modified externally since the last sync (determined by comparingnote.updatedAt > meta.kmsSyncedAt), the push is skipped and a warning is logged to avoid overwriting external changes. The toggle appears as a "Sync on Save" menu item with a checkbox indicator in the Share menu, visible only when the document is linked. - Pull Latest from Vault — a menu item in the Share menu that replaces the Writer
document's content with the latest vault note body. Before overwriting, a pre-import
version snapshot is created (
appendWriterVersionwith reason'pre-import') so the user can restore via the version history panel. The pull updateskms_synced_aton success and refreshes the sync status indicator. - Sync status indicator — a colored dot on the "Save to Vault" / "Update in Vault"
menu item showing the current sync state: green (in-sync), amber (writer-ahead or
vault-ahead), red (conflict — both sides changed since last sync). The status is
computed by comparing
meta.updatedAt,note.updatedAt, andmeta.kmsSyncedAtvia theWRITER_KMS_SYNC_STATUSIPC handler. - Conflict handling — when both the Writer document and vault note have changed
since the last sync (
meta.updatedAt > kmsSyncedAtANDnote.updatedAt > kmsSyncedAt), the status reports'conflict'(red dot) and sync-on-save is skipped. The user resolves conflicts manually via Pull Latest (which snapshots first) or Export (which overwrites the vault note). kms_synced_at— a newTEXTcolumn onwriter_documents(migration20260902214334) that records the ISO timestamp of the last successful sync in either direction (export or pull). Both the export handler and pull handler update this column on success. It is exposed inWriterDocumentMetaand the plugin bridge schema.
Send to PM tool (Phase C3)
A "Send to..." dropdown in the document header creates a task/card/issue in a connected PM tool
(ClickUp, Trello, Jira, or Arij) with the document content as the description. Each PM tool
appears only when its integration is visible (unreleased-feature-gated) and connected (credentials
exist). The dialog shows cascading selects for the provider's hierarchy (e.g. ClickUp:
Workspace → Space → Folder → List). The same flow is also accessible from the Share menu
("Send to PM Tool" item, gated on pmEnabled).
After creation, the external URL and provider are stored on the writer_documents row
(external_url, external_provider), and a linked indicator appears in the document header.
Sending again overwrites the previous link. Trello descriptions are truncated at 16,000 chars.
Both new IPC channels (writer:pm-status, writer:send-to-pm) are desktop-only
(BLOCKED_CHANNELS in the WS bridge) and CLI-parity-exempt (feature-scoped-surface).
Credentials stay in main — the renderer never sees API keys.
Related
The editor and document list the document is written in are in part 2, the AI capabilities that edit it in part 3, and the IPC channels behind these bridges in part 5; the Writer Studio (standalone writing editor) page is the parent.
Last verified 2026-10-06