mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 08:59:40 +02:00
refactor: simplify respawn controller display and improve CLAUDE.md
Respawn banner changes: - Human-friendly state labels (e.g., "Kickstarting..." vs "waiting_kickstart") - Removed redundant "→ waiting for" text - Simplified AI check display (only shows when actively checking) - Shorter cycle counter (#1 vs "Cycle 1") - Fixed NaN timer bug with validation - Flatter HTML structure CLAUDE.md improvements: - Reorganized Commands section into clear subsections - Test port allocation in table format - Simplified TypeScript config section - Removed duplicate keyboard shortcuts (already in README) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,85 +39,107 @@ npm install
|
|||||||
|
|
||||||
**CRITICAL**: `npm run dev` runs CLI help, NOT the web server. Use `npx tsx src/index.ts web` for development.
|
**CRITICAL**: `npm run dev` runs CLI help, NOT the web server. Use `npx tsx src/index.ts web` for development.
|
||||||
|
|
||||||
|
### Build & Clean
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build # Compile TS + copy static files + templates + make bins executable
|
npm run build # Compile TS + copy static files + templates + make bins executable
|
||||||
npm run clean # Remove dist/
|
npm run clean # Remove dist/
|
||||||
|
npm run typecheck # Type check without building (or: npx tsc --noEmit)
|
||||||
|
```
|
||||||
|
|
||||||
# Start web server (pick one):
|
### Web Server
|
||||||
|
|
||||||
|
```bash
|
||||||
npx tsx src/index.ts web # Dev mode - no build needed (RECOMMENDED)
|
npx tsx src/index.ts web # Dev mode - no build needed (RECOMMENDED)
|
||||||
npx tsx src/index.ts web -p 8080 # Dev mode with custom port
|
npx tsx src/index.ts web -p 8080 # Dev mode with custom port
|
||||||
npx tsx src/index.ts web --https # Dev mode with self-signed TLS (enables browser notifications)
|
npx tsx src/index.ts web --https # Dev mode with self-signed TLS (enables browser notifications)
|
||||||
npm run web # After npm run build (shorthand)
|
npm run web # After npm run build (shorthand)
|
||||||
node dist/index.js web # After npm run build
|
node dist/index.js web # After npm run build
|
||||||
claudeman web # After npm link
|
claudeman web # After npm link
|
||||||
|
```
|
||||||
|
|
||||||
# Start TUI (terminal user interface):
|
### TUI (Terminal User Interface)
|
||||||
|
|
||||||
|
```bash
|
||||||
npx tsx src/index.ts tui # Dev mode - prompts to start web if not running
|
npx tsx src/index.ts tui # Dev mode - prompts to start web if not running
|
||||||
claudeman tui # After npm link
|
claudeman tui # After npm link
|
||||||
claudeman tui --with-web # Auto-start web server if not running (no prompt)
|
claudeman tui --with-web # Auto-start web server if not running (no prompt)
|
||||||
claudeman tui --no-web # Skip web server check entirely
|
claudeman tui --no-web # Skip web server check entirely
|
||||||
claudeman tui -p 8080 # Specify web server port
|
claudeman tui -p 8080 # Specify web server port
|
||||||
|
```
|
||||||
|
|
||||||
# Testing (vitest)
|
### Testing
|
||||||
# Note: globals: true configured - no imports needed for describe/it/expect
|
|
||||||
|
```bash
|
||||||
npm run test # Run all tests once
|
npm run test # Run all tests once
|
||||||
npm run test:watch # Watch mode
|
npm run test:watch # Watch mode
|
||||||
npm run test:coverage # With coverage report
|
npm run test:coverage # With coverage report
|
||||||
npx vitest run test/session.test.ts # Single file
|
npx vitest run test/session.test.ts # Single file
|
||||||
npx vitest run -t "should create session" # By pattern
|
npx vitest run -t "should create session" # By pattern
|
||||||
|
```
|
||||||
|
|
||||||
# Test port allocation (integration tests spawn servers):
|
**Test Configuration** (vitest.config.ts):
|
||||||
# 3099: quick-start.test.ts
|
- `globals: true` - no imports needed for `describe`/`it`/`expect`
|
||||||
# 3102: session.test.ts
|
- `testTimeout: 30000` - 30s for integration tests
|
||||||
# 3105: scheduled-runs.test.ts
|
- `teardownTimeout: 60000` - 60s ensures cleanup runs even on failures
|
||||||
# 3107: sse-events.test.ts
|
- `fileParallelism: false` - sequential file execution to respect screen session limits
|
||||||
# 3110: edge-cases.test.ts
|
- Coverage excludes entry points: `src/index.ts`, `src/cli.ts`
|
||||||
# 3115: integration-flows.test.ts
|
|
||||||
# 3120: session-cleanup.test.ts
|
|
||||||
# 3125: ralph-integration.test.ts
|
|
||||||
# Unit tests (no port needed): respawn-controller, ralph-tracker, pty-interactive, task-queue, task, ralph-loop, session-manager, state-store, types, templates, ralph-config, spawn-detector, spawn-types, spawn-orchestrator, hooks-config, ai-idle-checker
|
|
||||||
# Next available: 3127+
|
|
||||||
|
|
||||||
# Tests mock PTY - no real Claude CLI spawned
|
**Test Port Allocation** (integration tests spawn servers):
|
||||||
# Test timeout: 30s (configured in vitest.config.ts)
|
|
||||||
# Teardown timeout: 60s (ensures cleanup runs even on failures)
|
|
||||||
# Coverage excludes: src/index.ts, src/cli.ts (entry points)
|
|
||||||
# Global test utilities (describe/it/expect) available without imports (globals: true)
|
|
||||||
# Tests run sequentially (fileParallelism: false) to respect screen session limits
|
|
||||||
# Global setup (test/setup.ts) enforces max 10 concurrent screens + orphan cleanup
|
|
||||||
#
|
|
||||||
# ✅ TEST SAFETY: test/setup.ts protects its own process tree during cleanup.
|
|
||||||
# You can safely run tests from within a Claudeman-managed session - the cleanup
|
|
||||||
# will not kill your own Claude instance. The respawn-controller tests use
|
|
||||||
# MockSession (not real screens).
|
|
||||||
|
|
||||||
# TypeScript checking
|
| Port | Test File |
|
||||||
npm run typecheck # Type check without building (or: npx tsc --noEmit)
|
|------|-----------|
|
||||||
# Note: No ESLint/Prettier configured - rely on TypeScript strict mode
|
| 3099 | quick-start.test.ts |
|
||||||
|
| 3102 | session.test.ts |
|
||||||
|
| 3105 | scheduled-runs.test.ts |
|
||||||
|
| 3107 | sse-events.test.ts |
|
||||||
|
| 3110 | edge-cases.test.ts |
|
||||||
|
| 3115 | integration-flows.test.ts |
|
||||||
|
| 3120 | session-cleanup.test.ts |
|
||||||
|
| 3125 | ralph-integration.test.ts |
|
||||||
|
| 3127+ | Next available |
|
||||||
|
|
||||||
# MCP Server (for Claude Code to call spawn tools directly):
|
Unit tests (no port needed): respawn-controller, ralph-tracker, pty-interactive, task-queue, task, ralph-loop, session-manager, state-store, types, templates, ralph-config, spawn-detector, spawn-types, spawn-orchestrator, hooks-config, ai-idle-checker
|
||||||
# Configure in Claude Code's MCP settings:
|
|
||||||
# command: "node", args: ["dist/mcp-server.js"]
|
**Test Safety**: `test/setup.ts` enforces max 10 concurrent screens, performs orphan cleanup, and protects its own process tree. You can safely run tests from within a Claudeman-managed session - the cleanup will not kill your own Claude instance. The respawn-controller tests use MockSession (not real screens).
|
||||||
# env: { CLAUDEMAN_API_URL: "http://localhost:3000", CLAUDEMAN_SESSION_ID: "<id>" }
|
|
||||||
|
### MCP Server
|
||||||
|
|
||||||
|
```bash
|
||||||
npx tsx src/mcp-server.ts # Dev mode (stdio transport)
|
npx tsx src/mcp-server.ts # Dev mode (stdio transport)
|
||||||
|
```
|
||||||
|
|
||||||
# Debugging
|
Configure in Claude Code's MCP settings:
|
||||||
|
```json
|
||||||
|
{ "command": "node", "args": ["dist/mcp-server.js"], "env": { "CLAUDEMAN_API_URL": "http://localhost:3000", "CLAUDEMAN_SESSION_ID": "<id>" } }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
|
||||||
|
```bash
|
||||||
screen -ls # List GNU screen sessions
|
screen -ls # List GNU screen sessions
|
||||||
screen -r <name> # Attach to screen session (Ctrl+A D to detach)
|
screen -r <name> # Attach to screen session (Ctrl+A D to detach)
|
||||||
curl localhost:3000/api/sessions # Check active sessions
|
curl localhost:3000/api/sessions # Check active sessions
|
||||||
curl localhost:3000/api/status | jq . # Full app state including respawn
|
curl localhost:3000/api/status | jq . # Full app state including respawn
|
||||||
cat ~/.claudeman/state.json | jq . # View main state
|
cat ~/.claudeman/state.json | jq . # View main state
|
||||||
cat ~/.claudeman/state-inner.json | jq . # View Ralph loop state
|
cat ~/.claudeman/state-inner.json | jq . # View Ralph loop state
|
||||||
|
```
|
||||||
|
|
||||||
# Systemd service (respawning, survives logout):
|
### Systemd Service
|
||||||
|
|
||||||
|
```bash
|
||||||
systemctl --user status claudeman-web # Check status
|
systemctl --user status claudeman-web # Check status
|
||||||
systemctl --user restart claudeman-web # Restart
|
systemctl --user restart claudeman-web # Restart
|
||||||
systemctl --user stop claudeman-web # Stop
|
systemctl --user stop claudeman-web # Stop
|
||||||
journalctl --user -u claudeman-web -f # Stream logs
|
journalctl --user -u claudeman-web -f # Stream logs
|
||||||
# Install: ln -sf scripts/claudeman-web.service ~/.config/systemd/user/
|
```
|
||||||
# Enable: systemctl --user enable claudeman-web && loginctl enable-linger $USER
|
|
||||||
|
|
||||||
# Kill stuck screen sessions
|
Install: `ln -sf scripts/claudeman-web.service ~/.config/systemd/user/`
|
||||||
|
Enable: `systemctl --user enable claudeman-web && loginctl enable-linger $USER`
|
||||||
|
|
||||||
|
### Kill Stuck Screens
|
||||||
|
|
||||||
|
```bash
|
||||||
screen -X -S <name> quit # Graceful quit
|
screen -X -S <name> quit # Graceful quit
|
||||||
pkill -f "SCREEN.*claudeman" # Force kill all claudeman screens
|
pkill -f "SCREEN.*claudeman" # Force kill all claudeman screens
|
||||||
```
|
```
|
||||||
@@ -149,16 +171,6 @@ claudeman status # Overall status
|
|||||||
claudeman reset # Reset all state
|
claudeman reset # Reset all state
|
||||||
```
|
```
|
||||||
|
|
||||||
## Keyboard Shortcuts (Web UI)
|
|
||||||
|
|
||||||
| Shortcut | Action |
|
|
||||||
|----------|--------|
|
|
||||||
| `Ctrl+Enter` | Quick-start session |
|
|
||||||
| `Ctrl+W` | Close session |
|
|
||||||
| `Ctrl+Tab` | Next session |
|
|
||||||
| `Ctrl+K` | Kill all sessions |
|
|
||||||
| `Ctrl+L` | Clear terminal |
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Key Files
|
### Key Files
|
||||||
@@ -405,19 +417,9 @@ CLI commands (`claudeman status/session list`) read from `state.json` to display
|
|||||||
|
|
||||||
### TypeScript Config
|
### TypeScript Config
|
||||||
|
|
||||||
Module resolution: NodeNext. Target: ES2022. Strict mode with additional checks:
|
Module resolution: NodeNext. Target: ES2022. Strict mode enabled with all additional strictness flags (`noUnusedLocals`, `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch`, etc.). No ESLint/Prettier configured - rely on TypeScript strict mode.
|
||||||
|
|
||||||
| Setting | Effect |
|
TUI uses React JSX (`jsx: react-jsx`, `jsxImportSource: react`) for Ink components.
|
||||||
|---------|--------|
|
|
||||||
| `noUnusedLocals` | Error on unused local variables |
|
|
||||||
| `noUnusedParameters` | Error on unused function parameters |
|
|
||||||
| `noImplicitReturns` | All code paths must return a value |
|
|
||||||
| `noImplicitOverride` | Require `override` keyword for overridden methods |
|
|
||||||
| `noFallthroughCasesInSwitch` | Require break/return in switch cases |
|
|
||||||
| `allowUnreachableCode: false` | Error on unreachable code |
|
|
||||||
| `allowUnusedLabels: false` | Error on unused labels |
|
|
||||||
|
|
||||||
TUI uses React JSX (`jsxImportSource: react`) for Ink components.
|
|
||||||
|
|
||||||
## Adding New Features
|
## Adding New Features
|
||||||
|
|
||||||
|
|||||||
+56
-52
@@ -917,18 +917,12 @@ class ClaudemanApp {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventSource.addEventListener('respawn:stepSent', (e) => {
|
this.eventSource.addEventListener('respawn:stepSent', (_e) => {
|
||||||
const data = JSON.parse(e.data);
|
// Step info is shown via state label (e.g., "Sending prompt", "Clearing context")
|
||||||
if (data.sessionId === this.activeSessionId) {
|
|
||||||
document.getElementById('respawnStep').textContent = data.input;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventSource.addEventListener('respawn:autoAcceptSent', (e) => {
|
this.eventSource.addEventListener('respawn:autoAcceptSent', (e) => {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
if (data.sessionId === this.activeSessionId) {
|
|
||||||
document.getElementById('respawnStep').textContent = '⏎ Auto-accepted plan';
|
|
||||||
}
|
|
||||||
const session = this.sessions.get(data.sessionId);
|
const session = this.sessions.get(data.sessionId);
|
||||||
this.notificationManager?.notify({
|
this.notificationManager?.notify({
|
||||||
urgency: 'info',
|
urgency: 'info',
|
||||||
@@ -950,34 +944,20 @@ class ClaudemanApp {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventSource.addEventListener('respawn:aiCheckStarted', (e) => {
|
this.eventSource.addEventListener('respawn:aiCheckStarted', (_e) => {
|
||||||
const data = JSON.parse(e.data);
|
// AI check status shown via updateDetectionDisplay
|
||||||
if (data.sessionId === this.activeSessionId) {
|
|
||||||
document.getElementById('respawnStep').textContent = 'AI Check: Analyzing...';
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventSource.addEventListener('respawn:aiCheckCompleted', (e) => {
|
this.eventSource.addEventListener('respawn:aiCheckCompleted', (_e) => {
|
||||||
const data = JSON.parse(e.data);
|
// AI check status shown via updateDetectionDisplay
|
||||||
if (data.sessionId === this.activeSessionId) {
|
|
||||||
const icon = data.verdict === 'IDLE' ? '✓' : '⏳';
|
|
||||||
document.getElementById('respawnStep').textContent = `AI Check: ${data.verdict} ${icon}`;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventSource.addEventListener('respawn:aiCheckFailed', (e) => {
|
this.eventSource.addEventListener('respawn:aiCheckFailed', (_e) => {
|
||||||
const data = JSON.parse(e.data);
|
// AI check status shown via updateDetectionDisplay
|
||||||
if (data.sessionId === this.activeSessionId) {
|
|
||||||
document.getElementById('respawnStep').textContent = `AI Check: Error - ${data.error?.substring(0, 50)}`;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventSource.addEventListener('respawn:aiCheckCooldown', (e) => {
|
this.eventSource.addEventListener('respawn:aiCheckCooldown', (_e) => {
|
||||||
const data = JSON.parse(e.data);
|
// AI check status shown via updateDetectionDisplay
|
||||||
if (data.sessionId === this.activeSessionId && data.active) {
|
|
||||||
const remaining = Math.ceil((data.endsAt - Date.now()) / 1000);
|
|
||||||
document.getElementById('respawnStep').textContent = `AI Check: WORKING (cooldown ${remaining}s)`;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Respawn run timer events (timed respawn runs)
|
// Respawn run timer events (timed respawn runs)
|
||||||
@@ -1981,8 +1961,28 @@ class ClaudemanApp {
|
|||||||
this.hideRespawnTimer();
|
this.hideRespawnTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Human-friendly state labels
|
||||||
|
getStateLabel(state) {
|
||||||
|
const labels = {
|
||||||
|
'stopped': 'Stopped',
|
||||||
|
'watching': 'Watching',
|
||||||
|
'confirming_idle': 'Confirming idle',
|
||||||
|
'ai_checking': 'AI checking',
|
||||||
|
'sending_update': 'Sending prompt',
|
||||||
|
'waiting_update': 'Running prompt',
|
||||||
|
'sending_clear': 'Clearing context',
|
||||||
|
'waiting_clear': 'Clearing...',
|
||||||
|
'sending_init': 'Initializing',
|
||||||
|
'waiting_init': 'Initializing...',
|
||||||
|
'monitoring_init': 'Waiting for work',
|
||||||
|
'sending_kickstart': 'Kickstarting',
|
||||||
|
'waiting_kickstart': 'Kickstarting...',
|
||||||
|
};
|
||||||
|
return labels[state] || state.replace(/_/g, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
updateRespawnBanner(state) {
|
updateRespawnBanner(state) {
|
||||||
this.$('respawnState').textContent = state.replace(/_/g, ' ');
|
this.$('respawnState').textContent = this.getStateLabel(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDetectionDisplay(detection) {
|
updateDetectionDisplay(detection) {
|
||||||
@@ -1991,50 +1991,48 @@ class ClaudemanApp {
|
|||||||
const statusEl = this.$('detectionStatus');
|
const statusEl = this.$('detectionStatus');
|
||||||
const waitingEl = this.$('detectionWaiting');
|
const waitingEl = this.$('detectionWaiting');
|
||||||
const confidenceEl = this.$('detectionConfidence');
|
const confidenceEl = this.$('detectionConfidence');
|
||||||
|
const aiCheckEl = document.getElementById('detectionAiCheck');
|
||||||
|
|
||||||
// Update status text
|
// Simplified status - only show when meaningful
|
||||||
statusEl.textContent = detection.statusText || '';
|
if (detection.statusText && detection.statusText !== 'Watching...') {
|
||||||
|
statusEl.textContent = detection.statusText;
|
||||||
// Update waiting for text
|
statusEl.style.display = '';
|
||||||
if (detection.waitingFor) {
|
|
||||||
waitingEl.textContent = `→ ${detection.waitingFor}`;
|
|
||||||
} else {
|
} else {
|
||||||
waitingEl.textContent = '';
|
statusEl.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update confidence level
|
// Hide "waiting for" text - it's redundant with the state label
|
||||||
|
waitingEl.style.display = 'none';
|
||||||
|
|
||||||
|
// Show confidence only when confirming (>0%)
|
||||||
const confidence = detection.confidenceLevel || 0;
|
const confidence = detection.confidenceLevel || 0;
|
||||||
if (confidence > 0) {
|
if (confidence > 0) {
|
||||||
confidenceEl.textContent = `${confidence}%`;
|
confidenceEl.textContent = `${confidence}%`;
|
||||||
confidenceEl.style.display = '';
|
confidenceEl.style.display = '';
|
||||||
confidenceEl.className = 'detection-confidence';
|
confidenceEl.className = 'detection-confidence';
|
||||||
if (confidence >= 60) {
|
if (confidence >= 60) confidenceEl.classList.add('high');
|
||||||
confidenceEl.classList.add('high');
|
else if (confidence >= 30) confidenceEl.classList.add('medium');
|
||||||
} else if (confidence >= 30) {
|
|
||||||
confidenceEl.classList.add('medium');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
confidenceEl.style.display = 'none';
|
confidenceEl.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show AI check info if available
|
// Simplified AI check display
|
||||||
const aiCheckEl = document.getElementById('detectionAiCheck');
|
|
||||||
if (aiCheckEl && detection.aiCheck) {
|
if (aiCheckEl && detection.aiCheck) {
|
||||||
const ai = detection.aiCheck;
|
const ai = detection.aiCheck;
|
||||||
let aiText = '';
|
let aiText = '';
|
||||||
if (ai.status === 'checking') {
|
if (ai.status === 'checking') {
|
||||||
aiText = 'AI: Analyzing...';
|
aiText = 'AI analyzing...';
|
||||||
} else if (ai.status === 'cooldown' && ai.cooldownEndsAt) {
|
} else if (ai.status === 'cooldown' && ai.cooldownEndsAt) {
|
||||||
const remaining = Math.ceil((ai.cooldownEndsAt - Date.now()) / 1000);
|
const remaining = Math.ceil((ai.cooldownEndsAt - Date.now()) / 1000);
|
||||||
aiText = `AI: WORKING (${remaining}s cooldown)`;
|
if (remaining > 0) aiText = `Cooldown ${remaining}s`;
|
||||||
} else if (ai.status === 'disabled') {
|
} else if (ai.status === 'disabled') {
|
||||||
aiText = `AI: Disabled (${ai.disabledReason || 'errors'})`;
|
aiText = 'AI disabled';
|
||||||
} else if (ai.lastVerdict) {
|
|
||||||
aiText = `AI: Last=${ai.lastVerdict}`;
|
|
||||||
if (ai.lastCheckDurationMs) aiText += ` (${Math.round(ai.lastCheckDurationMs / 1000)}s)`;
|
|
||||||
}
|
}
|
||||||
|
// Don't show "last verdict" - it's noise
|
||||||
aiCheckEl.textContent = aiText;
|
aiCheckEl.textContent = aiText;
|
||||||
aiCheckEl.style.display = aiText ? '' : 'none';
|
aiCheckEl.style.display = aiText ? '' : 'none';
|
||||||
|
} else if (aiCheckEl) {
|
||||||
|
aiCheckEl.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2062,6 +2060,12 @@ class ClaudemanApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const timer = this.respawnTimers[this.activeSessionId];
|
const timer = this.respawnTimers[this.activeSessionId];
|
||||||
|
// Guard against invalid timer data
|
||||||
|
if (!timer.endAt || isNaN(timer.endAt)) {
|
||||||
|
this.hideRespawnTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const remaining = Math.max(0, timer.endAt - now);
|
const remaining = Math.max(0, timer.endAt - now);
|
||||||
|
|
||||||
|
|||||||
@@ -70,25 +70,19 @@
|
|||||||
<div class="respawn-banner-row">
|
<div class="respawn-banner-row">
|
||||||
<span class="respawn-indicator">↻</span>
|
<span class="respawn-indicator">↻</span>
|
||||||
<span class="respawn-state" id="respawnState">Watching</span>
|
<span class="respawn-state" id="respawnState">Watching</span>
|
||||||
<span class="respawn-detection" id="respawnDetection">
|
<span class="detection-status" id="detectionStatus"></span>
|
||||||
<span class="detection-status" id="detectionStatus"></span>
|
<span class="detection-waiting" id="detectionWaiting" style="display: none;"></span>
|
||||||
<span class="detection-waiting" id="detectionWaiting"></span>
|
<span class="detection-confidence" id="detectionConfidence"></span>
|
||||||
<span class="detection-confidence" id="detectionConfidence"></span>
|
<span class="detection-ai-check" id="detectionAiCheck" style="display: none;"></span>
|
||||||
<span class="detection-ai-check" id="detectionAiCheck" style="display: none;"></span>
|
<span class="respawn-spacer"></span>
|
||||||
</span>
|
<span class="respawn-cycles">#<span id="respawnCycleCount">0</span></span>
|
||||||
<span class="respawn-cycles">Cycle <span id="respawnCycleCount">0</span></span>
|
|
||||||
<span class="respawn-timer" id="respawnTimer" style="display: none;"></span>
|
<span class="respawn-timer" id="respawnTimer" style="display: none;"></span>
|
||||||
<span class="respawn-tokens" id="respawnTokens" style="display: none;"></span>
|
<span class="respawn-tokens" id="respawnTokens" style="display: none;"></span>
|
||||||
<span class="respawn-step" id="respawnStep"></span>
|
|
||||||
<button class="btn-icon-only" onclick="app.stopRespawn()" title="Stop Respawn">■</button>
|
<button class="btn-icon-only" onclick="app.stopRespawn()" title="Stop Respawn">■</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="respawn-timers-row" id="respawnTimersRow" style="display: none;">
|
<div class="respawn-timers-row" id="respawnTimersRow" style="display: none;">
|
||||||
<div class="respawn-countdown-timers" id="respawnCountdownTimers">
|
<div class="respawn-countdown-timers" id="respawnCountdownTimers"></div>
|
||||||
<!-- Dynamically populated countdown timers -->
|
<div class="respawn-action-log" id="respawnActionLog"></div>
|
||||||
</div>
|
|
||||||
<div class="respawn-action-log" id="respawnActionLog">
|
|
||||||
<!-- Recent actions scrolling list -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -568,20 +568,16 @@ body {
|
|||||||
.respawn-state {
|
.respawn-state {
|
||||||
color: var(--green);
|
color: var(--green);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-transform: capitalize;
|
}
|
||||||
|
|
||||||
|
.respawn-spacer {
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.respawn-cycles {
|
.respawn-cycles {
|
||||||
color: var(--text-dim);
|
|
||||||
}
|
|
||||||
|
|
||||||
.respawn-step {
|
|
||||||
flex: 1;
|
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-family: monospace;
|
font-family: 'SF Mono', Monaco, monospace;
|
||||||
overflow: hidden;
|
font-size: 0.7rem;
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.respawn-timer {
|
.respawn-timer {
|
||||||
@@ -599,23 +595,14 @@ body {
|
|||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.respawn-detection {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
border-left: 1px solid rgba(34, 197, 94, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.detection-status {
|
.detection-status {
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detection-waiting {
|
.detection-ai-check {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
font-style: italic;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detection-confidence {
|
.detection-confidence {
|
||||||
|
|||||||
Reference in New Issue
Block a user