@bitcobblers/wod-wiki-wql 0.44.2160 → 0.45.2166

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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { KNOWN_OUTPUT_TYPES, dimensionKeyOf, convertViaCatalog, systemDefaultUnit, createMetricOwnershipLedger, MetricType, valueKindOf, normalizeFieldPath, toNotebookId, notebookSlugFromId, validateNoteBlocks, fieldRefKey, normalizeFieldComponent } from '@bitcobblers/wod-wiki-core';
1
+ import { KNOWN_OUTPUT_TYPES, dimensionKeyOf, convertViaCatalog, systemDefaultUnit, createMetricOwnershipLedger, MetricType, valueKindOf, normalizeFieldPath, toNotebookId, notebookSlugFromId, resolvePlacements, validateNoteBlocks, fieldRefKey, normalizeFieldComponent } from '@bitcobblers/wod-wiki-core';
2
2
  export { DEFAULT_NOTEBOOK_SLUGS, SYSTEM_DEFAULT_OUTPUT_UNIT, UnitConversionError, convertViaCatalog, dimensionKeyOf, isNoteEditable, lookupUnit, notebookSlugFromId, systemDefaultUnit, toNotebookId } from '@bitcobblers/wod-wiki-core';
3
3
  import { LRParser } from '@lezer/lr';
4
4
  import { LRLanguage, HighlightStyle, LanguageSupport, syntaxHighlighting } from '@codemirror/language';
@@ -30,7 +30,7 @@ var WQL_TAG_KEYS = [
30
30
  "result",
31
31
  "tags"
32
32
  ];
33
- var WQL_CONTENT_ONLY_KEYS = ["type", "text", "has", "source", "catalog", "notebook"];
33
+ var WQL_CONTENT_ONLY_KEYS = ["type", "text", "source", "notebook", "contributes"];
34
34
  var WQL_EFFORT_FILTER_KEYS = ["effort", "discipline", "intensity", "origin", "text"];
35
35
  var WQL_VIRTUAL_DIMS = ["day", "week", "session", "round"];
