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.
- package/CLAUDE.md +55 -18
- package/admin.js +345 -273
- package/cards.js +65 -13
- package/core.js +571 -124
- package/dashboard_example.html +8 -4
- package/{CARDS_UX.md → docs/CARDS_UX.md} +81 -0
- package/frugaliot.css +173 -67
- package/graph.js +4 -11
- package/images/Battery0.png +0 -0
- package/images/Battery1.png +0 -0
- package/images/Battery2.png +0 -0
- package/images/Battery3.png +0 -0
- package/images/Battery4.png +0 -0
- package/images/Battery5.png +0 -0
- package/images/Battery6.png +0 -0
- package/index-embedded.html +11 -9
- package/index.html +2 -3
- package/login.html +9 -12
- package/login.js +224 -0
- package/package.json +1 -1
- package/scripts/make-battery-icons.js +110 -0
- package/test/cards.test.js +125 -1
- package/test/cardui.test.js +47 -0
- package/test/datatree.test.js +77 -0
- package/test/embedded.test.js +50 -0
- package/test/fixtures/config.json +574 -55
- package/test/grid.test.js +23 -0
- package/test/login.test.js +134 -0
- package/test/mock.html +4 -5
- package/test/mock.js +36 -15
- package/test/modules.test.js +1 -2
- package/test/page.test.js +37 -7
- package/test/permissions.test.js +34 -4
- package/test/smoke.test.js +15 -9
- package/test/snapshots/card-control-wired-back.txt +5 -5
- package/test/snapshots/card-control-wired-front.txt +6 -3
- package/test/snapshots/card-one-device-summary.txt +1 -0
- package/test/snapshots/control-wired.txt +11 -11
- package/test/snapshots/default-front.txt +8 -8
- package/test/snapshots/mixed-sensors.txt +7 -7
- package/test/snapshots/one-device.txt +38 -5
- package/test/snapshots/unknown-module.txt +3 -3
- package/widgets.js +19 -6
- package/index-old.html +0 -37
- package/index-project.html +0 -27
- package/nodeview.js +0 -433
- package/test/oldui.test.js +0 -40
- package/test/snapshots/no-readings.txt +0 -54
- package/webcomponents.js +0 -19
- /package/{CARDS_PLAN.md → docs/CARDS_PLAN.md} +0 -0
- /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
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
30
|
-
deferred work is L-n, and it is the place to argue with a choice rather than re-deciding
|
|
31
|
-
`CARDS_PLAN.md` is how it was built, including the bugs found on the way.
|
|
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.
|
|
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-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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 `
|
|
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 `
|
|
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
|
|