Files
GearBox/.planning/phases/37-admin-global-item-management/37-UI-SPEC.md

13 KiB
Raw Blame History

phase, slug, status, shadcn_initialized, preset, created, reviewed_at
phase slug status shadcn_initialized preset created reviewed_at
37 admin-global-item-management approved false none 2026-04-19 2026-04-19

Phase 37 — UI Design Contract

Visual and interaction contract for Admin — Global Item Management. Generated from CONTEXT.md decisions, RESEARCH.md findings, and codebase pattern analysis.


Design System

Property Value
Tool none — Tailwind CSS v4 utility-first
Preset not applicable
Component library none (custom components)
Icon library LucideIcon from src/client/lib/iconData
Font System default (Inter-like sans-serif via browser)

No components.json detected. No shadcn. All styling is plain Tailwind utility classes matching the app's existing light/airy aesthetic.


Spacing Scale

Standard 8-point scale from existing app patterns:

Token Value Usage
xs 4px (gap-1, p-1) Icon gaps, inline chip gaps
sm 8px (gap-2, p-2) Compact table cell padding, tag chips
md 16px (gap-4, p-4) Default form field spacing, sidebar padding
lg 24px (gap-6, p-6) Section padding, edit page padding
xl 32px (gap-8, p-8) Page-level layout gaps
2xl 48px Major section breaks (used sparingly)
3xl 64px Not used in admin panel

Exceptions:

  • Table row height: py-3 (12px vertical) + px-4 (16px horizontal) for dense data rows
  • Sidebar nav items: px-3 py-2 (12px vertical) — matches Phase 36 admin shell

Typography

Matches existing app type scale. No new sizes introduced.

Role Size Weight Line Height Tailwind
Body / table cell 14px 400 1.5 text-sm
Label / column header 12px 600 1.4 text-xs font-semibold
Form field label 14px 600 1.4 text-sm font-semibold
Page heading 18px 600 1.3 text-lg font-semibold

Weights used: 400 (regular) + 600 (semibold). No medium (500), no bold (700).


Color

60/30/10 rule — matches Phase 36 admin shell exactly:

Role Value Tailwind Usage
Dominant (60%) #ffffff bg-white Page backgrounds, table rows, edit form
Secondary (30%) #f9fafb bg-gray-50 Main content area, input backgrounds
Border / divider #f3f4f6 border-gray-100 Table row dividers, sidebar border
Text primary #111827 text-gray-900 Item names, column data
Text secondary #4b5563 text-gray-600 Labels, form hints
Text muted #9ca3af text-gray-400 Column headers, disabled states
Accent (10%) #2563eb text-blue-600, bg-blue-50 Save button, active nav link indicator
Destructive #dc2626 bg-red-600 hover:bg-red-700 Delete button ONLY

Accent reserved for:

  • Save/Update primary button
  • Active sidebar nav link highlight (left border or background)

Destructive reserved for:

  • Delete button in edit page
  • Confirm delete button inside the delete dialog

Component Inventory

Admin Items List (/admin/items)

Layout:

┌─────────────────────────────────────────────────┐
│ Heading: "Catalog Items"      [search input]    │
│ Tag filter chips                                 │
│ "1,247 items" count label                        │
├──────┬────────────┬─────┬───────┬───────┬───────┤
│ Brand+Model │ Category │ Wt │ Price │ Tags │ Owners│
├──────┴────────────┴─────┴───────┴───────┴───────┤
│ row row row ... (50 per page)                    │
│ [sentinel div — triggers next page]              │
└─────────────────────────────────────────────────┘

Table element specs:

  • Wrapper: w-full overflow-hidden rounded-xl border border-gray-100 bg-white
  • <table>: w-full text-sm
  • <thead>: bg-gray-50 border-b border-gray-100
  • Column header cells: px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide
  • <tbody> rows: border-b border-gray-50 hover:bg-gray-50 cursor-pointer transition-colors
  • Body cells: px-4 py-3 text-sm text-gray-700
  • Brand+Model cell: bold brand (font-medium text-gray-900) + muted model (text-gray-500)

Tags column:

  • ≤ 2 tags: render as inline chips — text-xs bg-gray-100 text-gray-500 px-2 py-0.5 rounded-full
  • 2 tags: render count badge — text-xs bg-gray-100 text-gray-500 px-2 py-0.5 rounded-full displaying "+N"

Owner count column:

  • Numeric, right-aligned, text-gray-500
  • 0 owners: text-gray-300

Search input:

  • rounded-lg border border-gray-200 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-300
  • Placeholder: "Search catalog..."
  • Width: w-64

Tag filter:

  • Row of clickable chips below search bar
  • Selected: bg-blue-50 text-blue-600 border border-blue-200
  • Unselected: bg-gray-100 text-gray-600
  • Multi-select allowed (matches existing CatalogSearchOverlay pattern)

Infinite scroll sentinel:

  • <div ref={sentinelRef} className="h-4" /> at bottom of table
  • IntersectionObserver triggers fetchNextPage() when sentinel enters viewport
  • Loading indicator: <div className="py-4 text-center text-sm text-gray-400">Loading...</div>

Admin Items Edit Page (/admin/items/$itemId)

Layout:

← Items                        (back link)

Salsa Woodsmoke 700            (page heading: brand + model)
3 users in collection          (owner count subtext)

