Skip to content

feat(menubar): optional second row in the macOS menu bar item - #1290

Open
ozymandiashh wants to merge 2 commits into
getagentseal:mainfrom
ozymandiashh:feat/1252-menubar-second-row
Open

feat(menubar): optional second row in the macOS menu bar item#1290
ozymandiashh wants to merge 2 commits into
getagentseal:mainfrom
ozymandiashh:feat/1252-menubar-second-row

Conversation

@ozymandiashh

Copy link
Copy Markdown
Collaborator

Summary

  • Adds an optional second line to the macOS menu bar status item, off by default, configured from
    the existing Settings → General → Display section: a "Second row" toggle plus a picker for what it
    shows — quota remaining with its reset countdown for whichever connected provider is nearest its
    limit, today's all-provider cost, today's total tokens, or running sessions. Every metric is one
    the app already computes; nothing here adds a fetch or a refresh cadence.
  • The two lines render as one attributed title at 9pt with both line heights clamped to 10pt and the
    inline flame reduced to an 8pt symbol, which measures 20pt inside the standard 22pt menu bar. With
    the setting off, or whenever the chosen metric has no data yet, the title falls back to the
    existing single-row composition unchanged. Settings persist as CodeBurnMenubarSecondRowEnabled
    and CodeBurnMenubarSecondRowMetric alongside the other menubar defaults keys.
  • This is a deliberately reduced slice of Feature request: configurable multi-row macOS menu bar layout #1252. Not included: the layout editor, presets, live
    preview, three rows, multiple items or aligned columns per row, per-item provider/period/scope,
    optional labels, decimal precision, reordering and text size. The row formatting is a pure type
    (MenubarRowFormatter over MenubarRowSettings + MenubarRowSnapshot) so that surface can be
    layered on later without touching the rendering path.

Testing

  • I have tested this locally against real data (not just unit tests)
  • npm test passes
  • npm run build succeeds

mac/ change, so no TypeScript was touched and the CLI suites are not affected.

  • cd mac && swift build → Build complete.
  • New suite mac/Tests/CodeBurnMenubarTests/MenubarSecondRowTests.swift: 19 tests covering each
    metric, the off state, the no-data fallback to one line, the reset-countdown shape against an
    injected clock, the nearest-limit provider selection and its tie-break, UserDefaults round-trip
    including an unknown stored metric, and the two-row geometry — including one test that composes
    the real attributed title (flame attachment plus the paragraph clamp) for 16 row combinations and
    asserts each measures 20pt, inside the 22pt menu bar.
  • swift test could not run on the machine this was written on: it has the Command-Line-Tools
    toolchain only, which ships no swift-testing module, so every existing test file in the target
    fails with no such module 'Testing'. To avoid shipping unexecuted assertions, all of the new
    suite's expectations were re-run as a standalone swiftc binary (52/52 pass), and the height
    assertions were re-run the same way against AppKit (all 16 combinations measure 20.0pt). CI runs
    the real suite.
  • Not verified: the rendered look on a real menu bar (no GUI session available), menu bars whose
    thickness is not 22pt, and behaviour when macOS runs out of menu bar space.

The menu bar item could show one figure, so seeing subscription quota and
today's local usage at the same time meant opening the popover. This adds an
optional second line, off by default, configured from the existing
Settings > General > Display section: a "Second row" toggle plus a picker for
what it shows. All four metrics are figures the app already computes for the
popover, so nothing here adds a fetch or a refresh cadence: quota remaining
with its reset countdown for whichever connected provider is nearest its limit
(the same provider the flame already tints for), today's all-provider cost,
today's total tokens, or running sessions.

The rows are one attributed title. A paragraph style clamps text line height
but not an attachment, so the inline flame at the text's own point size dragged
the title to 24pt and AppKit clipped the second line away; the flame is
requested at 8pt and seated back inside the line, which puts every row
combination at exactly 20pt inside the standard 22pt menu bar, centred with 1pt
of slack above and below. Measured with NSAttributedString.boundingRect and
asserted in the tests, since those four numbers only work together.

With the setting off, or whenever the chosen metric has no data yet (no
connected provider with a quota window, no today payload, a CLI that predates
the liveSessions block), the title falls back to the previous single-row
composition unchanged: same font, same attributes, no paragraph style. The row
formatting is a pure type over the row settings and a plain-value payload
snapshot, so it is covered without an AppStore or a status item.

This is a deliberately reduced slice of the request. Not included: the layout
editor, presets, live preview, a third row, multiple items or aligned columns
per row, per-item provider/period/scope, optional labels, decimal precision,
reordering and text size. Keeping the formatter pure leaves room for those
later without touching the rendering path.

Closes getagentseal#1252
…gure

CI's swift test on macos-latest measured the two-row title at 21pt where this
machine measures 20pt, so the exact-equality assertion failed 16 times. The
figure is release-dependent: SF Symbol metrics and line rounding move between
macOS versions. Nothing positions the rows off it — AppKit's button cell
centres whatever it measures inside the status item — so 21pt is centred just
as correctly as 20pt, and the requirement was always "fits a 22pt menu bar",
not "measures exactly 20pt". The test now asserts that band, and
twoRowMeasuredHeight is replaced by twoRowMaximumHeight (the bar) and
twoRowMinimumHeight (a floor, so a lost paragraph clamp or a dropped second
line is still caught rather than passing as "fits").

The 1pt delta does expose a real risk the constant was hiding: the first line's
height follows the flame image, and the image size for a point size is the
system's business. A release that renders it taller could push the title past
the bar. The two-row attachment box is now computed by
twoRowAttachmentBounds(imageSize:), which scales the image down to the clamped
line height — never up — so the first line cannot outgrow its clamp. Verified
by composing the real title with the flame forced to 11, 13, 16 and 24pt tall:
every combination still lays out at 20pt here. The single-row path keeps the
raw image size and its -3pt offset, so it stays byte-identical.
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