@bgub/fig 0.0.1 → 0.1.0-alpha.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +501 -0
  2. package/README.md +50 -19
  3. package/dist/element-BZ7r9ncY.d.ts +342 -0
  4. package/dist/element-DmbzNH0T.js +299 -0
  5. package/dist/element-DmbzNH0T.js.map +1 -0
  6. package/dist/hooks-QDRabULx.d.ts +158 -0
  7. package/dist/index.d.ts +3 -5
  8. package/dist/index.js +4 -4
  9. package/dist/internal.d.ts +212 -6
  10. package/dist/internal.js +14 -43
  11. package/dist/internal.js.map +1 -1
  12. package/dist/jsx-runtime.d.ts +1 -1
  13. package/dist/jsx-runtime.js +3 -3
  14. package/dist/jsx-runtime.js.map +1 -1
  15. package/dist/payload-format-KTNaSI4h.js +366 -0
  16. package/dist/payload-format-KTNaSI4h.js.map +1 -0
  17. package/dist/payload.d.ts +92 -0
  18. package/dist/payload.js +429 -0
  19. package/dist/payload.js.map +1 -0
  20. package/dist/{transition-DEqcImne.js → resource-kXeIR52S.js} +121 -71
  21. package/dist/resource-kXeIR52S.js.map +1 -0
  22. package/dist/{data-store-CRnNAT9-.js → transition-CC4kjjrU.js} +165 -54
  23. package/dist/transition-CC4kjjrU.js.map +1 -0
  24. package/package.json +5 -6
  25. package/dist/data-CHJh_xU9.d.ts +0 -79
  26. package/dist/data-h46EcMnE.js +0 -37
  27. package/dist/data-h46EcMnE.js.map +0 -1
  28. package/dist/data-store-CRnNAT9-.js.map +0 -1
  29. package/dist/data-store-EQOLQsW4.d.ts +0 -32
  30. package/dist/element-Bh_k9c3N.js +0 -179
  31. package/dist/element-Bh_k9c3N.js.map +0 -1
  32. package/dist/element-CCOOi4Ka.d.ts +0 -209
  33. package/dist/server.browser.d.ts +0 -9
  34. package/dist/server.browser.js +0 -8
  35. package/dist/server.browser.js.map +0 -1
  36. package/dist/server.d.ts +0 -9
  37. package/dist/server.js +0 -9
  38. package/dist/server.js.map +0 -1
  39. package/dist/transition-Cl6mAPcx.d.ts +0 -96
  40. package/dist/transition-DEqcImne.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1 +1,502 @@
