diff --git a/frontend/dashboard.css b/frontend/dashboard.css
index 761e1d9..3dfb3fc 100644
--- a/frontend/dashboard.css
+++ b/frontend/dashboard.css
@@ -241,6 +241,7 @@ textarea { resize: vertical; min-height: 120px; }
.plan-today-header { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; }
.plan-today-header h2, .plan-today-header p { margin-top:0; }
.plan-today-header button { min-width:44px; min-height:44px; }
+.week-plan-progress { margin:8px 0 4px; color:#bfdbfe; font-weight:700; }
.week-plan-dates { display:flex; gap:8px; margin:8px 0 12px; padding:2px 0 8px; overflow-x:auto; overscroll-behavior-inline:contain; }
.week-plan-dates[hidden] { display:none; }
.week-plan-dates button { min-height:44px; min-width:86px; flex:0 0 auto; padding:6px 10px; }
diff --git a/frontend/dashboard.js b/frontend/dashboard.js
index 141e45b..507ce06 100644
--- a/frontend/dashboard.js
+++ b/frontend/dashboard.js
@@ -3120,8 +3120,8 @@
qs('#plan-today-error').textContent = '';
qs('#plan-today-sheet').hidden = false;
document.body.classList.add('task-overlay-open');
- weekWorkflow.renderDates();
renderPlanToday();
+ weekWorkflow.renderDates();
if (protectProposal) qs('#plan-today-build-status').textContent = protectProposal.summary +
(protectProposal.displaced.length ? '. Displaced work remains unchanged until you save.' : '. Review estimates and capacity before saving.');
qs('#cancel-plan-today').focus();
@@ -7920,7 +7920,7 @@
const result = planToday.commit({ start, confirmOverCapacity: button.dataset.confirmOverCapacity === 'true' });
if (result === 'saved') {
button.dataset.confirmOverCapacity = '';
- taskOverlayHistory.leave();
+ if (!weekWorkflow.active() || !weekWorkflow.advance()) taskOverlayHistory.leave();
} else if (result === 'confirm-over-capacity') {
button.dataset.confirmOverCapacity = 'true';
qs('#plan-today-error').textContent = 'This plan exceeds your available time. Press again to save over capacity.';
diff --git a/frontend/index.html b/frontend/index.html
index 85cd60b..6bd717b 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -460,6 +460,7 @@
Plan Today
Choose and order the work you want to finish next.
+
Cross-device change
diff --git a/frontend/week-plan.js b/frontend/week-plan.js
index 6403d0e..030da88 100644
--- a/frontend/week-plan.js
+++ b/frontend/week-plan.js
@@ -49,6 +49,12 @@ function createWeekPlan({fetchJson,localDate,timeZone,storage,getLogin}={}) {
const found=week.days.find(item=>item.plan_date===planDate);
return found?cloneDay(found):{plan_date:planDate,ids:[],capacity_minutes:null,estimates:{}};
}
+ function pass(planDate) {
+ const available=dates(),index=available.findIndex(item=>item.date===planDate);
+ if(index<0) return null;
+ return {position:index+1,total:available.length,planned:week.days.filter(item=>item.ids.length).length,
+ next_date:available[index+1]?.date||null,last:index===available.length-1};
+ }
async function load() {
const queued=pending();
if(queued){week=queued;return state();}
@@ -231,13 +237,21 @@ function createWeekPlan({fetchJson,localDate,timeZone,storage,getLogin}={}) {
const label=planned.length?`${items} item${items===1?'':'s'} across ${planned.length} day${planned.length===1?'':'s'}`:'Nothing planned';
return label+(pending()?' · sync pending':'');
}
- return {adopt,state,dates,day,load,saveDay,stageDay,pending,flush,conflict,chooseDay,saveMerged,
+ return {adopt,state,dates,day,pass,load,saveDay,stageDay,pending,flush,conflict,chooseDay,saveMerged,
keepLocal,useRemote,promote,summary};
}
function createWeekPlanWorkflow({controller,qs,getLogin,openPlanner,escapeHtml,escapeAttribute,
todayWork,refresh,warm}={}) {
let selectedDate=null;
let blockedReviewOpen=false;
+ function renderPass() {
+ const progress=qs('#week-plan-progress'),save=qs('#save-today-plan');
+ if(!selectedDate){progress.hidden=true;return;}
+ const current=controller.pass(selectedDate);
+ progress.hidden=false;
+ progress.textContent=`Day ${current.position} of ${current.total} · ${current.planned} planned`;
+ save.textContent=current.last?'Save week':'Save & next';
+ }
function conflictDetail(day) {
const estimates=day.estimates||{},ids=day.ids||[];
const minutes=ids.reduce((total,id)=>total+(Number(estimates[id])||0),0),capacity=Number(day.capacity_minutes)||0;
@@ -273,6 +287,7 @@ function createWeekPlanWorkflow({controller,qs,getLogin,openPlanner,escapeHtml,e
function renderDates() {
const root=qs('#week-plan-dates');
root.hidden=!selectedDate;
+ renderPass();
if(!selectedDate)return;
root.innerHTML=controller.dates().map(item=>'