@camstack/ui-library 1.2.316 → 1.2.318

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
@@ -1899,26 +1899,6 @@ var Flashlight = require_createLucideIcon.createLucideIcon("flashlight", [
1899
1899
  * This source code is licensed under the ISC license.
1900
1900
  * See the LICENSE file in the root directory of this source tree.
1901
1901
  */
1902
- var FlaskConical = require_createLucideIcon.createLucideIcon("flask-conical", [
1903
- ["path", {
1904
- d: "M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2",
1905
- key: "18mbvz"
1906
- }],
1907
- ["path", {
1908
- d: "M6.453 15h11.094",
1909
- key: "3shlmq"
1910
- }],
1911
- ["path", {
1912
- d: "M8.5 2h7",
1913
- key: "csnxdl"
1914
- }]
1915
- ]);
1916
- /**
1917
- * @license lucide-react v0.576.0 - ISC
1918
- *
1919
- * This source code is licensed under the ISC license.
1920
- * See the LICENSE file in the root directory of this source tree.
1921
- */
1922
1902
  var Footprints = require_createLucideIcon.createLucideIcon("footprints", [
1923
1903
  ["path", {
1924
1904
  d: "M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z",
@@ -5870,7 +5850,7 @@ function lucideExportName(key) {
5870
5850
  function loadIconGlyphs() {
5871
5851
  if (fullTable !== void 0) return Promise.resolve();
5872
5852
  if (failure$1 !== void 0) return Promise.reject(failure$1);
5873
- pending ??= Promise.resolve().then(() => require("./icons-BTux33dH.cjs")).then((module) => {
5853
+ pending ??= Promise.resolve().then(() => require("./icons-pqV1UOV_.cjs")).then((module) => {
5874
5854
  fullTable = module;
5875
5855
  publish();
5876
5856
  }, (error) => {
@@ -10801,6 +10781,10 @@ var useDeviceHistoryGetDeviceEnabled = trpc.deviceHistory.getDeviceEnabled.useQu
10801
10781
  var useDeviceHistorySetDeviceEnabled = trpc.deviceHistory.setDeviceEnabled.useMutation;
10802
10782
  /** Generated alias around `trpc.deviceHistory.getStatus.useQuery`. */
10803
10783
  var useDeviceHistoryGetStatus = trpc.deviceHistory.getStatus.useQuery;
10784
+ /** Generated alias around `trpc.deviceHistory.listOrphanSeries.useQuery`. */
10785
+ var useDeviceHistoryListOrphanSeries = trpc.deviceHistory.listOrphanSeries.useQuery;
10786
+ /** Generated alias around `trpc.deviceHistory.deleteOrphanSeries.useMutation`. */
10787
+ var useDeviceHistoryDeleteOrphanSeries = trpc.deviceHistory.deleteOrphanSeries.useMutation;
10804
10788
  /** Generated alias around `trpc.deviceManager.allocateDeviceId.useMutation`. */
10805
10789
  var useDeviceManagerAllocateDeviceId = trpc.deviceManager.allocateDeviceId.useMutation;
10806
10790
  /** Generated alias around `trpc.deviceManager.migrateDevice.useMutation`. */
@@ -11953,6 +11937,8 @@ var usePipelineOrchestratorSetAgentReachableHost = trpc.pipelineOrchestrator.set
11953
11937
  var usePipelineOrchestratorSetAgentInferenceDevices = trpc.pipelineOrchestrator.setAgentInferenceDevices.useMutation;
11954
11938
  /** Generated alias around `trpc.pipelineOrchestrator.getNodeInferenceDevices.useQuery`. */
11955
11939
  var usePipelineOrchestratorGetNodeInferenceDevices = trpc.pipelineOrchestrator.getNodeInferenceDevices.useQuery;
11940
+ /** Generated alias around `trpc.pipelineOrchestrator.forgetInferenceDevice.useMutation`. */
11941
+ var usePipelineOrchestratorForgetInferenceDevice = trpc.pipelineOrchestrator.forgetInferenceDevice.useMutation;
11956
11942
  /** Generated alias around `trpc.pipelineOrchestrator.resetNodePipelineDefaults.useMutation`. */
11957
11943
  var usePipelineOrchestratorResetNodePipelineDefaults = trpc.pipelineOrchestrator.resetNodePipelineDefaults.useMutation;
11958
11944
  /** Generated alias around `trpc.pipelineOrchestrator.planAutoTune.useQuery`. */
@@ -19149,20 +19135,30 @@ function useDeviceAutotrack(deviceId) {
19149
19135
  }
19150
19136
  //#endregion
19151
19137
  //#region src/composites/widget-panel.tsx
19152
- function WidgetPanel({ title, icon, headerRight, collapsible = false, defaultOpen = true, bodyClassName = "p-3", children }) {
19153
- const [open, setOpen] = (0, react$1.useState)(defaultOpen);
19138
+ function WidgetPanel({ title, icon, headerRight, collapsible = false, defaultOpen = true, open: openProp, onOpenChange, bodyClassName = "p-3", children }) {
19139
+ const [openState, setOpenState] = (0, react$1.useState)(defaultOpen);
19140
+ const open = openProp ?? openState;
19141
+ const toggle = () => {
19142
+ const next = !open;
19143
+ if (openProp === void 0) setOpenState(next);
19144
+ onOpenChange?.(next);
19145
+ };
19154
19146
  const showBody = !collapsible || open;
19155
19147
  const headerInner = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
19156
19148
  className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-foreground",
19157
19149
  children: [icon, title]
19158
- }), collapsible ? open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3.5 w-3.5 text-foreground-subtle" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRight, { className: "h-3.5 w-3.5 text-foreground-subtle" }) : headerRight] });
19150
+ }), collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
19151
+ className: "flex min-w-0 items-center gap-2",
19152
+ children: [headerRight, open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3.5 w-3.5 shrink-0 text-foreground-subtle" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-foreground-subtle" })]
19153
+ }) : headerRight] });
19159
19154
  const headerClass = `px-4 py-2 flex items-center justify-between ${showBody ? "border-b border-border" : ""}`;
19160
19155
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19161
19156
  className: "rounded-lg border border-border bg-surface overflow-hidden",
19162
19157
  children: [collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
19163
19158
  type: "button",
19164
- onClick: () => setOpen((v) => !v),
19165
- className: `w-full text-left ${headerClass}`,
19159
+ "aria-expanded": open,
19160
+ onClick: toggle,
19161
+ className: `w-full gap-2 text-left ${headerClass}`,
19166
19162
  children: headerInner
19167
19163
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
19168
19164
  className: headerClass,
@@ -20523,6 +20519,60 @@ function useDeviceCapability(options) {
20523
20519
  };
20524
20520
  }
20525
20521
  //#endregion
20522
+ //#region src/hooks/use-persisted-disclosure.ts
20523
+ /**
20524
+ * usePersistedDisclosure — an open / closed choice the viewer made, remembered
20525
+ * per viewer under the caller's key (one key per section and device).
20526
+ *
20527
+ * `null` means the viewer never chose. The caller then shows its own default,
20528
+ * which may follow the data (the Consumables section opens itself while an
20529
+ * item needs attention) — the default is never written, so it can change with
20530
+ * the data until the viewer picks.
20531
+ *
20532
+ * Storage is a convenience, never a requirement: every read and write is
20533
+ * guarded, and a blocked / private-mode storage means the choice lasts until
20534
+ * the page is left. A stored value this hook did not write is ignored.
20535
+ */
20536
+ var OPEN = "open";
20537
+ var CLOSED = "closed";
20538
+ function readStored$1(key) {
20539
+ if (key === null || typeof window === "undefined") return null;
20540
+ try {
20541
+ const raw = window.localStorage.getItem(key);
20542
+ if (raw === OPEN) return true;
20543
+ if (raw === CLOSED) return false;
20544
+ return null;
20545
+ } catch {
20546
+ return null;
20547
+ }
20548
+ }
20549
+ function writeStored$1(key, open) {
20550
+ if (key === null || typeof window === "undefined") return;
20551
+ try {
20552
+ window.localStorage.setItem(key, open ? OPEN : CLOSED);
20553
+ } catch {}
20554
+ }
20555
+ /** @returns `[choice, setChoice]` — `choice` is `null` until the viewer picks. */
20556
+ function usePersistedDisclosure(storageKey) {
20557
+ const [choice, setChoice] = (0, react$1.useState)(() => ({
20558
+ key: storageKey,
20559
+ open: readStored$1(storageKey)
20560
+ }));
20561
+ const current = choice.key === storageKey ? choice : {
20562
+ key: storageKey,
20563
+ open: readStored$1(storageKey)
20564
+ };
20565
+ if (current !== choice) setChoice(current);
20566
+ const update = (0, react$1.useCallback)((open) => {
20567
+ setChoice({
20568
+ key: storageKey,
20569
+ open
20570
+ });
20571
+ writeStored$1(storageKey, open);
20572
+ }, [storageKey]);
20573
+ return [current.open, update];
20574
+ }
20575
+ //#endregion
20526
20576
  //#region src/composites/consumable-level-badge.tsx
20527
20577
  var LOOKS$1 = {
20528
20578
  ok: {
@@ -20543,6 +20593,12 @@ var LOOKS$1 = {
20543
20593
  labelKey: "uiLibrary.consumableLevel.empty",
20544
20594
  labelFallback: "Empty"
20545
20595
  },
20596
+ full: {
20597
+ variant: "warning",
20598
+ barClass: "bg-warning",
20599
+ labelKey: "uiLibrary.consumableLevel.full",
20600
+ labelFallback: "Full"
20601
+ },
20546
20602
  unknown: {
20547
20603
  variant: "neutral",
20548
20604
  barClass: "bg-foreground-subtle",
@@ -20566,39 +20622,57 @@ function consumableLevelLabel(level, t) {
20566
20622
  const look = lookOf(level);
20567
20623
  return t(look.labelKey, look.labelFallback);
20568
20624
  }
20569
- /** A remaining-life % at or below this is `low`. */
20625
+ /** A remaining-life (or supply fill) % at or below this is `low`. */
20570
20626
  var CONSUMABLE_LOW_PERCENT = 15;
20571
- /**
20572
- * A `consumables` cap item as a level: a known % (0 = empty, at or below
20573
- * {@link CONSUMABLE_LOW_PERCENT} = low), never better than `low` when the
20574
- * provider also says `replace`; without a % the discrete status (`replace` =
20575
- * low); else unknown.
20576
- */
20577
- function consumableItemLevel(item) {
20627
+ /** A waste item: full is bad, low or empty is what it should be. */
20628
+ function wasteLevel(item) {
20629
+ if (item.status === "full" || item.status === "replace") return "full";
20630
+ if (item.level !== null) return item.level >= 85 ? "full" : "ok";
20631
+ return item.status === null ? null : "ok";
20632
+ }
20633
+ /** A wear part or a supply: low / empty is bad, a full supply is fine. */
20634
+ function drawnLevel(item) {
20578
20635
  if (item.level !== null) {
20579
- if (item.level <= 0) return "empty";
20580
- if (item.level <= 15 || item.status === "replace") return "low";
20636
+ if (item.level <= 0 || item.status === "empty") return "empty";
20637
+ if (item.level <= 15 || item.status === "replace" || item.status === "low") return "low";
20581
20638
  return "ok";
20582
20639
  }
20583
20640
  switch (item.status) {
20584
- case "replace": return "low";
20585
- case "ok": return "ok";
20641
+ case "replace":
20642
+ case "low": return "low";
20643
+ case "empty": return "empty";
20644
+ case "ok":
20645
+ case "full": return "ok";
20586
20646
  case null: return null;
20587
20647
  }
20588
20648
  }
20589
20649
  /**
20650
+ * A `consumables` cap item as a level, by its kind (D915):
20651
+ * - a `waste` item (a dust bin) is `full` when the provider says so or its
20652
+ * fill is at or above {@link CONSUMABLE_FULL_PERCENT}, else ok;
20653
+ * - a `wear` part or a `supply` reads a known % (0 = empty, at or below
20654
+ * {@link CONSUMABLE_LOW_PERCENT} = low), never better than `low` when the
20655
+ * provider says `replace` or `low`; without a % its discrete status.
20656
+ * Nothing known is unknown (`null`).
20657
+ */
20658
+ function consumableItemLevel(item) {
20659
+ return (0, _camstack_types.consumableKindOf)(item) === "waste" ? wasteLevel(item) : drawnLevel(item);
20660
+ }
20661
+ /** The supply vocabulary a level FIELD of a cap is typed by: `ok | low | empty`. */
20662
+ var SUPPLY_LEVELS = _camstack_types.ConsumableLevelSchema.exclude(["full"]).options;
20663
+ /**
20590
20664
  * Whether a cap state field IS a consumable level: an enum whose members are
20591
- * exactly the consumable level enum. Derived from the cap's schema, so a new
20592
- * cap that uses the enum is recognised without naming it anywhere.
20665
+ * exactly the supply levels (`ok | low | empty`). Derived from the schema, so a
20666
+ * new cap that uses the vocabulary is recognised without naming it anywhere.
20593
20667
  */
20594
20668
  function isConsumableLevelField(field) {
20595
20669
  if (field.kind !== "enum" || field.enumValues === void 0) return false;
20596
- const members = _camstack_types.DishwasherConsumableLevelSchema.options;
20670
+ const members = SUPPLY_LEVELS;
20597
20671
  return field.enumValues.length === members.length && members.every((m) => field.enumValues?.includes(m));
20598
20672
  }
20599
20673
  /** A value read from a slice as a level; anything that is not a member is unknown. */
20600
20674
  function parseConsumableLevel(value) {
20601
- const parsed = _camstack_types.DishwasherConsumableLevelSchema.safeParse(value);
20675
+ const parsed = _camstack_types.ConsumableLevelSchema.safeParse(value);
20602
20676
  return parsed.success ? parsed.data : null;
20603
20677
  }
20604
20678
  function ConsumableLevelBadge({ level, name, label, size = "md", className }) {
@@ -20613,26 +20687,50 @@ function ConsumableLevelBadge({ level, name, label, size = "md", className }) {
20613
20687
  });
20614
20688
  }
20615
20689
  //#endregion
20616
- //#region src/composites/cap-settings/ConsumablesPanel.tsx
20617
- /**
20618
- * ConsumablesPanel — maintenance-items surface for the generic
20619
- * `consumables` capability. A cap-settings component (ui-library),
20620
- * mounted as a cap-contributed top-level device-detail tab via the
20621
- * cap-UI contribution mechanism (the `consumables` cap declares
20622
- * `ui: { kind:'widget', widgetId:'host/consumables-panel', tab:'consumables', topTab:true, ... }`).
20623
- *
20624
- * Reads the cap's pushed runtime-state slice via `useDeviceCapability`
20625
- * and renders one row per item: a fill bar + percentage when `level` is
20626
- * known, else a `ConsumableLevelBadge` (OK / Replace, or Unknown when
20627
- * neither is known) — the bar's colour and the badge's tone come from the
20628
- * same `consumableItemLevel` mapping. When a provider also reports `remaining` (a count in
20629
- * days/hours/cycles), its value is shown alongside — `null` renders a
20630
- * muted em-dash, never a fake `0` (D393); absent `remaining` renders
20631
- * nothing, exactly as before this field existed. A per-item "Replaced"
20632
- * button calls `cap.reset` when the item is `resettable`. The cap is
20633
- * inert until a provider sets items — an empty list renders a muted
20634
- * placeholder.
20635
- */
20690
+ //#region src/primitives/skeleton.tsx
20691
+ var Skeleton = (0, react$1.forwardRef)(({ className, ...props }, ref) => {
20692
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20693
+ ref,
20694
+ className: cn("animate-pulse bg-surface-hover rounded-md h-4 w-full", className),
20695
+ ...props
20696
+ });
20697
+ });
20698
+ Skeleton.displayName = "Skeleton";
20699
+ //#endregion
20700
+ //#region src/primitives/button.tsx
20701
+ var buttonVariants = cva("inline-flex items-center justify-center rounded-md font-medium transition-colors cursor-pointer disabled:opacity-50 disabled:pointer-events-none", {
20702
+ variants: {
20703
+ variant: {
20704
+ primary: "bg-primary text-primary-foreground hover:bg-primary/90",
20705
+ secondary: "bg-surface text-foreground hover:bg-surface-hover",
20706
+ ghost: "hover:bg-surface-hover text-foreground-muted",
20707
+ danger: "bg-danger text-white hover:bg-danger/90",
20708
+ outline: "border border-border bg-transparent text-foreground hover:bg-surface-hover"
20709
+ },
20710
+ size: {
20711
+ sm: "h-7 px-2.5 text-xs gap-1.5",
20712
+ md: "h-8 px-3 text-sm gap-2",
20713
+ lg: "h-9 px-4 text-sm gap-2"
20714
+ }
20715
+ },
20716
+ defaultVariants: {
20717
+ variant: "primary",
20718
+ size: "sm"
20719
+ }
20720
+ });
20721
+ var Button = (0, react$1.forwardRef)(({ className, variant, size, ...props }, ref) => {
20722
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
20723
+ ref,
20724
+ className: cn(buttonVariants({
20725
+ variant,
20726
+ size
20727
+ }), className),
20728
+ ...props
20729
+ });
20730
+ });
20731
+ Button.displayName = "Button";
20732
+ //#endregion
20733
+ //#region src/composites/cap-settings/consumable-rows.tsx
20636
20734
  /** `value` null = unknown (D393) — renders an em-dash, never a fake `0`. */
20637
20735
  function formatRemaining(t, r) {
20638
20736
  if (r.value === null) return "—";
@@ -20664,55 +20762,278 @@ function formatResetAt(ms) {
20664
20762
  return "";
20665
20763
  }
20666
20764
  }
20667
- function ConsumableRow({ item, busy, canReset, onReset }) {
20765
+ function needsAttention(level) {
20766
+ return level === "low" || level === "empty" || level === "full";
20767
+ }
20768
+ function clampPercent(value) {
20769
+ return Math.max(0, Math.min(100, value));
20770
+ }
20771
+ function RowFrame({ item, warn, children }) {
20772
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20773
+ "data-form": (0, _camstack_types.consumableFormOf)(item),
20774
+ className: `flex gap-2.5 rounded-md border p-3 ${warn ? "border-warning/40 bg-warning/5" : "border-border-subtle"}`,
20775
+ children
20776
+ });
20777
+ }
20778
+ function RowLabel({ item, warn }) {
20779
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20780
+ className: `truncate text-xs font-semibold ${warn ? "text-warning" : "text-foreground"}`,
20781
+ children: item.label
20782
+ });
20783
+ }
20784
+ /** A wear part: bar + %, or its verdict; remaining time; the reset. */
20785
+ function PartRow({ item, busy, canReset, onReset }) {
20668
20786
  const t = useUiText();
20669
20787
  const level = consumableItemLevel(item);
20670
- const warn = level === "low" || level === "empty";
20671
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
20672
- className: `flex flex-col gap-1.5 rounded-md border p-3 ${warn ? "border-warning/40 bg-warning/5" : "border-border/40 bg-surface-elevated/30"}`,
20673
- children: [
20674
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
20675
- className: "flex items-center justify-between gap-2",
20676
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20677
- className: `text-xs font-semibold ${warn ? "text-warning" : "text-foreground"}`,
20678
- children: item.label
20679
- }), item.resettable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
20788
+ const warn = needsAttention(level);
20789
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowFrame, {
20790
+ item,
20791
+ warn,
20792
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
20793
+ className: "flex min-w-0 flex-1 flex-col gap-1.5",
20794
+ children: [
20795
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowLabel, {
20796
+ item,
20797
+ warn
20798
+ }),
20799
+ item.level !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
20800
+ className: "flex items-center gap-2",
20801
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20802
+ className: "h-1.5 flex-1 overflow-hidden rounded-full bg-surface-hover",
20803
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20804
+ className: `h-full rounded-full ${consumableLevelBarClass(level)} transition-all`,
20805
+ style: { width: `${clampPercent(item.level)}%` }
20806
+ })
20807
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
20808
+ className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
20809
+ children: [Math.round(item.level), "%"]
20810
+ })]
20811
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
20812
+ level,
20813
+ size: "sm",
20814
+ className: "w-fit",
20815
+ label: item.status === "replace" ? t("uiLibrary.consumablesPanel.replace", "Replace") : void 0
20816
+ }),
20817
+ item.remaining && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20818
+ className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
20819
+ children: formatRemaining(t, item.remaining)
20820
+ }),
20821
+ item.lastResetAt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20822
+ className: "text-[10px] text-foreground-subtle",
20823
+ children: t("uiLibrary.consumablesPanel.replacedOn", "Replaced {{date}}", { date: formatResetAt(item.lastResetAt) })
20824
+ }),
20825
+ item.resettable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
20680
20826
  type: "button",
