Project and document scope#
Projects and documents use immutable application-generated UUIDs. Mutable names and titles are display labels only. Every persistence and agent operation carries { projectId, documentId }; repositories verify that the document belongs to the project before reading or writing.
Document workspace files live at projects/<project-id>/documents/<document-id>/, including sources/ and .pi/sessions/. The accepted draft itself is persisted as BlockNote block JSON in SQLite. SQLite schema version 6 scopes sources, suggestion projections, command receipts, immutable suggestion history, checkpoints, event streams, and consumer cursors to the document. The selected identities are persisted in the singleton workspace settings row and validated on startup. During alpha, incompatible database versions are intentionally not migrated; delete the local database to recreate the current schema.
The renderer treats a selected document as a keyed session. A switch flushes document and suggestion queues, stops the old agent, removes preview state, selects and hydrates the target, and only then enables the target state. Async controllers compare their captured session identity before applying completions. The main process replaces the document-specific Pi process on selection so its working directory and session history cannot cross document boundaries.
System boundary#
The React renderer has one application composition: Electron supplies a required preload bridge, main-process orchestration, a SQLite utility process, and a Pi utility process. Vite serves that renderer inside Electron during development and builds it for file:// loading in production. There is still no routing library.
View Mermaid source
flowchart LR
User[User]
Shell[App composition root]
WorkspaceController[Workspace controller]
Editor[BlockNote editor]
Suggestions[Suggestion controller]
Dock[Writing partner UI]
Pins[Workspace cards]
Storage[(localStorage widths)]
Bridge[Electron preload bridge]
Main[Electron main]
Database[SQLite storage process]
Agent[Pi agent process]
User --> Shell
Shell --> Editor
Shell --> WorkspaceController
WorkspaceController --> Suggestions
Suggestions --> Dock
Suggestions --> Pins
Dock -->|select, pin, dismiss, preview| Shell
Editor -->|preview accepted/cancelled| Suggestions
Shell <--> Storage
Shell <--> Bridge
Bridge <--> Main
Main <--> Database
Main <--> Agent
Agent <--> DatabaseDesktop queries, commands, and committed events use the typed DesktopBridge contract. The suggestion controller derives optimistic projections through the same pure transition policy used by storage, then reconciles authoritative revisions and command IDs from storage events.
Composition root#
App.tsx is the layout composition root:
useCreateBlockNotecreates the editor fromwritingSchemaand seeded content.useWorkspaceControllerowns hydration, serialized autosave, desktop subscriptions, agent controls, inbox integration, and preview coordination.useWorkspaceLayoutanduseWorkspaceKeybindingsconnect controller actions to the responsive layout.Appcomposes the editor, sidebars, dock, drawers, and keyboard surfaces.
The workspace controller owns one desktop event subscription. It routes suggestion events into the suggestion controller and applies other desktop events to their feature controllers.
State ownership#
| State | Owner | Lifetime / persistence |
|---|---|---|
| Editor blocks and selection | BlockNote editor created in App |
Selection is in-memory; accepted blocks autosave in Electron |
| Inbox, pins, command queue | useSuggestionController |
Projection persists in Electron; optimistic state lives for the renderer session |
| Selected detail, preview identity, stale/withdrawn flags | useSuggestionController |
Renderer-only and reset on hydration/document switch |
| Workspace pin geometry and z-order | Suggestion transition policy | Persists in Electron |
| Hydration, autosave queue, sources, last text cursor | useWorkspaceController |
Current page; durable values cross the desktop bridge |
| Workspace panels, drawers, and column widths | useWorkspaceLayout |
Panel state is in-memory; widths use localStorage |
| Keyboard sequence and suggestion target | Keybinding and suggestion navigation hooks | Current page only |
| Documents, imported sources, suggestions | SQLite/storage process | Electron application data and managed project workspace |
| Agent model/auth configuration | Pi coding-agent | Native settings.json, auth.json, and models.json under <userData>/pi; environment credentials also resolve |
| Agent session/loop state | Pi SessionManager and Scribe extension |
Project-scoped .pi/sessions JSONL |
| Agent start/stop state | Agent utility process | Current launch only; every app launch starts stopped |
| Canonical agent status and error | AgentRuntime, reported by the agent through Electron main |
Current launch only |
| Activity diagnostics | Electron main | Current-launch 500-item memory ring |
| Draft title, tab, source, and navigation data | Component constants | Static |
There is intentionally one owner for each lifecycle. Components such as SuggestionDock, WorkspacePins, and DocumentHeader receive values and callbacks; they do not own duplicate application state.
Module boundaries#
src/renderer/features/editor#
schema.tsxextends BlockNote with thesuggestionPreviewblock and implements accept/cancel behavior.previewEvents.tsis a small in-process event bridge from the custom block renderer back toApp.
The editor layer knows suggestion IDs, but it does not import or mutate inbox state.
src/renderer/features/keybindings and src/renderer/features/workspace#
commands.tsdefines stable semantic command IDs independently of keys and handlers.defaultKeymap.tscontains the fixedCtrl+;keymap;sequenceMatcher.tsresolves partial and complete sequences without React or DOM dependencies.useKeybindingController.tsowns global keyboard capture and timing.useWorkspaceKeybindings.tsis the only adapter allowed to coordinate editor, layout, and suggestion actions.useWorkspaceLayout.tsowns responsive panels, drawers, widths, and their shared imperative operations.useWorkspaceController.tsowns renderer orchestration: hydration, autosave serialization, desktop events, agent controls, inbox persistence, and preview coordination.
The command strip and shortcut dialog read the same command catalog and default keymap used for execution. This prevents discoverability copy from drifting away from the active bindings and leaves a clean keymap-replacement seam for future configurability.
Suggestion domain and renderer feature#
schema.tsdefines suggestion data and agent event variants.state.tsdefines the persisted projection, empty state, 30-entry limit, and shared eviction policy used by renderer and storage.aggregate.tsis the pure suggestion aggregate and strict projection reducer. Storage records versioned intent, appends immutable facts, advances the projection, and stores the receipt in one transaction. Rebuild and repair use the same reducer; no application service writes the projection directly.transitions.tsimplements the durable command and agent-event policy shared by renderer and storage.useSuggestionController.tsowns optimistic commands, authoritative reconciliation, and transient selection/preview presentation.inbox.tsexposes renderer-facing suggestion types and selectors.workspacePinLayout.tssupplies type-specific initial card sizes.
The durable transition policy is pure and React-independent so storage and optimistic rendering cannot drift. Stale and withdrawn flags are presentation-only because they protect transient detail/preview state rather than durable projection state.
Runtime roots#
src/main/index.tsowns Electron lifecycle, renderer IPC, utility processes, revision forwarding, and the activity ring.src/utility/storage/index.tsis the storage utility-process entry point; storage behavior lives underapplication/,persistence/, andworkspace/.src/utility/agent/index.tscreates the durable Pi coding-agent session and drives user-enabled autonomous cycles. Revisions continue to coalesce while the agent is stopped.src/utility/agent/extension.tsdefines suggestion/yield tools and persists extension loop state.src/preload/index.tsexposes the typed desktop API.desktopClient.tsprovides renderer-side access to the preload bridge.src/contracts/desktop-bridge.tsis the cross-process contract.
Renderer features and UI primitives#
EditorWorkspace.tsxjoins the document header and editor surface.DocumentEditor.tsxrenders BlockNote and calculates initial workspace-card placement.SuggestionDock.tsxcomposes focused activity, detail, and queue views.WorkspacePins.tsxrenders desktop cards and handles bounded pointer/keyboard geometry.DocumentHeader.tsxexposes responsive panel controls and document action placeholders.ResponsiveDrawer.tsxprovides reusable below-desktop modal panel behavior.ColumnResizeHandle.tsxprovides reusable pointer and keyboard column resizing.SuggestionPresentation.tsxrenders kind badges and structured suggestion visuals.MermaidDiagram.tsxlazy-loads Mermaid and renders an accessible fallback on failure.Sidebar.tsxrenders the static project navigation shell plus the persisted Electron source list and upload callback.
Components rely on their props for application actions. When adding behavior, prefer moving data and transitions into the relevant owner rather than making a display component stateful.
Renderer bootstrap sequence#
The renderer refuses to construct App without the Electron bridge. The complete process startup is documented separately in Desktop persistence and Pi runtime.
- 01Render
main.tsx mounts App inside StrictMode.
- 02Create
App creates the schema-backed editor and layout controller.
- 03Connect
The workspace controller creates the stable desktop feed and inbox.
- 04Hydrate
The controller restores accepted blocks and persisted workspace state.
- 05Subscribe
Suggestion events and AgentRuntime updates use separate contracts.
- 06Autosave
Accepted editor changes enter the serialized save queue.
- 07Reduce
Committed suggestion events enter the inbox state machine.
View Mermaid source
sequenceDiagram
participant Main as main.tsx
participant App
participant Controller as useWorkspaceController
participant Editor as BlockNote
participant Bridge as Electron bridge
participant Suggestions as useSuggestionController
Main->>Bridge: require preload bridge
Main->>App: render inside StrictMode
App->>Editor: create schema-backed editor
App->>Controller: connect desktop and editor
Bridge-->>Controller: committed suggestion event
Controller->>Suggestions: reconcile state/revision/command IDData direction and dependency rules#
Runtime code follows an inward dependency rule across Electron main, preload, renderer, storage, and agent:
Inbox and Pins can exit through dismiss or an accepted preview; normal live items can also be retracted.
View Mermaid source
flowchart TD
I["Infrastructure: Electron, SQLite, files, Pi, RPC"] --> A["Application operations and ports"]
A --> D["Pure domain policies"]
A --> C["Shared process contracts and value types"]
D --> CThe boundaries are concrete rather than mirrored framework folders:
- Put cross-process schemas, operation registries, and bridge-facing types in
src/contracts/. - Put runtime-neutral product policy shared by more than one runtime in
src/domain/and test it without Electron, React, SQLite, files, or Pi. - Put renderer-owned UI, hooks, and browser-only adapters under
src/renderer/, grouped by feature first andui/only for reusable primitives. - Put window, dialog, process supervision, IPC routing, and diagnostics in
src/main/. - Put preload bridge exposure in
src/preload/and keep it limited to Electron renderer APIs plus contracts. - Put storage use cases and ports in
src/utility/storage/application/; SQLite repositories, migrations, durable JSON mapping, outbox dispatch, and backups insrc/utility/storage/persistence/; workspace file and identity handling insrc/utility/storage/workspace/. - Put agent loop policy in
src/utility/agent/domain/, Pi SDK conversion and sessions insrc/utility/agent/pi/, and Scribe tool integration at the agent utility-process boundary.
scripts/source-boundaries.test.mjs enforces that runtime-neutral modules stay free of runtime implementations, renderer code does not import privileged processes, preload stays narrow, and storage/agent utility layers do not reach across foreign runtimes. Runtime entry points are composition roots: they construct each required adapter and contain no product policy.
The intended dependency direction is:
contracts + shared domain
↑
runtime application logic
↑
runtime entries and platform adaptersPractical rules:
- Cross-process types and schemas belong in
src/contracts/, not in UI components or runtime entries. - Durable suggestion lifecycle changes belong in
src/domain/suggestions/transitions.tsand should have transition tests. - Runtime-neutral revision, cursor, activity-redaction, or aggregate rules belong in
src/domain/when more than one runtime uses them. - Transport, model SDK, and Pi session code belongs in the agent utility process behind storage operations.
- Cross-feature renderer orchestration belongs in
useWorkspaceController;App.tsxremains responsible for layout composition. - CSS layout variables are set by
Appbut interpreted bysrc/renderer/index.css.
Naming and placement rules#
- Use kebab-case directory names for new folders.
- Keep React component filenames in PascalCase.
- Keep hooks in
useSomething.tsoruseSomething.tsxbeside their owning feature. - Give each module one primary owner: contracts, domain, renderer feature, main, preload, agent utility, or storage utility.
- Put a module in
src/contracts/only when its schema or type crosses a runtime boundary. - Put a module in
src/domain/only when it is runtime-neutral product policy used by more than one runtime. - Put renderer code under the feature that owns the behavior; use
src/renderer/ui/only for visual or interaction primitives that are not owned by one product feature. - Keep executable entry modules thin and make their runtime obvious from their path.
- Prefer direct imports from the owning module over broad re-export trees.
Styling architecture#
Tailwind CSS 4 is loaded through the Vite plugin and @import "tailwindcss" in index.css. Most component styling is inline utility classes. The global stylesheet is reserved for:
- theme tokens and brand colors;
- base focus and typography rules;
- the responsive three-column grid;
- BlockNote variable and content overrides;
- the custom suggestion-preview block;
- Mermaid SVG sizing.
BlockNote's shadcn stylesheet is imported by DocumentEditor.tsx. Its utility classes are made visible to Tailwind's scanner with @source "../node_modules/@blocknote/shadcn".
Build and runtime assumptions#
- TypeScript is strict and emits no files during type-checking.
- Vite targets a browser application; there is no server-side rendering guard around browser globals.
- Vite's
baseis./because the production renderer is loaded fromfile://; changing it back to/breaks all built renderer assets in Electron. - Electron main, storage, and agent bundles are ES modules. Main must finish evaluating before Electron can become ready, so application startup is registered as a promise continuation rather than awaited at module scope.
- Mermaid is a dynamic chunk because
MermaidDiagramimports it lazily. - Google Fonts are external runtime requests. Font failure degrades to local fallbacks.
dist/,dist-electron/,release/,test-results/, anddocs/html/are generated outputs; application source code lives undersrc/.- Files in
artifacts/are not imported and have no runtime effect.
Architectural invariants#
Changes should preserve these unless the design is deliberately revised and documented:
- Only one editable suggestion preview can exist at a time.
- Preview content is user-owned once inserted; feed updates never overwrite it.
- Pinned suggestions are frozen snapshots and ignore later feed updates or retractions.
- The live inbox holds at most 30 entries; pinned and workspace entries do not count toward that limit.
- Selected and previewed entries are protected from queue eviction.
- Desktop panel state and mobile drawer state are separate.
- Workspace geometry is clamped to the current editor canvas.