@telemetryos/cli 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/commands/claude-code.d.ts +2 -0
  3. package/dist/commands/claude-code.js +29 -0
  4. package/dist/commands/init.js +22 -9
  5. package/dist/index.js +2 -0
  6. package/dist/services/create-project.d.ts +13 -0
  7. package/dist/services/create-project.js +188 -0
  8. package/dist/services/project-config.d.ts +3 -0
  9. package/dist/services/project-config.js +3 -0
  10. package/dist/services/run-server.js +48 -25
  11. package/dist/utils/template.d.ts +2 -0
  12. package/dist/utils/template.js +30 -0
  13. package/package.json +2 -2
  14. package/templates/{vite-react-typescript → claude-code}/CLAUDE.md +10 -3
  15. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-architecture/SKILL.md +138 -61
  16. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-debugging/SKILL.md +2 -2
  17. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-media-api/SKILL.md +3 -3
  18. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-multi-mode/SKILL.md +97 -4
  19. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-requirements/SKILL.md +70 -5
  20. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-store-sync/SKILL.md +4 -2
  21. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-weather-api/SKILL.md +7 -6
  22. package/templates/claude-code/_claude/skills/tos-web-ui-design/SKILL.md +373 -0
  23. package/templates/vite-react-typescript/_gitignore +4 -2
  24. package/templates/vite-react-typescript/telemetry.config.json +2 -1
  25. package/templates/vite-react-typescript-web/_gitignore +32 -0
  26. package/templates/vite-react-typescript-web/assets/telemetryos-wordmark.svg +11 -0
  27. package/templates/vite-react-typescript-web/assets/tos-app.svg +12 -0
  28. package/templates/vite-react-typescript-web/index.html +15 -0
  29. package/templates/vite-react-typescript-web/package.json +24 -0
  30. package/templates/vite-react-typescript-web/src/App.tsx +25 -0
  31. package/templates/vite-react-typescript-web/src/hooks/store.ts +8 -0
  32. package/templates/vite-react-typescript-web/src/index.css +24 -0
  33. package/templates/vite-react-typescript-web/src/index.tsx +11 -0
  34. package/templates/vite-react-typescript-web/src/views/Render.css +67 -0
  35. package/templates/vite-react-typescript-web/src/views/Render.tsx +44 -0
  36. package/templates/vite-react-typescript-web/src/views/Settings.tsx +72 -0
  37. package/templates/vite-react-typescript-web/src/views/Web.css +105 -0
  38. package/templates/vite-react-typescript-web/src/views/Web.tsx +52 -0
  39. package/templates/vite-react-typescript-web/telemetry.config.json +16 -0
  40. package/templates/vite-react-typescript-web/tsconfig.json +19 -0
  41. package/templates/vite-react-typescript-web/vite.config.ts +18 -0
  42. /package/templates/{vite-react-typescript → claude-code}/AGENTS.md +0 -0
  43. /package/templates/{vite-react-typescript → claude-code}/_claude/settings.local.json +0 -0
  44. /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-proxy-fetch/SKILL.md +0 -0
  45. /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-render-kiosk-design/SKILL.md +0 -0
  46. /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-render-signage-design/SKILL.md +0 -0
  47. /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-render-ui-design/SKILL.md +0 -0
  48. /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-settings-ui/SKILL.md +0 -0
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  name: tos-architecture
3
- description: Understand TelemetryOS two-mount-point architecture (Render vs Settings). Use when debugging mount-point issues or understanding how Settings and Render communicate.
3
+ description: Understand TelemetryOS mount point architecture (Render, Settings, and optional Web). Use when debugging mount-point issues or understanding how mount points communicate through the store.
4
4
  ---
5
5
 
6
6
  # TelemetryOS Architecture
7
7
 
8
8
  TelemetryOS applications run on digital signage devices (TVs, kiosks, displays). Understanding the architecture is key to building effective apps.
9
9
 
10
- ## Two Mount Points
10
+ ## Mount Points
11
11
 
12
- Every standard TOS app has two entry points:
12
+ Every TOS app has at least two entry points (Render and Settings). Apps that need a browser-accessible interface add a third: the Web mount point.
13
13
 
14
14
  ### /render - Device Display
15
15
 
@@ -19,7 +19,7 @@ Every standard TOS app has two entry points:
19
19
  │ (TV, Kiosk, Digital Sign) │
