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 <noreply@anthropic.com>
This commit is contained in:
parent
68bd308c3c
commit
2b44ff633d
1 changed files with 263 additions and 0 deletions
263
notes/bf-1bmg-js-ts.md
Normal file
263
notes/bf-1bmg-js-ts.md
Normal file
|
|
@ -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 |
|
||||
Loading…
Add table
Reference in a new issue