diff --git a/frontend/dashboard.css b/frontend/dashboard.css
index 01c78c9..b013d2f 100644
--- a/frontend/dashboard.css
+++ b/frontend/dashboard.css
@@ -264,6 +264,26 @@ textarea { resize: vertical; min-height: 120px; }
.tomorrow-conflict-plans { grid-template-columns:1fr; }
.tomorrow-conflict-actions { flex-direction:column; }
}
+.week-conflict-review { margin-top:14px; }
+.week-conflict-plans { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; }
+.week-conflict-plans > section { min-width:0; padding:12px; border:1px solid #31577f; border-radius:12px; background:#10233a; }
+.week-conflict-plans h4 { margin:0 0 8px; }
+.week-conflict-day { margin:0 0 10px; padding-bottom:10px; border-bottom:1px solid #31577f; }
+.week-conflict-day:last-child { margin-bottom:0; padding-bottom:0; border-bottom:0; }
+.week-conflict-day strong { display:block; color:#bfdbfe; }
+.week-conflict-actions { display:flex; gap:8px; margin-top:14px; }
+.week-conflict-actions button { min-height:44px; flex:1 1 0; }
+.week-conflict-status { min-height:1.4em; margin-top:8px; }
+.plan-today-sheet.week-conflict-mode .week-plan-dates,
+.plan-today-sheet.week-conflict-mode .mobile-plan-today-nav,
+.plan-today-sheet.week-conflict-mode #plan-today-fit,
+.plan-today-sheet.week-conflict-mode #plan-today-selected,
+.plan-today-sheet.week-conflict-mode #plan-today-available-work,
+.plan-today-sheet.week-conflict-mode .plan-today-actions { display:none; }
+@media (max-width:480px) {
+ .week-conflict-plans { grid-template-columns:1fr; }
+ .week-conflict-actions { flex-direction:column; }
+}
.mobile-plan-today-nav { position:sticky; top:0; z-index:5; display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:4px; margin:0 -18px 10px; padding:4px 18px; background:rgba(11,21,38,.98); border-block:1px solid #2a496e; }
.mobile-plan-today-nav button { min-width:0; min-height:44px; padding:4px; border-color:transparent; font-size:12px; }
.mobile-plan-today-nav button[aria-current="location"] { color:#bfdbfe; background:#17365a; border-color:#31577f; }
diff --git a/frontend/dashboard.js b/frontend/dashboard.js
index 2189cfb..a409577 100644
--- a/frontend/dashboard.js
+++ b/frontend/dashboard.js
@@ -391,6 +391,7 @@
});
const weekPlan = createWeekPlan({
fetchJson:fetchReviewJson,localDate:todayRollover.localDate,timeZone:todayRollover.timeZone,
+ storage:localStorage,getLogin:() => planningOwnerLogin,
});
function openWeekPlanner(trigger) { planningTomorrow=false; return weekWorkflow.open(trigger); }
function renderTomorrowQueueSummary(value) {
@@ -511,6 +512,7 @@
documentObject:document,
check:() => Promise.all([
syncPendingTomorrow(),
+ weekPlan.pending() ? weekPlan.flush().catch(() => false) : false,
latestTodayPlan ? weekWorkflow.promote(latestTodayPlan) : false,
]),
});
@@ -2700,6 +2702,8 @@
weekWorkflow.clear();
qs('#tomorrow-conflict-review').hidden = true;
qs('#plan-today-sheet').classList.remove('tomorrow-conflict-mode');
+ qs('#week-conflict-review').hidden = true;
+ qs('#plan-today-sheet').classList.remove('week-conflict-mode');
qs('#plan-today-sheet').hidden = true;
document.body.classList.remove('task-overlay-open');
if (planTodayTrigger?.dataset.mobileQueue === 'tomorrow') {
@@ -3059,6 +3063,32 @@
requestAnimationFrame(() => keep.focus());
}
+ function weekConflictMarkup(value) {
+ const days=value?.days||[];
+ if(!days.length)return '
Nothing planned.
';
+ return days.map(day=>{
+ const estimates=day.estimates||{};
+ const minutes=(day.ids||[]).reduce((total,id)=>total+(Number(estimates[id])||0),0);
+ const capacity=Number(day.capacity_minutes)||0;
+ const detail=(day.ids||[]).length+' item'+((day.ids||[]).length===1?'':'s')+
+ (minutes&&capacity?' · '+minutes+' of '+capacity+' min':'');
+ return ''+escapeHtml(day.plan_date)+''+escapeHtml(detail)+'
';
+ }).join('');
+ }
+
+ function showWeekConflict(conflict) {
+ qs('#plan-today-title').textContent='Resolve Week Ahead conflict';
+ qs('#week-conflict-phone-plan').innerHTML=weekConflictMarkup(conflict.local);
+ qs('#week-conflict-server-plan').innerHTML=weekConflictMarkup(conflict.remote);
+ qs('#week-conflict-status').textContent='';
+ qs('#week-conflict-review').hidden=false;
+ qs('#plan-today-sheet').classList.add('week-conflict-mode');
+ qs('#plan-today-sheet').hidden=false;
+ document.body.classList.add('task-overlay-open');
+ const keep=qs('#keep-phone-week');
+ keep.focus();requestAnimationFrame(()=>keep.focus());
+ }
+
function openPlanToday(trigger, navigate = true, actualMinutes = null) {
if (!planningOwnerLogin && !weekWorkflow.active()) {
qs('#my-work-action-status').textContent = 'Planning is unavailable until your operator identity is restored.';
@@ -3078,8 +3108,12 @@
showTomorrowConflict(conflict);
return;
}
+ const weekConflict=weekWorkflow.active()?weekPlan.conflict():null;
+ if(weekConflict){showWeekConflict(weekConflict);return;}
qs('#tomorrow-conflict-review').hidden = true;
qs('#plan-today-sheet').classList.remove('tomorrow-conflict-mode');
+ qs('#week-conflict-review').hidden=true;
+ qs('#plan-today-sheet').classList.remove('week-conflict-mode');
const recommendations = actualMinutes || pendingPlanActualMinutes || todayRecapView.pendingReplan()?.actual_minutes;
pendingPlanActualMinutes = null;
const protectProposal = pendingProtectToday;
@@ -7832,6 +7866,30 @@
qs('#my-work-action-status').textContent = 'Saved account Tomorrow plan selected. Today was not changed.';
closePlanToday();
});
+ qs('#keep-phone-week').addEventListener('click', async () => {
+ const keep=qs('#keep-phone-week'),use=qs('#use-server-week');
+ keep.disabled=true;use.disabled=true;
+ qs('#week-conflict-status').textContent='Saving this phone’s complete week…';
+ try {
+ await weekPlan.keepLocal();
+ qs('#mobile-week-summary').textContent=weekPlan.summary();
+ qs('#my-work-action-status').textContent='This phone’s Week Ahead plan is saved to your account. Today was not changed.';
+ closePlanToday();
+ } catch(error) {
+ const conflict=weekPlan.conflict();
+ if(conflict)showWeekConflict(conflict);
+ qs('#week-conflict-status').textContent=error?.status===409?
+ 'Week Ahead changed again. Both latest weeks are preserved; choose again.':
+ `${error.message||'Week Ahead could not be saved.'} Both weeks are still preserved.`;
+ } finally {keep.disabled=false;use.disabled=false;}
+ });
+ qs('#use-server-week').addEventListener('click', () => {
+ const adopted=weekPlan.useRemote();
+ if(!adopted)return;
+ qs('#mobile-week-summary').textContent=weekPlan.summary();
+ qs('#my-work-action-status').textContent='Saved account Week Ahead plan selected. Today was not changed.';
+ closePlanToday();
+ });
qs('#cancel-plan-today').addEventListener('click', closePlanToday);
qs('#plan-today-sheet').addEventListener('click', event => {
if (event.target === qs('#plan-today-sheet')) closePlanToday();
diff --git a/frontend/index.html b/frontend/index.html
index c782d61..4aa15d7 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -481,6 +481,26 @@
+
+ Cross-device change
+ Choose which Week Ahead plan to keep
+ Both complete weeks stay preserved until one choice succeeds. Today will not change.
+
+
+
+ Saved on your account
+
+
+
+
+
+
+
+
+