Repository navigation
fix(ui): align the window controls with the title bar - #84
Merged
Merged
Conversation
The OS draws minimize/maximize/close into a region whose height is `titleBarOverlay.height`. It was 35px while the renderer's title bar was `h-11` (44px), so on Windows the controls sat above the centre of the bar they appear to belong to. Both halves now read their geometry from `src/shared/utils/windowChrome.ts`, which also carries the width of the control strip: the renderer reserved `w-32` (128px) while the three caption buttons occupy 138px, which put the settings button under the minimize button. Same pass, same surfaces: - Windows and Linux both reserve the control strip. Linux reserved nothing, so its tab strip ran under the controls. - Home page gutters: the hero used `px-6` and the grid `px-12`, so the page title and the first card did not share a left edge. - Notebook cards no longer carry a `--chart-*` colour rail. DESIGN.md forbids chart colours outside chart/graph surfaces; the card now has the hover state it was missing, composited as an overlay because swapping the opaque `surface-raised` fill for translucent `surface-hover` is invisible in dark mode. - `color-scheme` is declared per theme, so native surfaces (scrollbars, caret, selection, form controls) stop rendering in light mode inside the dark theme. - The document title was still `Electron`. - The boot screen's hardcoded "Loading..." moved to the existing `common.loading` key. - Blockquotes are 2px in both reading surfaces; they were 4px in chat and 3px in the editor. DESIGN.md records the window chrome geometry, the raised-card hover recipe, and the rule that the two halves of the title bar share one source of truth.
This was referenced Sep 24, 2026
mrsibe
added a commit
that referenced
this pull request
Sep 24, 2026
The app has four windows and each one hand-rolled its own title bar, so the
geometry under the OS window controls drifted apart:
macOS left non-macOS left right reserve
TopNavigationBar 80px 0 138px
MindMap / Quiz 64px 100px 128px, win32 only
Anki 64px 128px 128px, win32 + linux
`titleBarOverlay` is transparent, so whatever the renderer draws is what the
user sees under minimize/maximize/close. On Windows the controls sat over the
content in every window; on Linux two windows reserved nothing at all.
`windowChrome.ts` now owns all three measurements, and
`components/common/WindowTitleBar.tsx` is the only place that applies them.
`MAC_TRAFFIC_LIGHTS_WIDTH` (80px) is new: the three secondary windows reserved
64px, which is narrower than the traffic lights they sit under.
Consequences:
- Windows and Linux reserve 138px on the right in every window.
- macOS reserves 80px on the left in every window.
- The 100px / 128px "keep the title centred" left spacers are gone. The centre
slot is centred by `flex-1` instead of by a magic number that mirrored a
reserve it did not know the width of.
- The three secondary windows drop their per-page `platform` state and the IPC
call that fed it. `isMac()` reads the synchronous `userAgentData` value, so
the reserves are right on the first render instead of after an await.
- `paddingTop: '44px'` becomes `paddingTop: TITLE_BAR_HEIGHT`.
One bug introduced and fixed inside this refactor: the Anki export button lost
its `no-drag` region, which would have made it unclickable. It sets it now.
Two cleanups in the same surfaces:
- Deleted `components/I18nProvider.tsx`. Nothing imported it, it held the last
hardcoded `Loading...` left after #84, and it was the impeccable detector's
only remaining finding (a CSS-border spinner, which the rule reads as a card
with an accent border). The wider dead i18n cluster — `LanguageSelector`,
`Versions`, `lib/i18n.ts` — is reported separately rather than deleted here.
- Deleted `--secondary` / `--secondary-foreground`. DESIGN.md already recorded
them as dead, nothing rendered them, and their light value was the only
blue-tinted neutral in the palette.
DESIGN.md gains the shared-component recipe, the third constant, and a
correction: the surface table listed the title bar under `--surface-sunken`,
but all four windows paint `surface-base`. Three independent implementations
agreeing is the evidence, so the table was wrong, not the code.
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.
What does this PR do?
Fixes the title-bar / window-control geometry mismatch on Windows and Linux, makes a small pass over the theme and layout details around it, and corrects
DESIGN.mdwhere it describes something the code no longer does.Why?
The OS draws minimize / maximize / close into a region whose height is
titleBarOverlay.height. It was 35px while the renderer's title bar wash-11(44px), so on Windows the controls sat above the centre of the bar theyappear to belong to.
Fixing that exposed three neighbours in the same surface, so they are fixed here
rather than left as follow-ups:
w-32(128px) for the controls, but three captionbuttons occupy 138px — 10px short, which let the settings button slide under
the minimize button.
surface-hoveris translucent, so it cannot give an opaquesurface-raisedcard a visible hover in dark mode:
surface-hovercomposited oversurface-baselands on almost the same lightness assurface-raised. That iswhy the card hover is composited as an overlay rather than swapped in.
Related issue
Related to #26 (same
titleBarOverlaymodule; that PR fixed the symbol colour,this one fixes the geometry).
What changed?
Window chrome
src/shared/utils/windowChrome.tsholdsTITLE_BAR_HEIGHT(44px) andWINDOW_CONTROLS_WIDTH(138px). The main process and the renderer both readfrom it, so the two halves of the title bar cannot drift apart again.
titleBarOverlayOptions()now returnsheight: TITLE_BAR_HEIGHT(was aliteral 35).
TopNavigationBarreserves the control strip on every non-macOS platform, atthe real caption-button width.
test/titleBarOverlay.test.tsasserts the overlay height equals the rendererbar height and that the strip is 3 × 46px, so a regression fails a test rather
than showing up on someone's Windows machine.
Theme
theme.cssdeclarescolor-schemeper theme. Native surfaces the CSS does notpaint — scrollbars, caret, text selection, form controls,
<select>popups,autofill — were rendering in light mode inside the dark theme.
Electron.Loading...now uses the existingcommon.loadingkey and a spinner, so it is translated and not a bare string.Layout
px-6and the notebook gridpx-12, so thepage title and the first card did not share a left edge. Both now use
px-6(the page gutter in DESIGN.md), as does the empty state.
NotebookCardno longer renders a--chart-*colour rail. DESIGN.md forbidschart colours outside chart/graph surfaces, and the rail was the only place
they appeared as decoration. The card picks up the hover state it was missing.
the editor.
Documentation (
docs(design)commit)impeccable doctorreports one finding (design-md-coverage), but readingDESIGN.md against the code found several places where the document describes
something the code does not do. No rendered pixel changes.
text-xsand thecontrol
h-8;Label/FieldLabelaretext-sm font-medium,Inputand theSelecttrigger areh-9, and the density table already saidh-9— therecipe contradicted both the components and the table. Error text was
documented as
text-xs;FieldErroristext-sm.components/ui/field.tsxwas never documented, yet it is what the settingssurfaces use. The recipe described a hand-rolled stack instead.
a 44px bar), which made
TopNavigationBarread as a violation of thesize-8toolbar-icon-button rule rather than a deliberate tier.
Tabswas documented as having exactly one form. The open-notebook strip isa second form, and it expresses selection with the translucent fill rather than
a segmented control's surface step.
border-noneopt-out that fix(ui): remove the dashed border from the knowledge base empty state #58 added to panel-level empty states was notrecorded.
--secondarywas one of thosealiases. It is not: it holds its own literals, nothing renders it (
ButtonandBadgevariant="secondary"usebg-muted), and its light value is the onlyblue-tinted neutral in the palette. DESIGN.md now marks it for deletion rather
than adoption.
How was this tested?
npm run typecheck— passes.npm run lint— 0 errors (113 pre-existing warnings, unchanged).npm test— 50/50 pass, including the new geometry assertions.npm run check:design— no violations.npx electron-vite build— main, preload and renderer all build; verified inthe output that main carries
TITLE_BAR_HEIGHT = 44and the renderer carriesWINDOW_CONTROLS_WIDTH = 138used by the title-bar spacer.npx prettier --check— clean.Not verified: the actual on-screen result of the controls against the bar. That
needs a Windows (and Linux) run — I could not capture it from this environment,
and I am not going to publish screenshots of the local notebook data that a
running instance would show. The geometry is asserted by test instead; a
screenshot from a Windows build would still be worth having before merge.
Open decisions this PR does not make
Both were found while checking DESIGN.md and need a call rather than a doc edit:
surface-sunken; it renderssurface-base.TopNavigationBarcarries no background class and inheritsthe page root, so the bar is the same tone as the canvas, not a distinct chrome
band. Either the table row is wrong, or the bar should get
bg-surface-sunken— that second option is a visible change, so I left it alone.
--secondary/--secondary-foregroundare dead. Now documented as suchin DESIGN.md; deleting them from
theme.cssis a separate, mechanical change.Screenshots / recordings
No before/after captures attached (see above). Happy to add them if someone can
run a Windows build.
Checklist
npm run typecheckpasses.npm run buildpasses.Desktop / build changes