mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Review found three scroll containers sitting on surfaces the rebinding contract covers, none of which rebound: ui-primitives' shared Menu card on --dsw-specific-menu (the surface PopupSelectView already rebinds for), and the composer input and question composer cards, both on --dsw-specific-input-major. Each rendered the l1 thumb, which differs from l2 only in the dark palette and only on that surface, so a light-palette screenshot and a code read both look correct. Adds the mechanical check that would have caught them instead of leaving it to inspection: a sheet that scrolls somewhere and paints a known elevated surface somewhere must rebind. The elevated set is derived from the sheets that already rebind, since a rebinding rule paints the surface whose elevation it declares, so a new elevated surface joins the set by rebinding rather than by anyone updating a list. Surface-level rather than element-level because the card and the descendant that scrolls are separate rules and CSS text does not say which contains which. Verified by reverting each of the three fixes in turn: the check names the sheet and the surface every time. Also commits snapshots/sidebar-scrollbar/geometry.expected.md, the resolved scrollbar style and geometry in both palettes. The aria goldens the other web scenarios commit cannot carry a CSS-only change, since it alters no DOM and no accessible name and leaves their trees byte-identical. Absolute coordinates stay out: they track font metrics and the laid-out sidebar width, so committing them would document the platform and force a per-platform re-record.
203 lines
4.9 KiB
CSS
203 lines
4.9 KiB
CSS
.root {
|
||
position: relative;
|
||
display: inline-flex;
|
||
}
|
||
|
||
/* Dropdown card (figma MenuDropdown 122:9481 / 419:16920): menu surface,
|
||
* r12, inverted hairline border, shadow-lv3, 4px inset padding. */
|
||
.list,
|
||
.submenu {
|
||
/* min-widths below are the design's outer card widths — include the pad. */
|
||
box-sizing: border-box;
|
||
padding: 4px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0;
|
||
border: 1px solid var(--dsw-alias-border-inverted);
|
||
border-radius: 12px;
|
||
background: var(--dsw-specific-menu);
|
||
box-shadow: var(--dsw-shadow-lv3);
|
||
/* Elevated surface: the scrollbar thumb takes the l2 elevation tokens. The
|
||
declaration sits on the card rather than on `.scrollable .viewport`
|
||
because the elevation is a property of this surface, and the custom
|
||
properties inherit down to whichever descendant actually scrolls (see
|
||
ui-theme styles/scrollbar.css for the rebinding contract). */
|
||
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
||
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
||
}
|
||
|
||
/* Primary card is 218 wide in the design across both hosts. */
|
||
.list {
|
||
position: absolute;
|
||
top: calc(100% + 4px);
|
||
left: 0;
|
||
z-index: 100;
|
||
min-width: 218px;
|
||
max-width: 360px;
|
||
}
|
||
|
||
/* Portal mode: fixed in the viewport, coordinates supplied inline from the
|
||
* anchor rect (side/align resolved in JS, the in-place offset rules above
|
||
* don't apply). Portaled lists must layer above modal overlays (z 1000) —
|
||
* an anchor inside a dialog still expects its menu on top. */
|
||
.portal {
|
||
position: fixed;
|
||
top: auto;
|
||
left: auto;
|
||
z-index: 1100;
|
||
}
|
||
|
||
/* Open above the anchor (empty-state workspace chip: figma 122:9481). */
|
||
.sideTop {
|
||
top: auto;
|
||
bottom: calc(100% + 4px);
|
||
}
|
||
|
||
.alignEnd {
|
||
left: auto;
|
||
right: 0;
|
||
}
|
||
|
||
/* Viewport fit: the card stops 12px short of the viewport's top/bottom edges
|
||
* (24 = 2 × the portal MARGIN in Menu.tsx) and taller content scrolls inside
|
||
* .viewport, so a pinned .footer stays visible. Menus with submenu rows skip
|
||
* this class — the overflow clip would crop the side card, so they rely on
|
||
* staying short. */
|
||
.scrollable {
|
||
max-height: calc(100vh - 24px);
|
||
}
|
||
|
||
.viewport {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
}
|
||
|
||
.scrollable .viewport {
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* Pinned rows below the scroll region; l2 hairline (l1 is near-invisible on
|
||
* the menu surface) mirrors the .separator spacing. */
|
||
.footer {
|
||
flex: none;
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin-top: 4px;
|
||
padding-top: 4px;
|
||
border-top: 1px solid var(--dsw-alias-border-l2);
|
||
}
|
||
|
||
.itemWrap {
|
||
position: relative;
|
||
}
|
||
|
||
/* Menu cell (figma .Menu_cell): min-h 40, r10, pad 10/8, 14/22 primary,
|
||
* gap 8 between leading icon / label / trailing check. */
|
||
.item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
min-height: 40px;
|
||
padding: 8px 10px;
|
||
border: none;
|
||
border-radius: 10px;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
line-height: 22px;
|
||
color: var(--dsw-alias-label-primary);
|
||
text-align: left;
|
||
}
|
||
|
||
.item:hover:not(:disabled) {
|
||
background: var(--dsw-alias-interactive-bg-hover);
|
||
}
|
||
|
||
.item:disabled {
|
||
opacity: 0.4;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.itemIcon {
|
||
display: inline-flex;
|
||
flex: none;
|
||
width: 16px;
|
||
height: 16px;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--dsw-alias-label-tertiary);
|
||
}
|
||
|
||
.itemLabel {
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.check {
|
||
flex: none;
|
||
color: var(--dsw-alias-label-primary);
|
||
}
|
||
|
||
/* Selected cell keeps the plain fill (marker is the trailing check); the
|
||
* class remains as a hook for owner-side emphasis. */
|
||
.selected {
|
||
background: transparent;
|
||
}
|
||
|
||
/* Destructive row: error text/icon, danger hover fill. */
|
||
.danger {
|
||
color: var(--dsw-alias-state-error-primary);
|
||
}
|
||
|
||
.danger .itemIcon {
|
||
color: var(--dsw-alias-state-error-primary);
|
||
}
|
||
|
||
.danger:hover:not(:disabled) {
|
||
background: var(--dsw-alias-interactive-bg-hover-danger);
|
||
}
|
||
|
||
/* Heading row: non-interactive small grey text, padding aligned with items. */
|
||
.label {
|
||
padding: 8px 10px;
|
||
font-size: 12px;
|
||
line-height: 16px;
|
||
color: var(--dsw-alias-label-tertiary);
|
||
}
|
||
|
||
/* Separator cell (figma 122:9481): py 4 / px 2 around the hairline. */
|
||
.separator {
|
||
height: 1px;
|
||
margin: 4px 2px;
|
||
background: var(--dsw-alias-border-l1);
|
||
}
|
||
|
||
/* Nested card to the right of the parent row (figma 419:16920).
|
||
* Bottom-aligned with the parent menu card (grows upward): itemWrap sits in
|
||
* .list's 4px pad, so bottom: -4px matches the list's outer bottom edge.
|
||
* Horizontal: list pad (4px) + 6px card gap = 10px past itemWrap — plain
|
||
* `100% + 6px` collapses to ~2px between outer card edges.
|
||
* ::before bridges the full gap so the pointer can cross without mouseLeave. */
|
||
.submenu {
|
||
position: absolute;
|
||
top: auto;
|
||
bottom: -4px;
|
||
left: calc(100% + 10px);
|
||
z-index: 101;
|
||
min-width: 163px;
|
||
}
|
||
|
||
.submenu::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
bottom: 0;
|
||
left: -10px;
|
||
width: 10px;
|
||
}
|