From 096cb5a1dde94fe481bd9792655c27178f3be08d Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sun, 19 Apr 2026 21:57:19 +0200 Subject: [PATCH] docs(38): add UI design contract for admin tag management Co-Authored-By: Claude Sonnet 4.6 --- .../38-admin-tag-management/38-UI-SPEC.md | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 .planning/phases/38-admin-tag-management/38-UI-SPEC.md diff --git a/.planning/phases/38-admin-tag-management/38-UI-SPEC.md b/.planning/phases/38-admin-tag-management/38-UI-SPEC.md new file mode 100644 index 0000000..b49afa8 --- /dev/null +++ b/.planning/phases/38-admin-tag-management/38-UI-SPEC.md @@ -0,0 +1,258 @@ +--- +phase: 38 +slug: admin-tag-management +status: draft +shadcn_initialized: false +preset: none +created: 2026-04-19 +--- + +# Phase 38 — UI Design Contract + +> Visual and interaction contract for Phase 38: Admin Tag Management. +> Generated by gsd-ui-researcher. All values extracted from existing codebase — no user questions required. + +--- + +## Design System + +| Property | Value | +|----------|-------| +| Tool | none — hand-crafted Tailwind v4 | +| Preset | not applicable | +| Component library | none (custom components only) | +| Icon library | Lucide via `LucideIcon` from `src/client/lib/iconData` | +| Font | system default (no custom font declared in app.css) | + +Source: `src/client/app.css` (single `@import "tailwindcss"` — no preset), existing admin routes. + +--- + +## Spacing Scale + +Declared values (multiples of 4): + +| Token | Value | Usage | +|-------|-------|-------| +| xs | 4px | Icon gaps (`gap-1`), inline badge padding (`px-1.5 py-0.5`) | +| sm | 8px | Tag chip gaps (`gap-2`), compact row padding (`py-2`) | +| md | 16px | Default field spacing (`mt-4`), grid gaps (`gap-4`) | +| lg | 24px | Section padding (`p-6`), page section breaks (`pt-6`) | +| xl | 32px | Form bottom margin (`mt-8`) | +| 2xl | 48px | Not used in admin shell | +| 3xl | 64px | Not used in admin shell | + +Exceptions: +- Tree row indent: 20px per depth level (value between `sm` and `md` — closest Tailwind class: `pl-5` per level). Source: CONTEXT.md specifics, "16-24px per level". +- Chevron toggle button: 28px click target minimum (`p-1` around 16px icon = 24px; acceptable for desktop-only admin tool). + +--- + +## Typography + +| Role | Size | Weight | Line Height | +|------|------|--------|-------------| +| Body | 14px (text-sm) | 400 (regular) | 1.5 | +| Label | 12px (text-xs) | 600 (semibold) | 1.4 | +| Heading | 18px (text-lg) | 600 (semibold) | 1.2 | +| Display | 12px (text-xs) | 600 (semibold, uppercase, tracking-wide) | 1.4 | + +Source: `src/client/routes/admin/items.tsx` — `text-lg font-semibold text-gray-900`, `text-xs font-semibold text-gray-400 uppercase tracking-wide`, `text-sm`. + +Display role = table column headers (uppercase, tracked, muted gray-400). Not a separate heading level. + +--- + +## Color + +| Role | Value | Usage | +|------|-------|-------| +| Dominant (60%) | `#f9fafb` (gray-50) | Main content area background (`bg-gray-50`) | +| Secondary (30%) | `#ffffff` (white) | Cards, sidebar, modal dialogs, form containers (`bg-white`) | +| Accent (10%) | `#2563eb` (blue-600) | Primary save/submit buttons, active input ring | +| Destructive | `#dc2626` (red-600) | Delete button background, delete confirmation button only | + +Accent reserved for: +- "Save Changes" / "Add Tag" primary submit button (`bg-blue-600 hover:bg-blue-700 text-white`) +- Input focus ring (`focus:ring-2 focus:ring-blue-500/20 focus:border-blue-300`) +- Active tag filter chip in existing items list (`bg-blue-50 text-blue-600 border border-blue-200`) + +Destructive reserved for: +- Delete button on edit page (`border-red-200 text-red-600 hover:bg-red-50`) +- Confirm delete button inside modal (`bg-red-600 hover:bg-red-700 text-white`) + +All other interactive elements (back links, chevron toggles, row hovers) use gray-scale only. + +Source: `src/client/routes/admin/items.$itemId.tsx` — extracted exact Tailwind classes. + +--- + +## Component Inventory + +### Existing (reuse without modification) + +| Component | File | Usage in Phase 38 | +|-----------|------|-------------------| +| Admin shell layout | `src/client/routes/admin.tsx` | Unchanged — enable Tags sidebar Link | +| Tags sidebar Link | `src/client/routes/admin.tsx` L43-52 | Change disabled `
` → `` | +| Input class pattern | `items.$itemId.tsx` L177 | Reuse `inputClass` constant verbatim | +| Label class pattern | `items.$itemId.tsx` L178 | Reuse `labelClass` constant verbatim | +| Section divider class | `items.$itemId.tsx` L179 | Reuse `sectionClass` constant verbatim | +| Delete confirmation modal | `items.$itemId.tsx` L397-432 | Extend with child count copy; same structural pattern | +| Back navigation button | `items.$itemId.tsx` L212-218 | Replicate with "← Tags" label | +| Skeleton loader rows | `items.tsx` L139-148 | Reuse `animate-pulse` skeleton pattern | +| Error state | `items.tsx` L105-109 | Replicate for tag list error state | + +### New (build for Phase 38) + +| Component | Description | Visual Spec | +|-----------|-------------|-------------| +| `TagTreeRow` | Single row in the collapsible tree | See Tree Row spec below | +| `TagTreeView` | Container rendering all tree rows from flat list | Renders inside `rounded-xl border border-gray-100 bg-white` card | +| `TagQuickAddForm` | Inline form above the tree | Name input + parent picker + "Add Tag" button | +| `TagParentPicker` | Searchable dropdown for parent selection | Reuse `inputClass` for trigger; popover with filtered options | + +--- + +## Tree Row Visual Spec + +Each row in the collapsible tag tree: + +``` +[ chevron ] [ tag name ] [ item count ] [ Edit ] + indent per level +``` + +### Structure + +- Indent: `pl-5` (20px) per depth level starting at depth 1. Root tags (depth 0) have no indent. +- Chevron: `LucideIcon name="chevron-right"` (collapsed) / `LucideIcon name="chevron-down"` (expanded), `size={14}`, color `text-gray-400`. Click area: `p-1 rounded hover:bg-gray-100`. +- Leaf nodes (no children): no chevron rendered; indent pad replaced with `w-6` spacer to align name column. +- Row hover: `hover:bg-gray-50 transition-colors` on the full row `
`. +- Tag name: `text-sm font-medium text-gray-900`. +- Item count: `text-sm text-gray-400` — e.g. "12 items" or "0 items". +- Edit action: `text-xs text-gray-400 hover:text-gray-600` link-style button → navigates to `/admin/tags/$tagId`. +- Row height: `py-2.5` (10px top/bottom) giving ~36px per row. + +### Tree Expand/Collapse + +- State: local `useState>` of expanded tag IDs. All parent IDs populated on mount (start expanded per D-03). +- Toggle: clicking chevron button calls `toggle(tagId)` — no row-click collapse (chevron-only per Claude's discretion). +- Hidden children: `display: none` equivalent — conditionally render child rows when parent ID is in expanded set. + +### Search / Filter Behavior (D-05) + +- Non-matching leaf rows: not rendered. +- Parent rows with matching children: rendered even if parent name does not match. +- Parent rows whose name matches: rendered with all their children visible (show context). +- Search input: `w-64 rounded-lg border border-gray-200 px-3 py-2 text-sm` — identical to items list. + +--- + +## Quick-Add Form Spec (D-07) + +Position: above the tree view card, below the page header. + +``` +[ Name input (flex-1) ] [ Parent picker (w-48) ] [ Add Tag button ] +``` + +- Name input: `inputClass` styling, placeholder "Tag name...". +- Parent picker: native `` with `labelClass` + `inputClass`; options exclude the tag itself and all its descendants (cycle prevention per D-11) +4. Actions row: `flex items-center justify-between mt-8 pt-6 border-t border-gray-100` + - Left: "Delete Tag" destructive button + - Right: "Save Changes" primary button + +Parent picker "no parent" option label: "No parent (top-level)" as first `