argent-lens

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Prerequisite — feature flag. This workflow is gated behind the
argent-lens
flag (off by default). Run
argent enable argent-lens
once before using it. If
propose_variant
/
await_user_selection
come back not-found, the flag is off — enable it and retry.
前置条件——功能标志。此工作流受
argent-lens
标志限制(默认关闭)。使用前需运行一次
argent enable argent-lens
。如果
propose_variant
/
await_user_selection
返回未找到,则说明标志未开启——请启用后重试。

1. Overview

1. 概述

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.
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.
你需要实现多个候选设计,捕获每个设计在设备上运行的画面,并通过
propose_variant
进行提交。每个提交的元素会作为浮动卡片显示在Argent Lens窗口(自动打开的原生窗口)中的实时模拟器流旁边,通过细线连接到对应的实际元素。用户可为每个元素做出选择,还可选择为元素添加自由格式的评论,然后点击完成选择
await_user_selection
是唯一的阻塞调用,会返回用户的决策结果。
黄金法则:一个变体对应一张真实、独特的截图。只有当
previewImage
展示了该变体在设备上实际渲染的画面(且是应用该特定变体后捕获的),提案才有意义。绝不要提交你未在屏幕上构建和查看过的变体,也绝不要让两个变体指向同一个文件路径——如果两次捕获的内容字节完全相同,说明你并未做出任何实际更改,Argent Lens会显示相同的缩略图。按照规划→构建→导航→截图→提交的流程,针对每个元素的每个变体重复执行,最后调用一次等待方法。

2. Tools

2. 工具

ToolBlocking?Purpose
propose_variant
NoStage ONE variant for ONE element. Call once per variant. Keep working.
await_user_selection
YesCall 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.
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).
工具名称是否阻塞用途
propose_variant
为单个元素提交一个变体。每个变体调用一次。可继续执行其他操作。
await_user_selection
在所有变体提交完成后调用一次。会暂停执行直到用户完成选择。
propose_variant
的参数:
element
(元素的人工命名)、可选的
match
{ by: "text"|"label"|"identifier"|"role", value }
)、可选的
udid
(捕获变体的设备ID),以及
variant
{ name, summary, code?, filePath?, previewImage?, frame? }
)。针对同一个
element
重复调用会为该元素累积变体;不同的
element
值会创建独立的卡片。
务必传入
udid
(即你截图并描述的模拟器/模拟器ID)。预览窗口随后会直接流式传输该设备的画面——用户无需手动选择模拟器。在一轮提交的第一个
propose_variant
中设置该参数;后续调用可省略(最后传入的值会生效)。

3. Workflow

3. 工作流

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.
首先启动模拟器/模拟器(
argent-ios-simulator-setup
/
argent-android-emulator-setup
),对于React Native项目,使用
argent-react-native-app-workflow
运行应用并重新加载包。Argent会在用户屏幕上自动打开的原生预览窗口中显示已提交的变体;你无需手动打开或显示任何内容。只需提交变体并调用
await_user_selection
,窗口就会自动弹出。

Step 0 — Plan the variants

步骤0——规划变体

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.
在编写代码之前,明确你要重新设计的元素以及每个元素的独特变体。将它们记录下来(例如:“搜索框:填充式/轮廓式/胶囊式”——“主CTA:纯色/渐变”)。每个变体必须是可独立应用、截图和还原的单一独立变更。模糊或重叠的变体无法产生有用的提案。

Step 1 — Get a precise matcher

步骤1——获取精确匹配器

For each element, run
describe
(or
debugger-component-tree
for RN) on the screen where it lives and read its exact
label
/
identifier
/
role
. Pass that as
match
so the floating card's connector anchors to the right element:
  • Stable testID / accessibilityIdentifier →
    { by: "identifier", value: "search-input" }
    (most reliable)
  • Exact a11y label →
    { by: "label", value: "Search" }
  • Otherwise →
    { by: "text", value: "Search" }
    (fuzzy contains; the default if
    match
    is omitted)
