@bitcobblers/wod-wiki-wql 0.35.2116 → 0.35.2119

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
@@ -654,10 +654,10 @@ function validateSourceFilter(filters) {
654
654
  if (val === "collection" || val === "collections") {
655
655
  return `source:${val} is retired \u2014 collections were renamed to feeds; use source:feeds or :feed{}.`;
656
656
  }
657
- if (WQL_SOURCE_VALUES.includes(val) || val === "feed" || val.startsWith("collection:")) {
657
+ if (WQL_SOURCE_VALUES.includes(val) || val === "feed" || val.startsWith("collection:") || val.startsWith("feed:")) {
658
658
  continue;
659
659
  }
660
- return `Unknown source "${val}". Try: ${WQL_SOURCE_VALUES.join(", ")} (or collection:<id>)`;
660
+ return `Unknown source "${val}". Try: ${WQL_SOURCE_VALUES.join(", ")} (or collection:<id>, feed:<id>)`;
661
661
  }
662
662
  }
663
663
  return void 0;
@@ -1144,9 +1144,7 @@ function findTargetAdvisories(query) {
1144
1144
  if (query.groupBy?.length) {
1145
1145
  const dimensions = wqlGroupingDimensions(target, "find");
1146
1146
  const unsupported = query.groupBy.filter((dim) => !dimensions.some((d) => d.toLowerCase() === dim.toLowerCase()));
1147
- if (target === "note" || target === "block") {
1148
- if (unsupported.length) advisories.push(`:${target} cannot group by ${unsupported.join(", ")}; grouped by tag instead. Valid alternatives: ${dimensions.join(", ")}.`);
1149
- } else if (target === "segment" || target === "event") {
1147
+ if (target === "note" || target === "block") ; else if (target === "segment" || target === "event") {
1150
1148
  const selected = query.pipes?.select?.map((column) => column.col);
1151
1149
  const unavailable = query.groupBy.filter((dim) => selected ? !selected.includes(dim) : !dimensions.includes(dim));
1152
1150
  if (unavailable.length) advisories.push(`:${target} groups only emitted table columns; ${unavailable.join(", ")} may be unassigned. Select each grouping column explicitly.`);
@@ -2592,8 +2590,8 @@ function normalizeSummaryFacts(logs, identity) {
2592
2590
  origin: identity.origin,
2593
2591
  pageId: identity.pageId,
2594
2592
  grain: "summary",
2595
- segmentId: identity.segmentId ?? "",
2596
- segmentVersion: identity.segmentVersion ?? 0,
2593
+ blockId: identity.blockId ?? "",
2594
+ blockVersion: identity.blockVersion ?? 0,
2597
2595
  resultId: identity.resultId,
2598
2596
  type: f.metricKey,
2599
2597
  value: f.value,
@@ -2645,8 +2643,8 @@ function toEventRows(logs, identity) {
2645
2643
  text: output.text,
2646
2644
  dialect: output.dialect,
2647
2645
  completionReason: output.completionReason,
2648
- segmentId: identity.segmentId,
2649
- segmentVersion: identity.segmentVersion
2646
+ blockId: identity.blockId,
2647
+ blockVersion: identity.blockVersion
2650
2648
  }));
2651
2649
  }
2652
2650
  function toSummaryEventRows(logs, identity) {
@@ -2693,8 +2691,8 @@ function toSummaryEventRows(logs, identity) {
2693
2691
  ...f.grade ? { grade: f.grade } : {}
2694
2692
  }
2695
2693
  }],
2696
- segmentId: identity.segmentId,
2697
- segmentVersion: identity.segmentVersion
2694
+ blockId: identity.blockId,
2695
+ blockVersion: identity.blockVersion
2698
2696
  }));
2699
2697
  }
2700
2698
  function projectEventToFacts(record) {
@@ -2713,8 +2711,8 @@ function projectEventToFacts(record) {
2713
2711
  origin: record.origin,
2714
2712
  pageId: record.pageId,
2715
2713
  grain: "summary",
2716
- segmentId: record.segmentId ?? "",
2717
- segmentVersion: record.segmentVersion ?? 0,
2714
+ blockId: record.blockId ?? "",
2715
+ blockVersion: record.blockVersion ?? 0,
2718
2716
  resultId: record.resultId,
2719
2717
  type: metricKey,
2720
2718
  value: m.value,
@@ -2772,8 +2770,8 @@ function projectEventToFacts(record) {
2772
2770
  origin: record.origin,
2773
2771
  pageId: record.pageId,
2774
2772
  grain: "event",
2775
- segmentId: record.segmentId ?? "",
2776
- segmentVersion: record.segmentVersion ?? 0,
2773
+ blockId: record.blockId ?? "",
2774
+ blockVersion: record.blockVersion ?? 0,
2777
2775
  resultId: record.resultId,
2778
2776
  type: metricKey,
2779
2777
  value: m.value,
@@ -2815,117 +2813,6 @@ function eventsToStoredLogs(events) {
2815
2813
  }));
2816
2814
  }
2817
2815
 
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
2816
  // src/selection.ts
