youiyoux 0.1.17 → 0.1.19

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.
Files changed (28) hide show
  1. package/dist/chunks/{chunk-TW24A5TT.js → chunk-2F2HM7FE.js} +6 -6
  2. package/dist/chunks/{chunk-XHJQOWD4.js → chunk-4BQPCZLL.js} +1 -1
  3. package/dist/chunks/{chunk-I6HDWG3U.js → chunk-5D7PLY7A.js} +1 -1
  4. package/dist/chunks/{chunk-CKH54UT6.js → chunk-75LXBAYB.js} +245 -36
  5. package/dist/chunks/{chunk-2HRV3FFX.js → chunk-AFEQZT2N.js} +2 -2
  6. package/dist/chunks/{chunk-BMBXZG7W.js → chunk-DHZDMZXE.js} +64 -23
  7. package/dist/chunks/{chunk-CLYYS6VI.js → chunk-M56633DV.js} +8 -1
  8. package/dist/chunks/{chunk-W3ZCAAXH.js → chunk-NG73FB4O.js} +4 -4
  9. package/dist/chunks/{chunk-COJJZPVD.js → chunk-OE4RYJOS.js} +3 -3
  10. package/dist/chunks/{chunk-G5BKBWEQ.js → chunk-SLAQGAAP.js} +1 -1
  11. package/dist/chunks/{chunk-Q77GHEZ5.js → chunk-UJMXSTUF.js} +2 -2
  12. package/dist/chunks/{figma-auth-O7YTQOAD.js → figma-auth-A2SAMRLF.js} +1 -1
  13. package/dist/chunks/{ingest-XSUSELVB.js → ingest-X4MIF5F6.js} +7 -7
  14. package/dist/chunks/{src-X34V5MHP.js → src-AXF5XUQE.js} +5 -1
  15. package/dist/chunks/{src-COKDT2AL.js → src-DFXQLHEP.js} +2 -2
  16. package/dist/chunks/{src-CWTSWOK4.js → src-HBKEBCH6.js} +2 -2
  17. package/dist/chunks/{src-WSHA347Z.js → src-LGHUKRL2.js} +7 -7
  18. package/dist/chunks/{src-RMQQPWAD.js → src-Q3ZJHEUQ.js} +3 -3
  19. package/dist/chunks/{src-3F2YGGDW.js → src-ULAQV67E.js} +3 -3
  20. package/dist/chunks/{src-O535ZT77.js → src-UXXYZOQ5.js} +2 -2
  21. package/dist/chunks/{src-HKWBBHA4.js → src-VSWKGVFT.js} +4 -4
  22. package/dist/hub-previews.js +6 -6
  23. package/dist/hub-server.js +3 -3
  24. package/dist/hub-snapshot.js +1 -1
  25. package/dist/schema/youi.inventory.schema.json +34 -0
  26. package/dist/youi.js +8 -8
  27. package/dist/youiyoux.js +8 -8
  28. package/package.json +4 -4
@@ -1,10 +1,10 @@
1
1
  import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
2
2
  import {
3
3
  loadProject
4
- } from "./chunk-CKH54UT6.js";
4
+ } from "./chunk-75LXBAYB.js";
5
5
  import {
6
6
  shortHash
7
- } from "./chunk-CLYYS6VI.js";
7
+ } from "./chunk-M56633DV.js";
8
8
 
9
9
  // ../cli/src/ui.ts
10
10
  import * as util from "node:util";
@@ -57,7 +57,7 @@ import { homedir, userInfo } from "node:os";
57
57
  import { join, resolve } from "node:path";
