@colmbus72/yeehaw 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
2
+ import { useState, useEffect } from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
4
  import TextInput from 'ink-text-input';
5
5
  import { Header } from '../components/Header.js';
6
+ import { LivestockHeader } from '../components/LivestockHeader.js';
7
+ import { List } from '../components/List.js';
6
8
  import { Panel } from '../components/Panel.js';
7
9
  import { PathInput } from '../components/PathInput.js';
8
10
  import { loadBarn } from '../lib/config.js';
9
- export function LivestockDetailView({ project, livestock, onBack, onOpenLogs, onOpenSession, onUpdateLivestock, }) {
11
+ import { getWindowStatus } from '../lib/tmux.js';
12
+ export function LivestockDetailView({ project, livestock, source, sourceBarn, windows, onBack, onOpenLogs, onOpenSession, onSelectWindow, onUpdateLivestock, }) {
10
13
  const [mode, setMode] = useState('normal');
11
14
  // Edit form state
12
15
  const [editName, setEditName] = useState(livestock.name);
@@ -17,6 +20,15 @@ export function LivestockDetailView({ project, livestock, onBack, onOpenLogs, on
17
20
  const [editEnvPath, setEditEnvPath] = useState(livestock.env_path || '');
18
21
  // Get barn info if remote
19
22
  const barn = livestock.barn ? loadBarn(livestock.barn) : null;
23
+ // Sync form state when livestock prop changes (e.g., after save)
24
+ useEffect(() => {
25
+ setEditName(livestock.name);
26
+ setEditPath(livestock.path);
27
+ setEditRepo(livestock.repo || '');
28
+ setEditBranch(livestock.branch || '');
29
+ setEditLogPath(livestock.log_path || '');
30
+ setEditEnvPath(livestock.env_path || '');
31
+ }, [livestock]);
20
32
  const resetForm = () => {
21
33
  setEditName(livestock.name);
22
34
  setEditPath(livestock.path);
@@ -25,29 +37,18 @@ export function LivestockDetailView({ project, livestock, onBack, onOpenLogs, on
25
37
  setEditLogPath(livestock.log_path || '');
26
38
  setEditEnvPath(livestock.env_path || '');
27
39
  };
28
- const saveEdit = (field, value) => {
29
- const updated = { ...livestock };
30
- switch (field) {
31
- case 'name':
32
- updated.name = value;
33
- break;
34
- case 'path':
35
- updated.path = value;
36
- break;
37
- case 'repo':
38
- updated.repo = value || undefined;
39
- break;
40
- case 'branch':
41
- updated.branch = value || undefined;
42
- break;
43
- case 'log_path':
44
- updated.log_path = value || undefined;
45
- break;
46
- case 'env_path':
47
- updated.env_path = value || undefined;
48
- break;
49
- }
50
- onUpdateLivestock(updated);
40
+ // Save all pending changes at once
41
+ const saveAllChanges = () => {
42
+ const updated = {
43
+ ...livestock,
44
+ name: editName.trim() || livestock.name,
45
+ path: editPath.trim() || livestock.path,
46
+ repo: editRepo.trim() || undefined,
47
+ branch: editBranch.trim() || undefined,
48
+ log_path: editLogPath.trim() || undefined,
49
+ env_path: editEnvPath.trim() || undefined,
50
+ };
51
+ onUpdateLivestock(livestock, updated);
51
52
  setMode('normal');
52
53
  };
53
54
  useInput((input, key) => {
@@ -62,13 +63,17 @@ export function LivestockDetailView({ project, livestock, onBack, onOpenLogs, on
62
63
  }
63
64
  return;
64
65
  }
66
+ // Handle Ctrl+S to save and exit from any edit mode
67
+ // Note: Ctrl+S sends ASCII 19 (\x13), not 's'
68
+ if ((key.ctrl && input === 's') || input === '\x13') {
69
+ if (mode !== 'normal') {
70
+ saveAllChanges();
71
+ return;
72
+ }
73
+ }
65
74
  // Only process these in normal mode
66
75
  if (mode !== 'normal')
67
76
  return;
68
- if (input === 'q') {
69
- onBack();
70
- return;
71
- }
72
77
  if (input === 's') {
73
78
  onOpenSession();
74
79
  return;
@@ -91,50 +96,47 @@ export function LivestockDetailView({ project, livestock, onBack, onOpenLogs, on
91
96
  if (mode === 'edit-name') {
92
97
  return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: `Edit: ${livestock.name}`, color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Livestock" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Name: " }), _jsx(TextInput, { value: editName, onChange: setEditName, onSubmit: () => {
93
98
  if (editName.trim()) {
94
- saveEdit('name', editName.trim());
95
99
  setMode('edit-path');
96
100
  }
97
- } })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
101
+ } })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Ctrl+S: save & exit, Esc: cancel" }) })] })] }));
98
102
  }
99
103
  // Edit path
100
104
  if (mode === 'edit-path') {
101
105
  return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: `Edit: ${livestock.name}`, color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Livestock" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Path: " }), _jsx(PathInput, { value: editPath, onChange: setEditPath, onSubmit: () => {
102
106
  if (editPath.trim()) {
103
- saveEdit('path', editPath.trim());
104
107
  setMode('edit-repo');
105
108
  }
106
- } })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Tab: autocomplete, Esc: cancel" }) })] })] }));
109
+ } })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next, Tab: autocomplete, Ctrl+S: save & exit, Esc: cancel" }) })] })] }));
107
110
  }
108
111
  // Edit repo
109
112
  if (mode === 'edit-repo') {
110
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: `Edit: ${livestock.name}`, color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Livestock" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Git Repo (optional): " }), _jsx(TextInput, { value: editRepo, onChange: setEditRepo, onSubmit: () => {
111
- saveEdit('repo', editRepo.trim());
112
- setMode('edit-branch');
113
- } })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
113
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: `Edit: ${livestock.name}`, color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Livestock" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Git Repo (optional): " }), _jsx(TextInput, { value: editRepo, onChange: setEditRepo, onSubmit: () => setMode('edit-branch') })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Ctrl+S: save & exit, Esc: cancel" }) })] })] }));
114
114
  }
