Skip to content

fix(notch): keep a full reserved band on a display with no camera housing - #548

Merged
Tlahey merged 1 commit into
mainfrom
claude/notifications-cropped-non-macbook-109311
Sep 7, 2026
Merged

Tlahey merged 1 commit into
mainfrom
claude/notifications-cropped-non-macbook-109311

Conversation

@Tlahey

@Tlahey Tlahey commented Sep 7, 2026

Copy link
Copy Markdown
Owner

What changed

On a display with no camera housing β€” every external monitor, and so every setup whose main display is one β€” the notch card's close button was rendered sliced through its own vertical middle, along with the "GIT MANAGER" label beside it. It now renders whole, exactly as it does on a MacBook's own screen.

NOTCH_BAND_HEIGHT becomes a floor rather than merely a default, via a new resolveBandHeight. The per-machine safe-area measurement can still make the reserved band taller; it can no longer make it shorter than the content the band itself holds.

Why

The reserved band is not only a hole for the camera housing: it is also the row that holds the product name and the βœ• (NotchNotification's bandStart/bandEnd). Its height comes from NSScreen.safeAreaInsets.top via get_notch_metrics, and a display with no housing reports that as a real 0 β€” not null β€” so the frontend's own "nothing to go on" fallback never fires and bandHeight: 0 travels to the card in its URL.

Laying 0 out does not reserve less space, it collapses the row to its 1pt hairline. The 20pt close button then overflows a 1pt flex row items-center, extending ~9.5pt above the card's top edge, where the shell's overflow-hidden cuts it in half. That is precisely the reported symptom.

It is a regression of reading per-machine metrics: before that, the hard-coded 32 was used everywhere and the notchless case rendered correctly β€” which is also why the clamp restores known-good behaviour rather than inventing one.

Notes for the reviewer

  • Why a floor and not a ?? fallback at the source. Making notchWindow.ts send undefined when the metric is 0 would fix the symptom but lose the distinction between "measured, and it is zero" and "could not measure" β€” and it would leave NotchCard still able to render a 0 any other caller passed. The floor lives in the geometry, which is the layer both the measurement and the render already go through.
  • One clamp point, deliberately. The payload still carries the raw measurement; the geometry decides the layout. resolveBandHeight is applied at the only three places that consume bandHeight β€” notchRowHeights (which sizes the OS window), rewardConfettiOrigin, and NotchCard's own render β€” so the window height and the content that fills it cannot drift apart, which is the failure mode this whole module is built to avoid.
  • The rationale is in the module doc comment, not in a docs/ file, per the repo convention for invariants someone could plausibly break later.
  • Adjacent, deliberately left out: notchMetrics.ts caches the metrics for the life of the process, reasoning that "a display cannot grow or lose its camera housing without the machine restarting" β€” docking and changing the main display does exactly that at runtime. With the floor in place the dangerous direction is neutralised (0 β†’ 32); what remains is a cosmetic few points on a machine reserving more than 32pt. Worth its own change, not this one.
  • Not verified visually. This is a native Tauri window, so it cannot be rendered here; confirming it on screen needs pnpm dev with an external display set as the main one. The bandHeight={0} render test pins the exact geometry that was broken.

Verification

  • pnpm typecheck
  • pnpm lint (75 warnings in @git-manager/desktop, all pre-existing; 0 errors)
  • pnpm --filter @git-manager/desktop test β€” 762 files, 8313 tests, all passing
  • pnpm --filter @git-manager/notch test β€” 211 tests, all passing
  • Rust β€” no Rust touched by this change
  • cargo test β€” no Rust module touched
  • Ran it in the app β€” no: see the last note above. The change is only observable on a second physical display with the app's own notification window up, which is not reachable from here.

If this PR touches…

  • A new or changed source file β€” resolveBandHeight is covered in notchGeometry.test.ts, and the render it fixes is pinned by a new bandHeight={0} case in NotchCard.test.tsx
  • An invariant someone could plausibly break later β€” recorded in resolveBandHeight's doc comment, beside the code that enforces it

…sing

The card's reserved band is not only a hole for the camera housing β€” it is
also the row that holds the product name and the close button. Its height
comes from `NSScreen.safeAreaInsets.top`, and a display with no housing
reports that as a real `0` rather than as "no answer", so the frontend's own
fallback never fires and `bandHeight: 0` travels to the card.

Laying that out collapses the row to its 1pt hairline: the 20pt close button
overflows it `items-center` and the shell's `overflow-hidden` slices it
through its own middle. On every setup whose main display is an external
monitor, the βœ• was cut in half.

A regression of reading per-machine metrics β€” before that the constant 32 was
used everywhere and the notchless case rendered correctly.

`resolveBandHeight` makes `NOTCH_BAND_HEIGHT` a floor rather than merely a
default, so the measured inset can only ever make the band taller. Applied at
the three places that consume `bandHeight` β€” `notchRowHeights`,
`rewardConfettiOrigin` and `NotchCard`'s render β€” so the measurement that
sizes the OS window and the layout that fills it cannot drift apart. The
payload still carries the raw measurement; the geometry decides the layout,
which keeps the clamp in one place.
@Tlahey
Tlahey merged commit 0950aba into main Sep 7, 2026
3 checks passed
@Tlahey
Tlahey deleted the claude/notifications-cropped-non-macbook-109311 branch September 7, 2026 13:00
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