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

17 KiB

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 toolclickMenuBarItem/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):

  1. 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).
  2. 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.
  3. 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 (browsers own that popup; it cannot be coordinate-clicked).
  4. 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".
  5. 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.