mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
refactor: UI cleanup - move respawn controls, remove mode label, fix dir width
- Move respawn controls from toolbar to session options modal - Remove unnecessary Mode label from session options (visible in tab) - Increase directory input width from 200px to 350px - Add RTL direction to directory display to show end of long paths - Add respawn status indicator with stop/configure buttons in modal Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,8 @@ Claudeman is a Claude Code session manager with a web interface and autonomous R
|
|||||||
|
|
||||||
**Tech Stack**: TypeScript, Node.js, Fastify, Server-Sent Events, node-pty
|
**Tech Stack**: TypeScript, Node.js, Fastify, Server-Sent Events, node-pty
|
||||||
|
|
||||||
|
**Requirements**: Node.js 18+, Claude CLI (`claude`) installed and available in PATH
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -18,13 +20,6 @@ npm run clean # Remove dist/
|
|||||||
npm link # Make 'claudeman' globally available
|
npm link # Make 'claudeman' globally available
|
||||||
claudeman web # Start web interface on port 3000
|
claudeman web # Start web interface on port 3000
|
||||||
claudeman web -p 8080 # Custom port
|
claudeman web -p 8080 # Custom port
|
||||||
|
|
||||||
# Testing
|
|
||||||
npm run test # Run all tests once
|
|
||||||
npm run test:watch # Run tests in watch mode
|
|
||||||
npm run test:coverage # Run tests with coverage report
|
|
||||||
npx vitest run test/session.test.ts # Run single test file
|
|
||||||
npx vitest run -t "should create session" # Run tests matching pattern
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
@@ -314,8 +309,30 @@ GET /api/events # SSE stream (real-time events)
|
|||||||
GET /api/status # Full state snapshot (sessions + scheduled + respawn)
|
GET /api/status # Full state snapshot (sessions + scheduled + respawn)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Tests use Vitest and auto-discover `*.test.ts` files in the `test/` directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run test # Run all tests once
|
||||||
|
npm run test:watch # Watch mode
|
||||||
|
npm run test:coverage # With coverage report
|
||||||
|
npx vitest run test/session.test.ts # Single file
|
||||||
|
npx vitest run -t "should create session" # By pattern
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
The web UI (`src/web/public/`) uses vanilla JavaScript with:
|
||||||
|
- **xterm.js**: Terminal emulator with WebGL renderer for 60fps performance
|
||||||
|
- **xterm-addon-fit**: Auto-resize terminal to container
|
||||||
|
- **Server-Sent Events**: Real-time updates from `/api/events`
|
||||||
|
- **No build step**: Static files served directly by Fastify
|
||||||
|
|
||||||
## Pending Tasks
|
## Pending Tasks
|
||||||
|
|
||||||
|
**Note to Claude: Do NOT remove or modify this section during /init. These tasks are actively being worked on by other sessions.**
|
||||||
|
|
||||||
- [ ] Remove the "New Session" tab and add a gear icon (⚙️) in the top right corner for app settings
|
- [ ] Remove the "New Session" tab and add a gear icon (⚙️) in the top right corner for app settings
|
||||||
- [ ] Add confirmation dialog when clicking "x" on a session tab - warn user that the screen session behind will be closed
|
- [ ] Add confirmation dialog when clicking "x" on a session tab - warn user that the screen session behind will be closed
|
||||||
- [ ] In settings: add option to configure a default CLAUDE.md file path (e.g. /home/arkon/default/CLAUDE.md) that will be used for new sessions/cases
|
- [ ] In settings: add option to configure a default CLAUDE.md file path (e.g. /home/arkon/default/CLAUDE.md) that will be used for new sessions/cases
|
||||||
|
|||||||
+46
-1
@@ -1167,14 +1167,59 @@ class ClaudemanApp {
|
|||||||
this.editingSessionId = sessionId;
|
this.editingSessionId = sessionId;
|
||||||
|
|
||||||
document.getElementById('sessionNameInput').value = session.name || '';
|
document.getElementById('sessionNameInput').value = session.name || '';
|
||||||
document.getElementById('sessionModeDisplay').textContent = session.mode === 'shell' ? 'Shell' : 'Claude';
|
|
||||||
document.getElementById('sessionDirDisplay').textContent = session.workingDir || 'Unknown';
|
document.getElementById('sessionDirDisplay').textContent = session.workingDir || 'Unknown';
|
||||||
|
|
||||||
|
// Update respawn status display
|
||||||
|
const respawnStatus = document.getElementById('sessionRespawnStatus');
|
||||||
|
const stopBtn = document.getElementById('sessionStopRespawnBtn');
|
||||||
|
const configBtn = document.getElementById('sessionConfigRespawnBtn');
|
||||||
|
|
||||||
|
if (this.respawnStatus[sessionId]) {
|
||||||
|
respawnStatus.classList.add('active');
|
||||||
|
respawnStatus.querySelector('.respawn-status-text').textContent =
|
||||||
|
`Active (${this.respawnStatus[sessionId].state || 'running'})`;
|
||||||
|
stopBtn.style.display = '';
|
||||||
|
configBtn.textContent = 'Reconfigure';
|
||||||
|
} else {
|
||||||
|
respawnStatus.classList.remove('active');
|
||||||
|
respawnStatus.querySelector('.respawn-status-text').textContent = 'Not active';
|
||||||
|
stopBtn.style.display = 'none';
|
||||||
|
configBtn.textContent = 'Configure Respawn';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only show respawn section for claude mode sessions with a running process
|
||||||
|
const respawnSection = document.getElementById('sessionRespawnSection');
|
||||||
|
if (session.mode === 'claude' && session.pid) {
|
||||||
|
respawnSection.style.display = '';
|
||||||
|
} else {
|
||||||
|
respawnSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('sessionOptionsModal').classList.add('active');
|
document.getElementById('sessionOptionsModal').classList.add('active');
|
||||||
|
|
||||||
// Focus the name input
|
// Focus the name input
|
||||||
setTimeout(() => document.getElementById('sessionNameInput').focus(), 100);
|
setTimeout(() => document.getElementById('sessionNameInput').focus(), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async stopRespawnFromOptions() {
|
||||||
|
if (!this.editingSessionId) return;
|
||||||
|
try {
|
||||||
|
await fetch(`/api/sessions/${this.editingSessionId}/respawn/stop`, { method: 'POST' });
|
||||||
|
delete this.respawnTimers[this.editingSessionId];
|
||||||
|
|
||||||
|
// Update the modal display
|
||||||
|
const respawnStatus = document.getElementById('sessionRespawnStatus');
|
||||||
|
respawnStatus.classList.remove('active');
|
||||||
|
respawnStatus.querySelector('.respawn-status-text').textContent = 'Not active';
|
||||||
|
document.getElementById('sessionStopRespawnBtn').style.display = 'none';
|
||||||
|
document.getElementById('sessionConfigRespawnBtn').textContent = 'Configure Respawn';
|
||||||
|
|
||||||
|
this.showToast('Respawn stopped', 'success');
|
||||||
|
} catch (err) {
|
||||||
|
this.showToast('Failed to stop respawn', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
closeSessionOptions() {
|
closeSessionOptions() {
|
||||||
this.editingSessionId = null;
|
this.editingSessionId = null;
|
||||||
document.getElementById('sessionOptionsModal').classList.remove('active');
|
document.getElementById('sessionOptionsModal').classList.remove('active');
|
||||||
|
|||||||
+10
-11
@@ -85,13 +85,6 @@
|
|||||||
</button>
|
</button>
|
||||||
<input type="text" id="dirInput" class="toolbar-input hidden" placeholder="Working directory..." onblur="app.hideDirInput()">
|
<input type="text" id="dirInput" class="toolbar-input hidden" placeholder="Working directory..." onblur="app.hideDirInput()">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Respawn Settings -->
|
|
||||||
<div class="toolbar-group">
|
|
||||||
<button class="btn-toolbar" onclick="app.toggleRespawnPanel()" id="respawnToggleBtn" title="Respawn settings">
|
|
||||||
Respawn
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
@@ -257,14 +250,20 @@
|
|||||||
<label>Session Name</label>
|
<label>Session Name</label>
|
||||||
<input type="text" id="sessionNameInput" placeholder="Enter session name...">
|
<input type="text" id="sessionNameInput" placeholder="Enter session name...">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
|
||||||
<label>Mode</label>
|
|
||||||
<div class="session-mode-display" id="sessionModeDisplay">Claude</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label>Directory</label>
|
<label>Directory</label>
|
||||||
<div class="session-dir-display" id="sessionDirDisplay">/path/to/dir</div>
|
<div class="session-dir-display" id="sessionDirDisplay">/path/to/dir</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row session-respawn-section" id="sessionRespawnSection">
|
||||||
|
<label>Respawn Controller</label>
|
||||||
|
<div class="session-respawn-status" id="sessionRespawnStatus">
|
||||||
|
<span class="respawn-status-text">Not active</span>
|
||||||
|
</div>
|
||||||
|
<div class="session-respawn-actions">
|
||||||
|
<button class="btn-toolbar btn-sm" onclick="app.toggleRespawnPanel(); app.closeSessionOptions();" id="sessionConfigRespawnBtn">Configure Respawn</button>
|
||||||
|
<button class="btn-toolbar btn-sm btn-danger" onclick="app.stopRespawnFromOptions()" id="sessionStopRespawnBtn" style="display: none;">Stop Respawn</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button class="btn-toolbar" onclick="app.closeSessionOptions()">Cancel</button>
|
<button class="btn-toolbar" onclick="app.closeSessionOptions()">Cancel</button>
|
||||||
|
|||||||
@@ -486,7 +486,17 @@ body {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
width: 200px;
|
width: 350px;
|
||||||
|
max-width: 50vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dirDisplay {
|
||||||
|
max-width: 300px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
direction: rtl;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-input:focus {
|
.toolbar-input:focus {
|
||||||
@@ -714,7 +724,6 @@ body {
|
|||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.session-mode-display,
|
|
||||||
.session-dir-display {
|
.session-dir-display {
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
background: var(--bg-input);
|
background: var(--bg-input);
|
||||||
@@ -728,6 +737,42 @@ body {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Session respawn section in options modal */
|
||||||
|
.session-respawn-section {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-respawn-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
background: var(--bg-input);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-respawn-status .respawn-status-text {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-respawn-status.active {
|
||||||
|
border-color: rgba(34, 197, 94, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-respawn-status.active .respawn-status-text {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-respawn-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-header {
|
.modal-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
Reference in New Issue
Block a user