Skip to content

iter-43: Pre-release DX fixes (eval --file, --full-page, computed, summary, --wait-idle)#45

Merged
ractive merged 2 commits intomainfrom
iter-43/dx-fixes
Apr 16, 2026
Merged

iter-43: Pre-release DX fixes (eval --file, --full-page, computed, summary, --wait-idle)#45
ractive merged 2 commits intomainfrom
iter-43/dx-fixes

Conversation

@ractive
Copy link
Copy Markdown
Owner

@ractive ractive commented Apr 16, 2026

Summary

Last pre-release iteration. Addresses the concrete friction surfaced in the nova-template dogfooding session — each fix is scoped, cheap, and removes an "I kept reinventing this" reflex a first-time user would hit in minutes.

  • eval --file / --stdin — sidesteps shell-level quoting gotchas (optional chaining, template literals, multi-statement JS) that made users rewrite perfectly valid JS.
  • screenshot --full-page / --viewport-height — captures the whole scrollable document instead of just the first fold.
  • computed command — first-class wrapper around getComputedStyle(), the most-reused eval snippet in CSS debugging. Mirrors dom's multi-match shape ({selector, index, computed: {...}}).
  • console summary field — output now includes {total, matched, shown, by_level} so callers can tell at a glance whether the filter caught what they expected. --limit audited and confirmed working.
  • reload --wait-idle — blocks until no network activity for --idle-ms (default 500) or --timeout expires, replacing flaky sleep 5 hacks.
  • navigate long_about — clarifies that URL is positional; no --url flag exists.
  • Backlog entrieswait --console-includes and screenshot --annotate deferred with kb/backlog notes.

Docs: README.md command table + llm-help reference updated.

Test plan

  • cargo fmt --check clean
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo test --workspace all green (includes new e2e tests for each command)
  • eval new tests: from-file, from-stdin, missing-source error, conflicting-sources error
  • screenshot new tests: --full-page JS wiring, --viewport-height JS wiring, conflict error
  • computed new tests: single-match object, multi-match array, --prop scalar, no-match error, --jq filter, --prop + --all conflict
  • console new tests: summary present, --limit 10 on 110-message fixture yields shown=10 with total reflecting full count, matched reflects filter
  • reload --wait-idle tests: observes network events; returns quickly when idle

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added computed command (--prop / --all), richer eval input modes (--file, --stdin), screenshot --full-page / --viewport-height, and reload --wait-idle with idle/timeouts
    • Console output now includes a summary (total, matched, shown, per-level)
  • Tests

    • Added end-to-end tests and fixtures covering computed, eval modes, console summary, screenshot flags, and reload --wait-idle
  • Documentation

    • README and help text updated with new commands, options, and examples

…ommand (iter-43)

Addresses friction captured in dogfooding session:
- eval now accepts --file / --stdin to sidestep shell quoting issues
- screenshot --full-page / --viewport-height captures beyond the first fold
- new computed command wraps getComputedStyle for CSS debugging
- console output now includes a summary field (total, matched, shown, by_level)
- reload --wait-idle blocks until network quiescence, replacing sleep hacks
- navigate long_about clarifies URL is positional (no --url flag)
- backlog entries for wait --console-includes and screenshot --annotate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a629142-d890-4209-b198-34ebab41785c

📥 Commits

Reviewing files that changed from the base of the PR and between 4404b05 and 7692427.

📒 Files selected for processing (6)
  • crates/ff-rdp-cli/src/cli/args.rs
  • crates/ff-rdp-cli/src/commands/computed.rs
  • crates/ff-rdp-cli/src/commands/eval.rs
  • crates/ff-rdp-cli/src/commands/nav_action.rs
  • crates/ff-rdp-cli/src/commands/screenshot.rs
  • crates/ff-rdp-cli/tests/screenshot_e2e_test.rs

📝 Walkthrough

Walkthrough

Adds a new computed CLI subcommand; extends eval to accept positional/file/stdin sources; injects a summary object into console JSON output; enhances screenshot with --full-page and --viewport-height; adds reload --wait-idle network-idle monitoring; updates docs and adds extensive E2E tests and fixtures.

Changes

Cohort / File(s) Summary
CLI Argument Definitions
crates/ff-rdp-cli/src/cli/args.rs, crates/ff-rdp-cli/src/dispatch.rs
Command enum and dispatch updated: Eval accepts positional/file/stdin (ArgGroup enforced); Screenshot gains full_page and viewport_height; Reload gains wait_idle, idle_ms, reload_timeout; new Computed variant added; dispatch routes to new signatures.
New Computed Command
crates/ff-rdp-cli/src/commands/computed.rs, crates/ff-rdp-cli/src/commands/mod.rs
New computed implementation: generates JS to call getComputedStyle for selector, supports --prop vs --all with reference-element filtering, unwraps LongString actors, strips sentinel, parses JSON, formats output envelope.
Eval input handling
crates/ff-rdp-cli/src/commands/eval.rs, crates/ff-rdp-cli/tests/eval_e2e_test.rs
Refactored to load_script(script, file, use_stdin) and run accepts optional script/file/stdin; added unit tests and E2E tests for file/stdin/missing/conflict modes.
Console summary & tests
crates/ff-rdp-cli/src/commands/console.rs, crates/ff-rdp-cli/tests/console_e2e_test.rs, crates/ff-rdp-cli/tests/fixtures/get_cached_messages_large_response.json
Adds summary object (total, matched, shown, by_level) computed from cached messages and inserted into JSON envelope; E2E tests and large fixture added.
Screenshot enhancements
crates/ff-rdp-cli/src/commands/screenshot.rs, crates/ff-rdp-cli/tests/screenshot_e2e_test.rs
Replaced static JS with runtime build_screenshot_js and HeightOverride (default / full-page / explicit); run signature changed to accept ScreenshotOpts; added tests for JS generation and CLI conflict handling.
Reload wait-idle flow
crates/ff-rdp-cli/src/commands/nav_action.rs, crates/ff-rdp-cli/tests/nav_action_e2e_test.rs
Added run_reload_wait_idle that subscribes to watcher network-event, reloads, polls with short read timeout, counts resource batches, detects idle via elapsed time or EOF-like errors, and emits { reloaded, idle_at_ms, requests_observed }. E2E tests added.
Dispatch & wiring updates
crates/ff-rdp-cli/src/dispatch.rs
Dispatch arms updated to call new command signatures and options (computed, eval sources, screenshot opts, reload wait-idle path).
Docs & LLM help
README.md, crates/ff-rdp-cli/src/commands/llm_help.rs
Documentation updated to list new subcommands/options and examples (computed, eval --file/--stdin, console --limit, screenshot --full-page, reload --wait-idle).
E2E tests & fixtures
crates/ff-rdp-cli/tests/computed_e2e_test.rs, crates/ff-rdp-cli/tests/..., crates/ff-rdp-cli/tests/fixtures/eval_result_computed_*.json
Comprehensive tests added for computed (single/multi/prop/empty), console summary behavior, eval input modes, reload wait-idle, and screenshot flags; JSON fixtures added for computed scenarios.
Backlog docs
kb/backlog/screenshot-annotate.md, kb/backlog/wait-console-includes.md
New backlog/requirements docs describing future --annotate and wait --console-includes features.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI (ff-rdp)
    participant RDP as RDP Server
    participant Firefox as Firefox Tab

    CLI->>RDP: connect & authenticate
    CLI->>RDP: getTarget()
    RDP-->>CLI: target actor
    CLI->>Firefox: eval computed-styles JS (selector + --prop/--all)
    Firefox->>Firefox: query matching elements and compute styles
    Firefox-->>RDP: evaluation result (JSON sentinel + payload)
    RDP-->>CLI: result payload
    CLI->>CLI: strip sentinel & parse JSON
    CLI->>CLI: format output envelope
    CLI-->>User: JSON output
Loading
sequenceDiagram
    participant CLI as CLI (ff-rdp)
    participant FS as File System
    participant RDP as RDP Server
    participant Firefox as Firefox Tab

    CLI->>CLI: parse args (--file mode)
    CLI->>FS: read script file
    FS-->>CLI: JS source
    CLI->>RDP: connect & getTarget()
    CLI->>Firefox: eval(loaded JS)
    Firefox-->>RDP: evaluation result
    RDP-->>CLI: payload
    CLI->>CLI: parse & format output
    CLI-->>User: JSON result
Loading
sequenceDiagram
    participant CLI as CLI (ff-rdp)
    participant RDP as RDP Server
    participant Watcher as Watcher Actor
    participant Firefox as Firefox Tab

    CLI->>RDP: connect & getWatcher()
    RDP-->>CLI: watcher actor
    CLI->>Watcher: watchResources("network-event")
    CLI->>Firefox: reload()
    loop poll until idle or timeout
        Watcher-->>CLI: network-event batches or EOF
        CLI->>CLI: accumulate requests_observed
        CLI->>CLI: check idle_ms since last activity
    end
    CLI->>Watcher: unwatch_resources
    CLI->>CLI: emit {reloaded, idle_at_ms, requests_observed}
    CLI-->>User: JSON output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through selectors, one, two, three,

Files, stdin, or posargs set eval free,
Console counts the mess; screenshots stretch tall,
Reload waits for quiet after network's call,
I nibble tests and fixtures—cheers to thee! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically summarizes the main changes: iter-43 pre-release DX fixes with clear enumeration of the five key features (eval --file, --full-page, computed, summary, --wait-idle). It directly reflects the core changes in the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch iter-43/dx-fixes

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
kb/backlog/wait-console-includes.md (1)

25-25: Use the existing wait timeout flag name to avoid spec drift.

Line 25 says --timeout, but current wait implementation uses --wait-timeout (crates/ff-rdp-cli/src/commands/wait.rs:11-55). This mismatch can cause implementation/docs inconsistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kb/backlog/wait-console-includes.md` at line 25, The docs mention the flag
name `--timeout` but the actual `wait` command uses `--wait-timeout`; update the
markdown to reference the existing `--wait-timeout` flag (or alternatively
update the `wait` command's argument parsing in wait.rs to accept `--timeout`)
so docs and implementation match; look for the `wait` command definition and the
`--wait-timeout` argument in wait.rs (and any associated variable like
`wait_timeout`) and make the doc change to `--wait-timeout`.
crates/ff-rdp-cli/tests/screenshot_e2e_test.rs (1)

631-662: Assert output artifact for --viewport-height success path.

This test currently checks process success only. Add a file existence assertion (like the --full-page test) so regressions in file writing are caught.

Suggested test hardening
     assert!(
         output.status.success(),
         "expected success, stderr: {}",
         String::from_utf8_lossy(&output.stderr)
     );
+    assert!(out_path.exists(), "PNG file should have been written");
 
     let _ = std::fs::remove_dir_all(&out_dir);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/ff-rdp-cli/tests/screenshot_e2e_test.rs` around lines 631 - 662, The
test screenshot_viewport_height_flag_accepted currently only asserts the ff-rdp
process exit status; after calling handle.join() and the success assertion, add
an assertion that the expected output file (out_path) exists and is a regular
file (e.g., check std::path::Path::exists()/is_file() on out_path) to mirror the
--full-page test and catch regressions in file writing, then keep the existing
cleanup (std::fs::remove_dir_all(&out_dir)).
crates/ff-rdp-cli/tests/nav_action_e2e_test.rs (1)

74-81: Avoid relying on unknownMethod for reload in success-path tests.

Right now the wait-idle tests pass while reload returns an error from the mock. That can hide regressions in actual reload invocation. Prefer registering a normal "reload" response in this server helper for the success scenarios.

Also applies to: 83-157

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/ff-rdp-cli/tests/nav_action_e2e_test.rs` around lines 74 - 81, The
mock server helper currently omits a successful "reload" handler and relies on
the mock returning "unknownMethod" (which masks real regressions); fix by
registering a proper reload response using the same pattern as the existing
handler (e.g., add .on("reload", load_fixture("reload_response.json")) before
.close_after_followups() in the helper where "unwatchResources" is registered),
and apply the same change to the other similar server-helper blocks in this test
file so all success-path tests have an explicit "reload" fixture response.
crates/ff-rdp-cli/tests/computed_e2e_test.rs (1)

179-190: Please add one positive --all E2E case.

Right now --all is only covered as a conflict path. A happy-path ff-rdp computed ... --all test would catch regressions in clap parsing, dispatch, and output shaping for the new flag.

As per coding guidelines, **/*.rs: Make Rust code unit testable and add tests when feasible; add e2e tests for all commands/subcommands.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/ff-rdp-cli/tests/computed_e2e_test.rs` around lines 179 - 190, Add a
new positive end-to-end test alongside computed_prop_and_all_conflict that
invokes ff_rdp_bin() with args ["computed", "h1", "--all"], asserts the process
exits successfully, and verifies the stdout contains the expected computed
properties (at minimum check for a known field like "color" or another
deterministic property from the fixture); name the test something like
computed_all_happy_path or computed_prop_all_ok and reuse the same style of
spawning and output inspection as the existing test to cover clap parsing,
dispatch, and output shaping for the --all flag.
crates/ff-rdp-cli/tests/eval_e2e_test.rs (1)

194-276: Assert the submitted JS, not just the JSON envelope.

These two tests only prove that eval --file / eval --stdin reach the handler and produce a normal response. They do not verify that the file/stdin contents are what gets sent to evaluateJSAsync, so a regression that drops or replaces the loaded source could still pass. If MockRdpServer can inspect request params, please assert the evaluated script matches the temp-file/stdin payload.

As per coding guidelines, **/*.rs: Make Rust code unit testable and add tests when feasible; add e2e tests for all commands/subcommands.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/ff-rdp-cli/tests/eval_e2e_test.rs` around lines 194 - 276, The tests
eval_from_file and eval_from_stdin only check the JSON envelope; update them to
also assert the actual JS sent to the RDP handler (evaluateJSAsync) matches the
written file/stdin string: modify eval_server/MockRdpServer (or its returned
server handle) to capture the incoming request params (the evaluated script),
expose a method or field like last_evaluated_script or a get_last_request() on
the server returned by eval_server, and after handle.join().unwrap() assert that
last_evaluated_script == "getComputedStyle(document.body)?.display" (for the
file test) and similarly for the stdin test (include the newline if written).
Use the existing eval_server, MockRdpServer, and evaluateJSAsync identifiers to
locate and hook into the request handling logic.
crates/ff-rdp-cli/src/commands/computed.rs (1)

152-164: Avoid cloning entries here.

entries is owned in this scope, so both entries.clone() calls are avoidable. Compute total first, then move entries into the final Value; that keeps this path aligned with the repo’s no-unnecessary-clone rule.

As per coding guidelines, **/*.rs: No clone() unless the borrow checker demands it in Rust — try references first.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/ff-rdp-cli/src/commands/computed.rs` around lines 152 - 164, Compute
total = entries.len() up front and then move entries instead of cloning: replace
the entries.clone() usages and entries[0].clone() by using
entries.into_iter().next().unwrap() for the single-item cases (so you can call
.get("value").cloned().unwrap_or(Value::Null) on the moved first element when
prop.is_some(), or return the moved element directly when prop.is_none()), and
use Value::Array(entries) for the multi-item branches; this removes the
unnecessary clones while preserving the same branching logic involving prop and
results.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/ff-rdp-cli/src/commands/eval.rs`:
- Around line 149-152: The test load_script_no_source_errors is currently
discarding the boolean result of matches!(err, AppError::User(_)); update the
assertion to actually assert the match (e.g., wrap matches! in assert! or use
assert!(matches!(...))) so that load_script(None, None, false).unwrap_err() is
verified to be an AppError::User; reference the test function
load_script_no_source_errors and the load_script call to locate the change.

---

Nitpick comments:
In `@crates/ff-rdp-cli/src/commands/computed.rs`:
- Around line 152-164: Compute total = entries.len() up front and then move
entries instead of cloning: replace the entries.clone() usages and
entries[0].clone() by using entries.into_iter().next().unwrap() for the
single-item cases (so you can call .get("value").cloned().unwrap_or(Value::Null)
on the moved first element when prop.is_some(), or return the moved element
directly when prop.is_none()), and use Value::Array(entries) for the multi-item
branches; this removes the unnecessary clones while preserving the same
branching logic involving prop and results.

In `@crates/ff-rdp-cli/tests/computed_e2e_test.rs`:
- Around line 179-190: Add a new positive end-to-end test alongside
computed_prop_and_all_conflict that invokes ff_rdp_bin() with args ["computed",
"h1", "--all"], asserts the process exits successfully, and verifies the stdout
contains the expected computed properties (at minimum check for a known field
like "color" or another deterministic property from the fixture); name the test
something like computed_all_happy_path or computed_prop_all_ok and reuse the
same style of spawning and output inspection as the existing test to cover clap
parsing, dispatch, and output shaping for the --all flag.

In `@crates/ff-rdp-cli/tests/eval_e2e_test.rs`:
- Around line 194-276: The tests eval_from_file and eval_from_stdin only check
the JSON envelope; update them to also assert the actual JS sent to the RDP
handler (evaluateJSAsync) matches the written file/stdin string: modify
eval_server/MockRdpServer (or its returned server handle) to capture the
incoming request params (the evaluated script), expose a method or field like
last_evaluated_script or a get_last_request() on the server returned by
eval_server, and after handle.join().unwrap() assert that last_evaluated_script
== "getComputedStyle(document.body)?.display" (for the file test) and similarly
for the stdin test (include the newline if written). Use the existing
eval_server, MockRdpServer, and evaluateJSAsync identifiers to locate and hook
into the request handling logic.

In `@crates/ff-rdp-cli/tests/nav_action_e2e_test.rs`:
- Around line 74-81: The mock server helper currently omits a successful
"reload" handler and relies on the mock returning "unknownMethod" (which masks
real regressions); fix by registering a proper reload response using the same
pattern as the existing handler (e.g., add .on("reload",
load_fixture("reload_response.json")) before .close_after_followups() in the
helper where "unwatchResources" is registered), and apply the same change to the
other similar server-helper blocks in this test file so all success-path tests
have an explicit "reload" fixture response.

In `@crates/ff-rdp-cli/tests/screenshot_e2e_test.rs`:
- Around line 631-662: The test screenshot_viewport_height_flag_accepted
currently only asserts the ff-rdp process exit status; after calling
handle.join() and the success assertion, add an assertion that the expected
output file (out_path) exists and is a regular file (e.g., check
std::path::Path::exists()/is_file() on out_path) to mirror the --full-page test
and catch regressions in file writing, then keep the existing cleanup
(std::fs::remove_dir_all(&out_dir)).

In `@kb/backlog/wait-console-includes.md`:
- Line 25: The docs mention the flag name `--timeout` but the actual `wait`
command uses `--wait-timeout`; update the markdown to reference the existing
`--wait-timeout` flag (or alternatively update the `wait` command's argument
parsing in wait.rs to accept `--timeout`) so docs and implementation match; look
for the `wait` command definition and the `--wait-timeout` argument in wait.rs
(and any associated variable like `wait_timeout`) and make the doc change to
`--wait-timeout`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6025acf6-494b-452c-b02e-1c5621725891

📥 Commits

Reviewing files that changed from the base of the PR and between 0870fa6 and 4404b05.

📒 Files selected for processing (22)
  • README.md
  • crates/ff-rdp-cli/src/cli/args.rs
  • crates/ff-rdp-cli/src/commands/computed.rs
  • crates/ff-rdp-cli/src/commands/console.rs
  • crates/ff-rdp-cli/src/commands/eval.rs
  • crates/ff-rdp-cli/src/commands/llm_help.rs
  • crates/ff-rdp-cli/src/commands/mod.rs
  • crates/ff-rdp-cli/src/commands/nav_action.rs
  • crates/ff-rdp-cli/src/commands/screenshot.rs
  • crates/ff-rdp-cli/src/dispatch.rs
  • crates/ff-rdp-cli/tests/computed_e2e_test.rs
  • crates/ff-rdp-cli/tests/console_e2e_test.rs
  • crates/ff-rdp-cli/tests/eval_e2e_test.rs
  • crates/ff-rdp-cli/tests/fixtures/eval_result_computed_empty.json
  • crates/ff-rdp-cli/tests/fixtures/eval_result_computed_multi.json
  • crates/ff-rdp-cli/tests/fixtures/eval_result_computed_prop.json
  • crates/ff-rdp-cli/tests/fixtures/eval_result_computed_single.json
  • crates/ff-rdp-cli/tests/fixtures/get_cached_messages_large_response.json
  • crates/ff-rdp-cli/tests/nav_action_e2e_test.rs
  • crates/ff-rdp-cli/tests/screenshot_e2e_test.rs
  • kb/backlog/screenshot-annotate.md
  • kb/backlog/wait-console-includes.md

Comment thread crates/ff-rdp-cli/src/cli/args.rs
Comment thread crates/ff-rdp-cli/src/commands/eval.rs
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR focuses on pre-release developer-experience improvements to ff-rdp by adding safer input modes, new debugging commands, richer console output metadata, and more reliable navigation waits, with corresponding docs and e2e/fixture coverage.

Changes:

  • Add new CLI capabilities: eval --file/--stdin, screenshot --full-page/--viewport-height, new computed command, and reload --wait-idle.
  • Extend console output with a summary field (total/matched/shown/by_level) and add fixtures/tests to validate behavior.
  • Update CLI help (llm-help), README command list/examples, and add backlog notes for deferred features.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
kb/backlog/wait-console-includes.md Adds backlog note/spec for wait --console-includes.
kb/backlog/screenshot-annotate.md Adds backlog note/spec for screenshot --annotate.
crates/ff-rdp-cli/tests/screenshot_e2e_test.rs Adds e2e coverage for screenshot height modes and conflicts.
crates/ff-rdp-cli/tests/nav_action_e2e_test.rs Adds e2e coverage for reload --wait-idle.
crates/ff-rdp-cli/tests/fixtures/get_cached_messages_large_response.json Adds large console fixture used to validate limit/summary semantics.
crates/ff-rdp-cli/tests/fixtures/eval_result_computed_single.json Adds computed-style eval fixture (single match).
crates/ff-rdp-cli/tests/fixtures/eval_result_computed_prop.json Adds computed-style eval fixture (--prop).
crates/ff-rdp-cli/tests/fixtures/eval_result_computed_multi.json Adds computed-style eval fixture (multi match).
crates/ff-rdp-cli/tests/fixtures/eval_result_computed_empty.json Adds computed-style eval fixture (no matches).
crates/ff-rdp-cli/tests/eval_e2e_test.rs Adds e2e coverage for eval --file/--stdin and argument validation.
crates/ff-rdp-cli/tests/console_e2e_test.rs Adds e2e coverage for console.summary totals/limit/filter behavior.
crates/ff-rdp-cli/tests/computed_e2e_test.rs Adds e2e coverage for new computed command.
crates/ff-rdp-cli/src/dispatch.rs Wires new CLI flags/subcommands into dispatch.
crates/ff-rdp-cli/src/commands/screenshot.rs Implements screenshot height overrides via generated JS + opts struct.
crates/ff-rdp-cli/src/commands/nav_action.rs Implements run_reload_wait_idle network-idle drain loop.
crates/ff-rdp-cli/src/commands/mod.rs Exposes new computed module.
crates/ff-rdp-cli/src/commands/llm_help.rs Updates LLM-friendly help text for new flags/commands and console summary.
crates/ff-rdp-cli/src/commands/eval.rs Adds load_script supporting positional/--file/--stdin.
crates/ff-rdp-cli/src/commands/console.rs Adds summary field computation and insertion into output envelope.
crates/ff-rdp-cli/src/commands/computed.rs Adds computed command implementation (JS builder + parsing + output shaping).
crates/ff-rdp-cli/src/cli/args.rs Adds CLI surface for new flags/commands and updates long help strings.
README.md Updates command list and usage examples for new features.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +99 to +112
let mut requests_observed: u64 = 0;
let mut last_event_at = Instant::now();

loop {
// Check total timeout first.
if start.elapsed() >= total_deadline {
break;
}

// Check idle threshold: if we've had at least one request and the last
// event was more than idle_ms ago, declare idle.
if requests_observed > 0 && last_event_at.elapsed() >= idle_threshold {
break;
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--wait-idle should return once there has been no network activity for idle_ms, even if that means “no events ever arrived after reload”. The current requests_observed > 0 gate prevents the idle condition from ever triggering in the no-traffic case (it will wait until EOF/connection close or the total timeout). Consider initializing last_event_at = start and removing the requests_observed > 0 requirement so an empty-traffic reload can still complete after idle_ms.

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +133
if msg_type == "resources-available-array" || msg_type == "resources-updated-array"
{
// Count each individual network resource in the batch.
let count = msg
.get("array")
.and_then(serde_json::Value::as_array)
.map_or(1, |arr| {
arr.iter()
.filter_map(|pair| pair.as_array())
.filter_map(|p| p.get(1))
.filter_map(serde_json::Value::as_array)
.map(Vec::len)
.sum::<usize>()
}) as u64;
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When counting network resources, map_or(1, ...) will add 1 request if a resources-*-array message is missing the array field (or has an unexpected shape). That inflates requests_observed and can make the idle logic behave incorrectly. Defaulting this to 0 (or ignoring the message when array is absent) is safer.

Copilot uses AI. Check for mistakes.
Comment on lines +349 to +370
#[command(long_about = "Reload the page.

With --wait-idle, the command blocks after reload until network activity has been
idle for --idle-ms (default 500) or the --timeout expires (default 10000).

Examples:
ff-rdp reload
ff-rdp reload --wait-idle
ff-rdp reload --wait-idle --idle-ms 1000 --timeout 30000

Output (plain): {\"results\": {\"action\": \"reload\"}, \"total\": 1, \"meta\": {...}}
Output (wait-idle): {\"results\": {\"reloaded\": true, \"idle_at_ms\": N, \"requests_observed\": M}, \"total\": 1, \"meta\": {...}}")]
Reload {
/// Block until network is idle after reload
#[arg(long)]
wait_idle: bool,
/// Milliseconds of network inactivity that counts as idle (--wait-idle only)
#[arg(long, default_value_t = 500)]
idle_ms: u64,
/// Maximum total milliseconds to wait for idle (--wait-idle only)
#[arg(long, default_value_t = 10000)]
reload_timeout: u64,
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reload long_help text and example reference a --timeout flag, but this subcommand defines --reload-timeout (and --timeout is a global option with different semantics). Update the help text/examples to use --reload-timeout to avoid misleading users.

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +47
fn build_js(selector: &str, prop: Option<&str>, include_all: bool) -> String {
let escaped_sel = escape_selector(selector);

if let Some(p) = prop {
// Escape the property name the same way as the selector — both end up
// inside single-quoted JS literals.
let escaped_prop = escape_selector(p);
return format!(
r"(function() {{
var els = document.querySelectorAll('{escaped_sel}');
var out = [];
for (var i = 0; i < els.length; i++) {{
var cs = getComputedStyle(els[i]);
out.push({{selector: '{escaped_sel}', index: i, value: cs.getPropertyValue('{escaped_prop}') || cs['{escaped_prop}'] || ''}});
}}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In --prop mode the output embeds selector: '{escaped_sel}', which means selectors containing ' will be returned with escape backslashes (e.g. div[data-x=\'y\']) rather than the exact selector the user passed. Since other commands (e.g. geometry/responsive) preserve the original selector string in output, consider keeping a separate “display selector” literal (e.g. JSON-encoded) for the selector field while still using the escaped value only for querySelectorAll.

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +77
document.body.appendChild(ref);
var rcs = getComputedStyle(ref);
var obj = {};
for (var j = 0; j < cs.length; j++) {
var name = cs[j];
var v = cs.getPropertyValue(name);
if (rcs.getPropertyValue(name) !== v) {
obj[name] = v;
}
}
ref.remove();
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The non-default filtering path assumes document.body exists (document.body.appendChild(ref)), which will throw on documents without a body (e.g. XML documents or early in navigation). Use a safer container (e.g. document.body || document.documentElement) or skip the default-diff optimization when there’s no body to attach to.

Suggested change
document.body.appendChild(ref);
var rcs = getComputedStyle(ref);
var obj = {};
for (var j = 0; j < cs.length; j++) {
var name = cs[j];
var v = cs.getPropertyValue(name);
if (rcs.getPropertyValue(name) !== v) {
obj[name] = v;
}
}
ref.remove();
var container = document.body || document.documentElement;
var rcs = null;
if (container) {
container.appendChild(ref);
rcs = getComputedStyle(ref);
}
var obj = {};
for (var j = 0; j < cs.length; j++) {
var name = cs[j];
var v = cs.getPropertyValue(name);
if (!rcs || rcs.getPropertyValue(name) !== v) {
obj[name] = v;
}
}
if (container) {
ref.remove();
}

Copilot uses AI. Check for mistakes.
Comment thread crates/ff-rdp-cli/src/commands/eval.rs Outdated
Comment on lines +41 to +43
return std::fs::read_to_string(path)
.with_context(|| format!("eval: could not read script file '{path}'"))
.map_err(AppError::from);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_script maps file read failures through anyhow into AppError::Internal, which makes a missing/unreadable --file path exit with code 2 and print internal error:. This is user input/path/permission failure and should be surfaced as AppError::User (exit 1) with the same helpful message.

Suggested change
return std::fs::read_to_string(path)
.with_context(|| format!("eval: could not read script file '{path}'"))
.map_err(AppError::from);
return std::fs::read_to_string(path).map_err(|e| {
AppError::User(format!("eval: could not read script file '{path}': {e}"))
});

Copilot uses AI. Check for mistakes.
Comment thread crates/ff-rdp-cli/src/commands/eval.rs Outdated
#[test]
fn load_script_no_source_errors() {
let err = load_script(None, None, false).unwrap_err();
matches!(err, AppError::User(_));
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test currently computes matches!(err, AppError::User(_)) but doesn’t assert the result, so it will pass even if load_script returns the wrong error variant. Wrap this in assert!(matches!(...)) (or equivalent) so the invariant is actually tested.

Suggested change
matches!(err, AppError::User(_));
assert!(matches!(err, AppError::User(_)));

Copilot uses AI. Check for mistakes.
- Fix discarded matches!() in load_script test (now assert!)
- Map file-read errors to AppError::User instead of Internal
- Reduce ScreenshotOpts visibility to pub(crate)
- Eliminate unnecessary clones in computed.rs by moving total above match
- Rename JS var ref to refEl (reserved word) and guard document.body
- Remove requests_observed>0 gate so zero-traffic pages exit after idle_ms
- Use map_or(0,...) for missing array field and guard last_event_at update
- Fix help text: --timeout → --reload-timeout in reload examples
- Add requires="wait_idle" constraint on --idle-ms and --reload-timeout
- Assert PNG file exists in screenshot_viewport_height test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ractive ractive merged commit 8de495b into main Apr 16, 2026
4 of 6 checks passed
@ractive ractive deleted the iter-43/dx-fixes branch April 16, 2026 05:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants