Merge pull request 'Bound Today readiness latency with demand-driven checks' (#442) from timmy/441-demand-driven-today-readiness into main
This commit is contained in:
commit
60bc7cd686
|
|
@ -1,6 +1,6 @@
|
|||
const BASE = new URL('./', self.location.href).pathname;
|
||||
importScripts(BASE + 'static/background-issue-sync.js');
|
||||
const CACHE = 'stackchain-dashboard-shell-v73';
|
||||
const CACHE = 'stackchain-dashboard-shell-v74';
|
||||
const OFFLINE_LEASE_URL = new URL(BASE + '__offline-session-lease', self.location.origin).href;
|
||||
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
|
||||
const NAVIGATION_TIMEOUT_MS = self.__STACKCHAIN_NAVIGATION_TIMEOUT_MS || 4000;
|
||||
|
|
|
|||
|
|
@ -32,15 +32,19 @@
|
|||
const candidates = preferredIndex >= 0
|
||||
? ordered.slice(preferredIndex).concat(ordered.slice(0, preferredIndex))
|
||||
: [preferred].concat(ordered);
|
||||
const states = [];
|
||||
for (const item of candidates) states.push({ item, ...(await classify(item)) });
|
||||
const targetState = { item:preferred, ...(await classify(preferred)) };
|
||||
if (request !== generation) return 'superseded';
|
||||
const targetState = states[0];
|
||||
if (targetState.status === 'ready') {
|
||||
pending = null;
|
||||
onOpen(action, preferred);
|
||||
return 'opened';
|
||||
}
|
||||
const fallbackStates = await Promise.all(candidates.slice(1).map(async item => ({
|
||||
item,
|
||||
...(await classify(item)),
|
||||
})));
|
||||
const states = [targetState, ...fallbackStates];
|
||||
if (request !== generation) return 'superseded';
|
||||
const nextReadyState = states.slice(1).find(state => state.status === 'ready');
|
||||
pending = {
|
||||
action,
|
||||
|
|
|
|||
|
|
@ -347,5 +347,5 @@ async def test_dashboard_syncs_every_later_change_and_exposes_account_status():
|
|||
def test_later_sync_ships_atomically_in_the_offline_shell():
|
||||
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v73" in source
|
||||
assert "stackchain-dashboard-shell-v74" in source
|
||||
assert "BASE + 'static/later-sync.js'" in source
|
||||
|
|
|
|||
|
|
@ -137,4 +137,4 @@ def test_markdown_work_bodies_are_mobile_safe_block_containers():
|
|||
assert ".markdown-content { min-width:0; max-width:100%; overflow-wrap:anywhere;" in css
|
||||
assert ".markdown-content pre { max-width:100%; overflow-x:auto;" in css
|
||||
assert ".markdown-content a { min-height:44px;" in css
|
||||
assert "stackchain-dashboard-shell-v73" in worker
|
||||
assert "stackchain-dashboard-shell-v74" in worker
|
||||
|
|
|
|||
|
|
@ -35,4 +35,4 @@ def test_offline_shell_contains_every_local_dashboard_runtime_asset():
|
|||
shell_assets = set(re.findall(r"BASE \+ '([^']+)'", worker.split("async function sessionCsrf", 1)[0]))
|
||||
|
||||
assert local_assets <= shell_assets, f"Offline shell is missing: {sorted(local_assets - shell_assets)}"
|
||||
assert "stackchain-dashboard-shell-v73" in worker
|
||||
assert "stackchain-dashboard-shell-v74" in worker
|
||||
|
|
|
|||
|
|
@ -232,6 +232,6 @@ async def test_plan_today_wires_cancel_back_and_success_through_overlay_history(
|
|||
def test_plan_today_controller_is_available_in_the_offline_shell():
|
||||
source = SERVICE_WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v73" in source
|
||||
assert "stackchain-dashboard-shell-v74" in source
|
||||
assert "BASE + 'static/plan-today.js'" in source
|
||||
assert "BASE + 'static/plan-today-preview.js'" in source
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ async function dispatchNotificationClick(route) {{
|
|||
def test_resumable_today_session_ships_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v73" in source
|
||||
assert "stackchain-dashboard-shell-v74" in source
|
||||
assert "BASE + 'static/my-work.js'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
assert "BASE + 'static/dashboard.css'" in source
|
||||
|
|
@ -131,7 +131,7 @@ def test_resumable_today_session_ships_in_a_new_offline_shell():
|
|||
def test_offline_review_next_ships_today_completion_atomically():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v73" in source
|
||||
assert "stackchain-dashboard-shell-v74" in source
|
||||
assert "BASE + 'static/today-completion.js'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
|
||||
|
|
@ -139,7 +139,7 @@ def test_offline_review_next_ships_today_completion_atomically():
|
|||
def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v73" in source
|
||||
assert "stackchain-dashboard-shell-v74" in source
|
||||
assert "BASE + 'static/create-issue-sheet.js'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
|
||||
|
|
@ -147,14 +147,14 @@ def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
|
|||
def test_exact_later_picker_ships_atomically_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v73" in source
|
||||
assert "stackchain-dashboard-shell-v74" in source
|
||||
assert "BASE + 'static/later-picker.js'" in source
|
||||
|
||||
|
||||
def test_navigation_deadline_ships_in_a_new_shell_cache():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v73" in source
|
||||
assert "stackchain-dashboard-shell-v74" in source
|
||||
assert "BASE + 'static/dashboard.css'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
assert "BASE + 'static/install-app.js'" in source
|
||||
|
|
@ -163,21 +163,21 @@ def test_navigation_deadline_ships_in_a_new_shell_cache():
|
|||
def test_today_convergence_ships_in_a_new_shell_cache():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v73" in source
|
||||
assert "stackchain-dashboard-shell-v74" in source
|
||||
assert "BASE + 'static/today-sync.js'" in source
|
||||
|
||||
|
||||
def test_mobile_search_viewport_ships_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v73" in source
|
||||
assert "stackchain-dashboard-shell-v74" in source
|
||||
assert "BASE + 'static/mobile-search-viewport.js'" in source
|
||||
|
||||
|
||||
def test_update_ownership_flow_ships_atomically_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v73" in source
|
||||
assert "stackchain-dashboard-shell-v74" in source
|
||||
assert "BASE + 'static/update-ownership.js'" in source
|
||||
|
||||
|
||||
|
|
@ -358,7 +358,7 @@ def test_one_session_bound_csrf_proof_is_reused_for_a_background_drain():
|
|||
def test_queue_today_ships_atomically_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v73" in source
|
||||
assert "stackchain-dashboard-shell-v74" in source
|
||||
assert "BASE + 'static/queue-today.js'" in source
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,23 +18,59 @@ def run_node(script):
|
|||
).stdout
|
||||
|
||||
|
||||
def test_readiness_opens_ready_target_without_showing_a_gate():
|
||||
def test_readiness_opens_ready_target_without_inspecting_the_rest_of_the_queue():
|
||||
script = f"""
|
||||
const createTodayReadiness = require({json.dumps(str(READINESS))});
|
||||
const calls = [];
|
||||
const inspections = [];
|
||||
const item = (number, kind='issue') => ({{kind, repository:'stackchain/dashboard', number, title:'Item ' + number}});
|
||||
const readiness = createTodayReadiness({{
|
||||
inspect: async candidate => ({{available:true, dependencies:[]}}),
|
||||
inspect: async candidate => {{ inspections.push(candidate.number); return {{available:true, dependencies:[]}}; }},
|
||||
onOpen: (action, candidate) => calls.push(['open', action, candidate.number]),
|
||||
onGate: state => calls.push(['gate', state.status]),
|
||||
}});
|
||||
readiness.run('start', [item(1), item(2)]).then(result =>
|
||||
process.stdout.write(JSON.stringify({{result, calls}}))
|
||||
readiness.run('start', [item(1), item(2), item(3)]).then(result =>
|
||||
process.stdout.write(JSON.stringify({{result, calls, inspections}}))
|
||||
);
|
||||
"""
|
||||
assert json.loads(run_node(script)) == {
|
||||
"result": "opened",
|
||||
"calls": [["open", "start", 1]],
|
||||
"inspections": [1],
|
||||
}
|
||||
|
||||
|
||||
def test_readiness_checks_fallbacks_concurrently_but_preserves_queue_order():
|
||||
script = f"""
|
||||
const createTodayReadiness = require({json.dumps(str(READINESS))});
|
||||
const items = [1, 2, 3].map(number => ({{kind:'issue', repository:'r', number}}));
|
||||
const starts = [];
|
||||
const resolvers = {{}};
|
||||
const readiness = createTodayReadiness({{
|
||||
inspect: candidate => {{
|
||||
starts.push(candidate.number);
|
||||
if (candidate.number === 1) return Promise.resolve({{available:true, dependencies:[{{state:'open'}}]}});
|
||||
return new Promise(resolve => {{ resolvers[candidate.number] = resolve; }});
|
||||
}},
|
||||
onOpen: () => undefined,
|
||||
onGate: state => {{ globalThis.gated = state.nextReady.number; }},
|
||||
}});
|
||||
(async () => {{
|
||||
const running = readiness.run('start', items);
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
const startedTogether = starts.slice();
|
||||
if (resolvers[3]) resolvers[3]({{available:true, dependencies:[]}});
|
||||
resolvers[2]({{available:true, dependencies:[]}});
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
if (!startedTogether.includes(3)) resolvers[3]({{available:true, dependencies:[]}});
|
||||
const result = await running;
|
||||
process.stdout.write(JSON.stringify({{result, startedTogether, gated:globalThis.gated}}));
|
||||
}})();
|
||||
"""
|
||||
assert json.loads(run_node(script)) == {
|
||||
"result": "gated",
|
||||
"startedTogether": [1, 2, 3],
|
||||
"gated": 2,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -135,7 +171,10 @@ async def test_dashboard_exposes_mobile_today_readiness_gate_and_runtime():
|
|||
|
||||
|
||||
def test_readiness_runtime_is_available_in_offline_shell():
|
||||
assert "BASE + 'static/today-readiness.js'" in SERVICE_WORKER.read_text()
|
||||
service_worker = SERVICE_WORKER.read_text()
|
||||
|
||||
assert "const CACHE = 'stackchain-dashboard-shell-v74';" in service_worker
|
||||
assert "BASE + 'static/today-readiness.js'" in service_worker
|
||||
|
||||
|
||||
def test_mobile_readiness_gate_has_touch_safe_wrapping_actions():
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ sync.enqueue('add', 'issue:r:1:');
|
|||
def test_inflight_today_drain_ships_in_a_new_offline_shell():
|
||||
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v73" in source
|
||||
assert "stackchain-dashboard-shell-v74" in source
|
||||
assert "BASE + 'static/today-sync.js'" in source
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user