Skip to content

P20/P29: cursor-on-black detection, repro tooling, auto-recovery - #29

Merged
toobuntu merged 24 commits into
mainfrom
feature/p20-detection-diff
Jul 20, 2026
Merged

P20/P29: cursor-on-black detection, repro tooling, auto-recovery#29
toobuntu merged 24 commits into
mainfrom
feature/p20-detection-diff

Conversation

@toobuntu

Copy link
Copy Markdown
Owner

Summary

Closes out the empirical phase of the cursor-on-black investigation (technical-debt P20/P29) and lands the first automatic recovery.

  • Detection: field-by-field diffing of matched black/clean diagnostic bundles found one host-visible signal that separates cursor-on-black wakes from clean ones — WindowServer's hotplug-coalescing log line. Validated 56/56 black vs 0/33 clean across matrix runs 1–104 (cable-triggered, software-triggered, natural, locked, AC, and lid regimes), with controls showing the trigger itself never plants the marker. Every other candidate field (dcp/connection-mode/ioreg/config) diffs byte-identical — negatives recorded.
  • Repro tooling: repro gained a software cable-trigger (--trigger extcycle, 26/29 black), built-in sudo priming, --lock, notification-based wake detection with an "awake" cue and a 60 s did-not-sleep guard, and a machine-filled trigger field on run sheets.
  • Recovery: the displaysleep cycle cleared every programmatic recovery attempt in the matrix, locked (n=5) and unlocked. The daemon now runs it automatically at wake-settle when the marker is present, gated by a new recoveryStrategy pref (blackoutd recovery <none|displaysleep>, default displaysleep). Decision recorded as ADR 0010; architecture.md updated.
  • Follow-up levers in the tree: recover --method extcycle (unlocked-only; hazardous while locked — documented) and an experimental fbpower IOMobileFramebuffer probe for the flicker-free follow-up.
  • Data: 94 run sheets (runs 11–104), matrix index tables and verdicts (MonitorControlLite retired as a confound; attribution: host-side macOS SkyLight/DCP wake path), and the full P20/P29 update trail.

Verification

Manual, on the target hardware (M2 MacBook Air + Dell SP2309W): 94 recorded eyewitness-paired repro runs; every code change exercised live by the maintainer during collection (wake detection, trigger, lock, recovery methods). Build, clang-format, and clang-tidy clean; dry-run walkthroughs for every new CLI path. The daemon's live auto-recovery has been validated.

Written with AI assistance (Claude Fable 5, high); all display-state observations are the maintainer's, recorded in the run sheets.

toobuntu and others added 19 commits July 20, 2026 02:20
Repro-generated run sheets for cohort 2, collected 2026-07-19 on the
v0.4.0 build. Group A baseline runs 11-24 and 36 (blacks 14, 22, 24
on-condition; 11 black on AC; 17 settled E2 variant; 19 disregarded
as possibly misreported) and group B displaysleep-recovery runs 25-35
and 37-40 (one black, run 37, cleared by the recovery).
Add group A cohort 2 and group B tables indexing run sheets 11-40,
with the off-condition notes (run 11 on AC, run 17 settled E2, run 19
disregarded) and a pointer to the P29 detection result.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cohort 2 met the capture gate (3 on-condition black, 9 clean settled
group A captures). Field-by-field diff of the post-wake bundles:

- dcp.txt / connection-mode.txt / config.txt / ioreg.txt: negative.
  Black and clean bundles are byte-identical (ioreg after masking
  volatile counters), closing the NormalModeActive and
  DCPPowerAssertionCount questions from the 2026-06-10 update.
- windowserver.txt: positive. The SkyLight hotplug-coalescing line
  (out replaced with in for display 2) appears in the wake window of
  every black-panel run and no clean run: 10/10 vs 0/15 across both
  cohorts (cohort 2: blacks 14, 22, 24 + corroborating 11, 37, 17;
  cleans 12, 13, 15, 16, 18, 20, 21, 23, 36; cohort 1 corroborates
  4/4 vs 0/6).

