`;
if (data.status === 'interviewing' || data.status === 'review') {
- // Show raw MEMORY.md content during interview/review
if (data.memory) {
- html += '
' + renderSimpleMarkdown(data.memory) + '
';
+ html += `
${renderSimpleMarkdown(data.memory)}
`;
+ }
+ if (data.status === 'review') {
+ html += `
+
+
»
+
Slide to Approve
+
+
`;
}
} else {
- // Show structured phase/step list during executing
if (data.phases && data.phases.length > 0) {
html += renderPhases(data.phases, data.current_phase);
}
}
el.innerHTML = html;
+ if (data.status === 'review') setupSlideApprove();
+}
+
+var slideApproveAC = null;
+function setupSlideApprove() {
+ if (slideApproveAC) slideApproveAC.abort();
+ slideApproveAC = new AbortController();
+ var signal = slideApproveAC.signal;
+
+ var track = document.querySelector('.slide-approve-track');
+ if (!track) return;
+ var thumb = track.querySelector('.slide-approve-thumb');
+ var label = track.querySelector('.slide-approve-label');
+ var dragging = false;
+ var startX = 0;
+ var thumbStartLeft = 0;
+
+ function getMaxLeft() {
+ return track.offsetWidth - thumb.offsetWidth - 4; // 2px padding each side
+ }
+
+ function onStart(e) {
+ if (track.classList.contains('approved')) return;
+ dragging = true;
+ thumb.classList.add('dragging');
+ var clientX = e.touches ? e.touches[0].clientX : e.clientX;
+ startX = clientX;
+ thumbStartLeft = thumb.offsetLeft - 2; // subtract initial 2px offset
+ e.preventDefault();
+ }
+
+ function onMove(e) {
+ if (!dragging) return;
+ var clientX = e.touches ? e.touches[0].clientX : e.clientX;
+ var dx = clientX - startX;
+ var newLeft = Math.max(0, Math.min(thumbStartLeft + dx, getMaxLeft()));
+ thumb.style.left = (newLeft + 2) + 'px';
+ e.preventDefault();
+ }
+
+ function onEnd(e) {
+ if (!dragging) return;
+ dragging = false;
+ thumb.classList.remove('dragging');
+ var currentLeft = thumb.offsetLeft - 2;
+ var maxLeft = getMaxLeft();
+ if (currentLeft >= maxLeft * 0.8) {
+ // Approved
+ track.classList.add('approved');
+ label.textContent = 'Approved!';
+ thumb.classList.add('hidden');
+ sendCommand('/plan start');
+ } else {
+ // Snap back
+ thumb.style.left = '2px';
+ }
+ }
+
+ thumb.addEventListener('touchstart', onStart, { passive: false, signal: signal });
+ thumb.addEventListener('mousedown', onStart, { signal: signal });
+ document.addEventListener('touchmove', onMove, { passive: false, signal: signal });
+ document.addEventListener('mousemove', onMove, { signal: signal });
+ document.addEventListener('touchend', onEnd, { signal: signal });
+ document.addEventListener('mouseup', onEnd, { signal: signal });
}
function renderSimpleMarkdown(text) {
@@ -723,26 +848,24 @@ function renderSimpleMarkdown(text) {
return out.join('');
}
-async function loadPlan() {
- var loading = document.getElementById('plan-loading');
- var el = document.getElementById('plan-content');
- loading.style.display = 'block';
- loading.textContent = 'Loading plan...';
- el.style.display = 'none';
+async function loadTab(loadingId, contentId, label, fetchFn, renderFn) {
+ var loading = document.getElementById(loadingId);
+ var el = document.getElementById(contentId);
+ loading.classList.remove('hidden');
+ loading.textContent = 'Loading ' + label + '...';
+ el.classList.add('hidden');
+ try { renderFn(await fetchFn()); }
+ catch (e) { loading.textContent = 'Failed to load ' + label + '.'; }
+}
- try {
- var data = await apiFetch('/miniapp/api/plan');
- renderPlanFromData(data);
- } catch (e) {
- loading.textContent = 'Failed to load plan.';
- loading.style.display = 'block';
- el.style.display = 'none';
- }
+function loadPlan() {
+ return loadTab('plan-loading', 'plan-content', 'plan',
+ function() { return apiFetch('/miniapp/api/plan'); },
+ renderPlanFromData);
}
function renderPhases(phases, currentPhase) {
- let html = '';
- for (const phase of phases) {
+ return phases.map(phase => {
const doneCount = phase.steps.filter(s => s.done).length;
const total = phase.steps.length;
@@ -758,30 +881,25 @@ function renderPhases(phases, currentPhase) {
indicator = String(phase.number);
}
- html += '
';
- html += '
';
- html += '
' + indicator + '
';
- html += '' + escapeHtml(phase.title || 'Phase ' + phase.number) + '';
- if (total > 0) {
- html += '' + doneCount + '/' + total + '';
- }
- html += '
`;
}
function renderSessionFromData(sessions, stats) {
var loading = document.getElementById('session-loading');
var el = document.getElementById('session-content');
- loading.style.display = 'none';
- el.style.display = 'block';
+ loading.classList.add('hidden');
+ el.classList.remove('hidden');
var html = renderActiveSessions(sessions);
@@ -912,43 +1017,33 @@ function renderSessionFromData(sessions, stats) {
var since = stats.since ? new Date(stats.since).toLocaleDateString() : 'N/A';
var today = stats.today || {};
- html +=
- '