115
115
  // Edit branch
116
116
  if (mode === 'edit-branch') {
117
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: `Edit: ${livestock.name}`, color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Livestock" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Git Branch (optional): " }), _jsx(TextInput, { value: editBranch, onChange: setEditBranch, onSubmit: () => {
118
- saveEdit('branch', editBranch.trim());
119
- setMode('edit-log-path');
120
- } })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
117
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: `Edit: ${livestock.name}`, color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Livestock" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Git Branch (optional): " }), _jsx(TextInput, { value: editBranch, onChange: setEditBranch, onSubmit: () => setMode('edit-log-path') })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Ctrl+S: save & exit, Esc: cancel" }) })] })] }));
121
118
  }
122
119
  // Edit log path
123
120
  if (mode === 'edit-log-path') {
124
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: `Edit: ${livestock.name}`, color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Livestock" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Log Path (optional, relative): " }), _jsx(TextInput, { value: editLogPath, onChange: setEditLogPath, onSubmit: () => {
125
- saveEdit('log_path', editLogPath.trim());
126
- setMode('edit-env-path');
127
- } })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
121
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: `Edit: ${livestock.name}`, color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Livestock" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Log Path (optional, relative): " }), _jsx(TextInput, { value: editLogPath, onChange: setEditLogPath, onSubmit: () => setMode('edit-env-path') })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Ctrl+S: save & exit, Esc: cancel" }) })] })] }));
128
122
  }
129
- // Edit env path
123
+ // Edit env path (last field - saves all changes)
130
124
  if (mode === 'edit-env-path') {
131
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: `Edit: ${livestock.name}`, color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Livestock" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Env Path (optional, relative): " }), _jsx(TextInput, { value: editEnvPath, onChange: setEditEnvPath, onSubmit: () => {
132
- saveEdit('env_path', editEnvPath.trim());
133
- // All done - return to normal mode
134
- setMode('normal');
135
- } })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: save & finish, Esc: cancel" }) })] })] }));
125
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: `Edit: ${livestock.name}`, color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Livestock" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Env Path (optional, relative): " }), _jsx(TextInput, { value: editEnvPath, onChange: setEditEnvPath, onSubmit: saveAllChanges })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: save & finish, Ctrl+S: save & exit, Esc: cancel" }) })] })] }));
136
126
  }
