Back to Sketch Loop
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.

tests/browser.mjs
// 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();}