36
36
  var WQL_CALC_TARGETS = [
@@ -58,8 +58,8 @@ var WQL_CALC_TARGETS = [
58
58
  ];
59
59
  var WQL_INTENSITY_TIERS = ["low", "moderate", "high"];
60
60
  var WQL_GRAINS = ["summary", "event"];
61
- var WQL_FIND_TARGETS = ["note", "block", "effort", "session", "segment", "event"];
62
- var WQL_SOURCE_VALUES = ["journal", "feeds", "guides", "playground", "dashboards", "efforts", "equipment"];
61
+ var WQL_FIND_TARGETS = ["note", "block", "effort", "session", "segment", "event", "notebook", "page", "tag", "equipment"];
62
+ var WQL_SOURCE_VALUES = ["journal", "notebooks", "guides", "playground", "dashboards", "efforts", "equipment"];
63
63
  var WQL_SOURCE_HEADS = [
64
64
  "note",
65
65
  "notes",
@@ -71,23 +71,18 @@ var WQL_SOURCE_HEADS = [
71
71
  "journal",
72
72
  "notebook",
73
73
  "notebooks",
74
- "feed",
75
- "feeds",
76
- "catalog",
77
- "catalogs",
78
74
  "playground",
75
+ "page",
76
+ "tag",
77
+ "equipment",
79
78
  "dashboard"
80
79
  ];
81
80
  var WQL_SOURCE_HEAD_SCOPES = {
82
81
  journal: "journal",
83
- notebook: "feeds",
84
- notebooks: "feeds",
85
- feed: "feeds",
86
- feeds: "feeds",
87
- catalog: "feeds",
88
- catalogs: "feeds",
82
+ notebooks: "notebooks",
89
83
  playground: "playground"
90
84
  };
85
+ var WQL_NOTE_HEAD_ALIASES = ["notes"];
91
86
  var WQL_TYPE_HEADS = {
92
87
  dashboard: "dashboard"
93
88
  };
@@ -96,8 +91,8 @@ var WQL_FUNCTION_HEADS = WQL_AGGREGATORS;
96
91
  var WQL_CHART_HEADS = ["timeseries", "bar", "table", "donut", "toplist", "value"];
97
92
  var WQL_STANDARD_DATASETS = ["@session", "@today"];
98
93
  var WQL_RESULT_PLANES = KNOWN_OUTPUT_TYPES;
99
- var WQL_CONTENT_FILTER_KEYS = ["type", "text", "has", "source", "catalog", "notebook", ...WQL_TAG_KEYS];
100
- var WQL_SOURCES = ["note", "block", "effort", "session", "segment", "event"];
94
+ var WQL_CONTENT_FILTER_KEYS = ["type", "text", "source", "notebook", "page", "contributes", ...WQL_TAG_KEYS];
95
+ var WQL_SOURCES = WQL_FIND_TARGETS;
101
96
  var WQL_ROLLUP_PERIODS = ["2w", "4w"];
102
97
  var WQL_DISPLAY_UNITS = ["kg", "lb"];
103
98
  var WQL_KEYWORDS = [
@@ -133,12 +128,25 @@ var AGGREGATE_TYPES = /* @__PURE__ */ new Set([
133
128
  "metric"
134
129
  ]);
135
130
  var TARGET_TYPES = {
136
- note: /* @__PURE__ */ new Set(["text", "catalog", "notebook", "tag", "effort", ...WQL_TYPED_TAG_KEYS, "type", "page", "source", "note", "time", "groupby", "where"]),
137
- block: /* @__PURE__ */ new Set(["text", "catalog", "notebook", "tag", "effort", "type", "source", "note", "time", "groupby", "where"]),
131
+ // The notebook plane IS the note plane (its executor delegates), so both
132
+ // advertise the same member-criteria keys — container criteria (tags:,
133
+ // effort:, typed tags, text:) narrow notebook results, never ignored.
134
+ notebook: /* @__PURE__ */ new Set(["text", "notebook", "tag", "effort", ...WQL_TYPED_TAG_KEYS, "type", "page", "source", "note", "time", "groupby", "where"]),
135
+ note: /* @__PURE__ */ new Set(["text", "notebook", "tag", "effort", ...WQL_TYPED_TAG_KEYS, "type", "page", "source", "note", "time", "groupby", "where"]),
136
+ block: /* @__PURE__ */ new Set(["text", "notebook", "tag", "effort", "type", "source", "note", "time", "groupby", "where"]),
138
137
  effort: /* @__PURE__ */ new Set([...WQL_EFFORT_FILTER_KEYS, "groupby"]),
139
138
  session: /* @__PURE__ */ new Set(["result", "block", "note", "plane", "time", "groupby"]),
140
139
  segment: /* @__PURE__ */ new Set([...TABLE_KEYS, "time", "groupby", "unit"]),
141
- event: /* @__PURE__ */ new Set([...TABLE_KEYS, "time", "groupby", "unit"])
140
+ event: /* @__PURE__ */ new Set([...TABLE_KEYS, "time", "groupby", "unit"]),
141
+ // Container planes (ticket 08). `:notebook{}` delegates to the note plane
142
+ // (see the notebook entry above); `:page{}` lists the page placements;
143
+ // `:tag{}` the tags (results are notes); `:equipment{}` the equipment
144
+ // notes. The tag plane's `contributes:` filter matches what a tag
145
+ // contributes — never offered as a `tags:` label filter. These executors
146
+ // return flat rows (no `by {}` grouping), so groupby stays unadvertised.
147
+ page: /* @__PURE__ */ new Set(["page", "notebook", "text", "note", "time"]),
148
+ tag: /* @__PURE__ */ new Set(["tag", "type", "text", "contributes", "time"]),
149
+ equipment: /* @__PURE__ */ new Set(["equipment", "tags", "text", "notebook", "time"])
142
150
  };
143
151
  var EMPTY_TYPES = /* @__PURE__ */ new Set();
144
152
  var AGGREGATE_KEYS = WQL_TAG_KEYS;
@@ -653,18 +661,24 @@ function validateSourceFilter(filters) {
653
661
  for (const v of f.values) {
654
662
  const val = v.value;
655
663
  if (val === "page" || val === "pages") {
656
- return `source:${val} is retired \u2014 page-ness is handled by type: (e.g. type:feed or source:guides).`;
664
+ return `source:${val} is retired \u2014 page-ness is handled by type:page or source:guides.`;
657
665
  }
658
666
  if (val === "all") {
659
667
  return `source:all is retired \u2014 omit the source: filter to query all sources.`;
660
668
  }
661
- if (val === "collection" || val === "collections") {
662
- return `source:${val} is retired \u2014 collections were renamed to feeds; use source:feeds or :feed{}.`;
669
+ if (val === "notebook") {
670
+ return `source:notebook is retired \u2014 :notebook{} is the notebook plane (every notebook); narrow it with the notebook: filter, e.g. :notebook{notebook:journal}.`;
671
+ }
672
+ if (val === "feed" || val === "feeds" || val.startsWith("feed:")) {
673
+ return `source:${val} is retired \u2014 the feed corpus is the notebook plane; use source:notebooks or notebook:<slug>.`;
674
+ }
675
+ if (val === "collection" || val === "collections" || val.startsWith("collection:")) {
676
+ return `source:${val} is retired \u2014 collections are notebooks; use source:notebooks or notebook:<slug>.`;
663
677
  }
664
- if (WQL_SOURCE_VALUES.includes(val) || val === "feed" || val === "notebook" || val === "notebooks" || val.startsWith("collection:") || val.startsWith("feed:") || val.startsWith("notebook:") || val.startsWith("notebook/")) {
678
+ if (WQL_SOURCE_VALUES.includes(val) || val.startsWith("notebook:") || val.startsWith("notebook/")) {
665
679
  continue;
666
680
  }
667
- return `Unknown source "${val}". Try: ${WQL_SOURCE_VALUES.join(", ")} (or collection:<id>, feed:<id>)`;
681
+ return `Unknown source "${val}". Try: ${WQL_SOURCE_VALUES.join(", ")} (or notebook:<slug>)`;
668
682
  }
669
683
  }
670
684
  return void 0;
@@ -947,11 +961,7 @@ function parseFindQuery(raw, opts) {
947
961
  if (scope || typeScope) {
948
962
  result.target = "note";
949
963
  } else {
950
- result.target = headName === "notes" ? "note" : headName;
951
- if (result.target === "page") {
952
- result.error = "find:page is retired \u2014 page-ness is handled by type: (e.g. :note{source:guides} or type:feed).";
953
- return result;
954
- }
964
+ result.target = WQL_NOTE_HEAD_ALIASES.includes(headName) ? "note" : headName;
955
965
  if (!WQL_FIND_TARGETS.includes(result.target)) {
956
966
  result.error = `Unknown find target "${result.target}". Try: ${WQL_FIND_TARGETS.join(", ")}`;
957
967
  return result;
@@ -971,13 +981,6 @@ function parseFindQuery(raw, opts) {
971
981
  negate: false,
972
982
  values: [{ value: scope, wildcard: false }]
973
983
  });
974
- if (headName === "catalog" || headName === "catalogs" || headName === "notebook" || headName === "notebooks") {
975
- result.filters.push({
976
- key: "type",
977
- negate: false,
978
- values: [{ value: "feed", wildcard: false }]
979
- });
980
- }
981
984
  }
982
985
  const inScope = opts?.colon && result.displayUnit && result.displayUnit !== "kg" && result.displayUnit !== "lb" ? result.displayUnit : legacyScope;
983
986
  if (opts?.colon && inScope) {
@@ -1486,10 +1489,12 @@ function chained(list, suffix) {
1486
1489
  ...c,
1487
1490
  apply: (view, _c, from, to) => {
1488
1491
  const text = typeof c.apply === "string" ? c.apply : c.label;
1492
+ const selfTerminated = /\W$/.test(text);
1489
1493
  const present = view.state.sliceDoc(to, to + suffix.length) === suffix;
1494
+ const insert = selfTerminated || present ? text : text + suffix;
1490
1495
  view.dispatch({
1491
- changes: { from, to, insert: present ? text : text + suffix },
1492
- selection: { anchor: from + text.length + suffix.length },
1496
+ changes: { from, to: to + (selfTerminated && present ? suffix.length : 0), insert },
1497
+ selection: { anchor: from + insert.length },
1493
1498
  userEvent: "input.complete"
1494
1499
  });
1495
1500
  startCompletion(view);
@@ -1507,7 +1512,7 @@ function headApply(o, colonTyped) {
1507
1512
  return { ...o, apply: `${colonTyped ? "" : ":"}${o.label}{` };
1508
1513
  }
1509
1514
  var LEGACY_HEAD_OPTIONS = WQL_AGGREGATORS.map((label) => ({ label, type: "keyword" }));
1510
- var NOTE_COLUMNS = ["title", "date", "createdAt", "type", "sourceId", "catalog", "notebookId"];
1515
+ var NOTE_COLUMNS = ["title", "date", "createdAt", "type", "sourceId", "notebookId"];
1511
1516
  var BLOCK_COLUMNS = ["noteTitle", "dataType", "position", "createdAt", "noteId", "sourceId"];
1512
1517
  var EFFORT_COLUMNS = ["slug", "label", "registrySource"];
1513
1518
  var SESSION_COLUMNS = ["duration", "startTime", "origin"];
@@ -1535,7 +1540,7 @@ function resolveColonHead(name) {
1535
1540
  if (WQL_FUNCTION_HEADS.includes(n)) return { family: "aggregate" };
1536
1541
  if (WQL_CHART_HEADS.includes(n)) return { family: "find" };
1537
1542
  const headTarget = (head) => {
1538
- if (head === "notes") return "note";
1543
+ if (WQL_NOTE_HEAD_ALIASES.includes(head)) return "note";
1539
1544
  const scoped = WQL_SOURCE_HEAD_SCOPES[head] || WQL_TYPE_HEADS[head];
1540
1545
  return scoped ? "note" : head;
1541
1546
  };
@@ -1704,12 +1709,17 @@ function wqlCompletionSource(options_ = {}) {
1704
1709
  if (/\s+to\s+$/i.test(tail)) {
1705
1710
  return finish(context, context.pos, [{ label: "2026-03-31", detail: "end date (YYYY-MM-DD)", type: "constant" }], "");
1706
1711
  }
1712
+ const hasBy = /\bby\s*\{[^{}]*\}/.test(tail);
1713
+ const hasRollup = /\.rollup\([^()]*\)/.test(tail);
1714
+ const hasWindow = /\s+last\s+\d+[dw]\b/i.test(tail) || /\s+from\s+\d{4}-\d{2}-\d{2}(?:\s+to\s+\d{4}-\d{2}-\d{2})?\b/i.test(tail);
1707
1715
  const dims = wqlGroupingDimensions(head.target ?? "", head.family);
1708
1716
  const keywords = [
1709
- { label: "by {}", apply: "by ", detail: dims.length ? "group by dimensions" : `advisory: no grouping dimensions for find:${head.target}`, type: "keyword" },
1710
- ...head.family === "aggregate" ? [{ label: ".rollup()", apply: ".rollup(", detail: "bucket period", type: "keyword" }] : [],
1711
- { label: "last", apply: "last ", detail: "relative window: last <n>d|w", type: "keyword" },
1712
- { label: "from", apply: "from ", detail: "date range: from YYYY-MM-DD [to YYYY-MM-DD]", type: "keyword" },
1717
+ ...hasBy ? [] : [{ label: "by {}", apply: "by ", detail: dims.length ? "group by dimensions" : `advisory: no grouping dimensions for find:${head.target}`, type: "keyword" }],
1718
+ ...head.family === "aggregate" && !hasRollup ? [{ label: ".rollup()", apply: ".rollup(", detail: "bucket period", type: "keyword" }] : [],
1719
+ ...hasWindow ? [] : [
1720
+ { label: "last", apply: "last ", detail: "relative window: last <n>d|w", type: "keyword" },
1721
+ { label: "from", apply: "from ", detail: "date range: from YYYY-MM-DD [to YYYY-MM-DD]", type: "keyword" }
1722
+ ],
1713
1723
  ...head.family === "find" ? [{ label: "|", apply: "| ", detail: "presentation pipes: order by / select / limit", type: "keyword" }] : []
1714
1724
  ];
1715
1725
  return finish(context, from, chained(keywords, " "), typed);
@@ -2275,6 +2285,7 @@ var QueryDocumentRunner = class {
2275
2285
  ...findRun.blocks ? { blocks: findRun.blocks } : {},
2276
2286
  ...findRun.efforts ? { efforts: findRun.efforts } : {},
2277
2287
  ...findRun.containers ? { containers: findRun.containers } : {},
2288
+ ...findRun.placements ? { placements: findRun.placements } : {},
2278
2289
  ...findRun.runs ? { runs: findRun.runs } : {},
2279
2290
  ...findRun.table ? { table: findRun.table } : {},
2280
2291
  ...findRun.unit ? { unit: findRun.unit } : {},
@@ -2305,6 +2316,7 @@ var QueryDocumentRunner = class {
2305
2316
  ...pipelineRun.blocks ? { blocks: pipelineRun.blocks } : {},
2306
2317
  ...pipelineRun.efforts ? { efforts: pipelineRun.efforts } : {},
2307
2318
  ...pipelineRun.containers ? { containers: pipelineRun.containers } : {},
2319
+ ...pipelineRun.placements ? { placements: pipelineRun.placements } : {},
2308
2320
  ...pipelineRun.runs ? { runs: pipelineRun.runs } : {},
2309
2321
  ...pipelineRun.unit ? { unit: pipelineRun.unit } : {},
2310
2322
  ...pipelineRun.chart ? { chart: pipelineRun.chart } : {},
@@ -2911,16 +2923,18 @@ function dedupeById(facts) {
2911
2923
  }
2912
2924
  return out;
2913
2925
  }
2926
+
2927
+ // src/QueryService.ts
2914
2928
  var NOTE_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2915
- function catalogOfItem(item) {
2916
- if (item.catalog) return item.catalog;
2917
- const isCollectionOrFeed = !!item.sourceId && /^(collection|feed):/.test(item.sourceId);
2918
- const raw = isCollectionOrFeed ? item.sourceId.replace(/^(collection|feed):/, "") : item.noteId || item.id || "";
2919
- if (!raw) return void 0;
2920
- if (NOTE_UUID_RE.test(raw)) return void 0;
2921
- const clean = raw.startsWith("feeds/") ? raw.slice("feeds/".length) : raw;
2922
- if (!isCollectionOrFeed && !clean.includes("/")) return void 0;
2923
- return clean.split("/")[0];
2929
+ function notebookSlugOfSourceId(sourceId) {
2930
+ if (!sourceId?.startsWith("notebook:")) return void 0;
2931
+ const rest = sourceId.slice("notebook:".length);
2932
+ if (!rest || rest === "_root") return void 0;
2933
+ return rest.split("/")[0];
2934
+ }
2935
+ function feedOfItem(item) {
2936
+ const id = item.feedSourceId;
2937
+ return typeof id === "string" && id.trim() ? id : void 0;
2924
2938
  }
2925
2939
  function notebookOfItem(item) {
2926
2940
  if (item.notebookId && item.notebookId.trim()) {
@@ -2929,46 +2943,75 @@ function notebookOfItem(item) {
2929
2943
  if (item.tags) {
2930
2944
  for (const tag of item.tags) {
2931
2945
  if (tag.startsWith("notebook:") || tag.startsWith("notebook/")) {
2932
- const slug = notebookSlugFromId(tag);
2933
- if (slug) return toNotebookId(slug);
2946
+ const slug2 = notebookSlugFromId(tag);
2947
+ if (slug2) return toNotebookId(slug2);
2934
2948
  }
2935
2949
  }
2936
2950
  }
2951
+ const corpus = /^markdown\/(notebooks|canvas|efforts|dashboards|equipment)\/(.+)$/.exec(item.sourcePath ?? "");
2952
+ if (corpus) {
2953
+ const [, family, rest] = corpus;
2954
+ if (family === "canvas") return "notebook/guides";
2955
+ if (family !== "notebooks") return toNotebookId(family);
2956
+ const segs = rest.split("/");
2957
+ if (segs.length > 1) return toNotebookId(segs[0]);
2958
+ }
2937
2959
  if (sourceMatches(item, "playground")) return "notebook/playground";
2938
2960
  if (sourceMatches(item, "guides")) return "notebook/guides";
2939
- const cat = catalogOfItem(item);
2940
- if (cat) return toNotebookId(cat);
2941
- if (sourceMatches(item, "journal")) return "notebook/journal";
2961
+ if (item.sourceId === "efforts") return "notebook/efforts";
2962
+ if (item.sourceId === "equipment") return "notebook/equipment";
2963
+ if (item.sourceId === "dashboards" || item.type === "dashboard") return "notebook/dashboards";
2964
+ const slug = notebookSlugOfSourceId(item.sourceId);
2965
+ if (slug) return toNotebookId(slug);
2966
+ if (isJournalRow(item)) return "notebook/journal";
2942
2967
  return void 0;
2943
2968
  }
2969
+ function notebookOriginOfItem(item) {
2970
+ const notebook = notebookOfItem(item);
2971
+ if (!notebook) return void 0;
2972
+ if (item.notebookId?.trim()) return "user";
2973
+ if (item.tags?.some((t2) => t2.startsWith("notebook:") || t2.startsWith("notebook/"))) return "user";
2974
+ if (notebook === "notebook/journal" || notebook === "notebook/playground") return "default";
2975
+ return "feed";
2976
+ }
2977
+ function isJournalRow(item) {
2978
+ const sourceId = item.sourceId;
2979
+ if (item.type === "playground" || item.id?.startsWith("playground/") || item.noteId?.startsWith("playground/")) return false;
2980
+ if (sourceId && sourceId !== "journal" && !NOTE_UUID_RE.test(sourceId) && !/^journal\/\d{4}-\d{2}-\d{2}$/.test(sourceId)) return false;
2981
+ return item.type !== "dashboard" && item.type !== "effort" && item.type !== "equipment" && item.seedOrigin !== "seed" && item.feedOrigin !== "feed" && item.seedChunkId === void 0 && !(!!item.sourcePath && item.sourcePath.startsWith("markdown/"));
2982
+ }
2983
+ var SOURCE_ID_SHAPES = {
2984
+ notebooks: { prefixes: ["notebook:"] },
2985
+ pages: { exact: ["page"], prefixes: ["page:", "guides:"] },
2986
+ guides: { prefixes: ["guides:"] },
2987
+ playground: { exact: ["playground"], legacyType: "playground" },
2988
+ efforts: { exact: ["efforts"] },
2989
+ equipment: { exact: ["equipment"] },
2990
+ dashboards: { exact: ["dashboards"] }
2991
+ };
2992
+ var SOURCE_SHAPE_ALIASES = {
2993
+ page: "pages",
2994
+ guide: "guides"
2995
+ };
2996
+ function sourceShape(kind) {
2997
+ return SOURCE_ID_SHAPES[SOURCE_SHAPE_ALIASES[kind] ?? kind];
2998
+ }
2944
2999
  function sourceMatches(item, kind) {
2945
3000
  const sourceId = item.sourceId;
2946
3001
  if (kind === "all") return !sourceId || WQL_SOURCE_VALUES.some((k) => sourceMatches(item, k));
2947
3002
  if (kind === "journal") {
2948
- if (item.type === "playground" || item.id?.startsWith("playground/") || item.noteId?.startsWith("playground/")) return false;
2949
- if (sourceId && sourceId !== "journal" && !NOTE_UUID_RE.test(sourceId) && !/^journal\/\d{4}-\d{2}-\d{2}$/.test(sourceId)) return false;
2950
- return item.type !== "dashboard" && item.type !== "effort" && item.type !== "equipment" && item.seedOrigin !== "seed" && item.seedChunkId === void 0 && !item.catalog && !(!!item.sourcePath && item.sourcePath.startsWith("markdown/"));
3003
+ return notebookOfItem(item) === "notebook/journal";
2951
3004
  }
2952
- if (kind === "feed" || kind === "feeds" || kind === "notebook" || kind === "notebooks") {
2953
- return !!sourceId && (sourceId.startsWith("collection:") || sourceId.startsWith("page:collection:"));
2954
- }
2955
- if (kind === "page" || kind === "pages") {
2956
- return !!sourceId && (sourceId === "page" || sourceId.startsWith("page:") || sourceId.startsWith("guides:"));
2957
- }
2958
- if (kind === "guide" || kind === "guides") {
2959
- return !!sourceId && sourceId.startsWith("guides:");
2960
- }
2961
- if (kind === "playground") {
2962
- return sourceId === "playground" || item.type === "playground";
2963
- }
2964
- if (kind.startsWith("feed:")) {
2965
- const rest = kind.slice("feed:".length).replace(/^feeds\//, "");
2966
- if (!rest || !sourceId?.startsWith("feed:")) return false;
2967
- const idRest = sourceId.slice("feed:".length).replace(/^feeds\//, "");
2968
- return idRest === rest || idRest.startsWith(`${rest}/`);
3005
+ const shape = sourceShape(kind);
3006
+ if (shape) {
3007
+ const idHit = !!sourceId && (shape.exact?.includes(sourceId) || shape.prefixes?.some((p) => sourceId.startsWith(p)) === true);
3008
+ return idHit || !!shape.legacyType && item.type === shape.legacyType;
2969
3009
  }
2970
3010
  if (kind.startsWith("notebook:") || kind.startsWith("notebook/")) {
2971
- return notebookOfItem(item) === toNotebookId(kind);
3011
+ const rest = notebookSlugFromId(kind);
3012
+ const idRest = sourceId?.startsWith("notebook:") ? sourceId.slice("notebook:".length) : void 0;
3013
+ if (!idRest) return notebookOfItem(item) === toNotebookId(kind);
3014
+ return idRest === rest || idRest.startsWith(`${rest}/`);
2972
3015
  }
2973
3016
  return sourceId === kind;
2974
3017
  }
@@ -2997,8 +3040,7 @@ function applyNotebookFilter(items, filters) {
2997
3040
  return items;
2998
3041
  }
2999
3042
  var DOMAIN_SOURCE_KINDS = {
3000
- feed: true,
3001
- feeds: true,
3043
+ notebooks: true,
3002
3044
  page: true,
3003
3045
  pages: true,
3004
3046
  guide: true,
@@ -3024,6 +3066,10 @@ function compileContentRead(filters, plane, sourceScope) {
3024
3066
  residual = true;
3025
3067
  continue;
3026
3068
  }
3069
+ if (plane === "note" && WQL_TYPED_TAG_KEYS.includes(filter.key) && literal) {
3070
+ compiled.push({ field: "typedTag", tagType: filter.key, values });
3071
+ continue;
3072
+ }
3027
3073
  if (filter.key === "text" && literal) {
3028
3074
  compiled.push({ field: "text", value: values.join(" ") });
3029
3075
  continue;
@@ -3686,9 +3732,17 @@ var QueryService = class {
3686
3732
  if (parsed.target === "segment" || parsed.target === "event") {
3687
3733
  return this.runFindTable(parsed, options2);
3688
3734
  }
3689
- 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" || v.value === "notebook" || v.value === "notebooks")) || parsed.sourceScope?.includes("feeds") || parsed.sourceScope?.includes("notebooks"));
3690
- if (isCatalogHead) {
3691
- return this.runFindCatalog(parsed, options2, await this.noteStore.getAllNotes());
3735
+ if (parsed.target === "notebook") {
3736
+ return this.runFindNotebook(parsed, options2);
3737
+ }
3738
+ if (parsed.target === "page") {
3739
+ return this.runFindPage(parsed, options2);
3740
+ }
3741
+ if (parsed.target === "tag") {
3742
+ return this.runFindTag(parsed, options2);
3743
+ }
3744
+ if (parsed.target === "equipment") {
3745
+ return this.runFindEquipment(parsed, options2);
3692
3746
  }
3693
3747
  const read = compileContentRead(parsed.filters, "note", parsed.sourceScope);
3694
3748
  const paging = domainPaging(parsed, options2, read.residual);
@@ -3708,14 +3762,14 @@ var QueryService = class {
3708
3762
  const selectedCount = selectedFromDomain ?? notes.length;
3709
3763
  const ctx = runContext(options2);
3710
3764
  for (const filter of parsed.filters) {
3711
- if (filter.key === "tags" && !filter.negate) {
3765
+ if (isTagLabelKey(filter.key) && !filter.negate) {
3712
3766
  const matchingIds = /* @__PURE__ */ new Set();
3713
3767
  for (const v of filter.values) {
3714
3768
  const ids = await this.noteStore.getNoteIdsForTag(v.value);
3715
3769
  ids.forEach((id) => matchingIds.add(id));
3716
3770
  }
3717
3771
  notes = notes.filter((n) => matchingIds.has(n.id));
3718
- } else if (filter.key === "tags" && filter.negate) {
3772
+ } else if (isTagLabelKey(filter.key) && filter.negate) {
3719
3773
  for (const v of filter.values) {
3720
3774
  const ids = await this.noteStore.getNoteIdsForTag(v.value);
3721
3775
  notes = notes.filter((n) => !ids.has(n.id));
@@ -3758,17 +3812,6 @@ var QueryService = class {
3758
3812
  notes = notes.filter((n) => wantsPage ? n.type === "page" : n.type !== "page");
3759
3813
  }
3760
3814
  }
3761
- for (const filter of parsed.filters) {
3762
- if (filter.key === "catalog") {
3763
- const wanted = new Set(filter.values.map((v) => v.value));
3764
- notes = notes.filter((n) => {
3765
- const cat = catalogOfItem(n);
3766
- if (!cat) return filter.negate;
3767
- const hit = wanted.has(cat);
3768
- return filter.negate ? !hit : hit;
3769
- });
3770
- }
3771
- }
3772
3815
  notes = applyNotebookFilter(notes, parsed.filters);
3773
3816
  for (const filter of parsed.filters) {
3774
3817
  if (filter.key === "note") {
@@ -3799,46 +3842,56 @@ var QueryService = class {
3799
3842
  const offset = pipes.offset ?? 0;
3800
3843
  notes = pipes.limit !== void 0 ? notes.slice(offset, offset + pipes.limit) : notes.slice(offset);
3801
3844
  }
3845
+ const relations = notes.length && (this.pageStore || this.relationStore) ? await this.containersAndPlacements(notes) : void 0;
3802
3846
  return {
3803
3847
  parsed,
3804
3848
  notes,
3805
3849
  blocks: [],
3806
- ...notes.length && (this.pageStore || this.relationStore) ? { containers: await this.containersForNotes(notes) } : {},
3850
+ ...relations ? { containers: relations.containers, placements: relations.placements } : {},
3807
3851
  stages: { selected: selectedCount, matched: notes.length }
3808
3852
  };
3809
3853
  }
3810
- /** Highest container per note id: the parent Page from the canonical
3811
- * page_notes junction (first link by position) wins; a note with no page
3812
- * link stays a standalone Note container. Batched when a relationStore
3813
- * is injected — one bundle read per run instead of a link/page read per
3814
- * matched note. */
3815
- async containersForNotes(notes) {
3854
+ /** The container projection and the full placement set per note id.
3855
+ *
3856
+ * The container is the parent Page from the canonical page_notes junction
3857
+ * (the primary placement wins); a note with no page link stays a
3858
+ * standalone Note container. Batched when a relationStore is injected —
3859
+ * one bundle read per run instead of a link/page read per matched note.
3860
+ *
3861
+ * The same read resolves EVERY page the note is placed on: the junction
3862
+ * is many-to-many, so the container is a projection of that set, not the
3863
+ * set itself. */
3864
+ async containersAndPlacements(notes) {
3816
3865
  const containers = {};
3866
+ const placements = {};
3817
3867
  const bundles = this.relationStore ? await this.relationStore.getBatches([...new Set(notes.map((n) => n.id))]) : void 0;
3818
3868
  const pageCache = /* @__PURE__ */ new Map();
3819
3869
  for (const note of notes) {
3820
- let page;
3821
3870
  let bundle;
3871
+ let resolved;
3822
3872
  if (bundles) {
3823
3873
  bundle = bundles.get(note.id);
3824
- const [first] = [...bundle?.links ?? []].sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
3825
- page = first ? bundle.pages.find((p) => p.id === first.pageId) : void 0;
3874
+ resolved = resolvePlacements(bundle?.links ?? [], bundle?.pages ?? []);
3826
3875
  if (bundle && bundle.tags.length > 0 && (!note.tags || note.tags.length === 0)) {
3827
3876
  note.tags = bundle.tags.map((t2) => t2.label);
3828
3877
  }
3829
3878
  } else {
3830
3879
  const links = await this.pageStore.getNotePages(note.id);
3831
- if (links.length > 0) {
3832
- const [first] = [...links].sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
3833
- if (!pageCache.has(first.pageId)) {
3834
- pageCache.set(first.pageId, await this.pageStore.getPage(first.pageId));
3880
+ const pages = [];
3881
+ for (const link of links) {
3882
+ if (!pageCache.has(link.pageId)) {
3883
+ pageCache.set(link.pageId, await this.pageStore.getPage(link.pageId));
3835
3884
  }
3836
- page = pageCache.get(first.pageId);
3885
+ const page = pageCache.get(link.pageId);
3886
+ if (page) pages.push(page);
3837
3887
  }
3888
+ resolved = resolvePlacements(links, pages);
3838
3889
  }
3839
- containers[note.id] = page ? { kind: "page", id: page.id, ...page.slug ? { slug: page.slug } : {}, ...page.date ? { date: page.date } : {} } : { kind: "note", id: note.id };
3890
+ placements[note.id] = resolved;
3891
+ const primary = resolved[0];
3892
+ containers[note.id] = primary ? { kind: "page", id: primary.pageId, ...primary.slug ? { slug: primary.slug } : {}, ...primary.date ? { date: primary.date } : {} } : { kind: "note", id: note.id };
3840
3893
  }
3841
- return containers;
3894
+ return { containers, placements };
3842
3895
  }
3843
3896
  async hydrateBlocks(rows) {
3844
3897
  const ids = [...new Set(rows.map((row) => row.noteId))];
@@ -3897,20 +3950,9 @@ var QueryService = class {
3897
3950
  blocks = blocks.filter((b) => wanted.has(b.dataType));
3898
3951
  }
3899
3952
  }
3900
- for (const filter of parsed.filters) {
3901
- if (filter.key === "catalog") {
3902
- const wanted = new Set(filter.values.map((v) => v.value));
3903
- blocks = blocks.filter((b) => {
3904
- const cat = catalogOfItem(b);
3905
- if (!cat) return filter.negate;
3906
- const hit = wanted.has(cat);
3907
- return filter.negate ? !hit : hit;
3908
- });
3909
- }
3910
- }
3911
3953
  blocks = applyNotebookFilter(blocks, parsed.filters);
3912
3954
  for (const filter of parsed.filters) {
3913
- if (filter.key === "tags" && !filter.negate) {
3955
+ if (isTagLabelKey(filter.key) && !filter.negate) {
3914
3956
  const matchingNoteIds = /* @__PURE__ */ new Set();
3915
3957
  for (const v of filter.values) {
3916
3958
  const ids = await this.noteStore.getNoteIdsForTag(v.value);
@@ -3960,7 +4002,9 @@ var QueryService = class {
3960
4002
  return { parsed, notes: [], blocks, stages: { selected: selectedCount, matched: blocks.length } };
3961
4003
  }
3962
4004
  /**
3963
- * Execute a :effort query against the effort registry.
4005
+ * Execute a :effort query: the registry is the filter plane (discipline,
4006
+ * intensity, origin, text), the note plane is the result plane — matched
4007
+ * projections resolve to their owning definition notes.
3964
4008
  */
3965
4009
  async runFindEffort(parsed) {
3966
4010
  const all = await this.effortStore.getAllEfforts();
@@ -4009,7 +4053,17 @@ var QueryService = class {
4009
4053
  const offset = pipes.offset ?? 0;
4010
4054
  efforts = pipes.limit !== void 0 ? efforts.slice(offset, offset + pipes.limit) : efforts.slice(offset);
4011
4055
  }
4012
- return { parsed, notes: [], blocks: [], efforts, stages: { selected: selectedCount, matched: efforts.length } };
4056
+ const ownerIds = [...new Set(efforts.map((e) => e.ownerNoteId).filter((id) => typeof id === "string" && id.length > 0))];
4057
+ let notes = [];
4058
+ if (ownerIds.length > 0) {
4059
+ const fetched = this.noteStore.getNotesByIds ? await this.noteStore.getNotesByIds(ownerIds) : (await this.noteStore.getAllNotes()).filter((n) => ownerIds.includes(n.id));
4060
+ const byId = new Map(fetched.map((n) => [n.id, n]));
4061
+ notes = ownerIds.flatMap((id) => {
4062
+ const note = byId.get(id);
4063
+ return note ? [note] : [];
4064
+ });
4065
+ }
4066
+ return { parsed, notes, blocks: [], efforts, stages: { selected: selectedCount, matched: efforts.length } };
4013
4067
  }
4014
4068
  async run(parsed, options2 = {}) {
4015
4069
  if (isPipelineQuery(parsed)) {
@@ -4121,6 +4175,7 @@ var QueryService = class {
4121
4175
  ...content.runs?.length ? { runs: content.runs } : {},
4122
4176
  ...content.table ? { table: content.table } : {},
4123
4177
  ...content.containers ? { containers: content.containers } : {},
4178
+ ...content.placements ? { placements: content.placements } : {},
4124
4179
  ...parsed.sink ? { chart: parsed.sink } : {}
4125
4180
  };
4126
4181
  }
@@ -4493,139 +4548,293 @@ var QueryService = class {
4493
4548
  return new Set(links.map((l) => l.noteId));
4494
4549
  }
4495
4550
  /**
4496
- * Find catalogs: identifies catalogs whose member notes (or catalog landing
4497
- * notes) match the authored criteria (effort, tags, text, typed tags, etc.).
4551
+ * Execute a :notebook query (ticket 08) — the notebook plane is a NOTE
4552
+ * plane (ticket 05 semantics preserved): bare `:notebook{}` returns the
4553
+ * notes of every notebook — journal, playground, defaults, user-created,
4554
+ * feed-delivered. `notebook:{slug}` narrows to members of those
4555
+ * notebooks and `!notebook:` excludes them; the result shape never
4556
+ * changes with polarity, and container criteria (tags:, effort:, typed
4557
+ * tags, text:) narrow the members exactly like the note plane — this
4558
+ * delegates to it. The notebook CONTAINER listing (the /notebooks
4559
+ * management surface) is the app's NotebookService model, not WQL rows.
4498
4560
  */
4499
- async runFindCatalog(parsed, options2, allNotes) {
4561
+ async runFindNotebook(parsed, options2 = {}) {
4562
+ return this.runFind({ ...parsed, target: "note" }, options2);
4563
+ }
4564
+ /**
4565
+ * Execute a :page query (ticket 08) — the placement plane. Results are
4566
+ * the placed notes; `placements` carries every page each note sits on
4567
+ * (many-to-many) and `containers` the primary projection. `page:{slug}`
4568
+ * (or a `YYYY-MM-DD` date, or a page id) narrows the placements.
4569
+ */
4570
+ async runFindPage(parsed, options2 = {}) {
4571
+ const empty = { parsed, notes: [], blocks: [], stages: { selected: 0, matched: 0 } };
4572
+ if (!this.pageStore?.getAllPages || !this.pageStore?.getAllLinks) return empty;
4500
4573
  const ctx = runContext(options2);
4501
- const feedNotes = allNotes.filter((n) => sourceMatches(n, "feeds") || n.catalog && !n.sourceId);
4502
- let catalogNotes = feedNotes.filter((n) => n.type === "feed" || n.sourceId?.startsWith("page:collection:"));
4503
- const existingCatIds = new Set(catalogNotes.map((n) => catalogOfItem(n) ?? n.id));
4504
- for (const n of feedNotes) {
4505
- const cat = catalogOfItem(n);
4506
- if (cat && !existingCatIds.has(cat)) {
4507
- catalogNotes.push({
4508
- id: cat,
4509
- title: cat,
4510
- type: "feed",
4511
- sourceId: `page:collection:${cat}`,
4512
- catalog: cat,
4513
- createdAt: n.createdAt ?? 0,
4514
- rawContent: "",
4515
- sourceRevision: 1,
4516
- blocks: {}
4517
- });
4518
- existingCatIds.add(cat);
4574
+ const [pages, links] = await Promise.all([this.pageStore.getAllPages(), this.pageStore.getAllLinks()]);
4575
+ let placements = resolvePlacements(links, pages);
4576
+ const selected = new Set(placements.map((p) => p.noteId)).size;
4577
+ for (const filter of parsed.filters) {
4578
+ if (filter.key !== "page") continue;
4579
+ const wanted = new Set(filter.values.map((v) => v.value));
4580
+ placements = placements.filter((p) => {
4581
+ const hit = wanted.has(p.pageId) || p.slug !== void 0 && wanted.has(p.slug) || p.date !== void 0 && wanted.has(p.date);
4582
+ return filter.negate ? !hit : hit;
4583
+ });
4584
+ }
4585
+ const placedIds = [...new Set(placements.map((p) => p.noteId))];
4586
+ let notes = this.noteStore.getNotesByIds ? await this.noteStore.getNotesByIds(placedIds) : (await this.noteStore.getAllNotes()).filter((n) => placedIds.includes(n.id));
4587
+ for (const filter of parsed.filters) {
4588
+ if (filter.key === "note") {
4589
+ const wanted = new Set(filter.values.map((v) => v.value));
4590
+ notes = notes.filter((n) => filter.negate ? !wanted.has(n.id) : wanted.has(n.id));
4519
4591
  }
4520
4592
  }
4521
- const membersByCat = /* @__PURE__ */ new Map();
4522
- for (const catNote of catalogNotes) {
4523
- const catId = catalogOfItem(catNote) ?? catNote.id;
4524
- membersByCat.set(catId, [catNote]);
4525
- }
4526
- for (const n of feedNotes) {
4527
- const catId = catalogOfItem(n);
4528
- if (catId) {
4529
- const list = membersByCat.get(catId);
4530
- if (list) {
4531
- if (!list.includes(n)) list.push(n);
4532
- } else {
4533
- membersByCat.set(catId, [n]);
4534
- }
4593
+ for (const filter of parsed.filters) {
4594
+ if (filter.key === "text") {
4595
+ const search = filter.values.map((v) => v.value).join(" ").toLowerCase();
4596
+ notes = notes.filter((n) => {
4597
+ const hit = n.title.toLowerCase().includes(search) || n.rawContent.toLowerCase().includes(search);
4598
+ return filter.negate ? !hit : hit;
4599
+ });
4535
4600
  }
4536
4601
  }
4537
- const criteriaFilters = parsed.filters.filter(
4538
- (f) => !(f.key === "source" && !f.negate && f.values.some((v) => v.value === "feeds" || v.value === "feed" || v.value === "notebooks" || v.value === "notebook")) && !(f.key === "type" && !f.negate && f.values.some((v) => v.value === "feed"))
4539
- );
4540
- let matchingCatalogs = [...catalogNotes];
4541
- for (const filter of criteriaFilters) {
4542
- if (filter.key === "catalog" || filter.key === "notebook") {
4543
- const wanted = new Set(filter.values.map((v) => notebookSlugFromId(v.value)));
4544
- matchingCatalogs = matchingCatalogs.filter((c) => {
4545
- const catId = notebookSlugFromId(catalogOfItem(c) ?? c.id);
4546
- const hit = wanted.has(catId);
4602
+ notes = applyNotebookFilter(notes, parsed.filters);
4603
+ if (parsed.window || options2.range) {
4604
+ notes = notes.filter((n) => effectiveTimeWindow(n.date ?? n.createdAt, parsed.window, options2.range, ctx));
4605
+ }
4606
+ notes = applyOrderAndPage(notes, parsed);
4607
+ const survived = new Set(notes.map((n) => n.id));
4608
+ const byNote = /* @__PURE__ */ new Map();
4609
+ for (const p of placements) {
4610
+ if (!survived.has(p.noteId)) continue;
4611
+ const list = byNote.get(p.noteId);
4612
+ if (list) list.push(p);
4613
+ else byNote.set(p.noteId, [p]);
4614
+ }
4615
+ const containers = {};
4616
+ for (const note of notes) {
4617
+ const primary = byNote.get(note.id)?.[0];
4618
+ containers[note.id] = primary ? { kind: "page", id: primary.pageId, ...primary.slug ? { slug: primary.slug } : {}, ...primary.date ? { date: primary.date } : {} } : { kind: "note", id: note.id };
4619
+ }
4620
+ return {
4621
+ parsed,
4622
+ notes,
4623
+ blocks: [],
4624
+ containers,
4625
+ placements: Object.fromEntries(byNote),
4626
+ stages: { selected, matched: notes.length }
4627
+ };
4628
+ }
4629
+ /**
4630
+ * Execute a :tag query (ticket 08) — the tag plane. Every tag record
4631
+ * surfaces as one synthetic note row, so results are notes end to end.
4632
+ * `tag:{label}` narrows; `type:` narrows by the tag's configured type;
4633
+ * `contributes:` narrows by what a tag contributes (`metric`,
4634
+ * `metric:<type>`, `tag:<slug>`, `function:<name>`) — a contribution is
4635
+ * exposed here, never as a `tags:` label filter. The synthetic row's body
4636
+ * summarizes the contributions so `text:` reaches them too.
4637
+ */
4638
+ async runFindTag(parsed, options2 = {}) {
4639
+ const ctx = runContext(options2);
4640
+ const tags = this.tagsStore.getAll ? await this.tagsStore.getAll() : [];
4641
+ const selected = tags.length;
4642
+ let matched = tags;
4643
+ for (const filter of parsed.filters) {
4644
+ if (isTagLabelKey(filter.key)) {
4645
+ const wanted = new Set(filter.values.map((v) => v.value.toLowerCase()));
4646
+ matched = matched.filter((t2) => {
4647
+ const hit = typeof t2.label === "string" && wanted.has(t2.label.toLowerCase());
4547
4648
  return filter.negate ? !hit : hit;
4548
4649
  });
4549
- } else if (filter.key === "effort") {
4550
- const matchingIds = /* @__PURE__ */ new Set();
4551
- for (const v of filter.values) {
4552
- const ids = await this.getNoteIdsForEffort(v.value);
4553
- ids.forEach((id) => matchingIds.add(id));
4554
- }
4555
- matchingCatalogs = matchingCatalogs.filter((c) => {
4556
- const catId = catalogOfItem(c) ?? c.id;
4557
- const members = membersByCat.get(catId) ?? [];
4558
- const hit = members.some((m) => matchingIds.has(m.id));
4650
+ } else if (filter.key === "type") {
4651
+ const wanted = new Set(filter.values.map((v) => v.value));
4652
+ matched = matched.filter((t2) => {
4653
+ const hit = t2.type !== void 0 && wanted.has(t2.type);
4559
4654
  return filter.negate ? !hit : hit;
4560
4655
  });
4561
- } else if (filter.key === "tags") {
4656
+ } else if (filter.key === "contributes") {
4657
+ matched = matched.filter((t2) => {
4658
+ const hit = (t2.contributions ?? []).some((c) => filter.values.some((v) => tagContributionMatches(c, v.value)));
4659
+ return filter.negate ? !hit : hit;
4660
+ });
4661
+ } else if (filter.key === "text") {
4662
+ const search = filter.values.map((v) => v.value).join(" ").toLowerCase();
4663
+ matched = matched.filter((t2) => filter.negate !== `${t2.label ?? ""} ${tagContributionSummary(t2)}`.toLowerCase().includes(search));
4664
+ }
4665
+ }
4666
+ let rows = matched.map((t2) => ({
4667
+ id: `tag/${typeof t2.type === "string" && t2.type ? t2.type : "general"}/${t2.label}`,
4668
+ title: t2.label,
4669
+ type: "tag",
4670
+ tagType: typeof t2.type === "string" ? t2.type : void 0,
4671
+ contributions: t2.contributions ?? [],
4672
+ createdAt: typeof t2.createdAt === "number" ? t2.createdAt : 0,
4673
+ content: tagContributionSummary(t2),
4674
+ rawContent: tagContributionSummary(t2),
4675
+ sourceRevision: 1,
4676
+ blocks: {}
4677
+ }));
4678
+ if (parsed.window || options2.range) {
4679
+ rows = rows.filter((r) => effectiveTimeWindow(r.createdAt, parsed.window, options2.range, ctx));
4680
+ }
4681
+ rows = applyOrderAndPage(rows, parsed);
4682
+ return { parsed, notes: rows, blocks: [], stages: { selected, matched: rows.length } };
4683
+ }
4684
+ /**
4685
+ * Execute an :equipment query (ticket 08) — the equipment plane. Results
4686
+ * are the equipment notes (the equipment notebook's members, typed
4687
+ * 'equipment' rows included). `equipment:{label}` narrows by the
4688
+ * equipment typed tag ("every equipment note tagged kettlebell" is
4689
+ * `:equipment{equipment:kettlebell}`); `tags:` and `text:` narrow like
4690
+ * the note plane.
4691
+ */
4692
+ async runFindEquipment(parsed, options2 = {}) {
4693
+ const allNotes = await this.noteStore.getAllNotes();
4694
+ const ctx = runContext(options2);
4695
+ let notes = allNotes.filter((n) => notebookOfItem(n) === "notebook/equipment" || n.type === "equipment" || sourceMatches(n, "equipment"));
4696
+ const selected = notes.length;
4697
+ for (const filter of parsed.filters) {
4698
+ if (filter.key === "equipment") {
4562
4699
  const matchingIds = /* @__PURE__ */ new Set();
4563
4700
  for (const v of filter.values) {
4564
- const ids = await this.noteStore.getNoteIdsForTag(v.value);
4565
- ids.forEach((id) => matchingIds.add(id));
4701
+ (await this.getNoteIdsForTypedTag("equipment", v.value)).forEach((id) => matchingIds.add(id));
4566
4702
  }
4567
- matchingCatalogs = matchingCatalogs.filter((c) => {
4568
- const catId = catalogOfItem(c) ?? c.id;
4569
- const members = membersByCat.get(catId) ?? [];
4570
- const hit = members.some((m) => matchingIds.has(m.id) || m.tags && filter.values.some((v) => m.tags.includes(v.value)));
4571
- return filter.negate ? !hit : hit;
4572
- });
4573
- } else if (WQL_TYPED_TAG_KEYS.includes(filter.key)) {
4703
+ notes = notes.filter((n) => filter.negate ? !matchingIds.has(n.id) : matchingIds.has(n.id));
4704
+ } else if (isTagLabelKey(filter.key) && !filter.negate) {
4574
4705
  const matchingIds = /* @__PURE__ */ new Set();
4575
4706
  for (const v of filter.values) {
4576
- const ids = await this.getNoteIdsForTypedTag(filter.key, v.value);
4577
- ids.forEach((id) => matchingIds.add(id));
4707
+ (await this.noteStore.getNoteIdsForTag(v.value)).forEach((id) => matchingIds.add(id));
4578
4708
  }
4579
- matchingCatalogs = matchingCatalogs.filter((c) => {
4580
- const catId = catalogOfItem(c) ?? c.id;
4581
- const members = membersByCat.get(catId) ?? [];
4582
- const hit = members.some((m) => matchingIds.has(m.id));
4583
- return filter.negate ? !hit : hit;
4584
- });
4585
- } else if (filter.key === "text") {
4709
+ notes = notes.filter((n) => matchingIds.has(n.id));
4710
+ } else if (isTagLabelKey(filter.key) && filter.negate) {
4711
+ for (const v of filter.values) {
4712
+ const ids = await this.noteStore.getNoteIdsForTag(v.value);
4713
+ notes = notes.filter((n) => !ids.has(n.id));
4714
+ }
4715
+ }
4716
+ }
4717
+ for (const filter of parsed.filters) {
4718
+ if (filter.key === "text") {
4586
4719
  const search = filter.values.map((v) => v.value).join(" ").toLowerCase();
4587
- matchingCatalogs = matchingCatalogs.filter((c) => {
4588
- const catId = catalogOfItem(c) ?? c.id;
4589
- const members = membersByCat.get(catId) ?? [];
4590
- const hit = members.some((m) => m.title.toLowerCase().includes(search) || m.rawContent.toLowerCase().includes(search));
4591
- return filter.negate ? !hit : hit;
4592
- });
4593
- } else if (filter.key === "note") {
4594
- const wanted = new Set(filter.values.map((v) => v.value));
4595
- matchingCatalogs = matchingCatalogs.filter((c) => {
4596
- const catId = catalogOfItem(c) ?? c.id;
4597
- const members = membersByCat.get(catId) ?? [];
4598
- const hit = members.some((m) => wanted.has(m.id));
4720
+ notes = notes.filter((n) => {
4721
+ const hit = n.title.toLowerCase().includes(search) || n.rawContent.toLowerCase().includes(search);
4599
4722
  return filter.negate ? !hit : hit;
4600
4723
  });
4601
4724
  }
4602
4725
  }
4726
+ notes = applyNotebookFilter(notes, parsed.filters);
4603
4727
  if (parsed.window || options2.range) {
4604
- matchingCatalogs = matchingCatalogs.filter((c) => {
4605
- const catId = catalogOfItem(c) ?? c.id;
4606
- const members = membersByCat.get(catId) ?? [];
4607
- return members.some((m) => effectiveTimeWindow(m.date ?? m.createdAt, parsed.window, options2.range, ctx));
4608
- });
4609
- }
4610
- if (parsed.pipes?.order) {
4611
- for (const order of [...parsed.pipes.order].reverse()) {
4612
- const dir = order.dir === "desc" ? -1 : 1;
4613
- matchingCatalogs = [...matchingCatalogs].sort((a, b) => {
4614
- const av = a[order.col] ?? "";
4615
- const bv = b[order.col] ?? "";
4616
- if (av === bv) return 0;
4617
- return (av > bv ? 1 : -1) * dir;
4618
- });
4619
- }
4728
+ notes = notes.filter((n) => effectiveTimeWindow(n.date ?? n.createdAt, parsed.window, options2.range, ctx));
4620
4729
  }
4621
- return {
4622
- parsed,
4623
- notes: matchingCatalogs,
4624
- blocks: [],
4625
- stages: { selected: catalogNotes.length, matched: matchingCatalogs.length }
4626
- };
4730
+ notes = applyOrderAndPage(notes, parsed);
4731
+ return { parsed, notes, blocks: [], stages: { selected, matched: notes.length } };
4627
4732
  }
4628
4733
  };
4734
+ function applyOrderAndPage(rows, parsed) {
4735
+ let out = rows;
4736
+ if (parsed.pipes?.order) {
4737
+ for (const order of [...parsed.pipes.order].reverse()) {
4738
+ const dir = order.dir === "desc" ? -1 : 1;
4739
+ out = [...out].sort((a, b) => {
4740
+ const av = a[order.col] ?? "";
4741
+ const bv = b[order.col] ?? "";
4742
+ if (av === bv) return 0;
4743
+ return (av > bv ? 1 : -1) * dir;
4744
+ });
4745
+ }
4746
+ }
4747
+ const pipes = parsed.pipes;
4748
+ if (pipes && (pipes.limit !== void 0 || (pipes.offset ?? 0) > 0)) {
4749
+ const offset = pipes.offset ?? 0;
4750
+ out = pipes.limit !== void 0 ? out.slice(offset, offset + pipes.limit) : out.slice(offset);
4751
+ }
4752
+ return out;
4753
+ }
4754
+ function isTagLabelKey(key) {
4755
+ return key === "tags" || key === "tag";
4756
+ }
4757
+ function tagContributionMatches(contribution, value) {
4758
+ const sep = value.indexOf(":");
4759
+ const kind = sep === -1 ? value : value.slice(0, sep);
4760
+ const qualifier = sep === -1 ? "" : value.slice(sep + 1);
4761
+ if (contribution.kind !== kind) return false;
4762
+ if (!qualifier) return true;
4763
+ if (contribution.kind === "metric") return contribution.type === qualifier;
4764
+ if (contribution.kind === "tag") return contribution.slug === qualifier;
4765
+ return contribution.name === qualifier;
4766
+ }
4767
+ function tagContributionSummary(tag) {
4768
+ const parts = (tag.contributions ?? []).map((c) => c.kind === "metric" ? `metric ${c.type}` : c.kind === "tag" ? `tag ${c.slug}` : `function ${c.name}`);
4769
+ return parts.length ? `contributes ${parts.join(", ")}` : "";
4770
+ }
4771
+
4772
+ // src/notebookVocabulary.ts
4773
+ function migrateLegacyChunkId(chunkId) {
4774
+ if (chunkId.startsWith("collection.")) return `notebook.${chunkId.slice("collection.".length)}`;
4775
+ if (chunkId.startsWith("feed.")) return `notebook.${chunkId.slice("feed.".length)}`;
4776
+ return chunkId;
4777
+ }
4778
+ function migrateLegacySourcePath(path) {
4779
+ if (path.startsWith("markdown/collections/")) return `markdown/notebooks/${path.slice("markdown/collections/".length)}`;
4780
+ if (path.startsWith("markdown/feeds/")) return `markdown/notebooks/${path.slice("markdown/feeds/".length)}`;
4781
+ return path;
4782
+ }
4783
+ function migrateLegacySourceId(sourceId) {
4784
+ if (sourceId.startsWith("page:collection:")) return `page:${sourceId.slice("page:collection:".length)}`;
4785
+ if (sourceId.startsWith("collection:")) return `notebook:${sourceId.slice("collection:".length)}`;
4786
+ if (sourceId.startsWith("feed:")) {
4787
+ return `notebook:${sourceId.slice("feed:".length).replace(/^feeds\//, "")}`;
4788
+ }
4789
+ return sourceId;
4790
+ }
4791
+ function migrateLegacyRouteId(routeId) {
4792
+ return routeId.startsWith("feeds/") ? routeId.slice("feeds/".length) : routeId;
4793
+ }
4794
+ function migrateLegacyRow(row) {
4795
+ let next = row;
4796
+ const apply = (patch) => {
4797
+ next = { ...next, ...patch };
4798
+ };
4799
+ const sourceId = row["sourceId"];
4800
+ if (typeof sourceId === "string") {
4801
+ const migrated = migrateLegacySourceId(sourceId);
4802
+ if (migrated !== sourceId) apply({ sourceId: migrated });
4803
+ }
4804
+ const sourcePath = row["sourcePath"];
4805
+ if (typeof sourcePath === "string") {
4806
+ const migrated = migrateLegacySourcePath(sourcePath);
4807
+ if (migrated !== sourcePath) apply({ sourcePath: migrated });
4808
+ }
4809
+ const seedChunkId = row["seedChunkId"];
4810
+ if (typeof seedChunkId === "string") {
4811
+ const migrated = migrateLegacyChunkId(seedChunkId);
4812
+ if (migrated !== seedChunkId) apply({ seedChunkId: migrated });
4813
+ }
4814
+ const routeId = row["routeId"];
4815
+ if (typeof routeId === "string") {
4816
+ const migrated = migrateLegacyRouteId(routeId);
4817
+ if (migrated !== routeId) apply({ routeId: migrated });
4818
+ }
4819
+ if ("catalog" in next) {
4820
+ next = { ...next };
4821
+ delete next["catalog"];
4822
+ }
4823
+ if (next["type"] === "feed") apply({ type: "note" });
4824
+ return next;
4825
+ }
4826
+ function hasLegacyIdentity(row) {
4827
+ const sourceId = row["sourceId"];
4828
+ if (typeof sourceId === "string" && (/^(collection|feed):/.test(sourceId) || sourceId.startsWith("page:collection:"))) return true;
4829
+ const sourcePath = row["sourcePath"];
4830
+ if (typeof sourcePath === "string" && (sourcePath.startsWith("markdown/collections/") || sourcePath.startsWith("markdown/feeds/"))) return true;
4831
+ const seedChunkId = row["seedChunkId"];
4832
+ if (typeof seedChunkId === "string" && /^(collection|feed)\./.test(seedChunkId)) return true;
4833
+ const routeId = row["routeId"];
4834
+ if (typeof routeId === "string" && routeId.startsWith("feeds/")) return true;
4835
+ if ("catalog" in row || row["type"] === "feed") return true;
4836
+ return false;
4837
+ }
4629
4838
 
4630
4839
  // src/dashboard/model.ts
4631
4840
  var DASHBOARD_WIDGET_TYPES = [
@@ -5268,15 +5477,15 @@ ${query}
5268
5477
  }
5269
5478
 
5270
5479
  // src/version.ts
5271
- var VERSION = "0.44.2160";
5272
- var GIT_SHA = "d191f5e";
5273
- var BUILD_TIME = "2026-10-24T23:21:37.364Z";
5480
+ var VERSION = "0.45.2166";
5481
+ var GIT_SHA = "32bda10";
5482
+ var BUILD_TIME = "2026-10-26T20:01:09.868Z";
5274
5483
  var SEMVER = {
5275
- version: "0.44.2160",
5276
- gitSha: "d191f5e",
5277
- buildTime: "2026-10-24T23:21:37.364Z"
5484
+ version: "0.45.2166",
5485
+ gitSha: "32bda10",
5486
+ buildTime: "2026-10-26T20:01:09.868Z"
5278
5487
  };
5279
5488
 
5280
- export { BUILD_TIME, CONTENT_GROUPING_DIMENSIONS, DASHBOARD_GRID_MAX_COLS, DASHBOARD_WIDGET_TYPES, DEFAULT_DASHBOARD_TITLE, DISCIPLINE_FACTORS, EFFORT_DISCIPLINES, GIT_SHA, PLANNED_WIDGET_TYPES, PageSourceRegistry, QueryDocumentRunner, QueryService, SEMVER, QueryDocumentRunner2 as SharedQueryDocumentRunner, UNASSIGNED, VERSION, WQL_AGGREGATORS, WQL_CALC_TARGETS, WQL_CHART_HEADS, WQL_COMPARISON_OPS, WQL_CONTENT_FILTER_KEYS, WQL_CONTENT_ONLY_KEYS, WQL_DISPLAY_UNITS, WQL_EFFORT_FILTER_KEYS, WQL_FIND_TARGETS, WQL_FUNCTION_HEADS, WQL_GRAINS, WQL_INTENSITY_TIERS, WQL_KEYWORDS, WQL_METRIC_AGGREGATES, WQL_METRIC_FAMILIES, WQL_NOTE_DEFAULT_SOURCES, WQL_RESULT_PLANES, WQL_ROLLUP_PERIODS, WQL_SOURCES, WQL_SOURCE_HEADS, WQL_SOURCE_HEAD_SCOPES, WQL_SOURCE_VALUES, WQL_STANDARD_DATASETS, WQL_TAG_KEYS, WQL_TYPED_TAG_KEYS, WQL_TYPE_HEADS, WQL_VIRTUAL_DIMS, allowedFilterTypesForTarget, appendSuffixes, appendWidget, buildDashboardDocument, buildDashboardScaffold, buildDrillDownQuery, captureContext, catalogOfItem, civilDateAdd, civilDateDiff, civilDateOf, civilDatesCoveredByRange, civilMonday, consumesRollupFacts, convertDisplay, createBuiltinFormulaEvaluator, dashboardSlug, defaultTokenValues, disciplineFactorFor, duplicateWidget, eventsToStoredLogs, extractDashboardTokens, extractFrontmatterTags, findTargetAdvisories, getList, getScalar, getUnitFamily, inRange, isAggregateQuery, isDashboardMeta, isDashboardWidgetType, isEffortDiscipline, isFindQuery, isPipelineQuery, isProposedMetric, migrateLegacyDashboardBodies, moveWidget, normalizeSummaryFacts, normalizeWql, notebookOfItem, parseDashboardNote, parseDocument, parseFlatProperties, parseFrontmatter, parseFrontmatterBody, parseFrontmatterCategories, parseFrontmatterProps, parseQuery, parseQueryWidgetSuffix, parseWqlSuffixes, projectEventToFacts, referencedTokens, removeWidget, renderWidgetGroup, resizeWidget, resolveCanonicalMetricKey, resolveOutputUnit, resolveWidgetType, resolveWindowRange, rowsParseError, serialize, serializeDocument, serializeFrontmatter, setDashboardTokenValue, sourceMatches, splitAtWhere, stripFrontmatter, substituteTokens2 as substituteTokens, substituteTokens as substituteTokensRaw, supportsWqlFilterKey, systemTimeZone, toEventRows, toSummaryEventRows, unknownTokensMessage, unknownWidgetTypeMessage, updateWidget, widgetBodyLine, widgetFenceTag, wql, wqlCompletion, wqlCompletionSource, wqlFilterKeys, wqlGroupingDimensions, wqlHighlightStyle, wqlLanguage, parser as wqlParser, wql_parser_terms_exports as wqlTerms, zonedNoon, zonedStartOfDay };
5489
+ export { BUILD_TIME, CONTENT_GROUPING_DIMENSIONS, DASHBOARD_GRID_MAX_COLS, DASHBOARD_WIDGET_TYPES, DEFAULT_DASHBOARD_TITLE, DISCIPLINE_FACTORS, EFFORT_DISCIPLINES, GIT_SHA, PLANNED_WIDGET_TYPES, PageSourceRegistry, QueryDocumentRunner, QueryService, SEMVER, SOURCE_ID_SHAPES, QueryDocumentRunner2 as SharedQueryDocumentRunner, UNASSIGNED, VERSION, WQL_AGGREGATORS, WQL_CALC_TARGETS, WQL_CHART_HEADS, WQL_COMPARISON_OPS, WQL_CONTENT_FILTER_KEYS, WQL_CONTENT_ONLY_KEYS, WQL_DISPLAY_UNITS, WQL_EFFORT_FILTER_KEYS, WQL_FIND_TARGETS, WQL_FUNCTION_HEADS, WQL_GRAINS, WQL_INTENSITY_TIERS, WQL_KEYWORDS, WQL_METRIC_AGGREGATES, WQL_METRIC_FAMILIES, WQL_NOTE_DEFAULT_SOURCES, WQL_NOTE_HEAD_ALIASES, WQL_RESULT_PLANES, WQL_ROLLUP_PERIODS, WQL_SOURCES, WQL_SOURCE_HEADS, WQL_SOURCE_HEAD_SCOPES, WQL_SOURCE_VALUES, WQL_STANDARD_DATASETS, WQL_TAG_KEYS, WQL_TYPED_TAG_KEYS, WQL_TYPE_HEADS, WQL_VIRTUAL_DIMS, allowedFilterTypesForTarget, appendSuffixes, appendWidget, buildDashboardDocument, buildDashboardScaffold, buildDrillDownQuery, captureContext, civilDateAdd, civilDateDiff, civilDateOf, civilDatesCoveredByRange, civilMonday, consumesRollupFacts, convertDisplay, createBuiltinFormulaEvaluator, dashboardSlug, defaultTokenValues, disciplineFactorFor, duplicateWidget, eventsToStoredLogs, extractDashboardTokens, extractFrontmatterTags, feedOfItem, findTargetAdvisories, getList, getScalar, getUnitFamily, hasLegacyIdentity, inRange, isAggregateQuery, isDashboardMeta, isDashboardWidgetType, isEffortDiscipline, isFindQuery, isPipelineQuery, isProposedMetric, migrateLegacyChunkId, migrateLegacyDashboardBodies, migrateLegacyRouteId, migrateLegacyRow, migrateLegacySourceId, migrateLegacySourcePath, moveWidget, normalizeSummaryFacts, normalizeWql, notebookOfItem, notebookOriginOfItem, notebookSlugOfSourceId, parseDashboardNote, parseDocument, parseFlatProperties, parseFrontmatter, parseFrontmatterBody, parseFrontmatterCategories, parseFrontmatterProps, parseQuery, parseQueryWidgetSuffix, parseWqlSuffixes, projectEventToFacts, referencedTokens, removeWidget, renderWidgetGroup, resizeWidget, resolveCanonicalMetricKey, resolveOutputUnit, resolveWidgetType, resolveWindowRange, rowsParseError, serialize, serializeDocument, serializeFrontmatter, setDashboardTokenValue, sourceMatches, sourceShape, splitAtWhere, stripFrontmatter, substituteTokens2 as substituteTokens, substituteTokens as substituteTokensRaw, supportsWqlFilterKey, systemTimeZone, toEventRows, toSummaryEventRows, unknownTokensMessage, unknownWidgetTypeMessage, updateWidget, widgetBodyLine, widgetFenceTag, wql, wqlCompletion, wqlCompletionSource, wqlFilterKeys, wqlGroupingDimensions, wqlHighlightStyle, wqlLanguage, parser as wqlParser, wql_parser_terms_exports as wqlTerms, zonedNoon, zonedStartOfDay };
5281
5490
  //# sourceMappingURL=index.mjs.map
5282
5491
  //# sourceMappingURL=index.mjs.map