youiyoux 0.1.2 → 0.1.4

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 (212) hide show
  1. package/README.md +10 -0
  2. package/dist/chunks/{chunk-S7UV34H5.js → chunk-42YJX3W5.js} +1 -3
  3. package/dist/chunks/{chunk-FUB7B3VK.js → chunk-4BBYLTZX.js} +10 -10
  4. package/dist/chunks/chunk-52KOKYY6.js +8710 -0
  5. package/dist/chunks/chunk-FJZFCAL5.js +129 -0
  6. package/dist/chunks/chunk-FVVL5JCY.js +24 -0
  7. package/dist/chunks/{chunk-UHPK62Q5.js → chunk-M5XZWCPS.js} +240 -60
  8. package/dist/chunks/{chunk-ZLRFD5JI.js → chunk-NYDJYMID.js} +263 -16
  9. package/dist/chunks/{src-7ENJHYG4.js → chunk-RDTHHRZN.js} +319 -165
  10. package/dist/chunks/{chunk-E4HNK7VF.js → chunk-WNTS7ZO4.js} +1 -1
  11. package/dist/chunks/chunk-XAEMVEYX.js +188 -0
  12. package/dist/chunks/{chunk-CLNMVQZJ.js → chunk-YFCMTTXS.js} +708 -494
  13. package/dist/chunks/chunk-YJ7WMBTT.js +13761 -0
  14. package/dist/chunks/src-5IP4NGKT.js +115 -0
  15. package/dist/chunks/src-6CJR547Y.js +124 -0
  16. package/dist/chunks/{src-WQWYJ6IQ.js → src-BPETAFLW.js} +175 -76
  17. package/dist/chunks/{src-B742AYFC.js → src-LLV7IY6W.js} +55 -16
  18. package/dist/chunks/src-RHNZELGE.js +306 -0
  19. package/dist/chunks/{src-4KFNL3MY.js → src-SFKFERZ2.js} +18 -8
  20. package/dist/chunks/{src-VEHGMKYV.js → src-VL2ZYO6X.js} +4 -4
  21. package/dist/hub/assets/Badge-CUJUit4d.js +1 -0
  22. package/dist/hub/assets/CandidatePanel-CnbK4S0j.js +1 -0
  23. package/dist/hub/assets/CatalogScreen-B5OVL-AR.js +1 -0
  24. package/dist/hub/assets/ChangesScreen-GwGqJwxN.js +1 -0
  25. package/dist/hub/assets/CheatSheet-BLSOq-xa.js +1 -0
  26. package/dist/hub/assets/CodeBlock-CsiGFgvI.js +1 -0
  27. package/dist/hub/assets/CommandPalette-CAWQfdXJ.js +1 -0
  28. package/dist/hub/assets/CopyButton-DE8drl4P.js +1 -0
  29. package/dist/hub/assets/Disclosure-B1kTgPgk.js +1 -0
  30. package/dist/hub/assets/DiscoverScreen-Tl_Z6mgY.js +2 -0
  31. package/dist/hub/assets/DraftEditor-yIDapXTs.js +5 -0
  32. package/dist/hub/assets/Field-LwrJXoOs.js +1 -0
  33. package/dist/hub/assets/GapsScreen-DXZAlm6v.js +1 -0
  34. package/dist/hub/assets/HomeScreen-CEpiVhOa.js +1 -0
  35. package/dist/hub/assets/InventoryScreen-CEG3Pmw_.js +1 -0
  36. package/dist/hub/assets/KindIcon-BJ6bz-ny.js +1 -0
  37. package/dist/hub/assets/LibraryScreen-IaVnxrX9.js +1 -0
  38. package/dist/hub/assets/LicenceBadge-CLCUMMHE.js +1 -0
  39. package/dist/hub/assets/Opened-DJyf4Hnj.js +1 -0
  40. package/dist/hub/assets/Preview-C069zVMP.js +1 -0
  41. package/dist/hub/assets/RuleDetail-B4g2-TRe.js +3 -0
  42. package/dist/hub/assets/RulesScreen-CAaXIWjO.js +3 -0
  43. package/dist/hub/assets/ScreenImage-DcI7_G3O.js +1 -0
  44. package/dist/hub/assets/ScreensScreen-DwczCjZD.js +1 -0
  45. package/dist/hub/assets/SearchField-DaCpNAj6.js +1 -0
  46. package/dist/hub/assets/Section-BTnvUq_W.js +1 -0
  47. package/dist/hub/assets/Segmented-Dzi1x49S.js +1 -0
  48. package/dist/hub/assets/ServiceDown-Bv-BPXro.js +1 -0
  49. package/dist/hub/assets/SpecimenCapture-DlsjshVE.js +1 -0
  50. package/dist/hub/assets/Specimens-C-lHp7sr.js +2 -0
  51. package/dist/hub/assets/StackScreen-DkmELsqR.js +1 -0
  52. package/dist/hub/assets/StubScreens-Bt7lWLVK.js +1 -0
  53. package/dist/hub/assets/StyleScreen-tq1tbRBo.js +1 -0
  54. package/dist/hub/assets/Swatch-KDz4qxdM.js +1 -0
  55. package/dist/hub/assets/Tabs-1QQDS0I6.js +1 -0
  56. package/dist/hub/assets/TokenDetail-DvCw6GqC.js +1 -0
  57. package/dist/hub/assets/TokenValue-CsPRc2ai.js +1 -0
  58. package/dist/hub/assets/TokensScreen-DoENV8Xy.js +1 -0
  59. package/dist/hub/assets/arrow-right-DcbNDzqs.js +1 -0
  60. package/dist/hub/assets/bot-B3gC9oUR.js +1 -0
  61. package/dist/hub/assets/button-6mvMVtpb.js +1 -0
  62. package/dist/hub/assets/button.demo-DuU0DQbv.js +1 -0
  63. package/dist/hub/assets/card.demo-CufAxMAA.js +1 -0
  64. package/dist/hub/assets/chevron-down-CzN07vMi.js +1 -0
  65. package/dist/hub/assets/circle-alert-DT2LC4yf.js +1 -0
  66. package/dist/hub/assets/circle-check-DkwBMT62.js +1 -0
  67. package/dist/hub/assets/client-D7whEyep.js +1 -0
  68. package/dist/hub/assets/clsx-DB0hHKMi.js +1 -0
  69. package/dist/hub/assets/compass-BkKOZPYe.js +1 -0
  70. package/dist/hub/assets/contrast-BkNHUAPa.js +1 -0
  71. package/dist/hub/assets/createLucideIcon-CGDJWtKO.js +1 -0
  72. package/dist/hub/assets/discover-XjJEViwk.js +1 -0
  73. package/dist/hub/assets/dist-BYQYaPBV.js +1 -0
  74. package/dist/hub/assets/dist-Bk4srtDn.js +41 -0
  75. package/dist/hub/assets/dist-BogNzeK9.js +1 -0
  76. package/dist/hub/assets/dist-D0z7Bvsy.js +1 -0
  77. package/dist/hub/assets/download-BQI-Si9R.js +1 -0
  78. package/dist/hub/assets/engine-CMmt9yEz.js +1 -0
  79. package/dist/hub/assets/esm-NPpg23M8.js +1 -0
  80. package/dist/hub/assets/family-tiles-CyTdy_cP.js +1 -0
  81. package/dist/hub/assets/format-DxAYNTN_.js +1 -0
  82. package/dist/hub/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  83. package/dist/hub/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  84. package/dist/hub/assets/geist-latin-core-vkBVTr4s.woff2 +0 -0
  85. package/dist/hub/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  86. package/dist/hub/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  87. package/dist/hub/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  88. package/dist/hub/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  89. package/dist/hub/assets/geist-mono-latin-core-DccGGzmg.woff2 +0 -0
  90. package/dist/hub/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  91. package/dist/hub/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  92. package/dist/hub/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  93. package/dist/hub/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  94. package/dist/hub/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  95. package/dist/hub/assets/git-pull-request-arrow-RqUUIRCU.js +1 -0
  96. package/dist/hub/assets/input.demo-238gqWpz.js +1 -0
  97. package/dist/hub/assets/inventory-BI8QBdar.js +1 -0
  98. package/dist/hub/assets/inventory-source-lZYzcA8O.js +1 -0
  99. package/dist/hub/assets/jsx-runtime-D6zNwnaE.js +9 -0
  100. package/dist/hub/assets/layers-DfzdK_wG.js +1 -0
  101. package/dist/hub/assets/layout-grid-xFY9L9Oc.js +1 -0
  102. package/dist/hub/assets/library-W5R8GwvG.js +1 -0
  103. package/dist/hub/assets/main-7snyIdoi.css +2 -0
  104. package/dist/hub/assets/main-B9CIkk5m.js +2 -0
  105. package/dist/hub/assets/minus-BDifFhOo.js +1 -0
  106. package/dist/hub/assets/monitor-X_lELkcU.js +1 -0
  107. package/dist/hub/assets/mouse-pointer-click-CVJ6hWg8.js +1 -0
  108. package/dist/hub/assets/paintbrush-DOkEgSTO.js +1 -0
  109. package/dist/hub/assets/plural-BbGQPQKA.js +1 -0
  110. package/dist/hub/assets/preview-BA8CPsKb.css +2 -0
  111. package/dist/hub/assets/preview-DUm13vr1.js +3 -0
  112. package/dist/hub/assets/protocol-caP6umNL.js +1 -0
  113. package/dist/hub/assets/react-DB-4Zxce.js +1 -0
  114. package/dist/hub/assets/shield-check-0ka0kQct.js +1 -0
  115. package/dist/hub/assets/trash-B7aEfJps.js +1 -0
  116. package/dist/hub/assets/triangle-alert-Dkdi9eDR.js +1 -0
  117. package/dist/hub/assets/try-live-zX3CSd3P.js +1 -0
  118. package/dist/hub/assets/utils-BoaXfs8y.js +1 -0
  119. package/dist/hub/assets/values-DzxR_TlI.js +1 -0
  120. package/dist/hub/assets/worker-Bh-wSp42.js +1 -0
  121. package/dist/hub/assets/workflow-jpV8wh-U.js +1 -0
  122. package/dist/hub/assets/x-MLbFVa4v.js +1 -0
  123. package/dist/hub/favicon.svg +1 -0
  124. package/dist/hub/index.html +35 -0
  125. package/dist/hub/kit/candidates.json +1 -0
  126. package/dist/hub/kit/chunks/chunk-2NYFUOIV.js +14 -0
  127. package/dist/hub/kit/chunks/chunk-DCANE7XH.js +1 -0
  128. package/dist/hub/kit/chunks/chunk-FTGHCY4I.js +14 -0
  129. package/dist/hub/kit/chunks/chunk-IBF3UTJF.js +33 -0
  130. package/dist/hub/kit/chunks/chunk-LLXY4VSH.js +1 -0
  131. package/dist/hub/kit/chunks/chunk-PUPZMW4P.js +14 -0
  132. package/dist/hub/kit/chunks/chunk-PWLNZQET.js +1 -0
  133. package/dist/hub/kit/chunks/chunk-ULXWVOKJ.js +1 -0
  134. package/dist/hub/kit/chunks/chunk-WM4BGGMK.js +1 -0
  135. package/dist/hub/kit/chunks/chunk-ZQUNRK72.js +1 -0
  136. package/dist/hub/kit/class-variance-authority.js +1 -0
  137. package/dist/hub/kit/clsx.js +1 -0
  138. package/dist/hub/kit/fonts/geist-cyrillic-ext-wght-normal.woff2 +0 -0
  139. package/dist/hub/kit/fonts/geist-cyrillic-wght-normal.woff2 +0 -0
  140. package/dist/hub/kit/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
  141. package/dist/hub/kit/fonts/geist-latin-wght-normal.woff2 +0 -0
  142. package/dist/hub/kit/fonts/geist-vietnamese-wght-normal.woff2 +0 -0
  143. package/dist/hub/kit/frame.css.src +73 -0
  144. package/dist/hub/kit/frame.js +2 -0
  145. package/dist/hub/kit/lucide-react.js +1878 -0
  146. package/dist/hub/kit/preview.html +13 -0
  147. package/dist/hub/kit/radix-ui-react-dialog.js +41 -0
  148. package/dist/hub/kit/radix-ui-react-label.js +1 -0
  149. package/dist/hub/kit/radix-ui-react-separator.js +1 -0
  150. package/dist/hub/kit/radix-ui-react-slot.js +1 -0
  151. package/dist/hub/kit/radix-ui-react-switch.js +1 -0
  152. package/dist/hub/kit/radix-ui-react-tabs.js +1 -0
  153. package/dist/hub/kit/react-dom-client.js +1 -0
  154. package/dist/hub/kit/react-dom.js +1 -0
  155. package/dist/hub/kit/react-jsx-runtime.js +1 -0
  156. package/dist/hub/kit/react.js +1 -0
  157. package/dist/hub/kit/tailwind-merge.js +1 -0
  158. package/dist/hub/kit/tailwindcss/index.css +950 -0
  159. package/dist/hub/kit/tailwindcss/preflight.css +398 -0
  160. package/dist/hub/kit/tailwindcss/theme.css +510 -0
  161. package/dist/hub/kit/tailwindcss/utilities.css +1 -0
  162. package/dist/hub-demo/frame/button.demo.js +1 -0
  163. package/dist/hub-demo/frame/card.demo.js +1 -0
  164. package/dist/hub-demo/frame/demos.js +6 -0
  165. package/dist/hub-demo/frame/frame.css +770 -0
  166. package/dist/hub-demo/frame/input.demo.js +1 -0
  167. package/dist/hub-demo/preview/components/button.demo.tsx +47 -0
  168. package/dist/hub-demo/preview/components/button.tsx +31 -0
  169. package/dist/hub-demo/preview/components/card.demo.tsx +26 -0
  170. package/dist/hub-demo/preview/components/card.tsx +20 -0
  171. package/dist/hub-demo/preview/components/input.demo.tsx +39 -0
  172. package/dist/hub-demo/preview/components/input.tsx +19 -0
  173. package/dist/hub-demo/preview/components/utils.ts +5 -0
  174. package/dist/hub-demo/preview/system.css +161 -0
  175. package/dist/hub-demo/public/data/demo-app.usage.bedd9c82fe.json +1 -0
  176. package/dist/hub-demo/public/data/demo.catalog.a605de2281.json +1 -0
  177. package/dist/hub-demo/public/data/gaps.129a93538d.json +1 -0
  178. package/dist/hub-demo/public/data/inventory.165a4f6408.json +1 -0
  179. package/dist/hub-demo/public/data/inventory.console.badge.ad0238784e.json +1 -0
  180. package/dist/hub-demo/public/data/inventory.console.button.3249daff35.json +1 -0
  181. package/dist/hub-demo/public/data/inventory.console.card.9ea88708ac.json +1 -0
  182. package/dist/hub-demo/public/data/inventory.console.dialog.8b6574d93a.json +1 -0
  183. package/dist/hub-demo/public/data/inventory.console.input.bdffb756a7.json +1 -0
  184. package/dist/hub-demo/public/data/inventory.console.link-as-button.1ce7faa1f3.json +1 -0
  185. package/dist/hub-demo/public/data/inventory.console.tabs.813e694c44.json +1 -0
  186. package/dist/hub-demo/public/data/inventory.extras.e5d06c6ff4.json +1 -0
  187. package/dist/hub-demo/public/data/inventory.site.badge.047a68f644.json +1 -0
  188. package/dist/hub-demo/public/data/inventory.site.button.720938384e.json +1 -0
  189. package/dist/hub-demo/public/data/inventory.site.card.d8df5f6873.json +1 -0
  190. package/dist/hub-demo/public/data/inventory.site.link-as-button.9c1f8a55bf.json +1 -0
  191. package/dist/hub-demo/public/data/inventory.site.tabs.59bd0200b9.json +1 -0
  192. package/dist/hub-demo/public/data/manifest.json +39 -0
  193. package/dist/hub-demo/public/data/specimens.console.dark.046471907d.css +786 -0
  194. package/dist/hub-demo/public/data/specimens.console.light.aaffa2d213.css +786 -0
  195. package/dist/hub-demo/public/data/specimens.site.dark.94a6706908.css +92 -0
  196. package/dist/hub-demo/public/data/specimens.site.light.3ddbc8d28d.css +92 -0
  197. package/dist/hub-demo/public/previews/button.dark.webp +0 -0
  198. package/dist/hub-demo/public/previews/button.light.webp +0 -0
  199. package/dist/hub-demo/public/previews/card.dark.webp +0 -0
  200. package/dist/hub-demo/public/previews/card.light.webp +0 -0
  201. package/dist/hub-demo/public/previews/input.dark.webp +0 -0
  202. package/dist/hub-demo/public/previews/input.light.webp +0 -0
  203. package/dist/hub-previews.js +173 -0
  204. package/dist/hub-server.js +6329 -0
  205. package/dist/hub-snapshot.js +573 -0
  206. package/dist/schema/youi.gaps.schema.json +235 -0
  207. package/dist/schema/youi.request.schema.json +229 -0
  208. package/dist/tailwind-worker.js +3 -3
  209. package/dist/youi.js +10 -8
  210. package/dist/youiyoux.js +10 -8
  211. package/package.json +4 -4
  212. package/dist/chunks/chunk-3ZHGONNA.js +0 -21797
