@fluojs/react 0.1.0 → 0.2.0

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 (59) hide show
  1. package/README.ko.md +629 -16
  2. package/README.md +644 -25
  3. package/dist/decorators.d.ts +3 -3
  4. package/dist/decorators.d.ts.map +1 -1
  5. package/dist/decorators.js +10 -4
  6. package/dist/diagnostics.d.ts +110 -0
  7. package/dist/diagnostics.d.ts.map +1 -0
  8. package/dist/diagnostics.js +180 -0
  9. package/dist/error-representation.d.ts +28 -0
  10. package/dist/error-representation.d.ts.map +1 -0
  11. package/dist/error-representation.js +28 -0
  12. package/dist/experimental/rsc-flight-response.d.ts.map +1 -1
  13. package/dist/experimental/rsc-flight-response.js +2 -6
  14. package/dist/experimental/server-functions-server.js +1 -1
  15. package/dist/experimental/server-functions-types.d.ts +1 -1
  16. package/dist/experimental/server-functions-types.d.ts.map +1 -1
  17. package/dist/index.d.ts +16 -4
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +7 -1
  20. package/dist/module.d.ts +8 -2
  21. package/dist/module.d.ts.map +1 -1
  22. package/dist/module.js +31 -8
  23. package/dist/page-catalog.d.ts +29 -0
  24. package/dist/page-catalog.d.ts.map +1 -0
  25. package/dist/page-catalog.js +46 -0
  26. package/dist/page-metadata.d.ts +58 -0
  27. package/dist/page-metadata.d.ts.map +1 -0
  28. package/dist/page-metadata.js +139 -0
  29. package/dist/page-renderer.d.ts +24 -0
  30. package/dist/page-renderer.d.ts.map +1 -0
  31. package/dist/page-renderer.js +18 -0
  32. package/dist/page-result.d.ts +16 -0
  33. package/dist/page-result.d.ts.map +1 -0
  34. package/dist/page-result.js +101 -0
  35. package/dist/render-diagnostics.d.ts +25 -0
  36. package/dist/render-diagnostics.d.ts.map +1 -0
  37. package/dist/render-diagnostics.js +94 -0
  38. package/dist/render-policy-metadata.d.ts +40 -0
  39. package/dist/render-policy-metadata.d.ts.map +1 -0
  40. package/dist/render-policy-metadata.js +136 -0
  41. package/dist/render-policy.d.ts +87 -0
  42. package/dist/render-policy.d.ts.map +1 -0
  43. package/dist/render-policy.js +203 -0
  44. package/dist/render-stream.d.ts +1 -1
  45. package/dist/render-stream.d.ts.map +1 -1
  46. package/dist/render-stream.js +80 -15
  47. package/dist/render.d.ts +12 -3
  48. package/dist/render.d.ts.map +1 -1
  49. package/dist/render.js +70 -76
  50. package/dist/server-entry.d.ts +13 -1
  51. package/dist/server-entry.d.ts.map +1 -1
  52. package/dist/server-entry.js +21 -11
  53. package/dist/typegen-artifact.d.ts +9 -0
  54. package/dist/typegen-artifact.d.ts.map +1 -0
  55. package/dist/typegen-artifact.js +116 -0
  56. package/dist/typegen.d.ts +41 -0
  57. package/dist/typegen.d.ts.map +1 -0
  58. package/dist/typegen.js +162 -0
  59. package/package.json +11 -6
package/README.md CHANGED
@@ -4,19 +4,31 @@
4
4
 
5
5
  Runtime-neutral React integration for fluo applications.
6
6
 
7
+ Preparing for the coordinated Node 24 release? Follow the [consumer migration guide](../../docs/getting-started/migrate-node24.md) before upgrading packages. React remains a `0.x` minor release, not a `1.0` graduation.
8
+
7
9
  ## Table of Contents
8
10
 
