Skip to content

[RFC] Claude-integrated desktop writing app #294

Description

@soroushm

Note: This RFC's scope has been narrowed by a follow-up Epic (apps/editor, linked below once
filed). Phase 1 (extracting packages/ui) is no longer needed — packages/design-system and
packages/markdown already exist as publishable workspace packages. The Claude backend is CLI
subprocess (claude -p, reusing the local claude login) rather than the Agent SDK, so each user
— including anyone besides me who runs the app — authenticates with their own subscription with
zero token wiring. Phase 3 (live two-way sync draft store, terminal-MCP leg) is deferred; v1 is a
single-window editor with one-shot, selection-scoped AI edits instead.

What's the Motivation? 🤔

The Markdown editor built under apps/web/src/theme/Markdown (with section/MarkdownEditor
and the dev-only /articles/write page) should grow into a personal desktop writing tool
with Claude built in
— draft an article, ask Claude to revise/extend it, and see changes live.

A browser page is the wrong host for this. It cannot spawn a local CLI, cannot run the Node-based
Claude Agent SDK, and cannot hold long-lived local state — so an in-browser version needs a local
"bridge" process plus WebSocket/SSE/CORS plumbing purely to work around the sandbox. A desktop
app removes that constraint entirely
: its own Node process runs the CLI/SDK directly and owns
the draft, so the desired hybrid two-way sync collapses to one process with one source of truth and
simple IPC to the UI.

This RFC proposes building that desktop app, reusing the site's design system, and retiring the
web writing page
(and the bridge idea) in its favour.

What are the requirements? ❓

  • Reuse the existing markdown editor UI so the tool matches the site's design system.
  • Claude integrated on the user's own Claude subscription — no separate API billing, and no
    shared/bundled credential when someone other than me runs the app.
  • Live two-way sync: edits in the editor and edits from Claude both update the same draft.
    (Narrowed for v1 — see note above: one-shot selection edits instead.)
  • Optional: drive the draft from a terminal Claude Code session (get/set the draft). (Deferred.)
  • The existing web app must stay green throughout (build + 100% coverage on its touched files).
  • Primary development on Windows 11; keep a cross-platform path open.
  • Secrets (the subscription token) never bundled or committed.

What are our options? 💡

Integration surface

  • A — Browser page + local bridge. A dev-only Vite plugin owning draft state, exposing WebSocket
    (editor sync), SSE (streamed completions), and an HTTP MCP endpoint. Rejected — substantial
    plumbing that exists only to defeat the browser sandbox.
  • B — Desktop app. Node process hosts the Claude integration and the draft directly. Chosen.

Desktop framework (given the Claude tooling is a JS library/CLI)

Axis Electron (chosen) Tauri
Claude on subscription Runs (or spawns claude) from the Node main process, zero glue No Node core → needs a Node sidecar, or fall back to spawning the claude CLI
Bundle / RAM ~80–150 MB, heavier ~5–15 MB (advantage erodes once a Node sidecar is added)
Design-system reuse Guaranteed Chromium renderer, pixel-identical to the site WebView2/WebKit → possible CSS drift on macOS
Repo fit Stays 100% in the pnpm+TS+Vite+Vitest toolchain Adds Rust + cargo to the monorepo/CI

Claude backend

  • Subprocess the claude CLI (chosen for v1) — inherits the CLI login with zero token
    wiring; every user (me or anyone else running the app) authenticates via their own claude login
    and uses their own subscription automatically. Non---bare claude -p reads OAuth/keychain.
  • Agent SDK on subscription — @anthropic-ai/claude-agent-sdk, authenticated with a
    CLAUDE_CODE_OAUTH_TOKEN from claude setup-token; more programmatic control (streaming,
    structured messages) but needs a per-user token-generation step. Kept as a future option if the
    CLI-subprocess approach turns out too limited.
  • Anthropic API key — separate pay-per-token billing; not wanted.

UI reuse

  • Shared design-system package (already done) — packages/design-system and
    packages/markdown are already extracted, publishable workspace packages; no extraction phase
    needed.
  • Fresh desktop UI — rejected, diverges from the site and duplicates components.
  • Alias hack — point desktop at apps/web/src/theme; rejected (reaches into another app's src).

Proposed solution 🟢

Electron app (apps/editor) whose Node main process owns file I/O and the claude CLI subprocess
bridge; a React renderer consumes @soroush.tech/design-system and @soroush.tech/markdown
directly (both already exist as workspace packages); typed IPC keeps them in sync.

apps/editor (Electron, electron-vite)
  renderer (React)  ── imports ──▶  @soroush.tech/design-system, @soroush.tech/markdown
       │  IPC (file:*, claude:editSelection)
       ▼
  main (Node)
       ├─ file store    (New/Open/Save/Save As via native dialogs)
       └─ claude bridge  spawns `claude -p` per edit request (stdin = selection, prompt = instruction)

Full breakdown, acceptance criteria, and file layout are tracked in the follow-up Epic (linked
below once filed) rather than duplicated here.

Risks & mitigations 🚨

  • Electron/IPC glue is hard to reach 100% coverage. Mitigate: unit-test pure logic (file
    handlers with fs/dialog injected, the Claude bridge's argv/output parsing); take the same
    e2e-only carve-out apps/web already uses for pages/, backed by Playwright-Electron smoke
    tests for the wiring itself.
  • Subprocess invocation quirks on Windows. claude -p piping large selections via stdin, PATH
    resolution for the claude binary from an Electron main process, and quoting on PowerShell.
    Mitigate: pipe text via stdin (not argv) to sidestep escaping, resolve the binary explicitly
    rather than relying on inherited shell PATH.
  • Unwanted context bleed. Running claude -p from a repo directory would load that project's
    CLAUDE.md/hooks/MCP config into every edit request. Mitigate: always run from a neutral,
    per-app scratch working directory, with --permission-mode dontAsk and empty --allowedTools so
    no tool use is possible regardless.
  • Electron footprint / signing / auto-update. Accept the larger bundle; defer code-signing for
    personal use.
  • Scope creep. Live two-way sync and the terminal-MCP leg are deferred, not built into v1.

Resources and benchmarks 🔗

  • Completed editor work this RFC builds on: soroush-tech/core issues [Task] Markdown editor compound (theme/Markdown) #267–[Task] Markdown toolbar icons (theme/Icon) #275 (theme/Markdown,
    CodeBlock, Quote, toolbar icons).
  • Conventions: packages/packages.md, packages/design-system/design-system.md,
    packages/markdown/markdown.md, .claude/skills/Electron/SKILL.md.
  • Prior monorepo restructure Epic: issues/epic-apps-monorepo-restructure.md.
  • Tooling: claude CLI (-p/--print, --output-format json, --permission-mode), electron-vite,
    Electron Forge, Playwright.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

status: needs triageIssue received and awaiting initial assessment to determine next steps.

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions