Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f98d8d5
feat: Add spike-react-native package with GdButton integration and co…
alex-povolotskiy-exa Jul 10, 2026
41ca5c9
feat(ui): Optimize A2UI rendering with caching and React.memo integra…
alex-povolotskiy-exa Jul 13, 2026
b96b4a0
feat: CTORNDSD-581: Replace hardcoded style values with theme-driven …
alex-povolotskiy-exa Jul 13, 2026
74c1ea6
feat: CTORNDSD-581: Introduce theme-driven style resolvers for wrappe…
alex-povolotskiy-exa Jul 13, 2026
7e47cec
chore: Remove deprecated token resolver tests and reproduction artifa…
alex-povolotskiy-exa Jul 14, 2026
0d7fdec
feat: Add isolation and performance test harnesses for CTORNDSD-581
alex-povolotskiy-exa Jul 14, 2026
8fff226
chore: CTORNDSD-581: add usage documentation and examples to web-comp…
Zlodej43sm Jul 14, 2026
526b56e
Merge branch 'develop' into feature/CTORNDSD-580
Zlodej43sm Jul 22, 2026
96e8e4c
Potential fix for pull request finding
Zlodej43sm Jul 22, 2026
984830d
chore: remove CTOR ticket references, clean up comments and metadata
Zlodej43sm Jul 22, 2026
d22f3e9
Potential fix for pull request finding
Zlodej43sm Jul 22, 2026
89ca91e
Potential fix for pull request finding
Zlodej43sm Jul 22, 2026
ca03415
Potential fix for pull request finding
Zlodej43sm Jul 22, 2026
2e34e72
Potential fix for pull request finding
Zlodej43sm Jul 22, 2026
b86c5dd
chore: exclude `spike-react-native` from lint scripts, update import …
Zlodej43sm Jul 22, 2026
826a499
Merge branch 'feature/CTORNDSD-580' into feature/CTORNDSD-581
Zlodej43sm Jul 22, 2026
4450a19
chore: remove performance strategy document, simplify `spike-react-na…
Zlodej43sm Jul 22, 2026
3d4b6fd
chore: add JSX typings for gd-form elements, update dependencies, and…
Zlodej43sm Jul 22, 2026
3b2b0ed
chore: remove CTOR ticket references and streamline metadata
Zlodej43sm Jul 22, 2026
2cbfc03
chore: update `.prettierignore` to exclude additional SSR harness files
Zlodej43sm Jul 22, 2026
5dd5656
Merge branch 'develop' into feature/CTORNDSD-581
Zlodej43sm Aug 5, 2026
2aba814
Feature/ctorndsd 646 (#20)
Zlodej43sm Aug 13, 2026
d72618f
[SPIKE]feat:CTORNDSD-590: react native support (#16)
Zlodej43sm Aug 13, 2026
ab61c54
Add GridKit demo run card
Zlodej43sm Sep 18, 2026
fcff080
docs: Update component generation date in auto-generated docs
Zlodej43sm Sep 21, 2026
0b7b705
feat: Add `gd-avatar` component with tests, React adapter, and docume…
Zlodej43sm Sep 21, 2026
593680a
Merge branch 'develop' into feature/CTORNDSD-580
Zlodej43sm Sep 21, 2026
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
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ __screenshots__
.claude/tools/node_modules
/libs/ui/scripts/output/*
/libs/ui/.size-limit.json
/libs/web-components/bundle-size-report.json

# Generated by `npm run check:web-components-ssr` — regenerated on every run, so tracking them
# makes the working tree dirty for anyone who runs the check. The command that produces them is
# step 6 of the walkthrough in the root README.
/libs/web-components/harness/ssr-dsd-static.html
/libs/web-components/harness/ssr-dsd-hydrated.html

# Vitest artifacts
.vitest-artifacts
Expand All @@ -84,3 +91,10 @@ plans/
.mcp.json
AGENTS.md
/stats.html
/libs/web-components/screenshots/

# CTORNDSD-646b platform fixtures — build output and generated types only; the fixture
# sources themselves are tracked. See docs/webcomponents-migration/08-react-and-nextjs.md.
/fixtures/*/.next/
/fixtures/*/next-env.d.ts
.vitest-attachments
5 changes: 5 additions & 0 deletions .nxignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# CTORNDSD-646b: throwaway platform fixtures live here. Next.js generates
# `fixtures/next-ssr-check/.next/dev/package.json` with no `name` field, which makes Nx fail to
# process the project graph for the ENTIRE repo ("The projects in the following directories have no
# name provided"). Every `nx` command — lint, build, test — breaks until this is excluded.
fixtures/**
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ dist/
libs/ui/src/stories
libs/ui/scripts/
libs/ui/stats.html
libs/web-components/harness/ssr-dsd-static.html
libs/web-components/harness/ssr-dsd-hydrated.html
package.json
package-lock.json
.github/
Expand Down
119 changes: 119 additions & 0 deletions DEMO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# GridKit Demo — Run Card

Hi there!

Three weeks of work in short:

we rebuilt five GridKit components twice — as Web Components in Lit, and as a React Native app on iOS — and pulled their shared token and state logic into `gd-design-core`, now covered by 121 tests.

Alongside, two new Dynamic Builder checklists document

- what runtime AI-generated UI costs and
- how to pick a model.

## Pre-flight — Stops 3–8 only

```bash
npm install && npm run demo:setup # ~3 min, do it before the meeting
npm run demo:harness # :5173 — Stops 3–5
npm run check:web-components-ssr && npm run check:web-components-size # SSR pages + warm size cache
npm run demo:next # :5373 — Stop 5 insert
```

**Tabs to open in advance:** [1 · fidelity](http://localhost:5173/harness/fidelity-check.html) · [2 · shell-isolation](http://localhost:5173/harness/shell-isolation-check.html) · [3 · form-participation](http://localhost:5173/harness/form-participation-check.html) · [Next.js fixture](http://localhost:5373/) — plus the Simulator window and the two screenshots (Tabs 4–6).

## Stop 1 — Dynamic Builder: performance

- **Open** — [Performance Improvement Checklist](https://griddynamics.atlassian.net/wiki/spaces/RNDM/pages/4685791299/Dynamic+Builder+Performance+Improvement+Checklist)
- **Why** — one A2UI page load: ~74.6k tokens, ~21s, ~$0.09
- **Shows** — nine levers:
- send only the schema a screen needs
- merge repeated component groups
- cap nesting
- patch in the browser instead of regenerating
- cache stable inputs
- feature-flag new surfaces
- ask whether the screen needs generated UI
- **Plus** — a proposed SLA: ≤8s, ≤8k tokens, ≤$0.02 per call. Not ratified — that is the decision to ask for

## Stop 2 — Dynamic Builder: model choice

- **Open** — [Model Benchmark & Selection Checklist](https://griddynamics.atlassian.net/wiki/spaces/RNDM/pages/4685955158/Dynamic+Builder+Model+Benchmark+Selection+Checklist)
- **Why** — we pick models by inheritance, not evidence
- **Shows** — which model for which job:
- classification and extraction — Haiku-class or o3-mini
- hard reasoning — Gemini 2.5 Pro or Sonnet-class
- A2UI — Flash Lite with a scoped schema
- preview models — not yet
- **Behind it** — timings from 38.65s to 204.57s, plus public pricing. No controlled study exists, and Haiku 3.5 is already retired

## Stop 3 — Fidelity check

- **Open** — Tab 1 · [localhost:5173/harness/fidelity-check.html](http://localhost:5173/harness/fidelity-check.html)
- **Why** — sets the visual bar before Stop 4 strips the theme. Keep it to 15 seconds
- **Shows**
- five components rebuilt in Lit — button, checkbox, typography, input, select
- running on the real GridKit theme, not lookalikes
- **Look for** — gold primary button, secondary/outlined/disabled/loading, gold checkboxes, Fira Sans headings, a working select
- **Note** — skip this and Stop 4 looks like a broken port

## Stop 4 — Shell isolation

- **Open** — Tab 2 · [localhost:5173/harness/shell-isolation-check.html](http://localhost:5173/harness/shell-isolation-check.html)
- **Why** — CTORNDSD-286: a host app's global CSS leaked into our components
- **Shows**
- at the top, a global reset wrecks the plain Emotion button — the old bug, reproduced
- the Lit component beside it is untouched
- **Read the JSON, not the buttons** — the theme is empty on purpose, so both render grey
- **Last two lines** — the Lit-shell shortcut blocked its own styling too. Why we dropped it

### ↳ `npm run check:web-components-size`

- **Why** — bundle cost is the main upside
- **Shows** — 7–16× smaller per component:
- Button 2.05 vs 18.66 kB
- Checkbox 2.11 vs 23.22 kB
- Typography 1.09 vs 17.64 kB
- Input 2.53 vs 18.51 kB
- Select 2.91 vs 27.69 kB
- **Headline** — all five together, **11.20 kB against 105.72 kB**. 18.08 kB counting Lit's runtime, which most React apps have not already paid. The last line is a CI gate

## Stop 5 — Form participation

- **Open** — Tab 3 · [localhost:5173/harness/form-participation-check.html](http://localhost:5173/harness/form-participation-check.html)
- **Why** — proves these are real form controls, not a mock-up
- **Shows**
- the app's own CSS reaches in where we allow it — `::part()` gives the magenta ring, cyan fill, red checkbox
- a plain descendant selector still cannot — 4 pixels changed, not 99
- **Also** — they behave like inputs: listed in `form.elements`, submitted into `FormData`, block empty submit, reset cleanly

### ↳ `curl -s http://localhost:5373/ | grep -c shadowrootmode`