20827
+ variant: "outline",
20828
+ size: "sm",
20829
+ className: "w-fit",
20681
20830
  onClick: () => onReset(item.key),
20682
20831
  disabled: busy || !canReset,
20683
- className: "rounded-md border border-border bg-surface px-2 py-0.5 text-[10px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors",
20684
20832
  children: t("uiLibrary.consumablesPanel.replaced", "Replaced")
20685
- })]
20686
- }),
20687
- item.level !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
20833
+ })
20834
+ ]
20835
+ })
20836
+ });
20837
+ }
20838
+ /** A tank or a bin: a vertical gauge filled from the bottom, then the % or the verdict. */
20839
+ function GaugeRow({ item }) {
20840
+ const level = consumableItemLevel(item);
20841
+ const warn = needsAttention(level);
20842
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(RowFrame, {
20843
+ item,
20844
+ warn,
20845
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20846
+ className: "relative h-10 w-3 shrink-0 overflow-hidden rounded-sm border border-border bg-surface-hover",
20847
+ "data-testid": "consumable-gauge",
20848
+ children: item.level !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20849
+ className: `absolute inset-x-0 bottom-0 ${consumableLevelBarClass(level)} transition-all`,
20850
+ style: { height: `${clampPercent(item.level)}%` }
20851
+ })
20852
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
20853
+ className: "flex min-w-0 flex-1 flex-col justify-center gap-1",
20854
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowLabel, {
20855
+ item,
20856
+ warn
20857
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
20688
20858
  className: "flex items-center gap-2",
20689
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20690
- className: "h-1.5 flex-1 overflow-hidden rounded-full bg-surface-elevated/60",
20691
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20692
- className: `h-full rounded-full ${consumableLevelBarClass(level)} transition-all`,
20693
- style: { width: `${Math.max(0, Math.min(100, item.level))}%` }
20694
- })
20695
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
20859
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
20860
+ level,
20861
+ size: "sm",
20862
+ className: "w-fit"
20863
+ }), item.level !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
20696
20864
  className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
