Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Roadmap

MOTIR-4232Done

17 (motir-core) Soft playful OVERFLOWS the left nav rail at both widths — its 40px `--height-control` is 1px wider than the 56px collapsed rail's content box, and pushes the expanded rail past the fold 68–76px earlier than the default style

Opened by Zhu Yue ·

Type · implementation defect (a raw layout constant that does not track the shape token it exists to hold, plus a single-axis overflow declaration). Not a planning mistake — no card asked for a rail width that ignores --height-control, and the shell already documents the CSS rule the rail misses. No lesson owed.

Parent · parentless at the ROOT. Reported out-of-band by Yue from manual dogfooding (data-style="soft-playful" on /plans), so there is no finding story to carry a blocked_by edge into and no in-flight card whose epic could take it. Same placement and same shape as MOTIR-3522, which was a named style exposing a shell defect the style's own card never touched.

Discovered in · manual dogfooding, 2026-09-02. Two screenshots: the expanded 240px rail and the collapsed 56px rail, both drawing a full-height classic scrollbar; the collapsed one also draws a horizontal bar under the last icon.


Root cause / fix

Reproduced, not read

Rendered headlessly against the REAL token layer — packages/design-system/theme.css compiled through @tailwindcss/postcss, the exact class strings components/ui/Sidebar.tsx emits, at the shell geometry components/ui/AppLayout.tsx builds (a h-14 top bar, then md:grid-cols-[240px_1fr] / md:grid-cols-[56px_1fr]). Measured on origin/main @ f8fb84d80f934fd1e5b8bb5a8ef99125d907dc24. There are two defects and they are independent.

Defect A — the collapsed 56px rail is NARROWER than the control it holds. Viewport-independent.

AppLayout.tsx:117 states the rail column as a raw literal:

collapsed ? 'md:grid-cols-[56px_1fr]' : 'md:grid-cols-[240px_1fr]',

The <nav> inside it is px-2 with a border-r, so its content box is 56 − 16 − 1 = 39px. Every collapsed row is Sidebar.tsx:131 / :143:

'mx-auto flex h-(--height-control) w-(--height-control) …'

--height-control is the style axis's own density token, and two of the eleven styles set it above 39px:

style--height-controlfits the 39px box?
swiss-minimal-flat · cybercore-y2k34px
warm-editorial (the default) · neo-brutalism36px
glassmorphism · aurora · neumorphism · hand-drawn-indie · retrofuturism38px⚠️ 1px of margin
soft-playful · 3d-immersive40pxoverflows by 1px

Measured under soft-playful, collapsed, at BOTH 1280×1074 and 1280×800: scrollWidth 40 into a clientWidth of 39 — hScroll: true. Under warm-editorial, 36 into 39 — hScroll: false. The 56px is the defect: it is a layout constant sized for a 36px control and nothing re-derives it when the style axis moves the control. motir-core/CLAUDE.md's shape rule is the standing statement of exactly this ("NEVER a fixed raw utility … so density flips too"); the rail is the one place a layout width is load-bearing on a control token, which is why the layout-spacing exemption does not cover it.

Defect B — the rail's scroller states ONE axis, so overflow-x computes to auto and the 1px of A becomes a second scrollbar

Sidebar.tsx:317:

<div className="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto">

Measured getComputedStyle: overflow-y: auto, overflow-x: auto — CSS Overflow 3 computes visible to auto whenever the other axis is non-visible. So A does not clip, it draws a horizontal bar in a 56px rail. The shell one file away already knows thisAppLayout.tsx's <main> carries a six-line comment stating the rule verbatim ("Left implicit, overflow-x does not stay visible: CSS Overflow 3 computes it to auto … so the shell's one scroller would acquire a horizontal bar nobody chose") and sets both axes deliberately. The rail's scroller was never given the same treatment.

The measurement behind the report's own words — the style moves the FOLD by 68–76px

Viewport height at and below which the expanded 240px rail starts to scroll, by rail configuration (the bottom section is 5 or 6 rows depending on workspaceTierRevealed; the primary section gains a 13th row when canResume):

rail configurationwarm-editorial (default)soft-playful
12 primary + 5 bottom790px858px
12 primary + 6 bottom828px900px
13 primary + 6 bottom866px942px

