Writing phase 2: autocomplete runs inside Transcripted (behind WritingDebugEnabled) - #1869
Merged
Merged
Conversation
Transcripted gets a third capture feature, Writing, next to Meetings and Dictations: Tilde's local autocomplete keyboard ported with exact behavior, plus optional writing capture saved as plain Markdown in the capture library. The plan records the decisions made with Justin, the approved Writing tab copy, a parity checklist against Tilde f36f6562, where the code lands, build and release changes, storage, agent tools, analytics, six phases with exit criteria, and risks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Six project subagents for the Writing port: writing-porter (Opus 5.5, xhigh), writing-porter-fable (Fable 5.1, xhigh) for the keyboard, socket auth and Screen Memory, writing-builder (Opus 5.5, high), writing-worker (Opus 5.5, medium), writing-reviewer (Fable 5.1, max, read-only) and writing-scout (Opus 5.5, low). They share one set of ground rules. docs/writing-port-ledger.md maps every shipping Tilde file and test at f36f6562 to its Transcripted destination, phase and status (107 source rows, 92 test rows), plus the identity rename table and the pinned llama-server code hash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build-deps.sh fetches Tilde 0.1.0 beta 1's release zip, checks its hash, lifts Contents/Helpers/llama-server out, strips Tilde's signature and checks the code-bytes hash before installing deps-tools/llama-server. Either mismatch stops the build. build.sh and build-beta.sh require the helper, copy it to Contents/Helpers/, and sign it in the existing Helpers loop. Both sign steps now sign any Contents/Library/Input Methods/*.app before the outer app (hardened runtime, timestamp and the new, empty keyboard.plist entitlements in the beta build). The loop does nothing until a keyboard bundle exists. The app compile skips Sources/TranscriptedKeyboard/. PackagedAppSmoke checks the helper is present and passes codesign. Adds llama.cpp's MIT license and routes the Writing, keyboard and entitlements paths in the test matrix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The static fixture ships a llama-server helper so the passing case stays green, and two new cases fail on a missing helper and a bad signature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ports the 46 `todo` Sources/TildeCore rows of docs/writing-port-ledger.md verbatim into Sources/TranscriptedWriting/Core, same subfolders and file names, and marks them `ported`. The five not-ported rows (PreviewModelAsset, PsychicReplay, ReplayEval, H01BlockRandomization, PreviewModelChoice) are not copied. 44 files have no parity drift. The two that do: - TildeProductProfile: the ledger renames (bundle IDs, connection name, Transcripted/writing support dir, "Transcripted Keyboard.app", display name, llama port 17891, keychain service com.justinbetker.draft.writing.personal-history), and the ledger's "drop preview-build profiles" strip: .preview26B and .modelPreview go, .production and .preview9B (the Qwen behaviour profile) stay. - SuggestionArbiter: PersonalReplayEval.normalizeWord inlined as a private helper, since ReplayEval isn't ported. Package.swift gains a dependency-free TranscriptedWritingCore target at Sources/TranscriptedWriting/Core. build.sh already compiles these files into the app module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ports 45 of Tilde's 46 todo core test files to Tests/TranscriptedWritingTests/Core with the ledger renames, and adds a TranscriptedWritingTests Swift Testing target in Package.swift. Trimmed only what tests dropped types: the .preview26B/.modelPreview profiles (SceneEchoPolicyTests, TildeProductProfileTests) and PreviewModelChoice (TildeProductProfileTests). RedactionCorpusSanityTests stays todo: its jsonl corpus fixture is not in the f36f6562 export. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Straight port of Tilde f36f6562's IMKit keyboard (7 Swift files plus Info.plist) into Sources/TranscriptedKeyboard with the ledger's identity renames. Key handling is unchanged: Tab adds a word, the keyCode-50 backtick/tilde key accepts all, Esc is swallowed only with a ghost visible; type-through, reveal delays, drawing, secure input, tickets and staleness rules are verbatim. Stripped per the plan: the H01 randomization call sites (experimentArm now travels as nil, variant falls back to the ledger's champion default) and GhostOutcomeLedger's plaintext word diary, so the keyboard writes only text-free v3 events while the 5 s / 30 s / segment-close kept-or-edited checks keep running from memory. main.swift had no dev flags at this commit. Three DispatchQueue labels move from bar.r3d/com.tilde to the Transcripted namespace. Package.swift gains a TranscriptedKeyboard executable target on TranscriptedWritingCore so later tests can build it; the ledger rows are marked ported. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…of the app binary scripts/entrypoints/lib/bundle-input-method.sh compiles Sources/TranscriptedWriting/Core and Sources/TranscriptedKeyboard as one Swift 5 module with swiftc -O -framework InputMethodKit into Contents/Library/Input Methods/Transcripted Keyboard.app, copies the checked-in Info.plist with both version keys stamped from the root Info.plist, checks the controller class the plist names is really in the binary, and replaces the previous bundle whole so it is safe to re-run. It never signs; the build scripts' signing loop owns that, and build.sh/build-beta.sh are not wired yet. swiftc-app-args.sh now excludes Sources/TranscriptedKeyboard/ the way it excludes TranscriptedCore: without it build.sh compiled main.swift's top-level IMKServer code under -parse-as-library and failed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hase-1 # Conflicts: # Package.swift # scripts/entrypoints/lib/swiftc-app-args.sh
build.sh and build-beta.sh now call bundle_transcripted_input_method right after the CLI helper, before the nested-code signing step, so Contents/Library/Input Methods/Transcripted Keyboard.app is signed inside-out with the rest of the app. PackagedAppSmoke checks the bundle exists and passes codesign --verify --strict, with fixture tests for a missing and an unsigned keyboard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ction fixtures bundle-input-method.sh checked for the controller class with `nm | grep -q`. Under the build scripts' `set -o pipefail`, grep exits on the first match and nm takes SIGPIPE, so the check failed on a good binary and build.sh stopped. Read nm into a variable first. The nightly secret scan flagged the fake keys in Tilde's SecretRules and RawContinuationPrompt tests, which exist to prove those shapes are scrubbed. Allowlist exactly those two files, like the existing sanitizer test allowlist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ngRuntime Straight port of TildeApp minus UI and lifecycle into Sources/TranscriptedWriting/Runtime: the socket server and peer auth, the llama-server host, completion engine and scaffold prewarmer, the model manager, Screen Memory, personal history, the outcome-ledger readers and stats, TildeSettings, and the keyboard installer. Identity renames per the ledger's rename table. Dev-only hooks stripped: H01, the preview models and PreviewModelSelection, TILDE_* overrides (except the DEBUG unsigned-peer flag, renamed), release-proof paths, the local OCR evaluation store and the incremental-OCR flag. Allowed changes: the keyboard installer reads Contents/Library/Input Methods/<profile.inputMethodInstalledBundleName>, and the diagnostics log writes to Transcripted's logs directory. The runtime.lock flock stays; a duplicate instance makes start() return false. Adds the TranscriptedWritingRuntime SPM target (Core dependency, system frameworks only). build.sh compiles the same files into the app module, so the Core import is guarded with canImport. Nothing starts the runtime yet; the Sources/Writing bridge (P2-B) owns lifecycle. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- KeyboardIdentityTests pins the keyboard Info.plist (bundle ID, TISInputSourceID, connection name, controller class, executable) to TildeProductProfile.production, so a drifted connection name can't ship a keyboard that installs and never gets a session. - Ledger records why .preview9B stays: Qwen's completion behavior reads it. Collapse it in phase 2 cleanup. - repo-layout, Sources/CLAUDE.md and Tests/README.md name the new Writing folders and test target. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port the TildeAppTests rows whose types now live in Sources/TranscriptedWriting/Runtime/ or Sources/TranscriptedKeyboard/ to Tests/TranscriptedWritingTests/Runtime/ with the ledger renames. Trim only the cases that exercise stripped or replaced code (H01, preview profiles, OCR evaluation, incremental OCR, StatusMenuHost, the settings view model, YourTildeView, AppDelegate launch modes). Mark the tests for replaced or not-ported code in the ledger. The TranscriptedWritingTests target now depends on the runtime and keyboard targets too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DiagnosticsLog.shared wrote every runtime test's events into the real ~/Library/Application Support/Transcripted/logs/writing-diagnostics.log. It now follows FileLogger's rule: no file writes under XCTest or Swift Testing, or with TRANSCRIPTED_DISABLE_FILE_LOGGER=1. Tests pin both the detection and that this test process never writes. The ledger gains a follow-ups list (Tilde-branded strings, .preview9B, the llama pin). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…abled WritingController (Sources/Writing) wires Tilde's runtime in its startup order minus the dev-only steps: socket server, Screen Memory observers (activation observer + 1 s window poll), prewarmer, model preparation, keyboard installer. Settings use the keyboard's own defaults domain for keyboard keys and com.justinbetker.draft.writing for app keys; models live in Application Support/Transcripted/models/writing. TranscriptedAppState owns it, starts it from initialize() only when WritingDebugEnabled is set, checks the helper on wake, and stops it in shutdown() in Tilde's order. A graceful stop sets the keyboard's quiet-quit flag; start clears it. New Runtime pieces, covered by SPM tests with fakes and temp fixtures: - Tilde-model adoption: size + SHA-256 verified off the main thread, then cloned from the same descriptor into Transcripted's model folder - Qwen needs 16 GiB; a persisted ineligible Qwen runs Gemma - model switch without relaunch: stop helper, persist, rebuild, prepare, restart helper - suggestions gate seam (Tilde's Screen Memory rule plus on/paused) - wake policy for the helper - TISEnableInputSource on first setup, then select No Accessibility or Screen Recording prompt at launch; no relaunch code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hase-2 # Conflicts: # Package.swift
…cache the app seal check - bundle-input-method.sh stamps an integer CFBundleVersion (1.1.66 -> 1001066). The ported installer upgrades only on a larger integer, so a dotted build made installOrUpdateIfNeeded() fail and nothing installed. - The keyboard only summons Transcripted when it isn't running. A running app means the model or helper is still loading, and a second open would deliver a reopen event that shows the window up to once a minute. - KeyboardInstaller validates this app's own code seal once per launch instead of on every call (about 0.2 s on the main thread each time). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ons) - Quiet-quit is set only when the user quits. applicationShouldTerminate records whether the quit Apple event carries a system quit reason (logout, restart, shutdown). Those leave the flag clear, so after a reboot without launch at login the keyboard can still wake Transcripted. - ModelManager.cancel() stops a superseded download on a model switch; waitUntilSettled returns when cancelled instead of spinning. - The ledger records every Transcripted deviation from Tilde in one list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2 of
docs/writing-plan.md, stacked on #1868. There's still no UI. Autocomplete runs when a debug default is on.What's in it
Sources/TranscriptedWriting/Runtime/(28 files): the model manager,llama-serverhost and restart policy, the authenticated socket server, Screen Memory (Accessibility tree first, then OCR; memory only), personal history, stats, and the keyboard installer. Parity against Tildef36f6562differs only by the documented strips and deviations (see the ledger's new "Deviations from Tilde" list).Sources/Writing/WritingController.swift, started fromTranscriptedAppStateonly whenWritingDebugEnabledis on. It has Tilde's startup and stop order and adds a wake hook, which Tilde never had.clonefileTISEnableInputSourceDiagnosticsLognever writes under tests.Verification (local)
build.sh,codesign --verify --deep --strict(app and keyboard) and the beta DMG build all pass.run-tests.sh: 18450/18450.swift test: 588 XCTest + 731 Swift Testing.linux-checks.sh: all pass except the VM self-tests.How to try it (Justin)
bash build.sh --no-open); ad hoc builds can't authenticate the keyboard.defaults write com.justinbetker.draft WritingDebugEnabled -bool YESTabtakes one word,~takes all of it,Eschides it.defaults delete com.justinbetker.draft WritingDebugEnabled, then switch input sources back.🤖 Generated with Claude Code