Omitting
match
defaults to
{ by: "text", value: element }
, which is fine only when the element's visible text is unique.
对于每个元素,在其所在屏幕上运行
describe
(或针对RN使用
debugger-component-tree
),读取其确切的
label
/
identifier
/
role
。将其作为
match
参数传入,以便浮动卡片的连接线锚定到正确的元素:
  • 稳定的testID/accessibilityIdentifier →
    { by: "identifier", value: "search-input" }
    (最可靠)
  • 精确的无障碍标签 →
    { by: "label", value: "Search" }
  • 其他情况 →
    { by: "text", value: "Search" }
    (模糊匹配包含内容;如果省略
    match
    则默认使用此方式)
省略
match
参数会默认使用
{ by: "text", value: element }
,仅当元素的可见文本唯一时才适用。

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

步骤2——针对每个变体:构建→导航→截图→提交

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.
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.
遍历每个元素的每个变体:
  1. 构建变体。在代码中实现该变体。
  2. 在设备上应用。重新加载RN包(
    debugger-reload-metro
    )或按需重新构建,使运行中的应用显示该变体。
  3. 导航到目标页面。通过
    argent-device-interact
    操作应用,导航到元素可见的屏幕——只有当元素在屏幕上可见时,截图才有意义。
  4. 截图。调用
    screenshot
    并将返回的文件路径直接作为
    variant.previewImage
    传入
    绝不要手动裁剪、调整大小、重新编码或复制截图到其他文件夹(例如使用
    crop.py
    保存到
    /tmp/variants/
    ):这会与预览窗口自身的裁剪功能冲突,且图片会被保存到服务器无法访问的位置(显示“无预览”)。捕获整个屏幕——预览窗口会使用
    variant.frame
    (步骤5)自动裁剪图片。你获取的路径必须是新文件;如果你怀疑设备冻结或变体未应用(与上一次捕获相比无可见变化),在提交前对比两个路径的哈希值(
    shasum -a 256
    )——字节完全相同的捕获说明变体尚未显示在屏幕上。请先修复问题再提交,不要直接提交。
  5. 提交。调用
    propose_variant
    ,传入
    element
    match
    udid
    (你捕获截图的设备),并将
    variant.previewImage
    设置为该截图路径。工具会自动捕获裁剪框:它会在提交时描述设备并匹配元素,因此每个缩略图会根据当前布局自动裁剪——只要在调用
    propose_variant
    时变体仍显示在屏幕上
    (截图后立即提交,不要先还原)。你也可以传入
    variant.frame
    (通过对该变体执行
    describe
    得到的匹配节点的标准化
    {x, y, width, height}
    ,取值范围0..1)来覆盖自动捕获——当元素在提交时无法保持在屏幕上时非常有用。可添加
    summary
    (变更内容及原因),必要时添加
    code
    /
    filePath
  6. 还原。在构建下一个变体之前,回滚当前变体的变更——同一时间屏幕上只能显示一个变体。继续执行;
    propose_variant
    不会阻塞。
previewImage
接受本地截图路径(从系统临时目录/当前工作目录提供)、
http(s)
URL或
data:
URI。强烈建议使用真实运行变体的本地截图。

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

步骤3——等待用户决策(仅一次)

After every variant for every element is staged, call
await_user_selection
exactly once. It returns:
  • { status: "completed", selections: [{ element, chosenVariant, comment? }], unselected, annotations: [{ target, match, comment }], globalComment }
    — apply
    chosenVariant
    for each element; skip elements in
    unselected
    . Treat each
    annotations
    entry (inspector comments the human pinned to elements) and
    globalComment
    as a change request.
  • { status: "pending", proposedElements }
    timeoutSeconds
    elapsed, not an error. Proposals are still live; call
    await_user_selection
    again.
  • { status: "no_proposals" }
    — you called it before any
    propose_variant
    . Stage variants first.
