feat: avoid imported busy time in Week Ahead export (Closes #1210)
CI / lint (pull_request) Successful in 3m17s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 4m38s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-21 05:50:23 +00:00
1 parent 865083a835
commit 4e275b1518
6 files changed
+99 -24

No files matched your search

+5 -4
View File
@@ -107,9 +107,9 @@ def test_release_artifact_plans_seven_touch_safe_mobile_dates(
added = (date.today() + timedelta(days=4)).strftime("%Y%m%d")
calendar = (
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\n"
f"SUMMARY:{private_title}\r\nDTSTART:{tomorrow}T100000\r\n"
f"DTEND:{tomorrow}T110000\r\nRRULE:FREQ=DAILY;COUNT=3\r\n"
f"EXDATE:{excluded}T100000\r\nRDATE:{added}T100000\r\nEND:VEVENT\r\nEND:VCALENDAR"
f"SUMMARY:{private_title}\r\nDTSTART:{tomorrow}T090000\r\n"
f"DTEND:{tomorrow}T100000\r\nRRULE:FREQ=DAILY;COUNT=3\r\n"
f"EXDATE:{excluded}T090000\r\nRDATE:{added}T090000\r\nEND:VEVENT\r\nEND:VCALENDAR"
)
unsupported_title = "Private monthly board review"
unsupported_calendar = (
@@ -167,7 +167,8 @@ def test_release_artifact_plans_seven_touch_safe_mobile_dates(
expect(page.locator("#week-review")).to_be_hidden()
start = page.locator("[data-week-calendar-start]").first
expect(start).to_have_value("09:00")
expect(page.locator('[data-week-calendar-preview="issue:acme/mobile:41:"]')).to_have_text("09:00–09:30 · 30 min")
expect(page.locator('[data-week-calendar-preview="issue:acme/mobile:41:"]')).to_have_text("10:00–10:30 · 30 min")
expect(page.locator("#week-calendar-status")).to_contain_text("Planning around imported busy time")
for control in (start, page.locator("#back-to-week-review-from-calendar"), page.locator("#share-week-calendar")):
bounds = control.bounding_box()
assert bounds and bounds["height"] >= 44
+24
View File
@@ -69,6 +69,27 @@ console.log(JSON.stringify({{blocks,text}}));
assert "URL:https://forge.alexanderwhitestone.com/git/stackchain/stackchain-dashboard/issues/12" in unfolded
def test_week_calendar_places_whole_tasks_in_imported_free_windows_and_reports_unfitted_work():
source = f"""
const calendar=require({json.dumps(str(WEEK_CALENDAR))});
const plan={{days:[{{plan_date:'2026-08-21',ids:['one','two','three'],estimates:{{one:45,two:90,three:400}}}}]}};
const items={{one:{{title:'First',repository:'r',number:1}},two:{{title:'Second',repository:'r',number:2}},
three:{{title:'Too large',repository:'r',number:3}}}};
const availability=[{{plan_date:'2026-08-21',free_windows:[{{start_time:'09:00',end_time:'10:00'}},{{start_time:'11:00',end_time:'17:00'}}]}}];
const result=calendar.buildWeekSchedule(plan,{{'2026-08-21':'09:00'}},id=>items[id],null,availability);
console.log(JSON.stringify(result));
"""
result = run_node(source)
assert [(block["id"], block["start_time"], block["end_time"]) for block in result["blocks"]] == [
("one", "09:00", "09:45"),
("two", "11:00", "12:30"),
]
assert result["blockers"] == [{
"id": "three", "title": "Too large", "plan_date": "2026-08-21", "minutes": 400
}]
def test_week_calendar_excludes_unselected_items_and_downloads_when_native_share_is_unavailable():
source = f"""
const calendar=require({json.dumps(str(WEEK_CALENDAR))});
@@ -104,7 +125,10 @@ def test_week_calendar_handoff_is_wired_into_confirmation_with_mobile_privacy_co
assert 'id="share-week-calendar"' in index
assert 'id="back-to-week-review-from-calendar"' in index
assert "Titles, references, dates, and times are included" in index
assert "Imported busy time is used only until this page reloads" in index
assert "mountWeekCalendarHandoff" in WEEK_CALENDAR.read_text()
assert "weekCalendar.open(weekPlan.state())" in dashboard
assert "weekCalendar.close()" in dashboard
assert "StackchainWeekCalendar.setAvailabilityProvider(workflow.availability)" in (WEEK_CALENDAR.parent / "week-calendar-import.js").read_text()
assert "Planning around imported busy time" in WEEK_CALENDAR.read_text()
assert '"static/week-calendar.js", "static/week-calendar-import.js", "static/week-plan.js"' in bundle
+16 -3
View File
@@ -27,10 +27,15 @@ console.log(JSON.stringify({days,serialized:JSON.stringify(days)}));
""")
assert result["days"][0] == {
"plan_date": "2026-08-21", "busy_minutes": 150, "capacity_minutes": 330
"plan_date": "2026-08-21", "busy_minutes": 150, "capacity_minutes": 330,
"free_windows": [
{"start_time": "09:00", "end_time": "10:00"},
{"start_time": "12:00", "end_time": "16:30"},
],
}
assert result["days"][1] == {
"plan_date": "2026-08-22", "busy_minutes": 60, "capacity_minutes": 420
"plan_date": "2026-08-22", "busy_minutes": 60, "capacity_minutes": 420,
"free_windows": [{"start_time": "10:00", "end_time": "17:00"}],
}
assert [day["capacity_minutes"] for day in result["days"][2:]] == [480] * 5
for private_value in ("Private planning", "Secret customer call", "private@example.com", "Private office"):
@@ -178,7 +183,7 @@ const reviewed=workflow.review(source);
await workflow.apply();
console.log(JSON.stringify({reviewed,staged,flushes,appliedCalls,rootHidden:elements.get('#week-capacity-import').hidden,
status:elements.get('#week-capacity-status').textContent,markup:elements.get('#week-capacity-days').innerHTML,
fileValue:elements.get('#week-capacity-file').value,workflowState:workflow.state()}));
fileValue:elements.get('#week-capacity-file').value,workflowState:workflow.state(),availability:workflow.availability()}));
""")
assert result["reviewed"][0]["capacity_minutes"] == 420
@@ -189,6 +194,14 @@ console.log(JSON.stringify({reviewed,staged,flushes,appliedCalls,rootHidden:elem
assert result["rootHidden"] is True
assert result["fileValue"] == ""
assert result["workflowState"] is None
assert result["availability"][0] == {
"plan_date": "2026-08-21",
"free_windows": [
{"start_time": "09:00", "end_time": "10:00"},
{"start_time": "11:00", "end_time": "17:00"},
],
}
assert "free_windows" not in json.dumps(result["staged"])
assert "Do not retain me" not in json.dumps(result)