v1.5: running-CI badge, display-tier framework, and GitHub quota gauges - #9
Merged
Conversation
New feature: ci_status shows an actively-running CI job, intended to alternate with the calendar. Two empirical probes (required before any implementation, per the task brief) found the firmware diverges from its own OpenAPI documentation in ways that reshape the whole design: 1. Equal priority from a different application_name is REJECTED (409 "low priority"), not an "override" as the docs claim. Only a strictly-greater priority succeeds against the currently-showing app. The running badge therefore uses priority 21, not the 20 the brief specified -- otherwise every draw would 409 whenever the calendar (also priority 20) happens to be showing. 2. Occluded elements are EVICTED, not restored: probed both occluder-timeout-expiry and occluder-explicit-clear, and in both cases a lower-priority app's still-live element did not reappear -- the panel went black instead. This rules out "clean alternation, zero coordination" and forces the documented fallback: badge with a fixed ~10s timeout drawn once per poll cycle; the calendar only returns if its own independent 60s redraw happens to land in the gap. Feature implementation on top of the corrected model: - ci_status/github.py: fetch_running_runs (separate ETag slot from the existing failure/stuck poll -- different URL/query) and fetch_median_eta (cached per workflow_id for the process's lifetime; a successful fetch caches even a confirmed-empty history, but a network/HTTP error does not, so it retries next time rather than permanently locking in "no history"). - ci_status/logic.py: select_running_run (most-recently-started across repos, +N for others active), compute_median_duration_minutes, ETA text (~4m / ~1h05m reusing calendar_countdown._format_countdown verbatim, "soon" once floored under a minute, "3m in" with no history), progress-bar track repurposed as elapsed/median, and build_ci_payload extended with strict failure > stuck > running > quiet-green precedence via a new RunningInfo dataclass so the render/precedence logic stays pure and testable without network mocking. - ci_status/main.py: run_once gains an optional running_cache param (mirrors state_cache's existing mutate-in-place pattern, gated on show_running so old callers/configs stay backward compatible); next_poll_seconds is a small pure function deciding running_poll_seconds vs poll_seconds, kept separate from run_once's return value so it's unit-testable without mocking time.sleep. - Config: show_running (default true), running_poll_seconds (default 20) added to DEFAULTS and config.example.toml. 76 tests on main before this branch (verified by checking main out directly), 124 after: +48 covering running-run selection, PR/branch fallback, ETA math (median, no-history, soon threshold), payload precedence, and the cadence switch.
New spec section leads with the two probe findings (equal-priority rejection, occlusion eviction) since they invalidated the brief's assumed "clean alternation" design before any code was written, plus the feature description, palette/geometry, cadence/precedence, and config additions. ci_status README gets a full "Running-Job Badge" section spelling out the honest on-device-observed rhythm (badge visible ~50% of the time, calendar not observed to recover during a ~130s/6-cycle test window) rather than describing it as a clean alternation. calendar_countdown README cross-references the same caveat so the interaction is documented from both sides.
Generalize the priority/dwell mechanics the v1.5 probes uncovered into a reusable framework (src/busybar/display.py): a strictly-increasing priority ladder (AMBIENT=20, OVERLAY=21, ALERT=60, SESSION=90 reference) plus the two contract helpers each tier actually needs (ambient_timeout, overlay_gap_elapsed), so future integrations adopt the same pattern instead of re-deriving it from scratch. calendar_countdown now imports PRIORITY_AMBIENT/ambient_timeout from the shared module instead of a local constant, and its default poll_seconds moves 60 -> 10 after on-device re-measurement (60s: 0/6 dwell cycles recovered; 15s: 2/6; 10s: 4/6 -- see the spec doc and reports for the full tables). Also adds should_log_info() so the faster 10s cadence doesn't multiply calendar.log's line rate for unchanged-state polls (INFO only on summary change or a 10-minute heartbeat, DEBUG otherwise). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sion)
Rebuild the running-CI badge on top of the new busybar.display framework
(PRIORITY_OVERLAY/PRIORITY_ALERT imported, not hardcoded; local geometry
constants renamed RUNNING_* -> OVERLAY_* with back-compat aliases since
they're now shared with the quota frames too) and add a dwell-gate +
round-robin state machine in run_once: the overlay tier now rotates
through up to three frames per active run -- the running badge, then
(if show_quota) a GitHub GraphQL quota frame, then a REST quota frame --
one frame per OVERLAY_DWELL_SECONDS dwell slot, staying silent for a
full gap before redrawing so the ambient-tier calendar gets a fair shot
at reclaiming the screen.
Quota frames (integrations/ci_status/logic.py, github.py):
- RestPoller.fetch_rate_limit(): GET /rate_limit, explicitly exempt from
GitHub's own rate limiting, fetched fresh once per running-poll cycle
(no ETag caching attempted -- there's no quota cost to save and the
values change continuously).
- parse_rate_limit() extracts the core/graphql buckets; a 5-minute
staleness window in main.py (_refresh_quota) means a single failed
fetch doesn't blank the frame but sustained failures eventually do --
build_overlay_payload returns None for unavailable data and the
caller skips that dwell slot entirely (no draw, no clear, no stale
numbers).
- Frame layout: title ribbon ("GITHUB GRAPHQL"/"GITHUB REST", scrolls
if it doesn't fit the ribbon width), a track bar for fraction used
(clamped [1, PANEL_WIDTH], verified against a real account where
GitHub's point-based GraphQL "used" exceeded "limit"), and two large
numerals sharing the bottom row (percent remaining, reset-in).
Background/track/title/numeral colors all key off remaining-quota
headroom (high >50%, medium 20-50%, low <20%).
- OVERLAY_FRAME_SHAPE distinguishes the badge's element-id set from the
quota frames' (same upsert-by-id firmware behavior the v1.3.1
calendar transition-clear fix addressed); main.py clears only on an
actual shape change, never between quota_gql <-> quota_rest.
- overlay_state mutations (frame_index, last_dwell_end, last_shape)
only commit after client.draw() returns DRAWN, never optimistically.
show_quota=true is the new config default (config.py/config.example.toml
land with the framework commit); has no effect when show_running=false.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…vision) Spec doc: new "Framework generalization, tuned alternation, and quota frames" subsection under the existing v1.5 section -- the priority-ladder table and join-recipes for future integrations, the three-round ambient poll_seconds tuning table (60s/15s/10s, 0/2/4 of 6 dwell cycles recovered), and the quota-frame design (rate_limit-is-free fact, frame layout, headroom color table with exact hexes, round-robin/shape-change mechanics, on-device verification results including the real-account GraphQL used>limit data quirk). calendar_countdown README: updated priority language (PRIORITY_AMBIENT, not a literal "priority 20"), the poll_seconds default and its tuning rationale, and the outdated "roughly half" alternation claim replaced with the measured three-round table. ci_status README: split the old single "Running-Job Badge" section into "Display Priority Tiers" (shared framework, firmware facts, precedence) and "Overlay Rotation: Running Badge + Quota Frames" (frame content, show_quota config, headroom theming table, measured rotation rhythm including the full 3-frame on-device sequence). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Critical fix: overlay_state["last_shape"] is now a unified tracker of whatever payload shape (element-id set) was last actually drawn to APP, checked and committed by every tier that can draw there -- an alert badge, quiet-green text, or either overlay frame kind -- not just the two overlay-frame shapes as before. The prior code only compared shapes inside the overlay branch and reset the whole overlay_state dict (wiping last_shape along with the rotation bookkeeping) whenever an alert preempted the overlay or a run ended. That meant an alert resolving back into an active-run poll saw "no shape on record" and skipped the clear it actually needed, leaving the alert's `ci` text element (timeout up to 1.5x poll_seconds, e.g. 180s at the default) rendered on top of the running badge/quota frames for minutes -- the v1.3.1 upsert-by-id bug class at a seam OVERLAY_FRAME_SHAPE never covered. Fix: compute shape generically as frozenset(e["id"] for e in payload["elements"]) once per draw, gate on it right before the single client.draw() call regardless of which precedence tier produced the payload, and split the alert/no-run branch's state reset so it only pops frame_index/last_dwell_end (rotation bookkeeping) -- last_shape is left untouched unless a clear actually happens this same poll (the "nothing to show" branch) or a draw actually lands (DRAWN-gated commit, same discipline as before). Important fix: a skipped overlay dwell slot (quota data unavailable) previously fell through into build_ci_payload(overlay=None), which -- with no alert and show_green off -- returned None and triggered an unwanted client.clear(APP), evicting the still-valid, still-in-dwell previous frame. run_once now returns early on a skipped slot before build_ci_payload is even called, matching build_overlay_payload's own "no draw, no clear" docstring contract; tests assert both draw and clear call counts stay unchanged across a skip. Minors: should_log_info's docstring and test_calendar_loop.py's header comment still cited the interim 15s/"quadruple" numbers from the first tuning round instead of the shipped 10s/"sixfold" (matching the README, which was already correct); added the missing _quota_used_width regression test for the live-observed used>limit case. New tests: test_overlay_then_alert_clears_stale_overlay_shape, test_alert_then_overlay_clears_stale_alert_shape, test_quiet_green_then_overlay_clears_stale_green_shape (all assert clear() fires exactly once at the transition, not zero/twice), updated skip-slot tests to assert clear.call_count == 0, updated test_overlay_state_resets_when_run_ends for the corrected last_shape semantics, added test_quota_used_width_clamped_max_when_used_exceeds_limit. TZ=UTC uv run pytest -q: 174 passed (170 before this fix). On-device: real run_once driven against the device (application_name monkey-patched to "preview") through a synthetic failure -> resolved -> running-badge transition. Alert frame captured with red bg + white "CI FAIL o/r:tests" text (956 matching pixels). Post-transition frame: zero stale red-bg or white-text pixels, 244 badge-bg pixels confirming the new shape actually drew -- clean transition, no stale banner ink. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds live CI-run visibility with a reusable display-tier framework:
REPO #PR WORKFLOWribbon (+N for concurrent), ETA from median of the last 5 same-workflow durations, progress track; 20s cadence while active.Tests: 174 (was 76 at branch base). Rotation measured live: badge→gql→rest at exact 20s cycle; calendar reclaims 3/4 gap windows.
🤖 Generated with Claude Code