@camstack/ui-library 1.2.218 → 1.2.219

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.
@@ -40,6 +40,10 @@ export interface EventStreamProps {
40
40
  readonly onClose?: () => void;
41
41
  readonly className?: string;
42
42
  }
43
+ /** Categories visible in the events tab by default. The "All" badge
44
+ * reflects this set, not the raw enum. */
45
+ export declare const DEFAULT_EVENT_CATEGORIES: readonly string[];
46
+ export declare function summarizeEvent(category: string, data: Record<string, unknown>): string | null;
43
47
  export declare function EventStream({ agentId, addonId, deviceId, defaultCategories, categories: legacyCategories, category: propsCategory, maxHeight, limit, showCategoryFilter, showFilters: _showFilters, liveBuffer: externalBuffer, onClose, className, }: EventStreamProps): import("react").JSX.Element;
44
48
  export interface FloatingEventStreamProps extends EventStreamProps {
45
49
  readonly position?: 'bottom' | 'overlay';
@@ -22,14 +22,31 @@ export interface ExtensionsUnknownView {
22
22
  export interface ExtensionsNoneView {
23
23
  readonly kind: 'none';
24
24
  }
25
- /** Cards in table order, and every owner that could not answer. */
25
+ /**
26
+ * A row of a slot this build does NOT declare — an older or newer server than
27
+ * this admin UI (a rollback after slice 6 still answers `doorbell-source`).
28
+ * It cannot be a card (no descriptor), so it is said by name instead: never
29
+ * dropped, never crashing the panel (D315, task 17 re-review R1).
30
+ */
31
+ export interface ExtensionsUnknownSlotRow {
32
+ readonly slot: string;
33
+ readonly slotRef: string;
34
+ }
35
+ /** Cards in table order, every slot this build does not know, and every owner that could not answer. */
26
36
  export interface ExtensionsRowsView {
27
37
  readonly kind: 'rows';
28
38
  readonly slots: readonly ExtensionSlotState[];
39
+ readonly unknownSlots: readonly ExtensionsUnknownSlotRow[];
29
40
  readonly unreachable: readonly ExtensionSlotUnreachable[];
30
41
  }
31
42
  export type ExtensionsView = ExtensionsPendingView | ExtensionsUnknownView | ExtensionsNoneView | ExtensionsRowsView;
32
- export declare function resolveExtensionsView(query: ExtensionsQueryView): ExtensionsView;
43
+ /** The order cards are shown in: the generated slot table's. */
44
+ export type ExtensionSlotTableOrder = readonly string[];
45
+ /**
46
+ * `order` is the generated table (`EXTENSION_SLOT_IDS`); a parameter only so a
47
+ * spec can pin the comparator while the table holds a single slot.
48
+ */
49
+ export declare function resolveExtensionsView(query: ExtensionsQueryView, order?: ExtensionSlotTableOrder): ExtensionsView;
33
50
  /** Add or remove one pick. `one` replaces; `many` toggles. */
34
51
  export declare function togglePick(draft: readonly number[], deviceId: number, cardinality: ExtensionSlotCardinality): readonly number[];
35
52
  /** Suggested candidates the operator has NOT picked — shown to confirm, never saved on their own (D356). */
package/dist/index.cjs CHANGED
@@ -20975,10 +20975,21 @@ function ClipsSection({ deviceId }) {
20975
20975
  * Also here, pure: the editor's draft rules, so the component holds no logic
20976
20976
  * a spec cannot reach.
20977
20977
  */
20978
- function tableOrder(slot) {
20979
- return _camstack_types.EXTENSION_SLOT_IDS.indexOf(slot);
20978
+ /**
20979
+ * Whether the WIRE row's slot is one this build declares. The tRPC client does
20980
+ * not validate output, so a row's `slot` can be any string at runtime even
20981
+ * though its type says otherwise; the comparison runs on the string.
20982
+ */
20983
+ function isDeclaredSlotRow(row, order) {
20984
+ const slot = row.slot;
20985
+ return order.some((id) => id === slot);
20980
20986
  }
20981
- function resolveExtensionsView(query) {
20987
+ /**
20988
+ * `order` is the generated table (`EXTENSION_SLOT_IDS`); a parameter only so a
20989
+ * spec can pin the comparator while the table holds a single slot.
20990
+ */
20991
+ function resolveExtensionsView(query, order = _camstack_types.EXTENSION_SLOT_IDS) {
20992
+ const tableOrder = (slot) => order.indexOf(slot);
20982
20993
  if (query.status === "pending") return { kind: "pending" };
20983
20994
  if (query.status === "error") return {
20984
20995
  kind: "unknown",
@@ -20987,12 +20998,18 @@ function resolveExtensionsView(query) {
20987
20998
  const rows = query.data ?? [];
20988
20999
  if (rows.length === 0) return { kind: "none" };
20989
21000
  const slots = [];
21001
+ const unknownSlots = [];
20990
21002
  const unreachable = [];
20991
- for (const row of rows) if (row.state === "ok") slots.push(row);
20992
- else unreachable.push(row);
21003
+ for (const row of rows) if (row.state !== "ok") unreachable.push(row);
21004
+ else if (isDeclaredSlotRow(row, order)) slots.push(row);
21005
+ else unknownSlots.push({
21006
+ slot: row.slot,
21007
+ slotRef: row.slotRef
21008
+ });
20993
21009
  return {
20994
21010
  kind: "rows",
20995
21011
  slots: slots.toSorted((a, b) => tableOrder(a.slot) - tableOrder(b.slot)),
21012
+ unknownSlots,
20996
21013
  unreachable
20997
21014
  };
20998
21015
  }
@@ -21098,8 +21115,8 @@ function slotWriteValue(saved, deviceIds, tracked) {
21098
21115
  * (D356).
21099
21116
  * - Every saved source shows its health; a missing or unavailable one is
21100
21117
  * shown with its reason and never silently dropped from the draft.
21101
- * - A gate owned by ANOTHER authority (the doorbell wrapper's binding) is
21102
- * shown read-only, naming that authority (D62).
21118
+ * - A gate owned by ANOTHER authority (e.g. a wrapper's binding; no slot
21119
+ * reports one today) is shown read-only, naming that authority (D62).
21103
21120
  * - A slot whose value carries a tracked-event opt-in (`linked-devices`,
21104
21121
  * D655) gets one toggle per LINKED device — the picks, what the owner links
21105
21122
  * by structure (`impliedSources`: tree children, never a container, D356)
@@ -21428,9 +21445,11 @@ function ExtensionsPanel({ deviceId, slots, title, readOnly = false }) {
21428
21445
  data: query.data,
21429
21446
  error: query.error
21430
21447
  });
21431
- const shown = view.kind !== "rows" ? [] : slots === void 0 ? view.slots : view.slots.filter((row) => slots.includes(row.slot));
21448
+ const named = (slot) => slots === void 0 || slots.some((id) => id === slot);
21449
+ const shown = view.kind !== "rows" ? [] : view.slots.filter((row) => named(row.slot));
21450
+ const unknownShown = view.kind !== "rows" ? [] : view.unknownSlots.filter((row) => named(row.slot));
21432
21451
  const panelTitle = title ?? "Extensions";
21433
- const filteredToNothing = view.kind === "rows" && slots !== void 0 && shown.length === 0 && view.unreachable.length === 0;
21452
+ const filteredToNothing = view.kind === "rows" && slots !== void 0 && shown.length === 0 && unknownShown.length === 0 && view.unreachable.length === 0;
21434
21453
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetPanel, {
21435
21454
  title: panelTitle,
21436
21455
  icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Puzzle, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
@@ -21466,6 +21485,30 @@ function ExtensionsPanel({ deviceId, slots, title, readOnly = false }) {
21466
21485
  onSaved: () => void query.refetch(),
21467
21486
  readOnly
21468
21487
  }, row.slotRef)),
21488
+ unknownShown.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21489
+ role: "alert",
21490
+ className: "flex flex-col gap-1 rounded-md border border-warning/40 bg-warning/5 p-3",
21491
+ children: [
21492
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21493
+ className: "flex items-center gap-2",
21494
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
21495
+ variant: "warning",
21496
+ children: "Unknown slot"
21497
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
21498
+ className: "text-xs font-semibold text-foreground",
21499
+ children: row.slot
21500
+ })]
21501
+ }),
21502
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
21503
+ className: "text-[11px] text-foreground-muted break-words",
21504
+ children: row.slotRef
21505
+ }),
21506
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
21507
+ className: "text-[10px] text-foreground-subtle",
21508
+ children: "The admin UI and the server disagree: the server offers a slot this admin UI does not know. Update the admin UI, or the server."
21509
+ })
21510
+ ]
21511
+ }, `unknown:${row.slotRef}`)),
21469
21512
  view.unreachable.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21470
21513
  role: "alert",
21471
21514
  className: "flex flex-col gap-1 rounded-md border border-warning/40 bg-warning/5 p-3",
@@ -36026,7 +36069,8 @@ var LIVE_NOISE_CATEGORIES = Object.freeze([
36026
36069
  _camstack_types.EventCategory.PipelineAudioInferenceResult,
36027
36070
  _camstack_types.EventCategory.PlatformProbePhase,
36028
36071
  _camstack_types.EventCategory.BenchmarkProgress,
36029
- _camstack_types.EventCategory.ModelDownloadProgress
36072
+ _camstack_types.EventCategory.ModelDownloadProgress,
36073
+ _camstack_types.EventCategory.ProviderMotion
36030
36074
  ]);
36031
36075
  var LIVE_NOISE_SET = new Set(LIVE_NOISE_CATEGORIES);
36032
36076
  /** Categories visible in the events tab by default. The "All" badge
@@ -36074,7 +36118,7 @@ function summarizeEvent(category, data) {
36074
36118
  if (typeof score === "number") parts.push(`${Math.round(score * 100)}%`);
36075
36119
  return parts.length > 0 ? parts.join(" — ") : null;
36076
36120
  }
36077
- if (category === _camstack_types.EventCategory.ProviderMotion) return data["active"] === true ? "motion start" : "motion stop";
36121
+ if (category === _camstack_types.EventCategory.ProviderMotion) return data["detected"] === true ? "motion start" : "motion stop";
36078
36122
  if (category === _camstack_types.EventCategory.DeviceStateChanged) {
36079
36123
  const slice = data["slice"];
36080
36124
  if (slice && typeof slice === "object") {
@@ -36879,7 +36923,7 @@ function runtimeStateKeys(capName) {
36879
36923
  }
36880
36924
  /**
36881
36925
  * Cold placeholder slice for a bound cap that declares `runtimeState` but
36882
- * has never written it (e.g. a virtual-doorbell camera that has never rung).
36926
+ * has never written it (e.g. a doorbell that has never rung).
36883
36927
  * Every declared field is seeded `null` so the tree surfaces the cap's
36884
36928
  * presence and reads as "never" instead of the cap being OMITTED entirely.
36885
36929
  * Returns `null` when the cap has no runtime-state schema (nothing to seed).
package/dist/index.js CHANGED
@@ -20951,10 +20951,21 @@ function ClipsSection({ deviceId }) {
20951
20951
  * Also here, pure: the editor's draft rules, so the component holds no logic
20952
20952
  * a spec cannot reach.
20953
20953
  */
