Feature documentation · code-grounded · 2026-07-05

oll-write — Feature Documentation

The writing app, all of it: a calm manuscript editor with a redline-preview AI op menu, and a per-user memory sidecar that writes from — and chats with — your own knowledge. Any model, pay once, private by construction. Siblings: the product thesis (why the market cell is empty) and the integration plan (how the two halves weld). This is the third: what actually ships, verified against the code.

"Your writing, all right. Any model, pay once."

What this is — and how to read it

Three panes, one surface. The middle is the editor you write in; the left is your knowledge (docs in, related notes out); the right is chat over only your corpus. Everything AI-shaped is a preview you accept or reject — nothing edits your prose behind your back — and everything private is scoped to you by the server, so the browser can never even name another person's corpus.

This doc is a reference, grouped by surface, and it is honest about the seam between what is wired today and what is still a plan (section 5). Where a feature was previously mis-reported as done, it says so.

◀ Left · Your Knowledge

Docs in, notes out

Add text or .md/.txt, search it, and see related chunks pushed to you as you type.

report.md · §Onboarding · 0.82"churn fell to 4.1% after the onboarding rework."
● Middle · The Editor

A calm manuscript

Floating cream page, warm ink. Select text → ✨ op menu. Hover a block → "Edit this idea". Every change previews as a redline you accept or reject.

▶ Right · Chat

Ask your corpus

Grounded answers with citation chips that jump to the source chunk.

answer · gpt · 3 sources"You raised the indemnity concern in 3 threads [1] doc_a4"

1 · The editor — the middle pane

The prose surface. Two AI gestures, one preview-and-commit model, and a signature "ink-in" reveal. Nothing here writes into your document without a preview you accept.

1.1 · The ✨ selection-op menu

Select any run of text and a floating bubble offers nine visible ops in three groups. Each op sends the selection plus its instruction to the generation path and returns a preview. (A legacy clarify op still works but is hidden from the menu.)

GroupOpWhat it does
EditImprove writingGeneral polish — clarity, flow, word choice — keeping meaning and voice.
Rewrite…Freeform: you type an instruction and it rewrites the selection to match.
Fix grammarGrammar, spelling and punctuation only; no stylistic drift.
Change tone ▸Submenu: Formal · Casual · Friendly · Professional · Confident — re-voices the selection to the chosen register.
(tone = 5 targets)
LengthShortenTightens to fewer words without losing the point.
ExpandDevelops the selection with more detail or explanation.
TransformBulletsReshapes prose into a bulleted list.
SimplifyPlainer language; lower reading level.
TL;DRCondenses to a one-line summary.

1.2 · Two gestures

Selection-op

Select → pick an op → get a result. The precise, surgical gesture: you point at exactly the text you mean and act on it.

"Edit this idea" — the flagship

Hover any block → a ✨ gutter button appears → type a plain instruction like "tighten this" → the paragraph is rewritten and shown as an inline word-diff. The whole-thought gesture, keyed to a single paragraph, no selection needed.

1.3 · Preview · Accept · Reject · Retry

The safety contract of the whole editor: the AI proposes, you dispose. Edit-shaped ops render a word-level redline diff; transforms render a plain preview. Nothing touches your document until you accept.
The onboarding rework was a thing that we did and it cut churn went down to to 4.1%.
✓ Accept · Enter / ⌘↵✗ Reject · Esc↻ Retry
gesture · edit-this-ideaprovider · groqmodel · llama-3.1-8b-instant

1.4 · The signature "Ink-In" animation

On Accept, the new text doesn't just pop in — it inks in via a CSS clip-path sweep, left to right, as if the page is absorbing the change. It is the one deliberately delightful moment in an otherwise calm surface, and it respects prefers-reduced-motion — the sweep is skipped entirely for anyone who has asked their system to reduce animation.

1.5 · Modes & chrome — the calm surface

Focus mode ⇧⌘F

Every block except the one under the caret dims. The sentence you're writing is the only lit thing on the page.

Manuscript ↔ Margin

Manuscript is the clean page. Margin mode works the AI marginalia in the right margin, joined to the text by leader lines — annotations beside the prose, not on top of it.

The top bar

The only chrome: wordmark, live word-count + read-time, model toggle, focus toggle. It idle-dims so the page is what you see.