20
20
  │ │
21
21
  │ ┌───────────────────────────┐ │
22
- │ │ Chrome Browser │ │
22
+ │ │ TelemetryOS Electron App │ │
23
23
  │ │ ┌─────────────────────┐ │ │
24
24
  │ │ │ Your App │ │ │
25
25
  │ │ │ /render route │ │ │
@@ -33,7 +33,7 @@ Every standard TOS app has two entry points:
33
33
 
34
34
  **Characteristics:**
35
35
  - Runs on physical device (TelemetryOS player)
36
- - Chrome browser in iframe sandbox
36
+ - Iframe sandbox in Electron app
37
37
  - Has access to `store().device` (device-local storage)
38
38
  - Displays content to viewers/customers
39
39
  - Full screen, optimized for viewing from distance
@@ -66,6 +66,38 @@ Every standard TOS app has two entry points:
66
66
  - Side panel in Studio editor
67
67
  - Form-based controls for settings
68
68
 
69
+ ### /web - Browser-Accessible Interface (Optional)
70
+
71
+ ```
72
+ ┌─────────────────────────────────┐
73
+ │ Any Browser (Phone/Tablet/ │
74
+ │ Desktop — Staff or Public) │
75
+ │ │
76
+ │ ┌───────────────────────────┐ │
77
+ │ │ TelemetryOS Web Host │ │
78
+ │ │ ┌─────────────────────┐ │ │
79
+ │ │ │ Your App │ │ │
80
+ │ │ │ /app-name route │ │ │
81
+ │ │ │ │ │ │
82
+ │ │ │ Operator desk, │ │ │
83
+ │ │ │ public form, staff │ │ │
84
+ │ │ │ dashboard │ │ │
85
+ │ │ └─────────────────────┘ │ │
86
+ │ └───────────────────────────┘ │
87
+ └─────────────────────────────────┘
88
+ ```
89
+
90
+ **Characteristics:**
91
+ - Runs in any browser, accessible via URL
92
+ - Can be staff-facing (private) or public-facing
93
+ - Application and dynamic namespace store scopes only
94
+ - **NO access to `store().instance` or `store().device`**
95
+ - Multi-level navigation with React Router
96
+ - Standard HTML/CSS (no SDK settings components, no UI scaling)
97
+ - Always pairs with multi-mode architecture and entity-driven routing
98
+
99
+ See `tos-web-ui-design` for full design patterns.
100
+
69
101
  ## Communication Flow
70
102
 
71
103
  ```
@@ -78,10 +110,31 @@ Every standard TOS app has two entry points:
78
110
  └─────────────┘ └─────────────────┘ └─────────────┘
79
111
  ```
80
112
 
81
- 1. **Settings writes** to instance store via hooks
82
- 2. **Render subscribes** to instance store via same hooks
83
- 3. Changes sync automatically (real-time)
84
- 4. Same hooks work in both mount points
113
+ With a Web mount point, communication extends through shared namespaces:
114
+
115
+ ```
116
+ ┌─────────────┐ ┌─────────────────┐ ┌─────────────┐
117
+ │ Settings │ ───▶ │ store(). │ ◀─── │ Web │
118
+ │ /settings │ WRITE │ shared() │ READ │ /app-name │
119
+ │ │ │ namespace │ +WRITE │ │
120
+ │ Admin │ │ │ │ Operator │
121
+ │ configures │ │ { queues, │ │ manages │
122
+ │ entities │ │ counters } │ │ live data │
123
+ └─────────────┘ └─────────────────┘ └─────────────┘
124
+
125
+ ▼ READ + SUB
126
+ ┌─────────────┐
127
+ │ Render │
128
+ │ /render │
129
+ │ Displays │
130
+ │ live data │
131
+ └─────────────┘
132
+ ```
133
+
134
+ 1. **Settings writes** configuration to instance and application store
135
+ 2. **Web reads and writes** entity-scoped data via dynamic namespace store
136
+ 3. **Render subscribes** and displays changes in real-time
137
+ 4. Same store hooks work across all mount points (scoped by what each can access)
85
138
 
86
139
  ## Project Structure
87
140
 
@@ -91,9 +144,12 @@ src/
91
144
  ├── App.tsx # Router - directs to correct view
92
145
  ├── views/
