Skip to content

feat(desktop): give processing conversations an identity, a bounded wait, and an exit - #12595

Open
Git-on-my-level wants to merge 1 commit into
mainfrom
feat/macos-processing-row-ux
Open

feat(desktop): give processing conversations an identity, a bounded wait, and an exit#12595
Git-on-my-level wants to merge 1 commit into
mainfrom
feat/macos-processing-row-ux

Conversation

@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Why

A conversation that is still being summarized renders as "Processing…" with a fallback 💬, a pulsing pill that promises "shortly" without a bound, and a client that silently stops checking after ~230 s. Meanwhile the transcript is already in the local cache and on the detail endpoint, the backend flips completed before memories/action items run, /reprocess works from a processing row, and free-tier desktop rows sit on processing forever by design (deferred=true, nothing running). None of that reached the UI. "Pending/zombie conversations" is a top user-complaint cluster, and the 2026‑08‑30 finalization outage ran six hours with every row looking like this screenshot.

What changes (macOS only, no backend changes)

1. Status is never the title. The title slot shows the first substantive transcript line (Let's go over the launch timeline for next week), or Recording at 11:12 PM when no transcript is loaded. The pill carries status. No fake 💬: a waveform tile until the pipeline produces an emoji. List refreshes keep an already-loaded transcript when the server list omits transcript_segments (omitted = not loaded, not empty).

2. Bounded wait with an exit. Pill reads SummarizingTaking longer than usual (2 min, orange) → Stuck (10 min, red) on a 15 s TimelineView that only live-pipeline rows pay for. Stuck rows show Reprocess inline on the row and in the detail banner. A new ProcessingConversationWatcher follows every visible processing row to a terminal state with 3→60 s backoff and never gives up while the row is visible; the detail view's poll uses the same backoff instead of 15×2 s.

3. Two waves. When status flips to completed the row gets its real title and shows · Adding memories & tasks… for a 45 s grace, then refetches once so action items land without a manual refresh.

4. Deferred ≠ processing. Free-tier desktop rows now read Tap to summarize, are not timed, and are not polled. The detail banner says "Summarizing now…" for them, which is what opening actually does.

5. Continuity. When a capture stops, the Live card's slot is held by a "Saving" card (last transcript line) until the next conversations load lands the row; row insertion animates. The flag is set where a finished recording enters the lifecycle and cleared in loadConversations, so every stop path (user stop, automation stop, max-duration rotation) shares one clear.

6. Telemetry to tune the thresholds. Conversation Processing Completed {elapsed_seconds, outcome} and Conversation Processing Stalled {elapsed_seconds}, emitted once per conversation by the watcher. Nothing measures processing latency today; the 2/10 min thresholds are a starting point to be re-set from the measured p95.

Product invariants affected

  • INV-NAV-1 — touched by path only (ConversationRowView.swift, ConversationsPage.swift). The Conversations page keeps every control and destination; this adds row states and a transient card above the list, it does not introduce a reduced copy or occlude top-navigation controls.

Not in scope

Percentage progress (no honest denominator), streaming partial summaries, and the merging state for overlapping phone + desktop captures.

Verification

  • xcrun swift build -c debug green on the rebased tree.
  • New behavioral tests (injected clock/sleeper, no wall-clock waits): ConversationProcessingProgressTests, ProcessingConversationWatcherTests, ConversationProcessingTelemetryTests; extended ConversationDisplayStateTests, ConversationReconciliationPolicyTests. 88 tests across the touched suites + ShellGlassChromeTests pass.
  • swift-format and SwiftLint clean. Oversized-file ratchet satisfied by splitting: the detail banner is its own component (ConversationProcessingBanner), the two analytics methods live in AnalyticsManager+ConversationProcessing, and AppState+Transcription.swift is untouched. check_desktop_test_quality.py reports the same pre-existing baseline drift (55/149 vs 54/147) with this branch stashed, so it is not introduced here.
  • Not dogfooded in a named bundle yet. The row/badge/banner/saving-card states are unit-tested at the model layer and compile, but I have not watched a live capture land through the new flow. Reviewer or I should run OMI_APP_NAME=omi-processing-row ./run.sh, stop a recording, and confirm: Saving card → row with transcript-line title and Summarizing pill → real title → Adding memories & tasks… → clears.

🤖 Generated with Claude Code

…ait, and an exit

A conversation that is still being summarized used to render as
"Processing…" with a fallback 💬, a pulsing pill that promised "shortly"
without a bound, and a client that stopped checking after ~230s. The
transcript was already on disk and the reprocess route already worked from
a processing row; the UI just never used either.

Row identity
- The title slot carries the first substantive transcript line (or the
  recording time when no transcript is loaded). Status moves to the badge.
- No fake emoji while the pipeline has not produced one: a waveform tile.
- List refreshes keep an already-loaded transcript when the server list
  omits transcript_segments (omitted means "not loaded", not "empty").

Bounded wait
- Pill reads Summarizing → Taking longer than usual (2 min) → Stuck
  (10 min), driven by a 15s TimelineView only on live-pipeline rows.
- Stuck rows show Reprocess inline; the detail banner does the same.
- ProcessingConversationWatcher follows every visible processing row to a
  terminal state with backoff (3s→60s) and never gives up while the row is
  visible; the detail view's poll uses the same backoff instead of 15×2s.

Two waves
- After status flips to completed the row shows "Adding memories & tasks…"
  for a 45s grace and refetches once so action items land without a manual
  refresh.

Four meanings, split
- Deferred free-tier desktop rows (`deferred=true`, `processing` on the
  wire, nothing running) now read "Tap to summarize" instead of
  "Processing…", are not timed, and are not polled.

Continuity
- The Live card's slot stays occupied by a "Saving" card until the capture
  is a row in the list; row insertion animates.

Telemetry
- "Conversation Processing Completed" (elapsed_seconds, outcome) and
  "Conversation Processing Stalled" (elapsed_seconds), so the thresholds
  can be re-set from a measured p95 instead of a guess.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Git-on-my-level
Git-on-my-level force-pushed the feat/macos-processing-row-ux branch from b3b9205 to 553e993 Compare September 2, 2026 07:58
@Git-on-my-level

Copy link
Copy Markdown
Collaborator Author

Rebased onto current main (8592a3ccd9) and root-caused the two failing Desktop Swift checks — no code change was needed; the failures were inherited from the PR's old merge base.

Root cause: the only failing suite was OnboardingFlowTests.testOnboardingProceedActionsUseDefaultActionKeyboardShortcut ("9 is not equal to 8"). That static tripwire expected eight isDefaultAction: true sites in SBOnboardingView.swift while the source had nine — a pre-existing breakage on main (introduced by e4316bd9bf, hidden behind the test-quality ratchet) that this PR's 21-commits-old merge base predated. It was already fixed on main by #12577 (8c90efa8f2, "expect nine"). "Desktop Swift Build & Tests" was just the aggregation gate mirroring that failure. This PR never touches onboarding files.

Verification on the rebased head 553e9931:

  • All files the failing test inspects are byte-identical to main, where Desktop Swift CI is green at that exact SHA.
  • swift test --filter OnboardingFlowTests on the rebased head: 25 tests, 0 failures.
  • Full bounded pre-push gate passed, including a complete desktop debug build; git diff --check and the changelog gate are clean; Desktop Swift Release Compile already passed pre-rebase and the diff didn't change.
  • The only rebase file overlap with main (PostHogManager.swift) was checked: getFeatureFlagPayload (main) and the conversation-processing telemetry (this PR) coexist cleanly.

Desktop Swift CI on the new head was queued behind the shared hosted-macOS runner queue at post time (6 runs pending); no failures reported.

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