Supports hypothesis A: the external link drops during sleep and the
wake coalesces the out/in pair, so WindowServer skips the full
re-attach and the DCP scanout stays un-rearmed. Group B recovery
held 1/1 (run 37). Next: second group B black, then a read-only
log-query detector prototype at wake-settle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Group B extension runs 41-57 (blacks 49, 51-54, 56-57 — all but 49
provoked by the new C1 cable trigger; run 55 the C1 miss) and the
first group C runs 58-60 (58 effectively unlocked due to lock
ordering; 60 the first locked black, cleared while locked).
Add the R0/M1/C1 short-code legend (C1 = pre-run USB-C unplug/replug
cable trigger), the built-in immediate-state caveat (possible full
inversions with the cursor unseen; observed directly on runs 54,
56-57), the group B extension rows 41-57 with the group's verdict
(displaysleep cleared 8/8 blacks), and a group C section recording
the working lock-before-repro osascript invocation and runs 58-60.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Marker validated on the extension: 9/9 blacks vs 0/11 cleans,
running total 19/19 vs 0/26; FuseBoard secondary demoted (split
breaks on runs 49 and 60). Record the C1 cable trigger as the first
on-demand repro lever, the displaysleep recovery verdict (10/10
programmatic clears including one while locked), and the surveyed
engineering paths: IOMobileFramebuffer HPD notifications as the
deterministic upstream detection candidate (OSLogStore as the
implementation of record meanwhile), and RequestPowerChange / CGS
external cycle / DDC 0xD6 as targeted recovery candidates to test
against C1 blacks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Build the P29 candidate (2) primitive — a CG transaction that
disables then re-enables the external display — and expose it two
ways: as a second recovery method (recover --method extcycle, repro
--recover extcycle) and as a pre-sleep repro trigger (--trigger
extcycle), the software analog of the C1 cable trigger (disable,
5 s for the built-in restore/redraw, re-enable, 5 s re-attach
settle). The run sheet's conditions block gains a machine-filled
trigger line so soft-trigger runs are distinguished from physical
C1 runs, and --help now documents the recover and repro methods,
which were previously undocumented.

The daemon needs no changes: it reacts to the cycle's
reconfiguration callbacks as it would to a physical unplug/replug
(safety-invariant restore, then auto-blackout on re-attach).

Verified: make build clean; clang-format and clang-tidy clean;
--dry-run walkthrough exercises trigger placement, recovery
dispatch, and the unknown-method/trigger errors. Runtime behavior
against a real black is pending maintainer C1 runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Document --trigger extcycle in the matrix how-to and add the
trigger line to the per-run template; extend P29 with the tooling
note and its validation order (soft-trigger A/B, then extcycle as
recovery against C1 blacks). Correct run 58 in both docs: the
maintainer later recalled the cable trigger may have been applied,
so it must not be cited as a trigger-free natural repro.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Soft-trigger series 61-80 (--trigger extcycle; 18 blacks, misses 71
and 72; all W1 — scheduled wake failed, manual wake) split between
displaysleep recovery (61-68) and extcycle recovery (69-80, cleared
10/10 unlocked), and group C runs 81-85 (--recover extcycle while
locked: failed on both blacks and induced black externals on clean
wakes, run 84 leaving the external CG-offline).
Every --trigger run (61-80) hit W1 — scheduled wake failed — because
the wake was scheduled before the trigger, whose ~15 s of cueing and
settling left the wake time in the past by the time the machine
slept. Reorder repro: prime sudo (sudo --validate is now built in;
no manual prefix), run the trigger, then schedule the wake with the
lead computed after the trigger completes.

Add an "awake" cue spoken within ~1 s of the wake: instead of one
fixed post-sleepnow interval, 1 s ticks detect the wake (a tick
whose wall duration jumps past 5 s spanned the sleep), which also
anchors the settle countdown at the wake itself and makes manual
and overnight wakes capture correctly.

Add --lock: issue the ctrl-cmd-q session-lock keystroke (System
Events, as validated manually in runs 59-60) just before sleep,
after every possible sudo prompt, replacing the external osascript
chain for group C runs. Abort if the lock fails so a run recorded
as locked was actually locked.

Verified: build, clang-format, dry-run walkthroughs of the
trigger+lock and wake=0 paths. Live behavior pending maintainer
runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Matrix: add W1 and M0 short codes, the awake cue and built-in sudo
priming and --lock to the how-to, the soft-trigger series table
(runs 61-80) with its verdicts (trigger 18/20; extcycle recovery
10/10 unlocked), and the group C extension table (runs 81-85:
extcycle while locked fails and induces blacks, run 84 leaving the
external CG-offline).

