@ohzw/worktree-command-tui 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.js CHANGED
@@ -3,7 +3,7 @@ import { Alert, Spinner } from '@inkjs/ui';
3
3
  import { memo, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { Box, Text, useApp, useInput, useStdin, useStdout } from 'ink';
5
5
  import { ActionPanel } from './components/ActionPanel.js';
6
- import { ContextBar } from './components/ContextBar.js';
6
+ import { ShortcutBar } from './components/ShortcutBar.js';
7
7
  import { Header } from './components/Header.js';
8
8
  import { HelpWindow } from './components/HelpWindow.js';
9
9
  import { FloatingLogWindow } from './components/FloatingLogWindow.js';
@@ -50,14 +50,14 @@ export function shouldUseCompactLayout(_columns, _rows, _worktreeCount = 0) {
50
50
  export function shouldUseMinimalLayout(columns, rows) {
51
51
  return columns < 20 || rows < 10;
52
52
  }
53
- const STACKED_LAYOUT_FRAME_ROWS = 9;
54
- const HEADER_HEIGHT = 5;
55
- const CONTEXT_BAR_HEIGHT = 4;
53
+ const STACKED_LAYOUT_FRAME_ROWS = 5;
54
+ const HEADER_HEIGHT = 4;
55
+ const SHORTCUT_BAR_HEIGHT = 1;
56
56
  const PANE_GAP_WIDTH = 1;
57
57
  const MIN_STACKED_PANE_HEIGHT = 9;
58
58
  export const RESIZE_DEBOUNCE_MS = 100;
59
59
  export function shouldStackPanes(columns, rows, _worktreeCount = 0) {
60
- // Header + context bar consume the fixed chrome; each stacked pane still keeps ~6 visible content lines at the minimum height.
60
+ // Header + shortcut bar consume the fixed chrome; each stacked pane still keeps ~6 visible content lines at the minimum height.
61
61
  return columns < 96 && rows >= STACKED_LAYOUT_FRAME_ROWS + (MIN_STACKED_PANE_HEIGHT * 2);
62
62
  }
63
63
  function getLogPaneHeight(_rootHeight) {
@@ -579,8 +579,8 @@ function AppShellContent({ initialModel, actions, windowSize, }) {
579
579
  const stackedWorktreePaneBottom = stackedLayout && stackedWorktreePaneTop !== undefined && paneHeight !== undefined ? stackedWorktreePaneTop + paneHeight - 1 : undefined;
580
580
  const stackedSelectionPaneTop = stackedLayout && stackedWorktreePaneBottom !== undefined ? stackedWorktreePaneBottom + 1 : undefined;
581
581
  const stackedSelectionPaneBottom = stackedLayout && stackedSelectionPaneTop !== undefined && paneHeight !== undefined ? stackedSelectionPaneTop + paneHeight - 1 : undefined;
582
- const logPaneTop = showLogPanel ? rootHeight - CONTEXT_BAR_HEIGHT - logPaneHeight + 1 : undefined;
583
- const logPaneBottom = showLogPanel ? rootHeight - CONTEXT_BAR_HEIGHT : undefined;
582
+ const logPaneTop = showLogPanel ? rootHeight - SHORTCUT_BAR_HEIGHT - logPaneHeight + 1 : undefined;
583
+ const logPaneBottom = showLogPanel ? rootHeight - SHORTCUT_BAR_HEIGHT : undefined;
584
584
  useEffect(() => {
585
585
  const onData = (data) => {
586
586
  if (isHelpOverlayOpen) {
@@ -712,6 +712,6 @@ function AppShellContent({ initialModel, actions, windowSize, }) {
712
712
  ? 'Keys: d/y confirm | Esc/n/q cancel'
713
713
  : `Keys: ↑↓/jk g/G / Filter ↵${model.setupAvailable ? ' i' : ''}${model.editorAvailable ? ' e' : ''} o d L s r q · Resize terminal for split view` })] }));
714
714
  }
715
- return (_jsxs(Box, { width: rootWidth, height: rootHeight, flexDirection: "column", children: [_jsx(Header, { repoName: model.repoName, namespace: model.namespace, activeBranch: model.activeBranch }), _jsxs(Box, { flexDirection: stackedLayout ? 'column' : 'row', flexGrow: stackedLayout ? 0 : 1, flexShrink: 1, children: [_jsx(WorktreeList, { rows: visibleRows, selectedIndex: selectedIndex, width: stackedLayout ? bodyWidth : listWidth, height: paneHeight, stacked: stackedLayout, scrollOffset: worktreeScrollOffset, filterQuery: filterQuery, isFilterInputOpen: isFilterInputOpen, totalRowCount: model.rows.length }), _jsx(ActionPanel, { selectedRow: selected, activePath: model.activePath, setupAvailable: model.setupAvailable, stacked: stackedLayout, width: stackedLayout ? bodyWidth : actionWidth, height: paneHeight, compactDetails: compactDetailPane, scrollOffset: selectionScrollOffset })] }), showLogPanel ? _jsx(LogPanel, { logs: model.logs, width: bodyWidth, height: logPaneHeight, scrollOffset: logScrollOffset }) : null, _jsx(ContextBar, { status: visibleStatus, setupAvailable: model.setupAvailable, editorAvailable: model.editorAvailable, confirmationOpen: confirmationOpen }), safeCompletedAlert ? (_jsx(Box, { position: "absolute", top: 1, right: 2, children: _jsx(Alert, { variant: "success", children: safeCompletedAlert }) })) : null] }));
715
+ return (_jsxs(Box, { width: rootWidth, height: rootHeight, flexDirection: "column", children: [_jsx(Header, { repoName: model.repoName, activeBranch: model.activeBranch, status: visibleStatus }), _jsxs(Box, { flexDirection: stackedLayout ? 'column' : 'row', flexGrow: stackedLayout ? 0 : 1, flexShrink: 1, children: [_jsx(WorktreeList, { rows: visibleRows, selectedIndex: selectedIndex, width: stackedLayout ? bodyWidth : listWidth, height: paneHeight, stacked: stackedLayout, scrollOffset: worktreeScrollOffset, filterQuery: filterQuery, isFilterInputOpen: isFilterInputOpen, totalRowCount: model.rows.length }), _jsx(ActionPanel, { selectedRow: selected, activePath: model.activePath, setupAvailable: model.setupAvailable, stacked: stackedLayout, width: stackedLayout ? bodyWidth : actionWidth, height: paneHeight, compactDetails: compactDetailPane, scrollOffset: selectionScrollOffset })] }), showLogPanel ? _jsx(LogPanel, { logs: model.logs, width: bodyWidth, height: logPaneHeight, scrollOffset: logScrollOffset }) : null, _jsx(ShortcutBar, { setupAvailable: model.setupAvailable, editorAvailable: model.editorAvailable, confirmationOpen: confirmationOpen }), safeCompletedAlert ? (_jsx(Box, { position: "absolute", top: 1, right: 2, children: _jsx(Alert, { variant: "success", children: safeCompletedAlert }) })) : null] }));
716
716
  }
717
717
  const MemoizedAppShell = memo(AppShell);
@@ -1,6 +1,6 @@
1
- import React from 'react';
2
- export declare function Header({ repoName, namespace, activeBranch, }: {
1
+ import type { AppStatus } from '../core/runtime.js';
2
+ export declare function Header({ repoName, activeBranch, status, }: {
3
3
  repoName: string;
4
- namespace: string;
5
4
  activeBranch: string | null;
6
- }): React.JSX.Element;
5
+ status: AppStatus;
6
+ }): import("react").JSX.Element;
@@ -1,9 +1,49 @@
1
1
  import { jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import { sanitizeInlineText } from '../core/worktree-projection.js';
4
- export function Header({ repoName, namespace, activeBranch, }) {
4
+ const KIND_TO_ICON = {
5
+ idle: 'ℹ',
6
+ starting: '⚠',
7
+ 'setting-up': '⚠',
8
+ running: '✓',
9
+ stopping: '⚠',
10
+ error: '✘',
11
+ };
12
+ const KIND_TO_COLOR = {
13
+ idle: 'blue',
14
+ starting: 'yellow',
15
+ 'setting-up': 'yellow',
16
+ running: 'green',
17
+ stopping: 'yellow',
18
+ error: 'red',
19
+ };
20
+ const KIND_TO_LABEL = {
21
+ idle: 'Idle',
22
+ starting: 'Starting',
23
+ 'setting-up': 'Setting up',
24
+ running: 'Running',
25
+ stopping: 'Stopping',
26
+ error: 'Error',
27
+ };
28
+ export function Header({ repoName, activeBranch, status, }) {
5
29
  const safeRepoName = sanitizeInlineText(repoName);
6
- const safeNamespace = sanitizeInlineText(namespace);
7
- const safeActiveBranch = activeBranch === null ? '-' : sanitizeInlineText(activeBranch);
8
- return (_jsxs(Box, { borderStyle: "round", borderColor: "blue", flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "blue", wrap: "truncate-end", children: ["Worktree Command TUI \u00B7 Repo: ", safeRepoName] }), _jsxs(Text, { color: "green", wrap: "truncate-end", children: ["Active: ", safeActiveBranch] }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ["Namespace: ", safeNamespace] })] }));
30
+ const safeActiveBranch = activeBranch === null ? null : sanitizeInlineText(activeBranch);
31
+ const safeStatusMessage = sanitizeInlineText(status.message);
32
+ const branchSummary = safeActiveBranch === null
33
+ ? ''
34
+ : status.kind === 'running' ? `: ${safeActiveBranch}` : ` · ${safeActiveBranch}`;
35
+ let statusMessageSummary = safeStatusMessage;
36
+ if (status.kind === 'running' && safeActiveBranch !== null) {
37
+ if (safeStatusMessage === 'running' || safeStatusMessage === `Active: ${safeActiveBranch}`) {
38
+ statusMessageSummary = '';
39
+ }
40
+ else if (safeStatusMessage === `started ${safeActiveBranch}`) {
41
+ statusMessageSummary = 'started';
42
+ }
43
+ else if (safeStatusMessage === `restarted ${safeActiveBranch}`) {
44
+ statusMessageSummary = 'restarted';
45
+ }
46
+ }
47
+ const messageSummary = statusMessageSummary === '' ? '' : ` — ${statusMessageSummary}`;
48
+ return (_jsxs(Box, { borderStyle: "round", borderColor: "blue", flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "blue", wrap: "truncate-end", children: ["Repo: ", safeRepoName] }), _jsxs(Text, { color: KIND_TO_COLOR[status.kind], wrap: "truncate-end", children: [KIND_TO_ICON[status.kind], " ", KIND_TO_LABEL[status.kind], branchSummary, messageSummary] })] }));
9
49
  }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export declare function ShortcutBar({ setupAvailable, editorAvailable, confirmationOpen, }: {
3
+ setupAvailable: boolean;
4
+ editorAvailable: boolean;
5
+ confirmationOpen: boolean;
6
+ }): React.JSX.Element;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text } from 'ink';
4
+ function buildKeyHints(setupAvailable, editorAvailable, confirmationOpen) {
5
+ if (confirmationOpen) {
6
+ return [
7
+ { binding: 'd/y', label: 'Confirm' },
8
+ { binding: 'Esc/n/q', label: 'Cancel' },
9
+ ];
10
+ }
11
+ const hints = [
12
+ { binding: '↑↓/jk', label: 'Move' },
13
+ { binding: 'Enter', label: 'Switch' },
14
+ ];
15
+ hints.push({ binding: '/', label: 'Filter' });
16
+ if (setupAvailable) {
17
+ hints.push({ binding: 'i', label: 'Setup' });
18
+ }
19
+ if (editorAvailable) {
20
+ hints.push({ binding: 'e', label: 'Editor' });
21
+ }
22
+ hints.push({ binding: 'o', label: 'Open PR' }, { binding: 'd', label: 'Delete' }, { binding: 'L', label: 'Logs' }, { binding: 's', label: 'Stop' }, { binding: 'r', label: 'Refresh' }, { binding: '?', label: 'Help' }, { binding: 'q', label: 'Quit' });
23
+ return hints;
24
+ }
25
+ export function ShortcutBar({ setupAvailable, editorAvailable, confirmationOpen, }) {
26
+ const keyHints = buildKeyHints(setupAvailable, editorAvailable, confirmationOpen);
27
+ return (_jsx(Box, { flexShrink: 0, paddingX: 1, children: _jsx(Text, { wrap: "truncate-end", children: keyHints.map((hint, hintIndex) => (_jsxs(React.Fragment, { children: [hintIndex === 0 ? null : _jsx(Text, { dimColor: true, children: " | " }), _jsx(Text, { color: "white", children: hint.binding }), _jsxs(Text, { dimColor: true, children: [" ", hint.label] })] }, hint.binding))) }) }));
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohzw/worktree-command-tui",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "A TUI for managing git worktrees",
5
5
  "private": false,
6
6
  "type": "module",