@camstack/ui-library 1.2.383 → 1.2.384

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
@@ -24107,6 +24107,22 @@ function formatCursorTime(t, spanMs) {
24107
24107
  return `${d.toLocaleDateString(void 0, DATE_DM)} ${d.toLocaleTimeString(void 0, TIME_HM)}`;
24108
24108
  }
24109
24109
  //#endregion
24110
+ //#region src/composites/charts/chart-bands.ts
24111
+ var TONE_TOKEN = {
24112
+ neutral: "foreground-subtle",
24113
+ info: "info",
24114
+ success: "success",
24115
+ warning: "warning",
24116
+ danger: "danger"
24117
+ };
24118
+ function bandToken(tone) {
24119
+ return TONE_TOKEN[tone];
24120
+ }
24121
+ /** The bands that contain `x` (edges included), in input order. */
24122
+ function bandsAt(bands, x) {
24123
+ return bands.filter((b) => x >= b.from && x <= b.to);
24124
+ }
24125
+ //#endregion
24110
24126
  //#region src/composites/charts/chart-scale.ts
24111
24127
  var LINEAR_MAX_MANTISSAS = [
24112
24128
  1,
@@ -24338,6 +24354,23 @@ function gapShadeHook(spans) {
24338
24354
  ctx.restore();
24339
24355
  };
24340
24356
  }
24357
+ /** Paint the bands behind the lines, each in its tone. */
24358
+ function bandShadeHook(bands) {
24359
+ return (u) => {
24360
+ const list = bands();
24361
+ if (list.length === 0) return;
24362
+ const ctx = u.ctx;
24363
+ ctx.save();
24364
+ for (const b of list) {
24365
+ const x0 = u.valToPos(b.from, "x", true);
24366
+ const x1 = u.valToPos(b.to, "x", true);
24367
+ ctx.fillStyle = withAlpha(resolveChartColor(bandToken(b.tone)), BAND_SHADE_ALPHA);
24368
+ ctx.fillRect(x0, u.bbox.top, Math.max(1, x1 - x0), u.bbox.height);
24369
+ }
24370
+ ctx.restore();
24371
+ };
24372
+ }
24373
+ var BAND_SHADE_ALPHA = .16;
24341
24374
  var BYTE_UNITS = new Set([
24342
24375
  "B",
24343
24376
  "bytes",
@@ -24692,7 +24725,7 @@ function lineTooltipBody(input) {
24692
24725
  preference: input.preference
24693
24726
  })
24694
24727
  }));
24695
- return asBody(input.timeLabel, lines);
24728
+ return asBody(input.timeLabel, [...lines, ...input.extra ?? []]);
24696
24729
  }
24697
24730
  /** The state in force at `t`, and how long that span lasts. */
24698
24731
  function stateSpanAt(segments, t) {
@@ -42821,6 +42854,176 @@ function CopyButton({ value, label, srLabel, className, disabled }) {
42821
42854
  });
42822
42855
  }
42823
42856
  //#endregion
42857
+ //#region src/composites/device-history/history-runs.ts
42858
+ /** The context kept on each side of a run when a row opens it. */
42859
+ var RUN_WINDOW_MARGIN_MS = 15 * 6e4;
42860
+ function runStatus(run) {
42861
+ return run.state === "open" ? "running" : run.outcome ?? "unknown";
42862
+ }
42863
+ function runEnd(run, now) {
42864
+ return run.state === "open" || run.endedAt === null ? now : run.endedAt;
42865
+ }
42866
+ /** Elapsed time of a run; an open one counts up to `now`. `null` when a closed run has no duration. */
42867
+ function runDurationMs(run, now) {
42868
+ if (run.state === "open") return Math.max(0, now - run.startedAt);
42869
+ if (run.durationMs !== null) return run.durationMs;
42870
+ return run.endedAt === null ? null : Math.max(0, run.endedAt - run.startedAt);
42871
+ }
42872
+ function formatKwh(kwh) {
42873
+ return kwh === null ? "—" : `${kwh.toFixed(2)} kWh`;
42874
+ }
42875
+ function formatWatts(w) {
42876
+ return w === null ? "—" : `${String(Math.round(w))} W`;
42877
+ }
42878
+ function formatRunDuration(ms) {
42879
+ return ms === null ? "—" : formatDurationMs(ms);
42880
+ }
42881
+ var STATUS_TONE = {
42882
+ running: "info",
42883
+ completed: "success",
42884
+ aborted: "warning",
42885
+ error: "danger",
42886
+ unknown: "neutral"
42887
+ };
42888
+ function runTone(status) {
42889
+ return STATUS_TONE[status];
42890
+ }
42891
+ /** The window that frames a run: the run plus {@link RUN_WINDOW_MARGIN_MS} each side. */
42892
+ function runWindow(run, now) {
42893
+ return {
42894
+ from: run.startedAt - RUN_WINDOW_MARGIN_MS,
42895
+ to: runEnd(run, now) + RUN_WINDOW_MARGIN_MS
42896
+ };
42897
+ }
42898
+ /** One band per run that overlaps `window`; the label states the duration and the energy. */
42899
+ function runBands(runs, now, window, t) {
42900
+ const title = t("uiLibrary.history.runs.band", "Run");
42901
+ return runs.filter((r) => runEnd(r, now) > window.from && r.startedAt < window.to).map((r) => {
42902
+ const status = runStatus(r);
42903
+ const parts = [formatRunDuration(runDurationMs(r, now)), formatKwh(r.detail.energyKwh)];
42904
+ return {
42905
+ id: r.runId,
42906
+ from: r.startedAt,
42907
+ to: runEnd(r, now),
42908
+ title,
42909
+ label: status === "running" ? t("uiLibrary.history.runs.bandRunning", "running · {{parts}}", { parts: parts.join(" · ") }) : parts.join(" · "),
42910
+ tone: runTone(status)
42911
+ };
42912
+ });
42913
+ }
42914
+ //#endregion
42915
+ //#region src/composites/DeviceRunsPanel.tsx
42916
+ /**
42917
+ * DeviceRunsPanel — the runs of an appliance, newest first, on the device's
42918
+ * History tab: start, end, duration, energy, peak and mean power, outcome.
42919
+ * A row opens the run's window (the run plus a margin) in the history view.
42920
+ *
42921
+ * Presentational: the caller reads the runs (`useDeviceRuns`) and decides
42922
+ * whether the device has any — the panel never answers "no runs" itself, an
42923
+ * unanswered query is not an empty one (D315). A measurement that failed is
42924
+ * `—`, never `0` (D393).
42925
+ */
42926
+ function statusLabel(status, t) {
42927
+ switch (status) {
42928
+ case "running": return t("uiLibrary.history.runs.status.running", "Running");
42929
+ case "completed": return t("uiLibrary.history.runs.status.completed", "Completed");
42930
+ case "aborted": return t("uiLibrary.history.runs.status.aborted", "Aborted");
42931
+ case "error": return t("uiLibrary.history.runs.status.error", "Error");
42932
+ case "unknown": return t("uiLibrary.history.runs.status.unknown", "Unknown");
42933
+ }
42934
+ }
42935
+ var formatStamp = (ms) => new Date(ms).toLocaleString(void 0, {
42936
+ dateStyle: "short",
42937
+ timeStyle: "short"
42938
+ });
42939
+ function runColumns(now, t) {
42940
+ return [
42941
+ {
42942
+ key: "start",
42943
+ header: t("uiLibrary.history.runs.start", "Start"),
42944
+ render: (r) => formatStamp(r.startedAt),
42945
+ sortValue: (r) => r.startedAt
42946
+ },
42947
+ {
42948
+ key: "end",
42949
+ header: t("uiLibrary.history.runs.end", "End"),
42950
+ render: (r) => r.state === "open" || r.endedAt === null ? t("uiLibrary.history.runs.runningSince", "Running since {{time}}", { time: formatStamp(r.startedAt) }) : formatStamp(r.endedAt),
42951
+ sortValue: (r) => r.endedAt
42952
+ },
42953
+ {
42954
+ key: "duration",
42955
+ header: t("uiLibrary.history.runs.duration", "Duration"),
42956
+ align: "right",
42957
+ render: (r) => formatRunDuration(runDurationMs(r, now)),
42958
+ sortValue: (r) => runDurationMs(r, now)
42959
+ },
42960
+ {
42961
+ key: "energy",
42962
+ header: t("uiLibrary.history.runs.energy", "Energy"),
42963
+ align: "right",
42964
+ render: (r) => formatKwh(r.detail.energyKwh),
42965
+ sortValue: (r) => r.detail.energyKwh
42966
+ },
42967
+ {
42968
+ key: "peak",
42969
+ header: t("uiLibrary.history.runs.peak", "Peak"),
42970
+ align: "right",
42971
+ render: (r) => formatWatts(r.detail.peakW),
42972
+ sortValue: (r) => r.detail.peakW
42973
+ },
42974
+ {
42975
+ key: "avg",
42976
+ header: t("uiLibrary.history.runs.avg", "Average"),
42977
+ align: "right",
42978
+ render: (r) => formatWatts(r.detail.avgW),
42979
+ sortValue: (r) => r.detail.avgW
42980
+ },
42981
+ {
42982
+ key: "outcome",
42983
+ header: t("uiLibrary.history.runs.outcome", "Outcome"),
42984
+ render: (r) => {
42985
+ const status = runStatus(r);
42986
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
42987
+ variant: runTone(status),
42988
+ children: statusLabel(status, t)
42989
+ });
42990
+ },
42991
+ notSortableReason: t("uiLibrary.history.runs.outcomeNoSort", "Outcomes have no order")
42992
+ }
42993
+ ];
42994
+ }
42995
+ function DeviceRunsPanel({ runs, now, onSelectWindow, className }) {
42996
+ const t = useUiText();
42997
+ const [expanded, setExpanded] = (0, react$1.useState)(true);
42998
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleCard, {
42999
+ className,
43000
+ expanded,
43001
+ onExpandedChange: setExpanded,
43002
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
43003
+ className: "flex items-center gap-2 min-w-0",
43004
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
43005
+ className: "text-xs font-semibold text-foreground truncate",
43006
+ children: t("uiLibrary.history.runs.title", "Runs")
43007
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
43008
+ className: "text-[10px] text-foreground-subtle shrink-0",
43009
+ children: t("uiLibrary.history.runs.count", {
43010
+ one: "{{count}} run",
43011
+ other: "{{count}} runs"
43012
+ }, { count: runs.length })
43013
+ })]
43014
+ }),
43015
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTable, {
43016
+ columns: runColumns(now, t),
43017
+ rows: runs,
43018
+ rowKey: (r) => r.runId,
43019
+ onRowClick: (r) => onSelectWindow(runWindow(r, now)),
43020
+ maxBodyHeight: TABLE_SCROLL_MAX_H,
43021
+ bordered: false,
43022
+ minWidthPx: 640
43023
+ })
43024
+ });
43025
+ }
43026
+ //#endregion
42824
43027
  //#region src/composites/device-list/fuzzy-match.ts
42825
43028
  /**
42826
43029
  * fuzzy-match — case and separator-insensitive text search helper.
@@ -71771,6 +71974,7 @@ function latestValues(model) {
71771
71974
  * for the cross-device History page, where the reader chose the series.
71772
71975
  */
71773
71976
  var DEFAULT_HEIGHT = 120;
71977
+ var NO_BANDS = [];
71774
71978
  function seriesSpan(range, xs) {
71775
71979
  if (range !== void 0) return range.to - range.from;
71776
71980
  if (xs.length < 2) return 0;
@@ -71797,18 +72001,25 @@ function seriesTooltip(input) {
71797
72001
  value: values[i] ?? null,
71798
72002
  quantity: sample.quantity,
71799
72003
  unit: sample.unit ?? null
72004
+ })),
72005
+ extra: bandsAt(input.bands, at).map((b) => ({
72006
+ id: `band:${b.id}`,
72007
+ label: b.title,
72008
+ value: b.label
71800
72009
  }))
71801
72010
  });
71802
72011
  },
71803
72012
  onDataX: input.onDataX
71804
72013
  };
71805
72014
  }
71806
- function TimeSeriesChart({ title, series, range, height = DEFAULT_HEIGHT, stacked = false, area = false, syncKey, formatValue, displayPreference, axisFor, emptyMessage, pending = false, actions, footer, windowToggle, className }) {
72015
+ function TimeSeriesChart({ title, series, bands = NO_BANDS, range, height = DEFAULT_HEIGHT, stacked = false, area = false, syncKey, formatValue, displayPreference, axisFor, emptyMessage, pending = false, actions, footer, windowToggle, className }) {
71807
72016
  const t = useUiText();
71808
72017
  const model = (0, react$1.useMemo)(() => buildTimeSeriesModel(series, stacked), [series, stacked]);
71809
72018
  const [cursorIdx, setCursorIdx] = (0, react$1.useState)(null);
71810
72019
  const modelRef = (0, react$1.useRef)(model);
71811
72020
  modelRef.current = model;
72021
+ const bandsRef = (0, react$1.useRef)(bands);
72022
+ bandsRef.current = bands;
71812
72023
  const rangeRef = (0, react$1.useRef)(range);
71813
72024
  rangeRef.current = range;
71814
72025
  const fmt = (0, react$1.useCallback)((v, unit) => (formatValue ?? formatChartValue)(v, unit), [formatValue]);
@@ -71937,7 +72148,7 @@ function TimeSeriesChart({ title, series, range, height = DEFAULT_HEIGHT, stacke
71937
72148
  bands,
71938
72149
  cursor: chartCursor(syncKey),
71939
72150
  hooks: {
71940
- drawClear: [gapShadeHook(() => modelRef.current.gaps)],
72151
+ drawClear: [bandShadeHook(() => bandsRef.current), gapShadeHook(() => modelRef.current.gaps)],
71941
72152
  setCursor: [(u) => setCursorIdx(u.cursor.idx ?? null)]
71942
72153
  }
71943
72154
  };
@@ -71949,6 +72160,7 @@ function TimeSeriesChart({ title, series, range, height = DEFAULT_HEIGHT, stacke
71949
72160
  const tooltip = (0, react$1.useMemo)(() => seriesTooltip({
71950
72161
  model,
71951
72162
  series,
72163
+ bands,
71952
72164
  range,
71953
72165
  preference: displayPreference,
71954
72166
  onDataX: (dataX) => {
@@ -71957,6 +72169,7 @@ function TimeSeriesChart({ title, series, range, height = DEFAULT_HEIGHT, stacke
71957
72169
  }), [
71958
72170
  model,
71959
72171
  series,
72172
+ bands,
71960
72173
  range,
71961
72174
  displayPreference
71962
72175
  ]);
@@ -72744,7 +72957,7 @@ function booleanLabels(key, t) {
72744
72957
  function failure(entry) {
72745
72958
  return entry !== void 0 && !entry.ok ? entry.error : null;
72746
72959
  }
72747
- function HistoryChart({ spec, entryFor, reads, window, compare, syncKey, actions }) {
72960
+ function HistoryChart({ spec, entryFor, reads, window, compare, syncKey, actions, runs, now }) {
72748
72961
  const t = useUiText();
72749
72962
  const pending = reads === null;
72750
72963
  if (spec.type === "states") {
@@ -72819,13 +73032,15 @@ function HistoryChart({ spec, entryFor, reads, window, compare, syncKey, actions
72819
73032
  compare,
72820
73033
  syncKey,
72821
73034
  actions,
73035
+ runs: spec.series.some((s) => s.key === "cap:power-meter.power") ? runs : void 0,
73036
+ now,
72822
73037
  labelOf: (s) => spec.series.length > 1 || s.scope !== "" ? seriesLabel(s) : spec.title
72823
73038
  });
72824
73039
  }
72825
73040
  function seriesLabel(s) {
72826
73041
  return s.scope === "" ? s.displayName : `${s.displayName} · ${s.scope}`;
72827
73042
  }
72828
- function LinesChart({ title, series, entryFor, reads, window, compare, syncKey, actions, labelOf, toolbar, colorOf }) {
73043
+ function LinesChart({ title, series, entryFor, reads, window, compare, syncKey, actions, labelOf, toolbar, colorOf, runs, now }) {
72829
73044
  const t = useUiText();
72830
73045
  const errors = [...new Set(series.map((s) => failure(entryFor(s.key, false))).filter((e) => e !== null))];
72831
73046
  const defs = (0, react$1.useMemo)(() => {
@@ -72875,6 +73090,12 @@ function LinesChart({ title, series, entryFor, reads, window, compare, syncKey,
72875
73090
  colorOf,
72876
73091
  t
72877
73092
  ]);
73093
+ const bands = (0, react$1.useMemo)(() => runs === void 0 ? [] : runBands(runs, now ?? window.to, window, t), [
73094
+ runs,
73095
+ now,
73096
+ window,
73097
+ t
73098
+ ]);
72878
73099
  if (errors.length > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadFailed, {
72879
73100
  title,
72880
73101
  error: errors.join("; "),
@@ -72883,6 +73104,7 @@ function LinesChart({ title, series, entryFor, reads, window, compare, syncKey,
72883
73104
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimeSeriesChart, {
72884
73105
  title,
72885
73106
  series: defs,
73107
+ bands,
72886
73108
  range: window,
72887
73109
  syncKey,
72888
73110
  pending: reads === null,
@@ -73218,6 +73440,24 @@ function HistoryWindowBar({ value, onChange, custom, onCustomChange, compare, on
73218
73440
  });
73219
73441
  }
73220
73442
  //#endregion
73443
+ //#region src/composites/device-history/use-device-runs.ts
73444
+ /** How many runs the panel and the bands are given; the cap's page size. */
73445
+ var DEVICE_RUNS_PAGE = 50;
73446
+ var REFRESH_MS = 6e4;
73447
+ /** The newest runs, or `undefined` while unanswered, absent or empty. */
73448
+ function useDeviceRuns(deviceId) {
73449
+ const q = useDeviceRunsListRuns({
73450
+ deviceId,
73451
+ limit: 50
73452
+ }, {
73453
+ staleTime: REFRESH_MS,
73454
+ refetchInterval: REFRESH_MS,
73455
+ retry: false
73456
+ });
73457
+ if (q.status !== "success" || q.data.runs.length === 0) return void 0;
73458
+ return q.data.runs;
73459
+ }
73460
+ //#endregion
73221
73461
  //#region src/composites/device-history/history-reads.ts
73222
73462
  /**
73223
73463
  * Batched history reads. `queryMany` answers up to 24 reads over one window
@@ -73502,7 +73742,9 @@ function DeviceHistoryView({ deviceId, fixedWindow, onNavigate }) {
73502
73742
  const custom = fixedWindow ? null : decodeCustomWindow(customParam);
73503
73743
  const compare = fixedWindow === void 0 && compareParam === "1";
73504
73744
  const spanGuess = resolveHistoryWindow(windowId, Date.now(), custom);
73505
- const range = resolveHistoryWindow(windowId, anchorNow(Date.now(), spanGuess.to - spanGuess.from), custom);
73745
+ const now = anchorNow(Date.now(), spanGuess.to - spanGuess.from);
73746
+ const range = resolveHistoryWindow(windowId, now, custom);
73747
+ const runs = useDeviceRuns(deviceId);
73506
73748
  const { presence, list } = useDeviceSeriesList(deviceId);
73507
73749
  const panels = useHistoryPanels();
73508
73750
  const hidden = (0, react$1.useMemo)(() => new Set(panels.value.hidden[String(deviceId)] ?? []), [panels.value.hidden, deviceId]);
@@ -73570,81 +73812,95 @@ function DeviceHistoryView({ deviceId, fixedWindow, onNavigate }) {
73570
73812
  description: t("uiLibrary.history.empty", "A value appears here once it changes, or at the next five-minute check.")
73571
73813
  });
73572
73814
  const syncKey = `device-history:${String(deviceId)}`;
73815
+ const showWindow = (w) => {
73816
+ setWindowParam("custom");
73817
+ setCustomParam(encodeCustomWindow(w));
73818
+ };
73573
73819
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
73574
73820
  className: STACK_GAP,
73575
- children: [fixedWindow === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
73576
- className: "flex flex-wrap items-center justify-between gap-2",
73577
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryWindowBar, {
73578
- value: windowId,
73579
- onChange: (id) => setWindowParam(id === "24h" ? null : id),
73580
- custom,
73581
- onCustomChange: (w) => setCustomParam(encodeCustomWindow(w)),
73582
- compare,
73583
- onCompareChange: (on) => setCompareParam(on ? "1" : null)
73584
- }), hiddenCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
73585
- variant: "ghost",
73586
- size: "sm",
73587
- onClick: showAllHidden,
73588
- disabled: !panels.writable,
73589
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EyeOff, {
73590
- className: "h-3.5 w-3.5",
73591
- "aria-hidden": "true"
73592
- }), t("uiLibrary.history.showHidden", {
73593
- one: "Show {{count}} hidden chart",
73594
- other: "Show {{count}} hidden charts"
73595
- }, { count: hiddenCount })]
73596
- })]
73597
- }), visible.map((section) => {
73598
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleCard, {
73599
- expanded: !collapsed.has(section.id),
73600
- onExpandedChange: (next) => setCollapsed((prev) => {
73601
- const s = new Set(prev);
73602
- if (next) s.delete(section.id);
73603
- else s.add(section.id);
73604
- return s;
73605
- }),
73606
- header: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
73607
- className: "flex items-center gap-2 min-w-0",
73608
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
73609
- className: "text-xs font-semibold text-foreground truncate",
73610
- children: humanizeName(section.name)
73611
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
73612
- className: "text-[10px] text-foreground-subtle shrink-0",
73613
- children: t("uiLibrary.history.chartCount", {
73614
- one: "{{count}} chart",
73615
- other: "{{count}} charts"
73616
- }, { count: section.charts.length })
73617
- })]
73618
- }),
73619
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
73620
- className: "grid grid-cols-1 gap-4 2xl:grid-cols-2",
73621
- children: section.charts.map((spec) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryChart, {
73622
- spec,
73623
- entryFor,
73624
- reads: readsData,
73625
- window: range,
73626
- compare,
73627
- syncKey,
73628
- actions: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KebabMenu, {
73629
- title: t("uiLibrary.history.chartMenu", "Chart actions"),
73630
- items: [{
73631
- id: "open",
73632
- label: t("uiLibrary.history.openInHistory", "Open in History"),
73633
- icon: ChartLine,
73634
- onClick: () => navigate(historyPageHref(refsOf(deviceId, spec), windowId))
73635
- }, {
73636
- id: "hide",
73637
- label: t("uiLibrary.history.hide", "Hide"),
73638
- icon: EyeOff,
73639
- hidden: fixedWindow !== void 0,
73640
- disabled: !panels.writable,
73641
- onClick: () => setHidden(spec.id, true)
73642
- }]
73643
- })
73644
- }, spec.id))
73645
- })
73646
- }, section.id);
73647
- })]
73821
+ children: [
73822
+ fixedWindow === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
73823
+ className: "flex flex-wrap items-center justify-between gap-2",
73824
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryWindowBar, {
73825
+ value: windowId,
73826
+ onChange: (id) => setWindowParam(id === "24h" ? null : id),
73827
+ custom,
73828
+ onCustomChange: (w) => setCustomParam(encodeCustomWindow(w)),
73829
+ compare,
73830
+ onCompareChange: (on) => setCompareParam(on ? "1" : null)
73831
+ }), hiddenCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
73832
+ variant: "ghost",
73833
+ size: "sm",
73834
+ onClick: showAllHidden,
73835
+ disabled: !panels.writable,
73836
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EyeOff, {
73837
+ className: "h-3.5 w-3.5",
73838
+ "aria-hidden": "true"
73839
+ }), t("uiLibrary.history.showHidden", {
73840
+ one: "Show {{count}} hidden chart",
73841
+ other: "Show {{count}} hidden charts"
73842
+ }, { count: hiddenCount })]
73843
+ })]
73844
+ }),
73845
+ fixedWindow === void 0 && runs !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceRunsPanel, {
73846
+ runs,
73847
+ now,
73848
+ onSelectWindow: showWindow
73849
+ }),
73850
+ visible.map((section) => {
73851
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleCard, {
73852
+ expanded: !collapsed.has(section.id),
73853
+ onExpandedChange: (next) => setCollapsed((prev) => {
73854
+ const s = new Set(prev);
73855
+ if (next) s.delete(section.id);
73856
+ else s.add(section.id);
73857
+ return s;
73858
+ }),
73859
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
73860
+ className: "flex items-center gap-2 min-w-0",
73861
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
73862
+ className: "text-xs font-semibold text-foreground truncate",
73863
+ children: humanizeName(section.name)
73864
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
73865
+ className: "text-[10px] text-foreground-subtle shrink-0",
73866
+ children: t("uiLibrary.history.chartCount", {
73867
+ one: "{{count}} chart",
73868
+ other: "{{count}} charts"
73869
+ }, { count: section.charts.length })
73870
+ })]
73871
+ }),
73872
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
73873
+ className: "grid grid-cols-1 gap-4 2xl:grid-cols-2",
73874
+ children: section.charts.map((spec) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryChart, {
73875
+ spec,
73876
+ entryFor,
73877
+ reads: readsData,
73878
+ window: range,
73879
+ compare,
73880
+ syncKey,
73881
+ runs,
73882
+ now,
73883
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KebabMenu, {
73884
+ title: t("uiLibrary.history.chartMenu", "Chart actions"),
73885
+ items: [{
73886
+ id: "open",
73887
+ label: t("uiLibrary.history.openInHistory", "Open in History"),
73888
+ icon: ChartLine,
73889
+ onClick: () => navigate(historyPageHref(refsOf(deviceId, spec), windowId))
73890
+ }, {
73891
+ id: "hide",
73892
+ label: t("uiLibrary.history.hide", "Hide"),
73893
+ icon: EyeOff,
73894
+ hidden: fixedWindow !== void 0,
73895
+ disabled: !panels.writable,
73896
+ onClick: () => setHidden(spec.id, true)
73897
+ }]
73898
+ })
73899
+ }, spec.id))
73900
+ })
73901
+ }, section.id);
73902
+ })
73903
+ ]
73648
73904
  });
73649
73905
  }
73650
73906
  function HistorySkeleton() {
@@ -73804,7 +74060,7 @@ function TimelineSlider({ markers, bands = [], selectedId = null, step = 5, aria
73804
74060
  }
73805
74061
  //#endregion
73806
74062
  //#region src/ui-library-version.ts
73807
- var uiLibraryVersion = "1.2.383";
74063
+ var uiLibraryVersion = "1.2.384";
73808
74064
  //#endregion
73809
74065
  //#region src/hooks/use-account-unit-preferences.ts
73810
74066
  /**
@@ -74909,6 +75165,7 @@ exports.DEVICE_NAME_JOIN = DEVICE_NAME_JOIN;
74909
75165
  exports.DEVICE_PICKER_GROUP_STORAGE_KEY = DEVICE_PICKER_GROUP_STORAGE_KEY;
74910
75166
  exports.DEVICE_ROLE_META = DEVICE_ROLE_META;
74911
75167
  exports.DEVICE_ROLE_NOUNS = DEVICE_ROLE_NOUNS;
75168
+ exports.DEVICE_RUNS_PAGE = DEVICE_RUNS_PAGE;
74912
75169
  exports.DEVICE_TYPE_CONTROL = DEVICE_TYPE_CONTROL;
74913
75170
  exports.DEVICE_TYPE_META = DEVICE_TYPE_META;
74914
75171
  exports.DEVICE_TYPE_NOUNS = DEVICE_TYPE_NOUNS;
@@ -74939,6 +75196,7 @@ exports.DeviceMultiSelectField = DeviceMultiSelectField;
74939
75196
  exports.DeviceName = DeviceName;
74940
75197
  exports.DevicePicker = DevicePicker;
74941
75198
  exports.DeviceQuickDetailProvider = DeviceQuickDetailProvider;
75199
+ exports.DeviceRunsPanel = DeviceRunsPanel;
74942
75200
  exports.DeviceSelectField = DeviceSelectField;
74943
75201
  exports.DeviceSelectorPicker = DeviceSelectorPicker;
74944
75202
  exports.DeviceStepMatrix = DeviceStepMatrix;
@@ -75188,6 +75446,7 @@ exports.RIGHT = RIGHT;
75188
75446
  exports.ROI_AREA_WARN_FRACTION = ROI_AREA_WARN_FRACTION;
75189
75447
  exports.ROLE_DESCRIPTOR = ROLE_DESCRIPTOR;
75190
75448
  exports.ROOT_DETECTOR_STEP_ID = ROOT_DETECTOR_STEP_ID;
75449
+ exports.RUN_WINDOW_MARGIN_MS = RUN_WINDOW_MARGIN_MS;
75191
75450
  exports.Radar = Radar;
75192
75451
  exports.RadialGauge = RadialGauge;
75193
75452
  exports.Radio = Radio;
@@ -75685,6 +75944,8 @@ exports.roiTooLarge = roiTooLarge;
75685
75944
  exports.roiTooLarge$1 = roiTooLarge;
75686
75945
  exports.rootSourceState = rootSourceState;
75687
75946
  exports.routerBasename = routerBasename;
75947
+ exports.runBands = runBands;
75948
+ exports.runWindow = runWindow;
75688
75949
  exports.runningDurationSince = runningDurationSince;
75689
75950
  exports.runningStatusParts = runningStatusParts;
75690
75951
  exports.sameDeviceName = sameDeviceName;
@@ -76180,6 +76441,7 @@ exports.useDeviceProviderTestCreationField = useDeviceProviderTestCreationField;
76180
76441
  exports.useDeviceProxy = useDeviceProxy;
76181
76442
  exports.useDeviceProxy$1 = useDeviceProxy;
76182
76443
  exports.useDeviceQuickDetailOpen = useDeviceQuickDetailOpen;
76444
+ exports.useDeviceRuns = useDeviceRuns;
76183
76445
  exports.useDeviceRunsGetCurrentRun = useDeviceRunsGetCurrentRun;
76184
76446
  exports.useDeviceRunsGetRun = useDeviceRunsGetRun;
76185
76447
  exports.useDeviceRunsListRuns = useDeviceRunsListRuns;