2. Staff console sidebar IA
Current rule (v2.0.1, 2026-11-13): The 264 px proposal in §2.2 is retained research geometry. Adopted brand E defaults are 256 px width / 20 px inset / 44 px items / 4 px gaps; keep the IA and responsive disclosure behaviour. The current item is teal, navigation is text-only and the user menu offers Light / Dark / System. (1A-N24) — supersedes the line above; see docs/current-rules.md#cr-sidebar-01.
2.0 App-wide navigation rules (all shells)
Section titled “2.0 App-wide navigation rules (all shells)”These apply to every screen, including the long-tail tabs and pages in ux-screens-longtail.md:
- One navigation surface per shell. Staff and member areas use
AppShell(L1) with its sidebar; public pages use thePublicLayoutheader nav (only links for modules that are on); auth, centred-card and kiosk screens have no navigation beyond the logo and one escape link. - Navigation is data, not markup. Each shell’s nav is built from an existing gating source
(
moduleAllowedTabsfor staff,memberTabs(modules)for members, bootstrapmodulesfor public) plus a presentation map. No screen adds its own nav, and no layout contains gating. - Pages never repeat the sidebar. In-page tabs (P3/P8) are for views of one object or filters of one list. A new top-level destination goes into the sidebar map, never into tabs.
- Sub-items appear only under the active parent (Settings family, machine types, and in the member shell, none).
- ≤5 destinations means bottom tabs on mobile (handout-only console, member area with “More” as the fifth), otherwise a drawer.
- Counts in navigation are limited to unread/needs-action badges (§2.3 rule 7). Everything else is counted on its page.
- Active state comes from the route, with
aria-current="page", and the active item is always visible (auto-expand/scroll). - Labels are nouns for places (“Requests”, “Items”, “Payments”) and verbs only for actions
inside pages. The visible nav label may differ from the page
h1, but theh1must contain the nav label’s noun.
2.1 What exists
Section titled “2.1 What exists”ALL_TABS in frontend/src/features/staff/staffAccess.ts holds 42 keys (the brief said 41;
platform-dashboard and usage are the two most often forgotten). TAB_GROUPS sorts them into
9 groups shown as popovers in the floating StaffDock. Problems:
- “Operate” mixes the front desk (requests, direct, handover), money (payments), stock movement (ledger, transfers, stocktake) and procurement (tobuy).
- “Admin” holds 12 items of very different frequency.
- The three cross-venue tabs (organized, organization-analytics, organizations) are split across three groups even though they share one property: all are hidden in single-tenant mode and scoped beyond the selected makerspace.
Unchanged: keys, TAB_PATHS, staffTabPath, getStaffAccess, filterTabsByEnabledModules,
moduleAllowedTabs, the deny/redirect logic, aria-label="Staff sections",
aria-current="page", and the unread count in the accessible name. The sidebar is a pure
function of moduleAllowedTabs, a new SIDEBAR_SECTIONS presentation map, TAB_LABELS (or a
new SIDEBAR_LABELS override map) and staffTabPath. TAB_GROUPS stays for CommandPalette /
SetupChecklist until those are moved to the new map in the same commit.
2.2 Proposed sections (desktop ≥1024, 264 px sidebar)
Section titled “2.2 Proposed sections (desktop ≥1024, 264 px sidebar)”Every one of the 42 keys appears exactly once. Bold = always visible within its section; italic = inside that section’s “More” disclosure (see the collapse rules in 2.3). Labels in quotes are proposed display labels; the key and URL do not change.
| # | Section header | Items (key → label) | Notes |
|---|---|---|---|
| 0 | (no header) | dashboard → “Home”, notifications → “Inbox” (unread badge) | The “Home” pattern. The unread count stays in the accessible name (“Inbox, 3 unread”). |
| 1 | Front desk | requests → “Requests” (count badge: pending + ready), direct → “Direct handout”, handover → “Job pickups”, payments → “Payments” | The daily loop. Payments sits here because record_payment is a handout action and front-desk staff take money at the desk. |
| 2 | Inventory | inventory → “Items”, needsfix → “Repair shelf”, stocktake → “Stocktake”, transfers → “Transfers”, tobuy → “To buy”; categories → “Categories”, containers → “Containers”, ledger → “Stock ledger”, warranty → “Warranties”, qr → “QR tools”, scanner → “Scanner”, bulk → “Bulk import” | Page h1 stays “Inventory” (the test pin is the panel heading, not the nav label). “Ledger” gains “Stock” so it is not mistaken for money. |
| 3 | Machines | machines → “Machines” + dynamic sub-items per machine type when active, devices → “Devices” | Sub-items come from the machine-type list already fetched by MachinesPanel, linked with staffTabPath("machines", …, machineTypeSegment(type)). This uses the existing TABS_WITH_SUBPATHS route, so it is the “Settings › General” sub-nav pattern on an existing route. |
| 4 | Community | members → “Members”, events → “Events”, bookings → “Bookings”, cards → “Member cards” | |
| 5 | Insights | reports → “Reports”, audit → “Audit log”, accountability → “Accountability” | |
| 6 | Organizations | organizations → “Organizations”, organized → “Organized events”, organization-analytics → “Org analytics” | All three are cross-venue. The section shows a muted caption “Across venues” so the user knows the selected makerspace does not scope it. In single-tenant mode all three are hidden by gating, so the section disappears. For a governance-only actor this is the only section (see 2.5). |
| — | (bottom block, above the user chip) | settings → “Settings” with sub-items when any Settings-family tab is active: settings → “General”, users → “Staff & roles”, emailtemplates → “Email templates”, email-logs → “Delivery log”, api → “API access”, exports → “Data export”, backups → “Backup & restore”, usage → “Usage & limits”, modules → “Modules”, migration → “Tenant migration” | The Settings › General / API access sub-nav (R5, R6). “Admin” becomes one item that expands in place. If settings itself is not allowed but a child is (e.g. a role with only edit_inventory sees Email templates), the parent link goes to the first allowed child. |
| — | Platform (bottom, superadmin only) | platform-dashboard → “Platform dashboard”, platform → “Platform settings” | Rendered under a hairline with a muted “Platform” label so it is visibly a different scope. |
Item order inside a section is by frequency of use, not alphabetical.
2.3 Collapse and disclosure rules
Section titled “2.3 Collapse and disclosure rules”- A section header renders only if at least one of its items is allowed.
- A section with 4 or fewer visible items renders flat with no “More”. Most narrow roles hit this, e.g. a printing-only actor sees just “Warranties” in Inventory, flat.
- A section with more than 4 visible items shows its bold items and a “More (n)” row
(14 muted, chevron). Expanding is per-section and remembered per user in
localStorage(wrapped in try/catch, a convenience only). A section auto-expands when it contains the active tab, so the active item is never hidden (this matches the unmounted-collapsed-content accessibility rule in PLAN §5). - Settings sub-items show only while a Settings-family tab is active (sub-nav rule); otherwise Settings is one row.
- Machine-type sub-items show only while on
machines, at most 8, then “All types →”. - The sidebar body scrolls independently (
overflow-y:auto) between the switcher and the bottom block. The active item is scrolled into view on route change. - Counts in the sidebar are limited to two: the Inbox unread count (existing) and the Requests “needs action” count (pending + ready-for-handover, from the already-cached dashboard query when the actor may read it; otherwise no badge). Badge: an 18 px tall pill, 12 semibold, neutral tint. It becomes the accent tint when greater than 0 and is never red. Everything else gets its counts on its own page (P4 and P8).
2.4 Top and bottom of the sidebar
Section titled “2.4 Top and bottom of the sidebar”- Top: logo (horizontal lockup), then the workspace switcher card (makerspace tile, name,
the role name from
activeRoleNamesuch as “Space Manager” or “Super Admin”, and a chevron). The menu lists other makerspaces (searchable when there are more than 6), “Switch makerspace” for superadmin (today’sonSwitchMakerspace), and “View public page ↗”. - Under the switcher: a full-width search trigger styled as an input (“Search or jump to…
Ctrl K”). This is the
CommandPalettetrigger and keeps its accessible name “Search and commands” and its shortcut. - Bottom block: “View public page ↗” (repeated for one-click access), then the user chip
(initials, username, role). Its menu holds: “Account & two-step sign-in” (→
/mfa), Theme (Light / Dark / System segmented), and “Sign out” as a real<button>(test pin). JobTray(background jobs) attaches to the bottom block as a compact progress row when jobs are running, not as a floating tray.
2.5 Narrow consoles
Section titled “2.5 Narrow consoles”Handout-only (front desk, handoutOnly === true). The allowed tabs are at most
requests, direct, handover, payments, notifications, all filtered further.
- No section headers. A flat list of 5 or fewer items in the order Requests, Direct
handout, Job pickups, Payments, Inbox. No Home (they have no dashboard;
defaultTabis Requests). - The switcher card subtitle shows the role (“Front desk”) and the base path stays
/guest-admin. - Below 1024 px a bottom tab bar replaces the drawer (5 or fewer items fit exactly: 64 tall, icon plus 12 px label, 44 px minimum targets, a safe-area inset). Front-desk staff work on tablets and phones at the counter, so one tap beats opening a drawer. The user chip moves into a top-bar avatar menu.
- The Requests page opens on the Ready tab for this actor (they cannot review, and the
pending query is already disabled for
guestOnly). - Optional, product decision: a persistent “Scan box” button in the top bar that opens the QR scanner and jumps to the matching ready request. This needs a box-to-request lookup and is a behaviour addition, so it is out of scope unless approved.
Governance-only (allowedTabs = ["organizations"]): sidebar = Organizations only, and no
switcher menu entries other than “View public page” (there is no makerspace scope). The page uses
the home pattern R1 (“Your organizations” cards).
Machine-only (isMachineOnly): typically Home, Inbox, Machines (with type sub-items),
Warranties, Reports, Email templates. Rule 2 keeps it flat. The machine-type sub-items become the
main navigation, which is exactly what the scoped operator needs.
2.6 Below 1024 px (non-handout)
Section titled “2.6 Below 1024 px (non-handout)”A slim top bar (56 tall): hamburger (opens the sidebar as an off-canvas drawer, focus-trapped, Esc closes), the makerspace name, a search icon (palette), and the user avatar. The page title lives in the page, not the bar. The drawer reuses the same sidebar component.
Part of Space Works UX spec, published from docs/ux-spec.md. Edit it there.