Remove old review system from dashboard (cosmetic only)
CI / build (push) Has been cancelled

The review system (REVIEW.md, approve/changes_requested buttons,
review filter, review badges) was purely cosmetic — only the dashboard
read/wrote it. No workflow component (status.py, autopilot.py,
loop-runner.py, prompts) ever enforced it.

The 'Approve' button in the task detail panel confused users into
thinking it approved the task's phase gate. In reality it only wrote
to REVIEW.md, which had zero effect on transitions.

Removed:
- Review section (buttons, textarea, status badge) from detail panel
- Review badge from task cards
- Review filter from toolbar
- Pending-review counter from header
- All review-related CSS

Users now use the single '🔓 Approve Phase' button in the detail
panel, which calls status.py --approve and actually transitions the
task.
This commit is contained in:
Lap Tran
2026-06-25 11:23:55 -04:00
parent 54f65f9861
commit fe43b9e1fc
9 changed files with 12 additions and 94 deletions
+6 -68
View File
@@ -1,7 +1,7 @@
const state = {
scope: 'none', currentView: 'board', theme: 'default', selectedTask: null,
tasks: [], refreshCount: 0, autoRefresh: true, showWaves: true,
filterPhase: 'all', filterReview: 'all', filterWave: 'all', searchQuery: '', filterVisible: false,
filterPhase: 'all', filterWave: 'all', searchQuery: '', filterVisible: false,
refreshInterval: null, projectName: null,
};
@@ -48,14 +48,8 @@ function getPhaseGroupForState(state) {
return group ? group.id : null;
}
// Get display group for a task, accounting for review status.
// Approved planning tasks advance to Design; rejected ones go to Blocked.
// Get display group for a task based on its phase state.
function getTaskDisplayGroup(task) {
const review = task.review ? task.review.status : 'pending';
if ((task.state === 'research' || task.state === 'decomposition' || task.state === 'backlog')) {
if (review === 'approved') return 'design';
if (review === 'changes_requested') return 'blocked';
}
return getPhaseGroupForState(task.state);
}
@@ -89,8 +83,7 @@ function renderHeader() {
wipTasks.textContent = filtered.filter(t => wipStates.includes(t.state)).length;
doneTasks.textContent = filtered.filter(t => t.state === 'done').length;
blockedTasks.textContent = filtered.filter(t => t.state === 'blocked').length;
const pendingReview = document.getElementById('pending-review');
if (pendingReview) pendingReview.textContent = filtered.filter(t => (t.state !== 'done' && t.state !== 'blocked') && (!t.review || t.review.status === 'pending')).length;
// Project name display
const projectName = state.projectName;
if (projectName) {
@@ -163,10 +156,6 @@ function renderTaskCard(task) {
const statusClass = task.state === 'done' ? 'done' : task.state === 'blocked' ? 'blocked' : 'in_progress';
const statusIcon = task.state === 'done' ? '✅' : task.state === 'blocked' ? '❌' : '🔄';
const subLabel = getSubLabel(task.state);
const reviewStatus = task.review ? task.review.status : 'pending';
const reviewBadge = reviewStatus === 'approved' ? '<span class="review-badge approved" title="Approved">✅</span>'
: reviewStatus === 'changes_requested' ? '<span class="review-badge changes" title="Changes requested">❌</span>'
: '<span class="review-badge pending" title="Pending review">🟡</span>';
const progressHtml = task.sub_tasks.length > 0
? `<span class="subtask-progress">${task.sub_tasks.filter(st => st.has_verdict && st.verdict_status === 'PASS').length}/${task.sub_tasks.length}</span>`
: '';
@@ -177,14 +166,12 @@ function renderTaskCard(task) {
return `<div class="subtask-item"><span class="subtask-status ${stStatus}">${stIcon}</span><span>${st.name}</span></div>`;
}).join('')}</div>`
: '';
const artifactsHtml = (reviewStatus === 'pending' || reviewStatus === 'changes_requested')
? `<div class="task-card-artifacts">${COLUMNS.filter(col => task.artifacts[col.id]).map(col => {
const artifactsHtml = `<div class="task-card-artifacts">${COLUMNS.filter(col => task.artifacts[col.id]).map(col => {
const label = ARTIFACT_LABELS[col.id] || col.label;
return `<span class="artifact-badge" title="${col.label}">${label}</span>`;
}).join('')}</div>`
: '';
}).join('')}</div>`;
return `<div class="task-card" data-task="${task.name}" data-status="${statusClass}">
<div class="task-card-header"><span class="task-card-name">${escapeHtml(task.display_name)}</span><span style="display:flex;align-items:center;gap:4px">${reviewBadge}<span class="task-card-status ${statusClass}">${statusIcon}</span></span></div>
<div class="task-card-header"><span class="task-card-name">${escapeHtml(task.display_name)}</span><span class="task-card-status ${statusClass}">${statusIcon}</span></div>
<div class="task-card-sublabel">${subLabel}</div>
${task.status_reason ? `<div class="task-card-reason">${escapeHtml(task.status_reason)}</div>` : ''}
${artifactsHtml}
@@ -211,18 +198,6 @@ function renderDetail(task) {
return `<span class="detail-artifact"><span class="${cls}">${icon}</span>${col.label}</span>`;
}).join('');
const phaseGroupHtml = phaseGroup ? `<span class="detail-phase-badge" style="background: ${phaseGroupColor}20; color: ${phaseGroupColor}">${PHASE_GROUPS.find(g => g.id === phaseGroup).label}</span>` : '';
const reviewStatus = task.review ? task.review.status : 'pending';
const reviewStatusText = reviewStatus === 'approved' ? '✅ Approved' : reviewStatus === 'changes_requested' ? '❌ Changes Requested' : '🟡 Pending Review';
const reviewComment = task.review && task.review.comment ? `<p class="review-comment">${escapeHtml(task.review.comment)}</p>` : '';
let reviewActionsHtml;
if (reviewStatus === 'approved') {
reviewActionsHtml = '<button class="review-btn revoke" data-task="' + escapeHtml(task.name) + '" data-status="pending">↩ Revoke Approval</button>';
} else if (reviewStatus === 'changes_requested') {
reviewActionsHtml = '<button class="review-btn revoke" data-task="' + escapeHtml(task.name) + '" data-status="pending">↩ Revoke Changes</button>';
} else {
reviewActionsHtml = '<button class="review-btn approve" data-task="' + escapeHtml(task.name) + '" data-status="approved">✅ Approve</button>' +
'<button class="review-btn changes" data-task="' + escapeHtml(task.name) + '" data-status="changes_requested">❌ Request Changes</button>';
}
const approvePhaseBtn = task.is_approval_gated
? `<button class="approve-phase-btn" data-task="${escapeHtml(task.name)}">🔓 Approve Phase</button>`
: '';
@@ -233,14 +208,6 @@ function renderDetail(task) {
${task.phase_guidance ? `<div class="detail-section detail-guidance"><h4>▶ What's Next</h4><pre class="detail-guidance-text">${escapeHtml(task.phase_guidance)}</pre></div>` : ''}
${task.state === 'blocked' && task.blocked_action_items && task.blocked_action_items.length > 0 ? `<div class="detail-section detail-action-items"><h4>📋 Action Items</h4><ul class="detail-action-list">${task.blocked_action_items.map(item => `<li>${escapeHtml(item)}</li>`).join('')}</ul></div>` : ''}
<div class="detail-section"><h4>Artifacts</h4><div class="detail-artifacts">${artifactsHtml}</div></div>
<div class="detail-section"><h4>Review</h4>
<span class="review-badge ${reviewStatus}">${reviewStatusText}</span>
${reviewComment}
<textarea class="review-textarea" id="review-comment-${task.name}" placeholder="Optional comment..." rows="2"></textarea>
<div class="review-actions">
${reviewActionsHtml}
</div>
</div>
${task.sub_tasks.length > 0 ? `<div class="detail-section"><h4>Sub-tasks (${task.sub_tasks.filter(st => st.has_verdict).length}/${task.sub_tasks.length})</h4>
<ul class="detail-subtask-list">${task.sub_tasks.map(st => {
const stStatus = st.has_verdict && st.verdict_status === 'PASS' ? 'pass' : st.has_verdict && st.verdict_status === 'FAIL' ? 'fail' : 'incomplete';
@@ -522,25 +489,6 @@ async function renderAgentTab() {
_startAgentPoller();
}
async function submitReview(taskName, status) {
const textarea = document.getElementById(`review-comment-${taskName}`);
const comment = textarea ? textarea.value : '';
try {
const res = await fetch(`/api/task/${taskName}/review`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status, comment }),
});
const data = await res.json();
if (data.success) {
closeDetail();
await refreshData();
}
} catch (err) {
console.error('Review submission failed:', err);
}
}
async function approvePhase(taskName) {
try {
const res = await fetch(`/api/approve/${taskName}`, { method: 'POST' });
@@ -559,9 +507,6 @@ async function approvePhase(taskName) {
function getFilteredTasks() {
let filtered = [...state.tasks];
if (state.filterPhase !== 'all') filtered = filtered.filter(t => t.state === state.filterPhase);
if (state.filterReview === 'pending') filtered = filtered.filter(t => !t.review || t.review.status === 'pending');
else if (state.filterReview === 'approved') filtered = filtered.filter(t => t.review && t.review.status === 'approved');
else if (state.filterReview === 'changes_requested') filtered = filtered.filter(t => t.review && t.review.status === 'changes_requested');
if (state.filterWave === 'has-waves') filtered = filtered.filter(t => t.sub_tasks.length > 0);
else if (state.filterWave === 'no-waves') filtered = filtered.filter(t => t.sub_tasks.length === 0);
if (state.searchQuery) {
@@ -640,16 +585,9 @@ function setupUI() {
document.getElementById('btn-close-detail').addEventListener('click', closeDetail);
document.getElementById('detail-overlay').addEventListener('click', (e) => { if (e.target === e.currentTarget) closeDetail(); });
document.getElementById('filter-phase').addEventListener('change', (e) => { state.filterPhase = e.target.value; renderCurrentView(); });
document.getElementById('filter-review').addEventListener('change', (e) => { state.filterReview = e.target.value; renderCurrentView(); });
document.getElementById('filter-wave').addEventListener('change', (e) => { state.filterWave = e.target.value; renderCurrentView(); });
document.getElementById('search-input').addEventListener('input', (e) => { state.searchQuery = e.target.value; renderCurrentView(); });
document.addEventListener('click', (e) => {
const btn = e.target.closest('.review-btn');
if (btn) {
const taskName = btn.dataset.task;
const status = btn.dataset.status;
if (taskName && status) submitReview(taskName, status);
}
const approveBtn = e.target.closest('.approve-phase-btn');
if (approveBtn) {
const taskName = approveBtn.dataset.task;
-10
View File
@@ -26,7 +26,6 @@
<span class="stat-wip">WIP: <strong id="wip-tasks">0</strong></span>
<span class="stat-done">Done: <strong id="done-tasks">0</strong></span>
<span class="stat-blocked">Blocked: <strong id="blocked-tasks">0</strong></span>
<span class="stat-pending">Pending: <strong id="pending-review">0</strong></span>
</div>
<div class="header-controls">
<button class="btn btn-icon" id="btn-refresh" title="Refresh">↻</button>
@@ -50,15 +49,6 @@
<option value="blocked">Blocked</option>
</select>
</div>
<div class="filter-group">
<label>Review:</label>
<select id="filter-review">
<option value="all">All</option>
<option value="pending">Pending</option>
<option value="approved">Approved</option>
<option value="changes_requested">Changes Requested</option>
</select>
</div>
<div class="filter-group">
<label>Waves:</label>
<select id="filter-wave">
-13
View File
@@ -305,21 +305,8 @@ kbd {
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--beerus); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--border-active); }
.review-badge { font-size: 12px; margin-left: 4px; }
.review-badge.approved { color: var(--success); }
.review-badge.changes_requested { color: var(--error); }
.review-badge.pending { color: var(--warning); }
.review-actions { display: flex; gap: 10px; margin-top: 10px; }
.review-btn { padding: 7px 16px; border: 1px solid var(--border-color); border-radius: var(--radius-sm); cursor: pointer; font-size: 12px; transition: all 0.15s; background: var(--bg-card); color: var(--text-primary); font-family: inherit; font-weight: 500; }
.review-btn.approve { border-color: var(--success); color: var(--success); }
.review-btn.approve:hover { background: var(--success-bg); }
.review-btn.changes { border-color: var(--error); color: var(--error); }
.review-btn.changes:hover { background: var(--error-bg); }
.approve-phase-btn { display: inline-block; margin-left: 8px; padding: 4px 12px; border: 1px solid var(--warning); border-radius: var(--radius-sm); cursor: pointer; font-size: 11px; font-weight: 500; background: var(--warning-bg); color: var(--warning); font-family: inherit; transition: all 0.15s; }
.approve-phase-btn:hover { filter: brightness(1.1); }
.review-comment { font-size: 12px; color: var(--text-secondary); padding: 10px 12px; background: var(--bg-primary); border-radius: var(--radius-sm); margin-top: 6px; border-left: 2px solid var(--border-color); }
.review-textarea { width: 100%; margin-top: 8px; padding: 10px; background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 12px; font-family: inherit; resize: vertical; }
.review-textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 2px rgba(138,180,248,0.2); }
.task-card-artifacts { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.artifact-badge { font-size: 10px; padding: 2px 8px; background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 4px; color: var(--text-secondary); font-family: 'SF Mono', 'Fira Code', monospace; font-weight: 500; }
+1 -1
View File
@@ -1 +1 @@
research:awaiting_approval
research:approved
@@ -0,0 +1 @@
research:approved|2026-06-25T15:15:10.847478+00:00|user
+1 -1
View File
@@ -1 +1 @@
research:awaiting_approval
research:approved
@@ -0,0 +1 @@
research:approved|2026-06-25T15:23:32.644024+00:00|user
+1 -1
View File
@@ -1 +1 @@
research:awaiting_approval
decomposition
@@ -0,0 +1 @@
research:approved|2026-06-25T15:01:16.837463+00:00|user