novac 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +0 -0
  3. package/demo.nv +0 -0
  4. package/demo_builtins.nv +0 -0
  5. package/demo_http.nv +0 -0
  6. package/examples/bf.nv +69 -0
  7. package/examples/math.nv +21 -0
  8. package/kits/birdAPI/kitdef.js +954 -0
  9. package/kits/kitRNG/kitdef.js +740 -0
  10. package/kits/kitSSH/kitdef.js +1272 -0
  11. package/kits/kitadb/kitdef.js +606 -0
  12. package/kits/kitai/kitdef.js +2185 -0
  13. package/kits/kitcanvas/kitdef.js +914 -0
  14. package/kits/kitclippy/kitdef.js +925 -0
  15. package/kits/kitgps/kitdef.js +1862 -0
  16. package/kits/kitlibproc/kitdef.js +3 -2
  17. package/kits/kitmorse/kitdef.js +229 -0
  18. package/kits/kitmpatch/kitdef.js +906 -0
  19. package/kits/kitnet/kitdef.js +1401 -0
  20. package/kits/kitproto/kitdef.js +613 -0
  21. package/kits/kitqr/kitdef.js +637 -0
  22. package/kits/kitrequire/kitdef.js +1599 -0
  23. package/kits/libtea/kitdef.js +2691 -0
  24. package/kits/libterm/kitdef.js +2 -0
  25. package/novac/LICENSE +21 -0
  26. package/novac/README.md +1823 -0
  27. package/novac/bin/novac +950 -0
  28. package/novac/bin/nvc +522 -0
  29. package/novac/bin/nvml +542 -0
  30. package/novac/demo.nv +245 -0
  31. package/novac/demo_builtins.nv +209 -0
  32. package/novac/demo_http.nv +62 -0
  33. package/novac/examples/bf.nv +69 -0
  34. package/novac/examples/math.nv +21 -0
  35. package/novac/kits/kitai/kitdef.js +2185 -0
  36. package/novac/kits/kitansi/kitdef.js +1402 -0
  37. package/novac/kits/kitformat/kitdef.js +1485 -0
  38. package/novac/kits/kitgps/kitdef.js +1862 -0
  39. package/novac/kits/kitlibfs/kitdef.js +231 -0
  40. package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
  41. package/novac/kits/kitmatrix/ex.js +19 -0
  42. package/novac/kits/kitmatrix/kitdef.js +960 -0
  43. package/novac/kits/kitmpatch/kitdef.js +906 -0
  44. package/novac/kits/kitnovacweb/README.md +1572 -0
  45. package/novac/kits/kitnovacweb/demo.nv +12 -0
  46. package/novac/kits/kitnovacweb/demo.nvml +71 -0
  47. package/novac/kits/kitnovacweb/index.nova +12 -0
  48. package/novac/kits/kitnovacweb/kitdef.js +692 -0
  49. package/novac/kits/kitnovacweb/nova.kit.json +8 -0
  50. package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
  51. package/novac/kits/kitnovacweb/nvml/index.js +67 -0
  52. package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
  53. package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
  54. package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
  55. package/novac/kits/kitparse/kitdef.js +1688 -0
  56. package/novac/kits/kitregex++/kitdef.js +1353 -0
  57. package/novac/kits/kitrequire/kitdef.js +1599 -0
  58. package/novac/kits/kitx11/kitdef.js +1 -0
  59. package/novac/kits/kitx11/kitx11.js +2472 -0
  60. package/novac/kits/kitx11/kitx11_conn.js +948 -0
  61. package/novac/kits/kitx11/kitx11_worker.js +121 -0
  62. package/novac/kits/libterm/ex.js +285 -0
  63. package/novac/kits/libterm/kitdef.js +1927 -0
  64. package/novac/node_modules/chalk/license +9 -0
  65. package/novac/node_modules/chalk/package.json +83 -0
  66. package/novac/node_modules/chalk/readme.md +297 -0
  67. package/novac/node_modules/chalk/source/index.d.ts +325 -0
  68. package/novac/node_modules/chalk/source/index.js +225 -0
  69. package/novac/node_modules/chalk/source/utilities.js +33 -0
  70. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  71. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  72. package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  73. package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  74. package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  75. package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  76. package/novac/node_modules/commander/LICENSE +22 -0
  77. package/novac/node_modules/commander/Readme.md +1176 -0
  78. package/novac/node_modules/commander/esm.mjs +16 -0
  79. package/novac/node_modules/commander/index.js +24 -0
  80. package/novac/node_modules/commander/lib/argument.js +150 -0
  81. package/novac/node_modules/commander/lib/command.js +2777 -0
  82. package/novac/node_modules/commander/lib/error.js +39 -0
  83. package/novac/node_modules/commander/lib/help.js +747 -0
  84. package/novac/node_modules/commander/lib/option.js +380 -0
  85. package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
  86. package/novac/node_modules/commander/package-support.json +19 -0
  87. package/novac/node_modules/commander/package.json +82 -0
  88. package/novac/node_modules/commander/typings/esm.d.mts +3 -0
  89. package/novac/node_modules/commander/typings/index.d.ts +1113 -0
  90. package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
  91. package/novac/node_modules/node-addon-api/README.md +95 -0
  92. package/novac/node_modules/node-addon-api/common.gypi +21 -0
  93. package/novac/node_modules/node-addon-api/except.gypi +25 -0
  94. package/novac/node_modules/node-addon-api/index.js +14 -0
  95. package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  96. package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
  97. package/novac/node_modules/node-addon-api/napi.h +3364 -0
  98. package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
  99. package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
  100. package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
  101. package/novac/node_modules/node-addon-api/package-support.json +21 -0
  102. package/novac/node_modules/node-addon-api/package.json +480 -0
  103. package/novac/node_modules/node-addon-api/tools/README.md +73 -0
  104. package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
  105. package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
  106. package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
  107. package/novac/node_modules/serialize-javascript/LICENSE +27 -0
  108. package/novac/node_modules/serialize-javascript/README.md +149 -0
  109. package/novac/node_modules/serialize-javascript/index.js +297 -0
  110. package/novac/node_modules/serialize-javascript/package.json +33 -0
  111. package/novac/package.json +27 -0
  112. package/novac/scripts/update-bin.js +24 -0
  113. package/novac/src/core/bstd.js +1035 -0
  114. package/novac/src/core/config.js +155 -0
  115. package/novac/src/core/describe.js +187 -0
  116. package/novac/src/core/emitter.js +499 -0
  117. package/novac/src/core/error.js +86 -0
  118. package/novac/src/core/executor.js +5606 -0
  119. package/novac/src/core/formatter.js +686 -0
  120. package/novac/src/core/lexer.js +1026 -0
  121. package/novac/src/core/nova_builtins.js +717 -0
  122. package/novac/src/core/nova_thread_worker.js +166 -0
  123. package/novac/src/core/parser.js +2181 -0
  124. package/novac/src/core/types.js +112 -0
  125. package/novac/src/index.js +28 -0
  126. package/novac/src/runtime/stdlib.js +244 -0
  127. package/package.json +3 -2
  128. package/scripts/update-bin.js +0 -0
  129. package/src/core/bstd.js +835 -361
  130. package/src/core/executor.js +427 -246
  131. package/src/core/lexer.js +19 -2
  132. package/src/core/parser.js +13 -0
  133. package/src/index.js +0 -0
  134. package/examples/example-project/README.md +0 -3
  135. package/examples/example-project/src/main.nova +0 -3
  136. package/src/core/environment.js +0 -0
  137. /package/{kits → novac/kits}/libtea/tf.js +0 -0
  138. /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
