Skip to content

refactor: build the seven Base* primitives the design-system contract mandates (BaseButton, BaseInput, BaseSelect, BaseToggle, BaseTextarea, BaseBadge, BaseCard) #2122

Description

@vybe

Summary

The design-system contract (docs/memory/design-system-contract.md) and the system of record (docs/memory/design-system.md) instruct every frontend PR to compose BaseButton, BaseInput, BaseSelect, BaseToggle, BaseTextarea, BaseBadge, and BaseCard — "never hand-roll a lookalike". None of these seven components exist. An asset audit (2026-08-12) found no file matching Base* anywhere under src/frontend/src and zero imports. The docs describe them as composable today, but they are spec-only.

The rest of the design-system asset base is in place: the token layer (src/frontend/tailwind.config.js semantic families + gray-750), both guard scripts (check-design-tokens.mjs, scan-raw-colors.mjs + raw-color-baseline.json), and the newer primitives (ConfirmDialog.vue, OverflowTabs.vue, LoadFailed.vue, InlineError.vue, ScanlineReveal.vue). The form-control/surface layer is the missing half.

Context

Epic #1430's acceptance criteria name exactly these primitives ("Shared primitives exist and are used by at least the core surfaces"), but no sub-issue tracks building them — the existing sub-issues (#1921#1927) cover loading motion, ink ladder, modal contract, ConfirmDialog adoption, OverflowTabs adoption, and honest-state bugs. Until the primitives exist, the contract's "Primitives first" section is unenforceable: a compliant PR has nothing to import for buttons, inputs, badges, or cards, which is why every panel still hand-rolls them.

This is a build-to-spec task, not a design task — the full specs (variants, sizes, paddings, radii, focus/error/disabled/in-flight states, both-theme token recipes) are already written in docs/memory/design-system.md, with docs/memory/design-system-reference.html as the approved visual spec.

Acceptance Criteria

  • The seven primitives exist (e.g. src/frontend/src/components/base/): BaseButton (primary/secondary/danger/ghost × md/sm, disabled opacity .45, 16px in-flight spinner + progressive label, focus ring on all variants), BaseInput/BaseSelect (field bg, border-strong, radius 6px, focus ring, named error rendering), BaseTextarea (min-height 84px, vertical resize only, mono variant), BaseToggle, BaseBadge (token-driven pill, one fact per badge), BaseCard (the only surface recipe)
  • Token-only styling — zero raw palette classes/hexes; npm run check:tokens passes and raw-color-baseline.json does not grow
  • Light and dark both first-class; keyboard/focus baseline per the contract (visible focus ring, AA contrast, dark meta text off gray-500)
  • Each primitive is adopted on at least one real surface (proves the spec against real usage; the fleet-wide migration stays with the epic's ratchet, not this issue)
  • Existing near-primitives absorb rather than duplicate: at minimum ConfirmDialog's action buttons become BaseButton (per Design system v2: shared UI primitives, raw-color ratchet, and written style guide #1430 technical notes)
  • docs/memory/design-system.md primitives catalog updated to mark each as implemented with its file path; design-system-contract.md stays accurate; rendered output matches design-system-reference.html
  • No visual regressions on touched surfaces (Playwright snapshots updated deliberately, not incidentally)

Technical Notes

  • Specs: docs/memory/design-system.md (primitives catalog, both-theme token recipes, type/spacing/radius scales); condensed binding form in docs/memory/design-system-contract.md.
  • Conventions to follow: the shipped primitives (ScanlineReveal.vue, OverflowTabs.vue, LoadFailed.vue/InlineError.vue) show the expected prop/slot/emit style.
  • Primitives consume tokens exclusively (action-primary-* for interactive, status-* for semantic states) so a palette swap stays a one-file change.
  • Presentation-layer only — Invariants Feature/process engine #6/security: Fix token logging and add HTML reports to gitignore #7 (domain stores, single API client) untouched.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions