M2: planning-view Sharpen control with non-clobbering coach updates

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 14:20:39 -04:00
parent 38a62e4c68
commit c61c9d46b1
+38 -1
View File
@@ -38,6 +38,7 @@
<script> <script>
const view = document.getElementById('view'); const view = document.getElementById('view');
let countdownTimer = null; let countdownTimer = null;
let renderedState = null; // which runtime_state the DOM currently shows
function post(path, body) { function post(path, body) {
return fetch(path, { return fetch(path, {
@@ -70,9 +71,36 @@ function evidenceBlock(ev) {
</div>`; </div>`;
} }
function updatePlanningCoach(coach) {
const statusEl = document.getElementById('coachStatus');
if (!statusEl) return;
if (!coach || coach.status === 'idle') { statusEl.textContent = ''; return; }
if (coach.status === 'pending') { statusEl.textContent = 'Sharpening…'; return; }
if (coach.status === 'error') { statusEl.textContent = coach.error || 'coach unavailable'; return; }
if (coach.status === 'ready' && coach.proposal) {
statusEl.textContent = 'AI suggestion applied — edit freely.';
// Pre-fill once per ready proposal so later manual edits are not clobbered
// by subsequent SSE ticks (idempotent via the dataset stamp below).
const stamp = JSON.stringify(coach.proposal);
if (statusEl.dataset.applied === stamp) return;
statusEl.dataset.applied = stamp;
const na = document.getElementById('na'), sc = document.getElementById('sc'),
mins = document.getElementById('mins'), start = document.getElementById('start');
if (na && !na.value.trim()) na.value = coach.proposal.next_action || '';
if (sc && !sc.value.trim()) sc.value = coach.proposal.success_condition || '';
if (mins && coach.proposal.timebox_secs) mins.value = Math.round(coach.proposal.timebox_secs / 60);
if (start) start.disabled = !(na.value.trim() && sc.value.trim() && +mins.value > 0);
}
}
function render(state) { function render(state) {
if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; }
const rs = state.runtime_state; const rs = state.runtime_state;
if (rs === 'planning' && renderedState === 'planning') {
updatePlanningCoach(state.coach); // partial update only
return;
}
if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; }
renderedState = rs;
if (rs === 'locked') { if (rs === 'locked') {
view.innerHTML = `<span class="pill">Locked</span> view.innerHTML = `<span class="pill">Locked</span>
<p class="meta">No active commitment.</p> <p class="meta">No active commitment.</p>
@@ -80,6 +108,10 @@ function render(state) {
document.getElementById('plan').onclick = () => post('/planning'); document.getElementById('plan').onclick = () => post('/planning');
} else if (rs === 'planning') { } else if (rs === 'planning') {
view.innerHTML = `<span class="pill">Planning</span> view.innerHTML = `<span class="pill">Planning</span>
<label>Rough intent</label>
<input id="intent" placeholder="e.g. work on the quarterly report">
<button id="sharpen" type="button">Sharpen</button>
<div id="coachStatus" class="meta"></div>
<label>Next action</label><input id="na" placeholder="What exactly will you do?"> <label>Next action</label><input id="na" placeholder="What exactly will you do?">
<label>Success condition</label><input id="sc" placeholder="How do you know it's done?"> <label>Success condition</label><input id="sc" placeholder="How do you know it's done?">
<label>Minutes</label><input id="mins" type="number" min="1" value="25"> <label>Minutes</label><input id="mins" type="number" min="1" value="25">
@@ -93,6 +125,11 @@ function render(state) {
success_condition: sc.value.trim(), success_condition: sc.value.trim(),
timebox_secs: Math.round(+mins.value * 60), timebox_secs: Math.round(+mins.value * 60),
}); });
document.getElementById('sharpen').onclick = () => {
const intent = document.getElementById('intent').value.trim();
if (intent) post('/coach', { intent });
};
updatePlanningCoach(state.coach);
} else if (rs === 'active') { } else if (rs === 'active') {
const c = state.commitment || {}; const c = state.commitment || {};
view.innerHTML = `<span class="pill">Active</span> view.innerHTML = `<span class="pill">Active</span>