Skip to content

Only show the call prompt for real browser calls, and remember Not now - #1800

Merged
claude[bot] merged 12 commits into
mainfrom
claude/project-thread-3bya58
Sep 24, 2026
Merged

claude[bot] merged 12 commits into
mainfrom
claude/project-thread-3bya58

Conversation

@claude

@claude claude Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Justin · project thread

Before: any browser that held the mic for 3 seconds got a "Call detected in your browser" prompt. ChatGPT voice, web dictation, a mic test page: all of them. "Not now" lasted 30 minutes and was forgotten on relaunch, so the same person got asked again and again.

After: a browser window whose title only exists during a call (a Meet tab, the Zoom web client, a Teams meeting) prompts right away with the right name. A focused voice assistant or recorder (ChatGPT, Claude, Loom...) doesn't prompt for that session, even after a tab switch. A focused call app that isn't clearly in a call (Teams chat, a Slack huddle, the Meet home page) waits 20 seconds. Anything else waits 60 seconds of steady mic use (20 if the browser is also playing audio or the camera is on) and then asks once. Each Not now keeps it quiet longer (30 min, 2 h, 8 h, then a day for unknown sites), a recording resets it, it survives a relaunch, and turning auto call detection off and on clears it. The prompts also show the user's own meeting shortcut instead of always saying Option-M.

How: the app reads the browser's window titles through the Accessibility access it already has for paste-back, on a background queue under a 0.3 s budget. Titles are never logged, stored or sent. No new permissions. Not now is remembered per kind of prompt (unknown browser use, call site, camera, named browser call, each native app), so turning down ChatGPT voice never hides a real Meet. It also stops sending the same suppression twice to analytics (about 33k duplicate events a month).

Why

Pain point #4 in the customer list: the "call detected" prompt pops up every time a browser uses the mic. Over 30 days (CI builds excluded) browser prompts got 2.4 Not nows for every recording, vs 1.3 for native apps. Half of all Not nows came from people who had already said Not now 3 times in a row.

Product Impact

  • Affects: meetings
  • Lane: meeting reliability
  • Why this matters: fewer wrong prompts means people trust the prompt when it does show up, and real Meet, Zoom and Teams calls still get asked right away.

