@adia-ai/adia-ui-factory 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/.mcp.json +8 -0
  3. package/CHANGELOG.md +55 -0
  4. package/README.md +44 -0
  5. package/bin/adia-lint +270 -0
  6. package/bin/adia-scaffold +411 -0
  7. package/commands/adia-compose.md +10 -0
  8. package/commands/adia-genui.md +12 -0
  9. package/commands/adia-migrate.md +10 -0
  10. package/commands/adia-orient.md +14 -0
  11. package/commands/adia-scaffold.md +17 -0
  12. package/commands/adia-verify.md +10 -0
  13. package/commands/adia-wire.md +13 -0
  14. package/hooks/hooks.json +15 -0
  15. package/package.json +40 -0
  16. package/references/a2ui-mcp-tools.md +64 -0
  17. package/references/authoring-components.md +88 -0
  18. package/references/component-model.md +71 -0
  19. package/references/data-and-hydration.md +84 -0
  20. package/references/genui-a2ui.md +62 -0
  21. package/references/llm.md +79 -0
  22. package/references/migration.md +57 -0
  23. package/references/project-shapes.md +96 -0
  24. package/references/shell-admin.md +65 -0
  25. package/references/shell-chat.md +44 -0
  26. package/references/shell-editor.md +48 -0
  27. package/references/shell-embed.md +33 -0
  28. package/references/shell-simple.md +38 -0
  29. package/references/spa-architecture.md +116 -0
  30. package/references/ssr-integration.md +117 -0
  31. package/references/verification.md +39 -0
  32. package/skills/adia-ui-compose/SKILL.md +57 -0
  33. package/skills/adia-ui-data/SKILL.md +62 -0
  34. package/skills/adia-ui-factory/SKILL.md +113 -0
  35. package/skills/adia-ui-genui/SKILL.md +74 -0
  36. package/skills/adia-ui-llm/SKILL.md +51 -0
  37. package/skills/adia-ui-migrate/SKILL.md +64 -0
  38. package/skills/adia-ui-project/SKILL.md +77 -0
  39. package/skills/adia-ui-shells/SKILL.md +62 -0
  40. package/skills/adia-ui-spa/SKILL.md +52 -0
  41. package/skills/adia-ui-ssr/SKILL.md +52 -0
  42. package/skills/adia-ui-verify/SKILL.md +44 -0
