Files
rose-ash/plans/designs/f5-cookies.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

5.2 KiB
Raw Blame History

F5 — Cookie API (+5)

Suite: hs-upstream-expressions/cookies
Target: All 5 tests are SKIP (untranslated).

1. The 5 tests

From upstream test/expressions/cookies.js:

Test What it checks
length is 0 when no cookies are set cookies.length == 0 with no cookies set
basic set cookie values work set cookies.name to "value" then cookies.name == "value"
update cookie values work set, then set again, value updates
basic clear cookie values work set cookies.name to "value" then clear cookies.name, then cookies.name == undefined
iterate cookies values work for name in cookies iterates cookie names

cookies is a special global expression in HyperScript backed by document.cookie. The upstream implementation wraps document.cookie in a proxy:

  • cookies.name → read cookie by name (returns string or undefined)
  • set cookies.name to val → write cookie (sets document.cookie = "name=val")
  • clear cookies.name → delete cookie (sets max-age=-1)
  • cookies.length → number of cookies set
  • for name in cookies → iterate over cookie names

3. Test runner mock

All 5 tests are untranslated — no SX test bodies exist yet. The generator needs patterns for the cookie expressions, and hs-run-filtered.js needs a document.cookie mock.

Mock in tests/hs-run-filtered.js

Add a simple in-memory cookie store to the dom mock:

let _cookieStore = {};
Object.defineProperty(global.document, 'cookie', {
  get() {
    return Object.entries(_cookieStore)
      .map(([k,v]) => `${k}=${v}`)
      .join('; ');
  },
  set(str) {
    const [pair, ...attrs] = str.split(';');
    const [name, val] = pair.split('=').map(s => s.trim());
    const maxAge = attrs.find(a => a.trim().startsWith('max-age='));
    if (maxAge && parseInt(maxAge.split('=')[1]) < 0) {
      delete _cookieStore[name];
    } else {
      _cookieStore[name] = val;
    }
  },
  configurable: true
});

Add _cookieStore = {} reset to hs-cleanup! equivalent in the runner.

4. SX runtime additions in lib/hyperscript/runtime.sx

HS needs a cookies special expression that the compiler resolves. Two approaches:

Option A (simpler): Treat cookies as a built-in variable bound to a proxy dict at runtime. When property access cookies.name is evaluated, dispatch to cookie read/write helpers.

Option B (upstream-faithful): Parse cookies as a special primary expression, emit runtime calls hs-cookie-get, hs-cookie-set, hs-cookie-delete, hs-cookie-length, hs-cookie-names.

Option A is less invasive. The runtime env gets a cookies binding pointing to a special object; property access and assignment on it dispatch to the cookie helpers, which call (platform-cookie-get name) / (platform-cookie-set name val) / (platform-cookie-delete name).

Platform cookie operations map to document.cookie reads/writes in JS.

5. Generator patterns (tests/playwright/generate-sx-tests.py)

The upstream tests use patterns like:

await page.evaluate(() => { _hyperscript.evaluate("set cookies.foo to 'bar'") });
expect(await page.evaluate(() => _hyperscript.evaluate("cookies.foo"))).toBe("bar");

In our SX harness these become direct eval-hs calls. Since all 5 tests are untranslated, hand-write them rather than extending the generator (similar to E39).

6. Translated test bodies

(deftest "length is 0 when no cookies are set"
  (hs-cleanup!)
  (assert= (eval-hs "cookies.length") 0))

(deftest "basic set cookie values work"
  (hs-cleanup!)
  (eval-hs "set cookies.foo to 'bar'")
  (assert= (eval-hs "cookies.foo") "bar"))

(deftest "update cookie values work"
  (hs-cleanup!)
  (eval-hs "set cookies.foo to 'bar'")
  (eval-hs "set cookies.foo to 'baz'")
  (assert= (eval-hs "cookies.foo") "baz"))

(deftest "basic clear cookie values work"
  (hs-cleanup!)
  (eval-hs "set cookies.foo to 'bar'")
  (eval-hs "clear cookies.foo")
  (assert= (eval-hs "cookies.foo") nil))

(deftest "iterate cookies values work"
  (hs-cleanup!)
  (eval-hs "set cookies.a to '1'")
  (eval-hs "set cookies.b to '2'")
  (let ((names (eval-hs "for name in cookies collect name")))
    (assert (contains? names "a"))
    (assert (contains? names "b"))))

7. Implementation checklist

  1. Add cookie mock to tests/hs-run-filtered.js. Wire reset into test cleanup.
  2. Add hs-cookie-get, hs-cookie-set, hs-cookie-delete, hs-cookie-length, hs-cookie-names to lib/hyperscript/runtime.sx.
  3. Add cookies as a special expression in the HS parser/evaluator that dispatches to the above.
  4. Replace 5 SKIP bodies in spec/tests/test-hyperscript-behavioral.sx with translated test bodies above.
  5. Run hs_test_run suite="hs-upstream-expressions/cookies" — expect 5/5.
  6. Run smoke 0195 — no regressions.
  7. Commit: HS: cookie API — document.cookie proxy + 5 tests

8. Risk

Medium. The mock is simple. The main risk is the cookies expression integration in the parser — it needs to hook into property-access and assignment paths that are already well-exercised. Keep the implementation thin: cookies is a runtime value with a special type, not a new parse form.