20697
20865
  children: [Math.round(item.level), "%"]
20698
20866
  })]
20699
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
20700
- level,
20701
- size: "sm",
20702
- className: "w-fit",
20703
- label: item.status === "replace" ? t("uiLibrary.consumablesPanel.replace", "Replace") : void 0
20704
- }),
20705
- item.remaining && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20706
- className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
20707
- children: formatRemaining(t, item.remaining)
20708
- }),
20709
- item.lastResetAt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20710
- className: "text-[10px] text-foreground-subtle",
20711
- children: t("uiLibrary.consumablesPanel.replacedOn", "Replaced {{date}}", { date: formatResetAt(item.lastResetAt) })
20712
- })
20713
- ]
20867
+ })]
20868
+ })]
20869
+ });
20870
+ }
20871
+ /** How many of the three steps a product's level fills. */
20872
+ var PRODUCT_STEPS = {
20873
+ full: 3,
20874
+ ok: 3,
20875
+ low: 1,
20876
+ empty: 0,
20877
+ unknown: 0
20878
+ };
20879
+ var STEP_INDEXES = [
20880
+ 0,
20881
+ 1,
20882
+ 2
20883
+ ];
20884
+ /** The refill wording a product's level reads as. */
20885
+ function productLabel(level, t) {
20886
+ if (level === "low") return t("uiLibrary.consumablesPanel.refillSoon", "Refill soon");
20887
+ if (level === "empty") return t("uiLibrary.consumablesPanel.refillNow", "Refill now");
20888
+ }
20889
+ /** A product bought and refilled: package icon, three steps, "refill". */
20890
+ function ProductRow({ item }) {
20891
+ const t = useUiText();
20892
+ const level = consumableItemLevel(item);
20893
+ const warn = needsAttention(level);
20894
+ const filled = PRODUCT_STEPS[level ?? "unknown"];
20895
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(RowFrame, {
20896
+ item,
20897
+ warn,
20898
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Package, { className: `mt-0.5 h-4 w-4 shrink-0 ${warn ? "text-warning" : "text-foreground-subtle"}` }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
20899
+ className: "flex min-w-0 flex-1 flex-col gap-1.5",
20900
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowLabel, {
20901
+ item,
20902
+ warn
20903
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
20904
+ className: "flex flex-wrap items-center gap-2",
20905
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20906
+ className: "flex gap-0.5",
20907
+ "data-testid": "consumable-steps",
20908
+ "data-filled": filled,
20909
+ children: STEP_INDEXES.map((i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `h-1.5 w-3 rounded-full ${i < filled ? consumableLevelBarClass(level) : "bg-surface-hover"}` }, i))
20910
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
20911
+ level,
20912
+ size: "sm",
20913
+ className: "whitespace-nowrap",
20914
+ label: productLabel(level, t)
20915
+ })]
20916
+ })]
20917
+ })]
20714
20918
  });
