mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 14:09:42 +02:00
feat(ui): add 20-tab support and Claude CLI settings
- Increase max tab count from 10 to 20 - Fix header to properly expand for multiple tab rows (up to 4 rows) - Add scrollbar for tabs area when many tabs exist - Add Claude CLI startup mode setting (skip permissions/normal/allowed tools) - Reorganize settings modal into sections (CLI, Paths, Features) - Fix case select dropdown to fit content width - Create optimization todos document for future work Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# Claudeman Code Optimization TODO List
|
||||
|
||||
> Generated: 2026-01-21
|
||||
> Purpose: Comprehensive optimization roadmap for future sessions
|
||||
|
||||
## Overview
|
||||
|
||||
This document contains prioritized optimization tasks identified through codebase analysis.
|
||||
Each item has specific file:line references and expected impact estimates.
|
||||
|
||||
---
|
||||
|
||||
## HIGH PRIORITY OPTIMIZATIONS
|
||||
|
||||
### 1. String Concatenation in Hot Paths
|
||||
- **Files**: `src/session.ts:460, 677-686`, `src/respawn-controller.ts:469, 826`, `src/inner-loop-tracker.ts:408`
|
||||
- **Issue**: String `+=` in terminal buffer accumulation creates GC pressure
|
||||
- **Fix**: Use array-based accumulation with periodic joins
|
||||
- **Impact**: 10-20% faster terminal streaming
|
||||
|
||||
### 2. Regex Pattern lastIndex Reset
|
||||
- **Files**: `src/inner-loop-tracker.ts:464-479`, `src/screen-manager.ts:264`
|
||||
- **Issue**: Global regex without lastIndex reset causes skip-every-other-match bug
|
||||
- **Fix**: Always reset `pattern.lastIndex = 0` before use
|
||||
- **Impact**: Correct pattern matching, eliminate intermittent bugs
|
||||
|
||||
### 3. Unbounded Buffer Growth
|
||||
- **Files**: `src/respawn-controller.ts:268-269`, `src/inner-loop-tracker.ts:257-258`
|
||||
- **Issue**: Buffers lack proper size limits or have loose limits
|
||||
- **Fix**: Add hard limits with Ring Buffer pattern
|
||||
- **Impact**: Bounded memory, prevent OOM in long sessions
|
||||
|
||||
### 4. Event Listener Management
|
||||
- **Files**: `src/respawn-controller.ts:446-455`, `src/session.ts:481-501`, `src/web/server.ts:987-1084`
|
||||
- **Issue**: Listeners created repeatedly without cleanup tracking
|
||||
- **Fix**: Use debounce/throttle utilities, consolidate timeout management
|
||||
- **Impact**: Fewer memory leaks, reduced GC sweeps
|
||||
|
||||
### 5. Task Lookup Optimization
|
||||
- **Files**: `src/task-tracker.ts:214-220, 231-234`
|
||||
- **Issue**: O(n) sequential iteration for task lookups
|
||||
- **Fix**: Add secondary indices `runningTasksByType: Map<string, Set<string>>`
|
||||
- **Impact**: O(1) lookups, faster with 1000+ tasks
|
||||
|
||||
---
|
||||
|
||||
## MEDIUM PRIORITY OPTIMIZATIONS
|
||||
|
||||
### 6. Pattern Test Result Caching
|
||||
- **Files**: `src/inner-loop-tracker.ts:529-543`
|
||||
- **Issue**: Multiple pattern tests on same data without caching
|
||||
- **Fix**: Use `.exec()` once and reuse result
|
||||
- **Impact**: 2-3x faster todo detection
|
||||
|
||||
### 7. Dynamic Terminal Batching
|
||||
- **Files**: `src/web/server.ts:46, 1350-1360`
|
||||
- **Issue**: 16ms flush interval is too long for interactive sessions
|
||||
- **Fix**: Flush immediately if batch > 1KB OR timeout 16ms
|
||||
- **Impact**: Snappier UI feel
|
||||
|
||||
### 8. Buffer Pagination
|
||||
- **Files**: `src/session.ts:314-365`, `src/web/server.ts:219-224`
|
||||
- **Issue**: Full buffer sent on every state update
|
||||
- **Fix**: Add `/api/sessions/:id/history?offset=X&limit=Y`
|
||||
- **Impact**: 10-100x faster reconnects
|
||||
|
||||
### 9. Remove Unused Regex Patterns
|
||||
- **Files**: `src/respawn-controller.ts:52-59`
|
||||
- **Issue**: ANSI_ESCAPE_PATTERN, WHITESPACE_PATTERN marked @deprecated but still compiled
|
||||
- **Fix**: Remove unused patterns
|
||||
- **Impact**: Cleaner code, slightly faster startup
|
||||
|
||||
### 10. State Serialization
|
||||
- **Files**: `src/state-store.ts:80, 196`, `src/web/server.ts:1343`
|
||||
- **Issue**: Full JSON.stringify on every save
|
||||
- **Fix**: Incremental or selective serialization
|
||||
- **Impact**: 5-10x faster state saves
|
||||
|
||||
---
|
||||
|
||||
## LOW PRIORITY OPTIMIZATIONS
|
||||
|
||||
### 11. Error Handling Consistency
|
||||
- **Files**: `src/screen-manager.ts:101-104`, `src/session.ts:726-729`, `src/web/server.ts:838-845`
|
||||
- **Issue**: Mix of try-catch and error codes
|
||||
- **Fix**: Create ErrorRegistry, standardize responses
|
||||
- **Impact**: Better debugging
|
||||
|
||||
### 12. TypeScript Strict Typing
|
||||
- **Files**: `src/task-tracker.ts:179`, `src/web/server.ts:138, 174`
|
||||
- **Issue**: Some implicit `any` types
|
||||
- **Fix**: Strong typing for all messages and payloads
|
||||
- **Impact**: Better IDE support, fewer runtime errors
|
||||
|
||||
### 13. Dead Code Removal
|
||||
- **Files**: `src/task.ts`, `src/task-queue.ts`, `src/ralph-loop.ts`
|
||||
- **Issue**: Potentially unused files
|
||||
- **Fix**: Verify imports, remove unused
|
||||
- **Impact**: Cleaner codebase
|
||||
|
||||
### 14. Console Logging
|
||||
- **Files**: Throughout `src/session.ts`, `src/respawn-controller.ts`, `src/screen-manager.ts`
|
||||
- **Issue**: Debug logs not disabled in production
|
||||
- **Fix**: Use logger abstraction with levels
|
||||
- **Impact**: Cleaner logs
|
||||
|
||||
### 15. Magic Numbers
|
||||
- **Files**: `src/session.ts:397, 428`, `src/respawn-controller.ts:614, 619`
|
||||
- **Issue**: Hardcoded timeout values
|
||||
- **Fix**: Extract to named constants
|
||||
- **Impact**: Easier tuning
|
||||
|
||||
---
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1 (Critical) - Items #1-5
|
||||
- Time: 8-12 hours
|
||||
- Expected Gain: 30% performance
|
||||
|
||||
### Phase 2 (Important) - Items #6-10
|
||||
- Time: 6-8 hours
|
||||
- Expected Gain: 15% performance
|
||||
|
||||
### Phase 3 (Polish) - Items #11-15
|
||||
- Time: 4-6 hours
|
||||
- Expected Gain: 5% + code quality
|
||||
|
||||
---
|
||||
|
||||
## Progress Tracking
|
||||
|
||||
| Item | Status | Completed By | Notes |
|
||||
|------|--------|--------------|-------|
|
||||
| 1 | ☐ Pending | | |
|
||||
| 2 | ☐ Pending | | |
|
||||
| 3 | ☐ Pending | | |
|
||||
| 4 | ☐ Pending | | |
|
||||
| 5 | ☐ Pending | | |
|
||||
| 6 | ☐ Pending | | |
|
||||
| 7 | ☐ Pending | | |
|
||||
| 8 | ☐ Pending | | |
|
||||
| 9 | ☐ Pending | | |
|
||||
| 10 | ☐ Pending | | |
|
||||
| 11 | ☐ Pending | | |
|
||||
| 12 | ☐ Pending | | |
|
||||
| 13 | ☐ Pending | | |
|
||||
| 14 | ☐ Pending | | |
|
||||
| 15 | ☐ Pending | | |
|
||||
+41
-43
@@ -283,12 +283,32 @@ class ClaudemanApp {
|
||||
}
|
||||
});
|
||||
|
||||
this.eventSource.addEventListener('session:clearTerminal', (e) => {
|
||||
this.eventSource.addEventListener('session:clearTerminal', async (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.id === this.activeSessionId) {
|
||||
// Clear terminal after screen attach to remove initialization blank space
|
||||
this.terminal.clear();
|
||||
this.terminal.reset();
|
||||
// Fetch buffer, clear terminal, write buffer, resize (no Ctrl+L needed)
|
||||
try {
|
||||
const res = await fetch(`/api/sessions/${data.id}/terminal`);
|
||||
const termData = await res.json();
|
||||
|
||||
this.terminal.clear();
|
||||
this.terminal.reset();
|
||||
if (termData.terminalBuffer) {
|
||||
this.terminal.write(termData.terminalBuffer);
|
||||
}
|
||||
|
||||
// Send resize to ensure proper dimensions
|
||||
const dims = this.fitAddon.proposeDimensions();
|
||||
if (dims) {
|
||||
await fetch(`/api/sessions/${data.id}/resize`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ cols: dims.cols, rows: dims.rows })
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('clearTerminal refresh failed:', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -669,15 +689,6 @@ class ClaudemanApp {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ cols: dims.cols, rows: dims.rows })
|
||||
});
|
||||
// Send Ctrl+L to fix squished display after tab switch
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (session && session.mode !== 'shell') {
|
||||
fetch(`/api/sessions/${sessionId}/input`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ input: '\x0c' })
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update respawn banner
|
||||
@@ -858,7 +869,7 @@ class ClaudemanApp {
|
||||
incrementTabCount() {
|
||||
const input = document.getElementById('tabCount');
|
||||
const current = parseInt(input.value) || 1;
|
||||
input.value = Math.min(10, current + 1);
|
||||
input.value = Math.min(20, current + 1);
|
||||
}
|
||||
|
||||
decrementTabCount() {
|
||||
@@ -869,7 +880,7 @@ class ClaudemanApp {
|
||||
|
||||
async runClaude() {
|
||||
const caseName = document.getElementById('quickStartCase').value || 'testcase';
|
||||
const tabCount = Math.min(10, Math.max(1, parseInt(document.getElementById('tabCount').value) || 1));
|
||||
const tabCount = Math.min(20, Math.max(1, parseInt(document.getElementById('tabCount').value) || 1));
|
||||
|
||||
this.terminal.clear();
|
||||
this.terminal.writeln(`\x1b[1;32m Starting ${tabCount} Claude session(s) in ${caseName}...\x1b[0m`);
|
||||
@@ -947,35 +958,9 @@ class ClaudemanApp {
|
||||
this.terminal.writeln(`\x1b[90m Created session ${i}/${tabCount}: ${sessionName}\x1b[0m`);
|
||||
}
|
||||
|
||||
// Auto-switch to the new session
|
||||
// Auto-switch to the new session using selectSession (does proper refresh)
|
||||
if (firstSessionId) {
|
||||
// IMPORTANT: Clear terminal BEFORE setting activeSessionId
|
||||
// This prevents SSE events from writing during the transition
|
||||
this.terminal.clear();
|
||||
this.terminal.reset();
|
||||
|
||||
// Small delay to ensure terminal is fully reset before SSE events can write
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
|
||||
// NOW set the active session so SSE events start populating
|
||||
this.activeSessionId = firstSessionId;
|
||||
this.renderSessionTabs();
|
||||
this.renderInnerStatePanel(); // Render Ralph panel if enabled globally
|
||||
|
||||
// Send resize to the new session
|
||||
const dims = this.fitAddon.proposeDimensions();
|
||||
if (dims) {
|
||||
await fetch(`/api/sessions/${firstSessionId}/resize`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ cols: dims.cols, rows: dims.rows })
|
||||
});
|
||||
}
|
||||
// Mark this session as needing Ctrl+L fix once Claude is running
|
||||
this.pendingCtrlL = this.pendingCtrlL || new Set();
|
||||
this.pendingCtrlL.add(firstSessionId);
|
||||
console.log('[DEBUG] Marked session for pending Ctrl+L:', firstSessionId);
|
||||
|
||||
await this.selectSession(firstSessionId);
|
||||
this.loadQuickStartCases();
|
||||
}
|
||||
|
||||
@@ -1664,6 +1649,16 @@ class ClaudemanApp {
|
||||
document.getElementById('appSettingsShowSystemStats').checked = settings.showSystemStats ?? true;
|
||||
document.getElementById('appSettingsShowTokenCount').checked = settings.showTokenCount ?? true;
|
||||
document.getElementById('appSettingsShowMonitor').checked = settings.showMonitor ?? true;
|
||||
// Claude CLI settings
|
||||
const claudeModeSelect = document.getElementById('appSettingsClaudeMode');
|
||||
const allowedToolsRow = document.getElementById('allowedToolsRow');
|
||||
claudeModeSelect.value = settings.claudeMode || 'dangerously-skip-permissions';
|
||||
document.getElementById('appSettingsAllowedTools').value = settings.allowedTools || '';
|
||||
allowedToolsRow.style.display = claudeModeSelect.value === 'allowedTools' ? '' : 'none';
|
||||
// Toggle allowed tools row visibility based on mode selection
|
||||
claudeModeSelect.onchange = () => {
|
||||
allowedToolsRow.style.display = claudeModeSelect.value === 'allowedTools' ? '' : 'none';
|
||||
};
|
||||
document.getElementById('appSettingsModal').classList.add('active');
|
||||
}
|
||||
|
||||
@@ -1681,6 +1676,9 @@ class ClaudemanApp {
|
||||
showSystemStats: document.getElementById('appSettingsShowSystemStats').checked,
|
||||
showTokenCount: document.getElementById('appSettingsShowTokenCount').checked,
|
||||
showMonitor: document.getElementById('appSettingsShowMonitor').checked,
|
||||
// Claude CLI settings
|
||||
claudeMode: document.getElementById('appSettingsClaudeMode').value,
|
||||
allowedTools: document.getElementById('appSettingsAllowedTools').value.trim(),
|
||||
};
|
||||
|
||||
// Save to localStorage
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
</button>
|
||||
<div class="tab-count-group" title="Number of tabs to open (1-10)">
|
||||
<button class="tab-count-btn" onclick="app.decrementTabCount()">−</button>
|
||||
<input type="number" id="tabCount" class="tab-count-input" value="1" min="1" max="10" readonly>
|
||||
<input type="number" id="tabCount" class="tab-count-input" value="1" min="1" max="20" readonly>
|
||||
<button class="tab-count-btn" onclick="app.incrementTabCount()">+</button>
|
||||
</div>
|
||||
<button class="btn-toolbar btn-shell" onclick="app.runShell()" title="Run Shell">
|
||||
@@ -479,7 +479,24 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<div class="settings-section-title">General</div>
|
||||
<div class="settings-section-title">Claude CLI</div>
|
||||
<div class="form-row">
|
||||
<label>Startup Mode</label>
|
||||
<select id="appSettingsClaudeMode" class="form-select">
|
||||
<option value="dangerously-skip-permissions">Skip Permissions (default)</option>
|
||||
<option value="normal">Normal (with prompts)</option>
|
||||
<option value="allowedTools">Allowed Tools Only</option>
|
||||
</select>
|
||||
<span class="form-hint">How Claude CLI is started in screen sessions</span>
|
||||
</div>
|
||||
<div class="form-row" id="allowedToolsRow" style="display: none;">
|
||||
<label>Allowed Tools</label>
|
||||
<input type="text" id="appSettingsAllowedTools" placeholder="Read,Glob,Grep,Bash">
|
||||
<span class="form-hint">Comma-separated list of tools to allow (e.g., Read,Glob,Grep,Bash)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<div class="settings-section-title">Paths & Defaults</div>
|
||||
<div class="form-row">
|
||||
<label>Default CLAUDE.md Template Path</label>
|
||||
<input type="text" id="appSettingsClaudeMdPath" placeholder="/path/to/CLAUDE.md template">
|
||||
@@ -490,6 +507,9 @@
|
||||
<input type="text" id="appSettingsDefaultDir" placeholder="~/projects">
|
||||
<span class="form-hint">Default directory for new sessions.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<div class="settings-section-title">Features</div>
|
||||
<div class="form-row form-row-switch">
|
||||
<label>Enable Ralph Wiggum Tracker</label>
|
||||
<label class="switch">
|
||||
|
||||
+30
-10
@@ -42,9 +42,9 @@ body {
|
||||
/* Compact Header */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--header-height);
|
||||
padding: 0 0.75rem;
|
||||
align-items: flex-start;
|
||||
min-height: var(--header-height);
|
||||
padding: 0.35rem 0.75rem;
|
||||
background: var(--bg-card);
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
@@ -56,6 +56,8 @@ body {
|
||||
padding-right: 0.75rem;
|
||||
border-right: 1px solid var(--border);
|
||||
margin-right: 0.5rem;
|
||||
height: var(--header-height);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
@@ -68,12 +70,30 @@ body {
|
||||
/* Session Tabs */
|
||||
.session-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
gap: 4px 2px;
|
||||
flex: 1;
|
||||
overflow-x: auto;
|
||||
padding: 0 0.25rem;
|
||||
overflow-y: auto;
|
||||
max-height: 120px;
|
||||
padding: 0.15rem 0.25rem;
|
||||
}
|
||||
|
||||
.session-tabs::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.session-tabs::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.session-tabs::-webkit-scrollbar-thumb {
|
||||
background: var(--border);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.session-tabs::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--border-light);
|
||||
}
|
||||
|
||||
.session-tabs::-webkit-scrollbar {
|
||||
@@ -1225,9 +1245,9 @@ body {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 0.4rem center;
|
||||
width: auto;
|
||||
min-width: 60px;
|
||||
max-width: 150px;
|
||||
width: fit-content;
|
||||
min-width: unset;
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
.toolbar-select:hover {
|
||||
|
||||
Reference in New Issue
Block a user