# 06 — Part 2: implementation record & current state
> **Status: Part 2 IMPLEMENTED (2026-07-02).** As-built companion to the
> [`04`](04-part2-single-app-merge.md) plan: the pcbnew and eeschema kifaces are now
> statically linked into **one** WASM executable, `kicad_editor`, and all four editors
> (PCB / Footprint / Schematic / Symbol) are runtime `--frame` choices of that single
> bundle. One editor per page load (unchanged UX); multi-frame/cross-probing remains a
> follow-up this merge lays the groundwork for.
## One-paragraph summary
`kicad_editor.wasm` (178 MB at `-O1` debug vs 147 + 82 for the two engines separately —
shared wx/common/boost linked once) serves every editor tool. The frontend maps all four
editor tools onto the one bundle (`TOOL_BUNDLE`) and passes `--frame=pcb|fpedit|sch|
symedit`. The merge's real work was symbol-collision surgery: each engine's `Kiface()`
accessor and `KIFACE_1` getter got per-engine names via CMake compile definitions (zero
call-site edits), **21 additional ODR collisions found by a symbol audit** (upstream
reuses class names like `DIALOG_TEXT_PROPERTIES` for *different* classes per module) were
renamed on the PCB side the same way, six shared `common/` call sites now resolve their
owning frame's kiface exactly, and the duplicated embind layer became per-editor entries
behind a single dispatching registration.
## What changed
### C++ — `kicad` submodule (commit `a542e264`; ALL gated on `KICAD_WASM_MERGED_EDITOR`, OFF by default — option-OFF trees and native builds are byte-identical)
| File | Change |
|---|---|
| `include/kiway.h` | `KIFACE_GETTER` macro `#ifndef`-guarded so each kiface can carry a distinct getter symbol. |
| `CMakeLists.txt` | `KICAD_WASM_MERGED_EDITOR` option; `KICAD_WASM_PCB_SIDE_RENAMES` (the `Kiface=PcbKiface` static binding + the 21 audited ODR renames, see below); gated `add_subdirectory(${KICAD_WASM_LAYER}/editor)` after both engines. |
| `pcbnew/CMakeLists.txt`, `eeschema/CMakeLists.txt`, `common/CMakeLists.txt` | Apply the renames to `pcbnew_kiface_objects` + `pcbcommon` / `Kiface=SchKiface` to `eeschema_kiface_objects`; per-source `KIFACE_GETTER=<engine>_kiface_getter` on the hoisted `pcbnew.cpp`/`eeschema.cpp`; both `*_KIFACE_LIBRARIES` lists exported `CACHE INTERNAL` for the out-of-fork merged target. |
| `common/single_top.cpp` | `KICAD_MERGED_KIFACES` branch registers **both** faces (`set_kiface(FACE_PCB/FACE_SCH, …)`, `OnKifaceStart` stays lazy — the native project manager's model); **latent Part 1 bug fixed**: `PreloadLibraries` now uses the runtime-resolved frame, not compile-time `TOP_FRAME` (a `--frame=sch` boot on the PCB-default image preloaded the wrong face). |
| `common/eda_base_frame.cpp`, `common/dialogs/dialog_color_picker.cpp`, `common/design_block_tree_model_adapter.cpp` | The six real `Kiface()` call sites in shared code (`config()`/`sys_search()`/`help_name()` + color picker ×2 + design-block tree) resolve their owning frame's kiface exactly (`m_ident` → `KifaceType` → `KiFACE`), falling back to the global accessor. `__EMSCRIPTEN__`-only; standalone-tree behavior identical. |
### Why static per-engine binding (not one "active kiface" pointer)
PCB code runs while a **schematic** frame is active — e.g. the symbol chooser's footprint
preview reaches `KiFACE(FACE_PCB)` via `common/project.cpp`, after which `pcbcommon`
painter/IO code (`pcb_painter.cpp`, `pad.cpp`, …) calls `Kiface()` with no frame context
and must get the **PCB** kiface. A focus-based global would hand it the schematic one.
Hence: module code binds statically (`Kiface=PcbKiface`/`SchKiface`); only the merged
image's fallback `Kiface()` (in `wasm/editor/merged_kiface_dispatch.cpp`) does a
focus/top-window walk, and the six common/ sites resolve per-frame.
### The ODR audit (Stage 0 gate — the research doc's list was incomplete)
`llvm-nm` strong-symbol intersection of the two kiface object sets + weak-symbol
intersection minus shared-lib symbols found, beyond `Kiface()`/`KIFACE_1`:
- Same-name-**different-class** pairs (vtables/typeinfo/inline members would silently
cross-bind under `--allow-multiple-definition` → memory corruption, not link errors):