20715
20919
  }
20920
+ /** The row for an item, chosen by its form. */
20921
+ function ConsumableRow(props) {
20922
+ switch ((0, _camstack_types.consumableFormOf)(props.item)) {
20923
+ case "part": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PartRow, { ...props });
20924
+ case "tank":
20925
+ case "bin": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeRow, { item: props.item });
20926
+ case "product": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProductRow, { item: props.item });
20927
+ }
20928
+ }
20929
+ //#endregion
20930
+ //#region src/composites/cap-settings/consumables-summary.ts
20931
+ var SEVERITY = {
20932
+ empty: 3,
20933
+ full: 2,
20934
+ low: 1
20935
+ };
20936
+ function isAttention(level) {
20937
+ return level === "low" || level === "empty" || level === "full";
20938
+ }
20939
+ function summarizeConsumables(items) {
20940
+ if (items.length === 0) return {
20941
+ kind: "none",
20942
+ needsAttention: false
20943
+ };
20944
+ const attention = [];
20945
+ let ok = 0;
20946
+ let unknown = 0;
20947
+ for (const item of items) {
20948
+ const level = consumableItemLevel(item);
20949
+ if (isAttention(level)) attention.push({
20950
+ label: item.label,
20951
+ level,
20952
+ replace: item.status === "replace"
20953
+ });
20954
+ else if (level === "ok") ok += 1;
20955
+ else unknown += 1;
20956
+ }
20957
+ const worst = [...attention].sort((a, b) => SEVERITY[b.level] - SEVERITY[a.level])[0];
20958
+ if (worst !== void 0) return {
20959
+ kind: "attention",
20960
+ needsAttention: true,
20961
+ count: attention.length,
20962
+ worst
20963
+ };
20964
+ return unknown === 0 ? {
20965
+ kind: "ok",
20966
+ needsAttention: false,
20967
+ count: ok
20968
+ } : {
20969
+ kind: "partial",
20970
+ needsAttention: false,
20971
+ ok,
20972
+ unknown
20973
+ };
20974
+ }
20975
+ //#endregion
20976
+ //#region src/composites/cap-settings/ConsumablesPanel.tsx
20977
+ /**
20978
+ * ConsumablesPanel — the ONE consumables surface of every device (D915): wear
20979
+ * parts, tanks (a vacuum's clean and dirty water), products (detergent, a
20980
+ * dishwasher's salt and rinse aid) and bins (a dust bin), for whichever device
20981
+ * is bound to the `consumables` capability. A cap-settings component
20982
+ * (ui-library), shown directly under the device page's hero card through the
20983
+ * cap-UI contribution mechanism (the cap declares
20984
+ * `ui: { kind:'widget', widgetId:'host/consumables-panel', placement:'hero', ... }`)
20985
+ * — never drawn by a device type's hero card itself.
20986
+ *
20987
+ * Reads the cap's pushed runtime-state slice via `useDeviceCapability` and
20988
+ * renders one row per item, drawn by its form (`consumable-rows.tsx`). A
20989
+ * slice that has not landed is a loading line, never "none reported" (D315).
20990
+ *
20991
+ * The section COLLAPSES smartly: it starts open while any item needs
20992
+ * attention (low, empty, a full bin or waste tank, a part to replace) and
20993
+ * closed while every item is ok or unknown; closed, its header carries a
20994
+ * one-line summary (`consumables-summary.ts`). The operator's own toggle wins
20995
+ * and is remembered per device and per viewer.
20996
+ */
20997
+ /** Where one viewer's open / closed choice for one device's section is kept. */
20998
+ function consumablesOpenStorageKey(deviceId) {
20999
+ return `camstack.device.${String(deviceId)}.consumables.open`;
21000
+ }
21001
+ /** The one line a collapsed section says. */
21002
+ function SummaryLine({ summary, t }) {
21003
+ switch (summary.kind) {
21004
+ case "none": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
21005
+ variant: "neutral",
21006
+ size: "sm",
21007
+ children: t("uiLibrary.consumablesPanel.noneReported", "None reported")
21008
+ });
21009
+ case "ok": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
21010
+ variant: "success",
21011
+ size: "sm",
21012
+ children: t("uiLibrary.consumablesPanel.allOk", "All OK")
21013
+ });
21014
+ case "partial": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
21015
+ variant: "neutral",
21016
+ size: "sm",
21017
+ children: t("uiLibrary.consumablesPanel.okAndUnknown", "{{ok}} OK · {{unknown}} unknown", {
21018
+ ok: summary.ok,
21019
+ unknown: summary.unknown
21020
+ })
21021
+ });
21022
+ case "attention": return summary.count === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
21023
+ level: summary.worst.level,
21024
+ name: summary.worst.label,
21025
+ size: "sm",
21026
+ label: summary.worst.replace ? t("uiLibrary.consumablesPanel.replace", "Replace") : void 0
21027
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
21028
+ variant: consumableLevelVariant(summary.worst.level),
21029
+ size: "sm",
21030
+ children: t("uiLibrary.consumablesPanel.needAttention", {
21031
+ one: "{{count}} needs attention",
21032
+ other: "{{count}} need attention"
21033
+ }, { count: summary.count })
21034
+ });
21035
+ }
21036
+ }
20716
21037
  function ConsumablesPanel({ deviceId }) {
20717
21038
  const t = useUiText();
20718
21039
  const system = useSystem();
@@ -20724,6 +21045,7 @@ function ConsumablesPanel({ deviceId }) {
20724
21045
  slice: (d) => d.state.consumables
20725
21046
  });
