Skip to content

[Component Audit] LayoutPanel: full-bleed children reach the panel's outer edge - #10774

Merged
cixzhang merged 4 commits into
mainfrom
night-watch-component-audit/LayoutPanel/2026-11-09
Nov 9, 2026
Merged

cixzhang merged 4 commits into
mainfrom
night-watch-component-audit/LayoutPanel/2026-11-09

Conversation

@cixzhang

@cixzhang cixzhang commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

User impact

A child that bleeds to its container's edges now reaches a panel's outer edge. On an edge where a LayoutPanel takes the Layout's own inset (its outer inline edge, and a block edge with no header or footer beside it), the panel applied that inset but told its children the inner inset. When the two differ, a full-bleed child stopped short:

  • In a Layout with padding={6}, a <Divider isFullBleed /> in a start panel stopped 8px before the panel's outer edge (24px applied, 16px told). It now reaches the edge, in left-to-right and right-to-left.
  • In a Layout with padding={0}, the same Divider ran 16px past the panel's outer edge into its clip. It now ends at the edge.
  • Every child that reads its container's padding moves the same way inside a panel, such as a full-bleed Table or TabList, or a nested Layout.
  • With the default insets the two values are equal, so nothing moves. The frames of the existing Layout stories are byte-identical to the build before the fix.

Builders get docs that match the component: hasDivider says which edge it draws on, that the Layout's defaultHasDividers does not apply, and to leave it off beside a ResizeHandle that draws its own line; padding says that 0 is full bleed; ref is listed.

Audit result

  • Package/component: core/LayoutPanel (released in @astryxdesign/core under ./Layout)
  • Mode: Night Watch whole-component audit, rubric 1.16.6
  • Base: f8a426486ee
  • Exact head: dacd2c1cda05791364a597e5ed99aa666cdd8260
  • Component contract: existing LayoutPanel.spec.md (authority: current), unchanged. FR3 already requires the panel to publish container-padding values equal to the padding it applies (architecture:container-padding) and records the automatic-path mismatch as an implementation gap (Current-state impact); this pull request closes that gap in code. No observational backfill was prepared.
  • Runtime change: the four automatic edge styles also set the matching --container-padding-* value. No prop, default, DOM, target, or applied padding changes.
Before After
Score 93.3 / A (0 open BLOCKs) 98.0 / A (0 open BLOCKs)
Distinct defects 0 BLOCK, 10 FIX, 0 NIT 0 BLOCK, 3 FIX, 0 NIT
Section W Before After State
§1 Accessibility 16 5 5 scored; no role or name without a caller role while the panel does not overflow (AR1); caller role and label reach the root (FR5); a scrolling panel carries role="group" and aria-label="Content" (AR3); the audit's axe pass reports no violations
§2 Theming 14 5 5 scored; one layout-panel target on the painting root (padding and divider paint there); --color-border, spacing tokens, and logical properties only
§3 Public API 14 5 5 scored; ref, className, style, DOM, data, ARIA, and event props reach the root (FR6)
§4 Behavior 12 4 4.5 scored; FIX closed: the automatic edges published the inner inset instead of the applied one (FR3). Open FIX: Needs Review 2
§5a Design, objective 6 5 5 scored; insets from the spacing scale; a 1px --color-border line on the content-facing edge; no radius or elevation; the slide motion is the resize controller's (FR7)
§5b Design, rendered 4 not measured 5 scored from 11 frames bound to the head
§6 Testing 8 3.5 4.5 scored; FIXes closed: no role without a caller role, style/DOM/data/ARIA/event props, the per-edge inset and its publication (automatic, beside a header and footer, explicit, and Layout padding={0}). Open FIXes: Needs Review 3 and 4
§7 Code health 8 5 5 scored; each layout Effect names the system it syncs (the resize controller's element and the browser's focus) and is idempotent; no listener, observer, or timer of its own beyond AR3 (PR1)
§8 Docs 8 4 5 scored; FIXes closed: hasDivider did not name its edge, the Layout default, or the ResizeHandle line; padding did not say 0 is full bleed; ref had no entry
§9 i18n and RTL 5 5 5 scored; the only string is the translated scroll-region name; logical properties; curated RTL D2 target; RTL frames mirror
§10 Responsive and touch 5 5 5 scored; panels stay inside their Layout in a 320px viewport (the fixture frames are fixed at 360px, so sideways page scroll is not asserted there); no touch interaction of its own

Measured weight is 96 of 100 before (§5b not measured) and 100 after. §11 is not run for an existing core component.