20954
- function tableOrder(slot) {
20955
- return EXTENSION_SLOT_IDS.indexOf(slot);
20954
+ /**
20955
+ * Whether the WIRE row's slot is one this build declares. The tRPC client does
20956
+ * not validate output, so a row's `slot` can be any string at runtime even
20957
+ * though its type says otherwise; the comparison runs on the string.
20958
+ */
20959
+ function isDeclaredSlotRow(row, order) {
20960
+ const slot = row.slot;
20961
+ return order.some((id) => id === slot);
20956
20962
  }
20957
- function resolveExtensionsView(query) {
20963
+ /**
20964
+ * `order` is the generated table (`EXTENSION_SLOT_IDS`); a parameter only so a
20965
+ * spec can pin the comparator while the table holds a single slot.
20966
+ */
20967
+ function resolveExtensionsView(query, order = EXTENSION_SLOT_IDS) {
20968
+ const tableOrder = (slot) => order.indexOf(slot);
20958
20969
  if (query.status === "pending") return { kind: "pending" };
20959
20970
  if (query.status === "error") return {
20960
20971
  kind: "unknown",
@@ -20963,12 +20974,18 @@ function resolveExtensionsView(query) {
20963
20974
  const rows = query.data ?? [];
20964
20975
  if (rows.length === 0) return { kind: "none" };
20965
20976
  const slots = [];
20977
+ const unknownSlots = [];
20966
20978
  const unreachable = [];
20967
- for (const row of rows) if (row.state === "ok") slots.push(row);
20968
- else unreachable.push(row);
20979
+ for (const row of rows) if (row.state !== "ok") unreachable.push(row);
20980
+ else if (isDeclaredSlotRow(row, order)) slots.push(row);
20981
+ else unknownSlots.push({
20982
+ slot: row.slot,
20983
+ slotRef: row.slotRef
20984
+ });
20969
20985
  return {
20970
20986
  kind: "rows",
20971
20987
  slots: slots.toSorted((a, b) => tableOrder(a.slot) - tableOrder(b.slot)),
20988
+ unknownSlots,
20972
20989
  unreachable
20973
20990
  };
20974
20991
  }
@@ -21074,8 +21091,8 @@ function slotWriteValue(saved, deviceIds, tracked) {
21074
21091
  * (D356).
21075
21092
  * - Every saved source shows its health; a missing or unavailable one is
21076
21093
  * shown with its reason and never silently dropped from the draft.
21077
- * - A gate owned by ANOTHER authority (the doorbell wrapper's binding) is
21078
- * shown read-only, naming that authority (D62).
21094
+ * - A gate owned by ANOTHER authority (e.g. a wrapper's binding; no slot
21095
+ * reports one today) is shown read-only, naming that authority (D62).
21079
21096
  * - A slot whose value carries a tracked-event opt-in (`linked-devices`,
21080
21097
  * D655) gets one toggle per LINKED device — the picks, what the owner links
21081
21098
  * by structure (`impliedSources`: tree children, never a container, D356)
@@ -21404,9 +21421,11 @@ function ExtensionsPanel({ deviceId, slots, title, readOnly = false }) {
21404
21421
  data: query.data,
21405
21422
  error: query.error
21406
21423
  });
21407
- const shown = view.kind !== "rows" ? [] : slots === void 0 ? view.slots : view.slots.filter((row) => slots.includes(row.slot));
21424
+ const named = (slot) => slots === void 0 || slots.some((id) => id === slot);
21425
+ const shown = view.kind !== "rows" ? [] : view.slots.filter((row) => named(row.slot));
21426
+ const unknownShown = view.kind !== "rows" ? [] : view.unknownSlots.filter((row) => named(row.slot));
21408
21427
  const panelTitle = title ?? "Extensions";
21409
- const filteredToNothing = view.kind === "rows" && slots !== void 0 && shown.length === 0 && view.unreachable.length === 0;
21428
+ const filteredToNothing = view.kind === "rows" && slots !== void 0 && shown.length === 0 && unknownShown.length === 0 && view.unreachable.length === 0;
21410
21429
  return /* @__PURE__ */ jsx(WidgetPanel, {
21411
21430
  title: panelTitle,
21412
21431
  icon: /* @__PURE__ */ jsx(Puzzle, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
@@ -21442,6 +21461,30 @@ function ExtensionsPanel({ deviceId, slots, title, readOnly = false }) {
21442
21461
  onSaved: () => void query.refetch(),
21443
21462
  readOnly
21444
21463
  }, row.slotRef)),
21464
+ unknownShown.map((row) => /* @__PURE__ */ jsxs("div", {
21465
+ role: "alert",
21466
+ className: "flex flex-col gap-1 rounded-md border border-warning/40 bg-warning/5 p-3",
21467
+ children: [
21468
+ /* @__PURE__ */ jsxs("div", {
21469
+ className: "flex items-center gap-2",
21470
+ children: [/* @__PURE__ */ jsx(Badge, {
21471
+ variant: "warning",
21472
+ children: "Unknown slot"
21473
+ }), /* @__PURE__ */ jsx("span", {
21474
+ className: "text-xs font-semibold text-foreground",
21475
+ children: row.slot
21476
+ })]
21477
+ }),
21478
+ /* @__PURE__ */ jsx("p", {
21479
+ className: "text-[11px] text-foreground-muted break-words",
21480
+ children: row.slotRef
21481
+ }),
21482
+ /* @__PURE__ */ jsx("p", {
21483
+ className: "text-[10px] text-foreground-subtle",
21484
+ children: "The admin UI and the server disagree: the server offers a slot this admin UI does not know. Update the admin UI, or the server."
21485
+ })
21486
+ ]
21487
+ }, `unknown:${row.slotRef}`)),
21445
21488
  view.unreachable.map((row) => /* @__PURE__ */ jsxs("div", {
21446
21489
  role: "alert",
21447
21490
  className: "flex flex-col gap-1 rounded-md border border-warning/40 bg-warning/5 p-3",
@@ -36002,7 +36045,8 @@ var LIVE_NOISE_CATEGORIES = Object.freeze([
36002
36045
  EventCategory.PipelineAudioInferenceResult,
36003
36046
  EventCategory.PlatformProbePhase,
36004
36047
  EventCategory.BenchmarkProgress,
36005
- EventCategory.ModelDownloadProgress
36048
+ EventCategory.ModelDownloadProgress,
36049
+ EventCategory.ProviderMotion
36006
36050
  ]);
36007
36051
  var LIVE_NOISE_SET = new Set(LIVE_NOISE_CATEGORIES);
36008
36052
  /** Categories visible in the events tab by default. The "All" badge
@@ -36050,7 +36094,7 @@ function summarizeEvent(category, data) {
36050
36094
  if (typeof score === "number") parts.push(`${Math.round(score * 100)}%`);
36051
36095
  return parts.length > 0 ? parts.join(" — ") : null;
36052
36096
  }
36053
- if (category === EventCategory.ProviderMotion) return data["active"] === true ? "motion start" : "motion stop";
36097
+ if (category === EventCategory.ProviderMotion) return data["detected"] === true ? "motion start" : "motion stop";
36054
36098
  if (category === EventCategory.DeviceStateChanged) {
36055
36099
  const slice = data["slice"];
36056
36100
  if (slice && typeof slice === "object") {
@@ -36855,7 +36899,7 @@ function runtimeStateKeys(capName) {
36855
36899
  }
36856
36900
  /**
36857
36901
  * Cold placeholder slice for a bound cap that declares `runtimeState` but
36858
- * has never written it (e.g. a virtual-doorbell camera that has never rung).
36902
+ * has never written it (e.g. a doorbell that has never rung).
36859
36903
  * Every declared field is seeded `null` so the tree surfaces the cap's
36860
36904
  * presence and reads as "never" instead of the cap being OMITTED entirely.
36861
36905
  * Returns `null` when the cap has no runtime-state schema (nothing to seed).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/ui-library",
3
- "version": "1.2.218",
3
+ "version": "1.2.219",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",