@adia-ai/web-modules 0.3.4 → 0.3.6

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 (57) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/chat/chat-composer/chat-composer.a2ui.json +94 -0
  3. package/chat/chat-composer/chat-composer.examples.html +28 -0
  4. package/chat/chat-composer/chat-composer.html +43 -0
  5. package/chat/chat-composer/chat-composer.js +107 -0
  6. package/chat/chat-composer/chat-composer.test.js +112 -0
  7. package/chat/chat-composer/chat-composer.yaml +91 -0
  8. package/chat/chat-empty/chat-empty.a2ui.json +68 -0
  9. package/chat/chat-empty/chat-empty.examples.html +34 -0
  10. package/chat/chat-empty/chat-empty.html +42 -0
  11. package/chat/chat-empty/chat-empty.yaml +58 -0
  12. package/chat/chat-header/chat-header.a2ui.json +77 -0
  13. package/chat/chat-header/chat-header.examples.html +30 -0
  14. package/chat/chat-header/chat-header.html +42 -0
  15. package/chat/chat-header/chat-header.yaml +68 -0
  16. package/chat/chat-shell/chat-shell.css +1 -0
  17. package/chat/chat-shell/chat-shell.examples.html +43 -2
  18. package/chat/chat-shell/chat-shell.js +35 -7
  19. package/chat/chat-shell/css/chat-shell.bespoke.css +196 -0
  20. package/chat/chat-sidebar/chat-sidebar.a2ui.json +136 -0
  21. package/chat/chat-sidebar/chat-sidebar.examples.html +36 -0
  22. package/chat/chat-sidebar/chat-sidebar.html +43 -0
  23. package/chat/chat-sidebar/chat-sidebar.js +227 -0
  24. package/chat/chat-sidebar/chat-sidebar.test.js +110 -0
  25. package/chat/chat-sidebar/chat-sidebar.yaml +140 -0
  26. package/chat/chat-status/chat-status.a2ui.json +63 -0
  27. package/chat/chat-status/chat-status.examples.html +29 -0
  28. package/chat/chat-status/chat-status.html +42 -0
  29. package/chat/chat-status/chat-status.yaml +52 -0
  30. package/chat/chat-thread/chat-thread.a2ui.json +91 -0
  31. package/chat/chat-thread/chat-thread.examples.html +36 -0
  32. package/chat/chat-thread/chat-thread.html +43 -0
  33. package/chat/chat-thread/chat-thread.js +106 -0
  34. package/chat/chat-thread/chat-thread.test.js +82 -0
  35. package/chat/chat-thread/chat-thread.yaml +89 -0
  36. package/chat/index.js +3 -0
  37. package/editor/editor-canvas/editor-canvas.a2ui.json +87 -0
  38. package/editor/editor-canvas/editor-canvas.js +103 -0
  39. package/editor/editor-canvas/editor-canvas.test.js +100 -0
  40. package/editor/editor-canvas/editor-canvas.yaml +88 -0
  41. package/editor/editor-canvas-empty/editor-canvas-empty.a2ui.json +69 -0
  42. package/editor/editor-canvas-empty/editor-canvas-empty.yaml +56 -0
  43. package/editor/editor-shell/css/editor-shell.bespoke.css +172 -0
  44. package/editor/editor-shell/editor-shell.css +1 -0
  45. package/editor/editor-shell/editor-shell.js +85 -30
  46. package/editor/editor-sidebar/editor-sidebar.a2ui.json +88 -0
  47. package/editor/editor-sidebar/editor-sidebar.js +173 -0
  48. package/editor/editor-sidebar/editor-sidebar.test.js +126 -0
  49. package/editor/editor-sidebar/editor-sidebar.yaml +83 -0
  50. package/editor/editor-statusbar/editor-statusbar.a2ui.json +76 -0
  51. package/editor/editor-statusbar/editor-statusbar.yaml +57 -0
  52. package/editor/editor-toolbar/editor-toolbar.a2ui.json +96 -0
  53. package/editor/editor-toolbar/editor-toolbar.js +58 -0
  54. package/editor/editor-toolbar/editor-toolbar.test.js +99 -0
  55. package/editor/editor-toolbar/editor-toolbar.yaml +81 -0
  56. package/editor/index.js +3 -0
  57. package/package.json +1 -1
