@adia-ai/web-modules 0.3.4 → 0.3.5

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 (37) hide show
  1. package/CHANGELOG.md +24 -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/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -11,6 +11,30 @@ Built from `@adia-ai/web-components` primitives.
11
11
 
12
12
  _No pending changes._
13
13
 
14
+ ## [0.3.5] - 2026-05-07
15
+
16
+ ### Added
17
+
18
+ - **Chat cluster bespoke family** — second cluster decomposed per ADR-0023, proving the family pattern is replicable.
19
+ - **`<chat-thread streaming?>`** — JS-bearing (106 LOC + 8 unit tests). Replaces legacy `<section data-chat-messages>`. Owns scroll-to-bottom on new message (with user-scroll-up suspension), `[streaming]` and `[empty]` reflected attributes. Public API: `.scrollToBottom()` / `.scrollToBottomInstant()`.
20
+ - **`<chat-composer disabled?>`** — JS-bearing (100 LOC + 9 unit tests). Wraps `<chat-input-ui>` (or any input-like child). Forwards inner submit as `composer-submit` event (bespoke name to prevent double-fire). Propagates `[disabled]` to inner input.
21
+ - **`<chat-sidebar slot="leading|trailing" resizable collapsible>`** — JS-bearing (227 LOC + 10 unit tests). Mirrors `<admin-sidebar>`. Cluster-namespaced localStorage prefix (`adia-chat-sidebar-{name}`).
22
+ - **3 CSS-only structural children** — `<chat-header>`, `<chat-status>`, `<chat-empty>` (visibility driven by parent `<chat-thread>[empty]`).
23
+
24
+ - **CSS bridge** `chat/chat-shell/css/chat-shell.bespoke.css` (196 LOC) — maps the 6 bespoke chat tags to existing CSS without touching legacy layer files. Imported last in `chat-shell.css`.
25
+
26
+ - **`chat/index.js` exports** all 4 JS-bearing chat children (ChatShell + ChatThread + ChatComposer + ChatSidebar). Subpath import unchanged: `import '@adia-ai/web-modules/chat'`.
27
+
28
+ ### Changed
29
+
30
+ - **`<chat-shell>` host** — now coordinates bespoke children rather than centralizing their behavior. `connected()` reads BOTH legacy + bespoke shapes via priority chain. Listens for `composer-submit` on bespoke composer, `submit` on legacy input (mutually exclusive). `startStreaming()` / `stopStreaming()` propagate `[streaming]` to `<chat-thread>`.
31
+
32
+ - **`chat-shell.examples.html` reorganized** — split "Basic shape" into "Basic shape (legacy)" + new "Basic shape (bespoke — recommended)" + "State as attribute" section with CSS `:has()` examples.
33
+
34
+ ### Backwards compatibility
35
+
36
+ Legacy authoring shapes (`<section data-chat-messages>`, `<chat-input-ui data-chat-input>`, `[data-chat-empty]`, `[data-chat-status]`) all still work identically. Mixed markup renders the same. Migration is opt-in.
37
+
14
38
  ## [0.3.4] - 2026-05-07
15
39
 
16
40
  ### Added
