Skip to content

docs(apps): design-system component + theme showcase (VitePress-primary, HTML⇄React) - #22

Merged
ZacxDev merged 7 commits into
mainfrom
zach/design-system-showcase
Jul 22, 2026
Merged

docs(apps): design-system component + theme showcase (VitePress-primary, HTML⇄React)#22
ZacxDev merged 7 commits into
mainfrom
zach/design-system-showcase

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Design-system component + theme showcase (VitePress-primary, HTML⇄React)

Adds a live component showcase + token gallery for Civitai's
dual-consumption design system (@civitai/theme / @civitai/components /
@civitai/components-react) into the developer-docs site — VitePress
markdown-primary, with a Shoelace-style HTML⇄React source toggle. Every live
preview is painted by the published packages a real consumer installs, so
the docs render exactly what a user gets.

Status: READY. Full build + test matrix is green (one pre-existing,
unrelated snapshot drift noted below).

Why VitePress-primary (not a standalone Storybook)

The design-system docs already live here (apps/guide/theming, the generated
apps/reference/components). A showcase belongs next to them: same nav, search,
shiki code blocks, and the existing test:snippets:appblocks snippet-typecheck
drift-guard already typechecks React examples against the pinned
@civitai/components-react declarations. One site, one deploy
(developer.civitai.com).

What's built

  • M1 — pinned @civitai/{theme,components,components-react} to 0.1.2
    (exact); loaded the tokens + component CSS globally in the custom theme.
  • M2<ComponentDemo>: a live, themed HTML preview with an HTML⇄React
    source toggle. The preview is derived from the same text shown in the HTML
    panel, so it can't drift from the shown source.
  • M3apps/showcase.md: a <ComponentDemo> gallery for all 10
    components — Button (variants × sizes + loading/disabled), Badge (variants +
    the new 0.1.2 data-color intents + sizes), Alert (all four intents +
    title + dismiss), TextInput / Textarea / NumberInput (incl. the invalid
    state), Card (±border, padding), Stack & Group (gaps), Loader (sizes +
    standalone role="status"). Added nav + a shared Design system sidebar.
  • M4apps/tokens.md + <TokenGallery>: reads the tokens live from
    the published @civitai/theme JS export (tokens / darkTokens /
    tokenVars) plus a getComputedStyle probe per theme — color swatches
    (hex + name + resolved), typography specimens, radius, a full light/dark
    token table, the component size scales (clearly labelled as component-level,
    not theme tokens), and the DTCG tokens.json in a collapsible panel. Every
    row derives from Object.keys(tokenVars), so it can't drift from the package.
    Containers follow useData().isDark.
  • M5 — complete test coverage (below).

Also fixed two latent issues surfaced while wiring the previews:

  1. <ComponentDemo> hid the React panel on every bare <ComponentDemo> — Vue
    3.3+ infers type: Boolean from react?: boolean, and an absent Boolean
    prop casts to false (not undefined). Added an explicit { react: true }
    default.
  2. Preview <button>/<input> rendered unthemed (transparent) because
    VitePress ships an unlayered form-control reset, and every
    @civitai/components rule lives in @layer civitai.components — so the
    unlayered chrome reset won. Fixed with a .cds-preview-scoped
    all: revert-layer that rolls the cascade back past the reset to the
    design-system layer. Scoped to previews only; the chrome is provably
    untouched (see the chrome test).

Test coverage — verbatim results

npm run build — exit 0, no dead links:

✓ rendering pages...
build complete in 51.57s.

npm run test:snippets:appblocks (existing gate; typechecks all React
snippets, incl. the 9 new showcase.md tsx blocks + tokens.md ts, against
pinned @civitai/components-react@0.1.2):

App Blocks snippets: 18 found · 18 passed · 0 skipped · 0 failed

npm run test:tokens:drift (new — TokenGallery shows EXACTLY the theme's
exported tokens; missing/stale/misnamed all fail):

TokenGallery drift: 17 exported · 17 rendered
✓ token gallery matches @civitai/theme exactly (no missing/stale/misnamed).

npm run test:showcase:e2e (new — playwright-core + system/nix Chromium,
against the built + served site; skips gracefully when no Chromium):