2930
2817
  function partitionKey(coverage, effortSlug) {
2931
2818
  const tags = coverage?.groupTags ? Object.entries(coverage.groupTags).sort(([a], [b]) => a < b ? -1 : 1).map(([k, v]) => `${k}=${v}`).join(",") : "";
@@ -3045,6 +2932,12 @@ function sourceMatches(item, kind) {
3045
2932
  if (kind === "playground") {
3046
2933
  return sourceId === "playground" || item.type === "playground";
3047
2934
  }
2935
+ if (kind.startsWith("feed:")) {
2936
+ const rest = kind.slice("feed:".length).replace(/^feeds\//, "");
2937
+ if (!rest || !sourceId?.startsWith("feed:")) return false;
2938
+ const idRest = sourceId.slice("feed:".length).replace(/^feeds\//, "");
2939
+ return idRest === rest || idRest.startsWith(`${rest}/`);
2940
+ }
3048
2941
  return sourceId === kind;
3049
2942
  }
3050
2943
  function applySourceFilter(items, filters) {
@@ -3399,7 +3292,6 @@ var QueryService = class {
3399
3292
  noteStore;
3400
3293
  blockStore;
3401
3294
  effortStore;
3402
- staticNoteStore;
3403
3295
  pageStore;
3404
3296
  relationStore;
3405
3297
  datasetStore;
@@ -3414,14 +3306,13 @@ var QueryService = class {
3414
3306
  noteTagsStore = {
3415
3307
  getAllFromIndex: async () => []
3416
3308
  };
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)) {
3309
+ constructor(storesOrEventStore, noteStore, blockStore, effortStore) {
3310
+ 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
3311
  const stores = storesOrEventStore;
3420
3312
  this.store = stores.eventStore ?? defaultEventStore;
3421
3313
  this.noteStore = stores.noteStore ?? defaultNoteStore;
3422
3314
  this.blockStore = stores.blockStore ?? defaultBlockStore;
3423
3315
  this.effortStore = stores.effortStore ?? defaultEffortStore;
3424
- this.staticNoteStore = stores.staticNoteStore;
3425
3316
  this.pageStore = stores.pageStore;
3426
3317
  this.relationStore = stores.relationStore;
3427
3318
  this.datasetStore = stores.datasetStore;
@@ -3433,7 +3324,6 @@ var QueryService = class {
3433
3324
  this.noteStore = noteStore ?? defaultNoteStore;
3434
3325
  this.blockStore = blockStore ?? defaultBlockStore;
3435
3326
  this.effortStore = effortStore ?? defaultEffortStore;
3436
- this.staticNoteStore = staticNoteStore;
3437
3327
  }
3438
3328
  }
3439
3329
  /** Windowed flat-fact fetch — the projected view over the event store. */
@@ -3753,14 +3643,11 @@ var QueryService = class {
3753
3643
  }
3754
3644
  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
3645
  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);
3646
+ return this.runFindCatalog(parsed, options2, await this.noteStore.getAllNotes());
3760
3647
  }
3761
3648
  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({
3649
+ const paging = domainPaging(parsed, options2, read.residual);
3650
+ const fetchedNotes = (read.filters.length > 0 || read.selection.length > 0 || paging.paged) && this.noteStore.queryDomain ? await this.noteStore.queryDomain({
3764
3651
  plan: "notes",
3765
3652
  selection: read.selection,
3766
3653
  ...read.filters.length ? { filters: read.filters } : {},
@@ -3772,11 +3659,6 @@ var QueryService = class {
3772
3659
  let notes = domainNotes ? domainNotes.rows : await this.noteStore.getAllNotes();
3773
3660
  const selectedFromDomain = domainNotes ? domainNotes.selectedCount : void 0;
3774
3661
  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
3662
  notes = applyNoteSelectionStages(notes, parsed);
3781
3663
  const selectedCount = selectedFromDomain ?? notes.length;
3782
3664
  const ctx = runContext(options2);
@@ -3785,16 +3667,13 @@ var QueryService = class {
3785
3667
  const matchingIds = /* @__PURE__ */ new Set();
3786
3668
  for (const v of filter.values) {
3787
3669
  const ids = await this.noteStore.getNoteIdsForTag(v.value);
3788
- const sIds = this.staticNoteStore ? await this.staticNoteStore.getNoteIdsForTag(v.value) : /* @__PURE__ */ new Set();
3789
3670
  ids.forEach((id) => matchingIds.add(id));
3790
- sIds.forEach((id) => matchingIds.add(id));
3791
3671
  }
3792
3672
  notes = notes.filter((n) => matchingIds.has(n.id));
3793
3673
  } else if (filter.key === "tags" && filter.negate) {
3794
3674
  for (const v of filter.values) {
3795
3675
  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));
3676
+ notes = notes.filter((n) => !ids.has(n.id));
3798
3677
  }
3799
3678
  } else if (filter.key === "effort" && !filter.negate) {
3800
3679
  const matchingIds = /* @__PURE__ */ new Set();
@@ -3813,9 +3692,12 @@ var QueryService = class {
3813
3692
  }
3814
3693
  }
3815
3694
  for (const filter of parsed.filters) {
3816
- if (filter.key === "text" && !filter.negate) {
3695
+ if (filter.key === "text") {
3817
3696
  const search = filter.values.map((v) => v.value).join(" ").toLowerCase();
3818
- notes = notes.filter((n) => n.title.toLowerCase().includes(search));
3697
+ notes = notes.filter((n) => {
3698
+ const hit = n.title.toLowerCase().includes(search) || n.rawContent.toLowerCase().includes(search);
3699
+ return filter.negate ? !hit : hit;
3700
+ });
3819
3701
  }
3820
3702
  }
3821
3703
  for (const filter of parsed.filters) {
@@ -3912,6 +3794,26 @@ var QueryService = class {
3912
3794
  }
3913
3795
  return containers;
3914
3796
  }
3797
+ async hydrateBlocks(rows) {
3798
+ const ids = [...new Set(rows.map((row) => row.noteId))];
3799
+ if (ids.length === 0) return [];
3800
+ const notes = this.noteStore.getNotesByIds ? await this.noteStore.getNotesByIds(ids) : await this.noteStore.getAllNotes();
3801
+ const parents = /* @__PURE__ */ new Map();
3802
+ for (const note of notes) {
3803
+ if (!Number.isSafeInteger(note.sourceRevision) || note.sourceRevision < 1) continue;
3804
+ try {
3805
+ wodWikiCore.validateNoteBlocks(note.rawContent, note.blocks, note.sourceRevision);
3806
+ parents.set(note.id, note);
3807
+ } catch {
3808
+ }
3809
+ }
3810
+ return rows.flatMap((row) => {
3811
+ const note = parents.get(row.noteId);
3812
+ const block = note?.blocks[row.blockId];
3813
+ 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 [];
3814
+ return [{ ...row, rawContent: note.rawContent.slice(block.from, block.to) }];
3815
+ });
3816
+ }
3915
3817
  /**
3916
3818
  * Execute a :block query against the derived block_index store.
3917
3819
  */
@@ -3927,17 +3829,20 @@ var QueryService = class {
3927
3829
  ...paging.limit !== void 0 ? { limit: paging.limit } : {}
3928
3830
  }) : void 0;
3929
3831
  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;
3832
+ const candidates = domainBlocks ? domainBlocks.rows : await this.blockStore.getAllBlocks();
3833
+ let blocks = await this.hydrateBlocks(candidates);
3834
+ const invalidCandidates = blocks.length !== candidates.length;
3835
+ const selectedFromDomain = domainBlocks && !invalidCandidates ? domainBlocks.selectedCount : void 0;
3836
+ const blocksPaged = !!domainBlocks && paging.paged && !invalidCandidates;
3837
+ if (domainBlocks && invalidCandidates) blocks = await this.hydrateBlocks(await this.blockStore.getAllBlocks());
3933
3838
  blocks = applySourceFilter(blocks, parsed.filters);
3934
3839
  blocks = blocks.filter((b) => !b.sourceId || WQL_SOURCE_VALUES.some((k) => sourceMatches(b, k)));
3935
3840
  const selectedCount = selectedFromDomain ?? blocks.length;
3936
3841
  const ctx = runContext(options2);
3937
3842
  for (const filter of parsed.filters) {
3938
- if (filter.key === "text" && !filter.negate) {
3843
+ if (filter.key === "text") {
3939
3844
  const search = filter.values.map((v) => v.value).join(" ").toLowerCase();
3940
- blocks = blocks.filter((b) => b.rawContent.toLowerCase().includes(search));
3845
+ blocks = blocks.filter((b) => filter.negate !== b.rawContent.toLowerCase().includes(search));
3941
3846
  }
3942
3847
  }
3943
3848
  for (const filter of parsed.filters) {
@@ -3962,18 +3867,16 @@ var QueryService = class {
3962
3867
  const matchingNoteIds = /* @__PURE__ */ new Set();
3963
3868
  for (const v of filter.values) {
3964
3869
  const ids = await this.noteStore.getNoteIdsForTag(v.value);
3965
- const sIds = this.staticNoteStore ? await this.staticNoteStore.getNoteIdsForTag(v.value) : /* @__PURE__ */ new Set();
3966
3870
  ids.forEach((id) => matchingNoteIds.add(id));
3967
- sIds.forEach((id) => matchingNoteIds.add(id));
3968
3871
  }
3969
3872
  blocks = blocks.filter((b) => matchingNoteIds.has(b.noteId));
3970
3873
  } else if (filter.key === "effort" && !filter.negate) {
3971
3874
  const matchingBlockIds = /* @__PURE__ */ new Set();
3972
3875
  for (const v of filter.values) {
3973
3876
  const rows = await this.blockEffortsStore.getAllFromIndex("by-effort", v.value);
3974
- for (const r of rows) matchingBlockIds.add(r.blockContentId);
3877
+ for (const row of rows) matchingBlockIds.add(`${row.noteId}:${row.blockId}`);
3975
3878
  }
3976
- blocks = blocks.filter((b) => b.blockContentId && matchingBlockIds.has(b.blockContentId));
3879
+ blocks = blocks.filter((block) => matchingBlockIds.has(`${block.noteId}:${block.blockId}`));
3977
3880
  }
3978
3881
  }
3979
3882
  for (const filter of parsed.filters) {
@@ -3983,11 +3886,7 @@ var QueryService = class {
3983
3886
  }
3984
3887
  }
3985
3888
  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
- }
3889
+ const allNotes = await this.noteStore.getAllNotes();
3991
3890
  const noteTimeMap = new Map(allNotes.map((n) => [n.id, n.date ?? n.createdAt]));
3992
3891
  blocks = blocks.filter((b) => effectiveTimeWindow(noteTimeMap.get(b.noteId) ?? b.createdAt, parsed.window, options2.range, ctx));
3993
3892
  }
@@ -4365,7 +4264,7 @@ var QueryService = class {
4365
4264
  let values;
4366
4265
  try {
4367
4266
  values = members.map(
4368
- (m) => shouldConvert && targetUnit ? wodWikiCore.convertViaCatalog(m.value, m.unit ?? m.metricUnit, targetUnit) : m.value
4267
+ (m) => shouldConvert && targetUnit && (m.unit ?? m.metricUnit) !== void 0 ? wodWikiCore.convertViaCatalog(m.value, m.unit ?? m.metricUnit, targetUnit) : m.value
4369
4268
  );
4370
4269
  } catch (e) {
4371
4270
  error = e instanceof Error ? e.message : String(e);
@@ -4487,7 +4386,7 @@ var QueryService = class {
4487
4386
  }
4488
4387
  /** All content blocks — journal and seeded corpus in one store. */
4489
4388
  async allContentBlocks() {
4490
- return this.blockStore.getAllBlocks();
4389
+ return this.hydrateBlocks(await this.blockStore.getAllBlocks());
4491
4390
  }
4492
4391
  /** Map each note id to the wod blockContentIds it owns. */
4493
4392
  async noteContentMap(noteIds) {
@@ -4564,7 +4463,10 @@ var QueryService = class {
4564
4463
  type: "feed",
4565
4464
  sourceId: `page:collection:${cat}`,
4566
4465
  catalog: cat,
4567
- createdAt: n.createdAt ?? 0
4466
+ createdAt: n.createdAt ?? 0,
4467
+ rawContent: "",
4468
+ sourceRevision: 1,
4469
+ blocks: {}
4568
4470
  });
4569
4471
  existingCatIds.add(cat);
4570
4472
  }
@@ -4613,9 +4515,7 @@ var QueryService = class {
4613
4515
  const matchingIds = /* @__PURE__ */ new Set();
4614
4516
  for (const v of filter.values) {
4615
4517
  const ids = await this.noteStore.getNoteIdsForTag(v.value);
4616
- const sIds = this.staticNoteStore ? await this.staticNoteStore.getNoteIdsForTag(v.value) : /* @__PURE__ */ new Set();
4617
4518
  ids.forEach((id) => matchingIds.add(id));
4618
- sIds.forEach((id) => matchingIds.add(id));
4619
4519
  }
4620
4520
  matchingCatalogs = matchingCatalogs.filter((c) => {
4621
4521
  const catId = catalogOfItem(c) ?? c.id;
@@ -4640,7 +4540,7 @@ var QueryService = class {
4640
4540
  matchingCatalogs = matchingCatalogs.filter((c) => {
4641
4541
  const catId = catalogOfItem(c) ?? c.id;
4642
4542
  const members = membersByCat.get(catId) ?? [];
4643
- const hit = members.some((m) => m.title.toLowerCase().includes(search));
4543
+ const hit = members.some((m) => m.title.toLowerCase().includes(search) || m.rawContent.toLowerCase().includes(search));
4644
4544
  return filter.negate ? !hit : hit;
4645
4545
  });
4646
4546
  } else if (filter.key === "note") {
@@ -4680,56 +4580,6 @@ var QueryService = class {
4680
4580
  }
4681
4581
  };
4682
4582
 
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
4583
  // src/dashboard/model.ts
4734
4584
  var DASHBOARD_WIDGET_TYPES = [
4735
4585
  "table",
@@ -4918,6 +4768,117 @@ function buildDashboardDocument(sections, meta) {
4918
4768
  };
4919
4769
  }
4920
4770
 
4771
+ // src/dashboard/frontmatter.ts
4772
+ var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
4773
+ function unquote(value) {
4774
+ return value.replace(
4775
+ /^(['"])(.*)\1$/,
4776
+ (_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
4777
+ );
4778
+ }
4779
+ function parseFrontmatter(raw) {
4780
+ const match = raw.match(FRONTMATTER_RE);
4781
+ if (!match) return { meta: {}, body: raw };
4782
+ return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
4783
+ }
4784
+ function parseMetaLines(lines) {
4785
+ const meta = {};
4786
+ for (let i = 0; i < lines.length; i++) {
4787
+ const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
4788
+ if (!keyMatch) continue;
4789
+ const key = keyMatch[1];
4790
+ const rawVal = keyMatch[2].trim();
4791
+ if (rawVal === "") {
4792
+ const items = [];
4793
+ while (i + 1 < lines.length) {
4794
+ const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
4795
+ if (!itemMatch) break;
4796
+ items.push(itemMatch[1].trim());
4797
+ i++;
4798
+ }
4799
+ meta[key] = items.length > 0 ? items : "";
4800
+ } else {
4801
+ const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
4802
+ const value = unquote(rawVal);
4803
+ const num = Number(value);
4804
+ meta[key] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
4805
+ }
4806
+ }
4807
+ return meta;
4808
+ }
4809
+ function parseFrontmatterBody(innerContent) {
4810
+ return parseMetaLines(innerContent.split(/\r?\n/));
4811
+ }
4812
+ function quoteYamlScalar(value) {
4813
+ if (value === "") return '""';
4814
+ const looksNumeric = !isNaN(Number(value));
4815
+ const looksKeyword = /^(true|false|null|yes|no|on|off)$/i.test(value);
4816
+ if (/[":'\n#{}[\\],&*?|<>=%!@`]/.test(value) || value !== value.trim() || value.startsWith("-") || looksNumeric || looksKeyword) {
4817
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
4818
+ }
4819
+ return value;
4820
+ }
4821
+ function serializeFrontmatter(meta) {
4822
+ const lines = [];
4823
+ for (const [key, value] of Object.entries(meta)) {
4824
+ if (Array.isArray(value)) {
4825
+ if (value.length === 0) {
4826
+ lines.push(`${key}: ""`);
4827
+ continue;
4828
+ }
4829
+ lines.push(`${key}:`);
4830
+ for (const item of value) {
4831
+ lines.push(` - ${quoteYamlScalar(item)}`);
4832
+ }
4833
+ } else if (typeof value === "number") {
4834
+ lines.push(`${key}: ${value}`);
4835
+ } else {
4836
+ lines.push(`${key}: ${quoteYamlScalar(value)}`);
4837
+ }
4838
+ }
4839
+ return lines.join("\n");
4840
+ }
4841
+ function stripFrontmatter(raw) {
4842
+ return parseFrontmatter(raw).body;
4843
+ }
4844
+ function getScalar(meta, key) {
4845
+ const value = meta[key];
4846
+ return typeof value === "string" || typeof value === "number" ? value : void 0;
4847
+ }
4848
+ function getList(meta, key) {
4849
+ const value = meta[key];
4850
+ return Array.isArray(value) ? value : [];
4851
+ }
4852
+ function parseFrontmatterCategories(raw) {
4853
+ return getList(parseFrontmatter(raw).meta, "category").map((c) => c.toLowerCase());
4854
+ }
4855
+ function extractFrontmatterTags(raw) {
4856
+ const meta = FRONTMATTER_RE.test(raw) ? parseFrontmatter(raw).meta : parseFrontmatterBody(raw);
4857
+ const tags = meta["tags"];
4858
+ const list = Array.isArray(tags) ? tags : typeof tags === "string" && tags.trim() ? tags.trim().startsWith("[") && tags.trim().endsWith("]") ? tags.trim().slice(1, -1).split(",") : [tags] : [];
4859
+ return [...new Set(list.map((tag) => tag.trim()).filter(Boolean))];
4860
+ }
4861
+ function parseFlatProperties(innerContent) {
4862
+ const props = {};
4863
+ for (const line of innerContent.split(/\r?\n/)) {
4864
+ const match = line.match(/^([^:]+):\s*(.*)$/);
4865
+ if (match) {
4866
+ props[match[1].trim()] = match[2].trim();
4867
+ }
4868
+ }
4869
+ return props;
4870
+ }
4871
+ function parseFrontmatterProps(lines) {
4872
+ const props = {};
4873
+ for (const line of lines) {
4874
+ const match = line.match(/^([^:]+):\s*(.*)$/);
4875
+ if (match) {
4876
+ props[match[1].trim()] = match[2].trim();
4877
+ }
4878
+ }
4879
+ return props;
4880
+ }
4881
+
4921
4882
  // src/dashboard/parser.ts
4922
4883
  function parseDashboardNote(rawContent) {
4923
4884
  const { meta } = parseFrontmatter(rawContent);
@@ -5122,6 +5083,44 @@ function appendWidget(raw, spec) {
5122
5083
  while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
5123
5084
  return [...lines, "", ...group, ""].join("\n");
5124
5085
  }
5086
+ function widgetResult(raw, note, order) {
5087
+ let from = 0;
5088
+ while (from < raw.length && from < note.length && raw[from] === note[from]) from++;
5089
+ let to = raw.length;
5090
+ let nextTo = note.length;
5091
+ while (to > from && nextTo > from && raw[to - 1] === note[nextTo - 1]) {
5092
+ to--;
5093
+ nextTo--;
5094
+ }
5095
+ const queries = parseDashboardNote(raw).sections.filter((section) => section.type === "query");
5096
+ const nextQueries = parseDashboardNote(note).sections.filter((section) => section.type === "query");
5097
+ const oldOffsets = lineOffsets(raw);
5098
+ const nextOffsets = lineOffsets(note);
5099
+ const oldLines = raw.split("\n");
5100
+ const nextLines = note.split("\n");
5101
+ const retainedBlocks = [];
5102
+ for (const [nextIndex, oldIndex] of (order ?? queries.map((_, index) => index)).entries()) {
5103
+ if (oldIndex === null) continue;
5104
+ const block = queries[oldIndex];
5105
+ const next = nextQueries[nextIndex];
5106
+ if (!block || !next) throw new Error("Widget mutation lost its source lineage");
5107
+ retainedBlocks.push({
5108
+ from: oldOffsets[block.startLine],
5109
+ to: oldOffsets[block.endLine] + oldLines[block.endLine].replace(/\r$/, "").length,
5110
+ nextFrom: nextOffsets[next.startLine],
5111
+ nextTo: nextOffsets[next.endLine] + nextLines[next.endLine].replace(/\r$/, "").length
5112
+ });
5113
+ }
5114
+ return { ok: true, note, changes: raw === note ? [] : [{ from, to, insert: note.slice(from, nextTo) }], retainedBlocks };
5115
+ }
5116
+ function lineOffsets(raw) {
5117
+ let offset = 0;
5118
+ return raw.split("\n").map((line) => {
5119
+ const from = offset;
5120
+ offset += line.length + 1;
5121
+ return from;
5122
+ });
5123
+ }
5125
5124
  function updateWidget(raw, key, expectedBody, spec) {
5126
5125
  const { sections } = parseDashboardNote(raw);
5127
5126
  const found = findWidgetGroup(sections, key, expectedBody);
@@ -5129,7 +5128,7 @@ function updateWidget(raw, key, expectedBody, spec) {
5129
5128
  const { group } = found;
5130
5129
  const lines = raw.split("\n");
5131
5130
  lines.splice(group.startLine, group.endLine - group.startLine + 1, ...renderWidgetGroup(spec));
5132
- return { ok: true, note: lines.join("\n") };
5131
+ return widgetResult(raw, lines.join("\n"));
5133
5132
  }
5134
5133
  function duplicateWidget(raw, key, expectedBody) {
5135
5134
  const { sections } = parseDashboardNote(raw);
@@ -5139,7 +5138,9 @@ function duplicateWidget(raw, key, expectedBody) {
5139
5138
  const lines = raw.split("\n");
5140
5139
  const copy = lines.slice(group.startLine, group.endLine + 1);
5141
5140
  lines.splice(group.endLine + 1, 0, "", ...copy);
5142
- return { ok: true, note: lines.join("\n") };
5141
+ const order = sections.filter((section) => section.type === "query").map((_, index) => index);
5142
+ order.splice(group.widgetIndex + 1, 0, null);
5143
+ return widgetResult(raw, lines.join("\n"), order);
5143
5144
  }
5144
5145
  function removeWidget(raw, key, expectedBody) {
5145
5146
  const { sections } = parseDashboardNote(raw);
@@ -5151,7 +5152,9 @@ function removeWidget(raw, key, expectedBody) {
5151
5152
  if (group.startLine > 0 && group.startLine < lines.length && lines[group.startLine - 1] === "" && lines[group.startLine] === "") {
5152
5153
  lines.splice(group.startLine, 1);
5153
5154
  }
5154
- return { ok: true, note: lines.join("\n") };
5155
+ const order = sections.filter((section) => section.type === "query").map((_, index) => index);
5156
+ order.splice(group.widgetIndex, 1);
5157
+ return widgetResult(raw, lines.join("\n"), order);
5155
5158
  }
5156
5159
  function moveWidget(raw, key, expectedBody, delta) {
5157
5160
  const { sections } = parseDashboardNote(raw);
@@ -5160,7 +5163,7 @@ function moveWidget(raw, key, expectedBody, delta) {
5160
5163
  const { group } = found;
5161
5164
  const groups = widgetGroupRanges(sections);
5162
5165
  const neighbor = groups[group.widgetIndex + delta];
5163
- if (!neighbor) return { ok: true, note: raw };
5166
+ if (!neighbor) return widgetResult(raw, raw);
5164
5167
  const lines = raw.split("\n");
5165
5168
  const moving = lines.slice(group.startLine, group.endLine + 1);
5166
5169
  if (delta === -1) {
@@ -5172,7 +5175,10 @@ function moveWidget(raw, key, expectedBody, delta) {
5172
5175
  const insertAt = neighbor.endLine - shift + 1;
5173
5176
  lines.splice(insertAt, 0, ...moving);
5174
5177
  }
5175
- return { ok: true, note: lines.join("\n") };
5178
+ const order = groups.map((_, index) => index);
5179
+ order.splice(group.widgetIndex, 1);
5180
+ order.splice(group.widgetIndex + delta, 0, group.widgetIndex);
5181
+ return widgetResult(raw, lines.join("\n"), order);
5176
5182
  }
5177
5183
  function resizeWidget(raw, key, expectedBody, span) {
5178
5184
  if (span.spanCols != null && (span.spanCols < 1 || span.spanCols > DASHBOARD_GRID_MAX_COLS)) {
@@ -5194,7 +5200,7 @@ function resizeWidget(raw, key, expectedBody, span) {
5194
5200
  attributes: block.attributes,
5195
5201
  attributeOrder: block.attributeOrder
5196
5202
  });
5197
- return { ok: true, note: lines.join("\n") };
5203
+ return widgetResult(raw, lines.join("\n"));
5198
5204
  }
5199
5205
  function migrateLegacyDashboardBodies(raw) {
5200
5206
  let out = raw;
@@ -5215,13 +5221,13 @@ ${query}
5215
5221
  }
5216
5222
 
5217
5223
  // src/version.ts
5218
- var VERSION = "0.35.2116";
5219
- var GIT_SHA = "a4d411f";
5220
- var BUILD_TIME = "2026-10-12T00:14:24.795Z";
5224
+ var VERSION = "0.35.2119";
5225
+ var GIT_SHA = "e0eb840";
5226
+ var BUILD_TIME = "2026-10-13T06:24:52.520Z";
5221
5227
  var SEMVER = {
5222
- version: "0.35.2116",
5223
- gitSha: "a4d411f",
5224
- buildTime: "2026-10-12T00:14:24.795Z"
5228
+ version: "0.35.2119",
5229
+ gitSha: "e0eb840",
5230
+ buildTime: "2026-10-13T06:24:52.520Z"
5225
5231
  };
5226
5232
 
5227
5233
  Object.defineProperty(exports, "SYSTEM_DEFAULT_OUTPUT_UNIT", {
@@ -5356,9 +5362,6 @@ exports.serializeFrontmatter = serializeFrontmatter;
5356
5362
  exports.setDashboardTokenValue = setDashboardTokenValue;
5357
5363
  exports.sourceMatches = sourceMatches;
5358
5364
  exports.splitAtWhere = splitAtWhere;
5359
- exports.staticCatalogRoute = staticCatalogRoute;
5360
- exports.staticNotesFromBlocks = staticNotesFromBlocks;
5361
- exports.staticTagIndexFromBlocks = staticTagIndexFromBlocks;
5362
5365
  exports.stripFrontmatter = stripFrontmatter;
5363
5366
  exports.substituteTokens = substituteTokens2;
5364
5367
  exports.substituteTokensRaw = substituteTokens;