redweb 0.16.2 → 0.16.4

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 (71) hide show
  1. package/CHANGELOG.md +40 -29
  2. package/README.md +293 -291
  3. package/contract.d.ts +11 -11
  4. package/docs/API_EXAMPLES_VERIFICATION.md +22 -22
  5. package/docs/APPLICATION.md +96 -94
  6. package/docs/CLI.md +122 -122
  7. package/docs/CLIENT_DEVELOPMENT.md +9 -9
  8. package/docs/CONNECTED_CLIENTS_VERIFICATION.md +65 -65
  9. package/docs/DEVELOPMENT.md +81 -81
  10. package/docs/GETTING_STARTED.md +78 -78
  11. package/docs/LIVE_HTML.md +555 -478
  12. package/docs/MIGRATION.md +28 -28
  13. package/docs/RELEASE_TRUST.md +88 -88
  14. package/docs/RUNTIME_DIAGNOSTICS.md +78 -78
  15. package/docs/SOCKET_CONTRACTS.md +42 -42
  16. package/docs/SOCKET_PAGES.md +172 -172
  17. package/docs/SOCKET_PAGE_RELEASE_PREPARATION.md +120 -120
  18. package/docs/SOCKET_PAGE_VERIFICATION.md +85 -85
  19. package/docs/generated.json +2290 -2286
  20. package/docs/guides/chatroom.md +1 -1
  21. package/docs/guides/jsx-without-react.md +14 -14
  22. package/docs/reference.json +1333 -1329
  23. package/docs/releases/0.15.0.json +2217 -2217
  24. package/docs/releases/0.16.0.json +2217 -2217
  25. package/docs/releases/0.16.1.json +2286 -2286
  26. package/docs/releases/0.16.2.json +2286 -2286
  27. package/docs/releases/0.16.3.json +2286 -0
  28. package/docs/releases/0.16.4.json +2290 -0
  29. package/docs/snippets/components.tsx +24 -24
  30. package/docs/snippets/counter.tsx +16 -16
  31. package/docs/snippets/room-access.tsx +11 -11
  32. package/docs/snippets/site.css +2 -2
  33. package/docs/snippets/site.tsx +22 -22
  34. package/docs/topics.json +3 -3
  35. package/index.d.ts +96 -58
  36. package/index.js +14 -8
  37. package/package.json +12 -8
  38. package/recipes/foundation/README.md +7 -7
  39. package/recipes/foundation/app.test.cjs +15 -15
  40. package/recipes/foundation/app.tsx +12 -12
  41. package/recipes/shared/README.md +7 -7
  42. package/src/Application.js +4 -4
  43. package/src/access/failure-codes.json +4 -0
  44. package/src/cli/ProjectInitializer.js +1 -1
  45. package/src/cli/arguments.js +21 -21
  46. package/src/cli/run.js +12 -12
  47. package/src/cli/templates.js +40 -40
  48. package/src/docs/Documentation.js +29 -29
  49. package/src/htmx/CodeHighlight.js +98 -0
  50. package/src/htmx/Html.js +4 -3
  51. package/src/htmx/Jsx.js +2 -2
  52. package/src/htmx/LiveHtmlServer.js +5 -1
  53. package/src/htmx/LivePage.js +5 -1
  54. package/src/htmx/LiveResource.js +96 -0
  55. package/src/htmx/PageManager.js +126 -13
  56. package/src/htmx/PageSocketRoute.js +132 -132
  57. package/src/htmx/PageTaskLane.js +39 -0
  58. package/src/htmx/ReactiveRenderer.js +8 -8
  59. package/src/htmx/SocketAction.js +19 -19
  60. package/src/htmx/TemplateRenderer.js +1 -1
  61. package/src/htmx/index.js +4 -2
  62. package/src/htmx/metadata.js +117 -6
  63. package/src/ws/BaseHandler.js +6 -6
  64. package/src/ws/ConnectedClients.js +207 -207
  65. package/src/ws/HandlerGuard.js +4 -4
  66. package/src/ws/RoomRegistry.js +4 -4
  67. package/src/ws/RouteRuntime.js +11 -11
  68. package/src/ws/SocketAction.js +16 -16
  69. package/src/ws/SocketContract.js +3 -3
  70. package/src/ws/SocketRoute.js +7 -7
  71. package/styles/code-highlight.css +16 -0
package/contract.d.ts CHANGED
@@ -15,17 +15,17 @@ export interface SocketSchema<Input = unknown, Output = Input> {
15
15
  export type SocketSchemas = Readonly<Record<string, SocketSchema>>;
16
16
  export type ContractInput<Schema extends SocketSchema> = NonNullable<Schema['~standard']['types']>['input'];
17
17
  export type ContractOutput<Schema extends SocketSchema> = Awaited<NonNullable<Schema['~standard']['types']>['output']>;
18
- export type ContractMessage<Schemas extends SocketSchemas> = {
18
+ export type ContractMessage<Schemas extends SocketSchemas> = {
19
19
  [Type in keyof Schemas & string]: ProtocolEnvelope<ContractOutput<Schemas[Type]>> & { type: Type };
20
- }[keyof Schemas & string];
21
-
22
- declare const socketActionBrand: unique symbol;
23
- export interface SocketAction { readonly [socketActionBrand]: true; }
24
- export interface SocketHandler<Input> {
25
- new (): BaseHandler;
26
- /** Bind a JSON payload to a server-rendered rw-click/rw-submit control. */
27
- with(payload: Input): SocketAction;
28
- }
20
+ }[keyof Schemas & string];
21
+
22
+ declare const socketActionBrand: unique symbol;
23
+ export interface SocketAction { readonly [socketActionBrand]: true; }
24
+ export interface SocketHandler<Input> {
25
+ new (): BaseHandler;
26
+ /** Bind a JSON payload to a server-rendered rw-click/rw-submit control. */
27
+ with(payload: Input): SocketAction;
28
+ }
29
29
 
30
30
  export interface ContractClient<Schemas extends SocketSchemas> {
31
31
  envelope<Type extends keyof Schemas & string>(type: Type, payload: ContractInput<Schemas[Type]>, metadata?: ProtocolMetadata):
@@ -44,7 +44,7 @@ export interface SocketContract<Schemas extends SocketSchemas> {
44
44
  handler<Type extends keyof Schemas & string>(type: Type, callback: (
45
45
  socket: RedWebSocket, payload: ContractOutput<Schemas[Type]>,
46
46
  message: ProtocolEnvelope<ContractOutput<Schemas[Type]>> & { type: Type },
47
- ) => unknown): SocketHandler<ContractInput<Schemas[Type]>>;
47
+ ) => unknown): SocketHandler<ContractInput<Schemas[Type]>>;
48
48
  client(socket: SendableSocket): ContractClient<Schemas>;
49
49
  send<Type extends keyof Schemas & string>(socket: RedWebSocket, type: Type, payload: ContractInput<Schemas[Type]>, metadata?: ProtocolMetadata): Promise<boolean>;
50
50
  }
@@ -1,22 +1,22 @@
1
- # API example refresh verification
2
-
3
- Verified locally on Windows, 2026-09-02, for the 0.16.1 documentation patch.
4
-
5
- ## Scope
6
-
7
- - Reviewed the current 31 API sections and 11 showcase examples. Prefer concise `defineApp`, plain decorated classes and TSX for application examples; retain explicitly labeled lower-level APIs.
8
- - Source reuse keeps the counter, component and multi-page/CSS examples identical between generated documentation and acceptance fixtures.
9
- - No runtime API change. Documentation generation and the executable room example were changed.
10
- - Prior release snapshots, including 0.16.0, remain untouched. The website remains pinned to published 0.16.0 until manual npm publication and subsequent site synchronization.
11
-
12
- ## Passed checks
13
-
14
- - `tests/unit/documentation.unit.test.js` and `tests/unit/api-examples.unit.test.js`: 13 tests; 100% statements, branches, functions and lines for `src/docs/Documentation.js`. All current API/showcase examples parse as TSX; partial API patterns are not claimed to be independently runnable or fully type-checked applications.
15
- - `tests/integration/api-examples.integration.test.js`: six real headed-Chromium scenarios, counter/components/two-page site under both standard and legacy decorators. Exact source is compiled; only a fixture ownership export is appended. Verifies server updates, a second counter tab, component isolation, CSS and navigation without mocks.
16
- - Focused `tests/integration/documentation.integration.test.js` and `tests/unit/room-verifier.unit.test.js` gate (`shared page/room|actual generator|room`): 10 tests passed, two unrelated tests not selected. Includes actual HTTP/WebSocket room authorization and cleanup under both decorator modes.
17
- - `npm run pretest`: generated examples, protocol declarations, documentation freshness and all three consumer type configurations pass.
18
- - `npm run prepublishOnly`: release channel and immutable 0.16.1 snapshot match the package version.
19
- - `npm pack --dry-run --json`: packaging/prepack checks pass.
20
- - Senior critic reviewed modular source reuse, tests and API semantics. Corrected all findings, including both lifecycle prose and option-table distinctions between application and lower-level shutdown budgets.
21
-
22
- No soak, fixed-duration observation gate, full runtime suite or deployment was run for this documentation-only patch. Coverage above is scoped to the changed documentation generator, not a new whole-project coverage claim.
1
+ # API example refresh verification
2
+
3
+ Verified locally on Windows, 2026-09-02, for the 0.16.1 documentation patch.
4
+
5
+ ## Scope
6
+
7
+ - Reviewed the current 31 API sections and 11 showcase examples. Prefer concise `defineApp`, plain decorated classes and TSX for application examples; retain explicitly labeled lower-level APIs.
8
+ - Source reuse keeps the counter, component and multi-page/CSS examples identical between generated documentation and acceptance fixtures.
9
+ - No runtime API change. Documentation generation and the executable room example were changed.
10
+ - Prior release snapshots, including 0.16.0, remain untouched. The website remains pinned to published 0.16.0 until manual npm publication and subsequent site synchronization.
11
+
12
+ ## Passed checks
13
+
14
+ - `tests/unit/documentation.unit.test.js` and `tests/unit/api-examples.unit.test.js`: 13 tests; 100% statements, branches, functions and lines for `src/docs/Documentation.js`. All current API/showcase examples parse as TSX; partial API patterns are not claimed to be independently runnable or fully type-checked applications.
15
+ - `tests/integration/api-examples.integration.test.js`: six real headed-Chromium scenarios, counter/components/two-page site under both standard and legacy decorators. Exact source is compiled; only a fixture ownership export is appended. Verifies server updates, a second counter tab, component isolation, CSS and navigation without mocks.
16
+ - Focused `tests/integration/documentation.integration.test.js` and `tests/unit/room-verifier.unit.test.js` gate (`shared page/room|actual generator|room`): 10 tests passed, two unrelated tests not selected. Includes actual HTTP/WebSocket room authorization and cleanup under both decorator modes.
17
+ - `npm run pretest`: generated examples, protocol declarations, documentation freshness and all three consumer type configurations pass.
18
+ - `npm run prepublishOnly`: release channel and immutable 0.16.1 snapshot match the package version.
19
+ - `npm pack --dry-run --json`: packaging/prepack checks pass.
20
+ - Senior critic reviewed modular source reuse, tests and API semantics. Corrected all findings, including both lifecycle prose and option-table distinctions between application and lower-level shutdown budgets.
21
+
22
+ No soak, fixed-duration observation gate, full runtime suite or deployment was run for this documentation-only patch. Coverage above is scoped to the changed documentation generator, not a new whole-project coverage claim.
@@ -1,94 +1,96 @@
1
- # One application, one listener
2
-
3
- `defineApp()` describes the application. `await app.run()` initializes it and opens one port for HTTP pages and WebSocket routes. You do not create an HTTP server and then pass it to a second socket server.
4
-
5
- Added in Redweb 0.14.0. Earlier releases do not export `defineApp`.
6
-
7
- ## The entry point
8
-
9
- In a Redweb TypeScript project, the application can be as small as:
10
-
11
- ```tsx
12
- import { defineApp, page, action, state } from 'redweb';
13
-
14
- @page('/', { shared: true })
15
- class HomePage {
16
- @state() count = 0;
17
- @action() increment() { this.count += 1; }
18
- render() {
19
- return <main><h1>Hello</h1><button rw-click="increment">Count {this.count}</button></main>;
20
- }
21
- }
22
-
23
- @page('/about', { live: false })
24
- class AboutPage {
25
- render() { return <main><h1>About this app</h1></main>; }
26
- }
27
-
28
- const app = defineApp({ pages: [HomePage, AboutPage] });
29
- app.run();
30
- ```
31
-
32
- `run()` returns a Promise. Await it when startup completion matters; an ESM entry point can use top-level `await app.run()`. In CommonJS, the direct call above also works; add rejection handling when the application needs a custom failure policy. A module imported by tests should export its definition separately or guard startup with `require.main === module`. Constructing a definition never opens a port or installs signal handlers.
33
-
34
- ## Add socket routes and services
35
-
36
- The same definition accepts all three registration arrays:
37
-
38
- ```ts
39
- const app = defineApp({
40
- pages: [HomePage, AboutPage],
41
- sockets: [MatchRoute, ChatRoute],
42
- services: [GameSimulation],
43
- port: 8181,
44
- });
45
-
46
- await app.run();
47
- ```
48
-
49
- These names refer to your application's classes. A page's decorator chooses its HTTP path. A `SocketRoute` chooses its WebSocket path, such as `/match`; its handlers dispatch `join`, `move`, and `resume` by message `type`. Live-page connections and custom socket routes share one upgrade listener. Do not register a custom socket route at the live-page socket path.
50
-
51
- In five-year-old terms: the app has one front door. Pages are ordinary visits, sockets are ongoing conversations, and services are the staff who prepare the building before the door opens and clean up after it closes.
52
-
53
- `services` are application-wide lifecycle classes, not HTTP endpoint descriptors or route-specific `SocketService` classes:
54
-
55
- ```ts
56
- import type { ApplicationContext, ApplicationService } from 'redweb';
57
-
58
- class GameSimulation implements ApplicationService {
59
- private timer?: ReturnType<typeof setInterval>;
60
- private ticks = 0;
61
-
62
- onInit(app: ApplicationContext, signal: AbortSignal) {
63
- signal.throwIfAborted();
64
- app.app.get('/health', (_request, response) => response.json({ ticks: this.ticks }));
65
- this.timer = setInterval(() => { this.ticks++; }, 1000);
66
- }
67
-
68
- onShutdown() { clearInterval(this.timer); }
69
- }
70
- ```
71
-
72
- Keep constructors inert. Acquire resources in `onInit(app, signal)` and release them in `onShutdown()`. Initializers run in registration order before the port opens; cleanup runs in reverse order, including the service whose initialization failed. Asynchronous methods are supported. Pass the abort signal to cancelable work and make cleanup safe after partial initialization. A deadline cannot stop arbitrary code that ignores cancellation or blocks the event loop.
73
-
74
- Use `httpServices` for the existing HTTP endpoint descriptor array. Keep route-specific `SocketService` registrations on their `SocketRoute`. For an existing Express application, pass it as `server`; Redweb still creates and owns the underlying Node listener. The low-level server classes remain available for applications that explicitly manage their own listener ownership.
75
-
76
- ## Lifecycle contract
77
-
78
- - Definitions are deferred. `app.server`, `app.app`, `app.http`, and `app.sockets` are initially `null`. Await `run()` before accessing their runtime values; its returned value has non-null HTTP members in TypeScript.
79
- - `pages`, `sockets`, and `services` are optional. HTTP-only, socket-only, and non-live-page applications use the same entry point. Non-live pages need no WebSocket server unless custom socket routes were supplied.
80
- - Repeated `run()` calls share the pending or successful startup promise while the application is running. `shutdown()` is idempotent. A stopped application cannot restart; define a new application instead.
81
- - Startup failure rolls back resources before rejection, subject to the shutdown deadline. Cleanup failures are retained alongside the original startup error, not reported as success.
82
- - `startupTimeoutMs` and `shutdownTimeoutMs` default to 5000. Each is one total application budget, not a fresh full timeout for every service. Shutdown cancels pending startup, closes admission, attempts page/socket cleanup, closes owned connections, and releases services.
83
- - `signals: true` is the default. Signal handlers are installed when `run()` begins, so SIGINT/SIGTERM also cancel pending initialization. Unexpected listener closure and listener errors trigger owned shutdown. Repeated signals do not bypass active cleanup. Failed process-owned cleanup sets a failure exit status and retains a deadline for leaked handles. Explicit `shutdown()` rejects on cleanup failure but never forcibly exits its caller.
84
- - Tests and embedded applications should set `signals: false` and call `shutdown()` in their own cleanup. Use `port: 0` for an OS-assigned test port. TLS uses the existing `ssl` key/certificate options and shares one HTTPS/WSS listener.
85
-
86
- ## Boundaries
87
-
88
- This is application composition, not dependency injection, a distributed worker manager, or durable storage. It does not automatically inject services into page constructors. `shared: true` shares in-process state across visitors, not across server processes or restarts. Static file export remains the separate `exportStatic()` API; a non-live page served over HTTP is not a static export.
89
-
90
- `app.revoke(principal)` revokes matching live-page sessions and returns their count; it returns zero when no live-page server has been created. `app.inspect()` exposes opt-in development metadata and otherwise returns `null`. These preserve the same live-page policies as `start()`.
91
-
92
- `app.options` is the copied definition. An independent test instance can use `defineApp({ ...app.options, port: 0, signals: false })`, followed by `await run()` and owned cleanup. This creates new page/service instances, but does not clone an Express application or objects deliberately captured by class closures. The chat module exports a default `ChatroomPage`; its optional `createChatroomPage()` factory creates isolated rooms for separate apps or tests.
93
-
94
- For rendering and CSS see [Live HTML](LIVE_HTML.md). For message validation and handler classes see [socket contracts](SOCKET_CONTRACTS.md). For deployment and persistence boundaries see [operations](MULTIPLAYER_OPERATIONS.md).
1
+ # One application, one listener
2
+
3
+ `defineApp()` describes the application. `await app.run()` initializes it and opens one port for HTTP pages and WebSocket routes. You do not create an HTTP server and then pass it to a second socket server.
4
+
5
+ Added in Redweb 0.14.0. Earlier releases do not export `defineApp`.
6
+
7
+ ## The entry point
8
+
9
+ In a Redweb TypeScript project, the application can be as small as:
10
+
11
+ ```tsx
12
+ import { defineApp, page, action, state } from 'redweb';
13
+
14
+ @page('/', { shared: true })
15
+ class HomePage {
16
+ @state() count = 0;
17
+ @action() increment() { this.count += 1; }
18
+ render() {
19
+ return <main><h1>Hello</h1><button rw-click="increment">Count {this.count}</button></main>;
20
+ }
21
+ }
22
+
23
+ @page('/about', { live: false })
24
+ class AboutPage {
25
+ render() { return <main><h1>About this app</h1></main>; }
26
+ }
27
+
28
+ const app = defineApp({ pages: [HomePage, AboutPage] });
29
+ app.run();
30
+ ```
31
+
32
+ `run()` returns a Promise. Await it when startup completion matters; an ESM entry point can use top-level `await app.run()`. In CommonJS, the direct call above also works; add rejection handling when the application needs a custom failure policy. A module imported by tests should export its definition separately or guard startup with `require.main === module`. Constructing a definition never opens a port or installs signal handlers.
33
+
34
+ ## Add socket routes and services
35
+
36
+ The same definition accepts all three registration arrays:
37
+
38
+ ```ts
39
+ const app = defineApp({
40
+ pages: [HomePage, AboutPage],
41
+ sockets: [MatchRoute, ChatRoute],
42
+ services: [GameSimulation],
43
+ port: 8181,
44
+ });
45
+
46
+ await app.run();
47
+ ```
48
+
49
+ These names refer to your application's classes. A page's decorator chooses its HTTP path. A `SocketRoute` chooses its WebSocket path, such as `/match`; its handlers dispatch `join`, `move`, and `resume` by message `type`. Live-page connections and custom socket routes share one upgrade listener. Do not register a custom socket route at the live-page socket path.
50
+
51
+ `providers` is different from `services`: it is a named map of already-created application objects that a page explicitly requests through `@inject('name')`. Use it for a store or other dependency shared across page instances. Redweb does not construct, persist, or authorize those objects for you.
52
+
53
+ In five-year-old terms: the app has one front door. Pages are ordinary visits, sockets are ongoing conversations, and services are the staff who prepare the building before the door opens and clean up after it closes.
54
+
55
+ `services` are application-wide lifecycle classes, not HTTP endpoint descriptors or route-specific `SocketService` classes:
56
+
57
+ ```ts
58
+ import type { ApplicationContext, ApplicationService } from 'redweb';
59
+
60
+ class GameSimulation implements ApplicationService {
61
+ private timer?: ReturnType<typeof setInterval>;
62
+ private ticks = 0;
63
+
64
+ onInit(app: ApplicationContext, signal: AbortSignal) {
65
+ signal.throwIfAborted();
66
+ app.app.get('/health', (_request, response) => response.json({ ticks: this.ticks }));
67
+ this.timer = setInterval(() => { this.ticks++; }, 1000);
68
+ }
69
+
70
+ onShutdown() { clearInterval(this.timer); }
71
+ }
72
+ ```
73
+
74
+ Keep constructors inert. Acquire resources in `onInit(app, signal)` and release them in `onShutdown()`. Initializers run in registration order before the port opens; cleanup runs in reverse order, including the service whose initialization failed. Asynchronous methods are supported. Pass the abort signal to cancelable work and make cleanup safe after partial initialization. A deadline cannot stop arbitrary code that ignores cancellation or blocks the event loop.
75
+
76
+ Use `httpServices` for the existing HTTP endpoint descriptor array. Keep route-specific `SocketService` registrations on their `SocketRoute`. For an existing Express application, pass it as `server`; Redweb still creates and owns the underlying Node listener. The low-level server classes remain available for applications that explicitly manage their own listener ownership.
77
+
78
+ ## Lifecycle contract
79
+
80
+ - Definitions are deferred. `app.server`, `app.app`, `app.http`, and `app.sockets` are initially `null`. Await `run()` before accessing their runtime values; its returned value has non-null HTTP members in TypeScript.
81
+ - `pages`, `sockets`, and `services` are optional. HTTP-only, socket-only, and non-live-page applications use the same entry point. Non-live pages need no WebSocket server unless custom socket routes were supplied.
82
+ - Repeated `run()` calls share the pending or successful startup promise while the application is running. `shutdown()` is idempotent. A stopped application cannot restart; define a new application instead.
83
+ - Startup failure rolls back resources before rejection, subject to the shutdown deadline. Cleanup failures are retained alongside the original startup error, not reported as success.
84
+ - `startupTimeoutMs` and `shutdownTimeoutMs` default to 5000. Each is one total application budget, not a fresh full timeout for every service. Shutdown cancels pending startup, closes admission, attempts page/socket cleanup, closes owned connections, and releases services.
85
+ - `signals: true` is the default. Signal handlers are installed when `run()` begins, so SIGINT/SIGTERM also cancel pending initialization. Unexpected listener closure and listener errors trigger owned shutdown. Repeated signals do not bypass active cleanup. Failed process-owned cleanup sets a failure exit status and retains a deadline for leaked handles. Explicit `shutdown()` rejects on cleanup failure but never forcibly exits its caller.
86
+ - Tests and embedded applications should set `signals: false` and call `shutdown()` in their own cleanup. Use `port: 0` for an OS-assigned test port. TLS uses the existing `ssl` key/certificate options and shares one HTTPS/WSS listener.
87
+
88
+ ## Boundaries
89
+
90
+ This is application composition, not a general-purpose dependency-injection container, distributed worker manager, or durable storage. `services` are lifecycle classes and are not injected into page constructors. For an explicit application-owned dependency, register an instance in `providers` and declare a matching `@inject('name')` field on the page; see the [complete Live HTML example](LIVE_HTML.md#putting-uploads-providers-and-resources-together). `shared: true` shares in-process state across visitors, not across server processes or restarts. Static file export remains the separate `exportStatic()` API; a non-live page served over HTTP is not a static export.
91
+
92
+ `app.revoke(principal)` revokes matching live-page sessions and returns their count; it returns zero when no live-page server has been created. `app.inspect()` exposes opt-in development metadata and otherwise returns `null`. These preserve the same live-page policies as `start()`.
93
+
94
+ `app.options` is the copied definition. An independent test instance can use `defineApp({ ...app.options, port: 0, signals: false })`, followed by `await run()` and owned cleanup. This creates new page/service instances, but does not clone an Express application or objects deliberately captured by class closures. The chat module exports a default `ChatroomPage`; its optional `createChatroomPage()` factory creates isolated rooms for separate apps or tests.
95
+
96
+ For rendering and CSS see [Live HTML](LIVE_HTML.md). For message validation and handler classes see [socket contracts](SOCKET_CONTRACTS.md). For deployment and persistence boundaries see [operations](MULTIPLAYER_OPERATIONS.md).
package/docs/CLI.md CHANGED
@@ -1,122 +1,122 @@
1
- # Redweb command-line tools
2
-
3
- Use the version installed in your project (`npx --no-install redweb`) when troubleshooting an existing app. The tool reports a warning if its version differs from the project's installed Redweb version.
4
-
5
- ## Add pages, components, and socket routes
6
-
7
- These commands are available in `redweb@0.16.2`.
8
-
9
- ```sh
10
- npx --no-install redweb add page dashboard
11
- npx --no-install redweb add component notifications
12
- npx --no-install redweb add socket-route match
13
- npx --no-install redweb add page account-settings --dry-run --json
14
- ```
15
-
16
- Each addition writes a named-export TypeScript module and a `.test.cjs` file. Pages and owned components demonstrate server state plus an exposed increment action. The socket route demonstrates a validated `ping` handler returning `pong`, without an inner action dispatcher; extend its contract and register additional handlers as needed. For complete join/move/resume behavior, use the existing socket starter instead.
17
-
18
- Run these commands in an existing project with Redweb declared as an installed runtime dependency and TypeScript installed. Declare/install `ws` explicitly (normally as a development dependency) for the generated network tests. Socket additions also require application-installed Zod as a runtime dependency. The generator reports missing prerequisites; it never installs dependencies or changes your manifest.
19
-
20
- The default source location is the effective TypeScript `rootDir`, with `pages/`, `components/`, or `socket-routes/` beneath it. The default test directory is `test/`. An optional project directory follows the kind/name. Use `--config build.json`, `--source-dir features`, or `--test-dir checks` to select paths relative to that project. Names must be lowercase kebab-case, start with a letter, and contain at most 64 characters.
21
-
22
- The command supports a single emitting TypeScript project using CommonJS, Node16 or NodeNext module settings and standard or legacy decorators. HTML additions require Redweb's automatic JSX runtime. Effective inherited configuration controls inclusion and emission; `--source-dir` chooses placement, **not** the compiler's `rootDir`. Ambiguous placement requires that option or an explicit `rootDir`. Project-reference roots, bundler-only pipelines, bundled output, disabled JavaScript emission, output outside the project, mismatched source/output package module types, and compiled test locations are rejected with guidance. Select the appropriate child project/configuration yourself rather than allowing the command to rewrite a monorepo.
23
-
24
- The planner parses source and performs an in-memory TypeScript emit, without importing the application or writing build output. It checks the prospective module, its actual emitted path (including imported source dependencies), and whether an inferred root would relocate existing output. It rejects a test directory that TypeScript would compile when `allowJs` is enabled. This is not a replacement for a whole-project build or its existing tests. The virtual-file matcher uses a feature-checked TypeScript runtime API; unsupported compiler shapes fail explicitly rather than guessing glob behavior.
25
-
26
- `--dry-run` writes nothing; `--json` returns a versioned report with planned/created paths, source/output/test paths, a named import, `registration.status: "pending"`, and explicit build/test argument arrays. Human commands are quoted for PowerShell on Windows and a POSIX shell elsewhere. Run the reported build and then its test from the project root. The test imports **only the generated artifact**, starts an isolated loopback server on a temporary port, and exercises a real HTTP/WebSocket action or message exchange. It never imports the existing application entry point.
27
-
28
- Registration is intentionally your next step. Register pages and socket routes in `defineApp({ pages: [...], sockets: [...] })`. For components, create an owned field (`widget = new NotificationsComponent()`) and render `{this.widget}`. Adjust the report's project-root-relative named import to the file where you use it; Node-compatible imports use the emitted `.js` extension. No imports, registration lists, package scripts, manifests, or configuration files are rewritten. Add the new test to your project's normal test command yourself; a generated test is not claimed to be automatically registered.
29
-
30
- The shared writer rejects any destination conflict before writing and creates files exclusively. It rejects path escapes, unsafe portable names, case aliases and symbolic-link ancestors. Concurrent failures report which files were completed and which path was attempted; writing is not transactional and does not lock the filesystem tree. Existing application files are never overwritten.
31
-
32
- ## Initialize a project
33
-
34
- Follow a [complete recipe's version-specific setup](GETTING_STARTED.md#start-with-a-complete-recipe). Its commands initialize a new directory, install the matching release or packed artifact, run tests, and start development. The unreleased channel requires the same tarball for initialization and installation; ordinary `npx redweb` does not select this checkout.
35
-
36
- The initializer creates missing files only. It does not install dependencies, run package scripts, or validate existing source code. A message saying initialization completed means the file operation completed, not that a preserved existing project is valid.
37
-
38
- Without a template, `redweb init my-app` creates a neutral, runnable TypeScript/TSX foundation. It contains one placeholder page, CSS, build/development scripts, and real HTTP/lifecycle tests, but no counter, chatroom, dashboard, or game-domain code. This is the common starting point for tutorials and new applications.
39
-
40
- `--with auth,multiplayer` composes optional dependency sets into that same foundation without generating another example. `auth` adds Express, Zod, their TypeScript declarations, and the Node 22.13+ requirement used by Redweb's native-SQLite authentication path. `multiplayer` adds Redweb Client and Zod. Either capability can be selected alone, the comma-separated list must not contain duplicates, and capabilities may also supplement an explicit example template.
41
-
42
- `--template realtime|chat|site|socket|dashboard|http-ws` explicitly selects a complete runnable example. `realtime` is a shared server-owned counter. `chat` includes the canonical reusable chat component, validated actions and its stylesheet; `site` has two non-live pages with a shared layout; `socket` exposes `/match` with separate `join`, `move`, and `resume` handlers, a shared Zod contract, and bounded in-memory sessions. The [dashboard](../recipes/dashboard/README.md) combines private live cards, SQLite persistence, explicit account provisioning, expiring sessions and account-wide sign-out. It requires Node 22.13+. The [http-ws starter](../recipes/http-ws/README.md) combines an HTTP health endpoint and a raw socket route on one explicitly owned listener. Templates are learning/reference applications, not prerequisites for starting a project.
43
-
44
- Every non-bare initialization includes network tests and a development watcher. `--bare` retains the same runnable source, CSS, manifest, compiler configuration, build scripts, and watcher, but omits the `test/` directory, test scripts, and the test-only coverage dependency. `--existing` cannot be combined with `--template`, `--with`, or `--bare`. The optional capability and template dependencies remain application-local; Redweb itself does not require Zod or SQLite at runtime.
45
-
46
- Doctor also checks the application's declared `engines.node` minimum (for example `>=22.13.0`). An incompatible runtime produces `PROJECT_NODE_UNSUPPORTED`. More complex ranges produce `PROJECT_NODE_UNCHECKED`, not a guessed success; npm remains responsible for its full engine-range interpretation. CI runs the dashboard acceptance tests on Node 22; older core compatibility jobs explicitly skip that recipe's runtime execution.
47
-
48
- Run `npm test` for type checking, asset copying, and real HTTP/WebSocket tests on an ephemeral loopback port. `npm run dev` uses development-only Nodemon to rebuild and restart on changes to `src/` or `tsconfig.json`, enabling loopback-only browser refresh through its `REDWEB_DEV_REFRESH=1` environment. Clean HTML pages refresh automatically; detected edits keep the old document with a confirmation notice. This is not browser hot-module replacement or autosave. A type error prevents startup until corrected; outages alone do not trigger reload. See [development refresh](DEVELOPMENT.md#browser-refresh) for draft, connection, hostname and production boundaries. `npm run build` produces runtime code and assets in `dist/`; production needs that directory and installed runtime dependencies, not TypeScript or `src/`.
49
-
50
- Templates come from `recipes/`, with common configuration/test helpers maintained once. The package gate extracts a tarball, generates every template, runs each generated `npm test`, then removes access to `src/` and runs the network tests again to validate production asset resolution.
51
-
52
- For an existing application:
53
-
54
- ```sh
55
- npx --no-install redweb init --existing --dry-run --json
56
- npx --no-install redweb init --existing
57
- npx --no-install redweb doctor --json
58
- ```
59
-
60
- `--existing` creates only a missing `tsconfig.json`; it does not generate a new app, CSS, or package manifest. Adjust the generated source/output directories for your application. An existing `tsconfig.json` is never overwritten, even if it is incompatible.
61
-
62
- `--dry-run` does not create files or directories. `--json` reports a versioned result with `operation`, `foundation`, `capabilities`, `tests`, `root`, `created`, `skipped`, and `planned`. The shared file-plan writer preflights all destinations, including planned directory/file conflicts, case aliases and nonportable segments such as Windows device names, alternate streams and trailing dots/spaces. It rejects symbolic links/junctions in the destination's ancestor chain, including above the chosen project root. Exclusive creation prevents overwriting a file created concurrently.
63
-
64
- This is not a transactional installer or a lock on the filesystem tree. An operating-system error during writing can leave completed files, a partial attempted file, or new directories; the error reports completed writes and the attempted destination. Inspect those paths before retrying. Rerunning preserves existing files rather than repairing their contents. Another process must not rename or replace destination directories while generation runs.
65
-
66
- ## Diagnose without changing the project
67
-
68
- ```sh
69
- npx --no-install redweb doctor --json
70
- npx --no-install redweb doctor --port 8181
71
- ```
72
-
73
- The current checks are explicit in the result's `checks` array:
74
-
75
- - Node version against the package's current minimum.
76
- - Redweb installation in the project or its ancestor workspace's `node_modules`.
77
- - Difference between the invoked CLI and installed library versions.
78
- - Installed TypeScript (5 or newer) and a root `tsconfig.json`.
79
- - Effective inherited JSX runtime configuration, syntax/config errors, and legacy-decorator settings.
80
- - Declared page CSS/templates and duplicate page/route/handler registrations in statically readable TypeScript source.
81
- - Literal `rw-click`/`rw-submit` names against the owning page/component's public `@action()` methods.
82
- - Optional temporary bind to `127.0.0.1` to check a TCP port, immediately released on success.
83
-
84
- Each finding includes `code`, `severity`, `file`, `message`, and `suggestion`. Source findings also include one-based `line` and `column` when attached to a specific declaration. Error findings produce exit status 1; warnings do not. JSON diagnostic reports go to stdout. Invalid CLI arguments and filesystem failures go to stderr, with exit status 1. `--help` and `--version` require no project.
85
-
86
- Doctor loads the installed TypeScript compiler to read configuration and parse source, but never imports or executes the application's modules. It does not perform a full type check, emit files, run application functions/plugins, or apply repairs. These checks do not prove full application correctness or validate every package's semver range. Port availability is a point-in-time loopback check, not a reservation or a test of an external proxy. Dependency discovery currently targets conventional npm-style `node_modules` installations.
87
-
88
- ## Source checks and their boundaries
89
-
90
- The `source` JSON object reports inspected file count, registration-group count, `mode: "static-source"`, and the number of unresolved/limited warnings. It is `null` when configuration or compiler problems prevent source inspection. `checks` lists `source-assets`, `source-routes`, `source-handlers`, and `source-actions` only when the source reader ran.
91
-
92
- Supported syntax includes named/namespace TypeScript imports from Redweb, imported local constants, literal strings, constant arrays/objects, known spreads, and simple handler/route constructors. The reader starts with the configuration's source files and follows relative source imports within the project. Declaration files and dependency implementation code are not inspected; an explicitly configured source outside the project can be read, but additional outside-project imports are not followed automatically.
93
-
94
- Duplicate paths are checked **within one registration group**, not across independent servers. The reader recognizes `defineApp`, `Application`, `start`, `exportStatic`, `site.export`, `LiveHtmlServer`, `SocketServer`, and `SecureSocketServer`. Handler names are checked in a `SocketRoute` configuration, including classes based on `BaseHandler` and contract handler factories. It does not evaluate arbitrary factory calls, CommonJS destructuring imports, custom boot wrappers, dynamic route additions, or application control flow.
95
-
96
- Page assets are checked for registered pages using their decorator's source directory, the owning site's shared-CSS directory, or a statically known explicit `templateRoot`. Shared stylesheet names are deduplicated with site-root precedence, like the runtime. `__dirname` is interpreted as the source directory for this source-only check. Missing assets, directory paths, path traversal, and links escaping the effective root are reported. This does **not** verify compiled/deployed asset copies: keep the starter's build/network tests and production checks.
97
-
98
- | Code | Meaning |
99
- | --- | --- |
100
- | `TYPESCRIPT_UNSUPPORTED` | Upgrade the project's compiler to TypeScript 5 or newer. |
101
- | `SOURCE_SYNTAX`, `SOURCE_UNREADABLE` | A configured source could not be parsed or read. |
102
- | `DUPLICATE_ROUTE`, `DUPLICATE_HANDLER` | A readable registration repeats a path or message type. |
103
- | `ASSET_UNAVAILABLE`, `ASSET_NOT_FILE`, `ASSET_OUTSIDE_ROOT` | A declared asset cannot be loaded from its effective source root. |
104
- | `SOURCE_UNRESOLVED` | Dynamic, mutated, escaped, or unsupported source cannot be determined safely. |
105
- | `SOURCE_LIMIT` | Source count/size or expression expansion exceeded the inspection budget. |
106
- | `ACTION_NOT_EXPOSED` | A literal binding has no matching public decorated instance method on its statically known owner. |
107
- | `ACTION_REFERENCE_INVALID` | The literal action name is empty, reserved, missing, or longer than 128 characters. |
108
- | `ACTION_REFERENCE_UNRESOLVED` | Action names, render output, method exposure, or component ownership cannot be established by the supported source checks. |
109
-
110
- ### Repair an action binding
111
-
112
- If a button says `<button rw-click="saev">Save</button>` but the class exposes `@action() save()`, doctor reports `ACTION_NOT_EXPOSED` at the binding. Correct the name, run doctor again, then run `npm test`. Doctor never calls the action or executes the renderer to discover it.
113
-
114
- Action inspection recognizes decorator aliases, literal names (including imported string constants), inherited methods and overrides, method/function-field renderers, conditional literal returns, and returned JSX/`html` constants. Literal HTML templates use the runtime's lexical tag scanner, ignoring comments and raw-text bodies. External templates are inspected for registered pages at their source asset root and have a separate 1 MiB limit. Page and component owners are checked separately.
115
-
116
- This is deliberately not a JavaScript evaluator or a full template type checker. JSX spreads (including constant objects), custom JSX wrappers, explicit component-scope attributes, HTML entities in action names, interpolated/dynamic HTML, unavailable inherited implementations, custom decorators, and potentially replaced instance methods produce warnings where encountered. Arbitrary function calls, dependency renderers and all runtime-produced nested markup cannot be proved by source inspection. A warning is a request for application/browser verification, not a hidden success. Keep real tests for reusable helpers, scoped components and dynamic output even when doctor exits successfully.
117
-
118
- `const` is not treated as proof that an array/object is immutable. Mutated aggregates, aliases that escape into unknown calls, runtime option spreads, custom class decorators, and constructor initialization that can overwrite names/paths produce warnings rather than guessed facts. A normal starter exposes runtime option overrides, so its `templateRoot` may correctly produce an unresolved warning. Green exit status means **no errors among the selected checks**, not that warnings were resolved or the application was proved correct.
119
-
120
- Source selection is limited to 256 files and 8 MiB; expression reading is limited to 50,000 operations and 4,096 entries per expanded array. Cycles and repeated spreads cannot expand without limit. The doctor is a read-only diagnostic, not a sandbox for untrusted installed compiler code or a substitute for tests.
121
-
122
- The remaining release work is tracked in [the release acceptance checklist](AGENT_READY_ACCEPTANCE.md).
1
+ # Redweb command-line tools
2
+
3
+ Use the version installed in your project (`npx --no-install redweb`) when troubleshooting an existing app. The tool reports a warning if its version differs from the project's installed Redweb version.
4
+
5
+ ## Add pages, components, and socket routes
6
+
7
+ These commands are available in `redweb@0.16.4`.
8
+
9
+ ```sh
10
+ npx --no-install redweb add page dashboard
11
+ npx --no-install redweb add component notifications
12
+ npx --no-install redweb add socket-route match
13
+ npx --no-install redweb add page account-settings --dry-run --json
14
+ ```
15
+
16
+ Each addition writes a named-export TypeScript module and a `.test.cjs` file. Pages and owned components demonstrate server state plus an exposed increment action. The socket route demonstrates a validated `ping` handler returning `pong`, without an inner action dispatcher; extend its contract and register additional handlers as needed. For complete join/move/resume behavior, use the existing socket starter instead.
17
+
18
+ Run these commands in an existing project with Redweb declared as an installed runtime dependency and TypeScript installed. Declare/install `ws` explicitly (normally as a development dependency) for the generated network tests. Socket additions also require application-installed Zod as a runtime dependency. The generator reports missing prerequisites; it never installs dependencies or changes your manifest.
19
+
20
+ The default source location is the effective TypeScript `rootDir`, with `pages/`, `components/`, or `socket-routes/` beneath it. The default test directory is `test/`. An optional project directory follows the kind/name. Use `--config build.json`, `--source-dir features`, or `--test-dir checks` to select paths relative to that project. Names must be lowercase kebab-case, start with a letter, and contain at most 64 characters.
21
+
22
+ The command supports a single emitting TypeScript project using CommonJS, Node16 or NodeNext module settings and standard or legacy decorators. HTML additions require Redweb's automatic JSX runtime. Effective inherited configuration controls inclusion and emission; `--source-dir` chooses placement, **not** the compiler's `rootDir`. Ambiguous placement requires that option or an explicit `rootDir`. Project-reference roots, bundler-only pipelines, bundled output, disabled JavaScript emission, output outside the project, mismatched source/output package module types, and compiled test locations are rejected with guidance. Select the appropriate child project/configuration yourself rather than allowing the command to rewrite a monorepo.
23
+
24
+ The planner parses source and performs an in-memory TypeScript emit, without importing the application or writing build output. It checks the prospective module, its actual emitted path (including imported source dependencies), and whether an inferred root would relocate existing output. It rejects a test directory that TypeScript would compile when `allowJs` is enabled. This is not a replacement for a whole-project build or its existing tests. The virtual-file matcher uses a feature-checked TypeScript runtime API; unsupported compiler shapes fail explicitly rather than guessing glob behavior.
25
+
26
+ `--dry-run` writes nothing; `--json` returns a versioned report with planned/created paths, source/output/test paths, a named import, `registration.status: "pending"`, and explicit build/test argument arrays. Human commands are quoted for PowerShell on Windows and a POSIX shell elsewhere. Run the reported build and then its test from the project root. The test imports **only the generated artifact**, starts an isolated loopback server on a temporary port, and exercises a real HTTP/WebSocket action or message exchange. It never imports the existing application entry point.
27
+
28
+ Registration is intentionally your next step. Register pages and socket routes in `defineApp({ pages: [...], sockets: [...] })`. For components, create an owned field (`widget = new NotificationsComponent()`) and render `{this.widget}`. Adjust the report's project-root-relative named import to the file where you use it; Node-compatible imports use the emitted `.js` extension. No imports, registration lists, package scripts, manifests, or configuration files are rewritten. Add the new test to your project's normal test command yourself; a generated test is not claimed to be automatically registered.
29
+
30
+ The shared writer rejects any destination conflict before writing and creates files exclusively. It rejects path escapes, unsafe portable names, case aliases and symbolic-link ancestors. Concurrent failures report which files were completed and which path was attempted; writing is not transactional and does not lock the filesystem tree. Existing application files are never overwritten.
31
+
32
+ ## Initialize a project
33
+
34
+ Follow a [complete recipe's version-specific setup](GETTING_STARTED.md#start-with-a-complete-recipe). Its commands initialize a new directory, install the matching release or packed artifact, run tests, and start development. The unreleased channel requires the same tarball for initialization and installation; ordinary `npx redweb` does not select this checkout.
35
+
36
+ The initializer creates missing files only. It does not install dependencies, run package scripts, or validate existing source code. A message saying initialization completed means the file operation completed, not that a preserved existing project is valid.
37
+
38
+ Without a template, `redweb init my-app` creates a neutral, runnable TypeScript/TSX foundation. It contains one placeholder page, CSS, build/development scripts, and real HTTP/lifecycle tests, but no counter, chatroom, dashboard, or game-domain code. This is the common starting point for tutorials and new applications.
39
+
40
+ `--with auth,multiplayer` composes optional dependency sets into that same foundation without generating another example. `auth` adds Express, Zod, their TypeScript declarations, and the Node 22.13+ requirement used by Redweb's native-SQLite authentication path. `multiplayer` adds Redweb Client and Zod. Either capability can be selected alone, the comma-separated list must not contain duplicates, and capabilities may also supplement an explicit example template.
41
+
42
+ `--template realtime|chat|site|socket|dashboard|http-ws` explicitly selects a complete runnable example. `realtime` is a shared server-owned counter. `chat` includes the canonical reusable chat component, validated actions and its stylesheet; `site` has two non-live pages with a shared layout; `socket` exposes `/match` with separate `join`, `move`, and `resume` handlers, a shared Zod contract, and bounded in-memory sessions. The [dashboard](../recipes/dashboard/README.md) combines private live cards, SQLite persistence, explicit account provisioning, expiring sessions and account-wide sign-out. It requires Node 22.13+. The [http-ws starter](../recipes/http-ws/README.md) combines an HTTP health endpoint and a raw socket route on one explicitly owned listener. Templates are learning/reference applications, not prerequisites for starting a project.
43
+
44
+ Every non-bare initialization includes network tests and a development watcher. `--bare` retains the same runnable source, CSS, manifest, compiler configuration, build scripts, and watcher, but omits the `test/` directory, test scripts, and the test-only coverage dependency. `--existing` cannot be combined with `--template`, `--with`, or `--bare`. The optional capability and template dependencies remain application-local; Redweb itself does not require Zod or SQLite at runtime.
45
+
46
+ Doctor also checks the application's declared `engines.node` minimum (for example `>=22.13.0`). An incompatible runtime produces `PROJECT_NODE_UNSUPPORTED`. More complex ranges produce `PROJECT_NODE_UNCHECKED`, not a guessed success; npm remains responsible for its full engine-range interpretation. CI runs the dashboard acceptance tests on Node 22; older core compatibility jobs explicitly skip that recipe's runtime execution.
47
+
48
+ Run `npm test` for type checking, asset copying, and real HTTP/WebSocket tests on an ephemeral loopback port. `npm run dev` uses development-only Nodemon to rebuild and restart on changes to `src/` or `tsconfig.json`, enabling loopback-only browser refresh through its `REDWEB_DEV_REFRESH=1` environment. Clean HTML pages refresh automatically; detected edits keep the old document with a confirmation notice. This is not browser hot-module replacement or autosave. A type error prevents startup until corrected; outages alone do not trigger reload. See [development refresh](DEVELOPMENT.md#browser-refresh) for draft, connection, hostname and production boundaries. `npm run build` produces runtime code and assets in `dist/`; production needs that directory and installed runtime dependencies, not TypeScript or `src/`.
49
+
50
+ Templates come from `recipes/`, with common configuration/test helpers maintained once. The package gate extracts a tarball, generates every template, runs each generated `npm test`, then removes access to `src/` and runs the network tests again to validate production asset resolution.
51
+
52
+ For an existing application:
53
+
54
+ ```sh
55
+ npx --no-install redweb init --existing --dry-run --json
56
+ npx --no-install redweb init --existing
57
+ npx --no-install redweb doctor --json
58
+ ```
59
+
60
+ `--existing` creates only a missing `tsconfig.json`; it does not generate a new app, CSS, or package manifest. Adjust the generated source/output directories for your application. An existing `tsconfig.json` is never overwritten, even if it is incompatible.
61
+
62
+ `--dry-run` does not create files or directories. `--json` reports a versioned result with `operation`, `foundation`, `capabilities`, `tests`, `root`, `created`, `skipped`, and `planned`. The shared file-plan writer preflights all destinations, including planned directory/file conflicts, case aliases and nonportable segments such as Windows device names, alternate streams and trailing dots/spaces. It rejects symbolic links/junctions in the destination's ancestor chain, including above the chosen project root. Exclusive creation prevents overwriting a file created concurrently.
63
+
64
+ This is not a transactional installer or a lock on the filesystem tree. An operating-system error during writing can leave completed files, a partial attempted file, or new directories; the error reports completed writes and the attempted destination. Inspect those paths before retrying. Rerunning preserves existing files rather than repairing their contents. Another process must not rename or replace destination directories while generation runs.
65
+
66
+ ## Diagnose without changing the project
67
+
68
+ ```sh
69
+ npx --no-install redweb doctor --json
70
+ npx --no-install redweb doctor --port 8181
71
+ ```
72
+
73
+ The current checks are explicit in the result's `checks` array:
74
+
75
+ - Node version against the package's current minimum.
76
+ - Redweb installation in the project or its ancestor workspace's `node_modules`.
77
+ - Difference between the invoked CLI and installed library versions.
78
+ - Installed TypeScript (5 or newer) and a root `tsconfig.json`.
79
+ - Effective inherited JSX runtime configuration, syntax/config errors, and legacy-decorator settings.
80
+ - Declared page CSS/templates and duplicate page/route/handler registrations in statically readable TypeScript source.
81
+ - Literal `rw-click`/`rw-submit` names against the owning page/component's public `@action()` methods.
82
+ - Optional temporary bind to `127.0.0.1` to check a TCP port, immediately released on success.
83
+
84
+ Each finding includes `code`, `severity`, `file`, `message`, and `suggestion`. Source findings also include one-based `line` and `column` when attached to a specific declaration. Error findings produce exit status 1; warnings do not. JSON diagnostic reports go to stdout. Invalid CLI arguments and filesystem failures go to stderr, with exit status 1. `--help` and `--version` require no project.
85
+
86
+ Doctor loads the installed TypeScript compiler to read configuration and parse source, but never imports or executes the application's modules. It does not perform a full type check, emit files, run application functions/plugins, or apply repairs. These checks do not prove full application correctness or validate every package's semver range. Port availability is a point-in-time loopback check, not a reservation or a test of an external proxy. Dependency discovery currently targets conventional npm-style `node_modules` installations.
87
+
88
+ ## Source checks and their boundaries
89
+
90
+ The `source` JSON object reports inspected file count, registration-group count, `mode: "static-source"`, and the number of unresolved/limited warnings. It is `null` when configuration or compiler problems prevent source inspection. `checks` lists `source-assets`, `source-routes`, `source-handlers`, and `source-actions` only when the source reader ran.
91
+
92
+ Supported syntax includes named/namespace TypeScript imports from Redweb, imported local constants, literal strings, constant arrays/objects, known spreads, and simple handler/route constructors. The reader starts with the configuration's source files and follows relative source imports within the project. Declaration files and dependency implementation code are not inspected; an explicitly configured source outside the project can be read, but additional outside-project imports are not followed automatically.
93
+
94
+ Duplicate paths are checked **within one registration group**, not across independent servers. The reader recognizes `defineApp`, `Application`, `start`, `exportStatic`, `site.export`, `LiveHtmlServer`, `SocketServer`, and `SecureSocketServer`. Handler names are checked in a `SocketRoute` configuration, including classes based on `BaseHandler` and contract handler factories. It does not evaluate arbitrary factory calls, CommonJS destructuring imports, custom boot wrappers, dynamic route additions, or application control flow.
95
+
96
+ Page assets are checked for registered pages using their decorator's source directory, the owning site's shared-CSS directory, or a statically known explicit `templateRoot`. Shared stylesheet names are deduplicated with site-root precedence, like the runtime. `__dirname` is interpreted as the source directory for this source-only check. Missing assets, directory paths, path traversal, and links escaping the effective root are reported. This does **not** verify compiled/deployed asset copies: keep the starter's build/network tests and production checks.
97
+
98
+ | Code | Meaning |
99
+ | --- | --- |
100
+ | `TYPESCRIPT_UNSUPPORTED` | Upgrade the project's compiler to TypeScript 5 or newer. |
101
+ | `SOURCE_SYNTAX`, `SOURCE_UNREADABLE` | A configured source could not be parsed or read. |
102
+ | `DUPLICATE_ROUTE`, `DUPLICATE_HANDLER` | A readable registration repeats a path or message type. |
103
+ | `ASSET_UNAVAILABLE`, `ASSET_NOT_FILE`, `ASSET_OUTSIDE_ROOT` | A declared asset cannot be loaded from its effective source root. |
104
+ | `SOURCE_UNRESOLVED` | Dynamic, mutated, escaped, or unsupported source cannot be determined safely. |
105
+ | `SOURCE_LIMIT` | Source count/size or expression expansion exceeded the inspection budget. |
106
+ | `ACTION_NOT_EXPOSED` | A literal binding has no matching public decorated instance method on its statically known owner. |
107
+ | `ACTION_REFERENCE_INVALID` | The literal action name is empty, reserved, missing, or longer than 128 characters. |
108
+ | `ACTION_REFERENCE_UNRESOLVED` | Action names, render output, method exposure, or component ownership cannot be established by the supported source checks. |
109
+
110
+ ### Repair an action binding
111
+
112
+ If a button says `<button rw-click="saev">Save</button>` but the class exposes `@action() save()`, doctor reports `ACTION_NOT_EXPOSED` at the binding. Correct the name, run doctor again, then run `npm test`. Doctor never calls the action or executes the renderer to discover it.
113
+
114
+ Action inspection recognizes decorator aliases, literal names (including imported string constants), inherited methods and overrides, method/function-field renderers, conditional literal returns, and returned JSX/`html` constants. Literal HTML templates use the runtime's lexical tag scanner, ignoring comments and raw-text bodies. External templates are inspected for registered pages at their source asset root and have a separate 1 MiB limit. Page and component owners are checked separately.
115
+
116
+ This is deliberately not a JavaScript evaluator or a full template type checker. JSX spreads (including constant objects), custom JSX wrappers, explicit component-scope attributes, HTML entities in action names, interpolated/dynamic HTML, unavailable inherited implementations, custom decorators, and potentially replaced instance methods produce warnings where encountered. Arbitrary function calls, dependency renderers and all runtime-produced nested markup cannot be proved by source inspection. A warning is a request for application/browser verification, not a hidden success. Keep real tests for reusable helpers, scoped components and dynamic output even when doctor exits successfully.
117
+
118
+ `const` is not treated as proof that an array/object is immutable. Mutated aggregates, aliases that escape into unknown calls, runtime option spreads, custom class decorators, and constructor initialization that can overwrite names/paths produce warnings rather than guessed facts. A normal starter exposes runtime option overrides, so its `templateRoot` may correctly produce an unresolved warning. Green exit status means **no errors among the selected checks**, not that warnings were resolved or the application was proved correct.
119
+
120
+ Source selection is limited to 256 files and 8 MiB; expression reading is limited to 50,000 operations and 4,096 entries per expanded array. Cycles and repeated spreads cannot expand without limit. The doctor is a read-only diagnostic, not a sandbox for untrusted installed compiler code or a substitute for tests.
121
+
122
+ The remaining release work is tracked in [the release acceptance checklist](AGENT_READY_ACCEPTANCE.md).