在所有元素的所有变体提交完成后,精确调用一次
await_user_selection
。它会返回:
  • { status: "completed", selections: [{ element, chosenVariant, comment? }], unselected, annotations: [{ target, match, comment }], globalComment }
    ——为每个元素应用
    chosenVariant
    ;跳过
    unselected
    中的元素。将每个
    annotations
    条目(用户固定到元素的检查器评论)和
    globalComment
    视为变更请求。
  • { status: "pending", proposedElements }
    ——
    timeoutSeconds
    超时,并非错误。提案仍然有效;可再次调用
    await_user_selection
  • { status: "no_proposals" }
    ——你在未提交任何
    propose_variant
    的情况下调用了该方法。请先提交变体。

Step 4 — Apply the outcome

步骤4——应用结果

Implement the chosen variant for every selected element, address every annotation/comment, and report what you applied and what was skipped. If the human commented but skipped a variant, the comment still matters — act on it.
为每个选中的元素实现选定的变体,处理所有注释/评论,并报告你应用的内容和跳过的内容。如果用户对某个变体添加了评论但未选择该变体,该评论仍然有效——请据此执行。

4. Rules

4. 规则

  • 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.
  • 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.
  • 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.
  • 每个元素至少两个变体。选择需要备选方案——你提交的每个元素必须有≥2个独特变体(针对该元素至少调用两次
    propose_variant
    )。如果你只为某个元素设计了一种外观,要么生成一个真实的替代方案,要么不要提交该元素;单个变体无法构成选择。
  • 先构建再提交。每个
    previewImage
    必须是该变体在设备上实际运行的截图。禁止使用模型图、猜测图,禁止提交未构建的想法。
  • 每个变体对应独特截图。在两个变体中重复使用
    previewImage
    路径——或者捕获两个字节完全相同的路径——会完全违背Argent Lens的初衷。如果你无法生成视觉上不同的截图(例如应用为只读、无障碍功能损坏导致无法导航、包无法热重载),请停止操作并告知用户,不要提交重复内容。
  • 仅一次阻塞调用
    propose_variant
    永远不会阻塞——可自由提交。
    await_user_selection
    是唯一会等待的调用,且你只需在最后调用一次。
  • 准确锚定。从
    describe
    获取匹配器;错误的
    match
    会导致卡片指向错误的元素或无锚点浮动。
  • 同一时间屏幕上仅显示一个变体。应用→截图→还原后再处理下一个变体,避免截图内容重叠。
  • pending
    是正常状态
    。当返回
    pending
    时,只需再次等待——提案会在超时后保留。
  • 重新提交会开启新的一轮。在一轮提案被处理后调用
    propose_variant
    会开启第N+1轮,并清除上一轮的元素;每一轮都需要提交完整的变体集。

5. Example

5. 示例

describe { udid }                                  # read exact label/identifier
propose_variant { element: "Search field",
  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",
  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",
  match: { by: "label", value: "Get started" },
  variant: { name: "Gradient", summary: "Accent gradient fill",
             previewImage: "/var/folders/.../cta-gradient.png" } }
await_user_selection {}                             # ONE blocking call → human picks
→ { status: "completed",
    selections: [ { element: "Search field", chosenVariant: { name: "Pill" } },
                  { element: "Primary CTA",  chosenVariant: { name: "Gradient" } } ],
    annotations: [ { target: "Tab bar", comment: "raise contrast" } ] }
describe { udid }                                  # 读取确切的标签/标识符
propose_variant { element: "Search field",
  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",
  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",
  match: { by: "label", value: "Get started" },
  variant: { name: "Gradient", summary: "Accent gradient fill",
             previewImage: "/var/folders/.../cta-gradient.png" } }
await_user_selection {}                             # 唯一的阻塞调用 → 用户选择
→ { status: "completed",
    selections: [ { element: "Search field", chosenVariant: { name: "Pill" } },
                  { element: "Primary CTA",  chosenVariant: { name: "Gradient" } } ],
    annotations: [ { target: "Tab bar", comment: "raise contrast" } ] }

→ apply Pill + Gradient, and raise tab-bar contrast.

→ 应用Pill和Gradient变体,并提高标签栏对比度。

undefined
undefined