From 8908c33e61e5caeaec0a3b8c8deb23ff4bc3503c Mon Sep 17 00:00:00 2001 From: noah Date: Wed, 5 Aug 2026 18:50:03 -0400 Subject: [PATCH 1/2] fix(fork): sidebar brand mark is no longer blurry The mark's art is a 23x23 pixel grid, but the asset shipped as a 24x24 PNG. 23 does not divide into 24, so the export resampled every cell with smoothing and baked the blur into the file: 562 of its 576 pixels were not in the art's 9-colour palette, and it held 136 distinct colours. `image-rendering: pixelated` then enlarged that mush into hard blocks on retina rather than sharpening it. The export had also flattened Figma's #1E1E1E canvas into the bitmap, which is the dark square behind the mark. Rebuilt the mark as a vector from the Figma node: integer coordinates on a 0 0 23 23 viewBox with shape-rendering="crispEdges", so cells stay hard-edged at any size and DPR. Dropped the now-pointless image-rendering hack, and the mark composites onto the sidebar instead of carrying a backdrop. Figma exports these rects under rotate()/matrix() transforms, so positions came from transforming each rect's corners; all 340 landed exactly on the grid, and sampling every cell of Figma's own 960px render matched all 529 cells. The guard previously asserted the PNG was 24x24, which encoded the defect. It now pins the vector invariants instead. Co-Authored-By: Claude Opus 5 (1M context) --- .fork/customizations.yaml | 2 +- .../__fork_guards__/forkSidebarChrome.test.ts | 20 ++++++++++++++---- .../src/components/sidebar/SidebarChrome.tsx | 13 ++++++------ .../src/custom/assets/sidebar-brand-mark.png | Bin 1093 -> 0 bytes .../src/custom/assets/sidebar-brand-mark.svg | 11 ++++++++++ 5 files changed, 34 insertions(+), 12 deletions(-) delete mode 100644 apps/web/src/custom/assets/sidebar-brand-mark.png create mode 100644 apps/web/src/custom/assets/sidebar-brand-mark.svg diff --git a/.fork/customizations.yaml b/.fork/customizations.yaml index 8b850468e963..f6a1956534bd 100644 --- a/.fork/customizations.yaml +++ b/.fork/customizations.yaml @@ -1083,7 +1083,7 @@ - apps/web/src/custom/SidebarV2ChromeRows.tsx - apps/web/src/custom/useScrollGutterWidth.ts - apps/web/src/custom/SidebarStageDitherArt.tsx - - apps/web/src/custom/assets/sidebar-brand-mark.png + - apps/web/src/custom/assets/sidebar-brand-mark.svg - apps/web/src/custom/assets/sidebar-stage-dither-dev.png shadows: [] watch: diff --git a/apps/web/src/__fork_guards__/forkSidebarChrome.test.ts b/apps/web/src/__fork_guards__/forkSidebarChrome.test.ts index bcf08fe6f7ab..8404b9c661d4 100644 --- a/apps/web/src/__fork_guards__/forkSidebarChrome.test.ts +++ b/apps/web/src/__fork_guards__/forkSidebarChrome.test.ts @@ -182,13 +182,25 @@ describe("fork guard: fork-sidebar-chrome", () => { expect(chrome).toMatch(/sidebar-brand[^"]*ml-auto/u); expect(chrome).not.toContain("ml-[var(--workspace-titlebar-content-left)]"); expect(chrome).toMatch(/sidebar-brand[^"]*text-sidebar-foreground/u); - expect(chrome).toContain("size-6 shrink-0 [image-rendering:pixelated]"); + expect(chrome).toContain("size-6 shrink-0"); expect(chrome).toContain("text-[0.875rem] leading-4 font-medium"); + // The mark's art is a 23x23 pixel grid drawn at a 24px slot. A bitmap can + // only get there by resampling 23 cells onto 24 pixels, which blends every + // cell into its neighbours — the blur the vector mark replaced. Keep it + // vector, keep the viewBox on the 23-unit grid, and keep crispEdges so the + // cells stay hard-edged at every DPR instead of antialiasing back to mush. const mark = NodeFS.readFileSync( - NodeURL.fileURLToPath(new URL("../custom/assets/sidebar-brand-mark.png", import.meta.url)), + NodeURL.fileURLToPath(new URL("../custom/assets/sidebar-brand-mark.svg", import.meta.url)), + "utf8", ); - expect(mark.readUInt32BE(16)).toBe(24); - expect(mark.readUInt32BE(20)).toBe(24); + expect(mark).toContain('viewBox="0 0 23 23"'); + expect(mark).toContain('shape-rendering="crispEdges"'); + expect(mark).not.toMatch(/image-rendering| { diff --git a/apps/web/src/components/sidebar/SidebarChrome.tsx b/apps/web/src/components/sidebar/SidebarChrome.tsx index 0f01ec5e9fc3..9db38a1f5f8c 100644 --- a/apps/web/src/components/sidebar/SidebarChrome.tsx +++ b/apps/web/src/components/sidebar/SidebarChrome.tsx @@ -11,7 +11,7 @@ import { } from "../SidebarStageBackdrop"; import { Badge } from "../ui/badge"; /* fork:begin fork-sidebar-chrome — see .fork/customizations.yaml#fork-sidebar-chrome */ -import sidebarBrandMarkUrl from "~/custom/assets/sidebar-brand-mark.png"; +import sidebarBrandMarkUrl from "~/custom/assets/sidebar-brand-mark.svg"; /* fork:end fork-sidebar-chrome */ import { SidebarFooter, @@ -92,12 +92,11 @@ function SidebarBrand() { {/* fork:begin fork-app-identity — see .fork/customizations.yaml#fork-app-identity The borrowed T3 wordmark stays gone. The fork's own exported mark and APP_BASE_NAME form the exact 24px / 4px / 14px lockup from the design, - while the injected desktop name and bridge-less fallback still agree. */} - + while the injected desktop name and bridge-less fallback still agree. + The mark is vector: its art is a 23x23 pixel grid, which no bitmap can + resample into a 24px box without smearing every cell. crispEdges lives + in the asset, so the grid stays hard-edged at any size or DPR. */} + {APP_BASE_NAME} {/* fork:end fork-app-identity */} diff --git a/apps/web/src/custom/assets/sidebar-brand-mark.png b/apps/web/src/custom/assets/sidebar-brand-mark.png deleted file mode 100644 index a131487d08e513becea6aea9c3364e192a0580a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1093 zcmV-L1iJf)P)y z**h zm_h5`Dzx)uHg`5U$bU8sYenDj>>~!K%MWg47zW&5OArzw;Qa_E#B zFijJ;OUq0z2m(gO#-D6}Ce8Thb$GJK=KgvOrsV|VW&`fcCXz=L*u;bOLJgef&l*_7r(RDztOL>`AT z5oorHj|&pM+=ssE5=d8`reW!P_!2hLp$kVHII7T1WOIPoIIru(-n zyc;9Y9g|Sf-M+0{#0Q@-f$Q5zro=_LrMYVu-m8RPq-X$0|D#l2WC7BXe`qDD^%cLs}3#jPG-IKPOAQWX;<{wumeZttLwZNR(#H zGU`_Yx;{~qFy0Qr$w?lBoWaHolVjk$W!l#Vbiw}fMnDk=8f}jnK|vL8YDU6e`Zg$e z4$dvKP;Yx!D0z^ipbtb*BscG8SZ>gRLp$`_FBHs>+CKAr74uh3rvKzv4DY=m;mWd& zxgRWyPZXa75sr&~{G-i43rY~W!$lGE3ntQu9oc>`UDG;l>OMYwCxO{%sjELhZhz`{ z9E}bKo)@0oKrTOu)8Do5(|;~BpOi1hF@2pFJ$KQ-*+*zt3-B z*NBKp6EK?=?ri!DJj|2Hh5f7Oee_eQ)Qf%esnBrIGNke}zb*FxYk+w%!xoRL00000 LNkvXXu0mjf@00W# diff --git a/apps/web/src/custom/assets/sidebar-brand-mark.svg b/apps/web/src/custom/assets/sidebar-brand-mark.svg new file mode 100644 index 000000000000..a5fe465a51b5 --- /dev/null +++ b/apps/web/src/custom/assets/sidebar-brand-mark.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + From 926855dab8827fa0eeb7f9041fad33746fea2300 Mon Sep 17 00:00:00 2001 From: noah Date: Wed, 5 Aug 2026 22:32:04 -0400 Subject: [PATCH 2/2] fix(fork): brand-mark guard actually guards the defect it exists for MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pixelated-hack check now covers the component, where the hack lived, not just the asset. The integer-grid loop can no longer pass vacuously: it asserts a coordinate count and pins the geometry to plain rect attributes, so an svgo pass or re-export routing geometry through transforms or path data fails instead of hiding. The lockup comment stops claiming cell uniformity at every DPR — the 23-into-24 center line at DPR 1 is a recorded trade, not an oversight. Co-Authored-By: Claude Fable 5 --- .../src/__fork_guards__/forkSidebarChrome.test.ts | 14 ++++++++++++-- apps/web/src/components/sidebar/SidebarChrome.tsx | 5 ++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/web/src/__fork_guards__/forkSidebarChrome.test.ts b/apps/web/src/__fork_guards__/forkSidebarChrome.test.ts index 8404b9c661d4..7a81b85a0171 100644 --- a/apps/web/src/__fork_guards__/forkSidebarChrome.test.ts +++ b/apps/web/src/__fork_guards__/forkSidebarChrome.test.ts @@ -184,6 +184,9 @@ describe("fork guard: fork-sidebar-chrome", () => { expect(chrome).toMatch(/sidebar-brand[^"]*text-sidebar-foreground/u); expect(chrome).toContain("size-6 shrink-0"); expect(chrome).toContain("text-[0.875rem] leading-4 font-medium"); + // The pixelated hack lived on the component, not the asset — a substring + // check on the class list would not notice it coming back beside it. + expect(chrome).not.toContain("image-rendering"); // The mark's art is a 23x23 pixel grid drawn at a 24px slot. A bitmap can // only get there by resampling 23 cells onto 24 pixels, which blends every // cell into its neighbours — the blur the vector mark replaced. Keep it @@ -197,10 +200,17 @@ describe("fork guard: fork-sidebar-chrome", () => { expect(mark).toContain('shape-rendering="crispEdges"'); expect(mark).not.toMatch(/image-rendering| check pins the + // geometry to plain rect attributes — an svgo pass or Figma re-export that + // moves it into transforms or path data would otherwise hide fractional + // coordinates from this guard entirely. + const coords = [...mark.matchAll(/\s(?:x|y|width|height)="([^"]+)"/gu)]; + expect(coords.length).toBeGreaterThan(100); + for (const coord of coords) { expect(coord[1]).toMatch(/^\d+$/u); } + expect(mark).not.toMatch(/transform=| { diff --git a/apps/web/src/components/sidebar/SidebarChrome.tsx b/apps/web/src/components/sidebar/SidebarChrome.tsx index 9db38a1f5f8c..39dd976f31cc 100644 --- a/apps/web/src/components/sidebar/SidebarChrome.tsx +++ b/apps/web/src/components/sidebar/SidebarChrome.tsx @@ -95,7 +95,10 @@ function SidebarBrand() { while the injected desktop name and bridge-less fallback still agree. The mark is vector: its art is a 23x23 pixel grid, which no bitmap can resample into a 24px box without smearing every cell. crispEdges lives - in the asset, so the grid stays hard-edged at any size or DPR. */} + in the asset, so cells stay hard-edged at every DPR. The 23-into-24 fit + still renders the center row and column of cells 2px at DPR 1 — one + heavy line, accepted over either blurring all of them or drawing the + art at 23px, which centers on a half-pixel. */} {APP_BASE_NAME} {/* fork:end fork-app-identity */}