Skip to content

perf: move transcript restyle + saved-transcript analytics off the main actor#1072

Open
r3dbars wants to merge 1 commit into
mainfrom
claude/vigilant-euclid-a4f673
Open

perf: move transcript restyle + saved-transcript analytics off the main actor#1072
r3dbars wants to merge 1 commit into
mainfrom
claude/vigilant-euclid-a4f673

Conversation

@r3dbars

@r3dbars r3dbars commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Why

Every transcript save ran MeetingTranscriptStyler.restyleTranscript synchronously on the main actor — a full transcript read, rewrite, and file + audio-dir rename — and savedTranscriptAnalyticsProperties re-read the entire transcript again on the main actor just to bucket analytics. Multi-hour meetings produce multi-MB markdown, so the UI hitched exactly at "transcript saved". (Audit-verified finding.)

Product Impact

  • Affects: meetings
  • Lane: meeting reliability
  • Why this matters: removes a save-time UI freeze that scales with meeting length, and stops a parse regression from being able to silently destroy transcript bodies.

What changed

  • MeetingSessionController: the taskManager.$lastSavedTranscriptURL subscription now runs the restyle on a serialized detached task (restyleSavedTranscriptInBackground); only the published state updates, live-sidecar attach, and diagnostics hop back to the main actor. Restyles are chained so two passes never touch the same artifacts concurrently (matters for the speaker-review re-save), and a stale-result guard drops superseded updates while retained-audio compression still runs for every save.
  • savedTranscriptAnalyticsProperties is now nonisolated static and runs detached. It awaits the in-flight restyle so it reads the renamed artifact — previously it read the pre-rename path, which the synchronous restyle had often already renamed away, silently producing empty analytics buckets — and it uses the bounded TranscriptFrontmatter.readValues(from:) chunk reader instead of loading the whole file. The AnalyticsReporter.track call itself stays on the main actor (the reporter's lazy distinctID is not thread-safe).
  • MeetingTranscriptStyler: persisting now fails closed when the body carries transcript text the entry parser cannot understand — no rewrite, no rename, logged as meeting_transcript_restyle_skipped — instead of destructively replacing the body with _No transcript captured._. Genuinely empty transcripts (empty block, or only ---/*Generated by... remnants, matching the real saver's empty shape) still get the placeholder rewrite.
  • Tests: two new fast tests pin the fail-closed and genuinely-empty behaviors; the source-slice anchor in RepoCommandContractTests follows the renamed analytics function. Sources/Meeting/CLAUDE.md flow step 12 updated.

How I checked it

  • Selected checks from .agents/test-matrix.yml for the files changed (Sources/Meeting/** rule)
  • bash build-deps.sh --force
  • bash build.sh --no-open
  • bash run-tests.sh — 4660/4660 passed, including MeetingTranscriptStylerTests and the repo-contract suite
  • bash run-integration-smoke.sh
  • Manual check: n/a — behavior covered by fast tests; no UI surface changed

Risk Review

  • Privacy / local-first behavior reviewed — same meeting_transcript_saved event and allowlisted bucketed properties; only timing/threading changed
  • Storage path or migration impact reviewed — none; restyle output and rename behavior unchanged for parseable transcripts
  • No private transcripts, audio, tokens, personal paths, or customer data are included

Notes

Known trade-off: if a second save lands while the first save's restyle is still in flight, the first meeting's live-Codex final-transcript attach can be skipped — but the attach guard (lastSavedTranscriptTaskId == awaitedJobID) would have rejected it at that point anyway, and the activeCount-driven attach path provides a second chance before that window closes.

Side effect worth knowing: because the restyle no longer runs inside the @Published willSet, populateSavedMetadata in TranscriptionTaskManager now reads the transcript before any rename, so lastSavedTitle/lastSavedDuration/lastSavedSpeakerCount populate correctly in cases where they previously read a just-renamed-away path.

🤖 Generated with Claude Code

…in actor

Every transcript save ran MeetingTranscriptStyler.restyleTranscript
synchronously on the main actor (full read, rewrite, and file/audio-dir
rename), and the saved-transcript analytics re-read the whole file again
on the main actor just to bucket properties. Multi-hour meetings produce
multi-MB markdown, so the UI hitched exactly at "transcript saved".

- Restyle now runs on a serialized detached task; only the published
  state updates, live-sidecar attach, and diagnostics hop back to the
  main actor. Restyles are chained so two passes never touch the same
  artifacts concurrently, and a stale-result guard drops superseded
  updates while retained-audio compression still runs for every save.
- savedTranscriptAnalyticsProperties is now nonisolated static, runs
  detached, awaits the in-flight restyle so it reads the renamed
  artifact (it previously read the pre-rename path, which the
  synchronous restyle had often already renamed away), and uses the
  bounded frontmatter chunk reader instead of loading the whole file.
- The restyle persist path fails closed when the body carries
  transcript text the entry parser cannot understand, instead of
  destructively replacing it with "_No transcript captured._".
  Genuinely empty transcripts still get the placeholder rewrite.

Gates: build-deps.sh --force, build.sh --no-open, run-tests.sh
(4660/4660), run-integration-smoke.sh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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