Skip to content

Send keys only where an action points - #99

Merged
senamakel merged 3 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/type-only-into-the-focused-target
Oct 14, 2026
Merged

senamakel merged 3 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/type-only-into-the-focused-target

Conversation

@YellowSnnowmann

@YellowSnnowmann YellowSnnowmann commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

agent-browser types and presses into whatever holds the keyboard focus. It asks the target to take the focus first, but an element that takes none (a heading, a label) leaves the focus where it was. Text typed "into" a heading then lands in the field filled before it, a card number say, and a key pressed on one presses whatever button last had the focus. A security review of an agent driving BrowserPerform found both.

Browser::perform now checks first, for Focus, Fill, Type with a target, and Press with a target (new, below). The check lives in sessions/focus.rs:

  1. It takes the focus off whatever holds it, reaching through open shadow roots and same-origin frames.
  2. It gives the target the focus, with the engine's focus command.
  3. It marks the elements now holding the focus with a fresh random value (data-tc-focus), then reads that attribute on the target with getattribute. The engine resolves the target in its own frame, so only the target, or a shadow host around it, carries the mark. The marks come off again either way.
  4. If the mark isn't on the target, the action fails with Error::FocusMissed, and nothing is typed or pressed. On the wire this is NOT_ACTIONABLE. The error names only the tag that holds the focus, never page text, and its suggestion is to act on the field itself.

Press gains an optional target (contract 2.18), so a key can be checked the same way. The browser tool maps its selector onto it.

Why in the adapter: the right home for this is the engine: one function that focuses and compares activeElement in a single call. That fix cannot land in the agent-browser fork for now, so this composes the commands the engine already exposes. When the engine verifies the focus itself, this check can go.

Related issue

None.

API or behavior changes

  • Contract 2.18: Action::Press gains target: Option<Target>, with #[serde(default)], and is omitted on the wire when None. Untargeted presses serialize exactly as before.
    • A 2.17 module ignores the field and presses wherever the focus is. That's why this is a minor bump.
    • Rust callers that build Action::Press { key } with a struct literal must add target: None.
  • Behavior:
    • Focus, Fill, and Type with a ref or selector target now fail NOT_ACTIONABLE (Error::FocusMissed) when the focus does not land on the target. Before, they reported success with the keys gone elsewhere.
    • A focus inside a cross-origin frame goes ahead: the page cannot see into the frame, and with the focus taken off everything first, only the target can have put it there. Card fields in a payment provider's frame keep working.
    • A locator's Fill cannot be checked, since only the engine resolves the locator. It gets the focus taken off first, so its text lands in the field or nowhere.
    • Type and Press without a target are unchanged: they go wherever the focus is.
  • New error variant: Error::FocusMissed { focused }. The enum is #[non_exhaustive], so this isn't breaking.
  • Limit: a page's own scripts run between the check's steps and could fool it, as they could any in-page check. It stops mistakes, not a page set on defeating it.
  • Cost: each checked action sends four or five more engine commands (two to four page scripts, a focus, an attribute read).

Validation

Commands actually run, with their outcome:

  • cargo fmt --all -- --check: clean.
  • cargo clippy --locked -p tinycomputer-bus -p tinycomputer-browser --all-targets -- -D warnings -A unused-imports: clean.
    • -A unused-imports is only for the CENTRE_JS import in surface/mod.rs, which is unused in a default-feature test build. That was already the case on main; this change doesn't touch it.
    • Not run: --all-features over the whole workspace.
  • cargo build --all-targets --all-features: not run. The module crate links both engines, and this machine didn't have the disk space to build them in debug. CI runs it.
  • cargo test --locked -p tinycomputer-bus -p tinycomputer-browser: browser 177 passed (12 new), plus 2 doctests; bus 227 passed, plus 34 doctests.
    • cargo test --all-features, including the module crate's new bus test, was not run locally, for the same reason. CI runs it.
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps -p tinycomputer-bus -p tinycomputer-browser: clean.

No live Chrome run, and no live test yet. The live_page helper is private to the sight tests. A follow-up could share it and add a live_ test: a page with a heading and a focused card field, then a Type on the heading's ref, expecting FocusMissed with the card field untouched.

