pcbjam/docs/features/wx-dom-port/visual-notes.md
Istvan Matejcsok cee569dc5d fix: 🐛 kicad aui resize
 Closes: #20
2026-06-22 08:49:22 +02:00

302 lines
17 KiB
Markdown

# Cross-port visual comparison notes (canvas baseline vs DOM port)
Protocol: per feature, view `tests/baseline-screenshots/<name>.png` (canvas)
next to `tests/test-results/dom/<name>.png` (DOM). Expectation: same widget
geometry (within a few px), same text/content/state; only widget styling
(native control chrome, fonts antialiasing) may differ.
## Phase 2 (2026-06-10) — layout suite
`layout-01-loaded` and siblings, after the geometry-propagation and frame-bar
fixes:
- **Matches canvas:** splitter pane split position, left/right item placement
and vertical rhythm, event-log textarea geometry and content ("Layout test
app started / Splitter position: 300" as real selectable text), status bar
text at the bottom, window title at top-left.
- **Expected styling deltas:** native `<textarea>` chrome vs univ-drawn text
area; statusbar/panel grey backgrounds not painted yet (canvas islands do
not erase-paint panel backgrounds in DOM mode — Phase 4); `wxStaticBox`
"Event Log" caption+border missing (statbox DOM node lands in Phase 3 B1);
text baseline within spans sits ~2px higher than canvas text.
- **Bugs found & fixed by this comparison:**
1. DOM rects are TLW-relative, so ancestor moves must recursively refresh
descendants' DOM geometry (children's wx rects don't change when a
parent moves). Fixed in `wxWindowWasm::UpdateDomGeometry`.
2. `WriteText`/`AppendText` mutate the wxTextEntry cache without
`DoSetValue`, so the DOM `value` must be pushed there too. Fixed in
`wxTextCtrl::WriteText`.
3. Stub `wxFrame` didn't position bars — status bar text rendered at
(0,0) over the title. Fixed by porting the univ bar geometry
(`framuniv.cpp`) into `src/wasm/frame.cpp`.
## Phase 2 (2026-06-10) — dialog/timer apps, second round
`dialog-02-info-clicked` vs canvas baseline after the second fix round:
- **Matches canvas:** button rows centered with correct sizes/labels, modal
Information dialog as near-twin (title bar + close button, two message
lines, OK button bottom-right with stock label), grey window chrome,
event-log textarea with content.
- **Expected deltas:** info icon missing (wxStaticBitmap is Phase 3), dialog
border/shadow chrome thinner (Phase 5), native button styling.
- **Bugs found & fixed by this comparison round:**
4. `wxSystemSettingsNative::GetColour` returned black for nearly all
colours (univ theme used to mask it) — black dialog bodies/canvas
islands. Fixed with a classic light palette (native mode only).
5. DOM children of hidden ancestors (unselected notebook pages) stayed
visible — visibility now syncs the whole subtree via IsShownOnScreen.
6. Unhandled wxEraseEvent left canvas islands black in native mode —
default background fill added (native only).
7. DoGetBestSize measured 0x0 while elements/ancestors were display:none
(sizers run before Show) — measurement now uses a clone in an
offscreen always-rendered host.
8. `white-space: nowrap` discarded multiline wxStaticText newlines →
'pre'. wxDomSetShown also restored display:'' wiping flex → remembers
the preferred display.
9. Stock-id buttons with empty labels (msgdlg OK) rendered tiny — stock
label resolution added to wxButton::Create like other ports;
wxButtonBase::GetDefaultSize implemented.
10. Shim-only edits didn't relink test apps — JS pre-js files are now
Makefile dependencies of every app link rule.
## Phase 3 B1 (2026-06-10) — minimal app Controls tab
`04-controls-tab` vs canvas baseline with checkbox/radio/toggle/gauge/
slider/statline/statbox wired:
- **Matches canvas:** checkbox with native checked state, toggle button
with pressed highlight, slider thumb position, gauge fill proportion,
static-box group borders with legends, status bar fields, layout rhythm.
- **Native-styling deltas (accepted):** browser-blue range/progress chrome
vs univ grey; native checkbox glyph.
- **Known gaps (tracked):** notebook tab strip renders black boxes — the
generic notebook/tabg canvas island doesn't paint label text/background
(Phase 4 sweep); wxRadioBox "Options" group absent (composite control,
Phase 3 B2); File/Help menubar absent (Phase 5).
## Phase 3 B2+B3 (2026-06-10) — lists, spin, bitmaps, combo, checklist
- **Working as native DOM:** choice `<select>` (color dropdown matches),
listbox `<select multiple>` with items, radiobox fieldset with radio
rows, spinbutton ▲▼ pair (drives the generic wxSpinCtrl composite),
bitmap buttons with real PNG icons (toolbar rows, art-provider icons,
shapes), statbmp `<img>` (dialog info icon restored), editable combobox
as `<input>`+`<datalist>` autocomplete, checklistbox checkbox rows.
- **Bugs found by comparison/probing this round:**
11. "No image handler for type 15" warning dialog — test apps don't call
wxInitAllImageHandlers; lazy wxPNGHandler registration + wxLogNull
in wxDomBitmapToDataURL.
12. Bitmaps set but invisible: images load asynchronously, so the
measurement clone saw 0x0 — explicit img width/height from the wx
bitmap dimensions (+ flex-shrink:0 against squish).
13. wxDomSetIntValue would have written the selection INDEX into the
combobox text input — datalist branch maps selection↔option text.
- **Polish items (open):** bitmap-button vertical centering; checklistbox
selection highlight (only checked state is wired); wxLB_SINGLE uses the
multiple select.
## Phase 4 (2026-06-10) — canvas-island sweep
- **Near-pixel twins:** AUI (dock panels, title glyphs, tints), calendar
(month grid, weekend/selection colours, month select + year spinner),
listctrl (virtual 10k rows, striped, headers), grid tab, dialogs.
- **Upstream-grade generic-notebook bugs found & fixed:**
14. wxTabView::Init never initialized its background pen/brush (the
init line is commented out upstream) — tabs drew as black boxes.
Fixed in src/generic/tabg.cpp with wxSYS_COLOUR_BTNFACE.
15. wxNotebook::InsertPage's ChangePage(-1, 0) fallback never synced the
tab view's m_tabSelection, so the FIRST tab switch reported old
selection -1 and never hid the initial page — its DOM controls
ghosted over every later page. Fixed in src/generic/notebook.cpp by
SetTabSelection(..., false) after the fallback.
- **Polish items (open):** generic wxSpinCtrl's text field collapses
beside the spin pair; Event Log textarea lacks the univ scrollbar
gutter (native scrollbars appear on overflow instead).
## Phase 5 (2026-06-10) — menus, toolbars, tooltips
- **Working as native DOM:** menubar strip (File/Edit/View/Tools/Help) with
popup menus — items, separators, check marks, submenu expansion, hover
highlight, outside-click dismissal, drop shadow (`menu-03-file-clicked`
is a close twin of the canvas popup); toolbar strips with PNG icon
buttons, toggle state, separators; tooltips as native `title`
attributes (wxUSE_TOOLTIPS re-enabled for the DOM build).
- **Compatibility:** menu titles register as rendered elements
(`menuitem`/`menubar`), popup items as `menuitem`, tools as `tool`
`clickMenuBarItem`/`findRenderedByType`-based specs pass unchanged
(menu.spec 7/7, toolbar.spec 6/6).
- **Architecture note:** menu structures serialize C++→JS as JSON
(wxDomMenuSetStructure); activation routes back via
wxDomGetLastCommandId + wxDOM_EVENT_MENU → wxMenu::SendEvent. Toolbar
identical shape (wxDomToolbarSetTools / wxDOM_EVENT_TOOL →
wxToolBarBase::OnLeftClick with radio-group + veto handling).
## Phase 7 (2026-06-11) — KiCad on the DOM port
- **All six apps build and run** (pcbnew, eeschema, calculator,
pl_editor, symbol_editor, gerbview); full kicad e2e suite under
WX_PORT=dom: 28 passed / 1 flaky / 2 skipped / 0 failed.
- **Screenshots:** pcbnew = layer manager, appearance panes, toolbars,
WebGL board canvas, native menubar (File…Help, 9 menus) — near-twin of
the canvas reference modulo native chrome styling. eeschema = full
editor with real checkbox filter panels. calculator = native DOM form
controls (inputs, radios, buttons) beside canvas-island diagrams.
Port-aware reference images: tests/wizard-04-finish-headless-dom.png.
- **Bugs found & fixed by KiCad-on-DOM:**
16. DomMenuItemsToJson passed labels through wxString::Format, which
returns "" when vsnprintf rejects an argument (some KiCad labels)
→ empty JSON item → whole menu document unparseable. Labels are
now concatenated; wxDomJsonEscape hardened for ctrl chars < 0x20.
17. The first TLW (the browser page) started logically hidden; KiCad
never calls Show(true) on it (canvas renders regardless), so every
pre-Show child stayed display:none. Main frame now starts shown.
18. Keyboard arbitration was stateful (focusin/focusout flag); Firefox
fires no focusout when a focused element is REMOVED (wizard page
destroyed mid-focus) → flag stuck → all keys except Escape
swallowed for the session (eeschema Backspace/Delete dead). Now a
stateless document.activeElement check.
19. Build pipeline: fresh build trees failed at the embind pre-compile
(generated pcb_lexer.h not yet emitted) — found here and fixed
independently upstream (f936eb1); upstream's version kept.
- **Known cosmetic gap:** DOM menubar/toolbar styling differs from univ
pixels by design (native buttons, system font); the kicad reference
regions therefore use per-port reference images.
## Appearance-panel workstreams (2026-06-12)
User-reported pcbnew issues, all fixed and e2e-pinned
(tests/kicad/appearance.spec.ts, screenshots appearance-*.png):
20. DOM elements never clipped to ancestor viewports → layer rows
rendered past the pane. UpdateDomGeometry now pushes
clip-path:inset() from the accumulated ancestor client rects.
21. wxWindowWasm::ScrollWindow never moved children (univ does) →
scrolled panes could not scroll at all.
22. Mouse events over DOM-backed children never reached the wx pipeline
(listeners only on #canvas) → no hover/wheel/right-click over
labels/sliders. Document-level forwarding into wx_dom_mouse.
23. Wheel events died at the deepest window: wx mouse events don't
self-propagate and native ports rely on platform routing.
HandleMouseWheelEvent walks up to the TLW.
24. Generic (Motif-era) notebook replaced by a DOM-native wxNotebook:
real side-by-side tab buttons (ellipsis, no label overflow),
reliable switching, native look; registry 'tab' contract kept.
25. Universal tooltip layer (#wx-tooltip div, 600 ms, hover hit-test
driven) — island widgets (color swatches, visibility toggles) get
KiCad's SetToolTip texts; title attrs replaced by aria-label.
## DOM-only consolidation (2026-06-12)
The canvas (wxUniversal) mode and dual-build plumbing were removed; the
DOM port is THE WASM port. Rebuilding everything from clean surfaced
three latent bugs (all pre-existing — verified by rebuilding the
pre-consolidation tree, which failed identically):
26. The DOM projection treated GetScreenPosition() as the window's
top-left; it is ClientToScreen(0,0) — the CLIENT-AREA origin. The
first widget with a non-zero client origin (the DOM notebook's tab
strip) rendered its whole box a strip-height too low, overlapping
its own pages, and ComputeAncestorClip double-added the origin,
clipping page content (dataview/grid/minimal buttons cut to
slivers, clicks swallowed by the strip). This was bug-logged
earlier as "tab-strip/content slight overlap" and believed
cosmetic. Fixed in UpdateDomGeometryRecursive (+ eager strip
re-measure + re-projection in wxNotebook::WasmRebuildTabs).
27. The DOM port never mirrored spin arrows and text fields into the
e2e registry (the canvas port published them from univ paint
hooks, which died with univ). wx-dom.js now registers
'spinbutton' (up/down) and 'textctrl' (singleline/multiline)
rendered elements, and clears a control's mirrored entries on
destroy.
28. tests: clickCanvas() used locator.click(), whose actionability
check refuses points covered by DOM elements — in the DOM port
real widgets legitimately cover the canvas. It now dispatches via
page.mouse. The minimal-app comprehensive spec drives the wxChoice
through its native <select> (browsers own that popup; it cannot be
coordinate-clicked).
29. wxClipboard::IsSupported suspended for up to 2 s per call
(EM_ASYNC_JS readText raced vs timeout) on KiCad's idle path;
overlapping suspensions clobber Asyncify.currData → "indirect call
to null"/"signature mismatch" storms after a document loads (bug 2
of features/async on main; fix was designed in May, landed now).
The predicate answers from the cache, else "Clipboard API
available".
### Known-red kicad specs (asyncify scope, NOT this feature)
The 10 file-loading kicad specs (load-pcb, pcbnew-collab, eeschema-collab,
eeschema-ui Backspace/Delete/text-dialog) sit on the unfixed asyncify
substrate: a single corrupting fault still fires at document load (fiber
swap crossing the parked main loop — bug 3 / per-context currData
authority in features/async designs A/B) and breaks subsequent keyboard
and collab-commit processing. Bisect-verified pre-existing: eeschema
binaries built from BOTH the pre-consolidation and post-consolidation wx
trees fault identically; binary timing decides whether the race is lost,
so any rebuild can flip these specs. They were red before this
consolidation (the "31 passed" gates) and stay red after it; greening
them is the async feature's exit criterion.
## Layers tab goes blank after a tab round-trip (2026-06-15, pcbjam#8)
Switching the pcbnew appearance notebook Layers → Objects/Nets → Layers left
the Layers page blank: every layer row was present in the DOM but fully
clip-pathed away, and the layers `wxScrolledWindow` viewport had collapsed to
zero height (`154x0`).
Root cause (two layers):
30. KiCad's `APPEARANCE_CONTROLS::OnNotebookPageChanged` calls
`m_panelLayers->Fit()` on every page change, which shrinks the page to
its content min size and collapses the proportion-1 scrolled child's
viewport to height 0. `wxBookCtrlBase::DoSetSelection()` fires
`wxEVT_NOTEBOOK_PAGE_CHANGED` LAST, so this happens after the page was
sized/shown.
31. `wxWindowWasm::DoSetSize()` only emits `wxEVT_SIZE` (which drives
auto-Layout) when the size actually changes. Resizing the page back to
the page area is therefore a no-op that never re-runs the sizer, so the
collapsed scrolled child sticks and `UpdateDomGeometry` clip-paths every
row out of the empty ancestor viewport.
Fix: `wxNotebook::OnDomEvent` now calls `WasmRelayoutSelectedPage()` after
`SetSelection()` returns (all PAGE_CHANGED handlers done) — it re-asserts the
page to the full page area, forces `page->Layout()`, and re-projects the DOM
geometry. wxwidgets-layer only; KiCad untouched.
Test hardening: `appearance.spec.ts`'s `rowLabelTops()` previously returned
`getBoundingClientRect().top`, which is `0` (not null) for a clip-pathed row,
so the round-trip assertion passed even when the panel was blank. It now
hit-tests the row centre via `elementsFromPoint` (clip-path affects
hit-testing), so a clipped-away row reads as null and fails the assertion.
## Dock-panel resize: cursor, live preview, grey XOR lines (2026-06-19, pcbjam#20)
Resizing a docked side panel (a `wxAuiManager` dock sash, as KiCad uses for
all its side panels) was broken three ways in the DOM port: no resize cursor on
hover, no live preview during the drag, and a trail of grey vertical "stripes"
smeared across the pane when narrowing it.
Root causes (both wasm-layer):
32. `wxAuiManager` only live-resizes when `wxUSE_AUI_LIVE_RESIZE_ALWAYS`
(Mac/GTK3 only) or the `wxAUI_MGR_LIVE_RESIZE` flag is set; otherwise
`OnMotion`/`OnLeftUp` paint the sash hint via `wxScreenDC` + `wxXOR`
(`DrawResizeHint`). On a Canvas2D island XOR neither composites nor
erases, so every drag step's `wxPaneCreateStippleBitmap` hint stays put —
a 1px black/light stipple trail — and the panes only re-dock on mouse-up
(no preview). Fixed by adding `__EMSCRIPTEN__` to the
`wxUSE_AUI_LIVE_RESIZE_ALWAYS` platform list (`src/aui/framemanager.cpp`),
exactly as the existing comment intends ("not possible to show sash
feedback, so always use live update"). The XOR hint path is now never
taken: panes follow the cursor live, no stripes. KiCad untouched.
33. The wasm mouse path (`src/wasm/app.cpp` `HandleMouseEvent`) only refreshed
the cursor when the hovered *window* changed, from that window's static
`GetCursor()`; it never dispatched `wxEVT_SET_CURSOR`. An AUI dock sash is
painted inside the managed frame (not a child window), so `OnSetCursor`
(which sets `wxCURSOR_SIZEWE`/`SIZENS`) was never consulted. Fixed by
dispatching `wxSetCursorEvent` up the window chain on motion (like the
native ports' `HandleSetCursor`) and applying the cursor only when a
handler supplies one — so windows that manage their own cursor (the GAL
canvas, plain controls, busy cursor) are untouched.
Tests: `tests/e2e/aui-resize.spec.ts` pins all three on the faithful surface
(`aui_test.cpp`'s default-flag `wxAuiManager`): hover→`ew-resize`, pane width
changes mid-drag (live), and a stipple-fraction scan of the drag region
(broken ~0.12, clean ~0.001; threshold 0.02). RED before the fix, GREEN after.