Nothing planned.
';
- return ''+escapeHtml(day.label)+'
'+
+ return ''+escapeHtml(day.label)+'
'+
escapeHtml(load)+'
'+items+'';
}).join('');
duplicates.hidden=!value.duplicates.length;
@@ -381,10 +390,31 @@ function createWeekPlanWorkflow({controller,qs,getLogin,openPlanner,setReviewMod
controller.flush().then(()=>{renderReview();qs('#mobile-week-summary').textContent=controller.summary();})
.catch(error=>{qs('#week-review-status').textContent=(error.message||'Week Ahead sync is unavailable.')+' Changes remain on this phone.';});
}));
+ root.querySelectorAll('[data-week-edit-day]').forEach(button=>button.addEventListener('click',()=>editDay(button.dataset.weekEditDay)));
renderPass();
}
+ function editDay(planDate) {
+ if(!controller.dates().some(item=>item.date===planDate))return false;
+ reviewing=false;editingFromReview=true;selectedDate=planDate;setReviewMode(false);
+ qs('#week-review').hidden=true;
+ const back=qs('#back-to-week-review');if(back)back.hidden=false;
+ renderDates();openPlanner(null,false);
+ qs('#week-plan-progress')?.focus?.();
+ return true;
+ }
+ function returnToReview() {
+ if(!editingFromReview)return false;
+ const editedDate=selectedDate;
+ editingFromReview=false;reviewing=true;selectedDate=null;setReviewMode(true);
+ qs('#week-review').hidden=false;
+ const back=qs('#back-to-week-review');if(back)back.hidden=true;
+ renderDates();renderReview();openPlanner(null,false);
+ qs('#week-review-days').querySelector(`[data-week-edit-day="${editedDate}"]`)?.focus?.();
+ return true;
+ }
async function open(trigger) {
- trigger.disabled=true;reviewing=false;setReviewMode(false);const reviewRoot=qs('#week-review');if(reviewRoot)reviewRoot.hidden=true;
+ trigger.disabled=true;reviewing=false;editingFromReview=false;setReviewMode(false);const reviewRoot=qs('#week-review');if(reviewRoot)reviewRoot.hidden=true;
+ const back=qs('#back-to-week-review');if(back)back.hidden=true;
selectedDate=controller.dates()[0].date;renderDates();openPlanner(trigger);
qs('#mobile-week-summary').textContent='Loading Week Ahead…';
try{await controller.load();qs('#mobile-week-summary').textContent=controller.summary();renderDates();openPlanner(null,false);return true;}
@@ -408,6 +438,7 @@ function createWeekPlanWorkflow({controller,qs,getLogin,openPlanner,setReviewMod
return true;
}
function advance() {
+ if(editingFromReview)return returnToReview();
const current=selectedDate&&controller.pass(selectedDate);
if(current?.last){
reviewing=true;selectedDate=null;setReviewMode(true);qs('#week-review').hidden=false;
@@ -424,6 +455,7 @@ function createWeekPlanWorkflow({controller,qs,getLogin,openPlanner,setReviewMod
if(!reviewing||!value.can_confirm||controller.pending()) return false;
reviewing=false;setReviewMode(false);qs('#week-review').hidden=true;return true;
}
+ qs('#back-to-week-review')?.addEventListener('click',returnToReview);
async function promote(plan){
try{await controller.load();const promoted=await controller.promote(plan.revision);if(!promoted)return false;blockedReviewOpen=false;todayWork.replace(promoted.ids);todayWork.replacePlanning({capacity_minutes:promoted.capacity_minutes??null,estimates:promoted.estimates||{}});refresh();warm();qs('#mobile-week-summary').textContent=controller.summary();qs('#my-work-action-status').textContent='Your saved Week Ahead plan is now Today.';return true;}
catch(error){
@@ -440,7 +472,8 @@ function createWeekPlanWorkflow({controller,qs,getLogin,openPlanner,setReviewMod
reviewing:()=>reviewing,selectedDate:()=>selectedDate,
day:()=>selectedDate?controller.day(selectedDate):null,
copy:()=>selectedDate?{title:'Plan Week Ahead',heading:selectedDate+', in order',available:'Available this day',build:'Build this day'}:null,
- clear(){selectedDate=null;reviewing=false;setReviewMode(false);qs('#week-review').hidden=true;renderDates();}};
+ clear(){selectedDate=null;reviewing=false;editingFromReview=false;setReviewMode(false);qs('#week-review').hidden=true;
+ const back=qs('#back-to-week-review');if(back)back.hidden=true;renderDates();}};
}
if(typeof module!=='undefined'&&module.exports){
module.exports=createWeekPlan;
diff --git a/tests/e2e/test_mobile_week_ahead_release.py b/tests/e2e/test_mobile_week_ahead_release.py
index 92e80bd..e903c68 100644
--- a/tests/e2e/test_mobile_week_ahead_release.py
+++ b/tests/e2e/test_mobile_week_ahead_release.py
@@ -44,7 +44,11 @@ def test_release_artifact_plans_seven_touch_safe_mobile_dates(
}))
return
route.fulfill(status=200, content_type="application/json", body=json.dumps({
- "revision": 0, "timezone": None, "days": [],
+ "revision": 0, "timezone": None, "days": [{
+ "plan_date": (date.today() + timedelta(days=1)).isoformat(),
+ "ids": ["issue:acme/mobile:41:"], "capacity_minutes": 60,
+ "estimates": {"issue:acme/mobile:41:": 30},
+ }],
}))
page.route("**/api/v1/week", week_route)
@@ -67,18 +71,32 @@ def test_release_artifact_plans_seven_touch_safe_mobile_dates(
assert bounds and bounds["height"] >= 44
dates.nth(2).click()
expect(dates.nth(2)).to_have_attribute("aria-current", "date")
- expect(page.locator("#week-plan-progress")).to_have_text("Day 3 of 7 · 0 planned")
+ expect(page.locator("#week-plan-progress")).to_have_text("Day 3 of 7 · 1 planned")
expect(page.locator("#save-today-plan")).to_have_text("Save & next")
page.locator("#save-today-plan").click()
expect(page.locator("#plan-today-sheet")).to_be_visible()
expect(dates.nth(3)).to_have_attribute("aria-current", "date")
- expect(page.locator("#week-plan-progress")).to_have_text("Day 4 of 7 · 0 planned")
+ expect(page.locator("#week-plan-progress")).to_have_text("Day 4 of 7 · 1 planned")
dates.nth(6).click()
expect(page.locator("#save-today-plan")).to_have_text("Save week")
page.locator("#save-today-plan").click()
expect(page.locator("#plan-today-sheet")).to_be_visible()
expect(page.locator("#week-review")).to_be_visible()
expect(page.locator("#week-review-days .week-review-day")).to_have_count(7)
+ first_day = page.locator("#week-review-days .week-review-day").first
+ expect(first_day).to_contain_text("Ship mobile capture")
+ expect(first_day).to_contain_text("acme/mobile #41 · Issue · 30 min")
+ expect(first_day.locator("code")).to_have_count(0)
+ edit = first_day.locator("[data-week-edit-day]")
+ bounds = edit.bounding_box()
+ assert bounds and bounds["height"] >= 44
+ edit.click()
+ expect(page.locator("#week-review")).to_be_hidden()
+ expect(page.locator("#back-to-week-review")).to_be_visible()
+ expect(page.locator("#save-today-plan")).to_have_text("Save & review")
+ page.locator("#back-to-week-review").click()
+ expect(page.locator("#week-review")).to_be_visible()
+ expect(first_day).to_contain_text("Ship mobile capture")
page.wait_for_function("() => !document.querySelector('#confirm-week-plan').disabled")
confirm = page.locator("#confirm-week-plan")
bounds = confirm.bounding_box()
@@ -86,7 +104,7 @@ def test_release_artifact_plans_seven_touch_safe_mobile_dates(
confirm.click()
expect(page.locator("#plan-today-sheet")).to_be_hidden()
page.wait_for_function("() => document.querySelector('#mobile-week-summary').textContent !== 'Loading Week Ahead…'")
- assert saved and len(saved[-1]["days"]) == 2
+ assert saved and len(saved[-1]["days"]) == 3
assert [day["plan_date"] for day in saved[-1]["days"]] == sorted(
day["plan_date"] for day in saved[-1]["days"]
)
diff --git a/tests/test_week_plan_frontend.py b/tests/test_week_plan_frontend.py
index 0fe5e77..11be7cd 100644
--- a/tests/test_week_plan_frontend.py
+++ b/tests/test_week_plan_frontend.py
@@ -173,6 +173,70 @@ console.log(JSON.stringify({advanced,reviewing:workflow.reviewing(),reviewMode,
assert "shared" in result["duplicates"]
+def test_week_review_names_work_and_can_edit_a_day_then_return_to_review():
+ result = run_controller("""
+const createWorkflow=createWeekPlan.Workflow;
+const dates=['2026-08-21','2026-08-22','2026-08-23','2026-08-24','2026-08-25','2026-08-26','2026-08-27'];
+const elements=new Map();
+const makeElement=()=>({hidden:false,textContent:'',disabled:false,innerHTML:'',dataset:{},listeners:{},
+ addEventListener(name,listener){this.listeners[name]=listener;},focus(){this.focused=true;},
+ querySelectorAll:()=>[],querySelector:()=>null,scrollIntoView:()=>{}});
+const dateRoot=makeElement();
+Object.defineProperty(dateRoot,'innerHTML',{set(value){this.value=value;this.buttons=[...value.matchAll(/data-week-plan-date=\"([^\"]+)/g)].map(match=>({
+ dataset:{weekPlanDate:match[1]},addEventListener:()=>{},scrollIntoView:()=>{},focus:()=>{}
+}));},get(){return this.value||'';}});
+dateRoot.querySelectorAll=()=>dateRoot.buttons||[];
+dateRoot.querySelector=selector=>(dateRoot.buttons||[]).find(button=>selector.includes(button.dataset.weekPlanDate))||null;
+elements.set('#week-plan-dates',dateRoot);
+const reviewDays=makeElement();
+Object.defineProperty(reviewDays,'innerHTML',{set(value){
+ this.value=value;this.editButtons=[...value.matchAll(/data-week-edit-day=\"([^\"]+)/g)].map(match=>{
+ const button=makeElement();button.dataset.weekEditDay=match[1];return button;
+ });
+},get(){return this.value||'';}});
+reviewDays.querySelectorAll=selector=>selector==='[data-week-edit-day]'?reviewDays.editButtons||[]:[];
+reviewDays.querySelector=selector=>(reviewDays.editButtons||[]).find(button=>selector.includes(button.dataset.weekEditDay))||null;
+elements.set('#week-review-days',reviewDays);
+for(const selector of ['#mobile-week-summary','#my-work-action-status','#week-plan-progress','#save-today-plan',
+ '#week-review','#week-review-duplicates','#confirm-week-plan','#week-review-status','#back-to-week-review']) {
+ if(!elements.has(selector))elements.set(selector,makeElement());
+}
+let reviewMode=false;
+const review={days:dates.map((date,index)=>({plan_date:date,label:'Day '+(index+1),
+ ids:index===0?['issue:stackchain/dashboard:42:']:[],capacity_minutes:60,
+ estimates:index===0?{'issue:stackchain/dashboard:42:':30}:{},planned_minutes:index===0?30:0,overloaded:false})),
+ duplicates:[],can_confirm:true};
+const controller={dates:()=>dates.map((date,index)=>({date,label:'Day '+(index+1)})),day:date=>review.days.find(day=>day.plan_date===date),
+ pass:date=>{const index=dates.indexOf(date);return {position:index+1,total:7,planned:1,next_date:dates[index+1]||null,last:index===6};},
+ load:async()=>({}),summary:()=> '1 item across 1 day',review:()=>review,pending:()=>false,move:()=>true,flush:async()=>({})};
+const workflow=createWorkflow({controller,qs:selector=>elements.get(selector),getLogin:()=> 'timmy',
+ getItem:id=>id==='issue:stackchain/dashboard:42:'?{kind:'issue',title:'Fix mobile sign in',repository:'stackchain/dashboard',number:42}:null,
+ openPlanner:()=>{},setReviewMode:value=>{reviewMode=value;},escapeHtml:value=>value,escapeAttribute:value=>value,
+ todayWork:{replace:()=>{},replacePlanning:()=>{}},refresh:()=>{},warm:()=>{}});
+await workflow.open({disabled:false});
+for(let index=0;index<7;index++)workflow.advance();
+const markup=reviewDays.innerHTML;
+const edit=reviewDays.editButtons[0];edit.listeners.click();
+const whileEditing={selected:workflow.selectedDate(),reviewing:workflow.reviewing(),reviewMode,
+ reviewHidden:elements.get('#week-review').hidden,backHidden:elements.get('#back-to-week-review').hidden};
+elements.get('#back-to-week-review').listeners.click();
+console.log(JSON.stringify({markup,whileEditing,after:{selected:workflow.selectedDate(),reviewing:workflow.reviewing(),reviewMode,
+ reviewHidden:elements.get('#week-review').hidden,editFocused:reviewDays.editButtons[0].focused}}));
+""")
+
+ assert "Fix mobile sign in" in result["markup"]
+ assert "stackchain/dashboard #42 · Issue · 30 min" in result["markup"]
+ assert "issue:stackchain/dashboard:42:" not in result["markup"]
+ assert result["whileEditing"] == {
+ "selected": "2026-08-21", "reviewing": False, "reviewMode": False,
+ "reviewHidden": True, "backHidden": False,
+ }
+ assert result["after"] == {
+ "selected": None, "reviewing": True, "reviewMode": True,
+ "reviewHidden": False, "editFocused": True,
+ }
+
+
def test_week_controller_preserves_both_versions_when_another_device_changes_the_week():
result = run_controller("""
let calls=0;