@colmbus72/yeehaw 0.4.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/dist/app.js +34 -3
- package/dist/components/Header.js +16 -1
- package/dist/mcp-server.js +57 -4
- package/dist/views/GlobalDashboard.d.ts +2 -1
- package/dist/views/GlobalDashboard.js +7 -2
- package/dist/views/NightSkyView.js +66 -63
- package/dist/views/WikiView.js +2 -9
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -83,6 +83,7 @@ export function App() {
|
|
|
83
83
|
const [showHelp, setShowHelp] = useState(false);
|
|
84
84
|
const [error, setError] = useState(null);
|
|
85
85
|
const [pendingGo, setPendingGo] = useState(false); // For g+number sequence
|
|
86
|
+
const [isChildInputMode, setIsChildInputMode] = useState(false); // Track when child views have text input active
|
|
86
87
|
// Get terminal height for full-height layout
|
|
87
88
|
const terminalHeight = stdout?.rows || 24;
|
|
88
89
|
// Check tmux availability
|
|
@@ -95,6 +96,36 @@ export function App() {
|
|
|
95
96
|
ensureCorrectStatusBar();
|
|
96
97
|
}
|
|
97
98
|
}, [view.type]);
|
|
99
|
+
// Hot-reload: sync view state when projects/barns change (e.g., from MCP server updates)
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
setView((currentView) => {
|
|
102
|
+
// Update project data in views that contain a project
|
|
103
|
+
if (currentView.type === 'project' || currentView.type === 'wiki' || currentView.type === 'issues') {
|
|
104
|
+
const freshProject = projects.find((p) => p.name === currentView.project.name);
|
|
105
|
+
if (freshProject && freshProject !== currentView.project) {
|
|
106
|
+
return { ...currentView, project: freshProject };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Update livestock detail view
|
|
110
|
+
if (currentView.type === 'livestock' || currentView.type === 'logs') {
|
|
111
|
+
const freshProject = projects.find((p) => p.name === currentView.project.name);
|
|
112
|
+
if (freshProject && freshProject !== currentView.project) {
|
|
113
|
+
const freshLivestock = (freshProject.livestock || []).find((l) => l.name === currentView.livestock.name);
|
|
114
|
+
if (freshLivestock) {
|
|
115
|
+
return { ...currentView, project: freshProject, livestock: freshLivestock };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Update barn data in barn view
|
|
120
|
+
if (currentView.type === 'barn') {
|
|
121
|
+
const freshBarn = barns.find((b) => b.name === currentView.barn.name);
|
|
122
|
+
if (freshBarn && freshBarn !== currentView.barn) {
|
|
123
|
+
return { ...currentView, barn: freshBarn };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return currentView;
|
|
127
|
+
});
|
|
128
|
+
}, [projects, barns]);
|
|
98
129
|
const handleSelectProject = useCallback((project) => {
|
|
99
130
|
setView({ type: 'project', project });
|
|
100
131
|
updateStatusBar(project.name);
|
|
@@ -357,8 +388,8 @@ export function App() {
|
|
|
357
388
|
setPendingGo(true);
|
|
358
389
|
return;
|
|
359
390
|
}
|
|
360
|
-
// v: Enter night sky visualizer (only from global dashboard
|
|
361
|
-
if (input === 'v' && view.type === 'global') {
|
|
391
|
+
// v: Enter night sky visualizer (only from global dashboard when not in text input mode)
|
|
392
|
+
if (input === 'v' && view.type === 'global' && !isChildInputMode) {
|
|
362
393
|
handleEnterNightSky();
|
|
363
394
|
return;
|
|
364
395
|
}
|
|
@@ -370,7 +401,7 @@ export function App() {
|
|
|
370
401
|
}
|
|
371
402
|
switch (view.type) {
|
|
372
403
|
case 'global':
|
|
373
|
-
return (_jsx(GlobalDashboard, { projects: projects, barns: barns, windows: windows, versionInfo: versionInfo, onSelectProject: handleSelectProject, onSelectBarn: handleSelectBarn, onSelectWindow: handleSelectWindow, onNewClaude: handleNewClaude, onCreateProject: handleCreateProject, onCreateBarn: handleCreateBarn, onSshToBarn: handleSshToBarn }));
|
|
404
|
+
return (_jsx(GlobalDashboard, { projects: projects, barns: barns, windows: windows, versionInfo: versionInfo, onSelectProject: handleSelectProject, onSelectBarn: handleSelectBarn, onSelectWindow: handleSelectWindow, onNewClaude: handleNewClaude, onCreateProject: handleCreateProject, onCreateBarn: handleCreateBarn, onSshToBarn: handleSshToBarn, onInputModeChange: setIsChildInputMode }));
|
|
374
405
|
case 'project':
|
|
375
406
|
return (_jsx(ProjectContext, { project: view.project, barns: barns, windows: windows, onBack: handleBack, onNewClaude: handleNewClaude, onSelectWindow: handleSelectWindow, onSelectLivestock: (livestock, barn) => handleOpenLivestockDetail(view.project, livestock, 'project'), onOpenLivestockSession: (livestock, barn) => handleOpenLivestockSession(livestock, barn, view.project.name), onUpdateProject: handleUpdateProject, onDeleteProject: handleDeleteProject, onOpenWiki: () => handleOpenWiki(view.project), onOpenIssues: () => handleOpenIssues(view.project) }));
|
|
376
407
|
case 'barn':
|
|
@@ -2,6 +2,21 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import figlet from 'figlet';
|
|
5
|
+
// Compare semver versions - returns true if latest > current
|
|
6
|
+
function isNewerVersion(latest, current) {
|
|
7
|
+
const parseVersion = (v) => v.split('.').map(n => parseInt(n, 10) || 0);
|
|
8
|
+
const [lMajor, lMinor, lPatch] = parseVersion(latest);
|
|
9
|
+
const [cMajor, cMinor, cPatch] = parseVersion(current);
|
|
10
|
+
if (lMajor > cMajor)
|
|
11
|
+
return true;
|
|
12
|
+
if (lMajor < cMajor)
|
|
13
|
+
return false;
|
|
14
|
+
if (lMinor > cMinor)
|
|
15
|
+
return true;
|
|
16
|
+
if (lMinor < cMinor)
|
|
17
|
+
return false;
|
|
18
|
+
return lPatch > cPatch;
|
|
19
|
+
}
|
|
5
20
|
// Convert hex to RGB
|
|
6
21
|
function hexToRgb(hex) {
|
|
7
22
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
@@ -89,5 +104,5 @@ export function Header({ text, subtitle, summary, color, gradientSpread, gradien
|
|
|
89
104
|
const showTumbleweed = text.toLowerCase() === 'yeehaw';
|
|
90
105
|
// Vertically center tumbleweed next to ASCII art
|
|
91
106
|
const tumbleweedTopPadding = Math.max(0, Math.floor((lines.length - TUMBLEWEED.length) / 2));
|
|
92
|
-
return (_jsxs(Box, { flexDirection: "column", paddingTop: 1, paddingLeft: 2, children: [_jsxs(Box, { flexDirection: "row", children: [showTumbleweed && (_jsxs(Box, { flexDirection: "column", marginRight: 2, children: [Array(tumbleweedTopPadding).fill(null).map((_, i) => (_jsx(Text, { children: " " }, `pad-${i}`))), TUMBLEWEED.map((line, i) => (_jsx(Text, { color: TUMBLEWEED_COLOR, bold: true, children: line }, `tumbleweed-${i}`)))] })), _jsx(Box, { flexDirection: "column", children: lines.map((line, i) => (_jsx(Text, { color: gradientColors[i], children: line }, i))) }), versionInfo && showTumbleweed && (_jsx(Box, { marginLeft: 2, paddingRight: 1, alignItems: "flex-end", flexGrow: 1, justifyContent: "flex-end", children: versionInfo.latest && versionInfo.latest
|
|
107
|
+
return (_jsxs(Box, { flexDirection: "column", paddingTop: 1, paddingLeft: 2, children: [_jsxs(Box, { flexDirection: "row", children: [showTumbleweed && (_jsxs(Box, { flexDirection: "column", marginRight: 2, children: [Array(tumbleweedTopPadding).fill(null).map((_, i) => (_jsx(Text, { children: " " }, `pad-${i}`))), TUMBLEWEED.map((line, i) => (_jsx(Text, { color: TUMBLEWEED_COLOR, bold: true, children: line }, `tumbleweed-${i}`)))] })), _jsx(Box, { flexDirection: "column", children: lines.map((line, i) => (_jsx(Text, { color: gradientColors[i], children: line }, i))) }), versionInfo && showTumbleweed && (_jsx(Box, { marginLeft: 2, paddingRight: 1, alignItems: "flex-end", flexGrow: 1, justifyContent: "flex-end", children: versionInfo.latest && isNewerVersion(versionInfo.latest, versionInfo.current) ? (_jsxs(Text, { children: [_jsxs(Text, { dimColor: true, children: ["v", versionInfo.current] }), _jsx(Text, { dimColor: true, children: " \u2192 " }), _jsxs(Text, { color: "yellow", children: ["v", versionInfo.latest] })] })) : (_jsxs(Text, { dimColor: true, children: ["v", versionInfo.current] })) }))] }), (subtitle || summary) && (_jsxs(Box, { gap: 2, children: [subtitle && _jsx(Text, { dimColor: true, children: subtitle }), summary && _jsxs(Text, { color: "gray", children: ["- ", summary] })] }))] }));
|
|
93
108
|
}
|
package/dist/mcp-server.js
CHANGED
|
@@ -321,7 +321,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
321
321
|
// Wiki tools
|
|
322
322
|
{
|
|
323
323
|
name: 'get_wiki',
|
|
324
|
-
description: 'Get all wiki
|
|
324
|
+
description: 'Get all wiki section titles for a project (use get_wiki_section to fetch content)',
|
|
325
325
|
inputSchema: {
|
|
326
326
|
type: 'object',
|
|
327
327
|
properties: {
|
|
@@ -330,6 +330,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
330
330
|
required: ['project'],
|
|
331
331
|
},
|
|
332
332
|
},
|
|
333
|
+
{
|
|
334
|
+
name: 'get_wiki_section',
|
|
335
|
+
description: 'Get the content of a specific wiki section',
|
|
336
|
+
inputSchema: {
|
|
337
|
+
type: 'object',
|
|
338
|
+
properties: {
|
|
339
|
+
project: { type: 'string', description: 'Project name' },
|
|
340
|
+
title: { type: 'string', description: 'Section title' },
|
|
341
|
+
},
|
|
342
|
+
required: ['project', 'title'],
|
|
343
|
+
},
|
|
344
|
+
},
|
|
333
345
|
{
|
|
334
346
|
name: 'add_wiki_section',
|
|
335
347
|
description: 'Add a new wiki section to a project',
|
|
@@ -379,11 +391,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
379
391
|
// Project operations
|
|
380
392
|
case 'list_projects': {
|
|
381
393
|
const projects = loadProjects();
|
|
394
|
+
// Return simplified project data to reduce context usage
|
|
395
|
+
const simplified = projects.map((p) => ({
|
|
396
|
+
name: p.name,
|
|
397
|
+
path: p.path,
|
|
398
|
+
summary: p.summary,
|
|
399
|
+
color: p.color,
|
|
400
|
+
livestock: (p.livestock || []).map((l) => ({
|
|
401
|
+
name: l.name,
|
|
402
|
+
path: l.path,
|
|
403
|
+
barn: l.barn,
|
|
404
|
+
})),
|
|
405
|
+
}));
|
|
382
406
|
return {
|
|
383
407
|
content: [
|
|
384
408
|
{
|
|
385
409
|
type: 'text',
|
|
386
|
-
text: JSON.stringify(
|
|
410
|
+
text: JSON.stringify(simplified, null, 2),
|
|
387
411
|
},
|
|
388
412
|
],
|
|
389
413
|
};
|
|
@@ -397,8 +421,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
397
421
|
isError: true,
|
|
398
422
|
};
|
|
399
423
|
}
|
|
424
|
+
// Return project with wiki section titles only (not full content)
|
|
425
|
+
// Use get_wiki_section to fetch individual section content
|
|
426
|
+
const projectWithWikiTitles = {
|
|
427
|
+
...project,
|
|
428
|
+
wiki: (project.wiki || []).map((s) => ({ title: s.title })),
|
|
429
|
+
};
|
|
400
430
|
return {
|
|
401
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
431
|
+
content: [{ type: 'text', text: JSON.stringify(projectWithWikiTitles, null, 2) }],
|
|
402
432
|
};
|
|
403
433
|
}
|
|
404
434
|
case 'create_project': {
|
|
@@ -712,8 +742,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
712
742
|
isError: true,
|
|
713
743
|
};
|
|
714
744
|
}
|
|
745
|
+
// Return only section titles to reduce context usage
|
|
746
|
+
const titles = (project.wiki || []).map((s) => ({ title: s.title }));
|
|
747
|
+
return {
|
|
748
|
+
content: [{ type: 'text', text: JSON.stringify(titles, null, 2) }],
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
case 'get_wiki_section': {
|
|
752
|
+
const projectName = requireString(args, 'project');
|
|
753
|
+
const title = requireString(args, 'title');
|
|
754
|
+
const project = loadProject(projectName);
|
|
755
|
+
if (!project) {
|
|
756
|
+
return {
|
|
757
|
+
content: [{ type: 'text', text: `Project not found: ${projectName}` }],
|
|
758
|
+
isError: true,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
const section = (project.wiki || []).find((s) => s.title === title);
|
|
762
|
+
if (!section) {
|
|
763
|
+
return {
|
|
764
|
+
content: [{ type: 'text', text: `Wiki section not found: ${title}` }],
|
|
765
|
+
isError: true,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
715
768
|
return {
|
|
716
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
769
|
+
content: [{ type: 'text', text: JSON.stringify(section, null, 2) }],
|
|
717
770
|
};
|
|
718
771
|
}
|
|
719
772
|
case 'add_wiki_section': {
|
|
@@ -15,6 +15,7 @@ interface GlobalDashboardProps {
|
|
|
15
15
|
onCreateProject: (name: string, path: string) => void;
|
|
16
16
|
onCreateBarn: (barn: Barn) => void;
|
|
17
17
|
onSshToBarn: (barn: Barn) => void;
|
|
18
|
+
onInputModeChange?: (isInputMode: boolean) => void;
|
|
18
19
|
}
|
|
19
|
-
export declare function GlobalDashboard({ projects, barns, windows, versionInfo, onSelectProject, onSelectBarn, onSelectWindow, onNewClaude, onCreateProject, onCreateBarn, onSshToBarn, }: GlobalDashboardProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export declare function GlobalDashboard({ projects, barns, windows, versionInfo, onSelectProject, onSelectBarn, onSelectWindow, onNewClaude, onCreateProject, onCreateBarn, onSshToBarn, onInputModeChange, }: GlobalDashboardProps): import("react/jsx-runtime").JSX.Element;
|
|
20
21
|
export {};
|
|
@@ -12,9 +12,14 @@ import { isLocalBarn } from '../lib/config.js';
|
|
|
12
12
|
function countSessionsForProject(projectName, windows) {
|
|
13
13
|
return windows.filter((w) => w.name.startsWith(projectName)).length;
|
|
14
14
|
}
|
|
15
|
-
export function GlobalDashboard({ projects, barns, windows, versionInfo, onSelectProject, onSelectBarn, onSelectWindow, onNewClaude, onCreateProject, onCreateBarn, onSshToBarn, }) {
|
|
15
|
+
export function GlobalDashboard({ projects, barns, windows, versionInfo, onSelectProject, onSelectBarn, onSelectWindow, onNewClaude, onCreateProject, onCreateBarn, onSshToBarn, onInputModeChange, }) {
|
|
16
16
|
const [focusedPanel, setFocusedPanel] = useState('projects');
|
|
17
|
-
const [mode,
|
|
17
|
+
const [mode, setModeInternal] = useState('normal');
|
|
18
|
+
// Wrapper to notify parent when input mode changes
|
|
19
|
+
const setMode = (newMode) => {
|
|
20
|
+
setModeInternal(newMode);
|
|
21
|
+
onInputModeChange?.(newMode !== 'normal');
|
|
22
|
+
};
|
|
18
23
|
// New project form state
|
|
19
24
|
const [newProjectName, setNewProjectName] = useState('');
|
|
20
25
|
const [newProjectPath, setNewProjectPath] = useState('');
|
|
@@ -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
|
}
|
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('');
|