@tdk-landscape/tdk-cli-core 1.3.115 → 1.3.117

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.
@@ -8,6 +8,7 @@ import { findProjectRoot } from "../utils/paths.js";
8
8
  import { describeSearch } from "../utils/search-status.js";
9
9
  import { clearMetadataCache, discoverResources, discoverStacks, getResourceMetadata, getStackMetadata, } from "../utils/services.js";
10
10
  import { createStatusMessageController } from "../utils/status-message.js";
11
+ import { getListRowFromMouseY, getTerminalRuleWidth } from "../utils/terminal-layout.js";
11
12
  import { isTiltAvailable } from "../utils/tilt.js";
12
13
  // biome-ignore lint/correctness/noUnusedFunctionParameters: reserved callback prop kept in the component API
13
14
  const HelpPanel = ({ onClose }) => (_jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 2, paddingY: 1, flexDirection: "column", width: 60, children: [_jsx(Text, { bold: true, color: "cyan", children: "Keyboard Shortcuts" }), _jsxs(Box, { marginY: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, underline: true, children: "Navigation" }), _jsx(Text, { children: " \u2191/\u2193 Navigate list items" }), _jsx(Text, { children: " Enter Select item / Open detail" }), _jsx(Text, { children: " Space Toggle expand (tree view)" }), _jsx(Text, { children: " Tab Next tab" }), _jsx(Text, { children: " 1-5 Direct tab access" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, underline: true, children: "Actions" }) }), _jsx(Text, { children: " m Toggle mouse support" }), _jsx(Text, { children: " t Toggle tooltips" }), _jsx(Text, { children: " e Toggle enabled/disabled services" }), _jsx(Text, { children: " r Refresh data" }), _jsx(Text, { children: " / Search/filter" }), _jsx(Text, { children: " ? Show this help" }), _jsx(Text, { children: " q Quit \u2502 Esc Back" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Press any key to close..." }) })] }));
@@ -40,6 +41,7 @@ const TUIApp = ({ animated = true }) => {
40
41
  const [terminalWidth, setTerminalWidth] = useState(stdout.columns || 120);
41
42
  const [selectedFile, setSelectedFile] = useState(null);
42
43
  const [mouseEnabled, setMouseEnabled] = useState(true);
44
+ const [listTop, setListTop] = useState(null);
43
45
  const [loading, setLoading] = useState(true);
44
46
  const [error, setError] = useState(null);
45
47
  const [showTooltips, setShowTooltips] = useState(true);
@@ -160,6 +162,13 @@ const TUIApp = ({ animated = true }) => {
160
162
  return [];
161
163
  }, [activeTab, filteredStacks, filteredServices, selectedStackData, selectedServiceData]);
162
164
  const items = getItems();
165
+ const selectableListVisible = !loading &&
166
+ !error &&
167
+ !showHelp &&
168
+ (activeTab === "overview" ||
169
+ (activeTab === "resources" && !selectedStackData) ||
170
+ (activeTab === "files" && !selectedServiceData) ||
171
+ (activeTab === "config" && !selectedServiceData));
163
172
  // Only the stack and service lists are filtered by the query; drilled-in views are not.
164
173
  const searchList = activeTab === "overview" || (activeTab === "resources" && !selectedStackData)
165
174
  ? { total: stacks.length }
@@ -234,8 +243,7 @@ const TUIApp = ({ animated = true }) => {
234
243
  // Check if it's a left click (btn & 0b11 == 0 means left button)
235
244
  const isLeftClick = (btn & 0b11) === 0;
236
245
  if (isLeftClick && !release) {
237
- // Calculate row in the list (header takes ~6 lines)
238
- const listRow = y - 7; // Adjust for header, tabs, and borders
246
+ const listRow = selectableListVisible && listTop !== null ? getListRowFromMouseY(y, listTop) : -1;
239
247
  if (listRow >= 0 && listRow < items.length) {
240
248
  setHighlightedIndex(listRow);
241
249
  const item = items[listRow];
@@ -252,7 +260,7 @@ const TUIApp = ({ animated = true }) => {
252
260
  stdin.off("data", handleMouseData);
253
261
  stdin.removeAllListeners("data");
254
262
  };
255
- }, [stdin, items, mouseEnabled, handleSelect]);
263
+ }, [stdin, items, listTop, selectableListVisible, mouseEnabled, handleSelect]);
256
264
  const handleResize = useCallback(() => {
257
265
  setTerminalWidth(stdout.columns || 120);
258
266
  }, [stdout.columns]);
@@ -418,8 +426,7 @@ const TUIApp = ({ animated = true }) => {
418
426
  }));
419
427
  }, [selectedServiceData, services]);
420
428
  const showSidebar = terminalWidth > 100;
421
- const compactTabBar = terminalWidth < 100;
422
- const compact = terminalWidth < 80;
429
+ const mainPanelWidth = showSidebar ? terminalWidth - 45 : terminalWidth - 4;
423
430
  if (loading) {
424
431
  return _jsx(LoadingScreen, { message: "Discovering resources...", animated: animated });
425
432
  }
@@ -429,7 +436,7 @@ const TUIApp = ({ animated = true }) => {
429
436
  if (services.length === 0) {
430
437
  return _jsx(EmptyState, { message: message });
431
438
  }
432
- return (_jsxs(Box, { flexDirection: "column", height: stdout.rows || 24, children: [_jsx(Box, { paddingX: 1, paddingY: 0, children: _jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: "\u2593\u2592\u2591 TDK NEON EDITION \u2591\u2592\u2593" }), _jsx(Text, { color: "gray", children: " \u2502 " }), _jsx(Text, { color: "white", children: projectRoot }), _jsx(Text, { color: "gray", children: " \u2502 " }), _jsxs(Text, { color: "green", children: [services.length, " services ready"] })] }) }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "gray", children: "─".repeat(compact ? 60 : Math.min(terminalWidth - 4, 100)) }) }), isSearching && (_jsxs(Box, { paddingX: 1, height: 1, children: [_jsxs(Text, { color: "yellow", children: ["Search: ", searchQuery, "_"] }), searchStatus && _jsx(Text, { color: "gray", dimColor: true, children: ` ${searchStatus.summary}` })] })), searchStatus?.emptyMessage && (_jsx(Box, { paddingX: 1, height: 1, children: _jsx(Text, { color: "gray", children: searchStatus.emptyMessage }) })), !isSearching && message && (_jsx(Box, { paddingX: 1, height: 1, children: _jsxs(Text, { color: "cyan", children: ["\u2593\u2592\u2591 ", message, " \u2591\u2592\u2593"] }) })), !isSearching && !message && showTooltips && !showHelp && (_jsx(Box, { paddingX: 1, height: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: activeTab === "overview" && selectedStack
439
+ return (_jsxs(Box, { flexDirection: "column", height: stdout.rows || 24, children: [_jsx(Box, { paddingX: 1, paddingY: 0, children: _jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: "\u2593\u2592\u2591 TDK NEON EDITION \u2591\u2592\u2593" }), _jsx(Text, { color: "gray", children: " \u2502 " }), _jsx(Text, { color: "white", children: projectRoot }), _jsx(Text, { color: "gray", children: " \u2502 " }), _jsxs(Text, { color: "green", children: [services.length, " services ready"] })] }) }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "gray", children: "─".repeat(getTerminalRuleWidth(terminalWidth)) }) }), isSearching && (_jsxs(Box, { paddingX: 1, height: 1, children: [_jsxs(Text, { color: "yellow", children: ["Search: ", searchQuery, "_"] }), searchStatus && _jsx(Text, { color: "gray", dimColor: true, children: ` ${searchStatus.summary}` })] })), searchStatus?.emptyMessage && (_jsx(Box, { paddingX: 1, height: 1, children: _jsx(Text, { color: "gray", children: searchStatus.emptyMessage }) })), !isSearching && message && (_jsx(Box, { paddingX: 1, height: 1, children: _jsxs(Text, { color: "cyan", children: ["\u2593\u2592\u2591 ", message, " \u2591\u2592\u2593"] }) })), !isSearching && !message && showTooltips && !showHelp && (_jsx(Box, { paddingX: 1, height: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: activeTab === "overview" && selectedStack
433
440
  ? `Stack "${selectedStack}" selected. [Enter] view │ [Esc] back │ [e] ${showEnabledOnly ? "show all" : "enabled only"} │ [?] help`
434
441
  : activeTab === "overview" && !selectedStack
435
442
  ? `[↑/↓] Navigate │ [Enter] Select │ [e] ${showEnabledOnly ? "show all" : "enabled only"} │ [?] help`
@@ -443,7 +450,7 @@ const TUIApp = ({ animated = true }) => {
443
450
  ? `Select service to view files │ [e] ${showEnabledOnly ? "show all" : "enabled only"} │ [?] help`
444
451
  : activeTab === "config"
445
452
  ? `View configurations │ [e] ${showEnabledOnly ? "show all" : "enabled only"} │ [?] help`
446
- : `[Tab] Next │ [1-5] Tabs │ [e] ${showEnabledOnly ? "show all" : "enabled only"} │ [?] help │ [q] Quit` }) })), showHelp && (_jsx(Box, { paddingX: 1, flexGrow: 1, children: _jsx(HelpPanel, { onClose: () => setShowHelp(false) }) })), !showHelp && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(TabBar, { activeTab: activeTab, onTabChange: setActiveTab, compact: compactTabBar }) }), _jsxs(Box, { flexDirection: "row", paddingX: 1, flexGrow: 1, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, width: showSidebar ? terminalWidth - 45 : terminalWidth - 4, children: [activeTab === "overview" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Stacks \u2500" }) }), _jsx(Box, { marginTop: 1, flexGrow: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex }) })] })), activeTab === "resources" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Resources \u2500" }) }), selectedStackData ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "gray", children: ["Stack: ", selectedStackData.stack.name] }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceTable, { resources: selectedStackData.metadata.resources, maxWidth: terminalWidth - (showSidebar ? 50 : 10) }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: "Select a stack to view resources" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex }) })] }))] })), activeTab === "events" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Events \u2500" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Events tab not yet implemented" }) })] })), activeTab === "files" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Autogenerated Files \u2500" }) }), selectedServiceData ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "gray", children: ["Service: ", selectedServiceData.service.name] }), _jsx(Box, { marginTop: 1, children: _jsx(FileTree, { nodes: fileTreeNodes, selectedPath: selectedFile || undefined }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: "Select a service to view files" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex }) })] }))] })), activeTab === "config" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Configuration \u2500" }) }), selectedServiceData ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: selectedServiceData.service.configPath }), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: _jsx(Text, { color: "gray", wrap: "wrap", children: JSON.stringify(selectedServiceData.service.config, null, 2).slice(0, 1000) }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: "Select a service to view configuration" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex }) })] }))] }))] }), showSidebar && (_jsx(Box, { marginLeft: 2, children: _jsx(DetailPanel, { stack: selectedStackData?.stack || null, service: selectedServiceData?.service || null, stackMetadata: selectedStackData?.metadata || null, visible: !!selectedStack || !!selectedService }) }))] }), _jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, height: 3, flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: "cyan", bold: true, children: ["\u2593\u2592\u2591 ", activeTab] }), _jsxs(Text, { color: "green", children: ["\u25CF ", services.filter((s) => s.stack).length, " in stack"] }), _jsxs(Text, { color: "yellow", children: ["\u25CB ", services.filter((s) => !s.stack).length, " no stack"] })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: "gray", children: ["Stacks: ", stacks.length] }), _jsxs(Text, { color: "gray", children: ["Services: ", services.length] }), _jsxs(Text, { color: "gray", children: ["\uD83D\uDDB1\uFE0F ", mouseEnabled ? "ON" : "OFF", " \u2502 \u2139\uFE0F ", showTooltips ? "ON" : "OFF", " \u2502", _jsx(Text, { color: showEnabledOnly ? "green" : "yellow", children: showEnabledOnly ? "✓ enabled" : "✓ all" }), " │ ", "[?] Help \u2502 [q] Quit"] })] })] })] }))] }));
453
+ : `[Tab] Next │ [1-5] Tabs │ [e] ${showEnabledOnly ? "show all" : "enabled only"} │ [?] help │ [q] Quit` }) })), showHelp && (_jsx(Box, { paddingX: 1, flexGrow: 1, children: _jsx(HelpPanel, { onClose: () => setShowHelp(false) }) })), !showHelp && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(TabBar, { activeTab: activeTab, onTabChange: setActiveTab, terminalWidth: terminalWidth }) }), _jsxs(Box, { flexDirection: "row", paddingX: 1, flexGrow: 1, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, width: mainPanelWidth, children: [activeTab === "overview" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Stacks \u2500" }) }), _jsx(Box, { marginTop: 1, flexGrow: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, width: mainPanelWidth, onLayout: setListTop }) })] })), activeTab === "resources" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Resources \u2500" }) }), selectedStackData ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "gray", children: ["Stack: ", selectedStackData.stack.name] }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceTable, { resources: selectedStackData.metadata.resources, maxWidth: terminalWidth - (showSidebar ? 50 : 10) }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: "Select a stack to view resources" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, width: mainPanelWidth, onLayout: setListTop }) })] }))] })), activeTab === "events" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Events \u2500" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Events tab not yet implemented" }) })] })), activeTab === "files" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Autogenerated Files \u2500" }) }), selectedServiceData ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "gray", children: ["Service: ", selectedServiceData.service.name] }), _jsx(Box, { marginTop: 1, children: _jsx(FileTree, { nodes: fileTreeNodes, selectedPath: selectedFile || undefined }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: "Select a service to view files" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, width: mainPanelWidth, onLayout: setListTop }) })] }))] })), activeTab === "config" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Configuration \u2500" }) }), selectedServiceData ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: selectedServiceData.service.configPath }), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: _jsx(Text, { color: "gray", wrap: "wrap", children: JSON.stringify(selectedServiceData.service.config, null, 2).slice(0, 1000) }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: "Select a service to view configuration" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, width: mainPanelWidth, onLayout: setListTop }) })] }))] }))] }), showSidebar && (_jsx(Box, { marginLeft: 2, children: _jsx(DetailPanel, { stack: selectedStackData?.stack || null, service: selectedServiceData?.service || null, stackMetadata: selectedStackData?.metadata || null, visible: !!selectedStack || !!selectedService }) }))] }), _jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, height: 3, flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: "cyan", bold: true, children: ["\u2593\u2592\u2591 ", activeTab] }), _jsxs(Text, { color: "green", children: ["\u25CF ", services.filter((s) => s.stack).length, " in stack"] }), _jsxs(Text, { color: "yellow", children: ["\u25CB ", services.filter((s) => !s.stack).length, " no stack"] })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: "gray", children: ["Stacks: ", stacks.length] }), _jsxs(Text, { color: "gray", children: ["Services: ", services.length] }), _jsxs(Text, { color: "gray", children: ["\uD83D\uDDB1\uFE0F ", mouseEnabled ? "ON" : "OFF", " \u2502 \u2139\uFE0F ", showTooltips ? "ON" : "OFF", " \u2502", _jsx(Text, { color: showEnabledOnly ? "green" : "yellow", children: showEnabledOnly ? "✓ enabled" : "✓ all" }), " │ ", "[?] Help \u2502 [q] Quit"] })] })] })] }))] }));
447
454
  };
