41 lines
6.7 KiB
JavaScript
41 lines
6.7 KiB
JavaScript
export const P='10000000-0000-4000-8000-000000000001', T='20000000-0000-4000-8000-000000000001', S='30000000-0000-4000-8000-000000000001';
|
|
export const stub=`(() => {
|
|
const real=window.fetch.bind(window),P=${JSON.stringify(P)},T=${JSON.stringify(T)},S=${JSON.stringify(S)};
|
|
const json=(v,status=200)=>new Response(JSON.stringify(v),{status,headers:{'content-type':'application/json'}});
|
|
window.reviewReads=[];window.reviewWrites=[];window.reviewUnexpected=[];window.reviewFail={};window.reviewDelay={};
|
|
window.fixtureProjects=[{id:P,name:'Pomodoro Timer',repo:'timer'},...['Writing workspace','Recipe box','Research notebook','Release planning'].map((name,i)=>({id:'10000000-0000-4000-8000-00000000000'+(i+2),name,repo:null})),{id:'10000000-0000-4000-8000-000000000009',name:'Archived project',repo:null,archived:true}];
|
|
window.fixtureThreads=[{id:T,project:P,name:'Build offline timer',instance:'assistant'},...['assistant','reviewer'].map((name,i)=>({id:'20000000-0000-4000-8000-00000000000'+(i+2),project:P,name,instance:name}))];
|
|
window.fixtureMessages=[{id:'40000000-0000-4000-8000-000000000001',from_role:'human',body:'Build a self-contained HTML Pomodoro timer I can open locally.',request:'request-1',at:'2026-11-06T10:24:00Z'},{id:'40000000-0000-4000-8000-000000000002',from_role:'assistant',body:'I will take this through requirements, implementation and review. I will bring you decisions that need your input.',at:'2026-11-06T10:25:00Z'}];
|
|
window.fixtureRoom=[{id:'50000000-0000-4000-8000-000000000001',ids:['50000000-0000-4000-8000-000000000001'],from_role:'human',to:['assistant','reviewer'],body:'Where are we on the timer, and is anything blocked?',at:'2026-11-06T10:24:00Z'},...['assistant','reviewer'].map((name,i)=>({id:'50000000-0000-4000-8000-00000000000'+(i+2),ids:['50000000-0000-4000-8000-00000000000'+(i+2)],from_role:name,to:['human'],body:i?'Nothing blocked on my side. I will review the cue when the build is ready.':'Start, pause and reset work. I am adding the visible phase-end cue now.',at:'2026-11-06T10:25:00Z'}))];
|
|
window.reviewStates={};window.reviewDrafts={};
|
|
for(const [i,id] of ['request-1','request-2'].entries()){
|
|
const titles=['Focus and break phases','Start, pause and reset','Works offline from one file','Keyboard access','Visible phase-end cue','Local delivery as timer.html'];
|
|
const candidate={request:id,project:'timer',mode_revision:1,prd_version:'v'+(3+i),records_commit:'9c41d0e',prd_path:'docs/prd.md',prd_blob:'a81f3c2',spec_commit:'3b7a2f5',requirements:titles.map((title,j)=>({id:'R'+(j+1),title,path:'docs/r'+j+'.md',blob:'b'+j}))};
|
|
window.reviewStates[id]={version:1,request:id,revision:1,phase:'implementation',project:'timer',mode:'autonomous',mode_revision:1,candidate,snapshot:'snapshot-'+id,tasks:['vikunja:2/12','vikunja:2/14'],wait:null};
|
|
window.reviewDrafts[id]={candidate,prd:'# Pomodoro Timer PRD '+candidate.prd_version+'\\n\\nGoal: a single HTML file that runs a Pomodoro timer offline.\\n\\n## Phases\\n\\n25-minute focus and 5-minute break.\\n\\n## Controls\\n\\nStart, pause and reset, all reachable by keyboard.\\n\\n## Cue\\n\\nA visible phase-end cue that does not depend on sound.\\n\\n## Delivery\\n\\nTimer.html, opened from the local disk.\\n\\n## Additional requirement\\n\\nUse readable typography and preserve state while paused.',requirements:candidate.requirements.map(r=>({...r,text:'Verified requirement text for '+r.title})),snapshot:{id:'snapshot-'+id,revision:1}};
|
|
}
|
|
window.reviewStates['request-2']={...window.reviewStates['request-2'],phase:'complete',tasks:[],delivery:'delivery-2',delivery_evidence:{id:'delivery-2',request:'request-2',project:'timer',snapshot:'snapshot-request-2',artifact:{path:'timer.html'},verification:{record_path:'docs/review.md'},pull_request:{url:'https://example.invalid/fixture/pr/1',request:'request-2',project:'timer'}}};
|
|
window.fetch=async(url,init={})=>{
|
|
const path=String(url),method=init.method||'GET';if(!path.startsWith('/api/'))return real(url,init);
|
|
if(method!=='GET'){window.reviewWrites.push({path,body:JSON.parse(init.body||'{}')});return json({error:'fixture-refusal',sent:false},409);}
|
|
window.reviewReads.push(path);if(window.reviewDelay[path])await new Promise(r=>setTimeout(r,window.reviewDelay[path]));if(window.reviewFail[path])return json({error:'unavailable'},503);
|
|
if(path==='/api/config')return json({send:true,requests:true,requestWrites:true,chatGrants:[]});
|
|
if(path==='/api/chat/projects')return json({rows:window.fixtureProjects.filter(p=>!p.archived)});
|
|
if(path==='/api/chat/projects/archived')return json({rows:window.fixtureProjects.filter(p=>p.archived)});
|
|
if(path==='/api/chat/instances')return json({rows:[{instance:'assistant',name:'assistant'},{instance:'reviewer',name:'reviewer'}]});
|
|
if(path.startsWith('/api/chat/threads')&&!path.includes('/messages')&&!path.includes('/sessions')&&!path.includes('/shares')){const u=new URL(path,location.origin);return json({rows:u.searchParams.get('shared')==='true'?[]:window.fixtureThreads.filter(t=>!u.searchParams.get('project')||t.project===u.searchParams.get('project'))});}
|
|
if(path.endsWith('/sessions'))return json({rows:[{id:S,name:'Main',default:true,state:'active',revision:1}]});
|
|
if(path.includes('/shares'))return json({rows:[]});
|
|
if(path.includes('/rooms/')&&path.includes('/messages'))return window.reviewClosed?json({error:'room-not-open',sent:false},409):json({rows:window.fixtureRoom,after:null,hasMore:false});
|
|
if(path.includes('/messages'))return json({rows:path.includes(T)?window.fixtureMessages:path.includes('20000000-0000-4000-8000-000000000003')?[{...window.fixtureMessages[0],request:'request-2'}]:[],after:null,hasMore:false});
|
|
const m=path.match(/^\\/api\\/requests\\/(request-[12])(\\/draft|\\/workspace)?$/);if(m){return json(m[2]==='/draft'?window.reviewDrafts[m[1]]:m[2]==='/workspace'?{available:true,entries:(m[1]==='request-1'?['index.html','timer.js','styles.css','README.md']:[]).map(name=>({name})),truncated:false}:window.reviewStates[m[1]]);}
|
|
if(path==='/api/bus/tasks')return json({rows:[{task_ref:'vikunja:2/12',fields:{title:'Accessible phase-end cue',bucket:3,done:false,assignees:['coder']}},{task_ref:'vikunja:2/14',fields:{title:'Confirm offline delivery',bucket:1,done:false,assignees:[]}}]});
|
|
if(path.startsWith('/api/bus/'))return json({rows:[],unread:0,at:'2026-11-06T10:24:00Z'});
|
|
if(path==='/api/business')return json({projects:['timer']});
|
|
if(path.startsWith('/api/library/status'))return json({error:'not-configured'},404);
|
|
if(path.startsWith('/api/project-files?'))return json({project:'timer',files:['spec/north-star.md','docs/prd.md','docs/requirements.md'].map(path=>({path}))});
|
|
if(path.startsWith('/api/project-file?'))return json({project:'timer',path:'spec/north-star.md',content:'A reliable Pomodoro timer that opens locally and works offline.'});
|
|
window.reviewUnexpected.push(path);return json({error:'unexpected-fixture-api'},404);
|
|
};
|
|
})();`;
|