mirror of
https://github.com/stablyai/orca.git
synced 2026-10-29 00:03:37 +00:00
* feat(search): bundle ripgrep for local, WSL, and SSH search Ship @vscode/ripgrep-universal's prebuilt rg for all six relay platforms in every desktop artifact. Local and WSL searches spawn the bundled binary and drop the git ls-files / git grep fallbacks; SSH deploys upload the remote's binary once per ripgrep version and the relay prefers it over PATH rg. * fix(search): address bundled ripgrep review findings - Key the SSH ripgrep cache on the binary's content hash; a package bump is the only update step - glibc verifier: read arch tokens below the slice root and accept static ELFs (arm64 release blocker) - Ship ripgrep/PCRE2/musl license notices; bundle rg with orcad - Packaged builds never spawn a bare rg; report fd pressure as transient - SSH: install rg before sweep/GC, size-validate installs, back off instead of disabling on launch failure - Scope Dependabot to @vscode/ripgrep-universal; revert unrelated lockfile churn * chore(search): drop bundled-ripgrep reference doc; assert full packaging layout parity * refactor(search): one entry point for spawning the bundled ripgrep Local Quick Open, Quick Open path search, the Explorer name filter, and runtime text search each repeated the same three steps: resolve the bundled command, spread in the WSL distro, spread in the WSL shell expression. Fold that into spawnBundledRipgrep so one place owns the rule that a bare 'rg' must never reach spawn, and simplify the resolver's command/packaged checks. Restore the AGENTS.md ripgrep rule dropped alongside its reference doc in63f4dac, and note why the relay's availability probe may spawn a bare 'rg'. No behaviour change; verified by the existing suites plus a new test that pins the local, WSL-routed, and distro-routed-but-Windows-output cases. * refactor(search): drop the local install-ripgrep path; enforce the rg rule Bundling rg removed the local git/readdir fallback, so nothing can produce the "install ripgrep on the host running the Quick Open scan" guidance any more -- only a remote host an upload never reached still reaches the capped listing. Drop the host parameter, the renderer's local branch and its translation key, and the relay wrapper that existed only to pass 'remote'. Add a ratchet test for bare 'rg' spawns, since the AGENTS.md rule alone had nothing enforcing it. Its one allowlist entry is the relay's PATH probe, which asks about PATH by definition. Verified the guard catches a planted offender rather than passing vacuously. Also stop chaining the remote cleanup sweep behind the ripgrep upload: on a cold host that is a multi-MB transfer, and stale upload stages and superseded version dirs were left on the remote for its whole duration. The two touch different trees, so they now run concurrently. * test(ssh): pin that the cleanup sweep does not wait on the ripgrep upload * fix(search): derive rg spawn types instead of importing node:child_process A type-only import still counts against the child_process ratchet, whose pin and allowlist only ever shrink. Derive both types from wslAwareSpawn instead. * fix(search): surface an unreachable WSL workspace instead of an empty result Inside `bash -c`, a failed `cd` exits 1 -- the same code ripgrep uses for "no matches" -- so a WSL workspace whose directory had gone away reported an empty listing as a successful scan. main did not have this hole: checkRgAvailable ran the same `cd` wrapper first and settled on `code === 0`, diverting to the git fallback that this PR deletes. The WSL wrapper now takes an optional cwdFailureExitCode; rg passes 97, and all four close handlers reject with a clear error before the unavailable check can blame the install. Also from review: - Bound the fire-and-forget ripgrep upload with deploySignal. The controller aborts only on the deploy timeout, never on success, so this cancels a still-running upload when the deploy gives up. - Run the stale-stage sweep before the installed check rather than inside its else branch. Once rg was installed every later deploy took the PRESENT path, so a stage orphaned by a dropped connection was never collected again. - Note in orcad-remote-deploy.ts why wiring it up needs ripgrep work first: build-orcad.mjs copies only the build host's rg, and orcad reports isPackaged() === true, so a remote of another platform would find nothing. ssh-relay-deploy.test.ts sat at the max-lines cap, so any edit to it failed the gate. Split the four Windows named-pipe deploys into their own file (926 -> 737 + 333); both are now well clear of it. * fix(search): name the unreachable root in every handler, not three of four Round-two review caught that the missing-cwd branch in scanRipgrepPaths sat AFTER isRipgrepUnavailableExit, which classifies any code above 2 as a broken install -- so for exit 97 it was dead code and Quick Open still told the user to reinstall Orca. Reordered; all four handlers now check it first. Also from review: - A vanished workspace makes spawn fail with ENOENT, which read as a damaged install on every local path. Confirm the cwd with isRipgrepSpawnCwdUsable -- the guard the relay already applies -- before blaming the binary. The async continuation re-checks `resolved`, because finish() drops its argument once settled and the rejected promise would otherwise go unhandled. - bundledRipgrepCommand returned a bare 'rg' for an arch outside the bundled set, bypassing the guard that exists so Windows cannot resolve a bare name against the repo cwd. A packaged app now always names an absolute path. Drop ci-shards/unit-assignment.json, a 9,425-line CI artifact swept in from reproducing a shard locally, and gitignore the directory that produced it. The "rg genuinely cannot start" test pointed at a synthetic /repo, which the new guard correctly reports as unreachable; it now resolves to a real root so it still tests what its name says. * fix(search): let the error handler own the spawn-failure verdict A failed spawn emits 'error' and THEN 'close' with a negative code. The cwd check added in the error handler did not settle, so the close handler settled first -- synchronously, with the reinstall message -- and won the race every time. The branch was not merely flaky, it was unreachable in all four handlers: it is guarded by pid === undefined, which is exactly the case that always produces a following close(code < 0). Verified against a real spawn: 3/3 runs give error(ENOENT) -> close(-2). The error handler now detaches 'close' before the probe, so it owns the outcome. The probe also had no rejection handler, so a probe that rejected left the search unsettled forever -- a hang, not just a wrong message. It now falls back to the prior verdict rather than inventing one. Tests: filesystem-search-rg-timeout and orca-runtime-files-search already cover error-first and close-first, but against synthetic roots that the new guard correctly calls unreachable; they now resolve to a real root, keeping each test's stated intent. Added a Quick Open case for the vanished-workspace path and confirmed it fails with the old ordering. * test(search): cover exit code 97 in all four ripgrep close handlers Round-four review found the missing-cwd branch had zero handler coverage: no test anywhere emitted close(97), only -2/0/1/2/127. Ordering was correct, but guarded by source-line order alone -- and that exact ordering was wrong in three of four handlers two commits ago. Each suite now drives close(97) through its real handler and expects the unreachable-root message. Verified the tests earn their place: neutering the missing-cwd check fails exactly four tests, one per handler. Also drop a Reflect.get the anti-slop gate rejects, in favour of `in` narrowing. * docs(search): stop claiming the close handler always wins the race The previous commit asserted close "would beat this threadpool round-trip every time", from an n=3 sample that measured event ordering -- which was never in dispute -- rather than probe-vs-close. Two later measurements disagree with each other: 50/50 close-first here, 30/50 probe-first in review. Either way it is a race on a sub-millisecond margin, and the detach is what makes the verdict deterministic. Why this wording matters: "close wins every time" is an argument for deleting the detach as a guard against an impossible race. No test would catch that -- the suites emit error and close in the same synchronous tick. * chore(search): ship the jemalloc and libunwind notices the Linux rg needs The statically linked Linux builds carry jemalloc (BSD-2-Clause) and LLVM libunwind (Apache-2.0 WITH LLVM-exception) in addition to PCRE2 and musl, and both require their notice on binary redistribution. Confirmed with `strings`: their symbols are present in linux-x64 and linux-arm64 and absent from the darwin and win32 builds. Texts taken from the upstream canonical sources. extraResources already copies the whole licenses directory, so these ship without a packaging change. * fix(relay): stop spawning a bare rg, name unreachable roots, collect old builds Three gaps the reviews surfaced on the remote side, all pre-existing on main. Bare `rg` on Windows remotes. Both relay spawn sites pass the user's repo as cwd, and CreateProcessW searches the cwd before PATH -- the same hijack the desktop side already fixes. The relay now walks PATH itself and spawns an absolute rg.exe, skipping relative PATH entries because those resolve against the cwd. No rg on PATH yields null, which callers treat as "ripgrep unavailable" rather than handing spawn a bare name. POSIX keeps the bare name: execvp never consults the cwd, so there is nothing to resolve and nothing to gain. With the last probe converted, the bare-spawn ratchet allowlist is empty. Empty results for an unreachable root. settleLaunchFailure resolved an empty, successful-looking scan when the root was gone but PATH rg existed, and the git/readdir chain never engaged because it only triggers on RipgrepUnavailableError. Both relay paths now reject naming the root, matching local workspaces. Missing-rg keeps precedence over a missing root, because only that verdict engages the fallback chain -- two tests pinned that deliberately and it would have been wrong to flip it. Unbounded ~/.orca-remote/ripgrep/. Nothing collected this tree; the relay's version GC only matches `relay-*`, so every rg bump left another ~5 MB per host forever. The probe command now also drops sibling builds older than two weeks, sparing the current one and live upload stages, on POSIX and PowerShell alike. Two weeks because a client pinned to an older build may still be using it; the cost of collecting one early is that client re-uploading once. * fix(relay): probe the rg that failed, and close the drive-relative PATH hole Five review findings against the previous commit, all reproduced first. The launch-failure classifier probed PATH rg, but the spawn that failed was the bundled binary. On the normal remote setup -- no rg on PATH, which is why Orca uploads one -- the probe failed and a moved workspace was reported as a missing ripgrep, telling the user to install what Orca already ships. So the fix was inert on exactly the hosts the uploader exists for. It now takes a candidate list and asks the binary that actually failed first, then PATH. path.win32.isAbsolute accepts `\tools` and `/tools`: rooted, but carrying no drive, so they resolve against whatever drive the process is on. The probe would have validated one against the relay's drive while the spawn, running with the user's repo as cwd, resolved it against the repo's -- the same cwd-dependence this lookup removes, narrowed from directory to drive. A real drive letter or UNC root is now required. probeRipgrepVersion had lost the timeout's kill in the rewrite, leaking a live process and a ref'd handle per launch failure -- for a hang, which is the very case the bundled-rg back-off exists for. It also spawned without windowsHide, which would flash a console; fixing that made an allowlist entry stale, so the entry is gone and the pin ratchets down 63 -> 62. `windowsPathRipgrep ??= …` never memoised a miss, because null is nullish. The caching was inverted against cost: a hit stops at the first directory, a miss stats every one, and only the miss was repeated -- per spawn. The bare-spawn ratchet claimed "nothing in production spawns a bare rg", which is false on POSIX. It now also matches PATH_RIPGREP_COMMAND at a spawn site, and the comment states plainly what a textual guard cannot see: the POSIX bare name reaches spawn as a parameter, and is safe because execvp ignores the cwd. The drive-rooted predicate is tested directly rather than through the filesystem -- a temp dir on a POSIX CI host has no drive letter to exercise win32 semantics with, so the filesystem test could never have caught this. * test(mobile): repin the session closure past #22452's two shared modules Merging main brought the closure to 4220 against a pin of 4218. The two extra modules are `src/shared/agent-turn-outcome.ts` and `src/shared/main-agent-status.ts` from #22452, which the status projection this route already reaches import. That change was src/shared-only, so the mobile job never ran on it -- the same way the structured tool line slipped past, as the ledger above already records. Repinned here because this PR's file set is what next made the job run, not because this PR reaches either module. Verified: of the 28 source files this branch changes, none appear anywhere in the route's 4220-module closure. * fix(search): preserve remote binaries and complete runtime packaging * test(relay): pin the probe's env now that it inherits the relay's PATH8d6759athreaded the relay env into probeRipgrepVersion -- correctly, since the probe decides whether a launch failure was the binary or the root and so has to resolve the same rg the failed spawn would have. It left the assertion that pins the probe's spawn arguments behind, which is what CI caught. Asserting buildRelayCommandEnv() rather than loosening the match to any object: under process.env the probe could resolve a different rg, or none, which is the regression the change exists to prevent. * feat(ssh): collect remote ripgrep builds by reference, not by age Nothing collected `~/.orca-remote/ripgrep/`: the version GC matches only `relay-*`, so every change to the shipped bytes left another ~5 MB on every SSH host, permanently. The age window this replaces was the wrong instrument -- a directory's mtime is when it was written, not when it was last used, so it cannot tell a superseded build from the one a live relay was launched against. Deleting the latter is not graceful degradation: without a PATH ripgrep remote text search rejects outright, and listing drops to the capped walk this PR exists to remove. So the question is reference. Each relay directory now records the build it runs against in `.ripgrep-ref`, written only once that binary is confirmed present, and the GC collects a build only when no installation names it. The discipline is ssh-relay-native-deps-cache-gc.ts': anything the pass cannot account for blocks the whole pass. A relay directory with no readable marker is an older Orca's, possibly running right now against a binary it never recorded, so the pass declines rather than guessing. Those directories are removed by the version GC in time, which is what makes their builds collectable -- hence running after it, not beside it. Deletion is the same tombstone, recheck under the rename, then remove, so a deploy that takes a reference mid-pass gets its tree restored. Windows has no pass yet, matching the native-deps cache's gate. One test note: the first version of the "unaccountable blocks the pass" test passed against a deliberately broken guard, because the tombstone recheck masked its absence. The test now puts a readable recheck behind an unreadable first scan, which is the only shape that fails when that guard is removed. Recording the reference lives inside ensureRemoteBundledRipgrep rather than at the call site: it is the same concern, and it keeps the deploy's ripgrep surface to one call for the tests that mock it to protect their exec queues. * feat(ssh): collect Windows remotes too, and ship the Rust crate notices Three items previously left documented-but-open. Windows remote accumulation. The cache GC was POSIX-gated, so the leak did not go away -- it moved to the platform with the larger binary (rg.exe is 5.43 MB on win32-x64, against 4.77 MB for linux-arm64). The PowerShell dialect now does the same reference scan: entries and references carry token prefixes, because PowerShell writes every uncaptured value to stdout and an untokenised listing would feed Remove-Item whatever a cmdlet happened to emit. Verified on a real Windows host rather than a mock: the listing emits its ENTRY/LIST_OK tokens, a relay directory carrying a marker yields REF <entry>, and a relay directory without one yields REFS_ERR -- the safety path, on the real interpreter. Rust crate notices. The crate set was read out of the shipped binary's symbols and the licence identifiers taken from crates.io rather than assumed. Where a crate offers the Unlicense, Orca elects it: a public-domain dedication carries no notice obligation, and that covers eight of them. The four that do not offer it get their MIT text reproduced. encoding_rs carries a BSD-3-Clause notice for its WHATWG-derived encoding data that is joined by AND, not OR, so electing MIT does not discharge it. Release-only validation, corrected rather than repeated. Linux AppImage/deb/rpm already runs in CI's package job on every PR, and Windows signing was already rehearsed on this branch. macOS notarization is the only item a release must still exercise, and the exposure is narrow: notarization requires signatures on Mach-O binaries, and of the six bundled builds only the two darwin ones are Mach-O -- `file` reports ELF for linux and PE32+ for win32 -- so signIgnore excludes only files the notary never asks about. orcad-artifacts.test.ts caught the new notice file missing from the standalone runtime's shipped list, which is exactly the gap that test exists to catch: a notice committed to the repo but never actually shipped. * fix(search): protect relay cache references and handle failed spawns * fix(ripgrep): close review gaps and repair deployment fixtures * test(mobile): refresh merged session module census * fix(ssh): preserve ripgrep caches with empty legacy references * test(mobile): assert bundle boundaries instead of global module count
563 lines
33 KiB
JavaScript
563 lines
33 KiB
JavaScript
import { join } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { mobileWebAppRouteClosure } from './build-mobile-web-app-bundle.mjs'
|
|
import { mobileWebAppDependenciesPresent } from './mobile-web-app-bundle-dependencies.mjs'
|
|
import { mobileWebAppRouteChunkClosure } from './mobile-web-app-route-chunk-closure.mjs'
|
|
import {
|
|
editableHostFontSizeOffenders,
|
|
editableHostsIn,
|
|
unresolvedEditableHostStyles
|
|
} from './mobile-web-app-editable-host-font-size.mjs'
|
|
import {
|
|
textInputFontSizeOffenders,
|
|
unresolvedTextInputStyles
|
|
} from './mobile-web-app-text-input-font-size-seam.mjs'
|
|
|
|
/**
|
|
* What putting the terminal on the page costs the session route's closure.
|
|
*
|
|
* The route is not served on the page until C7.7 — its module is still the native switch and
|
|
* there is no `.web.tsx` beside it — but the closure the bundler would walk is the same one, and
|
|
* the terminal is by far the largest thing in it. Measured here so the trade is a number rather
|
|
* than a claim, and so that a later change cannot quietly put the engine string back.
|
|
*
|
|
* Re-anchored on main at 4a3a32206d, the squash that landed C7.5b, and re-measured there. The
|
|
* reading has been re-taken at each merge rather than adjusted, because the arithmetic keeps not
|
|
* working: main has re-pinned this count three times for modules that arrived from three other PRs,
|
|
* and a number carried forward would have been wrong about every one of them.
|
|
*
|
|
* modules 4286 -> 4328 (+42)
|
|
* local modules 936 -> 978 (+42)
|
|
*
|
|
* The +42 is this lane's, and it is 43 modules in and one out. Out:
|
|
* `terminal-webview-document-factory.generated.ts`, the one emitted file C7.5b's page imported,
|
|
* which carried the whole document. In: the document's 39 source modules the page imports directly
|
|
* under ruling 25, the three page modules their tap group reaches — `terminal-webview-url-tap`,
|
|
* `terminal-path-tap` and `terminal-file-url-tap` — which the generator used to substitute as
|
|
* literals, and `terminal-text-scales`, the leaf the presets moved to so the WebView's own bundle
|
|
* cannot reach `storage/preferences` and the AsyncStorage import behind it. Nothing generated is in
|
|
* this reading now: the phone's script is built from these same modules and is not imported here.
|
|
*
|
|
* The three modules the base gained, none of them this branch's and all of them in its 4286 by
|
|
* main's own route:
|
|
*
|
|
* - `src/mobile-web-shell/bridge/bridge-haptics-notify.ts`, which `haptics.web.ts` reaches. C7.10
|
|
* item E (#21864) and mermaid (#21871) were each green against a main that lacked the other, so
|
|
* main held 4323 while measuring 4324, and #21908 re-pinned it there.
|
|
* - `src/mobile-web-shell/bridge/bridge-page-route-grants.ts` and the
|
|
* `mobile-web-bundle/manifest-contract.ts` whose grant grammar it imports rather than restates,
|
|
* both C2.9's. They reach every page closure through `bridge-envelope.ts`, which the page reads
|
|
* to parse `init`, so this count moves for any route the page serves and not for the session
|
|
* alone.
|
|
*
|
|
* Which is the point of re-measuring rather than summing. The merged total was one above the sum
|
|
* the first time, when main had drifted the haptics module after recording its own number, and a
|
|
* sum would have read 4283 and been wrong about a module neither side of that merge touched.
|
|
*
|
|
* Both sides read with `mobileWebAppRouteClosure(SESSION_ROUTE)` and every postinstall generator
|
|
* `mobile/package.json` names run first — five at this reading, since C7.10 C1 added the rich
|
|
* Markdown editor's document, and the list is read there rather than counted from here because it
|
|
* grows. The before side is a scratch worktree detached at the same sha, and the
|
|
* three modules above read out of the after side's list by name rather than inferred from the
|
|
* total. Measured rather than taken from main's pin because the pin covers only the module count,
|
|
* so the local count beside it would otherwise be a number nobody had read.
|
|
*
|
|
* The byte reading is not re-measured and stays anchored where it was taken, against main at
|
|
* ec82173130: 3,768,122 -> 3,766,312 minified (-1,810). `mobileWebAppRouteClosure` reads
|
|
* `metafile.inputs` and returns no byte total, so a figure produced here would be a different
|
|
* computation rather than a newer reading of that one.
|
|
*
|
|
* The bytes fall because threading the scope deletes a closure: every function names its state as a
|
|
* parameter, and a parameter minifies to one character where a shared module-level object could not.
|
|
* Folding the seventeen never-written fields out of that object takes the rest: a constant read
|
|
* through `scope.X` is a property access the minifier must keep, and the same constant as a module
|
|
* `const` is inlined.
|
|
*
|
|
* What moved is which files carry the document, not whether the page carries it. C7.5 put the
|
|
* document's own source modules in this closure and started them per mount; ruling 23 gave the
|
|
* page the factory the WebView's script was generated from, so the same program arrived as one
|
|
* emitted file and its 41 inputs leave. The bytes barely move because it is the same program: what
|
|
* goes is the import and export plumbing between the modules, and what the generator substitutes.
|
|
*
|
|
* The two commits inside the -40, because only one of them is the factory arriving: making the
|
|
* document a factory put the `host` argument on `createTerminalDocumentScope`, the lane's only edit
|
|
* to a module this closure already carried, and cost 80 bytes on its own -- 3,768,202 measured at
|
|
* that commit. The -1,890 from there is the page importing the emitted factory instead.
|
|
*
|
|
* xterm was already a static import of the mount before this, so nothing here is xterm arriving: it
|
|
* and its two addons are 607,945 bytes minified ESM on their own, and they are on both sides of the
|
|
* reading above.
|
|
*
|
|
* Two earlier readings of the same measurement, against the bases this branch sat on before:
|
|
* -47,255 at 51ae7b1b03 and -55,561 at 0ce0fc99a2. They differ because C7.1's own round-1 fold
|
|
* deleted `URL_TAP_WEBVIEW_JS` from a module only the page's component brings into this closure,
|
|
* so the saving lands on the after side and no base can show it.
|
|
*
|
|
* Then C7.10 item B put mermaid on the page, and the module list moved again. Its own reading, at
|
|
* the base it was taken against:
|
|
*
|
|
* modules 4320 -> 4323 (+3)
|
|
* local modules 970 -> 973 (+3)
|
|
*
|
|
* Three modules: the configuration both hosts read, the loader, and the pre-bundled engine the
|
|
* loader imports on demand. The engine's own 66 files and the d3, dagre, katex and cytoscape trees
|
|
* under them are inside that one artifact rather than in this graph, which is why the count barely
|
|
* moves. Importing the package here instead read +2,056 and emitted 103 scripts, a package
|
|
* splitting along its own lazy diagram-type boundaries -- every one of them inside the OTA generation
|
|
* the phone had already downloaded, so the split moved no bytes and spent 103 of the 256 manifest
|
|
* assets the shell will load. One artifact costs one script and one module.
|
|
*
|
|
* What the generation weighs, because every chunk ships in it whether or not a phone ever fetches
|
|
* one: the built bundle is 8,016,714 bytes across 112 assets, against the 9 MiB ceiling in
|
|
* `verify-mobile-web-app-bundle.mjs`. That is 84.9% of it, with 1,420,470 bytes left for the rest
|
|
* of C7.10 and for C7.7. Before item B the same bundle was 4,539,090 bytes, and the engine is the
|
|
* difference -- deferring it defers evaluation and a fetch, never the download.
|
|
*
|
|
* `mobileWebAppRouteClosure` reads `metafile.inputs`, which holds dynamically imported modules
|
|
* under `splitting: true` just as it does under `splitting: false`, so it cannot express "on
|
|
* demand" about anything. Ruling 28: the fence for this route is `entryStaticClosure`, which
|
|
* follows `import-statement` edges only, and the module list's total is a recorded number rather
|
|
* than a budget. It moves whenever main adds a module this route reaches, and is re-recorded rather
|
|
* than argued with.
|
|
*/
|
|
|
|
const projectDir = fileURLToPath(new URL('../..', import.meta.url))
|
|
const mobileDir = join(projectDir, 'mobile')
|
|
|
|
const SESSION_ROUTE = 'app/h/[hostId]/session/[worktreeId].tsx'
|
|
|
|
/** Gone with the WebView: string literals of a program the page has no way to run. */
|
|
const SHED = [
|
|
'src/terminal/TerminalWebView.tsx',
|
|
'src/terminal/terminal-webview-engine.generated.ts',
|
|
'src/terminal/terminal-webview-document-script.generated.ts',
|
|
'src/terminal/terminal-webview-html.ts',
|
|
'src/terminal/terminal-webview-html/document-shell.ts',
|
|
'src/terminal/terminal-webview-html/document-close.ts'
|
|
]
|
|
|
|
/** The component, its mount, the stylesheet and the markup, and the modules the splits made. */
|
|
const GAINED_OUTSIDE_THE_DOCUMENT = [
|
|
'src/terminal/TerminalWebView.web.tsx',
|
|
'src/terminal/terminal-web-document-mount.ts',
|
|
'src/terminal/terminal-webview-engine-css.generated.ts',
|
|
'src/terminal/terminal-webview-html.web.ts',
|
|
'src/terminal/terminal-webview-html/document-markup.ts',
|
|
'src/terminal/terminal-webview-html/document-style.ts',
|
|
'src/terminal/terminal-webview-ready-promises.ts',
|
|
'src/terminal/use-terminal-webview-controller.ts',
|
|
// The page's half of the stylesheet: the document-level rules are dropped and the rest is held
|
|
// under the host, so what the page injects can only reach what the terminal owns. It sits
|
|
// outside `src/terminal/` because the rich Markdown editor's mount reads the same rewrite.
|
|
'src/style-scoping/document-style-scoping.ts'
|
|
]
|
|
|
|
const XTERM_PACKAGES = ['@xterm/xterm', '@xterm/addon-unicode11', '@xterm/addon-webgl']
|
|
|
|
/**
|
|
* The 16 px seam's verdict for this route, which C7.5 must leave exactly where C7.2 left it.
|
|
*
|
|
* Design §3 counted nine inputs under the floor here and C7.2 moved all nine onto the seam, so the
|
|
* answer is now none. Asserted rather than left unmeasured because the terminal's own modules
|
|
* joining this closure is precisely the kind of change that could add a tenth unread.
|
|
*/
|
|
const EXPECTED_OFFENDERS = 0
|
|
|
|
/** The deferred engine, as the page reaches it: one artifact, not the package's own file tree. */
|
|
const MERMAID_PAGE_ENGINE = 'src/components/pr-sidebar/mermaid-page-engine.generated.ts'
|
|
const MERMAID_PACKAGE = 'node_modules/mermaid/'
|
|
|
|
/**
|
|
* The module list on the merge, recorded at the base in the docstring above, which is where every
|
|
* part of it is accounted for: the document's own modules replacing the factory that carried them,
|
|
* mermaid's three, and the three bridge modules #21908 and C2.9 pin on main.
|
|
*
|
|
* Then C7.10 item D put dictation's capture on the page, and the list moved down rather than up.
|
|
*
|
|
* modules 4328 -> 4323 (-5)
|
|
* local modules 978 -> 981 (+3)
|
|
*
|
|
* Three local modules join — `src/platform/dictation-capture.web.ts`, its contract
|
|
* `src/platform/dictation-capture-contract.ts`, and the verb shapes in
|
|
* `src/mobile-web-shell/bridge/bridge-audio-verbs.ts` — and eight vendored ones leave, because the
|
|
* capture seam is what stops the page importing a microphone it does not have. Five are
|
|
* `@orca/expo-two-way-audio` (its web module, `core`, `events`, `hooks` and the index) and three
|
|
* are `expo-keep-awake`; the page asks the shell for the microphone over
|
|
* `native.audio.start|read|stop` instead, and never asks about the screen at all — an open mic
|
|
* holds it on the device side. The native halves of the seam resolve out of this closure entirely,
|
|
* which is the -8 + 3.
|
|
*
|
|
* Measured, not derived: `mobile-web-app-session-dictation-capture.test.mjs` moves the web file
|
|
* aside and walks the closure again, which puts those eight back.
|
|
*
|
|
* C7.7 registers the route and adds one more: the walk now enters through
|
|
* `app/h/[hostId]/session/[worktreeId].web.tsx` rather than the native switch, and reaches
|
|
* `src/session/MobileSessionRouteScreen.tsx` under it — the route file is one input either way and
|
|
* the component is the one that is new.
|
|
*
|
|
* The number below is re-measured rather than summed, which is what the reading above kept having
|
|
* to do: C7.7 measured 4,328 -> 4,329 against `23207bfde2` and item D measured 4,328 -> 4,323
|
|
* against a different base, and neither side's arithmetic survives the other. The merge with main
|
|
* read 4,324 modules and 982 local — one more than the 4,323 / 981 item D pinned, and that one is
|
|
* C7.7's route body, read out of `ROUTE_ENTRY` below by name rather than inferred.
|
|
*
|
|
* Round 1 re-measured it at 4,326 / 984. The two were named rather than counted:
|
|
* `notification-pane-tab.ts`, which both siblings of the pane hook read (a `.web.ts` cannot import
|
|
* its native neighbour by the plain path — the bundler answers with itself), and
|
|
* `bridge-init-route.ts`, the route half of `init` split out of an envelope that was at its line
|
|
* cap. The pane hook's own web sibling replaces the native file rather than joining it, so it
|
|
* costs nothing.
|
|
*
|
|
* Ruling 34 measures 4,330 / 988, and the four are named the same way. `bridge-frame-fields.ts`
|
|
* and `bridge-notify-envelope.ts` are the two halves an envelope back at its line cap was split
|
|
* into; the page-to-shell union in the second names the param the page may erase, which is
|
|
* declared beside the route-update accept, so `bridge-route-update.ts` and the
|
|
* `shell-screen-route.ts` it reads a route key from now enter through the envelope as well. All
|
|
* four are schema and string constants: the closure grew, the download did not gain a package.
|
|
*
|
|
* Main measures 4,333 at `3cfb070294`: #21924 (`2739246058`) turned `agent-session-wire.ts`'s
|
|
* type-only import of `agent-session-record` into a value import, so `src/shared/agent-session-record.ts`
|
|
* and the two it reaches, `agent-session-conversation-name.ts` and `surrogate-safe-text-slice.ts`,
|
|
* entered the page bundle between C7.7's measurement on `f07bf8544c` and its merge. Named by
|
|
* diffing the closure at `f07bf8544c` against `2739246058`; nothing on the C7.7 side moved.
|
|
*
|
|
* Then C7.10 item C put the rich Markdown editor on the page, and the list moved again. Its own
|
|
* reading, both sides measured with `mobileWebAppRouteClosure(SESSION_ROUTE)` at base `9267423f22`
|
|
* with all five postinstall generators run first:
|
|
*
|
|
* modules 4333 -> 4360 (+27)
|
|
* local modules 991 -> 1018 (+27)
|
|
*
|
|
* Every one of the 27 is local and none is vendored, because the editor is the app's own code
|
|
* rather than a library: the document's 24 modules under `src/components/rich-markdown/` — which
|
|
* C7.6's plain field did not reach at all — plus the page's mount, the toolbar both siblings render,
|
|
* and the controller and keyboard-inset module the native component already had. Nothing leaves:
|
|
* the web sibling replaces its own native file, which was never in this closure. Named by diffing
|
|
* the two `local` lists rather than inferred from the total.
|
|
*
|
|
* `style-scoping/document-style-scoping.ts` is in the reading on both sides and costs
|
|
* nothing: the terminal's own mount already brings it, and the editor's mount imports the second
|
|
* export it grew rather than a module of its own.
|
|
*
|
|
* What the generation weighs, measured the same way on both sides: 8,028,418 -> 8,056,166 bytes
|
|
* (+27,748) across 109 assets, against the 9 MiB ceiling in `verify-mobile-web-app-bundle.mjs` —
|
|
* 85.1% of it before and 85.4% after. The script count does not move at all (67, against the
|
|
* 76 the chunk fence allows for 15 routes) and neither does the entry's static closure
|
|
* (1,612,052 bytes against a 3 MiB bound): the editor is code the session route already reached
|
|
* for, not a new chunk boundary.
|
|
*
|
|
* Main's own paragraph for the same pin, kept because the two provenances are independent: ruling
|
|
* 36 gave the screen to the microphone and two local modules left,
|
|
* `src/hooks/mobile-dictation-keep-awake.ts` and
|
|
* `src/hooks/mobile-dictation-foreground-keep-awake.ts`, the page's wake-tag owner and its Android
|
|
* foreground re-acquire. Both are deleted rather than moved — the device module that opens the
|
|
* microphone takes the screen and gives it back — so the page has nothing left to own.
|
|
*
|
|
* modules 4333 -> 4331 (-2)
|
|
* local modules 991 -> 989 (-2)
|
|
*
|
|
* Then the two other table parsers gave up their own row splitters and read the editor's
|
|
* `src/components/rich-markdown/markdown-table-rows.ts` instead, which the session page reaches
|
|
* through the PR comment renderer. It is the one module that joins, and the only one it can be: it
|
|
* imports nothing, and no other file under `rich-markdown/` is in the closure beside it.
|
|
*
|
|
* modules 4331 -> 4332 (+1)
|
|
* local modules 989 -> 990 (+1)
|
|
*
|
|
* Then #18790 (`0677271709`) taught the agent icon table a new agent, and its icon
|
|
* `src/shared/agent-icons/freebuff.png` entered through `mobile-agent-icon-assets.ts`, which the
|
|
* session page reaches as it reaches every other icon there. An image asset, not a package, and
|
|
* the one line that differs between the closure at `226f4a0775` and at `059ee59a48`; it landed
|
|
* between #22114's measurement and its merge, so main read one short.
|
|
*
|
|
* modules 4332 -> 4333 (+1)
|
|
* local modules 990 -> 991 (+1)
|
|
*
|
|
* C7.10 item C then put the whole of that directory on the page, and the merge of the two is
|
|
* measured rather than summed — which is what this reading keeps having to do. The arithmetic of
|
|
* 4,358 on this branch and 4,332 on main double-counts `markdown-table-rows.ts`: main reached it
|
|
* first through the comment renderer, and it is also one of the 27 the editor brings. Measured on
|
|
* the merged head with every generator run first:
|
|
*
|
|
* modules 4333 -> 4359 (+26)
|
|
* local modules 991 -> 1017 (+26)
|
|
*
|
|
* and the two local lists diffed against main's, which names the difference module by module: the
|
|
* editor's own 26, with `markdown-table-rows.ts` already on both sides and the dictation pair
|
|
* already gone from both. 26 rather than 27 for exactly that reason — main reached the row splitter
|
|
* first, so it is not this branch's to add twice.
|
|
*
|
|
* The 4,333 the merge is measured against is main's corrected reading, not the 4,332 it pinned at
|
|
* `059ee59a48`: that census failed, `expected [ …(4333) ] to have a length of 4332`, because the
|
|
* icon above joined beside the row splitter and was not counted. #22119 (`197550c952`) repinned
|
|
* main to 4,333 with the paragraph above, and folding it here moves nothing — the icon was already
|
|
* on both sides of the +26, so the pin below is this merge's own measurement, unchanged.
|
|
*
|
|
* Then #21705 (`eb92222e7f`) taught the agent option catalog Antigravity, and
|
|
* `src/shared/agent-session-option-catalog-antigravity.ts` entered through the catalog the session
|
|
* page already reaches. One module, string constants, no package; the one line that differs between
|
|
* the closure at `841d06a969` and at `eb92222e7f`. It landed beside C2's merge, so main read one
|
|
* short again.
|
|
*
|
|
* modules 4359 -> 4360 (+1)
|
|
* local modules 1017 -> 1018 (+1)
|
|
*
|
|
* C8.1 then gave the HTML preview a capability to ask about, and three local modules join. Both
|
|
* sides measured with `mobileWebAppRouteClosure(SESSION_ROUTE)` at base `841d06a969` with all five
|
|
* postinstall generators run first, and the two `local` lists diffed rather than the total inferred:
|
|
*
|
|
* modules 4359 -> 4362 (+3)
|
|
* local modules 1017 -> 1020 (+3)
|
|
*
|
|
* Named, and all three local: `src/components/use-html-preview-link-grant.web.ts`, the page's read
|
|
* of `init.grants.native`; `src/components/html-preview-inert-links.ts`, the pass that turns the
|
|
* artifact's links back into text without it; and
|
|
* `src/mobile-web-shell/cancelled-navigation-target.ts`, which declares the grant token beside the
|
|
* rule that acts on it and is reached both by that hook and by `page-route-policy.ts`. The
|
|
* `bridge-caps.ts` it imports was already in this closure, and the hook's native sibling is
|
|
* replaced rather than joined. Nothing vendored: three source modules, no package.
|
|
*
|
|
* The merge of the two is measured rather than summed, which is what this reading keeps having to
|
|
* do. It agrees with the arithmetic this once, and only because the two additions are disjoint:
|
|
* main's one module is the option catalog and this branch's three are the preview's, so neither
|
|
* side counts the other's. Measured on the merged head with all five generators run first:
|
|
*
|
|
* modules 4360 -> 4363 (+3, and 4359 -> 4363 from the shared base)
|
|
* local modules 1018 -> 1021 (+3)
|
|
*
|
|
* The C6.5 follow-up then aliased `zod` in the builder, so the four `src/shared` modules this route
|
|
* reaches stop pulling the root's second copy in. The only reading here that has ever fallen: both
|
|
* lists diffed, 94 gone and every one of them vendored `zod@4.5.4`, none added.
|
|
*
|
|
* modules 4363 -> 4269 (-94)
|
|
* local modules 1021 -> 1021 (unchanged)
|
|
*
|
|
* The live-input seam then adds one: the two hooks that write the terminal's hidden field now go
|
|
* through `src/terminal/terminal-live-input-text-write.ts`, and the page resolves its `.web.ts`.
|
|
* One module, not two — the sibling replaces the native file, and both hooks were already here.
|
|
*
|
|
* modules 4269 -> 4270 (+1)
|
|
* local modules 1021 -> 1022 (+1)
|
|
*
|
|
* The page's client identity joins beside it:
|
|
* `src/mobile-web-shell/bridge/bridge-page-client-identity.ts` declares the placeholder
|
|
* `client-context.web.tsx` claims, so the provider every screen reads imports it. One local module,
|
|
* nothing vendored. Measured on this merged head rather than summed, all five generators run first:
|
|
*
|
|
* modules 4270 -> 4271 (+1)
|
|
* local modules 1022 -> 1023 (+1)
|
|
*
|
|
* Cutting `expo-notifications` out of the page takes 62 vendored modules with it: 55 of its own,
|
|
* and behind it expo-application 3, abort-controller 2, badgin 1, event-target-shim 1. The three
|
|
* `.web` siblings replace their native files, so the local +1 is `host-app-version.ts` alone.
|
|
*
|
|
* modules 4271 -> 4210 (-61)
|
|
* local modules 1023 -> 1024 (+1)
|
|
*
|
|
* The page's paint report joins beside that one, for the same reason:
|
|
* `src/mobile-web-shell/bridge/bridge-page-painted.ts` holds the name the page posts and the name
|
|
* it declares in `ready`, so `bridge-client-notifications.ts` — which every screen's client is
|
|
* built from — imports it. One local module, nothing vendored; the seam that schedules the report
|
|
* is the web entry's and does not enter a route closure. Re-measured on this merged head rather
|
|
* than carried over from before the cut, with all five generators run first.
|
|
*
|
|
* modules 4210 -> 4211 (+1)
|
|
* local modules 1024 -> 1025 (+1)
|
|
*
|
|
* The terminal fields' submit seam joins next, onto the 4,207 #22283 left, and both of its modules
|
|
* are local. react-native-web withholds `onSubmitEditing` whenever the Enter keydown reports an
|
|
* open composition, which is a soft keyboard's normal state mid-word, so both of the dock's fields
|
|
* bind the browser's own line-break signal as well.
|
|
* `src/terminal/use-terminal-text-field-submit-binding.ts` is the callback ref they take, and
|
|
* `src/terminal/terminal-text-field-submit-binding.web.ts` is the binding it resolves to here; the
|
|
* native sibling stays out of this closure, which is what the pair is for. Measured on this merged
|
|
* head with all five generators run first, and the two joiners read off the closure list itself
|
|
* rather than inferred from the delta.
|
|
*
|
|
* modules 4207 -> 4209 (+2)
|
|
* local modules 1021 -> 1023 (+2)
|
|
*
|
|
* The page's claim on the device Back key joins beside those (#22300 landed first, so this is measured on the merged head). Two local
|
|
* modules, nothing vendored, each named rather than left inside the total:
|
|
* `src/navigation/use-back-claim.web.ts`, the seam every sheet and the handoff take, which enters
|
|
* through `route-handoff.web.ts`; and `src/mobile-web-shell/bridge/bridge-page-back.ts`, the two
|
|
* names the lane is negotiated under, which the envelope this route already reads imports.
|
|
* `page-back-consumers.ts` is not a third: it hangs off `bridge-rpc-client.ts`, and no route
|
|
* closure carries that — the page's client is built by the entry. Re-measured on the merged head
|
|
* with all five generators run first.
|
|
*
|
|
* modules 4209 -> 4211 (+2)
|
|
* local modules 1023 -> 1025 (+2)
|
|
*
|
|
* One joiner from outside `mobile/`: #22299 (9ece273056) added
|
|
* `src/shared/agent-session-journal-producer.ts`, and three shared modules this route already
|
|
* carries import it (`structured-agent-session-live-turn.ts`, `structured-agent-session-projection.ts`,
|
|
* `native-chat-turn-activity.ts`). That PR changed no file under `mobile/`, so the mobile job never
|
|
* ran and main landed one over this pin. Re-pinned here, on the head that merged it, by measuring
|
|
* the closure on 9ece273056 against the previous head and diffing the two lists.
|
|
*
|
|
* modules 4211 -> 4212 (+1)
|
|
* local modules 1025 -> 1026 (+1)
|
|
*
|
|
* The page's Retry decision joins after those: `src/transport/connection-retry-action.ts` says
|
|
* whether a failed screen's Retry re-dials, re-reads or is not offered, and the session route
|
|
* reaches it through the explorer, source control and git history it docks. One local module.
|
|
*
|
|
* modules 4212 -> 4213 (+1)
|
|
* local modules 1026 -> 1027 (+1)
|
|
*
|
|
* Muse then joined the mobile agent catalog with its bundled icon, one more local input to the
|
|
* shared agent picker.
|
|
*
|
|
* modules 4213 -> 4214 (+1)
|
|
* local modules 1027 -> 1028 (+1)
|
|
*
|
|
* Muse's worker launch preferences then added `src/shared/agent-session-option-catalog-muse.ts`,
|
|
* which the option catalog this route already reaches imports. One local module, measured.
|
|
*
|
|
* modules 4214 -> 4215 (+1)
|
|
* local modules 1028 -> 1029 (+1)
|
|
*
|
|
* The structured tool line then added `src/shared/structured-agent-session-tool-call-block.ts`,
|
|
* which the projection and live turn this route already reaches import. One local module,
|
|
* measured; the change was src/shared-only, so its own CI never ran this suite.
|
|
*
|
|
* modules 4215 -> 4216 (+1)
|
|
* local modules 1029 -> 1030 (+1)
|
|
*
|
|
* The page then took over its own safe area: the bridge client reaches
|
|
* `bridge/bridge-safe-area-insets.ts`, and the closure gained the page's root layout
|
|
* (`app/_layout.web.tsx`), which wraps every route. Two local modules, measured.
|
|
*
|
|
* modules 4216 -> 4218 (+2)
|
|
* local modules 1030 -> 1032 (+2)
|
|
*
|
|
* #22452 (`80f5aae0f9`) then added `src/shared/main-agent-status.ts` and
|
|
* `src/shared/agent-turn-outcome.ts`, which `agent-status-types.ts` on this route imports. Two local
|
|
* modules; the change was src/shared-only, so its own CI never ran this suite and main read two over.
|
|
*
|
|
* modules 4218 -> 4220 (+2)
|
|
* local modules 1032 -> 1034 (+2)
|
|
*
|
|
* Reverting #18790 then took `src/shared/agent-icons/freebuff.png` back out of
|
|
* `mobile-agent-icon-assets.ts`, undoing the one module #22119 pinned for it. Measured on the revert.
|
|
*
|
|
* modules 4220 -> 4219 (-1)
|
|
* local modules 1034 -> 1033 (-1)
|
|
*
|
|
* #22301 (`25d7c21fcb`) then added `src/shared/agent-session-context-usage.ts` and
|
|
* `src/shared/agent-session-context-usage-schema.ts`, which `agent-session-wire.ts` and
|
|
* `agent-session-journal-types.ts` on this route import. Two local modules; it touched nothing under
|
|
* `mobile/`, so its own CI never ran this suite.
|
|
*
|
|
* modules 4219 -> 4221 (+2)
|
|
* local modules 1033 -> 1035 (+2)
|
|
*
|
|
* The browser pane's double buffer then moved into one pacer module, replacing the frame-apply
|
|
* hook, the pane-layers hook and the layer-flip module. Measured on main after the squash.
|
|
*
|
|
* modules 4221 -> 4219 (-2)
|
|
* local modules 1035 -> 1033 (-2)
|
|
*/
|
|
/** What the page enters this route through once the route is a switch with a `.web.tsx` sibling. */
|
|
const ROUTE_ENTRY = [
|
|
'app/h/[hostId]/session/[worktreeId].web.tsx',
|
|
'src/session/MobileSessionRouteScreen.tsx',
|
|
// Round 1's two, named for the reading above rather than left inside the total.
|
|
'src/session/notification-pane-tab.ts',
|
|
'src/mobile-web-shell/bridge/bridge-init-route.ts'
|
|
]
|
|
|
|
const artifactModules = (inputs) => inputs.filter((input) => input.includes(MERMAID_PAGE_ENGINE))
|
|
const packageModules = (inputs) => inputs.filter((input) => input.includes(MERMAID_PACKAGE))
|
|
|
|
const bundles = mobileWebAppDependenciesPresent()
|
|
const describeClosure = bundles ? describe : describe.skip
|
|
|
|
describeClosure(
|
|
"the session route's page closure with the terminal on it",
|
|
() => {
|
|
it('gains the document, xterm and the addons, and sheds the engine string', async () => {
|
|
const { local, modules } = await mobileWebAppRouteClosure(SESSION_ROUTE)
|
|
for (const gone of SHED) {
|
|
expect(local, `${gone} is still in the closure`).not.toContain(gone)
|
|
}
|
|
for (const gained of GAINED_OUTSIDE_THE_DOCUMENT) {
|
|
expect(local, `${gained} is not in the closure`).toContain(gained)
|
|
}
|
|
for (const name of XTERM_PACKAGES) {
|
|
expect(
|
|
modules.some((module) => module.includes(`node_modules/${name}/`)),
|
|
`${name} is not in the closure`
|
|
).toBe(true)
|
|
}
|
|
// The document, whole, and as modules: ruling 25 makes it ordinary TypeScript that the page
|
|
// imports and calls, so the closure carries every module — including `message-bridge`, whose
|
|
// two host facts are seams now — and nothing generated at all.
|
|
const documentModules = local.filter((module) => module.startsWith('src/terminal/document/'))
|
|
expect(documentModules).toContain('src/terminal/document/create-terminal-document.ts')
|
|
expect(documentModules).toContain('src/terminal/document/message-bridge.ts')
|
|
expect(documentModules.length).toBeGreaterThanOrEqual(36)
|
|
// The bundle and its entry belong to the phone: a page reaching either would ship the
|
|
// document twice, once as modules and once as a string.
|
|
expect(documentModules).not.toContain('src/terminal/document/native-document-entry.ts')
|
|
expect(local).not.toContain('src/terminal/terminal-webview-document-script.generated.ts')
|
|
}, 300_000)
|
|
|
|
it('enters through the web sibling and the route body, not the switch', async () => {
|
|
const { local } = await mobileWebAppRouteClosure(SESSION_ROUTE)
|
|
for (const entry of ROUTE_ENTRY) {
|
|
expect(local, `${entry} is not in the closure`).toContain(entry)
|
|
}
|
|
// The switch itself is what the shell renders natively, and it reaches
|
|
// `MobileWebShellScreen`, whose module calls `requireNativeViewManager` at import. A closure
|
|
// that carried it would be a bundle that throws when the manifest imports this route.
|
|
expect(local).not.toContain('app/h/[hostId]/session/[worktreeId].tsx')
|
|
expect(local).not.toContain('src/mobile-web-shell/MobileWebShellScreen.tsx')
|
|
}, 300_000)
|
|
|
|
it('reaches the engine as one deferred module and never as part of the download', async () => {
|
|
const { modules } = await mobileWebAppRouteClosure(SESSION_ROUTE)
|
|
// The engine is here, as the one artifact the loader imports.
|
|
expect(artifactModules(modules)).toHaveLength(1)
|
|
// Package sources stay inside that artifact; unrelated module counts are not this boundary.
|
|
expect(packageModules(modules)).toEqual([])
|
|
|
|
const download = await mobileWebAppRouteChunkClosure(SESSION_ROUTE)
|
|
// The fence: nothing of the engine is reachable from the route's own chunk by an import
|
|
// statement, so opening the session pays none of it.
|
|
expect(artifactModules(download.staticInputs)).toEqual([])
|
|
// The precondition that absence needs. The artifact is in the bundle, in a chunk the route
|
|
// reaches by a `dynamic-import` edge instead -- a deferred engine, not a dropped one.
|
|
expect(artifactModules(download.deferredInputs)).toHaveLength(1)
|
|
// And the walk read a real download rather than one chunk: the route's own chunk is in it.
|
|
expect(download.staticChunks).toContain(download.routeChunk)
|
|
expect(download.staticInputs.length).toBeGreaterThan(1000)
|
|
}, 600_000)
|
|
|
|
it('leaves the 16px seam census exactly where C7.2 left it', async () => {
|
|
const closure = await mobileWebAppRouteClosure(SESSION_ROUTE)
|
|
// Two preconditions, because zero offenders is what a walk that read nothing also reports:
|
|
// the seam's own web module has to be in the closure, and no style may be unresolved.
|
|
expect(closure.local).toContain('src/platform/text-input-font-size.web.ts')
|
|
expect(unresolvedTextInputStyles(mobileDir, closure)).toEqual([])
|
|
expect(textInputFontSizeOffenders(mobileDir, closure)).toHaveLength(EXPECTED_OFFENDERS)
|
|
}, 300_000)
|
|
|
|
it('holds the editables the TextInput census cannot see to the same floor', async () => {
|
|
const closure = await mobileWebAppRouteClosure(SESSION_ROUTE)
|
|
// The rich Markdown editor's surface is a `contenteditable` in a markup string, sized by a
|
|
// rule in a stylesheet: `modulesDeclaringTextInput` matches JSX tags and never sees it, so it
|
|
// shipped at 14 px and was measured at 14 px in both engines. The same floor, read by a rule
|
|
// that starts from the markup instead of from a prop.
|
|
expect(editableHostsIn(mobileDir, closure)).toEqual([
|
|
{ file: 'src/components/rich-markdown/document-markup.ts', id: 'editor' }
|
|
])
|
|
expect(unresolvedEditableHostStyles(mobileDir, closure)).toEqual([])
|
|
expect(editableHostFontSizeOffenders(mobileDir, closure)).toEqual([])
|
|
}, 300_000)
|
|
},
|
|
900_000
|
|
)
|