Skip to content

iter-141: output hygiene — text padding, invalid JSON, snapshot economics - #180

Merged
ractive merged 4 commits into
mainfrom
iter-141/output-hygiene
Aug 11, 2026
Merged

iter-141: output hygiene — text padding, invalid JSON, snapshot economics#180
ractive merged 4 commits into
mainfrom
iter-141/output-hygiene

Conversation

@ractive

@ractive ractive commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • Theme A: --format text table rendering now middle-ellipsizes every cell (not just url columns) — fixes the 255 KB / 8725-column blowup a single long console message caused.
  • Theme B: index now calls navigate::run_core (non-printing) instead of the printing navigate::run, so stdout is exactly one JSON document; its robots.txt parser now respects User-agent: grouping (a foreign agent's Disallow: / no longer blocks a generic crawl); outgoing links are deduped against an enqueued set.
  • Theme C: snapshot's meta now reports truncated/text_truncated explicitly instead of a marker buried inside results; --max-chars help text now describes the actual (whole-tree) bound.
  • Theme D: an empty --format text result array now surfaces sampled/capped context instead of printing a bare [].
  • Theme E: JS exceptions during dom/click/wait/etc. (e.g. an invalid CSS selector) are now routed through the standard {"error":…,"error_type":"User"} JSON envelope instead of a bare error: ... line on stderr with no JSON at all.
  • Theme F: OutputPipeline::finalize_with_hints now returns AppError directly so jq syntax/compile/runtime errors and --jq-strict missing-path errors classify as error_type: "User", not "Internal"; network's slowest (top-20) list now carries an explicit slowest_truncated marker.
  • Deferred (cosmetic, no misleading data): sources' actor: "", --fields silently dropping unknown names, cookies/storage text-mode formatting polish, network --format text vs --detail total reconciliation — see the iteration plan's "Theme F disposition" note.

Test plan

  • cargo fmt --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace -q (860+ unit/e2e tests green)
  • cargo run -p xtask -- check-iteration-ready --plan kb/iterations/iteration-141-output-hygiene.md --base origin/main — 10/10 PASS
  • New unit tests: output_pipeline.rs (cell-bounding, empty-results), index.rs (robots.txt UA grouping, enqueue dedup), snapshot.rs (snapshot_truncation_flags), network.rs (slowest_truncated)
  • New e2e tests (mock RDP server): e2e_invalid_selector_json_envelope, e2e_network_truncation_flag, e2e_jq_error_type_is_user, e2e_jq_strict_missing_path_error_type_is_user
  • New live tests (gated FF_RDP_LIVE_TESTS=1, daemon-mode default per iter-137 discipline): live_141_console_text_bounded, live_141_index_single_json_document, live_141_index_robots_user_agent_groups, live_141_snapshot_truncation_in_meta, live_141_text_empty_result_keeps_metadata

🤖 Generated with Claude Code## Claims vs code
<generated 2026-08-11T11:53:31Z by ralph-loop>

  • an → ✅ matched in diff
  • AppError::User → ✅ matched in diff

Review (review-pr, local-only)Local diff review (no Copilot — invoked with local-only). Found one issue:

# File:Line Issue Source
1 crates/ff-rdp-cli/src/commands/eval.rs:351 eval's own inline JS-exception handler still printed error: ... to stderr and returned AppError::Exit(1), bypassing the JSON error envelope entirely — the exact same anti-pattern Theme E fixed in eval_or_bail/poll_js_condition, just not applied to eval.rs itself. Local

Fixed: routed through Err(AppError::User(...)), matching the pattern already established elsewhere in this PR; updated eval_exception_exits_nonzero to assert on the stdout JSON envelope instead of stderr text. All quality gates (fmt, clippy -D warnings, cargo test --workspace, check-iteration-ready 10/10 with FF_RDP_LIVE_TESTS=1) re-verified green after the fix.

Noted but not fixed (out of scope, flagged in iteration-142's plan instead): the same envelope-bypass shape still exists in click.rs (2 call sites) and scroll.rs's run_until timeout — no live-test coverage for those paths currently, and click.rs documents the stderr/stdout divergence as deliberate in an existing comment, so it needs its own verification pass rather than a rushed sweep here.

ractive and others added 4 commits August 11, 2026 13:51
…g, surface truncation

Dogfooding session 63 found several output-hygiene defects: --format text
padded every table row to the width of the single longest cell (255KB for
39 console rows); index concatenated a navigate envelope per crawled page
ahead of its own summary JSON, breaking `| jq`; its robots.txt parser
ignored User-agent grouping, applying a foreign bot's `Disallow: /` to
every crawler; snapshot's truncation flag was buried inside results with
no meta signal; empty --format text results printed a bare `[]` that hid
sampled/capped context; JS exceptions during dom/click/wait bypassed the
JSON error envelope entirely; jq syntax errors were misclassified as
Internal instead of User; and network's slowest-20 list had no truncation
marker.

- output_pipeline.rs: middle_ellipsis now bounds every table cell, not
  just `url` columns; empty result arrays render sampled/capped context
  instead of a bare `[]`.
- index.rs: crawl_page now calls navigate::run_core (non-printing) instead
  of the printing navigate::run, so stdout is exactly one JSON document;
  robots.txt is now parsed into User-agent groups and only the wildcard
  group's Disallow rules apply; outgoing links are deduped against an
  `enqueued` set to stop redundant queue growth.
- snapshot.rs: meta now reports `truncated`/`text_truncated` explicitly;
  --max-chars help text describes the actual (whole-tree) bound.
- js_helpers.rs: eval_or_bail/poll_js_condition route JS exceptions
  through AppError::User (the JSON envelope) instead of printing to
  stderr and exiting silently.
- output_pipeline.rs: finalize_with_hints now returns AppError directly so
  jq parse/compile/runtime errors and --jq-strict missing-path errors
  classify as error_type "User", not "Internal".
- network.rs: build_network_summary adds an explicit `slowest_truncated`
  flag (JSON and --format text) for the silent 20-request cap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…envelope

Review of iter-141 (output hygiene / Theme E) found eval.rs's own inline
exception handling still bypassed the JSON envelope — printing `error: ...`
to stderr and returning AppError::Exit(1), the exact same anti-pattern
Theme E fixed in eval_or_bail/poll_js_condition (js_helpers.rs). `eval` is
the most direct way to trigger a raw JS exception, so this closes the last
gap: AppError::User now carries the exception message through main's single
JSON-envelope emission, consistent with every other command failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
All 8 acceptance criteria landed in this PR (verified against the diff by
ac-fidelity-check.sh); annotate the heading per the iter-137 convention.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…velope-bypass bug

iter-141's review pass changed eval.rs's inline JS-exception handler to route
through AppError::User (JSON envelope) instead of bare stderr + Exit(1).
Flag this for whoever implements iter-142 Theme E (which also touches
eval.rs's wrapper), and note the same envelope-bypass shape still exists in
click.rs/scroll.rs — worth a look if Theme E has runway, otherwise a
follow-up plan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ractive
ractive merged commit 60d4ab1 into main Aug 11, 2026
10 checks passed
@ractive
ractive deleted the iter-141/output-hygiene branch August 11, 2026 12:12
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.

1 participant