137
- // Normal view - show livestock info
138
- const hints = `[s] shell ${livestock.log_path ? '[l] logs ' : ''}[e] edit [q] back`;
139
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: livestock.name, color: project.color }), _jsx(Box, { flexGrow: 1, paddingX: 1, children: _jsx(Panel, { title: "Livestock Details", focused: true, hints: hints, children: _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Box, { children: [_jsx(Box, { width: 12, children: _jsx(Text, { bold: true, children: "Name:" }) }), _jsx(Text, { children: livestock.name })] }), _jsxs(Box, { children: [_jsx(Box, { width: 12, children: _jsx(Text, { bold: true, children: "Path:" }) }), _jsx(Text, { children: livestock.path })] }), _jsxs(Box, { children: [_jsx(Box, { width: 12, children: _jsx(Text, { bold: true, children: "Location:" }) }), barn ? (_jsxs(Text, { children: [barn.name, " ", _jsxs(Text, { dimColor: true, children: ["(", barn.host, ")"] })] })) : (_jsx(Text, { children: "Local" }))] }), livestock.repo && (_jsxs(Box, { children: [_jsx(Box, { width: 12, children: _jsx(Text, { bold: true, children: "Repo:" }) }), _jsx(Text, { children: livestock.repo })] })), livestock.branch && (_jsxs(Box, { children: [_jsx(Box, { width: 12, children: _jsx(Text, { bold: true, children: "Branch:" }) }), _jsx(Text, { children: livestock.branch })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, color: "yellow", children: "Operational Config" }) }), _jsxs(Box, { children: [_jsx(Box, { width: 12, children: _jsx(Text, { bold: true, children: "Log Path:" }) }), livestock.log_path ? (_jsx(Text, { children: livestock.log_path })) : (_jsx(Text, { dimColor: true, children: "Not configured" }))] }), _jsxs(Box, { children: [_jsx(Box, { width: 12, children: _jsx(Text, { bold: true, children: "Env Path:" }) }), livestock.env_path ? (_jsx(Text, { children: livestock.env_path })) : (_jsx(Text, { dimColor: true, children: "Not configured" }))] })] }) }) })] }));
127
+ // Filter windows to this livestock (match pattern: projectname-livestockname)
128
+ const livestockWindowName = `${project.name}-${livestock.name}`;
129
+ const livestockWindows = windows.filter(w => w.name === livestockWindowName || w.name.startsWith(`${livestockWindowName}-`));
130
+ const sessionItems = livestockWindows.map((w, i) => ({
131
+ id: String(w.index),
132
+ label: `[${i + 1}] shell`,
133
+ status: w.active ? 'active' : 'inactive',
134
+ meta: getWindowStatus(w),
135
+ }));
136
+ // Normal view - show livestock info inline with sessions
137
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(LivestockHeader, { project: project, livestock: livestock }), _jsxs(Box, { paddingX: 2, gap: 3, children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "path:" }), " ", livestock.path] }), barn && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "barn:" }), " ", barn.name, " ", _jsxs(Text, { dimColor: true, children: ["(", barn.host, ")"] })] }))] }), _jsxs(Box, { paddingX: 2, gap: 3, marginBottom: 1, children: [livestock.repo && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "repo:" }), " ", livestock.repo] })), livestock.log_path && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "logs:" }), " ", livestock.log_path] })), livestock.env_path && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "env:" }), " ", livestock.env_path] }))] }), _jsx(Box, { paddingX: 1, flexGrow: 1, children: _jsx(Panel, { title: "Sessions", focused: true, hints: `[s] shell ${livestock.log_path ? '[l] logs ' : ''}[e] edit`, children: sessionItems.length > 0 ? (_jsx(List, { items: sessionItems, focused: true, onSelect: (item) => {
138
+ const window = livestockWindows.find(w => String(w.index) === item.id);
139
+ if (window)
140
+ onSelectWindow(window);
141
+ } })) : (_jsx(Text, { dimColor: true, italic: true, children: "No active sessions. Press [s] to start a shell." })) }) })] }));
140
142
  }
@@ -33,7 +33,7 @@ export function LogsView({ project, livestock, onBack }) {
33
33
  }, [livestock]);
34
34
  const totalLines = logs.length;
35
35
  useInput((input, key) => {
36
- if (key.escape || input === 'q') {
36
+ if (key.escape) {
37
37
  onBack();
38
38
  return;
39
39
  }
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
2
+ import { useState, useCallback } from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
4
  import TextInput from 'ink-text-input';
5
5
  import { Header } from '../components/Header.js';
@@ -9,6 +9,70 @@ import { PathInput } from '../components/PathInput.js';
9
9
  import { getWindowStatus } from '../lib/tmux.js';
10
10
  import { detectGitInfo, detectRemoteGitInfo } from '../lib/git.js';
11
11
  import { detectLivestockConfig } from '../lib/livestock.js';
12
+ import { isLocalBarn } from '../lib/config.js';
13
+ // HSL color utilities for color picker
14
+ function hexToHsl(hex) {
15
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
16
+ if (!result)
17
+ return null;
18
+ const r = parseInt(result[1], 16) / 255;
19
+ const g = parseInt(result[2], 16) / 255;
20
+ const b = parseInt(result[3], 16) / 255;
21
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
22
+ let h = 0, s = 0;
23
+ const l = (max + min) / 2;
24
+ if (max !== min) {
25
+ const d = max - min;
26
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
27
+ switch (max) {
28
+ case r:
29
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
30
+ break;
31
+ case g:
32
+ h = ((b - r) / d + 2) / 6;
33
+ break;
34
+ case b:
35
+ h = ((r - g) / d + 4) / 6;
36
+ break;
37
+ }
38
+ }
39
+ return { h: h * 360, s: s * 100, l: l * 100 };
40
+ }
41
+ function hslToHex(h, s, l) {
42
+ h = ((h % 360) + 360) % 360;
43
+ s = Math.max(0, Math.min(100, s)) / 100;
44
+ l = Math.max(0, Math.min(100, l)) / 100;
45
+ const c = (1 - Math.abs(2 * l - 1)) * s;
46
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
47
+ const m = l - c / 2;
48
+ let r = 0, g = 0, b = 0;
49
+ if (h < 60) {
50
+ r = c;
51
+ g = x;
52
+ }
53
+ else if (h < 120) {
54
+ r = x;
55
+ g = c;
56
+ }
57
+ else if (h < 180) {
58
+ g = c;
59
+ b = x;
60
+ }
61
+ else if (h < 240) {
62
+ g = x;
63
+ b = c;
64
+ }
65
+ else if (h < 300) {
66
+ r = x;
67
+ b = c;
68
+ }
69
+ else {
70
+ r = c;
71
+ b = x;
72
+ }
73
+ const toHex = (n) => Math.round((n + m) * 255).toString(16).padStart(2, '0');
74
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
75
+ }
12
76
  export function ProjectContext({ project, barns, windows, onBack, onNewClaude, onSelectWindow, onSelectLivestock, onOpenLivestockSession, onUpdateProject, onDeleteProject, onOpenWiki, onOpenIssues, }) {
13
77
  const [focusedPanel, setFocusedPanel] = useState('livestock');
14
78
  const [mode, setMode] = useState('normal');
@@ -17,6 +81,8 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
17
81
  const [editPath, setEditPath] = useState(project.path);
18
82
  const [editSummary, setEditSummary] = useState(project.summary || '');
19
83
  const [editColor, setEditColor] = useState(project.color || '');
84
+ const [editGradientSpread, setEditGradientSpread] = useState(project.gradientSpread ?? 5);
85
+ const [editGradientInverted, setEditGradientInverted] = useState(project.gradientInverted ?? false);
20
86
  // New livestock form state
21
87
  const [newLivestockName, setNewLivestockName] = useState('');
22
88
  const [newLivestockPath, setNewLivestockPath] = useState('');
@@ -33,6 +99,40 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
33
99
  const [detectedGit, setDetectedGit] = useState(null);
34
100
  // Framework detection for config suggestions
35
101
  const [detectedConfig, setDetectedConfig] = useState(null);
102
+ // Color picker keyboard handlers
103
+ const shiftHue = useCallback((delta) => {
104
+ const currentColor = editColor || '#f0c040';
105
+ const hsl = hexToHsl(currentColor);
106
+ if (hsl) {
107
+ const newHex = hslToHex(hsl.h + delta, hsl.s, hsl.l);
108
+ setEditColor(newHex);
109
+ }
110
+ else if (!editColor) {
111
+ setEditColor(hslToHex(delta > 0 ? 10 : 350, 70, 50));
112
+ }
113
+ }, [editColor]);
114
+ const adjustSpread = useCallback((delta) => {
115
+ setEditGradientSpread((s) => Math.max(0, Math.min(10, s + delta)));
116
+ }, []);
117
+ const toggleInvert = useCallback(() => {
118
+ setEditGradientInverted((i) => !i);
119
+ }, []);
120
+ // Handle color picker inputs (must be before any conditional returns)
121
+ // Using [ ] for hue shift and ! for invert to avoid conflicts with text input
122
+ useInput((input, key) => {
123
+ if (mode !== 'edit-color')
124
+ return;
125
+ if (input === '[')
126
+ shiftHue(-10);
127
+ else if (input === ']')
128
+ shiftHue(10);
129
+ else if (key.upArrow)
130
+ adjustSpread(1);
131
+ else if (key.downArrow)
132
+ adjustSpread(-1);
133
+ else if (input === '!')
134
+ toggleInvert();
135
+ }, { isActive: mode === 'edit-color' });
36
136
  // Filter windows to this project (by name prefix)
37
137
  const projectWindows = windows.filter((w) => w.index > 0 && w.name.startsWith(project.name));
38
138
  // Create a map from display number (1-9) to window for quick access
@@ -47,6 +147,8 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
47
147
  setEditPath(project.path);
48
148
  setEditSummary(project.summary || '');
49
149
  setEditColor(project.color || '');
150
+ setEditGradientSpread(project.gradientSpread ?? 5);
151
+ setEditGradientInverted(project.gradientInverted ?? false);
50
152
  setMode('edit-name');
51
153
  };
52
154
  const cancelEdit = () => {
@@ -61,6 +163,8 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
61
163
  path: editPath,
62
164
  summary: editSummary || undefined,
63
165
  color: editColor || undefined,
166
+ gradientSpread: editGradientSpread,
167
+ gradientInverted: editGradientInverted,
64
168
  });
65
169
  setMode('normal');
66
170
  }
@@ -159,10 +263,6 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
159
263
  }
