Skip to content

Split the hotspot files - #1950

Merged
r3dbars merged 57 commits into
mainfrom
claude/cleanup-split
Oct 2, 2026
Merged

r3dbars merged 57 commits into
mainfrom
claude/cleanup-split

Conversation

@r3dbars

@r3dbars r3dbars commented Oct 2, 2026

Copy link
Copy Markdown
Owner

Stacked on #1949 (claude/cleanup-unpin). Merge that first.

This splits the 13 biggest hotspot files into smaller files in the same folders. It's a move, not a rewrite: each lane moved line ranges with a script and checked a line-multiset diff against the old file. The only differences are imports, extension wrappers, file headers, and private dropped where code in another file now needs it. No logic, strings, ordering, lock scopes, or nonisolated/@MainActor modifiers changed.

File Before After (main file) Files now
Sources/Meeting/MeetingSessionController.swift 4,412 848 13
Sources/Speech/ParakeetEngine.swift 3,345 668 8
Sources/UI/Overlay/DictationSessionController.swift 3,275 540 10
Sources/TranscriptedCore/Audio/Audio.swift 3,135 813 12
Sources/TranscriptedCore/Pipeline/TranscriptionTaskManager.swift 3,000 623 7
Tools/TranscriptedQA/.../Commands/PackagedAppSmoke.swift 2,345 220 6
Sources/TranscriptedCore/Speaker/SpeakerNamingCoordinator.swift 2,013 601 7
Sources/TranscriptedCore/Pipeline/TranscriptionPipeline.swift 1,954 951 5
Tools/TranscriptedMCP/.../TranscriptIndex.swift 1,876 600 5
Sources/Support/ClipboardRestoringTextPaster.swift 1,756 795 6
Sources/UI/Settings/HomeView.swift 1,708 750 6
Sources/Meeting/MeetingPromptDetector.swift 1,654 447 6
Sources/TranscriptedCore/Audio/AudioFileManager.swift 1,515 596 7

Things worth knowing:

  • MeetingSessionController: the class declaration now lives in MeetingSessionController+State.swift. MeetingSessionController.swift keeps start/stop/signal verification because Notch island is the only dictation window #1946's UIAutomationSurfaceContractTests reads them by that path.
  • HomeView: the code Notch island is the only dictation window #1946's contract test reads stays in HomeView.swift; the view model and models moved out.
  • ParakeetEngine: readParakeetEngineSource() in Tests/TestHelpers.swift now joins the 8 engine files, and slice end markers moved to the next declaration in the same file. check-source-pins can't resolve the joined helper, so those pins only run in the fast tests.
  • AirPods / CoreAudio: no engine, inputNode or real-time callback body changed. Functions moved whole, so a Bluetooth headset as default input behaves as before.
  • Access widening: only what the compiler asked for. Audio.swift has the most (about 60 stored properties lost private); TranscriptionTaskManager keeps tasks private behind three tiny helpers.
  • Source lists: new files added to run-tests.sh (MeetingPromptDetector), shared-smoke-sources.sh (clipboard paster) and run-e2e-smoke.sh (TranscriptIndex).

Pins dropped instead of re-pinned (the controllers can't be built in the fast runner, so there's no behavior seam yet):

  • MeetingSessionController: skipped no-speech outcome, stopped-audio transcriptPersisted: true, the prompt-outcome event count and nil guard, retranscribe reading LocalSpeakerPreferences, capture-stop stash ordering.
  • DictationSessionController: held-back-text wiring, start/stop click counts, persist-before-model-wait and the rest of the stop checkpoint wiring, Quit preservation order, checkpoint retry wiring, stop-task ownership ordering, Try Again isRetry: true.
  • The drainRecordedSamplesForInference() pin Tests: replace source-text pins with behavior tests #1949 restored is kept, now on the joined engine source.

Owner calls: accept the dropped pins above, and the MeetingSessionController class living in +State.swift.

Checks on the merged head: build-deps --force, build.sh --no-open, run-tests.sh (20,339 passed), run-integration-smoke.sh, run-e2e-smoke.sh, swift test (Core), swift test in TranscriptedMCP and TranscriptedQA, check.sh, check-source-pins, check-duplicate-declarations, check-build-source-lists, check-test-shape.

Not done yet: independent review of the full diff (use git diff --color-moved=dimmed-zebra), bash scripts/ops/transcripted-qa-bench.sh --mode full, and bash check.sh hardware with AirPods as the default input. Hotspot rules want those before merge.