20726
21047
  const [busyKey, setBusyKey] = (0, react$1.useState)(null);
21048
+ const [choice, setChoice] = usePersistedDisclosure(ready ? consumablesOpenStorageKey(deviceId) : null);
20727
21049
  const onReset = (0, react$1.useCallback)(async (key) => {
20728
21050
  if (!cap) return;
20729
21051
  setBusyKey(key);
@@ -20735,17 +21057,30 @@ function ConsumablesPanel({ deviceId }) {
20735
21057
  setBusyKey(null);
20736
21058
  }
20737
21059
  }, [cap]);
20738
- const items = slice?.items ?? [];
21060
+ const summary = slice === void 0 ? null : summarizeConsumables(slice.items);
21061
+ const open = choice ?? summary?.needsAttention ?? false;
21062
+ const loading = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
21063
+ className: open ? "h-10 w-full" : "h-4 w-16",
21064
+ "data-testid": "consumables-loading",
21065
+ "aria-label": t("uiLibrary.consumablesPanel.loading", "Reading consumables…")
21066
+ });
20739
21067
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetPanel, {
20740
21068
  title: t("uiLibrary.consumablesPanel.title", "Consumables"),
20741
21069
  icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Gauge, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
20742
21070
  bodyClassName: "p-3 overflow-y-auto",
20743
- children: items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
21071
+ collapsible: true,
21072
+ open,
21073
+ onOpenChange: setChoice,
21074
+ headerRight: open ? void 0 : summary === null ? loading : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SummaryLine, {
21075
+ summary,
21076
+ t
21077
+ }),
21078
+ children: slice === void 0 ? loading : slice.items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
20744
21079
  className: "text-[11px] text-foreground-subtle italic leading-relaxed",
20745
21080
  children: t("uiLibrary.consumablesPanel.empty", "No consumables reported.")
20746
21081
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20747
- className: "flex flex-col gap-2",
20748
- children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableRow, {
21082
+ className: "grid grid-cols-1 gap-2 sm:grid-cols-2",
21083
+ children: slice.items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableRow, {
20749
21084
  item,
20750
21085
  busy: busyKey === item.key,
20751
21086
  canReset: available,
@@ -20785,49 +21120,6 @@ function useCapabilityPresence(capabilityName) {
20785
21120
  return classifyCapability(query.data);
20786
21121
  }
20787
21122
  //#endregion
20788
- //#region src/primitives/button.tsx
20789
- var buttonVariants = cva("inline-flex items-center justify-center rounded-md font-medium transition-colors cursor-pointer disabled:opacity-50 disabled:pointer-events-none", {
20790
- variants: {
20791
- variant: {
20792
- primary: "bg-primary text-primary-foreground hover:bg-primary/90",
20793
- secondary: "bg-surface text-foreground hover:bg-surface-hover",
20794
- ghost: "hover:bg-surface-hover text-foreground-muted",
20795
- danger: "bg-danger text-white hover:bg-danger/90",
20796
- outline: "border border-border bg-transparent text-foreground hover:bg-surface-hover"
20797
- },
20798
- size: {
20799
- sm: "h-7 px-2.5 text-xs gap-1.5",
20800
- md: "h-8 px-3 text-sm gap-2",
20801
- lg: "h-9 px-4 text-sm gap-2"
20802
- }
20803
- },
20804
- defaultVariants: {
20805
- variant: "primary",
20806
- size: "sm"
20807
- }
20808
- });
20809
- var Button = (0, react$1.forwardRef)(({ className, variant, size, ...props }, ref) => {
20810
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
20811
- ref,
20812
- className: cn(buttonVariants({
20813
- variant,
20814
- size
20815
- }), className),
20816
- ...props
20817
- });
20818
- });
20819
- Button.displayName = "Button";
20820
- //#endregion
20821
- //#region src/primitives/skeleton.tsx
20822
- var Skeleton = (0, react$1.forwardRef)(({ className, ...props }, ref) => {
20823
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20824
- ref,
20825
- className: cn("animate-pulse bg-surface-hover rounded-md h-4 w-full", className),
20826
- ...props
20827
- });
20828
- });
20829
- Skeleton.displayName = "Skeleton";
20830
- //#endregion
20831
21123
  //#region src/composites/cap-settings/recording-timeline-model.ts
20832
21124
  /** Tie-break precedence for a bucket's dominant kind (first wins on equal count). */
20833
21125
  var KIND_PRECEDENCE = [
@@ -47102,147 +47394,6 @@ function BatteryBadge$2({ level }) {
47102
47394
  });
47103
47395
  }
