April 18, 2026

downport

date: 2026-04-18


took plan98 and stripped it to a bios demo for kids.

three apps, one shell, one window manager. escape shows you the source code. that's the whole thing.


what got cut

geckos.io multiplayer. the lazy module loader. 200 shell commands. wallet. gallery. auth. activity pub. the whole WAS stack.

what's left is draw: art music coding. plus exit quit escape to kill. the shell doesn't need to know anything else.


iframes are gone

plan98 loaded apps into iframes at /app/flip-book etc — server routed those paths. plan1 has no server. so we swapped the iframes for custom elements directly in the tray body.


the sandbox gotcha

plan98's store runs merge functions inside a QuickJS WASM sandbox. functions get serialized to strings and re-evaluated in an isolated context.

closure variables don't survive serialization.

// breaks — w and h are from the outer scope, gone in QuickJS
$.teach(apps, (state, payload) => {
  newState[tray] = { width: w, height: h, ... }
})

// works — w and h are in the payload
$.teach(apps, (state, payload) => {
  payload.forEach(({ tray, url, w, h, ... }) => {
    newState[tray] = { width: w, height: h, ... }
  })
})

same rule applies to setState in multi-task — it was passing an options object (mergeHandler, parameters) as the nuance arg. that API doesn't exist in the stripped plan98.js. rewrote it to pass tray through the payload instead.


credits

berkeley mono is the system font. all objects belong to their original artists.


what's next

the four quadrant layout loads on startup. ur-shell in the top-left, art top-right, music bottom-left, coding bottom-right. no src attribute needed — multi-task defaults to the demo grid.