yamlover 0.3.50 → 0.3.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -67887,6 +67887,27 @@ function deadLinkDiagnostics(doc, store, dataRoot) {
67887
67887
  }
67888
67888
  return out;
67889
67889
  }
67890
+ function doubledFragmentDiagnostics(doc) {
67891
+ const out = [];
67892
+ const walk = (node, segs) => {
67893
+ const entries = node.entries ?? [];
67894
+ entries.forEach((e3, i2) => {
67895
+ const key = typeof e3.key === "string" ? e3.key : null;
67896
+ if (key === "fragments" && segs[segs.length - 1] === "fragments" && segs[segs.length - 2] === "yo") {
67897
+ out.push({
67898
+ code: "annotations/doubled-fragments",
67899
+ severity: "warning",
67900
+ message: `a \`fragments:\` mapping nested inside \`yo: fragments:\` \u2014 left by a pre-fix annotate over the flat spelling`,
67901
+ path: ":" + [...segs, key].join(":"),
67902
+ hint: "move its child fragments up one level, into the enclosing fragments mapping"
67903
+ });
67904
+ }
67905
+ if (e3.value !== null && typeof e3.value === "object") walk(e3.value, [...segs, key ?? String(i2)]);
67906
+ });
67907
+ };
67908
+ walk(doc.root, []);
67909
+ return out;
67910
+ }
67890
67911
  function deadLinkTargets(doc, store) {
67891
67912
  const out = /* @__PURE__ */ new Set();
67892
67913
  for (const l2 of scanTextLinks(doc)) {
@@ -67935,6 +67956,18 @@ function findKeyLine(lines, lo, hi, indent, key) {
67935
67956
  }
67936
67957
  return -1;
67937
67958
  }
67959
+ function flatRun(line, keys, k) {
67960
+ let rest = line.trim();
67961
+ let n2 = 0;
67962
+ while (k + n2 < keys.length && rest !== "") {
67963
+ const key = keys[k + n2];
67964
+ const tok = [key, keyToken(key)].find((t3) => rest === `${t3}:` || rest.startsWith(`${t3}: `));
67965
+ if (tok === void 0) break;
67966
+ rest = rest === `${tok}:` ? "" : rest.slice(tok.length + 2);
67967
+ n2++;
67968
+ }
67969
+ return Math.max(1, n2);
67970
+ }
67938
67971
  function trimBack(lines, floor, end) {
67939
67972
  let e3 = end;
67940
67973
  while (e3 > floor + 1 && !isContentLine(lines[e3 - 1])) e3--;
@@ -67971,7 +68004,8 @@ function bodyUnder(lines, L2, hi, indent) {
67971
68004
  }
67972
68005
  function reachBodyAt(lines, start, within) {
67973
68006
  let { lo, hi, indent } = start;
67974
- for (const key of within) {
68007
+ for (let k = 0; k < within.length; ) {
68008
+ const key = within[k];
67975
68009
  const L2 = findKeyLine(lines, lo, hi, indent, key);
67976
68010
  if (L2 < 0) {
67977
68011
  const at = trimBack(lines, lo - 1, hi);
@@ -67979,18 +68013,23 @@ function reachBodyAt(lines, start, within) {
67979
68013
  lo = at + 1;
67980
68014
  hi = at + 1;
67981
68015
  indent += 2;
68016
+ k++;
67982
68017
  continue;
67983
68018
  }
68019
+ const consumed = flatRun(lines[L2], within, k);
67984
68020
  ({ lo, hi, indent } = bodyUnder(lines, L2, hi, indent));
68021
+ k += consumed;
67985
68022
  }
67986
68023
  return { lo, hi, indent };
67987
68024
  }
67988
68025
  function findBody(lines, within) {
67989
68026
  let region = { lo: 0, hi: lines.length, indent: firstContentIndent(lines) };
67990
- for (const key of within) {
67991
- const L2 = findKeyLine(lines, region.lo, region.hi, region.indent, key);
68027
+ for (let k = 0; k < within.length; ) {
68028
+ const L2 = findKeyLine(lines, region.lo, region.hi, region.indent, within[k]);
67992
68029
  if (L2 < 0) return null;
68030
+ const consumed = flatRun(lines[L2], within, k);
67993
68031
  region = bodyUnder(lines, L2, region.hi, region.indent);
68032
+ k += consumed;
67994
68033
  }
67995
68034
  return region;
67996
68035
  }
@@ -68016,31 +68055,40 @@ function seqItemLines(lines, region, key) {
68016
68055
  }
68017
68056
  return { keyLine, items, end: trimBack(lines, end, region.hi) };
68018
68057
  }
68019
- function upsertMapEntry(text, within, mapKey, entryKey, render) {
68058
+ function upsertMapEntry(text, within, mapPath, entryKey, render) {
68020
68059
  const lines = text.replace(/\n$/, "").split("\n");
68021
- upsertMapEntryAt(lines, reachBody(lines, within), mapKey, entryKey, render);
68060
+ upsertMapEntryAt(lines, reachBody(lines, [...within, ...mapPath]), entryKey, render);
68022
68061
  return lines.join("\n") + "\n";
68023
68062
  }
68024
- function upsertMapEntryAt(lines, region, mapKey, entryKey, render) {
68025
- let mapLine = findKeyLine(lines, region.lo, region.hi, region.indent, mapKey);
68026
- if (mapLine < 0) {
68027
- const at = trimBack(lines, region.lo - 1, region.hi);
68028
- lines.splice(at, 0, `${" ".repeat(region.indent)}${keyToken(mapKey)}:`);
68029
- mapLine = at;
68030
- }
68031
- const mapIndent = region.indent + 2;
68032
- const mapBody = { lo: mapLine + 1, hi: blockEnd(lines, mapLine + 1, lines.length, region.indent + 1), indent: mapIndent };
68033
- const existing = findKeyLine(lines, mapBody.lo, mapBody.hi, mapIndent, entryKey);
68063
+ function upsertMapEntryAt(lines, body, entryKey, render) {
68064
+ const existing = findKeyLine(lines, body.lo, body.hi, body.indent, entryKey);
68034
68065
  if (existing >= 0) {
68035
- const end = blockEnd(lines, existing + 1, mapBody.hi, mapIndent + 1);
68036
- lines.splice(existing, end - existing, ...render(mapIndent));
68066
+ const end = blockEnd(lines, existing + 1, body.hi, body.indent + 1);
68067
+ lines.splice(existing, end - existing, ...render(body.indent));
68037
68068
  } else {
68038
- const at = trimBack(lines, mapLine, mapBody.hi);
68039
- lines.splice(at, 0, ...render(mapIndent));
68069
+ const at = trimBack(lines, body.lo - 1, body.hi);
68070
+ lines.splice(at, 0, ...render(body.indent));
68040
68071
  }
68041
68072
  }
68073
+ function replaceSeqEntryAt(lines, region, key, render) {
68074
+ const keyLine = findKeyLine(lines, region.lo, region.hi, region.indent, key);
68075
+ if (keyLine < 0) {
68076
+ const at = trimBack(lines, region.lo - 1, region.hi);
68077
+ lines.splice(at, 0, ...render(region.indent));
68078
+ return;
68079
+ }
68080
+ let end = keyLine + 1;
68081
+ for (let i2 = keyLine + 1; i2 < region.hi; i2++) {
68082
+ if (!isContentLine(lines[i2])) continue;
68083
+ const ind = indentOf(lines[i2]);
68084
+ if (ind < region.indent) break;
68085
+ if (ind === region.indent && seqMarkLen(lines[i2].trim()) === null) break;
68086
+ end = i2 + 1;
68087
+ }
68088
+ lines.splice(keyLine, trimBack(lines, keyLine, end) - keyLine, ...render(region.indent));
68089
+ }
68042
68090
  function upsertFragment(text, within, slug, render) {
68043
- return upsertMapEntry(text, [...within, YO_KEY], FRAGMENTS_SUBKEY, slug, render);
68091
+ return upsertMapEntry(text, within, [YO_KEY, FRAGMENTS_SUBKEY], slug, render);
68044
68092
  }
68045
68093
  function pruneEmptyKeyAt(lines, region, key) {
68046
68094
  const L2 = findKeyLine(lines, region.lo, region.hi, region.indent, key);
@@ -68103,7 +68151,7 @@ function pruneEmptyYo(text, within) {
68103
68151
  return pruneEmptyKeyAt(lines, region, YO_KEY) ? lines.join("\n") + "\n" : text;
68104
68152
  }
68105
68153
  function upsertThumbnail(text, within, resKey, render) {
68106
- return upsertMapEntry(text, within, THUMBNAILS_KEY, resKey, render);
68154
+ return upsertMapEntry(text, within, [THUMBNAILS_KEY], resKey, render);
68107
68155
  }
68108
68156
  function annotationsRemainAt(lines, region) {
68109
68157
  const seq = seqItemLines(lines, region, ANNOTATIONS_KEY);
@@ -68144,31 +68192,126 @@ function pruneEmptyAnnotations(text, within, pruneHosts) {
68144
68192
  const droppedHosts = pruneHosts && pruneEmptyKeys(lines, within);
68145
68193
  return droppedHusk || droppedHosts ? lines.join("\n") + "\n" : text;
68146
68194
  }
68147
- function removeMapEntry(text, within, mapKey, entryKey) {
68195
+ function findMapping(lines, region, mapPath) {
68196
+ let r2 = region;
68197
+ let line = -1;
68198
+ for (let k = 0; k < mapPath.length; ) {
68199
+ const L2 = findKeyLine(lines, r2.lo, r2.hi, r2.indent, mapPath[k]);
68200
+ if (L2 < 0) return null;
68201
+ const consumed = flatRun(lines[L2], mapPath, k);
68202
+ line = L2;
68203
+ r2 = bodyUnder(lines, L2, r2.hi, r2.indent);
68204
+ k += consumed;
68205
+ }
68206
+ return line < 0 ? null : { line, body: r2 };
68207
+ }
68208
+ function removeMapEntry(text, within, mapPath, entryKey) {
68148
68209
  const lines = text.replace(/\n$/, "").split("\n");
68149
- return removeMapEntryAt(lines, reachBody(lines, within), mapKey, entryKey) ? lines.join("\n") + "\n" : text;
68150
- }
68151
- function removeMapEntryAt(lines, region, mapKey, entryKey) {
68152
- const mapLine = findKeyLine(lines, region.lo, region.hi, region.indent, mapKey);
68153
- if (mapLine < 0) return false;
68154
- const mapIndent = region.indent + 2;
68155
- const mapHi = blockEnd(lines, mapLine + 1, lines.length, region.indent + 1);
68156
- const entry = findKeyLine(lines, mapLine + 1, mapHi, mapIndent, entryKey);
68210
+ const region = findBody(lines, within);
68211
+ if (!region) return text;
68212
+ return removeMapEntryAt(lines, region, mapPath, entryKey) ? lines.join("\n") + "\n" : text;
68213
+ }
68214
+ function removeMapEntryAt(lines, region, mapPath, entryKey) {
68215
+ const map = findMapping(lines, region, mapPath);
68216
+ if (!map) return false;
68217
+ const entry = findKeyLine(lines, map.body.lo, map.body.hi, map.body.indent, entryKey);
68157
68218
  if (entry < 0) return false;
68158
- const end = blockEnd(lines, entry + 1, mapHi, mapIndent + 1);
68219
+ const end = blockEnd(lines, entry + 1, map.body.hi, map.body.indent + 1);
68159
68220
  lines.splice(entry, end - entry);
68160
- const newHi = blockEnd(lines, mapLine + 1, lines.length, region.indent + 1);
68221
+ const mapIndent = indentOf(lines[map.line]);
68222
+ const newHi = blockEnd(lines, map.line + 1, lines.length, mapIndent + 1);
68161
68223
  let hasChild = false;
68162
- for (let i2 = mapLine + 1; i2 < newHi; i2++) {
68163
- if (isContentLine(lines[i2]) && indentOf(lines[i2]) === mapIndent) {
68224
+ for (let i2 = map.line + 1; i2 < newHi; i2++) {
68225
+ if (isContentLine(lines[i2]) && indentOf(lines[i2]) === map.body.indent) {
68164
68226
  hasChild = true;
68165
68227
  break;
68166
68228
  }
68167
68229
  }
68168
- if (!hasChild) lines.splice(mapLine, blockEnd(lines, mapLine + 1, lines.length, region.indent + 1) - mapLine);
68230
+ if (!hasChild) lines.splice(map.line, newHi - map.line);
68169
68231
  return true;
68170
68232
  }
68171
68233
 
68234
+ // src/board-model.ts
68235
+ var supersetOf = (tags, wanted) => wanted.every((t3) => tags.has(t3));
68236
+ function compartmentAt(structure, at) {
68237
+ if (!at) return null;
68238
+ return structure[at.lane]?.[at.comp] ?? null;
68239
+ }
68240
+ function reconcile(structure, chapters) {
68241
+ let changed = false;
68242
+ const out = structure.map(
68243
+ (lane) => lane.map((comp) => {
68244
+ if (comp.tags.length === 0) {
68245
+ const kept2 = comp.items.filter((it) => !chapters.has(it.path));
68246
+ if (kept2.length === comp.items.length) return comp;
68247
+ changed = true;
68248
+ return { tags: comp.tags, items: kept2 };
68249
+ }
68250
+ const kept = [];
68251
+ const seen = /* @__PURE__ */ new Set();
68252
+ let compChanged = false;
68253
+ for (const item of comp.items) {
68254
+ if (seen.has(item.path)) {
68255
+ compChanged = true;
68256
+ continue;
68257
+ }
68258
+ const tags = chapters.get(item.path);
68259
+ if (tags && !supersetOf(tags, comp.tags)) {
68260
+ compChanged = true;
68261
+ continue;
68262
+ }
68263
+ kept.push(item);
68264
+ seen.add(item.path);
68265
+ }
68266
+ for (const [p2, tags] of chapters) {
68267
+ if (seen.has(p2) || !supersetOf(tags, comp.tags)) continue;
68268
+ kept.push({ path: p2 });
68269
+ seen.add(p2);
68270
+ compChanged = true;
68271
+ }
68272
+ if (!compChanged) return comp;
68273
+ changed = true;
68274
+ return { tags: comp.tags, items: kept };
68275
+ })
68276
+ );
68277
+ return changed ? { structure: out, changed } : { structure, changed };
68278
+ }
68279
+ function moveDeltas(chapterTags, from, to2) {
68280
+ const toTags = new Set(to2?.tags ?? []);
68281
+ const untag = (from?.tags ?? []).filter((t3) => chapterTags.has(t3) && !toTags.has(t3));
68282
+ const tag = (to2?.tags ?? []).filter((t3) => !chapterTags.has(t3));
68283
+ return { untag, tag };
68284
+ }
68285
+ function applyMove(structure, task, from, to2) {
68286
+ let moved = { path: task };
68287
+ const out = structure.map(
68288
+ (lane, li) => lane.map((comp, ci) => {
68289
+ let items = comp.items;
68290
+ if (from && from.lane === li && from.comp === ci) {
68291
+ const i2 = items.findIndex((it) => it.path === task);
68292
+ if (i2 >= 0) {
68293
+ moved = items[i2];
68294
+ items = [...items.slice(0, i2), ...items.slice(i2 + 1)];
68295
+ }
68296
+ }
68297
+ return items === comp.items ? comp : { tags: comp.tags, items };
68298
+ })
68299
+ );
68300
+ if (to2) {
68301
+ const dest = out[to2.lane]?.[to2.comp];
68302
+ if (dest && !dest.items.some((it) => it.path === task)) {
68303
+ out[to2.lane] = out[to2.lane].map((c3, ci) => ci === to2.comp ? { tags: c3.tags, items: [...c3.items, moved] } : c3);
68304
+ }
68305
+ }
68306
+ return out;
68307
+ }
68308
+ function seedFromWorkflow(statePaths) {
68309
+ return statePaths.map((p2) => [{ tags: [p2], items: [] }]);
68310
+ }
68311
+ function seedFromOldLanes(lanes) {
68312
+ return lanes.map((laneTags) => laneTags.length === 0 ? [{ tags: [], items: [] }] : laneTags.map((t3) => ({ tags: [t3], items: [] })));
68313
+ }
68314
+
68172
68315
  // src/concrete.ts
68173
68316
  var OVERLAY_DIR = ".yo";
68174
68317
  var BODY_FILE2 = "body.yo";
@@ -69355,7 +69498,7 @@ function createHandlers(dataRoot, opts = {}) {
69355
69498
  }
69356
69499
  };
69357
69500
  let reconcilePending = null;
69358
- const reconcile = () => {
69501
+ const reconcile2 = () => {
69359
69502
  if (reconcilePending) return reconcilePending;
69360
69503
  const p2 = enqueue(async () => {
69361
69504
  reconcilePending = null;
@@ -69400,7 +69543,7 @@ function createHandlers(dataRoot, opts = {}) {
69400
69543
  return;
69401
69544
  }
69402
69545
  log(`watch: ${external.length} change(s) \u2014 ${external.slice(0, 5).join(", ")}${external.length > 5 ? ", \u2026" : ""}`);
69403
- reconcile().catch(() => {
69546
+ reconcile2().catch(() => {
69404
69547
  });
69405
69548
  }, { ignore: ignore2 }) : null;
69406
69549
  const handler = (req, res, url) => {
@@ -69424,14 +69567,14 @@ function createHandlers(dataRoot, opts = {}) {
69424
69567
  return;
69425
69568
  }
69426
69569
  if (req.method === "POST" && url.pathname === "/api/reindex") {
69427
- reconcile().then((diff2) => sendJson(res, 200, diff2)).catch((e3) => sendJson(res, 500, { error: String(e3.message || e3) }));
69570
+ reconcile2().then((diff2) => sendJson(res, 200, diff2)).catch((e3) => sendJson(res, 500, { error: String(e3.message || e3) }));
69428
69571
  return;
69429
69572
  }
69430
69573
  if (url.pathname === "/api/doctor") {
69431
69574
  try {
69432
69575
  const v = validateTree(scanTree(dataRoot, ignore2));
69433
69576
  const doc = cachedDoc ?? walkTree(dataRoot, walkOpts).doc;
69434
- sendJson(res, 200, { allowed: v.allowed, diagnostics: [...v.diagnostics, ...deadLinkDiagnostics(doc, s2, dataRoot)] });
69577
+ sendJson(res, 200, { allowed: v.allowed, diagnostics: [...v.diagnostics, ...deadLinkDiagnostics(doc, s2, dataRoot), ...doubledFragmentDiagnostics(doc)] });
69435
69578
  } catch (e3) {
69436
69579
  sendJson(res, 500, { error: String(e3.message || e3) });
69437
69580
  }
@@ -69585,14 +69728,48 @@ function createHandlers(dataRoot, opts = {}) {
69585
69728
  readBody(req).then(
69586
69729
  (data) => enqueue(async () => {
69587
69730
  const b = data;
69588
- const lanes = Array.isArray(b?.lanes) ? b.lanes.map((lane) => Array.isArray(lane) ? lane.map((p3) => String(p3)) : []) : [];
69589
- const { bodyFile } = hostFor(dataRoot, s2, strToSegs(b?.path || ":"));
69590
- mkdirInside(dataRoot, path11.dirname(bodyFile), { recursive: true });
69591
- const src = fs10.existsSync(bodyFile) ? fs10.readFileSync(bodyFile, "utf8") : "";
69592
- writeBody(dataRoot, s2, bodyFile, writeBoardLanes(src, lanes));
69593
- broadcast(await doReindex());
69594
- scheduleHasher();
69595
- return { ok: true };
69731
+ const boardSegs = strToSegs(b?.path || ":");
69732
+ const boardStore = storePath(boardSegs);
69733
+ if (!s2.node(boardStore)) throw new Error(`no board at ${b?.path || ":"}`);
69734
+ const op = String(b?.op ?? "");
69735
+ const read2 = readBoardStructure(s2, boardStore);
69736
+ const structure = read2 ?? seedBoardStructure(s2, boardStore);
69737
+ const writeStructure = async (st) => {
69738
+ const { bodyFile } = hostFor(dataRoot, s2, boardSegs);
69739
+ mkdirInside(dataRoot, path11.dirname(bodyFile), { recursive: true });
69740
+ const src = fs10.existsSync(bodyFile) ? fs10.readFileSync(bodyFile, "utf8") : "";
69741
+ writeBody(dataRoot, s2, bodyFile, writeBoardStructure(src, st));
69742
+ broadcast(await doReindexFile(bodyFile));
69743
+ };
69744
+ if (op === "structure") {
69745
+ const st = sanitizeBoardStructure(b.structure);
69746
+ const rec = reconcile(st, memberTagSets(s2, boardMembers(dataRoot, s2, boardStore)));
69747
+ await writeStructure(rec.structure);
69748
+ scheduleHasher();
69749
+ return { seeded: false, ...resolveBoard(dataRoot, s2, boardMembers(dataRoot, s2, boardStore), rec.structure) };
69750
+ }
69751
+ if (op === "move") {
69752
+ const task = String(b.task ?? "");
69753
+ const taskStore = storePath(strToSegs(task));
69754
+ if (!task || !s2.node(taskStore)) throw new Error("move needs a `task` naming an existing node");
69755
+ const from = boardCoord(b.from);
69756
+ const to2 = boardCoord(b.to);
69757
+ const deltas = moveDeltas(memberTagSets(s2, [task]).get(task), compartmentAt(structure, from), compartmentAt(structure, to2));
69758
+ for (const t3 of deltas.untag) broadcast(await doReindexFile(unembedAnnotation(dataRoot, s2, task, t3)));
69759
+ for (const t3 of deltas.tag) broadcast(await doReindexFile(embedAnnotation(dataRoot, s2, settings.sidecars, { target: task, tag: t3 })));
69760
+ const moved = applyMove(structure, task, from, to2);
69761
+ const rec = reconcile(moved, memberTagSets(s2, boardMembers(dataRoot, s2, boardStore)));
69762
+ await writeStructure(rec.structure);
69763
+ scheduleHasher();
69764
+ return { seeded: false, ...resolveBoard(dataRoot, s2, boardMembers(dataRoot, s2, boardStore), rec.structure) };
69765
+ }
69766
+ if (op === "reconcile") {
69767
+ const rec = reconcile(structure, memberTagSets(s2, boardMembers(dataRoot, s2, boardStore)));
69768
+ if (read2 !== null && rec.changed) await writeStructure(rec.structure);
69769
+ scheduleHasher();
69770
+ return { seeded: read2 === null, ...resolveBoard(dataRoot, s2, boardMembers(dataRoot, s2, boardStore), rec.structure) };
69771
+ }
69772
+ throw new Error(`unknown board op: ${op || "(none)"}`);
69596
69773
  })
69597
69774
  ).then((body) => sendJson(res, 201, body)).catch((e3) => sendJson(res, 400, { error: String(e3.message || e3) }));
69598
69775
  return;
@@ -69660,7 +69837,7 @@ function createHandlers(dataRoot, opts = {}) {
69660
69837
  }
69661
69838
  if (url.pathname === "/api/content" || url.pathname.startsWith("/api/content/")) {
69662
69839
  (async () => {
69663
- if (!cachedDoc) await reconcile();
69840
+ if (!cachedDoc) await reconcile2();
69664
69841
  const doc = cachedDoc;
69665
69842
  if (!doc) throw new Error("the merged document is unavailable (index rebuild failed)");
69666
69843
  const rest = url.pathname === "/api/content" ? "" : url.pathname.slice("/api/content/".length);
@@ -69809,6 +69986,15 @@ function createHandlers(dataRoot, opts = {}) {
69809
69986
  sendJson(res, 200, taggedMaterials(dataRoot, s2, p2));
69810
69987
  return;
69811
69988
  }
69989
+ if (url.pathname === "/api/board") {
69990
+ const row2 = s2.node(p2);
69991
+ if (!row2) return notFound(res, url);
69992
+ const read2 = readBoardStructure(s2, p2);
69993
+ const members = boardMembers(dataRoot, s2, p2);
69994
+ const { structure } = reconcile(read2 ?? seedBoardStructure(s2, p2), memberTagSets(s2, members));
69995
+ sendJson(res, 200, { seeded: read2 === null, ...resolveBoard(dataRoot, s2, members, structure) });
69996
+ return;
69997
+ }
69812
69998
  if (url.pathname === "/api/tree") {
69813
69999
  const row2 = s2.node(p2);
69814
70000
  if (!row2) return notFound(res, url);
@@ -69841,15 +70027,18 @@ function createHandlers(dataRoot, opts = {}) {
69841
70027
  }
69842
70028
  return sendJson(res, 415, { error: `no thumbnail for format: ${fmt ?? "unknown"}` });
69843
70029
  }
69844
- const serve = (file) => {
70030
+ const serve = (file, mime2 = "image/jpeg") => {
69845
70031
  res.statusCode = 200;
69846
- res.setHeader("Content-Type", "image/jpeg");
70032
+ res.setHeader("Content-Type", mime2);
69847
70033
  res.setHeader("Content-Length", String(fs10.statSync(file).size));
69848
70034
  res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
69849
70035
  const stream = fs10.createReadStream(file);
69850
70036
  stream.on("error", () => res.destroy());
69851
70037
  stream.pipe(res);
69852
70038
  };
70039
+ if (segs.some((g) => String(g) === OVERLAY_DIR)) {
70040
+ return serve(sourceAbs, sourceAbs.endsWith(".png") ? "image/png" : "image/jpeg");
70041
+ }
69853
70042
  const ready2 = existingThumb(dataRoot, s2, settings.sidecars, segs, sourceRow, w, h);
69854
70043
  if (ready2) return serve(ready2);
69855
70044
  if (readOnly) return sendJson(res, 415, { error: `thumbnail not pre-generated (read-only)` });
@@ -70272,21 +70461,131 @@ function pointerRaw(clientPath) {
70272
70461
  }
70273
70462
  return "::" + out;
70274
70463
  }
70275
- function writeBoardLanes(src, lanes) {
70276
- const laneLine = (lane) => `- [${lane.map((p2) => pointerToken(pointerRaw(p2))).join(", ")}]`;
70277
- const block = lanes.length === 0 ? ["lanes: []"] : ["lanes:", ...lanes.map(laneLine)];
70464
+ var LANES_SUBKEY = "lanes";
70465
+ var BOARD_CONFIG_KEYS = /* @__PURE__ */ new Set(["workflow", "lanes", "yamlover", "ontos", YO_KEY2]);
70466
+ var TAGISH_FORMATS = /* @__PURE__ */ new Set([ONTO_FORMAT, "x-yamlover-workflow", "x-yamlover-board"]);
70467
+ function writeBoardStructure(src, structure) {
70278
70468
  let lines = src.replace(/\n+$/, "").split("\n");
70279
70469
  if (src.trim() === "") lines = ["!!<*yamlover:$defs:board>"];
70280
- const start = lines.findIndex((l2) => /^lanes:/.test(l2));
70281
- if (start >= 0) {
70282
- let end = start + 1;
70283
- while (end < lines.length && (lines[end] === "" || /^[ \t-]/.test(lines[end]))) end++;
70284
- lines.splice(start, end - start, ...block);
70285
- } else {
70286
- lines.push(...block);
70287
- }
70470
+ const render = (indent) => {
70471
+ const pad = " ".repeat(indent);
70472
+ if (structure.length === 0) return [`${pad}lanes: []`];
70473
+ const out = [`${pad}lanes:`];
70474
+ const cpad = " ".repeat(indent + 2);
70475
+ const bpad = " ".repeat(indent + 4);
70476
+ for (const lane of structure) {
70477
+ const comps = lane.length ? lane : [{ tags: [], items: [] }];
70478
+ comps.forEach((comp, ci) => {
70479
+ out.push(ci === 0 ? `${pad}- -` : `${cpad}-`);
70480
+ for (const t3 of comp.tags) out.push(`${bpad}&${pointerRaw(t3)}:-`);
70481
+ for (const it of comp.items)
70482
+ out.push(it.key !== void 0 ? `${bpad}${keyToken(it.key)}: *${pointerRaw(it.path)}` : `${bpad}- *${pointerRaw(it.path)}`);
70483
+ });
70484
+ }
70485
+ return out;
70486
+ };
70487
+ replaceSeqEntryAt(lines, reachBody(lines, [YO_KEY2]), LANES_SUBKEY, render);
70288
70488
  return lines.join("\n") + "\n";
70289
70489
  }
70490
+ function membershipPathsOf(s2, store) {
70491
+ return s2.relationships(store).out.filter((e3) => e3.kind === "back" && e3.label == null).map((e3) => segsToStr(storePathToSegs(e3.to)));
70492
+ }
70493
+ function readBoardStructure(s2, boardStore) {
70494
+ const lanesStore = childPath(childPath(boardStore, YO_KEY2), LANES_SUBKEY);
70495
+ if (!s2.node(lanesStore)) return null;
70496
+ return s2.children(lanesStore).map(
70497
+ (lane) => s2.children(lane.to).map((comp) => ({
70498
+ tags: membershipPathsOf(s2, comp.to),
70499
+ items: s2.entries(comp.to).filter((e3) => e3.kind === "ref").map((e3) => ({ path: segsToStr(storePathToSegs(e3.to)), ...e3.label != null ? { key: e3.label } : {} }))
70500
+ }))
70501
+ );
70502
+ }
70503
+ function seedBoardStructure(s2, boardStore) {
70504
+ const oldLanes = childPath(boardStore, LANES_SUBKEY);
70505
+ if (s2.node(oldLanes)) {
70506
+ return seedFromOldLanes(
70507
+ s2.children(oldLanes).map(
70508
+ (lane) => s2.entries(lane.to).filter((e3) => e3.kind === "ref" && e3.label == null).map((e3) => segsToStr(storePathToSegs(e3.to)))
70509
+ )
70510
+ );
70511
+ }
70512
+ const wf = s2.relationships(boardStore).out.find((e3) => e3.kind === "ref" && e3.label === "workflow");
70513
+ if (wf) {
70514
+ const children2 = s2.children(wf.to).filter((c3) => c3.label && c3.label !== "initial" && c3.label !== "color");
70515
+ const ontos = children2.filter((c3) => s2.node(c3.to)?.format === ONTO_FORMAT);
70516
+ return seedFromWorkflow((ontos.length ? ontos : children2).map((c3) => segsToStr(storePathToSegs(c3.to))));
70517
+ }
70518
+ return [];
70519
+ }
70520
+ function boardMembers(dataRoot, s2, boardStore) {
70521
+ const out = [];
70522
+ for (const c3 of s2.children(boardStore)) {
70523
+ if (!c3.label || BOARD_CONFIG_KEYS.has(c3.label) || isHidden(s2, c3.to)) continue;
70524
+ const n2 = s2.node(c3.to);
70525
+ if (!n2 || n2.format && TAGISH_FORMATS.has(n2.format)) continue;
70526
+ const segs = storePathToSegs(c3.to);
70527
+ if (n2.type === "scalar" && s2.children(c3.to).length === 0 && !fs10.existsSync(path11.resolve(dataRoot, ...segs.map(String)))) continue;
70528
+ out.push(segsToStr(segs));
70529
+ }
70530
+ return out;
70531
+ }
70532
+ function memberTagSets(s2, members) {
70533
+ return new Map(members.map((p2) => [p2, new Set(membershipPathsOf(s2, storePath(strToSegs(p2))))]));
70534
+ }
70535
+ function boardCardStub(dataRoot, s2, p2) {
70536
+ const segs = strToSegs(p2);
70537
+ const store = storePath(segs);
70538
+ const row = s2.node(store);
70539
+ const field = (k) => {
70540
+ const v = s2.node(childPath(store, k))?.value;
70541
+ return v == null ? null : String(v);
70542
+ };
70543
+ return {
70544
+ path: p2,
70545
+ title: (row ? displayNameOf(s2, store) : null) ?? String(segs[segs.length - 1] ?? p2),
70546
+ type: row ? tocType(s2, store, row) : null,
70547
+ format: row?.format ?? null,
70548
+ concrete: row ? concreteOf(s2, dataRoot, segs, row) : null,
70549
+ priority: field("priority"),
70550
+ assignee: field("assignee"),
70551
+ due: field("due"),
70552
+ // resolved refs, not bare paths — the card FACE shows its tags (names + colors)
70553
+ tags: membershipPathsOf(s2, store).map((t3) => projectTag(s2, storePath(strToSegs(t3)))).filter(Boolean)
70554
+ };
70555
+ }
70556
+ function resolveBoard(dataRoot, s2, members, structure) {
70557
+ const inComp = /* @__PURE__ */ new Set();
70558
+ const lanes = structure.map(
70559
+ (lane) => lane.map((comp) => ({
70560
+ tags: comp.tags.map((t3) => projectTag(s2, storePath(strToSegs(t3)))).filter(Boolean),
70561
+ items: comp.items.map((it) => {
70562
+ inComp.add(it.path);
70563
+ return { ...boardCardStub(dataRoot, s2, it.path), ...it.key !== void 0 ? { key: it.key } : {} };
70564
+ })
70565
+ }))
70566
+ );
70567
+ const backlog = members.filter((p2) => !inComp.has(p2)).map((p2) => boardCardStub(dataRoot, s2, p2));
70568
+ return { lanes, backlog };
70569
+ }
70570
+ function sanitizeBoardStructure(raw) {
70571
+ if (!Array.isArray(raw)) throw new Error("board structure must be an array of lanes");
70572
+ return raw.map(
70573
+ (lane) => (Array.isArray(lane) ? lane : []).map((comp) => {
70574
+ const c3 = comp;
70575
+ return {
70576
+ tags: Array.isArray(c3?.tags) ? c3.tags.map(String) : [],
70577
+ items: Array.isArray(c3?.items) ? c3.items.map((it) => {
70578
+ const o2 = it;
70579
+ return { path: String(o2?.path ?? ""), ...o2?.key != null ? { key: String(o2.key) } : {} };
70580
+ }).filter((it) => it.path !== "") : []
70581
+ };
70582
+ })
70583
+ );
70584
+ }
70585
+ function boardCoord(v) {
70586
+ const o2 = v;
70587
+ return o2 != null && typeof o2.lane === "number" && typeof o2.comp === "number" ? { lane: o2.lane, comp: o2.comp } : null;
70588
+ }
70290
70589
  function overlaidDir(bodyFile) {
70291
70590
  if (bodyFile.endsWith(path11.join(OVERLAY_DIR, BODY_FILE2))) return path11.dirname(path11.dirname(bodyFile));
70292
70591
  return path11.basename(bodyFile) === INDEX_FILE2 ? path11.dirname(bodyFile) : null;
@@ -70436,15 +70735,34 @@ function embedAnnotation(dataRoot, s2, mode, a2) {
70436
70735
  writeBody(dataRoot, s2, bodyFile2, lines.join("\n") + "\n");
70437
70736
  return bodyFile2;
70438
70737
  }
70738
+ const own = ownDocFile(dataRoot, segs);
70739
+ if (own) {
70740
+ writeBody(dataRoot, s2, own, appendBookmark(fs10.readFileSync(own, "utf8"), [], tokens));
70741
+ return own;
70742
+ }
70439
70743
  const { bodyFile, within } = hostFor(dataRoot, s2, segs);
70440
70744
  mkdirInside(dataRoot, path11.dirname(bodyFile), { recursive: true });
70441
70745
  const src = fs10.existsSync(bodyFile) ? fs10.readFileSync(bodyFile, "utf8") : "";
70442
70746
  writeBody(dataRoot, s2, bodyFile, appendBookmark(src, within, tokens));
70443
70747
  return bodyFile;
70444
70748
  }
70749
+ function ownDocFile(dataRoot, segs) {
70750
+ if (segs.length === 0 || segs.some((s2) => typeof s2 === "number")) return null;
70751
+ const abs = path11.resolve(dataRoot, ...segs.map(String));
70752
+ if (!/\.(yo|yamlover)$/i.test(abs)) return null;
70753
+ try {
70754
+ return fs10.statSync(abs).isFile() ? abs : null;
70755
+ } catch {
70756
+ return null;
70757
+ }
70758
+ }
70445
70759
  function embedFragment(dataRoot, s2, mode, f2) {
70446
70760
  const segs = strToSegs(f2.target || ":");
70447
- const slug = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
70761
+ const chosen = typeof f2.slug === "string" ? f2.slug.trim() : "";
70762
+ if (chosen !== "" && s2.node(storePath([...segs, YO_KEY2, FRAGS_SUBKEY, chosen]))) {
70763
+ throw new Error(`a fragment \`${chosen}\` already exists on this node`);
70764
+ }
70765
+ const slug = chosen !== "" ? chosen : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
70448
70766
  if (isChunkTarget(s2, segs)) {
70449
70767
  const { docSegs, bodyFile: bodyFile2 } = chapterSource(dataRoot, s2, segs);
70450
70768
  const { indices, keys } = splitChunkWithin(segs.slice(docSegs.length));
@@ -70457,7 +70775,7 @@ function embedFragment(dataRoot, s2, mode, f2) {
70457
70775
  /*ensureOmni*/
70458
70776
  true
70459
70777
  );
70460
- upsertMapEntryAt(lines, reachBodyAt(lines, region, [YO_KEY2]), FRAGS_SUBKEY, slug, (indent) => fragmentBlockLines(slug, f2.selector, null, indent));
70778
+ upsertMapEntryAt(lines, reachBodyAt(lines, region, [YO_KEY2, FRAGS_SUBKEY]), slug, (indent) => fragmentBlockLines(slug, f2.selector, null, indent));
70461
70779
  writeBody(dataRoot, s2, bodyFile2, lines.join("\n") + "\n");
70462
70780
  return { slug, fragmentPath: segsToStr([...segs, YO_KEY2, FRAGS_SUBKEY, slug]) };
70463
70781
  }
@@ -70542,7 +70860,7 @@ function unembedAnnotation(dataRoot, s2, target, tag, sel) {
70542
70860
  ), keys);
70543
70861
  removeBookmarkAt(lines, fragRegion, pred);
70544
70862
  if (isFragmentSegs(keys) && !bookmarksRemainAt(lines, fragRegion())) {
70545
- removeMapEntryAt(lines, reachBodyAt(lines, chunkFieldRegion(lines, indices, false), keys.slice(0, -2)), FRAGS_SUBKEY, keys[keys.length - 1]);
70863
+ removeMapEntryAt(lines, chunkFieldRegion(lines, indices, false), keys.slice(0, -1), keys[keys.length - 1]);
70546
70864
  pruneEmptyKeyAt(lines, chunkFieldRegion(lines, indices, false), YO_KEY2);
70547
70865
  collapseChunkOmni(lines, indices);
70548
70866
  } else if (keys.length === 0 && !bookmarksRemainAt(lines, fragRegion())) {
@@ -70551,12 +70869,21 @@ function unembedAnnotation(dataRoot, s2, target, tag, sel) {
70551
70869
  writeBody(dataRoot, s2, bodyFile2, lines.join("\n") + "\n");
70552
70870
  return bodyFile2;
70553
70871
  }
70872
+ const own = ownDocFile(dataRoot, segs);
70873
+ if (own) {
70874
+ const before = fs10.readFileSync(own, "utf8");
70875
+ const after = removeBookmark(before, [], pred);
70876
+ if (after !== before) {
70877
+ writeBody(dataRoot, s2, own, after);
70878
+ return own;
70879
+ }
70880
+ }
70554
70881
  const { bodyFile, within } = hostFor(dataRoot, s2, segs);
70555
70882
  if (!fs10.existsSync(bodyFile)) return bodyFile;
70556
70883
  let src = removeBookmark(fs10.readFileSync(bodyFile, "utf8"), within, pred);
70557
70884
  const overlay2 = overlaidDir(bodyFile) !== null;
70558
70885
  if (isFragmentSegs(within) && !bookmarksRemain(src, within)) {
70559
- src = removeMapEntry(src, within.slice(0, -2), FRAGS_SUBKEY, within[within.length - 1]);
70886
+ src = removeMapEntry(src, within.slice(0, -3), [YO_KEY2, FRAGS_SUBKEY], within[within.length - 1]);
70560
70887
  src = pruneEmptyYo(src, within.slice(0, -3));
70561
70888
  src = pruneEmptyAnnotations(src, within.slice(0, -3), overlay2);
70562
70889
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yamlover",
3
- "version": "0.3.50",
3
+ "version": "0.3.52",
4
4
  "description": "A local-first, editable knowledge graph for humans and LLMs: npx yamlover <root> serves a browser UI over a plain-text directory, with an editor, live queries, pointers, and an LLM-agent API.",
5
5
  "type": "module",
6
6
  "license": "MIT",