1
+ ## @bgub/fig@0.1.0-alpha.1
2
+
3
+ ### Asset descriptors use native names and preserve native ordering
4
+
5
+ Client-inserted and host-rendered stylesheets now form precedence buckets in
6
+ the order each distinct precedence value is first discovered. A stylesheet
7
+ discovered later for an existing bucket is inserted before the following
8
+ bucket, keeping lazy and payload-delivered CSS in its intended cascade order.
9
+
10
+ Raw `<script>` elements now enter the asset registry only when explicitly
11
+ marked `async`. Non-async scripts retain their native document position and
12
+ execution semantics; explicit `script()` descriptors continue to support all
13
+ asset-delivery modes.
14
+
15
+ Asset descriptor options and serialized payload asset rows now use native HTML
16
+ attribute names: `crossorigin`, `fetchpriority`, and `http-equiv`. The previous
17
+ React-style `crossOrigin`, `fetchPriority`, and `httpEquiv` spellings are
18
+ removed.
19
+
20
+ Host resource resolution now receives the actual host parent and fixes
21
+ out-of-band placement once per fiber. SVG and MathML titles consequently stay
22
+ in their native namespace, while HTML titles carrying `itemprop` stay in-tree;
23
+ ordinary document titles continue to use the shared head registry.
24
+
25
+ ### Drop the `clientReferenceAssets` helper from the main entry
26
+
27
+ The runtime helper `clientReferenceAssets(reference)` (read a client
28
+ reference's declared assets, resolving thunks) had two homes; it is now
29
+ exported only from `@bgub/fig/internal`, where its consumers — the payload
30
+ serializer and framework manifest plumbing — already import it. Apps
31
+ declare assets with `clientReference({ assets })` and never call the
32
+ helper. The `ClientReferenceAssets` type stays on the main entry because
33
+ it appears in the public `ClientReferenceOptions.assets` signature.
34
+
35
+ ### Stable client-reference identity across decodes
36
+
37
+ `@bgub/fig/payload` exposes `createPayloadClientReferenceResolver(resolve)`:
38
+ a caller-owned stateful resolver passed as the `resolveClientReference`
39
+ decode option. With one, every resolvable client reference decodes to one
40
+ resolver-owned wrapper per reference id across all decodes sharing the
41
+ resolver — gated, ungated, or asynchronously resolved — so re-decoding a
42
+ payload updates islands in place instead of remounting them. Reveal gates
43
+ ride the decoded element instances rather than the wrapper: each decode
44
+ gates exactly its own content, so a newer decode's pending assets never
45
+ re-suspend an island already on screen, while its new island instances
46
+ still wait for the stylesheets they declared. The caller owns the
47
+ resolver's lifetime; under fast refresh no manual invalidation is needed
48
+ (hot edits remap the latched resolution through component families, and
49
+ unaccepted updates full-reload), while `delete`/`clear` cover manifest
50
+ swaps without a reload. With a plain resolve function, gated and async
51
+ references keep their per-decode wrapper identity, now with the same
52
+ per-element gating.
53
+
54
+ `@bgub/fig-dom`'s `payloadDataLoader` accepts the stateful resolver through
55
+ its existing `resolveClientReference` option.
56
+
57
+ Framework adapters can retain one resolver across refreshes and navigations,
58
+ preventing asset-gated islands from remounting on every re-decode.
59
+
60
+ ### Publish metadata only with its visible owner
61
+
62
+ Title and meta resources now travel through Payload as owner-bound
63
+ declarations and update the document only when their decoded tree commits.
64
+ Pending or superseded refreshes keep the previous metadata visible.
65
+
66
+ Streaming HTML now treats Suspense fallbacks as metadata owners and reconciles
67
+ the completed visible metadata snapshot in the boundary reveal operation.
68
+ Partial segments and failed or abandoned primary work cannot mutate the head.
69
+
70
+ The obsolete `onAssetError` option and its asset-diagnostic types are removed:
71
+ late metadata is delivered with its owner instead of being dropped.
72
+
73
+ ### Supersession abort waits for the successor's value
74
+
75
+ Refreshing a fulfilled data-resource entry no longer aborts the previous
76
+ generation's signal when the new load starts. Authority transfers when the
77
+ successor's value publishes: the visible stale value keeps streaming through
78
+ the refresh window (payload holes keep filling), subscribers re-render onto
79
+ the new tree in the same pass the old generation retires in, and a failed
80
+ refresh leaves the previous generation fully alive — stale value usable,
81
+ live holes included. Value-less pending loads still abort immediately when
82
+ superseded, and hydrate-over/eviction/disposal still end every generation.
83
+
84
+ This closes the gap where refreshing a serialized-component resource while
85
+ its holes were still streaming surfaced "Payload decode aborted." through
86
+ the nearest ErrorBoundary — cancellation now stays retirement, never a user
87
+ error, for plain consumers too.
88
+
89
+ ### DevTools show per-fiber data-resource dependencies
90
+
91
+ `@bgub/fig` data stores expose `inspectDataDependencyCanonicalKeys(owner)`,
92
+ a dev-only inspection read of the canonical keys an owner's committed
93
+ `readData` subscriptions point at (returns an empty array in prod builds).
94
+
95
+ `@bgub/fig-reconciler` devtools snapshots record those keys on every fiber
96
+ as `FigDevtoolsFiberSnapshot.dataResourceCanonicalKeys`. The field is
97
+ required, so external snapshot producers must supply it (empty array when
98
+ unknown).
99
+
100
+ The `@bgub/fig-devtools` panel filters the Data section by the selected
101
+ fiber's dependencies instead of always listing the entire store, and tree
102
+ rows show a green badge with the fiber's data-resource read count next to
103
+ the blue hook-count badge. Selecting
104
+ the root still lists every entry, including those with no committed
105
+ subscriber (unclaimed preloads, hydrated-but-unread rows). Refreshing
106
+ entries no longer render a redundant `Pending` row.
107
+
108
+ ### `ensureData`: the awaitable read for code outside render
109
+
110
+ `ensureData(resource, ...args)` resolves the value a key would render with:
111
+ the cached value when the entry has one (kicking the same background
112
+ revalidation a stale `readData` does), or the in-flight load's settlement on
113
+ a cache miss. It rejects with the error `readData` would throw, follows
114
+ superseding loads and server hydrations to the authoritative value, and never
115
+ subscribes — pair it with `readData` in the component, which claims the
116
+ settled entry within the preload retention window. An awaiting caller retains
117
+ the entry, so the unclaimed-preload eviction cannot abort a load out from
118
+ under an ensure.
119
+
120
+ Available as a free function (ambient store) and on the explicit handle
121
+ (`readDataStore()`, `root.data`). This is the delegation verb for external
122
+ routers: a route loader awaits `ensureData`, the component reads with
123
+ `readData`, and the data store stays the single cache.
124
+
125
+ ### Serialized components move to the data-resource model
126
+
127
+ Serialized trees are now ordinary data resources: servers return plain
128
+ Payload streams, and clients consume them through keyed `dataResource`
129
+ instances with `payloadDataLoader`. Refresh is `refreshData`, navigation can
130
+ select a new key, and back/forward navigations reuse cached entries. Commits
131
+ wait for the incoming Payload, island modules, and stylesheet gates.
132
+
133
+ Supporting API additions: `payloadDataLoader` accepts a `prepareAssets`
134
+ override (defaults to `insertAssetResources`), and `decodePayloadStream`
135
+ accepts an `onClientReference` observer for reference metadata.
136
+
137
+ ### Compose host behavior with mixins
138
+
139
+ Core now exports `createMixin()` and resolves render-time host behavior through
140
+ the `mix` prop. Mixins may contribute host props or nested mixins while keeping
141
+ the host type and subtree fixed. Explicit host props form the baseline; mixins
142
+ run in authoring order, and later results win.
143
+
144
+ DOM event listeners now use `mix={on(type, callback)}`. Migrate
145
+ `events={[on(type, callback)]}` to `mix={on(type, callback)}`; multiple or
146
+ conditional descriptors move into `mix={[...]}` and preserve positional
147
+ listener identity.
148
+
149
+ ### Keep `useId` stable through selective hydration
150
+
151
+ `useId` now follows one canonical server/hydration tree path through Suspense
152
+ and Activity. Dehydrated boundaries snapshot that path when they claim their
153
+ server marker, then restore it when hydration resumes, so client updates that
154
+ insert or move surrounding siblings cannot renumber ids already present in the
155
+ server HTML. Suspense's private Activity wrapper is transparent to the path.
156
+
157
+ Components mounted only on the client now receive ids from a separate
158
+ `fig-C-*` namespace. Those ids remain stable for the component lifetime and
159
+ cannot collide with ids reserved by server-rendered content that has not
160
+ hydrated yet.
161
+
162
+ ### `isValidElement` has a single home on the main entry
163
+
164
+ `isValidElement` was the one runtime export with two homes: the app-facing
165
+ main entry and `@bgub/fig/internal` (grouped with the other `$$typeof`
166
+ brand predicates). It is now exported only from `@bgub/fig`; the renderer
167
+ and server packages import it from there. The internal-only predicates
168
+ (`isSuspense`, `isPortal`, ...) are unchanged.
169
+
170
+ ### Expose the payload decoder on JSR
171
+
172
+ The JSR manifest now exports `@bgub/fig/payload`, matching the npm package
173
+ and making the browser-safe payload decoder available from both registries.
174
+
175
+ ### Payload exposes rendering and decoding, not its implementation
176
+
177
+ The payload packages now present two primary operations:
178
+ `renderToPayloadStream` on the server and `decodePayloadStream` in the
179
+ browser-safe core entry. Row/model types, value encoding, codec machinery,
180
+ content-type negotiation helpers, and framework document transports are
181
+ internal implementation details.
182
+
183
+ Client references use one `resolveClientReference(reference)` seam that may
184
+ return a component synchronously or asynchronously. It replaces the separate
185
+ load, resolve, and observation callbacks; reference metadata now includes its
186
+ stream-safe assets. The unused `load` field is also removed from
187
+ `clientReference(...)` declarations.
188
+
189
+ `payloadDataLoader` keeps only `request`, `resolveClientReference`, and the
190
+ optional `prepareAssets` override. Payload encoding is fixed internally rather
191
+ than exposed as a speculative custom-codec interface.
192
+
193
+ ### Generation-lifetime loader signals and `payloadDataLoader`
194
+
195
+ Data-resource loader signals now live as long as their load's generation,
196
+ not just the pending promise: the `{ signal }` a loader receives stays
197
+ unaborted after the value lands and aborts when the generation loses
198
+ authority — a newer load supersedes it, a server push hydrates over it, the
199
+ entry evicts, or the store is disposed. A rejected load's own signal aborts
200
+ on settlement, and `invalidateData` never aborts (marking stale does not
201
+ revoke authority). Loaders that stream into their value in the background —
202
+ payload decodes filling holes — tie that work to the signal; plain fetch
203
+ loaders are unaffected.
204
+
205
+ The load context also carries an internal, generation-guarded hydration
206
+ capability (symbol-keyed; read through `@bgub/fig/internal`) that hydrates
207
+ server-pushed `data` rows through the calling store only while the load is
208
+ authoritative, skipping rows that target the loading entry's own key.
209
+
210
+ New in `@bgub/fig-dom`: `payloadDataLoader({ request,
211
+ resolveClientReference?, prepareAssets? })` adapts a payload-stream
212
+ endpoint into an ordinary data-resource loader. It validates the response
213
+ (status, body, payload codec content-type; unusable bodies are cancelled),
214
+ wires `decodePayloadStream` to the generation-lifetime signal, hydrates
215
+ `data` rows through the store capability, inserts stream-discovered assets
216
+ with `insertAssetResources` (stylesheet gates delay only dependent reveal),
217
+ and resolves with the decoded root value — so `readData(postResource, slug)`
218
+ suspends like any read and returns renderable elements while streamed holes
219
+ keep filling in the background.
220
+
221
+ ### `@bgub/fig/payload`: the browser-safe payload home and `decodePayloadStream`
222
+
223
+ The payload decoder moved from `@bgub/fig-server/payload` to a new browser-safe
224
+ `@bgub/fig/payload` entry. Browser code no longer imports the server package to
225
+ decode; fig-server's serializer and the decoder share their private row/value
226
+ format through `@bgub/fig/internal`.
227
+
228
+ New client half: `decodePayloadStream(stream, options)` returns a live
229
+ `PayloadDecode` — `value` resolves when the root row decodes while outlined
230
+ holes keep streaming in, the never-rejecting `completion` reports
231
+ complete/failed/aborted, and `abort()` idempotently rejects unresolved holes
232
+ with an internal cancellation reason. Options wire data-row hydration
233
+ (`hydrate`), unified client-reference resolution, and asset preparation with
234
+ reveal gating (`prepareAssets`).
235
+
236
+ Internally, `assets` rows now carry an optional `for` — the row id whose
237
+ reveal depends on those assets, decided at serialization scope exit so a
238
+ suspended subtree's assets gate its outlined row rather than the enclosing
239
+ tree.
240
+
241
+ Dev-time enforcement: serialized components are render-only. During
242
+ `renderToPayloadStream`, `useState`, `useActionState`, `useTransition`,
243
+ `useStableEvent`, and the effect hooks now throw in development; the read
244
+ verbs, `useMemo`, `useId`, and `useSyncExternalStore`'s `getServerSnapshot`
245
+ path stay server-safe.
246
+
247
+ ### Deliver client-reference assets outside the resolution suspension
248
+
249
+ A decoded client reference's asset declarations now attach above its
250
+ module-load and reveal-gate suspension points. A server document render
251
+ that hits a cold reference module emits the island's stylesheet with the
252
+ segment containing the reference instead of with the late fill, so
253
+ first-request asset ordering matches warm requests.
254
+
255
+ ### Move `createPortalNode` to `@bgub/fig/internal`
256
+
257
+ `createPortalNode` is the cross-package seam renderers wrap in their
258
+ container-typed `createPortal`; apps never call it directly. It now lives
259
+ on the internal entry with the other renderer protocol exports instead of
260
+ the app-facing main entry. Portal-creating apps keep using
261
+ `createPortal(children, container, key?)` from `@bgub/fig-dom`; the
262
+ `FigPortal` type stays on the main entry because it appears in public
263
+ signatures.
264
+
265
+ ### Promise-shaped payload decoder
266
+
267
+ `decodePayloadStream` now returns `Promise<FigNode>` directly — the root
268
+ value promise — instead of a `PayloadDecode` handle. Cancellation is
269
+ signal-only (`options.signal`, unchanged); the redundant `abort()` method is
270
+ gone. The `completion` promise is replaced by an `onStreamDone(result)`
271
+ decode option, called exactly once when ingestion settles as `complete`,
272
+ `failed`, or `aborted` — post-root failures that strand no pending hole
273
+ remain observable there. The callback is never awaited and its exceptions
274
+ and rejections are swallowed, so an observer — sync or async — cannot break
275
+ decode teardown or leak an unhandled rejection. The
276
+ `PayloadDecode` interface and its non-thenable caveat are deleted;
277
+ `PayloadDecodeCompletion` remains as the callback's result type.
278
+
279
+ `@bgub/fig-dom`'s `payloadDataLoader` migrates internally; its public API is
280
+ unchanged.
281
+
282
+ Also considered and declined: narrowing `ResolveClientReference` to an
283
+ opaque id. The reference's `exportName`/`ssr`/`assets` mirror the `client`
284
+ row's wire fields and are all load-bearing in framework document pipelines;
285
+ the rationale is recorded in `docs/concepts/payload.md`.
286
+
287
+ ### Promise-valued children render through Suspense
288
+
289
+ `FigNode` now accepts promises of nodes. Promise children occupy distinct,
290
+ host-transparent child slots, suspend through the nearest `Suspense`, and route
291
+ rejections or invalid resolved children through normal error handling.
292
+
293
+ HTML rendering retains exact promise children as independent streaming tasks,
294
+ while Payload uses node-validated promise rows that decode to the same child
295
+ shape. Payload-rendered async components are invoked once and retain their
296
+ component-scoped assets on the outlined row.
297
+
298
+ ### Tighten public component, loader, asset, and bind signatures
299
+
300
+ `@bgub/fig` now names the shared data loader contract as
301
+ `DataResourceLoader`, constrains lazy loaders to components, and exposes
302
+ `ComponentProps` so lazy wrappers preserve the loaded component's props
303
+ without exposing its implementation statics. Client-reference SSR
304
+ implementations stay aligned with the reference's props. Stable-event typing
305
+ now models the trailing lifecycle signal separately from caller arguments.
306
+
307
+ `meta()` descriptors now require exactly one valid metadata identity:
308
+ `charset`, `name` plus `content`, `property` plus `content`, or `http-equiv`
309
+ plus `content`. Raw meta elements that do not satisfy the same shape remain
310
+ ordinary host elements instead of entering the asset registry.
311
+
312
+ `@bgub/fig-dom` payload requests now receive the standard
313
+ `DataResourceLoadContext`, portals retain their DOM container type in the
314
+ returned `FigPortal`, and bind callbacks must return `undefined`; cleanup is
315
+ exclusively driven by their `AbortSignal`.
316
+
317
+ ### Attribute rejected payload holes to their data resource
318
+
319
+ Payload hole errors are attributed to the authoritative owning data-resource
320
+ generation. Error boundaries receive `dataResourceKeys`, and
321
+ `invalidateDataError` retires the broken fulfilled value before retrying so a
322
+ remounted boundary suspends on fresh content instead of immediately catching
323
+ the same rejected hole. `decodePayloadStream` also exposes an observational
324
+ `onHoleError` callback.
325
+
326
+ The load context's hydration capability now shares the same authority window:
327
+ a still-visible generation's `data` rows keep hydrating through a superseding
328
+ refresh's window instead of being dropped the moment the refresh starts.
329
+
330
+ ### Remove the `FigText` type alias
331
+
332
+ `FigText` was a two-member alias (`string | number`) whose only use was
333
+ as a constituent of the `FigNode` union; no signature anywhere took it
334
+ by name. The union now spells out `string | number` directly, and the
335
+ alias is gone from both the main and internal entries. Code that
336
+ referenced `FigText` should use `string | number` (or `FigNode` where
337
+ the full children type is meant).
338
+
339
+ ### Remove the `h` alias for `createElement`
340
+
341
+ `@bgub/fig` no longer exports `createElement` under the second name `h`.
342
+ Every export has one home and one name; migrate by importing
343
+ `createElement` (or alias it locally: `const h = createElement`).
344
+
345
+ ### Use one data-resource API in every environment
346
+
347
+ `dataResource` now covers shared, browser, and server-only loaders without a
348
+ second API. Server-only loaders belong behind the framework's server module
349
+ boundary; browser code uses an explicit key-only resource when it needs the
350
+ same hydrated value.
351
+
352
+ The pass-through `serverDataResource` API, `@bgub/fig/server` entry point,
353
+ `figData` Vite transform, and generated browser resource stubs are removed.
354
+
355
+ ### The targeted-refresh protocol and payload consumer are gone
356
+
357
+ The serialized-components deletion gate passed across the demo e2e suites,
358
+ so the legacy architecture is removed rather than deprecated:
359
+
360
+ - `@bgub/fig-server/payload` no longer exports `createPayloadConsumer`,
361
+ `PayloadConsumer`, `PayloadBoundary`, `PAYLOAD_BOUNDARY_HEADER`,
362
+ `PayloadFetchError`, or the consumer fetch/ingestion seam, and
363
+ `renderToPayloadStream` drops its `refreshBoundary` option.
364
+ - The wire format loses the `refresh`/`refresh-error` rows and the
365
+ `$fig:"boundary"` model: the refresh unit is the data-resource key that
366
+ delivers the payload, so refreshing is requesting the same stream again.
367
+
368
+ Replacements, all already shipping: `decodePayloadStream` in
369
+ `@bgub/fig/payload` is the client half; fig-dom's `payloadDataLoader`
370
+ delivers a serialized tree as an ordinary data resource; the freshness
371
+ verbs (`refreshData`/`invalidateData`) are the refresh story; sub-tree
372
+ refresh granularity is finer resource keys.
373
+
374
+ ### Move HTML escaping helpers to a focused subpath
375
+
376
+ `escapeAttribute`, `escapeText`, `escapeScriptText`, and `escapeScriptJson` now export from
377
+ `@bgub/fig-server/html` instead of the main `@bgub/fig-server` entry.
378
+ The dedicated subpath keeps companion-markup helpers separate from server
379
+ render entry points while preserving their exact escaping behavior. The
380
+ TanStack Start adapter now consumes these helpers, Fig's internal data-store
381
+ brand predicate, and its own storage-context API instead of duplicating them.
382
+
383
+ ### Server-route navigations commit content in one pass
384
+
385
+ Navigating to a payload server route used to commit an empty slot for one
386
+ retry beat (stretched to the full animation length when a view transition
387
+ was running) before the content revealed, because three already-settled
388
+ promises still suspended on their first render read:
389
+
390
+ - payload element gates are now tracked at creation, so a gate that settles
391
+ before its first read resolves synchronously;
392
+ - client-reference module resolutions are tracked the same way;
393
+ - pre-commit asset preparation now reveals the island hydration gate,
394
+ so navigations mount real islands instead of paying a placeholder →
395
+ reveal follow-up commit.
396
+
397
+ The route swap now lands as a single commit containing the full decoded
398
+ content, so view transitions capture the destination page with its content
399
+ already present.
400
+
401
+ ### Resolved client references keep their identity across decodes
402
+
403
+ `decodePayloadStream` used to wrap every client reference in a fresh
404
+ per-decode component, so re-decoding a surrounding payload (refreshing a
405
+ server component that contains islands) remounted every island and dropped
406
+ its client state.
407
+
408
+ References that `resolveClientReference` resolves synchronously and that carry no asset
409
+ gate now decode to the resolved component itself. The element type is
410
+ identity-stable across decodes, so a refresh of the surrounding payload
411
+ updates islands in place and their state survives. Gated or
412
+ asynchronously resolved references still decode to per-decode wrappers.
413
+
414
+ ### Preserve framework-managed asset placement
415
+
416
+ Framework adapters can now keep externally managed head and body tags in their
417
+ declared positions without exposing a DOM prop. TanStack Router uses this for
418
+ route-managed links, styles, and scripts while continuing to map title and meta
419
+ entries through Fig asset resources. Full-document hydration now ignores the
420
+ doctype and one shared marker identifies every server-owned node without a
421
+ client fiber. Declarative asset lists also gain a client commit lifecycle, so
422
+ route titles and metadata update during navigation.
423
+
424
+ ### Add Payload routes to the TanStack Start adapter
425
+
426
+ `@bgub/fig-tanstack-start/payload` now exposes `payloadResource`, which compiles
427
+ an inline render callback into a private server function and Fig-owned route
428
+ data resource. Applications supply the cache key and component tree without
429
+ authoring transport plumbing. The shared declaration can stay in one
430
+ `.payload.tsx` module; its render callback and render-only imports are omitted
431
+ from the browser build. The initial SSR response is embedded into the document
432
+ and adopted without refetching; client navigation and refresh use the same
433
+ resource request path. Payload data rows
434
+ hydrate the shared store, asset resources are retained on their owning server
435
+ segments or inserted through the browser registry, and client references retain
436
+ their resolver-defined identity. `decodePayloadStream` and `payloadDataLoader`
437
+ now expose `retainAssets` for server document renderers that need this delivery
438
+ path.
439
+
440
+ `@bgub/fig-tanstack-start/server` exposes the lower-level
441
+ `renderPayloadResponse` used by the generated TanStack server function; it
442
+ defaults its render abort signal to the incoming request, so a disconnected
443
+ client cancels the Payload render. Shell
444
+ HTML streams while outlined Suspense holes settle, and the completed initial
445
+ responses are embedded before TanStack starts full-document hydration. The Vite
446
+ adapter also publishes assets imported only by server modules into the client
447
+ build output. Fig Router links also consume TanStack's `viewTransition`
448
+ navigation option without forwarding it as an invalid attribute to the rendered
449
+ anchor, and derive active state from the resolved route instead of an in-flight
450
+ location.
451
+
452
+ ### Add the TanStack Start runtime adapter
453
+
454
+ `createDataStore` now creates a root-neutral Fig store that route loaders can
455
+ populate before a renderer exists. Server and client renderers adopt that exact
456
+ store, preserving one cache while attaching their lifecycle and scheduling.
457
+
458
+ The new TanStack Start runtime uses the store for route loading, server
459
+ rendering, Fig-owned document serialization, client deserialization, and
460
+ hydration. Route-managed head and script output maps through the Router adapter,
461
+ including Fig asset resources. The end-to-end contract verifies no initial
462
+ client refetch and exactly one reload after invalidation.
463
+
464
+ ### Share the text-separator protocol constant; small DOM cleanups
465
+
466
+ The `<!--,-->` text-separator comment the server writes between adjacent text
467
+ fibers was hardcoded independently by the server renderer and fig-dom's
468
+ hydration cursor. The comment data now lives in `@bgub/fig/internal` as
469
+ `TEXT_SEPARATOR_DATA`, next to the other streaming protocol constants, so the
470
+ two sides cannot drift. No wire change — the emitted markup is identical.
471
+
472
+ fig-dom also drops an unused internal `rootFor` helper, routes style clearing
473
+ through the shared `isEmptyPropValue` predicate, and simplifies a redundant
474
+ branch in select-value syncing. No behavior change.
475
+
476
+ ### Add typed View Transition scopes and abortable lifecycle events
477
+
478
+ `transition` and the `useTransition` starter now accept explicit transition
479
+ types, which Fig carries with the updates that reach each root and forwards to
480
+ the browser without leaking into unrelated commits.
481
+
482
+ `ViewTransition` adds one `onTransition(event, signal)` lifecycle callback for
483
+ enter, exit, share, and update phases. Events expose all participating surfaces
484
+ and the commit's transition types. Fig DOM's optional View Transition entry can
485
+ resolve those opaque surfaces into group, image-pair, old, and new pseudo
486
+ handles for animation and inspection; the signal aborts when the native
487
+ transition finishes.
488
+
489
+ ### Bound stalled view-transition waits
490
+
491
+ Transition-eligible commits and annotated streaming reveals now wait at most 60
492
+ seconds for a previous browser View Transition. If its completion promise never
493
+ settles, Fig releases the document mutex and proceeds with the latest work
494
+ instead of parking it forever.
495
+
496
+ ## @bgub/fig@0.1.0-alpha.0 (alpha)
497
+
498
+ ### Initial alpha release
499
+
500
+ First public alpha release of Fig.
501
+
1
502
  # Changelog
package/README.md CHANGED
@@ -32,7 +32,7 @@ function App() {
32
32
 
33
33
  return (
34
34
  <main>
35
- <button events={[on("click", () => setCount((value) => value + 1))]}>
35
+ <button mix={on("click", () => setCount((value) => value + 1))}>
36
36
  Count {count}
37
37
  </button>
38
38
  <Suspense fallback={<span>Loading</span>}>
@@ -52,6 +52,10 @@ createRoot(container).render(<App />);
52
52
 
53
53
  - Elements: `createElement`, `isValidElement`, `Fragment`, and the JSX
54
54
  runtime.
55
+ - Host behavior: `createMixin(type)` creates render-time prop composers for
56
+ intrinsic elements. A host accepts one descriptor or nested arrays through
57
+ `mix`; mixins may return props or more mixins but cannot replace `children`,
58
+ `key`, or `unsafeHTML`.
55
59
  - State and memoization: `useState`, `useDeferredValue`, `useMemo`, and
56
60
  `useCallback`.
57
61
  - Stable identifiers: `useId()` generates IDs that match server render and
@@ -91,7 +95,9 @@ createRoot(container).render(<App />);
91
95
  - `ErrorBoundary` catches render and Fig effect errors. Use `onError` for
92
96
  reporting and change the boundary key to reset sticky fallback state. Data
93
97
  resource load failures report failed keys on `info.dataResourceKeys`, so
94
- recovery flows can refresh or invalidate those keys before resetting.
98
+ recovery flows can refresh or invalidate those keys before resetting. This
99
+ includes a streamed payload hole that rejects after its owning entry has
100
+ already fulfilled.
95
101
  - `transition(callback)` and `useTransition()` mark updates that may preserve
96
102
  already-revealed Suspense content while new work is pending. Updates scheduled
97
103
  inside the callback run at transition priority, including updates after an
@@ -134,7 +140,8 @@ Use `@bgub/fig-dom` for browser rendering:
134
140
  - `hydrateRoot(container, children, options?)` hydrates server HTML and reports
135
141
  recoverable mismatches with `onRecoverableError`.
136
142
  - `createPortal(children, container, key?)` renders into external DOM targets.
137
- - DOM events use `events={[on("click", (event, signal) => ...)]}`.
143
+ - DOM events use `mix={on("click", (event, signal) => ...)}`; arrays compose
144
+ multiple or conditional mixins.
138
145
  - DOM node access uses `bind={(node, signal) => ...}`.
139
146
  - Raw trusted HTML uses `unsafeHTML="<p>trusted html</p>"`.
140
147
 
@@ -169,7 +176,7 @@ function Profile({ id }: { id: string }) {
169
176
  const user = readData(userResource, id);
170
177
 
171
178
  return (
172
- <button events={[on("click", () => void refreshData(userResource, id))]}>
179
+ <button mix={on("click", () => void refreshData(userResource, id))}>
173
180
  {user.name}
174
181
  </button>
175
182
  );
@@ -261,8 +268,24 @@ await save();
261
268
  data.run(() => refreshData(userResource, "one"));
262
269
  ```
263
270
 
264
- Client roots accept `dataPartition` and `initialData` options. The partition
265
- separates a store's internal keyspace without changing public resource keys.
271
+ Framework adapters can create and populate a root-neutral store before a
272
+ renderer exists, then let exactly one root adopt that same store:
273
+
274
+ ```ts
275
+ const dataStore = createDataStore();
276
+
277
+ await dataStore.ensureData(userResource, "one");
278
+
279
+ const root = createRoot(container, { dataStore });
280
+ root.data === dataStore; // true
281
+ ```
282
+
283
+ Adoption preserves entry identity and subscriptions. The adopting root owns
284
+ the store lifetime and disposes it when the root unmounts.
285
+
286
+ Client roots accept `dataStore`, `dataPartition`, and `initialData` options.
287
+ `dataStore` is exclusive with the other two options. The partition separates a
288
+ store's internal keyspace without changing public resource keys.
266
289
  Loaders receive only their resource arguments plus `{ signal }`; app services,
267
290
  request cookies, and auth state belong in the surrounding adapter or closure,
268
291
  not in the data store.
@@ -270,22 +293,30 @@ not in the data store.
270
293
  ### Server Values And Hydration
271
294
 
272
295
  Server renderers expose fulfilled data entries with `getData()`. Pass those
273
- entries to the client root as `initialData` to hydrate by key.
296
+ entries to the client root as `initialData` to hydrate by key, or use
297
+ `createDataStore()` when an adapter needs to populate and render through the
298
+ same live store.
274
299
 
275
300
  For server-only data, split the browser-safe key resource from the server
276
301
  loader:
277
302
 
278
303
  ```ts
304
+ // user-data.ts
279
305
  import { dataResource } from "@bgub/fig";
280
- import { serverDataResource } from "@bgub/fig/server";
281
306
 
282
307
  export const userKey = (id: string) => ["user", id];
283
308
 
284
309
  export const userResource = dataResource<[string], User>({
285
310
  key: userKey,
286
311
  });
312
+ ```
313
+
314
+ ```ts
315
+ // user-data.server.ts
316
+ import { dataResource } from "@bgub/fig";
317
+ import { userKey } from "./user-data.ts";
287
318
 
288
- export const userServerResource = serverDataResource({
319
+ export const userServerResource = dataResource({
289
320
  key: userKey,
290
321
  load: async (id, { signal }) => fetchUser(id, signal),
291
322
  });
@@ -297,18 +328,18 @@ resource has no client loader, `refreshData(userResource, id)` resolves with
297
328
  `{ status: "unsupported", reason: "no-client-loader" }`; revalidation needs a
298
329
  payload or framework refresh path.
299
330
 
300
- Browser bundles need the `figData` transform from `@bgub/fig-vite` so imports of
301
- `.server.ts(x)` modules become loader-free client stubs instead of bundling the
302
- server loader. Fig Start includes this transform automatically.
331
+ Place the loader-backed definition behind the host framework's server-only
332
+ module boundary. Browser code imports only the explicit loader-free resource;
333
+ Fig joins the two definitions by key during hydration without a bundler
334
+ transform. If only server-rendered code reads the value, the single
335
+ loader-backed definition is sufficient.
303
336
 
304
337
  Direct client refreshes of server data are a framework feature, not a core
305
- data one: an endpoint has to exist to serve them. Fig Start provides
306
- `remoteDataResource` (from `@bgub/fig-start/server`), whose browser import
307
- compiles into a plain `dataResource` with a loader that calls the framework
308
- data endpoint. Without a framework, define an isomorphic `dataResource` whose
309
- loader fetches an endpoint you own. Server route payload navigations do not
310
- make an extra data request either way — data read during the payload render
311
- streams in that same payload response.
338
+ data one: an endpoint has to exist to serve them. Define an isomorphic
339
+ `dataResource` whose loader calls the framework endpoint; with TanStack Start,
340
+ compose it with `createServerFn`. Server route payload navigations do not make
341
+ an extra data request either way data read during the payload render streams
342
+ in that same payload response.
312
343
 
313
344
  ### Activity, Errors, And DevTools
314
345