PocketCompiler 3DS: Difference between revisions
More actions
Created page with "{{Infobox 3DS Homebrews |title=PocketCompiler |image=PocketCompiler3DS.png |description=An almost fully featured HTML/JS/CSS compiler application made for 3DS/n3DS consoles. |author=PlanetDogeCodes |lastupdated=2026/06/17 |type=Other Apps |version=0.36 |license=Apache-2.0 |download=https://dlhb.gamebrew.org/3dshomebrews/PocketCompiler3DS.7z |website=https://github.com/PlanetDogeCodes/Pocket-Compiler |source=https://github.com/PlanetDogeCodes/Pocket-Compiler }} PocketComp..." |
0.48 |
||
| (2 intermediate revisions by the same user not shown) | |||
| Line 4: | Line 4: | ||
|description=An almost fully featured HTML/JS/CSS compiler application made for 3DS/n3DS consoles. | |description=An almost fully featured HTML/JS/CSS compiler application made for 3DS/n3DS consoles. | ||
|author=PlanetDogeCodes | |author=PlanetDogeCodes | ||
|lastupdated=2026/ | |lastupdated=2026/07/14 | ||
|type=Other Apps | |type=Other Apps | ||
|version=0. | |version=0.48 | ||
|license=Apache-2.0 | |license=Apache-2.0 | ||
|download=https://dlhb.gamebrew.org/3dshomebrews/PocketCompiler3DS.7z | |download=https://dlhb.gamebrew.org/3dshomebrews/PocketCompiler3DS.7z | ||
| Line 12: | Line 12: | ||
|source=https://github.com/PlanetDogeCodes/Pocket-Compiler | |source=https://github.com/PlanetDogeCodes/Pocket-Compiler | ||
}} | }} | ||
PocketCompiler is an experimental Nintendo 3DS HTML/JS/CSS compiler and code editor | PocketCompiler is an experimental Nintendo 3DS HTML/JS/CSS compiler and code editor. | ||
PocketCompiler is designed to let you: | |||
* | * Write HTML/JS/CSS on the 3DS, allowing for easy development | ||
* Compile and run web pages directly on the top screen with a real DOM-based rendering engine | |||
* Save/load projects from the SD card | * Save/load/rename/delete projects from the SD card | ||
* Debug with a live JS console | |||
The default project path is <code>sdmc:/3ds/PocketCompiler/projects</code>/ | The default project path is <code>sdmc:/3ds/PocketCompiler/projects</code>/ | ||
== Features == | == Features == | ||
* | * HTML parsing with DOM tree construction | ||
* | * CSS engine with cascade, inheritance, specificity, custom properties (<code>var()</code>), <code>calc()</code>, <code>@media</code> skip, <code>:not()</code>, <code>:hover</code>/<code>:focus</code>/<code>:active</code>/<code>:checked</code>/<code>:disabled</code>, <code>:first-child</code>/<code>:last-child</code>/<code>:nth-child()</code>, comma selectors, <code>!important</code>, box-sizing, flex/grid layout, transitions, box-shadow, linear-gradient, word-wrap, and 40+ CSS properties | ||
* | * JavaScript runtime via Duktape (ES5 + subset of ES6) | ||
* DOM- | * DOM manipulation: <code>getElementById</code>, <code>querySelector</code>/<code>querySelectorAll</code>, <code>createElement</code>, <code>appendChild</code>, <code>innerHTML</code>, <code>textContent</code>, <code>classList</code>, live <code>style</code> object (Proxy-backed), <code>addEventListener</code>, <code>onclick</code>/<code>onload</code>/etc. property handlers | ||
* Keyboard/ | * Keyboard, mouse, and touch events with bubbling | ||
* | * <code>document.write()</code> with incremental parsing | ||
* | * Software keyboard integration for <code><input></code>/<code><textarea></code> text entry and <code>window.prompt()</code> | ||
* | * Form submission via click on submit buttons | ||
* IndexedDB | * <code>fetch()</code> / XMLHttpRequest (SD-card backed) | ||
* | * localStorage / sessionStorage (SD-card persisted) | ||
* | * IndexedDB (SD-card persisted, simplified sync API) | ||
* Image loading | * Canvas 2D rendering | ||
* Web | * WebGL scaffolding (Citro3D backend) | ||
* Image loading (PNG via LodePNG) | |||
* Web Audio (oscillators, gain, WAV playback via NDSP) | |||
* Near-complete iframe support | |||
* Auto-save draft (writes to SD on compile + 10s of idle) | |||
* Crash-recovery: "Recover autosave" action restores unsaved work | |||
* Remembers last-opened file across launches | |||
* File explorer: save, load, rename, delete, sort by name/size/date | |||
== Canvas / WebGL == | == Canvas / WebGL == | ||
PocketCompiler has rendering systems for: | PocketCompiler has rendering systems for: | ||
* | * Canvas 2D: rectangles, paths, text, images, gradients, transforms, <code>fillText</code>/<code>strokeText</code>/<code>measureText</code> | ||
* WebGL: <code>getContext("webgl")</code> detection, buffers, shaders/programs, uniforms/attributes, textures, draw calls, viewport/depth/blend/scissor state, Citro3D backend scaffolding | |||
* <code>getContext( | |||
This is not yet full WebGL. | This is not yet full WebGL. | ||
== Storage == | == Storage == | ||
PocketCompiler includes SD-card-backed storage systems: | PocketCompiler includes SD-card-backed storage systems: | ||
Supported | <pre>sdmc:/3ds/PocketCompiler/ | ||
* IndexedDB-style databases | config.txt <- last-opened file (auto-managed) | ||
autosave.html <- crash-recovery draft (auto-managed) | |||
* localStorage- | console_log.txt <- exported JS console log (written on demand) | ||
* sessionStorage- | projects/ <- all .html project files | ||
* | storage/ | ||
local_*.json <- localStorage data | |||
idb/ <- IndexedDB data</pre> | |||
Supported: | |||
* IndexedDB-style databases with <code>put</code>, <code>get</code>, <code>delete</code>, <code>clear</code>, <code>close</code> | |||
* localStorage with SD-card persistence | |||
* sessionStorage (in-memory, per session) | |||
* Cookie-style storage | |||
== Audio == | == Audio == | ||
PocketCompiler includes a | PocketCompiler includes a Web Audio-style system: | ||
* AudioContext-like runtime | * AudioContext-like runtime | ||
* OscillatorNode | * OscillatorNode (sine/square/triangle/sawtooth) | ||
* GainNode | |||
* AudioBufferSourceNode with WAV file loading | |||
* NDSP-backed output when available | * NDSP-backed output when available | ||
* | * Safe fallback if audio fails | ||
== Limitations == | == Limitations == | ||
| Line 79: | Line 81: | ||
It does not yet fully support: | It does not yet fully support: | ||
* Full modern CSS (no CSS animations/@keyframes, no rotate()/scale() transforms, limited position: fixed) | |||
* Full CSS | * Full modern JavaScript (no ES modules, no async/await, no Proxy traps beyond get/set) | ||
* Full modern JavaScript/ | * Real networking (all fetch/XHR URLs resolve to SD-card files) | ||
* | * Full WebGL 1.0 (no real GLSL shader translation) | ||
* Full WebGL 1.0 | * Full Canvas 2D API (no drawImage from video, no complex path fills) | ||
* Full Web Audio API (no real-time effects, no offline rendering) | |||
* Full Canvas 2D API | |||
* Full Web Audio API | |||
* Complete iframe isolation | * Complete iframe isolation | ||
* Pointer lock | * Pointer lock | ||
The 3DS also has very limited RAM, CPU, GPU power, and screen space, so PocketCompiler uses strict safety limits ( | The 3DS also has very limited RAM, CPU, GPU power, and screen space, so PocketCompiler uses strict safety limits (bounded DOM pool, bounded event queues, bounded command buffers, 4 MB max audio buffer, recursion depth guards). | ||
==Controls== | |||
'''Edit Mode — Editor Tab:''' | |||
D-Pad Up/Down - Scroll code vertically | |||
D-Pad Left/Right - Scroll code horizontally | |||
Circle Pad - Move cursor dot | |||
Touch - Snap cursor to tap; tap tabs / code lines / file rows | |||
A - Edit current line (3DS keyboard; auto-indent if empty) | |||
B - Undo (5-step ring) | |||
X - Save project (file explorer) | |||
Y - Load project (file explorer) | |||
L - Toggle JS Console tab | |||
R - Open Actions menu (Find, Go-to, Duplicate, Comment, Rename, Delete, Sort, Export log, Recover autosave) | |||
Start - Compile + run | |||
Select - Show controls menu (scrollable) | |||
'''Edit Mode — Console Tab:''' | |||
D-Pad Up/Down - Scroll console log | |||
A - Type & eval JS expression | |||
B - Clear console log | |||
L - Back to editor tab | |||
'''Run Mode:''' | |||
D-Pad - Scroll web page (all directions) | |||
Circle Pad - Move cursor (never scrolls) | |||
A / L - Left-click | |||
B / R - Right-click | |||
X - Release cursor lock | |||
Y - Space key (sent to web content) | |||
Start - Stop, Return to editor | |||
== Screenshots == | == Screenshots == | ||
| Line 115: | Line 150: | ||
== Changelog == | == Changelog == | ||
'''v0.48''' | |||
* The 3DS crash issue in the previous version was caused by the '''Duktape fatal error handler''' entering an infinite spin loop (<code>for(;;) svcSleepThread(...)</code>) after JS context creation completed. When any post-creation Duktape operation triggered a fatal error (OOM, etc.), the handler froze the entire 3DS, requiring a reboot. | |||
* Fixes Applied | |||
** '''Fatal handler no longer spins infinitely (web_js.c)''' | |||
*** Changed the post-creation fatal handler from <code>for(;;) svcSleepThread(1000000000LL)</code> to simply <code>return</code>. While returning from a Duktape fatal handler is technically undefined, in practice Duktape propagates the error as a JS exception or returns a safe value. The app continues running instead of freezing the 3DS. | |||
** '''<code><style></code> block buffer increased (web_html_parser.c)''' | |||
*** <code>stbuf</code> increased from 8192 to 16384 bytes. A 31KB HTML file can easily have >8KB of CSS. Truncation at 8KB produced malformed CSS that could confuse the parser. | |||
** '''<code><script></code> block buffer increased (web_html_parser.c)''' | |||
*** <code>scbuf</code> increased from 16384 to 32768 bytes. Large inline scripts were being truncated at 16KB, causing syntax errors (caught by <code>duk_peval_string</code> but wasteful). | |||
** '''DOM pool sizes increased (web_dom.h)''' | |||
*** <code>WE_MAX_NODES</code>: 1024 → 2048 (a complex 31KB page can have >1024 elements + text nodes) | |||
*** <code>WE_MAX_ATTRS</code>: 512 → 1024 (matching the larger node pool) | |||
** '''C2D_TextBuf NULL checks (web_render.c)''' | |||
*** Added OOM guards after <code>C2D_TextBufNew(4096)</code> in both the wrapping and non-wrapping text render paths. If allocation fails, the function skips drawing that text instead of crashing. | |||
** '''All unprotected <code>duk_eval_string</code> calls replaced with protected versions:''' | |||
: {| class="wikitable" | |||
|- | |||
! File | |||
! Call | |||
! Fix | |||
|- | |||
| <code>web_js.c:1346</code> | |||
| <code>duk_eval_string_noresult</code> (polyfills) | |||
| → <code>duk_peval_string_noresult</code> | |||
|- | |||
| <code>web_iframe.c:184</code> | |||
| <code>duk_eval_string_noresult</code> (postMessage stub) | |||
| → <code>duk_peval_string_noresult</code> | |||
|- | |||
| <code>web_canvas.c:369</code> | |||
| <code>duk_eval_string</code> (canvas property setup) | |||
| → <code>duk_peval_string</code> + fallback to plain object on failure | |||
|} | |||
'''v0.47''' | |||
* '''Multi-line text rendering''' | |||
** <code>wrender_text</code> previously drew the entire text string as a single <code>C2D_DrawText</code> call, so paragraphs that the layout engine had correctly wrapped to multiple lines rendered as one long line overflowing off-screen. The function now detects when text exceeds the available width, splits at word boundaries using the same <code>char_width</code> metric as the layout engine, and draws each line separately with correct alignment, bold, italic, and decoration. | |||
* '''<code>position: fixed</code> / <code>sticky</code>''' | |||
** <code>position: fixed</code> elements no longer scroll with the page — the renderer skips applying the scroll offset for <code>position==3</code> nodes, keeping them pinned to the viewport. <code>position: sticky</code> is now parsed (value 4) and treated like <code>relative</code> for layout. True scroll-tracking stickiness is deferred. | |||
* '''Dirty-only CSS cascade''' | |||
** <code>css_cascade</code> previously ran <code>css_compute_node</code> on every element every frame when any node was dirty. On a 100-element page with 50 CSS rules, that's ~5000 <code>selector_matches</code> calls per frame. Now only processes nodes where <code>dirty==1</code>. On page load all nodes are dirty (unchanged behavior); during per-frame hover/focus updates, typically 1-2 nodes are dirty, reducing cascade cost by ~99%. | |||
* '''<code>window.getComputedStyle()</code>''' | |||
** Returns a read-only object with 20+ computed style properties as CSS-formatted strings: <code>width</code>, <code>height</code>, <code>display</code> (as string), <code>color</code>/<code>backgroundColor</code> (as <code>rgba(...)</code>), <code>fontSize</code>, <code>position</code>, <code>opacity</code> (as 0-1 float), <code>visibility</code>, margins, padding, border, <code>left</code>/<code>top</code>, and <code>offsetLeft/Top/Width/Height</code>. Fixed: <code>display</code> was being set twice (boolean then string); <code>opacity</code> was returning 0-255 instead of 0.0-1.0. | |||
* '''<code>Element.matches()</code> rewritten''' | |||
** Previously only handled <code>#id</code>, <code>.class</code>, and bare tag names. Now delegates to <code>css_selector_matches</code> — the full selector matching engine — so it handles descendant selectors, attribute selectors, pseudo-classes (<code>:hover</code>, <code>:checked</code>), <code>:not()</code>, adjacent siblings (<code>+</code>), and pseudo-elements. | |||
* '''<code>Element.closest()</code>''' | |||
** New method. Walks up the ancestor chain (including self) until one matches the given selector, returning the element or <code>null</code>. Cycle-guarded with <code>g_dom_node_n</code> bound. Uses the full <code>css_selector_matches</code> engine for each ancestor check. | |||
* '''<code>Array.from()</code> polyfill + <code>NodeList</code> alias''' | |||
** Polyfills <code>Array.from</code> for array-like objects and iterables with optional <code>mapFn</code>/<code>thisArg</code>. Aliases <code>NodeList</code> to <code>Array</code> so <code>instanceof NodeList</code> checks pass and <code>NodeList.prototype.forEach</code> exists (Duktape arrays already have <code>forEach</code>). <code>JSON.parse</code>/<code>JSON.stringify</code> verified working via Duktape's built-in <code>DUK_USE_JSON_BUILTIN</code>. | |||
* '''Files changed''' | |||
{| class="wikitable" | |||
|- | |||
! File | |||
! Changes | |||
|- | |||
| <code>web_render.c</code> | |||
| Multi-line text wrapping in <code>wrender_text</code>; <code>position:fixed</code> scroll-offset skip | |||
|- | |||
| <code>web_layout.c</code> | |||
| <code>position:fixed</code> uses viewport (0,0); <code>position:sticky</code> (value 4) treated like relative | |||
|- | |||
| <code>web_css.c</code> | |||
| Dirty-only cascade in <code>css_cascade</code>; <code>css_selector_matches</code> public wrapper; <code>position:sticky</code> parser | |||
|- | |||
| <code>web_css.h</code> | |||
| <code>css_selector_matches</code> declaration; <code>CSS_SEL_LEN</code> 64→128 | |||
|- | |||
| <code>web_dom.h</code> | |||
| <code>position</code> comment updated for sticky (value 4) | |||
|- | |||
| <code>web_js.c</code> | |||
| <code>getComputedStyle</code>, <code>closest</code>, rewritten <code>matches</code>, <code>Array.from</code> polyfill, <code>NodeList</code> alias, opacity format fix | |||
|} | |||
'''v0.45''' | |||
* '''All New Stuff''' | |||
** CSS selectors were completely failing on complex selectors | |||
*** The <code>selector_matches</code> function only handled '''simple selectors''' (tag, <code>.class</code>, <code>#id</code>, <code>tag.class</code>). It could not match any selector containing: | |||
**** '''Descendant combinators''' (spaces): <code>.tog input</code>, <code>.field input[type=text]</code>, <code>details:not([open]) summary</code> | |||
**** '''Adjacent sibling combinators''' (<code>+</code>): <code>.tog input:checked + .tog-sw</code> | |||
**** '''Attribute selectors''' (<code>[type=checkbox]</code>): <code>input[type=checkbox]</code> | |||
**** '''Pseudo-elements''' (<code>::after</code>, <code>::before</code>): <code>summary::after</code>, <code>.tog-sw::after</code> | |||
*** Since nearly every selector in the EaglerLite CSS uses at least one of these, '''zero CSS rules were matching''' — the page rendered with default styles only. | |||
** DPad scroll was broken | |||
*** The v0.43 DPad scroll fix (which used <code>we_page_scroll_by</code> when the web engine was loaded) was accidentally reverted during the v0.44 bugfix pass, going back to the old text-mode-only scroll. DPad Up/Down did nothing when viewing a web page. | |||
* '''What I fixed''' | |||
** New <code>simple_selector_matches</code> function | |||
*** Handles a single simple selector component, now with: | |||
**** '''Attribute selectors''': <code>[attr]</code>, <code>[attr=val]</code>, <code>[attr="val"]</code> — parses the bracket, splits on <code>=</code>, strips quotes, checks the node's attribute | |||
**** '''Pseudo-elements''': <code>::after</code>, <code>::before</code>, <code>::marker</code>, <code>::placeholder</code>, <code>::selection</code> — always accepted (we don't render them, but the selector matches the base element so its other properties still apply) | |||
**** '''<code>*</code> with attribute selectors''': <code>*[attr=val]</code> works | |||
**** Uses <code>css_classlist_has</code> (local, no strtok) instead of <code>classlist_has</code> (which lives in web_js.c) | |||
** Rewritten <code>selector_matches</code> function | |||
*** Now handles '''compound selectors''' with combinators: | |||
**** '''Descendant''' (space): <code>.tog input</code> — walks up ancestors checking each part right-to-left | |||
**** '''Adjacent sibling''' (<code>+</code>): <code>input + .tog-sw</code> — checks the immediately preceding sibling | |||
**** Splits the selector at spaces and <code>+</code> (respecting <code>[bracket]</code> depth so <code>[type=checkbox]</code> doesn't get split) | |||
**** Each part is matched via <code>simple_selector_matches</code> | |||
**** Guard counter on all ancestor walks prevents infinite loops | |||
** DPad scroll restored | |||
*** DPad Up/Down/Left/Right now scrolls the web page via <code>we_page_scroll_by(0, ±30)</code> when <code>g_web_engine.loaded</code> is true. Falls back to text-mode scroll when the engine isn't loaded. Circle Pad is always cursor-only. | |||
'''v0.44''' | |||
* '''CSS Custom Properties (Variables) — <code>var()</code> + <code>:root</code>''' | |||
** The biggest addition. <code>:root { --bg: #000; }</code> stores custom properties in a global variable table. <code>var(--bg)</code> is resolved at parse time before any property handler runs — works in colors, lengths, and any value. Supports <code>var(--name, fallback)</code> syntax and recursive resolution (variables referencing other variables). Up to 64 variables stored. Reset on page recompile. | |||
* '''<code>@media</code> Queries — Graceful Skip''' | |||
** <code>@media(max-width: 680px) { ... }</code> is now parsed and skipped gracefully (with proper brace-nesting tracking). Previously, <code>@media</code> would confuse the stylesheet parser and corrupt all subsequent rules. Also skips <code>@keyframes</code>, <code>@import</code>, <code>@font-face</code>, and <code>@charset</code>. | |||
* '''<code>:root</code> Selector''' | |||
** <code>:root { ... }</code> declarations are applied as a universal <code>*</code> rule with the lowest specificity, so <code>:root</code> properties cascade to all elements via inheritance. Custom properties (<code>--name: value</code>) are stored in the variable table. | |||
* '''<code>:not()</code> Selector''' | |||
** <code>:not(.hidden)</code> now works. The inner selector is recursively matched and negated. Forward-declared to handle the mutual recursion between <code>pseudo_matches</code> → <code>pseudo_not_matches</code> → <code>selector_matches</code>. | |||
* '''Comma-Separated Selectors''' | |||
** <code>h1, h2, h3 { ... }</code> now creates a separate rule for each sub-selector instead of creating one rule with a comma in the selector (which never matched anything). | |||
* '''<code>!important</code> Support''' | |||
** <code>color: red !important</code> is now accepted — the <code>!important</code> suffix is stripped before parsing. All declarations are treated as having the same specificity (later wins), so <code>!important</code> doesn't change cascade behavior but no longer causes parse failures. | |||
* '''New CSS Properties (15 additions)''' | |||
{| class="wikitable" | |||
|- | |||
! Property | |||
! Description | |||
|- | |||
| <code>box-sizing</code> | |||
| <code>border-box</code> / <code>content-box</code> (parsed, stored) | |||
|- | |||
| <code>flex</code> shorthand | |||
| <code>flex: 1</code> → grow=1; <code>flex: 1 0 auto</code> → grow/shrink/basis | |||
|- | |||
| <code>flex-shrink</code> | |||
| Default 1 | |||
|- | |||
| <code>flex-basis</code> | |||
| <code>auto</code> or length | |||
|- | |||
| <code>pointer-events</code> | |||
| <code>none</code> / <code>auto</code> | |||
|- | |||
| <code>user-select</code> | |||
| <code>none</code> / <code>auto</code> | |||
|- | |||
| <code>outline</code> | |||
| Width + color (simplified, no layout impact) | |||
|- | |||
| <code>outline-width</code> / <code>outline-color</code> / <code>outline-style</code> | |||
| Individual properties | |||
|- | |||
| <code>text-transform</code> | |||
| <code>uppercase</code> / <code>lowercase</code> / <code>capitalize</code> / <code>none</code> | |||
|- | |||
| <code>letter-spacing</code> | |||
| px or <code>normal</code> | |||
|- | |||
| <code>line-height</code> | |||
| px, unitless multiplier, or <code>normal</code> | |||
|- | |||
| <code>list-style</code> / <code>list-style-type</code> | |||
| <code>none</code> / <code>disc</code> / <code>circle</code> / <code>square</code> / <code>decimal</code> | |||
|- | |||
| <code>object-fit</code> | |||
| <code>fill</code> / <code>contain</code> / <code>cover</code> | |||
|- | |||
| <code>min/max-width/height</code> | |||
| Now handles <code>none</code> and <code>auto</code> correctly | |||
|} | |||
'''v0.43''' | |||
* '''UI: DPad = Scroll, Circle Pad = Cursor (fixed)''' | |||
** The biggest UX fix: DPad and Circle Pad no longer conflict. Previously, DPad both scrolled the fallback text renderer AND sent arrow key events to web content — pressing Up would scroll the page ''and'' fire a <code>keydown("ArrowUp")</code> at the same time. Circle Pad could also trigger scroll in some paths. | |||
** '''Now:''' | |||
*** '''DPad''' scrolls the web page (30px per press, all four directions). When the web engine isn't loaded, it scrolls the fallback text output instead. | |||
*** '''Circle Pad''' always moves the cursor — it never scrolls, in any mode. | |||
*** DPad was removed from the web content key map entirely, so no more phantom arrow key events. | |||
*** Only '''Y''' (Space) generates a keyboard event for web content. | |||
*** Controls help screen updated to reflect the new assignments. | |||
* '''Cookie Clicker Compatibility: 3 Major JS APIs Added''' | |||
** '''<code>element.classList</code>''' — Cookie Clicker (and most modern JS) uses <code>classList.add()</code>, <code>.remove()</code>, <code>.toggle()</code>, <code>.contains()</code> extensively. Previously only <code>className</code> existed. Now <code>classList</code> returns a live object with all four methods that manipulate the node's <code>class</code> attribute and trigger <code>css_compute_node</code> so styles re-cascade. | |||
** '''Live <code>element.style</code> object''' — Cookie Clicker does <code>element.style.display = 'none'</code>, <code>element.style.left = '100px'</code>, etc. Previously <code>style</code> was a dead object — setting properties did nothing. Now the style object is wrapped in a '''Duktape Proxy''' that intercepts every property get/set: | |||
*** '''Get''' (<code>element.style.display</code>): parses the inline <code>style</code> attribute and returns the property value. | |||
*** '''Set''' (<code>element.style.display = 'none'</code>): updates the inline style string via a C-backed <code>cssText</code> setter, triggers <code>css_compute_node</code> + <code>dom_mark_dirty</code> so the layout and renderer pick up the change immediately. | |||
** '''<code>onclick</code>/<code>onload</code>/<code>onerror</code> property handlers''' — Cookie Clicker assigns <code>element.onclick = function(){...}</code> instead of using <code>addEventListener</code> or inline <code>onclick="..."</code> attributes. Previously only inline attribute handlers and <code>addEventListener</code> worked. Now <code>we_dispatch</code> checks for JS function properties on the node object (<code>onclick</code>, <code>onmousedown</code>, <code>onload</code>, <code>onsubmit</code>, etc.) and calls them with a proper event object. Supports all 13 event types. | |||
'''v0.42''' | |||
* '''CSS Transitions''' | |||
** <code>transition: 0.3s ease-in-out</code> now actually animates. When a style property changes on an element with a transition duration, the old and new values are interpolated over the duration using smoothstep easing. Supports 25 numeric properties (opacity, width, height, margins, padding, position, font-size, border, transforms) plus color properties (color, background) which interpolate per-channel. The transition engine runs in the frame loop and marks transitioning nodes dirty so layout re-runs each frame to reflect the changing values. | |||
* '''Box-Shadow''' | |||
** <code>box-shadow: 2px 2px 4px rgba(0,0,0,0.5)</code> is parsed into offset/blur/color and drawn as a semi-transparent rect behind the element before the background fill. No real gaussian blur (3DS hardware can't do that efficiently), but the spread approximation looks reasonable for most UI shadows. | |||
* '''Linear-Gradient Backgrounds''' | |||
** <code>background: linear-gradient(to right, red, blue)</code> is parsed into start/end colors and a direction, then rendered as 16 interpolated color bands. Supports <code>to right/bottom/left/top</code> directions and <code>Ndeg</code> angles. Falls back to solid <code>background</code> color when no gradient is active. Transparent colors work correctly (uses explicit "set" flags instead of checking for zero). | |||
* '''CSS Grid Layout''' | |||
** <code>display: grid</code> with <code>grid-template-columns</code> now lays out children in a grid. Supports fixed pixel widths, <code>fr</code>/<code>auto</code> (equal distribution), and <code>repeat(N, ...)</code> syntax up to 8 columns. Rows auto-size to the tallest cell, gap is applied between cells, and children are vertically centered within their row. | |||
* '''CSS <code>calc()</code>''' | |||
** <code>calc(100% - 20px)</code> is now evaluated at layout time. Supports <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code> with correct operator precedence, and all length units (px, em, rem, vw, vh, %) work inside calc terms. Self-contained in the length parser — no new systems. | |||
* '''Form Submission''' | |||
** Clicking a <code><button type="submit"></code> or <code><input type="submit"></code> inside a <code><form></code> now fires the form's <code>onsubmit</code> handler and dispatches a <code>submit</code> event. This replaces the old START-key approach which conflicted with START=exit-run-mode. | |||
* '''Keybind Conflict Fixes''' | |||
** Resolved button assignment conflicts between the app shell and web content event system. In run mode, only DPad (arrow keys) and Y (Space) generate keyboard events for web content. A/B/X/L/R/START are exclusively mouse clicks, cursor lock, and exit-run-mode — no more phantom keydown events alongside mouse clicks. | |||
* '''Bug Fixes''' | |||
** IndexedDB path overflow: <code>store</code>/<code>key</code> strings from JS are now length-validated to prevent path truncation | |||
** IndexedDB <code>get()</code> return logic: was returning true for empty files | |||
** IndexedDB <code>clear()</code> prefix buffer: enlarged from 80 to 128 bytes to handle longer store names | |||
** Transition color precision: RGBA8 colors now stored as uint32 (not float) to avoid precision loss past 2^24 | |||
** Gradient transparent-color: <code>0x00000000</code> (transparent) no longer treated as "unset" | |||
'''v0.41''' | |||
* '''<code><input></code>/<code><textarea></code> text entry''' | |||
** New <code>we_input_prompt()</code> in <code>web_events.c</code> opens the 3DS software keyboard when a click lands on an <code><input></code>/<code><textarea></code> (or an ancestor of one). Reads the current <code>value</code> attribute, shows it as initial text, writes the result back, fires <code>input</code> + <code>change</code> events, and marks the node dirty so the renderer shows the new text. | |||
** New <code>we_text_input()</code> helper wraps <code>swkbdInit</code>/<code>swkbdInputText</code> for reuse. | |||
* '''CSS <code>transform: translate()</code>''' | |||
** <code>wrender_node</code> now applies <code>translate_x</code>/<code>translate_y</code> to the draw position, and folds the offset into the children's <code>ox</code>/<code>oy</code> so children move with the translated parent. ~6 lines of real logic; the parser already stored the values since v0.39. | |||
* '''Word-wrap in the layout engine''' | |||
** Replaced the naive <code>tw/avail_w + 1</code> character-count wrapping with proper word-boundary wrapping. Walks the text word by word, tracks line width, breaks when the next word would overflow. Hard-breaks words longer than a full line. Respects <code>white-space: pre</code> and <code>nowrap</code>. | |||
* '''CSS <code>:hover</code> / <code>:focus</code> / <code>:nth-child</code> pseudo-classes''' | |||
** New <code>pseudo_matches()</code> function checks <code>:hover</code>, <code>:focus</code>, <code>:active</code>, <code>:checked</code>, <code>:disabled</code>, <code>:first-child</code>, <code>:last-child</code>, <code>:nth-child(n/odd/even)</code>. | |||
** <code>selector_matches()</code> rewritten to split selectors at <code>:</code> and check the pseudo-class after the base selector matches. Supports chained pseudos (<code>a:focus:hover</code>). | |||
** Dynamic re-cascade: <code>we_events_update</code> marks nodes dirty on hover/focus changes; <code>we_update</code> now re-runs <code>css_cascade</code> (not just <code>layout_document</code>) when nodes are dirty, so <code>:hover</code>/<code>:focus</code> styles apply dynamically. | |||
* '''Keyboard input to web content''' | |||
** <code>window.prompt(message, default)</code> now opens the 3DS software keyboard and returns the typed string (was a no-op stub returning <code>undefined</code>). | |||
** Clicking an <code><input></code>/<code><textarea></code> opens the keyboard (Feature 1 above). | |||
* '''localStorage persistence + IndexedDB completion''' | |||
** '''IndexedDB C API hardened''': <code>ws_idb_open</code> now checks for existing databases with the same name (no duplicates); <code>ws_idb_get</code> validates <code>fread</code> return (was a potential underflow); <code>ws_idb_clear</code> is no longer a no-op — it scans the directory with <code>opendir</code>/<code>readdir</code> and removes all files matching the store prefix; all functions now have proper NULL/bounds checks. | |||
** '''IndexedDB JS bindings''': <code>ws_install_idb</code> now installs a real <code>indexedDB</code> object to JS with <code>open()</code>, <code>put()</code>, <code>get()</code>, <code>delete()</code>, <code>clear()</code>, <code>close()</code> methods. <code>put()</code> JSON-encodes values; <code>get()</code> safely JSON-parses them back (falls back to string if not valid JSON). The "IndexedDB" feature pill is now actually true. | |||
'''v0.38''' | |||
* '''Crash / correctness bugs:''' | |||
** Canvas 2D silently broken — wc_install_bindings() built its property-setup script with snprintf into a 512-byte buffer, but the actual script is 854 bytes. It was truncating mid-statement, so ctx.fillStyle = 'red' and every other canvas property setter did nothing, with zero error shown. Now passed as a literal directly to duk_eval_string — no buffer, no truncation possible. | |||
** Event listeners could misfire — DOMEvent.js_ref was a uint8_t, silently wrapping after the 255th addEventListener() call in a session and firing the wrong callback. Widened to uint16_t. | |||
** Listener ref counter never reset — it was a function-local static that persisted across every recompile instead of resetting with the fresh Duktape context each page load gets. Moved to file scope with an explicit reset tied to context creation. | |||
** Unchecked array access in we_document_open() — switched to the existing bounds-checked accessor so a corrupted sibling chain can't read out-of-bounds memory. | |||
* '''Stack-safety hardening (3DS has only 256KB of stack):''' | |||
** ~3KB of stacked buffers in the CSS parser — css_parse_inline's 2048-byte buffer is called while css_parse_stylesheet's 1024-byte buffer is still live. Both made static (verified safe: parsing is never re-entrant). | |||
** Unbounded layout recursion — nothing stopped a deeply-nested DOM tree (up to the 1024-node cap) from recursing that many stack frames deep. Added a depth guard capped well above any realistic page. | |||
* Version v0.37 was skipped due to issues with a beta build. | |||
* CIA files will be added in version 1.0 | |||
'''v0.36''' | '''v0.36''' | ||
* '''Crash / correctness fixes:''' | * '''Crash / correctness fixes:''' | ||
| Line 193: | Line 461: | ||
== Credits == | == Credits == | ||
PocketCompiler project and idea by PlanetDogeCodes | PocketCompiler project and idea by PlanetDogeCodes. Some browser runtime features, custom Canvas/WebGL Integration, and 3DS Optimizations were all made with help from GPT-5.5 (xhigh) and GLM 5.2 (max). Some UI elements and DOM-style event handling were made with help from Claude Sonnet 5 (high) | ||
Built with: | Built with: | ||
Latest revision as of 02:08, 17 July 2026
| PocketCompiler | |
|---|---|
| General | |
| Author | PlanetDogeCodes |
| Type | Other Apps |
| Version | 0.48 |
| License | Apache-2.0 |
| Last Updated | 2026/07/14 |
| Links | |
| Download | |
| Website | |
| Source | |
PocketCompiler is an experimental Nintendo 3DS HTML/JS/CSS compiler and code editor.
PocketCompiler is designed to let you:
- Write HTML/JS/CSS on the 3DS, allowing for easy development
- Compile and run web pages directly on the top screen with a real DOM-based rendering engine
- Save/load/rename/delete projects from the SD card
- Debug with a live JS console
The default project path is sdmc:/3ds/PocketCompiler/projects/
Features
- HTML parsing with DOM tree construction
- CSS engine with cascade, inheritance, specificity, custom properties (
var()),calc(),@mediaskip,:not(),:hover/:focus/:active/:checked/:disabled,:first-child/:last-child/:nth-child(), comma selectors,!important, box-sizing, flex/grid layout, transitions, box-shadow, linear-gradient, word-wrap, and 40+ CSS properties - JavaScript runtime via Duktape (ES5 + subset of ES6)
- DOM manipulation:
getElementById,querySelector/querySelectorAll,createElement,appendChild,innerHTML,textContent,classList, livestyleobject (Proxy-backed),addEventListener,onclick/onload/etc. property handlers - Keyboard, mouse, and touch events with bubbling
document.write()with incremental parsing- Software keyboard integration for
<input>/<textarea>text entry andwindow.prompt() - Form submission via click on submit buttons
fetch()/ XMLHttpRequest (SD-card backed)- localStorage / sessionStorage (SD-card persisted)
- IndexedDB (SD-card persisted, simplified sync API)
- Canvas 2D rendering
- WebGL scaffolding (Citro3D backend)
- Image loading (PNG via LodePNG)
- Web Audio (oscillators, gain, WAV playback via NDSP)
- Near-complete iframe support
- Auto-save draft (writes to SD on compile + 10s of idle)
- Crash-recovery: "Recover autosave" action restores unsaved work
- Remembers last-opened file across launches
- File explorer: save, load, rename, delete, sort by name/size/date
Canvas / WebGL
PocketCompiler has rendering systems for:
- Canvas 2D: rectangles, paths, text, images, gradients, transforms,
fillText/strokeText/measureText - WebGL:
getContext("webgl")detection, buffers, shaders/programs, uniforms/attributes, textures, draw calls, viewport/depth/blend/scissor state, Citro3D backend scaffolding
This is not yet full WebGL.
Storage
PocketCompiler includes SD-card-backed storage systems:
sdmc:/3ds/PocketCompiler/
config.txt <- last-opened file (auto-managed)
autosave.html <- crash-recovery draft (auto-managed)
console_log.txt <- exported JS console log (written on demand)
projects/ <- all .html project files
storage/
local_*.json <- localStorage data
idb/ <- IndexedDB data
Supported:
- IndexedDB-style databases with
put,get,delete,clear,close - localStorage with SD-card persistence
- sessionStorage (in-memory, per session)
- Cookie-style storage
Audio
PocketCompiler includes a Web Audio-style system:
- AudioContext-like runtime
- OscillatorNode (sine/square/triangle/sawtooth)
- GainNode
- AudioBufferSourceNode with WAV file loading
- NDSP-backed output when available
- Safe fallback if audio fails
Limitations
PocketCompiler is still in early development.
It does not yet fully support:
- Full modern CSS (no CSS animations/@keyframes, no rotate()/scale() transforms, limited position: fixed)
- Full modern JavaScript (no ES modules, no async/await, no Proxy traps beyond get/set)
- Real networking (all fetch/XHR URLs resolve to SD-card files)
- Full WebGL 1.0 (no real GLSL shader translation)
- Full Canvas 2D API (no drawImage from video, no complex path fills)
- Full Web Audio API (no real-time effects, no offline rendering)
- Complete iframe isolation
- Pointer lock
The 3DS also has very limited RAM, CPU, GPU power, and screen space, so PocketCompiler uses strict safety limits (bounded DOM pool, bounded event queues, bounded command buffers, 4 MB max audio buffer, recursion depth guards).
Controls
Edit Mode — Editor Tab:
D-Pad Up/Down - Scroll code vertically
D-Pad Left/Right - Scroll code horizontally
Circle Pad - Move cursor dot
Touch - Snap cursor to tap; tap tabs / code lines / file rows
A - Edit current line (3DS keyboard; auto-indent if empty)
B - Undo (5-step ring)
X - Save project (file explorer)
Y - Load project (file explorer)
L - Toggle JS Console tab
R - Open Actions menu (Find, Go-to, Duplicate, Comment, Rename, Delete, Sort, Export log, Recover autosave)
Start - Compile + run
Select - Show controls menu (scrollable)
Edit Mode — Console Tab:
D-Pad Up/Down - Scroll console log
A - Type & eval JS expression
B - Clear console log
L - Back to editor tab
Run Mode:
D-Pad - Scroll web page (all directions)
Circle Pad - Move cursor (never scrolls)
A / L - Left-click
B / R - Right-click
X - Release cursor lock
Y - Space key (sent to web content)
Start - Stop, Return to editor
Screenshots
Changelog
v0.48
- The 3DS crash issue in the previous version was caused by the Duktape fatal error handler entering an infinite spin loop (
for(;;) svcSleepThread(...)) after JS context creation completed. When any post-creation Duktape operation triggered a fatal error (OOM, etc.), the handler froze the entire 3DS, requiring a reboot. - Fixes Applied
- Fatal handler no longer spins infinitely (web_js.c)
- Changed the post-creation fatal handler from
for(;;) svcSleepThread(1000000000LL)to simplyreturn. While returning from a Duktape fatal handler is technically undefined, in practice Duktape propagates the error as a JS exception or returns a safe value. The app continues running instead of freezing the 3DS.
- Changed the post-creation fatal handler from
<style>block buffer increased (web_html_parser.c)stbufincreased from 8192 to 16384 bytes. A 31KB HTML file can easily have >8KB of CSS. Truncation at 8KB produced malformed CSS that could confuse the parser.
<script>block buffer increased (web_html_parser.c)scbufincreased from 16384 to 32768 bytes. Large inline scripts were being truncated at 16KB, causing syntax errors (caught byduk_peval_stringbut wasteful).
- DOM pool sizes increased (web_dom.h)
WE_MAX_NODES: 1024 → 2048 (a complex 31KB page can have >1024 elements + text nodes)WE_MAX_ATTRS: 512 → 1024 (matching the larger node pool)
- C2D_TextBuf NULL checks (web_render.c)
- Added OOM guards after
C2D_TextBufNew(4096)in both the wrapping and non-wrapping text render paths. If allocation fails, the function skips drawing that text instead of crashing.
- Added OOM guards after
- All unprotected
duk_eval_stringcalls replaced with protected versions:
- Fatal handler no longer spins infinitely (web_js.c)
File Call Fix web_js.c:1346duk_eval_string_noresult(polyfills)→ duk_peval_string_noresultweb_iframe.c:184duk_eval_string_noresult(postMessage stub)→ duk_peval_string_noresultweb_canvas.c:369duk_eval_string(canvas property setup)→ duk_peval_string+ fallback to plain object on failure
v0.47
- Multi-line text rendering
wrender_textpreviously drew the entire text string as a singleC2D_DrawTextcall, so paragraphs that the layout engine had correctly wrapped to multiple lines rendered as one long line overflowing off-screen. The function now detects when text exceeds the available width, splits at word boundaries using the samechar_widthmetric as the layout engine, and draws each line separately with correct alignment, bold, italic, and decoration.
position: fixed/stickyposition: fixedelements no longer scroll with the page — the renderer skips applying the scroll offset forposition==3nodes, keeping them pinned to the viewport.position: stickyis now parsed (value 4) and treated likerelativefor layout. True scroll-tracking stickiness is deferred.
- Dirty-only CSS cascade
css_cascadepreviously rancss_compute_nodeon every element every frame when any node was dirty. On a 100-element page with 50 CSS rules, that's ~5000selector_matchescalls per frame. Now only processes nodes wheredirty==1. On page load all nodes are dirty (unchanged behavior); during per-frame hover/focus updates, typically 1-2 nodes are dirty, reducing cascade cost by ~99%.
window.getComputedStyle()- Returns a read-only object with 20+ computed style properties as CSS-formatted strings:
width,height,display(as string),color/backgroundColor(asrgba(...)),fontSize,position,opacity(as 0-1 float),visibility, margins, padding, border,left/top, andoffsetLeft/Top/Width/Height. Fixed:displaywas being set twice (boolean then string);opacitywas returning 0-255 instead of 0.0-1.0.
- Returns a read-only object with 20+ computed style properties as CSS-formatted strings:
Element.matches()rewritten- Previously only handled
#id,.class, and bare tag names. Now delegates tocss_selector_matches— the full selector matching engine — so it handles descendant selectors, attribute selectors, pseudo-classes (:hover,:checked),:not(), adjacent siblings (+), and pseudo-elements.
- Previously only handled
Element.closest()- New method. Walks up the ancestor chain (including self) until one matches the given selector, returning the element or
null. Cycle-guarded withg_dom_node_nbound. Uses the fullcss_selector_matchesengine for each ancestor check.
- New method. Walks up the ancestor chain (including self) until one matches the given selector, returning the element or
Array.from()polyfill +NodeListalias- Polyfills
Array.fromfor array-like objects and iterables with optionalmapFn/thisArg. AliasesNodeListtoArraysoinstanceof NodeListchecks pass andNodeList.prototype.forEachexists (Duktape arrays already haveforEach).JSON.parse/JSON.stringifyverified working via Duktape's built-inDUK_USE_JSON_BUILTIN.
- Polyfills
- Files changed
| File | Changes |
|---|---|
web_render.c
|
Multi-line text wrapping in wrender_text; position:fixed scroll-offset skip
|
web_layout.c
|
position:fixed uses viewport (0,0); position:sticky (value 4) treated like relative
|
web_css.c
|
Dirty-only cascade in css_cascade; css_selector_matches public wrapper; position:sticky parser
|
web_css.h
|
css_selector_matches declaration; CSS_SEL_LEN 64→128
|
web_dom.h
|
position comment updated for sticky (value 4)
|
web_js.c
|
getComputedStyle, closest, rewritten matches, Array.from polyfill, NodeList alias, opacity format fix
|
v0.45
- All New Stuff
- CSS selectors were completely failing on complex selectors
- The
selector_matchesfunction only handled simple selectors (tag,.class,#id,tag.class). It could not match any selector containing:- Descendant combinators (spaces):
.tog input,.field input[type=text],details:not([open]) summary - Adjacent sibling combinators (
+):.tog input:checked + .tog-sw - Attribute selectors (
[type=checkbox]):input[type=checkbox] - Pseudo-elements (
::after,::before):summary::after,.tog-sw::after
- Descendant combinators (spaces):
- Since nearly every selector in the EaglerLite CSS uses at least one of these, zero CSS rules were matching — the page rendered with default styles only.
- The
- DPad scroll was broken
- The v0.43 DPad scroll fix (which used
we_page_scroll_bywhen the web engine was loaded) was accidentally reverted during the v0.44 bugfix pass, going back to the old text-mode-only scroll. DPad Up/Down did nothing when viewing a web page.
- The v0.43 DPad scroll fix (which used
- CSS selectors were completely failing on complex selectors
- What I fixed
- New
simple_selector_matchesfunction- Handles a single simple selector component, now with:
- Attribute selectors:
[attr],[attr=val],[attr="val"]— parses the bracket, splits on=, strips quotes, checks the node's attribute - Pseudo-elements:
::after,::before,::marker,::placeholder,::selection— always accepted (we don't render them, but the selector matches the base element so its other properties still apply) *with attribute selectors:*[attr=val]works- Uses
css_classlist_has(local, no strtok) instead ofclasslist_has(which lives in web_js.c)
- Attribute selectors:
- Handles a single simple selector component, now with:
- Rewritten
selector_matchesfunction- Now handles compound selectors with combinators:
- Descendant (space):
.tog input— walks up ancestors checking each part right-to-left - Adjacent sibling (
+):input + .tog-sw— checks the immediately preceding sibling - Splits the selector at spaces and
+(respecting[bracket]depth so[type=checkbox]doesn't get split) - Each part is matched via
simple_selector_matches - Guard counter on all ancestor walks prevents infinite loops
- Descendant (space):
- Now handles compound selectors with combinators:
- DPad scroll restored
- DPad Up/Down/Left/Right now scrolls the web page via
we_page_scroll_by(0, ±30)wheng_web_engine.loadedis true. Falls back to text-mode scroll when the engine isn't loaded. Circle Pad is always cursor-only.
- DPad Up/Down/Left/Right now scrolls the web page via
- New
v0.44
- CSS Custom Properties (Variables) —
var()+:root- The biggest addition.
:root { --bg: #000; }stores custom properties in a global variable table.var(--bg)is resolved at parse time before any property handler runs — works in colors, lengths, and any value. Supportsvar(--name, fallback)syntax and recursive resolution (variables referencing other variables). Up to 64 variables stored. Reset on page recompile.
- The biggest addition.
@mediaQueries — Graceful Skip@media(max-width: 680px) { ... }is now parsed and skipped gracefully (with proper brace-nesting tracking). Previously,@mediawould confuse the stylesheet parser and corrupt all subsequent rules. Also skips@keyframes,@import,@font-face, and@charset.
:rootSelector:root { ... }declarations are applied as a universal*rule with the lowest specificity, so:rootproperties cascade to all elements via inheritance. Custom properties (--name: value) are stored in the variable table.
:not()Selector:not(.hidden)now works. The inner selector is recursively matched and negated. Forward-declared to handle the mutual recursion betweenpseudo_matches→pseudo_not_matches→selector_matches.
- Comma-Separated Selectors
h1, h2, h3 { ... }now creates a separate rule for each sub-selector instead of creating one rule with a comma in the selector (which never matched anything).
!importantSupportcolor: red !importantis now accepted — the!importantsuffix is stripped before parsing. All declarations are treated as having the same specificity (later wins), so!importantdoesn't change cascade behavior but no longer causes parse failures.
- New CSS Properties (15 additions)
| Property | Description |
|---|---|
box-sizing
|
border-box / content-box (parsed, stored)
|
flex shorthand
|
flex: 1 → grow=1; flex: 1 0 auto → grow/shrink/basis
|
flex-shrink
|
Default 1 |
flex-basis
|
auto or length
|
pointer-events
|
none / auto
|
user-select
|
none / auto
|
outline
|
Width + color (simplified, no layout impact) |
outline-width / outline-color / outline-style
|
Individual properties |
text-transform
|
uppercase / lowercase / capitalize / none
|
letter-spacing
|
px or normal
|
line-height
|
px, unitless multiplier, or normal
|
list-style / list-style-type
|
none / disc / circle / square / decimal
|
object-fit
|
fill / contain / cover
|
min/max-width/height
|
Now handles none and auto correctly
|
v0.43
- UI: DPad = Scroll, Circle Pad = Cursor (fixed)
- The biggest UX fix: DPad and Circle Pad no longer conflict. Previously, DPad both scrolled the fallback text renderer AND sent arrow key events to web content — pressing Up would scroll the page and fire a
keydown("ArrowUp")at the same time. Circle Pad could also trigger scroll in some paths. - Now:
- DPad scrolls the web page (30px per press, all four directions). When the web engine isn't loaded, it scrolls the fallback text output instead.
- Circle Pad always moves the cursor — it never scrolls, in any mode.
- DPad was removed from the web content key map entirely, so no more phantom arrow key events.
- Only Y (Space) generates a keyboard event for web content.
- Controls help screen updated to reflect the new assignments.
- The biggest UX fix: DPad and Circle Pad no longer conflict. Previously, DPad both scrolled the fallback text renderer AND sent arrow key events to web content — pressing Up would scroll the page and fire a
- Cookie Clicker Compatibility: 3 Major JS APIs Added
element.classList— Cookie Clicker (and most modern JS) usesclassList.add(),.remove(),.toggle(),.contains()extensively. Previously onlyclassNameexisted. NowclassListreturns a live object with all four methods that manipulate the node'sclassattribute and triggercss_compute_nodeso styles re-cascade.- Live
element.styleobject — Cookie Clicker doeselement.style.display = 'none',element.style.left = '100px', etc. Previouslystylewas a dead object — setting properties did nothing. Now the style object is wrapped in a Duktape Proxy that intercepts every property get/set:- Get (
element.style.display): parses the inlinestyleattribute and returns the property value. - Set (
element.style.display = 'none'): updates the inline style string via a C-backedcssTextsetter, triggerscss_compute_node+dom_mark_dirtyso the layout and renderer pick up the change immediately.
- Get (
onclick/onload/onerrorproperty handlers — Cookie Clicker assignselement.onclick = function(){...}instead of usingaddEventListeneror inlineonclick="..."attributes. Previously only inline attribute handlers andaddEventListenerworked. Nowwe_dispatchchecks for JS function properties on the node object (onclick,onmousedown,onload,onsubmit, etc.) and calls them with a proper event object. Supports all 13 event types.
v0.42
- CSS Transitions
transition: 0.3s ease-in-outnow actually animates. When a style property changes on an element with a transition duration, the old and new values are interpolated over the duration using smoothstep easing. Supports 25 numeric properties (opacity, width, height, margins, padding, position, font-size, border, transforms) plus color properties (color, background) which interpolate per-channel. The transition engine runs in the frame loop and marks transitioning nodes dirty so layout re-runs each frame to reflect the changing values.
- Box-Shadow
box-shadow: 2px 2px 4px rgba(0,0,0,0.5)is parsed into offset/blur/color and drawn as a semi-transparent rect behind the element before the background fill. No real gaussian blur (3DS hardware can't do that efficiently), but the spread approximation looks reasonable for most UI shadows.
- Linear-Gradient Backgrounds
background: linear-gradient(to right, red, blue)is parsed into start/end colors and a direction, then rendered as 16 interpolated color bands. Supportsto right/bottom/left/topdirections andNdegangles. Falls back to solidbackgroundcolor when no gradient is active. Transparent colors work correctly (uses explicit "set" flags instead of checking for zero).
- CSS Grid Layout
display: gridwithgrid-template-columnsnow lays out children in a grid. Supports fixed pixel widths,fr/auto(equal distribution), andrepeat(N, ...)syntax up to 8 columns. Rows auto-size to the tallest cell, gap is applied between cells, and children are vertically centered within their row.
- CSS
calc()calc(100% - 20px)is now evaluated at layout time. Supports+,-,*,/with correct operator precedence, and all length units (px, em, rem, vw, vh, %) work inside calc terms. Self-contained in the length parser — no new systems.
- Form Submission
- Clicking a
<button type="submit">or<input type="submit">inside a<form>now fires the form'sonsubmithandler and dispatches asubmitevent. This replaces the old START-key approach which conflicted with START=exit-run-mode.
- Clicking a
- Keybind Conflict Fixes
- Resolved button assignment conflicts between the app shell and web content event system. In run mode, only DPad (arrow keys) and Y (Space) generate keyboard events for web content. A/B/X/L/R/START are exclusively mouse clicks, cursor lock, and exit-run-mode — no more phantom keydown events alongside mouse clicks.
- Bug Fixes
- IndexedDB path overflow:
store/keystrings from JS are now length-validated to prevent path truncation - IndexedDB
get()return logic: was returning true for empty files - IndexedDB
clear()prefix buffer: enlarged from 80 to 128 bytes to handle longer store names - Transition color precision: RGBA8 colors now stored as uint32 (not float) to avoid precision loss past 2^24
- Gradient transparent-color:
0x00000000(transparent) no longer treated as "unset"
- IndexedDB path overflow:
v0.41
<input>/<textarea>text entry- New
we_input_prompt()inweb_events.copens the 3DS software keyboard when a click lands on an<input>/<textarea>(or an ancestor of one). Reads the currentvalueattribute, shows it as initial text, writes the result back, firesinput+changeevents, and marks the node dirty so the renderer shows the new text. - New
we_text_input()helper wrapsswkbdInit/swkbdInputTextfor reuse.
- New
- CSS
transform: translate()wrender_nodenow appliestranslate_x/translate_yto the draw position, and folds the offset into the children'sox/oyso children move with the translated parent. ~6 lines of real logic; the parser already stored the values since v0.39.
- Word-wrap in the layout engine
- Replaced the naive
tw/avail_w + 1character-count wrapping with proper word-boundary wrapping. Walks the text word by word, tracks line width, breaks when the next word would overflow. Hard-breaks words longer than a full line. Respectswhite-space: preandnowrap.
- Replaced the naive
- CSS
:hover/:focus/:nth-childpseudo-classes- New
pseudo_matches()function checks:hover,:focus,:active,:checked,:disabled,:first-child,:last-child,:nth-child(n/odd/even). selector_matches()rewritten to split selectors at:and check the pseudo-class after the base selector matches. Supports chained pseudos (a:focus:hover).- Dynamic re-cascade:
we_events_updatemarks nodes dirty on hover/focus changes;we_updatenow re-runscss_cascade(not justlayout_document) when nodes are dirty, so:hover/:focusstyles apply dynamically.
- New
- Keyboard input to web content
window.prompt(message, default)now opens the 3DS software keyboard and returns the typed string (was a no-op stub returningundefined).- Clicking an
<input>/<textarea>opens the keyboard (Feature 1 above).
- localStorage persistence + IndexedDB completion
- IndexedDB C API hardened:
ws_idb_opennow checks for existing databases with the same name (no duplicates);ws_idb_getvalidatesfreadreturn (was a potential underflow);ws_idb_clearis no longer a no-op — it scans the directory withopendir/readdirand removes all files matching the store prefix; all functions now have proper NULL/bounds checks. - IndexedDB JS bindings:
ws_install_idbnow installs a realindexedDBobject to JS withopen(),put(),get(),delete(),clear(),close()methods.put()JSON-encodes values;get()safely JSON-parses them back (falls back to string if not valid JSON). The "IndexedDB" feature pill is now actually true.
- IndexedDB C API hardened:
v0.38
- Crash / correctness bugs:
- Canvas 2D silently broken — wc_install_bindings() built its property-setup script with snprintf into a 512-byte buffer, but the actual script is 854 bytes. It was truncating mid-statement, so ctx.fillStyle = 'red' and every other canvas property setter did nothing, with zero error shown. Now passed as a literal directly to duk_eval_string — no buffer, no truncation possible.
- Event listeners could misfire — DOMEvent.js_ref was a uint8_t, silently wrapping after the 255th addEventListener() call in a session and firing the wrong callback. Widened to uint16_t.
- Listener ref counter never reset — it was a function-local static that persisted across every recompile instead of resetting with the fresh Duktape context each page load gets. Moved to file scope with an explicit reset tied to context creation.
- Unchecked array access in we_document_open() — switched to the existing bounds-checked accessor so a corrupted sibling chain can't read out-of-bounds memory.
- Stack-safety hardening (3DS has only 256KB of stack):
- ~3KB of stacked buffers in the CSS parser — css_parse_inline's 2048-byte buffer is called while css_parse_stylesheet's 1024-byte buffer is still live. Both made static (verified safe: parsing is never re-entrant).
- Unbounded layout recursion — nothing stopped a deeply-nested DOM tree (up to the 1024-node cap) from recursing that many stack frames deep. Added a depth guard capped well above any realistic page.
- Version v0.37 was skipped due to issues with a beta build.
- CIA files will be added in version 1.0
v0.36
- Crash / correctness fixes:
editor_findandeditor_find_prev:start_linewas not clamped before use — with a corrupted or uninitialized value it could makeeditor_get_linewalk off the end of the buffer. Now clamped to[0, total-1].editor_replace_at: addedcol > llenguard so an exact-end-of-line replacement can't produce a negative memcpy count.we_eval_js: added!g_web_engine.loadedcheck — without it, a stale non-NULLjs_ctxpointer left from a previouswe_unload()call could be dereferenced.draw_console:i < WJS_LOG_MAX_LINESguard added — a corruptedg_wjs_log_ncould have caused a read past the log array bounds.
- Compiler warning fixes:
(down & KEY_X) && ...— explicit parentheses added (GCC -Wall warning eliminated).- Two
strcat()calls replaced withstrncat(). LAYOUT_ED_Hcorrected to178.0f(was180.0f) — the 2px mismatch caused the cursor-to-line mapping to select the wrong line near the bottom of the editor panel.
v0.34
- Slight UI changes
- Bordered rendering for text & iframe
- Memory usage fixes
- More accurate CSS parsing
| File | Fixes/Changes |
|---|---|
web_layout.c
|
Body avail_w bug fixed: was 2×margin_left, now margin_left + margin_right.
|
web_layout.c
|
Text node lay_w uses full available container width for alignment.
|
web_render.c
|
Scale formula size/11.0f → size/30.0f (correct for system font at scale 1.0 ≈ 30px).
|
web_render.c
|
TEXT nodes use parent container width for proper text alignment. |
web_render.c
|
Bottom border drawn on document viewport to frame the output area. |
web_engine.c
|
css_apply_ua_defaults() called after css_reset(), before parse — so user styles always win.
|
web_engine.c
|
RUNNING bar cleaned up; conflicting title bar overlay removed. |
v0.33.5
- Hopefully fixed crashing issues
v0.33.4
- MAYBE fixed crashing issues (untested, will update when tested)
v0.33.3
- Fixed surface-level crashing issues
v0.33.2
- Maybe fixed crashing issues
- Stability fixes
- Fixed errors while compiling
v0.33
- Fixed scrolling
- Fixed line truncation
- Added more complete rendering capabilities
- Added pointer lock integration
- Added page interaction
v0.32
- Fixed the HTML rendering engine
- Slight UI changes
v0.31.1
- Completely overhauled the UI (AI was used to help with this)
- New frame buffer that minimizes the visual bug to almost unnoticeable proportions.
Pre-Release v0.30
- Fixed app crashing on file explorer open (MAYBE FR THIS TIME)
- Fixed the visual bug (kinda)
Credits
PocketCompiler project and idea by PlanetDogeCodes. Some browser runtime features, custom Canvas/WebGL Integration, and 3DS Optimizations were all made with help from GPT-5.5 (xhigh) and GLM 5.2 (max). Some UI elements and DOM-style event handling were made with help from Claude Sonnet 5 (high)
Built with:
- Visual Studio Code
- devkitPro
- devkitARM
- libctru
- Citro2D
- Citro3D
External links
- GitHub - https://github.com/PlanetDogeCodes/Pocket-Compiler
- Universal-DB - https://db.universal-team.net/3ds/pocket-compiler