LearnNewsExamplesServices
Frontmatter
id16423
titleWorkstation cards size from the viewport, not their pane
stateClosed
labels
bugai
assigneesneo-fable
createdAtAug 3, 2026, 1:41 AM
updatedAtAug 4, 2026, 1:04 PM
githubUrlhttps://github.com/neomjs/neo/issues/16423
authorneo-opus-grace
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 4, 2026, 12:38 PM

Workstation cards size from the viewport, not their pane

neo-opus-grace
neo-opus-grace commented on Aug 3, 2026, 1:41 AM

Context

Operator observation 2026-08-02, from the workstation demo: "cards at the right side, in case the window / main container is small look quite 'pressed'."

Reproduced and measured live at http://localhost:<dev>/apps/workstation/, dark theme, at two viewport sizes. Live latest-open sweep 2026-08-02T23:3xZ (newest #16419); A2A claim sweep over the last ~14 messages (newest 23:30Z) — no equivalent ticket and no in-flight claim. Neighbours checked: #16406 (tab-drag label-over-content — a FLIP paint artifact during drag, not static sizing), #16356 (Security pane zero-rect FLIP frame), #16336 (CLOSED — dock-rail label clipping). None owns card-internal sizing.

The Problem

The resident cards size their contents from the viewport and their box from the pane. Those two have no relationship, so a short pane keeps full-size type and silently shrinks a declared element to nothing.

Measured at viewport 1180x840 — every card reports the same metric font-size and padding, across a 2.3× spread in card height:

card height metric font-size padding
Task Queue Pressure 436px 47.2px 23.6px
Priority Alert Observatory 436px 47.2px 23.6px
System Metrics 188px 47.2px 23.6px
Commit Stream 188px 47.2px 23.6px

4vw of 1180 = 47.2. The type is keyed to the window; the box is keyed to the pane.

The unambiguous symptom — a declared element renders at zero. .workstation-resident-wave is declared height: 15px and carries the default flex-shrink: 1:

card height used height
436px 15px
436px 15px
188px 0px
188px 0px

The sparkline is not merely small — it is gone. The tall cards render their bars; the short ones render nothing, which is visible in the operator's screenshot and in a fresh capture.

The arithmetic, on a 188px card:

content box            140.8px   (188 − 23.6 − 23.6 padding)
children                119px    (kicker 12 + icon 24 + metric 45 + title 17 + footer 21 + wave 0)
margins                  31px    (title 8 + footer 14 + wave 9)
required                150px  →  −9.2px of slack

Negative slack, so flex shrinks the only shrinkable item to zero. At viewport 900x700 the same cards are 137px tall and the wave is still 0px, so this is not a single-breakpoint artifact — it scales with the layout.

The Architectural Reality

resources/scss/src/apps/workstation/Viewport.scss:

  • :239 .workstation-resident-carddisplay: flex; flex-direction: column; height: 100%; min-height: 0, padding: clamp(12px, 2vw, 24px)
  • :270 .workstation-resident-iconfont-size: clamp(18px, 2vw, 30px)
  • :278 .workstation-resident-metricfont-size: clamp(24px, 4vw, 58px)
  • :288 .workstation-resident-titlefont-size: clamp(11px, 1.2vw, 16px)
  • :294 .workstation-resident-footermargin-top: 14px; padding-top: 9px
  • :314 .workstation-resident-waveheight: 15px; margin-top: 9px, no flex-shrink

Every clamp() upper track is vw. In a dock/workstation layout the card's container is its pane, whose width and height are set by the dock split, not by the window. apps/workstation/view/Workspace.mjs:2245 renders the same markup for every resident, so each pane inherits sizing calibrated for a viewport it does not occupy.

The fixed margin-top values compound it: they are absolute while the type is viewport-relative, so the fixed and fluid parts of the card scale against different references.

The Fix

(Prescription — the ticket should choose deliberately rather than fold these together.)

Make the card's own box the sizing reference, and make degradation explicit rather than emergent.

  1. Container queries. Declare container-type: size on .workstation-resident-card and move the clamp() upper tracks from vw to cqi / cqh. This is the root fix: the pane becomes the reference it already is visually. Cost: container-type: size requires the element's size to be independent of its contents, which holds here (height: 100%).
  2. A floor on the sparklineflex-shrink: 0 on .workstation-resident-wave, so a declared 15px element can never resolve to 0px unannounced.
  3. Explicit progressive disclosure — below a container-height threshold, hide the footer/wave deliberately rather than letting flex shrink them toward invisibility. A hidden element is a decision; a zero-height one is an accident.

My read is 1 + 2 + 3: (1) removes the cause, (2) converts a silent disappearance into a loud one during development, and (3) decides what a genuinely short card should drop. (2) alone is a trap — with negative slack it converts a vanishing sparkline into overflow, which is not better.

Acceptance Criteria

  • Card metric/icon/title/padding sizing tracks the card's own box, not the viewport: two cards of materially different height in the same window resolve to different metric font-sizes. A spec asserts the two are unequal — asserting a specific px value would pin the layout instead of the relationship.
  • .workstation-resident-wave never resolves to a used height of 0px while it is in the DOM and its card is rendered. Either it renders at its declared height, or it is explicitly hidden by a stated rule.
  • At a viewport where a resident pane is under ~200px tall, the short card has non-negative slack — its content fits its content box without relying on flex to absorb the difference.
  • Verified at a minimum of two viewport sizes and both themes, since the light theme shares this SCSS.
  • No CSS-in-JS: the change stays in the SCSS token/skin layer under resources/scss/src/apps/workstation/.

Out of Scope

  • The tab-header overflow control and the active-tab indicator — now #16424. My first reading of the screenshot was wrong: the control is positioned correctly beside the tab label; what runs underneath it is the active-tab strip segment. My probe was wrong twice over — it tested clipping when the defect is overlap, and it walked the toolbar's children when src/tab/plugin/Overflow.mjs:17 roots the control at document.body. Both corrections came from the operator, not from re-reading. Split out because it lives in src/tab/, not in this app's SCSS.
  • #16406 — the tab-drag label paint artifact. Different mechanism (drag-time FLIP), different lane.
  • The dock split algorithm that produces the short panes. The panes are legitimately short; the cards should cope.
  • Any other app's cards. This sweep covered apps/workstation only.

Avoided Traps

  • "vw is responsive, so this is already responsive." It is responsive — to the wrong thing. The code has fluid sizing and that sizing genuinely tracks the window, which is exactly why this survives review: nothing in it looks broken. The container simply is not the viewport. Reading the coarse reference when the finer one (the pane) is the actual container is the whole defect.
  • Fixing it by shrinking the type constants. That trades a pressed large card for an undersized large one; the two card heights need different answers, which is the point of a container reference.
  • Treating the zero-height sparkline as the bug. It is the loudest symptom. Pinning it with flex-shrink: 0 while leaving vw sizing in place converts a silent disappearance into an overflow.

Related

  • #16406 — tab drag paints the tab header label over the content label (adjacent, different mechanism)
  • #16356 — Workstation Security pane zero-rect FLIP staging frame
  • #16336 — CLOSED; dock rail label clipping
  • apps/workstation/view/Workspace.mjs, resources/scss/src/apps/workstation/Viewport.scss

Decision Record impact: none — app-level presentation, no ADR authority touched. Agent OS structure-map gate: N/A (no ai/, MCP, Memory Core, or skill surface; no new or relocated .mjs).

Origin Session ID: 8c150fe3-e8a4-4475-8694-a6f92115dce9

Retrieval Hint: query_raw_memories("workstation resident card vw clamp viewport not container sparkline shrinks to zero short pane")