Why the score moved: the publication fix closes the §4 FIX; new unit cases close three §6 FIXes; the doc fixes close three §8 FIXes; §5b is measured.

Pre-fix score: scored from base source before any change. Night Watch records only the landed post-fix result in the score ledger, so the pre-fix score lives here.

Findings fixed

ID Finding Authority Fix and proof
FR3 (FIX) On the automatic path the panel published the inner inset on every edge, while it applied the Layout-edge inset on its outer inline edge and on a block edge with no header or footer component:LayoutPanel FR3; architecture:container-padding (a region publishes its applied padding) Each automatic edge style sets the matching --container-padding-*. Red, then green: the new publication unit case fails at the fixture commit and passes after the fix; the Panel States receipts fail every automatic outer edge (applies 24, publishes 16) and the full-bleed Divider (ends 8px short) against a Storybook built from the fixture commit, and pass after the fix
V5 / AR1 (FIX) No test asserted that a panel without role exposes no role or name FR5, AR1 New case
V3 / FR6 (FIX) style, DOM, data, ARIA, and event props reaching the root had no test FR6; architecture:public-component-api INV5–INV8 New case
V3 / FR3 (FIX) No test covered the automatic per-edge inset, the padding replacement, or the published values FR3, ORD2 Four publication cases (automatic edges, beside a header and footer, Layout padding={0}, explicit padding); Panel States receipts for every edge, padding={2}, and full bleed
X5b / P31 (FIX) hasDivider docs said only "Border on the appropriate edge"; the JSDoc's ResizeHandle note, the shared gap, and the Layout-default rule were missing FR2, FR4; family:layout-regions FR5 English, Chinese, and dense docs; the JSDoc names the shared gap's padding condition
X5b (FIX) padding docs did not say that it also replaces the shared gap, or that 0 is full bleed FR3, FR4 English, Chinese, and dense docs and JSDoc
X5b (FIX) ref had no doc entry FR6 Added
Audit routing (procedure) LayoutPanel has no stories of its own, so the audit's axe pass would resolve none rubric procedure .github/a11y-audit-axe.json routes the axe pass to the four Layout stories the audit spec uses

Needs Review

  1. Record follow-up. LayoutPanel.spec.md FR3 and Current-state impact, and the "Known conformance gap" and region-publisher row in architecture:container-padding, still describe the publication gap; the LayoutPanel verification map still lists the gaps the new tests close. A later owner-approved record change can remove them.
  2. Two divider owners on one boundary (FIX, unchanged). FR4 records that nothing prevents a panel hasDivider and an adjacent ResizeHandle line from both painting (family:layout-regions FR5). Which component prevents it is an owner decision; the docs now tell callers to leave hasDivider off there.
  3. Ongoing root, scroll, and paint check (FIX). The non-shrinking root, scrolling, isScrollable={false}, which edge carries the divider, the shared gap, and the full-bleed reach are measured in this audit's browser receipts, but no check that runs on every Layout change covers them. The publication itself now has unit cases.
  4. xstyle merge order (FIX). No test pins it.
  5. List edge on a panel (observation, owned by component:List). List's docs say the automatic padding of LayoutPanel is not supported for edge="outset" and edge="bleed". With this fix that host state publishes its applied padding; whether List admits it is List's decision. Not scored against LayoutPanel.
  6. Width 0 (observation, not scored). A panel at width 0 applies no inline padding but still publishes its inset. Its content is clipped and inert, so no child can show the difference; this pull request leaves that state as it was.
  7. Theming reflection (rubric gap, not scored). hasDivider and padding select styles but are not reflected by themeProps. architecture:component-theming-surface INV6 requires reflection for variant, size, selection, disabled, and interaction state, and no current record requires it for these props, so rubric T6 has no owner here.

