# 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 0–195 — 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.