160
264
  if (mode !== 'normal')
161
265
  return;
162
- if (input === 'q') {
163
- onBack();
164
- return;
165
- }
166
266
  if (key.tab) {
167
267
  setFocusedPanel((p) => (p === 'livestock' ? 'sessions' : 'livestock'));
168
268
  return;
@@ -225,37 +325,44 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
225
325
  });
226
326
  // Edit mode screens
227
327
  if (mode === 'edit-name') {
228
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Editing project", color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Project" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Name: " }), _jsx(TextInput, { value: editName, onChange: setEditName, onSubmit: () => saveAndNext('edit-path') })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
328
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Editing project", color: project.color, gradientSpread: project.gradientSpread, gradientInverted: project.gradientInverted }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Project" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Name: " }), _jsx(TextInput, { value: editName, onChange: setEditName, onSubmit: () => saveAndNext('edit-path') })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
229
329
  }
230
330
  if (mode === 'edit-path') {
231
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Editing project", color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Edit Project: ", editName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Path: " }), _jsx(PathInput, { value: editPath, onChange: setEditPath, onSubmit: () => saveAndNext('edit-summary') })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Tab: autocomplete, Enter: next field, Esc: cancel" }) })] })] }));
331
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Editing project", color: project.color, gradientSpread: project.gradientSpread, gradientInverted: project.gradientInverted }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Edit Project: ", editName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Path: " }), _jsx(PathInput, { value: editPath, onChange: setEditPath, onSubmit: () => saveAndNext('edit-summary') })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Tab: autocomplete, Enter: next field, Esc: cancel" }) })] })] }));
232
332
  }
233
333
  if (mode === 'edit-summary') {
234
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Editing project", color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Edit Project: ", editName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Summary: " }), _jsx(TextInput, { value: editSummary, onChange: setEditSummary, onSubmit: () => saveAndNext('edit-color'), placeholder: "Short description..." })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel (leave blank to skip)" }) })] })] }));
334
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Editing project", color: project.color, gradientSpread: project.gradientSpread, gradientInverted: project.gradientInverted }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Edit Project: ", editName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Summary: " }), _jsx(TextInput, { value: editSummary, onChange: setEditSummary, onSubmit: () => saveAndNext('edit-color'), placeholder: "Short description..." })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel (leave blank to skip)" }) })] })] }));
235
335
  }