P29: evening update — marker running total 39/39 vs 0/31 with proof
the trigger does not plant it (clean trigger runs 71-72; marker
timestamps at the wake); W1 root cause and same-evening fixes;
extcycle recovery lock-state split; attribution assessment (host-side
macOS display stack; cable and monitor exonerated as necessary
conditions; MonitorControlLite control series proposed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tick-loop wake detector only noticed a wake when one 1 s tick
spanned more than 5 s of wall clock, so a short sleep (lead mostly
consumed by falling asleep) woke undetected and repro spun silently
with no ceiling — the post-run-85 shakedown failure.

Register NSWorkspaceWillSleepNotification and
NSWorkspaceDidWakeNotification observers before sleepnow and wait on
the did-wake notification in 1 s run-loop slices: deterministic for
arbitrarily short sleeps. Keep the tick-jump check only as a
fallback for missed delivery, and abort if no will-sleep arrives
within 60 s of sleepnow so a sleep that never happens errors instead
of hanging; the wake side has no ceiling on purpose (overnight
manual wakes are a legitimate run shape).

Also schedule the wake immediately before sleepnow — after the lock,
condition reads, and cue speech — so the lead is consumed only by
falling asleep, and skip the interactive sudo fallback when --lock
is active (nobody can answer a prompt on a locked screen).

Verified: build, clang-format, clang-tidy clean; dry-run walkthrough
of the trigger+lock path. Live sleep/wake pending maintainer
shakedown.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P29: the post-run-85 shakedown reproduced the black on the reordered
build but exposed the tick-loop wake detector (sub-5 s sleeps woke
undetected; loop had no ceiling); record the notification-based
rework, the 60 s did-not-sleep guard, the schedule-last timing
change, and the note that userspace ticks can neither cause nor
prevent a wake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Shakedowns 86-89 on the notification-based wake detection (no W1),
MonitorControlLite-quit control series 90-97 (blacks unabated —
confound retired), locked displaysleep runs 98-102 (recovery cleared
4/4 locked blacks), and group E lid runs 103-104 (103: AC, clamshell
closed, woke clean to the external; 104: battery, no closed-lid
wake, lid-open wake went black and displaysleep cleared it).
Runs 87, 89, 90, 91, 96, 97 ran on AC power (machine-recorded),
which is coverage row D, not B — rename the sheets' grp tags and
group fields per the maintainer. Locked AC runs 98-99 stay group C
(D is defined unlocked; the power field carries the truth). Bundle
directory names carry no group tag, so no bundle paths change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire the validated detection and recovery into the daemon. At
wakeSettleTimerFired, when an external display is present, query the
unified log (log show over a window opening 30 s before the recorded
wake — the same unprivileged path diagnose uses) for the WindowServer
hotplug-coalescing marker that separates black wakes from clean ones
(56/56 vs 0/33 across matrix runs 1-104). Marker present: run the
displaysleep cycle (pmset displaysleepnow, 2 s, caffeinate -u) off
the main queue — the recovery every programmatic clear in the matrix
used, validated locked and unlocked. Marker absent: log and do
nothing.

Gated by a new recoveryStrategy pref (String, default displaysleep;
none disables; unknown values fall back to none with a warning),
reloaded via SIGHUP, settable with the new blackoutd recovery
<none|displaysleep> subcommand (P23 verbosity pattern) and surfaced
in status and diagnose config.txt. Guards: a once-per-wake latch (the
settle timer can re-fire on err=1014 retries), a _systemSleeping
re-check after the query, and the fact that the display-sleep cycle
emits screen (not system) sleep notifications, so it cannot re-arm
the wake path and loop.

The marker wording is private SkyLight internals pinned to macOS 26;
its absence is always treated as clean-or-unknown, never an error.

Verified: build, clang-format, clang-tidy clean; recovery subcommand
usage/validation exercised; status shows the new line against the
live daemon. Live wake behavior pending the maintainer's next runs
(note: matrix data collection now requires blackoutd recovery none
first).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Matrix: index runs 86-104 (shakedowns, MCL controls, groups C/D/E)
with the block's verdicts — MonitorControlLite retired as a
confound, locked displaysleep at n=5, group D answered at n=6, group
E's lived scenario reproduced and recovered, awake-cue audibility
quirk — plus the data-collection caveat that the daemon's new
auto-recovery must be disabled (blackoutd recovery none) before
matrix runs. All-time marker tally 56/56 black vs 0/33 clean.

P20/P29: record the confound retirement and the implemented
detector-gated displaysleep auto-recovery (recoveryStrategy pref,
blackoutd recovery subcommand); flicker-free candidates and BD RE
remain the follow-up. AGENTS.md: document the recoveryStrategy
NSUserDefaults key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Experimental CLI-only recovery candidate for the flicker-free
follow-up (P29): open the external framebuffer by name via private
IOMobileFramebuffer.framework (dlsym-resolved, no link dependency)
and request a power 0 -> 1 cycle. Signatures are reconstructed from
public reverse-engineering headers and unverified on macOS 26; the
probe prints every return code, and even a refusal at open is an
answer — the HPD-notification detection idea depends on the same
user-client access. The daemon cannot invoke it (recoveryStrategy
accepts only none|displaysleep).

Verified: build, clang-format clean; dry-run and unknown-method
paths exercised. Live invocation is maintainer-run against a
soft-trigger black.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Record the detector-gated displaysleep recovery decision as ADR 0010
(context, evidence tallies, guard rails, and the considered
alternatives: reconfig-flag keying, below-CG polling, unconditional
recovery, extcycle, IOMFB HPD notifications, OSLogStore). Add the
matching Key Design Decisions entry to docs/architecture.md, fix the
stale ADR count in AGENTS.md (9 ADRs, numbered through 0010; 0009 is
cited in docs but not yet written), and note the fbpower probe in
P29's flicker-free follow-up bullet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 107 files, which is 7 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --type committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 534df4ea-359f-4cc2-987e-1d5d7c075f3c

📥 Commits

Reviewing files that changed from the base of the PR and between fafd1b2 and db4d0f7.

📒 Files selected for processing (107)
  • AGENTS.md
  • docs/architecture.md
  • docs/debug/cursor-on-black-matrix.md
  • docs/debug/repro-matrix/run011-grp-a-20260719-000924.md
  • docs/debug/repro-matrix/run012-grp-a-20260719-082315.md
  • docs/debug/repro-matrix/run013-grp-a-20260719-110044.md
  • docs/debug/repro-matrix/run014-grp-a-20260719-110205.md
  • docs/debug/repro-matrix/run015-grp-a-20260719-110325.md
  • docs/debug/repro-matrix/run016-grp-a-20260719-110439.md
  • docs/debug/repro-matrix/run017-grp-a-20260719-120552.md
  • docs/debug/repro-matrix/run018-grp-a-20260719-120742.md
  • docs/debug/repro-matrix/run019-grp-a-20260719-120843.md
  • docs/debug/repro-matrix/run020-grp-a-20260719-120955.md
  • docs/debug/repro-matrix/run021-grp-a-20260719-121755.md
  • docs/debug/repro-matrix/run022-grp-a-20260719-121916.md
  • docs/debug/repro-matrix/run023-grp-a-20260719-122131.md
  • docs/debug/repro-matrix/run024-grp-a-20260719-122252.md
  • docs/debug/repro-matrix/run025-grp-b-20260719-122426.md
  • docs/debug/repro-matrix/run026-grp-b-20260719-122726.md
  • docs/debug/repro-matrix/run027-grp-b-20260719-122934.md
  • docs/debug/repro-matrix/run028-grp-b-20260719-131053.md
  • docs/debug/repro-matrix/run029-grp-b-20260719-131252.md
  • docs/debug/repro-matrix/run030-grp-b-20260719-131452.md
  • docs/debug/repro-matrix/run031-grp-b-20260719-131825.md
  • docs/debug/repro-matrix/run032-grp-b-20260719-132525.md
  • docs/debug/repro-matrix/run033-grp-b-20260719-132720.md
  • docs/debug/repro-matrix/run034-grp-b-20260719-132947.md
  • docs/debug/repro-matrix/run035-grp-b-20260719-140116.md
  • docs/debug/repro-matrix/run036-grp-a-20260719-141127.md
  • docs/debug/repro-matrix/run037-grp-b-20260719-141825.md
  • docs/debug/repro-matrix/run038-grp-b-20260719-142134.md
  • docs/debug/repro-matrix/run039-grp-b-20260719-142353.md
  • docs/debug/repro-matrix/run040-grp-b-20260719-142621.md
  • docs/debug/repro-matrix/run041-grp-b-20260719-145713.md
  • docs/debug/repro-matrix/run042-grp-b-20260719-150121.md
  • docs/debug/repro-matrix/run043-grp-b-20260719-150356.md
  • docs/debug/repro-matrix/run044-grp-b-20260719-150606.md
  • docs/debug/repro-matrix/run045-grp-b-20260719-150805.md
  • docs/debug/repro-matrix/run046-grp-b-20260719-151024.md
  • docs/debug/repro-matrix/run047-grp-b-20260719-151223.md
  • docs/debug/repro-matrix/run048-grp-b-20260719-151427.md
  • docs/debug/repro-matrix/run049-grp-b-20260719-151715.md
  • docs/debug/repro-matrix/run050-grp-b-20260719-152024.md
  • docs/debug/repro-matrix/run051-grp-b-20260719-152300.md
  • docs/debug/repro-matrix/run052-grp-b-20260719-152718.md
  • docs/debug/repro-matrix/run053-grp-b-20260719-153031.md
  • docs/debug/repro-matrix/run054-grp-b-20260719-153227.md
  • docs/debug/repro-matrix/run055-grp-b-20260719-153508.md
  • docs/debug/repro-matrix/run056-grp-b-20260719-153711.md
  • docs/debug/repro-matrix/run057-grp-b-20260719-153916.md
  • docs/debug/repro-matrix/run058-grp-c-20260719-154916.md
  • docs/debug/repro-matrix/run059-grp-c-20260719-155209.md
  • docs/debug/repro-matrix/run060-grp-c-20260719-155510.md
  • docs/debug/repro-matrix/run061-grp-b-20260719-202401.md
  • docs/debug/repro-matrix/run062-grp-b-20260719-202947.md
  • docs/debug/repro-matrix/run063-grp-b-20260719-203423.md
  • docs/debug/repro-matrix/run064-grp-b-20260719-203715.md
  • docs/debug/repro-matrix/run065-grp-b-20260719-203953.md
  • docs/debug/repro-matrix/run066-grp-b-20260719-204340.md
  • docs/debug/repro-matrix/run067-grp-b-20260719-204849.md
  • docs/debug/repro-matrix/run068-grp-b-20260719-205256.md
  • docs/debug/repro-matrix/run069-grp-b-20260719-205745.md
  • docs/debug/repro-matrix/run070-grp-b-20260719-210158.md
  • docs/debug/repro-matrix/run071-grp-b-20260719-210655.md
  • docs/debug/repro-matrix/run072-grp-b-20260719-211053.md
  • docs/debug/repro-matrix/run073-grp-b-20260719-211335.md
  • docs/debug/repro-matrix/run074-grp-b-20260719-211608.md
  • docs/debug/repro-matrix/run075-grp-b-20260719-212006.md
  • docs/debug/repro-matrix/run076-grp-b-20260719-212233.md
  • docs/debug/repro-matrix/run077-grp-b-20260719-212714.md
  • docs/debug/repro-matrix/run078-grp-b-20260719-212937.md
  • docs/debug/repro-matrix/run079-grp-b-20260719-213221.md
  • docs/debug/repro-matrix/run080-grp-b-20260719-214926.md
  • docs/debug/repro-matrix/run081-grp-c-20260719-215853.md
  • docs/debug/repro-matrix/run082-grp-c-20260719-220650.md
  • docs/debug/repro-matrix/run083-grp-c-20260719-220914.md
  • docs/debug/repro-matrix/run084-grp-c-20260719-221345.md
  • docs/debug/repro-matrix/run085-grp-c-20260719-222052.md
  • docs/debug/repro-matrix/run086-grp-b-20260719-233431.md
  • docs/debug/repro-matrix/run087-grp-d-20260719-235246.md
  • docs/debug/repro-matrix/run088-grp-b-20260719-235545.md
  • docs/debug/repro-matrix/run089-grp-d-20260719-235755.md
  • docs/debug/repro-matrix/run090-grp-d-20260720-000202.md
  • docs/debug/repro-matrix/run091-grp-d-20260720-000746.md
  • docs/debug/repro-matrix/run092-grp-b-20260720-001020.md
  • docs/debug/repro-matrix/run093-grp-b-20260720-001334.md
  • docs/debug/repro-matrix/run094-grp-b-20260720-001600.md
  • docs/debug/repro-matrix/run095-grp-b-20260720-002033.md
  • docs/debug/repro-matrix/run096-grp-d-20260720-002246.md
  • docs/debug/repro-matrix/run097-grp-d-20260720-002550.md
  • docs/debug/repro-matrix/run098-grp-c-20260720-002935.md
  • docs/debug/repro-matrix/run099-grp-c-20260720-003235.md
  • docs/debug/repro-matrix/run100-grp-c-20260720-003513.md
  • docs/debug/repro-matrix/run101-grp-c-20260720-003736.md
  • docs/debug/repro-matrix/run102-grp-c-20260720-004031.md
  • docs/debug/repro-matrix/run103-grp-e-20260720-004529.md
  • docs/debug/repro-matrix/run104-grp-e-20260720-004902.md
  • docs/debug/repro-matrix/run105-20260720-020909.md
  • docs/debug/repro-matrix/run106-20260720-021026.md
  • docs/debug/repro-matrix/run107-20260720-021403.md
  • docs/decisions/0009-sp2309w-color-quirk.md
  • docs/decisions/0010-cursor-on-black-detection-and-recovery.md
  • docs/technical-debt.md
  • src/AppDelegate.m
  • src/DisplayController.h
  • src/DisplayController.m
  • src/main.m

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Review on demand using usage pricing
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/p20-detection-diff

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@toobuntu

Copy link
Copy Markdown
Owner Author

@CodeRabbit review --dir src

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

@toobuntu: Starting a focused review of the changes under src.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

toobuntu and others added 5 commits July 20, 2026 03:09
Per external review of PR #29 (assessed and adopted): emit the log
show --start timestamp with an explicit local zone and offset (the
"YYYY-MM-DD HH:MM:SSZZZZZ" form log(1) documents) instead of relying
on the formatter's local-zone default matching log's local-zone
interpretation; check pmset displaysleepnow's exit status before the
2 s pause and caffeinate nudge, logging and returning early on
failure; and document why the marker window opens 30 s before the
recorded wake (notification-vs-hardware wake skew plus log-flush
latency, still far inside consecutive-run spacing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reconstruct the long-cited-but-unwritten ADR 0009 from technical-debt
P26, the 2026-05-25 session handoff, and inject_edid ADRs 0001-0002:
the SP2309W EDID correction, if hosted in blackoutd, lives as an
opt-in isolated quirk module (own translation unit under src/quirks/,
compiled only under make QUIRKS=sp2309w, runtime-keyed to vendor
0x10AC / product 0xD01D, one narrow core hook, no quirk framework,
self-originated reconfiguration marking that never weakens the safety
invariant). Status proposed, per P26's first acceptance criterion;
recorded retroactively with the reconstruction sources cited.

Correct AGENTS.md's ADR note: no 0009 collision existed —
repo-foundation's 0009 (logging) is a different repo's namespace.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live validation of the daemon's marker-gated displaysleep recovery
(runs 106-107; 105 was a false start): with recoveryStrategy
displaysleep active and no repro-side --recover, a soft-trigger
black wake (106) cleared to E0/B0 before the capture cue, and a
clean-side run (107) passed through untouched — both with no
perceived flicker, the cycle landing inside the wake transition.
Runs 106-107: marker-gated displaysleep cleared a black wake before
the capture cue and left a clean wake untouched, with no perceived
flicker (the cycle lands inside the wake transition) — lowering the
urgency of the flicker-free follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
adrs doctor flagged both for a missing Decision Outcome section.
Reshape to the org MADR 4.0 layout (RF ADR 0004 / 0018): Decision
Drivers, Considered Options, Decision Outcome with "Chosen:",
Good/Bad/Neutral-because Consequences, Confirmation (0010 cites the
live validation runs 106-107; 0009 records the standalone-confirmed
mechanism with the hosted form pending), and Pros and Cons of the
Options. Content unchanged; adrs doctor now reports no issues.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@toobuntu
toobuntu merged commit 2fbab14 into main Jul 20, 2026
15 checks passed
@toobuntu
toobuntu deleted the feature/p20-detection-diff branch July 20, 2026 07:22
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