Compare commits

...

3 Commits

Author SHA1 Message Date
agent_coder 731a4f0dca refactor(#292 review): extract NAVBAR_COLLAPSE_BREAKPOINT constant (F2)
The AppShell navbar breakpoint and both burger toggles' hiddenFrom/visibleFrom
must be equal, or the sidebar becomes unreachable on tablet widths (the round-1
regression). A comment guarded that before; now a shared const does. Add
NAVBAR_COLLAPSE_BREAKPOINT='md' to sidebar-atom.ts and reference it from the navbar
breakpoint (global-app-shell) + both toggles (app-header). aside.breakpoint and the
sm brand/search gates are intentionally separate contracts, left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:02:37 +03:00
claude-stand fa439d7c7b fix(ui): match sidebar toggle breakpoint to navbar (md) so the tablet drawer opens
Follow-up to the navbar sm->md change on this branch: the two header sidebar
toggles were still gated at sm, so in the 768-991 band the DESKTOP toggle was
shown while the navbar used the MOBILE drawer collapse state — clicking it
flipped the wrong atom and the drawer could not be opened (sidebar unreachable
at 768/820, caught by QA). Gate the mobile toggle hiddenFrom=md and the desktop
toggle visibleFrom=md so the mobile toggle drives the drawer across the whole
tablet band.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 18:19:59 +03:00
claude-stand affa32cbaa fix(ui): collapse global sidebar to a drawer below 992px (tablet layout overflow)
At tablet widths (~768px) the fixed ~300px global sidebar stayed pinned, leaving
too little room for content: the settings tables (Members etc.) overflowed the
offset content area and pushed the Role/actions columns off-screen with no
horizontal scroll (unreachable). Raise the AppShell navbar (and page aside)
breakpoint from `sm` (768px) to `md` (992px) so the whole tablet band uses the
toggle drawer (closed by default) and content gets the full width.

Verified with Playwright screenshots: 768px settings/members now fits all columns
(table right 736<768, no overflow); desktop (>=992px) unchanged (sidebar pinned,
content offset); mobile unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:28:03 +03:00
3 changed files with 25 additions and 4 deletions
@@ -12,6 +12,7 @@ import TopMenu from "@/components/layouts/global/top-menu.tsx";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { import {
NAVBAR_COLLAPSE_BREAKPOINT,
desktopSidebarAtom, desktopSidebarAtom,
mobileSidebarAtom, mobileSidebarAtom,
} from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
@@ -53,7 +54,13 @@ export function AppHeader() {
aria-label={t("Sidebar toggle")} aria-label={t("Sidebar toggle")}
opened={mobileOpened} opened={mobileOpened}
onClick={toggleMobile} onClick={toggleMobile}
hiddenFrom="sm" // Must match the AppShell navbar breakpoint (md). The navbar
// collapses to the MOBILE drawer below md, so the mobile toggle
// (which flips mobileOpened) must be the one visible across the
// whole <md band — otherwise at 768-991 the desktop toggle showed
// but flipped the wrong atom, leaving the drawer unopenable (the
// regression from the initial sm->md navbar change).
hiddenFrom={NAVBAR_COLLAPSE_BREAKPOINT}
size="sm" size="sm"
/> />
</Tooltip> </Tooltip>
@@ -63,7 +70,7 @@ export function AppHeader() {
aria-label={t("Sidebar toggle")} aria-label={t("Sidebar toggle")}
opened={desktopOpened} opened={desktopOpened}
onClick={toggleDesktop} onClick={toggleDesktop}
visibleFrom="sm" visibleFrom={NAVBAR_COLLAPSE_BREAKPOINT}
size="sm" size="sm"
/> />
</Tooltip> </Tooltip>
@@ -6,6 +6,7 @@ import SettingsSidebar from "@/components/settings/settings-sidebar.tsx";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { import {
APP_NAVBAR_ID, APP_NAVBAR_ID,
NAVBAR_COLLAPSE_BREAKPOINT,
asideStateAtom, asideStateAtom,
desktopSidebarAtom, desktopSidebarAtom,
mobileSidebarAtom, mobileSidebarAtom,
@@ -88,7 +89,13 @@ export default function GlobalAppShell({
header={{ height: 45 }} header={{ height: 45 }}
navbar={{ navbar={{
width: isSpaceRoute ? sidebarWidth : 300, width: isSpaceRoute ? sidebarWidth : 300,
breakpoint: "sm", // `md` (not `sm`): below 992px the fixed ~300px sidebar leaves too little
// room for content — the settings tables (Members/…) overflow the offset
// content area on tablet (~768px) and clip the Role/actions columns
// off-screen with no horizontal scroll. Collapsing the navbar to a toggle
// drawer across the whole tablet band frees the full width for content
// (the mobile drawer is closed by default, so nothing overlaps on load).
breakpoint: NAVBAR_COLLAPSE_BREAKPOINT,
collapsed: { collapsed: {
mobile: !mobileOpened, mobile: !mobileOpened,
desktop: !desktopOpened, desktop: !desktopOpened,
@@ -97,7 +104,7 @@ export default function GlobalAppShell({
aside={ aside={
isPageRoute && { isPageRoute && {
width: 420, width: 420,
breakpoint: "sm", breakpoint: "md",
collapsed: { mobile: !isAsideOpen, desktop: !isAsideOpen }, collapsed: { mobile: !isAsideOpen, desktop: !isAsideOpen },
} }
} }
@@ -7,6 +7,13 @@ import { atom } from "jotai";
// would create a shell -> chat-window -> shell import cycle). // would create a shell -> chat-window -> shell import cycle).
export const APP_NAVBAR_ID = "app-shell-navbar"; export const APP_NAVBAR_ID = "app-shell-navbar";
// Single source of truth for the navbar collapse breakpoint. The AppShell navbar
// `breakpoint` and BOTH burger toggles' `hiddenFrom`/`visibleFrom` MUST use this
// exact value: if they drift, the sidebar becomes unreachable on tablet widths
// (the round-1 regression of #292). Kept here so the shell and the header share
// one constant the compiler enforces, instead of three hand-synced string literals.
export const NAVBAR_COLLAPSE_BREAKPOINT = "md";
export const mobileSidebarAtom = atom<boolean>(false); export const mobileSidebarAtom = atom<boolean>(false);
export const desktopSidebarAtom = atomWithWebStorage<boolean>( export const desktopSidebarAtom = atomWithWebStorage<boolean>(