@colmbus72/yeehaw 0.3.0 → 0.4.1

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.
@@ -37,29 +37,18 @@ export function LivestockDetailView({ project, livestock, source, sourceBarn, wi
37
37
  setEditLogPath(livestock.log_path || '');
38
38
  setEditEnvPath(livestock.env_path || '');
39
39
  };
40
- const saveEdit = (field, value) => {
41
- const updated = { ...livestock };
42
- switch (field) {
43
- case 'name':
44
- updated.name = value;
45
- break;
46
- case 'path':
47
- updated.path = value;
48
- break;
49
- case 'repo':
50
- updated.repo = value || undefined;
51
- break;
52
- case 'branch':
53
- updated.branch = value || undefined;
54
- break;
55
- case 'log_path':
56
- updated.log_path = value || undefined;
57
- break;
58
- case 'env_path':
59
- updated.env_path = value || undefined;
60
- break;
61
- }
62
- 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);
63
52
  setMode('normal');
64
53
  };
65
54
  useInput((input, key) => {
@@ -74,6 +63,14 @@ export function LivestockDetailView({ project, livestock, source, sourceBarn, wi
74
63
  }
75
64
  return;
76
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
+ }
77
74
  // Only process these in normal mode
78
75
  if (mode !== 'normal')
79
76
  return;
@@ -99,48 +96,33 @@ export function LivestockDetailView({ project, livestock, source, sourceBarn, wi
99
96
  if (mode === 'edit-name') {
100
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: () => {
101
98
  if (editName.trim()) {
102
- saveEdit('name', editName.trim());
103
99
  setMode('edit-path');
104
100
  }
105
- } })] }), _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" }) })] })] }));
106
102
  }
107
103
  // Edit path
108
104
  if (mode === 'edit-path') {
109
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: () => {
110
106
  if (editPath.trim()) {
111
- saveEdit('path', editPath.trim());
112
107
  setMode('edit-repo');
113
108
  }
114
- } })] }), _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" }) })] })] }));
115
110
  }
116
111
  // Edit repo
117
112
  if (mode === 'edit-repo') {
118
- 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: () => {
119
- saveEdit('repo', editRepo.trim());
120
- setMode('edit-branch');
121
- } })] }), _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" }) })] })] }));
122
114
  }
123
115
  // Edit branch
124
116
  if (mode === 'edit-branch') {
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: "Git Branch (optional): " }), _jsx(TextInput, { value: editBranch, onChange: setEditBranch, onSubmit: () => {
126
- saveEdit('branch', editBranch.trim());
127
- setMode('edit-log-path');
128
- } })] }), _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" }) })] })] }));
129
118
  }
130
119
  // Edit log path
131
120
  if (mode === 'edit-log-path') {
132
- 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: () => {
133
- saveEdit('log_path', editLogPath.trim());
134
- setMode('edit-env-path');
135
- } })] }), _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" }) })] })] }));
136
122
  }
137
- // Edit env path
123
+ // Edit env path (last field - saves all changes)
138
124
  if (mode === 'edit-env-path') {
139
- 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: () => {
140
- saveEdit('env_path', editEnvPath.trim());
141
- // All done - return to normal mode
142
- setMode('normal');
143
- } })] }), _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" }) })] })] }));
144
126
  }
145
127
  // Filter windows to this livestock (match pattern: projectname-livestockname)
146
128
  const livestockWindowName = `${project.name}-${livestock.name}`;
@@ -273,8 +273,8 @@ export function NightSkyView({ onExit }) {
273
273
  }, FRAME_INTERVAL);
274
274
  return () => clearInterval(interval);
275
275
  }, [isPaused]);
276
- // Rendering helpers
277
- const getWinkingStarDisplay = (star) => {
276
+ // Memoized rendering helpers to ensure stable references
277
+ const getWinkingStarDisplay = useCallback((star) => {
278
278
  const { progress, holdDuration } = star;
279
279
  let brightness;
280
280
  if (progress < 1) {
@@ -288,13 +288,13 @@ export function NightSkyView({ onExit }) {
288
288
  }
289
289
  const chars = [' ', '.', '·', '+', '*'];
290
290
  return { char: chars[Math.round(brightness * 4)], dim: brightness < 0.5 };
291
- };
292
- const getStaticStarDisplay = (star) => {
291
+ }, []);
292
+ const getStaticStarDisplay = useCallback((star) => {
293
293
  const brightness = star.baseBrightness + Math.sin(star.pulsePhase) * star.pulseAmount;
294
294
  return { char: star.char, dim: brightness < 0.4 };
295
- };
295
+ }, []);
296
296
  // Cloud display - simple opacity-based fading
297
- const getCloudOpacity = (cloud) => {
297
+ const getCloudOpacity = useCallback((cloud) => {
298
298
  const { progress } = cloud;
299
299
  if (progress < 0.3) {
300
300
  // Fading in - dim
@@ -308,67 +308,70 @@ export function NightSkyView({ onExit }) {
308
308
  // Fading out - dim
309
309
  return { dim: true, visible: true };
310
310
  }
311
- };
312
- // Render sky
313
- const skyRows = [];
314
- for (let y = 0; y < skyHeight; y++) {
315
- let rowChars = ' '.repeat(width).split('');
316
- let rowDims = new Array(width).fill(false);
317
- let rowColors = new Array(width).fill(undefined);
318
- // Place stars
319
- for (const star of state.stars) {
320
- if (star.y === y && star.x >= 0 && star.x < width) {
321
- const display = star.type === 'winking' ? getWinkingStarDisplay(star) : getStaticStarDisplay(star);
322
- if (display.char !== ' ') {
323
- rowChars[star.x] = display.char;
324
- rowDims[star.x] = display.dim;
311
+ }, []);
312
+ // Memoized sky row rendering to reduce re-render overhead
313
+ const skyRows = useMemo(() => {
314
+ const rows = [];
315
+ for (let y = 0; y < skyHeight; y++) {
316
+ const rowChars = ' '.repeat(width).split('');
317
+ const rowDims = new Array(width).fill(false);
318
+ const rowColors = new Array(width).fill(undefined);
319
+ // Place stars
320
+ for (const star of state.stars) {
321
+ if (star.y === y && star.x >= 0 && star.x < width) {
322
+ const display = star.type === 'winking' ? getWinkingStarDisplay(star) : getStaticStarDisplay(star);
323
+ if (display.char !== ' ') {
324
+ rowChars[star.x] = display.char;
325
+ rowDims[star.x] = display.dim;
326
+ }
325
327
  }
326
328
  }
327
- }
328
- // Place clouds
329
- for (const cloud of state.clouds) {
330
- const display = getCloudOpacity(cloud);
331
- if (!display.visible)
332
- continue;
333
- const cloudY = Math.round(cloud.y);
334
- const cloudX = Math.round(cloud.x);
335
- // Simple box cloud
336
- const textLen = cloud.text.length + 2;
337
- const lines = [
338
- '' + '─'.repeat(textLen) + '',
339
- '' + cloud.text + '',
340
- '╰' + '─'.repeat(textLen) + '╯',
341
- ];
342
- const lineIdx = y - cloudY;
343
- if (lineIdx >= 0 && lineIdx < 3) {
344
- const line = lines[lineIdx];
345
- for (let lx = 0; lx < line.length; lx++) {
346
- const cellX = cloudX + lx;
347
- if (cellX >= 0 && cellX < width) {
348
- rowChars[cellX] = line[lx];
349
- rowDims[cellX] = display.dim;
350
- rowColors[cellX] = '#87CEEB';
329
+ // Place clouds
330
+ for (const cloud of state.clouds) {
331
+ const display = getCloudOpacity(cloud);
332
+ if (!display.visible)
333
+ continue;
334
+ const cloudY = Math.round(cloud.y);
335
+ const cloudX = Math.round(cloud.x);
336
+ // Simple box cloud
337
+ const textLen = cloud.text.length + 2;
338
+ const lines = [
339
+ '╭' + '─'.repeat(textLen) + '╮',
340
+ '' + cloud.text + '',
341
+ '' + '─'.repeat(textLen) + '',
342
+ ];
343
+ const lineIdx = y - cloudY;
344
+ if (lineIdx >= 0 && lineIdx < 3) {
345
+ const line = lines[lineIdx];
346
+ for (let lx = 0; lx < line.length; lx++) {
347
+ const cellX = cloudX + lx;
348
+ if (cellX >= 0 && cellX < width) {
349
+ rowChars[cellX] = line[lx];
350
+ rowDims[cellX] = display.dim;
351
+ rowColors[cellX] = '#87CEEB';
352
+ }
351
353
  }
352
354
  }
353
355
  }
354
- }
355
- // Build segments for efficient rendering
356
- const segments = [];
357
- let seg = { text: rowChars[0], dim: rowDims[0], color: rowColors[0] };
358
- for (let x = 1; x < width; x++) {
359
- if (rowDims[x] === seg.dim && rowColors[x] === seg.color) {
360
- seg.text += rowChars[x];
361
- }
362
- else {
363
- segments.push(seg);
364
- seg = { text: rowChars[x], dim: rowDims[x], color: rowColors[x] };
356
+ // Build segments for efficient rendering - join consecutive same-style chars
357
+ const segments = [];
358
+ let seg = { text: rowChars[0], dim: rowDims[0], color: rowColors[0] };
359
+ for (let x = 1; x < width; x++) {
360
+ if (rowDims[x] === seg.dim && rowColors[x] === seg.color) {
361
+ seg.text += rowChars[x];
362
+ }
363
+ else {
364
+ segments.push(seg);
365
+ seg = { text: rowChars[x], dim: rowDims[x], color: rowColors[x] };
366
+ }
365
367
  }
368
+ segments.push(seg);
369
+ rows.push(_jsx(Text, { children: segments.map((s, i) => (_jsx(Text, { color: s.color || 'white', dimColor: s.dim, children: s.text }, `${y}-${i}`))) }, y));
366
370
  }
367
- segments.push(seg);
368
- skyRows.push(_jsx(Text, { children: segments.map((s, i) => (_jsx(Text, { color: s.color || 'white', dimColor: s.dim, children: s.text }, i))) }, y));
369
- }
370
- // Render landscape with cacti and ground
371
- const renderLandscape = () => {
371
+ return rows;
372
+ }, [state.stars, state.clouds, width, skyHeight, getWinkingStarDisplay, getStaticStarDisplay, getCloudOpacity]);
373
+ // Memoized landscape rendering - only changes when cacti/ground change
374
+ const landscapeRows = useMemo(() => {
372
375
  const GROUND_LINE_Y = 5; // Ground line position in landscape
373
376
  // Create ground grid
374
377
  const ground = groundBase.map(row => [...row]);
@@ -433,9 +436,9 @@ export function NightSkyView({ onExit }) {
433
436
  }
434
437
  if (currentText)
435
438
  segments.push({ text: currentText, color: currentColor, dim: currentDim });
436
- rows.push(_jsx(Text, { children: segments.map((s, i) => (_jsx(Text, { color: s.color, dimColor: s.dim, children: s.text }, i))) }, `land-${y}`));
439
+ rows.push(_jsx(Text, { children: segments.map((s, i) => (_jsx(Text, { color: s.color, dimColor: s.dim, children: s.text }, `land-${y}-${i}`))) }, `land-${y}`));
437
440
  }
438
441
  return rows;
439
- };
440
- return (_jsxs(Box, { flexDirection: "column", height: height, children: [_jsx(Box, { flexDirection: "column", children: skyRows }), _jsx(Box, { flexDirection: "column", children: renderLandscape() })] }));
442
+ }, [cacti, groundBase, width]);
443
+ return (_jsxs(Box, { flexDirection: "column", height: height, children: [_jsx(Box, { flexDirection: "column", children: skyRows }), _jsx(Box, { flexDirection: "column", children: landscapeRows })] }));
441
444
  }
@@ -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';
@@ -10,6 +10,69 @@ import { getWindowStatus } from '../lib/tmux.js';
10
10
  import { detectGitInfo, detectRemoteGitInfo } from '../lib/git.js';
11
11
  import { detectLivestockConfig } from '../lib/livestock.js';
12
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
+ }
13
76
  export function ProjectContext({ project, barns, windows, onBack, onNewClaude, onSelectWindow, onSelectLivestock, onOpenLivestockSession, onUpdateProject, onDeleteProject, onOpenWiki, onOpenIssues, }) {
14
77
  const [focusedPanel, setFocusedPanel] = useState('livestock');
15
78
  const [mode, setMode] = useState('normal');
@@ -18,6 +81,8 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
18
81
  const [editPath, setEditPath] = useState(project.path);
19
82
  const [editSummary, setEditSummary] = useState(project.summary || '');
20
83
  const [editColor, setEditColor] = useState(project.color || '');
84
+ const [editGradientSpread, setEditGradientSpread] = useState(project.gradientSpread ?? 5);
85
+ const [editGradientInverted, setEditGradientInverted] = useState(project.gradientInverted ?? false);
21
86
  // New livestock form state
22
87
  const [newLivestockName, setNewLivestockName] = useState('');
23
88
  const [newLivestockPath, setNewLivestockPath] = useState('');
@@ -34,6 +99,40 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
34
99
  const [detectedGit, setDetectedGit] = useState(null);
35
100
  // Framework detection for config suggestions
36
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' });
37
136
  // Filter windows to this project (by name prefix)
38
137
  const projectWindows = windows.filter((w) => w.index > 0 && w.name.startsWith(project.name));
39
138
  // Create a map from display number (1-9) to window for quick access
@@ -48,6 +147,8 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
48
147
  setEditPath(project.path);
49
148
  setEditSummary(project.summary || '');
50
149
  setEditColor(project.color || '');
150
+ setEditGradientSpread(project.gradientSpread ?? 5);
151
+ setEditGradientInverted(project.gradientInverted ?? false);
51
152
  setMode('edit-name');
52
153
  };
53
154
  const cancelEdit = () => {
@@ -62,6 +163,8 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
62
163
  path: editPath,
63
164
  summary: editSummary || undefined,
64
165
  color: editColor || undefined,
166
+ gradientSpread: editGradientSpread,
167
+ gradientInverted: editGradientInverted,
65
168
  });
66
169
  setMode('normal');
67
170
  }
@@ -222,20 +325,26 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
222
325
  });
223
326
  // Edit mode screens
224
327
  if (mode === 'edit-name') {
225
- 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" }) })] })] }));
226
329
  }
227
330
  if (mode === 'edit-path') {
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: [_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" }) })] })] }));
229
332
  }
230
333
  if (mode === 'edit-summary') {
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: "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)" }) })] })] }));
232
335
  }
