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