Plan for review · Atticus

Voice Lab — the room

The five-dimension voice profile with evidence quotes and Preserve / Neutral / Challenge mentor steering, planned against the canonical mockup.

Source of truth: .claude/plans/voice-lab-room.md
Canonical design: .claude/handoffs/atticus/mockups/voice-lab-60F26437/mockup.html — 19 KB, isCanonical: true, 2026-04-10, previously uncited by any plan.

Resolve before starting: two docs disagree on whether Voice Lab is in v1

atticus-implementation.md:171 lists Voice Lab under “Explicitly out of v1 scope” — “leave it stubbed for v1.” Its Phase 9 is Launch prep. Meanwhile replan.md, marked Status: Active. Supersedes the original Phase-4-onward roadmap, makes Phase 9 = “Voice Lab + polish.”

Every Phase 9 reference in the source follows the replan numbering. The master plan is also ~3 months stale — it claims a 352-test suite; we’re at 671. This plan assumes Voice Lab is in. If that’s wrong, stop — the master plan currently says don’t do this.

Scope decisions

DecisionResolution
Surfaces that honor the profileDraft synthesis, Critique findings, Composition advisor + ambient callouts. Discovery mentor turns excluded.v1
Deterministic gates (RegisterBaseline → VoiceCeiling)Left untouched. Voice Lab runs as a parallel qualitative layer.v1
Per-sample title + work typePrerequisite — the samples panel and quote attribution can’t render without them.v1
Drag-and-drop ingestAdded. Formats stay TXT / Markdown / RTF / DOCX.v1
Register filter (All work / Fiction / Essays)~3× analysis cost; needs more samples per type than a new writer has.deferred
“Profile updated” diff bannerNeeds profile version history plus a second LLM pass.deferred
Opt-in “include my Atticus drafts”—deferred
PDF · Google Docs linkDocs link is realistically its own plan (OAuth, fetch, token refresh).deferred

Consequence: mockup copy must be trimmed, not shipped as drawn

Three deferred features mean the register-filter row, the update banner, and the “Your recent drafts… Turn on” footer don’t render in v1. The upload zone’s format line must say .txt, .md, .rtf, .docx — not the mockup’s .txt, .pdf, .docx, or Google Docs link. A UI that promises formats it rejects is worse than a shorter list.

Why the deterministic gates stay untouched

VoiceCeiling ships today and actively regenerates drafts scoring above your register. Two reasons not to feed Voice Lab samples in:

  1. The scorer’s own calibration finding is that chat and prose don’t compare — which is why sentenceComplexity is weighted zero. A polished uploaded chapter sits far above a Discovery chat answer on lexical rarity too.
  2. Mixing samples in would silently raise the baseline and change which drafts get regenerated, with no visible cause — a regression that manifests only as “the drafts feel different.”

Voice Lab therefore influences prompts, not scores.

Phases

A · Sample model + true multi-sample store

No LLM, no UI. The current model holds one sample and actively deletes the others.

  • Latent bug — Remove the destructive pruning in writeVoiceSample. It runs delete(stale) on every sibling row. Correct for a single-sample UI; in a multi-sample world it eats the library on every edit. The most dangerous existing line for this plan.
  • Add title and workTypeRaw to VoiceSample + a VoiceWorkType enum with a computed wrapper — house convention, so new cases absorb via lightweight migration.
  • Idempotent backfill: title from filename minus extension, else “Untitled sample”; type .other.
  • New store methods: addVoiceSample (appends), removeVoiceSample, renameVoiceSample, setWorkType.
  • Add a sample-count ceiling (~12) so the analysis prompt has a bounded worst case. Refuse politely; never silently drop.
Deliverable: a sample library that can hold many samples without losing any.

B · The room

Renders what exists; no analysis yet. Shippable and honest on its own.

  • VoiceLabView replaces VoiceLabStubSurface — delete the stub rather than leaving both.
  • Two-column mockup layout. Map the mockup’s raw hex to AtticusTheme tokens; don’t reintroduce raw hex.
  • Samples panel: title · type · date, × remove with confirmation — it’s destructive action on writer material.
  • Upload zone: drag-and-drop, browse, paste — the drop path runs the same extraction and error surfacing as the picker.
  • Dimensions render in an unanalyzed state until Phase D: five names, no prose, one “Build my profile” action. No placeholder analysis.
  • Dimension rows are real disclosure controls — keyboard-reachable, correct expanded state, VoiceOver labels on remove and stance.
Deliverable: the Voice Lab room, managing a real sample library.

C · Analysis contract + quote verification

Pure, no LLM. Built and fully tested before anything calls a model.

  • VoiceDimension — exactly five fixed cases, not LLM-chosen. This is what lets your stance survive a re-analysis; the model fills in prose for known dimensions, it never invents the taxonomy.
  • MentorStance: preserve / neutral / challenge, defaulting to neutral.
  • VoiceAnalysis Codable value type with schemaVersion, stored as a JSON blob on VoiceProfile.analysisJSON — mirrors the voiceFingerprintJSON convention so the 5-dimension shape iterates without schema migrations.
  • Stances persist in a separate blob. A rebuild overwrites the analysis; losing a deliberate “Preserve my long sentences” because you added a sample would be unforgivable.
  • Load-bearing QuoteVerifier — every evidence quote must be a verbatim substring of the sample it’s attributed to, after normalizing whitespace, smart quotes, dashes, ellipses. Non-matching quotes are dropped, not displayed.

    This is your own prose. A subtly paraphrased quote — the failure mode LLMs are most prone to — is instantly visible to the one person who wrote it, and it destroys trust in the whole profile. Reuse the existing substring-anchoring logic rather than writing a third one.

