Skip to content

[00129] Replace the Four Hardcoded Barrel Guard Tests With an Eager Graph Reachability Assertion - #112

Merged
rorychatt merged 5 commits into
mainfrom
tendril/00129-ReplaceTheSingleBarrelLazyLoadingGuardTestWithAnEagerGraphRe
Aug 2, 2026
Merged

[00129] Replace the Four Hardcoded Barrel Guard Tests With an Eager Graph Reachability Assertion#112
rorychatt merged 5 commits into
mainfrom
tendril/00129-ReplaceTheSingleBarrelLazyLoadingGuardTestWithAnEagerGraphRe

Conversation

@rorychatt

Copy link
Copy Markdown
Contributor

00129 - Replace the Four Hardcoded Barrel Guard Tests With an Eager Graph Reachability Assertion

What landed

One new test replaces four hardcoded string tests, and the README stops pointing at a file that no
longer exists.

src/frontend/src/widgets/__tests__/lazyWidgetBarrels.test.ts (103 lines, 2 cases, 11 ms) indexes the
static import edges of every first-party non-test file, walks them breadth-first from src/index.tsx,
and fails if any module named in a widgetMap.ts import("@/...") is reachable that way. A reachable
lazy module is one whose import() no longer splits anything. The failure message is the whole import
chain, e.g.

@/widgets/lists/ListWidget is eagerly reachable: index.tsx -> components/App.tsx ->
widgets/widgetRenderer.tsx -> widgets/lists/index.ts -> widgets/lists/ListWidget.tsx

Deleted, each an 11-line read-the-barrel-and-regex test covering one barrel and one widget name:
src/widgets/lists/index.test.ts, src/widgets/chunkSplitting.test.ts,
src/widgets/inputs/ContentInputWidget/index.test.ts,
src/widgets/inputs/DateTimeInputWidget/index.test.ts.

src/frontend/README.md gains a pointer to the new test in place of the dangling reference, plus four
lines naming the build gate's one real blind spot.

Net: 6 files changed, 113 insertions(+), 51 deletions(-).

Commits

Hash What
8e498df Add the test, delete the four string tests; mutation matrix in the message
08407c3 Both README edits
9e425d5 Resolve the README conflict with main's rewrite of the same section
a385b1f Recalibrate the test for main's 55-widget lazification
b4a50a7 Merge origin/main 9b7c764 (clean, no conflicts)

Branch tendril/00129-..., 5 commits ahead of origin/main, merged up to 9b7c764. main moved four
times during execution: df4e685 -> 7bd1fdc -> 6e7664c -> e6c4398 -> 9b7c764.

Why this shape

The four deleted tests were the same idea copied four times, and two of the four could not fail for a
structural reason: inputs/ContentInputWidget/index.ts and inputs/DateTimeInputWidget/index.ts
are the import() target, so a re-export inside them can never create an eager edge. The new test
asserts reachability instead of names, so barrels are just edges - which removes the name matching, the
inert-barrel filter, and the carve-out those two files needed.

It is deliberately redundant with the build-time assert-lazy-chunks gate. It reads source only, so it
needs no build and runs in 11 ms rather than 2 s plus a toolchain; it names the import chain rather than
a chunk filename; and it survives a change to the bundler's graph API. The README now says explicitly
that it is a complement to the build gate, not a replacement.

Mutation matrix (re-measured on the merged tree)

Eleven rows, each reverted after, exit codes read unpiped, git diff --stat confirmed empty between
rows. Full table with evidence in Verification/CheckResult.md. The three rows the plan requires (2, 6,
8) were all reproduced. Headlines:

  • The test fires on all four ways an eager edge gets created: bare side-effect import "x", a used
    named import, an unused named import, and a barrel re-export reached through either
    widgetMap.ts (row 6b) or widgetRenderer.tsx (row 11).
  • It stays silent where the build stays silent: import type (row 5) and an inert barrel (row 7).
  • The anti-vacuity assertion works: stubbing src/index.tsx to export {}; fails with "the eager walk
    from index.tsx never reached widgets/widgetMap.ts" instead of passing trivially.
  • Row 4 is the one place the test is stricter than the build, as the plan predicted. New finding: that
    only holds for a plainly named binding - with an underscore-prefixed alias, pnpm lint and tsc -b
    both stay green by design and this test is the only gate that catches it.

