Two fixes surfaced by the Phase C gate review iterations:
# C-gate R1 BLOCK: partial listener registration not exception-safe
Five `add_listener?` calls in `mount()` were sequenced via the
question-mark operator. If registration #K returned `Err`, the
K-1 already-landed listeners would never be unregistered:
WebShell never reaches `Ok(WebShell { ... })` so its `Drop`
never runs, and the partial `listeners` Vec drops without
calling `remove_event_listener_with_callback` first — the
browser-held DOM callbacks then outlive their Closures, which
is dangling-listener UB at the wasm-bindgen boundary.
Fix: wrap all 5 registrations in an inner closure that consumes
`&mut Vec<Listener>` and returns `Result<(), JsValue>`. On Err
we drain the partial vec and unregister each listener with the
same body Drop uses, then return the error. Comment cites the
codex finding inline.
# C-gate R1 CONCERN + R3 BLOCK: incomplete named-key coverage
`map_key_value` covered Enter/Escape/Tab/Space/Backspace/Delete/
Arrow{Up,Down,Left,Right} only. jian's `NamedKey` enum has 31
variants total — Home, End, PageUp, PageDown, F1..F12, Shift,
Control, Alt, Meta, and CapsLock were all falling through to
`Unidentified(<key>)`.
`map_key_code` similarly missed Home, End, PageUp, PageDown, and
the 8 modifier physical codes (ShiftLeft / ShiftRight / etc).
Fix: extended both tables. After the fix, every NamedKey jian
exposes round-trips through map_key_value, and every
non-Unknown KeyCode variant round-trips through map_key_code
(F1-F12 stay Unknown because jian's KeyCode does NOT have F-key
variants — NamedKey covers them; comment in keyboard.rs
explains).
# Tests added (3 new round-trip tests, 22 → 25)
- `keyboard_navigation_keys_mapped` — Home / End / PageUp /
PageDown / CapsLock all map to Named(<variant>), not
Unidentified.
- `keyboard_function_keys_mapped` — F1..F12 round-trip via a
for-loop.
- `keyboard_modifier_keys_mapped_to_named_values` — Shift /
Control / Alt / Meta with both Left and Right `code` variants
assert both Named(<modifier>) on `key` and the matching
KeyCode::{Mod}{Left,Right} on `code`.
CapsLock specifically was the Round 3 NO-GO discovery — the
only NamedKey variant the Round 1 fix missed; Round 4 added it
+ the round-trip tests above.
Verification:
- `cargo test -p openpencil-shell-web --test dom_event_mapping` —
25/25 passing
- `cargo build -p openpencil-shell-web --target
wasm32-unknown-unknown --features skia --release` — green
- `bash tools/check-wasm-bundle.sh` — PASS:
- 0 env.* imports
- 621 151 bytes gzip = 59% of 1 MiB ceiling (+0.6 KiB vs
pre-fix; new key table entries are tiny)
- `bash tools/check-widget-boundary.sh` — PASS
Codex Phase C gate review: 5 rounds (gate-level, not commit-level).
Round 1 BLOCK + CONCERN, Round 2 sandbox-only BLOCK, Round 3
NO-GO (CapsLock + missing test coverage), Round 4 sandbox-only
BLOCK (source verdict clean), Round 5 GO with Phase B5 R2
precedent applied for sandbox-only build evidence.