@bitcobblers/wod-wiki-wql 0.35.2115 → 0.35.2117

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/index.cjs CHANGED
@@ -2592,8 +2592,8 @@ function normalizeSummaryFacts(logs, identity) {
2592
2592
  origin: identity.origin,
2593
2593
  pageId: identity.pageId,
2594
2594
  grain: "summary",
2595
- segmentId: identity.segmentId ?? "",
2596
- segmentVersion: identity.segmentVersion ?? 0,
2595
+ blockId: identity.blockId ?? "",
2596
+ blockVersion: identity.blockVersion ?? 0,
2597
2597
  resultId: identity.resultId,
2598
2598
  type: f.metricKey,
2599
2599
  value: f.value,
@@ -2645,8 +2645,8 @@ function toEventRows(logs, identity) {
2645
2645
  text: output.text,
2646
2646
  dialect: output.dialect,
2647
2647
  completionReason: output.completionReason,
2648
- segmentId: identity.segmentId,
2649
- segmentVersion: identity.segmentVersion
2648
+ blockId: identity.blockId,
2649
+ blockVersion: identity.blockVersion
2650
2650
  }));
2651
2651
  }
2652
2652
  function toSummaryEventRows(logs, identity) {
@@ -2693,8 +2693,8 @@ function toSummaryEventRows(logs, identity) {
2693
2693
  ...f.grade ? { grade: f.grade } : {}
2694
2694
  }
2695
2695
  }],
2696
- segmentId: identity.segmentId,
2697
- segmentVersion: identity.segmentVersion
2696
+ blockId: identity.blockId,
2697
+ blockVersion: identity.blockVersion
2698
2698
  }));
2699
2699
  }
2700
2700
  function projectEventToFacts(record) {
@@ -2713,8 +2713,8 @@ function projectEventToFacts(record) {
2713
2713
  origin: record.origin,
2714
2714
  pageId: record.pageId,
2715
2715
  grain: "summary",
2716
- segmentId: record.segmentId ?? "",
2717
- segmentVersion: record.segmentVersion ?? 0,
2716
+ blockId: record.blockId ?? "",
2717
+ blockVersion: record.blockVersion ?? 0,
2718
2718
  resultId: record.resultId,
2719
2719
  type: metricKey,
2720
2720
  value: m.value,
@@ -2772,8 +2772,8 @@ function projectEventToFacts(record) {
2772
2772
  origin: record.origin,
2773
2773
  pageId: record.pageId,
2774
2774
  grain: "event",
2775
- segmentId: record.segmentId ?? "",
2776
- segmentVersion: record.segmentVersion ?? 0,
2775
+ blockId: record.blockId ?? "",
2776
+ blockVersion: record.blockVersion ?? 0,
2777
2777
  resultId: record.resultId,
2778
2778
  type: metricKey,
2779
2779
  value: m.value,
@@ -2815,117 +2815,6 @@ function eventsToStoredLogs(events) {
2815
2815
  }));
2816
2816
  }
2817
2817
 