Aesthetic: a floating cream page on a warm room, Source Serif 4 prose in warm ink, a quiet blue accent. The design language is documented separately; the point is that the tool disappears and the writing stays.

1.6 · Generation today — the two paths & the model toggle

How a click on an op actually becomes text — and where "any model" is literally wired, not just claimed.
Op firesselection / paragraph + instruction → /api/write proxy
Default pathproxies to the write-service (Core-JWT auth) — the production route
Direct pathWRITE_DIRECT_MODEL=true runs the op straight on the oll-model gateway (Groq · llama-3.1-8b-instant) — the self-contained local demo

The provider toggle — "any model," wired

A persisted switch flips every op between Groq (Fast / free) and Claude (Quality / pro), threaded through the whole op pipeline — so the promise "any model" is literally in the request path, not marketing. Tier gating (who gets Claude) is framing-only until billing lands; the plumbing is done.

2 · The memory sidecar — left + right panes

The half that makes it "write from your own knowledge." The left pane is your corpus (in and out); the right pane talks to it. Both live beside the editor and never interrupt it.

2.1 · LEFT · "Your Knowledge" — three sections

1 · Add to knowledge

Paste text, or upload .md/.txt. Each ingested doc shows in a list with its chunk count, so the corpus is legible, not a black box.

2 · Search your knowledge

A manual semantic search box for when you do know what you're looking for — returns ranked ChunkCards.

3 · As you write

The proactive lane: cards pushed from the editor based on the paragraph under your cursor — retrieval you didn't ask for.

Every result is a ChunkCard: a snippet + its document_id + an optional heading or page + a similarity score. Provenance is on the card, always — you can see where a memory came from before you trust it.

2.2 · Proactive retrieval — retrieval as a background sense

An always-mounted layer watches the caret. When you settle in a paragraph it retrieves related knowledge and pushes it to the left pane — no query typed. The discipline is in the guardrails, so it helps without nagging:

GuardrailBehaviour
Debounce 600 msWaits until you pause before firing — never mid-keystroke.
Ignores < 20-char paragraphsA half-written line isn't enough signal; it stays quiet.
DedupsWon't re-push the same chunk you're already looking at.
Top-5 from your corpusPrecision over recall — a short, dead-on list beats a long fuzzy one.
Fails silentlyIf retrieval errors, nothing happens — it never disturbs the writing.
The why: the moment that earns retention is "it remembered something for me I didn't know to search for." That only happens if retrieval is proactive — and it only stays welcome if it's precise and silent on failure. Both disciplines are in the code, not aspirations.

2.3 · RIGHT · "Chat" — talk to your corpus

Chat grounded in only your documents. Each answer carries citation chips[n] docId, hover to see the chunk text — plus a provider · model meta line, so every claim is one hover from its source and you always know which model answered.

answer · groq · llama-3.1-8b-instant · 3 sourcesYou flagged the indemnity clause across three threads in spring 2019 [1] doc_a4 [2] doc_9f [3] doc_c1.

MVP note: chat answers are not streamed — the full grounded answer arrives at once.

2.4 · Layout — editor-shell.tsx

On the desktop it's a 3-column grid; each side pane is independently collapsible, so you can write full-width when you want to. Below 1024px the side panes become drawers. Crucially, the middle editor is passed through untouched — the sidecar wraps the writing surface, it doesn't rebuild it.

3 · Identity, privacy & the data model — the trust boundary

The part that has to be right before anything else matters: your corpus is yours, and the architecture — not a policy — is what guarantees it.

3.1 · Core magic-link auth

Passwordless sign-in via Core magic-link. The identity is a 72h HS256 identity-only JWT in a readable oll_token cookie; the plan is read live from /api/auth/me (never baked into the token — mutable entitlement never rides in a token). The /editor route is gated by middleware; auth is on in production and off in local dev for a frictionless loop.

3.2 · Per-user scoped corpus — the one security rule

The browser can't name another user's corpus

On every memory call, the BFF derives collection = mem:user:{id} and acl = [u:{id}] server-side, from the authenticated Core id. The browser never supplies them and never holds the service token. So a user is architecturally incapable of naming someone else's corpus — the classic cross-user IDOR is closed by construction, not by a check that could be forgotten.

