@kudzujs/core 0.15.1 → 0.16.3
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.
- package/MIGRATION_ROADMAP.md +4 -1
- package/PERFORMANCE.md +93 -0
- package/README.md +2 -2
- package/RELEASES.md +72 -0
- package/docs/next-architecture/0.9-implementation-plan.md +2 -2
- package/docs/next-architecture/0.9-semantic-compression.md +3 -3
- package/docs/next-architecture/README.md +2 -2
- package/docs/next-architecture/application-capability-release-plan.md +111 -3
- package/docs/next-architecture/compiler-current-architecture.md +1 -1
- package/docs/next-architecture/large-application-ai-native-roadmap.md +4 -4
- package/docs/next-architecture/versioning.md +2 -2
- package/framework/compiler/analysis/binding-index.mjs +1 -1
- package/framework/compiler/effect-private-ref-pass.mjs +62 -2
- package/package.json +7 -2
package/MIGRATION_ROADMAP.md
CHANGED
|
@@ -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
|
|
54
|
+
The completed 0.9 plan defines the cross-framework comparison and release gate. The active application packet is `0.16.4` 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
|
|
|
@@ -128,6 +128,9 @@ The following are available building blocks, not future vertical roadmaps:
|
|
|
128
128
|
- `useState`, independent repeated non-keyed child state with conditional mount ownership, reduced relative-imported `useReducer`, direct dispatch specialization, and reduced Zustand-shaped shared state lowered through package-neutral shared-state/action IR and proven by migration fixtures.
|
|
129
129
|
- Mount and dependency effects with cleanup, route/layout lifetimes, conditional/keyed ownership, stale-write isolation, and relative TypeScript Workers.
|
|
130
130
|
- Direct browser-only package references in inline effect setup/cleanup callbacks bundle into route-owned effect ESM without entering build-time component execution or static sibling output.
|
|
131
|
+
- A CodeMirror-class editor may retain one package instance across one mount effect and later dependency update effects through a null-initialized ref with one direct setup assignment and null-reset cleanup. The ref lowers to existing owner-scoped object state; package DOM, bidirectional updates, errors, accessibility, conditional disposal, and fresh remount require no widget runtime or React island.
|
|
132
|
+
- A Chart.js-class canvas instance uses the same retained-handle contract. One mount effect owns construction, resize listeners, and destroy/reset cleanup; later data effects read the instance without replacing it. Enhanced route disposal and fresh remount require no chart runtime or package-specific IR.
|
|
133
|
+
- A SortableJS-class drag engine may temporarily move keyed DOM during a gesture, but Kudzu state remains the durable order owner. The package callback restores authored DOM order before one immutable state reorder; existing keyed reconciliation then preserves row identity. Keyboard controls use the same state operation, package disposal is effect-owned, and static siblings exclude the package.
|
|
131
134
|
- Keyed local-state and imported static collections, pure selectors, nested direct-property lists, recursive and directly exported row specialization, static/keyed component reuse, analyzable specialized prop spreads, serializable literal defaults, direct intrinsic rest forwarding, forwarded JSX children, latest-item handlers, row state/effects/refs, and stable keyed identity.
|
|
132
135
|
- Direct object-state props with one-segment binding/effect/list consumers when the child directly maps an array field; ComponentAnalysis links every consumer to the parent SignalIR without authored field state or a browser component.
|
|
133
136
|
- Complete-document native fallback and optional shared-layout navigation groups with parameter initialization, finite prefetch caching, focus, history, cleanup, and independent capability output.
|
package/PERFORMANCE.md
CHANGED
|
@@ -2,6 +2,99 @@
|
|
|
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.3 State-Owned Drag And Drop
|
|
6
|
+
|
|
7
|
+
Measured 2026-08-28 on Linux x64 with Node 24.14.0 and Chrome. The real
|
|
8
|
+
SortableJS fixture emits 10 JavaScript files totaling 72,251 raw / 26,603
|
|
9
|
+
aggregate gzip bytes, while its static sibling emits zero JavaScript. Required
|
|
10
|
+
Chrome proves drag and keyboard reorder, retained row/input identity, invalid
|
|
11
|
+
package-index recovery, conditional and document disposal, and fresh remount.
|
|
12
|
+
|
|
13
|
+
The maintained Worker benchmark remains byte-identical at 907 raw / 477 gzip B
|
|
14
|
+
for the Worker graph and 14,456 raw / 6,159 gzip B for the window graph. Seven
|
|
15
|
+
clean builds record a 999.7 ms median on this host. Timing is not compared with
|
|
16
|
+
the prior macOS sweep. The change adds no runtime file or drag/drop abstraction;
|
|
17
|
+
it classifies `HTMLElement` with the existing browser globals used by bundled
|
|
18
|
+
effect callbacks.
|
|
19
|
+
|
|
20
|
+
## 0.16.1 Retained Editor Ownership
|
|
21
|
+
|
|
22
|
+
Measured 2026-08-28 on Linux x64 with Node 24.14.0 and Chrome. The new
|
|
23
|
+
CodeMirror fixture emits 7 JavaScript files totaling 214,968 raw / 71,607
|
|
24
|
+
aggregate gzip bytes, while its static sibling emits zero JavaScript. These
|
|
25
|
+
bytes belong to the real editor package and existing selected effect/runtime
|
|
26
|
+
capabilities; no retained-ref runtime file or shared widget runtime was added.
|
|
27
|
+
|
|
28
|
+
Required Chrome proves initial content, retained package DOM across application
|
|
29
|
+
updates, editor-to-state updates, accessible error recovery, conditional
|
|
30
|
+
disposal/remount, and document disposal. The maintained Worker benchmark remains
|
|
31
|
+
byte-identical to 0.15.1 at 907 raw / 477 gzip B for the Worker graph and 14,456
|
|
32
|
+
raw / 6,159 gzip B for the window graph. The generated 50-route, 500-module
|
|
33
|
+
source-scale fixture retains compiler digest
|
|
34
|
+
`7c366079a984b3d62fb19a26305326d3278f029d2193d39340ddd3a4be5adc6e` and
|
|
35
|
+
output digest `e107d78a7f55bc8a1af0ea6e53efeffa19b3d44d21c892484d103fa346e7ba7b`.
|
|
36
|
+
Timing from this Linux host is recorded only as a local check and is not compared
|
|
37
|
+
with the prior macOS release sweep.
|
|
38
|
+
|
|
39
|
+
## 0.15.1 Maintained Benchmark Sweep
|
|
40
|
+
|
|
41
|
+
Remeasured 2026-08-27 on macOS arm64 with Node 24.14.0, Apple M4 hardware,
|
|
42
|
+
16 GiB RAM, and Chrome 151.0.7922.174. Candidate comparisons use clean
|
|
43
|
+
`v0.14.3` except project-state scale, whose maintained contract compares the
|
|
44
|
+
published `0.10.0` baseline. Every paired compiler/native/commerce output digest
|
|
45
|
+
or manifest is identical unless the row below records route-specific `0.15.1`
|
|
46
|
+
output.
|
|
47
|
+
|
|
48
|
+
| Maintained runner | Current `0.15.1` result |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `npm run benchmark` | Worker-effects clean-build median 220.1 ms; Worker graph 907 raw / 477 gzip B; window graph 14,456 / 6,159 B. |
|
|
51
|
+
| `npm run benchmark:keyed` | 2,000-row build 233.2 ms; append 2.2 ms, filter 3.9 ms, restore 15.6 ms, reverse 5.3 ms medians; 30,048 raw / 11,748 gzip B JavaScript. |
|
|
52
|
+
| `RUNS=7 npm run benchmark:native` | `v0.14.3` / `0.15.1` dispatch medians 3.5 / 3.4 ms for 5,000 clicks; identical 14,354 raw / 6,470 gzip B JavaScript and zero changed files. |
|
|
53
|
+
| `RUNS=7 WARMUPS=1 npm run benchmark:module-cache` | `v0.14.3` / `0.15.1` medians 248.338 / 249.692 ms and 296.9 / 296.8 MiB peak RSS; identical 454,007-byte result and digest. |
|
|
54
|
+
| `npm run benchmark:source-scale` | 50 routes, 500 modules, 50,550 lines: `v0.14.3` / `0.15.1` compile medians 499.3 / 498.9 ms and clean builds 851.5 / 853.8 ms; identical compiler and output digests. |
|
|
55
|
+
| `RUNS=7 npm run benchmark:commerce` | 1,011-page storefront: `v0.14.3` / `0.15.1` clean-build medians 1,854.6 / 1,822.1 ms; all 1,053 files and 10,140,618 output bytes match. |
|
|
56
|
+
| `npm run benchmark:project-navigation` | Seven-profile table update median 0.4 ms and navigation median 2.3 ms; 17 session JavaScript files total 77,779 raw / 27,050 gzip B. |
|
|
57
|
+
| `npm run benchmark:project-state` | One-, eight-, and 32-state commit medians 0.2, 0.3, and 0.4 ms; deploy digest `3f83712254d95f1cf59c5034f363ea906406efa1bd9539f3bb8b9a0d54a3fdc5`. |
|
|
58
|
+
| `npm run benchmark:project-list-decision` | Direct load 138.2 ms with 90,023 nodes; pagination load/range 39.6/5.8 ms with 2,867 nodes; window load/range 40.7/5.8 ms with 2,868 nodes. |
|
|
59
|
+
|
|
60
|
+
The paired timing ranges overlap at module-cache and source-scale granularity,
|
|
61
|
+
so no compiler regression or improvement is claimed. Native output is byte
|
|
62
|
+
identical. Pagination remains selected over the timing-tied authored window: it
|
|
63
|
+
uses one listener instead of two, has lower median heap (1,485,168 versus
|
|
64
|
+
1,586,528 B), and preserves native page, focus, keyboard, and variable-row-height
|
|
65
|
+
behavior without fixed-height range policy.
|
|
66
|
+
|
|
67
|
+
## 0.15.1 Public Cross-Framework Commerce Fixture
|
|
68
|
+
|
|
69
|
+
Measured 2026-08-27 from public fixture commit
|
|
70
|
+
`f2d5be1a516c539e30f7125f6870d42b1dd02ecd` with 1,000 products and Kudzu
|
|
71
|
+
linked to this `0.15.1` checkout. The matched variants use Astro 7.1.3 with
|
|
72
|
+
React 19.2.8, React Router 8.3.0, TanStack Start 1.168.32, and Next.js 16.2.11.
|
|
73
|
+
All five builds completed before the same asset and browser harnesses ran.
|
|
74
|
+
|
|
75
|
+
| Variant | Initial JavaScript across six routes, gzip | Total output | First reliable click after first paint | Degraded capabilities |
|
|
76
|
+
|---|---:|---:|---:|---:|
|
|
77
|
+
| Kudzu 0.15.1 | 4.2-9.9 KiB | 9.42 MiB | 300 ms | 15 / 18 |
|
|
78
|
+
| Astro 7.1.3 + React | 60.6-61.1 KiB | 13.95 MiB | 1,500 ms | 12 / 18 |
|
|
79
|
+
| React Router 8.3.0 | 103.8-104.6 KiB | 7.04 MiB | 2,000 ms | 8 / 18 |
|
|
80
|
+
| TanStack Start 1.168.32 | 103.5-104.1 KiB | 9.87 MiB | 2,000 ms | 9 / 18 |
|
|
81
|
+
| Next.js 16.2.11 | 144.2-146.2 KiB | 35.76 MiB | 3,000 ms | 8 / 18 |
|
|
82
|
+
|
|
83
|
+
The initial-JavaScript column records what Chrome actually received for home,
|
|
84
|
+
search, collection, product, policy, and checkout routes. Browser results are
|
|
85
|
+
medians from seven fresh sessions under 4x CPU slowdown and Chrome Slow 4G.
|
|
86
|
+
Reliable click is the first tested delay with zero lost add-to-cart attempts in
|
|
87
|
+
seven isolated sessions. The resilience score covers six matched capabilities
|
|
88
|
+
under blocked JavaScript, a two-second script delay, and one missing script.
|
|
89
|
+
Pre-rendered content and native links remain usable without JavaScript in every
|
|
90
|
+
variant; the score does not treat read-only fallback as full interactivity.
|
|
91
|
+
|
|
92
|
+
This is the current executable public commerce comparison, not a replacement
|
|
93
|
+
for the broader historical 0.9 C1/C2/C5 contracts. That ignored local workspace
|
|
94
|
+
is unavailable in current checkouts, so its React, Vue, Svelte, and Astro result
|
|
95
|
+
remains provenance rather than a current ranking. The public commerce fixture
|
|
96
|
+
does not contain Vue or Svelte variants.
|
|
97
|
+
|
|
5
98
|
## 0.15.1 Key-Scoped Native Popover Intake
|
|
6
99
|
|
|
7
100
|
Measured 2026-08-27 on macOS arm64 with Node 24.14.0 and Chrome
|
package/README.md
CHANGED
|
@@ -12,9 +12,9 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
|
|
|
12
12
|
|
|
13
13
|
*Watch: React-shaped TSX to static HTML and route-specific ESM in 22 seconds.*
|
|
14
14
|
|
|
15
|
-
> Experimental `0.
|
|
15
|
+
> Experimental `0.16.x`: the compiler API and supported TSX surface may change.
|
|
16
16
|
|
|
17
|
-
**Latest release: 0.
|
|
17
|
+
**Latest release: 0.16.3 - State-owned drag and drop.** A real SortableJS effect may move DOM during a gesture while keyed Kudzu state remains the durable order and identity owner, with keyboard parity, exact disposal, and static exclusion. Read the [release notes](./RELEASES.md#0163---state-owned-drag-and-drop), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.3), 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,77 @@
|
|
|
1
1
|
# Kudzu Releases
|
|
2
2
|
|
|
3
|
+
## 0.16.3 - State-Owned Drag And Drop
|
|
4
|
+
|
|
5
|
+
Kudzu 0.16.3 proves that a real SortableJS drag engine can remain a bounded
|
|
6
|
+
effect while existing keyed state owns durable order and DOM identity.
|
|
7
|
+
|
|
8
|
+
### Changed in 0.16.3
|
|
9
|
+
|
|
10
|
+
- Treats the browser `HTMLElement` constructor as a platform global inside
|
|
11
|
+
bundled effect callbacks while preserving lexical shadowing.
|
|
12
|
+
- Restores package-moved DOM before applying one immutable state reorder, so
|
|
13
|
+
Kudzu remains the only durable keyed DOM owner.
|
|
14
|
+
- Uses the same state operation for drag, keyboard movement, and reset.
|
|
15
|
+
- Recovers invalid package indexes without changing application state.
|
|
16
|
+
- Disposes on conditional and document release; remount creates a fresh package
|
|
17
|
+
instance.
|
|
18
|
+
- Adds no drag/drop IR, animation frame, runtime concept, React, VDOM, hydration,
|
|
19
|
+
or retained component tree.
|
|
20
|
+
- Updates `create-kudzu@0.1.128` to generate projects on
|
|
21
|
+
`@kudzujs/core@^0.16.3`.
|
|
22
|
+
|
|
23
|
+
### Output And Browser Evidence
|
|
24
|
+
|
|
25
|
+
- The SortableJS fixture emits 10 JavaScript files totaling 72,251 raw / 26,603
|
|
26
|
+
aggregate gzip bytes; its static sibling emits zero JavaScript.
|
|
27
|
+
- Required Chrome proves drag and keyboard reorder, state synchronization,
|
|
28
|
+
retained row/input identity, invalid-input recovery, disposal, and fresh
|
|
29
|
+
remount.
|
|
30
|
+
- The maintained Worker and window graphs remain 907 raw / 477 gzip bytes and
|
|
31
|
+
14,456 raw / 6,159 gzip bytes respectively.
|
|
32
|
+
|
|
33
|
+
### Upgrade
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm install @kudzujs/core@^0.16.3
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 0.16.1 - Retained External Editor Ownership
|
|
40
|
+
|
|
41
|
+
Kudzu 0.16.1 lets one CodeMirror-class package instance remain owned across a
|
|
42
|
+
mount effect and later dependency update effects without a React island or
|
|
43
|
+
generic widget runtime.
|
|
44
|
+
|
|
45
|
+
### Changed in 0.16.1
|
|
46
|
+
|
|
47
|
+
- Lowers one proven unattached `useRef(null)` retained-instance handle to
|
|
48
|
+
existing owner-scoped object state.
|
|
49
|
+
- Requires one empty-dependency mount effect, one direct setup assignment,
|
|
50
|
+
direct null-reset cleanup, and read-only later dependency effects.
|
|
51
|
+
- Preserves editor DOM identity across application-state and editor-to-state
|
|
52
|
+
updates, including accessible package errors and recovery.
|
|
53
|
+
- Disposes on conditional and document release; remount receives a fresh handle.
|
|
54
|
+
- Rejects malformed cleanup, update-effect writes, multiple retained handles,
|
|
55
|
+
aliases, event use, and attached retained refs.
|
|
56
|
+
- Adds no IR kind, runtime concept, resource registry, React, or widget runtime.
|
|
57
|
+
- Updates `create-kudzu@0.1.127` to generate projects on
|
|
58
|
+
`@kudzujs/core@^0.16.1`.
|
|
59
|
+
|
|
60
|
+
### Output And Browser Evidence
|
|
61
|
+
|
|
62
|
+
- The CodeMirror fixture emits 7 JavaScript files totaling 214,968 raw /
|
|
63
|
+
71,607 aggregate gzip bytes; its static sibling emits zero JavaScript.
|
|
64
|
+
- Required Chrome proves initial mount, retained application updates,
|
|
65
|
+
editor-to-state updates, package-error recovery, disposal, and fresh remount.
|
|
66
|
+
- The smaller Typed.js boundary fixture proves the same existing effect owner
|
|
67
|
+
without any production compiler change.
|
|
68
|
+
|
|
69
|
+
### Upgrade
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
npm install @kudzujs/core@^0.16.1
|
|
73
|
+
```
|
|
74
|
+
|
|
3
75
|
## 0.15.1 - Key-Scoped Native Popovers
|
|
4
76
|
|
|
5
77
|
Kudzu 0.15.1 lets ordinary keyed row components use `useId()` for native
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Document Role
|
|
4
4
|
|
|
5
|
-
This document is the detailed delivery
|
|
5
|
+
This document is the completed detailed delivery record for [`0.9-semantic-compression.md`](./0.9-semantic-compression.md). It retains the executable sessions, artifacts, tests, measurements, reviews, and stop conditions that produced `0.9.0`; current execution follows the application capability release plan.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`0.9.0` is released and this plan is historical. The current package version remains the version recorded in `package.json`; package metadata, release notes, public support claims, and generator ranges still change only through the active release procedure after every gate passes.
|
|
8
8
|
|
|
9
9
|
Use this document to answer:
|
|
10
10
|
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
This is the completed execution record after `0.8.62`. Every release gate in this document is satisfied by `0.9.0`; follow-on model-driven delivery work remains separately gated for 1.0.
|
|
6
6
|
|
|
7
|
-
The
|
|
7
|
+
The historical session-by-session decomposition, implementation seams, benchmark restoration work, report templates, and release procedure are retained in [`0.9-implementation-plan.md`](./0.9-implementation-plan.md). This document remains authoritative for the completed 0.9 direction, order, and release gates; current execution follows the application capability release plan.
|
|
8
8
|
|
|
9
|
-
Read documents in this order
|
|
9
|
+
Read documents in this order when reviewing the completed 0.9 work:
|
|
10
10
|
|
|
11
11
|
1. [`MIGRATION_ROADMAP.md`](../../MIGRATION_ROADMAP.md) for product invariants and feature selection.
|
|
12
|
-
2. This document for the
|
|
12
|
+
2. This document for the completed 0.9 scope and work order.
|
|
13
13
|
3. [`0.9-implementation-plan.md`](./0.9-implementation-plan.md) for executable session packets and evidence.
|
|
14
14
|
4. [`compiler-current-architecture.md`](./compiler-current-architecture.md) for current owners and data flow.
|
|
15
15
|
5. [`performance-gates.md`](./performance-gates.md) for required measurements.
|
|
@@ -7,7 +7,7 @@ The top-level [`GOAL_A.md`](../../GOAL_A.md) and [`GOAL_B.md`](../../GOAL_B.md)
|
|
|
7
7
|
| Goal | Decision | Start condition |
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| A: compiler foundation | Complete in `0.8.23` | The no-write source compiler, RouteIR v1, CapabilityIR v1, generator boundaries, and output baseline are recorded. |
|
|
10
|
-
| B: optimization benchmarks |
|
|
10
|
+
| B: optimization benchmarks | Maintained gate | Keyed restoration, no-op normalization, exact route-entry transform reuse, and identical route-entry file sharing are retained; continue only when another current fixture reproduces a material loss. |
|
|
11
11
|
| C: state/resource model | Research only | Reduced fixtures expose a limitation |
|
|
12
12
|
| D: routing compatibility | Current behavior preserved | Revisit only with migration evidence and invariant review |
|
|
13
13
|
|
|
@@ -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. 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.16.4`. 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
|
|
|
@@ -959,7 +959,7 @@ projects on `@kudzujs/core@^0.14.1`.
|
|
|
959
959
|
- **Done condition:** the full journey passes with explicit network and memory
|
|
960
960
|
bounds.
|
|
961
961
|
|
|
962
|
-
|
|
962
|
+
Published release evidence complete. The maintained project application adds one
|
|
963
963
|
intrinsic sentinel, an owned native `IntersectionObserver`, cursor and request
|
|
964
964
|
primitive state, an owned fetch with `AbortController`, immutable keyed append,
|
|
965
965
|
duplicate ID suppression, and explicit loading/error/retry/end UI. The authored
|
|
@@ -1047,7 +1047,8 @@ to the next evidence-ready application capability.
|
|
|
1047
1047
|
- **Stop condition:** a custom modal/focus-trap runtime replaces `<dialog>`.
|
|
1048
1048
|
- **Done condition:** project delete and edit confirmation pass accessibly.
|
|
1049
1049
|
|
|
1050
|
-
|
|
1050
|
+
Validation evidence is complete and the packet closed without consuming a release
|
|
1051
|
+
because no production compiler or runtime changed. The maintained
|
|
1051
1052
|
project application replaces immediate keyed project deletion with one shared
|
|
1052
1053
|
native `<dialog>`, one route-owned object ref, and one pending project ID state.
|
|
1053
1054
|
Existing row callback specialization opens the browser top layer; native cancel,
|
|
@@ -1114,6 +1115,16 @@ plus navigation samples of `[4, 2.4, 2.6, 2.5, 2.5, 2.8, 2.4]` ms, a 2.5 ms
|
|
|
1114
1115
|
median and 2.4/4 ms range. These retained-path ranges overlap prior evidence,
|
|
1115
1116
|
so no timing improvement or regression is claimed.
|
|
1116
1117
|
|
|
1118
|
+
Release transaction complete at commit
|
|
1119
|
+
`2dd5c409d3553cdf0b04080f1ba7c39b4541c523` and immutable tag `v0.15.1`.
|
|
1120
|
+
CI run `33069675599` passed both jobs. The GitHub release is
|
|
1121
|
+
[`v0.15.1`](https://github.com/kudzujs/kudzu/releases/tag/v0.15.1), and publish
|
|
1122
|
+
workflow run `33071439012` published and verified `@kudzujs/core@0.15.1` and
|
|
1123
|
+
`create-kudzu@0.1.126`. A fresh registry install imported `useId()` from core
|
|
1124
|
+
and confirmed both exact package versions. The first publish attempt stopped at
|
|
1125
|
+
the existing concurrent Chrome timing gate before any publish step; the exact
|
|
1126
|
+
same immutable tag passed on the release-event retry.
|
|
1127
|
+
|
|
1117
1128
|
### `0.15.2`: Toast And Notification Ownership
|
|
1118
1129
|
|
|
1119
1130
|
- **Purpose:** provide application feedback across route mutations.
|
|
@@ -1125,6 +1136,15 @@ so no timing improvement or regression is claimed.
|
|
|
1125
1136
|
- **Done condition:** notifications remain owner-bounded and absent from unused
|
|
1126
1137
|
route output.
|
|
1127
1138
|
|
|
1139
|
+
**Result:** closed by ordinary layout composition. The maintained project
|
|
1140
|
+
application now owns one deduplicated notification queue in layout state, renders
|
|
1141
|
+
keyed rows in a native polite live region, and uses one cleanup-owned sequential
|
|
1142
|
+
timeout plus explicit dismissal. Required Chrome preserves the live-region and
|
|
1143
|
+
row identities through enhanced route replacement, proves timeout removal and
|
|
1144
|
+
disposal, and keeps `/help` at zero JavaScript. The 45-file deploy totals 193,770
|
|
1145
|
+
raw / 60,227 aggregate gzip bytes. No scheduler, primitive, runtime concept, or
|
|
1146
|
+
production source changed, so no release was consumed.
|
|
1147
|
+
|
|
1128
1148
|
### `0.15.3`: Layer Owner Decision
|
|
1129
1149
|
|
|
1130
1150
|
- **Purpose:** decide whether logical ownership of DOM rendered outside its
|
|
@@ -1135,6 +1155,13 @@ so no timing improvement or regression is claimed.
|
|
|
1135
1155
|
- **Done condition:** no new concept is added, or a minimal owner edge with exact
|
|
1136
1156
|
cleanup/focus order is approved.
|
|
1137
1157
|
|
|
1158
|
+
**Result:** closed with no owner edge. Native dialog and Popover already satisfy
|
|
1159
|
+
modal and non-modal top-layer behavior, while maintained dropdown, tooltip,
|
|
1160
|
+
notification, and menu fixtures compose in their authored ranges. Zero fixtures
|
|
1161
|
+
fail because DOM must render outside its authored range, below the required three
|
|
1162
|
+
independent failures. Portal source compatibility alone remains insufficient;
|
|
1163
|
+
no production source or release changed.
|
|
1164
|
+
|
|
1138
1165
|
## `0.16.x`: External Complex UI
|
|
1139
1166
|
|
|
1140
1167
|
### `0.16.0`: Bounded External UI Contract
|
|
@@ -1148,6 +1175,12 @@ so no timing improvement or regression is claimed.
|
|
|
1148
1175
|
- **Done condition:** package ownership remains inside effect/handler ESM and no
|
|
1149
1176
|
generic widget runtime is added.
|
|
1150
1177
|
|
|
1178
|
+
**Result:** closed through existing effect ownership. A real Typed.js instance
|
|
1179
|
+
creates DOM from one attached host ref, replaces on application-state dependency
|
|
1180
|
+
change, disposes on conditional/document release, and remounts fresh. The package
|
|
1181
|
+
is absent from the complete zero-JavaScript static sibling. No compiler, IR,
|
|
1182
|
+
runtime concept, or production source changed, so no release was consumed.
|
|
1183
|
+
|
|
1151
1184
|
### `0.16.1`: Real Editor Lifecycle
|
|
1152
1185
|
|
|
1153
1186
|
- **Purpose:** integrate one real CodeMirror-class editor.
|
|
@@ -1157,6 +1190,18 @@ so no timing improvement or regression is claimed.
|
|
|
1157
1190
|
- **Done condition:** the native package owns its DOM and Kudzu owns only its
|
|
1158
1191
|
bounded lifecycle.
|
|
1159
1192
|
|
|
1193
|
+
**Result:** complete. The Apache Answer-derived CodeMirror fixture exposes the
|
|
1194
|
+
first real retained-instance failure: an unattached `useRef(null)` shared by one
|
|
1195
|
+
mount/cleanup effect and a later dependency update effect was incorrectly treated
|
|
1196
|
+
as a DOM ref. Kudzu now lowers exactly one proven retained handle to existing
|
|
1197
|
+
owner-scoped object state. Required Chrome proves initial value, retained
|
|
1198
|
+
application updates, editor-to-state updates, package-error recovery, accessible
|
|
1199
|
+
textbox naming, conditional disposal, fresh remount, and document disposal. The
|
|
1200
|
+
fixture emits 214,968 raw / 71,607 aggregate gzip JavaScript bytes while its
|
|
1201
|
+
static sibling emits zero. One existing normalization pass grows; semantic
|
|
1202
|
+
concepts, IR kinds, runtime concepts, runtime files, and public APIs remain
|
|
1203
|
+
unchanged.
|
|
1204
|
+
|
|
1160
1205
|
### `0.16.2`: Chart And Map Lifecycle
|
|
1161
1206
|
|
|
1162
1207
|
- **Purpose:** validate the same contract against unrelated canvas/SVG/map DOM.
|
|
@@ -1165,6 +1210,24 @@ so no timing improvement or regression is claimed.
|
|
|
1165
1210
|
- **Stop condition:** package-specific concepts enter generic IR/runtime.
|
|
1166
1211
|
- **Done condition:** one lifecycle model covers editor and chart/map evidence.
|
|
1167
1212
|
|
|
1213
|
+
**Result:** closed through the `0.16.1` retained-instance contract. A reduced
|
|
1214
|
+
Mattermost Chart.js doughnut chart first reproduced the existing diagnostic when
|
|
1215
|
+
its dependency effect both created and updated the retained instance. Moving
|
|
1216
|
+
acquisition, resize-listener registration, and exact destroy/reset cleanup into
|
|
1217
|
+
one mount effect leaves later data effects read-only and lowers the handle to the
|
|
1218
|
+
same owner-scoped object state used by CodeMirror. Required Chrome proves initial
|
|
1219
|
+
drawing, retained data and resize updates, listener removal, enhanced route
|
|
1220
|
+
disposal, fresh remount, accessibility, and package exclusion from the static
|
|
1221
|
+
sibling. The interactive graph is 208,686 raw / 73,312 aggregate gzip bytes
|
|
1222
|
+
across six JavaScript files; the static sibling emits zero. Semantic primitives,
|
|
1223
|
+
IR kinds, compiler passes, core/compiler LOC, normalization rules, runtime
|
|
1224
|
+
concepts, runtime files, and public APIs change by zero. One positive fixture and
|
|
1225
|
+
one browser contract were added. The maintained Worker/effect benchmark remains
|
|
1226
|
+
907 raw / 477 gzip bytes for the Worker graph and 14,456 raw / 6,159 gzip bytes
|
|
1227
|
+
for the window graph; its seven-run Linux median was 2,464.9 ms, recorded without
|
|
1228
|
+
a timing comparison because the host differs from the published baseline. No
|
|
1229
|
+
production source changed, so no release was consumed.
|
|
1230
|
+
|
|
1168
1231
|
### `0.16.3`: Grid And Drag/Drop Decision
|
|
1169
1232
|
|
|
1170
1233
|
- **Purpose:** test external data-grid or drag engine ownership against keyed
|
|
@@ -1175,6 +1238,45 @@ so no timing improvement or regression is claimed.
|
|
|
1175
1238
|
- **Done condition:** either bounded ownership is proven or the package is
|
|
1176
1239
|
explicitly unsupported with a migration path.
|
|
1177
1240
|
|
|
1241
|
+
**Result:** complete through existing keyed state and effect ownership. A
|
|
1242
|
+
THRM-derived SortableJS fixture reproduced one compiler failure because the
|
|
1243
|
+
browser `HTMLElement` constructor was classified as an effect capture and then
|
|
1244
|
+
evaluated during the Node build. Adding that platform constructor to the
|
|
1245
|
+
existing browser-global set fixes the shared root cause while lexical shadowing
|
|
1246
|
+
remains covered. During a gesture SortableJS may move DOM temporarily, but its
|
|
1247
|
+
`onEnd` callback restores authored order before one immutable state reorder;
|
|
1248
|
+
Kudzu's keyed reconciler remains the durable DOM owner and preserves retained
|
|
1249
|
+
row and input identity. The same state operation provides keyboard movement and
|
|
1250
|
+
reset, while invalid package indexes restore DOM without changing state.
|
|
1251
|
+
Required Chrome proves drag and keyboard reorder, state synchronization,
|
|
1252
|
+
identity, invalid-input recovery, conditional/document disposal, and fresh
|
|
1253
|
+
remount. The interactive graph is 72,251 raw / 26,603 aggregate gzip bytes
|
|
1254
|
+
across ten JavaScript files; the static sibling emits zero. Its normalized
|
|
1255
|
+
deploy archive SHA-256 is
|
|
1256
|
+
`a55c18e14ba29df3f1f8ccdc7bcae936092508a01d05abdb887417e6fffb2ecf`.
|
|
1257
|
+
Semantic primitives, IR kinds, compiler passes, normalization rules, runtime
|
|
1258
|
+
concepts, runtime files, drag/drop abstractions, and public APIs change by zero;
|
|
1259
|
+
one existing compiler lookup gains one token. One positive fixture, one browser
|
|
1260
|
+
contract, and one browser-global shadowing case were added. The maintained
|
|
1261
|
+
Worker/effect benchmark remains 907 raw / 477 gzip bytes for the Worker graph
|
|
1262
|
+
and 14,456 raw / 6,159 gzip bytes for the window graph; its seven-run Linux
|
|
1263
|
+
median was 999.7 ms, recorded without a timing comparison because the host
|
|
1264
|
+
differs from the published baseline.
|
|
1265
|
+
|
|
1266
|
+
### `0.16.4`: GSAP Animation Lifecycle
|
|
1267
|
+
|
|
1268
|
+
- **Purpose:** validate one real GSAP landing-page animation against existing
|
|
1269
|
+
DOM-ref and effect ownership.
|
|
1270
|
+
- **Expected boundary:** one owned effect and native reduced-motion behavior;
|
|
1271
|
+
no animation IR or scheduler runtime.
|
|
1272
|
+
- **Acceptance:** mount, deterministic static fallback, scoped targets,
|
|
1273
|
+
prefers-reduced-motion behavior, dependency update where required, route and
|
|
1274
|
+
conditional disposal, fresh remount, and static sibling asset exclusion.
|
|
1275
|
+
- **Stop condition:** package timelines compete with Kudzu for durable DOM
|
|
1276
|
+
structure or require a retained component renderer.
|
|
1277
|
+
- **Done condition:** GSAP owns only bounded presentation changes and exact
|
|
1278
|
+
cleanup while Kudzu retains document structure and lifetime ownership.
|
|
1279
|
+
|
|
1178
1280
|
## `0.17.x`: Long-Lived Applications
|
|
1179
1281
|
|
|
1180
1282
|
### `0.17.0`: Endurance Harness
|
|
@@ -1545,4 +1647,10 @@ release transaction where possible or document and publish a forward-fix patch.
|
|
|
1545
1647
|
| `0.14.4` | Closed by stop condition | Preserve pagination as the selected strategy until three independent fixtures authorize virtual ownership review. | Evidence favors pagination; zero qualifying fixtures |
|
|
1546
1648
|
| `0.15.0` | Closed by native composition | Preserve the native dialog, focus, cancellation, keyed deletion, and route cleanup evidence. | No production change; no release consumed |
|
|
1547
1649
|
| `0.15.1` | Released | Preserve key-scoped IDs, native Popover behavior, retained keyed identity, and route cleanup evidence. | None |
|
|
1548
|
-
| `0.15.2` |
|
|
1650
|
+
| `0.15.2` | Closed by layout composition | Preserve owner-bounded queue, deduplication, dismissal, route persistence, live-region, and timer cleanup evidence. | No production change; no release consumed |
|
|
1651
|
+
| `0.15.3` | Closed by stop condition | Preserve native top-layer and in-place composition until three independent authored-range failures exist. | Zero qualifying failures; no release consumed |
|
|
1652
|
+
| `0.16.0` | Closed by existing effect ownership | Preserve real package mount, replacement, disposal, remount, and static exclusion evidence. | No production change; no release consumed |
|
|
1653
|
+
| `0.16.1` | Released | Preserve the retained CodeMirror instance boundary, bidirectional updates, error recovery, accessibility, and exact cleanup. | None |
|
|
1654
|
+
| `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 |
|
|
1655
|
+
| `0.16.3` | Released | Preserve state-owned order, keyed identity, keyboard parity, invalid-input recovery, package disposal, and static exclusion. | None |
|
|
1656
|
+
| `0.16.4` | Active | Validate one real GSAP landing-page animation through bounded effect ownership. | None |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Current Compiler Architecture
|
|
2
2
|
|
|
3
|
-
This maps the current `0.
|
|
3
|
+
This maps the current `0.16.3` 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.16.4`; 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
|
|
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.16.4`. 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
|
|
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.16.4`; 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
|
|
|
@@ -453,7 +453,7 @@ The first comparison is Kudzu versus React + Vite using the same agent, model, t
|
|
|
453
453
|
|
|
454
454
|
## Production Gates Before 1.0
|
|
455
455
|
|
|
456
|
-
- The
|
|
456
|
+
- The completed `0.9.0` cross-framework gate proves lower browser cost and matched user-facing performance against React + Vite, Vue, Svelte, and Astro before AI productivity claims are considered.
|
|
457
457
|
- The maintained AI delivery suite uses the same model, tools, requirements, budgets, and acceptance checks across frameworks, includes failed attempts, and establishes the highest success rate plus lowest median cost per successful task before `1.0.0`.
|
|
458
458
|
- Async native and effect work cannot write after ownership release.
|
|
459
459
|
- Build output is staged, collision-safe, and rollback/recovery guarded.
|
|
@@ -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
|
-
|
|
486
|
+
All listed foundation and `0.9` slices are complete. Current work continues at `0.16.4` GSAP Animation Lifecycle 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
|
|
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.16.4`; 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.
|
|
69
|
+
`create-kudzu@0.1.128` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.3`.
|
|
70
70
|
|
|
71
71
|
## Release Boundary
|
|
72
72
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ts from "typescript"
|
|
2
2
|
|
|
3
3
|
export const knownGlobalNames = new Set([
|
|
4
|
-
"Array", "ArrayBuffer", "BigInt", "Blob", "Boolean", "Date", "Error", "Event", "FileReader", "FormData", "Infinity", "IntersectionObserver", "Intl", "JSON", "Map", "Math", "NaN", "Number", "Object", "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp", "Set", "String", "Symbol", "TypeError", "URL", "URLSearchParams", "WeakMap", "WeakSet", "WebSocket", "Worker", "alert", "atob", "btoa", "cancelAnimationFrame", "clearInterval", "clearTimeout", "console", "crypto", "document", "fetch", "globalThis", "history", "isFinite", "isNaN", "localStorage", "location", "navigator", "parseFloat", "parseInt", "performance", "queueMicrotask", "requestAnimationFrame", "setInterval", "setTimeout", "structuredClone", "undefined", "window"
|
|
4
|
+
"Array", "ArrayBuffer", "BigInt", "Blob", "Boolean", "Date", "Error", "Event", "FileReader", "FormData", "HTMLElement", "Infinity", "IntersectionObserver", "Intl", "JSON", "Map", "Math", "NaN", "Number", "Object", "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp", "Set", "String", "Symbol", "TypeError", "URL", "URLSearchParams", "WeakMap", "WeakSet", "WebSocket", "Worker", "alert", "atob", "btoa", "cancelAnimationFrame", "clearInterval", "clearTimeout", "console", "crypto", "document", "fetch", "globalThis", "history", "isFinite", "isNaN", "localStorage", "location", "navigator", "parseFloat", "parseInt", "performance", "queueMicrotask", "requestAnimationFrame", "setInterval", "setTimeout", "structuredClone", "undefined", "window"
|
|
5
5
|
])
|
|
6
6
|
|
|
7
7
|
export function createBindingIndex(sourceFile) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript"
|
|
2
|
-
import { effectReturns, importDeclarationNames, isNodeWithin, isShadowedIdentifier, nearestFunction, referenceIdentifiers, sourceNodeError, statementDeclaresName, unwrapExpression } from "./ast-helpers.mjs"
|
|
2
|
+
import { effectReturns, importDeclarationNames, isNodeWithin, isShadowedIdentifier, nearestFunction, referenceIdentifiers, referencesIdentifier, sourceNodeError, statementDeclaresName, unwrapExpression } from "./ast-helpers.mjs"
|
|
3
3
|
|
|
4
4
|
export function normalizeEffectPrivateRefs(sourceFile, factory, context) {
|
|
5
5
|
const frameCall = (node, name) => ts.isCallExpression(node) && (
|
|
@@ -24,8 +24,10 @@ export function normalizeEffectPrivateRefs(sourceFile, factory, context) {
|
|
|
24
24
|
}
|
|
25
25
|
const hasUseRefImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useRef"))
|
|
26
26
|
const hasUseEffectImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useEffect"))
|
|
27
|
+
const hasUseStateImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useState"))
|
|
27
28
|
const privateRefs = new Map()
|
|
28
29
|
const effectRefs = new Map()
|
|
30
|
+
const retainedRefs = new Map()
|
|
29
31
|
const registerPrivateRef = (node, callback) => {
|
|
30
32
|
const initializer = ts.isNumericLiteral(node.initializer.arguments[0]) ? factory.createNumericLiteral(0) : factory.createNull()
|
|
31
33
|
privateRefs.set(node, callback)
|
|
@@ -42,20 +44,60 @@ export function normalizeEffectPrivateRefs(sourceFile, factory, context) {
|
|
|
42
44
|
const owner = nearestFunction(node)
|
|
43
45
|
if (!owner?.body || !ts.isBlock(owner.body)) return
|
|
44
46
|
const references = referenceIdentifiers(owner.body, node.name.text)
|
|
47
|
+
const attachedToJsx = references.some(reference => ts.isJsxExpression(reference.parent) && ts.isJsxAttribute(reference.parent.parent) && reference.parent.parent.name.text === "ref")
|
|
45
48
|
const invalidReference = references.find(reference => !ts.isPropertyAccessExpression(reference.parent) || reference.parent.expression !== reference || reference.parent.name.text !== "current")
|
|
46
49
|
const accesses = references.filter(reference => ts.isPropertyAccessExpression(reference.parent) && reference.parent.expression === reference && reference.parent.name.text === "current").map(reference => reference.parent)
|
|
50
|
+
const mutations = accesses.filter(access =>
|
|
51
|
+
(ts.isBinaryExpression(access.parent) && unwrapExpression(access.parent.left) === access && access.parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && access.parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment) ||
|
|
52
|
+
((ts.isPrefixUnaryExpression(access.parent) || ts.isPostfixUnaryExpression(access.parent)) && access.parent.operand === access && [ts.SyntaxKind.PlusPlusToken, ts.SyntaxKind.MinusMinusToken].includes(access.parent.operator)) ||
|
|
53
|
+
(ts.isDeleteExpression(access.parent) && access.parent.expression === access)
|
|
54
|
+
)
|
|
47
55
|
const frameAssignments = accesses.filter(access => ts.isBinaryExpression(access.parent) && unwrapExpression(access.parent.left) === access && access.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && frameCall(unwrapExpression(access.parent.right), "requestAnimationFrame") && unshadowedFrameCall(unwrapExpression(access.parent.right), owner))
|
|
48
56
|
const statement = node.parent?.parent
|
|
49
57
|
const topLevelOwner = owner.parent === sourceFile || ts.isVariableDeclaration(owner.parent) && owner.parent.parent?.parent?.parent === sourceFile
|
|
50
58
|
const topLevelConst = topLevelOwner && ts.isVariableStatement(statement) && (statement.declarationList.flags & ts.NodeFlags.Const) && statement.declarationList.declarations.length === 1 && statement.parent === owner.body
|
|
51
59
|
if (frameAssignments.length && invalidReference) throw sourceNodeError(invalidReference, sourceFile, "Animation frame refs may only use direct .current reads and assignments")
|
|
52
60
|
if (frameAssignments.length && !topLevelConst) throw sourceNodeError(node, sourceFile, "Animation frame refs must be one top-level component const")
|
|
61
|
+
if (attachedToJsx && mutations.length) throw sourceNodeError(mutations[0], sourceFile, "JSX object refs may not assign to ref.current")
|
|
53
62
|
if (!topLevelConst || invalidReference || !accesses.length) return
|
|
54
63
|
const effectCalls = owner.body.statements.flatMap(statement => hasUseEffectImport && ts.isExpressionStatement(statement) && ts.isCallExpression(statement.expression) && ts.isIdentifier(statement.expression.expression) && statement.expression.expression.text === "useEffect" && !isShadowedIdentifier(statement.expression.expression, sourceFile) ? [statement.expression] : [])
|
|
55
64
|
const effects = effectCalls.filter(effect => {
|
|
56
65
|
const callback = effect.arguments[0]
|
|
57
66
|
return callback && accesses.every(access => isNodeWithin(access, callback))
|
|
58
67
|
})
|
|
68
|
+
if (!frameAssignments.length && refInitializer?.kind === ts.SyntaxKind.NullKeyword && !invalidReference && !effects.length) {
|
|
69
|
+
const owners = effectCalls.filter(effect => {
|
|
70
|
+
const callback = effect.arguments[0]
|
|
71
|
+
return callback && accesses.some(access => isNodeWithin(access, callback))
|
|
72
|
+
})
|
|
73
|
+
const callbacks = owners.map(effect => effect.arguments[0])
|
|
74
|
+
const directCallbacks = callbacks.every(callback => (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) && ts.isBlock(callback.body))
|
|
75
|
+
const everyAccessOwned = accesses.every(access => callbacks.filter(callback => isNodeWithin(access, callback)).length === 1)
|
|
76
|
+
const mountEffects = owners.filter(effect => ts.isArrayLiteralExpression(effect.arguments[1]) && !effect.arguments[1].elements.length)
|
|
77
|
+
const updateEffects = owners.filter(effect => ts.isArrayLiteralExpression(effect.arguments[1]) && effect.arguments[1].elements.length)
|
|
78
|
+
if (owners.length >= 2 && directCallbacks && everyAccessOwned && mountEffects.length === 1 && updateEffects.length === owners.length - 1 && owners[0] === mountEffects[0]) {
|
|
79
|
+
const mount = mountEffects[0]
|
|
80
|
+
const callback = mount.arguments[0]
|
|
81
|
+
const returns = effectReturns(callback)
|
|
82
|
+
const cleanup = returns.cleanups.length === 1 ? returns.cleanups[0] : undefined
|
|
83
|
+
const writes = accesses.filter(access => ts.isBinaryExpression(access.parent) && unwrapExpression(access.parent.left) === access && access.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken)
|
|
84
|
+
const setupWrites = writes.filter(access => isNodeWithin(access, callback) && (!cleanup || !isNodeWithin(access, cleanup)))
|
|
85
|
+
const cleanupWrites = cleanup ? writes.filter(access => isNodeWithin(access, cleanup)) : []
|
|
86
|
+
const setupStatement = setupWrites[0]?.parent.parent
|
|
87
|
+
const cleanupStatement = cleanupWrites[0]?.parent.parent
|
|
88
|
+
const directSetup = setupWrites.length === 1 && ts.isExpressionStatement(setupStatement) && setupStatement.parent === callback.body && unwrapExpression(setupWrites[0].parent.right).kind !== ts.SyntaxKind.NullKeyword
|
|
89
|
+
const directCleanup = cleanup && ts.isBlock(cleanup.body) && cleanupWrites.length === 1 && ts.isExpressionStatement(cleanupStatement) && cleanupStatement.parent === cleanup.body && unwrapExpression(cleanupWrites[0].parent.right).kind === ts.SyntaxKind.NullKeyword
|
|
90
|
+
const updateWrites = mutations.some(access => updateEffects.some(effect => isNodeWithin(access, effect.arguments[0])))
|
|
91
|
+
if (directSetup && directCleanup && !updateWrites) {
|
|
92
|
+
if (!hasUseStateImport) throw sourceNodeError(node, sourceFile, "Retained instance refs require a named useState import")
|
|
93
|
+
const setterName = `__kSetRetainedRef_${node.name.text}_${node.pos}`
|
|
94
|
+
if (referencesIdentifier(owner.body, setterName)) throw sourceNodeError(node, sourceFile, "Retained instance ref conflicts with a compiler-owned binding")
|
|
95
|
+
retainedRefs.set(node, setterName)
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (owners.length >= 2) throw sourceNodeError(node, sourceFile, "Retained instance refs require one empty-dependency mount effect with one direct assignment and null-reset cleanup followed by read-only dependency effects")
|
|
100
|
+
}
|
|
59
101
|
if (!frameAssignments.length) {
|
|
60
102
|
const callback = effects.length === 1 ? effects[0].arguments[0] : undefined
|
|
61
103
|
if (callback && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) && ts.isBlock(callback.body)) {
|
|
@@ -107,9 +149,27 @@ export function normalizeEffectPrivateRefs(sourceFile, factory, context) {
|
|
|
107
149
|
registerPrivateRef(node, callback)
|
|
108
150
|
}
|
|
109
151
|
inspect(sourceFile)
|
|
110
|
-
|
|
152
|
+
const retainedByOwner = new Map()
|
|
153
|
+
for (const declaration of retainedRefs.keys()) {
|
|
154
|
+
const owner = nearestFunction(declaration)
|
|
155
|
+
const refs = retainedByOwner.get(owner) ?? []
|
|
156
|
+
refs.push(declaration)
|
|
157
|
+
retainedByOwner.set(owner, refs)
|
|
158
|
+
}
|
|
159
|
+
for (const refs of retainedByOwner.values()) if (refs.length > 1) throw sourceNodeError(refs[1], sourceFile, "Components may own only one retained instance ref")
|
|
160
|
+
if (!privateRefs.size && !retainedRefs.size) return sourceFile
|
|
111
161
|
const visitor = node => {
|
|
112
162
|
if (ts.isVariableStatement(node) && node.declarationList.declarations.length === 1 && privateRefs.has(node.declarationList.declarations[0])) return undefined
|
|
163
|
+
if (ts.isVariableDeclaration(node) && retainedRefs.has(node)) {
|
|
164
|
+
const binding = factory.createArrayBindingPattern([
|
|
165
|
+
factory.createBindingElement(undefined, undefined, node.name),
|
|
166
|
+
factory.createBindingElement(undefined, undefined, retainedRefs.get(node))
|
|
167
|
+
])
|
|
168
|
+
const initializer = factory.createCallExpression(factory.createIdentifier("useState"), undefined, [
|
|
169
|
+
factory.createObjectLiteralExpression([factory.createPropertyAssignment("current", factory.createNull())])
|
|
170
|
+
])
|
|
171
|
+
return factory.updateVariableDeclaration(node, binding, node.exclamationToken, undefined, initializer)
|
|
172
|
+
}
|
|
113
173
|
const refs = effectRefs.get(node)
|
|
114
174
|
if (refs && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && ts.isBlock(node.body)) {
|
|
115
175
|
const body = ts.visitEachChild(node.body, visitor, context)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kudzujs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.3",
|
|
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,7 @@
|
|
|
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/*.test.mjs",
|
|
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",
|
|
57
57
|
"test:package": "node test/package-smoke.mjs",
|
|
58
58
|
"benchmark": "node test/performance.mjs",
|
|
59
59
|
"benchmark:keyed": "node test/keyed-performance.mjs",
|
|
@@ -73,6 +73,11 @@
|
|
|
73
73
|
"typescript": "^5.9.2"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
+
"@codemirror/view": "^6.43.9",
|
|
77
|
+
"@types/sortablejs": "^1.15.9",
|
|
78
|
+
"chart.js": "^3.8.2",
|
|
79
|
+
"sortablejs": "^1.15.7",
|
|
80
|
+
"typed.js": "^3.0.0",
|
|
76
81
|
"wrangler": "^4.112.0"
|
|
77
82
|
}
|
|
78
83
|
}
|