236
336
  if (mode === 'edit-color') {
237
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Editing project", color: editColor || project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Edit Project: ", editName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Color (hex): " }), _jsx(TextInput, { value: editColor, onChange: setEditColor, onSubmit: () => saveAndNext('done'), placeholder: "#ff6b6b" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: save project, Esc: cancel (leave blank for default)" }) })] })] }));
337
+ const displayColor = editColor || '#f0c040';
338
+ // Filter out special keys used for color picker controls
339
+ const handleColorChange = (value) => {
340
+ const filtered = value.replace(/[\[\]!]/g, '');
341
+ setEditColor(filtered);
342
+ };
343
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Editing project", color: displayColor, gradientSpread: editGradientSpread, gradientInverted: editGradientInverted }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Edit Project: ", editName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Color (hex): " }), _jsx(TextInput, { value: editColor, onChange: handleColorChange, onSubmit: () => saveAndNext('done'), placeholder: "#ff6b6b" })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["Spread: ", editGradientSpread, "/10 ", editGradientInverted ? '(inverted)' : ''] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: displayColor, children: "\u2588\u2588\u2588\u2588" }), _jsx(Text, { children: " Preview" })] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "[ ] shift color \u2191/\u2193 gradient spread ! invert" }), _jsx(Text, { dimColor: true, children: "Enter: save project, Esc: cancel" })] })] })] }));
238
344
  }
239
345
  // Add livestock flow: name → barn → path (with auto-detect)
240
346
  if (mode === 'add-livestock-name') {
241
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Adding livestock", color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "green", children: "Add Livestock" }), _jsx(Text, { dimColor: true, children: "Livestock are deployed instances of your app" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Name: " }), _jsx(TextInput, { value: newLivestockName, onChange: setNewLivestockName, onSubmit: () => {
347
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Adding livestock", color: project.color, gradientSpread: project.gradientSpread, gradientInverted: project.gradientInverted }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "green", children: "Add Livestock" }), _jsx(Text, { dimColor: true, children: "Livestock are deployed instances of your app" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Name: " }), _jsx(TextInput, { value: newLivestockName, onChange: setNewLivestockName, onSubmit: () => {
242
348
  if (newLivestockName.trim()) {
243
349
  setMode('add-livestock-barn');
244
350
  }
245
351
  }, placeholder: "local, dev, production..." })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next, Esc: cancel" }) })] })] }));
246
352
  }
247
353
  if (mode === 'add-livestock-barn') {
248
- // Build barn options including "Local" option
354
+ // Build barn options including "Local" option (filter out local barn from array to avoid duplicates)
355
+ const remoteBarns = barns.filter((b) => !isLocalBarn(b));
249
356
  const barnOptions = [
250
357
  { id: '__local__', label: 'Local (this machine)', status: 'active' },
251
- ...barns.map((b) => ({
358
+ ...remoteBarns.map((b) => ({
252
359
  id: b.name,
253
360
  label: b.name,
254
361
  status: 'active',
255
362
  meta: `${b.user}@${b.host}`,
256
363
  })),
257
364
  ];
258
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Adding livestock", color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add Livestock: ", newLivestockName] }), _jsx(Text, { dimColor: true, children: "Where is this livestock deployed?" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(List, { items: barnOptions, focused: true, onSelect: (item) => {
365
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Adding livestock", color: project.color, gradientSpread: project.gradientSpread, gradientInverted: project.gradientInverted }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add Livestock: ", newLivestockName] }), _jsx(Text, { dimColor: true, children: "Where is this livestock deployed?" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(List, { items: barnOptions, focused: true, onSelect: (item) => {
259
366
  if (item.id === '__local__') {
260
367
  setSelectedBarn(null);
261
368
  }
@@ -270,7 +377,7 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
270
377
  const locationLabel = selectedBarn
271
378
  ? `on ${selectedBarn.name} (${selectedBarn.host})`
272
379
  : 'local';
273
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Adding livestock", color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add Livestock: ", newLivestockName] }), _jsxs(Text, { dimColor: true, children: ["Location: ", locationLabel] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Path: " }), _jsx(PathInput, { value: newLivestockPath, onChange: setNewLivestockPath, onSubmit: handlePathSubmit, barn: selectedBarn || undefined })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Tab: autocomplete, Enter: next, Esc: cancel" }) })] })] }));
380
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Adding livestock", color: project.color, gradientSpread: project.gradientSpread, gradientInverted: project.gradientInverted }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add Livestock: ", newLivestockName] }), _jsxs(Text, { dimColor: true, children: ["Location: ", locationLabel] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Path: " }), _jsx(PathInput, { value: newLivestockPath, onChange: setNewLivestockPath, onSubmit: handlePathSubmit, barn: selectedBarn || undefined })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Tab: autocomplete, Enter: next, Esc: cancel" }) })] })] }));
274
381
  }
