create-rezi 0.1.0-alpha.38 → 0.1.0-alpha.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +2 -2
- package/templates/starship/src/__tests__/render.test.ts +24 -8
- package/templates/starship/src/helpers/debug.ts +22 -0
- package/templates/starship/src/helpers/layout.ts +66 -0
- package/templates/starship/src/helpers/state.ts +28 -1
- package/templates/starship/src/main.ts +115 -27
- package/templates/starship/src/screens/bridge.ts +390 -139
- package/templates/starship/src/screens/cargo.ts +293 -156
- package/templates/starship/src/screens/comms.ts +420 -151
- package/templates/starship/src/screens/crew.ts +240 -90
- package/templates/starship/src/screens/engineering.ts +374 -158
- package/templates/starship/src/screens/primitives.ts +162 -0
- package/templates/starship/src/screens/settings.ts +244 -208
- package/templates/starship/src/screens/shell.ts +479 -88
- package/templates/starship/src/theme.ts +357 -41
- package/templates/starship/src/types.ts +3 -0
package/README.md
CHANGED
|
@@ -37,8 +37,8 @@ npm create rezi my-app -- --template dashboard
|
|
|
37
37
|
npm create rezi my-app -- --template stress-test
|
|
38
38
|
npm create rezi my-app -- --template cli-tool
|
|
39
39
|
npm create rezi my-app -- --template animation-lab
|
|
40
|
-
npm create rezi my-app -- --template minimal
|
|
41
40
|
npm create rezi my-app -- --template starship
|
|
41
|
+
npm create rezi my-app -- --template minimal
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
List templates and highlights from the CLI:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-rezi",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.39",
|
|
4
4
|
"description": "Scaffold a Rezi terminal UI app.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://rezitui.dev",
|
|
@@ -28,6 +28,6 @@
|
|
|
28
28
|
"bun": ">=1.3.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@rezi-ui/testkit": "0.1.0-alpha.
|
|
31
|
+
"@rezi-ui/testkit": "0.1.0-alpha.39"
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -66,7 +66,8 @@ test("bridge screen renders core markers", () => {
|
|
|
66
66
|
|
|
67
67
|
assert.match(output, /USS Rezi/);
|
|
68
68
|
assert.match(output, /Bridge Overview/);
|
|
69
|
-
assert.match(output, /
|
|
69
|
+
assert.match(output, /Navigation/);
|
|
70
|
+
assert.match(output, /Route Health/);
|
|
70
71
|
});
|
|
71
72
|
|
|
72
73
|
test("engineering screen renders core markers", () => {
|
|
@@ -77,7 +78,20 @@ test("engineering screen renders core markers", () => {
|
|
|
77
78
|
.toText();
|
|
78
79
|
|
|
79
80
|
assert.match(output, /Engineering Deck/);
|
|
80
|
-
assert.match(output, /
|
|
81
|
+
assert.match(output, /Fleet Active/);
|
|
82
|
+
assert.match(output, /Route Health/);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("engineering screen hides secondary panels at medium height", () => {
|
|
86
|
+
const state = createInitialState(0);
|
|
87
|
+
const renderer = createTestRenderer({ viewport: { cols: 160, rows: 46 } });
|
|
88
|
+
const output = renderer
|
|
89
|
+
.render(renderEngineeringScreen(createContext(state, "engineering"), createDeps()))
|
|
90
|
+
.toText();
|
|
91
|
+
|
|
92
|
+
assert.match(output, /Engineering Deck/);
|
|
93
|
+
assert.doesNotMatch(output, /Subsystem Tree/);
|
|
94
|
+
assert.doesNotMatch(output, /Subsystem Diagnostics/);
|
|
81
95
|
});
|
|
82
96
|
|
|
83
97
|
test("crew screen renders loading/ops markers", () => {
|
|
@@ -88,7 +102,8 @@ test("crew screen renders loading/ops markers", () => {
|
|
|
88
102
|
.toText();
|
|
89
103
|
|
|
90
104
|
assert.match(output, /Crew Manifest/);
|
|
91
|
-
assert.match(output, /
|
|
105
|
+
assert.match(output, /Fleet Active/);
|
|
106
|
+
assert.match(output, /Navigation/);
|
|
92
107
|
});
|
|
93
108
|
|
|
94
109
|
test("comms screen renders control markers", () => {
|
|
@@ -99,8 +114,8 @@ test("comms screen renders control markers", () => {
|
|
|
99
114
|
.toText();
|
|
100
115
|
|
|
101
116
|
assert.match(output, /Communications/);
|
|
102
|
-
assert.match(output, /
|
|
103
|
-
assert.match(output, /
|
|
117
|
+
assert.match(output, /Fleet Active/);
|
|
118
|
+
assert.match(output, /Palette/);
|
|
104
119
|
});
|
|
105
120
|
|
|
106
121
|
test("settings screen renders form fields", () => {
|
|
@@ -111,8 +126,8 @@ test("settings screen renders form fields", () => {
|
|
|
111
126
|
.toText();
|
|
112
127
|
|
|
113
128
|
assert.match(output, /Ship Settings/);
|
|
114
|
-
assert.match(output, /
|
|
115
|
-
assert.match(output, /
|
|
129
|
+
assert.match(output, /Theme Night Shift/);
|
|
130
|
+
assert.match(output, /Navigation/);
|
|
116
131
|
});
|
|
117
132
|
|
|
118
133
|
test("cargo screen renders manifest widgets", () => {
|
|
@@ -123,5 +138,6 @@ test("cargo screen renders manifest widgets", () => {
|
|
|
123
138
|
.toText();
|
|
124
139
|
|
|
125
140
|
assert.match(output, /Cargo Hold/);
|
|
126
|
-
assert.match(output, /
|
|
141
|
+
assert.match(output, /Fleet Active/);
|
|
142
|
+
assert.match(output, /Route Health/);
|
|
127
143
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { appendFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
const DEBUG_ENABLED = process.env.REZI_STARSHIP_DEBUG === "1";
|
|
4
|
+
const DEBUG_LOG_PATH = process.env.REZI_STARSHIP_DEBUG_LOG ?? "/tmp/rezi-starship-layout.log";
|
|
5
|
+
const lastSnapshotByScope = new Map<string, string>();
|
|
6
|
+
|
|
7
|
+
export function debugSnapshot(scope: string, payload: Readonly<Record<string, unknown>>): void {
|
|
8
|
+
if (!DEBUG_ENABLED) return;
|
|
9
|
+
|
|
10
|
+
const serialized = JSON.stringify(payload);
|
|
11
|
+
if (lastSnapshotByScope.get(scope) === serialized) return;
|
|
12
|
+
lastSnapshotByScope.set(scope, serialized);
|
|
13
|
+
|
|
14
|
+
appendFileSync(
|
|
15
|
+
DEBUG_LOG_PATH,
|
|
16
|
+
`${JSON.stringify({
|
|
17
|
+
ts: new Date().toISOString(),
|
|
18
|
+
scope,
|
|
19
|
+
...payload,
|
|
20
|
+
})}\n`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { RouteId } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export type ResponsiveLayout = Readonly<{
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
wide: boolean;
|
|
7
|
+
stackRightRail: boolean;
|
|
8
|
+
compactSidebar: boolean;
|
|
9
|
+
hideNonCritical: boolean;
|
|
10
|
+
sidebarWidth: number;
|
|
11
|
+
crewMasterWidth: number;
|
|
12
|
+
chartWidth: number;
|
|
13
|
+
canvasWidth: number;
|
|
14
|
+
}>;
|
|
15
|
+
|
|
16
|
+
export type ViewportSnapshot = Readonly<{
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}>;
|
|
20
|
+
|
|
21
|
+
function clamp(value: number, min: number, max: number): number {
|
|
22
|
+
if (value < min) return min;
|
|
23
|
+
if (value > max) return max;
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveLayout(viewport: ViewportSnapshot): ResponsiveLayout {
|
|
28
|
+
const width = Math.max(40, Math.floor(viewport.width));
|
|
29
|
+
const height = Math.max(18, Math.floor(viewport.height));
|
|
30
|
+
const wide = width >= 120;
|
|
31
|
+
const stackRightRail = width < 120;
|
|
32
|
+
const compactSidebar = width < 90;
|
|
33
|
+
const hideNonCritical = width < 80 || height < 26;
|
|
34
|
+
const sidebarWidth = compactSidebar ? 18 : 34;
|
|
35
|
+
|
|
36
|
+
return Object.freeze({
|
|
37
|
+
width,
|
|
38
|
+
height,
|
|
39
|
+
wide,
|
|
40
|
+
stackRightRail,
|
|
41
|
+
compactSidebar,
|
|
42
|
+
hideNonCritical,
|
|
43
|
+
sidebarWidth,
|
|
44
|
+
crewMasterWidth: wide ? 60 : 100,
|
|
45
|
+
chartWidth: clamp(Math.floor(width * (wide ? 0.5 : 0.9)), 28, 132),
|
|
46
|
+
canvasWidth: clamp(Math.floor(width * (wide ? 0.48 : 0.9)), 26, 116),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const COMPACT_ROUTE_LABEL: Readonly<Record<RouteId, string>> = Object.freeze({
|
|
51
|
+
bridge: "Br",
|
|
52
|
+
engineering: "Eng",
|
|
53
|
+
crew: "Crew",
|
|
54
|
+
comms: "Com",
|
|
55
|
+
cargo: "Cargo",
|
|
56
|
+
settings: "Set",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export function routeLabel(routeId: RouteId, title: string, compact: boolean): string {
|
|
60
|
+
if (!compact) return title;
|
|
61
|
+
return COMPACT_ROUTE_LABEL[routeId] ?? title;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function padLabel(label: string, width: number): string {
|
|
65
|
+
return label.length >= width ? label.slice(0, width) : label.padEnd(width, " ");
|
|
66
|
+
}
|
|
@@ -179,6 +179,23 @@ function defaultTelemetry(): TelemetrySnapshot {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
export function createInitialState(nowMs = Date.now()): StarshipState {
|
|
182
|
+
const viewportCols = 120;
|
|
183
|
+
const viewportRows = 40;
|
|
184
|
+
return createInitialStateWithViewport(nowMs, {
|
|
185
|
+
cols: viewportCols,
|
|
186
|
+
rows: viewportRows,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
type InitialViewport = Readonly<{
|
|
191
|
+
cols: number;
|
|
192
|
+
rows: number;
|
|
193
|
+
}>;
|
|
194
|
+
|
|
195
|
+
export function createInitialStateWithViewport(
|
|
196
|
+
nowMs: number,
|
|
197
|
+
viewport: InitialViewport,
|
|
198
|
+
): StarshipState {
|
|
182
199
|
const telemetry = defaultTelemetry();
|
|
183
200
|
const telemetryHistory = Object.freeze(
|
|
184
201
|
Array.from({ length: TELEMETRY_HISTORY_LIMIT }, (_, index) =>
|
|
@@ -199,13 +216,15 @@ export function createInitialState(nowMs = Date.now()): StarshipState {
|
|
|
199
216
|
tick: 0,
|
|
200
217
|
nowMs,
|
|
201
218
|
alertLevel: "green",
|
|
202
|
-
themeName: "
|
|
219
|
+
themeName: "night",
|
|
203
220
|
showHelp: false,
|
|
204
221
|
showCommandPalette: false,
|
|
205
222
|
commandQuery: "",
|
|
206
223
|
commandIndex: 0,
|
|
207
224
|
autopilot: true,
|
|
208
225
|
paused: false,
|
|
226
|
+
viewportCols: clampInt(viewport.cols, 40, 300),
|
|
227
|
+
viewportRows: clampInt(viewport.rows, 18, 200),
|
|
209
228
|
|
|
210
229
|
telemetry,
|
|
211
230
|
telemetryHistory,
|
|
@@ -328,6 +347,14 @@ function applyCommand(state: StarshipState, commandId: string): StarshipState {
|
|
|
328
347
|
}
|
|
329
348
|
|
|
330
349
|
export function reduceStarshipState(state: StarshipState, action: StarshipAction): StarshipState {
|
|
350
|
+
if (action.type === "set-viewport") {
|
|
351
|
+
return freezeState({
|
|
352
|
+
...state,
|
|
353
|
+
viewportCols: clampInt(action.cols, 40, 300),
|
|
354
|
+
viewportRows: clampInt(action.rows, 18, 200),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
331
358
|
if (action.type === "tick") {
|
|
332
359
|
const tick = state.tick + 1;
|
|
333
360
|
const telemetry = state.paused ? state.telemetry : evolveTelemetry(state, tick);
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { exit } from "node:process";
|
|
2
2
|
import { createNodeApp } from "@rezi-ui/node";
|
|
3
|
+
import { debugSnapshot } from "./helpers/debug.js";
|
|
3
4
|
import { resolveStarshipCommand } from "./helpers/keybindings.js";
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
createInitialStateWithViewport,
|
|
7
|
+
filteredMessages,
|
|
8
|
+
reduceStarshipState,
|
|
9
|
+
} from "./helpers/state.js";
|
|
5
10
|
import { STARSHIP_ROUTES, createStarshipRoutes } from "./screens/index.js";
|
|
6
11
|
import { themeSpec } from "./theme.js";
|
|
7
12
|
import type { RouteDeps, RouteId, StarshipAction, StarshipState } from "./types.js";
|
|
@@ -10,18 +15,36 @@ const UI_FPS_CAP = 30;
|
|
|
10
15
|
const TICK_MS = 800;
|
|
11
16
|
const TOAST_PRUNE_MS = 3000;
|
|
12
17
|
|
|
13
|
-
const initialState =
|
|
18
|
+
const initialState = createInitialStateWithViewport(Date.now(), {
|
|
19
|
+
cols: process.stdout.columns ?? 120,
|
|
20
|
+
rows: process.stdout.rows ?? 40,
|
|
21
|
+
});
|
|
14
22
|
const enableHsr = process.argv.includes("--hsr") || process.env.REZI_HSR === "1";
|
|
15
23
|
const hasInteractiveTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
16
24
|
if (!hasInteractiveTty && process.env.ZIREAEL_POSIX_PIPE_MODE === undefined) {
|
|
17
25
|
process.env.ZIREAEL_POSIX_PIPE_MODE = "1";
|
|
18
26
|
}
|
|
27
|
+
debugSnapshot("runtime.bootstrap", {
|
|
28
|
+
argv: process.argv.slice(2),
|
|
29
|
+
cwd: process.cwd(),
|
|
30
|
+
pid: process.pid,
|
|
31
|
+
node: process.version,
|
|
32
|
+
stdinIsTty: Boolean(process.stdin.isTTY),
|
|
33
|
+
stdoutIsTty: Boolean(process.stdout.isTTY),
|
|
34
|
+
cols: process.stdout.columns ?? null,
|
|
35
|
+
rows: process.stdout.rows ?? null,
|
|
36
|
+
enableHsr,
|
|
37
|
+
});
|
|
19
38
|
|
|
20
39
|
// biome-ignore lint/style/useConst: circular bootstrap wiring requires post-declaration assignment
|
|
21
40
|
let app!: ReturnType<typeof createNodeApp<StarshipState>>;
|
|
22
41
|
let stopping = false;
|
|
23
42
|
let tickTimer: ReturnType<typeof setInterval> | null = null;
|
|
24
43
|
let toastTimer: ReturnType<typeof setInterval> | null = null;
|
|
44
|
+
let lastViewport = {
|
|
45
|
+
cols: initialState.viewportCols,
|
|
46
|
+
rows: initialState.viewportRows,
|
|
47
|
+
};
|
|
25
48
|
|
|
26
49
|
type CreateRoutesFn = typeof createStarshipRoutes;
|
|
27
50
|
type RoutesModule = Readonly<{ createStarshipRoutes?: CreateRoutesFn }>;
|
|
@@ -44,6 +67,24 @@ function dispatch(action: StarshipAction): void {
|
|
|
44
67
|
}
|
|
45
68
|
}
|
|
46
69
|
|
|
70
|
+
function syncViewport(cols: number, rows: number): void {
|
|
71
|
+
if (cols === lastViewport.cols && rows === lastViewport.rows) return;
|
|
72
|
+
lastViewport = { cols, rows };
|
|
73
|
+
debugSnapshot("runtime.viewport", {
|
|
74
|
+
cols,
|
|
75
|
+
rows,
|
|
76
|
+
route: currentRouteId(),
|
|
77
|
+
});
|
|
78
|
+
dispatch({ type: "set-viewport", cols, rows });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function syncViewportFromStdout(): void {
|
|
82
|
+
if (!process.stdout.isTTY) return;
|
|
83
|
+
const cols = process.stdout.columns ?? lastViewport.cols;
|
|
84
|
+
const rows = process.stdout.rows ?? lastViewport.rows;
|
|
85
|
+
syncViewport(cols, rows);
|
|
86
|
+
}
|
|
87
|
+
|
|
47
88
|
function currentRouteId(): RouteId {
|
|
48
89
|
const routeId = app.router?.currentRoute().id;
|
|
49
90
|
if (routeId === "bridge") return "bridge";
|
|
@@ -59,7 +100,8 @@ function navigate(routeId: RouteId): void {
|
|
|
59
100
|
const router = app.router;
|
|
60
101
|
if (!router) return;
|
|
61
102
|
if (router.currentRoute().id === routeId) return;
|
|
62
|
-
|
|
103
|
+
// Top-level deck switches are peer navigation; replace avoids unbounded breadcrumb growth.
|
|
104
|
+
router.replace(routeId);
|
|
63
105
|
}
|
|
64
106
|
|
|
65
107
|
function navigateDeckOffset(offset: 1 | -1): void {
|
|
@@ -111,6 +153,12 @@ async function stopApp(code = 0): Promise<void> {
|
|
|
111
153
|
|
|
112
154
|
function applyCommand(command: ReturnType<typeof resolveStarshipCommand>): void {
|
|
113
155
|
if (!command) return;
|
|
156
|
+
debugSnapshot("runtime.command", {
|
|
157
|
+
command,
|
|
158
|
+
route: currentRouteId(),
|
|
159
|
+
viewportCols: lastViewport.cols,
|
|
160
|
+
viewportRows: lastViewport.rows,
|
|
161
|
+
});
|
|
114
162
|
|
|
115
163
|
if (command === "quit") {
|
|
116
164
|
void stopApp(0);
|
|
@@ -371,21 +419,45 @@ function bindKeys(): void {
|
|
|
371
419
|
) as Record<string, () => void>;
|
|
372
420
|
|
|
373
421
|
bindingMap.escape = () => {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
422
|
+
let nextTheme = initialState.themeName;
|
|
423
|
+
let themeChanged = false;
|
|
424
|
+
app.update((state) => {
|
|
425
|
+
if (state.showHelp) {
|
|
426
|
+
const next = reduceStarshipState(state, { type: "toggle-help" });
|
|
427
|
+
if (next.themeName !== state.themeName) {
|
|
428
|
+
nextTheme = next.themeName;
|
|
429
|
+
themeChanged = true;
|
|
430
|
+
}
|
|
431
|
+
return next;
|
|
432
|
+
}
|
|
433
|
+
if (state.showCommandPalette) {
|
|
434
|
+
const next = reduceStarshipState(state, { type: "toggle-command-palette" });
|
|
435
|
+
if (next.themeName !== state.themeName) {
|
|
436
|
+
nextTheme = next.themeName;
|
|
437
|
+
themeChanged = true;
|
|
438
|
+
}
|
|
439
|
+
return next;
|
|
440
|
+
}
|
|
441
|
+
if (state.showHailDialog) {
|
|
442
|
+
const next = reduceStarshipState(state, { type: "toggle-hail-dialog" });
|
|
443
|
+
if (next.themeName !== state.themeName) {
|
|
444
|
+
nextTheme = next.themeName;
|
|
445
|
+
themeChanged = true;
|
|
446
|
+
}
|
|
447
|
+
return next;
|
|
448
|
+
}
|
|
449
|
+
if (state.showResetDialog) {
|
|
450
|
+
const next = reduceStarshipState(state, { type: "toggle-reset-dialog" });
|
|
451
|
+
if (next.themeName !== state.themeName) {
|
|
452
|
+
nextTheme = next.themeName;
|
|
453
|
+
themeChanged = true;
|
|
454
|
+
}
|
|
455
|
+
return next;
|
|
456
|
+
}
|
|
457
|
+
return state;
|
|
458
|
+
});
|
|
459
|
+
if (themeChanged) {
|
|
460
|
+
app.setTheme(themeSpec(nextTheme).theme);
|
|
389
461
|
}
|
|
390
462
|
};
|
|
391
463
|
|
|
@@ -394,6 +466,13 @@ function bindKeys(): void {
|
|
|
394
466
|
|
|
395
467
|
const routes = buildRoutes(createStarshipRoutes);
|
|
396
468
|
|
|
469
|
+
debugSnapshot("runtime.app.create", {
|
|
470
|
+
routeCount: routes.length,
|
|
471
|
+
initialRoute: "bridge",
|
|
472
|
+
fpsCap: UI_FPS_CAP,
|
|
473
|
+
executionMode: "inline",
|
|
474
|
+
});
|
|
475
|
+
|
|
397
476
|
app = createNodeApp({
|
|
398
477
|
initialState,
|
|
399
478
|
routes,
|
|
@@ -421,24 +500,26 @@ app = createNodeApp({
|
|
|
421
500
|
});
|
|
422
501
|
|
|
423
502
|
bindKeys();
|
|
503
|
+
syncViewportFromStdout();
|
|
424
504
|
|
|
425
505
|
app.onEvent((event) => {
|
|
426
506
|
if (event.kind === "fatal") {
|
|
507
|
+
debugSnapshot("runtime.fatal", {
|
|
508
|
+
route: currentRouteId(),
|
|
509
|
+
viewportCols: lastViewport.cols,
|
|
510
|
+
viewportRows: lastViewport.rows,
|
|
511
|
+
});
|
|
427
512
|
void stopApp(1);
|
|
428
513
|
return;
|
|
429
514
|
}
|
|
430
515
|
|
|
431
516
|
if (event.kind === "engine" && event.event.kind === "resize") {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
message: `Viewport ${event.event.cols}x${event.event.rows}`,
|
|
437
|
-
level: "info",
|
|
438
|
-
timestamp: Date.now(),
|
|
439
|
-
durationMs: 1800,
|
|
440
|
-
},
|
|
517
|
+
debugSnapshot("runtime.resize.event", {
|
|
518
|
+
cols: event.event.cols,
|
|
519
|
+
rows: event.event.rows,
|
|
520
|
+
route: currentRouteId(),
|
|
441
521
|
});
|
|
522
|
+
syncViewport(event.event.cols, event.event.rows);
|
|
442
523
|
}
|
|
443
524
|
});
|
|
444
525
|
|
|
@@ -450,6 +531,7 @@ process.once("SIGINT", onSignal);
|
|
|
450
531
|
process.once("SIGTERM", onSignal);
|
|
451
532
|
|
|
452
533
|
tickTimer = setInterval(() => {
|
|
534
|
+
syncViewportFromStdout();
|
|
453
535
|
dispatch({ type: "tick", nowMs: Date.now() });
|
|
454
536
|
}, TICK_MS);
|
|
455
537
|
|
|
@@ -457,5 +539,11 @@ toastTimer = setInterval(() => {
|
|
|
457
539
|
dispatch({ type: "prune-toasts", nowMs: Date.now() });
|
|
458
540
|
}, TOAST_PRUNE_MS);
|
|
459
541
|
|
|
542
|
+
debugSnapshot("runtime.app.start", {
|
|
543
|
+
route: currentRouteId(),
|
|
544
|
+
viewportCols: lastViewport.cols,
|
|
545
|
+
viewportRows: lastViewport.rows,
|
|
546
|
+
});
|
|
547
|
+
|
|
460
548
|
await app.start();
|
|
461
549
|
await new Promise<void>(() => {});
|