@adia-ai/web-modules 0.6.16 → 0.6.18

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/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog — @adia-ai/web-modules
2
2
 
3
+ ## [0.6.18] — 2026-05-21
4
+
5
+ ### Added — `admin-sidebar` resize-handle diagnostic (FB-17)
6
+
7
+ - **`<admin-sidebar resizable>` now warns when no `[data-resize]` child is
8
+ present.** `#setupResizeHandle()` previously returned silently when the
9
+ author-supplied drag-handle element was missing, leaving `resizable` a no-op
10
+ with zero diagnostic signal. It now emits a one-shot `console.warn`
11
+ (WeakSet-guarded) naming the fix. Warning-only; no functional change.
12
+ (~22 LOC `admin-sidebar.js`.)
13
+
14
+ ### Docs
15
+
16
+ - **Live `<admin-sidebar>` variant gallery + collapse/expand demo** added to `admin-shell.html` and `admin-shell.examples.html` — covers the resizable / collapsible / variant axes in one browsable surface.
17
+
18
+ ### Note
19
+
20
+ - The headline v0.6.18 work shipped in `@adia-ai/web-components` — `stat-ui` + `table-ui` `loading` props (FB-12 P2) and `text-ui` `size` / `color` / `weight` / `text-align` overlay attributes (FB-10). See `packages/web-components/CHANGELOG.md#0618--2026-05-21` for details.
21
+
22
+ ## [0.6.17] — 2026-05-21
23
+
24
+ ### Fixed
25
+ - **Collapsed sidebar — text labels overflow for vanilla HTML consumers.** When `<admin-sidebar>` collapses to its ≤96px icon-rail state, the existing `@container sidebar (max-width: 96px)` rules in `admin-shell.collapsed.css` target only the AdiaUI primitives (`<nav-ui>`, `<nav-item-ui>`, `<select-ui>`, `<button-ui>`). Consumers that use vanilla HTML inside the sidebar — `<button class="nav-item">Label</button>` with native text nodes, or `<span slot="heading">Brand</span>` for the app wordmark — were not covered, and their text content overflowed the 48px rail via `overflow: visible`. Symptom: "Claims UI" wordmark wrapped to two lines, and nav labels (Dashboard/Claims/Users/Settings) rendered visibly past the rail edge into the content area. Reported by claims-ui-v3 cold-start v0.6.16 screenshot.
26
+
27
+ Fix adds a forgiving fallback block to `admin-shell.collapsed.css`:
28
+ - Clips overflow on the sidebar itself + chrome containers (`admin-topbar`, `admin-statusbar`, `section`, `section-ui`, `nav`) so escaping text descendants stay inside the rail edge.
29
+ - Hides `[slot="heading"]` children — authors who want a brand mark visible when collapsed should put an `<icon-ui>` or logo image in `[slot="leading"]` instead.
30
+ - For vanilla `<button class="nav-item">`, `<a class="nav-item">`, and `[data-nav]` — applies `justify-content: center`, `overflow: hidden`, `text-indent: -9999px`, `white-space: nowrap` to clip text labels while preserving icons. Restores `text-indent: 0` on child `<icon-ui>` so the icon itself stays visible.
31
+
32
+ Using `<nav-item-ui>` / `<nav-ui>` remains the canonical, more capable path — popover tooltips on hover, badge slots, separator drawing all work via the primitives. The fallback is a safety net for consumers who don't (yet) wire the AdiaUI primitive stack inside the sidebar. Files: `packages/web-modules/shell/admin-shell/css/admin-shell.collapsed.css` (+60 LOC).
33
+
34
+ ### Verification
35
+ - `components.mjs --verify` clean (142 files up-to-date).
36
+ - `verify:traits` 56/56 clean.
37
+ - `check:lockstep` — all 9 at 0.6.17, ranges at ^0.6.0.
38
+ - `eval:diff --engine zettel` cov=49% / avg=90 (baseline-identical).
39
+ - Browser-verified at `http://localhost:5175/` (claims-ui-v3, after copying the updated CSS to local `node_modules`): toggling the sidebar to collapsed state hides `[slot="heading"]` (display=none), applies `text-indent: -9999px` + `overflow: hidden` to vanilla nav buttons, and preserves icon visibility via `text-indent: 0` on `<icon-ui>` descendants. Vision pass confirms no overflow, no "Claims UI" wrapping, icons stacked vertically in the rail.
40
+
3
41
  ## [0.6.16] — 2026-05-21
4
42
 
5
43
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-modules",
3
- "version": "0.6.16",
3
+ "version": "0.6.18",
4
4
  "description": "AdiaUI composite custom elements \u2014 shell, chat, editor, runtime clusters built from @adia-ai/web-components primitives. Subpath exports per cluster.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, resolve } from 'node:path';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const bespokeCSS = readFileSync(resolve(__dirname, 'css/admin-shell.bespoke.css'), 'utf8');
8
+ const collapsedCSS = readFileSync(resolve(__dirname, 'css/admin-shell.collapsed.css'), 'utf8');
9
+
10
+ // ── Helpers ────────────────────────────────────────────────────────
11
+ function mount(html) {
12
+ document.body.innerHTML = html;
13
+ return document.body.firstElementChild;
14
+ }
15
+
16
+ beforeEach(() => {
17
+ document.body.innerHTML = '';
18
+ });
19
+
20
+ // ── Regression guards ─────────────────────────────────────────────
21
+ //
22
+ // These tests document substrate selector decisions that have been
23
+ // regressed and re-fixed at least once each. They run against the raw
24
+ // CSS file content + DOM `element.matches()` rather than computed style,
25
+ // because happy-dom doesn't fully resolve external stylesheet cascade.
26
+ // The trade-off: we don't assert that the rule WAS applied, but we DO
27
+ // assert that the rule's selector REACHES the element it's meant to
28
+ // style — which is the part that broke in v0.6.16.
29
+
30
+ describe('admin-shell.bespoke.css — selector reachability', () => {
31
+ it('v0.6.16 fix: admin-page rule uses descendant combinator (not >)', () => {
32
+ // The previous selector `admin-scroll > admin-page` broke when a
33
+ // router (e.g. <router-ui>) sat between <admin-scroll> and
34
+ // <admin-page>. The fix uses :is() + descendant combinator so any
35
+ // depth of intermediate wrapping still matches.
36
+ expect(bespokeCSS).toMatch(/:is\(admin-scroll,\s*admin-content\)\s+admin-page\s*\{/);
37
+ // Negative assertion: the broken direct-child form should not be
38
+ // present any more (catches accidental reverts).
39
+ expect(bespokeCSS).not.toMatch(/^admin-scroll\s*>\s*admin-page\s*,/m);
40
+ expect(bespokeCSS).not.toMatch(/^admin-content\s*>\s*admin-page\s*\{/m);
41
+ });
42
+
43
+ it('admin-page rule reaches admin-page inside <router-ui>', () => {
44
+ // Synthesize the docs-site DOM shape that hit the v0.6.16 bug.
45
+ // The selector under test must match <admin-page> via descendant
46
+ // combinator even when <router-ui> sits between.
47
+ mount(`
48
+ <admin-shell>
49
+ <admin-content>
50
+ <admin-scroll>
51
+ <router-ui id="router">
52
+ <admin-page id="page"></admin-page>
53
+ </router-ui>
54
+ </admin-scroll>
55
+ </admin-content>
56
+ </admin-shell>
57
+ `);
58
+ const page = document.getElementById('page');
59
+ expect(page).toBeTruthy();
60
+ // Direct-child match should fail (router-ui sits between)
61
+ expect(page.matches('admin-scroll > admin-page')).toBe(false);
62
+ expect(page.matches('admin-content > admin-page')).toBe(false);
63
+ // Descendant match must succeed (this is what v0.6.17 introduced)
64
+ expect(page.matches(':is(admin-scroll, admin-content) admin-page')).toBe(true);
65
+ });
66
+
67
+ it('admin-page rule still reaches admin-page as direct child', () => {
68
+ // Canonical playground/example layout — no router wrapper.
69
+ // The descendant combinator must still match this case.
70
+ mount(`
71
+ <admin-shell>
72
+ <admin-content>
73
+ <admin-scroll>
74
+ <admin-page id="page"></admin-page>
75
+ </admin-scroll>
76
+ </admin-content>
77
+ </admin-shell>
78
+ `);
79
+ const page = document.getElementById('page');
80
+ expect(page.matches(':is(admin-scroll, admin-content) admin-page')).toBe(true);
81
+ });
82
+ });
83
+
84
+ describe('admin-shell.collapsed.css — vanilla HTML fallback (v0.6.17)', () => {
85
+ it('contains the vanilla-HTML fallback block', () => {
86
+ // v0.6.17 added rules covering <button class="nav-item"> etc. so
87
+ // text labels don't overflow the 48px rail when the consumer doesn't
88
+ // use AdiaUI <nav-item-ui> primitives.
89
+ expect(collapsedCSS).toMatch(/Vanilla-HTML fallback/);
90
+ expect(collapsedCSS).toMatch(/\[slot="heading"\]\s*\{[\s\S]*?display:\s*none/);
91
+ expect(collapsedCSS).toMatch(/button\.nav-item/);
92
+ expect(collapsedCSS).toMatch(/text-indent:\s*-9999px/);
93
+ });
94
+
95
+ it('preserves icon visibility inside vanilla nav buttons', () => {
96
+ // text-indent:-9999px on the parent must be undone on the icon
97
+ // child, otherwise the icon disappears with the text. Both rules
98
+ // must be present in the file.
99
+ expect(collapsedCSS).toMatch(/button\.nav-item\s*>\s*icon-ui[\s\S]*?text-indent:\s*0/);
100
+ });
101
+
102
+ it('fallback rules sit inside the @container sidebar query', () => {
103
+ // The vanilla-HTML fallback must be scoped to the collapsed state
104
+ // (max-width: 96px container query). Outside that scope, the
105
+ // text-indent: -9999px clip would also break the expanded state.
106
+ const containerMatch = collapsedCSS.match(/@container\s+sidebar\s*\(max-width:\s*96px\)\s*\{([\s\S]+)\}/);
107
+ expect(containerMatch).toBeTruthy();
108
+ const inside = containerMatch[1];
109
+ expect(inside).toMatch(/Vanilla-HTML fallback/);
110
+ expect(inside).toMatch(/text-indent:\s*-9999px/);
111
+ });
112
+ });
@@ -83,4 +83,64 @@
83
83
  button-ui [slot="trailing"] {
84
84
  display: none;
85
85
  }
86
+
87
+ /* ─── Vanilla-HTML fallback ───
88
+ The primitive-targeted rules above (nav-ui, nav-item-ui, button-ui) cover
89
+ the canonical AdiaUI composition. Consumers using vanilla HTML inside the
90
+ sidebar (e.g. <button class="nav-item">Label</button> with native text
91
+ nodes, or <span slot="heading">Brand</span>) need a forgiving fallback —
92
+ without it, text labels overflow the 48px collapsed bbox via
93
+ `overflow: visible` and spill into the content area, producing the
94
+ "Claims UI wraps to two lines + nav labels visible past the rail" bug
95
+ reported by claims-ui-v3 cold-start v0.6.16 §sidebar-overflow.
96
+
97
+ Strategy: clip the sidebar itself + hide common text-bearing slot
98
+ children. Icons (<icon-ui>) and avatars (<img>, <avatar-ui>) stay
99
+ visible. This is a safety net — using <nav-item-ui> / <nav-ui> remains
100
+ the canonical, more capable path (popover tooltips, badges, etc.). */
101
+
102
+ /* Clip the sidebar's chrome containers — text descendants that escape
103
+ their parent's width are now invisible past the rail edge. */
104
+ :scope,
105
+ :is(admin-topbar, admin-statusbar),
106
+ :is(section, section-ui, nav) {
107
+ overflow: hidden;
108
+ }
109
+
110
+ /* Hide [slot="heading"] (typically the app brand label in the sidebar's
111
+ <admin-topbar slot="header">) — keeps the rail clean in collapsed mode.
112
+ Authors who want a brand mark visible when collapsed should put an
113
+ <icon-ui> or logo image in [slot="leading"] instead. */
114
+ [slot="heading"] {
115
+ display: none;
116
+ }
117
+
118
+ /* Vanilla nav buttons: any <button class="nav-item"> or [data-nav] item
119
+ not built from nav-item-ui. Hide non-icon children so only the icon
120
+ remains visible. Detection: a child of the sidebar that is NOT
121
+ itself an icon/avatar primitive and is NOT an element wrapping such
122
+ a primitive — i.e., bare text/spans/labels. */
123
+ button.nav-item,
124
+ a.nav-item,
125
+ [data-nav] {
126
+ /* Center the icon; hide any text by clipping (we can't safely
127
+ :not(:has(icon-ui)) every child without false-positives, so we
128
+ clip the box and rely on flex centering the icon). */
129
+ justify-content: center;
130
+ overflow: hidden;
131
+ text-indent: -9999px; /* push label text off-screen */
132
+ white-space: nowrap;
133
+ }
134
+
135
+ /* But: keep the icon inside the button visible. text-indent: -9999px
136
+ would push the icon too — so restore positioning for the icon child
137
+ and any element that wraps it. */
138
+ button.nav-item > icon-ui,
139
+ a.nav-item > icon-ui,
140
+ [data-nav] > icon-ui,
141
+ button.nav-item > :has(> icon-ui),
142
+ a.nav-item > :has(> icon-ui),
143
+ [data-nav] > :has(> icon-ui) {
144
+ text-indent: 0;
145
+ }
86
146
  }
@@ -54,6 +54,10 @@ class AdminSidebar extends UIElement {
54
54
 
55
55
  static template = () => null;
56
56
 
57
+ // FEEDBACK-17: elements already warned about a missing [data-resize]
58
+ // handle — one-shot per element so re-renders stay quiet. GC-friendly.
59
+ static #warnedNoHandle = new WeakSet();
60
+
57
61
  // The width the sidebar had before being collapsed — used for restore.
58
62
  // Map keyed by sidebar name allows multiple sidebars on one host.
59
63
  #previousExpandedWidth = '';
@@ -149,7 +153,23 @@ class AdminSidebar extends UIElement {
149
153
 
150
154
  #setupResizeHandle() {
151
155
  const handle = this.querySelector(':scope > [data-resize]');
152
- if (!handle) return;
156
+ if (!handle) {
157
+ // FEEDBACK-17: `resizable` opts in to drag-resize but relies on an
158
+ // author-supplied `[data-resize]` child. A silent return left authors
159
+ // with a no-op `resizable` attribute and zero diagnostic signal.
160
+ if (!AdminSidebar.#warnedNoHandle.has(this)) {
161
+ AdminSidebar.#warnedNoHandle.add(this);
162
+ // eslint-disable-next-line no-console
163
+ console.warn(
164
+ '[admin-sidebar] `resizable` is set but no `[data-resize]` child ' +
165
+ 'was found. Add `<div data-resize></div>` as a direct child to ' +
166
+ 'enable drag-to-resize. (Unlike `collapsible`, `resizable` requires ' +
167
+ 'this author-supplied handle.)',
168
+ this,
169
+ );
170
+ }
171
+ return;
172
+ }
153
173
 
154
174
  const slot = this.getAttribute('slot');
155
175
  const isLeading = slot === 'leading';