Overlap: .agents/test-shape-baseline.json and scripts/entrypoints/run-tests.sh are also touched by #1946, #1942 and #1941. Whichever lands second re-runs the shrink / re-adds the list lines. .agents/test-matrix.yml still needs the five new clipboard files added to the pasteback smoke rule after #1945 lands.

🤖 Generated with Claude Code

r3dbars added 30 commits October 1, 2026 21:17
Four Core audio tests read Sources/ as text. Each now checks the promise
it was guarding through real calls:

- Mic stop tail: a buffer delivered the instant stop() advances the
  generation is still saved. Fails if mic admission is armed after the bump.
  The old "close only in closeMicrophone" pin is already covered by the
  end-to-end test in the same file (closing earlier drops its tail).
- System stop tail: real Audio.stop() saves a buffer delivered at the
  generation bump and one still in the ring. Fails if beginFinishing moves
  after the bump or stop cancels instead of finish-and-drain.
- Mic-only diagnostics: with the last meeting's tap still stored, every
  tap-derived snapshot field is blank on a mic-only meeting.
- Mic-only start: makeSystemAudioCaptureForRecordingAttempt returns nil and
  never calls the factory on a mic-only recording.

Seams, same behavior: the mic-only gate moved from startAudioCapture into
makeSystemAudioCaptureForRecordingAttempt (its only caller, no else branch),
and beginRecordingSessionGeneration calls an optional test hook after the
bump, outside the lock. Nil in the app; start/stop path only, not a
real-time callback.

Each new test was checked against the regression it guards.
FocusOrderContractTests no longer reads Sources. It builds the real
menu bar row and section views and checks they take focus, activate on
Space/Return/Enter, and hand over rows in the declared Tab order. The
popover's key-view loop moves into MenuBarKeyViewLoop so the test can
check the chain and wrap. MenuBarUtilityActionsView takes a closure for
"is dictation loaded" instead of the whole app state, so it compiles in
the fast runner; same value as before.

The sidebar's primary row order is checked from
SettingsSidebarSection.primarySection. The pin on
.accessibilityIdentifier(page.automationIdentifier) is dropped: UI smoke
(TranscriptedQA UISmoke) finds those sidebar identifiers in the live
app, which is the real promise.

StatusItemPresentationTests now compares the tail curves the glyph
really draws with the generator's Q commands instead of grepping
MenuBarGlyph.swift. Its TranscriptedApp.swift pins stay for now.
…ests

Small seams, same behavior:
- SharedMeetingMicRecorder.recordBorrowedBuffer(_:meter:) is now the whole
  body of appendSharedMeetingMicBuffer; the test shows borrowed PCM is
  recorded even when the level meter is idle.
- PinnedDictationInputPolicy.pinnedSelection(...) holds the pinned mic pick;
  tests cover headset skipping, "Same as macOS" keeping AirPods, a picked mic
  winning, and a dead pick being excluded. No device or engine change, so a
  Bluetooth default input is handled exactly as before.
- DictationVoiceProcessingRoutePolicy.isRequested and
  ParakeetMicrophoneSharingPolicy.mayDowngrade carry the call-app VPIO rules.
- forceForMicrophoneSharing moved into the echo, probe and graph policies as
  a required argument, so a call-app downgrade's bypass is tested directly.

Deleted the "never calls MicrophoneProcessingPreferences.set" pin: the VPIO
decision is now a pure read of the saved value, and the test shows VPIO comes
back when the call app closes. Wiring and AVAudioEngine call-order pins stay
until ParakeetEngine can be built in tests.
Pulled the decisions out into small compiled seams and test those:
- MeetingSessionStateMachine.startAdmission: Record is turned away while a
  capture is starting/recording/stopping, or a start is in flight.
- MenuBarMeetingMenuAction: the menu button means Stop for the whole
  capture, and a Stop while starting joins the pending start.
- MeetingSleepWakeNotificationSource: capture listens on the workspace
  center with the real NSWorkspace sleep/wake names.
- Publisher.wholeSecondTicks: the menu bar's duration refresh fires once
  per whole second (fed fake 5 Hz ticks).
- HomeFailedMeetingRowActions: Show Audio for any kept file, retry only
  for complete retryable audio, cleanup is a destructive delete.