448
455
  export const uiCommand = new Command("ui")
449
456
  .description("Interactive TUI for managing stacks and services (Neon Edition)")
@@ -457,6 +464,7 @@ export const uiCommand = new Command("ui")
457
464
  errorFactories.tiltNotInstalled().exit();
458
465
  }
459
466
  requireProjectRoot();
460
- render(_jsx(TUIApp, { animated: options.animations }));
467
+ // The app has no Static content, so its live layout starts at row 1 in the alternate screen.
468
+ render(_jsx(TUIApp, { animated: options.animations }), { alternateScreen: true });
461
469
  });
462
470
  //# sourceMappingURL=ui.js.map
@@ -1,10 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text, useInput } from "ink";
3
- import { useEffect, useState } from "react";
2
+ import { Box, measureElement, Text, useInput } from "ink";
3
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
4
4
  // Keys match what ink-select-input handled: up/down or k/j (wrapping), 1-9 to pick, Enter to select.
5
- export const ResourceSelectInput = ({ items, onSelect, highlightedIndex, }) => {
5
+ export const ResourceSelectInput = ({ items, onSelect, highlightedIndex, width, onLayout, }) => {
6
+ const listRef = useRef(null);
6
7
  const [selected, setSelected] = useState(() => Math.max(0, Math.min(highlightedIndex, items.length - 1)));
7
8
  const itemKeys = items.map((item) => item.value).join("\0");
9
+ useLayoutEffect(() => {
10
+ if (listRef.current)
11
+ onLayout?.(measureElement(listRef.current).y);
12
+ });
8
13
  // biome-ignore lint/correctness/useExhaustiveDependencies: reset only when the item list changes
9
14
  useEffect(() => setSelected(0), [itemKeys]);
10
15
  useInput((input, key) => {
@@ -22,9 +27,9 @@ export const ResourceSelectInput = ({ items, onSelect, highlightedIndex, }) => {
22
27
  if (key.return)
23
28
  onSelect(items[selected]);
24
29
  });
25
- return (_jsx(Box, { flexDirection: "column", children: items.map((item, index) => {
30
+ return (_jsx(Box, { ref: listRef, flexDirection: "column", width: width, children: items.map((item, index) => {
26
31
  const isSelected = index === selected;
27
- return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? "cyan" : undefined, children: isSelected ? "▓▒░ " : " " }), _jsx(Text, { color: isSelected ? "cyan" : "white", bold: isSelected, backgroundColor: isSelected ? "black" : undefined, children: item.label })] }, item.value));
32
+ return (_jsx(Box, { width: width, children: _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: isSelected ? "cyan" : undefined, children: isSelected ? "\u2593\u2592\u2591 " : " " }), _jsx(Text, { color: isSelected ? "cyan" : "white", bold: isSelected, backgroundColor: isSelected ? "black" : undefined, children: item.label })] }) }, item.value));
28
33
  }) }));
