Short: JS runtime for AmigaOS Author: Juen/R3D+Appendix+Nah-Kolor Uploader: biuro cdlabel info (Pawel Nowak) Type: dev/lang Version: 0.25.0 Requires: 68000+, 1 MB RAM (060/32MB recommended) Architecture: m68k-amigaos A complete JavaScript engine and runtime environment for Classic Amiga, MorphOs and AmigaOs4 Built from scratch in C, targeting MC68000 and AmigaOS 2.0+. Inspired by Node.js, adapted for the Amiga platform. Documentation: http://juen.in/NodeAmiga/ Support author: http://juen.in/support/ Features -------- * JavaScript engine (ES5.1 + ES6+ features) - Arrow functions, classes with inheritance, super(), static - Class fields: public, private (#field), static - Template literals, tagged - Destructuring, spread/rest, default parameters - for-of loops, labeled break/continue - Regex literal /pattern/flags - Optional chaining (?.), nullish coalescing (??) - BigInt (123n, arithmetic, comparison, BigInt()) - let/const block scoping, numeric separators - async/await, Promises, dynamic import() - Generators (function*, yield, yield* delegation) - Async generators (async function*, for await...of) - Generator methods in classes and objects - Automatic Semicolon Insertion (ASI) - Closures, prototype chain * Built-in objects and functions - console (log, error, warn, dir, time/timeEnd, table, count/countReset) - Math (27 functions incl. cbrt/fround/clz32/imul) - JSON (with reviver/replacer) - Date (full API incl. UTC, getUTC*/setUTC*) - RegExp (lookahead, non-capturing groups, quantifiers, lazy, named groups, lookbehind, flags g/i/m/s/u/y) - Buffer (hex/base64) - Promise (then/catch/finally/all/race/allSettled) - Map, Set, WeakMap, WeakSet, Symbol - setTimeout/setInterval/clearTimeout - crypto (MD5, SHA-256), structuredClone - TextEncoder, TextDecoder - Typed Arrays: Uint8Array, Int8Array, Uint16Array, Int16Array, Uint32Array, Int32Array, Float32Array, Float64Array, Uint8ClampedArray, ArrayBuffer, DataView (from/of, forEach/map/filter/find/every/some/reduce, set/fill/slice/subarray/reverse/indexOf/includes/join) - fetch() global HTTP client - performance.now() timer - Error.cause (ES2022) - process.stdout.write, process.stdin.read - process.hrtime, process.nextTick, queueMicrotask * String methods: 29+ (charAt, indexOf, slice, split, replace, replaceAll, trim, includes, startsWith, padStart, normalize, localeCompare, codePointAt, etc.) * Array methods: 37+ (push, pop, map, filter, reduce, reduceRight, sort, find, findLast, findLastIndex, forEach, every, some, splice, flat, flatMap, entries, keys, values, copyWithin, at, Array.from/of/isArray, toReversed, toSorted, toSpliced, with) * Object: keys, values, entries, assign, create, freeze, seal, is, fromEntries, defineProperty/ies, hasOwn, getPrototypeOf, setPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor(s), preventExtensions, isExtensible, groupBy, getOwnPropertySymbols, getters/setters * Number: isInteger, isFinite, isNaN, isSafeInteger, parseInt, parseFloat, toString(radix), toFixed, toExponential, toPrecision, constants (EPSILON, MAX_SAFE_INTEGER, MIN_SAFE_INTEGER, NaN, Infinity) * Global functions: encodeURIComponent, decodeURIComponent, encodeURI, decodeURI, parseInt, parseFloat, isNaN, isFinite, eval(), fetch() * Symbol: Symbol(), Symbol.for/keyFor, well-known symbols, Symbol as property key, Iterator protocol (Symbol.iterator) * Amiga Intuition GUI bindings - Open windows with title, size, position - Graphics: lines, rects, circles, text, pixels - Mouse and keyboard events - System requesters (alert, confirm) - Screen information query * GadTools GUI toolkit (gui module) - createWindow() with declarative gadget layout - 12 widget types: button, string, text, integer, checkbox, cycle, slider, listview, mx, number, scroller, area (bevel box) - gui.gfx: drawing on GadTools windows (setColor, drawLine, fillRect, drawCircle, drawText, setPixel, setFont, etc.) - innerSize() for drawable area, auto GZZ offset - Resizable windows (GIMMEZEROZERO clipping) - Percentage layout (left: "50%", width: "80%") - Negative positioning (top: -20 = anchor bottom) - flex: true - gadget expands to fill available space - minWidth/minHeight - font: { name, size } per-window custom font (e.g. topaz.font 8 for fixed-width column alignment) - Event-driven: waitEvent/pollEvent, gadgetup/gadgetdown/ mousemove/close/key - evt.x, evt.y mouse coords on gadget events - get/set gadget values at runtime - setTitle, listview dynamic item update - Menu bar: setMenu() with keyboard shortcuts - ASL file requester: fileRequest() with patterns - ASL font requester: fontRequest() - Proper GadTools cleanup * Clipboard module (clipboard) - read() - read text from system clipboard - write(text) - write text - clear() - clear clipboard - Uses IFF FTXT/CHRS via iffparse.library * ARexx module (arexx) - send(port, command) - send ARexx command to app - createPort(name) / closePort() - getMsg() / waitMsg() / reply() - listPorts() - list all public message ports - Uses rexxsyslib.library * FFI module (amiga) - Direct Amiga library function calls from JavaScript - openLibrary(name, version) / closeLibrary(lib) - call(lib, offset, regs) - call any library function with register args - peek8/16/32(addr), poke8/16/32(addr, val) - memory access - peekString(addr), pokeString(addr, str) - allocMem(size, flags) / freeMem(ptr, size) - makeTags(array) - build TagItem array for Amiga tag-based APIs - Full 32-bit unsigned support (0xDEADBEEF etc.) * Interactive debugger (--debug) - Step, next, step-out, continue - Breakpoints by line number - Variable inspection (p varname) - Call stack backtrace (bt) - Source listing around current line * Node.js-compatible modules - require() with caching - CommonJS exports (module.exports) - ES modules: import/export (default, named, namespace) - Dynamic import() expression - Native modules: fs, http, net, buffer, os, child_process, crypto, intuition, gui, readline, dns, clipboard, arexx, amiga - JS modules: path, events, url, querystring, util, stream, assert, repl, timers, string_decoder, punycode, console, constants, zlib, iff * File I/O (fs module) - readFileSync, writeFileSync, appendFileSync - existsSync, unlinkSync, renameSync, statSync - readdirSync, mkdirSync, rmdirSync, copyFileSync - readFile, writeFile (async) - createReadStream, createWriteStream - Full AmigaOS path support (Volume:dir/file) * HTTP/HTTPS server and client - http.createServer() non-blocking, 8 concurrent - http.get(), http.post(), http.request() - HTTPS via AmiSSL (build with -DENABLE_AMISSL) - Auto redirect following (301/302/307) - 10-second socket timeout, dns.lookup() - Requires bsdsocket.library (Roadshow, Miami, AmiTCP) * OS integration - process.platform ("amigaos"), process.arch ("m68k") - process.argv, process.env, process.cwd(), process.exit - os.cpus() (detects 68000/020/030/040/060) - os.freemem(), os.version() (Kickstart version) - child_process.execSync() via SystemTagList() * Interactive REPL with command history (arrow up/down) * Error types: Error, TypeError, RangeError, ReferenceError, SyntaxError, EvalError, URIError * Error messages with filename and line number * Compiler: bundle JS + interpreter into standalone executable - NodeAmiga -compile MyApp script.js - Automatic require() and import dependency resolution - All JS modules bundled into single executable - No NodeAmiga, no libs/ needed to run the output Usage ----- NodeAmiga script.js Execute a JavaScript file NodeAmiga -e "code" Evaluate inline JavaScript NodeAmiga -compile out script Compile to standalone exe NodeAmiga Start interactive REPL NodeAmiga -help Show help NodeAmiga -v Show version NodeAmiga --vm script.js Use bytecode VM (experimental) NodeAmiga --tree script.js Use tree-walking interpreter (default) NodeAmiga --debug script.js Run with interactive debugger NodeAmiga --ast script.js Print AST (debug) Installation ------------ 1. Copy NodeAmiga to C: or any directory in your path 2. Copy the libs/ directory to PROGDIR: (same directory as NodeAmiga), or to LIBS:node/ 3. Optional: copy examples/ for demo scripts Quick test: NodeAmiga -e "console.log('Hello Amiga!')" NodeAmiga examples/hello.js NodeAmiga examples/gui_hello.js NodeAmiga examples/fibonacci.js Compile to standalone exe: NodeAmiga -compile Hello examples/hello.js Hello ; Runs without NodeAmiga or libs/ ! System Requirements ------------------- * MC68000 or higher CPU * AmigaOS 2.04 (Kickstart 37) or higher * Minimum 1 MB RAM (2+ MB recommended) * bsdsocket.library for networking features (Roadshow, Miami, AmiTCP) * No FPU required (software floating-point) Technical Details ----------------- * Engine: custom-built lexer, recursive descent parser with Pratt precedence climbing, tree-walking interpreter (default). A bytecode VM is also bundled (--vm) but currently experimental and disabled by default while issues are sorted out. * Memory: reference counting (no GC pauses), arena allocator for AST, integer cache 0-255 * Stack: auto-allocates 64 KB via StackSwap Included Examples ----------------- hello.js Platform info and greeting fibonacci.js Recursive/iterative benchmark file_io.js File read/write/append http_server.js HTTP server with routing http_client.js HTTP GET and POST requests classes.js ES6 classes with inheritance closures.js Closures, IIFE, module pat. array_fun.js map/filter/reduce/sort regex.js RegExp /literal/ and methods events.js EventEmitter custom events promises.js Promise chains, all, race timers.js setTimeout and setInterval buffer.js Binary data manipulation streams.js Readable/Writable/Transform sysinfo.js Full system info display todo_app.js TODO app with persistence mini_grep.js Pattern search in files url_parser.js URL and query string parsing calculator.js Recursive descent expr parser json_db.js JSON file-based database myip.js Fetch public IP from internet nettime.js Internet time via HTTP API weather.js Weather info for a city download.js Download file and save gui_hello.js Simplest Intuition window gui_demo.js Interactive drawing + mouse gui_calculator.js GadTools calculator (320x200) 3d.js Rotating 3D wireframe cube 3d_tunnel.js Wireframe tunnel fly-through pong.js Bouncing ball (XOR, circle) gui_translator.js Google Translate with GUI gui_window.js GadTools widget gallery gui_menu.js Menu bar + ASL requesters gui_drawing.js gui.gfx primitives (4 scenes) gui_gfx_clock.js Analog clock (resizable) gui_keyboard_and_scrolls.js Arrow keys + scrollers demo gui_weather.js Weather app with GadTools GUI aminet_browser.js Aminet archive browser + GUI aminet_browser_opt.js Optimized variant of aminet_browser gui_disasm.js 68k disassembler for Amiga EXE gui_monitor.js System monitor (mem/tasks/libs) gui_ftp_client.js FTP client (PASV) with GUI gui_extra_demo.js Modal dialogs + image + colour clipboard.js System clipboard read/write arexx.js ARexx port communication dns_lookup.js DNS hostname resolution readline_chat.js Interactive readline + prompt ffi.js Amiga library FFI demo iff_info.js IFF/ILBM/8SVX file viewer Known Limitations ----------------- * No Proxy * await is synchronous (spin-waits on event loop) Version History --------------- 0.25.0 (2026-05-07) Spec correctness pass + memory hardening, driven by a new 2300-assertion test suite - test/test_extra.js (2319 assertions over 50 sections) catches the boundary cases test.js skipped: NaN equality, sparse arrays, refcount stress, proto chain, property descriptors, etc. - parseInt("") / parseFloat("") and any input whose first non-space char is not [-+.0-9] now return NaN. Some libcs ate 'a' as a hex digit and gave 0. - parseFloat("abc") rejects up front instead of relying on strtod's endptr. - JSON.stringify(undefined / function / symbol) returns the JS undefined value (not the string "null"). - JSON.stringify(o, null, "\t") accepts string indent (was numbers only). - JSON.parse on malformed input throws SyntaxError (was returning null). - Object.prototype.constructor now points at the Object global; same for Array.prototype.constructor. - Plain {} now has Object.prototype as proto; Object.getPrototypeOf({}) returns Object.prototype, "hasOwnProperty" in {} works. Object.create(null) still gives a null-proto object. - All built-in prototype methods are now non-enumerable; for-in over a user object no longer yields hasOwnProperty, push, map, etc. - Array's `length` property is non-enum. - Date arithmetic via _timestamp lookup; d2 - d1 returns ms delta (was NaN). - Array.from(Set/Map) and [...Set/Map] walk via [@@iterator]; spread on generators ([...g()]) walks via .next(). - Array length: a[10]=x extends length; a.length=N truncates and deletes orphan entries. - [NaN].includes(NaN) true (SameValueZero). - js_new_number(NaN) no longer aliases to g_int_cache[0]. (NaN >= 0 evaluates true on some -Os/m68k combos, which had been silently turning NaN into 0 everywhere ? Math.sign, isNaN of parseInt failures, etc.) - Custom math_ceil / floor / round ? Amiga -noixemul libc's ceil(0.0) returned 1.0; ours uses long-cast truncation for inputs in long range. - Object.defineProperty {writable:false} enforced via PROP_FLAG_NON_WRITABLE on JsProperty (checked in both js_object_set and js_object_set_by_ref). - Arrow function `this` is lexical: is_- arrow flag on JsFunction; call_function skips the env_define("this") step for arrows so lookup walks up to enclosing scope. - for (let i...) creates a per-iteration binding; closures captured in body see this iteration's value, not the post- update one. Update runs in loop_env so iter_env stays "frozen" for closures. - WeakMap.set / WeakSet.add throw real TypeError on primitive keys (was stderr-only). - readline.createInterface({input: fs.- createReadStream(path)}) drains lines synchronously when 'line' attaches. - Template `${"a"}-${"b"}` produces `a-b` (was `"a"-"b"`). - RegExp.flags string in canonical order gimsuy. - RE_MAX_CAPTURES 16 -> 64; METAR-class patterns with 50+ capture groups now compile + match. RE_MAX_STEPS 100k -> 1M so legitimate big regexes don't spuriously bail. - fetch() Response gains text() and json() methods (were missing ? `await res.text()` called undefined). - fs.writeFileSync(path, buffer) writes raw bytes (was stringifying Buffer to "[object Object]"). - Stack bumped 64 KB -> 256 KB so sum(100)-class recursion fits. - module_cache_cleanup and interp_destroy no longer cascade-release on exit; the pool sweep in value_cleanup handles everything without traversing references. Plugs an ~19 MB-per-run leak that survived process termination. - examples/readline_file.js demonstrates the readline+stream pattern. 0.24.0 (2026-05-05) Readline file streaming + 7 spec bugs caught by 1000-test extra suite - readline.createInterface({input: stream}) now actually fires line/close events (was a stub that ignored input). - Template `${"a"}-${"b"}` produced `"a"-"b"`. eval_template_literal now uses parts-list parity rather than node type to tell template text apart from inline string-literal expressions. - RegExp.flags string was missing; added canonical-order gimsuy string at both construction sites (literal + ctor). - `"hasOwnProperty" in {}` returned false because eval_object never wired up the Object.prototype as new-object proto. - Array sparse assign (a[10]=x) did not bump a.length, and a.length=N did not truncate. Both fixed in eval_assign. - [NaN].includes(NaN) returned false; added SameValueZero NaN special case. - (3.5).toFixed(0) returned "3"; rewrote to scale-and-round so half-up applies at any digits, including digits=0. - New test/test_extra.js with 1010 assertions covering refcount stress, proto chain, NaN equality, sparse arrays, all option combinations. - examples/readline_file.js demonstrates the new readline+stream API. - Makefile uses wildcard for test/*.js. 0.23.0 (2026-05-04) Refcount leak fix, runtime cycle/mx update, diagnostics - FIX: every `var x = LITERAL;` and every property in every `{a: 1, b: 2}` was leaking one refcount. The JsValue could never reach 0 and stayed pinned in the pool for the rest of the process. A budget app with hundreds of object-literal accounts would gradually exhaust the JsProperty pool, causing js_alloc_property to return NULL and env_define to silently fail to add the binding -- the script seemed to "lose" variables (even consts). eval_var_declaration and eval_object_expression now release the extra ref ONLY when the init expression type is one that is guaranteed to produce a fresh JsValue (NUMBER/STRING/BIGINT/OBJECT/ARRAY/BINARY/ UNARY/UPDATE/FUNCTION/NEW/REGEX/TEMPLATE literals). Identifier, member, call, tagged-template, assign, logical, conditional and sequence expressions stay conservative -- they may return a borrowed ref which we must not free. - NEW: gui.set(win, id, items) on cycle and mx (radio) gadgets accepts an array, replacing the items list at runtime. Previously cycle/mx only accepted a number to change the active index, which forced callers to destroy and rebuild the whole window whenever the menu of choices changed. - NEW: process.poolStats() returns the live counts in each internal pool (jsval, jsobj, jsfn, jsprop, env). Useful for spotting refcount leaks before they exhaust the heap. - FIX: env_define, env_set and js_object_set print to stderr when js_alloc_property or PROP_SET_KEY's malloc fails. Previously the property simply didn't get added and the script saw "var undefined" with no clue why. - DOC: docs/limitations.html refreshed -- the entries claiming Array.prototype.sort is bubble sort and that fromCharCode writes only the low byte both pre-dated the 0.22.0 audit and were stale. 0.22.0 (2026-04-30) Raw TCP, FTP client example - NEW: net.connect(host, port [, timeoutMs]) ? synchronous raw-TCP client. Returns a Socket object with .write(data) (String or Buffer), .read(maxLen) (Buffer), .readLine() (String, strips trailing CRLF, null on EOF), .close(), .isOpen() and .setTimeout(ms). Pool of 16 sockets; net_cleanup() closes any survivors on exit so fds don't leak across runs. - NEW: examples/gui_ftp_client.js ? minimal FTP client (passive mode) using the new net.connect. Supports directory listing, change-dir, upload, download, delete, mkdir. Blocks the GUI during transfers and shows progress in the status bar. - FIX: gui listview gadgets now keep the clicked row visually highlighted (GTLV_ShowSelected was missing, so the selection vanished on mouse-up). - FIX: socket.write now loops until every byte has left the socket (or fails). Bsdsocket's send() is allowed to return a short write -- the previous one-shot send would silently truncate uploads of files larger than a few kB. - FIX: socket.readLine drains the rest of an over-long line up to the next LF before returning, so the next readLine starts on a fresh record instead of picking up the truncated tail as a new line. - FIX: gui_ftp_client wraps every transfer in try/finally so a recv timeout / connection drop releases the data socket. After enough errors the pool of 16 sockets used to fill up and even reconnect would fail. - NEW: gui_ftp_client also parses Windows / IIS LIST output ("MM-DD-YY HH:MMxM |size name") in addition to Unix-style ls -l listings. - FIX: amiga.pokeString accepts an optional maxLen argument and refuses to write past it. The old one-shot strcpy could clobber arbitrary memory if the caller miscounted the destination buffer. - FIX: lexer now reports "unterminated block comment" and "malformed exponent" (e.g. "1e" or "2.5e+") as proper syntax errors instead of silently swallowing them. - FIX: HTTP server reason phrase matches the status code -- res.writeHead(404) now sends "404 Not Found" rather than the previous hard-coded "404 OK". - FIX: child_process.execSync now captures stderr in addition to stdout (via SYS_Error) and returns an Error{code,stdout,stderr} value (instead of a string) on non-zero return code -- callers can branch on typeof out !== 'string'. exec(cmd, cb) passes a real Error to cb on failure. Temp files are per-Task so two NodeAmiga processes can run execSync concurrently without colliding. - FIX: fs.readFileSync rejects files over 16 MB so a bogus huge size from ftell can't take the process down with an unchecked malloc; returns undefined on that or on OOM, same as for "file not found". - PERF: Array.prototype.sort uses a stable merge sort instead of bubble sort -- O(n log n) where it used to be O(n^2). Several existing examples (Aminet browser, system monitor) sort hundreds of items. - SEC: HTTP redirects now reject any Location: target whose scheme isn't http://, https:// or a root path. A malicious server could previously hand back "Location: javascript:..." or "file:///..." which do_http_request would happily try to parse. - FIX: Buffer.from(str, 'hex') now stops at the first invalid character pair and returns the bytes decoded so far (matching Node's behaviour). Previously it silently substituted zero, corrupting the output. - FIX: String.fromCharCode no longer writes a NUL byte for codes >= 256. NodeAmiga strings are byte-based, so the function is hybrid: codes 0..255 stay a single raw byte (preserves charCodeAt round-trip and Latin-1 Topaz display in libs like iff.js), codes >= 256 are encoded as multi-byte UTF-8. So String.fromCharCode(0x100) is now "?" (2 UTF-8 bytes) instead of "\\0". - FIX: String.fromCodePoint encodes the full Unicode range (0..0x10FFFF) as proper UTF-8, including 4-byte sequences for SMP code points. Out-of-range values coerce to U+FFFD. - FIX: JSON.parse now recognises UTF-16 surrogate pairs ("\\uD83D\\uDE00") and emits a single 4-byte UTF-8 code point. Lone surrogates are still passed through as 3-byte UTF-8 for tolerance. - NEW: JSON.stringify accepts an array as the second argument (ES5 allowlist of property names). Numbers in the array are coerced to string keys; only matching object properties are emitted. - FIX: -compile bundler also collects modules pulled in via dynamic import("name") expressions, not just static require() and ESM `from`. - FIX: HTTP client now decodes Transfer-Encoding: chunked responses. Many HTTP/1.1 servers reply chunked even when our request says HTTP/1.0; the previous body parser handed back the raw "size\r\n data\r\nsize\r\ndata\r\n0\r\n\r\n" sequence which JSON.parse, etc. obviously couldn't read. Decode is done in place (dechunked body is always <= chunked). - FIX: fs.readFile (async variant) gained the same ftell-< 0 / 16 MB cap guards as readFileSync; an oversize or unreadable file now surfaces through the callback with an Error rather than firing an unchecked malloc. - NEW: gui.alert(msg [,title]) and gui.confirm(msg [,title]) modal requesters via intuition's EasyRequest. confirm returns a boolean. - NEW: gui.prompt(msg [,default [,title]]) ? a small modal window with a string gadget; returns the entered text, or null on Cancel / close gadget / ESC. Enter on the field accepts. ESC cancels. - NEW: gui.beep() ? DisplayBeep on the default screen. - NEW: gui.colorRequest([initialPen]) ? pop a swatch grid sized to the current screen depth, click to pick a pen, ESC / close cancels (returns -1). Enter accepts the highlighted initial pen. - NEW: gui.setMenuItem(win, id, {disabled: bool}) ? enable or disable a menu item at runtime via OnMenu / OffMenu. Items are looked up by the `id` passed into setMenu(). - NEW: gui.loadImage(path) / gui.freeImage(img) and gfx.drawImage(win, img, x, y [,w,h]) ? load any image format datatypes.library can decode (IFF, JPEG, PNG, GIF, ...) and blit to a window. Demoed in examples/gui_extra_demo.js. 0.21.0 (2026-04-23) Bug fixes: lexer, await, iff, web runtime - FIX: Unterminated regex literal no longer silently returns a slash token; lexer now reports a proper syntax error (matches behavior of unterminated strings) - FIX: await on a Promise that never resolves within ~100s now throws Error instead of silently returning undefined (made debugging of hanging async code impossible) - FIX: libs/iff.js parse() and parseChunks() had duplicated chunk-walking logic; refactored so parse() delegates to parseChunks(), eliminating the fix-twice trap - FIX: addons/web drag handler installed three document-level listeners per opened window that were never removed on close (accumulating leak); enableDrag now returns a cleanup fn invoked on closeWindow - UPDATE: addons/web version bumped to 0.21.0-web (was lagging at 0.12.0) - NEW: online user documentation at http://juen.in/NodeAmiga/ covering the global API, all require() modules, the 44 bundled examples, and known limitations; link shown in -help / -v / REPL banner - FIX: compiled executables (-compile output) were left reported as "object in use" after running. The old compiler appended the JS bundle as raw bytes after a HUNK_BREAK marker, which caused LoadSeg to keep a Lock on the file alive past the process exit. Bundle is now embedded as a real HUNK_DATA hunk inside the executable: LoadSeg loads it like any other segment, UnLoadSeg releases it on exit, the file is no longer locked. Single-file output unchanged. - CHANGE: tree-walking interpreter is now the default. The bytecode VM still has open issues, so it is opt-in via --vm. --tree is kept as a no-op alias for the default; --debug always forces tree mode (debugger requires AST). When both --vm and --tree are passed, --vm wins. - FIX: gui.createWindow gadgets using percent or negative-anchor sizes (e.g. width: '50%', width: -8) could clip the window border or leave a gap on first open. The initial layout used the requested WA_InnerWidth/Height while subsequent resizes used the actual GZZWidth/Height -- if Intuition adjusted the inner area on open, the two layouts disagreed. Now if the actual inner area differs from what was requested, a single rebuild runs before the window goes interactive, so the first frame matches every later resize. - FIX: rebuild_gadgets restored an IDCMP mask that dropped IDCMP_MENUPICK and unconditionally added IDCMP_NEWSIZE -- so non-resizable windows started receiving spurious NEWSIZE handling, and resizing any window silently broke menu events. The mask now mirrors OpenWindowTagList exactly. 0.20.0 (2026-04-17) Equality, Promise.finally, custom fonts - FIX: Strict equality 0 === "0" wrongly returned true with tagged ints (now correctly false) - FIX: Loose equality 0 == null wrongly returned true with tagged ints (now correctly false per spec) - FIX: typeof on tagged ints could crash (defensive check added in js_typeof) - FIX: Promise.finally() was implemented as .then(cb,cb) which dropped the original value -- now properly passes through resolved value or rejection reason - FIX: Promise.finally() use-after-free when callback threw (released thrown value before reject_promise could retain it) - NEW: String.prototype.toString() and valueOf() (were missing from prototype registration) - NEW: Number.prototype.valueOf() - NEW: gui.createWindow accepts font: { name, size } option for per-window fixed font (e.g. topaz.font 8 for proper column alignment in listviews) - aminet_browser.js v1.4: Recent button (browses /recent), Find renamed from Search All, version check on startup, custom topaz 8 font for column alignment 0.19.0 (2026-04-16) FFI, area gadget, debugger - NEW: FFI module (require('amiga')) - call any Amiga library function from JavaScript - NEW: openLibrary/closeLibrary, call with register args - NEW: peek8/16/32, poke8/16/32, peekString, pokeString - NEW: allocMem/freeMem, makeTags for tag-based APIs - NEW: Area gadget (kind:'area') - clickable bevel box with gadgetdown/gadgetup/mousemove events - NEW: evt.x, evt.y mouse coordinates on gadget events - NEW: Debugger 'p' prints objects/arrays recursively - FIX: Scroller/slider gadgetup not firing (added GA_RelVerify + GA_Immediate tags) - FIX: Regex escape sequences (\n, \r, \t, \f, \v, \0) - FIX: aminet_browser.js eating 'r' chars from readme - FIX: VM hex/octal/binary clamped at 0x7FFFFFFF - FIX: VM OP_GET_PROP for Number.prototype methods - FIX: toString(radix) for values >= 2^31 on soft-float - FIX: double-to-ULONG in FFI for values > 0x7FFFFFFF 0.18.1 (2026-04-14) - FIX: Scroller drag ID - switching between scrollers no longer emits stale IDs - FIX: Non-GZZ window gadget coords aligned with gfx.innerSize() - FIX: MOUSEMOVE identifies gadget from pointer, not from stale active_gad_id 0.18.0 (2026-04-13) - NEW: Arrow keys, F1-F10, Help key events via IDCMP_RAWKEY (evt.key = "ArrowUp"/"ArrowDown"/"ArrowLeft"/"ArrowRight"/ "F1".."F10"/"Help") -- both gui and intuition - NEW: Vertical scroller orientation (vertical:true) - NEW: gui.set() scroller accepts object {top, total, visible} - FIX: Duplicate key events prevented (RAWKEY filtered for VANILLAKEY codes) - FIX: Modifier keys filtered from RAWKEY - Example: gui_keyboard_and_scrolls.js 0.17.0 (2026-04-12) - NEW: gui.gfx sub-module -- drawing primitives on GadTools windows: setColor, setBColor, setDrawMode, moveTo, lineTo, drawLine, drawRect, fillRect, drawCircle, fillCircle, drawEllipse, drawText, setPixel, getPixel, clear, innerSize, setFont, waitTOF - FIX: Intuition openWindow uses WA_InnerWidth/Height so dimensions match drawable area (was outer) - FIX: Intuition gfx.clear uses EraseRect (GZZ-safe) instead of SetRast - FIX: Intuition resize events update win.width/height - FIX: Intuition keypress includes 'key' property - Examples: gui_drawing.js, gui_gfx_clock.js Credits ------- Juen/R3D+Appendix+Nah-Kolor