Evidence

  • Panel States fixture: one new story with twelve labelled panels (data-panel-case) in plain frames with Layout padding={6}: start and end panels with and without a divider, a start panel beside a header and footer, a full-bleed Divider inside, padding={2}, padding={0}, width="10rem", a navigation landmark with a label, overflowing content, and overflowing content with isScrollable={false}.
  • Red before: the audit spec, run locally in Chromium against a Storybook built from the fixture commit 76f4997 (component unchanged from base; the padding={0} unit case was added later), fails only the publication checks: in all five Panel States frames each automatic outer edge applies 24px but publishes 16px, and the full-bleed Divider ends 8px before the panel's outer edge. Every other check passes, and the six frames of existing stories pass.
  • After, exact head: CI run 42615976084 with ci:run-a11y and ci:run-rtl: all 11 audit tests pass in artifact pr-a11y-evidence-dacd2c1-1 (sha256:697d84df1609b0b55e959f6324f3cf487ed97a8fce7221f4566267e8d98a8586), each receipt bound to build dacd2c1; the audit's axe pass over the four Layout stories reports no violations; pr-rtl and Stable visual regression green. One unrelated Lightbox timing check in pr-interaction failed on the first attempt and was rerun. Locally, all 11 frames pass their sensor receipts bound to the head. Against the red-before build, the six frames of existing stories (With Sidebar light, dark, and RTL; Dual Panels; Content Width with Start Panel, LTR and RTL) are byte-identical. In the five Panel States frames, the only changed pixels are the 8 CSS px of the full-bleed Divider at the panel's outer edge.
    • every panel: only the layout-panel target; flex-shrink: 0; page direction; not inert; no line on the outer edge, and a line on the content-facing edge is 1px solid --color-border; applied padding equals published padding on every edge; the panel stays inside its Layout;
    • Panel States, padding={6}: a start panel applies 24 / 16 / 24 / 24px (inline start, inline end, block start, block end), an end panel 16 / 24 / 24 / 24px; beside a header and footer the block edges are 16px; without a divider the panel overlaps the content by 16px on its content-facing edge, with a divider it does not; padding={2} 8px on every edge; padding={0} 0; width="10rem" 160px; the landmark carries role="navigation" and aria-label="Sections"; the overflowing panel scrolls and carries role="group" and aria-label="Content" (attributes; the accessibility-tree check is the existing scroll-region spec); with isScrollable={false} it clips and has no role; the full-bleed Divider spans the panel's padding box;
    • RTL frames mirror; no sideways document scroll at 1280px.
  • Visual acceptance: N/A for existing stories: their frames are byte-identical between the red-before and fixed builds. The only rendered change is the full-bleed Divider in the new Panel States fixture, shown by the red-before and green-after receipts above.

Closed evidence inventory

Surface Source Tests Consumer docs Rendered evidence Authority Result
Exports LayoutPanel, LayoutPanelProps Layout/index.ts export checks in CI LayoutPanel.doc.mjs — architecture:public-component-api reachable
Root, layout-panel target, non-shrinking LayoutPanel.tsx class case — every frame FR1 pass
width (number, string), resizable wins LayoutPanel.tsx width and resizable cases width, resizable Panel States cases FR1, ORD1 pass
Scrolling, isScrollable={false} LayoutPanel.tsx — isScrollable scrolling, not-scrollable cases FR1 pass (Needs Review 3)
Start and end edges LayoutPanel.tsx slot cases hasDivider every Panel States case, RTL frames FR2 pass
Automatic inset, padding, full bleed, publication LayoutPanel.tsx four publication cases (new) padding Panel States cases, bleed-child FR3, ORD2; architecture:container-padding pass after this fix
Divider and shared gap LayoutPanel.tsx — hasDivider Panel States cases FR4 pass (Needs Review 2, 3)
Caller role and label; none by default; scrolling group name LayoutPanel.tsx, useScrollRegionProps.ts landmark case; no-role case (new); scroll-region cases role, label landmark, scrolling cases FR5, AR1, AR3 pass
ref, DOM, data, ARIA, event props, className, style, xstyle LayoutPanel.tsx ref, className, pass-through (new) cases ref (new) — FR6 pass (xstyle order untested)
Slide, focus leaving, inert while collapsed LayoutPanel.tsx, resizablePane.ts paneSlide.interaction.browser.spec.ts, collapsedPanel.interaction.browser.spec.ts, resizableSlot.test.tsx resizable, width — FR7, FR8, AR2 pass (existing checks)
RTL LayoutPanel.tsx curated RTL D2 target — RTL frames design:bidirectionality measured
Narrow viewport LayoutPanel.tsx — — 320px frame — panels stay inside their Layout; page reflow not asserted (fixed 360px frames)
FR10 eligibility report
{
  "schema": "astryx.audit-eligibility/v1",
  "component": "LayoutPanel",
  "package": "core",
  "mode": "night-watch",
  "rubricVersion": "1.16.6",
  "repository": {
    "base": "f8a426486ee",
    "head": "dacd2c1cda05791364a597e5ed99aa666cdd8260"
  },
  "contract": {
    "id": "component:LayoutPanel",
    "authority": "current",
    "changed": false
  },
  "inventory": {
    "closed": true,
    "rows": 12,
    "open": []
  },
  "unresolved": {
    "objective": [
      "root, scrolling, clipping, divider edge, shared gap, and full-bleed reach have no ongoing automated check",
      "xstyle merge order untested"
    ],
    "manual": [
      "record follow-up: FR3 implementation-gap text, Current-state impact, verification-map gaps, and the container-padding known conformance gap",
      "FR4: which component prevents a panel divider and a ResizeHandle line from both painting"
    ],
    "ownedElsewhere": [
      "component:List eligibility of an automatic LayoutPanel as an edge host"
    ]
  },
  "remediations": [
    {"id": "FR3", "kind": "bug-fix", "evidence": "publication unit case and Panel States receipts red at the fixture commit, green at head"},
    {"id": "V5/AR1", "kind": "objective-test", "evidence": "no role or name without a caller role"},
    {"id": "V3/FR6", "kind": "objective-test", "evidence": "style, DOM, data, ARIA, and event props"},
    {"id": "V3/FR3", "kind": "objective-test", "evidence": "published inset on automatic, header-and-footer, zero-padding, and explicit edges"},
    {"id": "X5b/P31", "kind": "doc-drift", "evidence": "hasDivider edge, shared gap, Layout default, ResizeHandle line; padding replaces the shared gap and 0 is full bleed; ref entry"}
  ],
  "approvals": {
    "specOwner": "success (no record change)"
  },
  "checks": "green at dacd2c1 (CI run 42615976084)",
  "eligible": false,
  "reasons": [
    "spec:AST-029 is phase accepted; FR11 activation has not shipped"
  ]
}

