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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tycono",
3
- "version": "0.3.14-beta.18",
3
+ "version": "0.3.14-beta.19",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- if (input === 'k' || key.upArrow) { onMove('up'); return; }
127
- if (input === 'j' || key.downArrow) { onMove('down'); return; }
128
- if (key.return) { onSelect(); return; }
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 from COMPANY_ROOT
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 > 50) return;
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')) mdFiles.push(full.replace(companyRoot + '/', ''));
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
- const maxDocs = Math.max(5, termHeight - 12);
196
- rightContentLines.push(`${mdFiles.length} documents`);
197
- for (const f of mdFiles.slice(0, maxDocs)) {
198
- rightContentLines.push(` ${f.slice(0, rightWidth - 4)}`);
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>