@@ -0,0 +1,42 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="auto">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Chat Empty — AdiaUI</title>
7
+
8
+ <link rel="stylesheet" href="../../../web-components/styles/resets.css">
9
+ <link rel="stylesheet" href="../../../web-components/styles/tokens.css">
10
+ <link rel="stylesheet" href="../chat-shell/chat-shell.css">
11
+ <link rel="stylesheet" href="../../../web-components/components/code/code.css">
12
+ <link rel="stylesheet" href="../../../web-components/components/tag/tag.css">
13
+
14
+ <script type="module" src="../chat-shell/chat-shell.js"></script>
15
+ <script type="module" src="../../../web-components/components/code/code.js"></script>
16
+ <script type="module" src="../../../web-components/components/tag/tag.js"></script>
17
+
18
+ <style>
19
+ :where(html, body) { margin: 0; min-height: 100vh; background: var(--a-bg); color: var(--a-fg); font-family: var(--a-font); }
20
+ main { max-width: 960px; margin-inline: auto; padding: var(--a-space-6) var(--a-space-5); }
21
+ </style>
22
+ </head>
23
+ <body>
24
+
25
+ <main id="demo-root">
26
+ <p>Loading examples…</p>
27
+ </main>
28
+
29
+ <script type="module">
30
+ const root = document.getElementById('demo-root');
31
+ try {
32
+ const res = await fetch('./chat-empty.examples.html');
33
+ if (!res.ok) throw new Error(`fetch failed (${res.status})`);
34
+ root.innerHTML = await res.text();
35
+ } catch (err) {
36
+ root.innerHTML = `<p style="color:var(--a-danger-strong);">Failed to load chat-empty.examples.html — ${err.message}</p>`;
37
+ console.error('[chat-empty.html]', err);
38
+ }
39
+ </script>
40
+
41
+ </body>
42
+ </html>
@@ -0,0 +1,58 @@
1
+ # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
3
+ name: ChatEmpty
4
+ tag: chat-empty
5
+ component: ChatEmpty
6
+ category: feedback
7
+ version: 1
8
+ description: |
9
+ Module-tier chat empty state. CSS-only — no behavior, no JS. Sits
10
+ as the first child of <chat-thread> as the empty state placeholder.
11
+ Visibility is driven by <chat-thread>'s [empty] reflected attribute
12
+ via CSS (no JS toggling needed) — when messages arrive, the parent
13
+ thread loses [empty] and CSS hides this stub.
14
+
15
+ Replaces the legacy <empty-state-ui data-chat-empty> child of the
16
+ messages container per ADR-0023. Both shapes still work via
17
+ backwards compat; new code should prefer <chat-empty>.
18
+
19
+ props: {}
20
+
21
+ events: {}
22
+
23
+ slots:
24
+ default:
25
+ description: >-
26
+ Empty state content — typically an icon + heading + description.
27
+ Authors compose with <empty-state-ui> as a child, or supply
28
+ inline content directly.
29
+
30
+ states:
31
+ - name: idle
32
+ description: Default, visible.
33
+ - name: hidden
34
+ description: Hidden via parent <chat-thread>:not([empty]) selector.
35
+
36
+ traits: []
37
+
38
+ a2ui:
39
+ rules:
40
+ - >-
41
+ chat-empty is the bespoke replacement for legacy
42
+ <empty-state-ui data-chat-empty>. Place as the first child of
43
+ <chat-thread>; visibility is automatic via the [empty]
44
+ reflected attribute.
45
+
46
+ keywords:
47
+ - chat-empty
48
+ - empty-state
49
+ - placeholder
50
+ - no-messages
51
+
52
+ synonyms:
53
+ chat-empty: [empty-state, placeholder, no-conversations]
54
+
55
+ related:
56
+ - ChatShell
57
+ - ChatThread
58
+ - EmptyState
@@ -0,0 +1,77 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/ChatHeader.json",
4
+ "title": "ChatHeader",
5
+ "description": "Module-tier chat header chrome bar. CSS-only — no behavior, no JS.\nSits at the top of <chat-shell> or inside a <chat-sidebar>. Holds\nthe chat name, status indicator, and action clusters via slot\nvocabulary.\n\nReplaces the legacy <header> with [data-chat-name] / [data-chat-status]\ndata-attribute children. Both shapes still work per ADR-0023 backwards\ncompat; new code should prefer <chat-header> with <chat-status> as\na slotted child.\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
+ "component": {
17
+ "const": "ChatHeader"
18
+ }
19
+ },
20
+ "required": [
21
+ "component"
22
+ ],
23
+ "unevaluatedProperties": false,
24
+ "x-adiaui": {
25
+ "anti_patterns": [],
26
+ "category": "layout",
27
+ "events": {},
28
+ "examples": [],
29
+ "keywords": [
30
+ "chat-header",
31
+ "chat-titlebar",
32
+ "chrome-bar",
33
+ "app-header"
34
+ ],
35
+ "name": "ChatHeader",
36
+ "related": [
37
+ "ChatShell",
38
+ "ChatStatus",
39
+ "ChatThread",
40
+ "ChatComposer"
41
+ ],
42
+ "slots": {
43
+ "default": {
44
+ "description": "Default — typically [slot=\"name\"] + [slot=\"status\"] children, or ad-hoc inline content."
45
+ },
46
+ "action": {
47
+ "description": "Trailing control cluster (settings, share, clear conversation)."
48
+ },
49
+ "action-leading": {
50
+ "description": "Leading control cluster (back button, conversation switcher)."
51
+ },
52
+ "name": {
53
+ "description": "Primary chat label (model name, conversation title, etc.)."
54
+ },
55
+ "status": {
56
+ "description": "Status indicator — typically <chat-status> showing connection / streaming state."
57
+ }
58
+ },
59
+ "states": [
60
+ {
61
+ "description": "Default, the only state.",
62
+ "name": "idle"
63
+ }
64
+ ],
65
+ "synonyms": {
66
+ "chat-header": [
67
+ "titlebar",
68
+ "app-header",
69
+ "navbar"
70
+ ]
71
+ },
72
+ "tag": "chat-header",
73
+ "tokens": {},
74
+ "traits": [],
75
+ "version": 1
76
+ }
77
+ }
@@ -0,0 +1,30 @@
1
+ <header>
2
+ <div>
3
+ <h1>Chat Header</h1>
4
+ <div data-actions>
5
+ <tag-ui size="sm">chat-header</tag-ui>
6
+ <tag-ui size="sm" variant="ghost">CSS-only</tag-ui>
7
+ </div>
8
+ </div>
9
+ <p>Module-tier chat top chrome bar. CSS-only. Holds chat name, status, and action clusters.</p>
10
+ </header>
11
+
12
+ <section data-section>
13
+ <h2 variant="section">Role</h2>
14
+ <p>Top chrome bar with slot vocabulary — name, status, action, action-leading clusters. Pure CSS-only stub styled by parent shell via tag-presence.</p>
15
+ </section>
16
+
17
+ <section data-section>
18
+ <h2 variant="section">Composition</h2>
19
+ <p>Typical placement inside <code>&lt;chat-shell&gt;</code>:</p>
20
+ <code-ui language="html">&lt;chat-header&gt;
21
+ &lt;span slot="name"&gt;Claude&lt;/span&gt;
22
+ &lt;chat-status slot="status"&gt;Connected&lt;/chat-status&gt;
23
+ &lt;button-ui slot="action" icon="gear" variant="ghost"&gt;&lt;/button-ui&gt;
24
+ &lt;/chat-header&gt;
25
+ </section>
26
+
27
+ <section data-section>
28
+ <h2 variant="section">Family</h2>
29
+ <p>Per <a href="../../../../.brain/adrs/0023-bespoke-shell-tier-children.md">ADR-0023</a>, the chat cluster's bespoke family — <code>&lt;chat-shell&gt;</code> (host), <code>&lt;chat-thread&gt;</code>, <code>&lt;chat-composer&gt;</code>, <code>&lt;chat-sidebar&gt;</code> (JS-bearing) + <code>&lt;chat-header&gt;</code>, <code>&lt;chat-status&gt;</code>, <code>&lt;chat-empty&gt;</code> (CSS-only). Mirrors the admin cluster's pattern.</p>
30
+ </section>
@@ -0,0 +1,42 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="auto">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Chat Header — AdiaUI</title>
7
+
8
+ <link rel="stylesheet" href="../../../web-components/styles/resets.css">
9
+ <link rel="stylesheet" href="../../../web-components/styles/tokens.css">
10
+ <link rel="stylesheet" href="../chat-shell/chat-shell.css">
11
+ <link rel="stylesheet" href="../../../web-components/components/code/code.css">
12
+ <link rel="stylesheet" href="../../../web-components/components/tag/tag.css">
13
+
14
+ <script type="module" src="../chat-shell/chat-shell.js"></script>
15
+ <script type="module" src="../../../web-components/components/code/code.js"></script>
16
+ <script type="module" src="../../../web-components/components/tag/tag.js"></script>
17
+
18
+ <style>
19
+ :where(html, body) { margin: 0; min-height: 100vh; background: var(--a-bg); color: var(--a-fg); font-family: var(--a-font); }
20
+ main { max-width: 960px; margin-inline: auto; padding: var(--a-space-6) var(--a-space-5); }
21
+ </style>
22
+ </head>
23
+ <body>
24
+
25
+ <main id="demo-root">
26
+ <p>Loading examples…</p>
27
+ </main>
28
+
29
+ <script type="module">
30
+ const root = document.getElementById('demo-root');
31
+ try {
32
+ const res = await fetch('./chat-header.examples.html');
33
+ if (!res.ok) throw new Error(`fetch failed (${res.status})`);
34
+ root.innerHTML = await res.text();
35
+ } catch (err) {
36
+ root.innerHTML = `<p style="color:var(--a-danger-strong);">Failed to load chat-header.examples.html — ${err.message}</p>`;
37
+ console.error('[chat-header.html]', err);
38
+ }
39
+ </script>
40
+
41
+ </body>
42
+ </html>
@@ -0,0 +1,68 @@
1
+ # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
3
+ name: ChatHeader
4
+ tag: chat-header
5
+ component: ChatHeader
6
+ category: layout
7
+ version: 1
8
+ description: |
9
+ Module-tier chat header chrome bar. CSS-only — no behavior, no JS.
10
+ Sits at the top of <chat-shell> or inside a <chat-sidebar>. Holds
11
+ the chat name, status indicator, and action clusters via slot
12
+ vocabulary.
13
+
14
+ Replaces the legacy <header> with [data-chat-name] / [data-chat-status]
15
+ data-attribute children. Both shapes still work per ADR-0023 backwards
16
+ compat; new code should prefer <chat-header> with <chat-status> as
17
+ a slotted child.
18
+
19
+ props: {}
20
+
21
+ events: {}
22
+
23
+ slots:
24
+ default:
25
+ description: >-
26
+ Default — typically [slot="name"] + [slot="status"] children, or
27
+ ad-hoc inline content.
28
+ name:
29
+ description: >-
30
+ Primary chat label (model name, conversation title, etc.).
31
+ status:
32
+ description: >-
33
+ Status indicator — typically <chat-status> showing connection /
34
+ streaming state.
35
+ action:
36
+ description: >-
37
+ Trailing control cluster (settings, share, clear conversation).
38
+ action-leading:
39
+ description: >-
40
+ Leading control cluster (back button, conversation switcher).
41
+
42
+ states:
43
+ - name: idle
44
+ description: Default, the only state.
45
+
46
+ traits: []
47
+
48
+ a2ui:
49
+ rules:
50
+ - >-
51
+ chat-header replaces the legacy <header> chrome bar inside
52
+ <chat-shell>. Use named slots for canonical clusters; ad-hoc
53
+ content goes in the default slot.
54
+
55
+ keywords:
56
+ - chat-header
57
+ - chat-titlebar
58
+ - chrome-bar
59
+ - app-header
60
+
61
+ synonyms:
62
+ chat-header: [titlebar, app-header, navbar]
63
+
64
+ related:
65
+ - ChatShell
66
+ - ChatStatus
67
+ - ChatThread
68
+ - ChatComposer
@@ -8,3 +8,4 @@
8
8
  @import "./css/chat-shell.streaming.css";
