youiyoux 0.1.0 → 0.1.2
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/README.md +24 -14
- package/dist/chunks/{chunk-UXLATKEQ.js → chunk-CLNMVQZJ.js} +351 -128
- package/dist/youi.js +1 -1
- package/dist/youiyoux.js +1 -1
- package/package.json +10 -12
package/README.md
CHANGED
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
# youiyoux
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Your agent rebuilds components you already have. youiyoux catches it.**
|
|
4
|
+
|
|
5
|
+
UI context and verification for coding agents: it tells the agent what your repo already has before
|
|
6
|
+
it writes UI, then checks what it wrote.
|
|
4
7
|
|
|
5
8
|
```sh
|
|
6
|
-
npx youiyoux
|
|
7
|
-
npx youiyoux path/to/repo
|
|
9
|
+
npx youiyoux # scan this repo
|
|
10
|
+
npx youiyoux path/to/repo # or another one; --out <dir> writes elsewhere
|
|
8
11
|
```
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
No config, no account. You get:
|
|
14
|
+
|
|
15
|
+
- `youiyoux-report.html`: near-duplicate components and which to keep, hardcoded values your theme already has, off-library imports, and the top fixes with their next steps. One offline page.
|
|
16
|
+
- `youiyoux-card.png`: a 1200×630 card with the headline numbers.
|
|
17
|
+
- `youiyoux-summary.md`: paste it into a PR or issue.
|
|
18
|
+
|
|
19
|
+
## For your agent
|
|
11
20
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
| `youiyoux-summary.md` | A short summary for a README or pull request |
|
|
21
|
+
```sh
|
|
22
|
+
npx youiyoux agents install # rule in AGENTS.md + CLAUDE.md, MCP server for Claude Code + Codex
|
|
23
|
+
npx youiyoux hooks install # check after each Claude Code edit, and at git commit
|
|
24
|
+
```
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
- `ui_find_component` (MCP): before writing a component, the existing one with its import, props and a real usage.
|
|
27
|
+
- `npx youiyoux check --diff`: after UI edits, what the change adds, each with its fix.
|
|
28
|
+
- `ui_render` (MCP) or `npx youiyoux render <file>`: screenshots light and dark, accessibility issues, a diff against base.
|
|
19
29
|
|
|
20
|
-
|
|
30
|
+
## Privacy
|
|
21
31
|
|
|
22
|
-
|
|
32
|
+
Runs locally; nothing is uploaded, no telemetry. The report includes relative paths, component names, class lists, counts and rendered images when available. No source lines unless `--include-code`; home paths, usernames, env values and credentials are stripped. Rendered looks need a headless Chromium (`--render` offers to download it, ~100 MB, once); specimen images are cached in `~/.cache/youiyoux/specimens`.
|
|
23
33
|
|
|
24
|
-
Node 20 or later.
|
|
34
|
+
Node 20 or later. The `youi` bin is a silent alias.
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
Docs, agent reference and source: [github.com/ap9000/youiyoux](https://github.com/ap9000/youiyoux)
|
|
@@ -2374,9 +2374,9 @@ function renderInventory(report, outFile) {
|
|
|
2374
2374
|
const roles = INVENTORY_ROLES.filter((role) => uses.some((l) => l.role === role));
|
|
2375
2375
|
const width = Math.max(...projects.map((p) => p.length), 9);
|
|
2376
2376
|
const cell = (role, project2) => {
|
|
2377
|
-
const
|
|
2378
|
-
const families = new Set(
|
|
2379
|
-
return
|
|
2377
|
+
const looks = uses.filter((l) => l.role === role && l.project === project2);
|
|
2378
|
+
const families = new Set(looks.flatMap((l) => l.family ? [l.family] : [])).size;
|
|
2379
|
+
return looks.length === 0 ? style.dim("\xB7") : `${families}f ${looks.length}`;
|
|
2380
2380
|
};
|
|
2381
2381
|
const lines = [
|
|
2382
2382
|
`${style.bold("inventory")} ${style.dim(`\xB7 ${plural(report.projects.length, "project")} \xB7 ${plural(report.summary.looks, "look")} in ${plural(report.families.length, "family", "families")}${report.summary.stories ? ` \xB7 ${plural(report.summary.stories, "story", "stories")} beside them` : ""}`)}`,
|
|
@@ -2560,7 +2560,7 @@ function plainAscii(text) {
|
|
|
2560
2560
|
// ../cli/src/report/card.ts
|
|
2561
2561
|
var CARD_WIDTH = 1200;
|
|
2562
2562
|
var CARD_HEIGHT = 630;
|
|
2563
|
-
var INK = { background: [
|
|
2563
|
+
var INK = { background: [8, 9, 10], track: [25, 26, 29], accent: [113, 112, 255], accent2: [69, 69, 147], accent3: [40, 40, 76], text: [247, 248, 248], muted: [138, 143, 152], ok: [143, 181, 164] };
|
|
2564
2564
|
var Canvas = class {
|
|
2565
2565
|
pixels;
|
|
2566
2566
|
width;
|
|
@@ -2641,25 +2641,35 @@ function drawCard(input) {
|
|
|
2641
2641
|
const canvas = new Canvas(CARD_WIDTH, CARD_HEIGHT, INK.background);
|
|
2642
2642
|
const margin = 72;
|
|
2643
2643
|
const inner = CARD_WIDTH - margin * 2;
|
|
2644
|
-
canvas.
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
const
|
|
2649
|
-
const
|
|
2650
|
-
const
|
|
2651
|
-
|
|
2652
|
-
const
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
const
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2644
|
+
canvas.text(margin, 64, "youiyoux report", 3, INK.muted);
|
|
2645
|
+
const name = fit(input.repo, inner, 9, 5);
|
|
2646
|
+
canvas.text(margin, 104, name.text, name.scale, INK.text);
|
|
2647
|
+
const total = input.parts.reduce((n, part) => n + part.places, 0);
|
|
2648
|
+
const value = fit(String(total), inner / 2, 14, 8);
|
|
2649
|
+
const valueWidth = canvas.text(margin, 236, value.text, value.scale, total > 0 ? INK.text : INK.ok);
|
|
2650
|
+
const words = total === 0 ? "places to fix: clean" : total === 1 ? "place to fix" : "places to fix";
|
|
2651
|
+
canvas.text(margin + valueWidth + 28, 236 + 7 * value.scale - 7 * 4, words, 4, INK.muted);
|
|
2652
|
+
const top = 380;
|
|
2653
|
+
const steps = [INK.accent, INK.accent2, INK.accent3];
|
|
2654
|
+
canvas.rect(margin, top, inner, 20, INK.track, 10);
|
|
2655
|
+
if (total > 0) {
|
|
2656
|
+
let x2 = margin;
|
|
2657
|
+
const shown2 = input.parts.filter((part) => part.places > 0);
|
|
2658
|
+
const room = inner - 6 * (shown2.length - 1);
|
|
2659
|
+
input.parts.forEach((part, i) => {
|
|
2660
|
+
if (part.places === 0) return;
|
|
2661
|
+
const last = part === shown2.at(-1);
|
|
2662
|
+
const width = last ? margin + inner - x2 : Math.max(10, Math.round(part.places / total * room));
|
|
2663
|
+
canvas.rect(x2, top, width, 20, steps[i] ?? INK.accent3, 10);
|
|
2664
|
+
x2 += width + 6;
|
|
2662
2665
|
});
|
|
2666
|
+
}
|
|
2667
|
+
let x = margin;
|
|
2668
|
+
input.parts.forEach((part, i) => {
|
|
2669
|
+
canvas.rect(x, top + 52, 18, 18, part.places > 0 ? steps[i] ?? INK.accent3 : INK.track, 4);
|
|
2670
|
+
x += 32;
|
|
2671
|
+
x += canvas.text(x, top + 52, part.label.split(" ")[0], 3, INK.muted) + 16;
|
|
2672
|
+
x += canvas.text(x, top + 52, String(part.places), 3, part.places > 0 ? INK.text : INK.ok) + 40;
|
|
2663
2673
|
});
|
|
2664
2674
|
const credit = "npx youiyoux";
|
|
2665
2675
|
const creditWidth = textWidth(credit, 3);
|
|
@@ -3119,22 +3129,24 @@ async function nearDuplicateGroups(input) {
|
|
|
3119
3129
|
const where = keeperFacts.project.name && importedBy(keeperFacts) > 0 ? `${keeperFacts.project.name}, ${keeperFile}` : keeperFile;
|
|
3120
3130
|
const lead = kept ? `Keep ${keeperName} (${where})` : `Create ${keeperName} in ${keeperFile} (a new file in ${extract.where}) with this shape`;
|
|
3121
3131
|
const [one, many] = KIND_WORDS[kind];
|
|
3132
|
+
const components = new Set([kept, ...copies].flatMap((v) => v?.kind === "component" ? [v.id] : [])).size;
|
|
3122
3133
|
findings2.push(finding(
|
|
3123
3134
|
home.project.dir,
|
|
3124
3135
|
`${plural2(members.length, one, many)} look the same (${shapeWords(family.shape)}) but are built ${sides.length} ways`,
|
|
3125
3136
|
counted.length,
|
|
3126
3137
|
[...copies].sort((a, b) => path(a).localeCompare(path(b)) || a.line - b.line).slice(0, SITES_PER_FINDING).map((v) => ({ at: `${path(v)}:${v.line}`, file: path(v), line: v.line })),
|
|
3127
3138
|
lead,
|
|
3128
|
-
steps
|
|
3139
|
+
steps,
|
|
3140
|
+
components
|
|
3129
3141
|
));
|
|
3130
3142
|
}
|
|
3131
3143
|
findings2.push(...sameNamed.map((g) => g.finding));
|
|
3132
3144
|
return findings2.sort((a, b) => b.count - a.count || a.title.localeCompare(b.title));
|
|
3133
3145
|
}
|
|
3134
|
-
function finding(project2, title, count, sites, lead, steps) {
|
|
3146
|
+
function finding(project2, title, count, sites, lead, steps, exported) {
|
|
3135
3147
|
const shown2 = steps.slice(0, STEPS_SHOWN);
|
|
3136
3148
|
const more = steps.length > shown2.length ? `; and ${plural2(steps.length - shown2.length, "more file")} (listed in the report)` : "";
|
|
3137
|
-
return { kind: "near-duplicate", project: project2, title, detail: null, count, sites, lead, steps, next: `${lead}. ${shown2.join(". ")}${more}
|
|
3149
|
+
return { kind: "near-duplicate", project: project2, title, detail: null, count, sites, lead, steps, next: `${lead}. ${shown2.join(". ")}${more}`, exported };
|
|
3138
3150
|
}
|
|
3139
3151
|
function commonDir(files) {
|
|
3140
3152
|
const parts = files.map((f) => f.split("/").slice(0, -1));
|
|
@@ -3189,7 +3201,7 @@ async function sameNamedGroups(facts, defs, usage, importText) {
|
|
|
3189
3201
|
const lead = how === "same" ? `Keep ${name} (${keeperFile})` : how === "near" ? `Keep one ${name} (${keeperFile}) that takes what differs as props` : `Keep ${name} (${keeperFile}) as the one ${name}`;
|
|
3190
3202
|
const sites = [keep, ...copies].slice(0, SITES_PER_FINDING).map((d) => ({ at: `${repoPath(dir, d.file)}:${d.line}`, file: repoPath(dir, d.file), line: d.line }));
|
|
3191
3203
|
const title = how === "different" ? `${name} is defined in ${copies.length + 1} files as different components` : `${name} is defined in ${copies.length + 1} files with ${how === "same" ? "the same" : "near-identical"} structure`;
|
|
3192
|
-
return { ids: [keep, ...copies].map((d) => idOf(d.file)), finding: finding(dir, title, copies.length, sites, lead, steps) };
|
|
3204
|
+
return { ids: [keep, ...copies].map((d) => idOf(d.file)), finding: finding(dir, title, copies.length, sites, lead, steps, [keep, ...copies].filter((d) => d.exported).length) };
|
|
3193
3205
|
}
|
|
3194
3206
|
return found;
|
|
3195
3207
|
}
|
|
@@ -3373,7 +3385,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3373
3385
|
const changeProject = target ? await inferProject(project2.root, target) : null;
|
|
3374
3386
|
const moduleMap = new Map(modules.map((m) => [m.file, m]));
|
|
3375
3387
|
const eligible = new Set(inventory?.definitions.filter((d) => d.project === projectId && uiDefinition(d, moduleMap)).map((d) => d.id));
|
|
3376
|
-
const
|
|
3388
|
+
const looks = inventory ? inventory.looks.filter((l) => l.project === projectId && !l.story && !isShowcase(l.file)) : [];
|
|
3377
3389
|
let libraries = [];
|
|
3378
3390
|
let unused = [];
|
|
3379
3391
|
const showcaseFiles = /* @__PURE__ */ new Set();
|
|
@@ -3395,11 +3407,11 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3395
3407
|
incomplete.push(`UI libraries were not read (${errorText(error)}); run ${NPX} detect ${project2.dir} to see why`);
|
|
3396
3408
|
}
|
|
3397
3409
|
const roles = INVENTORY_ROLES.flatMap((role) => {
|
|
3398
|
-
const of =
|
|
3410
|
+
const of = looks.filter((l) => l.role === role);
|
|
3399
3411
|
return of.length === 0 ? [] : [{ role, uses: of.length, families: new Set(of.flatMap((l) => l.family ? [l.family] : [])).size }];
|
|
3400
3412
|
});
|
|
3401
3413
|
const byComponent = /* @__PURE__ */ new Map();
|
|
3402
|
-
for (const look of
|
|
3414
|
+
for (const look of looks) {
|
|
3403
3415
|
if (look.recipe?.kind !== "component") continue;
|
|
3404
3416
|
const key2 = `${look.recipe.file}#${look.recipe.export}`;
|
|
3405
3417
|
const entry = byComponent.get(key2) ?? { name: look.recipe.export, file: repoPath2(project2.dir, look.recipe.file), roles: [] };
|
|
@@ -3452,7 +3464,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
|
|
|
3452
3464
|
definitions: inventory ? inventory.definitions.filter((d) => d.project === projectId && !isShowcase(d.file)).length : 0,
|
|
3453
3465
|
kits: inventory ? inventory.kits.filter((k) => k.project === projectId).map((k) => ({ path: repoPath2(project2.dir, k.path), kind: k.kind === "cva-folder" ? "CVA components" : "CSS classes", members: k.members.length })) : [],
|
|
3454
3466
|
roles,
|
|
3455
|
-
uses:
|
|
3467
|
+
uses: looks.length,
|
|
3456
3468
|
components,
|
|
3457
3469
|
hardcoded,
|
|
3458
3470
|
offLibrary,
|
|
@@ -3466,6 +3478,8 @@ function topNext(f) {
|
|
|
3466
3478
|
const text = f.lead && f.steps ? `${f.lead}${f.steps.length > 1 ? `, then change ${plural3(f.steps.length, "file")} (each listed in the report)` : `; ${f.steps[0].charAt(0).toLowerCase()}${f.steps[0].slice(1)}`}` : f.next;
|
|
3467
3479
|
return `${text.charAt(0).toLowerCase()}${text.slice(1).replace(/\.$/, "")}`;
|
|
3468
3480
|
}
|
|
3481
|
+
var DUPLICATE_RULE = "Duplicated components: groups of 2+ exported components that look alike or share a name.";
|
|
3482
|
+
var isComponentDuplicate = (f) => (f.exported ?? 0) >= 2;
|
|
3469
3483
|
function summaryLines(model) {
|
|
3470
3484
|
const t = model.totals;
|
|
3471
3485
|
const top = model.projects.flatMap((p) => p.components).sort((a, b) => b.uses - a.uses)[0];
|
|
@@ -3473,7 +3487,7 @@ function summaryLines(model) {
|
|
|
3473
3487
|
return [
|
|
3474
3488
|
`${model.repo}: ${plural3(t.projects, "project")}, ${plural3(t.files, "file")} scanned; UI libraries: ${t.libraries.length > 0 ? t.libraries.join(", ") : "none found"}.`,
|
|
3475
3489
|
`Components: ${t.definitions} defined, ${plural3(t.uses, "button, input, card or other UI element", "buttons, inputs, cards and other UI elements")} in use${top ? `; most used: ${top.name} (${plural3(top.uses, "use")})` : ""}.`,
|
|
3476
|
-
`Near-duplicates: ${plural3(t.nearDuplicates, "group")} of
|
|
3490
|
+
`Near-duplicates: ${plural3(t.nearDuplicates, "group")} of UI that looks the same but is built more than once; ${t.componentDuplicates} of them between exported components.`,
|
|
3477
3491
|
`Hardcoded values: ${plural3(t.hardcoded, "colour, spacing or radius", "colours, spacings and radii")} a theme token already holds; off-library imports: ${t.offLibrary}.`,
|
|
3478
3492
|
first ? `Top fix: ${first.title.replace(/\.$/, "")}; ${topNext(first)}.` : `Nothing to fix first; next: ${NPX} agents install, so your agent keeps it that way.`
|
|
3479
3493
|
];
|
|
@@ -3504,6 +3518,7 @@ async function buildReportModel(dir, options = {}) {
|
|
|
3504
3518
|
definitions: projects.reduce((n, p) => n + p.definitions, 0),
|
|
3505
3519
|
uses: projects.reduce((n, p) => n + p.uses, 0),
|
|
3506
3520
|
nearDuplicates: near.length,
|
|
3521
|
+
componentDuplicates: near.filter(isComponentDuplicate).length,
|
|
3507
3522
|
hardcoded: projects.reduce((n, p) => n + p.hardcoded.reduce((m, f) => m + f.count, 0), 0),
|
|
3508
3523
|
offLibrary: projects.reduce((n, p) => n + p.offLibrary.reduce((m, f) => m + f.count, 0), 0)
|
|
3509
3524
|
};
|
|
@@ -3533,108 +3548,316 @@ var escape2 = (text) => String(text).replace(/&/g, "&").replace(/</g, "<"
|
|
|
3533
3548
|
var plural4 = (count, word, many = `${word}s`) => `${count} ${count === 1 ? word : many}`;
|
|
3534
3549
|
var anchor = (dir) => `p-${dir === "." ? "root" : dir.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "")}`;
|
|
3535
3550
|
var CSS = `
|
|
3536
|
-
:root{color-scheme:light dark;--
|
|
3537
|
-
@media (prefers-color-scheme:dark){:root{--
|
|
3551
|
+
:root{color-scheme:light dark;--canvas:#f4f5f7;--surface:#fcfcfd;--raised:#f6f7f9;--sunken:#f8f9fa;--line:#e3e4e8;--line-strong:#d0d2d8;--ink:#16171a;--ink-2:#3d4049;--muted:#6b6f78;--faint:#9ea2ab;--accent:#5e6ad2;--accent-soft:#eceefa;--success:#4f7a69;--success-soft:#eaf1ee;--danger:#ad5a43;--danger-soft:#f7ece8;--neutral-soft:#eef0f3;--k-dup:var(--accent);--k-hard:color-mix(in oklch,var(--accent) 55%,var(--surface));--k-off:color-mix(in oklch,var(--accent) 25%,var(--surface));--shadow:0 1px 2px rgba(16,17,20,.04)}
|
|
3552
|
+
@media (prefers-color-scheme:dark){:root{--canvas:#08090a;--surface:#0f1011;--raised:#151618;--sunken:#0b0c0d;--line:#1d1e21;--line-strong:#2b2c30;--ink:#f7f8f8;--ink-2:#d0d6e0;--muted:#8a8f98;--faint:#62666d;--accent:#7170ff;--accent-soft:#1a1b33;--success:#8fb5a4;--success-soft:#14201b;--danger:#d99a85;--danger-soft:#251713;--neutral-soft:#191a1d;--shadow:none}}
|
|
3538
3553
|
*{box-sizing:border-box}
|
|
3539
|
-
html{-webkit-text-size-adjust:100
|
|
3540
|
-
body{margin:0;background:var(--
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
.
|
|
3550
|
-
.
|
|
3551
|
-
.
|
|
3552
|
-
.
|
|
3553
|
-
.
|
|
3554
|
-
.
|
|
3555
|
-
.
|
|
3556
|
-
.
|
|
3557
|
-
.
|
|
3558
|
-
|
|
3559
|
-
.
|
|
3560
|
-
.
|
|
3554
|
+
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;scroll-padding-top:16px}
|
|
3555
|
+
body{margin:0;background:var(--canvas);color:var(--ink);font:14px/1.5 Geist,"Geist Variable",ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Roboto,sans-serif;overflow-wrap:anywhere;-webkit-font-smoothing:antialiased}
|
|
3556
|
+
a{color:var(--accent);text-decoration:none}
|
|
3557
|
+
a:hover{text-decoration:underline}
|
|
3558
|
+
code,pre,.mono{font-family:"Geist Mono","Geist Mono Variable",ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}
|
|
3559
|
+
code{background:var(--neutral-soft);border:1px solid var(--line);border-radius:5px;padding:1px 6px;color:var(--ink-2)}
|
|
3560
|
+
pre{background:var(--sunken);border:1px solid var(--line);border-radius:6px;padding:8px 10px;margin:4px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}
|
|
3561
|
+
h1,h2,h3,h4,p{margin:0}
|
|
3562
|
+
.shell{display:grid;grid-template-columns:232px minmax(0,1fr);min-height:100vh}
|
|
3563
|
+
.rail{position:sticky;top:0;height:100vh;overflow-y:auto;border-right:1px solid var(--line);background:var(--surface);padding:16px 12px;display:flex;flex-direction:column;gap:16px}
|
|
3564
|
+
.brand{display:flex;align-items:center;gap:10px;padding:2px 6px;font-weight:600;font-size:14px}
|
|
3565
|
+
.mark{display:inline-grid;place-items:center;width:24px;height:24px;border-radius:7px;background:var(--ink);color:var(--canvas);font-size:13px;font-weight:700}
|
|
3566
|
+
.rail nav{display:flex;flex-direction:column;gap:1px}
|
|
3567
|
+
.rail .group{font-size:11.5px;color:var(--faint);padding:12px 8px 4px}
|
|
3568
|
+
.rail a{display:flex;justify-content:space-between;align-items:center;gap:8px;height:30px;padding:0 8px;border-radius:6px;color:var(--ink-2);font-size:13px}
|
|
3569
|
+
.rail a:hover{background:var(--raised);color:var(--ink);text-decoration:none}
|
|
3570
|
+
.rail a .n{font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums}
|
|
3571
|
+
.rail a .n.warn{color:var(--ink)}
|
|
3572
|
+
.rail .foot{margin-top:auto;font-size:11.5px;color:var(--faint);padding:0 8px}
|
|
3573
|
+
main{min-width:0;padding:28px 32px 64px;max-width:1120px;width:100%;margin:0 auto}
|
|
3574
|
+
.top{display:flex;flex-direction:column;gap:10px;padding-bottom:20px}
|
|
3575
|
+
.crumb{font-size:12px;color:var(--muted)}
|
|
3576
|
+
.top h1{font-size:24px;line-height:1.2;font-weight:650;letter-spacing:-.01em}
|
|
3577
|
+
.facts{display:flex;flex-wrap:wrap;gap:6px;padding:0;margin:0;list-style:none}
|
|
3578
|
+
.facts li{display:inline-flex;align-items:center;gap:6px;height:24px;padding:0 9px;border:1px solid var(--line);border-radius:6px;background:var(--surface);font-size:12px;color:var(--ink-2)}
|
|
3579
|
+
.facts li b{font-weight:600;color:var(--ink);font-variant-numeric:tabular-nums}
|
|
3580
|
+
.headline{display:flex;flex-direction:column;gap:14px;padding:4px 0 8px}
|
|
3581
|
+
.say{font-size:26px;line-height:1.3;font-weight:600;letter-spacing:-.015em;max-width:30em}
|
|
3582
|
+
.say b{font-variant-numeric:tabular-nums;font-weight:700}
|
|
3583
|
+
.say b.warn{color:var(--ink);box-shadow:inset 0 -.14em 0 var(--line-strong)}
|
|
3584
|
+
.say b.ok{color:var(--success)}
|
|
3585
|
+
.split{display:flex;height:10px;border-radius:5px;overflow:hidden;gap:2px;background:var(--neutral-soft)}
|
|
3586
|
+
.seg{display:block;height:100%;min-width:3px}
|
|
3587
|
+
.s0{background:var(--k-dup)}
|
|
3588
|
+
.s1{background:var(--k-hard)}
|
|
3589
|
+
.s2{background:var(--k-off)}
|
|
3590
|
+
.legend{display:flex;flex-wrap:wrap;align-items:center;gap:6px 18px;margin:0;padding:0;list-style:none;font-size:12.5px;color:var(--ink-2)}
|
|
3591
|
+
.legend a{color:inherit;display:inline-flex;align-items:center;gap:7px}
|
|
3592
|
+
.legend b{color:var(--ink);font-variant-numeric:tabular-nums}
|
|
3593
|
+
.legend .key{width:10px;height:10px;border-radius:3px;display:inline-block}
|
|
3594
|
+
.legend .key.zero{background:var(--line-strong)}
|
|
3595
|
+
.legend .clean{color:var(--success);font-size:11.5px}
|
|
3596
|
+
.legend .of{margin-left:auto;color:var(--muted);font-size:12px}
|
|
3597
|
+
.shapes{list-style:none;margin:0;padding:0}
|
|
3598
|
+
.shapes li{display:grid;grid-template-columns:120px minmax(0,1fr) 150px;align-items:center;gap:14px;padding:9px 0;border-top:1px solid var(--line)}
|
|
3599
|
+
.shapes li:first-child{border-top:0}
|
|
3600
|
+
.shapes .role{font-size:12.5px;color:var(--ink)}
|
|
3601
|
+
.shapes .sq{display:flex;flex-wrap:wrap;gap:4px;align-items:center;min-width:0}
|
|
3602
|
+
.shapes i,.panel header i.k{display:inline-block;width:14px;height:14px;border-radius:3px}
|
|
3603
|
+
.shapes i.one,i.k.one{background:var(--faint)}
|
|
3604
|
+
.shapes i.more,i.k.more{background:var(--accent-soft);box-shadow:inset 0 0 0 1.5px var(--accent)}
|
|
3605
|
+
.panel header i.k{width:10px;height:10px;vertical-align:-1px;margin:0 4px 0 10px}
|
|
3606
|
+
.shapes .over{font-size:11.5px;color:var(--muted);margin-left:4px}
|
|
3607
|
+
.shapes .unmeasured{font-size:12px;color:var(--faint);font-style:italic}
|
|
3608
|
+
.shapes .val{text-align:right;font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}
|
|
3609
|
+
.shapes .drift .val b{color:var(--ink)}
|
|
3610
|
+
.shapes .even .val b{color:var(--success)}
|
|
3611
|
+
section{scroll-margin-top:16px}
|
|
3612
|
+
.panel{background:var(--surface);border:1px solid var(--line);border-radius:10px;margin:16px 0 0;box-shadow:var(--shadow);overflow:hidden}
|
|
3613
|
+
.panel>header{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:12px 16px;border-bottom:1px solid var(--line);background:var(--raised)}
|
|
3614
|
+
.panel>header h2,.panel>header h3{font-size:14px;font-weight:600}
|
|
3615
|
+
.panel>header .aside{font-size:12px;color:var(--muted);text-align:right}
|
|
3616
|
+
.panel>.body{padding:14px 16px}
|
|
3617
|
+
.panel>.body>*+*{margin-top:10px}
|
|
3618
|
+
.panel>.body+.findings{border-top:1px solid var(--line)}
|
|
3619
|
+
.muted,small{color:var(--muted)}
|
|
3620
|
+
small{font-size:12px}
|
|
3621
|
+
.next{font-size:13px;color:var(--ink-2)}
|
|
3622
|
+
.next strong{color:var(--accent);font-weight:600}
|
|
3623
|
+
.none{color:var(--success);font-weight:500;font-size:13px}
|
|
3624
|
+
.incomplete{color:var(--muted);font-size:12.5px;margin-top:12px}
|
|
3625
|
+
.incomplete summary{color:var(--muted)}
|
|
3626
|
+
.unread{margin:8px 0 0;padding-left:18px;font-size:12px}
|
|
3627
|
+
.legend .alert{position:relative;margin-left:auto}
|
|
3628
|
+
.legend .alert+.of{margin-left:0}
|
|
3629
|
+
.legend .alert a{display:inline-flex;align-items:center;gap:6px;height:22px;padding:0 8px;border:1px solid var(--line);border-radius:6px;color:var(--muted);font-size:11.5px}
|
|
3630
|
+
.legend .alert a:hover,.legend .alert a:focus-visible{color:var(--ink);border-color:var(--line-strong);text-decoration:none}
|
|
3631
|
+
.legend .alert i{display:inline-grid;place-items:center;width:13px;height:13px;border-radius:50%;background:var(--danger-soft);color:var(--danger);font-style:normal;font-size:9.5px;font-weight:700}
|
|
3632
|
+
.legend .tip{position:absolute;z-index:5;top:calc(100% + 6px);right:0;width:260px;padding:8px 10px;border:1px solid var(--line-strong);border-radius:8px;background:var(--surface);color:var(--ink-2);font-size:12px;line-height:1.45;box-shadow:0 8px 24px rgba(0,0,0,.12);opacity:0;visibility:hidden;transition:opacity .12s}
|
|
3633
|
+
.legend .alert:hover .tip,.legend .alert:focus-within .tip{opacity:1;visibility:visible}
|
|
3634
|
+
.unread-at{scroll-margin-top:16px}
|
|
3561
3635
|
ol.findings,ul.findings{list-style:none;padding:0;margin:0}
|
|
3562
|
-
.finding
|
|
3563
|
-
.
|
|
3564
|
-
.
|
|
3565
|
-
.
|
|
3566
|
-
.
|
|
3567
|
-
.
|
|
3568
|
-
.
|
|
3569
|
-
.
|
|
3570
|
-
.
|
|
3571
|
-
.
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
.
|
|
3576
|
-
.
|
|
3577
|
-
.
|
|
3578
|
-
.
|
|
3579
|
-
.
|
|
3580
|
-
.
|
|
3581
|
-
.
|
|
3582
|
-
.
|
|
3583
|
-
.
|
|
3584
|
-
|
|
3585
|
-
|
|
3636
|
+
.finding{padding:14px 16px;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:8px}
|
|
3637
|
+
.finding:first-child{border-top:0}
|
|
3638
|
+
.head{display:flex;align-items:flex-start;gap:12px}
|
|
3639
|
+
.count{flex:none;width:44px;padding:4px 0 3px;border-radius:6px;background:var(--neutral-soft);color:var(--ink);font-weight:650;font-size:13px;line-height:1.1;display:flex;flex-direction:column;align-items:center;font-variant-numeric:tabular-nums}
|
|
3640
|
+
.count small{font-size:9.5px;font-weight:500;color:inherit;opacity:.8;letter-spacing:.02em}
|
|
3641
|
+
.head .what{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
3642
|
+
.finding .title{font-weight:600;font-size:13.5px;line-height:1.4}
|
|
3643
|
+
.finding .what .muted{font-size:12.5px}
|
|
3644
|
+
.chip::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--kind,var(--faint));order:-1}
|
|
3645
|
+
.chip:has(.swatch)::before{display:none}
|
|
3646
|
+
[data-kind=near-duplicate]{--kind:var(--k-dup)}
|
|
3647
|
+
[data-kind=hardcoded-value]{--kind:var(--k-hard)}
|
|
3648
|
+
[data-kind=off-library]{--kind:var(--k-off)}
|
|
3649
|
+
.chip{flex:none;display:inline-flex;align-items:center;gap:6px;height:22px;padding:0 8px;border-radius:6px;background:var(--neutral-soft);color:var(--ink-2);font-size:11.5px;font-weight:500;white-space:nowrap}
|
|
3650
|
+
.swatch{display:inline-block;width:12px;height:12px;border-radius:3px;border:1px solid var(--line-strong);flex:none}
|
|
3651
|
+
.specs{display:flex;flex-wrap:wrap;gap:6px;padding:0 0 0 56px;margin:0;list-style:none}
|
|
3652
|
+
.specs li{height:22px;padding:0 8px;border-radius:6px;border:1px solid var(--line);background:var(--sunken);display:inline-flex;align-items:center;font-family:"Geist Mono","Geist Mono Variable",ui-monospace,"SF Mono",Menlo,monospace;font-size:11.5px;color:var(--ink-2)}
|
|
3653
|
+
.compare{margin-left:56px;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);border:1px solid var(--line);border-radius:8px;overflow:hidden}
|
|
3654
|
+
.compare>.next,.compare>.steps,.compare>.more,.compare>.shot{margin:0;padding:10px 12px}
|
|
3655
|
+
.compare>.next{grid-column:1;grid-row:1 / span 2;border-right:1px solid var(--line);background:var(--sunken)}
|
|
3656
|
+
.compare>.next::before{content:"Canonical";display:block;font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ink-2);margin-bottom:4px}
|
|
3657
|
+
.compare>.next strong{display:none}
|
|
3658
|
+
.compare>.steps{grid-column:2;list-style:none;font-size:12.5px;color:var(--ink-2);display:flex;flex-direction:column;gap:6px}
|
|
3659
|
+
.compare>.steps::before{content:"Replace";display:block;font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
|
|
3660
|
+
.compare>.steps li,.compare>.more .steps li{padding-left:14px;position:relative}
|
|
3661
|
+
.compare>.steps li::before,.compare>.more .steps li::before{content:"";position:absolute;left:2px;top:.6em;width:5px;height:5px;border-radius:50%;background:var(--faint)}
|
|
3662
|
+
.compare>.more{grid-column:2;padding-top:0}
|
|
3663
|
+
.compare>.more .steps{list-style:none;padding:6px 0 0;margin:0;font-size:12.5px;color:var(--ink-2);display:flex;flex-direction:column;gap:6px}
|
|
3664
|
+
.compare>.shot{grid-column:1 / -1;border-top:1px solid var(--line);display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;background:var(--sunken)}
|
|
3665
|
+
.shot figure{margin:0;display:flex;flex-direction:column;gap:6px;min-width:0}
|
|
3666
|
+
.shot figcaption{font-size:11.5px;color:var(--muted)}
|
|
3667
|
+
.shot img{display:block;width:100%;max-width:360px;height:140px;object-fit:cover;object-position:top left;border-radius:6px;border:1px solid var(--line);background:var(--surface)}
|
|
3668
|
+
.steps{margin:0;padding-left:20px;color:var(--ink-2);font-size:12.5px}
|
|
3669
|
+
.more summary{cursor:pointer;color:var(--accent);font-size:12.5px;list-style:none}
|
|
3670
|
+
.more summary::-webkit-details-marker{display:none}
|
|
3671
|
+
.more summary::before{content:"+ "}
|
|
3672
|
+
.more[open] summary::before{content:"\u2212 "}
|
|
3673
|
+
.sites{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 0 56px;padding:0;list-style:none}
|
|
3674
|
+
.sites li{font-size:12px;color:var(--muted);display:inline-flex;align-items:center}
|
|
3675
|
+
.sites.code{display:block}
|
|
3676
|
+
.sites.code li{display:block;margin:6px 0}
|
|
3677
|
+
.finding>.next{margin-left:56px}
|
|
3678
|
+
.rows{list-style:none;padding:0;margin:0}
|
|
3679
|
+
.rows li{display:flex;justify-content:space-between;gap:12px;border-top:1px solid var(--line);padding:8px 0;font-size:13px}
|
|
3680
|
+
.rows li:first-child{border-top:0}
|
|
3681
|
+
.rows li span:last-child{color:var(--muted);text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
3682
|
+
.rows small{font-family:"Geist Mono","Geist Mono Variable",ui-monospace,"SF Mono",Menlo,monospace;font-size:11.5px;margin-left:6px}
|
|
3683
|
+
h4{font-size:12px;font-weight:600;color:var(--muted);margin-top:16px}
|
|
3684
|
+
.agent{display:flex;flex-direction:column;gap:4px;background:var(--surface);border:1px solid var(--line);border-left:3px solid var(--accent);border-radius:10px;padding:12px 16px;margin:16px 0 0;font-size:13px}
|
|
3685
|
+
.agent strong{font-weight:600}
|
|
3686
|
+
.looks{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,300px),1fr));gap:12px}
|
|
3687
|
+
.looks figure{margin:0;border:1px solid var(--line);border-radius:8px;overflow:hidden;background:var(--sunken);min-width:0}
|
|
3688
|
+
.looks figcaption{display:flex;justify-content:space-between;align-items:baseline;gap:8px;padding:8px 12px;border-bottom:1px solid var(--line);background:var(--surface);font-size:13px;font-weight:600}
|
|
3689
|
+
.looks figcaption small{min-width:0;font-weight:400;font-family:"Geist Mono","Geist Mono Variable",ui-monospace,"SF Mono",Menlo,monospace;font-size:11px;text-align:right}
|
|
3690
|
+
.looks .pair{display:grid;grid-template-columns:1fr 1fr}
|
|
3691
|
+
.looks img{display:block;width:100%;height:220px;object-fit:cover;object-position:top left}
|
|
3692
|
+
.looks figure>p{padding:8px 12px;border-top:1px solid var(--line);font-size:12px}
|
|
3693
|
+
.project{margin-top:40px}
|
|
3694
|
+
.project>h2{font-size:18px;font-weight:650;display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
|
|
3695
|
+
.project>h2 small{font-family:"Geist Mono","Geist Mono Variable",ui-monospace,"SF Mono",Menlo,monospace;font-weight:400}
|
|
3696
|
+
.projects nav ul{list-style:none;padding:0;margin:0}
|
|
3697
|
+
.projects nav li{border-top:1px solid var(--line);padding:6px 0;font-size:13px}
|
|
3698
|
+
.projects nav li:first-child{border-top:0}
|
|
3699
|
+
footer{margin-top:48px;color:var(--muted);font-size:12px;border-top:1px solid var(--line);padding-top:16px;display:flex;flex-direction:column;gap:6px}
|
|
3700
|
+
@media (max-width:860px){
|
|
3701
|
+
.shell{display:block}
|
|
3702
|
+
.rail{position:sticky;top:0;z-index:2;height:auto;flex-direction:row;align-items:center;gap:8px;padding:8px 12px;border-right:0;border-bottom:1px solid var(--line);overflow-x:auto;overflow-y:hidden}
|
|
3703
|
+
.rail .group,.rail .foot,.brand span:last-child{display:none}
|
|
3704
|
+
.rail nav{flex-direction:row;gap:4px}
|
|
3705
|
+
.rail a{white-space:nowrap;height:28px;border:1px solid var(--line)}
|
|
3706
|
+
main{padding:20px 16px 48px}
|
|
3707
|
+
html{scroll-padding-top:60px}
|
|
3708
|
+
section{scroll-margin-top:60px}
|
|
3709
|
+
.panel>header .aside{display:none}
|
|
3710
|
+
.specs,.sites,.compare,.finding>.next{margin-left:0;padding-left:0}
|
|
3711
|
+
.compare{grid-template-columns:minmax(0,1fr)}
|
|
3712
|
+
.compare>.next{grid-row:auto;border-right:0;border-bottom:1px solid var(--line)}
|
|
3713
|
+
.compare>.steps,.compare>.more{grid-column:1}
|
|
3714
|
+
.compare>.shot{grid-template-columns:minmax(0,1fr)}
|
|
3715
|
+
.say{font-size:20px}
|
|
3716
|
+
.legend .of{margin-left:0}
|
|
3717
|
+
.shapes li{grid-template-columns:minmax(0,1fr) auto;gap:8px}
|
|
3718
|
+
.shapes .sq{grid-column:1 / -1;grid-row:2}
|
|
3719
|
+
}
|
|
3586
3720
|
`;
|
|
3721
|
+
var FACT = /import \{[^}]*\} from "[^&]*"|var\(--[\w-]+\)|(?<![\w/.-])(?:[\w@.\[\]-]+\/)+[\w.\[\]-]+\.(?:tsx|ts|jsx|js|mjs|css|scss|vue|svelte|astro|html)(?::\d+)?|\b(?:color|radius|spacing|space|size|font|shadow|typography)\.[\w-]+/g;
|
|
3722
|
+
var rich = (text) => escape2(text).replace(FACT, (fact) => `<span class="mono">${fact}</span>`);
|
|
3587
3723
|
function next(text) {
|
|
3588
|
-
return `<p class="next"><strong>Next:</strong> ${
|
|
3724
|
+
return `<p class="next"><strong>Next:</strong> ${rich(text.replace(/\.$/, ""))}.</p>`;
|
|
3589
3725
|
}
|
|
3590
3726
|
var STEPS_SHOWN2 = 5;
|
|
3591
|
-
|
|
3592
|
-
|
|
3727
|
+
var KIND = { "near-duplicate": "Duplicate", "hardcoded-value": "Hardcoded", "off-library": "Off-library" };
|
|
3728
|
+
function splitMeasures(title) {
|
|
3729
|
+
const m = /\s*\(([^()]*\d[^()]*)\)/.exec(title);
|
|
3730
|
+
if (!m) return { text: title, measures: [] };
|
|
3731
|
+
return { text: (title.slice(0, m.index) + title.slice(m.index + m[0].length)).replace(/\s+/g, " ").trim(), measures: m[1].split(/,\s*/).filter(Boolean) };
|
|
3732
|
+
}
|
|
3733
|
+
var COLOUR = /#[0-9a-fA-F]{3,8}\b|\b(?:rgba?|hsla?|oklch|oklab|lab|lch)\([0-9.,%\s/+-]+\)/;
|
|
3734
|
+
function swatch(title) {
|
|
3735
|
+
const colour = COLOUR.exec(title)?.[0];
|
|
3736
|
+
return colour ? `<span class="swatch" style="background:${escape2(colour)}" aria-hidden="true"></span>` : "";
|
|
3737
|
+
}
|
|
3738
|
+
function shots(f, looks) {
|
|
3739
|
+
if (looks.size === 0) return "";
|
|
3740
|
+
const kept = /\(([^()]+\.[a-z]+)\)/.exec(f.lead ?? "")?.[1];
|
|
3741
|
+
const copy = (f.steps ?? []).map((s) => /^In ([^,]+),/.exec(s)?.[1]).find((file) => file !== void 0 && file !== kept && looks.has(file));
|
|
3742
|
+
const pair = [];
|
|
3743
|
+
if (kept && looks.get(kept)?.light) pair.push(["Keep", looks.get(kept)]);
|
|
3744
|
+
if (copy && looks.get(copy)?.light) pair.push(["Copy", looks.get(copy)]);
|
|
3745
|
+
if (pair.length === 0) return "";
|
|
3746
|
+
return `<div class="shot">${pair.map(([label, look]) => `<figure><picture>${look.dark ? `<source media="(prefers-color-scheme: dark)" srcset="${look.dark}">` : ""}<img alt="${escape2(look.component)} (${label.toLowerCase()})" src="${look.light}"></picture><figcaption>${label} \xB7 ${escape2(look.component)} <span class="mono">${escape2(look.file)}</span></figcaption></figure>`).join("")}</div>`;
|
|
3747
|
+
}
|
|
3748
|
+
function finding2(f, showProject, looks) {
|
|
3749
|
+
const { text, measures } = f.kind === "near-duplicate" ? splitMeasures(f.title) : { text: f.title, measures: [] };
|
|
3750
|
+
const where = showProject && f.project !== "." ? `<p class="muted">in <span class="mono">${escape2(f.project)}</span></p>` : "";
|
|
3751
|
+
const head = `<div class="head"><span class="count">${f.count}<small>${f.count === 1 ? "place" : "places"}</small></span><div class="what"><p class="title">${rich(text)}</p>${f.detail ? `<p class="muted">${escape2(f.detail)}</p>` : ""}${where}</div><span class="chip">${f.kind === "hardcoded-value" ? swatch(f.title) : ""}${KIND[f.kind]}</span></div>`;
|
|
3752
|
+
const specs = measures.length > 0 ? `<ul class="specs">${measures.map((m) => `<li>${escape2(m)}</li>`).join("")}</ul>` : "";
|
|
3593
3753
|
if (f.lead && f.steps && f.steps.length > 0) {
|
|
3594
|
-
const item = (step) => `<li>${
|
|
3754
|
+
const item = (step) => `<li>${rich(step.replace(/\.$/, ""))}</li>`;
|
|
3595
3755
|
const rest = f.steps.slice(STEPS_SHOWN2);
|
|
3596
3756
|
const more2 = rest.length > 0 ? `<details class="more"><summary>and ${plural4(rest.length, "more file")}</summary><ol class="steps" start="${STEPS_SHOWN2 + 1}">${rest.map(item).join("")}</ol></details>` : "";
|
|
3597
|
-
return `<li class="finding" data-kind="${f.kind}">${head}${next(f.lead)}<ol class="steps">${f.steps.slice(0, STEPS_SHOWN2).map(item).join("")}</ol>${more2}</li>`;
|
|
3757
|
+
return `<li class="finding" data-kind="${f.kind}">${head}${specs}<div class="compare">${next(f.lead)}<ol class="steps">${f.steps.slice(0, STEPS_SHOWN2).map(item).join("")}</ol>${more2}${shots(f, looks)}</div></li>`;
|
|
3598
3758
|
}
|
|
3759
|
+
const code = f.sites.some((s) => s.code);
|
|
3599
3760
|
const sites = f.sites.map((s) => `<li><code>${escape2(s.at)}</code>${s.code ? `<pre>${escape2(s.code)}</pre>` : ""}</li>`).join("");
|
|
3600
|
-
const more = f.count > f.sites.length ? `<li
|
|
3601
|
-
return `<li class="finding" data-kind="${f.kind}">${head}<ul class="sites">${sites}${more}</ul>${next(f.next)}</li>`;
|
|
3761
|
+
const more = f.count > f.sites.length ? `<li>+ ${plural4(f.count - f.sites.length, "more place")}</li>` : "";
|
|
3762
|
+
return `<li class="finding" data-kind="${f.kind}">${head}${specs}<ul class="sites${code ? " code" : ""}">${sites}${more}</ul>${next(f.next)}</li>`;
|
|
3763
|
+
}
|
|
3764
|
+
function findings(list, none, after, looks) {
|
|
3765
|
+
if (list.length === 0) return `<div class="body"><p class="none">${escape2(none)}</p>${next(after)}</div>`;
|
|
3766
|
+
return `<ul class="findings">${list.map((f) => finding2(f, false, looks)).join("")}</ul>`;
|
|
3602
3767
|
}
|
|
3603
|
-
function
|
|
3604
|
-
|
|
3605
|
-
return `<ul class="findings">${list.map((f) => finding2(f, false)).join("")}</ul>`;
|
|
3768
|
+
function panel(name, id, title, aside, body, level = "h3") {
|
|
3769
|
+
return `<section class="panel" data-section="${name}" id="${id}"><header><${level}>${escape2(title)}</${level}>${aside ? `<span class="aside">${aside}</span>` : ""}</header>${body}</section>`;
|
|
3606
3770
|
}
|
|
3607
|
-
function projectSection(p, many) {
|
|
3771
|
+
function projectSection(p, many, looks) {
|
|
3772
|
+
const id = anchor(p.dir);
|
|
3608
3773
|
const where = p.dir === "." ? "" : ` --project ${p.dir}`;
|
|
3609
|
-
const libraries = p.libraries.length === 0 ? `<p class="none">No UI library found.</p>${next(`if it uses one, run ${NPX} detect ${p.dir} to see what was read`)}
|
|
3610
|
-
const
|
|
3611
|
-
const
|
|
3612
|
-
const
|
|
3613
|
-
const
|
|
3614
|
-
const
|
|
3615
|
-
return `<section class="project" data-section="project" data-project="${escape2(p.dir)}" id="${
|
|
3774
|
+
const libraries = p.libraries.length === 0 ? `<div class="body"><p class="none">No UI library found.</p>${next(`if it uses one, run ${NPX} detect ${p.dir} to see what was read`)}</div>` : `<div class="body"><ul class="rows">${p.libraries.map((l) => `<li><span>${escape2(l.name)}${l.for.length > 0 ? ` <small>${escape2(l.for.join(", "))}</small>` : ""}</span><span>${escape2(l.confidence)} confidence</span></li>`).join("")}</ul>${p.unused.length > 0 ? `<p class="muted">Declared but never imported (not counted): ${escape2(p.unused.join(", "))}</p>` : ""}${next(`build new UI with these, not a new library: ${NPX} agents install tells your agent so`)}</div>`;
|
|
3775
|
+
const kits = p.kits.length > 0 ? `<p class="muted">Kits: ${p.kits.map((k) => `<code>${escape2(k.path)}</code> (${escape2(k.kind)}, ${plural4(k.members, "member")})`).join(", ")}</p>` : "";
|
|
3776
|
+
const components = p.components.length > 0 ? `<h4>Most used</h4><ul class="rows">${p.components.map((c) => `<li><span>${escape2(c.name)}<small>${escape2(c.file)}</small></span><span>${plural4(c.uses, "use")}</span></li>`).join("")}</ul>` : "";
|
|
3777
|
+
const inventory = `<div class="body"><p>${plural4(p.definitions, "component")} defined in ${plural4(p.files, "file")}; ${plural4(p.uses, "UI element")} in use (buttons, links styled as buttons, inputs, cards, badges, dialogs, tabs).</p>${p.roles.length > 0 ? `<ul class="rows">${p.roles.map((r) => `<li><span class="mono">${escape2(r.role)}</span><span>${plural4(r.uses, "use")} \xB7 ${plural4(r.families, "shape")}</span></li>`).join("")}</ul>` : ""}${kits}${components}${next(`see every look grouped by shape with ${NPX} inventory${where}`)}</div>`;
|
|
3778
|
+
const unread = p.failed.length > 0 ? `<details class="incomplete more"><summary>${plural4(p.failed.length, "file")} not fully read; what is in them is unknown</summary><ul class="unread">${p.failed.map((f) => `<li>${rich(f)}</li>`).join("")}</ul></details>` : "";
|
|
3779
|
+
const count = (list) => list.length === 0 ? "None" : `${plural4(list.length, "finding")} \xB7 ${plural4(list.reduce((n, f) => n + f.count, 0), "place")}`;
|
|
3780
|
+
return `<section class="project" data-section="project" data-project="${escape2(p.dir)}" id="${id}">
|
|
3616
3781
|
<h2>${escape2(many || p.dir !== "." ? p.dir : "This repo")}${p.name && p.name !== p.dir ? ` <small>${escape2(p.name)}</small>` : ""}</h2>
|
|
3617
|
-
${incomplete.map((i) => `<p class="incomplete">${escape2(i)}</p>`).join("")}
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3782
|
+
${p.incomplete.length > 0 || p.failed.length > 0 ? `<div class="unread-at" id="${id}-unread">${p.incomplete.map((i) => `<p class="incomplete">${escape2(i)}</p>`).join("")}${unread}</div>` : ""}
|
|
3783
|
+
${panel("near-duplicates", `${id}-near`, "Near-duplicate groups", count(p.nearDuplicates), `<div class="body"><p class="muted">Components that look the same (height, padding, corners, text size and weight) but are built more than once.</p></div>${findings(p.nearDuplicates, "None found: each look is built once.", `keep it that way: ${NPX} agents install has your agent reuse these components`, looks)}`)}
|
|
3784
|
+
${panel("hardcoded", `${id}-hardcoded`, "Hardcoded values that should be tokens", count(p.hardcoded), `<div class="body"><p class="muted">Colours, spacing and radii written as literals although a theme variable holds the same value.</p></div>${findings(p.hardcoded, "None found.", `run ${NPX} check --diff on each change to keep new literals out`, looks)}`)}
|
|
3785
|
+
${panel("off-library", `${id}-off`, "Off-library imports", count(p.offLibrary), `<div class="body"><p class="muted">Imports of an element from a library other than the one the rest of the code uses for it.</p></div>${findings(p.offLibrary, "None found.", `run ${NPX} check --diff on each change to catch a second library early`, looks)}`)}
|
|
3786
|
+
${panel("inventory", `${id}-inventory`, "Component inventory", `${plural4(p.definitions, "component")} \xB7 ${plural4(p.uses, "use")}`, inventory)}
|
|
3787
|
+
${panel("libraries", `${id}-libraries`, "UI libraries", p.libraries.length > 0 ? plural4(p.libraries.length, "library", "libraries") : "", libraries)}
|
|
3623
3788
|
${p.notes.map((n) => `<p class="muted">${escape2(n.replace(/\.?$/, "."))}</p>`).join("")}
|
|
3624
3789
|
</section>`;
|
|
3625
3790
|
}
|
|
3626
|
-
function
|
|
3791
|
+
function looksSection(model) {
|
|
3627
3792
|
const drawn = model.projects.flatMap((p) => p.rendered.map((r) => ({ ...r, project: p.dir })));
|
|
3628
|
-
const body = drawn.length > 0 ? `<div class="looks">${drawn.map((r) => `<figure data-component="${escape2(r.component)}"><figcaption>${escape2(r.component)} <small>${escape2(r.file)}</small></figcaption><div class="pair">${r.light ? `<img alt="${escape2(r.component)}, light" src="${r.light}">` : ""}${r.dark ? `<img alt="${escape2(r.component)}, dark" src="${r.dark}">` : ""}</div>${r.looks.length > 0 ? `<p class="muted">${
|
|
3629
|
-
|
|
3793
|
+
const body = drawn.length > 0 ? `<div class="looks">${drawn.map((r) => `<figure data-component="${escape2(r.component)}"><figcaption>${escape2(r.component)} <small>${escape2(r.file)}</small></figcaption><div class="pair">${r.light ? `<img alt="${escape2(r.component)}, light" src="${r.light}">` : ""}${r.dark ? `<img alt="${escape2(r.component)}, dark" src="${r.dark}">` : ""}</div>${r.looks.length > 0 ? `<p class="muted">${plural4(r.looks.length, "look")} drawn, light and dark</p>` : ""}</figure>`).join("")}</div>` : "";
|
|
3794
|
+
const after = model.render.next ? next(model.render.next) : drawn.length > 0 ? next(`see a component after you change it with ${NPX} render <file>`) : next(model.render.state === "off" ? `run ${NPX} --render to draw the most-used components (it asks before downloading the ~100 MB headless Chromium, once)` : `run ${NPX} render <file> to see why a component could not be drawn`);
|
|
3795
|
+
return `<section class="panel" data-section="looks" data-render="${model.render.state}" id="looks"><header><h2>Rendered looks</h2><span class="aside">${drawn.length > 0 ? plural4(drawn.length, "component") : ""}</span></header><div class="body">${body}<p class="${drawn.length > 0 ? "muted" : ""}">${escape2(model.render.note)}</p>${after}</div></section>`;
|
|
3796
|
+
}
|
|
3797
|
+
function placesByKind(model) {
|
|
3798
|
+
const places = (pick) => model.projects.reduce((n, p) => n + pick(p).reduce((m, f) => m + f.count, 0), 0);
|
|
3799
|
+
return [
|
|
3800
|
+
{ label: "Near-duplicates", places: places((p) => p.nearDuplicates) },
|
|
3801
|
+
{ label: "Hardcoded values", places: places((p) => p.hardcoded) },
|
|
3802
|
+
{ label: "Off-library imports", places: places((p) => p.offLibrary) }
|
|
3803
|
+
];
|
|
3804
|
+
}
|
|
3805
|
+
function headline(model) {
|
|
3806
|
+
const t = model.totals;
|
|
3807
|
+
const [near, hard, off] = placesByKind(model);
|
|
3808
|
+
const parts = [
|
|
3809
|
+
{ label: "Near-duplicates", places: near.places, href: model.projects.length > 1 ? "top-fixes" : `${anchor(model.projects[0]?.dir ?? ".")}-near` },
|
|
3810
|
+
{ label: "Hardcoded values", places: hard.places, href: model.projects.length > 1 ? "top-fixes" : `${anchor(model.projects[0]?.dir ?? ".")}-hardcoded` },
|
|
3811
|
+
{ label: "Off-library imports", places: off.places, href: model.projects.length > 1 ? "top-fixes" : `${anchor(model.projects[0]?.dir ?? ".")}-off` }
|
|
3812
|
+
];
|
|
3813
|
+
const total = parts.reduce((n2, part) => n2 + part.places, 0);
|
|
3814
|
+
const n = (value) => `<b class="${value > 0 ? "warn" : "ok"}">${value}</b>`;
|
|
3815
|
+
const unknown = model.projects.some((p) => p.incomplete.length > 0 || p.failed.length > 0);
|
|
3816
|
+
const analyses = model.projects.reduce((n2, p) => n2 + p.incomplete.length, 0);
|
|
3817
|
+
const unread = model.projects.reduce((n2, p) => n2 + p.failed.length, 0);
|
|
3818
|
+
const caveat = [analyses > 0 ? `${plural4(analyses, "analysis", "analyses")} could not run` : "", unread > 0 ? `${plural4(unread, "file")} could not be fully read` : ""].filter(Boolean).join(" and ");
|
|
3819
|
+
const said = [];
|
|
3820
|
+
if (t.nearDuplicates > 0) said.push(`${n(t.nearDuplicates)} ${t.nearDuplicates === 1 ? "look is" : "looks are"} built more than once`);
|
|
3821
|
+
if (t.hardcoded > 0) said.push(`${n(t.hardcoded)} ${t.hardcoded === 1 ? "value skips" : "values skip"} the theme`);
|
|
3822
|
+
if (t.offLibrary > 0) said.push(`${n(t.offLibrary)} ${t.offLibrary === 1 ? "import comes" : "imports come"} from a second library`);
|
|
3823
|
+
const sentence2 = said.length === 0 ? `Every look is built once and every value comes from the theme${unknown ? ", in what could be read" : ""}.` : `${said.length > 1 ? `${said.slice(0, -1).join(", ")} and ${said.at(-1)}` : said[0]}: <b class="warn">${total}</b> ${total === 1 ? "place" : "places"} to fix.`;
|
|
3824
|
+
const bar2 = total > 0 ? `<div class="split" role="img" aria-label="${parts.map((part) => `${part.label} ${part.places}`).join(", ")}">${parts.map((part, i) => part.places > 0 ? `<span class="seg s${i}" style="width:${(part.places / total * 100).toFixed(2)}%"></span>` : "").join("")}</div>` : "";
|
|
3825
|
+
const legend = `<ul class="legend">${parts.map((part, i) => `<li><a href="#${part.href}"><span class="key s${i}${part.places === 0 ? " zero" : ""}"></span>${escape2(part.label)} <b>${part.places}</b>${part.places === 0 ? ` <span class="clean">Clean</span>` : ""}</a></li>`).join("")}${unknown ? `<li class="alert"><a href="#${anchor(model.projects.find((p) => p.failed.length > 0 || p.incomplete.length > 0)?.dir ?? ".")}-unread" aria-describedby="unread-tip"><i aria-hidden="true">!</i>${analyses + unread} not read</a><span class="tip" id="unread-tip" role="tooltip">${escape2(caveat.charAt(0).toUpperCase() + caveat.slice(1))}: what is in them is unknown, not zero.</span></li>` : ""}<li class="of">${plural4(t.definitions, "component")} \xB7 ${plural4(t.uses, "UI element")} in use \xB7 ${plural4(t.files, "file")}</li></ul>`;
|
|
3826
|
+
return `<section class="headline" data-section="headline"><p class="say">${sentence2}</p>${bar2}${legend}</section>`;
|
|
3827
|
+
}
|
|
3828
|
+
var SQUARES = 40;
|
|
3829
|
+
function shapeMap(model) {
|
|
3830
|
+
const roles = /* @__PURE__ */ new Map();
|
|
3831
|
+
for (const p of model.projects) for (const r of p.roles) {
|
|
3832
|
+
const was = roles.get(r.role) ?? { uses: 0, families: 0 };
|
|
3833
|
+
roles.set(r.role, { uses: was.uses + r.uses, families: was.families + r.families });
|
|
3834
|
+
}
|
|
3835
|
+
if (roles.size === 0) return "";
|
|
3836
|
+
const rows = [...roles].map(([role, { uses, families }]) => {
|
|
3837
|
+
const shown2 = Math.min(families, SQUARES);
|
|
3838
|
+
const squares = families === 0 ? `<span class="unmeasured">shape not measured</span>` : `${Array.from({ length: shown2 }, (_, i) => `<i class="${i === 0 ? "one" : "more"}"></i>`).join("")}${families > shown2 ? `<span class="over">+${families - shown2}</span>` : ""}`;
|
|
3839
|
+
return `<li class="${families > 1 ? "drift" : families === 1 ? "even" : ""}"><span class="role mono">${escape2(role)}</span><span class="sq">${squares}</span><span class="val"><b>${families}</b> ${families === 1 ? "shape" : "shapes"} \xB7 ${plural4(uses, "use")}</span></li>`;
|
|
3840
|
+
}).join("");
|
|
3841
|
+
const many = model.projects.length > 1 ? ", summed over the projects" : "";
|
|
3842
|
+
return `<section class="panel" data-section="shapes" id="shapes"><header><h2>Shapes per element</h2><span class="aside"><i class="k one"></i>first shape <i class="k more"></i>each extra shape</span></header><div class="body"><p class="muted">One square per distinct look of each kind of element${many}. One square is consistent; every extra square is a copy someone built.</p><ul class="shapes">${rows}</ul></div></section>`;
|
|
3843
|
+
}
|
|
3844
|
+
function rail(model, many) {
|
|
3845
|
+
const t = model.totals;
|
|
3846
|
+
const link = (href, label, n, warn = false) => `<a href="#${href}"><span>${escape2(label)}</span>${n === void 0 ? "" : `<span class="n${warn && n > 0 ? " warn" : ""}">${n}</span>`}</a>`;
|
|
3847
|
+
const report = [link("overview", "Overview"), link("shapes", "Shapes"), link("top-fixes", "Top fixes", model.topFixes.length, true), link("looks", "Rendered looks")];
|
|
3848
|
+
const projects = many ? model.projects.map((p) => link(anchor(p.dir), p.dir, p.nearDuplicates.length + p.hardcoded.length + p.offLibrary.length, true)) : model.projects.flatMap((p) => {
|
|
3849
|
+
const id = anchor(p.dir);
|
|
3850
|
+
return [link(`${id}-near`, "Near-duplicates", t.nearDuplicates, true), link(`${id}-hardcoded`, "Hardcoded values", t.hardcoded, true), link(`${id}-off`, "Off-library imports", t.offLibrary, true), link(`${id}-inventory`, "Inventory", t.definitions), link(`${id}-libraries`, "UI libraries", t.libraries.length)];
|
|
3851
|
+
});
|
|
3852
|
+
return `<aside class="rail"><div class="brand"><span class="mark">Y</span><span>youiyoux</span></div><nav aria-label="Sections"><div class="group">Report</div>${report.join("")}<div class="group">${many ? "Projects" : "Findings"}</div>${projects.join("")}</nav><p class="foot">Self-contained. Nothing was uploaded.</p></aside>`;
|
|
3630
3853
|
}
|
|
3631
3854
|
function renderHtml(model) {
|
|
3632
3855
|
const t = model.totals;
|
|
3633
3856
|
const many = model.projects.length > 1;
|
|
3634
|
-
const stat3 = (label, value, tone2 = "") => `<div class="${tone2}"><dt>${escape2(label)}</dt><dd>${value}</dd></div>`;
|
|
3635
|
-
const tone = (n) => n > 0 ? "warn" : "ok";
|
|
3636
3857
|
const date = model.generatedAt.slice(0, 10);
|
|
3637
|
-
const
|
|
3858
|
+
const looks = new Map(model.projects.flatMap((p) => p.rendered.map((r) => [r.file, r])));
|
|
3859
|
+
const top = model.topFixes.length > 0 ? `<ol class="findings">${model.topFixes.map((f) => finding2(f, many, looks)).join("")}</ol>` : `<div class="body"><p class="none">Nothing to fix first.</p>${next(`${NPX} agents install, so your agent keeps it that way`)}</div>`;
|
|
3860
|
+
const libraries = t.libraries.length > 0 ? `<li>${escape2(t.libraries.join(" \xB7 "))}</li>` : "";
|
|
3638
3861
|
return `<!doctype html>
|
|
3639
3862
|
<html lang="en">
|
|
3640
3863
|
<head>
|
|
@@ -3647,24 +3870,27 @@ function renderHtml(model) {
|
|
|
3647
3870
|
<style>${CSS}</style>
|
|
3648
3871
|
</head>
|
|
3649
3872
|
<body>
|
|
3873
|
+
<div class="shell">
|
|
3874
|
+
${rail(model, many)}
|
|
3650
3875
|
<main>
|
|
3651
|
-
<header>
|
|
3652
|
-
<p class="
|
|
3876
|
+
<header class="top" id="overview">
|
|
3877
|
+
<p class="crumb">youiyoux report</p>
|
|
3653
3878
|
<h1>${escape2(model.repo)}</h1>
|
|
3654
|
-
<
|
|
3655
|
-
<ul class="summary">${model.summary.map((line) => `<li>${escape2(line)}</li>`).join("")}</ul>
|
|
3656
|
-
<dl class="stats">${stat3("Components defined", t.definitions)}${stat3("Near-duplicate groups", t.nearDuplicates, tone(t.nearDuplicates))}${stat3("Hardcoded values", t.hardcoded, tone(t.hardcoded))}${stat3("Off-library imports", t.offLibrary, tone(t.offLibrary))}</dl>
|
|
3879
|
+
<ul class="facts"><li><b>${t.files}</b> files</li><li><b>${t.projects}</b> ${t.projects === 1 ? "project" : "projects"}</li>${libraries}<li class="mono">${escape2(date)}</li><li class="mono">${(model.durationMs / 1e3).toFixed(1)} s</li></ul>
|
|
3657
3880
|
</header>
|
|
3658
|
-
|
|
3881
|
+
${headline(model)}
|
|
3882
|
+
${shapeMap(model)}
|
|
3883
|
+
${panel("top-fixes", "top-fixes", "Top fixes", "Ranked by how many places each one touches", top, "h2")}
|
|
3659
3884
|
<aside class="agent" data-section="agent"><p><strong>${escape2(model.agent)}</strong></p>${next(`${NPX} agents install, then restart your agent`)}</aside>
|
|
3660
|
-
${
|
|
3661
|
-
<section data-section="projects"><h2>Projects</h2><p>${escape2(model.layout.charAt(0).toUpperCase() + model.layout.slice(1))}.</p>${many ? `<nav><ul>${model.projects.map((p) => `<li><a href="#${anchor(p.dir)}">${escape2(p.dir)}</a>${p.name && p.name !== p.dir ? ` <small>${escape2(p.name)}</small>` : ""}</li>`).join("")}</ul></nav>` : ""}${next(many ? "open each project below; each has its own fixes" : "read this repo's section below")}</section>
|
|
3662
|
-
${model.projects.map((p) => projectSection(p, many)).join("\n")}
|
|
3885
|
+
${looksSection(model)}
|
|
3886
|
+
<section class="panel projects" data-section="projects" id="projects"><header><h2>Projects</h2><span class="aside">${plural4(model.projects.length, "project")}</span></header><div class="body"><p>${escape2(model.layout.charAt(0).toUpperCase() + model.layout.slice(1))}.</p>${many ? `<nav><ul>${model.projects.map((p) => `<li><a href="#${anchor(p.dir)}">${escape2(p.dir)}</a>${p.name && p.name !== p.dir ? ` <small>${escape2(p.name)}</small>` : ""}</li>`).join("")}</ul></nav>` : ""}${next(many ? "open each project below; each has its own fixes" : "read this repo's section below")}</div></section>
|
|
3887
|
+
${model.projects.map((p) => projectSection(p, many, looks)).join("\n")}
|
|
3663
3888
|
<footer>
|
|
3664
3889
|
<p>Made with ${escape2(NPX)} on ${escape2(date)} in ${(model.durationMs / 1e3).toFixed(1)} s. This file is self-contained: it loads nothing from the network, and nothing was uploaded.</p>
|
|
3665
3890
|
<p>Paths are relative to the repo. ${model.includeCode ? "Lines of code were included (--include-code); keys, tokens and environment values in them were removed." : "Includes relative paths, component names, class lists, counts and rendered images when available. Literal prop values and text content are omitted from captions and fixes; --include-code adds source lines and full captions."} Home paths, usernames, environment values and credentials were removed.</p>
|
|
3666
3891
|
</footer>
|
|
3667
3892
|
</main>
|
|
3893
|
+
</div>
|
|
3668
3894
|
</body>
|
|
3669
3895
|
</html>
|
|
3670
3896
|
`;
|
|
@@ -3779,9 +4005,11 @@ function renderMarkdown(model, reportFile) {
|
|
|
3779
4005
|
"",
|
|
3780
4006
|
...model.summary.map((line) => `- ${md(line)}`),
|
|
3781
4007
|
"",
|
|
3782
|
-
"| Projects | Components |
|
|
4008
|
+
"| Projects | Components | Duplicated components | Hardcoded values | Off-library imports |",
|
|
3783
4009
|
"| ---: | ---: | ---: | ---: | ---: |",
|
|
3784
|
-
`| ${t.projects} | ${t.definitions} | ${t.
|
|
4010
|
+
`| ${t.projects} | ${t.definitions} | ${t.componentDuplicates} | ${t.hardcoded} | ${t.offLibrary} |`,
|
|
4011
|
+
"",
|
|
4012
|
+
md(DUPLICATE_RULE),
|
|
3785
4013
|
""
|
|
3786
4014
|
];
|
|
3787
4015
|
if (model.topFixes.length > 0) {
|
|
@@ -3993,12 +4221,7 @@ async function runReport(options = {}, io = {}) {
|
|
|
3993
4221
|
const t = clean.totals;
|
|
3994
4222
|
const card = {
|
|
3995
4223
|
repo: redactor.scrub(clean.repo),
|
|
3996
|
-
|
|
3997
|
-
{ value: t.definitions, label: ["components", "defined"] },
|
|
3998
|
-
{ value: t.nearDuplicates, label: ["near-", "duplicates"], tone: t.nearDuplicates > 0 ? "warn" : "ok" },
|
|
3999
|
-
{ value: t.hardcoded, label: ["hardcoded", "values"], tone: t.hardcoded > 0 ? "warn" : "ok" },
|
|
4000
|
-
{ value: t.offLibrary, label: ["off-library", "imports"], tone: t.offLibrary > 0 ? "warn" : "ok" }
|
|
4001
|
-
],
|
|
4224
|
+
parts: placesByKind(clean),
|
|
4002
4225
|
footer: redactor.scrub(t.libraries.length > 0 ? `UI libraries: ${t.libraries.join(", ")}` : "No UI library found")
|
|
4003
4226
|
};
|
|
4004
4227
|
const html = redactor.scrub(renderHtml(clean));
|
|
@@ -4011,7 +4234,7 @@ async function runReport(options = {}, io = {}) {
|
|
|
4011
4234
|
return { code: 0, files, model: clean };
|
|
4012
4235
|
}
|
|
4013
4236
|
const width = Math.max(...["Report", "Card", "Summary"].map((w) => w.length)) + 2;
|
|
4014
|
-
const
|
|
4237
|
+
const looks = clean.render.state === "drawn" ? "" : clean.render.state === "off" && !renderNote ? `; to add rendered looks, run ${NPX} --render` : "";
|
|
4015
4238
|
print2([
|
|
4016
4239
|
...clean.summary,
|
|
4017
4240
|
"",
|
|
@@ -4019,7 +4242,7 @@ async function runReport(options = {}, io = {}) {
|
|
|
4019
4242
|
`${"Card".padEnd(width)}${shown(files.card)}`,
|
|
4020
4243
|
`${"Summary".padEnd(width)}${shown(files.summary)}`,
|
|
4021
4244
|
"",
|
|
4022
|
-
`Next: open ${shown(files.report)} in a browser and start with its top fix; set up youiyoux for your agent with ${NPX} agents install${
|
|
4245
|
+
`Next: open ${shown(files.report)} in a browser and start with its top fix; set up youiyoux for your agent with ${NPX} agents install${looks}.`
|
|
4023
4246
|
].join("\n"));
|
|
4024
4247
|
return { code: 0, files, model: clean };
|
|
4025
4248
|
}
|
package/dist/youi.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire as __youiRequire } from "node:module"; const require = __youiRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
main
|
|
5
|
-
} from "./chunks/chunk-
|
|
5
|
+
} from "./chunks/chunk-CLNMVQZJ.js";
|
|
6
6
|
import "./chunks/chunk-FUB7B3VK.js";
|
|
7
7
|
import "./chunks/chunk-UHPK62Q5.js";
|
|
8
8
|
import "./chunks/chunk-3ZHGONNA.js";
|
package/dist/youiyoux.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire as __youiRequire } from "node:module"; const require = __
|
|
|
3
3
|
import {
|
|
4
4
|
COMMAND_NAMES,
|
|
5
5
|
main
|
|
6
|
-
} from "./chunks/chunk-
|
|
6
|
+
} from "./chunks/chunk-CLNMVQZJ.js";
|
|
7
7
|
import "./chunks/chunk-FUB7B3VK.js";
|
|
8
8
|
import "./chunks/chunk-UHPK62Q5.js";
|
|
9
9
|
import "./chunks/chunk-3ZHGONNA.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "youiyoux",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Your agent rebuilds components you already have. youiyoux catches it: npx youiyoux turns any repo into a shareable report of near-duplicate components, hardcoded token values and off-library imports, with the fixes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
},
|
|
15
15
|
"type": "module",
|
|
16
16
|
"bin": {
|
|
17
|
-
"youiyoux": "
|
|
18
|
-
"youi": "
|
|
17
|
+
"youiyoux": "dist/youiyoux.js",
|
|
18
|
+
"youi": "dist/youi.js"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"dist",
|
|
@@ -32,17 +32,12 @@
|
|
|
32
32
|
"ai-agents",
|
|
33
33
|
"claude-code",
|
|
34
34
|
"codex",
|
|
35
|
-
"cursor",
|
|
36
35
|
"mcp",
|
|
37
36
|
"tailwind",
|
|
38
37
|
"shadcn",
|
|
39
38
|
"react",
|
|
40
39
|
"audit"
|
|
41
40
|
],
|
|
42
|
-
"scripts": {
|
|
43
|
-
"build": "node build.mjs",
|
|
44
|
-
"prepack": "node build.mjs"
|
|
45
|
-
},
|
|
46
41
|
"dependencies": {
|
|
47
42
|
"@ast-grep/napi": "0.45.3",
|
|
48
43
|
"axe-core": "4.14.0",
|
|
@@ -51,11 +46,14 @@
|
|
|
51
46
|
"typescript": "6.0.3"
|
|
52
47
|
},
|
|
53
48
|
"devDependencies": {
|
|
54
|
-
"@youiyoux/cli": "
|
|
55
|
-
"@youiyoux/core": "
|
|
56
|
-
"@youiyoux/scanner": "
|
|
49
|
+
"@youiyoux/cli": "0.1.2",
|
|
50
|
+
"@youiyoux/core": "0.1.2",
|
|
51
|
+
"@youiyoux/scanner": "0.1.2"
|
|
57
52
|
},
|
|
58
53
|
"exports": {
|
|
59
54
|
"./package.json": "./package.json"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "node build.mjs"
|
|
60
58
|
}
|
|
61
|
-
}
|
|
59
|
+
}
|