@@ -1,21 +1,24 @@
1
- import { createRequire as __youiRequire } from "node:module"; const require = __youiRequire(import.meta.url);
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
+ PICTURE_SCALE,
3
4
  SCANNER_VERSION,
4
- buildTokens,
5
5
  checkInputFor,
6
- contrastText,
7
- cssValue,
8
- gradePairs,
9
6
  loadProject,
10
- resolverLayout,
11
- ringOffset,
12
7
  ruleCapability,
13
8
  runChecks,
14
9
  scanProject,
15
- systemPairs,
10
+ screenSlug,
16
11
  tokenImpact
17
- } from "./chunk-3ZHGONNA.js";
18
- import "./chunk-S7UV34H5.js";
12
+ } from "./chunk-52KOKYY6.js";
13
+ import {
14
+ buildTokens,
15
+ contrastText,
16
+ cssValue,
17
+ gradePairs,
18
+ resolverLayout,
19
+ ringOffset,
20
+ systemPairs
21
+ } from "./chunk-YJ7WMBTT.js";
19
22
  import {
20
23
  BUILT_IN_CHECKS,
21
24
  COMPONENT_STATES,
@@ -23,6 +26,10 @@ import {
23
26
  ElementId,
24
27
  LIBRARY_DIR,
25
28
  LibraryEntry,
29
+ REQUEST_ID,
30
+ REQUEST_STATUSES,
31
+ RequestDoc,
32
+ RequestTarget,
26
33
  RuleCheck,
27
34
  RuleRollout,
28
35
  RuleSpec,
@@ -38,10 +45,10 @@ import {
38
45
  toPosix,
39
46
  tokenValueProblems,
40
47
  writeFileAtomic
41
- } from "./chunk-ZLRFD5JI.js";
48
+ } from "./chunk-NYDJYMID.js";
42
49
  import {
43
50
  __toESM
44
- } from "./chunk-E4HNK7VF.js";
51
+ } from "./chunk-WNTS7ZO4.js";
45
52
 
