Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Meetings: idle detection stops redrawing the app every 3 seconds, and the page knows a download is under way

Open
#378 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
14/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
macos, swift

Research direction

Start with LocalVoice --check-meetings and the detection loop in MeetingModel.swift (refreshDetection, around :717-718), then MeetingDetection.swift's snapshot and evaluate paths. The speech-readiness part touches MeetingAdmission.swift and MeetingWorkspaceView.swift, with the gallery's meetings-setup shots. Done means the listed acceptance checks pass: 0 publishes over 5 idle polls, a single publish on the 3rd Zoom observation, and the new speech title and caption assertions, plus native confirmation.

Written by the indexing model from the issue text.

Description

bug

Outcome: Meetings: idle detection stops redrawing the app every 3 seconds, and the page knows a download is under way.

Verified by an independent skeptic against the release train head d7cea35 (round four of the 15–16 October pass: energy, leaks, first run, keyboard, concurrency, settings propagation, public docs).

Idle meeting detection stops redrawing Home and the toolbar every 3 seconds, and reads only the apps it can offer

What a person sees: With Detect Meetings & Calls on and no call, every 3-second poll does a lot of UI work although nothing on screen changes. It redraws the whole Home page when Home is open and both floating-toolbar views. It also runs the toolbar host's placement pass, which forces layout and calls orderFrontRegardless. That is about 1,200 times an hour. The poll also blocks the main thread for about 11 ms. Together these are the maintainer's recorded ~1% idle sample. After the change, an idle Mac does no UI work for detection, and the poll costs about 3 ms. Offers, their order, the PID offered, the three-observation rule, cooldowns, snooze and the auto-finish check behave exactly as now.

Evidence (d7cea35): Part A, publishing. MeetingModel is @MainActor (MeetingModel.swift:8-9) and offer is @Published (:11). configureDetection loops every 3 s (:695-707). refreshDetection assigns offer = detector.evaluate() unconditionally (:717), and nil→nil also fires objectWillChange. :718 re-sets error, and with it the @Published problem, on every poll while a metadata error persists. The disabled branch (:713) assigns offer = nil too. Subscribers to every MeetingModel publish: WorkbenchHomePage @ObservedObject var meetings (WorkbenchHome.swift:562); both toolbar views (FloatingToolbar.swift:85, :395); the toolbar host sink (CapturePanel.swift:343-345 → update(model:) :405-458, which calls measureToolbar :434 when on tools, orderFrontRegardless :444 and updateCoach :457); ToolbarModeFollower (ToolbarModeFollower.swift:38); and the offer sink (MeetingWorkspaceView.swift:380). That sink already ignores an equal offer (:387-388) and hides on nil, so skipping equal assignments changes no panel behaviour. evaluate() returns nil while suppressed (MeetingDetection.swift:152), so busy still clears the offer. Existing house rule: MeetingModel.swift:171 if admission != next, and AppCoordinator.swift:811-814 (Timer publishes only on change). Part B, reading. snapshot() reads the PID, bundle ID and two HAL activity properties, and builds an NSRunningApplication, for every Core Audio process object (MeetingDetection.swift:30-47). evaluate() then sorts the whole list twice in availableApps (:117-147), with comparators that call MeetingAppCatalogue.known(), a linear scan whose owns() lowercases and allocates per call (MeetingModels.swift:29-35, :86-88). Only catalogue apps (:160) and the call service (:170) can become offers. A non-catalogue process keys choices by its own bundle ID (:

Change: A (must, MeetingModel.swift):

  • In refreshDetection, replace :717-718 with let next = detector.evaluate(); if next != offer { offer = next } and if let issue = detector.lastError, !isBusy, problem != .unknown(issue) { error = issue }.
  • In its guard branch (:713) and in configureDetection's disabled branch (:697), write if offer != nil { offer = nil }.
  • Change nothing else in the model.

B (MeetingDetection.swift):

  • Add static func considers(_ bundleID: String) -> Bool { MeetingAppCatalogue.known(bundleID) != nil || MeetingAppCatalogue.callServiceBundleIDs.contains(bundleID) } to MeetingDetector.
  • Add the requirement func detectionSnapshot() throws -> [MeetingProcessSnapshot] to MeetingProcessSource. Give it an extension default try snapshot().filter { MeetingDetector.considers($0.bundleID) }, so the three fixture conformers compile unchanged.
  • In MeetingSystemProcessSource, move the loop into one private snapshot(including: (String) -> Bool). It reads the bundle ID first, checks exclusion and including, and only then reads the PID, both activity flags and NSRunningApplication. snapshot() passes { _ in true } and detectionSnapshot() passes MeetingDetector.considers. Do not keep two copies of the loop.
  • MeetingDetector.read() and activity(for:) call detectionSnapshot(). enumerateApps() (the picker) keeps snapshot().
  • Leave out the optional catalogue re-keying.
  • The PR text should say that an activity-read error on an unrelated process no longer fails the poll.

Checks (MeetingChecks.swift, --check-meetings):

  • Attach an objectWillChange counter after enabling detection and one warm-up poll. 5 idle polls give 0 publishes. With a Zoom input fixture, the 3rd poll gives exactly 1 publish carrying the offer and the next 2 give 0.
  • A persistent fixt

Acceptance: LocalVoice --check-meetings passes when these all hold:

  • 0 MeetingModel.objectWillChange over 5 idle refreshDetection() calls, with the counter attached after enabling detection and one warm-up poll.
  • Exactly 1 publish when a Zoom input fixture reaches its 3rd observation, and 0 on the next 2 polls.
  • A persistent metadata failure sets problem once.
  • Full-snapshot and filtered-snapshot detectors driven through the same streak, cooldown, Not now, snooze, disable and PID-change sequence over a ~40-process fixture return identical offers (id, name, bundleID, including Chrome's main-app PID) and identical activity(for:) results.
  • The existing offer-panel lifecycle assertions (MeetingChecks.swift:602-656) and LiveVoiceExperienceChecks activity checks still pass.
  • No assertion depends on two live Core Audio reads agreeing.

Owed natively and not needed to merge: a sample of the installed Preview with detection on and Home open no longer shows an NSHostingView transaction or CapturePanelController.update every 3 s.

Value: 3 of 5 · Grammar: Quality · Risk: Low. Part A only stops publishing values that have not changed. The one offer consumer already ignores an equal offer, and default users (detection off) never had the 3 s tick, so nothing can depend on it. Part B is equivalent for offers and activity because the catalogue and call-service keys cannot collide with non-catalogue bundle IDs. Its only behaviour change is that a failed activity read on an unrelated process no longer suppresses detection or shows an error, which is a robustness gain t

Meetings says "Speech setup needed / Open Models before starting" even while the download the person started is running, with no button beside it

What a person sees: A newcomer who starts the Parakeet download from Home and opens Meetings to get ready for a call reads 'Speech setup needed'. A problem card then tells them to 'Open Models before starting speech recognition. Downloading Parakeet · 42%': to set up something that is already setting up, with no button. Before the download, the same sentence appears twice, in the card and as the caption, and the only door is a small Models… link. Dictate handles these states with 'Preparing dictation…' and 'Dictation unavailable'.

Evidence (d7cea35): MeetingModel.swift:153 turns any snapshot that cannot transcribe into .speech(host.recognition.line), including a running download (canTranscribe is false while operationID is set, RecognitionReadiness.swift:17). AppModel.acceptRecognition calls meetings.refreshAdmission() on every progress delivery (AppModel.swift:270). MeetingAdmission.swift:15 prefixes 'Open Models before starting speech recognition. ', and :32 gives the title 'Speech setup needed', which shows at MeetingWorkspaceView.swift:82. statusLine stays 'Start once and follow the words as they happen · Up to 2 hours' (:234-239). The problem card (:159-161) has buttons only for microphone and app-audio problems (:164-180). The caption repeats the same readiness line beside the page's only Models… link (:151-153; engineName is model.modelMessage, WorkbenchHome.swift:208). Dictate: Views.swift:294 'Preparing dictation…' and :296 'Dictation unavailable'. The gallery renders only the idle setup state.

Change: Make Meetings' speech readiness look like Dictate's: one title, the readiness line once, no triangle card while speech is merely unready or preparing.

  1. MeetingAdmission.swift

    • Add var speechPreparing = false to MeetingAdmission.
    • Title: when captureProblem is .speech, return "Preparing speech…" if speechPreparing is true, otherwise "Speech setup needed". All other titles stay as they are.
    • .speech(detail)'s message becomes the detail alone, without "Open Models before starting speech recognition."
  2. MeetingModel.refreshAdmission

    • Pass speechPreparing: host.recognition.isPreparing into the admission it builds.
    • Mirror the microphone clear at :172: if problem is .speech and host.recognition.canTranscribe, call setProblem(nil). That removes the stale card a refused call-offer Start leaves behind.
    • Precedence of .speech over source and microphone problems is unchanged.
  3. MeetingWorkspaceView.swift

    • statusLine: when idle and model.admission.recognitionProblem != nil, return engineName (the readiness line, e.g. the progress or the Download offer).
    • Bottom engine row: while recognitionProblem != nil and idle, show only the Models… link, not the label, as Dictate's dictateEngine does (Views.swift:274-276). The line then appears once.
    • Problem card (:159): skip .speech whether it comes from model.problem or admission.captureProblem; the title and caption already carry it.
    • Start recording stays disabled through admission.canStart. Kept-for-later Transcribe stays disabled through recognitionProblem.
    • No spinner or Cancel setup is added here; Models owns Cancel.
    • Expose the caption decision as a small static helper, so a check can call it the way WorkbenchPageChecks calls ContentView.progressCaption.
  4. Meet

Acceptance: 1. LocalVoice --check-meetings, run from scripts/test.sh:97, passes with the new assertions:

  • A preparing snapshot gives admission.title "Preparing speech…", canStart false, and no "Open Models before" text in any MeetingProblem message.
  • An idle unavailable snapshot gives "Speech setup needed".
  • The caption helper returns the snapshot's line ("Downloading Parakeet · 42%", or "Download Parakeet to turn speech into text on this Mac.").
  • The card predicate excludes .speech.
  • A .speech problem from a refused start is nil after a localReady snapshot.
  • Existing admission checks (:1186-1245) still pass.
  1. The gallery's Meetings recovery pass renders meetings-setup and meetings-setup-preparing in light and dark. Each shows the setup title, the readiness line exactly once (under the title), the Models… link, a disabled Start recording, and no orange-triangle card.
  2. The existing microphone and source-failure shots are unchanged.

Value: 2 of 5 · Grammar: Quality · Risk: Low.

  • It changes copy and layout in MeetingWorkspaceView, which three train PRs touched tonight (#343, #349, #371), and it changes the build under test right before ship. Rebasing is trivial.
  • .speech still masks microphone and source problems until speech is ready, as it does today, so a newcomer with a denied microphone still learns of it only after setup.
  • A call-offer Start refused for speech now explains itself only through the title and caption, with no dismissable card. Detection is

Refs #280


Build-ready record from round four of the overnight lead pass, verified by an independent skeptic per item. Closes only after native acceptance.

Dominant language
Swift
Stars
0
Forks
1
Avg merge
8h 5m
Merged PRs (30d)
215

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from Ship-Work/workbench

All issues in Ship-Work/workbench

Similar issues

More Swift issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.