Tests

  • sessions_tests/focus_tests.rs (12 tests) covers:
    • typing into a focused target;
    • text meant for a heading refused, with the field that held the focus untouched;
    • a target that takes no focus;
    • a cross-origin frame going ahead;
    • a targeted press refused, then going through;
    • untargeted keys unchecked;
    • a locator fill getting the blur only;
    • Focus refused;
    • a press on a locator refused before anything is sent;
    • a failed mark read reported, with the marks still removed;
    • the focus named by tag only;
    • a fresh mark on every check.
  • The Fake: it now models where a page puts the focus (PageFocus, defaulting to the target) and answers the focus check before a test's own script. So the existing surface tests, which answer every evaluate, keep running unchanged.
  • Errors: error_tests.rs covers FocusMissed's wire name, envelope and suggestion.
  • Conversion: interaction_tests.rs checks that a targeted press is the plain key command.
  • Bus crate:
    • wire pins for a press with and without a target (action_tests.rs);
    • the tool's selector becoming the press target (tool_tests.rs);
    • the version pins (2.18, with 2.17 now older).
  • Module crate: tinybus_module_tests/browser_tests.rs sends a press with a target over the in-memory bus. It checks that the press is refused NOT_ACTIONABLE with the suggestion, and is never sent.

Documentation

  • docs/crates/tinycomputer-browser/interacting.md: a new section, "Keys go only where an action points", plus the press section.
  • docs/crates/tinycomputer-bus/browser.md, docs/crates/tinycomputer-bus/versioning.md.
  • docs/technical/specs/desktop-module-contract.md: the 2.18 note.

Checklist

  • The change is focused on one logical change
  • No new #[allow(...)], #[ignore], or relaxed lints
  • No secrets, tokens, or .env contents in the diff or the description

agent-browser types and presses into whatever holds the keyboard focus. It
asks the target to take the focus first, but an element that takes none (a
heading, a label) leaves it where it was: text typed into a heading lands
in the field filled before it, and a key pressed on one presses whatever
button last had the focus.

Browser::perform now checks first, for Focus, Fill, Type with a target and
Press with a target: it takes the focus off whatever holds it, gives it to
the target, marks the elements holding the focus with a fresh random value
and reads that attribute back on the target through the engine, which
resolves the target in its own frame. A focus that landed elsewhere fails
Error::FocusMissed (NOT_ACTIONABLE on the wire, naming only the tag that
holds it) with nothing typed or pressed. A focus inside a cross-origin
frame goes ahead, since only the target can have put it there; a locator's
fill, which only the engine resolves, gets the focus taken off first.

Press gains an optional target (contract 2.17), so a key can be checked
like typing; the browser tool maps its selector onto it.

The check composes existing engine commands because the engine's own fix
cannot land in the agent-browser fork for now; when the engine verifies
the focus itself, this can go.
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4d5a8ce9-b103-41f5-af3c-c9f63c416755
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

A whitespace key is refused as invalid before the focus check runs, so the
test now presses Enter on the element whose focus misses.
main took contract 2.17 for StartTask.site and the SiteMemoryHost epoch, so
the optional Press target moves to 2.18.
@tinysweeper

tinysweeper Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

⚠️ Review failed for a083057e1159. the review of #99 did not finish within 900s

Last completed report

Tiny Sweeper review

Tiny Sweeper reviewed this change across 5 lane(s) and found 0 active actionable finding(s). Detailed lane evidence and any incomplete work are listed below.

State: Incomplete
Priority: medium
Reviewed head: a083057e1159
Updated: 2026-10-13T18:35:05Z

Review snapshot

Change surface Files Review signal Count
Production 11 Active findings 0
Tests 10 Noted findings 1
Documentation 4 Resolved findings 0
Configuration 0 Pending checks/questions 3

Completeness: Incomplete
Test assessment: No supported feature-to-test mapping was available; this does not mean tests are absent or passed.

Features

None identified with supported citations.

Tests

No supported feature-to-test mapping was produced. Test execution is not inferred.

Findings

No active actionable findings.

Lower-confidence notes

  • medium · critique — Handle a locator target in the press arm too (crates/tinycomputer\-browser/src/sessions/page\.rs)

Could not review: crates/tinycomputer/src/tinybus_module/tinybus_module_tests/browser_tests.rs, docs/crates/tinycomputer-bus/browser.md, docs/technical/specs/desktop-module-contract.md

Before merge

  • Complete the critique review for crates/tinycomputer/src/tinybus_module/tinybus_module_tests/browser_tests.rs, docs/crates/tinycomputer-bus/browser.md, docs/technical/specs/desktop-module-contract.md.

How this fits together

flowchart LR
  n0["wait_for<br/>changed"]:::changed
  n1["every_variant<br/>changed"]:::changed
  n2["Error<br/>changed"]:::changed
  n3["Fake<br/>changed"]:::changed
  n4["Value"]:::impacted
  n5["...fore_the_page_claim_nothing_was_delivered"]:::impacted
  n6["locator"]:::impacted
  n7["to_action"]:::impacted
  n8["Err"]:::impacted
  n0 -->|uses| n4
  n0 -->|calls| n8
  n1 -->|uses| n2
  n3 -->|uses| n4
  n5 -->|calls| n1
  n5 -->|tests| n1
  n5 -->|uses| n2
  n7 -->|calls| n6
  n7 -->|calls| n8
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading
Agent review details

critique

  • Conclusion: Success
  • Scope reviewed: incomplete; unanswered: crates/tinycomputer/src/tinybus_module/tinybus_module_tests/browser_tests.rs, docs/crates/tinycomputer-bus/browser.md, docs/technical/specs/desktop-module-contract.md
  • Lane summary: Reviewed 22 files; 1 finding. 3 files could not be reviewed: crates/tinycomputer/src/tinybus_module/tinybus_module_tests/browser_tests.rs, docs/crates/tinycomputer-bus/browser.md, docs/technical/specs/desktop-module-contract.md. _The code index is behind this pull request (indexed at `a083057e1159`), so retrieved context may be out of date._

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Reviewed 21 files; 0 findings. 4 files were not security-reviewed: docs/crates/tinycomputer-browser/interacting.md (prose or tabular data), docs/crates/tinycomputer-bus/browser.md (prose or tabular data), docs/crates/tinycomputer-bus/versioning.md (prose or tabular data), docs/technical/specs/desktop-module-contract.md (prose or tabular data). _The code index is behind this pull request (indexed at `a083057e1159`), so retrieved context may be out of date._

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: The change adds an optional `target` to `Action::Press` and a focus-verification step before any action that sends keys, with a new `Error::FocusMissed` variant, a contract bump to 2.18, and a substantial test suite in `sessions_tests/focus_tests.rs` that pins the command order, the refusal-with-nothing-sent paths, the stale-ref path, the mark hygiene, and the tag-only naming. The tests exercise the new branches on both sides of the seam (bus serialization, tool args, module envelope), and the fake's `PageFocus` scripting is genuinely deterministic, so the invariants claimed in the comments are backed by tests that would fail if the behaviour regressed. I found no defect I can anchor to the diff. _The code index is behind this pull request (indexed at `a083057e1159`), so retrieved context may be out of date._

commits

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: Nothing sensitive found in what this pull request commits.

description

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: The description matches the diff closely: it states the new `Press.target` field at contract 2.18, the `FocusMissed`/`NOT_ACTIONABLE` behaviour, the locator and cross-origin exceptions, the new tests, and the docs. One thing it does not mention: the `browser` tool's `selector` was already accepted for `press`, and now that selector is passed through as the press target, so a previously-ignored parameter changes behaviour — the PR describes the mapping but frames it as new capability rather than a behaviour change for existing tool callers. I could not verify from the evidence whether the tool schema documents `selector` for press; otherwise the description is accurate, including its candid notes about the untested all-features build. _The code index is behind this pull request (indexed at `a083057e1159`), so retrieved context may be out of date._
Evidence and run details
  • Models: ladder/vectors-oai3, deepseek-v4.1-flash,
  • Spend: $0.003853
  • Tokens: 1907308 input · 48012 output · 323712 cached · 739 embedding
Head State Pass summary
a083057e1159 incomplete 0 active finding(s), 0 resolved finding(s) (at 2026-10-13T18:35:05Z)

tinysweeper 0.1.2

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

tinysweeper found nothing blocking, but could not review everything, so this is not an approval: crates/tinycomputer/src/tinybus_module/tinybus_module_tests/browser_tests.rs, docs/crates/tinycomputer-bus/browser.md, docs/technical/specs/desktop-module-contract.md.

             $0.0039 · 1,907,308 in / 48,012 out · 323,712 cached (17%) · ladder/vectors-oai3, deepseek-v4.1-flash,  · 739 embedded
critique:    $0.0028 · 1,108,753 in / 36,550 out · 205,824 cached (19%) · deepseek-v4.1-flash,
security:    $0.0006 · 633,389 in   / 7,160 out  · 106,752 cached (17%) · deepseek-v4.1-flash
tests:       $0.0001 · 99,764 in    / 1,045 out  · 6,912 cached (7%)    · deepseek-v4.1-flash
description: $0.0000 · 45,573 in    / 553 out    · 4,224 cached (9%)    · deepseek-v4.1-flash

@senamakel
senamakel merged commit 3960d7f into tinyhumansai:main Oct 14, 2026
19 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants