@camstack/ui-library 1.2.383 → 1.2.385

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
@@ -11777,12 +11777,6 @@ var useIntegrationsCreate = /*#__PURE__*/ alias(() => trpc.integrations.create.u
11777
11777
  var useIntegrationsUpdate = /*#__PURE__*/ alias(() => trpc.integrations.update.useMutation);
11778
11778
  /** Generated alias around `trpc.integrations.delete.useMutation`. */
11779
11779
  var useIntegrationsDelete = /*#__PURE__*/ alias(() => trpc.integrations.delete.useMutation);
11780
- /** Generated alias around `trpc.integrations.getSettings.useQuery`. */
11781
- var useIntegrationsGetSettings = /*#__PURE__*/ alias(() => trpc.integrations.getSettings.useQuery);
11782
- /** Generated alias around `trpc.integrations.setSettings.useMutation`. */
11783
- var useIntegrationsSetSettings = /*#__PURE__*/ alias(() => trpc.integrations.setSettings.useMutation);
11784
- /** Generated alias around `trpc.integrations.clearSettings.useMutation`. */
11785
- var useIntegrationsClearSettings = /*#__PURE__*/ alias(() => trpc.integrations.clearSettings.useMutation);
11786
11780
  /** Generated alias around `trpc.integrations.getAvailableTypes.useQuery`. */
11787
11781
  var useIntegrationsGetAvailableTypes = /*#__PURE__*/ alias(() => trpc.integrations.getAvailableTypes.useQuery);
11788
11782
  /** Generated alias around `trpc.integrations.testConnection.useMutation`. */
@@ -24107,6 +24101,22 @@ function formatCursorTime(t, spanMs) {
24107
24101
  return `${d.toLocaleDateString(void 0, DATE_DM)} ${d.toLocaleTimeString(void 0, TIME_HM)}`;
24108
24102
  }
24109
24103
  //#endregion
24104
+ //#region src/composites/charts/chart-bands.ts
24105
+ var TONE_TOKEN = {
24106
+ neutral: "foreground-subtle",
24107
+ info: "info",
24108
+ success: "success",
24109
+ warning: "warning",
24110
+ danger: "danger"
24111
+ };
24112
+ function bandToken(tone) {
24113
+ return TONE_TOKEN[tone];
24114
+ }
24115
+ /** The bands that contain `x` (edges included), in input order. */
24116
+ function bandsAt(bands, x) {
24117
+ return bands.filter((b) => x >= b.from && x <= b.to);
24118
+ }
24119
+ //#endregion
24110
24120
  //#region src/composites/charts/chart-scale.ts
24111
24121
  var LINEAR_MAX_MANTISSAS = [
24112
24122
  1,
@@ -24338,6 +24348,23 @@ function gapShadeHook(spans) {
24338
24348
  ctx.restore();
24339
24349
  };
24340
24350
  }
24351
+ /** Paint the bands behind the lines, each in its tone. */
24352
+ function bandShadeHook(bands) {
24353
+ return (u) => {
24354
+ const list = bands();
24355
+ if (list.length === 0) return;
24356
+ const ctx = u.ctx;
24357
+ ctx.save();
24358
+ for (const b of list) {
24359
+ const x0 = u.valToPos(b.from, "x", true);
24360
+ const x1 = u.valToPos(b.to, "x", true);
24361
+ ctx.fillStyle = withAlpha(resolveChartColor(bandToken(b.tone)), BAND_SHADE_ALPHA);
24362
+ ctx.fillRect(x0, u.bbox.top, Math.max(1, x1 - x0), u.bbox.height);
24363
+ }
24364
+ ctx.restore();
24365
+ };
24366
+ }
24367
+ var BAND_SHADE_ALPHA = .16;
24341
24368
  var BYTE_UNITS = new Set([
24342
24369
  "B",
24343
24370
  "bytes",
@@ -24692,7 +24719,7 @@ function lineTooltipBody(input) {
24692
24719
  preference: input.preference
24693
24720
  })
24694
24721
  }));
24695
- return asBody(input.timeLabel, lines);
24722
+ return asBody(input.timeLabel, [...lines, ...input.extra ?? []]);
24696
24723
  }
24697
24724
  /** The state in force at `t`, and how long that span lasts. */
24698
24725
  function stateSpanAt(segments, t) {
@@ -42821,6 +42848,176 @@ function CopyButton({ value, label, srLabel, className, disabled }) {
42821
42848
  });
42822
42849
  }
42823
42850
  //#endregion
42851
+ //#region src/composites/device-history/history-runs.ts
42852
+ /** The context kept on each side of a run when a row opens it. */
42853
+ var RUN_WINDOW_MARGIN_MS = 15 * 6e4;
42854
+ function runStatus(run) {
42855
+ return run.state === "open" ? "running" : run.outcome ?? "unknown";
42856
+ }
42857
+ function runEnd(run, now) {
42858
+ return run.state === "open" || run.endedAt === null ? now : run.endedAt;
42859
+ }
42860
+ /** Elapsed time of a run; an open one counts up to `now`. `null` when a closed run has no duration. */
42861
+ function runDurationMs(run, now) {
42862
+ if (run.state === "open") return Math.max(0, now - run.startedAt);
42863
+ if (run.durationMs !== null) return run.durationMs;
42864
+ return run.endedAt === null ? null : Math.max(0, run.endedAt - run.startedAt);
42865
+ }
42866
+ function formatKwh(kwh) {
42867
+ return kwh === null ? "—" : `${kwh.toFixed(2)} kWh`;
42868
+ }
42869
+ function formatWatts(w) {
42870
+ return w === null ? "—" : `${String(Math.round(w))} W`;
42871
+ }
42872
+ function formatRunDuration(ms) {
42873
+ return ms === null ? "—" : formatDurationMs(ms);
42874
+ }
42875
+ var STATUS_TONE = {
42876
+ running: "info",
42877
+ completed: "success",
42878
+ aborted: "warning",
42879
+ error: "danger",
42880
+ unknown: "neutral"
42881
+ };
42882
+ function runTone(status) {
42883
+ return STATUS_TONE[status];
42884
+ }
42885
+ /** The window that frames a run: the run plus {@link RUN_WINDOW_MARGIN_MS} each side. */
42886
+ function runWindow(run, now) {
42887
+ return {
42888
+ from: run.startedAt - RUN_WINDOW_MARGIN_MS,
42889
+ to: runEnd(run, now) + RUN_WINDOW_MARGIN_MS
42890
+ };
42891
+ }
42892
+ /** One band per run that overlaps `window`; the label states the duration and the energy. */
42893
+ function runBands(runs, now, window, t) {
42894
+ const title = t("uiLibrary.history.runs.band", "Run");
42895
+ return runs.filter((r) => runEnd(r, now) > window.from && r.startedAt < window.to).map((r) => {
42896
+ const status = runStatus(r);
42897
+ const parts = [formatRunDuration(runDurationMs(r, now)), formatKwh(r.detail.energyKwh)];
42898
+ return {
42899
+ id: r.runId,
42900
+ from: r.startedAt,
42901
+ to: runEnd(r, now),
42902
+ title,
42903
+ label: status === "running" ? t("uiLibrary.history.runs.bandRunning", "running · {{parts}}", { parts: parts.join(" · ") }) : parts.join(" · "),
42904
+ tone: runTone(status)
42905
+ };
42906
+ });
42907
+ }
42908
+ //#endregion
42909
+ //#region src/composites/DeviceRunsPanel.tsx
42910
+ /**
42911
+ * DeviceRunsPanel — the runs of an appliance, newest first, on the device's
42912
+ * History tab: start, end, duration, energy, peak and mean power, outcome.
42913
+ * A row opens the run's window (the run plus a margin) in the history view.
42914
+ *
42915
+ * Presentational: the caller reads the runs (`useDeviceRuns`) and decides
42916
+ * whether the device has any — the panel never answers "no runs" itself, an
42917
+ * unanswered query is not an empty one (D315). A measurement that failed is
42918
+ * `—`, never `0` (D393).
42919
+ */
42920
+ function statusLabel(status, t) {
42921
+ switch (status) {
42922
+ case "running": return t("uiLibrary.history.runs.status.running", "Running");
42923
+ case "completed": return t("uiLibrary.history.runs.status.completed", "Completed");
42924
+ case "aborted": return t("uiLibrary.history.runs.status.aborted", "Aborted");
42925
+ case "error": return t("uiLibrary.history.runs.status.error", "Error");
42926
+ case "unknown": return t("uiLibrary.history.runs.status.unknown", "Unknown");
42927
+ }
42928
+ }
42929
+ var formatStamp = (ms) => new Date(ms).toLocaleString(void 0, {
42930
+ dateStyle: "short",
42931
+ timeStyle: "short"
42932
+ });
42933
+ function runColumns(now, t) {
42934
+ return [
42935
+ {
42936
+ key: "start",
42937
+ header: t("uiLibrary.history.runs.start", "Start"),
42938
+ render: (r) => formatStamp(r.startedAt),
42939
+ sortValue: (r) => r.startedAt
42940
+ },
42941
+ {
42942
+ key: "end",
42943
+ header: t("uiLibrary.history.runs.end", "End"),
42944
+ render: (r) => r.state === "open" || r.endedAt === null ? t("uiLibrary.history.runs.runningSince", "Running since {{time}}", { time: formatStamp(r.startedAt) }) : formatStamp(r.endedAt),
42945
+ sortValue: (r) => r.endedAt
42946
+ },
42947
+ {
42948
+ key: "duration",
42949
+ header: t("uiLibrary.history.runs.duration", "Duration"),
42950
+ align: "right",
42951
+ render: (r) => formatRunDuration(runDurationMs(r, now)),
42952
+ sortValue: (r) => runDurationMs(r, now)
42953
+ },
42954
+ {
42955
+ key: "energy",
42956
+ header: t("uiLibrary.history.runs.energy", "Energy"),
42957
+ align: "right",
42958
+ render: (r) => formatKwh(r.detail.energyKwh),
42959
+ sortValue: (r) => r.detail.energyKwh
42960
+ },
42961
+ {
42962
+ key: "peak",
42963
+ header: t("uiLibrary.history.runs.peak", "Peak"),
42964
+ align: "right",
42965
+ render: (r) => formatWatts(r.detail.peakW),
42966
+ sortValue: (r) => r.detail.peakW
42967
+ },
42968
+ {
42969
+ key: "avg",
42970
+ header: t("uiLibrary.history.runs.avg", "Average"),
42971
+ align: "right",
42972
+ render: (r) => formatWatts(r.detail.avgW),
42973
+ sortValue: (r) => r.detail.avgW
42974
+ },
42975
+ {
42976
+ key: "outcome",
42977
+ header: t("uiLibrary.history.runs.outcome", "Outcome"),
42978
+ render: (r) => {
42979
+ const status = runStatus(r);
42980
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
42981
+ variant: runTone(status),
42982
+ children: statusLabel(status, t)
42983
+ });
42984
+ },
42985
+ notSortableReason: t("uiLibrary.history.runs.outcomeNoSort", "Outcomes have no order")
42986
+ }
42987
+ ];
42988
+ }
42989
+ function DeviceRunsPanel({ runs, now, onSelectWindow, className }) {
42990
+ const t = useUiText();
42991
+ const [expanded, setExpanded] = (0, react$1.useState)(true);
42992
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleCard, {
42993
+ className,
42994
+ expanded,
42995
+ onExpandedChange: setExpanded,
42996
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
42997
+ className: "flex items-center gap-2 min-w-0",
42998
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
42999
+ className: "text-xs font-semibold text-foreground truncate",
43000
+ children: t("uiLibrary.history.runs.title", "Runs")
43001
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
43002
+ className: "text-[10px] text-foreground-subtle shrink-0",
43003
+ children: t("uiLibrary.history.runs.count", {
43004
+ one: "{{count}} run",
43005
+ other: "{{count}} runs"
43006
+ }, { count: runs.length })
43007
+ })]
43008
+ }),
43009
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTable, {
43010
+ columns: runColumns(now, t),
43011
+ rows: runs,
43012
+ rowKey: (r) => r.runId,
43013
+ onRowClick: (r) => onSelectWindow(runWindow(r, now)),
43014
+ maxBodyHeight: TABLE_SCROLL_MAX_H,
43015
+ bordered: false,
43016
+ minWidthPx: 640
43017
+ })
43018
+ });
43019
+ }
43020
+ //#endregion
42824
43021
  //#region src/composites/device-list/fuzzy-match.ts
42825
43022
  /**
42826
43023
  * fuzzy-match — case and separator-insensitive text search helper.
@@ -71771,6 +71968,7 @@ function latestValues(model) {
71771
71968
  * for the cross-device History page, where the reader chose the series.
71772
71969
  */
71773
71970
  var DEFAULT_HEIGHT = 120;
71971
+ var NO_BANDS = [];
71774
71972
  function seriesSpan(range, xs) {
71775
71973
  if (range !== void 0) return range.to - range.from;
71776
71974
  if (xs.length < 2) return 0;
@@ -71797,18 +71995,25 @@ function seriesTooltip(input) {
71797
71995
  value: values[i] ?? null,
71798
71996
  quantity: sample.quantity,
71799
71997
  unit: sample.unit ?? null
71998
+ })),
71999
+ extra: bandsAt(input.bands, at).map((b) => ({
72000
+ id: `band:${b.id}`,
72001
+ label: b.title,
72002
+ value: b.label
71800
72003
  }))
71801
72004
  });
71802
72005
  },
71803
72006
  onDataX: input.onDataX
71804
72007
  };
71805
72008
  }
71806
- function TimeSeriesChart({ title, series, range, height = DEFAULT_HEIGHT, stacked = false, area = false, syncKey, formatValue, displayPreference, axisFor, emptyMessage, pending = false, actions, footer, windowToggle, className }) {
72009
+ 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
72010
  const t = useUiText();
71808
72011
  const model = (0, react$1.useMemo)(() => buildTimeSeriesModel(series, stacked), [series, stacked]);
71809
72012
  const [cursorIdx, setCursorIdx] = (0, react$1.useState)(null);
71810
72013
  const modelRef = (0, react$1.useRef)(model);
71811
72014
  modelRef.current = model;
72015
+ const bandsRef = (0, react$1.useRef)(bands);
72016
+ bandsRef.current = bands;
71812
72017
  const rangeRef = (0, react$1.useRef)(range);
71813
72018
  rangeRef.current = range;
71814
72019
  const fmt = (0, react$1.useCallback)((v, unit) => (formatValue ?? formatChartValue)(v, unit), [formatValue]);
@@ -71937,7 +72142,7 @@ function TimeSeriesChart({ title, series, range, height = DEFAULT_HEIGHT, stacke
71937
72142
  bands,
71938
72143
  cursor: chartCursor(syncKey),
71939
72144
  hooks: {
71940
- drawClear: [gapShadeHook(() => modelRef.current.gaps)],
72145
+ drawClear: [bandShadeHook(() => bandsRef.current), gapShadeHook(() => modelRef.current.gaps)],
71941
72146
  setCursor: [(u) => setCursorIdx(u.cursor.idx ?? null)]
71942
72147
  }
71943
72148
  };
@@ -71949,6 +72154,7 @@ function TimeSeriesChart({ title, series, range, height = DEFAULT_HEIGHT, stacke
71949
72154
  const tooltip = (0, react$1.useMemo)(() => seriesTooltip({
71950
72155
  model,
71951
72156
  series,
72157
+ bands,
71952
72158
  range,
71953
72159
  preference: displayPreference,
71954
72160
  onDataX: (dataX) => {
@@ -71957,6 +72163,7 @@ function TimeSeriesChart({ title, series, range, height = DEFAULT_HEIGHT, stacke
71957
72163
  }), [
71958
72164
  model,
71959
72165
  series,
72166
+ bands,
71960
72167
  range,
71961
72168
  displayPreference
71962
72169
  ]);
@@ -72744,7 +72951,7 @@ function booleanLabels(key, t) {
72744
72951
  function failure(entry) {
72745
72952
  return entry !== void 0 && !entry.ok ? entry.error : null;
72746
72953
  }
72747
- function HistoryChart({ spec, entryFor, reads, window, compare, syncKey, actions }) {
72954
+ function HistoryChart({ spec, entryFor, reads, window, compare, syncKey, actions, runs, now }) {
72748
72955
  const t = useUiText();
72749
72956
  const pending = reads === null;
72750
72957
  if (spec.type === "states") {
@@ -72819,13 +73026,15 @@ function HistoryChart({ spec, entryFor, reads, window, compare, syncKey, actions
72819
73026
  compare,
72820
73027
  syncKey,
72821
73028
  actions,
73029
+ runs: spec.series.some((s) => s.key === "cap:power-meter.power") ? runs : void 0,
73030
+ now,
72822
73031
  labelOf: (s) => spec.series.length > 1 || s.scope !== "" ? seriesLabel(s) : spec.title
72823
73032
  });
72824
73033
  }
72825
73034
  function seriesLabel(s) {
72826
73035
  return s.scope === "" ? s.displayName : `${s.displayName} · ${s.scope}`;
72827
73036
  }
72828
- function LinesChart({ title, series, entryFor, reads, window, compare, syncKey, actions, labelOf, toolbar, colorOf }) {
73037
+ function LinesChart({ title, series, entryFor, reads, window, compare, syncKey, actions, labelOf, toolbar, colorOf, runs, now }) {
72829
73038
  const t = useUiText();
72830
73039
  const errors = [...new Set(series.map((s) => failure(entryFor(s.key, false))).filter((e) => e !== null))];
72831
73040
  const defs = (0, react$1.useMemo)(() => {
@@ -72875,6 +73084,12 @@ function LinesChart({ title, series, entryFor, reads, window, compare, syncKey,
72875
73084
  colorOf,
72876
73085
  t
72877
73086
  ]);
73087
+ const bands = (0, react$1.useMemo)(() => runs === void 0 ? [] : runBands(runs, now ?? window.to, window, t), [
73088
+ runs,
73089
+ now,
73090
+ window,
73091
+ t
73092
+ ]);
72878
73093
  if (errors.length > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadFailed, {
72879
73094
  title,
72880
73095
  error: errors.join("; "),
@@ -72883,6 +73098,7 @@ function LinesChart({ title, series, entryFor, reads, window, compare, syncKey,
72883
73098
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimeSeriesChart, {
72884
73099
  title,
72885
73100
  series: defs,
73101
+ bands,
72886
73102
  range: window,
72887
73103
  syncKey,
72888
73104
  pending: reads === null,
@@ -73218,6 +73434,24 @@ function HistoryWindowBar({ value, onChange, custom, onCustomChange, compare, on
73218
73434
  });
73219
73435
  }
73220
73436
  //#endregion
73437
+ //#region src/composites/device-history/use-device-runs.ts
73438
+ /** How many runs the panel and the bands are given; the cap's page size. */
73439
+ var DEVICE_RUNS_PAGE = 50;
73440
+ var REFRESH_MS = 6e4;
73441
+ /** The newest runs, or `undefined` while unanswered, absent or empty. */
73442
+ function useDeviceRuns(deviceId) {
73443
+ const q = useDeviceRunsListRuns({
73444
+ deviceId,
73445
+ limit: 50
73446
+ }, {
73447
+ staleTime: REFRESH_MS,
73448
+ refetchInterval: REFRESH_MS,
73449
+ retry: false
73450
+ });
73451
+ if (q.status !== "success" || q.data.runs.length === 0) return void 0;
73452
+ return q.data.runs;
73453
+ }
73454
+ //#endregion
73221
73455
  //#region src/composites/device-history/history-reads.ts
73222
73456
  /**
73223
73457
  * Batched history reads. `queryMany` answers up to 24 reads over one window
@@ -73502,7 +73736,9 @@ function DeviceHistoryView({ deviceId, fixedWindow, onNavigate }) {
73502
73736
  const custom = fixedWindow ? null : decodeCustomWindow(customParam);
73503
73737
  const compare = fixedWindow === void 0 && compareParam === "1";
73504
73738
  const spanGuess = resolveHistoryWindow(windowId, Date.now(), custom);
73505
- const range = resolveHistoryWindow(windowId, anchorNow(Date.now(), spanGuess.to - spanGuess.from), custom);
73739
+ const now = anchorNow(Date.now(), spanGuess.to - spanGuess.from);
73740
+ const range = resolveHistoryWindow(windowId, now, custom);
73741
+ const runs = useDeviceRuns(deviceId);
73506
73742
  const { presence, list } = useDeviceSeriesList(deviceId);
73507
73743
  const panels = useHistoryPanels();
73508
73744
  const hidden = (0, react$1.useMemo)(() => new Set(panels.value.hidden[String(deviceId)] ?? []), [panels.value.hidden, deviceId]);
@@ -73570,81 +73806,95 @@ function DeviceHistoryView({ deviceId, fixedWindow, onNavigate }) {
73570
73806
  description: t("uiLibrary.history.empty", "A value appears here once it changes, or at the next five-minute check.")
73571
73807
  });
73572
73808
  const syncKey = `device-history:${String(deviceId)}`;
73809
+ const showWindow = (w) => {
73810
+ setWindowParam("custom");
73811
+ setCustomParam(encodeCustomWindow(w));
73812
+ };
73573
73813
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
73574
73814
  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
- })]
73815
+ children: [
73816
+ fixedWindow === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
73817
+ className: "flex flex-wrap items-center justify-between gap-2",
73818
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryWindowBar, {
73819
+ value: windowId,
73820
+ onChange: (id) => setWindowParam(id === "24h" ? null : id),
73821
+ custom,
73822
+ onCustomChange: (w) => setCustomParam(encodeCustomWindow(w)),
73823
+ compare,
73824
+ onCompareChange: (on) => setCompareParam(on ? "1" : null)
73825
+ }), hiddenCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
73826
+ variant: "ghost",
73827
+ size: "sm",
73828
+ onClick: showAllHidden,
73829
+ disabled: !panels.writable,
73830
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EyeOff, {
73831
+ className: "h-3.5 w-3.5",
73832
+ "aria-hidden": "true"
73833
+ }), t("uiLibrary.history.showHidden", {
73834
+ one: "Show {{count}} hidden chart",
73835
+ other: "Show {{count}} hidden charts"
73836
+ }, { count: hiddenCount })]
73837
+ })]
73838
+ }),
73839
+ fixedWindow === void 0 && runs !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceRunsPanel, {
73840
+ runs,
73841
+ now,
73842
+ onSelectWindow: showWindow
73843
+ }),
73844
+ visible.map((section) => {
73845
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleCard, {
73846
+ expanded: !collapsed.has(section.id),
73847
+ onExpandedChange: (next) => setCollapsed((prev) => {
73848
+ const s = new Set(prev);
73849
+ if (next) s.delete(section.id);
73850
+ else s.add(section.id);
73851
+ return s;
73852
+ }),
73853
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
73854
+ className: "flex items-center gap-2 min-w-0",
73855
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
73856
+ className: "text-xs font-semibold text-foreground truncate",
73857
+ children: humanizeName(section.name)
73858
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
73859
+ className: "text-[10px] text-foreground-subtle shrink-0",
73860
+ children: t("uiLibrary.history.chartCount", {
73861
+ one: "{{count}} chart",
73862
+ other: "{{count}} charts"
73863
+ }, { count: section.charts.length })
73864
+ })]
73865
+ }),
73866
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
73867
+ className: "grid grid-cols-1 gap-4 2xl:grid-cols-2",
73868
+ children: section.charts.map((spec) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryChart, {
73869
+ spec,
73870
+ entryFor,
73871
+ reads: readsData,
73872
+ window: range,
73873
+ compare,
73874
+ syncKey,
73875
+ runs,
73876
+ now,
73877
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KebabMenu, {
73878
+ title: t("uiLibrary.history.chartMenu", "Chart actions"),
73879
+ items: [{
73880
+ id: "open",
73881
+ label: t("uiLibrary.history.openInHistory", "Open in History"),
73882
+ icon: ChartLine,
73883
+ onClick: () => navigate(historyPageHref(refsOf(deviceId, spec), windowId))
73884
+ }, {
73885
+ id: "hide",
73886
+ label: t("uiLibrary.history.hide", "Hide"),
73887
+ icon: EyeOff,
73888
+ hidden: fixedWindow !== void 0,
73889
+ disabled: !panels.writable,
73890
+ onClick: () => setHidden(spec.id, true)
73891
+ }]
73892
+ })
73893
+ }, spec.id))
73894
+ })
73895
+ }, section.id);
73896
+ })
73897
+ ]
73648
73898
  });
73649
73899
  }
73650
73900
  function HistorySkeleton() {
@@ -73804,7 +74054,7 @@ function TimelineSlider({ markers, bands = [], selectedId = null, step = 5, aria
73804
74054
  }
73805
74055
  //#endregion
73806
74056
  //#region src/ui-library-version.ts
73807
- var uiLibraryVersion = "1.2.383";
74057
+ var uiLibraryVersion = "1.2.385";
73808
74058
  //#endregion
73809
74059
  //#region src/hooks/use-account-unit-preferences.ts
73810
74060
  /**
@@ -74909,6 +75159,7 @@ exports.DEVICE_NAME_JOIN = DEVICE_NAME_JOIN;
74909
75159
  exports.DEVICE_PICKER_GROUP_STORAGE_KEY = DEVICE_PICKER_GROUP_STORAGE_KEY;
74910
75160
  exports.DEVICE_ROLE_META = DEVICE_ROLE_META;
74911
75161
  exports.DEVICE_ROLE_NOUNS = DEVICE_ROLE_NOUNS;
75162
+ exports.DEVICE_RUNS_PAGE = DEVICE_RUNS_PAGE;
74912
75163
  exports.DEVICE_TYPE_CONTROL = DEVICE_TYPE_CONTROL;
74913
75164
  exports.DEVICE_TYPE_META = DEVICE_TYPE_META;
74914
75165
  exports.DEVICE_TYPE_NOUNS = DEVICE_TYPE_NOUNS;
@@ -74939,6 +75190,7 @@ exports.DeviceMultiSelectField = DeviceMultiSelectField;
74939
75190
  exports.DeviceName = DeviceName;
74940
75191
  exports.DevicePicker = DevicePicker;
74941
75192
  exports.DeviceQuickDetailProvider = DeviceQuickDetailProvider;
75193
+ exports.DeviceRunsPanel = DeviceRunsPanel;
74942
75194
  exports.DeviceSelectField = DeviceSelectField;
74943
75195
  exports.DeviceSelectorPicker = DeviceSelectorPicker;
74944
75196
  exports.DeviceStepMatrix = DeviceStepMatrix;
@@ -75188,6 +75440,7 @@ exports.RIGHT = RIGHT;
75188
75440
  exports.ROI_AREA_WARN_FRACTION = ROI_AREA_WARN_FRACTION;
75189
75441
  exports.ROLE_DESCRIPTOR = ROLE_DESCRIPTOR;
75190
75442
  exports.ROOT_DETECTOR_STEP_ID = ROOT_DETECTOR_STEP_ID;
75443
+ exports.RUN_WINDOW_MARGIN_MS = RUN_WINDOW_MARGIN_MS;
75191
75444
  exports.Radar = Radar;
75192
75445
  exports.RadialGauge = RadialGauge;
75193
75446
  exports.Radio = Radio;
@@ -75685,6 +75938,8 @@ exports.roiTooLarge = roiTooLarge;
75685
75938
  exports.roiTooLarge$1 = roiTooLarge;
75686
75939
  exports.rootSourceState = rootSourceState;
75687
75940
  exports.routerBasename = routerBasename;
75941
+ exports.runBands = runBands;
75942
+ exports.runWindow = runWindow;
75688
75943
  exports.runningDurationSince = runningDurationSince;
75689
75944
  exports.runningStatusParts = runningStatusParts;
75690
75945
  exports.sameDeviceName = sameDeviceName;
@@ -76180,6 +76435,7 @@ exports.useDeviceProviderTestCreationField = useDeviceProviderTestCreationField;
76180
76435
  exports.useDeviceProxy = useDeviceProxy;
76181
76436
  exports.useDeviceProxy$1 = useDeviceProxy;
76182
76437
  exports.useDeviceQuickDetailOpen = useDeviceQuickDetailOpen;
76438
+ exports.useDeviceRuns = useDeviceRuns;
76183
76439
  exports.useDeviceRunsGetCurrentRun = useDeviceRunsGetCurrentRun;
76184
76440
  exports.useDeviceRunsGetRun = useDeviceRunsGetRun;
76185
76441
  exports.useDeviceRunsListRuns = useDeviceRunsListRuns;
@@ -76287,15 +76543,12 @@ exports.useIntegrationInstancesListInstances = useIntegrationInstancesListInstan
76287
76543
  exports.useIntegrationInstancesSetInstanceEnabled = useIntegrationInstancesSetInstanceEnabled;
76288
76544
  exports.useIntegrationInstancesTestInstance = useIntegrationInstancesTestInstance;
76289
76545
  exports.useIntegrationInstancesUpdateInstance = useIntegrationInstancesUpdateInstance;
76290
- exports.useIntegrationsClearSettings = useIntegrationsClearSettings;
76291
76546
  exports.useIntegrationsCreate = useIntegrationsCreate;
76292
76547
  exports.useIntegrationsDelete = useIntegrationsDelete;
76293
76548
  exports.useIntegrationsGet = useIntegrationsGet;
76294
76549
  exports.useIntegrationsGetAvailableTypes = useIntegrationsGetAvailableTypes;
76295
76550
  exports.useIntegrationsGetByAddonId = useIntegrationsGetByAddonId;
76296
- exports.useIntegrationsGetSettings = useIntegrationsGetSettings;
76297
76551
  exports.useIntegrationsList = useIntegrationsList;
76298
- exports.useIntegrationsSetSettings = useIntegrationsSetSettings;
76299
76552
  exports.useIntegrationsTestConnection = useIntegrationsTestConnection;
76300
76553
  exports.useIntegrationsUpdate = useIntegrationsUpdate;
76301
76554
  exports.useIntercomEndTalkSession = useIntercomEndTalkSession;