Deleted the two TranscriptionQueueCoordinator pins. They only checked
that two function names existed; the compiler already guarantees the
public one, and the private no-arg finalize overload had no callers, so
it's gone too.

No behavior change.
…vior

EventReporter.capture's decisions move into ObservabilityEventCapturePlan,
a pure function the fast runner compiles: build identity stamping, the
raw entry for the reliability recorder vs the blanked disk copy, PostHog
forwarding from the caller's context only, and the Sentry policy.
EventReporter just runs the plan; same sinks, same values. EventLevel
moves with it.

EventFileWriter moves to its own file with an injectable file URL, so
tests can check a buffered info event lands on shutdown flush and a
pre-existing events.jsonl is made owner-only. AppLogSink takes an
optional log URL (default unchanged) and imports TranscriptedCore only
when it can, so the test checks what log() stores is redacted.

The console-path pins become a check that prepare failures hand the
callbacks a message with no absolute path.

PasteLastDictationFeedbackPanel is no longer private, so the overlay
privacy test builds one and checks sharingType instead of slicing text.

Still source-read, for later: EventReporter/TranscriptedApp shutdown
flush wiring and the legacy FileHandle API sweep.
The dictation controller can't run in the fast tests, so the decisions it
makes after Stop move into small compiled policies and the tests call those.
Behavior is the same.

New seams:
- DictationSessionCapFinish: the cap finalizes (never discards) and pastes
  only when the original app is still in front.
- DictationSessionCapSavePolicy + completionProperties: cap save notice,
  Paste It result, and the dictation_completed delivery/failure keys.
- DictationStopRoute: early-release cancel, "Still finishing", recovered
  audio going on to transcription.
- DictationDeliveryPresentation + TextPasteOutcome.deliveryProperties: what
  the pill shows after a paste, and one delivery value for diagnostics and
  analytics.
- DictationInterruptionPlan, DictationTerminationFinisher,
  DictationRetainedAudioRetry: interruption offer, Quit ordering, Retry Saving
  ordering, run with fakes.
- DictationEarlyReleaseCancelReport: the .error event that reaches Sentry.
  EventLevel moves to ObservabilityEvent.swift so the fast tests can see it.
- FocusedTextPasteConfirmationPolicy.boundedFocusedElement: AX timeout and
  the CFGetTypeID guard, driven with a fake AX reader.

Pins deleted, not converted:
- OverlayHeaderView "Dictation saved only" VoiceOver copy: no path shows a
  "Saved only" success title any more (the cap uses showSavedNotice), and the
  mini cursor view is slated for removal after #1946.
- canImport(TranscriptedCore) guard in ClipboardRestoringTextPaster: the fast
  test compile already fails without it.
- Absence pins on old names (Paste Again, pasteConfirmationUnavailable*):
  the delivery presentation tests now show a likely paste never offers a
  second paste.

ContextCaptureEnginePolicyTests and DictationStartReadinessTests had pins on
the moved code; those assertions now call the new policies.

