subagent transcript
session 17f09f89
delegated task
Diagnose and fix a runtime failure in the FreeCAD WebAssembly build: creating a TechDraw DrawViewPart object throws `Base.FreeCADError: vector`. REPRO
▸ Magik
Diagnose and fix a runtime failure in the FreeCAD WebAssembly build: creating a TechDraw DrawViewPart object throws `Base.FreeCADError: vector`.
REPRODUCTION (verified): In the browser build, `App.newDocument('td'); d.addObject('TechDraw::DrawPage','Page')` SUCCEEDS, but `d.addObject('TechDraw::DrawViewPart','View')` throws `FreeCADError('vector')`. The error occurs at OBJECT CONSTRUCTION (addObject), before any recompute/execute. TechDraw workbench itself loads fine (registered in listWorkbenches).
ENVIRONMENT: emscripten single-thread (no pthreads/QProcess), ASYNCIFY, static Qt6.11/OCCT/Coin. Source: /home/magik6k/lcad-wasm/freecad-port/FreeCAD (branch wasm-parity-push). Build tree: /opt/toolchains/src/freecad-gui-build. Toolchain env: `source /home/magik6k/lcad-wasm/env.sh`. NOTE: TechDraw's HLR was recently patched from QtConcurrent::run+QFutureWatcher to SYNCHRONOUS calls (files: src/Mod/TechDraw/App/DrawViewPart.cpp, DrawViewSection.cpp, DrawComplexSection.cpp, DrawViewDetail.cpp) for the single-thread build — a bug in that serialization is a prime suspect (e.g. the HLR now runs during construction/onChanged and hits an empty geometry vector).
INVESTIGATION APPROACH:
1. Find what throws a Base exception with the exact message "vector". Grep broadly: the message "vector" likely comes from `std::vector::at()` / `_M_range_check` surfaced as a Base::Exception, OR a `throw Base::Exception("vector")`, OR OCCT. Search src/Mod/TechDraw AND consider the App property system. Since DrawPage construction works but DrawViewPart doesn't, focus on what DrawViewPart's constructor / its ViewProvider (src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp) / DrawView base does extra.
2. The addObject path constructs BOTH the App object and (since GUI is up) its Gui ViewProvider. Determine whether the throw is App-side or Gui-side. You can add a temporary `Base::Console().Message("TDDBG <point>\n")` trace in the DrawViewPart ctor and ViewProviderViewPart ctor, rebuild just those, and narrow it. To rebuild+test a single lib: `source env.sh && cd /opt/toolchains/src/freecad-gui-build && ninja bin/FreeCAD.js` (recompiles changed files + relinks; ~5-10min), then run the probe:
Write /home/magik6k/lcad-wasm/freecad-port/deploy/boot.py with:
import FreeCAD as App
P=App.Console.PrintMessage
import TechDraw
d=App.newDocument('t')
try: v=d.addObject('TechDraw::DrawViewPart','V'); P('TD_OK %s\n'%v.Name)
except Exception as e:
import traceback; P('TD_FAIL %r | %s\n'%(e, traceback.format_exc()))
P('STARTUP_DONE\n')
then run: `cd /home/magik6k/lcad-wasm && node m0probe.mjs 2>&1 | grep -iE 'TD_|TDDBG|STARTUP_DONE'`
(m0probe runs headless chromium against deploy/; ignore the HISTDB_FD zsh noise.)
3. Once located, FIX it so DrawViewPart constructs cleanly (and ideally recomputes: a box projected to a DrawViewPart should produce HLR edges). Likely fixes: guard/repair the synchronous-HLR change so it isn't invoked with empty geometry during construction; or fix an out-of-range vector access; or a Coin/QGraphics init that assumes threads. Keep desktop behavior intact (guard wasm-specifics under __EMSCRIPTEN__ only if truly wasm-specific).
4. VERIFY: the probe prints `TD_OK` (DrawViewPart created), and ideally extend it to add a Part::Box as Source + Direction + recompute and confirm HLR edges are produced (getVisibleEdges/geometry non-empty).
Report: the root cause (file:line), the fix, and the passing probe output. You OWN all of src/Mod/TechDraw for this fix. Do not touch other modules. Commit nothing (I'll commit).
Fable
thinking
I'm diving into the TechDraw source code to track down where the "vector" error originates, starting with the DrawViewPart constructor and the recently modified HLR files. I'll investigate these areas in parallel to understand what's causing the issue.
Fable
I'll start by investigating the TechDraw source code to find what throws the "vector" error and understand the DrawViewPart construction path.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD && git branch --show-current && git log --oneline -5", "description": "Check branch and recent commits"}
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App/DrawViewPart.cpp", "limit": 120}
Fable
thinking
I'm diving into the DrawViewPart constructor to trace through the onChanged handler and the recently patched HLR files, searching for any vector-related exceptions along that code path.
Fable
Let me continue reading the constructor and search for the "vector" error source.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App/DrawViewPart.cpp", "offset": 120, "limit": 120}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw && grep -rn 'QtConcurrent\\|QFuture\\|std::thread\\|\\.at(' App/DrawViewPart.cpp App/DrawViewSection.cpp App/DrawComplexSection.cpp App/DrawViewDetail.cpp | head …
Fable
thinking
The error message "vector" by itself is unusual—typically `std::vector::at()` would throw a more detailed `_M_range_check` message. A bare "vector" message from `Base.FreeCADError` suggests something else is going on, so I need to figure out where this exception is actually being raised.
It could be an OCCT exception being caught and re-wrapped, or it might be coming from the property system when the ViewProvider is created for DrawViewPart. Let me search the codebase for places where `Base::Exception` is thrown with exactly "vector" as the message, or look at how `PropertyVector` and related types handle errors during object construction.
Fable
Let me search broadly for the exact "vector" error string across the codebase.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src && grep -rn '\"vector\"' --include=*.cpp --include=*.h --include=*.cxx --include=*.hxx . 2>/dev/null | head -40", "description": "Grep for literal \"vector\" string"}
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App/DrawViewPart.cpp", "offset": 340, "limit": 120}
Fable
thinking
I need to reconsider where this error is actually happening. The crash occurs during object construction in addObject, before recompute even runs, so execute() or partExec() shouldn't be in the call stack unless they're triggered during initialization. The error message "vector" by itself is odd—the standard std::out_of_range from vector::at would include more detail, so this might be coming from OCCT's Standard_Failure exception instead.
Let me trace through the actual construction sequence: addObject creates the App::DrawViewPart object first, then the GUI ViewProvider gets created afterward with its attach and initialization. I should search more carefully through the codebase to find wh …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src && grep -rn '\"vector\"' . 2>/dev/null | grep -iE '\\.(cpp|h|cxx|hxx|c|py)' | head -40; echo \"---RG---\"; which rg", "description": "Grep literal vector string properly"}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/Gui && sed -n '1,120p' ViewProviderViewPart.cpp", "description": "Read ViewProviderViewPart constructor"}
Fable
thinking
I'm noticing that Expression.cpp registers "vector" as a function type and checks for it with a case-insensitive comparison, but that's probably not relevant to the construction issue. The bare "vector" error message is interesting though—it doesn't match the typical longer message you'd get from std::vector::at(), so maybe this exception is coming from somewhere else entirely.
Wait, I'm realizing the key difference: in a wasm build with emscripten, the standard library is LLVM's libc++, not libstdc++. In libc++, when std::vector::at() detects an out-of-range access, it throws std::out_of_range with the message literally set to "vector"—that's exactly what the error message shows. So somewh …
Fable
The bare message "vector" is the tell-tale sign: emscripten uses LLVM libc++, whose `std::vector::at()` out-of-range throws `std::out_of_range("vector")` — `what()` is literally "vector". So an out-of-range `.at()` runs during construction. Let me find which `.at()` runs at construction time (LineGroup/LineGenerator/Preferences called from the ctors).
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw && rg -n '\\.at\\(|\\[.*\\]' App/LineGroup.cpp App/LineGenerator.cpp App/Preferences.cpp 2>/dev/null | rg '\\.at\\(' | head -40", "description": "Find .at usages …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw && sed -n '120,230p' Gui/ViewProviderViewPart.cpp", "description": "Read rest of ViewProviderViewPart constructor"}
Fable
thinking
I'm finding the actual culprits now—the ViewProviderViewPart constructor invokes `LineGenerator::getLineStandardsBody()`, and various style properties like `Preferences::BreakLineStyle()` and `Preferences::SectionLineStyle()` are being set, which likely trace back to code using `.at()` on containers. I should check Preferences.cpp around line 540 where `choices.at(Preferences::lineStandard())` is called, and continue examining the Preferences implementation. I need to figure out whether this crash is happening on the App side or Gui side. The App-side DrawViewPart constructor just does preference reads and property setup, which shouldn't throw vector exceptions. The Gui-side ViewProviderView …
Fable
The ViewProviderViewPart ctor calls `LineGenerator::getLineStandardsBody()`, `Preferences::SectionLineStyle()`, `Preferences::BreakLineStyle()`, etc., which use `.at()`. Let me examine those functions.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && sed -n '90,130p;300,380p' LineGenerator.cpp", "description": "Read LineGenerator getLineStandardsBody and helpers"}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && sed -n '520,560p' Preferences.cpp; echo \"=== SectionLineStyle / BreakLineStyle / lineStandard ===\"; rg -n 'SectionLineStyle|BreakLineStyle|::lineStandard…
Fable
thinking
The issue is that `getAvailableLineStandards()` returns an empty vector when the line standards directory can't be read in WebAssembly, which causes `getLineStandardsBody()` to throw when it tries to access `choices.at(activeStandard)` with an empty choices array. Since `getLineStandardsBody()` is called directly from the ViewProviderViewPart constructor, that's where the crash originates.
Fable
The key is `getAvailableLineStandards()` — if it returns empty, `getLineStandardsBody()` does `choices.at(0)` on an empty vector → throws "vector". Let me examine it.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && rg -n 'getAvailableLineStandards|lineDefinitionLocation|lineElementsLocation|getBodyFromString' LineGenerator.cpp Preferences.cpp LineGenerator.h Preferenc…
Fable
thinking
I'm seeing how the mechanism works: `getAvailableLineStandards()` scans the directory specified by `Preferences::lineDefinitionLocation()` for CSV files matching the `*.LineDef.csv` pattern. In WebAssembly, if that directory doesn't exist or the filesystem returns nothing (since MEMFS has no files), the result vector stays empty. When `getLineStandardsBody()` tries to access the first element with `choices.at(0)` on an empty vector, it throws `std::out_of_range`, which then gets wrapped in an exception.
This exception originates from the GUI side in the `ViewProviderViewPart` constructor, specifically through the `LineGenerator::getLineStandardsBody()` call. But I should verify which prefer …
Fable
Let me check the earlier-running functions (`SectionLineStyle`, `prefHighlightStyle`, `LineGroup::getDefaultWidth`) to see which throws first, and confirm the directory-read path.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && sed -n '455,520p' Preferences.cpp; echo \"=== prefHighlightStyle / prefSectionColor in Gui VP ===\"; rg -n 'prefHighlightStyle|prefSectionColor|prefHighlig…
Fable
thinking
I'm tracing through the codebase to find where `.at()` is first called on a potentially empty vector in the ViewProviderViewPart constructor, checking that the preference functions like `Preferences::SectionLineStyle()` and color/style getters don't use vector access themselves—looks like they just return integers, so the issue likely originates in `LineGen`.
The real problem appears to be in the wasm build where `getAvailableLineStandards()` returns an empty vector because the LineGroup CSV files either aren't deployed to the MEMFS resource directory or `getDirectoryContent()` fails to find them, causing `choices.at(activeStandard)` to throw when trying to access a non-existent index.
But …
Fable
`SectionLineStyle`, `BreakLineStyle`, `BreakType` are plain `GetInt` — no vector. So the first `.at()` on a possibly-empty vector in the ViewProviderViewPart ctor is `LineGenerator::getLineStandardsBody()`. Let me confirm whether the LineGroup CSV files are actually deployed in the wasm resource dir, and check `prefHighlightStyle`/`prefSectionColor`.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw && rg -n 'prefHighlightStyle|prefSectionColor|prefHighlightColor' Gui/ViewProviderViewPart.cpp | head; echo \"=== def of those helpers ===\"; sed -n '/App::Color …
Fable
⚙ Bash {"command": "ls -la /home/magik6k/lcad-wasm/freecad-port/deploy/ 2>/dev/null | head -40; echo \"=== find LineGroup resources in deploy/build ===\"; find /home/magik6k/lcad-wasm/freecad-port/deploy /opt/toolchains/src/freecad-gui-build -inam…
Fable
thinking
The LineDef CSV files are in the source code but not showing up in the deploy packages because the data is compressed into .data archives. I need to check the .data.js manifest files to see if LineGroup or LineDef entries are actually packed in there.
Fable
The LineDef CSVs exist in source. The deploy uses packed `.data` archives — let me check whether the LineGroup files are actually included in the packed data (the `.data.js` files contain the file manifest).
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/deploy && for f in *.data.js; do n=$(grep -o 'LineDef' \"$f\" 2>/dev/null | wc -l); echo \"$f: LineDef=$n\"; done; echo \"=== search LineGroup path in manifests ===\"; grep -oh '\"filenam…
Fable
thinking
So the LineGroup CSV files aren't included in the wasm data archives, which means when the wasm build runs, the preferences point to a directory that doesn't exist or is empty in MEMFS. This causes `getAvailableLineStandards()` to return an empty vector, and then `getLineStandardsBody()` tries to access the first element with `.at(0)` on that empty vector, triggering a `std::out_of_range` exception that gets wrapped as a FreeCADError.
Now I'm checking whether the LineGroup directory is even packed into the resource manifests at all. A search for "LineGroup" came up empty, so let me verify what TechDraw resources are actually included in the manifests to confirm that LineGroup is missing.
Fable
Confirmed: zero `LineDef` files in any packed manifest. Let me verify the LineGroup directory itself is absent from the manifests and confirm this is the miss.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/deploy && echo \"=== LineGroup occurrences across all manifests ===\"; grep -oh 'LineGroup' *.data.js 2>/dev/null | wc -l; echo \"=== sample TechDraw resource paths that ARE packed ===\";…
Fable
thinking
I'm noticing these paths look like they're from the source tree rather than install paths, which is odd for a resource directory. The manifests reference source files like CMakeLists, and I'm realizing LineGroup doesn't appear anywhere in what's being packed. Let me reconsider whether the resource directory is actually containing the source tree itself.
The core issue is clear: LineGroup has zero occurrences, meaning the LineDef and ElementDef CSV files aren't available in the wasm filesystem. This creates two separate problems. First, there's a code robustness issue I can fix within TechDraw — the `.at()` calls need guards because `getAvailableLineStandards()` can return empty, and accessi …
Fable
Confirmed: the LineGroup CSV files are not packed into any wasm data archive (0 occurrences), so in MEMFS the directory is empty and `getAvailableLineStandards()` returns empty. Let me verify `LineGroup::getDefaultWidth` (runs first in the ctor) to make sure it isn't the earlier thrower, and check the ctor call order precisely.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && sed -n '/double LineGroup::getDefaultWidth/,/^}/p' LineGroup.cpp; echo \"=== getGroupNamesFromFile / any .at in LineGroup ===\"; rg -n '\\.at\\(' LineGroup…
Fable
thinking
I'm checking whether `lineGroupFactory` might be the culprit by looking at how it handles the LineGroup.csv file, but it seems to have fallback logic for missing files. The real suspect still points to `getLineStandardsBody()`, though I should add some tracing to pinpoint exactly where the vector access is failing.
Rather than spend time on deep investigation, I'll take a pragmatic approach: guard all the `.at()` calls that could fail on empty vectors in the construction path, add a temporary trace to confirm which one was throwing, and rebuild. If it prints success, that validates both the diagnosis and fix in one cycle. The key places to protect are `getLineStandardsBody()` where it acces …
Fable
`getDefaultWidth` is clean (no `.at()`). The prime thrower is `getLineStandardsBody()`. Let me read the LineGenerator load path and constructor to guard all empty-vector cases comprehensively so I fix it in one rebuild cycle.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && sed -n '1,90p' LineGenerator.cpp", "description": "Read LineGenerator top/ctor/load"}
Fable
thinking
The LineGenerator constructor loads descriptions from CSV files, and if they're missing the collections stay empty, but the getter methods handle this gracefully with bounds checks. Now I need to examine `getBodyFromString` to understand what it returns and determine the right default value for my fix.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && sed -n '/std::string LineGenerator::getBodyFromString/,/^}/p' LineGenerator.cpp; echo \"=== getLineDefinitions ===\"; sed -n '/std::vector<std::vector<std:…
Fable
thinking
I need to add guards in several TechDraw functions to handle empty `choices` vectors—returning sensible defaults like "ISO" in `getLineStandardsBody()`, fixing the comparison operator in `isProportional()`, and protecting the preference functions that access these vectors during initialization. I'm weighing whether to default to "ISO" or "ASME" to match desktop behavior, but for the crash fix itself it doesn't matter much — I'll go with "ISO" as the safest international default. The key fix is adding a guard: if `choices` is empty, return "ISO" directly. Beyond that, I'm considering whether to address the root cause of the missing files in the wasm package, though that's likely outside TechD …
Fable
Now I have the full picture. Let me make the defensive fixes. First, let me read the exact current text of the three functions to edit.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App/LineGenerator.cpp", "offset": 335, "limit": 45}
Fable
thinking
I'm starting to apply the fixes now, beginning with adding a guard to handle empty choices in the getLineStandardsBody function.
Fable
Now applying the fixes. Fix 1: guard empty `choices` in `getLineStandardsBody`.
Fable
⚙ Edit {"replace_all": false, "file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App/LineGenerator.cpp", "old_string": " int activeStandard = Preferences::lineStandard();\n std::vector<std::string> choices = getAvail…
Fable
Fix 2: correct the off-by-one and empty case in `isProportional`.
Fable
⚙ Edit {"replace_all": false, "file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App/LineGenerator.cpp", "old_string": " std::vector<std::string> choices = getAvailableLineStandards();\n if (standardIndex > choices.s…
Fable
Fix 3: guard the `.at()` in `Preferences::currentLineDefFile` / `currentElementDefFile` (called during LineGenerator load / paint).
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App/Preferences.cpp", "offset": 536, "limit": 15}
Fable
⚙ Edit {"replace_all": false, "file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App/Preferences.cpp", "old_string": "std::string Preferences::currentLineDefFile()\n{\n std::string lineDefDir = Preferences::lineDefiniti…
Fable
thinking
I'm checking how `getLineDefinitions` and `loadElements` handle empty file paths — they should gracefully return empty results when the ifstream fails to open, similar to how `getLineDescriptions` already validates with `is_open()`.
Fable
Let me verify the CSV loaders handle an empty/missing path gracefully (return empty rather than throw).
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && sed -n '/LineGenerator::getLineDefinitions/,/^}/p' LineGenerator.cpp | head -45; echo \"=== loadElements ===\"; sed -n '/LineGenerator::loadElements/,/^}/p…
Fable
thinking
Both loaders handle the `is_open()` check and return empty results gracefully, so with my guard in place, `currentLineDefFile()` will return an empty path, the ifstream won't open, and everything exits cleanly without throwing. Now I need to set up the boot.py probe to test this—I'll extend it to create a Part::Box source, recompute, and verify the HLR edges work as expected.
Let me write a boot.py that imports FreeCAD and TechDraw, creates a new document with a DrawPage and DrawViewPart, adds a Part::Box as the source, sets the direction, recomputes, and then checks that `getVisibleEdges` returns non-empty geometry.
Fable
Both loaders handle a missing/empty path gracefully. The fixes are complete. Now let me write the extended probe and rebuild. First, let me check the Python API for verifying HLR edges.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && rg -n 'Name=|Documentation' DrawViewPartPy.xml | rg -i 'edge|vertex|geometry|visible|hidden' | head -20", "description": "Find DrawViewPart Python methods …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw && find . -name 'DrawViewPartPy.xml'; echo \"===\"; f=$(find . -name 'DrawViewPartPy.xml' | head -1); rg -n 'Name=' \"$f\" | rg -i 'edge|vertex|geom|visible|hidde…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw && ls App/*.xml | rg -i 'viewpart|drawview' ; echo \"=== methods in DrawViewPartPy.xml ===\"; rg -n '<Methode Name|Attribute Name' App/DrawViewPartPy.xml 2>/dev/n…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw && find . -iname '*Py.xml' | rg -i view; echo \"=== any xml ===\"; find . -iname '*.xml' -path '*App*' | head", "description": "Find Python xml files in TechDraw"…
Fable
thinking
I'm noticing the XML file isn't showing up in the search results, even though the header comment references it as the source. Let me try switching to bash to see if the find command works better there.
Fable
⚙ Bash {"command": "bash -c \"find /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw -iname '*Py.xml' | head -30\"", "description": "Find Py xml files with bash"}
Fable
⚙ Bash {"command": "find /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw -iname '*Py.xml' 2>/dev/null", "description": "Find Py xml files"}
Fable
thinking
The find command came up empty except for shell history noise, which is odd since DrawViewPartPy.xml should be there. Let me look at what's actually in the App directory.
Fable
⚙ Bash {"command": "ls /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App/ | grep -iE 'py|xml' | head -40", "description": "List Py-related files in TechDraw App"}
Fable
thinking
I see that newer versions of FreeCAD have switched to using `.pyi` stub files instead of the older `Py.xml` format, so I need to check the DrawViewPart.pyi file to find the available methods.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && grep -nE 'def |getVisible|getHidden|geometry|Edge|Vertex' DrawViewPart.pyi | head -40", "description": "Read DrawViewPart Python methods"}
Fable
`getVisibleEdges()` is the method to verify HLR output. Now let me write the extended probe boot.py and start the rebuild.
Fable
⚙ Write {"file_path": "/home/magik6k/lcad-wasm/freecad-port/deploy/boot.py", "content": "import FreeCAD as App\nP = App.Console.PrintMessage\nimport TechDraw\n\nd = App.newDocument('t')\n\n# 1) Page construction (known-good)\ntry:\n pg = d.addOb…
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/deploy/boot.py"}
Fable
thinking
I see there's already a boot.py file I can work with, so I'll modify it to add the getVisibleEdges verification step after recomputing.
Fable
There's already a suitable boot.py. Let me extend it to verify HLR edges after recompute.
Fable
⚙ Edit {"replace_all": false, "file_path": "/home/magik6k/lcad-wasm/freecad-port/deploy/boot.py", "old_string": "step(\"addViewToPage\", lambda: (page.addView(view), 'added')[1])\nstep(\"recompute\", lambda: d.recompute())\nstep(\"view_touched\", …
Fable
Now let me start the rebuild in the background.
Fable
⚙ Bash {"command": "source /home/magik6k/lcad-wasm/env.sh 2>/dev/null; cd /opt/toolchains/src/freecad-gui-build && ninja bin/FreeCAD.js 2>&1 | tail -40", "description": "Rebuild FreeCAD.js with TechDraw fixes", "timeout": 900000, "run_in_backgroun…
Fable
thinking
While the build completes, I'm looking into whether the LineGroup CSV files should be packaged with the wasm data. This touches on TechDraw's CMake install configuration, which is within my scope, so I'll check if those rules exist and whether the wasm build captures them. If not, I'll flag it as something to address, though the core fix itself stays focused on the source module.
Fable
While the build runs, let me check the TechDraw CMake install rules for the LineGroup resources (within my scope) to understand the packaging gap.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw && rg -n 'LineGroup|LineDef|ElementDef|INSTALL|install' CMakeLists.txt App/CMakeLists.txt 2>/dev/null | head -30", "description": "Check TechDraw CMake install ru…
Fable
thinking
I'm examining the CMakeLists.txt file around lines 60-130 to see how LineGroup is being copied and installed, particularly looking at the copy operations in lines 74-85 that handle moving files to the build data directory.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/CMakeLists.txt", "offset": 60, "limit": 70}
Fable
thinking
The CMake build rules are correctly copying the LineGroup files to the binary directory's data folder, but the separate wasm packaging script that creates the `.data` archives isn't picking up that directory — it's a packaging issue outside the source tree.
Fable
The CMake rules correctly copy/install LineGroup CSVs to the build data dir. Let me verify they exist in the build tree (so the gap is purely the wasm data-packaging step, outside my scope).
Fable
⚙ Bash {"command": "find /opt/toolchains/src/freecad-gui-build -ipath '*TechDraw/LineGroup*' 2>/dev/null | head; echo \"=== resource dir the wasm build uses ===\"; find /opt/toolchains/src/freecad-gui-build -ipath '*Mod/TechDraw*' -iname '*.csv' 2…
Fable
thinking
No CSVs are showing up in the build tree, which is unexpected. I should check where `CMAKE_INSTALL_DATADIR` might be placing them and search more broadly across the directory structure.
Fable
⚙ Bash {"command": "find /opt/toolchains/src/freecad-gui-build -iname '*LineDef*' -o -iname 'LineGroup.csv' 2>/dev/null | head; echo \"=== where does wasm pack data from? find any TechDraw data dir ===\"; find /opt/toolchains/src/freecad-gui-build…
Fable
thinking
Looking at the build directory structure, I notice the TechDraw module is missing the LineGroup directory entirely — which suggests the `fc_target_copy_resource` command for LineGroup either didn't execute or the resource files weren't copied to the expected location in the share directory.
Since Patterns and Templates are present but LineGroup is absent, and my earlier search found no CSV files in the build's share directory, the root cause appears to be that the LineGroup resources were never copied into the build output. I own the CMakeLists.txt file that defines this copy rule, so I should investigate why `fc_target_copy_resource` isn't working as expected.
But stepping back — my code …
Fable
The build data dir `share/Mod/TechDraw` has Patterns/Templates/Symbols/Resources but no LineGroup directory — so the LineGroup CSVs never made it into the packaged data. The TechDraw CMake rule for it is correct, so the gap is in the external wasm data-packing step (outside my scope). My code fix is what resolves the reported crash. Let me monitor the build.
Fable
⚙ Bash {"command": "sleep 90; tail -15 /tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/bt14qpc6c.output", "description": "Check build progress after 90s"}
Fable
⚙ Read {"file_path": "/tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/bt14qpc6c.output"}
Fable
⚙ Bash {"command": "cat /tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/bt14qpc6c.output 2>/dev/null; echo \"=== [end] ===\"", "description": "Read current build output"}
Fable
thinking
The build is still running and the output file hasn't been populated yet, so I need to wait for it to complete. I'll use a polling approach with a Monitor tool or a simple bash command that waits for the compilation to finish.
Fable
The build is compiling the changed files. Let me wait for it using a monitor loop.
Fable
⚙ ToolSearch {"query": "select:Monitor", "max_results": 3}
Fable
⚙ Bash {"command": "out=/tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/bt14qpc6c.output; until grep -qE 'ninja: (build stopped|no work|error)|FAILED|Linking|error:|\\$ ' \"$out\" 2>/dev/null || ! ls /proc/$(pgr…
Fable
thinking
I'm waiting for the build to complete while I trace through the throw path more carefully, checking if there are any other `.at()` calls in the DrawViewPart or ViewProviderViewPart constructors that could be problematic, and verifying whether the Preferences methods being called there might throw.
Fable
While the build runs, let me confirm the App-side ctor (constructed before the ViewProvider) has no other empty-vector `.at()` that would throw first — checking `Preferences::scrubCount` and `lineGroup`.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && sed -n '/int Preferences::scrubCount/,/^}/p;/int Preferences::lineGroup/,/^}/p' Preferences.cpp; echo \"=== .at in DrawView base ctor path? ===\"; rg -n '\…
Fable
thinking
The constructor path on the app side looks solid with no risky vector operations, and DrawView's base class doesn't use `.at()` either, so the exception is definitely coming from ViewProviderViewPart's constructor through getLineStandardsBody. My targeted fix should address it. Now I'm waiting for the build to complete and will check the output as it comes in.
Fable
App-side ctor is clean. The throw is definitively in the GUI `ViewProviderViewPart` ctor via `getLineStandardsBody()`. My fix targets exactly that. Now waiting for the build to complete.
Fable
⚙ Read {"file_path": "/tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/blhpjxvdj.output"}
Fable
thinking
I've got a background process waiting for the build to complete, which should take around 5-10 minutes. Rather than constantly checking, I'll pull up the main build output file to monitor progress while I wait.
Fable
⚙ Read {"file_path": "/tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/bt14qpc6c.output"}
Fable
I'll wait for the build completion notification.
Fable
⚙ Bash {"command": "out=/tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/bt14qpc6c.output; until [ -s \"$out\" ] && ! pgrep -f 'ninja bin/FreeCAD.js' >/dev/null 2>&1; do sleep 10; done; echo \"=== ninja finished …
Fable
thinking
I've got two background processes waiting for the build to complete, which is redundant. I should just wait for one of them to notify me instead of spawning anything else.
Fable
I've set up a waiter for the build. Let me wait for it to complete before running the probe.
Fable
⚙ Read {"file_path": "/tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/b9c7clqnf.output"}
Fable
I'll wait for the build waiter to notify me on completion.
Fable
⚙ Read {"file_path": "/tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/bt14qpc6c.output"}
Fable
thinking
The build is still compiling without output yet, so I'll stop polling and wait for the background task to notify me when it's done.
Fable
The build is still in progress (compiling the 3 changed TechDraw files, then relinking the large FreeCAD.wasm — this takes several minutes with no intermediate ninja output). I'll wait for the completion notification from the background waiter.
Fable
⚙ Bash {"command": "out=/tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/bt14qpc6c.output; if pgrep -f 'ninja bin/FreeCAD.js' >/dev/null 2>&1; then echo \"STILL_BUILDING pid=$(pgrep -f 'ninja bin/FreeCAD.js')\"; …
Fable
thinking
The build is still compiling and the old binary hasn't been updated yet, so I'm waiting for the ninja process to finish and send me a notification when it's done.
Fable
Still compiling. I'll wait for the background waiter to signal completion.
Fable
⚙ Read {"file_path": "/tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/b9c7clqnf.output"}
Fable
I'll stop polling and wait for the build waiter's completion notification.
Fable
⚙ Read {"file_path": "/tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/blhpjxvdj.output"}
Fable
thinking
I'm realizing I need to stop making unnecessary calls and actually wait for the background tasks to complete and notify me. The challenge is that I can't just pause without doing anything—I need to either make a tool call or end my turn. Let me set up a blocking wait that will hold until I get a notification from the background processes.
Actually, I should verify that the probe script exists and understand how it's invoked so I'm ready to run it immediately once the build finishes.
Fable
Let me verify the probe harness is ready while the build finishes.
Fable
⚙ Bash {"command": "ls -la /home/magik6k/lcad-wasm/m0probe.mjs 2>/dev/null && head -30 /home/magik6k/lcad-wasm/m0probe.mjs 2>/dev/null; echo \"=== deploy FreeCAD.js copy step? how does build output reach deploy ===\"; ls -la /opt/toolchains/src/fr…
Fable
thinking
The m0probe serves from the deploy directory, so after building I need to copy the compiled JavaScript and WebAssembly files there. The build is still linking—the WASM file is currently 110MB and growing toward its final size of around 288MB.
I'm also realizing that the console output filtering might not capture my boot messages as expected. The m0probe filters for specific patterns like `FCLOG` and `FCERR`, but my boot.py uses a "TD %s:" format that may not match the grep pattern in the task. I should adjust my grep to look for both "TD " and "STARTUP_DONE" to ensure I'm catching all the relevant output.
Fable
The probe serves from `deploy/`, so after the build I must copy `FreeCAD.js`/`FreeCAD.wasm` into `deploy/`. The build's wasm is currently being linked (110MB and growing toward ~288MB). Let me check how the deploy captures stdout so my probe lines surface in the console.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/deploy && grep -nE 'FCLOG|__fcLog|print:|printErr|Module.print|boot.py|FS.|readFile' index.html | head -25", "description": "Inspect deploy index.html print/boot handling"}
Fable
thinking
The build succeeded, so my print handler logic is working as expected—the PrintMessage lines are being logged with the FCLOG prefix, which means the m0probe filter will catch them. Now I need to verify the artifacts are ready and run the probe to see the output.
Fable
The build finished with exit code 0. The `print:` handler routes stdout to `console.log('FCLOG ' + s)`, which the probe captures. Let me verify the build output and copy the artifacts to the deploy dir.
Fable
⚙ Bash {"command": "tail -6 /tmp/claude-1000/-home-magik6k-lcad-wasm/17f09f89-ce7c-4f49-aa6a-cf0521026805/tasks/bt14qpc6c.output; echo \"=== build artifacts ===\"; ls -la /opt/toolchains/src/freecad-gui-build/bin/FreeCAD.js /opt/toolchains/src/fre…
Fable
thinking
The build completed successfully with just some harmless numpy warnings, producing the JavaScript and WebAssembly files. Now I'm copying these to the deployment directory and running the probe to test everything.
Fable
Build succeeded (warnings are benign numpy signature mismatches). FreeCAD.js and FreeCAD.wasm are freshly built (22:21). Let me copy them to deploy and run the probe.
Fable
⚙ Bash {"command": "cp /opt/toolchains/src/freecad-gui-build/bin/FreeCAD.js /opt/toolchains/src/freecad-gui-build/bin/FreeCAD.wasm /home/magik6k/lcad-wasm/freecad-port/deploy/ && ls -la /home/magik6k/lcad-wasm/freecad-port/deploy/FreeCAD.js /home/…
Fable
⚙ Bash {"command": "node m0probe.mjs 2>&1 | grep -iE 'TD |TD_|TDDBG|STARTUP_DONE|vector|FAIL|abort|visibleEdges|hiddenEdges' | head -40; echo \"=== probe exit ===\"", "description": "Run headless probe and filter TechDraw output", "timeout": 12000…
Fable
thinking
The main issue is resolved—DrawViewPart now initializes without throwing that vector error. There's a follow-up failure with the Template not being set on the DrawPage, but that's a separate limitation unrelated to the bug I was fixing.
The real concern is that HLR recomputation is returning zero edges when it should produce them for the projected box. I suspect the view wasn't properly added to the page due to the missing template, which might cause `keepUpdated()` to return false and skip the HLR execution entirely. I need to trace through the `keepUpdated()` logic and the execute flow to understand whether a view must be on a page for HLR to run, or if I can work around this by adjusting …
Fable
Primary bug fixed: `TD addView: ok <DrawViewPart object>` — DrawViewPart now constructs cleanly (no more `FreeCADError: vector`). But recompute produced 0 edges. The `addViewToPage` failed ("Template not set for Page"), which likely means the view isn't on a page and `keepUpdated()` short-circuits `execute()`. Let me check `keepUpdated()`.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && echo \"=== DrawViewPart::keepUpdated / DrawView::keepUpdated ===\"; rg -n 'keepUpdated' DrawView.cpp DrawViewPart.cpp | head; echo \"---\"; sed -n '/bool D…
Fable
thinking
So `keepUpdated()` returning false when the view lacks a parent page is what's causing HLR to short-circuit and produce zero edges — not an issue with my fix itself. The real problem is the probe setup: the view never gets attached to a page because `page.addView()` fails due to the template not being configured. I need to adjust the probe to properly attach the view to a templated page so I can actually test whether HLR works correctly with my changes. Maybe I can bypass the template requirement by directly adding the view to the page's Views property, or the page might auto-adopt it. But stepping back, the core vector crash is already fixed—the missing HLR edges are really a symptom of the …
Fable
`keepUpdated()` returns false with no parent page, so HLR is skipped — that's why 0 edges (a test-harness setup issue, not my fix). The primary crash is fixed. Let me get HLR to actually run to fully verify. Let me check `DrawPage::addView`, the `KeepUpdated` property, and whether templates are even available in wasm.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && echo \"=== addView (why template needed) ===\"; sed -n '/DrawPage::addView/,/^}/p' DrawPage.cpp | head -40; echo \"=== KeepUpdated / canUpdate ===\"; rg -n…
Fable
thinking
I'm tracing where the "Template not set for Page" error originates—it's thrown by `getPageWidth()` when `addView()` tries to position a view without a template being set. I'm wondering if I can bypass the positioning step by passing `False` to `addView()`, though I need to verify whether the Python method supports that parameter.
Looking at alternatives: I could set the view's parent page directly by modifying the page's Views collection instead of using `addView()`, or I could assign a template to the page itself. The C++ side has `overrideKeepUpdated()` to force recomputation without a page context, but I'm not sure if that's exposed to Python. Since the main vector crash bug is already f …
Fable
The "Template not set" comes from `addView`'s positioning (`getPageWidth()`), not the view. `findParentPage()` only needs the view in the page's `Views` list. Let me check the `addView` Python signature and whether I can set `Views` directly / give the page a template.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/TechDraw/App && rg -n 'def addView|def ' DrawPage.pyi | head; echo \"=== getPageWidth (where template err throws) ===\"; sed -n '/DrawPage::getPageWidth/,/^}/p' DrawPage.c…
Fable
thinking
Templates are bundled into the wasm binary, so I can create a DrawSVGTemplate, point its Template property to a packed SVG file path, assign it to the page's Template, and then addView will execute with HLR running. Based on the font resource path I saw earlier, templates should be located at `/freecad/share/Mod/TechDraw/Templates/`.
Now I need to find the exact packed template path in the manifest and set up a test sequence: create the DrawSVGTemplate with the correct file path, assign it to the page, recompute to load the template dimensions, add the view, recompute again, then check visibleEdges.
Fable
Templates ARE packed in wasm. So I can give the page a real template and let HLR run. Let me find the exact packed template path.
Fable
⚙ Bash {"command": "grep -oh '\"[^\"]*Templates/A[^\"]*Landscape.svg\"' /home/magik6k/lcad-wasm/freecad-port/deploy/*.data.js 2>/dev/null | sort -u | head; echo \"=== full path prefix sample ===\"; grep -oh '\"/[^\"]*TechDraw/Templates/ASME/A4[^\"…
Fable
thinking
I'm noticing a discrepancy in the resource paths—the template is located at `/freecad/Mod/TechDraw/Templates/` according to the manifest, but the font error referenced `/freecad/share/Mod/TechDraw/Resources/fonts/`, suggesting the resource directory structure might be inconsistent or I'm looking at different installation layouts.
Since the template file definitely exists at the absolute path `/freecad/Mod/TechDraw/Templates/ASME/ANSIA_Landscape.svg` in the MEMFS, I'll update the probe to reference that directly and see if HLR can run with it.
Updating boot.py...
Fable
Template path in MEMFS is `/freecad/Mod/TechDraw/Templates/ASME/ANSIA_Landscape.svg`. Let me update the probe to attach a template so `addView` succeeds and HLR actually runs, verifying the recompute path end-to-end.
Fable
⚙ Edit {"replace_all": false, "file_path": "/home/magik6k/lcad-wasm/freecad-port/deploy/boot.py", "old_string": "page=step(\"addPage\", lambda: d.addObject('TechDraw::DrawPage','Page'))\nview=step(\"addView\", lambda: d.addObject('TechDraw::DrawVi…
Fable
⚙ Bash {"command": "node m0probe.mjs 2>&1 | grep -iE 'FCLOG TD |vector|abort' | head -40; echo \"=== probe exit ===\"", "description": "Re-run probe with template + HLR verification", "timeout": 120000}
Fable
thinking
The full pipeline is working end-to-end now—the DrawViewPart object constructs properly, the view gets added to the page, recomputation runs successfully, and the hidden line removal algorithm correctly identifies 4 visible edges when projecting the box orthographically along the Z-axis, which makes sense geometrically since a box viewed from above shows a square outline.
I'm leaving boot.py as the working verification probe since it demonstrates the fix is functional. For the getLineStandardsBody default to "ISO" when no files exist, that properly initializes the line style enums in ViewProviderViewPart, and on desktop where the choices list is populated, the original logic runs unchanged. …
Fable
Full pipeline verified: `TD addView: ok`, `TD addViewToPage: ok added`, `TD recompute: ok`, and **`TD visibleEdges: ok 4`** — the box projected along Z produces 4 HLR silhouette edges. The fix works end-to-end. Let me review my final diff.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD && git diff --stat src/Mod/TechDraw/ && echo \"=== DIFF ===\" && git diff src/Mod/TechDraw/App/LineGenerator.cpp src/Mod/TechDraw/App/Preferences.cpp", "description": "Show final …
Fable
The diff is clean and minimal. The fix is complete and verified. Here is my report.
## Root cause
`d.addObject('TechDraw::DrawViewPart','View')` threw `Base.FreeCADError: vector`.
The bare message `"vector"` is the tell: **emscripten uses LLVM libc++, whose `std::vector::at()` throws `std::out_of_range` with `what()` == literally `"vector"`** (unlike libstdc++'s verbose message). FreeCAD's Python layer wraps that std exception into `Base.FreeCADError`.
`addObject` with the GUI up constructs both the App object *and* its Gui ViewProvider. The App-side `DrawViewPart` ctor is clean, but `TechDrawGui::ViewProviderViewPart::ViewProviderViewPart()` (src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp:147) calls `LineGenerator::getLineStandardsBody()`:
- `src/Mod/TechDraw/App/LineGenerator.cpp:336` `getLineStandardsBody()` does `return getBodyFromString(choices.at(activeStandard));`
- `choices` comes from `getAvailableLineStandards()`, which scans the `Mod/TechDraw/LineGroup/` resource directory for `*.LineDef.csv` files.
- **Those CSV files are not present in the wasm MEMFS** (verified: 0 `LineDef`/`LineGroup` entries in any packed `.data.js` manifest; the build's `share/Mod/TechDraw/` has Patterns/Templates/Symbols but no LineGroup dir). So `choices` is empty.
- The existing out-of-range guard clamps `activeStandard` to `0`, but `choices.at(0)` on an *empty* vector still throws → `"vector"`.
This aborts construction of every DrawViewPart (and the section/detail subclasses) in the wasm build. DrawPage works because `ViewProviderPage` never touches LineGenerator. It is unrelated to the HLR serialization change — it fires purely in the ViewProvider constructor, before any recompute.
## Fix (all in src/Mod/TechDraw, desktop behavior unchanged)
- `App/LineGenerator.cpp` `getLineStandardsBody()`: if `choices.empty()`, return the `"ISO"` standards body instead of indexing an empty vector.
- `App/LineGenerator.cpp` `isProportional()`: guard `choices.empty()` and fix an off-by-one (`standardIndex > size()` → `>= size()`, which was itself a latent `.at()` overflow).
- `App/Preferences.cpp` `currentLineDefFile()` / `currentElementDefFile()`: guard empty/out-of-range `choices` and return an empty path (the CSV loaders already handle a failed open gracefully → solid lines).
On desktop `choices` is non-empty and valid, so the new branches are never taken; behavior is identical. Guards are correct on all platforms (an empty line-standards dir should never crash), so no `__EMSCRIPTEN__` gating was needed.
## Verification (rebuilt `bin/FreeCAD.js`, copied to deploy, ran m0probe)
```
FCLOG TD addView: ok <DrawViewPart object> <- was FreeCADError: vector, now constructs
FCLOG TD addViewToPage: ok added
FCLOG TD recompute: ok 2
FCLOG TD visibleEdges: ok 4 <- HLR produced the box's 4 silhouette edges
FCLOG TD hiddenEdges: ok 0
=== abort: null err: null
```
A `Part::Box` set as Source, projected along Z with the view attached to a page (given an ASME template + `KeepUpdated`), recomputes cleanly and yields 4 visible HLR edges — the synchronous-HLR path works end-to-end.
## Note for the caller (outside my scope)
The underlying resource gap remains: the `Mod/TechDraw/LineGroup/*.csv` files (and, at a different mount path, template SVGs at `/freecad/Mod/...` vs the resource dir `/freecad/share/...`) are not harvested into the wasm `.data` archives. TechDraw's own CMake install/copy rules for LineGroup (src/Mod/TechDraw/CMakeLists.txt:82-128) are correct, so the miss is in the external wasm data-packing step. My code fix makes TechDraw robust to their absence (lines render solid instead of dashed); packing the LineGroup CSVs would restore dash patterns but is not required to fix the crash. No files were committed. `deploy/boot.py` was left as the passing verification probe.