Skip to content

Writing phase 4 + 5: the Writing tab, and count-only usage telemetry - #1871

Merged
r3dbars merged 24 commits into
mainfrom
claude/writing-phase-4
Sep 28, 2026
Merged

r3dbars merged 24 commits into
mainfrom
claude/writing-phase-4

Conversation

@r3dbars

@r3dbars r3dbars commented Sep 25, 2026

Copy link
Copy Markdown
Owner

Phases 4 and 5 of docs/writing-plan.md, stacked on #1870. Telemetry (phase 5) is folded in because the tab calls it.

What's in it

  • Sidebar: Writing sits after Dictations on ⌘4, with a quiet "New" badge. Speakers moves to ⌘5 and Agent to ⌘6, and the Go menu and all pinned tests are updated.
  • Writing tab: built to the approved design and copy.
    • Intro: two pages, including a SwiftUI demo loop that holds still under Reduce Motion, plus a "Not now" link.
    • Setup: three steps with validation. Step 3 shows only the rows the step 1 toggles need, and Qwen is greyed out under 16 GB.
    • After "Turn on writing": the keyboard, then the model download, then Screen Recording last. It's never offered while recording or transcribing.
    • Everyday view: status, today's saved writing, stats, pause (which stops suggestions, saving and screen reading), delete all, storage, the model switch, and a personalized-suggestions toggle (default off). If a day file can't be saved, it says so.
  • Activation: Writing starts from real setup state, not the debug default. Turning both features off stops it. Autocomplete off means no model, helper or screen reading.
  • Permission copy: meetings need only System Audio Recording, and full Screen Recording is only for Writing's autocomplete. Screen Recording is not in the global permission list, so meeting-only users never see it.
  • Telemetry:
    • writing_daily_counts: yesterday's counts, once a day, only when something was shown or accepted.
    • writing_setup_completed: first setup only.
    • Counts and enums only, under the analytics toggle. Taxonomy files and the privacy doc are updated in lockstep.
  • "Tilde" wording: user-visible runtime strings now use Transcripted/Writing copy.

Verification (local)

  • build.sh, codesign --verify --deep --strict and the beta DMG build: pass.
  • run-tests.sh: 19232/19232.
  • swift test: 782 Writing tests (Swift Testing). The full XCTest suite and the tool packages passed in the pre-fix gate on this branch.
  • Package tests: CaptureKit, CLI, MCP and QA pass. run-e2e-smoke.sh passes.
  • Checks: analytics emitters, taxonomy, telemetry keys, source pins, parity, no relaunch code, and the real user log untouched.
  • Independent review (Fable 5.1): fix-first on two items, both fixed here: a "Not now" that clears the badge, and no main-thread signature check on every activation. Its recommended items are fixed too: first-setup-only funnel, pause stops screen reading, hold copy, accessibility actions, and Reduce Motion on the page transition.

Not done yet: nobody has clicked through the tab on a real Mac. That's Justin's checkpoint.

🤖 Generated with Claude Code

r3dbars and others added 17 commits September 25, 2026 15:57
Writing sits after Dictations on ⌘4; Speakers moves to ⌘5 and Agent to ⌘6.
The row shows a quiet "New" badge, styled like the footer's update badge,
until WritingSidebarNewBadge.dismissedDefaultsKey is set. The shell routes
.writing to a placeholder WritingSettingsPage that the page work replaces.

Updates the exhaustive switches (refresh policy, discovery area, Go menu,
focus order), the pinned tests, UISmoke, and the UI CLAUDE docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n Recording

Meetings still only need System Audio Recording. Full Screen Recording is
now only for Writing's autocomplete, if you turn it on, so the migration
guide no longer tells everyone to turn the broader permission off.
Screen Recording stays out of TranscriptedPermissionKind; Writing owns it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…toggle

writing_daily_counts reports the previous whole local day once, from the
text-free outcome ledger summary: suggestions shown and accepted, a words
bucket on the word_count_bucket boundaries, and the setup enums. The day is
claimed in the Writing app suite before the ledger is read, so launch and a
quick wake can't double-send, and idle days send nothing. It runs from the
controller's launch and wake hooks. writing_setup_completed gets its emitter
for the Writing tab to call later.

