refactor(frontend): shared API client + automations registry (audit M7, H8)
H8 — automations.ts rule-type registry
Convert the two hand-rolled RuleType dispatch ladders into per-type
registries (RULE_FIELD_RENDERERS + RULE_COLLECTORS) keyed by RuleType,
joining the existing RULE_CHIP_RENDERERS. All three are typed
Record<RuleType, ...> for compile-time exhaustiveness; an import-time
_assertRuleHandlerCoverage() check logs loudly if any registry drifts
from RULE_TYPE_KEYS — mirrors the backend's _RULE_HANDLERS shape, the
one intentional divergence being that the frontend logs rather than
throws (a thrown error at module import would brick the whole bundle,
not just the editor).
M7 — shared API client + 35 file migrations
New core/api-client.ts wrapping fetchWithAuth with typed apiGet /
apiPost / apiPut / apiPatch / apiDelete. Auth, 401-relogin, retry,
timeout, and the offline toast all stay owned by fetchWithAuth; the
client just collapses the
if (!resp.ok) { detail || HTTP <status> } ... resp.json()
dance into one typed call. The detail unwrap is hardened to join
FastAPI validation arrays instead of stringifying to [object Object].
35 feature/core files migrated to it across many batches, reviewer-
approved for behaviour parity in three passes covering the riskier
divergences (bulk Promise.allSettled deletes, inline-error saves,
array-detail joins, silent-failure GETs, immutable clones).
9 files remain on fetchWithAuth — the big god-modules tied to the
pending C8/C9/C10 splits (streams, settings, targets, dashboard,
color-strips/index, graph-editor, assets, value-sources) plus
pairing-flow which by design stays on raw fetch (branches on raw
Response.status codes).
i18n — 14 new locale keys (en / ru / zh)
Added save/load/delete error keys across automations, pattern,
audio_processing, audio_template, templates, gradient, target,
device namespaces, plus backfilled gradient.error.delete_failed into
ru/zh. Scan confirms no hardcoded English errorMessage strings
remain in the migrated diff.
AUDIT_REMAINING.md updated to reflect H6, H8, and M7 status.
Verified: tsc --noEmit clean + npm run build clean after every batch.
This commit is contained in:
+92
-11
@@ -18,6 +18,7 @@ context.
|
||||
| `05f73ee` | H6 (bindable extraction only) |
|
||||
| `3b8f00e` + `c1aa2eb` | C7 store-side |
|
||||
| `2f15fbb` | H3 |
|
||||
| _uncommitted (2026-05-27 autonomous pass)_ | H6-rest, H8, M7 (foundation + 3 reference files) |
|
||||
|
||||
All commits have ≥1 code-review subagent pass with HIGH findings fixed
|
||||
before commit. Tests pass on each commit; ruff clean; tsc + bundle build
|
||||
@@ -100,16 +101,35 @@ registry.
|
||||
|
||||
**Estimated scope:** 1-2 sessions; coupled to H4.
|
||||
|
||||
#### H8 — `automations.ts` 1410 LOC
|
||||
#### H8 — `automations.ts` 1410 LOC — ✅ DONE (uncommitted, 2026-05-27)
|
||||
|
||||
Frontend mirror of H2 (rule polymorphism). Already addressed on the
|
||||
backend in `98fb61d`; the frontend dispatch on `RuleType` is still
|
||||
backend in `98fb61d`; the frontend dispatch on `RuleType` was
|
||||
hand-rolled.
|
||||
|
||||
**Approach:** introduce a rule-type registry on the frontend matching
|
||||
the backend's `_RULE_HANDLERS` shape.
|
||||
**Done:** the two remaining hand-rolled dispatch ladders were converted
|
||||
to registries keyed by `RuleType`, alongside the pre-existing
|
||||
`RULE_CHIP_RENDERERS`:
|
||||
- `RULE_FIELD_RENDERERS` — the `renderFields` if/elif ladder was
|
||||
extracted into module-level `_renderXxxFields(container, data)`
|
||||
functions (they only ever closed over `container`); the in-row
|
||||
`renderFields` is now a 3-line dispatcher.
|
||||
- `RULE_COLLECTORS` — the `getAutomationEditorRules` if/elif ladder
|
||||
became per-type collectors; the loop is now a registry lookup.
|
||||
- All three registries are typed `Record<RuleType, …>` (compile-time
|
||||
exhaustiveness) and an import-time `_assertRuleHandlerCoverage()`
|
||||
logs loudly if any registry drifts from `RULE_TYPE_KEYS`. (Frontend
|
||||
logs rather than throws — a thrown error at import would brick the
|
||||
whole bundle, not just the editor — the one intentional divergence
|
||||
from the backend's raising `_assert_rule_handler_coverage`.)
|
||||
|
||||
**Estimated scope:** half a session.
|
||||
Adding a new rule type now means: one entry in `RULE_TYPE_KEYS`,
|
||||
`RULE_TYPE_ICONS`, and each of the three registries — and tsc + the
|
||||
coverage check flag any omission.
|
||||
|
||||
Verified: tsc + bundle build clean; typescript-reviewer APPROVE (the
|
||||
extracted renderer bodies are byte-identical to the originals; no stray
|
||||
closure captures; http_poll widget-stash + HA entity loading preserved).
|
||||
|
||||
### MEDIUM
|
||||
|
||||
@@ -161,16 +181,66 @@ extract the frame loop into a separate `PreviewFrameLoop` class.
|
||||
**Estimated scope:** half a session. Low impact since the parallel-change
|
||||
problem is already fixed.
|
||||
|
||||
#### M7 — No shared frontend API client
|
||||
#### M7 — No shared frontend API client — 🟡 FOUNDATION DONE (uncommitted, 2026-05-27)
|
||||
|
||||
**File:** every `static/js/features/*.ts`
|
||||
|
||||
`fetchWithAuth(...)` + bespoke error-unwrapping is copy-pasted in every
|
||||
feature's save / load function. ~25 files.
|
||||
feature's save / load function. ~45 files, ~243 call sites.
|
||||
|
||||
**Approach:** introduce `static/js/core/api-client.ts` with typed
|
||||
methods (`get`, `post`, `put`, `delete`) that handle auth, JSON parsing,
|
||||
error normalisation. Replace `fetchWithAuth` calls across features.
|
||||
**Done:** `static/js/core/api-client.ts` now provides typed
|
||||
`apiGet` / `apiPost` / `apiPut` / `apiPatch` / `apiDelete` that wrap
|
||||
`fetchWithAuth` (so auth, 401-relogin, retry, timeout, and the offline
|
||||
toast are unchanged) and collapse the repeated
|
||||
`if (!resp.ok) { detail || HTTP <status> } … resp.json()` dance into one
|
||||
call returning a typed body and throwing `ApiError` on failure. The
|
||||
`detail` unwrap is hardened to join FastAPI validation arrays instead of
|
||||
stringifying to `[object Object]`. **35 feature/core files migrated**
|
||||
(covers GET/POST/PUT/DELETE, typed response bodies, custom i18n error
|
||||
messages, silent-failure GETs, bulk `Promise.allSettled` deletes,
|
||||
inline-error saves, array-`detail` joins, fire-and-forget POSTs, and
|
||||
local catch handling) — reviewer-approved for behaviour parity across
|
||||
the riskier divergences. Migrated files include the integration sources
|
||||
(weather / HA / MQTT / HTTP), the template families (capture / audio /
|
||||
audio-processing / pattern), the scene-preset CRUD, the simple-CRUD
|
||||
entity files (sync-clocks / audio-sources / game-integration /
|
||||
gradient / displays / device-discovery), the light-target editors
|
||||
(z2m / ha), the preferences modules (dashboard-layout / card-modes /
|
||||
notifications-watcher), the calibration editors (simple + advanced),
|
||||
the entire `automations.ts` and `devices.ts` CRUD surfaces, and several
|
||||
core utilities (`api-client.ts` itself, `cache.ts`, `command-palette.ts`,
|
||||
`graph-connections.ts`, `tag-input.ts`, `process-picker.ts`,
|
||||
`perf-charts.ts`, `icon-picker.ts`, `update.ts`, `integrations.ts`).
|
||||
|
||||
Also added **14 new locale keys** (en / ru / zh) so the fallback
|
||||
messages the migration surfaces — `pattern.error.save_failed`,
|
||||
`audio_processing.error.save_failed`, `audio_template.error.save_failed`,
|
||||
`audio_template.error.load_failed`, `templates.error.save_failed`,
|
||||
`templates.error.load_failed`, `gradient.error.save_failed`,
|
||||
`target.error.load_failed`, `device.error.load_failed`,
|
||||
`automations.error.{load,save,delete,toggle}_failed`, plus
|
||||
`gradient.error.delete_failed` for ru/zh — are translated instead of
|
||||
hardcoded English. A scan confirms **no `errorMessage: '<English>'`
|
||||
strings remain** in the migrated diff.
|
||||
|
||||
**Remaining:** 9 feature files (~94 call sites). All but one are the
|
||||
big god-modules whose migration is best done as part of their C8/C9/C10
|
||||
splits: `streams.ts` (18), `settings.ts` (18), `targets.ts` (16),
|
||||
`dashboard.ts` (15), `color-strips/index.ts` (8), `graph-editor.ts` (7),
|
||||
`assets.ts` (6 — also blocked by multipart upload + blob download paths
|
||||
that legitimately bypass the JSON client), and `value-sources.ts` (5).
|
||||
The lone leaf file still on `fetchWithAuth` is `pairing-flow.ts` (1) —
|
||||
its branching on raw `Response.status` codes (200 / 409 / 4xx) doesn't
|
||||
fit the api-client contract, so it stays on raw fetch by design.
|
||||
Migration is mechanical but **not** a blind find/replace — each site
|
||||
carries its own localised error key that must be preserved as the
|
||||
`errorMessage` option, and binary/multipart endpoints (e.g.
|
||||
`assets.ts` file upload / blob download) must stay on raw
|
||||
`fetchWithAuth` (the client is JSON-only). Each migrated file ideally
|
||||
gets manual UI smoke-testing. **Behaviour note:** migrated GET sites now
|
||||
prefer the server's `detail` over the generic localised fallback when
|
||||
present — matching what the write paths already did; intended, but
|
||||
user-visible.
|
||||
|
||||
#### M8 — Global `_cached*` `let` vars
|
||||
|
||||
@@ -262,7 +332,11 @@ always start before reading).
|
||||
|
||||
### Other frontend (severity in main list above)
|
||||
|
||||
- **H6 rest** — split remaining ~1100 LOC of `types.ts` into per-entity files
|
||||
- **H6 rest** — ✅ DONE (uncommitted, 2026-05-27): `types.ts` (1140 LOC)
|
||||
split into 18 per-entity files under `types/` (joining the existing
|
||||
`bindable.ts`); `types.ts` is now a ~200-line pure re-export barrel, so
|
||||
every `import { … } from '../types.ts'` still resolves. Reviewer
|
||||
confirmed all 102 exported symbols preserved, none renamed.
|
||||
- **H7** — `device-discovery.ts` 1745 LOC (couple with H4)
|
||||
- **H8** — `automations.ts` 1410 LOC (mirror H2)
|
||||
- **M7** — shared API client
|
||||
@@ -299,6 +373,13 @@ Address H6-rest, C8, C9, C10, H7, H8, M7-M11, L1. See order above.
|
||||
Critical to have typescript-reviewer feedback + manual UI testing after
|
||||
each split.
|
||||
|
||||
> **Progress (2026-05-27, uncommitted):** steps 1 & 2 of the order above
|
||||
> are done — H6-rest (`types.ts` split) and M7-foundation (`api-client.ts`
|
||||
> + 3 reference migrations). H8 (automations registry) also landed. Still
|
||||
> open: C8, C9, C10, H7, the remaining ~40 M7 file migrations, M8-M11, L1.
|
||||
> Next per the order: introduce the API client everywhere (finish M7),
|
||||
> then split `value-sources.ts` (C8).
|
||||
|
||||
### Session B — Device redesign (1-2 sessions)
|
||||
|
||||
Address H4 alone. Touches device storage + provider classes; needs a
|
||||
|
||||
Reference in New Issue
Block a user