@bitcobblers/wod-wiki-ui 0.11.43 → 0.35.2116

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,9 +1,9 @@
1
- import { cn, WidgetProblemBadge, WidgetChart } from './chunk-PZXX6GBK.mjs';
2
- export { AGG_OPTIONS, AddCalcDropdown, AddFilterDropdown, CONTENT_SOURCES, ClausePopover, ComposerRegistry, CustomSlotPopover, DEFAULT_DIAGNOSTICS_DEBOUNCE_MS, DefaultFrontmatterWidget, GROUPBY_OPTIONS, GoalRings, METRIC_OPTIONS, MULTI_VALUE_TYPES, ProposedMetricBadge, QueryBlockView, QueryValue, ROLLUP_OPTIONS, RowsResultsChrome, RowsTable, SERIES_COLORS, SOURCE_OPTIONS, SUGGESTION_BINDINGS, StackedBar, TIME_OPTIONS, TokenSlotPill, TopList, UNIT_OPTIONS, WHERE_AGGREGATORS, WHERE_METRICS, WHERE_OPERATORS, WidgetChart, WidgetProblemBadge, WqlBars, WqlComposer, WqlDiagnosticsStrip, WqlEmptyState, WqlQueryInspectorModal, WqlTable, WqlTimeseries, ZoneDistribution, activeCursorSection, addRuntimePanel, blockContentId, blockTypesFromBlocks, catalogIdsFromBlocks, cn, compactNumber, composerRegistry, configureOverlayActions, cursorFocusExtension, dateRangeSlot, dispatchAddRuntimePanel, dispatchExpandRuntimePanel, dispatchGutterHighlights, dispatchRemoveRuntimePanel, editingWidgetsField, editorPreset, editorTheme, embedPreviewDecorations, expandRuntimePanel, extractBlockQueries, fenceCompletion, findQueryBlockSection, findWorkoutErrors, forceSectionParse, formatTimestamp, frontmatterPreview, frontmatterPreviewField, getCursorFocusState, getOverlayActions, getSuggestionBinding, gutterHighlightsField, gutterUnified, handleFenceAutoWrap, hashCode, inlineButtonDecoration, invalidateSuggestions, lineIdsExtension, linkOpen, loadSuggestions, markdownSyntaxHiding, markdownTablePreview, mergeSeries, mergeTagSuggestions, navigationFacet, patchBlockQuery, previewDecorations, queryBlockPreview, readSessionRpe, removeRuntimePanel, runtimePanelField, saveBlockQuerySource, sectionAtPos, sectionField, sectionGeometry, sessionQueryInsert, sessionQueryWql, setGutterHighlights, setSuggestionBinding, smartIncrement, sourcePlane, summarizeAggregate, summarizeFind, tagsFromStaticBlocks, toggleWidgetEdit, tooltipTimestamp, urlAtPos, useChartShape, useComposerSlots, useSuggestions, useWqlStageCounts, widgetBlockPreview, wodAutoWrap, wodAutocompletion, wodEditorKeymap, wodLinter, wodOverlayPanel, workoutLintSource, wrapInTimeFence } from './chunk-PZXX6GBK.mjs';
3
- import { jsxs, jsx } from 'react/jsx-runtime';
4
- import { useMemo, useState, useEffect, useCallback } from 'react';
5
- import { Edit3 } from 'lucide-react';
6
- import { defaultTokenValues, substituteTokens, parseQuery, isDashboardWidgetType, unknownWidgetTypeMessage, unknownTokensMessage, isFindQuery, resolveWidgetType } from '@bitcobblers/wod-wiki-wql';
1
+ import { cn, WidgetProblemBadge, WidgetChart, toChartResult } from './chunk-GUN6PZNI.mjs';
2
+ export { AGG_OPTIONS, CANONICAL_BLOCK_TYPES, CLAUSE_META, CONTENT_GROUPING_DIMENSIONS, ComposerRegistry, DEFAULT_DIAGNOSTICS_DEBOUNCE_MS, DEFAULT_OUTPUT_FILTERS, DEFAULT_PRIMARY_FILTER, DefaultFrontmatterWidget, EditorDialog, GROUPBY_OPTIONS, GoalRings, KIND_OPTIONS, METRIC_OPTIONS, MULTI_VALUE_TYPES, MetricBadge, OUTPUT_TYPE_CLASS, OUTPUT_TYPE_DOT, OutputFilterPills, OutputStatementsTable, PLANE_OPTIONS, ProposedMetricBadge, QueryBlockView, QueryValue, ROLLUP_OPTIONS, RowsResultsChrome, RowsTable, SERIES_COLORS, SOURCE_OPTIONS, SUGGESTION_BINDINGS, StackedBar, TARGET_OPTIONS, TIME_OPTIONS, TONE_CLASS, TokenSlotPill, TopList, UNIT_OPTIONS, WHERE_AGGREGATORS, WHERE_METRICS, WidgetChart, WidgetProblemBadge, WqlBars, WqlComposer, WqlDiagnosticsStrip, WqlEmptyState, WqlQueryInspectorModal, WqlTable, WqlTimeseries, ZoneDistribution, activeCursorSection, addRuntimePanel, blockContentId, blockTypesFromBlocks, canonicalSuggestionItems, catalogIdsFromBlocks, cn, compactNumber, composerRegistry, configureOverlayActions, cursorFocusExtension, dateRangeSlot, dispatchAddRuntimePanel, dispatchExpandRuntimePanel, dispatchGutterHighlights, dispatchRemoveRuntimePanel, editQueryClause, editingWidgetsField, editorPreset, editorTheme, embedPreviewDecorations, expandRuntimePanel, extractBlockQueries, extractMetricsByCategory, fenceCompletion, findQueryBlockSection, findWorkoutErrors, forceSectionParse, formatClockTime, formatMMSS, formatTimestamp, frontmatterPreview, frontmatterPreviewField, frontmatterSuggestions, frontmatterVisibility, getClauseMeta, getCursorFocusState, getOverlayActions, getSuggestionBinding, gutterHighlightsField, gutterUnified, handleFenceAutoWrap, hashCode, inlineButtonDecoration, invalidateSuggestions, lineIdsExtension, linkOpen, linkPreview, loadSuggestions, markdownSyntaxHiding, markdownTablePreview, matchFilterTypeahead, mergeSeries, mergeSuggestionItems, mergeTagSuggestions, navigationFacet, normalizeOutputFilter, patchBlockQuery, presentBadges, previewDecorations, queryBlockPreview, readSessionRpe, registerCustomCompletionSource, removeRuntimePanel, resolveQueryDraft, runtimePanelField, saveBlockQuerySource, sectionAtPos, sectionField, sectionGeometry, sessionQueryInsert, sessionQueryWql, setGutterHighlights, setSuggestionBinding, smartIncrement, summarizeAggregate, summarizeFind, summarizePipeline, tagsFromStaticBlocks, toggleWidgetEdit, tooltipTimestamp, unregisterCustomCompletionSource, urlAtPos, useChartShape, useComposerSlots, useSuggestions, useVisualViewportRect, useWqlStageCounts, widgetBlockPreview, wodAutoWrap, wodAutocompletion, wodEditorKeymap, wodLinter, wodOverlayPanel, workoutLintSource, wqlToPills, wrapInTimeFence } from './chunk-GUN6PZNI.mjs';
3
+ import { Eye, ExternalLink, Edit3, ChevronUp, ChevronDown, ChevronRight, Copy, Trash2 } from 'lucide-react';
4
+ import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
5
+ import { useMemo, useState, useEffect, Fragment, useCallback } from 'react';
6
+ import { defaultTokenValues, substituteTokens, parseQuery, isDashboardWidgetType, unknownWidgetTypeMessage, unknownTokensMessage, resolveWidgetType, isFindQuery, isPipelineQuery, SharedQueryDocumentRunner } from '@bitcobblers/wod-wiki-wql';
7
7
 