Both events are registered in the psv taxonomy and the privacy doc. Fast tests
cover the day boundary (including DST), no re-emit, zero-day skips, exact keys,
enums, and the sanitizer round trip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ettings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Writing now starts at launch once the Writing tab's setup is done and
Save my writing or Autocomplete is on (the WritingDebugEnabled default
still starts it for development). The tab can start it right after
"Turn on writing" and stop it when both features go off; a later start
builds a fresh runtime. After the quit nothing starts again.

Autocomplete alone runs the model, the llama-server helper and Screen
Memory: with only Save my writing on, nothing downloads and nothing on
screen is read, and turning Autocomplete off stops the download and the
helper. The controller also gains what the tab needs: pause and resume
(which pause saving too), a keyboard turn-on that selects the input
source every time it's asked, the Screen Recording settings link, and
a sizes-only storage reading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The held-back, menu-detail, engine and history status lines the Writing
tab can show still named Tilde and its "Screen Access". They now say
autocomplete, Transcripted and Screen Recording. The tests that pin the
strings follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The approved design, built in SwiftUI: two intro pages (the second with
a looping autocomplete demo that holds still under Reduce Motion), the
three setup steps, and the everyday view with today's saved writing,
the autocomplete numbers and a settings section (the two features,
personalized suggestions, the model switch, a storage meter and Delete
all writing with a confirm).

"Turn on writing" saves the choices, turns on and selects the keyboard,
starts Writing (which starts the model download), then asks for Screen
Recording last. That ask, and the tab's later one, waits while a
meeting or dictation records, since macOS may ask Transcripted to quit
and reopen after the grant. It also records writing_setup_completed and
clears the sidebar's New badge.

The copy lives in WritingSetupPresentation and the demo in
WritingDemoScript, both Foundation-only. The shell only routes: it hands
the page the controller and the recording check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g tab

Fast tests pin the approved copy word for word, which step 3 rows show,
the keyboard badge, validation, the everyday summary and status lines,
the app-chip order, the demo script, the day-file reader, when Writing
runs, and the storage meter. The helpers join APP_SOURCES.
UIAutomationSurfaceContractTests pins the tab's automation identifiers,
its routing, and the rule that Screen Recording is never asked for while
anything records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…once

The Settings window stays alive when closed, so the Writing tab's
refresh timers and the intro demo now idle unless its window is on
screen. "Turn on writing" gives Text Input Sources a second to list a
just-registered keyboard before falling back to Keyboard settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
llama-server --version on the pinned helper reports version 0.2.0-dev
(build 1, commit 2115b73), AppleClang 21 for Darwin arm64. The ledger
follow-up now carries a reproducible build recipe to confirm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hase-4

# Conflicts:
#	Sources/Writing/WritingController.swift
- The everyday view says "Writing couldn't be saved to this folder" while
  Save my writing is on and the day-file recorder has a write failure.
- Ledger rows for the replaced Tilde settings, setup and stats views point
  at their Writing tab files; Sources/UI/Settings/CLAUDE.md no longer calls
  the page a placeholder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Intro gets a quiet "Not now" that leaves Writing off and clears the
  sidebar's New badge (plan: the badge stays until setup finishes or the
  intro is dismissed).
- App activation refreshes keyboard state only while the Writing page is on
  screen; refreshKeyboard validates code signatures on the main thread.
- writing_setup_completed fires on the first setup only, not on Edit setup.
- Pause also stops Screen Memory, so a paused Writing reads nothing.
- The Screen Recording hold line says "Finish recording and transcribing
  first", matching the hold condition (queued transcription counts).
- Entry rows and the collapse control get accessibility actions; the page
  transition respects Reduce Motion.
- The plan's everyday view documents pause and Not now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
r3dbars and others added 7 commits September 25, 2026 20:15
An entry under 3 words (a quick "sounds good") no longer ends at a
segment break when the same app's next segment starts within a minute.
It folds into that entry, each segment on its own line. App switches,
2 minutes idle, and deletions still split as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
macOS 26 returns noErr from TISEnableInputSource and leaves the keyboard
off, so the enable now re-reads the source and returns .needsUserToAdd,
and WritingController logs what actually happened.