233
336
  if (mode === 'edit-color') {
234
- 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" })] })] })] }));
235
344
  }
236
345
  // Add livestock flow: name → barn → path (with auto-detect)
237
346
  if (mode === 'add-livestock-name') {
238
- 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: () => {
239
348
  if (newLivestockName.trim()) {
240
349
  setMode('add-livestock-barn');
241
350
  }
@@ -253,7 +362,7 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
253
362
  meta: `${b.user}@${b.host}`,
254
363
  })),
255
364
  ];
256
- 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) => {
257
366
  if (item.id === '__local__') {
258
367
  setSelectedBarn(null);
259
368
  }
@@ -268,7 +377,7 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
268
377
  const locationLabel = selectedBarn
269
378
  ? `on ${selectedBarn.name} (${selectedBarn.host})`
270
379
  : 'local';
271
- 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" }) })] })] }));
272
381
  }
273
382
  if (mode === 'add-livestock-log-path') {
274
383
  const locationLabel = selectedBarn
@@ -277,13 +386,13 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
277
386
  const frameworkLabel = detectedConfig?.framework && detectedConfig.framework !== 'unknown'
278
387
  ? ` (${detectedConfig.framework} detected)`
279
388
  : '';
280
- 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" }) })] })] }));
281
390
  }
282
391
  if (mode === 'add-livestock-env-path') {
283
392
  const locationLabel = selectedBarn
284
393
  ? `on ${selectedBarn.name} (${selectedBarn.host})`
285
394
  : 'local';
286
- 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" }) })] })] }));
287
396
  }
