From cdd09372b2fc86c63998cadfb3947a69c5ed2629 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 24 Oct 2026 12:17:24 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=E2=80=94=20pf=2089?= =?UTF-8?q?=EB=B0=94=ED=80=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 84e4521401aa5bb21b0b29b91ff97ab4b3b5458d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 24 Oct 2026 12:32:38 +0000 Subject: [PATCH 2/3] web: drop the 13 Pattern Lab CSS classes no component uses Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NxHZJYQmJBPASBcBvdaLbJ --- web/src/app/pattern-lab/PatternLab.module.css | 193 +----------------- .../panels/CapturePanel.module.css | 33 --- .../panels/DirectorPanel.module.css | 6 - 3 files changed, 6 insertions(+), 226 deletions(-) diff --git a/web/src/app/pattern-lab/PatternLab.module.css b/web/src/app/pattern-lab/PatternLab.module.css index d7c66f102..09ea3c7b8 100644 --- a/web/src/app/pattern-lab/PatternLab.module.css +++ b/web/src/app/pattern-lab/PatternLab.module.css @@ -88,57 +88,16 @@ color: #171512; } -.header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 18px; - padding-bottom: 18px; - border-bottom: 1px solid rgba(23, 21, 18, 0.18); -} - -.headerMeta, -.stats, -.actionRow { +.stats { display: flex; align-items: center; flex-wrap: wrap; - gap: 10px; -} - -.headerMeta, -.stats { + gap: 8px; color: rgba(23, 21, 18, 0.62); font-family: var(--font-jetbrains), monospace; font-size: 12px; line-height: 1.45; - text-transform: uppercase; -} - -.previewHeader { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - margin-bottom: 16px; -} - -.editorHeader { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 16px; - margin-bottom: 16px; -} - -.stats { - display: flex; text-transform: none; - align-items: center; - gap: 8px; - color: rgba(23, 21, 18, 0.62); - font-family: var(--font-jetbrains), monospace; - font-size: 12px; } .dotSep { @@ -248,116 +207,12 @@ overflow-wrap: anywhere; } -.actionRow { - margin-top: 12px; -} - -.actionRow button, -.editorHeader button { - min-height: 38px; - border: 1px solid rgba(23, 21, 18, 0.22); - background: #ffffff; - color: #171512; - padding: 0 12px; - font: inherit; - font-family: var(--font-jetbrains), monospace; - font-size: 12px; - text-transform: uppercase; - cursor: pointer; -} - -.actionRow button:hover, -.editorHeader button:hover { - border-color: #171512; -} - -.darkButton, -.editorHeader button { +.darkButton { background: #171512 !important; color: #f7f3ea !important; border-color: #171512 !important; } -.editorHeader .guideButton { - background: #e8dcc2 !important; - color: #171512 !important; - border-color: #171512 !important; -} - -.editorHeader .guideButton:hover { - background: #d9c8a4 !important; -} - -.editorHeader .pasteButton { - display: none; - background: #e8552e !important; - color: #ffffff !important; - border-color: #e8552e !important; - font-weight: 600; -} - -.editorHeader .pasteButton:hover { - background: #d44722 !important; - border-color: #d44722 !important; -} - -.editorHeader .clearButton { - display: none; - background: #ffffff !important; - color: #171512 !important; - border: 1px solid rgba(23, 21, 18, 0.3) !important; -} - -.editorHeader .clearButton:hover { - background: #f0eae1 !important; - border-color: #171512 !important; -} - -.editorHeader { - flex: 0 0 auto; - min-height: 64px; - margin: 0; - padding: 12px 14px; - border-bottom: 1px solid rgba(23, 21, 18, 0.18); -} - -.editorHeader span { - display: block; - font-size: 14px; - font-weight: 700; -} - -.editorHeader button:disabled { - opacity: 0.5; - cursor: default; -} - -.viewToggle { - display: flex; - gap: 0; -} - -.editorHeader .viewToggle button { - min-height: 32px; - border: 1px solid #171512 !important; - background: #ffffff !important; - color: #171512 !important; - padding: 0 14px; - font-family: var(--font-jetbrains), monospace; - font-size: 12px; - text-transform: uppercase; - cursor: pointer; -} - -.editorHeader .viewToggle button + button { - border-left: none !important; -} - -.editorHeader .viewToggle button[data-active="true"] { - background: #171512 !important; - color: #f7f3ea !important; -} - .variantActions { display: flex; gap: 8px; @@ -503,46 +358,10 @@ } @media (max-width: 980px) { - .header, - .previewHeader, - .editorHeader { - align-items: flex-start; - flex-direction: column; - } - - .previewHeader { - gap: 10px; - margin-bottom: 12px; - } - - .editorHeader button { - min-height: 36px; - padding: 0 10px; - font-size: 11px; - } - - .editorHeader .pasteButton, - .editorHeader .clearButton { - display: inline-flex; - } - .stats { - justify-content: flex-start; - text-align: left; - } - - /* Action buttons become full-width rows — easy to hit, nothing truncated. */ - .actionRow { - display: grid; - grid-template-columns: 1fr; - gap: 8px; - } - - .actionRow button { - width: 100%; - min-height: 44px; - } - + justify-content: flex-start; + text-align: left; + } } /* ── Community fork badge ── */ diff --git a/web/src/app/pattern-lab/panels/CapturePanel.module.css b/web/src/app/pattern-lab/panels/CapturePanel.module.css index 44331107e..47f67f0dd 100644 --- a/web/src/app/pattern-lab/panels/CapturePanel.module.css +++ b/web/src/app/pattern-lab/panels/CapturePanel.module.css @@ -107,39 +107,6 @@ overflow: auto; } -.stageBox { - flex: 1 1 auto; - min-height: 120px; - min-width: 0; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - background: #1a1816; - border: 1px solid #171512; -} - -/* The canvas is sized by its own bitmap and only ever scaled DOWN to fit — - a replaced element keeps its ratio under max-width/max-height. */ -.stageCanvas { - display: block; - width: auto; - height: auto; - max-width: 100%; - max-height: 100%; - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); -} - -/* Transparency shows as the usual checkerboard, under the picture only. */ -.stageCanvasTransparent { - background-color: #d9d4ca; - background-image: - linear-gradient(45deg, #bdb7aa 25%, transparent 25%, transparent 75%, #bdb7aa 75%), - linear-gradient(45deg, #bdb7aa 25%, transparent 25%, transparent 75%, #bdb7aa 75%); - background-size: 16px 16px; - background-position: 0 0, 8px 8px; -} - /* The auto look's verdict, full width under the output controls. */ .verdict { flex: 1 1 100%; diff --git a/web/src/app/pattern-lab/panels/DirectorPanel.module.css b/web/src/app/pattern-lab/panels/DirectorPanel.module.css index 2d46a534b..9e6c88f5d 100644 --- a/web/src/app/pattern-lab/panels/DirectorPanel.module.css +++ b/web/src/app/pattern-lab/panels/DirectorPanel.module.css @@ -229,9 +229,3 @@ color: #dc2626; font-weight: 600; } - -.hint { - padding: 10px 12px; - font-size: 12px; - color: #777; -} From 93e4845fc9f542cab9b4175a52768abed1ecffc9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 24 Oct 2026 12:36:35 +0000 Subject: [PATCH 3/3] web: fail the Lab CSS test when a module defines a class nothing uses Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NxHZJYQmJBPASBcBvdaLbJ --- web/src/app/pattern-lab/labCss.test.ts | 39 +++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/web/src/app/pattern-lab/labCss.test.ts b/web/src/app/pattern-lab/labCss.test.ts index 9accfc178..3549ab2e4 100644 --- a/web/src/app/pattern-lab/labCss.test.ts +++ b/web/src/app/pattern-lab/labCss.test.ts @@ -9,7 +9,7 @@ // On the same screen Del was 37×28px, the layer eye 20×17px and Push 40×22px, // and the Pixel status bar was 51px, two lines with the mouse gestures. -import { readFileSync } from "node:fs"; +import { readFileSync, readdirSync } from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; import { describe, expect, it } from "vitest"; @@ -180,3 +180,40 @@ describe("Pixel status line on a touch screen", () => { expect(mediaRule(dock, "(hover: none)", ".pixelStatus")).toBe(""); }); }); + +describe("Lab CSS modules", () => { + // Until 24 October 2026 these files held 13 classes that no component put + // on an element, and 220 lines of rules for them. A class counts as used + // when a file importing its module reads `styles.name` or `styles["name"]`, + // or a rule in the same file composes it. A module read by a computed name + // counts as used whole. + const src = path.resolve(__dirname, "../.."); + const sources = (readdirSync(src, { recursive: true }) as string[]) + .filter((file) => /\.tsx?$/.test(file) && !/\.test\.tsx?$/.test(file)) + .map((file) => path.join(src, file)) + .map((file) => ({ file, text: readFileSync(file, "utf8") })); + const modules = (readdirSync(__dirname, { recursive: true }) as string[]).filter((file) => file.endsWith(".module.css")); + + it("define no class that no component uses", () => { + const unused: Record = {}; + for (const name of modules) { + const file = path.join(__dirname, name); + const bare = css(name).replace(/\/\*[\s\S]*?\*\//g, "").replace(/"[^"]*"|'[^']*'/g, ""); + const defined = new Set([...bare.matchAll(/\.(-?[_a-zA-Z][\w-]*)/g)].map((m) => m[1])); + const used = new Set([...bare.matchAll(/composes:([^;]+);/g)].flatMap((m) => m[1].trim().split(/\s+/))); + let computed = false; + for (const { file: from, text } of sources) { + for (const [, local, spec] of text.matchAll(/import (\w+) from "([^"]+\.module\.css)"/g)) { + const target = spec.startsWith("@/") ? path.join(src, spec.slice(2)) : path.resolve(path.dirname(from), spec); + if (target !== file) continue; + for (const m of text.matchAll(new RegExp(`\\b${local}(?:\\.(\\w+)|\\[["'\`](\\w+)["'\`]\\])`, "g"))) used.add(m[1] ?? m[2]); + if (new RegExp(`\\b${local}\\[(?!["'\`])`).test(text)) computed = true; + } + } + const dead = computed ? [] : [...defined].filter((cls) => !used.has(cls)).sort(); + if (dead.length) unused[name] = dead; + } + expect(modules.length).toBeGreaterThan(0); + expect(unused).toEqual({}); + }); +});