A new WritingKeyboardSetupState (selected, enabledNotSelected,
needsUserToAdd, needsRelogin) drives setup step 3 and the everyday
"Keyboard off" row with plain steps and an Open Keyboard Settings button.
needsRelogin comes from a persisted marker: the login session (boot time
plus audit session ID) the app first copied the keyboard in.

The tab re-checks on activation and on the Input Sources notifications,
only while it's on screen, and selects the keyboard once the moment it
becomes enabled. Only the button opens System Settings; Turn on writing
no longer waits on or opens anything for the keyboard.

Tests: fast-test copy per state; Swift Testing resolution, enable and
auto-select tests with a fake Input Sources provider. Plan doc updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every agent runs its own MCP server on one index. After an update (or with
two Transcripted builds side by side) an old server still has it open when a
new one trips the schema gate. Deleting the file under that connection left
the old server failing every query with "disk I/O error" until restarted,
and the old server then stamped its lower version back, so the next new
server rebuilt again. Now the gate drops the tables in one transaction, and
opening only ever raises user_version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1872)

* Writing phase 6 (P6-D): user docs and agent prompt for Writing

- AgentConnectionGuide: list <capture-library>/writing/ next to meetings
  and dictations in the starter prompt, Codex setup prompt, copied folder
  paths, and Codex inbox AGENTS.md; search "meetings, dictations, and
  writing together". Uses the pure FileManager.writingDirectory(in:) so
  building a prompt never creates the folder. Tests updated.
- README: a Writing bullet, the writing/ folder, model disk and memory
  needs, a Privacy line for suggestion counts, and a new Uninstall section
  that removes the keyboard.
- agent-connect.md: list_writing/read_writing, kind "writing" on
  search_context/recent_context, the writing fallback folder and
  TRANSCRIPTED_WRITING_DIR.
- Ledger: drop the AgentConnectionGuide follow-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Writing phase 6: cask zap, "wrote" wording, Agent page writing folder

- The Homebrew cask's zap removes the Transcripted keyboard and Writing's
  two preference domains.
- MCP tool descriptions say what the user wrote, not typed.
- The Agent settings page lists the Writing folder next to Meetings and
  Dictation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Writing docs: llama-server provenance verified by an exact rebuild

A from-source build of llama.cpp 2115b73 with Command Line Tools 26.6
reproduces the pinned unsigned code hash 3f6895ab… exactly (twice, fresh
clones). docs/llama-server-provenance.md records the recipe, what it
depends on (toolchain, GGML_NATIVE=OFF, prefix map, the web UI snapshot)
and why build-deps should keep pinning Tilde's binary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Writing phase 6 (P6-H): keep manifest-writing tests out of the real app support

TranscriptedStoragePathsTests used to snapshot the manifest at
transcriptedMCPDirectoriesManifestURL and write it back afterwards. Under
run-tests.sh that URL is the throwaway TRANSCRIPTED_CONTAINER_DIR, but the
test itself never checked that, so run anywhere else it would rewrite
~/Library/Application Support/Transcripted/mcp-directories.json. Drop the
rewrite-restore. Fail closed unless the manifest URL resolves under the test
container, read the parity suite's manifest from that checked URL, and end
with a guard that the real manifest's mtime and size didn't change.

The home recent-captures benchmark had no container override at all: its
loader and its cleanup both go through transcriptedCaptureLibraryDir, which
rewrote the real manifest (to the benchmark process's own default library,
not the user's), and the loader filled the real Home metadata cache. Point
TRANSCRIPTED_CONTAINER_DIR at the run's scratch folder before any storage is
touched, and refuse to run if the override doesn't take.

No production change: the writer already takes manifestURL, and the
container root is already overridable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: r3dbars <r3dbars@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Brings in the Notch island work and the timing-test deflake (#1878).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@r3dbars
r3dbars merged commit 3f37c06 into main Sep 28, 2026
8 checks passed
@r3dbars
r3dbars deleted the claude/writing-phase-4 branch September 28, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant