diff --git a/README.md b/README.md
index 395ffc6..b9f967f 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ and an active Today session shows the current estimate plus estimated remaining
first previews its Gitea dependencies: unresolved blockers are listed with links and require the
explicit **Add blocked item anyway** override, while an unavailable dependency lookup is reported
as unknown rather than unblocked. Starting a Today work session also
-stores an account-bound checkpoint on the current device and starts an account-bound actual-time timer for the exact item. The sticky mobile session controls show elapsed time beside the estimate and let the operator pause or resume it. Switching items preserves each item's elapsed value, while wall-clock checkpoints keep a running timer accurate through app backgrounding, reloads, and installed-app restarts without double counting. **End session** stops accumulation but retains measured time with the private device data. The recap identifies each item by title and repository, reports per-item estimate variance, and **Save recap & adjust plan** continues into the current ordered Today plan. Actual time appears there as an explicit estimate recommendation; it changes only the planning draft until the operator chooses **Save plan** or **Save & start**. The recap and any corrected actual minutes are also saved as an account-bound device draft: an offline save failure can survive a reload and retry with the same idempotent session ID, while another account cannot view it. The draft and timer are cleared only after the account confirms the recap.
+stores an account-bound checkpoint on the current device and starts an account-bound actual-time timer for the exact item. The sticky mobile session controls show elapsed time beside the estimate and let the operator pause or resume it. Switching items preserves each item's elapsed value, while wall-clock checkpoints keep a running timer accurate through app backgrounding, reloads, and installed-app restarts without double counting. **End session** stops accumulation but retains measured time with the private device data. The recap identifies each item by title and repository, reports per-item estimate variance, and **Save recap & adjust plan** continues into the current ordered Today plan. Actual time appears there as an explicit estimate recommendation; it changes only the planning draft until the operator chooses **Save plan** or **Save & start**. After the recap is confirmed, this recommendation handoff remains account-bound on the device through reloads, app restarts, planner cancellation, and failed plan admission. Opening **Plan Today** resumes it without reposting the recap; a successful plan save clears it, while **Discard recap feedback** removes only the handoff and leaves recap history unchanged. The recap and any corrected actual minutes are also saved as an account-bound device draft: an offline save failure can survive a reload and retry with the same idempotent session ID, while another account cannot view it. The draft and timer are cleared only after the account confirms the recap.
After a reload or installed-app
restart, **Resume Today** reopens the saved item (or the next surviving item if work changed);
**Comment & next** on that current issue or pull request posts the handoff online or admits it
diff --git a/frontend/dashboard.css b/frontend/dashboard.css
index f3c25d5..044f5ee 100644
--- a/frontend/dashboard.css
+++ b/frontend/dashboard.css
@@ -127,6 +127,7 @@ textarea { resize: vertical; min-height: 120px; }
.plan-today-available input, .plan-today-estimate { box-sizing:border-box; min-height:44px; width:108px; }
.plan-today-estimate-wrap { display:flex; align-items:center; gap:6px; margin-top:8px; }
.plan-today-error { min-height:1.4em; color:#fca5a5; }
+.discard-recap-replan { min-height:44px; margin-bottom:8px; }
.plan-today-list, .plan-today-candidates { display:grid; gap:8px; }
.plan-today-item { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:8px; align-items:center; padding:10px; border:1px solid #1f3a5f; border-radius:12px; background:#0f1d33; }
.plan-today-item-copy { min-width:0; overflow-wrap:anywhere; }
diff --git a/frontend/dashboard.js b/frontend/dashboard.js
index 21f697a..e27ea34 100644
--- a/frontend/dashboard.js
+++ b/frontend/dashboard.js
@@ -1375,6 +1375,7 @@
if (capacityAware && !todaySync.enqueueConfiguration(plan.capacity_minutes, plan.estimates)) return false;
if (!todayWork.replace(ids)) return false;
if (capacityAware && !todayWork.replacePlanning(plan)) return false;
+ todayRecapView.completeReplan();
refreshMyWorkView();
todaySync.flush();
warmTodayOffline();
@@ -1640,9 +1641,10 @@
taskOverlayHistory.open('plan-today');
return;
}
- const recommendations = actualMinutes || pendingPlanActualMinutes;
+ const recommendations = actualMinutes || pendingPlanActualMinutes || todayRecapView.pendingReplan()?.actual_minutes;
pendingPlanActualMinutes = null;
planToday.open(todayMyWork, activeMyWork, todayWork.planning(), recommendations);
+ qs('#discard-recap-replan').hidden = !todayRecapView.pendingReplan();
qs('#plan-today-error').textContent = '';
qs('#plan-today-sheet').hidden = false;
document.body.classList.add('task-overlay-open');
@@ -1650,6 +1652,14 @@
qs('#cancel-plan-today').focus();
}
+ qs('#discard-recap-replan').addEventListener('click', () => {
+ todayRecapView.discardReplan();
+ planToday.open(todayMyWork, activeMyWork, todayWork.planning());
+ qs('#discard-recap-replan').hidden = true;
+ qs('#plan-today-error').textContent = 'Recap feedback discarded. Your saved recap is unchanged.';
+ renderPlanToday();
+ });
+
const detailDefer = createDetailDefer({
laterWork,
session: workSession,
diff --git a/frontend/index.html b/frontend/index.html
index 3ae471b..33289f4 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -251,6 +251,7 @@
min