Deliverable: a verified contract that cannot display prose you didn’t write.

D · The analysis service

The LLM pass. Where the product risk lives.

  • VoiceAnalyzing protocol test seam, mirroring DiffSummarizing. Non-streaming: a discrete background job, not a conversation.
  • Input budgeting. 12 × 20,000 chars ≈ 60k tokens — too much. Subsample each sample (head slice + middle slice, so excerpts aren’t all opening paragraphs) under a ~12k-token ceiling. This is the subsampling the store’s cap comment defers to the consumer.
  • Minimum-material gate, mirroring RegisterBaseline.minimumContentWords — below the floor, say so plainly rather than producing confident prose from two sentences.
  • Trigger: explicit rebuild + debounced auto-rebuild on sample change. Never on view appearance — that bills you for opening a tab.
  • A failure must not clear the previous profile. Show the stale one with a quiet “couldn’t refresh” and a retry.
Deliverable: a real voice profile, built from your work, with verified evidence.

E · Steering reaches the prompts

The phase that makes this more than a mirror.

  • VoiceDirectiveBuilder compiles analysis + stances into a compact prompt block. Neutral dimensions emit nothing — only your deliberate choices speak, which keeps the default cheap and the block small.
  • Wire into DraftGenerator (replacing the transcript-derived voice sketch — DraftGenerator.swift:6 already says “Voice Lab in Phase 9 replaces this”), Critique (a Preserve on rhythm must stop “break up these long sentences”), and Composition advisor + ambient callouts.
  • Place the directive in a cache_control: ephemeral block — it’s stable across turns, so cache it rather than re-billing per message.
  • Write 5 × 3 = 15 consequence strings. These are a promise about behavior — they must describe what the directive actually instructs, or the UI is lying.
  • Document the exclusions at each site so a future contributor doesn’t “finish” it by accident.
Deliverable: your stated preferences demonstrably change what the mentor says.

F · Integration + validation

Including the one step tests can’t cover.

  • End-to-end: build a profile, verify quotes trace to samples, flip a stance, confirm the downstream change in Critique and a generated draft.
  • Owner: Sarah Writer-validation pass — is the analysis accurate about your voice? The one thing tests cannot answer.
  • Blueprint updates, plus correct the stale claim at RegisterScorer.swift:30 that “Phase D calibration confirmed” the weights — that was analytical, not fixture-calibrated.
  • Resolve // TODO(lens-shift) at ConceptViewModel.swift:1500, which replan.md gates on Voice Lab landing.

Final review

Pros

  • Sequenced by real dependency — model → UI → pure contract → LLM → integration. Phases A–C ship value with no model call.
  • Quote verification is deterministic. The most credibility-destroying failure is closed by a substring check, not prompt politeness.
  • Stances are structurally protected from re-analysis — the decision most likely to be gotten wrong by default.
  • Zero regression risk to shipped synthesis. Worst case for existing behavior is “no change.”
  • Phase A fixes an active latent bug that would delete writer material the first time a second sample existed.

Cons & where this may be inadequate

  • The core deliverable is unverifiable by tests. Every test can pass while the profile reads as horoscope copy — the specific way this fails in the market. Phase F is the only defense, and it’s the same writer-owned step that has stalled synthesis-quality-gates.md for three months. This plan will hit the same wall.
  • A new coherence risk. VoiceCeiling may regenerate a draft for being “elevated” while Voice Lab says Preserve on word choices. Independence avoids regression but doesn’t make them agree — same shape as the Callouts-vs-Critique conflict.
  • Fixed five dimensions are rigid. A poet gets a “How you build scenes” row.
  • Deferring the register filter weakens the design’s argument — “your recent fiction uses more fragmented structures” is one of its best lines, and both it and the diff banner are cut. v1 will feel flatter than the mockup reads.
  • Cost is real and unbounded per writer beyond debouncing. And Challenge may be weak — three positions, possibly two reliable behaviors.

Adjustments worth considering

  1. Ship A–C first and stop to look. The library plus a verified contract is useful and costs no model spend — and it exercises the quote verifier against your real writing before any prose is generated.
  2. Do the validation pass during Phase D, not after E. If the analysis reads generic, the Phase E prompt work is built on sand.
  3. Consider two dimensions instead of five for v1. Sentence rhythm and tonal range are the two the mockup expands and the two most likely to land. Five rows where three are thin reads worse than two that work.
  4. Revisit the register filter before the diff banner — cheaper, and carries more of the design’s argument.
72/100
Confidence. The architecture I’d defend: phase sequencing, the quote verifier, the stance/analysis separation, leaving the deterministic gates alone. The 28 points of doubt sit in one place — whether an LLM pass over a handful of samples produces prose specific enough to be worth showing a writer about their own work, and whether stance steering reliably changes model behavior. Both are empirical, both have a precedent in this repo of stalling at exactly that validation step. The engineering is tractable; the product risk is real and lives in Phase D.