You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
chore: set up Storybook with per-PR previews on GitHub Pages - #64
Sets up Storybook for ui-common's own components, with a per-PR preview on GitHub Pages and a PR comment that links each changed component's stories. Modeled on backend.ai-webui's packages/backend.ai-ui setup.
Storybook 10 with @storybook/react-vite, addon-docs (autodocs on every story) and @vueless/storybook-dark-mode.
A global decorator wraps every story in Astryx <Theme> and InternationalizationProvider. The toolbar switches:
theme (lablup / neutral);
dark mode;
locale (every catalog in uiCommonMessages; Astryx catalogs load lazily).
.storybook/vite.config.ts shares scripts/build-defines.mjs with the library build, so stories see the same compile-time defines.
pnpm storybook / pnpm build-storybook.
Stories
One <Dir>.stories.tsx per directory in src/components and src/forks: 43 files, 232 stories.
A directory with several components (Skeleton/, DataGrid/) has one file. The other members go in subcomponents, and each member gets its own story.
src/stories/helpers.tsx holds a few small helpers:
useOpenState / useArgState keep overlay state in React state. Storybook's useArgs throws when the global decorator re-renders a story on a dark-mode or locale switch.
overlayDocs gives an overlay story its own frame on the docs page.
withMaxWidth is a layout-only decorator.
Props that stories pass a React element to get control: false, as in webui. Otherwise Controls dumps the element and the component's whole source.
Stories are excluded from the dts build. dist is unchanged (check:pack clean).
Guidance for contributors and agents
docs/storybook.md is the guide to writing stories: where they go, meta, story naming, overlays, providers, content rules and a pre-push check.
src/components/AGENTS.md requires stories for a new component in the same PR and points to the guide.
lists the changed component directories (analyze-pr.mjs);
measures dist per component group for head and base (measure-dist.mjs).
publish (write): pushes the build to gh-pages at pr/<n>/storybook/, retrying on a push race, and regenerates the landing page.
report: posts or updates one "PR Analysis Report" comment. It has:
the preview link;
a table of changed components, each linked to its stories, with new components that have no stories flagged;
the bundle-size groups that changed.
PRs from forks are skipped.
Security trade-off (as in backend.ai-webui; see the header of pr-preview.yml): publish and report run the PR's own copy of .github/scripts/pr-preview with a write token. They install nothing and run no package scripts, only those two small scripts on the build's artifacts. The deploy lives in the same run so that one approval of a Copilot coding agent run covers it; approving such a run is therefore a review of the whole diff. A same-repository PR author already has write access, and fork PRs never reach these jobs.
cleanup: removes a PR's preview when it closes, plus a daily sweep. A weekly job compacts gh-pages history.
Before merging / after
GitHub Pages is not enabled yet. A maintainer needs to turn it on: Settings → Pages → Deploy from a branch, gh-pages / root. Until then, publish succeeds but the links in the comment 404.
Verification
pnpm typecheck, pnpm lint, format, check:boundary, build, check:pack, build-storybook pass.
Add Storybook 10 (react-vite) for ui-common's components, modeled on
backend.ai-webui's packages/backend.ai-ui setup:
- .storybook/: stories render inside <Theme> and Astryx's
InternationalizationProvider, with toolbar switches for the theme
(Lablup / Astryx neutral), the locale (Astryx + ui-common catalogs) and
dark mode. Storybook uses its own minimal Vite config instead of the
library build's.
- Seed stories for StatCard and PageHeader, and an Introduction page.
- Stories are excluded from dts emission, so dist is unchanged.
Add the PR Preview workflow: build Storybook, deploy it to gh-pages under
pr/<n>/storybook/, and post or update a "PR Analysis Report" comment
that links each changed component's stories and the bundle-size delta
against the base. A cleanup workflow removes previews of closed PRs and
compacts gh-pages history weekly. GitHub Pages itself still has to be
enabled for the gh-pages branch.
Resolves#61
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Share the build constants between the library and Storybook Vite configs
(scripts/build-defines.mjs), and hold .storybook/storybook.css to the
layer order in src/layerOrder.test.ts.
- Derive the locale toolbar from uiCommonMessages, cache loaded Astryx
catalogs across stories, fold the decorator into one component, and move
the body colors into storybook.css.
- Drop the `changes` job (the paths filter already decides), the per-SHA
artifact names, unread analysis fields, and unreachable no-URL branches;
parse script arguments with node:util parseArgs.
- Run the cleanup after a preview run only when that run succeeded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs/storybook.md: where stories go, the meta and story conventions, the
global providers, and what to check before pushing.
- src/components/AGENTS.md: a new component ships with stories in the same
PR, and points to docs/storybook.md. It is a plain path, not an @import,
so the guide is read when needed instead of at every session start.
- The Introduction page links the guide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A directory is one entry in the Storybook sidebar. A directory that exports
several components (Skeleton) gets one stories file: the main component as
`component`, the rest as `subcomponents`, and one story per member. Add
Skeleton.stories.tsx as the worked example.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With one stories file per component directory, a directory has one story
title, so the analyzer records a single deep link and the comment drops the
per-title links and the title escaping they needed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cover all 38 remaining directories under src/components and src/forks,
one stories file per directory as docs/storybook.md describes: modals and
notices, form and inputs, grids and tokens, badges and small widgets, and
layout, plus the Drawer, Tour and ComplexSelector forks.
Shared pieces live in src/stories/helpers.tsx (excluded from the library
build and its declarations): useOpenArg keeps an overlay's isOpen arg,
Controls and Actions in step, overlayDocs gives an overlay its own frame on
the Documentation page, and withMaxWidth is the width-limiting decorator.
docs/storybook.md documents them, along with generic components and the
TS2742 annotation fallback.
All 229 stories render without errors or console errors in the static
build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The global decorator re-renders a story when dark mode toggles or a locale's
Astryx catalog arrives. A story that called Storybook's useArgs then threw
"Storybook preview hooks can only be called inside decorators and story
functions", because React re-rendered it outside Storybook's render pass.
That hit the four modal stories and NotificationStack.
Replace useOpenArg with useOpenState and add useArgState: React state that
starts from the arg and resets when Controls changes it. docs/storybook.md
now says not to use useArgs in stories.
Checked by rendering every story, toggling dark mode and switching the
locale: 229 stories, no error display and no page errors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Controls shows a React element as an object, and its `type` is the
component function, so the panel printed the component's whole compiled
source (NotificationItem's `action`, Modal's `children`, and so on). As in
backend.ai-webui, each story meta now sets `control: false` on the props
its stories pass an element: 12 files. docs/storybook.md states the rule.
Checked: no story's Controls panel shows an element or function dump.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- analyze-pr: a rename reports both the old and the new directory
- pr-preview: scripts/build-defines.mjs is a build input for the base bundle
- PagedSelector stories: a new query clears a stale load-more flag
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stale previews persist after PR paths become ineligible
.github/workflows/pr-preview.yml:35
If an eligible PR later reverts all matching paths, GitHub does not start a new path-filtered run. Its previously published preview and report then remain indefinitely while the PR is open, because cleanup preserves every open PR. Run a lightweight eligibility/cleanup path on every PR update so it can remove stale previews and comments when the diff stops matching.
Missing Storybook workflow path filters
.github/workflows/pr-preview.yml:40
build-storybook depends on both scripts/build-defines.mjs (imported by .storybook/vite.config.ts) and the workspace install policy, but neither path triggers this workflow. A PR changing either input alone can break Storybook without running the only Storybook CI build. Add these inputs to the path filter.
The local value never resynchronizes when Storybook changes args.value, so the Value control stops affecting the picker after mount. Use useArgState here, matching the helper's stated purpose.
SingleSelector preserves this mount-time value when Storybook updates args.value, making the Value control ineffective after the initial render. Initialize through useArgState so arg changes reset the controlled demo.
This initializer is not rerun when the Value control changes, so the story keeps displaying stale local state. Use useArgState so controlled args continue to work after interaction.
Committed value remains stale after control changes
The component itself reseeds when defaultValue changes, but this displayed “Committed” value remains the mount-time value, so changing the Default Value control makes the story report a stale commit. Use useArgState for the displayed state as well.
This state only reads args.value on mount, so changing the Value control after interacting with the story leaves the rendered selector on the old local value. Use the new useArgState(args.value) helper so Controls updates reset the demo state as documented.
- ColorPicker, PagedSelector, StepNumberInput, UncontrolledInput and
ComplexSelector stories hold their value with useArgState, so a Controls
change after mount still reaches the story
- PR Preview also runs when pnpm-workspace.yaml or scripts/build-defines.mjs
changes, both inputs of build-storybook
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up on the second Copilot review's "Previously missed" findings:
Stateful stories ignore Controls (ColorPicker, PagedSelector, StepNumberInput, UncontrolledInput, ComplexSelector): fixed in 73df229. They hold their value with useArgState, so a Controls change after mount reaches the story.
Missing workflow path filters: fixed in 73df229. pnpm-workspace.yaml and scripts/build-defines.mjs now trigger PR Preview.
Stale preview after a PR stops matching the paths: leaving this as is. It needs a job on every PR update to cover a rare case, and the preview is removed when the PR closes.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Bundle hashing is nondeterministic and the public FormItemVisual component lacks the required story coverage.
0 open findings
Files not reviewed (1)
pnpm-lock.yaml: Generated file
Previously missed (2)
In code that hasn't changed since last review
Sort directory entries before hashing to ensure deterministic traversal
.github/scripts/pr-preview/measure-dist.mjs:26
readdirSync() does not guarantee entry order, while the group hash below is updated in traversal order and used to decide whether a bundle group changed. Identical head/base outputs can therefore produce different hashes and spurious delta rows. Sort each directory's entries before recursing to make the measurement deterministic.
Document FormItemVisual with a subcomponent entry and representative story
src/components/Form/Form.stories.tsx:187
FormItemVisual is a public visual component exported from src/components/Form/index.ts:57, but it is omitted here and has no dedicated story. That leaves its props out of the generated docs and conflicts with the new rule that every exported component in a directory is covered (src/components/AGENTS.md:7-8; docs/storybook.md:24-28). Add it to subcomponents and provide a representative story.
- Form stories list FormItemVisual as a subcomponent and show it in an
ItemVisual story with error, warning and success states
- measure-dist walks directories in sorted order, so a group's hash no
longer depends on readdir order
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up on the third Copilot review's "Previously missed" findings, both fixed in 1b87543:
Nondeterministic bundle hashing: measure-dist.mjs walks each directory in sorted order, so a group's hash depends only on its files. Two runs over the same dist now give identical output.
FormItemVisual without stories: it is in the Form stories' subcomponents, so its props show on the docs page. A new ItemVisual story shows its error, warning and success states.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Sets up Storybook for ui-common's own components, with a per-PR preview on GitHub Pages and a PR comment that links each changed component's stories. Modeled on backend.ai-webui's
packages/backend.ai-uisetup.Closes #61.
Storybook (
.storybook/)@storybook/react-vite,addon-docs(autodocs on every story) and@vueless/storybook-dark-mode.<Theme>andInternationalizationProvider. The toolbar switches:uiCommonMessages; Astryx catalogs load lazily)..storybook/vite.config.tssharesscripts/build-defines.mjswith the library build, so stories see the same compile-time defines.pnpm storybook/pnpm build-storybook.Stories
<Dir>.stories.tsxper directory insrc/componentsandsrc/forks: 43 files, 232 stories.Skeleton/,DataGrid/) has one file. The other members go insubcomponents, and each member gets its own story.src/stories/helpers.tsxholds a few small helpers:useOpenState/useArgStatekeep overlay state in React state. Storybook'suseArgsthrows when the global decorator re-renders a story on a dark-mode or locale switch.overlayDocsgives an overlay story its own frame on the docs page.withMaxWidthis a layout-only decorator.control: false, as in webui. Otherwise Controls dumps the element and the component's whole source.distis unchanged (check:packclean).Guidance for contributors and agents
docs/storybook.mdis the guide to writing stories: where they go, meta, story naming, overlays, providers, content rules and a pre-push check.src/components/AGENTS.mdrequires stories for a new component in the same PR and points to the guide.PR preview (
.github/workflows/pr-preview.yml,pr-preview-cleanup.yml)analyze-pr.mjs);distper component group for head and base (measure-dist.mjs).gh-pagesatpr/<n>/storybook/, retrying on a push race, and regenerates the landing page.pr-preview.yml):publishandreportrun the PR's own copy of.github/scripts/pr-previewwith a write token. They install nothing and run no package scripts, only those two small scripts on the build's artifacts. The deploy lives in the same run so that one approval of a Copilot coding agent run covers it; approving such a run is therefore a review of the whole diff. A same-repository PR author already has write access, and fork PRs never reach these jobs.gh-pageshistory.Before merging / after
GitHub Pages is not enabled yet. A maintainer needs to turn it on: Settings → Pages → Deploy from a branch,
gh-pages/ root. Until then, publish succeeds but the links in the comment 404.Verification
pnpm typecheck,pnpm lint, format,check:boundary,build,check:pack,build-storybookpass..github/scripts/pr-preview/pr-preview.test.ts: 3 tests pass.