47104
47396
  /**
47105
- * Pure helper — partitions a tank kind into "supply" (low is bad) vs
47106
- * "waste" (full is bad) via an exhaustive switch. A future TankKind that
47107
- * forgets to classify itself fails the build (the `never` default arm),
47108
- * rather than silently defaulting to the waste branch.
47109
- */
47110
- function tankIsSupply(kind) {
47111
- switch (kind) {
47112
- case "cleanWater": return true;
47113
- case "detergent": return true;
47114
- case "dirtyWater": return false;
47115
- case "dustBin": return false;
47116
- default: return kind;
47117
- }
47118
- }
47119
- /**
47120
- * Pure helper — classifies a tank as `ok` or `warning`.
47121
- *
47122
- * Clean-water + detergent are "supply" tanks: low is bad. Dirty-water +
47123
- * dust-bin are "waste" tanks: full is bad. Prefers the numeric `level`
47124
- * (thresholds 15 / 85), falling back to the discrete `status` flag. Both
47125
- * unknown → `ok`. Exhaustive over TankKind.
47126
- */
47127
- function tankAlert(kind, tank) {
47128
- const isSupply = tankIsSupply(kind);
47129
- if (tank.level !== null) {
47130
- if (isSupply) return tank.level <= 15 ? "warning" : "ok";
47131
- return tank.level >= 85 ? "warning" : "ok";
47132
- }
47133
- if (tank.status !== null) {
47134
- if (isSupply) return tank.status === "low" ? "warning" : "ok";
47135
- return tank.status === "full" ? "warning" : "ok";
47136
- }
47137
- return "ok";
47138
- }
47139
- function tankMeta(t, kind) {
47140
- switch (kind) {
47141
- case "cleanWater": return {
47142
- label: t("uiLibrary.vacuum.cleanWater", "Clean water"),
47143
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Droplet, { className: "h-3.5 w-3.5" })
47144
- };
47145
- case "dirtyWater": return {
47146
- label: t("uiLibrary.vacuum.dirtyWater", "Dirty water"),
47147
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Droplets, { className: "h-3.5 w-3.5" })
47148
- };
47149
- case "detergent": return {
47150
- label: t("uiLibrary.vacuum.detergent", "Detergent"),
47151
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FlaskConical, { className: "h-3.5 w-3.5" })
47152
- };
47153
- case "dustBin": return {
47154
- label: t("uiLibrary.vacuum.dustBin", "Dust bin"),
47155
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Trash2, { className: "h-3.5 w-3.5" })
47156
- };
47157
- default: return kind;
47158
- }
47159
- }
47160
- /**
47161
- * TankIndicator — one tank row: label + glyph, then a fill bar (numeric
47162
- * level) or a status pill (discrete), tinted by `tankAlert`. Renders
47163
- * nothing only when the tank is absent (null). A PRESENT tank whose
47164
- * reading is unknown (both fields null) still renders a neutral row with
47165
- * an em-dash placeholder, so the user always sees which tanks the robot
47166
- * has (Dreame reports presence but no fill telemetry).
47167
- */
47168
- function TankIndicator({ kind, tank }) {
47169
- const t = useUiText();
47170
- if (tank === null) return null;
47171
- const meta = tankMeta(t, kind);
47172
- const warning = tankAlert(kind, tank) === "warning";
47173
- if (tank.level !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47174
- className: "flex items-center gap-2 text-xs",
47175
- children: [
47176
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
47177
- className: cn("inline-flex items-center gap-1 w-24 shrink-0", warning ? "text-warning" : "text-foreground-subtle"),
47178
- children: [meta.icon, meta.label]
47179
- }),
47180
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
47181
- className: "relative h-1.5 flex-1 overflow-hidden rounded-full bg-surface-hover",
47182
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
47183
- className: cn("absolute inset-y-0 left-0 rounded-full transition-all", warning ? "bg-warning" : "bg-primary"),
47184
- style: { width: `${tank.level}%` }
47185
- })
47186
- }),
47187
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
47188
- className: cn("tabular-nums w-9 text-right", warning ? "text-warning" : "text-foreground-subtle"),
47189
- children: [tank.level, "%"]
47190
- })
47191
- ]
47192
- });
47193
- if (tank.status !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47194
- className: "flex items-center gap-2 text-xs",
47195
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
47196
- className: cn("inline-flex items-center gap-1 w-24 shrink-0", warning ? "text-warning" : "text-foreground-subtle"),
47197
- children: [meta.icon, meta.label]
47198
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
47199
- level: warning ? "low" : "ok",
47200
- label: tankStatusLabel(t, tank.status),
47201
- size: "sm"
47202
- })]
47203
- });
47204
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47205
- className: "flex items-center gap-2 text-xs",
47206
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
47207
- className: "inline-flex items-center gap-1 w-24 shrink-0 text-foreground-subtle",
47208
- children: [meta.icon, meta.label]
47209
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
47210
- level: null,
47211
- size: "sm"
47212
- })]
47213
- });
47214
- }
47215
- /** A tank's discrete reading in words; the tone is `tankAlert`'s (a full waste tank warns, a full supply tank does not). */
47216
- function tankStatusLabel(t, status) {
47217
- switch (status) {
47218
- case "ok": return consumableLevelLabel("ok", t);
47219
- case "low": return consumableLevelLabel("low", t);
47220
- case "full": return t("uiLibrary.vacuum.tankFull", "Full");
47221
- }
47222
- }
47223
- /**
47224
- * TanksRow — compact 2-column grid of the present tanks for the vacuum
47225
- * hero. Absent tanks (null) are skipped; every PRESENT tank is shown, even
47226
- * when its reading is unknown (`{level:null,status:null}`) — it renders a
47227
- * neutral em-dash row. The row renders iff the robot has at least one tank.
47228
- */
47229
- function TanksRow({ cleanWater, dirtyWater, detergent, dustBin }) {
47230
- const tanks = [
47231
- ["cleanWater", cleanWater],
47232
- ["dirtyWater", dirtyWater],
47233
- ["detergent", detergent],
47234
- ["dustBin", dustBin]
47235
- ];
47236
- if (!tanks.some(([, t]) => t !== null)) return null;
47237
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
47238
- className: "grid grid-cols-1 gap-x-6 gap-y-2 rounded-md border border-border bg-surface-hover/40 p-3 sm:grid-cols-2",
47239
- children: tanks.map(([kind, tank]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TankIndicator, {
47240
- kind,
47241
- tank
47242
- }, kind))
47243
- });
47244
- }
47245
- /**
47246
47397
  * VacuumInlineControl — compact, state-aware vacuum controls for a
47247
47398
  * device-list row. Start / Pause / Return-to-base + battery badge.
47248
47399
  */
@@ -47324,114 +47475,105 @@ function VacuumHeroCard({ trpc, deviceId }) {
47324
47475
  };
47325
47476
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47326
47477
  className: "flex flex-col items-stretch gap-4 rounded-lg border border-border bg-surface p-4",
