@bitcobblers/wod-wiki-wql 0.50.2188 → 0.50.2190

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
@@ -136,9 +136,9 @@ var TARGET_TYPES = {
136
136
  note: /* @__PURE__ */ new Set(["text", "notebook", "tag", "effort", ...WQL_TYPED_TAG_KEYS, "type", "page", "source", "note", "time", "groupby", "where", "feed", "subscribed"]),
137
137
  block: /* @__PURE__ */ new Set(["text", "notebook", "tag", "effort", "type", "source", "note", "time", "groupby", "where", "feed", "subscribed"]),
138
138
  effort: /* @__PURE__ */ new Set([...WQL_EFFORT_FILTER_KEYS, "groupby"]),
139
- session: /* @__PURE__ */ new Set(["result", "block", "note", "plane", "time", "groupby"]),
140
- segment: /* @__PURE__ */ new Set([...TABLE_KEYS, "time", "groupby", "unit"]),
141
- event: /* @__PURE__ */ new Set([...TABLE_KEYS, "time", "groupby", "unit"]),
139
+ session: /* @__PURE__ */ new Set(["result", "block", "note", "plane", ...FACT_KEYS.map((key) => key === "tags" ? "tag" : key), "time", "groupby"]),
140
+ segment: /* @__PURE__ */ new Set([...TABLE_KEYS, "tag", "time", "groupby", "unit"]),
141
+ event: /* @__PURE__ */ new Set([...TABLE_KEYS, "tag", "time", "groupby", "unit"]),
142
142
  // Container planes (ticket 08). `:notebook{}` delegates to the note plane
143
143
  // (see the notebook entry above); `:page{}` lists the page placements;
144
144
  // `:tag{}` the tags (results are notes); `:equipment{}` the equipment
@@ -173,7 +173,7 @@ function wqlGroupingDimensions(target, family) {
173
173
  function supportsWqlFilterKey(target, family, rawKey) {
174
174
  const key = rawKey === "tag" ? "tags" : rawKey;
175
175
  if (family === "aggregate" || target === "segment" || target === "event") {
176
- return !WQL_CONTENT_ONLY_KEYS.includes(key) && key !== "plane" && !(family === "find" && key === "tags");
176
+ return !WQL_CONTENT_ONLY_KEYS.includes(key) && key !== "plane";
177
177
  }
178
178
  return wqlFilterKeys(target, family).includes(key);
179
179
  }
@@ -3552,6 +3552,14 @@ var QueryService = class {
3552
3552
  }
3553
3553
  groups.sort((a, b) => sessionCompletedAt(b[1]) - sessionCompletedAt(a[1]));
3554
3554
  const planeFilters = parsed.filters.filter((f) => f.key === "plane");
3555
+ const factFilters = parsed.filters.filter((f) => !["result", "block", "note", "plane"].includes(f.key));
3556
+ const touchesTags = factFilters.some((f) => isTagLabelKey(f.key));
3557
+ let sessionNoteTags = /* @__PURE__ */ new Map();
3558
+ if (touchesTags && groups.length > 0) {
3559
+ const dummyPoints = groups.map(([, rows]) => ({ noteId: rows[0]?.noteId ?? "" }));
3560
+ sessionNoteTags = await this.loadNoteTags(dummyPoints, true);
3561
+ }
3562
+ const normalizedFactFilters = factFilters.map((f) => f.key === "tag" ? { ...f, key: "tags" } : f);
3555
3563
  let runs = groups.map(([resultId, rows]) => {
3556
3564
  let events = rows;
3557
3565
  if (planeFilters.length > 0) {
@@ -3562,6 +3570,13 @@ var QueryService = class {
3562
3570
  })
3563
3571
  );
3564
3572
  }
3573
+ if (normalizedFactFilters.length > 0) {
3574
+ const matchingSession = events.some((row) => {
3575
+ const facts = projectEventToFacts(row);
3576
+ return facts.some((fact) => matchesFilters(fact, normalizedFactFilters, sessionNoteTags));
3577
+ });
3578
+ if (!matchingSession) events = [];
3579
+ }
3565
3580
  return {
3566
3581
  resultId,
3567
3582
  noteId: rows[0].noteId,
@@ -3650,8 +3665,10 @@ var QueryService = class {
3650
3665
  if (temporal?.temporalKind === "civil-date" && temporal.civilDate) return zonedNoon(temporal.civilDate, ctx.timeZone);
3651
3666
  return row.timestamp;
3652
3667
  };
3653
- const noteTags = /* @__PURE__ */ new Map();
3654
- const tagFilters = parsed.filters.filter((f) => !["result", "block", "note", "plane"].includes(f.key));
3668
+ const rawTagFilters = parsed.filters.filter((f) => !["result", "block", "note", "plane"].includes(f.key));
3669
+ const tagFilters = rawTagFilters.map((f) => f.key === "tag" ? { ...f, key: "tags" } : f);
3670
+ const touchesTags = tagFilters.some((f) => f.key === "tags");
3671
+ const noteTags = touchesTags && eligibleRecords.length > 0 ? await this.loadNoteTags(eligibleRecords.map((r) => ({ noteId: r.noteId })), true) : /* @__PURE__ */ new Map();
3655
3672
  const timeScoped = !!(parsed.window || options2.range);
3656
3673
  const eligible = eligibleRecords.filter((row) => !timeScoped || effectiveTimeWindow(anchorTs(row), parsed.window, options2.range, ctx)).filter((row) => {
3657
3674
  if (tagFilters.length === 0) return true;
@@ -5144,152 +5161,9 @@ function buildDashboardDocument(sections, meta) {
5144
5161
  };
5145
5162
  }
5146
5163
 
5147
- // src/dashboard/frontmatter.ts
5148
- var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
5149
- function unquote(value) {
5150
- return value.replace(
5151
- /^(['"])(.*)\1$/,
5152
- (_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
5153
- );
5154
- }
5155
- function parseFrontmatter(raw) {
5156
- const match = raw.match(FRONTMATTER_RE);
5157
- if (!match) return { meta: {}, body: raw };
5158
- return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
5159
- }
5160
- function parseMetaLines(lines) {
5161
- const meta = {};
5162
- for (let i = 0; i < lines.length; i++) {
5163
- const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
5164
- if (!keyMatch) continue;
5165
- const key = keyMatch[1];
5166
- const rawVal = keyMatch[2].trim();
5167
- if (rawVal === "") {
5168
- const items = [];
5169
- while (i + 1 < lines.length) {
5170
- const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
5171
- if (!itemMatch) break;
5172
- items.push(itemMatch[1].trim());
5173
- i++;
5174
- }
5175
- if (items.length > 0) {
5176
- meta[key] = items;
5177
- } else if (key === "metrics" || key === "summaries") {
5178
- while (i + 1 < lines.length) {
5179
- const childLine = lines[i + 1];
5180
- if (/^\s*(?:#.*)?$/.test(childLine)) {
5181
- i++;
5182
- continue;
5183
- }
5184
- const childMatch = childLine.match(/^\s{2,}([A-Za-z][\w.-]*)\s*:(.*)$/);
5185
- if (!childMatch) break;
5186
- i++;
5187
- const childKey = `${key}.${childMatch[1]}`;
5188
- const childRaw = childMatch[2].trim();
5189
- if (childRaw === "") {
5190
- const childItems = [];
5191
- while (i + 1 < lines.length) {
5192
- const listMatch = lines[i + 1].match(/^\s{4,}-\s+(.+)$/);
5193
- if (!listMatch) break;
5194
- childItems.push(listMatch[1].trim());
5195
- i++;
5196
- }
5197
- meta[childKey] = childItems.length > 0 ? childItems : "";
5198
- } else {
5199
- const wasQuoted = /^(['"])(.*)\1$/.test(childRaw);
5200
- const value = unquote(childRaw);
5201
- const num = Number(value);
5202
- meta[childKey] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
5203
- }
5204
- }
5205
- } else {
5206
- meta[key] = "";
5207
- }
5208
- } else {
5209
- const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
5210
- const value = unquote(rawVal);
5211
- const num = Number(value);
5212
- meta[key] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
5213
- }
5214
- }
5215
- return meta;
5216
- }
5217
- function parseFrontmatterBody(innerContent) {
5218
- return parseMetaLines(innerContent.split(/\r?\n/));
5219
- }
5220
- function quoteYamlScalar(value) {
5221
- if (value === "") return '""';
5222
- const looksNumeric = !isNaN(Number(value));
5223
- const looksKeyword = /^(true|false|null|yes|no|on|off)$/i.test(value);
5224
- if (/[":'\n#{}[\\],&*?|<>=%!@`]/.test(value) || value !== value.trim() || value.startsWith("-") || looksNumeric || looksKeyword) {
5225
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
5226
- }
5227
- return value;
5228
- }
5229
- function serializeFrontmatter(meta) {
5230
- const lines = [];
5231
- for (const [key, value] of Object.entries(meta)) {
5232
- if (Array.isArray(value)) {
5233
- if (value.length === 0) {
5234
- lines.push(`${key}: ""`);
5235
- continue;
5236
- }
5237
- lines.push(`${key}:`);
5238
- for (const item of value) {
5239
- lines.push(` - ${quoteYamlScalar(item)}`);
5240
- }
5241
- } else if (typeof value === "number") {
5242
- lines.push(`${key}: ${value}`);
5243
- } else {
5244
- lines.push(`${key}: ${quoteYamlScalar(value)}`);
5245
- }
5246
- }
5247
- return lines.join("\n");
5248
- }
5249
- function stripFrontmatter(raw) {
5250
- return parseFrontmatter(raw).body;
5251
- }
5252
- function getScalar(meta, key) {
5253
- const value = meta[key];
5254
- return typeof value === "string" || typeof value === "number" ? value : void 0;
5255
- }
5256
- function getList(meta, key) {
5257
- const value = meta[key];
5258
- return Array.isArray(value) ? value : [];
5259
- }
5260
- function parseFrontmatterCategories(raw) {
5261
- return getList(parseFrontmatter(raw).meta, "category").map((c) => c.toLowerCase());
5262
- }
5263
- function extractFrontmatterTags(raw) {
5264
- const meta = FRONTMATTER_RE.test(raw) ? parseFrontmatter(raw).meta : parseFrontmatterBody(raw);
5265
- const tags = meta["tags"];
5266
- const list = Array.isArray(tags) ? tags : typeof tags === "string" && tags.trim() ? tags.trim().startsWith("[") && tags.trim().endsWith("]") ? tags.trim().slice(1, -1).split(",") : [tags] : [];
5267
- return [...new Set(list.map((tag) => tag.trim()).filter(Boolean))];
5268
- }
5269
- function parseFlatProperties(innerContent) {
5270
- const props = {};
5271
- for (const line of innerContent.split(/\r?\n/)) {
5272
- const match = line.match(/^([^:]+):\s*(.*)$/);
5273
- if (match) {
5274
- props[match[1].trim()] = match[2].trim();
5275
- }
5276
- }
5277
- return props;
5278
- }
5279
- function parseFrontmatterProps(lines) {
5280
- const props = {};
5281
- for (const line of lines) {
5282
- const match = line.match(/^([^:]+):\s*(.*)$/);
5283
- if (match) {
5284
- props[match[1].trim()] = match[2].trim();
5285
- }
5286
- }
5287
- return props;
5288
- }
5289
-
5290
5164
  // src/dashboard/parser.ts
5291
5165
  function parseDashboardNote(rawContent) {
5292
- const { meta } = parseFrontmatter(rawContent);
5166
+ const { meta } = wodWikiCore.parseFrontmatter(rawContent);
5293
5167
  const lines = rawContent.split(/\r?\n/);
5294
5168
  const sections = [];
5295
5169
  let i = 0;
@@ -5303,7 +5177,12 @@ function parseDashboardNote(rawContent) {
5303
5177
  while (i < lines.length) {
5304
5178
  const line = lines[i];
5305
5179
  const trimmed = line.trim();
5306
- if (/^```query/.test(trimmed)) {
5180
+ const isQueryFence = /^```query(?:[:\s]|$)/i.test(trimmed);
5181
+ const widgetFenceMatch = !isQueryFence ? trimmed.match(/^```widget:([a-zA-Z0-9_-]+.*)?$/i) : null;
5182
+ const isWidgetChartFence = Boolean(
5183
+ widgetFenceMatch && widgetFenceMatch[1] && isDashboardWidgetType(parseQueryWidgetSuffix(widgetFenceMatch[1]).type)
5184
+ );
5185
+ if (isQueryFence || isWidgetChartFence) {
5307
5186
  const tag = trimmed.slice(3);
5308
5187
  let widgetType;
5309
5188
  let spanCols;
@@ -5311,8 +5190,9 @@ function parseDashboardNote(rawContent) {
5311
5190
  let widgetError;
5312
5191
  let attributes;
5313
5192
  let attributeOrder;
5314
- if (tag.startsWith("query:")) {
5315
- const spec = parseQueryWidgetSuffix(tag.slice("query:".length));
5193
+ const colonMatch = tag.match(/^(?:query|widget):(.*)$/i);
5194
+ if (colonMatch) {
5195
+ const spec = parseQueryWidgetSuffix(colonMatch[1]);
5316
5196
  widgetType = spec.type || void 0;
5317
5197
  spanCols = spec.spanCols;
5318
5198
  spanFull = spec.spanFull;
@@ -5627,15 +5507,490 @@ ${query}
5627
5507
  });
5628
5508
  return out;
5629
5509
  }
5510
+ var TYPED_TAG_SET = new Set(WQL_TYPED_TAG_KEYS);
5511
+ function formatFilterValue(val) {
5512
+ const trimmed = val.trim();
5513
+ if (!trimmed) return '""';
5514
+ return /[\s,|{}]/.test(trimmed) ? `"${trimmed.replace(/"/g, '\\"')}"` : trimmed;
5515
+ }
5516
+ function extractNoteWqlOverride(rawContent) {
5517
+ if (!rawContent || !rawContent.trimStart().startsWith("---")) return void 0;
5518
+ const { meta } = wodWikiCore.parseFrontmatter(rawContent);
5519
+ const explicit = wodWikiCore.getScalar(meta, "wql") ?? wodWikiCore.getScalar(meta, "dashboard.wql") ?? wodWikiCore.getScalar(meta, "dashboard.scope");
5520
+ return typeof explicit === "string" && explicit.trim().length > 0 ? explicit.trim() : void 0;
5521
+ }
5522
+ function resolveEntityScope(input) {
5523
+ const rawId = (input.noteId ?? input.slug ?? "").trim();
5524
+ const explicitWql = input.wql?.trim() || extractNoteWqlOverride(input.rawContent);
5525
+ let kind = input.kind ?? "note";
5526
+ let slug = (input.slug ?? "").trim();
5527
+ let category = input.category?.trim();
5528
+ if (!input.kind && rawId) {
5529
+ if (rawId.startsWith("effort/")) {
5530
+ kind = "effort";
5531
+ slug = slug || rawId.slice("effort/".length);
5532
+ } else if (rawId.startsWith("equipment/")) {
5533
+ kind = "equipment";
5534
+ slug = slug || rawId.slice("equipment/".length);
5535
+ } else if (rawId.startsWith("t/") || rawId.startsWith("tag/")) {
5536
+ const prefix = rawId.startsWith("t/") ? "t/" : "tag/";
5537
+ const rest = rawId.slice(prefix.length);
5538
+ const parts = rest.split("/").filter(Boolean);
5539
+ if (parts.length >= 2) {
5540
+ kind = "tag";
5541
+ category = category || parts[0];
5542
+ slug = slug || parts.slice(1).join("/");
5543
+ } else if (parts.length === 1) {
5544
+ kind = "tag-category";
5545
+ category = category || parts[0];
5546
+ slug = slug || parts[0];
5547
+ }
5548
+ } else if (rawId.startsWith("notebook/")) {
5549
+ const rest = rawId.slice("notebook/".length);
5550
+ const parts = rest.split("/").filter(Boolean);
5551
+ if (parts.length === 1) {
5552
+ kind = "notebook";
5553
+ slug = slug || parts[0];
5554
+ } else {
5555
+ kind = "note";
5556
+ slug = slug || rawId;
5557
+ }
5558
+ } else if (rawId.startsWith("feed/")) {
5559
+ kind = "feed";
5560
+ slug = slug || rawId.slice("feed/".length);
5561
+ } else if (rawId.startsWith("session/") || rawId.startsWith("result/")) {
5562
+ kind = "session";
5563
+ slug = slug || rawId.slice(rawId.indexOf("/") + 1);
5564
+ }
5565
+ }
5566
+ if (!slug) slug = rawId || "unknown";
5567
+ const id = rawId || (category ? `${kind}/${category}/${slug}` : `${kind}/${slug}`);
5568
+ const label = (input.label ?? slug).trim() || slug;
5569
+ const safeSlug = formatFilterValue(slug);
5570
+ switch (kind) {
5571
+ case "effort": {
5572
+ const scopeFilter = `effort:${safeSlug}`;
5573
+ const notesWql = `:note{${scopeFilter}}`;
5574
+ const blocksWql = `:block{${scopeFilter}}`;
5575
+ const sessionsWql = `:session{${scopeFilter}}`;
5576
+ const recordsWql = `:segment{${scopeFilter}}`;
5577
+ return {
5578
+ kind,
5579
+ id,
5580
+ slug,
5581
+ category: "effort",
5582
+ label,
5583
+ scopeFilter,
5584
+ notesWql,
5585
+ blocksWql,
5586
+ sessionsWql,
5587
+ recordsWql,
5588
+ primaryWql: explicitWql ?? notesWql,
5589
+ tokens: {
5590
+ scope: scopeFilter,
5591
+ effort: slug,
5592
+ note: id,
5593
+ metric: "totalVolume"
5594
+ }
5595
+ };
5596
+ }
5597
+ case "equipment": {
5598
+ const scopeFilter = `equipment:${safeSlug}`;
5599
+ const notesWql = `:note{${scopeFilter}}`;
5600
+ const blocksWql = `:block{tags:${safeSlug}}`;
5601
+ const sessionsWql = `:session{${scopeFilter}}`;
5602
+ const recordsWql = `:segment{${scopeFilter}}`;
5603
+ return {
5604
+ kind,
5605
+ id,
5606
+ slug,
5607
+ category: "equipment",
5608
+ label,
5609
+ scopeFilter,
5610
+ notesWql,
5611
+ blocksWql,
5612
+ sessionsWql,
5613
+ recordsWql,
5614
+ primaryWql: explicitWql ?? notesWql,
5615
+ tokens: {
5616
+ scope: scopeFilter,
5617
+ equipment: slug,
5618
+ note: id,
5619
+ metric: "totalVolume"
5620
+ }
5621
+ };
5622
+ }
5623
+ case "tag": {
5624
+ const cat = (category ?? "tag").trim() || "tag";
5625
+ if (cat === "effort") {
5626
+ return resolveEntityScope({ ...input, kind: "effort", slug });
5627
+ }
5628
+ if (cat === "equipment") {
5629
+ return resolveEntityScope({ ...input, kind: "equipment", slug });
5630
+ }
5631
+ const isTypedKey = TYPED_TAG_SET.has(cat) || cat === "discipline" || cat === "intensity";
5632
+ const scopeFilter = isTypedKey ? `${cat}:${safeSlug}` : `tags:${safeSlug}`;
5633
+ const notesWql = `:note{${scopeFilter}}`;
5634
+ const blocksWql = `:block{tags:${safeSlug}}`;
5635
+ const sessionsWql = `:session{${scopeFilter}}`;
5636
+ const recordsWql = `:segment{${scopeFilter}}`;
5637
+ return {
5638
+ kind,
5639
+ id,
5640
+ slug,
5641
+ category: cat,
5642
+ label,
5643
+ scopeFilter,
5644
+ notesWql,
5645
+ blocksWql,
5646
+ sessionsWql,
5647
+ recordsWql,
5648
+ primaryWql: explicitWql ?? notesWql,
5649
+ tokens: {
5650
+ scope: scopeFilter,
5651
+ tag: slug,
5652
+ category: cat,
5653
+ note: id,
5654
+ metric: "tis"
5655
+ }
5656
+ };
5657
+ }
5658
+ case "tag-category": {
5659
+ const cat = (category ?? slug).trim() || "tag";
5660
+ const safeCat = formatFilterValue(cat);
5661
+ const scopeFilter = `type:${safeCat}`;
5662
+ const notesWql = cat === "effort" ? ":effort{}" : cat === "equipment" ? ":equipment{}" : `:tag{${scopeFilter}}`;
5663
+ const blocksWql = `:block{}`;
5664
+ const sessionsWql = `:session{}`;
5665
+ const recordsWql = `:segment{}`;
5666
+ return {
5667
+ kind,
5668
+ id,
5669
+ slug: cat,
5670
+ category: cat,
5671
+ label,
5672
+ scopeFilter,
5673
+ notesWql,
5674
+ blocksWql,
5675
+ sessionsWql,
5676
+ recordsWql,
5677
+ primaryWql: explicitWql ?? notesWql,
5678
+ tokens: {
5679
+ scope: scopeFilter,
5680
+ category: cat,
5681
+ note: id,
5682
+ metric: "tis"
5683
+ }
5684
+ };
5685
+ }
5686
+ case "notebook": {
5687
+ const nbSlug = slug.replace(/^notebook\//, "");
5688
+ const safeNb = formatFilterValue(nbSlug);
5689
+ const scopeFilter = `notebook:${safeNb}`;
5690
+ const notesWql = `:note{${scopeFilter}}`;
5691
+ const blocksWql = `:block{${scopeFilter}}`;
5692
+ const sessionsWql = `:session{} where ${notesWql}`;
5693
+ const recordsWql = `:segment{} where ${notesWql}`;
5694
+ return {
5695
+ kind,
5696
+ id: `notebook/${nbSlug}`,
5697
+ slug: nbSlug,
5698
+ label,
5699
+ scopeFilter,
5700
+ notesWql,
5701
+ blocksWql,
5702
+ sessionsWql,
5703
+ recordsWql,
5704
+ primaryWql: explicitWql ?? notesWql,
5705
+ tokens: {
5706
+ scope: scopeFilter,
5707
+ notebook: nbSlug,
5708
+ metric: "tis"
5709
+ }
5710
+ };
5711
+ }
5712
+ case "feed": {
5713
+ const feedSlug = slug.replace(/^feed\//, "");
5714
+ const safeFeed = formatFilterValue(feedSlug);
5715
+ const scopeFilter = `feed:${safeFeed}`;
5716
+ const notesWql = `:notebook{${scopeFilter}}`;
5717
+ const blocksWql = `:block{${scopeFilter}}`;
5718
+ const sessionsWql = `:session{} where ${notesWql}`;
5719
+ const recordsWql = `:segment{} where ${notesWql}`;
5720
+ return {
5721
+ kind,
5722
+ id: `feed/${feedSlug}`,
5723
+ slug: feedSlug,
5724
+ label,
5725
+ scopeFilter,
5726
+ notesWql,
5727
+ blocksWql,
5728
+ sessionsWql,
5729
+ recordsWql,
5730
+ primaryWql: explicitWql ?? notesWql,
5731
+ tokens: {
5732
+ scope: scopeFilter,
5733
+ feed: feedSlug,
5734
+ metric: "tis"
5735
+ }
5736
+ };
5737
+ }
5738
+ case "session": {
5739
+ const scopeFilter = `result:${safeSlug}`;
5740
+ const notesWql = `:note{}`;
5741
+ const blocksWql = `:block{}`;
5742
+ const sessionsWql = `:session{${scopeFilter}}`;
5743
+ const recordsWql = `:segment{${scopeFilter}}`;
5744
+ return {
5745
+ kind,
5746
+ id,
5747
+ slug,
5748
+ label,
5749
+ scopeFilter,
5750
+ notesWql,
5751
+ blocksWql,
5752
+ sessionsWql,
5753
+ recordsWql,
5754
+ primaryWql: explicitWql ?? sessionsWql,
5755
+ tokens: {
5756
+ scope: scopeFilter,
5757
+ result: slug,
5758
+ metric: "tis"
5759
+ }
5760
+ };
5761
+ }
5762
+ case "wql": {
5763
+ const query = explicitWql ?? ":note{}";
5764
+ return {
5765
+ kind: "wql",
5766
+ id: id || "wql",
5767
+ slug: slug || "query",
5768
+ label,
5769
+ scopeFilter: "",
5770
+ notesWql: query,
5771
+ blocksWql: query,
5772
+ sessionsWql: query,
5773
+ recordsWql: query,
5774
+ primaryWql: query,
5775
+ tokens: {
5776
+ scope: query,
5777
+ metric: "tis"
5778
+ }
5779
+ };
5780
+ }
5781
+ case "note":
5782
+ default: {
5783
+ const noteKey = formatFilterValue(id || slug);
5784
+ const scopeFilter = `note:${noteKey}`;
5785
+ const notesWql = `:note{${scopeFilter}}`;
5786
+ const blocksWql = `:block{${scopeFilter}}`;
5787
+ const sessionsWql = `:session{${scopeFilter}}`;
5788
+ const recordsWql = `:segment{${scopeFilter}}`;
5789
+ return {
5790
+ kind: "note",
5791
+ id: id || slug,
5792
+ slug,
5793
+ label,
5794
+ scopeFilter,
5795
+ notesWql,
5796
+ blocksWql,
5797
+ sessionsWql,
5798
+ recordsWql,
5799
+ primaryWql: explicitWql ?? sessionsWql,
5800
+ tokens: {
5801
+ scope: scopeFilter,
5802
+ note: id || slug,
5803
+ metric: "tis"
5804
+ }
5805
+ };
5806
+ }
5807
+ }
5808
+ }
5809
+ function buildWidgetsForWqlScope(wqlOrScope, extraWidgets = []) {
5810
+ if (typeof wqlOrScope === "string") {
5811
+ const query = wqlOrScope.trim();
5812
+ const parsed = parseQuery(query);
5813
+ const widgets2 = [];
5814
+ if (!parsed.error && isFindQuery(parsed)) {
5815
+ const isSessionOrSegment = parsed.target === "session" || parsed.target === "segment" || parsed.target === "event";
5816
+ widgets2.push({
5817
+ title: isSessionOrSegment ? "Matching Records" : "Matching Items",
5818
+ type: isSessionOrSegment ? "table" : "list",
5819
+ spanFull: true,
5820
+ wql: query
5821
+ });
5822
+ if (parsed.target === "note" || parsed.target === "notebook" || parsed.target === "block") {
5823
+ widgets2.push({
5824
+ title: "Weekly Volume Across Scope",
5825
+ question: "How does $metric trend across matching workouts?",
5826
+ type: "timeseries",
5827
+ spanCols: 2,
5828
+ wql: `sum:$metric{} by {week} where ${query}`
5829
+ });
5830
+ }
5831
+ } else if (!parsed.error && isPipelineQuery(parsed)) {
5832
+ const sinkType = parsed.sink?.head ?? "table";
5833
+ widgets2.push({
5834
+ title: "Query Output",
5835
+ type: sinkType,
5836
+ spanCols: sinkType === "value" ? 1 : 2,
5837
+ wql: query
5838
+ });
5839
+ } else {
5840
+ widgets2.push({
5841
+ title: "Trend Overview",
5842
+ type: "timeseries",
5843
+ spanCols: 2,
5844
+ wql: query || "sum:$metric{} by {week}"
5845
+ });
5846
+ }
5847
+ return [...widgets2, ...extraWidgets];
5848
+ }
5849
+ const scope = wqlOrScope;
5850
+ const widgets = [];
5851
+ switch (scope.kind) {
5852
+ case "effort":
5853
+ widgets.push(
5854
+ {
5855
+ title: `${scope.label} Volume Trend`,
5856
+ question: `How does $metric for ${scope.label} trend week over week?`,
5857
+ type: "timeseries",
5858
+ spanCols: 2,
5859
+ wql: `sum:$metric{${scope.scopeFilter}} by {week}`
5860
+ },
5861
+ {
5862
+ title: `${scope.label} Recent Sessions`,
5863
+ type: "table",
5864
+ spanCols: 2,
5865
+ wql: scope.recordsWql
5866
+ },
5867
+ {
5868
+ title: `Workouts Containing ${scope.label}`,
5869
+ type: "list",
5870
+ spanFull: true,
5871
+ wql: scope.notesWql
5872
+ }
5873
+ );
5874
+ break;
5875
+ case "equipment":
5876
+ widgets.push(
5877
+ {
5878
+ title: `${scope.label} Volume Trend`,
5879
+ type: "timeseries",
5880
+ spanCols: 2,
5881
+ wql: `sum:$metric{${scope.scopeFilter}} by {week}`
5882
+ },
5883
+ {
5884
+ title: `Workouts Using ${scope.label}`,
5885
+ type: "list",
5886
+ spanFull: true,
5887
+ wql: scope.notesWql
5888
+ }
5889
+ );
5890
+ break;
5891
+ case "tag":
5892
+ widgets.push(
5893
+ {
5894
+ title: `${scope.label} Weekly Trend`,
5895
+ type: "timeseries",
5896
+ spanCols: 2,
5897
+ wql: `sum:$metric{${scope.scopeFilter}} by {week}`
5898
+ },
5899
+ {
5900
+ title: `Tagged Notes (${scope.label})`,
5901
+ type: "list",
5902
+ spanFull: true,
5903
+ wql: scope.notesWql
5904
+ }
5905
+ );
5906
+ break;
5907
+ case "tag-category":
5908
+ widgets.push({
5909
+ title: `${scope.label} Tags`,
5910
+ type: "list",
5911
+ spanFull: true,
5912
+ wql: scope.notesWql
5913
+ });
5914
+ break;
5915
+ case "notebook":
5916
+ case "feed":
5917
+ widgets.push(
5918
+ {
5919
+ title: `${scope.label} Workouts`,
5920
+ type: "list",
5921
+ spanFull: true,
5922
+ wql: scope.notesWql
5923
+ },
5924
+ {
5925
+ title: `${scope.label} Weekly Activity`,
5926
+ type: "timeseries",
5927
+ spanCols: 2,
5928
+ wql: `sum:$metric{} by {week} where ${scope.notesWql}`
5929
+ }
5930
+ );
5931
+ break;
5932
+ case "note":
5933
+ default:
5934
+ widgets.push(
5935
+ {
5936
+ title: "Session History",
5937
+ type: "table",
5938
+ spanCols: 2,
5939
+ wql: scope.sessionsWql
5940
+ },
5941
+ {
5942
+ title: "Recorded Statements",
5943
+ type: "table",
5944
+ spanCols: 2,
5945
+ wql: scope.recordsWql
5946
+ }
5947
+ );
5948
+ break;
5949
+ }
5950
+ return [...widgets, ...extraWidgets];
5951
+ }
5952
+ function buildDashboardFromScope(options2) {
5953
+ const scopeObj = typeof options2.scope === "object" ? options2.scope : void 0;
5954
+ const rawWql = typeof options2.scope === "string" ? options2.scope : scopeObj?.primaryWql;
5955
+ const title = (options2.title ?? (scopeObj ? `${scopeObj.label} Dashboard` : DEFAULT_DASHBOARD_TITLE)).trim() || DEFAULT_DASHBOARD_TITLE;
5956
+ const slug = options2.slug?.trim() || dashboardSlug(title);
5957
+ const widgets = options2.widgets && options2.widgets.length > 0 ? options2.widgets : buildWidgetsForWqlScope(options2.scope ?? "sum:$metric{} by {week}");
5958
+ const headerLines = [
5959
+ "---",
5960
+ "dashboard: true",
5961
+ "dashboard.active: true",
5962
+ `title: ${title}`,
5963
+ `slug: ${slug}`
5964
+ ];
5965
+ if (rawWql) {
5966
+ headerLines.push(`wql: "${rawWql.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`);
5967
+ }
5968
+ const defaultMetric = scopeObj?.tokens.metric ?? "totalVolume";
5969
+ headerLines.push("dashboard.metric:");
5970
+ headerLines.push(` - ${defaultMetric}`);
5971
+ for (const [k, v] of Object.entries(options2.tokens ?? {})) {
5972
+ const tokenKey = k.startsWith("dashboard.") ? k : `dashboard.${k}`;
5973
+ if (tokenKey === "dashboard.metric") continue;
5974
+ if (Array.isArray(v)) {
5975
+ headerLines.push(`${tokenKey}:`);
5976
+ for (const item of v) headerLines.push(` - ${item}`);
5977
+ } else {
5978
+ headerLines.push(`${tokenKey}: ${v}`);
5979
+ }
5980
+ }
5981
+ headerLines.push("---", "");
5982
+ const bodyGroups = widgets.map((w) => renderWidgetGroup(w).join("\n"));
5983
+ return headerLines.join("\n") + bodyGroups.join("\n\n") + "\n";
5984
+ }
5630
5985
 
5631
5986
  // src/version.ts
5632
- var VERSION = "0.50.2188";
5633
- var GIT_SHA = "59cf7f2";
5634
- var BUILD_TIME = "2026-11-01T13:47:18.479Z";
5987
+ var VERSION = "0.50.2190";
5988
+ var GIT_SHA = "9ee7872";
5989
+ var BUILD_TIME = "2026-11-01T23:32:46.829Z";
5635
5990
  var SEMVER = {
5636
- version: "0.50.2188",
5637
- gitSha: "59cf7f2",
5638
- buildTime: "2026-11-01T13:47:18.479Z"
5991
+ version: "0.50.2190",
5992
+ gitSha: "9ee7872",
5993
+ buildTime: "2026-11-01T23:32:46.829Z"
5639
5994
  };
5640
5995
 
5641
5996
  Object.defineProperty(exports, "DEFAULT_NOTEBOOK_SLUGS", {
@@ -5658,6 +6013,22 @@ Object.defineProperty(exports, "dimensionKeyOf", {
5658
6013
  enumerable: true,
5659
6014
  get: function () { return wodWikiCore.dimensionKeyOf; }
5660
6015
  });
6016
+ Object.defineProperty(exports, "extractFrontmatterTags", {
6017
+ enumerable: true,
6018
+ get: function () { return wodWikiCore.extractFrontmatterTags; }
6019
+ });
6020
+ Object.defineProperty(exports, "extractTypedFrontmatterTags", {
6021
+ enumerable: true,
6022
+ get: function () { return wodWikiCore.extractTypedFrontmatterTags; }
6023
+ });
6024
+ Object.defineProperty(exports, "getList", {
6025
+ enumerable: true,
6026
+ get: function () { return wodWikiCore.getList; }
6027
+ });
6028
+ Object.defineProperty(exports, "getScalar", {
6029
+ enumerable: true,
6030
+ get: function () { return wodWikiCore.getScalar; }
6031
+ });
5661
6032
  Object.defineProperty(exports, "isNoteEditable", {
5662
6033
  enumerable: true,
5663
6034
  get: function () { return wodWikiCore.isNoteEditable; }
@@ -5670,6 +6041,34 @@ Object.defineProperty(exports, "notebookSlugFromId", {
5670
6041
  enumerable: true,
5671
6042
  get: function () { return wodWikiCore.notebookSlugFromId; }
5672
6043
  });
6044
+ Object.defineProperty(exports, "parseFlatProperties", {
6045
+ enumerable: true,
6046
+ get: function () { return wodWikiCore.parseFlatProperties; }
6047
+ });
6048
+ Object.defineProperty(exports, "parseFrontmatter", {
6049
+ enumerable: true,
6050
+ get: function () { return wodWikiCore.parseFrontmatter; }
6051
+ });
6052
+ Object.defineProperty(exports, "parseFrontmatterBody", {
6053
+ enumerable: true,
6054
+ get: function () { return wodWikiCore.parseFrontmatterBody; }
6055
+ });
6056
+ Object.defineProperty(exports, "parseFrontmatterCategories", {
6057
+ enumerable: true,
6058
+ get: function () { return wodWikiCore.parseFrontmatterCategories; }
6059
+ });
6060
+ Object.defineProperty(exports, "parseFrontmatterProps", {
6061
+ enumerable: true,
6062
+ get: function () { return wodWikiCore.parseFrontmatterProps; }
6063
+ });
6064
+ Object.defineProperty(exports, "serializeFrontmatter", {
6065
+ enumerable: true,
6066
+ get: function () { return wodWikiCore.serializeFrontmatter; }
6067
+ });
6068
+ Object.defineProperty(exports, "stripFrontmatter", {
6069
+ enumerable: true,
6070
+ get: function () { return wodWikiCore.stripFrontmatter; }
6071
+ });
5673
6072
  Object.defineProperty(exports, "systemDefaultUnit", {
5674
6073
  enumerable: true,
5675
6074
  get: function () { return wodWikiCore.systemDefaultUnit; }
@@ -5727,8 +6126,10 @@ exports.allowedFilterTypesForTarget = allowedFilterTypesForTarget;
5727
6126
  exports.appendSuffixes = appendSuffixes;
5728
6127
  exports.appendWidget = appendWidget;
5729
6128
  exports.buildDashboardDocument = buildDashboardDocument;
6129
+ exports.buildDashboardFromScope = buildDashboardFromScope;
5730
6130
  exports.buildDashboardScaffold = buildDashboardScaffold;
5731
6131
  exports.buildDrillDownQuery = buildDrillDownQuery;
6132
+ exports.buildWidgetsForWqlScope = buildWidgetsForWqlScope;
5732
6133
  exports.captureContext = captureContext;
5733
6134
  exports.chunkIdOfItem = chunkIdOfItem;
5734
6135
  exports.civilDateAdd = civilDateAdd;
@@ -5745,12 +6146,10 @@ exports.disciplineFactorFor = disciplineFactorFor;
5745
6146
  exports.duplicateWidget = duplicateWidget;
5746
6147
  exports.eventsToStoredLogs = eventsToStoredLogs;
5747
6148
  exports.extractDashboardTokens = extractDashboardTokens;
5748
- exports.extractFrontmatterTags = extractFrontmatterTags;
6149
+ exports.extractNoteWqlOverride = extractNoteWqlOverride;
5749
6150
  exports.feedIdOfItem = feedIdOfItem;
5750
6151
  exports.feedOfItem = feedOfItem;
5751
6152
  exports.findTargetAdvisories = findTargetAdvisories;
5752
- exports.getList = getList;
5753
- exports.getScalar = getScalar;
5754
6153
  exports.getUnitFamily = getUnitFamily;
5755
6154
  exports.hasLegacyIdentity = hasLegacyIdentity;
5756
6155
  exports.inRange = inRange;
@@ -5776,11 +6175,6 @@ exports.notebookOriginOfItem = notebookOriginOfItem;
5776
6175
  exports.notebookSlugOfSourceId = notebookSlugOfSourceId;
5777
6176
  exports.parseDashboardNote = parseDashboardNote;
5778
6177
  exports.parseDocument = parseDocument;
5779
- exports.parseFlatProperties = parseFlatProperties;
5780
- exports.parseFrontmatter = parseFrontmatter;
5781
- exports.parseFrontmatterBody = parseFrontmatterBody;
5782
- exports.parseFrontmatterCategories = parseFrontmatterCategories;
5783
- exports.parseFrontmatterProps = parseFrontmatterProps;
5784
6178
  exports.parseQuery = parseQuery;
5785
6179
  exports.parseQueryWidgetSuffix = parseQueryWidgetSuffix;
5786
6180
  exports.parseWqlSuffixes = parseWqlSuffixes;
@@ -5790,18 +6184,17 @@ exports.removeWidget = removeWidget;
5790
6184
  exports.renderWidgetGroup = renderWidgetGroup;
5791
6185
  exports.resizeWidget = resizeWidget;
5792
6186
  exports.resolveCanonicalMetricKey = resolveCanonicalMetricKey;
6187
+ exports.resolveEntityScope = resolveEntityScope;
5793
6188
  exports.resolveOutputUnit = resolveOutputUnit;
5794
6189
  exports.resolveWidgetType = resolveWidgetType;
5795
6190
  exports.resolveWindowRange = resolveWindowRange;
5796
6191
  exports.rowsParseError = rowsParseError;
5797
6192
  exports.serialize = serialize;
5798
6193
  exports.serializeDocument = serializeDocument;
5799
- exports.serializeFrontmatter = serializeFrontmatter;
5800
6194
  exports.setDashboardTokenValue = setDashboardTokenValue;
5801
6195
  exports.sourceMatches = sourceMatches;
5802
6196
  exports.sourceShape = sourceShape;
5803
6197
  exports.splitAtWhere = splitAtWhere;
5804
- exports.stripFrontmatter = stripFrontmatter;
5805
6198
  exports.substituteTokens = substituteTokens2;
5806
6199
  exports.substituteTokensRaw = substituteTokens;
5807
6200
  exports.supportsWqlFilterKey = supportsWqlFilterKey;