Files
rose-ash/plans/designs/f1-null-safety.md
giles 985671cd76 hs: query targets, prolog hook, loop scripts, new plans, WASM regen
Hyperscript compiler/runtime:
- query target support in set/fire/put commands
- hs-set-prolog-hook! / hs-prolog-hook / hs-prolog in runtime
- runtime log-capture cleanup

Scripts: sx-loops-up/down, sx-hs-e-up/down, sx-primitives-down
Plans: datalog, elixir, elm, go, koka, minikanren, ocaml, hs-bucket-f,
       designs (breakpoint, null-safety, step-limit, tell, cookies, eval,
       plugin-system)
lib/prolog/hs-bridge.sx: initial hook-based bridge draft
lib/common-lisp/tests/runtime.sx: CL runtime tests

WASM: regenerate sx_browser.bc.js from updated hs sources

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 09:19:56 +00:00

3.3 KiB
Raw Blame History

F1 — Null Safety Reporting (+7)

Suite: hs-upstream-core/runtimeErrors
Target: 7 currently-failing tests (decrement, default, increment, put, remove, settle, transition commands)

1. Failing tests

The suite has 18 tests total; 11 already pass. The 7 failures all share the pattern:

Expected '#doesntExist' is null, got 

The eval-hs-error helper already exists (landed in null-safety piece 1). It compiles and runs a HS snippet and returns the error string. The problem is that the listed commands don't guard against null targets before operating, so they produce no error (or a cryptic one) instead of "'#doesntExist' is null".

Test Command Null target expression
decrement decrement #doesntExist's innerHTML #doesntExist
default default #doesntExist's innerHTML to 'foo' #doesntExist
increment increment #doesntExist's innerHTML #doesntExist
put put 'foo' into/before/after/at start of/at end of #doesntExist #doesntExist
remove remove .foo/.@foo/#doesntExist from #doesntExist #doesntExist
settle settle #doesntExist #doesntExist
transition transition #doesntExist's *visibility to 0 #doesntExist

Note: add, hide, measure, send, sets, show, toggle, trigger already pass — they already guard.

2. Required error format

'#doesntExist' is null

The apostrophe-quoted selector string followed by is null. The selector text is the original source text of the element expression (e.g. #doesntExist, not a stringified DOM node).

This is the same format already used by passing commands. The null-safety piece 1 commit added eval-hs-error and hs-null-error helper — just need to call it at the right point in each missing command.

3. Where to add guards

All in lib/hyperscript/runtime.sx. Pattern for each command:

(when (nil? target)
  (hs-null-error target-source-text))

Where hs-null-error (or equivalent) raises with the formatted message.

Per-command location

  • decrement / increment — after resolving the target element, before reading/writing innerHTML
  • default — after resolving target element, before reading current value
  • put — after resolving destination element (covers all put variants: into, before, after, at start, at end)
  • remove — after resolving the from target element
  • settle — after resolving target element, before starting transition poll
  • transition — after resolving target element, before reading/setting style

4. Implementation checklist

  1. Find each failing command's runtime function in lib/hyperscript/runtime.sx using sx_find_all.
  2. For each: sx_read_subtree on the function body, locate where target is resolved, insert null guard calling hs-null-error (or the equivalent raise form already used by passing commands).
  3. After all 7: run hs_test_run suite="hs-upstream-core/runtimeErrors" — expect 18/18.
  4. Run smoke range 0195 — expect no regressions.
  5. Commit: HS: null-safety guards on decrement/default/increment/put/remove/settle/transition (+7)

5. Risk

Low. The pattern is established by the 11 already-passing tests. The only risk is finding the correct point in each command where the element is resolved and before it's first used.