Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
11748b5
feat: scaffold standalone kuri-browser workspace
blackfloofie Apr 25, 2026
90e84e5
feat: add first kuri-browser render prototype
blackfloofie Apr 25, 2026
e127bf4
refactor: split kuri-browser into core model and shell
blackfloofie Apr 25, 2026
c860051
docs: remove external-runtime wording from browser readme
blackfloofie Apr 25, 2026
b259adf
feat: add parsed DOM selectors to kuri-browser
blackfloofie Apr 25, 2026
1a500af
feat: add cookie-aware browser navigation state
blackfloofie Apr 25, 2026
aebec1e
feat: add form extraction for complex pages
blackfloofie Apr 25, 2026
bcb7675
feat: add session-backed form submission
blackfloofie Apr 25, 2026
90ba477
feat: add har capture for browser flows
blackfloofie Apr 25, 2026
285e121
feat: load static subresources in kuri-browser
blackfloofie Apr 25, 2026
ace5e00
feat: add quickjs evaluation to kuri-browser
blackfloofie Apr 25, 2026
abb65ca
feat: add js fetch and xhr bridge to kuri-browser
blackfloofie Apr 25, 2026
f2daced
feat: improve kuri-browser js runtime compatibility
blackfloofie Apr 26, 2026
abf87f0
feat: add kuri-browser parity and agent actions
blackfloofie Apr 26, 2026
31374cc
feat: add browser readiness parity bench
blackfloofie Apr 26, 2026
e411870
feat: add minimal CDP discovery server
blackfloofie Apr 26, 2026
8fc0eca
feat: add compressed screenshot fallback
blackfloofie Apr 26, 2026
4ee0c85
docs: explain kuri-browser cli workflow
blackfloofie Apr 26, 2026
6edff3e
feat: add minimal CDP websocket router
blackfloofie Apr 26, 2026
4b800c5
test: disclose benchmark cache policy
blackfloofie Apr 26, 2026
682c88c
feat: add native svg paint renderer
blackfloofie Apr 26, 2026
e634455
test: add native paint pixel parity harness
blackfloofie Apr 26, 2026
c231af6
feat: improve native paint flow parity
blackfloofie Apr 26, 2026
25d3334
feat: render Hacker News native paint
blackfloofie Apr 26, 2026
948f1da
feat: paint serialized js dom
blackfloofie Apr 26, 2026
feb1cbf
feat: improve js dom rendering
blackfloofie Apr 26, 2026
2dfac20
feat: improve quotes native paint parity
blackfloofie Apr 26, 2026
b15303f
feat: add heavy page screenshot waits
blackfloofie Apr 26, 2026
ca9c29c
feat: real CSS engine + small layout/paint engine wired through CDP
justrach Apr 26, 2026
04dc45e
refactor: route native_paint generic flow through engine.zig
justrach Apr 26, 2026
242b9df
feat: per-char glyph widths + whitespace, <br>, text-indent in engine
justrach Apr 27, 2026
8c9d8a9
feat(engine): unified renderer — text metrics, replaced elements, dec…
justrach Apr 27, 2026
91aef21
docs(parity): honest re-measurement of pixel parity vs Chrome
justrach Apr 27, 2026
71578b0
fix(engine): cascade body shorthand bg + recognize auto in parseEdgeS…
justrach Apr 27, 2026
2e602a3
docs(parity): update example.com numbers after bg/auto-margin fix
justrach Apr 27, 2026
e0694b2
feat(css): expand font/border/padding/margin/list-style shorthands in…
justrach Apr 27, 2026
df9f35f
feat(engine): calibrate per-char glyph widths against headless Chrome
justrach Apr 27, 2026
fb63f11
feat(engine): real CSS table layout for <table>/<tr>/<td>
justrach Apr 27, 2026
a0542cb
fix(engine): propagate root/body background to viewport canvas
justrach Apr 27, 2026
7215295
docs(parity): record example.com 98.45% wrapper / 86.37% direct after…
justrach Apr 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .claude/skills/kuri-server/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,37 @@ If you already know a tab id, set it directly with:
curl -s -H "X-Kuri-Session: $SESSION" "$BASE/tab/current?tab_id=ABC123"
```

## Choosing the right Kuri browser path

Use the main `kuri` server for production browser automation. It drives Chrome/CDP and exposes HTTP sessions, page info, snapshots, actions, HAR, cookies, and screenshots.

Use the separate `kuri-browser/` workspace only for the experimental Zig-native browser runtime. It is not wired into the root build and cannot replace headless Chrome yet.

```bash
cd kuri-browser
zig build run -- render https://news.ycombinator.com --selector ".titleline a" --dump text
zig build run -- render https://todomvc.com/examples/react/dist/ --js --wait-eval "document.querySelectorAll('.todo-list li').length >= 1"
zig build run -- bench --offline
zig build run -- parity --offline
zig build run -- serve-cdp --port 9333
```

`serve-cdp` exposes Chrome-style HTTP discovery plus a minimal WebSocket JSON-RPC router. It can answer basic Browser/Target/Page/Runtime/DOM methods, and `Runtime.evaluate` returns V8-shaped CDP remote objects backed by QuickJS. This is useful for protocol smoke tests, but it is not broad Playwright/Puppeteer compatibility and cannot replace Chrome yet.

For screenshots, `kuri-browser` currently delegates to the main Kuri/CDP renderer:

```bash
# terminal 1, repo root
zig build
./zig-out/bin/kuri