@vercel

vercel Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Nov 9, 2026 11:51am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 9, 2026
@astracat-bot

astracat-bot Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 9, 2026, 12:18 PM UTC

@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Nov 9, 2026
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · dacd2c1

Storybook · Sandbox · built from this PR's head

Modified components

Component Size LoC Visual · A11y · RTL
LayoutPanel 21.3 kB (+34 B) · 4.9 kB CSS (+47 B) — ✓ · ✓ · ✓
AppShell 91.1 kB (+7 B) · 13.5 kB CSS (0) — — · — · —
Layout 7.0 kB (0) · 2.3 kB CSS (0) — ✓ · ✓ · ✓
3 more components
Component Size LoC Visual · A11y · RTL
LayoutContent 17.7 kB (0) · 1.7 kB CSS (0) — ✓ · ✓ · ✓
LayoutFooter 6.1 kB (0) · 1.6 kB CSS (0) — ✓ · ✓ · —
LayoutHeader 6.2 kB (0) · 1.6 kB CSS (0) — ✓ · ✓ · —

Package size: @astryxdesign/core 7.21 MB packed (+504 B); no new dependencies.

API changes: none in this pull request

Committed API report behind this build: cli 29 rows, core 36 rows, theme-butter 1 row, theme-y2k 1 row; run pnpm api:report.

Touched components checked; add ci:run-visual to run the full suite.


Generated by PR Enrichment workflow | View full report

@astracat-bot astracat-bot Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. CI passes on this head.

The four automatic edge styles in LayoutPanel.tsx now publish the inset they apply, as component:LayoutPanel FR3 and architecture:container-padding require. The new publication tests fail against the old component and pass here, and a full-bleed Divider in a padding={6} Layout now reaches the panel's outer edge while the existing Layout stories render unchanged.

  • Non-blocking: LayoutPanel.spec.md, container-padding.md and layout-regions.md still describe the automatic-edge mismatch as an open gap; a follow-up record change can remove those statements.

[Automated review]

@cixzhang cixzhang added ci:run-a11y Run the accessibility suite on this PR; write access, next push (AST-030 FR12) ci:run-rtl Run the RTL suite on this PR; write access, next push (AST-030 FR12) labels Nov 9, 2026
@cixzhang cixzhang closed this Nov 9, 2026
@cixzhang cixzhang reopened this Nov 9, 2026
@cixzhang
cixzhang merged commit fe174dd into main Nov 9, 2026
267 of 269 checks passed
@github-actions
github-actions Bot deleted the night-watch-component-audit/LayoutPanel/2026-11-09 branch November 10, 2026 06:04

This branch was successfully deployed

1 active deployment
Preview — dacd2c1c Deployed Nov 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run-a11y Run the accessibility suite on this PR; write access, next push (AST-030 FR12) ci:run-rtl Run the RTL suite on this PR; write access, next push (AST-030 FR12) CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant