You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Wire ie-net into the headless execution path so that --headless --dump-source --url <URL> actually fetches the page and prints it. Also implement the interactive headless JSON protocol that e2e tests will use.
This is the Phase 1a milestone: the first time the browser does something useful end-to-end.
Prerequisites
Step 2 (ie-net — working HTTP client)
Step 3 (headless mode and CLI)
Step 4 (navigation service, tab model, bookmarks)
File Changes
src/headless.rs — replace placeholder implementations with real logic
Add serde/serde_json to ie-shell deps if not already present
Implementation
One-shot headless commands (headless.rs)
DumpSource:
let navigator = InProcessNavigator::new()?;let result = navigator.navigate(&url).await?;let text = String::from_utf8_lossy(&result.body);print!("{text}");
Exit code 0 on success
On navigation error: print error to stderr, exit code 1
DumpStatus:
let navigator = InProcessNavigator::new()?;let result = navigator.navigate(&url).await?;println!("{}", result.status);
On success: check content-type — if not text/html (and content-type is set), respond with {"ok": false, "error": "unsupported content type: ..."} and set tab state to Error
On success with valid content-type: update tab (url, state=Loaded, source=body as UTF-8), respond {"ok": true, "data": {"status": 200, "url": "..."}}
On failure: update tab (state=Error), respond {"ok": false, "error": "..."}
get_source:
Return active tab's source or error if no source/no active tab
{"ok": true, "data": "<html>..."}
get_tabs:
Return list of tabs: {"ok": true, "data": [{"id": 1, "url": "...", "title": "...", "state": "loaded"}]}
State serialized as lowercase string: "blank", "loading", "loaded", "error"
new_tab:
tab_manager.new_tab()
{"ok": true, "data": {"id": 2}}
close_tab:
tab_manager.close_tab(id)
{"ok": true} or {"ok": false, "error": "tab not found"}
switch_tab:
tab_manager.switch_to(TabId(id))
{"ok": true} or {"ok": false, "error": "tab not found"}
go_back:
tab_manager.go_back()
{"ok": true, "data": {"url": "..."}} if navigated back, {"ok": false, "error": "no back history"} if at beginning
go_forward:
tab_manager.go_forward()
{"ok": true, "data": {"url": "..."}} if navigated forward, {"ok": false, "error": "no forward history"} if at end
On malformed JSON input: respond with {"ok": false, "error": "invalid command: ..."} and continue (don't crash)
On EOF (stdin closed): exit cleanly
Sequential command processing
Commands are processed one at a time. navigate blocks until the HTTP request completes before the next command is read. This is by design for Phase 1 simplicity — it makes the protocol deterministic and easy to test. Document this constraint in code comments.
Future optimization: allow concurrent navigations with request IDs for correlation.
--allow-http flag support
The --allow-http flag from CLI is passed through to the InProcessNavigator:
let navigator = InProcessNavigator::new()?.with_https_only(!cli.allow_http);
This affects both one-shot commands (DumpSource, DumpStatus) and interactive mode
Bookmark store path for headless
Interactive headless mode: use a unique temp directory for bookmark storage by default (so tests don't pollute real bookmarks)
Add --data-dir <path> CLI flag to override data directory (useful for e2e tests that need persistence across restarts)
Verification
# Fetch a page and dump its source:
cargo run -p ie-shell -- --headless --dump-source --url https://example.com
# Should print the HTML of example.com# Get just the status code:
cargo run -p ie-shell -- --headless --dump-status --url https://example.com
# Should print: 200# Interactive mode:echo'{"cmd":"navigate","url":"https://example.com"}{"cmd":"get_source"}{"cmd":"quit"}'| cargo run -p ie-shell -- --headless
# Should print JSON responses for each command# Error case:
cargo run -p ie-shell -- --headless --dump-status --url https://nonexistent.invalid
# Should print error to stderr, exit code 1
Tests
Integration tests (in crates/ie-shell/tests/)
These spawn the binary as a subprocess and test the full pipeline:
dump_status_200: start local test server returning 200. Run --headless --dump-status --url http://127.0.0.1:PORT --allow-http. Assert stdout is "200\n" and exit code 0.
dump_status_404: server returns 404. Assert stdout is "404\n".
dump_source_matches: server returns <html>test</html>. Run --headless --dump-source --allow-http. Assert stdout matches.
interactive_quit: send quit, verify process exits cleanly.
Test helper
Reusable test server helper: fn start_test_server() -> (SocketAddr, impl Drop) — HTTP server returning configurable responses, automatically stopped on drop
Acceptance Criteria
cargo run -p ie-shell -- --headless --dump-source --url https://example.com prints HTML
cargo run -p ie-shell -- --headless --dump-status --url https://example.com prints 200
Interactive headless mode handles all command types including switch_tab, go_back, go_forward
Malformed input doesn't crash the interactive session
All integration tests pass
cargo clippy -p ie-shell -- -D warnings — no warnings
Parent: #2
Goal
Wire ie-net into the headless execution path so that
--headless --dump-source --url <URL>actually fetches the page and prints it. Also implement the interactive headless JSON protocol that e2e tests will use.This is the Phase 1a milestone: the first time the browser does something useful end-to-end.
Prerequisites
File Changes
src/headless.rs— replace placeholder implementations with real logicserde/serde_jsonto ie-shell deps if not already presentImplementation
One-shot headless commands (
headless.rs)DumpSource:DumpStatus:Interactive headless mode — JSON protocol (
headless.rs)Command handlers
navigate:https://if no scheme)navigator.navigate(&url).awaittext/html(and content-type is set), respond with{"ok": false, "error": "unsupported content type: ..."}and set tab state to Error{"ok": true, "data": {"status": 200, "url": "..."}}{"ok": false, "error": "..."}get_source:{"ok": true, "data": "<html>..."}get_tabs:{"ok": true, "data": [{"id": 1, "url": "...", "title": "...", "state": "loaded"}]}new_tab:tab_manager.new_tab(){"ok": true, "data": {"id": 2}}close_tab:tab_manager.close_tab(id){"ok": true}or{"ok": false, "error": "tab not found"}switch_tab:tab_manager.switch_to(TabId(id)){"ok": true}or{"ok": false, "error": "tab not found"}go_back:tab_manager.go_back(){"ok": true, "data": {"url": "..."}}if navigated back,{"ok": false, "error": "no back history"}if at beginninggo_forward:tab_manager.go_forward(){"ok": true, "data": {"url": "..."}}if navigated forward,{"ok": false, "error": "no forward history"}if at endbookmark_add:bookmark_store.add(url, title){"ok": true}bookmark_list:{"ok": true, "data": [{"url": "...", "title": "...", "created": "..."}]}quit:{"ok": true}, then break the read loop and exitMain read loop
run_interactive_headless():{"ok": false, "error": "invalid command: ..."}and continue (don't crash)Sequential command processing
Commands are processed one at a time.
navigateblocks until the HTTP request completes before the next command is read. This is by design for Phase 1 simplicity — it makes the protocol deterministic and easy to test. Document this constraint in code comments.Future optimization: allow concurrent navigations with request IDs for correlation.
--allow-httpflag support--allow-httpflag from CLI is passed through to theInProcessNavigator:DumpSource,DumpStatus) and interactive modeBookmark store path for headless
--data-dir <path>CLI flag to override data directory (useful for e2e tests that need persistence across restarts)Verification
Tests
Integration tests (in
crates/ie-shell/tests/)These spawn the binary as a subprocess and test the full pipeline:
dump_status_200: start local test server returning 200. Run--headless --dump-status --url http://127.0.0.1:PORT --allow-http. Assert stdout is "200\n" and exit code 0.dump_status_404: server returns 404. Assert stdout is "404\n".dump_source_matches: server returns<html>test</html>. Run--headless --dump-source --allow-http. Assert stdout matches.dump_source_error: URL points to nothing. Assert exit code 1, stderr non-empty.interactive_navigate: spawn--headless --allow-http, send navigate command, send get_source, send quit. Verify navigate response has"ok": true, get_source returns the page body.interactive_tabs: spawn, send new_tab, get_tabs (expect 2), close_tab, get_tabs (expect 1).interactive_switch_tab: spawn, new_tab, switch_tab to first tab, get_tabs to verify active tab changed.interactive_go_back_forward: spawn, navigate to A, navigate to B, go_back (expect A), go_forward (expect B).interactive_bookmarks: spawn, navigate, bookmark_add, bookmark_list (expect 1 entry).interactive_invalid_json: send garbage line, verify error response (not crash), then send valid quit command.interactive_quit: send quit, verify process exits cleanly.Test helper
fn start_test_server() -> (SocketAddr, impl Drop)— HTTP server returning configurable responses, automatically stopped on dropAcceptance Criteria
cargo run -p ie-shell -- --headless --dump-source --url https://example.comprints HTMLcargo run -p ie-shell -- --headless --dump-status --url https://example.comprints200switch_tab,go_back,go_forwardcargo clippy -p ie-shell -- -D warnings— no warnings