@camstack/ui-library 1.2.385 → 1.2.387

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/{PrivacyMaskSettings-BUWvVF_n.cjs → PrivacyMaskSettings-BLM8OfT5.cjs} +1 -1
  3. package/dist/{PrivacyMaskSettings-D7o4tx8e.js → PrivacyMaskSettings-DZfNWppi.js} +1 -1
  4. package/dist/{SceneMonitorEditor-BwelmHlF.js → SceneMonitorEditor-C09HggGp.js} +40 -5
  5. package/dist/{SceneMonitorEditor-C-way4ES.cjs → SceneMonitorEditor-DjZ61oOM.cjs} +39 -4
  6. package/dist/{createLucideIcon-kNomBP27.js → chevron-down-B71qEdFG.js} +11 -1
  7. package/dist/{createLucideIcon-COWDteJh.cjs → chevron-down-VnEXTuJa.cjs} +16 -0
  8. package/dist/composites/cap-settings/NavigationPanel.d.ts +0 -11
  9. package/dist/composites/cap-settings/SceneSettingsForm.d.ts +0 -35
  10. package/dist/composites/data-table.d.ts +8 -1
  11. package/dist/composites/device-controls/dishwasher-control.d.ts +0 -14
  12. package/dist/composites/device-controls/primary-child.d.ts +9 -3
  13. package/dist/composites/device-controls/vacuum-control.d.ts +0 -16
  14. package/dist/composites/device-controls/water-heater-control.d.ts +0 -17
  15. package/dist/composites/device-item/helpers.d.ts +8 -0
  16. package/dist/composites/device-list/device-mode.d.ts +8 -0
  17. package/dist/composites/device-list/page-size-select.d.ts +0 -6
  18. package/dist/composites/device-list/table-layout.d.ts +4 -1
  19. package/dist/composites/page-shell.d.ts +9 -1
  20. package/dist/composites/pipeline-step.d.ts +0 -7
  21. package/dist/composites/scope-picker.d.ts +0 -23
  22. package/dist/composites/stream-panel.d.ts +0 -10
  23. package/dist/composites/unit-picker/UnitPicker.d.ts +0 -5
  24. package/dist/hooks/index.d.ts +4 -0
  25. package/dist/hooks/use-device-removal.d.ts +26 -0
  26. package/dist/hooks/use-row-operations.d.ts +22 -0
  27. package/dist/{icons-cIFSolkx.js → icons-B1fIm76S.js} +4 -4
  28. package/dist/{icons-BWpVdTJu.cjs → icons-BP_WA6rt.cjs} +1486 -1486
  29. package/dist/index.cjs +551 -379
  30. package/dist/index.js +308 -139
  31. package/dist/{mic-off-C2VTYr91.js → mic-off-B83GCRGo.js} +1 -1
  32. package/dist/{mic-off-4nCMfrMI.cjs → mic-off-BKguA8pd.cjs} +3 -3
  33. package/dist/primitives/select.d.ts +7 -0
  34. package/dist/{settings-2-jT1iBqHD.js → settings-2-CHcjJCqa.js} +1 -1
  35. package/dist/{settings-2-CR4WtEw4.cjs → settings-2-LxL8s7fK.cjs} +1 -1
  36. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.387] - 2026-11-05
11
+
12
+ ### Changed
13
+
14
+ - Choosing a primary child on a container whose children carry a source id (ESPHome) now sticks: the picked child is shown and used. Every dropdown in the library is now the shared Select.
15
+ - A confirmed device delete now shows the row as Removing… with its actions disabled until the hub has finished, then drops it; a failure restores the row with the error
16
+
17
+ ## [1.2.386] - 2026-11-05
18
+
19
+ ### Changed
20
+
21
+ - PageShell exposes PageHeaderActions so a page body can place its Add button in the page header
22
+
10
23
  ## [1.2.385] - 2026-11-05
11
24
 
12
25
  ### Removed
@@ -1,4 +1,4 @@
1
- const require_mic_off = require("./mic-off-4nCMfrMI.cjs");
1
+ const require_mic_off = require("./mic-off-BKguA8pd.cjs");
2
2
  const require_index = require("./index.cjs");
3
3
  const require_MaskShapeCanvas = require("./MaskShapeCanvas-NCNwu9_5.cjs");
4
4
  let react = require("react");
@@ -1,4 +1,4 @@
1
- import { n as Hexagon, t as MicOff } from "./mic-off-C2VTYr91.js";
1
+ import { n as Hexagon, t as MicOff } from "./mic-off-B83GCRGo.js";
2
2
  import { A as Trash2, TEXT_HINT, UiTrans, W as Square, WidgetPanel, Yt as Mic, isAbsentProvider, useDeviceProxy, usePlayerOverlayLayer, useSystem, useUiText, zn as EyeOff } from "./index.js";
3
3
  import { MaskShapeCanvas } from "./MaskShapeCanvas-C9cJlxvG.js";
4
4
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -1,5 +1,5 @@
1
- import { t as Settings2 } from "./settings-2-jT1iBqHD.js";
2
- import { A as Trash2, Or as Camera, SCENE_CAPTURE_VARIANTS, TEXT_HINT, UiTrans, _n as ImageOff, badgeFor, coverageLine, gt as RotateCcw, hubPath, isAbsentProvider, roiTooLarge, statusLine, useConfirm, useDeviceProxy, useDeviceSnapshotImage, useDeviceStateSlice, useSystem, useUiText, variantLabel } from "./index.js";
1
+ import { t as Settings2 } from "./settings-2-CHcjJCqa.js";
2
+ import { A as Trash2, Dr as Camera, SCENE_CAPTURE_VARIANTS, Select, TEXT_HINT, UiTrans, _n as ImageOff, badgeFor, coverageLine, gt as RotateCcw, hubPath, isAbsentProvider, roiTooLarge, statusLine, useConfirm, useDeviceProxy, useDeviceSnapshotImage, useDeviceStateSlice, useSystem, useUiText, variantLabel } from "./index.js";
3
3
  import { MaskShapeCanvas } from "./MaskShapeCanvas-C9cJlxvG.js";
4
4
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
5
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -183,6 +183,41 @@ function formatCapturedAt(at) {
183
183
  }
184
184
  //#endregion
185
185
  //#region src/composites/cap-settings/SceneSettingsForm.tsx
186
+ /**
187
+ * SceneSettingsForm — every knob a scene actually has, per scene.
188
+ *
189
+ * ## Why this exists
190
+ *
191
+ * The model has carried `enabled`, `triggerMode`, `checkIntervalSec`,
192
+ * `check.threshold`, `check.hysteresisCount`, `quietSeconds`,
193
+ * `minObservationSpacingSec`, `emit`, `autoRestore` and `confirm` since the
194
+ * provider shipped, and `updateScene` has accepted a patch for all of them —
195
+ * but the editor exposed **none**. Every scene therefore ran on the creation
196
+ * defaults, and an operator whose scene fired too eagerly (or not at all) had
197
+ * no move available except deleting it and drawing it again. Tuning knobs that
198
+ * exist and cannot be reached are worse than absent ones: they read, from the
199
+ * code, as a feature that shipped.
200
+ *
201
+ * ## What is deliberately NOT here
202
+ *
203
+ * `check.mode: 'llm'`. The cap declares it and the engine has an adapter seam
204
+ * for it, but no production caller supplies `SceneEngineDeps.llm` — an
205
+ * `llm`-mode scene reports `availability: 'unavailable'` forever. Offering the
206
+ * switch would let an operator convert a working scene into a permanently
207
+ * broken one in one click. The vision model reaches a scene through the
208
+ * **`confirm`** adjudicator instead, which IS wired (`SceneConfirmGate`, built
209
+ * unconditionally at the engine's construction site) and is the better
210
+ * topology anyway: CLIP proposes at the poll cadence, the model is asked only
211
+ * on a candidate flip.
212
+ *
213
+ * ## Editing model
214
+ *
215
+ * Local draft, committed on blur when it differs — the `AutotrackSection`
216
+ * shape. Selects and checkboxes commit on change, because there is no
217
+ * intermediate state worth protecting. There is no second store and no "save"
218
+ * button: `updateScene` writes the authority the provider already owns and the
219
+ * caller refetches (D62).
220
+ */
186
221
  var FIELD = "rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground focus:border-primary focus:outline-none disabled:opacity-50";
187
222
  var LABEL = "text-[10px] font-medium text-foreground-subtle";
188
223
  var GROUP = "flex flex-col gap-2 rounded-md border border-border bg-surface/40 p-2";
@@ -289,7 +324,7 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
289
324
  children: [/* @__PURE__ */ jsx("span", {
290
325
  className: LABEL,
291
326
  children: t("uiLibrary.sceneSettingsForm.whenToLook", "When to look")
292
- }), /* @__PURE__ */ jsxs("select", {
327
+ }), /* @__PURE__ */ jsxs(Select, {
293
328
  value: monitor.triggerMode,
294
329
  disabled,
295
330
  className: FIELD,
@@ -487,7 +522,7 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
487
522
  children: [/* @__PURE__ */ jsx("span", {
488
523
  className: LABEL,
489
524
  children: t("uiLibrary.sceneSettingsForm.noReference", "When a light has no reference of its own")
490
- }), /* @__PURE__ */ jsxs("select", {
525
+ }), /* @__PURE__ */ jsxs(Select, {
491
526
  value: monitor.onUncoveredCondition,
492
527
  disabled,
493
528
  className: FIELD,
@@ -522,7 +557,7 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
522
557
  children: [/* @__PURE__ */ jsx("span", {
523
558
  className: LABEL,
524
559
  children: t("uiLibrary.sceneSettingsForm.notificationsFollow", "Notifications follow")
525
- }), /* @__PURE__ */ jsxs("select", {
560
+ }), /* @__PURE__ */ jsxs(Select, {
526
561
  value: monitor.emit,
527
562
  disabled,
528
563
  className: FIELD,
@@ -1,4 +1,4 @@
1
- const require_settings_2 = require("./settings-2-CR4WtEw4.cjs");
1
+ const require_settings_2 = require("./settings-2-LxL8s7fK.cjs");
2
2
  const require_index = require("./index.cjs");
3
3
  const require_MaskShapeCanvas = require("./MaskShapeCanvas-NCNwu9_5.cjs");
4
4
  let react = require("react");
@@ -183,6 +183,41 @@ function formatCapturedAt(at) {
183
183
  }
184
184
  //#endregion
185
185
  //#region src/composites/cap-settings/SceneSettingsForm.tsx
186
+ /**
187
+ * SceneSettingsForm — every knob a scene actually has, per scene.
188
+ *
189
+ * ## Why this exists
190
+ *
191
+ * The model has carried `enabled`, `triggerMode`, `checkIntervalSec`,
192
+ * `check.threshold`, `check.hysteresisCount`, `quietSeconds`,
193
+ * `minObservationSpacingSec`, `emit`, `autoRestore` and `confirm` since the
194
+ * provider shipped, and `updateScene` has accepted a patch for all of them —
195
+ * but the editor exposed **none**. Every scene therefore ran on the creation
196
+ * defaults, and an operator whose scene fired too eagerly (or not at all) had
197
+ * no move available except deleting it and drawing it again. Tuning knobs that
198
+ * exist and cannot be reached are worse than absent ones: they read, from the
199
+ * code, as a feature that shipped.
200
+ *
201
+ * ## What is deliberately NOT here
202
+ *
203
+ * `check.mode: 'llm'`. The cap declares it and the engine has an adapter seam
204
+ * for it, but no production caller supplies `SceneEngineDeps.llm` — an
205
+ * `llm`-mode scene reports `availability: 'unavailable'` forever. Offering the
206
+ * switch would let an operator convert a working scene into a permanently
207
+ * broken one in one click. The vision model reaches a scene through the
208
+ * **`confirm`** adjudicator instead, which IS wired (`SceneConfirmGate`, built
209
+ * unconditionally at the engine's construction site) and is the better
210
+ * topology anyway: CLIP proposes at the poll cadence, the model is asked only
211
+ * on a candidate flip.
212
+ *
213
+ * ## Editing model
214
+ *
215
+ * Local draft, committed on blur when it differs — the `AutotrackSection`
216
+ * shape. Selects and checkboxes commit on change, because there is no
217
+ * intermediate state worth protecting. There is no second store and no "save"
218
+ * button: `updateScene` writes the authority the provider already owns and the
219
+ * caller refetches (D62).
220
+ */
186
221
  var FIELD = "rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground focus:border-primary focus:outline-none disabled:opacity-50";
187
222
  var LABEL = "text-[10px] font-medium text-foreground-subtle";
188
223
  var GROUP = "flex flex-col gap-2 rounded-md border border-border bg-surface/40 p-2";
@@ -289,7 +324,7 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
289
324
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
290
325
  className: LABEL,
291
326
  children: t("uiLibrary.sceneSettingsForm.whenToLook", "When to look")
292
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
327
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_index.Select, {
293
328
  value: monitor.triggerMode,
294
329
  disabled,
295
330
  className: FIELD,
@@ -487,7 +522,7 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
487
522
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
488
523
  className: LABEL,
489
524
  children: t("uiLibrary.sceneSettingsForm.noReference", "When a light has no reference of its own")
490
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
525
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_index.Select, {
491
526
  value: monitor.onUncoveredCondition,
492
527
  disabled,
493
528
  className: FIELD,
@@ -522,7 +557,7 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
522
557
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
523
558
  className: LABEL,
524
559
  children: t("uiLibrary.sceneSettingsForm.notificationsFollow", "Notifications follow")
525
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
560
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_index.Select, {
526
561
  value: monitor.emit,
527
562
  disabled,
528
563
  className: FIELD,
@@ -107,5 +107,15 @@ var createLucideIcon = (iconName, iconNode) => {
107
107
  Component.displayName = toPascalCase(iconName);
108
108
  return Component;
109
109
  };
110
+ /**
111
+ * @license lucide-react v0.576.0 - ISC
112
+ *
113
+ * This source code is licensed under the ISC license.
114
+ * See the LICENSE file in the root directory of this source tree.
115
+ */
116
+ var ChevronDown = createLucideIcon("chevron-down", [["path", {
117
+ d: "m6 9 6 6 6-6",
118
+ key: "qrunsl"
119
+ }]]);
110
120
  //#endregion
111
- export { createLucideIcon as t };
121
+ export { createLucideIcon as n, ChevronDown as t };
@@ -107,7 +107,23 @@ var createLucideIcon = (iconName, iconNode) => {
107
107
  Component.displayName = toPascalCase(iconName);
108
108
  return Component;
109
109
  };
110
+ /**
111
+ * @license lucide-react v0.576.0 - ISC
112
+ *
113
+ * This source code is licensed under the ISC license.
114
+ * See the LICENSE file in the root directory of this source tree.
115
+ */
116
+ var ChevronDown = createLucideIcon("chevron-down", [["path", {
117
+ d: "m6 9 6 6 6-6",
118
+ key: "qrunsl"
119
+ }]]);
110
120
  //#endregion
121
+ Object.defineProperty(exports, "ChevronDown", {
122
+ enumerable: true,
123
+ get: function() {
124
+ return ChevronDown;
125
+ }
126
+ });
111
127
  Object.defineProperty(exports, "createLucideIcon", {
112
128
  enumerable: true,
113
129
  get: function() {
@@ -1,14 +1,3 @@
1
- /**
2
- * NavigationPanel — the `navigation` cap's own panel (`host/navigation-panel`):
3
- * a drive pad, the data-driven action and sound buttons, the fill light.
4
- *
5
- * Nothing here knows a vendor: the buttons come from `listActions()` (enabled
6
- * entries only, icons mapped from the entry's lucide hint), the primitives
7
- * from `getFeatures()`, and the live chips from the `navigation` slice. Hold
8
- * a drive button to move — the hook nudges at 1 Hz and stops on release, like
9
- * the robot app's joystick. The manual level slider appears only in manual
10
- * mode, as the cap's own contract asks.
11
- */
12
1
  import { type JSX } from "react";
13
2
  import type { CapSettingsComponentProps } from "./index";
14
3
  export declare function NavigationPanel({ deviceId }: CapSettingsComponentProps): JSX.Element;
@@ -1,38 +1,3 @@
1
- /**
2
- * SceneSettingsForm — every knob a scene actually has, per scene.
3
- *
4
- * ## Why this exists
5
- *
6
- * The model has carried `enabled`, `triggerMode`, `checkIntervalSec`,
7
- * `check.threshold`, `check.hysteresisCount`, `quietSeconds`,
8
- * `minObservationSpacingSec`, `emit`, `autoRestore` and `confirm` since the
9
- * provider shipped, and `updateScene` has accepted a patch for all of them —
10
- * but the editor exposed **none**. Every scene therefore ran on the creation
11
- * defaults, and an operator whose scene fired too eagerly (or not at all) had
12
- * no move available except deleting it and drawing it again. Tuning knobs that
13
- * exist and cannot be reached are worse than absent ones: they read, from the
14
- * code, as a feature that shipped.
15
- *
16
- * ## What is deliberately NOT here
17
- *
18
- * `check.mode: 'llm'`. The cap declares it and the engine has an adapter seam
19
- * for it, but no production caller supplies `SceneEngineDeps.llm` — an
20
- * `llm`-mode scene reports `availability: 'unavailable'` forever. Offering the
21
- * switch would let an operator convert a working scene into a permanently
22
- * broken one in one click. The vision model reaches a scene through the
23
- * **`confirm`** adjudicator instead, which IS wired (`SceneConfirmGate`, built
24
- * unconditionally at the engine's construction site) and is the better
25
- * topology anyway: CLIP proposes at the poll cadence, the model is asked only
26
- * on a candidate flip.
27
- *
28
- * ## Editing model
29
- *
30
- * Local draft, committed on blur when it differs — the `AutotrackSection`
31
- * shape. Selects and checkboxes commit on change, because there is no
32
- * intermediate state worth protecting. There is no second store and no "save"
33
- * button: `updateScene` writes the authority the provider already owns and the
34
- * caller refetches (D62).
35
- */
36
1
  import type { SceneCheck, SceneMonitor, SceneUncoveredPolicy } from "@camstack/types";
37
2
  import { type JSX } from "react";
38
3
  /** The subset of `updateScene`'s patch this form can produce. */
@@ -46,6 +46,7 @@
46
46
  * the operator moves through the rows.
47
47
  */
48
48
  import { type JSX, type ReactNode } from "react";
49
+ import type { RowOperationState } from "../hooks/use-row-operations";
49
50
  import { type DataTablePaginationLabels } from "./data-table-pagination";
50
51
  import { type DataTableSelection } from "./data-table-selection";
51
52
  import { type TableSortState, type TableSortValue } from "./table-sort";
@@ -108,6 +109,12 @@ export interface DataTableProps<T> {
108
109
  /** Per-row className resolver (e.g. status-coloured backgrounds). */
109
110
  readonly rowClassName?: (row: T, rowIndex: number) => string | undefined;
110
111
  /**
112
+ * Per-row operation in flight (`useRowOperations().stateOf(key)`): `pending`
113
+ * dims the row, marks it `aria-busy` and withholds `onRowClick`; `failed`
114
+ * tints it and exposes the message as its title. Absent = idle rows.
115
+ */
116
+ readonly rowState?: (row: T, rowIndex: number) => RowOperationState | undefined;
117
+ /**
111
118
  * Tailwind `max-h-*` class capping the table. The rows then scroll INSIDE
112
119
  * this container and the column header is pinned to its top; the page around
113
120
  * it does not move. Absent (default) = the table is as tall as its rows and
@@ -176,4 +183,4 @@ export interface DataTableProps<T> {
176
183
  * does not need a breakpoint of its own.
177
184
  */
178
185
  export declare const TABLE_SCROLL_MAX_H: "max-h-[60vh]";
179
- export declare function DataTable<T>({ columns, rows, rowKey, onRowClick, minWidthPx, emptyMessage, className, bordered, rowClassName, maxBodyHeight, fill, defaultSort, sort: sortProp, onSortChange, pageSize, page: pageProp, onPageChange, paginationLabels, selection, renderExpanded, isExpanded }: DataTableProps<T>): JSX.Element | null;
186
+ export declare function DataTable<T>({ columns, rows, rowKey, onRowClick, minWidthPx, emptyMessage, className, bordered, rowClassName, rowState, maxBodyHeight, fill, defaultSort, sort: sortProp, onSortChange, pageSize, page: pageProp, onPageChange, paginationLabels, selection, renderExpanded, isExpanded }: DataTableProps<T>): JSX.Element | null;
@@ -1,17 +1,3 @@
1
- /**
2
- * Dishwasher controls for a `DeviceType.Dishwasher` device.
3
- *
4
- * Reads the `dishwasher` cap status slice and renders inline + hero surfaces
5
- * mirroring vacuum-control / pet-feeder patterns.
6
- *
7
- * Salt and rinse aid are not here: they are items of the device's `consumables`
8
- * cap, shown by the page's Consumables section like every device's (D915).
9
- *
10
- * UNKNOWN IS NOT OFF. The slice carries `null` for everything the appliance has not
11
- * reported, and this view renders that as "—" / a disabled control, never as an unchecked
12
- * box or a zero. It also does not echo a command before the appliance confirms it: the
13
- * slice moves when the appliance says so.
14
- */
15
1
  import { type UiTextFn } from "../../contexts/ui-text";
16
2
  import type { ReactNode } from "react";
17
3
  import type { DishwasherOperationState, DishwasherStatus } from "@camstack/types";
@@ -3,6 +3,12 @@ export declare const PRIORITY: readonly string[];
3
3
  export interface ChildRef {
4
4
  readonly id: number;
5
5
  readonly entityId: string;
6
+ /**
7
+ * The child's own `stableId` — the key a pick is persisted under. Distinct
8
+ * from `entityId` whenever the integration populates `sourceInfo.id`
9
+ * (ESPHome children do), so it must travel separately.
10
+ */
11
+ readonly stableId?: string | undefined;
6
12
  readonly type: string;
7
13
  /** `DeviceRole`, ranked ABOVE `type` — see `CONTAINER_CHILD_ROLE_PRIORITY`. */
8
14
  readonly role?: string | undefined;
@@ -13,8 +19,8 @@ export interface ChildRef {
13
19
  * Resolve a container's primary child: the operator's pick when it still
14
20
  * matches a live child, else the highest-priority type.
15
21
  *
16
- * `entityId` here is `sourceInfo?.id ?? stableId` (see `containerChildToRef`).
17
- * The shared resolver matches its canonical `stableId` key first and accepts
18
- * that legacy value too, so a pick stored under either keeps resolving.
22
+ * `entityId` here is `sourceInfo?.id ?? stableId` (see `containerChildToRef`);
23
+ * `stableId` is the child's own. The shared resolver matches the canonical
24
+ * `stableId` key first and accepts the `entityId` as the legacy value, so a pick stored under either keeps resolving.
19
25
  */
20
26
  export declare function resolvePrimaryChild(children: readonly ChildRef[], overrideEntityId: string | undefined, containerName?: string): ChildRef | null;
@@ -1,19 +1,3 @@
1
- /**
2
- * Robot-vacuum controls for a `DeviceType.Vacuum` device.
3
- *
4
- * Reads the `vacuum-control` cap status slice (state / batteryLevel /
5
- * fanSpeed / availableFanSpeeds) and renders:
6
- * - VacuumInlineControl (device-list row): compact Start / Pause /
7
- * Return-to-base buttons with state-aware highlight + a battery %
8
- * badge.
9
- * - VacuumHeroCard (device-detail hero): a state glyph + label, the
10
- * full action set (Start / Pause / Stop / Return / Locate), a
11
- * fan-speed selector built from `availableFanSpeeds`, and the
12
- * battery readout.
13
- *
14
- * Mirrors the cover/update controls' `useDeviceCapability` pattern so the
15
- * row and hero never diverge. Buttons are gated on `available`.
16
- */
17
1
  import { type UiTextFn } from "../../contexts/ui-text";
18
2
  import type { ReactNode } from "react";
19
3
  import type { VacuumState } from "@camstack/types";
@@ -1,20 +1,3 @@
1
- /**
2
- * Water-heater controls for a `DeviceType.WaterHeater` device.
3
- *
4
- * Reads the `water-heater` cap status slice (current / target temp /
5
- * operation mode / away / bounds) and renders:
6
- * - WaterHeaterInlineControl (device-list row): a compact
7
- * `current°→target°` readout + a tiny mode hint. Row-narrow.
8
- * - WaterHeaterHeroCard (device-detail hero): a bespoke state-tinted
9
- * tank / boiler SVG (water fill + heating-element glow when actively
10
- * heating) in the framed-box language, a target-temp stepper with
11
- * min/max clamp, an operation-mode `<select>`, an "Away" toggle
12
- * (only when `away !== null`), and the current-temp readout.
13
- *
14
- * Mirrors the climate / valve controls' `useDeviceCapability` +
15
- * `useOptimisticSlice` pattern. Controls are gated on `available`. The
16
- * decorative SVG is aria-hidden.
17
- */
18
1
  import { type ReactNode } from "react";
19
2
  import type { DeviceControlProps } from "./control-registry";
20
3
  /** Heating phase derived from the slice — drives the tank tint + element glow. */
@@ -15,6 +15,7 @@
15
15
  * `hasMotionTriggerFeature`.
16
16
  * - `shortStableId` — display-token for stableId tail.
17
17
  */
18
+ import type { RowOperationState } from "../../hooks/use-row-operations";
18
19
  import { type ChildLayout, type DeviceDisplayOverride, type RoleDisplayDefault, type SourceInfo } from "@camstack/types";
19
20
  import type { LucideIcon } from "lucide-react";
20
21
  import type { ReactNode } from "react";
@@ -187,6 +188,13 @@ export interface DeviceItemProps {
187
188
  */
188
189
  readonly onDelete?: (device: DeviceItemDevice) => void;
189
190
  /**
191
+ * An operation in flight on this row (table view). `pending` dims the row,
192
+ * swaps its actions for a "Removing…" indicator and withholds the row click;
193
+ * `failed` keeps the row interactive and shows the error inline. Comes from
194
+ * `useDeviceRemoval().rowOperation`.
195
+ */
196
+ readonly operation?: RowOperationState;
197
+ /**
190
198
  * When provided, renders a leading checkbox for row selection.
191
199
  * Only passed to top-level rows (accessory sub-rows omit it).
192
200
  * Omitted ⇒ no checkbox, zero layout change for existing callers.
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { UseDeviceProxyTrpc } from "../../hooks/use-device-proxy";
3
+ import type { RowOperationState } from "../../hooks/use-row-operations";
3
4
  import type { DeviceItemDevice, DeviceItemParent } from "../device-item/helpers";
4
5
  import { type TableContext } from "./columns";
5
6
  import type { MultiSelectOption } from "./multi-select";
@@ -89,6 +90,13 @@ export interface DeviceListDeviceProps {
89
90
  */
90
91
  readonly onDeleteDevice?: (device: DeviceItemDevice) => void;
91
92
  /**
93
+ * The state of a removal in flight, per row (`useDeviceRemoval().rowOperation`).
94
+ * `pending` shows the row dimmed with "Removing…" and its actions disabled until
95
+ * the removal has completed; `failed` restores it with the error inline.
96
+ * Omitted ⇒ rows render exactly as before.
97
+ */
98
+ readonly rowOperation?: (device: DeviceItemDevice) => RowOperationState | undefined;
99
+ /**
92
100
  * When `true`, renders a leading checkbox on each top-level row
93
101
  * (accessory sub-rows are excluded — same gate as `renderRowAction`).
94
102
  * Selection state may be controlled via `selectedIds`; when omitted
@@ -1,9 +1,3 @@
1
- /**
2
- * <PageSizeSelect> — the "Per page" control under a paginated list.
3
- *
4
- * One control for both list modes (the device table and the generic table),
5
- * so the two never drift in look or wording. `PAGE_SIZE_ALL` renders as "All".
6
- */
7
1
  import { type ReactNode } from "react";
8
2
  import { type UiTextFn } from "../../contexts/ui-text";
9
3
  export interface PageSizeSelectProps {
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { UseDeviceProxyTrpc } from "../../hooks/use-device-proxy";
3
+ import type { RowOperationState } from "../../hooks/use-row-operations";
3
4
  import type { DeviceItemDevice, DeviceItemParent } from "../device-item/helpers";
4
5
  import { type DeviceColumnId } from "./columns";
5
6
  import type { DeviceSort, SortableColumnId } from "./sort";
@@ -30,6 +31,8 @@ interface TableLayoutProps extends DeviceRowsProps {
30
31
  * top-level rows carry it (accessory sub-rows are excluded — same gate
31
32
  * as `renderRowAction`). */
32
33
  readonly onDeleteDevice: ((device: DeviceItemDevice) => void) | null;
34
+ /** Per-row operation in flight (a removal): pending dims + disables the row. */
35
+ readonly rowOperation: ((device: DeviceItemDevice) => RowOperationState | undefined) | null;
33
36
  /** Parent rows the OPERATOR opened. Only the chevron writes it — a search
34
37
  * never does (it narrows the rows; it does not open them). */
35
38
  readonly expandedParents: ReadonlySet<number>;
@@ -46,5 +49,5 @@ interface TableLayoutProps extends DeviceRowsProps {
46
49
  /** Header-click handler — toggles the column's sort (asc→desc→none). */
47
50
  readonly onSort: (columnId: SortableColumnId) => void;
48
51
  }
49
- export declare function TableLayout({ rows, accessoriesByParent, expandedParents, onToggleExpand, devices, visibleColumns, trpc, resolveIntegrationIcon, resolveParent, onNavigate, renderRowAction, onDeleteDevice, selectable, rowClickSelects, activeSelected, onToggleSelection, sort, onSort }: TableLayoutProps): ReactNode;
52
+ export declare function TableLayout({ rows, accessoriesByParent, expandedParents, onToggleExpand, devices, visibleColumns, trpc, resolveIntegrationIcon, resolveParent, onNavigate, renderRowAction, onDeleteDevice, rowOperation, selectable, rowClickSelects, activeSelected, onToggleSelection, sort, onSort }: TableLayoutProps): ReactNode;
50
53
  export {};
@@ -6,7 +6,15 @@
6
6
  * `nav` slot under it for hub pills / selectors.
7
7
  */
8
8
  import type { LucideIcon } from "lucide-react";
9
- import type { ReactNode, JSX } from "react";
9
+ import { type ReactNode, type JSX } from "react";
10
+ /**
11
+ * Put `children` in the actions area of the enclosing PageShell header, from any depth of the
12
+ * page body (a table that owns its own Add button). Outside a shell with a title row there is no
13
+ * header to join, so it renders inline in a right-aligned row instead.
14
+ */
15
+ export declare function PageHeaderActions({ children }: {
16
+ readonly children: ReactNode;
17
+ }): JSX.Element | null;
10
18
  export interface PageShellProps {
11
19
  readonly icon?: LucideIcon;
12
20
  readonly title?: string;
@@ -1,10 +1,3 @@
1
- /**
2
- * PipelineStep — single step card in the pipeline builder.
3
- *
4
- * Driven entirely by PipelineAddonSchema. No hardcoded addon names or models.
5
- * Renders: colored left border, header with I/O class tags, expandable config
6
- * (Model → Confidence), children recursively.
7
- */
8
1
  import { type JSX } from "react";
9
2
  import type { PipelineSlot, PipelineAddonSchema, ConfigField } from "@camstack/types";
10
3
  export interface PipelineStepDisplayConfig {
@@ -1,26 +1,3 @@
1
- /**
2
- * <ScopePicker> — operator-facing editor for a `TokenScope[]` value.
3
- *
4
- * Two consumers: the user-scope editor on `/system/users` (admin grants
5
- * a baseline scope set to a non-admin user) and the scoped-token create
6
- * modal on `/system/api-keys` (a caller carves out a subset of THEIR
7
- * scopes for a token).
8
- *
9
- * Row shapes per scope `type`:
10
- * - category → target: 'device' | 'system'
11
- * - capability → target: cap name from `KNOWN_CAP_NAMES`
12
- * - addon → target: addon id from `useAddonsList`
13
- * - device → a v3 `DeviceSelector` (all / ids / types / locations)
14
- * plus `includeLinked`, edited by `<DeviceSelectorPicker>`
15
- *
16
- * `access` is always 3 independent checkboxes (view / create / delete).
17
- *
18
- * `clampToParent` narrows the target dropdown + access checkboxes to
19
- * what the CALLER possesses. Admins pass `null` to allow everything.
20
- *
21
- * This is the RAW editor, for TOKENS. People are not edited here: a person
22
- * is an Admin or a Viewer (D921), set from `/system/identity?tab=users`.
23
- */
24
1
  import { type ReactNode } from "react";
25
2
  import { type TokenScope } from "@camstack/types";
26
3
  import { type UiTextFn } from "../contexts/ui-text";
@@ -1,13 +1,3 @@
1
- /**
2
- * StreamPanel — unified camera stream viewer with header bar, overlay toggles,
3
- * stream selector, pipeline phase, play/stop, and snapshot preview.
4
- *
5
- * Combines features from LiveStreamViewer (admin-ui) and PipelineTab (benchmark)
6
- * into a single reusable component in ui-library.
7
- *
8
- * The component is data-agnostic: callers fetch devices/streams/detections using
9
- * their own hooks and pass the results as props.
10
- */
11
1
  import { type ReactNode, type JSX } from "react";
12
2
  import type { CamProfile, WebrtcSessionDebug } from "@camstack/types";
13
3
  import type { ApplyDownlinkProbe } from "./adaptive-downlink";
@@ -1,8 +1,3 @@
1
- /**
2
- * Same-dimension unit picker. Lists only `unitsForDimension(dimension)`; an
3
- * empty dimension disables the control and shows `emptyReason` so the operator
4
- * never sees a refused conversion as a pickable option.
5
- */
6
1
  import { type UnitDimension } from "@camstack/types";
7
2
  import type { ReactNode } from "react";
8
3
  export interface UnitPickerProps {
@@ -31,6 +31,10 @@ export { SNAPSHOT_MEDIA_PATH, useDeviceSnapshotImage } from "./use-device-snapsh
31
31
  export type { DevicePipelineMetrics, DeviceWebrtcResult, SessionRenegotiationState, UseDeviceWebrtcTrpc, WebrtcTarget } from "./use-device-webrtc";
32
32
  export { useDeviceWebrtc } from "./use-device-webrtc";
33
33
  export { useDevice, useDevices } from "./use-devices";
34
+ export type { DeviceRemoval, UseDeviceRemovalOptions } from "./use-device-removal";
35
+ export { useDeviceRemoval } from "./use-device-removal";
36
+ export type { RowOperationState, UseRowOperationsResult } from "./use-row-operations";
37
+ export { errorMessageOf, useRowOperations } from "./use-row-operations";
34
38
  export type { DoorbellPressEvent, UseDoorbellEventsOptions, UseDoorbellEventsResult } from "./use-doorbell-events";
35
39
  export { useDoorbellEvents } from "./use-doorbell-events";
36
40
  export { useEventInvalidation } from "./use-event-invalidation";
@@ -0,0 +1,26 @@
1
+ import { type RowOperationState } from "./use-row-operations";
2
+ export interface UseDeviceRemovalOptions {
3
+ /** Must resolve only once the removal has completed on the hub. */
4
+ readonly remove: (input: {
5
+ readonly deviceId: number;
6
+ }) => Promise<unknown>;
7
+ /** Awaited after `remove`: refetch the devices list the table renders. */
8
+ readonly refresh?: () => Promise<unknown>;
9
+ /** A removal failed; the row is restored. Surface `message` (a toast). */
10
+ readonly onError?: (deviceId: number, message: string) => void;
11
+ }
12
+ export interface DeviceRemoval {
13
+ /** Fire-and-track; has the shape of `DeviceListBatchActions.remove`. */
14
+ readonly remove: (input: {
15
+ readonly deviceId: number;
16
+ }) => void;
17
+ /** Awaitable form: `true` when the device was removed. */
18
+ readonly removeAsync: (input: {
19
+ readonly deviceId: number;
20
+ }) => Promise<boolean>;
21
+ /** The row's state for `<DeviceList rowOperation>`; undefined = idle. */
22
+ readonly rowOperation: (device: {
23
+ readonly id: number;
24
+ }) => RowOperationState | undefined;
25
+ }
26
+ export declare function useDeviceRemoval(options: UseDeviceRemovalOptions): DeviceRemoval;
@@ -0,0 +1,22 @@
1
+ export type RowOperationState = {
2
+ readonly kind: "pending";
3
+ } | {
4
+ readonly kind: "failed";
5
+ readonly message: string;
6
+ };
7
+ export interface UseRowOperationsResult<K> {
8
+ /** Every row with a state — absent keys are idle. */
9
+ readonly states: ReadonlyMap<K, RowOperationState>;
10
+ readonly stateOf: (key: K) => RowOperationState | undefined;
11
+ readonly isPending: (key: K) => boolean;
12
+ /**
13
+ * Run `task` for `key`. Resolves `true` when it completed, `false` when it
14
+ * threw or when `key` was already pending (nothing was started). A failure
15
+ * is reported to `onError` and kept as the key's `failed` state.
16
+ */
17
+ readonly run: (key: K, task: () => Promise<unknown>, onError?: (message: string) => void) => Promise<boolean>;
18
+ /** Forget a key's failure (the row goes back to idle). */
19
+ readonly clearFailure: (key: K) => void;
20
+ }
21
+ export declare function errorMessageOf(error: unknown): string;
22
+ export declare function useRowOperations<K>(): UseRowOperationsResult<K>;