8
8
  // src/contracts/dedupe.ts
9
9
  var CODEMIRROR_SINGLETON_DEPS = [
@@ -21,25 +21,119 @@ var CODEMIRROR_SINGLETON_DEPS = [
21
21
  "@lezer/lr",
22
22
  "@lezer/markdown"
23
23
  ];
24
- function WidgetFrame({ title, question, query, span, children }) {
24
+ function WidgetFrame({ id, title, question, query, span, showQuery = false, onInspect, onOpenInExplorer, toolbar, children }) {
25
25
  return /* @__PURE__ */ jsxs(
26
26
  "div",
27
27
  {
28
+ id,
28
29
  className: cn(
29
- "bg-card border border-border rounded-lg p-4 flex flex-col min-h-0",
30
+ "relative group/frame bg-card border border-border rounded-lg p-4 flex flex-col min-h-0",
30
31
  span
31
32
  ),
32
33
  children: [
33
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-3 mb-1", children: [
34
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-foreground min-w-0", children: title }),
35
- /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground italic text-right max-w-[60%] line-clamp-2 shrink-0", children: question })
34
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 mb-2", children: [
35
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
36
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-foreground", children: title }),
37
+ question && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: question })
38
+ ] }),
39
+ (onInspect || onOpenInExplorer) && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
40
+ onInspect && /* @__PURE__ */ jsx(
41
+ "button",
42
+ {
43
+ type: "button",
44
+ onClick: onInspect,
45
+ title: "Inspect widget query",
46
+ "aria-label": `Inspect query for ${title || "widget"}`,
47
+ className: "inline-flex items-center justify-center p-1 max-lg:min-h-11 max-lg:min-w-11 rounded bg-muted/80 text-muted-foreground hover:text-foreground transition-colors",
48
+ children: /* @__PURE__ */ jsx(Eye, { className: "w-3.5 h-3.5" })
49
+ }
50
+ ),
51
+ onOpenInExplorer && /* @__PURE__ */ jsx(
52
+ "button",
53
+ {
54
+ type: "button",
55
+ onClick: onOpenInExplorer,
56
+ title: "Open in explorer",
57
+ "aria-label": `Open ${title || "widget"} in explorer`,
58
+ className: "inline-flex items-center justify-center p-1 max-lg:min-h-11 max-lg:min-w-11 rounded bg-muted/80 text-muted-foreground hover:text-foreground transition-colors",
59
+ children: /* @__PURE__ */ jsx(ExternalLink, { className: "w-3.5 h-3.5" })
60
+ }
61
+ )
62
+ ] })
36
63
  ] }),
37
- /* @__PURE__ */ jsx("div", { className: "font-mono text-[11px] text-primary/90 bg-background/60 rounded px-2 py-1 mb-3 whitespace-pre-wrap break-all", children: query }),
64
+ toolbar && /* @__PURE__ */ jsx("div", { className: "mb-3", children: toolbar }),
65
+ showQuery && query != null && query !== "" && /* @__PURE__ */ jsx("div", { className: "font-mono text-[11px] text-primary/90 bg-background/60 rounded px-2 py-1 mb-3 whitespace-pre-wrap break-all", children: query }),
38
66
  /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children })
39
67
  ]
40
68
  }
41
69
  );
42
70
  }
71
+ function WidgetToolButton({
72
+ label,
73
+ onClick,
74
+ disabled,
75
+ active,
76
+ children
77
+ }) {
78
+ return /* @__PURE__ */ jsx(
79
+ "button",
80
+ {
81
+ type: "button",
82
+ onClick,
83
+ title: label,
84
+ "aria-label": label,
85
+ disabled,
86
+ className: cn(
87
+ "inline-flex items-center justify-center p-1 max-lg:min-h-11 max-lg:min-w-11 rounded bg-muted/80 text-muted-foreground hover:text-foreground disabled:opacity-40 disabled:hover:text-muted-foreground transition-colors",
88
+ active && "text-primary bg-primary/10"
89
+ ),
90
+ children
91
+ }
92
+ );
93
+ }
94
+ function WidgetEditButton({ title, onClick }) {
95
+ return /* @__PURE__ */ jsx(
96
+ "button",
97
+ {
98
+ type: "button",
99
+ onClick,
100
+ title: "Edit widget",
101
+ "aria-label": `Edit widget ${title || ""}`.trim(),
102
+ className: "inline-flex items-center justify-center p-1 max-lg:min-h-11 max-lg:min-w-11 rounded bg-muted/80 text-muted-foreground hover:text-foreground transition-colors",
103
+ children: /* @__PURE__ */ jsx(Edit3, { className: "w-3.5 h-3.5" })
104
+ }
105
+ );
106
+ }
107
+ function ScalarTokenInput({
108
+ name,
109
+ value,
110
+ disabled,
111
+ onCommit
112
+ }) {
113
+ const [draft, setDraft] = useState(value);
114
+ useEffect(() => setDraft(value), [value]);
115
+ const commit = () => {
116
+ if (draft !== value) onCommit(draft);
117
+ };
118
+ return /* @__PURE__ */ jsx(
119
+ "input",
120
+ {
121
+ type: "text",
122
+ value: draft,
123
+ disabled,
124
+ onChange: (e) => setDraft(e.target.value),
125
+ onBlur: commit,
126
+ onKeyDown: (e) => {
127
+ if (e.key === "Enter") {
128
+ commit();
129
+ e.currentTarget.blur();
130
+ }
131
+ },
132
+ "aria-label": `Token ${name}`,
133
+ className: "w-20 rounded-lg border border-border bg-card px-2 py-1 text-xs font-mono text-foreground focus:outline-none focus:border-primary disabled:opacity-60"
134
+ }
135
+ );
136
+ }
43
137
  function DashboardTokenControls({ tokens, values, onChange }) {
44
138
  if (tokens.length === 0) return null;
45
139
  const readOnly = !onChange;
@@ -60,7 +154,9 @@ function DashboardTokenControls({ tokens, values, onChange }) {
60
154
  {
61
155
  type: "button",
62
156
  disabled: readOnly,
63
- onClick: () => onChange?.(token.name, option),
157
+ onClick: () => {
158
+ if (option !== current) onChange?.(token.name, option);
159
+ },
64
160
  className: cn(
65
161
  "text-xs px-2.5 py-1 rounded-md transition-colors",
66
162
  option === current ? "bg-primary text-primary-foreground font-medium" : "text-muted-foreground hover:text-foreground hover:bg-muted",
@@ -70,12 +166,11 @@ function DashboardTokenControls({ tokens, values, onChange }) {
70
166
  },
71
167
  option
72
168
  )) }) : readOnly ? /* @__PURE__ */ jsx("span", { className: "text-xs text-foreground font-mono", children: current }) : /* @__PURE__ */ jsx(
73
- "input",
169
+ ScalarTokenInput,
74
170
  {
75
- type: "text",
171
+ name: token.name,
76
172
  value: current,
77
- onChange: (e) => onChange(token.name, e.target.value),
78
- className: "w-20 rounded-lg border border-border bg-card px-2 py-1 text-xs font-mono text-foreground focus:outline-none focus:border-primary"
173
+ onCommit: (next) => onChange?.(token.name, next)
79
174
  }
80
175
  )
81
176
  ] }, token.name);
@@ -83,19 +178,91 @@ function DashboardTokenControls({ tokens, values, onChange }) {
83
178
  }
84
179
  );
85
180
  }
181
+ function createExecutorDocumentHost(executor, range = {}) {
182
+ return {
183
+ runAggregate: (queryText, overrides) => executor.runQuery(queryText, {
184
+ context: overrides.context,
185
+ rangeStart: range.rangeStart,
186
+ rangeEnd: range.rangeEnd,
187
+ preferredUnit: range.preferredUnit
188
+ }),
189
+ runFind: async (queryText, context) => {
190
+ const parsed = parseQuery(queryText);
191
+ if (!isFindQuery(parsed)) return { kind: "find", error: `not a content query: ${queryText}` };
192
+ const res = await executor.runFind(parsed, {
193
+ context,
194
+ range: range.rangeStart !== void 0 ? { start: range.rangeStart, end: range.rangeEnd ?? Number.MAX_SAFE_INTEGER, endExclusive: false } : void 0
195
+ });
196
+ return {
197
+ kind: "find",
198
+ notes: res.notes,
199
+ blocks: res.blocks,
200
+ efforts: res.efforts,
201
+ containers: res.containers,
202
+ runs: res.runs,
203
+ table: res.table
204
+ };
205
+ },
206
+ runPipeline: async (queryText, context) => {
207
+ const res = await executor.runPipeline?.(queryText, {
208
+ context,
209
+ rangeStart: range.rangeStart,
210
+ rangeEnd: range.rangeEnd,
211
+ preferredUnit: range.preferredUnit
212
+ });
213
+ return res ?? { error: "pipeline queries are not supported by this executor" };
214
+ },
215
+ rollupEnsure: range.rollupEnsure
216
+ };
217
+ }
218
+ function outputToRunResult(parsed, out) {
219
+ const base = toChartResult({ parsed, series: out.series, unit: out.unit, error: out.error, table: out.table });
220
+ return Object.assign(base, {
221
+ ...out.notes ? { notes: out.notes } : {},
222
+ ...out.blocks ? { blocks: out.blocks } : {},
223
+ ...out.efforts ? { efforts: out.efforts } : {},
224
+ ...out.containers ? { containers: out.containers } : {},
225
+ ...out.runs ? { runs: out.runs } : {}
226
+ });
227
+ }
86
228
  function spanClass(widget) {
229
+ if (widget.spanFull) return "col-span-1 md:col-span-2 lg:col-span-4";
230
+ if (widget.spanCols != null) {
231
+ if (widget.spanCols <= 1) return "col-span-1";
232
+ if (widget.spanCols === 2) return "col-span-1 md:col-span-2";
233
+ if (widget.spanCols === 3) return "col-span-1 md:col-span-2 lg:col-span-3";
234
+ return "col-span-1 md:col-span-2 lg:col-span-4";
235
+ }
87
236
  const resolved = resolveWidgetType(widget.type);
88
237
  if (resolved === "value") return "col-span-1";
89
238
  if (resolved === "table") return "col-span-1 md:col-span-2";
90
239
  return "col-span-1 md:col-span-2 lg:col-span-4";
91
240
  }
241
+ var SIZE_OPTIONS = [
242
+ { label: "Span 1 column", span: { spanCols: 1 }, testId: "widget-size-1" },
243
+ { label: "Span 2 columns", span: { spanCols: 2 }, testId: "widget-size-2" },
244
+ { label: "Span 4 columns", span: { spanCols: 4 }, testId: "widget-size-4" },
245
+ { label: "Full row", span: { spanFull: true }, testId: "widget-size-full" }
246
+ ];
247
+ function sizeIsActive(widget, span) {
248
+ if (span.spanFull) return widget.spanFull === true;
249
+ return !widget.spanFull && (widget.spanCols ?? null) === (span.spanCols ?? null);
250
+ }
92
251
  function DashboardView({
93
252
  document,
253
+ headings,
94
254
  executor,
95
255
  onEnsureRollupFacts,
96
256
  tokenValues,
97
257
  onTokenChange,
98
- onEditQuery,
258
+ editMode = false,
259
+ onEditWidget,
260
+ onInspectWidget,
261
+ onOpenInExplorer,
262
+ onDuplicateWidget,
263
+ onRemoveWidget,
264
+ onMoveWidget,
265
+ onResizeWidget,
99
266
  rangeStart,
100
267
  rangeEnd,
101
268
  preferredUnit
@@ -106,8 +273,15 @@ function DashboardView({
106
273
  );
107
274
  const resolved = useMemo(
108
275
  () => document.widgets.map((widget) => {
109
- const { query, missing } = substituteTokens(widget.query, values);
110
- return { widget, query, missing };
276
+ const { query, missing } = substituteTokens(widget.body, values);
277
+ const attributes = {};
278
+ const allMissing = new Set(missing);
279
+ for (const [key, value] of Object.entries(widget.attributes)) {
280
+ const sub = substituteTokens(value, values);
281
+ attributes[key] = sub.query;
282
+ for (const m of sub.missing) allMissing.add(m);
283
+ }
284
+ return { widget, query, missing: [...allMissing], attributes };
111
285
  }),
112
286
  [document.widgets, values]
113
287
  );
@@ -137,11 +311,17 @@ function DashboardView({
137
311
  next[widget.key] = { error: parsed.error };
138
312
  continue;
139
313
  }
314
+ const resolvedType = resolveWidgetType(widget.type);
140
315
  if (isFindQuery(parsed)) {
141
- next[widget.key] = { error: "find: queries render inline in notes, not as dashboard widgets" };
316
+ if (resolvedType !== "table" && resolvedType !== "list") {
317
+ next[widget.key] = { error: "content queries return things \u2014 use a table or list widget instead" };
318
+ continue;
319
+ }
320
+ } else if (!isPipelineQuery(parsed) && resolvedType === "list") {
321
+ next[widget.key] = { error: "aggregate queries return numbers \u2014 use a chart or value widget instead" };
142
322
  continue;
143
323
  }
144
- runnable.push({ widget, query });
324
+ runnable.push({ widget, query, parsed });
145
325
  }
146
326
  if (!executor) {
147
327
  if (!cancelled) {
@@ -150,15 +330,26 @@ function DashboardView({
150
330
  }
151
331
  return;
152
332
  }
153
- const consumesRollups = runnable.some((r) => r.query.includes("calc."));
154
- if (consumesRollups && onEnsureRollupFacts) {
155
- await onEnsureRollupFacts().catch(() => void 0);
156
- }
333
+ const host = createExecutorDocumentHost(executor, {
334
+ rangeStart,
335
+ rangeEnd,
336
+ preferredUnit,
337
+ rollupEnsure: onEnsureRollupFacts
338
+ });
157
339
  await Promise.all(
158
- runnable.map(async ({ widget, query }) => {
340
+ runnable.map(async ({ widget, query, parsed }) => {
159
341
  try {
342
+ const runner = new SharedQueryDocumentRunner(host);
343
+ const res = await runner.run(query);
344
+ const out = res.outputs[0];
345
+ if (!out) {
346
+ next[widget.key] = { error: res.diagnostics[0] ?? "widget produced no output" };
347
+ return;
348
+ }
349
+ const error = out.error ?? res.diagnostics[0];
160
350
  next[widget.key] = {
161
- result: await executor.runQuery(query, { rangeStart, rangeEnd, preferredUnit })
351
+ result: outputToRunResult(parsed, out),
352
+ ...error ? { error } : {}
162
353
  };
163
354
  } catch (err) {
164
355
  next[widget.key] = { error: err instanceof Error ? err.message : String(err) };
@@ -174,46 +365,114 @@ function DashboardView({
174
365
  return () => {
175
366
  cancelled = true;
176
367
  };
177
- }, [resolvedKey, executor, onEnsureRollupFacts, rangeStart, rangeEnd, preferredUnit]);
368
+ }, [resolvedKey, resolved, executor, onEnsureRollupFacts, rangeStart, rangeEnd, preferredUnit]);
178
369
  return /* @__PURE__ */ jsxs("div", { "data-testid": "dashboard-view", children: [
179
370
  /* @__PURE__ */ jsx(DashboardTokenControls, { tokens: document.tokens, values, onChange: onTokenChange }),
180
371
  loading && /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground mb-3", children: "Loading widgets\u2026" }),
181
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4", children: resolved.map(({ widget, query }) => {
182
- const run = runs[widget.key];
183
- return /* @__PURE__ */ jsx(
184
- WidgetFrame,
372
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4", children: [
373
+ resolved.map(({ widget, query, attributes }, index) => {
374
+ const run = runs[widget.key];
375
+ const canArrange = editMode && (onEditWidget || onDuplicateWidget || onRemoveWidget || onMoveWidget || onResizeWidget);
376
+ const before = (headings ?? []).filter((h) => h.beforeWidget === index);
377
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
378
+ before.map((h) => /* @__PURE__ */ jsx(
379
+ "div",
380
+ {
381
+ id: h.id,
382
+ className: cn("col-span-full font-semibold text-foreground", h.level <= 2 ? "text-base mt-2" : "text-sm"),
383
+ children: h.text
384
+ },
385
+ h.id
386
+ )),
387
+ /* @__PURE__ */ jsx(
388
+ WidgetFrame,
389
+ {
390
+ id: `widget-${widget.key}`,
391
+ title: widget.title ?? "",
392
+ question: widget.question ?? "",
393
+ query,
394
+ span: spanClass(widget),
395
+ onInspect: onInspectWidget ? () => onInspectWidget(widget) : void 0,
396
+ onOpenInExplorer: onOpenInExplorer ? () => onOpenInExplorer(widget, query) : void 0,
397
+ toolbar: canArrange ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", "data-testid": `widget-toolbar-${widget.key}`, children: [
398
+ onEditWidget && /* @__PURE__ */ jsx(WidgetEditButton, { title: widget.title ?? "", onClick: () => onEditWidget(widget) }),
399
+ onMoveWidget && /* @__PURE__ */ jsxs(Fragment$1, { children: [
400
+ /* @__PURE__ */ jsx(
401
+ WidgetToolButton,
402
+ {
403
+ label: `Move ${widget.title || "widget"} up`,
404
+ disabled: index === 0,
405
+ onClick: () => onMoveWidget(widget, -1),
406
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "w-3.5 h-3.5" })
407
+ }
408
+ ),
409
+ /* @__PURE__ */ jsx(
410
+ WidgetToolButton,
411
+ {
412
+ label: `Move ${widget.title || "widget"} down`,
413
+ disabled: index === resolved.length - 1,
414
+ onClick: () => onMoveWidget(widget, 1),
415
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "w-3.5 h-3.5" })
416
+ }
417
+ )
418
+ ] }),
419
+ onResizeWidget && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-0 rounded bg-muted/60 p-0.5", children: SIZE_OPTIONS.map((option) => /* @__PURE__ */ jsx(
420
+ "button",
421
+ {
422
+ type: "button",
423
+ "data-testid": `${option.testId}-${widget.key}`,
424
+ title: option.label,
425
+ "aria-label": `${option.label} for ${widget.title || "widget"}`,
426
+ "aria-pressed": sizeIsActive(widget, option.span),
427
+ disabled: sizeIsActive(widget, option.span),
428
+ onClick: () => onResizeWidget(widget, option.span),
429
+ className: "inline-flex items-center justify-center px-1 py-0.5 max-lg:min-h-11 max-lg:min-w-11 text-xs font-mono rounded text-muted-foreground hover:text-foreground disabled:text-primary disabled:bg-primary/10",
430
+ children: option.span.spanFull ? /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3" }) : option.span.spanCols
431
+ },
432
+ option.testId
433
+ )) }),
434
+ onDuplicateWidget && /* @__PURE__ */ jsx(
435
+ WidgetToolButton,
436
+ {
437
+ label: `Duplicate ${widget.title || "widget"}`,
438
+ onClick: () => onDuplicateWidget(widget),
439
+ children: /* @__PURE__ */ jsx(Copy, { className: "w-3.5 h-3.5" })
440
+ }
441
+ ),
442
+ onRemoveWidget && /* @__PURE__ */ jsx(
443
+ WidgetToolButton,
444
+ {
445
+ label: `Remove ${widget.title || "widget"}`,
446
+ onClick: () => onRemoveWidget(widget),
447
+ children: /* @__PURE__ */ jsx(Trash2, { className: "w-3.5 h-3.5" })
448
+ }
449
+ )
450
+ ] }) : void 0,
451
+ children: run?.error ? /* @__PURE__ */ jsx(WidgetProblemBadge, { message: run.error }) : /* @__PURE__ */ jsx(
452
+ WidgetChart,
453
+ {
454
+ type: widget.type,
455
+ result: run?.result,
456
+ label: widget.title,
457
+ unit: preferredUnit,
458
+ attributes
459
+ }
460
+ )
461
+ },
462
+ widget.key
463
+ )
464
+ ] }, widget.key);
465
+ }),
466
+ (headings ?? []).filter((h) => h.beforeWidget >= resolved.length).map((h) => /* @__PURE__ */ jsx(
467
+ "div",
185
468
  {
186
- title: widget.title ?? "",
187
- question: widget.question ?? "",
188
- query,
189
- span: spanClass(widget),
190
- children: /* @__PURE__ */ jsxs("div", { className: "relative group/card h-full min-h-[160px]", children: [
191
- onEditQuery && /* @__PURE__ */ jsx(
192
- "button",
193
- {
194
- type: "button",
195
- onClick: () => onEditQuery(widget),
196
- title: "Edit widget query",
197
- "aria-label": `Edit query for ${widget.title ?? "widget"}`,
198
- className: "absolute top-0 right-0 p-1 rounded bg-muted/80 text-muted-foreground hover:text-foreground opacity-0 group-hover/card:opacity-100 transition-opacity z-10",
199
- children: /* @__PURE__ */ jsx(Edit3, { className: "w-3.5 h-3.5" })
200
- }
201
- ),
202
- run?.error ? /* @__PURE__ */ jsx(WidgetProblemBadge, { message: run.error }) : /* @__PURE__ */ jsx(
203
- WidgetChart,
204
- {
205
- type: widget.type,
206
- result: run?.result,
207
- label: widget.title,
208
- unit: preferredUnit,
209
- params: widget.params
210
- }
211
- )
212
- ] })
469
+ id: h.id,
470
+ className: cn("col-span-full font-semibold text-foreground", h.level <= 2 ? "text-base mt-2" : "text-sm"),
471
+ children: h.text
213
472
  },
214
- widget.key
215
- );
216
- }) })
473
+ h.id
474
+ ))
475
+ ] })
217
476
  ] });
