chore: add lint rules, fix script\configs

This commit is contained in:
2026-10-08 04:51:15 +05:00
parent 76039f7bf3
commit 0e8c2478a1
32 changed files with 753 additions and 272 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
// Генерирует static/og/default.png — дефолтная OG-карточка 1200×630.
// Запуск: pnpm --dir web gen:og. Карточка рисуется в браузере (текст, иконки),
// per-tool карточки — этим же harness позже (docs/plan-seo.md S1e).
// Generates static/og/default.png: the default 1200x630 OG card.
// Run: pnpm --dir web gen:og. The card is rendered in a browser (text,
// icons); per-tool cards will reuse this harness (docs/plan-seo.md S1e).
import { mkdirSync } from "node:fs";
import { chromium } from "playwright";
+21 -13
View File
@@ -4,18 +4,10 @@ import { fileURLToPath } from "node:url";
const WEB_DIR = fileURLToPath(new URL("../", import.meta.url));
const node = process.execPath;
// Every lint layer of the redesign, in one command. Each step runs even if a
// previous one fails; the exit code is nonzero if any did.
//
// Stream wiring: children run with stdio ["ignore", 1, 1] — both stdout and
// stderr are bound to OUR stdout file descriptor (shell `2>&1`). It is real FD
// inheritance, no pipes or buffering, so `> css-lint.txt` catches every byte.
// Color decisions are then the tool's own against the actual destination:
// a terminal gets colors, a redirected file gets plain text.
//
// One quirk: stylelint prints its report to stderr and paints it even when the
// destination is not a TTY. So for a non-TTY destination we pass its official
// --no-color explicitly.
// Every lint layer in one command. Each step runs even if a previous one
// fails; the exit code is nonzero if any did. Children inherit our stdout FD
// directly (no pipes), so `> out.txt` catches every byte; stylelint paints
// even to a non-TTY, hence the explicit --no-color there.
const colorFlag = process.stdout.isTTY ? [] : ["--no-color"];
const bins = {
@@ -28,10 +20,26 @@ const bins = {
checkTokens: fileURLToPath(new URL("check-tokens.mjs", import.meta.url)),
};
// Warn-only rules with a non-zero baseline stay out of the gate until the
// cleanup lands (docs/backlog.md, tests-and-lint section): they nag in
// `pnpm lint` and editors, but --max-warnings=0 would make the gate red.
// After the cleanup the overrides are removed and the rules become errors.
const pendingCleanupOverrides = [
"conventions/ascii-only",
"conventions/comments-english",
"conventions/comment-format",
].flatMap((rule) => ["--rule", `${rule}: off`]);
const steps = [
{
name: "ESLint (svelte) + design-tokens rules",
args: [bins.eslint, ...colorFlag, "--max-warnings=0", "."],
args: [
bins.eslint,
...colorFlag,
"--max-warnings=0",
...pendingCleanupOverrides,
".",
],
},
{
name: "Stylelint (css) — design-token style",
+4 -13
View File
@@ -1,16 +1,7 @@
// postcss-hct: PostCSS plugin that lets design tokens be authored as hct() and
// emits plain sRGB hex colors.
//
// Supported syntax (Material Design 3 HCT; hue 0–360, chroma 0–~120, tone 0–100):
//
// --color-success: hct(155 40 55); // literal
// --color-muted: hct(from var(--brand-main) h 6 97); // take hue from seed
// --brand-alt: hct(from var(--brand-main) h c calc(t + 36)); // channel math
//
// `from` seeds are resolved from sibling custom properties in the same
// stylesheet (:root tokens plus the current rule's own tokens). Seeds may be
// #hex, oklch(), rgb() or another hct(). Anything the plugin cannot resolve is
// left as-is. The emitter is sRGB hex; HCT stays the single source of truth.
// postcss-hct: PostCSS plugin that lets design tokens be authored as hct()
// (literal, `from var(...)` seed, channel math) and emits sRGB hex. `from`
// seeds resolve from sibling custom properties (#hex, oklch(), rgb(), hct());
// anything unresolvable is left as-is. HCT stays the single source of truth.
import { Color } from "@panmdaa/colors";
+4 -4
View File
@@ -1,6 +1,6 @@
// Статический сервер для e2e (Playwright webServer). Раздаёт web/build
// (adapter-static): /foo -> foo.html | foo/index.html, MIME по расширению.
// Запуск: pnpm build && node scripts/serve-static.mjs --port 4173
// Static server for e2e (Playwright webServer). Serves web/build
// (adapter-static): /foo -> foo.html | foo/index.html, MIME by extension.
// Run: pnpm build && node scripts/serve-static.mjs --port 4173
import { createServer } from "node:http";
import { readFileSync, statSync } from "node:fs";
import { extname, join } from "node:path";
@@ -56,7 +56,7 @@ function serve(req, res) {
res.end(body);
return;
} catch {
/* not found — try next candidate */
/* not found - try next candidate */
}
}
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
+3 -5
View File
@@ -1,9 +1,7 @@
// Color authorship: every color value in app.css must be authored as hct()
// — literals AND derived forms (hct(from var(...) h c t) with channel math) —
// so the whole palette is computed through HCT channels and output as sRGB by
// the postcss-hct plugin. Exceptions: the seed tokens --brand-main/--brand-alt
// (any format allowed) and color-mix(...) (the only sanctioned way to blend
// two tokens).
// (literals and derived forms), so the whole palette is computed through HCT
// and output as sRGB by postcss-hct. Exceptions: the seed tokens
// --brand-main/--brand-alt and color-mix(...) (the only sanctioned blend).
import { isColorValue } from "./helpers.mjs";
+2 -2
View File
@@ -41,14 +41,14 @@ export function collectUsedTokens() {
return used;
}
// A token is "colorful" when its value is a color literal — those must have a
// A token is "colorful" when its value is a color literal; those must have a
// dark-mate. Non-color tokens (fonts, radii, durations) are exempt.
const COLOR_RE =
/#[0-9a-fA-F]{3,8}\b|\b(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklch|oklab|hct|color-mix)\s*\(/i;
export const isColorValue = (value) => COLOR_RE.test(value);
// A token is "derived" when its value references var(...) — it adapts to the
// A token is "derived" when its value references var(...); it adapts to the
// theme automatically, so it must NOT have a hardcoded dark twin.
export const isDerived = (value) => value.includes("var(");
+2 -5
View File
@@ -1,9 +1,6 @@
// Unresolved references: every var(--x) in app.css must point at a token the
// same file defines. A typo here silently drops the declaration, so unlike the
// unused-token dust it is a defect, not a style choice.
//
// `var(--x, fallback)` is legal without --x (CSS custom property fallback), so
// those references are exempt.
// same file defines: a typo silently drops the declaration, so this is a
// defect, not a style choice. `var(--x, fallback)` is legal without --x.
const VAR_REF_RE = /var\(\s*(--[\w-]+)\s*([,)])/g;
+1 -1
View File
@@ -1,5 +1,5 @@
// Unused tokens: defined in app.css but never referenced via var()
// anywhere in src. A dead token is not the single source of truth — it's dust.
// anywhere in src. A dead token is not the single source of truth; it's dust.
// Reported as a warning; it does not fail the run. The `used` set is injectable
// so tests can pass a fixture instead of scanning src.