Created using Ivy Tendril.

rorychatt and others added 5 commits August 2, 2026 11:54
… reachability assertion

Adds src/frontend/src/widgets/__tests__/lazyWidgetBarrels.test.ts, which walks
static non-type import edges from src/index.tsx and fails if any widgetMap.ts
import() target is eagerly reachable, printing the import chain. Deletes the four
near-identical string tests it subsumes:

  src/widgets/lists/index.test.ts
  src/widgets/chunkSplitting.test.ts
  src/widgets/inputs/ContentInputWidget/index.test.ts
  src/widgets/inputs/DateTimeInputWidget/index.test.ts

Each asserted one barrel does not mention one widget name. Three of them are
false positives against the authoritative assert-lazy-chunks build gate: their
barrels are inert (nothing imports lists/, and the two inputs/ barrels ARE the
import() target), so re-adding the export exits 1 in the string test while
vp build exits 0. The reachability rule agrees with the build gate there and
covers the direct-import shape none of the four could see.

Mutation matrix, measured at base 6e7664c, each row reverted after:

  1 clean tree                                          test 0   build 0
  2 widgetMap.ts: import "@/widgets/tree/TreeWidget"    test 1   build 1
  3 named import of a lazy module, binding used          test 1   build 1
  4 same, binding unused                                 test 1   build 0
  5 import type from a lazy module                       test 0   build 0
  6 chat/index.ts: export * from "./ChatWidget"         test 1   build 1
  7 lists/index.ts re-exports ListWidget (inert)         test 0   build 0
  8 src/index.tsx replaced with export {} (anti-vacuity) test 1   n/a
  9 clean again                                          test 0   build 0

Row 4 is the only place the test is stricter than the build: rolldown elides an
unused named import so no eager edge is emitted. Both pnpm lint and tsc -b
already reject that code, so it cannot reach main; the test is only first to
complain.

Frontend suite: 57 files / 1217 tests before, 54 files / 1215 tests after
(+1 file / +2 cases, -4 files / -4 cases), exit 0 both ways.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nd repoint the guard-test reference

Two edits to the "Module Graph and Lazy Loading" section:

