spawn-term 3.1.3 → 3.1.4

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.
@@ -24,6 +24,7 @@ function _interop_require_default(obj) {
24
24
  default: obj
25
25
  };
26
26
  }
27
+ var isMac = process.platform === 'darwin';
27
28
  function AppContent(param) {
28
29
  var store = param.store;
29
30
  var exit = (0, _ink.useApp)().exit;
@@ -47,8 +48,10 @@ function AppContent(param) {
47
48
  var isInteractive = (0, _react.useSyncExternalStore)(store.subscribe, store.getIsInteractive);
48
49
  // Calculate visible process count (reserve lines for header, divider, status bar, expanded output)
49
50
  // When a process is expanded, reserve space for the expanded output to prevent terminal scrolling
51
+ // In interactive mode without expansion, reserve space for potential list scroll hint
50
52
  var expandedHeight = expandedId ? _constantsts.EXPANDED_MAX_VISIBLE_LINES + 1 : 0; // +1 for scroll hint
51
- var reservedLines = (header ? 2 : 0) + (showStatusBar ? 2 : 0) + expandedHeight;
53
+ var listHintHeight = mode === 'interactive' && !expandedId ? 1 : 0; // Reserve for list scroll hint
54
+ var reservedLines = (header ? 2 : 0) + (showStatusBar ? 2 : 0) + expandedHeight + listHintHeight;
52
55
  var visibleProcessCount = Math.max(1, terminalHeight - reservedLines);
53
56
  // Derived state (computed from processes which is already subscribed)
54
57
  var runningCount = store.getRunningCount();
@@ -77,6 +80,18 @@ function AppContent(param) {
77
80
  mode,
78
81
  store
79
82
  ]);
83
+ // Clamp viewport when collapsing to avoid empty space
84
+ // This runs after render with correct visibleProcessCount
85
+ (0, _react.useEffect)(function() {
86
+ if (mode === 'interactive' && !expandedId) {
87
+ store.clampListViewport(visibleProcessCount);
88
+ }
89
+ }, [
90
+ mode,
91
+ expandedId,
92
+ visibleProcessCount,
93
+ store
94
+ ]);
80
95
  // Keyboard handling (only active when raw mode is supported)
81
96
  (0, _ink.useInput)(function(input, key) {
82
97
  if (mode === 'normal') {
@@ -85,33 +100,45 @@ function AppContent(param) {
85
100
  store.toggleErrorFooter();
86
101
  }
87
102
  } else if (mode === 'interactive') {
103
+ // Pre-calculate visible counts for expand/collapse transitions
104
+ var baseReserved = (header ? 2 : 0) + (showStatusBar ? 2 : 0);
105
+ var visibleWhenExpanded = Math.max(1, terminalHeight - baseReserved - _constantsts.EXPANDED_MAX_VISIBLE_LINES - 1);
106
+ var visibleWhenCollapsed = Math.max(1, terminalHeight - baseReserved - 1); // -1 for list hint
88
107
  if (input === 'q' || key.escape) {
89
108
  if (expandedId) {
90
- store.collapse();
109
+ store.collapse(visibleWhenCollapsed);
91
110
  } else {
92
111
  store.signalExit(function() {});
93
112
  }
94
113
  } else if (key.return) {
95
- store.toggleExpand();
114
+ store.toggleExpand(visibleWhenExpanded, visibleWhenCollapsed);
96
115
  // Jump to top - Option+↑ (detected as meta), vim: g
97
116
  // Must check meta+arrow BEFORE plain arrow
98
117
  } else if (key.meta && key.upArrow || input === 'g') {
99
118
  if (expandedId) {
100
119
  store.scrollToTop();
120
+ } else {
121
+ store.selectFirst(visibleProcessCount);
101
122
  }
102
123
  // Jump to bottom - Option+↓ (detected as meta), vim: G
103
124
  } else if (key.meta && key.downArrow || input === 'G') {
104
125
  if (expandedId) {
105
126
  store.scrollToBottom(_constantsts.EXPANDED_MAX_VISIBLE_LINES);
127
+ } else {
128
+ store.selectLast(visibleProcessCount);
106
129
  }
107
- // Page scrolling - Tab/Shift+Tab
130
+ // Page scrolling - Tab/Shift+Tab (use same page size as expanded view)
108
131
  } else if (key.tab && key.shift) {
109
132
  if (expandedId) {
110
133
  store.scrollPageUp(_constantsts.EXPANDED_MAX_VISIBLE_LINES);
134
+ } else {
135
+ store.selectPageUp(_constantsts.EXPANDED_MAX_VISIBLE_LINES, visibleProcessCount);
111
136
  }
112
137
  } else if (key.tab && !key.shift) {
113
138
  if (expandedId) {
114
139
  store.scrollPageDown(_constantsts.EXPANDED_MAX_VISIBLE_LINES);
140
+ } else {
141
+ store.selectPageDown(_constantsts.EXPANDED_MAX_VISIBLE_LINES, visibleProcessCount);
115
142
  }
116
143
  // Line scrolling - arrows and vim j/k
117
144
  } else if (key.downArrow || input === 'j') {
@@ -159,24 +186,36 @@ function AppContent(param) {
159
186
  /*#__PURE__*/ (0, _jsxruntime.jsx)(_Dividerts.default, {})
160
187
  ]
161
188
  }),
162
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Box, {
189
+ /*#__PURE__*/ (0, _jsxruntime.jsxs)(_ink.Box, {
163
190
  flexDirection: "column",
164
- children: visibleProcesses.map(function(item) {
165
- var originalIndex = processes.indexOf(item);
166
- return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_ink.Box, {
167
- flexDirection: "column",
191
+ children: [
192
+ visibleProcesses.map(function(item) {
193
+ var originalIndex = processes.indexOf(item);
194
+ return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_ink.Box, {
195
+ flexDirection: "column",
196
+ children: [
197
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_CompactProcessLinets.default, {
198
+ item: item,
199
+ isSelected: showSelection && originalIndex === selectedIndex
200
+ }),
201
+ expandedId === item.id && /*#__PURE__*/ (0, _jsxruntime.jsx)(_ExpandedOutputts.default, {
202
+ lines: store.getProcessLines(item.id),
203
+ scrollOffset: scrollOffset
204
+ })
205
+ ]
206
+ }, item.id);
207
+ }),
208
+ mode === 'interactive' && !expandedId && processes.length > visibleProcessCount && /*#__PURE__*/ (0, _jsxruntime.jsxs)(_ink.Text, {
209
+ dimColor: true,
168
210
  children: [
169
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_CompactProcessLinets.default, {
170
- item: item,
171
- isSelected: showSelection && originalIndex === selectedIndex
172
- }),
173
- expandedId === item.id && /*#__PURE__*/ (0, _jsxruntime.jsx)(_ExpandedOutputts.default, {
174
- lines: store.getProcessLines(item.id),
175
- scrollOffset: scrollOffset
176
- })
211
+ "[+",
212
+ processes.length - visibleProcessCount,
213
+ " more, Tab/⇧Tab page, ",
214
+ isMac ? '⌥↑/↓' : 'g/G',
215
+ " top/bottom]"
177
216
  ]
178
- }, item.id);
179
- })
217
+ })
218
+ ]
180
219
  }),
181
220
  showStatusBar && processes.length > 0 && /*#__PURE__*/ (0, _jsxruntime.jsxs)(_jsxruntime.Fragment, {
182
221
  children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/App.tsx"],"sourcesContent":["import { Box, Text, useApp, useInput, useStdin, useStdout } from 'ink';\nimport { useEffect, useMemo, useSyncExternalStore } from 'react';\nimport { EXPANDED_MAX_VISIBLE_LINES } from '../constants.ts';\nimport type { ProcessStore } from '../state/processStore.ts';\nimport { StoreContext } from '../state/StoreContext.ts';\nimport CompactProcessLine from './CompactProcessLine.ts';\nimport Divider from './Divider.ts';\nimport ErrorFooter from './ErrorFooter.ts';\nimport ExpandedOutput from './ExpandedOutput.ts';\nimport StatusBar from './StatusBar.ts';\n\ninterface AppProps {\n store: ProcessStore;\n}\n\nfunction AppContent({ store }: AppProps): React.JSX.Element {\n const { exit } = useApp();\n const { isRawModeSupported } = useStdin();\n const { stdout } = useStdout();\n const terminalHeight = stdout?.rows || 24;\n\n // Subscribe to store state\n const processes = useSyncExternalStore(store.subscribe, store.getSnapshot);\n const shouldExit = useSyncExternalStore(store.subscribe, store.getShouldExit);\n const mode = useSyncExternalStore(store.subscribe, store.getMode);\n const selectedIndex = useSyncExternalStore(store.subscribe, store.getSelectedIndex);\n const expandedId = useSyncExternalStore(store.subscribe, store.getExpandedId);\n const scrollOffset = useSyncExternalStore(store.subscribe, store.getScrollOffset);\n const listScrollOffset = useSyncExternalStore(store.subscribe, store.getListScrollOffset);\n const errorFooterExpanded = useSyncExternalStore(store.subscribe, store.getErrorFooterExpanded);\n // Subscribe to buffer version to trigger re-renders when terminal buffer content changes\n const _bufferVersion = useSyncExternalStore(store.subscribe, store.getBufferVersion);\n\n // Subscribed state that triggers re-renders\n const header = useSyncExternalStore(store.subscribe, store.getHeader);\n const showStatusBar = useSyncExternalStore(store.subscribe, store.getShowStatusBar);\n const isInteractive = useSyncExternalStore(store.subscribe, store.getIsInteractive);\n\n // Calculate visible process count (reserve lines for header, divider, status bar, expanded output)\n // When a process is expanded, reserve space for the expanded output to prevent terminal scrolling\n const expandedHeight = expandedId ? EXPANDED_MAX_VISIBLE_LINES + 1 : 0; // +1 for scroll hint\n const reservedLines = (header ? 2 : 0) + (showStatusBar ? 2 : 0) + expandedHeight;\n const visibleProcessCount = Math.max(1, terminalHeight - reservedLines);\n\n // Derived state (computed from processes which is already subscribed)\n const runningCount = store.getRunningCount();\n const doneCount = store.getDoneCount();\n const errorCount = store.getErrorCount();\n const errorLineCount = store.getErrorLineCount();\n const _isAllComplete = store.isAllComplete();\n const errorLines = store.getErrorLines();\n\n // Handle exit signal\n useEffect(() => {\n if (shouldExit) {\n exit();\n }\n }, [shouldExit, exit]);\n\n // Auto-enter interactive mode immediately when interactive flag is set\n // This allows selecting and viewing logs of running processes\n useEffect(() => {\n if (isInteractive && mode === 'normal') {\n store.setMode('interactive');\n }\n }, [isInteractive, mode, store]);\n\n // Keyboard handling (only active when raw mode is supported)\n useInput(\n (input, key) => {\n if (mode === 'normal') {\n // In non-interactive mode, 'e' toggles error footer\n if (input === 'e' && errorCount > 0) {\n store.toggleErrorFooter();\n }\n } else if (mode === 'interactive') {\n if (input === 'q' || key.escape) {\n if (expandedId) {\n store.collapse();\n } else {\n store.signalExit(() => {});\n }\n } else if (key.return) {\n store.toggleExpand();\n // Jump to top - Option+↑ (detected as meta), vim: g\n // Must check meta+arrow BEFORE plain arrow\n } else if ((key.meta && key.upArrow) || input === 'g') {\n if (expandedId) {\n store.scrollToTop();\n }\n // Jump to bottom - Option+↓ (detected as meta), vim: G\n } else if ((key.meta && key.downArrow) || input === 'G') {\n if (expandedId) {\n store.scrollToBottom(EXPANDED_MAX_VISIBLE_LINES);\n }\n // Page scrolling - Tab/Shift+Tab\n } else if (key.tab && key.shift) {\n if (expandedId) {\n store.scrollPageUp(EXPANDED_MAX_VISIBLE_LINES);\n }\n } else if (key.tab && !key.shift) {\n if (expandedId) {\n store.scrollPageDown(EXPANDED_MAX_VISIBLE_LINES);\n }\n // Line scrolling - arrows and vim j/k\n } else if (key.downArrow || input === 'j') {\n if (expandedId) {\n store.scrollDown(EXPANDED_MAX_VISIBLE_LINES);\n } else {\n store.selectNext(visibleProcessCount);\n }\n } else if (key.upArrow || input === 'k') {\n if (expandedId) {\n store.scrollUp();\n } else {\n store.selectPrev(visibleProcessCount);\n }\n }\n }\n },\n { isActive: isRawModeSupported === true }\n );\n\n // Slice processes to visible viewport in interactive mode\n const visibleProcesses = useMemo(() => {\n if (mode === 'interactive') {\n return processes.slice(listScrollOffset, listScrollOffset + visibleProcessCount);\n }\n return processes;\n }, [processes, mode, listScrollOffset, visibleProcessCount]);\n\n // Normal/Interactive view - render in original registration order\n const showSelection = mode === 'interactive';\n\n // Force full re-render when layout structure changes\n // Note: scrollOffset is NOT included - scrolling within expansion doesn't change structure\n const layoutKey = `${listScrollOffset}-${expandedId}-${errorCount}-${errorFooterExpanded}`;\n\n return (\n <Box key={layoutKey} flexDirection=\"column\">\n {/* Header */}\n {header && (\n <>\n <Text>{header}</Text>\n <Divider />\n </>\n )}\n\n {/* Visible processes */}\n <Box flexDirection=\"column\">\n {visibleProcesses.map((item) => {\n const originalIndex = processes.indexOf(item);\n return (\n <Box key={item.id} flexDirection=\"column\">\n <CompactProcessLine item={item} isSelected={showSelection && originalIndex === selectedIndex} />\n {expandedId === item.id && <ExpandedOutput lines={store.getProcessLines(item.id)} scrollOffset={scrollOffset} />}\n </Box>\n );\n })}\n </Box>\n\n {/* Status bar */}\n {showStatusBar && processes.length > 0 && (\n <>\n <Divider />\n <StatusBar running={runningCount} done={doneCount} errors={errorCount} errorLines={errorLineCount} />\n </>\n )}\n\n {/* Error footer (non-interactive mode only) */}\n {!isInteractive && errorCount > 0 && <ErrorFooter errors={errorLines} isExpanded={errorFooterExpanded} />}\n </Box>\n );\n}\n\n// Wrapper component that provides store context\nexport default function App({ store }: AppProps): React.JSX.Element {\n return (\n <StoreContext.Provider value={store}>\n <AppContent store={store} />\n </StoreContext.Provider>\n );\n}\n"],"names":["App","AppContent","store","exit","useApp","isRawModeSupported","useStdin","stdout","useStdout","terminalHeight","rows","processes","useSyncExternalStore","subscribe","getSnapshot","shouldExit","getShouldExit","mode","getMode","selectedIndex","getSelectedIndex","expandedId","getExpandedId","scrollOffset","getScrollOffset","listScrollOffset","getListScrollOffset","errorFooterExpanded","getErrorFooterExpanded","_bufferVersion","getBufferVersion","header","getHeader","showStatusBar","getShowStatusBar","isInteractive","getIsInteractive","expandedHeight","EXPANDED_MAX_VISIBLE_LINES","reservedLines","visibleProcessCount","Math","max","runningCount","getRunningCount","doneCount","getDoneCount","errorCount","getErrorCount","errorLineCount","getErrorLineCount","_isAllComplete","isAllComplete","errorLines","getErrorLines","useEffect","setMode","useInput","input","key","toggleErrorFooter","escape","collapse","signalExit","return","toggleExpand","meta","upArrow","scrollToTop","downArrow","scrollToBottom","tab","shift","scrollPageUp","scrollPageDown","scrollDown","selectNext","scrollUp","selectPrev","isActive","visibleProcesses","useMemo","slice","showSelection","layoutKey","Box","flexDirection","Text","Divider","map","item","originalIndex","indexOf","CompactProcessLine","isSelected","id","ExpandedOutput","lines","getProcessLines","length","StatusBar","running","done","errors","ErrorFooter","isExpanded","StoreContext","Provider","value"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/App.tsx"],"sourcesContent":["import { Box, Text, useApp, useInput, useStdin, useStdout } from 'ink';\nimport { useEffect, useMemo, useSyncExternalStore } from 'react';\nimport { EXPANDED_MAX_VISIBLE_LINES } from '../constants.ts';\nimport type { ProcessStore } from '../state/processStore.ts';\nimport { StoreContext } from '../state/StoreContext.ts';\nimport CompactProcessLine from './CompactProcessLine.ts';\nimport Divider from './Divider.ts';\nimport ErrorFooter from './ErrorFooter.ts';\nimport ExpandedOutput from './ExpandedOutput.ts';\nimport StatusBar from './StatusBar.ts';\n\nconst isMac = process.platform === 'darwin';\n\ninterface AppProps {\n store: ProcessStore;\n}\n\nfunction AppContent({ store }: AppProps): React.JSX.Element {\n const { exit } = useApp();\n const { isRawModeSupported } = useStdin();\n const { stdout } = useStdout();\n const terminalHeight = stdout?.rows || 24;\n\n // Subscribe to store state\n const processes = useSyncExternalStore(store.subscribe, store.getSnapshot);\n const shouldExit = useSyncExternalStore(store.subscribe, store.getShouldExit);\n const mode = useSyncExternalStore(store.subscribe, store.getMode);\n const selectedIndex = useSyncExternalStore(store.subscribe, store.getSelectedIndex);\n const expandedId = useSyncExternalStore(store.subscribe, store.getExpandedId);\n const scrollOffset = useSyncExternalStore(store.subscribe, store.getScrollOffset);\n const listScrollOffset = useSyncExternalStore(store.subscribe, store.getListScrollOffset);\n const errorFooterExpanded = useSyncExternalStore(store.subscribe, store.getErrorFooterExpanded);\n // Subscribe to buffer version to trigger re-renders when terminal buffer content changes\n const _bufferVersion = useSyncExternalStore(store.subscribe, store.getBufferVersion);\n\n // Subscribed state that triggers re-renders\n const header = useSyncExternalStore(store.subscribe, store.getHeader);\n const showStatusBar = useSyncExternalStore(store.subscribe, store.getShowStatusBar);\n const isInteractive = useSyncExternalStore(store.subscribe, store.getIsInteractive);\n\n // Calculate visible process count (reserve lines for header, divider, status bar, expanded output)\n // When a process is expanded, reserve space for the expanded output to prevent terminal scrolling\n // In interactive mode without expansion, reserve space for potential list scroll hint\n const expandedHeight = expandedId ? EXPANDED_MAX_VISIBLE_LINES + 1 : 0; // +1 for scroll hint\n const listHintHeight = mode === 'interactive' && !expandedId ? 1 : 0; // Reserve for list scroll hint\n const reservedLines = (header ? 2 : 0) + (showStatusBar ? 2 : 0) + expandedHeight + listHintHeight;\n const visibleProcessCount = Math.max(1, terminalHeight - reservedLines);\n\n // Derived state (computed from processes which is already subscribed)\n const runningCount = store.getRunningCount();\n const doneCount = store.getDoneCount();\n const errorCount = store.getErrorCount();\n const errorLineCount = store.getErrorLineCount();\n const _isAllComplete = store.isAllComplete();\n const errorLines = store.getErrorLines();\n\n // Handle exit signal\n useEffect(() => {\n if (shouldExit) {\n exit();\n }\n }, [shouldExit, exit]);\n\n // Auto-enter interactive mode immediately when interactive flag is set\n // This allows selecting and viewing logs of running processes\n useEffect(() => {\n if (isInteractive && mode === 'normal') {\n store.setMode('interactive');\n }\n }, [isInteractive, mode, store]);\n\n // Clamp viewport when collapsing to avoid empty space\n // This runs after render with correct visibleProcessCount\n useEffect(() => {\n if (mode === 'interactive' && !expandedId) {\n store.clampListViewport(visibleProcessCount);\n }\n }, [mode, expandedId, visibleProcessCount, store]);\n\n // Keyboard handling (only active when raw mode is supported)\n useInput(\n (input, key) => {\n if (mode === 'normal') {\n // In non-interactive mode, 'e' toggles error footer\n if (input === 'e' && errorCount > 0) {\n store.toggleErrorFooter();\n }\n } else if (mode === 'interactive') {\n // Pre-calculate visible counts for expand/collapse transitions\n const baseReserved = (header ? 2 : 0) + (showStatusBar ? 2 : 0);\n const visibleWhenExpanded = Math.max(1, terminalHeight - baseReserved - EXPANDED_MAX_VISIBLE_LINES - 1);\n const visibleWhenCollapsed = Math.max(1, terminalHeight - baseReserved - 1); // -1 for list hint\n\n if (input === 'q' || key.escape) {\n if (expandedId) {\n store.collapse(visibleWhenCollapsed);\n } else {\n store.signalExit(() => {});\n }\n } else if (key.return) {\n store.toggleExpand(visibleWhenExpanded, visibleWhenCollapsed);\n // Jump to top - Option+↑ (detected as meta), vim: g\n // Must check meta+arrow BEFORE plain arrow\n } else if ((key.meta && key.upArrow) || input === 'g') {\n if (expandedId) {\n store.scrollToTop();\n } else {\n store.selectFirst(visibleProcessCount);\n }\n // Jump to bottom - Option+↓ (detected as meta), vim: G\n } else if ((key.meta && key.downArrow) || input === 'G') {\n if (expandedId) {\n store.scrollToBottom(EXPANDED_MAX_VISIBLE_LINES);\n } else {\n store.selectLast(visibleProcessCount);\n }\n // Page scrolling - Tab/Shift+Tab (use same page size as expanded view)\n } else if (key.tab && key.shift) {\n if (expandedId) {\n store.scrollPageUp(EXPANDED_MAX_VISIBLE_LINES);\n } else {\n store.selectPageUp(EXPANDED_MAX_VISIBLE_LINES, visibleProcessCount);\n }\n } else if (key.tab && !key.shift) {\n if (expandedId) {\n store.scrollPageDown(EXPANDED_MAX_VISIBLE_LINES);\n } else {\n store.selectPageDown(EXPANDED_MAX_VISIBLE_LINES, visibleProcessCount);\n }\n // Line scrolling - arrows and vim j/k\n } else if (key.downArrow || input === 'j') {\n if (expandedId) {\n store.scrollDown(EXPANDED_MAX_VISIBLE_LINES);\n } else {\n store.selectNext(visibleProcessCount);\n }\n } else if (key.upArrow || input === 'k') {\n if (expandedId) {\n store.scrollUp();\n } else {\n store.selectPrev(visibleProcessCount);\n }\n }\n }\n },\n { isActive: isRawModeSupported === true }\n );\n\n // Slice processes to visible viewport in interactive mode\n const visibleProcesses = useMemo(() => {\n if (mode === 'interactive') {\n return processes.slice(listScrollOffset, listScrollOffset + visibleProcessCount);\n }\n return processes;\n }, [processes, mode, listScrollOffset, visibleProcessCount]);\n\n // Normal/Interactive view - render in original registration order\n const showSelection = mode === 'interactive';\n\n // Force full re-render when layout structure changes\n // Note: scrollOffset is NOT included - scrolling within expansion doesn't change structure\n const layoutKey = `${listScrollOffset}-${expandedId}-${errorCount}-${errorFooterExpanded}`;\n\n return (\n <Box key={layoutKey} flexDirection=\"column\">\n {/* Header */}\n {header && (\n <>\n <Text>{header}</Text>\n <Divider />\n </>\n )}\n\n {/* Visible processes */}\n <Box flexDirection=\"column\">\n {visibleProcesses.map((item) => {\n const originalIndex = processes.indexOf(item);\n return (\n <Box key={item.id} flexDirection=\"column\">\n <CompactProcessLine item={item} isSelected={showSelection && originalIndex === selectedIndex} />\n {expandedId === item.id && <ExpandedOutput lines={store.getProcessLines(item.id)} scrollOffset={scrollOffset} />}\n </Box>\n );\n })}\n {/* List scroll hint (interactive mode without expansion) */}\n {mode === 'interactive' && !expandedId && processes.length > visibleProcessCount && (\n <Text dimColor>\n [+{processes.length - visibleProcessCount} more, Tab/⇧Tab page, {isMac ? '⌥↑/↓' : 'g/G'} top/bottom]\n </Text>\n )}\n </Box>\n\n {/* Status bar */}\n {showStatusBar && processes.length > 0 && (\n <>\n <Divider />\n <StatusBar running={runningCount} done={doneCount} errors={errorCount} errorLines={errorLineCount} />\n </>\n )}\n\n {/* Error footer (non-interactive mode only) */}\n {!isInteractive && errorCount > 0 && <ErrorFooter errors={errorLines} isExpanded={errorFooterExpanded} />}\n </Box>\n );\n}\n\n// Wrapper component that provides store context\nexport default function App({ store }: AppProps): React.JSX.Element {\n return (\n <StoreContext.Provider value={store}>\n <AppContent store={store} />\n </StoreContext.Provider>\n );\n}\n"],"names":["App","isMac","process","platform","AppContent","store","exit","useApp","isRawModeSupported","useStdin","stdout","useStdout","terminalHeight","rows","processes","useSyncExternalStore","subscribe","getSnapshot","shouldExit","getShouldExit","mode","getMode","selectedIndex","getSelectedIndex","expandedId","getExpandedId","scrollOffset","getScrollOffset","listScrollOffset","getListScrollOffset","errorFooterExpanded","getErrorFooterExpanded","_bufferVersion","getBufferVersion","header","getHeader","showStatusBar","getShowStatusBar","isInteractive","getIsInteractive","expandedHeight","EXPANDED_MAX_VISIBLE_LINES","listHintHeight","reservedLines","visibleProcessCount","Math","max","runningCount","getRunningCount","doneCount","getDoneCount","errorCount","getErrorCount","errorLineCount","getErrorLineCount","_isAllComplete","isAllComplete","errorLines","getErrorLines","useEffect","setMode","clampListViewport","useInput","input","key","toggleErrorFooter","baseReserved","visibleWhenExpanded","visibleWhenCollapsed","escape","collapse","signalExit","return","toggleExpand","meta","upArrow","scrollToTop","selectFirst","downArrow","scrollToBottom","selectLast","tab","shift","scrollPageUp","selectPageUp","scrollPageDown","selectPageDown","scrollDown","selectNext","scrollUp","selectPrev","isActive","visibleProcesses","useMemo","slice","showSelection","layoutKey","Box","flexDirection","Text","Divider","map","item","originalIndex","indexOf","CompactProcessLine","isSelected","id","ExpandedOutput","lines","getProcessLines","length","dimColor","StatusBar","running","done","errors","ErrorFooter","isExpanded","StoreContext","Provider","value"],"mappings":"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"}
@@ -43,11 +43,7 @@ var _default = /*#__PURE__*/ (0, _react.memo)(function ErrorFooter(param) {
43
43
  color: "red",
44
44
  children: '\u25b8'
45
45
  }),
46
- " ".concat(summary, " "),
47
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Text, {
48
- dimColor: true,
49
- children: "[e]"
50
- })
46
+ " ".concat(summary)
51
47
  ]
52
48
  })
