@adia-ai/web-modules 0.8.36 → 0.8.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +4 -4
  3. package/agent-admin/admin-roster/admin-roster.js +1 -1
  4. package/agent-admin/admin-settings/admin-settings.js +1 -1
  5. package/agent-admin/agent-admin/agent-admin.js +1 -1
  6. package/billing/billing-overview/billing-overview.a2ui.json +281 -0
  7. package/billing/billing-overview/billing-overview.class.js +838 -0
  8. package/billing/billing-overview/billing-overview.css +223 -0
  9. package/billing/billing-overview/billing-overview.js +16 -0
  10. package/billing/billing-overview/billing-overview.yaml +441 -0
  11. package/billing/index.js +21 -0
  12. package/billing/invoice-detail/invoice-detail.a2ui.json +276 -0
  13. package/billing/invoice-detail/invoice-detail.class.js +711 -0
  14. package/billing/invoice-detail/invoice-detail.css +305 -0
  15. package/billing/invoice-detail/invoice-detail.js +16 -0
  16. package/billing/invoice-detail/invoice-detail.yaml +411 -0
  17. package/billing/invoice-history/invoice-history.a2ui.json +315 -0
  18. package/billing/invoice-history/invoice-history.class.js +672 -0
  19. package/billing/invoice-history/invoice-history.css +130 -0
  20. package/billing/invoice-history/invoice-history.js +16 -0
  21. package/billing/invoice-history/invoice-history.yaml +398 -0
  22. package/billing/payment-method-form/payment-method-form.a2ui.json +310 -0
  23. package/billing/payment-method-form/payment-method-form.class.js +886 -0
  24. package/billing/payment-method-form/payment-method-form.css +149 -0
  25. package/billing/payment-method-form/payment-method-form.js +16 -0
  26. package/billing/payment-method-form/payment-method-form.yaml +430 -0
  27. package/billing/payment-method-list/payment-method-list.a2ui.json +283 -0
  28. package/billing/payment-method-list/payment-method-list.class.js +705 -0
  29. package/billing/payment-method-list/payment-method-list.css +128 -0
  30. package/billing/payment-method-list/payment-method-list.js +16 -0
  31. package/billing/payment-method-list/payment-method-list.yaml +355 -0
  32. package/billing/plan-picker/plan-picker.a2ui.json +349 -0
  33. package/billing/plan-picker/plan-picker.class.js +635 -0
  34. package/billing/plan-picker/plan-picker.css +227 -0
  35. package/billing/plan-picker/plan-picker.js +16 -0
  36. package/billing/plan-picker/plan-picker.yaml +453 -0
  37. package/chat/README.md +2 -2
  38. package/chat/chat-composer/chat-composer.js +2 -2
  39. package/chat/chat-empty/chat-empty.js +2 -2
  40. package/chat/chat-shell/chat-shell.js +6 -6
  41. package/chat/chat-sidebar/chat-sidebar.js +2 -2
  42. package/chat/chat-surfaces/chat-surfaces.js +3 -3
  43. package/chat/chat-thread/chat-thread.js +2 -2
  44. package/dashboard/dashboard-layout/dashboard-layout.a2ui.json +254 -0
  45. package/dashboard/dashboard-layout/dashboard-layout.css +190 -0
  46. package/dashboard/dashboard-layout/dashboard-layout.js +289 -0
  47. package/dashboard/dashboard-layout/dashboard-layout.yaml +374 -0
  48. package/dashboard/date-range-selector/date-range-selector.a2ui.json +236 -0
  49. package/dashboard/date-range-selector/date-range-selector.css +84 -0
  50. package/dashboard/date-range-selector/date-range-selector.js +686 -0
  51. package/dashboard/date-range-selector/date-range-selector.yaml +358 -0
  52. package/dashboard/index.js +14 -0
  53. package/dist/chat/chat-shell.min.js +1 -1
  54. package/dist/everything.min.js +121 -102
  55. package/editor/editor-canvas/editor-canvas.js +2 -2
  56. package/editor/editor-shell/editor-shell.js +2 -2
  57. package/editor/editor-sidebar/editor-sidebar.js +2 -2
  58. package/editor/editor-toolbar/editor-toolbar.js +2 -2
  59. package/feedback/confirm-dialog/confirm-dialog.a2ui.json +206 -0
  60. package/feedback/confirm-dialog/confirm-dialog.css +157 -0
  61. package/feedback/confirm-dialog/confirm-dialog.js +383 -0
  62. package/feedback/confirm-dialog/confirm-dialog.yaml +297 -0
  63. package/feedback/index.js +10 -0
  64. package/form/form-popover/form-popover.js +1 -1
  65. package/generative/index.d.ts +1 -1
  66. package/generative/index.js +2 -2
  67. package/onboarding/index.js +1 -0
  68. package/onboarding/onboarding-checklist/onboarding-checklist.a2ui.json +263 -0
  69. package/onboarding/onboarding-checklist/onboarding-checklist.css +157 -0
  70. package/onboarding/onboarding-checklist/onboarding-checklist.js +428 -0
  71. package/onboarding/onboarding-checklist/onboarding-checklist.yaml +236 -0
  72. package/package.json +99 -6
  73. package/runtime/a2ui-root/a2ui-root.js +3 -3
  74. package/runtime/gen-root/gen-root.js +5 -5
  75. package/settings/index.js +12 -0
  76. package/settings/integrations-page/integrations-page.a2ui.json +274 -0
  77. package/settings/integrations-page/integrations-page.css +177 -0
  78. package/settings/integrations-page/integrations-page.d.ts +77 -0
  79. package/settings/integrations-page/integrations-page.js +413 -0
  80. package/settings/integrations-page/integrations-page.yaml +317 -0
  81. package/settings/notification-preferences/notification-preferences.a2ui.json +245 -0
  82. package/settings/notification-preferences/notification-preferences.css +204 -0
  83. package/settings/notification-preferences/notification-preferences.js +622 -0
  84. package/settings/notification-preferences/notification-preferences.yaml +358 -0
  85. package/shell/admin-command/admin-command.js +2 -2
  86. package/shell/admin-shell/admin-shell.js +2 -2
  87. package/shell/admin-sidebar/admin-sidebar.js +2 -2
  88. package/shell/embed-shell/embed-shell.js +3 -3
  89. package/simple/simple-shell/simple-shell.js +1 -1
  90. package/theme/theme-panel/theme-panel.js +2 -2