93
146
  │ ├── Settings.tsx # /settings mount point
94
- └── Render.tsx # /render mount point
147
+ ├── Render.tsx # /render mount point
148
+ │ ├── Render.css # /render styles
149
+ │ ├── Web.tsx # /app-name mount point (if using web)
150
+ │ └── Web.css # Web view styles (if using web)
95
151
  ├── hooks/
96
- │ └── store.ts # Shared store hooks (used in both views)
152
+ │ └── store.ts # Shared store hooks (used across all mount points)
97
153
  ├── components/ # Reusable UI components
98
154
  ├── types/ # TypeScript interfaces
99
155
  └── utils/ # Helper functions
@@ -103,29 +159,34 @@ src/
103
159
 
104
160
  ```typescript
105
161
  // App.tsx
162
+ import { createBrowserRouter, RouterProvider } from 'react-router'
106
163
  import Settings from './views/Settings'
107
164
  import Render from './views/Render'
108
165
 
109
- export function App() {
110
- const path = window.location.pathname
111
-
112
- if (path === '/settings') return <Settings />
113
- if (path === '/render') return <Render />
166
+ const router = createBrowserRouter([
167
+ { path: '/render', element: <Render /> },
168
+ { path: '/settings', element: <Settings /> },
169
+ ])
114
170
 
115
- return <div>Invalid mount point: {path}</div>
171
+ export function App() {
172
+ return <RouterProvider router={router} />
116
173
  }
117
174
  ```
118
175
 
119
- Or with React Router:
176
+ With a web mount point, add routes under the app name:
120
177
 
121
178
  ```typescript
122
179
  import { createBrowserRouter, RouterProvider } from 'react-router'
123
- import Settings from './views/Settings'
124
- import Render from './views/Render'
180
+ import { Render } from './views/Render'
181
+ import { Settings } from './views/Settings'
182
+ import { WebEntities, WebDetail, WebOperator } from './views/Web'
125
183
 
126
184
  const router = createBrowserRouter([
127
- { path: '/render', element: <Render /> },
128
- { path: '/settings', element: <Settings /> },
185
+ { path: '/render', Component: Render },
186
+ { path: '/settings', Component: Settings },
187
+ { path: '/my-app', Component: WebEntities },
188
+ { path: '/my-app/locations/:locationName', Component: WebDetail },
189
+ { path: '/my-app/locations/:locationName/counters/:counterId', Component: WebOperator },
129
190
  ])
130
191
 
131
192
  export function App() {
@@ -157,6 +218,15 @@ export function App() {
157
218
  - Form-based interaction
158
219
  - Preview pane integration
159
220
 
221
+ ### Web Only
222
+
223
+ - Standard HTML/CSS (no SDK settings components)
224
+ - Runs in any browser (phone, tablet, desktop)
225
+ - `store().application` and `store().shared(namespace)` only
226
+ - NO access to `store().instance` or `store().device`
227
+ - Multi-level React Router navigation
228
+ - No UI scaling (`useUiScaleToSetRem` not used)
229
+
160
230
  ## telemetry.config.json
161
231
 
162
232
  ```json
@@ -174,12 +244,32 @@ export function App() {
174
244
  }
175
245
  ```
176
246
 
247
+ With a web mount point:
248
+
249
+ ```json
250
+ {
251
+ "name": "my-app",
252
+ "version": "1.0.0",
253
+ "useSpaRouting": true,
254
+ "mountPoints": {
255
+ "render": "/render",
256
+ "settings": "/settings",
257
+ "web": "/my-app"
258
+ },
259
+ "devServer": {
260
+ "runCommand": "vite --port 3000",
261
+ "url": "http://localhost:3000"
262
+ }
263
+ }
264
+ ```
265
+
177
266
  ### Mount Point Configuration
178
267
 
179
- | Mount Point | Purpose | Route |
180
- |-------------|---------|-------|
181
- | render | Device display | /render |
182
- | settings | Admin config UI | /settings |
268
+ | Mount Point | Purpose | Route | Store Access |
269
+ |-------------|---------|-------|--------------|
270
+ | render | Device display | /render | instance, application, device, shared |
271
+ | settings | Admin config UI | /settings | instance, application, shared |
272
+ | web (optional) | Browser interface | /app-name | application, shared |
183
273
 
184
274
  ### Optional: Workers
185
275
 
@@ -198,34 +288,32 @@ export function App() {
198
288
 
199
289
  ```
