Skip to content

refactor(handlers): funnel HandlerRegistry's two maps through one insert/evict pair#149

Merged
zzal merged 1 commit into
mainfrom
feat/handler-registry-consolidation
Jul 6, 2026
Merged

refactor(handlers): funnel HandlerRegistry's two maps through one insert/evict pair#149
zzal merged 1 commit into
mainfrom
feat/handler-registry-consolidation

Conversation

@zzal

@zzal zzal commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

Closes Part I Wave 3 #3 of the arch+DX audit. Every handler lived in both the instance handlers dict and the static globalTable, hand-synced across four methods (register, remove, closeScope, deinit). A missed write in any of them leaked a globalTable entry (a handler outliving its component) or corrupted scope diagnostics.

Why not the audit's "one authoritative map"

Both maps are genuinely load-bearing for different consumers, so they can't be collapsed:

  • globalTable (static, all registries) backs dispatchGlobal — used by the single window.__swiflowDispatch JS callback, which receives only an integer handler ID and can't know which registry owns it.
  • The instance handlers dict backs dispatch — used by TestRenderer, per-instance so parallel host tests don't cross-contaminate through a shared static.

Approach — one mutation funnel

Instead of collapsing the maps, funnel all mutation through one private pair:

private func insert(_ handler: EventHandler, scope: ScopeID?)  // the ONE add path
private func evict(_ id: Int)                                  // the ONE remove path

register / remove / closeScope / deinit now route through these, so the two maps (and a handler's scope attribution) can never drift — one add path, one remove path. Every one of the four call sites got shorter.

Test plan

  • 4 new sync-invariant tests pinning the property the funnel guarantees: registerboth dispatch paths reach the handler; remove/closeScopeneither does; a released registry (deinit) drops its handlers from the global table (no leak).
  • All existing HandlerRegistry tests pass unchanged — the refactor is behavior-preserving.
  • Full Swift monorepo suite 1361/273 (+4).
  • Counter Playwright suite 11/11 — real-browser button clicks flow through dispatchGlobal → the refactored globalTable, exercising the production dispatch path end-to-end.

🤖 Generated with Claude Code

…ert/evict pair

Closes Part I Wave 3 #3 of the arch+DX audit. Every handler lived in BOTH the
instance `handlers` dict and the `static globalTable`, hand-synced across four
methods (register/remove/closeScope/deinit) — a missed write leaked a global
entry (a handler outliving its component) or corrupted scope diagnostics.

Both maps are genuinely load-bearing and can't be collapsed: `globalTable`
backs production JS dispatch via the single `window.__swiflowDispatch` callback
(ID-only, can't know the owning registry), while the instance dict backs
`TestRenderer`'s per-instance dispatch (so parallel host tests don't
cross-contaminate through a shared static). So instead of the audit's "one
authoritative map" option, this funnels ALL mutation through one private
`insert(_:scope:)` / `evict(_:)` pair. The two maps (and a handler's scope
attribution) now have exactly one add path and one remove path and can never
drift; register/remove/closeScope/deinit all got shorter.

Behavior-preserving. Adds 4 sync-invariant tests (register → both dispatch
paths reach it; remove/closeScope → neither; a released registry drops its
handlers from the global table). Verified: host suite 1361/273, counter e2e
11/11 (real-browser dispatch flows through the refactored globalTable).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

📦 Bundle size

Artifact Baseline This PR Δ
App.wasm 5.06 MB 5.15 MB +1.88%
App.wasm (gzip) 1.80 MB 1.84 MB +2.18%
JS runtime 54.5 KB 54.5 KB +0.00%
JS runtime (gzip) 11.3 KB 11.3 KB +0.00%
Total (gzip) 1.81 MB 1.85 MB +2.16%

✅ Within budget (≤5% growth allowed).

Baseline: Swift 6.3, WASM SDK 6.3-RELEASE, measured 2026-06-18.

@zzal zzal merged commit 6460764 into main Jul 6, 2026
6 checks passed
@zzal zzal deleted the feat/handler-registry-consolidation branch July 6, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant