tauri-runtime-servocat 2.2.0

Servo-replacement runtime for Tauri: wires html-cat, css-cat, dom-cat, layout-cat, paint-cat, net-cat, boa-cat, ecma-runtime-cat, and web-api-cat into a single rendering + scripting pipeline. v2.2.0 closes the cookie loop: `http://` navigates now parse `Set-Cookie` response headers and merge them into the webview's jar (replace-by-name semantics), so a navigation that sets a cookie persists for the next request without the host having to call `set_cookie` from Rust. The Servo no-AI policy disqualifies upstream contribution; this is the AI-built parallel.
# CLAUDE.md -- Rust Project Conventions

## Philosophy

Functional, type-driven, domain-driven.

## Architecture

- Modules by domain context.
- One module per concern (error, pipeline, script, frame).
- Thin lib.rs that exposes `render(html, css, viewport)` and
  `run_script(html, css, js, viewport)`.

## Types

- Newtypes for `RenderInput`, `ScriptInput`, `Frame`.
- 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

- The full cat-stack: `html-cat`, `css-cat`, `dom-cat`, `layout-cat`,
  `paint-cat`, `net-cat`, `boa-cat`, `ecma-runtime-cat`, `web-api-cat`,
  `ecma-lex-cat`, `ecma-parse-cat`.
- `proptest` dev-dep.
- No path dependencies.

## Documentation

- `///` doc comments on every public item.
- `# Examples` with runnable code blocks.

## Layer context

This is the **meta-crate** at the top of the cat-stack:

1. `ecma-syntax-cat`, `ecma-lex-cat`, `ecma-parse-cat` -- JS AST/parse.
2. `boa-cat`, `ecma-runtime-cat` -- JS engine + built-ins.
3. `html-cat`, `css-cat` -- web-spec parsers.
4. `dom-cat`, `layout-cat`, `paint-cat` -- rendering pipeline.
5. `net-cat` -- HTTP transport.
6. `web-api-cat` -- JS<->DOM/fetch bridge.
7. **`tauri-runtime-servocat`** -- meta-crate wiring all of the above
   into a single pipeline targeting Tauri integration.

## v2.2.0 scope (current)

Headless pipeline + script driver + tiny-skia rasterizer + cosmic-text
glyph raster + winit/softbuffer window + IPC bridge + DOM back-prop +
real winit-backed `tauri_runtime::Runtime` window lifecycle + webview
navigation + script eval + per-window softbuffer presentation +
state-query round-trip from worker threads + `eval_script_with_callback`
result delivery + monitor enumeration + `run_on_main_thread` +
percent-decoded `data:` / `file://` / `http://` URL loading + Rust- and
JS-side `ipc_handler` bridge + cursor / size / drag / theme / icon /
user-attention / center plumbing + per-window `WindowFlags`
bookkeeping + tracked webview bounds + per-webview zoom / background
color / auto-resize / in-memory cookie jar + PNG snapshot
(`WebviewDispatch::print`) + main-thread `with_webview` callback +
stable `WebviewId` so `reparent(new_window_id)` follows the dispatcher +
visual `background_color` in the softbuffer compositor + `Cookie:`
header attached to net-cat HTTP `navigate` requests + visual `zoom`
applied via nearest-neighbour sampling at `1/zoom` step in the
compositor + `Set-Cookie` response headers parsed back into the
webview's jar so the cookie loop is bidirectional.

- `render(html, css, viewport)` -- parses HTML+CSS, builds the DOM, runs
  cascade + block layout, emits the paint-cat `DisplayList`.  Returns a
  [`Frame`] carrying the document, layout tree, and display list.
- `run_script(html, css, js, viewport)` -- as above, plus an inline JS
  step driven by `boa-cat` + `ecma-runtime-cat` + `web-api-cat`.  Script
  value lands on the returned `Frame`; layout is the pre-script one.
- `run_script_with_commands(.., &HostCommands)` -- same but installs
  the registry as the `__TAURI__` global.
- `run_script_with_backprop(.., &HostCommands)` -- additionally walks
  the post-script JS-side DOM via `web_api_cat::extract_document` and
  re-runs layout + paint so the returned `Frame`'s display list
  reflects scripted mutations.
- `HostCommands` -- registry of `NativeFn` host commands callable from
  JS via `__TAURI__.invoke('cmd', ...args)` (dispatcher) or
  `__TAURI__.cmd(...args)` (direct method).  Function-pointer-only
  (no captured state); host state threads through the boa-cat heap.
- `render_to_pixels(frame, w, h)` / `render_to_pixels_with(.., &mut TextRenderer)`
  -- tiny-skia rasterization with cosmic-text + swash glyph rendering
  for `FillText`.
- `run_window(frame, viewport)` -- opens a winit window and presents
  the rasterized frame via softbuffer; composites premultiplied RGBA
  over white before writing to softbuffer's 0RGB word format.
- `cargo run --bin demo` -- v0.4 demo (windowed render).
- `cargo run --bin demo_ipc` -- v0.5 demo (registers a host command,
  invokes from JS, mutates the DOM, back-props, shows the updated
  render in a window).
- `ServocatRuntime` / `ServocatHandle` / `ServocatWindowDispatch` /
  `ServocatWebviewDispatch` / `ServocatEventLoopProxy` /
  `ServocatWindowBuilder` -- implementations of the
  `tauri_runtime::Runtime`, `RuntimeHandle`, `WindowDispatch`,
  `WebviewDispatch`, `EventLoopProxy`, and `WindowBuilder` traits.
- v1.1 wires the window lifecycle through a real winit event loop:
  `Runtime::new` builds an `EventLoop<RuntimeEvent<T>>`; window
  lifecycle / mutation methods (`set_title`, `set_size`,
  `set_position`, `show`, `hide`, `close`, `destroy`, `maximize`,
  `unmaximize`, `minimize`, `unminimize`, `set_resizable`,
  `set_decorations`, `set_fullscreen`, `set_focus`) on
  `WindowDispatch` / `RuntimeHandle` send `RuntimeEvent`s through the
  winit `EventLoopProxy`; `Runtime::run` drives an
  `ApplicationHandler` that creates queued windows, dispatches
  `WindowEvent`s back to Tauri's callback, and exits when the last
  window closes.  Webview lifecycle and most getters still return
  errors / zero values.
- `cargo run --bin demo_tauri_runtime` -- v1.1 demo (constructs the
  Runtime on the main thread, queues a window via `create_window`,
  renames it from a worker thread via `set_title`, runs the loop
  until the window closes).
- v1.2 wires the webview lifecycle: `Runtime::create_webview` /
  `RuntimeHandle::create_webview` / `WindowDispatch::create_webview`
  send a `CreateWebview` event carrying the URL; the handler parses
  `data:text/html,...` URLs, runs `pipeline::render`, stores the
  result as a per-window `WebviewState`, and triggers a redraw.
  `WebviewDispatch::navigate` / `reload` / `eval_script` /
  `eval_script_with_callback` send the matching events; eval is
  routed through `run_script_with_backprop` so DOM mutations
  back-propagate into layout before the next paint.  On
  `WindowEvent::RedrawRequested` the handler converts the current
  frame to a softbuffer surface and presents it on the window.
- `cargo run --bin demo_tauri_webview` -- v1.2 demo (opens a window,
  attaches a webview with `data:text/html,...` content, navigates to
  a second page after 1.5s, then runs a `setAttribute` JS script that
  back-propagates into the displayed frame).
- v1.3 wires state-query round-trip + eval result delivery: the
  `RuntimeEvent` enum gains `QueryInnerSize` / `QueryOuterSize` /
  `QueryInnerPosition` / `QueryOuterPosition` / `QueryScaleFactor` /
  `QueryTitle` / `QueryIsVisible` / `QueryIsFocused` /
  `QueryIsMaximized` / `QueryIsMinimized` / `QueryIsFullscreen` /
  `QueryIsDecorated` / `QueryIsResizable` / `QueryTheme` variants,
  each carrying an `mpsc::Sender<R>` reply channel; the matching
  `WindowDispatch` getters call a blocking `query()` helper
  (500 ms timeout) that returns the value the handler sends back.
  `EvalScriptWithCallback` carries a `Box<dyn Fn(String) + Send>` so
  the JS return value lands in the user's callback after eval
  completes.
- `cargo run --bin demo_tauri_queries` -- v1.3 demo (queries
  title/inner_size/scale_factor/is_focused/is_visible/is_maximized/
  is_fullscreen from a worker thread, then runs
  `eval_script_with_callback("1 + 41")` and prints the result `42`).
- v1.4 wires monitor enumeration + `run_on_main_thread` + URL scheme
  expansion: new `QueryPrimaryMonitor` / `QueryAvailableMonitors` /
  `QueryMonitorFromPoint` / `QueryCurrentMonitor` /
  `RunOnMainThread` variants on `RuntimeEvent`; the handler
  enumerates monitors through `ActiveEventLoop` and forwards each as
  a `tauri_runtime::monitor::Monitor` (with the full monitor rect
  doubling as `work_area` since winit doesn't expose work-area
  separately).  `html_from_url` now handles percent-decoded
  `data:text/html,...`, `file:///...` via `std::fs::read_to_string`,
  and `http://...` via net-cat's blocking `fetch`.  Cursor position
  remains `FailedToGetCursorPosition` (winit 0.30 doesn't expose
  desktop-relative cursor pos).
- `cargo run --bin demo_tauri_monitors` -- v1.4 demo (enumerates
  monitors from a worker thread via `RuntimeHandle::primary_monitor`
  and `available_monitors`, dispatches a closure back to the main
  thread via `run_on_main_thread`, loads a page from a
  percent-encoded `data:text/html,...` URL).
- v1.5 wires the Rust side of the `WebviewIpcHandler` bridge:
  `RuntimeEvent::CreateWebview` now carries `Option<WebviewIpcHandler<T,
  ServocatRuntime<T>>>`, which `WebviewState` stores; the new
  `RuntimeEvent::IpcRequest` variant + inherent
  `ServocatWebviewDispatch::send_ipc(request)` fire a stored handler.
  When invoked, the handler receives the same `DetachedWebview`
  shape it would in a real Tauri runtime, and can send a reply back
  through `dispatcher.eval_script(...)` (which Tauri's command system
  already does internally).  `AppHandler` now retains a proxy clone
  so it can rebuild dispatchers for handler invocations.
- `cargo run --bin demo_tauri_ipc_handler` -- v1.5 demo (registers a
  `WebviewIpcHandler` on a webview that prints the request URI/body
  and replies via `eval_script` running `console.log(...)`, then
  fires it from a worker thread via `send_ipc(ipc://greet)`).  Run
  output confirms the full round-trip: worker -> proxy -> main
  thread -> handler -> boa-cat eval -> console.log).
- v1.5.1 wires the JS side: every `EvalScript` /
  `EvalScriptWithCallback` arm wraps the eval in `with_ipc_dispatch`
  which stashes a type-erased `IpcDispatchImpl<T>` in a thread-local
  for the duration of the eval.  `WebviewState::eval` registers
  `post_ipc_message_impl` as a `NativeFn` under `__TAURI__`, so a
  JS call `__TAURI__.post_ipc_message(payload)` reads the
  thread-local, builds an `http::Request<String>` with the
  `ipc://post-message` URI, and fires a `RuntimeEvent::IpcRequest`
  through the proxy -- the same handler invocation path
  `send_ipc(request)` uses.  The bare-fn-pointer constraint on
  `NativeFn` is the only place we use thread-local state.
- `cargo run --bin demo_tauri_ipc_js` -- v1.5.1 demo (registers an
  `ipc_handler` and runs `eval_script("__TAURI__.post_ipc_message(
  '{...}')")` from a worker thread; the handler fires through the
  thread-local proxy, prints the request body, and replies via
  `console.log`).
