youiyoux 0.1.13 → 0.1.15

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 (77) hide show
  1. package/dist/chunks/{chunk-ZWVWENSB.js → chunk-A2IZOTJG.js} +1 -1
  2. package/dist/chunks/{chunk-5I2VHSEN.js → chunk-ALQVHSF2.js} +1 -1
  3. package/dist/chunks/{chunk-DX2NKY2M.js → chunk-CZJFEND6.js} +4 -4
  4. package/dist/chunks/{chunk-ZOQW6LNH.js → chunk-DAGYR4NV.js} +450 -30
  5. package/dist/chunks/{chunk-N5Z5A637.js → chunk-FTL53MU3.js} +2 -2
  6. package/dist/chunks/{chunk-PCLXMWZ2.js → chunk-HII2I2PI.js} +3 -3
  7. package/dist/chunks/{chunk-XOXF2ILO.js → chunk-HIXGUALJ.js} +1 -1
  8. package/dist/chunks/{chunk-PWGZRP3C.js → chunk-MIUG2Z43.js} +180 -8
  9. package/dist/chunks/chunk-QEUZDGIF.js +110 -0
  10. package/dist/chunks/{chunk-NF4OJYKC.js → chunk-S7UN5KQK.js} +1 -1
  11. package/dist/chunks/{chunk-EV62TUDV.js → chunk-T4EHV4XY.js} +230 -288
  12. package/dist/chunks/ingest-R2TM3QBM.js +52 -0
  13. package/dist/chunks/{src-IKDQ2BZO.js → src-4BOFDAZD.js} +1 -1
  14. package/dist/chunks/{src-4QOHR5GC.js → src-6JO73FOS.js} +3 -3
  15. package/dist/chunks/{src-PBM76LFQ.js → src-7SHBAJEO.js} +2 -2
  16. package/dist/chunks/{src-R7JGKOYI.js → src-CCGTRT5J.js} +18 -4
  17. package/dist/chunks/{src-E6ZPFKPR.js → src-IMWXYWQK.js} +23 -3
  18. package/dist/chunks/{src-624TVB6T.js → src-LUPO3O76.js} +2 -2
  19. package/dist/chunks/{src-HVYMF4S2.js → src-OHHZY3TA.js} +7 -7
  20. package/dist/chunks/{src-YC64NNQ3.js → src-WMGLZKYK.js} +2 -2
  21. package/dist/hub/assets/{CandidatePanel-DfntneYT.js → CandidatePanel-BbE2xCN2.js} +1 -1
  22. package/dist/hub/assets/CatalogScreen-CBlaW1-N.js +1 -0
  23. package/dist/hub/assets/ChangesScreen-WaA9Y7zq.js +1 -0
  24. package/dist/hub/assets/{CheatSheet-B1dXTYTW.js → CheatSheet-CGfTS7zw.js} +1 -1
  25. package/dist/hub/assets/{CommandPalette-LBZHJ659.js → CommandPalette-D4WigyuD.js} +1 -1
  26. package/dist/hub/assets/{DiscoverScreen-Yd3gVUG4.js → DiscoverScreen-CVtcQCM4.js} +2 -2
  27. package/dist/hub/assets/DraftEditor-Mp1bT8-o.js +5 -0
  28. package/dist/hub/assets/{GapsScreen-DPYGlNnA.js → GapsScreen-DzB0TfH3.js} +1 -1
  29. package/dist/hub/assets/{HomeScreen-BCPPk7mz.js → HomeScreen-BVxjYDtc.js} +1 -1
  30. package/dist/hub/assets/InventoryScreen-D33_YchG.js +1 -0
  31. package/dist/hub/assets/{LibraryScreen-BVXhOVPU.js → LibraryScreen-B8lAJBGW.js} +1 -1
  32. package/dist/hub/assets/{Opened-CJyMbxqV.js → Opened-DgNa-hGA.js} +1 -1
  33. package/dist/hub/assets/{OrgApp-Dxs_7CxZ.js → OrgApp-Q8YNFE-z.js} +1 -1
  34. package/dist/hub/assets/{Preview-DWFP0BIe.js → Preview-yu3LaZKL.js} +1 -1
  35. package/dist/hub/assets/{RuleDetail-C2fTlLJL.js → RuleDetail-BqVdc20s.js} +1 -1
  36. package/dist/hub/assets/{RulesScreen-BABeAGJg.js → RulesScreen-mb3OUlOB.js} +1 -1
  37. package/dist/hub/assets/{ScreenImage-CyhKm_nj.js → ScreenImage-DUMSgBJL.js} +1 -1
  38. package/dist/hub/assets/{ScreensScreen-8Ih8byGy.js → ScreensScreen-DjN5mwSP.js} +1 -1
  39. package/dist/hub/assets/{SearchField-hF-6-YDM.js → SearchField-ByftoJws.js} +1 -1
  40. package/dist/hub/assets/{Segmented-Dzi1x49S.js → Segmented-CTMaLPgh.js} +1 -1
  41. package/dist/hub/assets/{ServiceDown-S4ya0Cob.js → ServiceDown-BghtjpTn.js} +1 -1
  42. package/dist/hub/assets/{SpecimenCapture-e_pgT4Sp.js → SpecimenCapture-CglZ4bn-.js} +1 -1
  43. package/dist/hub/assets/{Specimens-D_9hjlJH.js → Specimens-D5ehj5-O.js} +2 -2
  44. package/dist/hub/assets/{StackScreen-J2FQilLW.js → StackScreen-w5hCLj_R.js} +1 -1
  45. package/dist/hub/assets/{StubScreens-DAkMph3i.js → StubScreens-DfF4aAbp.js} +1 -1
  46. package/dist/hub/assets/{StyleScreen-C5WytSZU.js → StyleScreen-DKkerxmC.js} +1 -1
  47. package/dist/hub/assets/{Tabs-DU-fFRDV.js → Tabs-B1IFs-wv.js} +1 -1
  48. package/dist/hub/assets/TokenDetail-BPTZ9o6J.js +1 -0
  49. package/dist/hub/assets/TokensScreen-CKnQuVaT.js +1 -0
  50. package/dist/hub/assets/{WorkspaceScreen-CzZ16fPZ.js → WorkspaceScreen-DwjGW6eY.js} +1 -1
  51. package/dist/hub/assets/{client-Bdgr12LP.js → client-uMxZXiDl.js} +1 -1
  52. package/dist/hub/assets/{discover-va2KFuK_.js → discover-Yfb0eB14.js} +1 -1
  53. package/dist/hub/assets/{dist-CC_p8DIj.js → dist-BvRlRM3Z.js} +1 -1
  54. package/dist/hub/assets/{dist-C8tf4g8c.js → dist-CL3qgCbi.js} +1 -1
  55. package/dist/hub/assets/{dist-YTWqg5sS.js → dist-tjVfXRHc.js} +1 -1
  56. package/dist/hub/assets/{family-tiles-qaK1-_c8.js → family-tiles-dZqjsxro.js} +1 -1
  57. package/dist/hub/assets/link-2-dzvhLwTC.js +1 -0
  58. package/dist/hub/assets/main-DNLr0KzQ.css +2 -0
  59. package/dist/hub/assets/main-Djqho539.js +2 -0
  60. package/dist/hub/assets/{try-live-Iys3nCBG.js → try-live-Dxsjf62v.js} +1 -1
  61. package/dist/hub/assets/{workflow-C53UBmRZ.js → workflow-Bv814-L8.js} +1 -1
  62. package/dist/hub/index.html +2 -2
  63. package/dist/hub-previews.js +6 -6
  64. package/dist/hub-server.js +3 -3
  65. package/dist/hub-snapshot.js +1 -1
  66. package/dist/schema/youi.library.schema.json +3 -1
  67. package/dist/youi.js +8 -7
  68. package/dist/youiyoux.js +8 -7
  69. package/package.json +4 -4
  70. package/dist/hub/assets/CatalogScreen-kpFYZqX3.js +0 -1
  71. package/dist/hub/assets/ChangesScreen-Chu-Ui1B.js +0 -1
  72. package/dist/hub/assets/DraftEditor-QH9Sa_Uc.js +0 -5
  73. package/dist/hub/assets/InventoryScreen-SleB733C.js +0 -1
  74. package/dist/hub/assets/TokenDetail-CDY1xxb9.js +0 -1
  75. package/dist/hub/assets/TokensScreen-Sx4aICWk.js +0 -1
  76. package/dist/hub/assets/main-cvOFSjox.js +0 -2
  77. package/dist/hub/assets/main-pNY4iNRo.css +0 -2
