fix(3ds): the runtime fixes Pocket Shell found — adjacent-swap reconcile, the guest's stack budget, a pinned emulator clock - #352
Draft
doodlewind wants to merge 4 commits into
Draft
Conversation
Omarchy binds every window action to SUPER plus one key, and SUPER + K shows the table. Pocket Shell keeps that grammar and changes the hardware: L is super, R is shift, L+R addresses workspaces, and the chord map for whichever shoulder is down replaces the minimap on the touch screen — the reference appears exactly when the modifier that needs it is pressed, and disappears when it is released. The top screen is the stage: Omarchy's tokyo-night wallpaper under windows tiled by dwindle (a binary split tree, longer side first, splits preserved across a close) or scrolling (columns at 0.49 of the width on a strip that pans to keep the focused column whole), five workspaces, a 14 px bar. The touch screen is the deck: workspace strip, live minimap, dock, launcher, and a hand-laid keyboard. Windows hold local applets — pocketsh, a clock, notes, the key sheet, stats, about — so nothing needs a companion. Touch does what the buttons do. Tap the minimap to focus, drag a window onto another to swap or onto a tab to move it, drag the gap between two windows to move that split. Closing is a hold, a slide and a release onto a bar, the Pocket Term convention: the panel is resistive and single-contact, and a small × is how a shell gets killed by accident. wm.ts, chords.ts and shell.ts are pure and unit-tested; the render layer replays the golden tape headlessly in the sim, which is what caught the renderer bug below in 30 ms instead of an emulator boot. Ten Azahar goldens cover both screens and reproduce byte-exactly across runs. Also here, because the app was the first thing to need them: - framework: insertBefore(node, node) is a no-op, as in the DOM. Solid's reconcileArrays issues exactly that call when two adjacent <For> items swap, and unlinking first left the node as its own missing anchor — every adjacent window swap threw. Covered in tests/renderer.test.ts. - e2e: the Azahar fixture pins the emulated clock so a guest that shows the time stays byte-exact, and the default 3DS spec set now runs both apps. Nothing is bound to ZL/ZR: they are New-3DS-only, arrive through ir:rst rather than the HID pad, and have no BTN constant in the spec yet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…y applet in a golden Opening one `keys` window on hardware took the whole guest down: the runtime rolled back to last-good, which on a card that also holds Pocket Term is Pocket Term, so the shell appeared to turn into another app and stay there. The guest had hit `InternalError: stack overflow`. The 3DS spends its JS stack on JSX NESTING DEPTH, not node count — a QuickJS call frame is expensive and mounting descends the tree — and an applet sits under stage, windows, chrome and content before it draws anything. 192 KiB did not cover a windowed applet whose rows each carried a wrapper view with a Show inside. The sibling Pocket Term work already found 384 KiB; the main thread's stack is 1 MiB and the limit costs nothing until it is used. Two changes, because either alone leaves a trap: - hosts/3ds/src/qjs.c raises the budget to 384 KiB. This is the root cause, and it needs a reflash to reach a card already carrying the old binary. - Keys and Stats render each column as a flat pass of absolutely-positioned Text under the applet root — a row is an offset, not a node — which is what lets the fix reach an unflashed console over the dev wire. The README now states the budget so the next applet starts the right shape. The first tape never opened these applets, so three of the six reached a device for the first time in Evan's hands. `pocket-shell-applets` taps every dock cell and is in the default spec set; it reproduces the crash in ~90 s against the old budget, which is how this was diagnosed rather than guessed. An emulator with a generous stack would not have warned us, so the tape is the guard, not the sim. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ress Four things Evan found on the console, three of them real defects. **The clock flickered.** Not a rendering fault: two wrong times alternating frame to frame. The RTC's epoch is sound — Date.now() is monotonic and correct on hardware — but the console's breakdown of it is not, and getHours() intermittently disagreed with the epoch by whole hours, applying the timezone on some reads and not others. Measured over the dev wire, the big time text changed by ~1600 px between consecutive frames; it now changes by zero. Nothing calls a Date breakdown method any more: civilFromEpoch derives the civil date and time by arithmetic, tested across leap years, the 1900/2100 century rules and the exact reading the device produced. The zone is sampled once at boot and kept only if it looks like a real one, so a bad read cannot be frozen in — and because this console reports none, pocketsh gained `tz +8` to state the offset it could not. This also explains the one golden frame that read 06:47 among neighbours reading 00:00. It reproduced byte-exactly, so I let it stand; it was the same bug, visible and dismissed. A capture that reproduces is not thereby correct. **The term icon drew a tofu box** — `❯` is not in the baked face, so a Text rendered the placeholder. It is pixels now. Rotation was the obvious alternative and would have been worse: `-rotate-45` is not a utility this compiler accepts, and an unknown token makes the whole class literal unknown, which paints nothing at all. **The keys icon read as a row of dots.** Two flex rows in a container with no `flex-col` — the default direction here is row, so they sat side by side. It is a keyboard now, placed absolutely. **Painted buttons say nothing when pressed.** A physical button reports itself; on a resistive panel with no hover, nothing else tells you the panel heard you. The strip pills, layout badge, workspace tabs, gutter buttons, dock cells, chord rows and launcher cards now darken or invert under the finger and hold it a few frames after release, so a quick tap is still seen. A pan or a long press withdraws it, because those are not taps. The applets tape captures a frame mid-tap to keep it honest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ime fixes it found The shell is a product, not a demo of the runtime, so it now lives in pocket-stack/pocket-shell with the PocketJS checkout as its vendor submodule — the same split Pocket Term took. Its app, tests, tapes and byte-exact frames move there; this branch keeps only what belongs to the runtime. What stays, and why it is worth keeping on its own: - framework/src/native-tree.ts: insertBefore(node, node) is a no-op, like the DOM. Solid's reconcileArrays issues exactly that call when two ADJACENT <For> items swap, and the old code unlinked first, so the node became its own missing anchor and threw. Any app that reorders a <For> by one position hit it; tests/renderer.test.ts pins the case. - hosts/3ds/src/qjs.c: the guest's JS stack is 384 KiB, not 192. The console spends it on JSX nesting depth rather than node count, and a UI four levels deeper than a demo overflowed it mid-frame — after which the runtime rolled back to last-good, which on a shared SD card looks like a different app booting. - tests/e2e/azahar.ts: the fixture pins the emulated clock, so a guest that shows the RTC can still be compared byte for byte. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Pocket Shell now lives in its own repository — pocket-stack/pocket-shell, with this checkout as its
vendor/pocketjssubmodule, the same split Pocket Term took. This branch keeps only what belongs to the runtime.Three fixes, each found by running a real UI on the console rather than a demo:
framework/src/native-tree.ts—insertBefore(node, node)is a no-op, like the DOM. Solid'sreconcileArraysissues exactly that call when two adjacent<For>items swap; the old code unlinked the node first, so it became its own missing anchor and threwinsert anchor is not a child of parent. Every adjacent swap crashed. Any app that reorders a<For>by one position hits this;tests/renderer.test.tspins the case.hosts/3ds/src/qjs.c— the guest's JS stack is 384 KiB, not 192. The console spends it on JSX nesting depth, not node count: a QuickJS call frame is expensive and mounting descends the tree. A UI four levels deeper than a demo overflowed it mid-frame, after which the runtime's recovery chain fell back to last-good — which on a shared SD card looks exactly like a different app booting. This is the same budget the Pocket Term work independently needed.tests/e2e/azahar.ts— the fixture pins the emulated clock (init_clock=1,init_time= 2000-01-01), so a guest that shows the RTC can still be compared byte for byte.DEFAULT_SPEC_NAMESis back to the demo.Verified
bunx tsc --noEmitclean;tests/renderer.test.ts51/51 andtests/platform-contracts.test.ts37/37 green after the removal.The app, its tests, its input tapes and those frames now live in the product repository, along with a frame recorder that turns the same tapes into the animations its README shows.
🤖 Generated with Claude Code