@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.
- package/CHANGELOG.md +26 -0
- package/dist/commands/claude-code.d.ts +2 -0
- package/dist/commands/claude-code.js +29 -0
- package/dist/commands/init.js +22 -9
- package/dist/index.js +2 -0
- package/dist/services/create-project.d.ts +13 -0
- package/dist/services/create-project.js +188 -0
- package/dist/services/project-config.d.ts +3 -0
- package/dist/services/project-config.js +3 -0
- package/dist/services/run-server.js +48 -25
- package/dist/utils/template.d.ts +2 -0
- package/dist/utils/template.js +30 -0
- package/package.json +2 -2
- package/templates/{vite-react-typescript → claude-code}/CLAUDE.md +10 -3
- package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-architecture/SKILL.md +138 -61
- package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-debugging/SKILL.md +2 -2
- package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-media-api/SKILL.md +3 -3
- package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-multi-mode/SKILL.md +97 -4
- package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-requirements/SKILL.md +70 -5
- package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-store-sync/SKILL.md +4 -2
- package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-weather-api/SKILL.md +7 -6
- package/templates/claude-code/_claude/skills/tos-web-ui-design/SKILL.md +373 -0
- package/templates/vite-react-typescript/_gitignore +4 -2
- package/templates/vite-react-typescript/telemetry.config.json +2 -1
- package/templates/vite-react-typescript-web/_gitignore +32 -0
- package/templates/vite-react-typescript-web/assets/telemetryos-wordmark.svg +11 -0
- package/templates/vite-react-typescript-web/assets/tos-app.svg +12 -0
- package/templates/vite-react-typescript-web/index.html +15 -0
- package/templates/vite-react-typescript-web/package.json +24 -0
- package/templates/vite-react-typescript-web/src/App.tsx +25 -0
- package/templates/vite-react-typescript-web/src/hooks/store.ts +8 -0
- package/templates/vite-react-typescript-web/src/index.css +24 -0
- package/templates/vite-react-typescript-web/src/index.tsx +11 -0
- package/templates/vite-react-typescript-web/src/views/Render.css +67 -0
- package/templates/vite-react-typescript-web/src/views/Render.tsx +44 -0
- package/templates/vite-react-typescript-web/src/views/Settings.tsx +72 -0
- package/templates/vite-react-typescript-web/src/views/Web.css +105 -0
- package/templates/vite-react-typescript-web/src/views/Web.tsx +52 -0
- package/templates/vite-react-typescript-web/telemetry.config.json +16 -0
- package/templates/vite-react-typescript-web/tsconfig.json +19 -0
- package/templates/vite-react-typescript-web/vite.config.ts +18 -0
- /package/templates/{vite-react-typescript → claude-code}/AGENTS.md +0 -0
- /package/templates/{vite-react-typescript → claude-code}/_claude/settings.local.json +0 -0
- /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-proxy-fetch/SKILL.md +0 -0
- /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-render-kiosk-design/SKILL.md +0 -0
- /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-render-signage-design/SKILL.md +0 -0
- /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-render-ui-design/SKILL.md +0 -0
- /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-settings-ui/SKILL.md +0 -0
package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-architecture/SKILL.md
RENAMED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tos-architecture
|
|
3
|
-
description: Understand TelemetryOS
|
|
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
|
-
##
|
|
10
|
+
## Mount Points
|
|
11
11
|
|
|
12
|
-
Every
|
|
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
|
-
│ │
|
|
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
|
-
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
│
|
|
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
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (path === '/render') return <Render />
|
|
166
|
+
const router = createBrowserRouter([
|
|
167
|
+
{ path: '/render', element: <Render /> },
|
|
168
|
+
{ path: '/settings', element: <Settings /> },
|
|
169
|
+
])
|
|
114
170
|
|
|
115
|
-
|
|
171
|
+
export function App() {
|
|
172
|
+
return <RouterProvider router={router} />
|
|
116
173
|
}
|
|
117
174
|
```
|
|
118
175
|
|
|
119
|
-
|
|
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
|
|
124
|
-
import
|
|
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',
|
|
128
|
-
{ path: '/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
|
-
│
|
|
291
|
+
│ Account │
|
|
292
|
+
│ │
|
|
202
293
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
203
|
-
│ │ store().
|
|
204
|
-
│ │
|
|
205
|
-
│ │
|
|
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
|
-
│ │
|
|
219
|
-
│ │
|
|
220
|
-
│ │
|
|
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
|
package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-debugging/SKILL.md
RENAMED
|
@@ -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:**
|
package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-media-api/SKILL.md
RENAMED
|
@@ -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
|
package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-multi-mode/SKILL.md
RENAMED
|
@@ -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
|
|
86
|
-
2. Each device selects a mode AND
|
|
87
|
-
3. All devices on the same
|
|
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
|
package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-requirements/SKILL.md
RENAMED
|
@@ -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
|
|
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. **
|
|
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)
|
package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-store-sync/SKILL.md
RENAMED
|
@@ -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>
|