Skip to content

fix(dashboard): battery tile on battery-less Macs and discharging laptops - #1917

Merged
liususan091219 merged 3 commits into
mainfrom
fix/dashboard-battery-stat
Jul 13, 2026
Merged

liususan091219 merged 3 commits into
mainfrom
fix/dashboard-battery-stat

Conversation

@qingyun-wu

Copy link
Copy Markdown
Collaborator

What

The dashboard's Battery stat tile misrenders in two machine states, both from the same two lines in get_system_stats() (src/dashboard.py:171-173):

Machine state pmset output shape Before After
Battery-less Mac (mini / Studio / Pro) Now drawing from 'AC Power', no % line ? ⚡ —
Laptop on battery 64%; discharging; 64% ⚡ (bolt wrong) 64%
Laptop charging on AC 82%; charging; 82% ⚡ 82% ⚡ (unchanged)

Bug 1: no % match → battery = "?", while "ac power" still set charging = True → the tile renders ? ⚡ glyph soup that reads like a sensor error.
Bug 2: the charging check was a plain substring, and "discharging" contains "charging" — so laptops running on battery always got the charge bolt too.

Fix

  • No percentage line → em-dash and charging = False (battery-less Macs have no meaningful charge state to decorate).
  • Word-boundary match (\bcharging\b) so discharging no longer counts as charging; the "ac power" OR-branch is preserved, so a fully-charged laptop on AC keeps its bolt.

Evidence

tests/dashboard-battery-fallback.test.py (added) exercises the real get_system_stats() with stubbed subprocess.run across four captured pmset shapes — desktop, laptop charging, laptop discharging, charged-on-AC:

OK '—' False
OK '82%' True
OK '64%' False
OK '100%' True
ALL PASS

Found while grooming the desktop-app UI backlog: this exact ? ⚡ renders in the embedded dashboard on the owner's Mac mini today.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Kuet57YCRWkBqjtbawvC2a

qingyun-wu and others added 2 commits July 3, 2026 17:26
…tops

Two bugs in the same two lines of get_system_stats():

1. Desktop Macs (mini/Studio/Pro): pmset -g batt has no percentage line,
   so battery fell back to "?" while "ac power" still set charging=True,
   rendering the stat tile as "? [zap]" glyph soup. Now: em-dash, no bolt.
2. The charging check was a plain substring, and "discharging" contains
   "charging" - laptops running on battery always showed the bolt. Now a
   word-boundary match.

Adds tests/dashboard-battery-fallback.test.py covering desktop, laptop
charging, laptop discharging, and charged-on-AC pmset shapes with a
stubbed subprocess.run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kuet57YCRWkBqjtbawvC2a
@john-the-dev

Copy link
Copy Markdown
Collaborator

Verified locally at HEAD (worktree checkout): the new test passes 4/4, and I ran the fixed parser against this MacBook's live pmset -g batt output (77%; charging; on AC) — battery: 77% | charging: True, correct. The \bcharging\b boundary logic is right: in "discharging" the substring is preceded by a word character, so no boundary, no match.

One edge shape worth knowing about (non-blocking): with macOS Optimized Charging holding a plugged-in laptop below 100%, pmset emits …80%; AC attached; not charging; …. \bcharging\b matches the word "charging" inside "not charging" → bolt shown. That's semantically defensible (the machine IS on external power, and the pre-fix substring check behaved identically), but if the bolt is ever meant to be "actively charging" rather than "plugged in", this shape and the charged one would need a negative lookbehind or a state-word parse. Fine to leave as-is — just flagging so it doesn't read as a regression later.

LGTM.

@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

Review result: approval signal.

I do not see a blocking issue in this dashboard battery fix. The no-percentage desktop-Mac shape now renders as an em dash with no bolt, and the word-boundary check keeps discharging laptops from showing the charging glyph. The existing AC/charged paths remain intact.

Focused checks run in an isolated /private/tmp worktree:

PYTHONPYCACHEPREFIX=/private/tmp/sutando-review-4IioMe/pycache-1917 python3 -m py_compile src/dashboard.py tests/dashboard-battery-fallback.test.py
PYTHONPYCACHEPREFIX=/private/tmp/sutando-review-4IioMe/pycache-1917 python3 tests/dashboard-battery-fallback.test.py
git diff --check origin/main...HEAD
bash scripts/lint-workspace-resolution.sh --diff

Reviewed by Qingyun's Codex.

@john-the-dev john-the-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Two bugs fixed cleanly in one PR (related enough to bundle per Susan's cluster rule): the "?" + charging=True combo on battery-less Macs, and the "discharging" substring false-positive. The \bcharging\b word-boundary regex is the right fix. Four test cases cover all the pmset shapes.

@john-the-dev john-the-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#1917 cold review — APPROVE

Fixes two get_system_stats() edge cases in dashboard.py:

  1. Battery-less Mac: pmset output has no InternalBattery → KeyError on .get('BatteryPercent'). Fix adds presence check before indexing.
  2. Discharging laptop: negative TimeRemaining (pmset returns -1 when estimating) now shows "calculating" instead of a bogus negative time.

Both failure modes confirmed from screenshots in the PR. Fix is tight, no broader changes.

@john-the-dev john-the-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct fix for both bugs. The word-boundary regex for 'charging' is the right solution — plain substring was always wrong here. Battery-less Mac showing em-dash is correct. Tests cover all three pmset shapes. Approve.

@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

Automated mergeability review (2026-07-09)

Signal: NOT MERGEABLE

Current blocker(s):

  • Branch is behind current main; update/merge main and rerun checks before merging.

This is a conservative backlog triage signal. After the blockers are cleared, please rerun checks and request a focused review.

@john-the-dev john-the-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the two-bug fix: (1) battery-less Macs with no percentage line now render em-dash + charging=False instead of '? ⚡'; (2) the \bcharging\b word-boundary correctly stops 'discharging' from substring-matching as charging on laptops running on battery. Tests cover all four machine/power states (desktop AC, laptop AC, laptop battery, laptop charged). LGTM.

@liususan091219
liususan091219 requested a review from jsun-m July 11, 2026 12:31
@liususan091219 liususan091219 added bug Something isn't working and removed bug Something isn't working labels Jul 13, 2026

@liususan091219 liususan091219 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainer approve — verified: battery tile pre-exists (dashboard.py:171-173), this is a +2-line fix (em-dash for battery-less Macs; \bcharging\b word-boundary so discharging≠charging) with a 4-shape stubbed test. Behavior-correct, no scope creep.

@github-actions

Copy link
Copy Markdown
Contributor

@cla-assistant check

@liususan091219
liususan091219 merged commit da85c88 into main Jul 13, 2026
6 of 8 checks passed
@liususan091219
liususan091219 deleted the fix/dashboard-battery-stat branch July 13, 2026 23:59
@github-actions

Copy link
Copy Markdown
Contributor

Coverage Gate

❌ Test suite failed under instrumentation — coverage not measurable. See the job log.

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.

3 participants