chore: bump version to 0.1402

This commit is contained in:
arkon
2026-01-27 23:15:25 +01:00
parent 9fc3d2eda3
commit 46434ee6d0
11 changed files with 2253 additions and 870 deletions
+349 -26
View File
@@ -464,6 +464,11 @@ class ClaudemanApp {
this._subagentHideTimeout = null; // Timeout for hover-based dropdown hide
this.ralphStatePanelCollapsed = true; // Default to collapsed
// Plan subagent windows (visible Opus agents during plan generation)
this.planSubagents = new Map(); // Map<agentId, { type, model, status, startTime, element }>
this.planSubagentWindowZIndex = 1100;
this.planGenerationStopped = false; // Flag to ignore SSE events after Stop
// Project Insights tracking (active Bash tools with clickable file paths)
this.projectInsights = new Map(); // Map<sessionId, ActiveBashTool[]>
this.logViewerWindows = new Map(); // Map<windowId, { element, eventSource, filePath }>
@@ -1907,6 +1912,13 @@ class ClaudemanApp {
}, 5 * 60 * 1000); // 5 minutes
});
// Plan subagent visibility events (show Opus agents during plan generation)
addListener('plan:subagent', (e) => {
const data = JSON.parse(e.data);
console.log('[Plan Subagent]', data);
this.handlePlanSubagentEvent(data);
});
// Plan generation progress events (for detailed mode with subagents)
addListener('plan:progress', (e) => {
const data = JSON.parse(e.data);
@@ -2578,7 +2590,7 @@ class ClaudemanApp {
generatedPlan: null, // [{content, priority, enabled, id}] or null
planGenerated: false,
skipPlanGeneration: false,
planDetailLevel: 'standard', // 'brief', 'standard', 'detailed'
planDetailLevel: 'detailed', // 'brief', 'standard', 'detailed'
};
planLoadingTimer = null;
planLoadingStartTime = null;
@@ -2596,7 +2608,7 @@ class ClaudemanApp {
generatedPlan: null,
planGenerated: false,
skipPlanGeneration: false,
planDetailLevel: 'standard',
planDetailLevel: 'detailed',
// Existing plan detection
existingPlan: null, // { todos, stats, content } from @fix_plan.md
useExistingPlan: false, // User chose to use existing plan
@@ -2620,10 +2632,16 @@ class ClaudemanApp {
this.updateRalphWizardUI();
document.getElementById('ralphWizardModal').classList.add('active');
document.getElementById('ralphTaskDescription').focus();
// Update connection lines to show wizard connections
this.updateConnectionLines();
}
closeRalphWizard() {
document.getElementById('ralphWizardModal')?.classList.remove('active');
// Update connection lines (wizard closed, revert to normal)
this.updateConnectionLines();
}
populateRalphCaseSelector() {
@@ -2925,6 +2943,10 @@ class ClaudemanApp {
const spinnerEl = document.querySelector('.plan-spinner');
if (spinnerEl) spinnerEl.style.display = '';
// Reset stopped indicator
const stoppedIndicator = document.getElementById('planStoppedIndicator');
if (stoppedIndicator) stoppedIndicator.style.display = 'none';
// Reset existing plan badge
const badge = document.getElementById('existingPlanBadge');
if (badge) badge.style.display = 'none';
@@ -2937,6 +2959,9 @@ class ClaudemanApp {
// Stop any existing generation first
this.stopPlanGeneration();
// Reset stopped flag to allow new SSE events
this.planGenerationStopped = false;
// Create abort controller for this generation
this.planGenerationAbortController = new AbortController();
@@ -3164,6 +3189,10 @@ class ClaudemanApp {
const errorEl = document.getElementById('planGenerationError');
const msgEl = document.getElementById('planErrorMsg');
const stoppedIndicator = document.getElementById('planStoppedIndicator');
// Hide the stopped indicator for real errors, show error message
if (stoppedIndicator) stoppedIndicator.style.display = 'none';
if (msgEl) msgEl.textContent = message;
errorEl?.classList.remove('hidden');
}
@@ -3176,6 +3205,9 @@ class ClaudemanApp {
document.getElementById('planGenerationError')?.classList.add('hidden');
document.getElementById('planEditor')?.classList.remove('hidden');
// Close plan subagent windows since generation is complete
this.closePlanSubagentWindows();
const list = document.getElementById('planItemsList');
if (!list) return;
@@ -3565,13 +3597,183 @@ class ClaudemanApp {
cancelPlanGeneration() {
this.stopPlanGeneration();
this.showToast('Plan generation cancelled', 'info');
this.planGenerationStopped = true; // Ignore future SSE events
this.showToast('Plan generation stopped', 'info');
// Allow user to proceed without a plan by clicking Next
this.ralphWizardConfig.skipPlanGeneration = true;
// Show stopped state with clear visual indicator
const errorEl = document.getElementById('planGenerationError');
const msgEl = document.getElementById('planErrorMsg');
if (msgEl) msgEl.textContent = 'Plan generation was cancelled.';
errorEl?.classList.remove('hidden');
const stoppedIndicator = document.getElementById('planStoppedIndicator');
// Show the stopped indicator, hide error message since this was intentional
if (stoppedIndicator) stoppedIndicator.style.display = 'flex';
if (msgEl) msgEl.textContent = '';
// Hide spinner immediately and show stopped state
document.getElementById('planGenerationLoading')?.classList.add('hidden');
errorEl?.classList.remove('hidden');
// Close all plan subagent windows
this.closePlanSubagentWindows();
}
// ========== Plan Subagent Windows (Visible Opus Agents) ==========
handlePlanSubagentEvent(event) {
// Ignore events if user has stopped plan generation
if (this.planGenerationStopped) return;
const { type, agentId, agentType, model, status, detail, itemCount, durationMs, error } = event;
if (type === 'started') {
// Create new plan subagent window
this.createPlanSubagentWindow(agentId, agentType, model, detail);
} else if (type === 'completed' || type === 'failed') {
// Update window to show completion/failure
this.updatePlanSubagentWindow(agentId, status, itemCount, durationMs, error);
} else if (type === 'progress') {
// Update progress detail
const windowData = this.planSubagents.get(agentId);
if (windowData?.element) {
const detailEl = windowData.element.querySelector('.plan-subagent-detail');
if (detailEl) detailEl.textContent = detail || '';
}
}
}
createPlanSubagentWindow(agentId, agentType, model, detail) {
if (this.planSubagents.has(agentId)) return;
const wizardModal = document.getElementById('ralphWizardModal');
const wizardContent = wizardModal?.querySelector('.modal-content');
const wizardRect = wizardContent?.getBoundingClientRect();
// Calculate position - alternate left/right of wizard
const windowWidth = 280;
const windowHeight = 100;
const windowCount = this.planSubagents.size;
const viewportWidth = window.innerWidth;
let x, y;
if (wizardRect) {
const centerX = wizardRect.left + wizardRect.width / 2;
const gap = 30;
// Stack windows vertically on each side
const side = windowCount % 2 === 0 ? 'right' : 'left';
const sideIndex = Math.floor(windowCount / 2);
if (side === 'right') {
x = wizardRect.right + gap;
} else {
x = wizardRect.left - windowWidth - gap;
}
y = wizardRect.top + sideIndex * (windowHeight + 15);
// Clamp to viewport
x = Math.max(10, Math.min(x, viewportWidth - windowWidth - 10));
y = Math.max(60, Math.min(y, window.innerHeight - windowHeight - 10));
} else {
// Fallback positioning
x = 50 + windowCount * 30;
y = 120 + windowCount * 30;
}
// Create window element
const win = document.createElement('div');
win.className = 'plan-subagent-window';
win.id = `plan-subagent-${agentId}`;
win.style.left = `${x}px`;
win.style.top = `${y}px`;
win.style.zIndex = ++this.planSubagentWindowZIndex;
const typeLabels = {
requirements: 'Requirements Analyst',
architecture: 'Architecture Planner',
testing: 'TDD Specialist',
risks: 'Risk Analyst',
verification: 'Verification Expert',
};
const typeIcons = {
requirements: '📋',
architecture: '🏗️',
testing: '🧪',
risks: '⚠️',
verification: '✓',
};
win.innerHTML = `
<div class="plan-subagent-header">
<span class="plan-subagent-icon">${typeIcons[agentType] || '🤖'}</span>
<span class="plan-subagent-title">${typeLabels[agentType] || agentType}</span>
<span class="plan-subagent-model">${model}</span>
</div>
<div class="plan-subagent-body">
<div class="plan-subagent-status running">
<span class="plan-subagent-spinner"></span>
<span class="plan-subagent-status-text">Running...</span>
</div>
<div class="plan-subagent-detail">${detail || ''}</div>
</div>
`;
document.body.appendChild(win);
// Store reference
this.planSubagents.set(agentId, {
type: agentType,
model,
status: 'running',
startTime: Date.now(),
element: win,
});
// Update connection lines
this.updateConnectionLines();
}
updatePlanSubagentWindow(agentId, status, itemCount, durationMs, error) {
const windowData = this.planSubagents.get(agentId);
if (!windowData?.element) return;
const win = windowData.element;
const statusEl = win.querySelector('.plan-subagent-status');
const statusTextEl = win.querySelector('.plan-subagent-status-text');
const spinnerEl = win.querySelector('.plan-subagent-spinner');
const detailEl = win.querySelector('.plan-subagent-detail');
windowData.status = status;
if (status === 'completed') {
statusEl?.classList.remove('running');
statusEl?.classList.add('completed');
if (spinnerEl) spinnerEl.style.display = 'none';
if (statusTextEl) statusTextEl.textContent = `Done (${itemCount || 0} items)`;
if (detailEl && durationMs) detailEl.textContent = `${(durationMs / 1000).toFixed(1)}s`;
} else if (status === 'failed' || status === 'cancelled') {
statusEl?.classList.remove('running');
statusEl?.classList.add('failed');
if (spinnerEl) spinnerEl.style.display = 'none';
if (statusTextEl) statusTextEl.textContent = status === 'cancelled' ? 'Cancelled' : 'Failed';
if (detailEl) detailEl.textContent = error || '';
}
// Update connection lines
this.updateConnectionLines();
}
closePlanSubagentWindows() {
for (const [agentId, windowData] of this.planSubagents) {
if (windowData.element) {
windowData.element.remove();
}
}
this.planSubagents.clear();
this.updateConnectionLines();
}
skipPlanGeneration() {
@@ -7768,34 +7970,118 @@ class ClaudemanApp {
svg.innerHTML = '';
// Check if Ralph wizard modal is open
const wizardModal = document.getElementById('ralphWizardModal');
const wizardOpen = wizardModal && !wizardModal.classList.contains('hidden') &&
wizardModal.style.display !== 'none';
const wizardContent = wizardOpen ? wizardModal.querySelector('.modal-content') : null;
for (const [agentId, windowInfo] of this.subagentWindows) {
if (windowInfo.minimized || windowInfo.hidden) continue;
const agent = this.subagents.get(agentId);
if (!agent?.parentSessionId) continue;
const tab = document.querySelector(`.session-tab[data-id="${agent.parentSessionId}"]`);
const win = windowInfo.element;
if (!tab || !win) continue;
if (!win) continue;
const tabRect = tab.getBoundingClientRect();
const winRect = win.getBoundingClientRect();
// Draw curved line from tab bottom-center to window top-center
const x1 = tabRect.left + tabRect.width / 2;
const y1 = tabRect.bottom;
const x2 = winRect.left + winRect.width / 2;
const y2 = winRect.top;
// If wizard is open, draw lines from wizard to subagent windows
if (wizardOpen && wizardContent) {
const wizardRect = wizardContent.getBoundingClientRect();
// Bezier curve control points for smooth curve
const midY = (y1 + y2) / 2;
const path = `M ${x1} ${y1} C ${x1} ${midY}, ${x2} ${midY}, ${x2} ${y2}`;
// Determine which side of wizard the window is on
const winCenterX = winRect.left + winRect.width / 2;
const wizardCenterX = wizardRect.left + wizardRect.width / 2;
const line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
line.setAttribute('d', path);
line.setAttribute('class', 'connection-line');
line.setAttribute('data-agent-id', agentId);
svg.appendChild(line);
let x1, y1, x2, y2;
if (winCenterX < wizardCenterX) {
// Window is on the left - connect from wizard left edge
x1 = wizardRect.left;
y1 = wizardRect.top + wizardRect.height / 2;
x2 = winRect.right;
y2 = winRect.top + winRect.height / 2;
} else {
// Window is on the right - connect from wizard right edge
x1 = wizardRect.right;
y1 = wizardRect.top + wizardRect.height / 2;
x2 = winRect.left;
y2 = winRect.top + winRect.height / 2;
}
// Bezier curve for smooth horizontal connection
const midX = (x1 + x2) / 2;
const path = `M ${x1} ${y1} C ${midX} ${y1}, ${midX} ${y2}, ${x2} ${y2}`;
const line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
line.setAttribute('d', path);
line.setAttribute('class', 'connection-line wizard-connection');
line.setAttribute('data-agent-id', agentId);
svg.appendChild(line);
} else if (agent?.parentSessionId) {
// Normal mode - connect to parent session tab
const tab = document.querySelector(`.session-tab[data-id="${agent.parentSessionId}"]`);
if (!tab) continue;
const tabRect = tab.getBoundingClientRect();
// Draw curved line from tab bottom-center to window top-center
const x1 = tabRect.left + tabRect.width / 2;
const y1 = tabRect.bottom;
const x2 = winRect.left + winRect.width / 2;
const y2 = winRect.top;
// Bezier curve control points for smooth curve
const midY = (y1 + y2) / 2;
const path = `M ${x1} ${y1} C ${x1} ${midY}, ${x2} ${midY}, ${x2} ${y2}`;
const line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
line.setAttribute('d', path);
line.setAttribute('class', 'connection-line');
line.setAttribute('data-agent-id', agentId);
svg.appendChild(line);
}
}
// Draw lines from wizard to plan subagent windows (Opus agents during plan generation)
if (wizardOpen && wizardContent && this.planSubagents.size > 0) {
const wizardRect = wizardContent.getBoundingClientRect();
for (const [agentId, windowData] of this.planSubagents) {
const win = windowData.element;
if (!win) continue;
const winRect = win.getBoundingClientRect();
// Determine which side of wizard the window is on
const winCenterX = winRect.left + winRect.width / 2;
const wizardCenterX = wizardRect.left + wizardRect.width / 2;
let x1, y1, x2, y2;
if (winCenterX < wizardCenterX) {
// Window is on the left
x1 = wizardRect.left;
y1 = wizardRect.top + wizardRect.height / 3 + (this.planSubagents.size > 3 ? 0 : 50);
x2 = winRect.right;
y2 = winRect.top + winRect.height / 2;
} else {
// Window is on the right
x1 = wizardRect.right;
y1 = wizardRect.top + wizardRect.height / 3 + (this.planSubagents.size > 3 ? 0 : 50);
x2 = winRect.left;
y2 = winRect.top + winRect.height / 2;
}
const midX = (x1 + x2) / 2;
const path = `M ${x1} ${y1} C ${midX} ${y1}, ${midX} ${y2}, ${x2} ${y2}`;
const line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
line.setAttribute('d', path);
line.setAttribute('class', 'connection-line wizard-connection plan-subagent-line');
line.setAttribute('data-plan-agent-id', agentId);
svg.appendChild(line);
}
}
}
@@ -7874,11 +8160,48 @@ class ClaudemanApp {
const windowWidth = 420;
const windowHeight = 350;
const gap = 20;
const startX = 50;
const startY = 120;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const maxCols = Math.floor((viewportWidth - startX - 50) / (windowWidth + gap)) || 1;
// Check if Ralph wizard modal is open - if so, position windows on the sides
const wizardModal = document.getElementById('ralphWizardModal');
const wizardOpen = wizardModal && !wizardModal.classList.contains('hidden') &&
wizardModal.style.display !== 'none';
let startX, startY, maxCols;
if (wizardOpen) {
// Wizard is ~720px wide, centered. Position windows on left/right sides
const wizardWidth = 720;
const centerX = viewportWidth / 2;
const wizardLeft = centerX - wizardWidth / 2;
const wizardRight = centerX + wizardWidth / 2;
// Alternate between left and right sides of the wizard
const leftSideSpace = wizardLeft - 20;
const rightSideSpace = viewportWidth - wizardRight - 20;
if (windowCount % 2 === 0 && rightSideSpace >= windowWidth) {
// Even windows go to the right
startX = wizardRight + 20;
maxCols = Math.floor(rightSideSpace / (windowWidth + gap)) || 1;
} else if (leftSideSpace >= windowWidth) {
// Odd windows go to the left
startX = Math.max(10, wizardLeft - windowWidth - 20);
maxCols = 1; // Usually only room for 1 column on left
} else {
// Not enough side space, use right side
startX = wizardRight + 20;
maxCols = 1;
}
startY = 80; // Start higher when wizard is open
} else {
// Normal positioning
startX = 50;
startY = 120;
maxCols = Math.floor((viewportWidth - startX - 50) / (windowWidth + gap)) || 1;
}
const maxRows = Math.floor((viewportHeight - startY - 50) / (windowHeight + gap)) || 1;
const col = windowCount % maxCols;
const row = Math.floor(windowCount / maxCols) % maxRows; // Wrap rows to stay in viewport
+24 -6
View File
@@ -172,6 +172,13 @@
<span class="ralph-info-label">Elapsed</span>
<span id="ralphElapsed">0m</span>
</div>
<div class="ralph-info-row" id="ralphVersionRow" style="display: none;">
<span class="ralph-info-label">Plan</span>
<div class="ralph-version-info">
<span class="version-badge" id="ralphPlanVersion">v1</span>
<button class="rollback-btn" id="ralphRollbackBtn" onclick="app.showPlanHistory()" title="View history">history</button>
</div>
</div>
</div>
<div class="ralph-tasks-grid" id="ralphTasksGrid"></div>
</div>
@@ -1013,16 +1020,23 @@
</div>
</div>
<p class="plan-loading-hint" id="planLoadingHint">Initializing deep reasoning model</p>
<button class="btn-toolbar btn-sm plan-skip-btn" onclick="app.skipPlanGeneration()">Skip</button>
<div class="plan-loading-actions">
<button class="btn-toolbar btn-sm plan-cancel-btn" onclick="app.cancelPlanGeneration()">Stop</button>
<button class="btn-toolbar btn-sm" onclick="app.regeneratePlan()">Regenerate</button>
</div>
</div>
<!-- Error state -->
<!-- Stopped/Error state -->
<div id="planGenerationError" class="plan-section hidden">
<div class="plan-stopped-indicator" id="planStoppedIndicator">
<span class="plan-stopped-icon">&#x25A0;</span>
<span class="plan-stopped-text">Stopped</span>
</div>
<p class="plan-error-msg" id="planErrorMsg"></p>
<div class="plan-actions">
<button class="btn-toolbar btn-primary" onclick="app.generatePlan()">Retry</button>
<button class="btn-toolbar" onclick="app.skipPlanGeneration()">Skip</button>
<button class="btn-toolbar btn-primary" onclick="app.generatePlan()">Regenerate Plan</button>
</div>
<p class="plan-skip-hint">Use <strong>Back</strong> to edit your task, or <strong>Next</strong> to continue without a plan</p>
</div>
<!-- Editor state -->
@@ -1034,8 +1048,8 @@
<label>Detail:</label>
<div class="plan-detail-btns">
<button type="button" class="plan-detail-btn" data-detail="brief" onclick="app.setPlanDetail('brief')" title="High-level milestones only">Brief</button>
<button type="button" class="plan-detail-btn active" data-detail="standard" onclick="app.setPlanDetail('standard')" title="Single-pass generation with Opus 4.5">Standard</button>
<button type="button" class="plan-detail-btn" data-detail="detailed" onclick="app.setPlanDetail('detailed')" title="Enhanced: 4 parallel subagents + verification (slower but more thorough)">Enhanced</button>
<button type="button" class="plan-detail-btn" data-detail="standard" onclick="app.setPlanDetail('standard')" title="Single-pass generation with Opus 4.5">Standard</button>
<button type="button" class="plan-detail-btn active" data-detail="detailed" onclick="app.setPlanDetail('detailed')" title="Enhanced: 4 parallel subagents + verification (slower but more thorough)">Enhanced</button>
</div>
</div>
<button class="btn-toolbar btn-sm" onclick="app.regeneratePlan()">Regenerate</button>
@@ -1045,6 +1059,10 @@
<div id="planItemsList" class="plan-items-list"></div>
<div class="plan-editor-footer">
<button class="btn-toolbar btn-sm" onclick="app.addPlanItem()">+ Add Step</button>
<div class="plan-view-toggle">
<button type="button" class="plan-view-btn active" data-view="list" onclick="app.setPlanViewMode('list')" title="Flat list">&#x2630;</button>
<button type="button" class="plan-view-btn" data-view="grouped" onclick="app.setPlanViewMode('grouped')" title="Group by TDD phase">&#x229E;</button>
</div>
</div>
</div>
</div>
+738 -7
View File
@@ -4692,6 +4692,19 @@ kbd {
stroke-dashoffset: 1000;
}
/* Wizard connection lines - different color to show plan generation link */
.connection-line.wizard-connection {
stroke: #60a5fa;
filter: drop-shadow(0 0 4px #60a5fa);
stroke-dasharray: 8 4;
animation: wizard-pulse 1.5s ease-in-out infinite;
}
@keyframes wizard-pulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 0.9; }
}
/* ========== Project Insights Panel (Bash File Viewers) ========== */
.project-insights-panel {
@@ -5366,10 +5379,23 @@ kbd {
gap: 0.5rem;
}
/* Wizard Modal - wider layout */
/* Wizard Modal - wider layout with height constraints */
.modal-wizard {
width: 720px;
max-width: 95vw;
max-height: 80vh;
display: flex;
flex-direction: column;
}
.modal-wizard .modal-body {
overflow-y: auto;
flex: 1;
min-height: 0;
}
.modal-wizard .wizard-footer {
flex-shrink: 0;
}
.wizard-page-compact {
@@ -5641,20 +5667,158 @@ kbd {
display: flex;
flex-direction: column;
gap: 0.35rem;
max-height: 300px;
max-height: 200px;
overflow-y: auto;
margin-bottom: 0.5rem;
}
.plan-item {
.plan-loading-actions {
display: flex;
gap: 0.5rem;
justify-content: center;
}
.plan-cancel-btn {
color: var(--red);
}
/* Plan Stopped State */
.plan-stopped-indicator {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.35rem 0.5rem;
background: var(--bg-input);
border-radius: 4px;
justify-content: center;
gap: 0.5rem;
margin-bottom: 1rem;
padding: 0.75rem;
background: var(--surface);
border-radius: 6px;
border: 1px solid var(--border);
}
.plan-stopped-icon {
color: var(--red);
font-size: 1rem;
}
.plan-stopped-text {
font-weight: 500;
color: var(--text);
}
.plan-actions-single {
justify-content: center;
}
.plan-skip-hint {
font-size: 0.75rem;
color: var(--text-muted);
text-align: center;
margin-top: 0.75rem;
}
.plan-skip-hint strong {
color: var(--text-dim);
}
/* ========== Plan Subagent Windows (Visible Opus Agents) ========== */
.plan-subagent-window {
position: fixed;
width: 280px;
background: var(--bg-card);
border: 1px solid var(--accent);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4), 0 0 20px rgba(96, 165, 250, 0.2);
overflow: hidden;
animation: planSubagentSpawn 0.3s ease-out;
}
@keyframes planSubagentSpawn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
.plan-subagent-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: linear-gradient(135deg, rgba(96, 165, 250, 0.2), rgba(96, 165, 250, 0.1));
border-bottom: 1px solid var(--border);
}
.plan-subagent-icon {
font-size: 1rem;
}
.plan-subagent-title {
flex: 1;
font-weight: 500;
font-size: 0.8rem;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.plan-subagent-model {
font-size: 0.65rem;
padding: 0.15rem 0.4rem;
background: #7c3aed;
color: white;
border-radius: 3px;
text-transform: uppercase;
font-weight: 600;
}
.plan-subagent-body {
padding: 0.75rem;
}
.plan-subagent-status {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.35rem;
}
.plan-subagent-status.running .plan-subagent-spinner {
width: 14px;
height: 14px;
border: 2px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 1s linear infinite;
}
.plan-subagent-status.completed .plan-subagent-status-text {
color: var(--green);
}
.plan-subagent-status.failed .plan-subagent-status-text {
color: var(--red);
}
.plan-subagent-status-text {
font-size: 0.8rem;
font-weight: 500;
}
.plan-subagent-detail {
font-size: 0.7rem;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Note: main .plan-item styles are in "Enhanced Plan Item Styling" section below */
.plan-item-checkbox {
flex-shrink: 0;
width: 14px;
@@ -5701,6 +5865,8 @@ kbd {
color: var(--red);
}
/* Note: .plan-item-phase and .plan-item-verify enhanced styles are in section below */
.plan-item:has(.plan-item-checkbox:not(:checked)) {
opacity: 0.5;
}
@@ -5761,6 +5927,571 @@ kbd {
color: #fbbf24;
}
/* Enhanced Plan Item Styling */
.plan-item {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.35rem 0.5rem;
background: var(--bg-input);
border-radius: 4px;
border-left: 3px solid transparent;
transition: border-color 0.15s, background-color 0.15s, transform 0.15s;
cursor: default;
}
.plan-item.dragging {
opacity: 0.5;
transform: scale(0.98);
}
.plan-item.drag-over {
background: var(--accent-dim);
border-left-color: var(--accent);
}
/* Priority color borders */
.plan-item.priority-p0 {
border-left-color: #ef4444;
}
.plan-item.priority-p1 {
border-left-color: #f59e0b;
}
.plan-item.priority-p2 {
border-left-color: #3b82f6;
}
/* Drag handle */
.plan-item-drag {
cursor: grab;
color: var(--text-muted);
font-size: 0.9rem;
padding: 0 0.2rem;
opacity: 0.5;
user-select: none;
}
.plan-item-drag:hover {
opacity: 1;
}
.plan-item-drag:active {
cursor: grabbing;
}
/* TDD Phase badge - enhanced */
.plan-item-phase {
flex-shrink: 0;
min-width: 22px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
border-radius: 3px;
background: var(--bg-card);
}
.plan-item-phase.phase-setup {
background: #6366f1;
color: white;
}
.plan-item-phase.phase-test {
background: #8b5cf6;
color: white;
}
.plan-item-phase.phase-impl {
background: #06b6d4;
color: white;
}
.plan-item-phase.phase-review {
background: #f59e0b;
color: white;
}
.plan-item-phase.phase-verify {
background: #22c55e;
color: white;
}
/* Enhanced verification indicator */
.plan-item-verify {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 0.15rem;
color: var(--green);
font-size: 0.65rem;
cursor: help;
padding: 0.1rem 0.25rem;
background: rgba(34, 197, 94, 0.1);
border-radius: 3px;
opacity: 0.85;
}
.plan-item-verify:hover {
opacity: 1;
background: rgba(34, 197, 94, 0.2);
}
/* Dependencies indicator */
.plan-item-deps {
flex-shrink: 0;
font-size: 0.65rem;
color: var(--text-muted);
padding: 0.1rem 0.25rem;
background: var(--bg-card);
border-radius: 3px;
cursor: help;
}
.plan-item-deps:hover {
color: var(--accent);
}
/* Review checklist indicator */
.plan-item-checklist {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 0.15rem;
color: #f59e0b;
font-size: 0.65rem;
cursor: help;
padding: 0.1rem 0.25rem;
background: rgba(245, 158, 11, 0.1);
border-radius: 3px;
opacity: 0.85;
}
.plan-item-checklist:hover {
opacity: 1;
background: rgba(245, 158, 11, 0.2);
}
/* Checklist modal */
.checklist-modal-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.checklist-modal {
padding: 1rem;
}
.checklist-modal h3 {
margin: 0 0 0.5rem 0;
color: var(--text);
}
.checklist-task {
color: var(--text-muted);
font-size: 0.85rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border);
}
.checklist-list {
list-style: none;
padding: 0;
margin: 0 0 1rem 0;
max-height: 300px;
overflow-y: auto;
}
.checklist-item {
display: flex;
align-items: flex-start;
gap: 0.5rem;
padding: 0.35rem 0;
}
.checklist-checkbox {
flex-shrink: 0;
margin-top: 0.15rem;
}
.checklist-item label {
color: var(--text);
font-size: 0.85rem;
cursor: pointer;
}
.checklist-footer {
text-align: right;
}
/* Group headers for TDD phases */
.plan-group-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.3rem 0.5rem;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
color: var(--text-muted);
border-bottom: 1px solid var(--border);
margin-top: 0.5rem;
cursor: pointer;
user-select: none;
}
.plan-group-header:first-child {
margin-top: 0;
}
.plan-group-header:hover {
color: var(--text);
}
.plan-group-header .group-icon {
font-size: 0.8rem;
}
.plan-group-header .group-count {
margin-left: auto;
color: var(--text-dim);
font-weight: normal;
}
.plan-group-header .group-chevron {
transition: transform 0.15s;
}
.plan-group-header.collapsed .group-chevron {
transform: rotate(-90deg);
}
.plan-group-items {
display: flex;
flex-direction: column;
gap: 0.25rem;
padding: 0.25rem 0;
}
.plan-group-items.collapsed {
display: none;
}
/* View mode toggle */
.plan-view-toggle {
display: flex;
gap: 0.25rem;
}
.plan-view-btn {
padding: 0.2rem 0.4rem;
font-size: 0.7rem;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: 3px;
color: var(--text-muted);
cursor: pointer;
transition: all 0.15s;
}
.plan-view-btn:hover {
color: var(--text);
border-color: var(--accent);
}
.plan-view-btn.active {
background: var(--accent);
color: white;
border-color: var(--accent);
}
/* Verification criteria tooltip expansion */
.plan-item-verify-expanded {
position: absolute;
left: 0;
right: 0;
bottom: calc(100% + 4px);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.5rem;
font-size: 0.75rem;
color: var(--text);
z-index: 10;
box-shadow: 0 -2px 8px rgba(0,0,0,0.2);
max-width: 300px;
}
.plan-item-verify-expanded::after {
content: '';
position: absolute;
bottom: -6px;
left: 50%;
transform: translateX(-50%);
border: 5px solid transparent;
border-top-color: var(--border);
}
/* Plan toolbar improvements */
.plan-editor-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border);
}
.plan-toolbar-left {
display: flex;
gap: 0.5rem;
align-items: center;
}
.plan-toolbar-right {
display: flex;
gap: 0.5rem;
align-items: center;
}
/* Plan item inline actions */
.plan-item-actions {
display: flex;
gap: 0.1rem;
opacity: 0;
transition: opacity 0.15s;
}
.plan-item:hover .plan-item-actions {
opacity: 1;
}
.plan-item-action {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
font-size: 0.75rem;
padding: 0.1rem 0.2rem;
border-radius: 2px;
}
.plan-item-action:hover {
color: var(--text);
background: var(--bg-card);
}
/* Runtime task card enhancements */
.ralph-task-card {
position: relative;
}
.ralph-task-attempts {
position: absolute;
top: 2px;
right: 2px;
font-size: 0.6rem;
color: var(--text-muted);
background: var(--bg-card);
padding: 0 3px;
border-radius: 2px;
}
.ralph-task-attempts.has-errors {
color: var(--red);
background: rgba(239, 68, 68, 0.1);
}
.ralph-task-verify-badge {
position: absolute;
bottom: 2px;
right: 2px;
font-size: 0.6rem;
color: var(--green);
opacity: 0.7;
}
.ralph-task-deps-indicator {
position: absolute;
top: 2px;
left: 2px;
font-size: 0.55rem;
color: var(--accent);
}
/* Quick actions on task hover */
.ralph-task-actions {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: none;
gap: 0.25rem;
background: rgba(0,0,0,0.85);
padding: 0.25rem;
border-radius: 4px;
}
.ralph-task-card:hover .ralph-task-actions {
display: flex;
}
.ralph-task-action-btn {
padding: 0.2rem 0.35rem;
font-size: 0.65rem;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: 3px;
color: var(--text);
cursor: pointer;
}
.ralph-task-action-btn:hover {
background: var(--accent);
color: white;
border-color: var(--accent);
}
/* Plan version indicator */
.plan-version-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.75rem;
color: var(--text-muted);
padding: 0.25rem 0.5rem;
background: var(--bg-input);
border-radius: 4px;
}
.plan-version-badge {
background: var(--accent);
color: white;
padding: 0.1rem 0.3rem;
border-radius: 3px;
font-size: 0.65rem;
font-weight: 500;
}
.plan-version-history {
cursor: pointer;
color: var(--accent);
}
.plan-version-history:hover {
text-decoration: underline;
}
/* Plan history dropdown */
.plan-history-dropdown {
position: absolute;
top: 100%;
right: 0;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
z-index: 100;
min-width: 200px;
max-height: 200px;
overflow-y: auto;
}
.plan-history-list {
max-height: 300px;
overflow-y: auto;
border: 1px solid var(--border);
border-radius: 4px;
}
.plan-history-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0.75rem;
border-bottom: 1px solid var(--border);
cursor: pointer;
font-size: 0.8rem;
transition: background-color 0.15s;
}
.plan-history-item:last-child {
border-bottom: none;
}
.plan-history-item:hover {
background: var(--bg-input);
}
.plan-history-item.current {
background: var(--accent-dim);
cursor: default;
}
.plan-history-item.current::after {
content: '(current)';
font-size: 0.65rem;
color: var(--accent);
margin-left: 0.5rem;
}
.plan-history-version {
font-weight: 500;
color: var(--text);
}
.plan-history-tasks {
font-size: 0.7rem;
color: var(--text-muted);
margin-left: 0.5rem;
}
.plan-history-time {
color: var(--text-muted);
font-size: 0.7rem;
}
/* Ralph panel version info */
.ralph-version-info {
display: flex;
align-items: center;
gap: 0.35rem;
font-size: 0.7rem;
color: var(--text-muted);
margin-left: auto;
}
.ralph-version-info .version-badge {
background: var(--bg-card);
padding: 0.1rem 0.25rem;
border-radius: 2px;
font-family: 'SF Mono', Monaco, monospace;
}
.ralph-version-info .rollback-btn {
background: none;
border: none;
color: var(--accent);
cursor: pointer;
font-size: 0.65rem;
padding: 0.1rem 0.25rem;
}
.ralph-version-info .rollback-btn:hover {
text-decoration: underline;
}
/* Textarea with AI Assist button */
.textarea-with-assist {
position: relative;