9
9
  @import "./css/chat-shell.markdown.css";
10
10
  @import "./css/chat-shell.empty.css";
11
+ @import "./css/chat-shell.bespoke.css";
@@ -10,8 +10,8 @@
10
10
  </header>
11
11
 
12
12
  <section data-section>
13
- <h2 variant="section">Basic shape</h2>
14
- <p data-note>Author provides the structural DOM; the shell binds the LLM streaming behavior and renders chunks into the messages container.</p>
13
+ <h2 variant="section">Basic shape (legacy)</h2>
14
+ <p data-note>Author provides the structural DOM; the shell binds the LLM streaming behavior and renders chunks into the messages container. This is the original raw-HTML authoring shape — still fully supported.</p>
15
15
  <code-ui language="html">&lt;chat-shell provider="anthropic" model="claude-sonnet-4-7" proxy-url="/api/llm"&gt;
16
16
  &lt;header&gt;
17
17
  &lt;span slot="heading"&gt;Chat&lt;/span&gt;
@@ -31,6 +31,47 @@
31
31
  &lt;/chat-shell&gt;</code-ui>
32
32
  </section>
33
33
 
34
+ <section data-section>
35
+ <h2 variant="section">Basic shape (bespoke — recommended)</h2>
36
+ <p data-note>The bespoke shape uses module-namespaced custom elements per <a href="../../../../.brain/adrs/0023-bespoke-shell-tier-children.md">ADR-0023</a>. Each child owns its own behavior + state attributes; queryable from outside via <code>:has(chat-thread[streaming])</code>.</p>
37
+ <code-ui language="html">&lt;chat-shell provider="anthropic" model="claude-sonnet-4-7" proxy-url="/api/llm"&gt;
38
+ &lt;chat-header&gt;
39
+ &lt;span slot="name"&gt;Claude&lt;/span&gt;
40
+ &lt;chat-status slot="status"&gt;Connected&lt;/chat-status&gt;
41
+ &lt;/chat-header&gt;
42
+
43
+ &lt;chat-thread&gt;
44
+ &lt;chat-empty&gt;
45
+ &lt;empty-state-ui icon="chat-circle" heading="Hello!" description="Ask me anything."&gt;&lt;/empty-state-ui&gt;
46
+ &lt;/chat-empty&gt;
47
+ &lt;/chat-thread&gt;
48
+
49
+ &lt;chat-composer&gt;
50
+ &lt;chat-input-ui placeholder="Message…" submit-on-enter&gt;&lt;/chat-input-ui&gt;
51
+ &lt;/chat-composer&gt;
52
+ &lt;/chat-shell&gt;</code-ui>
53
+ </section>
54
+
55
+ <section data-section>
56
+ <h2 variant="section">State as attribute</h2>
57
+ <p>Every queryable state is reflected on the relevant child element. Style cross-cuts via <code>:has()</code>:</p>
58
+ <code-ui language="css">/* Subtle indicator while LLM is streaming */
59
+ chat-shell:has(chat-thread[streaming]) chat-header {
60
+ /* … */
61
+ }
62
+
63
+ /* Disable composer style during streaming */
64
+ chat-shell:has(chat-composer[disabled]) {
65
+ /* … */
66
+ }
67
+
68
+ /* Empty-state visibility — driven automatically */
69
+ chat-thread:not([empty]) > chat-empty {
70
+ display: none;
71
+ }</code-ui>
72
+ <p data-note>JS reads the same attributes — <code>shell.querySelector('chat-thread').streaming</code>. The host (<code>&lt;chat-shell&gt;</code>) propagates <code>[streaming]</code> to the bespoke <code>&lt;chat-thread&gt;</code> child automatically when an LLM response is in flight.</p>
73
+ </section>
74
+
34
75
  <section data-section>
