-
Notifications
You must be signed in to change notification settings - Fork 0
arm E report
Surface: route /packages/cli/assets/templates/pages/detail-page, template viewer,
1440×1000 @dpr 3, theme neutral, light dark, LTR. Container padding 16px
(--container-padding-inline-*: 16px), stop padding 12px (--spacing-3).
Arm E commit: 0584db591b2 (branch vis-3938-e, bare repo {review-root}/astryx.git),
built on arm D2 b747a345b03, which sits on main 58f95421362.
Sign convention: measured against the container's content inset. Negative = outside it (into the container's padding), positive = inside it.
| # | Measurement | A (main) | D2 | E |
|---|---|---|---|---|
| 1 | first tab label vs content inset | 0.00 | 0.00 | 0.00 |
| 2 | first tab box / hover pill vs content inset | −12.00 | −16.00 | −12.00 |
| 3 | last stop (TabMenu) end padding / box width |
12px / 78.41 | 12px / 78.41 | 12px / 78.41 |
| 4 | ghost panel-toggle centre / tab-row centre (from header top) | 133.98 / 133.48 (Δ0.50) | 133.98 / 133.48 (Δ0.50) | 133.98 / 133.48 (Δ0.50) |
| 5 |
hasDivider underline span vs container content width |
n/a (no bleed) | 1168 / 1136 | 1168 / 1136 |
Supporting numbers on the same frames:
| A | D2 | E | |
|---|---|---|---|
| strip box vs container box edge (0 = fully bled) | 4 | 0 | 0 |
| strip element inline padding | 0 / 0 | 0 / 0 | 4 / 4 |
| first stop padding-inline-start | 12 | 16 | 12 |
| first stop width | 70.70 | 74.70 | 70.70 |
| header height | 152.98 | 152.98 | 152.98 |
| strip bottom → header bottom | 1 | 1 | 1 |
Metric 3, expanded. On this template the strip is layout="hug", so the
TabMenu sits nowhere near the end edge and no arm moves it. But the reason it
does not move in D2 is not that it does not need to — it is that D2's
TabMenu :last-child rule never matches. TabMenu returns a fragment:
<button data-tab-menu> followed by popover.render(...), which emits a
<template> and a zero-size <div> as siblings inside the strip. The
trigger is therefore never the strip's last child.
Where the end edge actually reaches the container — layout="fill", same 16px
container:
layout="fill", 3 tabs + TabMenu |
D2 | E |
|---|---|---|
| last stop box end vs inset | −16.00 | −12.00 |
| last stop padding-inline-end | 12px | 12px |
| ⇒ menu content (chevron) vs inset | 4.00 outside | 0.00 |
| stop widths | 363.86 / 359.88 / 359.86 / 78.41 | 358.53 / 358.53 / 358.53 / 78.41 |
D2 reproduces the exact 4px regression this PR exists to fix, at the end edge,
on any strip whose last stop is a TabMenu — i.e. the common case. E does not,
and needs no TabMenu change to get there. Verified, not assumed.
Metric 5, expanded. The template's TabList has no hasDivider (the header
draws its own rule), so this was measured on a rig with hasDivider on:
nav border-box 1168px against a 1136px container content width = content +
2×16px, i.e. the full container box, border-bottom-width: 1px, in both
arms. The divider rides the nav, which both arms bleed identically; E's strip
padding is inside it and does not shorten it.
E is as correct as D2 on this template — pixel-identical at rest — and better on every axis where they differ, except containers whose inline padding is under 12px, where D2 is exact and E is off by (12 − padding).
Container padding below the stop's own 12px. E can only add padding to the
strip; it can never take padding away from a stop. With <LayoutHeader padding={2}> (8px):
| container padding | A/main-style | D2 first label | E first label |
|---|---|---|---|
8px (padding={2}) |
— | 0.00 | +4.00 (inside the inset) |
12px (padding={3}) |
— | 0.00 | 0.00 |
16px (padding={4}) |
0.00 | 0.00 | 0.00 |
24px (padding={6}) |
— | 0.00 | 0.00 |
This is structural, not a tuning error: below 12px the fix requires shrinking
the first stop's padding, which no rule on the strip can do. Note main has the
same limitation — its hardcoded marginInline: -12 over-bleeds an 8px
container — so E is no worse than today; D2 is strictly better there.
A restated constant. E puts --spacing-3 in TabList to subtract the stop's
own padding, duplicating Tab/TabMenu's paddingInline (the import would be
circular). If anyone changes a stop's inline padding, E's edge alignment drifts
silently. D2 has no such coupling — it publishes, the stop consumes. Worth a
unit test if E lands.
-
Dead end-edge rule with a
TabMenu(above): 4px outside the inset inlayout="fill"; E lands 0.00. -
Widens stops that are not at an edge.
:last-childfires on the last Tab when there is no menu, even inhug, where it is nowhere near the end: last tab 79.39 in D2 vs 75.39 in E — a 4px-wider hover pill on one tab for no reason. Same infill, where the first stop grows to 363.86 against its siblings' 359.88; E's three growing stops are all 358.53. -
Regresses
padding={0}containers. The property is published as0px, somax(var(--container-padding-inline-start, 12px), --spacing-2)resolves to8px— the first stop's padding shrinks 12→8 and its label moves 4px, in a container with no padding to bleed. Measured: D2 first stop 66.70 wide, label +8.00; E unchanged at 70.70 / +12.00 (identical toisFullBleedoff). -
Single-tab strip. Both
:first-childand:last-childfire, so D2's one tab is 8px wider (78.70 vs E's 70.70). Both land the label at 0.00. - Three files and a private var protocol versus one rule in one file.
- RTL — symmetric in both, same numbers as LTR (a 1px rounding appears in both arms in the narrow scrolling case, not arm-specific).
-
Container that publishes nothing — both are exact no-ops (E's
max(…, 0px)clamps; D2's fallback resolves to 12px). Strip box unmoved at 24/24, first label +12.00, identical toisFullBleedoff, in both. -
layout="fill"distributable area — E's is 8px narrower than D2's (the strip's 4+4 padding), which spreads to ~1.3px per growing stop (358.53 vs 359.87). Not visible; labels are centred in a fill stop anyway. - Scrolling strip (viewport 300px) — both scroll, both fade, both show one arrow; scroll extent 324 in E vs 320 in D2 vs 316 unbled. Rest-state label still 0.00 in E.
-
overflow="none"(strip is not a scroll container, so--_tab-strip-bleedis undefined) — E'svar(--_tab-strip-bleed, 0px)fallback holds: strip padding 4/4, label 0.00, box −12.00. - Block axis — untouched by both; header height, strip-to-divider gap and both centres are identical to main to 0.01px.
| Frame | Receipt row |
|---|---|
{review-root}/shots-3938-arme/E-hover-row.png |
E-hover tab row sha=0584db591b28c78e5d1e6e8710da59fa77be55e6 route=/packages/cli/assets/templates/pages/detail-page theme=neutral mode=light dark dir=ltr vp=1440x1000@3 targets=1 state={"tabs":4,"active":"Details","ghost":"present"} |
{review-root}/shots-3938-arme/D2-hover-row.png |
D2-hover tab row sha=b747a345b03f5a577c23f7d1d78580ea2b72887c route=… theme=neutral mode=light dark dir=ltr vp=1440x1000@3 targets=1 state={"tabs":4,"active":"Details","ghost":"present"} |
{review-root}/shots-3938-arme/A-row.png (control) |
A tab row sha=58f95421362527e586c078b9377b1fcab2274910 route=… theme=neutral mode=light dark dir=ltr vp=1440x1000@3 targets=1 state={"tabs":4,"active":"Details","ghost":"present"} |
{review-root}/shots-3938-arme/E-row.png, D2-row.png
|
rest-state pair, shot first; 0 differing pixels between them, which is why the pair was re-shot hovered |
Sensor JSON beside each PNG (*.png.sensors.json), receipt summaries in
*-receipt-summary.txt.
Pixel diffs (device px, dpr 3):
D2-hover vs E-hover diffPx=1350 0.3342% maxChannelDelta=13 box(css)=x24.0-37.0 y94.0-129.7
D2 vs E diffPx=0 identical
A vs E diffPx=1218 0.0996% maxChannelDelta=232 box(css)=x886.7-1206.3 (the ghost
button, which main's row-level
marginInline shifts 12px)
The only place D2 and E differ on screen is the 13px-wide band at the first stop's pill edge, and only while it is hovered or focused.
-
{review-root}/shots-3938-arme/{A,D2,E,E-hover,D2-hover}.json— the template route. -
{review-root}/shots-3938-arme/matrix-{E,D2}.json— the 14-rig edge-case matrix (fill, no-menu, single tab, paddings 0/2/3/4/6, unpadded, prop off, RTL,overflow="none"), plus-w300(scrolling) variants.
-
{review-root}/probe-kit/tpl-3938-arme.cjs— the route probe (extendstpl-3938-armd.cjswith the edge-stop metrics;HOVER=1paints the pill). -
{review-root}/probe-kit/matrix-3938-arme.cjs— the DOM-only edge-case matrix. -
apps/storybook/stories/ZzBleed3938ArmE.stories.tsxin the worktree — scratch rig, deliberately left untracked.
D2-to-E.diff.
Reverts Tab.tsx and TabMenu.tsx to main, and in TabList.tsx:
const STOP_INLINE_PADDING = spacingVars['--spacing-3'];
const EDGE_PAD_START = `max(var(--container-padding-inline-start, 0px) - ${STOP_INLINE_PADDING}, 0px)`;
const EDGE_PAD_END = `max(var(--container-padding-inline-end, 0px) - ${STOP_INLINE_PADDING}, 0px)`;
fullBleedStrip: {
paddingInlineStart: `calc(var(${BLEED_VAR}, 0px) + ${EDGE_PAD_START})`,
paddingInlineEnd: `calc(var(${BLEED_VAR}, 0px) + ${EDGE_PAD_END})`,
},applied to the strip after stripScroll (both write the strip's inline
padding) and carrying --_tab-strip-bleed through, so stripScroll's negative
margin still cancels the focus-ring share. Compiled output verified in
packages/core/dist/astryx.css:
padding-inline-start:calc(var(--_tab-strip-bleed,0px) + max(var(--container-padding-inline-start,0px) - var(--spacing-3),0px))main-to-E-core.diff is the same arm against main, packages/core only.
Nothing was pushed and nothing was posted to GitHub.