Repository navigation
v1.2.0
- Browser test suite optimizations — net result vs baseline: integration suite -41% wall-clock, -51% test time;
core-fidelityfile -63% per-file:- Switched
page.goto()waitUntilfrom"networkidle"to"domcontentloaded"— eliminates the idle-network wait that was the single largest per-navigation cost (-62% pergotoSandbox()call, -19% integration wall-clock). - Added server pool — each Vitest worker acquires a warm server on first use and holds it across all test sessions in that worker, skipping cold-start for every test after the first per file (-25% wall-clock, -24% ui wall-clock).
- Added
resetSandbox()browser command — firesPOST /__harness/restartwhich triggers Socket.IOharness:reload→ shell callscore.reloadStage()→ stage iframe reloads without a full page navigation. Per-call cost drops from ~280 ms (gotoSandbox) to ~99 ms (-65%). Applied to the eight repeat-reset tests incore-fidelity. - Added frame handle
WeakMapcache increate-browser-commands.ts— avoids repeatedpage.frame()lookups within a single test. - Switched
cleanupAllSandboxSessionRuntimesto parallel teardown viaPromise.allfor concurrent session cleanup. - Excluded
.runtime-cache/from the module file watcher — fixture stylesheet and state writes no longer trigger spuriousharness:reloadevents during tests. - Fixed
publishStageReadyrace instage-bridge.ts—request-stage-snapshothandler now passesBoolean(core.lifecycleState?.domCreated)instead of a bare truthy check, eliminating a class of intermittent flaky failures.
- Switched
- Child process management hardening — prevents orphan Node.js server processes from accumulating across test runs and on Ctrl+C:
- Replaced internal
(stream as any)._handle?.unref?.()/._handle?.ref?.()with the publicstream.unref()/stream.ref()API onchild.stdoutandchild.stderr(net.Socketinstances). The internal_handlepath was bypassing the public interface; the public path is stable, non-internal, and documented. - Added
stdout.destroy()andstderr.destroy()afterterminateChildProcessin the final pool teardown path — flushes the libuvuv_pipe_thandles so they release their event-loop ref immediately instead of waiting for the GC. - Registered
killAllPooledServers()onprocess.exit,SIGINT, andSIGTERMwith a__poolCleanupRegisteredguard to prevent double-registration when bothintegrationanduiprojects import the module in the same Vitest worker process. Previously Ctrl+C mid-run left pooled servers alive in the background.
- Replaced internal
- CI
actcompatibility — local pipeline runs viaactno longer fail on the coverage-publish step:- Added
!env.ACTto thePublish coverage artifacts branchstep condition. TheJamesIves/github-pages-deploy-actionrequiresrsyncand GitHub deploy tooling that are absent from localactDocker images; the guard skips the step cleanly when running underact. - Updated
ci:actscript to pass--rm— containers are removed automatically after each run without requiring a separate Docker cleanup command.
- Added
- Upgraded
ignorefrom^5.3.2to^7.0.5. Used inserver/watch.tsfor.gitignorepattern matching; API surface (ignore(),.add(),.ignores()) is unchanged across major versions. - Excluded
client/styles/fromnpm run format— all files in that directory are generated at build time and must not be reformatted. - Additional MagicMirror fidelity fixes identified by a second-pass divergence audit:
module.data.pathnow matches MM format —pathis nowmodules/${name}/(relative, trailing slash) matchingmain.js. Previously the sandbox used an absolute path with no trailing slash (/modules/${name}), causingthis.data.path + filenameconcatenation to produce malformed URLs.module.file()is now implemented as(this.data.path + filename).replace(/\/\//g, "/"), a verbatim port ofmodule.js.lockStringsprotocol fully implemented —hide(speed, cb, {lockString})now pushes the lock string ontothis.lockStrings;show(speed, cb, {lockString})removes it and skips the show call when locks remain, matchingmodule.js. Previouslythis.lockStringswas initialized but never read or written, so modules that hide via multiple independent actors (presence sensors, schedules, voice commands) would show prematurely on anyshow()call.loadTemplates()respects module overrides and pre-warms Nunjucks — the base implementation now carries atypeof instance.loadTemplates === "function"guard (likegetTemplateandgetTemplateData) so a module that defines its ownloadTemplates()is no longer silently overwritten. The default base implementation now pre-warms the Nunjucks template cache by rendering the file template with an empty data set beforestart()is called, matchingmodule.jsboot-phase behavior.MM.getModules()filter methods emit diagnostics on empty results —withClass(),exceptWithClass(), andexceptModule()now log aLog.warnwhen they return an empty collection, explaining that the sandbox is a single-module environment and that the module's intent is recorded but cannot be simulated. Previously these methods silently returned empty arrays, making it opaque that the sandbox does not support sibling-module coordination.
- D8 test suite no longer produces console noise — the VM context for the
installGlobals creates Logtest block now receives a silent no-op console soLog.*method invocations during the test do not write to stdout/stderr. - Browser runtime now matches MagicMirror core behavior exactly across all divergences identified by a full contract audit (
client/runtime/module.ts,lifecycle.ts,notifications.ts):getDom()always returnsPromise<HTMLElement>— the base implementation is now a verbatim port ofmodule.js:82-107. Previously the sandbox returned a synchronousHTMLElement(or a deferred wrapper) whengetDomwas called viathis._super(), masking bugs that surfaced only in real MagicMirror.hide()andshow()suspend/resume timing —suspend()andresume()now fire inside the animation callback after the transition completes, matchingmodule.js:367-413. Previously they fired before the animation began.module.hiddenasymmetry —MM.hideModulesetsmodule.hidden = trueimmediately before the animation (matchingmain.js:721);MM.showModulesetsmodule.hidden = falseonly after the animation completes (matchingmain.js:729-731).- Header rendered as
innerHTML—getHeader()return values are now inserted viainnerHTML, nottextContent, matchingmain.js:253. Modules that return HTML markup ingetHeader()now render correctly. getHeader()default simplified toreturn this.data.header— matchesmodule.js:115-116. The previous sandbox-specific false/empty-string handling diverged from core.Logobject now complete —Log.debug,Log.group,Log.groupCollapsed,Log.groupEnd,Log.time,Log.timeEnd,Log.timeStamp, andLog.setLogLevelare now exposed, matchinglogger.js. Previously calling any of these threwTypeError: Log.X is not a function.MM.getModules()filter methods — the returned array now carries.withClass(),.exceptWithClass(),.exceptModule(), and.enumerate()as non-enumerable properties, matchingmain.js:501-585. Previously the sandbox returned a plain array without these helpers.notificationReceived()receives the original payload — the sandbox previously passed a JSON clone of the payload. The original object reference is now forwarded, matchingmain.js:98-101. The clone is still used for the debug log entry only.- Template preloading layer removed —
loadTemplates()now resolves immediately. The deferred-wrapper and sync-preload machinery (createDeferredGetDomWrapper,preloadTemplateDependencyTree,seedTemplateLoaderCache,renderPreloadedTemplate,attachDeferredDomReady,hasPreloadedTemplate,readTemplateSource,extractTemplateDependencies,resolveTemplateDependencyPath,isGetDomSuperCall) has been deleted. The nunjucksWebLoaderhandles async template fetching as it does in core.
Full Changelog: v1.1.0...v1.2.0