tycono 0.3.14-beta.17 → 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.17",
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;
@@ -162,7 +185,12 @@ const PanelModeInner: React.FC<PanelModeProps> = ({
162
185
  if (line) rightContentLines.push(line.slice(0, rightWidth));
163
186
  }
164
187
  if (rightContentLines.length === 0) {
165
- rightContentLines.push(waveId ? `Waiting... (total ${events.length} events)` : 'No active stream. Type a directive to start.');
188
+ if (selectedRoleId && events.length > 0) {
189
+ rightContentLines.push(`No events for ${selectedRoleId} (${events.length} total)`);
190
+ rightContentLines.push('Press Enter to show all roles');
191
+ } else {
192
+ rightContentLines.push(waveId ? `Waiting for events... (${events.length} in buffer)` : 'No active stream. Type a directive to start.');
193
+ }
166
194
  }
167
195
  } else if (rightTab === 'info') {
168
196
  rightContentLines.push(`Wave: ${focusedWave?.waveId ?? 'none'}`);
@@ -170,29 +198,41 @@ const PanelModeInner: React.FC<PanelModeProps> = ({
170
198
  rightContentLines.push(`Sessions: ${waveSessionCount} Events: ${events.length}`);
171
199
  rightContentLines.push(`Stream: ${streamStatus}`);
172
200
  } else if (rightTab === 'docs') {
173
- // Docs: scan .md files from COMPANY_ROOT
201
+ // Docs: scan .md files with j/k scroll + Enter to open
174
202
  try {
175
203
  const skip = new Set(['.git', 'node_modules', '.tycono', '.worktrees', 'dist', '.claude', '.obsidian']);
176
204
  const mdFiles: string[] = [];
205
+ const mdPaths: string[] = []; // full paths for vim
177
206
  const walk = (dir: string, depth: number) => {
178
- if (depth > 3 || mdFiles.length > 50) return;
207
+ if (depth > 3 || mdFiles.length > 200) return;
179
208
  try {
180
209
  for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
181
210
  if (skip.has(e.name)) continue;
182
211
  const full = path.join(dir, e.name);
183
212
  if (e.isDirectory()) walk(full, depth + 1);
184
- 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
+ }
185
217
  }
186
218
  } catch {}
187
219
  };
188
220
  walk(companyRoot, 0);
189
221
  mdFiles.sort();
190
- const maxDocs = Math.max(5, termHeight - 12);
191
- rightContentLines.push(`${mdFiles.length} documents`);
192
- for (const f of mdFiles.slice(0, maxDocs)) {
193
- 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)}`);
194
235
  }
195
- if (mdFiles.length > maxDocs) rightContentLines.push(` ... +${mdFiles.length - maxDocs} more`);
196
236
  } catch {
197
237
  rightContentLines.push('Cannot scan documents');
198
238
  }
@@ -254,7 +294,7 @@ const PanelModeInner: React.FC<PanelModeProps> = ({
254
294
  <Text color="gray">{sep}</Text>
255
295
  <Text>
256
296
  {waveTabs ? <Text color="gray">{waveTabs + ' '}</Text> : null}
257
- <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>
258
298
  {waves.length > 1 ? <Text color="gray" dimColor>{'[1-9] wave '}</Text> : null}
259
299
  <Text color="gray" dimColor>{'[Esc] back'}</Text>
260
300
  </Text>