┌──────────────────────────────────────────────────┐
│  [Image preview — GearImage]                     │
│  [Image URL input]                               │
├──────────────────────────────────────────────────┤
│  Brand (Manufacturer)    [dropdown]              │
│  Model                   [text input]            │
│  Category                [text input]            │
├──────────────────────────────────────────────────┤
│  Weight (g)              [number input]          │
│  Price (€)               [number input]          │
├──────────────────────────────────────────────────┤
│  Tags                    [chip input]            │
│  Description             [textarea]              │
│  Source URL              [url input]             │
│  Image Credit            [text input]            │
│  Image Source URL        [url input]             │
├──────────────────────────────────────────────────┤
│                    [Save Changes]   [Delete Item]│
└──────────────────────────────────────────────────┘

Form wrapper: max-w-2xl mx-auto (consistent with catalog detail page width)

Field groups: separated by border-t border-gray-100 pt-6 mt-6

Input styles (all fields):

  • Text/number/url: w-full rounded-lg border border-gray-200 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-300
  • Textarea: same + min-h-[80px] resize-y
  • Manufacturer dropdown: <select> styled same as inputs, appearance-none bg-white
  • Label: block text-sm font-medium text-gray-700 mb-1

Tags chip input:

  • Active chips: inline-flex items-center gap-1 bg-gray-100 text-gray-700 text-xs px-2 py-1 rounded-full
  • Remove button per chip: × button, text-gray-400 hover:text-gray-600
  • Input: inline text input at end of chip row, presses Enter or comma to add
  • Wrapper: flex flex-wrap gap-2 rounded-lg border border-gray-200 px-3 py-2 min-h-[40px]

Save button:

  • px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium transition-colors disabled:opacity-50
  • Label: "Save Changes"

Delete button:

  • px-4 py-2 rounded-lg border border-red-200 text-red-600 hover:bg-red-50 text-sm font-medium transition-colors
  • Positioned at far right of action row (space-between layout)
  • Label: "Delete Item"

Back link:

  • text-sm text-gray-400 hover:text-gray-600 transition-colors
  • Text: "← Items"
  • Navigates to /admin/items

Page heading:

  • text-lg font-semibold text-gray-900 — "{Brand} {Model}"
  • Subtext: text-sm text-gray-400 mt-0.5 — "{N} users in collection" (uses ownerCount from API)

Delete Confirmation Dialog

Inline state-driven modal (not UIStore — local useState):

┌──────────────────────────────────────────────┐
│  Delete {Brand} {Model}?                     │
│                                              │
│  {N} users have this item in their           │
│  collection. This cannot be undone.          │
│                                              │
│              [Cancel]  [Delete]              │
└──────────────────────────────────────────────┘
  • Modal backdrop: fixed inset-0 z-50 flex items-center justify-center + absolute inset-0 bg-black/30
  • Dialog box: relative bg-white rounded-xl shadow-lg p-6 max-w-sm mx-4 w-full (matches ConfirmDialog)
  • Heading: text-lg font-semibold text-gray-900 mb-2
  • Body: text-sm text-gray-600 mb-6
  • Cancel: px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg
  • Confirm delete: px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-lg

Replace the disabled <div> with an active <Link>:

<Link
  to="/admin/items"
  activeProps={{ className: "bg-gray-100 text-gray-900 font-medium" }}
  inactiveProps={{ className: "text-gray-600 hover:bg-gray-50" }}
  className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors"
>
  <LucideIcon name="package" size={16} />
  <span>Items</span>
</Link>

Remove the "Soon" badge and cursor-not-allowed class.


Copywriting Contract

Element Copy
Page heading (list) "Catalog Items"
Item count label "{N} items" (e.g. "1,247 items")
Search placeholder "Search catalog..."
Empty state heading "No items found"
Empty state body "Try a different search or clear your filters."
Edit page — 0 owners "Not in any collection"
Edit page — 1 owner "1 user in collection"
Edit page — N owners "{N} users in collection"
Save CTA "Save Changes"
Delete CTA (edit page) "Delete Item"
Delete dialog heading "Delete {Brand} {Model}?"
Delete dialog body (0 owners) "This item is not in any collection. This cannot be undone."
Delete dialog body (N owners) "{N} users have this item in their collection. This cannot be undone."
Delete dialog confirm "Delete"
Delete dialog cancel "Cancel"
Back link "← Items"
Loading more (infinite scroll) "Loading..."
All items loaded "All {N} items loaded"
Error state "Failed to load catalog items. Please try again."
Save success (no toast — form stays; navigation optional)

States to Handle

Component States
Items list Loading (skeleton rows), Loaded, Empty, Error, Loading next page
Edit page Loading (skeleton form), Loaded, Saving, Save success, Save error
Delete dialog Closed, Open, Deleting, Delete error
Manufacturer dropdown Loading manufacturers, Loaded

Loading skeleton for table rows:

4-6 rows with `<div className="h-4 bg-gray-100 rounded animate-pulse" />` in each cell

Save/delete pending state:

  • Button disabled + opacity-50
  • No spinner needed — button text stays static

Registry Safety

Registry Blocks Used Safety Gate
None — no shadcn not applicable

No third-party component registries used. All UI is plain Tailwind + custom components.


Checker Sign-Off

  • Dimension 1 Copywriting: PASS
  • Dimension 2 Visuals: PASS
  • Dimension 3 Color: PASS
  • Dimension 4 Typography: PASS
  • Dimension 5 Spacing: PASS
  • Dimension 6 Registry Safety: PASS

Approval: approved 2026-04-19