@kudzujs/core 0.16.4 → 0.16.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -51,7 +51,7 @@ Syntax compatibility does not mean reproducing React wholesale. Kudzu accepts th
51
51
  - stronger JavaScript failure resilience;
52
52
  - no remaining material large-route build-scaling weakness.
53
53
 
54
- The completed 0.9 plan defines the cross-framework comparison and release gate. The active application packet is `0.17.0` in the capability release plan below. Benchmark-only feature omission, unmatched accessibility, weighted scores that hide losses, and unrecorded environment differences do not count as proof.
54
+ The completed 0.9 plan defines the cross-framework comparison and release gate. The active application packet is `0.18.2` in the capability release plan below. Benchmark-only feature omission, unmatched accessibility, weighted scores that hide losses, and unrecorded environment differences do not count as proof.
55
55
 
56
56
  ### 0.10.0 Through 0.21.x: Application Capability Release Train
57
57
 
package/PERFORMANCE.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  Reproducibility classes: `npm run benchmark`, `npm run benchmark:keyed`, `npm run benchmark:native`, `npm run benchmark:module-cache`, `npm run benchmark:project-navigation`, `npm run benchmark:project-state`, and `npm run benchmark:source-scale` are maintained in this repository; `npm run benchmark:commerce` is a maintained paired runner over the public external storefront; older excluded-workspace sections are historical provenance only and are not current framework rankings.
4
4
 
5
+ ## 0.16.6 Owner-Triggered Capability Imports
6
+
7
+ Measured 2026-08-29 on macOS arm64 with Node 24.14.0 and Chrome. The existing
8
+ eager CodeMirror route owns 214,968 raw / 71,607 aggregate gzip B. Its guarded
9
+ lazy counterpart initially owns 12,982 raw / 6,153 gzip B and defers 250,086
10
+ raw / 80,890 gzip B, reducing initial gzip by 65,454 B (91.4%). Its static
11
+ sibling remains complete HTML with zero JavaScript.
12
+
13
+ Required Chrome proves zero initial deferred requests, one request on owner
14
+ activation, mount, exact cleanup, cached remount without another request, and
15
+ document disposal. The maintained Worker benchmark remains 907 raw / 477 gzip B
16
+ for the Worker graph and 14,456 raw / 6,160 gzip B for the window graph. Seven
17
+ clean builds record a 222.8 ms median. No browser runtime file or concept was
18
+ added.
19
+
20
+ ## 0.16.5 Bounded Navigation Cache
21
+
22
+ Measured 2026-08-28 on Linux x64 with Node 24.14.0 and Chrome. Before the
23
+ production fix, five held-prefetch races increased retained CDP documents from 8
24
+ to 16 after a newer canonical navigation had pruned those URLs. Moving the
25
+ existing revision check before the cache write restores the exact bound without
26
+ adding an LRU, cancellation registry, runtime file, or cache abstraction.
27
+
28
+ The default endurance gate runs ten batched cache races and 30 ownership cycles
29
+ in one Chrome profile. Across 101 navigations, baseline/final documents remain
30
+ 8, browser state entries 0, DOM nodes 174, listeners 7, and editor
31
+ mounts/disposals 71/71. Forced-GC heap moves from 1,826,244 to 2,022,160 bytes,
32
+ below the larger of the declared 2 MiB or 15% alarm.
33
+
34
+ The maintained Worker benchmark remains 907 raw / 477 gzip B for the Worker
35
+ graph and 14,456 raw / 6,160 gzip B for the window graph. Seven clean builds
36
+ record a 795.3 ms median on this host; timing is not compared because concurrent
37
+ host load differs. Production runtime LOC, concepts, and files remain unchanged.
38
+
5
39
  ## 0.16.4 Scoped GSAP Animation Lifecycle
6
40
 
7
41
  Measured 2026-08-28 on Linux x64 with Node 24.14.0 and Chrome. The real GSAP
package/README.md CHANGED
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
14
14
 
15
15
  > Experimental `0.16.x`: the compiler API and supported TSX surface may change.
16
16
 
