@bastani/atomic 0.9.11-alpha.6 → 0.9.11-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/impeccable/SKILL.md +33 -129
- package/dist/builtin/workflows/skills/impeccable/agents/impeccable_asset_producer.toml +11 -3
- package/dist/builtin/workflows/skills/impeccable/agents/impeccable_documenter.toml +26 -0
- package/dist/builtin/workflows/skills/impeccable/agents/impeccable_finish_reviewer.toml +35 -0
- package/dist/builtin/workflows/skills/impeccable/reference/android.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/animate.md +72 -189
- package/dist/builtin/workflows/skills/impeccable/reference/audit.md +10 -9
- package/dist/builtin/workflows/skills/impeccable/reference/audit.native.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/bolder.md +19 -108
- package/dist/builtin/workflows/skills/impeccable/reference/clarify.md +59 -253
- package/dist/builtin/workflows/skills/impeccable/reference/colorize.md +51 -222
- package/dist/builtin/workflows/skills/impeccable/reference/craft-floor.md +45 -0
- package/dist/builtin/workflows/skills/impeccable/reference/craft.md +3 -121
- package/dist/builtin/workflows/skills/impeccable/reference/critique.md +28 -20
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/asset-producer.md +97 -0
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/documenter.md +23 -0
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/finish-reviewer.md +32 -0
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/dist/builtin/workflows/skills/impeccable/reference/delight.md +47 -279
- package/dist/builtin/workflows/skills/impeccable/reference/distill.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/doctor.md +53 -0
- package/dist/builtin/workflows/skills/impeccable/reference/document.md +60 -73
- package/dist/builtin/workflows/skills/impeccable/reference/harden.md +1 -12
- package/dist/builtin/workflows/skills/impeccable/reference/hooks.md +17 -4
- package/dist/builtin/workflows/skills/impeccable/reference/init.md +64 -163
- package/dist/builtin/workflows/skills/impeccable/reference/ios.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/layout.md +52 -153
- package/dist/builtin/workflows/skills/impeccable/reference/live.md +44 -32
- package/dist/builtin/workflows/skills/impeccable/reference/new-work.md +105 -0
- package/dist/builtin/workflows/skills/impeccable/reference/{product.md → operate.md} +6 -5
- package/dist/builtin/workflows/skills/impeccable/reference/optimize.md +4 -4
- package/dist/builtin/workflows/skills/impeccable/reference/overdrive.md +1 -4
- package/dist/builtin/workflows/skills/impeccable/reference/polish.md +68 -212
- package/dist/builtin/workflows/skills/impeccable/reference/quieter.md +3 -3
- package/dist/builtin/workflows/skills/impeccable/reference/routing.md +18 -0
- package/dist/builtin/workflows/skills/impeccable/reference/shape.md +38 -144
- package/dist/builtin/workflows/skills/impeccable/reference/typeset.md +48 -269
- package/dist/builtin/workflows/skills/impeccable/reference/visualize.md +38 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/command-metadata.json +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/concept-seed.mjs +584 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +117 -9
- package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +486 -59
- package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +16 -45
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +96 -10
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +143 -26
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +181 -12
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +3187 -182
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +102 -7
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +282 -70
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +183 -15
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +22 -7
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/findings.mjs +7 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +16 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +155 -42
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +2988 -155
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/constants.mjs +11 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/doctor.mjs +336 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/generate-image.mjs +232 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/hook-admin.mjs +90 -10
- package/dist/builtin/workflows/skills/impeccable/scripts/hook-lib.mjs +410 -78
- package/dist/builtin/workflows/skills/impeccable/scripts/hook.mjs +25 -8
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/artifact-schema.mjs +93 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/composition-catalog.mjs +165 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/concept-catalog.mjs +329 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +20 -5
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-paths.mjs +16 -8
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/provider.mjs +1 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness-deep.mjs +455 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness-notice.mjs +169 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness.mjs +457 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/surface-briefs.mjs +151 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/target-slug.mjs +33 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/template-extensions.mjs +146 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/completion.mjs +10 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live/event-validation.mjs +15 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/generation-preflight.mjs +149 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/poll-lanes.mjs +14 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/session-store.mjs +109 -32
- package/dist/builtin/workflows/skills/impeccable/scripts/live/source-lock.mjs +105 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/source-search.mjs +105 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +8 -6
- package/dist/builtin/workflows/skills/impeccable/scripts/live/tanstack-adapter.mjs +280 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +208 -68
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +563 -112
- package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +3 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +198 -13
- package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +24 -6
- package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +6 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +44 -14
- package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +398 -33
- package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +9 -5
- package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +81 -67
- package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +7 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/palette.mjs +76 -81
- package/dist/builtin/workflows/skills/impeccable/scripts/pin.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/serve-question.mjs +890 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/surface-brief.mjs +74 -0
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +201 -22
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-control.ts +23 -9
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +1 -0
- package/dist/builtin/workflows/src/runs/foreground/postmortem-stage-chat.ts +20 -5
- package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +10 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-delivery-activity.ts +86 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +6 -2
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +19 -13
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-send-user-message.ts +8 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +16 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-delivery-activity.ts +121 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-live-events.ts +42 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +16 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +4 -0
- package/dist/modes/interactive/components/chat-session-host-actions.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-actions.js +2 -14
- package/dist/modes/interactive/components/chat-session-host-actions.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.js +9 -0
- package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-runtime.d.ts +25 -0
- package/dist/modes/interactive/components/chat-session-host-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-runtime.js +53 -0
- package/dist/modes/interactive/components/chat-session-host-runtime.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.d.ts +13 -0
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.js +22 -1
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.js +9 -3
- package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
- package/docs/tui.md +7 -2
- package/docs/workflows.md +1 -1
- package/npm-shrinkwrap.json +23 -23
- package/package.json +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/brand.md +0 -108
- package/dist/builtin/workflows/skills/impeccable/reference/codex.md +0 -105
- package/dist/builtin/workflows/skills/impeccable/reference/interaction-design.md +0 -189
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
4
5
|
import { finding } from './findings.mjs';
|
|
@@ -7,6 +8,11 @@ import { parseAnyColor, resolveLengthPx } from './rules/checks.mjs';
|
|
|
7
8
|
|
|
8
9
|
const DESIGN_NAMES = ['DESIGN.md', 'Design.md', 'design.md'];
|
|
9
10
|
const FALLBACK_DIRS = ['.agents/context', 'docs'];
|
|
11
|
+
// Files/dirs whose presence marks a directory as a project root. Mirrors the
|
|
12
|
+
// walk-up semantics of skill/scripts/context.mjs (`resolveProject`), which the
|
|
13
|
+
// CLI can't import (separate tree). `.git` and `package.json` are the common
|
|
14
|
+
// boundaries; `.impeccable` is our own project marker.
|
|
15
|
+
const PROJECT_ROOT_MARKERS = ['.git', 'package.json', '.impeccable'];
|
|
10
16
|
const COLOR_CHANNEL_TOLERANCE = 6;
|
|
11
17
|
const RADIUS_TOLERANCE_PX = 0.5;
|
|
12
18
|
const FONT_SIZE_TOLERANCE_PX = 0.5;
|
|
@@ -288,15 +294,74 @@ function addTypographyFonts(out, typography) {
|
|
|
288
294
|
}
|
|
289
295
|
}
|
|
290
296
|
|
|
297
|
+
function addFontSizeStep(out, raw, { fluid = false } = {}) {
|
|
298
|
+
const text = String(raw ?? '').trim().toLowerCase();
|
|
299
|
+
if (!FONT_SIZE_LITERAL_RE.test(text)) return;
|
|
300
|
+
const px = resolveLengthPx(text, 16);
|
|
301
|
+
if (px == null || !Number.isFinite(px) || px <= 0) return;
|
|
302
|
+
out.allowedFontSizes.push({ value: text, px, fluid });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Split a fluid value into its three terms, or null when it is not a
|
|
306
|
+
// well-formed clamp(). Used both to read DESIGN.md's fluid roles and to
|
|
307
|
+
// validate fluid values in source, so the two stay symmetric.
|
|
308
|
+
function parseClampArgs(raw) {
|
|
309
|
+
const match = /^clamp\(\s*([\s\S]+)\s*\)$/i.exec(String(raw ?? '').trim());
|
|
310
|
+
if (!match) return null;
|
|
311
|
+
const args = splitTopLevelArgs(match[1]);
|
|
312
|
+
return args.length === 3 ? args : null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// A fluid role declares its two fixed endpoints and interpolates between them
|
|
316
|
+
// with a viewport unit. Both endpoints are documented sizes, so they belong in
|
|
317
|
+
// the allowlist; the middle term is viewport-relative and never a fixed step.
|
|
318
|
+
// Endpoints are marked `fluid` because they do not *enumerate* a ramp: see
|
|
319
|
+
// `hasFontSizes` below for why that distinction has to survive.
|
|
320
|
+
function addClampEndpoints(out, raw) {
|
|
321
|
+
const args = parseClampArgs(raw);
|
|
322
|
+
if (!args) return false;
|
|
323
|
+
addFontSizeStep(out, args[0], { fluid: true });
|
|
324
|
+
addFontSizeStep(out, args[2], { fluid: true });
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function splitTopLevelArgs(s) {
|
|
329
|
+
const args = [];
|
|
330
|
+
let depth = 0;
|
|
331
|
+
let current = '';
|
|
332
|
+
for (const ch of String(s)) {
|
|
333
|
+
if (ch === '(') depth++;
|
|
334
|
+
else if (ch === ')') depth--;
|
|
335
|
+
if (ch === ',' && depth === 0) {
|
|
336
|
+
args.push(current.trim());
|
|
337
|
+
current = '';
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
current += ch;
|
|
341
|
+
}
|
|
342
|
+
if (current.trim()) args.push(current.trim());
|
|
343
|
+
return args;
|
|
344
|
+
}
|
|
345
|
+
|
|
291
346
|
function addTypographySizes(out, typography) {
|
|
292
347
|
if (!typography || typeof typography !== 'object') return;
|
|
293
|
-
|
|
348
|
+
|
|
349
|
+
// `scale` is the enumerated ramp: a name -> size map, since the frontmatter
|
|
350
|
+
// parser has no list support. It sits alongside the named roles.
|
|
351
|
+
const scale = typography.scale;
|
|
352
|
+
if (scale && typeof scale === 'object') {
|
|
353
|
+
for (const value of Object.values(scale)) {
|
|
354
|
+
if (typeof value !== 'string' && typeof value !== 'number') continue;
|
|
355
|
+
addFontSizeStep(out, value);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
for (const [name, role] of Object.entries(typography)) {
|
|
360
|
+
if (name === 'scale') continue;
|
|
294
361
|
if (!role || typeof role !== 'object') continue;
|
|
295
362
|
const raw = String(role.fontSize ?? '').trim().toLowerCase();
|
|
296
|
-
if (
|
|
297
|
-
|
|
298
|
-
if (px == null || !Number.isFinite(px) || px <= 0) continue;
|
|
299
|
-
out.allowedFontSizes.push({ value: raw, px });
|
|
363
|
+
if (addClampEndpoints(out, raw)) continue;
|
|
364
|
+
addFontSizeStep(out, raw);
|
|
300
365
|
}
|
|
301
366
|
}
|
|
302
367
|
|
|
@@ -371,7 +436,10 @@ function normalizeDesignSystem(input = {}) {
|
|
|
371
436
|
out.hasFonts = out.allowedFonts.size > 0;
|
|
372
437
|
out.hasColors = out.allowedColorKeys.size > 0;
|
|
373
438
|
out.hasRadii = out.allowedRadii.length > 0;
|
|
374
|
-
|
|
439
|
+
// Gate on *enumerated* steps only. A fully fluid system declares clamp
|
|
440
|
+
// endpoints but no discrete ramp, so treating those endpoints as the whole
|
|
441
|
+
// allowlist would flag every intermediate size. Abstain instead.
|
|
442
|
+
out.hasFontSizes = out.allowedFontSizes.some(entry => !entry.fluid);
|
|
375
443
|
return out;
|
|
376
444
|
}
|
|
377
445
|
|
|
@@ -407,6 +475,62 @@ function loadDesignSystemForCwd(cwd = process.cwd()) {
|
|
|
407
475
|
});
|
|
408
476
|
}
|
|
409
477
|
|
|
478
|
+
// Directory to begin the project-root walk from, given a scan target that may
|
|
479
|
+
// be a file or a directory (and may not exist yet).
|
|
480
|
+
function designSystemStartDir(targetPath, cwd = process.cwd()) {
|
|
481
|
+
const abs = path.isAbsolute(targetPath) ? targetPath : path.resolve(cwd, targetPath);
|
|
482
|
+
try {
|
|
483
|
+
return fs.statSync(abs).isDirectory() ? abs : path.dirname(abs);
|
|
484
|
+
} catch {
|
|
485
|
+
// Nonexistent path: treat an extension-bearing leaf as a file.
|
|
486
|
+
return path.extname(abs) ? path.dirname(abs) : abs;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Walk up from `startDir` to the directory that governs the target's design
|
|
491
|
+
// system, mirroring skill/scripts/context.mjs's project-boundary semantics:
|
|
492
|
+
//
|
|
493
|
+
// - A directory carrying a DESIGN.md (directly or in a fallback dir) IS the
|
|
494
|
+
// design root — that's where the rules live.
|
|
495
|
+
// - A directory carrying a project marker (.git / package.json / .impeccable)
|
|
496
|
+
// but no DESIGN.md is a project BOUNDARY: the walk stops with no design
|
|
497
|
+
// system, so a sibling project never inherits a parent's or cwd's rules.
|
|
498
|
+
// - Reaching the home directory / filesystem root with neither means no
|
|
499
|
+
// design system at all — never process.cwd()'s.
|
|
500
|
+
//
|
|
501
|
+
// Returns { dir, hasDesign } for the stopping directory, or null when the walk
|
|
502
|
+
// runs out. This is the fix for cross-project contamination.
|
|
503
|
+
export function findDesignRoot(startDir) {
|
|
504
|
+
let dir = path.resolve(startDir);
|
|
505
|
+
const homeDir = path.resolve(os.homedir());
|
|
506
|
+
while (true) {
|
|
507
|
+
if (resolveDesignMdPath(dir)) return { dir, hasDesign: true };
|
|
508
|
+
if (PROJECT_ROOT_MARKERS.some((marker) => fs.existsSync(path.join(dir, marker)))) {
|
|
509
|
+
return { dir, hasDesign: false };
|
|
510
|
+
}
|
|
511
|
+
if (dir === homeDir) return null;
|
|
512
|
+
const parent = path.dirname(dir);
|
|
513
|
+
if (parent === dir) return null;
|
|
514
|
+
dir = parent;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Resolve the design system that governs a specific scan target, by walking up
|
|
519
|
+
// from the target's own location — never process.cwd(). Scanning project B's
|
|
520
|
+
// files from inside project A applies B's DESIGN.md (or none), not A's.
|
|
521
|
+
//
|
|
522
|
+
// Pass a `cache` Map to memoize by resolved design root across a multi-file
|
|
523
|
+
// scan; a target with no design root above it resolves to null.
|
|
524
|
+
export function loadDesignSystemForTarget(targetPath, { cache, cwd = process.cwd() } = {}) {
|
|
525
|
+
const startDir = designSystemStartDir(targetPath, cwd);
|
|
526
|
+
const found = findDesignRoot(startDir);
|
|
527
|
+
const key = found ? `root:${found.dir}` : '\0none';
|
|
528
|
+
if (cache && cache.has(key)) return cache.get(key);
|
|
529
|
+
const loaded = found?.hasDesign ? loadDesignSystemForCwd(found.dir) : null;
|
|
530
|
+
if (cache) cache.set(key, loaded);
|
|
531
|
+
return loaded;
|
|
532
|
+
}
|
|
533
|
+
|
|
410
534
|
function isAllowedFont(font, designSystem) {
|
|
411
535
|
if (!font || GENERIC_FONTS.has(font)) return true;
|
|
412
536
|
if (!designSystem?.hasFonts) return true;
|
|
@@ -438,17 +562,43 @@ function isAllowedRadiusRaw(raw, designSystem) {
|
|
|
438
562
|
return designSystem.allowedRadii.some(entry => Math.abs(entry.px - px) <= RADIUS_TOLERANCE_PX);
|
|
439
563
|
}
|
|
440
564
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
565
|
+
// One term of a font-size value. `unjudgeable` covers var(), calc(), percentages
|
|
566
|
+
// and units the ramp cannot resolve (em is parent-relative, not root-relative);
|
|
567
|
+
// those abstain rather than guess.
|
|
568
|
+
function fontSizeStepStatus(raw, designSystem) {
|
|
569
|
+
const text = String(raw || '').trim().toLowerCase();
|
|
570
|
+
if (!FONT_SIZE_LITERAL_RE.test(text)) return 'unjudgeable';
|
|
445
571
|
const px = resolveLengthPx(text, 16);
|
|
446
|
-
if (px == null || !Number.isFinite(px) || px <= 0) return
|
|
572
|
+
if (px == null || !Number.isFinite(px) || px <= 0) return 'unjudgeable';
|
|
447
573
|
return designSystem.allowedFontSizes.some(
|
|
448
574
|
entry => Math.abs(entry.px - px) <= FONT_SIZE_TOLERANCE_PX,
|
|
575
|
+
) ? 'on-ramp' : 'off-ramp';
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// The off-ramp endpoints of a fluid value, or null when `raw` is not a fluid
|
|
579
|
+
// value at all. Only the min and max are judged: the viewport term interpolates
|
|
580
|
+
// between them and is never a fixed step.
|
|
581
|
+
//
|
|
582
|
+
// Reading clamp endpoints as documented steps without also checking them in
|
|
583
|
+
// usage would let `clamp(99rem, 1vw, 200rem)` through, which is how a fluid
|
|
584
|
+
// declaration stayed invisible until someone measured computed styles.
|
|
585
|
+
export function offRampClampEndpoints(raw, designSystem) {
|
|
586
|
+
if (!designSystem?.hasFontSizes) return null;
|
|
587
|
+
const args = parseClampArgs(String(raw || '').trim().replace(/\s*!important\s*$/i, ''));
|
|
588
|
+
if (!args) return null;
|
|
589
|
+
return [args[0], args[2]].filter(
|
|
590
|
+
endpoint => fontSizeStepStatus(endpoint, designSystem) === 'off-ramp',
|
|
449
591
|
);
|
|
450
592
|
}
|
|
451
593
|
|
|
594
|
+
function isAllowedFontSizeRaw(raw, designSystem) {
|
|
595
|
+
if (!designSystem?.hasFontSizes) return true;
|
|
596
|
+
const text = String(raw || '').trim().toLowerCase().replace(/\s*!important\s*$/, '');
|
|
597
|
+
const offRampEndpoints = offRampClampEndpoints(text, designSystem);
|
|
598
|
+
if (offRampEndpoints) return offRampEndpoints.length === 0;
|
|
599
|
+
return fontSizeStepStatus(text, designSystem) !== 'off-ramp';
|
|
600
|
+
}
|
|
601
|
+
|
|
452
602
|
function lineLooksCommented(line) {
|
|
453
603
|
const trimmed = String(line || '').trim();
|
|
454
604
|
return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*') || trimmed.startsWith('<!--');
|
|
@@ -543,12 +693,31 @@ function checkRadiusValue(value, filePath, line, designSystem, context) {
|
|
|
543
693
|
function checkFontSizeValue(value, filePath, line, designSystem, context) {
|
|
544
694
|
const token = String(value || '').trim();
|
|
545
695
|
if (isAllowedFontSizeRaw(token, designSystem)) return [];
|
|
696
|
+
|
|
697
|
+
// Name the offending endpoint on a fluid value; the whole clamp() string is
|
|
698
|
+
// not actionable on its own, and it makes a poor ignore-value.
|
|
699
|
+
const offRampEndpoints = offRampClampEndpoints(token, designSystem) || [];
|
|
700
|
+
if (offRampEndpoints.length > 0) {
|
|
701
|
+
const plural = offRampEndpoints.length > 1 ? 's' : '';
|
|
702
|
+
return [makeDesignFinding(
|
|
703
|
+
'design-system-font-size',
|
|
704
|
+
filePath,
|
|
705
|
+
`${context}: ${token} has fluid endpoint${plural} ${offRampEndpoints.join(' and ')} off the DESIGN.md type ramp`,
|
|
706
|
+
line,
|
|
707
|
+
{ ignoreValue: offRampEndpoints[0] },
|
|
708
|
+
)];
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// The snippet shows the declaration as authored, but the ignoreValue has to
|
|
712
|
+
// be what a `hooks ignore-value` waiver can match, so the priority marker is
|
|
713
|
+
// stripped. Otherwise the same size needs two different waivers depending on
|
|
714
|
+
// whether it carries !important. font-family already behaves this way.
|
|
546
715
|
return [makeDesignFinding(
|
|
547
716
|
'design-system-font-size',
|
|
548
717
|
filePath,
|
|
549
718
|
`${context}: ${token} is off the DESIGN.md type ramp`,
|
|
550
719
|
line,
|
|
551
|
-
{ ignoreValue: token },
|
|
720
|
+
{ ignoreValue: token.replace(/\s*!important\s*$/i, '').trim() },
|
|
552
721
|
)];
|
|
553
722
|
}
|
|
554
723
|
|