fix: recover server-confirmed Today breaks (Closes #1274)
This commit is contained in:
parent
ce9301f145
commit
a629b5a93e
|
|
@ -1,5 +1,6 @@
|
||||||
function createTodaySessionSync({
|
function createTodaySessionSync({
|
||||||
fetchJson, getDeviceId, timer, onRemote = () => {}, onTransferred = () => {}, onStatus = () => {},
|
fetchJson, getDeviceId, timer, onRemote = () => {}, onTransferred = () => {}, onStatus = () => {},
|
||||||
|
onOwnedRestore = () => {},
|
||||||
setInterval = globalThis.setInterval, clearInterval = globalThis.clearInterval,
|
setInterval = globalThis.setInterval, clearInterval = globalThis.clearInterval,
|
||||||
}) {
|
}) {
|
||||||
let current = null;
|
let current = null;
|
||||||
|
|
@ -25,6 +26,8 @@ function createTodaySessionSync({
|
||||||
current = session;
|
current = session;
|
||||||
if (session.device_id === deviceId()) {
|
if (session.device_id === deviceId()) {
|
||||||
ownedRevision = session.revision;
|
ownedRevision = session.revision;
|
||||||
|
if (Number.isFinite(session.break_deadline_at) &&
|
||||||
|
timer?.restoreBreak?.(session.identity, session.break_deadline_at)) onOwnedRestore(session);
|
||||||
onRemote(null);
|
onRemote(null);
|
||||||
} else if ((session.running || Number.isFinite(session.break_deadline_at)) && session.identity) {
|
} else if ((session.running || Number.isFinite(session.break_deadline_at)) && session.identity) {
|
||||||
if (previousOwned) {
|
if (previousOwned) {
|
||||||
|
|
@ -186,6 +189,7 @@ function attachTodaySessionHandoff({
|
||||||
};
|
};
|
||||||
const sync = createTodaySessionSync({
|
const sync = createTodaySessionSync({
|
||||||
fetchJson, getDeviceId, timer,
|
fetchJson, getDeviceId, timer,
|
||||||
|
onOwnedRestore:renderTimer,
|
||||||
onStatus:state => showSessionStatus(
|
onStatus:state => showSessionStatus(
|
||||||
state === 'syncing' ? 'Session syncing…' :
|
state === 'syncing' ? 'Session syncing…' :
|
||||||
(state === 'offline' ? 'Session offline · will retry.' : '')
|
(state === 'offline' ? 'Session offline · will retry.' : '')
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,15 @@ function createTodayTimer({ storage, getLogin, now = () => Date.now(), onChange
|
||||||
state.timed_break = { identity, deadline_at:now() + duration * 60000 };
|
state.timed_break = { identity, deadline_at:now() + duration * 60000 };
|
||||||
return write(state) ? validBreak(state) : false;
|
return write(state) ? validBreak(state) : false;
|
||||||
},
|
},
|
||||||
|
restoreBreak(identity, deadlineAt) {
|
||||||
|
const deadline = Number(deadlineAt);
|
||||||
|
if (typeof identity !== 'string' || !identity || !Number.isFinite(deadline) || deadline <= now()) return false;
|
||||||
|
const state = read();
|
||||||
|
const entry = state.entries[identity];
|
||||||
|
if (state.active_identity !== identity || !entry || entry.running || validBreak(state)) return false;
|
||||||
|
state.timed_break = { identity, deadline_at:Math.floor(deadline) };
|
||||||
|
return write(state);
|
||||||
|
},
|
||||||
breakSnapshot() {
|
breakSnapshot() {
|
||||||
return validBreak(read());
|
return validBreak(read());
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
SOURCE = Path(__file__).parents[1] / "frontend" / "today-session-sync.js"
|
SOURCE = Path(__file__).parents[1] / "frontend" / "today-session-sync.js"
|
||||||
|
TIMER = Path(__file__).parents[1] / "frontend" / "today-timer.js"
|
||||||
|
|
||||||
|
|
||||||
def run_node(script: str) -> dict:
|
def run_node(script: str) -> dict:
|
||||||
|
|
@ -92,6 +93,43 @@ const sync=createTodaySessionSync({
|
||||||
assert result["claimed"]["device_id"] == "desktop-b"
|
assert result["claimed"]["device_id"] == "desktop-b"
|
||||||
|
|
||||||
|
|
||||||
|
def test_owner_refresh_recovers_a_server_confirmed_break_missing_from_device_state():
|
||||||
|
result = run_node(
|
||||||
|
f"""
|
||||||
|
const createTimer = require({json.dumps(str(TIMER))});
|
||||||
|
const values = new Map();
|
||||||
|
const storage = {{
|
||||||
|
getItem:key=>values.get(key)||null,
|
||||||
|
setItem:(key,value)=>values.set(key,value),
|
||||||
|
}};
|
||||||
|
const timer = createTimer({{storage,getLogin:()=> 'timmy',now:()=>100000}});
|
||||||
|
timer.adopt('issue:r:42:', 90000, false);
|
||||||
|
let rendered = 0;
|
||||||
|
const remote = {{
|
||||||
|
revision:3,device_id:'phone-a',identity:'issue:r:42:',elapsed_ms:90000,
|
||||||
|
running:false,break_deadline_at:400000,updated_at:10,
|
||||||
|
}};
|
||||||
|
const sync = createTodaySessionSync({{
|
||||||
|
getDeviceId:()=> 'phone-a',fetchJson:async()=>remote,timer,
|
||||||
|
onOwnedRestore:()=>{{rendered += 1;}},
|
||||||
|
}});
|
||||||
|
(async()=>{{
|
||||||
|
await sync.refresh();
|
||||||
|
process.stdout.write(JSON.stringify({{breakSnapshot:timer.breakSnapshot(),rendered}}));
|
||||||
|
}})().catch(error=>{{console.error(error);process.exit(1);}});
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"breakSnapshot": {
|
||||||
|
"identity": "issue:r:42:",
|
||||||
|
"deadline_at": 400_000,
|
||||||
|
"expired": False,
|
||||||
|
},
|
||||||
|
"rendered": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_break_handoff_summary_is_actionable_and_privacy_safe():
|
def test_break_handoff_summary_is_actionable_and_privacy_safe():
|
||||||
result = run_node(
|
result = run_node(
|
||||||
r"""
|
r"""
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user