Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 49932bd2ee3f581d06a012ead7fc043359098ce2
parent f0a3486d47ed0795be4c0a79a1a638d63ecf8547
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Sun,  4 Oct 2026 16:32:55 -0400

report-to-video: tests for the local-source tiers, --no-network and the bench's agreement with them

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Aumtool/lib/report/raw-cache.test.mjs | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mumtool/report-to-video/build-video.mjs | 21++++++++++++---------
Aumtool/report-to-video/sources.test.mjs | 357+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 470 insertions(+), 9 deletions(-)

diff --git a/umtool/lib/report/raw-cache.test.mjs b/umtool/lib/report/raw-cache.test.mjs @@ -0,0 +1,101 @@ +// The clip bench's view of what is cached: out/clips-raw plus the corpus's +// windows, listed by clipWindowDirs through the render's own sources.mjs -- +// so a window the bench calls fetched is one the build cuts from. +// +// Run with: pnpm test:scripts +import assert from "node:assert/strict"; +import test from "node:test"; +import path from "node:path"; +import os from "node:os"; +import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises"; + +import { rawCacheOf } from "./raw-cache.mjs"; +import { clipWindowDirs } from "../projects/report.mjs"; +import { resolveLocalSource } from "umtool-report-to-video/sources"; + +const SLUG = "demo-channel"; + +async function fixture() { + const root = await mkdtemp(path.join(os.tmpdir(), "raw-cache-")); + const projectDir = path.join(root, "project"); + const channelsDir = path.join(root, "channels"); + const rawDir = path.join(projectDir, "out", "clips-raw"); + await mkdir(rawDir, { recursive: true }); + const video = async (id) => { + const dir = path.join(channelsDir, SLUG, "data", id); + await mkdir(path.join(dir, "clips"), { recursive: true }); + return dir; + }; + return { root, projectDir, channelsDir, rawDir, video }; +} + +const touch = (p) => writeFile(p, Buffer.alloc(16, 1)); + +test("rawCacheOf with no corpus windows reads clips-raw alone", async () => { + const f = await fixture(); + try { + await touch(path.join(f.rawDir, "abc123_0.00-30.00.mp4")); + const c = await rawCacheOf(f.projectDir); + assert.equal(c.containing("abc123", 5, 10).name, "abc123_0.00-30.00.mp4"); + assert.equal(c.isFetched({ video: "abc123", start: 25, end: 31 }), false); + } finally { + await rm(f.root, { recursive: true, force: true }); + } +}); + +test("clipWindowDirs: corpus windows and a saved source, and the render resolves the same files", async () => { + const f = await fixture(); + try { + const a = await f.video("abc123"); + await touch(path.join(a, "clips", "100.00-160.00.mp4")); + await touch(path.join(a, "clips", "100.00-160.00.json")); + // A window on a drive that is not there is no window. + await symlink(path.join(f.root, "gone.mp4"), path.join(a, "clips", "300.00-400.00.mp4")); + + // A second video with its whole source in the store; its span is the cue + // doc's duration. + const b = await f.video("def456"); + const store = path.join(f.root, "store", SLUG, "def456"); + await mkdir(store, { recursive: true }); + await touch(path.join(store, "source-media.mp4")); + await writeFile(path.join(b, "saved-video.json"), JSON.stringify({ dir: store, file: "source-media.mp4", bytes: 16 })); + await writeFile(path.join(b, "transcript.cues.json"), JSON.stringify({ duration: 1800, cues: [] })); + + const m = { + provenance: { channelSlug: SLUG }, + timeline: [ + { id: "c1", type: "clip", video: "abc123", start: 110, end: 120 }, + { id: "c2", type: "clip", video: "abc123", start: 320, end: 330 }, + { id: "c3", type: "clip", video: "def456", start: 1000, end: 1010 }, + { id: "c4", type: "clip", video: "nope99", start: 1, end: 2 }, + ], + }; + const extra = await clipWindowDirs(m, f.channelsDir); + assert.deepEqual( + extra.map((e) => [e.video, e.windows.map((w) => [w.kind, w.name, w.from, w.to])]), + [ + ["abc123", [["corpus-window", "100.00-160.00.mp4", 100, 160]]], + ["def456", [["saved-video", "source-media.mp4", 0, 1800]]], + ], + ); + + const c = await rawCacheOf(f.projectDir, { extraWindows: extra }); + assert.equal(c.isFetched(m.timeline[0]), true); + assert.equal(c.isFetched(m.timeline[1]), false, "the dangling window is not fetched"); + assert.equal(c.isFetched(m.timeline[2]), true); + assert.equal(c.isFetched(m.timeline[3]), false); + + // The render, asked the same question, finds the same files. + const probe = async () => ({ duration: 1800 }); + for (const e of m.timeline.slice(0, 3)) { + const hit = await resolveLocalSource( + { video: e.video, slug: SLUG, from: e.start, to: e.end }, + { channelsDir: f.channelsDir, probe }, + ); + const bench = c.containing(e.video, e.start, e.end); + assert.equal(hit?.path ?? null, bench?.path ?? null, e.id); + } + } finally { + await rm(f.root, { recursive: true, force: true }); + } +}); diff --git a/umtool/report-to-video/build-video.mjs b/umtool/report-to-video/build-video.mjs @@ -603,7 +603,8 @@ export async function clipsNeedingFetch(entries, timeline, render, opts, local) const span = fetchSpan(entry, render, opts); if (await local.resolve(entry, span, { exact: !!opts.noReuse })) continue; out.push({ - index: timeline.indexOf(entry), + // By id: a variant's view may be a copy of the manifest's entry. + index: timeline.findIndex((e) => e === entry || (entry.id != null && e?.id === entry.id)), id: entry.id, slug: local.slugOf(entry), video: entry.video, @@ -3562,6 +3563,16 @@ export async function buildVideo({ manifestPath, opts = {}, out, only, fetchOnly return { out: r.out, failures: [] }; } + // `--no-network`: every clip's source is found on disk BEFORE anything is + // rendered, and a build that would have to fetch even one is refused here, + // naming each -- not twenty minutes in, at the first one. (The runs that + // rebuild no segment fetch nothing, so they are not asked.) + if (opts.noNetwork && !opts.chaptersOnly && !opts.railOnly && !opts.chromeOnly && !opts.chromePreview) { + const want = manifest.timeline.filter((e) => !only || e.id === only); + const missing = await clipsNeedingFetch(want, whole.timeline ?? [], render, opts, dirs.local); + if (missing.length) throw new Error(needsFetchMessage(missing)); + } + // Footer chrome is shared by every clip, so build it once up front. The deck // has none: it is the chrome. (`hyper` is the chart band's legacy switch, // which assertChrome already refuses beside a deck; the `!deck` says so here @@ -3743,14 +3754,6 @@ export async function buildVideo({ manifestPath, opts = {}, out, only, fetchOnly return { out: dirs.final, failures: [] }; } - // `--no-network`: every clip's source is found on disk BEFORE anything is - // rendered, and a build that would have to fetch even one is refused here, - // naming each -- not twenty minutes in, at the first one. - if (opts.noNetwork) { - const missing = await clipsNeedingFetch(entries, whole.timeline ?? [], render, opts, dirs.local); - if (missing.length) throw new Error(needsFetchMessage(missing)); - } - EMIT("start", { title: manifest.title, entries: entries.length, out: outDir }); for (let i = 0; i < entries.length; i += 1) { const entry = entries[i]; diff --git a/umtool/report-to-video/sources.test.mjs b/umtool/report-to-video/sources.test.mjs @@ -0,0 +1,357 @@ +// Tests for sources.mjs: where a clip's media is found on disk before the +// build fetches it, and the build's use of it (fetchSpan, --no-network). +// +// Run with: pnpm test:scripts +import assert from "node:assert/strict"; +import test from "node:test"; +import path from "node:path"; +import os from "node:os"; +import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises"; + +import { + SOURCE_KINDS, + TIERS, + channelsDirFor, + corpusWindowsOf, + rawWindowName, + readSavedVideoPointer, + resolveLocalSource, + windowsFromBareNames, +} from "./sources.mjs"; +import { + buildVideo, + clipsNeedingFetch, + fetchSpan, + findContainingWindow, + localSources, + needsFetchMessage, +} from "./build-video.mjs"; + +const SLUG = "demo-channel"; +const VIDEO = "abc123"; + +async function tree() { + const root = await mkdtemp(path.join(os.tmpdir(), "rtv-sources-")); + const rawDir = path.join(root, "out", "clips-raw"); + const channelsDir = path.join(root, "channels"); + const videoDir = path.join(channelsDir, SLUG, "data", VIDEO); + const clipsDir = path.join(videoDir, "clips"); + const storeDir = path.join(root, "store", SLUG, VIDEO); + for (const d of [rawDir, clipsDir, storeDir]) await mkdir(d, { recursive: true }); + return { root, rawDir, channelsDir, videoDir, clipsDir, storeDir }; +} + +const touch = (p) => writeFile(p, Buffer.alloc(16, 1)); + +// A probe that says how long a container is, and counts how often it is asked. +function fakeProbe(duration = 600, extra = {}) { + const calls = []; + const probe = async (file) => { + calls.push(file); + return { duration, ...extra }; + }; + return { probe, calls }; +} + +async function pointTo(videoDir, storeDir, file, extra = {}) { + await writeFile( + path.join(videoDir, "saved-video.json"), + JSON.stringify({ storedAt: "2026-01-01T00:00:00Z", dir: storeDir, file, bytes: 16, ...extra }), + ); +} + +test("the tiers are listed in the order they are consulted", () => { + assert.deepEqual(TIERS.map((t) => t.kind), SOURCE_KINDS); + assert.deepEqual(SOURCE_KINDS, ["raw-cache", "corpus-window", "saved-video"]); +}); + +test("rawWindowName is the build's cache key, two decimals always", () => { + assert.equal(rawWindowName(VIDEO, 7, 21.5), "abc123_7.00-21.50.mp4"); +}); + +test("raw-cache: the exact name first, else the tightest containing window", async () => { + const t = await tree(); + try { + await touch(path.join(t.rawDir, rawWindowName(VIDEO, 10, 20))); + await touch(path.join(t.rawDir, rawWindowName(VIDEO, 0, 60))); + await touch(path.join(t.rawDir, rawWindowName(VIDEO, 9, 25))); + // A video id that is a prefix of this one must not claim its files. + await touch(path.join(t.rawDir, rawWindowName(`${VIDEO}x`, 9, 21))); + const exact = await resolveLocalSource({ video: VIDEO, slug: SLUG, from: 10, to: 20 }, { rawDir: t.rawDir }); + assert.equal(exact.kind, "raw-cache"); + assert.equal(exact.name, "abc123_10.00-20.00.mp4"); + const tight = await resolveLocalSource({ video: VIDEO, slug: SLUG, from: 11, to: 22 }, { rawDir: t.rawDir }); + assert.equal(tight.name, "abc123_9.00-25.00.mp4"); + assert.equal(tight.windowStart, 9); + assert.equal(tight.windowEnd, 25); + // The re-export build-video has always offered answers the same. + assert.equal((await findContainingWindow(t.rawDir, VIDEO, 11, 22)).name, tight.name); + } finally { + await rm(t.root, { recursive: true, force: true }); + } +}); + +test("coverage is of the PADDED span, to WIN_EPS", async () => { + const t = await tree(); + try { + await touch(path.join(t.clipsDir, "10.00-20.00.mp4")); + const ask = (from, to) => + resolveLocalSource({ video: VIDEO, slug: SLUG, from, to }, { channelsDir: t.channelsDir }); + // The clip alone fits; the clip with a 3 s pad either side does not. + assert.ok(await ask(13, 17)); + assert.equal(await ask(10 - 3 + 0.5, 17 + 3), null, "the after-pad runs past 20"); + assert.equal(await ask(7, 17), null, "the before-pad starts before 10"); + // A 2 dp name read back a hair outside the request still counts. + assert.ok(await ask(9.99, 20.01)); + assert.equal(await ask(9.9, 20), null); + } finally { + await rm(t.root, { recursive: true, force: true }); + } +}); + +test("corpus-window: bare names, the editor's three extensions, never a sidecar or a partial", () => { + const got = windowsFromBareNames( + ["1.00-2.00.mp4", "1.00-3.00.mkv", "1.00-4.00.webm", "1.00-5.00.json", "1.00-6.00.part.mp4", "6.00-5.00.mp4", "x.mp4"], + "/c", + ); + assert.deepEqual(got.map((w) => w.name), ["1.00-2.00.mp4", "1.00-3.00.mkv", "1.00-4.00.webm"]); + assert.equal(got[0].path, path.join("/c", "1.00-2.00.mp4")); +}); + +test("precedence: raw-cache over corpus-window over saved-video, and no probe when a nearer tier hits", async () => { + const t = await tree(); + try { + await touch(path.join(t.storeDir, "source-media.mp4")); + await pointTo(t.videoDir, t.storeDir, "source-media.mp4"); + const { probe, calls } = fakeProbe(600); + const cfg = { rawDir: t.rawDir, channelsDir: t.channelsDir, probe }; + const want = { video: VIDEO, slug: SLUG, from: 100, to: 110 }; + + let hit = await resolveLocalSource(want, cfg); + assert.equal(hit.kind, "saved-video"); + assert.equal(calls.length, 1); + + await touch(path.join(t.clipsDir, "90.00-120.00.mp4")); + calls.length = 0; + hit = await resolveLocalSource(want, cfg); + assert.equal(hit.kind, "corpus-window"); + assert.equal(hit.windowStart, 90); + assert.equal(calls.length, 0, "the saved container is not probed when a window serves"); + + // Wider than the corpus window, and still preferred: it is this build's own. + await touch(path.join(t.rawDir, rawWindowName(VIDEO, 50, 200))); + hit = await resolveLocalSource(want, cfg); + assert.equal(hit.kind, "raw-cache"); + assert.equal(hit.windowStart, 50); + + // `kinds` narrows the tiers consulted. + hit = await resolveLocalSource(want, { ...cfg, kinds: ["corpus-window"] }); + assert.equal(hit.kind, "corpus-window"); + } finally { + await rm(t.root, { recursive: true, force: true }); + } +}); + +test("exact (--no-reuse): only the raw file named for exactly this span", async () => { + const t = await tree(); + try { + await touch(path.join(t.rawDir, rawWindowName(VIDEO, 0, 60))); + await touch(path.join(t.clipsDir, "0.00-60.00.mp4")); + const want = { video: VIDEO, slug: SLUG, from: 10, to: 20 }; + const cfg = { rawDir: t.rawDir, channelsDir: t.channelsDir, exact: true }; + assert.equal(await resolveLocalSource(want, cfg), null); + await touch(path.join(t.rawDir, rawWindowName(VIDEO, 10, 20))); + const hit = await resolveLocalSource(want, cfg); + assert.equal(hit.kind, "raw-cache"); + assert.equal(hit.name, "abc123_10.00-20.00.mp4"); + } finally { + await rm(t.root, { recursive: true, force: true }); + } +}); + +test("saved-video: the pointer's container is the window [0, probed duration], height from the pointer", async () => { + const t = await tree(); + try { + await touch(path.join(t.storeDir, "source-media.webm")); + await pointTo(t.videoDir, t.storeDir, "source-media.webm", { format: { preset: "video_720", height: 720 } }); + const p = await readSavedVideoPointer(t.videoDir); + assert.equal(p.path, path.join(t.storeDir, "source-media.webm")); + assert.equal(p.height, 720); + + const { probe } = fakeProbe(300.5, { height: 1080 }); + const cfg = { channelsDir: t.channelsDir, probe }; + const hit = await resolveLocalSource({ video: VIDEO, slug: SLUG, from: 250, to: 300 }, cfg); + assert.deepEqual(hit, { + kind: "saved-video", + path: path.join(t.storeDir, "source-media.webm"), + name: "source-media.webm", + windowStart: 0, + windowEnd: 300.5, + height: 720, + }); + // Past the container's end is not covered. + assert.equal(await resolveLocalSource({ video: VIDEO, slug: SLUG, from: 290, to: 310 }, cfg), null); + } finally { + await rm(t.root, { recursive: true, force: true }); + } +}); + +test("saved-video: a source-media container still in the video dir, through a relative link into media/", async () => { + const t = await tree(); + try { + const mediaDir = path.join(t.channelsDir, SLUG, "media", VIDEO); + await mkdir(mediaDir, { recursive: true }); + await touch(path.join(mediaDir, "source-media.mp4")); + await symlink(path.join("..", "..", "media", VIDEO, "source-media.mp4"), path.join(t.videoDir, "source-media.mp4")); + // Not a source: an audio-only container, and yt-dlp's scratch. + await touch(path.join(t.videoDir, "source-media.m4a")); + await touch(path.join(t.videoDir, "source-media.temp.mp4")); + const { probe, calls } = fakeProbe(120, { height: 480 }); + const hit = await resolveLocalSource({ video: VIDEO, slug: SLUG, from: 5, to: 15 }, { channelsDir: t.channelsDir, probe }); + assert.equal(hit.kind, "saved-video"); + assert.equal(hit.name, "source-media.mp4"); + assert.equal(hit.height, 480, "no pointer format, so the probe's height"); + assert.deepEqual(calls, [path.join(t.videoDir, "source-media.mp4")]); + } finally { + await rm(t.root, { recursive: true, force: true }); + } +}); + +test("a dangling link is not found -- never an error -- and the next candidate or tier answers", async () => { + const t = await tree(); + try { + // The tightest corpus window is a link to a drive that is not there. + await symlink(path.join(t.root, "unmounted", "w.mp4"), path.join(t.clipsDir, "10.00-20.00.mp4")); + await touch(path.join(t.clipsDir, "0.00-40.00.mp4")); + let hit = await resolveLocalSource({ video: VIDEO, slug: SLUG, from: 11, to: 19 }, { channelsDir: t.channelsDir }); + assert.equal(hit.name, "0.00-40.00.mp4"); + + // A pointer into an unmounted store and a dangling in-dir link: no source, + // and the probe is never asked about either. + await pointTo(t.videoDir, path.join(t.root, "unmounted-store"), "source-media.mp4"); + await symlink(path.join("..", "..", "media", VIDEO, "source-media.mkv"), path.join(t.videoDir, "source-media.mkv")); + const { probe, calls } = fakeProbe(9999); + hit = await resolveLocalSource({ video: VIDEO, slug: SLUG, from: 100, to: 110 }, { channelsDir: t.channelsDir, probe }); + assert.equal(hit, null); + assert.equal(calls.length, 0); + } finally { + await rm(t.root, { recursive: true, force: true }); + } +}); + +test("a container the probe cannot measure is no source rather than a guess", async () => { + const t = await tree(); + try { + await touch(path.join(t.storeDir, "source-media.mp4")); + await pointTo(t.videoDir, t.storeDir, "source-media.mp4"); + const hit = await resolveLocalSource( + { video: VIDEO, slug: SLUG, from: 0, to: 1 }, + { channelsDir: t.channelsDir, probe: async () => null }, + ); + assert.equal(hit, null); + } finally { + await rm(t.root, { recursive: true, force: true }); + } +}); + +test("no channel slug or no channels tree: only the raw cache can answer", async () => { + const t = await tree(); + try { + await touch(path.join(t.clipsDir, "0.00-40.00.mp4")); + assert.equal(await resolveLocalSource({ video: VIDEO, slug: null, from: 1, to: 2 }, { channelsDir: t.channelsDir }), null); + assert.equal(await resolveLocalSource({ video: VIDEO, slug: SLUG, from: 1, to: 2 }, { rawDir: t.rawDir }), null); + } finally { + await rm(t.root, { recursive: true, force: true }); + } +}); + +test("corpusWindowsOf: the corpus tiers' windows, tagged, present ones only", async () => { + const t = await tree(); + try { + await touch(path.join(t.rawDir, rawWindowName(VIDEO, 0, 10))); + await touch(path.join(t.clipsDir, "5.00-15.00.mp4")); + await symlink(path.join(t.root, "gone.mp4"), path.join(t.clipsDir, "1.00-2.00.mp4")); + await touch(path.join(t.storeDir, "source-media.mp4")); + await pointTo(t.videoDir, t.storeDir, "source-media.mp4"); + const got = await corpusWindowsOf({ video: VIDEO, slug: SLUG, channelsDir: t.channelsDir, probe: fakeProbe(90).probe }); + assert.deepEqual( + got.map((w) => [w.kind, w.name, w.from, w.to]), + [ + ["corpus-window", "5.00-15.00.mp4", 5, 15], + ["saved-video", "source-media.mp4", 0, 90], + ], + ); + } finally { + await rm(t.root, { recursive: true, force: true }); + } +}); + +test("channelsDirFor: declared, then the shadow tree, then the fallback", () => { + assert.equal(channelsDirFor("/p", { provenance: { channelsDir: "../c" } }, { fallback: "/g" }), path.resolve("/p", "../c")); + assert.equal(channelsDirFor("/p", {}, { shadowExists: true, fallback: "/g" }), "/p/.shadow-channels"); + assert.equal(channelsDirFor("/p", {}, { fallback: "/g" }), "/g"); +}); + +// ---- the build's side -------------------------------------------------------- + +test("fetchSpan: the extent widened by the pad, one edge at a time, clamped at 0", () => { + const e = { start: 10, end: 20 }; + assert.deepEqual(fetchSpan(e, {}), { from: 7, to: 23 }); + assert.deepEqual(fetchSpan(e, { fetchPad: 1 }), { from: 9, to: 21 }); + assert.deepEqual(fetchSpan(e, { fetchPad: 1 }, { pad: 5, padAfter: 2 }), { from: 5, to: 22 }); + assert.deepEqual(fetchSpan({ start: 1, end: 2 }, {}), { from: 0, to: 5 }); +}); + +test("clipsNeedingFetch: the clips no tier serves, with their timeline index", async () => { + const t = await tree(); + try { + await touch(path.join(t.clipsDir, "0.00-40.00.mp4")); + const timeline = [ + { id: "t0", type: "card" }, + { id: "c1", type: "clip", video: VIDEO, start: 10, end: 20 }, + { id: "c2", type: "clip", video: "def456", start: 10, end: 20 }, + { id: "c3", type: "clip", video: VIDEO, start: 38, end: 45 }, + ]; + const local = localSources({ rawDir: t.rawDir, channelsDir: t.channelsDir, channelSlug: SLUG }); + const missing = await clipsNeedingFetch(timeline, timeline, { fetchPad: 3 }, {}, local); + assert.deepEqual(missing.map((m) => [m.index, m.id, m.slug, m.video, m.from, m.to]), [ + [2, "c2", SLUG, "def456", 7, 23], + [3, "c3", SLUG, VIDEO, 35, 48], + ]); + const msg = needsFetchMessage(missing); + assert.match(msg, /--no-network: 2 clip\(s\)/); + assert.match(msg, /timeline\[2\] c2 {2}demo-channel\/def456 {2}7\.00–23\.00/); + assert.match(msg, /timeline\[3\] c3 {2}demo-channel\/abc123 {2}35\.00–48\.00/); + } finally { + await rm(t.root, { recursive: true, force: true }); + } +}); + +test("buildVideo --no-network refuses before rendering, naming every clip that would need a fetch", async () => { + const t = await tree(); + try { + await touch(path.join(t.clipsDir, "0.00-40.00.mp4")); + const manifestPath = path.join(t.root, "video.manifest.json"); + await writeFile(manifestPath, JSON.stringify({ + slug: "demo", + title: "Demo", + provenance: { channelSlug: SLUG, channelsDir: "channels", siteOrigin: "https://example.invalid" }, + render: { width: 1280, height: 720, fps: 30, fetchPad: 3 }, + timeline: [ + { id: "c1", type: "clip", video: VIDEO, start: 10, end: 20 }, + { id: "c2", type: "clip", video: "def456", start: 1, end: 4 }, + ], + })); + await assert.rejects( + buildVideo({ manifestPath, opts: { noNetwork: true } }), + (err) => { + assert.match(err.message, /1 clip\(s\) have no local source/); + assert.match(err.message, /timeline\[1\] c2 {2}demo-channel\/def456 {2}0\.00–7\.00/); + assert.doesNotMatch(err.message, /c1/); + return true; + }, + ); + } finally { + await rm(t.root, { recursive: true, force: true }); + } +});