Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/docs/docs/features/lens.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ Argent Lens is in development. The feature is behind the `argent-lens` flag and
## How a review round works

1. You ask the agent for a design change with an open design space, for example "restyle the header".
2. The agent renders each variant in the app and captures a screenshot of it.
3. The agent stages each variant with the `propose_variant` tool. The agent does not block. The Lens window opens on your screen.
2. The agent renders each variant in the app.
3. The agent stages each variant with the `propose_variant` tool while the variant is on screen. The tool screenshots the device for the preview image. The agent does not block. The Lens window opens on your screen.
4. You review the variants in the Lens window. You pick one variant per element, or you leave an element as it is. You can add a comment on each element, a comment on any other element of the screen, and one overall comment.
5. You press **Complete**. Lens sends your decision to the agent.
6. The agent applies the chosen variants to the source files. If a comment asks for more changes, the agent makes the change or stages a new round of variants.
Expand Down
2 changes: 1 addition & 1 deletion packages/skills/rules/argent.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ Routing: one-off check → `argent-test-ui-flow`; saved path → `argent-create-

PROPOSING DESIGN VARIANTS FOR HUMAN SELECTION
Use skill: `argent-lens`
When: The user asks for design alternatives / options / A-B choices for a screen or component, or you have produced more than one candidate look for an element and want a human to pick before committing. Covers the build → navigate → screenshot → propose_variant loop and the single blocking await_user_selection call. (Gated behind the `argent-lens` flag, off by default — run `argent enable argent-lens` first.)
When: The user asks for design alternatives / options / A-B choices for a screen or component, or you have produced more than one candidate look for an element and want a human to pick before committing. Covers the build → navigate → propose_variant loop and the single blocking await_user_selection call. (Gated behind the `argent-lens` flag, off by default — run `argent enable argent-lens` first.)
Prompt keywords: variant, design option, alternative, A/B, "let me pick", "show me options"
</skill_routing>

Expand Down
45 changes: 22 additions & 23 deletions packages/skills/skills/argent-lens/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ description: Propose multiple visual design variants for on-screen elements and

## 1. Overview

You implement several candidate designs, capture each one running on the device, and stage them with `propose_variant`. Each proposed element shows up as a floating card next to the live simulator stream in the Argent Lens window (a native window that opens automatically), connected by a thin line to the real element. The human picks per element, optionally pins free-form comments to elements, and presses **Complete selection**. `await_user_selection` is the single blocking call that returns their decision.
You implement several candidate designs and stage each one with `propose_variant` while it is running on the device, which captures the screen for the preview. Each proposed element shows up as a floating card next to the live simulator stream in the Argent Lens window (a native window that opens automatically), connected by a thin line to the real element. The human picks per element, optionally pins free-form comments to elements, and presses **Complete selection**. `await_user_selection` is the single blocking call that returns their decision.

**The golden rule: one variant = one real, _distinct_ screenshot.** A proposal is only useful if its `previewImage` shows the variant actually rendered on the device, captured AFTER that specific variant was applied. Never propose a variant you have not built and seen on screen, and never point two variants at the same file path — if two captures end up byte-identical you have not actually changed anything and the Argent Lens degenerates to identical thumbnails. Plan → build → navigate → screenshot → propose, repeated for every variant of every element, then await once.
**The golden rule: one variant = one real, _distinct_ screen.** A proposal is only useful if its preview shows the variant actually rendered on the device, so call `propose_variant` while that variant is on screen — the tool screenshots the device for you at that moment. Never propose a variant you have not built and seen on screen. If the capture comes back byte-identical to another variant of the same element, `propose_variant` refuses it: nothing changed on screen, and both cards would show the same thumbnail. Plan → build → navigate → propose, repeated for every variant of every element, then await once.

## 2. Tools

Expand All @@ -18,17 +18,17 @@ You implement several candidate designs, capture each one running on the device,
| `propose_variant` | No | Stage ONE variant for ONE element. Call once per variant. Keep working. |
| `await_user_selection` | Yes | Call ONCE after every variant is staged. Parks until the human is done. |

`propose_variant` params: `element` (human name), optional `match` (`{ by: "text"|"label"|"identifier"|"role", value }`), optional `udid` (the device id you captured the variants on), and `variant` (`{ name, summary, code?, filePath?, previewImage?, frame? }`). Repeated calls with the same `element` accumulate variants on that element; different `element` values create separate cards.
`propose_variant` params: `element` (human name), optional `match` (`{ by: "text"|"label"|"identifier"|"role", value }`), optional `udid` (the device the variants run on), and `variant` (`{ name, summary, code?, filePath?, previewImage?, frame? }`). Repeated calls with the same `element` accumulate variants on that element; different `element` values create separate cards — as long as their matchers differ, since the matcher is what identifies a card.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

**Always pass `udid`** (the same simulator/emulator id you screenshotted and described with). The preview window then streams _that_ device directly — the human never has to pick a simulator. Set it on the first `propose_variant` of a round; later calls may omit it (the last value wins).
**Always pass `udid`** (the same simulator/emulator id you described with). It is the device `propose_variant` captures the preview from, and the preview window then streams _that_ device directly — the human never has to pick a simulator. Omitting it reuses the last one you passed, which persists across rounds: a `udid`-less propose after you switch devices captures the _old_ one and stages a preview of the wrong screen, with no error. Pass it on every call and that cannot happen. With no `udid` ever set and no `variant.previewImage`, `propose_variant` fails — it has nothing to capture from.

## 3. Workflow

Resolve a simulator/emulator first (`argent-ios-simulator-setup` / `argent-android-emulator-setup`) and, for React Native, `argent-react-native-app-workflow` to run the app and reload the bundle. Argent shows the staged variants in a native preview window that opens automatically on the user's screen; you don't open or display anything yourself. Just stage variants and call `await_user_selection`, and the window appears on its own.

### Step 0 — Plan the variants

Decide, before touching code, exactly which elements you are redesigning and the distinct variants for each. Write them down (e.g. "Search field: Filled / Outlined / Pill" — "Primary CTA: Solid / Gradient"). Each variant must be a single, self-contained change you can apply, screenshot, and revert independently. Vague or overlapping variants produce useless proposals.
Decide, before touching code, exactly which elements you are redesigning and the distinct variants for each. Write them down (e.g. "Search field: Filled / Outlined / Pill" — "Primary CTA: Solid / Gradient"). Each variant must be a single, self-contained change you can apply, propose, and revert independently. Vague or overlapping variants produce useless proposals.

### Step 1 — Get a precise matcher

Expand All @@ -40,18 +40,17 @@ For each element, run `describe` (or `debugger-component-tree` for RN) on the sc

Omitting `match` defaults to `{ by: "text", value: element }`, which is fine only when the element's visible text is unique.

### Step 2 — For each variant: build → navigate → screenshot → propose
### Step 2 — For each variant: build → navigate → propose

Loop over every variant of every element:

1. **Build the variant.** Implement that one variant in code.
2. **Apply it on the device.** Reload the RN bundle (`debugger-reload-metro`) or rebuild as needed so the running app shows this variant.
3. **Navigate to it.** Drive the app (`argent-device-interact`) to the screen where the element is visible — a screenshot is only meaningful if the element is actually on screen.
4. **Screenshot.** Call `screenshot` and pass the returned file path **straight through** as `variant.previewImage`. **NEVER hand-crop, resize, re-encode, or copy the screenshot to another folder** (e.g. a `crop.py` into `/tmp/variants/`): that double-crops against the preview window's own cropping and writes the image somewhere the server won't serve it ("No preview"). Capture the whole screen — the preview window crops it for you using `variant.frame` (step 5). The path you got back must be a NEW file; if you suspect the device froze or the variant didn't apply (you see no visible change vs. the previous capture), diff with the previous path (`shasum -a 256`) before proposing — byte-identical captures mean the variant is not on screen yet. Fix that before proposing, never propose anyway.
5. **Propose.** Call `propose_variant` with `element`, `match`, `udid` (the device you captured on), and `variant.previewImage` set to that screenshot path. The tool **auto-captures the crop frame**: it describes the device at propose time and matches the element, so each thumbnail crops to its own current layout — **as long as the variant is still on screen when you call `propose_variant`** (propose right after the screenshot, before reverting). You may pass `variant.frame` (the matched node's normalized `{x, y, width, height}` in 0..1 from a `describe` on THIS variant) to override the auto-capture — useful when the element can't stay on screen at propose time. Add `summary` (what changed and why) and `code`/`filePath` when useful.
6. **Revert.** Roll the variant change back before building the next one — only one variant can be on screen at a time. Keep going; `propose_variant` does not block.
3. **Navigate to it.** Drive the app (`argent-device-interact`) to the screen where the element is visible — the preview is captured from whatever is on screen when you propose.
4. **Propose.** Call `propose_variant` with `element`, `match` and `udid`, **while the variant is on screen**. The tool captures both halves of the preview from the device at that moment: it screenshots the whole screen, and it describes the device and matches the element for the **crop frame**, so each thumbnail crops to its own current layout. Add `summary` (what changed and why) and `code`/`filePath` when useful. No separate `screenshot` call and nothing to hash by hand: a capture byte-identical to another variant of the same element is refused. That catch is exact byte equality, so it fires on a frozen or unchanged screen but not on one where only the status-bar clock moved — still check that each thumbnail looks different, and fix a variant that didn't apply rather than working around the refusal.
5. **Revert.** Roll the variant change back before building the next one — only one variant can be on screen at a time. Keep going; `propose_variant` does not block.

`previewImage` accepts a local screenshot path (served from the OS temp dir / cwd), an `http(s)` URL, or a `data:` URI. A local screenshot of the real running variant is strongly preferred.
Two optional overrides, for the case where the element cannot be on screen at propose time. `variant.frame` — the matched node's normalized `{x, y, width, height}` in 0..1 from a `describe` on THIS variant — replaces the frame capture. `variant.previewImage` replaces the screenshot: a local image path (served from the OS temp dir / cwd), an `http(s)` URL, or a `data:` URI. An image you pass is used **verbatim**, so it must be a whole, uncropped screen — **never hand-crop, resize, re-encode, or copy it to another folder** (e.g. a `crop.py` into `/tmp/variants/`): that double-crops against the preview window's own cropping and writes the image somewhere the server won't serve it ("No preview"). It also opts out of the duplicate check, so two variants pointing at one image go unnoticed.

### Step 3 — Await the human's decision (once)

Expand All @@ -68,30 +67,30 @@ Implement the chosen variant for every selected element, address every annotatio
## 4. Rules

- **At least two variants per element.** A choice needs alternatives — every element you propose must have ≥2 distinct variants (call `propose_variant` at least twice for it). If you only have one look for an element, either produce a real alternative or don't propose that element at all; a lone variant isn't a choice.
- **Build before you propose.** Every `previewImage` must be a screenshot of that variant actually running on the device. No mockups, no guesses, no proposing un-built ideas.
- **Distinct screenshot per variant.** Reusing a `previewImage` path across two variants — or capturing two paths whose bytes turn out identical — defeats the whole point of the Argent Lens. If you can't produce visibly different captures (e.g. the app is read-only, accessibility is broken so you can't navigate, the bundle won't hot-reload), STOP and tell the user instead of staging duplicates.
- **Build before you propose.** Every preview must show that variant actually running on the device. No mockups, no guesses, no proposing un-built ideas.
- **Distinct screen per variant.** Two variants of one element that look identical defeat the whole point of the Argent Lens, so `propose_variant` refuses a capture whose bytes match another variant of the same element. Byte equality is a backstop, not a full check — two captures of one unchanged screen differ as soon as the clock ticks — so judging that the variants actually look different stays your job. If you can't produce visibly different screens (e.g. the app is read-only, accessibility is broken so you can't navigate, the bundle won't hot-reload), STOP and tell the user instead of forcing duplicates through with `previewImage`.
- **One blocking call.** `propose_variant` never blocks — stage freely. `await_user_selection` is the only call that waits, and you call it once, last.
- **Anchor accurately.** Pull matchers from `describe`; a wrong `match` makes the card point at the wrong element or float unanchored.
- **One variant on screen at a time.** Apply → screenshot → revert before the next variant so screenshots never bleed together.
- **One variant on screen at a time.** Apply → propose → revert before the next variant, so each capture shows one variant.
- **`pending` is normal.** On `pending`, just await again — proposals persist across timeouts.
- **Re-proposing starts a fresh round.** Calling `propose_variant` after a round was consumed begins round N+1 and clears the previous round's elements; stage a full set each round.

## 5. Example

```
Comment thread
coderabbitai[bot] marked this conversation as resolved.
describe { udid } # read exact label/identifier
propose_variant { element: "Search field",
# apply "Outlined", then propose while it is on screen
propose_variant { element: "Search field", udid,
match: { by: "identifier", value: "search-input" },
variant: { name: "Outlined", summary: "1pt border, transparent fill",
previewImage: "/var/folders/.../search-outlined.png" } }
propose_variant { element: "Search field",
variant: { name: "Outlined", summary: "1pt border, transparent fill" } }
# apply "Pill", then propose
propose_variant { element: "Search field", udid,
match: { by: "identifier", value: "search-input" },
variant: { name: "Pill", summary: "Fully rounded, filled grey",
previewImage: "/var/folders/.../search-pill.png" } }
propose_variant { element: "Primary CTA",
variant: { name: "Pill", summary: "Fully rounded, filled grey" } }
# apply "Gradient", then propose
propose_variant { element: "Primary CTA", udid,
match: { by: "label", value: "Get started" },
variant: { name: "Gradient", summary: "Accent gradient fill",
previewImage: "/var/folders/.../cta-gradient.png" } }
variant: { name: "Gradient", summary: "Accent gradient fill" } }
await_user_selection {} # ONE blocking call → human picks
→ { status: "completed",
selections: [ { element: "Search field", chosenVariant: { name: "Pill" } },
Expand Down
Loading