288
397
  if (mode === 'delete-project-confirm') {
289
398
  const handleDeleteConfirm = () => {
@@ -295,7 +404,7 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
295
404
  }
296
405
  // Delete livestock confirmation
297
406
  if (mode === 'delete-livestock-confirm' && deleteLivestockTarget) {
298
- 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" })] })] })] }));
299
408
  }
300
409
  // Get livestock with resolved barn info
301
410
  const livestockWithBarns = (project.livestock || []).map((livestock) => ({
@@ -308,17 +417,28 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
308
417
  status: 'active', // TODO: actual health check
309
418
  meta: livestock.path,
310
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
+ };
311
426
  // Use display numbers (1-9) instead of tmux window index
312
- const sessionItems = projectWindows.map((w, i) => ({
313
- id: String(w.index),
314
- label: `[${i + 1}] ${w.name.replace(`${project.name}-`, '')}`,
315
- status: w.active ? 'active' : 'inactive',
316
- meta: getWindowStatus(w),
317
- }));
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
+ });
318
438
  // Panel-specific hints (page-level hotkeys like c/w/i are in BottomBar)
319
439
  const livestockHints = '[s] shell [n] new [d] delete';
320
440
  const sessionHints = '1-9 switch';
321
- 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) => {
322
442
  const found = livestockWithBarns.find((l) => l.livestock.name === item.id);
323
443
  if (found) {
324
444
  onSelectLivestock(found.livestock, found.barn);
@@ -70,17 +70,10 @@ export function WikiView({ project, onBack, onUpdateProject }) {
70
70
  setFocusedPanel((prev) => (prev === 'sections' ? 'content' : 'sections'));
71
71
  return;
72
72
  }
73
- // Only handle section navigation when sections panel is focused
73
+ // Only handle section operations when sections panel is focused
74
+ // Note: j/k navigation is handled by the List component
74
75
  if (focusedPanel !== 'sections')
75
76
  return;
76
- if (input === 'j' || key.downArrow) {
77
- setSelectedIndex((i) => Math.min(i + 1, wiki.length - 1));
78
- return;
79
- }
80
- if (input === 'k' || key.upArrow) {
81
- setSelectedIndex((i) => Math.max(i - 1, 0));
82
- return;
83
- }
84
77
  if (input === 'n') {
85
78
  setEditTitle('');
86
79
  setEditContent('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colmbus72/yeehaw",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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
  ],