200
290
  ┌────────────────────────────────────────────────────────────────┐
201
- Account
291
+ Account
292
+ │ │
202
293
  │ ┌──────────────────────────────────────────────────────────┐ │
203
- │ │ store().application │ │
204
- │ │ Shared across ALL instances of this app │ │
205
- │ │ (API keys, account-wide settings) │ │
294
+ │ │ store().shared('namespace') │ │
295
+ │ │ Dynamic namespace scoped by entity │ │
296
+ │ │ Accessible from: Settings, Render, Web │ │
206
297
  │ └──────────────────────────────────────────────────────────┘ │
207
-
208
- │ ┌─────────────────────┐ ┌─────────────────────┐ │
209
- │ │ App Instance 1 │ │ App Instance 2 │ │
210
- │ │ instance store │ │ instance store │ │
211
- │ │ (Settings↔Render) │ │ (Settings↔Render) │ │
212
- │ └─────────────────────┘ └─────────────────────┘ │
213
- └────────────────────────────────────────────────────────────────┘
214
-
215
- ┌────────────────────────────────────────────────────────────────┐
216
- │ Physical Device │
298
+
217
299
  │ ┌──────────────────────────────────────────────────────────┐ │
218
- │ │ store().device │ │
219
- │ │ Local to this device only (Render only!) │ │
220
- │ │ (Cache, calibration, device-specific data) │ │
300
+ │ │ Application │ │
301
+ │ │ │ │
302
+ │ │ ┌────────────────────────────────────────────────────┐ │ │
303
+ │ │ │ store().application │ │ │
304
+ │ │ │ Shared across ALL instances of this app │ │ │
305
+ │ │ │ (API keys, account-wide settings) │ │ │
306
+ │ │ │ Accessible from: Settings, Render, Web │ │ │
307
+ │ │ └────────────────────────────────────────────────────┘ │ │
308
+ │ │ │ │
309
+ │ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │
310
+ │ │ │ Device │ │ Instance │ │ │
311
+ │ │ │ store().device │ │ store().instance │ │ │
312
+ │ │ │ Render only! │ │ Settings ↔ Render │ │ │
313
+ │ │ │ (Cache, calibration)│ │ NOT in Web │ │ │
314
+ │ │ └──────────────────────┘ └──────────────────────┘ │ │
221
315
  │ └──────────────────────────────────────────────────────────┘ │
222
316
  └────────────────────────────────────────────────────────────────┘
223
-
224
- ┌────────────────────────────────────────────────────────────────┐
225
- │ store().shared('namespace') │
226
- │ Inter-app communication (any app can read/write) │
227
- │ (Weather data sharing, event broadcasting) │
228
- └────────────────────────────────────────────────────────────────┘
229
317
  ```
230
318
 
231
319
  ## Development Workflow
@@ -236,6 +324,7 @@ export function App() {
236
324
  Both the render and settings mounts points are visible in the development host.
237
325
  The Render mount point is presented in a resizable pane.
238
326
  The Settings mount point shows in the right sidebar.
327
+ The Web mount point (if configured) appears as a tab in the development host, displayed in the same area as the other mount points.
239
328
 
240
329
 
241
330
  ### Build & Deploy
@@ -243,10 +332,6 @@ The Settings mount point shows in the right sidebar.
243
332
  ```bash
244
333
  # Build production
245
334
  npm run build
246
-
247
- # Deploy via Git
248
- git add . && git commit -m "Update" && git push
249
- # GitHub integration auto-deploys
250
335
  ```
251
336
 
252
337
  ## Common Patterns
@@ -265,14 +350,6 @@ return <WeatherDisplay city={city} />
265
350
 
266
351
  ## Debugging
267
352
 
268
- ### Check Current Path
269
-
270
- ```typescript
271
- console.log('Current path:', window.location.pathname)
272
- console.log('Is Settings:', window.location.pathname === '/settings')
273
- console.log('Is Render:', window.location.pathname === '/render')
274
- ```
275
-
276
353
  ### Verify SDK Configuration
277
354
 
278
355
  ```typescript
@@ -39,10 +39,10 @@ ReactDOM.createRoot(document.getElementById('root')!).render(<App />)
39
39
  **Solution:**
