@askrjs/askr 0.3.1 → 0.4.1
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/CHANGELOG.md +898 -1
- package/README.md +61 -29
- package/capabilities.json +19 -9
- package/dist/actions/index.js +56 -12
- package/dist/benchmark.js +10 -15
- package/dist/boot/index.d.ts +3 -2
- package/dist/boot/index.js +1 -1
- package/dist/boot-C5HnQZio.js +4032 -0
- package/dist/browser-activity-2r5gA1Cb.js +222 -0
- package/dist/components/index.d.ts +8 -1
- package/dist/components/index.js +1 -43
- package/dist/{aria-hKjThHcp.js → compose-ref-ccRU59BM.js} +48 -5
- package/dist/control/index.d.ts +2 -0
- package/dist/control/index.js +1 -1
- package/dist/control-hr3B0QaD.js +211 -0
- package/dist/core.d.ts +5 -9
- package/dist/csp-nonce-D-XM_uuJ.js +35 -0
- package/dist/data/index.js +3 -3
- package/dist/{data-2edW0jhF.js → data-BsnOUpQ6.js} +294 -95
- package/dist/data-runtime-i-RD0Ffm.js +242 -0
- package/dist/{deferred-CbgNhf67.js → deferred-nGZglpj6.js} +42 -20
- package/dist/domains/actions.d.ts +8 -3
- package/dist/domains/component.d.ts +1 -107
- package/dist/domains/context.d.ts +0 -15
- package/dist/domains/control.d.ts +36 -249
- package/dist/domains/data.d.ts +31 -13
- package/dist/domains/diagnostics.d.ts +1 -2
- package/dist/domains/lifecycle.d.ts +2 -5
- package/dist/domains/refs.d.ts +0 -2
- package/dist/domains/routing.d.ts +43 -8
- package/dist/domains/scheduler.d.ts +1 -76
- package/dist/domains/server.d.ts +5 -2
- package/dist/domains/state.d.ts +2 -5
- package/dist/domains/telemetry.d.ts +0 -2
- package/dist/elements.d.ts +214 -51
- package/dist/error-boundary-ChXoIFVl.js +293 -0
- package/dist/foundations/icon/index.d.ts +0 -58
- package/dist/foundations/icon/index.js +2 -2
- package/dist/foundations/index.js +2 -1
- package/dist/foundations/interactions/index.js +1 -1
- package/dist/foundations/state/index.d.ts +3 -3
- package/dist/foundations/state/index.js +5 -3
- package/dist/foundations/structures/index.js +2 -1
- package/dist/foundations/utilities/index.d.ts +2 -0
- package/dist/foundations/utilities/index.js +1 -2
- package/dist/fx/index.d.ts +31 -58
- package/dist/fx/index.js +313 -195
- package/dist/hooks-kjLigwDJ.js +202 -0
- package/dist/index.d.ts +11 -74
- package/dist/index.js +8 -447
- package/dist/instance-DL-YKrj6.js +992 -0
- package/dist/{interactions-CTeACdT_.js → interactions-BAa2xuJ-.js} +3 -2
- package/dist/jsx-dev-runtime.d.ts +41 -18
- package/dist/jsx-dev-runtime.js +1 -6
- package/dist/{jsx-runtime-CPJXAOBF.js → jsx-runtime-C4idqHY0.js} +18 -7
- package/dist/jsx-runtime.js +1 -1
- package/dist/jsx.d.ts +61 -37
- package/dist/{manifest-D1P3UNWV.js → manifest-BNupb86f.js} +3 -3
- package/dist/{navigate-prC5V8XV.js → navigate-DsfO12hB.js} +468 -85
- package/dist/navigation.d.ts +12 -3
- package/dist/peer-types.d.ts +8 -0
- package/dist/portal-LYf4cbHd.js +349 -0
- package/dist/promise-Dz8iIHAm.js +16 -0
- package/dist/query-registry-tzSiVrV_.js +325 -0
- package/dist/readable-DFWOsTac.js +21 -0
- package/dist/{render-context-B7ohlh6t.js → render-context-DGHnEveC.js} +27 -11
- package/dist/render-sync-CUxvoVab.js +1256 -0
- package/dist/resolution-CF1BqHLm.js +1258 -0
- package/dist/resource-BhcaFqdB.js +377 -0
- package/dist/resources/index.d.ts +21 -11
- package/dist/resources/index.js +65 -266
- package/dist/router/index.d.ts +14 -5
- package/dist/router/index.js +22 -53
- package/dist/scope-ila_zaId.js +55 -0
- package/dist/{shared-kU8yvryp.js → shared-O2ZnmYAf.js} +1 -7
- package/dist/{snapshot-source-An4iKajG.js → snapshot-Cjx4N988.js} +6 -1
- package/dist/ssg/index.d.ts +1 -2
- package/dist/ssg/index.js +43 -22
- package/dist/ssr/index.d.ts +26 -28
- package/dist/ssr/index.js +4 -4
- package/dist/{ssr-CiNm6Vzl.js → ssr-9LOx_bEu.js} +133 -122
- package/dist/ssr-DPfdcq0e.js +229 -0
- package/dist/state-i9D4EtY6.js +250 -0
- package/dist/structures-CkehlOT3.js +231 -0
- package/dist/testing/index.d.ts +8 -4
- package/dist/testing/index.js +29 -34
- package/dist/url-C9xMpMMv.js +185 -0
- package/dist/utilities.d.ts +48 -2
- package/dist/verify-hydration-D_cyn1OX.js +152 -0
- package/package.json +19 -9
- package/dist/access-CEA2pOry.js +0 -1046
- package/dist/activity-oy2YFTFF.js +0 -201
- package/dist/boot-DLplW9h4.js +0 -1104
- package/dist/capabilities-Bbf0mlp9.js +0 -42
- package/dist/compose-ref-v0qGlHoN.js +0 -26
- package/dist/control-BR_mrX__.js +0 -173
- package/dist/control-C-ApnFs1.js +0 -20
- package/dist/csp-nonce-BfFWcd0w.js +0 -173
- package/dist/data-runtime-CxoY98lX.js +0 -149
- package/dist/default-portal-runtime-DvMvWJVp.js +0 -20
- package/dist/dom-renderer.d.ts +0 -77
- package/dist/domains/renderer.d.ts +0 -127
- package/dist/eager-control.d.ts +0 -8
- package/dist/effect-C3Zsc1k6.js +0 -292
- package/dist/for-state-BDykeOJL.js +0 -1753
- package/dist/instance-VAFYVJOa.js +0 -1027
- package/dist/notify-DxxuDPeY.js +0 -266
- package/dist/portal-w3oQ6oQU.js +0 -29
- package/dist/query-registry-LrYpQsDJ.js +0 -83
- package/dist/render-resolved-BC0W7_5b.js +0 -1123
- package/dist/renderer-CT0HeY58.js +0 -8623
- package/dist/resolution-3DskUkjG.js +0 -577
- package/dist/resource-18-sFRl9.js +0 -344
- package/dist/route-matching-CUzZ31gT.js +0 -551
- package/dist/router-internal-7nuNEHfG.js +0 -36
- package/dist/scope-Cq_iYbTR.js +0 -403
- package/dist/selector-BSPVkjKP.js +0 -311
- package/dist/ssr-EPjwJH5R.js +0 -29
- package/dist/state-BXJfudq1.js +0 -91
- package/dist/structures-CjEzfj4h.js +0 -654
- package/dist/timer-C7uqnJVt.js +0 -85
- package/dist/verify-hydration-ikaxotf7.js +0 -27
- package/dist/vnode-D24p6IW2.js +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,903 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.4.1 — 2026-10-01
|
|
4
|
+
|
|
5
|
+
- fix(data): honor defined-query `skipInitialFetch` and `initialData` options,
|
|
6
|
+
retain prefetched/hydrated data precedence, and retire active or queued
|
|
7
|
+
refreshes when the last reader leaves a query retained by `gcTime` (#724).
|
|
8
|
+
- fix(data): retire obsolete requests immediately when invalidation supersedes
|
|
9
|
+
them, preserve every string query key during dehydration, and keep sparse
|
|
10
|
+
query-key arrays from colliding with empty arrays. Dense-array keys are unchanged.
|
|
11
|
+
- fix(resources): abort and close failed stream generations, capture stream
|
|
12
|
+
definitions from the committed render, and preserve component ownership in
|
|
13
|
+
listener and timer callbacks so nested FX and invalidation use the owning app.
|
|
14
|
+
- fix(core): restore `derive()`/`selector()` definitions and subscriptions after
|
|
15
|
+
a rejected render, and run replacement refs even when previous ref cleanup
|
|
16
|
+
throws.
|
|
17
|
+
- fix(core): retain the following server element when hydration adopts merged
|
|
18
|
+
adjacent text nodes, including text claimed through reserved cursors.
|
|
19
|
+
- fix(control): preserve native and class instance receivers in `For` rows while
|
|
20
|
+
retaining reactive overlays for plain records and stable keyed row lifetimes.
|
|
21
|
+
- fix(foundations): prevent stale collection cleanup from removing a newer
|
|
22
|
+
registration for the same node, and preserve own `__proto__` props in
|
|
23
|
+
`mergeProps()` without changing the output record's prototype.
|
|
24
|
+
- fix(router): preserve prototype-named path parameters and query fields without
|
|
25
|
+
changing the prototype of the returned plain records. Server snapshots omit
|
|
26
|
+
browser history state, and hydration omission guards apply only to declared
|
|
27
|
+
omissions rather than inherited object properties.
|
|
28
|
+
- fix(router): capture each committed `onRouteChange()` callback and snapshot,
|
|
29
|
+
preserving ordered changes and cleanup when several commits precede a flush.
|
|
30
|
+
Destination snapshots read the matching navigation state before browser
|
|
31
|
+
history is written, and navigation state getters run once per destination.
|
|
32
|
+
- fix(ssr): distinguish actual deferred hydration values from ordinary records
|
|
33
|
+
with the same marker keys. Existing generated pages retain their original
|
|
34
|
+
deferred decoding, unambiguous pages retain their existing envelope bytes,
|
|
35
|
+
and the hydration envelope remains version 1.
|
|
36
|
+
- fix(rendering): keep attribute names such as `constructor` and `toString`
|
|
37
|
+
literal rather than interpreting them as inherited alias or namespace entries.
|
|
38
|
+
- fix(hydration): retain the clicked submit button when replaying a form
|
|
39
|
+
submission after hydration replaces its controls. Failed idle attempts retain
|
|
40
|
+
interaction and visibility retries, and mixed completion paths release their
|
|
41
|
+
replay and scroll listeners after the final deferred section activates.
|
|
42
|
+
- fix(ssg): normalize named splat entry parameters while preserving accepted
|
|
43
|
+
raw token spellings, encode dynamic render URLs while preserving existing
|
|
44
|
+
output paths and data override keys, and rebuild
|
|
45
|
+
unchanged routes when their generated HTML is missing.
|
|
46
|
+
|
|
47
|
+
## 0.4.0 — 2026-09-28
|
|
48
|
+
|
|
49
|
+
- feat(router): expose `matchRoute(path, { registry })` for synchronous route
|
|
50
|
+
path checks without resolving policies or loading route data. The testing
|
|
51
|
+
helper uses the same implementation.
|
|
52
|
+
|
|
53
|
+
- fix(core): a component tree too deep for the call stack fails with a
|
|
54
|
+
`RenderDepthError` (exported from `@askrjs/askr`) that explains the limit,
|
|
55
|
+
instead of a bare `RangeError` (or Firefox `InternalError`). It is raised
|
|
56
|
+
where render errors are first caught, so error boundaries, updates, and SSR
|
|
57
|
+
see it. The rendering guide documents the supported depth (#624).
|
|
58
|
+
|
|
59
|
+
- perf(core): remove quadratic owner, scheduler, and context paths (#612).
|
|
60
|
+
Detaching an owner is O(1) (holes in the parent's child list, compacted when
|
|
61
|
+
they dominate), the render lane is a depth-ordered heap and the effect and
|
|
62
|
+
post lanes are O(1) queues, and each component caches its inherited context
|
|
63
|
+
revision. On the new `tier2-subsystem-core-scaling` bench: disposing 10,000
|
|
64
|
+
sibling owners 50.7 ms to 0.43 ms, 5,000 render jobs 13.4 ms to 0.87 ms,
|
|
65
|
+
20,000 effect jobs 20.2 ms to 2.06 ms, patching a 2,000-deep chain 13.0 ms
|
|
66
|
+
to 0.72 ms (mean).
|
|
67
|
+
|
|
68
|
+
- fix(jsx): export `createElement` from `@askrjs/askr`. The automatic JSX
|
|
69
|
+
transform in TypeScript, esbuild, Babel, and oxc (Vite) compiles a `key`
|
|
70
|
+
written after a spread (`<Row {...props} key={id} />`) to `createElement`
|
|
71
|
+
from the import source, which previously failed to resolve. It removes the
|
|
72
|
+
development-only `__self`/`__source` props and, like `jsxs()`, marks several
|
|
73
|
+
child arguments as static children (#617).
|
|
74
|
+
|
|
75
|
+
- fix(foundations): `controllableState()` results destructure like `state()`
|
|
76
|
+
(`const [value, setValue] = controllableState(...)`). `ControllableState` is
|
|
77
|
+
now based on `StateTuple`, so the destructured getter and setter are typed.
|
|
78
|
+
The getter is readable like a `state()` getter, and an updater passed to its
|
|
79
|
+
setter runs once (#621).
|
|
80
|
+
|
|
81
|
+
- docs(foundations): document stacking dialogs and toasts through one writer
|
|
82
|
+
that owns the layer list and writes it through its own `definePortal()`
|
|
83
|
+
channel, since a portal shows the most recent writer's content (#495). The
|
|
84
|
+
default portal is part of the core client bundle, because every application
|
|
85
|
+
root provides and hosts it; the SSG hydration bundle check now asserts this
|
|
86
|
+
and the SSG guide no longer says otherwise (#611).
|
|
87
|
+
|
|
88
|
+
- **breaking** refactor(api): control, data, `getSignal` and `onRouteChange`
|
|
89
|
+
each have one import path (#487).
|
|
90
|
+
Migration:
|
|
91
|
+
- `For`, `Show`, `Case`, `Match` and their prop types: import from
|
|
92
|
+
`@askrjs/askr/control` instead of `@askrjs/askr`.
|
|
93
|
+
- `createQuery`, `createQueryCollection`, `defineQuery`, `serveQuery`,
|
|
94
|
+
`defineServerQueries`, `prefetchQuery`, `dehydrateDataRuntime`,
|
|
95
|
+
`hydrateDataRuntime` and the query types: import from `@askrjs/askr/data`
|
|
96
|
+
instead of `@askrjs/askr`.
|
|
97
|
+
- `getSignal`: import from `@askrjs/askr` instead of
|
|
98
|
+
`@askrjs/askr/resources`.
|
|
99
|
+
- `onRouteChange`, `RouteChangeCleanup`, `RouteChangeOptions`: import from
|
|
100
|
+
`@askrjs/askr/router` instead of `@askrjs/askr/resources`.
|
|
101
|
+
|
|
102
|
+
- **breaking** refactor(api): unused low-level exports are removed (#490).
|
|
103
|
+
`resolveRequest` and `renderResolvedToStringSync` are no longer exported from
|
|
104
|
+
`@askrjs/askr/ssr`. To resolve auth, redirects and status without rendering,
|
|
105
|
+
call `resolveRouteRequest(url, { registry, mode: 'ssr', auth, authContext,
|
|
106
|
+
request, signal })` from `@askrjs/askr/router`. Pass `mode: 'ssr'`
|
|
107
|
+
explicitly (the default is `'spa'` when a global `window` exists) and pass
|
|
108
|
+
`signal` explicitly (it is not taken from `request.signal`). The result may be a plain value or a
|
|
109
|
+
Promise, and errors can throw synchronously, so call it inside an `async`
|
|
110
|
+
function and `await` it to keep the old always-async behavior; to render, use
|
|
111
|
+
`renderToString`, `renderRouteRequest` or `renderRouteRequestToString`. `debounce` and `DebounceOptions` are no longer
|
|
112
|
+
exported from `@askrjs/askr/fx`; use `debounceEvent` for event handlers, or keep a local
|
|
113
|
+
debounce helper for code outside components.
|
|
114
|
+
|
|
115
|
+
- fix(types): intrinsic JSX typings accept MathML elements (`<math>`, `<mi>`,
|
|
116
|
+
`<mglyph>`, ...), `dangerouslySetInnerHTML` on every element, and the
|
|
117
|
+
`formAction`/`formMethod`/`formEncType`/`formNoValidate`/`formTarget`
|
|
118
|
+
attributes on `<button>` and `<input>`.
|
|
119
|
+
|
|
120
|
+
- fix(ssr): a `dangerouslySetInnerHTML` payload whose `__html` is `null` or
|
|
121
|
+
`undefined` renders no content, as on the client, instead of the text
|
|
122
|
+
`null`/`undefined`.
|
|
123
|
+
|
|
124
|
+
- fix(types): `resource<T>(load, deps)` with an explicit result type selects
|
|
125
|
+
the deps overload, `<ErrorBoundary>` accepts function and mixed children,
|
|
126
|
+
and `mergeProps()` returns `MergedProps` (base values win unless `undefined`;
|
|
127
|
+
a key the injected props always supply is required; index-signature props
|
|
128
|
+
keep the intersection) instead of an intersection that could collapse to
|
|
129
|
+
`never`. `MergedProps` is exported from `@askrjs/askr/foundations/utilities`.
|
|
130
|
+
|
|
131
|
+
- chore(tests): `tests/` is typechecked by `npm run typecheck` (#556).
|
|
132
|
+
|
|
133
|
+
- fix(types): JSX accepts a `string` or `number` `key` on function components
|
|
134
|
+
whose props do not declare it, as it does on intrinsic elements (#489).
|
|
135
|
+
|
|
136
|
+
- fix(types): `<For each={items}>` with a `state()` getter or `derive()`
|
|
137
|
+
result types each row as the list element instead of
|
|
138
|
+
`State<T[]> | StateSetter<T[]>`, including nullable getters, which render
|
|
139
|
+
nothing for `null`/`undefined`. A getter that does not return a list is now
|
|
140
|
+
a type error. `ForGetterProps` is exported.
|
|
141
|
+
|
|
142
|
+
- breaking(types): remove the `EagerControlPrimitive` type and the
|
|
143
|
+
`unknown`-returning `jsx()`/`jsxs()`/`jsxDEV()` overloads left from eager
|
|
144
|
+
control flow, and delete retired runtime declarations that no export
|
|
145
|
+
reached (#489, #610).
|
|
146
|
+
|
|
147
|
+
- fix(dom): a controlled `<select value>` keeps its value when a child
|
|
148
|
+
component, `For`, or function child adds, removes, or edits its options, or
|
|
149
|
+
an option's text or bound `value` changes, without the select itself
|
|
150
|
+
re-rendering (#608).
|
|
151
|
+
|
|
152
|
+
- fix(ssr): a value-less `<option>` (no `value`, or `null`/`false`) is
|
|
153
|
+
selected by its rendered text with whitespace stripped and collapsed, as the
|
|
154
|
+
browser's `option.value` reads it, including text from component children
|
|
155
|
+
and raw HTML entities, and excluding script text (#609).
|
|
156
|
+
|
|
157
|
+
- fix(state): `selector()` releases candidate entries once nothing reads
|
|
158
|
+
them, so querying many distinct values no longer grows it for the
|
|
159
|
+
component's lifetime (#613).
|
|
160
|
+
|
|
161
|
+
- fix(router): a navigation that changes the pathname remounts the route
|
|
162
|
+
leaf, so `state()`, `task()`, and resources start fresh for the new URL
|
|
163
|
+
(`/user/1` to `/user/2`). Layouts keep their instances and DOM.
|
|
164
|
+
|
|
165
|
+
- fix(router): when a destination's commit applies but a ref or binding
|
|
166
|
+
throws, navigation completes (history, location, metadata) and reports the
|
|
167
|
+
error. When a failed DOM write undoes the commit, navigation rolls back and
|
|
168
|
+
rethrows, keeping the previous page and location.
|
|
169
|
+
|
|
170
|
+
- fix(boot): mounting without hydration replaces the container's existing
|
|
171
|
+
content (a loading placeholder, unhydrated markup) instead of rendering
|
|
172
|
+
beside it.
|
|
173
|
+
|
|
174
|
+
- fix(foundations): an explicit `<DefaultPortal>` unmounted in the flush that
|
|
175
|
+
mounted it no longer hides `<Portal>` content from the automatic host.
|
|
176
|
+
|
|
177
|
+
- fix(core): renders that are discarded or rewound by an `ErrorBoundary`
|
|
178
|
+
release event delegation and `For` row property reads they created.
|
|
179
|
+
|
|
180
|
+
- fix(router): `<Resolve>` renders deferred route data after client
|
|
181
|
+
navigation. It claimed its resource hook only while the value was pending,
|
|
182
|
+
so the render after the promise settled failed the hook-order check and the
|
|
183
|
+
pending placeholder stayed on screen.
|
|
184
|
+
|
|
185
|
+
- fix(boot): `hydrate: { verifyMarkup: true }` no longer reports a mismatch
|
|
186
|
+
when a ref callback updates state after the hydration commit (for example,
|
|
187
|
+
adopting a persisted theme). Client markup passes if it matches the server
|
|
188
|
+
at the commit or after scheduled hydration work settles.
|
|
189
|
+
|
|
190
|
+
- breaking(api): remove `@askrjs/askr/experimental` and its runtime and
|
|
191
|
+
renderer-host construction APIs. Use the public boot, component, and state
|
|
192
|
+
APIs to mount and extend applications.
|
|
193
|
+
|
|
194
|
+
- breaking(types): drop the internal `_controlState` field from the published
|
|
195
|
+
`VNode`/`DOMElement` declaration. It exposed the retired runtime's control,
|
|
196
|
+
child-scope, and component-instance shapes, which the rebuilt core does not
|
|
197
|
+
have. No export is removed.
|
|
198
|
+
|
|
199
|
+
- fix(runtime): retain standalone reactive component updates in packed builds
|
|
200
|
+
by installing the DOM render host when a root is created.
|
|
201
|
+
|
|
202
|
+
- fix(renderer): forced bulk reuse now propagates key attribute write failures
|
|
203
|
+
and restores a partially written key before changing that row's content.
|
|
204
|
+
|
|
205
|
+
- fix(renderer): strip the forced positional bulk reuse diagnostic switch from
|
|
206
|
+
production bundles; it remains available in development tests.
|
|
207
|
+
|
|
208
|
+
- feat(fx): `scheduleRetry()` now returns a `result` promise with the final
|
|
209
|
+
success value, terminal error, or cancellation status. Terminal errors still
|
|
210
|
+
reach the host reporter.
|
|
211
|
+
|
|
212
|
+
- breaking(runtime): remove the scheduler's mutable `setInHandler` flag.
|
|
213
|
+
Use `runInHandlerScope()` to hold handler permissions for a lexical scope.
|
|
214
|
+
|
|
215
|
+
- feat(data): mutations support a synchronous `optimistic` callback that
|
|
216
|
+
returns a rollback for failure or abort. Overlapping executions no longer
|
|
217
|
+
abort earlier writes by default; explicit abort cancels all pending writes.
|
|
218
|
+
|
|
219
|
+
- fix(data): ownerless client queries now evict their cache lookup entry after
|
|
220
|
+
`gcTime` (five minutes by default, or immediately with `gcTime: 0`) while
|
|
221
|
+
the returned handle remains usable.
|
|
222
|
+
SSR request caches retain entries through dehydration.
|
|
223
|
+
|
|
224
|
+
- feat(data): `createQuery()` accepts `gcTime` to retain settled data for a
|
|
225
|
+
bounded interval after the last component reader unmounts. The default
|
|
226
|
+
remains immediate eviction.
|
|
227
|
+
|
|
228
|
+
- breaking(runtime): scheduler diagnostic state no longer includes
|
|
229
|
+
`taskCount`; use `queueLength` for pending work.
|
|
230
|
+
|
|
231
|
+
- breaking(runtime): scheduler diagnostic state no longer includes the
|
|
232
|
+
redundant `executionDepth` field; use `running` or `isExecuting()`.
|
|
233
|
+
|
|
234
|
+
- fix(runtime): report failed bulk-commit probes and reject work while commit
|
|
235
|
+
state is unknown instead of silently admitting it.
|
|
236
|
+
|
|
237
|
+
- fix(fx): `raf()` wrappers now expose `cancel()` and remain usable after a
|
|
238
|
+
callback throws.
|
|
239
|
+
|
|
240
|
+
- breaking(fx): remove `defer(fn)` from `@askrjs/askr/fx`; use the platform's
|
|
241
|
+
`queueMicrotask(fn)` for callback scheduling. `defer(promise)` remains in
|
|
242
|
+
`@askrjs/askr/router` for deferred route data.
|
|
243
|
+
|
|
244
|
+
- breaking(data): `DataRuntime` no longer exposes test override maps or accepts
|
|
245
|
+
them in `createDataRuntime()` options. Use the query and mutation test
|
|
246
|
+
registries to install fixtures. Both registries now accept an existing
|
|
247
|
+
runtime when a test needs to share one cache and fixture scope.
|
|
248
|
+
|
|
249
|
+
- fix(types): intrinsic event props now cover the DOM global handler map,
|
|
250
|
+
including animation, composition, drag, media, pointer-capture, and
|
|
251
|
+
transition events. Their `Capture` variants receive the same event type.
|
|
252
|
+
|
|
253
|
+
- fix(types): intrinsic JSX and `jsx()` refs now use each tag's element type.
|
|
254
|
+
An `<input>` ref can no longer be passed to `<button>`, and callback refs
|
|
255
|
+
receive the correct element type. This includes standard tags outside the
|
|
256
|
+
explicitly tailored intrinsic-prop set.
|
|
257
|
+
|
|
258
|
+
- breaking(types): Askr's JSX namespace now belongs only to the
|
|
259
|
+
`jsxImportSource` runtime modules. Import `type JSX` from
|
|
260
|
+
`@askrjs/askr/jsx-runtime` instead of using global `JSX`. Misspelled standard
|
|
261
|
+
tags now fail typechecking; hyphenated custom elements remain supported.
|
|
262
|
+
The unused class-component `ElementAttributesProperty` hook was removed.
|
|
263
|
+
|
|
264
|
+
- docs(scope): mark the published interaction and icon foundation subpaths as
|
|
265
|
+
platform internal contracts for sibling UI and icon packages. Their imports
|
|
266
|
+
and behavior remain available for those packages; application code should
|
|
267
|
+
use the composed UI and icon packages.
|
|
268
|
+
|
|
269
|
+
- breaking(api): runtime construction and renderer-host extension exports moved
|
|
270
|
+
from `@askrjs/askr` to `@askrjs/askr/experimental`. Change their import path;
|
|
271
|
+
their behavior and signatures remain the same. The root retains application
|
|
272
|
+
primitives. The experimental subpath is for runtime and renderer maintainers;
|
|
273
|
+
`createRuntime()` does not isolate mounted trees.
|
|
274
|
+
|
|
275
|
+
- fix(renderer): a chain of three or more components of the same type, each
|
|
276
|
+
returning the next directly, now keeps the state of every link when an
|
|
277
|
+
outer link re-renders. The update walk previously failed to find the deeper
|
|
278
|
+
links and recreated them, which also made updating a long chain quadratic in
|
|
279
|
+
its length. Updating a 10,000-component wrapper chain now takes linear time.
|
|
280
|
+
The runtime enforcement docs now state the nesting depths Askr guarantees
|
|
281
|
+
for each tree shape and rendering path: the 10,000-level guarantee covers
|
|
282
|
+
client wrapper chains only, while element nesting, server rendering, and
|
|
283
|
+
hydration recurse and are bounded by the engine's call stack.
|
|
284
|
+
- fix(renderer): a component that renders text, or a component whose result
|
|
285
|
+
spans several nodes, no longer gets a wrapper `<div>` on the client. This
|
|
286
|
+
applied when the result was the first render or followed an empty first
|
|
287
|
+
render, including `<Portal>{'x'}</Portal>`, Portal function children,
|
|
288
|
+
wrapper fragments rendering text, and content in the automatic default-portal
|
|
289
|
+
host. The client now places that content among its siblings inside
|
|
290
|
+
`askr-range` comment anchors, matching the server markup. A nested
|
|
291
|
+
component that renders nothing and later renders text keeps its instance
|
|
292
|
+
and state.
|
|
293
|
+
|
|
294
|
+
- fix(runtime): a failed render no longer leaves a structural function child
|
|
295
|
+
stale. A function child in a component's fragment or array result
|
|
296
|
+
(`<>{() => Array.from({ length: n() }, ...)}</>`) renders as a small
|
|
297
|
+
component; when its update joined a parent render that failed, the rollback
|
|
298
|
+
dropped that update, so the list kept the old item count until `n` changed
|
|
299
|
+
again, even after the parent recovered. The same applied to any child
|
|
300
|
+
component re-rendering on its own state. A rolled-back render now restores
|
|
301
|
+
the update the component was due (a queued run, or a scheduled render it
|
|
302
|
+
superseded), so the component renders again with the current state, as
|
|
303
|
+
fine-grained bindings do since #546.
|
|
304
|
+
- fix(runtime): cleanup failures are no longer swallowed in production. When
|
|
305
|
+
an update removes DOM or an app is cleaned up, a callback ref that throws on
|
|
306
|
+
`null`, a listener that cannot be removed, a throwing component cleanup
|
|
307
|
+
function (from a mount operation, task, or watch), or a failing root cleanup
|
|
308
|
+
callback was logged with a development-only warning. Every cleanup step still
|
|
309
|
+
runs, and the failures are now reported with `reportError()` in every build,
|
|
310
|
+
queued until the current task finishes: one report per removed subtree (a
|
|
311
|
+
single failure as-is, several as an `AggregateError`) and one per component.
|
|
312
|
+
Error handlers run after the update, can update state, and cannot roll it
|
|
313
|
+
back. `For` row disposal failures (previously a development-only
|
|
314
|
+
`console.error`), provisional component cleanup failures during a failed
|
|
315
|
+
render (previously dropped), and an async mount cleanup that resolves after
|
|
316
|
+
unmount (previously `console.error`) are reported the same way. A throwing
|
|
317
|
+
ref is no longer called with `null` twice. With
|
|
318
|
+
`cleanupStrict: true`, `cleanupApp()` now throws the failures of descendant
|
|
319
|
+
refs, listeners, and components, including components rendered inside `For`,
|
|
320
|
+
`Show`, and `Case`, instead of dropping them. In hosts without
|
|
321
|
+
`reportError()`, such as jsdom and Node under Vitest, the failures surface as
|
|
322
|
+
unhandled errors; stub `globalThis.reportError` in tests that throw from
|
|
323
|
+
cleanup on purpose.
|
|
324
|
+
- breaking(data): `dehydrateDataRuntime()` now throws a `TypeError` naming the
|
|
325
|
+
query key and property path when cached query data is not JSON
|
|
326
|
+
transport-safe (a `Date`, `Map`, `Set`, class instance, bigint, non-finite
|
|
327
|
+
number, `undefined`, accessor, or cyclic value). It was documented as
|
|
328
|
+
dropping non-serializable values, and in practice let `JSON.stringify()`
|
|
329
|
+
turn a `Date` into a string and a `Map` into `{}`. Query data now follows
|
|
330
|
+
the same transport rules as route hydration data. An SSR-mode
|
|
331
|
+
`prefetchQuery()` applies them as each value arrives and
|
|
332
|
+
`renderRouteRequest()` checks the data runtime before returning a streamed
|
|
333
|
+
result, so the render fails before a shell is sent. The shared validator
|
|
334
|
+
walks iteratively, so deeply nested data no longer overflows the stack, and
|
|
335
|
+
a fulfilled deferred that contains itself is reported as cyclic.
|
|
336
|
+
- breaking(actions): a failed `action().submit()` now always rejects with an
|
|
337
|
+
`Error` whose message carries the HTTP status (`Action failed (403): ...`),
|
|
338
|
+
including non-JSON bodies on any status. The server value (the envelope
|
|
339
|
+
`error`, or the body itself such as an RFC 7807 problem, whose `detail` or
|
|
340
|
+
`title` is used for the message) is kept as `cause`. It used to throw the
|
|
341
|
+
raw envelope `error` value, and a non-JSON body surfaced as a JSON
|
|
342
|
+
`SyntaxError` that hid the status. 422 validation replays keep their
|
|
343
|
+
`ActionValidationError` shape, and a bodiless success (204, 205, or an empty
|
|
344
|
+
2xx body) resolves with `undefined` and still runs declared invalidations.
|
|
345
|
+
- fix(ssr): dehydrated query data now travels in a separate `queries` record
|
|
346
|
+
of the hydration payload instead of the `resources` record, so a query key
|
|
347
|
+
such as `r:0` no longer replaces resource slot data and resource slots are
|
|
348
|
+
no longer hydrated into the client data runtime. HTML rendered before this
|
|
349
|
+
change carries query data under `resources`; a new client bundle hydrating
|
|
350
|
+
such HTML fetches that query data again instead of reusing it.
|
|
351
|
+
- fix(data): concurrent `prefetchQuery()` calls for the same key and runtime
|
|
352
|
+
share one in-flight fetch. A joiner still rejects promptly with its own
|
|
353
|
+
`signal.reason`, and starts a replacement fetch as soon as the starting
|
|
354
|
+
context aborts. An `invalidate()` covering the key detaches the fetch: later
|
|
355
|
+
prefetches fetch again, and no caller of the detached fetch stores its
|
|
356
|
+
pre-invalidation result.
|
|
357
|
+
- perf(data): invalidation skips invalidation-listener dispatch entirely when
|
|
358
|
+
no listener is registered (listeners come only from
|
|
359
|
+
`createInvalidationRecorder()`).
|
|
360
|
+
|
|
361
|
+
- fix(runtime): cleanup failures are no longer swallowed in production. When
|
|
362
|
+
an update removes DOM or an app is cleaned up, a callback ref that throws on
|
|
363
|
+
`null`, a listener that cannot be removed, a throwing component cleanup
|
|
364
|
+
function (from a mount operation, task, or watch), or a failing root cleanup
|
|
365
|
+
callback was logged with a development-only warning. Every cleanup step still
|
|
366
|
+
runs, and the failures are now reported with `reportError()` in every build,
|
|
367
|
+
queued until the current task finishes: one report per removed DOM node, one
|
|
368
|
+
per component tree disposed together, and one per update for work that runs
|
|
369
|
+
after the update commits (a single failure as-is, several as an
|
|
370
|
+
`AggregateError`). Error handlers run after the update, can update state, and
|
|
371
|
+
cannot roll it back. `For` row disposal failures (previously a
|
|
372
|
+
development-only `console.error`), provisional component cleanup failures
|
|
373
|
+
during a failed render (previously dropped), an async mount cleanup that
|
|
374
|
+
resolves after unmount, cleanup of the previous route after navigation, and
|
|
375
|
+
mount or commit operations that throw after an update commits (all
|
|
376
|
+
previously `console.error`) are reported the same way. A throwing ref is no
|
|
377
|
+
longer called with `null` twice. With `cleanupStrict: true`, `cleanupApp()`
|
|
378
|
+
now throws the failures of descendant refs, listeners, and components,
|
|
379
|
+
including components rendered inside `For`, `Show`, and `Case` at any depth
|
|
380
|
+
and after re-renders, instead of dropping them; failures during ordinary
|
|
381
|
+
updates of a strict app are reported. A cleanup failure during server
|
|
382
|
+
rendering is thrown from the render call. In hosts without `reportError()`,
|
|
383
|
+
such as jsdom and Node under Vitest, reported failures surface as unhandled
|
|
384
|
+
errors; stub `globalThis.reportError` in tests that throw from cleanup on
|
|
385
|
+
purpose.
|
|
386
|
+
- fix(hydration): markup verification (`hydrate: { verifyMarkup }`, on by
|
|
387
|
+
default outside production) now also compares the server HTML with the DOM
|
|
388
|
+
the client renderer produces while hydrating it, so SSR/client renderer
|
|
389
|
+
divergences such as a function child the server rendered empty throw
|
|
390
|
+
`Hydration mismatch detected` instead of passing a server-against-server
|
|
391
|
+
comparison. Both comparisons normalize through the DOM and compare `style`
|
|
392
|
+
attributes by their parsed declarations, so the SSR `color:red;` and the
|
|
393
|
+
DOM's `color: red;` are equal. The client check is skipped for static pages
|
|
394
|
+
hydrated at a client-only query or hash and for pages with server-rendered
|
|
395
|
+
portal content. A mount failure under `hydrate: { deferUntilIdle: true }` now
|
|
396
|
+
rejects `hydrateSPA()` instead of leaving it pending.
|
|
397
|
+
|
|
398
|
+
- fix(runtime): an `ErrorBoundary` fallback now removes the portal content its
|
|
399
|
+
failed subtree wrote. A component that rendered no DOM of its own (such as
|
|
400
|
+
a writer that returns only `<Portal>`) shared the boundary's host node and
|
|
401
|
+
survived the fallback, so its `Portal` content stayed in the host, still
|
|
402
|
+
mounted, and its cleanups never ran. The fallback now disposes every
|
|
403
|
+
component inside the boundary. This applies to render, function-valued prop
|
|
404
|
+
and control-flow errors, and `reset()`/`resetKey` recovery writes the portal
|
|
405
|
+
again. An explicit `DefaultPortal` host replaced by a fallback (directly, or
|
|
406
|
+
inside a component or `Show`/`For`/`Case`) keeps the portal claimed, so its
|
|
407
|
+
content does not move to the automatic host. During SSR and SSG, portal
|
|
408
|
+
writes made by a subtree whose boundary renders its fallback are discarded
|
|
409
|
+
instead of being emitted.
|
|
410
|
+
- fix(events): delegated handlers on app nodes inside an open shadow root
|
|
411
|
+
attached within an app's tree now run, once and in native bubbling order,
|
|
412
|
+
with `stopPropagation()` respected and `event.target` set to the real target
|
|
413
|
+
inside the shadow tree (retargeted to the host outside it). Dispatch follows
|
|
414
|
+
`composedPath()` up to the app root, falling back to the target's ancestry
|
|
415
|
+
when a host's composed path skips ancestors. `change` and `submit`, which
|
|
416
|
+
are not composed and never leave a shadow root, are no longer delegated and
|
|
417
|
+
attach directly to their element, so `onChange` and `onSubmit` run inside
|
|
418
|
+
open and closed shadow roots; each element with one of these handlers now
|
|
419
|
+
carries its own native listener. Delegated handlers inside closed shadow roots,
|
|
420
|
+
and delegated event types dispatched with `composed: false` inside a shadow
|
|
421
|
+
root, still do not run; mount an app inside the shadow root instead.
|
|
422
|
+
- fix(state): a `derive()` or `selector()` owned by a component whose
|
|
423
|
+
ancestor is queued to re-render (including a portal writer) or whose `<For>`
|
|
424
|
+
is about to reconcile (including through a `derive()`/`selector()` chain
|
|
425
|
+
feeding `each`) is no longer evaluated in the derived lane with the
|
|
426
|
+
component's stale props. It waits for that render, so a list row being
|
|
427
|
+
removed no longer runs (and throws from) a derive that indexes by its old
|
|
428
|
+
prop; a surviving component still gets the updated value in the same flush.
|
|
429
|
+
- fix(router): navigation edge cases. `navigate()` and guard redirects to
|
|
430
|
+
another origin now load that URL with `location.assign()` (or `replace()`)
|
|
431
|
+
instead of rendering its path in-app. A hash that is not valid
|
|
432
|
+
percent-encoding (`#%E0`) no longer throws after the history entry was
|
|
433
|
+
written. `<Link target="_self">` is handled by the router. String targets for
|
|
434
|
+
`navigate()`, `<Link href>` and `redirect()` are always logical below a
|
|
435
|
+
registry `basePath`: `/app/settings` under `/app` now goes to
|
|
436
|
+
`/app/app/settings` instead of being treated as already mounted, and
|
|
437
|
+
development builds warn about such strings. `navigate()`, `redirect()`,
|
|
438
|
+
`loginPath` and `authenticatedRedirectTo` accept a typed destination from
|
|
439
|
+
`to()`, whose public href is used as-is; `RouteDestination` is branded so
|
|
440
|
+
only `to()` creates one. Only targets with an explicit `http:`/`https:`
|
|
441
|
+
scheme may leave the origin: path-like strings that resolve to another host
|
|
442
|
+
(`//evil.example`, `/\evil.example`) throw a `TypeError`, on the client,
|
|
443
|
+
in server redirect decisions, for `loginPath` and at `<Link href>` render.
|
|
444
|
+
A redirect to another origin during the first load is handed to the browser
|
|
445
|
+
instead of rendering its path locally, and an absolute `loginPath` on
|
|
446
|
+
another origin keeps its origin when `next` is appended. Same-origin paths whose
|
|
447
|
+
dot segments collapse to a leading `//` (`/.//evil.example`) are refused
|
|
448
|
+
too, and history writes and document loads receive absolute URLs.
|
|
449
|
+
`history.pushState()`/`replaceState()` calls from `navigate()`, `<Link>`,
|
|
450
|
+
redirects and `updateRouteQuery()` now pass an absolute same-origin URL
|
|
451
|
+
(`https://site.example/page`) instead of a root-relative one, so code that
|
|
452
|
+
wraps or spies on them sees the full URL. Enhanced action redirects are
|
|
453
|
+
checked the same way and assigned as absolute URLs.
|
|
454
|
+
- fix(resources): a synchronous `task()` registers its cleanup as it runs
|
|
455
|
+
instead of one microtask later, so removing its owner right after mount
|
|
456
|
+
(for example a function child remounting when its hooks change) runs the
|
|
457
|
+
cleanup before the replacement's task. A task that throws still reports
|
|
458
|
+
the error as before.
|
|
459
|
+
- fix(ssr): function children and props, and `state`/`derive` cells passed
|
|
460
|
+
as children or props, now render their current value on the
|
|
461
|
+
server instead of nothing (children) or the function's source text (props).
|
|
462
|
+
Each is called once, untracked, and escaped like a static value, so the
|
|
463
|
+
server markup matches the client and hydration adopts it in place. On both
|
|
464
|
+
server and client, a function child that returns a `state`/`derive` cell
|
|
465
|
+
renders that cell's value (and the client follows it); any other function
|
|
466
|
+
in a function child's result renders nothing. A component that returns a
|
|
467
|
+
function or a cell itself renders nothing, but function and cell items in
|
|
468
|
+
the fragment or array a component returns (for example a layout that
|
|
469
|
+
renders `<>{props.children}</>`), and a function child of `ErrorBoundary`,
|
|
470
|
+
now render reactively on the client as they do on the server; the client
|
|
471
|
+
dropped them. `ErrorBoundary` no longer wraps text or fragment content in a
|
|
472
|
+
`<div>` on the client. A function child may use hooks
|
|
473
|
+
(`state()`, `resource()`, `task()`, `watch()`), `Show`/`For`/`Case` and
|
|
474
|
+
`readScope()` in every position, on both sides. The server renders each
|
|
475
|
+
one as a `FunctionChild` component; on the server these threw. On the
|
|
476
|
+
client, an element's function child that only reads values stays a direct
|
|
477
|
+
DOM binding, and upgrades in place to a mounted `FunctionChild` component
|
|
478
|
+
the first time a run asks for a component (code before the first hook then
|
|
479
|
+
runs again); a function child whose hooks change between runs remounts
|
|
480
|
+
with fresh state rather than reporting a hook-order error. Before, hooks there rendered
|
|
481
|
+
nothing, resources never resolved, and `readScope()` could read the wrong
|
|
482
|
+
provider. A function child of `Portal` now renders on the client. Parent
|
|
483
|
+
re-renders no longer remove the text of an element's function child when
|
|
484
|
+
the element is a component's root (`<div data-n={n}>{() => o()}</div>`),
|
|
485
|
+
also with element siblings. Hydrating an element returned by a function child now sets up that
|
|
486
|
+
element's own function children instead of clearing them. A function
|
|
487
|
+
child that throws on the client now goes to the nearest `ErrorBoundary`,
|
|
488
|
+
or is thrown from the update without one, like a reactive prop; it was
|
|
489
|
+
logged and swallowed, leaving the element empty, while the server
|
|
490
|
+
rendered the boundary's fallback.
|
|
491
|
+
- fix(renderer): a child list that starts with an item that renders nothing
|
|
492
|
+
(a plain object, a function, `true`) no longer duplicates the following
|
|
493
|
+
text or elements when it updates existing nodes, such as server markup
|
|
494
|
+
being hydrated (#544).
|
|
495
|
+
- fix(ssr): `renderResolvedToStringSync()` no longer throws "no route found"
|
|
496
|
+
for a route without params when `params` is omitted.
|
|
497
|
+
- fix(renderer): a component returning a fragment or array now retains its
|
|
498
|
+
child components when it re-renders with new props. Matching children keep
|
|
499
|
+
their state and DOM identity, including after hydration; nested fragments
|
|
500
|
+
reconcile against the same flattened child list used at creation.
|
|
501
|
+
- fix(control): existing `For` rows now render with the latest row callback.
|
|
502
|
+
A value the parent computed during render and captured in the callback (for
|
|
503
|
+
example `const current = selected()`) kept its first value in rows that were
|
|
504
|
+
already mounted. When the parent rerenders with a new callback, retained rows
|
|
505
|
+
rerun with it and keep their DOM, key, and local state; a stable callback
|
|
506
|
+
still skips them. A row that reruns on its own, because it read a reactive
|
|
507
|
+
value, now keeps its key: a component in that row previously lost its local
|
|
508
|
+
state when the row rendered again in the same flush. The docs now also state
|
|
509
|
+
that a reactive read inside the callback subscribes the row that made it
|
|
510
|
+
(they previously said it did not subscribe). See docs/guides/control-flow.md.
|
|
511
|
+
- fix(renderer): a failed keyed reconciliation commit now propagates to the
|
|
512
|
+
component update, which rolls the DOM back and routes the error to the
|
|
513
|
+
nearest `ErrorBoundary` (or throws it from the flush). Previously any commit
|
|
514
|
+
error was swallowed and the parent was rebuilt with `replaceChildren()`,
|
|
515
|
+
which also tore down children that were being reused. Errors from grouped
|
|
516
|
+
blueprint bindings (the second and later instances of a component or `For`
|
|
517
|
+
row) and from reactive child functions (`{() => ...}`) now reach the nearest
|
|
518
|
+
`ErrorBoundary` like single reactive props, or are thrown from the update
|
|
519
|
+
when there is none, instead of a development-only warning. A reactive child
|
|
520
|
+
update that fails while changing the element's children is rolled back
|
|
521
|
+
instead of left half-applied.
|
|
522
|
+
- fix(fx): errors thrown by `scheduleTimeout`/`scheduleIdle` callbacks, by
|
|
523
|
+
handlers run later by `debounceEvent`/`throttleEvent`/`rafEvent`, and by
|
|
524
|
+
`scheduleRetry` (a synchronous throw, the last attempt's rejection, or a
|
|
525
|
+
throwing `backoff`) are reported with `reportError()` like
|
|
526
|
+
event handler errors, instead of only being logged.
|
|
527
|
+
- build(deps): `@askrjs/auth` and `@askrjs/schema` are now optional peer
|
|
528
|
+
dependencies instead of dependencies, so apps that do not use route auth or
|
|
529
|
+
schemas no longer install auth's SAML/XML stack. Askr uses them for types
|
|
530
|
+
only: route requirements are composed by Askr itself, and the published
|
|
531
|
+
declarations typecheck without either package installed. Apps that use
|
|
532
|
+
`@askrjs/auth` or `@askrjs/schema` must list them in their own
|
|
533
|
+
dependencies.
|
|
534
|
+
- fix(ssg): the `@askrjs/askr/ssg` declarations no longer contain a stray
|
|
535
|
+
`import 'node:fs/promises'`, so consumers without `@types/node` typecheck.
|
|
536
|
+
- test(test-utils): `npm run typecheck` (and so `npm run lint`) now also
|
|
537
|
+
typechecks `test-utils/**`, including the Playwright browser app, through
|
|
538
|
+
`test-utils/tsconfig.json`. The existing type errors are fixed: fixtures
|
|
539
|
+
import `state` from the root entry, the playwright app's Vite config uses
|
|
540
|
+
`oxc.jsx`, and two scenarios no longer rely on unsafe nullable state reads.
|
|
541
|
+
- test(benches): `npm run typecheck` also typechecks `benches/`. Bench fixtures
|
|
542
|
+
now use `htmlFor`, a numeric `tabIndex`, `RouteHandler` route handlers and a
|
|
543
|
+
complete auth context, matching the public types.
|
|
544
|
+
- perf(env): development/production checks and renderer debug-flag reads no
|
|
545
|
+
longer copy `process.env` on every call. `isProductionEnvironment()` runs on every component render, and
|
|
546
|
+
enumerating the environment is expensive on Windows, where it dominated
|
|
547
|
+
deep component trees (a 10,000-deep chain took ~60s in jsdom on Windows CI;
|
|
548
|
+
it now mounts in ~0.1s locally, down from ~2.9s).
|
|
549
|
+
- test: scheduler tests now exercise the behaviour their names describe
|
|
550
|
+
(render-time writes, nested handlers, `scheduleEventHandler` deferral,
|
|
551
|
+
mid-flush lane order, the render-time write guard error), the runtime and
|
|
552
|
+
native owner-view consumer contracts assert observable behaviour instead of
|
|
553
|
+
private fields, and the browser form tests wait on the
|
|
554
|
+
pending render instead of wall-clock timing.
|
|
555
|
+
|
|
556
|
+
- fix(ssr): `renderRouteRequest()` streams each deferred `Resolve` boundary as
|
|
557
|
+
soon as its value settles instead of awaiting boundaries one at a time in
|
|
558
|
+
declaration order, so a slow boundary no longer holds back faster ones.
|
|
559
|
+
Patches can arrive out of order; boundary ids stay deterministic. A `Resolve`
|
|
560
|
+
rendered inside a settled boundary's content now streams too (id `d:0.0`
|
|
561
|
+
under `d:0`) with the same request route state and auth, where its fallback
|
|
562
|
+
was previously never replaced.
|
|
563
|
+
- fix(renderer): a failed render no longer leaves fine-grained bindings
|
|
564
|
+
(function-valued props and children) stale. A binding whose function the
|
|
565
|
+
render replaced kept the new value after the DOM rolled back, so later
|
|
566
|
+
renders saw nothing to change, and an update the binding was due to run in
|
|
567
|
+
the same flush was dropped. Bindings now roll back with the render and then
|
|
568
|
+
catch up with their state; see "Fine-grained bindings and rollback" in
|
|
569
|
+
docs/core/rendering.md.
|
|
570
|
+
- fix(control): development and production now agree on invalid `For` keys and
|
|
571
|
+
`Case`/`Match` children. A null, undefined, or duplicate `For` key throws in
|
|
572
|
+
every build (production previously dropped rows and showed the last
|
|
573
|
+
duplicate's data), and a non-`Match` child of `Case` or a `Match` outside a
|
|
574
|
+
`Case` throws in every build (production previously dropped it). These errors
|
|
575
|
+
reach the nearest `ErrorBoundary`, including a duplicate key introduced by a
|
|
576
|
+
boundary-local list update, a list inside a `Show`/`Case` branch, and an
|
|
577
|
+
invalid `Case` child, which previously escaped the boundary. Component
|
|
578
|
+
errors rendered inside a `For`/`Show`/`Case` created above an
|
|
579
|
+
`ErrorBoundary` now reach that boundary too. `For` resolves each row key
|
|
580
|
+
once per update, and the development-only key-type-change check (which could
|
|
581
|
+
never fire) is removed.
|
|
582
|
+
- fix(renderer): props whose live state is not the attribute now set the DOM
|
|
583
|
+
property. `<video muted>` sets `video.muted` (and keeps the attribute),
|
|
584
|
+
`<input indeterminate>` sets `input.indeterminate` without an attribute, and
|
|
585
|
+
object/array values on custom elements are assigned as properties. New
|
|
586
|
+
`prop:name` and `attr:name` escape hatches force either path. SSR renders
|
|
587
|
+
only attribute-backed values; property-only values apply on hydration. Removed
|
|
588
|
+
properties reset to their default, property writes roll back with a failed
|
|
589
|
+
commit, and the escape hatches keep the URL and raw-HTML guards.
|
|
590
|
+
- fix(renderer): delegated event handlers now match native dispatch.
|
|
591
|
+
Delegated listeners attach at each app root instead of `document.body`, so
|
|
592
|
+
apps mounted in shadow roots or iframes receive events and nested apps each
|
|
593
|
+
dispatch their own handlers once. Non-bubbling events (`focus`, `blur`,
|
|
594
|
+
`scroll`) attach directly to their element, so an ancestor's `onScroll` or
|
|
595
|
+
`onFocus` no longer runs, ancestor-first, for a descendant. `onWheel`,
|
|
596
|
+
`onTouchStart` and `onTouchMove` attach directly with `{ passive: false }`,
|
|
597
|
+
so `preventDefault()` in them takes effect. Hydrated nodes use the same
|
|
598
|
+
delegated listeners as client-rendered ones, so a client-rendered child's
|
|
599
|
+
handler runs before (and can stop) a hydrated ancestor's handler.
|
|
600
|
+
- fix(renderer): `onFocus` and `onBlur` no longer bubble: they only run when
|
|
601
|
+
their own element gains or loses focus, as with native `focus`/`blur`.
|
|
602
|
+
Container components that tracked focus inside a subtree with `onFocus`/
|
|
603
|
+
`onBlur` should migrate to `onFocusIn`/`onFocusOut`.
|
|
604
|
+
- fix(resources): a `resource()` deps change seen by a render that is rolled
|
|
605
|
+
back (for example because a sibling component throws in the same render) no
|
|
606
|
+
longer leaves the resource stuck `pending`. The new deps, loader and generation are
|
|
607
|
+
committed with the render, so the next committed render still starts the
|
|
608
|
+
fetch, and one back on the committed deps keeps the committed value.
|
|
609
|
+
- breaking(data): raw-string `invalidate(prefix)`, `invalidateOnInterval()`
|
|
610
|
+
and mutation `affects` prefixes now match whole `:`-delimited key segments.
|
|
611
|
+
`invalidate('user:1')` still matches `user:1` and `user:1:permissions` but no
|
|
612
|
+
longer matches `user:10`; prefixes ending in `:` (including every
|
|
613
|
+
`queryScope()` prefix) match as before. Only `:` is a segment boundary, so
|
|
614
|
+
raw prefixes built with other separators no longer match by text:
|
|
615
|
+
`invalidate('/api/users')` no longer matches `/api/users/1`, and
|
|
616
|
+
`invalidate('a.b')` no longer matches `a.b.c`. Move such keys to `:`
|
|
617
|
+
delimiters or `queryScope()`.
|
|
618
|
+
- fix(boot): `createSPA({ dataRuntime })` and `hydrateSPA({ dataRuntime })` now
|
|
619
|
+
use the configured runtime consistently. `hydrateSPA` previously seeded the
|
|
620
|
+
custom runtime from the hydration payload while route-component queries read
|
|
621
|
+
the default one, so readers showed a loading state and refetched instead of
|
|
622
|
+
using the hydrated value. Route `preload` hooks (initial route and client
|
|
623
|
+
navigations) also prefetched into the default runtime, so readers of a
|
|
624
|
+
custom runtime never saw the preloaded data.
|
|
625
|
+
- feat(ssr): `escapeHtml()` from `@askrjs/askr/ssr` escapes `&`, `<`, `>`, `"`
|
|
626
|
+
and `'` for request-derived values interpolated into a hand-written
|
|
627
|
+
`document` renderer template. It accepts any value; `null` and `undefined`
|
|
628
|
+
become an empty string. The SSR, SSG and rendering guides now use it.
|
|
629
|
+
- docs: fix examples that failed at runtime. The API overview and core data
|
|
630
|
+
guide no longer call `state()`/`derive()` at module scope, the quick-start,
|
|
631
|
+
resources, core data and resources reference `resource()` examples check `error` before `pending || !value` so a failed first load no
|
|
632
|
+
longer shows "Loading..." forever, and the runtime-enforcement examples now
|
|
633
|
+
actually trigger the documented hook-order and render-mutation errors and
|
|
634
|
+
quote the real message. Doc fences tagged `run=<id>` are now imported and
|
|
635
|
+
exercised in jsdom by `npm run test:checks`
|
|
636
|
+
(`tests/checks/docs/runnable-snippets.test.ts`), not only type-checked.
|
|
637
|
+
- fix(renderer): event handler errors are reported with `reportError()`, which
|
|
638
|
+
dispatches a `window` `error` event, instead of only being logged. This covers
|
|
639
|
+
delegated and direct listeners and `scheduleEventHandler`; the remaining
|
|
640
|
+
handlers for the event still run. Hosts without `reportError()` (Node, jsdom)
|
|
641
|
+
rethrow the error from a microtask, so it arrives as an `uncaughtException`
|
|
642
|
+
and test runners that fail on unhandled errors report it. Errors thrown by
|
|
643
|
+
function-valued (reactive) props now reach the nearest `ErrorBoundary`
|
|
644
|
+
(including one whose direct children contain the binding; bindings in a
|
|
645
|
+
fallback go to the boundary above), or are thrown from the update when there
|
|
646
|
+
is none. Previously they were a development-only warning and silent in
|
|
647
|
+
production.
|
|
648
|
+
- fix(renderer): keyed fast paths no longer catch errors and retry through a
|
|
649
|
+
slower path. A row whose render throws now renders once per update instead of
|
|
650
|
+
up to three times, and the error surfaces once. Production commits no longer
|
|
651
|
+
capture an `Error().stack` for diagnostics.
|
|
652
|
+
- chore(bench): the benchmark workflow runs only the existing tier1 and tier2
|
|
653
|
+
lanes, as one matrix job per tier, instead of 36 copy-pasted steps that also
|
|
654
|
+
invoked the removed `bench:tier3`/`bench:tier4` scripts. The browser-only
|
|
655
|
+
`precise_clock` input and its dead tier3/4 config are removed, artifacts are
|
|
656
|
+
uploaded per tier (`bench-results-stable-tier<N>`), and docs no longer
|
|
657
|
+
describe the deleted lanes, their guardrails, or hydration timings taken from
|
|
658
|
+
them. A `tests/checks` guard fails on bench scripts, configs, or files that
|
|
659
|
+
docs and workflows reference but do not exist.
|
|
660
|
+
- chore(agents): AGENTS.md now explicitly allows maintainer-run release tooling
|
|
661
|
+
(`scripts/publish-order.mjs`) and prefers workflow matrices over copy-pasted
|
|
662
|
+
steps; `tests/checks` fails on any unlisted `scripts/*` file.
|
|
663
|
+
- fix(router): when several page `fallback()`s match a URL, the deepest page
|
|
664
|
+
prefix (counted in segments) now wins on the client and in sync and async
|
|
665
|
+
SSR. Previously the longest prefix string won, so an encoded prefix such as
|
|
666
|
+
`/caf%C3%A9` could outrank a deeper `/café/x`.
|
|
667
|
+
- fix(router): registering two routes that match the same URLs now throws
|
|
668
|
+
`Duplicate route path` instead of silently shadowing the second. Routes are
|
|
669
|
+
compared the way they match: parameter and splat names, trailing slashes and
|
|
670
|
+
percent-encoding of static segments are ignored, a `*` wildcard equals a
|
|
671
|
+
param, and a `fallback()` equals a named splat at its prefix. Each registry
|
|
672
|
+
is checked separately. Declare a template once and use `entries()` for its
|
|
673
|
+
pages.
|
|
674
|
+
- fix(router): `fallback()` inside a parameterized page such as
|
|
675
|
+
`page('/{lang}')` now handles misses under `/en/...` (and receives `lang`)
|
|
676
|
+
instead of matching only the literal `/{lang}/...`.
|
|
677
|
+
- fix(ssg): `invalidationKeys` passed to `route()` were dropped from the
|
|
678
|
+
registry, so incremental generation treated those routes as keyless and
|
|
679
|
+
always rebuilt them. They now apply to every page the route's `entries()`
|
|
680
|
+
generate.
|
|
681
|
+
- fix(boot): error messages no longer point at a nonexistent `createSSR`; they
|
|
682
|
+
name `createSPA`/`hydrateSPA` (and `createIslands`). Removed the unreachable
|
|
683
|
+
redirect branches in `createSPA`/`hydrateSPA`, and sync SSR now matches
|
|
684
|
+
routes against the registry's manifest records like async SSR does.
|
|
685
|
+
- fix(ssr): async render contexts resolve `AsyncLocalStorage` from
|
|
686
|
+
`globalThis.AsyncLocalStorage` or `process.getBuiltinModule('node:async_hooks')`
|
|
687
|
+
instead of `new Function('return require(...)')`. Previously synchronous
|
|
688
|
+
`withRenderContext()` could not accept async callbacks under Node ESM (where
|
|
689
|
+
that loader never resolved `require`), and async render contexts were
|
|
690
|
+
rejected under a CSP without `'unsafe-eval'` and on runtimes without
|
|
691
|
+
`process.versions.node`. Any runtime that provides `AsyncLocalStorage`
|
|
692
|
+
globally or via `process.getBuiltinModule` is now supported; see the SSR
|
|
693
|
+
guide.
|
|
694
|
+
- fix(runtime): `cspNonce()` decides whether a render scope is active from
|
|
695
|
+
scope state instead of matching the text of `readScope()`'s error message.
|
|
696
|
+
- fix(runtime): `state.set()` now throws when called inside a `derive()` or
|
|
697
|
+
`selector()` computation, including recomputes in the derived lane where no
|
|
698
|
+
component is rendering. Previously only render-time recomputes were caught
|
|
699
|
+
(by the render-mutation guard), so a derived-lane write went through
|
|
700
|
+
silently and could loop. A same-value (no-op) `set()` is still allowed.
|
|
701
|
+
- fix(runtime): an error thrown by the renderer while marking reactive props
|
|
702
|
+
dirty is no longer swallowed. Component readers of the source are still
|
|
703
|
+
notified, then the error is rethrown to the writer (or aggregated by the
|
|
704
|
+
scheduler inside a flush). If notifying readers also fails, both errors are
|
|
705
|
+
thrown together as an `AggregateError`.
|
|
706
|
+
- fix(router): `hydrateSPA()` no longer redirects a server-authorized page to
|
|
707
|
+
the login route when the browser cannot resolve the identity itself (for
|
|
708
|
+
example httpOnly-cookie sessions). Apps opt in with the new
|
|
709
|
+
`auth.dehydrate(context)` hook, which selects the minimal identity snapshot
|
|
710
|
+
(`authenticated`, `principal`, `tenant`, `scopes`; never the session)
|
|
711
|
+
serialized into the hydration payload. Hydration uses it for the initial
|
|
712
|
+
route; navigations use `auth.resolve`, or keep the snapshot when no resolver
|
|
713
|
+
is configured. Nothing about the identity is serialized without the hook.
|
|
714
|
+
`RouteAuthOptions.resolve` is now optional.
|
|
715
|
+
- fix(fx): `scheduleTimeout()`, `scheduleIdle()` and `scheduleRetry()` now
|
|
716
|
+
cancel pending work when the component that scheduled them unmounts, as
|
|
717
|
+
documented. Calls made from a mounted component's `task()`, `watch()`
|
|
718
|
+
callback, mount/commit operation or event handler bind to that component's
|
|
719
|
+
lifetime, including portal content (owned by the writer) and handlers
|
|
720
|
+
wrapped by `debounceEvent()`, `throttleEvent()`, `rafEvent()` or
|
|
721
|
+
`scheduleEventHandler()`. Scheduled callbacks and retry attempts run as the
|
|
722
|
+
scheduling component, so work they reschedule (for example a polling loop)
|
|
723
|
+
also stops on unmount, and a stable portal handler follows the writer that
|
|
724
|
+
last rendered it. A synchronous scheduler flush inside a handler no
|
|
725
|
+
longer runs unrelated queued work as that handler's component. Previously no
|
|
726
|
+
cleanup was ever registered and timers fired after unmount. The unreachable
|
|
727
|
+
SSR branches in these helpers are removed, and `scheduleRetry()` settles
|
|
728
|
+
when `fn` throws synchronously or returns a non-promise.
|
|
729
|
+
- fix(fx): `throttle(fn, ms, { leading: false })` waits the full interval
|
|
730
|
+
after an idle gap instead of firing on the next tick, and a throttle without
|
|
731
|
+
a trailing edge no longer retains the last arguments.
|
|
732
|
+
- fix(fx): `debounceEvent({ leading: true })` and the default
|
|
733
|
+
`throttleEvent()` no longer call the handler twice for a single event. The
|
|
734
|
+
trailing edge only runs when another event arrived after the leading call.
|
|
735
|
+
Both now share their edge logic with `debounce()` and `throttle()`, and
|
|
736
|
+
`debounceEvent().flush()` only runs a pending trailing call.
|
|
737
|
+
- fix(foundations): `mergeProps` no longer lets a `base` value of `undefined`
|
|
738
|
+
overwrite an injected prop. Forwarding an optional prop that was not supplied
|
|
739
|
+
(`onClick={props.onClick}`) used to wipe the primitive's handler or ARIA
|
|
740
|
+
attribute; `undefined` now means "not provided". Pass `null` to clear an
|
|
741
|
+
injected prop explicitly; intrinsic `on*` handler props now accept `null` in
|
|
742
|
+
their types. `mergeInteractionProps` still lets the policy own `disabled`, so
|
|
743
|
+
an enabled native policy clears a fixed `disabled` from the user or child.
|
|
744
|
+
- fix(data): an inline `createQuery({ key, fetch })` no longer warns about a
|
|
745
|
+
conflicting shared query definition on every re-render, and no longer keeps
|
|
746
|
+
the first render's `fetch` closure forever. The reader that defines a key
|
|
747
|
+
now replaces its `fetch`, `isConsistent`, and `reconcile` on each render;
|
|
748
|
+
only other readers of the same key with a different definition warn, after
|
|
749
|
+
the current render work settles (a keyed row replacing the owner does not
|
|
750
|
+
warn). When the defining reader unmounts, a remaining reader's definition
|
|
751
|
+
takes over immediately. An in-flight fetch is checked and reconciled with the
|
|
752
|
+
callbacks it started with. `createQueryCollection()` entries are redefined on
|
|
753
|
+
each update, so `retry()` fetches with the entry's current `input`.
|
|
754
|
+
- fix(ssr): text children of HTML `<script>` and `<style>` are written verbatim
|
|
755
|
+
instead of entity-escaped, so `a > b` no longer becomes `a > b` and breaks
|
|
756
|
+
the CSS or JavaScript. In styles every `<` becomes the CSS escape `\3c `; in
|
|
757
|
+
scripts every `</`, `<script` and `<!--` is rewritten (JSON-safe), so content
|
|
758
|
+
cannot close the element or an ancestor. Text stays entity-escaped inside SVG
|
|
759
|
+
or MathML, `<style>` inside `<select>`, and under raw text or RCDATA ancestors
|
|
760
|
+
such as `<noscript>` and `<textarea>`. Element children inside these elements
|
|
761
|
+
now throw during SSR.
|
|
762
|
+
- fix(router): route precedence is decided segment by segment, as documented:
|
|
763
|
+
the first segment where two routes differ picks static > param > wildcard >
|
|
764
|
+
splat, so `/docs/{*rest}` now beats `/{lang}/{page}` for `/docs/intro`
|
|
765
|
+
instead of losing on a summed score. SPA, SSR and SSG share the ordering.
|
|
766
|
+
`RouteRecord.rank` now encodes this order and its numeric values changed.
|
|
767
|
+
- fix(router): static route segments, `fallback()` prefixes and registry
|
|
768
|
+
`basePath` values are compared against decoded URL segments, so routes such
|
|
769
|
+
as `/café` and `/a b` (and fallbacks or registries mounted under them) match
|
|
770
|
+
`/caf%C3%A9` and `/a%20b` on the client, in SSR and in SSG. Malformed
|
|
771
|
+
encodings do not throw.
|
|
772
|
+
- fix(router): the `*` capture of wildcards, catch-alls and fallbacks is now
|
|
773
|
+
percent-decoded like param and splat captures (`café`, not `caf%C3%A9`).
|
|
774
|
+
Encoded separators `%2F` and `%5C` now stay encoded in every capture,
|
|
775
|
+
including params and named splats, which previously decoded `%2F` to `/`:
|
|
776
|
+
`/files/..%2F..%2Fetc` captures `..%2F..%2Fetc`, not `../../etc`. `to()`
|
|
777
|
+
passes kept `%2F`/`%5C` through, so captures round-trip to their URL.
|
|
778
|
+
- fix(renderer,ssr): camelCase SVG presentation props such as
|
|
779
|
+
`strokeDasharray`, `fillOpacity`, `stopColor` and `clipPath` now render as
|
|
780
|
+
their hyphenated attribute names, and `xlinkHref`/`xmlLang`/`xmlSpace`/
|
|
781
|
+
`xmlnsXlink` render as namespaced `xlink:`/`xml:`/`xmlns:` attributes (set
|
|
782
|
+
with `setAttributeNS` on SVG and MathML elements on the client). Previously
|
|
783
|
+
only five SVG names were mapped and the rest were written verbatim, which
|
|
784
|
+
browsers ignore. The mapping also applies to HTML elements, so a prop such
|
|
785
|
+
as `fontSize` or `pointerEvents` on a `<div>` now renders `font-size` or
|
|
786
|
+
`pointer-events` instead of `fontsize`. Custom elements (tag names with a
|
|
787
|
+
`-`) keep their prop names as before. The SVG prop types now list the
|
|
788
|
+
presentation attributes.
|
|
789
|
+
- fix(renderer,ssr): numeric `style` values get a `px` unit on non-unitless
|
|
790
|
+
properties, so `style={{ width: 10 }}` renders `width:10px` instead of the
|
|
791
|
+
invalid `width:10`. Unitless properties (React's list plus
|
|
792
|
+
`font-size-adjust`, `initial-letter` and `math-depth`, with or without a
|
|
793
|
+
vendor prefix), `0` and custom properties are unchanged. `ms`-prefixed names
|
|
794
|
+
such as `msFlexPositive` now render as `-ms-flex-positive`.
|
|
795
|
+
- fix(renderer,ssr): `false` renders `"false"` for the enumerated attributes
|
|
796
|
+
`draggable`, `spellCheck`, `contentEditable` and `writingSuggestions`
|
|
797
|
+
instead of removing them, so
|
|
798
|
+
`<img draggable={false}>` is no longer draggable. Their prop types now accept
|
|
799
|
+
booleans.
|
|
800
|
+
- breaking(data): `defineQuery()` fetchers now receive the input and the abort
|
|
801
|
+
signal as separate arguments, `fetch(input, { signal })`, instead of one
|
|
802
|
+
merged `{ ...input, signal }` object. The merged shape dropped primitive
|
|
803
|
+
inputs (a `QueryDefinition<number, ...>` fetcher only saw `{ signal }`) and
|
|
804
|
+
let the abort signal overwrite an input field named `signal`. Rewrite
|
|
805
|
+
`fetch: ({ id, signal }) => ...` as `fetch: ({ id }, { signal }) => ...`.
|
|
806
|
+
Old-style fetchers with an annotated parameter
|
|
807
|
+
(`({ id, signal }: { id: string; signal: AbortSignal })`) still compile at
|
|
808
|
+
the `defineQuery()` definition but fail to typecheck at the `createQuery()`
|
|
809
|
+
call site; move `signal` to the second argument.
|
|
810
|
+
Inline `createQuery({ key, fetch })` fetchers are unchanged.
|
|
811
|
+
- fix(router): client navigation no longer dead-ends on URLs no registered app
|
|
812
|
+
can render. A `Link` click or `navigate()` to an unmatched same-origin URL, or
|
|
813
|
+
one outside the registry `basePath`, now loads the URL as a document, and
|
|
814
|
+
Back/Forward to an unmatched entry reloads the page instead of leaving the old
|
|
815
|
+
page mounted under the new URL. A `fallback()` route still renders in place.
|
|
816
|
+
Navigating to the already-loaded URL with no route, or a fragment-only
|
|
817
|
+
Back/Forward on such a page, skips the load. A failed Back/Forward render or
|
|
818
|
+
rejected Back/Forward loader now returns with `history.go()` to the entry whose page
|
|
819
|
+
is still rendered instead of overwriting the entry the user landed on; Askr
|
|
820
|
+
stamps an `askrIndex` position into the history state it writes and reloads
|
|
821
|
+
when an entry written by other code makes positions unknown.
|
|
822
|
+
- fix(resources): a resource hydrated from preloaded data keeps its value on
|
|
823
|
+
later re-renders instead of resetting to pending and refetching. The preloaded
|
|
824
|
+
value now seeds the resource, so `refresh()` and `deps` changes also work
|
|
825
|
+
after hydration.
|
|
826
|
+
- fix(ssr): deferred `Resolve` boundaries now render with the request URL, route
|
|
827
|
+
table, base path and params. Previously the streamed boundary saw the root
|
|
828
|
+
path with empty params, so `currentRoute()` and `Link` produced the wrong HTML
|
|
829
|
+
and hydration reported a markup mismatch.
|
|
830
|
+
- fix(data): SSR `prefetchQuery()` no longer throws a `TypeError` in
|
|
831
|
+
development when a query has no registered server handler. The
|
|
832
|
+
skipped-preload warning is tracked per runtime outside the frozen
|
|
833
|
+
`DataRuntime`, so it logs once per query key and resolves `false` as intended.
|
|
834
|
+
- fix(runtime): `derive()` no longer serves a value computed by a previous
|
|
835
|
+
render's closure (for example after a second render in the same flush from
|
|
836
|
+
`watch()` or `task()`, new props, or a local read from another derive). A
|
|
837
|
+
source change evaluates the derive once with the last render's function and
|
|
838
|
+
skips the owner re-render when the value is unchanged; when it changed, the
|
|
839
|
+
owner re-renders and evaluates its new function once more. A render-time
|
|
840
|
+
recompute of `derive()` or `selector()` now notifies downstream readers, so
|
|
841
|
+
derived values in other components no longer stay one update behind.
|
|
842
|
+
- fix(runtime): hook-order enforcement now catches a render that claims fewer
|
|
843
|
+
hooks than the first render, and a slot whose hook kind changes (for example
|
|
844
|
+
`derive()` where the first render called `state()`). Previously only extra
|
|
845
|
+
hooks threw; skipped or swapped hooks passed silently. Messages name hooks by
|
|
846
|
+
their public API (`createQuery()`, `onRouteChange()`, `<For>`). The unreachable
|
|
847
|
+
monotonic index check is removed, and the internal `ComponentInstance`
|
|
848
|
+
field `expectedStateIndices` is replaced by `expectedHookKinds`.
|
|
849
|
+
- fix(renderer): re-renders no longer strip attributes, class tokens and inline
|
|
850
|
+
styles added by other code (focus traps setting `aria-hidden`/`inert`,
|
|
851
|
+
animation libraries setting `style.transform`, tooltips adding `data-*`,
|
|
852
|
+
`classList.add`). Prop reconciliation now diffs against the props Askr last
|
|
853
|
+
applied to each element instead of the live DOM, so only what Askr rendered is
|
|
854
|
+
removed or patched.
|
|
855
|
+
|
|
856
|
+
- fix(ssr): sync `renderToString({ url, registry })`/`renderToStream()` no
|
|
857
|
+
longer follow auth redirects or render a denial marker with an implicit 200.
|
|
858
|
+
Redirect and deny decisions throw the new `SSRAccessDecisionError`, whose
|
|
859
|
+
`decision` matches what `renderRouteRequest()` returns. The sync path also no
|
|
860
|
+
longer starts route loaders: a loader route throws `SSRDataMissingError`
|
|
861
|
+
naming the route and pointing to `renderRouteRequest()`, and abandoned async
|
|
862
|
+
resolution no longer leaks an unhandled rejection.
|
|
863
|
+
- breaking(ssr): sync `renderToString({ url, registry })`/`renderToStream()`
|
|
864
|
+
no longer follow auth redirects or render a denial marker with an implicit 200. Redirect and deny decisions throw the new `SSRAccessDecisionError`, whose
|
|
865
|
+
`decision` matches what `renderRouteRequest()` returns. The sync path also no
|
|
866
|
+
longer runs route loaders: any route that declares a loader, including
|
|
867
|
+
a synchronous one that previously rendered, now throws `SSRDataMissingError`
|
|
868
|
+
naming the route and pointing to `renderRouteRequest()`, before its preload,
|
|
869
|
+
lazy import, or loader starts. Abandoned async resolution no longer leaks an
|
|
870
|
+
unhandled rejection, and lazy routes whose component is already loaded now
|
|
871
|
+
resolve synchronously.
|
|
872
|
+
- fix(runtime): an update loop that trips the scheduler's `MAX_FLUSH_DEPTH`
|
|
873
|
+
guard no longer aborts the flush. The looping task is dropped and its error is
|
|
874
|
+
reported together with earlier task failures, remaining queued work still
|
|
875
|
+
runs, a dropped component update re-renders on its next write, and
|
|
876
|
+
production builds now fail such a loop instead of hanging. Effects,
|
|
877
|
+
`derive()` and `selector()` are now each limited to 50 runs per flush,
|
|
878
|
+
counted across lanes, so a reactive cycle through them (including
|
|
879
|
+
derive-to-derive cycles that previously hung inside one batch) stops at the
|
|
880
|
+
looping entry without stranding sibling work.
|
|
881
|
+
- fix(router): `currentAuth()` no longer falls back to the process-wide client
|
|
882
|
+
identity during server rendering. A server render without request auth now
|
|
883
|
+
sees an anonymous identity, and server-mode route resolution no longer writes
|
|
884
|
+
the client identity, so one request cannot observe another request's user.
|
|
885
|
+
- fix(ssr): rejected `defer()` values no longer serialize the server error
|
|
886
|
+
message into the hydration payload. The client receives a generic reason
|
|
887
|
+
unless the error sets `expose: true`.
|
|
888
|
+
- fix(renderer,ssr): drop `javascript:` and `vbscript:` URLs from `src` and
|
|
889
|
+
`data` attributes (for example `<iframe src>` and `<object data>`) on both
|
|
890
|
+
client and server. `data:`, `blob:` and custom-scheme resource URLs are
|
|
891
|
+
unchanged.
|
|
892
|
+
- fix(data): hydrated or prefetched query data is now consumed by the first
|
|
893
|
+
client reader for its key instead of staying in `runtime.queryData` forever.
|
|
894
|
+
After a `refresh()` and a remount, the query fetches again instead of
|
|
895
|
+
reviving the original server value as fresh, and consumed entries no longer
|
|
896
|
+
accumulate on the default runtime. Server renders still read without
|
|
897
|
+
consuming. Prefetches (including route `preload`) skip keys a mounted query
|
|
898
|
+
already owns and discard results that resolve after a reader mounted. The
|
|
899
|
+
browser keeps at most 50 unread prefetched entries per runtime, evicting the
|
|
900
|
+
oldest; server and SSG payload building is not capped.
|
|
4
901
|
|
|
5
902
|
## 0.3.1 — 2026-09-12
|
|
6
903
|
|