fix(ui): the platform layer speaks platform words; boxes scale with the text

- PriorityStepper labelled itself with channels.row.* and told every caller
  it orders the Feed; Combobox said plex.loading / plex.filter.moreRefine /
  sidebar.match on the feed sidebar. The generic strings moved to common.*,
  and what a priority MEANS arrives as the stepper's `hint` prop.
  uiI18n.contract.test.ts pins components/ui/ to the platform namespaces —
  the import boundary could never see an i18n key.
- 32 px-frozen box utilities (w-[150px], max-w-[1600px], left-[18px], …)
  became spacing-scale steps (w-37.5, max-w-400, left-4.5): identical at the
  default text size, and now they grow with the Settings slider instead of
  clipping or re-wrapping their own labels. An eslint rule refuses the px
  form beside the older text-[Npx] one, pinned by pxBox.contract.test.ts;
  measured in the browser that every new step is generated at its old px.
- chipLane's import-strip regex ran past a side-effect import into the next
  statement's `from`, hiding a lane between them; it now stops at the
  import's own `;`.
This commit is contained in:
2026-09-19 07:46:33 +02:00
parent 29875b7367
commit 8018cd4685
36 changed files with 223 additions and 75 deletions
+13 -1
View File
@@ -30,7 +30,10 @@ function count(haystack: string, needle: string): number {
* lane in a file that already had a compliant one still passed, which is the exact hole this test
* was rewritten to close (E10 review round 2, caught by mutation). */
function laneCount(src: string): number {
return count(src.replace(/^\s*import[\s\S]*?from\s+"[^"]*";$/gm, ""), "chipLaneClass");
// `[^;]` rather than `[\s\S]`: an import is stripped up to ITS OWN `from "…";`. The lazy any-char
// form ran past a side-effect import (`import "./x.css";` has no `from`) into the next statement's
// `from`, swallowing whatever lay between — a lane there would be uncounted.
return count(src.replace(/^\s*import\s[^;]*?\sfrom\s+"[^"]*";$/gm, ""), "chipLaneClass");
}
/** Every source file under src/, not just src/components/ — App.tsx and main.tsx sit above it. */
@@ -55,6 +58,15 @@ function withMirror(): Array<[string, string]> {
}
describe("chip lane containment contract", () => {
it("strips imports without swallowing what follows a side-effect import", () => {
const src = [
'import "./x.css";',
"const lane = chipLaneClass;",
'import { chipLaneClass } from "./chipLane";',
].join("\n");
expect(laneCount(src)).toBe(1);
});
it("keeps all three halves of the lane class — offsetParent, clip, and room for the focus ring", () => {
expect(chipLaneClass.split(/\s+/)).toEqual(
expect.arrayContaining(["relative", "overflow-clip", "[overflow-clip-margin:0.25rem]"]),
+26
View File
@@ -0,0 +1,26 @@
import { fileURLToPath } from "node:url";
import { ESLint } from "eslint";
import { describe, expect, it } from "vitest";
// The px-frozen BOX rule in eslint.config.js (beside the older `text-[Npx]` one). A `no-restricted-
// syntax` selector that matches nothing looks exactly like a clean codebase, so pin that it fires.
const FRONTEND = fileURLToPath(new URL("../../", import.meta.url));
async function refused(source: string): Promise<number> {
const eslint = new ESLint({ cwd: FRONTEND });
const [result] = await eslint.lintText(source, { filePath: "src/components/__probe.tsx" });
return (result?.messages ?? []).filter((m) => m.message.startsWith("px-frozen box size")).length;
}
describe("a px-frozen box utility", () => {
it("is refused in a string and in a template literal", async () => {
expect(await refused('export const a = "group w-[150px] shrink-0";\n')).toBe(1);
expect(await refused("export const b = `x ${1} max-w-[240px]`;\n")).toBe(1);
});
it("leaves the spacing scale, a rem arbitrary and a grid track alone", async () => {
expect(await refused('export const c = "w-37.5 max-w-60 text-[0.75rem]";\n')).toBe(0);
expect(await refused('export const d = "grid-cols-[1fr_240px]";\n')).toBe(0);
});
});