4runr-os 1.0.79 → 1.0.80
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/dist/ui/v3/section0/index.d.ts +22 -0
- package/dist/ui/v3/section0/index.d.ts.map +1 -0
- package/dist/ui/v3/section0/index.js +88 -0
- package/dist/ui/v3/section0/index.js.map +1 -0
- package/dist/ui/v3/section0/runtime/createScreen.d.ts +27 -0
- package/dist/ui/v3/section0/runtime/createScreen.d.ts.map +1 -0
- package/dist/ui/v3/section0/runtime/createScreen.js +55 -0
- package/dist/ui/v3/section0/runtime/createScreen.js.map +1 -0
- package/dist/ui/v3/section0/runtime/lifecycle.d.ts +53 -0
- package/dist/ui/v3/section0/runtime/lifecycle.d.ts.map +1 -0
- package/dist/ui/v3/section0/runtime/lifecycle.js +172 -0
- package/dist/ui/v3/section0/runtime/lifecycle.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Section 0 - Stable App Shell
|
|
4
|
+
*
|
|
5
|
+
* Objective: Build a minimal TUI app shell that:
|
|
6
|
+
* - starts reliably
|
|
7
|
+
* - exits cleanly
|
|
8
|
+
* - restores the terminal every time
|
|
9
|
+
* - does not register duplicate handlers or multiple render loops
|
|
10
|
+
*
|
|
11
|
+
* No UI panels, no commands, no collectors yet. Just a stable shell.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Start Section 0 TUI shell
|
|
15
|
+
*
|
|
16
|
+
* Rules:
|
|
17
|
+
* - One render loop only (or none for Section 0 - just render once)
|
|
18
|
+
* - No console.log() while TUI is active
|
|
19
|
+
* - Exit must always restore terminal state
|
|
20
|
+
*/
|
|
21
|
+
export declare function startSection0(): Promise<void>;
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ui/v3/section0/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAaH;;;;;;;GAOG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAwDnD"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Section 0 - Stable App Shell
|
|
4
|
+
*
|
|
5
|
+
* Objective: Build a minimal TUI app shell that:
|
|
6
|
+
* - starts reliably
|
|
7
|
+
* - exits cleanly
|
|
8
|
+
* - restores the terminal every time
|
|
9
|
+
* - does not register duplicate handlers or multiple render loops
|
|
10
|
+
*
|
|
11
|
+
* No UI panels, no commands, no collectors yet. Just a stable shell.
|
|
12
|
+
*/
|
|
13
|
+
import { createScreen } from './runtime/createScreen.js';
|
|
14
|
+
import { initializeLifecycle } from './runtime/lifecycle.js';
|
|
15
|
+
import blessed from 'neo-blessed';
|
|
16
|
+
const blessedLib = blessed;
|
|
17
|
+
// Suppress console.log during TUI (write debug logs to file if needed)
|
|
18
|
+
const originalConsoleLog = console.log;
|
|
19
|
+
const originalConsoleError = console.error;
|
|
20
|
+
/**
|
|
21
|
+
* Start Section 0 TUI shell
|
|
22
|
+
*
|
|
23
|
+
* Rules:
|
|
24
|
+
* - One render loop only (or none for Section 0 - just render once)
|
|
25
|
+
* - No console.log() while TUI is active
|
|
26
|
+
* - Exit must always restore terminal state
|
|
27
|
+
*/
|
|
28
|
+
export async function startSection0() {
|
|
29
|
+
// Suppress console.log during TUI
|
|
30
|
+
console.log = () => { };
|
|
31
|
+
// Keep console.error for fatal errors (but restore before using)
|
|
32
|
+
let consoleErrorRestored = false;
|
|
33
|
+
const restoreConsoleError = () => {
|
|
34
|
+
if (!consoleErrorRestored) {
|
|
35
|
+
console.error = originalConsoleError;
|
|
36
|
+
consoleErrorRestored = true;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
// Create screen (singleton, guarded)
|
|
41
|
+
const screen = createScreen();
|
|
42
|
+
// Initialize lifecycle (exit handlers, guarded against duplicates)
|
|
43
|
+
initializeLifecycle(screen);
|
|
44
|
+
// Create a single centered text node
|
|
45
|
+
const message = '4Runr OS V3 — Shell OK (press q to exit)';
|
|
46
|
+
const textNode = blessedLib.box({
|
|
47
|
+
top: Math.floor((screen.height - 1) / 2),
|
|
48
|
+
left: Math.floor((screen.width - message.length) / 2),
|
|
49
|
+
width: message.length,
|
|
50
|
+
height: 1,
|
|
51
|
+
content: message,
|
|
52
|
+
tags: true,
|
|
53
|
+
style: {
|
|
54
|
+
fg: 'cyan',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
screen.append(textNode);
|
|
58
|
+
// Single render (no loop for Section 0)
|
|
59
|
+
screen.render();
|
|
60
|
+
// Restore console.log after successful start (for future use if needed)
|
|
61
|
+
// But keep it suppressed during TUI operation
|
|
62
|
+
// console.log = originalConsoleLog; // Commented: keep suppressed during TUI
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
// Restore console.error for error output
|
|
66
|
+
restoreConsoleError();
|
|
67
|
+
console.error('FATAL: Section 0 TUI failed to start:', error);
|
|
68
|
+
// Ensure terminal is restored even on error
|
|
69
|
+
if (process.stdout.isTTY) {
|
|
70
|
+
process.stdout.write('\x1b[?25h'); // Show cursor
|
|
71
|
+
process.stdout.write('\x1b[?1049l'); // Exit alternate screen
|
|
72
|
+
}
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// If run directly (not imported), start the TUI
|
|
77
|
+
// Simple check: if this file is executed directly, start the TUI
|
|
78
|
+
const scriptPath = process.argv[1]?.replace(/\\/g, '/') || '';
|
|
79
|
+
const isMainModule = scriptPath.includes('section0/index.js') ||
|
|
80
|
+
scriptPath.includes('section0/index.ts') ||
|
|
81
|
+
import.meta.url.includes('section0/index');
|
|
82
|
+
if (isMainModule) {
|
|
83
|
+
startSection0().catch((error) => {
|
|
84
|
+
console.error('FATAL: Section 0 failed:', error);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/ui/v3/section0/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section 0 - Create Screen
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Initialize neo-blessed screen cleanly
|
|
6
|
+
* - Ensure only one screen instance exists
|
|
7
|
+
* - Configure screen with proper settings
|
|
8
|
+
*/
|
|
9
|
+
import type { Widgets } from 'neo-blessed';
|
|
10
|
+
/**
|
|
11
|
+
* Create or get the singleton screen instance
|
|
12
|
+
*
|
|
13
|
+
* Rules:
|
|
14
|
+
* - Must only create one screen instance
|
|
15
|
+
* - If screen already exists, return it (do not create another)
|
|
16
|
+
* - Configure with smartCSR, fullUnicode, dockBorders, warnings: false
|
|
17
|
+
*/
|
|
18
|
+
export declare function createScreen(): Widgets.Screen;
|
|
19
|
+
/**
|
|
20
|
+
* Get the current screen instance (may be null)
|
|
21
|
+
*/
|
|
22
|
+
export declare function getScreen(): Widgets.Screen | null;
|
|
23
|
+
/**
|
|
24
|
+
* Clear the screen instance reference (for testing/cleanup)
|
|
25
|
+
*/
|
|
26
|
+
export declare function clearScreen(): void;
|
|
27
|
+
//# sourceMappingURL=createScreen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/v3/section0/runtime/createScreen.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAM3C;;;;;;;GAOG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAAC,MAAM,CAyB7C;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAEjD;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section 0 - Create Screen
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Initialize neo-blessed screen cleanly
|
|
6
|
+
* - Ensure only one screen instance exists
|
|
7
|
+
* - Configure screen with proper settings
|
|
8
|
+
*/
|
|
9
|
+
import blessed from 'neo-blessed';
|
|
10
|
+
const blessedLib = blessed;
|
|
11
|
+
let screenInstance = null;
|
|
12
|
+
/**
|
|
13
|
+
* Create or get the singleton screen instance
|
|
14
|
+
*
|
|
15
|
+
* Rules:
|
|
16
|
+
* - Must only create one screen instance
|
|
17
|
+
* - If screen already exists, return it (do not create another)
|
|
18
|
+
* - Configure with smartCSR, fullUnicode, dockBorders, warnings: false
|
|
19
|
+
*/
|
|
20
|
+
export function createScreen() {
|
|
21
|
+
if (screenInstance) {
|
|
22
|
+
return screenInstance;
|
|
23
|
+
}
|
|
24
|
+
screenInstance = blessedLib.screen({
|
|
25
|
+
smartCSR: true,
|
|
26
|
+
fullUnicode: true,
|
|
27
|
+
dockBorders: true,
|
|
28
|
+
warnings: false,
|
|
29
|
+
title: '4Runr OS (Terminal UI V3)',
|
|
30
|
+
});
|
|
31
|
+
if (!screenInstance) {
|
|
32
|
+
throw new Error('Failed to create blessed screen');
|
|
33
|
+
}
|
|
34
|
+
// Hide cursor on start (will be restored on exit)
|
|
35
|
+
if (screenInstance.program && screenInstance.program.hideCursor) {
|
|
36
|
+
screenInstance.program.hideCursor();
|
|
37
|
+
}
|
|
38
|
+
else if (process.stdout.isTTY) {
|
|
39
|
+
process.stdout.write('\x1b[?25l'); // ANSI hide cursor
|
|
40
|
+
}
|
|
41
|
+
return screenInstance;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get the current screen instance (may be null)
|
|
45
|
+
*/
|
|
46
|
+
export function getScreen() {
|
|
47
|
+
return screenInstance;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Clear the screen instance reference (for testing/cleanup)
|
|
51
|
+
*/
|
|
52
|
+
export function clearScreen() {
|
|
53
|
+
screenInstance = null;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=createScreen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createScreen.js","sourceRoot":"","sources":["../../../../../src/ui/v3/section0/runtime/createScreen.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,OAAO,MAAM,aAAa,CAAC;AAGlC,MAAM,UAAU,GAAG,OAAc,CAAC;AAElC,IAAI,cAAc,GAA0B,IAAI,CAAC;AAEjD;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY;IAC1B,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,cAAc,GAAG,UAAU,CAAC,MAAM,CAAC;QACjC,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,IAAI;QACjB,WAAW,EAAE,IAAI;QACjB,QAAQ,EAAE,KAAK;QACf,KAAK,EAAE,2BAA2B;KACnC,CAAmB,CAAC;IAErB,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IAED,kDAAkD;IAClD,IAAK,cAAsB,CAAC,OAAO,IAAK,cAAsB,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;QACjF,cAAsB,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;IAC/C,CAAC;SAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,mBAAmB;IACxD,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,cAAc,GAAG,IAAI,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section 0 - Lifecycle Management
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Attach exit handlers (single registration, guarded)
|
|
6
|
+
* - Implement safe shutdown that always restores terminal
|
|
7
|
+
* - Track handler attachment to prevent duplicates
|
|
8
|
+
*/
|
|
9
|
+
import type { Widgets } from 'neo-blessed';
|
|
10
|
+
/**
|
|
11
|
+
* Attach exit handlers to screen
|
|
12
|
+
*
|
|
13
|
+
* Rules:
|
|
14
|
+
* - Must attach handlers exactly once (protected by exitHandlersAttached guard)
|
|
15
|
+
* - If called again, must no-op
|
|
16
|
+
* - Registers: q → exit, C-c → exit, escape → exit (optional)
|
|
17
|
+
*/
|
|
18
|
+
export declare function attachExitHandlers(screen: Widgets.Screen): void;
|
|
19
|
+
/**
|
|
20
|
+
* Attach process-level exit handlers
|
|
21
|
+
*
|
|
22
|
+
* Rules:
|
|
23
|
+
* - Must attach handlers exactly once (protected by exitHandlersAttached guard)
|
|
24
|
+
* - If called again, must no-op
|
|
25
|
+
* - Registers: SIGINT, SIGTERM
|
|
26
|
+
* - Tracks listeners for cleanup
|
|
27
|
+
*/
|
|
28
|
+
export declare function attachProcessExitHandlers(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Initialize all exit handlers (screen + process)
|
|
31
|
+
*
|
|
32
|
+
* This is the main entry point for lifecycle setup.
|
|
33
|
+
*/
|
|
34
|
+
export declare function initializeLifecycle(screen: Widgets.Screen): void;
|
|
35
|
+
/**
|
|
36
|
+
* Get internal debug counters (for checkpoint verification)
|
|
37
|
+
*
|
|
38
|
+
* These counters verify:
|
|
39
|
+
* - Exit handlers attach exactly once
|
|
40
|
+
* - Shutdown runs exactly once
|
|
41
|
+
*
|
|
42
|
+
* Example expectation: Pressing q triggers exactly one shutdown path.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getDebugCounters(): {
|
|
45
|
+
shutdownCallCount: number;
|
|
46
|
+
screenHandlerAttachmentCount: number;
|
|
47
|
+
processHandlerAttachmentCount: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Reset lifecycle state (for testing only)
|
|
51
|
+
*/
|
|
52
|
+
export declare function resetLifecycle(): void;
|
|
53
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../../src/ui/v3/section0/runtime/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAmF3C;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAsB/D;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAuBhD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAGhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI;IAClC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4BAA4B,EAAE,MAAM,CAAC;IACrC,6BAA6B,EAAE,MAAM,CAAC;CACvC,CAMA;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAQrC"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section 0 - Lifecycle Management
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Attach exit handlers (single registration, guarded)
|
|
6
|
+
* - Implement safe shutdown that always restores terminal
|
|
7
|
+
* - Track handler attachment to prevent duplicates
|
|
8
|
+
*/
|
|
9
|
+
import { getScreen } from './createScreen.js';
|
|
10
|
+
// Guards to prevent duplicate handler attachment
|
|
11
|
+
let screenHandlersAttached = false;
|
|
12
|
+
let processHandlersAttached = false;
|
|
13
|
+
let shuttingDown = false;
|
|
14
|
+
// Internal debug counters (NOT console.log - for checkpoint verification)
|
|
15
|
+
// These track handler attachment and shutdown calls
|
|
16
|
+
let shutdownCallCount = 0;
|
|
17
|
+
let screenHandlerAttachmentCount = 0;
|
|
18
|
+
let processHandlerAttachmentCount = 0;
|
|
19
|
+
// Track attached process listeners for cleanup
|
|
20
|
+
const attachedProcessListeners = [];
|
|
21
|
+
/**
|
|
22
|
+
* Shutdown function that always restores terminal state
|
|
23
|
+
*
|
|
24
|
+
* Rules:
|
|
25
|
+
* - Must run exactly once (protected by shuttingDown guard)
|
|
26
|
+
* - Steps (in order):
|
|
27
|
+
* 1. screen.program.showCursor()
|
|
28
|
+
* 2. screen.destroy() (restores terminal modes)
|
|
29
|
+
* 3. Remove any process listeners we attached
|
|
30
|
+
* 4. process.exit(0)
|
|
31
|
+
*/
|
|
32
|
+
function shutdown(reason) {
|
|
33
|
+
// Internal debug counter
|
|
34
|
+
shutdownCallCount++;
|
|
35
|
+
// Guard: ensure shutdown runs exactly once
|
|
36
|
+
if (shuttingDown) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
shuttingDown = true;
|
|
40
|
+
const screen = getScreen();
|
|
41
|
+
if (screen) {
|
|
42
|
+
try {
|
|
43
|
+
// Step 1: Show cursor
|
|
44
|
+
if (screen.program && screen.program.showCursor) {
|
|
45
|
+
screen.program.showCursor();
|
|
46
|
+
}
|
|
47
|
+
else if (process.stdout.isTTY) {
|
|
48
|
+
process.stdout.write('\x1b[?25h'); // ANSI show cursor
|
|
49
|
+
}
|
|
50
|
+
// Step 2: Destroy screen (restores terminal modes)
|
|
51
|
+
screen.destroy();
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
// Ignore destroy errors, but ensure cursor is shown
|
|
55
|
+
if (process.stdout.isTTY) {
|
|
56
|
+
process.stdout.write('\x1b[?25h');
|
|
57
|
+
process.stdout.write('\x1b[?1049l'); // Exit alternate screen if needed
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// No screen, but ensure cursor is visible
|
|
63
|
+
if (process.stdout.isTTY) {
|
|
64
|
+
process.stdout.write('\x1b[?25h');
|
|
65
|
+
process.stdout.write('\x1b[?1049l');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Step 3: Remove process listeners we attached
|
|
69
|
+
for (const { event, listener } of attachedProcessListeners) {
|
|
70
|
+
try {
|
|
71
|
+
process.removeListener(event, listener);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
// Ignore removal errors
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
attachedProcessListeners.length = 0;
|
|
78
|
+
// Step 4: Exit
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Attach exit handlers to screen
|
|
83
|
+
*
|
|
84
|
+
* Rules:
|
|
85
|
+
* - Must attach handlers exactly once (protected by exitHandlersAttached guard)
|
|
86
|
+
* - If called again, must no-op
|
|
87
|
+
* - Registers: q → exit, C-c → exit, escape → exit (optional)
|
|
88
|
+
*/
|
|
89
|
+
export function attachExitHandlers(screen) {
|
|
90
|
+
// Guard: ensure handlers are attached exactly once
|
|
91
|
+
if (screenHandlersAttached) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
screenHandlersAttached = true;
|
|
95
|
+
screenHandlerAttachmentCount++;
|
|
96
|
+
// q → exit
|
|
97
|
+
screen.key(['q', 'Q'], () => {
|
|
98
|
+
shutdown('q pressed');
|
|
99
|
+
});
|
|
100
|
+
// C-c → exit
|
|
101
|
+
screen.key(['C-c'], () => {
|
|
102
|
+
shutdown('Ctrl+C pressed');
|
|
103
|
+
});
|
|
104
|
+
// escape → exit (optional, as requested)
|
|
105
|
+
screen.key(['escape'], () => {
|
|
106
|
+
shutdown('Escape pressed');
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Attach process-level exit handlers
|
|
111
|
+
*
|
|
112
|
+
* Rules:
|
|
113
|
+
* - Must attach handlers exactly once (protected by exitHandlersAttached guard)
|
|
114
|
+
* - If called again, must no-op
|
|
115
|
+
* - Registers: SIGINT, SIGTERM
|
|
116
|
+
* - Tracks listeners for cleanup
|
|
117
|
+
*/
|
|
118
|
+
export function attachProcessExitHandlers() {
|
|
119
|
+
// Guard: ensure handlers are attached exactly once
|
|
120
|
+
if (processHandlersAttached) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
processHandlersAttached = true;
|
|
124
|
+
processHandlerAttachmentCount++;
|
|
125
|
+
const sigintHandler = () => {
|
|
126
|
+
shutdown('SIGINT received');
|
|
127
|
+
};
|
|
128
|
+
const sigtermHandler = () => {
|
|
129
|
+
shutdown('SIGTERM received');
|
|
130
|
+
};
|
|
131
|
+
process.on('SIGINT', sigintHandler);
|
|
132
|
+
process.on('SIGTERM', sigtermHandler);
|
|
133
|
+
attachedProcessListeners.push({ event: 'SIGINT', listener: sigintHandler }, { event: 'SIGTERM', listener: sigtermHandler });
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Initialize all exit handlers (screen + process)
|
|
137
|
+
*
|
|
138
|
+
* This is the main entry point for lifecycle setup.
|
|
139
|
+
*/
|
|
140
|
+
export function initializeLifecycle(screen) {
|
|
141
|
+
attachExitHandlers(screen);
|
|
142
|
+
attachProcessExitHandlers();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get internal debug counters (for checkpoint verification)
|
|
146
|
+
*
|
|
147
|
+
* These counters verify:
|
|
148
|
+
* - Exit handlers attach exactly once
|
|
149
|
+
* - Shutdown runs exactly once
|
|
150
|
+
*
|
|
151
|
+
* Example expectation: Pressing q triggers exactly one shutdown path.
|
|
152
|
+
*/
|
|
153
|
+
export function getDebugCounters() {
|
|
154
|
+
return {
|
|
155
|
+
shutdownCallCount,
|
|
156
|
+
screenHandlerAttachmentCount,
|
|
157
|
+
processHandlerAttachmentCount,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Reset lifecycle state (for testing only)
|
|
162
|
+
*/
|
|
163
|
+
export function resetLifecycle() {
|
|
164
|
+
screenHandlersAttached = false;
|
|
165
|
+
processHandlersAttached = false;
|
|
166
|
+
shuttingDown = false;
|
|
167
|
+
attachedProcessListeners.length = 0;
|
|
168
|
+
shutdownCallCount = 0;
|
|
169
|
+
screenHandlerAttachmentCount = 0;
|
|
170
|
+
processHandlerAttachmentCount = 0;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../../../../src/ui/v3/section0/runtime/lifecycle.ts"],"names":[],"mappings":"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"}
|