taskplane 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,9 +17,11 @@ function formatDuration(ms) {
17
17
  return `${m}m ${String(s).padStart(2, "0")}s`;
18
18
  }
19
19
 
20
- function relativeTime(epochMs) {
21
- if (!epochMs) return "";
22
- const diff = Date.now() - epochMs;
20
+ function relativeTime(epochOrIso) {
21
+ if (!epochOrIso) return "";
22
+ const ts = typeof epochOrIso === "string" ? new Date(epochOrIso).getTime() : epochOrIso;
23
+ if (isNaN(ts)) return "";
24
+ const diff = Date.now() - ts;
23
25
  if (diff < 60000) return `${Math.floor(diff / 1000)}s ago`;
24
26
  if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
25
27
  return `${Math.floor(diff / 3600000)}h ago`;
@@ -308,9 +310,17 @@ function renderSummary(batch) {
308
310
  let batchChecked = 0, batchTotal = 0;
309
311
  const waveStats = wavePlan.map((taskIds, waveIdx) => {
310
312
  let wChecked = 0, wTotal = 0;
313
+ let allSucceeded = taskIds.length > 0;
311
314
  for (const tid of taskIds) {
312
315
  const t = taskMap.get(tid);
313
- if (t && t.statusData) {
316
+ if (!t || t.status !== "succeeded") allSucceeded = false;
317
+ if (t && t.status === "succeeded" && t.statusData) {
318
+ // Succeeded task with statusData: count as fully done even if
319
+ // STATUS.md checkboxes weren't all ticked before .DONE was created
320
+ const total = t.statusData.total || 1;
321
+ wChecked += total;
322
+ wTotal += total;
323
+ } else if (t && t.statusData) {
314
324
  wChecked += t.statusData.checked || 0;
315
325
  wTotal += t.statusData.total || 0;
316
326
  } else if (t && t.status === "succeeded") {
@@ -322,7 +332,7 @@ function renderSummary(batch) {
322
332
  }
323
333
  batchChecked += wChecked;
324
334
  batchTotal += wTotal;
325
- return { waveIdx, taskIds, checked: wChecked, total: wTotal };
335
+ return { waveIdx, taskIds, checked: wChecked, total: wTotal, allSucceeded };
326
336
  });
327
337
 
328
338
  const overallPct = batchTotal > 0 ? Math.round((batchChecked / batchTotal) * 100) : 0;
@@ -333,15 +343,19 @@ function renderSummary(batch) {
333
343
  for (const ws of waveStats) {
334
344
  const segWidthPct = batchTotal > 0 ? (ws.total / batchTotal) * 100 : (100 / waveStats.length);
335
345
  const fillPct = ws.total > 0 ? (ws.checked / ws.total) * 100 : 0;
336
- const isDone = ws.checked === ws.total && ws.total > 0;
337
- const isCurrent = ws.waveIdx === currentWaveIdx && batch.phase === "executing";
346
+ const checkboxDone = ws.checked === ws.total && ws.total > 0;
347
+ const pastWave = ws.waveIdx < currentWaveIdx;
348
+ const batchDone = batch.phase === "completed" || batch.phase === "merging";
349
+ const isDone = checkboxDone || pastWave || batchDone || ws.allSucceeded;
350
+ const isCurrent = ws.waveIdx === currentWaveIdx && (batch.phase === "executing" || batch.phase === "merging");
338
351
  const isFuture = ws.waveIdx > currentWaveIdx && batch.phase === "executing";
339
352
 
340
353
  const fillClass = isDone ? "pct-hi" : fillPct > 50 ? "pct-mid" : fillPct > 0 ? "pct-low" : "pct-0";
354
+ const fillWidth = isDone ? 100 : fillPct;
341
355
  const segClass = isCurrent ? "wave-seg-current" : isFuture ? "wave-seg-future" : "";
342
356
 
343
357
  barHtml += `<div class="wave-seg ${segClass}" style="width:${segWidthPct.toFixed(1)}%" title="W${ws.waveIdx + 1}: ${ws.checked}/${ws.total} checkboxes (${ws.taskIds.join(', ')})">`;
344
- barHtml += ` <div class="wave-seg-fill ${fillClass}" style="width:${fillPct.toFixed(1)}%"></div>`;
358
+ barHtml += ` <div class="wave-seg-fill ${fillClass}" style="width:${fillWidth.toFixed(1)}%"></div>`;
345
359
  barHtml += ` <span class="wave-seg-label">W${ws.waveIdx + 1}</span>`;
346
360
  barHtml += `</div>`;
347
361
  }
@@ -699,17 +713,16 @@ function renderNoBatch() {
699
713
  if ($mergePanel) $mergePanel.style.display = "none";
700
714
  if ($errorsPanel) $errorsPanel.style.display = "none";
701
715
 
702
- // Try to show the latest history entry
703
- if (historyList.length > 0 && !viewingHistoryId) {
704
- viewHistoryEntry(historyList[0].batchId);
705
- $historySelect.value = historyList[0].batchId;
706
- } else if (historyList.length === 0) {
707
- // No history yet — show placeholder in the history panel
716
+ // Show a placeholder while history loads. loadHistoryList() (called in
717
+ // render() just before this) is async — the fresh list may not be
718
+ // available yet. The loadHistoryList callback will replace this with
719
+ // the actual latest entry once it resolves.
720
+ if (!viewingHistoryId) {
708
721
  $historyBody.innerHTML = `
709
722
  <div class="no-batch">
710
723
  <div class="no-batch-icon">⏳</div>
711
- <div class="no-batch-title">No batch running</div>
712
- <div class="no-batch-hint">.pi/batch-state.json not found<br>Start an orchestrator batch to see the dashboard.</div>
724
+ <div class="no-batch-title">Batch complete</div>
725
+ <div class="no-batch-hint">Loading history…</div>
713
726
  </div>`;
714
727
  $historyPanel.style.display = "";
715
728
  }
@@ -1393,10 +1406,22 @@ function loadHistoryList() {
1393
1406
  .then(list => {
1394
1407
  historyList = list || [];
1395
1408
  renderHistoryDropdown();
1396
- // If no live batch and no history shown yet, auto-select latest
1397
- if (noBatchRendered && !viewingHistoryId && historyList.length > 0) {
1409
+ // Auto-select the latest history entry when no live batch is running.
1410
+ // Always update the view here renderNoBatch() shows a placeholder
1411
+ // while this async fetch completes, so we need to replace it with
1412
+ // the actual latest entry. This fixes #20 where the stale cached
1413
+ // historyList caused the previous batch to be shown.
1414
+ if (noBatchRendered && historyList.length > 0) {
1398
1415
  viewHistoryEntry(historyList[0].batchId);
1399
1416
  $historySelect.value = historyList[0].batchId;
1417
+ } else if (noBatchRendered && historyList.length === 0) {
1418
+ $historyBody.innerHTML = `
1419
+ <div class="no-batch">
1420
+ <div class="no-batch-icon">⏳</div>
1421
+ <div class="no-batch-title">No batch running</div>
1422
+ <div class="no-batch-hint">.pi/batch-state.json not found<br>Start an orchestrator batch to see the dashboard.</div>
1423
+ </div>`;
1424
+ $historyPanel.style.display = "";
1400
1425
  }
1401
1426
  })
1402
1427
  .catch(() => {});