@adia-ai/adia-ui-factory 0.2.2 → 0.2.3
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.
- package/.claude-plugin/plugin.json +12 -3
- package/CHANGELOG.md +8 -0
- package/README.md +24 -1
- package/bin/adia-scaffold +93 -13
- package/package.json +2 -2
- package/references/shell-embed.md +10 -0
- package/references/spa-architecture.md +7 -0
- package/skills/adia-ui-kit/CHANGELOG.md +41 -0
- package/skills/adia-ui-kit/SKILL.md +442 -0
- package/skills/adia-ui-kit/assets/figma-make/guidelines/Guidelines.md +87 -0
- package/skills/adia-ui-kit/assets/figma-make/guidelines/components.md +142 -0
- package/skills/adia-ui-kit/assets/figma-make/guidelines/styles.md +118 -0
- package/skills/adia-ui-kit/assets/figma-make/guidelines/tokens.md +120 -0
- package/skills/adia-ui-kit/assets/team-scaffold/archive-README.md +30 -0
- package/skills/adia-ui-kit/assets/team-scaffold/drafts-README.md +57 -0
- package/skills/adia-ui-kit/assets/team-scaffold/inbox-README.md +76 -0
- package/skills/adia-ui-kit/assets/team-scaffold/outbox-README.md +46 -0
- package/skills/adia-ui-kit/assets/team-scaffold/team-root.README.md +51 -0
- package/skills/adia-ui-kit/assets/team-scaffold/tickets-README.md +208 -0
- package/skills/adia-ui-kit/assets/team-scaffold/tickets-REGISTRY.md +81 -0
- package/skills/adia-ui-kit/assets/templates/AGENTS.template.md +128 -0
- package/skills/adia-ui-kit/assets/templates/CLAUDE.template.md +1 -0
- package/skills/adia-ui-kit/assets/templates/FEEDBACK-OMISSIONS-DEFENSE.template.md +150 -0
- package/skills/adia-ui-kit/assets/templates/FEEDBACK.template.md +213 -0
- package/skills/adia-ui-kit/assets/templates/RESPONSE.template.md +114 -0
- package/skills/adia-ui-kit/assets/templates/figma-make-guidelines.md +90 -0
- package/skills/adia-ui-kit/evals/adversarial-content-trust.json +83 -0
- package/skills/adia-ui-kit/evals/evals.json +55 -0
- package/skills/adia-ui-kit/evals/routing-corpus.json +121 -0
- package/skills/adia-ui-kit/evals/teach-routing-cases.json +92 -0
- package/skills/adia-ui-kit/references/capabilities.md +245 -0
- package/skills/adia-ui-kit/references/catalog-lookup.md +186 -0
- package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v1.md +8 -0
- package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v2.md +3585 -0
- package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v3.md +1899 -0
- package/skills/adia-ui-kit/references/common-gotchas-consumer.md +522 -0
- package/skills/adia-ui-kit/references/component-literacy.md +291 -0
- package/skills/adia-ui-kit/references/composition.md +245 -0
- package/skills/adia-ui-kit/references/correction-loop.md +462 -0
- package/skills/adia-ui-kit/references/css-policy.md +343 -0
- package/skills/adia-ui-kit/references/decision-model.md +158 -0
- package/skills/adia-ui-kit/references/diagnostic-output.md +57 -0
- package/skills/adia-ui-kit/references/docs-catalog.md +239 -0
- package/skills/adia-ui-kit/references/exclusions.md +225 -0
- package/skills/adia-ui-kit/references/feedback-authoring.md +466 -0
- package/skills/adia-ui-kit/references/figma-make.md +244 -0
- package/skills/adia-ui-kit/references/flows-auth.md +307 -0
- package/skills/adia-ui-kit/references/flows-content.md +206 -0
- package/skills/adia-ui-kit/references/flows-data.md +348 -0
- package/skills/adia-ui-kit/references/flows-interaction.md +181 -0
- package/skills/adia-ui-kit/references/flows-shells.md +336 -0
- package/skills/adia-ui-kit/references/gap-detection.md +266 -0
- package/skills/adia-ui-kit/references/harness.md +284 -0
- package/skills/adia-ui-kit/references/loading-protocol.md +406 -0
- package/skills/adia-ui-kit/references/local-link.md +198 -0
- package/skills/adia-ui-kit/references/mcp.md +401 -0
- package/skills/adia-ui-kit/references/migration.md +116 -0
- package/skills/adia-ui-kit/references/mission.md +194 -0
- package/skills/adia-ui-kit/references/mode-routing.md +298 -0
- package/skills/adia-ui-kit/references/patterns-agent.md +223 -0
- package/skills/adia-ui-kit/references/patterns-data.md +1066 -0
- package/skills/adia-ui-kit/references/patterns-forms.md +223 -0
- package/skills/adia-ui-kit/references/patterns-layout.md +326 -0
- package/skills/adia-ui-kit/references/patterns-recipes.md +1416 -0
- package/skills/adia-ui-kit/references/patterns-shells.md +1097 -0
- package/skills/adia-ui-kit/references/patterns-traits.md +259 -0
- package/skills/adia-ui-kit/references/patterns-wiring.md +339 -0
- package/skills/adia-ui-kit/references/pev-rationale.md +137 -0
- package/skills/adia-ui-kit/references/reasoning-gate.md +146 -0
- package/skills/adia-ui-kit/references/reasoning-methodologies.md +112 -0
- package/skills/adia-ui-kit/references/recon.md +192 -0
- package/skills/adia-ui-kit/references/rendering-model.md +431 -0
- package/skills/adia-ui-kit/references/response-authoring.md +398 -0
- package/skills/adia-ui-kit/references/session-log-schema.md +203 -0
- package/skills/adia-ui-kit/references/session-log-template.md +118 -0
- package/skills/adia-ui-kit/references/setup.md +652 -0
- package/skills/adia-ui-kit/references/skill-hygiene.md +135 -0
- package/skills/adia-ui-kit/references/spec-to-ui-reasoning.md +394 -0
- package/skills/adia-ui-kit/references/state.md +229 -0
- package/skills/adia-ui-kit/references/teach-protocol.md +421 -0
- package/skills/adia-ui-kit/references/theme.md +142 -0
- package/skills/adia-ui-kit/references/version-check.md +69 -0
- package/skills/adia-ui-kit/scripts/audit-decision-trace.mjs +198 -0
- package/skills/adia-ui-kit/scripts/audit-kit-roster.mjs +366 -0
- package/skills/adia-ui-kit/scripts/changelog-archive.mjs +126 -0
- package/skills/adia-ui-kit/scripts/check-adia-versions.py +404 -0
- package/skills/adia-ui-kit/scripts/run-skill-evals.mjs +358 -0
- package/skills/adia-ui-kit/scripts/validate-session-log.mjs +242 -0
- package/skills/adia-ui-kit/skill.json +102 -0
- package/skills/adia-ui-migration/CHANGELOG.md +47 -0
- package/skills/adia-ui-migration/SKILL.md +381 -0
- package/skills/adia-ui-migration/skill.json +29 -0
- package/skills/adia-ui-spa/SKILL.md +1 -1
- package/skills/adia-ui-verify/SKILL.md +2 -2
|
@@ -0,0 +1,1416 @@
|
|
|
1
|
+
# §Patterns — Concrete answers to prompt-shaped pattern asks
|
|
2
|
+
|
|
3
|
+
> **Loading note.** Reference bundle extracted from `SKILL.md` per the
|
|
4
|
+
> §LoadingProtocol inversion contract (v2.20.4+). Load via
|
|
5
|
+
> `skill_view(name='adia-ui-kit', file_path='references/patterns-recipes.md')`
|
|
6
|
+
> when an active task asks for a concrete recipe matching one of the
|
|
7
|
+
> headings below (admin shell anatomy, REST→component data binding,
|
|
8
|
+
> chart data wiring, segmented-control children rule, entity-list →
|
|
9
|
+
> drawer pattern, status → badge mapping, design-board → token mapping,
|
|
10
|
+
> v0.6.18 loading state, imperative toasts via feed-ui). Skip
|
|
11
|
+
> otherwise.
|
|
12
|
+
>
|
|
13
|
+
> **Transitional bundle.** These 10 recipes were authored inline in
|
|
14
|
+
> SKILL.md §Patterns and extracted as a single bundle in v2.20.4 to
|
|
15
|
+
> get them out of the seed. Future polish cut (v2.21+) may redistribute
|
|
16
|
+
> them into the topic-bundled patterns-{shells,data,forms,layout,wiring}.md
|
|
17
|
+
> files where they would live more naturally:
|
|
18
|
+
> - Admin shell anatomy + composition tiers → `patterns-shells.md`
|
|
19
|
+
> - REST data binding + imperative toasts → `patterns-wiring.md`
|
|
20
|
+
> - Chart wiring + entity list/drawer + status badge + loading state → `patterns-data.md`
|
|
21
|
+
> - Segmented control rule → `patterns-forms.md`
|
|
22
|
+
> - Design board → token mapping → standalone or `patterns-layout.md`
|
|
23
|
+
|
|
24
|
+
Concrete answers to patterns that appear in prompts like
|
|
25
|
+
"Admin UI that loads X from endpoint Y following design board Z."
|
|
26
|
+
|
|
27
|
+
> **For the visual reference** of any pattern below, browse
|
|
28
|
+
> `https://ui-kit.exe.xyz/site/pages/patterns/`. For markup not covered
|
|
29
|
+
> here, check `docs/guides/71-recipes.md` (in-repo) or fetch the relevant
|
|
30
|
+
> page-shell template from `https://ui-kit.exe.xyz/site/modules/`.
|
|
31
|
+
|
|
32
|
+
## Admin shell anatomy
|
|
33
|
+
|
|
34
|
+
> ⚠️ **Shell CSS is REQUIRED and not auto-imported.** Before any
|
|
35
|
+
> `<admin-shell>` markup renders, your entry file MUST contain:
|
|
36
|
+
>
|
|
37
|
+
> ```ts
|
|
38
|
+
> import '@adia-ai/web-modules/shell/admin-shell.css';
|
|
39
|
+
> ```
|
|
40
|
+
>
|
|
41
|
+
> `admin-shell.js` does NOT self-import its layout CSS — every layout
|
|
42
|
+
> rule (the fixed-viewport flex grid, sidebar widths, topbar/statusbar
|
|
43
|
+
> heights, scroll geometry, bespoke-tag selectors) lives in
|
|
44
|
+
> `admin-shell.css`. **This is policy, not a bug** — see §CSSPolicy
|
|
45
|
+
> above for the full reasoning (light-DOM cascade control, tree-shake
|
|
46
|
+
> honesty, theme layering). Omitting the import produces an **unstyled
|
|
47
|
+
> stacked divs layout with no error or warning** — the sidebar renders
|
|
48
|
+
> above the content instead of beside it. Same caveat for `chat-shell`,
|
|
49
|
+
> `editor-shell`, `simple-shell` — import the matching CSS for the shell
|
|
50
|
+
> tag you use (`shell/admin-shell.css`, `chat/chat-shell.css`,
|
|
51
|
+
> `editor/editor-shell.css`, `simple/simple-shell.css`).
|
|
52
|
+
|
|
53
|
+
> ℹ️ **There is only ONE CSS file per shell — `admin-shell.css`.** (FB-06)
|
|
54
|
+
> The `./shell/*.css` exports wildcard makes `@adia-ai/web-modules/shell/
|
|
55
|
+
> admin-topbar.css` / `admin-sidebar.css` / `admin-content.css` /
|
|
56
|
+
> `admin-page.css` / `admin-scroll.css` *look* like they should resolve,
|
|
57
|
+
> but **none of those files exist on disk**. Vite 8 (rolldown) fails to
|
|
58
|
+
> resolve them at build time with a terse error. Every sub-component
|
|
59
|
+
> style (topbar layout, sidebar grid, content scroll geometry, page
|
|
60
|
+
> sticky-header band, bespoke-tag selectors) is bundled into
|
|
61
|
+
> `admin-shell.css`. Import it once and you're done. Same convention for
|
|
62
|
+
> the other shells: one CSS file per shell tag, all sub-component styles
|
|
63
|
+
> bundled. The `admin-command.css` import is the ONE exception — it
|
|
64
|
+
> ships separately because the command palette is opt-in and most apps
|
|
65
|
+
> don't use it.
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<admin-shell>
|
|
69
|
+
<!-- v0.6.13+: bare <admin-shell> auto-promotes to mode="rounded borderless"
|
|
70
|
+
on connectedCallback. Any explicit mode="..." attribute is respected
|
|
71
|
+
verbatim (including mode="" for the flat legacy chrome). The empty
|
|
72
|
+
string is the only opt-out; omitting the attribute entirely uses the
|
|
73
|
+
modern soft-chrome default. -->
|
|
74
|
+
<!-- Sidebar: navigation lives here -->
|
|
75
|
+
<admin-sidebar slot="leading" resizable collapsible>
|
|
76
|
+
<admin-topbar slot="header">
|
|
77
|
+
<span slot="heading">Claims Portal</span>
|
|
78
|
+
</admin-topbar>
|
|
79
|
+
<nav-ui>
|
|
80
|
+
<a href="/claims">Claims</a>
|
|
81
|
+
<a href="/policies">Policies</a>
|
|
82
|
+
<a href="/adjusters">Adjusters</a>
|
|
83
|
+
</nav-ui>
|
|
84
|
+
<div data-resize></div>
|
|
85
|
+
</admin-sidebar>
|
|
86
|
+
|
|
87
|
+
<!-- Main content area -->
|
|
88
|
+
<admin-content>
|
|
89
|
+
<admin-topbar>
|
|
90
|
+
<button-ui data-sidebar-toggle="leading" icon="sidebar" variant="ghost" size="sm"></button-ui>
|
|
91
|
+
<breadcrumb-ui></breadcrumb-ui>
|
|
92
|
+
<span data-spacer></span>
|
|
93
|
+
<div data-actions>
|
|
94
|
+
<popover-ui placement="bottom-end">
|
|
95
|
+
<button-ui icon="palette" variant="ghost" size="sm" slot="trigger"></button-ui>
|
|
96
|
+
<theme-panel slot="content" persist parametric presets scheme-toggle></theme-panel>
|
|
97
|
+
</popover-ui>
|
|
98
|
+
</div>
|
|
99
|
+
</admin-topbar>
|
|
100
|
+
<admin-scroll>
|
|
101
|
+
<!-- SPA route content lives here — the framework's route element
|
|
102
|
+
(e.g. <vue-router-view />, <Outlet />, <slot />, <lit-route-outlet>)
|
|
103
|
+
goes inside <admin-scroll>. In SSR contexts (Next.js / Nuxt /
|
|
104
|
+
SvelteKit / Astro / Rails+Turbo / Django+HTMX / Phoenix), the
|
|
105
|
+
framework's route children fill <admin-scroll> directly — there
|
|
106
|
+
is no separate route-outlet element. See §Rendering Model →
|
|
107
|
+
Routing ownership for the per-framework substitution table.
|
|
108
|
+
There is exactly ONE route owner per app — running two at once
|
|
109
|
+
causes click-double-handling and URL/content desync. -->
|
|
110
|
+
<!-- your router outlet here -->
|
|
111
|
+
</admin-scroll>
|
|
112
|
+
<admin-statusbar>
|
|
113
|
+
<span>App</span>
|
|
114
|
+
<span data-spacer></span>
|
|
115
|
+
<span>v1.0.0</span>
|
|
116
|
+
</admin-statusbar>
|
|
117
|
+
</admin-content>
|
|
118
|
+
|
|
119
|
+
<!-- Cmd+K command palette -->
|
|
120
|
+
<admin-command>
|
|
121
|
+
<command-ui placeholder="Search claims…"></command-ui>
|
|
122
|
+
</admin-command>
|
|
123
|
+
</admin-shell>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Route setup requires a **one-time script block** — this is the sole
|
|
127
|
+
legitimate exception to "no `<script>`". The script only wires routing
|
|
128
|
+
metadata; it emits no layout or components:
|
|
129
|
+
|
|
130
|
+
> ⚠️ **SPA-only.** Skip this script entirely in SSR. Next.js / Nuxt /
|
|
131
|
+
> SvelteKit / Astro / Rails+Turbo / Django+HTMX / Phoenix all own
|
|
132
|
+
> routing themselves (filesystem routes + framework `<Link>` /
|
|
133
|
+
> `link_to` / `<.link>` primitives). See §Rendering Model → Routing
|
|
134
|
+
> ownership for the per-framework path. Adding this script in SSR
|
|
135
|
+
> creates two competing route owners.
|
|
136
|
+
|
|
137
|
+
> ✅ **Router provider — auto-registered from the barrel at v0.6.40+.**
|
|
138
|
+
> `import '@adia-ai/web-components'` side-effect-imports
|
|
139
|
+
> `core/provider.js` (which registers `<router-ui>`). The router
|
|
140
|
+
> "just works" after the standard cold-start import.
|
|
141
|
+
>
|
|
142
|
+
> ⚠️ **Subpath-imported consumers must register the provider
|
|
143
|
+
> explicitly.** Apps with bundle-size SLOs that skip the main
|
|
144
|
+
> barrel — `import '@adia-ai/web-components/components/button'` etc.
|
|
145
|
+
> — must add `import '@adia-ai/web-components/core/provider';`
|
|
146
|
+
> alongside their primitive imports. Omitting it makes
|
|
147
|
+
> `<router-ui>` render as `HTMLUnknownElement`,
|
|
148
|
+
> `router.routes = [...]` silently no-op (no error, no warning),
|
|
149
|
+
> and the route content area stay empty. Same failure mode applies
|
|
150
|
+
> in any consumer setup that side-effect-imports per-primitive
|
|
151
|
+
> instead of the full barrel.
|
|
152
|
+
>
|
|
153
|
+
> ```ts
|
|
154
|
+
> // Bundle-size-conscious cold-start:
|
|
155
|
+
> import '@adia-ai/web-components/components/button';
|
|
156
|
+
> import '@adia-ai/web-components/components/table';
|
|
157
|
+
> // ...
|
|
158
|
+
> import '@adia-ai/web-components/core/provider'; // <-- router
|
|
159
|
+
> ```
|
|
160
|
+
>
|
|
161
|
+
> Source: `core/provider.js` `customElements.define('router-ui', UIRouter)`;
|
|
162
|
+
> barrel-side-effect-import added 2026-05-25 (FB-72 substrate fix).
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<script type="module">
|
|
166
|
+
// The ONLY script block allowed — route wiring, not layout
|
|
167
|
+
const router = document.querySelector('router-ui');
|
|
168
|
+
router.routes = [
|
|
169
|
+
{ path: '/claims', content: '/pages/claims-list.html', title: 'Claims' },
|
|
170
|
+
{ path: '/claims/:id', content: '/pages/claim-detail.html', title: 'Claim Detail' },
|
|
171
|
+
{ path: '/policies', content: '/pages/policies.html', title: 'Policies' },
|
|
172
|
+
];
|
|
173
|
+
</script>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Each page file is a standalone HTML fragment (no `<html>` wrapper).
|
|
177
|
+
`<router-ui>` fetches and injects the fragment on navigation.
|
|
178
|
+
|
|
179
|
+
## App-shell composition tiers — Advanced / Simple / Minimal (branched checklist)
|
|
180
|
+
|
|
181
|
+
The admin-shell anatomy above is the **canonical structure**, but real
|
|
182
|
+
apps differ in how many of the shell's optional surfaces they actually
|
|
183
|
+
populate. Pick a tier explicitly **at the §0 Reasoning Gate** — don't
|
|
184
|
+
defer it until mid-composition. The default for any new product surface
|
|
185
|
+
is **Advanced**; drop down only when there's a specific reason.
|
|
186
|
+
|
|
187
|
+
Use the matching tier as the starting checklist; then trim
|
|
188
|
+
component-by-component as your audit justifies it. Live references:
|
|
189
|
+
[admin-shell.examples.html](https://ui-kit.exe.xyz/site/components/admin-shell)
|
|
190
|
+
ships a side-by-side gallery of all three tiers.
|
|
191
|
+
|
|
192
|
+
#### Decision rule
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
Does the product span more than one platform / sub-product? → Advanced
|
|
196
|
+
Does the app need an authenticated user identity? → Advanced
|
|
197
|
+
Does the surface need contextual help / changelog visibility? → Advanced
|
|
198
|
+
↓ NONE of the above:
|
|
199
|
+
Does the app need authentication + a meaningful nav tree? → Simple
|
|
200
|
+
↓ otherwise:
|
|
201
|
+
Single page or two-route demo, no auth, flat nav → Minimal
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
When in doubt, **start Advanced and trim**. Adding `<context-switcher>`
|
|
205
|
+
or a trailing sidebar to a Minimal scaffold mid-build is a multi-slot
|
|
206
|
+
refactor; removing them from an Advanced scaffold is delete-only.
|
|
207
|
+
|
|
208
|
+
#### Tier checklists
|
|
209
|
+
|
|
210
|
+
| Surface | Advanced (default) | Simple | Minimal |
|
|
211
|
+
|---|---|---|---|
|
|
212
|
+
| **Leading sidebar** (`<admin-sidebar slot="leading">`) | ✓ resizable + collapsible | ✓ collapsible | ✓ (no resize) |
|
|
213
|
+
| ↳ Context switcher (top, `<select-ui avatar="…" value="…" variant="ghost">` for workspace/product jump) | ✓ | — (static brand `<span slot="heading">`) | — (static brand) |
|
|
214
|
+
| ↳ `<nav-ui>` with `<nav-item-ui>` + `<nav-group-ui>` + dividers | ✓ grouped + nested + badges + dividers | ✓ groups, no badges | ✓ flat nav-item-ui only |
|
|
215
|
+
| ↳ User menu (sidebar footer `<admin-statusbar slot="footer">` + `<select-ui avatar="…" value="…" variant="ghost">`) | ✓ avatar + account + sign-out | ✓ sign-in entry only | — |
|
|
216
|
+
| **Topbar** (`<admin-topbar slot="header">` inside `<admin-content>`) <sup>*</sup> | ✓ | ✓ | ✓ |
|
|
217
|
+
| ↳ Leading: sidebar toggle button + `<breadcrumb-ui>` (hydrated) | ✓ | ✓ | ✓ |
|
|
218
|
+
| ↳ Trailing `slot="action"`: theme toggle + sidebar toggle for trailing sidebar | ✓ | ✓ (theme only) | — |
|
|
219
|
+
| **Scroll region** (`<admin-scroll>`) | ✓ | ✓ | ✓ |
|
|
220
|
+
| ↳ `<router-ui>` (SPA) or framework children (SSR, see §Rendering Model → Routing ownership) | ✓ | ✓ | ✓ |
|
|
221
|
+
| ↳ `<admin-page>` wrapping route content | ✓ | ✓ | ✓ |
|
|
222
|
+
| ↳ `<admin-page-header>` with `<header>` + `<tabs-ui>` for page-scoped tabs | ✓ | ✓ (no tabs unless the page needs them) | — (h1 only) |
|
|
223
|
+
| ↳ `<admin-page-body>` for page content | ✓ | ✓ | ✓ |
|
|
224
|
+
| **Content statusbar** (`<admin-statusbar slot="footer">` inside `<admin-content>`) | ✓ pkg + skill versions (overrideable) | — | — |
|
|
225
|
+
| **Trailing sidebar** (`<admin-sidebar slot="trailing">`) | ✓ changelog / contextual help | — | — |
|
|
226
|
+
| **Command palette** (`<admin-command>`) | ✓ Cmd+K `<command-ui>` | optional | — |
|
|
227
|
+
|
|
228
|
+
#### Advanced — full markup (start here for any new product surface)
|
|
229
|
+
|
|
230
|
+
> **SoT alignment.** This markup mirrors `site/index.html` (the live
|
|
231
|
+
> docs-site shell at `ui-kit.exe.xyz`) and `admin-shell.examples.html`'s
|
|
232
|
+
> sidebar-variant gallery (the canonical demo). Where the two diverge,
|
|
233
|
+
> the gallery is the new-code canonical form; `site/index.html` ships
|
|
234
|
+
> a few legacy patterns (`[data-actions]` instead of `slot="action"`,
|
|
235
|
+
> `data-sidebar-toggle-trailing` boolean instead of
|
|
236
|
+
> `data-sidebar-toggle="trailing"` value) that admin-topbar.yaml flags
|
|
237
|
+
> as "for one release, prefer slots." This skill teaches the
|
|
238
|
+
> new-canonical forms; if you're working in the chat-ui repo itself,
|
|
239
|
+
> the docs site's bespoke wiring may differ.
|
|
240
|
+
>
|
|
241
|
+
> <sup>*</sup> **`<admin-topbar slot="header">` vs no-slot.** Both forms work and render identically. `<admin-content>` is light-DOM per ADR-0033 (no shadow root), so `slot="header"` is inert and CSS positions `<admin-topbar>` by tag + DOM order regardless. This skill and `site/index.html` use `slot="header"` for parity with substrate demos; `loading-protocol.md`'s 13-part list uses the no-slot form. Both are correct; do not "fix" one to the other.
|
|
242
|
+
|
|
243
|
+
```html
|
|
244
|
+
<admin-shell mode="rounded borderless">
|
|
245
|
+
|
|
246
|
+
<!-- ═══ Leading sidebar ═══
|
|
247
|
+
name="…" attribute is OPTIONAL — defaults to the slot value
|
|
248
|
+
("leading" / "trailing") for localStorage namespacing. Only set
|
|
249
|
+
it when running multiple sidebars with the same slot. -->
|
|
250
|
+
<admin-sidebar slot="leading" resizable collapsible>
|
|
251
|
+
|
|
252
|
+
<!-- Top: context switcher (jump between products / platforms /
|
|
253
|
+
workspaces). Canonical pattern: <select-ui avatar="…" value="…"
|
|
254
|
+
variant="ghost"> — used by the live docs site at
|
|
255
|
+
ui-kit.exe.xyz/site/examples/admin-dashboard (chat-ui:site/index.html
|
|
256
|
+
line 47). The `avatar` attribute renders the leading image; the
|
|
257
|
+
`value` attribute selects the current option from .options=[…].
|
|
258
|
+
No <span slot="trigger"> hand-roll needed — the primitive
|
|
259
|
+
self-stamps the trigger row (avatar + label + caret-up-down).
|
|
260
|
+
|
|
261
|
+
Legacy form (admin-shell.examples.html v0.5.x): <menu-ui> with
|
|
262
|
+
a button-trigger holding brand logo + label + caret. Still
|
|
263
|
+
works but is NOT the canonical pattern as of v0.6.30 — the
|
|
264
|
+
site shell migrated to select-ui because it handles
|
|
265
|
+
workspace/user dropdown cases more cleanly (auto avatar
|
|
266
|
+
stamping, value+options data binding, narrower API surface).
|
|
267
|
+
menu-ui remains correct for richer non-dropdown trigger menus
|
|
268
|
+
(multi-level command menus, etc.) but NOT for the
|
|
269
|
+
context-switcher slot. -->
|
|
270
|
+
<admin-topbar slot="header">
|
|
271
|
+
<select-ui id="workspace-select"
|
|
272
|
+
avatar="/icons/brand.svg"
|
|
273
|
+
value="studio"
|
|
274
|
+
variant="ghost"
|
|
275
|
+
style="flex:1">
|
|
276
|
+
<!-- Options hydrated via .options = [{ value, label, icon? }, …]
|
|
277
|
+
in the page's main.ts. The primitive stamps the listbox +
|
|
278
|
+
trigger row automatically. -->
|
|
279
|
+
</select-ui>
|
|
280
|
+
</admin-topbar>
|
|
281
|
+
|
|
282
|
+
<!-- Middle: nav with groups + dividers + badges.
|
|
283
|
+
Canonical wrapper: <section-ui> (the AdiaUI primitive). Used by
|
|
284
|
+
the live docs site at chat-ui:site/index.html line 50. The
|
|
285
|
+
section-ui wrap is what admin-sidebar's light-DOM CSS targets
|
|
286
|
+
for inner scroll + padding rules; native <section> works in
|
|
287
|
+
simple cases but loses the scroll-area treatment that
|
|
288
|
+
section-ui provides.
|
|
289
|
+
|
|
290
|
+
Legacy form: native <section>. The admin-shell.examples.html
|
|
291
|
+
"Advanced" gallery uses native <section> from before
|
|
292
|
+
section-ui shipped. Treat it as a deprecated alternate; prefer
|
|
293
|
+
<section-ui>. -->
|
|
294
|
+
<section-ui>
|
|
295
|
+
<nav-ui>
|
|
296
|
+
<nav-item-ui icon="house" text="Dashboard" value="dashboard" selected></nav-item-ui>
|
|
297
|
+
<nav-item-ui icon="tray" text="Inbox" value="inbox" badge="12"></nav-item-ui>
|
|
298
|
+
|
|
299
|
+
<hr data-nav-divider>
|
|
300
|
+
|
|
301
|
+
<nav-group-ui icon="chart-line" text="Analytics" open>
|
|
302
|
+
<nav-item-ui text="Overview" value="an-overview"></nav-item-ui>
|
|
303
|
+
<nav-item-ui text="Funnels" value="an-funnels"></nav-item-ui>
|
|
304
|
+
<nav-item-ui text="Retention" value="an-retention"></nav-item-ui>
|
|
305
|
+
</nav-group-ui>
|
|
306
|
+
|
|
307
|
+
<nav-group-ui icon="folders" text="Content">
|
|
308
|
+
<nav-item-ui text="Pages" value="content-pages"></nav-item-ui>
|
|
309
|
+
<nav-item-ui text="Media" value="content-media" badge="3"></nav-item-ui>
|
|
310
|
+
<nav-item-ui text="Drafts" value="content-drafts"></nav-item-ui>
|
|
311
|
+
</nav-group-ui>
|
|
312
|
+
|
|
313
|
+
<hr data-nav-divider>
|
|
314
|
+
|
|
315
|
+
<nav-group-ui icon="gear" text="Settings">
|
|
316
|
+
<nav-item-ui text="General" value="settings-general"></nav-item-ui>
|
|
317
|
+
<nav-item-ui text="Members" value="settings-members"></nav-item-ui>
|
|
318
|
+
<nav-item-ui text="Billing" value="settings-billing"></nav-item-ui>
|
|
319
|
+
</nav-group-ui>
|
|
320
|
+
</nav-ui>
|
|
321
|
+
</section-ui>
|
|
322
|
+
|
|
323
|
+
<!-- Bottom: user menu (avatar + account menu).
|
|
324
|
+
Inside an <admin-sidebar slot="leading"> the <admin-statusbar
|
|
325
|
+
slot="footer"> is the USER MENU band. Same primitive used at
|
|
326
|
+
<admin-content slot="footer"> = VERSION-STRIP band (see below).
|
|
327
|
+
|
|
328
|
+
Canonical pattern: <select-ui avatar="…" value="…" variant="ghost">
|
|
329
|
+
— used by the live docs site (chat-ui:site/index.html line 62).
|
|
330
|
+
Same primitive as the context-switcher above; the `avatar`
|
|
331
|
+
attribute auto-stamps the leading image (use a URL like
|
|
332
|
+
`https://i.pravatar.cc/32?u=username` or your own /icons/ path).
|
|
333
|
+
|
|
334
|
+
Legacy form (admin-shell.examples.html v0.5.x): <menu-ui> with
|
|
335
|
+
a button-trigger holding <avatar-ui> + name. Still works but is
|
|
336
|
+
NOT the canonical pattern as of v0.6.30; the docs site
|
|
337
|
+
migrated to select-ui for the same reasons as the
|
|
338
|
+
context-switcher above (auto avatar stamping, narrower API
|
|
339
|
+
surface, value+options data binding). -->
|
|
340
|
+
<admin-statusbar slot="footer">
|
|
341
|
+
<select-ui id="user-select"
|
|
342
|
+
avatar="https://i.pravatar.cc/32?u=maya"
|
|
343
|
+
value="maya"
|
|
344
|
+
variant="ghost"
|
|
345
|
+
style="flex:1">
|
|
346
|
+
<!-- Options: { value: 'maya', label: 'Maya Chen', icon: 'user' }, etc.
|
|
347
|
+
For account actions (Profile / Settings / Sign out), add
|
|
348
|
+
them as option items; select-ui emits change events on
|
|
349
|
+
selection that the page can route to the right action. -->
|
|
350
|
+
</select-ui>
|
|
351
|
+
</admin-statusbar>
|
|
352
|
+
|
|
353
|
+
<!-- Legacy menu-ui form (kept for reference; do not use for new
|
|
354
|
+
shells — site/index.html is canonical):
|
|
355
|
+
|
|
356
|
+
<admin-statusbar slot="footer">
|
|
357
|
+
<menu-ui placement="top-start">
|
|
358
|
+
<button type="button" slot="trigger" class="user-switcher">
|
|
359
|
+
<avatar-ui text="Maya Chen" size="sm"></avatar-ui>
|
|
360
|
+
<span class="user-label">Maya Chen</span>
|
|
361
|
+
<icon-ui name="caret-up-down" size="14"></icon-ui>
|
|
362
|
+
</button>
|
|
363
|
+
<menu-item-ui icon="user" text="Profile" value="profile"></menu-item-ui>
|
|
364
|
+
<menu-item-ui icon="gear" text="Account settings" value="account"></menu-item-ui>
|
|
365
|
+
<menu-item-ui icon="moon" text="Appearance" value="appearance"></menu-item-ui>
|
|
366
|
+
<menu-divider-ui></menu-divider-ui>
|
|
367
|
+
<menu-item-ui icon="sign-out" text="Sign out" value="signout"></menu-item-ui>
|
|
368
|
+
</menu-ui>
|
|
369
|
+
</admin-statusbar>
|
|
370
|
+
-->
|
|
371
|
+
|
|
372
|
+
<!-- Read-only identity primitive — admin-entity-item.
|
|
373
|
+
The canonical shell-tier identity-row primitive (shipped
|
|
374
|
+
@adia-ai/web-modules@0.7.13, FEEDBACK-38). Use it on EITHER
|
|
375
|
+
end of <admin-sidebar>:
|
|
376
|
+
- <admin-topbar slot="header"> for brand chrome (single-
|
|
377
|
+
product, no switcher) — see patterns-shells.md §Brand
|
|
378
|
+
chrome Pattern A
|
|
379
|
+
- <admin-statusbar slot="footer"> for user identity (non-
|
|
380
|
+
interactive, no menu)
|
|
381
|
+
|
|
382
|
+
Geometry mirrors <select-ui>'s trigger row (icon ≡ leading,
|
|
383
|
+
label ≡ display, badge ≡ caret) so it aligns 1px-perfect with
|
|
384
|
+
<nav-item-ui> icons in the section between header and footer.
|
|
385
|
+
The wrapper collapses as one slottable unit inside a
|
|
386
|
+
collapsible sidebar: icon survives, label + badge hide. Without
|
|
387
|
+
admin-entity-item, bare [slot="icon"] + [slot="heading"]
|
|
388
|
+
siblings have no shared collapse boundary and land at the
|
|
389
|
+
topbar's --page-sidebar-px (4px) — 8px short of nav alignment.
|
|
390
|
+
|
|
391
|
+
Brand chrome (header):
|
|
392
|
+
<admin-topbar slot="header">
|
|
393
|
+
<admin-entity-item slot="heading">
|
|
394
|
+
<icon-ui slot="icon" name="lightning"></icon-ui>
|
|
395
|
+
<span slot="label">Claims Portal</span>
|
|
396
|
+
<!-- Optional: <badge-ui slot="badge" text="Staging" size="sm" variant="warning"></badge-ui> -->
|
|
397
|
+
</admin-entity-item>
|
|
398
|
+
</admin-topbar>
|
|
399
|
+
|
|
400
|
+
User identity (footer):
|
|
401
|
+
<admin-statusbar slot="footer">
|
|
402
|
+
<admin-entity-item slot="heading">
|
|
403
|
+
<icon-ui slot="icon" name="user-circle"></icon-ui>
|
|
404
|
+
<span slot="label">Kim Granlund</span>
|
|
405
|
+
<badge-ui slot="badge" text="Operator" size="sm" variant="muted"></badge-ui>
|
|
406
|
+
</admin-entity-item>
|
|
407
|
+
</admin-statusbar>
|
|
408
|
+
|
|
409
|
+
For an INTERACTIVE workspace switcher OR user menu, keep the
|
|
410
|
+
<select-ui>/<menu-ui> trigger patterns above. admin-entity-item
|
|
411
|
+
is read-only display; select-ui/menu-ui are interactive. -->
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
<div data-resize></div>
|
|
415
|
+
</admin-sidebar>
|
|
416
|
+
|
|
417
|
+
<!-- ═══ Main content area ═══ -->
|
|
418
|
+
<admin-content>
|
|
419
|
+
|
|
420
|
+
<admin-topbar slot="header">
|
|
421
|
+
<!-- Leading: sidebar toggle + hydrated breadcrumb.
|
|
422
|
+
data-sidebar-toggle="leading|trailing" is the canonical
|
|
423
|
+
value-bearing attribute (per admin-shell.js wiring); the
|
|
424
|
+
sidebar matches by its `name` attribute (default = slot value). -->
|
|
425
|
+
<button-ui data-sidebar-toggle="leading" icon="sidebar" variant="ghost" size="sm"></button-ui>
|
|
426
|
+
<breadcrumb-ui>
|
|
427
|
+
<a href="/">Studio</a>
|
|
428
|
+
<a href="/analytics">Analytics</a>
|
|
429
|
+
<span aria-current="page">Overview</span>
|
|
430
|
+
</breadcrumb-ui>
|
|
431
|
+
|
|
432
|
+
<!-- Trailing: persistent app-wide controls.
|
|
433
|
+
slot="action" is the new-code canonical anchor (v0.6.13+).
|
|
434
|
+
The legacy <div data-actions> hook still works (admin-topbar.yaml
|
|
435
|
+
"coexists for one release") and is what the docs-site shell
|
|
436
|
+
ships today; new code should prefer slot="action".
|
|
437
|
+
|
|
438
|
+
For richer theme control than scheme toggle (light/dark/system),
|
|
439
|
+
swap <toggle-scheme-ui> for <popover-ui> + <theme-panel> — the
|
|
440
|
+
docs site uses the panel form. -->
|
|
441
|
+
<toggle-scheme-ui slot="action" scheme="auto"></toggle-scheme-ui>
|
|
442
|
+
<button-ui slot="action" data-sidebar-toggle="trailing" icon="sidebar-simple-reverse" variant="ghost" size="sm"></button-ui>
|
|
443
|
+
</admin-topbar>
|
|
444
|
+
|
|
445
|
+
<admin-scroll>
|
|
446
|
+
<!-- SPA: the router outlet stays EMPTY in the shell — admin-page +
|
|
447
|
+
admin-page-header + admin-page-body live in the ROUTE FRAGMENT
|
|
448
|
+
(a separate .html file loaded by the router). The shell defines
|
|
449
|
+
layout; routes define page content. See the "Route fragment"
|
|
450
|
+
snippet below. SSR: omit the outlet entirely — see §Rendering
|
|
451
|
+
Model → Routing ownership. -->
|
|
452
|
+
<!-- your router outlet here -->
|
|
453
|
+
</admin-scroll>
|
|
454
|
+
|
|
455
|
+
<!-- Content statusbar: status / version-strip. The same <admin-statusbar>
|
|
456
|
+
primitive that hosted the user menu in the sidebar is the
|
|
457
|
+
version-strip band when used at admin-content slot="footer".
|
|
458
|
+
Use <span data-spacer> to push trailing content to the end.
|
|
459
|
+
The version label is meant to be overridden per-product (sync
|
|
460
|
+
indicator, last-saved timestamp, row count, etc.). -->
|
|
461
|
+
<admin-statusbar slot="footer">
|
|
462
|
+
<span>Adia Studio</span>
|
|
463
|
+
<span data-spacer></span>
|
|
464
|
+
<span id="version-label">v0.6.19</span>
|
|
465
|
+
</admin-statusbar>
|
|
466
|
+
|
|
467
|
+
</admin-content>
|
|
468
|
+
|
|
469
|
+
<!-- ═══ Trailing sidebar — changelog / contextual help ═══
|
|
470
|
+
Starts hidden — click the trailing-sidebar toggle in the topbar
|
|
471
|
+
to reveal. resizable + collapsible matches the leading sidebar
|
|
472
|
+
so users get consistent affordances. -->
|
|
473
|
+
<admin-sidebar slot="trailing" resizable collapsible hidden>
|
|
474
|
+
<admin-topbar slot="header">
|
|
475
|
+
<span slot="heading">What's new</span>
|
|
476
|
+
<!-- Close button — slot="action" cluster (canonical) -->
|
|
477
|
+
<button-ui slot="action" data-sidebar-toggle="trailing" icon="x" variant="ghost" size="sm" aria-label="Close changelog"></button-ui>
|
|
478
|
+
</admin-topbar>
|
|
479
|
+
<section-ui>
|
|
480
|
+
<text-ui variant="kicker">v0.6.18 · 2026-05-21</text-ui>
|
|
481
|
+
<text-ui variant="heading">Loading state is first-class</text-ui>
|
|
482
|
+
<text-ui>
|
|
483
|
+
<code><stat-ui loading></code> + <code><table-ui loading></code> now
|
|
484
|
+
render skeleton placeholders while data fetches. Pair with
|
|
485
|
+
<code>data-stream-*</code> for declarative loading.
|
|
486
|
+
</text-ui>
|
|
487
|
+
</section-ui>
|
|
488
|
+
<div data-resize></div>
|
|
489
|
+
</admin-sidebar>
|
|
490
|
+
|
|
491
|
+
<!-- ═══ Cmd+K command palette ═══ -->
|
|
492
|
+
<admin-command>
|
|
493
|
+
<command-ui placeholder="Search anything…"></command-ui>
|
|
494
|
+
</admin-command>
|
|
495
|
+
|
|
496
|
+
</admin-shell>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
**Route fragment** (`pages/overview.html`) — the content the router
|
|
500
|
+
fetches and injects into `<router-ui>` on navigation. Each route is a
|
|
501
|
+
standalone HTML fragment with no `<html>` / `<body>` wrapper:
|
|
502
|
+
|
|
503
|
+
```html
|
|
504
|
+
<admin-page>
|
|
505
|
+
<admin-page-header>
|
|
506
|
+
<header>
|
|
507
|
+
<div>
|
|
508
|
+
<h1>Overview</h1>
|
|
509
|
+
<div data-actions>
|
|
510
|
+
<button-ui text="Export" variant="outline" icon="download"></button-ui>
|
|
511
|
+
<button-ui text="New" variant="primary" icon="plus"></button-ui>
|
|
512
|
+
</div>
|
|
513
|
+
</div>
|
|
514
|
+
</header>
|
|
515
|
+
<tabs-ui value="summary">
|
|
516
|
+
<tab-ui value="summary">Summary</tab-ui>
|
|
517
|
+
<tab-ui value="trends">Trends</tab-ui>
|
|
518
|
+
<tab-ui value="cohorts">Cohorts</tab-ui>
|
|
519
|
+
</tabs-ui>
|
|
520
|
+
</admin-page-header>
|
|
521
|
+
<admin-page-body>
|
|
522
|
+
<section>
|
|
523
|
+
<!-- Page content — KPI cards, tables, charts wired via data-stream-* -->
|
|
524
|
+
</section>
|
|
525
|
+
</admin-page-body>
|
|
526
|
+
</admin-page>
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
> ⚠️ **`<admin-page-body>` needs an inner column-owner** —
|
|
530
|
+
> `<section>` (or `<header-ui>` / `<section-ui>` / `<iframe>` /
|
|
531
|
+
> `<editor-shell>` / `<pane-ui>`). The wrapper element is a
|
|
532
|
+
> flex/grid container that doesn't own the `--card-inset` padding;
|
|
533
|
+
> a bare `<col-ui>` / `<grid-ui>` / `<card-ui>` directly inside
|
|
534
|
+
> renders edge-to-edge with zero gutter. The inner `<section>` carries
|
|
535
|
+
> the inset. Same shape for `<admin-page-header>` and its inner
|
|
536
|
+
> `<header>` — both wrap a content-owner. See
|
|
537
|
+
> `patterns-shells.md` §Wrapper-vs-column callout + the matching
|
|
538
|
+
> §5 entry in `references/common-gotchas-consumer.md`. Mechanically
|
|
539
|
+
> enforced via `npm run audit:admin-page-structure --strict`.
|
|
540
|
+
|
|
541
|
+
Route wiring (the one-time script block from "Admin shell anatomy"
|
|
542
|
+
above) connects each route path to its fragment URL.
|
|
543
|
+
|
|
544
|
+
**SSR variant** — in Next.js / Nuxt / SvelteKit / Astro the route
|
|
545
|
+
fragment is whatever the framework injects as `{children}` /
|
|
546
|
+
`<slot />` / `<Astro.children />`; the empty `<router-ui>` slot in
|
|
547
|
+
`<admin-scroll>` is replaced by that injection point. See
|
|
548
|
+
§Rendering Model → Routing ownership for the full per-framework table.
|
|
549
|
+
|
|
550
|
+
#### Simple — Advanced minus context-switcher, trailing sidebar, content statusbar
|
|
551
|
+
|
|
552
|
+
Use when there's **one product**, optional auth, and no need for
|
|
553
|
+
persistent contextual chrome. Delta from Advanced (5 deletions):
|
|
554
|
+
|
|
555
|
+
```diff
|
|
556
|
+
<admin-sidebar slot="leading" resizable collapsible name="primary">
|
|
557
|
+
<admin-topbar slot="header">
|
|
558
|
+
- <select-ui avatar="…" value="…" variant="ghost"></select-ui> <!-- context-switcher -->
|
|
559
|
+
+ <span slot="heading">My App</span> <!-- static brand -->
|
|
560
|
+
</admin-topbar>
|
|
561
|
+
<section-ui><nav-ui>…</nav-ui></section-ui>
|
|
562
|
+
<admin-statusbar slot="footer">
|
|
563
|
+
<!-- Either: <select-ui avatar="…"> for full account, OR a single Sign-in entry: -->
|
|
564
|
+
- <select-ui avatar="…" value="…" variant="ghost"></select-ui> <!-- full user menu -->
|
|
565
|
+
+ <button-ui text="Sign in" variant="ghost" icon="sign-in"></button-ui>
|
|
566
|
+
</admin-statusbar>
|
|
567
|
+
</admin-sidebar>
|
|
568
|
+
|
|
569
|
+
<admin-content>
|
|
570
|
+
<admin-topbar slot="header">…</admin-topbar>
|
|
571
|
+
<admin-scroll>
|
|
572
|
+
<router-ui><admin-page>…</admin-page></router-ui>
|
|
573
|
+
</admin-scroll>
|
|
574
|
+
- <admin-statusbar slot="footer">…version-strip…</admin-statusbar>
|
|
575
|
+
</admin-content>
|
|
576
|
+
|
|
577
|
+
- <admin-sidebar slot="trailing" collapsible name="changelog">…</admin-sidebar>
|
|
578
|
+
<admin-command>…</admin-command>
|
|
579
|
+
</admin-shell>
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
Keep the topbar trailing `slot="action"` cluster (theme toggle is still
|
|
583
|
+
useful even without a trailing sidebar). Drop the trailing-sidebar
|
|
584
|
+
toggle button since the trailing sidebar is gone.
|
|
585
|
+
|
|
586
|
+
#### Minimal — Advanced minus auth, statusbar (both), trailing sidebar, command palette, tabs
|
|
587
|
+
|
|
588
|
+
Use for single-page demos, two-route prototypes, or embedded surfaces
|
|
589
|
+
where the parent already owns chrome. Delta from Advanced:
|
|
590
|
+
|
|
591
|
+
```diff
|
|
592
|
+
<admin-shell>
|
|
593
|
+
<admin-sidebar slot="leading" collapsible name="primary"> <!-- no resizable -->
|
|
594
|
+
<admin-topbar slot="header">
|
|
595
|
+
- <select-ui avatar="…" value="…" variant="ghost"></select-ui> <!-- context-switcher -->
|
|
596
|
+
+ <span slot="heading">My App</span>
|
|
597
|
+
</admin-topbar>
|
|
598
|
+
<section-ui>
|
|
599
|
+
<nav-ui>
|
|
600
|
+
- <nav-item-ui …badge="12"></nav-item-ui>
|
|
601
|
+
- <nav-group-ui …>…</nav-group-ui> <!-- no groups -->
|
|
602
|
+
+ <nav-item-ui icon="house" text="Home" value="home" selected></nav-item-ui>
|
|
603
|
+
+ <nav-item-ui icon="folder" text="Items" value="items"></nav-item-ui>
|
|
604
|
+
+ <nav-item-ui icon="gear" text="Settings" value="settings"></nav-item-ui>
|
|
605
|
+
</nav-ui>
|
|
606
|
+
</section-ui>
|
|
607
|
+
- <admin-statusbar slot="footer">…</admin-statusbar> <!-- no user menu -->
|
|
608
|
+
</admin-sidebar>
|
|
609
|
+
|
|
610
|
+
<admin-content>
|
|
611
|
+
<admin-topbar slot="header">
|
|
612
|
+
<button-ui data-sidebar-toggle="leading" icon="sidebar" …></button-ui>
|
|
613
|
+
<breadcrumb-ui>…</breadcrumb-ui>
|
|
614
|
+
- <toggle-scheme-ui slot="action" …></toggle-scheme-ui>
|
|
615
|
+
- <button-ui slot="action" data-sidebar-toggle="trailing" …></button-ui>
|
|
616
|
+
</admin-topbar>
|
|
617
|
+
<admin-scroll>
|
|
618
|
+
<router-ui>
|
|
619
|
+
<admin-page>
|
|
620
|
+
<admin-page-header>
|
|
621
|
+
<header><h1>Home</h1></header>
|
|
622
|
+
- <tabs-ui>…</tabs-ui> <!-- no tabs -->
|
|
623
|
+
</admin-page-header>
|
|
624
|
+
<admin-page-body>…</admin-page-body>
|
|
625
|
+
</admin-page>
|
|
626
|
+
</router-ui>
|
|
627
|
+
</admin-scroll>
|
|
628
|
+
- <admin-statusbar slot="footer">…version-strip…</admin-statusbar>
|
|
629
|
+
</admin-content>
|
|
630
|
+
|
|
631
|
+
- <admin-sidebar slot="trailing" …>…</admin-sidebar>
|
|
632
|
+
- <admin-command>…</admin-command>
|
|
633
|
+
</admin-shell>
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
#### Authoring discipline
|
|
637
|
+
|
|
638
|
+
- **Default to Advanced.** It's delete-only to step down. Stepping up is
|
|
639
|
+
a multi-slot refactor.
|
|
640
|
+
- **Audit against SoT BEFORE authoring.** The canonical references are
|
|
641
|
+
`site/index.html` (the live docs-site shell), `apps/saas/app/
|
|
642
|
+
admin-dashboard/admin-dashboard.contents.html` (a real product
|
|
643
|
+
surface), and `packages/web-modules/shell/admin-shell/admin-shell.examples.html`'s
|
|
644
|
+
sidebar-variant gallery. When yaml + SoT disagree (e.g. `slot="action"`
|
|
645
|
+
vs `[data-actions]`), yaml is the new-code canonical — but check
|
|
646
|
+
whether the SoT lags before deciding which to ship in a chunk that
|
|
647
|
+
must work against the deployed system. **A 30-second `cat site/index.html`
|
|
648
|
+
prevents the divergence class.**
|
|
649
|
+
- **Each removal must be justified at the §0 Reasoning Gate.** "There's
|
|
650
|
+
no second product" → context-switcher out. "No authenticated identity"
|
|
651
|
+
→ user-menu out. "No persistent chrome need" → trailing sidebar out.
|
|
652
|
+
- **`name="…"` on sidebars is OPTIONAL.** It defaults to the slot value
|
|
653
|
+
(`"leading"` / `"trailing"`) for `localStorage` namespacing. Only set
|
|
654
|
+
it when running multiple sidebars with the same slot (e.g. two
|
|
655
|
+
trailing sidebars on different routes).
|
|
656
|
+
- **`data-sidebar-toggle="<name>"` is value-bearing.** Buttons target
|
|
657
|
+
the sidebar by its `name` attribute (default = slot value). The
|
|
658
|
+
docs-site shell ships a bespoke boolean form
|
|
659
|
+
`data-sidebar-toggle-trailing` — that's the docs site's own wiring,
|
|
660
|
+
NOT the canonical contract documented in `admin-shell.js`.
|
|
661
|
+
- **`<admin-statusbar>` is dual-use.** Inside `<admin-sidebar slot="leading">
|
|
662
|
+
slot="footer">` it's the **user menu band**. Inside `<admin-content>
|
|
663
|
+
slot="footer">` it's the **content version-strip / status band**.
|
|
664
|
+
Same primitive, different host = different role.
|
|
665
|
+
- **`<admin-page-header>` always wraps a `<header>`** (native HTML). Inside it,
|
|
666
|
+
put `<h1>` + the action cluster in a title-row `<div>`
|
|
667
|
+
(`<div><h1/><div data-actions>…</div></div>`) — `[data-actions]` is what the
|
|
668
|
+
substrate right-aligns; a bare `<span slot="action">` is NOT styled for
|
|
669
|
+
`admin-page-header` and stacks under the `<h1>`. Tabs go AFTER the `<header>`
|
|
670
|
+
but still inside `<admin-page-header>`.
|
|
671
|
+
- **`<router-ui>` stays EMPTY in the shell.** `<admin-page>` +
|
|
672
|
+
`<admin-page-header>` + `<admin-page-body>` live in the **route
|
|
673
|
+
fragment** — a separate `.html` file loaded by the router on
|
|
674
|
+
navigation. The shell defines layout; routes define page content.
|
|
675
|
+
Putting `<admin-page>` inline inside `<router-ui>` makes the page
|
|
676
|
+
static across all routes (the router can't replace it).
|
|
677
|
+
- **The version-strip in the content statusbar is meant to be
|
|
678
|
+
overridden.** Most products replace it with their own status content
|
|
679
|
+
(sync indicator, row count, last-saved timestamp). Keep the
|
|
680
|
+
`<span data-spacer></span>` between leading + trailing spans to
|
|
681
|
+
preserve the push-to-end layout.
|
|
682
|
+
|
|
683
|
+
## Data binding: REST endpoint → component
|
|
684
|
+
|
|
685
|
+
Use `data-stream-*` attributes for declarative HTTP binding.
|
|
686
|
+
**No `fetch()` calls in script blocks.** No manual DOM updates.
|
|
687
|
+
|
|
688
|
+
```html
|
|
689
|
+
<!-- GET /api/claims every 10s → table rows -->
|
|
690
|
+
<table-ui
|
|
691
|
+
data-stream-src="/api/claims"
|
|
692
|
+
data-stream-interval="10000"
|
|
693
|
+
data-stream-path="items">
|
|
694
|
+
</table-ui>
|
|
695
|
+
|
|
696
|
+
<!-- GET /api/stats (one-shot) → KPI card -->
|
|
697
|
+
<stat-ui
|
|
698
|
+
label="Open Claims"
|
|
699
|
+
data-stream-src="/api/stats"
|
|
700
|
+
data-stream-target="value"
|
|
701
|
+
data-stream-path="open_count">
|
|
702
|
+
</stat-ui>
|
|
703
|
+
|
|
704
|
+
<!-- SSE stream → live updates -->
|
|
705
|
+
<table-ui
|
|
706
|
+
data-stream-src="/api/claims/live"
|
|
707
|
+
data-stream-mode="sse"
|
|
708
|
+
data-stream-event="claim-updated">
|
|
709
|
+
</table-ui>
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
| Attribute | Purpose | Example |
|
|
713
|
+
|---|---|---|
|
|
714
|
+
| `data-stream-src` | Endpoint URL | `/api/claims` |
|
|
715
|
+
| `data-stream-interval` | Poll interval (ms) | `10000` |
|
|
716
|
+
| `data-stream-mode` | `fetch` (default) or `sse` | `sse` |
|
|
717
|
+
| `data-stream-path` | JSON path into response | `items`, `data.claims` |
|
|
718
|
+
| `data-stream-target` | Which property to set | `value`, `rows`, `textContent` |
|
|
719
|
+
| `data-stream-method` | HTTP method | `POST` |
|
|
720
|
+
| `data-stream-headers` | JSON headers string | `{"Authorization":"Bearer …"}` |
|
|
721
|
+
| `data-stream-id` | Share one transport across elements | `claims-feed` |
|
|
722
|
+
|
|
723
|
+
**Shared transport:** two elements with the same `data-stream-id` share
|
|
724
|
+
one HTTP connection:
|
|
725
|
+
|
|
726
|
+
```html
|
|
727
|
+
<stat-ui data-stream-id="claims-feed" data-stream-src="/api/claims" data-stream-path="total" data-stream-target="value" label="Total"></stat-ui>
|
|
728
|
+
<table-ui data-stream-id="claims-feed" data-stream-path="items"></table-ui>
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
## Chart data wiring
|
|
732
|
+
|
|
733
|
+
`chart-ui` takes its data as a **JS property** — an array of plain objects —
|
|
734
|
+
plus `x=` / `y=` attributes naming which object key maps to each axis. It is
|
|
735
|
+
**not** the Chart.js `{ labels, datasets }` envelope; passing that (or any
|
|
736
|
+
non-array) renders a blank chart (a one-shot `console.warn` flags it).
|
|
737
|
+
|
|
738
|
+
```js
|
|
739
|
+
const chart = document.querySelector('chart-ui');
|
|
740
|
+
chart.data = [
|
|
741
|
+
{ month: 'Jan', revenue: 3200, expenses: 2100 },
|
|
742
|
+
{ month: 'Feb', revenue: 4100, expenses: 2400 },
|
|
743
|
+
];
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
```html
|
|
747
|
+
<!-- x= / y= name the object keys. Multi-series: comma-separate y. -->
|
|
748
|
+
<chart-ui type="bar" x="month" y="revenue"></chart-ui>
|
|
749
|
+
<chart-ui type="line" x="month" y="revenue,expenses"></chart-ui>
|
|
750
|
+
|
|
751
|
+
<!-- Sparkline / pie / donut: y only, no x -->
|
|
752
|
+
<chart-ui type="sparkline" y="revenue"></chart-ui>
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
`x` and `y` are documented props in `chart.yaml`; `.data` is a JS property
|
|
756
|
+
(also settable as a `data="[…]"` JSON-string attribute). The Chart.js
|
|
757
|
+
`{ labels, datasets }` shape is a different library's API — never chart-ui's.
|
|
758
|
+
|
|
759
|
+
## Segmented control — children must be `<segment-ui>`
|
|
760
|
+
|
|
761
|
+
`segmented-ui`'s children must be `<segment-ui>` custom elements. A bare
|
|
762
|
+
`<segment>` tag renders its text but receives **no** sliding indicator, no
|
|
763
|
+
`role="radio"` / `aria-checked`, and no reliable `change` state — silently
|
|
764
|
+
(a one-shot `console.warn` flags a non-`segment-ui` child).
|
|
765
|
+
|
|
766
|
+
```html
|
|
767
|
+
<!-- CORRECT -->
|
|
768
|
+
<segmented-ui value="30d">
|
|
769
|
+
<segment-ui value="30d" text="30d"></segment-ui>
|
|
770
|
+
<segment-ui value="7d" text="7d"></segment-ui>
|
|
771
|
+
<segment-ui value="1d" text="1d"></segment-ui>
|
|
772
|
+
</segmented-ui>
|
|
773
|
+
|
|
774
|
+
<!-- WRONG — bare <segment> breaks the indicator + a11y -->
|
|
775
|
+
<segmented-ui value="30d">
|
|
776
|
+
<segment value="30d">30d</segment>
|
|
777
|
+
</segmented-ui>
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
Importing `@adia-ai/web-components/components/segmented` pulls in
|
|
781
|
+
`<segment-ui>` transitively — no separate `components/segment` import needed.
|
|
782
|
+
|
|
783
|
+
## Entity list → detail drawer pattern
|
|
784
|
+
|
|
785
|
+
The canonical pattern for "list of claims → click row → detail panel":
|
|
786
|
+
|
|
787
|
+
```html
|
|
788
|
+
<!-- List view -->
|
|
789
|
+
<table-ui id="claims-table" data-stream-src="/api/claims" data-stream-path="items">
|
|
790
|
+
</table-ui>
|
|
791
|
+
|
|
792
|
+
<!-- Detail drawer: header + body + dual-cluster footer -->
|
|
793
|
+
<drawer-ui id="claim-drawer" side="right" size="lg">
|
|
794
|
+
|
|
795
|
+
<header slot="header">
|
|
796
|
+
<col-ui gap="1">
|
|
797
|
+
<h3 id="drawer-title">Claim Detail</h3>
|
|
798
|
+
<badge-ui id="drawer-status-badge" size="sm" hidden></badge-ui>
|
|
799
|
+
</col-ui>
|
|
800
|
+
<span slot="action">
|
|
801
|
+
<button-ui id="drawer-close" icon="x" variant="ghost" size="sm" aria-label="Close"></button-ui>
|
|
802
|
+
</span>
|
|
803
|
+
</header>
|
|
804
|
+
|
|
805
|
+
<section slot="body" id="claim-detail-body">
|
|
806
|
+
<!-- populated by row-select event.
|
|
807
|
+
NOTE: drawer-ui's body slot is the content surface.
|
|
808
|
+
Wrap stacked content in <col-ui gap="N"> if you need vertical rhythm;
|
|
809
|
+
drawer body has no default flex layout (only padding inset). -->
|
|
810
|
+
</section>
|
|
811
|
+
|
|
812
|
+
<footer slot="footer">
|
|
813
|
+
<!-- Dual-cluster footer: destructive on the left, commit on the right -->
|
|
814
|
+
<span slot="action-leading">
|
|
815
|
+
<button-ui id="drawer-delete" text="Delete" icon="trash" variant="danger" hidden></button-ui>
|
|
816
|
+
</span>
|
|
817
|
+
<span slot="action">
|
|
818
|
+
<button-ui id="drawer-cancel" text="Cancel" variant="ghost"></button-ui>
|
|
819
|
+
<button-ui id="drawer-save" text="Save" variant="primary" disabled></button-ui>
|
|
820
|
+
</span>
|
|
821
|
+
</footer>
|
|
822
|
+
|
|
823
|
+
</drawer-ui>
|
|
824
|
+
|
|
825
|
+
<script type="module">
|
|
826
|
+
// Row-select wiring — the only permitted script
|
|
827
|
+
document.getElementById('claims-table').addEventListener('select', e => {
|
|
828
|
+
const drawer = document.getElementById('claim-drawer');
|
|
829
|
+
const body = document.getElementById('claim-detail-body');
|
|
830
|
+
// Populate detail from event.detail.row
|
|
831
|
+
body.querySelector('[data-field="id"]').textContent = e.detail.row.id;
|
|
832
|
+
drawer.open = true;
|
|
833
|
+
});
|
|
834
|
+
</script>
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
> **Button density inside the drawer.** Header close button: `size="sm"`
|
|
838
|
+
> (icon-only, tucked in the corner — matches topbar chrome convention).
|
|
839
|
+
> Footer commit/cancel/delete buttons: **default size (no `size=` attribute)**
|
|
840
|
+
> — these are terminal commit/cancel/delete affordances on the user's
|
|
841
|
+
> decision moment; sub-sizing them with `size="sm"` makes them feel
|
|
842
|
+
> optional on the ~80px footer band. See §Button density by surface below.
|
|
843
|
+
|
|
844
|
+
> **Dual-cluster footer layout.** `slot="action-leading"` (destructive,
|
|
845
|
+
> left edge) + `slot="action"` (commit/cancel, trailing edge) is the
|
|
846
|
+
> canonical Delete/Cancel/Save layout. Do NOT wrap the buttons in a `<div>`
|
|
847
|
+
> — drawer-ui's footer CSS positions each `[slot=…]` cluster directly;
|
|
848
|
+
> wrapping bypasses the slot grid (same anti-pattern as card-ui header).
|
|
849
|
+
|
|
850
|
+
> **Drawer slot vocabulary.** `<drawer-ui>` internally creates the
|
|
851
|
+
> `<dialog>` host; **content MUST be slotted** via `slot="header"` /
|
|
852
|
+
> `slot="body"` / `slot="footer"` (the three positioned slots). Direct
|
|
853
|
+
> children with no `slot=` attribute do not project — the drawer renders
|
|
854
|
+
> empty. The earlier `<card-ui>` wrapper shape (recipe pre-2026-05-25)
|
|
855
|
+
> still works but offloads chrome ownership to the card; for terminal
|
|
856
|
+
> commit/cancel flows, native drawer slots give cleaner footer layout.
|
|
857
|
+
|
|
858
|
+
### Button density by surface
|
|
859
|
+
|
|
860
|
+
The footer-vs-chrome size convention matters across the kit. Default
|
|
861
|
+
size = "the action is the surface's primary purpose"; `size="sm"` =
|
|
862
|
+
"the action is one of many chrome affordances in a dense band":
|
|
863
|
+
|
|
864
|
+
| Surface | `size=` | Why |
|
|
865
|
+
|---|---|---|
|
|
866
|
+
| admin-topbar / chat-header / editor-toolbar | `"sm"` | Chrome density; many buttons in a narrow band |
|
|
867
|
+
| card-ui header action cluster | `"sm"` | Matches kebab-menu / icon-button neighbor density |
|
|
868
|
+
| table-toolbar-ui actions | `"sm"` | Same chrome-band density |
|
|
869
|
+
| drawer-ui **header** close button | `"sm"` | Icon-only corner-tucked affordance |
|
|
870
|
+
| **drawer-ui footer** commit/cancel/delete | **default** | Terminal-action; needs visual weight on the decision moment |
|
|
871
|
+
| modal-ui footer commit/cancel | default | Same logic as drawer footer |
|
|
872
|
+
| admin-page-header action cluster | default | Page-level CTAs feel primary, not chrome |
|
|
873
|
+
| inline-form commit/cancel | default | Same |
|
|
874
|
+
|
|
875
|
+
When in doubt: `size="sm"` for chrome-band buttons (topbar, card-header,
|
|
876
|
+
toolbar); default size for content-band terminal-action buttons (drawer
|
|
877
|
+
footer, modal footer, page-header CTAs, form commits).
|
|
878
|
+
|
|
879
|
+
**drawer-ui vs modal-ui decision:**
|
|
880
|
+
- `drawer-ui` — detail panels, contextual editing, side-by-side review
|
|
881
|
+
- `modal-ui` — confirmation dialogs, destructive actions, focused forms
|
|
882
|
+
|
|
883
|
+
## Entity status → badge variant mapping
|
|
884
|
+
|
|
885
|
+
| Domain status | `badge-ui` variant |
|
|
886
|
+
|---|---|
|
|
887
|
+
| Open / Active / New | `info` |
|
|
888
|
+
| In Review / Processing | `warning` |
|
|
889
|
+
| Approved / Resolved / Complete | `success` |
|
|
890
|
+
| Rejected / Failed / Overdue | `danger` |
|
|
891
|
+
| Closed / Archived / Inactive | `neutral` |
|
|
892
|
+
|
|
893
|
+
## Card-ui header — slotted children, NOT `<div>` wrapper
|
|
894
|
+
|
|
895
|
+
`<card-ui>` exposes a `<header>` slot that uses CSS Grid activated by
|
|
896
|
+
`:has(> [slot])`. h1-h6 elements auto-place into the heading column
|
|
897
|
+
without an explicit slot; elements with `slot="action"` go into the
|
|
898
|
+
trailing auto-width action column. The `1fr auto` grid only activates
|
|
899
|
+
when at least one **direct child** of `<header>` carries a `slot=`
|
|
900
|
+
attribute.
|
|
901
|
+
|
|
902
|
+
> ⚠️ **Anti-pattern: wrapping heading + action in a `<div>` bypasses
|
|
903
|
+
> the grid entirely.** The grid activator (`:has(> [slot])`) doesn't
|
|
904
|
+
> match because the `<header>`'s direct child is the `<div>`, which
|
|
905
|
+
> has no `slot=`. Both elements fall into the unslotted full-width
|
|
906
|
+
> fallback row — the action badge appears below the title instead of
|
|
907
|
+
> right-aligned.
|
|
908
|
+
|
|
909
|
+
**Wrong** (action appears below title — grid never activates):
|
|
910
|
+
|
|
911
|
+
```html
|
|
912
|
+
<card-ui>
|
|
913
|
+
<header>
|
|
914
|
+
<div> <!-- ✗ bare div bypasses grid -->
|
|
915
|
+
<h3>Title</h3>
|
|
916
|
+
<tag-ui>Badge</tag-ui>
|
|
917
|
+
</div>
|
|
918
|
+
</header>
|
|
919
|
+
</card-ui>
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
**Correct** (h3 auto-placed in 1fr heading column, tag-ui in trailing auto column):
|
|
923
|
+
|
|
924
|
+
```html
|
|
925
|
+
<card-ui>
|
|
926
|
+
<header>
|
|
927
|
+
<h3>Title</h3> <!-- auto-placed: 1fr heading column -->
|
|
928
|
+
<tag-ui slot="action" size="sm">Badge</tag-ui> <!-- slot=action: trailing auto column -->
|
|
929
|
+
</header>
|
|
930
|
+
</card-ui>
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
**Available header slots**:
|
|
934
|
+
|
|
935
|
+
| Slot | Position | Notes |
|
|
936
|
+
|------|----------|-------|
|
|
937
|
+
| `icon` (leading) | left of heading | optional |
|
|
938
|
+
| `heading` (1fr) | main column | `<h1>`–`<h6>` auto-place without explicit `slot=` |
|
|
939
|
+
| `description` (row 2) | spans full width | for sub-text below heading |
|
|
940
|
+
| `action` (trailing auto) | right edge | badges, buttons, kebab menus |
|
|
941
|
+
|
|
942
|
+
**CSS mechanism** (from `card.css`, for reference):
|
|
943
|
+
- `header:has(> [slot]) { display: grid; gap: … }` — grid only activates with slotted child
|
|
944
|
+
- `header :is([slot="heading"], h1, h2, h3, h4, h5, h6) { grid-column: 1; grid-row: 1 }`
|
|
945
|
+
- `header:not(:has(> [slot="icon"])):has(> [slot="action"]) { grid-template-columns: 1fr auto }`
|
|
946
|
+
|
|
947
|
+
**Detection** (manual): visually, the badge appears below or beside-the-wrong-edge of the title. No console error — it's a silent layout regression.
|
|
948
|
+
|
|
949
|
+
**Source**: 2026-05-23 docs-overhaul session generated this pattern repeatedly; the failure required visual debugging to identify. Documented to prevent recurrence in agent-generated card markup.
|
|
950
|
+
|
|
951
|
+
### Card-ui header — inline title + controls without `slot=action`
|
|
952
|
+
|
|
953
|
+
The CSS grid only activates when at least one direct child carries `slot=`. Two unslotted siblings (e.g., `<text-ui>` + `<button-ui>`) fall into the unslotted fallback row, which is `display: block` — they stack vertically instead of sharing one row.
|
|
954
|
+
|
|
955
|
+
**Wrong** (controls stack BELOW the title):
|
|
956
|
+
|
|
957
|
+
```html
|
|
958
|
+
<card-ui>
|
|
959
|
+
<header>
|
|
960
|
+
<text-ui weight="600">Recent activity</text-ui> <!-- no slot — fallback row -->
|
|
961
|
+
<button-ui size="sm" text="View all"></button-ui> <!-- no slot — next fallback row -->
|
|
962
|
+
</header>
|
|
963
|
+
</card-ui>
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
**Right A** (slot-grid path — canonical for hand-authored HTML):
|
|
967
|
+
|
|
968
|
+
```html
|
|
969
|
+
<card-ui>
|
|
970
|
+
<header>
|
|
971
|
+
<h3>Recent activity</h3> <!-- auto-placed: 1fr -->
|
|
972
|
+
<button-ui slot="action" size="sm" text="View all"></button-ui> <!-- trailing -->
|
|
973
|
+
</header>
|
|
974
|
+
</card-ui>
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
**Right B** (`<row-ui>` fallback — for contexts that strip `slot=`):
|
|
978
|
+
|
|
979
|
+
```html
|
|
980
|
+
<card-ui>
|
|
981
|
+
<header>
|
|
982
|
+
<row-ui justify="space-between" align="center">
|
|
983
|
+
<h3>Recent activity</h3>
|
|
984
|
+
<button-ui size="sm" text="View all"></button-ui>
|
|
985
|
+
</row-ui>
|
|
986
|
+
</header>
|
|
987
|
+
</card-ui>
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
**When to use each**:
|
|
991
|
+
- Right A is preferred — minimal markup, uses the primitive's intended grammar
|
|
992
|
+
- Right B is required when authoring corpus HTML for the A2UI gen-UI pipeline. The transpiler strips `slot=` attributes from many native-element children (`<span slot="action">`, `<text-ui slot="description">`). The `<row-ui>` form survives transpilation intact.
|
|
993
|
+
|
|
994
|
+
## Card-ui vs `<card-ui raw>` — surface-vs-chrome decision
|
|
995
|
+
|
|
996
|
+
`<card-ui>` defaults to a **chromed surface** (background + border + shadow). `<card-ui raw>` strips that chrome — content structure (`<header>`/`<section>`/`<footer>` grammar) still applies, but the parent owns the visible surface.
|
|
997
|
+
|
|
998
|
+
| Use | When |
|
|
999
|
+
|---|---|
|
|
1000
|
+
| `<card-ui>` (default) | Standalone content surface on a page background — dashboards, settings cards, summary tiles, list containers, form containers. The card IS the visible boundary. |
|
|
1001
|
+
| `<card-ui raw>` | Layout wrapper where the parent already owns the surface — auth screens centred on the viewport, embedded inside another chromed container, transparent column collectors. The card carries the slot grammar but not the chrome. |
|
|
1002
|
+
|
|
1003
|
+
**Two recurring anti-patterns**:
|
|
1004
|
+
|
|
1005
|
+
1. **Defaulting to `raw` when a chromed surface is intended.** Card content (icon + heading + body + footer) floats against the page background with no visible boundary. Looks "minimal" but is the symptom of "missed default" — the agent reached for `raw` to skip thinking about surface.
|
|
1006
|
+
|
|
1007
|
+
2. **Non-raw card without an explicit `<header>`.** The card has chrome but no title — the first visible content element (a `<field-ui label="…">`, a `<list-item-ui>`'s primary text, the first paragraph) becomes the de-facto heading visually. Reads as "two-title card" when label text and content text both compete for top-of-card primacy. Always include `<header>` with an explicit title on non-raw cards.
|
|
1008
|
+
|
|
1009
|
+
```html
|
|
1010
|
+
<!-- WRONG — chrome present, no header → field-ui label reads as "Section heading" -->
|
|
1011
|
+
<card-ui>
|
|
1012
|
+
<section>
|
|
1013
|
+
<field-ui label="Email addresses">…</field-ui>
|
|
1014
|
+
</section>
|
|
1015
|
+
</card-ui>
|
|
1016
|
+
|
|
1017
|
+
<!-- RIGHT — explicit header owns the title; field-ui label is field-scoped -->
|
|
1018
|
+
<card-ui>
|
|
1019
|
+
<header><h3>Invite teammates</h3></header>
|
|
1020
|
+
<section>
|
|
1021
|
+
<field-ui label="Email addresses">…</field-ui>
|
|
1022
|
+
</section>
|
|
1023
|
+
</card-ui>
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
**Source**: 2026-05-26 gen-review cycle 17 surfaced this across 8 demos (invite-members, contact-form, billing-overview, role-management, status-dashboard, audit-log, integrations-grid, webhook-config). Non-raw card without `<header>` was indistinguishable from a `<card-ui raw>` field group at first glance.
|
|
1027
|
+
|
|
1028
|
+
## Design board → AdiaUI token mapping
|
|
1029
|
+
|
|
1030
|
+
When the user provides a Figma board or screenshot:
|
|
1031
|
+
|
|
1032
|
+
1. **Extract intent, not values.** Read the board for visual hierarchy,
|
|
1033
|
+
component patterns, and UX intent — not pixel measurements.
|
|
1034
|
+
2. **Map design components to AdiaUI primitives.** A Figma "Status Chip"
|
|
1035
|
+
→ `<badge-ui>`. A Figma "Data Row" → `<table-ui>` row. A Figma
|
|
1036
|
+
"Side Panel" → `<drawer-ui>`.
|
|
1037
|
+
3. **Map colors to semantic tokens** — never copy hex values:
|
|
1038
|
+
- Primary brand color → `var(--a-primary-bg)` / `var(--a-primary-fg)`
|
|
1039
|
+
- Danger/error red → `var(--a-danger-strong)`
|
|
1040
|
+
- Neutral gray → `var(--a-chrome-*)`
|
|
1041
|
+
- Surface background → `var(--a-bg)` / `var(--a-bg-muted)` / `var(--a-bg-strong)`
|
|
1042
|
+
4. **Flag mismatches.** If the board shows a component AdiaUI doesn't have
|
|
1043
|
+
(e.g. a custom Gantt chart, a specialized calendar heatmap), flag it
|
|
1044
|
+
explicitly in the GenUI Plan `deviations:` field — do not silently
|
|
1045
|
+
approximate it with the wrong primitive.
|
|
1046
|
+
5. **Do not override tokens.** No inline `style="color: #3b82f6"`.
|
|
1047
|
+
If the design calls for a color not in the token system, that is a
|
|
1048
|
+
design system gap to surface to the user, not a one-off override.
|
|
1049
|
+
|
|
1050
|
+
## Loading state (v0.6.18 — `stat-ui` + `table-ui`)
|
|
1051
|
+
|
|
1052
|
+
Pre-v0.6.18, consumers hand-rolled skeleton CSS to bridge the gap between
|
|
1053
|
+
mount and first data render. v0.6.18 adds `loading` as a first-class
|
|
1054
|
+
prop on the two highest-leverage data-display primitives:
|
|
1055
|
+
|
|
1056
|
+
```html
|
|
1057
|
+
<!-- KPI cards: loading skeleton until value/change arrive -->
|
|
1058
|
+
<stat-ui label="Total Revenue" loading></stat-ui>
|
|
1059
|
+
|
|
1060
|
+
<!-- When data arrives, flip loading off and set the value+change -->
|
|
1061
|
+
<script>
|
|
1062
|
+
const card = document.querySelector('stat-ui');
|
|
1063
|
+
fetchKPI().then(({value, change, trend}) => {
|
|
1064
|
+
card.removeAttribute('loading');
|
|
1065
|
+
card.setAttribute('value', value);
|
|
1066
|
+
card.setAttribute('change', change);
|
|
1067
|
+
card.setAttribute('trend', trend);
|
|
1068
|
+
});
|
|
1069
|
+
</script>
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
```html
|
|
1073
|
+
<!-- Tables: skeleton rows preserve header + columns -->
|
|
1074
|
+
<table-ui id="recent" paginate="10" loading></table-ui>
|
|
1075
|
+
|
|
1076
|
+
<script>
|
|
1077
|
+
const t = document.getElementById('recent');
|
|
1078
|
+
t.columns = [{key:'name',label:'Name'}, /* … */];
|
|
1079
|
+
fetchRows().then(rows => {
|
|
1080
|
+
t.data = rows;
|
|
1081
|
+
t.removeAttribute('loading');
|
|
1082
|
+
});
|
|
1083
|
+
</script>
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
**What v0.6.18 ships:**
|
|
1087
|
+
|
|
1088
|
+
- `<stat-ui loading>` — renders `<skeleton-ui>` shimmer in the value + change
|
|
1089
|
+
slots. Label + icon are preserved (static metadata). Host gets `aria-busy="true"`.
|
|
1090
|
+
- `<table-ui loading>` — renders N ghost skeleton rows (N = `paginate` capped at 8,
|
|
1091
|
+
else 5). Header + columns stay intact so layout is preserved. Host gets
|
|
1092
|
+
`aria-busy="true"`. Pre-v0.6.18 the impl was a `<progress-ui>` spinner overlay
|
|
1093
|
+
even though the yaml description always said "skeleton rows" — that drift is
|
|
1094
|
+
now fixed.
|
|
1095
|
+
|
|
1096
|
+
**`chart-ui` does NOT have a `loading` prop** — by design. `chart-ui` is a
|
|
1097
|
+
renderer over already-shaped data; loading-state belongs in the container
|
|
1098
|
+
wrapping it. Canonical pattern:
|
|
1099
|
+
|
|
1100
|
+
```html
|
|
1101
|
+
<card-ui>
|
|
1102
|
+
<section>
|
|
1103
|
+
<!-- Wrap chart-ui with a sibling skeleton-ui while data fetches -->
|
|
1104
|
+
<skeleton-ui height="180px" width="100%" id="chart-skeleton"></skeleton-ui>
|
|
1105
|
+
<chart-ui hidden id="my-chart"></chart-ui>
|
|
1106
|
+
</section>
|
|
1107
|
+
</card-ui>
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
This split (own-state for data-prop primitives, compose-state for renderers)
|
|
1111
|
+
is intentional. See `components/chart/chart.examples.html` for the canonical
|
|
1112
|
+
demo, and `outbox/FEEDBACK-12--...--followup.md` for the design discussion.
|
|
1113
|
+
|
|
1114
|
+
## Button `text=` + `icon=` symbol doubling (FEEDBACK-15 trap)
|
|
1115
|
+
|
|
1116
|
+
`<button-ui>` renders `icon=` as a prepended `<icon-ui>` child AND
|
|
1117
|
+
renders `text=` as the visible label. When `text=` begins with the
|
|
1118
|
+
same symbol the icon already provides — `+` / `-` / `×` / `✓` / `→` /
|
|
1119
|
+
`←` — the glyph renders **twice**. Substrate console-warns via
|
|
1120
|
+
`ICON_TEXT_PREFIXES` in `node_modules/@adia-ai/web-components/components/button/class.js:23-30`,
|
|
1121
|
+
but the warn is per-element WeakSet-guarded and easy to miss in a
|
|
1122
|
+
noisy dev console.
|
|
1123
|
+
|
|
1124
|
+
```html
|
|
1125
|
+
<!-- Right -->
|
|
1126
|
+
<button-ui text="New Claim" icon="plus" variant="primary"></button-ui>
|
|
1127
|
+
|
|
1128
|
+
<!-- Wrong — doubled "+"; substrate console-warns (FEEDBACK-15) -->
|
|
1129
|
+
<button-ui text="+ New Claim" icon="plus" variant="primary"></button-ui>
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
**Discipline: `icon=` provides the glyph; `text=` is the semantic
|
|
1133
|
+
label only.** Strip the symbol from `text=` whenever the icon
|
|
1134
|
+
provides it. Covered collision pairs (substrate `ICON_TEXT_PREFIXES`):
|
|
1135
|
+
|
|
1136
|
+
| `icon=` | symbols stripped from `text=` |
|
|
1137
|
+
|---|---|
|
|
1138
|
+
| `plus` | `+`, `+` |
|
|
1139
|
+
| `minus` | `-`, `−`, `–` |
|
|
1140
|
+
| `x` | `×`, `x `, `X ` |
|
|
1141
|
+
| `check` | `✓`, `✔` |
|
|
1142
|
+
| `arrow-right` | `→`, `>` |
|
|
1143
|
+
| `arrow-left` | `←`, `<` |
|
|
1144
|
+
|
|
1145
|
+
The list is high-frequency, not exhaustive. If you find a doubled
|
|
1146
|
+
glyph that isn't covered (e.g. `dots-three` + `…`), the discipline
|
|
1147
|
+
still applies — substrate just won't warn yet.
|
|
1148
|
+
|
|
1149
|
+
## Imperative toasts via `feed-ui` (FB-11 P2)
|
|
1150
|
+
|
|
1151
|
+
`<feed-ui position="bottom-right">` is the toast container. To post a toast
|
|
1152
|
+
imperatively, either:
|
|
1153
|
+
|
|
1154
|
+
**Option A — global CustomEvent (works everywhere, no import needed):**
|
|
1155
|
+
|
|
1156
|
+
```js
|
|
1157
|
+
window.dispatchEvent(new CustomEvent('feed', {
|
|
1158
|
+
detail: { kind: 'success', title: 'Saved', message: 'Changes synced.' }
|
|
1159
|
+
}));
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
This is the canonical, lowest-friction API. `feed.js` (loaded via the
|
|
1163
|
+
barrel import `@adia-ai/web-components`) installs a global listener on
|
|
1164
|
+
`window` that routes `'feed'` CustomEvents to `UIFeed.post()`. No
|
|
1165
|
+
import needed — just dispatch the event from any module / framework /
|
|
1166
|
+
inline script.
|
|
1167
|
+
|
|
1168
|
+
**Option B — explicit class import:**
|
|
1169
|
+
|
|
1170
|
+
```ts
|
|
1171
|
+
import { UIFeed } from '@adia-ai/web-components/components/feed/class';
|
|
1172
|
+
UIFeed.post({ kind: 'success', title: 'Saved', message: 'Changes synced.' });
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
The canonical import path is the `/class` shorthand (resolves via the
|
|
1176
|
+
`./components/*/class` exports pattern). Do NOT try
|
|
1177
|
+
`@adia-ai/web-components/components/feed/feed.js` — Node's exports
|
|
1178
|
+
wildcards can't substitute across multiple path segments, so it resolves
|
|
1179
|
+
to a phantom nested path and fails. (Verified end-to-end in FB-11.)
|
|
1180
|
+
|
|
1181
|
+
**Post-options shape (same for both APIs):**
|
|
1182
|
+
|
|
1183
|
+
```ts
|
|
1184
|
+
{
|
|
1185
|
+
kind: 'success' | 'info' | 'warning' | 'danger',
|
|
1186
|
+
title: string,
|
|
1187
|
+
message: string,
|
|
1188
|
+
timeout: number, // ms, 0 = sticky
|
|
1189
|
+
}
|
|
1190
|
+
```
|
|
1191
|
+
|
|
1192
|
+
## Editor inspector pane (right-side property form inside `<editor-sidebar slot="trailing">`)
|
|
1193
|
+
|
|
1194
|
+
The canonical right-pane idiom inside `<editor-shell>` is a property
|
|
1195
|
+
inspector. It's not a separate component — it's a composition of
|
|
1196
|
+
primitives the editor cluster expects in the trailing sidebar.
|
|
1197
|
+
|
|
1198
|
+
```html
|
|
1199
|
+
<editor-sidebar slot="trailing" collapsible persist="inspector">
|
|
1200
|
+
<pane-ui side="trailing" resizable min-width="200">
|
|
1201
|
+
<header>
|
|
1202
|
+
<span>Inspector</span>
|
|
1203
|
+
<span slot="action">
|
|
1204
|
+
<button-ui id="wrap-btn" icon="brackets-square" variant="ghost" size="sm" title="Wrap selection"></button-ui>
|
|
1205
|
+
<button-ui id="duplicate-btn" icon="copy" variant="ghost" size="sm" title="Duplicate"></button-ui>
|
|
1206
|
+
</span>
|
|
1207
|
+
</header>
|
|
1208
|
+
<section>
|
|
1209
|
+
<fields-ui>
|
|
1210
|
+
<field-ui label="Name">
|
|
1211
|
+
<input-ui name="name" value="..."></input-ui>
|
|
1212
|
+
</field-ui>
|
|
1213
|
+
<field-ui label="Variant">
|
|
1214
|
+
<select-ui name="variant" data-options='[{"value":"primary","label":"Primary"}]'></select-ui>
|
|
1215
|
+
</field-ui>
|
|
1216
|
+
<field-ui label="Size">
|
|
1217
|
+
<segmented-ui>
|
|
1218
|
+
<segment-ui value="sm" text="Sm"></segment-ui>
|
|
1219
|
+
<segment-ui value="md" text="Md"></segment-ui>
|
|
1220
|
+
<segment-ui value="lg" text="Lg"></segment-ui>
|
|
1221
|
+
</segmented-ui>
|
|
1222
|
+
</field-ui>
|
|
1223
|
+
</fields-ui>
|
|
1224
|
+
</section>
|
|
1225
|
+
</pane-ui>
|
|
1226
|
+
</editor-sidebar>
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
**Why this shape:**
|
|
1230
|
+
- `<pane-ui side="trailing" resizable>` carries the inner-edge resize seam (per `pane.yaml` a2ui.rules — `[side]` flips grabber + chrome to the inner edge).
|
|
1231
|
+
- `<header>` slot is what `<pane-ui>` projects as the pane title; `[slot="action"]` button cluster pushes to the trailing edge.
|
|
1232
|
+
- `<section>` carries the form rows. Use `<fields-ui>` + `<field-ui label="…">` for the typed-row scaffolding — `<field-ui>` is the canonical label+control wrapper, not a raw `<label>`.
|
|
1233
|
+
- Persistence via `persist="inspector"` puts pane width under `adia-editor-sidebar-inspector` localStorage key (cluster-distinct from admin and chat sidebars).
|
|
1234
|
+
|
|
1235
|
+
**Pitfalls:**
|
|
1236
|
+
- Don't substitute raw `<div>` for `<fields-ui>` — the typography + spacing tokens hang on the bespoke wrapper. Reference: `apps/genui/a2ui-editor/` for the production canonical.
|
|
1237
|
+
- Don't put header `[slot="action"]` buttons *outside* the `<header>` element. The pane projects them via the header projection.
|
|
1238
|
+
- A non-resizable inspector (fixed-width detail rail) drops `resizable` from `<pane-ui>` but keeps `persist=` for future toggleability.
|
|
1239
|
+
|
|
1240
|
+
## Editor sub-views inside `<editor-canvas>` (Preview / Schema / DOM toggle)
|
|
1241
|
+
|
|
1242
|
+
When an editor has multiple representations of the same document
|
|
1243
|
+
(preview pane vs schema text vs DOM text), the canonical shape is a
|
|
1244
|
+
`<tabs-ui>` inside `<editor-canvas-toolbar>` driving multiple
|
|
1245
|
+
`<div data-view="…">` panels inside the canvas body.
|
|
1246
|
+
|
|
1247
|
+
```html
|
|
1248
|
+
<editor-canvas>
|
|
1249
|
+
<editor-canvas-toolbar>
|
|
1250
|
+
<tabs-ui id="view-tabs" value="preview" variant="underline">
|
|
1251
|
+
<tab-ui value="preview" text="Preview" icon="eye"></tab-ui>
|
|
1252
|
+
<tab-ui value="schema" text="Schema" icon="brackets-curly"></tab-ui>
|
|
1253
|
+
<tab-ui value="dom" text="DOM" icon="code"></tab-ui>
|
|
1254
|
+
</tabs-ui>
|
|
1255
|
+
</editor-canvas-toolbar>
|
|
1256
|
+
|
|
1257
|
+
<div data-view-body>
|
|
1258
|
+
<div data-view="preview">
|
|
1259
|
+
<!-- live rendered tree -->
|
|
1260
|
+
</div>
|
|
1261
|
+
<div data-view="schema" hidden>
|
|
1262
|
+
<code-ui id="schema-code" language="json" editable bare></code-ui>
|
|
1263
|
+
</div>
|
|
1264
|
+
<div data-view="dom" hidden>
|
|
1265
|
+
<!-- nested canvas-toolbar for HTML/CSS/JS sub-tabs -->
|
|
1266
|
+
<editor-canvas-toolbar>
|
|
1267
|
+
<tabs-ui id="dom-tabs" value="html" variant="underline">
|
|
1268
|
+
<tab-ui value="html" text="HTML"></tab-ui>
|
|
1269
|
+
<tab-ui value="css" text="CSS"></tab-ui>
|
|
1270
|
+
<tab-ui value="js" text="JS"></tab-ui>
|
|
1271
|
+
</tabs-ui>
|
|
1272
|
+
</editor-canvas-toolbar>
|
|
1273
|
+
<div data-dom-body>
|
|
1274
|
+
<div data-dom-view="html"><code-ui language="html" bare></code-ui></div>
|
|
1275
|
+
<div data-dom-view="css" hidden><code-ui language="css" bare></code-ui></div>
|
|
1276
|
+
<div data-dom-view="js" hidden><code-ui language="javascript" bare></code-ui></div>
|
|
1277
|
+
</div>
|
|
1278
|
+
</div>
|
|
1279
|
+
</div>
|
|
1280
|
+
</editor-canvas>
|
|
1281
|
+
```
|
|
1282
|
+
|
|
1283
|
+
**Why this shape:**
|
|
1284
|
+
- `<editor-canvas-toolbar>` is canvas-scope chrome (sticky at the top of the canvas body, per the yaml SoT). Distinct from the app-scope `<editor-toolbar>` at the top of `<editor-shell>`.
|
|
1285
|
+
- `<tabs-ui>` is the in-page section switcher (per the nav-decision rule — "switching views within the same logical page" not "navigating away").
|
|
1286
|
+
- The wiring is dead-simple: a `[value]` listener on `<tabs-ui>` flips the `[hidden]` attribute on the matching `<div data-view="…">`. The shell doesn't need to know about the sub-views.
|
|
1287
|
+
- Nested toolbars work: a sub-view can contain its own `<editor-canvas-toolbar>` for sub-sub-tabs. The DOM nesting is enough — the CSS doesn't constrain depth.
|
|
1288
|
+
|
|
1289
|
+
**Pitfalls:**
|
|
1290
|
+
- Don't use `<editor-toolbar>` for view-mode tabs. `<editor-toolbar>` is app-scope (top of the shell, document-wide actions); `<editor-canvas-toolbar>` is canvas-scope (top of the canvas, view-mode affordances). The yaml SoT spells this out in `editor-canvas-toolbar.yaml`'s `a2ui.rules`.
|
|
1291
|
+
- Don't toggle `[hidden]` on `<tab-ui>` — toggle it on the `<div data-view="…">` panels. The tabs-ui keeps its own selected-state via `[value]`.
|
|
1292
|
+
- Programmatic writes to a focused `<code-ui editable bare>` are no-ops by design (render-guard prevents clobbering in-flight typing). If you `.text = X` while user is typing in Schema view, the write silently won't apply. Intentional — don't "fix" it.
|
|
1293
|
+
|
|
1294
|
+
Production canonical: `apps/genui/a2ui-editor/` uses this exact shape with three top-level views + nested DOM sub-tabs.
|
|
1295
|
+
|
|
1296
|
+
---
|
|
1297
|
+
|
|
1298
|
+
## Meta / Introspection surfaces
|
|
1299
|
+
|
|
1300
|
+
**Pattern class:** Internal tooling, review galleries, audit dashboards, dogfood harnesses — any page whose purpose is *inspecting the system itself* rather than *using it*.
|
|
1301
|
+
|
|
1302
|
+
**Structural rules that consumer patterns don't cover:**
|
|
1303
|
+
|
|
1304
|
+
### 1. No nested card chrome
|
|
1305
|
+
|
|
1306
|
+
The most common mistake: wrapping a prompt block in a card, then wrapping the output inside another card. Every level of card chrome implies "this is a standalone surfaced thing" — but in an inspection surface, the hierarchy is already established by document headings.
|
|
1307
|
+
|
|
1308
|
+
❌ Wrong:
|
|
1309
|
+
```html
|
|
1310
|
+
<card-ui> <!-- prompt "card" -->
|
|
1311
|
+
<header>...</header>
|
|
1312
|
+
<section>
|
|
1313
|
+
<card-ui> <!-- output card inside -->
|
|
1314
|
+
<header>...</header>
|
|
1315
|
+
<section><canvas-ui></canvas-ui></section>
|
|
1316
|
+
</card-ui>
|
|
1317
|
+
</section>
|
|
1318
|
+
</card-ui>
|
|
1319
|
+
```
|
|
1320
|
+
|
|
1321
|
+
✅ Right — flat document rhythm:
|
|
1322
|
+
```html
|
|
1323
|
+
<section class="prompt-row"> <!-- role: document section, not card -->
|
|
1324
|
+
<h3>Login Form</h3>
|
|
1325
|
+
<code class="prompt-text">"login form with email and password"</code>
|
|
1326
|
+
<!-- flat tab bar, no wrapper chrome -->
|
|
1327
|
+
<div role="tablist">...</div>
|
|
1328
|
+
<div class="output-area"> <!-- single inset, not a card -->
|
|
1329
|
+
<canvas-ui></canvas-ui>
|
|
1330
|
+
</div>
|
|
1331
|
+
</section>
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
### 2. Headings as section breaks, not card headers
|
|
1335
|
+
|
|
1336
|
+
Use `h2` / `h3` for group and prompt labels. A card header is for interactive chrome that needs the card's slot vocabulary (icon, action, description). A static label needs none of that — it's just a heading.
|
|
1337
|
+
|
|
1338
|
+
```html
|
|
1339
|
+
<!-- group -->
|
|
1340
|
+
<h2>Auth <badge-ui text="6" variant="muted" size="sm"></badge-ui></h2>
|
|
1341
|
+
|
|
1342
|
+
<!-- individual item -->
|
|
1343
|
+
<h3>Login Form</h3>
|
|
1344
|
+
```
|
|
1345
|
+
|
|
1346
|
+
### 3. Code for literal values
|
|
1347
|
+
|
|
1348
|
+
Prompt strings, output IDs, version tags, selectors — any value the user will read verbatim or copy:
|
|
1349
|
+
|
|
1350
|
+
```html
|
|
1351
|
+
<!-- prompt text -->
|
|
1352
|
+
<code class="prompt-text">"login form with email and password"</code>
|
|
1353
|
+
|
|
1354
|
+
<!-- copyable ID chip -->
|
|
1355
|
+
<code class="output-id">auth-login-form-Z-v2</code>
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
Font: `var(--a-font-family-code, ui-monospace, monospace)`. Not `--a-font-family` (proportional).
|
|
1359
|
+
|
|
1360
|
+
### 4. Tab bars flush to content, no padding wrapper
|
|
1361
|
+
|
|
1362
|
+
Engine or view-mode tabs sit directly above their output, separated only by a 1px border. They don't need a padded container.
|
|
1363
|
+
|
|
1364
|
+
```css
|
|
1365
|
+
.gallery-tabs {
|
|
1366
|
+
display: flex;
|
|
1367
|
+
border-bottom: 1px solid var(--a-border-subtle);
|
|
1368
|
+
/* No padding, no background card, no border-radius */
|
|
1369
|
+
}
|
|
1370
|
+
.gallery-tab[aria-selected="true"] {
|
|
1371
|
+
border-bottom: 2px solid var(--a-accent-strong);
|
|
1372
|
+
color: var(--a-accent-strong);
|
|
1373
|
+
}
|
|
1374
|
+
```
|
|
1375
|
+
|
|
1376
|
+
### 5. Inset-not-card for output areas
|
|
1377
|
+
|
|
1378
|
+
The rendered output area gets visual containment via `background: var(--a-bg-subtle)` + `border: 1px solid var(--a-border-subtle)` + `border-radius`. That's enough — no `<card-ui>` wrapper needed.
|
|
1379
|
+
|
|
1380
|
+
```css
|
|
1381
|
+
.output-area {
|
|
1382
|
+
background: var(--a-bg-subtle);
|
|
1383
|
+
border: 1px solid var(--a-border-subtle);
|
|
1384
|
+
border-radius: var(--a-radius-md);
|
|
1385
|
+
padding: var(--a-space-4);
|
|
1386
|
+
min-height: 200px;
|
|
1387
|
+
max-height: 600px;
|
|
1388
|
+
overflow-y: auto;
|
|
1389
|
+
}
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
### 6. Two-column inspection layout
|
|
1393
|
+
|
|
1394
|
+
Left: sticky anchor-link rail to jump between groups (not a `<toc-ui>` — that auto-scans headings; here we want explicit curated links).
|
|
1395
|
+
Right: scrollable content with generous padding.
|
|
1396
|
+
|
|
1397
|
+
```html
|
|
1398
|
+
<div style="display:grid;grid-template-columns:220px 1fr;min-height:100dvh;">
|
|
1399
|
+
<nav style="position:sticky;top:0;height:100dvh;overflow-y:auto;border-right:1px solid var(--a-border-subtle);padding:var(--a-space-4);">
|
|
1400
|
+
<!-- explicit group links, not toc-ui -->
|
|
1401
|
+
<a href="#group-auth" class="rail-link">Auth <small>(6)</small></a>
|
|
1402
|
+
...
|
|
1403
|
+
</nav>
|
|
1404
|
+
<main style="padding:var(--a-space-6);">
|
|
1405
|
+
<!-- groups -->
|
|
1406
|
+
</main>
|
|
1407
|
+
</div>
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
**When to use this pattern:**
|
|
1411
|
+
- Gen UI output gallery
|
|
1412
|
+
- Eval result browsers
|
|
1413
|
+
- Audit log / drift report pages
|
|
1414
|
+
- Any internal tooling surface where the content IS the system's output
|
|
1415
|
+
|
|
1416
|
+
**What separates this from `patterns-shells.md`:** Shell patterns are for product pages users navigate to complete tasks. Meta surfaces are for operators inspecting or debugging the system — document-like, dense, information-first, no decorative chrome.
|