58
58
  async function swapImpact(candidate, family) {
59
59
  try {
60
- const { discoverIndexPath, sharedStore } = await import("./src-3F2YGGDW.js");
60
+ const { discoverIndexPath, sharedStore } = await import("./src-ULAQV67E.js");
61
61
  const path = discoverIndexPath();
62
62
  if (!process.env.DATABASE_URL && !existsSync(path)) return null;
63
63
  const impact = await (await sharedStore(path, process.env, { readOnly: true })).swapImpact(candidate, family);
@@ -68,7 +68,7 @@ async function swapImpact(candidate, family) {
68
68
  }
69
69
  async function libraryLicence(id) {
70
70
  try {
71
- const { discoverCacheDir, findListing } = await import("./src-3F2YGGDW.js");
71
+ const { discoverCacheDir, findListing } = await import("./src-ULAQV67E.js");
72
72
  const { entry } = await findListing(discoverCacheDir(void 0, process.env), id);
73
73
  return entry.id === id ? entry.license : null;
74
74
  } catch {
@@ -79,11 +79,11 @@ function workflowStateDir(project, state) {
79
79
  return resolve(state ?? join(homedir(), ".youi", "workflow", shortHash(`${project.repo.root}\0${project.root}`)));
80
80
  }
81
81
  async function ingest(link) {
82
- const { readLink } = await import("./ingest-XSUSELVB.js");
82
+ const { readLink } = await import("./ingest-X4MIF5F6.js");
83
83
  return readLink(link, { allowLocal: false });
84
84
  }
85
85
  async function startRuleEditor(options) {
86
- const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-HKWBBHA4.js");
86
+ const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-VSWKGVFT.js");
87
87
  const projectRoot = resolve(options.project);
88
88
  const load = { ...options.config ? { configPath: resolve(options.config) } : {}, ...options.design ? { designRoot: resolve(options.design) } : {}, ...options.root ? { productRoot: resolve(options.root) } : {} };
89
89
  const project = await loadProject(projectRoot, load);
@@ -6,7 +6,7 @@ import {
6
6
  require_dist,
7
7
  toPosix,
8
8
  validateTokenSets
9
- } from "./chunk-CLYYS6VI.js";
9
+ } from "./chunk-M56633DV.js";
10
10
  import {
11
11
  __commonJS,
12
12
  __toESM
@@ -4,7 +4,7 @@ import {
4
4
  findFiles,
5
5
  require_dist,
6
6
  writeFileAtomic
7
- } from "./chunk-CLYYS6VI.js";
7
+ } from "./chunk-M56633DV.js";
8
8
  import {
9
9
  __toESM
10
10
  } from "./chunk-WNTS7ZO4.js";
@@ -18,11 +18,12 @@ import {
18
18
  sameColor,
19
19
  splitTopLevel,
20
20
  tailwindTheme
21
- } from "./chunk-XHJQOWD4.js";
21
+ } from "./chunk-4BQPCZLL.js";
22
22
  import {
23
23
  AST_GREP_LANGUAGES,
24
24
  BUILT_IN_CHECKS,
25
25
  ComponentContract,
26
+ GLOB_SYNTAX,
26
27
  INVENTORY_ROLES,
27
28
  ProjectConfig,
28
29
  RouteConfig,
@@ -51,7 +52,7 @@ import {
51
52
  toPosix,
52
53
  unifiedDiff,
53
54
  writeFileAtomic
54
- } from "./chunk-CLYYS6VI.js";
55
+ } from "./chunk-M56633DV.js";
55
56
 
56
57
  // ../scanner/src/parse.ts
57
58
  import { Lang, parseAsync } from "@ast-grep/napi";
@@ -1113,8 +1114,8 @@ function clusterShapes(shapes, linkage = "complete", tolerance = DEFAULT_TOLERAN
1113
1114
  }
1114
1115
 
1115
1116
  // ../scanner/src/inventory/theme.ts
1116
- import { readFile as readFile2 } from "node:fs/promises";
1117
- import { join as join4 } from "node:path";
1117
+ import { readdir as readdir2, readFile as readFile2 } from "node:fs/promises";
1118
+ import { dirname as dirname4, join as join4 } from "node:path";
1118
1119
  import { Lang as Lang3, parseAsync as parseAsync2 } from "@ast-grep/napi";
1119
1120
  var ROOT_FONT_SIZE = 16;
1120
1121
  var PILL = 9999;
@@ -1222,12 +1223,14 @@ var Theme = class {
1222
1223
  warnings;
1223
1224
  /** Project declarations only: excludes implicit Tailwind defaults. */
1224
1225
  declared;
1226
+ forms;
1225
1227
  vars;
1226
- constructor(vars, tailwind, warnings, declared = /* @__PURE__ */ new Map()) {
1228
+ constructor(vars, tailwind, warnings, declared = /* @__PURE__ */ new Map(), forms = null) {
1227
1229
  this.vars = vars;
1228
1230
  this.declared = declared;
1229
1231
  this.tailwind = tailwind;
1230
1232
  this.warnings = warnings;
1233
+ this.forms = forms;
1231
1234
  }
1232
1235
  variable(name) {
1233
1236
  return this.vars.get(name);
@@ -1274,6 +1277,68 @@ function literal(node2) {
1274
1277
  function pairsOf(object) {
1275
1278
  return object.children().filter((c) => c.kind() === "pair" || c.kind() === "spread_element" || c.kind() === "method_definition" || c.kind() === "shorthand_property_identifier").map((node2) => ({ key: node2.kind() === "pair" ? keyText(node2.field("key")) : null, value: node2.kind() === "pair" ? node2.field("value") : null, node: node2 }));
1276
1279
  }
1280
+ var FORMS = "@tailwindcss/forms";
1281
+ var formsMode = (text) => /strategy\s*:\s*["']?class/.test(text.slice(text.indexOf(FORMS))) ? "class" : "base";
1282
+ async function formsPluginOf(root, sheets, configFile) {
1283
+ for (const sheet of sheets) {
1284
+ const at = sheet.source.search(/@plugin\s+["']@tailwindcss\/forms["']/);
1285
+ if (at >= 0) return formsMode(sheet.source.slice(at, at + 200));
1286
+ }
1287
+ const configSource = configFile ? await readFile2(join4(root, configFile), "utf8").catch(() => null) : null;
1288
+ const texts = configSource ? [configSource, ...await presetSources(root, configSource)] : [];
1289
+ const found = texts.find((t) => t.includes(FORMS));
1290
+ if (found) return formsMode(found);
1291
+ for (let dir = root, up = 0; up < 6; dir = dirname4(dir), up++) {
1292
+ const pkg = await readFile2(join4(dir, "package.json"), "utf8").catch(() => null);
1293
+ if (pkg?.includes(`"${FORMS}"`)) return "unknown";
1294
+ if (await readFile2(join4(dir, ".git", "HEAD"), "utf8").then(() => true, () => false)) break;
1295
+ if (dirname4(dir) === dir) break;
1296
+ }
1297
+ return null;
1298
+ }
1299
+ async function presetSources(root, config) {
1300
+ const sources = [];
1301
+ const specifiers = [...config.matchAll(/(?:from\s+|require\()\s*["']([^"']+)["']/g)].map((m) => m[1]).filter((s) => !s.startsWith("tailwindcss") && s !== FORMS);
1302
+ const read = async (base) => {
1303
+ for (const ext of ["", ".ts", ".js", ".mjs", ".cjs", ".mts", "/tailwind.config.ts", "/tailwind.config.js", "/index.ts", "/index.js"]) {
1304
+ const text = await readFile2(base + ext, "utf8").catch(() => null);
1305
+ if (text !== null) return text;
1306
+ }
1307
+ return null;
1308
+ };
1309
+ for (const specifier of specifiers.slice(0, 10)) {
1310
+ if (specifier.startsWith(".")) {
1311
+ const text = await read(join4(root, specifier));
1312
+ if (text) sources.push(text);
1313
+ continue;
1314
+ }
1315
+ const parts = specifier.split("/");
1316
+ const name = specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
1317
+ const rest = parts.slice(specifier.startsWith("@") ? 2 : 1).join("/");
1318
+ for (let dir = root, up = 0; up < 6; dir = dirname4(dir), up++) {
1319
+ let found = null;
1320
+ for (const folder of ["packages", "libs", "config", "tooling"]) {
1321
+ for (const entry of await readdir2(join4(dir, folder)).catch(() => [])) {
1322
+ const pkg = await readFile2(join4(dir, folder, entry, "package.json"), "utf8").catch(() => null);
1323
+ let named2 = null;
1324
+ try {
1325
+ named2 = pkg ? JSON.parse(pkg).name : null;
1326
+ } catch {
1327
+ named2 = null;
1328
+ }
1329
+ if (named2 === name) found = join4(dir, folder, entry);
1330
+ }
1331
+ }
1332
+ if (found) {
1333
+ const text = await read(rest ? join4(found, rest) : found);
1334
+ if (text) sources.push(text);
1335
+ break;
1336
+ }
1337
+ if (dirname4(dir) === dir) break;
1338
+ }
1339
+ }
1340
+ return sources;
1341
+ }
1277
1342
  async function readTailwindConfig(root) {
1278
1343
  const vars = /* @__PURE__ */ new Map();
1279
1344
  const warnings = [];
@@ -1344,7 +1409,7 @@ async function buildTheme(root, sheets, usesTailwindPackage) {
1344
1409
  }
1345
1410
  }
1346
1411
  }
1347
- return new Theme(vars, version, config.warnings, declared);
1412
+ return new Theme(vars, version, config.warnings, declared, version ? await formsPluginOf(root, sheets, config.file) : null);
1348
1413
  }
1349
1414
 
1350
1415
  // ../scanner/src/inventory/style.ts
@@ -1361,9 +1426,12 @@ var StyleAccumulator = class {
1361
1426
  states = [];
1362
1427
  preflight;
1363
1428
  tag;
1364
- constructor(tag, preflight) {
1429
+ /** A form control whose unstyled padding a plugin may set, unread: unknown, never 0. */
1430
+ unsetUnknown;
1431
+ constructor(tag, preflight, unsetUnknown = false) {
1365
1432
  this.tag = tag;
1366
1433
  this.preflight = preflight;
1434
+ this.unsetUnknown = unsetUnknown;
1367
1435
  }
1368
1436
  /** Whether any declaration applied at all; an element nothing styles has no known padding or radius. */
1369
1437
  touched = false;
@@ -1393,7 +1461,7 @@ var StyleAccumulator = class {
1393
1461
  }
1394
1462
  finish() {
1395
1463
  const derived = [];
1396
- const zero = this.touched && (this.preflight || !FORM_CONTROLS.has(this.tag ?? "")) ? 0 : null;
1464
+ const zero = this.touched && !this.unsetUnknown && (this.preflight || !FORM_CONTROLS.has(this.tag ?? "")) ? 0 : null;
1397
1465
  const axis = (a, b) => {
1398
1466
  const x = this.known(a);
1399
1467
  const y = this.known(b);
@@ -1700,8 +1768,20 @@ function applyDeclaration(style, declaration, theme, selector) {
1700
1768
  if (PADDING_SIDES[declaration.property]) for (const prop of PADDING_SIDES[declaration.property]) set(prop, length(value));
1701
1769
  }
1702
1770
  }
1771
+ var FORMS_CONTROLS = /* @__PURE__ */ new Set(["input", "select", "textarea"]);
1703
1772
  function resolveClasses(classes, tag, theme, rules) {
1704
- const style = new StyleAccumulator(tag, false);
1773
+ const forms = FORMS_CONTROLS.has(tag ?? "") ? theme.forms === "base" || theme.forms === "class" && classes.some((c) => /^form-(?:input|select|textarea|multiselect)$/.test(c)) ? "styled" : theme.forms === "unknown" ? "unknown" : null : null;
1774
+ const style = new StyleAccumulator(tag, false, forms === "unknown");
1775
+ if (forms === "styled") {
1776
+ style.set("paddingTop", 8);
1777
+ style.set("paddingBottom", 8);
1778
+ style.set("paddingLeft", 12);
1779
+ style.set("paddingRight", tag === "select" ? 40 : 12);
1780
+ style.set("fontSize", 16);
1781
+ style.set("lineHeight", 24);
1782
+ style.set("border", 1);
1783
+ style.set("radius", 0);
1784
+ }
1705
1785
  rules.apply(style, classes, theme);
1706
1786
  let tailwindClasses = 0;
1707
1787
  if (theme.tailwind) {
@@ -2108,6 +2188,12 @@ function expoRoute(file, appDir) {
2108
2188
  }
2109
2189
  function routeEntries(files, config) {
2110
2190
  const entries = /* @__PURE__ */ new Map();
2191
+ const exact = /* @__PURE__ */ new Map();
2192
+ const globs = [];
2193
+ for (const m of config.strategy === "map" ? config.map : []) {
2194
+ if (GLOB_SYNTAX.test(m.file)) globs.push(m);
2195
+ else exact.set(m.file, [...exact.get(m.file) ?? [], ...m.routes]);
2196
+ }
2111
2197
  for (const file of files) {
2112
2198
  if (matchesAny(file, config.shell)) {
2113
2199
  entries.set(file, { routes: [SHELL_ROUTE], shell: true });
@@ -2123,7 +2209,7 @@ function routeEntries(files, config) {
2123
2209
  }
2124
2210
  }
2125
2211
  if (config.strategy === "map") {
2126
- const routes = config.map.filter((m) => matchesAny(file, [m.file])).flatMap((m) => m.routes);
2212
+ const routes = [...exact.get(file) ?? [], ...globs.filter((m) => matchesAny(file, [m.file])).flatMap((m) => m.routes)];
2127
2213
  if (routes.length > 0) entries.set(file, { routes: [...new Set(routes)], shell: false });
2128
2214
  }
2129
2215
  }
@@ -3925,7 +4011,7 @@ function ruleRows(project) {
3925
4011
  // ../scanner/src/watch.ts
3926
4012
  import { existsSync, mkdtempSync, rmSync, watch, writeFileSync } from "node:fs";
3927
4013
  import { tmpdir } from "node:os";
3928
- import { dirname as dirname4, join as join8, relative as relative3, resolve as resolve4, sep } from "node:path";
4014
+ import { dirname as dirname5, join as join8, relative as relative3, resolve as resolve4, sep } from "node:path";
3929
4015
  var DEFAULT_DEBOUNCE_MS = 120;
3930
4016
  var LIVE_TIMEOUT_MS = 1e4;
3931
4017
  var PROBE_INTERVAL_MS = 50;
@@ -4028,8 +4114,8 @@ var ProjectWatcher = class _ProjectWatcher {
4028
4114
  for (const w of this.watchers) w.close();
4029
4115
  const { root, config, repo, system } = project;
4030
4116
  const sourceDirs = covering(config.include.map((g) => resolve4(root, staticBase(g))).filter((d) => existsSync(d)));
4031
- const definitionDirs = covering([dirname4(system.config.tokens), system.config.components, system.config.rules, ...system.config.design ? [dirname4(system.config.design)] : []].map((p) => resolve4(repo.root, p)).filter((d) => existsSync(d) && d !== repo.root));
4032
- const topLevel = [.../* @__PURE__ */ new Set([repo.root, dirname4(this.configFile), root])];
4117
+ const definitionDirs = covering([dirname5(system.config.tokens), system.config.components, system.config.rules, ...system.config.design ? [dirname5(system.config.design)] : []].map((p) => resolve4(repo.root, p)).filter((d) => existsSync(d) && d !== repo.root));
4118
+ const topLevel = [.../* @__PURE__ */ new Set([repo.root, dirname5(this.configFile), root])];
4033
4119
  this.watchers = [
4034
4120
  ...sourceDirs.map((dir) => watch2(dir, { recursive: true }, (_event, name) => name && this.sourceChanged(project, join8(dir, name.toString())))),
4035
4121
  ...definitionDirs.map((dir) => watch2(dir, { recursive: true }, () => this.definitionsChanged())),
@@ -4041,7 +4127,7 @@ var ProjectWatcher = class _ProjectWatcher {
4041
4127
  watchConfigOnly() {
4042
4128
  const watch2 = this.options.watch ?? defaultWatch;
4043
4129
  for (const w of this.watchers) w.close();
4044
- this.watchers = [watch2(dirname4(this.configFile), { recursive: true }, () => this.definitionsChanged())];
4130
+ this.watchers = [watch2(dirname5(this.configFile), { recursive: true }, () => this.definitionsChanged())];
4045
4131
  for (const w of this.watchers) w.on("error", (error) => this.watcherFailed(error));
4046
4132
  void this.whenLive();
4047
4133
  }
@@ -4257,21 +4343,22 @@ function readTemplate(source, offset) {
4257
4343
  }
4258
4344
  const parent = stack.at(-1);
4259
4345
  if (parent && parent.firstChild === null) parent.firstChild = tag;
4346
+ const inside = parent ? parent.at : null;
4260
4347
  const lower = tag.toLowerCase();
4261
4348
  if (RAW_TEXT.has(lower) && !selfClosing) {
4262
4349
  const close = source.indexOf(`</${lower}`, j);
4263
4350
  const end = close < 0 ? source.length : close;
4264
4351
  if (lower === "style") styles.push({ css: source.slice(j, end), line: positionAt(starts, j).line });
4265
- found.push({ tag, index: i, attributes: attributes2, opened: null, text: null });
4352
+ found.push({ tag, index: i, attributes: attributes2, opened: null, text: null, parent: inside });
4266
4353
  i = close < 0 ? source.length : source.indexOf(">", close) + 1 || source.length;
4267
4354
  continue;
4268
4355
  }
4269
- const opened = selfClosing ? null : { tag, index: i, firstChild: null };
4356
+ const opened = selfClosing ? null : { tag, index: i, firstChild: null, at: found.length };
4270
4357
  if (opened) stack.push(opened);
4271
- found.push({ tag, index: i, attributes: attributes2, opened, text: opened ? textAt(source, j) : null });
4358
+ found.push({ tag, index: i, attributes: attributes2, opened, text: opened ? textAt(source, j) : null, parent: inside });
4272
4359
  i = j;
4273
4360
  }
4274
- const tags = found.map(({ tag, index, attributes: attributes2, opened, text }) => ({ tag, ...positionAt(starts, index), attributes: attributes2, firstChild: opened?.firstChild ?? null, text }));
4361
+ const tags = found.map(({ tag, index, attributes: attributes2, opened, text, parent }) => ({ tag, ...positionAt(starts, index), attributes: attributes2, firstChild: opened?.firstChild ?? null, text, parent }));
4275
4362
  return { tags, styles, problems };
4276
4363
  }
4277
4364
  function frontmatterOf(source) {
@@ -4744,6 +4831,23 @@ async function pathAliases(root, tsconfig) {
4744
4831
  }
4745
4832
  }
4746
4833
  var position2 = (node2) => ({ line: node2.range().start.line + 1, column: node2.range().start.column + 1 });
4834
+ var COPY_OVERLAP = 0.6;
4835
+ var markupToken = (tag, classes) => classes.length > 0 ? `${tag}.${[...new Set(classes)].sort().join(".")}` : tag;
4836
+ function overlap2(a, b) {
4837
+ const shared = [...a].filter((t) => b.has(t)).length;
4838
+ return shared / (a.size + b.size - shared || 1);
4839
+ }
4840
+ function jsxMarkup(element) {
4841
+ const tag = element.field("name")?.text();
4842
+ if (!tag || !/^[a-z]/.test(tag)) return null;
4843
+ const container = element.kind() === "jsx_opening_element" ? element.parent() : null;
4844
+ const inside = /* @__PURE__ */ new Set();
4845
+ for (const child of container?.findAll({ rule: { any: [{ kind: "jsx_opening_element" }, { kind: "jsx_self_closing_element" }] } }) ?? []) {
4846
+ if (child.id() === element.id()) continue;
4847
+ inside.add(markupToken(child.field("name")?.text() ?? "", classValueOf(jsxClassAttribute(child)).certain));
4848
+ }
4849
+ return { tag, classes: [...new Set(classValueOf(jsxClassAttribute(element)).certain)].sort(), inside };
4850
+ }
4747
4851
  var pascal = (text) => text.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
4748
4852
  var packageOf = (specifier) => specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
4749
4853
  var literalOf2 = (value) => value && "literal" in value ? String(value.literal) : null;
@@ -4807,6 +4911,11 @@ var ProjectPass = class {
4807
4911
  target;
4808
4912
  modules = /* @__PURE__ */ new Map();
4809
4913
  astro = /* @__PURE__ */ new Map();
4914
+ /** Shaped plain classes not named for an element: members only once a look of a known role uses them (settleProvisional). */
4915
+ provisional = /* @__PURE__ */ new Set();
4916
+ /** A React definition's root element, for inlineCopies. */
4917
+ reactRoots = /* @__PURE__ */ new Map();
4918
+ copies = [];
4810
4919
  sheets = [];
4811
4920
  failed = [];
4812
4921
  definitions = /* @__PURE__ */ new Map();
@@ -4902,7 +5011,7 @@ var ProjectPass = class {
4902
5011
  const shaped = rule.declarations.some((d) => SHAPE_PROPERTIES.test(d.property));
4903
5012
  for (const selector of splitTopLevel(prelude, ",")) {
4904
5013
  const match = /^\.([a-z][a-z0-9]*(?:-[a-z0-9]+)*)(?:--([a-z0-9-]+))?$/.exec(selector.trim());
4905
- if (!match || !roleOfClass(match[1])) continue;
5014
+ if (!match) continue;
4906
5015
  const [, name, modifier] = match;
4907
5016
  const entry = plain2.get(name) ?? { line: rule.declarations[0]?.line ?? 1, modifiers: /* @__PURE__ */ new Set(), shaped: false };
4908
5017
  if (modifier) entry.modifiers.add(modifier);
@@ -4924,7 +5033,12 @@ var ProjectPass = class {
4924
5033
  prefixes.push(prefix);
4925
5034
  for (const block of names) found.push({ name: block, role: roleOfBlock(block, prefix), ...blocks.get(block) });
4926
5035
  }
4927
- for (const [name, entry] of plain2) if (entry.shaped && !found.some((f) => f.name === name)) found.push({ name, role: roleOfClass(name), line: entry.line, modifiers: entry.modifiers });
5036
+ for (const [name, entry] of plain2) {
5037
+ if (!entry.shaped || found.some((f) => f.name === name)) continue;
5038
+ const role = roleOfClass(name);
5039
+ if (!role) this.provisional.add(name);
5040
+ found.push({ name, role, line: entry.line, modifiers: entry.modifiers });
5041
+ }
4928
5042
  if (found.length === 0) continue;
4929
5043
  const members = [];
4930
5044
  for (const { name, role, line: line2, modifiers } of found.sort((a, b) => a.name.localeCompare(b.name))) {
@@ -4938,10 +5052,36 @@ var ProjectPass = class {
4938
5052
  this.kits.push({ id: kit, project: this.id, kind: "class-kit", path: sheet.file, ...prefixes.length === 1 ? { prefix: prefixes[0] } : {}, members });
4939
5053
  }
4940
5054
  }
5055
+ /**
5056
+ * Keep a provisional class only when it styles an element of a known role, with that role (the
5057
+ * one most of its looks have); drop the rest, so a layout class never counts as a component.
5058
+ */
5059
+ settleProvisional() {
5060
+ for (const name of this.provisional) {
5061
+ const definition = this.blocks.get(name);
5062
+ if (!definition) continue;
5063
+ const roles = this.looks.filter((l) => l.definition === definition.entry.id).map((l) => l.role);
5064
+ const kit = this.kits.find((k) => k.id === definition.entry.kit);
5065
+ if (roles.length === 0) {
5066
+ this.definitions.delete(definition.entry.id);
5067
+ this.blocks.delete(name);
5068
+ if (kit) kit.members = kit.members.filter((m) => m.name !== definition.entry.name);
5069
+ continue;
5070
+ }
5071
+ const counts = /* @__PURE__ */ new Map();
5072
+ for (const r of roles) counts.set(r, (counts.get(r) ?? 0) + 1);
5073
+ const role = [...counts].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0][0];
5074
+ definition.entry.role = role;
5075
+ const member = kit?.members.find((m) => m.name === definition.entry.name);
5076
+ if (member) member.role = role;
5077
+ }
5078
+ this.provisional.clear();
5079
+ for (let i = this.kits.length - 1; i >= 0; i--) if (this.kits[i].members.length === 0) this.kits.splice(i, 1);
5080
+ }
4941
5081
  /* ---------------------------------------------------------- definitions */
4942
5082
  kitOfClasses(classes) {
4943
5083
  for (const name of classes) {
4944
- const block = this.blocks.get(name);
5084
+ const block = this.provisional.has(name) ? void 0 : this.blocks.get(name);
4945
5085
  if (block) return block.entry.kit;
4946
5086
  }
4947
5087
  return null;
@@ -5031,6 +5171,7 @@ var ProjectPass = class {
5031
5171
  }
5032
5172
  const range = declaration.node.range();
5033
5173
  this.definitions.set(`${module.file}#${entry.name}`, { entry: def, recipe: found?.recipe ?? null, range: { start: range.start.index, end: range.end.index }, rootId: found?.root.id() ?? null });
5174
+ if (found) this.reactRoots.set(def.id, found.root);
5034
5175
  }
5035
5176
  }
5036
5177
  for (const folder of [...cvaFolders].sort()) {
@@ -5066,6 +5207,70 @@ var ProjectPass = class {
5066
5207
  this.definitions.set(`${file}#default`, { entry, recipe, range: null, rootId: root ? root.line * 1e5 + root.column : null });
5067
5208
  }
5068
5209
  }
5210
+ /* -------------------------------------------------------- inline copies */
5211
+ /**
5212
+ * Elements that write out a component's markup instead of rendering it: in another file, the
5213
+ * component's root tag with at least its root classes, and inside it at least COPY_OVERLAP of the
5214
+ * same elements (each a tag with its classes). A component whose root has no class, or holds
5215
+ * fewer than two elements, has no markup distinct enough to recognise.
5216
+ */
5217
+ async inlineCopies() {
5218
+ const components = [];
5219
+ for (const definition of this.definitions.values()) {
5220
+ const { entry } = definition;
5221
+ if (entry.kind === "react") {
5222
+ const root = this.reactRoots.get(entry.id);
5223
+ const markup = root ? jsxMarkup(root) : null;
5224
+ if (markup) components.push({ id: entry.id, file: entry.file, markup });
5225
+ } else if (entry.kind === "astro") {
5226
+ const astro = this.astro.get(entry.file);
5227
+ const top = astro?.tags.flatMap((t, i) => t.parent === null && !["style", "script", "slot", "fragment"].includes(t.tag.toLowerCase()) ? [i] : []) ?? [];
5228
+ const at = top.length === 1 && /^[a-z]/.test(astro.tags[top[0]].tag) ? top[0] : -1;
5229
+ const markup = astro && at >= 0 ? await this.astroMarkup(astro, at) : null;
5230
+ if (markup) components.push({ id: entry.id, file: entry.file, markup });
5231
+ }
5232
+ }
5233
+ const usable = components.filter((c) => c.markup.classes.length > 0 && c.markup.inside.size >= 2);
5234
+ if (usable.length === 0) return;
5235
+ const byTag = /* @__PURE__ */ new Map();
5236
+ for (const c of usable) byTag.set(c.markup.tag, [...byTag.get(c.markup.tag) ?? [], c]);
5237
+ const consider = (file, at, tag, classes, markup) => {
5238
+ const candidates = (byTag.get(tag) ?? []).filter((c) => c.file !== file && c.markup.classes.every((k) => classes.includes(k)));
5239
+ if (candidates.length === 0) return;
5240
+ return (async () => {
5241
+ const found = await markup();
5242
+ if (!found) return;
5243
+ const best = candidates.map((c) => ({ c, score: overlap2(c.markup.inside, found.inside) })).filter((m) => m.score >= COPY_OVERLAP).sort((a, b) => b.score - a.score)[0];
5244
+ if (best) this.copies.push({ project: this.id, component: best.c.id, file, ...at });
5245
+ })();
5246
+ };
5247
+ for (const module of this.sortedModules()) {
5248
+ if (module.lang !== "tsx") continue;
5249
+ for (const element of module.root.root().findAll({ rule: { any: [{ kind: "jsx_opening_element" }, { kind: "jsx_self_closing_element" }] } })) {
5250
+ const tag = element.field("name")?.text() ?? "";
5251
+ if (!/^[a-z]/.test(tag)) continue;
5252
+ await consider(module.file, position2(element), tag, classValueOf(jsxClassAttribute(element)).certain, () => jsxMarkup(element));
5253
+ }
5254
+ }
5255
+ for (const [file, astro] of [...this.astro].sort(([a], [b]) => a.localeCompare(b))) {
5256
+ for (let i = 0; i < astro.tags.length; i++) {
5257
+ const tag = astro.tags[i];
5258
+ if (!/^[a-z]/.test(tag.tag)) continue;
5259
+ await consider(file, { line: tag.line, column: tag.column }, tag.tag, (await this.templateClasses(tag)).certain, () => this.astroMarkup(astro, i));
5260
+ }
5261
+ }
5262
+ }
5263
+ async astroMarkup(astro, at) {
5264
+ const root = astro.tags[at];
5265
+ if (!root) return null;
5266
+ const inside = /* @__PURE__ */ new Set();
5267
+ for (let j = at + 1; j < astro.tags.length; j++) {
5268
+ let up = astro.tags[j].parent;
5269
+ while (up !== null && up !== at) up = astro.tags[up].parent;
5270
+ if (up === at) inside.add(markupToken(astro.tags[j].tag, (await this.templateClasses(astro.tags[j])).certain));
5271
+ }
5272
+ return { tag: root.tag, classes: [...new Set((await this.templateClasses(root)).certain)].sort(), inside };
5273
+ }
5069
5274
  /* ------------------------------------------------------------------ looks */
5070
5275
  addLook(input) {
5071
5276
  const resolved = resolveClasses(input.classes, input.rootTag, this.theme, this.rulesFor(input.file));
@@ -5173,7 +5378,8 @@ var ProjectPass = class {
5173
5378
  }
5174
5379
  intrinsicLook(file, at, tag, props, site, inside, label) {
5175
5380
  let role = roleOfIntrinsic(tag, props);
5176
- const block = site.certain.map((c) => this.blocks.get(c)).find(Boolean) ?? null;
5381
+ const named2 = site.certain.filter((c) => this.blocks.has(c));
5382
+ const block = this.blocks.get(named2.find((c) => !this.provisional.has(c)) ?? named2[0] ?? "") ?? null;
5177
5383
  role ??= block?.entry.role ?? null;
5178
5384
  if (!role || role === inside) return;
5179
5385
  const recipe = { kind: "class", tag, classes: [...new Set(site.certain)], label: /^(?:input|img|hr|br)$/.test(tag) ? null : label };
@@ -5409,8 +5615,10 @@ async function readProject(target, options = {}) {
5409
5615
  pass.classKits();
5410
5616
  await pass.reactDefinitions();
5411
5617
  await pass.astroDefinitions();
5618
+ await pass.inlineCopies();
5412
5619
  await pass.collectLooks();
5413
5620
  await pass.storyLooks();
5621
+ pass.settleProvisional();
5414
5622
  return pass;
5415
5623
  }
5416
5624
  async function collectProject(target, options = {}) {
@@ -5419,7 +5627,7 @@ async function collectProject(target, options = {}) {
5419
5627
  const definitions = [...pass.definitions.values()].map((d) => d.entry).sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.name.localeCompare(b.name));
5420
5628
  const primitives = [...pass.primitives].map(([pkg, p]) => ({ package: pkg, project: target.id, exports: [...p.exports].sort(), count: p.count, sites: p.sites.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column) })).sort((a, b) => a.package.localeCompare(b.package));
5421
5629
  const looks = pass.looks.sort(byPosition);
5422
- return { id: target.id, target, commit: git3.commit, failed: pass.failed.sort((a, b) => a.file.localeCompare(b.file)), warnings: pass.theme.warnings, definitions, kits: pass.kits.sort((a, b) => a.id.localeCompare(b.id)), primitives, looks, kitImports: pass.kitImports, stories: pass.stories };
5630
+ return { id: target.id, target, commit: git3.commit, failed: pass.failed.sort((a, b) => a.file.localeCompare(b.file)), warnings: pass.theme.warnings, definitions, kits: pass.kits.sort((a, b) => a.id.localeCompare(b.id)), primitives, looks, kitImports: pass.kitImports, stories: pass.stories, copies: pass.copies.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column) };
5423
5631
  }
5424
5632
 
5425
5633
  // ../scanner/src/inventory/target.ts
@@ -5641,6 +5849,7 @@ async function buildInventory(dirs, options = {}) {
5641
5849
  looks: allLooks,
5642
5850
  families,
5643
5851
  flags: flagsOf(projects),
5852
+ copies: projects.flatMap((p) => p.copies),
5644
5853
  summary: {
5645
5854
  looks: uses.length,
5646
5855
  clustered: uses.filter((l) => l.family !== null).length,
@@ -5919,7 +6128,7 @@ import { rmSync as rmSync2 } from "node:fs";
5919
6128
  import { createHash as createHash2 } from "node:crypto";
5920
6129
  import { existsSync as existsSync2, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync as writeFileSync2 } from "node:fs";
5921
6130
  import { homedir as homedir2 } from "node:os";
5922
- import { dirname as dirname5, join as join13 } from "node:path";
6131
+ import { dirname as dirname6, join as join13 } from "node:path";
5923
6132
  var SPECIMEN_FORMAT = "youiyoux.specimens/5";
5924
6133
  var newStats = () => ({ tailwindCompiles: 0, bundles: 0, hits: 0, misses: 0 });
5925
6134
  function defaultCacheDir(env = process.env) {
@@ -5961,7 +6170,7 @@ function specimenKey(root, commit, files, recipes) {
5961
6170
  return hash.digest("hex").slice(0, 24);
5962
6171
  }
5963
6172
  function writeJsonAtomic(file, value) {
5964
- mkdirSync(dirname5(file), { recursive: true });
6173
+ mkdirSync(dirname6(file), { recursive: true });
5965
6174
  writeFileSync2(`${file}.${process.pid}.tmp`, JSON.stringify(value));
5966
6175
  renameSync(`${file}.${process.pid}.tmp`, file);
5967
6176
  }
@@ -5995,7 +6204,7 @@ var SpecimenCache = class {
5995
6204
  // ../scanner/src/specimens/css.ts
5996
6205
  import { createRequire as createRequire2 } from "node:module";
5997
6206
  import { existsSync as existsSync3, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
5998
- import { basename as basename3, dirname as dirname6, extname, join as join14, posix as posix5, resolve as resolve6 } from "node:path";
6207
+ import { basename as basename3, dirname as dirname7, extname, join as join14, posix as posix5, resolve as resolve6 } from "node:path";
5999
6208
  import { Worker } from "node:worker_threads";
6000
6209
  var SCHEMES = ["light", "dark"];
6001
6210
  var TAILWIND_TIMEOUT_MS = 3e4;
@@ -6030,7 +6239,7 @@ var isFile = (path) => {
6030
6239
  function resolveSheet(root, from, specifier, aliases) {
6031
6240
  let rel = null;
6032
6241
  if (specifier.startsWith(".") && !within(root, from)) {
6033
- const target = resolve6(dirname6(from), specifier);
6242
+ const target = resolve6(dirname7(from), specifier);
6034
6243
  if (!inPackage(target)) return { refused: `"${specifier}" leaves the package ${displayPath(root, from)}` };
6035
6244
  return existsSync3(target) ? { path: target } : null;
6036
6245
  }
@@ -7356,7 +7565,7 @@ async function projectTheme(root) {
7356
7565
 
7357
7566
  // ../scanner/src/specimens/preview-bundle.ts
7358
7567
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
7359
- import { basename as basename4, dirname as dirname7, isAbsolute as isAbsolute3, join as join17, resolve as resolve9, sep as sep2 } from "node:path";
7568
+ import { basename as basename4, dirname as dirname8, isAbsolute as isAbsolute3, join as join17, resolve as resolve9, sep as sep2 } from "node:path";
7360
7569
  var NAMESPACE2 = "youi-preview";
7361
7570
  var GUARDED2 = "youiPreviewGuarded";
7362
7571
  var ENTRY2 = "youi-preview.js";
@@ -7592,7 +7801,7 @@ async function bundlePreview(job, options = {}) {
7592
7801
  const at = args.importer.lastIndexOf(`${sep2}node_modules${sep2}`) + `${sep2}node_modules${sep2}`.length;
7593
7802
  const rest = args.importer.slice(at).split(sep2);
7594
7803
  const base = args.importer.slice(0, at) + rest.slice(0, rest[0].startsWith("@") ? 2 : 1).join(sep2);
7595
- const target = resolve9(dirname7(args.importer), args.path);
7804
+ const target = resolve9(dirname8(args.importer), args.path);
7596
7805
  if (target.startsWith(base + sep2)) return void 0;
7597
7806
  }
7598
7807
  const again = (path, resolveDir) => build.resolve(path, { importer: args.importer, resolveDir, kind: args.kind, pluginData: { [GUARDED2]: true }, with: args.with });
@@ -7769,7 +7978,7 @@ import { execFile as execFile3 } from "node:child_process";
7769
7978
  import { createHash as createHash3 } from "node:crypto";
7770
7979
  import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
7771
7980
  import { createRequire as createRequire3 } from "node:module";
7772
- import { dirname as dirname8, extname as extname2, isAbsolute as isAbsolute4, join as join18, relative as relative5 } from "node:path";
7981
+ import { dirname as dirname9, extname as extname2, isAbsolute as isAbsolute4, join as join18, relative as relative5 } from "node:path";
7773
7982
  import { promisify as promisify3 } from "node:util";
7774
7983
  var SHOTS_FORMAT = "youiyoux.shots/4";
7775
7984
  var SHOT_WIDTHS = [1280, 390];
@@ -7928,7 +8137,7 @@ var Eyes = class {
7928
8137
  }
7929
8138
  async open() {
7930
8139
  if (this.browser) return this.browser;
7931
- const { launchBrowser } = await import("./src-COKDT2AL.js");
8140
+ const { launchBrowser } = await import("./src-DFXQLHEP.js");
7932
8141
  try {
7933
8142
  this.browser = await launchBrowser(this.options.executablePath, { purpose: { what: "youiyoux render", command: "youiyoux render" }, ...this.options.download === false ? { download: false } : {}, ...this.options.log ? { log: this.options.log } : {} });
7934
8143
  } catch (error) {
@@ -8133,7 +8342,7 @@ async function renderShots(rootDir, targets, options = {}) {
8133
8342
  for (const key of [join18(root, file), join18(rootDir, file)]) overlay.set(key, contents);
8134
8343
  if (contents !== null) {
8135
8344
  const copy = join18(baseSources, file);
8136
- mkdirSync2(dirname8(copy), { recursive: true });
8345
+ mkdirSync2(dirname9(copy), { recursive: true });
8137
8346
  writeFileSync4(copy, contents);
8138
8347
  }
8139
8348
  }
@@ -8257,7 +8466,7 @@ function shotsText(report, again = (t) => `run youiyoux render${t ? ` ${t}` : ""
8257
8466
  const moved = c.diff.shots.filter((d) => d.changed > 0);
8258
8467
  lines.push(moved.length === 0 ? ` before/after against ${c.diff.base}: no visible change` : ` before/after against ${c.diff.base}: pixels changed ${moved.map((d) => `${percent(d.changed)} in ${d.scheme} ${d.width}`).join(", ")}`);
8259
8468
  for (const d of moved) lines.push(` diff ${d.scheme} ${d.width}: ${d.diff}`);
8260
- if (c.diff.shots[0]) lines.push(` before images: ${dirname8(c.diff.shots[0].before)}`);
8469
+ if (c.diff.shots[0]) lines.push(` before images: ${dirname9(c.diff.shots[0].before)}`);
8261
8470
  } else lines.push(` before/after: ${c.diff.none}`);
8262
8471
  if (c.a11y.length === 0) lines.push(" accessibility: no issues axe can find (WCAG 2.2 A and AA, both schemes and widths)");
8263
8472
  else {
@@ -8652,7 +8861,7 @@ import { randomBytes } from "node:crypto";
8652
8861
  import { existsSync as existsSync7, realpathSync as realpathSync2 } from "node:fs";
8653
8862
  import { chmod, mkdir as mkdir2, rename as rename3, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
8654
8863
  import { homedir as homedir3 } from "node:os";
8655
- import { basename as basename5, dirname as dirname9, join as join20 } from "node:path";
8864
+ import { basename as basename5, dirname as dirname10, join as join20 } from "node:path";
8656
8865
  import { chromium } from "playwright-core";
8657
8866
  function sessionFile(root, env = process.env) {
8658
8867
  let real = root;
@@ -8670,8 +8879,8 @@ function chosenSession(root, named2, env = process.env) {
8670
8879
  return existsSync7(file) ? { file, saved: true } : null;
8671
8880
  }
8672
8881
  async function writePrivate(file, content) {
8673
- await mkdir2(dirname9(file), { recursive: true, mode: 448 });
8674
- const temp = join20(dirname9(file), `.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
8882
+ await mkdir2(dirname10(file), { recursive: true, mode: 448 });
8883
+ const temp = join20(dirname10(file), `.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
8675
8884
  try {
8676
8885
  await writeFile2(temp, content, { encoding: "utf8", mode: 384 });
8677
8886
  await chmod(temp, 384);
@@ -4,10 +4,10 @@ import {
4
4
  loadPackage,
5
5
  readTryIndex,
6
6
  send
7
- } from "./chunk-W3ZCAAXH.js";
7
+ } from "./chunk-NG73FB4O.js";
8
8
  import {
9
9
  writeFileAtomic
10
- } from "./chunk-CLYYS6VI.js";
10
+ } from "./chunk-M56633DV.js";
11
11
 
12
12
  // ../../apps/web/scripts/discover-thumbs.ts
13
13
  import { existsSync, readFileSync } from "node:fs";