SOURCE / PINNED RELEASE
Made of little things.
Sketch Loop
- Release
- 14966bba4a2a…
- Author-recorded commit
- e68361295d81…
- License
- LICENSE
- Author’s source reference
- nostr://npub1n8ga89w8h6tvwamxusfyzexw8gjy84yxu9rxgnmk955cxtml4ujswzxydd/wss%3A%2F%2Fgit.napplet.soy%2F/n-929bc2a88cb
Archive hash verified: 5e0385e3c08f944f…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Real soyLI sandbox integration checks. Start `soyli dev --no-open` first.
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { mkdir, readFile } from 'node:fs/promises';
const require=createRequire(import.meta.url);
const cliRequire=createRequire(require.resolve('@napplet/conformance-cli/package.json'));
const {chromium}=cliRequire('playwright');
const browser=await chromium.launch();
const output=new URL('../.napplet-space/qa/',import.meta.url).pathname;
await mkdir(output,{recursive:true});
const page=await browser.newPage({viewport:{width:1200,height:820}});
const errors=[];page.on('pageerror',e=>errors.push(e.message));
async function load(){await page.goto('http://127.0.0.1:4173/');await page.locator('iframe').waitFor();const frame=await page.locator('iframe').elementHandle().then(e=>e.contentFrame());await frame.locator('#code').waitFor();return frame;}
try{
const frame=await load();
await frame.waitForFunction(()=>Number(document.querySelector('#frame-label').textContent.replace(/\D/g,''))>=300);
assert.match(await frame.locator('#status').innerText(),/Preview running/);
console.log('PASS BusyMozaic boots and animates in the real host');
for(const choice of ['1','2']){await frame.locator('#example').selectOption(choice);await page.waitForTimeout(150);assert.doesNotMatch(await frame.locator('#status').innerText(),/not supported|not available|exceeded/);}
await frame.locator('#example').selectOption('0');
await frame.locator('#pause').click();
const count=await frame.locator('#frame-label').textContent();await page.waitForTimeout(80);assert.equal(await frame.locator('#frame-label').textContent(),count);await frame.locator('#pause').click();
console.log('PASS examples and pause/resume');
for(const [source,pattern] of [['draw=()=>{while(true){}}',/budget/],['draw=()=>{window.napplet}',/not available/],['draw=()=>{',/Unexpected token/]]){
await frame.locator('#code').fill(source);await frame.locator('#run').click();assert.match(await frame.locator('#status').innerText(),pattern);
}
await frame.locator('#example').selectOption('0');
console.log('PASS invalid and runaway sketches recover');
await frame.locator('#duration').fill('30');await frame.locator('#export').click();await frame.locator('#cancel').click();await frame.locator('#status').filter({hasText:'Export cancelled'}).waitFor();
console.log('PASS export cancellation');
await frame.locator('#duration').fill('1.1');await frame.locator('#start').fill('5');await frame.locator('#export').click();await frame.locator('#result').waitFor({state:'visible',timeout:30000});
await frame.locator('#save-gif').click();await page.getByRole('button',{name:'Save file',exact:true}).click();
await frame.locator('#status').filter({hasText:'Saved sketch-1.1s.gif'}).waitFor();
const downloadPromise=page.waitForEvent('download');await page.locator('a[download="sketch-1.1s.gif"]').click();const download=await downloadPromise;await download.saveAs(`${output}export-1.1s.gif`);
const bytes=await readFile(`${output}export-1.1s.gif`);assert.equal(bytes.subarray(0,6).toString(),'GIF89a');
console.log(`PASS GIF generation → host picker → chunked writes → browser download (${bytes.length} bytes)`);
// Save/restore uses actual SDK storage through the sandbox.
await frame.locator('#save-draft').click();await frame.locator('#status').filter({hasText:'Draft and export settings saved'}).waitFor();
await page.reload();await page.locator('iframe').waitFor();const restored=await page.locator('iframe').elementHandle().then(e=>e.contentFrame());await restored.locator('#status').filter({hasText:'Saved draft restored'}).waitFor();assert.equal(await restored.locator('#duration').inputValue(),'1.1');await restored.locator('#run').click();
console.log('PASS SDK draft persistence and explicit rerun after restore');
for(const theme of ['light','dark']){
for(const [width,height] of [[200,160],[320,560],[900,600],[2400,1200]]){
await page.setViewportSize({width:Math.max(400,width),height:Math.max(300,height)});
await page.locator('iframe').evaluate((e,{width,height})=>{e.style.cssText=`position:fixed;left:0;top:0;width:${width}px;height:${height}px;min-width:0;min-height:0;max-width:none;max-height:none;z-index:9999;border:0;`;},{width,height});
const colors=theme==='dark'?{background:'#141b1c',text:'#edf2e9',primary:'#b9ed80'}:{background:'#f8f7ee',text:'#272c23',primary:'#73aa99'};
await page.evaluate(colors=>document.querySelector('iframe').contentWindow.postMessage({type:'theme.changed',theme:{colors}},'*'),colors);
await page.waitForTimeout(60);
assert.deepEqual(await restored.evaluate(()=>[innerWidth,innerHeight]),[width,height]);
const rootColors=await restored.evaluate(()=>['html','body','#app'].map(s=>getComputedStyle(document.querySelector(s)).backgroundColor));
assert.equal(new Set(rootColors).size,1);
assert.equal(rootColors[0],theme==='dark'?'rgb(20, 27, 28)':'rgb(248, 247, 238)');
for(const view of ['preview','editor','export']){
await restored.locator(`.view-tabs [data-view=${view}]`).click();
const layout=await restored.evaluate(()=>({width:innerWidth,doc:document.documentElement.scrollWidth,body:document.body.scrollWidth,buttons:[...document.querySelectorAll('button')].filter(e=>e.getClientRects().length).map(e=>({id:e.id,right:e.getBoundingClientRect().right,left:e.getBoundingClientRect().left}))}));
assert.ok(layout.doc<=width&&layout.body<=width,`${theme} ${width} ${view} horizontal overflow`);
for(const b of layout.buttons)assert.ok(b.left>=-1&&b.right<=width+1,`${theme} ${width} ${view} clipped ${b.id}`);
}
await restored.locator('.view-tabs [data-view=preview]').click();
await page.locator('iframe').screenshot({path:`${output}${theme}-${width}x${height}.png`});
console.log(`PASS ${width}×${height}, ${theme}, all three views`);
}
}
assert.deepEqual(errors,[]);
}finally{await browser.close();}