Skip to content

Add annotations, interactions, masking, color-scheme, sessions, and workflows - #8

Open
brijr wants to merge 7 commits into
mainfrom
capture-features
Open

brijr wants to merge 7 commits into
mainfrom
capture-features

Conversation

@brijr

@brijr brijr commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the full six-phase capture feature plan for iris:

  • Annotations — repeatable --point SELECTOR LABEL draws numbered markers with auto-placed labels and leader arrows; --highlight outlines elements; --dim dims the rest of the page. Element clips grow to cover overlay boxes.
  • Pre-capture interactions — ordered --click/--fill/--hover/--press/--wait steps (--open aliases --click; text=... targets the deepest visible match) run after the readiness gate with per-step budgets and step-indexed errors.
  • Masking & redaction — repeatable --mask inks out matches (or --mask-blur blurs); opt-in --mask-patterns email,phone,ssn,account covers conservative sensitive-data patterns in text nodes and form values. Masks run before overlays.
  • Color scheme control--color-scheme light|dark|system with --light/--dark shorthands; system preserves historical output. JSON reports record the effective scheme.
  • Authenticated sessionsiris login --session NAME URL opens a visible browser for one manual sign-in and persists the profile under the OS data dir; --session NAME reuses it with a login hint when unknown. Session names are validated against path escapes.
  • Workflow recipes — repeatable --workflow FILE (.yaml/.yml/.json) carries url, viewport, steps, redactions, annotations, selector, session, timeout, and output; --dry-run prints the resolved plan without launching Chrome; unknown fields fail fast. Recipes convert into capture requests so files, flags, and MCP calls validate through one prepare path.
  • MCP parity — capture gains annotations/highlight/dim, ordered steps, redact/mask_blur_px/redact_patterns, color_scheme (with dark:true back-compat), and session with a per-session cache.

Test plan

  • Browser contract tests: annotations, interactions, masking, color-scheme, session persistence/isolation
  • Workflow equivalence test proving full-field recipe → capture request conversion
  • No-browser CLI test: --dry-run resolves the workflow plan
  • New fixtures: interact.html, private.html, session.html, workflow.yaml
  • Manual smoke: iris login --session demo <url> then iris --session demo <url>

Generated with Devin

Implements plan Phase 1: repeatable --point SELECTOR LABEL flags draw
numbered markers with auto-placed labels and outlines via pre-capture
DOM overlay; --highlight outlines elements; --dim dims the rest of the
page. Element clips grow to include nearby markers. MCP capture gains
matching annotations/highlight/dim fields; JSON reports add an
annotations count (omitted when zero).
Implements plan Phase 2: repeatable --click/--fill/--hover/--press
flags (--open aliases --click; text=... targets the deepest visible
element containing the text) run as ordered in-page steps after the
readiness gate, with per-step budgets and step-indexed errors. MCP
capture gains an ordered steps array for exact mixed ordering. Adds
the interact.html fixture and a browser interaction contract test.
Implements plan Phase 3: repeatable --mask covers every match with
ink (or --mask-blur blur); opt-in --mask-patterns email,phone,ssn,
account covers conservative pattern matches in text nodes and form
values. Masks run before overlays; JSON reports count boxes
(masked). MCP capture gains redact/mask_blur_px/redact_patterns.
Adds the private.html fixture and a browser masking contract test.
Implements plan Phase 4: --color-scheme light|dark|system is the
source of truth with --light/--dark shorthands (clap conflicts keep
them exclusive); system applies no override, preserving historical
output. The engine forces the scheme through the existing emulated
media path; JSON reports record the effective scheme. MCP capture
gains color_scheme with dark:true back-compat. Adds a browser
color-scheme contract test.
Implements plan Phase 5: iris login --session NAME URL opens a
visible browser for one manual sign-in and keeps the profile under
the OS data dir; --session NAME captures reuse it (erroring with a
login hint when unknown). Session names are validated against path
escapes. MCP capture gains session with a per-session cache. Adds
the session.html fixture and a browser session contract test proving
persistence across launches and isolation from temp profiles.
Implements plan Phase 6: --workflow FILE (repeatable, .yaml/.yml/
.json) carries url, viewport, steps, redactions, annotations,
selector, session, timeout, and output per file. Recipes convert
into capture requests so files, flags, and MCP calls validate
through one prepare path (proven by a full-field equivalence test).
--dry-run prints the resolved plan without launching Chrome;
unknown fields fail fast naming the field. YAML uses serde-saphyr.
Adds the workflow.yaml fixture, a no-browser dry-run CLI test, and
a README workflows section with a CI example.
Audit follow-up on annotations: --point now connects each numbered
marker to its label with an orange leader line painted beneath both,
so markers genuinely point at elements. Element clips already grow
over overlay boxes, which now include the arrow span.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-10T17:10:53.032815Z 8888857 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8888857eba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/capture.rs
Comment on lines +1191 to +1193
element.dispatchEvent(new PointerEvent("pointerover", at));
element.dispatchEvent(new MouseEvent("mouseover", at));
element.dispatchEvent(new MouseEvent("mouseenter", at));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Drive a real pointer for hover steps