@@ -0,0 +1,121 @@
1
+ 'use strict';
2
+ // ============================================================
3
+ // kitx11_worker.js — Socket worker thread
4
+ //
5
+ // Owns the TCP or Unix-domain socket to the X11 server.
6
+ // Uses a SharedArrayBuffer to signal connect success/failure
7
+ // back to the main thread synchronously via Atomics.
8
+ //
9
+ // Message protocol (main → worker):
10
+ // { cmd: 'write', data: Uint8Array } — send bytes (no reply)
11
+ // { cmd: 'read', n: number, port: Port } — read n bytes, reply on port
12
+ // { cmd: 'close' } — close and exit
13
+ // ============================================================
14
+
15
+ const { parentPort, workerData } = require('worker_threads');
16
+ const net = require('net');
17
+
18
+ const { display, signalSab } = workerData;
19
+ const signal = new Int32Array(signalSab); // signal[0]: 0=waiting, 1=ok, 2=error
20
+
21
+ let readBuf = Buffer.alloc(0);
22
+ let readWaiter = null; // { n, port }
23
+
24
+ function tryFlushRead() {
25
+ if (!readWaiter) return;
26
+ const { n, port } = readWaiter;
27
+ if (readBuf.length >= n) {
28
+ const chunk = readBuf.slice(0, n);
29
+ readBuf = readBuf.slice(n);
30
+ readWaiter = null;
31
+ const arr = new Uint8Array(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength));
32
+ port.postMessage({ ok: true, data: arr }, [arr.buffer]);
33
+ }
34
+ }
35
+
36
+ function attachSocketHandlers(sock) {
37
+ sock.on('data', chunk => {
38
+ readBuf = Buffer.concat([readBuf, chunk]);
39
+ tryFlushRead();
40
+ });
41
+ sock.on('error', err => {
42
+ if (readWaiter) {
43
+ readWaiter.port.postMessage({ ok: false, error: err.message });
44
+ readWaiter = null;
45
+ }
46
+ });
47
+ sock.on('close', () => {
48
+ if (readWaiter) {
49
+ readWaiter.port.postMessage({ ok: false, error: 'Socket closed unexpectedly' });
50
+ readWaiter = null;
51
+ }
52
+ });
53
+ }
54
+
55
+ function signalError(msg) {
56
+ parentPort.postMessage({ ok: false, error: msg });
57
+ Atomics.store(signal, 0, 2);
58
+ Atomics.notify(signal, 0);
59
+ }
60
+
61
+ function signalConnected() {
62
+ Atomics.store(signal, 0, 1);
63
+ Atomics.notify(signal, 0);
64
+ }
65
+
66
+ // ── Parse DISPLAY string ─────────────────────────────────────
67
+ const m = display.match(/^(.*):(\d+)(?:\.(\d+))?$/);
68
+ if (!m) { signalError(`Cannot parse DISPLAY string: "${display}"`); process.exit(1); }
69
+
70
+ const host = m[1] || '';
71
+ const displayNum = parseInt(m[2], 10);
72
+ const unixPath = `/tmp/.X11-unix/X${displayNum}`;
73
+ const tcpPort = 6000 + displayNum;
74
+ const tcpHost = host || '127.0.0.1';
75
+
76
+ // ── Connect: try Unix socket first, then TCP ─────────────────
77
+ let socket;
78
+
79
+ function tryTCP() {
80
+ socket = net.createConnection({ host: tcpHost, port: tcpPort });
81
+ attachSocketHandlers(socket);
82
+ socket.once('connect', () => { listenForCommands(); signalConnected(); });
83
+ socket.once('error', err => {
84
+ signalError(
85
+ `Could not connect via unix:${unixPath} or tcp:${tcpHost}:${tcpPort} — ${err.message}`
86
+ );
87
+ });
88
+ }
89
+
90
+ socket = net.createConnection({ path: unixPath });
91
+ attachSocketHandlers(socket);
92
+ socket.once('connect', () => { listenForCommands(); signalConnected(); });
93
+ socket.once('error', () => { socket.removeAllListeners(); tryTCP(); });
94
+
95
+ // ── Command handler ───────────────────────────────────────────
96
+ function listenForCommands() {
97
+ parentPort.on('message', ({ cmd, data, n, port }) => {
98
+ switch (cmd) {
99
+ case 'write': {
100
+ socket.write(Buffer.from(data.buffer, data.byteOffset, data.byteLength));
101
+ break;
102
+ }
103
+ case 'read': {
104
+ if (readBuf.length >= n) {
105
+ const chunk = readBuf.slice(0, n);
106
+ readBuf = readBuf.slice(n);
107
+ const arr = new Uint8Array(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength));
108
+ port.postMessage({ ok: true, data: arr }, [arr.buffer]);
109
+ } else {
110
+ readWaiter = { n, port };
111
+ }
112
+ break;
113
+ }
114
+ case 'close': {
115
+ socket.destroy();
116
+ process.exit(0);
117
+ break;
118
+ }
119
+ }
120
+ });
121
+ }
@@ -0,0 +1,285 @@
1
+ 'use strict';
2
+ /**
3
+ * kitdef-demo.js — full showcase of all kitdef subsystems
4
+ * Run: node kitdef-demo.js
5
+ */
6
+
7
+ const { kitdef } = require('./kitdef');
8
+ const {
9
+ Terminal, TWidget, TRouter, TShell, TEnv, TLog, TTheme,
10
+ TView, TVPixel, inputManager, ansi, px,
11
+ } = kitdef;
12
+
13
+ // ─────────────────────────────────────────────────────────────────────────────
14
+ // Boot Terminal (starts input, enters alt-screen, wires resize)
15
+ // ─────────────────────────────────────────────────────────────────────────────
16
+
17
+ const term = new Terminal({
18
+ altScreen: true,
19
+ mouse: true,
20
+ theme: 'dark',
21
+ logLevel: 'debug',
22
+ });
23
+
24
+ // ─────────────────────────────────────────────────────────────────────────────
25
+ // TEnv — define + load config schema
26
+ // ─────────────────────────────────────────────────────────────────────────────
27
+
28
+ term.env
29
+ .define('APP_NAME', { type:'string', default:'kitdef-demo' })
30
+ .define('LOG_LEVEL', { type:'enum', values:['trace','debug','info','warn','error'], default:'debug' })
31
+ .define('PORT', { type:'int', default:3000 })
32
+ .define('VERBOSE', { type:'bool', default:false })
33
+ .load();
34
+
35
+ // ─────────────────────────────────────────────────────────────────────────────
36
+ // Layout: top bar / [left pane | right pane] / sensor bar / bottom bar
37
+ // ─────────────────────────────────────────────────────────────────────────────
38
+
39
+ term.on('ready', async () => {
40
+ const [topBar, middle, bottomBar] = term.screen.cutWeighted([0.07, 0.86, 0.07]);
41
+ const [leftPane, rightPane] = middle.cutHorizontal(2);
42
+
43
+ // ── TTheme — paint with semantic tokens ──────────────────────────────────
44
+
45
+ const T = term.theme; // active TTheme
46
+ topBar.fill(0, 0, topBar.rows, topBar.cols, { color: [null, T.get('bgAlt')] });
47
+ topBar.writeCentered(0,
48
+ `★ kitdef v1.0 | ${term.env.get('APP_NAME')} | port=${term.env.get('PORT')} ★`,
49
+ { color: [T.get('primary'), T.get('bgAlt')], style: 'bold' });
50
+ if (topBar.rows > 1) {
51
+ topBar.writeText(1, 1,
52
+ '[1] Menu [2] List [3] Dialog [4] Shell [5] Env [6] Theme Tab=focus Ctrl-C=exit',
53
+ { color: [T.get('fgMuted'), T.get('bgAlt')] });
54
+ }
55
+ topBar.render();
56
+
57
+ // ── TLog — pipe into rightPane ────────────────────────────────────────────
58
+
59
+ term.log.setView(rightPane);
60
+ rightPane.border = true;
61
+ rightPane.borderStyle = 'rounded';
62
+ rightPane.title = ' Log ';
63
+ rightPane.scrollable = true;
64
+ rightPane.onScroll('up', () => rightPane.scrollUp(3));
65
+ rightPane.onScroll('down', () => rightPane.scrollDown(3));
66
+ rightPane.onArrow('up', () => rightPane.scrollUp(1));
67
+ rightPane.onArrow('down', () => rightPane.scrollDown(1));
68
+ rightPane.onFocus(() => { rightPane.borderStyle='heavy'; rightPane.markDirty(); });
69
+ rightPane.onBlur(() => { rightPane.borderStyle='rounded'; rightPane.markDirty(); });
70
+ term.log.info('kitdef ready', { theme: T.name, port: term.env.get('PORT') });
71
+ term.log.debug('TEnv loaded', term.env.all());
72
+
73
+ // ── TRouter — left pane is the "screen" ──────────────────────────────────
74
+
75
+ leftPane.border = true;
76
+ leftPane.borderStyle = 'rounded';
77
+ leftPane.title = ' Screen ';
78
+ leftPane.onFocus(() => { leftPane.borderStyle='heavy'; leftPane.markDirty(); });
79
+ leftPane.onBlur(() => { leftPane.borderStyle='rounded'; leftPane.markDirty(); });
80
+
81
+ const router = new TRouter(leftPane, inputManager);
82
+
83
+ // ── Screen: home ──────────────────────────────────────────────────────────
84
+ router.register('home', (view, r) => {
85
+ view.writeCentered(1, ' HOME ', { color: [T.get('primary'), null], style: 'bold' });
86
+ view.writeCentered(3, 'Use [1–6] keys or press Enter', { color: [T.get('fg'), null] });
87
+ view.writeCentered(4, 'to explore subsystems', { color: [T.get('fgMuted'), null] });
88
+
89
+ // Spinner while "loading"
90
+ const stop = view.spinner(6, Math.floor(view.cols/2)-1, 'dots', 80, { color: [T.get('accent'), null] });
91
+ view.writeText(6, Math.floor(view.cols/2)+1, ' idle', { color: [T.get('fgMuted'), null] });
92
+
93
+ // Status bar at bottom of pane
94
+ view.statusBar(' kitdef ', ' home ', { color: ['black', T.get('primary')] });
95
+ term.log.debug('screen:home mounted');
96
+
97
+ return () => { stop(); view.clear(); };
98
+ });
99
+
100
+ // ── Screen: menu demo ─────────────────────────────────────────────────────
101
+ router.register('menu', async (view, r) => {
102
+ view.writeCentered(1, ' MENU WIDGET ', { color: [T.get('primary'), null], style: 'bold' });
103
+ view.markDirty();
104
+ const items = ['Option Alpha', 'Option Beta', 'Option Gamma', 'Option Delta', '← Back'];
105
+ const choice = await TWidget.menu(view, items, {
106
+ selectedColor: T.get('primary'),
107
+ normalColor: T.get('fg'),
108
+ });
109
+ term.log.info('menu selected', { choice });
110
+ if (choice === '← Back' || choice === null) r.pop();
111
+ return () => view.clear();
112
+ });
113
+
114
+ // ── Screen: list demo ─────────────────────────────────────────────────────
115
+ router.register('list', async (view, r) => {
116
+ view.writeCentered(1, ' LIST WIDGET ', { color: [T.get('primary'), null], style: 'bold' });
117
+ view.writeCentered(2, 'Space=toggle Enter=confirm', { color: [T.get('fgMuted'), null] });
118
+ view.markDirty();
119
+ const fruits = ['Apple','Banana','Cherry','Date','Elderberry','Fig','Grape','Honeydew'];
120
+ const chosen = await TWidget.list(view, fruits, {
121
+ selectedColor: T.get('primary'), normalColor: T.get('fg'),
122
+ });
123
+ term.log.info('list selected', { chosen });
124
+ r.pop();
125
+ return () => view.clear();
126
+ });
127
+
128
+ // ── Screen: dialog demo ───────────────────────────────────────────────────
129
+ router.register('dialog', async (view, r) => {
130
+ view.writeCentered(2, ' DIALOG / INPUT WIDGETS ', { color: [T.get('primary'), null], style:'bold' });
131
+ view.writeCentered(4, 'A dialog will appear…', { color: [T.get('fg'), null] });
132
+ view.markDirty();
133
+ await new Promise(res => setTimeout(res, 800));
134
+
135
+ const answer = await TWidget.dialog(view, ' Confirm ', 'Delete all the things?', ['Yes','No','Cancel']);
136
+ term.log.info('dialog answer', { answer });
137
+
138
+ // After dialog, show a text input
139
+ view.clear();
140
+ view.writeCentered(2, 'Type something and press Enter:', { color: [T.get('fg'), null] });
141
+ view.markDirty();
142
+ const typed = await TWidget.textInput(view, 4, Math.floor(view.cols/4), Math.floor(view.cols/2), {
143
+ placeholder: 'type here…',
144
+ });
145
+ term.log.info('textInput result', { typed });
146
+
147
+ // Password input
148
+ view.writeCentered(6, 'Now a masked password input:', { color: [T.get('fg'), null] });
149
+ view.markDirty();
150
+ const pw = await TWidget.textInput(view, 8, Math.floor(view.cols/4), Math.floor(view.cols/2), {
151
+ placeholder: '••••••••', mask: '●',
152
+ });
153
+ term.log.info('password entered', { length: pw?.length });
154
+
155
+ // Notify
156
+ TWidget.notify(view, `You typed: "${typed}"`, { type:'success', duration:2500 });
157
+
158
+ await new Promise(res => setTimeout(res, 2600));
159
+ r.pop();
160
+ return () => view.clear();
161
+ });
162
+
163
+ // ── Screen: shell demo ────────────────────────────────────────────────────
164
+ router.register('shell', async (view, r) => {
165
+ view.border=true; view.borderStyle='rounded'; view.scrollable=true;
166
+ view.title=' Shell Output ';
167
+ view.writeCentered(1, ' TSHELL DEMO ', { color:[T.get('primary'),null], style:'bold' });
168
+ view.markDirty();
169
+
170
+ const sh = new TShell({ view });
171
+
172
+ term.log.info('running: echo + date');
173
+ const r1 = await sh.shell('echo "Hello from TShell!" && date');
174
+ term.log.debug('exit code', { code: r1.code });
175
+
176
+ term.log.info('running: ls -la (truncated)');
177
+ const r2 = await sh.shell('ls -la | head -8');
178
+ term.log.debug('ls done', { lines: r2.stdout.split('\n').length });
179
+
180
+ view.writeCentered(view.rows - 3, 'Press any key to go back', { color:[T.get('fgMuted'),null] });
181
+ view.markDirty();
182
+
183
+ await new Promise(resolve => {
184
+ const off = view.on('key', () => { view.removeListener('key',off); resolve(); });
185
+ });
186
+ view.scrollable=false; view.border=false;
187
+ r.pop();
188
+ return () => { view.clear(); };
189
+ });
190
+
191
+ // ── Screen: env table ─────────────────────────────────────────────────────
192
+ router.register('envscreen', (view, r) => {
193
+ view.clear();
194
+ view.writeCentered(0, ' TENV VARIABLES ', { color:[T.get('primary'),null], style:'bold' });
195
+ const tableData = term.env.describe();
196
+ view.table(2, 1, tableData, {
197
+ headerColor: ['black', T.get('primary')],
198
+ cellColor: [T.get('fg'), null],
199
+ });
200
+ view.writeCentered(view.rows-2, 'Press any key to go back', { color:[T.get('fgMuted'),null] });
201
+ view.markDirty();
202
+
203
+ const off = view.on('key', () => { view.removeListener('key',off); r.pop(); });
204
+ return () => { view.clear(); };
205
+ });
206
+
207
+ // ── Screen: theme switcher ────────────────────────────────────────────────
208
+ router.register('themescreen', (view, r) => {
209
+ view.clear();
210
+ view.writeCentered(1, ' TTHEME SWITCHER ', { color:[T.get('primary'),null], style:'bold' });
211
+ const themeNames = T.names();
212
+ let sel = themeNames.indexOf(T.name);
213
+
214
+ const draw = () => {
215
+ themeNames.forEach((name, i) => {
216
+ const active = name === T.name, cursor = i === sel;
217
+ const pre = cursor ? '▶ ' : ' ';
218
+ view.writeText(3+i, 2, (pre+name).padEnd(view.cols-4), {
219
+ color: [active ? T.get('primary') : cursor ? T.get('fg') : T.get('fgMuted'), null],
220
+ style: active?'bold':null,
221
+ });
222
+ });
223
+ view.writeCentered(view.rows-2, `Active: ${T.name} — ↑↓ move Enter=apply Esc=back`, { color:[T.get('fgMuted'),null] });
224
+ view.markDirty();
225
+ };
226
+ draw();
227
+
228
+ const offUp = view.onArrow('up', () => { sel=(sel-1+themeNames.length)%themeNames.length; draw(); });
229
+ const offDown = view.onArrow('down', () => { sel=(sel+1)%themeNames.length; draw(); });
230
+ const offEnter= view.onKey('enter', () => {
231
+ T.use(themeNames[sel]);
232
+ term.log.info('theme changed', { theme: T.name });
233
+ // Re-paint top bar with new theme
234
+ topBar.fill(0,0,topBar.rows,topBar.cols,{color:[null,T.get('bgAlt')]});
235
+ topBar.writeCentered(0,'★ kitdef v1.0 ★',{color:[T.get('primary'),T.get('bgAlt')],style:'bold'});
236
+ topBar.markDirty();
237
+ draw();
238
+ });
239
+ const offEsc = view.onKey('escape', () => done());
240
+
241
+ function done() { offUp();offDown();offEnter();offEsc(); r.pop(); }
242
+ return () => { view.clear(); };
243
+ });
244
+
245
+ // ── Global key bindings ───────────────────────────────────────────────────
246
+ inputManager.on('key', key => {
247
+ if (leftPane.focused) {
248
+ if (key.name==='1') router.push('menu');
249
+ else if (key.name==='2') router.push('list');
250
+ else if (key.name==='3') router.push('dialog');
251
+ else if (key.name==='4') router.push('shell');
252
+ else if (key.name==='5') router.push('envscreen');
253
+ else if (key.name==='6') router.push('themescreen');
254
+ }
255
+ });
256
+
257
+ // Tab cycles focus
258
+ let focusList = [leftPane, rightPane], focusIdx = 0;
259
+ inputManager.on('key', k => {
260
+ if (k.name==='tab'&&!k.shift) {
261
+ focusIdx=(focusIdx+1)%focusList.length;
262
+ inputManager.focus(focusList[focusIdx]);
263
+ }
264
+ });
265
+
266
+ // ── Status bar ────────────────────────────────────────────────────────────
267
+ bottomBar.fill(0,0,bottomBar.rows,bottomBar.cols,{color:[null,T.get('bgAlt')]});
268
+ const updateStatus = msg => {
269
+ bottomBar.fill(0,0,1,bottomBar.cols,{color:[null,T.get('bgAlt')]});
270
+ bottomBar.writeText(0,1,msg.slice(0,bottomBar.cols-2),{color:[T.get('fg'),T.get('bgAlt')]});
271
+ bottomBar.markDirty();
272
+ };
273
+ updateStatus(' kitdef ready — [1-6] navigate Tab=focus Ctrl-C=exit');
274
+ inputManager.on('key', k => updateStatus(` key: "${k.name}"${k.ctrl?'+ctrl':''} [1-6] screens Tab=focus`));
275
+ inputManager.on('click', e => updateStatus(` click (${e.row},${e.col}) btn=${e.button}`));
276
+ inputManager.on('resize',e => { updateStatus(` terminal resized → ${e.cols}×${e.rows}`); });
277
+ bottomBar.render();
278
+
279
+ // ── TWidget.Gauge live demo inside leftPane margin ───────────────────────
280
+ // (rendered after router pushes its content, so it's a passive layer)
281
+
282
+ // ── Boot router ───────────────────────────────────────────────────────────
283
+ router.push('home');
284
+ leftPane.focus();
285
+ });