feat: prefetch the next mobile update (Closes #757)
CI / lint (pull_request) Successful in 1m33s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-13 17:31:20 +00:00
1 parent 1a3df5d7a7
commit db08c7e75f
7 files changed
+141 -9

No files matched your search

+2 -2
View File
@@ -68,8 +68,8 @@ def test_product_workflows_are_stable_lazy_feature_chunks(tmp_path):
assert b"function attachSecurityCenter" in security_center.runtime_bytes
assert b"gitea_time_logged" not in first.runtime_bytes
assert b"gitea_time_logged" in security_center.runtime_bytes
# The recap adds only startup wiring; its UI remains in the lazy Today bundle.
assert len(first.runtime_gzip_bytes) <= 96 * 1024
# One-ahead Updates prefetch stays in the core reader so transitions can reuse its in-flight request.
assert len(first.runtime_gzip_bytes) <= 97 * 1024
assert f'name="stackchain-feature-issue-capture" content="{capture.runtime_name}"' in first.dashboard_html
assert f'name="stackchain-feature-pull-workflow" content="{pull_workflow.runtime_name}"' in first.dashboard_html
assert f"BASE + '{capture.runtime_name}'" in first.service_worker_source
+70
View File
@@ -4871,6 +4871,76 @@ reader.open(original[0], original).then(() =>
assert output["result"]["next"]["notification_id"] == 43
def test_notification_reader_consumes_one_bounded_prefetch_without_duplicate_load():
script = f"""
const buildMyWork = require({json.dumps(str(MY_WORK))});
const items = [1,2,3].map(notification_id => ({{kind:'update', notification_id, has_update:true}}));
const loaded = [], details = [], statuses = [];
const releases = {{}};
const reader = buildMyWork.createNotificationReader({{
load: id => {{
loaded.push(id);
if (id === 1) return Promise.resolve({{id}});
return new Promise(resolve => {{ releases[id] = resolve; }});
}},
markRead: async () => {{}},
onOpen: () => {{}}, onDetail: detail => details.push(detail.id), onItems: () => {{}},
onStatus: status => statuses.push(status), onClose: () => {{}},
}});
(async () => {{
await reader.open(items[0]);
const first = reader.prefetch(items[1]);
const duplicate = reader.prefetch(items[1]);
const rejected = reader.prefetch(items[2]);
const advancing = reader.open(items[1]);
await Promise.resolve();
releases[2]({{id:2}});
await advancing;
process.stdout.write(JSON.stringify({{
loaded, details, same:first === duplicate, rejected, statuses,
}}));
}})();
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output == {
"loaded": [1, 2],
"details": [1, 2],
"same": True,
"rejected": False,
"statuses": ["Loading update…", "Update ready.", "Update ready."],
}
def test_notification_reader_never_consumes_prefetch_from_another_account_scope():
script = f"""
const buildMyWork = require({json.dumps(str(MY_WORK))});
let scope = 'timmy';
const loaded = [];
const reader = buildMyWork.createNotificationReader({{
load: async id => {{ loaded.push([scope, id]); return {{owner:scope, id}}; }},
getScope: () => scope,
markRead: async () => {{}}, onOpen: () => {{}}, onItems: () => {{}}, onStatus: () => {{}},
onDetail: detail => {{ if (detail.owner !== scope) throw new Error('cross-account detail'); }},
onClose: () => {{}},
}});
(async () => {{
await reader.open({{notification_id:1}});
await reader.prefetch({{notification_id:2}});
scope = 'alexander';
const opened = await reader.open({{notification_id:2}});
process.stdout.write(JSON.stringify({{loaded, opened}}));
}})();
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output == {"loaded": [["timmy", 1], ["timmy", 2], ["alexander", 2]], "opened": True}
def test_notification_reader_acknowledges_once_and_opens_next_update():
script = f"""
const buildMyWork = require({json.dumps(str(MY_WORK))});
+21
View File
@@ -81,6 +81,27 @@ process.stdout.write(JSON.stringify({opened, progress, stored:values.get('stackc
}
def test_update_triage_exposes_only_the_next_surviving_snapshot_item():
result = run_session("""
const values = new Map();
let items = [1,2,3].map(notification_id => ({notification_id}));
const session = createSession({
storage:{getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)},
getLogin:()=> 'timmy', getItems:()=>items,
onOpen:()=>{}, onProgress:()=>{}, onFinish:()=>{},
});
session.start();
const first = session.next()?.notification_id;
items = [1,3,4].map(notification_id => ({notification_id}));
const afterRemoval = session.next()?.notification_id;
session.keepUnreadAndNext();
const afterAdvance = session.next();
process.stdout.write(JSON.stringify({first, afterRemoval, afterAdvance:afterAdvance || null}));
""")
assert result == {"first": 2, "afterRemoval": 3, "afterAdvance": None}
@pytest.mark.anyio
async def test_dashboard_wires_resumable_updates_triage_mobile_flow():
html = await dashboard()