2818
- // src/dashboard/frontmatter.ts
2819
- var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
2820
- function unquote(value) {
2821
- return value.replace(
2822
- /^(['"])(.*)\1$/,
2823
- (_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
2824
- );
2825
- }
2826
- function parseFrontmatter(raw) {
2827
- const match = raw.match(FRONTMATTER_RE);
2828
- if (!match) return { meta: {}, body: raw };
2829
- return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
2830
- }
2831
- function parseMetaLines(lines) {
2832
- const meta = {};
2833
- for (let i = 0; i < lines.length; i++) {
2834
- const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
2835
- if (!keyMatch) continue;
2836
- const key = keyMatch[1];
2837
- const rawVal = keyMatch[2].trim();
2838
- if (rawVal === "") {
2839
- const items = [];
2840
- while (i + 1 < lines.length) {
2841
- const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
2842
- if (!itemMatch) break;
2843
- items.push(itemMatch[1].trim());
2844
- i++;
2845
- }
2846
- meta[key] = items.length > 0 ? items : "";
2847
- } else {
2848
- const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
2849
- const value = unquote(rawVal);
2850
- const num = Number(value);
2851
- meta[key] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
2852
- }
2853
- }
2854
- return meta;
2855
- }
2856
- function parseFrontmatterBody(innerContent) {
2857
- return parseMetaLines(innerContent.split(/\r?\n/));
2858
- }
2859
- function quoteYamlScalar(value) {
2860
- if (value === "") return '""';
2861
- const looksNumeric = !isNaN(Number(value));
2862
- const looksKeyword = /^(true|false|null|yes|no|on|off)$/i.test(value);
2863
- if (/[":'\n#{}[\\],&*?|<>=%!@`]/.test(value) || value !== value.trim() || value.startsWith("-") || looksNumeric || looksKeyword) {
2864
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
2865
- }
2866
- return value;
2867
- }
2868
- function serializeFrontmatter(meta) {
2869
- const lines = [];
2870
- for (const [key, value] of Object.entries(meta)) {
2871
- if (Array.isArray(value)) {
2872
- if (value.length === 0) {
2873
- lines.push(`${key}: ""`);
2874
- continue;
2875
- }
2876
- lines.push(`${key}:`);
2877
- for (const item of value) {
2878
- lines.push(` - ${quoteYamlScalar(item)}`);
2879
- }
2880
- } else if (typeof value === "number") {
2881
- lines.push(`${key}: ${value}`);
2882
- } else {
2883
- lines.push(`${key}: ${quoteYamlScalar(value)}`);
2884
- }
2885
- }
2886
- return lines.join("\n");
2887
- }
2888
- function stripFrontmatter(raw) {
2889
- return parseFrontmatter(raw).body;
2890
- }
2891
- function getScalar(meta, key) {
2892
- const value = meta[key];
2893
- return typeof value === "string" || typeof value === "number" ? value : void 0;
2894
- }
2895
- function getList(meta, key) {
2896
- const value = meta[key];
2897
- return Array.isArray(value) ? value : [];
2898
- }
2899
- function parseFrontmatterCategories(raw) {
2900
- return getList(parseFrontmatter(raw).meta, "category").map((c) => c.toLowerCase());
2901
- }
2902
- function extractFrontmatterTags(raw) {
2903
- const meta = FRONTMATTER_RE.test(raw) ? parseFrontmatter(raw).meta : parseFrontmatterBody(raw);
2904
- const tags = meta["tags"];
2905
- const list = Array.isArray(tags) ? tags : typeof tags === "string" && tags.trim() ? tags.trim().startsWith("[") && tags.trim().endsWith("]") ? tags.trim().slice(1, -1).split(",") : [tags] : [];
2906
- return [...new Set(list.map((tag) => tag.trim()).filter(Boolean))];
2907
- }
2908
- function parseFlatProperties(innerContent) {
2909
- const props = {};
2910
- for (const line of innerContent.split(/\r?\n/)) {
2911
- const match = line.match(/^([^:]+):\s*(.*)$/);
2912
- if (match) {
2913
- props[match[1].trim()] = match[2].trim();
2914
- }
2915
- }
2916
- return props;
2917
- }
2918
- function parseFrontmatterProps(lines) {
2919
- const props = {};
2920
- for (const line of lines) {
2921
- const match = line.match(/^([^:]+):\s*(.*)$/);
2922
- if (match) {
2923
- props[match[1].trim()] = match[2].trim();
2924
- }
2925
- }
2926
- return props;
2927
- }
2928
-
2929
2818
  // src/selection.ts
2930
2819
  function partitionKey(coverage, effortSlug) {
2931
2820
  const tags = coverage?.groupTags ? Object.entries(coverage.groupTags).sort(([a], [b]) => a < b ? -1 : 1).map(([k, v]) => `${k}=${v}`).join(",") : "";
@@ -3399,7 +3288,6 @@ var QueryService = class {
3399
3288
  noteStore;
3400
3289
  blockStore;
3401
3290
  effortStore;
3402
- staticNoteStore;
3403
3291
  pageStore;
3404
3292
  relationStore;
3405
3293
  datasetStore;
@@ -3414,14 +3302,13 @@ var QueryService = class {
3414
3302
  noteTagsStore = {
3415
3303
  getAllFromIndex: async () => []
3416
3304
  };
3417
- constructor(storesOrEventStore, noteStore, blockStore, effortStore, staticNoteStore) {
3418
- if (storesOrEventStore && typeof storesOrEventStore === "object" && ("eventStore" in storesOrEventStore || "noteStore" in storesOrEventStore || "blockStore" in storesOrEventStore || "effortStore" in storesOrEventStore || "staticNoteStore" in storesOrEventStore || "pageStore" in storesOrEventStore || "relationStore" in storesOrEventStore || "datasetStore" in storesOrEventStore)) {
3305
+ constructor(storesOrEventStore, noteStore, blockStore, effortStore) {
3306
+ if (storesOrEventStore && typeof storesOrEventStore === "object" && ("eventStore" in storesOrEventStore || "noteStore" in storesOrEventStore || "blockStore" in storesOrEventStore || "effortStore" in storesOrEventStore || "pageStore" in storesOrEventStore || "relationStore" in storesOrEventStore || "datasetStore" in storesOrEventStore)) {
3419
3307
  const stores = storesOrEventStore;
3420
3308
  this.store = stores.eventStore ?? defaultEventStore;
3421
3309
  this.noteStore = stores.noteStore ?? defaultNoteStore;
3422
3310
  this.blockStore = stores.blockStore ?? defaultBlockStore;
3423
3311
  this.effortStore = stores.effortStore ?? defaultEffortStore;
3424
- this.staticNoteStore = stores.staticNoteStore;
3425
3312
  this.pageStore = stores.pageStore;
3426
3313
  this.relationStore = stores.relationStore;
3427
3314
  this.datasetStore = stores.datasetStore;
@@ -3433,7 +3320,6 @@ var QueryService = class {
3433
3320
  this.noteStore = noteStore ?? defaultNoteStore;
3434
3321
  this.blockStore = blockStore ?? defaultBlockStore;
3435
3322
  this.effortStore = effortStore ?? defaultEffortStore;
3436
- this.staticNoteStore = staticNoteStore;
3437
3323
  }
3438
3324
  }
3439
3325
  /** Windowed flat-fact fetch — the projected view over the event store. */
@@ -3753,14 +3639,11 @@ var QueryService = class {
3753
3639
  }
3754
3640
  const isCatalogHead = parsed.target === "note" && parsed.filters.some((f) => f.key === "type" && !f.negate && f.values.some((v) => v.value === "feed")) && (parsed.filters.some((f) => f.key === "source" && !f.negate && f.values.some((v) => v.value === "feed" || v.value === "feeds")) || parsed.sourceScope?.includes("feeds"));
3755
3641
  if (isCatalogHead) {
3756
- const notes2 = (await this.noteStore.getAllNotes()).concat(
3757
- this.staticNoteStore ? await this.staticNoteStore.getAllNotes() : []
3758
- );
3759
- return this.runFindCatalog(parsed, options2, notes2);
3642
+ return this.runFindCatalog(parsed, options2, await this.noteStore.getAllNotes());
3760
3643
  }
3761
3644
  const read = compileContentRead(parsed.filters, "note", parsed.sourceScope);
3762
- const paging = domainPaging(parsed, options2, read.residual || !!this.staticNoteStore);
3763
- const fetchedNotes = !this.staticNoteStore && (read.filters.length > 0 || read.selection.length > 0 || paging.paged) && this.noteStore.queryDomain ? await this.noteStore.queryDomain({
3645
+ const paging = domainPaging(parsed, options2, read.residual);
3646
+ const fetchedNotes = (read.filters.length > 0 || read.selection.length > 0 || paging.paged) && this.noteStore.queryDomain ? await this.noteStore.queryDomain({
3764
3647
  plan: "notes",
3765
3648
  selection: read.selection,
3766
3649
  ...read.filters.length ? { filters: read.filters } : {},
@@ -3772,11 +3655,6 @@ var QueryService = class {
3772
3655
  let notes = domainNotes ? domainNotes.rows : await this.noteStore.getAllNotes();
3773
3656
  const selectedFromDomain = domainNotes ? domainNotes.selectedCount : void 0;
3774
3657
  const notesPaged = !!domainNotes && paging.paged;
3775
- if (this.staticNoteStore) {
3776
- const staticRows = applyNoteSelectionStages(await this.staticNoteStore.getAllNotes(), parsed);
3777
- const known = new Set(notes.map((n) => n.id));
3778
- notes = notes.concat(staticRows.filter((n) => !known.has(n.id)));
3779
- }
3780
3658
  notes = applyNoteSelectionStages(notes, parsed);
3781
3659
  const selectedCount = selectedFromDomain ?? notes.length;
3782
3660
  const ctx = runContext(options2);
@@ -3785,16 +3663,13 @@ var QueryService = class {
3785
3663
  const matchingIds = /* @__PURE__ */ new Set();
3786
3664
  for (const v of filter.values) {
3787
3665
  const ids = await this.noteStore.getNoteIdsForTag(v.value);
3788
- const sIds = this.staticNoteStore ? await this.staticNoteStore.getNoteIdsForTag(v.value) : /* @__PURE__ */ new Set();
3789
3666
  ids.forEach((id) => matchingIds.add(id));
3790
- sIds.forEach((id) => matchingIds.add(id));
3791
3667
  }
3792
3668
  notes = notes.filter((n) => matchingIds.has(n.id));
3793
3669
  } else if (filter.key === "tags" && filter.negate) {
3794
3670
  for (const v of filter.values) {
3795
3671
  const ids = await this.noteStore.getNoteIdsForTag(v.value);
3796
- const sIds = this.staticNoteStore ? await this.staticNoteStore.getNoteIdsForTag(v.value) : /* @__PURE__ */ new Set();
3797
- notes = notes.filter((n) => !ids.has(n.id) && !sIds.has(n.id));
3672
+ notes = notes.filter((n) => !ids.has(n.id));
3798
3673
  }
3799
3674
  } else if (filter.key === "effort" && !filter.negate) {
3800
3675
  const matchingIds = /* @__PURE__ */ new Set();
@@ -3813,9 +3688,12 @@ var QueryService = class {
3813
3688
  }
3814
3689
  }
3815
3690
  for (const filter of parsed.filters) {
3816
- if (filter.key === "text" && !filter.negate) {
3691
+ if (filter.key === "text") {
3817
3692
  const search = filter.values.map((v) => v.value).join(" ").toLowerCase();
3818
- notes = notes.filter((n) => n.title.toLowerCase().includes(search));
3693
+ notes = notes.filter((n) => {
3694
+ const hit = n.title.toLowerCase().includes(search) || n.rawContent.toLowerCase().includes(search);
3695
+ return filter.negate ? !hit : hit;
3696
+ });
3819
3697
  }
3820
3698
  }
3821
3699
  for (const filter of parsed.filters) {
@@ -3912,6 +3790,26 @@ var QueryService = class {
3912
3790
  }
3913
3791
  return containers;
3914
3792
  }
3793
+ async hydrateBlocks(rows) {
3794
+ const ids = [...new Set(rows.map((row) => row.noteId))];
3795
+ if (ids.length === 0) return [];
3796
+ const notes = this.noteStore.getNotesByIds ? await this.noteStore.getNotesByIds(ids) : await this.noteStore.getAllNotes();
3797
+ const parents = /* @__PURE__ */ new Map();
3798
+ for (const note of notes) {
3799
+ if (!Number.isSafeInteger(note.sourceRevision) || note.sourceRevision < 1) continue;
3800
+ try {
3801
+ wodWikiCore.validateNoteBlocks(note.rawContent, note.blocks, note.sourceRevision);
3802
+ parents.set(note.id, note);
3803
+ } catch {
3804
+ }
3805
+ }
3806
+ return rows.flatMap((row) => {
3807
+ const note = parents.get(row.noteId);
3808
+ const block = note?.blocks[row.blockId];
3809
+ if (!note || !block || row.id !== `${note.id}:${block.id}` || block.id !== row.blockId || block.kind !== row.dataType || block.version !== row.blockVersion || block.sourceRevision !== row.sourceRevision || note.sourceRevision !== row.sourceRevision || block.from !== row.from || block.to !== row.to) return [];
3810
+ return [{ ...row, rawContent: note.rawContent.slice(block.from, block.to) }];
3811
+ });
3812
+ }
3915
3813
  /**
3916
3814
  * Execute a :block query against the derived block_index store.
3917
3815
  */
@@ -3927,17 +3825,20 @@ var QueryService = class {
3927
3825
  ...paging.limit !== void 0 ? { limit: paging.limit } : {}
3928
3826
  }) : void 0;
3929
3827
  const domainBlocks = fetchedBlocks && fetchedBlocks.plan === "blocks" ? fetchedBlocks : void 0;
3930
- let blocks = domainBlocks ? domainBlocks.rows : await this.blockStore.getAllBlocks();
3931
- const selectedFromDomain = domainBlocks ? domainBlocks.selectedCount : void 0;
3932
- const blocksPaged = !!domainBlocks && paging.paged;
3828
+ const candidates = domainBlocks ? domainBlocks.rows : await this.blockStore.getAllBlocks();
3829
+ let blocks = await this.hydrateBlocks(candidates);
3830
+ const invalidCandidates = blocks.length !== candidates.length;
3831
+ const selectedFromDomain = domainBlocks && !invalidCandidates ? domainBlocks.selectedCount : void 0;
3832
+ const blocksPaged = !!domainBlocks && paging.paged && !invalidCandidates;
3833
+ if (domainBlocks && invalidCandidates) blocks = await this.hydrateBlocks(await this.blockStore.getAllBlocks());
3933
3834
  blocks = applySourceFilter(blocks, parsed.filters);
3934
3835
  blocks = blocks.filter((b) => !b.sourceId || WQL_SOURCE_VALUES.some((k) => sourceMatches(b, k)));
3935
3836
  const selectedCount = selectedFromDomain ?? blocks.length;
3936
3837
  const ctx = runContext(options2);
3937
3838
  for (const filter of parsed.filters) {
3938
- if (filter.key === "text" && !filter.negate) {
3839
+ if (filter.key === "text") {
3939
3840
  const search = filter.values.map((v) => v.value).join(" ").toLowerCase();
3940
- blocks = blocks.filter((b) => b.rawContent.toLowerCase().includes(search));
3841
+ blocks = blocks.filter((b) => filter.negate !== b.rawContent.toLowerCase().includes(search));
3941
3842
  }
3942
3843
  }
3943
3844
  for (const filter of parsed.filters) {
@@ -3962,18 +3863,16 @@ var QueryService = class {
3962
3863
  const matchingNoteIds = /* @__PURE__ */ new Set();
3963
3864
  for (const v of filter.values) {
3964
3865
  const ids = await this.noteStore.getNoteIdsForTag(v.value);
3965
- const sIds = this.staticNoteStore ? await this.staticNoteStore.getNoteIdsForTag(v.value) : /* @__PURE__ */ new Set();
3966
3866
  ids.forEach((id) => matchingNoteIds.add(id));
3967
- sIds.forEach((id) => matchingNoteIds.add(id));
3968
3867
  }
3969
3868
  blocks = blocks.filter((b) => matchingNoteIds.has(b.noteId));
3970
3869
  } else if (filter.key === "effort" && !filter.negate) {
3971
3870
  const matchingBlockIds = /* @__PURE__ */ new Set();
3972
3871
  for (const v of filter.values) {
3973
3872
  const rows = await this.blockEffortsStore.getAllFromIndex("by-effort", v.value);
3974
- for (const r of rows) matchingBlockIds.add(r.blockContentId);
3873
+ for (const row of rows) matchingBlockIds.add(`${row.noteId}:${row.blockId}`);
3975
3874
  }
3976
- blocks = blocks.filter((b) => b.blockContentId && matchingBlockIds.has(b.blockContentId));
3875
+ blocks = blocks.filter((block) => matchingBlockIds.has(`${block.noteId}:${block.blockId}`));
3977
3876
  }
3978
3877
  }
3979
3878
  for (const filter of parsed.filters) {
@@ -3983,11 +3882,7 @@ var QueryService = class {
3983
3882
  }
3984
3883
  }
3985
3884
  if (parsed.window || options2.range) {
3986
- let allNotes = [];
3987
- allNotes = allNotes.concat(await this.noteStore.getAllNotes());
3988
- if (this.staticNoteStore) {
3989
- allNotes = allNotes.concat(await this.staticNoteStore.getAllNotes());
3990
- }
3885
+ const allNotes = await this.noteStore.getAllNotes();
3991
3886
  const noteTimeMap = new Map(allNotes.map((n) => [n.id, n.date ?? n.createdAt]));
3992
3887
  blocks = blocks.filter((b) => effectiveTimeWindow(noteTimeMap.get(b.noteId) ?? b.createdAt, parsed.window, options2.range, ctx));
3993
3888
  }
@@ -4365,7 +4260,7 @@ var QueryService = class {
4365
4260
  let values;
4366
4261
  try {
4367
4262
  values = members.map(
4368
- (m) => shouldConvert && targetUnit ? wodWikiCore.convertViaCatalog(m.value, m.unit ?? m.metricUnit, targetUnit) : m.value
4263
+ (m) => shouldConvert && targetUnit && (m.unit ?? m.metricUnit) !== void 0 ? wodWikiCore.convertViaCatalog(m.value, m.unit ?? m.metricUnit, targetUnit) : m.value
4369
4264
  );
4370
4265
  } catch (e) {
4371
4266
  error = e instanceof Error ? e.message : String(e);
@@ -4487,7 +4382,7 @@ var QueryService = class {
4487
4382
  }
4488
4383
  /** All content blocks — journal and seeded corpus in one store. */
4489
4384
  async allContentBlocks() {
4490
- return this.blockStore.getAllBlocks();
4385
+ return this.hydrateBlocks(await this.blockStore.getAllBlocks());
4491
4386
  }
4492
4387
  /** Map each note id to the wod blockContentIds it owns. */
4493
4388
  async noteContentMap(noteIds) {
@@ -4564,7 +4459,10 @@ var QueryService = class {
4564
4459
  type: "feed",
4565
4460
  sourceId: `page:collection:${cat}`,
4566
4461
  catalog: cat,
4567
- createdAt: n.createdAt ?? 0
4462
+ createdAt: n.createdAt ?? 0,
4463
+ rawContent: "",
4464
+ sourceRevision: 1,
4465
+ blocks: {}
4568
4466
  });
4569
4467
  existingCatIds.add(cat);
4570
4468
  }
@@ -4613,9 +4511,7 @@ var QueryService = class {
4613
4511
  const matchingIds = /* @__PURE__ */ new Set();
4614
4512
  for (const v of filter.values) {
4615
4513
  const ids = await this.noteStore.getNoteIdsForTag(v.value);
4616
- const sIds = this.staticNoteStore ? await this.staticNoteStore.getNoteIdsForTag(v.value) : /* @__PURE__ */ new Set();
4617
4514
  ids.forEach((id) => matchingIds.add(id));
4618
- sIds.forEach((id) => matchingIds.add(id));
4619
4515
  }
4620
4516
  matchingCatalogs = matchingCatalogs.filter((c) => {
4621
4517
  const catId = catalogOfItem(c) ?? c.id;
@@ -4640,7 +4536,7 @@ var QueryService = class {
4640
4536
  matchingCatalogs = matchingCatalogs.filter((c) => {
4641
4537
  const catId = catalogOfItem(c) ?? c.id;
4642
4538
  const members = membersByCat.get(catId) ?? [];
4643
- const hit = members.some((m) => m.title.toLowerCase().includes(search));
4539
+ const hit = members.some((m) => m.title.toLowerCase().includes(search) || m.rawContent.toLowerCase().includes(search));
4644
4540
  return filter.negate ? !hit : hit;
4645
4541
  });
4646
4542
  } else if (filter.key === "note") {
@@ -4680,56 +4576,6 @@ var QueryService = class {
4680
4576
  }
4681
4577
  };
4682
4578
 
4683
- // src/static.ts
4684
- function staticCatalogRoute(block) {
4685
- const sourceId = block.sourceId;
4686
- if (sourceId?.startsWith("page:collection:")) {
4687
- const catalog = sourceId.slice("page:collection:".length);
4688
- if (catalog) return { landing: true, catalog, sourceId };
4689
- } else if (sourceId?.startsWith("collection:")) {
4690
- const route = sourceId.slice("collection:".length);
4691
- if (route) {
4692
- const slash = route.indexOf("/");
4693
- return slash === -1 ? { landing: true, catalog: route, sourceId: `page:collection:${route}` } : { landing: false, catalog: route.slice(0, slash), sourceId };
4694
- }
4695
- } else if (sourceId?.startsWith("feed:")) {
4696
- const route = sourceId.slice("feed:".length);
4697
- if (route) {
4698
- const stem = route.startsWith("feeds/") ? route.slice("feeds/".length) : route;
4699
- return { landing: false, catalog: stem.split("/")[0], sourceId };
4700
- }
4701
- }
4702
- return { landing: false, catalog: catalogOfItem({ noteId: block.noteId, sourceId: block.sourceId }), sourceId };
4703
- }
4704
- function staticNotesFromBlocks(blocks) {
4705
- const map = /* @__PURE__ */ new Map();
4706
- for (const block of blocks) {
4707
- if (map.has(block.noteId)) continue;
4708
- const route = staticCatalogRoute(block);
4709
- map.set(block.noteId, {
4710
- id: block.noteId,
4711
- title: block.noteTitle,
4712
- createdAt: block.createdAt,
4713
- type: route.landing ? "feed" : "note",
4714
- sourceId: route.sourceId,
4715
- catalog: route.catalog
4716
- });
4717
- }
4718
- return Array.from(map.values());
4719
- }
4720
- function staticTagIndexFromBlocks(blocks) {
4721
- const index = /* @__PURE__ */ new Map();
4722
- for (const block of blocks) {
4723
- if (block.dataType !== "frontmatter") continue;
4724
- for (const tag of extractFrontmatterTags(block.rawContent)) {
4725
- const ids = index.get(tag);
4726
- if (ids) ids.add(block.noteId);
4727
- else index.set(tag, /* @__PURE__ */ new Set([block.noteId]));
4728
- }
4729
- }
4730
- return index;
4731
- }
4732
-
4733
4579
  // src/dashboard/model.ts
4734
4580
  var DASHBOARD_WIDGET_TYPES = [
4735
4581
  "table",
@@ -4918,6 +4764,117 @@ function buildDashboardDocument(sections, meta) {
4918
4764
  };
4919
4765
  }
4920
4766
 
4767
+ // src/dashboard/frontmatter.ts
4768
+ var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
4769
+ function unquote(value) {
4770
+ return value.replace(
4771
+ /^(['"])(.*)\1$/,
4772
+ (_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
4773
+ );
4774
+ }
4775
+ function parseFrontmatter(raw) {
4776
+ const match = raw.match(FRONTMATTER_RE);
4777
+ if (!match) return { meta: {}, body: raw };
4778
+ return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
4779
+ }
4780
+ function parseMetaLines(lines) {
4781
+ const meta = {};
4782
+ for (let i = 0; i < lines.length; i++) {
4783
+ const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
4784
+ if (!keyMatch) continue;
4785
+ const key = keyMatch[1];
4786
+ const rawVal = keyMatch[2].trim();
4787
+ if (rawVal === "") {
4788
+ const items = [];
4789
+ while (i + 1 < lines.length) {
4790
+ const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
4791
+ if (!itemMatch) break;
4792
+ items.push(itemMatch[1].trim());
4793
+ i++;
4794
+ }
4795
+ meta[key] = items.length > 0 ? items : "";
4796
+ } else {
4797
+ const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
4798
+ const value = unquote(rawVal);
4799
+ const num = Number(value);
4800
+ meta[key] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
4801
+ }
4802
+ }
4803
+ return meta;
4804
+ }
4805
+ function parseFrontmatterBody(innerContent) {
4806
+ return parseMetaLines(innerContent.split(/\r?\n/));
4807
+ }
4808
+ function quoteYamlScalar(value) {
4809
+ if (value === "") return '""';
4810
+ const looksNumeric = !isNaN(Number(value));
4811
+ const looksKeyword = /^(true|false|null|yes|no|on|off)$/i.test(value);
4812
+ if (/[":'\n#{}[\\],&*?|<>=%!@`]/.test(value) || value !== value.trim() || value.startsWith("-") || looksNumeric || looksKeyword) {
4813
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
4814
+ }
4815
+ return value;
4816
+ }
4817
+ function serializeFrontmatter(meta) {
4818
+ const lines = [];
4819
+ for (const [key, value] of Object.entries(meta)) {
4820
+ if (Array.isArray(value)) {
4821
+ if (value.length === 0) {
4822
+ lines.push(`${key}: ""`);
4823
+ continue;
4824
+ }
4825
+ lines.push(`${key}:`);
4826
+ for (const item of value) {
4827
+ lines.push(` - ${quoteYamlScalar(item)}`);
4828
+ }
4829
+ } else if (typeof value === "number") {
4830
+ lines.push(`${key}: ${value}`);
4831
+ } else {
4832
+ lines.push(`${key}: ${quoteYamlScalar(value)}`);
4833
+ }
4834
+ }
4835
+ return lines.join("\n");
4836
+ }
4837
+ function stripFrontmatter(raw) {
4838
+ return parseFrontmatter(raw).body;
4839
+ }
4840
+ function getScalar(meta, key) {
4841
+ const value = meta[key];
4842
+ return typeof value === "string" || typeof value === "number" ? value : void 0;
4843
+ }
4844
+ function getList(meta, key) {
4845
+ const value = meta[key];
4846
+ return Array.isArray(value) ? value : [];
4847
+ }
4848
+ function parseFrontmatterCategories(raw) {
4849
+ return getList(parseFrontmatter(raw).meta, "category").map((c) => c.toLowerCase());
4850
+ }
4851
+ function extractFrontmatterTags(raw) {
4852
+ const meta = FRONTMATTER_RE.test(raw) ? parseFrontmatter(raw).meta : parseFrontmatterBody(raw);
4853
+ const tags = meta["tags"];
4854
+ const list = Array.isArray(tags) ? tags : typeof tags === "string" && tags.trim() ? tags.trim().startsWith("[") && tags.trim().endsWith("]") ? tags.trim().slice(1, -1).split(",") : [tags] : [];
4855
+ return [...new Set(list.map((tag) => tag.trim()).filter(Boolean))];
4856
+ }
4857
+ function parseFlatProperties(innerContent) {
4858
+ const props = {};
4859
+ for (const line of innerContent.split(/\r?\n/)) {
4860
+ const match = line.match(/^([^:]+):\s*(.*)$/);
4861
+ if (match) {
4862
+ props[match[1].trim()] = match[2].trim();
4863
+ }
4864
+ }
4865
+ return props;
4866
+ }
4867
+ function parseFrontmatterProps(lines) {
4868
+ const props = {};
4869
+ for (const line of lines) {
4870
+ const match = line.match(/^([^:]+):\s*(.*)$/);
4871
+ if (match) {
4872
+ props[match[1].trim()] = match[2].trim();
4873
+ }
4874
+ }
4875
+ return props;
4876
+ }
4877
+
4921
4878
  // src/dashboard/parser.ts
4922
4879
  function parseDashboardNote(rawContent) {
4923
4880
  const { meta } = parseFrontmatter(rawContent);
@@ -5122,6 +5079,44 @@ function appendWidget(raw, spec) {
5122
5079
  while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
5123
5080
  return [...lines, "", ...group, ""].join("\n");
5124
5081
  }
5082
+ function widgetResult(raw, note, order) {
5083
+ let from = 0;
5084
+ while (from < raw.length && from < note.length && raw[from] === note[from]) from++;
5085
+ let to = raw.length;
5086
+ let nextTo = note.length;
5087
+ while (to > from && nextTo > from && raw[to - 1] === note[nextTo - 1]) {
5088
+ to--;
5089
+ nextTo--;
5090
+ }
5091
+ const queries = parseDashboardNote(raw).sections.filter((section) => section.type === "query");
5092
+ const nextQueries = parseDashboardNote(note).sections.filter((section) => section.type === "query");
5093
+ const oldOffsets = lineOffsets(raw);
5094
+ const nextOffsets = lineOffsets(note);
5095
+ const oldLines = raw.split("\n");
5096
+ const nextLines = note.split("\n");
5097
+ const retainedBlocks = [];
5098
+ for (const [nextIndex, oldIndex] of (order ?? queries.map((_, index) => index)).entries()) {
5099
+ if (oldIndex === null) continue;
5100
+ const block = queries[oldIndex];
5101
+ const next = nextQueries[nextIndex];
5102
+ if (!block || !next) throw new Error("Widget mutation lost its source lineage");
5103
+ retainedBlocks.push({
5104
+ from: oldOffsets[block.startLine],
5105
+ to: oldOffsets[block.endLine] + oldLines[block.endLine].replace(/\r$/, "").length,
5106
+ nextFrom: nextOffsets[next.startLine],
5107
+ nextTo: nextOffsets[next.endLine] + nextLines[next.endLine].replace(/\r$/, "").length
5108
+ });
5109
+ }
5110
+ return { ok: true, note, changes: raw === note ? [] : [{ from, to, insert: note.slice(from, nextTo) }], retainedBlocks };
5111
+ }
5112
+ function lineOffsets(raw) {
5113
+ let offset = 0;
5114
+ return raw.split("\n").map((line) => {
5115
+ const from = offset;
5116
+ offset += line.length + 1;
5117
+ return from;
5118
+ });
5119
+ }
5125
5120
  function updateWidget(raw, key, expectedBody, spec) {
5126
5121
  const { sections } = parseDashboardNote(raw);
5127
5122
  const found = findWidgetGroup(sections, key, expectedBody);
@@ -5129,7 +5124,7 @@ function updateWidget(raw, key, expectedBody, spec) {
5129
5124
  const { group } = found;
5130
5125
  const lines = raw.split("\n");
5131
5126
  lines.splice(group.startLine, group.endLine - group.startLine + 1, ...renderWidgetGroup(spec));
5132
- return { ok: true, note: lines.join("\n") };
5127
+ return widgetResult(raw, lines.join("\n"));
5133
5128
  }
5134
5129
  function duplicateWidget(raw, key, expectedBody) {
5135
5130
  const { sections } = parseDashboardNote(raw);
@@ -5139,7 +5134,9 @@ function duplicateWidget(raw, key, expectedBody) {
5139
5134
  const lines = raw.split("\n");
5140
5135
  const copy = lines.slice(group.startLine, group.endLine + 1);
5141
5136
  lines.splice(group.endLine + 1, 0, "", ...copy);
5142
- return { ok: true, note: lines.join("\n") };
5137
+ const order = sections.filter((section) => section.type === "query").map((_, index) => index);
5138
+ order.splice(group.widgetIndex + 1, 0, null);
5139
+ return widgetResult(raw, lines.join("\n"), order);
5143
5140
  }
5144
5141
  function removeWidget(raw, key, expectedBody) {
5145
5142
  const { sections } = parseDashboardNote(raw);
@@ -5151,7 +5148,9 @@ function removeWidget(raw, key, expectedBody) {
5151
5148
  if (group.startLine > 0 && group.startLine < lines.length && lines[group.startLine - 1] === "" && lines[group.startLine] === "") {
5152
5149
  lines.splice(group.startLine, 1);
5153
5150
  }
5154
- return { ok: true, note: lines.join("\n") };
5151
+ const order = sections.filter((section) => section.type === "query").map((_, index) => index);
5152
+ order.splice(group.widgetIndex, 1);
5153
+ return widgetResult(raw, lines.join("\n"), order);
5155
5154
  }
5156
5155
  function moveWidget(raw, key, expectedBody, delta) {
5157
5156
  const { sections } = parseDashboardNote(raw);
@@ -5160,7 +5159,7 @@ function moveWidget(raw, key, expectedBody, delta) {
5160
5159
  const { group } = found;
5161
5160
  const groups = widgetGroupRanges(sections);
5162
5161
  const neighbor = groups[group.widgetIndex + delta];
5163
- if (!neighbor) return { ok: true, note: raw };
5162
+ if (!neighbor) return widgetResult(raw, raw);
5164
5163
  const lines = raw.split("\n");
5165
5164
  const moving = lines.slice(group.startLine, group.endLine + 1);
5166
5165
  if (delta === -1) {
@@ -5172,7 +5171,10 @@ function moveWidget(raw, key, expectedBody, delta) {
5172
5171
  const insertAt = neighbor.endLine - shift + 1;
5173
5172
  lines.splice(insertAt, 0, ...moving);
5174
5173
  }
5175
- return { ok: true, note: lines.join("\n") };
5174
+ const order = groups.map((_, index) => index);
5175
+ order.splice(group.widgetIndex, 1);
5176
+ order.splice(group.widgetIndex + delta, 0, group.widgetIndex);
5177
+ return widgetResult(raw, lines.join("\n"), order);
5176
5178
  }
5177
5179
  function resizeWidget(raw, key, expectedBody, span) {
5178
5180
  if (span.spanCols != null && (span.spanCols < 1 || span.spanCols > DASHBOARD_GRID_MAX_COLS)) {
@@ -5194,7 +5196,7 @@ function resizeWidget(raw, key, expectedBody, span) {
5194
5196
  attributes: block.attributes,
5195
5197
  attributeOrder: block.attributeOrder
5196
5198
  });
5197
- return { ok: true, note: lines.join("\n") };
5199
+ return widgetResult(raw, lines.join("\n"));
5198
5200
  }
5199
5201
  function migrateLegacyDashboardBodies(raw) {
5200
5202
  let out = raw;
@@ -5215,13 +5217,13 @@ ${query}
5215
5217
  }
5216
5218
 
5217
5219
  // src/version.ts
5218
- var VERSION = "0.35.2115";
5219
- var GIT_SHA = "37944a8";
5220
- var BUILD_TIME = "2026-10-11T23:54:36.367Z";
5220
+ var VERSION = "0.35.2117";
5221
+ var GIT_SHA = "03d1dfc";
5222
+ var BUILD_TIME = "2026-10-12T20:08:12.374Z";
5221
5223
  var SEMVER = {
5222
- version: "0.35.2115",
5223
- gitSha: "37944a8",
5224
- buildTime: "2026-10-11T23:54:36.367Z"
5224
+ version: "0.35.2117",
5225
+ gitSha: "03d1dfc",
5226
+ buildTime: "2026-10-12T20:08:12.374Z"
5225
5227
  };
5226
5228
 
5227
5229
  Object.defineProperty(exports, "SYSTEM_DEFAULT_OUTPUT_UNIT", {
@@ -5356,9 +5358,6 @@ exports.serializeFrontmatter = serializeFrontmatter;
5356
5358
  exports.setDashboardTokenValue = setDashboardTokenValue;
5357
5359
  exports.sourceMatches = sourceMatches;
5358
5360
  exports.splitAtWhere = splitAtWhere;
5359
- exports.staticCatalogRoute = staticCatalogRoute;
5360
- exports.staticNotesFromBlocks = staticNotesFromBlocks;
5361
- exports.staticTagIndexFromBlocks = staticTagIndexFromBlocks;
5362
5361
  exports.stripFrontmatter = stripFrontmatter;
5363
5362
  exports.substituteTokens = substituteTokens2;
5364
5363
  exports.substituteTokensRaw = substituteTokens;