Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.git
.nx
node_modules
**/node_modules
dist
tmp
**/storybook-static
**/coverage
**/.vitest-artifacts
**/.vitest-attachments
6 changes: 6 additions & 0 deletions .github/workflows/pr-head.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ jobs:
- name: Build Storybook
run: npm run build-storybook

- name: Verify combined Storybook in Chromium
run: |
npx playwright install --with-deps chromium
node bin/storybook/smoke-test.mjs

- name: Build Form Configurator
run: npm run build:form-configurator

Expand All @@ -62,6 +67,7 @@ jobs:
npx nx build ui
cp libs/ui/package.json dist/libs/ui/package.json
node libs/ui/scripts/postbuild-types.mjs
node bin/storybook/check-links.mjs --package

- name: Upload dist/libs/ui artifact
uses: actions/upload-artifact@v7
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ libs/ui/coverage/*
# storybook
storybook-static/*
libs/ui/storybook-static/*
libs/web-components/storybook-static/*
*storybook.log

vite.config.*.timestamp*
Expand Down
5 changes: 4 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Node >= 22.17.0, npm workspaces. Run `npm install` after cloning.

```bash
# Development
npm run storybook # Storybook dev server at http://localhost:6006
npm run storybook # Shared React + Web Components Storybook at http://localhost:6006
npm test # Unit tests for gd-design-library (vitest, jsdom)
npm run test:ai # AI integration tests in src/ai/__tests__/
npm run test:form-configurator # Unit tests for both form-configurator packages (jest)
Expand All @@ -39,6 +39,7 @@ npm run storybook:visual # Run visual regression tests
npm run build:ui # Full gd-design-library build: readme-stats + ai-docs + type-check + nx build + postbuild-types
npm run build:form-configurator
npm run build-storybook
npm run storybook:check-links # Built story indexes + source/shipped AI link validation

# Verification (run on built dist — requires npm run build:ui first)
npm run verify:ui:full # All 10 verification phases + Verdaccio smoke test
Expand Down Expand Up @@ -67,6 +68,8 @@ Path aliases defined in `libs/ui/vitest.alias.ts` and `libs/ui/tsconfig.json`: `

**Build output** — Vite produces both ESM (`.js`, `preserveModules`) and CJS (`.cjs`) to `dist/libs/ui`. Post-build step (`scripts/postbuild-types.mjs`) patches `.d.ts` exports. React and `@emotion/*` are externalized. `bin/export-theme.js` serializes `defaultTheme` to JSON from the built dist; it runs automatically as part of `npm run storybook` and `npm run build-storybook`.

**Shared Storybook** — `npm run storybook` manages the React host (6006) and native Web Components preview (6007) together. Open only the host. Framework-neutral pages listed in `bin/storybook/shared-docs.json` appear at the root, before React, without a prefix. Other MDX and component stories receive a central `React/` title prefix; do not duplicate it in their titles. Keep React integration examples under React, even when their topic also has shared design guidance. The combined static artifact is `libs/ui/storybook-static`, with the child under `web-components/`. Use generated story IDs for links and run `storybook:check-links` after the combined build. Package-only validation uses `node bin/storybook/check-links.mjs --package` and indexes source stories without building previews. Do not regenerate the frozen legacy route inventory to conceal missing entries. Redirects support both original links and interim React-prefixed links to shared pages. Phase 1 contains only the native overview; component coverage and harness cleanup are later phases.

**Package subpaths** — the published package exposes four explicit subpaths (wildcards are intentionally removed to prevent internal path coupling):

| Subpath | Contents |
Expand Down
11 changes: 7 additions & 4 deletions Dockerfile.storybook
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ WORKDIR /app

COPY package.json package-lock.json nx.json tsconfig.base.json .npmrc eslint.config.mjs ./
COPY libs/ui/ libs/ui/
COPY bin/export-theme.js bin/export-theme.js
COPY libs/design-core/ libs/design-core/
COPY libs/web-components/ libs/web-components/
COPY libs/form-configurator/ libs/form-configurator/
COPY libs/react-native/package.json libs/react-native/package.json
COPY bin/ bin/
COPY README.md CLAUDE.md ./

RUN npm ci

RUN npx nx build ui \
&& node ./bin/export-theme.js \
&& npx nx run ui:build-storybook
RUN npm run build-storybook

FROM nginx:alpine AS runner
WORKDIR /usr/share/nginx/html
Expand Down
39 changes: 36 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default function App() {
}
```

See the [Storybook](https://github.com/griddynamics/gd-design-system#storybook) for interactive component examples and full API documentation.
See the [shared Storybook](https://storybook.cto-rnd-system-design.griddynamics.net/?path=/docs/introduction-welcome--docs) for design references, interactive component examples, and API documentation.

## Setup

Expand All @@ -52,7 +52,7 @@ npm install
## Development

```bash
npm run storybook # Storybook at http://localhost:6006
npm run storybook # Shared React + Web Components Storybook at http://localhost:6006
npm test # gd-design-library unit tests
npm run test:form-configurator # form-configurator tests
npm run type-check # TypeScript check
Expand All @@ -64,9 +64,42 @@ npm run lint # ESLint
```bash
npm run build:ui # Build gd-design-library (ESM + CJS + types)
npm run build:form-configurator # Build both form-configurator packages
npm run build-storybook # Build static Storybook
npm run build-storybook # Build and validate the complete static Storybook
```

## Storybook

Shared **Introduction**, **Theme & Tokens**, and framework-neutral **Patterns & Best Practices**
appear at the top level, before the framework catalogs.
One entry point contains two peer catalogs: **React** (components and React-specific guides)
and **Web Components** (native custom-element stories). The Web Components catalog currently
contains the Phase 1 overview; Phase 2 adds stories for the six existing atoms and two molecules.

`npm run storybook` manages both preview servers automatically; open `http://localhost:6006`.
Ports 6006 and 6007 must be free. There is no separate Web Components startup command.
`npm run build-storybook` produces one deployable directory at `libs/ui/storybook-static`,
including the composed preview under `web-components/`.

```bash
npm run storybook:check-links # Validate built Storybooks and shipped AI documentation links
npm run storybook:test # Existing React interaction tests (native coverage follows in Phase 2)
npm run storybook:visual # Existing React visual tests
```

React titles receive their `React/` prefix in `.storybook/main.ts`; do not add it again in
individual story/MDX titles. Framework-neutral MDX pages are explicitly listed by basename and
canonical ID in `bin/storybook/shared-docs.json` and receive no prefix. Keep basenames unique.
The shared pages cover welcome/contribution, default-theme JSON references, UI/UX principles,
and cross-device UX. Guides teaching React APIs (including Atomic Design examples, theme hooks,
FAQ, AI integration, form configuration, and component-based patterns) remain under React.
Use the IDs in the generated `index.json` for links. Both pre-migration URLs and interim
`react-` URLs for shared pages resolve to their canonical IDs, retaining args, globals, and anchors.
`bin/storybook/legacy-react-ids.json` is the frozen pre-migration inventory, not a generated
list to refresh when a story disappears. Link checks cover source documentation and the
`llms.txt`, AI Markdown/JSON, and AI JavaScript copied into the UI package.

The Angular/Vue and other Web Components harnesses are unchanged in Phase 1.

## Verification (gd-design-library)

```bash
Expand Down
127 changes: 127 additions & 0 deletions bin/storybook/check-links.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { execFileSync } from 'node:child_process';
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { extname, join, relative, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import { legacyReactIds, sharedDocIds } from './routes.mjs';
import { validateLinks } from './link-validation.mjs';

const root = fileURLToPath(new URL('../../', import.meta.url));
const built = process.argv.includes('--built');
const checkPackage = built || process.argv.includes('--package');
const readJson = (path) => JSON.parse(readFileSync(path, 'utf8'));
let indexes;
if (built) {
indexes = {
react: readJson(join(root, 'libs/ui/storybook-static/index.json')),
webComponents: readJson(join(root, 'libs/ui/storybook-static/web-components/index.json')),
};
} else {
// Indexing does not build the UI or either preview. Package builds can check
// links without creating a UI -> Storybook -> UI build dependency cycle.
const temporary = mkdtempSync(join(tmpdir(), 'gridkit-storybook-index-'));
try {
indexes = {};
for (const [catalog, project] of [
['react', 'ui'],
['webComponents', 'web-components'],
]) {
const output = join(temporary, `${catalog}.json`);
execFileSync(
process.execPath,
[
join(root, 'node_modules/storybook/dist/bin/dispatcher.js'),
'index',
'-c',
'.storybook',
'-o',
output,
'--disable-telemetry',
],
{ cwd: join(root, 'libs', project), stdio: 'inherit' }
);
indexes[catalog] = readJson(output);
}
} finally {
rmSync(temporary, { recursive: true, force: true });
}
}

const errors = [];
for (const id of legacyReactIds) {
const canonical = sharedDocIds.includes(id) ? id : `react-${id}`;
if (!indexes.react.entries[canonical]) errors.push(`Missing migrated entry: ${canonical}`);
}
for (const id of sharedDocIds) {
if (!indexes.react.entries[id]) errors.push(`Missing shared documentation: ${id}`);
if (indexes.react.entries[`react-${id}`]) errors.push(`Shared documentation duplicated under React: ${id}`);
}
for (const entry of Object.values(indexes.react.entries)) {
if (!entry.title.startsWith('React/') && !sharedDocIds.includes(entry.id))
errors.push(`Unclassified entry outside the React root: ${entry.id}`);
}
if (built) {
// The built index applies preview.storySort; shared docs must precede React.
const firstEntries = Object.values(indexes.react.entries).slice(0, sharedDocIds.length);
if (firstEntries.some((entry) => !sharedDocIds.includes(entry.id)))
errors.push('Shared documentation must appear before React in the built sidebar order');
}
if (!indexes.webComponents.entries['introduction-overview--overview']) errors.push('Missing Web Components overview');

const excluded = new Set(['node_modules', '.git', 'dist', 'storybook-static', 'coverage', 'output', '.nx']);
const extensions = new Set([
'.md',
'.mdx',
'.txt',
'.json',
'.ts',
'.tsx',
'.js',
'.jsx',
'.mjs',
'.cjs',
'.html',
'.yml',
'.yaml',
]);
function filesIn(path) {
if (!existsSync(path)) return [];
return readdirSync(path, { withFileTypes: true }).flatMap((entry) => {
if (entry.isSymbolicLink() || excluded.has(entry.name)) return [];
const file = join(path, entry.name);
return entry.isDirectory() ? filesIn(file) : extensions.has(extname(file)) ? [file] : [];
});
}

// The frozen migration registry and its tests intentionally contain old IDs.
const sourceFiles = ['README.md', 'CLAUDE.md']
.map((file) => join(root, file))
.filter(existsSync)
.concat(
...['libs', '.github'].map((dir) => filesIn(join(root, dir))),
filesIn(join(root, 'bin')).filter((file) => !file.startsWith(join(root, 'bin', 'storybook') + sep))
);
const packageFiles = [];
if (checkPackage) {
for (const file of ['llms.txt', 'ai/README.md']) {
if (!existsSync(join(root, 'dist/libs/ui', file))) errors.push(`Missing package artifact: ${file}`);
}
packageFiles.push(...filesIn(join(root, 'dist/libs/ui/ai')));
for (const file of readdirSync(join(root, 'dist/libs/ui'))) {
if (/\.(md|txt)$/.test(file)) packageFiles.push(join(root, 'dist/libs/ui', file));
}
}
let checked = 0;
for (const file of [...sourceFiles, ...packageFiles]) {
const result = validateLinks(readFileSync(file, 'utf8'), indexes);
checked += result.checked;
errors.push(...result.errors.map(({ line, message }) => `${relative(root, file)}:${line}: ${message}`));
}
if (errors.length) {
console.error(errors.join('\n'));
process.exitCode = 1;
} else {
console.log(
`Verified ${legacyReactIds.length} migrated entries and ${checked} Storybook links in source${checkPackage ? ' and built package artifacts' : ''}.`
);
}
Loading
Loading