9
11
  - [Installation](#installation)
10
12
  - [When to Use](#when-to-use)
13
+ - [Zero-to-First-Page Workflow](#zero-to-first-page-workflow)
11
14
  - [Stable SSR Mental Model](#stable-ssr-mental-model)
15
+ - [React User Concept Translation](#react-user-concept-translation)
12
16
  - [Runtime and Peer Contract](#runtime-and-peer-contract)
13
17
  - [Phase Boundaries](#phase-boundaries)
14
18
  - [ReactModule Registration](#reactmodule-registration)
19
+ - [Application Page Renderer](#application-page-renderer)
20
+ - [Render Policy Decorators](#render-policy-decorators)
21
+ - [SSR Diagnostic Phases](#ssr-diagnostic-phases)
22
+ - [HTTP Error Documents](#http-error-documents)
15
23
  - [Router and Path Decorators](#router-and-path-decorators)
24
+ - [Bootstrap-Resolved Page Catalog](#bootstrap-resolved-page-catalog)
25
+ - [Path-Only Page Type Generation](#path-only-page-type-generation)
26
+ - [Consumer Testing Loop](#consumer-testing-loop)
16
27
  - [Web Streams SSR](#web-streams-ssr)
17
28
  - [Hydration Asset Contract](#hydration-asset-contract)
18
29
  - [Vite Asset Manifest Integration](#vite-asset-manifest-integration)
19
30
  - [Client Navigation Runtime](#client-navigation-runtime)
31
+ - [Native Form Mutations](#native-form-mutations)
20
32
  - [Experimental RSC Prototype](#experimental-rsc-prototype)
21
33
  - [Experimental Server Functions](#experimental-server-functions)
22
34
  - [RSC Graduation Policy](#rsc-graduation-policy)
@@ -27,9 +39,9 @@ Runtime-neutral React integration for fluo applications.
27
39
 
28
40
  ## Installation
29
41
 
30
- The first public release target for this package is `0.1.0`. The manifest starts at
31
- `0.0.0` so Changesets can publish the initial `0.1.0` version through the canonical
32
- release workflow.
42
+ The package has completed its initial `0.1.0` release and no longer uses the `0.0.0` bootstrap
43
+ placeholder. Future versions are recorded through committed Changesets and published only through
44
+ the canonical release workflow.
33
45
 
34
46
  When the package is published, install it with React and React DOM as peers:
35
47
 
@@ -46,6 +58,36 @@ places React routers into ordinary module controller metadata, and `@Router(...)
46
58
  DTO binding, versioning, guards, interceptors, headers, route validation, matching, and dispatch
47
59
  continue to use the HTTP runtime contracts.
48
60
 
61
+ ## Zero-to-First-Page Workflow
62
+
63
+ Use the official generated composition when you want streamed SSR, hydration, and Vite assets
64
+ without assembling every stable seam before editing the first page:
65
+
66
+ ```bash
67
+ fluo new my-react-app --starter react-vite-ssr
68
+ cd my-react-app
69
+ pnpm dev
70
+ ```
71
+
72
+ Open `/products/sku-42?preview=true` and edit `src/page.tsx`. The explicit `@Router(...)` / `@Path(...)`
73
+ handler remains in `src/app.ts` and returns `createElement(ProductPage)`, one `ReactElement`, so `@fluojs/http` still
74
+ owns matching, DTO binding and validation, middleware, guards, interceptors, request scopes, and
75
+ not-found behavior.
76
+
77
+ The generated wiring moves the incidental first-edit work into discoverable application files:
78
+
79
+ - `src/entry-server.tsx` owns the replaceable `ReactPageRenderer`, parses the application-loaded
80
+ manifest with `@fluojs/react/vite`, and returns `ReactServerEntry` with
81
+ `createReactServerEntry(...)`.
82
+ - `src/react-app.tsx` gives server rendering and hydration one document,
83
+ `ReactClientRouterProvider`, route snapshot, and stylesheet composition.
84
+ - `src/entry-client.tsx` hydrates that same tree, while `src/main.ts` and `src/load-manifest.ts` keep
85
+ filesystem loading and actionable build-output failures at the Node.js application boundary.
86
+
87
+ Advanced applications can replace the generated renderer or pass explicit hydration options to
88
+ `createReactServerEntry(...)`; the stable APIs below remain available. No runtime-neutral root export
89
+ imports Node.js, Vite, or browser code, and the starter does not add another route matcher.
90
+
49
91
  ## Stable SSR Mental Model
50
92
 
51
93
  The stable `0.1.0` model is HTTP-first React SSR. `@Router(...)` and `@Path(...)` are lexical
@@ -58,8 +100,18 @@ validation, guards, interceptors, headers, module middleware, request scopes, an
58
100
  This package is therefore **not** a Next.js App Router clone, React Server Components framework,
59
101
  TanStack route tree, Angular `Routes[]` table, file-route scanner, or primary React-owned
60
102
  `routes: []` configuration model. Treat React routers as page-shaped HTTP handlers. Route discovery
61
- and dispatch stay in the existing fluo module/controller pipeline, and page handlers return either
62
- ordinary values or `createReactServerEntry(...)` when they want streamed HTML.
103
+ and dispatch stay in the existing fluo module/controller pipeline. Page handlers may return ordinary
104
+ HTTP values, return one valid `ReactElement` for the configured application page renderer, or return
105
+ `createReactServerEntry(...)` explicitly when they need per-route SSR options.
106
+
107
+ ## React User Concept Translation
108
+
109
+ If your starting vocabulary is pages, layouts, loaders, actions, navigation, pending UI, metadata,
110
+ hydration, or build assets, use the concise
111
+ [React concepts in fluo guide](../../docs/guides/react-user-concepts.md). It separates shipped
112
+ equivalents, intentionally different behavior, experimental surfaces, and unsupported concepts,
113
+ then follows one runnable path from `@Router(...)`/`@Path(...)` through the application renderer,
114
+ Vite assets, hydration, client navigation, and a native form mutation.
63
115
 
64
116
  ## Runtime and Peer Contract
65
117
 
@@ -133,8 +185,276 @@ sources, so `createHandlerMapping(...)` and `Dispatcher` still detect duplicate
133
185
  module-level middleware, create request scopes, run guards and interceptors, and resolve route
134
186
  versioning.
135
187
 
188
+ ## Application Page Renderer
189
+
190
+ Register one application-owned `renderPage` callback when every page should share the same document
191
+ shell, providers, hydration assets, route snapshot wiring, and recoverable-render policy. The callback
192
+ implements `ReactPageRenderer`, receives one `ReactElement` plus the active `ReactRenderContext`, and
193
+ must return `ReactServerEntry`. `ReactModule.forRoot(...)` registers and exports it through
194
+ `REACT_PAGE_RENDERER`, so routers can inject the callback without creating a second response path.
195
+
196
+ ```tsx
197
+ import { Module } from '@fluojs/core';
198
+ import {
199
+ Path,
200
+ ReactModule,
201
+ Router,
202
+ createReactServerEntry,
203
+ type ReactPageRenderer,
204
+ type ReactRenderContext,
205
+ } from '@fluojs/react';
206
+ import {
207
+ ReactClientRouterProvider,
208
+ createReactRouteSnapshot,
209
+ } from '@fluojs/react/client';
210
+
211
+ const hydrationOptions = {
212
+ assetMap: { 'client.js': '/assets/client.123.js' },
213
+ bootstrapModules: ['/assets/client.123.js'],
214
+ } as const;
215
+
216
+ const renderPage: ReactPageRenderer = (page, context) => {
217
+ const initialSnapshot = createReactRouteSnapshot({
218
+ params: context.request.params,
219
+ url: context.request.url,
220
+ });
221
+
222
+ return createReactServerEntry(
223
+ <html lang="en">
224
+ <body>
225
+ <ReactClientRouterProvider initialSnapshot={initialSnapshot}>
226
+ {page}
227
+ </ReactClientRouterProvider>
228
+ </body>
229
+ </html>,
230
+ hydrationOptions,
231
+ );
232
+ };
233
+
234
+ @Router('/products')
235
+ class ProductRouter {
236
+ @Path('/:id')
237
+ show(_input: undefined, context: ReactRenderContext) {
238
+ return <main>Product {context.request.params.id}</main>;
239
+ }
240
+ }
241
+
242
+ @Module({
243
+ imports: [ReactModule.forRoot({ controllers: [ProductRouter], renderPage })],
244
+ })
245
+ class AppModule {}
246
+ ```
247
+
248
+ The root package does not import `@fluojs/react/client` or `@fluojs/react/vite`. Applications may
249
+ compose client helpers explicitly as above and may close over hydration options produced from an
250
+ already-loaded manifest by `createReactViteAssetManifest(...)`. The renderer does not discover
251
+ manifests, generate bundles, match routes, or bypass DTO binding, middleware, guards, interceptors,
252
+ request scopes, abort propagation, shell failure handling, or recoverable streaming behavior.
253
+
254
+ When `renderPage` is configured, the final value from an `@Path(...)` request is passed to that
255
+ callback only when React `isValidElement(...)` recognizes it as one `ReactElement`. Plain objects,
256
+ strings, arrays, `null`, and other ordinary values keep the normal HTTP response path. Explicit
257
+ `ReactServerEntry` values remain unchanged and do not pass through the configured callback. The
258
+ `REACT_PAGE_RENDERER` token remains available when another application provider needs to invoke the
259
+ same renderer explicitly.
260
+
261
+ Returning a `ReactElement` without configuring `renderPage` fails before response commit with
262
+ `ReactSsrDiagnosticError`, code `react-ssr-missing-page-renderer`, and an actionable message. Configure
263
+ `ReactModule.forRoot({ ..., renderPage })` or return `createReactServerEntry(...)` explicitly.
264
+
265
+ ## Render Policy Decorators
266
+
267
+ Use `@PageLayout(...)`, `@SuspenseFallback(...)`, and `@PageMetadata(...)` when one React router
268
+ class or `@Path(...)` method needs route-local composition while the application keeps one
269
+ `renderPage` callback. Layout and fallback decorators accept component references, not pre-created
270
+ JSX elements. `PageMetadata` accepts a synchronous request-aware factory. Their metadata is resolved
271
+ after HTTP matching and passed only to the application page renderer; it never changes paths,
272
+ matching precedence, params, errors, or not-found behavior.
273
+
274
+ ```tsx
275
+ import { Suspense, createElement, type ReactElement } from 'react';
276
+ import {
277
+ PageLayout,
278
+ PageMetadata,
279
+ Path,
280
+ ReactModule,
281
+ Router,
282
+ SuspenseFallback,
283
+ createReactPageMetadataElements,
284
+ createReactServerEntry,
285
+ resolveReactPageMetadata,
286
+ type ReactPageLayoutProps,
287
+ type ReactPageRenderer,
288
+ type ReactSuspenseFallbackProps,
289
+ } from '@fluojs/react';
290
+
291
+ function ShopLayout({ children, context }: ReactPageLayoutProps) {
292
+ return <section data-request-path={context.request.path}>{children}</section>;
293
+ }
294
+
295
+ function ProductFallback({ context }: ReactSuspenseFallbackProps) {
296
+ return <p>Loading {context.request.params.id}…</p>;
297
+ }
298
+
299
+ const renderPage: ReactPageRenderer = (page, context, policies) => {
300
+ const metadata = resolveReactPageMetadata(policies, context);
301
+ const pageBoundary = policies.suspenseFallback === undefined
302
+ ? page
303
+ : createElement(Suspense, {
304
+ fallback: createElement(policies.suspenseFallback, { context }),
305
+ }, page);
306
+ const composedPage = policies.layouts.reduceRight<ReactElement>(
307
+ (children, Layout) => createElement(Layout, { children, context }),
308
+ pageBoundary,
309
+ );
310
+
311
+ return createReactServerEntry(
312
+ <html lang="en">
313
+ <head>{createReactPageMetadataElements(metadata)}</head>
314
+ <body>{composedPage}</body>
315
+ </html>,
316
+ );
317
+ };
318
+
319
+ @PageLayout(ShopLayout)
320
+ @Router('/products')
321
+ class ProductRouter {
322
+ @PageMetadata(({ request }) => ({
323
+ meta: [{ content: `Product ${request.params.id}`, name: 'description' }],
324
+ title: `Product ${request.params.id}`,
325
+ }))
326
+ @SuspenseFallback(ProductFallback)
327
+ @Path('/:id')
328
+ show() {
329
+ return <ProductPage />;
330
+ }
331
+ }
332
+
333
+ ReactModule.forRoot({ controllers: [ProductRouter], renderPage });
334
+ ```
335
+
336
+ Resolved layouts are ordered outermost to innermost: base class, derived class, base method, then
337
+ derived method. Layouts compose across inheritance. The nearest fallback wins, so a method fallback
338
+ overrides a class fallback and a derived declaration overrides a base declaration. A class or method
339
+ site may declare each policy kind once; same-site duplicates fail during bootstrap.
340
+
341
+ Metadata factories use the same broad-to-specific class and method order. The nearest defined title
342
+ wins. Later `<meta>` descriptors replace earlier descriptors with the same `name` or `property`
343
+ identity, and later `<link>` descriptors replace the same exact `rel` plus `href` pair. Unrelated
344
+ descriptors preserve declaration order. `resolveReactPageMetadata(...)` returns a frozen bounded
345
+ snapshot, and `createReactPageMetadataElements(...)` creates ordinary React title, meta, and link
346
+ elements so React owns text and attribute escaping. Neither helper discovers Vite assets, creates
347
+ script tags, accepts raw HTML, or serializes arbitrary inline data.
348
+
349
+ `ReactRenderContext` includes the active request-scope `container`. Policy components receive that
350
+ context as an explicit prop, but fluo does not instantiate React components through DI or resolve
351
+ tokens on their behalf. A policy without `renderPage`, a class policy outside `@Router(...)`, or a
352
+ method policy outside `@Path(...)` fails bootstrap with `ReactRenderPolicyConfigurationError` and a
353
+ stable value from `REACT_RENDER_POLICY_DIAGNOSTIC_CODES`.
354
+
355
+ Metadata factories receive `ReactPageMetadataContext`: the active `request`, optional `requestId`,
356
+ and the same request-scope `container` identity as the handler and renderer. The context intentionally
357
+ omits `response`, so metadata cannot change status, headers, or commit timing. fluo does not instantiate
358
+ factories through DI or resolve tokens for them. Load asynchronous data through the matched HTTP
359
+ handler before page composition; metadata factories remain synchronous.
360
+
361
+ `@SuspenseFallback(...)` supplies an ordinary React Suspense fallback for descendants that suspend
362
+ during SSR. It does not observe handler `await`, effects, event handlers, native form submission, or
363
+ full-document/client navigation pending state. HTTP pipeline errors, not-found/404 responses,
364
+ pre-commit shell failures, request aborts, and post-shell recoverable errors keep their separate
365
+ existing phases. The complete ordering, inheritance, duplicate, and phase decision is recorded in
366
+ the [React render policy decorator decision](../../docs/architecture/react-render-policy-decorators.md).
367
+ The follow-up [React page render policy decision](../../docs/architecture/react-page-render-policies.md)
368
+ accepts metadata but rejects a generic error-presentation decorator and page-local not-found
369
+ presentation. HTTP pipeline failures, shell failures, post-shell recoverable errors, request aborts,
370
+ client React errors, unmatched routes, and handler-thrown `NotFoundException` outcomes retain their
371
+ existing owners. No placeholder error or not-found export is provided.
372
+
373
+ ## SSR Diagnostic Phases
374
+
375
+ Register `onDiagnostic` on `ReactModule.forRoot(...)` when application logging or diagnostics tooling
376
+ needs one stable event shape across HTTP and React rendering boundaries:
377
+
378
+ ```tsx
379
+ ReactModule.forRoot({
380
+ controllers: [ProductRouter],
381
+ renderPage,
382
+ onDiagnostic(diagnostic) {
383
+ applicationDiagnostics.report(diagnostic);
384
+ },
385
+ });
386
+ ```
387
+
388
+ Each `ReactSsrDiagnostic` contains `code`, `phase`, the original `error`, the active `request`, and an
389
+ optional `requestId`. The callback is observational: an exception thrown by it does not replace the
390
+ request outcome. Stable phases and codes are:
391
+
392
+ | Phase | Code | Boundary |
393
+ | --- | --- | --- |
394
+ | `http-pipeline` | `react-ssr-http-pipeline-failure` | DTO binding, middleware, guards, interceptors, handlers, and other failures before React shell rendering. |
395
+ | `http-pipeline` | `react-ssr-missing-page-renderer` | A valid `ReactElement` reached an `@Path(...)` response without `renderPage` configuration. |
396
+ | `pre-commit-shell` | `react-ssr-pre-commit-shell-failure` | React shell creation or buffered stream collection failed before response commit. The original thrown `Error` identity is preserved. |
397
+ | `request-abort` | `react-ssr-request-abort` | The request signal or abort probe stopped React rendering; existing no-commit or committed-stream abort behavior remains unchanged. |
398
+ | `post-shell-recoverable` | `react-ssr-post-shell-recoverable-error` | React reported a recoverable render error after a shell could be written; status and headers are not rewritten. |
399
+
400
+ `REACT_SSR_DIAGNOSTIC_PHASES` and `REACT_SSR_DIAGNOSTIC_CODES` expose these values for comparisons.
401
+ The existing `onRecoverableError` entry hook also receives `code` and `phase` in
402
+ `ReactRecoverableErrorContext`.
403
+
404
+ ## HTTP Error Documents
405
+
406
+ Use `createReactErrorRepresentationProvider(...)` when the application wants React to produce the
407
+ optional HTML bytes selected by the HTTP error representation contract:
408
+
409
+ ```tsx
410
+ import { bootstrapApplication } from '@fluojs/runtime';
411
+ import {
412
+ createReactErrorRepresentationProvider,
413
+ createReactServerEntry,
414
+ } from '@fluojs/react';
415
+
416
+ const html = createReactErrorRepresentationProvider({
417
+ renderDocument({ json }) {
418
+ return createReactServerEntry(
419
+ <html lang="en">
420
+ <body><main>{json.error.status}: {json.error.message}</main></body>
421
+ </html>,
422
+ );
423
+ },
424
+ });
425
+
426
+ const app = await bootstrapApplication({
427
+ errorRepresentation: { html },
428
+ rootModule: AppModule,
429
+ });
430
+ ```
431
+
432
+ `@fluojs/http` performs route-miss conversion and `Accept` negotiation before this adapter runs.
433
+ The callback receives `HttpErrorRepresentationContext`, including canonical JSON and the active
434
+ request-scope container. It does not receive response mutation authority. The React entry is fully
435
+ buffered before HTTP applies status, `Content-Type`, `Vary`, `HEAD` suppression, and commit.
436
+
437
+ React escapes `json.error.message` in the example because it is a JSX text child. The application
438
+ still owns the complete trusted-HTML document contract: do not move request-derived or error-derived
439
+ content into `dangerouslySetInnerHTML` unless it has passed an application-approved sanitizer or
440
+ equivalent trusted-content boundary.
441
+
442
+ `ReactServerEntry.status` and `ReactServerEntry.headers` are ignored on this path. The helper never
443
+ calls `ReactPageRenderer`, `PageLayout`, `PageMetadata`, `SuspenseFallback`, or the page catalog, and
444
+ it never matches a URL. A render failure propagates to HTTP's one-shot canonical JSON fallback;
445
+ matched-page pre-commit shell failures remain a separate React SSR diagnostic phase and do not invoke
446
+ the provider. See the
447
+ [HTTP error representation decision](../../docs/architecture/http-error-representations.md).
448
+
136
449
  ## Router and Path Decorators
137
450
 
451
+ `@Path()` and `@Path(undefined)` use `''`, so `@Router('/cats')` serves GET `/cats`
452
+ and `@Router()` serves GET `/`. An explicit `'/'` has the same effective route but a
453
+ different raw path and never escapes the prefix. Omitted options leave the React metadata's
454
+ `options` property absent; `Path(undefined, options)` still records explicit options.
455
+ HTTP, React, and inspection metadata keep their existing contracts, including route conflicts
456
+ and grammar rejection. `PageLayout`, `SuspenseFallback`, and `PageMetadata` still require values.
457
+
138
458
  `@Router(basePath)` marks a class as a React router and writes HTTP controller metadata equivalent
139
459
  to `@Controller(basePath)`. It also stores React router marker metadata readable through
140
460
  `getReactRouterMetadata(...)` for diagnostics and future rendering integration.
@@ -176,12 +496,155 @@ The [HTTP catch-all route grammar decision](../../docs/architecture/http-catch-a
176
496
  defers wildcard adoption. React does not add its own syntax: page handlers should keep explicit
177
497
  server routes, and any future catch-all must first become an approved `@fluojs/http` contract.
178
498
 
499
+ ## Bootstrap-Resolved Page Catalog
500
+
501
+ Use `createReactPageCatalog(...)` after HTTP handler compilation when tooling needs a read-only list
502
+ of React pages. Pass the authoritative `HandlerDescriptor[]` from `createHandlerMapping(...)` or
503
+ `app.dispatcher.describeRoutes()`. The result contains only handlers marked by both `@Router(...)`
504
+ and `@Path(...)`, in descriptor registration order.
505
+
506
+ ```ts
507
+ import { createHandlerMapping } from '@fluojs/http';
508
+ import { createReactPageCatalog } from '@fluojs/react';
509
+
510
+ const mapping = createHandlerMapping([{ controllerToken: ProductRouter }]);
511
+ const pages = createReactPageCatalog(mapping.descriptors);
512
+
513
+ console.log(pages[0]);
514
+ // {
515
+ // kind: 'react-page', method: 'GET', path: '/v2/products/:productId',
516
+ // version: '2', params: ['productId'], router: 'ProductRouter', handler: 'show'
517
+ // }
518
+ ```
519
+
520
+ `getReactRouterMetadata(...)` and `getReactPathMetadata(...)` expose static values authored at the
521
+ decorator sites, such as the router base path, relative page path, and React options. The catalog is
522
+ different: its method, path, version, params, module, router, and handler fields are projected from
523
+ bootstrap-resolved HTTP descriptors, so controller composition and URI versioning are already
524
+ reflected. The returned array, entries, and `params` arrays are frozen defensive snapshots.
525
+
526
+ Catalog construction is observational only. It never calls or replaces the HTTP matcher and has no
527
+ role in route conflict detection, request dispatch, not-found behavior, or non-React handlers. It
528
+ does not create a route tree, client manifest, relative-route model, prefetch layer, or cache.
529
+
530
+ ## Path-Only Page Type Generation
531
+
532
+ Use `fluo typegen` to bootstrap an application module, project its compiled React page catalog, and
533
+ write application-owned TypeScript route helpers. The output comes from the dedicated
534
+ `@fluojs/react/typegen` subpath; the runtime-neutral package root does not export the generator.
535
+
536
+ ```bash
537
+ fluo typegen ./src/app.ts --output ./src/generated/react-pages.ts
538
+ fluo typegen ./src/admin.ts --export AdminModule --output ./src/generated/admin-pages.ts
539
+ fluo typegen ./src/app.ts --output ./src/generated/react-pages.ts --check
540
+ fluo typegen ./src/app.ts --output ./src/generated/react-pages.ts --watch
541
+ ```
542
+
543
+ The command creates the output directory when needed, overwrites stale output deterministically,
544
+ and reports `CREATE`, `UPDATE`, or `UNCHANGED`. Atomic replacement prevents partial publication and
545
+ byte-identical runs do not rewrite the file. `--check` compares exact generated bytes without writes
546
+ and classifies a structurally corrupted current-version body as malformed rather than stale.
547
+ `--watch` installs its application-module watcher before startup generation, reruns once for changes
548
+ observed during that generation before reporting ready, and then serializes coalesced changes while
549
+ preserving the last valid artifact after generation failures. Native `.js` and `.mjs` dependencies
550
+ are evaluated from a current module graph for each generation. Every run closes its bootstrapped
551
+ application after reading `app.dispatcher.describeRoutes()`. Import the generated artifact from
552
+ application code:
553
+
554
+ ```ts
555
+ import {
556
+ reactPageRoutes,
557
+ type ReactPageParams,
558
+ type ReactPagePath,
559
+ type ReactPageRouteId,
560
+ } from './generated/react-pages.js';
561
+
562
+ const productHref = reactPageRoutes['GET /products/:productId ProductRouter show'].href({
563
+ productId: 'desk/chair',
564
+ });
565
+ // /products/desk%2Fchair
566
+ ```
567
+
568
+ Generated route ids use the stable catalog `id`. Static builders accept no parameters; dynamic
569
+ builders require every catalog path parameter and encode each value with `encodeURIComponent(...)`.
570
+ The artifact also exports `ReactPagePathById`, `ReactPageParamsById`, `ReactPagePath<RouteId>`,
571
+ `ReactPageParams<RouteId>`, `ReactPageRoute`, `ReactPageLinkProps`, and `ReactPageNavigator`.
572
+
573
+ Each generated route also binds the same href builder to declarative `Link` and programmatic
574
+ `push`/`replace` authoring. `link(...)` returns an ordinary `{ href: string }` object for the existing
575
+ real-anchor `Link`; `push(router, ...)` and `replace(router, ...)` resolve the same absolute href before
576
+ calling the existing `ReactRouter` string overload. Route identity and exact params therefore remain
577
+ visible at the callsite without a second runtime route table:
578
+
579
+ ```tsx
580
+ import { Link, useRouter } from '@fluojs/react/client';
581
+ import { reactPageRoutes } from './generated/react-pages.js';
582
+
583
+ const productsRoute = reactPageRoutes['GET /products ProductRouter index'];
584
+ const productRoute = reactPageRoutes['GET /products/:productId ProductRouter show'];
585
+
586
+ function ProductNavigation({ productId }: { readonly productId: string }) {
587
+ const router = useRouter();
588
+
589
+ return (
590
+ <nav>
591
+ <Link {...productsRoute.link()}>Products</Link>
592
+ <Link {...productRoute.link({ productId })}>Current product</Link>
593
+ <button type="button" onClick={() => productRoute.push(router, { productId })}>Open</button>
594
+ <button type="button" onClick={() => productRoute.replace(router, { productId })}>Replace</button>
595
+ </nav>
596
+ );
597
+ }
598
+ ```
599
+
600
+ Static `link`, `push`, and `replace` methods accept no params; parameterized methods require every
601
+ path param and reject missing or extra keys. The existing generated `href(...)` builders,
602
+ `<Link href={stringOrUrl}>`, and `router.push(...)` / `router.replace(...)` string or `URL` calls remain
603
+ supported. Generated methods only produce or pass absolute href strings into those existing APIs, so
604
+ real-anchor fallback, full-document HTTP navigation, matching, DTO binding, guards, interceptors, and
605
+ not-found behavior keep their current owners.
606
+
607
+ This contract is deliberately path-only. It does not generate query strings, fragments, relative
608
+ routes, optional parameters, or a client route tree. Typegen rejects every catalog entry with a
609
+ `version` because the compiled catalog does not identify whether version selection came from the
610
+ URI, a header, media type, or a custom strategy; emitting one absolute href would otherwise claim a
611
+ URL contract that may be false.
612
+
613
+ ## Consumer Testing Loop
614
+
615
+ Use existing React, HTTP, testing, TypeScript, and browser seams as one layered consumer loop. No
616
+ React-specific testing helper is needed because the repeated setup is already isolated by ordinary
617
+ fixtures and the real dispatcher remains the subject of request tests.
618
+
619
+ 1. **Render-policy units**: call pure policy and metadata helpers directly. Cover broad-to-specific
620
+ layout order, nearest fallback or metadata precedence, and configuration diagnostics without
621
+ bootstrapping a browser.
622
+ 2. **Request dispatch**: build the authored module with `createTestApp({ rootModule })`, request the
623
+ explicit `@Path(...)` route through `app.request(...).send()`, and close the app in `finally`.
624
+ Include a direct `ReactElement` return and the missing-renderer diagnostic so the real HTTP
625
+ dispatcher, request scope, response ownership, and application renderer stay covered.
626
+ 3. **Generated-route compilation**: generate from the bootstrapped `HandlerDescriptor` catalog, run
627
+ TypeScript over positive and negative consumer fixtures, and run `fluo typegen ... --check` in CI.
628
+ Positive fixtures use known route ids and complete params; negative fixtures cover unknown ids,
629
+ missing or extra params, and stale, malformed, missing, or unsupported-version artifacts.
630
+ 4. **Hydration**: render the same application component on the server and hydrate it with
631
+ `hydrateRoot(...)`. The positive fixture expects interaction with no diagnostics. A deliberately
632
+ mismatched fixture captures `onRecoverableError` so mismatch reporting itself cannot regress.
633
+ 5. **Production browser and progressive enhancement**: run Playwright against built assets, fail on
634
+ console/page errors, and use a separate `javaScriptEnabled: false` context to submit the native
635
+ form through the ordinary `POST` → `303` → `GET` flow.
636
+
637
+ Executable references are `src/render-policy.test.ts`, `src/direct-page-return.test.ts`,
638
+ `../cli/src/commands/typegen-navigation.test.ts`, `../../examples/react-vite-ssr/src/app.test.ts`,
639
+ `../../examples/react-vite-ssr/src/hydration.test.ts`, and
640
+ `../../examples/react-vite-ssr/tests/production-hydration.spec.ts`.
641
+
179
642
  ## Web Streams SSR
180
643
 
181
- Return `createReactServerEntry(...)` from a React page handler to stream HTML through the existing
182
- fluo HTTP dispatcher. Guards, interceptors, module middleware, route headers, `@HttpCode(...)`, DTO
183
- binding, request scopes, and duplicate route detection all run before `renderReactResponse(...)`
184
- finalizes the HTML response.
644
+ Return one `ReactElement` through a configured application page renderer, or return
645
+ `createReactServerEntry(...)` explicitly, to stream HTML through the existing fluo HTTP dispatcher.
646
+ Guards, interceptors, module middleware, route headers, `@HttpCode(...)`, DTO binding, request scopes,
647
+ and duplicate route detection all run before `renderReactResponse(...)` finalizes the HTML response.
185
648
 
186
649
  ```tsx
187
650
  import { HttpCode, RequestDto, FromPath } from '@fluojs/http';
@@ -216,6 +679,9 @@ when an adapter provides one, and throws shell render failures before response b
216
679
  Recoverable Suspense errors are reported through `onRecoverableError` and do not rewrite an already
217
680
  committed status. Call `renderReactResponse(entry, requestContext)` directly only when a custom
218
681
  handler needs to finalize the response itself instead of returning the entry to the dispatcher.
682
+ On streaming hosts, an early response-sink close or a failed `write(...)` / `waitForDrain()` cancels
683
+ the unfinished React reader exactly once and releases its lock. Sink failures remain the reported
684
+ failure rather than being replaced by reader-cancellation cleanup.
219
685
 
220
686
  ## Hydration Asset Contract
221
687
 
@@ -415,6 +881,85 @@ matches an explicit `@Path(...)`/HTTP route or returns its normal not-found resp
415
881
  deployment-level document rewrite may be configured separately, but it does not create a React route
416
882
  grammar or change server DTO validation.
417
883
 
884
+ ## Native Form Mutations
885
+
886
+ Use a native HTML form when a React page needs a mutation that remains functional before hydration or
887
+ with client JavaScript disabled. Submit to an ordinary `@Post(...)` route rather than creating a
888
+ React-owned action transport. The runnable `examples/react-vite-ssr/` slice uses
889
+ `multipart/form-data`, which lets the browser provide the multipart boundary while the existing
890
+ adapter and `@RequestDto(...)` body-binding path materialize the fields.
891
+
892
+ ```tsx
893
+ function ProductForm({ name, sku }: { readonly name: string; readonly sku: string }) {
894
+ return (
895
+ <form action={`/products/${encodeURIComponent(sku)}`} encType="multipart/form-data" method="post">
896
+ <label htmlFor="product-name">Product name</label>
897
+ <input defaultValue={name} id="product-name" minLength={3} name="name" required />
898
+ <button type="submit">Save product</button>
899
+ </form>
900
+ );
901
+ }
902
+ ```
903
+
904
+ The target remains a normal HTTP handler. DTO binding and validation run once at the request
905
+ boundary; module middleware, guards, interceptors, request-scoped providers, observers, and adapter
906
+ response writing keep their ordinary ordering and ownership.
907
+
908
+ ```ts
909
+ import {
910
+ FromBody,
911
+ FromPath,
912
+ Post,
913
+ RequestDto,
914
+ type RequestContext,
915
+ UseGuards,
916
+ UseInterceptors,
917
+ } from '@fluojs/http';
918
+ import { Router } from '@fluojs/react';
919
+ import { IsString, MinLength } from '@fluojs/validation';
920
+
921
+ class RenameProductRequest {
922
+ @MinLength(3, {
923
+ code: 'PRODUCT_NAME_TOO_SHORT',
924
+ message: 'Product name must contain at least 3 characters.',
925
+ })
926
+ @IsString()
927
+ @FromBody('name')
928
+ name = '';
929
+
930
+ @IsString()
931
+ @FromPath('sku')
932
+ sku = '';
933
+ }
934
+
935
+ @Router('/products')
936
+ class ProductRouter {
937
+ @Post('/:sku')
938
+ @RequestDto(RenameProductRequest)
939
+ @UseGuards(EditorGuard)
940
+ @UseInterceptors(MutationAuditInterceptor)
941
+ rename(input: RenameProductRequest, context: RequestContext) {
942
+ productCatalog.rename(input.sku, input.name);
943
+ context.response.redirect(303, `/products/${encodeURIComponent(input.sku)}?updated=true`);
944
+ }
945
+ }
946
+ ```
947
+
948
+ An invalid submission keeps the canonical HTTP `400` validation envelope. Its `details` entries
949
+ contain the safe field, source, code, and application-authored validation message; they do not expose
950
+ the submitted value, stack, or internal exception. A successful mutation uses `303 See Other` so the
951
+ browser follows with `GET`, and that destination is matched again by the ordinary HTTP dispatcher.
952
+ Authorization and CSRF policy remain application responsibilities; use the same session/cookie,
953
+ guard, and middleware policies as non-React routes.
954
+
955
+ This recipe is intentionally different from React Router actions/fetchers, Astro Actions, and
956
+ Next.js Server Actions: fluo does not compile a function reference, own route matching, revalidate a
957
+ loader/client cache, or replace the document response. It is also separate from the experimental
958
+ fluo Server Functions transport. No stable submit-state helper is added in this phase because the
959
+ native form already supplies the complete fallback and `@fluojs/react/client` does not own mutation
960
+ routes or cache invalidation. Applications may add local pending UI after hydration only when the
961
+ real form action and native submission remain intact.
962
+
418
963
  ## Experimental RSC Prototype
419
964
 
420
965
  > **Experimental contract:** `@fluojs/react/experimental/rsc` can change before an explicit
@@ -483,6 +1028,10 @@ metadata, middleware, guards, interceptors, request scopes, errors, and adapter
483
1028
  the helper adds the fixed `text/x-component; charset=utf-8` content type and does not create a
484
1029
  parallel router.
485
1030
 
1031
+ Streamed Flight payloads use the same lifecycle guarantee as stable SSR: an early response-sink
1032
+ close or a failed write/drain cancels the unfinished reader exactly once, releases its lock, and
1033
+ preserves the sink failure for the ordinary HTTP error pipeline.
1034
+
486
1035
  ```ts
487
1036
  import { Controller, Get } from '@fluojs/http';
488
1037
  import { createReactFlightResponse } from '@fluojs/react/experimental/rsc';
@@ -635,10 +1184,16 @@ This package currently does **not** provide:
635
1184
  - a Next.js App Router, TanStack route tree, Angular `Routes[]`, file-route scanner, or React-owned
636
1185
  `routes: []` table
637
1186
  - automatic client bundle generation
1187
+ - href generation for versioned React pages; path-only typegen rejects versioned catalog entries
1188
+ until the catalog can distinguish URI versioning from non-path version strategies
638
1189
  - filesystem scanning or automatic manifest file discovery; pass an already-loaded manifest value to
639
1190
  `@fluojs/react/vite`
640
1191
  - automatic serialization of arbitrary data into `bootstrapScriptContent`
641
1192
  - Node-only `react-dom/server` pipeable stream root APIs such as `renderToPipeableStream(...)`
1193
+ - Next.js-style segment `loading`, `error`, `notFound`, template, or layout ancestry semantics;
1194
+ `@SuspenseFallback(...)` is SSR-descendant Suspense metadata only
1195
+ - a generic page error-presentation policy or page-local not-found renderer; optional HTML is a
1196
+ global/application HTTP representation and does not add page ancestry or matching semantics
642
1197
 
643
1198
  ## Public API
644
1199
 
@@ -646,8 +1201,48 @@ This package currently does **not** provide:
646
1201
  - `Path` — method decorator that writes HTTP `GET` route metadata plus React render metadata.
647
1202
  - `getReactRouterMetadata` — reads React router marker metadata from a router class.
648
1203
  - `getReactPathMetadata` — reads React render metadata from a router method.
1204
+ - `createReactPageCatalog` — creates a frozen, read-only React page catalog from authoritative
1205
+ compiled HTTP descriptors without participating in matching or dispatch.
1206
+ - `ReactPageCatalogEntry` — type-only bootstrap-resolved page descriptor with effective HTTP
1207
+ method/path/version/params plus the originating router and handler.
1208
+ - `@fluojs/react/typegen` subpath — `generateReactPageTypes(...)`,
1209
+ `inspectReactPageTypeArtifact(...)`, `REACT_PAGE_TYPEGEN_ARTIFACT_VERSION`,
1210
+ `ReactPageTypeArtifactInspection`, `ReactPageTypegenError`, `REACT_PAGE_TYPEGEN_ERROR_CODES`, and
1211
+ `ReactPageTypegenErrorCode` for deterministic path-only declarations, versioned artifact checks,
1212
+ absolute href builders, route-bound `Link` props, and typed `push`/`replace` methods without
1213
+ widening the package root or adding a runtime route table.
649
1214
  - `ReactModule` — runtime-neutral module facade whose `forRoot(...)` registers React routers through
650
1215
  the existing fluo module/controller metadata path.
1216
+ - `REACT_PAGE_RENDERER` — dependency-injection token for the application page renderer registered by
1217
+ `ReactModule.forRoot({ renderPage })`.
1218
+ - `ReactPageRenderer` — type-only application callback that composes a `ReactElement` and active
1219
+ `ReactRenderContext` plus resolved `ReactRenderPolicies` into an existing `ReactServerEntry`.
1220
+ - `PageLayout`, `SuspenseFallback`, and `PageMetadata` — class-or-method decorators that record
1221
+ renderer-only component references or one synchronous metadata factory.
1222
+ - `resolveReactPageMetadata` — composes ordered metadata factories for the active request without
1223
+ exposing response mutation authority.
1224
+ - `createReactPageMetadataElements` — creates ordinary escaped React title, meta, and link elements
1225
+ from one resolved metadata snapshot.
1226
+ - `getReactRenderPolicies` — resolves inherited class/method policies in outer-to-inner order.
1227
+ - `REACT_RENDER_POLICY_DIAGNOSTIC_CODES` and `ReactRenderPolicyConfigurationError` — stable
1228
+ bootstrap diagnostics for duplicate, invalid-target, invalid-reference, and missing-renderer policy
1229
+ declarations.
1230
+ - `ReactPageLayout`, `ReactPageLayoutProps`, `ReactSuspenseFallback`,
1231
+ `ReactSuspenseFallbackProps`, `ReactRenderPolicies`, and `ReactRenderPolicyDiagnosticCode` —
1232
+ type-only render-policy composition contracts.
1233
+ - `ReactPageMetadata`, `ReactPageMetadataFactory`, `ReactPageMetadataContext`, `ReactPageMeta`, and
1234
+ `ReactPageLink` — type-only bounded page metadata contracts.
1235
+ - `REACT_SSR_DIAGNOSTIC_PHASES` and `REACT_SSR_DIAGNOSTIC_CODES` — stable machine-readable SSR
1236
+ lifecycle phase and diagnostic code constants.
1237
+ - `ReactSsrDiagnosticError` — typed pre-commit configuration/render failure with stable `code` and
1238
+ `phase` metadata.
1239
+ - `ReactSsrDiagnostic`, `ReactSsrDiagnosticCode`, `ReactSsrDiagnosticErrorOptions`,
1240
+ `ReactSsrDiagnosticHandler`, and `ReactSsrDiagnosticPhase` — type-only contracts for application
1241
+ diagnostics tooling.
1242
+ - `createReactErrorRepresentationProvider` — adapts an application React error document renderer to
1243
+ the HTTP-owned HTML provider seam while buffering before commit.
1244
+ - `ReactErrorDocumentRenderer` and `ReactErrorRepresentationProviderOptions` — type-only contracts
1245
+ for the application callback, optional availability constraint, and renderer override.
651
1246
  - `createReactServerEntry` — creates a runtime-neutral React server entry returned by page handlers
652
1247
  for Web Streams SSR.
653
1248
  - `renderReactResponse` — renders one React server entry to a fluo HTML response with lazy
@@ -655,7 +1250,7 @@ This package currently does **not** provide:
655
1250
  - `ReactAssetMap`, `ReactBootstrapAsset`, and `ReactBootstrapScriptDescriptor` — type-only contracts
656
1251
  for build-produced asset maps and React DOM bootstrap script/module entries.
657
1252
  - `ReactModuleOptions` — options accepted by `ReactModule.forRoot(...)`, including `controllers`,
658
- `imports`, `providers`, `exports`, and module-level `middleware`.
1253
+ `imports`, `providers`, `exports`, module-level `middleware`, and optional `renderPage` registration.
659
1254
  - `ReactServerEntry`, `ReactServerEntryOptions`, `ReactServerEntryHeaders`,
660
1255
  `ReactRecoverableErrorHandler`, `ReactRecoverableErrorContext`, `ReactRenderContext`,
661
1256
  `ReactReadableStream`, `ReactReadableStreamRenderer`, `ReactReadableStreamRenderOptions`, and
@@ -664,28 +1259,43 @@ This package currently does **not** provide:
664
1259
  - `ReactRouterMetadata`, `ReactPathMetadata`, `ReactPathOptions` — type-only metadata contracts for
665
1260
  diagnostics and future rendering integration.
666
1261
  - `@fluojs/react/vite` subpath — `createReactViteAssetManifest(...)` plus
667
- `ReactViteBuildManifest`, `ReactViteBuildManifestChunk`, `ReactViteManifestOptions`,
668
- `ReactViteManifestDiagnostic`, `ReactViteAssetManifest`, `ReactViteHydrationOptions`,
669
- `ReactViteJavaScriptAssets`, `ReactViteBootstrapData`, and `ReactViteResolvedEntry` for parsing
670
- Vite manifests into the stable hydration asset contract without importing Vite from the root.
1262
+ `ReactViteBuildManifest`, `ReactViteBuildManifestChunk`, `ReactViteManifestEntries`,
1263
+ `ReactViteManifestOptions`, `ReactViteManifestDiagnostic`, `ReactViteManifestDiagnosticCode`,
1264
+ `ReactViteAssetManifest`, `ReactViteAssetManifestResult`, `ReactViteHydrationOptions`,
1265
+ `ReactViteJavaScriptAssets`, `ReactViteBootstrapData`, and `ReactViteResolvedEntry` for parsing Vite
1266
+ manifests into the stable hydration asset contract without importing Vite from the root.
671
1267
  - `@fluojs/react/client` subpath — `Link`, `ReactClientRouterProvider`,
672
- `createReactRouteSnapshot(...)`, `useRouter()`, `usePathname()`, `useParams()`,
673
- `useSearchParams()`, `useNavigation()`, and `useRouterState()` for progressive HTTP-first browser
674
- navigation without widening the root package or adding a client route grammar.
675
- - `@fluojs/react/experimental/rsc` subpath `inspectReactRscEnvironment(...)`,
676
- `createReactRscManifest(...)`, `createReactFlightResponse(...)`, exact-version and Flight content
677
- type constants, diagnostics, client-reference/server-module mapping types, signed
678
- `createReactServerFunctionRegistry(...)`, explicit `createReactServerFunctionClient(...)`, stable
679
- Server Function error codes, and related transport types without any root or stable-client
680
- re-export.
1268
+ `ReactClientNavigationError`, `ReactClientRouterContextError`, `createReactRouteSnapshot(...)`,
1269
+ `useRouter()`, `usePathname()`, `useParams()`, `useSearchParams()`, `useNavigation()`, and
1270
+ `useRouterState()` for progressive HTTP-first browser navigation without widening the root package
1271
+ or adding a client route grammar. Type exports are `LinkProps`, `ReactClientNavigationErrorCode`,
1272
+ `ReactClientRouterProviderProps`, `ReactNavigationSnapshot`, `ReactNavigationStatus`,
1273
+ `ReactNavigationType`, `ReactReadonlySearchParams`, `ReactRouteSnapshot`,
1274
+ `ReactRouteSnapshotInput`, and `ReactRouter`.
1275
+ - `@fluojs/react/experimental/rsc` subpath runtime exports are `REACT_RSC_DIAGNOSTIC_CODES`,
1276
+ `REACT_RSC_FLIGHT_CONTENT_TYPE`, `REACT_RSC_SUPPORTED_VERSION`,
1277
+ `REACT_SERVER_FUNCTION_ERROR_CODES`, `REACT_SERVER_FUNCTION_REQUEST_HEADER`,
1278
+ `ReactServerFunctionClientError`, `ReactServerFunctionConfigurationError`,
1279
+ `createReactFlightResponse(...)`, `createReactRscManifest(...)`,
1280
+ `createReactServerFunctionClient(...)`, `createReactServerFunctionRegistry(...)`, and
1281
+ `inspectReactRscEnvironment(...)`. Type exports are `ReactFlightPayload`, `ReactFlightResponse`,
1282
+ `ReactFlightResponseHeaders`, `ReactFlightResponseOptions`, `ReactRscBuildCapabilities`,
1283
+ `ReactRscClientReference`, `ReactRscClientReferenceManifest`, `ReactRscDiagnostic`,
1284
+ `ReactRscDiagnosticCode`, `ReactRscEnvironmentOptions`, `ReactRscManifest`,
1285
+ `ReactRscManifestInput`, `ReactRscManifestResult`, `ReactRscRuntimeCapabilities`,
1286
+ `ReactRscServerClientModuleMap`, `ReactRscSupportResult`, `ReactServerFunctionClient`,
1287
+ `ReactServerFunctionClientOptions`, `ReactServerFunctionErrorCode`, `ReactServerFunctionFetch`,
1288
+ `ReactServerFunctionHandler`, `ReactServerFunctionReference`, `ReactServerFunctionRegistry`,
1289
+ `ReactServerFunctionRegistryOptions`, `ReactServerFunctionResponse`, and
1290
+ `ReactServerFunctionValue`. None are re-exported from the root or stable client subpath.
681
1291
 
682
1292
  ## Related Packages
683
1293
 
684
1294
  - `@fluojs/core`: Provides the standard `@Module` decorator used by the scaffold.
685
1295
  - `@fluojs/http`: Provides the controller, route, DTO, guard, interceptor, header, and version
686
1296
  metadata pipeline reused by `@Router(...)` and `@Path(...)`.
687
- - `@fluojs/runtime`: Future React integration work is expected to compose with runtime bootstrap
688
- contracts without widening the root import boundary.
1297
+ - `@fluojs/runtime`: Registers the optional HTTP error representation together with the existing
1298
+ application bootstrap contracts without widening the React root import boundary.
689
1299
  - `@fluojs/vite`: Owns Vite's TC39 decorator transform boundary. It does not parse React hydration
690
1300
  manifests; use `@fluojs/react/vite` for React server/client asset mapping.
691
1301
  - Application-selected Flight renderer: Encodes RSC payloads and consumes renderer-specific build
@@ -702,6 +1312,7 @@ This package currently does **not** provide:
702
1312
  - `packages/react/src/experimental/rsc.test.ts`
703
1313
  - `packages/react/src/experimental/rsc-diagnostics.test.ts`
704
1314
  - `packages/react/src/experimental/rsc-flight.test.ts`
1315
+ - `packages/react/src/experimental/rsc-flight-stream-lifecycle.test.ts`
705
1316
  - `packages/react/src/experimental/rsc-manifest.test.ts`
706
1317
  - `packages/react/src/experimental/server-functions-server.ts`
707
1318
  - `packages/react/src/experimental/server-functions-client.ts`
@@ -712,7 +1323,15 @@ This package currently does **not** provide:
712
1323
  - `packages/react/src/decorators.ts`
713
1324
  - `packages/react/src/server-entry.ts`
714
1325
  - `packages/react/src/render.ts`
1326
+ - `packages/react/src/error-representation.ts`
1327
+ - `packages/react/src/error-representation.test.ts`
715
1328
  - `packages/react/src/module.ts`
1329
+ - `packages/react/src/page-renderer.ts`
1330
+ - `packages/react/src/render-policy.ts`
1331
+ - `packages/react/src/render-policy-metadata.ts`
1332
+ - `packages/react/src/render-policy.test.ts`
1333
+ - `packages/react/src/page-metadata.ts`
1334
+ - `packages/react/src/page-metadata.test.ts`
716
1335
  - `packages/react/src/render.test.ts`
717
1336
  - `packages/react/src/dispatcher-ssr.test.ts`
718
1337
  - `packages/react/src/hydration-assets.test.ts`