Files
GearBox/.planning/phases/35-bug-fixes/35-UI-SPEC.md
Jean-Luc Makiola d216c80892 docs(35): fix UI-SPEC typography and spacing checker violations
Collapse font weights from 3 to 2 (remove 500/medium, map Label to 600/semibold).
Remove non-multiple-of-4 Tailwind class references from spacing Usage column.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 19:24:57 +02:00

8.3 KiB

phase, slug, status, shadcn_initialized, preset, created
phase slug status shadcn_initialized preset created
35 bug-fixes draft false none 2026-04-19

Phase 35 — UI Design Contract

Visual and interaction contract for Phase 35: bug-fixes. Generated by gsd-ui-researcher, verified by gsd-ui-checker.

Phase scope: No new UI. All 5 fixes restore or polish existing interactions. The contract enforces consistency with patterns already established in the codebase — executor must not introduce new design tokens or visual patterns.


Design System

Property Value
Tool none (no shadcn — Tailwind v4 direct)
Preset not applicable
Component library none (custom components in src/client/components/)
Icon library Lucide (curated subset via src/client/lib/iconData.ts)
Font system-ui (browser default, no custom font declared)

Source: src/client/app.css (@import "tailwindcss" — no additional config), CONTEXT.md code_context, codebase scan.


Spacing Scale

Declared values (must be multiples of 4). This phase does not introduce any new spacing — all values are the existing codebase standard.

Token Value Usage
xs 4px Icon gaps, inline padding
sm 8px Compact element spacing, badge rows
md 16px Default card content padding (p-4)
lg 24px Section padding, modal padding (p-6)
xl 32px Layout gaps
2xl 48px Major section breaks
3xl 64px Page-level spacing

Exceptions: none for this phase.

Source: Codebase scan of ItemCard.tsx, GlobalItemCard.tsx, CandidateCard.tsx.


Typography

Matches existing codebase usage — no new type styles introduced in this phase.

Role Size Weight Line Height
Body 14px (text-sm) 400 (normal) 1.5
Label 12px (text-xs) 600 (semibold) 1.5
Heading 14px (text-sm) 600 (semibold) 1.2
Display 20px (text-xl) 600 (semibold) 1.2

Two weights only: 400 (normal) and 600 (semibold). Label badges (text-xs font-medium) are documented as 600 (semibold) since this phase does not change them.

Source: Codebase scan of ItemCard.tsx (text-sm font-semibold text-gray-900), GlobalItemCard.tsx (text-xs font-medium text-gray-400), login.tsx (text-xl font-semibold).


Color

No new colors introduced. All values are the existing Tailwind gray/blue/green palette already used across card components.

Role Value Usage
Dominant (60%) gray-50 (#f9fafb) Page backgrounds, fallback image areas
Secondary (30%) white + gray-100 border Cards (bg-white rounded-xl border border-gray-100)
Accent (10%) blue-50/blue-400 Weight badges only
Destructive red-100/red-500 Remove-from-setup hover only (existing ItemCard remove button)

Accent reserved for: weight value badges (bg-blue-50 text-blue-400). Price badges use green-50/green-500. Category badges use gray-50/gray-600. No new accent usage introduced this phase.

Source: Codebase scan of ItemCard.tsx, GlobalItemCard.tsx.


Image Skeleton Contract (FIX-03)

This phase adds image-specific loading states to all card types. The skeleton pattern must be identical across all three card components.

Pattern: animate-pulse gray placeholder fills the image area while imageUrl is resolving. Matches the existing SkeletonGrid pattern already used in src/client/routes/global-items/index.tsx and src/client/routes/index.tsx.

Card Image Area Selector Skeleton Class
ItemCard .aspect-[4/3] container bg-gray-100 animate-pulse
CandidateCard image area container bg-gray-100 animate-pulse
GlobalItemCard .aspect-[4/3] container bg-gray-100 animate-pulse

Loading state trigger: When imageUrl is truthy but the <img> onLoad has not yet fired. Use React useState for a loaded boolean on each image.

Loaded state transition: Fade-in via opacity-0 → opacity-100 transition-opacity duration-200 on the <img> tag once loaded.

Fallback (no image): Existing no-image placeholder (category icon centered on bg-gray-50) — unchanged.

Source: CONTEXT.md D-07/D-08, existing animate-pulse pattern in codebase.


Cursor Contract (FIX-05)

All interactive elements must show cursor-pointer. Use Tailwind utility cursor-pointer — not a global CSS rule.

Element Type Cursor Rule Notes
<button> that navigates (linkTo is not null) cursor-pointer ItemCard — add to existing conditional class
<button> with linkTo === null cursor-default Existing correct behavior — do not change
span[role="button"] action buttons cursor-pointer Already present on ItemCard action icons — verify all have it
<Link> components cursor-pointer Links already inherit pointer via browser default, but add explicitly if missing
Badges with onClick cursor-pointer Cycle badges (e.g. ClassificationBadge)
FAB menu items cursor-pointer Verify FabMenu.tsx
All role="button" elements cursor-pointer Tailwind utility per element

Source: CONTEXT.md D-11/D-12, ItemCard.tsx lines 76 and 106/138/170.


Auth Redirect Contract (FIX-04)

The LoginPage component (src/client/routes/login.tsx) must auto-redirect without showing any UI.

Before: Renders a full card UI with heading, description text, and a "Sign in" button that calls window.location.href = "/login".

After: Immediately calls window.location.href = "/login" inside useEffect on mount (no auth-state check needed — redirect on mount unconditionally, because the server /login route handles the Logto redirect and will bounce back to / if already authenticated).

Loading state: A minimal full-screen centered loading indicator (text-gray-500 text-sm) is acceptable during the brief useEffect tick. No elaborate UI.

Source: CONTEXT.md D-09/D-10, src/client/routes/login.tsx existing implementation.


Copywriting Contract

Phase 35 is a bug-fix phase. There are no new user-visible copy elements. The only copy consideration is the login page, which is being stripped of its UI.

Element Copy Notes
Primary CTA none (login page removes its button entirely) FIX-04
Empty state heading n/a — no new empty states introduced
Empty state body n/a
Error state n/a — no new error states
Destructive confirmation n/a — no destructive actions in this phase

Source: CONTEXT.md decisions, REQUIREMENTS.md FIX-01 through FIX-05 — no copy requirements.


Component Inventory

Components touched by this phase — executor must not create new components unless extending these.

Component File Fix Change Type
GearImage src/client/components/GearImage.tsx FIX-03 Add loading="lazy" to all <img> elements
ItemCard src/client/components/ItemCard.tsx FIX-03, FIX-05 Add image skeleton state; verify cursor-pointer coverage
CandidateCard src/client/components/CandidateCard.tsx FIX-03, FIX-05 Add image skeleton state; verify cursor-pointer coverage
GlobalItemCard src/client/components/GlobalItemCard.tsx FIX-03, FIX-05 Add image skeleton state; verify cursor-pointer coverage
Thread detail route src/client/routes/threads/$threadId/index.tsx FIX-01 Wire toolbar button to openCatalogSearch; delete AddCandidateModal
useItems hook src/client/hooks/useItems.ts FIX-02 Extend ItemWithCategory interface with image fields
Login route src/client/routes/login.tsx FIX-04 Replace page UI with immediate useEffect redirect

Registry Safety

Registry Blocks Used Safety Gate
shadcn official none not applicable — shadcn not initialized
third-party none not applicable

No new third-party components or registries in this phase.


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