Skip to content

v1.2.0

Choose a tag to compare

@angeldeejay angeldeejay released this 03 May 00:59
· 3 commits to master since this release
  • Browser test suite optimizations — net result vs baseline: integration suite -41% wall-clock, -51% test time; core-fidelity file -63% per-file:
    • Switched page.goto() waitUntil from "networkidle" to "domcontentloaded" — eliminates the idle-network wait that was the single largest per-navigation cost (-62% per gotoSandbox() 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 — fires POST /__harness/restart which triggers Socket.IO harness:reload → shell calls core.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 in core-fidelity.
    • Added frame handle WeakMap cache in create-browser-commands.ts — avoids repeated page.frame() lookups within a single test.
    • Switched cleanupAllSandboxSessionRuntimes to parallel teardown via Promise.all for concurrent session cleanup.
    • Excluded .runtime-cache/ from the module file watcher — fixture stylesheet and state writes no longer trigger spurious harness:reload events during tests.
    • Fixed publishStageReady race in stage-bridge.ts — request-stage-snapshot handler now passes Boolean(core.lifecycleState?.domCreated) instead of a bare truthy check, eliminating a class of intermittent flaky failures.
  • 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 public stream.unref() / stream.ref() API on child.stdout and child.stderr (net.Socket instances). The internal _handle path was bypassing the public interface; the public path is stable, non-internal, and documented.
    • Added stdout.destroy() and stderr.destroy() after terminateChildProcess in the final pool teardown path — flushes the libuv uv_pipe_t handles so they release their event-loop ref immediately instead of waiting for the GC.
    • Registered killAllPooledServers() on process.exit, SIGINT, and SIGTERM with a __poolCleanupRegistered guard to prevent double-registration when both integration and ui projects import the module in the same Vitest worker process. Previously Ctrl+C mid-run left pooled servers alive in the background.
  • CI act compatibility — local pipeline runs via act no longer fail on the coverage-publish step:
    • Added !env.ACT to the Publish coverage artifacts branch step condition. The JamesIves/github-pages-deploy-action requires rsync and GitHub deploy tooling that are absent from local act Docker images; the guard skips the step cleanly when running under act.
    • Updated ci:act script to pass --rm — containers are removed automatically after each run without requiring a separate Docker cleanup command.
  • Upgraded ignore from ^5.3.2 to ^7.0.5. Used in server/watch.ts for .gitignore pattern matching; API surface (ignore(), .add(), .ignores()) is unchanged across major versions.
  • Excluded client/styles/ from npm 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.path now matches MM format — path is now modules/${name}/ (relative, trailing slash) matching main.js. Previously the sandbox used an absolute path with no trailing slash (/modules/${name}), causing this.data.path + filename concatenation to produce malformed URLs. module.file() is now implemented as (this.data.path + filename).replace(/\/\//g, "/"), a verbatim port of module.js.
    • lockStrings protocol fully implemented — hide(speed, cb, {lockString}) now pushes the lock string onto this.lockStrings; show(speed, cb, {lockString}) removes it and skips the show call when locks remain, matching module.js. Previously this.lockStrings was initialized but never read or written, so modules that hide via multiple independent actors (presence sensors, schedules, voice commands) would show prematurely on any show() call.
    • loadTemplates() respects module overrides and pre-warms Nunjucks — the base implementation now carries a typeof instance.loadTemplates === "function" guard (like getTemplate and getTemplateData) so a module that defines its own loadTemplates() 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 before start() is called, matching module.js boot-phase behavior.
    • MM.getModules() filter methods emit diagnostics on empty results — withClass(), exceptWithClass(), and exceptModule() now log a Log.warn when 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 Log test block now receives a silent no-op console so Log.* 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 returns Promise<HTMLElement> — the base implementation is now a verbatim port of module.js:82-107. Previously the sandbox returned a synchronous HTMLElement (or a deferred wrapper) when getDom was called via this._super(), masking bugs that surfaced only in real MagicMirror.
    • hide() and show() suspend/resume timing — suspend() and resume() now fire inside the animation callback after the transition completes, matching module.js:367-413. Previously they fired before the animation began.
    • module.hidden asymmetry — MM.hideModule sets module.hidden = true immediately before the animation (matching main.js:721); MM.showModule sets module.hidden = false only after the animation completes (matching main.js:729-731).
    • Header rendered as innerHTML — getHeader() return values are now inserted via innerHTML, not textContent, matching main.js:253. Modules that return HTML markup in getHeader() now render correctly.
    • getHeader() default simplified to return this.data.header — matches module.js:115-116. The previous sandbox-specific false/empty-string handling diverged from core.
    • Log object now complete — Log.debug, Log.group, Log.groupCollapsed, Log.groupEnd, Log.time, Log.timeEnd, Log.timeStamp, and Log.setLogLevel are now exposed, matching logger.js. Previously calling any of these threw TypeError: Log.X is not a function.
    • MM.getModules() filter methods — the returned array now carries .withClass(), .exceptWithClass(), .exceptModule(), and .enumerate() as non-enumerable properties, matching main.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, matching main.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 nunjucks WebLoader handles async template fetching as it does in core.

Full Changelog: v1.1.0...v1.2.0