Skip to content

Lifecycle: the diagnostics report cannot describe the most common bug report, and two Candela builds can drive DDC at once #57

Description

@Rydersel

Current status

Resolved on main by #92, together with the earlier diagnostics work below. Changes merged after 1.0.3 have not been published as a release.

Completed in #92

  • Record why each online but uncontrolled display was excluded, including the online and controlled counts.
  • Make scheduled update prompts visible for the menu-bar app.
  • Prevent two Candela processes from driving the same displays.
  • Handle migrated preferences without an unexplained permission prompt on a new Mac.

Merged into main

Shipped

Verification result

The connected Dell U2725QE and MSI MAG 341C OLED checks are recorded in #92, including manual observations, automated regressions, and measurement limits. Live VoiceOver testing was deliberately skipped; accessibility labels and markers were checked. The numbered protocol below is retained as the original verification procedure.

Original investigation

The observations and procedure below describe the original report. Completed portions are identified in the current status above; their recorded verification is linked from the merged PR.

Four defects around app lifecycle: what the diagnostics paste can say, where update dialogs appear, what happens when two copies of the app run, and what a Mac restored from another Mac sees on first launch.

What happens

  • The diagnostics report cannot describe the most common bug report. "My monitor doesn't show up" produces a report that lists only the displays Candela already controls, so the interesting case (a display that was dropped, and why) is invisible. The report also omits whether the media-key tap is running, whether Screen Recording is granted and whether OLED care is enrolled. The tap state is shown on the diagnostics page itself but is not in the text a user pastes into an issue.
  • Update dialogs can come up behind everything. The app has no Dock icon, and the updater is built with no user-driver delegate and nothing that brings the app forward. For a scheduled check, the update framework's background-app branch shows the alert without activating and orders it behind whatever key window is up, so the first sign of an update can be nothing at all. The framework also logs a one-shot error for a background app that schedules checks without gentle reminders.
  • Nothing stops two Candela processes from driving DDC at once. Candela/App/CandelaApp.swift is the whole entry point and contains no running-instance check. LaunchServices blocks a second launch of the same bundle path, but not a debug build launched beside the installed release build, which is exactly what the normal build loop produces. Both processes discover the same display service and both write to it. Only one DDC writer should ever be running.
  • A Mac restored by Migration Assistant skips Setup and fires a bare system permission dialog. First run is defined as "no stored preferences schema version", but migration carries the whole preferences domain across, so the new Mac is not a first run. Accessibility grants, however, are per machine. The launch path therefore skips Setup, which exists to explain the grant, and fires the raw system dialog with no context around it.

Expected

The diagnostics paste should report how many displays are online against how many are controlled, name each uncontrolled display by vendor and model with the reason it was dropped, and state the media-key tap's state and the Screen Recording grant; a scheduled update should announce itself somewhere visible in the app instead of showing a dialog behind other windows, while a manual check keeps working exactly as it does now; launching a second copy of Candela should alert, naming both paths, and quit itself rather than share the DDC bus; and a Mac restored from another Mac should either run Setup or stay quiet until the user asks about keys, never fire a bare system dialog out of nowhere.

Notes

  • Diagnostics: the app does not currently know which displays it dropped. Display discovery drops candidates at two points (a policy that excludes built-in and virtual displays, and a filter for dummy plugs and missing services) and returns nothing about the drops. So the fix is either to widen discovery to return what it dropped and why, or to take the numeric vendor and model from the configurator's display list and pair each with a reason word: "virtual", "no DDC service", "dummy". Three lines go into the snapshot and the renderer; the renderer stays pure. No serial number on the uncontrolled line: follow the existing rule about which identifiers may appear in a pasted report. The snapshot's initializer is public and memberwise with two call sites, one of them a test.
  • Updates: the manual-check half of this needs no work. The update framework already activates the app for user-initiated checks, so adding activation there would be redundant. The scheduled half is the real one, and the fix is gentle scheduled reminders rather than activation: declare support for them, decline to show a scheduled update in immediate focus, put a marker on the status item or a panel row when the framework says an update is about to be shown, and clear it when the update session finishes. The risk to design against is a marker that never clears, which would make updates unreachable.
  • Two instances: exclude the app's own process id when checking for other running instances. The virtual-display helper is a bare process launched on the same executable and never becomes an application, so it will not appear in an application-level check; do not fall back to a process-name search, which would find it and produce a false positive. Terminate the new instance, not the running one, and have the alert name both bundle paths so a user can tell which is which. The risk here is a false positive locking someone out at launch, so the check should be narrow.
  • Migration case: suppress the launch-time permission prompt when the grant is missing and no marker says the media-key tap has ever been armed in this preferences domain. That marker is a new, additive preference key. The panel banner and the Keyboard pane already carry the ask, and the permission recheck still prompts when the user changes a key mode, so nobody loses the ability to grant. State the transition plainly: existing installs that never granted also stop seeing the launch prompt once. Needs a decision: ship the preference key now, or go straight to a machine-scoped marker under Application Support, which is the more correct answer because it does not travel with a migrated preferences domain at all. Getting the marker's polarity wrong would silence the nudge for someone who deliberately revoked the grant, though the banner and pane would still be there.

Hardware verification

  1. Attach a display Candela cannot control (a dummy plug, or a display with no DDC service) alongside the Dell U2725QE. Open the diagnostics page, copy the report, and confirm it names an online count higher than the controlled count and lists the uncontrolled display by vendor and model with a reason word, and with no serial number. Positive control: with only the Dell and the MSI MAG 341C attached, the two counts must match and no uncontrolled lines appear, so the new lines are not just always printed.
  2. Confirm the same report states whether the media-key tap is running and whether Screen Recording is granted. Revoke Screen Recording in System Settings and confirm that line flips on the next report.
  3. Point the app at a locally served test appcast and let a scheduled check run with the app in the background. No dialog may take focus, and a marker must appear on the status item or in the menu-bar panel. Click it and confirm the update dialog comes forward. Positive control: press Check for Updates with a real mouse click, not a synthetic accessibility press, and confirm the dialog comes to the front immediately, proving the manual path was not broken by the delegate.
  4. Let one scheduled update session finish (install or dismiss) and confirm the marker clears. Repeat once with the update declined, since a marker that never clears is the failure mode.
  5. With the release build in /Applications running, launch a debug build from its build directory. The second copy must alert, name both paths and quit itself, and the first must keep controlling both panels. Positive control: launch the release build again from /Applications and confirm the existing single-instance behaviour is unchanged. Do this only when nothing else is driving the displays, since two DDC writers on one bus is the state this guards against.
  6. Confirm the virtual display helper still works after the check lands: create and destroy one virtual display with the project's stable identity and confirm no alert appears.
  7. Migration case: quit the app, run tccutil reset Accessibility com.rydersel.Candela against the existing preferences domain, and relaunch. No bare system dialog may appear, and the panel banner and the Keyboard pane must both offer the grant. Allow around 13 seconds, which is how long that dialog has taken to surface when it does fire. Positive control: change the key mode in the Keyboard pane and confirm the prompt still appears there, so the suppression did not remove the only remaining way to grant.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions