2026-07-03 12:43:55 +02:00
# Optimization 12 — O(full-model) work on every edit, apply, and remote batch
**Severity:** performance (fine on demo boards; seconds-per-edit territory at 5– 10k items)
2026-07-06 08:35:18 +02:00
**Status:** TS items DONE 2026-07-03 (zod off the observer path, children index once — see [18 ](18-miss08-opts-12-13.md )); C++ item 1 delivered by [17 ](17-fixes-bugs-01-07.md ) batch 3 except the scalar-scan/legacy-wire retirement (goes with the legacy-wire removal)
2026-07-03 12:43:55 +02:00
## The costs, per hot path
**Every local commit (C++):**
- `flushDiff` runs `snapshotByUuid` — `itemToJson` for **every ** item on the
board/screen (json allocation per item) — plus a full map compare against
`g_baseline` (`pcbnew_embind.cpp:546-668` , eeschema `:374-453` ).
- The legacy scalar wire is still built and emitted alongside the v2 wire even though
production registers no `onDelta` listener (the json arrays are constructed
regardless; the `EM_ASM` no-ops).
**Every remote apply (C++):**
- `rebaseline()` at the end of `doApply` /`doApplyItems` — another full-model
`snapshotByUuid` .
**Every remote batch (TS):**
- The `observeDeep` handler builds `itemsView()` — `yToItem` for **every ** item, each
going through a zod `.parse` of its entire body tree
(`kicad-binding.ts:82-88,116` ). zod validation dominates; on a large board this is
by far the most expensive step, and it runs even for a single-item remote nudge.
- `deltaToItemsWire` 's `coveredByAncestor` /`renderItem` only need the delta's items
plus their ancestor chains and descendant subtrees — not the full view.
- `itemsWireToDelta` → `descendants()` rebuilds the full children index per wire item
(`items-wire.ts:63-83` ) → O(n·m) for an m-item wire.
**Drift check:** full scratch save + `fileToDoc` + `yToDoc` + `docDelta` — but only
every 50 doc updates and at unload; acceptable as designed. (The `beforeunload` check
is synchronous full-model work and will add visible tab-close latency on big boards —
worth a size guard, not a redesign.)
## Fixes, in leverage order
1. **Dirty-set + blob-hash diffing on the C++ side ** — the same change bug
[04 ](04-bug-lossy-change-detection.md ) needs for correctness. Collect touched-item
uuids from the listener callbacks (currently discarded), lift children to roots,
and post-settle compare only those roots' blob hashes against a uuid→hash baseline.
Replaces both full snapshots (flush AND rebaseline — see
[05 ](05-bug-rebaseline-swallows-local-edits.md ) for the targeted-rebaseline tie-in)
and retires the scalar snapshot + legacy emit entirely. Per-edit cost drops from
O(board) to O(edit).
2. **Kill zod on the TS hot path. ** `yToItem` 's schema parse guards against malformed
Y content, but the observer path re-validates the * entire * model on every batch.
Either:
- cache the materialized `KicadItem` per item Y.Map (WeakMap keyed by the Y.Map,
invalidated from the event's changed keys), so a batch only converts touched
items + the subtrees `renderItem` walks; or
- validate at trust boundaries only (wire parse already zod-validates; Y reads can
use a cheap structural cast) and keep zod for seed/materialize paths.
3. **Scope the view to the delta. ** `deltaToItemsWire` needs `view` for ancestor
chains and descendant rendering; build it lazily (resolve `items.get(uuid)` on
demand) instead of materializing the whole map up front.
4. **Index children once per conversion. ** Build the parent→children map once per
`itemsWireToDelta` call, not per wire item.
Items 2– 4 are contained TS changes; item 1 is the structural one and pairs with the
bug-04/05 fixes — do them as one piece of work.