@camstack/ui-library 1.2.205 → 1.2.207

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
@@ -4100,6 +4100,16 @@ var Projector = createLucideIcon("projector", [
4100
4100
  * This source code is licensed under the ISC license.
4101
4101
  * See the LICENSE file in the root directory of this source tree.
4102
4102
  */
4103
+ var Puzzle = createLucideIcon("puzzle", [["path", {
4104
+ d: "M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z",
4105
+ key: "w46dr5"
4106
+ }]]);
4107
+ /**
4108
+ * @license lucide-react v0.576.0 - ISC
4109
+ *
4110
+ * This source code is licensed under the ISC license.
4111
+ * See the LICENSE file in the root directory of this source tree.
4112
+ */
4103
4113
  var QrCode$1 = createLucideIcon("qr-code", [
4104
4114
  ["rect", {
4105
4115
  width: "5",
@@ -6932,6 +6942,14 @@ var useDeviceExportGetDeviceSettingsContribution = trpc.deviceExport.getDeviceSe
6932
6942
  var useDeviceExportGetDeviceLiveContribution = trpc.deviceExport.getDeviceLiveContribution.useQuery;
6933
6943
  /** Generated alias around `trpc.deviceExport.applyDeviceSettingsPatch.useMutation`. */
6934
6944
  var useDeviceExportApplyDeviceSettingsPatch = trpc.deviceExport.applyDeviceSettingsPatch.useMutation;
6945
+ /** Generated alias around `trpc.deviceExtension.listSources.useQuery`. */
6946
+ var useDeviceExtensionListSources = trpc.deviceExtension.listSources.useQuery;
6947
+ /** Generated alias around `trpc.deviceExtension.listSlots.useQuery`. */
6948
+ var useDeviceExtensionListSlots = trpc.deviceExtension.listSlots.useQuery;
6949
+ /** Generated alias around `trpc.deviceExtension.listCandidates.useQuery`. */
6950
+ var useDeviceExtensionListCandidates = trpc.deviceExtension.listCandidates.useQuery;
6951
+ /** Generated alias around `trpc.deviceExtension.setSlot.useMutation`. */
6952
+ var useDeviceExtensionSetSlot = trpc.deviceExtension.setSlot.useMutation;
6935
6953
  /** Generated alias around `trpc.deviceManager.allocateDeviceId.useMutation`. */
6936
6954
  var useDeviceManagerAllocateDeviceId = trpc.deviceManager.allocateDeviceId.useMutation;
6937
6955
  /** Generated alias around `trpc.deviceManager.migrateDevice.useMutation`. */
@@ -7584,6 +7602,8 @@ var useNotificationRulesListDeviceMutes = trpc.notificationRules.listDeviceMutes
7584
7602
  var useNotificationRulesSetDeviceMuted = trpc.notificationRules.setDeviceMuted.useMutation;
7585
7603
  /** Generated alias around `trpc.notificationRules.testRule.useMutation`. */
7586
7604
  var useNotificationRulesTestRule = trpc.notificationRules.testRule.useMutation;
7605
+ /** Generated alias around `trpc.notificationRules.previewStateSummary.useMutation`. */
7606
+ var useNotificationRulesPreviewStateSummary = trpc.notificationRules.previewStateSummary.useMutation;
7587
7607
  /** Generated alias around `trpc.notificationRules.getConditionCatalog.useQuery`. */
7588
7608
  var useNotificationRulesGetConditionCatalog = trpc.notificationRules.getConditionCatalog.useQuery;
7589
7609
  /** Generated alias around `trpc.notificationRules.getTemplateCatalog.useQuery`. */
@@ -7762,8 +7782,6 @@ var usePipelineAnalyticsDeselectRetrainFrame = trpc.pipelineAnalytics.deselectRe
7762
7782
  var usePipelineAnalyticsGetRetrainFrameImage = trpc.pipelineAnalytics.getRetrainFrameImage.useQuery;
7763
7783
  /** Generated alias around `trpc.pipelineAnalytics.proposeRetrainAnnotations.useMutation`. */
7764
7784
  var usePipelineAnalyticsProposeRetrainAnnotations = trpc.pipelineAnalytics.proposeRetrainAnnotations.useMutation;
7765
- /** Generated alias around `trpc.pipelineAnalytics.runReplayFrameProcessor.useMutation`. */
7766
- var usePipelineAnalyticsRunReplayFrameProcessor = trpc.pipelineAnalytics.runReplayFrameProcessor.useMutation;
7767
7785
  /** Generated alias around `trpc.pipelineAnalytics.listRetrainAnnotations.useQuery`. */
7768
7786
  var usePipelineAnalyticsListRetrainAnnotations = trpc.pipelineAnalytics.listRetrainAnnotations.useQuery;
7769
7787
  /** Generated alias around `trpc.pipelineAnalytics.saveRetrainAnnotations.useMutation`. */
@@ -7894,6 +7912,8 @@ var usePipelineOrchestratorGetPipelineAssignments = trpc.pipelineOrchestrator.ge
7894
7912
  var usePipelineOrchestratorGetPipelineAssignment = trpc.pipelineOrchestrator.getPipelineAssignment.useQuery;
7895
7913
  /** Generated alias around `trpc.pipelineOrchestrator.getAgentLoad.useQuery`. */
7896
7914
  var usePipelineOrchestratorGetAgentLoad = trpc.pipelineOrchestrator.getAgentLoad.useQuery;
7915
+ /** Generated alias around `trpc.pipelineOrchestrator.getClusterModelChoice.useQuery`. */
7916
+ var usePipelineOrchestratorGetClusterModelChoice = trpc.pipelineOrchestrator.getClusterModelChoice.useQuery;
7897
7917
  /** Generated alias around `trpc.pipelineOrchestrator.getGlobalMetrics.useQuery`. */
7898
7918
  var usePipelineOrchestratorGetGlobalMetrics = trpc.pipelineOrchestrator.getGlobalMetrics.useQuery;
7899
7919
  /** Generated alias around `trpc.pipelineOrchestrator.getCameraMetrics.useQuery`. */
@@ -20896,6 +20916,428 @@ function ClipsSection({ deviceId }) {
20896
20916
  });
20897
20917
  }
20898
20918
  //#endregion
20919
+ //#region src/composites/extensions/extension-invalidation.ts
20920
+ /** tRPC v11 query-key prefix of `deviceManager.getDeviceAggregate`. */
20921
+ var DEVICE_AGGREGATE_QUERY_KEY = [["deviceManager", "getDeviceAggregate"]];
20922
+ /** tRPC v11 query-key prefix of `deviceExtension.listSlots`. */
20923
+ var EXTENSION_SLOTS_QUERY_KEY = [["deviceExtension", "listSlots"]];
20924
+ /** Re-read both surfaces over the linked-devices / doorbell stores. */
20925
+ async function invalidateExtensionSurfaces(client) {
20926
+ await Promise.all([client.invalidateQueries({ queryKey: DEVICE_AGGREGATE_QUERY_KEY }), client.invalidateQueries({ queryKey: EXTENSION_SLOTS_QUERY_KEY })]);
20927
+ }
20928
+ //#endregion
20929
+ //#region src/composites/extensions/extensions-view-state.ts
20930
+ /**
20931
+ * What the Extensions section may CLAIM, from what `listSlots` answered
20932
+ * (D648, D315).
20933
+ *
20934
+ * Four states, and the two a boolean would collapse are kept apart on purpose:
20935
+ *
20936
+ * - `pending` — nobody has answered yet. Never "no extensions".
20937
+ * - `unknown` — the query FAILED. It is not an answer, so it is not absence
20938
+ * either; the reason is shown verbatim.
20939
+ * - `none` — the query answered with nothing. The only state that may say
20940
+ * "no extensions".
20941
+ * - `rows` — cards, in the generated table's order, and every owner that
20942
+ * could not answer as its own row with its reason (D554.4). A device whose
20943
+ * ONLY owner failed is `rows` with no slots, not `none`.
20944
+ *
20945
+ * Also here, pure: the editor's draft rules, so the component holds no logic
20946
+ * a spec cannot reach.
20947
+ */
20948
+ function tableOrder(slot) {
20949
+ return _camstack_types.EXTENSION_SLOT_IDS.indexOf(slot);
20950
+ }
20951
+ function resolveExtensionsView(query) {
20952
+ if (query.status === "pending") return { kind: "pending" };
20953
+ if (query.status === "error") return {
20954
+ kind: "unknown",
20955
+ reason: query.error?.message ?? "the request failed"
20956
+ };
20957
+ const rows = query.data ?? [];
20958
+ if (rows.length === 0) return { kind: "none" };
20959
+ const slots = [];
20960
+ const unreachable = [];
20961
+ for (const row of rows) if (row.state === "ok") slots.push(row);
20962
+ else unreachable.push(row);
20963
+ return {
20964
+ kind: "rows",
20965
+ slots: slots.toSorted((a, b) => tableOrder(a.slot) - tableOrder(b.slot)),
20966
+ unreachable
20967
+ };
20968
+ }
20969
+ /** Add or remove one pick. `one` replaces; `many` toggles. */
20970
+ function togglePick(draft, deviceId, cardinality) {
20971
+ if (draft.includes(deviceId)) return draft.filter((id) => id !== deviceId);
20972
+ return cardinality === "one" ? [deviceId] : [...draft, deviceId];
20973
+ }
20974
+ /** Suggested candidates the operator has NOT picked — shown to confirm, never saved on their own (D356). */
20975
+ function pendingSuggestions(candidates, draft) {
20976
+ return candidates.filter((c) => c.suggested && !draft.includes(c.deviceId));
20977
+ }
20978
+ /** Candidates matching a free-text search, not already picked. */
20979
+ function searchCandidates(candidates, draft, query) {
20980
+ const needle = query.trim().toLowerCase();
20981
+ if (needle.length === 0) return [];
20982
+ return candidates.filter((c) => !draft.includes(c.deviceId) && [
20983
+ c.name,
20984
+ c.location ?? "",
20985
+ c.parentName ?? "",
20986
+ c.type
20987
+ ].some((v) => v.toLowerCase().includes(needle)));
20988
+ }
20989
+ /** Whether the draft differs from what the owner's store holds. */
20990
+ function isDirty(saved, draft) {
20991
+ return saved.length !== draft.length || saved.some((id, i) => draft[i] !== id);
20992
+ }
20993
+ //#endregion
20994
+ //#region src/composites/extensions/AssociationSlotCard.tsx
20995
+ /**
20996
+ * One ASSOCIATION slot (D648): the devices a host lists, edited as a device
20997
+ * multiselect.
20998
+ *
20999
+ * - The saved value is the owner's store, read by `listSlots`. The draft is
21000
+ * local until Save, and Save is `setSlot` — the owner validates and writes
21001
+ * through its existing path, so a refusal comes back BY NAME and is shown.
21002
+ * - Candidates (`listCandidates`) are fetched only while editing. Suggestions
21003
+ * are chips the operator must click to add; nothing is picked on its own
21004
+ * (D356).
21005
+ * - Every saved source shows its health; a missing or unavailable one is
21006
+ * shown with its reason and never silently dropped from the draft.
21007
+ * - A gate owned by ANOTHER authority (the doorbell wrapper's binding) is
21008
+ * shown read-only, naming that authority (D62).
21009
+ */
21010
+ var HEALTH_LABEL = {
21011
+ ok: "OK",
21012
+ missing: "Missing",
21013
+ foreign: "Other owner",
21014
+ unavailable: "Not a valid source"
21015
+ };
21016
+ var HEALTH_VARIANT = {
21017
+ ok: "success",
21018
+ missing: "danger",
21019
+ foreign: "warning",
21020
+ unavailable: "warning"
21021
+ };
21022
+ /** The saved sources, each with its health — a bad one is shown, never hidden. */
21023
+ function SlotSourceList({ sources }) {
21024
+ if (sources.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
21025
+ className: "text-[11px] text-foreground-subtle italic",
21026
+ children: "No devices picked."
21027
+ });
21028
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
21029
+ className: "flex flex-col gap-1",
21030
+ children: sources.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
21031
+ className: "flex flex-wrap items-center gap-2 text-[11px]",
21032
+ children: [
21033
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
21034
+ className: "text-foreground",
21035
+ children: s.name ?? `Device ${s.deviceId}`
21036
+ }),
21037
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
21038
+ variant: HEALTH_VARIANT[s.health],
21039
+ children: HEALTH_LABEL[s.health]
21040
+ }),
21041
+ s.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
21042
+ className: "text-[10px] text-foreground-subtle",
21043
+ children: s.detail
21044
+ })
21045
+ ]
21046
+ }, s.deviceId))
21047
+ });
21048
+ }
21049
+ function ActivationLine({ row }) {
21050
+ const activation = row.activation;
21051
+ if (activation === null) return null;
21052
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21053
+ className: "flex flex-wrap items-center gap-2 text-[10px] text-foreground-subtle",
21054
+ children: [
21055
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
21056
+ variant: activation.state === "active" ? "success" : activation.state === "inactive" ? "warning" : "default",
21057
+ children: activation.state === "active" ? "Active" : activation.state === "inactive" ? "Inactive" : "Unknown"
21058
+ }),
21059
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["set by ", activation.authority] }),
21060
+ activation.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: activation.detail })
21061
+ ]
21062
+ });
21063
+ }
21064
+ function AssociationSlotCard({ deviceId, row, onSaved }) {
21065
+ const descriptor = _camstack_types.EXTENSION_SLOTS[row.slot].descriptor;
21066
+ const saved = row.value.deviceIds;
21067
+ const [editing, setEditing] = (0, react$1.useState)(false);
21068
+ const [draft, setDraft] = (0, react$1.useState)(saved);
21069
+ const [search, setSearch] = (0, react$1.useState)("");
21070
+ const [refusal, setRefusal] = (0, react$1.useState)(null);
21071
+ const candidatesQuery = useDeviceExtensionListCandidates({
21072
+ deviceId,
21073
+ slotRef: row.slotRef
21074
+ }, { enabled: editing });
21075
+ const setSlot = useDeviceExtensionSetSlot();
21076
+ const queryClient = (0, _tanstack_react_query.useQueryClient)();
21077
+ const candidates = candidatesQuery.data?.candidates ?? [];
21078
+ const nameOf = (id) => candidates.find((c) => c.deviceId === id)?.name ?? row.sources.find((s) => s.deviceId === id)?.name ?? `Device ${id}`;
21079
+ const startEditing = () => {
21080
+ setDraft(saved);
21081
+ setRefusal(null);
21082
+ setEditing(true);
21083
+ };
21084
+ const save = async () => {
21085
+ setRefusal(null);
21086
+ try {
21087
+ await setSlot.mutateAsync({
21088
+ deviceId,
21089
+ slotRef: row.slotRef,
21090
+ value: {
21091
+ slot: row.value.slot,
21092
+ deviceIds: [...draft]
21093
+ }
21094
+ });
21095
+ setEditing(false);
21096
+ await invalidateExtensionSurfaces(queryClient);
21097
+ onSaved();
21098
+ } catch (err) {
21099
+ setRefusal(err instanceof Error ? err.message : String(err));
21100
+ }
21101
+ };
21102
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21103
+ role: "group",
21104
+ "aria-label": descriptor.label,
21105
+ className: "flex flex-col gap-2 rounded-md border border-border/40 bg-surface-elevated/30 p-3",
21106
+ children: [
21107
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21108
+ className: "flex items-start justify-between gap-2",
21109
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21110
+ className: "flex flex-col gap-0.5",
21111
+ children: [
21112
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
21113
+ className: "text-xs font-semibold text-foreground",
21114
+ children: descriptor.label
21115
+ }),
21116
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
21117
+ className: "text-[10px] text-foreground-subtle",
21118
+ children: descriptor.description
21119
+ }),
21120
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
21121
+ className: "text-[10px] text-foreground-subtle",
21122
+ children: ["Stored in ", row.authority]
21123
+ })
21124
+ ]
21125
+ }), !editing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
21126
+ variant: "outline",
21127
+ size: "sm",
21128
+ onClick: startEditing,
21129
+ children: "Edit"
21130
+ })]
21131
+ }),
21132
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivationLine, { row }),
21133
+ !editing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SlotSourceList, { sources: row.sources }),
21134
+ editing && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21135
+ className: "flex flex-col gap-2",
21136
+ children: [
21137
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
21138
+ "aria-label": "Picked devices",
21139
+ className: "flex flex-wrap gap-1.5",
21140
+ children: draft.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
21141
+ type: "button",
21142
+ "aria-label": `Remove ${nameOf(id)}`,
21143
+ onClick: () => setDraft(togglePick(draft, id, descriptor.cardinality)),
21144
+ className: "inline-flex items-center gap-1 rounded-full bg-primary/15 px-2 py-0.5 text-[11px] text-foreground",
21145
+ children: [nameOf(id), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(X, { className: "h-3 w-3" })]
21146
+ }) }, id))
21147
+ }),
21148
+ candidatesQuery.status === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
21149
+ role: "status",
21150
+ className: "text-[10px] text-foreground-subtle",
21151
+ children: "Loading candidates…"
21152
+ }),
21153
+ candidatesQuery.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
21154
+ role: "alert",
21155
+ className: "text-[10px] text-danger",
21156
+ children: ["Candidates could not be read: ", candidatesQuery.error.message]
21157
+ }),
21158
+ pendingSuggestions(candidates, draft).length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21159
+ className: "flex flex-col gap-1",
21160
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
21161
+ className: "text-[10px] text-foreground-subtle",
21162
+ children: "Suggested — added only if you click them:"
21163
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
21164
+ "aria-label": "Suggestions",
21165
+ className: "flex flex-wrap gap-1.5",
21166
+ children: pendingSuggestions(candidates, draft).map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
21167
+ type: "button",
21168
+ "aria-label": `Add ${c.name}`,
21169
+ onClick: () => setDraft(togglePick(draft, c.deviceId, descriptor.cardinality)),
21170
+ className: "inline-flex items-center gap-1 rounded-full border border-dashed border-border px-2 py-0.5 text-[11px] text-foreground-muted",
21171
+ children: [
21172
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, { className: "h-3 w-3" }),
21173
+ c.name,
21174
+ c.reason !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
21175
+ className: "text-foreground-subtle",
21176
+ children: ["· ", c.reason]
21177
+ })
21178
+ ]
21179
+ }) }, c.deviceId))
21180
+ })]
21181
+ }),
21182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
21183
+ type: "search",
21184
+ "aria-label": "Search devices",
21185
+ value: search,
21186
+ onChange: (e) => setSearch(e.target.value),
21187
+ placeholder: "Search devices by name, location or type",
21188
+ className: "w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs text-foreground outline-none focus:border-primary"
21189
+ }),
21190
+ searchCandidates(candidates, draft, search).length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
21191
+ "aria-label": "Search results",
21192
+ className: "flex max-h-40 flex-col gap-0.5 overflow-y-auto",
21193
+ children: searchCandidates(candidates, draft, search).map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
21194
+ type: "button",
21195
+ onClick: () => {
21196
+ setDraft(togglePick(draft, c.deviceId, descriptor.cardinality));
21197
+ setSearch("");
21198
+ },
21199
+ className: "w-full rounded px-2 py-1 text-left text-[11px] text-foreground hover:bg-surface-hover",
21200
+ children: [
21201
+ c.name,
21202
+ c.parentName !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
21203
+ className: "text-foreground-subtle",
21204
+ children: [" in ", c.parentName]
21205
+ }),
21206
+ c.location !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
21207
+ className: "text-foreground-subtle",
21208
+ children: [" · ", c.location]
21209
+ })
21210
+ ]
21211
+ }) }, c.deviceId))
21212
+ }),
21213
+ refusal !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
21214
+ role: "alert",
21215
+ className: "text-[11px] text-danger break-words",
21216
+ children: ["Not saved: ", refusal]
21217
+ }),
21218
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21219
+ className: "flex justify-end gap-2",
21220
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
21221
+ variant: "ghost",
21222
+ size: "sm",
21223
+ onClick: () => setEditing(false),
21224
+ children: "Cancel"
21225
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
21226
+ size: "sm",
21227
+ disabled: !isDirty(saved, draft) || setSlot.isPending,
21228
+ onClick: () => void save(),
21229
+ children: "Save"
21230
+ })]
21231
+ })
21232
+ ]
21233
+ })
21234
+ ]
21235
+ });
21236
+ }
21237
+ //#endregion
21238
+ //#region src/composites/extensions/ExtensionsPanel.tsx
21239
+ /**
21240
+ * `host/extensions-panel` — what a device is extended with by OTHER devices,
21241
+ * in one place (D648).
21242
+ *
21243
+ * Mounted by the framework: `device-extension.cap.ts` declares
21244
+ * `deviceConfig.ui = { kind:'widget', widgetId:'host/extensions-panel',
21245
+ * tab:'general', order:72 }` and `deriveDeviceConfigContribution` emits the
21246
+ * section (D14). The panel STORES NOTHING: every card is one row of
21247
+ * `device-extension.listSlots`, read by the slot's owner from the store it
21248
+ * already has, and every save is `setSlot`, written by that owner through its
21249
+ * existing path. The hand-written "Linked devices" and "Virtual doorbell"
21250
+ * sections stay beside it until the live measurement says the two surfaces
21251
+ * agree for every camera (slice 1 of the design); both write the same store.
21252
+ *
21253
+ * Slice 1 renders ASSOCIATION cards only (a device multiselect). A `graft`
21254
+ * slot (slice 2) gets a read-only card here until its editor exists.
21255
+ *
21256
+ * Absence is claimed only from an answer (D315): pending, failed and empty
21257
+ * are three different sentences, and an owner that could not answer is shown
21258
+ * with its reason, verbatim — never dropped.
21259
+ */
21260
+ function ExtensionsPanel({ deviceId }) {
21261
+ const query = useDeviceExtensionListSlots({ deviceId });
21262
+ const view = resolveExtensionsView({
21263
+ status: query.status,
21264
+ data: query.data,
21265
+ error: query.error
21266
+ });
21267
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetPanel, {
21268
+ title: "Extensions",
21269
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Puzzle, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
21270
+ bodyClassName: "p-3",
21271
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
21272
+ "aria-label": "Extensions",
21273
+ className: "flex flex-col gap-3",
21274
+ children: [
21275
+ view.kind === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
21276
+ role: "status",
21277
+ className: "text-[11px] text-foreground-subtle",
21278
+ children: "Loading this device’s extensions…"
21279
+ }),
21280
+ view.kind === "unknown" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBox, {
21281
+ title: "Extensions could not be read",
21282
+ message: `Nothing is known about this device’s extensions yet: ${view.reason}`,
21283
+ onRetry: () => void query.refetch()
21284
+ }),
21285
+ view.kind === "none" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
21286
+ role: "status",
21287
+ className: "text-[11px] text-foreground-subtle italic",
21288
+ children: "No extensions are offered on this device."
21289
+ }),
21290
+ view.kind === "rows" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [view.slots.map((row) => _camstack_types.EXTENSION_SLOTS[row.slot].descriptor.shape === "association" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AssociationSlotCard, {
21291
+ deviceId,
21292
+ row,
21293
+ onSaved: () => void query.refetch()
21294
+ }, row.slotRef) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21295
+ className: "rounded-md border border-border/40 p-3",
21296
+ children: [
21297
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
21298
+ className: "text-xs font-semibold text-foreground",
21299
+ children: _camstack_types.EXTENSION_SLOTS[row.slot].descriptor.label
21300
+ }),
21301
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
21302
+ className: "text-[10px] text-foreground-subtle",
21303
+ children: [
21304
+ row.enabled === false ? "Off" : "On",
21305
+ " — ",
21306
+ row.authority,
21307
+ ". Editing it needs a newer admin."
21308
+ ]
21309
+ }),
21310
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SlotSourceList, { sources: row.sources })
21311
+ ]
21312
+ }, row.slotRef)), view.unreachable.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21313
+ role: "alert",
21314
+ className: "flex flex-col gap-1 rounded-md border border-warning/40 bg-warning/5 p-3",
21315
+ children: [
21316
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21317
+ className: "flex items-center gap-2",
21318
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
21319
+ variant: "warning",
21320
+ children: "Unreachable"
21321
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
21322
+ className: "text-xs font-semibold text-foreground",
21323
+ children: row.addonId
21324
+ })]
21325
+ }),
21326
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
21327
+ className: "text-[11px] text-foreground-muted break-words",
21328
+ children: row.reason
21329
+ }),
21330
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
21331
+ className: "text-[10px] text-foreground-subtle",
21332
+ children: "The extensions this component owns are unknown until it answers."
21333
+ })
21334
+ ]
21335
+ }, `unreachable:${row.addonId}`))] })
21336
+ ]
21337
+ })
21338
+ });
21339
+ }
21340
+ //#endregion
20899
21341
  //#region src/hooks/use-navigation.ts
20900
21342
  /**
20901
21343
  * useNavigation — drive a `navigation` robot camera from the UI.
@@ -23831,7 +24273,8 @@ var HOST_WIDGETS = {
23831
24273
  "host/ptz-autotrack": AutotrackSection,
23832
24274
  "host/recording-panel": RecordingPanel,
23833
24275
  "host/camera-grid-layout": CameraGridLayoutEditor,
23834
- "host/clips-browser": ClipsSection
24276
+ "host/clips-browser": ClipsSection,
24277
+ "host/extensions-panel": ExtensionsPanel
23835
24278
  };
23836
24279
  //#endregion
23837
24280
  //#region src/composites/widget-slot.tsx
@@ -52560,7 +53003,9 @@ function InferenceConfigSelector({ runtime, backend, modelId, agentId = "hub", r
52560
53003
  children: "Runtime"
52561
53004
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
52562
53005
  value: runtime,
52563
- onChange: (e) => onRuntimeChange(e.target.value),
53006
+ onChange: (e) => {
53007
+ if (e.target.value === "python") onRuntimeChange("python");
53008
+ },
52564
53009
  className: SELECT_CLASS,
52565
53010
  children: runtimes.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
52566
53011
  value: r.value,
@@ -56048,6 +56493,7 @@ exports.CoverInlineControl = CoverInlineControl;
56048
56493
  exports.CoverPanel = CoverPanel;
56049
56494
  exports.CustomFieldRenderersProvider = CustomFieldRenderersProvider;
56050
56495
  exports.DEFAULT_COLOR = DEFAULT_COLOR;
56496
+ exports.DEVICE_AGGREGATE_QUERY_KEY = DEVICE_AGGREGATE_QUERY_KEY;
56051
56497
  exports.DEVICE_COLUMNS = DEVICE_COLUMNS;
56052
56498
  exports.DEVICE_LIST_PAGE_SIZE_KEY = DEVICE_LIST_PAGE_SIZE_KEY;
56053
56499
  exports.DEVICE_LIST_PAGE_SIZE_OPTIONS = DEVICE_LIST_PAGE_SIZE_OPTIONS;
@@ -56091,6 +56537,7 @@ exports.DummyHeroCard = DummyHeroCard;
56091
56537
  exports.DummyInline = DummyInline;
56092
56538
  exports.EMPTY_DATA_TABLE_FILTER = EMPTY_DATA_TABLE_FILTER;
56093
56539
  exports.EVENT_KIND_ICONS = EVENT_KIND_ICONS;
56540
+ exports.EXTENSION_SLOTS_QUERY_KEY = EXTENSION_SLOTS_QUERY_KEY;
56094
56541
  exports.EmptyState = EmptyState;
56095
56542
  exports.ErrorBox = ErrorBox;
56096
56543
  exports.EventKindGlyph = EventKindGlyph;
@@ -56333,6 +56780,7 @@ exports.hubWsUrl = hubWsUrl;
56333
56780
  exports.humanDuration = humanDuration;
56334
56781
  exports.humidifierTint = humidifierTint;
56335
56782
  exports.initialScrubState = initialScrubState;
56783
+ exports.invalidateExtensionSurfaces = invalidateExtensionSurfaces;
56336
56784
  exports.isAbsentProvider = isAbsentProvider;
56337
56785
  exports.isAbsentProvider$1 = isAbsentProvider;
56338
56786
  exports.isDataTableFilterActive = isDataTableFilterActive;
@@ -56620,6 +57068,10 @@ exports.useDeviceExportGetStatus = useDeviceExportGetStatus;
56620
57068
  exports.useDeviceExportListExposedDevices = useDeviceExportListExposedDevices;
56621
57069
  exports.useDeviceExportListSupportedDeviceKinds = useDeviceExportListSupportedDeviceKinds;
56622
57070
  exports.useDeviceExportUnexposeDevice = useDeviceExportUnexposeDevice;
57071
+ exports.useDeviceExtensionListCandidates = useDeviceExtensionListCandidates;
57072
+ exports.useDeviceExtensionListSlots = useDeviceExtensionListSlots;
57073
+ exports.useDeviceExtensionListSources = useDeviceExtensionListSources;
57074
+ exports.useDeviceExtensionSetSlot = useDeviceExtensionSetSlot;
56623
57075
  exports.useDeviceId = useDeviceId;
56624
57076
  exports.useDeviceListPageSize = useDeviceListPageSize;
56625
57077
  exports.useDeviceManagerAddLocation = useDeviceManagerAddLocation;
@@ -56972,6 +57424,7 @@ exports.useNotificationRulesGetTemplateCatalog = useNotificationRulesGetTemplate
56972
57424
  exports.useNotificationRulesListDeviceMutes = useNotificationRulesListDeviceMutes;
56973
57425
  exports.useNotificationRulesListRules = useNotificationRulesListRules;
56974
57426
  exports.useNotificationRulesListSnoozes = useNotificationRulesListSnoozes;
57427
+ exports.useNotificationRulesPreviewStateSummary = useNotificationRulesPreviewStateSummary;
56975
57428
  exports.useNotificationRulesPreviewTemplate = useNotificationRulesPreviewTemplate;
56976
57429
  exports.useNotificationRulesResolveArtifactUrl = useNotificationRulesResolveArtifactUrl;
56977
57430
  exports.useNotificationRulesSetAlarmConfig = useNotificationRulesSetAlarmConfig;
@@ -57071,7 +57524,6 @@ exports.usePipelineAnalyticsRefreshStorageLocationsForMigration = usePipelineAna
57071
57524
  exports.usePipelineAnalyticsRelocateMedia = usePipelineAnalyticsRelocateMedia;
57072
57525
  exports.usePipelineAnalyticsRestageRetrainTrack = usePipelineAnalyticsRestageRetrainTrack;
57073
57526
  exports.usePipelineAnalyticsResumeForStorageMigration = usePipelineAnalyticsResumeForStorageMigration;
57074
- exports.usePipelineAnalyticsRunReplayFrameProcessor = usePipelineAnalyticsRunReplayFrameProcessor;
57075
57527
  exports.usePipelineAnalyticsSaveRetrainAnnotations = usePipelineAnalyticsSaveRetrainAnnotations;
57076
57528
  exports.usePipelineAnalyticsSearchObjectEvents = usePipelineAnalyticsSearchObjectEvents;
57077
57529
  exports.usePipelineAnalyticsSelectRetrainFrames = usePipelineAnalyticsSelectRetrainFrames;
@@ -57133,6 +57585,7 @@ exports.usePipelineOrchestratorGetCameraStatuses = usePipelineOrchestratorGetCam
57133
57585
  exports.usePipelineOrchestratorGetCameraStepOverrides = usePipelineOrchestratorGetCameraStepOverrides;
57134
57586
  exports.usePipelineOrchestratorGetCameraSwitches = usePipelineOrchestratorGetCameraSwitches;
57135
57587
  exports.usePipelineOrchestratorGetCapabilityBindings = usePipelineOrchestratorGetCapabilityBindings;
57588
+ exports.usePipelineOrchestratorGetClusterModelChoice = usePipelineOrchestratorGetClusterModelChoice;
57136
57589
  exports.usePipelineOrchestratorGetDeviceLiveContribution = usePipelineOrchestratorGetDeviceLiveContribution;
57137
57590
  exports.usePipelineOrchestratorGetDeviceSettingsContribution = usePipelineOrchestratorGetDeviceSettingsContribution;
57138
57591
  exports.usePipelineOrchestratorGetGlobalMetrics = usePipelineOrchestratorGetGlobalMetrics;