When a target reveals content through CSS :hover, these synthetic events do not change Chrome's pointer state, so the hover rule never matches and the screenshot is taken without the menu or tooltip. The comment immediately above acknowledges this limitation; move the pointer through the CDP input domain instead of merely dispatching DOM events.

Useful? React with 👍 / 👎.

Comment thread src/capture.rs
Comment on lines +1204 to +1207
const target = document.activeElement || document.body;
for (const type of ["keydown", "keypress", "keyup"]) {{
target.dispatchEvent(new KeyboardEvent(type, {{ key, bubbles: true, cancelable: true, view: window }}));
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Send key presses through the browser input domain

When press is expected to submit a form with Enter, dismiss browser UI with Escape, or invoke another native keyboard action, dispatchEvent creates untrusted events and does not perform the browser's default behavior. A normal fill step also does not focus its input, so a following press commonly targets body; the step can therefore report success while leaving the page unchanged. Use real CDP keyboard input and ensure the intended control is focused.

Useful? React with 👍 / 👎.

Comment thread src/capture.rs
Comment on lines +1263 to +1264
veil.style.cssText = css;
document.body.appendChild(veil);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep masks above top-layer elements

When the selected private content is inside an open native <dialog> or popover, appending the mask to document.body places it below the browser's top layer regardless of its maximum z-index. The dialog content therefore remains visible even though the capture reports it as masked; masks need to be rendered within the relevant top-layer container or applied outside normal document stacking.

Useful? React with 👍 / 👎.

Comment thread src/capture.rs
Comment on lines +1260 to +1262
if (blurPx === null) css += "background:#111310;";
else css += "background:rgba(17,19,16,.15);backdrop-filter:blur(" + blurPx + "px);"
+ "-webkit-backdrop-filter:blur(" + blurPx + "px);";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject zero-radius blur masks

When callers pass --mask-blur 0 or mask_blur_px: 0, the value is accepted and this branch draws only a 15%-opaque tint with blur(0px), leaving the supposedly redacted text readable while still incrementing the masked count. Reject zero or fall back to the opaque mask so a valid redaction request cannot silently expose its target.

Useful? React with 👍 / 👎.

Comment thread src/mcp.rs
Comment on lines +127 to +130
/// One pre-capture interaction. Array order is the execution order.
#[derive(Clone, Debug, Deserialize, schemars::JsonSchema)]
#[serde(untagged)]
pub enum StepRequest {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject extra keys in workflow steps

When a workflow step contains multiple action keys or an extra misspelled key, these untagged struct variants ignore the unknown fields and deserialize as the first matching action. For example, {click: "#open", wait_for: "#ready"} silently executes only the click despite the documented strict workflow validation, producing a partial capture instead of failing fast. Define deny-unknown-fields step shapes before applying the untagged enum.

Useful? React with 👍 / 👎.

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