Version history
The MacSurf timeline — milestone by milestone, from the first HTTP fetch over Open Transport to CSS Grid on a beige G3.
The First ES5 JavaScript Engine on Classic Macintosh
On April 15, 2026, a Power Macintosh running Mac OS 9 executed a JavaScript program and displayed the result in a web browser window. The title bar read "MacSurf - JS OK (1+1=2)." It was the first time a modern ECMAScript engine had ever run on Classic Mac OS hardware.
By the end of that same night, MacSurf was rendering ASCII Mandelbrot fractals computed entirely in JavaScript, sorting 5,000-element arrays via quicksort, evaluating the Ackermann function to depth 1,021, and parsing JSON — all inside a Carbon application on a machine from 1998.
No consumer browser has ever done this before. Classic IE 5.x shipped JScript 5, but it predates ES5 and ran on Mac OS X, not Classic. Classilla attempted Mozilla's SpiderMonkey on OS 9 but JavaScript was its primary source of crashes and instability. MacSurf is the first browser to ship a stable, fully functional ES5 engine on this platform.
The Machine
Power Macintosh G3 Minitower (Beige), Machine ID 510
- Processor: PowerPC G4 at 400 MHz (Sonnet upgrade card — the beige G3 never shipped with a G4)
- RAM: 192 MB (32 MB + 32 MB + 128 MB DIMMs in slots J3/J4/J5)
- Video Memory: 6 MB
- External L2 Cache: Not installed
- Startup Drive: "Back40" (Internal ATA)
- OS: Mac OS 9.1 (US)
- CarbonLib: 1.6
- QuickTime: 6.0.3
- Virtual Memory: 193 MB (on Back40)
- Network: 10.42.0.204 via Ethernet built-in
- Compiler: Metrowerks CodeWarrior 8 Pro (8.3 update)
This is Gary Hansen's machine with an aftermarket Sonnet G4 processor upgrade installed — a pretty sweet find. The Sonnet Processor Upgrade 1.4.5 extension is active in the System Folder. Every line of code was compiled on it. Every screenshot was captured from it. Every benchmark was measured on it. There is no emulator in this story. System profile generated live from the machine on April 15, 2026 at 8:47 PM.
The Technology Stack
MacSurf exists because of several pieces of technology, most of them older than the machine itself:
NetSurf (netsurf-browser.org)
The browser engine. An open-source web browser written in C with no external dependencies, designed for low-resource systems. NetSurf's clean architecture — with explicit support for cooperative multitasking and non-POSIX platforms via its frontend abstraction — made the port to Mac OS 9 possible. The RISC OS and AmigaOS frontends served as direct references. MacSurf links against all five NetSurf core libraries: libparserutils, libhubbub, libdom, libcss, and libwapcaplet — approximately 125,000 lines of C across 443 source files.
macQJS — QuickJS (bellard.org/quickjs)
The JavaScript engine, and MacSurf's own port of QuickJS to Classic Mac OS. It runs ES2023 natively on the Mac, linked into the base build, evaluating page scripts directly with no transpilation step and nothing offloaded to a server. Modern JavaScript — let/const, arrow functions, classes, template literals, destructuring, generators, async/await, Promises, modern built-ins — executes as written. macQJS is what lets a full contemporary WordPress stack (jQuery 3.7.1 and a real plugin bundle) run to completion on a 1998 PowerPC.
Previously: MacSurf shipped Duktape 2.7.0, Sami Vaarala's embeddable ES5.1 interpreter in portable C89, from the April 2026 JavaScript milestone through the 1.5 line. Duktape got the project its first JavaScript on Mac OS 9 and deserves the credit for it — but ES5.1 meant every modern bundle had to go through a custom ES6→ES5 transpiler that kept corrupting the scripts it rewrote. The 1.68 "macQJS" release replaced Duktape with QuickJS, removed it from the tree entirely, and retired the transpiler along with it.
Metrowerks CodeWarrior 8 Pro
The compiler. The last professional C/C++ IDE for Classic Mac OS, running natively on the same machine it targets. CW8 compiles in strict C89 — no inline, no // comments, no designated initializers, no declarations inside for — and it has still swallowed a combined codebase well past 130,000 lines, including a full QuickJS engine and 443 files of ported NetSurf libraries. The 8.3 update's improved optimizer generates reasonable PowerPC code despite the C89 constraints.
Carbon API (Apple)
The application framework. Carbon provides the window manager, event loop, controls, QuickDraw drawing, and Open Transport networking that MacSurf uses. Designed as a bridge between Classic Mac OS and OS X, Carbon gave MacSurf access to modern (for 2001) UI primitives while remaining compatible with the cooperative multitasking model of OS 9.
Open Transport (Apple)
The networking layer. MacSurf issues *InContext Open Transport calls for TCP/IP: the Carbon application sets up an OT client context at startup and every fetcher opens its endpoints through it. Synchronous calls use OTUseSyncIdleEvents with a notifier that yields to the Thread Manager, which is how the UI stays responsive during network operations under cooperative multitasking. cy384's SSHeven and the Retro68 OT TCP demo remain the reference implementations for the underlying patterns.
macTLS (custom)
The TLS stack, written for this project and linked directly into the browser. Hand-written TLS 1.3 (RFC 8446 — X25519 and multi-curve ECDHE, ChaCha20-Poly1305 and AES-128-GCM) with TLS 1.2 fallback, the full Mozilla CA bundle of 121 anchors baked in, and its own entropy source behind it. As far as we know it is the first TLS 1.3 implementation on Classic Mac OS (v1.3, May 29 2026). This is what lets the Mac open its own HTTPS connections to origin servers.
Previously: a single Go binary on a VPS terminated TLS on the Mac's behalf — the Mac sent plain HTTP, the proxy fetched the HTTPS URL and returned plain HTTP. macTLS replaced it around May 25 2026 and the proxy has been retired since; it is no longer on the request path. Making the Mac the TLS client is also what made real logins possible, since cookies and credentials now live on the client where sites expect them.
Claude (Anthropic)
Most of MacSurf — the frontend, library ports, build system, JavaScript engine, and TLS stack — was built with Claude, an AI assistant from Anthropic. The workflow: Claude wrote C89-compatible source code and diagnosed compilation errors from logs sent back through a reverse SSH tunnel from the Mac. Runtime crashes were debugged from photographs of the physical screen. A typical cycle: write code on Linux, SCP a tar to the Mac, compile in CodeWarrior, photograph or transcribe errors, send them back, and iterate.
The Timeline
v0.1 — The Browser Shell (March-April 2026)
- Carbon window with toolbar, URL bar, scrollbar
- Open Transport HTTP fetcher via proxy
- HTML tag stripping + word-wrap text renderer
- Cooperative event loop with WaitNextEvent
- Working navigation: back, forward, reload, home
- FrogFind.com loading successfully on real hardware
v0.2 — The NetSurf Core (April 15, 2026)
- All five NetSurf libraries linked (443 .c files, ~125K LOC)
- nsoption, nscolour, system colour subsystems initialized
- Full NetSurf js_thread API wired
- Browser boots through netsurf_init without crashing
- Real content fetching through the v0.1 OT path
v0.2-moonshot — JavaScript (April 15-16, 2026)
- Duktape 2.7.0 integrated in a single session
- Hand-crafted duk_config.h for PPC big-endian + CW8 C89
- Zero patches to duktape.c itself
1+1=2smoke test passed on first run- First real JavaScript-bearing web page loaded from mac.mp.ls
- 12 stress tests executed: closures, prototypes, quicksort, regex, promises, Ackermann, hash tables, matrix multiply, BST, and ASCII Mandelbrot
- All tests pass on real hardware
v0.3 — Real HTML rendering (April 18-19, 2026)
- libdom hubbub binding ported to C89 / CW8
- Full DOM tree construction from real HTML5
- libcss cascade wired into computed-style lookups
var()custom-property resolution working- MacTrove.com homepage rendering on real hardware for the first time
v0.4 — CSS3 visible on hardware (May 13, 2026)
- Defensive-clamp threshold first hit and worked around (precursor to fixes156)
- QuickDraw plotters honour cascade-computed colour, fonts, borders, padding, margins, backgrounds
- Box model fully wired
- Lists (disc / circle / square / decimal) with bullet-glyph rendering correct
- Flexbox
justify-content,order, basic line-box flow - Block + inline + table layout all real
v0.4.x — CSS feature catch-up (May 14-19, 2026)
- fixes47 — real linear gradients via QuickDraw row-by-row painting
- fixes48-49 —
box-shadowwith custom colours + asymmetric offsets, opacity via QD stipple - fixes50 —
text-shadowvia vendor-macsurf-text-shadow - fixes73a-e —
transform: rotate/scale/translatevia vendor packed property - fixes74d — radial gradients
- fixes75e — CSS Grid V1 (cols+rows, equal tracks, span)
- fixes77g — offscreen GWorld back buffer (no more flicker)
- fixes78 — image rendering (PNG/GIF/JPEG/BMP/TIFF via lodepng + libnsbmp + libnsgif + libjpeg + libtiff)
- fixes79b — PNG transparency via lodepng +
CopyMask - fixes115 —
grid-template-columnsstandard grammar (preprocessor route) - fixes119 — replaced-element
aspect-ratiopreservation in flex stretch - fixes128 — image scaling polish
- fixes134a — CSS counters
- fixes143a — disc-bullet glyph repair (U+00B7 over U+2022 in Helvetica TT)
- fixes146 —
text-overflow: ellipsis - fixes147 — CSS 2.1 stacking-context paint order (sibling-level z-index)
- fixes148 — Grid V2 standard track grammar (
1fr,repeat(),minmax(), idents) - fixes149 — viewport units, min-height verified working on hardware
v0.5 — Grid V2 + font dispatch (May 20, 2026)
- fixes150 —
grid-template-rows(px row tracks; FR/percent degrade to tallest-child) - fixes151 —
grid-columnplacement V1 (span N,A / B,1 / -1fill sentinel) - fixes152 — CSS
aspect-ratioV1 (vendor packed property) - fixes153 —
GetFontInfovmetric probe + audit ofgui_layout_tablefor per-font ascent/descent - fixes155 —
hrbc:diagnostic probe athtml_redraw_box_childrenentry; caught the smoking gun for fixes156 - fixes156 — major stability fix: raised defensive-clamp y/height threshold in
redraw.c html_redraw_boxfrom ±10000 to ±200000. Closed the "empty render" saga that had appeared after page-height growth past 10000 px from accumulated probe cards. Root cause was always a latent threshold, not the fixes154 alias retry it was initially blamed on. - fixes157 — font-family aliases dispatch (sans → Helvetica, serif → Times, monospace → Monaco). Closes the fixes52/fixes145 five-year "force Helvetica" workaround. The fixes145 horizontal-scrambling hypothesis was disproven on real hardware once fixes156 was in place.
- fixes158 — explicit CSS Grid placement (V1). Packed
grid-column+grid-row+-start/-endlonghands into the existing-macsurf-grid-col-spanint32 (four 8-bit fields). Three-pass walker inlayout_grid.c. - fixes158a — auto-flow occupancy bitmap so explicit grid items don't get overdrawn by auto-flow siblings. Per-row uint8 bitmap, pre-marks explicit cells, auto cursor skips occupied cells row-major.
- fixes159 — Grid V2 alignment (
justify-items,align-items,justify-self,align-selfwithstart | end | center | stretch). Crashed on G3 due to mid-struct field insertion. - fixes159a — relocate
macsurf_justifyto end ofcss_computed_style_istruct. CW8 misses-header-recompile meant libcss .o files had pre-fixes159 field offsets while netsurf .c files had new offsets — silent crash mid-reformat.
macTLS — HTTPS moves onto the Mac (May 2026)
- The proxy is retired (May 25) — after a year of terminating TLS on a VPS, the Go proxy comes off the request path for good.
- macTLS v1.3 (May 29) — hand-written TLS 1.3 (RFC 8446; X25519 + multi-curve ECDHE, ChaCha20-Poly1305 / AES-128-GCM) with TLS 1.2 fallback and the 121-anchor Mozilla CA bundle, linked into the browser. Believed to be the first TLS 1.3 on Classic Mac OS. The Mac now opens its own HTTPS connections to origin servers — which is also what makes real site logins possible, since cookies live on the client.
v1.5 "Modernity" (June 11, 2026)
- JS → DOM → render re-conversion, so JavaScript-mutated content actually repaints
- On-device ES6→ES5 transpilation — a stopgap for Duktape's ES5.1 ceiling, retired a month later when QuickJS landed
- Stability pass (fixes404-415): monotonic clock fix, SHA-384 self-tests, use-after-free guards, and a CSS Grid 8→16 column limit that fixed modern 12-column grids collapsing on XenForo forums
v1.68 "macQJS" — the JavaScript engine is replaced (July 7, 2026)
- Duktape out, QuickJS in. macQJS — MacSurf's own QuickJS port — replaces Duktape entirely and brings ES2023 to Mac OS 9. Duktape is removed from the tree, and the ES6→ES5 transpiler that had been papering over its limits is retired with it: modern JavaScript now runs as written.
- Sticky-overlay hit-testing (cookie-consent bars become clickable), login persistence across a post-login redirect, and full text input — blinking caret, click-drag selection, Cut/Copy/Paste via the Scrap Manager, Tab between form fields
- Near-instant startup: a ~17-second synchronous JS benchmark battery that ran before the event loop on every launch is removed. Startup drops under 1 second.
- Viewport lazy image loading — roughly 26% faster wall-clock on a heavy forum page
- All G3-verified. 1.68.1 / 1.68.2 follow on July 8.
v2.0 (July 11, 2026)
- The blank-screen fix (#207), the release's headline, with two independent real causes: hardcoded pointer-ceiling guards that rejected valid pointers when the app partition mapped high on higher-RAM Macs (breaking string interning, so no fetcher ever matched), and a persisted
deadhosts.txtthat condemned the home page across launches. - Cross-signed HTTPS chain reordering (#206) — macintoshgarden.org loads over https
- URL autocomplete with a suggestion dropdown, the text-selection cluster, cache-purge-on-login, Get Info version resource, and a typing-latency fix
- The entire 2.0 batch was hardware-verified on G3/G4 under OS 9.2.2 on release day.
v2.0.5 — the polish batch (July 17, 2026)
- A large modern-CSS pass: native
background-clip(#255), gridjustify-items(#279),image-rendering(#256), logical properties (#247), box-alignment shorthands, the typography cluster (tab-size,text-align-last,hyphens,caret-color, text-decoration longhands), and grid auto-track content sizing (#62) - Default font size corrected 13px → 16px (#244) — text renders at the right size everywhere
- Inline
style=""attributes now run through the modern-CSS rewriters too (#277) - Crash fixes: a bitmap use-after-free on refresh (#281) and the temp-memory GWorld reclamation cluster; typing latency re-fixed as a dirty-rect blit instead of a full-viewport copy (#239/#212)
- A TLS certificate-validation bug that compared local time against GMT, failing fresh certificates on Macs in timezones behind GMT (#282)
- Hacker News works, including login (#133)
- Dev Pack (DevPackKit2.sit) published July 15 — full source tree plus the
MacSurf.mcpproject file. CarbonLib and CodeWarrior Pro 8 still have to be supplied separately. See Build. - This is the current shipping release.
Since 2.0.5 — the 3.0 development line
- Mac OS X 10.0–10.4 from the same Carbon binary (maintainer-verified July 21) — a full HTTPS render of 68kmla.org on 10.3, and no regression on OS 9. No Mach-O target, no second build, no Classic environment. The root cause of the whole effort turned out to be a single compile-time flag:
TARGET_API_MAC_CARBONwas being set too late to matter, so MacSurf had been compiling as a non-Carbon application against Carbon headers for its entire history — survivable on OS 9, fatal on OS X. - A full modern WordPress stack runs with zero JavaScript exceptions (measured July 25): hackaday.com's front page, an article page, and a cross-origin comment iframe, with jQuery 3.7.1, slick, dotdotdot and Typekit all executing. The lesson of that batch was that pages break on confident wrong answers, not missing APIs — libraries feature-detect around gaps, but nothing defends against a fabricated value.
- An adversarial CSS testing pass turned up a grid track-sizing bug under constrained tracks (#279, open), flex/table/grid intrinsic-sizing gaps against Chrome, and line-breaker edge cases; a phantom hyphen at styled-inline and
<br>boundaries (#275) was fixed and closed. See CSS status.
The Benchmarks
Measured on the Power Macintosh G3 Minitower with Sonnet G4 400 MHz upgrade, Mac OS 9.1, 192 MB RAM.
These are the original April 2026 numbers, taken on Duktape 2.7.0 (DUK_USE_NATIVE_CALL_RECLIMIT 128) — the engine of the day. They are kept here as the historical record of that first night; the browser has since moved to macQJS and has not been re-benchmarked against this suite.
| Test | Result | Time |
|---|---|---|
| Linked list: 10,000 nodes + reversal | Correct | <1 ms |
| Regex: email extraction from prose | 4/4 found | <1 ms |
| Promise polyfill: 3-step chain | Correct (6) | <1 ms |
| BST: 15-node insert + in-order traversal | Correct | <1 ms |
| Matrix multiply: 30x30 | Correct | <1 ms |
| Event emitter: 1,000 events x 2 listeners | 2,001 fired | <1 ms |
| Quicksort: 5,000 random integers | Verified sorted | 1,000 ms |
| Hash table: 10,000 insert + lookup | 10,000/10,000 | 1,000 ms |
| String builder: 100,000 chars (array+join) | 100,000 length | 2,000 ms |
| 100K Math.sqrt + Math.sin loop | Completed | 3,000 ms |
| Ackermann(3,7) | 1,021 (correct) | 5,000-6,000 ms |
| Mandelbrot: 40x20 ASCII, 80 max iterations | Renders correctly | ~2,000 ms |
The Screenshots
The curated milestone set in screenshots/ shows the project as it stands today:
Running on real Mac OS 9

The fixes50 text-shadow probe page, rendered on a real Power Macintosh G3 iMac running Mac OS 9.1 with CarbonLib. Platinum theme, beige hardware, modern CSS3 in 2026.
Real CSS typography on Mac OS 9

"MacSurf — pushing the cascade." Author CSS with custom properties (var(--sans)), nested unordered and ordered lists with the bullet-glyph repair from fixes143a, definition lists, mathematical inline elements — all driven by libcss + the QuickDraw plotters.
Linear gradients + flex layout

fixes47's real per-row QuickDraw gradient painting plus flex-box justify-content distribution variants. Four gradient bars over start / end / center / between / around / evenly flex modes, all on real hardware.
Cards, callouts, tables

border-radius cards (via FrameRoundRect / PaintRoundRect), accent-left callouts for notes and warnings, and a real CSS feature-support matrix rendered as a styled HTML table.
Text-shadow + opacity

fixes50's -macsurf-text-shadow paints drop shadows under DrawText, with QuickDraw stipple patterns providing the opacity steps from solid → invisible.
The Mandelbrot

An ASCII Mandelbrot set, 40x20 characters, 80 iterations per pixel, computed entirely in JavaScript on a PowerPC G3 running Mac OS 9 — by Duktape, the engine of that first night in April 2026. The fractal that proved a 1998 machine can run a scripting language at all. The browser runs macQJS today.
What's Next
- Grid Round 2 (#279) — placement/span-aware auto-track sizing,
minmax()composition, and §12.8 stretch distribution, following on from the auto-track content-sizing work in #62 - i18n line-breaking (#308) — the July line-breaker audit's remaining follow-through
- Progressive JPEG (#269)
- JavaScript performance — on a heavy modern page, top-level bundle execution dominates the load: measured at 25 of 31 seconds on the hackaday front page, against roughly 1.2 seconds for cascade, layout and paint combined. The answer is a per-script budget or bytecode caching, not more layout tuning.
- The APIs real pages ask for and we currently fake —
matchMediaanswers false to every query (so every responsive branch takes the same path), MutationObserver and ResizeObserver accept observers and never fire, IntersectionObserver always reports intersecting, and<script type="module">is dropped outright - A real
innerHTMLserializer (#262) — readinginnerHTMLstill returnstextContent - Form-control styling (
appearance) — controls currently render through the Carbon Control Manager, soappearance:noneneeds synthetic CSS-painted controls and new input handling
Full open-issue list: Issues.
Credits
Patrick Britton — Creator, hardware wrangler, build engineer, tester, and the person who believed a 1998 Power Mac could run JavaScript in 2026.
Claude (Anthropic) — Development partner. Wrote the C code, diagnosed CW8 errors from transcribed logs, debugged crashes from photographs of a CRT monitor, and kept shipping fixes until the Mandelbrot rendered.
Sami Vaarala — Creator of Duktape. The decision to write an ES5 engine in portable C89 with a single-file amalgamation is what put JavaScript on Mac OS 9 in the first place. Duktape compiled on CodeWarrior 8 with zero source patches, and carried MacSurf from the first 1+1=2 through the 1.5 line.
Fabrice Bellard and Charlie Gordon — Creators of QuickJS. A small, portable, genuinely complete modern JavaScript engine is the reason a 1998 PowerPC can run ES2023 and execute a contemporary WordPress bundle. macQJS is MacSurf's port of their work.
NetSurf contributors — The NetSurf browser's clean C architecture, explicit POSIX-free design, and cooperative-multitasking frontend model gave MacSurf a real browser engine to build on.
cy384 — Author of SSHeven and the Retro68 OT TCP demo. The Open Transport patterns MacSurf uses for networking are directly derived from cy384's verified-working implementations.
Macintosh Garden (macintoshgarden.org) — The archive of Classic Mac software. CodeWarrior 8, ResEdit, system software, extensions, utilities — if it ran on a classic Mac and you needed it, Macintosh Garden had it. This project would not have been possible without the preservation work of that community.
Gary Hansen (1947–2002)




Gary Hansen was a communications lifer who rode every wave the industry threw at him. He started shooting photos and filing stories for the New Ulm Daily Journal at thirteen, landed at the Associated Press by twenty-one — covering Nixon, Bobby Kennedy, and McGovern out of the Pierre, SD bureau — and by the mid-'80s had pivoted hard into the desktop publishing revolution, training thousands of employees at shops like 3M, Honeywell, and General Mills on the Mac tools that were rewriting the rules of print. He called himself "the quintessential Renaissance Man of communications," and the resume backed it up.
By the mid-'90s Gary had founded Digital Concepts Corporation out of his home in Edina, Minnesota, building websites, managing hosting, and doing print design and photography for a stable of clients. He ran four browsers simultaneously for cross-browser testing, kept nearly 1,700 bookmarks organized across them, and was an early adopter of everything from PageMaker to cable internet to Napster. His Power Mac G3 — upgraded with a Sonnet Encore G4 processor because stock specs were never quite enough — was the nerve center for all of it.
Outside the screen, Gary was a private pilot, a certified SCUBA diver, a chess tactician, and a deeply committed motorcycle guy. He served on the board of the Minnesota Motorcycle Riders Association, designed their Freedom Review newsletter in PageMaker, lobbied for rider legislation, and eventually signed on as General Manager at Easyriders of Minneapolis — submitting a brutally honest five-page application that noted, among his weaknesses, "no tattoos."
He was a father who named every password after his daughter Kaija, a husband to Deb, and a guy who helped everyone around him get online, get a resume typed, or get a computer set up. He was 55 when he died in September 2002. The G3 that powers this browser was part of his world — and this project is a small way of keeping that machine, and the curiosity behind it, running.
The Classic Mac community — Everyone keeping these machines alive, sharing knowledge about CarbonLib, Universal Interfaces, and the art of writing software for an OS that Apple abandoned 24 years ago.
MacSurf is open source. The code, the story, and the screenshots are all real. No emulators were used. No shortcuts were taken. Gary's Power Macintosh G3 from 1998 runs JavaScript in 2026.