>_devkit
gitlab-admin-ui
skills/gitlab-admin-ui/

references/shell.md

The admin shell

Read after SKILL.md. The shell is the single template every route renders inside, so it is where the app stops looking like a collection of pages.

Anatomy

┌─ TopBar 48px ──────────────── logo · breadcrumb ····· ⌘K · bell · avatar ─┐
├──────────┬───────────────────────────────────────────────┬───────────────┤
│ Sidebar  │  content area (the only scrolling region)     │  Inspector    │
│ 240px    │                                               │  320px        │
│ collapse │                                               │  (optional,   │
│  → 56px  │                                               │   tabbed)     │
├──────────┴───────────────────────────────────────────────┴───────────────┤
└─ StatusBar 28px ── branch · sync · selection · "Press ? for shortcuts" ───┘

All four dimensions are tokens (--topbar-height, --sidebar-width, --sidebar-collapsed, --inspector-width, --statusbar-height), so the shell never hard-codes a pixel that a component also needs to know.

body { overflow: hidden }. The content area is the only scroll container. Skipping this gives you a page scrollbar and a panel scrollbar, which is the single most recognisable sign of an admin UI that was assembled rather than designed.

Template signature

interface AdminShellProps {
  breadcrumb: { label: string; href?: string }[];
  /** Optional override for the bottom status bar. */
  statusBar?: ReactNode;
  /** Page-specific palette items appended to the global ones. */
  paletteItems?: PaletteItem[];
  children: ReactNode;
}

A page passes a breadcrumb and its content. Everything else - nav, palette, shortcuts, toasts, auth gating - is the shell's problem. Keep this surface small; every prop added here is a prop every page has to think about.

Auth belongs in the shell too, wrapping the whole tree:

export function AdminShell(props: AdminShellProps) {
  return (
    <RequireAdmin>
      <AdminShellInner {...props} />
    </RequireAdmin>
  );
}

That way no page can forget to check, and there is exactly one place to audit.

Sidebar

Sectioned nav, not a flat list - group by workflow (Overview / Repository / Operations / Admin). Collapses to a 56px icon rail. Items carry an optional count badge for pending work, fed from one hook rather than each item fetching its own.

Active item: brand-orange 2px left border plus bg-brand-soft. Not a filled orange row - see rule 3 in SKILL.md.

Command palette

⌘K. This is the feature that makes the whole thing feel like a tool rather than a website, and it is worth building early.

  • Results grouped by kind (Files / Folders / Commands), not one flat list
  • Fully keyboard driven: arrows, enter, escape
  • Selections actually navigate - a palette that only filters is theatre
  • The shell provides global commands; each page appends its own via

paletteItems

Global commands read as "Go to Vehicles", "New auction" - verb-first, matching what someone would type.

Keyboard map

Document every binding in a ? overlay, grouped. If a shortcut is not in the overlay, it does not exist as far as users are concerned.

KeyAction
⌘KCommand palette
?Shortcut overlay
1 / 2List view / grid view
EscClose palette, overlay, or context menu

Chord shortcuts (G then F) are a small state machine and a nice touch, but document them before you implement them - the overlay is the contract.

Interaction inventory

What a complete shell handles, roughly in order of value:

  • Toasts - bottom-right stack, coloured left border by kind, auto-dismiss.

Every destructive or async action fires one.

  • Context menu on right-click - Open / Rename / Move / Duplicate /

Copy path / Download / Delete.

  • Status bar - current branch, sync state, selection count, latency, and a

standing Press ? for shortcuts hint.

  • Drag and drop, in two flavours that people conflate:
  • OS files into the window - full-area dashed-orange overlay with

"Drop to upload to /\<folder\>".

  • Row onto a folder row - target folder gets a dashed orange outline,

source rows go half-transparent.

Wire the visual layer first and have drops fire toasts. That is genuinely useful for design review, but be honest in the code that the backend is not connected - a drop that silently does nothing reads as a bug.

Deliberately skipped

Worth knowing so you do not burn a day on them:

  • Drag-out to desktop (HTML5 drag-out to download) - browser-quirky and

rarely worth speculative work.

  • Multi-drag chip ("4 items" near the cursor) - possible with custom drag

images, purely nice-to-have.

  • Skeleton rows and empty-state illustrations - add when there is real async

loading to cover, not before.