Skip to content

UI Library docs: page-per-component + source-generated tables (3/3) - #46

Merged
librowski merged 44 commits into
ui-consumersfrom
ui-docs
Aug 14, 2026
Merged

UI Library docs: page-per-component + source-generated tables (3/3)#46
librowski merged 44 commits into
ui-consumersfrom
ui-docs

Conversation

@librowski

@librowski librowski commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Part 3 of 3 of the @workflowbuilder/ui series (#44 packages → #45 consumers → this PR).

Summary

Adds the UI Library section to the docs site: an overview + design-tokens guide, 19 component pages and 5 diagram-component pages, each with a live example rendered from the real @workflowbuilder/ui build and a props table generated from source (TypeDoc) plus a CSS-variables table scraped from the component stylesheets. Generated data (ui-api.json) is gitignored and rebuilt on every dev/build/typecheck run.

How to review

  1. apps/docs/scripts/generate-ui-api.mjs - the generator. Union/overload components (Button, NavButton, SegmentPicker) document through an explicit variant merge (collectVariantProps); a variant's foo?: never exclusion counts as "absent in that variant". Every silent-empty path now fails the build: missing component directory, unknown table slug, first-party props hidden behind an unresolvable utility wrapper (Partial<X>), ambiguous type names.
  2. apps/docs/scripts/check-ui-component-coverage.mjs - parity guard: every ui vite entry must have a docs entry.
  3. apps/docs/src/components/ui-examples/** - live previews render inside shadow roots; portalled popups (Modal, Menu, Select, Tooltip, DatePicker) escape to document.body, so the library stylesheet also loads at document level (astro.config.mjs customCss) - it is fully layered with no reset, safe for the Starlight theme.
  4. .github/workflows/pr-check-docs.yml - new path-filtered PR gate (apps/docs/**, packages/ui/**, packages/tokens/**): full build:docs. Previously every docs gate first ran at release-time deploy.
  5. packages/ui changes riding here (by series convention: type exports needed by the generator): ~12 export type XProps promotions, DatePickerProps now covers the component's full runtime surface (value, defaultValue, placeholder, valueFormat, type, error), plus accurate @default tags. Covered by a minor changeset for @workflowbuilder/ui.

Review history

Three-reviewer adversarial pass (pipeline / content accuracy / coupling & hygiene). Fixed since: unstyled portalled previews (5 of 23 examples), a setup page teaching the pre-#44 import-order requirement that no longer exists, a DatePicker table that documented none of the props its own example used, SegmentPicker/NavButton tables contradicting their prose (first-wins union flattening), four silent-empty generator paths, the missing PR CI gate, sidebar ordering of section index pages.

2026-08-11 - OpenCode (GPT-5.6) adversarial round. 15 findings, all verified on code; fixed here:

  • Changeset overpromise (CRITICAL): the NavButton variant prop types (NavIconButtonProps, NavLabelButtonProps, NavIconLabelButtonProps) were announced but reachable from no barrel - now re-exported via types.ts and proven importable with a consumer-side tsc probe against dist.
  • Generator fidelity: variant-only props are no longer marked globally required (SegmentPicker documented value and defaultValue as simultaneously required - an impossible call); missing @default tags added for Tooltip/IconSwitch/EdgeLabel runtime defaults.
  • Copyable examples: nine stateful usage blocks gained their missing useState import.
  • Pages aligned with code: NodeAsPortWrapper now documents the real mechanism (stretches the existing target handle's hit area; no built-in highlight; requires a target Handle), the "missing token" note describes the markers as historical, overview drops the 1.4.x pin claim, Snackbar states it is purely presentational, and the custom-node guide gains the @workflowbuilder/ui install step.
  • Preview stage: oversized examples (Snackbar) shrink to the stage or grow it instead of being clipped (verified at a 320px column).
  • Guards: the coverage check now also requires an MDX page rendering each generated slug; the docs CI gate's path filter covers packages/sdk/** (starlight-typedoc reads it), root manifests, and the workflow file itself.
  • Tracked as tasks: reference-type resolution + CSS-variable attribution by import graph, stale missing token comment sweep.

2026-08-13 - maintainer review round. Eight findings, all confirmed and addressed:

  • Subcomponent variables no longer leak into parent tables. The generator globbed the whole component folder, so Button documented 17 of NavButton's variables and Switch 12 of IconSwitch's - overrides that do nothing on those pages. Nested entries with their own page are now excluded.
  • Color / Size grouping follows the value, not the name. edge-stroke-width sat under Color and the snackbar status borders under Size; the generator now resolves var() chains through the design tokens. 317 variables, zero misfiled in either direction.
  • The coverage guard can no longer pass empty. It regex-scraped COMPONENTS out of the generator source, so a change in quote style would produce an empty list and a green check over nothing. Both sides now import scripts/ui-components.mjs; the remaining scrape (the vite entry list, which is TypeScript) fails on an empty result. The guard also requires every generated slug to be rendered by an MDX page.
  • The last hand-written API tables are generated. NodePanel had no generator entry and listed 10 of its 20 CSS variables by hand; useEdgeStyle's parameter table was hand-written and its type unexported. Both now come from source.
  • The utility-type warning works again for all 20 single-type components - the main path never passed the context object it needs, so Partial<X> silently produced a slimmer table.
  • Interface references are followed, matching what the function already accepts at its entry point.
  • Docs fixes: the dead /ui-library/ link, the "Headless components" heading over a paragraph describing a styled library, the stale "pinned to 1.4.x" claim, useState imports missing from nine copyable examples, the NodeAsPortWrapper page describing behaviour the CSS does not implement, the "missing token" note, the private icons package in an example, Snackbar's scope, and the preview stage clipping wide examples.

Verification

  • Full production astro build: 208 pages, green - identical to the new CI job's gate.
  • Live-docs checks: portalled modal renders fully styled in dark/light; DatePicker/SegmentPicker/NavButton tables verified against generated output (variant notes included); negative tests confirmed loud failures (renamed component dir → exit 1; unknown slug → render error pointing at the file).
  • Post-Starlight-migration sweep: no Docusaurus leftovers (configs, @theme imports, frontmatter fields, unsupported admonitions).

Notes

@librowski
librowski marked this pull request as ready for review June 24, 2026 12:31
librowski-synergy and others added 28 commits August 14, 2026 10:17
…views

Modal, Menu, Select, Tooltip and DatePicker portal their popups to body,
outside the shadow roots that carry the preview styles - the popups
rendered unstyled. The library CSS is fully layered with no reset, so
loading it globally is safe for the Starlight theme.
Every built stylesheet has carried the @layer order statement since the
stamping fix, so 'import styles.css before any component' is no longer a
correctness requirement; styles.css also ships no reset, only typography.
The docs generator reads the exported DatePickerProps, but the props the
component actually accepts (value, defaultValue, placeholder, valueFormat,
type, error) lived on an unexported local widening type - the generated
table documented none of the props the page's own example uses. The
widening moves into the exported type, TSDoc included.
… merge

SegmentPicker's discriminated union was flattened first-wins: the table
claimed value is always required and typed defaultValue as never,
contradicting the page's own controlled/uncontrolled prose. NavButton
was generated from its flat base type, so children - the prop its page
is about - never appeared. Both now list their variant prop types, and
a variant's 'foo?: never' exclusion counts as the prop being absent
there instead of polluting the merged type.
Four holes of the same class - the tool swallowing a problem and shipping
a plausible-looking page:
- a moved/renamed component directory made extractCssVariables glob
  nothing and exit 0 (page claims 'no CSS variables')
- an unknown slug in PropsTable/CssVariablesTable rendered the empty
  state instead of failing the build (typo = false page in production)
- a first-party type hidden behind an unresolvable utility wrapper
  (Partial/Omit) dropped its props with no trace - the class that once
  got the library reshaped to suit the generator
- findTypeByName picked the first of duplicate type names silently
Every docs gate (ui-api generator, component-coverage guard, astro page
rendering) used to run for the first time at release-time deploy - a
broken docs change merged green and surfaced weeks later as a red
deploy. Path-filtered to docs/ui/tokens changes so unrelated PRs pay
nothing.
The index pages sorted alphabetically inside their own groups (the 'UI
Components' link sat 7th within UI Components). Order 0 plus an Overview
label matches the plugins/nodes convention; link lists use exact
component symbol names, and the section description gains sentence case.
The changeset announced them, but only NavBaseButtonProps left the
package - the variant types lived in component files outside every
barrel. Verified with a consumer-side tsc probe against dist.
The generator reads defaults from JSDoc only; Tooltip placement,
IconSwitch variant and EdgeLabel size/state/type had runtime defaults
with no tag, so their table cells rendered empty.
Absent variants were filtered out before the every() check, so
SegmentPicker documented value (controlled) and defaultValue
(uncontrolled) as simultaneously required - a call that cannot exist.
Required now means required in every variant; the variant note says
where a prop is required otherwise.
The coverage guard only cross-checked vite entries against generator
entries; an entry with no page rendering its slug passed silently.
starlight-typedoc reads packages/sdk sources, and root manifests plus
the workflow file itself shape the build - none of them triggered the
gate, so an SDK change could break the docs build unnoticed until
deploy.
The fixed 2/1 stage with overflow:hidden clipped wide examples
(Snackbar) on narrow layouts. The ratio is now a preferred size:
shadow-root children cap at the stage width and content restores the
automatic minimum height. Also corrects the isolation comment -
inherited typography crosses the shadow boundary by design.
Nine usage blocks called useState without importing it, so a copied
example failed to compile. Snackbar now says it is purely
presentational - no positioning, stacking, or auto-dismiss.
NodeAsPortWrapper stretches the existing target handle's hit area and
adds no highlight of its own - the page claimed the whole node becomes
a highlighted port with no preconditions. The missing-token note now
describes the comments as historical markers (several referenced tokens
exist in the export today). Overview drops the 1.4.x pin claim, and the
custom-node guide gains the missing ui package install step.
/ui-library/ has no index page - the section entry is /ui-library/overview/.
The 'Headless components' heading sat above a paragraph describing a fully
styled library built on headless primitives.
Subcomponents with their own page had their variables repeated on the
parent: Button listed 17 of NavButton's, Switch 12 of IconSwitch's, and
overriding them there does nothing. The Color/Size split now follows the
value through the design tokens instead of guessing from the name, which
put edge-stroke-width under Color and the snackbar status borders under
Size.
The guard regex-scraped COMPONENTS out of the generator source, so a
change in quote style would yield an empty list and pass having checked
nothing. Both now import the same module; the remaining scrape (the vite
entry list, which is TypeScript) fails on an empty result.
The reference branch accepted only type aliases, so a prop type written
as an interface would drop its members from the table with no warning -
the entry point of the same function already handled both forms.
NodePanel had no generator entry: its CSS variable list was written by
hand and covered 10 of the 20 variables in source. The useEdgeStyle
parameter table was hand-written too, so its type is now exported and
documented from source. An entry may carry dir: null when it documents
an API but owns no stylesheet - the edge variables stay on the Edge page
instead of splitting off to the hook.
The snippet imported Icon from @workflow-builder/icons, which is marked
private and cannot be installed from npm - a consumer copying it hits an
unresolvable dependency. It now uses Phosphor, which ships with the
package, and says that any icon library works.
Input documented four props while its own example used placeholder, value
and onChange - and no page said the component extends a native element's
attributes. The generator now detects the forwarded element by walking the
prop type (through Omit/DetailedHTMLProps wrappers and first-party
aliases) and each affected page states it in one line, instead of the
table listing ~280 DOM attributes or staying silent.
The examples import @phosphor-icons/react. It is a dependency of the
library, not something a consumer may import without declaring it - under
pnpm's strict node_modules that import does not resolve. The install page
now says so once, and the NodeIcon page points at it.
The example's import speaks for itself.
The comments it describes are stripped from the generated tables and do
not survive minification into dist, so a reader of the docs has no way to
encounter them.
@librowski
librowski merged commit f4871b4 into main Aug 14, 2026
10 checks passed
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.

3 participants