Skip to content

Replace the usage bar with a ring that has the reading inside it - #6

Closed
CaffreySun wants to merge 1 commit into
dncore:mainfrom
CaffreySun:usage-ring
Closed

CaffreySun wants to merge 1 commit into
dncore:mainfrom
CaffreySun:usage-ring

Conversation

@CaffreySun

Copy link
Copy Markdown
Contributor

What this does

The panel's Context used item was a 46×7pt bar with the percentage drawn
beside it. It is now a ring, 17pt across with a 2pt stroke, and the reading
drawn inside it.

Two reports from use, and they turn out to be one change:

  • the bar overlapped the Status message in the next column;
  • it was the widest thing in the row after the wording, in a row that has to
    hold nine items beside a pet.

The overlap was two answers to one question

The bar was drawn at the metrics' own width — contextBarSize, 46pt times the
scale — no matter what the column had been squeezed to. The column's floor, on
the other hand, was half a label's minimum: a number with nothing to do with
a bar. So on a narrower panel the column was cut to 17pt while the bar was
still drawn 46pt wide, straight under the Status message beside it, and the
percentage was given a zero-width rect and disappeared. That is the reported
screenshot exactly: a green bar with "Running" on top of it, and no number.

The item's claim is now the ring, and its floor is the ring. The drawn width
and the paid width are the same number, so the state that produced the overlap
cannot be represented at all — which is a stronger fix than choosing a better
number for each half.

Why the reading is inside the ring

Not for looks: because inside is the only place it is ever drawn.

Beside the ring, the item asks for the ring, a gap and the number — 43pt at the
baseline against a 17pt floor, 64pt at 1.5× — and the squeeze answers by giving
every non-flexible column its floor. Measured through --selftest, with all
nine items enabled and the panel width swept from 100% to 300% of the pet, the
number was on screen in none of the 11 places that row drew the ring. (The
three-item row, for contrast, had it in 9 of its 11.) Inside the ring, the
whole reading costs one ring's width, and the "ring but no number" state does
not exist.

What it costs. The ring may not be taller than the 18pt row, so with a 2pt
stroke its hole is 13pt, and the digits have to fit inside it. They are sized
for 100, the widest reading there is, because a font that shrank when the
number reached three digits would read as a bug. Measured with Core Text glyph
path bounds — the ink, not the font's line box: a digit has no descender,
so empty line height outside the hole draws nothing — 100 is 1.67pt of ink
across and 0.74pt down per point of font, a diagonal of 1.83, so the 13pt hole
holds 7.1pt: 0.68 of the panel's own 10.5pt text. baseContextRingFontRatio is
0.62, that with a margin in — the ink's corners then sit a little under a
point inside the hole.

So the number runs at about three fifths of the rest of the row: 6.5pt at a
112pt pet, 9.8pt on a 168pt one. That is the honest price of the change, and
--selftest re-measures the fit at 80, 112, 168 and 224pt so a future font
cannot quietly start drawing digits through the stroke.

The % sign is gone, and that is what buys the size: it makes the reading
half again as wide (2.64pt of ink per point of font against 1.67). A ring is
drawn only when there is a percentage, so 72 inside one cannot be read as a
token count — the item's own label, which is what --verbose logs, still
carries the sign.

Everything about it follows Pet → Size

Diameter, stroke and digits are three numbers drawn through the same
Metrics.scale as the rest of the row, so Pet → Size doubles all three
together: 17pt/2pt/6.5pt at a 112pt pet, 34pt/4pt/13pt at 224pt. No part of the
badge is a constant of the panel's own.

Looks unrelated, but is this PR's business

  • Four present-tense "bar" mentions renamed to "ring" (SettingsView's two
    help texts, README.md, and the comment on items(for:)). They describe this
    same item; leaving them would have the UI call one thing two names. The dated
    history in ARCHITECTURE.md and the old-bar references inside sentences that
    explain what was replaced are deliberately kept.
  • ARCHITECTURE.md §6.5 gains the root cause above, the badge's three
    numbers, and the measured cost of the reading.
  • The digits' ratio changed during review, 0.55 → 0.62. The first figure
    came from reasoning about the font's line box; the ink allows 0.68, so 0.55
    was leaving a fifth of the available size on the table. Both the code comment
    and ARCHITECTURE.md now state the measured numbers instead.

Self-test coverage

--selftest pins the badge: across the width sweep, every ring that is drawn
has the reading inside it and stays inside its column; 100 fits its hole at
80, 112, 168 and 224pt; diameter, stroke and digits each double with the pet;
and — new here — at an 80pt pet a session reporting tokens without a window
(a plain token count, no ring) and one reporting a percentage share the context
column, and both are drawn. That last one is a regression test: the token cell
used to keep the unscaled 17pt floor while its own claim could be smaller
(999 is 15pt at an 80pt pet), a column takes the widest floor of its cells,
so the whole column was dropped and it took the badges in sibling rows with it.
Its assertion is on the column (Column.isDrawn), not per cell — a per-cell
form cannot fire, because a dropped column produces no cells to complain about.