PASS  light: filled Button background === rgb(34, 139, 230)
PASS  light: Badge is a pill (border-radius 999px)
PASS  light: Card surface === rgb(254, 254, 254)
PASS  light: Loader is circular (border-radius 50%)
PASS  light: Alert has a themed (non-transparent) tint — color(srgb 0.133333 0.545098 0.901961 / 0.12)
PASS  light: labeled input control renders
PASS  light: preview container data-theme === light
PASS  ComponentDemo: HTML panel shown first
PASS  ComponentDemo: live preview rendered the passed HTML
PASS  ComponentDemo: clicking React tab swaps panels
PASS  ComponentDemo: React tab becomes aria-selected
PASS  snippet accuracy: shown HTML === live preview for all 9 demos
PASS  chrome: no [data-civitai-ui] elements leak into nav/sidebar
PASS  chrome: --vp-font-family-base and --civitai-font are distinct stacks
PASS  chrome: nav font resolves from VitePress base font (not the civitai stack)
PASS  chrome: preview font IS the civitai stack (DS font scoped to previews)
PASS  chrome: --vp-c-brand-1 not shadowed by --civitai-color-primary — brand=#0ea5e9 civitaiPrimary=rgb(34, 139, 230)
PASS  dark: preview container data-theme re-resolved === dark
PASS  dark: filled Button background === rgb(25, 113, 194)
PASS  dark: Card surface === rgb(26, 27, 30)
PASS  dark: button re-resolved to a DIFFERENT value than light — light=rgb(34, 139, 230) dark=rgb(25, 113, 194)

Showcase E2E: 21 checks · 21 passed · 0 failed

Coverage map to the plan: live-render-themed (light + dark, one anchor per
family, toggle re-resolves) ✓ · snippet accuracy — React typecheck via
test:snippets:appblocks + HTML↔preview parity in the E2E ✓ · token-gallery
drift
✓ · <ComponentDemo> behaviour (toggle + preview render) ✓ ·
site-chrome non-regression ✓ · existing gates kept green ✓.

Screenshots (light + dark, showcase + token gallery) are captured on-host by the
E2E run to .showcase-shots/ and were visually confirmed; the rgb(...)
assertions above are the objective, reproducible evidence of correct theming in
both modes.

Chrome-unaffected — confirmed

