fix(web): usable mobile layout and canonical YuTheme alignment #43

Merged
ellamizuki merged 2 commits from feat/panel-mobile-and-theme into main 2026-09-10 18:17:05 +00:00
ellamizuki commented 2026-09-10 17:18:35 +00:00 (Migrated from github.com)

What

Two panel problems reported by Yuri: mobile is unusable, and the theme does not match the ecosystem. Both measured in a real browser at 375/768/1440 before and after, authenticated against the running API.

1. Mobile was broken because the shell had no mobile layout

The 260px sidebar rendered at every viewport width. At 375px it took 69% of the screen, squeezed the main column to 115px, and pushed content out to right: 464px. Critically, document.scrollWidth stayed at 375, so that overflow was clipped and unreachable, not scrollable.

Route elements past viewport (before) after
/ dashboard 13 0
/guild/:id/members 99 0
/guild/:id/overview 89 0

Below lg the sidebar is now an off-canvas drawer:

  • opens from a topbar button, dimmed overlay closes it
  • Escape closes it and returns focus to the trigger
  • body scroll locks while open
  • closes automatically on navigation
  • prefers-reduced-motion respected

Desktop is untouched: verified at 1440px the sidebar is still in normal flow at 260px, main is 1180px, and the menu button is absent. Tablet (768px) uses the drawer.

Touch targets: 12 below the 44px minimum, now 1 (an unrelated error-state button). Nav links and topbar actions are 44px on mobile and keep the compact 36px rail on desktop.

Zoom 200%: no horizontal overflow.

Note on a subtle bug hit while implementing

The drawer initially would not slide despite correct state. Cause: cn() uses tailwind-merge, and the dynamic width class was merged after the mobile width/transform utilities, silently dropping them. Fixed by ordering the responsive desktop width before the mobile transform state.

2. Theme drift from canonical YuTheme

The panel defined its own palette instead of the canonical tokens in the website repo (packages/ui/styles/preset.css):

Token Before Now (canonical)
canvas #000000 pure black #1a1813 warm brown-black
window #0a0a0a #1b1913
text primary #e0e0e0 grey #f4f1e8 ivory
accent #c98218 orange #d6a84f gold
links orange #64d2ff cyan
success #8bc34a #31d158
agent #c7a45d #bf5af2 purple
borders rgba(255,255,255,...) cold rgba(214,204,176,...) warm

This was exactly the "dark mode is not pure black" pitfall from the design system: the panel read cold while the rest of the ecosystem reads warm. Both token layers were aligned (the --yu-* hex tokens and the --yu-rgb-* triples Tailwind consumes), so no component markup changed. Verified in-browser: computed body background is rgb(26, 24, 19) and text rgb(244, 241, 232).

Validation

  • type-check, lint, test on @yuebot/web: all green, 182 tests passing (17 files).
  • 3 new tests covering the mobile nav trigger (accessible name, aria-controls/aria-expanded, click handler).
  • Browser verification at 375px, 768px, 1440px and 200% zoom, drawer open/close/Escape cycles.

Also

Adds an optional VITE_DEV_API_PROXY dev proxy so local development can reach a running API on the same origin without relaxing the API's CORS allowlist or the dev CSP connect-src 'self'.

## What Two panel problems reported by Yuri: mobile is unusable, and the theme does not match the ecosystem. Both measured in a real browser at 375/768/1440 before and after, authenticated against the running API. ## 1. Mobile was broken because the shell had no mobile layout The 260px sidebar rendered at every viewport width. At 375px it took **69% of the screen**, squeezed the main column to **115px**, and pushed content out to `right: 464px`. Critically, `document.scrollWidth` stayed at 375, so that overflow was **clipped and unreachable**, not scrollable. | Route | elements past viewport (before) | after | |---|---|---| | `/` dashboard | 13 | **0** | | `/guild/:id/members` | 99 | **0** | | `/guild/:id/overview` | 89 | **0** | Below `lg` the sidebar is now an off-canvas drawer: - opens from a topbar button, dimmed overlay closes it - Escape closes it and returns focus to the trigger - body scroll locks while open - closes automatically on navigation - `prefers-reduced-motion` respected Desktop is untouched: verified at 1440px the sidebar is still in normal flow at 260px, main is 1180px, and the menu button is absent. Tablet (768px) uses the drawer. **Touch targets: 12 below the 44px minimum, now 1** (an unrelated error-state button). Nav links and topbar actions are 44px on mobile and keep the compact 36px rail on desktop. **Zoom 200%**: no horizontal overflow. ### Note on a subtle bug hit while implementing The drawer initially would not slide despite correct state. Cause: `cn()` uses tailwind-merge, and the dynamic width class was merged *after* the mobile width/transform utilities, silently dropping them. Fixed by ordering the responsive desktop width before the mobile transform state. ## 2. Theme drift from canonical YuTheme The panel defined its own palette instead of the canonical tokens in the website repo (`packages/ui/styles/preset.css`): | Token | Before | Now (canonical) | |---|---|---| | canvas | `#000000` pure black | `#1a1813` warm brown-black | | window | `#0a0a0a` | `#1b1913` | | text primary | `#e0e0e0` grey | `#f4f1e8` ivory | | accent | `#c98218` orange | `#d6a84f` gold | | links | orange | `#64d2ff` cyan | | success | `#8bc34a` | `#31d158` | | agent | `#c7a45d` | `#bf5af2` purple | | borders | `rgba(255,255,255,...)` cold | `rgba(214,204,176,...)` warm | This was exactly the "dark mode is not pure black" pitfall from the design system: the panel read cold while the rest of the ecosystem reads warm. Both token layers were aligned (the `--yu-*` hex tokens and the `--yu-rgb-*` triples Tailwind consumes), so no component markup changed. Verified in-browser: computed body background is `rgb(26, 24, 19)` and text `rgb(244, 241, 232)`. ## Validation - `type-check`, `lint`, `test` on `@yuebot/web`: all green, 182 tests passing (17 files). - 3 new tests covering the mobile nav trigger (accessible name, `aria-controls`/`aria-expanded`, click handler). - Browser verification at 375px, 768px, 1440px and 200% zoom, drawer open/close/Escape cycles. ## Also Adds an optional `VITE_DEV_API_PROXY` dev proxy so local development can reach a running API on the same origin without relaxing the API's CORS allowlist or the dev CSP `connect-src 'self'`.
Sign in to join this conversation.
No description provided.