Three negative controls, each run and reverted:

control what it prints
the bar's fixed width in place of the ring a 65pt ring in a 24pt column at 100%
the digits' ratio raised past the hole (0.75) "72" needs 19pt of hole in a 24pt ring
the token cell's floor put back above its claim the context column was dropped: width 15pt against a floor of 17pt

The ink rule itself is checked twice over: the same badgeInkFault is asked
with the nominal diameter and with the ring the layout actually handed out,
over every (configuration, width) pair the sweep covers.

What this does not do

  • No new items, no config keys, no change to the width settings or to any other
    item's geometry. The bar's colour and every other number in the row are
    untouched.
  • The ring's unfilled track keeps separatorColor at 0.6 alpha, the same as the
    bar's track: the shape changed, the palette did not.
  • The number is not clickable, hoverable or a tooltip. The panel is a drawing.

Verified

  • swift build --build-tests, swift test --parallel: 579 tests in 88
    suites
    .
  • The rest of the CI gate set run locally: the shim contract (both with and
    without a listening runtime), the bundle and plutil -lint,
    --diagnose/--status/--help exiting 0, and the leak scan.
  • --selftest passes on the assembled bundle and on the copy installed into
    /Applications, and reproduces the ring's geometry at both ends of the pet
    size range.
  • The change went through a multi-round adversarial review (partitioned
    reviewers, independent verification of every finding, fixes re-reviewed until
    a round came back with nothing) — the negative controls above and the
    regression test are its product, as is the 0.62 ratio.

One report and one request from use: the bar overlapped the status message
beside it, and it was the widest thing in the row after the wording.

**The overlap was two answers to one question.** The bar was drawn at the
metrics' own width no matter what the column had been squeezed to, and the
column's floor was half a *label's* minimum — a number with nothing to do with
a bar. So on a narrow panel the column was cut to 17pt and the bar was still
drawn 46pt wide, straight under the wording in the next column, while the
percentage was given a zero-width rect and vanished (the report's screenshot).

**The item is now a badge.** A 17pt ring with a 2pt stroke, inside the 18pt
row, and the reading drawn in the hole. The item's claim is the ring and its
floor is the ring: the drawn width and the paid width are the same number, so
the state that produced the overlap cannot be represented. The number goes
inside because inside is the only place it is ever *drawn* — beside the ring
the item asks for 43pt against a 17pt floor, and with all nine items enabled
the sweep from 100% to 300% of the pet put the number on screen in none of the
11 places that row drew the ring.

It costs size: the digits have to fit the 13pt hole and are sized for "100",
the widest reading, so they run at 0.62 of the panel's own text — measured
against the ink (glyph path bounds), not the font's line box, which is 1.83
times the point size across its diagonal and would have paid for only 0.55.

Everything about the badge follows Pet → Size: diameter, stroke and digits are
all drawn through `Metrics.scale`, so they double together at 224pt.

**Verified.** `--selftest` pins it: 22 rings across the width sweep, all 22
with the reading inside; "100" fits its hole at 80, 112, 168 and 224pt; the
diameter, the stroke and the digits each double with the pet; and at an 80pt
pet a token count and a badge share the context column and both are drawn.
Three negative controls fail as they should — the bar's fixed width in place of
the ring, the digits' ratio raised past the hole, and the token cell's floor
put back above its claim. `swift test --parallel`: 579 tests, 88 suites.

Docs: ARCHITECTURE.md §6.5 gets the root cause, the badge's three numbers and
the measured cost of the reading.
@dncore

dncore commented Sep 21, 2026 •

Copy link
Copy Markdown
Owner

Merged as e5479cf — rebased onto main rather than a merge commit, since this history has none; your commit and its message are unchanged. (The SHA moved from 69e5c30 after the repository's history was rewritten to take a private address out of the commit metadata; the content of every commit is byte-identical — git ls-tree before and after matches.)

Verified on the merged tree: swift test --parallel 579 tests in 88 suites, --selftest green including the five badge checks, and both negative controls reproduced here — the ring drawn at the bar's old fixed width reports a 46pt ring in a 17pt column at 100%, and the ratio raised to 0.75 reports "100" needs 14.2pt of hole in 13.0pt. The overlap this fixes is visible in the app's own default render: on main, the green bar is drawn under the $… and 5… columns.

Thanks — the measurement notes, the "one number is both the claim and the floor" shape of the fix, and the evidence in the description made this easy to accept.

@dncore dncore closed this Sep 21, 2026
@CaffreySun
CaffreySun deleted the usage-ring branch September 21, 2026 01:45
@CaffreySun

Copy link
Copy Markdown
Contributor Author

Landed as 69e5c30 — thanks.

Checked on my side that the ported patch is the reviewed one, line for line: the
changed-line sets of 3c47e97 and 69e5c30 hash identically, and nothing since
has touched the eight files (only ARCHITECTURE.md, by 36b9bb6). Rebased onto
fcd263a/4a958a3 is the visible tree difference; the badge's own hunks are
verbatim.

Branch deleted, fork's main fast-forwarded to 36b9bb6.

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.

2 participants