package/chat/README.md CHANGED
@@ -12,7 +12,7 @@ streaming vs. external-drive).
12
12
 
13
13
  `wireAgentEvents(chatShell, registry, { events, statusEl? })` folds ONE
14
14
  `AsyncIterable<AgentEvent>` (`@adia-ai/agent`'s stream contract — see
15
- [`packages/agent/README.md`](../../agent/README.md#events)) into the thread.
15
+ [`packages/llm/agent/README.md`](../../llm/agent/README.md#events)) into the thread.
16
16
  It never touches session state — `reduce(session, event)` is the caller's
17
17
  job, the same event object just also flows through this function to paint.
18
18
 
@@ -34,7 +34,7 @@ job, the same event object just also flows through this function to paint.
34
34
  | `done` | `reasoning.finish()` (or `.fail('Blocked by guardrail')` when `stopReason === 'guardrail_denied'`) + `stopStreaming()`. |
35
35
 
36
36
  **Why `<agent-reasoning-ui>`, not a new primitive:** the primitive audit
37
- (adia-author §0) found `agent-reasoning-ui` already built for exactly this
37
+ (primitive-authoring §0) found `agent-reasoning-ui` already built for exactly this
38
38
  shape — "Agent inner monologue + pipeline viewer with steps, thoughts,
39
39
  plans, and iterations," composing `<timeline-ui>` for step rows with a
40
40
  collapsible per-step `outcomes` list (the "collapsible tool-result body"
@@ -32,8 +32,8 @@
32
32
  * backwards compat.
33
33
  */
34
34
 
35
- import { defineIfFree } from '../../../web-components/core/register.js';
36
- import { UIElement } from '../../../web-components/core/element.js';
35
+ import { defineIfFree } from '@adia-ai/web-components/core/register';
36
+ import { UIElement } from '@adia-ai/web-components/core/element';
37
37
 
38
38
  class ChatComposer extends UIElement {
39
39
  static properties = {
@@ -21,8 +21,8 @@
21
21
  * consumer surfaces (patient-visit), so it gets the class.
22
22
  */
23
23
 
24
- import { defineIfFree } from '../../../web-components/core/register.js';
25
- import { UIElement } from '../../../web-components/core/element.js';
24
+ import { defineIfFree } from '@adia-ai/web-components/core/register';
25
+ import { UIElement } from '@adia-ai/web-components/core/element';
26
26
 
27
27
  class ChatEmpty extends UIElement {
28
28
  static properties = {};
@@ -1,13 +1,13 @@
1
- import { defineIfFree } from '../../../web-components/core/register.js';
2
- import { UIElement } from '../../../web-components/core/element.js';
3
- import { renderMarkdown } from '../../../web-components/core/markdown.js';
4
- // LAZY llm import (gh#541): packages/llm/index.js is a BUILD ARTIFACT
1
+ import { defineIfFree } from '@adia-ai/web-components/core/register';
2
+ import { UIElement } from '@adia-ai/web-components/core/element';
3
+ import { renderMarkdown } from '@adia-ai/web-components/core/markdown';
4
+ // LAZY llm import (gh#541): packages/llm/core/index.js is a BUILD ARTIFACT
5
5
  // (gitignored; TS source only in a fresh checkout). A static import here
6
6
  // rides the everything-barrel, so a missing artifact 500'd EVERY /site
7
7
  // route with a full-viewport vite-error-overlay — the freeze-shaped dev
8
8
  // failure. Deferred to first send: a missing build degrades chat-shell's
9
9
  // LLM wiring with an in-thread error instead of killing the site.
10
- import '../../../web-components/components/code/code.js';
10
+ import '@adia-ai/web-components/components/code';
11
11
 
12
12
  function escapeHTML(s) {
13
13
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -309,7 +309,7 @@ class ChatShell extends UIElement {
309
309
  } catch { /* try next */ }
310
310
  }
311
311
  if (!streamChat) {
312
- this.appendMessage({ role: 'error', content: '@adia-ai/llm is not available — in a fresh checkout run the packages/llm build (see gh#527/gh#541).' });
312
+ this.appendMessage({ role: 'error', content: '@adia-ai/llm is not available — in a fresh checkout run the packages/llm/core build (see gh#527/gh#541).' });
313
313
  this.#emit('error', { error: new Error('llm build artifact missing') });
314
314
  return;
315
315
  }
@@ -37,8 +37,8 @@
37
37
  * code should prefer <chat-sidebar>.
38
38
  */
39
39
 
40
- import { defineIfFree } from '../../../web-components/core/register.js';
41
- import { UIElement } from '../../../web-components/core/element.js';
40
+ import { defineIfFree } from '@adia-ai/web-components/core/register';
41
+ import { UIElement } from '@adia-ai/web-components/core/element';
42
42
 
43
43
  const SNAP_THRESHOLD = 96;
44
44
  const SNAP_MIN_USABLE = 160;
@@ -29,7 +29,7 @@
29
29
  * (unlike `@genui/*`), so `wireAgentEvents` imports `<agent-reasoning-ui>`
30
30
  * directly (gh#667) — the "agent inner monologue + pipeline viewer with
31
31
  * steps, thoughts, plans, and iterations" primitive already built for
32
- * exactly this shape (primitive audit, adia-author §0 — no new primitive
32
+ * exactly this shape (primitive audit, primitive-authoring §0 — no new primitive
33
33
  * was minted). One reasoning viewer is lazily mounted per assistant turn
34
34
  * and renders `thinking` (→ `addThought`), `tool_use`/`tool_input_delta`/
35
35
  * `tool_result` (→ `addStep`/`updateStep`/`completeStep`/`failStep`, with
@@ -39,7 +39,7 @@
39
39
  * render-silent (trace is devtools' surface, not the thread's — see the
40
40
  * event → rendering table in `packages/web-modules/chat/README.md`).
41
41
  */
42
- import '../../../web-components/components/agent-reasoning/agent-reasoning.js';
42
+ import '@adia-ai/web-components/components/agent-reasoning';
43
43
 
44
44
  const CLOSED_ANNOTATION_TEXT = 'Closed.';
45
45
  const PROTOCOL_VERSION = 'v1.0';
@@ -186,7 +186,7 @@ export const PROGRESS_LABELS = {
186
186
  * union already carries `workflow`/`step`/`data`), so no closed-vocabulary
187
187
  * `type` amendment is needed to carry this: `workflow: 'plan'` is just a
188
188
  * new value inside an already-open string field, the same way
189
- * `packages/agent/src/workflow.ts`'s own step events use `workflow` for a
189
+ * `packages/llm/agent/src/workflow.ts`'s own step events use `workflow` for a
190
190
  * different named workflow.
191
191
  *
192
192
  * Code-owned text (CHAT-HARNESS law 3's spirit, applied to `step` the way
@@ -29,8 +29,8 @@
29
29
  * markdown + escape rules); the thread just owns the scroll surface.
30
30
  */
31
31
 
32
- import { defineIfFree } from '../../../web-components/core/register.js';
33
- import { UIElement } from '../../../web-components/core/element.js';
32
+ import { defineIfFree } from '@adia-ai/web-components/core/register';
33
+ import { UIElement } from '@adia-ai/web-components/core/element';
34
34
 
35
35
  const SCROLL_BOTTOM_TOLERANCE = 40; // px from bottom that counts as "at bottom"
36
36
 
@@ -0,0 +1,254 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/DashboardLayout.json",
4
+ "title": "DashboardLayout",
5
+ "description": "Module-tier dashboard-body composite. Crystallizes the recurring\n\"toolbar + KPI grid + chart area + table\" dashboard layout into a\nsingle primitive with four named regions (toolbar / kpis / charts /\ntable) plus an optional aside column. Each region is filled by\nconsumer-supplied <card-ui>s; the composite owns band sizing,\nresponsive breakpoints, and a [density] knob that propagates to\nevery descendant primitive via the parametric --a-density cascade.\n\nSits inside an <admin-page-body>; replaces the hand-rolled\n<col-ui gap=\"4\"> + nested <grid-ui> + <card-ui> chains every\nanalytics consumer reinvents today. Per ADR-0033 (Light-DOM\nsubstrate) — slot= attributes are decorative metadata; CSS rules\nmatch `:scope > [slot=\"X\"]` to position bands in DOM order.\n\nSpec: .claude/docs/specs/implementation-ready/SPEC-018-dashboard-layout.md.\n",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "chart-split": {
17
+ "description": "Chart-band column split. Empty string = single full-width chart;\n`2` = two equal columns; `2:1` / `3:2` / `3:1` = asymmetric\nratios. Collapses to single column at narrow widths (≤48em).\n",
18
+ "type": "string",
19
+ "enum": [
20
+ "",
21
+ "2",
22
+ "2:1",
23
+ "3:2",
24
+ "3:1"
25
+ ],
26
+ "default": ""
27
+ },
28
+ "component": {
29
+ "const": "DashboardLayout"
30
+ },
31
+ "density": {
32
+ "description": "Spacing scale — propagates --a-density to descendant primitives\nvia CSS custom-property cascade. Empty string inherits whatever\ndensity is in effect on an ancestor (the default — respects an\napp-level density theme). `compact` / `comfortable` / `spacious`\nexplicitly override.\n",
33
+ "type": "string",
34
+ "enum": [
35
+ "",
36
+ "comfortable",
37
+ "compact",
38
+ "spacious"
39
+ ],
40
+ "default": ""
41
+ },
42
+ "gap": {
43
+ "description": "Override band-to-band vertical gap. Empty string = the\ndensity-modulated default (--a-space-4 at comfortable). Numeric\n`1`..`6` selects the corresponding --a-space-N step.\n",
44
+ "type": "string",
45
+ "default": ""
46
+ },
47
+ "kpi-columns": {
48
+ "description": "Target column count for the KPI band at desktop width. Range 2-6;\nout-of-range values fall back to 4. Container-query collapse\nreduces this at narrower widths (≤48em → 2; ≤32em → 1).\n",
49
+ "type": "string",
50
+ "enum": [
51
+ "2",
52
+ "3",
53
+ "4",
54
+ "5",
55
+ "6"
56
+ ],
57
+ "default": "4"
58
+ },
59
+ "loading": {
60
+ "description": "Marks the composite as data-loading. Sets aria-busy=true on the\nhost and dims slotted children to 60% opacity with pointer-events\nsuppressed. Skeletons inside slotted cards continue to render\nnormally; the dim is on top of them.\n",
61
+ "type": "boolean",
62
+ "default": false
63
+ }
64
+ },
65
+ "required": [
66
+ "component"
67
+ ],
68
+ "unevaluatedProperties": false,
69
+ "x-adiaui": {
70
+ "anti_patterns": [
71
+ {
72
+ "description": "Child without a [slot] attribute. Lands in the implicit default\nregion and breaks the band layout. Every direct child MUST\ndeclare its slot — toolbar / kpis / charts / table / aside.\n",
73
+ "right": "<dashboard-layout-ui>\n <card-ui slot=\"kpis\">...</card-ui>\n</dashboard-layout-ui>\n",
74
+ "rule": "Every direct child of dashboard-layout-ui declares its slot.\n",
75
+ "wrong": "<dashboard-layout-ui>\n <card-ui><!-- no slot -->...</card-ui>\n</dashboard-layout-ui>\n"
76
+ },
77
+ {
78
+ "description": "Nested DashboardLayout. Two density containers fight over the\n--a-density cascade. The inner one re-creates a 2-column layout\ninside a charts band, which is incoherent.\n",
79
+ "right": "<tabs-ui>\n <tab-panel-ui><dashboard-layout-ui>...</dashboard-layout-ui></tab-panel-ui>\n <tab-panel-ui><dashboard-layout-ui>...</dashboard-layout-ui></tab-panel-ui>\n</tabs-ui>\n",
80
+ "rule": "For nested dashboards, use sibling DashboardLayouts switched by\nTabs — not DashboardLayout inside DashboardLayout.\n",
81
+ "wrong": "<dashboard-layout-ui>\n <dashboard-layout-ui slot=\"charts\">...</dashboard-layout-ui>\n</dashboard-layout-ui>\n"
82
+ },
83
+ {
84
+ "description": "kpi-columns out of range. Eight cards in a row is too narrow\nto be legible at typical container widths; the container-query\ncollapse (≤48em → 2 cols, ≤32em → 1 col) won't help when N is\nalready that large at desktop.\n",
85
+ "right": "<dashboard-layout-ui kpi-columns=\"4\">...</dashboard-layout-ui>\n",
86
+ "rule": "kpi-columns ∈ {2,3,4,5,6}. Split into 2 rows of 4 by composing\n2 DashboardLayouts vertically if 8 cards are truly necessary.\n",
87
+ "wrong": "<dashboard-layout-ui kpi-columns=\"8\">...</dashboard-layout-ui>\n"
88
+ }
89
+ ],
90
+ "category": "layout",
91
+ "composes": [
92
+ "card-ui",
93
+ "grid-ui",
94
+ "row-ui",
95
+ "stat-ui",
96
+ "chart-ui",
97
+ "table-ui"
98
+ ],
99
+ "events": {
100
+ "density-change": {
101
+ "description": "Fired when [density] changes (programmatic or via setDensity()).\nBubbles; detail.density carries the new value.\n",
102
+ "detail": {
103
+ "density": {
104
+ "description": "The new density value (comfortable / compact / spacious).",
105
+ "type": "string"
106
+ }
107
+ }
108
+ },
109
+ "region-empty": {
110
+ "description": "Fired when a slot transitions from having content to being empty\n(consumer-driven removal of the last child for that slot).\nUseful for parent-level adjust-layout logic.\n",
111
+ "detail": {
112
+ "region": {
113
+ "description": "The slot name that became empty (toolbar / kpis / charts / table / aside).",
114
+ "type": "string"
115
+ }
116
+ }
117
+ }
118
+ },
119
+ "examples": [
120
+ {
121
+ "description": "KPI-strip + 2:1 chart split + table band inside an admin page.\n",
122
+ "a2ui": "[\n {\n \"id\": \"dash-1\",\n \"component\": \"DashboardLayout\",\n \"density\": \"\",\n \"kpiColumns\": \"4\",\n \"chartSplit\": \"2:1\",\n \"children\": [\"dash-toolbar\", \"dash-kpis\", \"dash-charts\", \"dash-table\"]\n },\n {\n \"id\": \"dash-toolbar\",\n \"component\": \"Row\",\n \"slot\": \"toolbar\"\n },\n {\n \"id\": \"dash-kpis\",\n \"component\": \"Group\",\n \"slot\": \"kpis\"\n },\n {\n \"id\": \"dash-charts\",\n \"component\": \"Group\",\n \"slot\": \"charts\"\n },\n {\n \"id\": \"dash-table\",\n \"component\": \"Card\",\n \"slot\": \"table\"\n }\n]\n",
123
+ "name": "canonical-analytics-dashboard"
124
+ },
125
+ {
126
+ "description": "Six-KPI dashboard at compact density (power-user view).\n",
127
+ "a2ui": "[\n {\n \"id\": \"dash-2\",\n \"component\": \"DashboardLayout\",\n \"density\": \"compact\",\n \"kpiColumns\": \"6\"\n }\n]\n",
128
+ "name": "compact-six-kpi"
129
+ },
130
+ {
131
+ "description": "Dashboard with a sticky aside column carrying a live activity feed.\n",
132
+ "a2ui": "[\n {\n \"id\": \"dash-3\",\n \"component\": \"DashboardLayout\",\n \"chartSplit\": \"2\",\n \"children\": [\"d3-aside\"]\n },\n {\n \"id\": \"d3-aside\",\n \"component\": \"Card\",\n \"slot\": \"aside\"\n }\n]\n",
133
+ "name": "with-aside-feed"
134
+ }
135
+ ],
136
+ "keywords": [
137
+ "dashboard",
138
+ "dashboard-layout",
139
+ "analytics",
140
+ "kpi-grid",
141
+ "chart-area",
142
+ "dashboard-body",
143
+ "admin-dashboard",
144
+ "reporting",
145
+ "metrics-page",
146
+ "layout-container"
147
+ ],
148
+ "name": "UIDashboardLayout",
149
+ "related": [
150
+ "AdminPageBody",
151
+ "AdminPage",
152
+ "Card",
153
+ "Grid",
154
+ "Stat",
155
+ "Chart",
156
+ "Table",
157
+ "DateRangeSelector"
158
+ ],
159
+ "slots": {
160
+ "aside": {
161
+ "description": "Optional right-side panel (e.g., live activity feed). When set,\npromotes the host to a 2-column grid with the aside as a sticky\nright column on desktop. Collapses when empty.\n"
162
+ },
163
+ "charts": {
164
+ "description": "Chart band — wrapper containing 1-3 <card-ui> with <chart-ui>.\n[chart-split] drives the grid columns. Collapses when empty.\n"
165
+ },
166
+ "kpis": {
167
+ "description": "KPI band — typically a wrapper element containing N <card-ui>\ninstances each with one <stat-ui>. The composite drives the\ngrid template via [kpi-columns]. Collapses when empty.\n"
168
+ },
169
+ "table": {
170
+ "description": "Table band — typically a single <card-ui slot=\"table\"> containing\n<table-ui> and optional <pagination-ui> in the footer. Full-bleed\nacross the main column. Collapses when empty.\n"
171
+ },
172
+ "toolbar": {
173
+ "description": "Top band — filter chips, segmented date range, global controls.\nTypically a single <row-ui> or <date-range-selector-ui> (SPEC-019).\nCollapses when empty (no row reserved).\n"
174
+ }
175
+ },
176
+ "states": [
177
+ {
178
+ "description": "Default; all regions interactive.",
179
+ "name": "idle"
180
+ },
181
+ {
182
+ "description": "Data fetch in progress; aria-busy=true; content dimmed.",
183
+ "attribute": "loading",
184
+ "name": "loading"
185
+ },
186
+ {
187
+ "description": "Compact-density variant; tighter band gaps + density cascade.",
188
+ "attribute": "density",
189
+ "name": "compact"
190
+ },
191
+ {
192
+ "description": "Spacious-density variant; looser band gaps + density cascade.",
193
+ "attribute": "density",
194
+ "name": "spacious"
195
+ }
196
+ ],
197
+ "status": "experimental",
198
+ "synonyms": {
199
+ "charts": [
200
+ "chart-area",
201
+ "viz",
202
+ "graphs"
203
+ ],
204
+ "dashboard": [
205
+ "analytics",
206
+ "report",
207
+ "overview",
208
+ "metrics"
209
+ ],
210
+ "kpi": [
211
+ "metric",
212
+ "stat",
213
+ "measure"
214
+ ],
215
+ "layout": [
216
+ "container",
217
+ "grid",
218
+ "frame",
219
+ "shell"
220
+ ],
221
+ "tags": [
222
+ "DashboardLayout",
223
+ "Dashboard",
224
+ "AnalyticsDashboard",
225
+ "MetricsDashboard"
226
+ ]
227
+ },
228
+ "tag": "dashboard-layout-ui",
229
+ "tokens": {
230
+ "--dashboard-layout-aside-width": {
231
+ "description": "Aside column width when [slot=\"aside\"] is present.",
232
+ "default": "20em"
233
+ },
234
+ "--dashboard-layout-chart-gap": {
235
+ "description": "Inter-card gap inside the chart band.",
236
+ "default": "var(--a-space-4)"
237
+ },
238
+ "--dashboard-layout-gap": {
239
+ "description": "Band-to-band vertical gap.",
240
+ "default": "var(--a-space-4)"
241
+ },
242
+ "--dashboard-layout-kpi-gap": {
243
+ "description": "Inter-card gap inside the KPI band.",
244
+ "default": "var(--a-space-2)"
245
+ },
246
+ "--dashboard-layout-padding": {
247
+ "description": "Outer padding around the composite.",
248
+ "default": "0"
249
+ }
250
+ },
251
+ "traits": [],
252
+ "version": 1
253
+ }
254
+ }
@@ -0,0 +1,190 @@
1
+ /* ═══════════════════════════════════════════════════════════════
2
+ <dashboard-layout-ui> — Module-tier dashboard-body composite.
3
+
4
+ Owns the four canonical dashboard bands (toolbar / kpis /
5
+ charts / table) plus an optional aside column. Children declare
6
+ their band via [slot="X"]; CSS rules position by tag + ancestor
7
+ + DOM order (per ADR-0033 Light-DOM substrate).
8
+
9
+ The composite is a CSS-grid container with named grid-areas;
10
+ slotted children are placed via `grid-area`. The KPI and chart
11
+ bands internally drive their own grid via [kpi-columns] and
12
+ [chart-split] attributes. Density variants override token values
13
+ only — never `display`, `position`, layout shape.
14
+
15
+ Two-block @scope pattern per docs/specs/component-token-contract.md.
16
+ SPEC: docs/specs/implementation-ready/SPEC-018-dashboard-layout.md.
17
+ Zero raw color values (repo hard rule); zero raw px ≥ 3 (rem only).
18
+ ═══════════════════════════════════════════════════════════════ */
19
+
20
+ /* ── Block 1: TOKEN DECLARATIONS ─────────────────────────────── */
21
+
22
+ dashboard-layout-ui {
23
+ --dashboard-layout-gap: var(--a-space-4);
24
+ --dashboard-layout-kpi-gap: var(--a-space-2);
25
+ --dashboard-layout-chart-gap: var(--a-space-4);
26
+ --dashboard-layout-aside-width: 20em;
27
+ --dashboard-layout-padding: 0;
28
+
29
+ /* Loading dim — shared opacity escalation. */
30
+ --dashboard-layout-loading-opacity: var(--a-opacity-disabled, 0.6);
31
+
32
+ /* Density-modulated gap overrides — applied at the [density="…"]
33
+ selectors below. Authors can override any of these via the
34
+ un-suffixed public token (--dashboard-layout-gap etc.). */
35
+ --dashboard-layout-gap-compact: var(--a-space-3);
36
+ --dashboard-layout-kpi-gap-compact: var(--a-space-2);
37
+ --dashboard-layout-gap-spacious: var(--a-space-5);
38
+ --dashboard-layout-kpi-gap-spacious: var(--a-space-3);
39
+ }
40
+
41
+ /* ── Block 2: SCOPED LAYOUT ──────────────────────────────────── */
42
+
43
+ @scope (dashboard-layout-ui) {
44
+
45
+ :scope {
46
+ display: grid;
47
+ grid-template-columns: 1fr;
48
+ grid-auto-rows: auto;
49
+ grid-template-areas:
50
+ "toolbar"
51
+ "kpis"
52
+ "charts"
53
+ "table";
54
+ gap: var(--dashboard-layout-gap);
55
+ padding: var(--dashboard-layout-padding);
56
+ /* Provide the container-query substrate so descendant primitives
57
+ can size against the dashboard container width. Per MEMORY
58
+ "Size-query provider pattern" — the composite is the
59
+ acknowledged provider for its descendants. */
60
+ container-type: inline-size;
61
+ container-name: dashboard-layout;
62
+ }
63
+
64
+ /* ── Region → grid-area mapping ──
65
+ Children carry [slot="X"]; CSS places them into the correct band
66
+ by tag + ancestor + DOM order. */
67
+ :scope > [slot="toolbar"] { grid-area: toolbar; }
68
+ :scope > [slot="kpis"] { grid-area: kpis; }
69
+ :scope > [slot="charts"] { grid-area: charts; }
70
+ :scope > [slot="table"] { grid-area: table; }
71
+ :scope > [slot="aside"] { grid-area: aside; }
72
+
73
+ /* ── KPI band — inner grid driven by [kpi-columns] ──
74
+ Default 4 columns; per-N selectors below override. Each column is
75
+ `1fr` — equal share of available width. Container queries below
76
+ (lines @container) collapse to fewer columns at narrow widths;
77
+ no per-column min-width floor (was minmax(16em, 1fr) — let
78
+ breakpoints manage responsive behavior, not minmax). */
79
+ :scope > [slot="kpis"] {
80
+ display: grid;
81
+ grid-template-columns: repeat(var(--dashboard-layout-kpi-cols, 4), 1fr);
82
+ gap: var(--dashboard-layout-kpi-gap);
83
+ }
84
+ :scope[kpi-columns="2"] > [slot="kpis"] { --dashboard-layout-kpi-cols: 2; }
85
+ :scope[kpi-columns="3"] > [slot="kpis"] { --dashboard-layout-kpi-cols: 3; }
86
+ :scope[kpi-columns="4"] > [slot="kpis"] { --dashboard-layout-kpi-cols: 4; }
87
+ :scope[kpi-columns="5"] > [slot="kpis"] { --dashboard-layout-kpi-cols: 5; }
88
+ :scope[kpi-columns="6"] > [slot="kpis"] { --dashboard-layout-kpi-cols: 6; }
89
+
90
+ /* ── Chart band — inner grid driven by [chart-split] ──
91
+ Empty / missing = single full-width chart. */
92
+ :scope > [slot="charts"] {
93
+ display: grid;
94
+ grid-template-columns: 1fr;
95
+ gap: var(--dashboard-layout-chart-gap);
96
+ }
97
+ :scope[chart-split="2"] > [slot="charts"] { grid-template-columns: 1fr 1fr; }
98
+ :scope[chart-split="2:1"] > [slot="charts"] { grid-template-columns: 2fr 1fr; }
99
+ :scope[chart-split="3:2"] > [slot="charts"] { grid-template-columns: 3fr 2fr; }
100
+ :scope[chart-split="3:1"] > [slot="charts"] { grid-template-columns: 3fr 1fr; }
101
+
102
+ /* ── Aside column — promotes the outer grid to 2 columns ──
103
+ When an [slot="aside"] child is present, the host becomes a
104
+ 2-column grid with all bands stacked in the main column and
105
+ the aside spanning all rows on the right. */
106
+ :scope:has(> [slot="aside"]) {
107
+ grid-template-columns: 1fr var(--dashboard-layout-aside-width);
108
+ grid-template-areas:
109
+ "toolbar toolbar"
110
+ "kpis aside"
111
+ "charts aside"
112
+ "table aside";
113
+ }
114
+ :scope > [slot="aside"] {
115
+ align-self: start;
116
+ position: sticky;
117
+ top: var(--a-space-4);
118
+ }
119
+
120
+ /* ── Density variants — override TOKEN values only ──
121
+ Per the contract: variants change tokens; modes change layout.
122
+ Density is a token-override, not a layout-shape change. */
123
+ :scope[density="comfortable"] {
124
+ --a-density: 1;
125
+ }
126
+ :scope[density="compact"] {
127
+ --dashboard-layout-gap: var(--dashboard-layout-gap-compact);
128
+ --dashboard-layout-kpi-gap: var(--dashboard-layout-kpi-gap-compact);
129
+ --a-density: 0.85;
130
+ }
131
+ :scope[density="spacious"] {
132
+ --dashboard-layout-gap: var(--dashboard-layout-gap-spacious);
133
+ --dashboard-layout-kpi-gap: var(--dashboard-layout-kpi-gap-spacious);
134
+ --a-density: 1.15;
135
+ }
136
+
137
+ /* ── Loading state — dim slotted regions ──
138
+ The composite itself stays interactive (e.g., consumer wants to
139
+ swap density mid-fetch); only the band content dims + becomes
140
+ pointer-inert. JS sets aria-busy on the host. */
141
+ :scope[loading] > [slot] {
142
+ opacity: var(--dashboard-layout-loading-opacity);
143
+ pointer-events: none;
144
+ }
145
+
146
+ /* ── Container-query collapse ──
147
+ At narrow widths the KPI grid collapses regardless of
148
+ [kpi-columns]; the chart split collapses to single column.
149
+
150
+ The selectors mirror the [kpi-columns="N"] / [chart-split="X"]
151
+ scope rules above so they tie on specificity ([0,2,0]) and win
152
+ by source order (last-declared, same specificity, in @scope).
153
+ Without [kpi-columns="N"] in the selector, the higher-specificity
154
+ scope rule would override the container-query token write. */
155
+ /* Graduated collapse (bug-14): the old single <48em→2 / <32em→1 was too
156
+ eager — it dropped a 3-col KPI band to 2 while ~552px still fits 3 cards
157
+ comfortably. Now the dense bands (5/6) thin first, the authored count
158
+ holds until cards approach a ~9rem floor, then steps down. */
159
+ @container dashboard-layout (inline-size < 44em) {
160
+ :scope[kpi-columns="5"] > [slot="kpis"],
161
+ :scope[kpi-columns="6"] > [slot="kpis"] {
162
+ --dashboard-layout-kpi-cols: 3;
163
+ }
164
+ }
165
+ @container dashboard-layout (inline-size < 40em) {
166
+ :scope[chart-split="2"] > [slot="charts"],
167
+ :scope[chart-split="2:1"] > [slot="charts"],
168
+ :scope[chart-split="3:2"] > [slot="charts"],
169
+ :scope[chart-split="3:1"] > [slot="charts"] {
170
+ grid-template-columns: 1fr;
171
+ }
172
+ }
173
+ @container dashboard-layout (inline-size < 32em) {
174
+ :scope[kpi-columns="3"] > [slot="kpis"],
175
+ :scope[kpi-columns="4"] > [slot="kpis"],
176
+ :scope[kpi-columns="5"] > [slot="kpis"],
177
+ :scope[kpi-columns="6"] > [slot="kpis"] {
178
+ --dashboard-layout-kpi-cols: 2;
179
+ }
180
+ }
181
+ @container dashboard-layout (inline-size < 20em) {
182
+ :scope[kpi-columns="2"] > [slot="kpis"],
183
+ :scope[kpi-columns="3"] > [slot="kpis"],
184
+ :scope[kpi-columns="4"] > [slot="kpis"],
185
+ :scope[kpi-columns="5"] > [slot="kpis"],
186
+ :scope[kpi-columns="6"] > [slot="kpis"] {
187
+ --dashboard-layout-kpi-cols: 1;
188
+ }
189
+ }
190
+ }