Plan step 35: <x-app-shell> becomes <x-scaffold>, M3's and Compose's name
for the structure of bars, rails and panes, with no alias; every package
view, the showcase, the tests, the skills, README and UPGRADE follow. Its
markup and Tailwind classes stay for the navigation rewrite. The new fab
slot places the FAB as Compose's Scaffold does - bottom-end, 16px from the
edges below medium and 24px from it, clear of the navigation bar and of a
snackbar on screen (resources/css/layout/scaffold.css) - and the content
region tells a pane inside that the margin is already drawn.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 35: <x-feed min-item gap>, following the reference's feed rules
- one column below medium, then as many equal columns of at least the
minimum item width as the feed has room for, 24px apart from medium,
items in their source order.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 35: <x-supporting-pane>, following the reference's placement
table - below the focus pane below expanded, beside it from 840px at a
fixed 360px (412px from large) or the two-thirds split. Below expanded
compact="sheet" docks the supporting pane as a bottom sheet whose drag
handle opens it; always on the trailing side, so focus order matches the
panes on screen, and mirrored in RTL.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 35: <x-list-detail>, following the reference's visible-panes
table row by row - one pane below expanded, the detail replacing the list
with a back button once something is selected; from 840px the list a
fixed 360px (412px from 1200px) beside the detail, 24px apart. The
selection binds with wire:model or x-model; below expanded focus moves to
the detail and back() returns it to the item (resources/js/layout.js).
Grid columns mirror in RTL, and the back arrow turns with them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 35: <x-pane>, a content region with M3's margins (16px below
medium, 24px from it) drawn once however panes and layouts nest, a width
cap, and an optional pane app bar - <x-app-bar> as a direct child of the
pane with title, subtitle, actions, a back link or action, a leading
slot, and the section navigation under it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 35: <x-surface level padding corner outlined>, a tonal region
in surface or a surface-container step, padded with a spacing token,
rounded with a corner token and optionally edged in outline-variant. A
pane on a surface keeps its margin again, since the surface is a new edge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 35: <x-grid :columns gap min-item>. The column count per
breakpoint is written as five inline custom properties, each filled from
the nearest smaller breakpoint, so a nested grid never inherits its
parent's; min-item fills a row by the room the grid has, and with columns
the counts become a ceiling.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 35: <x-row gap align justify wrap stack-below>, children side
by side in a pane and stacked under one another below an M3 breakpoint,
in the inline direction so a right-to-left document mirrors it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 35: <x-stack gap align>, the first of M3's layout components,
with what they all share - src/Support/Layout.php reading the props into
data-md-* attributes, the spacing-token gap and padding rules, the
hide-below/hide-from rules in material.visibility, the stylesheet checks
for resources/css/layout, the browser test file and the skill's Layout
group. The Workbench puts the material layers above Tailwind's preflight,
which would otherwise zero every layout padding and margin.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
tailwind.css and components.css now hold one block per audit group, and the new
layout.css gathers the layout components, so the parallel rewrites of plan steps
35 and 36 each edit their own lines. .browser-lock is the lock that keeps those
streams to one browser at a time.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
<x-icon size> writes --md-icon-size (24px by default) and takes the optical-size-20
cut for 20px or less, as M3 asks; mirror-rtl flips a directional symbol. <x-shape
size> writes --md-shape-size. Both draw from plain-CSS stylesheets in
material.components, gathered in components.css until all.css replaces it; a
caller's size class still wins while the other components are rewritten.
Plan steps 35 and 36, the prop every component rewrite depends on.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 33. The Workbench imports foundation.css ahead of
@import 'tailwindcss', so the material layers are declared first and
sit below Tailwind's, then tailwind.css: the theme, the token utilities
and the component stylesheets still written for Tailwind, with no
tokens, so nothing is declared twice. material.css keeps working for
1.x-style imports after Tailwind by bringing the tokens, the hiding
rules and tailwind.css, and never the reset, which would outrank
Tailwind's utilities from there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 34. text.css (material.text, imported by the foundation) is
the fixed set for text on plain elements: md-type-* and
md-type-emphasized-* with the type utilities' declarations, the role
inks md-ink to md-ink-inverse, md-text-start|center|end, md-truncate,
md-line-clamp-2|3, md-nowrap, md-tabular and md-visually-hidden. Its
header says when to use each, from the material-3 guideline and
docs/reference/m3/styles.md § Typography.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 33. foundation.css is the one required import: the full
material layer statement, then plain imports of its parts, each opening
with the same statement.
- foundation/reset.css (material.reset): Tailwind 4's preflight rule for
rule, with M3's on-surface-variant placeholder and no typeface.
- foundation/hidden.css: [hidden] and [x-cloak], outside every layer.
- foundation/tokens.css (material.tokens): the token files.
- foundation/base.css (material.base): the page's surface, ink and
brand typeface, and the font face.
- foundation/interaction.css (material.base): md-state-layer,
md-focus-ring, md-touch-target and md-link, the plain-CSS forms of the
utilities; md-state-layer also takes data-md-dragged, and
md-touch-target is 48px (space600) where the utility reads 3rem.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 33. tokens/spacing.css declares --md-sys-measurement-space25
to space900 (2 to 72px) in material.tokens, from
docs/reference/m3/styles-supplement.md § Spacing, in px because a dp
is a CSS pixel and spacing does not scale with text. The design skill
names the new token family, as its drift test requires.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 33. Each token file now opens with the material layer
statement and wraps its declarations in material.tokens (the font face
in material.base), so an application's unlayered scheme outranks the
package default whatever the selectors, and the foundation can import
the files with no layer() on the import.
The type-* and state utilities move to tokens/utilities.css, still
imported by material.css, so no file the foundation imports carries a
Tailwind directive. [x-cloak] moves beside the [hidden] rule in
foundation/hidden.css, outside every layer; material.css imports it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 22, item 8 (actions.md § Missing, "Adaptive menu → bottom sheet
at compact"): M3 says "at compact breakpoints, consider swapping a menu for
a bottom sheet", and nothing connected <x-menu> to <x-bottom-sheet>.
<x-menu sheet-at-compact> draws its slot twice, in the popover and in a
modal <x-bottom-sheet> teleported to <body>, and below `medium`
(upTo('medium')) the trigger opens the sheet. The trigger says
aria-haspopup="dialog" there and "menu" from medium, aria-expanded in
both. Items keep their roles and the APG keyboard in the sheet; choosing
one, Escape or Tab close it and return focus to the trigger; a submenu
opens in place under its item; `filter` works in both; a resize across
600px closes whichever is open. The sheet has a fixed id that menu.js
makes unique and keeps as its wire:key, and the lists are keyed, so a
Livewire render patches an open sheet instead of swapping it.
bottom-sheet.blade.php is used as is. The filter now finds its field and
empty row per list instead of through x-refs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Chrome makes a scroll container with nothing focusable inside a keyboard stop, and
showModal() gives it the dialog's first focus, which showed the browser's own ring.
The body now takes M3's 3px secondary indicator, inset so the dialog's rounded,
overflow-hidden box does not clip it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 23 (containment), containment.md § Missing: "no divider pinned
between a scrolling body and the header/actions". Every dialog now draws a
1px outline-variant rule under the header once the body is scrolled away
from its top, and over the actions while more of it is below; a body that
fits shows neither. The rules are pseudo-elements in the rows' own padding
(dialog.css), so showing one moves nothing, and x-dialog-dividers
(dialog.js) marks the wire:ignore.self <dialog>, which a morph leaves
alone, watching scroll and a ResizeObserver on the body and a wrapper
around the slot. The M3 gaps are split around the rules (8/8 under the
header, 8/16 over the actions). A full-screen dialog's rule sits under its
phone bar, and its action bar's always-on border follows the scroll too.
`separator` now means "draw both rules always" instead of rendering two
<x-divider> elements. The fade uses the effects-fast token, zero under
reduced motion.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 25, item 8 (navigation Missing: "Toolbar, large-screen
treatments"). M3's toolbar guidelines (Adaptive design, Resizing) say it
of the docked toolbar: "On web and large screens, the docked toolbar can
be rounded. Dividers can be used to organize large amounts of items",
and it can be placed in different parts of the page. `rounded` does that
from expanded (840px): fully rounded, spanning its container, lifted
16px off the window's edges at place="bottom"; below expanded it stays
the square full-width bar. An <x-divider vertical /> between groups of
controls stands as tall as the icon buttons inside any toolbar. The
floating toolbar is already fully rounded and gets no new form.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 25, item 7 (navigation Missing: "App bar, trailing-action
overflow at small widths"). `<x-app-bar :actions="[...]">` takes the
trailing actions as a list whose entries take <x-menu-item>'s props plus
any action attribute. M3 allows up to two trailing icon buttons, and up
to four on larger screens, and lets trailing actions collapse into an
overflow menu at smaller breakpoints: below medium the bar keeps at most
two icon buttons, from medium four, the "More options" button counted
among them, the rest in its menu. Both forms render and media queries
pick one, so there is no script and no flash; each width has its own
menu so the arrow keys never reach a hidden row. The actions slot still
takes markup and never overflows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 25 (navigation Missing): M3's toolbar configuration table lists
"With FAB" for both toolbars, but the `fab` slot was gated to the floating one.
A docked toolbar now takes it at its end, inside the toolbar where the arrow
keys reach it; its controls gather at the start, and the FAB rests flat on the
bar — elevation 0 for a FAB nested in another component, as Compose's
bottomAppBarFabElevation is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 22, actions.md § Missing (FAB to extended FAB scroll collapse):
M3 has an extended FAB collapse to a FAB on scroll-down and re-extend on
scroll-up, and `<x-button fab>` only swapped on window width.
`<x-fab collapse-on-scroll>` (extended, with an icon) takes a
`materialFab` flag from the new resources/js/fab.js, which reads the
window's scroll once a frame, ignores moves under 8px and extends again
near the top. The morph is CSS in actions.css: the label sits in a grid
track that closes to zero, the gap and minimum width follow on the
default spatial spring, and the label fades on the effects one; reduced
motion swaps outright through the motion tokens. The label is clipped, not
removed, so the collapsed FAB keeps its accessible name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 25 (navigation Missing): M3's "Expanded behavior | Hide when
collapsed" was only implicit below `medium` in the adaptive rail.
`<x-navigation-rail hide-when-collapsed>` (collapsible and adaptive rails) takes
the rail out of the layout when it is collapsed; `$store.rail.show()` from an
app-bar menu button brings it back expanded over a scrim, sliding in, and its
own menu button docks it again. It does not reach the bands where the window,
not the visitor, collapses a rail (below `medium` for collapsible, `medium` for
adaptive), since M3's collapsed rail may never hide. Every `rail-collapsed`
branch now stops while the rail is open. `<x-app-shell hide-rail-when-collapsed>`
exposes it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
M3 § Search names three entry points and only the bar existed; suggestions
before the first keystroke were missing too (plan step 24, audit
docs/audits/m3-alignment/inputs.md § Missing). `trigger="icon"` is the search
icon button — search as a secondary action, one 48px button that expands into
the full-screen view at any width, since an icon button has nothing to dock
under, and takes its focus back on close. The `suggestions` slot stands where
the results do until something is typed; the live region counts whichever of
the two is on screen and names suggestions as suggestions.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 25 (navigation Missing): M3's "alignment: top or center; on tablets
prefer center alignment for reach" had no implementation — the rail was
top-aligned only. `<x-navigation-rail align="center">` centres the destinations
between the header and the footer, which stay at their ends because M3 keeps the
menu icon and the FAB top-aligned. `safe center` gives the top back the moment
there are more destinations than fit, where centring would push the first one
out of reach above the scroller.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 22, actions.md § Missing (Square-by-default button groups): M3
lists "Default shape | Round, square" as a button-group configuration and
neither `<x-button-group>` nor `<x-group>` had a `shape` prop, so a group
of square buttons still drew round outer corners.
`shape="square"` passes the square corner scale to every child through
`data-shape` and two new variables in groups.css: `--group-outer` is what
the two ends of a group round to (half the height when round, the corner
its inner edges take when square — M3's square table, 4/8/8/16/20dp), and
`--group-square`/`--group-square-pressed` give a standard group's buttons
the scale `<x-button shape="square">` draws. The selected rules keep
`--group-full`, so a selected button in a square group rounds: M3 has a
toggle inside a group swap shape on selection either way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 25 (navigation Missing): NavigationRailCollapsedTokens
.NarrowContainerWidth, 80dp, had no way in — only the default 96.
`<x-navigation-rail width="narrow">` takes it; the collapsed width is now a
variable, so the four rules that each have their own reason to name it follow
without a specificity fight. No label fits under a 56px indicator at 80px, so a
narrow rail's items are their icons alone, with the label taken out of the
drawing and left in the accessibility tree, where it names the destination.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 22, actions.md § Missing (Menus as a filtering surface): M3's
menus page describes a menu that embeds a text field and filters its
options as you type, and nothing in the library did that.
`<x-menu filter>` renders the field, sticky above the list, and hides the
rows the query leaves out — client-side over the items already rendered,
so nothing is fetched and a `wire:click` stays where it was. The field
keeps the focus and the arrow keys move a highlight it names through
`aria-activedescendant`, the APG combobox keyboard `<x-choices
searchable>` already uses; Enter chooses the highlighted row, and a query
that leaves nothing says so. The list becomes a `role="menu"` inside the
popover, because a text field is not something a menu may contain.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 25 (navigation Missing): M3's "hides on scroll-down, reappears on
scroll-up" had no implementation. `<x-navigation-bar hide-on-scroll>` slides the
bar out on the default spatial spring — an instant swap under reduced motion,
which zeroes the duration token — and never while a snackbar, a bottom sheet or
a drawer is anchored to its edge; focus reaching the bar brings it back.
`<x-app-shell hide-bar-on-scroll>` picks it, and --material-bottom-bar follows
the bar down and up so a `fab` button and the snackbar keep their offsets.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
M3 § Date Pickers, Anatomy describes a 14-element full-screen range picker for
compact breakpoints; `<x-datepicker range>` always used the 360px modal dialog,
which on a phone is cramped for a two-month scroll (plan step 24, audit
docs/audits/m3-alignment/inputs.md § Missing). A third presentation, "full",
joins docked and modal: the same dialog grown to the screen, an app bar with a
close button and Save, the supporting text and the range as the headline over a
divider, the weekday labels held at the top, and the months in one vertically
scrolling list, each under its own label. The grid now draws from one `rows`
getter, which is this month's weeks everywhere else, so the docked and
single-date pickers render exactly as before. The list is a window of months
grown by the scroll and by the keyboard, since nothing here is lazy.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 23, containment.md § Missing ("Cards: the dragged state").
`data-dragged` on `<x-card>` takes the top of M3's card elevation scale
— Level4 (8dp) elevated, Level3 (6dp) filled and outlined — under the
16% dragged state layer from tokens/state.css. The row's hover and press
rules now exclude a dragged card, so the card a pointer is holding stays
at its dragged level. The application owns the attribute: nothing in the
browser tells a card it is being carried.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 25 (navigation Missing): NavigationBarTokens.TallContainerHeight,
80dp, had no prop. `<x-navigation-bar tall>` picks it, and the tall container
keeps the vertical item layout at every width — the 600px horizontal layout is
the short bar's alone, so every rule behind that container query now names
`:not([data-tall])`. `<x-app-shell tall-bar>` passes it through and grows
`--material-bottom-bar` to 5rem with it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 22, actions.md § Missing (Submenus): M3's Expressive vertical
menu specifies submenus and the Left/Right keys that open and close them,
and the library had neither.
`<x-menu-item submenu>` puts its slot in a second popover on the item's
end, flipping to the start where there is no room. `materialSubmenu`
reuses the menu button pattern one level in: the item is its own trigger,
so the anchor move and the button lookup are overridden away; Right,
Enter and Space open it, Left and Escape close it and return the focus,
and a fine pointer resting on the item opens it after a moment. `items()`
now stops at the popover it belongs to, so the arrows never walk between
a menu and an open submenu. The menu publishes its container as custom
properties so a vibrant menu's submenus are vibrant too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
M3 § Text Fields, Behaviour: compact may let a field span full width, but
medium and expanded must bound it — "never let it span the full width of a
large screen". Nothing in the package capped a field (plan step 24, audit
docs/audits/m3-alignment/inputs.md § Missing). From `medium` a field now stops
at 40rem — the site names no number, so the header says where this one comes
from — which a `max-w-*` class beats and `full` takes off. The search bar
already carries M3's own 720px.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
M3 lists "supporting text + character counter" as a configuration of both
text-field variants; nothing in the package drew one (plan step 24, audit
docs/audits/m3-alignment/inputs.md § Missing). `counter` on `<x-input>` and
`<x-textarea>` now puts `n/max` at the end of the supporting-text row, counted
from the control on every input, in the error colour past the maximum, and said
as M3's own "Character count, 5/20" from a polite region once typing settles.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
The actions stream has since given the small extended FAB an 80px minimum width,
which the morph would have kept once the label closed. Dropping that minimum and
squaring the box against the height its size already sets gives back the 56, 80
or 96px FAB without this file naming any of those numbers.
Plan: docs/plans/material-3-alignment.md, step 21 (navigation N-23).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
M3 asks the rail to animate its FAB into an extended FAB as it expands; the
documented pattern was two FABs swapped by `display`, so the collapsed one
appeared at once while the rail's width was still springing. The header now takes
one <x-fab label icon> whose label springs open and shut on the rail's own
spring — and names it at both widths, where the collapsed FAB had needed a
tooltip to have a name at all.
M3 also lists two container treatments as optional that the library had neither
of: `divider` draws the vertical divider on the page's edge, which is its own
answer to a page scrolling under a fixed rail, and `fill="false"` drops the
container colour. Neither reaches a rail open over a scrim. And a `collapsible`
rail, whose width is the visitor's choice rather than the window's, is held to
the collapsed 96px below `medium`, where M3 asks for a navigation bar instead.
Plan: docs/plans/material-3-alignment.md, step 21 (navigation N-22, N-23, N-24).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
M3's scaffold is bars, then rails, then panes, and the shell had no slot that
rendered above the rail: an application-wide bar could only be a pane bar beside
it. `banner` is that slot — the shell is a column now, with the rail and the page
as one row inside it — and the rail sticks under a pinned banner through
--material-banner rather than behind it. `top` still means the page's own bar.
Plan: docs/plans/material-3-alignment.md, step 21 (navigation N-14).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9