frugal-iot-client 1.3.14 → 2.0.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 (51) hide show
  1. package/CLAUDE.md +55 -18
  2. package/admin.js +345 -273
  3. package/cards.js +65 -13
  4. package/core.js +571 -124
  5. package/dashboard_example.html +8 -4
  6. package/{CARDS_UX.md → docs/CARDS_UX.md} +81 -0
  7. package/frugaliot.css +173 -67
  8. package/graph.js +4 -11
  9. package/images/Battery0.png +0 -0
  10. package/images/Battery1.png +0 -0
  11. package/images/Battery2.png +0 -0
  12. package/images/Battery3.png +0 -0
  13. package/images/Battery4.png +0 -0
  14. package/images/Battery5.png +0 -0
  15. package/images/Battery6.png +0 -0
  16. package/index-embedded.html +11 -9
  17. package/index.html +2 -3
  18. package/login.html +9 -12
  19. package/login.js +224 -0
  20. package/package.json +1 -1
  21. package/scripts/make-battery-icons.js +110 -0
  22. package/test/cards.test.js +125 -1
  23. package/test/cardui.test.js +47 -0
  24. package/test/datatree.test.js +77 -0
  25. package/test/embedded.test.js +50 -0
  26. package/test/fixtures/config.json +574 -55
  27. package/test/grid.test.js +23 -0
  28. package/test/login.test.js +134 -0
  29. package/test/mock.html +4 -5
  30. package/test/mock.js +36 -15
  31. package/test/modules.test.js +1 -2
  32. package/test/page.test.js +37 -7
  33. package/test/permissions.test.js +34 -4
  34. package/test/smoke.test.js +15 -9
  35. package/test/snapshots/card-control-wired-back.txt +5 -5
  36. package/test/snapshots/card-control-wired-front.txt +6 -3
  37. package/test/snapshots/card-one-device-summary.txt +1 -0
  38. package/test/snapshots/control-wired.txt +11 -11
  39. package/test/snapshots/default-front.txt +8 -8
  40. package/test/snapshots/mixed-sensors.txt +7 -7
  41. package/test/snapshots/one-device.txt +38 -5
  42. package/test/snapshots/unknown-module.txt +3 -3
  43. package/widgets.js +19 -6
  44. package/index-old.html +0 -37
  45. package/index-project.html +0 -27
  46. package/nodeview.js +0 -433
  47. package/test/oldui.test.js +0 -40
  48. package/test/snapshots/no-readings.txt +0 -54
  49. package/webcomponents.js +0 -19
  50. /package/{CARDS_PLAN.md → docs/CARDS_PLAN.md} +0 -0
  51. /package/{FLASH_PLAN.md → docs/FLASH_PLAN.md} +0 -0
package/CLAUDE.md CHANGED
@@ -14,22 +14,32 @@ it loadable on its own, and it is maintained by reaching the display elements **
14
14
  | `widgets.js` | core | `mqtt-bar`, `mqtt-text`, `mqtt-toggle`, `mqtt-gauge`, `mqtt-slider`, `mqtt-color`, `mqtt-choosetopic` — all shadow DOM |
15
15
  | `graph.js` | core, widgets | `mqtt-graph`, `mqtt-graphdataset` — pulls Chart.js |
16
16
  | `cards.js` | core | the card UI: `mqtt-devicecard`, `mqtt-devicegrid`, `mqtt-projectback`, `mqtt-dashboard`, the layout store |
17
- | `nodeview.js` | core, widgets | the old node/group UI — **retires with `index-old.html`**; nothing new belongs here |
18
17
  | `flash.js` | core | `mqtt-flash` — pulls esptool-js |
19
- | `admin.js` | core | `mqtt-admin`, `mqtt-login`, `tabbed-display` |
18
+ | `admin.js` | core | `mqtt-admin` — one admin section, rendered per card by `cards.js` |
19
+ | `login.js` | core | `mqtt-login` — sign in, register, forgot/reset password; light DOM |
20
20
 
21
- Entry points: `index.html` → `dashboard.js` (the card UI, everything but `nodeview.js`);
22
- `index-old.html` → `webcomponents.js` (everything, and the only remaining user of it);
23
- `index-embedded.html` needs only `core.js` + `widgets.js` and must keep working.
21
+ Every page loads the modules it needs; there is no file that loads everything.
22
+
23
+ | Page | Loads |
24
+ |---|---|
25
+ | `index.html` | `dashboard.js` — the card UI |
26
+ | `login.html` | `login.js`, which imports only `core.js` — that is why `mqtt-login` is not in `admin.js` |
27
+ | `index-embedded.html`, `dashboard_example.html` | `core.js` + `widgets.js` + `graph.js` |
28
+
29
+ Retired with the pre-cards UI: `index-old.html`, `index-project.html`, `nodeview.js` and
30
+ `webcomponents.js` (the shim that loaded everything), `mqtt-admin`'s tab strip, and the wrapper's
31
+ `headless` attribute — the data tree is all it builds now. `mqtt-admin` renders one section, chosen
32
+ by its `section` attribute; the cards on a project's back make one per card.
24
33
 
