subagent transcript
session 17f09f89 · workflow wf_4e894f41
delegated task
You are working on the FreeCAD -> WebAssembly port. Ground EVERYTHING in the real code; do not guess. - Source tree: /home/magik6k/lcad-wasm/freecad-p
▸ Magik
You are working on the FreeCAD -> WebAssembly port. Ground EVERYTHING in the real code; do not guess.
- Source tree: /home/magik6k/lcad-wasm/freecad-port/FreeCAD (git branch wasm-port). CMake build dir (out-of-source, already configured): /opt/toolchains/src/freecad-gui-build — read /opt/toolchains/src/freecad-gui-build/CMakeCache.txt for BUILD_* flags and dep paths. Build binary: bin/FreeCAD.wasm (~180 MB).
- Toolchain: emscripten, ASYNCIFY + JS exceptions (-fexceptions), SINGLE-THREADED (no pthreads). Static CPython 3.14 + static Qt 6.11 (wasm) + static OCCT + static Coin3D. Everything is statically linked; there is NO dlopen — Python C-extension modules are registered statically via PyImport_AppendInittab in the generated file src/Main/WasmInittab.cpp / WasmInittabGui.cpp (see also freecadWasmRegisterInittab in src/Main/MainGui.cpp).
- CRITICAL wasm constraints you MUST check for and design around:
(1) THREADING HANGS: any std::thread ctor / std::async(default or async policy) / QThreadPool / QtConcurrent / QThread worker HANGS or throws (no pthreads). Fix pattern: force synchronous/deferred on __EMSCRIPTEN__ (e.g. std::launch::deferred), or #ifndef FC_OS_WASM guard the worker and run inline. Example already in-tree: Mod/Sketcher/App/planegcs/GCS.cpp forced std::launch::deferred; App/Application.cpp guards _recomputeThread with #ifndef FC_OS_WASM; Gui/ProgressBar.cpp skips qApp->processEvents() on __EMSCRIPTEN__.
(2) MODAL DIALOGS / NESTED EVENT LOOPS: QDialog::exec()/QMessageBox::exec()/QEventLoop::exec() may deadlock under asyncify when driven from outside the main loop. Task-panel (docked, non-modal) dialogs are preferred.
(3) Reentrant ccalls from JS during asyncify suspend corrupt state — a QTimer command-pump (/fc-cmd.py) exists for JS->Python. QtWebEngine has NO wasm port (Web workbench / in-app help / real Start page can't use it).
(4) Wasm guards used in-tree: __EMSCRIPTEN__ (compiler-provided) and FC_OS_WASM (FreeCAD-defined) — both are defined on wasm.
- Built modules today: Part, Sketcher, Material, Start (+ kernel). Everything else is OFF (see BUILD_* in CMakeCache).
- PySide/shiboken: FREECAD_USE_PYSIDE=OFF, FREECAD_USE_SHIBOKEN=OFF, PySide6_DIR/Shiboken6_DIR NOTFOUND. Python 'PySide'/'shiboken6' import fails at runtime ("Cannot load shiboken6"). Many workbenches (Draft, BIM, most task dialogs) are Python+PySide and cannot function without it.
DELIVERABLES — do BOTH:
1) Write a thorough, concrete, ready-to-apply plan to /home/magik6k/lcad-wasm/porting/<SLUG>/plan.md — include: exact CMake flags/edits, exact source edits (file + before/after or unified-diff snippets precise enough to apply), any NEW files (full contents), external-dependency build recipes (how to build each dep for this emscripten/asyncify toolchain), a threading-hazard list (grep the real files, give file:line + the exact construct + the exact fix), a modal-dialog-hazard list, the PyImport_AppendInittab lines to add, an ordered step list, and an effort/risk estimate. Put any patch files or new-file contents as separate files in that dir too.
2) Return the structured summary (the schema). Keep summary fields concise; the depth goes in plan.md. Your returned text IS the structured object.
Be exhaustive and correct — token cost is not a concern. Verify claims against the actual files (use grep/read). If a dependency has no known wasm port, say so plainly and estimate the porting effort.
=== SYNTHESIS ===
You have 16 per-item analysis reports (summaries below; full plans are in the listed files, which you should READ for the details that matter):
- [pyside-recipe] needs-code | order 60 | 1-2 weeks | Buildable with on-box tools but bespoke: PySide has no upstream wasm path, so drive raw CMake for a dlopen-less static link; libshiboken/libpyside are already thread-safe for single-thread asyncify. (plan: /home/magik6k/lcad-wasm/porting/pyside-recipe/plan.md)
- [pyside-integrate] needs-dep-port | order 50 | weeks+ | FreeCAD-side wiring is small and fully specified (2 flags, 2 DIRs, ~5 inittab lines, 3 patches, 2 package shims), but it is blocked on static PySide6/shiboken6 wasm libs that DO NOT EXIST in the toolchain and have never been ported to static+asyncify+no-pthreads — that dependency port is weeks of research-grade work. (plan: /home/magik6k/lcad-wasm/porting/pyside-integrate/plan.md)
- [partdesign] config-only | order 1 | hours | Cheapest workbench yet: the wasm build was pre-wired for BUILD_PART_DESIGN (auto inittab + auto link, only requires BUILD_SKETCHER which is ON); zero C++ changes, no threading, and the one real edit is a try/except guard in InitGui.py so the PySide gear/sprocket imports don't abort activation. (plan: /home/magik6k/lcad-wasm/porting/partdesign/plan.md)
- [draft] needs-dep-port | order 50 | weeks+ | The 2 C++ files build fine and all 242 .py package trivially, but Draft is unusable (even headless import fails) until PySide6/shiboken and pivy _coin are ported to wasm. (plan: /home/magik6k/lcad-wasm/porting/draft/plan.md)
- [measure] config-only | order 1 | hours | Flip BUILD_MEASURE=ON (all wasm inittab/link plumbing already exists) plus a two-line PySide QT_TRANSLATE_NOOP fallback in two Python files; no new deps, no threading/modal hazards. (plan: /home/magik6k/lcad-wasm/porting/measure/plan.md)
- [spreadsheet-surface] config-only | order 2 | 1-2 days | Both are C++ workbenches (no PySide, no threads, no dep porting) — enabling them is a CMake/config change: flip two BUILD flags, remove a bogus BUILD_DRAFT gate for Spreadsheet, and add a Surface inittab block. (plan: /home/magik6k/lcad-wasm/porting/spreadsheet-surface/plan.md)
- [import-io] config-only | order 2 | hours | Best-case port: OCCT's STEP/IGES/glTF data-exchange toolkits are already built for the wasm/asyncify toolchain and the Import module has zero threading/network/subprocess code, so enabling it is one small CMake dependency patch + inittab registration + reusing the existing /fc-cmd.py file-picker pump …
Fable
thinking
I need to dig into the individual plan files to synthesize a comprehensive roadmap, starting with the key ones like the threading audit, modal audit, and the PySide contradiction to ground my understanding. I'm going to read through several of these in parallel to build out the full picture.
Fable
⚙ Bash {"command": "ls -la /home/magik6k/lcad-wasm/porting/ && echo \"---\" && for d in threading-audit modal-audit pyside-verify pyside-integrate partdesign missing-cmds-sketcher; do echo \"=== $d ===\"; wc -l /home/magik6k/lcad-wasm/porting/$d/p…
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/porting/threading-audit/plan.md"}
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/porting/modal-audit/plan.md"}
Fable
thinking
I'm looking at the pyside-verify adversarial tests and comparing them against the pyside-recipe and pyside-integrate approaches to identify where the contradictions lie, then grounding my understanding with the config-only patterns and related modules.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/porting/pyside-verify/plan.md"}
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/porting/pyside-integrate/plan.md", "limit": 80}
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/porting/partdesign/plan.md", "limit": 60}
Fable
thinking
I'm scanning through the config-only plans for a few modules to pull out the exact build commands and flag specifications I need to reference.
Fable
⚙ Bash {"command": "for d in measure import-io spreadsheet-surface small-python-mods missing-cmds-sketcher techdraw mesh; do echo \"===== $d =====\"; grep -iE \"ninja|cmake -D|BUILD_[A-Z_]+(:BOOL)?=|FreeCAD\\.(js|wasm)|-DBUILD\" /home/magik6k/lcad…
Fable
thinking
I'm mapping out the dependency structure for the roadmap, noting which modules need only configuration changes versus build flags, and identifying the order in which they should be applied based on their prerequisites. I'm spotting a critical contradiction: the synthesis suggests tackling PySide first, but the verification work actually concludes it's not viable now—weeks to months of research-grade effort with uncertain payoff. That flips the priority entirely; I should focus on the C++-only workbenches instead and treat PySide as a deferred spike. There's also a timing mismatch on TechDraw threading—the audit estimates 1-2 weeks for a QtConcurrent rewrite, but the plan claims 1-2 days by r …
Fable
Now I have enough grounding. Let me write the master roadmap.
Fable
⚙ Write {"file_path": "/home/magik6k/lcad-wasm/porting/ROADMAP.md", "content": "# FreeCAD → WebAssembly — Master Porting Roadmap\n\nTree: `/home/magik6k/lcad-wasm/freecad-port/FreeCAD` (branch `wasm-port`)\nBuild dir (configured, out-of-source, Nin…
Fable
thinking
Now I'll put together a concise executive summary of the roadmap.
Fable
Wrote `/home/magik6k/lcad-wasm/porting/ROADMAP.md`. Executive summary:
RECOMMENDED WAVE ORDER
1. Foundational needs-code first (gates everything): land the 2 live threading fixes (startup version-migration QThread+modal at StartupProcess.cpp:240; Start thumbnailing QThreadPool at DisplayedFilesModel.cpp:168/190) and the modal Class-B hang guards (NavigationAnimator, View3DInventorViewer, GraphvizView). Then the file-dialog browser bridge (new WasmFileBridge) to make File Open/Save/Import actually work.
2. Config-only C++ workbenches (hours–days each, no external deps, PySide-independent): PartDesign (BUILD_PART_DESIGN=ON + 1 try/except), Measure, Show, Spreadsheet+Surface, Import (STEP/IGES/glTF).
3. Needs-code workbenches: Sketcher command/fitAll fixes, Mesh+MeshPart (BRepMesh "Standard"), TechDraw (depends on Measure+Spreadsheet+Import).
4. Needs-dep-port (weeks–months, deferred): PySide6/shiboken6 — and everything gated on it (Draft, Inspection UI, Plot, OpenSCAD, Assembly, FEM Python framework).
5. Not-viable-now: FEM, full SMESH/netgen, QtWebEngine features.
KEY JUDGMENT — do NOT do PySide first. The optimistic "PySide first, 1–2 weeks" framing is refuted by the adversarial verification (`pyside-verify`): with the toolchain as-configured it is a multi-month, research-grade port (Qt `-no-feature-thread` breaks the stock QtCore typesystem; no released PySide supports Qt 6.11.1 × CPython 3.14; no static/no-dlopen build path; +180 MB size pressure). Nothing on the Wave A/B critical path depends on it. Contradictions flagged in the roadmap: PySide effort (1–2 wk vs. months → trust adversarial), and TechDraw threading (1–2 days reusing the synchronous path vs. 1–2 weeks rewriting the QtConcurrent state machine → budget the larger figure until verified).
SINGLE HIGHEST-VALUE NEXT ACTION: Land PR1 + PR2 (the threading + modal-hang fixes) — small, low-risk, and they remove live hang paths in the current build — then immediately enable PartDesign (PR5, BUILD_PART_DESIGN=ON + one InitGui.py try/except guard), the biggest parity-per-effort workbench win. About two weeks of low-risk work (PR1–PR11) roughly doubles the usable workbench set with working File Open/Save and zero live hangs, entirely without PySide.