1. The section claimed "this gate does not catch all regression shapes: a barrel
   re-exporting a lazy widget can defeat the split with exit 0 and no warning".
   That is false for assert-lazy-chunks and appears inherited from the older
   INEFFECTIVE_DYNAMIC_IMPORT warning gate, which genuinely never fired on
   vite-plus 0.2.7. Measured at 6e7664c: appending export * from "./ChatWidget"
   to chat/index.ts makes vp build exit 1 from [plugin assert-lazy-chunks],
   naming src/widgets/chat/ChatWidget.tsx -> assets/ChatWidget-DCeO37_C.js. The
   barrel shape is precisely what it does catch. Replaced with its real blind
   spots: a lazy module merged into the entry chunk (the plugin tests
   staticallyImported.has(chunk), and the entry is in no chunk's imports), and
   anything elided before the module graph is built - verified by an unused named
   import of a lazy module building exit 0. The byte-budget paragraph is
   untouched; check:bundle still reports 1603.5 kB across 38 of 244 chunks.

2. The "cheaper guard" paragraph pointed at src/widgets/lists/index.test.ts,
   deleted in the previous commit. Repointed to the new reachability test, and
   dropped "only for the barrel it names" - the property that no longer applies.

The inert-barrels section is left as is: its "Six of the 27 top-level barrels"
counts barrels with no importer, a different set from barrels re-exporting a lazy
widget, so the two counts do not need reconciling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ng section

Conflict 1 (the assert-lazy-chunks paragraph): resolved to main's version. A
sibling landed the same correction this plan's README Edit 2 was written to make -
the section no longer claims the gate is silent on the barrel shape - and it also
extended the plugin itself with || bundle[chunk]?.isEntry, closing the entry-chunk
blind spot. That makes this plan's version of the paragraph wrong, so main's text
is kept and only the one blind spot that survives the fix is added: an elided
unused named import, which never reaches the module graph.

Conflict 2 (the cheaper-guard paragraph): resolved to this plan's version. Main's
side still points at src/widgets/lists/index.test.ts, deleted in 8e498df, so
keeping it would leave a dangling reference. Reworded from "gates" plural to
singular since main's rewrite replaced the check:bundle paragraph between them
with a manual grep recipe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sibling commit 2376cb3 ("Document eager vs lazy criterion in widgetMap and lazify
55 widgets") landed between this branch's base and the merge, taking widgetMap.ts
from 50 static / 61 dynamic first-party imports to 3 / 117. Two assertions in this
test were calibrated against the old shape and failed on the merged tree. Neither
failure was the guard: the reachability walk finds 0 real offenders both before and
after, so the property still holds.

1. paths.size > 100 -> > 25. The eager graph is meant to shrink as widgets are
   lazified; it went 199 files -> 53 for exactly the reason this test exists. A
   floor near the current figure would fail the next lazification instead of a
   broken walk, so it is now deliberately loose and commented as such. The
   load-bearing anti-vacuity check is paths.has(widgetMapPath), which still fails
   on a stubbed entry (mutation row 8).

2. Comment stripping added for the dynamic-import scan. 2376cb3 also added a doc
   comment to widgetMap.ts containing a literal
   lazyWithRetry(() => import("@/widgets/<dir>/<Widget>")) template. The dynamic
   regex matches import("...") anywhere on a line, unlike the static one which is
   anchored to the line start, so the placeholder was collected as a real specifier
   and failed the resolve assertion. Whole-line // and /* comments are now dropped
   first. Verified this is the only such case: 0 line-start static imports inside
   block comments across all 478 first-party files.

Mutation matrix re-run against the MERGED tree, each row reverted after:

   1 clean tree                                          test 0   build 0
   2 bare side-effect import of a lazy module             test 1   build 1
   3 named import of a lazy module, binding used          test 1   build 1
   4 same, binding unused (elision)                       test 1   build 0
   5 import type from a lazy module                       test 0   build 0
  6b lists barrel re-exports ListWidget AND widgetMap
     imports the barrel (the historical two-hop bug)      test 1   build 1
  11 same barrel reached from widgetRenderer instead      test 1   build 1
   7 lists barrel re-exports ListWidget, barrel inert     test 0   build 0
   8 src/index.tsx replaced with export {}                test 1   n/a
  10 comment stripper removed (proves fix 2)              test 1   n/a
   9 clean again                                          test 0   build 0

Row 6 from the pre-merge matrix (chat/index.ts re-exports ChatWidget) now exits 0
on BOTH gates and is replaced by 6b: 2376cb3 lazified the three eager chat widgets,
so nothing imports @/widgets/chat and that barrel is inert. Agreeing with the build
there is the behaviour this plan set out to get; a hardcoded string test would have
gone red on a correct tree. No barrel is eagerly reachable at all now, which is why
6b has to create the eager edge to test barrel traversal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…TheSingleBarrelLazyLoadingGuardTestWithAnEagerGraphRe
@rorychatt rorychatt self-assigned this Aug 2, 2026
@rorychatt
rorychatt merged commit 1d58a57 into main Aug 2, 2026
@rorychatt
rorychatt deleted the tendril/00129-ReplaceTheSingleBarrelLazyLoadingGuardTestWithAnEagerGraphRe branch August 2, 2026 10:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant