# CLAUDE.md -- Rust Project Conventions
## Philosophy
Functional, type-driven, domain-driven.
## Architecture
- Modules by domain context.
- One module per concern (error, document, element, fetch, install, cookie).
- Thin lib.rs that exposes `install(env, heap, html_doc)`.
## Types
- Sum types for error variants.
- No public struct fields.
- `#[must_use]` on getters and constructors.
## Error Handling
- Single project-wide `Error` enum.
- Display + std::error::Error impls by hand; no thiserror, no anyhow.
- Never panic.
## Style
- Prefer match over if/else, except on bool.
- No `return` keyword.
- No `mut`.
- Combinators over loops.
- Never match on Option<_>; use combinators.
- No unwrap()/expect() anywhere.
- No loop or for.
- No scan.
- No Rc/Arc.
- No naked `as` casts.
- Exhaustive matches; no `_` wildcard arm on enums.
## Traits
- No dyn Trait.
- Implement standard traits over ad-hoc methods.
## Linting
```toml
[lints.clippy]
all = { level = "deny", priority = -1 }
pedantic = { level = "warn", priority = -1 }
needless_pass_by_value = "warn"
manual_map = "warn"
```
## Verification
- Always run `RUSTFLAGS="-D warnings" cargo clippy --all-targets`.
- Always run `cargo fmt`.
## Testing
- Tests return `Result<(), Error>` and propagate failures with `?`.
- No assert!, assert_eq!, panic!, unreachable!, unwrap, expect.
## Dependencies
- `boa-cat = "0.7"` -- JS engine (accessor-property support since 0.3; Promise type + microtask driver + await dispatch since 0.4-0.6; ecma-parse-cat 0.3 parser bump in 0.7 unblocks `async () => ...` arrow syntax + `obj.catch(cb)` member access; this crate uses the accessor API for `document.cookie` and adds `Value::Promise(_) => None` arms in DOM extraction so engine-allocated promises pass through cleanly).
- `html-cat = "0.1"` -- source DOM tree.
- `net-cat = "0.1"` -- HTTP transport for `fetch`.
- `proptest`, `ecma-lex-cat`, `ecma-parse-cat` dev-deps.
- No path dependencies.
## Documentation
- `///` doc comments on every public item.
- `# Examples` with runnable code blocks.
## Layer context
This crate is the **JS ↔ DOM/fetch bridge**:
1. `boa-cat` -- JS engine.
2. `html-cat`, `css-cat`, `dom-cat`, `layout-cat`, `paint-cat`, `net-cat` -- rendering + network.
3. **`web-api-cat`** -- exposes the DOM and fetch to scripts.
4. `tauri-runtime-servocat` -- meta-crate.
## Strategy
DOM data lives entirely in the boa-cat heap as a tree of Objects. Native callables look up element data via the heap-handle passed as `this`, then return a new wrapping value. Mutations create new heap objects (e.g. `setAttribute` returns the new element-object). Scripts hold the same handles before and after; the underlying boa-cat heap threads the new state through `evaluate_program_with`.
## v0.6 scope (current)
- `document.getElementById(id)` -- walks the document tree, returns the matching element-object or `null`.
- `document.querySelector(selector)` -- limited selector subset (`tag`, `.class`, `#id`, `tag.class`, `tag#id`).
- Element properties: `tagName`, `id`, `className`, `textContent`, `children` array.
- Element methods: `getAttribute(name)`, `setAttribute(name, value)`, `hasAttribute(name)`, `appendChild(child)` (v0.6.1), `removeChild(child)` (v0.6.2), `insertBefore(newNode, referenceNode)` (v0.6.2).
- Element properties (extended): `classList` (v0.6.3), a `DOMTokenList`-shaped Object exposing `add(token)`, `remove(token)`, `contains(token)`, `toggle(token)`. The classList Object carries a hidden `__element__` backref to its parent element's `ObjectId`; methods resolve the current element via that backref each call so the latest `className` is always read. Writebacks delegate to `write_attribute(element, "class", new_class, heap)` so `className`, `getAttribute('class')`, and `__attributes.class` all stay in sync. `toggle` returns the new presence-state (`true` after add, `false` after remove) per MDN. classList is installed by `install_class_list(element_id, heap)` (a post-alloc step in both `build_element` and `build_blank_element`) since the backref needs the element's `ObjectId` and that only exists once `alloc_object` has run.
- DOM mutation (v0.6.1): `document.createElement(tag)` allocates a fresh element-shaped Object on the heap with the requested `tagName`, empty `id` / `className` / `textContent`, empty children array, empty `__attributes` object, and every standard method (`getAttribute`, `setAttribute`, `hasAttribute`, `querySelector`, `appendChild`, `removeChild`, `insertBefore`). A shared `document::build_blank_element(tag, heap)` helper carries the property template. `Element.appendChild(child)` clones the parent's children-array Object, writes the next numeric-key slot, increments `length`, and `store_object`s the updated array; the parent's `children` slot already points at the same `ObjectId` so the parent update is implicit. Returns the appended child per spec. `extract_document` walks `children` numerically, so newly-appended nodes reach layout / paint on the next eval pass.
- Structural mutation extras (v0.6.2): `Element.removeChild(child)` finds `child` by `ObjectId` equality in `this.children`, rebuilds the array Object with the slot dropped and subsequent indexes shifted down by one, decrements `length`, and `store_object`s the result. No-ops (and still returns the requested `child`) if `child` isn't actually a child of `this` (DOM spec says `NotFoundError`; this scoped impl stays inside the always-`Ok` `NativeFn` contract). `Element.insertBefore(newNode, referenceNode)` finds `referenceNode` by `ObjectId` equality, rebuilds the array Object with `newNode` slotted at that index and subsequent children shifted up by one, increments `length`. When `referenceNode` is `null` / `undefined` / any non-Object value the call delegates to `append_child_to_parent` (matching the DOM spec's null-ref case). A shared `children_object_of(this, heap)` helper resolves `(children_id, children_obj)` for all three mutation methods. Both new methods reach layout / paint via the same `extract_document` back-prop path as `appendChild`.
- `fetch(url)` -- synchronous net-cat call returning `{ ok, status, statusText, text, headers }` object.
- `extract_document(document_value, heap)` -- walks the post-script JS-side DOM tree and reconstructs a `dom_cat::Document`, so callers (e.g. `tauri-runtime-servocat`) can back-propagate scripted mutations into layout-cat / paint-cat. Text content is synthesized as a single Text child of each leaf element; comments and original text-node positions are not preserved.
- `document.cookie` -- installed as a `boa_cat::AccessorPair` (v0.4, requires boa-cat 0.3). Reads dispatch through a native getter that returns the current host-supplied projection of the jar; writes dispatch through a native setter that (a) appends the raw RHS string to a hidden `__cookie_writes__` log (preserving any `Max-Age` / `Path` / `Domain` / `Secure` / `HttpOnly` attributes) and (b) updates the JS-readable `__cookies_visible__` projection by extracting just the leading `name=value` and merging into the existing string by name (replace-by-name semantics matching real browsers). Hosts call `set_document_cookie(document, heap, &str)` once per evaluation to seed the projection and clear the write log; `read_cookie_writes(document, &heap)` returns the post-eval per-write entries in order, ready for `cookie::Cookie::parse` on the host side. `get_document_cookie` still returns the running JS-visible string for hosts that don't need attribute-aware merging. Visibility filtering (`HttpOnly`) remains the host's responsibility because this crate has no notion of cookie attributes; it only shuttles strings.
## Deferred to v0.5+
- Full CSS selector syntax in `querySelector` (combinators, attribute selectors, pseudo-classes).
- Async `fetch` (Promise-based; needs comp-cat-rs scheduler integration).
- `Element.style` and computed styles.
- Event API.
- `XMLHttpRequest`, `WebSocket`.
- HTTPS support (waits on net-cat's TLS feature).
- Faithful round-trip of text-node positions and comments through `extract_document`.
- Object-literal `{ get cookie() {} }` / `{ set cookie(v) {} }` syntax in user-provided scripts -- waits on ecma-parse-cat emitting `ObjectPropertyKind::Get/Set` (the engine in boa-cat 0.3 already handles those AST variants; the v0.4 cookie accessor is installed from Rust via `Object::with_accessor`).