35
76
  <h2 variant="section">Properties</h2>
36
77
  <table>
@@ -79,16 +79,36 @@ class ChatShell extends UIElement {
79
79
  // ── Lifecycle ──
80
80
 
81
81
  connected() {
82
- this.#messagesEl = this.querySelector('[data-chat-messages]') || this.querySelector('section');
83
- this.#inputEl = this.querySelector('[data-chat-input]') || this.querySelector('chat-input-ui');
84
- this.#emptyEl = this.querySelector('[data-chat-empty]');
85
- this.#statusEl = this.querySelector('[data-chat-status]');
86
-
87
- this.#inputEl?.addEventListener('submit', this.#onSubmit);
82
+ // Per ADR-0023 — read BOTH legacy (data-* / raw HTML) AND bespoke
83
+ // (chat-thread / chat-composer / chat-empty / chat-status) shapes.
84
+ // The :is() selectors let consumers mix shapes without breakage.
85
+ this.#messagesEl = this.querySelector('chat-thread')
86
+ || this.querySelector('[data-chat-messages]')
87
+ || this.querySelector('section');
88
+ this.#inputEl = this.querySelector('chat-composer')
89
+ || this.querySelector('[data-chat-input]')
90
+ || this.querySelector('chat-input-ui');
91
+ this.#emptyEl = this.querySelector('chat-empty')
92
+ || this.querySelector('[data-chat-empty]');
93
+ this.#statusEl = this.querySelector('chat-status')
94
+ || this.querySelector('[data-chat-status]');
95
+
96
+ // Bespoke <chat-composer> emits 'composer-submit' instead of 'submit'
97
+ // (so the legacy 'submit' event from inside <chat-input-ui> doesn't
98
+ // double-fire). Listen for both depending on which shape is present.
99
+ if (this.#inputEl?.tagName?.toLowerCase() === 'chat-composer') {
100
+ this.#inputEl.addEventListener('composer-submit', this.#onSubmit);
101
+ } else {
102
+ this.#inputEl?.addEventListener('submit', this.#onSubmit);
103
+ }
88
104
  }
89
105
 
90
106
  disconnected() {
91
- this.#inputEl?.removeEventListener('submit', this.#onSubmit);
107
+ if (this.#inputEl?.tagName?.toLowerCase() === 'chat-composer') {
108
+ this.#inputEl.removeEventListener('composer-submit', this.#onSubmit);
109
+ } else {
110
+ this.#inputEl?.removeEventListener('submit', this.#onSubmit);
111
+ }
92
112
  this.abort();
93
113
  }
94
114
 
@@ -176,12 +196,20 @@ class ChatShell extends UIElement {
176
196
  this.streaming = true;
177
197
  if (this.#inputEl) this.#inputEl.disabled = true;
178
198
  if (this.#statusEl) this.#statusEl.textContent = 'Typing...';
199
+ // Bespoke chat-thread reflects [streaming] for CSS hooks like
200
+ // chat-shell:has(chat-thread[streaming]) { … }
201
+ if (this.#messagesEl?.tagName?.toLowerCase() === 'chat-thread') {
202
+ this.#messagesEl.streaming = true;
203
+ }
179
204
  }
180
205
 
181
206
  stopStreaming() {
182
207
  this.streaming = false;
183
208
  if (this.#inputEl) this.#inputEl.disabled = false;
184
209
  if (this.#statusEl) this.#statusEl.textContent = '';
210
+ if (this.#messagesEl?.tagName?.toLowerCase() === 'chat-thread') {
211
+ this.#messagesEl.streaming = false;
212
+ }
185
213
 
186
214
  // Remove cursor
187
215
  this.#messagesEl?.querySelector('[data-role]:last-child [data-cursor]')?.remove();
@@ -0,0 +1,196 @@
1
+ /* ═══════════════════════════════════════════════════════════════
2
+ chat-shell — Bespoke shell-tier children (Phase 2 of ADR-0023)
3
+
4
+ The chat-* CSS-only structural children are styled by SHARING the
5
+ same CSS rules as their legacy raw-HTML counterparts. This file
6
+ re-applies the existing selectors (header / section / footer) to
7
+ the new bespoke tags via display + structural mappings.
8
+
9
+ Mirrors the admin-shell.bespoke.css pattern. Keeps the chat
10
+ cluster's CSS modifications isolated in one bridge file so Phase 3
11
+ (legacy shape removal) is a single-file delete.
12
+ ═══════════════════════════════════════════════════════════════ */
13
+
14
+ /* ── chat-header — top chrome bar ── */
15
+ chat-shell > chat-header,
16
+ chat-sidebar > chat-header[slot="header"],
17
+ chat-sidebar > chat-header:first-child {
18
+ display: flex;
19
+ align-items: center;
20
+ gap: var(--chat-header-gap, var(--a-space-2));
21
+ padding: 0 var(--chat-header-px, var(--a-space-3));
22
+ height: var(--chat-header-height, var(--a-size-lg));
23
+ font-size: var(--chat-header-font, var(--a-ui-size));
24
+ border-bottom: var(--chat-border, 1px solid var(--a-border-subtle));
25
+ flex-shrink: 0;
26
+ background: var(--chat-bg, var(--a-bg));
27
+ }
28
+
29
+ /* Slot vocabulary inside chat-header */
30
+ chat-header > [slot="name"] {
31
+ font-weight: var(--a-weight-medium, 500);
32
+ color: var(--a-fg);
33
+ }
34
+
35
+ chat-header > [slot="status"] {
36
+ margin-inline-start: var(--a-space-2);
37
+ }
38
+
39
+ chat-header > [slot="action"]:first-of-type {
40
+ margin-inline-start: auto;
41
+ }
42
+
43
+ chat-header > [slot="action-leading"] {
44
+ margin-inline-end: var(--a-space-2);
45
+ }
46
+
47
+ /* ── chat-status — inline status indicator ── */
48
+ chat-status {
49
+ display: inline-flex;
50
+ align-items: center;
51
+ gap: var(--a-space-1);
52
+ font-size: var(--a-ui-sm);
53
+ color: var(--a-fg-muted);
54
+ }
55
+
56
+ /* ── chat-thread — message scroll surface ── */
57
+ chat-shell > chat-thread {
58
+ flex: 1;
59
+ min-height: 0;
60
+ overflow-y: auto;
61
+ overscroll-behavior: contain;
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: var(--chat-message-gap, var(--a-space-3));
65
+ padding: var(--chat-thread-py, var(--a-space-4)) var(--chat-thread-px, var(--a-space-4));
66
+ background: var(--chat-thread-bg, var(--a-bg));
67
+ }
68
+
69
+ chat-thread[streaming] {
70
+ /* Optional: subtle indicator while streaming. Default: no visual change. */
71
+ }
72
+
73
+ /* ── chat-empty — empty state, hidden when messages present ── */
74
+ chat-thread:not([empty]) > chat-empty {
75
+ display: none;
76
+ }
77
+
78
+ chat-thread[empty] > chat-empty {
79
+ display: flex;
80
+ flex: 1;
81
+ align-items: center;
82
+ justify-content: center;
83
+ padding: var(--a-space-6);
84
+ }
85
+
86
+ /* ── chat-composer — input wrapper ── */
87
+ chat-shell > chat-composer {
88
+ display: flex;
89
+ align-items: stretch;
90
+ gap: var(--a-space-2);
91
+ padding: var(--chat-composer-py, var(--a-space-3)) var(--chat-composer-px, var(--a-space-3));
92
+ border-top: var(--chat-border, 1px solid var(--a-border-subtle));
93
+ background: var(--chat-bg, var(--a-bg));
94
+ flex-shrink: 0;
95
+ }
96
+
97
+ chat-composer > [slot="leading"],
98
+ chat-composer > [slot="attach"] {
99
+ flex-shrink: 0;
100
+ display: inline-flex;
101
+ align-items: center;
102
+ gap: var(--a-space-1);
103
+ }
104
+
105
+ chat-composer > [slot="trailing"] {
106
+ flex-shrink: 0;
107
+ display: inline-flex;
108
+ align-items: center;
109
+ gap: var(--a-space-1);
110
+ }
111
+
112
+ /* The primary input child takes remaining space */
113
+ chat-composer > :is(chat-input-ui, input-ui, textarea-ui) {
114
+ flex: 1;
115
+ min-width: 0;
116
+ }
117
+
118
+ chat-composer[disabled] {
119
+ opacity: 0.6;
120
+ pointer-events: none;
121
+ }
122
+
123
+ /* ── chat-sidebar — bridges to admin-sidebar pattern ──
124
+ Uses the same geometry + container-query + resize handle rules as
125
+ admin-sidebar. The cluster-namespaced tag exists for discoverability;
126
+ the styling logic is identical because the concern is identical
127
+ (resizable side rail). */
128
+
129
+ :is(chat-sidebar[slot="leading"], chat-sidebar[slot="trailing"]) {
130
+ display: flex;
131
+ flex-direction: column;
132
+ flex-shrink: 0;
133
+ min-width: var(--chat-sidebar-min-width, 48px);
134
+ max-width: var(--chat-sidebar-max-width, 480px);
135
+ min-height: 0;
136
+ font-size: var(--chat-sidebar-font, var(--a-ui-size));
137
+ position: relative;
138
+ container-type: inline-size;
139
+ container-name: sidebar;
140
+ transition: width var(--chat-duration, var(--a-duration, 200ms)) var(--chat-easing, var(--a-easing, ease));
141
+ background: var(--chat-bg, var(--a-bg));
142
+ }
143
+
144
+ chat-sidebar[slot="leading"] {
145
+ width: var(--chat-sidebar-width-leading, 240px);
146
+ border-right: var(--chat-border, 1px solid var(--a-border-subtle));
147
+ }
148
+
149
+ chat-sidebar[slot="trailing"] {
150
+ width: var(--chat-sidebar-width-trailing, 320px);
151
+ border-left: var(--chat-border, 1px solid var(--a-border-subtle));
152
+ }
153
+
154
+ /* Resize handle */
155
+ :is(chat-sidebar[slot="leading"], chat-sidebar[slot="trailing"]) > [data-resize] {
156
+ position: absolute;
157
+ top: 0;
158
+ bottom: 0;
159
+ width: 6px;
160
+ cursor: col-resize;
161
+ z-index: 1;
162
+ user-select: none;
163
+ }
164
+
165
+ chat-sidebar[slot="leading"] > [data-resize] { right: -3px; }
166
+ chat-sidebar[slot="trailing"] > [data-resize] { left: -3px; }
167
+
168
+ :is(chat-sidebar[slot="leading"], chat-sidebar[slot="trailing"]) > [data-resize]:hover {
169
+ background: var(--a-accent);
170
+ opacity: 0.5;
171
+ }
172
+
173
+ chat-sidebar[resizing] {
174
+ transition: none;
175
+ }
176
+
177
+ chat-sidebar[resizing] > [data-resize] {
178
+ background: var(--a-accent);
179
+ opacity: 0.8;
180
+ }
181
+
182
+ /* ── chat-shell layout — when bespoke children are used ──
183
+ Adjusts flex direction so chat-sidebar can sit alongside the main
184
+ chat surface. Without this, chat-shell's existing layout.css
185
+ assumes a vertical stack (header / messages / composer). */
186
+
187
+ chat-shell:has(> chat-sidebar) {
188
+ display: flex;
189
+ flex-direction: row;
190
+ }
191
+
192
+ /* When sidebar is present, the main chat content (everything that
193
+ isn't a sidebar) needs to be a flex column itself */
194
+ chat-shell:has(> chat-sidebar) > :not(chat-sidebar) {
195
+ display: contents;
196
+ }