@camstack/types 1.2.202 → 1.2.204

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.
@@ -642,7 +642,11 @@ export declare const deviceManagerCapability: {
642
642
  * reference this label keep their `meta.location` value — the
643
643
  * registry is purely a suggestion list. To purge from devices,
644
644
  * iterate them and call `setLocation(deviceId, null)`
645
- * separately (or pass `cascade: true` to do it in one shot). */
645
+ * separately (or pass `cascade: true` to do it in one shot).
646
+ *
647
+ * The room's ICON goes with it, always — a key left behind survives in a
648
+ * map nobody lists, and a room later re-created under the same name would
649
+ * silently inherit an icon the operator never chose for it. */
646
650
  readonly removeLocation: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
647
651
  name: z.ZodString;
648
652
  cascade: z.ZodOptional<z.ZodBoolean>;
@@ -660,13 +664,219 @@ export declare const deviceManagerCapability: {
660
664
  * a suggestion list, and the devices that most need a rename are exactly
661
665
  * the ones carrying a label nobody registered. `to` renames onto an
662
666
  * existing room by MERGING into it, leaving no duplicate label. An empty
663
- * `to` throws before anything is written. */
667
+ * `to` throws before anything is written.
668
+ *
669
+ * The room's ICON travels with the rename, including when `from` was
670
+ * never registered — otherwise it orphans on a key no location carries
671
+ * any more. A merge keeps the TARGET's icon when it has one: renaming
672
+ * "Sala" into "Salone" moves devices into Salone, it does not restyle
673
+ * it. */
664
674
  readonly renameLocation: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
665
675
  from: z.ZodString;
666
676
  to: z.ZodString;
667
677
  }, z.core.$strip>, z.ZodObject<{
668
678
  moved: z.ZodNumber;
669
679
  }, z.core.$strip>, "mutation">;
680
+ /** Every location that carries an ICON, as `(locationKey, icon)` pairs.
681
+ *
682
+ * ADDITIVE, deliberately: `listLocations` keeps returning `string[]` —
683
+ * every consumer of it (the Device Info autocomplete, the HA adoption
684
+ * import, the recorder) reads a list of labels and none of them wants a
685
+ * shape change. A surface that wants icons asks for them and joins.
686
+ *
687
+ * Only assignments that EXIST are returned — a location with no icon is
688
+ * absent, not `map-pin`. The fallback belongs to the renderer
689
+ * (`resolveLocationIcon`), not to the store, so "the operator chose the
690
+ * pin" and "the operator chose nothing" stay distinguishable.
691
+ *
692
+ * The join key is `locationIconKey(label)` (trimmed, lower-cased) — the
693
+ * same equality the registry uses everywhere. Never the operator's
694
+ * casing: a case-only rename is a real edit here, and a map keyed on the
695
+ * displayed spelling would orphan its entry on exactly that edit. */
696
+ readonly listLocationIcons: import("./capability-definition.js").CapabilityMethodSchema<z.ZodVoid, z.ZodArray<z.ZodObject<{
697
+ locationKey: z.ZodString;
698
+ icon: z.ZodEnum<{
699
+ package: "package";
700
+ "map-pin": "map-pin";
701
+ house: "house";
702
+ "door-open": "door-open";
703
+ sofa: "sofa";
704
+ utensils: "utensils";
705
+ "bed-double": "bed-double";
706
+ bath: "bath";
707
+ briefcase: "briefcase";
708
+ car: "car";
709
+ "circle-parking": "circle-parking";
710
+ route: "route";
711
+ trees: "trees";
712
+ umbrella: "umbrella";
713
+ "waves-ladder": "waves-ladder";
714
+ fence: "fence";
715
+ warehouse: "warehouse";
716
+ store: "store";
717
+ sun: "sun";
718
+ radar: "radar";
719
+ }>;
720
+ }, z.core.$strip>>, import("./capability-definition.js").CapabilityMethodKind>;
721
+ /** Set (or clear) the icon of a location label. `icon: null` removes the
722
+ * assignment. The label need NOT be registered — the registry is a
723
+ * suggestion list and devices carry labels nobody registered — but an
724
+ * empty / whitespace-only label throws. Idempotent.
725
+ *
726
+ * The icon vocabulary is CLOSED (`LOCATION_ICON_IDS`) because the admin UI
727
+ * and the viewer draw from different icon modules; see
728
+ * `device/location-icon.ts` for the list and for what a surface that
729
+ * cannot draw a member must do (fall back to `map-pin`, never a hole). */
730
+ readonly setLocationIcon: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
731
+ location: z.ZodString;
732
+ icon: z.ZodNullable<z.ZodEnum<{
733
+ package: "package";
734
+ "map-pin": "map-pin";
735
+ house: "house";
736
+ "door-open": "door-open";
737
+ sofa: "sofa";
738
+ utensils: "utensils";
739
+ "bed-double": "bed-double";
740
+ bath: "bath";
741
+ briefcase: "briefcase";
742
+ car: "car";
743
+ "circle-parking": "circle-parking";
744
+ route: "route";
745
+ trees: "trees";
746
+ umbrella: "umbrella";
747
+ "waves-ladder": "waves-ladder";
748
+ fence: "fence";
749
+ warehouse: "warehouse";
750
+ store: "store";
751
+ sun: "sun";
752
+ radar: "radar";
753
+ }>>;
754
+ }, z.core.$strip>, z.ZodVoid, "mutation">;
755
+ /** Every location that carries a DESCRIPTION — what the place LOOKS like,
756
+ * in plain words, given to the vision model alongside the pictures so it
757
+ * knows what it is looking at. A driveway of cobbles between a brick wall
758
+ * and three parking spaces reads very differently from a back garden.
759
+ *
760
+ * Same additive shape and same join key as `listLocationIcons`, and that
761
+ * is the point of it being here at all: this lived in the post-analysis
762
+ * addon's own settings, keyed by a hand-typed label matched exactly and
763
+ * case-sensitively, while the icon hung off the location itself. A rename
764
+ * carried the icon and silently orphaned the description. One location,
765
+ * one authority, one key.
766
+ *
767
+ * Absent means nobody wrote one — the digest then tells the model only the
768
+ * location NAME, exactly as it did before descriptions existed. */
769
+ readonly listLocationDescriptions: import("./capability-definition.js").CapabilityMethodSchema<z.ZodVoid, z.ZodArray<z.ZodObject<{
770
+ locationKey: z.ZodString;
771
+ description: z.ZodString;
772
+ }, z.core.$strip>>, import("./capability-definition.js").CapabilityMethodKind>;
773
+ /** Set (or clear) a location's description. `description: null`, or a
774
+ * blank string, removes it — an empty description and no description are
775
+ * the same thing to every reader, and storing the first would send an
776
+ * empty line to the model.
777
+ *
778
+ * Like `setLocationIcon`, the label need NOT be registered, and an
779
+ * empty / whitespace-only LABEL throws. */
780
+ readonly setLocationDescription: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
781
+ location: z.ZodString;
782
+ description: z.ZodNullable<z.ZodString>;
783
+ }, z.core.$strip>, z.ZodVoid, "mutation">;
784
+ /** Every SITE ZONE — a named part of the property that groups locations
785
+ * ("Zona notte" holds the bedrooms). One call answers the whole picture:
786
+ * key, the label as typed, the icon, and the locations inside.
787
+ *
788
+ * Together, because a surface that asked for membership separately could
789
+ * draw a zone whose rooms it has not fetched — and the strip that renders
790
+ * these puts zones BEFORE locations, so a half-known zone is the first
791
+ * thing an operator sees.
792
+ *
793
+ * Nothing here touches a device: a camera's zone is derived from its
794
+ * location, never stored on the row. Grouping rooms must not rewrite the
795
+ * fleet. */
796
+ readonly listSiteZones: import("./capability-definition.js").CapabilityMethodSchema<z.ZodVoid, z.ZodArray<z.ZodObject<{
797
+ zoneKey: z.ZodString;
798
+ label: z.ZodString;
799
+ icon: z.ZodNullable<z.ZodEnum<{
800
+ package: "package";
801
+ "map-pin": "map-pin";
802
+ house: "house";
803
+ "door-open": "door-open";
804
+ sofa: "sofa";
805
+ utensils: "utensils";
806
+ "bed-double": "bed-double";
807
+ bath: "bath";
808
+ briefcase: "briefcase";
809
+ car: "car";
810
+ "circle-parking": "circle-parking";
811
+ route: "route";
812
+ trees: "trees";
813
+ umbrella: "umbrella";
814
+ "waves-ladder": "waves-ladder";
815
+ fence: "fence";
816
+ warehouse: "warehouse";
817
+ store: "store";
818
+ sun: "sun";
819
+ radar: "radar";
820
+ }>>;
821
+ locationKeys: z.ZodArray<z.ZodString>;
822
+ }, z.core.$strip>>, import("./capability-definition.js").CapabilityMethodKind>;
823
+ /** Create a zone, or set the icon of one that exists. `icon: null` clears
824
+ * the icon and KEEPS the zone — a zone with no icon is a normal zone, and
825
+ * conflating "no icon" with "no zone" would delete membership on a
826
+ * mis-click.
827
+ *
828
+ * Idempotent, and the label is stored as typed: the key is folded, so
829
+ * re-running this with different casing re-labels rather than duplicating. */
830
+ readonly setSiteZone: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
831
+ name: z.ZodString;
832
+ icon: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
833
+ package: "package";
834
+ "map-pin": "map-pin";
835
+ house: "house";
836
+ "door-open": "door-open";
837
+ sofa: "sofa";
838
+ utensils: "utensils";
839
+ "bed-double": "bed-double";
840
+ bath: "bath";
841
+ briefcase: "briefcase";
842
+ car: "car";
843
+ "circle-parking": "circle-parking";
844
+ route: "route";
845
+ trees: "trees";
846
+ umbrella: "umbrella";
847
+ "waves-ladder": "waves-ladder";
848
+ fence: "fence";
849
+ warehouse: "warehouse";
850
+ store: "store";
851
+ sun: "sun";
852
+ radar: "radar";
853
+ }>>>;
854
+ }, z.core.$strip>, z.ZodVoid, "mutation">;
855
+ /** Delete a zone. Its LOCATIONS survive — they simply stop belonging to
856
+ * anything. Deleting a grouping must never delete what was grouped. */
857
+ readonly removeSiteZone: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
858
+ name: z.ZodString;
859
+ }, z.core.$strip>, z.ZodVoid, "mutation">;
860
+ /** Rename a zone, carrying its icon AND its membership.
861
+ *
862
+ * It exists so that renaming is not delete-then-recreate, which would drop
863
+ * both — the exact orphaning that lost a location's description when the
864
+ * two hung off different keys. Renaming onto an existing zone MERGES into
865
+ * it and the target keeps its own icon. */
866
+ readonly renameSiteZone: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
867
+ from: z.ZodString;
868
+ to: z.ZodString;
869
+ }, z.core.$strip>, z.ZodVoid, "mutation">;
870
+ /** Put a location in a zone, or take it out with `zone: null`.
871
+ *
872
+ * At most one zone per location, by construction: this writes one key, so
873
+ * there is no state in which a room is in two places at once. The zone
874
+ * need not exist yet — it is created empty-labelled from this call, the
875
+ * same way `setLocationIcon` does not require a registered location. */
876
+ readonly setLocationZone: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
877
+ location: z.ZodString;
878
+ zone: z.ZodNullable<z.ZodString>;
879
+ }, z.core.$strip>, z.ZodVoid, "mutation">;
670
880
  /** Soft-disable / re-enable the device. Drivers consult
671
881
  * `BaseDevice.disabled` to gate lifecycle hooks. */
672
882
  readonly setDisabled: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
@@ -17,6 +17,8 @@ export { BATTERY_DEVICE_PROFILE, DEVICE_PROFILES, deviceMatchesProfile, resolveD
17
17
  export type { IDeviceRuntimeState, Snapshot as RuntimeStateSnapshot, } from './device-runtime-state.js';
18
18
  export { ChargingStatus, DeviceFeature, DeviceRole, DeviceType } from './device-type.js';
19
19
  export type { IBatteryOperated, IDoorbellButton, INativeSnapshot, IPanTiltZoom, IRebootable, ITwoWayAudio, } from './features.js';
20
+ export type { LocationDescriptionAssignment, LocationIconAssignment, LocationIconId, } from './location-icon.js';
21
+ export { LOCATION_ICON_FALLBACK, LOCATION_ICON_IDS, LOCATION_DESCRIPTION_MAX, LocationDescriptionAssignmentSchema, LocationIconAssignmentSchema, LocationIconIdSchema, locationIconKey, resolveLocationIcon, } from './location-icon.js';
20
22
  export { getByPath, setByPath } from './path-util.js';
21
23
  export type { ReachabilityPollHandle, ReachabilityPollLogger, ReachabilityPollOptions, } from './reachability-poll.js';
22
24
  export { REACHABILITY_FAILURES_TO_OFFLINE, REACHABILITY_POLL_INTERVAL_MS, REACHABILITY_PROBE_TIMEOUT_MS, startReachabilityPoll, } from './reachability-poll.js';
@@ -26,3 +28,5 @@ export type { WireableField } from './schema-fields.js';
26
28
  export { enumerateItemArrayFields, enumerateSchemaFields } from './schema-fields.js';
27
29
  export type { DeviceConfigEntry } from './zod-to-config-ui.js';
28
30
  export { zodEntriesToConfigUI } from './zod-to-config-ui.js';
31
+ export type { SiteZone } from './site-zone.js';
32
+ export { SiteZoneSchema, siteZoneKey } from './site-zone.js';
@@ -0,0 +1,182 @@
1
+ /**
2
+ * The CLOSED icon vocabulary a location may carry.
3
+ *
4
+ * A location is not an entity — it is a free-form label (`PersistedDeviceMeta.
5
+ * location`) plus an operator-curated registry of labels in the device-manager
6
+ * addon store. The icon is a second map beside that registry, keyed the same
7
+ * way the registry matches everywhere else: {@link locationIconKey}, i.e.
8
+ * trimmed + lower-cased.
9
+ *
10
+ * WHY A CLOSED SET, AND WHY THESE NAMES
11
+ * -------------------------------------
12
+ * Two surfaces draw this icon and they do NOT share an icon module: the admin
13
+ * UI imports `lucide-react`, the viewer imports a hand-maintained per-icon
14
+ * allowlist (`src/lib/icons.ts`, re-exporting `lucide-react-native/icons/*`
15
+ * because the barrel is 47 % of the app's modules). A free-form string would
16
+ * therefore be renderable on one surface and a hole on the other, decided at
17
+ * runtime by whichever icon happened to be bundled.
18
+ *
19
+ * So the vocabulary names the DRAWING, not the room: every member is the exact
20
+ * kebab-case lucide icon id. That makes "can this surface draw it?" a
21
+ * mechanical question with a mechanical answer — the admin UI maps the id to
22
+ * lucide-react's PascalCase export, the viewer maps it to
23
+ * `lucide-react-native/icons/<id>` — instead of a semantic judgement call, and
24
+ * it removes the per-surface meaning table a room-typed vocabulary
25
+ * (`kitchen`, `garage`, …) would have needed on both sides. An operator who
26
+ * puts the `car` icon on a room called "Ripostiglio" is not fighting the
27
+ * model; they picked a picture.
28
+ *
29
+ * The members, and the export each surface needs:
30
+ *
31
+ * | id | lucide-react | viewer `icons.ts` export |
32
+ * | ----------------- | ----------------- | ------------------------ |
33
+ * | `map-pin` | `MapPin` | present |
34
+ * | `house` | `House` | present |
35
+ * | `door-open` | `DoorOpen` | present |
36
+ * | `utensils` | `Utensils` | present |
37
+ * | `car` | `Car` | present |
38
+ * | `circle-parking` | `CircleParking` | present |
39
+ * | `route` | `Route` | present |
40
+ * | `package` | `Package` | present |
41
+ * | `sun` | `Sun` | present |
42
+ * | `radar` | `Radar` | present |
43
+ * | `sofa` | `Sofa` | MISSING — add |
44
+ * | `bed-double` | `BedDouble` | MISSING — add |
45
+ * | `bath` | `Bath` | MISSING — add |
46
+ * | `briefcase` | `Briefcase` | MISSING — add |
47
+ * | `trees` | `Trees` | MISSING — add |
48
+ * | `umbrella` | `Umbrella` | MISSING — add |
49
+ * | `waves-ladder` | `WavesLadder` | MISSING — add |
50
+ * | `fence` | `Fence` | MISSING — add |
51
+ * | `warehouse` | `Warehouse` | MISSING — add |
52
+ * | `store` | `Store` | MISSING — add |
53
+ *
54
+ * DEGRADATION IS A FALLBACK, NEVER A HOLE. A surface that cannot draw a member
55
+ * — a viewer build shipped before its allowlist caught up, a persisted value
56
+ * from a newer hub, a hand-edited settings row — draws
57
+ * {@link LOCATION_ICON_FALLBACK} (`map-pin`, the one every surface has always
58
+ * had). Read a stored/wire value through {@link resolveLocationIcon} and that
59
+ * is what you get; nothing anywhere renders an empty box.
60
+ *
61
+ * Growing the vocabulary is a framework change (`@camstack/types` travels in
62
+ * the `@camstack/server` closure) AND a viewer change. Add the id here, add the
63
+ * line to the viewer's `icons.ts`, and expect older viewers to draw the
64
+ * fallback for it until they update — which is exactly the contract above.
65
+ */
66
+ import { z } from 'zod';
67
+ /**
68
+ * Every icon id a location may carry. Kebab-case lucide icon ids — see the
69
+ * module docblock for why the vocabulary names the drawing and not the room.
70
+ */
71
+ export declare const LOCATION_ICON_IDS: readonly ["map-pin", "house", "door-open", "sofa", "utensils", "bed-double", "bath", "briefcase", "car", "circle-parking", "route", "trees", "umbrella", "waves-ladder", "fence", "package", "warehouse", "store", "sun", "radar"];
72
+ /** One member of {@link LOCATION_ICON_IDS}. */
73
+ export type LocationIconId = (typeof LOCATION_ICON_IDS)[number];
74
+ /**
75
+ * The id every surface is guaranteed to draw, and what an unrecognised value
76
+ * degrades to. It has been in both icon sets since before this vocabulary
77
+ * existed (the Locations page's own page icon), so "the fallback is missing"
78
+ * is not a state a build can reach.
79
+ */
80
+ export declare const LOCATION_ICON_FALLBACK: LocationIconId;
81
+ /** Wire/storage schema for one icon id. */
82
+ export declare const LocationIconIdSchema: z.ZodEnum<{
83
+ package: "package";
84
+ "map-pin": "map-pin";
85
+ house: "house";
86
+ "door-open": "door-open";
87
+ sofa: "sofa";
88
+ utensils: "utensils";
89
+ "bed-double": "bed-double";
90
+ bath: "bath";
91
+ briefcase: "briefcase";
92
+ car: "car";
93
+ "circle-parking": "circle-parking";
94
+ route: "route";
95
+ trees: "trees";
96
+ umbrella: "umbrella";
97
+ "waves-ladder": "waves-ladder";
98
+ fence: "fence";
99
+ warehouse: "warehouse";
100
+ store: "store";
101
+ sun: "sun";
102
+ radar: "radar";
103
+ }>;
104
+ /**
105
+ * One (location, icon) assignment as it travels.
106
+ *
107
+ * `locationKey` is ALWAYS {@link locationIconKey} of the label — never the
108
+ * operator's casing. The registry matches labels trimmed + case-insensitively
109
+ * everywhere (`addLocation`, `removeLocation`, `renameLocation`), and a
110
+ * case-only rename (`cucina` → `Cucina`) is a real edit this repo supports; a
111
+ * map keyed on the displayed casing would orphan its entry on exactly that
112
+ * edit. Consumers join by keying their own label through `locationIconKey`.
113
+ */
114
+ export declare const LocationIconAssignmentSchema: z.ZodObject<{
115
+ locationKey: z.ZodString;
116
+ icon: z.ZodEnum<{
117
+ package: "package";
118
+ "map-pin": "map-pin";
119
+ house: "house";
120
+ "door-open": "door-open";
121
+ sofa: "sofa";
122
+ utensils: "utensils";
123
+ "bed-double": "bed-double";
124
+ bath: "bath";
125
+ briefcase: "briefcase";
126
+ car: "car";
127
+ "circle-parking": "circle-parking";
128
+ route: "route";
129
+ trees: "trees";
130
+ umbrella: "umbrella";
131
+ "waves-ladder": "waves-ladder";
132
+ fence: "fence";
133
+ warehouse: "warehouse";
134
+ store: "store";
135
+ sun: "sun";
136
+ radar: "radar";
137
+ }>;
138
+ }, z.core.$strip>;
139
+ /** @see LocationIconAssignmentSchema */
140
+ export type LocationIconAssignment = z.infer<typeof LocationIconAssignmentSchema>;
141
+ /**
142
+ * The join key for a location label: trimmed, lower-cased. The ONE place this
143
+ * derivation lives — a caller that re-spells it is a rename away from an
144
+ * orphaned icon.
145
+ */
146
+ export declare function locationIconKey(label: string): string;
147
+ /**
148
+ * Read an arbitrary stored/wire value as a drawable icon id.
149
+ *
150
+ * Anything that is not a member of the vocabulary — `null`, `undefined`, a
151
+ * hand-edited settings row, an id minted by a newer hub — becomes
152
+ * {@link LOCATION_ICON_FALLBACK}. Never returns `null`: a caller that wants to
153
+ * distinguish "no icon chosen" from "icon unknown" must look at the assignment
154
+ * list, which only carries what was actually stored.
155
+ */
156
+ export declare function resolveLocationIcon(value: unknown): LocationIconId;
157
+ /**
158
+ * How long a location description may be.
159
+ *
160
+ * It is given to a vision model alongside the pictures, so it competes for the
161
+ * same attention the pictures need. A cap of one short paragraph is what keeps
162
+ * it a PLACE description rather than a second prompt — an operator who needs
163
+ * more than this is describing events, which change, instead of the ground,
164
+ * which does not.
165
+ */
166
+ export declare const LOCATION_DESCRIPTION_MAX = 400;
167
+ /**
168
+ * One (location, description) assignment as it travels.
169
+ *
170
+ * Same key discipline as {@link LocationIconAssignmentSchema}, and for the same
171
+ * reason: this lived for a while in the post-analysis addon's own settings,
172
+ * keyed by a HAND-TYPED label matched exactly and case-sensitively. A rename in
173
+ * the Locations page carried the icon and silently orphaned the description —
174
+ * two authorities for one thing, joined by two different rules. Now both hang
175
+ * off the location itself, on `locationIconKey`.
176
+ */
177
+ export declare const LocationDescriptionAssignmentSchema: z.ZodObject<{
178
+ locationKey: z.ZodString;
179
+ description: z.ZodString;
180
+ }, z.core.$strip>;
181
+ /** @see LocationDescriptionAssignmentSchema */
182
+ export type LocationDescriptionAssignment = z.infer<typeof LocationDescriptionAssignmentSchema>;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * A SITE ZONE — a named part of the property that several locations belong to.
3
+ *
4
+ * "Zona notte" holds every bedroom; "Zona giorno" the living room and the
5
+ * kitchen. A camera belongs to exactly one location, and a location to at most
6
+ * one zone, so a camera's zone is derived and never stored on the device: the
7
+ * whole point of this entity is that grouping rooms touches no device row.
8
+ *
9
+ * ## Why not `zone`
10
+ *
11
+ * Because `zone` is already the most loaded word in this product: 612 source
12
+ * files and 24 Italian strings where it means a DETECTION zone drawn on a
13
+ * camera. The two are unrelated — one is a polygon in a frame, the other is a
14
+ * set of rooms — and a reader who confuses them will look for a camera's
15
+ * bedrooms. So the code says `siteZone` everywhere, and the UI frees the plain
16
+ * word by calling the drawn one what it actually is: a detection zone.
17
+ *
18
+ * ## The key
19
+ *
20
+ * `siteZoneKey(label)` — trimmed, lower-cased, the SAME derivation locations
21
+ * use (`locationIconKey`). Membership and the icon both hang off it, so a
22
+ * case-only rename keeps them, which is exactly the orphaning that cost us a
23
+ * silently-dropped location description.
24
+ *
25
+ * The icon vocabulary is the location one, unchanged: a zone is drawn on the
26
+ * same two surfaces by the same glyph map, so a second vocabulary would be a
27
+ * second thing to keep drawable (`scripts/check-location-icons-drawable.ts`).
28
+ */
29
+ import { z } from 'zod';
30
+ /**
31
+ * One zone as it travels: its key, the label the operator typed, its icon and
32
+ * the locations inside it.
33
+ *
34
+ * The LABEL is carried alongside the key because the key is lossy on purpose —
35
+ * a surface must show "Zona notte", not "zona notte". Locations are carried
36
+ * here, rather than left to a second call, because every consumer wants both
37
+ * at once and a viewer that asked twice could render a zone whose membership
38
+ * it has not fetched yet.
39
+ *
40
+ * `icon: null` means nobody chose one, which is NOT the fallback pin: only one
41
+ * of those two is a choice, and a picker has to draw them differently.
42
+ */
43
+ export declare const SiteZoneSchema: z.ZodObject<{
44
+ zoneKey: z.ZodString;
45
+ label: z.ZodString;
46
+ icon: z.ZodNullable<z.ZodEnum<{
47
+ package: "package";
48
+ "map-pin": "map-pin";
49
+ house: "house";
50
+ "door-open": "door-open";
51
+ sofa: "sofa";
52
+ utensils: "utensils";
53
+ "bed-double": "bed-double";
54
+ bath: "bath";
55
+ briefcase: "briefcase";
56
+ car: "car";
57
+ "circle-parking": "circle-parking";
58
+ route: "route";
59
+ trees: "trees";
60
+ umbrella: "umbrella";
61
+ "waves-ladder": "waves-ladder";
62
+ fence: "fence";
63
+ warehouse: "warehouse";
64
+ store: "store";
65
+ sun: "sun";
66
+ radar: "radar";
67
+ }>>;
68
+ locationKeys: z.ZodArray<z.ZodString>;
69
+ }, z.core.$strip>;
70
+ /** @see SiteZoneSchema */
71
+ export type SiteZone = z.infer<typeof SiteZoneSchema>;
72
+ /**
73
+ * The join key for a zone label: trimmed, lower-cased.
74
+ *
75
+ * Deliberately a separate export from `locationIconKey` even though the
76
+ * derivation is identical — they key different maps, and a future change to one
77
+ * must not silently re-key the other. Their agreement is a fact about today,
78
+ * not a dependency.
79
+ */
80
+ export declare function siteZoneKey(label: string): string;
@@ -2254,6 +2254,69 @@ export type AppRouter = TrpcCoreRouter<{
2254
2254
  output: z.infer<typeof deviceManagerCapability.methods.renameLocation.output>;
2255
2255
  meta: object;
2256
2256
  }>;
2257
+ listLocationIcons: TRPCQueryProcedure<{
2258
+ input: {
2259
+ nodeId?: string | undefined;
2260
+ } | undefined;
2261
+ output: z.infer<typeof deviceManagerCapability.methods.listLocationIcons.output>;
2262
+ meta: object;
2263
+ }>;
2264
+ setLocationIcon: TRPCMutationProcedure<{
2265
+ input: {
2266
+ [x: string]: unknown;
2267
+ } & z.input<typeof deviceManagerCapability.methods.setLocationIcon.input>;
2268
+ output: z.infer<typeof deviceManagerCapability.methods.setLocationIcon.output>;
2269
+ meta: object;
2270
+ }>;
2271
+ listLocationDescriptions: TRPCQueryProcedure<{
2272
+ input: {
2273
+ nodeId?: string | undefined;
2274
+ } | undefined;
2275
+ output: z.infer<typeof deviceManagerCapability.methods.listLocationDescriptions.output>;
2276
+ meta: object;
2277
+ }>;
2278
+ setLocationDescription: TRPCMutationProcedure<{
2279
+ input: {
2280
+ [x: string]: unknown;
2281
+ } & z.input<typeof deviceManagerCapability.methods.setLocationDescription.input>;
2282
+ output: z.infer<typeof deviceManagerCapability.methods.setLocationDescription.output>;
2283
+ meta: object;
2284
+ }>;
2285
+ listSiteZones: TRPCQueryProcedure<{
2286
+ input: {
2287
+ nodeId?: string | undefined;
2288
+ } | undefined;
2289
+ output: z.infer<typeof deviceManagerCapability.methods.listSiteZones.output>;
2290
+ meta: object;
2291
+ }>;
2292
+ setSiteZone: TRPCMutationProcedure<{
2293
+ input: {
2294
+ [x: string]: unknown;
2295
+ } & z.input<typeof deviceManagerCapability.methods.setSiteZone.input>;
2296
+ output: z.infer<typeof deviceManagerCapability.methods.setSiteZone.output>;
2297
+ meta: object;
2298
+ }>;
2299
+ removeSiteZone: TRPCMutationProcedure<{
2300
+ input: {
2301
+ [x: string]: unknown;
2302
+ } & z.input<typeof deviceManagerCapability.methods.removeSiteZone.input>;
2303
+ output: z.infer<typeof deviceManagerCapability.methods.removeSiteZone.output>;
2304
+ meta: object;
2305
+ }>;
2306
+ renameSiteZone: TRPCMutationProcedure<{
2307
+ input: {
2308
+ [x: string]: unknown;
2309
+ } & z.input<typeof deviceManagerCapability.methods.renameSiteZone.input>;
2310
+ output: z.infer<typeof deviceManagerCapability.methods.renameSiteZone.output>;
2311
+ meta: object;
2312
+ }>;
2313
+ setLocationZone: TRPCMutationProcedure<{
2314
+ input: {
2315
+ [x: string]: unknown;
2316
+ } & z.input<typeof deviceManagerCapability.methods.setLocationZone.input>;
2317
+ output: z.infer<typeof deviceManagerCapability.methods.setLocationZone.output>;
2318
+ meta: object;
2319
+ }>;
2257
2320
  setDisabled: TRPCMutationProcedure<{
2258
2321
  input: {
2259
2322
  [x: string]: unknown;
@@ -6,7 +6,7 @@
6
6
  * scope+access check inside `protectedProcedure` (see
7
7
  * `server/backend/src/api/trpc/trpc.middleware.ts`).
8
8
  *
9
- * Coverage: 1039 method paths across 130 capabilities.
9
+ * Coverage: 1048 method paths across 130 capabilities.
10
10
  */
11
11
  import type { CapabilityMethodAccess } from '../capabilities/capability-definition.js';
12
12
  export interface MethodAccessRecord {
@@ -68,7 +68,7 @@ export interface SystemProxy {
68
68
  readonly decoder: Pick<InferProvider<typeof decoderCapability>, 'supportsCodec' | 'getInfo' | 'createSession' | 'destroySession' | 'pushPacket' | 'openStream' | 'pullFrames' | 'pullHandles' | 'getFrame' | 'getShmStats' | 'updateConfig' | 'getStats' | 'listActiveSessions' | 'reprobeHwaccel'>;
69
69
  readonly deviceAdoption: Pick<InferProvider<typeof deviceAdoptionCapability>, 'listCandidateFilters' | 'listCandidates' | 'getCandidate' | 'refresh' | 'adopt' | 'release' | 'resync'>;
70
70
  readonly deviceExport: Pick<InferProvider<typeof deviceExportCapability>, 'getStatus' | 'listSupportedDeviceKinds' | 'listExposedDevices' | 'exposeDevice' | 'unexposeDevice'>;
71
- readonly deviceManager: Pick<InferProvider<typeof deviceManagerCapability>, 'allocateDeviceId' | 'migrateDevice' | 'registerDevice' | 'removeDevice' | 'persistConfig' | 'getRoleDisplayDefaults' | 'setRoleDisplayDefaults' | 'listLocations' | 'addLocation' | 'removeLocation' | 'renameLocation' | 'listPersistedByAddon' | 'listAll' | 'getChildren' | 'getChildrenBatch' | 'getLinkedDevicesBatch' | 'removeByIntegration' | 'getBindingsBatch' | 'getAllBindings' | 'listWrappersForCap' | 'listBindableCapsForDeviceType' | 'discoverDevices' | 'adoptDevice' | 'getCreationSchema' | 'createDevice' | 'testCreationField' | 'adoptionListCandidateFilters' | 'adoptionListCandidates' | 'adoptionRefresh' | 'adoptionAdopt' | 'adoptionRelease' | 'adoptionStartJob' | 'adoptionListJobs' | 'adoptionCancelJob' | 'adoptionResync' | 'discoveryProviders' | 'discoverAllProviders' | 'discoverProvider' | 'providerCreationType' | 'providerDiscoveryParamsSchema' | 'getDeviceStatusAggregateBatch'>;
71
+ readonly deviceManager: Pick<InferProvider<typeof deviceManagerCapability>, 'allocateDeviceId' | 'migrateDevice' | 'registerDevice' | 'removeDevice' | 'persistConfig' | 'getRoleDisplayDefaults' | 'setRoleDisplayDefaults' | 'listLocations' | 'addLocation' | 'removeLocation' | 'renameLocation' | 'listLocationIcons' | 'setLocationIcon' | 'listLocationDescriptions' | 'setLocationDescription' | 'listSiteZones' | 'setSiteZone' | 'removeSiteZone' | 'renameSiteZone' | 'setLocationZone' | 'listPersistedByAddon' | 'listAll' | 'getChildren' | 'getChildrenBatch' | 'getLinkedDevicesBatch' | 'removeByIntegration' | 'getBindingsBatch' | 'getAllBindings' | 'listWrappersForCap' | 'listBindableCapsForDeviceType' | 'discoverDevices' | 'adoptDevice' | 'getCreationSchema' | 'createDevice' | 'testCreationField' | 'adoptionListCandidateFilters' | 'adoptionListCandidates' | 'adoptionRefresh' | 'adoptionAdopt' | 'adoptionRelease' | 'adoptionStartJob' | 'adoptionListJobs' | 'adoptionCancelJob' | 'adoptionResync' | 'discoveryProviders' | 'discoverAllProviders' | 'discoverProvider' | 'providerCreationType' | 'providerDiscoveryParamsSchema' | 'getDeviceStatusAggregateBatch'>;
72
72
  readonly deviceProvider: Pick<InferProvider<typeof deviceProviderCapability>, 'start' | 'stop' | 'getStatus' | 'getDevices' | 'supportsDiscovery' | 'discoverDevices' | 'getDiscoveryParamsSchema' | 'getManualCreationType' | 'adoptDiscoveredDevice' | 'supportsManualCreation' | 'getChildCreationSchema' | 'createDevice' | 'testCreationField'>;
73
73
  readonly deviceState: Pick<InferProvider<typeof deviceStateCapability>, 'getAllSnapshots'>;
74
74
  readonly faceGallery: Pick<InferProvider<typeof faceGalleryCapability>, 'listIdentities' | 'createIdentity' | 'renameIdentity' | 'deleteIdentity' | 'listIdentitySamples' | 'auditIdentitySamples' | 'removeSample' | 'getFaceMedia' | 'assignFace' | 'unassignFace' | 'deleteFace' | 'assignFaces' | 'unassignFaces' | 'suggestFaceClusters'>;
package/dist/index.d.ts CHANGED
@@ -174,6 +174,8 @@ export type { SliceEventBus, SliceHandle, SliceHandleApi, SliceHandleSource, } f
174
174
  export { createEventBusSliceSource, createLazyTrpcSource, createMirrorSource, createSliceHandle, } from './device/device-state-handle.js';
175
175
  export { ChargingStatus, DeviceFeature, DeviceRole, DeviceType } from './device/device-type.js';
176
176
  export type { IBatteryOperated, IDoorbellButton, INativeSnapshot, IPanTiltZoom, IRebootable, ITwoWayAudio, } from './device/features.js';
177
+ export type { LocationDescriptionAssignment, LocationIconAssignment, LocationIconId, } from './device/location-icon.js';
178
+ export { LOCATION_ICON_FALLBACK, LOCATION_ICON_IDS, LOCATION_DESCRIPTION_MAX, LocationDescriptionAssignmentSchema, LocationIconAssignmentSchema, LocationIconIdSchema, locationIconKey, resolveLocationIcon, } from './device/location-icon.js';
177
179
  export { getByPath, setByPath } from './device/path-util.js';
178
180
  export type { ReachabilityPollHandle, ReachabilityPollLogger, ReachabilityPollOptions, } from './device/reachability-poll.js';
179
181
  export { REACHABILITY_FAILURES_TO_OFFLINE, REACHABILITY_POLL_INTERVAL_MS, REACHABILITY_PROBE_TIMEOUT_MS, startReachabilityPoll, } from './device/reachability-poll.js';
@@ -255,3 +257,5 @@ export { evaluateZoneRules, type ZoneRuleEvalResult } from './utils/zone-rule-ev
255
257
  export { bindAddonActions } from './helpers/bind-addon-actions.js';
256
258
  export type { DeviceOption, InferenceDeviceDescriptor, RuntimeId, } from './inference/runtime-capabilities.js';
257
259
  export { defaultDeviceFor, enumerateInferenceDevices, modelFormatForRuntime, runtimeDevices, scoreRuntimes, supportedRuntimes, } from './inference/runtime-capabilities.js';
260
+ export type { SiteZone } from './device/site-zone.js';
261
+ export { SiteZoneSchema, siteZoneKey } from './device/site-zone.js';