Repository navigation
feat(os): mobile responsive shell below 768px (NEXT.md 3.1) - #100
Conversation
WindowFrame degrades to stacked full-width cards (stored geometry overridden below md, drag disabled via useMediaQuery, focused card scrolls into view). Taskbar becomes a touch-sized bottom sheet with a horizontally scrollable control strip, pinned as an overlay with safe-area padding. Root scrolls instead of clipping so cards stack above the sheet. Phone-viewport e2e covers stacking, drag no-op, scroll-into-view, sheet geometry, minimize/restore, reload.
m-vawter
left a comment
There was a problem hiding this comment.
Nice work. The stacked cards, the drag guard and the spec's coverage (width, drag, stacking, focus, minimize and restore, reload) all read well, and CI is green.
One thing breaks the sheet itself: it doesn't stay at the bottom once the cards scroll. With Agent Chat plus one app at 390×844, the page is 1290px tall. I measured the footer in a production build with this branch merged into main:
- before scrolling: top 788, bottom 844, at the bottom of the screen;
- after scrolling the root 446px: top 342, bottom 398, mid-screen over the cards.
The footer is absolute inside the root, and below md the root is now the scroll container, so the footer scrolls with the content. max-md:fixed pins it to the viewport, and the root's max-md:pb-16 already keeps the last card clear of it. With that one class (suggested inline), the sheet stays at 788–844 after the same scroll, and this spec and core-loop still pass.
For the spec (tests lane), all optional:
- A guard for this:
await test.step('the sheet stays at the bottom while the cards scroll', async () => { await page.locator('footer').evaluate((f) => { const root = f.closest('.h-screen') as HTMLElement root.scrollTop = root.scrollHeight }) const sheet = await page.locator('footer').boundingBox() expect(sheet!.y + sheet!.height).toBeCloseTo(844, 0) })
- The controls themselves. The "touch-sized controls" step measures the sheet, not the controls. In the same build, the sheet's buttons are 42px tall and the launcher pills 38px, a little under the 44px that
h-11aims for. If 44 is the target, assert it on the buttons. - Small ones. The comment says the cards never overlap;
chat.y + chat.height <= pomodoro.ywould check it. And Playwright'sboundingBox()is viewport-relative, so the "document-relative" comment can go once the sheet is fixed.
Requesting changes for the pinning only; the rest is optional.
| class="flex min-w-0 items-center gap-1.5 px-3 text-xs text-slate-200 hover:bg-slate-700" | ||
| :aria-label="`Open ${app.title}`" | ||
| @click="props.onOpenApp(app.id)" | ||
| <footer class="absolute bottom-0 left-0 right-0 z-40 flex h-12 max-md:h-14 items-center gap-2 max-md:gap-1 border-t border-white/10 bg-slate-950/90 px-3 max-md:px-2 backdrop-blur max-md:pb-[max(env(safe-area-inset-bottom),0.25rem)]"> |
There was a problem hiding this comment.
| <footer class="absolute bottom-0 left-0 right-0 z-40 flex h-12 max-md:h-14 items-center gap-2 max-md:gap-1 border-t border-white/10 bg-slate-950/90 px-3 max-md:px-2 backdrop-blur max-md:pb-[max(env(safe-area-inset-bottom),0.25rem)]"> | |
| <footer class="absolute max-md:fixed bottom-0 left-0 right-0 z-40 flex h-12 max-md:h-14 items-center gap-2 max-md:gap-1 border-t border-white/10 bg-slate-950/90 px-3 max-md:px-2 backdrop-blur max-md:pb-[max(env(safe-area-inset-bottom),0.25rem)]"> |
Pins the sheet to the viewport below md. Verified with the scroll measurement above.
|
Fixed on 9b40518: footer is now max-md:fixed so the sheet stays pinned at the viewport bottom while the cards scroll (measured 844 bottom after full scroll locally). Spec gains the scroll-pinning guard and the non-overlap check; the touch-target step now asserts every sheet button (bumped to max-md:py-2.5 so the window pills reach 44px). Optional items applied too. Full e2e 3/3 green locally. |
m-vawter
left a comment
There was a problem hiding this comment.
Re-reviewed at 9b40518 in a fresh container, with this branch merged into current main. The full e2e suite passes (5/5), and the sheet now stays pinned: after the stacked cards scroll 446px, its bottom edge is still at 844 of 844. Thanks for adding the scroll guard and the overlap check.
One footnote, not blocking. Every sheet button measures 42px tall in that build (the 44px h-11 box minus a 1px border top and bottom), and the spec asserts ≥ 38. That's well above WCAG 2.2's 24px minimum, so it's fine as is. If 44 is the goal, the border is the last 2px.
Closes #99 (NEXT.md step 3.1).
What
Below the
mdbreakpoint (768px) the desktop shell degrades to a phonelayout; at
mdand above nothing changes visually or behaviorally.mdeach window renders as a stacked full-widthcard - stored x/y/width/height are overridden by
max-md:!utilities(
static, margins,h-[70vh]), so the stored geometry is ignored as theissue requires. Dragging is a no-op at phone widths (
useMediaQueryguard in
onPointerDown,max-md:cursor-default) - ADR 0004 "touch-optimized gestures" deferred, taps still work. Focusing a card scrolls
it into view (the phone equivalent of raising it).
h-14,w-11/h-11controls) with a horizontally scrollable control strip(
overflow-x-auto,shrink-0items); DOMinic label and dividers hidebelow
md. Pinned as an absolute overlay withenv(safe-area-inset-bottom)padding. Desktop taskbar markup is byte-identical in behavior.mdso stacked cards flowabove the sheet; clock hidden at phone widths (taskbar overlay would
overlap it).
Files
Verification
card width bounds, drag no-op, chat stacked above insertion order,
focus scroll-into-view, sheet geometry, minimize/restore, reload
persistence - all verified locally against the production build
Notes
app/features/os/**is this lane's;app/app.vuetouchescomposition-root wiring only (launcher/clock repositioning declared in
the issue);
tests/e2e/**is quality lane - review request goes to theowner(s) of the other two paths.
git rebase-safe: no kernel stores changed;os.tsuntouched.