CAM mode's exporter treats the tool as continuously on from the first
cut to the last (correct for a spinning router bit, just lifted via Z
between cuts) - it has no way to turn a tool off between separate
disconnected cut regions. A real plasma torch may need to be genuinely
off during rapid travel between cuts, not just raised, matching
CMMS/ATG's own SteelTailor table's proven-working G-code (M07/M08
bracketing every individual feature, reverse-engineered from a real
working file, not a manual's spec).
Added a device.gcodeSpindleOff hook: when a profile sets it to a
non-empty array, every disconnected toolpath group re-triggers the
spindle-on command (forcing newOp regardless of operation-type changes)
and fires gcodeSpindleOff right after each group finishes - mirrors the
existing laser on/off mechanism, generalized. Every device profile that
doesn't set gcodeSpindleOff (i.e. every profile that existed before
this) behaves exactly as before - this is a pure opt-in addition, not a
behavior change to anything else.
New profile: SteelTailor.Plasma.json - G90/absolute (controller's own
touchscreen sets the origin, so gcodePre doesn't redefine it), M07/M08
via the new per-cut mechanism, no G41/G40 cutter comp (Kiri already
offsets toolpaths in software via the operation's own tool-diameter
setting - stacking controller-side comp on top would double-compensate).
Bed size/max height are placeholders pending real numbers.
restoreOrCreate() revives whatever document this browser last had open in
IndexedDB - the opposite of what a job-scoped CMMS launch wants (load
this exact file, not whatever was last open in this browser, per the
comment already above in init()). It only looked fixed on a browser with
no prior Mesh:Tool history (falls through to a blank "Untitled" doc in
that case) - any browser with real history would have shown a stale
name instead of the real filename.
document_new({name}) is what actually creates a fresh document with a
given name (mirrors what File > New does), so this uses that instead,
and drops the now-redundant separate set_doc_name() call since
document_new() already does that itself.
restore_space() (the normal boot path, restoring a previous IndexedDB
session) is intentionally skipped for a CMMS-scoped launch - but its
finalization tail (hide the loading curtain via boot_done(), enable
handles, publish app_ready) only ever ran inside restore_space() itself.
Skipping it meant the curtain never went away even after cmms_load_from_url
fetched and loaded the file successfully underneath it. app_ready
subscribers never fired either - mesh/history.js's undo-tracking enable,
mesh/build.js's doc-name label - a second, less visible bug from the same
cause.
Wrapped in try/finally so this always runs, even on a failed fetch -
a file that can't load shouldn't leave the user stuck behind the
curtain either. Also calls api.document.restoreOrCreate() first, since
restore_space() always does that before any file-load in normal use.
Cross-Origin-Opener-Policy describes a top-level document's opener
relationship and has no defined meaning for a nested document, but
addCorsHeaders() set it unconditionally on every response - including
when this app is loaded inside an iframe by a host like CMMS. That
turned out to silently break postMessage delivery into the frame: a
well-formed same-origin message with a correct explicit targetOrigin
simply never arrived, with zero error anywhere. Confirmed via CMMS's
cam-edit.php/mesh-edit.php integration: worked perfectly visited
directly (top-level), never worked embedded - consistent across
multiple browsers and devices, ruling out a client-side cause.
Both Chrome and Firefox send Sec-Fetch-Dest: iframe on a request for an
embed, so COOP is now skipped only for that case. COEP (what
SharedArrayBuffer actually needs) stays on unconditionally either way,
and a direct top-level visit (this app's other real use, e.g. as an
installable PWA) keeps full cross-origin isolation - no regression to
the working case, fixes the broken one.
A parent iframe cannot inject window.MESH_TOOL_CONFIG into this app's
window before init() runs - by the time the iframe's load event fires
(the only cross-frame signal available for a same-origin page this app's
own server generates, not the CMMS), init() has already executed and
already decided to call restore_space() instead. Query params are read
synchronously during init() itself, before the existing 'hide url params'
cleanup strips them - self.MESH_TOOL_CONFIG is still set from them so
util.download() (src/mesh/build.js) has one stable place to read from.
Additive only, gated entirely on window.MESH_TOOL_CONFIG being present (set
by the CMMS's public/mesh-edit.php host page, absent everywhere else -
standalone use is unaffected).
- src/main/mesh.js: cmms_load_from_url() fetches bytes from the
host-provided loadUrl and feeds them through the existing load_files()
path (same one drag-and-drop and the Import button use) - skips
restore_space() when embedded, since job-scoped launch means "load this
exact file", not whatever was last open in this browser's IndexedDB.
- src/mesh/build.js: util.download() calls cmms_save_to_host() instead of
triggering a browser download when embedded - POSTs to the same
job/manufacturing_project-scoped save endpoint pattern as the CAD editor
fork's post_bytes_to_host().
- Update base image from node:18-slim to node:22-slim (matching
package.json engines requirement)
- Add git to apt-get install (needed by npm dependencies)
- Add webpack-ext step (builds external bundles)
- Add mkdir -p src/pack (prevents race condition)
- Add pack-prod step (builds application bundles)
- Reorder: build steps before gs-app-server install
Tested on ARM64 (Raspberry Pi 4, Docker 29.4.0) and x86_64 (Ubuntu
24.04, Docker 28.0.4). Without these changes, the container starts
but serves an incomplete app that never finishes loading.
- Add await to fs.mkdir() call in generateDevices() (line 60). Without
this, writeFile can race ahead of directory creation, causing ENOENT
on clean builds or slower machines.
- Add 'node:module' to the externals array. Node.js treats 'module' and
'node:module' as distinct specifiers; esbuild fails to resolve the
prefixed form when bundling for the browser target.
Discovered during Docker builds on ARM64 where the race window is wider
due to slower I/O. Also reproduces on x86_64 with a clean src/pack/.