Skip to content

feat(ui): add core primitives and state panels - #1031

Merged
EhabY merged 12 commits into
mainfrom
feat/devex-618-ui-state-panels
Jul 27, 2026
Merged

feat(ui): add core primitives and state panels#1031
EhabY merged 12 commits into
mainfrom
feat/devex-618-ui-state-panels

Conversation

@EhabY

@EhabY EhabY commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Closes #1035

PR #1030 established the @repo/ui scaffold and semantic theme boundary. This stacked PR turns it into the primitives needed for upcoming Workspaces states: Icon, IconButton, Spinner, ProgressBar, SearchInput, StatusPill, EmptyState, and ErrorState. WelcomePanel is omitted (native viewsWelcome already covers it) and Skeleton is deferred until the Workspaces first-load layout provides concrete placeholder geometry.

VS Code fidelity

Components are native HTML elements styled to match VS Code's own controls, using VS Code Elements as a design reference without importing it: 22px toolbar icon buttons, 26px search input, 28px progress ring and 2px progress bar, native primary-button retry styling, VS Code's 0.4 disabled opacity, and high-contrast, forced-colors, and reduced-motion handling throughout. StatusPill defaults to VS Code's native badge; tones and icons are an extra, where a tone tints the pill GitHub-style with its own accent at 15% alpha so it stays legible in all four themes.

