3D / Immersive's plane ladder gains the SHELL — draw the chrome (top bar + rail) as a depth surface, and close the ladder over every surface class instead of an enumerated few
Opened by Zhu Yue ·
Type
design — the RE-SHAPE card required by plan-rules/kind-bug.md's repeat-defect trigger (see § Why this is a re-shape and not a third patch). It settles a specification gap; it ships no CSS. The implementation card MOTIR-4253 is blocked_by this one.
Parent
Parentless at the ROOT. Same ladder as MOTIR-3522 and MOTIR-4230: the discovery subtask MOTIR-1052 (7.3.39 Style: 3D / Immersive) and its whole ancestor chain — story MOTIR-804, Epic 7 MOTIR-464 — are done, so the epic rung is disqualified and the ladder falls through to a root sibling. No open epic owns the design-system axis.
The gap
docs/styles/3d-immersive.md §4's plane ladder has no row for the app shell chrome. It names modal, popover, card, board card, page panel, board column, button, quiet control/row, text field and status pill — and stops. The top bar and the rail are not on it.
The stylesheet matches the spec exactly: the [data-style='3d-immersive'] block (packages/design-system/theme.css:1120-1690) contains no [data-surface='header'] rule and no [data-surface='sidebar'] rule. Both hooks exist and are emitted — app/(authed)/_components/TopNav.tsx:171 and components/ui/Sidebar.tsx:309 — and five other styles use them:
| style | [data-surface='sidebar'] | [data-surface='header'] |
|---|---|---|
| glassmorphism | ✅ theme.css:698 | — |
| aurora | ✅ 1094 | — |
| hand-drawn-indie | ✅ 1848, 1853 | ✅ 1848, 1866 |
| neumorphism | ✅ 2029, 2060, 2072 | — |
| retrofuturism | ✅ 2307 | — |
| 3d-immersive | none | none |
So the frame renders identically under 3D / Immersive and under the default style: <header class="border-(--el-border) bg-(--el-page-bg) sticky top-0 z-30 border-b"> and a rail on a flat bg-(--el-sidebar-bg). Neither picks up the global EVERY PANEL FLOATS rule either — that keys on .rounded-\(--radius-card\) and neither chrome host carries it.
The spec contradicts itself as a result. §4's Quiet control / row row says a sidebar nav link is "flat at rest… the surface they sit in is what floats". The surface it sits in does not float, is not specified to float, and has nowhere on the ladder to be told to.
Why this is a re-shape and not a third patch
plan-rules/kind-bug.md § THE REPEAT-DEFECT TRIGGER: at THREE defects against the same code path a card is not sealable until it carries either (a) three independent root causes with no shared shape, or (b) the re-shape card. Counting against the [data-style='3d-immersive'] block:
- MOTIR-3522 — 199 of 280 interactive controls flat. The physical-key rule enumerated two compiled radius utilities; every control built on a third got nothing.
- MOTIR-4230 — the immersive atmosphere painted on
bodyalone; the signed-in shell root, a surface outside that enumeration, got nothing. - MOTIR-4253 — the chrome was never enumerated at all.
They share one root shape, so route (a) is not available: the style's coverage is an ENUMERATION of selectors with no closure over the surface classes the app actually renders, and a surface outside the enumeration fails silently. 3522's own explanation names the mechanism — "the rule binds a visual promise to a compiled Tailwind class name, so the promise silently narrows every time someone builds a control on a different radius token — and nothing fails." It then fixed the mechanism for controls only. The ladder still has no closure over SURFACES, which is why the fourth instance is the shell.
Route (b) is therefore taken: this card, with the patch blocked_by it.
The design question this card settles
There are three coherent answers in shipped design systems, and they conflict — so it is a decision, not a lookup:
- Chrome as GROUND (it recedes). Fluent 2 / Windows 11: occluding materials (Mica, Acrylic) are "base layers beneath interactive UI components"; content floats over chrome and the chrome never lifts. Material 3 agrees at rest — the top app bar is Surface at elevation level 0, and the surface role is used on the rail and top bar so they "blend against the overall background".
- Chrome as REACTIVE ground. Material 3's actual mechanic: level 0 → level 2 only when content scrolls beneath, with the container filling to a contrasting
surfaceContainer. Depth is earned by an event rather than painted permanently. - Chrome as ORNAMENT (it floats in front). visionOS: toolbars and tab bars become ornaments floating slightly in front of the window on the z-axis, on a glass capsule with specular edge highlights, overlapping the window edge to stay anchored.
- The web shorthand: the floating / inset rail — shadcn ships
floatingandinsetsidebar variants; Slack and Linear use it. The rail detaches from the viewport edge with a margin, takes the card radius and a shadow, and the canvas shows around it.
The recommendation to argue against or adopt (state the reasoning on the record either way): rail → floating/inset panel, header → the on-scroll lift. Two instruments because they are two objects — the rail is a panel with an edge, the header is a sticky lid content passes under. The argument for it over the Fluent recede is that §4's ladder already commits to it (the surface they sit in is what floats, plus a Page panels — float, but do not tilt row the rail fits), and that it composes with what MOTIR-4230 shipped: an inset rail only reads as inset if there is an atmosphere behind it, which that card put on the shell root.
Three constraints the drawing inherits
- Never add
positionto the sticky header.hand-drawn-indiecarries this guard explicitly attheme.css:1845-1851— the header is already a containing block and overriding it breaks the sticky. A header treatment isbox-shadowor::after, never positional. - Shadow-carried structure owes accessibility fallbacks.
neumorphismshipsprefers-contrast: moreandforced-colors: activeblocks (theme.css:2051-2081) restoring a hairline where the soft shadow carried the edge. Anything drawn here owes the same. - An inset rail moves geometry.
components/ui/AppLayout.tsxsizes the collapsed rail column ascalc(var(--height-control) + var(--width-rail-chrome))(MOTIR-4232, merged). Insetting adds a margin term to that sum or the collapsed rail mis-sizes.
Sequencing — this card waits on MOTIR-4238
blocked_by MOTIR-4238 (in_review, PR #2540 OPEN). Raised as a subsumption / likely-in-flight advisory by validate_work_item, and it is a REAL constraint rather than a file-touch coincidence:
- Same file. 4238 rewrites
design/shell/design-notes.md— the § The rail's bottom section table, its divergence ledger, and a new account-menu section. This card writes into the same document. - Different row set. 4238 takes the
Legalrow OUT of the rail's bottom section: six rows become five, andgrep -c ">Legal<" design/shell/rail-bottom-section.mock.htmlgoes 5 → 0. A 3D shell asset drawn against today's six-row rail would ship stale on its first day, and the staleness would be invisible — a mock is confidently wrong in exactly the way 4238's own explanation describes. - So the order is fixed, not preferred: 4238 merges, then this card draws the rail as 4238 leaves it.
Acceptance criteria
docs/styles/3d-immersive.md§4's plane ladder gains a shell chrome row naming the top bar and the rail, each with its plane and its treatment, and §8's ❌ list gains the shell-flat failure mode beside the existing "Leave buttons flat".- The same §4 states a CLOSURE RULE: every surface class the app renders is either assigned a plane or listed as deliberately flat with a named reason. A surface class absent from the ladder is a spec defect, not a default. Assert the rule's presence and that each currently-rendered surface class is classified — not a count of rows.
- The document records the DECISION between the four dispositions above: which was chosen, and why the other three lose. The verdict alone does not discharge this.
- A three-file design asset draws the TARGET shell under
data-style="3d-immersive"—design/shell/3d-immersive-shell.mock.html, its.pngexport, and the notes sections indesign/shell/design-notes.md. It draws the WHOLE shell at once — top bar, expanded rail, collapsed rail, and the main region — not a chrome fragment, and it draws the target state rather than a migration from today. - The rail is drawn with the row set MOTIR-4238 leaves behind — five rows in the bottom section's complete arm, no
Legal— not the six it has at819606e99. - The mock is drawn in BOTH light and dark, and reads correctly at the fold: it introduces no motion for a reduced-motion user, and it states the
prefers-contrast: more/forced-colors: activefallback for anything whose structure rests on a shadow. - Every colour in the mock is
--el-*-derived or acolor-mix()over--el-*/--color-*tokens — no raw hue — and every shaped box uses an element-semantic shape token. The mock's own<style>block owes the ink-contrast rules exactly as its utility classes do. - The asset names the geometry consequence for
AppLayout's collapsed-rail sum explicitly, so the implementation card is not left to rediscover it. publish_design_resultis called with this card's key, the mock asmock, the.pngasimage,design/shell/design-notes.mdasnote_file, andnoteMdcarrying only the##sections this work wrote. The returned evidenceidis recorded on this card — nothing else makes that call, and a missing publish is indistinguishable from a successful run.- Per
type-design.md's GIVES/TAKES sweep: before this card closes, re-read MOTIR-4253's acceptance criteria against what this asset settled, andupdate_work_itemits criteria AND its estimate in the same pass if the design gives it more than it is sized for. The sweep runs over the SUBTREE, not only the keys this card names — a card filed after this one was authored would not be in the list above.
Advisory dispositions
shape·likely-self-blocking-design(design criterion 4, surface criterion 2). Does not apply. The rule asks, criterion by criterion, what each one PRODUCES — a design asset column beside a rendered surface column means the design criterion is a child rather than a criterion. Run over all ten criteria here, every entry in that column is adesign/ordocs/styles/file: a ladder amendment, a closure rule, a recorded decision, a three-file asset, its.png, its notes sections, apublish_design_resultcall, and a sweep over another card's text. No criterion on this card has a rendered product surface as its subject — the card that renders one is MOTIR-4253, a separate cardblocked_bythis one, which is exactly the split the rule exists to force. The detector matched on surface-classification vocabulary that names what the LADDER classifies, not what this card renders. Same disposition, and for the same reason, as the one MOTIR-4238 records.subsumption·likely-in-flight— accepted, not dismissed. Wired as ablocked_byedge; see § Sequencing above.- No
likely-over-gate-sizing. 5 points / 60 minutes, both under the gate. MOTIR-4253's estimate is the one at risk, and criterion 10 is what obliges this card's run to revisit it.
Context refs
packages/design-system/theme.css:1120-1690— the whole[data-style='3d-immersive']block. Grep it fordata-surfaceand the header/sidebar hooks are absent.packages/design-system/theme.css:698·1094·1845-1878·2029-2081·2304-2325— the five styles that DO treat the chrome; the prior art in-house.packages/design-system/theme.css:1212-1240— theIMMERSIVE BACKGROUNDrule as MOTIR-4230 left it (body, [data-app-shell]), the atmosphere an inset rail would sit on.docs/styles/3d-immersive.md§4 (the ladder), §4a (the ROLE decides the plane), §8 (the ❌ list).app/(authed)/_components/TopNav.tsx:165-173— thedata-surface="header"host and its flat class string.components/ui/Sidebar.tsx:307-312— thedata-surface="sidebar"host and its flat class string.components/ui/AppLayout.tsx:126-150— the grid columns and the collapsed-rail sum.theme.css:1845-1851— the sticky-header positional guard, stated as a rule rather than rediscovered.design/shell/design-notes.md·rail-bottom-section.mock.html·top-bar.mock.html— the shell's existing assets, and the two MOTIR-4238 is rewriting.scripts/render-design-mock.mjs— the exporter; and MOTIR-4130 for the render-the-real-component method and its measured-frames lesson.- Prior art: Fluent 2 Material · Acrylic material · M3 top app bar · M3 navigation rail · visionOS ornaments · shadcn sidebar variants.
Resolution
Open.
Discussion
No comments yet.
Adding to this discussion signs you in on app.motir.co and brings you back to this request.