All Projects default. No project is selected. Switcher shows muted dot + "All Projects" in muted text. Metrics are aggregated across all projects.
Project tags on items. Each triage item shows a small colored dot + project name so you can distinguish sources at a glance.
Nav links carry context. All sidebar links append ?project= when a project is active, preserving filter as you navigate between pages.
Settings pinned to bottom. API Keys + Settings separated by divider at the bottom of the sidebar for quick access.
Colored dot in switcher. The project's assigned color (blue) replaces the muted dot. Name shown in full foreground color.
Context banner. A subtle banner confirms which project is active. Includes a "View all projects" link to clear the filter. Uses the project's color for the dot.
Scoped metrics. All numbers reflect only the selected project. Users see the subset of data, not the aggregate.
No project tags needed. When a project is selected, the triage items don't need project tags since everything is already filtered to one project.
Command pattern. Search input at top (shown when 5+ projects). Uses shadcn/ui Command component with fuzzy matching on project names.
Org-grouped projects. Projects are grouped under the organization name ("Revoir Software"). Each has a color dot, type icon (Globe/Smartphone/Wrench), and truncated name.
Active highlight. The currently selected project (Revoir iOS) has a subtle coral tint background and a coral checkmark.
Create action. "Create Project" at the bottom navigates to /admin/settings/projects/new. Shown in muted text with a folder-plus icon.
No switcher, no separator. When projects.length === 0, the ProjectSwitcher component is not rendered and the separator below it is also hidden. Clean, no wasted space.
Backward compatible. This is the exact current behavior. Existing single-product orgs see no change. Multi-product support is purely additive.
No project tags on items. Without projects, triage items don't need project context — everything belongs to the single implicit project.
Layout unchanged. Admin layout passes projects={[]} (default). The sidebar and main content render exactly as they do today.
projects[], currentProjectId, organizationName
router.push()
searchParams.project in page server component
?project param on every nav link
| Source | Component | Props | Notes |
|---|---|---|---|
layout.tsx |
AdminLayout | Fetches projects from Supabase | Server component, queries organizations + future projects table |
| AdminLayout | Sidebar | projects, currentProjectId, organizationName |
All optional — defaults to empty array / null / "Organization" |
| Sidebar | ProjectSwitcher | projects, currentProjectId, organizationName |
Only rendered when projects.length > 0 |
| Sidebar | Nav Links | buildHref(path) appends ?project= |
Reads from useSearchParams or currentProjectId prop |
| URL | Page Components | searchParams.project |
Each page server component reads the project filter from URL |
| Key | English Value | Used By |
|---|---|---|
admin.projects.switchProject |
Switch project | ProjectSwitcher (aria-label) |
admin.projects.allProjects |
All Projects | ProjectSwitcher trigger + dropdown |
admin.projects.searchProjects |
Search projects... | CommandInput placeholder |
admin.projects.noProjects |
No projects found | CommandEmpty state |
admin.projects.createProject |
Create Project | Create action in dropdown |
admin.nav.apiKeys |
API Keys | Sidebar nav item |
admin.nav.helpDocs |
Help & Docs | Sidebar nav item (external link to /docs) |
common.poweredBy |
Powered by | Sidebar footer |
Add i18n keys to src/messages/en.json — admin.projects.* and admin.nav.apiKeys
Update admin layout — fetch org name from Supabase, pass to Sidebar. Projects array stays empty until projects table exists.
Create projects table (future) — migration adds projects table with organization_id FK, plus project_id column on submissions.
Wire layout to projects (future) — query projects table in layout, pass to Sidebar. Filter all page queries by project.