fix: serialize Week-to-Today replay across tabs
This commit is contained in:
parent
c03d0a091f
commit
4a6a829401
|
|
@ -391,7 +391,7 @@
|
||||||
});
|
});
|
||||||
const weekPlan = createWeekPlan({
|
const weekPlan = createWeekPlan({
|
||||||
fetchJson:fetchReviewJson,localDate:todayRollover.localDate,timeZone:todayRollover.timeZone,
|
fetchJson:fetchReviewJson,localDate:todayRollover.localDate,timeZone:todayRollover.timeZone,
|
||||||
storage:localStorage,getLogin:() => planningOwnerLogin,
|
storage:localStorage,getLogin:() => planningOwnerLogin,coordinator:outboxCoordinator,
|
||||||
});
|
});
|
||||||
function openWeekPlanner(trigger) { planningTomorrow=false; return weekFlow.open(trigger); }
|
function openWeekPlanner(trigger) { planningTomorrow=false; return weekFlow.open(trigger); }
|
||||||
function renderTomorrowQueueSummary(value) {
|
function renderTomorrowQueueSummary(value) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
function createWeekPlan({fetchJson,localDate,timeZone,storage,getLogin,now=()=>Date.now()}={}) {
|
function createWeekPlan({fetchJson,localDate,timeZone,storage,getLogin,coordinator,now=()=>Date.now()}={}) {
|
||||||
let week={revision:0,timezone:null,days:[]};
|
let week={revision:0,timezone:null,days:[]};
|
||||||
let flushing=null;
|
let flushing=null;
|
||||||
let lastConflict=null;
|
let lastConflict=null;
|
||||||
|
|
@ -530,7 +530,7 @@ function createWeekPlan({fetchJson,localDate,timeZone,storage,getLogin,now=()=>D
|
||||||
function flushPull() {
|
function flushPull() {
|
||||||
if(pulling)return pulling;
|
if(pulling)return pulling;
|
||||||
if(!pendingPull()||!pullKey())return Promise.resolve(false);
|
if(!pendingPull()||!pullKey())return Promise.resolve(false);
|
||||||
pulling=(async()=>{
|
const run=async()=>{
|
||||||
let result,lastResult=false;
|
let result,lastResult=false;
|
||||||
while(pendingPull()){
|
while(pendingPull()){
|
||||||
const current=pendingPull();
|
const current=pendingPull();
|
||||||
|
|
@ -564,7 +564,9 @@ function createWeekPlan({fetchJson,localDate,timeZone,storage,getLogin,now=()=>D
|
||||||
}
|
}
|
||||||
adoptConfirmed(lastResult.week,{...confirmedItems,...pendingItems});
|
adoptConfirmed(lastResult.week,{...confirmedItems,...pendingItems});
|
||||||
return {...lastResult,sync_pending:false};
|
return {...lastResult,sync_pending:false};
|
||||||
})().finally(()=>{pulling=null;});
|
};
|
||||||
|
const delivery=coordinator?coordinator.runExclusive('week',run):run();
|
||||||
|
pulling=Promise.resolve(delivery).finally(()=>{pulling=null;});
|
||||||
return pulling;
|
return pulling;
|
||||||
}
|
}
|
||||||
async function startEarly(planDate,todayRevision) {
|
async function startEarly(planDate,todayRevision) {
|
||||||
|
|
|
||||||
|
|
@ -398,6 +398,43 @@ console.log(JSON.stringify({queuedFirst,queuedSecond,resumed,confirmed,delivered
|
||||||
assert result["state"]["revision"] == 9
|
assert result["state"]["revision"] == 9
|
||||||
|
|
||||||
|
|
||||||
|
def test_week_controller_allows_only_one_tab_to_replay_the_shared_pull_queue():
|
||||||
|
result = run_controller("""
|
||||||
|
const values=new Map(),held=new Set();
|
||||||
|
const storage={getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)};
|
||||||
|
const coordinator={runExclusive:async(queue,work)=>{
|
||||||
|
if(held.has(queue))return {lease_skipped:true};
|
||||||
|
held.add(queue);try{return await work();}finally{held.delete(queue);}
|
||||||
|
}};
|
||||||
|
let offline=true,posts=0,release;
|
||||||
|
const gate=new Promise(resolve=>release=resolve);
|
||||||
|
const fetchJson=async(_url,options={})=>{
|
||||||
|
if(offline)throw new Error('connection lost');
|
||||||
|
posts+=1;await gate;
|
||||||
|
const body=JSON.parse(options.body);
|
||||||
|
return {today:{revision:5,ids:['active',body.identity],capacity_minutes:180,estimates:{active:30,[body.identity]:35}},
|
||||||
|
week:{revision:8,timezone:'UTC',days:[{plan_date:'2026-08-21',ids:[],capacity_minutes:150,estimates:{}}]}};
|
||||||
|
};
|
||||||
|
const options={storage,coordinator,getLogin:()=> 'timmy',fetchJson,localDate:()=> '2026-08-20',timeZone:()=> 'UTC'};
|
||||||
|
const source=createWeekPlan(options);source.adopt({revision:7,timezone:'UTC',days:[{plan_date:'2026-08-21',ids:['first'],capacity_minutes:150,estimates:{first:35}}]});
|
||||||
|
await source.pullItem('first',{revision:4,ids:['active'],capacity_minutes:180,estimates:{active:30}},'pull-first');
|
||||||
|
offline=false;
|
||||||
|
const first=createWeekPlan(options),second=createWeekPlan(options);
|
||||||
|
const firstFlush=first.flushPull();
|
||||||
|
await Promise.resolve();
|
||||||
|
setImmediate(release);
|
||||||
|
const skipped=await second.flushPull();
|
||||||
|
release();
|
||||||
|
const confirmed=await firstFlush;
|
||||||
|
console.log(JSON.stringify({posts,skipped,confirmed,pending:first.pendingPull()}));
|
||||||
|
""")
|
||||||
|
|
||||||
|
assert result["posts"] == 1
|
||||||
|
assert result["skipped"]["lease_skipped"] is True
|
||||||
|
assert result["confirmed"]["sync_pending"] is False
|
||||||
|
assert result["pending"] is None
|
||||||
|
|
||||||
|
|
||||||
def test_week_controller_resumes_server_truth_when_a_fifo_head_conflicts():
|
def test_week_controller_resumes_server_truth_when_a_fifo_head_conflicts():
|
||||||
result = run_controller("""
|
result = run_controller("""
|
||||||
const values=new Map();
|
const values=new Map();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user