53
49
  ]
@@ -63,11 +59,7 @@ var _default = /*#__PURE__*/ (0, _react.memo)(function ErrorFooter(param) {
63
59
  color: "red",
64
60
  children: '\u25be'
65
61
  }),
66
- ' Errors ',
67
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Text, {
68
- dimColor: true,
69
- children: "[e]"
70
- })
62
+ ' Errors'
71
63
  ]
72
64
  }),
73
65
  /*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Box, {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/ErrorFooter.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport type { Line } from '../types.ts';\nimport { LineType } from '../types.ts';\nimport Divider from './Divider.ts';\n\ntype ErrorGroup = {\n processName: string;\n lines: Line[];\n};\n\ntype Props = {\n errors: ErrorGroup[];\n isExpanded: boolean;\n};\n\nexport default memo(function ErrorFooter({ errors, isExpanded }: Props) {\n // Calculate totals for collapsed summary\n const totalLines = errors.reduce((sum, e) => sum + e.lines.filter((l) => l.type === LineType.stderr).length, 0);\n const totalProcesses = errors.length;\n\n if (totalProcesses === 0) {\n return null;\n }\n\n const processText = totalProcesses === 1 ? 'process' : 'processes';\n\n if (!isExpanded) {\n // Collapsed view - single summary line\n const summary = totalLines > 0 ? `${totalLines} error line${totalLines === 1 ? '' : 's'} in ${totalProcesses} ${processText}` : `${totalProcesses} failed ${processText}`;\n return (\n <>\n <Divider />\n <Text>\n <Text color=\"red\">{'\\u25b8'}</Text>\n {` ${summary} `}\n <Text dimColor>[e]</Text>\n </Text>\n </>\n );\n }\n\n // Expanded view - show all error lines (or just process names if no stderr)\n return (\n <>\n <Divider />\n <Text>\n <Text color=\"red\">{'\\u25be'}</Text>\n {' Errors '}\n <Text dimColor>[e]</Text>\n </Text>\n <Box flexDirection=\"column\">\n {errors.map((errorGroup) => {\n const stderrLines = errorGroup.lines.filter((line) => line.type === LineType.stderr);\n if (stderrLines.length === 0) {\n // No stderr output - just show process name\n return (\n <Text key={errorGroup.processName}>\n <Text dimColor>[{errorGroup.processName}]</Text> <Text color=\"red\">(failed)</Text>\n </Text>\n );\n }\n return stderrLines.map((line, index) => (\n <Text key={`${errorGroup.processName}-${index}`}>\n <Text dimColor>[{errorGroup.processName}]</Text> {line.text}\n </Text>\n ));\n })}\n </Box>\n </>\n );\n});\n"],"names":["memo","ErrorFooter","errors","isExpanded","totalLines","reduce","sum","e","lines","filter","l","type","LineType","stderr","length","totalProcesses","processText","summary","Divider","Text","color","dimColor","Box","flexDirection","map","errorGroup","stderrLines","line","processName","index","text"],"mappings":";;;;+BAgBA;;;eAAA;;;;mBAhB0B;qBACL;uBAEI;gEACL;;;;;;IAYpB,yBAAeA,IAAAA,WAAI,EAAC,SAASC,YAAY,KAA6B;QAA3BC,SAAF,MAAEA,QAAQC,aAAV,MAAUA;IACjD,yCAAyC;IACzC,IAAMC,aAAaF,OAAOG,MAAM,CAAC,SAACC,KAAKC;eAAMD,MAAMC,EAAEC,KAAK,CAACC,MAAM,CAAC,SAACC;mBAAMA,EAAEC,IAAI,KAAKC,iBAAQ,CAACC,MAAM;WAAEC,MAAM;OAAE;IAC7G,IAAMC,iBAAiBb,OAAOY,MAAM;IAEpC,IAAIC,mBAAmB,GAAG;QACxB,OAAO;IACT;IAEA,IAAMC,cAAcD,mBAAmB,IAAI,YAAY;IAEvD,IAAI,CAACZ,YAAY;QACf,uCAAuC;QACvC,IAAMc,UAAUb,aAAa,IAAI,AAAC,GAA0BA,OAAxBA,YAAW,eAA+CW,OAAlCX,eAAe,IAAI,KAAK,KAAI,QAAwBY,OAAlBD,gBAAe,KAAe,OAAZC,eAAgB,AAAC,GAA2BA,OAAzBD,gBAAe,YAAsB,OAAZC;QAC5J,qBACE;;8BACE,qBAACE,kBAAO;8BACR,sBAACC,SAAI;;sCACH,qBAACA,SAAI;4BAACC,OAAM;sCAAO;;wBACjB,IAAW,OAARH,SAAQ;sCACb,qBAACE,SAAI;4BAACE,QAAQ;sCAAC;;;;;;IAIvB;IAEA,4EAA4E;IAC5E,qBACE;;0BACE,qBAACH,kBAAO;0BACR,sBAACC,SAAI;;kCACH,qBAACA,SAAI;wBAACC,OAAM;kCAAO;;oBAClB;kCACD,qBAACD,SAAI;wBAACE,QAAQ;kCAAC;;;;0BAEjB,qBAACC,QAAG;gBAACC,eAAc;0BAChBrB,OAAOsB,GAAG,CAAC,SAACC;oBACX,IAAMC,cAAcD,WAAWjB,KAAK,CAACC,MAAM,CAAC,SAACkB;+BAASA,KAAKhB,IAAI,KAAKC,iBAAQ,CAACC,MAAM;;oBACnF,IAAIa,YAAYZ,MAAM,KAAK,GAAG;wBAC5B,4CAA4C;wBAC5C,qBACE,sBAACK,SAAI;;8CACH,sBAACA,SAAI;oCAACE,QAAQ;;wCAAC;wCAAEI,WAAWG,WAAW;wCAAC;;;gCAAQ;8CAAC,qBAACT,SAAI;oCAACC,OAAM;8CAAM;;;2BAD1DK,WAAWG,WAAW;oBAIrC;oBACA,OAAOF,YAAYF,GAAG,CAAC,SAACG,MAAME;6CAC5B,sBAACV,SAAI;;8CACH,sBAACA,SAAI;oCAACE,QAAQ;;wCAAC;wCAAEI,WAAWG,WAAW;wCAAC;;;gCAAQ;gCAAED,KAAKG,IAAI;;2BADlD,AAAC,GAA4BD,OAA1BJ,WAAWG,WAAW,EAAC,KAAS,OAANC;;gBAI5C;;;;AAIR"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/ErrorFooter.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport type { Line } from '../types.ts';\nimport { LineType } from '../types.ts';\nimport Divider from './Divider.ts';\n\ntype ErrorGroup = {\n processName: string;\n lines: Line[];\n};\n\ntype Props = {\n errors: ErrorGroup[];\n isExpanded: boolean;\n};\n\nexport default memo(function ErrorFooter({ errors, isExpanded }: Props) {\n // Calculate totals for collapsed summary\n const totalLines = errors.reduce((sum, e) => sum + e.lines.filter((l) => l.type === LineType.stderr).length, 0);\n const totalProcesses = errors.length;\n\n if (totalProcesses === 0) {\n return null;\n }\n\n const processText = totalProcesses === 1 ? 'process' : 'processes';\n\n if (!isExpanded) {\n // Collapsed view - single summary line\n const summary = totalLines > 0 ? `${totalLines} error line${totalLines === 1 ? '' : 's'} in ${totalProcesses} ${processText}` : `${totalProcesses} failed ${processText}`;\n return (\n <>\n <Divider />\n <Text>\n <Text color=\"red\">{'\\u25b8'}</Text>\n {` ${summary}`}\n </Text>\n </>\n );\n }\n\n // Expanded view - show all error lines (or just process names if no stderr)\n return (\n <>\n <Divider />\n <Text>\n <Text color=\"red\">{'\\u25be'}</Text>\n {' Errors'}\n </Text>\n <Box flexDirection=\"column\">\n {errors.map((errorGroup) => {\n const stderrLines = errorGroup.lines.filter((line) => line.type === LineType.stderr);\n if (stderrLines.length === 0) {\n // No stderr output - just show process name\n return (\n <Text key={errorGroup.processName}>\n <Text dimColor>[{errorGroup.processName}]</Text> <Text color=\"red\">(failed)</Text>\n </Text>\n );\n }\n return stderrLines.map((line, index) => (\n <Text key={`${errorGroup.processName}-${index}`}>\n <Text dimColor>[{errorGroup.processName}]</Text> {line.text}\n </Text>\n ));\n })}\n </Box>\n </>\n );\n});\n"],"names":["memo","ErrorFooter","errors","isExpanded","totalLines","reduce","sum","e","lines","filter","l","type","LineType","stderr","length","totalProcesses","processText","summary","Divider","Text","color","Box","flexDirection","map","errorGroup","stderrLines","line","dimColor","processName","index","text"],"mappings":";;;;+BAgBA;;;eAAA;;;;mBAhB0B;qBACL;uBAEI;gEACL;;;;;;IAYpB,yBAAeA,IAAAA,WAAI,EAAC,SAASC,YAAY,KAA6B;QAA3BC,SAAF,MAAEA,QAAQC,aAAV,MAAUA;IACjD,yCAAyC;IACzC,IAAMC,aAAaF,OAAOG,MAAM,CAAC,SAACC,KAAKC;eAAMD,MAAMC,EAAEC,KAAK,CAACC,MAAM,CAAC,SAACC;mBAAMA,EAAEC,IAAI,KAAKC,iBAAQ,CAACC,MAAM;WAAEC,MAAM;OAAE;IAC7G,IAAMC,iBAAiBb,OAAOY,MAAM;IAEpC,IAAIC,mBAAmB,GAAG;QACxB,OAAO;IACT;IAEA,IAAMC,cAAcD,mBAAmB,IAAI,YAAY;IAEvD,IAAI,CAACZ,YAAY;QACf,uCAAuC;QACvC,IAAMc,UAAUb,aAAa,IAAI,AAAC,GAA0BA,OAAxBA,YAAW,eAA+CW,OAAlCX,eAAe,IAAI,KAAK,KAAI,QAAwBY,OAAlBD,gBAAe,KAAe,OAAZC,eAAgB,AAAC,GAA2BA,OAAzBD,gBAAe,YAAsB,OAAZC;QAC5J,qBACE;;8BACE,qBAACE,kBAAO;8BACR,sBAACC,SAAI;;sCACH,qBAACA,SAAI;4BAACC,OAAM;sCAAO;;wBACjB,IAAW,OAARH;;;;;IAIb;IAEA,4EAA4E;IAC5E,qBACE;;0BACE,qBAACC,kBAAO;0BACR,sBAACC,SAAI;;kCACH,qBAACA,SAAI;wBAACC,OAAM;kCAAO;;oBAClB;;;0BAEH,qBAACC,QAAG;gBAACC,eAAc;0BAChBpB,OAAOqB,GAAG,CAAC,SAACC;oBACX,IAAMC,cAAcD,WAAWhB,KAAK,CAACC,MAAM,CAAC,SAACiB;+BAASA,KAAKf,IAAI,KAAKC,iBAAQ,CAACC,MAAM;;oBACnF,IAAIY,YAAYX,MAAM,KAAK,GAAG;wBAC5B,4CAA4C;wBAC5C,qBACE,sBAACK,SAAI;;8CACH,sBAACA,SAAI;oCAACQ,QAAQ;;wCAAC;wCAAEH,WAAWI,WAAW;wCAAC;;;gCAAQ;8CAAC,qBAACT,SAAI;oCAACC,OAAM;8CAAM;;;2BAD1DI,WAAWI,WAAW;oBAIrC;oBACA,OAAOH,YAAYF,GAAG,CAAC,SAACG,MAAMG;6CAC5B,sBAACV,SAAI;;8CACH,sBAACA,SAAI;oCAACQ,QAAQ;;wCAAC;wCAAEH,WAAWI,WAAW;wCAAC;;;gCAAQ;gCAAEF,KAAKI,IAAI;;2BADlD,AAAC,GAA4BD,OAA1BL,WAAWI,WAAW,EAAC,KAAS,OAANC;;gBAI5C;;;;AAIR"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/index-cjs.ts"],"sourcesContent":["export { default as figures } from './lib/figures.ts';\nexport { default as formatArguments } from './lib/formatArguments.ts';\nexport * from './types.ts';\n\nimport type { createSession as createSessionType, Session } from './session.ts';\nexport type { Session };\nexport const createSession = undefined as typeof createSessionType;\n"],"names":["createSession","figures","formatArguments","undefined"],"mappings":";;;;;;;;;;;QAMaA;eAAAA;;QANOC;eAAAA,kBAAO;;QACPC;eAAAA,0BAAe;;;gEADA;wEACQ;qBAC7B;;;;;;;;;;;;;;;;;;;AAIP,IAAMF,gBAAgBG"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/index-cjs.ts"],"sourcesContent":["export { default as figures } from './lib/figures.ts';\nexport { default as formatArguments } from './lib/formatArguments.ts';\nexport type { Navigator } from './state/Navigator.ts';\nexport * from './types.ts';\n\nimport type { createSession as createSessionType, Session } from './session.ts';\nexport type { Session };\nexport const createSession = undefined as typeof createSessionType;\n"],"names":["createSession","figures","formatArguments","undefined"],"mappings":";;;;;;;;;;;QAOaA;eAAAA;;QAPOC;eAAAA,kBAAO;;QACPC;eAAAA,0BAAe;;;gEADA;wEACQ;qBAE7B;;;;;;;;;;;;;;;;;;;AAIP,IAAMF,gBAAgBG"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/index-esm.ts"],"sourcesContent":["export { default as figures } from './lib/figures.ts';\nexport { default as formatArguments } from './lib/formatArguments.ts';\nexport type { TerminalBuffer } from './lib/TerminalBuffer.ts';\nexport * from './types.ts';\n\nimport type { createSession as createSessionType, Session } from './createSessionWrapper.ts';\nexport type { Session };\n\nconst major = +process.versions.node.split('.')[0];\n\nimport { createSession as createSessionImpl } from './createSessionWrapper.ts';\nexport const createSession = major > 18 ? createSessionImpl : (undefined as typeof createSessionType);\n"],"names":["createSession","figures","formatArguments","major","process","versions","node","split","createSessionImpl","undefined"],"mappings":";;;;;;;;;;;QAWaA;eAAAA;;QAXOC;eAAAA,kBAAO;;QACPC;eAAAA,0BAAe;;;gEADA;wEACQ;qBAE7B;sCAOqC;;;;;;;;;;;;;;;;;;;AAFnD,IAAMC,QAAQ,CAACC,QAAQC,QAAQ,CAACC,IAAI,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE;AAG3C,IAAMP,gBAAgBG,QAAQ,KAAKK,qCAAiB,GAAIC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/index-esm.ts"],"sourcesContent":["export { default as figures } from './lib/figures.ts';\nexport { default as formatArguments } from './lib/formatArguments.ts';\nexport type { TerminalBuffer } from './lib/TerminalBuffer.ts';\nexport type { Navigator } from './state/Navigator.ts';\nexport * from './types.ts';\n\nimport type { createSession as createSessionType, Session } from './createSessionWrapper.ts';\nexport type { Session };\n\nconst major = +process.versions.node.split('.')[0];\n\nimport { createSession as createSessionImpl } from './createSessionWrapper.ts';\nexport const createSession = major > 18 ? createSessionImpl : (undefined as typeof createSessionType);\n"],"names":["createSession","figures","formatArguments","major","process","versions","node","split","createSessionImpl","undefined"],"mappings":";;;;;;;;;;;QAYaA;eAAAA;;QAZOC;eAAAA,kBAAO;;QACPC;eAAAA,0BAAe;;;gEADA;wEACQ;qBAG7B;sCAOqC;;;;;;;;;;;;;;;;;;;AAFnD,IAAMC,QAAQ,CAACC,QAAQC,QAAQ,CAACC,IAAI,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE;AAG3C,IAAMP,gBAAgBG,QAAQ,KAAKK,qCAAiB,GAAIC"}
@@ -1,5 +1,6 @@
1
1
  export { default as figures } from './lib/figures.js';
2
2
  export { default as formatArguments } from './lib/formatArguments.js';
3
+ export type { Navigator } from './state/Navigator.js';
3
4
  export * from './types.js';
4
5
  import type { createSession as createSessionType, Session } from './session.js';
5
6
  export type { Session };
@@ -1,6 +1,7 @@
1
1
  export { default as figures } from './lib/figures.js';
2
2
  export { default as formatArguments } from './lib/formatArguments.js';
3
3
  export type { TerminalBuffer } from './lib/TerminalBuffer.js';
4
+ export type { Navigator } from './state/Navigator.js';
4
5
  export * from './types.js';
5
6
  import type { createSession as createSessionType, Session } from './createSessionWrapper.js';
6
7
  export type { Session };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Navigator - a cursor over a bounded list with viewport tracking
3
+ *
4
+ * Used for both:
5
+ * - List navigation (process selection) - wraps around
6
+ * - Scroll navigation (content viewing) - clamps to bounds
7
+ */
8
+ export type NavigatorOptions = {
9
+ getLength: () => number;
10
+ wrap: boolean;
11
+ onMove?: () => void;
12
+ };
13
+ export type Navigator = {
14
+ readonly position: number;
15
+ readonly viewportOffset: number;
16
+ up(step?: number): void;
17
+ down(step?: number): void;
18
+ pageUp(pageSize: number, viewportSize?: number): void;
19
+ pageDown(pageSize: number, viewportSize?: number): void;
20
+ toStart(): void;
21
+ toEnd(): void;
22
+ ensureVisible(viewportSize: number): void;
23
+ clampViewport(viewportSize: number): boolean;
24
+ setPosition(position: number): void;
25
+ reset(): void;
26
+ };
27
+ export declare function createNavigator(options: NavigatorOptions): Navigator;
@@ -4,19 +4,17 @@ type Mode = 'normal' | 'interactive';
4
4
  export declare class ProcessStore {
5
5
  private processes;
6
6
  private completedIds;
7
- private listeners;
8
- private shouldExit;
9
- private exitCallback;
7
+ private listNav;
10
8
  private mode;
11
- private selectedIndex;
12
9
  private expandedId;
13
- private scrollOffset;
14
- private listScrollOffset;
15
10
  private errorFooterExpanded;
16
- private bufferVersion;
17
11
  private header;
18
12
  private showStatusBar;
19
13
  private isInteractive;
14
+ private listeners;
15
+ private shouldExit;
16
+ private exitCallback;
17
+ private bufferVersion;
20
18
  constructor(options?: SessionOptions);
21
19
  subscribe: (listener: Listener) => (() => void);
22
20
  getSnapshot: () => ChildProcess[];
@@ -28,42 +26,47 @@ export declare class ProcessStore {
28
26
  getDoneCount: () => number;
29
27
  getErrorCount: () => number;
30
28
  getErrorLineCount: () => number;
29
+ getErrorLines(): Array<{
30
+ processName: string;
31
+ lines: Line[];
32
+ }>;
33
+ addProcess(process: ChildProcess): void;
34
+ updateProcess(id: string, update: Partial<ChildProcess>): void;
35
+ appendLines(id: string, newLines: Line[]): void;
36
+ getProcess(id: string): ChildProcess | undefined;
37
+ getProcessLines(id: string): Line[];
38
+ getProcessLineCount(id: string): number;
31
39
  getMode: () => Mode;
32
40
  getSelectedIndex: () => number;
33
41
  getExpandedId: () => string | null;
34
- getScrollOffset: () => number;
35
42
  getListScrollOffset: () => number;
36
43
  getErrorFooterExpanded: () => boolean;
37
44
  getBufferVersion: () => number;
45
+ getScrollOffset: () => number;
38
46
  getHeader: () => string | undefined;
39
47
  getShowStatusBar: () => boolean;
40
48
  getIsInteractive: () => boolean;
41
49
  isAllComplete: () => boolean;
42
- addProcess(process: ChildProcess): void;
43
- updateProcess(id: string, update: Partial<ChildProcess>): void;
44
- appendLines(id: string, newLines: Line[]): void;
45
- getProcess(id: string): ChildProcess | undefined;
46
- getProcessLines(id: string): Line[];
47
- getProcessLineCount(id: string): number;
48
50
  setMode(mode: Mode): void;
49
- selectNext(visibleCount?: number): void;
50
- selectPrev(visibleCount?: number): void;
51
- private adjustListScroll;
52
51
  getSelectedProcess(): ChildProcess | undefined;
53
52
  toggleErrorFooter(): void;
54
53
  expandErrorFooter(): void;
55
- getErrorLines(): Array<{
56
- processName: string;
57
- lines: Line[];
58
- }>;
59
- toggleExpand(): void;
60
- collapse(): void;
54
+ selectNext(visibleCount?: number): void;
55
+ selectPrev(visibleCount?: number): void;
56
+ selectPageDown(pageSize: number, visibleCount?: number): void;
57
+ selectPageUp(pageSize: number, visibleCount?: number): void;
58
+ selectFirst(visibleCount?: number): void;
59
+ selectLast(visibleCount?: number): void;
60
+ clampListViewport(visibleCount: number): void;
61
+ private getExpandedNav;
61
62
  scrollDown(maxVisible: number): void;
62
63
  scrollUp(): void;
63
- scrollPageDown(maxVisible: number): void;
64
- scrollPageUp(maxVisible: number): void;
64
+ scrollPageDown(pageSize: number): void;
65
+ scrollPageUp(pageSize: number): void;
65
66
  scrollToTop(): void;
66
67
  scrollToBottom(maxVisible: number): void;
68
+ toggleExpand(visibleCountWhenExpanded?: number, visibleCountWhenCollapsed?: number): void;
69
+ collapse(visibleCountWhenCollapsed?: number): void;
67
70
  signalExit(callback: () => void): void;
68
71
  getShouldExit: () => boolean;
69
72
  getExitCallback: () => (() => void) | null;
@@ -26,6 +26,9 @@ export type ChildProcess = {
26
26
  title: string;
27
27
  state: State;
28
28
  lines: Line[];
29
+ /** @internal Virtual terminal for ANSI interpretation */
29
30
  terminalBuffer?: TerminalBuffer;
30
31
  expanded?: boolean;
32
+ /** @internal Per-process scroll navigation state */
33
+ scrollNav?: import('./state/Navigator.js').Navigator;
31
34
  };
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Navigator - a cursor over a bounded list with viewport tracking
3
+ *
4
+ * Used for both:
5
+ * - List navigation (process selection) - wraps around
6
+ * - Scroll navigation (content viewing) - clamps to bounds
7
+ */ "use strict";
8
+ Object.defineProperty(exports, "__esModule", {
9
+ value: true
10
+ });
11
+ Object.defineProperty(exports, "createNavigator", {
12
+ enumerable: true,
13
+ get: function() {
14
+ return createNavigator;
15
+ }
16
+ });
17
+ function createNavigator(options) {
18
+ var getLength = options.getLength, wrap = options.wrap, onMove = options.onMove;
19
+ var position = 0;
20
+ var viewportOffset = 0;
21
+ var clamp = function(value, min, max) {
22
+ return Math.max(min, Math.min(max, value));
23
+ };
24
+ var normalizePosition = function(pos) {
25
+ var length = getLength();
26
+ if (length === 0) return 0;
27
+ if (wrap) {
28
+ // Wrap around for list navigation
29
+ return (pos % length + length) % length;
30
+ }
31
+ // Clamp for scroll navigation
32
+ return clamp(pos, 0, Math.max(0, length - 1));
33
+ };
34
+ var ensureVisible = function(viewportSize) {
35
+ if (viewportSize <= 0) return;
36
+ if (position < viewportOffset) {
37
+ // Position is above viewport - scroll up
38
+ viewportOffset = position;
39
+ } else if (position >= viewportOffset + viewportSize) {
40
+ // Position is below viewport - scroll down
41
+ viewportOffset = position - viewportSize + 1;
42
+ }
43
+ };
44
+ return {
45
+ get position () {
46
+ return position;
47
+ },
48
+ get viewportOffset () {
49
+ return viewportOffset;
50
+ },
51
+ up: function up() {
52
+ var step = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 1;
53
+ var length = getLength();
54
+ if (length === 0) return;
55
+ position = normalizePosition(position - step);
56
+ onMove === null || onMove === void 0 ? void 0 : onMove();
57
+ },
58
+ down: function down() {
59
+ var step = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 1;
60
+ var length = getLength();
61
+ if (length === 0) return;
62
+ position = normalizePosition(position + step);
63
+ onMove === null || onMove === void 0 ? void 0 : onMove();
64
+ },
65
+ pageUp: function pageUp(pageSize, viewportSize) {
66
+ var length = getLength();
67
+ if (length === 0) return;
68
+ // For page navigation, don't wrap - stop at bounds
69
+ position = clamp(position - pageSize, 0, Math.max(0, length - 1));
70
+ if (viewportSize) {
71
+ ensureVisible(viewportSize);
72
+ }
73
+ onMove === null || onMove === void 0 ? void 0 : onMove();
74
+ },
75
+ pageDown: function pageDown(pageSize, viewportSize) {
76
+ var length = getLength();
77
+ if (length === 0) return;
78
+ // For page navigation, don't wrap - stop at bounds
79
+ position = clamp(position + pageSize, 0, Math.max(0, length - 1));
80
+ if (viewportSize) {
81
+ ensureVisible(viewportSize);
82
+ }
83
+ onMove === null || onMove === void 0 ? void 0 : onMove();
84
+ },
85
+ toStart: function toStart() {
86
+ position = 0;
87
+ viewportOffset = 0;
88
+ onMove === null || onMove === void 0 ? void 0 : onMove();
89
+ },
90
+ toEnd: function toEnd() {
91
+ var length = getLength();
92
+ if (length === 0) return;
93
+ position = length - 1;
94
+ onMove === null || onMove === void 0 ? void 0 : onMove();
95
+ },
96
+ ensureVisible: ensureVisible,
97
+ clampViewport: function clampViewport(viewportSize) {
98
+ var length = getLength();
99
+ var oldOffset = viewportOffset;
100
+ if (length === 0 || viewportSize <= 0) {
101
+ viewportOffset = 0;
102
+ return viewportOffset !== oldOffset;
103
+ }
104
+ // If all items fit in viewport, start from 0
105
+ if (length <= viewportSize) {
106
+ viewportOffset = 0;
107
+ return viewportOffset !== oldOffset;
108
+ }
109
+ // Ensure no empty space at bottom: viewportOffset + viewportSize <= length
110
+ var maxOffset = length - viewportSize;
111
+ if (viewportOffset > maxOffset) {
112
+ viewportOffset = maxOffset;
113
+ }
114
+ // Also ensure position is still visible in the (potentially moved) viewport
115
+ ensureVisible(viewportSize);
116
+ return viewportOffset !== oldOffset;
117
+ },
118
+ setPosition: function setPosition(newPosition) {
119
+ position = normalizePosition(newPosition);
120
+ },
121
+ reset: function reset() {
122
+ position = 0;
123
+ viewportOffset = 0;
124
+ }
125
+ };
126
+ }
127
+ /* 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/state/Navigator.ts"],"sourcesContent":["/**\n * Navigator - a cursor over a bounded list with viewport tracking\n *\n * Used for both:\n * - List navigation (process selection) - wraps around\n * - Scroll navigation (content viewing) - clamps to bounds\n */\n\nexport type NavigatorOptions = {\n getLength: () => number;\n wrap: boolean;\n onMove?: () => void;\n};\n\nexport type Navigator = {\n readonly position: number;\n readonly viewportOffset: number;\n\n up(step?: number): void;\n down(step?: number): void;\n pageUp(pageSize: number, viewportSize?: number): void;\n pageDown(pageSize: number, viewportSize?: number): void;\n toStart(): void;\n toEnd(): void;\n ensureVisible(viewportSize: number): void;\n clampViewport(viewportSize: number): boolean; // Returns true if viewport changed\n setPosition(position: number): void;\n reset(): void;\n};\n\nexport function createNavigator(options: NavigatorOptions): Navigator {\n const { getLength, wrap, onMove } = options;\n\n let position = 0;\n let viewportOffset = 0;\n\n const clamp = (value: number, min: number, max: number): number => {\n return Math.max(min, Math.min(max, value));\n };\n\n const normalizePosition = (pos: number): number => {\n const length = getLength();\n if (length === 0) return 0;\n\n if (wrap) {\n // Wrap around for list navigation\n return ((pos % length) + length) % length;\n }\n // Clamp for scroll navigation\n return clamp(pos, 0, Math.max(0, length - 1));\n };\n\n const ensureVisible = (viewportSize: number): void => {\n if (viewportSize <= 0) return;\n\n if (position < viewportOffset) {\n // Position is above viewport - scroll up\n viewportOffset = position;\n } else if (position >= viewportOffset + viewportSize) {\n // Position is below viewport - scroll down\n viewportOffset = position - viewportSize + 1;\n }\n };\n\n return {\n get position() {\n return position;\n },\n\n get viewportOffset() {\n return viewportOffset;\n },\n\n up(step = 1): void {\n const length = getLength();\n if (length === 0) return;\n\n position = normalizePosition(position - step);\n onMove?.();\n },\n\n down(step = 1): void {\n const length = getLength();\n if (length === 0) return;\n\n position = normalizePosition(position + step);\n onMove?.();\n },\n\n pageUp(pageSize: number, viewportSize?: number): void {\n const length = getLength();\n if (length === 0) return;\n\n // For page navigation, don't wrap - stop at bounds\n position = clamp(position - pageSize, 0, Math.max(0, length - 1));\n if (viewportSize) {\n ensureVisible(viewportSize);\n }\n onMove?.();\n },\n\n pageDown(pageSize: number, viewportSize?: number): void {\n const length = getLength();\n if (length === 0) return;\n\n // For page navigation, don't wrap - stop at bounds\n position = clamp(position + pageSize, 0, Math.max(0, length - 1));\n if (viewportSize) {\n ensureVisible(viewportSize);\n }\n onMove?.();\n },\n\n toStart(): void {\n position = 0;\n viewportOffset = 0;\n onMove?.();\n },\n\n toEnd(): void {\n const length = getLength();\n if (length === 0) return;\n\n position = length - 1;\n onMove?.();\n },\n\n ensureVisible,\n\n clampViewport(viewportSize: number): boolean {\n const length = getLength();\n const oldOffset = viewportOffset;\n\n if (length === 0 || viewportSize <= 0) {\n viewportOffset = 0;\n return viewportOffset !== oldOffset;\n }\n\n // If all items fit in viewport, start from 0\n if (length <= viewportSize) {\n viewportOffset = 0;\n return viewportOffset !== oldOffset;\n }\n\n // Ensure no empty space at bottom: viewportOffset + viewportSize <= length\n const maxOffset = length - viewportSize;\n if (viewportOffset > maxOffset) {\n viewportOffset = maxOffset;\n }\n\n // Also ensure position is still visible in the (potentially moved) viewport\n ensureVisible(viewportSize);\n\n return viewportOffset !== oldOffset;\n },\n\n setPosition(newPosition: number): void {\n position = normalizePosition(newPosition);\n },\n\n reset(): void {\n position = 0;\n viewportOffset = 0;\n },\n };\n}\n"],"names":["createNavigator","options","getLength","wrap","onMove","position","viewportOffset","clamp","value","min","max","Math","normalizePosition","pos","length","ensureVisible","viewportSize","up","step","down","pageUp","pageSize","pageDown","toStart","toEnd","clampViewport","oldOffset","maxOffset","setPosition","newPosition","reset"],"mappings":"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"}