feat(layout): replace sidebar with unified AppHeader + BottomTabBar#1141
feat(layout): replace sidebar with unified AppHeader + BottomTabBar#1141timothyfroehlich wants to merge 10 commits intomainfrom
Conversation
Add @media (pointer: fine) min-width rule so desktop browsers always show the sidebar/desktop header. Mobile devices (pointer: coarse) unaffected. overflow-x: hidden moved to pointer: coarse media query so desktop can scroll horizontally when window is narrower than 768px. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add @custom-variant desktop (pointer: fine) for shell toggles - Convert MainLayout, MobileHeader, BottomTabBar to desktop: variant - Convert issue detail page mode switches to desktop: - Add container queries for IssueTimeline, AddCommentForm, ImageGallery - Sidebar auto-collapse at <1280px viewport - Swap comment/upload photo positions - min-width 1088px on html for scroll floor Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the 4-piece navigation shell (Sidebar, MobileHeader, inline desktop header, BottomTabBar) with a single AppHeader that adapts at the md: (768px) breakpoint, plus the existing BottomTabBar (mobile). Changes: - Add AppHeader (client component): logo + APC logo (desktop), nav links (desktop), HelpMenu dropdown, notifications, user menu - Add HelpMenu: help circle icon with changelog badge; items for Feedback, What's New, Help, About - Add nav-utils: isNavItemActive() shared between AppHeader + BottomTabBar - Add admin link to UserMenu dropdown (role-gated) - Rewrite MainLayout: flex-col layout, AppHeader replaces all shell pieces - Update BottomTabBar: use shared nav-utils, revert desktop: → md: - Update issue detail page: revert desktop: → md:, keep container queries - Revert globals.css: remove @custom-variant desktop / pointer:fine - Remove sidebar cookies: SIDEBAR_COLLAPSED_KEY, getSidebarCollapsed, storeSidebarCollapsed - Delete: Sidebar.tsx, MobileHeader.tsx (+ tests) - Playwright: desktop viewport 1280×720 → 1024×768 - E2E: update all testid refs (mobile-nav-signin → nav-signin, mobile-user-menu-button → user-menu-button) - Update design bible skill doc (sections 3, 4, 8, component inventory) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Updates to Preview Branch (feat/design-consistency-phase2) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
There was a problem hiding this comment.
Pull request overview
This PR refactors the app’s navigation shell by removing the sidebar/mobile header split and introducing a unified responsive AppHeader (desktop header + mobile slim header), while keeping the mobile BottomTabBar for primary navigation.
Changes:
- Removed sidebar UI and its persisted “collapsed” cookie preference.
- Added
AppHeader,HelpMenu, and sharedisNavItemActive()logic (used by both header + bottom tabs). - Updated Playwright viewport and adjusted E2E selectors/flows to the unified header test IDs.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/cookies/preferences.ts | Removes sidebar-collapsed cookie helpers now that sidebar is gone. |
| src/lib/cookies/preferences.test.ts | Removes tests for the deleted sidebar-collapsed cookie helpers. |
| src/lib/cookies/constants.ts | Removes SIDEBAR_COLLAPSED_KEY. |
| src/lib/cookies/client.ts | Removes client helper for storing sidebar-collapsed state. |
| src/lib/cookies/client.test.ts | Removes tests/fixtures for sidebar-collapsed cookie. |
| src/components/layout/user-menu-client.tsx | Adds admin-only “Admin Panel” link inside the user menu dropdown. |
| src/components/layout/nav-utils.ts | New shared active-state helper for navigation items. |
| src/components/layout/nav-utils.test.ts | Unit tests for the shared nav active-state logic. |
| src/components/layout/Sidebar.tsx | Deleted sidebar implementation. |
| src/components/layout/Sidebar.test.tsx | Deleted sidebar tests. |
| src/components/layout/MobileHeader.tsx | Deleted old mobile header component. |
| src/components/layout/MobileHeader.test.tsx | Deleted old mobile header tests. |
| src/components/layout/MainLayout.tsx | Switches layout to use AppHeader and removes sidebar/mobile header composition. |
| src/components/layout/HelpMenu.tsx | Adds desktop help dropdown with feedback + “What’s New” badge behavior. |
| src/components/layout/BottomTabBar.tsx | Uses shared isNavItemActive() instead of local logic. |
| src/components/layout/AppHeader.tsx | New unified header component (desktop nav + mobile slim header behavior). |
| src/components/layout/AppHeader.test.tsx | Adds tests for new header behavior (but mocks currently don’t match AppHeader imports). |
| src/components/issues/IssueTimeline.tsx | Moves some breakpoint styling from md:/sm: to container-query variants. |
| src/components/issues/AddCommentForm.tsx | Adjusts layout using container-query variants; reorders submit button position. |
| src/components/images/ImageGallery.tsx | Converts responsive grid breakpoints to container-query variants. |
| src/app/(app)/m/[initials]/i/[issueNumber]/page.tsx | Adds @container to scope container-query responsive behavior on issue detail. |
| src/app/(app)/m/[initials]/i/[issueNumber]/editable-issue-title.tsx | Uses container-query variants for title sizing. |
| playwright.config.ts | Sets desktop viewport to 1024×768 for Chromium/Firefox. |
| e2e/support/actions.ts | Updates shared E2E helpers to assert unified app-header and unified user menu test id. |
| e2e/smoke/public-reporting.spec.ts | Updates sign-in click target to unified nav-signin. |
| e2e/smoke/navigation.spec.ts | Updates assertions from sidebar/mobile header to unified AppHeader. |
| e2e/smoke/landing-page.spec.ts | Updates header testid assertions to unified variants. |
| e2e/smoke/auth-flows.spec.ts | Updates post-login assertions to unified AppHeader visibility. |
| e2e/full/issue-list-extended.spec.ts | Updates wording/behavior for issues link persistence with AppHeader. |
| e2e/full/invite-signup.spec.ts | Updates user menu trigger test id to unified variant. |
| e2e/full/email-and-notifications.spec.ts | Updates comment wording referencing sidebar interaction. |
| e2e/full/auth-flows-extended.spec.ts | Updates signup/signin selectors to unified header test ids. |
| e2e/full/about-page.spec.ts | Updates About navigation to use HelpMenu on desktop (direct on mobile). |
| e2e/auth.setup.ts | Updates auth-setup layout assertion from sidebar to AppHeader. |
| .agent/skills/pinpoint-design-bible/SKILL.md | Updates documented shell/navigation contract for AppHeader + HelpMenu + BottomTabBar. |
| vi.mock("./header-sign-in-button", () => ({ | ||
| HeaderSignInButton: ({ | ||
| testId = "nav-signin", | ||
| }: { | ||
| testId?: string; | ||
| className?: string; | ||
| }) => ( | ||
| <a href="/login" data-testid={testId}> | ||
| Sign In | ||
| </a> | ||
| ), | ||
| })); | ||
|
|
||
| vi.mock("./HelpMenu", () => ({ | ||
| HelpMenu: ({ newChangelogCount }: { newChangelogCount: number }) => ( | ||
| <button | ||
| data-testid="help-menu-trigger" | ||
| data-changelog-count={newChangelogCount} | ||
| > | ||
| Help | ||
| </button> | ||
| ), | ||
| })); |
There was a problem hiding this comment.
These mocks target relative paths ("./header-sign-in-button", "./HelpMenu"), but AppHeader imports those modules via the "~/components/layout/..." alias. That means the mocks won't be used and the test will exercise the real components instead. Adjust the vi.mock() specifiers to exactly match the import paths used by AppHeader so the intended stubs are applied.
| vi.mock("./user-menu-client", () => ({ | ||
| UserMenu: ({ | ||
| userName, | ||
| role, | ||
| }: { | ||
| userName: string; | ||
| role?: string; | ||
| testId?: string; | ||
| }) => ( | ||
| <button data-testid="user-menu-button" data-role={role}> | ||
| {userName} | ||
| </button> | ||
| ), | ||
| })); |
There was a problem hiding this comment.
The module specifier used in this mock ("./user-menu-client") doesn't match the path AppHeader imports ("~/components/layout/user-menu-client"). As a result, this mock won't apply and the test will render the real UserMenu, making the test more brittle and potentially failing in jsdom. Update the vi.mock() target to match AppHeader's import path (or change AppHeader to use the relative import consistently).
AppHeader nav items now collapse to icon-only at md: (768px) and expand to icon+text at lg: (1024px). APC logo deferred to lg: to save space. This fixes the header overflowing at 768px where it needed ~828px. Add assertNoHorizontalOverflow() E2E helper that checks document.scrollWidth <= document.clientWidth. Sprinkled into 6 smoke test files covering dashboard, issues list, issue detail, machines list, machine detail, and report pages. Update design bible: document two-tier responsive nav pattern, overflow testing requirement, updated component inventory. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract NAV_ITEMS to nav-config.ts, used by both AppHeader and BottomTabBar. Standardizes on LayoutDashboard icon (was Home in BottomTabBar). Memoizes nav link rendering in AppHeader. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract NAV_ITEMS to nav-config.ts, used by both AppHeader and BottomTabBar. Standardizes on LayoutDashboard icon (was Home in BottomTabBar). Memoizes nav link rendering in AppHeader. Extracts duplicate tab base class in BottomTabBar. Fixes relative import to use ~/ alias. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- nav-utils.ts: hoist ISSUE_DETAIL_PATTERN regex to module level - actions.ts: remove unused _isMobile param from visibleUserMenu() - date-range-picker.tsx: remove window.innerWidth + resize listener, always render 2 calendar months (CSS handles narrow viewports) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Visits every major route at both desktop (1024×768) and mobile (375×667) viewports, asserting document.scrollWidth <= document.clientWidth. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add AGENTS.md rule #16, NON_NEGOTIABLES.md CORE-RESP-001 through 004, and update design bible with container query size table, decision tree, z-index hierarchy, and testing guidance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| {...(date?.from ? { defaultMonth: date.from } : {})} | ||
| selected={date} | ||
| onSelect={handleSelect} | ||
| numberOfMonths={isMobile ? 1 : 2} | ||
| numberOfMonths={2} | ||
| /> |
There was a problem hiding this comment.
DateRangePicker now always renders numberOfMonths={2} inside a PopoverContent that has no max-height/overflow handling. Because the Calendar stacks months vertically below md: (months class is flex-col md:flex-row), this can make the popover taller than the viewport on mobile/tablet and clip the second month with no way to scroll to it. Consider constraining the popover (e.g., max-height based on viewport + overflow-y-auto) or otherwise ensuring the calendar remains fully reachable on small viewports without reintroducing JS viewport detection.
Summary
AppHeaderthat adapts at themd:(768px) breakpoint@custom-variant desktop/pointer: fineapproach in favour of standard viewportmd:breakpoints (matching GitHub's approach)mobile-nav-signin/mobile-user-menu-buttonE2E testids updated to unified variantsNew files
AppHeader.tsx+AppHeader.test.tsx— unified responsive headerHelpMenu.tsx— help dropdown with changelog badgenav-utils.ts+nav-utils.test.ts— shared active-state logic for AppHeader + BottomTabBarDeleted files
Sidebar.tsx,Sidebar.test.tsxMobileHeader.tsx,MobileHeader.test.tsxPLAN-desktop-layout-stability.mdTest plan
pnpm run check— 766 unit tests, types, lint all passpnpm run smoke— 85 passed, 0 failed, 4 skipped🤖 Generated with Claude Code