spawn-term 1.1.8 → 2.0.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 (45) hide show
  1. package/dist/cjs/components/App.js +96 -9
  2. package/dist/cjs/components/App.js.map +1 -1
  3. package/dist/cjs/components/CompactProcessLine.js +153 -0
  4. package/dist/cjs/components/CompactProcessLine.js.map +1 -0
  5. package/dist/cjs/components/Divider.js +24 -0
  6. package/dist/cjs/components/Divider.js.map +1 -0
  7. package/dist/cjs/components/ErrorDetailModal.js +115 -0
  8. package/dist/cjs/components/ErrorDetailModal.js.map +1 -0
  9. package/dist/cjs/components/ErrorListModal.js +135 -0
  10. package/dist/cjs/components/ErrorListModal.js.map +1 -0
  11. package/dist/cjs/components/StatusBar.js +104 -0
  12. package/dist/cjs/components/StatusBar.js.map +1 -0
  13. package/dist/cjs/createApp.js +10 -5
  14. package/dist/cjs/createApp.js.map +1 -1
  15. package/dist/cjs/src/components/CompactProcessLine.d.ts +6 -0
  16. package/dist/cjs/src/components/Divider.d.ts +2 -0
  17. package/dist/cjs/src/components/ErrorDetailModal.d.ts +8 -0
  18. package/dist/cjs/src/components/ErrorListModal.d.ts +8 -0
  19. package/dist/cjs/src/components/StatusBar.d.ts +8 -0
  20. package/dist/cjs/src/state/processStore.d.ts +17 -0
  21. package/dist/cjs/state/processStore.js +98 -0
  22. package/dist/cjs/state/processStore.js.map +1 -1
  23. package/dist/esm/components/App.js +94 -9
  24. package/dist/esm/components/App.js.map +1 -1
  25. package/dist/esm/components/CompactProcessLine.js +134 -0
  26. package/dist/esm/components/CompactProcessLine.js.map +1 -0
  27. package/dist/esm/components/Divider.js +13 -0
  28. package/dist/esm/components/Divider.js.map +1 -0
  29. package/dist/esm/components/ErrorDetailModal.js +99 -0
  30. package/dist/esm/components/ErrorDetailModal.js.map +1 -0
  31. package/dist/esm/components/ErrorListModal.js +116 -0
  32. package/dist/esm/components/ErrorListModal.js.map +1 -0
  33. package/dist/esm/components/StatusBar.js +87 -0
  34. package/dist/esm/components/StatusBar.js.map +1 -0
  35. package/dist/esm/createApp.js +10 -5
  36. package/dist/esm/createApp.js.map +1 -1
  37. package/dist/esm/src/components/CompactProcessLine.d.ts +6 -0
  38. package/dist/esm/src/components/Divider.d.ts +2 -0
  39. package/dist/esm/src/components/ErrorDetailModal.d.ts +8 -0
  40. package/dist/esm/src/components/ErrorListModal.d.ts +8 -0
  41. package/dist/esm/src/components/StatusBar.d.ts +8 -0
  42. package/dist/esm/src/state/processStore.d.ts +17 -0
  43. package/dist/esm/state/processStore.js +65 -0
  44. package/dist/esm/state/processStore.js.map +1 -1
  45. package/package.json +1 -1
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "default", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return _default;
9
+ }
10
+ });
11
+ var _jsxruntime = require("react/jsx-runtime");
12
+ var _ink = require("ink");
13
+ var _react = require("react");
14
+ var _figurests = /*#__PURE__*/ _interop_require_default(require("../lib/figures.js"));
15
+ var _Spinnerts = /*#__PURE__*/ _interop_require_default(require("./Spinner.js"));
16
+ function _define_property(obj, key, value) {
17
+ if (key in obj) {
18
+ Object.defineProperty(obj, key, {
19
+ value: value,
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true
23
+ });
24
+ } else {
25
+ obj[key] = value;
26
+ }
27
+ return obj;
28
+ }
29
+ function _interop_require_default(obj) {
30
+ return obj && obj.__esModule ? obj : {
31
+ default: obj
32
+ };
33
+ }
34
+ function _object_spread(target) {
35
+ for(var i = 1; i < arguments.length; i++){
36
+ var source = arguments[i] != null ? arguments[i] : {};
37
+ var ownKeys = Object.keys(source);
38
+ if (typeof Object.getOwnPropertySymbols === "function") {
39
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
40
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
41
+ }));
42
+ }
43
+ ownKeys.forEach(function(key) {
44
+ _define_property(target, key, source[key]);
45
+ });
46
+ }
47
+ return target;
48
+ }
49
+ // From: https://github.com/sindresorhus/cli-spinners/blob/00de8fbeee16fa49502fa4f687449f70f2c8ca2c/spinners.json#L2
50
+ var SPINNER = {
51
+ interval: 80,
52
+ frames: [
53
+ '⠋',
54
+ '⠙',
55
+ '⠹',
56
+ '⠸',
57
+ '⠼',
58
+ '⠴',
59
+ '⠦',
60
+ '⠧',
61
+ '⠇',
62
+ '⠏'
63
+ ]
64
+ };
65
+ var _default = /*#__PURE__*/ (0, _react.memo)(function StatusBar(param) {
66
+ var running = param.running, done = param.done, errors = param.errors, errorLines = param.errorLines;
67
+ return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_ink.Box, {
68
+ justifyContent: "space-between",
69
+ children: [
70
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Box, {
71
+ children: /*#__PURE__*/ (0, _jsxruntime.jsxs)(_ink.Text, {
72
+ children: [
73
+ running > 0 ? /*#__PURE__*/ (0, _jsxruntime.jsx)(_Spinnerts.default, _object_spread({}, SPINNER)) : /*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Text, {
74
+ color: "green",
75
+ children: _figurests.default.tick
76
+ }),
77
+ " Running: ".concat(running, " "),
78
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Text, {
79
+ color: "green",
80
+ children: _figurests.default.tick
81
+ }),
82
+ " Done: ".concat(done, " "),
83
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Text, {
84
+ color: "red",
85
+ children: _figurests.default.cross
86
+ }),
87
+ " Errors: ".concat(errors),
88
+ errorLines > 0 && /*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Text, {
89
+ dimColor: true,
90
+ children: " (".concat(errorLines, " lines)")
91
+ })
92
+ ]
93
+ })
94
+ }),
95
+ errors > 0 && /*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Box, {
96
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Text, {
97
+ dimColor: true,
98
+ children: "[e]rrors"
99
+ })
100
+ })
101
+ ]
102
+ });
103
+ });
104
+ /* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/StatusBar.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport figures from '../lib/figures.ts';\nimport Spinner from './Spinner.ts';\n\n// From: https://github.com/sindresorhus/cli-spinners/blob/00de8fbeee16fa49502fa4f687449f70f2c8ca2c/spinners.json#L2\nconst SPINNER = {\n interval: 80,\n frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],\n};\n\ntype Props = {\n running: number;\n done: number;\n errors: number;\n errorLines: number;\n};\n\nexport default memo(function StatusBar({ running, done, errors, errorLines }: Props) {\n return (\n <Box justifyContent=\"space-between\">\n <Box>\n <Text>\n {running > 0 ? <Spinner {...SPINNER} /> : <Text color=\"green\">{figures.tick}</Text>}\n {` Running: ${running} `}\n <Text color=\"green\">{figures.tick}</Text>\n {` Done: ${done} `}\n <Text color=\"red\">{figures.cross}</Text>\n {` Errors: ${errors}`}\n {errorLines > 0 && <Text dimColor>{` (${errorLines} lines)`}</Text>}\n </Text>\n </Box>\n {errors > 0 && (\n <Box>\n <Text dimColor>[e]rrors</Text>\n </Box>\n )}\n </Box>\n );\n});\n"],"names":["SPINNER","interval","frames","memo","StatusBar","running","done","errors","errorLines","Box","justifyContent","Text","Spinner","color","figures","tick","cross","dimColor"],"mappings":";;;;+BAkBA;;;eAAA;;;;mBAlB0B;qBACL;gEACD;gEACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEpB,oHAAoH;AACpH,IAAMA,UAAU;IACdC,UAAU;IACVC,QAAQ;QAAC;QAAK;QAAK;QAAK;QAAK;QAAK;QAAK;QAAK;QAAK;QAAK;KAAI;AAC5D;IASA,yBAAeC,IAAAA,WAAI,EAAC,SAASC,UAAU,KAA4C;QAA1CC,UAAF,MAAEA,SAASC,OAAX,MAAWA,MAAMC,SAAjB,MAAiBA,QAAQC,aAAzB,MAAyBA;IAC9D,qBACE,sBAACC,QAAG;QAACC,gBAAe;;0BAClB,qBAACD,QAAG;0BACF,cAAA,sBAACE,SAAI;;wBACFN,UAAU,kBAAI,qBAACO,kBAAO,qBAAKZ,0BAAc,qBAACW,SAAI;4BAACE,OAAM;sCAASC,kBAAO,CAACC,IAAI;;wBACzE,aAAoB,OAARV,SAAQ;sCACtB,qBAACM,SAAI;4BAACE,OAAM;sCAASC,kBAAO,CAACC,IAAI;;wBAC/B,UAAc,OAALT,MAAK;sCAChB,qBAACK,SAAI;4BAACE,OAAM;sCAAOC,kBAAO,CAACE,KAAK;;wBAC9B,YAAkB,OAAPT;wBACZC,aAAa,mBAAK,qBAACG,SAAI;4BAACM,QAAQ;sCAAE,AAAC,KAAe,OAAXT,YAAW;;;;;YAGtDD,SAAS,mBACR,qBAACE,QAAG;0BACF,cAAA,qBAACE,SAAI;oBAACM,QAAQ;8BAAC;;;;;AAKzB"}
@@ -33,11 +33,16 @@ function createApp() {
33
33
  return;
34
34
  }
35
35
  if (!inkApp) throw new Error('Expecting inkApp');
36
- // Signal exit to React component, provide callback for after cleanup
37
- _processStorets.processStore.signalExit(function() {
38
- _processStorets.processStore.reset();
39
- process.stdout.write('\x1b[?25h'); // show cursor
40
- callback();
36
+ // Defer signalExit to allow React's reconciliation to complete fully
37
+ // Using setImmediate ensures we run after I/O callbacks and microtasks,
38
+ // preventing the Static component from outputting the last item twice
39
+ // (the second notify() from signalExit can race with Static's useLayoutEffect)
40
+ setImmediate(function() {
41
+ _processStorets.processStore.signalExit(function() {
42
+ _processStorets.processStore.reset();
43
+ process.stdout.write('\x1b[?25h'); // show cursor
44
+ callback();
45
+ });
41
46
  });
42
47
  // Wait for Ink to finish, then call the callback
43
48
  inkApp.waitUntilExit().then(function() {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/createApp.tsx"],"sourcesContent":["import { render } from 'ink';\nimport App from './components/App.ts';\nimport { type ProcessStore, processStore } from './state/processStore.ts';\n\nexport type ReleaseCallback = () => void;\n\nexport default function createApp() {\n let refCount = 0;\n let inkApp: ReturnType<typeof render> | null = null;\n\n return {\n retain(): ProcessStore {\n if (++refCount > 1) return processStore;\n\n // Render once - React handles all subsequent updates via useSyncExternalStore\n inkApp = render(<App />);\n return processStore;\n },\n\n release(callback: ReleaseCallback): void {\n if (--refCount > 0) {\n callback();\n return;\n }\n if (!inkApp) throw new Error('Expecting inkApp');\n\n // Signal exit to React component, provide callback for after cleanup\n processStore.signalExit(() => {\n processStore.reset();\n process.stdout.write('\\x1b[?25h'); // show cursor\n callback();\n });\n\n // Wait for Ink to finish, then call the callback\n inkApp\n .waitUntilExit()\n .then(() => {\n const cb = processStore.getExitCallback();\n cb?.();\n })\n .catch(() => {\n const cb = processStore.getExitCallback();\n cb?.();\n });\n\n inkApp = null;\n },\n };\n}\n"],"names":["createApp","refCount","inkApp","retain","processStore","render","App","release","callback","Error","signalExit","reset","process","stdout","write","waitUntilExit","then","cb","getExitCallback","catch"],"mappings":";;;;+BAMA;;;eAAwBA;;;;mBAND;4DACP;8BACgC;;;;;;AAIjC,SAASA;IACtB,IAAIC,WAAW;IACf,IAAIC,SAA2C;IAE/C,OAAO;QACLC,QAAAA,SAAAA;YACE,IAAI,EAAEF,WAAW,GAAG,OAAOG,4BAAY;YAEvC,8EAA8E;YAC9EF,SAASG,IAAAA,WAAM,gBAAC,qBAACC,cAAG;YACpB,OAAOF,4BAAY;QACrB;QAEAG,SAAAA,SAAAA,QAAQC,QAAyB;YAC/B,IAAI,EAAEP,WAAW,GAAG;gBAClBO;gBACA;YACF;YACA,IAAI,CAACN,QAAQ,MAAM,IAAIO,MAAM;YAE7B,qEAAqE;YACrEL,4BAAY,CAACM,UAAU,CAAC;gBACtBN,4BAAY,CAACO,KAAK;gBAClBC,QAAQC,MAAM,CAACC,KAAK,CAAC,cAAc,cAAc;gBACjDN;YACF;YAEA,iDAAiD;YACjDN,OACGa,aAAa,GACbC,IAAI,CAAC;gBACJ,IAAMC,KAAKb,4BAAY,CAACc,eAAe;gBACvCD,eAAAA,yBAAAA;YACF,GACCE,KAAK,CAAC;gBACL,IAAMF,KAAKb,4BAAY,CAACc,eAAe;gBACvCD,eAAAA,yBAAAA;YACF;YAEFf,SAAS;QACX;IACF;AACF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/createApp.tsx"],"sourcesContent":["import { render } from 'ink';\nimport App from './components/App.ts';\nimport { type ProcessStore, processStore } from './state/processStore.ts';\n\nexport type ReleaseCallback = () => void;\n\nexport default function createApp() {\n let refCount = 0;\n let inkApp: ReturnType<typeof render> | null = null;\n\n return {\n retain(): ProcessStore {\n if (++refCount > 1) return processStore;\n\n // Render once - React handles all subsequent updates via useSyncExternalStore\n inkApp = render(<App />);\n return processStore;\n },\n\n release(callback: ReleaseCallback): void {\n if (--refCount > 0) {\n callback();\n return;\n }\n if (!inkApp) throw new Error('Expecting inkApp');\n\n // Defer signalExit to allow React's reconciliation to complete fully\n // Using setImmediate ensures we run after I/O callbacks and microtasks,\n // preventing the Static component from outputting the last item twice\n // (the second notify() from signalExit can race with Static's useLayoutEffect)\n setImmediate(() => {\n processStore.signalExit(() => {\n processStore.reset();\n process.stdout.write('\\x1b[?25h'); // show cursor\n callback();\n });\n });\n\n // Wait for Ink to finish, then call the callback\n inkApp\n .waitUntilExit()\n .then(() => {\n const cb = processStore.getExitCallback();\n cb?.();\n })\n .catch(() => {\n const cb = processStore.getExitCallback();\n cb?.();\n });\n\n inkApp = null;\n },\n };\n}\n"],"names":["createApp","refCount","inkApp","retain","processStore","render","App","release","callback","Error","setImmediate","signalExit","reset","process","stdout","write","waitUntilExit","then","cb","getExitCallback","catch"],"mappings":";;;;+BAMA;;;eAAwBA;;;;mBAND;4DACP;8BACgC;;;;;;AAIjC,SAASA;IACtB,IAAIC,WAAW;IACf,IAAIC,SAA2C;IAE/C,OAAO;QACLC,QAAAA,SAAAA;YACE,IAAI,EAAEF,WAAW,GAAG,OAAOG,4BAAY;YAEvC,8EAA8E;YAC9EF,SAASG,IAAAA,WAAM,gBAAC,qBAACC,cAAG;YACpB,OAAOF,4BAAY;QACrB;QAEAG,SAAAA,SAAAA,QAAQC,QAAyB;YAC/B,IAAI,EAAEP,WAAW,GAAG;gBAClBO;gBACA;YACF;YACA,IAAI,CAACN,QAAQ,MAAM,IAAIO,MAAM;YAE7B,qEAAqE;YACrE,wEAAwE;YACxE,sEAAsE;YACtE,+EAA+E;YAC/EC,aAAa;gBACXN,4BAAY,CAACO,UAAU,CAAC;oBACtBP,4BAAY,CAACQ,KAAK;oBAClBC,QAAQC,MAAM,CAACC,KAAK,CAAC,cAAc,cAAc;oBACjDP;gBACF;YACF;YAEA,iDAAiD;YACjDN,OACGc,aAAa,GACbC,IAAI,CAAC;gBACJ,IAAMC,KAAKd,4BAAY,CAACe,eAAe;gBACvCD,eAAAA,yBAAAA;YACF,GACCE,KAAK,CAAC;gBACL,IAAMF,KAAKd,4BAAY,CAACe,eAAe;gBACvCD,eAAAA,yBAAAA;YACF;YAEFhB,SAAS;QACX;IACF;AACF"}
@@ -0,0 +1,6 @@
1
+ import type { ChildProcess } from '../types.js';
2
+ type Props = {
3
+ item: ChildProcess;
4
+ };
5
+ declare const _default: import("react").NamedExoticComponent<Props>;
6
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("react").NamedExoticComponent<object>;
2
+ export default _default;
@@ -0,0 +1,8 @@
1
+ import type { ChildProcess } from '../types.js';
2
+ type Props = {
3
+ error: ChildProcess;
4
+ currentIndex: number;
5
+ totalErrors: number;
6
+ };
7
+ declare const _default: import("react").NamedExoticComponent<Props>;
8
+ export default _default;
@@ -0,0 +1,8 @@
1
+ import type { ChildProcess } from '../types.js';
2
+ type Props = {
3
+ errors: ChildProcess[];
4
+ selectedIndex: number;
5
+ totalErrorLines: number;
6
+ };
7
+ declare const _default: import("react").NamedExoticComponent<Props>;
8
+ export default _default;
@@ -0,0 +1,8 @@
1
+ type Props = {
2
+ running: number;
3
+ done: number;
4
+ errors: number;
5
+ errorLines: number;
6
+ };
7
+ declare const _default: import("react").NamedExoticComponent<Props>;
8
+ export default _default;
@@ -1,16 +1,33 @@
1
1
  import type { ChildProcess, Line } from '../types.js';
2
2
  type Listener = () => void;
3
+ type Mode = 'normal' | 'errorList' | 'errorDetail';
3
4
  declare class ProcessStore {
4
5
  private processes;
6
+ private completedIds;
5
7
  private listeners;
6
8
  private shouldExit;
7
9
  private exitCallback;
10
+ private mode;
11
+ private selectedErrorIndex;
8
12
  subscribe: (listener: Listener) => (() => void);
9
13
  getSnapshot: () => ChildProcess[];
14
+ getRunningProcesses: () => ChildProcess[];
15
+ getCompletedProcesses: () => ChildProcess[];
16
+ getFailedProcesses: () => ChildProcess[];
17
+ getRunningCount: () => number;
18
+ getDoneCount: () => number;
19
+ getErrorCount: () => number;
20
+ getErrorLineCount: () => number;
21
+ getMode: () => Mode;
22
+ getSelectedErrorIndex: () => number;
10
23
  addProcess(process: ChildProcess): void;
11
24
  updateProcess(id: string, update: Partial<ChildProcess>): void;
12
25
  appendLines(id: string, newLines: Line[]): void;
13
26
  getProcess(id: string): ChildProcess | undefined;
27
+ setMode(mode: Mode): void;
28
+ selectNextError(): void;
29
+ selectPrevError(): void;
30
+ getSelectedError(): ChildProcess | undefined;
14
31
  signalExit(callback: () => void): void;
15
32
  getShouldExit: () => boolean;
16
33
  getExitCallback: () => (() => void) | null;
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "processStore", {
8
8
  return processStore;
9
9
  }
10
10
  });
11
+ var _typests = require("../types.js");
11
12
  function _array_like_to_array(arr, len) {
12
13
  if (len == null || len > arr.length) len = arr.length;
13
14
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -72,9 +73,13 @@ var ProcessStore = /*#__PURE__*/ function() {
72
73
  var _this = this;
73
74
  _class_call_check(this, ProcessStore);
74
75
  this.processes = [];
76
+ this.completedIds = []; // Track completion order
75
77
  this.listeners = new Set();
76
78
  this.shouldExit = false;
77
79
  this.exitCallback = null;
80
+ // UI state
81
+ this.mode = 'normal';
82
+ this.selectedErrorIndex = 0;
78
83
  // useSyncExternalStore API
79
84
  this.subscribe = function(listener) {
80
85
  _this.listeners.add(listener);
@@ -85,6 +90,59 @@ var ProcessStore = /*#__PURE__*/ function() {
85
90
  this.getSnapshot = function() {
86
91
  return _this.processes;
87
92
  };
93
+ // Filtered getters
94
+ this.getRunningProcesses = function() {
95
+ return _this.processes.filter(function(p) {
96
+ return p.state === 'running';
97
+ });
98
+ };
99
+ this.getCompletedProcesses = function() {
100
+ // Return in completion order
101
+ return _this.completedIds.map(function(id) {
102
+ return _this.processes.find(function(p) {
103
+ return p.id === id;
104
+ });
105
+ }).filter(function(p) {
106
+ return p !== undefined;
107
+ });
108
+ };
109
+ this.getFailedProcesses = function() {
110
+ return _this.processes.filter(function(p) {
111
+ return p.state === 'error';
112
+ });
113
+ };
114
+ // Counts
115
+ this.getRunningCount = function() {
116
+ return _this.processes.filter(function(p) {
117
+ return p.state === 'running';
118
+ }).length;
119
+ };
120
+ this.getDoneCount = function() {
121
+ return _this.processes.filter(function(p) {
122
+ return p.state !== 'running';
123
+ }).length;
124
+ };
125
+ this.getErrorCount = function() {
126
+ return _this.processes.filter(function(p) {
127
+ return p.state === 'error';
128
+ }).length;
129
+ };
130
+ this.getErrorLineCount = function() {
131
+ return _this.processes.filter(function(p) {
132
+ return p.state === 'error';
133
+ }).reduce(function(total, p) {
134
+ return total + p.lines.filter(function(l) {
135
+ return l.type === _typests.LineType.stderr;
136
+ }).length;
137
+ }, 0);
138
+ };
139
+ // UI state getters
140
+ this.getMode = function() {
141
+ return _this.mode;
142
+ };
143
+ this.getSelectedErrorIndex = function() {
144
+ return _this.selectedErrorIndex;
145
+ };
88
146
  this.getShouldExit = function() {
89
147
  return _this.shouldExit;
90
148
  };
@@ -101,9 +159,20 @@ var ProcessStore = /*#__PURE__*/ function() {
101
159
  this.notify();
102
160
  };
103
161
  _proto.updateProcess = function updateProcess(id, update) {
162
+ var oldProcess = this.processes.find(function(p) {
163
+ return p.id === id;
164
+ });
165
+ var wasRunning = (oldProcess === null || oldProcess === void 0 ? void 0 : oldProcess.state) === 'running';
166
+ var isNowComplete = update.state && update.state !== 'running';
104
167
  this.processes = this.processes.map(function(p) {
105
168
  return p.id === id ? _object_spread({}, p, update) : p;
106
169
  });
170
+ // Track completion order
171
+ if (wasRunning && isNowComplete && !this.completedIds.includes(id)) {
172
+ this.completedIds = _to_consumable_array(this.completedIds).concat([
173
+ id
174
+ ]);
175
+ }
107
176
  this.notify();
108
177
  };
109
178
  _proto.appendLines = function appendLines(id, newLines) {
@@ -121,6 +190,32 @@ var ProcessStore = /*#__PURE__*/ function() {
121
190
  return p.id === id;
122
191
  });
123
192
  };
193
+ // UI state mutations
194
+ _proto.setMode = function setMode(mode) {
195
+ this.mode = mode;
196
+ if (mode === 'errorList') {
197
+ this.selectedErrorIndex = 0;
198
+ }
199
+ this.notify();
200
+ };
201
+ _proto.selectNextError = function selectNextError() {
202
+ var failed = this.getFailedProcesses();
203
+ if (failed.length > 0) {
204
+ this.selectedErrorIndex = (this.selectedErrorIndex + 1) % failed.length;
205
+ this.notify();
206
+ }
207
+ };
208
+ _proto.selectPrevError = function selectPrevError() {
209
+ var failed = this.getFailedProcesses();
210
+ if (failed.length > 0) {
211
+ this.selectedErrorIndex = (this.selectedErrorIndex - 1 + failed.length) % failed.length;
212
+ this.notify();
213
+ }
214
+ };
215
+ _proto.getSelectedError = function getSelectedError() {
216
+ var failed = this.getFailedProcesses();
217
+ return failed[this.selectedErrorIndex];
218
+ };
124
219
  // Exit signaling
125
220
  _proto.signalExit = function signalExit(callback) {
126
221
  this.shouldExit = true;
@@ -129,8 +224,11 @@ var ProcessStore = /*#__PURE__*/ function() {
129
224
  };
130
225
  _proto.reset = function reset() {
131
226
  this.processes = [];
227
+ this.completedIds = [];
132
228
  this.shouldExit = false;
133
229
  this.exitCallback = null;
230
+ this.mode = 'normal';
231
+ this.selectedErrorIndex = 0;
134
232
  };
135
233
  _proto.notify = function notify() {
136
234
  this.listeners.forEach(function(l) {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/state/processStore.ts"],"sourcesContent":["import type { ChildProcess, Line } from '../types.ts';\n\ntype Listener = () => void;\n\nclass ProcessStore {\n private processes: ChildProcess[] = [];\n private listeners = new Set<Listener>();\n private shouldExit = false;\n private exitCallback: (() => void) | null = null;\n\n // useSyncExternalStore API\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n getSnapshot = (): ChildProcess[] => this.processes;\n\n // Mutations - Ink handles render throttling at 30 FPS\n addProcess(process: ChildProcess): void {\n this.processes = [...this.processes, process];\n this.notify();\n }\n\n updateProcess(id: string, update: Partial<ChildProcess>): void {\n this.processes = this.processes.map((p) => (p.id === id ? { ...p, ...update } : p));\n this.notify();\n }\n\n appendLines(id: string, newLines: Line[]): void {\n const process = this.processes.find((p) => p.id === id);\n if (process) {\n this.updateProcess(id, { lines: process.lines.concat(newLines) });\n }\n }\n\n getProcess(id: string): ChildProcess | undefined {\n return this.processes.find((p) => p.id === id);\n }\n\n // Exit signaling\n signalExit(callback: () => void): void {\n this.shouldExit = true;\n this.exitCallback = callback;\n this.notify();\n }\n\n getShouldExit = (): boolean => this.shouldExit;\n getExitCallback = (): (() => void) | null => this.exitCallback;\n\n reset(): void {\n this.processes = [];\n this.shouldExit = false;\n this.exitCallback = null;\n }\n\n private notify(): void {\n this.listeners.forEach((l) => {\n l();\n });\n }\n}\n\nexport const processStore = new ProcessStore();\nexport type { ProcessStore };\n"],"names":["processStore","ProcessStore","processes","listeners","Set","shouldExit","exitCallback","subscribe","listener","add","delete","getSnapshot","getShouldExit","getExitCallback","addProcess","process","notify","updateProcess","id","update","map","p","appendLines","newLines","find","lines","concat","getProcess","signalExit","callback","reset","forEach","l"],"mappings":";;;;+BA+DaA;;;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA3Db,IAAA,AAAMC,6BAAN;;aAAMA;;gCAAAA;aACIC,YAA4B,EAAE;aAC9BC,YAAY,IAAIC;aAChBC,aAAa;aACbC,eAAoC;QAE5C,2BAA2B;aAC3BC,YAAY,SAACC;YACX,MAAKL,SAAS,CAACM,GAAG,CAACD;YACnB,OAAO;uBAAM,MAAKL,SAAS,CAACO,MAAM,CAACF;;QACrC;aAEAG,cAAc;mBAAsB,MAAKT,SAAS;;aA+BlDU,gBAAgB;mBAAe,MAAKP,UAAU;;aAC9CQ,kBAAkB;mBAA2B,MAAKP,YAAY;;;iBA5C1DL;IAcJ,sDAAsD;IACtDa,OAAAA,UAGC,GAHDA,SAAAA,WAAWC,OAAqB;QAC9B,IAAI,CAACb,SAAS,GAAG,AAAC,qBAAG,IAAI,CAACA,SAAS,SAAlB;YAAoBa;SAAQ;QAC7C,IAAI,CAACC,MAAM;IACb;IAEAC,OAAAA,aAGC,GAHDA,SAAAA,cAAcC,EAAU,EAAEC,MAA6B;QACrD,IAAI,CAACjB,SAAS,GAAG,IAAI,CAACA,SAAS,CAACkB,GAAG,CAAC,SAACC;mBAAOA,EAAEH,EAAE,KAAKA,KAAK,mBAAKG,GAAMF,UAAWE;;QAChF,IAAI,CAACL,MAAM;IACb;IAEAM,OAAAA,WAKC,GALDA,SAAAA,YAAYJ,EAAU,EAAEK,QAAgB;QACtC,IAAMR,UAAU,IAAI,CAACb,SAAS,CAACsB,IAAI,CAAC,SAACH;mBAAMA,EAAEH,EAAE,KAAKA;;QACpD,IAAIH,SAAS;YACX,IAAI,CAACE,aAAa,CAACC,IAAI;gBAAEO,OAAOV,QAAQU,KAAK,CAACC,MAAM,CAACH;YAAU;QACjE;IACF;IAEAI,OAAAA,UAEC,GAFDA,SAAAA,WAAWT,EAAU;QACnB,OAAO,IAAI,CAAChB,SAAS,CAACsB,IAAI,CAAC,SAACH;mBAAMA,EAAEH,EAAE,KAAKA;;IAC7C;IAEA,iBAAiB;IACjBU,OAAAA,UAIC,GAJDA,SAAAA,WAAWC,QAAoB;QAC7B,IAAI,CAACxB,UAAU,GAAG;QAClB,IAAI,CAACC,YAAY,GAAGuB;QACpB,IAAI,CAACb,MAAM;IACb;IAKAc,OAAAA,KAIC,GAJDA,SAAAA;QACE,IAAI,CAAC5B,SAAS,GAAG,EAAE;QACnB,IAAI,CAACG,UAAU,GAAG;QAClB,IAAI,CAACC,YAAY,GAAG;IACtB;IAEA,OAAQU,MAIP,GAJD,SAAQA;QACN,IAAI,CAACb,SAAS,CAAC4B,OAAO,CAAC,SAACC;YACtBA;QACF;IACF;WAxDI/B;;AA2DC,IAAMD,eAAe,IAAIC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/state/processStore.ts"],"sourcesContent":["import type { ChildProcess, Line } from '../types.ts';\nimport { LineType } from '../types.ts';\n\ntype Listener = () => void;\ntype Mode = 'normal' | 'errorList' | 'errorDetail';\n\nclass ProcessStore {\n private processes: ChildProcess[] = [];\n private completedIds: string[] = []; // Track completion order\n private listeners = new Set<Listener>();\n private shouldExit = false;\n private exitCallback: (() => void) | null = null;\n\n // UI state\n private mode: Mode = 'normal';\n private selectedErrorIndex = 0;\n\n // useSyncExternalStore API\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n getSnapshot = (): ChildProcess[] => this.processes;\n\n // Filtered getters\n getRunningProcesses = (): ChildProcess[] => {\n return this.processes.filter((p) => p.state === 'running');\n };\n\n getCompletedProcesses = (): ChildProcess[] => {\n // Return in completion order\n return this.completedIds.map((id) => this.processes.find((p) => p.id === id)).filter((p): p is ChildProcess => p !== undefined);\n };\n\n getFailedProcesses = (): ChildProcess[] => {\n return this.processes.filter((p) => p.state === 'error');\n };\n\n // Counts\n getRunningCount = (): number => this.processes.filter((p) => p.state === 'running').length;\n getDoneCount = (): number => this.processes.filter((p) => p.state !== 'running').length;\n getErrorCount = (): number => this.processes.filter((p) => p.state === 'error').length;\n getErrorLineCount = (): number => {\n return this.processes.filter((p) => p.state === 'error').reduce((total, p) => total + p.lines.filter((l) => l.type === LineType.stderr).length, 0);\n };\n\n // UI state getters\n getMode = (): Mode => this.mode;\n getSelectedErrorIndex = (): number => this.selectedErrorIndex;\n\n // Mutations - Ink handles render throttling at 30 FPS\n addProcess(process: ChildProcess): void {\n this.processes = [...this.processes, process];\n this.notify();\n }\n\n updateProcess(id: string, update: Partial<ChildProcess>): void {\n const oldProcess = this.processes.find((p) => p.id === id);\n const wasRunning = oldProcess?.state === 'running';\n const isNowComplete = update.state && update.state !== 'running';\n\n this.processes = this.processes.map((p) => (p.id === id ? { ...p, ...update } : p));\n\n // Track completion order\n if (wasRunning && isNowComplete && !this.completedIds.includes(id)) {\n this.completedIds = [...this.completedIds, id];\n }\n\n this.notify();\n }\n\n appendLines(id: string, newLines: Line[]): void {\n const process = this.processes.find((p) => p.id === id);\n if (process) {\n this.updateProcess(id, { lines: process.lines.concat(newLines) });\n }\n }\n\n getProcess(id: string): ChildProcess | undefined {\n return this.processes.find((p) => p.id === id);\n }\n\n // UI state mutations\n setMode(mode: Mode): void {\n this.mode = mode;\n if (mode === 'errorList') {\n this.selectedErrorIndex = 0;\n }\n this.notify();\n }\n\n selectNextError(): void {\n const failed = this.getFailedProcesses();\n if (failed.length > 0) {\n this.selectedErrorIndex = (this.selectedErrorIndex + 1) % failed.length;\n this.notify();\n }\n }\n\n selectPrevError(): void {\n const failed = this.getFailedProcesses();\n if (failed.length > 0) {\n this.selectedErrorIndex = (this.selectedErrorIndex - 1 + failed.length) % failed.length;\n this.notify();\n }\n }\n\n getSelectedError(): ChildProcess | undefined {\n const failed = this.getFailedProcesses();\n return failed[this.selectedErrorIndex];\n }\n\n // Exit signaling\n signalExit(callback: () => void): void {\n this.shouldExit = true;\n this.exitCallback = callback;\n this.notify();\n }\n\n getShouldExit = (): boolean => this.shouldExit;\n getExitCallback = (): (() => void) | null => this.exitCallback;\n\n reset(): void {\n this.processes = [];\n this.completedIds = [];\n this.shouldExit = false;\n this.exitCallback = null;\n this.mode = 'normal';\n this.selectedErrorIndex = 0;\n }\n\n private notify(): void {\n this.listeners.forEach((l) => {\n l();\n });\n }\n}\n\nexport const processStore = new ProcessStore();\nexport type { ProcessStore };\n"],"names":["processStore","ProcessStore","processes","completedIds","listeners","Set","shouldExit","exitCallback","mode","selectedErrorIndex","subscribe","listener","add","delete","getSnapshot","getRunningProcesses","filter","p","state","getCompletedProcesses","map","id","find","undefined","getFailedProcesses","getRunningCount","length","getDoneCount","getErrorCount","getErrorLineCount","reduce","total","lines","l","type","LineType","stderr","getMode","getSelectedErrorIndex","getShouldExit","getExitCallback","addProcess","process","notify","updateProcess","update","oldProcess","wasRunning","isNowComplete","includes","appendLines","newLines","concat","getProcess","setMode","selectNextError","failed","selectPrevError","getSelectedError","signalExit","callback","reset","forEach"],"mappings":"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"}
@@ -1,14 +1,29 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Box, useApp } from 'ink';
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Static, useApp, useInput, useStdin } from 'ink';
3
3
  import { useEffect, useSyncExternalStore } from 'react';
4
4
  import { processStore } from '../state/processStore.js';
5
- import ChildProcess from './ChildProcess.js';
5
+ import CompactProcessLine from './CompactProcessLine.js';
6
+ import Divider from './Divider.js';
7
+ import ErrorDetailModal from './ErrorDetailModal.js';
8
+ import ErrorListModal from './ErrorListModal.js';
9
+ import StatusBar from './StatusBar.js';
6
10
  export default function App() {
7
11
  const { exit } = useApp();
8
- // Subscribe to process state
12
+ const { isRawModeSupported } = useStdin();
13
+ // Subscribe to store state
9
14
  const processes = useSyncExternalStore(processStore.subscribe, processStore.getSnapshot);
10
- // Handle exit signal
11
15
  const shouldExit = useSyncExternalStore(processStore.subscribe, processStore.getShouldExit);
16
+ const mode = useSyncExternalStore(processStore.subscribe, processStore.getMode);
17
+ const selectedErrorIndex = useSyncExternalStore(processStore.subscribe, processStore.getSelectedErrorIndex);
18
+ // Derived state
19
+ const completedProcesses = processStore.getCompletedProcesses();
20
+ const runningProcesses = processStore.getRunningProcesses();
21
+ const failedProcesses = processStore.getFailedProcesses();
22
+ const runningCount = processStore.getRunningCount();
23
+ const doneCount = processStore.getDoneCount();
24
+ const errorCount = processStore.getErrorCount();
25
+ const errorLineCount = processStore.getErrorLineCount();
26
+ // Handle exit signal
12
27
  useEffect(()=>{
13
28
  if (shouldExit) {
14
29
  exit();
@@ -17,10 +32,80 @@ export default function App() {
17
32
  shouldExit,
18
33
  exit
19
34
  ]);
20
- return /*#__PURE__*/ _jsx(Box, {
35
+ // Keyboard handling (only active when raw mode is supported)
36
+ useInput((input, key)=>{
37
+ if (mode === 'normal') {
38
+ if (input === 'e' && errorCount > 0) {
39
+ processStore.setMode('errorList');
40
+ }
41
+ } else if (mode === 'errorList') {
42
+ if (key.escape) {
43
+ processStore.setMode('normal');
44
+ } else if (key.downArrow) {
45
+ processStore.selectNextError();
46
+ } else if (key.upArrow) {
47
+ processStore.selectPrevError();
48
+ } else if (key.return) {
49
+ processStore.setMode('errorDetail');
50
+ }
51
+ } else if (mode === 'errorDetail') {
52
+ if (key.escape) {
53
+ processStore.setMode('errorList');
54
+ } else if (key.downArrow) {
55
+ processStore.selectNextError();
56
+ } else if (key.upArrow) {
57
+ processStore.selectPrevError();
58
+ }
59
+ }
60
+ }, {
61
+ isActive: isRawModeSupported === true
62
+ });
63
+ // Error list modal
64
+ if (mode === 'errorList') {
65
+ return /*#__PURE__*/ _jsx(ErrorListModal, {
66
+ errors: failedProcesses,
67
+ selectedIndex: selectedErrorIndex,
68
+ totalErrorLines: errorLineCount
69
+ });
70
+ }
71
+ // Error detail modal
72
+ if (mode === 'errorDetail') {
73
+ const selectedError = processStore.getSelectedError();
74
+ if (selectedError) {
75
+ return /*#__PURE__*/ _jsx(ErrorDetailModal, {
76
+ error: selectedError,
77
+ currentIndex: selectedErrorIndex,
78
+ totalErrors: failedProcesses.length
79
+ });
80
+ }
81
+ // Fallback if no error selected
82
+ processStore.setMode('errorList');
83
+ }
84
+ // Normal view
85
+ return /*#__PURE__*/ _jsxs(Box, {
21
86
  flexDirection: "column",
22
- children: processes.map((item)=>/*#__PURE__*/ _jsx(ChildProcess, {
23
- item: item
24
- }, item.id))
87
+ children: [
88
+ /*#__PURE__*/ _jsx(Static, {
89
+ items: completedProcesses,
90
+ children: (item)=>/*#__PURE__*/ _jsx(CompactProcessLine, {
91
+ item: item
92
+ }, item.id)
93
+ }),
94
+ completedProcesses.length > 0 && runningProcesses.length > 0 && /*#__PURE__*/ _jsx(Divider, {}),
95
+ runningProcesses.map((item)=>/*#__PURE__*/ _jsx(CompactProcessLine, {
96
+ item: item
97
+ }, item.id)),
98
+ processes.length > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
99
+ children: [
100
+ /*#__PURE__*/ _jsx(Divider, {}),
101
+ /*#__PURE__*/ _jsx(StatusBar, {
102
+ running: runningCount,
103
+ done: doneCount,
104
+ errors: errorCount,
105
+ errorLines: errorLineCount
106
+ })
107
+ ]
108
+ })
109
+ ]
25
110
  });
26
111
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/App.tsx"],"sourcesContent":["import { Box, useApp } from 'ink';\nimport { useEffect, useSyncExternalStore } from 'react';\nimport { processStore } from '../state/processStore.ts';\nimport type { ChildProcess as ChildProcessT } from '../types.ts';\nimport ChildProcess from './ChildProcess.ts';\n\nexport default function App(): React.JSX.Element {\n const { exit } = useApp();\n\n // Subscribe to process state\n const processes = useSyncExternalStore(processStore.subscribe, processStore.getSnapshot);\n\n // Handle exit signal\n const shouldExit = useSyncExternalStore(processStore.subscribe, processStore.getShouldExit);\n\n useEffect(() => {\n if (shouldExit) {\n exit();\n }\n }, [shouldExit, exit]);\n\n return (\n <Box flexDirection=\"column\">\n {processes.map((item: ChildProcessT) => (\n <ChildProcess key={item.id} item={item} />\n ))}\n </Box>\n );\n}\n"],"names":["Box","useApp","useEffect","useSyncExternalStore","processStore","ChildProcess","App","exit","processes","subscribe","getSnapshot","shouldExit","getShouldExit","flexDirection","map","item","id"],"mappings":";AAAA,SAASA,GAAG,EAAEC,MAAM,QAAQ,MAAM;AAClC,SAASC,SAAS,EAAEC,oBAAoB,QAAQ,QAAQ;AACxD,SAASC,YAAY,QAAQ,2BAA2B;AAExD,OAAOC,kBAAkB,oBAAoB;AAE7C,eAAe,SAASC;IACtB,MAAM,EAAEC,IAAI,EAAE,GAAGN;IAEjB,6BAA6B;IAC7B,MAAMO,YAAYL,qBAAqBC,aAAaK,SAAS,EAAEL,aAAaM,WAAW;IAEvF,qBAAqB;IACrB,MAAMC,aAAaR,qBAAqBC,aAAaK,SAAS,EAAEL,aAAaQ,aAAa;IAE1FV,UAAU;QACR,IAAIS,YAAY;YACdJ;QACF;IACF,GAAG;QAACI;QAAYJ;KAAK;IAErB,qBACE,KAACP;QAAIa,eAAc;kBAChBL,UAAUM,GAAG,CAAC,CAACC,qBACd,KAACV;gBAA2BU,MAAMA;eAAfA,KAAKC,EAAE;;AAIlC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/App.tsx"],"sourcesContent":["import { Box, Static, useApp, useInput, useStdin } from 'ink';\nimport { useEffect, useSyncExternalStore } from 'react';\nimport { processStore } from '../state/processStore.ts';\nimport CompactProcessLine from './CompactProcessLine.ts';\nimport Divider from './Divider.ts';\nimport ErrorDetailModal from './ErrorDetailModal.ts';\nimport ErrorListModal from './ErrorListModal.ts';\nimport StatusBar from './StatusBar.ts';\n\nexport default function App(): React.JSX.Element {\n const { exit } = useApp();\n const { isRawModeSupported } = useStdin();\n\n // Subscribe to store state\n const processes = useSyncExternalStore(processStore.subscribe, processStore.getSnapshot);\n const shouldExit = useSyncExternalStore(processStore.subscribe, processStore.getShouldExit);\n const mode = useSyncExternalStore(processStore.subscribe, processStore.getMode);\n const selectedErrorIndex = useSyncExternalStore(processStore.subscribe, processStore.getSelectedErrorIndex);\n\n // Derived state\n const completedProcesses = processStore.getCompletedProcesses();\n const runningProcesses = processStore.getRunningProcesses();\n const failedProcesses = processStore.getFailedProcesses();\n const runningCount = processStore.getRunningCount();\n const doneCount = processStore.getDoneCount();\n const errorCount = processStore.getErrorCount();\n const errorLineCount = processStore.getErrorLineCount();\n\n // Handle exit signal\n useEffect(() => {\n if (shouldExit) {\n exit();\n }\n }, [shouldExit, exit]);\n\n // Keyboard handling (only active when raw mode is supported)\n useInput(\n (input, key) => {\n if (mode === 'normal') {\n if (input === 'e' && errorCount > 0) {\n processStore.setMode('errorList');\n }\n } else if (mode === 'errorList') {\n if (key.escape) {\n processStore.setMode('normal');\n } else if (key.downArrow) {\n processStore.selectNextError();\n } else if (key.upArrow) {\n processStore.selectPrevError();\n } else if (key.return) {\n processStore.setMode('errorDetail');\n }\n } else if (mode === 'errorDetail') {\n if (key.escape) {\n processStore.setMode('errorList');\n } else if (key.downArrow) {\n processStore.selectNextError();\n } else if (key.upArrow) {\n processStore.selectPrevError();\n }\n }\n },\n { isActive: isRawModeSupported === true }\n );\n\n // Error list modal\n if (mode === 'errorList') {\n return <ErrorListModal errors={failedProcesses} selectedIndex={selectedErrorIndex} totalErrorLines={errorLineCount} />;\n }\n\n // Error detail modal\n if (mode === 'errorDetail') {\n const selectedError = processStore.getSelectedError();\n if (selectedError) {\n return <ErrorDetailModal error={selectedError} currentIndex={selectedErrorIndex} totalErrors={failedProcesses.length} />;\n }\n // Fallback if no error selected\n processStore.setMode('errorList');\n }\n\n // Normal view\n return (\n <Box flexDirection=\"column\">\n {/* Static area - completed processes (completion order) */}\n <Static items={completedProcesses}>{(item) => <CompactProcessLine key={item.id} item={item} />}</Static>\n\n {/* Divider between completed and running */}\n {completedProcesses.length > 0 && runningProcesses.length > 0 && <Divider />}\n\n {/* Dynamic area - running processes */}\n {runningProcesses.map((item) => (\n <CompactProcessLine key={item.id} item={item} />\n ))}\n\n {/* Status bar */}\n {processes.length > 0 && (\n <>\n <Divider />\n <StatusBar running={runningCount} done={doneCount} errors={errorCount} errorLines={errorLineCount} />\n </>\n )}\n </Box>\n );\n}\n"],"names":["Box","Static","useApp","useInput","useStdin","useEffect","useSyncExternalStore","processStore","CompactProcessLine","Divider","ErrorDetailModal","ErrorListModal","StatusBar","App","exit","isRawModeSupported","processes","subscribe","getSnapshot","shouldExit","getShouldExit","mode","getMode","selectedErrorIndex","getSelectedErrorIndex","completedProcesses","getCompletedProcesses","runningProcesses","getRunningProcesses","failedProcesses","getFailedProcesses","runningCount","getRunningCount","doneCount","getDoneCount","errorCount","getErrorCount","errorLineCount","getErrorLineCount","input","key","setMode","escape","downArrow","selectNextError","upArrow","selectPrevError","return","isActive","errors","selectedIndex","totalErrorLines","selectedError","getSelectedError","error","currentIndex","totalErrors","length","flexDirection","items","item","id","map","running","done","errorLines"],"mappings":"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"}