fix: foreground mobile delivery attention handoffs (Closes #1018)
CI / lint (pull_request) Successful in 3m1s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Successful in 2m22s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-17 10:42:19 +00:00
1 parent 6913fc8257
commit 3fde7cf3ad
5 files changed
+156 -14

No files matched your search

@@ -194,6 +194,40 @@ def test_release_artifact_keeps_mobile_delivery_recovery_single_flight(tmp_path:
page.evaluate("Promise.all([window.__firstRecovery, window.__secondRecovery])")
expect(action).to_be_enabled()
assert page.evaluate("window.__deliveryAttempts") == 1
page.evaluate(
"""
() => {
const destination = document.createElement('button');
destination.type = 'button';
destination.className = 'draft-resume';
destination.dataset.draftIndex = '0';
destination.textContent = 'Open current review';
window.__attentionAttempts = 0;
window.__attentionSettled = new Promise(resolve => { window.__finishAttention = resolve; });
destination.onclick = async () => {
window.__attentionAttempts += 1;
await window.__attentionSettled;
return true;
};
document.querySelector('#my-work-list').append(destination);
window.__attentionRecovery = createMobileDeliveryRecovery({
getItems: () => [{outbox_id:'review-attention', status:'attention', title:'Review feedback'}],
getIndex: () => 0,
});
window.__attentionRecovery.open();
window.__firstAttention = window.__attentionRecovery.activate();
window.__secondAttention = window.__attentionRecovery.activate();
}
"""
)
expect(page.locator("#mobile-delivery-recovery")).to_be_hidden()
expect(page.locator("#my-work-list .draft-resume")).to_be_focused()
assert page.evaluate("window.__attentionAttempts") == 1
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
page.evaluate("window.__finishAttention()")
page.evaluate("Promise.all([window.__firstAttention, window.__secondAttention])")
assert page.evaluate("window.__attentionAttempts") == 1
assert browser_errors == []
browser.close()
finally:
+2
View File
@@ -72,6 +72,8 @@ def test_product_workflows_are_stable_lazy_feature_chunks(tmp_path):
assert b"function createQueueToday" in first.feature_bundles["today-timer"].runtime_bytes
assert b"function createConversationPager" in first.feature_bundles["today-timer"].runtime_bytes
assert b"function createConversationPager" not in first.feature_bundles["comment-actions"].runtime_bytes
assert b"function createMobileDeliveryRecovery" not in first.runtime_bytes
assert b"function createMobileDeliveryRecovery" in first.feature_bundles["today-timer"].runtime_bytes
assert b"gitea_time_logged" not in first.runtime_bytes
assert b"gitea_time_logged" in security_center.runtime_bytes
# Core mobile workflows stay below 99 KiB, including synced Search views and Update decisions.
+105 -1
View File
@@ -135,6 +135,109 @@ const createRecovery = require({json.dumps(str(RECOVERY))});
}
def test_attention_handoff_closes_modal_focuses_destination_and_awaits_work():
script = f"""
const createRecovery = require({json.dumps(str(RECOVERY))});
(async () => {{
let finish;
let handoffs = 0;
const settled = new Promise(resolve => {{finish = resolve}});
const destination = {{
focused:false, scrolled:false,
focus(){{this.focused=true}}, scrollIntoView(){{this.scrolled=true}},
async onclick(){{handoffs += 1; await settled; return true}},
}};
global.document = {{
getElementById: () => null,
querySelector: selector => selector.includes('draft-resume') ? destination : null,
}};
const element = () => ({{textContent:'', disabled:false}});
const elements = {{
dialog: {{open:false, showModal(){{this.open=true}}, close(){{this.open=false}}}},
title:element(), destination:element(), reason:element(), progress:element(),
action:element(), status:element(),
}};
const recovery = createRecovery({{
getItems: () => [{{outbox_id:'review', status:'attention', title:'Review feedback'}}],
getIndex: () => 3,
elements,
}});
recovery.open();
const first = recovery.activate();
const second = recovery.activate();
await Promise.resolve();
const pending = {{
dialogOpen:elements.dialog.open, disabled:elements.action.disabled,
focused:destination.focused, scrolled:destination.scrolled, handoffs,
}};
finish();
await Promise.all([first, second]);
process.stdout.write(JSON.stringify({{pending, handoffs, dialogOpen:elements.dialog.open}}));
}})();
"""
assert run_node(script) == {
"pending": {
"dialogOpen": False,
"disabled": True,
"focused": True,
"scrolled": True,
"handoffs": 1,
},
"handoffs": 1,
"dialogOpen": False,
}
def test_failed_attention_handoff_reopens_the_same_retryable_item():
script = f"""
const createRecovery = require({json.dumps(str(RECOVERY))});
(async () => {{
const destination = {{
focus(){{}}, scrollIntoView(){{}},
async onclick(){{throw new Error('Latest checklist unavailable')}},
}};
global.document = {{
getElementById: () => null,
querySelector: () => destination,
}};
const element = () => ({{textContent:'', disabled:false}});
const elements = {{
dialog: {{open:false, showModal(){{this.open=true}}, close(){{this.open=false}}}},
title:element(), destination:element(), reason:element(), progress:element(),
action:element(), status:element(),
}};
const recovery = createRecovery({{
getItems: () => [{{outbox_id:'conflict', checklist_conflict:true, title:'Checklist conflict'}}],
getIndex: () => 0,
elements,
}});
recovery.open();
const result = await recovery.activate();
process.stdout.write(JSON.stringify({{
result, dialogOpen:elements.dialog.open, status:elements.status.textContent,
action:elements.action.textContent,
}}));
}})();
"""
assert run_node(script) == {
"result": {
"count": 1,
"current": {
"id": "conflict",
"state": "attention",
"primary": "Review changes",
"position": 1,
"total": 1,
},
},
"dialogOpen": True,
"status": "Latest checklist unavailable Try again.",
"action": "Review changes",
}
def test_recovery_retains_failed_delivery_with_actionable_status():
script = f"""
const createRecovery = require({json.dumps(str(RECOVERY))});
@@ -170,7 +273,7 @@ const createRecovery = require({json.dumps(str(RECOVERY))});
},
},
"disabled": False,
"status": "Network unavailable Try again when the connection is ready.",
"status": "Network unavailable Try again.",
}
@@ -235,6 +338,7 @@ async def test_dashboard_packages_phone_safe_guided_delivery_recovery():
assert '<script src="static/mobile-delivery-recovery.js"></script>' in html
assert "const mobileDeliveryRecovery = createMobileDeliveryRecovery({" in html
assert "activate: recoverMobileDelivery," in html
assert "button.onclick=async()=>" in html
assert "await (authored ? authoredOutbox : issueOutbox).retry(item.outbox_id, activeFlushLogin)" in html
assert "(authored ? applyAuthoredOutboxResult : applyOutboxResult)(result)" in html
assert "openDelivery: () => mobileDeliveryRecovery.open()" in html