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

129 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 |
## 2. HyperScript cookie syntax
`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:
```js
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:
```js
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
```lisp
(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.