@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.
- package/claude-plugin/.claude-plugin/plugin.json +7 -0
- package/claude-plugin/skills/yeehaw-project-setup/SKILL.md +129 -0
- package/claude-plugin/skills/yeehaw-project-setup/references/color-discovery.md +170 -0
- package/claude-plugin/skills/yeehaw-project-setup/references/wiki-templates.md +266 -0
- package/dist/app.js +54 -11
- package/dist/components/Header.d.ts +4 -1
- package/dist/components/Header.js +48 -23
- package/dist/components/LivestockHeader.js +6 -6
- package/dist/components/SplashScreen.d.ts +5 -0
- package/dist/components/SplashScreen.js +178 -0
- package/dist/index.js +2 -5
- package/dist/lib/tmux.js +7 -2
- package/dist/mcp-server.js +57 -4
- package/dist/types.d.ts +2 -0
- package/dist/views/GlobalDashboard.d.ts +2 -1
- package/dist/views/GlobalDashboard.js +23 -18
- package/dist/views/LivestockDetailView.d.ts +1 -1
- package/dist/views/LivestockDetailView.js +27 -45
- package/dist/views/NightSkyView.js +66 -63
- package/dist/views/ProjectContext.js +138 -18
- package/dist/views/WikiView.js +2 -9
- package/package.json +2 -1
|
@@ -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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
313
|
-
const skyRows =
|
|
314
|
-
|
|
315
|
-
let
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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);
|
package/dist/views/WikiView.js
CHANGED
|
@@ -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
|
|
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
|
+
"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
|
],
|