docs(14): UI design contract for /pax8 surface
Pre-populated entirely from existing DESIGN.md tokens plus Phase 14 CONTEXT.md/RESEARCH.md decisions — no new visual language introduced. Flags the required additive DetailModal.tsx extension for the cost breakdown drill-down. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LHRgZqkzBHBbAbc3KHneuR
This commit is contained in:
parent
cd33955417
commit
a71ca10f9b
1 changed files with 189 additions and 0 deletions
189
.planning/phases/14-pax8-ui-surface/14-UI-SPEC.md
Normal file
189
.planning/phases/14-pax8-ui-surface/14-UI-SPEC.md
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
---
|
||||
phase: 14
|
||||
slug: pax8-ui-surface
|
||||
status: draft
|
||||
shadcn_initialized: true
|
||||
preset: "style=new-york, baseColor=neutral, cssVariables=true, iconLibrary=lucide, rsc=true"
|
||||
created: 2026-07-11
|
||||
---
|
||||
|
||||
# Phase 14 — UI Design Contract
|
||||
|
||||
> Visual and interaction contract for frontend phases. Generated by gsd-ui-researcher, verified by gsd-ui-checker.
|
||||
|
||||
This phase adds a new top-level page (`/pax8`) inside an already-mature,
|
||||
already-tokenized design system (see `DESIGN.md`, `components.json`). No new
|
||||
visual language is being introduced — this contract pins down which existing
|
||||
tokens/components apply and resolves the phase-specific gaps CONTEXT.md left
|
||||
to discretion (tab labels, column set, copy). Nearly every field below is
|
||||
pre-populated from `DESIGN.md`, `14-CONTEXT.md` (D-01…D-09), and
|
||||
`14-RESEARCH.md` — no user questions were required for this phase.
|
||||
|
||||
---
|
||||
|
||||
## Design System
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Tool | shadcn (already initialized — `components.json` present) |
|
||||
| Preset | `style: new-york`, `baseColor: neutral`, `cssVariables: true`, `rsc: true`, `tsx: true`, prefix: none (source: `components.json`) |
|
||||
| Component library | Radix UI primitives (via shadcn), `components/ui/` |
|
||||
| Icon library | `lucide-react`, imported per-icon (source: `DESIGN.md` §6) |
|
||||
| Font | IBM Plex Sans (`--font-plex-sans`, weights 300/400/500/600/700) for UI text; IBM Plex Mono for numerics/IDs/timestamps only (source: `DESIGN.md` §2 Type) |
|
||||
|
||||
No shadcn init gate was needed — the project already has a locked preset.
|
||||
No new `npx shadcn add` components are required for this phase (see
|
||||
Component Inventory below) so the registry vetting gate does not apply.
|
||||
|
||||
---
|
||||
|
||||
## Spacing Scale
|
||||
|
||||
Declared values (must be multiples of 4) — matches the sitewide scale in
|
||||
`DESIGN.md` §3, applied as-is to `/pax8`:
|
||||
|
||||
| Token | Value | Usage |
|
||||
|-------|-------|-------|
|
||||
| xs | 4px | Icon-to-text gaps (`gap-1`), inline badge padding |
|
||||
| sm | 8px | Compact row spacing (`gap-2`), candidate-card internal gaps |
|
||||
| md | 16px | Default element spacing (`gap-4`), Card body padding |
|
||||
| lg | 24px | Page container padding (`container mx-auto px-6 py-6`), section gaps (`space-y-6`) |
|
||||
| xl | 32px | Layout gaps between major page regions (Companies tab ↔ Needs Review tab content) |
|
||||
| 2xl | 48px | Not used this phase |
|
||||
| 3xl | 64px | Not used this phase |
|
||||
|
||||
Exceptions: none. `/pax8` follows the standard page shell exactly
|
||||
(`PageHeader` + `container mx-auto px-6 py-6` + `space-y-6` between the tab
|
||||
bar, table/card sections) — same shell `device-link-conflicts` uses.
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
Matches the sitewide scale documented in `DESIGN.md` §2 (Type) and used
|
||||
throughout `DetailModal.tsx` / `device-link-conflicts/page.tsx`. This is a
|
||||
pre-existing, sitewide scale — not introduced for this phase — so it
|
||||
carries 3 weights (400/600/700) rather than the generic 2-weight target;
|
||||
noted as an accepted exception since it matches every other page in the app.
|
||||
|
||||
| Role | Size | Weight | Line Height |
|
||||
|------|------|--------|-------------|
|
||||
| Body | 14px (`text-sm`) | 400 (regular) | 1.5 |
|
||||
| Label | 12px (`text-xs`, uppercase, `tracking-wider`) | 600 (semibold) | 1.4 |
|
||||
| Heading | 18px (`text-lg`, Card/section titles) | 600 (semibold) | 1.3 |
|
||||
| Display | 24px (`text-2xl`, page H1 via `PageHeader`) | 700 (bold) | 1.2 |
|
||||
|
||||
Numerics (subscription counts, cost totals, seat counts) render in
|
||||
`font-mono` (IBM Plex Mono) with `tabular-nums`, matching `DetailModal`'s
|
||||
existing `.num`/hours-entry convention — never in the sans body font.
|
||||
|
||||
---
|
||||
|
||||
## Color
|
||||
|
||||
Matches `DESIGN.md` §2 (Colors, OKLch) exactly — no new hex values, no new
|
||||
tokens for this phase.
|
||||
|
||||
| Role | Value | Usage |
|
||||
|------|-------|-------|
|
||||
| Dominant (60%) | `--background` / `--card` (white light / near-black dark) | Page surface, table/card backgrounds |
|
||||
| Secondary (30%) | `--secondary` / `--muted` (`oklch(0.97 0 0)` light) | Table header row, DetailModal field-label column (`bg-muted/40`), "Details" inline badge row background |
|
||||
| Accent (10%) | `--primary` (Wulf blue `oklch(0.55 0.16 220)`) | Reserved for: the "Link company" resolve button, active `Tabs` indicator (Companies / Needs Review), input/select focus rings, and hyperlinks (matched-company name link, `ExternalLink` affordances) |
|
||||
| Destructive | `--destructive` (`oklch(0.577 0.245 27.325)`) | No destructive actions exist in this phase (read-only view + a non-destructive resolve mutation) — token reserved but unused; do not repurpose it for the "Needs Review" flag itself (that's an informational/warning state, not destructive) |
|
||||
|
||||
Accent reserved for (explicit list — nothing else may use `--primary`):
|
||||
1. "Link company" / "Resolve" action buttons in the Needs Review tab
|
||||
2. Active state of the Companies / Needs Review `Tabs` trigger
|
||||
3. Focus rings on `Input`/`Select`/`Command` (manual company search)
|
||||
4. Text links (matched Autotask company name, any external-link affordance)
|
||||
|
||||
Warning/attention color for the Needs Review section itself: amber
|
||||
(`border-amber-200` card border + `text-amber-500` `AlertTriangle` icon),
|
||||
matching `device-link-conflicts`'s existing convention exactly — this is a
|
||||
semantic status hue per `DESIGN.md`'s "status hues outside this set" rule,
|
||||
not the 10% accent.
|
||||
|
||||
---
|
||||
|
||||
## Copywriting Contract
|
||||
|
||||
| Element | Copy |
|
||||
|---------|------|
|
||||
| Primary CTA | **"Link company"** — button on each top-3 candidate row in the Needs Review tab (mirrors `device-link-conflicts`'s per-candidate resolve button); the manual-search fallback path's action button reads **"Link to selected company"** once a search result is chosen |
|
||||
| Empty state heading (Companies tab, zero synced companies) | "No PAX8 companies synced yet" |
|
||||
| Empty state body (Companies tab) | "Run the PAX8 sync from `/admin/integrations`, then refresh this page." |
|
||||
| Empty state heading (Needs Review tab, zero open reviews) | "No companies need review" |
|
||||
| Empty state body (Needs Review tab, zero open reviews) | "Every synced PAX8 company is matched to an Autotask company." (mirrors `device-link-conflicts`'s "No conflicts" `Alert` copy pattern exactly) |
|
||||
| Empty state — per-row, zero-candidate case (D-09) | Heading: **"No suggested matches"** · Body: **"Search manually to link this company to its Autotask counterpart."** — rendered inline within that review card, not a separate page/flow |
|
||||
| Error state | Heading: "Couldn't load PAX8 data" · Body: "Check your connection and try again, or visit `/admin/integrations` if PAX8 sync is disabled." (mirrors the existing `Alert variant="destructive"` / "Failed to load" pattern in `device-link-conflicts`) |
|
||||
| Destructive confirmation | None in this phase. There is no delete/write-back action — the resolve mutation only repoints `pax8_companies.autotask_company_id` and is correctable by re-resolving. Mirrors `device-link-conflicts`'s precedent of no confirmation dialog on resolve; a success toast (`sonner`, `toast.success`) reading **"Linked to {companyName}"** is sufficient feedback, matching the existing `"Linked to CI {ciId}"` pattern verbatim in shape |
|
||||
| Resolve failure toast | `toast.error(err.message ?? 'Resolve failed')` — matches `device-link-conflicts` exactly |
|
||||
| Tab labels | **"Companies"** and **"Needs Review"** (per D-04's own example labels); "Needs Review" carries a count badge showing the open-review total when > 0 (Claude's Discretion item, resolved: yes, show count — consistent with `device-link-conflicts`'s "N unresolved conflicts" line and with the existing count-badge pattern already used on `DetailModal`'s Time/Notes tab triggers) |
|
||||
|
||||
---
|
||||
|
||||
## Registry Safety
|
||||
|
||||
| Registry | Blocks Used | Safety Gate |
|
||||
|----------|-------------|--------------|
|
||||
| shadcn official | `Tabs`, `Dialog`/`DetailModal` (extended in place), `Card`, `Badge`, `Select`, `Alert`, `Skeleton`, `Button`, `Separator`, `Input` — all already vendored in `components/ui/`, zero new `npx shadcn add` installs required | not required |
|
||||
|
||||
No third-party registries declared or used. Registry vetting gate does not
|
||||
apply to this phase.
|
||||
|
||||
---
|
||||
|
||||
## Component Inventory & Implementation Notes
|
||||
|
||||
Everything needed is either already vendored (`components/ui/`) or already
|
||||
built (`components/admin/DataTable.tsx`) — the only build-vs-reuse decision
|
||||
this phase makes is an **additive extension to `DetailModal.tsx`**, flagged
|
||||
as Pitfall 1 in `14-RESEARCH.md` and repeated here because it directly
|
||||
governs whether D-02's "used as-is" framing produces a usable UI:
|
||||
|
||||
- **Companies tab** — `components/admin/DataTable.tsx` (D-01), columns:
|
||||
company name, matched Autotask company (or an "Unmatched" `Badge`), active
|
||||
subscription count, city/country (sortable). Row click opens the
|
||||
extended `DetailModal`.
|
||||
- **Cost-breakdown drill-down** — `components/admin/DetailModal.tsx`,
|
||||
**extended additively**, not passed data "as-is":
|
||||
1. Add an optional `kind?: 'ticket' | 'company' | 'pax8_company'` prop so
|
||||
`detectGroups()` stops guessing from ambiguous field-name sniffing
|
||||
(a PAX8 company object has `name`, not `company_name`).
|
||||
2. Add a `PAX8_COMPANY_GROUPS` field-group set (Identity: name, status,
|
||||
city/state/country; System: id, synced_at, is_deleted) reusing existing
|
||||
`FieldType`s (`bool`, `date`, `id` — no new scalar types needed).
|
||||
3. Add one new, unconditional formatted-tab section (not a `FieldGroup`)
|
||||
that renders the subscriptions/cost-breakdown array as a compact table:
|
||||
product name, quantity, billing term, latest billed amount
|
||||
(`line_total`, never recomputed from `unit_price × quantity` — Pitfall
|
||||
3), styled with the same rounded-border card look as every other
|
||||
formatted-tab group, plus a summed total row.
|
||||
4. Do not touch `TICKET_GROUPS`/`COMPANY_GROUPS` or their detection
|
||||
branches — this must be a pure addition, verified by reading the file
|
||||
in full (`components/admin/DetailModal.tsx` lines 41–132, 256–260).
|
||||
5. Raw tab: unchanged mechanism, fed the raw company + subscriptions +
|
||||
order-items payload.
|
||||
- **Needs Review tab** — new cards mirroring
|
||||
`app/admin/device-link-conflicts/page.tsx`'s existing list-with-resolve
|
||||
layout exactly (amber-bordered `Card`, candidate rows with confidence
|
||||
`Badge`, per-candidate "Link company" button) plus a manual-search
|
||||
fallback using shadcn `Command`/`Popover` over the existing
|
||||
`/api/data/companies-list` endpoint (D-05) — new UI, not vendored, but a
|
||||
direct structural copy of an existing page.
|
||||
- **Nav entry** — one new object in `components/navigation/app-navigation.tsx`'s
|
||||
`navigationItems` array (both desktop `NavigationMenu` and mobile `Sheet`
|
||||
consume the same array).
|
||||
|
||||
---
|
||||
|
||||
## 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:** pending
|
||||
Loading…
Add table
Add a link
Reference in a new issue