Still source text: FloatingOverlayController and TranscriptedApp (open
#1946), startDictation's capture guard and the stop path's checkpoint wiring
(shared stop/start pipeline seam), the sound-cue counts, feedback silence,
the meeting stop terminal, and SpeakerFinalizationFailure raw values.
…r tests

ContextCaptureEnginePolicyTests and ExistingInstallModelPrefetchPolicyTests
no longer read Sources/ as text. The decisions they guarded now live in small
compiled seams with the same behavior, and the tests call those:

- HotkeyActionDebouncer + PhysicalShortcutAction.hotkeyDebounceID: per-action
  repeat guard, push-to-talk never debounced, same telemetry ids.
- PhysicalShortcutMatcher.configuredBindings(userDefaults:): the binding
  snapshot (meeting + paste always, dictation shortcuts first when on).
- PhysicalShortcutMatcher.reconcileAfterTapDisabled / isPhysicalKeyDown and a
  keyState overload of shouldActivateDelayedModifierPress: missed push-to-talk
  release after the tap is turned off, and bound-key checks for delayed presses.
- PhysicalShortcutTriggerStatus: tap failure text, Accessibility retry gate,
  and the one-banner rule (Fn conflict is advisory, not a registration error).
- DictationIgnoredStopFeedback.showsStillFinishing: ignored stop while a take
  is finishing still says so. The message literal stays at the call site
  because DictationSessionCapTests (b07) still pins it.
- ExistingInstallModelPrefetchPolicy.launchWarmupEnabled(environment:): models
  warm at launch unless TRANSCRIPTED_LAZY_MODEL_WARMUP is exactly "1".

Dropped without a replacement, because no runtime behavior can be checked here:
- "no bindingProvider" negative grep: the detector only takes a snapshot now,
  and the snapshot test covers what it builds.
- cancel-pending-before-reconcile and CGEventSource call-shape greps: the
  physical-state query is now an injected closure covered by the seam tests.

Still untested in the fast runner (needs the real engine/app): the tap's
dedicated run-loop thread, the Accessibility polling loop calling
reRegisterHotkeys, lock-held delayed press delivery, unregister keeping
onPasteLastDictation, wake recovery reading hotkeyRegistrationError, and the
launch warmup's meeting-model step and task priorities.
STT routing and stopped-audio recovery. Small pure seams, same behavior:
- SegmentedEngineTranscript: Whisper's join/trim plus custom dictionary.
- DictationLanguageScriptPolicy.unexpectedScript with a lazy language
  reader, so the router's Carbon lookup only runs for non-Latin text.
- DictationEmptyInferencePolicy: external-engine empty/failure reasons and
  the reason after an empty conversion (both ParakeetEngine call sites).
- TranscriptionLanguageModelErrors: the saved-language settings-fix errors,
  tested against MeetingFailureKind.
- STTRouter's load-progress wait switches on runtime.

Dropped pins that only matched a declaration (recoveredRecordingTimeline,
preserveCurrentRecordingBuffersForRecovery, clearRecoveredRecordingTimeline,
interruptRecordingAndClearRecoveredTimeline, drainRecordedSamplesForInference):
the compiler already enforces them, and the timeline is covered by
RecordedAudioTimelineTests. The Apple Speech "wired through every switch"
pins are covered by exhaustive switches. setActiveRecordingModel has no
await, so its in-function order was unobservable; the real promise (the
lease's variant wins) is now tested through the ownership types.

DictationSessionController, MeetingSessionController, TranscriptedApp and a
few ParakeetEngine/STTRouter ordering pins stay for the shared pipeline seam.
…r behavior tests

The b03 suites read ParakeetEngine, ParakeetZombieEngineRecovery and
ParakeetDeviceRecovery as text. Pulled the decisions into small compiled
pieces and test those instead. Behavior is unchanged.

- ParakeetASRInferenceGate: the decoder gate, moved out of the engine. A
  caller arriving during a handoff now provably waits behind the resumed
  waiter.
- ParakeetZombieEngineRecoverySequence: the order of the one bounded zombie
  retry (idle, owner check, recreate, settle, restart once, one terminal
  result), driven by injected steps. Also the duplicate-detection guard and
  the "a start cancels recovery unless it's the recovery's own" decision.
- ParakeetRecordingContinuityPolicy: when a start gets a fresh recording
  identity, and what an idle stop does (recovered speech beats a pending
  zombie restart).
- ParakeetDeviceRecoveryFailurePolicy.graphRepair(after:): keep / rebuild /
  abandon after a failed rewarm.

Dropped without replacement: the hasRecoverableRecording existence pin (the
compiler already enforces it through STTRouter and DictationSessionController).
Still unconverted, waiting on the shared audio-graph driver seam: zombie graph
recreation internals, the startRecording audio-start lease, stop/config-change
"invalidate and cancel in one actor turn", wake-time buffer preservation, and
the device-recovery restart wiring.
…ests

Three pieces of ParakeetEngine now live in compiled types the fast tests
can drive with fakes. Same behavior, just moved:

- the timed engine-work runner -> ParakeetTimedAudioEngineWorkLimiter.run
- the single-flight stop -> ParakeetSingleFlightLifecycle
- system-input late-completion reconciliation -> ParakeetSystemInputReconciler

ParakeetEngineWorkLifecycleTests covers what the pins guarded: a claimed
start never touches the engine, mid-flight cancellation cleans up on its
own worker, a wedged start times out and gets late cleanup, a full limiter
fails closed, duplicate stops join one drain and the stop stays visible
until it ends, CoreAudio error strings are retried rather than taken as
success, passes are bounded, late completions only re-run when intent
changed, and requests drain through one coalescing task. Mutation-probed:
each guard flipped turns the suite red.

The source-text suites for the system-input reconciliation and duplicate
stops are gone, plus the timed-work and stop-intent pins in two other
suites. The rest of the file still needs the audio-graph driver seam.
Meeting mic start and the in-meeting Boost now go through small policies in
Sources/Meeting/MeetingMicCapturePlan.swift (start plan, next-meeting boost
look-past and use-up, and the Boost arm steps behind closures). The bridge
calls them with the same reads in the same order, so behavior is unchanged.
The fast tests check those policies against a fake capture instead of
grepping MeetingCaptureBridge.swift.

RetainedDataSourceComboBox now keeps its source alive on a plain
dataSource assignment too. Nothing assigns that way today, so the app acts
the same, and the "use setRetainedDataSource, not .dataSource =" pins on
the speaker name boxes guard nothing anymore. A test covers the plain
assignment instead.

Dropped pins:
- "Boost must not save the mode": the arm steps take only capture hooks,
  so there's no path to the saved preferences left to pin.
- HomeView "secondaryAction*" names: HomeSettingsPage passes those labels,
  so the compiler already fails if they go.

Still source-text (targets are owned by open PR #1946 or are SwiftUI views
the fast runner can't build): the Home row and settings pins, the Settings
mic picker pins, the Home Boost row negative pin, and the SpeakerNamingSheet
self-data-source pin.
BluetoothRouteContractTests read ParakeetEngine, ParakeetDeviceRecovery and
PersistentDictationInputController as text. Most of those promises now run
through small compiled seams with fakes. Behavior doesn't change.

Seams (all in files the fast runner already compiles):
- ParakeetDictationTapPreparation: remove tap, apply VPIO, then pick the tap
  format from the live node and its actual VPIO state. Fake input node.
- ParakeetDictationInputSnapshotRead: bind the app's own AUHAL input before
  any format read (the AirPods call-mode trap). Fake graph.
- DictationInputBindingSequence + DictationInputSelectionReportPolicy: a failed
  bind throws before readiness, success is reported only after a verified settle.
- RecordedAudioTimeline.speechSamples: each segment resamples from its own rate.
- ParakeetConfigChangeAdmissionPolicy (post-lookup recheck),
  ParakeetConfigChangeGraphPolicy.action, ParakeetConfigChangeDebounce.
- DictationPersistentInputRefreshScheduler, ShutdownPolicy, RestorePolicy:
  the Mac-wide input write waits out dictation, meetings and other apps'
  capture, coalesces bursts, and quitting never touches another app's call.

applyPreferredDictationInputDevice is now nonisolated: it already ran on the
audio-engine queue, and the adapter calls it from there.

Deleted the "system input override restores after recording" suite. Nothing
arms pendingSystemInputRestore since the 2026-08-24 audit (Speech/CLAUDE.md
says so), so every restore call it pinned is a no-op, and the rest were
absence checks on the retired temporary crash marker.

Still source text, since the code can't compile in the fast runner: the
main-actor ordering in audioInputSnapshot, the persistent controller's
listener and shutdown wiring, and the QA report doc check. Two assertions in
ParakeetMicrophoneSharingSourceContractTests moved to the new behavior tests.

Mocked routes only; real AirPods still need bash check.sh hardware.
Stacked on b07 (claude/cleanup-unpin-b07). b07 had already built
DictationStopRoute, DictationEarlyReleaseCancelReport and
DictationTerminationFinisher on the same controller code, so this reuses
them instead of adding second copies. Behavior is the same.

New seams, all in files the fast tests already compile:
- DictationNativeMicrophoneStart: the native open shared by the fast start
  and the recovery loop (stage reports, recovery vs ordinary start, focus
  recovery only after a failed open). DictationSession's start functions no
  longer default isCurrentSession/onStartStageChanged/onStartFailed, so the
  recovery loop can't silently drop one.
- DictationStartActivationRecoveryGate: focus recovery at most once per
  session, keyed on the session id (replaces didAttemptStartActivation).
- DictationPendingStartStage + DictationPendingStartStageClock: named stages,
  session-scoped stage reports, and end() reads and resets in one step.
- DictationProcessActivityLabel: the App Nap reason goes back to "stop
  finalization" when a session ends, so the two readmissions no longer set it
  by hand.
- DictationStartReadinessPolicy.preparedDiagnostics: start diagnostics name
  the key action only when a shortcut started the take.
- DictationHotkeyRouter: Push to Talk and hands-free keys to session
  commands, with the real shortcut on each.
- DictationStopRequestRouting: no session, hands-free next press, then the
  finalizing fence, before b07's DictationStopRoute.
- DictationQueuedStartGate + DictationQueuedStartPolicy.previousLeftMessage /
  drop: Quit blocks queueing, refused Quit reopens, a dropped press is counted
  and stays quiet over a take still transcribing.
- DictationFastStart, DictationRecordingStarted: dictation_started only after
  the open succeeds; every start drops its start handle.
- DictationInterruptedAudioReadmission: wait for the old checkpoint, check
  ownership, then reset the fence.
- DictationStartAdmission.beginSession: the session id is minted after the
  request is counted.
- ModelWarmupOutcome.startFailureKind: model_load_failed / model_load_timeout.

Pins deleted, not converted:
- "activationPrepared" absence and "no startActivation.prepare in
  beginDictationRecording": absence pins on removed code. The runner tests
  already show a healthy open never activates.
- The recoverBackgroundHotkeyStart call count: onStartFailed is now a
  required argument on both native start paths, so the compiler holds it.
- "app_nap_suppressed" absence: the report test checks the exact key set.
- HotkeyPreferences absence pins: diagnostics and the early-release report
  take the key action as an input.
- The enterPendingStartStage("...") string pins: stages are an enum now.

Still source text: the Esc drop callback and the overlay's
messageCanGiveWayToNextStart reset (FloatingOverlayController, open #1946);
startDictation's countRequest/countRefusal/beginSession wiring and the
isRetry count on Try Again restarts (controller can't build in fast tests);
MeetingSessionController's unexpected-stop handler (also pinned in
MeetingSessionUIPolicyTests, b09's lane; convert together).
# Conflicts:
#	Tests/ParakeetMicrophoneSharingSourceContractTests.swift
# Conflicts:
#	Sources/UI/Overlay/DictationRecordingStartOverlayPolicy.swift
#	Sources/UI/Overlay/DictationSessionController.swift
#	Tests/ContextCaptureEnginePolicyTests.swift
# Conflicts:
#	Sources/Observability/EventReporter.swift
b05's externalEngineEmptyReason in ParakeetShortAudioGate now calls it.
Moving the timed runner out of the main-actor engine (b02) added a
concurrency warning for the captured didResume var. A small Sendable gate
holds the flag instead; same once-only resume. Speech census 25 -> 19.
The file was 2,345 lines with two runners and their models in it. Code moves
verbatim along type boundaries in the same Commands/ folder:

- PackagedAppSmoke.swift: the command, check/report types, process runner
- PackagedAppSmokeRunner.swift: the packaged-app checks and its private helpers
- FirstRunReliabilitySmokeRunner.swift: scenarios and how they're judged
- FirstRunReliabilityLaunch.swift: one isolated app launch, as an extension
- FirstRunReliabilityReport.swift: report and app-report Codable models
- PrivacyLogScanner.swift: the log privacy scanner

Only access changes: fileManager, timeout, launch, the four seed helpers and
the three launch structs go from private to internal so the two runner files
can share them. No behavior change.
TranscriptIndex.swift was 1,876 lines. It now keeps the connection,
schema gate, reconcile, and per-file indexing (582 lines). The query
code moves verbatim into four extensions:

- +MeetingQueries: utterance search, speaker history, meeting lists, who_is
- +DictationQueries: dictation days, entry search, recent entries
- +Context: cross-kind search/recent feed and index counts
- +SummaryRollups: summary items, action items, decisions, digest

Pure move. Eight members go from private to internal because private is
file-scoped: SQLITE_TRANSIENT, bind, colText, colTextOptional, dbError,
ftsQuery, MeetingQueryRow, queryMeetings. The e2e smoke source list
gains the four new files.
r3dbars added 24 commits October 1, 2026 23:21
Audio.swift was 3,135 lines. It's now the class shell (stored state,
generation token, inits, publisher wiring, deinit) at 813 lines, and
the methods moved verbatim into ten Audio+*.swift extensions plus
AudioCaptureTypes.swift for the top-level types. Largest new file is
480 lines.

No behavior change. Lines moved byte-for-byte; the only edits are
dropping `private` on stored state the moved code reads (extensions in
other files can't see it) and `public private(set)` -> `public
internal(set)` on startFailureStage and
systemAudioStartPermissionExplicitlyDenied, still read-only outside
the module. Lock scopes, lock order and stop() ordering are untouched.
AirPods: no engine or inputNode code changed, it only moved to
Audio+MeetingInputGraph.swift, so a Bluetooth default input behaves
exactly as before.
Pure move, no logic changes. HomeView.swift drops from 1,708 to 750 lines.
The view model, value types, feedback types, scan-warning card and the
day-grouped capture list each get their own file.

Row actions, the failed-meeting row, the feedback sheet and Load more stay
in HomeView.swift for now: UIAutomationSurfaceContractTests still reads
their text from that file, and that test is in open PR #1946.
Review of #1949 found these were dropped though the PR said they stayed.
They're back as extra assertions on reads the suites already make, so the
source-text count doesn't grow. Also fixes the EventLevel comment (pinned-mic
events reach PostHog at any level) and brings the source-text inventory up
to date.
The 3,000-line file is now the class shell, lifecycle state, status
publishers, and completion/cancel/shutdown (~620 lines), plus six
extensions in the same folder: +Start, +FailureClassification,
+FailedAudioRetention, +OrphanedRecordingRecovery, +Retry, and
+CleanupPaths. Bodies moved as-is.

Access only widened where a move forced it (private to internal, nothing
public). `tasks` and `TaskLifecycleState` stay private: the other files go
through three one-line helpers (beginTaskLifecycle, forgetTaskLifecycle,
lifecycleOwnedAudioPaths) that hold the exact same expressions.
Moves whole members out of the 1,954-line hotspot, no logic edits.
The main file keeps transcribeMultichannel (951 lines). New siblings:
+MicrophoneOnly, +MicDiarization, +Stages, +LastChanceSweep.

longestAudioDuration and audioDuration go from private to internal,
since both entry points now live in different files.
Pure move. ParakeetEngine.swift (3,345 lines) is now a 668-line core
(stored state, graph identity, rebuild/abandon, observers, cleanup,
deinit) plus seven extension files: InputReadiness, InputRoute,
AudioTap, RecordingStart, RecordingTeardown, DictationTranscription,
ASRInference. Function bodies moved byte for byte; access widened only
where a cross-file caller forced it.

Source-text contracts that sliced the old file now read the engine's
files together through readParakeetEngineSource(), with end markers
moved to the next declaration in the same file. No pins added.
Pure move, no behavior change. The two lock registries get their own files,
and the naming flow splits into planning, apply, request queue and finish
extensions. Five members go from private to internal so the flow can span
files: PlannedNamingChanges, planNamingUpdates, liveProfileConfirmations,
shouldApplyNoDialogDatabaseMutation, cleanupNamingArtifacts.
Pure move, no behavior change. The 4,412-line hotspot is now 13 files,
none over 850 lines.

- MeetingSessionController.swift keeps start, stop, the unexpected
  capture stop, the stop snapshot and signal verification. Those had to
  stay put: UIAutomationSurfaceContractTests pins them by path and that
  file is in another open PR.
- MeetingSessionController+State.swift now declares the class: stored
  and published state, init, transition and updateDisplayStatus.
- The rest moved to +Types, +RecordingStart, +CancelAndTermination,
  +MicBoost, +LiveCaptureWarnings, +ModelWarmup, +TranscriptionRequests,
  +Subscriptions, +TranscriptionOutcomes, +Telemetry, +FailedMeetings.

Function bodies are byte-identical; the only code edits are access
modifiers. Members used across the new files lost `private` (or
`private(set)`); state, displayStatus, failedMeetings, warmupStatus and
the other writer-owned values stay private(set).

Tests: source-text pins on text that moved out of the file are dropped,
not re-pinned (skipped-outcome error, recovery cleanup, prompt outcome
count, retranscribe preference, capture-stop evidence stash). The
handleUnexpectedCaptureStop needle drops "private". Test-shape baseline
shrunk to match.
Pure move, no logic change. The 3,275-line controller is now a 541-line
core (state, overlay wiring, startDictation, cancel) plus eight
DictationSessionController+*.swift extensions (RecordingStart, Stop,
PasteBack, Persistence, Recovery, Presses, SessionCap, Telemetry) and
DictationSessionDeliveryTypes.swift. Largest file is +Stop at 727 lines.

Members another file needs went from private to internal; the rest stay
private. The two statics moved into the one extension that uses them.

The source-text assertions that read moved code out of
DictationSessionController.swift are removed, not re-pinned: the held-back
text wiring, the stop/start click counts, the stop-path checkpoint and
empty-take wiring, the Quit preservation order, and the Try Again retry
count. Pins on code that stayed in the core file still hold. Test-shape
baseline shrunk to match.
…p-split

# Conflicts:
#	Tests/DictationStoppedAudioRecoveryTests.swift
…laude/cleanup-split

# Conflicts:
#	Tests/DictationAudioRecoveryTests.swift
#	Tests/DictationStoppedAudioRecoveryTests.swift
The split dropped about 20 source pins on DictationSessionController and
MeetingSessionController. The code they guard is unchanged, just spread
over +Area extension files. Two helpers now read a controller's core
plus its extensions (or one part), and the pins are back: persist before
the model wait, the Quit mark/wait/cancel order, the nine retry-marked
startDictation calls, one start click and one stop click, held-back
text, skipped-meeting outcomes, prompt outcome telemetry, retranscribe's
People-in-the-room preference, and the capture-stop evidence order.

TestHelpers' source-text baseline goes 8 -> 11 for the helpers; every
other file is at or under its pre-split count.
@r3dbars

r3dbars commented Oct 2, 2026

Copy link
Copy Markdown
Owner Author

Review verdict, applied in ad4173c. Two reviewers, findings merged:

Fixed: dropped pins (both reviewers' #1/#2) and the DictationSounds comment (#2). Every dropped anchor still existed word for word in the split files, so they're back. readDictationSessionControllerSource(part:) and readMeetingSessionControllerSource(part:) in Tests/TestHelpers.swift read the core file plus every +Area extension, or one part. Restored:

  • persist before DictationPostStopModelWait.run(, the checkpoint's ownership check, the empty-take branches, and the Quit mark → bounded wait → cancelDictation(preserveStoppedAudio: true) order
  • the nine .startDictation( calls all marked isRetry
  • start click queued before recordingStartRetryTask = Task, one play(.dictationStart), one play(.dictationStop) (the misleading comment is gone)
  • checkpointUnavailable / offerCheckpointRetry wiring, the stale stop-task guard order, held-back text
  • transcript_skipped outcome, meeting_prompt_outcome_recorded count and nil guard, retranscribe's People-in-the-room preference, capture-stop evidence stashed before the warning clears

Mutation check: renaming DictationPostStopModelWait.run( in +Stop.swift turns DictationStoppedAudioRecoveryTests red. Reverted after.

Test-shape baseline: TestHelpers 8 → 11 for the two helpers. That's a baseline increase, so it needs your OK. Every other file is at or under its pre-split count (Analytics 11 → 10, DictationStartedTelemetry 2 → 1). Total is 358 vs 357 before the split.

Skipped: pasteback smoke rule (#3/#1). The gap is real: test-matrix-checks.py on FocusedTextPasteConfirmation.swift doesn't select run-slow-pasteback-smoke.sh. But .agents/test-matrix.yml is also touched by open PR #1945, so it's off limits for this lane. The fix is five more paths in the rule at line 36. #1945's hunk is around line 590, so the two won't conflict. Land it right after #1945, or OK it here.

Skipped: private(set) widening (#4). It's real, and the c54ceda message is wrong about it. But Swift's private(set) only allows writes from the same file, and stored @Published properties can't live in extensions. Each of the 11 meeting properties, plus lastEmptyTranscriptionReason and lastRecordingStartFailureReason, is written from at least one extension file. Putting it back means either merging the writers back into the declaring file or adding setter funcs at about 40 write sites, and some of those are pinned (self.systemAudioDegradationWarning = nil). Nothing changes at runtime. It's a choice for you: accept the module-wide setter, or do a follow-up that moves writers behind setters.

Not applied here, docs (second reviewer's #3). AGENTS.md hotspot and most-pinned lists, docs/repo-layout.md:180-196, Sources/TranscriptedCore/CLAUDE.md:13-14 and Sources/Speech/CLAUDE.md:9 still describe the pre-split files. This lane doesn't own those docs, and #1947/#1948 are editing them. Flagged for whoever owns them.

Checks: bash run-tests.sh (20380 passed, 0 failed), check-test-shape + self-test, check-source-pins --changed-only, check-build-source-lists.

🤖 Generated with Claude Code

@r3dbars
r3dbars changed the base branch from claude/cleanup-unpin to main October 2, 2026 06:18
@r3dbars
r3dbars merged commit fc7bbfb into main Oct 2, 2026
8 checks passed
@r3dbars
r3dbars deleted the claude/cleanup-split branch October 2, 2026 06:18
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