@@ -0,0 +1,94 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/ChatComposer.json",
4
+ "title": "ChatComposer",
5
+ "description": "Module-tier chat composer wrapper — replaces legacy <chat-input-ui\ndata-chat-input> direct child of <chat-shell> per ADR-0023.\nForwards submit events as 'composer-submit', propagates [disabled]\nto the inner input, provides slot vocabulary for future composer\nsurfaces (file attach, autocomplete, model picker).\n\nSits inside <chat-shell> as the input region (typically inside or\nalongside the footer). Authors place an inner <chat-input-ui>\n(or input-ui / textarea-ui) as the primary input.\n\nBackwards compat — <chat-shell> still recognizes the legacy\n<chat-input-ui data-chat-input> shape via :is() selector. New\ncode should prefer <chat-composer>.\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": "ChatComposer"
18
+ },
19
+ "disabled": {
20
+ "description": "Reflected — set by the host while streaming, or by application\ncode. Propagated to the inner input element automatically.\n",
21
+ "type": "boolean",
22
+ "default": false
23
+ }
24
+ },
25
+ "required": [
26
+ "component"
27
+ ],
28
+ "unevaluatedProperties": false,
29
+ "x-adiaui": {
30
+ "anti_patterns": [],
31
+ "category": "input",
32
+ "events": {
33
+ "composer-submit": {
34
+ "description": "Bubbles when the inner input fires its 'submit' event. Detail mirrors the inner event's detail (typically text + model).",
35
+ "detail": {
36
+ "model": "string",
37
+ "text": "string"
38
+ }
39
+ }
40
+ },
41
+ "examples": [],
42
+ "keywords": [
43
+ "chat-composer",
44
+ "chat-input",
45
+ "composer",
46
+ "message-input",
47
+ "conversation-input"
48
+ ],
49
+ "name": "ChatComposer",
50
+ "related": [
51
+ "ChatShell",
52
+ "ChatThread",
53
+ "ChatInput",
54
+ "Input",
55
+ "Textarea"
56
+ ],
57
+ "slots": {
58
+ "default": {
59
+ "description": "Default — primary input child (typically <chat-input-ui> or <input-ui submit-on-enter>)."
60
+ },
61
+ "attach": {
62
+ "description": "Future — attachment trigger button (paperclip icon to upload)."
63
+ },
64
+ "leading": {
65
+ "description": "Leading controls — autocomplete trigger, voice input, etc."
66
+ },
67
+ "trailing": {
68
+ "description": "Trailing controls — model picker, send button override, etc."
69
+ }
70
+ },
71
+ "states": [
72
+ {
73
+ "description": "Default, accepting input.",
74
+ "name": "idle"
75
+ },
76
+ {
77
+ "description": "Input is disabled (typically during LLM streaming).",
78
+ "attribute": "disabled",
79
+ "name": "disabled"
80
+ }
81
+ ],
82
+ "synonyms": {
83
+ "composer": [
84
+ "message-input",
85
+ "chat-input",
86
+ "conversation-input"
87
+ ]
88
+ },
89
+ "tag": "chat-composer",
90
+ "tokens": {},
91
+ "traits": [],
92
+ "version": 1
93
+ }
94
+ }
@@ -0,0 +1,28 @@
1
+ <header>
2
+ <div>
3
+ <h1>Chat Composer</h1>
4
+ <div data-actions>
5
+ <tag-ui size="sm">chat-composer</tag-ui>
6
+ <tag-ui size="sm" variant="ghost">JS-bearing</tag-ui>
7
+ </div>
8
+ </div>
9
+ <p>Module-tier chat composer wrapper — forwards submit events, propagates [disabled], slot vocabulary for future composer surfaces.</p>
10
+ </header>
11
+
12
+ <section data-section>
13
+ <h2 variant="section">Role</h2>
14
+ <p>Forwards inner submit events as <code>composer-submit</code>, propagates <code>[disabled]</code> to the inner input, slot vocabulary for future composer surfaces (file attach, autocomplete trigger, model picker). JS-bearing.</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-composer&gt;
21
+ &lt;chat-input-ui placeholder="Message…" submit-on-enter&gt;&lt;/chat-input-ui&gt;
22
+ &lt;/chat-composer&gt;</code-ui>
23
+ </section>
24
+
25
+ <section data-section>
26
+ <h2 variant="section">Family</h2>
27
+ <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>
28
+ </section>
@@ -0,0 +1,43 @@
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 Composer — 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="./chat-composer.js"></script>
16
+ <script type="module" src="../../../web-components/components/code/code.js"></script>
17
+ <script type="module" src="../../../web-components/components/tag/tag.js"></script>
18
+
19
+ <style>
20
+ :where(html, body) { margin: 0; min-height: 100vh; background: var(--a-bg); color: var(--a-fg); font-family: var(--a-font); }
21
+ main { max-width: 960px; margin-inline: auto; padding: var(--a-space-6) var(--a-space-5); }
22
+ </style>
23
+ </head>
24
+ <body>
25
+
26
+ <main id="demo-root">
27
+ <p>Loading examples…</p>
28
+ </main>
29
+
30
+ <script type="module">
31
+ const root = document.getElementById('demo-root');
32
+ try {
33
+ const res = await fetch('./chat-composer.examples.html');
34
+ if (!res.ok) throw new Error(`fetch failed (${res.status})`);
35
+ root.innerHTML = await res.text();
36
+ } catch (err) {
37
+ root.innerHTML = `<p style="color:var(--a-danger-strong);">Failed to load chat-composer.examples.html — ${err.message}</p>`;
38
+ console.error('[chat-composer.html]', err);
39
+ }
40
+ </script>
41
+
42
+ </body>
43
+ </html>
@@ -0,0 +1,107 @@
1
+ /**
2
+ * <chat-composer disabled?>
3
+ * <chat-input-ui placeholder="..." submit-on-enter></chat-input-ui>
4
+ * <!-- future: <button-ui slot="attach" icon="paperclip"> -->
5
+ * </chat-composer>
6
+ *
7
+ * Module-tier chat composer wrapper — replaces legacy
8
+ * <chat-input-ui data-chat-input> direct child of <chat-shell>
9
+ * per ADR-0023. Owns:
10
+ *
11
+ * - Forward submit events from inner <chat-input-ui> as
12
+ * 'composer-submit' (so the host doesn't reach inside)
13
+ * - [disabled] reflected attribute, propagated to inner input
14
+ * - Slot vocabulary for future composer surfaces (file attach,
15
+ * autocomplete trigger, model picker, etc.)
16
+ * - Focus delegation — .focus() / .clear() proxy to inner input
17
+ *
18
+ * Reflected attributes:
19
+ * [disabled] — set by host while streaming; propagated to
20
+ * inner <chat-input-ui>
21
+ *
22
+ * Public methods:
23
+ * .focus() — focus inner input
24
+ * .clear() — clear inner input value
25
+ * .input() — getter: returns inner <chat-input-ui> reference
26
+ *
27
+ * Events forwarded from inner input:
28
+ * composer-submit — bubbles. detail: { text, model }
29
+ *
30
+ * The host (<chat-shell>) reads either <chat-composer> or
31
+ * [data-chat-input] / <chat-input-ui> via :is() selector for
32
+ * backwards compat.
33
+ */
34
+
35
+ import { UIElement } from '../../../web-components/core/element.js';
36
+
37
+ class ChatComposer extends UIElement {
38
+ static properties = {
39
+ disabled: { type: Boolean, default: false, reflect: true },
40
+ };
41
+
42
+ static template = () => null;
43
+
44
+ #inputEl = null;
45
+ #onSubmit = null;
46
+
47
+ connected() {
48
+ this.#inputEl = this.querySelector('chat-input-ui') || this.querySelector('input-ui') || this.querySelector('textarea-ui');
49
+
50
+ this.#onSubmit = (e) => {
51
+ // Re-emit as 'composer-submit' so host listens to one event
52
+ // regardless of which inner input it is
53
+ this.dispatchEvent(new CustomEvent('composer-submit', {
54
+ bubbles: true,
55
+ detail: e.detail || {},
56
+ }));
57
+ };
58
+ this.#inputEl?.addEventListener('submit', this.#onSubmit);
59
+
60
+ // Sync initial disabled state to inner input
61
+ this.#syncDisabled();
62
+ }
63
+
64
+ disconnected() {
65
+ if (this.#inputEl && this.#onSubmit) {
66
+ this.#inputEl.removeEventListener('submit', this.#onSubmit);
67
+ }
68
+ this.#inputEl = null;
69
+ this.#onSubmit = null;
70
+ }
71
+
72
+ // Reflected attribute change — propagate to inner input
73
+ attributeChangedCallback(name, oldVal, newVal) {
74
+ super.attributeChangedCallback?.(name, oldVal, newVal);
75
+ if (name === 'disabled') {
76
+ this.#syncDisabled();
77
+ }
78
+ }
79
+
80
+ // ── Public API ──
81
+
82
+ focus() {
83
+ this.#inputEl?.focus?.();
84
+ }
85
+
86
+ clear() {
87
+ this.#inputEl?.clear?.();
88
+ }
89
+
90
+ get input() {
91
+ return this.#inputEl;
92
+ }
93
+
94
+ // ── Internal ──
95
+
96
+ #syncDisabled() {
97
+ if (!this.#inputEl) return;
98
+ if (this.disabled) {
99
+ this.#inputEl.setAttribute('disabled', '');
100
+ } else {
101
+ this.#inputEl.removeAttribute('disabled');
102
+ }
103
+ }
104
+ }
105
+
106
+ customElements.define('chat-composer', ChatComposer);
107
+ export { ChatComposer };
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import '../../../web-components/core/element.js';
3
+ import './chat-composer.js';
4
+
5
+ const tick = () => new Promise((r) => queueMicrotask(r));
6
+
7
+ function mount(html) {
8
+ const wrap = document.createElement('div');
9
+ wrap.innerHTML = html;
10
+ document.body.appendChild(wrap);
11
+ return wrap.firstElementChild;
12
+ }
13
+
14
+ beforeEach(() => {
15
+ document.body.innerHTML = '';
16
+ });
17
+
18
+ describe('chat-composer', () => {
19
+ it('registers chat-composer as a custom element', () => {
20
+ expect(customElements.get('chat-composer')).toBeDefined();
21
+ });
22
+
23
+ it('defaults to disabled=false', () => {
24
+ const c = mount('<chat-composer></chat-composer>');
25
+ expect(c.disabled).toBe(false);
26
+ });
27
+
28
+ it('reflects [disabled] via property assignment', async () => {
29
+ const c = mount('<chat-composer></chat-composer>');
30
+ c.disabled = true;
31
+ await tick();
32
+ expect(c.hasAttribute('disabled')).toBe(true);
33
+ });
34
+
35
+ it('exposes .focus() / .clear() / .input getter', () => {
36
+ const c = mount('<chat-composer></chat-composer>');
37
+ expect(typeof c.focus).toBe('function');
38
+ expect(typeof c.clear).toBe('function');
39
+ expect('input' in c).toBe(true);
40
+ });
41
+
42
+ it('returns null .input when no inner input element', () => {
43
+ const c = mount('<chat-composer></chat-composer>');
44
+ expect(c.input).toBeNull();
45
+ });
46
+
47
+ it('forwards inner submit as composer-submit event', async () => {
48
+ // Plain input-like element with submit dispatch
49
+ const wrap = document.createElement('div');
50
+ wrap.innerHTML = `<chat-composer>
51
+ <chat-input-ui></chat-input-ui>
52
+ </chat-composer>`;
53
+ document.body.appendChild(wrap);
54
+ const composer = wrap.firstElementChild;
55
+ const innerInput = composer.querySelector('chat-input-ui');
56
+
57
+ const onComposerSubmit = vi.fn();
58
+ composer.addEventListener('composer-submit', onComposerSubmit);
59
+
60
+ innerInput.dispatchEvent(new CustomEvent('submit', { detail: { text: 'hello' } }));
61
+ await tick();
62
+
63
+ expect(onComposerSubmit).toHaveBeenCalledTimes(1);
64
+ expect(onComposerSubmit.mock.calls[0][0].detail).toEqual({ text: 'hello' });
65
+ });
66
+
67
+ it('propagates [disabled] to inner input via attribute', async () => {
68
+ const wrap = document.createElement('div');
69
+ wrap.innerHTML = `<chat-composer>
70
+ <chat-input-ui></chat-input-ui>
71
+ </chat-composer>`;
72
+ document.body.appendChild(wrap);
73
+ const composer = wrap.firstElementChild;
74
+ const innerInput = composer.querySelector('chat-input-ui');
75
+
76
+ composer.disabled = true;
77
+ await tick();
78
+
79
+ expect(innerInput.hasAttribute('disabled')).toBe(true);
80
+
81
+ composer.disabled = false;
82
+ await tick();
83
+
84
+ expect(innerInput.hasAttribute('disabled')).toBe(false);
85
+ });
86
+
87
+ it('cleanup on disconnect — removes inner submit listener', () => {
88
+ const wrap = document.createElement('div');
89
+ wrap.innerHTML = `<chat-composer>
90
+ <chat-input-ui></chat-input-ui>
91
+ </chat-composer>`;
92
+ document.body.appendChild(wrap);
93
+ const composer = wrap.firstElementChild;
94
+ const innerInput = composer.querySelector('chat-input-ui');
95
+ const removeSpy = vi.spyOn(innerInput, 'removeEventListener');
96
+
97
+ composer.remove();
98
+
99
+ const removedTypes = removeSpy.mock.calls.map((args) => args[0]);
100
+ expect(removedTypes).toContain('submit');
101
+ });
102
+
103
+ it('handles input-ui as inner input fallback', () => {
104
+ const wrap = document.createElement('div');
105
+ wrap.innerHTML = `<chat-composer>
106
+ <input-ui></input-ui>
107
+ </chat-composer>`;
108
+ document.body.appendChild(wrap);
109
+ const composer = wrap.firstElementChild;
110
+ expect(composer.input?.tagName?.toLowerCase()).toBe('input-ui');
111
+ });
112
+ });
@@ -0,0 +1,91 @@
1
+ # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
3
+ name: ChatComposer
4
+ tag: chat-composer
5
+ component: ChatComposer
6
+ category: input
7
+ version: 1
8
+ description: |
9
+ Module-tier chat composer wrapper — replaces legacy <chat-input-ui
10
+ data-chat-input> direct child of <chat-shell> per ADR-0023.
11
+ Forwards submit events as 'composer-submit', propagates [disabled]
12
+ to the inner input, provides slot vocabulary for future composer
13
+ surfaces (file attach, autocomplete, model picker).
14
+
15
+ Sits inside <chat-shell> as the input region (typically inside or
16
+ alongside the footer). Authors place an inner <chat-input-ui>
17
+ (or input-ui / textarea-ui) as the primary input.
18
+
19
+ Backwards compat — <chat-shell> still recognizes the legacy
20
+ <chat-input-ui data-chat-input> shape via :is() selector. New
21
+ code should prefer <chat-composer>.
22
+
23
+ props:
24
+ disabled:
25
+ description: |
26
+ Reflected — set by the host while streaming, or by application
27
+ code. Propagated to the inner input element automatically.
28
+ type: boolean
29
+ default: false
30
+ reflect: true
31
+
32
+ events:
33
+ composer-submit:
34
+ description: >-
35
+ Bubbles when the inner input fires its 'submit' event.
36
+ Detail mirrors the inner event's detail (typically text + model).
37
+ detail:
38
+ text: string
39
+ model: string
40
+
41
+ slots:
42
+ default:
43
+ description: >-
44
+ Default — primary input child (typically <chat-input-ui> or
45
+ <input-ui submit-on-enter>).
46
+ attach:
47
+ description: >-
48
+ Future — attachment trigger button (paperclip icon to upload).
49
+ trailing:
50
+ description: >-
51
+ Trailing controls — model picker, send button override, etc.
52
+ leading:
53
+ description: >-
54
+ Leading controls — autocomplete trigger, voice input, etc.
55
+
56
+ states:
57
+ - name: idle
58
+ description: Default, accepting input.
59
+ - name: disabled
60
+ attribute: disabled
61
+ description: Input is disabled (typically during LLM streaming).
62
+
63
+ traits: []
64
+
65
+ a2ui:
66
+ rules:
67
+ - >-
68
+ chat-composer is the bespoke replacement for legacy
69
+ <chat-input-ui data-chat-input> inside <chat-shell>. Place an
70
+ inner <chat-input-ui submit-on-enter> as the primary input.
71
+ - >-
72
+ The host listens for 'composer-submit' on the composer (not on
73
+ the inner input). The event detail mirrors the inner submit
74
+ event so existing handlers Just Work.
75
+
76
+ keywords:
77
+ - chat-composer
78
+ - chat-input
79
+ - composer
80
+ - message-input
81
+ - conversation-input
82
+
83
+ synonyms:
84
+ composer: [message-input, chat-input, conversation-input]
85
+
86
+ related:
87
+ - ChatShell
88
+ - ChatThread
89
+ - ChatInput
90
+ - Input
91
+ - Textarea
@@ -0,0 +1,68 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/ChatEmpty.json",
4
+ "title": "ChatEmpty",
5
+ "description": "Module-tier chat empty state. CSS-only — no behavior, no JS. Sits\nas the first child of <chat-thread> as the empty state placeholder.\nVisibility is driven by <chat-thread>'s [empty] reflected attribute\nvia CSS (no JS toggling needed) — when messages arrive, the parent\nthread loses [empty] and CSS hides this stub.\n\nReplaces the legacy <empty-state-ui data-chat-empty> child of the\nmessages container per ADR-0023. Both shapes still work via\nbackwards compat; new code should prefer <chat-empty>.\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": "ChatEmpty"
18
+ }
19
+ },
20
+ "required": [
21
+ "component"
22
+ ],
23
+ "unevaluatedProperties": false,
24
+ "x-adiaui": {
25
+ "anti_patterns": [],
26
+ "category": "feedback",
27
+ "events": {},
28
+ "examples": [],
29
+ "keywords": [
30
+ "chat-empty",
31
+ "empty-state",
32
+ "placeholder",
33
+ "no-messages"
34
+ ],
35
+ "name": "ChatEmpty",
36
+ "related": [
37
+ "ChatShell",
38
+ "ChatThread",
39
+ "EmptyState"
40
+ ],
41
+ "slots": {
42
+ "default": {
43
+ "description": "Empty state content — typically an icon + heading + description. Authors compose with <empty-state-ui> as a child, or supply inline content directly."
44
+ }
45
+ },
46
+ "states": [
47
+ {
48
+ "description": "Default, visible.",
49
+ "name": "idle"
50
+ },
51
+ {
52
+ "description": "Hidden via parent <chat-thread>:not([empty]) selector.",
53
+ "name": "hidden"
54
+ }
55
+ ],
56
+ "synonyms": {
57
+ "chat-empty": [
58
+ "empty-state",
59
+ "placeholder",
60
+ "no-conversations"
61
+ ]
62
+ },
63
+ "tag": "chat-empty",
64
+ "tokens": {},
65
+ "traits": [],
66
+ "version": 1
67
+ }
68
+ }
@@ -0,0 +1,34 @@
1
+ <header>
2
+ <div>
3
+ <h1>Chat Empty</h1>
4
+ <div data-actions>
5
+ <tag-ui size="sm">chat-empty</tag-ui>
6
+ <tag-ui size="sm" variant="ghost">CSS-only</tag-ui>
7
+ </div>
8
+ </div>
9
+ <p>Module-tier chat empty state. CSS-only. Visibility driven by parent <chat-thread>'s [empty] reflected attribute.</p>
10
+ </header>
11
+
12
+ <section data-section>
13
+ <h2 variant="section">Role</h2>
14
+ <p>Empty state placeholder. CSS-only. Visibility driven automatically by parent <code>&lt;chat-thread&gt;</code>'s <code>[empty]</code> reflected attribute — no JS toggling needed.</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-thread&gt;
21
+ &lt;chat-empty&gt;
22
+ &lt;empty-state-ui
23
+ icon="chat-circle"
24
+ heading="Hello!"
25
+ description="Ask me anything."&gt;&lt;/empty-state-ui&gt;
26
+ &lt;/chat-empty&gt;
27
+ &lt;!-- Messages get appended here; chat-empty auto-hides --&gt;
28
+ &lt;/chat-thread&gt;</code-ui>
29
+ </section>
30
+
31
+ <section data-section>
32
+ <h2 variant="section">Family</h2>
33
+ <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>
34
+ </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 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>