soft-playful raises --height-control 36 → 40px and --spacing-control-y 6 → 8px, which is 68–76px of extra rail across 17–19 rows. 1280×800 sits inside that band: the default style fits with room to spare and soft-playful does not — which is precisely "soft/playful theme gives the left nav bar a scroll bar". The scrolling itself is legitimate (a rail taller than the fold must scroll); what is not is that it draws the browser's classic scrollbar, which on the reporter's platform is ~15px and steals that width from a 56px rail — nothing anywhere in theme.css or globals.css styles a scrollbar at all (grep -n 'scrollbar\|::-webkit' over both: zero rules).

Fix direction

  1. A — the collapsed rail must be derived from --height-control, not from 56. Either widen the column to a calc() over the token (--height-control + the px-2 gutters + the border) or bound the row to the rail it sits in (w-full capped at --height-control). Whichever is chosen must hold for every style, not for 40px: five styles sit at 38px with a single pixel of margin, so a fix that only clears today's two is the same defect one style later.
  2. B — state overflow-x explicitly on Sidebar.tsx's scroller, with a comment citing the same CSS Overflow 3 rule AppLayout.tsx's <main> already cites. hidden is the right value here (unlike <main>, where wide content must stay reachable — a nav row has nothing to reveal horizontally).
  3. The bar itself — give the rail's scroller a thin, token-coloured scrollbar (scrollbar-width: thin + scrollbar-color from --el-*) so an overflowing rail costs ~6px rather than ~15px of a 56px column, and looks like the product rather than the OS.

Out of scope: re-deciding soft-playful's density, or the rail's row count. This card makes the rail correct at whatever density the style sets.


Acceptance criteria

  • Under every registered data-style, the collapsed rail's row fits its content box: rendered at the real compiled token layer at 1280×1074 and 1280×800, the rail's scroll container reports scrollWidth <= clientWidth. Asserted over the style REGISTRY (STYLE_REGISTRY), not over a hard-coded list, so a new style with a taller control fails the test rather than shipping the defect.
  • getComputedStyle on components/ui/Sidebar.tsx's scroll container returns overflow-x: hidden (stated, not inherited), and the declaration carries a comment naming the CSS Overflow 3 computation, as components/ui/AppLayout.tsx's <main> does.
  • The collapsed rail width is no longer a raw 56px independent of --height-control: changing --height-control in a [data-style] block changes the rendered rail width, demonstrated by a test that reads both.
  • When the expanded rail DOES overflow (measure at 1280×760, where the default style also scrolls), its scrollbar is the thin token-coloured one, not the platform default — assert the scrollbar-width / scrollbar-color declarations resolve on the scroller.
  • The default warm-editorial rail renders unchanged at 1280×900 (no scroll, same row geometry) — the fix is not allowed to re-shape the style nobody reported.
  • pnpm lint, pnpm typecheck and the theme suite are green.

Context refs

  • motir-core/components/ui/AppLayout.tsx:117 — the raw md:grid-cols-[56px_1fr]; and its <main> block, which states the CSS Overflow 3 rule and sets both axes (the model to follow).
  • motir-core/components/ui/Sidebar.tsx:317 — the single-axis overflow-y-auto scroller; :131, :143, :194 — the h-(--height-control) w-(--height-control) collapsed rows.
  • motir-core/packages/design-system/theme.css — the @theme --height-control: 36px and the eleven [data-style] blocks that move it (soft-playful and 3d-immersive to 40px).
  • motir-core/packages/design-system/src/theme/styles.ts:435DEFAULT_STYLE_ID = 'warm-editorial', the style the rail was sized against.
  • motir-core/app/(authed)/_components/SidebarNav.tsx — the 12-or-13 primary rows and the 5-or-6 bottom rows the measurement counts.
  • motir-core/tests/e2e/shell-viewport-floor.spec.ts · tests/theme/shellViewportUnits.test.ts — the shell's existing scroll invariants. Neither measures the RAIL, which is why this shipped.
  • MOTIR-2335 — the open story for the own-box padding/height escapes. relates_to, NOT a blocker: that story's 447-literal population is a control's OWN box, and this is a layout column that must track one. No edge owed.
  • MOTIR-3522 — the sibling defect in the same class (a named style exposing shell code that keys on the wrong thing), and the parentless-at-root precedent.
  • MOTIR-3208 — the card that made the shell own the only scroller and wrote the <main> comment this fix mirrors.

Resolution

Open.

Discussion

No comments yet.

Adding to this discussion signs you in on app.motir.co and brings you back to this request.

Add a comment