Local dev tokens map to a fixed dev corpus. The whole boundary is covered by a trust-boundary test suite, so a regression that re-opened it would fail CI.

3.3 · The BFF split

Browsercalls same-origin /api/memory/* only — never the memory service directly
Server-only clientholds X-Service-Token, pins the per-user collection + acl, then calls oll-memory

The rule that keeps it safe: the service token's env var must never be prefixed NEXT_PUBLIC_ — that would ship it in the browser bundle and re-open the IDOR. The split only works because the secret stays server-side.

4 · The stack under the hood

What each capability actually runs on. Two engines — generation and retrieval — behind one same-origin BFF.
ConcernRuns on
Generation (ops + chat)oll-model gateway /api/text/complete, privacy:"any"Groq by default (llama-3.1-8b-instant); Claude on the quality toggle.
Embeddings / retrievaloll-memory = LlamaIndex on pgvector, embeddings by Ollama nomic-embed-text — on-device / local, private. Hybrid dense + keyword fused with RRF.
BFF patternThin route handlers → pure, testable handlers with an injectable fetch. Chat composes retrieve → ground → generate inside the BFF, so the browser only ever sees a same-origin call.
Why on-device embeddings matter: your documents are embedded locally by nomic — the corpus never has to leave the machine to become searchable. That's the honest basis for both the privacy claim and the pay-once economics (on-device inference is ~$0/run). Details in the integration plan.

5 · Wired today vs deferred

The honest seam. Everything on the left is in the code and runs; everything on the right is a plan — and some of it was previously mis-reported as done, so it's named explicitly.

✓ Shipped — wired & running

  • 3-pane sidecar (collapsible panes + <1024px drawers)
  • Ingest: paste text + .md/.txt upload, with chunk counts
  • Manual semantic search
  • Proactive "as you write" cards + provenance (docId · heading/page · score)
  • Chat over your corpus + citation chips (hover = chunk)
  • Full ✨ op menu (9 ops) + "Edit this idea"
  • Word-level redline diff + Accept / Reject / Retry
  • Signature Ink-In animation (reduced-motion aware)
  • Direct-to-oll-model demo path (WRITE_DIRECT_MODEL)
  • Groq ↔ Claude provider toggle, threaded through every op
  • Core magic-link auth + per-user corpus scoping
  • Focus / Manuscript / Margin modes
  • Live word-count + read-time

○ Deferred — not wired yet

  • Drag-a-card-into-the-draft-as-citation is NOT wired — cards are read-only today (previously mis-reported as done)
  • Inline Plate citation node
  • PDF / binary ingest (UI takes text/markdown only — though the oll-memory backend already supports base64 PDF)
  • Streaming (chat answers arrive whole)
  • Draft-from-bullets
  • Gap-chips (ungrounded-claim flags)
  • Folder-sync / watch-sync
  • The 70b quality dial on chat
  • Production routing of chat generation through the write-service (chat is direct-to-oll-model in the demo)

The gap between the columns is the roadmap. The product thesis ranks which deferred items are the premium wedge (drag-to-cite, gap-chips, draft-from-bullets) versus retention plumbing (folder-sync, streaming).

6 · Headline capabilities

The five one-liners — each true today, each grounded in a section above.

Calm manuscript editor

Redline-preview AI ops + "Edit this idea" — the words ink in on accept. Nothing edits your prose without a preview.

Any model, pay once

A Groq ↔ Claude toggle wired through every op, persisted. "Any model" is in the request path, not the copy.

Write from your own knowledge

Ingest + semantic search + proactive related-notes as you type (600ms, top-5, fails silent).

Chat with your corpus

Grounded, cited answers over only your documents — every claim a hover from its chunk.

Private by construction

A per-user corpus the browser can't name, local nomic embeddings, and the service token never in the bundle.

"Your writing, all right. Any model, pay once."

oll-write — Feature Documentation · code-grounded reference · 2026-07-05. Siblings: the product thesis (the empty market cell) · the integration plan (how memory welds into the editor). Design language: oll-write design. Feeds Backlog + Strategy.

Rendered faithfully from the code as it stands; the deferred column (§5) is the honest edge of what is not yet wired. Any model, pay once, private by construction.