@adia-ai/web-modules 0.6.26 → 0.6.27

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,11 @@
1
1
  # Changelog — @adia-ai/web-modules
2
2
 
3
+ ## [0.6.27] — 2026-05-22
4
+
5
+ ### Fixed — `editor-sidebar` computedStyle guard for small-nonzero display-flip transients (§FB-49)
6
+
7
+ - **`editor-sidebar` — ResizeObserver fires with small-nonzero widths during `display:none` → `display:flex` transitions, latching `[collapsed]` even after the §FB-42 / §FB-48 `rect.width === 0` guard shipped in v0.6.26.** The 0-width guard only caught exact-zero samples; the `display-flip` cycle produces a brief 48px sample (matching the upstream `editor-shell[collapsed] > pane-ui { width: 48px !important }` rail minimum) before the pane fully re-enters layout. `#syncCollapsed()` read the transient, set `collapsed = true`, and the mirror-write `this.style.width = '48px'` pinned the host's grid track. Fix: guard on `getComputedStyle(this).display === 'none'` at the top of both the ResizeObserver callback and `#syncCollapsed()` — resolves the full CSS cascade (not just inline `style.display`) so any `@media`-driven hide triggers the guard. The §FB-42 `rect.width === 0` guard is retained as belt-and-braces. Added regression test: RO fires with a 48px transient while `style.display === 'none'` → `collapsed` stays false. Consumer workaround (CSS counter-rule at `@media (min-width: 900px)`) can now be safely retired. Reported by color-app (FEEDBACK-49). Files: `editor/editor-sidebar/editor-sidebar.js`, `editor/editor-sidebar/editor-sidebar.test.js`.
8
+
3
9
  ## [0.6.26] — 2026-05-22
4
10
 
5
11
  ### Fixed — `editor-sidebar` actually applies the FB-42 `#syncCollapsed` 0-width guard (FB-48)
@@ -92,6 +92,10 @@ class EditorSidebar extends UIElement {
92
92
  // explicit width — the pane then overflows out of the sidebar and
93
93
  // visually overlaps the canvas.
94
94
  this.#ro = new ResizeObserver((entries) => {
95
+ // §FB-49: skip samples while the host is not in layout — catches both
96
+ // explicit display:none and small-nonzero transients during the
97
+ // display-flip transition (e.g. 48px when pane re-enters layout).
98
+ if (getComputedStyle(this).display === 'none') return;
95
99
  this.#syncCollapsed();
96
100
  // Use offsetWidth so the sidebar wraps the full pane border-box
97
101
  // (contentRect would be 1px short on side="leading|trailing").
@@ -170,10 +174,13 @@ class EditorSidebar extends UIElement {
170
174
 
171
175
  #syncCollapsed() {
172
176
  if (!this.#pane) return;
177
+ // §FB-49: guard before reading rect — catches explicit display:none AND
178
+ // small-nonzero transients during display-flip (e.g. pane briefly at 48px
179
+ // while re-entering layout). getComputedStyle resolves CSS rules, not just
180
+ // inline style, covering both direct assignment and @media rules.
181
+ if (getComputedStyle(this).display === 'none') return;
173
182
  const rect = this.#pane.getBoundingClientRect();
174
- // §FB-42: a display:none host produces width=0 — not a real collapse signal.
175
- // Symmetric with the `if (w > 0)` guard on the width-write branch above.
176
- if (rect.width === 0) return;
183
+ if (rect.width === 0) return; // §FB-42 belt-and-braces for detached/hidden
177
184
  this.collapsed = rect.width <= SNAP_THRESHOLD;
178
185
  }
179
186
 
@@ -116,6 +116,38 @@ describe('editor-sidebar', () => {
116
116
  expect(removedTypes).toContain('pointerup');
117
117
  });
118
118
 
119
+ it('does not latch [collapsed] on small-transient RO sample while host is display:none (§FB-49)', async () => {
120
+ // Capture the RO callback so we can fire it manually with a transient width.
121
+ let roCallback;
122
+ const savedRO = globalThis.ResizeObserver;
123
+ globalThis.ResizeObserver = class {
124
+ constructor(cb) { roCallback = cb; }
125
+ observe() {} unobserve() {} disconnect() {}
126
+ };
127
+
128
+ const sb = mount('<editor-sidebar slot="leading" collapsible><pane-ui></pane-ui></editor-sidebar>');
129
+ const pane = sb.querySelector('pane-ui');
130
+ pane.style.width = '240px';
131
+
132
+ // Simulate display:none via inline style; stub getComputedStyle to reflect it.
133
+ sb.style.display = 'none';
134
+ const origGCS = globalThis.getComputedStyle;
135
+ globalThis.getComputedStyle = (el, ps) => {
136
+ if (el === sb && el.style.display === 'none') return { display: 'none' };
137
+ return origGCS(el, ps);
138
+ };
139
+
140
+ // Fire the RO with a small-nonzero transient (48px = SNAP_THRESHOLD).
141
+ if (roCallback) roCallback([{ target: pane, contentRect: { width: 48 } }]);
142
+ await tick();
143
+
144
+ expect(sb.collapsed).toBe(false);
145
+ expect(sb.hasAttribute('collapsed')).toBe(false);
146
+
147
+ globalThis.ResizeObserver = savedRO;
148
+ globalThis.getComputedStyle = origGCS;
149
+ });
150
+
119
151
  it('handles missing inner <pane-ui> gracefully (no crash)', () => {
120
152
  const sb = mount('<editor-sidebar slot="leading"></editor-sidebar>');
121
153
  // Should not throw
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-modules",
3
- "version": "0.6.26",
3
+ "version": "0.6.27",
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": {