29
34
  };
30
35
  //# sourceMappingURL=ResourceSelectInput.js.map
@@ -6,13 +6,22 @@ export const ResourceTable = ({ resources, maxWidth = 100 }) => {
6
6
  return (_jsx(Box, { paddingY: 1, children: _jsx(Text, { color: "gray", children: "No resources found" }) }));
7
7
  }
8
8
  const narrowMode = maxWidth < 80;
9
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Box, { width: narrowMode ? 20 : 25, children: _jsx(Text, { bold: true, children: "Logical ID" }) }), !narrowMode && (_jsx(Box, { width: 20, children: _jsx(Text, { bold: true, children: "Physical ID" }) })), _jsx(Box, { width: 12, children: _jsx(Text, { bold: true, children: "Type" }) }), _jsx(Box, { width: 15, children: _jsx(Text, { bold: true, children: "Status" }) }), !narrowMode && (_jsx(Box, { width: 20, children: _jsx(Text, { bold: true, children: "Created" }) }))] }), resources.map((resource, index) => {
9
+ const tableContentWidth = Math.max(1, Math.floor(maxWidth) - 4);
10
+ const extraWidth = tableContentWidth - (narrowMode ? 19 : 26);
11
+ const nameWidth = 4 + Math.floor(extraWidth * (narrowMode ? 0.37 : 0.29));
12
+ const stackWidth = 5 + Math.floor(extraWidth * (narrowMode ? 0.22 : 0.2));
13
+ const typeWidth = 4 + Math.floor(extraWidth * (narrowMode ? 0.2 : 0.13));
14
+ const statusWidth = narrowMode
15
+ ? tableContentWidth - nameWidth - stackWidth - typeWidth
16
+ : 6 + Math.floor(extraWidth * 0.17);
17
+ const createdWidth = narrowMode
18
+ ? 0
19
+ : tableContentWidth - nameWidth - stackWidth - typeWidth - statusWidth;
20
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Box, { width: nameWidth, children: _jsx(Text, { bold: true, children: "Name" }) }), _jsx(Box, { width: stackWidth, children: _jsx(Text, { bold: true, children: "Stack" }) }), _jsx(Box, { width: typeWidth, children: _jsx(Text, { bold: true, children: "Type" }) }), _jsx(Box, { width: statusWidth, children: _jsx(Text, { bold: true, children: "Status" }) }), !narrowMode && (_jsx(Box, { width: createdWidth, children: _jsx(Text, { bold: true, children: "Created" }) }))] }), resources.map((resource) => {
10
21
  const statusColor = getStatusColor(resource.status);
11
- const statusIcon = getStatusIcon(resource.status);
12
- const _isEven = index % 2 === 0;
13
- return (_jsxs(Box, { flexDirection: "row", paddingX: 1, children: [_jsx(Box, { width: narrowMode ? 20 : 25, children: _jsx(Text, { children: truncate(resource.name, narrowMode ? 18 : 23) }) }), !narrowMode && (_jsx(Box, { width: 20, children: _jsx(Text, { color: "gray", children: resource.stack && resource.stack !== "unknown"
14
- ? truncate(`${resource.stack}/${resource.name}`, 18)
15
- : truncate(resource.name, 18) }) })), _jsx(Box, { width: 12, children: _jsx(Text, { color: "cyan", children: resource.type }) }), _jsx(Box, { width: 15, children: _jsxs(Text, { color: statusColor, children: [statusIcon, " ", resource.status] }) }), !narrowMode && (_jsx(Box, { width: 20, children: _jsx(Text, { color: "gray", children: formatShortDate(resource.createdAt) }) }))] }, resource.name));
22
+ const statusLabel = `${getStatusIcon(resource.status)} ${resource.status}`;
23
+ const stackName = resource.stack && resource.stack !== "unknown" ? resource.stack : "-";
24
+ return (_jsxs(Box, { flexDirection: "row", paddingX: 1, children: [_jsx(Box, { width: nameWidth, children: _jsx(Text, { children: truncate(resource.name, Math.max(3, nameWidth - 1)) }) }), _jsx(Box, { width: stackWidth, children: _jsx(Text, { color: "gray", children: truncate(stackName, Math.max(3, stackWidth - 1)) }) }), _jsx(Box, { width: typeWidth, children: _jsx(Text, { color: "cyan", children: truncate(resource.type, Math.max(3, typeWidth - 1)) }) }), _jsx(Box, { width: statusWidth, children: _jsx(Text, { color: statusColor, children: truncate(statusLabel, Math.max(3, statusWidth - 1)) }) }), !narrowMode && (_jsx(Box, { width: createdWidth, children: _jsx(Text, { color: "gray", children: truncate(formatShortDate(resource.createdAt), Math.max(3, createdWidth - 1)) }) }))] }, resource.name));
16
25
  })] }));
17
26
  };
18
27
  //# sourceMappingURL=ResourceTable.js.map
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
+ import { getTabBarDensity, getTerminalRuleWidth } from "../utils/terminal-layout.js";
3
4
  const TABS = [
4
5
  { id: "overview", label: "OVERVIEW", shortcut: "1" },
5
6
  { id: "resources", label: "RESOURCES", shortcut: "2" },
@@ -7,11 +8,18 @@ const TABS = [
7
8
  { id: "files", label: "FILES", shortcut: "4" },
8
9
  { id: "config", label: "CONFIG", shortcut: "5" },
9
10
  ];
10
- // biome-ignore lint/correctness/noUnusedFunctionParameters: reserved callback prop kept in the component API
11
- export const TabBar = ({ activeTab, onTabChange, compact = false }) => {
12
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "gray", children: "─".repeat(compact ? 60 : 80) }) }), _jsx(Box, { flexDirection: "row", justifyContent: "space-between", paddingX: 1, children: TABS.map((tab) => {
11
+ export const TabBar = ({ activeTab, compact = false, terminalWidth }) => {
12
+ const responsive = terminalWidth !== undefined;
13
+ const density = terminalWidth === undefined ? "wide" : getTabBarDensity(terminalWidth);
14
+ const compactLayout = responsive && density === "compact";
15
+ const wide = !responsive || density === "wide";
16
+ const ruleWidth = responsive ? getTerminalRuleWidth(terminalWidth) : compact ? 60 : 80;
17
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "gray", children: "\u2500".repeat(ruleWidth) }) }), _jsx(Box, { flexDirection: "row", justifyContent: "space-between", paddingX: 1, children: TABS.map((tab) => {
13
18
  const isActive = activeTab === tab.id;
14
- return (_jsx(Box, { children: isActive ? (_jsx(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, backgroundColor: "black", children: _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "\u2593\u2592\u2591" }), _jsxs(Text, { color: "cyan", bold: true, children: [" ", "[", tab.shortcut, "] ", tab.label, " "] }), _jsx(Text, { color: "cyan", children: "\u2591\u2592\u2593" })] }) })) : (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: "gray", dimColor: true, children: ["[", tab.shortcut, "] ", compact ? tab.label.slice(0, 4) : tab.label] }) })) }, tab.id));
15
- }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "─".repeat(compact ? 60 : 80) }) })] }));
19
+ const label = compactLayout || (!responsive && compact && !isActive)
20
+ ? tab.label.slice(0, 4)
21
+ : tab.label;
22
+ return (_jsx(Box, { children: compactLayout ? (_jsxs(Text, { color: isActive ? "cyan" : "gray", bold: isActive, children: ["[", tab.shortcut, "] ", label] })) : isActive ? (_jsx(Box, { borderStyle: "single", borderColor: "cyan", paddingX: wide ? 1 : 0, backgroundColor: "black", children: _jsxs(Text, { children: [wide && _jsx(Text, { color: "cyan", children: "\u2593\u2592\u2591" }), _jsxs(Text, { color: "cyan", bold: true, children: [" ", "[", tab.shortcut, "] ", label, " "] }), wide && _jsx(Text, { color: "cyan", children: "\u2591\u2592\u2593" })] }) })) : (_jsx(Box, { paddingX: wide ? 1 : 0, children: _jsxs(Text, { color: "gray", dimColor: true, children: ["[", tab.shortcut, "] ", label] }) })) }, tab.id));
23
+ }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "\u2500".repeat(ruleWidth) }) })] }));
16
24
  };
17
25
  //# sourceMappingURL=TabBar.js.map
@@ -0,0 +1,20 @@
1
+ const COMPACT_TAB_WIDTH = 76;
2
+ const WIDE_TAB_WIDTH = 100;
3
+ export function getTerminalRuleWidth(terminalWidth) {
4
+ return Math.max(0, Math.min(terminalWidth - 4, 100));
5
+ }
6
+ export function getTabBarDensity(terminalWidth) {
7
+ if (terminalWidth < COMPACT_TAB_WIDTH)
8
+ return "compact";
9
+ if (terminalWidth < WIDE_TAB_WIDTH)
10
+ return "standard";
11
+ return "wide";
12
+ }
13
+ /**
14
+ * Convert an SGR mouse row (1-based) to an Ink layout row (0-based).
15
+ * The UI uses Ink's alternate screen with no Static content, so the live region starts at row 1.
16
+ */
17
+ export function getListRowFromMouseY(mouseY, listTop) {
18
+ return mouseY - 1 - listTop;
19
+ }
20
+ //# sourceMappingURL=terminal-layout.js.map
package/cli/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tdk-cli-internal",
3
3
  "private": true,
4
- "version": "1.3.115",
4
+ "version": "1.3.117",
5
5
  "description": "TDK CLI — start services on your laptop.",
6
6
  "keywords": [
7
7
  "tdk",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tdk-landscape/tdk-cli-core",
3
- "version": "1.3.115",
3
+ "version": "1.3.117",
4
4
  "description": "TDK CLI — start services on your laptop.",
5
5
  "homepage": "https://tdk-landscape.github.io/tdk-website",
6
6
  "repository": {