feat: add read-first Week Ahead overview (Closes #1216)
CI / lint (pull_request) Successful in 3m37s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 5m19s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-21 09:07:27 +00:00
1 parent 45e2798d37
commit 42e97b4a79
6 files changed
+121 -20

No files matched your search

@@ -62,6 +62,26 @@ def test_release_artifact_plans_seven_touch_safe_mobile_dates(
page.wait_for_timeout(100)
assert not page_errors, f"Week Ahead launch raised: {page_errors}"
expect(page.locator("#plan-today-title")).to_have_text("Week Ahead")
overview = page.locator("#week-review")
expect(overview).to_be_visible()
cards = page.locator("#week-review-days .week-review-day")
expect(cards).to_have_count(7)
expect(cards.first).to_contain_text("Next up")
expect(cards.first).to_contain_text("Ship mobile capture")
expect(page.locator("#week-review-status")).to_have_text(
"Week Ahead overview · no changes made."
)
expect(page.locator("#confirm-week-plan")).to_be_hidden()
edit_week = page.locator("#edit-week-plan")
expect(edit_week).to_be_visible()
assert saved == [], "opening and inspecting Week Ahead must not write"
for control in (cards.first.locator("[data-week-edit-day]"), edit_week):
bounds = control.bounding_box()
assert bounds and bounds["height"] >= 44
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
edit_week.click()
expect(overview).to_be_hidden()
expect(page.locator("#plan-today-title")).to_have_text("Plan Week Ahead")
dates = page.locator("#week-plan-dates button")
expect(dates).to_have_count(7)
@@ -243,6 +263,7 @@ def test_release_artifact_reconciles_only_the_week_day_changed_on_both_devices(
page.wait_for_url(origin + "/", wait_until="networkidle")
page.locator('[data-mobile-task="queues"]').click()
page.locator('[data-mobile-queue="week"]').click()
page.locator("#edit-week-plan").click()
page.locator("#plan-today-available").fill("60")
page.locator("#save-today-plan").click()
page.wait_for_function("() => document.querySelector('#mobile-week-summary').textContent.includes('Conflict')")
+63 -4
View File
@@ -179,15 +179,18 @@ Object.defineProperty(root,'innerHTML',{set(value){this.value=value;this.buttons
root.querySelectorAll=()=>root.buttons||[];
root.querySelector=selector=>(root.buttons||[]).find(button=>selector.includes(button.dataset.weekPlanDate))||null;
elements.set('#week-plan-dates',root);
for(const selector of ['#mobile-week-summary','#my-work-action-status','#week-plan-progress','#save-today-plan']) elements.set(selector,makeElement());
for(const selector of ['#mobile-week-summary','#my-work-action-status','#week-plan-progress','#save-today-plan',
'#week-review','#week-review-days','#week-review-duplicates','#confirm-week-plan','#week-review-status','#edit-week-plan']) elements.set(selector,makeElement());
let opened=0,staged=[];
const controller={dates:()=>dates.map((date,index)=>({date,label:'Day '+(index+1)})),day:date=>({plan_date:date,ids:[]}),
pass:date=>{const index=dates.indexOf(date);return {position:index+1,total:7,planned:staged.length,next_date:dates[index+1]||null,last:index===6};},
load:async()=>({}),summary:()=> 'Nothing planned',stageDay:(date,plan)=>{staged.push({date,plan});return true;},flush:async()=>({})};
load:async()=>({}),summary:()=> 'Nothing planned',review:()=>({days:dates.map(date=>({plan_date:date,label:date,ids:[],capacity_minutes:null,estimates:{},planned_minutes:0,overloaded:false})),duplicates:[],blockers:[],can_confirm:true}),pending:()=>false,
stageDay:(date,plan)=>{staged.push({date,plan});return true;},flush:async()=>({})};
const workflow=createWorkflow({controller,qs:selector=>elements.get(selector),getLogin:()=> 'timmy',
openPlanner:()=>{opened+=1;},escapeHtml:value=>value,escapeAttribute:value=>value,
todayWork:{replace:()=>{},replacePlanning:()=>{}},refresh:()=>{},warm:()=>{}});
await workflow.open({disabled:false});
workflow.editWeek();
workflow.save({ids:['issue:r:1:'],capacity_minutes:60,estimates:{'issue:r:1:':30}});
const continued=workflow.advance();
console.log(JSON.stringify({continued,selected:workflow.selectedDate(),opened,staged,
@@ -196,12 +199,67 @@ console.log(JSON.stringify({continued,selected:workflow.selectedDate(),opened,st
assert result["continued"] is True
assert result["selected"] == "2026-08-22"
assert result["opened"] == 3
assert result["opened"] == 4
assert result["staged"][0]["date"] == "2026-08-21"
assert result["progress"] == "Day 2 of 7 · 1 planned"
assert result["label"] == "Save & next"
def test_week_workflow_opens_a_read_only_overview_with_next_up_work():
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:'',addEventListener:()=>{},focus:()=>{},
querySelectorAll:()=>[],querySelector:()=>null,scrollIntoView:()=>{}});
const dateRoot=makeElement();
Object.defineProperty(dateRoot,'innerHTML',{set(value){this.value=value;this.buttons=[];},get(){return this.value||'';}});
elements.set('#week-plan-dates',dateRoot);
const reviewDays=makeElement();
Object.defineProperty(reviewDays,'innerHTML',{set(value){this.value=value;},get(){return this.value||'';}});
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',
'#edit-week-plan']) if(!elements.has(selector))elements.set(selector,makeElement());
let writes=0,opened=0,reviewMode=false;
const review={days:dates.map((date,index)=>({plan_date:date,label:'Day '+(index+1),
ids:index===2?['issue:stackchain/dashboard:42:']:[],capacity_minutes:60,
estimates:index===2?{'issue:stackchain/dashboard:42:':30}:{},planned_minutes:index===2?30:0,overloaded:false})),
duplicates:[],blockers:[],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()=>{writes+=1;}};
const workflow=createWorkflow({controller,qs:selector=>elements.get(selector),getLogin:()=> 'timmy',
getItem:id=>id.includes(':42:')?{kind:'issue',title:'Ship mobile overview',repository:'stackchain/dashboard',number:42}:null,
openPlanner:()=>{opened+=1;},setReviewMode:value=>{reviewMode=value;},escapeHtml:value=>value,escapeAttribute:value=>value,
todayWork:{replace:()=>{},replacePlanning:()=>{}},refresh:()=>{},warm:()=>{}});
await workflow.open({disabled:false});
console.log(JSON.stringify({reviewing:workflow.reviewing(),reviewMode,writes,opened,copy:workflow.copy(),
reviewHidden:elements.get('#week-review').hidden,datesHidden:elements.get('#week-plan-dates').hidden,
markup:reviewDays.innerHTML,status:elements.get('#week-review-status').textContent,
confirmHidden:elements.get('#confirm-week-plan').hidden,editWeekHidden:elements.get('#edit-week-plan').hidden,
editWeekLabel:elements.get('#edit-week-plan').textContent}));
""")
assert result["reviewing"] is True
assert result["reviewMode"] is True
assert result["writes"] == 0
assert result["copy"] == {
"title": "Week Ahead", "heading": "Seven-day overview",
"available": "Planned work", "build": "Edit week",
}
assert result["reviewHidden"] is False
assert result["datesHidden"] is True
assert "Ship mobile overview" in result["markup"]
assert "Next up" in result["markup"]
assert "data-week-move" not in result["markup"]
assert result["status"] == "Week Ahead overview · no changes made."
assert result["confirmHidden"] is True
assert result["editWeekHidden"] is False
assert result["editWeekLabel"] == "Edit week"
def test_week_workflow_opens_review_after_day_seven_and_blocks_duplicate_confirmation():
result = run_controller("""
const createWorkflow=createWeekPlan.Workflow;
@@ -213,7 +271,7 @@ const root=makeElement();
Object.defineProperty(root,'innerHTML',{set(value){this.value=value;this.buttons=[];},get(){return this.value||'';}});
elements.set('#week-plan-dates',root);
for(const selector of ['#mobile-week-summary','#my-work-action-status','#week-plan-progress','#save-today-plan',
'#week-review','#week-review-days','#week-review-duplicates','#confirm-week-plan','#week-review-status']) elements.set(selector,makeElement());
'#week-review','#week-review-days','#week-review-duplicates','#confirm-week-plan','#week-review-status','#edit-week-plan']) elements.set(selector,makeElement());
let reviewMode=false;
const review={days:dates.map((date,index)=>({plan_date:date,label:'Day '+(index+1),ids:index<2?['shared']:[],
capacity_minutes:index===0?30:60,planned_minutes:index===0?45:15,overloaded:index===0})),
@@ -225,6 +283,7 @@ const workflow=createWorkflow({controller,qs:selector=>elements.get(selector),ge
openPlanner:()=>{},setReviewMode:value=>{reviewMode=value;},escapeHtml:value=>value,escapeAttribute:value=>value,
todayWork:{replace:()=>{},replacePlanning:()=>{}},refresh:()=>{},warm:()=>{}});
await workflow.open({disabled:false});
workflow.editWeek();
for(let index=0;index<6;index++) workflow.advance();
const advanced=workflow.advance();
console.log(JSON.stringify({advanced,reviewing:workflow.reviewing(),reviewMode,