Asserted by the dedicated non-regression checks (all PASS above): no
[data-civitai-ui] element leaks into the nav/sidebar; the chrome font resolves
from --vp-font-family-base (Inter, …) while previews use --civitai-font
(-apple-system, …); and --vp-c-brand-1 (#0ea5e9) is not shadowed by
--civitai-color-primary. By construction, @civitai/theme ships only
--civitai-* custom properties (no --vp-* overlap) and @civitai/components
is scoped to [data-civitai-ui] under @layer civitai.components (layered CSS
loses to the unlayered chrome — the same mechanism the preview revert-layer
fix works with, scoped to .cds-preview).

Pre-existing, out-of-scope: check:snapshots

npm run check:snapshots reports 1 driftedappblocks-snapshots/MARKUP.md
vs the live upstream civitai-app-starters. This is pre-existing and
unrelated
to this PR: MARKUP.md was last modified in #18 and is byte-identical
at the branch base and HEAD. Upstream has since re-pinned the CDN URLs to @0.1.1
and added Badge data-color docs; the committed snapshot is unpinned and predates
data-color. Reconciling it needs a maintainer decision on the CDN pin (the repo
deliberately unpinned per #19/#20), so it's intentionally left untouched here.

🤖 Generated with Claude Code

ZacxDev and others added 5 commits July 22, 2026 15:20
…ents}@0.1.2 CSS

Pin the design-system devDeps to 0.1.2 (theme/components/components-react) and
load the tokens + component CSS globally in the custom theme so the upcoming
<ComponentDemo>/<TokenGallery> live previews render fully themed.

Safe against the VitePress chrome by construction (a dedicated non-regression
test will assert it): @civitai/theme ships only --civitai-* custom properties
(no --vp-* overlap, styles no elements); @civitai/components is scoped to
[data-civitai-ui] and wrapped in @layer civitai.components (layered CSS loses to
the unlayered chrome). Build stays green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ve demo

Shoelace-style demo component: renders the framework-agnostic `data-civitai-ui`
HTML LIVE as the primary themed preview, above a code panel toggling HTML⇄React
source (both shiki-highlighted with copy buttons via VitePress fenced-block
slots).

Single source of truth: the live preview is derived from the SAME text shown in
the HTML panel (the #html slot's rendered <code>), so the preview can never
drift from the shown HTML. The preview container's data-theme is driven by
VitePress dark mode (useData().isDark) so every --civitai-* token re-resolves to
match the site theme.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nents

Adds apps/showcase.md: a <ComponentDemo> live-themed gallery covering every
@civitai/components component (Button variants/sizes/loading, Badge variants +
0.1.2 data-color intents, Alert intents, the 3 labeled inputs incl. invalid
state, Card +/-border, Stack/Group, Loader) with an HTML-React source toggle,
plus nav + a shared 'Design system' sidebar (showcase + tokens).

Also fixes a latent <ComponentDemo> bug: Vue 3.3+ infers type: Boolean from the
react?: boolean annotation, so an absent Boolean prop casts to false — a bare
<ComponentDemo> hid the React panel. Explicit { react: true } default restores
the intended behaviour.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds apps/tokens.md and a <TokenGallery> that reads the tokens LIVE from the
published @civitai/theme JS export (tokens/darkTokens/tokenVars) plus a
getComputedStyle probe per theme — color swatches (hex+name), typography
specimens, radius, a full light/dark token table, the component size scales
(reference), and the DTCG tokens.json surfaced in a collapsible panel. Every
row is derived from Object.keys(tokenVars) so it cannot drift from the package.
Container follows useData().isDark. Registered in the theme + already in nav.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds two committed, runnable gates plus a faithful-render CSS fix:

- scripts/test-showcase-e2e.mjs (playwright-core + system/nix Chromium):
  21 checks — live-render-themed previews in BOTH light and dark (filled Button
  bg rgb(34,139,230)->rgb(25,113,194), Badge pill, Card surface, Loader circle,
  Alert tint, inputs; toggling the appearance switch re-resolves the tokens in
  place), <ComponentDemo> panel-swap + preview render, snippet accuracy
  (shown HTML === live preview for all 9 demos), and site-chrome
  non-regression (no DS leak into nav/sidebar; chrome font = --vp-font-family-base
  while previews use --civitai-font; --vp-c-brand-1 unshadowed). Captures
  light+dark screenshots. Skips gracefully (exit 0) when no Chromium is present.
- scripts/test-tokens-drift.mjs: asserts <TokenGallery> renders EXACTLY the
  tokens @civitai/theme exports (missing/stale/misnamed all fail).
- design-system.css: revert-layer fix so preview form controls render faithfully
  despite VitePress's UNLAYERED button/input reset beating the LAYERED component
  CSS — scoped to .cds-preview, chrome untouched (asserted by the chrome test).

React-snippet typecheck accuracy is covered by the existing test:snippets:appblocks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread scripts/test-showcase-e2e.mjs Fixed
@ZacxDev
ZacxDev marked this pull request as ready for review July 22, 2026 20:58
Clears the CodeQL high 'DOM text reinterpreted as HTML' finding in the snippet-
accuracy check. DOMParser.parseFromString is inert (no execution) and yields the
same parsed tree for the [data-civitai-ui] comparison. E2E still 21/21.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread scripts/test-showcase-e2e.mjs Fixed
CodeQL flags BOTH innerHTML and DOMParser.parseFromString when fed DOM text
(js/xss-through-dom). Compare the shown snippet's data-civitai-ui markers via a
regex string match instead of reinterpreting the code text as HTML — same
assertion, no sink. E2E still 21/21 (snippet-accuracy passes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ZacxDev
ZacxDev merged commit c11922a into main Jul 22, 2026
7 checks passed
@ZacxDev
ZacxDev deleted the zach/design-system-showcase branch July 22, 2026 21:10
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.

2 participants