17
- **Latest release: 0.16.4 - Scoped GSAP animation lifecycle.** Inline GSAP callbacks stay inside one owned effect while Kudzu retains structural DOM, native reduced-motion fallback, dependency replacement, disposal, and static exclusion. Read the [release notes](./RELEASES.md#0164---scoped-gsap-animation-lifecycle), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.4), or follow the [architecture packet](./docs/next-architecture/README.md).
17
+ **Latest release: 0.16.6 - Owner-triggered capability imports.** One literal package dependency may stay out of the initial route graph until its guarded effect owner activates, while static siblings remain JavaScript-free. Read the [release notes](./RELEASES.md#0166---owner-triggered-capability-imports), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.6), or follow the [architecture packet](./docs/next-architecture/README.md).
18
18
 
19
19
  - [Documentation](https://kudzujs.cloud/docs)
20
20
  - [Installation guide](https://kudzujs.cloud/docs#install)
package/RELEASES.md CHANGED
@@ -1,5 +1,86 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.16.6 - Owner-Triggered Capability Imports
4
+
5
+ Kudzu 0.16.6 lets one guarded effect load a literal package dependency only
6
+ when its owner activates, using native ESM code splitting without a loader or
7
+ component runtime.
8
+
9
+ ### Changed in 0.16.6
10
+
11
+ - Accepts one literal bare-package `import()` directly inside an inline
12
+ synchronous `useEffect` with a direct dependency-state early-return guard.
13
+ - Keeps relative, URL, render-time, event-handler, indirect, async-effect, and
14
+ multiple dynamic imports unsupported with source diagnostics.
15
+ - Removes the package edge from build-time component execution and preserves it
16
+ only in the route-owned effect bundle.
17
+ - Reports deferred handler closure separately as `handlers.lazyChunks` while
18
+ retaining exact route ownership and shared-chunk accounting.
19
+ - Uses native module-map deduplication across owner remounts and exact effect
20
+ cleanup on owner and document release.
21
+ - Adds no loader, retry cache, component runtime, React, VDOM, hydration, or
22
+ retained browser component tree.
23
+ - Updates `create-kudzu@0.1.131` to generate projects on
24
+ `@kudzujs/core@^0.16.6`.
25
+
26
+ ### Output And Browser Evidence
27
+
28
+ - The eager CodeMirror route owns 214,968 raw / 71,607 gzip bytes. The lazy
29
+ route initially owns 12,982 raw / 6,153 gzip bytes and defers 250,086 raw /
30
+ 80,890 gzip bytes, reducing initial gzip by 65,454 bytes (91.4%).
31
+ - Required Chrome proves zero initial chunk requests, one activation request,
32
+ package mount, exact owner cleanup, cached remount without another request,
33
+ and document cleanup.
34
+ - The static sibling emits zero JavaScript. The maintained Worker and window
35
+ graphs remain 907 raw / 477 gzip bytes and 14,456 raw / 6,160 gzip bytes.
36
+
37
+ ### Upgrade
38
+
39
+ ```sh
40
+ npm install @kudzujs/core@^0.16.6
41
+ ```
42
+
43
+ ## 0.16.5 - Bounded Navigation Cache
44
+
45
+ Kudzu 0.16.5 prevents superseded enhanced navigations from resurrecting pruned
46
+ prefetched documents and adds a reproducible long-session ownership gate.
47
+
48
+ ### Changed in 0.16.5
49
+
50
+ - Checks the navigation revision before writing a completed prefetched document
51
+ back into the route cache.
52
+ - Prevents stale slow navigations from retaining parsed documents after a newer
53
+ route has committed and pruned their URLs.
54
+ - Adds a dedicated Chrome endurance harness with machine-readable heap, DOM,
55
+ listener, logical-state, resource, and navigation counters.
56
+ - Exercises ten deterministic stale-prefetch races followed by 30 route,
57
+ conditional, keyed, dialog, and real CodeMirror ownership cycles.
58
+ - Proves a layout-owned project notification WebSocket journey with validated
59
+ keyed updates, reconnect, stale-callback rejection, offline recovery, retained
60
+ navigation lifetime, and exact cleanup.
61
+ - Adds no cache abstraction, LRU, transport runtime, ResourceIR, VDOM, hydration,
62
+ React runtime, or retained component tree.
63
+ - Updates `create-kudzu@0.1.130` to generate projects on
64
+ `@kudzujs/core@^0.16.5`.
65
+
66
+ ### Output And Browser Evidence
67
+
68
+ - Before the fix, five stale-prefetch races increased retained CDP documents
69
+ from 8 to 16 while logical state remained empty.
70
+ - With the fix, the default 101-navigation run keeps documents at 8,
71
+ `browserState.size` at 0, DOM nodes at 174, listeners at 7, and editor
72
+ mounts/disposals balanced at 71/71.
73
+ - Forced-GC heap moves from 1,826,244 to 2,022,160 bytes, below the declared
74
+ larger-of-2-MiB-or-15% alarm.
75
+ - The maintained Worker graph remains 907 raw / 477 gzip bytes; the window graph
76
+ remains 14,456 raw / 6,160 gzip bytes. Static routes remain zero JavaScript.
77
+
78
+ ### Upgrade
79
+
80
+ ```sh
81
+ npm install @kudzujs/core@^0.16.5
82
+ ```
83
+
3
84
  ## 0.16.4 - Scoped GSAP Animation Lifecycle
4
85
 
5
86
  Kudzu 0.16.4 lets inline GSAP callbacks remain inside one owned effect while
@@ -15,7 +15,7 @@ The completed `0.9.0` milestone is recorded in [`0.9-semantic-compression.md`](.
15
15
 
16
16
  [`1.0-large-application-compatibility-audit.md`](./1.0-large-application-compatibility-audit.md) records the first post-0.9 probes against Memos, Apache Answer, and Actual Budget. The audit finds that reduced slices build but whole-application source retention and behavior parity do not yet pass.
17
17
 
18
- [`application-capability-release-plan.md`](./application-capability-release-plan.md) is the authoritative post-0.9 execution queue, currently at `0.17.0`. It assigns one application-capability section to each minor release and one independently accepted evidence packet to each patch release from `0.10.0` through the `1.0.0` gate. It supersedes the provisional 0.10/0.11/0.12 tool-first ordering in the completed 0.9 handoff without rewriting that historical record.
18
+ [`application-capability-release-plan.md`](./application-capability-release-plan.md) is the authoritative post-0.9 execution queue, currently at `0.18.2`. It assigns one application-capability section to each minor release and one independently accepted evidence packet to each patch release from `0.10.0` through the `1.0.0` gate. It supersedes the provisional 0.10/0.11/0.12 tool-first ordering in the completed 0.9 handoff without rewriting that historical record.
19
19
 
20
20
  ## Required Invariants
21
21
 
@@ -1302,9 +1302,7 @@ Worker/effect benchmark remains 907 raw / 477 gzip bytes for the Worker graph an
1302
1302
  625.9 ms, recorded without a timing comparison because the host differs from the
1303
1303
  published baseline.
1304
1304
 
1305
- ## `0.17.x`: Long-Lived Applications
1306
-
1307
- ### `0.17.0`: Endurance Harness
1305
+ ### `0.16.5`: Endurance Harness
1308
1306
 
1309
1307
  - **Purpose:** make long-session correctness locally reproducible.
1310
1308
  - **Expected files:** a dedicated browser soak harness and machine-readable heap,
@@ -1314,7 +1312,40 @@ published baseline.
1314
1312
  - **Stop condition:** only a short synthetic timing loop is measured.
1315
1313
  - **Done condition:** failures identify the owner that retained memory or work.
1316
1314
 
1317
- ### `0.17.1`: WebSocket Application Journey
1315
+ **Result:** closed through existing document, route, conditional, keyed, and
1316
+ effect ownership. A dedicated application fixture repeatedly opens and closes a
1317
+ native dialog, removes and recreates keyed row state, conditionally disposes and
1318
+ remounts a real CodeMirror editor, and navigates to a canonical released route.
1319
+ The standalone Chrome harness keeps one browser profile alive, runs five warm-up
1320
+ and 30 measured cycles by default, forces GC only before five-cycle checkpoints,
1321
+ and retains machine-readable `samples.jsonl`, `summary.json`, and
1322
+ `environment.json` artifacts under the ignored `test-results/endurance/` path.
1323
+ The required default run completed 71 enhanced navigations with 71 editor mounts
1324
+ and 71 disposals. Every released checkpoint had zero active editors, dialogs,
1325
+ and keyed rows, 29 route elements, six documents, 141 DOM nodes, and seven event
1326
+ listeners. Forced-GC heap moved from 1,820,132 to 2,005,572 bytes, below the
1327
+ larger of the declared 2 MiB or 15% observation alarm. A deterministic failure
1328
+ reports `route:/plain` and the mismatched counters; browser exceptions and failed
1329
+ requests are retained with the same owner. The interactive route graph is
1330
+ 242,928 raw / 81,792 aggregate gzip bytes across 11 JavaScript files. Semantic
1331
+ primitives, IR kinds, compiler passes, core/compiler LOC, runtime concepts,
1332
+ runtime files, and public APIs change by zero; one test fixture and one browser
1333
+ harness were added. This evidence changed no production source by itself and was
1334
+ published with the later stale-prefetch fix in the actual `0.16.5` patch.
1335
+
1336
+ Release transaction complete at commit
1337
+ `f23db7403060df7615b684f3a4a39781e600ed23` and immutable tag `v0.16.5`.
1338
+ CI run `33235364419` passed Node 22, required-Chrome Node 24, and the Cloudflare
1339
+ deployment. The GitHub release is
1340
+ [`v0.16.5`](https://github.com/kudzujs/kudzu/releases/tag/v0.16.5), and publish
1341
+ workflow run `33235705347` published and verified `@kudzujs/core@0.16.5` and
1342
+ `create-kudzu@0.1.130`. A fresh registry-only install imported core, confirmed
1343
+ both exact package versions, generated a two-route application, and passed its
1344
+ TypeScript and production build check.
1345
+
1346
+ ## `0.17.x`: Long-Lived Applications
1347
+
1348
+ ### `0.17.0`: WebSocket Application Journey
1318
1349
 
1319
1350
  - **Purpose:** move from isolated fake socket ownership to project notifications.
1320
1351
  - **Acceptance:** connect, message, keyed update, reconnect, stale socket
@@ -1323,7 +1354,31 @@ published baseline.
1323
1354
  - **Done condition:** repeated navigation balances every listener, timer, and
1324
1355
  socket handle.
1325
1356
 
1326
- ### `0.17.2`: Shared SSE/Transport Decision
1357
+ **Result:** closed through existing layout state, `EffectIR`, keyed-list, and
1358
+ enhanced-navigation ownership. The first project-application browser extension
1359
+ failed at `websocket-connect`; one inline layout effect now owns a native
1360
+ WebSocket, four socket listeners, native online/offline listeners, generation
1361
+ and socket-identity stale guards, and one 50 ms reconnect timer. Incoming
1362
+ snapshots are validated before replacing the existing notification array, so a
1363
+ same-key server update retains its DOM node and live-region ownership. Required
1364
+ Chrome proves connect, message, keyed update, visible error, reconnect, stale
1365
+ message/error rejection, offline cancellation, online recovery, and eight
1366
+ list/detail enhanced transitions with one retained layout socket. The journey
1367
+ creates three sockets total; while connected it has one active socket, four
1368
+ socket listeners, and two online/offline listeners. Of two reconnect timers,
1369
+ one fires and one is cleared. Final document disposal leaves zero active sockets,
1370
+ socket listeners, window listeners, and reconnect timers, and repeated disposal
1371
+ changes no counter. The project deploy grows by 2,908 raw / 652 aggregate gzip
1372
+ bytes; the list-route JavaScript graph grows by 1,755 raw / 460 aggregate gzip
1373
+ bytes, while `/help` remains zero JavaScript. Semantic primitives, IR kinds,
1374
+ compiler passes, core/compiler LOC, runtime concepts, runtime files, transport
1375
+ abstractions, and public APIs change by zero; one existing application fixture
1376
+ and one browser scenario grow. The maintained Worker/effect graph remains 907
1377
+ raw / 477 gzip bytes for Worker and 14,456 raw / 6,159 gzip bytes for window;
1378
+ its seven-run Linux median was 605.6 ms. No production source changed, so no
1379
+ release was consumed.
1380
+
1381
+ ### `0.17.1`: Shared SSE/Transport Decision
1327
1382
 
1328
1383
  - **Purpose:** test multiple independently mounted consumers, replay, and
1329
1384
  reconnect against existing layout effects/shared state.
@@ -1334,7 +1389,20 @@ published baseline.
1334
1389
  - **Done condition:** existing composition passes or a minimal transport owner is
1335
1390
  architecture-approved.
1336
1391
 
1337
- ### `0.17.3`: Memory And Disposal Gate
1392
+ **Result:** closed by the stop condition with zero qualifying applications.
1393
+ Mattermost and Twenty remain external research candidates, but neither has a
1394
+ repository-owned reduction proving the complete shared-consumer, ordered replay,
1395
+ reconnect, independent removal, final close, and bounded-history contract. The
1396
+ project application has one layout consumer; all Lupin reductions belong to one
1397
+ application and each owns its socket in one page effect; the route WebSocket,
1398
+ navigation telemetry, Worker dashboard, Memos audit, and development EventSource
1399
+ also lack the complete shared transport shape. Existing layout state and
1400
+ independent `EffectIR` owners remain authoritative. Semantic primitives, IR
1401
+ kinds, compiler passes, core/compiler LOC, runtime concepts, runtime files,
1402
+ fixtures, browser bytes, and public APIs change by zero. No production source
1403
+ changed, so no release was consumed.
1404
+
1405
+ ### `0.17.2`: Memory And Disposal Gate
1338
1406
 
1339
1407
  - **Purpose:** close document/layout/route/range/external-instance memory risks.
1340
1408
  - **Acceptance:** long repeated journeys show bounded heap, DOM, prefetch cache,
@@ -1343,6 +1411,29 @@ published baseline.
1343
1411
  - **Done condition:** retained growth has a declared bound and repeated samples
1344
1412
  pass the agreed threshold.
1345
1413
 
1414
+ **Result:** complete with one navigation-cache ownership fix. A deterministic
1415
+ race holds unique prefetched documents, starts navigation to each, commits a
1416
+ canonical route that prunes them, and then releases the stale responses. Before
1417
+ the fix, five races left `browserState.size` at zero but increased CDP documents
1418
+ from 8 to 16 under owner `navigation:documents`: stale `navigate()` calls wrote
1419
+ their parsed documents back into the cache before checking the navigation
1420
+ revision. `navigation-runtime.js` now checks revision before the existing cache
1421
+ write; no cache type, LRU, cancellation registry, or public instrumentation was
1422
+ added. The default gate batches ten races within browser connection limits, then
1423
+ runs 30 route, conditional, keyed, dialog, and editor cycles in one Chrome
1424
+ profile. Across 101 navigations, baseline/final documents remain 8, browser state
1425
+ entries 0, DOM nodes 174, listeners 7, and editor mounts/disposals 71/71. Forced-
1426
+ GC heap moves from 1,826,244 to 2,022,160 bytes, below the declared larger-of-2-
1427
+ MiB-or-15% alarm. Semantic primitives, IR kinds, compiler passes, runtime
1428
+ concepts, runtime files, cache abstractions, and public APIs change by zero; one
1429
+ production runtime reorders one existing statement with zero LOC growth. The
1430
+ maintained Worker graph remains 907 raw / 477 gzip bytes and the window graph
1431
+ remains 14,456 raw bytes with gzip moving from 6,159 to 6,160 bytes; its seven-
1432
+ run Linux median was 795.3 ms, recorded without a timing comparison because the
1433
+ host load differs. One endurance fixture and browser gate grow, while static
1434
+ routes remain zero JavaScript. This packet ships as the actual `0.16.5` patch to
1435
+ retain the public `0.16.x` version line.
1436
+
1346
1437
  ## `0.18.x`: Lazy Loading And Code Splitting
1347
1438
 
1348
1439
  ### `0.18.0`: Route And Feature Artifact Baseline
@@ -1356,6 +1447,37 @@ published baseline.
1356
1447
  measured.
1357
1448
  - **Done condition:** every eager dependency has a structural reason.
1358
1449
 
1450
+ **Result:** closed by the existing RouteBuildRecord and artifact report. A
1451
+ test-only collector builds the greenfield project, real CodeMirror editor, four
1452
+ separate Apache Answer reductions, and Memos outline reduction: seven fixtures
1453
+ and 25 routes total. It joins each route's source page to HTML, runtime entries
1454
+ and requirements, handler entries/chunks, Worker entries/chunks, and styles,
1455
+ then records exact raw/gzip bytes and SHA-256 values. Every generated JavaScript
1456
+ file has at least one route owner, every direct module script has one matching
1457
+ `modulepreload`, and every static control owns zero JavaScript and zero preload.
1458
+ No measured fixture emits a shared chunk.
1459
+
1460
+ The greenfield deploy is 45 files and 196,678 raw / 60,880 aggregate gzip
1461
+ bytes. Its project routes own 38,652-69,178 raw JavaScript bytes; `/login` owns
1462
+ 12,093 raw bytes and `/help` owns zero. The editor route owns 214,968 raw /
1463
+ 71,607 gzip bytes, including its 200,260 raw / 64,949 gzip CodeMirror handler;
1464
+ its static sibling owns zero. The Answer question reduction owns 32,022 raw /
1465
+ 12,408 gzip bytes. Its route-shell static pages own zero while the two runtime
1466
+ parameter pages own 2,134 and 2,078 raw bytes. Answer auth routes own 25,684 and
1467
+ 26,818 raw bytes with a zero-byte public sibling; authoring/admin routes own
1468
+ 12,251 and 20,919 raw bytes with a zero-byte legal sibling. The Memos outline
1469
+ owns 14,701 raw / 7,044 gzip bytes and its static sibling owns zero.
1470
+
1471
+ Current navigation prefetch remains structurally bounded: eligible same-origin
1472
+ group links prefetch on hover, focus, viewport proximity, or idle fallback;
1473
+ promises deduplicate, failures leave the cache, current/eligible URLs define the
1474
+ retained set, and the `0.16.5` revision check rejects stale navigation results
1475
+ before cache insertion. The report intentionally records emitted closure rather
1476
+ than esbuild input-byte attribution. Answer and Memos remain disconnected
1477
+ reductions, not whole-application claims. Semantic primitives, IR kinds,
1478
+ compiler passes, production compiler/runtime LOC, runtime concepts/files, and
1479
+ public APIs change by zero. No release is consumed.
1480
+
1359
1481
  ### `0.18.1`: Bounded Lazy Capability Import
1360
1482
 
1361
1483
  - **Purpose:** permit one statically analyzable feature dependency to load only
@@ -1369,6 +1491,36 @@ published baseline.
1369
1491
  - **Done condition:** the feature loader is capability-specific, not a component
1370
1492
  runtime.
1371
1493
 
1494
+ **Completion evidence:** one direct literal bare-package `import()` may remain
1495
+ inside an inline synchronous effect when its first statement is the exact
1496
+ dependency-state guard `if (!state) return`. The source graph, descriptor
1497
+ session, ModuleIR validation, build-module erasure, esbuild split graph, and
1498
+ route artifact report preserve that edge without adding a loader. Relative,
1499
+ URL, render-time, event, indirect, async-effect, unguarded, and multiple dynamic
1500
+ imports remain rejected. Failed native ESM evaluation remains visible through
1501
+ application-owned error state; same-document retry of the failed URL is not
1502
+ claimed because the browser module map cannot be reset without adding the
1503
+ loader forbidden by this packet.
1504
+
1505
+ The CodeMirror-derived fixture initially requests no deferred chunk. Activation
1506
+ requests one chunk, mounts the editor, and owner release destroys it. Remount
1507
+ uses the browser module map without another request, and document disposal
1508
+ balances the second mount. The initial eager graph is 12,982 raw / 6,153 gzip
1509
+ bytes versus 214,968 raw / 71,607 gzip bytes for the existing eager editor;
1510
+ 250,086 raw / 80,890 gzip bytes move to the deferred graph. Initial gzip falls
1511
+ by 65,454 bytes (91.4%), while the static sibling remains 0 B JavaScript.
1512
+
1513
+ The packet adds no semantic primitive, compiler pass, public API, browser
1514
+ runtime concept/file, React, VDOM, hydration, retained component tree, or retry
1515
+ cache. It adds one validated `ImportIR` kind and additive
1516
+ `handlers.lazyChunks` reporting. Production compiler sources change by 76 added
1517
+ and 11 removed lines. The artifact collector now covers eight fixtures and 27
1518
+ routes with no ownerless JavaScript or preload mismatch. `npm run check`, 284
1519
+ required-Chrome tests, package smoke, and the maintained benchmark pass. The
1520
+ Worker graph remains 907 raw / 477 gzip bytes; the window graph remains 14,456
1521
+ raw / 6,160 gzip bytes, with a seven-build median of 222.8 ms. This packet ships
1522
+ as the actual `0.16.6` patch to retain the public `0.16.x` version line.
1523
+
1372
1524
  ### `0.18.2`: Lazy Editor Journey
1373
1525
 
1374
1526
  - **Purpose:** apply `0.18.1` to the real editor.
@@ -1679,4 +1831,10 @@ release transaction where possible or document and publish a forward-fix patch.
1679
1831
  | `0.16.2` | Closed by existing effect ownership | Preserve retained Chart.js data/resize updates, listener and route cleanup, fresh remount, accessibility, and static exclusion. | No production change; no release consumed |
1680
1832
  | `0.16.3` | Released | Preserve state-owned order, keyed identity, keyboard parity, invalid-input recovery, package disposal, and static exclusion. | None |
1681
1833
  | `0.16.4` | Released | Preserve scoped GSAP presentation, reduced-motion fallback, dependency/owner cleanup, fresh remount, and static exclusion. | None |
1682
- | `0.17.0` | Active | Establish the long-session endurance harness and machine-readable ownership counters. | None |
1834
+ | `0.16.5` | Released | Preserve the reproducible endurance evidence, project notification ownership, and bounded stale-prefetch cache disposal. | None |
1835
+ | `0.17.0` | Closed by existing layout effect ownership | Preserve project notification connect, keyed update, reconnect, stale rejection, offline recovery, retained navigation, and exact cleanup evidence. | No production change; no release consumed |
1836
+ | `0.17.1` | Closed by stop condition | Preserve independent effect ownership until three applications prove the complete shared transport and bounded replay contract. | Zero qualifying applications; no release consumed |
1837
+ | `0.17.2` | Released as `0.16.5` | Preserve stale-prefetch rejection and bounded document, state, resource, listener, DOM, and external-instance ownership. | Patch release retained the `0.16.x` public version line |
1838
+ | `0.18.0` | Closed by existing artifact reporting | Preserve exact route/source ownership, bytes, hashes, preload equality, zero-JavaScript controls, and bounded prefetch policy. | No production change; no release consumed |
1839
+ | `0.18.1` | Released as `0.16.6` | Preserve one guarded literal package edge, native module-map deduplication, exact owner cleanup, deferred artifact ownership, and static exclusion. | Patch release retained the `0.16.x` public version line |
1840
+ | `0.18.2` | Active | Apply the bounded edge to the retained editor mount/update/dispose journey without broadening dynamic imports. | None |
@@ -1,6 +1,6 @@
1
1
  # Current Compiler Architecture
2
2
 
3
- This maps the current `0.16.4` architecture, built on the completed `0.9.0` semantic-compression release and `0.8.23` Goal A compiler foundation. The active application packet is `0.17.0`; file and function names are the stable references, while line numbers are intentionally omitted because later work may still move code.
3
+ This maps the current `0.16.6` architecture, built on the completed `0.9.0` semantic-compression release and `0.8.23` Goal A compiler foundation. The active application packet is `0.18.2`; file and function names are the stable references, while line numbers are intentionally omitted because later work may still move code.
4
4
 
5
5
  ## Responsibility Map
6
6
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  ## Status
4
4
 
5
- Completed compiler-foundation record and longer-term plan after `0.8.35`. The [`0.9-semantic-compression.md`](./0.9-semantic-compression.md) execution queue is complete. The active queue for current work is [`application-capability-release-plan.md`](./application-capability-release-plan.md) at packet `0.17.0`. This document does not mark any remaining planned capability as supported and does not authorize a React runtime, VDOM, hydration, retained browser component tree, generic rerenderer, public store/query/resource API, SPA router, or islands.
5
+ Completed compiler-foundation record and longer-term plan after `0.8.35`. The [`0.9-semantic-compression.md`](./0.9-semantic-compression.md) execution queue is complete. The active queue for current work is [`application-capability-release-plan.md`](./application-capability-release-plan.md) at packet `0.18.2`. This document does not mark any remaining planned capability as supported and does not authorize a React runtime, VDOM, hydration, retained browser component tree, generic rerenderer, public store/query/resource API, SPA router, or islands.
6
6
 
7
- [`MIGRATION_ROADMAP.md`](../../MIGRATION_ROADMAP.md) remains the product authority for product invariants and fixture-driven feature selection. [`application-capability-release-plan.md`](./application-capability-release-plan.md) is authoritative for current work order and evidence at packet `0.17.0`; this plan retains the completed foundation, deferred program, and long-term production gates. If implementation evidence changes either boundary, update the relevant document before broadening a patch.
7
+ [`MIGRATION_ROADMAP.md`](../../MIGRATION_ROADMAP.md) remains the product authority for product invariants and fixture-driven feature selection. [`application-capability-release-plan.md`](./application-capability-release-plan.md) is authoritative for current work order and evidence at packet `0.18.2`; this plan retains the completed foundation, deferred program, and long-term production gates. If implementation evidence changes either boundary, update the relevant document before broadening a patch.
8
8
 
9
9
  ## Product Outcome
10
10
 
@@ -483,4 +483,4 @@ The first comparison is Kudzu versus React + Vite using the same agent, model, t
483
483
 
484
484
  ## Immediate Decision
485
485
 
486
- All listed foundation and `0.9` slices are complete. Current work continues at `0.17.0` Endurance Harness under the [`application-capability-release-plan.md`](./application-capability-release-plan.md); keep ResourceIR limited to qualifying independent fixtures, and do not add range ownership, virtualization, optimistic transactions, a public adapter/store API, or a router before evidence justifies them.
486
+ All listed foundation and `0.9` slices are complete. Current work continues at `0.18.2` Lazy Editor Journey under the [`application-capability-release-plan.md`](./application-capability-release-plan.md); keep ResourceIR limited to qualifying independent fixtures, and do not add range ownership, virtualization, optimistic transactions, a public adapter/store API, or a router before evidence justifies them.
@@ -1,6 +1,6 @@
1
1
  # Planned Version Sequence
2
2
 
3
- This is an execution sequence, not release history. `0.8.16` through `0.8.62` are completed scopes represented by package/release records. The `0.10.0` through `1.0.0` minor/patch sequence is maintained in [`application-capability-release-plan.md`](./application-capability-release-plan.md), currently at `0.17.0`; that plan supersedes the provisional tool-first 0.10/0.11/0.12 ordering in the completed 0.9 handoff.
3
+ This is an execution sequence, not release history. `0.8.16` through `0.8.62` are completed scopes represented by package/release records. The `0.10.0` through `1.0.0` minor/patch sequence is maintained in [`application-capability-release-plan.md`](./application-capability-release-plan.md), currently at `0.18.2`; that plan supersedes the provisional tool-first 0.10/0.11/0.12 ordering in the completed 0.9 handoff.
4
4
 
5
5
  Keep each patch behavior-preserving and independently reviewable. If a boundary proves inseparable, revise this plan before combining releases; do not silently broaden a patch.
6
6
 
@@ -66,7 +66,7 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
66
66
 
67
67
  ## Generator Versions
68
68
 
69
- `create-kudzu@0.1.129` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.4`.
69
+ `create-kudzu@0.1.131` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.6`.
70
70
 
71
71
  ## Release Boundary
72
72
 
@@ -4,6 +4,7 @@ import { knownGlobalNames } from "./analysis/binding-index.mjs"
4
4
  import { bindingNames, isFunctionLike, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, unwrapExpression } from "./ast-helpers.mjs"
5
5
  import { generateCommandBehavior } from "./codegen/command-codegen.mjs"
6
6
  import { assertModuleIRReferences, createModuleIR, registerBinding, registerCommandHandler, registerDerived, registerEffect, registerKeyedBlock, registerModuleHandler, registerSignal } from "./ir/module-ir.mjs"
7
+ import { ownedLazyPackageImport } from "./source-graph.mjs"
7
8
 
8
9
  export function createSemanticArtifact(file) {
9
10
  return { componentAnalysis: createComponentAnalysis(file), moduleIR: createModuleIR(file) }
@@ -168,6 +169,17 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
168
169
  const usedReducers = referencedReducerDispatches(expression.body, reducers, expression, indexedBindingIndex)
169
170
  const imports = [...referencedImportedBindings(expression, importBindings, indexedBindingIndex)].map(name => importBindings.get(name))
170
171
  imports.push(...[...usedReducers].map(name => reducers.get(name).import).filter(Boolean))
172
+ const dynamicImports = []
173
+ const visitDynamicImports = node => {
174
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
175
+ if (role !== "effect" || !ownedLazyPackageImport(node)) throw new Error("Dynamic package imports require one literal import() directly inside an inline owned effect")
176
+ dynamicImports.push({ kind: "dynamic", local: node.arguments[0].text, target: node.arguments[0].text, package: true })
177
+ }
178
+ ts.forEachChild(node, visitDynamicImports)
179
+ }
180
+ visitDynamicImports(expression)
181
+ if (dynamicImports.length > 1) throw new Error("An owned effect may contain only one dynamic package import")
182
+ imports.push(...dynamicImports)
171
183
  const captures = new Set([...allCaptures].filter(name => !importBindings.has(name) && !usedReducers.has(name)))
172
184
  registerClientImports(imports)
173
185
  const usedStates = referencedStateNames(expression.body, setters, expression, indexedBindingIndex)
@@ -24,6 +24,11 @@ export function assertModuleIRReferences(moduleIR, componentAnalysis) {
24
24
  indexed("EffectIR", moduleIR.effects)
25
25
  indexed("KeyedBlockIR", moduleIR.keyedBlocks)
26
26
  indexed("ImportIR", moduleIR.imports)
27
+ const dynamicImports = new Set()
28
+ for (const imported of moduleIR.imports) if (imported.kind === "dynamic") {
29
+ if (!imported.package || typeof imported.target !== "string" || !imported.target || imported.target.startsWith(".") || imported.target.startsWith("/") || /^[a-z][a-z\d+.-]*:/i.test(imported.target) || imported.local !== imported.target) throw new Error(`ImportIR ${imported.slot} has an invalid dynamic package target`)
30
+ dynamicImports.add(imported.slot)
31
+ }
27
32
  for (const action of moduleIR.sharedActions) slot(moduleIR.sharedStates, action.state, `SharedActionIR ${action.slot}`, "SharedStateIR")
28
33
  if (componentAnalysis) {
29
34
  indexed("Component owner", componentAnalysis.owners)
@@ -82,7 +87,10 @@ export function assertModuleIRReferences(moduleIR, componentAnalysis) {
82
87
  exported(handler, `HandlerIR ${handler.slot}`)
83
88
  for (const [index, command] of (handler.commands ?? []).entries()) slot(moduleIR.signals, command.signal, `HandlerIR ${handler.slot} command ${index}`, "SignalIR")
84
89
  for (const [index, signal] of (handler.signals ?? []).entries()) slot(moduleIR.signals, signal.signal, `HandlerIR ${handler.slot} signal ${index}`, "SignalIR")
85
- for (const [index, imported] of (handler.imports ?? []).entries()) slot(moduleIR.imports, imported, `HandlerIR ${handler.slot} import ${index}`, "ImportIR")
90
+ for (const [index, imported] of (handler.imports ?? []).entries()) {
91
+ slot(moduleIR.imports, imported, `HandlerIR ${handler.slot} import ${index}`, "ImportIR")
92
+ if (dynamicImports.has(imported) && handler.role !== "effect") throw new Error(`HandlerIR ${handler.slot} dynamic import ${index} requires role "effect"`)
93
+ }
86
94
  for (const [index, capture] of (handler.captures ?? []).entries()) if (capture.symbol !== undefined) slot(moduleIR.symbols, capture.symbol, `HandlerIR ${handler.slot} capture ${index}`, "SymbolRef")
87
95
  for (const [index, action] of (handler.actions ?? []).entries()) slot(moduleIR.sharedActions, action, `HandlerIR ${handler.slot} action ${index}`, "SharedActionIR")
88
96
  if (handler.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, handler.keyedBlock, `HandlerIR ${handler.slot} keyed block`, "KeyedBlockIR")
@@ -90,7 +98,10 @@ export function assertModuleIRReferences(moduleIR, componentAnalysis) {
90
98
  for (const binding of moduleIR.bindings) {
91
99
  exported(binding, `BindingIR ${binding.slot}`)
92
100
  for (const [index, signal] of (binding.signals ?? []).entries()) slot(moduleIR.signals, signal, `BindingIR ${binding.slot} signal ${index}`, "SignalIR")
93
- for (const [index, imported] of (binding.imports ?? []).entries()) slot(moduleIR.imports, imported, `BindingIR ${binding.slot} import ${index}`, "ImportIR")
101
+ for (const [index, imported] of (binding.imports ?? []).entries()) {
102
+ slot(moduleIR.imports, imported, `BindingIR ${binding.slot} import ${index}`, "ImportIR")
103
+ if (dynamicImports.has(imported)) throw new Error(`BindingIR ${binding.slot} cannot reference a dynamic import`)
104
+ }
94
105
  for (const [index, capture] of (binding.captures ?? []).entries()) if (capture.symbol !== undefined) slot(moduleIR.symbols, capture.symbol, `BindingIR ${binding.slot} capture ${index}`, "SymbolRef")
95
106
  if (binding.derived) {
96
107
  const derived = slot(moduleIR.derived, binding.derived.derived, `BindingIR ${binding.slot} derived value`, "DerivedIR")
@@ -39,7 +39,8 @@ export function createRouteArtifactReport(records, {
39
39
  runtime: routeRuntimeEdges(record, capability, family, base, navigationAssets.get(record.route)),
40
40
  handlers: {
41
41
  entries: handlerEntries,
42
- chunks: handlerOutputs.filter(output => !handlerEntries.includes(output))
42
+ chunks: handlerOutputs.eager.filter(output => !handlerEntries.includes(output)),
43
+ lazyChunks: handlerOutputs.lazy
43
44
  },
44
45
  workers,
45
46
  styles: [...record.artifacts.styles].sort()
@@ -47,7 +48,7 @@ export function createRouteArtifactReport(records, {
47
48
  }).sort((left, right) => left.route.localeCompare(right.route))
48
49
  const owners = new Map()
49
50
  for (const route of routes) {
50
- for (const path of [...route.handlers.chunks, ...route.workers.flatMap(worker => worker.chunks)]) {
51
+ for (const path of [...route.handlers.chunks, ...route.handlers.lazyChunks, ...route.workers.flatMap(worker => worker.chunks)]) {
51
52
  const paths = owners.get(path) ?? new Set()
52
53
  paths.add(route.route)
53
54
  owners.set(path, paths)
@@ -109,7 +110,7 @@ function outputGraph(metafile, outputDirectory, base) {
109
110
  for (const [output, metadata] of Object.entries(metafile.outputs)) {
110
111
  const absolute = resolve(output)
111
112
  const url = paths.get(absolute)
112
- const imports = (metadata.imports ?? []).filter(entry => !entry.external).map(entry => paths.get(resolve(entry.path)) ?? paths.get(resolve(dirname(absolute), entry.path))).filter(Boolean)
113
+ const imports = (metadata.imports ?? []).filter(entry => !entry.external).map(entry => ({ path: paths.get(resolve(entry.path)) ?? paths.get(resolve(dirname(absolute), entry.path)), kind: entry.kind })).filter(entry => entry.path)
113
114
  graph.set(url, [...new Set(imports)].sort())
114
115
  }
115
116
  return graph
@@ -123,12 +124,20 @@ function outputUrl(output, outputDirectory, base) {
123
124
 
124
125
  function closure(entries, graph, requireEntries) {
125
126
  const visited = new Set()
126
- const visit = output => {
127
- if (visited.has(output)) return
127
+ const eager = new Set()
128
+ const lazy = new Set()
129
+ const visit = (output, deferred = false) => {
130
+ const key = `${Number(deferred)}:${output}`
131
+ if (visited.has(key)) return
132
+ visited.add(key)
128
133
  if (requireEntries && !graph.has(output)) throw new Error(`Bundled handler entry was not emitted: ${output}`)
129
- visited.add(output)
130
- for (const imported of graph.get(output) ?? []) visit(imported)
134
+ if (deferred && !eager.has(output)) lazy.add(output)
135
+ else {
136
+ eager.add(output)
137
+ lazy.delete(output)
138
+ }
139
+ for (const imported of graph.get(output) ?? []) visit(imported.path, deferred || imported.kind === "dynamic-import")
131
140
  }
132
141
  for (const entry of entries) visit(entry)
133
- return [...visited].sort()
142
+ return { eager: [...eager].sort(), lazy: [...lazy].sort() }
134
143
  }
@@ -22,6 +22,7 @@ import { createReactMigrationPass, reactMemoExpression } from "./react-migration
22
22
  import { normalizeRenderControlFlow } from "./render-control-pass.mjs"
23
23
  import { createRouterPass } from "./router-pass.mjs"
24
24
  import { createProjectSession } from "./project-session.mjs"
25
+ import { ownedLazyPackageImport } from "./source-graph.mjs"
25
26
  import { createZustandPass } from "./zustand-pass.mjs"
26
27
 
27
28
  export function createSourceCompiler(project) {
@@ -2029,6 +2030,19 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
2029
2030
  if (calculationEntries.length && (calculationEntries.length !== 1 || dependencyEntries.length !== 1)) effectFail(dependencies, "useEffect() selected calculation fields must be the only dependency")
2030
2031
  if (!effectOwner) fail(node, "useEffect() cannot be used outside a Kudzu component")
2031
2032
  if (!ts.isBlock(callback.body)) effectFail(callback, "useEffect() callback must use a block body")
2033
+ const lazyPackageImports = []
2034
+ const collectLazyPackageImports = current => {
2035
+ if (ownedLazyPackageImport(current)) lazyPackageImports.push(current)
2036
+ else ts.forEachChild(current, collectLazyPackageImports)
2037
+ }
2038
+ collectLazyPackageImports(callback)
2039
+ if (lazyPackageImports.length) {
2040
+ const guard = callback.body.statements[0]
2041
+ const condition = guard && ts.isIfStatement(guard) ? unwrapExpression(guard.expression) : undefined
2042
+ const guardedState = condition && ts.isPrefixUnaryExpression(condition) && condition.operator === ts.SyntaxKind.ExclamationToken && ts.isIdentifier(unwrapExpression(condition.operand)) ? unwrapExpression(condition.operand).text : undefined
2043
+ const exits = guard && ts.isIfStatement(guard) && ts.isReturnStatement(guard.thenStatement) && !guard.thenStatement.expression
2044
+ if (lazyPackageImports.length !== 1 || !guardedState || !exits || !ordinaryDependencies.some(dependency => dependency.text === guardedState)) effectFail(lazyPackageImports[0], "Dynamic package import requires one direct dependency-state guard as the first effect statement")
2045
+ }
2032
2046
  const cleanupSubstitutions = new Map()
2033
2047
  const collectNamedCleanups = current => {
2034
2048
  if (current !== callback && isFunctionLike(current)) return
@@ -2120,7 +2134,13 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
2120
2134
  factory.createSpreadAssignment(evaluator.descriptor),
2121
2135
  factory.createPropertyAssignment("field", factory.createStringLiteral(dependencyEntries[index].field))
2122
2136
  ]) : factory.createNull()) : []
2123
- const buildCallback = [...packageBindings].some(([name]) => referenceIdentifiers(callback, name).length)
2137
+ let hasLazyPackageImport = false
2138
+ const findLazyPackageImport = current => {
2139
+ if (ownedLazyPackageImport(current)) hasLazyPackageImport = true
2140
+ else ts.forEachChild(current, findLazyPackageImport)
2141
+ }
2142
+ findLazyPackageImport(callback)
2143
+ const buildCallback = hasLazyPackageImport || [...packageBindings].some(([name]) => referenceIdentifiers(callback, name).length)
2124
2144
  ? factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createBlock([], false))
2125
2145
  : callback
2126
2146
  return factory.updateCallExpression(node, node.expression, node.typeArguments, [
@@ -21,6 +21,7 @@ export function createSourceGraph(root) {
21
21
  if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
22
22
  const argument = node.arguments[0]
23
23
  const specifier = node.arguments.length === 1 && ts.isStringLiteralLike(argument) ? JSON.stringify(argument.text) : argument?.getText(sourceFile) ?? "<missing>"
24
+ if (ownedLazyPackageImport(node)) return
24
25
  throw sourceNodeError(node, sourceFile, `Dynamic import ${specifier} is not supported in ordinary source modules`)
25
26
  }
26
27
  ts.forEachChild(node, rejectDynamicImports)
@@ -44,6 +45,18 @@ export function createSourceGraph(root) {
44
45
  return { ordinaryRuntimeDependencies, parseSourceFile, resolveSourceImport, runtimeModuleReference }
45
46
  }
46
47
 
48
+ export function ownedLazyPackageImport(node) {
49
+ if (!ts.isCallExpression(node) || node.expression.kind !== ts.SyntaxKind.ImportKeyword || node.arguments.length !== 1 || !ts.isStringLiteral(node.arguments[0])) return false
50
+ const target = node.arguments[0].text
51
+ if (!target || target.startsWith(".") || target.startsWith("/") || /^[a-z][a-z\d+.-]*:/i.test(target)) return false
52
+ for (let current = node.parent; current; current = current.parent) {
53
+ if (!ts.isArrowFunction(current) && !ts.isFunctionExpression(current)) continue
54
+ const call = current.parent
55
+ return ts.isCallExpression(call) && call.arguments[0] === current && ts.isIdentifier(call.expression) && call.expression.text === "useEffect" && !current.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)
56
+ }
57
+ return false
58
+ }
59
+
47
60
  export function runtimeModuleReference(node) {
48
61
  if (ts.isExportDeclaration(node)) return !node.isTypeOnly && (!node.exportClause || !ts.isNamedExports(node.exportClause) || node.exportClause.elements.some(entry => !entry.isTypeOnly))
49
62
  const clause = node.importClause
@@ -129,9 +129,9 @@ async function navigate(url, push, source) {
129
129
  try { documentResult = await cached }
130
130
  catch { documentResult = await fetchDocument(url, record, request.signal) }
131
131
  } else documentResult = await fetchDocument(url, record, request.signal)
132
- documents.set(url.href, Promise.resolve(documentResult))
133
132
  const { incoming, parsed, capabilities } = documentResult
134
133
  if (current !== revision) return
134
+ documents.set(url.href, Promise.resolve(documentResult))
135
135
  styleUpdate = prepareStyles(parsed.styles)
136
136
  pendingStyleUpdate = styleUpdate
137
137
  await styleUpdate.ready
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.16.4",
3
+ "version": "0.16.6",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -53,7 +53,8 @@
53
53
  "build": "node ./bin/kudzu.mjs build",
54
54
  "dev": "node ./bin/kudzu.mjs dev",
55
55
  "check": "tsc --noEmit && tsc -p test/fixtures/tsconfig.json --noEmit && node ./bin/kudzu.mjs build",
56
- "test": "node --test test/project-application.test.mjs && node --test --test-skip-pattern='^establishes the 0\\.15\\.2 notification ownership contract$' test/*.test.mjs",
56
+ "test": "node --test test/project-application.test.mjs && node --test --test-skip-pattern='^establishes the 0\\.17\\.0 notification WebSocket ownership contract$' test/*.test.mjs",
57
+ "test:endurance": "node test/endurance.mjs",
57
58
  "test:package": "node test/package-smoke.mjs",
58
59
  "benchmark": "node test/performance.mjs",
59
60
  "benchmark:keyed": "node test/keyed-performance.mjs",