@celestia-island/hikari 0.55.138 → 0.55.139

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.55.138",
3
+ "version": "0.55.139",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari Vue 3 component library — production-grade UI components based on shittim-chest design system",
@@ -139,6 +139,26 @@ describe("HkPlaceholderMarquee", () => {
139
139
  expect(host.classList.contains("hk-placeholder-marquee--hidden")).toBe(false);
140
140
  });
141
141
 
142
+ it("keeps a sub-pixel-exact fit static instead of flipping to scroll", async () => {
143
+ // A shrink-wrapped host (window == text width, e.g. the status-bar
144
+ // version cells) compares a fractional copy width against the
145
+ // INTEGER clientWidth: without the epsilon, every fractional part
146
+ // < 0.5 made the predicate true and permanently scrolled a FITTING
147
+ // text (2026-10-11 R1 finding, reproduced in Chromium). The epsilon
148
+ // lets exact fits keep the hidden probe while real overflow (≥0.5px
149
+ // past the edge) still flips.
150
+ const { container } = mountMarquee({ text: "0.1.0 feat/model-usage-selfhosted:770f625" });
151
+ // Copy (incl. 24px spacing) lands 0.3px past the window: fits.
152
+ await forceGeometry(container, /* copy */ 224.3, /* window */ 200);
153
+ const host = container.querySelector(".hk-placeholder-marquee") as HTMLElement;
154
+ expect(host.classList.contains("hk-placeholder-marquee--hidden"), "0.3px past → still fits").toBe(true);
155
+ expect(container.querySelectorAll(".hk-placeholder-marquee__copy")).toHaveLength(1);
156
+ // 2px past the edge: real overflow, the strip takes over.
157
+ await forceGeometry(container, /* copy */ 226, /* window */ 200);
158
+ expect(host.classList.contains("hk-placeholder-marquee--hidden"), "2px past → scrolls").toBe(false);
159
+ expect(container.querySelectorAll(".hk-placeholder-marquee__copy")).toHaveLength(3);
160
+ });
161
+
142
162
  it("drives the sweep with CSS custom properties instead of per-frame JS", async () => {
143
163
  const { container } = mountMarquee({ text: "advent calendar of placeholders" });
144
164
  await forceGeometry(container, 500, 200);
@@ -79,7 +79,16 @@ export const HkPlaceholderMarquee = defineComponent({
79
79
  const factor = space.x > 0 ? space.x : 1;
80
80
  const copyWidth = copy.getBoundingClientRect().width / factor;
81
81
  loopWidth.value = copyWidth;
82
- overflowing.value = copyWidth - COPY_SPACING > host.clientWidth;
82
+ // Sub-pixel boundary (2026-10-11): the copy width is deliberately
83
+ // fractional while clientWidth is an integer. A shrink-wrapped
84
+ // host — window == text width, e.g. the status-bar version cells —
85
+ // degenerates the predicate to `textW > round(textW)`, which is
86
+ // TRUE for every fractional part < 0.5 and would permanently flip
87
+ // a FITTING text into scrolling (no size change → the
88
+ // ResizeObserver never re-fires; reproduced in Chromium). The
89
+ // epsilon keeps exact fits static; real overflow (≥0.5px past the
90
+ // edge) still flips.
91
+ overflowing.value = copyWidth - COPY_SPACING > host.clientWidth + 0.5;
83
92
  emit("overflowChange", overflowing.value);
84
93
  };
85
94
 
@@ -668,5 +668,14 @@ describe("HkStatusBar", () => {
668
668
  const popoverRule = /\.s-status-bar-popover-value\s*\{[^}]*\}/.exec(scss)?.[0] ?? "";
669
669
  expect(popoverRule, "popover value cell bound exists").toContain("max-width: min(16rem, 60vw)");
670
670
  expect(scss).toMatch(/\.s-status-bar-popover-value \.s-status-bar-version\s*\{[^}]*max-width:\s*100%/s);
671
+ // Dead-overlay guard: if the marquee's measurement never fires, a
672
+ // capped cell degrades to an ellipsis cut, never a silent hard clip.
673
+ expect(scss).toMatch(
674
+ /\.s-status-bar-version__static\s*\{[^}]*text-overflow:\s*ellipsis/s,
675
+ );
676
+ // The stale combined selector list (`.s-status-bar-version,
677
+ // .s-status-bar-version-sep { nowrap }`) is gone — the dedicated
678
+ // block owns the version cell's rules (2026-10-11 R1 cleanup).
679
+ expect(scss).not.toMatch(/\.s-status-bar-version,\s*\n\s*\.s-status-bar-version-sep\s*\{/);
671
680
  });
672
681
  });
@@ -199,9 +199,13 @@
199
199
  }
200
200
 
201
201
  .s-status-bar-version__static {
202
- /* The ghost state (marquee owns the window): opacity keeps the text
203
- in the layout and the a11y tree; only the paint is handed over. */
202
+ /* Dead-overlay guard: if the marquee's measurement never fires (RO
203
+ unavailable, zero-width probe), a capped cell must degrade to an
204
+ ellipsis cut, not a silent hard clip. Invisible while the marquee
205
+ owns the window (the ghost is opacity-0 by then). */
204
206
  min-width: 0;
207
+ overflow: hidden;
208
+ text-overflow: ellipsis;
205
209
  }
206
210
 
207
211
  .s-status-bar-version[data-overflowing] .s-status-bar-version__static {
@@ -258,12 +262,8 @@
258
262
  }
259
263
  }
260
264
 
261
- .s-status-bar-version,
262
265
  .s-status-bar-version-sep {
263
266
  white-space: nowrap;
264
- }
265
-
266
- .s-status-bar-version-sep {
267
267
  opacity: 0.5;
268
268
  }
269
269