218
477
  }
219
478
  function RangeSelector({ weeks: weeksProp, onWeeksChange, className }) {
@@ -257,16 +516,32 @@ function useAnalyticsQueries(queries, weeks, executor, refreshKey = 0, preferred
257
516
  }
258
517
  const now = Date.now();
259
518
  const rangeStart = now - weeks * WEEK;
260
- const consumesRollups = queries.some((q) => q.query.includes("calc."));
519
+ const host = createExecutorDocumentHost(executor, {
520
+ rangeStart,
521
+ rangeEnd: now,
522
+ preferredUnit,
523
+ rollupEnsure: onEnsureRollupFacts
524
+ });
525
+ const runner = new SharedQueryDocumentRunner(host);
261
526
  try {
262
- if (consumesRollups && onEnsureRollupFacts) {
263
- await onEnsureRollupFacts().catch(() => void 0);
264
- }
265
527
  const settled = await Promise.all(
266
- queries.map(async (q) => [
267
- q.key,
268
- await executor.runQuery(q.query, { rangeStart, rangeEnd: now, preferredUnit })
269
- ])
528
+ queries.map(async (q) => {
529
+ const res = await runner.run(q.query);
530
+ const out = res.outputs[0];
531
+ const error = out?.error ?? res.diagnostics[0];
532
+ const result = toChartResult({
533
+ parsed: parseQuery(q.query),
534
+ series: out?.series,
535
+ unit: out?.unit,
536
+ error,
537
+ table: out?.table
538
+ });
539
+ if (out?.notes) result.notes = out.notes;
540
+ if (out?.blocks) result.blocks = out.blocks;
541
+ if (out?.efforts) result.efforts = out.efforts;
542
+ if (out?.runs) result.runs = out.runs;
543
+ return [q.key, result];
544
+ })
270
545
  );
271
546
  if (!cancelled) setResults(Object.fromEntries(settled));
272
547
  } finally {
@@ -405,15 +680,15 @@ function AnalyticsUnitPreference({ className, unit: unitProp, forced = false, st
405
680
  }
406
681
 
407
682
  // src/version.ts
408
- var VERSION = "0.11.43";
409
- var GIT_SHA = "ed28149";
410
- var BUILD_TIME = "2026-08-26T16:34:15.927Z";
683
+ var VERSION = "0.35.2116";
684
+ var GIT_SHA = "a4d411f";
685
+ var BUILD_TIME = "2026-10-12T00:14:24.795Z";
411
686
  var SEMVER = {
412
- version: "0.11.43",
413
- gitSha: "ed28149",
414
- buildTime: "2026-08-26T16:34:15.927Z"
687
+ version: "0.35.2116",
688
+ gitSha: "a4d411f",
689
+ buildTime: "2026-10-12T00:14:24.795Z"
415
690
  };
416
691
 
417
- export { ANALYTICS_UNIT_STORAGE_KEY, AnalyticsUnitPreference, BUILD_TIME, CODEMIRROR_SINGLETON_DEPS, DEFAULT_ANALYTICS_UNIT, DashboardTokenControls, DashboardView, GIT_SHA, RangeSelector, SEMVER, VERSION, WidgetFrame, getDashboardEffectiveUnit, getEffectiveAnalyticsUnit, readStoredUnit, useAnalyticsQueries, useAnalyticsUnitPreference };
692
+ export { ANALYTICS_UNIT_STORAGE_KEY, AnalyticsUnitPreference, BUILD_TIME, CODEMIRROR_SINGLETON_DEPS, DEFAULT_ANALYTICS_UNIT, DashboardTokenControls, DashboardView, GIT_SHA, RangeSelector, SEMVER, VERSION, WidgetEditButton, WidgetFrame, WidgetToolButton, getDashboardEffectiveUnit, getEffectiveAnalyticsUnit, readStoredUnit, useAnalyticsQueries, useAnalyticsUnitPreference };
418
693
  //# sourceMappingURL=index.mjs.map
419
694
  //# sourceMappingURL=index.mjs.map