Skip to content

Commit d08b4b0

Browse files
feat(node): add createNodeApp hotReload option + template/demo migration (#147)
* feat(node): integrate createNodeApp hotReload lifecycle API * chore(repo): apply biome formatting for package manifests
1 parent 286f6fa commit d08b4b0

30 files changed

Lines changed: 826 additions & 354 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The format is based on Keep a Changelog and the project follows Semantic Version
1212
- `@rezi-ui/bench` — comprehensive benchmark suite (Rezi native vs Ink-on-Rezi vs Ink)
1313
- Benchmark results and performance documentation
1414
- Hot State-Preserving Reload (HSR): `App.replaceView(...)`/`App.replaceRoutes(...)` in core and `createHotStateReload(...)` in `@rezi-ui/node` for in-process widget view and route-table hot swapping during development
15+
- `@rezi-ui/node` `createNodeApp(...)` now supports first-class `hotReload` wiring (auto lifecycle start/stop + `app.hotReload` controller) and route passthrough (`routes`, `initialRoute`)
1516
- Code-editor syntax tokenizer utilities in `@rezi-ui/core`: `tokenizeCodeEditorLine(...)`, `tokenizeCodeEditorLineWithCustom(...)`, `normalizeCodeEditorTokens(...)`, language presets (`typescript`, `javascript`, `json`, `go`, `rust`, `c`, `cpp`/`c++`, `csharp`/`c#`, `java`, `python`, `bash`)
1617
- Widget composition API (`defineWidget` with hooks)
1718
- Page router route guards (`guard`) and nested route trees (`children`) with `context.outlet` rendering
@@ -31,6 +32,7 @@ The format is based on Keep a Changelog and the project follows Semantic Version
3132
- Documentation expanded with 90+ pages covering all features
3233
- README updated with performance data, JSX support, Zireael engine reference
3334
- ROADMAP updated to reflect current project state
35+
- HSR demos and `create-rezi` templates now use `createNodeApp({ hotReload: ... })` instead of manual controller lifecycle wiring
3436
- Input widgets now include local undo/redo history (`Ctrl+Z`, `Ctrl+Shift+Z`, `Ctrl+Y`) with 300ms typing-grouping
3537
- Virtual-list routing and scroll math now consume measured-height caches when estimate mode is active, improving End/Page navigation and wheel behavior for variable-height content
3638

docs/backend/node.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@ knobs aligned:
3232

3333
Development hot reload:
3434

35-
- `createHotStateReload(...)` watches source files and hot-swaps either:
35+
- `createNodeApp({ hotReload: ... })` watches source files and hot-swaps either:
3636
- widget views via `app.replaceView(...)`, or
3737
- route tables via `app.replaceRoutes(...)` for route-managed apps.
38+
- lifecycle is automatic: watcher starts/stops with `app.start()/stop()/run()`.
39+
- `app.hotReload` exposes manual `reloadNow()` when needed.
40+
- low-level `createHotStateReload(...)` remains available for advanced manual wiring.
3841
- Raw draw mode remains excluded.
3942

4043
Execution mode details:

docs/dev/contributing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ node --test packages/core/src/app/__tests__/widgetRenderer.integration.test.ts
4242

4343
## HSR Change Checklist
4444

45-
If your change touches `app.view`, route integration, reconciliation identity, or `createHotStateReload`:
45+
If your change touches `app.view`, route integration, reconciliation identity, `createNodeApp({ hotReload })`, or `createHotStateReload`:
4646

4747
- add app runtime tests for `app.replaceView(...)` / `app.replaceRoutes(...)` behavior
4848
- verify widget local state survives reload with stable ids/keys

docs/dev/testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ Integration tests cover:
109109

110110
### Hot State-Preserving Reload (HSR) Tests
111111

112-
When changing `app.replaceView(...)`, `app.replaceRoutes(...)`, or `createHotStateReload(...)`, include:
112+
When changing `app.replaceView(...)`, `app.replaceRoutes(...)`, `createNodeApp({ hotReload })`, or `createHotStateReload(...)`, include:
113113

114114
- successful runtime widget-view swap test (`replaceView` while `Running`)
115115
- successful runtime route-table swap test (`replaceRoutes` while `Running` in route mode)

docs/getting-started/create-rezi.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Each template includes example tests for reducer logic, widget rendering, and ke
8989
`npm run dev` / `bun run dev`:
9090

9191
- dev script runs `tsx src/main.ts --hsr`
92-
- source changes hot-swap via:
92+
- source changes hot-swap through `createNodeApp({ hotReload: ... })` via:
9393
- `app.replaceView(...)` in widget-view templates (`minimal`, `dashboard`)
9494
- `app.replaceRoutes(...)` in route-managed template (`cli-tool`)
9595
- app state, focus, and stable widget local state are preserved across edits

docs/guide/lifecycle-and-updates.md

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,32 +72,34 @@ When a new view function is applied, Rezi keeps:
7272

7373
```ts
7474
import { ui } from "@rezi-ui/core";
75-
import { createNodeApp, createHotStateReload } from "@rezi-ui/node";
76-
77-
const app = createNodeApp({ initialState: { count: 0 } });
78-
app.view((state) => ui.text(`count=${String(state.count)}`));
75+
import { createNodeApp } from "@rezi-ui/node";
7976

80-
const hsr = createHotStateReload({
81-
app,
82-
viewModule: new URL("./screens/main-screen.ts", import.meta.url),
83-
moduleRoot: new URL("./src", import.meta.url),
77+
const app = createNodeApp({
78+
initialState: { count: 0 },
79+
hotReload: {
80+
viewModule: new URL("./screens/main-screen.ts", import.meta.url),
81+
moduleRoot: new URL("./src", import.meta.url),
82+
},
8483
});
85-
86-
await hsr.start();
87-
await app.start();
84+
app.view((state) => ui.text(`count=${String(state.count)}`));
85+
await app.run();
8886
```
8987

9088
Route-managed apps can hot-swap route tables instead:
9189

9290
```ts
93-
const hsr = createHotStateReload({
94-
app,
95-
routesModule: new URL("./screens/index.ts", import.meta.url),
96-
moduleRoot: new URL("./src", import.meta.url),
97-
resolveRoutes: (moduleNs) => {
98-
const routes = (moduleNs as { routes?: unknown }).routes;
99-
if (!Array.isArray(routes)) throw new Error("Expected `routes` array export");
100-
return routes;
91+
const app = createNodeApp({
92+
initialState,
93+
routes,
94+
initialRoute: "home",
95+
hotReload: {
96+
routesModule: new URL("./screens/index.ts", import.meta.url),
97+
moduleRoot: new URL("./src", import.meta.url),
98+
resolveRoutes: (moduleNs) => {
99+
const routesExport = (moduleNs as { routes?: unknown }).routes;
100+
if (!Array.isArray(routesExport)) throw new Error("Expected `routes` array export");
101+
return routesExport;
102+
},
101103
},
102104
});
103105
```

docs/packages/create-rezi.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ For full template descriptions and highlights, use: [Getting Started -> Create R
6060
Template dev workflow notes:
6161

6262
- `minimal`, `dashboard`, and `cli-tool` templates run `tsx src/main.ts --hsr` for `npm run dev` / `bun run dev`.
63-
- This enables in-process hot state-preserving reload through `@rezi-ui/node` `createHotStateReload(...)`.
63+
- This enables in-process hot state-preserving reload through `@rezi-ui/node` `createNodeApp({ hotReload: ... })`.
6464

6565
## Options
6666

docs/packages/node.md

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,43 +54,49 @@ lockstep:
5454

5555
## Hot State-Preserving Reload (HSR)
5656

57-
`@rezi-ui/node` ships `createHotStateReload(...)` for development-time widget-view
58-
or route-table swaps.
57+
`createNodeApp(...)` has first-class HSR wiring through `hotReload`.
5958

6059
```ts
6160
import { ui } from "@rezi-ui/core";
62-
import { createHotStateReload, createNodeApp } from "@rezi-ui/node";
63-
64-
const app = createNodeApp({ initialState: { count: 0 } });
65-
app.view((state) => ui.text(`count=${String(state.count)}`));
61+
import { createNodeApp } from "@rezi-ui/node";
6662

67-
const hsr = createHotStateReload({
68-
app,
69-
viewModule: new URL("./screens/main-screen.ts", import.meta.url),
70-
moduleRoot: new URL("./src", import.meta.url),
63+
const app = createNodeApp({
64+
initialState: { count: 0 },
65+
hotReload: {
66+
viewModule: new URL("./screens/main-screen.ts", import.meta.url),
67+
moduleRoot: new URL("./src", import.meta.url),
68+
},
7169
});
7270

73-
await hsr.start();
74-
await app.start();
71+
app.view((state) => ui.text(`count=${String(state.count)}`));
72+
await app.run(); // starts/stops hot reload watcher with app lifecycle
7573
```
7674

77-
Route-managed apps use `routesModule` + `app.replaceRoutes(...)`:
75+
Route-managed apps use `routesModule`:
7876

7977
```ts
80-
const hsr = createHotStateReload({
81-
app,
82-
routesModule: new URL("./screens/index.ts", import.meta.url),
83-
moduleRoot: new URL("./src", import.meta.url),
84-
resolveRoutes: (moduleNs) => {
85-
const routes = (moduleNs as { routes?: unknown }).routes;
86-
if (!Array.isArray(routes)) {
87-
throw new Error("Expected `routes` array export");
88-
}
89-
return routes;
78+
const app = createNodeApp({
79+
initialState: { count: 0 },
80+
routes,
81+
initialRoute: "home",
82+
hotReload: {
83+
routesModule: new URL("./screens/index.ts", import.meta.url),
84+
moduleRoot: new URL("./src", import.meta.url),
85+
resolveRoutes: (moduleNs) => {
86+
const routesExport = (moduleNs as { routes?: unknown }).routes;
87+
if (!Array.isArray(routesExport)) {
88+
throw new Error("Expected `routes` array export");
89+
}
90+
return routesExport;
91+
},
9092
},
9193
});
9294
```
9395

96+
`app.hotReload` exposes the controller (`reloadNow()`, `isRunning()`) when you need
97+
manual trigger points. For advanced/custom lifecycle control, low-level
98+
`createHotStateReload(...)` remains available.
99+
94100
What this does:
95101

96102
- watches source paths for changes
@@ -106,7 +112,7 @@ What stays intact across reload:
106112
Current scope:
107113

108114
- widget-mode apps (`app.view`/`app.replaceView`)
109-
- route-managed apps (`createApp({ routes, initialRoute })` + `app.replaceRoutes`)
115+
- route-managed apps (`createNodeApp({ routes, initialRoute })` + `app.replaceRoutes`)
110116
- not raw draw mode
111117

112118
## `NO_COLOR` behavior

packages/core/package.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,7 @@
104104
"default": "./dist/widgets/index.js"
105105
}
106106
},
107-
"files": [
108-
"dist/",
109-
"README.md",
110-
"!dist/**/__tests__/**",
111-
"!dist/**/__e2e__/**"
112-
],
107+
"files": ["dist/", "README.md", "!dist/**/__tests__/**", "!dist/**/__e2e__/**"],
113108
"engines": {
114109
"node": ">=18",
115110
"bun": ">=1.3.0"

packages/create-rezi/package.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,7 @@
1616
"bin": {
1717
"create-rezi": "dist/index.js"
1818
},
19-
"files": [
20-
"dist/",
21-
"templates/",
22-
"README.md",
23-
"!dist/**/__tests__/**",
24-
"!dist/**/__e2e__/**"
25-
],
19+
"files": ["dist/", "templates/", "README.md", "!dist/**/__tests__/**", "!dist/**/__e2e__/**"],
2620
"engines": {
2721
"node": ">=18",
2822
"bun": ">=1.3.0"

0 commit comments

Comments
 (0)