40
40
  ```typescript
41
41
  // WRONG - in Settings.tsx
42
- const [, value, setValue] = useMyState() // using createUseDeviceStoreState
42
+ const [isLoading, value, setValue] = useMyState() // using createUseDeviceStoreState
43
43
 
44
44
  // CORRECT - use instance scope
45
- const [, value, setValue] = useMyState() // using createUseInstanceStoreState
45
+ const [isLoading, value, setValue] = useMyState() // using createUseInstanceStoreState
46
46
  ```
47
47
 
48
48
  **Scope guide:**
@@ -334,15 +334,15 @@ import { media } from '@telemetryos/sdk'
334
334
  import { useFolderIdState, useIntervalState } from '../hooks/store'
335
335
 
336
336
  export default function Render() {
337
- const [, folderId] = useFolderIdState()
338
- const [, interval] = useIntervalState() // seconds
337
+ const [isLoadingFolder, folderId] = useFolderIdState()
338
+ const [isLoadingInterval, interval] = useIntervalState() // seconds
339
339
 
340
340
  const [images, setImages] = useState<string[]>([])
341
341
  const [currentIndex, setCurrentIndex] = useState(0)
342
342
 
343
343
  // Load images
344
344
  useEffect(() => {
345
- if (!folderId) return
345
+ if (isLoadingFolder || !folderId) return
346
346
 
347
347
  media().getAllByFolderId(folderId).then(content => {
348
348
  const urls = content
@@ -15,8 +15,10 @@ Listen for these indicators during requirements gathering:
15
15
  - "Kiosk and display" or "control panel and viewer"
16
16
  - "Different devices need different views of the same data"
17
17
  - Data that's organized by location, department, topic, or similar grouping
18
+ - "Staff need to manage/control from their phone or browser"
19
+ - "Public signup form" or "public status page" for the same data
18
20
 
19
- If ANY of these come up, this is a multi-mode app.
21
+ If ANY of these come up, this is a multi-mode app. If staff or the public need browser access outside the device, it also needs a **web mount point**.
20
22
 
21
23
  ---
22
24
 
@@ -80,12 +82,26 @@ Multi-mode apps use a 3-tier store pattern:
80
82
  └─────────────────────────────────────────────────────────────┘
81
83
  ```
82
84
 
85
+ With a web mount point, add a fourth access path:
86
+
87
+ ```
88
+ ┌─────────────────────────────────────────────────────────────┐
89
+ │ Web Mount Point (browser — staff or public) │
90
+ │ Entity from URL params, NOT instance store │
91
+ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐ │
92
+ │ │ Reads/writes │ │ Same │ │ Same namespace │ │
93
+ │ │ namespace │ │ store hooks │ │ helper function │ │
94
+ │ └──────────────┘ └──────────────┘ └─────────────────────┘ │
95
+ └─────────────────────────────────────────────────────────────┘
96
+ ```
97
+
83
98
  **How it connects:**
84
99
 
85
- 1. Admin creates locations in Settings (application scope)
86
- 2. Each device selects a mode AND a location (instance scope)
87
- 3. All devices on the same location share the same data (dynamic namespace scope)
100
+ 1. Admin creates entities in Settings (application scope)
101
+ 2. Each device selects a mode AND an entity (instance scope)
102
+ 3. All devices on the same entity share the same data (dynamic namespace scope)
88
103
  4. A kiosk at "Location A" and a display at "Location A" see the same queues
104
+ 5. Web views navigate to entities via URL and read/write the same namespace data
89
105
 
90
106
  ---
91
107
 
@@ -240,6 +256,75 @@ export function Render() {
240
256
 
241
257
  ---
242
258
 
259
+ ## Web Mount Point Integration
260
+
261
+ When your multi-mode app needs a browser-accessible interface (operator desk, public form, staff dashboard), add a web mount point. The web view accesses the **same entity-scoped data** as Render, but gets the entity from URL params instead of instance store.
262
+
263
+ ### Entity from URL vs Instance Store
264
+
265
+ ```typescript
266
+ // Render — entity from instance store (admin picks which entity this device shows)
267
+ const [isLoading, selectedLocation] = useSelectedLocationStoreState()
268
+ if (isLoading) return <div>Loading...</div>
269
+ const ns = locationNamespace(selectedLocation || 'Location A')
270
+
271
+ // Web — entity from URL params (user navigates via links)
272
+ const { locationName } = useParams()
273
+ const ns = locationNamespace(decodeURIComponent(locationName || ''))
274
+
275
+ // Same namespace → same data → real-time sync
276
+ ```
277
+
278
+ ### Web Routing Maps to Entities
279
+
280
+ Web routes use the app name as base path, with entity-driven sub-routes. Choose path segments that match your domain:
281
+
282
+ ```typescript
283
+ // App.tsx — add web routes alongside render and settings
284
+ const router = createBrowserRouter([
285
+ { path: '/render', Component: Render },
286
+ { path: '/settings', Component: Settings },
287
+ { path: '/my-app', Component: WebEntities },
288
+ { path: '/my-app/locations/:locationName', Component: WebDetail },
289
+ { path: '/my-app/locations/:locationName/counters/:counterId', Component: WebOperator },
290
+ ])
291
+ ```
292
+
293
+ ### Store Access in Web
294
+
295
+ Web views can only use **application** and **dynamic namespace** store hooks. No instance or device store (there's no device/instance context in the browser):
296
+
297
+ ```typescript
298
+ // ✅ Works in web
299
+ const [isLoading, locations] = useLocationsStoreState() // application scope
300
+ const [isLoadingCounters, counters, setCounters] = useCountersStoreState(ns, 250) // dynamic namespace
301
+ if (isLoading || isLoadingCounters) return <div>Loading...</div>
302
+
303
+ // ❌ NOT available in web
304
+ const [isLoading, mode] = useModeStoreState() // instance scope — no context
305
+ const [isLoading, uiScale] = useUiScaleStoreState() // instance scope — no context
306
+ ```
307
+
308
+ ### Config
309
+
310
+ Add the web mount point to `telemetry.config.json`:
311
+
312
+ ```json
313
+ {
314
+ "name": "my-app",
315
+ "useSpaRouting": true,
316
+ "mountPoints": {
317
+ "render": "/render",
318
+ "settings": "/settings",
319
+ "web": "/my-app"
320
+ }
321
+ }
322
+ ```
323
+
324
+ See `tos-web-ui-design` for complete web view design patterns.
325
+
326
+ ---
327
+
243
328
  ## Settings Organization
244
329
 
245
330
  Order Settings sections intentionally:
@@ -357,3 +442,11 @@ Before implementing a multi-mode app, confirm:
357
442
  - [ ] Render view loads all hooks before branching on mode
358
443
  - [ ] Settings ordered: mode → entity selector → config → entity management
359
444
  - [ ] Entity management has add/rename/remove with at-least-one validation
445
+
446
+ If using a web mount point, also confirm:
447
+
448
+ - [ ] `telemetry.config.json` has `"web": "/app-name"` and `useSpaRouting: true`
449
+ - [ ] Web routes registered in App.tsx under the app name
450
+ - [ ] Web views get entity from URL params (not instance store)
451
+ - [ ] Web views only use application and dynamic namespace store hooks
452
+ - [ ] Entity names are URL-encoded in links and decoded in components
@@ -11,7 +11,7 @@ Use this skill at the START of any new TelemetryOS application project. Gather c
11
11
 
12
12
  **IMPORTANT: This is a conversation, not a survey.** Ask questions one phase at a time. Wait for answers before proceeding. Use earlier answers to skip irrelevant questions.
13
13
 
14
- **All TelemetryOS apps use `@telemetryos/sdk`** with `render` and `settings` mount points. The key distinction is whether the render view is display-only or includes interactive elements.
14
+ **All TelemetryOS apps use `@telemetryos/sdk`** with `render` and `settings` mount points. Some apps add a third `web` mount point for browser-accessible interfaces. The key distinctions are: whether the render view is display-only or interactive, and whether a web interface is needed.
15
15
 
16
16
  ---
17
17
 
@@ -51,6 +51,14 @@ Wait for their answer. Their response will tell you:
51
51
  - "different devices need different views of the same data"
52
52
  - data organized by location, department, topic, or similar grouping
53
53
 
54
+ **Web Interface Indicators** (browser-accessible, outside the device):
55
+ - "staff manage from their phone" or "operator desk"
56
+ - "public signup form", "public status page", "public display"
57
+ - "access from any browser", "not on the device"
58
+ - "management console" or "control panel in the browser"
59
+ - **Result:** Add a web mount point alongside render and settings
60
+ - Web mount point always pairs with multi-mode architecture
61
+
54
62
  ### Confirm Interaction Model
55
63
 
56
64
  Before proceeding to Phase 2, explicitly state your understanding:
@@ -64,6 +72,9 @@ Before proceeding to Phase 2, explicitly state your understanding:
64
72
  **For Multi-Mode App:**
65
73
  > "Got it — this is a multi-mode app. Different devices will show different views: [Mode A description] and [Mode B description]. They'll share data scoped to a [entity]. Let me gather requirements for each mode."
66
74
 
75
+ **For Multi-Mode App with Web Interface:**
76
+ > "Got it — this is a multi-mode app with a web interface. Devices will show [Mode A] and [Mode B] on screen, and [staff/the public] will access a [description] through a browser. All views share data scoped to a [entity]. Let me gather requirements for each interface."
77
+
67
78
  Wait for confirmation before proceeding.
68
79
 
69
80
  ---
@@ -111,6 +122,21 @@ Ask conversationally, one at a time, based on what you already know:
111
122
  **Rotation/Playlist:**
112
123
  - "Does content rotate between different views, or stay on one view?"
113
124
 
125
+ #### For Web Interface Apps (if identified in Phase 1):
126
+
127
+ **Audience:**
128
+ - "Who uses the web interface? Staff, operators, the public?"
129
+
130
+ **Workflow:**
131
+ - "Walk me through what someone does in the web interface."
132
+ - Listen for: navigation flow, what actions they perform, what data they see/modify
133
+
134
+ **Navigation Depth:**
135
+ - "How many levels of navigation? For example: select a location → select a counter → manage queue"
136
+
137
+ **Actions:**
138
+ - "What actions can users perform? Creating, updating, triggering events?"
139
+
114
140
  ### Capture User Stories
115
141
 
116
142
  Based on their answers, document user stories:
@@ -134,6 +160,16 @@ User Story: Render View
134
160
  - onClick handlers needed for: [list interactive elements]
135
161
  ```
136
162
 
163
+ **For Web Interface:**
164
+ ```
165
+ User Story: Web View
166
+ - As a [staff member / public user], I open the web interface
167
+ - I see [entity list / landing page]
168
+ - I navigate to [entity] → [sub-item] → [detail view]
169
+ - I can [list actions: call next, submit form, view status]
170
+ - Changes I make are reflected on device displays in real-time
171
+ ```
172
+
137
173
  ### DO NOT Ask About Data Sources Yet
138
174
 
139
175
  At this phase:
@@ -309,6 +345,8 @@ For each setting identified, record:
309
345
 
310
346
  **Only if Phase 1 identified a multi-mode app.**
311
347
 
348
+ If both multi-mode Render AND a web interface were identified, the web mount point provides the browser-accessible interface (operator desk, public form) while Render modes handle the device displays. Both share entity-scoped data through the same dynamic namespace.
349
+
312
350
  ### Step 1: Identify the Modes
313
351
 
314
352
  Confirm the distinct Render views:
@@ -348,6 +386,16 @@ For each content element from Phase 2, determine if it's:
348
386
 
349
387
  Ask: "Are there any settings that only apply to one mode? For example, audio chime only on the display, or touch feedback only on the kiosk?"
350
388
 
389
+ ### Step 5: Web Mount Point (If Applicable)
390
+
391
+ If a web interface was identified in Phase 1:
392
+
393
+ - Confirm the web view's purpose: "The web interface will be a [operator desk / public form / staff dashboard] — is that right?"
394
+ - Confirm audience: "Is this for staff only, or publicly accessible?"
395
+ - Map the navigation hierarchy to the organizing entity: "Users will select a [entity] first, then drill into [sub-items]?"
396
+ - Confirm what actions the web view performs on the shared data
397
+ - Note: Web views only access application and dynamic namespace store — no instance or device store
398
+
351
399
  ### Reference: Multi-Mode Store Pattern
352
400
 
353
401
  ```typescript
@@ -374,9 +422,9 @@ After gathering all requirements, provide a structured summary:
374
422
  # [App Name] Requirements
375
423
 
376
424
  ## Interaction Model
377
- **[Display-Only | Interactive | Multi-Mode]**
425
+ **[Display-Only | Interactive | Multi-Mode | Multi-Mode + Web]**
378
426
  - SDK: `@telemetryos/sdk`
379
- - Mount Points: `render` (display), `settings` (config UI)
427
+ - Mount Points: `render` (display), `settings` (config UI)[, `web` (browser interface)]
380
428
  - Interaction: [Display-only with subscriptions | Interactive with onClick handlers | Multi-mode with entity-scoped data]
381
429
 
382
430
  ## Vision
@@ -418,6 +466,14 @@ After gathering all requirements, provide a structured summary:
418
466
  **Entity-Scoped Data:** [list shared data keys that use dynamic namespace]
419
467
  **Mode-Specific Settings:** [list any settings that only apply to one mode]
420
468
 
469
+ ## Web Mount Point (if applicable)
470
+
471
+ **Purpose:** [operator desk | public form | staff dashboard]
472
+ **Audience:** [staff only | public]
473
+ **Navigation:** [entity list] → [sub-item list] → [detail/action view]
474
+ **Actions:** [list what users can do: call next, submit form, view status]
475
+ **Store Access:** application scope (entity list), dynamic namespace (entity-scoped data)
476
+
421
477
  ## Store Keys (Settings Configuration)
422
478
 
423
479
  | Key | Category | Scope | Type | Default | UI Component | Required? |
@@ -455,7 +511,15 @@ After gathering all requirements, provide a structured summary:
455
511
  - Error handling
456
512
  - Refresh logic
457
513
 
458
- 5. **Polish**
514
+ 5. **Web View** (views/Web.tsx) — if using web mount point
515
+ - Add `"web": "/app-name"` and `useSpaRouting: true` to telemetry.config.json
516
+ - Register web routes in App.tsx under app name
517
+ - Entity list view (application store)
518
+ - Entity detail view (dynamic namespace store)
519
+ - Action/operator view with store mutations
520
+ - Standard CSS styling (no UI scaling, no SDK settings components)
521
+
522
+ 6. **Polish**
459
523
  - Responsive scaling (rem units)
460
524
  - Loading states
461
525
  - [For Interactive] Touch feedback animations
@@ -499,7 +563,7 @@ If you need to clarify layout:
499
563
  - **Menu Board** → Display-only, media library, scheduled updates
500
564
  - **Wayfinding Kiosk** → Interactive, touch navigation, search functionality
501
565
  - **Data Dashboard** → Display-only, external API, refresh interval
502
- - **Queue Manager** → Multi-mode (kiosk + display), entity: location, shared queue data
566
+ - **Queue Manager** → Multi-mode (kiosk + display) + web (operator desk), entity: location, shared queue data
503
567
 
504
568
  ### What to Infer vs What to Ask
505
569
 
@@ -527,6 +591,7 @@ After gathering requirements, use these skills to implement:
527
591
  - **`tos-render-ui-design`** - Design the Render view layout (foundation - always read first)
528
592
  - **`tos-render-signage-design`** - Display-only render patterns (if building digital signage)
529
593
  - **`tos-render-kiosk-design`** - Interactive render patterns (if building kiosk)
594
+ - **`tos-web-ui-design`** - Web mount point design patterns (if building web interface)
530
595
  - **`tos-proxy-fetch`** - Implement external API calls (if needed)
531
596
  - **`tos-weather-api`** - Integrate weather data (if needed)
532
597
  - **`tos-media-api`** - Access media library (if needed)
@@ -185,7 +185,7 @@ import { createUseSharedStoreState } from '@telemetryos/sdk/react'
185
185
  export const useTempStoreState = createUseSharedStoreState<string>('temp', '', 'weather-data')
186
186
 
187
187
  // Weather app publishes
188
- const [, , setTemp] = useTempStoreState()
188
+ const [isLoading, , setTemp] = useTempStoreState()
189
189
  setTemp('72°F')
190
190
 
191
191
  // Other apps subscribe
@@ -421,7 +421,9 @@ return <WeatherDisplay city={city} />
421
421
  ### Conditional Rendering
422
422
 
423
423
  ```typescript
424
- const [, showForecast] = useShowForecastStoreState()
424
+ const [isLoading, showForecast] = useShowForecastStoreState()
425
+
426
+ if (isLoading) return <div>Loading...</div>
425
427
 
426
428
  return (
427
429
  <div>