tycono 0.3.14-beta.18 → 0.3.14-beta.19
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/package.json +1 -1
- package/src/tui/components/PanelMode.tsx +48 -13
package/package.json
CHANGED
|
@@ -91,6 +91,7 @@ const PanelModeInner: React.FC<PanelModeProps> = ({
|
|
|
91
91
|
}) => {
|
|
92
92
|
const [termHeight, setTermHeight] = useState(process.stdout.rows || 30);
|
|
93
93
|
const [rightTab, setRightTab] = useState<RightTab>('stream');
|
|
94
|
+
const [docsIndex, setDocsIndex] = useState(0);
|
|
94
95
|
|
|
95
96
|
useEffect(() => {
|
|
96
97
|
const fn = () => setTermHeight(process.stdout.rows || 30);
|
|
@@ -123,9 +124,30 @@ const PanelModeInner: React.FC<PanelModeProps> = ({
|
|
|
123
124
|
if (idx < tabs.length - 1) setRightTab(tabs[idx + 1]);
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
126
|
-
|
|
127
|
-
if (input === '
|
|
128
|
-
|
|
127
|
+
// j/k context-dependent
|
|
128
|
+
if (input === 'k' || key.upArrow) {
|
|
129
|
+
if (rightTab === 'docs') { setDocsIndex(i => Math.max(0, i - 1)); }
|
|
130
|
+
else { onMove('up'); }
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (input === 'j' || key.downArrow) {
|
|
134
|
+
if (rightTab === 'docs') { setDocsIndex(i => i + 1); } // capped later by docsList length
|
|
135
|
+
else { onMove('down'); }
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (key.return) {
|
|
139
|
+
if (rightTab === 'docs' && selectedDocPath) {
|
|
140
|
+
// Open in vim
|
|
141
|
+
try {
|
|
142
|
+
const { execSync } = require('child_process');
|
|
143
|
+
const editor = process.env.EDITOR || 'vim';
|
|
144
|
+
execSync(`${editor} "${selectedDocPath}"`, { stdio: 'inherit' });
|
|
145
|
+
} catch { /* ignore */ }
|
|
146
|
+
} else {
|
|
147
|
+
onSelect();
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
129
151
|
// Wave switch 1-9
|
|
130
152
|
const num = parseInt(input, 10);
|
|
131
153
|
if (num >= 1 && num <= 9 && num <= waves.length) {
|
|
@@ -151,8 +173,9 @@ const PanelModeInner: React.FC<PanelModeProps> = ({
|
|
|
151
173
|
})),
|
|
152
174
|
];
|
|
153
175
|
|
|
154
|
-
// === Build right column: Stream/Info ===
|
|
176
|
+
// === Build right column: Stream/Info/Docs ===
|
|
155
177
|
const rightContentLines: string[] = [];
|
|
178
|
+
let selectedDocPath: string | null = null;
|
|
156
179
|
if (rightTab === 'stream') {
|
|
157
180
|
const maxEv = Math.max(5, termHeight - 10);
|
|
158
181
|
const filtered = selectedRoleId ? events.filter(e => e.roleId === selectedRoleId) : events;
|
|
@@ -175,29 +198,41 @@ const PanelModeInner: React.FC<PanelModeProps> = ({
|
|
|
175
198
|
rightContentLines.push(`Sessions: ${waveSessionCount} Events: ${events.length}`);
|
|
176
199
|
rightContentLines.push(`Stream: ${streamStatus}`);
|
|
177
200
|
} else if (rightTab === 'docs') {
|
|
178
|
-
// Docs: scan .md files
|
|
201
|
+
// Docs: scan .md files with j/k scroll + Enter to open
|
|
179
202
|
try {
|
|
180
203
|
const skip = new Set(['.git', 'node_modules', '.tycono', '.worktrees', 'dist', '.claude', '.obsidian']);
|
|
181
204
|
const mdFiles: string[] = [];
|
|
205
|
+
const mdPaths: string[] = []; // full paths for vim
|
|
182
206
|
const walk = (dir: string, depth: number) => {
|
|
183
|
-
if (depth > 3 || mdFiles.length >
|
|
207
|
+
if (depth > 3 || mdFiles.length > 200) return;
|
|
184
208
|
try {
|
|
185
209
|
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
186
210
|
if (skip.has(e.name)) continue;
|
|
187
211
|
const full = path.join(dir, e.name);
|
|
188
212
|
if (e.isDirectory()) walk(full, depth + 1);
|
|
189
|
-
else if (e.name.endsWith('.md'))
|
|
213
|
+
else if (e.name.endsWith('.md')) {
|
|
214
|
+
mdFiles.push(full.replace(companyRoot + '/', ''));
|
|
215
|
+
mdPaths.push(full);
|
|
216
|
+
}
|
|
190
217
|
}
|
|
191
218
|
} catch {}
|
|
192
219
|
};
|
|
193
220
|
walk(companyRoot, 0);
|
|
194
221
|
mdFiles.sort();
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
222
|
+
mdPaths.sort();
|
|
223
|
+
// Cap docsIndex
|
|
224
|
+
const cappedIdx = Math.min(docsIndex, mdFiles.length - 1);
|
|
225
|
+
if (cappedIdx !== docsIndex) setDocsIndex(Math.max(0, cappedIdx));
|
|
226
|
+
selectedDocPath = mdPaths[cappedIdx] ?? null;
|
|
227
|
+
|
|
228
|
+
const maxVisible = Math.max(5, termHeight - 12);
|
|
229
|
+
const scrollStart = Math.max(0, Math.min(cappedIdx - 3, mdFiles.length - maxVisible));
|
|
230
|
+
rightContentLines.push(`${mdFiles.length} documents [j/k] browse [Enter] ${process.env.EDITOR || 'vim'}`);
|
|
231
|
+
for (let i = scrollStart; i < Math.min(scrollStart + maxVisible, mdFiles.length); i++) {
|
|
232
|
+
const selected = i === cappedIdx;
|
|
233
|
+
const prefix = selected ? '\u25B6 ' : ' ';
|
|
234
|
+
rightContentLines.push(`${prefix}${mdFiles[i].slice(0, rightWidth - 4)}`);
|
|
199
235
|
}
|
|
200
|
-
if (mdFiles.length > maxDocs) rightContentLines.push(` ... +${mdFiles.length - maxDocs} more`);
|
|
201
236
|
} catch {
|
|
202
237
|
rightContentLines.push('Cannot scan documents');
|
|
203
238
|
}
|
|
@@ -259,7 +294,7 @@ const PanelModeInner: React.FC<PanelModeProps> = ({
|
|
|
259
294
|
<Text color="gray">{sep}</Text>
|
|
260
295
|
<Text>
|
|
261
296
|
{waveTabs ? <Text color="gray">{waveTabs + ' '}</Text> : null}
|
|
262
|
-
<Text color="gray" dimColor>{'[h/l] tab [j/k] role [Enter] filter '}</Text>
|
|
297
|
+
<Text color="gray" dimColor>{rightTab === 'docs' ? '[h/l] tab [j/k] browse [Enter] open ' : '[h/l] tab [j/k] role [Enter] filter '}</Text>
|
|
263
298
|
{waves.length > 1 ? <Text color="gray" dimColor>{'[1-9] wave '}</Text> : null}
|
|
264
299
|
<Text color="gray" dimColor>{'[Esc] back'}</Text>
|
|
265
300
|
</Text>
|