46
53
  // ../workflow/src/design-files.ts
47
54
  var import_yaml2 = __toESM(require_dist(), 1);
@@ -356,10 +363,10 @@ async function git(cwd, args, options = {}) {
356
363
  }
357
364
  }
358
365
  function gitWithInput(cwd, args, options) {
359
- return new Promise((resolve2, reject) => {
366
+ return new Promise((resolve, reject) => {
360
367
  const child = execFile("git", args, { cwd, maxBuffer: MAX_BUFFER, env: { ...process.env, ...options.env } }, (error, stdout, stderr) => {
361
368
  if (error) reject(Object.assign(error, { stderr }));
362
- else resolve2(stdout);
369
+ else resolve(stdout);
363
370
  });
364
371
  child.stdin?.end(options.input);
365
372
  });
@@ -407,12 +414,8 @@ async function isAncestor(top, commit, tip) {
407
414
  return false;
408
415
  }
409
416
  }
410
- async function listAt(top, commit, dir) {
411
- const out = await gitOrNull(top, ["ls-tree", "--name-only", `${commit}:${dir}`]);
412
- return out ? out.split("\n").filter(Boolean) : [];
413
- }
414
- async function listTreeAt(top, commit, dir) {
415
- const out = await gitOrNull(top, ["ls-tree", "-r", "--name-only", `${commit}:${dir}`]);
417
+ async function listAt(top, commit, dir, options = {}) {
418
+ const out = await gitOrNull(top, ["ls-tree", ...options.recursive ? ["-r"] : [], "--name-only", `${commit}:${dir}`]);
416
419
  return out ? out.split("\n").filter(Boolean) : [];
417
420
  }
418
421
  async function hasChanges(top, paths) {
@@ -436,14 +439,14 @@ async function fileDigest(file) {
436
439
  return sha256(await readFile(file));
437
440
  }
438
441
  function streamDigest(cwd, args) {
439
- return new Promise((resolve2, reject) => {
442
+ return new Promise((resolve, reject) => {
440
443
  const hash = createHash("sha256");
441
444
  const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
442
445
  let stderr = "";
443
446
  child.stdout.on("data", (chunk) => hash.update(chunk));
444
447
  child.stderr.on("data", (chunk) => stderr += chunk.toString());
445
448
  child.on("error", reject);
446
- child.on("close", (code) => code === 0 ? resolve2(hash.digest("hex")) : reject(new GitError(`git ${args.join(" ")} failed in ${cwd}: ${stderr.trim()}`)));
449
+ child.on("close", (code) => code === 0 ? resolve(hash.digest("hex")) : reject(new GitError(`git ${args.join(" ")} failed in ${cwd}: ${stderr.trim()}`)));
447
450
  });
448
451
  }
449
452
  var FALLBACK_IDENTITY = { name: "YouIyouX rule editor", email: "rule-editor@youiyoux.local" };
@@ -685,8 +688,10 @@ var DesignFiles = class {
685
688
  }
686
689
  /** The path a rule lives at, or would be created at. */
687
690
  async rulePath(commit, ruleId) {
688
- const existing = (await this.ruleFiles(commit)).get(ruleId);
689
- if (existing) return existing.path;
691
+ return (await this.ruleFiles(commit)).get(ruleId)?.path ?? this.newRulePath(ruleId);
692
+ }
693
+ /** Where a rule not in the design repository yet is created. */
694
+ newRulePath(ruleId) {
690
695
  return `${this.rulesDir}/${parseId(ruleId)?.name ?? "new-rule"}.rule.yaml`;
691
696
  }
692
697
  async contractPath(commit, componentId) {
@@ -712,7 +717,7 @@ var DesignFiles = class {
712
717
  const existing = rules.get(rule.id);
713
718
  const sibling = [...rules.values()][0]?.text;
714
719
  const schemaRef = (sibling ? (0, import_yaml2.parse)(sibling)?.$schema : void 0) ?? DEFAULT_SCHEMA_REF;
715
- const path = existing?.path ?? await this.rulePath(commit, rule.id);
720
+ const path = existing?.path ?? this.newRulePath(rule.id);
716
721
  const files = {};
717
722
  const ruleText = renderRuleYaml(rule, existing?.text ?? null, schemaRef);
718
723
  if (ruleText !== existing?.text) files[path] = ruleText;
@@ -735,7 +740,7 @@ var DesignFiles = class {
735
740
  /** Every file in the tokens directory at `commit` (design-root-relative path → text): what a token build reads. */
736
741
  async tokenTree(commit) {
737
742
  const dir = this.tokensDir;
738
- const names = await listTreeAt(this.repo.top, commit, this.top(dir));
743
+ const names = await listAt(this.repo.top, commit, this.top(dir), { recursive: true });
739
744
  const entries = await Promise.all(names.map(async (name) => [`${dir}/${name}`, await this.read(commit, `${dir}/${name}`) ?? ""]));
740
745
  return new Map(entries);
741
746
  }
@@ -801,11 +806,10 @@ var scannerExecution = {
801
806
  // ../workflow/src/http.ts
802
807
  import { createServer } from "node:http";
803
808
  import { timingSafeEqual } from "node:crypto";
804
- import { readFile as readFile2, stat } from "node:fs/promises";
805
- import { extname, join as join3, normalize, resolve, sep } from "node:path";
809
+ import { readFile as readFile2 } from "node:fs/promises";
806
810
 
807
811
  // ../workflow/src/policy.ts
808
- var PERSON_ONLY = /* @__PURE__ */ new Set(["approve", "handoff"]);
812
+ var PERSON_ONLY = /* @__PURE__ */ new Set(["approve", "handoff", "close"]);
809
813
  var WorkflowError = class extends Error {
810
814
  name = "WorkflowError";
811
815
  code;
@@ -817,7 +821,7 @@ var WorkflowError = class extends Error {
817
821
  }
818
822
  };
819
823
  function authorize(actor, action) {
820
- if (PERSON_ONLY.has(action) && actor.kind !== "person") throw new WorkflowError("forbidden", `an ${actor.kind} cannot ${action}: approving a design change is a person's decision`);
824
+ if (PERSON_ONLY.has(action) && actor.kind !== "person") throw new WorkflowError("forbidden", action === "close" ? `an ${actor.kind} cannot close or reopen a request: mark it addressed, and a person closes it` : `an ${actor.kind} cannot ${action}: approving a design change is a person's decision`);
821
825
  }
822
826
 
823
827
  // ../workflow/src/http.ts
@@ -846,22 +850,14 @@ var CreateInput = external_exports.union([
846
850
  external_exports.discriminatedUnion("mode", [external_exports.object({ kind: external_exports.literal("rule").optional(), mode: external_exports.literal("edit"), ruleId: ElementId }), external_exports.object({ kind: external_exports.literal("rule").optional(), mode: external_exports.literal("create") })])
847
851
  ]);
848
852
  var SaveInput = external_exports.object({ revision: external_exports.number().int().positive(), content: external_exports.union([DraftContentInput, StyleContentInput, LibraryContentInput]) });
853
+ var RequestInput = external_exports.object({ kind: external_exports.enum(["comment", "change"]), body: external_exports.string().min(1).max(4e3), target: RequestTarget });
854
+ var ReplyInput = external_exports.object({ body: external_exports.string().min(1).max(4e3) });
855
+ var StatusInput = external_exports.object({ status: external_exports.enum(REQUEST_STATUSES), body: external_exports.string().max(4e3).optional() });
849
856
  function styleContent(content) {
850
857
  return { ...content, tokens: content.tokens.map((t) => Object.fromEntries(Object.entries(t).filter(([, v]) => v !== void 0))) };
851
858
  }
852
859
  var ApproveInput = external_exports.object({ revision: external_exports.number().int().positive(), previewId: external_exports.string().min(1).max(40), acknowledgeContrast: external_exports.array(external_exports.string().min(1).max(600)).max(1e3).optional() });
853
860
  var STATUS = { "bad-request": 400, forbidden: 403, "not-found": 404, conflict: 409, stale: 409, invalid: 422, internal: 500 };
854
- var TYPES = {
855
- ".html": "text/html; charset=utf-8",
856
- ".js": "text/javascript; charset=utf-8",
857
- ".css": "text/css; charset=utf-8",
858
- ".json": "application/json; charset=utf-8",
859
- ".svg": "image/svg+xml",
860
- ".webp": "image/webp",
861
- ".png": "image/png",
862
- ".woff2": "font/woff2",
863
- ".ico": "image/x-icon"
864
- };
865
861
  var LOOPBACK_HOST = /^(?:127\.0\.0\.1|localhost|\[::1\])(?::\d+)?$/;
866
862
  var LOOPBACK_ORIGIN = /^http:\/\/(?:127\.0\.0\.1|localhost|\[::1\])(?::\d+)?$/;
867
863
  var BASE_HEADERS = { "x-content-type-options": "nosniff", "x-frame-options": "DENY", "content-security-policy": "frame-ancestors 'none'" };
@@ -927,6 +923,31 @@ async function api(req, res, path, options) {
927
923
  if ("kind" in input && input.kind === "library") return send(res, 201, await workflow.create(actor, { kind: "library", content: input.content }));
928
924
  return send(res, 201, await workflow.create(actor, "kind" in input && input.kind === "style" ? { kind: "style", ...input.content ? { content: styleContent(input.content) } : {} } : input));
929
925
  }
926
+ if (path === "/api/requests" && method === "GET") return send(res, 200, await (await workflow.requests()).list());
927
+ if (path === "/api/requests" && method === "POST") return send(res, 201, await (await workflow.requests()).create(actor, parse2(RequestInput, await readJson(req))));
928
+ const request = /^\/api\/requests\/(r-[0-9a-f]{10})(?:\/(replies|status|compare|before\.webp|after\.webp))?$/.exec(path);
929
+ if (request) {
930
+ const [, id, action] = request;
931
+ const store = await workflow.requests();
932
+ if (!action && method === "GET") return send(res, 200, await store.get(id));
933
+ if (action === "compare" && method === "GET") {
934
+ const { before, after } = await store.compare(id);
935
+ const view = (picture, which) => picture ? { ...picture, file: void 0, url: `/api/requests/${id}/${which}.webp` } : null;
936
+ return send(res, 200, { before: view(before, "before"), after: view(after, "after") });
937
+ }
938
+ if ((action === "before.webp" || action === "after.webp") && method === "GET") {
939
+ const picture = (await store.compare(id))[action === "before.webp" ? "before" : "after"];
940
+ if (!picture) throw new WorkflowError("not-found", `${id} has no ${action.replace(".webp", "")} picture`);
941
+ res.writeHead(200, { ...BASE_HEADERS, "content-type": "image/webp", "cache-control": "no-store" });
942
+ res.end(await readFile2(picture.file));
943
+ return;
944
+ }
945
+ if (action === "replies" && method === "POST") return send(res, 200, await store.reply(actor, id, parse2(ReplyInput, await readJson(req)).body));
946
+ if (action === "status" && method === "POST") {
947
+ const input = parse2(StatusInput, await readJson(req));
948
+ return send(res, 200, await store.setStatus(actor, id, input.status, input.body ?? ""));
949
+ }
950
+ }
930
951
  const match = /^\/api\/drafts\/(d-[0-9a-f]{10})(?:\/(preview|approve|rebase|handoff))?$/.exec(path);
931
952
  if (match) {
932
953
  const id = match[1];
@@ -944,27 +965,6 @@ async function api(req, res, path, options) {
944
965
  }
945
966
  throw new WorkflowError("not-found", `no endpoint ${method} ${path}`);
946
967
  }
947
- async function staticFile(res, root, path) {
948
- const base = resolve(root);
949
- let decoded;
950
- try {
951
- decoded = decodeURIComponent(path);
952
- } catch {
953
- throw new HttpError(400, "malformed path");
954
- }
955
- const wanted = resolve(base, `.${normalize(decoded)}`);
956
- if (wanted !== base && !wanted.startsWith(`${base}${sep}`)) throw new HttpError(404, "not found");
957
- let file = wanted;
958
- const info = await stat(file).catch(() => null);
959
- if (!info?.isFile()) {
960
- if (extname(path) in TYPES || /\.(?:map|txt)$/.test(path)) throw new HttpError(404, "not found");
961
- file = join3(base, "index.html");
962
- }
963
- const body = await readFile2(file);
964
- const immutable = file.includes(`${sep}assets${sep}`);
965
- res.writeHead(200, { ...BASE_HEADERS, "content-type": TYPES[extname(file)] ?? "application/octet-stream", "cache-control": immutable ? "public, max-age=31536000, immutable" : "no-cache" });
966
- res.end(body);
967
- }
968
968
  function createHandler(options) {
969
969
  return (req, res) => {
970
970
  void (async () => {
@@ -972,8 +972,7 @@ function createHandler(options) {
972
972
  const path = new URL(req.url ?? "/", "http://loopback").pathname;
973
973
  screen(req, options.sessionKey);
974
974
  if (path === "/api" || path.startsWith("/api/")) return await api(req, res, path, options);
975
- if (!options.webRoot || req.method !== "GET" && req.method !== "HEAD") throw new HttpError(404, "not found");
976
- await staticFile(res, options.webRoot, path);
975
+ throw new HttpError(404, "not found");
977
976
  } catch (error) {
978
977
  fail(res, error);
979
978
  }
@@ -1059,7 +1058,7 @@ var ORDER = { new: 0, kept: 1, resolved: 2 };
1059
1058
  function finding(v, change) {
1060
1059
  return { file: v.file, line: v.line, column: v.column, text: v.text, message: v.message, ...v.suggestion ? { suggestion: v.suggestion } : {}, severity: v.severity, change };
1061
1060
  }
1062
- var notRun = (run2) => run2 && (run2.state === "unsupported" || run2.state === "error") ? run2.reason ?? run2.state : null;
1061
+ var notRunReason = (run2) => run2 && (run2.state === "unsupported" || run2.state === "error") ? run2.reason ?? run2.state : null;
1063
1062
  function limitsOf(rule, baseline, scan, flagged, kept, baselineNotRun) {
1064
1063
  const limits = [];
1065
1064
  if (baselineNotRun) limits.push(`The published rule could not run on this snapshot (${baselineNotRun}), so nothing counts as already flagged or no longer flagged.`);
@@ -1077,10 +1076,10 @@ function limitsOf(rule, baseline, scan, flagged, kept, baselineNotRun) {
1077
1076
  function comparePreview(input) {
1078
1077
  const { project, scan, execution, draft, baseline } = input;
1079
1078
  const next = execution.evaluate(project, scan, [draft]);
1080
- const draftNotRun = notRun(next.checks[0]);
1079
+ const draftNotRun = notRunReason(next.checks[0]);
1081
1080
  if (draftNotRun) throw new WorkflowError("invalid", `the draft's check did not run: ${draftNotRun}`, [`check: ${draftNotRun}`]);
1082
1081
  const published = baseline ? execution.evaluate(project, scan, [baseline]) : null;
1083
- const baselineNotRun = notRun(published?.checks[0]);
1082
+ const baselineNotRun = notRunReason(published?.checks[0]);
1084
1083
  const before = published && !baselineNotRun ? published.violations : [];
1085
1084
  const beforeKeys = new Set(before.map(keyOf));
1086
1085
  const nextKeys = new Set(next.violations.map(keyOf));
@@ -1101,11 +1100,173 @@ function comparePreview(input) {
1101
1100
  };
1102
1101
  }
1103
1102
 
1104
- // ../workflow/src/service.ts
1103
+ // ../workflow/src/requests.ts
1105
1104
  import { randomBytes } from "node:crypto";
1105
+ import { existsSync, statSync } from "node:fs";
1106
+ import { copyFile, mkdir, readdir, readFile as readFile3, stat } from "node:fs/promises";
1107
+ import { join as join3 } from "node:path";
1108
+ var REQUESTS_DIR = ".youi/requests";
1109
+ var FILE = /^(r-[0-9a-f]{10})\.json$/;
1110
+ var authorOf = (actor) => ({ kind: actor.kind, name: actor.name });
1111
+ var screensDir = (root) => join3(root, ".youi/screens");
1112
+ var filedAt = (request) => request.thread.filter((e) => e.status === "open").at(-1)?.at ?? request.createdAt;
1113
+ var RequestStore = class {
1114
+ dir;
1115
+ root;
1116
+ project;
1117
+ options;
1118
+ /** `root`: the product's root, where `.youi/` lives; `project`: its id. */
1119
+ constructor(root, project, options = {}) {
1120
+ this.dir = join3(root, REQUESTS_DIR);
1121
+ this.root = root;
1122
+ this.project = project;
1123
+ this.options = options;
1124
+ }
1125
+ now() {
1126
+ return (this.options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
1127
+ }
1128
+ file(id) {
1129
+ if (!REQUEST_ID.test(id)) throw new WorkflowError("bad-request", `"${id.slice(0, 40)}" is not a request id (r-<10 hex>)`);
1130
+ return join3(this.dir, `${id}.json`);
1131
+ }
1132
+ async write(request) {
1133
+ const checked = RequestDoc.parse(request);
1134
+ await writeFileAtomic(this.file(checked.id), `${JSON.stringify(checked, null, 2)}
1135
+ `);
1136
+ return checked;
1137
+ }
1138
+ /** Every request, newest first. */
1139
+ async list() {
1140
+ const names = await readdir(this.dir).catch(() => []);
1141
+ const requests = [];
1142
+ const unreadable = [];
1143
+ for (const name of names.sort()) {
1144
+ const match = FILE.exec(name);
1145
+ if (!match) continue;
1146
+ const file = join3(REQUESTS_DIR, name);
1147
+ let value;
1148
+ try {
1149
+ value = JSON.parse(await readFile3(join3(this.dir, name), "utf8"));
1150
+ } catch (error) {
1151
+ unreadable.push({ file, reason: error instanceof Error ? error.message.split("\n")[0] : String(error) });
1152
+ continue;
1153
+ }
1154
+ const result = RequestDoc.safeParse(value);
1155
+ if (!result.success) unreadable.push({ file, reason: result.error.issues.map((i) => `${i.path.join(".") || "file"}: ${i.message}`).join("; ") });
1156
+ else if (result.data.id !== match[1]) unreadable.push({ file, reason: "its id is not its file name" });
1157
+ else requests.push(result.data);
1158
+ }
1159
+ requests.sort((a, b) => b.createdAt.localeCompare(a.createdAt) || a.id.localeCompare(b.id));
1160
+ return { requests, unreadable };
1161
+ }
1162
+ async get(id) {
1163
+ const text = await readFile3(this.file(id), "utf8").catch(() => null);
1164
+ if (text === null) throw new WorkflowError("not-found", `no request ${id}`);
1165
+ const parsed = RequestDoc.safeParse(JSON.parse(text));
1166
+ if (!parsed.success) throw new WorkflowError("invalid", `${REQUESTS_DIR}/${id}.json is not a valid youi.request/v1`, parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`));
1167
+ return parsed.data;
1168
+ }
1169
+ async create(actor, input) {
1170
+ authorize(actor, "draft");
1171
+ const body = input.body.trim();
1172
+ if (!body) throw new WorkflowError("invalid", "a request needs words: what to look at, or what to change");
1173
+ const at = this.now();
1174
+ const id = (this.options.newId ?? (() => `r-${randomBytes(5).toString("hex")}`))();
1175
+ const shot = join3(screensDir(this.root), `${screenSlug(input.target.route)}.light.webp`);
1176
+ let before;
1177
+ if (existsSync(shot)) {
1178
+ before = `${id}.before.webp`;
1179
+ await mkdir(this.dir, { recursive: true });
1180
+ await copyFile(shot, join3(this.dir, before));
1181
+ }
1182
+ return this.write({ schema: "youi.request/v1", id, project: this.project, kind: input.kind, status: "open", body, author: authorOf(actor), createdAt: at, updatedAt: at, target: input.target, ...before ? { before } : {}, thread: [] });
1183
+ }
1184
+ async reply(actor, id, body) {
1185
+ authorize(actor, "draft");
1186
+ const text = body.trim();
1187
+ if (!text) throw new WorkflowError("invalid", "a reply needs words");
1188
+ const request = await this.get(id);
1189
+ const at = this.now();
1190
+ return this.write({ ...request, updatedAt: at, thread: [...request.thread, { author: authorOf(actor), at, body: text }] });
1191
+ }
1192
+ /**
1193
+ * Before and after for a request: the picture kept when it was filed, and the screen's picture
1194
+ * now, with the component found again on it by its file and line (from the new capture's places).
1195
+ */
1196
+ async compare(id) {
1197
+ const request = await this.get(id);
1198
+ const t = request.target;
1199
+ const before = request.before === `${request.id}.before.webp` && existsSync(join3(this.dir, request.before)) ? { file: join3(this.dir, request.before), box: t.box ?? null, page: t.page ?? null, at: request.createdAt } : null;
1200
+ const slug = screenSlug(t.route);
1201
+ const shot = join3(screensDir(this.root), `${slug}.light.webp`);
1202
+ const info = await stat(shot).catch(() => null);
1203
+ if (!info) return { before, after: null };
1204
+ let box = t.box ?? null;
1205
+ let page = t.page ?? null;
1206
+ let found = false;
1207
+ try {
1208
+ const places = JSON.parse(await readFile3(join3(screensDir(this.root), `${slug}.places.json`), "utf8"));
1209
+ const again = t.file && t.line ? places.places?.find((p) => p.file === t.file && p.line === t.line && p.name === t.name) : void 0;
1210
+ if (places.size) page = places.size;
1211
+ if (again) {
1212
+ box = again.box;
1213
+ found = true;
1214
+ }
1215
+ } catch {
1216
+ }
1217
+ const at = info.mtime.toISOString();
1218
+ return { before, after: { file: shot, box, page, at, sinceFiled: at > filedAt(request), found } };
1219
+ }
1220
+ /**
1221
+ * Move a request on: `addressed` (anyone; an agent says what it did), or `closed` and `open`
1222
+ * (a person's: closing accepts the work, reopening says it is not done).
1223
+ */
1224
+ async setStatus(actor, id, status, body = "") {
1225
+ if (status !== "addressed") authorize(actor, "close");
1226
+ else authorize(actor, "draft");
1227
+ const request = await this.get(id);
1228
+ if (request.status === status) throw new WorkflowError("conflict", `${id} is already ${status}`);
1229
+ if (status === "addressed" && request.status === "closed") throw new WorkflowError("conflict", `${id} is closed: a person reopens it first`);
1230
+ const at = this.now();
1231
+ return this.write({ ...request, status, updatedAt: at, thread: [...request.thread, { author: authorOf(actor), at, body: body.trim(), status }] });
1232
+ }
1233
+ };
1234
+ function requestBrief(request, root) {
1235
+ const t = request.target;
1236
+ const picture = join3(root, ".youi/screens", `${screenSlug(t.route)}.light.webp`);
1237
+ const at = t.file && t.line ? `${t.file}:${t.line}` : null;
1238
+ const every = t.scope === "element" && t.element;
1239
+ const where = every ? `every use of ${t.element} (system_where_used lists them)` : at ? `${at}` : `the ${t.name} on ${t.route} (its line was not captured: find it from the picture and the route)`;
1240
+ const shot = existsSync(picture) ? statSync(picture).mtime.toISOString() : null;
1241
+ const recaptured = shot === null ? null : shot > filedAt(request);
1242
+ const capture = `screens_capture with route ${t.route} (or youiyoux screens capture --base <the app in development> --route ${t.route})`;
1243
+ const next = request.status === "closed" ? "Closed by a person: nothing to do." : request.status === "addressed" && recaptured === false ? `Marked addressed, but ${t.route} has not been captured since this was filed, so the person compares against the old picture: run ${capture}. A person then checks it and closes or reopens it.` : request.status === "addressed" ? "Marked addressed: a person checks it and closes or reopens it. Reply only if asked." : request.kind === "comment" ? `A comment on ${where}: reply with request_reply if it asks something; change code only if it asks for a change.` : `Make the change at ${where}${t.lookalike && t.element ? `, using the System's ${t.element} rather than the hand-built lookalike where that is the fix` : ""}; capture the screen again (${capture}) so the person sees it after; then call request_addressed with what you changed (file:line). Ask with request_reply first if it is unclear. A person closes it.`;
1244
+ return {
1245
+ id: request.id,
1246
+ kind: request.kind,
1247
+ status: request.status,
1248
+ ask: request.body,
1249
+ from: `${request.author.kind} ${request.author.name}`,
1250
+ createdAt: request.createdAt,
1251
+ route: t.route,
1252
+ component: t.name,
1253
+ element: t.element ?? null,
1254
+ lookalike: t.lookalike === true,
1255
+ at,
1256
+ scope: every ? `everywhere ${t.element} is used` : "this one, on this screen",
1257
+ picture: existsSync(picture) ? picture : null,
1258
+ recaptured,
1259
+ box: t.box ? { x: Math.round(t.box.x * PICTURE_SCALE), y: Math.round(t.box.y * PICTURE_SCALE), width: Math.round(t.box.width * PICTURE_SCALE), height: Math.round(t.box.height * PICTURE_SCALE) } : null,
1260
+ thread: request.thread.map((e) => `${e.author.kind} ${e.author.name}${e.status ? ` (marked ${e.status})` : ""}: ${e.body || "(no words)"}`),
1261
+ next
1262
+ };
1263
+ }
1264
+
1265
+ // ../workflow/src/service.ts
1266
+ import { randomBytes as randomBytes2 } from "node:crypto";
1106
1267
 
1107
1268
  // ../workflow/src/style-preview.ts
1108
- import { mkdir, mkdtemp as mkdtemp2, rm as rm2, writeFile } from "node:fs/promises";
1269
+ import { mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm2, writeFile } from "node:fs/promises";
1109
1270
  import { tmpdir as tmpdir2 } from "node:os";
1110
1271
  import { dirname, join as join4 } from "node:path";
1111
1272
  var MAX_LISTED = 5e3;
@@ -1113,7 +1274,7 @@ async function buildFrom(project, tree) {
1113
1274
  const root = await mkdtemp2(join4(tmpdir2(), "youi-style-"));
1114
1275
  try {
1115
1276
  for (const [path, text] of tree) {
1116
- await mkdir(dirname(join4(root, path)), { recursive: true });
1277
+ await mkdir2(dirname(join4(root, path)), { recursive: true });
1117
1278
  await writeFile(join4(root, path), text);
1118
1279
  }
1119
1280
  return (await buildTokens(root, project.system.config, project.config.outputs)).tokens;
@@ -1271,7 +1432,8 @@ function previewSummary(draft) {
1271
1432
  const p = draft.preview;
1272
1433
  return p ? `changes ${p.totals.tokens} token(s) and ${p.totals.contracts} component(s): ${p.totals.sites} site(s) and ${p.totals.direct} direct use(s) in ${p.totals.files} file(s) on ${p.totals.routes} screen(s)` : "no preview";
1273
1434
  }
1274
- var randomId = (prefix) => `${prefix}-${randomBytes(5).toString("hex")}`;
1435
+ var capitalized = (text) => `${text[0].toUpperCase()}${text.slice(1)}`;
1436
+ var randomId = (prefix) => `${prefix}-${randomBytes2(5).toString("hex")}`;
1275
1437
  var RuleWorkflow = class {
1276
1438
  options;
1277
1439
  execution;
@@ -1282,6 +1444,26 @@ var RuleWorkflow = class {
1282
1444
  this.options = options;
1283
1445
  this.execution = options.execution ?? scannerExecution;
1284
1446
  }
1447
+ /** The product's comments and change requests on its captured screens (requests.ts), in its `.youi/requests`. */
1448
+ async requests() {
1449
+ const project = await this.project();
1450
+ return new RequestStore(project.root, project.config.id, this.options.now ? { now: this.options.now } : {});
1451
+ }
1452
+ /** The project and its design definitions, as they are on disk now. */
1453
+ project() {
1454
+ const { projectRoot, configPath, designRoot, productRoot } = this.options;
1455
+ return loadProject(projectRoot, { ...configPath ? { configPath } : {}, ...designRoot ? { designRoot } : {}, ...productRoot ? { productRoot } : {} });
1456
+ }
1457
+ /** What every kind of preview starts with: the draft revision and snapshot it read, and how long it took. */
1458
+ previewHead(draft, before, started) {
1459
+ return { id: this.id("p"), at: this.now(), draftRevision: draft.revision, draftDigest: draft.digest, snapshot: before.snapshot, durationMs: Math.round(performance.now() - started) };
1460
+ }
1461
+ /** The design repository and project now, refused when they moved since `before` (while a preview ran). */
1462
+ async unchangedSince(before) {
1463
+ const after = await this.current();
1464
+ if (after.snapshot.digest !== before.snapshot.digest) throw new WorkflowError("stale", "the project or design repository changed while the preview ran; run it again");
1465
+ return after;
1466
+ }
1285
1467
  now() {
1286
1468
  return (this.options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
1287
1469
  }
@@ -1300,8 +1482,7 @@ var RuleWorkflow = class {
1300
1482
  }
1301
1483
  }
1302
1484
  async current() {
1303
- const { projectRoot, configPath, designRoot, productRoot } = this.options;
1304
- const project = await loadProject(projectRoot, { ...configPath ? { configPath } : {}, ...designRoot ? { designRoot } : {}, ...productRoot ? { productRoot } : {} });
1485
+ const project = await this.project();
1305
1486
  const repo = await locate(project.repo.root);
1306
1487
  if (!repo) throw new WorkflowError("invalid", `the design definitions at ${project.repo.root} are not in a git repository; drafts need a commit to start from`);
1307
1488
  const branch = await defaultBranch(repo.top);
@@ -1446,13 +1627,13 @@ var RuleWorkflow = class {
1446
1627
  current.project.system.rules.map(async (rule) => {
1447
1628
  const places = scan.report.violations.filter((v) => v.rule === rule.id);
1448
1629
  const run2 = scan.report.checks.find((c) => c.rule === rule.id);
1449
- const notRun2 = run2 && (run2.state === "unsupported" || run2.state === "error") ? run2.reason ?? run2.state : null;
1630
+ const notRun = notRunReason(run2);
1450
1631
  return {
1451
1632
  rule: { ...rule, execution: ruleCapability(rule) },
1452
1633
  file: files.get(rule.id)?.path ?? await current.files.rulePath(current.tip, rule.id),
1453
1634
  components: current.project.system.components.filter((c) => c.contract.rules.includes(rule.id)).map((c) => c.contract.id),
1454
- flagged: notRun2 ? null : places.length,
1455
- ...notRun2 ? { notRun: notRun2 } : {},
1635
+ flagged: notRun ? null : places.length,
1636
+ ...notRun ? { notRun } : {},
1456
1637
  places: places.slice(0, MAX_PLACES).map(({ file, line, column, text, message }) => ({ file, line, column, text, message }))
1457
1638
  };
1458
1639
  })
@@ -1511,7 +1692,7 @@ var RuleWorkflow = class {
1511
1692
  }
1512
1693
  /** A style draft starts from the default branch tip, bound to every file it could touch. */
1513
1694
  async createStyle(actor, current, initial) {
1514
- const content = normalizeStyleContent(initial ?? { title: "", tokens: [], states: [] });
1695
+ const content = normalizeStyleContent(initial ?? emptyStyleContent());
1515
1696
  const files = {};
1516
1697
  for (const path of await current.files.stylePaths(current.tip)) files[path] = await current.files.digest(current.tip, path);
1517
1698
  const at = this.now();
@@ -1563,7 +1744,7 @@ var RuleWorkflow = class {
1563
1744
  approval: null,
1564
1745
  handoff: null,
1565
1746
  discarded: false,
1566
- events: [this.event(actor, "created", `${describeLibrary(content)[0].toUpperCase()}${describeLibrary(content).slice(1)}, from ${current.branch}@${current.tip.slice(0, 7)}.`)]
1747
+ events: [this.event(actor, "created", `${capitalized(describeLibrary(content))}, from ${current.branch}@${current.tip.slice(0, 7)}.`)]
1567
1748
  };
1568
1749
  await this.options.store.put(draft);
1569
1750
  return await this.view(draft, current);
@@ -1632,20 +1813,14 @@ var RuleWorkflow = class {
1632
1813
  const scan = await scanProject(before.project);
1633
1814
  const baseline = before.project.system.rules.find((r) => r.id === draft.ruleId) ?? null;
1634
1815
  const comparison = comparePreview({ project: before.project, scan, execution: this.execution, draft: rule, baseline: draft.mode === "edit" ? baseline : null });
1635
- const after = await this.current();
1636
- if (after.snapshot.digest !== before.snapshot.digest) throw new WorkflowError("stale", "the project or design repository changed while the preview ran; run it again");
1816
+ const after = await this.unchangedSince(before);
1637
1817
  const { report } = scan;
1638
1818
  const designLimits = [
1639
1819
  ...before.designDirty ? ["The design repository has uncommitted changes or another branch checked out; this preview reads the working tree."] : [],
1640
1820
  ...before.snapshot.projectCommit === null ? [`${report.project} is not a git repository: later edits to it cannot be detected, so this preview cannot go stale.`] : []
1641
1821
  ];
1642
1822
  const preview = {
1643
- id: this.id("p"),
1644
- at: this.now(),
1645
- draftRevision: draft.revision,
1646
- draftDigest: draft.digest,
1647
- snapshot: before.snapshot,
1648
- durationMs: Math.round(performance.now() - started),
1823
+ ...this.previewHead(draft, before, started),
1649
1824
  scan: { status: report.status, files: report.coverage.filesScanned, failed: report.coverage.failed },
1650
1825
  ...comparison,
1651
1826
  limits: [...comparison.limits, ...designLimits]
@@ -1676,20 +1851,14 @@ var RuleWorkflow = class {
1676
1851
  if (!impact) indexed = false;
1677
1852
  swaps.push({ target, kind: "family", name: null, looks: impact?.looks ?? null, files: impact?.files ?? null, routes: impact?.routes ?? null, listed: impact?.listed ?? null });
1678
1853
  }
1679
- const after = await this.current();
1680
- if (after.snapshot.digest !== before.snapshot.digest) throw new WorkflowError("stale", "the project or design repository changed while the preview ran; run it again");
1854
+ const after = await this.unchangedSince(before);
1681
1855
  const entry = draft.content.entry;
1682
1856
  const preview = {
1683
- id: this.id("p"),
1684
- at: this.now(),
1685
- draftRevision: draft.revision,
1686
- draftDigest: draft.digest,
1687
- snapshot: before.snapshot,
1688
- durationMs: Math.round(performance.now() - started),
1857
+ ...this.previewHead(draft, before, started),
1689
1858
  file,
1690
1859
  exists,
1691
1860
  stage: draft.content.request === "adopt" ? "candidate" : "library",
1692
- license: entry.license && entry.license !== "unknown" ? entry.license.spdx : "unknown",
1861
+ license: licenceOf(draft.content),
1693
1862
  install: entry.install?.command ?? null,
1694
1863
  swaps,
1695
1864
  limits: [
@@ -1699,7 +1868,7 @@ var RuleWorkflow = class {
1699
1868
  ...swaps.some((s) => s.kind === "element") ? ["System elements are named, not counted: youiyoux where-used counts their sites."] : []
1700
1869
  ]
1701
1870
  };
1702
- const next = { ...draft, preview, updatedAt: this.now(), events: [...draft.events, this.event(actor, "previewed", `${previewSummary({ ...draft, preview })[0].toUpperCase()}${previewSummary({ ...draft, preview }).slice(1)}.`)] };
1871
+ const next = { ...draft, preview, updatedAt: this.now(), events: [...draft.events, this.event(actor, "previewed", `${capitalized(previewSummary({ ...draft, preview }))}.`)] };
1703
1872
  await this.options.store.put(next);
1704
1873
  return this.refreshed(next, after);
1705
1874
  }
@@ -1711,16 +1880,10 @@ var RuleWorkflow = class {
1711
1880
  const started = performance.now();
1712
1881
  const scan = await scanProject(before.project);
1713
1882
  const impact = await previewStyle({ project: before.project, scan, files: before.files, commit: draft.base.commit, content: draft.content, published: await before.tokens() });
1714
- const after = await this.current();
1715
- if (after.snapshot.digest !== before.snapshot.digest) throw new WorkflowError("stale", "the project or design repository changed while the preview ran; run it again");
1883
+ const after = await this.unchangedSince(before);
1716
1884
  const { report } = scan;
1717
1885
  const preview = {
1718
- id: this.id("p"),
1719
- at: this.now(),
1720
- draftRevision: draft.revision,
1721
- draftDigest: draft.digest,
1722
- snapshot: before.snapshot,
1723
- durationMs: Math.round(performance.now() - started),
1886
+ ...this.previewHead(draft, before, started),
1724
1887
  scan: { status: report.status, files: report.coverage.filesScanned, failed: report.coverage.failed },
1725
1888
  ...impact,
1726
1889
  limits: [
@@ -1925,7 +2088,7 @@ function shellQuote(path) {
1925
2088
  }
1926
2089
 
1927
2090
  // ../workflow/src/store.ts
1928
- import { mkdir as mkdir2, readdir, readFile as readFile3 } from "node:fs/promises";
2091
+ import { mkdir as mkdir3, readdir as readdir2, readFile as readFile4 } from "node:fs/promises";
1929
2092
  import { join as join5 } from "node:path";
1930
2093
  var DRAFT_ID = /^d-[0-9a-f]{10}$/;
1931
2094
  var FORMAT = "youiyoux.draft/v1";
@@ -1955,14 +2118,14 @@ var FileStore = class {
1955
2118
  this.artifacts = join5(dir, "handoffs");
1956
2119
  }
1957
2120
  async list() {
1958
- await mkdir2(this.drafts, { recursive: true });
1959
- const names = (await readdir(this.drafts)).filter((name) => /^d-[0-9a-f]{10}\.json$/.test(name)).sort();
2121
+ await mkdir3(this.drafts, { recursive: true });
2122
+ const names = (await readdir2(this.drafts)).filter((name) => /^d-[0-9a-f]{10}\.json$/.test(name)).sort();
1960
2123
  const drafts = [];
1961
2124
  const unreadable = [];
1962
2125
  for (const name of names) {
1963
2126
  const file = join5(this.drafts, name);
1964
2127
  try {
1965
- drafts.push(decode(await readFile3(file, "utf8"), file));
2128
+ drafts.push(decode(await readFile4(file, "utf8"), file));
1966
2129
  } catch (error) {
1967
2130
  unreadable.push({ file, reason: error instanceof Error ? error.message : String(error) });
1968
2131
  }
@@ -1974,7 +2137,7 @@ var FileStore = class {
1974
2137
  const file = join5(this.drafts, `${id}.json`);
1975
2138
  let text;
1976
2139
  try {
1977
- text = await readFile3(file, "utf8");
2140
+ text = await readFile4(file, "utf8");
1978
2141
  } catch (error) {
1979
2142
  if (error.code === "ENOENT") return null;
1980
2143
  throw error;
@@ -2012,72 +2175,63 @@ var MemoryStore = class {
2012
2175
  return `memory:${name}`;
2013
2176
  }
2014
2177
  };
2178
+
2015
2179
  export {
2016
- DEFAULT_FILES,
2017
- DRAFT_ID,
2018
- DesignFiles,
2019
- RuleWorkflow as DesignWorkflow,
2020
- FileStore,
2021
- GitError,
2022
- JsonEditError,
2023
- MAX_FINDINGS,
2024
- MAX_LISTED,
2025
- MAX_TITLE,
2026
- MemoryStore,
2027
- RuleWorkflow,
2028
- SESSION_HEADER,
2029
- WORKFLOW_VERSION,
2030
- WorkflowError,
2031
- adoptionRefusal,
2032
- authorize,
2033
- branchTip,
2034
- changesDigest,
2035
- comparePreview,
2036
- componentChanges,
2037
- contentDigest,
2038
- createHandler,
2039
- createReviewBranch,
2040
- defaultBranch,
2180
+ readJsonPath,
2181
+ setJsonPath,
2041
2182
  deleteJsonPath,
2042
- describeContrast,
2043
- describeLibrary,
2044
- describeStyle,
2045
- draftStatus,
2046
- effectiveStateEdits,
2047
- effectiveTokenEdits,
2048
- emptyStyleContent,
2049
2183
  fieldsFromRule,
2184
+ newRuleFields,
2185
+ toRuleSpec,
2186
+ validateContent,
2187
+ contentDigest,
2188
+ renderRuleYaml,
2189
+ renderContract,
2190
+ draftStatus,
2191
+ componentChanges,
2050
2192
  git,
2051
- hasChanges,
2193
+ locate,
2194
+ defaultBranch,
2195
+ branchTip,
2052
2196
  headCommit,
2197
+ readAt,
2053
2198
  isAncestor,
2054
- libraryDigest,
2055
- libraryPath,
2056
- librarySlug,
2057
2199
  listAt,
2058
- listTreeAt,
2059
- locate,
2060
- newContrastFailures,
2061
- newRuleFields,
2062
- normalizeLibraryContent,
2200
+ hasChanges,
2201
+ changesDigest,
2202
+ createReviewBranch,
2203
+ emptyStyleContent,
2063
2204
  normalizeStyleContent,
2064
- previewStyle,
2065
- previewSummary,
2066
- readAt,
2067
- readJsonPath,
2068
- renderContract,
2069
- renderContractStates,
2070
- renderLibraryEntry,
2071
- renderRuleYaml,
2205
+ styleDigest,
2206
+ tokenPathOf,
2207
+ validateStyleContent,
2208
+ effectiveStateEdits,
2072
2209
  renderTokenEdits,
2210
+ renderContractStates,
2211
+ styleSlug,
2212
+ describeStyle,
2213
+ DesignFiles,
2073
2214
  scannerExecution,
2215
+ WorkflowError,
2216
+ authorize,
2074
2217
  serveWorkflow,
2075
- setJsonPath,
2076
- styleDigest,
2077
- styleSlug,
2078
- toRuleSpec,
2079
- tokenPathOf,
2080
- validateContent,
2218
+ normalizeLibraryContent,
2219
+ libraryDigest,
2220
+ libraryPath,
2221
+ librarySlug,
2222
+ licenceOf,
2223
+ adoptionRefusal,
2081
2224
  validateLibraryContent,
2082
- validateStyleContent
2225
+ renderLibraryEntry,
2226
+ describeLibrary,
2227
+ notRunReason,
2228
+ comparePreview,
2229
+ REQUESTS_DIR,
2230
+ RequestStore,
2231
+ requestBrief,
2232
+ MAX_LISTED,
2233
+ previewStyle,
2234
+ RuleWorkflow,
2235
+ FileStore,
2236
+ MemoryStore
2083
2237
  };