- v1.6 wires the previously-no-op `WindowDispatch` methods that
  map cleanly onto winit's `Window` API:
  `set_cursor_visible` / `set_cursor_grab` / `set_cursor_icon` /
  `set_cursor_position` / `set_ignore_cursor_events`,
  `set_min_size` / `set_max_size`, `set_always_on_top`,
  `set_theme` (window-level), `start_dragging`,
  `request_user_attention`, `center`, and `set_icon`.  Each adds a
  matching `RuntimeEvent` variant + handler arm; the handler calls
  the corresponding `winit::window::Window` method.  `center`
  computes the offset against `current_monitor().size()`.
  `convert_cursor_icon` maps every Tauri `CursorIcon` variant to a
  winit equivalent (collapsing `Default | Arrow` since both target
  winit's `Default`).
- `cargo run --bin demo_tauri_window_methods` -- v1.6 demo (calls
  each of the 10 newly-wired methods from a worker thread; the
  window resizes to the min/max bounds, snaps to monitor center,
  bounces the dock for `request_user_attention(Informational)`,
  hides + grabs the cursor, and prints `Ok(())` for each call).
- v1.7 adds per-window `WindowFlags` bookkeeping
  (`enabled`/`maximizable`/`minimizable`/`closable`/
  `always_on_top`/`focusable`/`skip_taskbar`, defaults match
  winit's; flags map cleared on window destroy) and routes
  `set_enabled`/`set_maximizable`/`set_minimizable`/`set_closable`/
  `set_focusable`/`set_skip_taskbar` through `SetEnabled`/
  `SetMaximizable`/... events; `is_enabled`/`is_maximizable`/
  `is_minimizable`/`is_closable`/`is_always_on_top` round-trip via
  `QueryIs*` reply channels.  Webview-side, `WebviewState` now
  tracks `bounds: Rect` (initialized to the parent window's
  viewport); `set_bounds`/`set_size`/`set_position` update it via
  `SetWebviewBounds`/`SetWebviewSize`/`SetWebviewPosition` events,
  and `bounds()`/`position()`/`size()` round-trip via the matching
  query variants.  `Position::Logical` and `Size::Logical` are
  collapsed to physical at read time with `#[allow]`-gated
  narrowing.
- `cargo run --bin demo_tauri_state_tracking` -- v1.7 demo (prints
  defaults for the 5 flag getters + webview position/size, flips
  every flag and the bounds, then re-reads them so the change
  shows up).
- v1.8 adds per-webview tracking for the remaining
  `WebviewDispatch` methods that don't need real webview hooks:
  `zoom` (f64, default 1.0), `background_color` (`Option<Color>`,
  default None), `auto_resize` (bool, default false), and an
  in-memory `cookies: Vec<Cookie<'static>>` jar.
  `set_zoom` / `set_background_color` / `set_auto_resize` /
  `set_cookie` (replace-by-name) / `delete_cookie` (remove-by-name)
  / `clear_all_browsing_data` (clear the jar) update the state via
  new events; `cookies()` and `cookies_for_url(url)` query it.
  When auto-resize is on, `WinitWindowEvent::Resized` updates the
  webview's tracked bounds to the new window inner size.
  `with_webview` continues to return `Err(FailedToSendMessage)`
  since we have no native webview handle worth boxing as `Any`.
- `cargo run --bin demo_tauri_webview_state` -- v1.8 demo (calls
  every newly-wired method from a worker thread; adds two cookies,
  reads them back, deletes one by name, then clears the jar).
- v1.9 implements `WebviewDispatch::print` and `with_webview`:
  * `print()` sends `PrintWebview` through the proxy; the handler
    renders the current frame through `render_to_pixels_with`,
    copies the RGBA bytes into a `tiny_skia::Pixmap`, calls
    `encode_png()`, and writes the result to
    `$TMPDIR/tauri-runtime-servocat-print-{label}.png` (with a
    safe-character filter on the label).  Returns `Ok(())` on
    success; the path is logged to stdout.
  * `with_webview(f)` ships the user closure to the main thread
    via `RuntimeEvent::RunOnMainThread { thunk }`, where it's
    called with `Box::new(())` since the cat-stack runtime has no
    native webview handle.  Lets Tauri callers use `with_webview`
    for host-side scheduling even when a platform handle isn't
    available.
- `cargo run --bin demo_tauri_print` -- v1.9 demo (from a worker
  thread, calls `with_webview` (closure logs on the main thread)
  then `print()`; the runtime writes a 1280x720 PNG to the
  temp directory and stdout prints the path).
- v1.10 adds a stable `WebviewId` separate from `WindowId`.
  Webview-targeted `RuntimeEvent` variants carry `webview_id:
  WebviewId` instead of `window_id`; the dispatcher stores
  `webview_id`, and the handler maintains `webview_to_window`
  (webview -> current parent) and `window_to_webview` (parent ->
  attached webview) maps.  `reparent(new_window_id)` updates both
  maps and requests redraws on the old and new parents.
  `WebviewDispatch::close` and `set_focus` route through new
  `CloseWebviewParentWindow` / `FocusWebviewParentWindow` events
  that resolve the current parent via `webview_to_window`.
  Window destruction now evicts any attached webview from all
  three maps.  The remaining `dyn` sites
  (`EvalScriptWithCallback`, `RunOnMainThread`, `with_webview`'s
  `Box<dyn Any>`, `IpcDispatch`) are now documented with
  explicit FFI carve-out comments explaining why static dispatch
  isn't an option (concrete `winit::EventLoop<RuntimeEvent<T>>`
  enum + bare-fn-pointer `NativeFn` + tauri trait shapes).
- `cargo run --bin demo_tauri_reparent` -- v1.10 demo (opens two
  windows side-by-side, attaches a webview to the primary, then
  from a worker thread calls `dispatcher.reparent(secondary.id)`;
  the webview's state moves and the next paint lands in the
  secondary window).
- v2.0 applies the v1.8 `background_color` state visually:
  `present_webview` reads `webview.background_color`, defaults to
  opaque white when unset, and the softbuffer compositor blends
  premultiplied RGBA over the chosen colour (`out = src + ((1 -
  src_a / 255) * bg)`) using full-precision `u32` intermediate.
  `try_http_url` (used by `navigate(http://...)` and the initial
  `CreateWebview` URL fetch) now accepts the webview's cookies and
  attaches them as a single `Cookie: name=value; ...` header
  built by `cookie_header_value`, filtering by host match.
- `cargo run --bin demo_tauri_bg_color` -- v2.0 demo (loads a page
  with white text, then from a worker thread calls
  `set_background_color(navy)` and `reload()`; the next paint
  shows the white text on a navy background instead of the prior
  white background).
- v2.1 applies the v1.8 `zoom` state visually in the softbuffer
  compositor.  For each destination pixel `(x, y)`, `write_pixels`
  samples the source frame at `(x / zoom, y / zoom)` via
  nearest-neighbour lookup; the bounds-check + `f64`-to-`u32` cast
  are isolated in `sample_source_word` with the lossy cast
  documented as an FFI-style carve-out.  `zoom == 1` is the
  identity mapping; `zoom > 1` magnifies the top-left `1/zoom` of
  the source over the full window (zoom-in); `zoom < 1` lets the
  source extend past `src_w/src_h` and the uncovered area falls
  back to the tracked `background_color` (zoom-out).  Pipeline-side
  layout-with-zoom is deferred -- this is bitmap nearest-neighbour
  scaling so re-rasterized text isn't yet crisp.
  `SetWebviewZoom` now requests a redraw on the parent window so
  the next paint reflects the new factor.
- `cargo run --bin demo_tauri_zoom` -- v2.1 demo (loads a page
  with a red heading box, then from a worker thread calls
  `set_zoom(2.0)`; the next paint shows the top-left half of the
  source frame magnified 2x).
- v2.2 closes the cookie loop.  `html_from_url` now returns
  `(String, Vec<Cookie<'static>>)`.  `try_http_url` reads
  `response.headers().iter()`, filters for case-insensitive
  `Set-Cookie`, parses each value with `Cookie::parse(value)`,
  and `Cookie::into_owned`s the result.  The `CreateWebview` and
  `Navigate` handler arms call a new `merge_cookies(jar, new)`
  helper that evicts any existing entry with the same name before
  pushing, so the host's `Vec<Cookie>` reflects what the server
  most recently set.  Two inline unit tests
  (`merge_replaces_existing_by_name`, `merge_appends_unrelated_names`)
  lock in the replace-by-name semantics without needing a live
  HTTP server.

## Roadmap (2.x)

- v2.3+ -- proper "crisp" zoom: re-layout the page at
  `viewport / zoom`, then upscale the smaller pixmap to the
  window.  Adds a pipeline-side scale parameter to
  `pipeline::render` / `script::run_script_with_backprop`.

## Deferred to post-1.x

- Live re-render in the open window after IPC calls or user input
  (currently the window renders the v0.5 back-propped frame once at
  startup; mutations during the event loop don't trigger a redraw).
- Async / Promise-returning host commands (needs comp-cat-rs
  scheduler integration).
- Multiple `__TAURI__` invocation flavours that don't rely on `this`
  (e.g. a globally-bound `invoke` function).

The Servo no-AI policy disqualifies upstream contribution; this stack
is the AI-built parallel.