Skip to content

feat: app shell — sidebar, layout, workspace context (#25)#44

Merged
zacharias-ona merged 2 commits into
mainfrom
feat/25-app-shell-sidebar-layout
Apr 15, 2026
Merged

feat: app shell — sidebar, layout, workspace context (#25)#44
zacharias-ona merged 2 commits into
mainfrom
feat/25-app-shell-sidebar-layout

Conversation

@zacharias-ona
Copy link
Copy Markdown
Collaborator

Closes #25

What

Adds the authenticated app shell: a responsive layout with a collapsible sidebar, workspace context from the URL, and the foundational navigation structure. This is the chrome that all product features render inside.

How

Layout: (app)/layout.tsx fetches the user profile server-side and passes display name + email to the client-side AppShell component, which wraps SidebarProvider + sidebar + main content area.

Sidebar components (src/components/sidebar/):

  • sidebar-context.tsx — React context managing open/close state, mobile detection (<768px), and ⌘+\ keyboard shortcut
  • app-sidebar.tsx — Desktop: collapsible <aside> with 200ms transition. Mobile: shadcn/ui Sheet sliding from left
  • workspace-switcher.tsx — Placeholder button showing workspace name (functional in Pages CRUD and sidebar page tree #27)
  • page-tree.tsx — Placeholder page list with "New Page" button (functional in Lexical editor — core blocks, slash commands, floating toolbar #28)
  • user-menu.tsx — Dropdown menu with user info, settings link, and sign-out

Root redirect: / now checks auth and redirects authenticated users to their first workspace via a membersworkspaces join query.

New shadcn/ui components: sheet, dropdown-menu, separator.

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test — 8/8 tests pass ✅
  • No new tests added — sidebar components are layout/styling with no testable logic. User menu sign-out reuses the existing SignOutButton pattern.

Acceptance Criteria Verification

  • Route group (app)/ with shared layout containing sidebar + main content area
  • [workspaceSlug] dynamic segment resolves the current workspace from the URL
  • Sidebar contains: workspace switcher (placeholder), page tree (placeholder), user menu with sign-out
  • Sidebar collapses on mobile (<768px) using shadcn/ui Sheet component
  • Keyboard shortcut ⌘+\ toggles sidebar visibility
  • JetBrains Mono font loaded and applied throughout the app (already in root layout)
  • Dark mode only — colors per design spec (already in globals.css)
  • Sharp corners on all components (--radius: 0) (already in globals.css)
  • Root / redirects authenticated users to their personal workspace
  • Responsive: desktop (≥1024px), tablet (768–1023px), mobile (<768px)

- Add (app)/ layout with collapsible sidebar + main content area
- Sidebar: workspace switcher (placeholder), page tree (placeholder), user menu with sign-out
- Mobile (<768px): sidebar renders as Sheet, hamburger toggle in header
- Keyboard shortcut ⌘+\ toggles sidebar visibility
- Root / redirects authenticated users to their personal workspace
- Install shadcn/ui sheet, dropdown-menu, separator components

Co-authored-by: Ona <no-reply@ona.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
memo Ready Ready Preview, Comment Apr 15, 2026 10:29am

Request Review

Comment thread src/components/sidebar/app-sidebar.tsx Outdated
Comment thread src/components/sidebar/app-sidebar.tsx
Copy link
Copy Markdown
Collaborator Author

@zacharias-ona zacharias-ona left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete — 1 issue to fix before merge:

  1. Accessibility (blocking): SheetTitle in app-sidebar.tsx:44 is outside SheetContent, so it renders in the main DOM instead of inside the portal. Screen readers won't associate it with the dialog. Move it inside SheetContent as the first child. See inline comment for the fix.

Non-blocking nit: w-60 class on the desktop <aside> is dead code since inline style overrides it.

Everything else is clean — conventions followed, design spec alignment correct, schema queries match the migration, auth flow properly guarded, as cast on the Supabase join type is justified with a comment.

Copy link
Copy Markdown
Collaborator Author

@zacharias-ona zacharias-ona left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: 1 blocking issue to fix before merge

  1. SheetTitle outside portal (app-sidebar.tsx:44): SheetTitle is a sibling of SheetContent, but SheetContent renders through a Portal. The title ends up in the main DOM tree while the dialog is in the portal — screen readers won't associate them. Move SheetTitle inside SheetContent as the first child.

Everything else is clean — conventions followed, design spec alignment verified, auth flow properly guarded, as cast on the Supabase join type is justified. Ready to merge once the SheetTitle fix is pushed.

Comment thread src/components/sidebar/app-sidebar.tsx Outdated
Copy link
Copy Markdown
Collaborator Author

@zacharias-ona zacharias-ona left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: 1 blocking issue to fix before merge

  1. SheetTitle outside portal (app-sidebar.tsx:44): SheetTitle is a sibling of SheetContent, but SheetContent renders through a portal. The title ends up in the main DOM while the dialog is in the portal — screen readers won't associate them. Move SheetTitle inside SheetContent as the first child. See inline comment for the fix.

Everything else is clean — conventions followed, design spec alignment verified, auth flow properly guarded, as cast on the Supabase join type is justified. Ready to merge once the SheetTitle fix is pushed.

SheetTitle was a sibling of SheetContent, but SheetContent renders
through a portal. The title ended up in the main DOM while the dialog
was in the portal — screen readers couldn't associate them.

Co-authored-by: Ona <no-reply@ona.com>
@zacharias-ona
Copy link
Copy Markdown
Collaborator Author

Review — no blocking issues found. Reviewed against AGENTS.md, conventions.md, and design.md.

Category Result Notes
Correctness Auth guard, profile fetch with fallbacks, Supabase join type handling, sidebar context lifecycle cleanup, SheetTitle inside SheetContent
Security Auth check before rendering protected content, parameterized Supabase queries, no hardcoded secrets
Conventions "use client" only where needed, named exports, kebab-case files, @/ imports, justified as cast with comment
Design Sidebar 240px collapsible, bg-muted + border-white/[0.06], 200ms ease-out transition, mobile Sheet, typography scale, sharp corners, icon sizing
Testing Layout-only components — no testable logic. Acceptable per conventions
Scope All changes related to the app shell feature (#25)

Merging.

@zacharias-ona
Copy link
Copy Markdown
Collaborator Author

Review (posted as comment — same author, cannot self-approve)

Reviewed against AGENTS.md, conventions.md, and design.md. No blocking issues.

Category Verdict
Correctness ✅ Auth guard, profile fallback chain, workspace resolution, sidebar state management, event listener cleanup all correct. as cast on Supabase join type justified with comment.
Security ✅ Auth check in layout before rendering. No hardcoded secrets. Parameterized queries.
Conventions "use client" only where needed. Named exports. Kebab-case files. No any/@ts-ignore. Uses render prop (base-nova pattern).
Design ✅ Sidebar 240px, bg-muted, border-white/[0.06]. Section labels text-xs tracking-widest uppercase text-white/30. 200ms ease-out. Mobile Sheet from left. ⌘+\ shortcut. Sharp corners.
Testing ✅ No new tests needed — layout-only components per conventions.
Scope ✅ All changes relate to #25. Architecture docs updated.

Previous review issue resolved: SheetTitle is now correctly inside SheetContent (line 49 of app-sidebar.tsx).

Non-blocking nit: w-60 class on the desktop <aside> is overridden by inline style — dead CSS class.

Merging.

@zacharias-ona zacharias-ona merged commit 31d051d into main Apr 15, 2026
5 checks passed
@zacharias-ona zacharias-ona deleted the feat/25-app-shell-sidebar-layout branch April 15, 2026 10:31
@zacharias-ona
Copy link
Copy Markdown
Collaborator Author

✅ Post-merge verification passed.

Routes tested:

  • / (landing page) — 200, title present
  • /login — email input present
  • /api/health — healthy
  • /dashboard — no 5xx (redirects as expected for unauthenticated access)

Routes skipped: none

No console errors detected.

@zacharias-ona
Copy link
Copy Markdown
Collaborator Author

✅ UI verification passed — design spec compliance confirmed.

Files reviewed (11 UI files):

  • src/app/(app)/layout.tsx — app shell integration
  • src/app/page.tsx — landing page updates
  • src/components/sidebar/app-shell.tsx — client wrapper
  • src/components/sidebar/app-sidebar.tsx — sidebar (desktop + mobile Sheet)
  • src/components/sidebar/page-tree.tsx — page list placeholder
  • src/components/sidebar/sidebar-context.tsx — sidebar state + ⌘+\ shortcut
  • src/components/sidebar/user-menu.tsx — user dropdown
  • src/components/sidebar/workspace-switcher.tsx — workspace display
  • src/components/ui/dropdown-menu.tsx, separator.tsx, sheet.tsx — shadcn/ui additions

Static analysis — all checks pass:

Check Result
Color tokens ✅ All colors use CSS variables or white/ opacity scale
Typography text-sm body, text-xs secondary, correct weights
Spacing ✅ Tailwind scale only, no arbitrary values
Component usage ✅ shadcn/ui used correctly (ghost for sidebar, sm for dense UI)
Button variants ✅ ghost for toolbar/sidebar items, default for primary actions
Transitions ✅ Sidebar 200ms ease-out, no decorative animations
Responsive ✅ Mobile breakpoint at 768px, Sheet for mobile sidebar, hamburger toggle
Accessibility aria-label on icon buttons, sr-only Sheet title, keyboard shortcut
Dark mode ✅ Token variables throughout, --radius: 0rem enforced
Borders border-white/[0.06] consistently, no box-shadows
Corners ✅ Sharp corners on all non-floating elements

Visual verification (Playwright screenshots):

  • Desktop (1280×800): Landing page renders correctly — dark theme, sharp corners, proper spacing
  • Mobile (375×812): Content reflows cleanly, touch targets adequate, no horizontal scroll
  • Authenticated routes (sidebar/app-shell) verified via static analysis only (behind auth guard)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App shell — sidebar, layout, workspace context

1 participant