Hmbown/CodeWhale · error · Error

Preview points could not load.

Error message

Preview points could not load.

What it means

Shared-companion twin of the pet-watch preview failure: `makePreview()` throws this when `fetch('whale-points.tsv')` returns a non-OK status, meaning the 980-point whale point cloud needed to build the isolated `PetNative` preview is unavailable. `startWork()` catches it and shows the message; the preview never starts.

Solutions

  1. Deploy `whale-points.tsv` alongside `shared.html` in the same directory.
  2. Fix server routing/static-asset serving so the TSV is reachable.
  3. Reload the page after the asset is fixed to retry the fetch.
  4. Use live companion mode (attach via token) if preview assets cannot be served.

Example fix

null
Defensive patterns

Strategy: fallback

Validate before calling

const probe = await fetch('whale-points.tsv', { method: 'HEAD' });
if (!probe.ok) console.warn('Preview asset missing at', new URL('whale-points.tsv', location).href);

Type guard

null

Try / catch

try {
  await startWork();
} catch (e) {
  if (e.message === 'Preview points could not load.') showMessage('Preview unavailable: whale-points.tsv is missing from this deployment.');
  else showMessage(e.message);
}

Prevention

When it happens

Trigger: Clicking Work or Replay while `points` is unloaded and the server responds to `GET whale-points.tsv` with 404/5xx; page served from a context without the static asset.

Common situations: Serving `pet/public/shared.html` standalone without its sibling `whale-points.tsv`; incomplete deployment of static assets; file:// or restricted hosting where the relative fetch fails.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/2c57bd18d0309f97. Report an issue: GitHub.

Appendix: source

Thrown at pet/public/shared.html:58

for(const a of presets){const b=document.createElement('button');b.type='button';b.setAttribute('aria-pressed','false');const sw=document.createElement('span');sw.className='swatch';sw.style.background=hex(a.background);const dot=document.createElement('i');dot.style.background=hex(a.particle);sw.append(dot);b.append(sw,document.createTextNode(a.name));b.onclick=()=>{appearance=clean(a);selected=a.name;syncControls();saveAppearance()};$('presets').append(b);const f=document.createElement('figure'),c=document.createElement('canvas'),label=document.createElement('figcaption');c.setAttribute('role','img');c.setAttribute('aria-label',a.name+' appearance');label.textContent=a.name;label.style.color=readable(a.background);f.append(c,label);$('gallery').append(f)}
for(const key of Object.keys(base))$(key).oninput=()=>{appearance[key]=['background','backgroundTop','particle'].includes(key)?rgb($(key).value):['eventColors','environment'].includes(key)?$(key).checked:Number($(key).value);if(key==='particle')appearance.eventColors=false;selected='Custom';syncControls();saveAppearance()};
syncControls();
async function request(path,body,headers={}){const r=await fetch(path,{method:body===undefined?'GET':'POST',headers:{'Content-Type':'application/json',...headers},body:body===undefined?undefined:JSON.stringify(body),signal:AbortSignal.timeout(2500)});if(!r.ok){let e;try{e=await r.json()}catch{}const error=Error(e?.error||'Local pet unavailable. Reopen from Codewhale or use the preview.');error.rejected=r.status===409&&!error.message.includes('storage');throw error}return r.json()}
async function flushAction(){if(busy||!current||preview)return;if(!pending&&nextAppearance){pending={identity:current.identity,client,seq:seq+1,source_revision:current.sourceRevision,action:{kind:'appearance',appearance:nextAppearance}};nextAppearance=null}if(!pending)return;busy=true;try{await request('/v1/action',pending);seq=pending.seq;pending=null;$('message').textContent='Appearance and interactions saved by the shared companion.'}catch(e){if(e.rejected)pending=null;$('message').textContent=e.message}finally{busy=false}}
async function poll(){if(preview)return;try{const frame=await request('/v1/frame');if(frame.version!==1||!Array.isArray(frame.points)||frame.points.length!==980)throw Error('Unsupported pet snapshot');if(current?.epoch!==frame.epoch){previous=null;lastTickAt=performance.now()}else if(frame.tick!==current.tick){previous=current;lastTickAt=performance.now()}current=frame;received=performance.now();if(!nextAppearance&&!pending&&valid(frame.appearance)&&JSON.stringify(appearance)!==JSON.stringify(frame.appearance)){appearance=clean(frame.appearance);selected=presetName(appearance);syncControls()}$('identity').textContent='Pet '+frame.identity+' · '+frame.source+' · tick '+frame.tick+' · '+frame.digest;if(frame.audioUnavailable&&sound){sound=false;$('sound').textContent='Sound unavailable';$('sound').setAttribute('aria-pressed','false')}if(pending||nextAppearance)await flushAction()}catch(e){$('message').textContent=e.message}finally{if(!preview)setTimeout(poll,33)}}
function interact(food){if(preview){pet?.interact(food?'food':'attention',.2,-.15);return}if(!current||performance.now()-lastTickAt>800||pending)return;pending={identity:current.identity,client,seq:seq+1,source_revision:current.sourceRevision,action:{kind:'interact',food,x:.2,y:-.15}};void flushAction()}
$('focus').onclick=()=>interact(false);$('pulse').onclick=()=>interact(true);canvas.onpointerdown=()=>interact(false);
$('sound').onclick=async()=>{if(preview)return;try{const r=await request('/v1/audio',{client,enabled:!sound});sound=r.granted;$('sound').textContent=sound?'Sound on':'Sound off';$('sound').setAttribute('aria-pressed',String(sound))}catch(e){$('message').textContent=e.message}};
setInterval(()=>{if(sound&&!document.hidden&&performance.now()-lastTickAt<500)request('/v1/audio',{client,enabled:true}).catch(()=>{sound=false})},500);
document.addEventListener('visibilitychange',()=>{if(document.hidden&&sound){sound=false;void request('/v1/audio',{client,enabled:false});$('sound').textContent='Sound off';$('sound').setAttribute('aria-pressed','false')}lastDraw=0;accumulator=0});
$('compare').onclick=()=>{document.body.classList.toggle('gallery-mode');$('compare').setAttribute('aria-pressed',String(document.body.classList.contains('gallery-mode')))};
$('configure').onclick=()=>{document.body.classList.toggle('no-settings');$('configure').setAttribute('aria-pressed',String(!document.body.classList.contains('no-settings')))};
function immersive(on){document.body.classList.toggle('immersive',on);$('expand').textContent=on?'Back to studio':'Full habitat';if(on)document.body.classList.remove('gallery-mode')}
$('expand').onclick=()=>immersive(!document.body.classList.contains('immersive'));window.addEventListener('keydown',e=>{if(e.key==='Escape'){immersive(false);document.body.classList.remove('gallery-mode');$('compare').setAttribute('aria-pressed','false')}});
function download(name,data){const u=URL.createObjectURL(new Blob([JSON.stringify(data,null,2)],{type:'application/json'})),a=document.createElement('a');a.href=u;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(u),1000)}
$('export-config').onclick=()=>download('codewhale-appearance.json',{version:1,appearance:clean(appearance)});$('import-config').onclick=()=>$('config-file').click();$('config-file').onchange=async()=>{try{const f=$('config-file').files[0];if(!f)return;if(f.size>4096)throw Error('Choose an appearance file smaller than 4 KiB.');const value=JSON.parse(await f.text());if(value.version!==1)throw Error('Unsupported appearance version.');appearance=clean(value.appearance);selected='Custom';syncControls();saveAppearance()}catch(e){$('message').textContent=e.message}finally{$('config-file').value=''}};
$('save').onclick=async()=>{try{download(preview?'codewhale-preview-replay.json':'codewhale-shared-pet.json',preview?JSON.parse(pet.recording(true)):await request('/v1/export'))}catch(e){$('message').textContent=e.message}};
async function makePreview(){if(!points){const r=await fetch('whale-points.tsv');if(!r.ok)throw Error('Preview points could not load.');points=JSON.stringify((await r.text()).trim().split('\n').map(row=>row.trim().split(/\s+/).map(Number)))}if(!preview){previewSaved=true;if(sound){sound=false;void request('/v1/audio',{client,enabled:false})}}preview=true;$('return-live').hidden=!previewSaved;pet=new PetNative(points,'','[]',true);demoTick=0;actionTick=0;previous=null;current=null;$('connection-label').textContent='Isolated preview · no task running';$('sound').disabled=true;$('sound').textContent='Preview is silent';$('identity').textContent='Preview world · canonical 980-particle core · no live session';$('message').textContent='This preview is isolated. Its colors can be exported; it does not change a live pet.'}
function showResult(on){const el=$('result');if(!on&&el.contains(document.activeElement))$('work').focus();el.inert=!on;el.classList.toggle('visible',on);el.setAttribute('aria-hidden',String(!on));if(on&&document.activeElement===$('finish'))el.focus()}
async function startWork(){try{await makePreview();actionChoice='thinking';demoRunning=true;demoDone=false;showResult(false);document.body.classList.add('preview-running');immersive(true)}catch(e){$('message').textContent=e.message}}
function finishWork(){if(!pet)return;demoRunning=false;demoDone=true;pet.observeEngine(JSON.stringify({event:'turn_complete'}),demoTick*1000/30);showResult(true);document.body.classList.remove('preview-running')}
$('return-live').onclick=()=>{preview=false;pet=null;demoDone=false;demoRunning=false;showResult(false);$('return-live').hidden=true;$('sound').disabled=false;$('sound').textContent='Sound off';$('connection-label').textContent='Shared habitat';document.body.classList.remove('preview-running');void poll()};
$('work').onclick=startWork;$('replay').onclick=startWork;$('finish').onclick=finishWork;
const actionTools={reading:'read_file',searching:'search_files',editing:'apply_patch',executing:'exec_command',testing:'run_tests',browsing:'browser_navigate',memory:'retrieve_context'};
const workPhases=['thinking','reading','searching','browsing','memory','delegating','editing','executing','testing','waiting','responding'];
function beginAction(kind,at){pet.observeEngine(JSON.stringify({event:'turn_started'}),at);const emit=e=>pet.observeEngine(JSON.stringify(e),at);if(actionTools[kind])emit({event:'tool_call_started',tool_call_id:'preview-tool',tool_name:actionTools[kind]});else if(kind==='waiting')emit({event:'user_input_required',id:'preview-question'});else if(kind==='delegating'){for(let i=0;i<3;i++)emit({event:'agent_spawned',id:'preview-worker-'+i})}else if(kind==='thinking'||kind==='responding')emit({event:kind==='thinking'?'thinking_started':'message_started',index:0});else if(kind==='error')emit({event:'error'});actionTick=0}
$('action-preview').onchange=async()=>{try{if(!preview)await makePreview();demoRunning=false;demoDone=false;actionChoice=$('action-preview').value;beginAction(actionChoice,demoTick*1000/30);showResult(false);document.body.classList.remove('preview-running')}catch(e){$('message').textContent=e.message}};
function advancePreview(){const at=demoTick*1000/30;if(!demoDone){if(demoRunning){const phase=Math.min(workPhases.length-1,Math.floor(demoTick/120));if(demoTick%120===0){actionChoice=workPhases[phase];$('action-preview').value=actionChoice;beginAction(actionChoice,at)}}else if(demoTick===0)beginAction(actionChoice,at);if(actionTick%6===0){const emit=e=>pet.observeEngine(JSON.stringify(e),at);if(actionTools[actionChoice])emit({event:'tool_call_heartbeat'});else if(actionChoice==='delegating'){for(let i=0;i<3;i++)emit({event:'agent_progress',id:'preview-worker-'+i,worker_status:'running'})}else if(actionChoice==='thinking'||actionChoice==='responding')emit({event:'response_delta',index:0,channel:actionChoice==='thinking'?'reasoning':'text'});else if(actionChoice==='error')emit({event:'error'})}}actionTick++;pet.advanceEngine(++demoTick*1000/30,true,!demoDone&&actionChoice==='waiting');if(demoRunning&&demoTick>=120*workPhases.length)finishWork();previous=current;current=JSON.parse(pet.presentation());lastTickAt=received=performance.now();current.producerConnected=!demoDone&&actionChoice!=='unknown';current.epoch='preview';}
// View-only marks use the owner's fixed tick. They consume no random stream,
// claim no results, and freeze under Still. The moving body remains canonical.
function actionMarks(ctx,kind,t,w,h,ink,parallel){const cx=w*.5,cy=h*.55,r=Math.min(w*.29,h*.26),phase=t*.8;ctx.save();ctx.strokeStyle=ctx.fillStyle=ink;ctx.lineWidth=1;ctx.globalAlpha=.24;const line=(x1,y1,x2,y2)=>{ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke()},circle=(x,y,r)=>{ctx.beginPath();ctx.arc(x,y,r,0,Math.PI*2);ctx.stroke()};
if(['reading','memory','files'].includes(kind)){for(const side of [-1,1]){const x=cx+side*r*1.35;for(let j=0;j<6;j++){const y=cy-42+j*15;ctx.globalAlpha=.1+.16*(.5+.5*Math.sin(phase-j*.7));line(x-21,y,x+21-(j%3)*6,y)}}}
else if(kind==='searching'||kind==='browsing'||kind==='network'){circle(cx,cy,r*1.3);const a=phase%(Math.PI*2);line(cx+Math.cos(a)*r,cy+Math.sin(a)*r,cx+Math.cos(a)*r*1.42,cy+Math.sin(a)*r*1.42);for(let i=0;i<5;i++){const a=i*Math.PI*2/5;circle(cx+Math.cos(a)*r*1.3,cy+Math.sin(a)*r*1.3,3+i%2)}}
else if(['editing','executing','tool'].includes(kind)){for(const side of [-1,1]){const x=cx+side*r*1.3;line(x,cy-32,x,cy+32);line(x,cy-32,x-side*12,cy-32);line(x,cy+32,x-side*12,cy+32)}ctx.globalAlpha=.2+.2*(.5+.5*Math.sin(phase*3));line(cx+r*.9,cy+44,cx+r*.9+18,cy+44)}
else if(kind==='testing'){for(let i=0;i<12;i++){const a=i*Math.PI/6;ctx.globalAlpha=.08+.35*Math.max(0,Math.cos(a-phase));line(cx+Math.cos(a)*r*1.25,cy+Math.sin(a)*r*1.25,cx+Math.cos(a)*r*1.38,cy+Math.sin(a)*r*1.38)}}
else if(kind==='waiting'){for(let i=0;i<3;i++){ctx.globalAlpha=.08+.05*(.5+.5*Math.sin(phase-i));circle(cx,cy,r*(1.12+i*.14))}}

View on GitHub (pinned to 433685b202)