Hazel reports · ^report(39) · Hazel as a JS library · started 2026-11-03, updated 2026-11-03 07:23 MST · work in progress · after ^report(34)

Hazlets, So Far

A Hazlet is a JavaScript program whose event handlers are written in Hazel: the program hands an event to a Hazel function, and acts on what the function answers. Matthew's word for the pattern (2026-11-02). Messed Up Dress Up, a game for kids, is the first Hazlet and so far the only one. This report gathers in one place what has been built for Hazlets since 2026-10-20, where each piece stands today, and what comes next. Matthew asked for it on 2026-11-03, for his meeting with Cyrus on 2026-11-04 (^report(37), the agenda, its section 2).

Short answers. Each is as of 2026-11-03; the sources are at the end.

  1. One Hazlet runs today, live. Messed Up Dress Up's 📜 Scripting tab (since 0.0.412, 2026-10-24) holds a Hazel program of modules: 📚 Common, then one module per room that has logic, five rooms in all. Each room's on_event : (Globals, Event) -> Cmd answers the game's events, and each room can be run by Hazel, by Fumola or by the game's own JS (Figure 1).

  2. Nothing of it is in Hazel's dev branch yet. The library the game uses, hazel-lib.js, is on two draft PRs, #2711 and #2719, both with CI green; GitHub reports that #2711 now conflicts with dev. The tiles a player edits in are on a branch with no PR, embed-editable. The game loads both from branch builds (the table).

  3. The library is on npm, as hazeljs 0.0.1, since 2026-11-02 at 11:03 MST: one file, entry points for Node and for a script tag, TypeScript declarations and a README. A fresh project installs it from the registry and runs the README's examples. It is built from #2719's head, so it carries none of dev's changes since 2026-10-19 (below).

  4. A Hazel rule is fast enough for a lever, with one pause. In the game, a Hazel rule answers in about 3.5 ms of a 13.7 ms lever pull (Fumola about 1.6 ms, of 10.7). But the first event after a reset waits 0.5 to 0.7 s while the program is built (^report(32), a draft). Larger rules pay for crossing the boundary, and the fixes for that are on trial branches, not in the game (below).

  5. Next: the package's checks, its placeholder version and a home for its source; the game loading Hazel from the package; the trap room live in ^report(34), its rule edited in tiles; and the library's road to dev, which is the meeting's question (What next, For the meeting).

What a Hazlet is#

Many JavaScript programs are organized around events: a click, a key, a timer, a message. A Hazlet hands some of those events to Hazel. The JS side builds a value describing what happened and what the handler may see, calls a function written in Hazel, and gets back a value describing what to do. Hazel never touches the page directly: its answer is data, and the JS program acts it out. Two names keep the parts apart. The library (hazel-lib.js, which defines window.Hazel) is the API for analyzing and running Hazel from JS. A Hazlet is a program that uses the library this way, with Hazel functions as its handlers.

In Messed Up Dress Up, a red lever pulled in the castle's trap room is the event Pull(L). The game builds the room's view of its state, a Globals record (the levers, how the pits are wired, the game's random draws, and more), and calls TrapRoom.on_event. The rule answers a Cmd, a chain of commands such as Flip(L, SetPit(0, true, Done)): flip the lever, then open pit 0. The game plays each command as its own code would, with its animation and its pits. When the player edits a rule in the 📜 tab, the game hands every module's text to Hazel.runProgram, which builds the one program and reports errors, holes and unfinished brackets module by module. While the program has errors or is not finished, the rooms run the game's own JS, so the game keeps working; the Hazel rule still runs as far as its errors allow, and its answer goes in the event log beside them. Each Hazel box shows its module in Hazel's own tiles, from Hazel's app in a frame, with Hazel's error marks; an edit made in the tiles is posted back to the game.

One event the game (JS) a red lever pulled: Pull(L) the room's view Globals: the levers, the wiring, the draws the library calls TrapRoom.on_event; values converted both ways the rule (Hazel) answers a Cmd: Flip(L, SetPit(0, true, Done)) the game acts each command, as its own code would: lever, pit One edit the tiles, in a frame Hazel's app: the module in tiles, error marks 📜 the module's box Common, or one room; an edit from either side Hazel.runProgram the one program, built; errors, holes per module ready? yes: each room's on_event no: the rooms run their JS the handlers the next event calls the game's own JS the library (#2711, #2719) the room's rule, in Hazel the tiles' branch (embed-editable) The trap room's rule, as the game ships it (0.0.474); Pull(L) with the pits wired normally.
Figure 1. One event in the first Hazlet, and one edit of its rules. Top: the game hands an event and the room's view to the room's Hazel function, and acts out the commands it answers. Bottom: an edit in the 📜 tab rebuilds the program; while it is not ready, the room's JS answers instead. Each box's border says where that piece comes from: the game's own code, the library (#2711, #2719), or the tiles' branch. Read from the game's js/25-scripting.js at 0.0.474.

^report(33) reads what the rules are told today in detail: a view built for each room, with numbers for names. It proposes names for every place and thing in the game, and types built on them.

Progress so far#

WhenWhatWhere
2026-10-20Hazel as a JS library: Hazel.analyze and Hazel.run on any page, Hazel values as plain JS values, JS functions in and out.#2711, draft
2026-10-21Programs of named modules (analyzeProgram, runProgram), and embed mode: Hazel's tiles in another page's frame. The same day, Matthew's meeting with Cyrus encouraged Hazel as a game script system, starting with this game (^report(30)).#2719, draft, stacked on #2711
2026-10-22What one call into Hazel costs, for the game's World rule: most of it a type annotation re-checked at run time, which #2681 removes; under it, a floor for crossing the boundary that grows with the argument.^report(25)
2026-10-22The same rules in Fumola, beside Hazel and the game's JS.^ticket(542)
2026-10-22 to 23Tiles in the frame that select, take edits and post each edit's text back; themes, fonts, line numbers, go-to-definition.embed-editable, 12 commits, no PR
2026-10-23Two days in: five rooms and Common in Hazel and in Fumola, each checked against the game's JS; fixes for Hazel's speed on trial branches.^report(30)
2026-10-23The game loaded with each of the three as its default, timed as a speed run.^report(32), a draft
2026-10-24📜 Scripting released in the game (0.0.412): the panel, tiles in every box, the event log, Fumola with types, a JS, Hazel or Fumola chip per room.matthewhammer/kids-choice-games#1
2026-10-25A case study of the rules: what they are told, and names and types for every place and thing.^report(33)
2026-10-26Should the library be official? A suggestion: merge it as experimental, and promise stability for a small core once a second user has used it.^report(34)
2026-10-29Hazel's branch builds moved to build.hazel.org; the game followed (0.0.467), or its tiles' messages were dropped.^ticket(658)
2026-11-02"Hazlet" named. hazeljs 0.0.1 published on npm; ^report(34) updated with what the release carries beyond dev. Tickets for the package, and for the trap room live in a report.^ticket(668), ^ticket(675)

Where each piece stands#

PieceStateAs ofTicket or PR
The library: hazel-lib.js, which puts window.Hazel on a page (analyze, run, the value mapping)● Draft PR, CI green; GitHub reports a conflict with today's dev2026-11-03#2711 (opened 2026-10-20)
Programs of named modules (analyzeProgram, runProgram) and embed mode: Hazel's tiles in a frame, read-only● Draft PR stacked on #2711, CI green2026-11-03#2719 (opened 2026-10-21)
Tiles in a frame that a reader can select and edit, each edit's text posted back to the page● Branch embed-editable, 12 commits (2026-10-22 and 23), no PR yet2026-11-03^ticket(677)
Messed Up Dress Up's 📜 Scripting tab: five rooms' rules and a Common module, each room run by its JS, Hazel or Fumola● Live since 0.0.412 (2026-10-24); the game is at 0.0.474. It loads the library from #2719's branch build and the tiles from embed-editable's, on build.hazel.org2026-11-03matthewhammer/kids-choice-games#1 (merged 2026-10-24)
Speed fixes the game's rules want: a value known to have a type is not walked again for it● Open against dev, waiting for review; the other fixes ^report(30) measured are on trial branches, not in the game2026-11-03#2681
The npm package hazeljs 0.0.1: the library as one file, entry points for require, import and a script tag, TypeScript declarations, a README● Published 2026-11-02 11:03 MST, under Matthew's npm account. Installed from the registry into a fresh project: the README's examples run through require and import; jsDelivr and unpkg serve the file. Its source is not yet in a public repository2026-11-02 13:23^ticket(668), ^ticket(669) to ^ticket(671), ^ticket(673)
The package's checks: a browser page and TypeScript examples, and a GitHub workflow that installs the packed package into each● Not started2026-11-02 13:23^ticket(672)
A placeholder version npm made first, 0.0.0-stage● Still listed; to be deprecated, which needs Matthew's two-factor code2026-11-03^ticket(673)
The game loading its Hazel from the package, pinned to a version, instead of a branch build● Not started2026-11-02^ticket(674)
The trap room live in ^report(34), its Hazel rule editable in tiles● Not started (Matthew's ask, 2026-11-02)2026-11-02^ticket(675) to ^ticket(678)

Read on 2026-11-03 07:20 MST. A ticket's number links to its row on the work dashboard.

How fast it is#

The npm package, hazeljs#

A first, experimental release: npm install hazeljs, or a script tag from jsDelivr or unpkg. Version 0.0.1 has 9 files, 2.8 MB unpacked (about 640 KB gzipped). require("hazeljs") and import Hazel from "hazeljs" give the same Hazel object that a script tag puts on the page. The package also has TypeScript declarations for the API and the values, and a README that npmjs.com shows. The README covers install, the API call by call, the values table, programs of modules, size and speed, limits, and the game as a use case. It sends readers to hazel.org and to the contributors on GitHub for who makes Hazel. The name, hazeljs, is Matthew's choice; Hazlet stays the pattern's name. License MIT, as Hazel's.

What it is built from: #2719's head, 0d66c36618, which is dev of 2026-10-19 plus the library's own 11 commits. There are no modular editors (#2469), no livelits, and none of the performance PRs still open against dev. As of 2026-10-29 the release was 151 commits, 24 PRs, behind dev (^report(34) lists them).

Measured, in Node 22 on a laptop (the README): require about 170 ms; a small program run about 30 ms; a module of 100 short members analyzed in about 110 ms, of 400 in about 2.3 s.

Known issues, in its CHANGELOG: a module of about 150 members overflows Node's default stack; the result's ms field reads 0 under Node (a bug in the library's timing, not yet traced); a text only Hazel's slower, recovering parser can read prints a SLOW PARSE line to the console. Also open: npm lists a placeholder it made first, 0.0.0-stage, to be deprecated; and the package's own source is not yet in a public repository.

What next#

The package#

  1. The checks: a browser page and a TypeScript example (checked by tsc --noEmit), and a GitHub workflow that packs the package, installs it into each example as a user would, and runs them, the page in headless Firefox (^ticket(672)).
  2. Deprecate 0.0.0-stage. Decide where the package's source lives (a folder on #2711's branch, built by dune, or a repository of its own), how versions after 0.0.1 name the Hazel commit they are built from, and whether it moves to an @hazelgrove organization on npm (^ticket(673)).
  3. The game loads Hazel from the package, pinned to a version, so the game is the package's first real user; its speed run is the check (^ticket(674)).

The trap room live in a report#

Matthew's ask (2026-11-02): a reader plays the trap room on ^report(34)'s page and edits its Hazel rule in tiles, the room answering each edit at once (^ticket(675)). Three pieces, in order:

  1. The trap room outside the game: first a link that opens the game at the trap room with its 📜 tab showing and Hazel chosen, so a report can frame it; later the room as a package of its own, which means pulling it out of the game's shared state (^ticket(676)).
  2. Tiles in a page: a PR for embed-editable, stacked on #2719, and a place to serve it from that a report can frame (^ticket(677)). The library package has no editor.
  3. The wiring: the report's page posts the room's module text to the tiles, takes each edit back, runs it with hazeljs, and hands the new on_event to the room (^ticket(678)).

The library, toward dev#

From ^report(34)'s list, still open:

  1. Ready #2711: resolve its conflict with dev, bring its description up to date, take it out of draft.
  2. Open the PR for embed-editable (the same step as above), so all that the game uses is under review.
  3. Decide whether the library's programs take their outline and navigation from modular editors (#2469) before the library grows its own.
  4. Convert JS values straight into Hazel values at the boundary, rather than through syntax evaluated again (^report(25)'s floor), and land #2681, which the game's typed rules want.
  5. Run analysis in a worker inside the library, as the app does, so a slow read never freezes a page.
  6. Find a second Hazlet, a course page or the docs' own examples, before promising stability beyond the core.

The game's rules#

^report(33)'s steps: names for every place and thing, kept through moves; a RuleState for the tree room first, built from those names, beside today's Globals; then the trap room.

For the meeting on 2026-11-04#

From ^report(37), the agenda's section 2:

How this was put together#