Skip to content

Commit 6dc0544

Browse files
guFalconclaude
andcommitted
Archive openspec change floorplan-presence-sensors
Synced the new presence-floorplan capability into openspec/specs/ (9 requirements) and moved the change to archive/2026-06-27-floorplan-presence-sensors. 5 manual on-device QA tasks (section 7) remain for post-deploy verification. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9b46908 commit 6dc0544

6 files changed

Lines changed: 145 additions & 0 deletions

File tree

openspec/changes/floorplan-presence-sensors/.openspec.yaml renamed to openspec/changes/archive/2026-06-27-floorplan-presence-sensors/.openspec.yaml

File renamed without changes.

openspec/changes/floorplan-presence-sensors/design.md renamed to openspec/changes/archive/2026-06-27-floorplan-presence-sensors/design.md

File renamed without changes.

openspec/changes/floorplan-presence-sensors/proposal.md renamed to openspec/changes/archive/2026-06-27-floorplan-presence-sensors/proposal.md

File renamed without changes.

openspec/changes/floorplan-presence-sensors/specs/presence-floorplan/spec.md renamed to openspec/changes/archive/2026-06-27-floorplan-presence-sensors/specs/presence-floorplan/spec.md

File renamed without changes.

openspec/changes/floorplan-presence-sensors/tasks.md renamed to openspec/changes/archive/2026-06-27-floorplan-presence-sensors/tasks.md

File renamed without changes.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# presence-floorplan Specification
2+
3+
## Purpose
4+
TBD - created by archiving change floorplan-presence-sensors. Update Purpose after archive.
5+
## Requirements
6+
### Requirement: Reuse of existing Floorplan component
7+
8+
The presence-sensor visualisation SHALL be implemented by extending `src/components/floorplan/Floorplan.vue`, not by introducing a new floorplan component. No tabs, no floor selector, and no new layout type SHALL be added; floor switching continues to work via the existing `$store.state.gui.floorplan` mechanism that all `Kiosk*` views use.
9+
10+
#### Scenario: No new floorplan component
11+
12+
- **WHEN** the change is implemented
13+
- **THEN** there is no new file under `src/components/floorplan/` that re-implements the floorplan rendering
14+
- **AND** the presence-point rendering lives inside `Floorplan.vue`
15+
16+
#### Scenario: No tabs in presence view
17+
18+
- **WHEN** `KioskPresence.vue` is rendered
19+
- **THEN** it contains no tab/segment control for selecting floors
20+
21+
### Requirement: KioskPresence view mirrors KioskLights structure
22+
23+
`src/views/KioskPresence.vue` SHALL be a structural 1:1 clone of `src/views/KioskLights.vue`: same `<v-container>` wrapper, same `<Floorplan>` instantiation pattern with colour props, same `KioskLinkPanel` back-link to `/app/kioskoverview`, same `KioskTogglePanel` controlling `displayEnhancedDialog`, and the same `mounted()` call to `kioskMode(true)`.
24+
25+
#### Scenario: Filter prop
26+
27+
- **WHEN** `KioskPresence.vue` instantiates `<Floorplan>`
28+
- **THEN** it passes `:applianceTypeFilter="['OCCUPANCY_SENSOR']"`
29+
- **AND** does not pass a `classFqnFilter`
30+
31+
#### Scenario: Layout parity with Lights
32+
33+
- **WHEN** `KioskPresence.vue` is compared structurally to `KioskLights.vue`
34+
- **THEN** the template tree (root `<div class="home">`, `<v-container>`, child component order) matches; only the `applianceTypeFilter`, the `icon` prop, and the i18n title differ
35+
36+
### Requirement: Routing and navigation
37+
38+
A new lazy-loaded route `/app/kioskpresence` named `kioskPresence` SHALL be added to `src/router/index.ts`, and the navigation drawer SHALL link to it.
39+
40+
#### Scenario: Route is registered
41+
42+
- **WHEN** the user navigates to `/app/kioskpresence`
43+
- **THEN** the router renders `KioskPresence.vue`
44+
45+
#### Scenario: Drawer entry exists
46+
47+
- **WHEN** the navigation drawer is opened
48+
- **THEN** an entry labelled with `page.kiosk.presence.title` links to `/app/kioskpresence`
49+
50+
### Requirement: Live target points rendered by Floorplan
51+
52+
For every appliance whose `usageType === 'OCCUPANCY_SENSOR'` is placed on the active floor, `Floorplan.vue` SHALL render one absolutely-positioned point overlay per entry in `state.presences[0].objects[]`, transformed from sensor-local metres into plan pixels using the appliance's `iconPos` and the per-appliance `{ yawDeg, pxPerMeter }` from `src/lib/presence/sensorOrientation.ts`.
53+
54+
#### Scenario: Single target
55+
56+
- **WHEN** the SSE stream reports `objects: [{ id: 1, x: 1.0, y: 2.0 }]` for a sensor whose `iconPos = [100, 200]` and orientation entry is `{ yawDeg: 0, pxPerMeter: 40 }`
57+
- **THEN** exactly one point overlay is rendered at plan-pixel coordinates derived from `(100 + 1.0·40, 200 + 2.0·40)` (with the project's plan-Y orientation applied consistently with `iconPos`), scaled by the floorplan's `scale` factor
58+
59+
#### Scenario: Multiple targets
60+
61+
- **WHEN** `objects` contains three entries
62+
- **THEN** three point overlays are rendered, one per entry
63+
64+
#### Scenario: Target leaves
65+
66+
- **WHEN** an `objects` entry from the previous frame is no longer present in the next frame
67+
- **THEN** its point overlay disappears within one render cycle
68+
69+
#### Scenario: Cross-vendor parity
70+
71+
- **WHEN** Shelly Presence Gen4 and Apollo MTR-1 appliances both have placed entries
72+
- **THEN** their target points are rendered through the same code path, with no vendor-specific branching beyond reading the same `presences[0].objects[]` paths
73+
74+
#### Scenario: Z fields are ignored
75+
76+
- **WHEN** a Shelly target carries non-null `z`, `minZ`, `maxZ`
77+
- **THEN** those fields do NOT influence the rendered point
78+
79+
### Requirement: Footprint comes from existing imgMapCoords polygon
80+
81+
The presence-sensor footprint on the plan SHALL be the existing `imgMapCoords` polygon that `Floorplan.vue` already draws for every placed appliance. No new coverage-cone polygon SHALL be computed or rendered.
82+
83+
#### Scenario: No coverage-cone module
84+
85+
- **WHEN** the change is implemented
86+
- **THEN** there is no module computing footprint polygons from `(ceilingHeight, hAngle, vAngle, range)`
87+
- **AND** ceiling-height constants are not introduced anywhere in the frontend
88+
89+
#### Scenario: Admin-drawn polygon is the footprint
90+
91+
- **WHEN** an `OCCUPANCY_SENSOR` appliance is placed with `imgMapCoords`
92+
- **THEN** that polygon is the footprint shown in the view, identically to how Floorplan handles all other sensor types
93+
94+
### Requirement: SSE subscription via pathsForApplianceType
95+
96+
`src/utils/overmindUtils.ts` SHALL declare presence subscription paths for the new `OCCUPANCY_SENSOR` type so that `Floorplan.vue`'s existing transport-registration loop subscribes automatically without any presence-specific code in `Floorplan.vue`'s mount logic.
97+
98+
#### Scenario: COMPACT_PATHS entry
99+
100+
- **WHEN** `pathsForApplianceType('OCCUPANCY_SENSOR', 'compact')` is called
101+
- **THEN** it returns `['presences[0].presence', 'presences[0].objects']`
102+
103+
#### Scenario: DETAIL_PATHS entry
104+
105+
- **WHEN** `pathsForApplianceType('OCCUPANCY_SENSOR', 'detail')` is called
106+
- **THEN** it returns `['**']`
107+
108+
#### Scenario: ApplianceType union
109+
110+
- **WHEN** the `ApplianceType` type alias is inspected
111+
- **THEN** it includes the literal `'OCCUPANCY_SENSOR'`
112+
113+
### Requirement: Reactive writes via setPathValue
114+
115+
The transport-update path-walk MUST continue to use `setPathValue` from `overmindUtils.ts` (which uses `Vue.set` for every dynamic hop) so that a freshly tracked target — i.e. a previously unseen index in `presences[0].objects` — is reactive without any additional code.
116+
117+
#### Scenario: New target appears
118+
119+
- **WHEN** a `transport-update` adds a previously unseen index to `presences[0].objects` for an `OCCUPANCY_SENSOR` appliance
120+
- **THEN** the new entry is reactive and triggers a re-render of the point layer without a manual `Vue.set` call in `Floorplan.vue`
121+
122+
### Requirement: Sensor orientation map in frontend
123+
124+
`src/lib/presence/sensorOrientation.ts` SHALL export a `Record<number, { yawDeg: number; pxPerMeter: number }>` keyed by appliance id, plus a `DEFAULT_ORIENTATION` constant. When an `OCCUPANCY_SENSOR` appliance has no entry, the default SHALL be used and a console warning SHALL be emitted (once per appliance id per session).
125+
126+
#### Scenario: Known appliance
127+
128+
- **WHEN** appliance id 206 has an entry `{ yawDeg: 90, pxPerMeter: 50 }`
129+
- **THEN** that orientation is used for projecting its targets
130+
131+
#### Scenario: Unknown appliance falls back to default
132+
133+
- **WHEN** appliance id 999 (`OCCUPANCY_SENSOR`) has no entry in `sensorOrientation.ts`
134+
- **THEN** `DEFAULT_ORIENTATION` is used
135+
- **AND** a console warning is emitted exactly once per session for that id
136+
137+
### Requirement: Read-only
138+
139+
The view and the presence overlay MUST NOT issue any write operations against `OCCUPANCY_SENSOR` appliances (no `POST /execute`, no config writes).
140+
141+
#### Scenario: No execute calls
142+
143+
- **WHEN** the user interacts with the view (clicks, taps, opens dialogs)
144+
- **THEN** no `POST /execute` request is sent to any `OCCUPANCY_SENSOR` appliance
145+

0 commit comments

Comments
 (0)