- **Against** — the Next.js fixture at [localhost:5373](http://localhost:5373/)
- **Why** — the honest cost. Lead with it
- **Shows**
- both counts come back `0` — no Declarative Shadow DOM, no server-rendered heading
- with JavaScript off, unstyled text and no headings — worse than React today for SEO and first paint
- **Two more** — our token barrel breaks inside a Server Component; mount is 2.3–3.4× slower; nested theming has no equivalent yet

## Stop 6 — iOS Simulator, live

```
xcrun simctl boot "iPhone 16" && open -a Simulator # ~30s
npm run dev:react-native -- --ios --localhost
```

- **Turn to** — Tab 4 · the Simulator window, and scroll it. Live, not a screenshot
- **Why** — can we leave the DOM, not just React?
- **Shows** — the same five components off the same `gd-design-core`:
- gold button, GridKit type scale
- input with label and helper text
- select with chevron
- **The point** — one source, two outputs: 1.68 MB of native bytecode for the device, 392 kB of JavaScript for a browser. No DOM-specific code

## If it breaks

- Stops 3–5: `npm run verify:web-components` · Stops 6–8: `npm test --workspace=libs/react-native` + `npm run test:design-core`
- Ports: `kill $(lsof -ti:5173)` · `:5273` · `:5373` · `:8081` — teardown: `pkill -f "expo start"; xcrun simctl shutdown all`

Full detail: `docs/webcomponents-migration/README.md` · `libs/react-native/FINDINGS.md` · the two Confluence pages above.
201 changes: 201 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ Nx monorepo containing the GridKit design system packages.
| `gd-form-configurator` | ![npm](https://img.shields.io/npm/v/gd-form-configurator) | JSON-Schema form engine (AJV + Zustand) |
| `gd-form-configurator-react` | ![npm](https://img.shields.io/npm/v/gd-form-configurator-react) | React bindings for gd-form-configurator |

Not published, and under active investigation:

| Package | Description |
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `gd-design-core` (`libs/design-core`) | Framework-agnostic state stores + token resolution. No React, no Lit, no `gd-design-library` dependency |
| `web-components` (`libs/web-components`) | Lit custom-element port of 5 GridKit atoms. `private: true` — see [Web Components spike](#web-components-spike-ctorndsd-646) |

## Quick start

Install the primary package:
Expand Down Expand Up @@ -82,3 +89,197 @@ npm run build:form-configurator && npm run publish:form-configurator
```bash
npm run crc ComponentName
```

## Web Components spike (CTORNDSD-646)

An investigation into porting GridKit from React to Lit custom elements. **Every demo below runs from
the repo root — you never need to `cd` into a package or fixture.**

### One-time setup

```bash
npm install # if you haven't already
npm run demo:setup # builds dist/ + installs the two fixtures (~2-3 min)
```

`demo:setup` builds `gd-design-library`, `gd-design-core`, and `web-components`, then installs
`fixtures/react19-check` and `fixtures/next-ssr-check`. Some demos need those build artifacts; the
demo index tells you which.

### Test it step by step

Twelve steps, in order, all from the repo root. Each one lists the command, what you should see, and
which finding it proves. **Steps 1–6 are non-interactive** — run them and read the terminal. **Steps
7–11 open a browser.** Step 12 runs the whole automated set at once.

If you only have five minutes, run **step 12**.

#### 1. Type-check

```bash
npm run type-check:web-components
echo $? # 0
```

**Success is silent** — `tsc` prints nothing and exits `0`. Any output at all means a failure. This
checks two projects: the shipped library and the harness. The harness one was broken for a while and is
now covered.

#### 2. Lint

```bash
npx nx lint web-components
npx nx lint design-core
```

Expect `Successfully ran target lint` from both, with no rule violations listed above it. `nx` prints a
problem count only when there are problems.

#### 3. Framework-agnostic core tests

```bash
npm run test:design-core
```

Expect **73 passed (6 files)**. Includes the `resetTo` action added because form reset was silently
keeping a checkbox checked — see `FINDINGS.md` §17.1.

#### 4. Component tests in a real browser

```bash
npm run test:web-components
```

Expect **35 passed (4 files)**, running in real Chromium. This is the suite that found the `gd-input`
accessibility bug on its first run (§18.2). It covers the Input cursor guard, the checkbox
`attribute: false` constraint, form participation, the shared-stylesheet cache, and axe.

> Real Chromium is a constraint, not a preference — jsdom does not reliably implement Constructable
> StyleSheets, the `popover` attribute, or Declarative Shadow DOM.

#### 5. Bundle size and the regression gate

```bash
npm run check:web-components-size
```

Prints a per-atom Lit-vs-React table, then expect `✓ no bundle-size regressions`. Ballpark: 5 atoms at
**~11 kB** gzip (**~18 kB** including the `lit` runtime) against **~106 kB** for React+Emotion —
§3, §18.5.

Exact totals drift by a few dozen bytes between builds because Rollup redistributes shared-helper bytes
between chunks whenever any one chunk changes. That is why the gate's tolerance is 10% rather than 0 —
see [`10-performance-report.md`](./docs/webcomponents-migration/10-performance-report.md).

To prove the gate actually fails, edit a number down in
`libs/web-components/bundle-size-baseline.json` and re-run: it should exit `1`.

#### 6. Server rendering with zero client JavaScript

```bash
npm run check:web-components-ssr
```

Generates `libs/web-components/harness/ssr-dsd-static.html` (**0** `<script>` tags) and
`ssr-dsd-hydrated.html` (1). Expect `DSD present for gd-button: true` and the same for
`gd-typography`. Both pages are opened in step 10. Proves §2.

This step is worth running on its own after any change to a component's `render()`: it is the only check
that exercises the components in **Node**, where there is no shadow root and no Constructable
StyleSheets. It caught a real regression that the browser tests could not — §18.7.

#### 7. Start the harness and Storybook

```bash
npm run demo:harness # prints the demo map, serves harness pages on :5173
npm run storybook # SECOND terminal — the React baseline on :6006
```

`demo:harness` prints every page with its URL, what it proves, and any missing prerequisite. Run
`npm run demo:index` on its own any time for the same map.

#### 8. Style isolation — the core justification

Open **`/harness/shell-isolation-check.html`**. Read the JSON block on the page:

```json
{
"Global reset broke the plain Emotion button (control) — expect true": true,
"Global reset leaked INTO native gd-button Shadow DOM — expect false": false,
"Global reset leaked INTO gd-button-shell Shadow DOM — the question this test answers": false,
"gd-button-shell's own real Button styling actually rendered — separate question": false
}
```

The **control** matters: the first `true` proves the CTORNDSD-286 collision is real and reproduced. The
last `false` is the Lit-wraps-React shell failing — it blocks the bad styles _and_ its own good ones.
§1, §15.

#### 9. Forms, CSS Parts, and the remaining findings

Open **`/harness/form-participation-check.html`**. You should see the button ringed in magenta dashes
and the input filled cyan — that is light-DOM CSS reaching **through** the shadow boundary via
`::part()`. The control rule proves a plain descendant selector cannot. Then submit the empty form: the
browser blocks it with a native validation bubble. Results also on `window.__formCheck`. §17.1, §17.2.

Open **`/harness/remaining-findings-repro.html`** for three more: Input cursor stability under late
external writes, `document.querySelector('h1')` returning nothing for a shadow-rendered heading, and
Select's native `popover` dismissal. §4, §5, §6.

#### 10. Visual fidelity, SSR, and speed

| Open | What to look for |
| -------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `/harness/fidelity-check.html` | All 5 atoms with the real theme. Compare side by side against Storybook on :6006 |
| `/harness/ssr-dsd-static.html` | Fully styled with **zero** JavaScript. View source — no `<script>` tags |
| `/harness/ssr-dsd-hydrated.html` | Same markup, now hydrated without discarding the server-rendered node |
| `/harness/perf-check.html` | **Auto-runs, ~30s.** React vs native Lit vs shell. Raw per-trial numbers on `window.__PERF_RESULTS__` |

Speed figures are machine-specific — the _percentages_ in
[`10-performance-report.md`](./docs/webcomponents-migration/10-performance-report.md) are the portable
result, not the absolute milliseconds.

#### 11. The two framework fixtures

```bash
npm run demo:react19 # :5273
```

Read `window.__react19Check`. Expect object props to reach the property natively (`theme` by
reference, no stringified attribute) but `onGdChange` to fire **zero** times — silently, with no
warning. That silence is why the React wrapper layer should be generated. §17.3.

```bash
npm run demo:next # :5373
```

Then, in another terminal:

```bash
curl -s http://localhost:5373/ | grep -c shadowrootmode # expect 0
curl -s http://localhost:5373/ | grep -c '<h2' # expect 0
```

Both zeros are the finding: **Next.js emits no Declarative Shadow DOM**, so with JavaScript off the
page has unstyled text and no headings. The page also shows a server-side import probe failing with
`createContext is not a function` — the token barrel is not RSC-safe. §17.4.

#### 12. Everything automated, in one command

```bash
npm run verify:web-components
```

Runs steps 1–6 in sequence: type-check, lint, both test suites, the SSR check, and the size gate. Exit
code `0` means the whole non-interactive set passed. This is the CI gate.

### Read the results

| Where | What |
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| [`docs/webcomponents-migration/`](./docs/webcomponents-migration/) | The decision document set — 15 files. **Start with its `README.md`** |
| [`libs/web-components/FINDINGS.md`](./libs/web-components/FINDINGS.md) | The chronological engineering log; every measurement quoted in the docs |
| [`libs/web-components/README.md`](./libs/web-components/README.md) | Component API reference — props, events, slots, CSS parts, form participation |

Every claim in the docs is labelled **measured**, **reasoned**, or **not attempted**, so it is always
clear which conclusions rest on evidence.
Loading
Loading