@@ -0,0 +1,88 @@
1
+ # Authoring a project component
2
+
3
+ When no catalog primitive composes to the need, author a light-DOM custom element. The discipline below is what separates a component that themes, scales, and survives HMR from one that fights the system. It mirrors the framework's own audit gates — the `adia-lint` hook and the MCP's `check_anti_patterns` mechanize the catchable parts.
4
+
5
+ ## The skeleton
6
+
7
+ A component is three files in one folder (`components/<tag>/`):
8
+
9
+ ```text
10
+ components/my-thing/
11
+ ├── my-thing.js # the class + side-effect registration
12
+ ├── my-thing.css # the @scope block (token-only)
13
+ └── my-thing.class.js # (optional) the class alone, for tests/subclassing
14
+ ```
15
+
16
+ ```js
17
+ // my-thing.js
18
+ import { defineIfFree } from '@adia-ai/web-components/core/register.js';
19
+ import { UIElement } from '@adia-ai/web-components/core/element.js';
20
+
21
+ class UIMyThing extends UIElement {
22
+ static properties = { /* declared props become signals */ };
23
+ connected() { /* set up; pair every addEventListener with a teardown */ }
24
+ disconnected() { /* remove every listener added in connected() */ }
25
+ }
26
+ defineIfFree('my-thing', UIMyThing); // idempotent — safe on HMR / double import
27
+ export { UIMyThing };
28
+ ```
29
+
30
+ ## The two-block `@scope` CSS (mandatory shape)
31
+
32
+ Every component stylesheet is scoped to its tag and split into a zero-specificity token block and a base block:
33
+
34
+ ```css
35
+ @scope (my-thing) {
36
+ :where(:scope) { /* specificity (0,0,0) — themes + consumers override cleanly */
37
+ --my-thing-bg: var(--a-ui-bg);
38
+ --my-thing-fg: var(--a-ui-text);
39
+ --my-thing-radius: var(--a-radius);
40
+ --my-thing-px: var(--a-ui-px);
41
+ }
42
+ :scope { /* specificity (0,1,0) — base styles, consume component tokens only */
43
+ display: flex;
44
+ padding-inline: var(--my-thing-px);
45
+ background: var(--my-thing-bg);
46
+ color: var(--my-thing-fg);
47
+ border-radius: var(--my-thing-radius);
48
+ }
49
+ :scope[variant="outline"] { /* variants = token overrides only, never layout */
50
+ --my-thing-bg: transparent;
51
+ }
52
+ }
53
+ ```
54
+
55
+ **Why two blocks:** `:where(:scope)` is zero-specificity, so a parent theme provider or a consumer override beats your defaults without `!important`. Collapsing into a single `:scope` block breaks theme switching. The base block consumes the component's _own_ tokens (`--my-thing-*`), which alias the system tokens (`--a-*`) — so re-theming flows through one indirection layer.
56
+
57
+ ## Invariants
58
+
59
+ - **Token-only.** No raw `#hex` / `rgb()` / `oklch()` and no raw px ≥ 3 in component CSS — always `var(--a-*)` (border-hairlines 1–2px are the only carve-out, with a comment).
60
+ - **Size-agnostic chrome.** Never set `width`/`height`/`inline-size` on the tag's `:scope`. The component owns _intrinsic_ sizing (`min-height`, `padding`, `gap`); the **consumer** (a layout primitive or explicit style) owns extent. Width-on-tag fights the placer and the resizable frame.
61
+ - **Light DOM only.** Never `attachShadow`. No `::slotted()` (a shadow-DOM API) — style slotted content via `:scope > [slot="x"]`. To _read_ projected children, use `logicalChildren` / `logicalSlotted` from `@adia-ai/web-components/core/logical-children` — plain `this.children` misses children rendered through `${items.map(…)}` and the `display:contents` trap.
62
+ - **Guard define and boot.** `defineIfFree` guards re-definition; also guard the boot (`#booted` flag in `connected()`) — the callback fires again whenever the element moves in the DOM, which otherwise double-renders.
63
+ - **Symmetric lifecycle.** Every `addEventListener` in `connected()` has a matching `removeEventListener` in `disconnected()`. Use a stable handler reference (private field `#onClick = (e) => …`), not an inline arrow — `removeEventListener` needs reference equality.
64
+ - **Data down, events up.** Sub-components receive state via properties (`.rec = …`) and emit `CustomEvent`s; they never reach into a parent's internals.
65
+ - **Font-family floor.** A text-bearing component floors its family to a token: `font-family: var(--my-thing-family, var(--a-font-family-ui))` — otherwise it inherits a broken host font (the dead-`--a-font` trap resolves to UA serif).
66
+
67
+ ## Anti-patterns (the lint seeds)
68
+
69
+ The mechanizable smells the framework audits — these seed the phase-(c) hook and overlap the MCP's `check_anti_patterns`. Grep hints in parens.
70
+
71
+ | Smell | Fix |
72
+ | --- | --- |
73
+ | Shadow DOM (`attachShadow`) | light DOM only |
74
+ | Raw color in component CSS (`#`, `rgb(`, `oklch(`) | `var(--a-*)` token |
75
+ | Raw px ≥ 3 without carve-out comment | `var(--a-space-*)` |
76
+ | `::slotted(` | `:scope > [slot="x"]` |
77
+ | `width`/`height`/`inline-size` on the tag `:scope` | consumer owns extent |
78
+ | Dead font token (`--a-font` ≠ `--a-font-family*`) | floor to `--a-font-family-ui` |
79
+ | Single `:scope` block (no `:where(:scope)` token block) | two-block `@scope` |
80
+ | Boolean prop `default: true` | flip the name (`closable`→`permanent`) so absent = false |
81
+ | `attr:` in a property def | `attribute:` |
82
+ | `title`/`error`/`active`/`disabled` as a prop name | `heading` / `danger` / `value`/`step` / `readonly` |
83
+ | Bare `<div>` for layout; raw `<button>`/`<input>` | `<col-ui>`/`<row-ui>`/`<grid-ui>`; the `*-ui` control |
84
+ | Card/drawer body not wrapped in `<section>` | `<card-ui><section>…</section></card-ui>` |
85
+ | Hardcoded `open` on `<modal-ui>`/`<drawer-ui>` | drive via `.open = true` |
86
+ | `new URL('…', import.meta.url)` literal for a corpus path | hold the path in a variable first |
87
+
88
+ Run the framework's own gates when you have the repo (`npm run check:anti-patterns`, `audit:*`), and `mcp__a2ui__check_anti_patterns` / `validate_schema` on generated markup.
@@ -0,0 +1,71 @@
1
+ # The adia-ui component model
2
+
3
+ How to _think_ about the catalog. The **live** catalog is the a2ui MCP (`get_component_map`, `lookup_component`, `get_traits`) — query it for exact names, props, and counts; don't memorize them here (they drift per release). This file teaches the vocabulary so the MCP's answers make sense.
4
+
5
+ ## Three tiers
6
+
7
+ | Tier | Package | What it is | Examples (shape, not a contract) |
8
+ | --- | --- | --- | --- |
9
+ | **Primitives** | `@adia-ai/web-components` | Atomic custom elements — controls, layout, display, overlays | `<button-ui>` `<input-ui>` `<select-ui>` `<table-ui>` `<card-ui>` `<modal-ui>` `<drawer-ui>` |
10
+ | **Layout primitives** | (same) | The box model — never hand-roll a bare `<div>` for layout | `<col-ui>` `<row-ui>` `<grid-ui>` `<stack-ui>` `<page-ui>` `<section-ui>` |
11
+ | **Traits** | (subpath) | Reusable _behaviors_ attached to any element — not data, not components | `pressable` `focusable` `draggable` `ripple` `scale-press` `roving-tabindex` `focus-trap` |
12
+ | **Composites / shells** | `@adia-ai/web-modules` | Multi-component surfaces / page chrome | `<admin-shell>` `<admin-sidebar>` `<chat-shell>` `<editor-shell>` |
13
+
14
+ **Naming:** kebab-case with a `-ui` **suffix** (`<button-ui>`, not `<a-button>`). Shells/composites carry a domain prefix (`<admin-*>`, `<chat-*>`).
15
+
16
+ **Rule of first resort:** reach for a catalog primitive before authoring anything. Never use a raw `<button>`/`<input>`/`<div>` where a `*-ui` primitive exists — raw elements skip focus rings, theming, density, and form association. Author a project component (see [authoring-components.md](authoring-components.md)) only when no primitive composes to the need.
17
+
18
+ ## Registration is a side effect of import
19
+
20
+ Importing a component module **registers its tag**. Two forms:
21
+
22
+ ```js
23
+ import '@adia-ai/web-components'; // the barrel — registers every primitive (incl. router-ui)
24
+ import '@adia-ai/web-components/components/button/button.js'; // one component
25
+ ```
26
+
27
+ Registration uses `defineIfFree('button-ui', UIButton)` internally — idempotent, so double-import is safe. The non-registering class import (`.../button/class`) exists for tests/subclassing. **In SSR this import must be client-only** — see [ssr-integration.md](ssr-integration.md).
28
+
29
+ ## Signals, not a virtual DOM
30
+
31
+ Components extend `UIElement` (or `UIFormElement` for form-participating controls) and use fine-grained signals:
32
+
33
+ ```js
34
+ import { UIElement, signal, computed, effect } from '@adia-ai/web-components/core/element.js';
35
+ ```
36
+
37
+ - `signal(v)` — reactive value (`.value` get/set)
38
+ - `computed(() => …)` — memoized derived value
39
+ - `effect(() => …)` — runs on dependency change; auto-cleans on disconnect
40
+
41
+ Declared `static properties` are wrapped as signals automatically, so setting `el.disabled = true` re-renders. Don't run a parallel `CustomEvent`-only state path that competes with signals.
42
+
43
+ ## Traits — behavior by declaration
44
+
45
+ ```html
46
+ <button-ui traits="pressable scale-press ripple">Save</button-ui>
47
+ ```
48
+
49
+ or programmatically: `static traits = [pressable, scalePress]`. A trait is a factory (`defineTrait({ name, setup({host}) { …; return cleanup } })`) that manages attributes/events and tears itself down on disconnect. Query the live set with the MCP's `get_traits`.
50
+
51
+ ## Tokens — the only styling currency
52
+
53
+ Three levels, all `--a-*`:
54
+
55
+ | Level | Role | Examples |
56
+ | --- | --- | --- |
57
+ | **L1 primitive scales** | dimensionless base values | `--a-space-2` `--a-size-md` `--a-radius-sm` `--a-duration-fast` `--a-font-family-ui` |
58
+ | **L2 semantic families** | role vocabulary | `--a-primary` `--a-danger` `--a-accent-bg` |
59
+ | **L3 state × role** | what components consume | `--a-accent-bg-hover` `--a-danger-fg-active` `--a-ui-bg-disabled` |
60
+
61
+ - **Color scheme** uses `light-dark()` at the token layer — never swap tokens by hand. Toggle with `<toggle-scheme-ui scheme="auto" target=":root" persist>`.
62
+ - **Density** scales spacing via `--a-density` set at a provider boundary (`<div style="--a-density: 0.8">`).
63
+ - **Zero raw colors, zero raw px ≥ 3** in component CSS — always a token. This is mechanized by the `adia-lint` hook and by the MCP's `check_anti_patterns`.
64
+
65
+ ## "Registers" — typographic treatments, opt-in
66
+
67
+ A _register_ (e.g. `verse`, `prose`) is a typographic treatment applied two ways together: **link the register stylesheet** (`styles/verse.css`) **and** put the **attribute** on the surface (`<my-surface verse>`). One without the other is a no-op — a common smell. Body/UI text defaults to `--a-font-family-ui`; registers opt a subtree into a different family/rhythm.
68
+
69
+ ## Where this is incomplete
70
+
71
+ The deep internals — the `html` template engine's escaping contract, `BaseController` delegation, the icon loader (`installIconLoadersForRegistered` + Vite `import.meta.glob`), `@bp` responsive notation, and the A2UI runtime/`<a2ui-root>` resolver — are framework-owned and version-specific. Treat the MCP (`lookup_component`, `search_chunks`) as the source of truth for them rather than encoding them here.
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: data-and-hydration
3
+ load-when: wiring an adia-ui app's data-flow, state ownership, content hydration, or section registration
4
+ load-size: ~2.5k tokens
5
+ required-for: [adia-ui-data — all modes]
6
+ ---
7
+
8
+ # Data, state & hydration — patterns
9
+
10
+ Code shapes for the five data-flow patterns, the three hydration paths, and section wiring. The ownership rules (single-owner · projections-only · attribution) are the `adia-ui-data` rubric gates.
11
+
12
+ ## The five patterns
13
+
14
+ **1 · Signals** — fine-grained reactivity (no virtual DOM).
15
+
16
+ ```js
17
+ import { signal, computed, effect } from '@adia-ai/web-components/core/signals.js';
18
+ const view = signal('live');
19
+ effect(() => render(view.value)); // re-runs on change; auto-cleans on disconnect
20
+ ```
21
+
22
+ **2 · Service / Controller / Command** — CRUD with undo. The Service is **async from day one** (so an in-memory impl can later swap for a remote one behind the same interface); the Controller orchestrates signals + service; Commands record patches for undo.
23
+
24
+ ```js
25
+ class InMemoryTaskService { async create(d){…} async update(id,d){…} async delete(id){…} }
26
+ // swap for RemoteTaskService (same interface) → Controller + Commands unchanged
27
+ ```
28
+
29
+ **3 · DataClient + mappers** — the UI reads typed **projections**, never a backend. The pure mapper is the fixtures⇄API swap seam.
30
+
31
+ ```js
32
+ const labs = await client.read({ type: 'LabRecommendationSet', params }); // → projection
33
+ // mapper lives in app/shared/corpus/mappers/, pure: (sources) => Projection
34
+ await client.mutate({ type: 'order', payload }, { action_source: btn.dataset.action }); // attribution REQUIRED
35
+ ```
36
+
37
+ **Attribution `[gate]`:** every `mutate` passes an `action_source`; the client throws without it.
38
+
39
+ **4 · Property-API binding** — populate catalog components by property, not children.
40
+
41
+ ```js
42
+ table.columns = [{ key:'name', label:'Name', sortable:true }];
43
+ table.data = rows; // NOT: append <option>/<tr> children post-connect —
44
+ select.options = opts; // the element auto-stamps its slots at connected()
45
+ ```
46
+
47
+ **5 · Declarative `data-*`** — static flows; state is CSS.
48
+
49
+ ```html
50
+ <main data-auth> … </main>
51
+ <style>[data-auth] form { display: block; }</style>
52
+ ```
53
+
54
+ ## The three hydration paths
55
+
56
+ **SPA — self-boot.** The surface is a self-booting container; it fetches and renders itself.
57
+
58
+ ```js
59
+ connected() { if (this.#booted) return; this.#booted = true; this.#load(); }
60
+ ```
61
+
62
+ **SSR — server → props → client.** The framework fetches on the server, seeds components as initial props, and the client refreshes via property binding (React `ref`+`useEffect`, Vue `:prop`, Svelte `bind:`). See `adia-ui-ssr`.
63
+
64
+ **Hybrid — SPA island in an SSR page.** The server renders the page and emits the island's seed as a prop/attribute; the island **registers + boots on the client** and owns its own state and in-island routing. The framework owns the page and top-level routing; the island is a self-contained SPA surface inside it.
65
+
66
+ ```html
67
+ <!-- server-rendered page (SSR) -->
68
+ <analytics-panel data-seed-id="abc"></analytics-panel>
69
+ <script type="module">import('/islands/analytics-panel.js');</script> <!-- client-only registration -->
70
+ ```
71
+
72
+ Rule: exactly one route owner _per scope_ — the framework routes the page; a content-less `<router-ui>` may route tabs **inside** the island. Never let the island's router fight the framework's.
73
+
74
+ ## Section registration & connection
75
+
76
+ - **Register by side-effect import** — the barrel (`@adia-ai/web-components`) or the component module; an unimported section never upgrades.
77
+ - **Data down, events up** — props in (`.rec = …`), `CustomEvent`s out; no reaching into a parent's internals.
78
+ - **Read projected children** via `logicalChildren` / `logicalSlotted` (from `@adia-ai/web-components/core/logical-children`) — `this.children` misses `${items.map(…)}` output and the `display:contents` trap.
79
+
80
+ ## Routing & state ownership
81
+
82
+ - **Content-less `<router-ui>`** for in-DOM/in-island tabs: routes _without_ `content`; CSS shows the active view. A content-mode route fetches + `innerHTML`-replaces — wrong for stamped views.
83
+ - **Own the URL** when you need query params: `history.replaceState(...)` and reflect `data-route-path` yourself; don't set `router.routes` (it path-routes and clobbers query params).
84
+ - **Single owner** per piece of state — the route owns the active view, the component owns selection/toggles, the DataClient owns fetched data. A control mutates the route; an observer/CSS reflects it back — never a second source of truth.
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: genui-a2ui
3
+ load-when: authoring a generative-UI experience — mounting the a2ui runtime, feeding generated A2UI, wiring resolvers, or using the corpus
4
+ load-size: ~2.5k tokens
5
+ required-for: [adia-ui-genui — all modes]
6
+ ---
7
+
8
+ # a2ui runtime & corpus — consumer surface
9
+
10
+ The consumer-facing a2ui runtime (mount + feed + resolve) and corpus (core vs custom). Pipeline internals (compose strategies, zettel scoring, catalog) are maintainer territory — not here. Exact names below; treat as a snapshot (re-bake with the pinned MCP).
11
+
12
+ ## Render roots
13
+
14
+ **`<a2ui-root>`** — renders an A2UI surface.
15
+
16
+ - **Author mode:** set `.doc = A2UIMessage[]` (a JS property; setting it re-renders). Use for editors/previews/tests.
17
+ - **Stream mode:** set `src` + `transport` (`sse` | `ws` | `jsonl` | `mcp`); the root opens the stream and reconciles messages as they arrive. (`doc` wins if both are set.)
18
+ - **Events:** `a2ui-message` (per message) · `a2ui-connected` / `a2ui-error` / `a2ui-closed` (stream) · `a2ui-action` (a child `[data-action]` fired). History: `back()` / `forward()`.
19
+
20
+ **`<gen-root mode="chat|split|canvas">`** — a layout shell unifying chat + canvas (+ `inspector` to debug the generated tree). Switch layout via `mode`.
21
+
22
+ ```html
23
+ <a2ui-root id="canvas"></a2ui-root>
24
+ <script type="module">
25
+ import { registerResolver } from '@adia-ai/a2ui-runtime';
26
+ registerResolver('resource', async (uri) => fetchResource(uri)); // register BEFORE feeding
27
+ document.getElementById('canvas').doc = validatedMessages; // A2UIMessage[]
28
+ </script>
29
+ ```
30
+
31
+ ## A2UI message format
32
+
33
+ A discriminated union the runtime reconciles (set `.doc` to an array of these):
34
+
35
+ - `createSurface` — `{ type, surfaceId, root? }` — start a surface.
36
+ - `updateComponents` — `{ type, surfaceId, components: [{ id, component, children?, ...props }] }` — upsert the tree; `component` is an A2UI type name (`Card`, `Text`, `Stat`, …); other keys are props. A prop can be a **data binding** `{ path: 'kpi/value' }`.
37
+ - `updateDataModel` — `{ type, surfaceId, path, value }` — set data (JSON-Pointer path) that bindings read.
38
+ - `wireComponents` — `{ type, surfaceId, controllers?, dataSources?, … }` — declare controllers/handlers/data sources/actions.
39
+ - `meta` — `{ type, feedback? }` — pipeline metadata/reasoning (traces, feedback).
40
+
41
+ ## registerResolver
42
+
43
+ ```js
44
+ import { registerResolver } from '@adia-ai/a2ui-runtime';
45
+ registerResolver('resource', async (uri, params) => { … }); // resolves resource:// → your data
46
+ ```
47
+
48
+ Built-in schemes: `resource:` (→ a `/api/...` REST convention), `api:` (direct URL), `mock:` (returns a stub). Register **once per session, before** any generated UI references the scheme — an unresolved scheme renders empty.
49
+
50
+ ## The loop, end to end
51
+
52
+ `classify_intent` → `assemble_context` (or `search_chunks` to ground) → `generate_ui` → **`validate_schema` + `check_anti_patterns`** → set `root.doc` → `refine_ui` (carry a `sessionId` for multi-turn; the server keeps the session's fragment state) or `root.back()/forward()`. **Validate before every render** — the runtime renders what you give it; garbage in, garbage rendered.
53
+
54
+ ## Corpus — core vs roll-your-own
55
+
56
+ - **Shape:** chunks are JSON (`{ name, kind: block|panel|page, component tree, metadata }`), with an `_index`, a `catalog`, and pre-computed `chunk-embeddings`. Harvested from real pages' `data-chunk` markers.
57
+ - **Core:** the MCP ships and retrieves over it — `search_chunks` (keyword always; semantic when `VOYAGE_API_KEY`/OpenAI is set) / `lookup_chunk`. Offline-capable (embeddings are committed).
58
+ - **Roll your own** _(pattern, lightly documented):_ author demo pages → mark regions with `data-chunk` + `data-chunk-kind`/`-domain`/`-description`/`-keywords` → harvest to chunks → point retrieval at your set → (optional) build embeddings (needs a key). **Grounding rule:** every chunk must trace to a real page — no synthetic chunks.
59
+
60
+ ## Consumer vs maintainer
61
+
62
+ Consumer (here): mount roots, register resolvers, call the MCP to generate/validate/refine, use or author a corpus. Maintainer (elsewhere): compose strategies, zettel scoring, the component catalog, embedding-model choice, evals. If you're editing `packages/a2ui/compose/**` or tuning retrieval scoring, you've crossed into maintainer territory.
@@ -0,0 +1,79 @@
1
+ # LLM features with `@adia-ai/llm`
2
+
3
+ Wiring chat / streaming / AI features into an app. This is **app-level LLM use** — distinct from _generating UI markup_, which is the a2ui MCP's `generate_ui` (see the boundary at the end). All of the below is from the framework's own `@adia-ai/llm` package.
4
+
5
+ > **Snapshot, not a spec.** The exact signatures, `StreamChunk` field names, and `<chat-shell-ui>` API below are verified against `@adia-ai/llm` **v0.7.8** (the pinned MCP's sibling). LLM client APIs drift fast — treat precise names as a starting point, confirm against the installed version, and lean on the **smart-proxy contract** (stable) rather than memorizing fields.
6
+
7
+ ## Import & the core API
8
+
9
+ ```js
10
+ import { chat, streamChat, createClient } from '@adia-ai/llm';
11
+ import { MODELS, DEFAULT_MODEL } from '@adia-ai/llm/models';
12
+ ```
13
+
14
+ - `chat(opts) → Promise<{ text, usage, stopReason }>` — non-streaming.
15
+ - `streamChat(opts) → AsyncGenerator<StreamChunk>` — streaming; `for await` the chunks.
16
+ - `createClient(defaults) → { chat, stream }` — a client with baked-in defaults.
17
+
18
+ `ChatOpts` (the fields you'll actually use): `model`, `messages: [{role, content}]`, `provider?` (auto-detected from the model name), `system?`, `maxTokens?`, `temperature?`, `stream?`, `signal?` (AbortSignal), `proxyUrl?`, plus Anthropic extras `thinking?` / `cache?`.
19
+
20
+ `StreamChunk` is a tagged union — branch on `chunk.type`:
21
+
22
+ ```js
23
+ for await (const chunk of streamChat(opts)) {
24
+ if (chunk.type === 'text') append(chunk.text); // also has .snapshot (full text so far)
25
+ else if (chunk.type === 'thinking') showThinking(chunk.text); // Anthropic extended thinking
26
+ else if (chunk.type === 'done') finalize(chunk.usage, chunk.stopReason);
27
+ else if (chunk.type === 'error') fail(chunk.error);
28
+ }
29
+ ```
30
+
31
+ ## Providers
32
+
33
+ Anthropic, OpenAI, and Gemini, **auto-detected from the model name** (`claude*` → anthropic, `gpt*`/`o1*` → openai, `gemini*` → google). Override with `provider`. Keys are read server-side from `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GOOGLE_API_KEY`.
34
+
35
+ ## The proxy / security model — read this first
36
+
37
+ **Never ship a provider API key to the browser in production.** `@adia-ai/llm` supports two proxy shapes, auto-selected by the `proxyUrl`:
38
+
39
+ - **Smart proxy (production):** point `proxyUrl` at your own same-origin endpoint (e.g. `/api/chat`). The browser sends a provider-neutral body (`{provider, model, messages, …}`); **your server** holds the real key, reformats per provider, and pipes the SSE bytes back. The browser never sees a key.
40
+
41
+ ```js
42
+ for await (const c of streamChat({ proxyUrl: '/api/chat', model, messages })) { … }
43
+ ```
44
+
45
+ A reference server lives at `@adia-ai/llm`'s `server.js` (`POST /api/chat`).
46
+
47
+ - **Passthrough proxy (LOCAL DEV ONLY):** a `proxyUrl` matching `/api/llm/<provider>/…` (e.g. a Vite dev proxy). The browser sends the real upstream body **and the real key in headers** — anyone with DevTools can read it. The package logs a loud one-time warning. **Never deploy this shape.**
48
+
49
+ When authoring: default to the smart-proxy pattern; only use passthrough behind a Vite dev proxy, and say so explicitly.
50
+
51
+ ## The chat shell
52
+
53
+ `<chat-shell-ui>` is a behavior-only web-module that renders a chat surface and wires it to `streamChat` for you:
54
+
55
+ ```html
56
+ <chat-shell-ui proxy-url="/api/chat" model="claude-sonnet-4-6">
57
+ <chat-header><span slot="name">Assistant</span><chat-status slot="status"></chat-status></chat-header>
58
+ <chat-thread><chat-empty><empty-state-ui heading="Hello!"></empty-state-ui></chat-empty></chat-thread>
59
+ <chat-composer><chat-input-ui placeholder="Message…"></chat-input-ui></chat-composer>
60
+ </chat-shell-ui>
61
+ ```
62
+
63
+ - **Properties:** `model`, `provider`, `proxyUrl`, `system`, `thinking`, `streaming`.
64
+ - **Events:** `submit`, `chunk`, `thinking`, `done`, `error`, `abort`, `message`, `clear`.
65
+ - **Methods:** `send(text, {model})`, `appendMessage(...)`, `clear()`, `abort()`; accessors `messages` / `conversation` / `export` / `import(...)`.
66
+
67
+ Set `proxy-url` (or, dev-only, an `apiKey`) and it auto-sends on submit; otherwise it just emits `submit` for you to handle. For SSR, register it client-side like any other component (see `ssr-integration.md`).
68
+
69
+ ## Boundary: app LLM vs. UI generation
70
+
71
+ Two separate paths — don't conflate them:
72
+
73
+ - **`@adia-ai/llm`** — a generic chat/streaming client for _your app's_ AI features (a chat box, a summarize button). Knows nothing about A2UI.
74
+ - **a2ui MCP `generate_ui`** — turns an intent into A2UI component markup (retrieval-first, LLM fallback). Use it to _build UI_, then `validate_schema` / `check_anti_patterns` (see `a2ui-mcp-tools.md`).
75
+ - The bridge (`createAdapter` from `@adia-ai/llm/bridge`) is what the A2UI generation pipeline uses internally to call an LLM for that fallback — you rarely call it directly.
76
+
77
+ ## Not in the package (don't assume)
78
+
79
+ Tool/function-calling chunks, structured-output/JSON-schema modes, built-in retry/backoff, and client-side token estimation are **not** surfaced by `@adia-ai/llm` as of this snapshot. If you need them, handle them in your own server layer — and check `mcp__a2ui__search_chunks` for anything added since.
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: migration
3
+ load-when: migrating an adia-ui app — version upgrade, port-to-adia, or mode change
4
+ load-size: ~2.5k tokens
5
+ required-for: [adia-ui-migrate — all types]
6
+ ---
7
+
8
+ # Migration — types, history, discipline
9
+
10
+ Consumer migrations. The framework MIGRATION GUIDE (in the framework repo) is the source of truth per version; this is the durable method + the shape of past breaks. Treat versions/examples as a snapshot.
11
+
12
+ ## Types
13
+
14
+ - **version-upgrade** — bump `@adia-ai/*` X→Y (lockstep; all packages move together). PATCH cuts are drop-in; MINOR/MAJOR carry breaking items.
15
+ - **port-to-adia** — an existing app (raw HTML, or legacy `@agent-ui-kit`) → adia-ui: a tag rename map (`aui-button`→`button-ui`, `<button>`→`<button-ui>`) + token namespace swap (`--n-*`→`--a-*`).
16
+ - **mode-change** — SPA↔SSR: re-own routing (framework router vs `<router-ui>`), registration (top-level vs client-hook), and state (signals vs cookies). See `adia-ui-spa` / `adia-ui-ssr`.
17
+
18
+ ## The 5-step discipline
19
+
20
+ 1. **Read the guide** for the target version. Missing section → pause, ask, offer to draft it.
21
+ 2. **Audit** — `git grep -nlE '<pattern>'` each breaking item; cluster by component; count files/occurrences. Surface before sweeping.
22
+ 3. **Sweep** — mechanical per cluster:
23
+
24
+ ```bash
25
+ git grep -lE 'button-ui[^>]*variant="danger"' \
26
+ | xargs perl -i -pe 's/(<button-ui[^>]*?)variant="danger"/$1color="danger"/g'
27
+ ```
28
+
29
+ …or a shipped codemod. **Flag, don't auto-apply, judgment items** (below).
30
+
31
+ 4. **Verify** — `adia-lint` clean of `LEGACY-SHELL`/`NATIVE-PRIMITIVE`; build/render; then the **leftover-drift grep** across `.css`/`.js`/`.md`/`.json`; browser probe.
32
+ 5. **Report** — per-axis counts, manual-review list, gate results, next actions.
33
+
34
+ ## Real breaking-change history (before → after)
35
+
36
+ - **v0.0.20 (10 items):** `<button-ui variant="danger">` → `variant="solid" color="danger"`; stage Booleans (`completed`/`active`) → `status="completed|active"` enum (timeline/stepper/pipeline); `<table-toolbar-ui>` opt-out Booleans **inverted** (`searchable="false"` → `no-search`; default flipped); `<chat-input-ui busy>` → `loading`; `variant="error"` alias removed → `danger`; event prefixes dropped (`chat-submit`→`submit`); `<field-ui error>` moved to the child input; `<agent-trace-ui open>` → `collapsed` (**semantic flip — default-visible now**); kebab prop keys → camelCase (JS only). Safari floor → 18.
37
+ - **v0.0.29 — three-tier extraction:** `patterns/` moved `@adia-ai/web-components/patterns/*` → `@adia-ai/web-modules/{shell,chat,editor,runtime}/*`. Import-path rewrite + add the `web-modules` dep.
38
+ - **v0.4.0 — legacy shell shapes retired (ADR-0024):** `<aside data-sidebar>`→`<admin-sidebar slot>`; `<dialog data-command>`→`<admin-command>`; `[data-chat-messages/input/empty]`→`<chat-thread>/<chat-composer>/<chat-empty>`; `[data-editor-body]/[data-canvas]`→`<editor-canvas>` + `<editor-sidebar>`. JS selectors move with them. (`adia-lint` `LEGACY-SHELL` flags the remnants.)
39
+ - **v0.6.0/0.6.1:** `stat-ui.{js,css}`→`stat.{js,css}` (deep-import only); `<link-ui>` token rename `--link-color-*`→`--link-fg-*` (only if you override).
40
+
41
+ ## Judgment items (flag for review — never auto-sweep)
42
+
43
+ Semantic flips (`[open]`→`[collapsed]` inverts default visibility), Boolean opt-out inversions (default changes), attribution transfers (`field-ui error` → child input may not exist yet). Surface these with their call sites; let the author decide.
44
+
45
+ ## What the path-only sweep misses (leftover-drift categories)
46
+
47
+ Bare-name mentions in prose docs (README/AGENTS/ROADMAP); skill-directory names; JSON metadata fields (highest impact — silent harvest miss on rebuild); inventory tables in cross-cutting docs. Use a pre/post grep diff:
48
+
49
+ ```bash
50
+ grep -rln 'OLD_NAME' . --include='*.md' --include='*.json' | sort > /tmp/pre.txt
51
+ # … sweep …
52
+ grep -rln 'OLD_NAME' . --include='*.md' --include='*.json' | sort > /tmp/post.txt && diff /tmp/pre.txt /tmp/post.txt
53
+ ```
54
+
55
+ ## MCP aids
56
+
57
+ `search_chunks` (the updated example for a changed component) · `check_anti_patterns` (a swept file is clean) · `convert_html` (map legacy/foreign markup → current components, for ports). There's no "list breaking changes" tool — the MIGRATION GUIDE is read by hand.
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: project-shapes
3
+ load-when: classifying or laying out an adia-ui app's structure — picking the project shape, the four-axis layout, or page-trio vs page-DUO
4
+ load-size: ~2.5k tokens
5
+ required-for: [adia-ui-factory shape classifier, adia-ui-project (scaffold / add-surface / add-page)]
6
+ ---
7
+
8
+ # Project shapes & structure
9
+
10
+ How real adia-ui apps are laid out (synthesized from the chat-ui apps). Three shapes over one four-axis layout, with a page-trio/DUO rule. The **structure rubric** at the bottom is the gate; `bin/adia-scaffold` mechanizes the layout.
11
+
12
+ ## The four-axis layout (all shapes)
13
+
14
+ ```text
15
+ <app>/
16
+ ├── README.md · PATTERNS.md · CHANGELOG.md # entry + non-obvious patterns + history
17
+ ├── spec/ design — BRIEF · ARCHITECTURE · SPEC (+ per-screen specs)
18
+ ├── plan/ execution — ROADMAP · MILESTONES · PLAN
19
+ ├── skills/ procedural knowledge — <app>-expert/ (optional)
20
+ └── app/ runnable source (shape-specific below)
21
+ ```
22
+
23
+ Keep the axes separate: `spec/` is the contract, `plan/` the sequence, `app/` the build, `skills/` the app's own expertise. The design/plan docs are not optional scaffolding noise — they're how a surface stays traceable to intent.
24
+
25
+ ## The three shapes
26
+
27
+ ### Single-surface — one entry, one surface
28
+
29
+ One `app/<name>.html` + its contents + a controller; reactive state via signals, often Service/Controller/Command for mutations/undo.
30
+
31
+ ```text
32
+ app/
33
+ ├── index.html · <name>.css
34
+ ├── components/<tag>/<tag>.{js,css}
35
+ ├── controller/ · service/ · state/ # if it has real domain logic
36
+ └── seed-data.js
37
+ ```
38
+
39
+ Use for: a focused tool/widget (a board, a canvas, an embedded panel).
40
+
41
+ ### Rollup — many sibling sub-pages under one app
42
+
43
+ Each sub-page is a page-trio or page-DUO; a uniform (or per-subflow) shell template loads it. Three flavors:
44
+
45
+ - **heterogeneous** — independent playgrounds, each its own chrome + logic; shared bits in `app/_shared/`.
46
+ - **homogeneous** — uniform template, fixtures per page, property-API wiring (often `admin-shell`).
47
+ - **declarative-DUO** — mostly static flows (auth/onboarding), per-subflow chrome, few controllers.
48
+
49
+ ```text
50
+ app/
51
+ ├── _shared/ # cross-page helpers (heterogeneous)
52
+ └── <sub>/<sub>.{html,contents.html[,contents.js]}
53
+ ```
54
+
55
+ Use for: a suite of related surfaces (a SaaS admin, a flow set, a demo gallery).
56
+
57
+ ### Shared-foundation — sibling apps over a shared core
58
+
59
+ Root-level `spec/plan/` for cross-app concerns; each app under `app/<name>/` with its own `spec/plan/src/`; a shared `app/shared/` (DataClient, loaders, mappers, tokens). The embed pattern lives here → becomes **`adia-embed-shell`**.
60
+
61
+ ```text
62
+ app/
63
+ ├── shared/ # DataClient · corpus/mappers · components · tokens
64
+ └── <surface>/{spec,plan,src}/ # one per embedded surface
65
+ ```
66
+
67
+ Use for: multiple embedded surfaces sharing data context, components, and an embed bridge.
68
+
69
+ ## Page-trio vs page-DUO
70
+
71
+ | Form | Files | When |
72
+ | --- | --- | --- |
73
+ | **trio** | `<page>.html` + `<page>.contents.html` + `<page>.contents.js` (`export default setup(host)`) | the surface needs **behavior or property-API wiring** (events, `.columns=…`, streaming) |
74
+ | **DUO** | `<page>.html` + `<page>.contents.html` | the surface is **purely declarative** (static markup, CSS-only state) |
75
+
76
+ The `.html` shell fetches `.contents.html`, injects it, then dynamically imports `.contents.js` if present. **Rule:** add `.contents.js` only when there's behavior to wire — a DUO with a dead `.contents.js` and a trio missing its setup are both smells.
77
+
78
+ ## State & data — which pattern (depth: `adia-ui-data`)
79
+
80
+ `signal()`/`effect()` (single-surface reactivity) · Service/Controller/Command (mutations + undo) · `DataClient.read(projection)` + pure mappers, with **`action_source` required on every `mutate`** (shared-foundation) · property-API (`el.columns = […]`, never post-connect `<option>` children) · declarative `data-*` (static flows). The data skill owns the choice; this is the map.
81
+
82
+ ## Components
83
+
84
+ One folder per tag, mirroring the framework: `components/<tag>/<tag>.{js,css}` (folder name = custom-element tag). Authoring discipline: `authoring-components.md`.
85
+
86
+ ## Structure rubric `[gate]`
87
+
88
+ A project is well-structured when (gate = all `[gate]` hold):
89
+
90
+ - **Four-axis present** `[gate]` — `spec/` + `plan/` + `app/` (skills/ optional).
91
+ - **Shape declared & matched** `[gate]` — the layout matches one of the three shapes; no half-rollup.
92
+ - **Page form correct** `[gate]` — every surface is trio or DUO per the rule; a DUO carries no `.contents.js`; a trio's `.contents.js` exports `setup`.
93
+ - **Components foldered** `[gate]` — every custom element is `components/<tag>/<tag>.{js,css}`.
94
+ - **No duplicated cross-surface code** `[review]` — shared logic lives in `app/shared/` (or `_shared/`), not copied per page.
95
+
96
+ Verify target for a scaffold/edit: this rubric passes, and any new surface renders through `adia-ui-verify`.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: shell-admin
3
+ load-when: authoring or debugging an admin-shell app frame (sidebar + topbar + command palette + pages)
4
+ load-size: ~2.5k tokens
5
+ required-for: [adia-ui-shells — admin path]
6
+ ---
7
+
8
+ # admin-shell — the app frame
9
+
10
+ Full SaaS/admin chrome from `@adia-ai/web-modules`. Register the **cluster barrel**: `import '@adia-ai/web-modules/shell'`.
11
+
12
+ ## Cluster roster
13
+
14
+ `<admin-shell>` (host coordinator) · `<admin-sidebar>` (resizable/collapsible, `slot="leading"|"trailing"`) · `<admin-command>` (Cmd+K palette) · `<admin-content>` (center column) · `<admin-topbar>` / `<admin-statusbar>` (chrome bars, shared slots) · `<admin-scroll>` (vertical scroll container) · `<admin-page>` + `<admin-page-header>` / `<admin-page-body>` / `<admin-page-footer>` (the page; header/footer sticky, body scrolls) · `<admin-entity-item>` (icon+label+badge identity row). Three are JS-bearing (shell, sidebar, command); the rest are CSS-only structure.
15
+
16
+ ## Canonical skeleton
17
+
18
+ ```html
19
+ <admin-shell mode="rounded borderless">
20
+ <admin-sidebar slot="leading" resizable collapsible>
21
+ <admin-topbar slot="header"><span slot="heading">Workspace</span></admin-topbar>
22
+ <nav-ui>…</nav-ui>
23
+ <admin-statusbar slot="footer"><admin-entity-item slot="heading">…</admin-entity-item></admin-statusbar>
24
+ <div data-resize></div> <!-- required when [resizable] -->
25
+ </admin-sidebar>
26
+ <admin-content>
27
+ <admin-topbar slot="header">
28
+ <button-ui data-sidebar-toggle="leading" icon="sidebar" variant="ghost"></button-ui>
29
+ <breadcrumb-ui slot="heading">…</breadcrumb-ui>
30
+ </admin-topbar>
31
+ <admin-scroll>
32
+ <admin-page>
33
+ <admin-page-header slot="header"><header-ui><span slot="heading">Page</span></header-ui></admin-page-header>
34
+ <admin-page-body slot="body"><section-ui>…</section-ui></admin-page-body>
35
+ </admin-page>
36
+ </admin-scroll>
37
+ <admin-statusbar slot="footer"><span>Status</span></admin-statusbar>
38
+ </admin-content>
39
+ <admin-command><command-ui placeholder="Search…"></command-ui></admin-command>
40
+ </admin-shell>
41
+ ```
42
+
43
+ ## Props · events · methods
44
+
45
+ - `<admin-shell mode>` — space-separated (`rounded` `borderless`; default `"rounded borderless"`). Events (forwarded): `sidebar-toggle {name, expanded}`, `sidebar-resize {name, width}`, `command-select {value}`. Methods: `toggleLeading()` `toggleTrailing()` `openCommand()` `closeCommand()`.
46
+ - `<admin-sidebar>` — `resizable` `collapsible` `name` `min-width`; reflects `[collapsed]` (snap ≤96px) / `[resizing]`. Methods `.toggle()/.collapse()/.expand()`. A `[data-sidebar-toggle="leading"]` button anywhere wires to it via delegation.
47
+ - `<admin-command>` — `open` `shortcut` (`both`|`cmd+k`|`ctrl+k`) `no-shortcut`; `.show()/.hide()`. A `[data-command-trigger]` opens it.
48
+ - Read cross-cutting state off the child: `shell.querySelector('admin-sidebar[slot="leading"]').hasAttribute('collapsed')`; style with `admin-shell:has(admin-sidebar[collapsed]) …`.
49
+
50
+ ## SPA vs SSR
51
+
52
+ SPA mounts the full markup. SSR keeps the shell + chrome fixed and swaps only the **page body** via the framework outlet — replace the inner page content with `{children}` / `<slot/>`; never mount `<router-ui>` (see `adia-ui-ssr`).
53
+
54
+ ## Gotchas (mechanized where noted)
55
+
56
+ - **Piecemeal import** → `AdminSidebar`/`AdminCommand` unregistered; `.toggle()/.show()` undefined. Import the barrel.
57
+ - **Wrapping shell children in `<col-ui>`/`<row-ui>`** → breaks the grid (it reads tag selectors). Generics go _inside_ `admin-content`/`admin-page-body`.
58
+ - **Raw `<header>` inside `<admin-page-header>`** → slot routing silently drops; wrap `<header-ui>`.
59
+ - **`[resizable]` without a child `<div data-resize>`** → no drag handle.
60
+ - **Hardcoded `[collapsed]`** → won't auto-clear on resize; use `.collapse()/.expand()`.
61
+ - **Multiple `<admin-page>` in one `<admin-scroll>`** → single-axis scroll breaks; one page per scroll.
62
+ - **`@container (…)` instead of `@container page-content (…)`** → won't react to sidebar collapse.
63
+ - **Legacy shapes** (`<aside data-sidebar>`, `<dialog data-command>`) — retired v0.4.0 (`adia-lint` `LEGACY-SHELL`).
64
+
65
+ Real usage: `apps/saas/app/admin-dashboard/`.