feat: pull Week Ahead work into Today (Closes #1242)
CI / lint (pull_request) Successful in 3m22s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Successful in 5m11s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-22 00:10:39 +00:00
1 parent 6afdad6fe4
commit 3fd5983df6
7 files changed
+314 -13

No files matched your search

@@ -34,6 +34,28 @@ def test_release_artifact_plans_seven_touch_safe_mobile_dates(
page = browser.new_page(viewport={"width": width, "height": height}, timezone_id="UTC")
page_errors: list[str] = []
page.on("pageerror", lambda error: page_errors.append(str(error)))
pulled: list[dict] = []
page.route("**/api/v1/today", lambda route: route.fulfill(
status=200, content_type="application/json", body=json.dumps({
"revision": 3, "ids": ["issue:acme/mobile:99:"],
"capacity_minutes": 120, "estimates": {"issue:acme/mobile:99:": 30},
}),
))
def pull_item_route(route):
body = json.loads(route.request.post_data or "{}")
pulled.append(body)
route.fulfill(status=200, content_type="application/json", body=json.dumps({
"today": {"revision": 4, "ids": ["issue:acme/mobile:99:", body["identity"]],
"capacity_minutes": 120, "estimates": {body["identity"]: 45}},
"week": {"revision": 99, "timezone": "UTC", "days": [{
"plan_date": (date.today() + timedelta(days=1)).isoformat(),
"ids": [], "capacity_minutes": 60, "estimates": {},
}]},
}))
page.route("**/api/v1/week/pull-item", pull_item_route)
def week_route(route):
if route.request.method == "PUT":
@@ -66,6 +88,8 @@ def test_release_artifact_plans_seven_touch_safe_mobile_dates(
page.locator('input[name="access_token"]').fill(ACCESS_TOKEN)
page.locator("#submit-sign-in").click()
page.wait_for_url(origin + "/", wait_until="networkidle")
if page.locator("#plan-today-sheet").is_visible():
page.locator("#cancel-plan-today").click()
page.locator('[data-mobile-task="queues"]').click()
page.locator('[data-mobile-queue="week"]').click()
page.wait_for_timeout(100)
@@ -294,6 +318,17 @@ def test_release_artifact_plans_seven_touch_safe_mobile_dates(
assert len(saved) == before_retirement + 1
assert saved[-1]["days"][0]["ids"] == ["issue:acme/mobile:42:"]
assert saved[-1]["days"][0]["capacity_minutes"] == 60
pull = cards.first.locator('[data-week-pull-item="issue:acme/mobile:42:"]')
expect(pull).to_have_text("Add to Today")
pull_bounds = pull.bounding_box()
assert pull_bounds and pull_bounds["height"] >= 44
pull.click()
expect(cards.first).not_to_contain_text("Polish desktop filters")
expect(page.locator("#week-review-status")).to_have_text(
"Polish desktop filters added to Today."
)
assert pulled and pulled[-1]["identity"] == "issue:acme/mobile:42:"
assert pulled[-1]["today_revision"] == 3
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
browser.close()
finally:
+68
View File
@@ -189,6 +189,54 @@ def test_week_early_start_moves_a_future_day_to_empty_today_exactly_once(tmp_pat
assert store.get_week("timmy")["days"] == [sample_days()[1]]
def test_pull_week_item_atomically_appends_to_active_today_and_replays_exactly_once(tmp_path):
store = TodayStore(tmp_path / "today.sqlite3", encryption_key=b"i" * 32)
active_id = "issue:stackchain/dashboard:1:"
pulled_id = "issue:secret/repo:3:"
today = store.apply("timmy", "seed-active", "add", active_id)
week = store.replace_week("timmy", base_revision=0, days=sample_days(), timezone="UTC")
result = store.pull_week_item(
"timmy", operation_id="pull-secret-3", identity=pulled_id,
today_revision=today["revision"], week_revision=week["revision"],
)
replay = store.pull_week_item(
"timmy", operation_id="pull-secret-3", identity=pulled_id,
today_revision=today["revision"], week_revision=week["revision"],
)
assert replay == result
assert result["today"]["ids"] == [active_id, pulled_id]
assert result["today"]["estimates"] == {pulled_id: 60}
assert result["week"]["days"][0]["ids"] == ["issue:secret/repo:2:"]
assert result["week"]["days"][0]["estimates"] == {"issue:secret/repo:2:": 45}
assert result["week"]["days"][1] == sample_days()[1]
assert store.get("timmy") == result["today"]
assert store.get_week("timmy") == result["week"]
def test_pull_week_item_rejects_full_or_changed_today_without_partial_write(tmp_path):
store = TodayStore(tmp_path / "today.sqlite3", encryption_key=b"f" * 32)
today = store.get("timmy")
for index in range(5):
today = store.apply("timmy", f"seed-{index}", "add", f"issue:r:{index + 1}:")
week = store.replace_week("timmy", base_revision=0, days=sample_days(), timezone="UTC")
with pytest.raises(main.TodayPlanFull):
store.pull_week_item(
"timmy", operation_id="full", identity="issue:secret/repo:3:",
today_revision=today["revision"], week_revision=week["revision"],
)
with pytest.raises(TodayPromotionConflict):
store.pull_week_item(
"timmy", operation_id="stale", identity="issue:secret/repo:3:",
today_revision=today["revision"] - 1, week_revision=week["revision"],
)
assert store.get("timmy") == today
assert store.get_week("timmy") == week
def test_normal_week_promotion_still_rejects_a_future_day_without_writes(tmp_path):
store = TodayStore(
tmp_path / "today.sqlite3",
@@ -327,6 +375,26 @@ async def test_week_early_start_api_promotes_a_future_day(monkeypatch, tmp_path)
assert store.get_week("timmy")["days"] == [sample_days()[1]]
@pytest.mark.anyio
async def test_pull_week_item_api_returns_both_atomic_plans(monkeypatch, tmp_path):
async def user():
return {"login": "timmy"}
store = TodayStore(tmp_path / "today.sqlite3", encryption_key=b"u" * 32)
today = store.apply("timmy", "seed", "add", "issue:r:active:")
week = store.replace_week("timmy", base_revision=0, days=sample_days(), timezone="UTC")
monkeypatch.setattr(main, "current_user", user)
monkeypatch.setattr(main, "_today_store", lambda: store)
result = await main.pull_week_item_into_today(main.WeekItemPull(
operation_id="pull-api", identity="issue:secret/repo:3:",
week_revision=week["revision"], today_revision=today["revision"],
))
assert result["today"]["ids"] == ["issue:r:active:", "issue:secret/repo:3:"]
assert result["week"]["days"][0]["ids"] == ["issue:secret/repo:2:"]
@pytest.mark.anyio
async def test_week_reconciliation_api_returns_atomic_today_and_remaining_week(monkeypatch, tmp_path):
async def user():
+49 -7
View File
@@ -300,6 +300,31 @@ console.log(JSON.stringify({before,refused,moved,added,state:week.state(),pendin
assert result["pending"]["days"] == result["state"]["days"]
def test_week_controller_pulls_one_item_into_today_and_adopts_returned_week():
result = run_controller("""
const requests=[];
const week=createWeekPlan({fetchJson:async(url,options={})=>{
const body=JSON.parse(options.body);requests.push({url,body});
return {today:{revision:5,ids:['active',body.identity],capacity_minutes:120,estimates:{[body.identity]:45}},
week:{revision:8,timezone:'UTC',days:[{plan_date:'2026-08-21',ids:['sibling'],capacity_minutes:90,estimates:{sibling:30}}]}};
},localDate:()=> '2026-08-20',timeZone:()=> 'UTC'});
week.adopt({revision:7,timezone:'UTC',days:[{plan_date:'2026-08-21',ids:['pull','sibling'],capacity_minutes:90,estimates:{pull:45,sibling:30}}]});
const pulled=await week.pullItem('pull',{revision:4,ids:['active']},'stable-pull');
console.log(JSON.stringify({pulled,requests,state:week.state()}));
""")
assert result["requests"] == [{
"url": "api/v1/week/pull-item",
"body": {
"operation_id": "stable-pull", "identity": "pull",
"today_revision": 4, "week_revision": 7,
},
}]
assert result["pulled"]["today"]["ids"] == ["active", "pull"]
assert result["state"]["revision"] == 8
assert result["state"]["days"][0]["ids"] == ["sibling"]
def test_week_controller_retires_completed_work_from_every_day_in_one_durable_transition():
result = run_controller("""
const values=new Map();let writes=0;
@@ -407,28 +432,37 @@ 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||'';}});
Object.defineProperty(reviewDays,'innerHTML',{set(value){this.value=value;this.pullButtons=[...value.matchAll(/data-week-pull-item=\"([^\"]+)/g)].map(match=>({
dataset:{weekPullItem:match[1]},listeners:{},disabled:false,
addEventListener(name,listener){this.listeners[name]=listener;},focus(){this.focused=true;}
}));},get(){return this.value||'';}});
reviewDays.querySelectorAll=selector=>selector==='[data-week-pull-item]'?reviewDays.pullButtons||[]:[];
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;
let writes=0,opened=0,reviewMode=false,pullRequest=null,replacedToday=null;
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;}};
load:async()=>({}),summary:()=> '1 item across 1 day',review:()=>review,pending:()=>false,offline:()=>false,
move:()=>true,flush:async()=>{writes+=1;},pullItem:async(id,today,operationId)=>{
pullRequest={id,today,operationId};return {today:{revision:4,ids:['active',id],estimates:{[id]:30}},week:{revision:8,days:[]}};
}};
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:()=>{}});
today:()=>({revision:3,ids:['active'],estimates:{}}),operationId:()=> 'pull-once',
todayWork:{replace:ids=>{replacedToday=ids;},replacePlanning:()=>{}},refresh:()=>{},warm:()=>{}});
await workflow.open({disabled:false});
const initialMarkup=reviewDays.innerHTML;
await reviewDays.pullButtons[0].listeners.click({currentTarget:reviewDays.pullButtons[0]});
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,
markup:initialMarkup,status:elements.get('#week-review-status').textContent,pullRequest,replacedToday,
confirmHidden:elements.get('#confirm-week-plan').hidden,editWeekHidden:elements.get('#edit-week-plan').hidden,
editWeekLabel:elements.get('#edit-week-plan').textContent}));
""")
@@ -444,8 +478,12 @@ console.log(JSON.stringify({reviewing:workflow.reviewing(),reviewMode,writes,ope
assert result["datesHidden"] is True
assert "Ship mobile overview" in result["markup"]
assert "Next up" in result["markup"]
assert "Add to Today" in result["markup"]
assert result["pullRequest"]["id"] == "issue:stackchain/dashboard:42:"
assert result["pullRequest"]["operationId"] == "pull-once"
assert result["replacedToday"] == ["active", "issue:stackchain/dashboard:42:"]
assert "data-week-move" not in result["markup"]
assert result["status"] == "Week Ahead overview · no changes made."
assert result["status"] == "Ship mobile overview added to Today."
assert result["confirmHidden"] is True
assert result["editWeekHidden"] is False
assert result["editWeekLabel"] == "Edit week"
@@ -661,6 +699,10 @@ def test_dashboard_routes_week_overview_controls_through_existing_detail_flow_wi
start_rule = css.split(".week-start-early", 1)[1].split("}", 1)[0]
assert "min-height:44px" in start_rule
assert "width:100%" in start_rule
assert ".week-review-pull" in css
pull_rule = css.split(".week-review-pull", 1)[1].split("}", 1)[0]
assert "min-height:44px" in pull_rule
assert "width:100%" in pull_rule
def test_successful_issue_close_notifies_week_ahead_only_after_upstream_confirmation():