# terminal 2
cd kuri-browser
zig build run -- screenshot https://example.com --out example.jpg --compress --kuri-base http://127.0.0.1:8080
```

`--compress` captures a PNG baseline and JPEG candidate, writes the smaller file, and reports byte savings. Current local measurement on `https://example.com`: `20,523` bytes PNG to `18,183` bytes JPEG quality 50, saving `2,340` bytes or `11%`.

## Key endpoints

### Navigation & page control
Expand Down Expand Up @@ -161,3 +192,4 @@ curl -s 'https://target.com/api/v4/data' \
5. **HAR for API discovery** — start HAR before navigating, then use `/har/replay?filter=api` to find the site's API endpoints.
6. **Cookies transfer** — use `/cookies` to get browser session cookies, then make direct `curl` calls.
7. **Refs persist per snapshot only** — take a new snapshot after any navigation or meaningful DOM change.
8. **Native browser experiment is separate** — `kuri-browser` is useful for parity work and benchmarks. Its `serve-cdp` router is minimal, screenshots still use the Kuri/CDP fallback, and native layout/paint is not implemented.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ benchmarks/results/*
!benchmarks/results/20260327-111742-https_vercel.com/**
!benchmarks/results/20260327-111817-https_www.google.com_travel_flights_q=Flights%20to%20TPE%20from%/
!benchmarks/results/20260327-111817-https_www.google.com_travel_flights_q=Flights%20to%20TPE%20from%/**
kuri-browser/PARITY.md
77 changes: 77 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# AGENTS.md

Instructions for AI coding agents working in this repository.

## Benchmark Honesty

Never present a benchmark, parity score, or competitor comparison as stronger than the evidence supports.

- Always include the exact command, branch, commit, date, machine/OS when known, Zig/Chrome versions when relevant, run mode, and whether the run was offline or live.
- Always state whether each number is locally measured, CI-measured, copied from upstream documentation, or inferred from code inspection.
- Always report skipped checks, failed probes, timeouts, warmups, iteration counts, and sample size. Do not average away failures.
- Never compare Kuri numbers against another project's README numbers as apples-to-apples unless the same hardware, URLs, browser engine, cache policy, process model, and measurement tool were used.
- Treat README benchmark tables from other projects as upstream claims until reproduced locally.
- Do not claim `kuri-browser` can replace headless Chrome, Playwright, Puppeteer, Obscura, Lightpanda, or agent-browser unless the bench proves the same protocol surface and workload.
- If a benchmark depends on Kuri's Chrome/CDP fallback, label it as fallback-backed, not native `kuri-browser` rendering.

## Cache Disclosure

Benchmarks must explicitly disclose cache state.

- Prefer cold runs with fresh processes, fresh profiles, and cache-busted top-level URLs.
- If a live URL is used, add a cache-busting query parameter unless the endpoint semantics would be changed by doing so.
- If Chrome/CDP is involved, disclose whether the Chrome profile, HTTP cache, service workers, cookies, or IndexedDB may be warm.
- If a benchmark intentionally uses warm cache, label it `warm-cache` and explain why.
- If cache state is unknown, say `cache=unknown` and do not use the number for competitive claims.
- For `kuri-browser bench`, native fetch probes should use fresh runtime/fetch sessions. The screenshot probe delegates to Kuri/Chrome, so it must keep disclosing that Chrome cache can still affect fallback-rendered screenshots.

## Current Browser Baselines

Last refreshed from GitHub on 2026-04-26. Recheck upstream before changing comparative claims.

| Project | What it is | Current signal | How Kuri compares today |
|---|---|---|---|
| Obscura | Rust headless browser engine for agents/scraping | README claims V8, CDP, Puppeteer/Playwright compatibility, stealth mode, and Chrome-replacement metrics. The repo is split into `obscura-dom`, `obscura-net`, `obscura-browser`, `obscura-js`, `obscura-cdp`, and `obscura-cli`; `obscura-js` uses `deno_core` and creates a V8 startup snapshot. | `kuri-browser` is behind on engine completeness, stealth, and broad CDP. It has QuickJS-backed eval and a minimal CDP shim only. Do not claim parity. |
| Lightpanda | Zig headless browser designed for AI/automation | README says it is beta, uses V8, has DOM APIs, Ajax, cookies, proxy, network interception, CDP/websocket server, Puppeteer support, MCP, and a transparent 933-page crawler benchmark. Its `build.zig.zon` depends on `lightpanda-io/zig-v8-fork`, libcurl, html5ever-related pieces, and other browser infrastructure. | `kuri-browser` is architecturally closer because it is a Zig-native experiment, but it is much smaller: QuickJS, no native layout/paint, minimal CDP, no broad Web API matrix. |
| Vercel agent-browser | Rust browser automation CLI/daemon for AI agents | It is not a new browser engine. It drives Chrome for Testing or detected Chrome via CDP, has broad CLI actions, snapshots, screenshots, PDF, HAR, sessions/profiles, React/Web Vitals tooling, and benchmarks Node daemon vs Rust daemon in Vercel Sandbox. | Main `kuri` is the closer comparison because both automate Chrome/CDP. `kuri-browser` is not comparable as a Chrome replacement yet. |

Source URLs to recheck:

- https://github.com/h4ckf0r0day/obscura
- https://github.com/lightpanda-io/browser
- https://github.com/lightpanda-io/demo/blob/main/BENCHMARKS.md
- https://github.com/vercel-labs/agent-browser
- https://github.com/vercel-labs/agent-browser/tree/main/benchmarks

## Competitive Comparison Rules

- Obscura comparison must separate upstream claims from local reproduction. Its README currently does not provide enough hardware/cache/run detail for its headline table to be treated as verified here.
- Lightpanda comparison may cite its benchmark protocol because it publishes hardware, Chrome version, commit, page count, measurement tools, and commands. Still disclose that the upstream benchmark is their environment unless rerun locally.
- Agent-browser comparison must not be framed as native-browser parity. It benchmarks daemon overhead while still using Chrome, so compare it to Kuri's Chrome/CDP HTTP-agent path, not to `kuri-browser` native rendering.
- For any new comparison table, include a `Not Comparable Yet` row when workloads differ.
- If a score increases because a fallback path was added, report both native-only and fallback-backed interpretation.

## Local Verification Baseline

Before pushing browser-runtime benchmark changes, run:

```sh
cd kuri-browser
zig fmt src/bench.zig src/parity.zig src/cdp_server.zig src/js_runtime.zig
zig build test
zig build
./zig-out/bin/kuri-browser bench --offline
./zig-out/bin/kuri-browser parity --offline
```

When running live validation, start Kuri from a known state and record whether it was a fresh process:

```sh
cd ..
zig build
./zig-out/bin/kuri

cd kuri-browser
./zig-out/bin/kuri-browser bench --kuri-base http://127.0.0.1:8080
./zig-out/bin/kuri-browser parity --kuri-base http://127.0.0.1:8080
```
175 changes: 175 additions & 0 deletions kuri-browser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# kuri-browser

Experimental standalone browser-runtime workspace for Kuri.

This folder is intentionally not wired into the root `build.zig`. It exists as a separate Zig build so we can prototype a standalone fetch + DOM + JS runtime without disturbing Kuri's current Chrome/CDP path.

## Current Layout

- `src/model.zig`: shared `Page`, `Link`, and fallback-mode types
- `src/core.zig`: runtime shape plus page-loading orchestration
- `src/dom.zig`: parsed DOM tree plus basic selector queries
- `src/fetch.zig`: network acquisition, validation, redirects, and `curl` fallback
- `src/js_engine.zig`: QuickJS-backed page execution plus browser API shims
- `src/render.zig`: parsed-page extraction into the shared page model
- `src/native_paint.zig`: native SVG text/DOM paint output
- `src/screenshot.zig`: screenshot fallback through Kuri's existing CDP server
- `src/bench.zig`: replacement-readiness benchmark
- `src/parity.zig`: weighted parity score against Kuri's current browser surface
- `src/cdp_server.zig`: minimal Chrome-style HTTP discovery plus WebSocket JSON-RPC routing
- `src/shell.zig`: CLI-facing usage, status, roadmap, and text rendering
- `src/runtime.zig`: thin facade used by `src/main.zig`

This is intentionally closer to the repo boundaries in `nanoapi` and `turboAPI`: stable shared types in the middle, thin shell edges, and transport/rendering logic kept separate.

## Build

```sh
cd kuri-browser
zig build
zig build run -- --help
zig build run -- status
zig build run -- render https://example.com
```

## Current Scope

- keep Kuri's existing managed-Chrome/CDP server untouched
- prototype a Zig-native browser runtime in isolation
- use real HTTP fetch, redirects, cookies, parsed DOM, selector queries, and QuickJS-backed page evaluation
- keep a stable `Page` model so future DOM/JS layers have a fixed handoff point
- provide a small CDP discovery and WebSocket JSON-RPC shim while the native runtime evolves
- keep full native CSS layout, raster screenshots, PDF, broad CDP domain coverage, and full Playwright/Puppeteer compatibility out of scope until the runtime is stable

This is not wired into the root `zig build`, and it is not a production replacement for Kuri's managed Chrome path yet.

## Current Commands

```sh
zig build run -- status
zig build run -- roadmap
zig build run -- parity --offline
zig build run -- bench --offline
zig build run -- render https://news.ycombinator.com
zig build run -- render https://example.com --dump html
zig build run -- render https://news.ycombinator.com --dump links
zig build run -- render https://news.ycombinator.com --selector ".titleline a" --dump text
zig build run -- render https://todomvc.com/examples/react/dist/ --js --wait-eval "document.querySelectorAll('.todo-list li').length >= 1"
zig build run -- render https://example.com --har example.har
zig build run -- paint https://example.com --out example.svg
zig build run -- serve-cdp --port 9333
```

### CDP Shim

`serve-cdp` is an experimental compatibility shim, not a full browser protocol implementation.

```sh
zig build run -- serve-cdp --port 9333
curl http://127.0.0.1:9333/json/version
curl http://127.0.0.1:9333/json/list
```

The advertised `webSocketDebuggerUrl` upgrades to WebSocket and routes a small JSON-RPC surface: `Browser.getVersion`, basic `Target` lifecycle, `Runtime.evaluate`, `Page.navigate`, `Page.getFrameTree`, `DOM.getDocument`, and no-op enable/input methods. Runtime values are V8-shaped CDP remote objects backed by the existing QuickJS page runtime; no V8 dependency is added.

This is enough for local protocol smoke tests and parity tracking. It is not enough to replace Chrome for Playwright/Puppeteer yet because sessions, isolated worlds, robust target/frame events, locator actionability, screenshots, tracing, downloads, and native layout/paint are still incomplete.

### Native SVG Paint

`paint` writes a native SVG approximation directly from the fetched page model:

```sh
zig build run -- paint https://example.com --out example.svg
zig build run -- paint https://quotes.toscrape.com/js/ --js --out quotes.svg
```

This does not call Kuri/CDP or Chrome. With `--js`, it executes the page in the QuickJS DOM shim, serializes `document.documentElement.outerHTML`, reparses that mutated DOM, and paints the serialized page. It is useful for fast, token-light visual context from page title, text, links, form controls, images, and code blocks. It is not CSS layout, raster screenshot, PDF, canvas, video, or pixel-equivalent rendering.

Check pixel parity against real Chrome before treating this as a renderer replacement:

```sh
zig build
python3 tools/paint_parity.py https://example.com --keep-artifacts
python3 tools/paint_parity.py https://example.com --direct-svg --keep-artifacts
python3 tools/paint_parity.py https://quotes.toscrape.com/js/ --paint-js --keep-artifacts
```

Current local Chrome comparison on `https://example.com` at `1280x720`:

- Chrome actual screenshot: `16,577` bytes
- Native SVG paint artifact: `758` bytes
- Native SVG rasterized through a no-margin HTML wrapper: `16,583` bytes
- Exact matching pixels through wrapper: `99.35%`
- Mean absolute RGB delta through wrapper: `0.48/255`
- Direct standalone SVG screenshot exact matching pixels: `87.27%`

So this is much closer for the simple `example.com` target, but it is still not 1:1. Exact pixel parity requires matching Chrome's layout, font shaping, antialiasing, viewport behavior, and raster pipeline, not just drawing similar SVG text.

Current local Hacker News comparison on `https://news.ycombinator.com` at `1280x720`:

- Chrome actual screenshot: `159,387` bytes
- Native SVG paint artifact: `10,127` bytes
- Native SVG rasterized through wrapper: `146,370` bytes
- Exact matching pixels through wrapper: `88.06%`
- Mean absolute RGB delta through wrapper: `10.58/255`

Current local JS-rendered page comparison on `https://quotes.toscrape.com/js/` with `--paint-js` at `1280x720`:

- Chrome actual screenshot: `71,989` bytes
- Native SVG paint artifact: `8,457` bytes
- Native SVG rasterized through wrapper: `68,496` bytes
- Exact matching pixels through wrapper: `90.32%`
- Mean absolute RGB delta through wrapper: `7.47/255`

### Screenshot Fallback

`kuri-browser` can capture screenshots through the existing Kuri/CDP renderer while full native layout and raster paint are still missing.

Start the normal Kuri server in another terminal:

```sh
cd ..
zig build
./zig-out/bin/kuri
```

Then run:

```sh
cd kuri-browser
zig build run -- screenshot https://example.com --out example.png --kuri-base http://127.0.0.1:8080
zig build run -- screenshot https://example.com --out example.jpg --compress --kuri-base http://127.0.0.1:8080
zig build run -- screenshot https://www.singaporeair.com/en_UK/sg/home#/book/bookflight --out sia.png --kuri-base http://127.0.0.1:8080 --desktop-user-agent --wait-ms 15000
```

`--compress` is token-oriented. It captures a PNG baseline, captures a JPEG candidate, keeps whichever file is smaller, fixes the output extension to match the selected format, and prints:

- `original-bytes`: PNG baseline size
- `bytes`: selected output size
- `saved-bytes`: byte delta versus PNG
- `saved-percent`: rounded percentage saved versus PNG

Current local measurement on `https://example.com`: PNG `20,523` bytes to JPEG quality 50 `18,183` bytes, saving `2,340` bytes or `11%`.

For heavier JS sites, `--wait-ms`, `--wait-selector`, `--wait-timeout-ms`, `--user-agent`, and `--desktop-user-agent` make the CDP fallback wait for late-rendered app shells before capture. This is still Chrome/CDP fallback behavior, not native Kuri layout.

### Readiness Checks

Use these commands to keep the experiment honest:

```sh
zig build test
zig build run -- parity --offline
zig build run -- bench --offline
zig build run -- bench --kuri-base http://127.0.0.1:8080
```

The current live bench is useful for tracking progress, but the answer is still "not ready to replace headless Chrome" until broader CDP browser domains, full native layout/raster paint, pixel-parity checks, and Playwright/Puppeteer lifecycle support exist.

## Target Direction

1. HTTP navigation, redirects, cookies, and resource loading
2. DOM tree construction and selector queries
3. Embedded JS runtime for page execution
4. Agent-facing snapshot/evaluate APIs
5. Broader CDP and Playwright/Puppeteer compatibility once the core runtime is stable
Loading
Loading