nx.js Documentation Audit
The docs cover a small, opinionated homebrew runtime with reasonable narrative for the happy path (Getting Started, Concepts, Rendering), but the reference is uneven, several code samples are not safely copy-pasteable, and the docs site visibly lags the runtime's changelog. Nothing is dangerously broken for a human reader; an AI coding agent will trip several times.
1. Switch.Application example uses an invalid title ID literal (critical)
Location: /runtime/concepts/applications
Problem: The first example reads:
const app = new Switch.Application(0x0100000000010000b);
That literal is 17 hex digits, not 16, and is a plain Number rather than a BigInt — so it both exceeds Number.MAX_SAFE_INTEGER and does not match the "16-character hexadecimal string which starts with 01 and ends with 0000" rule the Metadata page enforces for title IDs. The Metadata page's own example ("0123456789ab0000") is 16 chars; this constructor example is 17 and is not suffixed with n.
Consequence: Anyone — human or agent — who copies the snippet either gets silent precision loss or instantiates an Application with a title ID that cannot exist on the system. The Applications page itself warns that "if the specified title is not currently installed on the system, then the accessor methods will throw an error," so the failure mode is delayed and confusing.
The fix: Change the example to a valid 16-hex-digit BigInt literal, e.g. new Switch.Application(0x0100000000010000n), and add a one-line note that title IDs must be passed as BigInt.
2. Concept and reference pages don't surface the new async filesystem APIs (critical)
Location: /runtime/concepts/file-system and /runtime/api/namespaces/Switch/functions/writeFile
Problem: The CHANGELOG for packages/runtime v0.0.70 ships three new async filesystem functions:
Switch.mkdir()— "async … for non-blocking directory creation"Switch.readDir()— "returning anAsyncIterable<Switch.DirEntry>for streaming directory listing"- async
Switch.writeFile()
The Switch namespace overview does enumerate readDir() and mkdir() in a flat list, but the File System concept page — the page a developer is most likely to read — describes only Switch.FsFile and synchronous helpers, and does not flag Switch.mkdir() / Switch.readDir() as the recommended non-blocking primitives. The writeFile reference page still lists only writeFileSync, readFile, and readFileSync under "Related Functions" with no narrative note that the function is now non-blocking or that an async readDir iterator exists. The 0.0.69 Canvas additions (shadowBlur/shadowColor/shadowOffsetX/shadowOffsetY, toDataURL, toBlob, convertToBlob) are likewise undocumented in the rendering pages.
Consequence: Developers reading the concept and reference pages will reach for the blocking *Sync variants (or hand-roll directory recursion) when the runtime already ships better-behaved async equivalents, and won't know Canvas now supports shadows or blob export.
The fix: Add mkdir, readDir, and the now-async behavior of writeFile/readFile to the File System concept page; cross-link them in the "Related Functions" sections of readFile/writeFile; document the new Canvas/OffscreenCanvas surface in the Canvas page; and publish a short "what's new" note per runtime release so the docs reflect the current runtime version (which is nowhere visible on the docs site itself).
3. No llms.txt / llms-full.txt and no machine-readable index for the API reference (significant)
Location: site root (https://nxjs.n8.io/)
Problem: There is no llms.txt or llms-full.txt advertised anywhere in the scraped pages, no sitemap surfaced from the docs, and the API reference is rendered as deep nested per-symbol pages (/runtime/api/namespaces/Switch/classes/..., /runtime/api/functions/...) with no consolidated machine-readable index. The Switch namespace overview lists capabilities in prose paragraphs but does not enumerate every class/function in a structured, parseable form.
Consequence: This is an AI-readiness gap rather than a correctness defect, but it bites a niche runtime like nx.js especially hard. Claude Code / Cursor / Copilot agents trying to answer "what's available on Switch.*?" cannot ingest the API surface in one hop; they will either over-fetch dozens of leaf pages or hallucinate APIs the runtime does not expose — exactly the failure mode this runtime invites, since it ships many Switch.* namespaced functions that don't exist in any other JS runtime.
The fix: Ship llms.txt and llms-full.txt at the docs root, and publish a single flat index of every exported class/function/variable in the Switch namespace and at globalThis (the runtime is already TypeDoc-generated from TS — the JSON output of TypeDoc would be enough).
4. fetch() example demos cleartext HTTP on a network-connected console (significant)
Location: /runtime/api/functions/fetch
Problem: The canonical example is:
fetch('http://jsonip.com')
.then(res => res.json())
.then(data => { console.log(`Current IP address: ${data.ip}`); });
The docs go on to list both http: and https: as supported protocols, but the only worked example uses plain HTTP against a service that supports HTTPS.
Consequence: Developers (and agents pattern-matching off the docs) will copy the cleartext form into their homebrew apps. On a Switch this lands every request, including any auth tokens later bolted on, in plaintext over whatever network the console is on.
The fix: Change the example to https://jsonip.com, and add a one-line note that http: is supported for legacy compatibility but https: should be preferred.
5. fetch() silently aliases file: to sdmc: (significant)
Location: /runtime/api/functions/fetch
Problem: The supported-protocols table includes the row:
file:— Equivalent to sdmc: protocol
There is no warning, no example, no note that this is a deviation from every other JavaScript runtime, where file: means the local filesystem of the host. In nx.js it means "the SD card," with no caveat.
Consequence: A web or Node developer reusing a file:// URL pattern they already trust will silently target the SD card instead of getting an error or a local-file read. Agents that have absorbed file:-handling semantics from MDN / Node docs will produce code that looks correct and routes data to an unexpected destination.
The fix: Either drop the alias, or keep it and add a callout: "Unlike Node.js and the web, file: here resolves to the SD card. Prefer sdmc: for explicit intent."
6. keypress is documented as a first-class keyboard event (significant)
Location: /runtime/concepts/keyboard
Problem: The page lists three event types and says they "function identically to web browser equivalents":
keydownkeyupkeypress— "Triggered when a key produces a character"
keypress has been deprecated in the web platform for years; the MDN equivalent specifically warns against using it. The page does not flag this.
Consequence: Developers will write keyboard handling against keypress based on the doc's "identical to web browser" framing, then be surprised when their muscle memory from modern web work (and any LLM trained on current MDN) tells them not to use it. Worse, if a future runtime release drops keypress to match the web, those apps break.
The fix: Recommend keydown for character-producing keys (via event.key), demote keypress to a compatibility note, and explicitly state whether the nx.js implementation intends to track the web's deprecation.
7. requestAnimationFrame reference contradicts the Event Loop concept page (significant)
Location: /runtime/api/functions/requestAnimationFrame vs /runtime/concepts/event-loop
Problem: The Event Loop concept page tells developers that requestAnimationFrame() lets them "hook into each frame of the event loop in nx.js" and "update application state at a fixed 60 FPS rate." The API reference page for the same function just restates the MDN definition ("Tells the application that you wish to perform an animation … prior to the next repaint") with no Switch-specific behavior — no mention of 60 FPS, no mention of docked vs handheld behavior, no mention of what happens if a frame callback exceeds 16.6ms.
Consequence: A developer (or agent) checking only the reference page won't learn the platform contract; one checking only the concept page won't know whether the 60 FPS claim is a guarantee or an approximation. These two pages need to tell the same story.
The fix: Move the platform-specific guarantees (frame rate, behavior when callbacks overrun, behavior in docked mode) onto the API reference page and link the concept page to it as the source of truth.
8. localStorage save-data contract is split across pages with no clear pointer (significant)
Location: /runtime/concepts/save-data, /runtime/metadata, /runtime/api/namespaces/Switch/classes/SaveData
Problem: Three pages each own a piece of the same contract and none cross-link them:
- Save Data concept page says
localStorageis "unique across user profiles and individual nx.js apps" and that you disable it withnacp.userAccountSaveDataSize: 0. - Metadata page says the title ID (
nacp.id) is what "uniquely identifies applications, particularly for the Save Data API vialocalStorage." SaveDataclass reference documents a totally different surface (mount,commit,extend,freeSpace, etc.) for explicit save-data filesystems.
A developer using localStorage does not learn from the localStorage page that they must set nacp.id in package.json for persistence to make any sense, nor that there is a lower-level Switch.SaveData API for anything localStorage can't express.
Consequence: Apps will ship without a stable nacp.id, lose save data on rebuild, or reach for localStorage when they actually need the SaveData class for cross-user or system saves.
The fix: On the Save Data concept page, lead with "you must set nacp.id" and link to Metadata; add a "when to use localStorage vs Switch.SaveData" table; from the SaveData reference, link back to the concept page.
9. Web browser firmware limitation has no version context or workaround (significant)
Location: /runtime/concepts/web-browser
Problem: The page states: "web browser applets currently do not work on firmware version 19 and later when using Atmosphère. Applications must run in Application mode (installed NSP or launched via hbmenu with title override)."
There is no nx.js version this limitation applies to, no date, no link to an upstream issue, and no concrete instructions for "launched via hbmenu with title override" — that phrase is dropped without explanation.
Consequence: Developers writing browser-applet code in 2026 cannot tell whether this is still true, and "title override" is undefined jargon for anyone who hasn't been deep in the Atmosphère scene. Agents will hallucinate workarounds.
The fix: Add the firmware version the constraint was last verified on, link to the upstream tracking issue, and either explain "title override" or link to the relevant Atmosphère / hbmenu doc.
10. Switch.Album extends Set with no docs for inherited mutators (significant)
Location: /runtime/api/namespaces/Switch/classes/Album
Problem: The reference page states that Album "extends JavaScript's native Set class" and lists "Inherits standard Set operations including add(), delete(), has(), clear(), forEach(), and modern Set operations like union(), intersection(), and difference()." Album entries are Switch.AlbumFile instances backed by the OS-level photo gallery (screenshots and MP4 recordings).
The docs say nothing about what these mutators actually do. Does album.delete(file) remove the screenshot from the gallery on disk? Throw? No-op in the in-memory Set only? Does album.clear() wipe the gallery? Does album.add(file) write a new file? Does union() produce a new Set or a new Album?
Consequence: Extending Set exposes a high-stakes API surface (data loss on a user's photo gallery) where the semantics are entirely guessed by the reader. An agent that knows Set will assume delete is a cheap, idempotent in-memory operation and may call it on the wrong abstraction.
The fix: Either override and document each mutator on Album (what it does, whether it touches disk, whether it can throw), or document explicitly that the inherited Set mutators are not supported and should not be called.
11. Switch.Profile.select() is synchronous with no async alternative noted (minor)
Location: /runtime/concepts/profiles
Problem: The page documents that Switch.Profile.select() "is synchronous, so it will block the event loop." This is honestly stated, but the API is shaped exactly like every other web/Node profile picker (navigator.credentials.get(), OS-modal pickers, etc.) which are always async. No alternative is offered (no async profile-selection event, no recommendation to debounce other work, no example of how to drain queued frames before calling it).
Consequence: Developers writing canvas apps at 60 FPS will call this in a button handler and freeze rendering mid-frame; agents auto-generating profile flows will treat it as await-able and silently drop the await.
The fix: Add a "what happens to your event loop / animation frame while this is open" note and an example of when to call it (e.g., during a deliberate splash/idle state).
12. Console rendering's "80 x 45 characters" claim has no platform caveats (minor)
Location: /runtime/rendering/console
Problem: The page asserts "fixed dimensions of 80 x 45 characters" for console mode and lists "limited Unicode character support" without specifying which ranges work. There is no note on docked vs handheld behavior, font size, or how this maps to the underlying 1280×720 framebuffer.
Consequence: Developers writing TUI-style apps (the page actively recommends kleur and sisteransi) can't predict layout reliably and will discover Unicode gaps by trial and error.
The fix: State which Unicode ranges libnx's console renders, confirm whether the 80×45 grid is identical in docked and handheld, and link to the WIP @nx.js/terminal package's status so readers know whether to invest in console mode or wait.
13. Touch event example dereferences touches[0] without guarding touchend (minor)
Location: /runtime/concepts/touchscreen
Problem: The page tells developers to read coordinates as e.touches[0].clientX / e.touches[0].clientY and lists touchend among the supported events — but on touchend, the touches list is typically empty (the lifted finger lives in changedTouches).
Consequence: Anyone copying the pattern into a touchend handler will get a TypeError on the first lift, which is exactly the moment most touch apps need coordinates.
The fix: Split the example into touchstart/touchmove (use e.touches[0]) and touchend (use e.changedTouches[0]), and note the distinction explicitly.
14. Docs homepage leads with crypto donation addresses before any code (minor)
Location: https://nxjs.n8.io/
Problem: The root docs page has three headings — "nx.js", "JavaScript runtime for Nintendo Switch homebrew applications", and "Donate" — and prominently displays raw BTC and ETH addresses alongside the single link to "Getting Started."
Consequence: First-time visitors (and link previews / social cards) see donation framing before they see what the runtime is or how to install it; agents scraping the landing page get crypto strings interleaved with the only navigation hint.
The fix: Lead the home page with an install/quickstart block (the create-nxjs-app command, a 5-line example, a link to /runtime). Move donation to a /support page or to the bottom of the home page.
What they do well
- Concepts hub is well-organized. /runtime/concepts cleanly partitions Input / Storage / Graphics / System / Runtime, which is rare for a hobby runtime.
- Web-platform fidelity is named explicitly. Pages call out
WinterCG, webCanvas, Web Streams,FontFace,CompressionStream, etc., which gives developers a real mental model rather than a bespoke API. - Networking section is unusually complete for a homebrew runtime — TCP, UDP, broadcast, multicast, StartTLS, DNS resolution all documented with examples.
Top 3 recommendations
- Sync the docs to the runtime version (finding 2). Surface
Switch.mkdir,Switch.readDir, asyncwriteFile, and the 0.0.69 Canvas additions, and put a current-version stamp on the docs site. - Fix the two snippets developers will most actually copy — the
Switch.Applicationtitle ID literal (finding 1) and the cleartextfetchexample (finding 4) — and clarify thefile:→ SD card aliasing (finding 5). - Document
Album's inheritedSetmutators or disable them (finding 10). Right now the docs imply destructive operations on the user's photo gallery are one method call away with no spec.