What changed

  • Sources/Meeting/BrowserCallEvidence.swift (new): title rules (call-only titles from any window, Meet's checked before the mail and calendar filter; call sites and non-call sites only from the focused window; mail and calendar skipped otherwise) and the prompt / wait / not-a-call decision.
  • Sources/Meeting/MeetingPromptLearnedBackoff.swift (new): remembered Not now streaks per kind, saved in UserDefaults, with a reset.
  • Sources/Meeting/BrowserWindowTitleReader.swift (new): background Accessibility title reader with an overall time budget; returns nothing when Accessibility is off.
  • MeetingPromptDetector: browser mic goes through the evidence check with sticky call / not-a-call verdicts, a 30 s grace when the mic drops, re-reads that stop once decided, new suppression reasons (awaiting_call_evidence, not_a_call, declined_this_call, learned_quiet), and a 15 min suppression telemetry cooldown instead of 90 s. Learned-off only applies when titles are readable.
  • MicActivityMonitor: reports when the browser that holds the mic is also playing audio.
  • TranscriptedApp: stops the duplicate meeting_prompt_outcome_recorded(suppressed) event, wires the new output signal, and resets the learning when auto call detection is turned back on.
  • Prompt copy: the app-reminder, call-detected and missed-call prompts show the current meeting shortcut (for example ⌥M) instead of a hardcoded "Option-M", and the missed-call nudge says "Click Record" instead of "Tap Record".
  • Accessibility summary now reads "For shortcuts, paste-back, and spotting calls in your browser."
  • Analytics: prompt events carry call_evidence; meeting_detected_call_ended carries app_signal (browser vs native) and the tab-named provider. Both allowlisted.
  • Tests: new BrowserCallEvidenceTests, MeetingPromptLearnedBackoffTests; detector, heuristics, monitor, telemetry and analytics-policy tests updated, including ChatGPT voice after a tab switch, chat apps in the background, a Not now covering the rest of the call, no learned-off without titles, manual recordings, and a rebound meeting shortcut.
  • Docs: Sources/Meeting/CLAUDE.md, docs/auto-call-detection-spec.md (Phase 5).

Expected change (PostHog replay of the new backoff over 56 devices' real browser prompt history): about 41% fewer browser Not nows from the backoff alone, at the cost of 12 of 142 browser prompt recordings (8.5%, on 5 devices; 9 of those 12 were on devices that also start meetings by hand). The title and wait rules come on top and can't be replayed from telemetry: up to 328 browser prompts a month were for mic sessions under a minute, which the wait removes.

How I checked it

  • scripts/dev/agent-preflight.sh
  • python3 scripts/dev/check-build-source-lists.py, python3 scripts/dev/check-analytics-emitters.py, bash -n scripts/entrypoints/run-tests.sh
  • bash build.sh --no-open (CI app-build green on 99e6706; no Swift toolchain in the session)
  • bash run-tests.sh (CI checks green on 99e6706)
  • bash run-integration-smoke.sh / spm-tests (CI, running on the latest head)
  • Two independent code reviews plus a re-check, findings fixed
  • Manual check: Mac test plan written (11 cases, including ChatGPT voice with a tab switch, chat apps left open, Meet, Zoom and Teams web, native Zoom, relaunch and reset, Accessibility off, and a rebound shortcut). Runs with the next release batch.

Risk Review

  • Privacy / local-first behavior reviewed: window titles stay on the Mac, are never logged or sent; call_evidence and app_signal are fixed enums. No new permissions.
  • Storage path or migration impact reviewed: one new UserDefaults key, meeting-prompt-learned-backoff-v1.
  • Public-facing copy stays concrete and matches current product scope
  • Release/update impact reviewed: PostHog insights that count meeting_prompt_outcome_recorded suppressions or suppression events should be annotated on release.
  • Agent PRs stay draft until human review
  • UI changes include sanitized .agent-review/visuals/ evidence (no layout changes; prompt text and one permission summary line changed)
  • No private transcripts, audio, tokens, personal paths, or customer data are included

Notes

🤖 Generated with Claude Code

https://claude.ai/code/session_01FW21bnupYQgxzJewPJgDup

…t now

WIP: browser mic needs a call tab title, or 20s with audio/camera, or 60s;
known non-call sites (ChatGPT, Loom) never prompt; Not now escalates and
persists per kind of call; suppressions send one event instead of two.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FW21bnupYQgxzJewPJgDup
…etector gating

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FW21bnupYQgxzJewPJgDup
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FW21bnupYQgxzJewPJgDup
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FW21bnupYQgxzJewPJgDup
@claude claude Bot assigned r3dbars Sep 24, 2026
@claude
claude Bot requested a review from r3dbars September 24, 2026 00:01
Only titles that exist during a call count from any window; chat apps,
call sites and the Meet home page only count while focused, and mail or
calendar titles are skipped. A focused non-call site sticks for the
browser session, so ChatGPT voice stays quiet after a tab switch.
Window titles are read off the main actor under a 0.3 s budget.

Learning: learned-off only applies when titles are readable, the camera
and call sites get their own kinds that only back off, a Not now to any
browser prompt covers the rest of that call (capped at 8 h), manual
recordings only credit kinds the call raised, old recordings age out,
and turning detection off and on resets it. Call-site prompts get their
own id and never quiet a whole provider.

Also: 30 s grace when a browser lets go of the mic, the camera wait
starts when that browser comes to the front, title re-reads stop once a
prompt is decided, the funnel keeps prompted or recorded sessions, the
nudge skips learned-quiet calls, tab-named calls report their provider,
not_a_call carries non_call_site evidence, and the Accessibility summary
mentions spotting browser calls.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FW21bnupYQgxzJewPJgDup
…nded

A Meet tab named after a calendar topic ("Meet - Q3 calendar planning")
was skipped by the mail and calendar filter and fell back to the generic
prompt; Meet's own title patterns now run first. meeting_detected_call_ended
now carries app_signal (browser_mic, native_output...) so the funnel can
split browser calls from native ones. Notes that Teams in-call prefixes
are English only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FW21bnupYQgxzJewPJgDup
- Present nothing while a browser session's first title read is still
  running, so a calendar prompt cannot win over the actual call (fixes the
  generic-browser-vs-calendar test that the async read broke).
- Keep a browser Not now through the 30 s mic-release grace, so a muted
  Safari call is not re-asked on unmute.
- Call apps are checked before non-call words, markers match whole
  words, and a non-call site only sticks when it was in front within
  10 s of the browser taking the mic.
- Mail and calendar titles only count for a leading Meet code; mail
  detection matches the app's own title part so "Meet - Q3 calendar
  planning" still counts; the Zoom rule no longer matches Zoom help pages.
- Compare browser families as app families (WebKit is Safari).
- stop() ends the browser session instead of leaving a cancelled read.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FW21bnupYQgxzJewPJgDup
CI's fast-test compile timed out type-checking an untyped literal
array of chained multiplications inside assertEqual.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FW21bnupYQgxzJewPJgDup
The app-reminder, call-detected and missed-call prompts hardcoded
"Option-M" even after the shortcut was rebound, and the missed-call
nudge said "Tap Record" on a Mac. They now show the current binding the
way the menu bar does (for example ⌥M) and say Click.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FW21bnupYQgxzJewPJgDup
@claude
claude Bot marked this pull request as ready for review September 24, 2026 04:39
@claude
claude Bot merged commit 26f650e into main Sep 24, 2026
7 checks passed
@claude
claude Bot deleted the claude/project-thread-3bya58 branch September 24, 2026 04:39
claude Bot pushed a commit that referenced this pull request Sep 24, 2026
… audio

Sources/Meeting/CLAUDE.md: take #1800's MicActivityMonitor entry and keep
the note that the one-shot currentMicInputBundleIDs (Boost Mic) runs on a
detached task.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgXMcLRGBtiSiybdgyD1VJ
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.

2 participants