47327
- children: [
47328
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47329
- className: "flex items-center gap-6",
47330
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumGraphic, {
47331
- state,
47332
- batteryLevel: slice?.batteryLevel ?? null
47333
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47334
- className: "flex flex-col gap-3 flex-1",
47335
- children: [
47336
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47337
- className: "flex items-center justify-between",
47338
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
47339
- className: cn("text-3xl font-semibold", meta.tone),
47340
- children: meta.label
47341
- }), slice?.batteryLevel != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBadge$2, { level: slice.batteryLevel })]
47342
- }),
47343
- slice?.progressPercent != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47344
- className: "flex items-center gap-2",
47345
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
47346
- className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
47347
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
47348
- className: "h-full bg-primary transition-[width] duration-500",
47349
- style: { width: `${Math.max(0, Math.min(100, slice.progressPercent))}%` }
47350
- })
47351
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
47352
- className: "tabular-nums text-xs text-foreground-subtle",
47353
- children: [Math.round(slice.progressPercent), "%"]
47354
- })]
47355
- }),
47356
- slice?.errorCode != null && slice.errorCode !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47357
- className: "inline-flex items-center gap-1 text-xs text-destructive",
47358
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TriangleAlert, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: slice.errorLabel ?? t("uiLibrary.vacuum.errorCode", "Error {{code}}", { code: slice.errorCode }) })]
47359
- }),
47360
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47361
- className: "flex items-center gap-2",
47362
- children: [
47363
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
47364
- label: t("uiLibrary.vacuum.start", "Start"),
47365
- busy,
47366
- active: state === "cleaning",
47367
- onClick: () => press(cap.start),
47368
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-5 w-5" })
47369
- }),
47370
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
47371
- label: t("uiLibrary.vacuum.pause", "Pause"),
47372
- busy,
47373
- active: state === "paused",
47374
- onClick: () => press(cap.pause),
47375
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-5 w-5" })
47376
- }),
47377
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
47378
- label: t("uiLibrary.vacuum.stop", "Stop"),
47379
- busy,
47380
- active: false,
47381
- onClick: () => press(cap.stop),
47382
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Square, { className: "h-4 w-4" })
47383
- }),
47384
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
47385
- label: t("uiLibrary.vacuum.returnToBase", "Return to base"),
47386
- busy,
47387
- active: state === "returning" || state === "docked",
47388
- onClick: () => press(cap.returnToBase),
47389
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(House, { className: "h-5 w-5" })
47390
- }),
47391
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
47392
- label: t("uiLibrary.vacuum.locate", "Locate"),
47393
- busy,
47394
- active: false,
47395
- onClick: () => press(cap.locate),
47396
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Megaphone, { className: "h-5 w-5" })
47397
- })
47398
- ]
47399
- })
47400
- ]
47401
- })]
47402
- }),
47403
- fanSpeeds.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
47404
- className: "flex items-center gap-2 text-xs text-foreground-subtle",
47405
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
47406
- className: "w-16 shrink-0",
47407
- children: t("uiLibrary.vacuum.fanSpeed", "Fan speed")
47408
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
47409
- "aria-label": t("uiLibrary.vacuum.fanSpeed", "Fan speed"),
47410
- value: slice?.fanSpeed ?? "",
47411
- disabled: busy,
47412
- onChange: (e) => {
47413
- onFanSpeed(e.target.value);
47414
- },
47415
- onClick: (e) => {
47416
- e.stopPropagation();
47417
- },
47418
- className: "flex-1 rounded border border-border bg-surface px-2 py-1 text-foreground disabled:opacity-50",
47419
- children: [slice?.fanSpeed == null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
47420
- value: "",
47421
- children: "—"
47422
- }), fanSpeeds.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
47423
- value: s,
47424
- children: s
47425
- }, s))]
47426
- })]
47427
- }),
47428
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TanksRow, {
47429
- cleanWater: slice?.cleanWater ?? null,
47430
- dirtyWater: slice?.dirtyWater ?? null,
47431
- detergent: slice?.detergent ?? null,
47432
- dustBin: slice?.dustBin ?? null
47433
- })
47434
- ]
47478
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47479
+ className: "flex items-center gap-6",
47480
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumGraphic, {
47481
+ state,
47482
+ batteryLevel: slice?.batteryLevel ?? null
47483
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47484
+ className: "flex flex-col gap-3 flex-1",
47485
+ children: [
47486
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47487
+ className: "flex items-center justify-between",
47488
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
47489
+ className: cn("text-3xl font-semibold", meta.tone),
47490
+ children: meta.label
47491
+ }), slice?.batteryLevel != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBadge$2, { level: slice.batteryLevel })]
47492
+ }),
47493
+ slice?.progressPercent != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47494
+ className: "flex items-center gap-2",
47495
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
47496
+ className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
47497
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
47498
+ className: "h-full bg-primary transition-[width] duration-500",
47499
+ style: { width: `${Math.max(0, Math.min(100, slice.progressPercent))}%` }
47500
+ })
47501
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
47502
+ className: "tabular-nums text-xs text-foreground-subtle",
47503
+ children: [Math.round(slice.progressPercent), "%"]
47504
+ })]
47505
+ }),
47506
+ slice?.errorCode != null && slice.errorCode !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47507
+ className: "inline-flex items-center gap-1 text-xs text-destructive",
47508
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TriangleAlert, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: slice.errorLabel ?? t("uiLibrary.vacuum.errorCode", "Error {{code}}", { code: slice.errorCode }) })]
47509
+ }),
47510
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47511
+ className: "flex items-center gap-2",
47512
+ children: [
47513
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
47514
+ label: t("uiLibrary.vacuum.start", "Start"),
47515
+ busy,
47516
+ active: state === "cleaning",
47517
+ onClick: () => press(cap.start),
47518
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-5 w-5" })
47519
+ }),
47520
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
47521
+ label: t("uiLibrary.vacuum.pause", "Pause"),
47522
+ busy,
47523
+ active: state === "paused",
47524
+ onClick: () => press(cap.pause),
47525
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-5 w-5" })
47526
+ }),
47527
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
47528
+ label: t("uiLibrary.vacuum.stop", "Stop"),
47529
+ busy,
47530
+ active: false,
47531
+ onClick: () => press(cap.stop),
47532
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Square, { className: "h-4 w-4" })
47533
+ }),
47534
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
47535
+ label: t("uiLibrary.vacuum.returnToBase", "Return to base"),
47536
+ busy,
47537
+ active: state === "returning" || state === "docked",
47538
+ onClick: () => press(cap.returnToBase),
47539
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(House, { className: "h-5 w-5" })
47540
+ }),
47541
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
47542
+ label: t("uiLibrary.vacuum.locate", "Locate"),
47543
+ busy,
47544
+ active: false,
47545
+ onClick: () => press(cap.locate),
47546
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Megaphone, { className: "h-5 w-5" })
47547
+ })
47548
+ ]
47549
+ })
47550
+ ]
47551
+ })]
47552
+ }), fanSpeeds.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
47553
+ className: "flex items-center gap-2 text-xs text-foreground-subtle",
47554
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
47555
+ className: "w-16 shrink-0",
47556
+ children: t("uiLibrary.vacuum.fanSpeed", "Fan speed")
47557
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
47558
+ "aria-label": t("uiLibrary.vacuum.fanSpeed", "Fan speed"),
47559
+ value: slice?.fanSpeed ?? "",
47560
+ disabled: busy,
47561
+ onChange: (e) => {
47562
+ onFanSpeed(e.target.value);
47563
+ },
47564
+ onClick: (e) => {
47565
+ e.stopPropagation();
47566
+ },
47567
+ className: "flex-1 rounded border border-border bg-surface px-2 py-1 text-foreground disabled:opacity-50",
47568
+ children: [slice?.fanSpeed == null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
47569
+ value: "",
47570
+ children: "—"
47571
+ }), fanSpeeds.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
47572
+ value: s,
47573
+ children: s
47574
+ }, s))]
47575
+ })]
47576
+ })]
47435
47577
  });
47436
47578
  }
47437
47579
  //#endregion
@@ -50359,6 +50501,9 @@ function PetFeederHeroCard({ trpc, deviceId, features, optimistic }) {
50359
50501
  * Reads the `dishwasher` cap status slice and renders inline + hero surfaces
50360
50502
  * mirroring vacuum-control / pet-feeder patterns.
50361
50503
  *
50504
+ * Salt and rinse aid are not here: they are items of the device's `consumables`
50505
+ * cap, shown by the page's Consumables section like every device's (D915).
50506
+ *
50362
50507
  * UNKNOWN IS NOT OFF. The slice carries `null` for everything the appliance has not
50363
50508
  * reported, and this view renders that as "—" / a disabled control, never as an unchecked
50364
50509
  * box or a zero. It also does not echo a command before the appliance confirms it: the
@@ -50492,60 +50637,44 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
50492
50637
  if (busy) return;
50493
50638
  fn();
50494
50639
  };
50495
- if (layout === "compact") {
50496
- const saltWarn = display.saltLevel === "low" || display.saltLevel === "empty";
50497
- const rinseWarn = display.rinseAidLevel === "low" || display.rinseAidLevel === "empty";
50498
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50499
- className: "inline-flex items-center gap-1.5 max-w-full",
50500
- children: [
50501
- display.doorOpen === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
50502
- className: "inline-flex text-warning shrink-0",
50503
- title: t("uiLibrary.dishwasher.doorOpen", "Door open"),
50504
- "aria-label": t("uiLibrary.dishwasher.doorOpen", "Door open"),
50505
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" })
50506
- }),
50507
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
50508
- className: cn("text-xs font-medium tabular-nums shrink-0", meta.tone),
50509
- children: meta.label
50510
- }),
50511
- display.program != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
50512
- className: "text-xs text-foreground-subtle truncate max-w-[7rem]",
50513
- title: display.program,
50514
- children: featureLabel(display.program)
50515
- }),
50516
- display.remainingSeconds != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
50517
- className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle tabular-nums shrink-0",
50518
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3 w-3" }), formatDuration(display.remainingSeconds, t)]
50519
- }),
50520
- saltWarn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
50521
- level: display.saltLevel,
50522
- name: t("uiLibrary.dishwasher.salt", "Salt"),
50523
- size: "sm",
50524
- className: "shrink-0"
50525
- }),
50526
- rinseWarn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
50527
- level: display.rinseAidLevel,
50528
- name: t("uiLibrary.dishwasher.rinseAid", "Rinse aid"),
50529
- size: "sm",
50530
- className: "shrink-0"
50531
- }),
50532
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
50533
- label: t("uiLibrary.dishwasher.start", "Start"),
50534
- busy: busy || !startable,
50535
- active: running,
50536
- onClick: () => press(() => cap.start({})),
50537
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-3 w-3" })
50538
- }),
50539
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
50540
- label: paused ? t("uiLibrary.dishwasher.resume", "Resume") : t("uiLibrary.dishwasher.pause", "Pause"),
50541
- busy: busy || !(running || paused),
50542
- active: paused,
50543
- onClick: () => press(() => paused ? cap.resume({}) : cap.pause({})),
50544
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-3 w-3" })
50545
- })
50546
- ]
50547
- });
50548
- }
50640
+ if (layout === "compact") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50641
+ className: "inline-flex items-center gap-1.5 max-w-full",
50642
+ children: [
50643
+ display.doorOpen === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
50644
+ className: "inline-flex text-warning shrink-0",
50645
+ title: t("uiLibrary.dishwasher.doorOpen", "Door open"),
50646
+ "aria-label": t("uiLibrary.dishwasher.doorOpen", "Door open"),
50647
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" })
50648
+ }),
50649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
50650
+ className: cn("text-xs font-medium tabular-nums shrink-0", meta.tone),
50651
+ children: meta.label
50652
+ }),
50653
+ display.program != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
50654
+ className: "text-xs text-foreground-subtle truncate max-w-[7rem]",
50655
+ title: display.program,
50656
+ children: featureLabel(display.program)
50657
+ }),
50658
+ display.remainingSeconds != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
50659
+ className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle tabular-nums shrink-0",
50660
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3 w-3" }), formatDuration(display.remainingSeconds, t)]
50661
+ }),
50662
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
50663
+ label: t("uiLibrary.dishwasher.start", "Start"),
50664
+ busy: busy || !startable,
50665
+ active: running,
50666
+ onClick: () => press(() => cap.start({})),
50667
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-3 w-3" })
50668
+ }),
50669
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
50670
+ label: paused ? t("uiLibrary.dishwasher.resume", "Resume") : t("uiLibrary.dishwasher.pause", "Pause"),
50671
+ busy: busy || !(running || paused),
50672
+ active: paused,
50673
+ onClick: () => press(() => paused ? cap.resume({}) : cap.pause({})),
50674
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-3 w-3" })
50675
+ })
50676
+ ]
50677
+ });
50549
50678
  const programs = display.availablePrograms;
50550
50679
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50551
50680
  className: "flex flex-col gap-3 rounded-lg border border-border bg-surface p-4",
@@ -50596,25 +50725,14 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
50596
50725
  }),
50597
50726
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50598
50727
  className: "flex flex-wrap items-center gap-x-3 gap-y-1.5",
50599
- children: [
50600
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
50601
- name: t("uiLibrary.dishwasher.salt", "Salt"),
50602
- level: display.saltLevel
50603
- }),
50604
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
50605
- name: t("uiLibrary.dishwasher.rinseAid", "Rinse aid"),
50606
- level: display.rinseAidLevel
50607
- }),
50608
- display.doorOpen === true && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
50609
- className: "inline-flex items-center gap-1 text-xs text-warning",
50610
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.doorOpen", "Door open")]
50611
- }),
50612
- display.activeEvents.map((event) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
50613
- className: "text-xs text-warning",
50614
- title: event,
50615
- children: featureLabel(event)
50616
- }, event))
50617
- ]
50728
+ children: [display.doorOpen === true && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
50729
+ className: "inline-flex items-center gap-1 text-xs text-warning",
50730
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.doorOpen", "Door open")]
50731
+ }), display.activeEvents.map((event) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
50732
+ className: "text-xs text-warning",
50733
+ title: event,
50734
+ children: featureLabel(event)
50735
+ }, event))]
50618
50736
  }),
50619
50737
  display.operationState === "Error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50620
50738
  className: "inline-flex items-center gap-1 text-xs text-danger",
@@ -68253,7 +68371,6 @@ exports.FilterBar = FilterBar;
68253
68371
  exports.Flame = Flame;
68254
68372
  exports.Flashlight = Flashlight;
68255
68373
  exports.FlashlightOff = FlashlightOff;
68256
- exports.FlaskConical = FlaskConical;
68257
68374
  exports.FloatingEventStream = FloatingEventStream;
68258
68375
  exports.FloatingLogStream = FloatingLogStream;
68259
68376
  exports.FloatingPanel = FloatingPanel;
@@ -68945,7 +69062,6 @@ exports.summarizeHubCandidates = summarizeHubCandidates;
68945
69062
  exports.swatchFillClass = swatchFillClass;
68946
69063
  exports.swatchLineClass = swatchLineClass;
68947
69064
  exports.tabForSelector = tabForSelector;
68948
- exports.tankAlert = tankAlert;
68949
69065
  exports.themeToCss = require_theme_to_root_css.themeToCss;
68950
69066
  exports.themeToRootCss = require_theme_to_root_css.themeToRootCss;
68951
69067
  exports.timeInState = timeInState;
@@ -69211,9 +69327,11 @@ exports.useDeviceExtensionListSlots = useDeviceExtensionListSlots;
69211
69327
  exports.useDeviceExtensionListSources = useDeviceExtensionListSources;
69212
69328
  exports.useDeviceExtensionSetSlot = useDeviceExtensionSetSlot;
69213
69329
  exports.useDeviceHistoryDeclareSeries = useDeviceHistoryDeclareSeries;
69330
+ exports.useDeviceHistoryDeleteOrphanSeries = useDeviceHistoryDeleteOrphanSeries;
69214
69331
  exports.useDeviceHistoryGetDeviceEnabled = useDeviceHistoryGetDeviceEnabled;
69215
69332
  exports.useDeviceHistoryGetStatus = useDeviceHistoryGetStatus;
69216
69333
  exports.useDeviceHistoryListNodeSeries = useDeviceHistoryListNodeSeries;
69334
+ exports.useDeviceHistoryListOrphanSeries = useDeviceHistoryListOrphanSeries;
69217
69335
  exports.useDeviceHistoryListSeries = useDeviceHistoryListSeries;
69218
69336
  exports.useDeviceHistoryQuery = useDeviceHistoryQuery;
69219
69337
  exports.useDeviceHistoryQueryMany = useDeviceHistoryQueryMany;
@@ -69666,6 +69784,7 @@ exports.useOsdManagerSetSlotCameraEnabled = useOsdManagerSetSlotCameraEnabled;
69666
69784
  exports.useOsdManagerUpsertTemplate = useOsdManagerUpsertTemplate;
69667
69785
  exports.useOsdSetOverlay = useOsdSetOverlay;
69668
69786
  exports.usePTZ = usePTZ;
69787
+ exports.usePersistedDisclosure = usePersistedDisclosure;
69669
69788
  exports.usePersistedPageSize = usePersistedPageSize;
69670
69789
  exports.usePetFeederCallPet = usePetFeederCallPet;
69671
69790
  exports.usePetFeederCancelFeed = usePetFeederCancelFeed;
@@ -69808,6 +69927,7 @@ exports.usePipelineOrchestratorApplyDeviceSettingsPatch = usePipelineOrchestrato
69808
69927
  exports.usePipelineOrchestratorAssignAudio = usePipelineOrchestratorAssignAudio;
69809
69928
  exports.usePipelineOrchestratorAssignPipeline = usePipelineOrchestratorAssignPipeline;
69810
69929
  exports.usePipelineOrchestratorDeleteNodeBenchmarkResult = usePipelineOrchestratorDeleteNodeBenchmarkResult;
69930
+ exports.usePipelineOrchestratorForgetInferenceDevice = usePipelineOrchestratorForgetInferenceDevice;
69811
69931
  exports.usePipelineOrchestratorGetAgentLoad = usePipelineOrchestratorGetAgentLoad;
69812
69932
  exports.usePipelineOrchestratorGetAgentSettings = usePipelineOrchestratorGetAgentSettings;
69813
69933
  exports.usePipelineOrchestratorGetAudioAssignment = usePipelineOrchestratorGetAudioAssignment;