@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,1097 @@
|
|
|
1
|
+
# Shell Patterns
|
|
2
|
+
|
|
3
|
+
Application-level layout shells. Use when composing a *whole product
|
|
4
|
+
surface* — not a single card or form, but the wrapper that holds an
|
|
5
|
+
entire admin app, code editor, or section-with-rail page. These
|
|
6
|
+
patterns are the highest-leverage compose-time primitives in the
|
|
7
|
+
library: they wire sidebar toggles, resize handles, command palettes,
|
|
8
|
+
and responsive breakpoints so authors don't reinvent any of it.
|
|
9
|
+
|
|
10
|
+
## When to reach for a shell
|
|
11
|
+
|
|
12
|
+
| Surface | Shell |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Admin product (sidebar nav + main content + optional command palette) | `<admin-shell>` + `<nav-ui>` |
|
|
15
|
+
| Code / design-tool editor (topbar + resizable panes + footer status) | `<editor-shell>` + `<pane-ui>` children |
|
|
16
|
+
| Embedded app — a primary surface + provider-supplied side panels (50/50 ≥760px; cover-sheet below) | `<embed-shell>` + `[app]` / `[panel="…"]` children · ADR-0024 attr contract (`[opens]`/`[close]`) · *draft* |
|
|
17
|
+
| Long-form page with sub-rail (docs site, settings hub) | bare card layout + `<nav-ui variant="section">` rail |
|
|
18
|
+
| LLM-streaming chat surface (history, input, response rendering) | `<chat-shell>` + `<chat-thread>` + `<chat-composer>` |
|
|
19
|
+
| Marketing / landing / 404 / 500 / thank-you / single-card sign-in | `<simple-shell>` + `<simple-hero>` (+ optional `<simple-content>`) |
|
|
20
|
+
| Single dashboard / form / single chat thread | no shell — card composition direct |
|
|
21
|
+
|
|
22
|
+
If the page has more than one navigation surface (sidebar AND tabs AND
|
|
23
|
+
breadcrumbs), it almost always belongs in a shell. If it's one card
|
|
24
|
+
deep, it doesn't.
|
|
25
|
+
|
|
26
|
+
## Admin shell (admin product layout)
|
|
27
|
+
|
|
28
|
+
Behavior-only wrapper. Author provides the structural DOM — the shell wires sidebar toggles, resize handles, a `Cmd+K` command palette, and a `ResizeObserver` that drives responsive breakpoints. Tokens come from the same component layer; no shell-specific CSS to author.
|
|
29
|
+
|
|
30
|
+
Per ADR-0023 (shipped v0.3.6, all consumers migrated) and ADR-0024 (legacy shapes retired v0.4.0), the **canonical authoring shape** uses bespoke cluster-namespaced children. **The single source of truth is `chat-ui:site/index.html`** — the canonical 13-part anatomy below was extracted directly from it. Do NOT use the substrate's `admin-shell.examples.html` demo as a reference (it carried pre-ADR-0023 shapes through v0.5.x; same drift class as the `editor-shell.examples.html` issue tracked in `.agents/team/tickets/drafts/FB-editor-shell-demo-slot-vocab.md`).
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<admin-shell mode="rounded borderless">
|
|
34
|
+
|
|
35
|
+
<!-- ═══ LEFT SIDEBAR ═══ -->
|
|
36
|
+
<admin-sidebar slot="leading" resizable collapsible>
|
|
37
|
+
<admin-topbar slot="header">
|
|
38
|
+
<!-- Brand chrome — CANONICAL for SINGLE-PRODUCT apps (no workspace switching).
|
|
39
|
+
This is the default for ~most admin apps. The icon + label collapse together
|
|
40
|
+
as one slottable unit inside the collapsible sidebar.
|
|
41
|
+
For MULTI-TENANT (user belongs to >1 workspace), swap with:
|
|
42
|
+
<select-ui id="workspace-select" avatar="/icons/brand.svg" value="default" variant="ghost" style="flex:1"></select-ui>
|
|
43
|
+
See §Brand chrome below for all 3 patterns (static / switcher / nav-home) + decision table.
|
|
44
|
+
Do NOT use bare <span slot="icon"> + <span slot="heading"> here — that's the
|
|
45
|
+
re-implement-bare anti-pattern explicitly forbidden by admin-entity-item.yaml. -->
|
|
46
|
+
<admin-entity-item slot="heading">
|
|
47
|
+
<icon-ui slot="icon" name="lightning"></icon-ui>
|
|
48
|
+
<span slot="label">App Name</span>
|
|
49
|
+
</admin-entity-item>
|
|
50
|
+
</admin-topbar>
|
|
51
|
+
|
|
52
|
+
<section-ui>
|
|
53
|
+
<nav-ui id="nav">
|
|
54
|
+
<nav-item-ui data-command-trigger icon="magnifying-glass" text="Search" value="search">
|
|
55
|
+
<kbd slot="trailing">⌘K</kbd>
|
|
56
|
+
</nav-item-ui>
|
|
57
|
+
<hr data-nav-divider />
|
|
58
|
+
<nav-item-ui icon="gauge" text="Dashboard" value="/"></nav-item-ui>
|
|
59
|
+
<nav-item-ui icon="users" text="Members" value="/members"></nav-item-ui>
|
|
60
|
+
</nav-ui>
|
|
61
|
+
</section-ui>
|
|
62
|
+
|
|
63
|
+
<admin-statusbar slot="footer">
|
|
64
|
+
<!-- User identity — two canonical shapes by intent:
|
|
65
|
+
• INTERACTIVE user menu (sign-out, profile, settings actions) → <select-ui avatar>
|
|
66
|
+
• STATIC user identity (just display who's signed in) → <admin-entity-item>
|
|
67
|
+
Both share the same geometry. Pick by whether clicking the row opens a menu.
|
|
68
|
+
Below: interactive multi-tenant canonical. For static, mirror the header
|
|
69
|
+
Pattern A shape with <icon-ui slot="icon"> or <avatar-ui slot="icon"> + slot="label". -->
|
|
70
|
+
<select-ui id="user-select" avatar="https://example.com/user.jpg" value="me" variant="ghost" style="flex:1"></select-ui>
|
|
71
|
+
</admin-statusbar>
|
|
72
|
+
|
|
73
|
+
<div data-resize></div>
|
|
74
|
+
</admin-sidebar>
|
|
75
|
+
|
|
76
|
+
<!-- ═══ MAIN ═══ -->
|
|
77
|
+
<admin-content>
|
|
78
|
+
|
|
79
|
+
<admin-topbar>
|
|
80
|
+
<button-ui data-sidebar-toggle="leading" icon="sidebar" title="Toggle navigation sidebar" variant="ghost" size="sm"></button-ui>
|
|
81
|
+
<breadcrumb-ui id="breadcrumb">
|
|
82
|
+
<a href="/">App</a>
|
|
83
|
+
<span>Dashboard</span>
|
|
84
|
+
</breadcrumb-ui>
|
|
85
|
+
<span data-spacer></span>
|
|
86
|
+
<div data-actions>
|
|
87
|
+
<!-- Theme switcher — canonical: popover-ui + theme-panel; theme-picker-ui does NOT exist -->
|
|
88
|
+
<popover-ui id="theme-popover" placement="bottom-end">
|
|
89
|
+
<button-ui icon="palette" title="Choose theme" variant="ghost" size="sm" slot="trigger"></button-ui>
|
|
90
|
+
<theme-panel slot="content" persist parametric presets scheme-toggle></theme-panel>
|
|
91
|
+
</popover-ui>
|
|
92
|
+
<button-ui data-sidebar-toggle-trailing icon="note" variant="ghost" size="sm" aria-label="Side panel"></button-ui>
|
|
93
|
+
</div>
|
|
94
|
+
</admin-topbar>
|
|
95
|
+
|
|
96
|
+
<admin-scroll>
|
|
97
|
+
<!-- Page content — usually <admin-page> or <router-ui> -->
|
|
98
|
+
<admin-page>
|
|
99
|
+
<admin-page-header>
|
|
100
|
+
<header> <!-- REQUIRED inner column-owner -->
|
|
101
|
+
<div> <!-- title row: h1 left, actions right -->
|
|
102
|
+
<h1>Dashboard</h1>
|
|
103
|
+
<div data-actions>
|
|
104
|
+
<button-ui text="New" variant="primary" icon="plus"></button-ui>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<p>Operator-facing claims review.</p> <!-- optional description -->
|
|
108
|
+
</header>
|
|
109
|
+
</admin-page-header>
|
|
110
|
+
<admin-page-body>
|
|
111
|
+
<section> <!-- REQUIRED inner column-owner -->
|
|
112
|
+
<!-- Cards / grids / charts / tables — multiple <card-ui> siblings
|
|
113
|
+
get gap: var(--page-section-gap) ≈ a-space-8 automatically -->
|
|
114
|
+
</section>
|
|
115
|
+
</admin-page-body>
|
|
116
|
+
</admin-page>
|
|
117
|
+
</admin-scroll>
|
|
118
|
+
|
|
119
|
+
<admin-statusbar>
|
|
120
|
+
<span>App</span>
|
|
121
|
+
<span data-spacer></span>
|
|
122
|
+
<span id="version-label">v1.0.0</span>
|
|
123
|
+
</admin-statusbar>
|
|
124
|
+
|
|
125
|
+
</admin-content>
|
|
126
|
+
|
|
127
|
+
<!-- ═══ RIGHT SIDEBAR (optional) ═══ -->
|
|
128
|
+
<admin-sidebar slot="trailing" resizable collapsible id="side-rail" hidden>
|
|
129
|
+
<admin-topbar slot="header">
|
|
130
|
+
<span slot="heading">Activity</span>
|
|
131
|
+
<button-ui slot="action" data-sidebar-toggle-trailing icon="x" variant="ghost" size="sm" aria-label="Close"></button-ui>
|
|
132
|
+
</admin-topbar>
|
|
133
|
+
<section-ui>
|
|
134
|
+
<!-- ai chat companion, changelog feed, inspector, etc. -->
|
|
135
|
+
</section-ui>
|
|
136
|
+
<div data-resize></div>
|
|
137
|
+
</admin-sidebar>
|
|
138
|
+
|
|
139
|
+
<!-- ═══ COMMAND PALETTE ═══ -->
|
|
140
|
+
<admin-command>
|
|
141
|
+
<command-ui id="cmd" placeholder="Search..."></command-ui>
|
|
142
|
+
</admin-command>
|
|
143
|
+
|
|
144
|
+
</admin-shell>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Slot vocabulary (where `slot=` is REQUIRED vs FORBIDDEN).** The same tag (`<admin-topbar>`, `<admin-statusbar>`) appears in TWO positions with DIFFERENT slot rules — getting this wrong silently breaks layout:
|
|
148
|
+
|
|
149
|
+
| Element | Position | `slot=` attribute |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `<admin-sidebar>` | Direct child of `<admin-shell>` | **REQUIRED**: `slot="leading"` or `slot="trailing"` |
|
|
152
|
+
| `<admin-content>` | Direct child of `<admin-shell>` | **No slot** (default position) |
|
|
153
|
+
| `<admin-command>` | Direct child of `<admin-shell>` | **No slot** |
|
|
154
|
+
| `<admin-topbar>` | Inside `<admin-sidebar>` (sidebar header) | **REQUIRED**: `slot="header"` |
|
|
155
|
+
| `<admin-topbar>` | Inside `<admin-content>` (content-area top chrome) | **No slot** — positioned as first child via CSS, not via slot vocab |
|
|
156
|
+
| `<admin-statusbar>` | Inside `<admin-sidebar>` (sidebar footer) | **REQUIRED**: `slot="footer"` |
|
|
157
|
+
| `<admin-statusbar>` | Inside `<admin-content>` (content-area bottom chrome) | **No slot** — positioned as last child via CSS |
|
|
158
|
+
| `<admin-page>` | Inside `<admin-scroll>` inside `<admin-content>` | **No slot** |
|
|
159
|
+
| `<admin-page-header>` / `<admin-page-body>` | Direct child of `<admin-page>` | **No slot** |
|
|
160
|
+
|
|
161
|
+
> ⚠️ **Common slot anti-pattern**: putting `slot="header"` on the **content-area** `<admin-topbar>` (inside `<admin-content>`). `slot="header"` only positions inside `<admin-sidebar>`. The content-area topbar is positioned by being the FIRST CHILD of `<admin-content>`, not by a slot attribute. Adding `slot="header"` is silently ignored at the CSS level but signals the wrong mental model — verify against the canonical above.
|
|
162
|
+
|
|
163
|
+
**The 13 canonical parts (post-ADR-0023):**
|
|
164
|
+
|
|
165
|
+
| Tag | Role | Attributes that matter |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `<admin-shell>` | Outer host; reflects `[mode]` to children | `mode="rounded borderless"` is the canonical visual |
|
|
168
|
+
| `<admin-sidebar slot="leading">` | Primary nav rail | **MUST have `resizable collapsible`** — pure `collapsible` loses the resize affordance; persists width per `[id]` to localStorage |
|
|
169
|
+
| `<admin-topbar slot="header">` inside sidebar | Sidebar's own top region — brand chrome (single-product `<admin-entity-item>` OR multi-tenant `<select-ui workspace-switcher>`) | Inner identity unit goes in `slot="heading"`; auxiliary buttons in `slot="action"`. See §Brand chrome below for the 3 canonical patterns + decision table. Do NOT use bare `<span slot="icon">` + `<span slot="heading">` (anti-pattern; admin-entity-item.yaml forbids it). |
|
|
170
|
+
| `<section-ui>` inside sidebar | The middle / scrollable section between the sidebar's header and footer | — |
|
|
171
|
+
| `<nav-ui>` inside section-ui | Primary nav items (`<nav-item-ui>`, `<nav-group-ui>`) | — |
|
|
172
|
+
| `<admin-statusbar slot="footer">` inside sidebar | Sidebar's footer region — user-menu (`<select-ui avatar>`) lives here | — |
|
|
173
|
+
| `<div data-resize>` inside sidebar | The resize-handle anchor element (REQUIRED for `resizable` to work) | — |
|
|
174
|
+
| `<admin-content>` (no slot — sibling of sidebars) | The center work region | — |
|
|
175
|
+
| `<admin-topbar>` inside admin-content (no slot — first child) | Content-area top: sidebar-toggle, breadcrumb, action cluster | `<button-ui data-sidebar-toggle="leading">` for the hamburger; `<span data-spacer>` for the auto-margin push; `<div data-actions>` for the trailing cluster |
|
|
176
|
+
| `<admin-scroll>` inside admin-content | The scroll viewport — owns scroll mechanics + sticky-bottom for `<admin-statusbar>` | — |
|
|
177
|
+
| `<admin-page>` inside admin-scroll | Per-page anatomy wrapper (alternative: `<router-ui>` for dynamic-route apps) | Children: `<admin-page-header>` + `<admin-page-body>` |
|
|
178
|
+
| `<admin-statusbar>` inside admin-content (no slot — last child) | Content-area footer: status indicator, page metadata, version label | — |
|
|
179
|
+
| `<admin-sidebar slot="trailing">` | OPTIONAL: secondary rail for activity feed, AI companion, inspector | `hidden` by default; toggled via `[data-sidebar-toggle-trailing]` |
|
|
180
|
+
| `<admin-command>` | Cmd+K command palette host | Wraps `<command-ui>`; carries the global keyboard listener |
|
|
181
|
+
|
|
182
|
+
**Wrapper-vs-column rule (the silent-degrade trap).** `<admin-page-header>` and `<admin-page-body>` are bespoke **wrappers** — they own full-width chrome (sticky position, border, background, scroll sizing). The **inner column-owner** (`<header>` / `<section>`) owns the centered reading column (max-width, padding, flex rhythm). Skipping the inner wrapper drops padding/max-width silently — the chrome still renders, but content flushes to the viewport edge.
|
|
183
|
+
|
|
184
|
+
| Wrapper | Required inner | What you lose if omitted |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| `<admin-page-header>` | `<header>` or `<header-ui>` | `padding-inline: var(--page-content-inset)` ≈ 40px, `max-width: var(--page-content-max-width)`, column flex layout |
|
|
187
|
+
| `<admin-page-body>` | `<section>` or `<section-ui>` | `padding: var(--page-content-inset)` ≈ 40px on all edges, `max-width`, `gap: var(--page-section-gap)` ≈ a-space-8 between sibling cards |
|
|
188
|
+
|
|
189
|
+
**Full-bleed escape hatch.** Use a direct-child element OTHER than `<header>` / `<section>` to opt out of the centered column intentionally — for `<iframe>` embeds, `<editor-shell>` nesting, or split-pane layouts. The wrapper's flex sizing still applies; only the column inset is dropped.
|
|
190
|
+
|
|
191
|
+
| Use case | Direct child of `<admin-page-body>` |
|
|
192
|
+
|---|---|
|
|
193
|
+
| Standard page content (cards, grids, charts, tables) | `<section>` — required for padding + section-gap |
|
|
194
|
+
| Iframe page (embedded doc, third-party widget) | `<iframe>` direct child — fills body, no padding |
|
|
195
|
+
| Editor-inside-admin (nested shell) | `<editor-shell>` direct child — opts out of column (see §Editor-inside-admin) |
|
|
196
|
+
| Split-pane layout | `<pane-ui>` siblings — owns its own chrome |
|
|
197
|
+
|
|
198
|
+
For full-bleed-with-padding (e.g. gallery surface), set `[data-content-full]` on the inner `<section>` — max-width relaxes to 100%, padding is preserved.
|
|
199
|
+
|
|
200
|
+
**No console warning fires** when the inner wrapper is omitted. Verify by reading `getComputedStyle(adminPageBody).padding` — should be `var(--page-content-inset)`, NOT `0px`. If `0px`, the inner `<section>` is missing.
|
|
201
|
+
|
|
202
|
+
**Key affordances:**
|
|
203
|
+
- `[data-sidebar-toggle="leading|trailing"]` — buttons that toggle the matching `<admin-sidebar>` rail. Place inside `<admin-topbar>` or anywhere in the shell.
|
|
204
|
+
- `[data-resize]` — anchor element inside `<admin-sidebar>` that grabs for width-drag. Required for `resizable` to work.
|
|
205
|
+
- `[data-spacer]` — empty `<span>` inside `<admin-topbar>` that pushes subsequent siblings to the trailing edge via auto-margin.
|
|
206
|
+
- `[data-actions]` — wrapping `<div data-actions>` for the trailing action cluster in `<admin-topbar>` **AND** for the action cluster inside `<admin-page-header>` `<header>` title row. Must be `<div>` with the `data-actions` attribute — bare `<span>` (or `<div>` without the attribute) doesn't match the CSS rule and the buttons fall into the title column instead of right-aligning. Anti-pattern: `<span><button-ui>New</button-ui></span>` next to `<h1>`.
|
|
207
|
+
|
|
208
|
+
**Responsive — `@bp` annotations are mandatory on layout primitives.** The shell itself handles sidebar collapse via the ResizeObserver, but **page content inside `<admin-page-body>` will overflow on narrow viewports unless you write `@bp`-annotated layouts.** This is the #1 missed kit guidance — full `@bp` reference in [`patterns-layout.md`](patterns-layout.md) §Responsive Layout. Quick reference:
|
|
209
|
+
|
|
210
|
+
```html
|
|
211
|
+
<!-- 5-up KPI grid on desktop, 2-up tablet, 1-up phone -->
|
|
212
|
+
<grid-ui columns="1 2@sm 3@md 5@lg" gap="3 4@md">
|
|
213
|
+
<stat-ui label="Total" value="766" trend="up"></stat-ui>
|
|
214
|
+
…
|
|
215
|
+
</grid-ui>
|
|
216
|
+
|
|
217
|
+
<!-- Side-by-side charts that stack on phone -->
|
|
218
|
+
<grid-ui columns="1 2@md" gap="4">
|
|
219
|
+
<card-ui>…</card-ui> <!-- volume chart -->
|
|
220
|
+
<card-ui>…</card-ui> <!-- donut chart -->
|
|
221
|
+
</grid-ui>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Breakpoints: `xs/sm/md/lg/xl` = 0/480/768/1024/1280 px. Never write `@media (max-width: …)` or inline `grid-template-columns` — both are anti-patterns the kit explicitly rejects.
|
|
225
|
+
|
|
226
|
+
**Legacy shapes RETIRED in v0.4.0** (ADR-0024). If you find ANY of these patterns in agent-generated DOM, the agent followed a stale recipe (chat-ui's `admin-shell.examples.html` carried these through v0.5.x; the actual canonical is `chat-ui:site/index.html`):
|
|
227
|
+
|
|
228
|
+
| Legacy shape (pre-v0.4.0) | Canonical replacement (post-v0.4.0) |
|
|
229
|
+
|---|---|
|
|
230
|
+
| `<header slot="header">` direct child of admin-shell | `<admin-topbar slot="header">` inside `<admin-sidebar>` |
|
|
231
|
+
| `<nav-ui slot="leading">` direct child of admin-shell | `<admin-sidebar slot="leading">` wrapping `<nav-ui>` |
|
|
232
|
+
| `<main>` direct child of admin-shell | `<admin-content>` |
|
|
233
|
+
| `<header-ui>` inside main | `<admin-topbar>` (no slot) inside admin-content |
|
|
234
|
+
| `<section-ui>` directly inside main (as the page body) | `<admin-scroll>` > `<admin-page>` > `{<admin-page-header>, <admin-page-body>}` |
|
|
235
|
+
| `<dialog data-command>` | `<admin-command>` |
|
|
236
|
+
| `<aside data-sidebar="leading">` | `<admin-sidebar slot="leading" resizable collapsible>` |
|
|
237
|
+
|
|
238
|
+
**The "halfway-modernized" anti-pattern.** A common failure mode: consumer adopts `<admin-shell>` + `<admin-sidebar>` + `<admin-content>` (the visible parts) but skips `<admin-scroll>` + `<admin-statusbar>` + `<admin-command>` (the invisible parts) and puts `<admin-topbar>` at the wrong level (sibling of `<admin-content>` instead of its first child). Symptoms: page scrolls the entire viewport instead of just the content region; no Cmd+K palette; missing status row at the bottom. Verify against the 13-part table above before shipping.
|
|
239
|
+
|
|
240
|
+
### Brand chrome in `<admin-topbar slot="header">` (sidebar header)
|
|
241
|
+
|
|
242
|
+
The canonical anatomy above shows the **single-product** brand shape (`<admin-entity-item>`) as the default — that's the majority case. For multi-tenant apps (user belongs to >1 workspace), swap to `<select-ui variant="ghost">`. Three canonical patterns total, picked by intent:
|
|
243
|
+
|
|
244
|
+
`<admin-entity-item>` is the canonical identity-row primitive (shipped `@adia-ai/web-modules@0.7.13`, FB-38). It owns the icon + label + badge geometry as one slottable unit that collapses correctly inside a collapsible sidebar AND aligns 1px-perfect with `<nav-item-ui>` icons in the section below.
|
|
245
|
+
|
|
246
|
+
> **For any non-interactive brand chrome (single product, no switcher), prefer `<admin-entity-item>` over bare `[slot="icon"]` + `[slot="heading"]`.** The primitive owns icon-label-badge geometry, the sidebar-collapse boundary, and `<nav-item-ui>` alignment. Per its own a2ui rule: *"admin-entity-item is the canonical icon + label + badge identity row for shell surfaces. Do NOT re-implement it with a bare `<span>` or an ad-hoc flex `<div>` — those have no shared collapse boundary."*
|
|
247
|
+
|
|
248
|
+
#### Pattern A — `<admin-entity-item>` (canonical static brand)
|
|
249
|
+
|
|
250
|
+
```html
|
|
251
|
+
<admin-sidebar slot="leading" resizable collapsible>
|
|
252
|
+
<admin-topbar slot="header">
|
|
253
|
+
<admin-entity-item slot="heading">
|
|
254
|
+
<icon-ui slot="icon" name="lightning"></icon-ui>
|
|
255
|
+
<span slot="label">Claims Portal</span>
|
|
256
|
+
<!-- Optional: <badge-ui slot="badge" text="Staging" size="sm" variant="warning"></badge-ui> -->
|
|
257
|
+
</admin-entity-item>
|
|
258
|
+
</admin-topbar>
|
|
259
|
+
…
|
|
260
|
+
</admin-sidebar>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Use when: one workspace, brand is the app's identity, no navigation home action.
|
|
264
|
+
|
|
265
|
+
**Why `<admin-entity-item>` over bare `[slot="icon"]` + `[slot="heading"]`:**
|
|
266
|
+
|
|
267
|
+
- **Aligns with `<nav-item-ui>` icons** — admin-entity-item's trigger-row geometry mirrors `<select-ui>` and sits at 12px (matches `<section-ui>` 4px + `<nav-item-ui>` 8px). Bare slot vocabulary lands at the topbar's `--page-sidebar-px` (4px) — **8px short** of nav alignment.
|
|
268
|
+
- **Collapses as one slottable unit** in collapsible sidebars (icon survives, label + badge hide together via the primitive's own CSS). Bare `[slot]` siblings have no shared collapse boundary and depend on separate cleanup rules that can drift across kit CSS evolution.
|
|
269
|
+
- **Has an explicit `[slot="badge"]`** for environment / role chips (staging, beta, production, internal).
|
|
270
|
+
- **Matches the user-identity footer** primitive — consistent identity-row geometry top AND bottom of the sidebar (see `<admin-statusbar slot="footer">` recipe in `patterns-recipes.md`).
|
|
271
|
+
|
|
272
|
+
#### Pattern B — `<select-ui variant="ghost">` interactive workspace switcher
|
|
273
|
+
|
|
274
|
+
```html
|
|
275
|
+
<admin-topbar slot="header">
|
|
276
|
+
<select-ui id="workspace-select" avatar="/icons/brand.svg" value="acme" variant="ghost" style="flex:1">
|
|
277
|
+
<option value="acme">Acme Co.</option>
|
|
278
|
+
<option value="bravo">Bravo Industries</option>
|
|
279
|
+
</select-ui>
|
|
280
|
+
</admin-topbar>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Use when: the user belongs to >1 workspace and can switch between them. **Do not use `<select-ui disabled>` to fake a static brand** — that re-implements `<admin-entity-item>` with a chevron artifact and `pointer-events: none`. For static (non-interactive) brand chrome, use Pattern A (`<admin-entity-item>`).
|
|
284
|
+
|
|
285
|
+
#### Pattern C — brand-anchored navigation home link
|
|
286
|
+
|
|
287
|
+
```html
|
|
288
|
+
<admin-topbar slot="header">
|
|
289
|
+
<a href="/" data-brand-home>
|
|
290
|
+
<admin-entity-item>
|
|
291
|
+
<icon-ui slot="icon" name="lightning"></icon-ui>
|
|
292
|
+
<span slot="label">Claims Portal</span>
|
|
293
|
+
</admin-entity-item>
|
|
294
|
+
</a>
|
|
295
|
+
</admin-topbar>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Use when: clicking the brand should route to `/` (home) — common in SPA admin apps where the brand doubles as a navigation anchor. Wrap `<admin-entity-item>` in an `<a>` rather than wrapping bare slots — preserves the identity-row collapse boundary.
|
|
299
|
+
|
|
300
|
+
#### Brand-chrome decision table
|
|
301
|
+
|
|
302
|
+
| Tier | Brand chrome | When |
|
|
303
|
+
|---|---|---|
|
|
304
|
+
| **Single-product, static** | **(A) `<admin-entity-item slot="heading">`** (canonical in anatomy above — line ~36) | **One workspace, brand is identity** |
|
|
305
|
+
| Multi-product workspace | (B) `<select-ui variant="ghost">` workspace switcher | User belongs to >1 workspace |
|
|
306
|
+
| Single-product, nav-home | (C) `<a href="/">` wrapping `<admin-entity-item>` | Brand should route to `/` on click |
|
|
307
|
+
|
|
308
|
+
> ⚠️ **Bare `<span slot="heading">Name</span>` (no icon, no anchor) is structurally valid but visually undersized AND misaligned with `<nav-item-ui>` icons (8px off).** Always use one of Pattern A/B/C above. Verified by `getBoundingClientRect`: bare-slot brand icon at x=4px vs admin-entity-item brand icon at x=12px (matches nav-icon at x=12px).
|
|
309
|
+
|
|
310
|
+
### Routing inside admin-shell
|
|
311
|
+
|
|
312
|
+
Three supported patterns for navigating between views inside the same admin-shell. Pick by app shape.
|
|
313
|
+
|
|
314
|
+
#### Pattern A — `<router-ui>` (dynamic routes)
|
|
315
|
+
|
|
316
|
+
The canonical pattern from `chat-ui:site/index.html`. Place `<router-ui>` where `<admin-page>` would otherwise go. The router stamps the matched route's `<template>` (keyed by path); each template contains a full `<admin-page>` with the inner `<header>`/`<section>` column-owners.
|
|
317
|
+
|
|
318
|
+
```html
|
|
319
|
+
<admin-scroll>
|
|
320
|
+
<router-ui id="router"></router-ui>
|
|
321
|
+
</admin-scroll>
|
|
322
|
+
|
|
323
|
+
<template id="route-dashboard">
|
|
324
|
+
<admin-page>
|
|
325
|
+
<admin-page-header><header><h1>Dashboard</h1></header></admin-page-header>
|
|
326
|
+
<admin-page-body><section><!-- cards / grids --></section></admin-page-body>
|
|
327
|
+
</admin-page>
|
|
328
|
+
</template>
|
|
329
|
+
|
|
330
|
+
<template id="route-claims">
|
|
331
|
+
<admin-page>
|
|
332
|
+
<admin-page-header><header><h1>Claims</h1></header></admin-page-header>
|
|
333
|
+
<admin-page-body><section><!-- cards / grids --></section></admin-page-body>
|
|
334
|
+
</admin-page>
|
|
335
|
+
</template>
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Use when: dynamic routes, deep linking, browser back/forward navigation.
|
|
339
|
+
|
|
340
|
+
#### Pattern B — separate `<admin-page>` per view, toggle via `[hidden]`
|
|
341
|
+
|
|
342
|
+
```html
|
|
343
|
+
<admin-scroll>
|
|
344
|
+
<admin-page id="view-dashboard">
|
|
345
|
+
<admin-page-header><header><h1>Dashboard</h1></header></admin-page-header>
|
|
346
|
+
<admin-page-body><section><!-- … --></section></admin-page-body>
|
|
347
|
+
</admin-page>
|
|
348
|
+
<admin-page id="view-claims" hidden>
|
|
349
|
+
<admin-page-header><header><h1>Claims</h1></header></admin-page-header>
|
|
350
|
+
<admin-page-body><section><!-- … --></section></admin-page-body>
|
|
351
|
+
</admin-page>
|
|
352
|
+
</admin-scroll>
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Each view is a complete `<admin-page>` direct child of `<admin-scroll>`. The `admin-page > admin-page-header` selector matches; sticky chrome works. Use when: 2-3 views, no deep linking, you want zero router setup.
|
|
356
|
+
|
|
357
|
+
#### Pattern C — anti-pattern: DON'T wrap in `<div>` for view toggle
|
|
358
|
+
|
|
359
|
+
```html
|
|
360
|
+
<!-- ✗ WRONG — wrapping breaks the direct-child selectors -->
|
|
361
|
+
<admin-scroll>
|
|
362
|
+
<admin-page>
|
|
363
|
+
<div id="view-dashboard">
|
|
364
|
+
<admin-page-header>…</admin-page-header>
|
|
365
|
+
<admin-page-body>…</admin-page-body>
|
|
366
|
+
</div>
|
|
367
|
+
</admin-page>
|
|
368
|
+
</admin-scroll>
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Symptoms: `<admin-page-header>` loses sticky/border/background (no `admin-page > admin-page-header` match); `<admin-page-body>` falls back to `display: inline` (HTMLUnknownElement default) and its `<section>` child renders inline-flowing. Catastrophic — and **silent**, no console warning.
|
|
372
|
+
|
|
373
|
+
Always nest the routing-control element OUTSIDE the per-view `<admin-page>` (Pattern B) or use `<router-ui>` (Pattern A).
|
|
374
|
+
|
|
375
|
+
**Don't reach for admin-shell when:** the page is a one-card login/signup, a marketing landing, or a doc page. Use `<simple-shell>` for those (see below). Admin-shell is for sidebar-IA product surfaces — settings, dashboards, lists, detail-with-aside.
|
|
376
|
+
|
|
377
|
+
## Editor shell (design-tool / code-editor layout)
|
|
378
|
+
|
|
379
|
+
Behavior-only editor shell for structural design-tool layouts. Author
|
|
380
|
+
provides toolbar + canvas + panes; the shell coordinates the bespoke
|
|
381
|
+
children + persists pane sizes.
|
|
382
|
+
|
|
383
|
+
Per ADR-0023
|
|
384
|
+
(shipped v0.3.6, all consumers migrated), the **canonical authoring
|
|
385
|
+
shape** uses cluster-namespaced bespoke children. Note: `<editor-sidebar>`
|
|
386
|
+
WRAPS `<pane-ui resizable>` rather than reimplementing drag — see
|
|
387
|
+
Pitfall #11 in the `bespoke-shell-children` skill.
|
|
388
|
+
|
|
389
|
+
```html
|
|
390
|
+
<editor-shell>
|
|
391
|
+
<editor-toolbar>
|
|
392
|
+
<span slot="action-leading"><icon-ui name="layers"></icon-ui></span>
|
|
393
|
+
<span slot="title">Untitled.fig</span>
|
|
394
|
+
<span slot="status">1920 × 1080 · Saved</span>
|
|
395
|
+
<span slot="action">
|
|
396
|
+
<button-ui text="Share" variant="ghost"></button-ui>
|
|
397
|
+
<button-ui text="Run" variant="primary"></button-ui>
|
|
398
|
+
</span>
|
|
399
|
+
</editor-toolbar>
|
|
400
|
+
|
|
401
|
+
<editor-sidebar slot="leading" collapsible persist="structure">
|
|
402
|
+
<pane-ui side="leading" resizable min-width="200">
|
|
403
|
+
<header>Layers</header>
|
|
404
|
+
<section><!-- tree / outline / explorer --></section>
|
|
405
|
+
</pane-ui>
|
|
406
|
+
</editor-sidebar>
|
|
407
|
+
|
|
408
|
+
<editor-canvas>
|
|
409
|
+
<editor-canvas-toolbar>
|
|
410
|
+
<tabs-ui value="preview" variant="underline">
|
|
411
|
+
<tab-ui value="preview" text="Preview"></tab-ui>
|
|
412
|
+
<tab-ui value="schema" text="Schema"></tab-ui>
|
|
413
|
+
</tabs-ui>
|
|
414
|
+
</editor-canvas-toolbar>
|
|
415
|
+
<editor-canvas-empty>
|
|
416
|
+
<empty-state-ui icon="square" heading="New document"
|
|
417
|
+
description="Drop content to begin."></empty-state-ui>
|
|
418
|
+
</editor-canvas-empty>
|
|
419
|
+
<!-- host-rendered canvas content (artboards, doc body) appears here -->
|
|
420
|
+
</editor-canvas>
|
|
421
|
+
|
|
422
|
+
<editor-sidebar slot="trailing" collapsible persist="inspector">
|
|
423
|
+
<pane-ui side="trailing" resizable min-width="200">
|
|
424
|
+
<header>Inspector</header>
|
|
425
|
+
<section><!-- properties / outputs / chat --></section>
|
|
426
|
+
</pane-ui>
|
|
427
|
+
</editor-sidebar>
|
|
428
|
+
|
|
429
|
+
<editor-statusbar>
|
|
430
|
+
<span slot="status">Saved</span>
|
|
431
|
+
<span slot="zoom">100%</span>
|
|
432
|
+
</editor-statusbar>
|
|
433
|
+
</editor-shell>
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**Slot vocabulary (substrate-grounded).** `editor-toolbar`, `editor-canvas-toolbar`, and `editor-statusbar` position children via CSS rules matching `[slot=…]` on the direct child (Light-DOM contract — there's no shadow projection). The CSS only positions the slot names below; other slot names render as inline children at the document-order position and may look "fine" but don't follow the canonical layout. Verified against `packages/web-modules/editor/editor-shell/css/editor-shell.bespoke.css`:
|
|
437
|
+
|
|
438
|
+
| Host | Positioned slots | Note |
|
|
439
|
+
|---|---|---|
|
|
440
|
+
| `<editor-toolbar>` | `title`, `status`, `action`, `action-leading` | First `[slot=action]` pushes to the trailing edge via auto-margin; subsequent `[slot=action]` siblings hug it. `[full-screen]` reflected (set by parent shell when `[focus-mode]`). |
|
|
441
|
+
| `<editor-canvas-toolbar>` | `default`, `title`, `action`, `action-leading` | `default` slot is the common case for the view-mode `<tabs-ui>`. |
|
|
442
|
+
| `<editor-statusbar>` | `status`, `cursor`, `zoom`, `action` | All four are inline siblings — separator-free, evenly spaced. |
|
|
443
|
+
| `<editor-sidebar>` | only `slot="leading"` / `slot="trailing"` on the host as discriminator | Sidebar's own children are NOT slot-positioned; you place a `<pane-ui resizable>` inside and the pane owns chrome. |
|
|
444
|
+
|
|
445
|
+
**Common drift trap (FB-trace D1, fixed v3.1.1):** earlier kit revisions used `slot="icon|heading|description"` on `<editor-toolbar>` (mirroring the `admin-topbar` vocabulary). Substrate CSS does NOT position those slot names on the editor cluster. Use `action-leading` for the leading icon, `title` for the document name, `status` for the secondary metadata cluster. The substrate's `editor-shell.examples.html` demo carried the same drift through v0.6.32; tracked in `.agents/team/tickets/drafts/FB-editor-shell-demo-slot-vocab.md`.
|
|
446
|
+
|
|
447
|
+
- `<editor-shell>` — host; reflects `[focus-mode]`; propagates to children's `[full-screen]` / `[focused]`
|
|
448
|
+
- `<editor-toolbar>` — app-scope top chrome with `title` / `status` / `action` / `action-leading` slot vocabulary; `[full-screen]` reflected; `[data-toolbar-action="<name>"]` buttons bubble `toolbar-action` events
|
|
449
|
+
- `<editor-canvas>` — central content surface; `[empty]` + `[focused]` reflected; public API `.focus()` / `.blur()` / `.resetView()` / `.zoom`
|
|
450
|
+
- `<editor-canvas-toolbar>` — sticky chrome bar at the TOP of `<editor-canvas>`; holds view-mode `<tabs-ui>` (preview / schema / DOM), breadcrumbs, or canvas-scoped actions. **Canvas-scope** (distinct from app-scope `<editor-toolbar>`); replaces the legacy `<div data-view-strip>` pattern. Slots: `default` / `title` / `action` / `action-leading`
|
|
451
|
+
- `<editor-canvas-empty>` — first-child empty-state slot inside `<editor-canvas>`; visibility driven by parent `[empty]` (CSS-only — no JS toggling)
|
|
452
|
+
- `<editor-sidebar slot="leading|trailing" collapsible persist="…">` — **WRAPS `<pane-ui resizable>` (delegation pattern)**; `[collapsed]` + `[resizing]` reflected; localStorage prefix `adia-editor-sidebar-{persist}`. The `[persist]` attribute is the storage-key suffix (preferred over deprecated `[name]` alias kept for v0.4.x back-compat).
|
|
453
|
+
- `<editor-statusbar>` — bottom chrome bar with `status` / `cursor` / `zoom` / `action` slots
|
|
454
|
+
|
|
455
|
+
**Legacy shapes retired in v0.4.0** (ADR-0024). Pre-v0.4.0 the shell read `<header>` / `<div data-canvas>` / `<pane-ui data-left|data-right>` / `<footer>` via priority chain; those paths were removed in v0.4.0. Consumers MUST use the bespoke vocabulary.
|
|
456
|
+
|
|
457
|
+
**Don't reach for editor-shell when:** the surface is a typical product page with sidebar + content + optional aside (use `admin-shell` instead). Editor-shell is for *editing* surfaces — code, canvases, designs — where pane resizing is a primary affordance.
|
|
458
|
+
|
|
459
|
+
## Standalone pane-ui (split-pane layout without a shell)
|
|
460
|
+
|
|
461
|
+
`<pane-ui>` is a [category: layout] primitive that works **standalone**, not just inside `<editor-sidebar>`. The substrate's `pane.yaml` SoT explicitly documents the standalone split-pane shape: two `<pane-ui side="leading|trailing">` siblings in a flex row produce a shared resize seam between them. Reach for this when you need a resizable split without the full editor cluster's toolbar + statusbar chrome — markdown-editor + preview, settings list + detail, JSON input + rendered output, side-by-side diff.
|
|
462
|
+
|
|
463
|
+
```html
|
|
464
|
+
<!-- Standalone two-pane split: editor + preview -->
|
|
465
|
+
<div style="display: flex; height: 100%;">
|
|
466
|
+
<pane-ui side="leading" resizable min-width="320">
|
|
467
|
+
<header>Source</header>
|
|
468
|
+
<section>
|
|
469
|
+
<textarea-ui placeholder="Type markdown…" rows="20"></textarea-ui>
|
|
470
|
+
</section>
|
|
471
|
+
</pane-ui>
|
|
472
|
+
<pane-ui side="trailing" resizable min-width="320">
|
|
473
|
+
<header>Preview</header>
|
|
474
|
+
<section>
|
|
475
|
+
<article><!-- rendered preview --></article>
|
|
476
|
+
</section>
|
|
477
|
+
</pane-ui>
|
|
478
|
+
</div>
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**Pane-ui contract (from `packages/web-components/components/pane/pane.yaml`):**
|
|
482
|
+
|
|
483
|
+
| Prop | Type | Default | Note |
|
|
484
|
+
|---|---|---|---|
|
|
485
|
+
| `resizable` | boolean | `false` | Adds the drag grabber. Without it, pane-ui is a static container. |
|
|
486
|
+
| `side` | enum `'' \| 'leading' \| 'trailing'` | `''` | Reflected. Unset → standalone-pane chrome (four-sided border, right-edge grabber). Set → "horizontal-sibling" chrome (border + grabber move to the inner edge so adjacent panes share a single seam). `trailing` also flips the drag direction so the pane grows leftward. |
|
|
487
|
+
| `collapsed` | boolean | `false` | Toggle to hide pane body; auto-created header arrow flips. |
|
|
488
|
+
| `min-width` | number | `200` | px floor for the resize drag. |
|
|
489
|
+
| `max-width` | number | `9999` | px ceiling for the resize drag. |
|
|
490
|
+
|
|
491
|
+
**Conventional inner shape** (matches `<editor-sidebar>` children, but you author it yourself when standalone):
|
|
492
|
+
|
|
493
|
+
```html
|
|
494
|
+
<pane-ui side="leading" resizable>
|
|
495
|
+
<header>Layers</header> <!-- auto-styled, with the toggle arrow on the right -->
|
|
496
|
+
<section>…</section> <!-- primary content; gets the scroll surface -->
|
|
497
|
+
<footer slot="footer">…</footer> <!-- optional, e.g. count summary -->
|
|
498
|
+
</pane-ui>
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Events:** `toggle` bubbles when `[collapsed]` flips (button press, keyboard, or programmatic `.toggle()`). Listen on the pane or any ancestor.
|
|
502
|
+
|
|
503
|
+
**When NOT to use standalone pane-ui:**
|
|
504
|
+
- The surface is an editor with a canvas + dual rails — use `<editor-shell>` instead; it carries the chrome (toolbar + statusbar) and the focus-mode propagation.
|
|
505
|
+
- The surface is an admin page where the sidebar is fixed-width (not user-resizable) — use `<admin-sidebar>` inside `<admin-shell>`; that delegation is already documented.
|
|
506
|
+
- You need keyboard shortcut wiring for sidebar collapse — wrap in the bespoke sidebar (`<editor-sidebar>` or `<admin-sidebar>`) which owns the `[data-sidebar-toggle]` keyboard plumbing.
|
|
507
|
+
|
|
508
|
+
**Anti-patterns:**
|
|
509
|
+
- **Don't nest a third pane-ui as a sibling of leading+trailing.** Pane-ui's `[side]` enum has only `leading` and `trailing`; three siblings break the resize-seam math. For three-pane layouts (file tree + editor + inspector), use `<editor-shell>` with both sidebars.
|
|
510
|
+
- **Don't hand-roll the resize handle.** Pane-ui owns drag entirely; styling the grabber requires editing the substrate (not consumer code).
|
|
511
|
+
|
|
512
|
+
## Editor-inside-admin (nested-shell composition)
|
|
513
|
+
|
|
514
|
+
Editor-shell and admin-shell are normally **sibling shells, not nested** — an admin product page typically links OUT to an editor flow (a separate route). The nested case **is supported** for editor-inside-admin-page-body pages — inline page builders, embedded canvases, or any "edit-this-thing" surface that stays inside the admin chrome rather than navigating away.
|
|
515
|
+
|
|
516
|
+
The nested case requires the `min-height: 0` flex-chain discipline; without it, the inner shell collapses to zero height because flex children default to `min-height: auto` (which equals content height, which is unknown for the canvas).
|
|
517
|
+
|
|
518
|
+
```html
|
|
519
|
+
<admin-shell>
|
|
520
|
+
<admin-sidebar slot="leading">…</admin-sidebar>
|
|
521
|
+
|
|
522
|
+
<admin-content>
|
|
523
|
+
<admin-topbar slot="header">
|
|
524
|
+
<breadcrumb-ui>
|
|
525
|
+
<a href="/">Dashboard</a>
|
|
526
|
+
<span>Page Builder</span>
|
|
527
|
+
</breadcrumb-ui>
|
|
528
|
+
</admin-topbar>
|
|
529
|
+
|
|
530
|
+
<admin-scroll>
|
|
531
|
+
<admin-page>
|
|
532
|
+
<admin-page-header>
|
|
533
|
+
<h1>Inline Page Builder</h1>
|
|
534
|
+
</admin-page-header>
|
|
535
|
+
|
|
536
|
+
<admin-page-body style="flex: 1; display: flex; flex-direction: column; min-height: 0;">
|
|
537
|
+
<editor-shell style="flex: 1; min-height: 0;">
|
|
538
|
+
<editor-toolbar>
|
|
539
|
+
<span slot="title">Untitled draft</span>
|
|
540
|
+
<span slot="status">Saved · 30s ago</span>
|
|
541
|
+
<button-ui slot="action" text="Publish" variant="primary"></button-ui>
|
|
542
|
+
</editor-toolbar>
|
|
543
|
+
|
|
544
|
+
<editor-sidebar slot="leading" collapsible persist="builder-structure">
|
|
545
|
+
<pane-ui side="leading" resizable min-width="200">
|
|
546
|
+
<header>Blocks</header>
|
|
547
|
+
<section><!-- block palette --></section>
|
|
548
|
+
</pane-ui>
|
|
549
|
+
</editor-sidebar>
|
|
550
|
+
|
|
551
|
+
<editor-canvas>
|
|
552
|
+
<editor-canvas-toolbar>
|
|
553
|
+
<tabs-ui value="design" variant="underline">
|
|
554
|
+
<tab-ui value="design" text="Design"></tab-ui>
|
|
555
|
+
<tab-ui value="preview" text="Preview"></tab-ui>
|
|
556
|
+
</tabs-ui>
|
|
557
|
+
</editor-canvas-toolbar>
|
|
558
|
+
<!-- live page rendering goes here -->
|
|
559
|
+
</editor-canvas>
|
|
560
|
+
|
|
561
|
+
<editor-sidebar slot="trailing" collapsible persist="builder-inspector">
|
|
562
|
+
<pane-ui side="trailing" resizable min-width="200">
|
|
563
|
+
<header>Properties</header>
|
|
564
|
+
<section><!-- inspector form --></section>
|
|
565
|
+
</pane-ui>
|
|
566
|
+
</editor-sidebar>
|
|
567
|
+
|
|
568
|
+
<editor-statusbar>
|
|
569
|
+
<span slot="status">12 blocks</span>
|
|
570
|
+
<span slot="zoom">100%</span>
|
|
571
|
+
</editor-statusbar>
|
|
572
|
+
</editor-shell>
|
|
573
|
+
</admin-page-body>
|
|
574
|
+
</admin-page>
|
|
575
|
+
</admin-scroll>
|
|
576
|
+
</admin-content>
|
|
577
|
+
</admin-shell>
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**The flex-chain rule (substrate-grounded):**
|
|
581
|
+
- Every ancestor of `<editor-shell>` from `<admin-page-body>` upward must carry `flex: 1; min-height: 0`.
|
|
582
|
+
- `<admin-page-body>` itself needs `display: flex; flex-direction: column` so its single editor-shell child sizes correctly.
|
|
583
|
+
- `<editor-shell>` itself needs `flex: 1; min-height: 0` to fill the body.
|
|
584
|
+
|
|
585
|
+
**Skipping any of those four properties = inner shell collapses to zero height** (the most common nested-shell bug). The substrate's `editor-shell.yaml` `a2ui.rules` calls this out explicitly:
|
|
586
|
+
|
|
587
|
+
> For editor-inside-admin nested-shell pages, place the editor-shell inside `<admin-page-body>` with `flex: 1; min-height: 0` on every ancestor in the flex chain. Without `min-height: 0` the inner shell collapses to zero height.
|
|
588
|
+
|
|
589
|
+
**Persist scoping.** When nesting, use distinct `persist=` suffixes for the editor's sidebars to avoid localStorage collisions with the outer admin shell. The example above uses `persist="builder-structure"` + `persist="builder-inspector"` — distinct from any unscoped `persist="structure"` / `persist="inspector"` you'd use in a standalone editor route.
|
|
590
|
+
|
|
591
|
+
**When NOT to nest:**
|
|
592
|
+
- The editor is the entire surface (no admin chrome around it) — keep it as a sibling route. Most editor flows are this shape.
|
|
593
|
+
- The "edit" interaction is small enough to fit in a `<dialog-ui>` or `<drawer-ui>` — don't reach for a full nested shell for an inline form-field edit.
|
|
594
|
+
|
|
595
|
+
## Editor canvas API + state (focus-mode keyboard, zoom controller, [empty] auto-management)
|
|
596
|
+
|
|
597
|
+
`<editor-shell>` and `<editor-canvas>` expose a small JS API for host wiring of keyboard shortcuts and zoom controllers. Three things you author yourself when building an editor; the substrate does the rest.
|
|
598
|
+
|
|
599
|
+
### 1. Focus-mode keyboard shortcut (host wires; shell propagates)
|
|
600
|
+
|
|
601
|
+
`<editor-shell>` carries `.toggleFocusMode()` which flips `[focus-mode]` and auto-propagates: `<editor-toolbar>[full-screen]`, `<editor-canvas>[focused]` (plus calls `<editor-canvas>.focus()` programmatically), and fires `editor-mode-change` with `detail.focusMode`. The shell does NOT bind a keyboard shortcut itself — the host owns the chord.
|
|
602
|
+
|
|
603
|
+
```html
|
|
604
|
+
<editor-shell id="editor">
|
|
605
|
+
<editor-toolbar>
|
|
606
|
+
<span slot="title">Untitled.fig</span>
|
|
607
|
+
<!-- A toolbar button with [data-toolbar-action] bubbles a `toolbar-action` event
|
|
608
|
+
on the shell — wire it OR a global keyboard shortcut OR both. -->
|
|
609
|
+
<button-ui slot="action" data-toolbar-action="toggle-focus"
|
|
610
|
+
icon="arrows-out" variant="ghost"
|
|
611
|
+
aria-label="Toggle focus mode"></button-ui>
|
|
612
|
+
</editor-toolbar>
|
|
613
|
+
<editor-canvas><!-- canvas content --></editor-canvas>
|
|
614
|
+
</editor-shell>
|
|
615
|
+
|
|
616
|
+
<script type="module">
|
|
617
|
+
const editor = document.getElementById('editor');
|
|
618
|
+
|
|
619
|
+
// (a) Toolbar-action route — listen to the bubbled custom event
|
|
620
|
+
editor.addEventListener('toolbar-action', (e) => {
|
|
621
|
+
if (e.detail?.action === 'toggle-focus') editor.toggleFocusMode();
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
// (b) Global keyboard route — Cmd+. (or Ctrl+. on non-mac) — your call
|
|
625
|
+
document.addEventListener('keydown', (e) => {
|
|
626
|
+
const isMac = navigator.platform.includes('Mac');
|
|
627
|
+
const mod = isMac ? e.metaKey : e.ctrlKey;
|
|
628
|
+
if (mod && e.key === '.') {
|
|
629
|
+
e.preventDefault();
|
|
630
|
+
editor.toggleFocusMode();
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// (c) React to the propagation — render a focus-mode-aware UI elsewhere
|
|
635
|
+
editor.addEventListener('editor-mode-change', (e) => {
|
|
636
|
+
document.body.classList.toggle('editor-focused', e.detail.focusMode);
|
|
637
|
+
});
|
|
638
|
+
</script>
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
**The propagation cascade (substrate-managed; do NOT toggle manually):**
|
|
642
|
+
- `editor-shell` sets `[focus-mode]` on itself → reflected attribute
|
|
643
|
+
- Auto-sets `editor-toolbar[full-screen]` (toolbar can `display: none` under that selector)
|
|
644
|
+
- Auto-sets `editor-canvas[focused]` AND calls `editor-canvas.focus()`
|
|
645
|
+
- Fires `editor-mode-change` `{detail: {focusMode: true|false}}` for host listeners
|
|
646
|
+
|
|
647
|
+
**Don't toggle `[focus-mode]`, `[full-screen]`, or `[focused]` yourself.** Use `editorShell.toggleFocusMode()` (or set `.focusMode = true/false` directly) and let the shell propagate. Toggling child attributes by hand breaks the focus call into the canvas and the toolbar/canvas state can desync.
|
|
648
|
+
|
|
649
|
+
### 2. Zoom controller — programmatic + UI integration
|
|
650
|
+
|
|
651
|
+
`<editor-canvas>` exposes `.zoom` as a getter/setter (default `1.0`) plus `.resetView()` which restores `1.0`. Setting `.zoom` writes `--editor-canvas-zoom` as a CSS custom property on the canvas — the host's canvas content layer can read it for scale transforms:
|
|
652
|
+
|
|
653
|
+
```html
|
|
654
|
+
<editor-shell>
|
|
655
|
+
<editor-toolbar>
|
|
656
|
+
<span slot="title">Document</span>
|
|
657
|
+
<span slot="status" id="zoom-readout">100%</span>
|
|
658
|
+
<button-ui slot="action" data-toolbar-action="zoom-out"
|
|
659
|
+
icon="minus" variant="ghost" aria-label="Zoom out"></button-ui>
|
|
660
|
+
<button-ui slot="action" data-toolbar-action="zoom-reset"
|
|
661
|
+
icon="dot" variant="ghost" aria-label="Reset zoom"></button-ui>
|
|
662
|
+
<button-ui slot="action" data-toolbar-action="zoom-in"
|
|
663
|
+
icon="plus" variant="ghost" aria-label="Zoom in"></button-ui>
|
|
664
|
+
</editor-toolbar>
|
|
665
|
+
|
|
666
|
+
<editor-canvas id="canvas">
|
|
667
|
+
<div class="artboard">
|
|
668
|
+
<!-- Host content scales via --editor-canvas-zoom: -->
|
|
669
|
+
<!-- .artboard { transform: scale(var(--editor-canvas-zoom, 1)); transform-origin: 0 0 } -->
|
|
670
|
+
</div>
|
|
671
|
+
</editor-canvas>
|
|
672
|
+
|
|
673
|
+
<editor-statusbar>
|
|
674
|
+
<span slot="zoom" id="status-zoom">100%</span>
|
|
675
|
+
</editor-statusbar>
|
|
676
|
+
</editor-shell>
|
|
677
|
+
|
|
678
|
+
<script type="module">
|
|
679
|
+
const editor = document.querySelector('editor-shell');
|
|
680
|
+
const canvas = document.getElementById('canvas');
|
|
681
|
+
const zoomReadout = document.getElementById('zoom-readout');
|
|
682
|
+
const statusZoom = document.getElementById('status-zoom');
|
|
683
|
+
|
|
684
|
+
const ZOOM_STEPS = [0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 4.0];
|
|
685
|
+
const updateReadouts = () => {
|
|
686
|
+
const pct = Math.round(canvas.zoom * 100) + '%';
|
|
687
|
+
zoomReadout.textContent = pct;
|
|
688
|
+
statusZoom.textContent = pct;
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
editor.addEventListener('toolbar-action', (e) => {
|
|
692
|
+
const idx = ZOOM_STEPS.findIndex(z => Math.abs(z - canvas.zoom) < 0.01);
|
|
693
|
+
if (e.detail?.action === 'zoom-in') {
|
|
694
|
+
canvas.zoom = ZOOM_STEPS[Math.min(idx + 1, ZOOM_STEPS.length - 1)] ?? canvas.zoom;
|
|
695
|
+
} else if (e.detail?.action === 'zoom-out') {
|
|
696
|
+
canvas.zoom = ZOOM_STEPS[Math.max(idx - 1, 0)] ?? canvas.zoom;
|
|
697
|
+
} else if (e.detail?.action === 'zoom-reset') {
|
|
698
|
+
canvas.resetView(); // resets to 1.0
|
|
699
|
+
}
|
|
700
|
+
updateReadouts();
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// Keyboard: Cmd+= / Cmd+- / Cmd+0 — host owns the chord
|
|
704
|
+
document.addEventListener('keydown', (e) => {
|
|
705
|
+
const mod = e.metaKey || e.ctrlKey;
|
|
706
|
+
if (!mod) return;
|
|
707
|
+
if (e.key === '=' || e.key === '+') { canvas.zoom = Math.min(canvas.zoom * 1.25, 4); updateReadouts(); e.preventDefault(); }
|
|
708
|
+
if (e.key === '-') { canvas.zoom = Math.max(canvas.zoom / 1.25, 0.25); updateReadouts(); e.preventDefault(); }
|
|
709
|
+
if (e.key === '0') { canvas.resetView(); updateReadouts(); e.preventDefault(); }
|
|
710
|
+
});
|
|
711
|
+
</script>
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
**Public API surface on `<editor-canvas>` (from `packages/web-modules/editor/editor-canvas/editor-canvas.js`):**
|
|
715
|
+
|
|
716
|
+
| API | Type | Note |
|
|
717
|
+
|---|---|---|
|
|
718
|
+
| `.zoom` | number, getter/setter | Default `1.0`. Setting writes `--editor-canvas-zoom` CSS custom property on the host. Host content reads via `transform: scale(var(--editor-canvas-zoom, 1))`. |
|
|
719
|
+
| `.resetView()` | method | Sets `.zoom = 1.0`. Forward-compatible — may also reset scroll position in future revs; don't replicate "set zoom to 1" inline. |
|
|
720
|
+
| `.focus()` | method | Claims focus; reflects `[focused]`. Called automatically by `editor-shell.toggleFocusMode()` when entering focus mode. |
|
|
721
|
+
| `.blur()` | method | Releases focus; clears `[focused]`. |
|
|
722
|
+
| `[empty]` | reflected boolean | **Auto-managed via MutationObserver** — see §3 below. Don't toggle. |
|
|
723
|
+
| `[focused]` | reflected boolean | Auto-set by `.focus()` / `.blur()`. Don't toggle. |
|
|
724
|
+
|
|
725
|
+
### 3. `[empty]` is auto-managed — don't toggle it yourself
|
|
726
|
+
|
|
727
|
+
`<editor-canvas>` auto-manages its `[empty]` reflected attribute via a MutationObserver on `childList`: `[empty]` is `true` when there are zero non-`<editor-canvas-empty>` children, `false` otherwise. The `<editor-canvas-empty>` child's visibility is then driven by the parent `[empty]` selector in CSS — entirely declarative, no JS toggling.
|
|
728
|
+
|
|
729
|
+
```html
|
|
730
|
+
<!-- Right — the canvas auto-detects empty/non-empty -->
|
|
731
|
+
<editor-canvas>
|
|
732
|
+
<editor-canvas-empty>
|
|
733
|
+
<empty-state-ui icon="square" heading="New document"
|
|
734
|
+
description="Drop content to begin."></empty-state-ui>
|
|
735
|
+
</editor-canvas-empty>
|
|
736
|
+
<!-- When you append a content child (artboard, doc body, etc.), [empty]
|
|
737
|
+
flips to false automatically; editor-canvas-empty hides via CSS. -->
|
|
738
|
+
</editor-canvas>
|
|
739
|
+
|
|
740
|
+
<script type="module">
|
|
741
|
+
// The right way to "show content":
|
|
742
|
+
const canvas = document.querySelector('editor-canvas');
|
|
743
|
+
const artboard = document.createElement('div');
|
|
744
|
+
artboard.className = 'artboard';
|
|
745
|
+
canvas.appendChild(artboard);
|
|
746
|
+
// → [empty] becomes false automatically; empty-state hides.
|
|
747
|
+
|
|
748
|
+
// The right way to "clear content":
|
|
749
|
+
canvas.querySelectorAll(':scope > :not(editor-canvas-empty)').forEach(el => el.remove());
|
|
750
|
+
// → [empty] becomes true automatically; empty-state shows.
|
|
751
|
+
</script>
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
**Anti-patterns:**
|
|
755
|
+
|
|
756
|
+
- ❌ `canvas.toggleAttribute('empty', isEmpty)` — manual toggle. The MutationObserver will fight you on the next childList mutation and reset to whatever it detects.
|
|
757
|
+
- ❌ `canvas.empty = isEmpty` — same problem; the substrate's MutationObserver overrides on next mutation.
|
|
758
|
+
- ❌ Wrapping content in `<editor-canvas-empty>` to "show it conditionally" — `<editor-canvas-empty>` is the empty-state slot; canvas content goes alongside it, not inside it.
|
|
759
|
+
- ❌ Toggling `<editor-canvas-empty>` visibility with `hidden` / `display: none` inline — the canvas's CSS already does that via `:host([empty]) ::slotted(editor-canvas-empty) { display: block }` (light-DOM equivalent: `editor-canvas[empty] > editor-canvas-empty { display: block }`).
|
|
760
|
+
|
|
761
|
+
**The implication for canvas content loading:** if your editor renders content asynchronously (fetch a document → mount it into the canvas), the empty state shows during the fetch and hides once the first content child mounts. Free behavior — no loading state wiring needed for the empty-state surface.
|
|
762
|
+
|
|
763
|
+
If you need a *distinct* loading state (vs. empty state — e.g. "loading…" with spinner instead of "drop content to begin"), nest a `<empty-state-ui>` with loading content as a sibling of `<editor-canvas-empty>`, gated by your own attribute:
|
|
764
|
+
|
|
765
|
+
```html
|
|
766
|
+
<editor-canvas>
|
|
767
|
+
<editor-canvas-empty>
|
|
768
|
+
<empty-state-ui icon="square" heading="New document"></empty-state-ui>
|
|
769
|
+
</editor-canvas-empty>
|
|
770
|
+
<div data-loading hidden>
|
|
771
|
+
<empty-state-ui icon="spinner" heading="Loading…"></empty-state-ui>
|
|
772
|
+
</div>
|
|
773
|
+
<!-- toggle [data-loading] yourself; [empty] auto-toggles based on child count -->
|
|
774
|
+
</editor-canvas>
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
## Chat shell (LLM streaming)
|
|
778
|
+
|
|
779
|
+
Behavior-only LLM-chat surface. Wires message history, streaming
|
|
780
|
+
adapter selection, and the input bar — given `proxy-url=` (or direct
|
|
781
|
+
provider creds) the shell handles the full request/response cycle and
|
|
782
|
+
streams tokens through `<chat-thread>` without any author JS.
|
|
783
|
+
|
|
784
|
+
Per ADR-0023
|
|
785
|
+
(shipped v0.3.5, all consumers migrated), the **canonical authoring
|
|
786
|
+
shape** uses cluster-namespaced bespoke children.
|
|
787
|
+
|
|
788
|
+
```html
|
|
789
|
+
<chat-shell
|
|
790
|
+
proxy-url="/api/chat"
|
|
791
|
+
provider="anthropic"
|
|
792
|
+
model="claude-sonnet-4-7"
|
|
793
|
+
system="You are a helpful assistant.">
|
|
794
|
+
<chat-header>
|
|
795
|
+
<span slot="name">Chat with Claude</span>
|
|
796
|
+
<chat-status slot="status" data-state="connected">Connected</chat-status>
|
|
797
|
+
</chat-header>
|
|
798
|
+
|
|
799
|
+
<chat-thread>
|
|
800
|
+
<chat-empty>
|
|
801
|
+
<empty-state-ui icon="chat-circle"
|
|
802
|
+
heading="Hello!" description="Ask me anything.">
|
|
803
|
+
</empty-state-ui>
|
|
804
|
+
</chat-empty>
|
|
805
|
+
</chat-thread>
|
|
806
|
+
|
|
807
|
+
<chat-composer>
|
|
808
|
+
<chat-input-ui placeholder="Message…" submit-on-enter></chat-input-ui>
|
|
809
|
+
</chat-composer>
|
|
810
|
+
</chat-shell>
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
**The 7-element chat cluster (per ADR-0023):**
|
|
814
|
+
- `<chat-shell>` — host orchestrator; reflects `[streaming]` / `[provider]` / `[model]` / `[system]` / `[proxy-url]` / `[thinking]`. Fires 8 events: `submit` (user message), `chunk` (streaming token), `thinking` (extended-thinking content), `done` (response complete), `error`, `abort`, `clear`, `message`.
|
|
815
|
+
- `<chat-header>` — top chrome stub; slot vocabulary `name` / `status` / `action` / `action-leading`. CSS-only.
|
|
816
|
+
- `<chat-thread streaming? empty?>` — scrollable message surface; scroll-to-bottom on new message with user-scroll-up suspension; `[streaming]` + `[empty]` reflected from host.
|
|
817
|
+
- `<chat-empty>` — first-child empty-state slot inside `<chat-thread>`; CSS-only visibility driven by parent `[empty]`.
|
|
818
|
+
- `<chat-composer disabled?>` — wraps `<chat-input-ui submit-on-enter>` (the `submit-on-enter` attribute is **required** for the composer-submit chain to fire on Enter); forwards inner submit as `composer-submit` event (prevents double-fire from the form-tier `submit`); reflects `[disabled]` from host `[streaming]`.
|
|
819
|
+
- `<chat-sidebar slot="leading|trailing" resizable collapsible persist="…">` — conversation rail; mirrors `<admin-sidebar>` API; `[collapsed]` + `[resizing]` reflected; localStorage prefix `adia-chat-sidebar-{persist}`. **Stable in v0.6.x** (no longer "planned").
|
|
820
|
+
- `<chat-status [data-state="streaming|connected|disconnected|…"]>` — inline status indicator; `[data-state]` drives token-coloring (CSS-only).
|
|
821
|
+
|
|
822
|
+
**Automatic state propagation — don't toggle child attributes manually.** When `<chat-shell>` reflects `[streaming]`, the shell automatically propagates `[streaming]` to `<chat-thread>` and `[disabled]` to `<chat-composer>`. Authors who toggle these by hand race the shell's own writes and produce flicker.
|
|
823
|
+
|
|
824
|
+
**Pick provider config explicitly.** The host attributes `[provider]` (anthropic / openai / google / stub), `[model]`, `[system]`, `[proxy-url]` (server-side proxy for keys), and `[thinking]` (Anthropic extended-thinking) are the supported surface; everything else routes through events.
|
|
825
|
+
|
|
826
|
+
**Legacy shapes retired in v0.4.0** (ADR-0024). Pre-v0.4.0 the shell read `<section data-chat-messages>`, `<chat-input-ui data-chat-input>`, and `<empty-state-ui data-chat-empty>` via priority chain; those paths were removed in v0.4.0. Consumers MUST use the bespoke vocabulary.
|
|
827
|
+
|
|
828
|
+
**Don't reach for chat-shell when:** the surface is a non-LLM messaging UI (use `<chat-thread>` + `<chat-composer>` directly without the shell — they work standalone and you wire your own submit handler). chat-shell's value is the LLM streaming + proxy-url + thinking-mode wiring.
|
|
829
|
+
|
|
830
|
+
## Simple shell (marketing / error / single-card)
|
|
831
|
+
|
|
832
|
+
Behavior-light single-surface shell — the canonical wrapper for any
|
|
833
|
+
page that **isn't** an admin dashboard, editor canvas, or LLM chat.
|
|
834
|
+
Use cases per substrate canonicals: marketing splashes, error pages
|
|
835
|
+
(404 / 500 / maintenance), thank-you / confirmation pages, single-card
|
|
836
|
+
flows (sign-in, password reset).
|
|
837
|
+
|
|
838
|
+
```html
|
|
839
|
+
<simple-shell centered>
|
|
840
|
+
<simple-hero>
|
|
841
|
+
<h1 slot="heading">404 — page not found</h1>
|
|
842
|
+
<p slot="lede">The page you're looking for doesn't exist (or moved).</p>
|
|
843
|
+
<div slot="actions">
|
|
844
|
+
<button-ui text="Back to home" variant="primary"></button-ui>
|
|
845
|
+
<button-ui text="Contact support" variant="ghost"></button-ui>
|
|
846
|
+
</div>
|
|
847
|
+
</simple-hero>
|
|
848
|
+
</simple-shell>
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
**The 3-element simple cluster (per ADR-0023):**
|
|
852
|
+
- `<simple-shell>` — host with two reflected boolean knobs:
|
|
853
|
+
- `[centered]` — vertically centers content in the viewport (use for 404 / 500 / maintenance / thank-you / sign-in card flows). Replaces the hand-rolled `<main style="display: grid; place-items: center">` pattern.
|
|
854
|
+
- `[full-bleed]` — drops the max-width constraint on `<simple-content>` children (use for marketing splashes where the hero should span edge-to-edge).
|
|
855
|
+
- `<simple-hero>` — hero region. Slots: `heading` (the `<h1>`), `lede` (a `<p>` description), `actions` (a `<div>` containing one or more `<button-ui>`), default (free-form content after the action row).
|
|
856
|
+
- `<simple-content>` — free-form body region; single default slot; width constrained unless host `[full-bleed]` is set.
|
|
857
|
+
|
|
858
|
+
```html
|
|
859
|
+
<!-- Marketing splash with two regions -->
|
|
860
|
+
<simple-shell>
|
|
861
|
+
<simple-hero>
|
|
862
|
+
<h1 slot="heading">Build UI that ships itself</h1>
|
|
863
|
+
<p slot="lede">A2UI + AdiaUI — generative UI as a first-class substrate.</p>
|
|
864
|
+
<div slot="actions">
|
|
865
|
+
<button-ui text="Get started" variant="primary"></button-ui>
|
|
866
|
+
<button-ui text="View docs" variant="ghost"></button-ui>
|
|
867
|
+
</div>
|
|
868
|
+
</simple-hero>
|
|
869
|
+
<simple-content>
|
|
870
|
+
<!-- features grid, testimonials, etc. -->
|
|
871
|
+
</simple-content>
|
|
872
|
+
</simple-shell>
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
**Don't reach for simple-shell when:** the surface has a sidebar / topbar / multi-column chrome — that's `<admin-shell>`. Don't author hand-rolled `<main style="display: grid; place-items: center"><card-ui>…</card-ui></main>` either: that re-implements `[centered]` and bypasses the canonical slot vocabulary.
|
|
876
|
+
|
|
877
|
+
## Nav family — one consolidated set of primitives
|
|
878
|
+
|
|
879
|
+
Per ADR-0015 § Nav consolidation, the prior 6-element family
|
|
880
|
+
(`<app-nav-ui>` / `<app-nav-group-ui>` / `<app-nav-item-ui>` /
|
|
881
|
+
`<section-nav-ui>` / `<section-nav-group-ui>` / `<section-nav-item-ui>`)
|
|
882
|
+
collapsed into 3 primitives in `@adia-ai/web-components`:
|
|
883
|
+
|
|
884
|
+
| Primitive | Role | Variants |
|
|
885
|
+
|---|---|---|
|
|
886
|
+
| `<nav-ui>` | Navigation rail | `[variant="primary"]` (default; app sidebar; ResizeObserver collapse to icon-only below 96px). `[variant="section"]` (subnav rail; quieter chrome; optional `[heading]` kicker). |
|
|
887
|
+
| `<nav-group-ui>` | Labeled group of nav items | Inherits parent's variant via CSS cascade. Primary: collapsible header with caret. Section: static kicker label, children always visible. |
|
|
888
|
+
| `<nav-item-ui>` | Single navigation link | Inherits parent's variant via CSS cascade. Primary: icon slot reserved (left indent). Section: empty icon hidden (flat), no leading selected indicator. |
|
|
889
|
+
| `<tabs-ui>` (+ `<tab-ui>`) | In-page section switcher | Lives inside `<card-ui>` header or as a child of `<main>`. NOT for cross-page navigation. |
|
|
890
|
+
|
|
891
|
+
### Decision rules
|
|
892
|
+
|
|
893
|
+
1. If the user can navigate AWAY from this page → `<nav-ui>` (sidebar or sub-rail).
|
|
894
|
+
2. If the user is switching VIEWS within the same logical page → `<tabs-ui>`.
|
|
895
|
+
3. App-style sidebar with collapse-to-icons → `<nav-ui>` (default `variant="primary"`).
|
|
896
|
+
4. Sub-rail beside an article (docs, settings hub) → `<nav-ui variant="section">`.
|
|
897
|
+
5. Standalone group/item with section styling outside a section rail → set `[variant="section"]` on the child directly (escape hatch from the cascade).
|
|
898
|
+
|
|
899
|
+
### Section-variant cascade pattern
|
|
900
|
+
|
|
901
|
+
`[variant="section"]` on the parent `<nav-ui>` cascades to direct
|
|
902
|
+
descendants via CSS rules using `:not([variant])`. The children's JS
|
|
903
|
+
state (their own `variant` prop) isn't mutated — the cascade is purely
|
|
904
|
+
visual. Explicit `[variant]` on a child always wins.
|
|
905
|
+
|
|
906
|
+
```html
|
|
907
|
+
<!-- Cascade — children inherit section styling -->
|
|
908
|
+
<nav-ui variant="section" heading="On this page">
|
|
909
|
+
<nav-group-ui text="Sign in">
|
|
910
|
+
<nav-item-ui text="Email" value="/auth/sign-in/email"></nav-item-ui>
|
|
911
|
+
<nav-item-ui text="Magic" value="/auth/sign-in/magic"></nav-item-ui>
|
|
912
|
+
</nav-group-ui>
|
|
913
|
+
<nav-group-ui text="Sign up">
|
|
914
|
+
<nav-item-ui text="Profile" value="/auth/sign-up/profile"></nav-item-ui>
|
|
915
|
+
</nav-group-ui>
|
|
916
|
+
</nav-ui>
|
|
917
|
+
|
|
918
|
+
<!-- Per-element — same shape, but explicit. Use when no parent rail. -->
|
|
919
|
+
<nav-group-ui variant="section" text="Sign in">
|
|
920
|
+
<nav-item-ui variant="section" text="Email" value="/email"></nav-item-ui>
|
|
921
|
+
</nav-group-ui>
|
|
922
|
+
|
|
923
|
+
<!-- Override — primary item inside a section rail -->
|
|
924
|
+
<nav-ui variant="section">
|
|
925
|
+
<nav-group-ui text="Account">
|
|
926
|
+
<nav-item-ui text="Profile" value="/profile"></nav-item-ui>
|
|
927
|
+
<nav-item-ui variant="primary" icon="warning"
|
|
928
|
+
text="Verify email" value="/verify"></nav-item-ui>
|
|
929
|
+
</nav-group-ui>
|
|
930
|
+
</nav-ui>
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
## Table family — table-ui + table-toolbar-ui pairing
|
|
934
|
+
|
|
935
|
+
A real data table is two components composed:
|
|
936
|
+
|
|
937
|
+
```html
|
|
938
|
+
<table-toolbar-ui for="members" text="Active members" count="24" no-search>
|
|
939
|
+
<button-ui slot="actions" text="Invite" variant="primary" icon="plus"></button-ui>
|
|
940
|
+
</table-toolbar-ui>
|
|
941
|
+
|
|
942
|
+
<table-ui id="members" data-stream-src="/api/members">
|
|
943
|
+
<!-- columns + rows wired via data-stream attribute -->
|
|
944
|
+
</table-ui>
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
**Why two components:** the toolbar carries the page-level affordances (title, count badge, filter / sort / columns popovers, debounced search input, bulk-action bar in selection mode); the table carries the rows. Pairing them via `[for]` lets each evolve independently — the toolbar doesn't need to know table internals; the table doesn't need to host its own header.
|
|
948
|
+
|
|
949
|
+
**Affordances on `<table-toolbar-ui>`:**
|
|
950
|
+
- `[slot="title"]` + `[slot="count"]` — left-cluster identification
|
|
951
|
+
- `[slot="action"]` — right-cluster primary actions (Invite, Export, +New)
|
|
952
|
+
- `[no-search]` / `[no-filter]` / `[no-sort]` / `[no-columns]` — opt out of any of the four built-in affordances. **Default: all four enabled.** (Inverted from a previous default-true API; if you see `[searchable]` / `[filterable]` in old examples that's the deprecated form.)
|
|
953
|
+
- `[variant="card"]` — wraps the toolbar in a card-style chrome for use outside `<card-ui>` parent
|
|
954
|
+
|
|
955
|
+
**Don't pair table-toolbar with:** `<card-ui>`'s own `<header>`. Pick one — either the toolbar above the table or a card header. Stacking both produces a doubled chrome row.
|
|
956
|
+
|
|
957
|
+
## Composition recipes
|
|
958
|
+
|
|
959
|
+
### Admin product page (the common shape)
|
|
960
|
+
|
|
961
|
+
```html
|
|
962
|
+
<admin-shell mode="rounded borderless">
|
|
963
|
+
<admin-sidebar slot="leading" resizable collapsible>
|
|
964
|
+
<admin-topbar slot="header">
|
|
965
|
+
<!-- Single-product brand chrome (canonical). For multi-tenant, swap to
|
|
966
|
+
<select-ui workspace-switcher> — see §Brand chrome above. -->
|
|
967
|
+
<admin-entity-item slot="heading">
|
|
968
|
+
<icon-ui slot="icon" name="lightning"></icon-ui>
|
|
969
|
+
<span slot="label">App Name</span>
|
|
970
|
+
</admin-entity-item>
|
|
971
|
+
</admin-topbar>
|
|
972
|
+
<section-ui>
|
|
973
|
+
<nav-ui>
|
|
974
|
+
<nav-item-ui icon="users" text="Members" value="/members"></nav-item-ui>
|
|
975
|
+
</nav-ui>
|
|
976
|
+
</section-ui>
|
|
977
|
+
<admin-statusbar slot="footer">
|
|
978
|
+
<!-- Interactive user menu (multi-tenant). For static user identity, swap to
|
|
979
|
+
<admin-entity-item> with avatar-ui or icon-ui in slot="icon". -->
|
|
980
|
+
<select-ui id="user" avatar="…" value="me" variant="ghost" style="flex:1"></select-ui>
|
|
981
|
+
</admin-statusbar>
|
|
982
|
+
<div data-resize></div>
|
|
983
|
+
</admin-sidebar>
|
|
984
|
+
|
|
985
|
+
<admin-content>
|
|
986
|
+
<admin-topbar>
|
|
987
|
+
<button-ui data-sidebar-toggle="leading" icon="sidebar" variant="ghost" size="sm"></button-ui>
|
|
988
|
+
<breadcrumb-ui><a href="/">App</a><span>Members</span></breadcrumb-ui>
|
|
989
|
+
<span data-spacer></span>
|
|
990
|
+
<div data-actions>
|
|
991
|
+
<popover-ui placement="bottom-end">
|
|
992
|
+
<button-ui icon="palette" variant="ghost" size="sm" slot="trigger"></button-ui>
|
|
993
|
+
<theme-panel slot="content" persist parametric presets scheme-toggle></theme-panel>
|
|
994
|
+
</popover-ui>
|
|
995
|
+
</div>
|
|
996
|
+
</admin-topbar>
|
|
997
|
+
|
|
998
|
+
<admin-scroll>
|
|
999
|
+
<admin-page>
|
|
1000
|
+
<admin-page-header>
|
|
1001
|
+
<header> <!-- REQUIRED inner column-owner -->
|
|
1002
|
+
<h1>Members</h1>
|
|
1003
|
+
</header>
|
|
1004
|
+
</admin-page-header>
|
|
1005
|
+
<admin-page-body>
|
|
1006
|
+
<section> <!-- REQUIRED inner column-owner -->
|
|
1007
|
+
<card-ui>
|
|
1008
|
+
<header><span slot="heading">Active members</span></header>
|
|
1009
|
+
<section bleed> <!-- card-ui's own section slot — different element -->
|
|
1010
|
+
<table-toolbar-ui for="members"></table-toolbar-ui>
|
|
1011
|
+
<table-ui id="members">…</table-ui>
|
|
1012
|
+
</section>
|
|
1013
|
+
</card-ui>
|
|
1014
|
+
</section>
|
|
1015
|
+
</admin-page-body>
|
|
1016
|
+
</admin-page>
|
|
1017
|
+
</admin-scroll>
|
|
1018
|
+
|
|
1019
|
+
<admin-statusbar>
|
|
1020
|
+
<span>App</span>
|
|
1021
|
+
<span data-spacer></span>
|
|
1022
|
+
<span>v1.0.0</span>
|
|
1023
|
+
</admin-statusbar>
|
|
1024
|
+
</admin-content>
|
|
1025
|
+
|
|
1026
|
+
<admin-command><command-ui placeholder="Search..."></command-ui></admin-command>
|
|
1027
|
+
</admin-shell>
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
- Admin-shell wraps everything; primary nav lives inside `<admin-sidebar slot="leading">`, NOT as a sibling.
|
|
1031
|
+
- Content region is `<admin-content>` containing `<admin-topbar>` + `<admin-scroll>` + `<admin-statusbar>`. The `<admin-scroll>` wraps `<admin-page>` which contains `<admin-page-header>` and `<admin-page-body>`.
|
|
1032
|
+
- **The inner `<header>` inside `<admin-page-header>` and the inner `<section>` inside `<admin-page-body>` are REQUIRED column-owners.** They own padding + max-width + section-gap; omitting them drops 40px of inset silently. See §Wrapper-vs-column rule.
|
|
1033
|
+
- The two `<section>` elements in this recipe are distinct: the outer is the page-body's centered column; the inner (with `bleed`) is the card's content slot. Both required.
|
|
1034
|
+
- Page content is a card per section; toolbar + table inside the card's `<section bleed>` (so the table runs edge-to-edge).
|
|
1035
|
+
- `<admin-command>` for the `Cmd+K` palette is always present (even if you don't expose it from the UI yet — wire it up later).
|
|
1036
|
+
- Full 13-part anatomy reference: see [§Admin shell](#admin-shell-admin-product-layout) above.
|
|
1037
|
+
|
|
1038
|
+
### Docs page (article + sub-rail)
|
|
1039
|
+
|
|
1040
|
+
```html
|
|
1041
|
+
<main data-docs prose>
|
|
1042
|
+
<nav-ui variant="section">
|
|
1043
|
+
<nav-group-ui text="Components">
|
|
1044
|
+
<nav-item-ui text="Button" value="/components/button"></nav-item-ui>
|
|
1045
|
+
…
|
|
1046
|
+
</nav-group-ui>
|
|
1047
|
+
</nav-ui>
|
|
1048
|
+
|
|
1049
|
+
<article>
|
|
1050
|
+
<h1>Button</h1>
|
|
1051
|
+
<p>…</p>
|
|
1052
|
+
</article>
|
|
1053
|
+
</main>
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
- No admin-shell — this is a doc, not a product surface
|
|
1057
|
+
- `<nav-ui variant="section">` is the sub-rail; sits as sibling to `<article>`
|
|
1058
|
+
- Children inherit section-variant styling automatically (kicker headers, flat items)
|
|
1059
|
+
|
|
1060
|
+
### Editor flow (panes + canvas)
|
|
1061
|
+
|
|
1062
|
+
```html
|
|
1063
|
+
<editor-shell>
|
|
1064
|
+
<editor-toolbar>…</editor-toolbar>
|
|
1065
|
+
<editor-sidebar slot="leading" collapsible>
|
|
1066
|
+
<pane-ui side="leading" resizable>…</pane-ui>
|
|
1067
|
+
</editor-sidebar>
|
|
1068
|
+
<editor-canvas>
|
|
1069
|
+
<code-ui language="js" editable>…</code-ui>
|
|
1070
|
+
</editor-canvas>
|
|
1071
|
+
<editor-sidebar slot="trailing" collapsible>
|
|
1072
|
+
<pane-ui side="trailing" resizable>…</pane-ui>
|
|
1073
|
+
</editor-sidebar>
|
|
1074
|
+
<editor-statusbar>…</editor-statusbar>
|
|
1075
|
+
</editor-shell>
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
- The center `<editor-canvas>` carries the work surface; `<editor-sidebar>` wraps each `<pane-ui>` on either side
|
|
1079
|
+
- Pane sizes persist via `<editor-sidebar>`'s localStorage prefix `adia-editor-sidebar-{name}` (cluster-distinct from admin / chat)
|
|
1080
|
+
|
|
1081
|
+
## Anti-patterns
|
|
1082
|
+
|
|
1083
|
+
- **Hand-rolling the sidebar toggle** instead of using `[data-sidebar-toggle]`. The shell wires the localStorage persistence + the resize-observer breakpoint logic; rolling your own re-derives both.
|
|
1084
|
+
- **Putting `<header>` inside `<main>` of `admin-shell`.** The shell already provides `[slot="header"]`; a second header in main is redundant chrome.
|
|
1085
|
+
- **Stacking `<card-ui>` header AND `<table-toolbar-ui>`.** Pick one. Both produce a chrome row above the data; both at once is doubled.
|
|
1086
|
+
- **Using `<nav-ui>` (primary) for in-page section switching.** That's `<tabs-ui>`'s job. Nav is for navigating between distinct routes/pages.
|
|
1087
|
+
- **Using the OLD `<app-nav-ui>` / `<section-nav-ui>` tags or any of their group/item variants.** The 6-element family was consolidated into 3 primitives in 0.0.33 per ADR-0015 § Nav consolidation. Section-rail migration: `<section-nav-ui …>` → `<nav-ui variant="section" …>`.
|
|
1088
|
+
- **Reaching for the `[collapsible]` attribute on `<nav-group-ui>` inside a section rail.** Section-variant groups render as static kicker labels by default; the cascade overrides the `[collapsible="true"]` default. If you really need an interactive collapse inside a section rail, set `[variant=""]` on the group to opt out of the cascade and re-enable primary-style collapse.
|
|
1089
|
+
- **Putting `<editor-shell>` outside `<admin-page-body>` when nesting under `<admin-shell>`.** Editor-shell and admin-shell are normally siblings (separate routes / surfaces), but the nested case **is** supported for editor-inside-admin pages (inline page builder, embedded canvas): wrap the editor-shell in `<admin-page-body style="flex: 1; display: flex; flex-direction: column; min-height: 0">` and set `style="flex: 1; min-height: 0"` on the editor-shell itself. Without `min-height: 0` on every ancestor in the flex chain, the inner shell collapses to zero height. Bare `<editor-shell>` directly inside `<admin-shell>` chrome (no `<admin-page-body>` wrapper) is unsupported.
|
|
1090
|
+
- **Manually styling pane resize handles** in editor-shell. `<pane-ui resizable>` owns drag; `<editor-sidebar>` wraps it and reflects `[collapsed]` / `[resizing]`. Just place the panes.
|
|
1091
|
+
|
|
1092
|
+
## Cross-references
|
|
1093
|
+
|
|
1094
|
+
- Layout primitives go inside shells: `patterns-layout.md`
|
|
1095
|
+
- Forms inside `admin-shell` main content: `patterns-forms.md`
|
|
1096
|
+
- Tables inside cards inside `admin-shell`: `patterns-data.md`
|
|
1097
|
+
- Wiring pages through A2UI: `patterns-wiring.md` (all shells participate via `<a2ui-root>` if generated)
|