Library boundary (NPM-ready)

  • ESLint rejects @repo/* and relative cross-package imports; React stays a peer dependency and codicons the only runtime dependency.
  • CodiconName is derived at type-check time from codicon metadata, and shared internals (#cx, #codicons, #storybook) use package.json subpath imports that ship with the package on a standalone split.
  • Component CSS consumes semantic --ui-* tokens only; tokens.css is the single VS Code mapping layer.

Storybook and visual regression

  • Theme variables and the default webview stylesheet are dumped from a real, pinned VS Code instance (pnpm sync:vscode-themes; deterministic capture, verbatim prettier-ignored output so a resync leaves no diff) and applied as a single synchronous :root stylesheet swap.
  • Pixel snapshots every story in Light, Dark, High Contrast, and High Contrast Light (parameters.pixel adds the high-contrast themes to the light/dark base matrix).
  • Storybook runs the React Compiler like the production webview builds, so stories, snapshots, and compiler lint (eslint-plugin-react-hooks combined with @eslint-react via its conflict preset) all exercise what ships.
  • UI/VSCodeParity renders each control beside its @vscode-elements counterpart under identical variables (plus a link row against the captured default stylesheet), so drift from VS Code styling shows up as a visual diff.

Coverage

  • One combined matrix story per component, plus Foundations (live token table), the parity sheet, and a snapshot-excluded icon gallery.
  • Focused Testing Library tests in test/webview/ui/ cover accessibility and behavior; EmptyState/ErrorState share one internal StatePanel so the panel markup can't drift.
  • Validated with type checking, linting, formatting, tests, Storybook CI build, and production build.

Generated by Coder Agents on behalf of @EhabY.

Implementation plan

UI core primitives and state panels

Goal

Create a stacked PR on feat/devex-624-ui-scaffold that adds concise, generic components to @repo/ui with Storybook, visual-regression, accessibility, and focused behavioral coverage, while preserving a clean future standalone-library boundary.

Scope

Implement Icon, IconButton, Spinner, ProgressBar, SearchInput, StatusPill, EmptyState, and ErrorState.

Do not implement WelcomePanel (native viewsWelcome remains) or Skeleton (no concrete first-load geometry yet).

Styling strategy

Match VS Code exactly with native elements rather than a custom design system or a runtime dependency on @vscode-elements:

  • Learn dimensions, variables, and interaction patterns from the installed VS Code Elements package.
  • Keep the toolbar-button, input, button, badge, and progress geometry consistent with VS Code defaults.
  • Map themes once in tokens.css; component CSS uses semantic --ui-* tokens only.
  • Support high contrast through VS Code contrast variables, plus forced-colors and prefers-reduced-motion media handling.

Minimal tooling

  • Derive CodiconName as a TypeScript type from codicon metadata (resolveJsonModule); commit no generated output and expose no runtime icon array.
  • Enforce package isolation with ESLint (@repo/* bans and import-x/no-relative-packages) instead of a custom script.

Storybook

One combined matrix story per component under UI/<Component>, four-theme Chromatic modes on each matrix, interaction assertions via play functions, and a snapshot-disabled curated icon gallery. Detailed behavior stays in Vitest.

Behavioral tests

Focused Testing Library/Vitest tests in test/webview/ui/ covering icon semantics, button accessibility, spinner status, progress ARIA/clamping, controlled search and clear-focus behavior, status pill tones, and state panel slots/retry.

Validation

pnpm format:check, pnpm typecheck, pnpm lint, pnpm test:webview ./test/webview/ui, pnpm storybook:ci, pnpm build, and git diff --check, plus manual Storybook review across all four themes.

Git and PR

  • Working branch feat/devex-618-ui-state-panels, stacked on feat/devex-624-ui-scaffold.
  • Conventional Commits.
  • PR discloses Coder Agents generation, links DEVEX-618, and explains omissions.

@linear-code

linear-code Bot commented Jul 13, 2026

Copy link
Copy Markdown

DEVEX-618

DEVEX-646

@EhabY EhabY self-assigned this Jul 13, 2026
@EhabY
EhabY force-pushed the feat/devex-618-ui-state-panels branch 4 times, most recently from e1122d6 to 839acc7 Compare July 14, 2026 10:08
@EhabY
EhabY force-pushed the feat/devex-624-ui-scaffold branch from b9d3bea to 5a6fe06 Compare July 20, 2026 11:21
@EhabY
EhabY force-pushed the feat/devex-618-ui-state-panels branch from da82146 to 6cd7c9c Compare July 20, 2026 11:33
Base automatically changed from feat/devex-624-ui-scaffold to main July 27, 2026 09:21
EhabY and others added 7 commits July 27, 2026 12:22
…joining

- Move the theme sync machinery into .storybook/themes/ next to its
  generated output: the extension-test suite becomes a real suite.cjs
  file instead of an escaped template string, and the fixture manifest
  is checked in instead of written to a temp dir at runtime (VS Code
  loads a manifest-only fixture; the empty extension.js shim is gone).
- Pin the captured VS Code to 1.128.0 so dumps are reproducible; bump
  VSCODE_VERSION in sync.mjs to resync against a newer release.
- Capture the startup theme (dark) last so every dump follows a real
  theme-change event; capturing it immediately at startup can read
  partially-loaded theme data. Failed activation rejects loudly instead
  of silently dumping the wrong theme.
- Format the generated stylesheet from sync.mjs so a resync never
  leaves an unformatted diff for format:check.
- Add a tiny cx() helper used by all components instead of the repeated
  filter/join expression, and reach shared internals (#cx, #codicons,
  #storybook) through package.json subpath imports instead of ../../
  paths; the aliases are scoped to packages/ui and ship with it.
- JSDoc for top-level declarations, readonly types on const
  collections, and list sync:vscode-themes in the AGENTS.md command
  table.
Typography, text color, and body margin now come from the dumped VS Code
default stylesheet; keep only the sidebar background, viewport sizing,
and the deliberate body-padding override.
@EhabY
EhabY force-pushed the feat/devex-618-ui-state-panels branch from 6cd7c9c to 9b2e4ca Compare July 27, 2026 09:22
Apply /simplify pass across the PR:

- derive Storybook themes from the generated dump and apply them as one
  synchronous :root stylesheet swap instead of ~1750 inline-style
  mutations per story navigation
- share one MutationObserver across useVscodeTheme instances
- extract an internal StatePanel component so EmptyState/ErrorState stop
  duplicating the panel markup by class-name convention
- read foundations token values from a live getComputedStyle view,
  dropping the rAF workaround and per-render stylesheet scans
- drop dead code: unused iconLabel prop, inert CSS rules that codicon.css
  already wins, undefined-propagating ProgressBar aliases
- exercise --ui-link-foreground with a Link row in the parity story
- lint *.cjs files (suite.cjs was covered by no config block)
- write generated theme dumps verbatim: ignore the directory in prettier,
  drop the sync-time format step, and commit the verbatim regeneration
- use the qs() test helper in the new ui tests
@EhabY
EhabY force-pushed the feat/devex-618-ui-state-panels branch from 7e4416e to a8ea854 Compare July 27, 2026 10:54
EhabY added 4 commits July 27, 2026 14:16
VS Code's badge is a plain counter with no icon or status variant, and
its tone accents (testing-iconPassed, errorForeground, ...) are tuned
against panel backgrounds, not the badge fill: on the badge they
measured as low as 1.2:1.

Tint the pill background with the tone accent itself (15% alpha via
color-mix) so accents sit over the surface they were designed for, keep
text at the theme foreground, and pull the icon color 30% toward the
foreground so pale light-theme accents stay visible. Works across all
four themes from a single formula, so the high-contrast override and
the now-unused badge tokens are gone.
The pill is VS Code first: without a tone it renders as the native
badge (badge-background/foreground), verified against VscodeBadge in
the parity story alongside a plain 42 counter. Tones remain the
GitHub-style tinted extra, and a Native story snapshots the plain pill
on its own.
- type props with ComponentProps so React 19 forwards refs through the
  existing spreads; SearchInput merges the consumer ref with its
  internal clear-and-refocus ref via a callback ref, with a test
- lint with eslint-plugin-react-hooks and its compiler diagnostics,
  combined with @eslint-react through the official
  disable-conflict-eslint-plugin-react-hooks preset so no rule reports
  twice; globals/immutability/refs enabled explicitly since the
  recommended preset leaves them off
- run the React Compiler in Storybook, matching production webview
  builds; this surfaced a real stale-memo bug in the foundations story
  (the compiler drops deps-array-only triggers), fixed by remounting
  the token table per theme and reading values in state initializers
- let story bodies scroll: real webviews scroll, so overflow: hidden
  was a wrong host stand-in that made tall stories unviewable
Removing the stand-in's own overflow: hidden wasn't enough: the preview
auto-imports every package's CSS and tasks' app shell hides body
overflow globally. Override it with a higher-specificity rule so load
order doesn't matter.
@EhabY
EhabY merged commit cdc31d8 into main Jul 27, 2026
11 checks passed
@EhabY
EhabY deleted the feat/devex-618-ui-state-panels branch July 27, 2026 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ui: core primitives + state panels

2 participants