diff --git a/package.json b/package.json
index 47574ec74..3af983163 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@mirrorstack-ai/web-ui-kit",
"packageManager": "pnpm@10.29.3",
- "version": "0.5.13",
+ "version": "0.5.14",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
diff --git a/src/components/ui/surfaces/notch/Notch.test.tsx b/src/components/ui/surfaces/notch/Notch.test.tsx
index 44c6bd566..08a015e8b 100644
--- a/src/components/ui/surfaces/notch/Notch.test.tsx
+++ b/src/components/ui/surfaces/notch/Notch.test.tsx
@@ -50,6 +50,19 @@ describe("Notch", () => {
expect(svg).toHaveAttribute("viewBox", "-0.5 -0.5 67 47");
});
+ it("bleeds the headOnly connecting edge to avoid a fractional-DPI seam", () => {
+ const { container } = render(
+ ,
+ );
+ const svg = container.querySelector("svg");
+ // The flat connecting edge (build x=0) is extended to x=-1 so the tab fill
+ // OVERLAPS the surface below rather than abutting it edge-to-edge — otherwise
+ // the shared boundary aliases into a 1px seam on non-integer DPR. overflow
+ // must be visible so that 1px bleed isn't clipped to the box.
+ expect(svg).toHaveAttribute("overflow", "visible");
+ expect(svg?.querySelector("path")?.getAttribute("d")).toMatch(/^M -1,/);
+ });
+
it("applies fill and stroke", () => {
const { container } = render(
,
diff --git a/src/components/ui/surfaces/notch/Notch.tsx b/src/components/ui/surfaces/notch/Notch.tsx
index 518407222..637ec853a 100644
--- a/src/components/ui/surfaces/notch/Notch.tsx
+++ b/src/components/ui/surfaces/notch/Notch.tsx
@@ -80,17 +80,34 @@ function buildPath(
return d.join(" ");
}
-function buildHeadPath(nw: number, nh: number, r: number, ir: number, atStart: boolean, atEnd: boolean) {
+// A headOnly tab sits flush on the surface below it. Extending its fill 1px past
+// that flat CONNECTING edge so the two same-colour fills OVERLAP (rather than abut
+// edge-to-edge) removes a sub-pixel seam: when the shared boundary lands on a
+// fractional device-pixel row — sub-pixel transforms or non-integer DPR such as
+// Windows display scaling — each fill paints <100% coverage on that physical row
+// and the layer beneath shows through as a 1px line. A whole-pixel overlap closes
+// the gap. (Invisible on integer-DPR/retina, where the edges snap and abut cleanly.)
+const HEAD_SEAM_BLEED = 1;
+
+function buildHeadPath(
+ nw: number, nh: number, r: number, ir: number,
+ atStart: boolean, atEnd: boolean,
+ // px to extend the flat connecting edge (build x = 0) past the box, toward the
+ // surface the tab attaches to. Only the build-x=0 edge moves; the radiused tab
+ // corners (build x = w, the tab TOP) are untouched. `bleed = 0` ⇒ original path.
+ bleed = 0,
+) {
const w = nw + ir;
const topIr = atStart ? 0 : ir;
const botIr = atEnd ? 0 : ir;
+ const x0 = -bleed;
const d: string[] = [];
if (!atStart) {
- d.push(`M 0,0`);
+ d.push(`M ${x0},0`);
d.push(`A ${ir},${ir} 0 0,0 ${ir},${topIr}`);
} else {
- d.push(`M 0,${topIr}`);
+ d.push(`M ${x0},${topIr}`);
}
d.push(`H ${w - r}`);
@@ -100,7 +117,7 @@ function buildHeadPath(nw: number, nh: number, r: number, ir: number, atStart: b
d.push(`H ${ir}`);
if (!atEnd) {
- d.push(`A ${ir},${ir} 0 0,0 0,${topIr + nh + botIr}`);
+ d.push(`A ${ir},${ir} 0 0,0 ${x0},${topIr + nh + botIr}`);
}
d.push(`V 0`);
@@ -188,7 +205,7 @@ export function Notch({
const botIr = atEnd ? 0 : ir;
const pathW = notchWidth + ir;
const pathH = notchHeight + topIr + botIr;
- const headPath = buildHeadPath(notchWidth, notchHeight, radius, ir, atStart, atEnd);
+ const headPath = buildHeadPath(notchWidth, notchHeight, radius, ir, atStart, atEnd, HEAD_SEAM_BLEED);
let svgW: number, svgH: number;
if (horiz) { svgW = pathW; svgH = pathH; }
@@ -201,10 +218,13 @@ export function Notch({
// every side so the centered stroke renders fully INSIDE the box. (Sizing
// the element to svgW+strokeWidth instead pins it `pad` larger and pushes
// the right & bottom strokes outside the box.)
+ // `overflow: visible` lets the HEAD_SEAM_BLEED extend the fill 1px past the
+ // box on the connecting edge without being clipped to the viewport.