25
34
  Dashboards (e.g. `dashboard_example.html`) are thin HTML pages importing selectively.
26
35
 
27
36
  ## Design documents
28
37
 
29
- `CARDS_UX.md` is the design for the card UI — every decision is numbered (D-n) with its reasoning,
30
- deferred work is L-n, and it is the place to argue with a choice rather than re-deciding it in code.
31
- `CARDS_PLAN.md` is how it was built, including the bugs found on the way. `FLASH_PLAN.md` and
32
- `HEADLESS_PLAN.md` cover their own areas.
38
+ `docs/CARDS_UX.md` is the design for the card UI — every decision is numbered (D-n) with its
39
+ reasoning, deferred work is L-n, and it is the place to argue with a choice rather than re-deciding
40
+ it in code. `docs/CARDS_PLAN.md` is how it was built, including the bugs found on the way.
41
+ `docs/FLASH_PLAN.md` and `HEADLESS_PLAN.md` cover their own areas. Comments throughout the code cite
42
+ these by bare filename (`see CARDS_UX.md 4.7`); they all live in `docs/` except `HEADLESS_PLAN.md`.
33
43
 
34
44
  ## Code style
35
45
 
@@ -93,7 +103,7 @@ Helper functions: `topicTwig(path)` → twig, `topicLeaf(path)` → leaf, `twigA
93
103
  - `mt.twig` — stored twig string (e.g. `sht/temperature`)
94
104
  - `mt.leaf` — getter, last segment of twig
95
105
  - `mt.topicPath` — getter, full path including node prefix
96
- - `mt.topicSetPath` — getter, write path (`…/set/…`); **throws when `this.node` is null** — only call on topics that are known to be part of the node hierarchy (i.e. `this.node` is set). Project-level or standalone topics (e.g. those created by an embedded `mqtt-graphdataset`) have no real node and must not reach this getter.
106
+ - `mt.topicSetPath` — getter, write path (`…/set/…`); **throws when `this.nodeMt` is null** — only call on topics that are part of the node hierarchy. Project-level topics have no node and must not reach this getter. Topics built by `standaloneTopic()` do have a `nodeMt` (a stub), so an embedded widget's set path resolves.
97
107
  - `mt.element` — back-reference to the DOM element currently bound to this topic (null in headless mode or before binding)
98
108
  - `el.mt` — forward-reference from a DOM element to its `MqttTopic`
99
109
 
@@ -159,10 +169,12 @@ HTMLElementExtended (from html-element-extended)
159
169
  MqttGroupFrugalIot the frugal_iot system group (name, battery, OTA…)
160
170
  MqttClient MQTT connection UI element
161
171
  MqttWrapper top-level entry point; fetches config, builds tree
162
- MqttLogin login/register form
163
172
  MqttAdmin admin panel (OTA, permissions, node table)
164
173
  LanguagePicker language selector
165
174
  TabbedDisplay generic tab UI
175
+ HTMLElementExtendedMinimum (light DOM - no shadow root, so frugaliot.css reaches inside)
176
+ MqttLogin sign in / register / forgot / reset, one card and four modes
177
+ MqttDeviceCard, MqttDeviceGrid, MqttProjectBack, MqttDashboard the card UI
166
178
  ```
167
179
 
168
180
  ### Adding a new display element
@@ -186,8 +198,8 @@ a control shows a rule. Then:
186
198
 
187
199
  ### The card UI
188
200
 
189
- - `mqtt-devicecard` and `mqtt-devicegrid` are **light DOM** (`HTMLElementExtendedMinimum`), so
190
- `frugaliot.css` reaches them directly. That needs html-element-extended ≥ 0.1.7 (`renderRoot`).
201
+ - `mqtt-devicecard`, `mqtt-devicegrid` and `mqtt-login` are **light DOM**
202
+ (`HTMLElementExtendedMinimum`), so `frugaliot.css` reaches them directly. That needs html-element-extended ≥ 0.1.7 (`renderRoot`).
191
203
  - A light-DOM element has no `<slot>`, and `renderAndReplace` **clears its own children** — so the
192
204
  grid renders once with `render0()` and mutates in place, and a card builds every child itself.
193
205
  - Widgets in a card are pre-bound (`el.mt = mt`) but deliberately **not** registered as `mt.element`:
@@ -233,6 +245,18 @@ if (prevBarMt) prevBarMt.element = null;
233
245
  prevBarMt = sensorMt;
234
246
  ```
235
247
 
248
+ ### Embedded widgets (no wrapper)
249
+
250
+ `index-embedded.html` puts an `mqtt-bar`/`mqtt-toggle`/`mqtt-graphdataset` on a page with nothing
251
+ else — no `mqtt-wrapper`, no discovery tree, no `/config.json`. `standaloneTopic(path, o)` in
252
+ `core.js` builds the one topic such a widget needs, giving it a stub `nodeMt` so `topicPath`,
253
+ `topicSetPath` and `groupMt` all resolve. It also sets `mt.standalone`, which is what makes
254
+ `canWrite` true: there is no permission list to consult, and the broker enforces the credentials on
255
+ the `mqtt-client` element instead. `test/embedded.test.js` guards this — it has broken twice
256
+ unnoticed.
257
+
258
+ Still not available on such a page: graph history (`addDataFrom` needs `server_config.logger.url`).
259
+
236
260
  ### Looking up topics
237
261
 
238
262
  Use `wrapper.projectMt.findTopic(fullPath)` — never fabricate a new `MqttTopic` with partial paths:
@@ -267,14 +291,27 @@ Each of these cost real time. They are not obvious from reading the code.
267
291
  `innerHTML`, `action`) as properties. **Any other function is silently put in `el.state`** and
268
292
  never wired up: `el({onpointerdown: fn})` does nothing at all. Use `addEventListener`.
269
293
  - **`@media (max-width: 1001px)` in `frugaliot.css`** inflates fonts and icons for the node/group UI,
270
- where a node is one wide block. Inside a card it makes everything wider than the card. Those rules
271
- read `var(--fi-chrome-font, …)`; the card page sets it to `1rem`. Anything new that is not
272
- `nodeview.js` should opt out the same way. Replace them properly when `nodeview.js` goes.
294
+ which is now retired — so these rules have no page they were drawn for. The card page opts out by
295
+ setting `--fi-chrome-font: 1rem`, which the rules read. They still reach the login page and the
296
+ embedded examples, so removing them is a visual change to those and wants looking at on a phone.
273
297
  - **`:host-context(details[open]) div { display: block }`** near the top of `frugaliot.css` has
274
298
  specificity (0,2,2) and overrides most rules for any div in a shadow root inside an open
275
299
  `<details>` — which is every widget in the old UI.
300
+ - **A card's own gestures eat keystrokes and drags meant for a widget inside it.** `mqtt-devicecard`
301
+ listens for `keydown` (space picks it up) and `pointerdown` (drag to reorder) on itself, so a text
302
+ field on the back had every space `preventDefault`ed and a mouse selection of its text lifted the
303
+ card. Both handlers now bail on `isEditableTarget(e)` from `core.js`, which reads
304
+ `composedPath()[0]` — the target a card sees has been retargeted to the widget host, so
305
+ `e.target.tagName` is `MQTT-TEXT`, never `INPUT`.
276
306
  - **Assets resolve against the module, not the document**: `CssUrl` and `ImagesUrl` use
277
307
  `import.meta.url`, or a page in a subdirectory 404s every stylesheet and icon.
308
+ - **A node reports in a burst**, publishing its whole topic set at once, so gaps between messages
309
+ are milliseconds. `MqttTopicNode.noteMessage` ignores anything closer than `REPORT_BURST_MS` when
310
+ learning the reporting interval — without that the interval collapses and every device reads as
311
+ offline seconds after reporting. Status is also floored by `MIN_REPORT_INTERVAL_MS`.
312
+ - **A device going quiet sends nothing**, so nothing prompts a re-render. `status` is derived rather
313
+ than timed, but a card still needs a slow interval to re-read it, or it shows whatever it showed
314
+ when the last message arrived.
278
315
  - **Timestamps go through `nowMs()`**, never `Date.now()`, or a replayed history collapses onto one
279
316
  instant and a graph has nothing to draw. `setClock()` is how tests decide what "now" is.
280
317
  - **Caching will lie to you.** `/node_modules` and the client directory are served
@@ -311,9 +348,9 @@ regress unnoticed; regenerate deliberately with `npm run test:update` and read t
311
348
 
312
349
  ## Internationalisation
313
350
 
314
- The master language table is `const languages` in `webcomponents.js`. Dashboard-specific strings are added via `addVocabulary(yamlString)` in the dashboard's `<script>`.
351
+ The master language table is `const languages` in `core.js`. Dashboard-specific strings are added via `addVocabulary(yamlString)` in the dashboard's `<script>`.
315
352
 
316
- Every `addVocabulary` block must include **all four** language sections (EN, FR, HI, ID). Strings absent from `webcomponents.js` must be explicitly added in the dashboard block — do not assume the core file covers them.
353
+ Every `addVocabulary` block must include **all four** language sections (EN, FR, HI, ID). Strings absent from `core.js` must be explicitly added in the dashboard block — do not assume the core file covers them.
317
354
 
318
355
  ### How a string gets translated
319
356