275
382
  if (mode === 'add-livestock-log-path') {
276
383
  const locationLabel = selectedBarn
@@ -279,13 +386,13 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
279
386
  const frameworkLabel = detectedConfig?.framework && detectedConfig.framework !== 'unknown'
280
387
  ? ` (${detectedConfig.framework} detected)`
281
388
  : '';
282
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Adding livestock", color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add Livestock: ", newLivestockName] }), _jsxs(Text, { dimColor: true, children: ["Location: ", locationLabel, " \u2022 Path: ", newLivestockPath] }), frameworkLabel && _jsx(Text, { color: "cyan", children: frameworkLabel }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Log path (optional): " }), _jsx(TextInput, { value: newLivestockLogPath, onChange: setNewLivestockLogPath, onSubmit: () => setMode('add-livestock-env-path'), placeholder: "storage/logs/ or logs/" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Relative to livestock path. Enter: next (leave blank to skip), Esc: cancel" }) })] })] }));
389
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Adding livestock", color: project.color, gradientSpread: project.gradientSpread, gradientInverted: project.gradientInverted }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add Livestock: ", newLivestockName] }), _jsxs(Text, { dimColor: true, children: ["Location: ", locationLabel, " \u2022 Path: ", newLivestockPath] }), frameworkLabel && _jsx(Text, { color: "cyan", children: frameworkLabel }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Log path (optional): " }), _jsx(TextInput, { value: newLivestockLogPath, onChange: setNewLivestockLogPath, onSubmit: () => setMode('add-livestock-env-path'), placeholder: "storage/logs/ or logs/" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Relative to livestock path. Enter: next (leave blank to skip), Esc: cancel" }) })] })] }));
283
390
  }
284
391
  if (mode === 'add-livestock-env-path') {
285
392
  const locationLabel = selectedBarn
286
393
  ? `on ${selectedBarn.name} (${selectedBarn.host})`
287
394
  : 'local';
288
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Adding livestock", color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add Livestock: ", newLivestockName] }), _jsxs(Text, { dimColor: true, children: ["Location: ", locationLabel, " \u2022 Path: ", newLivestockPath] }), newLivestockLogPath && _jsxs(Text, { dimColor: true, children: ["Log path: ", newLivestockLogPath] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Env path (optional): " }), _jsx(TextInput, { value: newLivestockEnvPath, onChange: setNewLivestockEnvPath, onSubmit: saveLivestock, placeholder: ".env" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Relative to livestock path. Enter: save livestock, Esc: cancel" }) })] })] }));
395
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Adding livestock", color: project.color, gradientSpread: project.gradientSpread, gradientInverted: project.gradientInverted }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add Livestock: ", newLivestockName] }), _jsxs(Text, { dimColor: true, children: ["Location: ", locationLabel, " \u2022 Path: ", newLivestockPath] }), newLivestockLogPath && _jsxs(Text, { dimColor: true, children: ["Log path: ", newLivestockLogPath] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Env path (optional): " }), _jsx(TextInput, { value: newLivestockEnvPath, onChange: setNewLivestockEnvPath, onSubmit: saveLivestock, placeholder: ".env" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Relative to livestock path. Enter: save livestock, Esc: cancel" }) })] })] }));
289
396
  }
290
397
  if (mode === 'delete-project-confirm') {
291
398
  const handleDeleteConfirm = () => {
@@ -297,7 +404,7 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
297
404
  }
298
405
  // Delete livestock confirmation
299
406
  if (mode === 'delete-livestock-confirm' && deleteLivestockTarget) {
300
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Remove livestock", color: project.color }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "red", children: "Remove Livestock" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Remove \"", deleteLivestockTarget.name, "\" from this project?"] }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Path: ", deleteLivestockTarget.path] }) }), _jsxs(Box, { marginTop: 1, gap: 2, children: [_jsx(Text, { color: "red", bold: true, children: "[y] Yes, remove" }), _jsx(Text, { dimColor: true, children: "[n/Esc] Cancel" })] })] })] }));
407
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: "Remove livestock", color: project.color, gradientSpread: project.gradientSpread, gradientInverted: project.gradientInverted }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "red", children: "Remove Livestock" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Remove \"", deleteLivestockTarget.name, "\" from this project?"] }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Path: ", deleteLivestockTarget.path] }) }), _jsxs(Box, { marginTop: 1, gap: 2, children: [_jsx(Text, { color: "red", bold: true, children: "[y] Yes, remove" }), _jsx(Text, { dimColor: true, children: "[n/Esc] Cancel" })] })] })] }));
301
408
  }
