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.