@@ -18,7 +18,7 @@ import {
18
18
  sameColor,
19
19
  splitTopLevel,
20
20
  tailwindTheme
21
- } from "./chunk-XOXF2ILO.js";
21
+ } from "./chunk-HIXGUALJ.js";
22
22
  import {
23
23
  AST_GREP_LANGUAGES,
24
24
  BUILT_IN_CHECKS,
@@ -51,7 +51,7 @@ import {
51
51
  toPosix,
52
52
  unifiedDiff,
53
53
  writeFileAtomic
54
- } from "./chunk-NF4OJYKC.js";
54
+ } from "./chunk-S7UN5KQK.js";
55
55
 
56
56
  // ../scanner/src/parse.ts
57
57
  import { Lang, parseAsync } from "@ast-grep/napi";
@@ -7928,7 +7928,7 @@ var Eyes = class {
7928
7928
  }
7929
7929
  async open() {
7930
7930
  if (this.browser) return this.browser;
7931
- const { launchBrowser } = await import("./src-PBM76LFQ.js");
7931
+ const { launchBrowser } = await import("./src-7SHBAJEO.js");
7932
7932
  try {
7933
7933
  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
7934
  } catch (error) {
@@ -6,7 +6,7 @@ import {
6
6
  require_dist,
7
7
  toPosix,
8
8
  validateTokenSets
9
- } from "./chunk-NF4OJYKC.js";
9
+ } from "./chunk-S7UN5KQK.js";
10
10
  import {
11
11
  __commonJS,
12
12
  __toESM
@@ -9,7 +9,7 @@ import {
9
9
  scanProject,
10
10
  screenSlug,
11
11
  tokenImpact
12
- } from "./chunk-PCLXMWZ2.js";
12
+ } from "./chunk-HII2I2PI.js";
13
13
  import {
14
14
  buildTokens,
15
15
  contrastText,
@@ -18,7 +18,7 @@ import {
18
18
  resolverLayout,
19
19
  ringOffset,
20
20
  systemPairs
21
- } from "./chunk-XOXF2ILO.js";
21
+ } from "./chunk-HIXGUALJ.js";
22
22
  import {
23
23
  BUILT_IN_CHECKS,
24
24
  COMPONENT_STATES,
@@ -45,7 +45,7 @@ import {
45
45
  toPosix,
46
46
  tokenValueProblems,
47
47
  writeFileAtomic
48
- } from "./chunk-NF4OJYKC.js";
48
+ } from "./chunk-S7UN5KQK.js";
49
49
  import {
50
50
  __toESM
51
51
  } from "./chunk-WNTS7ZO4.js";
@@ -844,12 +844,15 @@ var TokenEditInput = external_exports.object({ path: external_exports.string().m
844
844
  var StateEditInput = external_exports.object({ component: ElementId, state: external_exports.enum(COMPONENT_STATES), role: external_exports.string().min(1).max(100), token: ElementId });
845
845
  var StyleContentInput = external_exports.object({ title: external_exports.string().max(200), tokens: external_exports.array(TokenEditInput).max(500), states: external_exports.array(StateEditInput).max(200) });
846
846
  var LibraryContentInput = external_exports.object({ entry: LibraryEntry, request: external_exports.enum(["save", "adopt"]), reason: external_exports.string().max(2e3), swapsWith: external_exports.array(external_exports.string().min(1).max(200)).max(50) });
847
+ var IngestCreateInput = external_exports.object({ kind: external_exports.literal("ingest"), link: external_exports.string().min(1).max(2e3) });
848
+ var IngestKeepInput = external_exports.object({ keep: external_exports.array(external_exports.string().min(1).max(300)).max(1e3) }).strict();
847
849
  var CreateInput = external_exports.union([
848
850
  external_exports.object({ kind: external_exports.literal("style"), content: StyleContentInput.optional() }),
849
851
  external_exports.object({ kind: external_exports.literal("library"), content: LibraryContentInput }),
852
+ IngestCreateInput,
850
853
  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") })])
851
854
  ]);
852
- var SaveInput = external_exports.object({ revision: external_exports.number().int().positive(), content: external_exports.union([DraftContentInput, StyleContentInput, LibraryContentInput]) });
855
+ var SaveInput = external_exports.object({ revision: external_exports.number().int().positive(), content: external_exports.union([DraftContentInput, StyleContentInput, LibraryContentInput, IngestKeepInput]) });
853
856
  var RequestInput = external_exports.object({ kind: external_exports.enum(["comment", "change"]), body: external_exports.string().min(1).max(4e3), target: RequestTarget });
854
857
  var ReplyInput = external_exports.object({ body: external_exports.string().min(1).max(4e3) });
855
858
  var StatusInput = external_exports.object({ status: external_exports.enum(REQUEST_STATUSES), body: external_exports.string().max(4e3).optional() });
@@ -921,6 +924,7 @@ async function api(req, res, path, options) {
921
924
  if (path === "/api/drafts" && method === "POST") {
922
925
  const input = parse2(CreateInput, await readJson(req));
923
926
  if ("kind" in input && input.kind === "library") return send(res, 201, await workflow.create(actor, { kind: "library", content: input.content }));
927
+ if ("kind" in input && input.kind === "ingest") return send(res, 201, await workflow.create(actor, { kind: "ingest", link: input.link }));
924
928
  return send(res, 201, await workflow.create(actor, "kind" in input && input.kind === "style" ? { kind: "style", ...input.content ? { content: styleContent(input.content) } : {} } : input));
925
929
  }
926
930
  if (path === "/api/requests" && method === "GET") return send(res, 200, await (await workflow.requests()).list());
@@ -1051,6 +1055,55 @@ function describeLibrary(content) {
1051
1055
  return content.request === "adopt" ? `request adoption of ${name}${swaps} (licence ${licenceOf(content)})` : `save ${name} to the Library${swaps}`;
1052
1056
  }
1053
1057
 
1058
+ // ../workflow/src/ingest-drafts.ts
1059
+ function normalizeIngestContent(content) {
1060
+ const keep = new Set(content.keep);
1061
+ return { reading: content.reading, keep: content.reading.entries.map((e) => e.id).filter((id) => keep.has(id)) };
1062
+ }
1063
+ function ingestDigest(content) {
1064
+ return sha256(stableStringify(normalizeIngestContent(content)));
1065
+ }
1066
+ var keptEntries = (content) => {
1067
+ const keep = new Set(content.keep);
1068
+ return content.reading.entries.filter((e) => keep.has(e.id));
1069
+ };
1070
+ function validateIngestContent(content) {
1071
+ const problems = [];
1072
+ const ids = /* @__PURE__ */ new Set();
1073
+ const sources = new Set(content.reading.entries.map((e) => e.source.name));
1074
+ if (sources.size > 1) problems.push(`items come from ${sources.size} sources; one link is one source`);
1075
+ for (const entry of content.reading.entries) {
1076
+ const parsed = LibraryEntry.safeParse(entry);
1077
+ if (!parsed.success) problems.push(`${entry.id ?? "an item"}: ${parsed.error.issues.slice(0, 2).map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`);
1078
+ if (ids.has(entry.id)) problems.push(`${entry.id} is listed twice`);
1079
+ ids.add(entry.id);
1080
+ if (entry.stage !== "library") problems.push(`${entry.id}: an ingested item is a reference (stage library), not ${entry.stage}`);
1081
+ try {
1082
+ libraryPath(entry.id);
1083
+ } catch (error) {
1084
+ problems.push(error instanceof Error ? error.message : String(error));
1085
+ }
1086
+ }
1087
+ const unknown = content.keep.filter((id) => !ids.has(id));
1088
+ if (unknown.length > 0) problems.push(`keep: ${unknown.join(", ")} ${unknown.length === 1 ? "is" : "are"} not in this reading`);
1089
+ if (keptEntries(content).length === 0) problems.push("keep at least one item, or discard the draft");
1090
+ return problems;
1091
+ }
1092
+ function describeIngest(content) {
1093
+ const kept = keptEntries(content).length;
1094
+ const all = content.reading.entries.length;
1095
+ return `ingest ${kept === all ? `${all}` : `${kept} of ${all}`} item${all === 1 ? "" : "s"} from ${content.reading.source.name} into the Library`;
1096
+ }
1097
+ function ingestFiles(content, existing) {
1098
+ return Object.fromEntries(keptEntries(content).map((entry) => {
1099
+ const path = libraryPath(entry.id);
1100
+ return [path, renderLibraryEntry({ entry, request: "save", reason: "", swapsWith: [] }, existing(path))];
1101
+ }));
1102
+ }
1103
+ function ingestSlug(content) {
1104
+ return content.reading.source.name.toLowerCase().replace(/[^a-z0-9.-]+/g, "-").replace(/\.+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/, "") || "link";
1105
+ }
1106
+
1054
1107
  // ../workflow/src/preview.ts
1055
1108
  var MAX_FINDINGS = 400;
1056
1109
  var keyOf = (v) => `${v.file}:${v.line}:${v.column}:${v.text}`;
@@ -1423,6 +1476,12 @@ var WORKFLOW_VERSION = "0.1.0";
1423
1476
  var MAX_PLACES = 200;
1424
1477
  function previewSummary(draft) {
1425
1478
  if (draft.kind === "rule") return draft.preview ? `flags ${draft.preview.flagged} place(s)` : "no preview";
1479
+ if (draft.kind === "ingest") {
1480
+ const q = draft.preview;
1481
+ if (!q) return "no preview";
1482
+ const edits = q.files.filter((f) => f.exists).length;
1483
+ return `writes ${q.files.length} Library item(s)${edits ? ` (${edits} already there, edited)` : ""}${q.dropped ? `, ${q.dropped} dropped` : ""}, licence ${q.license}`;
1484
+ }
1426
1485
  if (draft.kind === "library") {
1427
1486
  const q = draft.preview;
1428
1487
  if (!q) return "no preview";
@@ -1520,6 +1579,7 @@ var RuleWorkflow = class {
1520
1579
  async problems(draft, current) {
1521
1580
  if (draft.kind === "style") return validateStyleContent(draft.content, await this.styleContext(current));
1522
1581
  if (draft.kind === "library") return validateLibraryContent(draft.content, { componentIds: new Set(current.project.system.components.map((c) => c.contract.id)) });
1582
+ if (draft.kind === "ingest") return validateIngestContent(draft.content);
1523
1583
  return validateContent(draft.content, {
1524
1584
  mode: draft.mode,
1525
1585
  ruleId: draft.ruleId,
@@ -1603,7 +1663,7 @@ var RuleWorkflow = class {
1603
1663
  checks: this.execution.supportedChecks(),
1604
1664
  executor: this.execution.name,
1605
1665
  components: current.project.system.components.map((c) => ({ id: c.contract.id, name: c.contract.name })),
1606
- kinds: ["rule", "style", "library"],
1666
+ kinds: ["rule", "style", "library", ...this.options.ingest ? ["ingest"] : []],
1607
1667
  limits: [
1608
1668
  `One design repository and one project (${current.project.config.id}).`,
1609
1669
  `Approved changes become a local review branch in the design repository; nothing is pushed or merged for you.`,
@@ -1655,6 +1715,7 @@ var RuleWorkflow = class {
1655
1715
  const current = await this.current();
1656
1716
  if (input.kind === "style") return this.createStyle(actor, current, input.content);
1657
1717
  if (input.kind === "library") return this.createLibrary(actor, current, input.content);
1718
+ if (input.kind === "ingest") return this.createIngest(actor, current, input.link);
1658
1719
  let content;
1659
1720
  let ruleId;
1660
1721
  const files = {};
@@ -1749,6 +1810,43 @@ var RuleWorkflow = class {
1749
1810
  await this.options.store.put(draft);
1750
1811
  return await this.view(draft, current);
1751
1812
  }
1813
+ /**
1814
+ * An ingest draft starts from what this service reads at the link, every item kept, bound to the
1815
+ * Library files the items would write. A link that cannot be read is said, and no draft exists.
1816
+ */
1817
+ async createIngest(actor, current, link) {
1818
+ if (!this.options.ingest) throw new WorkflowError("invalid", "this service cannot read links: run youiyoux library ingest <link> --draft");
1819
+ const reading = await this.options.ingest(link).catch((error) => {
1820
+ throw new WorkflowError("invalid", error instanceof Error ? error.message : String(error));
1821
+ });
1822
+ const content = normalizeIngestContent({ reading, keep: reading.entries.map((e) => e.id) });
1823
+ const problems = validateIngestContent(content);
1824
+ if (problems.length > 0) throw new WorkflowError("invalid", `${link} was read, but not into items the Library takes`, problems);
1825
+ const files = {};
1826
+ for (const entry of reading.entries) {
1827
+ const path = libraryPath(entry.id);
1828
+ files[path] = await current.files.digest(current.tip, path);
1829
+ }
1830
+ const at = this.now();
1831
+ const draft = {
1832
+ id: this.id("d"),
1833
+ kind: "ingest",
1834
+ mode: "create",
1835
+ createdAt: at,
1836
+ updatedAt: at,
1837
+ revision: 1,
1838
+ digest: ingestDigest(content),
1839
+ content,
1840
+ base: { branch: current.branch, commit: current.tip, files },
1841
+ preview: null,
1842
+ approval: null,
1843
+ handoff: null,
1844
+ discarded: false,
1845
+ events: [this.event(actor, "created", `${capitalized(describeIngest(content))}: read from ${reading.link}, from ${current.branch}@${current.tip.slice(0, 7)}.`)]
1846
+ };
1847
+ await this.options.store.put(draft);
1848
+ return await this.view(draft, current);
1849
+ }
1752
1850
  /**
1753
1851
  * The draft with the licence the local Discover index lists for its id, whatever the client
1754
1852
  * sent: `unknown` when the index does not list it. With no index, a save keeps what was sent
@@ -1770,8 +1868,10 @@ var RuleWorkflow = class {
1770
1868
  if (input.revision !== draft.revision) throw new WorkflowError("conflict", `draft ${id} is at revision ${draft.revision}, not ${input.revision}: it was saved elsewhere. Reload it before saving.`);
1771
1869
  const isStyle = "tokens" in input.content;
1772
1870
  const isLibrary = "entry" in input.content;
1773
- if (isStyle !== (draft.kind === "style") || isLibrary !== (draft.kind === "library")) throw new WorkflowError("bad-request", `draft ${id} is a ${draft.kind} draft; send ${draft.kind} content`);
1871
+ const isIngest = "keep" in input.content;
1872
+ if (isStyle !== (draft.kind === "style") || isLibrary !== (draft.kind === "library") || isIngest !== (draft.kind === "ingest")) throw new WorkflowError("bad-request", `draft ${id} is a ${draft.kind} draft; send ${draft.kind === "ingest" ? "the items to keep ({ keep: [ids] })" : `${draft.kind} content`}`);
1774
1873
  if (draft.kind === "library") return this.saveLibrary(actor, draft, input.revision, input.content);
1874
+ if (draft.kind === "ingest") return this.saveIngest(actor, draft, input.revision, input.content.keep);
1775
1875
  const current = await this.current();
1776
1876
  const content = isStyle ? normalizeStyleContent(input.content) : input.content;
1777
1877
  const digest = isStyle ? styleDigest(content) : contentDigest(content);
@@ -1784,6 +1884,21 @@ var RuleWorkflow = class {
1784
1884
  return this.view(next, current);
1785
1885
  });
1786
1886
  }
1887
+ /** Only which items are kept changes: the reading is what the service read, and stays so. */
1888
+ async saveIngest(actor, draft, revision, keep) {
1889
+ const current = await this.current();
1890
+ const known = new Set(draft.content.reading.entries.map((e) => e.id));
1891
+ const unknown = keep.filter((id) => !known.has(id));
1892
+ if (unknown.length > 0) throw new WorkflowError("invalid", `${unknown.join(", ")} ${unknown.length === 1 ? "is" : "are"} not in what ${draft.content.reading.source.name} held`);
1893
+ const content = normalizeIngestContent({ reading: draft.content.reading, keep });
1894
+ const digest = ingestDigest(content);
1895
+ if (digest === draft.digest) return this.refreshed(draft, current);
1896
+ const events = [...draft.events, this.event(actor, "saved", `Revision ${revision + 1}: ${describeIngest(content)}.`)];
1897
+ if (draft.approval) events.push(this.event(actor, "approval-invalidated", "Approval withdrawn: the draft changed."));
1898
+ const next = { ...draft, revision: draft.revision + 1, digest, content, approval: null, updatedAt: this.now(), events };
1899
+ await this.options.store.put(next);
1900
+ return this.view(next, current);
1901
+ }
1787
1902
  async saveLibrary(actor, draft, revision, input) {
1788
1903
  const current = await this.current();
1789
1904
  const content = await this.indexedLicence(normalizeLibraryContent(input));
@@ -1805,6 +1920,7 @@ var RuleWorkflow = class {
1805
1920
  this.guard(actor, "preview", draft);
1806
1921
  if (draft.kind === "style") return this.previewStyle(actor, draft);
1807
1922
  if (draft.kind === "library") return this.previewLibrary(actor, draft);
1923
+ if (draft.kind === "ingest") return this.previewIngest(actor, draft);
1808
1924
  const before = await this.current();
1809
1925
  const problems = await this.problems(draft, before);
1810
1926
  const { rule } = toRuleSpec(draft.content.rule);
@@ -1872,6 +1988,36 @@ var RuleWorkflow = class {
1872
1988
  await this.options.store.put(next);
1873
1989
  return this.refreshed(next, after);
1874
1990
  }
1991
+ /** An ingest draft's preview: each kept item's Library file, new or an edit, and what the items are not. */
1992
+ async previewIngest(actor, draft) {
1993
+ const before = await this.current();
1994
+ const problems = await this.problems(draft, before);
1995
+ if (problems.length > 0) throw new WorkflowError("invalid", "fix the draft before previewing it", problems);
1996
+ const started = performance.now();
1997
+ const files = [];
1998
+ for (const entry of keptEntries(draft.content)) {
1999
+ const file = libraryPath(entry.id);
2000
+ files.push({ id: entry.id, file, exists: await before.files.read(before.tip, file) !== null });
2001
+ }
2002
+ const after = await this.unchangedSince(before);
2003
+ const { reading } = draft.content;
2004
+ const license = reading.license === "unknown" ? "unknown" : reading.license.spdx;
2005
+ const preview = {
2006
+ ...this.previewHead(draft, before, started),
2007
+ files,
2008
+ dropped: reading.entries.length - files.length,
2009
+ license,
2010
+ limits: [
2011
+ "Nothing is installed and nothing joins the System: approving writes the kept items to the Library as references, each to be requested for adoption on its own.",
2012
+ ...license === "unknown" ? [`${reading.source.name}'s licence is unknown: its items can be kept as references, not adopted, until a licence is found.`] : [],
2013
+ ...files.some((f) => f.exists) ? ["Items already in the Library are rewritten with what this link holds now."] : [],
2014
+ ...reading.skipped.length > 0 ? [`${reading.skipped.length} thing(s) at the link were not read as items; the draft lists them.`] : []
2015
+ ]
2016
+ };
2017
+ const next = { ...draft, preview, updatedAt: this.now(), events: [...draft.events, this.event(actor, "previewed", `${capitalized(previewSummary({ ...draft, preview }))}.`)] };
2018
+ await this.options.store.put(next);
2019
+ return this.refreshed(next, after);
2020
+ }
1875
2021
  /** Build the tokens as published and as drafted, and trace every changed value through one fresh scan. */
1876
2022
  async previewStyle(actor, draft) {
1877
2023
  const before = await this.current();
@@ -1957,7 +2103,7 @@ var RuleWorkflow = class {
1957
2103
  const current = await this.current();
1958
2104
  const view = await this.refreshed(draft, current);
1959
2105
  if (!view.canHandoff) throw new WorkflowError("stale", "only a current approval can be handed off", [...view.problems, ...view.baseStale, ...view.approval ? [] : ["Approve the draft first (an approval is withdrawn when the draft, the design repository or the project changes)."]]);
1960
- const { files, branch, message } = draft.kind === "style" ? await this.styleChange(draft, view, current) : draft.kind === "library" ? await this.libraryChange(draft, view, current) : await this.ruleChange(draft, view, current);
2106
+ const { files, branch, message } = draft.kind === "style" ? await this.styleChange(draft, view, current) : draft.kind === "library" ? await this.libraryChange(draft, view, current) : draft.kind === "ingest" ? await this.ingestChange(draft, view, current) : await this.ruleChange(draft, view, current);
1961
2107
  if (Object.keys(files).length === 0) throw new WorkflowError("invalid", "this draft changes nothing in the design repository");
1962
2108
  const created = await createReviewBranch(current.repo.top, {
1963
2109
  base: draft.base.commit,
@@ -2050,6 +2196,25 @@ var RuleWorkflow = class {
2050
2196
  ].join("\n");
2051
2197
  return { files: text === existing ? {} : { [path]: text }, branch: `youi/library/${librarySlug(draft.content)}-${draft.id.slice(2, 8)}`, message };
2052
2198
  }
2199
+ /** What an ingest draft's review branch carries: one Library file per kept item. */
2200
+ async ingestChange(draft, view, current) {
2201
+ const existing = /* @__PURE__ */ new Map();
2202
+ for (const entry of keptEntries(draft.content)) existing.set(libraryPath(entry.id), await current.files.read(draft.base.commit, libraryPath(entry.id)));
2203
+ const all = ingestFiles(draft.content, (path) => existing.get(path) ?? null);
2204
+ const files = Object.fromEntries(Object.entries(all).filter(([path, text]) => text !== existing.get(path)));
2205
+ const { reading } = draft.content;
2206
+ const approval = view.approval;
2207
+ const message = [
2208
+ `library: ${describeIngest(draft.content)}`,
2209
+ "",
2210
+ `Read from ${reading.link} at ${reading.fetchedAt}; licence ${view.preview.license}.`,
2211
+ ...keptEntries(draft.content).map((e) => `- ${e.id}: ${e.name}`),
2212
+ "",
2213
+ `Ingest draft ${draft.id} revision ${draft.revision}.`,
2214
+ `Approved by ${approval.by.name} at ${approval.at}.`
2215
+ ].join("\n");
2216
+ return { files, branch: `youi/library/ingest-${ingestSlug(draft.content)}-${draft.id.slice(2, 8)}`, message };
2217
+ }
2053
2218
  async discard(actor, id) {
2054
2219
  return this.locked(id, async () => {
2055
2220
  const draft = await this.load(id);
@@ -2102,7 +2267,7 @@ function decode(text, file) {
2102
2267
  const commits = [draft.base.commit, draft.approval?.baseCommit, draft.handoff?.base, draft.handoff?.commit].filter((c) => c !== void 0);
2103
2268
  if (!commits.every((c) => typeof c === "string" && COMMIT.test(c))) throw new Error(`${file} names a commit that is not a git object id`);
2104
2269
  const kind = draft.kind ?? "rule";
2105
- if (kind !== "rule" && kind !== "style" && kind !== "library") throw new Error(`${file} is a "${String(kind)}" draft, which this youi does not know`);
2270
+ if (kind !== "rule" && kind !== "style" && kind !== "library" && kind !== "ingest") throw new Error(`${file} is a "${String(kind)}" draft, which this youi does not know`);
2106
2271
  return { ...draft, kind };
2107
2272
  }
2108
2273
  function checkId(id) {
@@ -2224,6 +2389,13 @@ export {
2224
2389
  validateLibraryContent,
2225
2390
  renderLibraryEntry,
2226
2391
  describeLibrary,
2392
+ normalizeIngestContent,
2393
+ ingestDigest,
2394
+ keptEntries,
2395
+ validateIngestContent,
2396
+ describeIngest,
2397
+ ingestFiles,
2398
+ ingestSlug,
2227
2399
  notRunReason,
2228
2400
  comparePreview,
2229
2401
  REQUESTS_DIR,
@@ -0,0 +1,110 @@
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
+ import {
3
+ loadProject
4
+ } from "./chunk-HII2I2PI.js";
5
+ import {
6
+ shortHash
7
+ } from "./chunk-S7UN5KQK.js";
8
+
9
+ // ../cli/src/ui.ts
10
+ import * as util from "node:util";
11
+ var styleText2 = util.styleText ?? ((format, text) => [format].flat().reduceRight((inner, name) => {
12
+ const [open, close] = util.inspect.colors[name] ?? [0, 0];
13
+ return `\x1B[${open}m${inner}\x1B[${close}m`;
14
+ }, text));
15
+ var enabled = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR && process.env.TERM !== "dumb";
16
+ var paint = (format) => (text) => enabled ? styleText2(format, String(text), { validateStream: false }) : String(text);
17
+ var style = {
18
+ bold: paint("bold"),
19
+ dim: paint("dim"),
20
+ error: paint("red"),
21
+ warn: paint("yellow"),
22
+ ok: paint("green"),
23
+ live: paint("blue"),
24
+ accent: paint(["bold", "white"])
25
+ };
26
+ function plural(count, word, many = `${word}s`) {
27
+ return `${count} ${count === 1 ? word : many}`;
28
+ }
29
+ var ANSI = /\u001b\[[0-9;]*m/g;
30
+ function visibleLength(text) {
31
+ return text.replace(ANSI, "").length;
32
+ }
33
+ function padEnd(text, width) {
34
+ return text + " ".repeat(Math.max(0, width - visibleLength(text)));
35
+ }
36
+ function padStart(text, width) {
37
+ return " ".repeat(Math.max(0, width - visibleLength(text))) + text;
38
+ }
39
+ function truncate(text, width) {
40
+ return text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}\u2026`;
41
+ }
42
+ function bar(value, max, width = 24) {
43
+ const cells = max === 0 ? 0 : Math.max(value > 0 ? 1 : 0, Math.round(value / max * width));
44
+ return style.dim("\u2588".repeat(cells));
45
+ }
46
+ function rule(width = 64) {
47
+ return style.dim("\u2500".repeat(width));
48
+ }
49
+ function ms(value) {
50
+ return value < 1e3 ? `${Math.round(value)} ms` : `${(value / 1e3).toFixed(2)} s`;
51
+ }
52
+
53
+ // ../cli/src/editor.ts
54
+ import { randomBytes } from "node:crypto";
55
+ import { existsSync } from "node:fs";
56
+ import { homedir, userInfo } from "node:os";
57
+ import { join, resolve } from "node:path";
58
+ async function swapImpact(candidate, family) {
59
+ try {
60
+ const { discoverIndexPath, sharedStore } = await import("./src-IMWXYWQK.js");
61
+ const path = discoverIndexPath();
62
+ if (!process.env.DATABASE_URL && !existsSync(path)) return null;
63
+ const impact = await (await sharedStore(path, process.env, { readOnly: true })).swapImpact(candidate, family);
64
+ return impact && !("choices" in impact) ? { looks: impact.looks, files: impact.files.length, routes: impact.routes.length, listed: impact.listed } : null;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+ async function libraryLicence(id) {
70
+ try {
71
+ const { discoverCacheDir, findListing } = await import("./src-IMWXYWQK.js");
72
+ const { entry } = await findListing(discoverCacheDir(void 0, process.env), id);
73
+ return entry.id === id ? entry.license : null;
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+ function workflowStateDir(project, state) {
79
+ return resolve(state ?? join(homedir(), ".youi", "workflow", shortHash(`${project.repo.root}\0${project.root}`)));
80
+ }
81
+ async function ingest(link) {
82
+ const { readLink } = await import("./ingest-R2TM3QBM.js");
83
+ return readLink(link, { allowLocal: false });
84
+ }
85
+ async function startRuleEditor(options) {
86
+ const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-CCGTRT5J.js");
87
+ const projectRoot = resolve(options.project);
88
+ const load = { ...options.config ? { configPath: resolve(options.config) } : {}, ...options.design ? { designRoot: resolve(options.design) } : {}, ...options.root ? { productRoot: resolve(options.root) } : {} };
89
+ const project = await loadProject(projectRoot, load);
90
+ const stateDir = workflowStateDir(project, options.state);
91
+ const workflow = new RuleWorkflow({ projectRoot, store: new FileStore(stateDir), swapImpact, libraryLicence, ingest, ...load });
92
+ const actor = { kind: "person", name: userInfo().username };
93
+ const info = await workflow.info(actor);
94
+ const sessionKey = randomBytes(24).toString("base64url");
95
+ const service = await serveWorkflow({ workflow, actor, sessionKey, port: 0 });
96
+ return { url: service.url, sessionKey, stateDir, info, close: () => service.close() };
97
+ }
98
+
99
+ export {
100
+ style,
101
+ plural,
102
+ padEnd,
103
+ padStart,
104
+ truncate,
105
+ bar,
106
+ rule,
107
+ ms,
108
+ workflowStateDir,
109
+ startRuleEditor
110
+ };
@@ -27450,7 +27450,7 @@ var FamilyId = external_exports.string().regex(FAMILY_ID_PATTERN, "expected <rol
27450
27450
  function isFamilyId(id) {
27451
27451
  return FAMILY_ID_PATTERN.test(id);
27452
27452
  }
27453
- var LIBRARY_SOURCE_KINDS = ["shadcn-registry", "npm", "github", "storybook"];
27453
+ var LIBRARY_SOURCE_KINDS = ["shadcn-registry", "npm", "github", "storybook", "figma", "design-md"];
27454
27454
  var Iso = external_exports.string().datetime({ offset: true });
27455
27455
  var LibraryLicense = external_exports.union([external_exports.literal("unknown"), external_exports.object({ spdx: external_exports.string().min(1), evidenceUrl: external_exports.string().url(), verifiedAt: Iso })]);
27456
27456
  var LibraryEntry = external_exports.object({