302
409
  // Get livestock with resolved barn info
303
410
  const livestockWithBarns = (project.livestock || []).map((livestock) => ({
@@ -310,17 +417,28 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
310
417
  status: 'active', // TODO: actual health check
311
418
  meta: livestock.path,
312
419
  }));
420
+ // Parse session name for type hint (consistent with GlobalDashboard)
421
+ const getSessionTypeHint = (name) => {
422
+ if (name.endsWith('-claude'))
423
+ return 'claude';
424
+ return 'shell';
425
+ };
313
426
  // Use display numbers (1-9) instead of tmux window index
314
- const sessionItems = projectWindows.map((w, i) => ({
315
- id: String(w.index),
316
- label: `[${i + 1}] ${w.name.replace(`${project.name}-`, '')}`,
317
- status: w.active ? 'active' : 'inactive',
318
- meta: getWindowStatus(w),
319
- }));
427
+ const sessionItems = projectWindows.map((w, i) => {
428
+ const sessionName = w.name.replace(`${project.name}-`, '');
429
+ const typeHint = getSessionTypeHint(w.name);
430
+ const displayName = sessionName.replace('-claude', '');
431
+ return {
432
+ id: String(w.index),
433
+ label: `[${i + 1}] ${displayName}`,
434
+ status: w.active ? 'active' : 'inactive',
435
+ meta: `${typeHint} · ${getWindowStatus(w)}`,
436
+ };
437
+ });
320
438
  // Panel-specific hints (page-level hotkeys like c/w/i are in BottomBar)
321
439
  const livestockHints = '[s] shell [n] new [d] delete';
322
440
  const sessionHints = '1-9 switch';
323
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: project.path, summary: project.summary, color: project.color }), _jsxs(Box, { flexGrow: 1, marginY: 1, paddingX: 1, gap: 2, children: [_jsx(Panel, { title: "Livestock", focused: focusedPanel === 'livestock', width: "50%", hints: livestockHints, children: livestockItems.length > 0 ? (_jsx(List, { items: livestockItems, focused: focusedPanel === 'livestock', selectedIndex: selectedLivestockIndex, onSelectionChange: setSelectedLivestockIndex, onSelect: (item) => {
441
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: project.path, summary: project.summary, color: project.color, gradientSpread: project.gradientSpread, gradientInverted: project.gradientInverted }), _jsxs(Box, { flexGrow: 1, marginY: 1, paddingX: 1, gap: 2, children: [_jsx(Panel, { title: "Livestock", focused: focusedPanel === 'livestock', width: "50%", hints: livestockHints, children: livestockItems.length > 0 ? (_jsx(List, { items: livestockItems, focused: focusedPanel === 'livestock', selectedIndex: selectedLivestockIndex, onSelectionChange: setSelectedLivestockIndex, onSelect: (item) => {
324
442
  const found = livestockWithBarns.find((l) => l.livestock.name === item.id);
325
443
  if (found) {
326
444
  onSelectLivestock(found.livestock, found.barn);
@@ -65,10 +65,6 @@ export function WikiView({ project, onBack, onUpdateProject }) {
65
65
  // Only process these in normal mode
66
66
  if (mode !== 'normal')
67
67
  return;
68
- if (input === 'q') {
69
- onBack();
70
- return;
71
- }
72
68
  // Tab to switch focus between panels
73
69
  if (key.tab) {
74
70
  setFocusedPanel((prev) => (prev === 'sections' ? 'content' : 'sections'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colmbus72/yeehaw",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Terminal dashboard for managing projects, servers, and deployments",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "files": [
12
12
  "dist",
13
+ "claude-plugin",
13
14
  "README.md",
14
15
  "LICENSE"
15
16
  ],