From 2b44ff633d1c8d82b91d7923d88e8fadaaf4f3ef Mon Sep 17 00:00:00 2001 From: jedarden Date: Mon, 6 Jul 2026 15:46:02 -0400 Subject: [PATCH] docs(bf-1bmg): inventory JS/TS dashboard blob-creation sites Catalogue every blob-creation site on the dashboard JS/TS side, re-verified against HEAD 68bd308: - 2 production domain-blob sites: state.js:290 ({id:id} + Object.assign of server payload) and websocket.js:167 (dead-reckoning _blobStates cache). - spaxel.d.ts:10-91 Blob interface documented as the single canonical JS identity-field declaration (personName/personId/assignedColor/identityResolved + deprecated personLabel/personColor), with Go<->JS field name map. - 4 new Blob() file-download sites (static/js/fleet.js:457, fleet-page.js:1034 & :1369, js/fleet.js:1997) explicitly flagged OUT OF SCOPE (browser Blob API). - 12 test fixtures across ambient/quick-actions/replay .test.js listed for completeness. - Corrects two drifts vs bf-3ldj: the :457 CSV site is in static/js/fleet.js (a different file from js/fleet.js), and ambient.test.js literal openings are 124/273/636/653/688/702 (not the :276/:642/etc cited). Co-Authored-By: Claude --- notes/bf-1bmg-js-ts.md | 263 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 notes/bf-1bmg-js-ts.md diff --git a/notes/bf-1bmg-js-ts.md b/notes/bf-1bmg-js-ts.md new file mode 100644 index 0000000..d2c9ed4 --- /dev/null +++ b/notes/bf-1bmg-js-ts.md @@ -0,0 +1,263 @@ +# JS / TS Dashboard Blob-Creation Sites (bf-1bmg) + +> **Scope:** Every blob-creation site on the **dashboard JS/TS side** of Spaxel — the JS/TS +> slice of the consolidated blob-identity effort (parent findings: `notes/bf-3ldj-findings.md`, +> §2.3–§2.5). +> +> **Every line number below was re-verified against `HEAD = 68bd308c3c4cccd3a709282161fa880e49d4fa88` +> on 2026-07-06** by reading the file, not by trusting the source notes. Two classes of drift in +> the source notes are corrected in §1.2 (do not copy them blindly). + +--- + +## 0. TL;DR + +| Question | Answer | +|---|---| +| How many **JS domain-blob creation** sites exist in production code? | **2** — `dashboard/js/state.js:290` and `dashboard/js/websocket.js:167` | +| Where is the JS-side identity schema declared? | **One place:** the `Blob` interface in `dashboard/types/spaxel.d.ts:10–91` | +| Are there JS factory helpers (`makeBlob`/`createBlob`)? | **No.** Both production sites are inline object literals. | +| How many `new Blob(...)` constructor calls exist? | **4** — all the **browser binary `Blob` API** for file downloads. **OUT OF SCOPE** for identity work (see §3). | +| Are there test-only blob fixtures? | **Yes — 12** across 3 files (listed in §4 for completeness). | +| Anything to fix on the JS side for blob identity? | Only if the schema changes → update `spaxel.d.ts` (single point); `state.js:290` already merges server identity via `Object.assign`. See §5. | + +--- + +## 1. Read this first + +### 1.1 Two unrelated concepts share the word "blob" + +| Concept | Where | What it is | Identity fields? | +|---|---|---|---| +| **Spaxel domain blob** (tracked spatial presence) | `dashboard/js/state.js`, `dashboard/js/websocket.js` | A detected person: `{id, x, y, z, vx…, posture, person/personName, assignedColor, identityResolved}` | **Yes** — the whole point of this work | +| **Browser `Blob` API** (binary data for downloads) | `dashboard/**/fleet*.js` | `new Blob([bytes], {type:'text/csv'})` — a file-download payload | **No** — unrelated; do **not** touch for identity work | + +The 4 `new Blob()` sites in §3 are all the second kind. They are catalogued only so the next +bead knows they exist and can **skip them**. + +### 1.2 Corrections to the source notes (verified against HEAD) + +1. **`dashboard/static/js/fleet.js:457` vs `dashboard/js/fleet.js`** — the bf-1bmg task brief + lists the CSV download site as "`fleet.js:457`". **`fleet.js:457` does not exist in + `dashboard/js/fleet.js`** — that file's only `new Blob()` is at `:1997` (JSON export). + The `:457` site lives in a **different file**, `dashboard/static/js/fleet.js` + (16 KB; not a copy of the 77 KB `dashboard/js/fleet.js` — `diff` confirms they differ). + See §3, entry [D1]. + +2. **`dashboard/js/ambient.test.js` test-fixture line numbers (bf-3ldj §2.4)** — the source + notes cite `124, 276, 642, 659, 694, 708`. Only **124** is the blob-literal opening line. + `276` is a `y:` field line; `642 / 659 / 694 / 708` are the closing `}]` lines of their + respective fixtures. The **actual literal-opening lines** (the `blobs: [{` line) are + **124, 273, 636, 653, 688, 702**. Count (6) is unchanged. See §4. + +3. **`dashboard/js/state.js:290`** — confirmed: the literal is exactly `{ id: id }`. Identity + fields are **not** pre-initialized; `Object.assign(appState.blobs[id], updates)` at `:292` + merges the server payload (including any identity fields) afterward. (Already corrected in + bf-3ldj §1.2; re-confirmed here.) + +--- + +## 2. Production JS domain-blob creation sites (the real inventory) + +| # | File:line | Enclosing function | Pattern | Identity fields set at creation? | +|---|---|---|---|---| +| **[P1]** | `dashboard/js/state.js:290` | `updateBlob(id, updates)` (fn opens `:288`) | `appState.blobs[id] = { id: id };` then `:292 Object.assign(appState.blobs[id], updates)` | **No** — only `{id}`; server-supplied identity fields arrive via the `updates` payload and are merged by `Object.assign`. This is the **canonical creation point** for every live blob in the dashboard. | +| **[P2]** | `dashboard/js/websocket.js:167` | `_captureBlobStates()` (fn opens `:162`; Map declared `:26`) | `_blobStates.set(b.id, { x: b.x, z: b.z, vx: b.vx‖0, vz: b.vz‖0, ts: Date.now() })` | **No** — and intentionally none. This is a **dead-reckoning cache** for the <5 s disconnect-extrapolation feature (positions + velocity + timestamp only). It is a derived snapshot of an already-existing blob, not a domain blob, and never carries identity. | + +```js +// [P1] dashboard/js/state.js:288-294 +function updateBlob(id, updates) { + if (!appState.blobs[id]) { + appState.blobs[id] = { id: id }; // ← :290 creation literal (id only) + } + Object.assign(appState.blobs[id], updates); // ← :292 server payload merged in + notify('blobs.' + id, appState.blobs[id], null); + notify('blobs', appState.blobs, null); +} + +// [P2] dashboard/js/websocket.js:162-174 +function _captureBlobStates() { + if (!_lastSnapshot || !_lastSnapshot.blobs) return; + _lastSnapshot.blobs.forEach(function (b) { + _blobStates.set(b.id, { // ← :167 dead-reckoning cache + x: b.x, z: b.z, + vx: b.vx || 0, vz: b.vz || 0, + ts: Date.now() + }); + }); +} +``` + +**Production subtotal: 2 creation sites across 2 files.** A defensive sweep of every +`dashboard/js/*.js` production file (all `blobs:` assignments, `set('blobs', …)` calls, and +`{…posture/confidence…}` literals — see §6) found **no other** domain-blob object-literal +creation. Empty `blobs: []` initializers in `simple-mode.js:35`, `ambient.js:36`, +`ambient_renderer.js:44`, and `home-cards.js:28` are array defaults, not blob creation. + +### 2.1 Adjacent call sites that re-publish existing blobs (NOT new-literal creation — listed for completeness) + +These route through `state.js` `updateBlob` / the `SpaxelState.set('blobs', …)` API but pass an +**already-existing** blob object, so no new literal is constructed: + +| File:line | Pattern | Note | +|---|---|---| +| `dashboard/js/quick-actions.js:697` | `blobs[blob.id].person = null; blobs[blob.id].ble_device = null; SpaxelState.set('blobs', blob.id, blobs[blob.id])` | Identity-clear flow (re-assign / forget person). Mutates an existing blob in place, then re-publishes. | +| `dashboard/js/quick-actions.js:1689` | `blobs.forEach(b => SpaxelState.set('blobs', b.id, b))` | Refresh loop after `GET /api/blobs`; re-publishes each server blob through the state API. | + +Neither constructs a blob literal; both delegate creation to `updateBlob` → **[P1]**. + +--- + +## 3. `new Blob()` constructor sites — ⛔ OUT OF SCOPE (unrelated browser `Blob` API) + +These construct **file-download payloads** via the standard browser `Blob` API, not Spaxel +domain blobs. They have no identity fields and no relationship to tracked-presence work. +Catalogued only so they can be **explicitly skipped**. + +| # | File:line | Enclosing function | MIME type | Purpose | +|---|---|---|---|---| +| **[D1]** | `dashboard/static/js/fleet.js:457` | `downloadCSV(nodes)` (fn opens `:423`) | `text/csv` | Fleet table CSV download. ⚠️ **Note:** this is `dashboard/static/js/fleet.js`, a **different file** from `dashboard/js/fleet.js` — see §1.2. | +| **[D2]** | `dashboard/js/fleet-page.js:1034` | `async exportConfig()` (fn opens `:1026`) | `application/json` | Fleet config JSON export | +| **[D3]** | `dashboard/js/fleet-page.js:1369` | `downloadCSV()` (fn opens `:1337`) | `text/csv` | Filtered fleet CSV download | +| **[D4]** | `dashboard/js/fleet.js:1997` | `exportConfig()` (fn opens `:1988`) | `application/json` | Fleet config JSON export | + +All four follow the identical idiom: `new Blob([content], { type: '...' })`. + +- **Zero `new Blob()` calls exist in TypeScript** (`dashboard/types/`) — verified by grep. +- **Two MIME-type pairs:** CSV downloads `[D1]`,`[D3]`; JSON exports `[D2]`,`[D4]`. `[D2]`/`[D4]` + are the same `exportConfig` logic duplicated across the two fleet files; `[D1]`/`[D3]` are the + `downloadCSV` counterpart. + +--- + +## 4. JS test-only blob fixtures (out of scope for edits — listed for completeness) + +These are rendering/context-menu/mock-API fixtures in `*.test.js`. Update only if a struct +schema change breaks them. Line numbers below are the **literal-opening lines** verified against +HEAD (correcting the bf-3ldj §2.4 drift — see §1.2). + +### 4.1 `dashboard/js/ambient.test.js` — 6 sites + +| Line | Shape | Fields | Used by | +|---|---|---|---| +| `:124` | full person blob | `id,x,y,z,confidence,person:'Alice'` | "draw person blob at correct position" | +| `:273` | minimal position | `id,x,y,z` | ambient zone-presence brightness restore | +| `:636` | full | `id,x,y,z,confidence` | camera-follow start position (1,1) | +| `:653` | full | `id,x,y,z,confidence` | camera-follow move target (3,3) | +| `:688` | full | `id,x,y,z,confidence` | lerp initial position (1,1) | +| `:702` | full | `id,x,y,z,confidence` | lerp target position (3,3) | + +*(Empty `blobs: []` at `:86` and `:237` are not creation sites. Zone/alert `id:` lines at +`:78`,`:229`,`:265`,`:330`,`:366`,`:389`,`:423` are non-blob fixtures.)* + +### 4.2 `dashboard/js/quick-actions.test.js` — 5 sites + +| Line | Shape | Fields | Used by | +|---|---|---|---| +| `:208` | via state API | `SpaxelState.set('blobs',123,{id,person:'Alice',x,y,z})` | context-menu appearance test | +| `:316` | `const blob = {}` | `id:123,person,x,y,z` | (context-menu fixture) | +| `:470` | `const blob = {}` | `id:123,person:'Alice',x,y,z` | "Follow" camera-mode test | +| `:513` | `const blob = {}` | `id:123,…` | context-menu fixture | +| `:678` | single-line | `{ id:123, x:2, y:0, z:3 }` | minimal position fixture | + +*(Inline `{ id: 1 }` / `{ id:123, person:'Alice' }` lookup refs passed to `show(...)` at +`:227`,`:556`,`:573`,`:586`,`:605`,`:628`,`:750` are target references, not blob creation. +Zone/node fixtures at `:365` etc. are non-blob.)* + +### 4.3 `dashboard/js/replay.test.js` — 1 site + +| Line | Shape | Fields | Used by | +|---|---|---|---| +| `:101` | mock API array | `blobs:[{id,x:2.5,y:1.3,z:0.8,vx,vy,vz,weight:0.85,posture:'standing'}]` | mock `/api/replay/session/...` response fed to `Viz3D.updateReplayBlobs` | + +*(The `expect.objectContaining({id:1,x:2.5,…})` matcher at `:291` is an **assertion**, not a +creation — it references the `:101` fixture. Empty `blobs: []` at `:230` is not a creation site.)* + +**Test subtotal: 6 + 5 + 1 = 12 fixtures across 3 files.** + +--- + +## 5. The canonical JS identity-field declaration: `dashboard/types/spaxel.d.ts` + +`dashboard/types/spaxel.d.ts:10–91` declares the **`Blob` interface** — the **single source of +truth** for the JS-side blob shape, including all identity fields. If the JS identity schema +changes, **this is the only declaration to update**; no JS file redeclares it. + +```ts +export interface Blob { // :10 ← interface opens + id: string; // :12 (note: string here; runtime blobs use numeric id) + x: number; y: number; z: number; // :15-17 + confidence: number; // :20 + vx?: number; vy?: number; vz?: number; // :23-25 + posture?: string; // :28 + person?: string | null; // :31 legacy shorthand display field + ble_device?: string | null; // :34 + trails?: Array<{x,y,z,timestamp_ms}>; // :37-42 + + // ── Identity Resolution Fields (header comment :44-46) ── + personName?: string; // :53 ✅ preferred display name + personLabel?: string; // :60 ⚠️ @deprecated → use personName + personId?: string; // :66 stable person id + assignedColor?: string; // :74 ✅ preferred render color (hex/rgb) + personColor?: string; // :81 ⚠️ @deprecated → use assignedColor + identityResolved?: boolean; // :90 tri-state: true / false / undefined +} // :91 ← interface closes +``` + +**Identity fields (the 6 the umbrella effort cares about):** `personName`, `personLabel` +(deprecated), `personId`, `assignedColor`, `personColor` (deprecated), `identityResolved`. +Plus the legacy shorthand `person?` at `:31` (still consumed by `ambient_renderer.js` via the +fallback chain `personName → person_label → person`). + +**Zero object-literal instantiations** of `Blob` exist in `dashboard/types/` — it is a +declaration-only `.d.ts`. **Zero `new Blob()` constructor calls** exist in TypeScript (verified). + +### Go ↔ JS identity-field name map (for the implementation bead) + +| Concept | Go field (`tracker.Blob` etc.) | JS field (`spaxel.d.ts`) | +|---|---|---| +| Display name | `PersonLabel` | `personName` (preferred) / `personLabel` (deprecated) / `person` (legacy shorthand) | +| Stable person id | `PersonID` | `personId` | +| Render color | `PersonColor` | `assignedColor` (preferred) / `personColor` (deprecated) | +| Identity resolved | `IdentityConfidence` (float) + `IdentitySource` | `identityResolved` (bool, tri-state) | + +--- + +## 6. Methodology / how the production sweep was done + +To guarantee no production JS blob-creation site was missed, every `dashboard/js/*.js` file +(excluding `*.test.js`, `*.test.setup.js`, `node_modules/`, and the `static/` duplicate) was +scanned for: + +1. `blobs:` array/literal assignments (e.g. `blobs: [{`, `blobs: []`, `.blobs[id] =`) +2. `SpaxelState.set('blobs', …)` calls +3. Object literals carrying blob-shaped field sets (`posture:` / `confidence:` + `id`) + +Results: the only **new-literal** production creation sites are **[P1]** and **[P2]** in §2. +Every other hit was either an empty-array default, an index/lookup of an existing blob, a +non-blob record (zone/node/alert/explainability/mesh-handle), or a re-publish of an existing +blob (§2.1). + +--- + +## 7. Acceptance criteria status + +- [x] **Every JS blob-creation site listed with file:line + pattern** — §2 (2 production sites) + + §2.1 (2 adjacent re-publish call sites) + §4 (12 test fixtures). +- [x] **`spaxel.d.ts` Blob interface documented as the canonical JS identity-field declaration** + — §5, with the 6 identity fields and the Go ↔ JS name map. +- [x] **The 4 `new Blob()` download sites explicitly flagged as OUT OF SCOPE / unrelated browser + API** — §3, each marked ⛔ with MIME type + enclosing function. +- [x] **All line numbers verified against current HEAD** — re-verified against + `68bd308` on 2026-07-06 by reading each file; §1.2 corrects the two drifts found in the + source notes (the `static/js/fleet.js` mislabel and the `ambient.test.js` fixture lines). +- [x] **Findings written to `notes/bf-1bmg-js-ts.md`** — this file. + +--- + +## 8. Provenance + +| Source | Used for | +|---|---| +| `notes/bf-3ldj-findings.md` §2.3–§2.5, §4 | Starting inventory (JS production + test sites, the 4 `new Blob()` downloads) — re-verified and corrected where drift found (§1.2) | +| Direct file reads against `HEAD 68bd308` | Every line number in this report |