From bd040538fbbf2a44e527ffcf9787d956435684af Mon Sep 17 00:00:00 2001 From: Charles Pan <30245070+charleslpan@users.noreply.github.com> Date: Sun, 18 Oct 2026 21:38:41 +0000 Subject: [PATCH 1/2] refactor(ui): size each shared component's glyphs itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The desktop's glyph defaults (.settings-icon, .icon-button-glyph, .settings-chevron, .credential-check, and the state glyph's box) were unlayered, so they beat every shared component's own glyph rule and the app had to exclude shared internals with :where(:not(.luke-…)). They now sit in the desktop's base layer, under the components layer, so a component's slot sizes its glyph by its own contract and the defaults reach only the window's own markup. - Delete the :not(.luke-…) exclusions, the dead .link-icon rule, and the app's .luke-menu-item-glyph and .luke-hover-card-* glyph rules. - Menu glyph slots size a picture as they size a glyph. - Hover-card glyph slots size whatever they hold, a face excepted. - Menu and picker end glyphs wear the end's ink. Co-Authored-By: Claude Opus 5.5 (1M context) --- apps/desktop/src/renderer/styles/base.css | 21 +++--- apps/desktop/src/renderer/styles/desktop.css | 42 +++--------- apps/desktop/src/renderer/styles/settings.css | 66 +++++++++---------- .../ui/src/components/combobox/combobox.css | 3 + .../src/components/hover-card/hover-card.css | 7 +- packages/ui/src/components/menu/menu.css | 10 ++- 6 files changed, 69 insertions(+), 80 deletions(-) diff --git a/apps/desktop/src/renderer/styles/base.css b/apps/desktop/src/renderer/styles/base.css index ecf467b713..66420b7ea0 100644 --- a/apps/desktop/src/renderer/styles/base.css +++ b/apps/desktop/src/renderer/styles/base.css @@ -736,15 +736,18 @@ body, the working mark stands in the glyph's place, centred in the glyph's own footprint, so a tab or a row moves nothing as its work starts or ends. Failed, the glyph stands as at rest, and the failed mark at its row's end - says it (`.failed-mark`). */ -.state-glyph { - position: relative; - display: inline-flex; - width: var(--state-glyph-size, 16px); - height: var(--state-glyph-size, 16px); - flex: 0 0 auto; - align-items: center; - justify-content: center; + says it (`.failed-mark`). In the `base` layer, so a shared component's + glyph slot that holds one sizes it as it sizes any glyph of its own. */ +@layer base { + .state-glyph { + position: relative; + display: inline-flex; + width: var(--state-glyph-size, 16px); + height: var(--state-glyph-size, 16px); + flex: 0 0 auto; + align-items: center; + justify-content: center; + } } .state-glyph > svg, diff --git a/apps/desktop/src/renderer/styles/desktop.css b/apps/desktop/src/renderer/styles/desktop.css index de89ecc4cf..188effad75 100644 --- a/apps/desktop/src/renderer/styles/desktop.css +++ b/apps/desktop/src/renderer/styles/desktop.css @@ -1802,11 +1802,14 @@ ul.actor-stack { /* Buttons --------------------------------------------------------------------- */ /* A glyph of `@luke/ui`'s own drawn outside a button, at the control's - glyph size. */ -.icon-button-glyph { - width: var(--luke-control-icon); - height: var(--luke-control-icon); - flex: 0 0 auto; + glyph size; in the `base` layer, so a shared component holding one sizes + it as its own. */ +@layer base { + .icon-button-glyph { + width: var(--luke-control-icon); + height: var(--luke-control-icon); + flex: 0 0 auto; + } } /* The file tree's Retry, beside the words saying why it is empty @@ -1915,10 +1918,8 @@ ul.actor-stack { /* The window's own menu is `@luke/ui`'s `Menu` (`action-menu.tsx`), its looks drawn by `@luke/ui/styles.css`. What stays here is the window's own: the layer it stands on over everything, or over a modal it was opened - from; its presses claimed back from the toolbar's drag strip it sits - over; and a repository's picture in the glyph slot, at a menu glyph's - size, because its own rule here would otherwise win over the shared - layer. */ + from, and its presses claimed back from the toolbar's drag strip it sits + over. */ .luke-menu { z-index: var(--luke-z-menu); -webkit-app-region: no-drag; @@ -1928,12 +1929,6 @@ ul.actor-stack { z-index: calc(var(--luke-z-dialog) + 1); } -.luke-menu-item-glyph .repository-picture { - width: var(--luke-menu-glyph); - height: var(--luke-menu-glyph); - flex: 0 0 auto; -} - /* Each a line naming an actor (`.actor-line`), whose spaces it keeps, wrapping between its words and names rather than cutting a name short. It clips past the ring an actor's hover fill bleeds by, so the ring is @@ -6507,17 +6502,6 @@ polygon.factory-campus-edge[data-edge="selected"] { mask-image: linear-gradient(to left, transparent, #000 var(--label-fade)); } -/* Every card the window hangs in a pill's place is drawn from `@luke/ui`'s - hover card parts (hover-card.css); what is the window's own is the size a - state glyph takes on its head and on the line under its name. */ -.luke-hover-card-glyph { - --state-glyph-size: 14px; -} - -.luke-hover-card-sub { - --state-glyph-size: 12px; -} - /* A card's corner leading with a pull request's mark sets it inline, so the corner keeps the head's baseline. */ .card-corner-mark { @@ -7464,12 +7448,6 @@ body:has(.luke-dialog-backdrop) .toast[data-action="true"] .toast-dismiss { } } -/* A card's rows (`@luke/ui`'s HoverCardRow) draw an agent's mark at a menu - glyph's size, as its tab does. */ -.luke-hover-card-row-glyph { - --state-glyph-size: var(--luke-menu-glyph); -} - /* A server's line at a card's head (`server-card.tsx`), set as a card's row is, a menu row's height and corner, its mark on the card's glyph column and the row's words beside it. */ diff --git a/apps/desktop/src/renderer/styles/settings.css b/apps/desktop/src/renderer/styles/settings.css index 2fbe59a658..3ecdd36ece 100644 --- a/apps/desktop/src/renderer/styles/settings.css +++ b/apps/desktop/src/renderer/styles/settings.css @@ -1,12 +1,17 @@ /* The settings tab: pages of rows, credentials, and the controls that write them. */ -/* Sized to the heading it labels rather than to a grid: the glyph is a letter - in the line, not an element beside it. */ -.settings-icon { - width: 14px; - height: 14px; - flex: 0 0 auto; - opacity: 0.85; +/* A glyph's own size wherever the window draws it bare, in the `base` + layer under the shared components, so a control, a menu, or a row that + holds one sizes it as its own contract says and the window's own markup + is all these reach. Sized to the heading it labels rather than to a grid: + the glyph is a letter in the line, not an element beside it. */ +@layer base { + .settings-icon { + width: 14px; + height: 14px; + flex: 0 0 auto; + opacity: 0.85; + } } /* Every row stands at the height a label-and-subtext pair needs, whether or @@ -52,16 +57,15 @@ } } -/* A chip, a menu item, and a picker's option draw their own chevron - (`chip.css`, `menu.css`, `combobox.css`), which this unlayered rule would - otherwise win over. */ -.settings-chevron:where( - :not(.luke-chip *, .luke-menu-item-end > *, .luke-combobox-option-end > *) -) { - width: 11px; - height: 11px; - flex: 0 0 auto; - color: var(--luke-color-text-subtle); +/* A chevron the window draws bare; a chip, a menu item, and a picker's + option draw their own over this (`chip.css`, `menu.css`, `combobox.css`). */ +@layer base { + .settings-chevron { + width: 11px; + height: 11px; + flex: 0 0 auto; + color: var(--luke-color-text-subtle); + } } .settings-copy { @@ -182,14 +186,16 @@ /* The one green thing on the line, in the same palette a finished session uses, so connected reads the same way complete does. A menu item and a - picker's option draw their own check (`menu.css`, `combobox.css`), which - this unlayered rule would otherwise win over. */ -.credential-check:where(:not(.luke-menu-item-end > *, .luke-combobox-option-end > *)) { - width: 12px; - height: 12px; - flex: 0 0 auto; - color: var(--luke-color-complete); - opacity: 1; + picker's option draw their own check over this (`menu.css`, + `combobox.css`). */ +@layer base { + .credential-check { + width: 12px; + height: 12px; + flex: 0 0 auto; + color: var(--luke-color-complete); + opacity: 1; + } } /* A name and what is true of it, on one line: the same gap the credential rows @@ -211,16 +217,6 @@ white-space: nowrap; } -/* The small glyph that rides a link's words. Note that a shared button's glyph - is left to the button, because this unlayered rule would otherwise shrink - the button's icon below the control contract's size. */ -.link-icon:where(:not(.luke-button-face > *)) { - width: 9px; - height: 9px; - margin-left: var(--luke-space-4); - vertical-align: -1px; -} - .settings-input { width: 100%; padding: var(--luke-space-8) var(--luke-space-12); diff --git a/packages/ui/src/components/combobox/combobox.css b/packages/ui/src/components/combobox/combobox.css index 05c226000b..4d6e822d89 100644 --- a/packages/ui/src/components/combobox/combobox.css +++ b/packages/ui/src/components/combobox/combobox.css @@ -144,9 +144,12 @@ color: var(--luke-color-text-subtle); } + /* Note that the end's glyph wears the end's ink rather than any its own + class gives it elsewhere, a connected check's green among them. */ .luke-combobox-option-end svg { width: 16px; height: 16px; + color: inherit; } :is(.luke-combobox-option-detail, .luke-combobox-option-hint) + .luke-combobox-option-end { diff --git a/packages/ui/src/components/hover-card/hover-card.css b/packages/ui/src/components/hover-card/hover-card.css index afc361064f..3f6a4b02fa 100644 --- a/packages/ui/src/components/hover-card/hover-card.css +++ b/packages/ui/src/components/hover-card/hover-card.css @@ -36,7 +36,10 @@ justify-content: center; } - .luke-hover-card-glyph > :is(svg, img) { + /* Note that the slot sizes whatever it holds, a glyph, a picture, or an + app's own mark around one, so no app has to size what it hands in; a + face (`Avatar`) brings its own size. */ + .luke-hover-card-glyph > :not(.luke-avatar, :has(.luke-avatar)) { width: 14px; /* off-scale: the card's own glyph */ height: 14px; } @@ -173,7 +176,7 @@ justify-content: center; } - .luke-hover-card-row-glyph > svg { + .luke-hover-card-row-glyph > :not(.luke-avatar, :has(.luke-avatar)) { width: var(--luke-menu-glyph); height: var(--luke-menu-glyph); } diff --git a/packages/ui/src/components/menu/menu.css b/packages/ui/src/components/menu/menu.css index f9061fec4c..edd8965dac 100644 --- a/packages/ui/src/components/menu/menu.css +++ b/packages/ui/src/components/menu/menu.css @@ -126,8 +126,8 @@ color: inherit; } - /* A glyph with no size of its own fills the slot it is in. */ - .luke-menu-item-glyph svg, + /* A glyph or a picture with no size of its own fills the slot it is in. */ + .luke-menu-item-glyph :is(svg, img), .luke-menu-item-end > svg { width: var(--luke-menu-glyph); height: var(--luke-menu-glyph); @@ -209,6 +209,12 @@ height: 14px; } + /* Note that the end's glyph wears the end's ink rather than any its own + class gives it elsewhere, a connected check's green among them. */ + .luke-menu-item-end > svg { + color: inherit; + } + .luke-menu-rule { height: 1px; margin: var(--luke-menu-inset) var(--luke-space-8); From 06d87a9096d29f425df436e545fcc6a18d1374a7 Mon Sep 17 00:00:00 2001 From: Charles Pan <30245070+charleslpan@users.noreply.github.com> Date: Sun, 18 Oct 2026 21:48:17 +0000 Subject: [PATCH 2/2] fix(ui): size only a picture standing in a menu glyph slot A face's picture fills its own disc, so the slot no longer reaches through a mark into an Avatar's picture. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/ui/src/components/menu/menu.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/menu/menu.css b/packages/ui/src/components/menu/menu.css index edd8965dac..e22b7d4a22 100644 --- a/packages/ui/src/components/menu/menu.css +++ b/packages/ui/src/components/menu/menu.css @@ -126,8 +126,11 @@ color: inherit; } - /* A glyph or a picture with no size of its own fills the slot it is in. */ - .luke-menu-item-glyph :is(svg, img), + /* A glyph or a picture with no size of its own fills the slot it is in. + Note that only a picture standing in the slot itself is sized, because + a face's picture fills the face it is drawn in (`Avatar`). */ + .luke-menu-item-glyph svg, + .luke-menu-item-glyph > img, .luke-menu-item-end > svg { width: var(--luke-menu-glyph); height: var(--luke-menu-glyph);