Files
nexu-io-open-design/specs
Caprika 3652fb7237 [codex] restore AMR wallet balance follow-ups (#4771)
* feat: show AMR wallet balance

* fix: preserve AMR wallet reauth state

* chore: retrigger PR validation

* fix: bound AMR wallet balance fetch

* chore: retrigger wallet validation

* chore: bump bundled Vela CLI to 0.0.18

* chore: retrigger Vela CLI validation

* chore(nix): refresh pnpm deps hash

* feat(web): reposition AMR as Open Design's cloud subscription (#4684)

* feat(web): reposition AMR as Open Design's cloud subscription

Fold the standalone "AMR" product into Open Design's own cloud service:
unify all user-facing copy, surface the signed-in account's plan +
wallet balance, and add upgrade CTAs.

- Copy: user-facing "AMR" / "Open Design AMR" -> "Open Design" across
  19 locales, agent labels, and daemon error strings (balance/recharge
  wording preserved).
- Account: daemon reads plan + balance via `vela billing summary`
  (CLI route, control-key) and injects them into the vela status; web
  shows plan + balance on the settings card, the model-switcher account
  row, and the runtime-switcher avatar row (balance field aligned with
  the console wallet's headline balanceUsd).
- Upgrade: filled "Upgrade" CTA next to the plan on all three surfaces
  when the tier is below max; opens the console wallet at ?view=plans.
- Runtime-switcher Open Design row restructured to carry account info
  inline, with a selected-state highlight.
- Analytics: new amr_entry elements settings_amr_upgrade /
  inline_amr_upgrade / avatar_amr_upgrade / avatar_amr_agent_card.
- specs/: design doc for the Open Design account/avatar direction.

* fix(daemon): surface live account for env-backed auth + isolate cache per credential

Address @nettee review on #4684:
- applyVelaLiveAccount() now populates plan/balance even when the login
  status has user:null (VELA_RUNTIME_KEY / VELA_LINK_URL env auth), so
  env-backed Open Design sessions no longer drop the fetched billing data.
- Key the live-account cache by the full credential revision (auth source
  + profile + userId + config mtime), not just profile, and clear it on
  logout — a logout / account switch can no longer surface the previous
  account's plan or wallet balance before the background refresh runs.
- Regression tests for both in tests/integrations/vela.test.ts.

Sync tests to the AMR -> Open Design copy:
- web: tests/providers/sse.test.ts error-string assertions.
- e2e: onboarding / logout / run-failure / login-pill specs + visual fixture.

* fix(i18n): drop duplicated "Open Design" in switchBody fallback copy

The AMR -> Open Design rename sweep collided with the existing brand
mention in chat.amrCard.switchBody, rendering "Open Design official
Open Design model service" across 13 locales (zh-TW, ja, ko, ar, es-ES,
hu, id, fa, pl, tr, ru, pt-BR, uk). Remove the redundant second mention
so the retry/switch card reads cleanly. Flagged by @nettee on #4684.

Also start aligning InlineModelSwitcher unit tests to the new copy
(agent display name AMR -> Open Design, compact error string).

* fix(amr): resolve live billing before first /status; hide Upgrade until plan known

Address @nettee review on #4684 (two correctness issues in the new
plan/balance path):

- Cold cache: the signed-in /status handler now BLOCKS the first response
  on fetchVelaBillingSummary() when the cache is empty, instead of
  returning config-only and refreshing in the background. The new account
  surfaces read /status once and never re-poll, so the old flow left
  plan/balance hidden until the user refocused. Warm cache keeps the
  non-blocking fast path. fake-vela gains a `billing summary` handler and
  vela.routes.test.ts covers the cold-cache-first-open case.
- Upgrade CTA: canUpgradeVelaPlan() now treats an unknown/empty plan as
  NOT upgradeable, so a signed-in session whose live summary hasn't
  resolved no longer flashes Upgrade at top-tier users. Unit-tested.

* style(amr): bold the plan/balance text in the switcher + avatar account rows

The settings card already renders both bold (the plan value reuses
.agent-card-amr-balance-value); align the inline switcher account row and
the runtime-switcher avatar row so plan + balance read with the same
emphasis everywhere.

* fix(amr): single-flight cold billing fetch + surface plan/balance on its own field

Address @nettee follow-up review on #4684:

- Race: the cold-cache decision keyed off the refresh throttle, so a second
  /status arriving during the first billing fetch saw refreshAccount=false,
  fell through, and returned config-only — which the read-once surfaces can't
  recover from. Now `peekVelaLiveAccount(key) === null` is the cold signal and
  the billing fetch is single-flighted per credential revision: concurrent
  cold callers await the same in-flight promise. setVelaLiveAccount stamps the
  fetch time so the warm path doesn't immediately re-refresh.
- Identity: applyVelaLiveAccount no longer fabricates a `{ id: '', email: '' }`
  user for env-backed sessions. The live billing projection now rides on a new
  VelaLoginStatus.account field, so `user` stays null (preserving the analytics
  `user.id` null signal) and plan/balance are surfaced uniformly. SettingsDialog
  / InlineModelSwitcher / AvatarMenu read status.account.{plan,balanceUsd}.

Regression coverage updated in vela.test.ts (applyVelaLiveAccount) and
vela.routes.test.ts (cold-cache first open now asserts body.account).

* style(amr): match the switcher plan text color to the balance

The inline switcher account row showed the balance in --text-strong but
the plan in --text-muted, so "$247.51 Plus" read with two different
weights of grey. Use --text-strong for the plan too so balance + plan
sit at the same emphasis.

* fix(amr): treat tier-less billing summary as free; register new entry sources

Address @nettee review on #4684:

- Free vs unknown: fetchVelaBillingSummary() returned plan:undefined when
  `membershipTier` was absent, but that field is omitted for free accounts. The
  new surfaces read status.account.plan for both display and canUpgradeVelaPlan,
  so a successful free-account read rendered as "unknown" — plan hidden and the
  Upgrade CTA suppressed for the exact users who should see it. A successful
  summary without a tier now normalizes to the explicit 'free' sentinel;
  "unknown" stays signalled by a null account (failed fetch). Regression in
  vela.routes.test.ts (balance, no tier → account.plan === 'free').
- Analytics: the four entry sources this PR adds (settings_amr_upgrade,
  inline_amr_upgrade, avatar_amr_upgrade, avatar_amr_agent_card) were in the
  source→page map but not in the AMR_ENTRY_SOURCES validator set, so
  parseAmrEntryAnalyticsPayload rejected them and /analytics-entry 400ed for the
  new buttons. Added them to the set; parser regression covers all four.

* fix(amr): invalidate the env-backed live-account cache on config/account change

Address @nettee 11:19 review (the inline comment did not post, but the flagged
env-backed live-account cache path has a real leak): readVelaCredentialRevision
forced configMtimeMs to null for env-backed auth, so the live-account cache key
became `env|profile|1||` with nothing to distinguish accounts. Because the live
billing summary is fetched with the config profile's controlKey, a config
rewrite (account switch) left an env-backed (VELA_RUNTIME_KEY) session serving
the previous account's plan/balance. Keep configMtimeMs for env auth too so a
config/account change yields a fresh key. Regression added in vela.test.ts.

* fix(amr): fingerprint the configured AMR env in the live-account cache key

Address @nettee 11:35 review on #4684: configMtimeMs alone wasn't enough. The
AMR env credentials (VELA_RUNTIME_KEY / VELA_LINK_URL) can come from
agentCliEnv.amr in app-config, and env-backed sessions report user:null — so
switching the Settings-backed credential from account A to B on the same
profile, without touching ~/.amr/config.json, left the revision at
`env|profile|1||<same-mtime>` and let B reuse A's cached plan/balance.

readVelaCredentialRevision now adds a non-secret credentialFingerprint (sha256
of the runtime key + link url, truncated) and velaLiveAccountCacheKey includes
it. Regressions: a unit test (env key differs by fingerprint) and a route test
that rewrites agentCliEnv.amr's VELA_RUNTIME_KEY without touching the config and
asserts the second account does not inherit the first's cached billing.

* feat(amr): label + stack balance/plan in the switcher account row; fix its tests

UI: the inline switcher Open Design account row rendered "$247.51 Plus" with no
labels, so users couldn't tell what the two values were. Balance and plan now
stack on two labelled lines ("可用余额 / Available balance" and "当前套餐 /
Current plan"), with the Upgrade CTA on the plan line. The settings.amrBalance /
settings.amrPlan labels are made descriptive across all 19 locales (the settings
card reuses them), so the meaning is explicit everywhere.

Tests: rewrite InlineModelSwitcher.test.tsx for the account-row structure — the
agent radio now carries status only in its aria-label (icon-only visible), and
the sign-in / signing-in / cancel affordances live on the
inline-model-switcher-account-action button. Redundant within(amrButton)
getByText assertions are dropped (the radio-name query already asserts status);
pending/error assertions target the account row. 22/22 green.

* test(amr): align AmrLoginPill tests to the Open Design copy

Account-status group is labelled 'Open Design account status', the console
link is 'Console', and the compact sign-in error reads 'Sign-in failed.' —
update the queries to match. 29/29 green.

* feat(amr): redesign the account row — tier badge + balance subtitle; short labels

Iterated on the Open Design account row in both the inline model switcher and
the runtime-switcher (avatar) list so the plan/balance no longer collide:

- Plan tier renders as a small neutral pill right after the "Open Design" name
  (identity + tier read as one group, à la Claude's "name · Max").
- Balance moves to a second line below the name, labelled "余额 $247.51"
  (small grey label + bold value) so users know what the number is.
- Upgrade stays as the trailing action, vertically centred against the two
  lines. Account logo bumped to 24px to balance the taller row.
- Revert the amrBalance / amrPlan labels back to the short "余额 / 套餐"
  ("Balance" / "Plan") across all 19 locales.

* test(amr): align remaining web specs to the Open Design copy + account row

Update the rest of the web component specs that still asserted the pre-rename
wording / old account-row shape (the `Web workspace tests` red CI @nettee
flagged):

- EntryShell.onboarding: "Sign in to Open Design Cloud" -> "Sign in to Open
  Design"; "AMR sign-in failed." -> "Sign-in failed.".
- SettingsDialog.execution: AMR card queried as /^Open Design\b/ (was
  /^Open Design AMR\b/).
- HandoffButton: agent label "Open Design"; website link "打开 Open Design 官网".
- AvatarMenu: drop the two obsolete avatar console-shortcut specs (the avatar
  console link was removed when the row was consolidated; the upgrade entry
  remains covered by the daemon parser + contract).

Full web suite green: 3403 passed.

* test(amr): cover the avatar-row upgrade CTA (plan/balance + stamped link)

Replaces the two removed avatar console-shortcut specs with focused coverage
for the new runtime-switcher upgrade path (@nettee): mocks a signed-in
/api/integrations/vela/status, opens the menu on the non-prod `test` profile,
asserts the rendered plan badge + balance, and verifies the clicked upgrade
link carries view=plans and od_entry_source=avatar_amr_upgrade attribution.

* test(e2e): update avatar visual spec to the redesigned account row

The Playwright visual (settings-workspace) job was red on a functional
assertion, not a pixel diff: the spec queried the removed
`.avatar-amr-account-link` console entry and 'AMR account' / 'Balance &
recharge' copy. Rewrite it to override the signed-out status with a signed-in
plan/balance, then assert the Open Design row renders the Plus badge + $247.51
and the upgrade link points at ?view=plans.

* chore: re-trigger CI on updated main — needs-validation gate moved to merge_group (#4714)

* test: capture Open Design account balance visuals

* chore: retrigger workspace validation

---------

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: lefarcen <935902669@qq.com>
Co-authored-by: a1chzt <chizblank@gmail.com>

* [codex] keep AMR wallet balance stable during refresh (#4730)

* fix: keep AMR wallet balance visible during refresh

* fix: remove AMR wallet refresh hover tooltip

* chore: retrigger validation

* fix(ci): align AMR wallet branch validation

* feat(web): Open Design 云账户入口 UI 优化(名牌/置顶/新 logo) (#4787)

* feat(web): polish Open Design cloud account surfaces (plan badge, pin-to-top, new logo)

- 模型切换器(InlineModelSwitcher): Open Design 账号卡片上位为可点选入口并置顶, 移除重复的代理行
- 头像菜单(AvatarMenu): Open Design 置顶代码代理列表; 执行模式选中态改为勾选标记(不再与代理选中态争色块)
- 统一套餐名牌: 新增共享 PlanBadge 组件(全局 CSS, 跟随主题色), 替换设置页/切换器/头像菜单三处各自实现
- 设置页 CLI 卡片: 删除无用的钱包刷新按钮与"更新于…缓存"行; 余额统一为两位小数
- 全局替换 AMR logo 资产(amr.svg)

* test(web): align AgentIcon AMR assertion with new logo fill

---------

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>

* fix(web): preserve AMR wallet balance in switcher

* fix(amr): avoid stale wallet status on refresh failures

* fix(web): align avatar AMR balance row

* fix(web): avoid stale AMR wallet snapshots

* fix(web): keep avatar AMR console link

* fix(amr): support wallet fallback for older vela cli

---------

Co-authored-by: michaeltaxman911-hue <michaeltaxman911@gmail.com>
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: lefarcen <935902669@qq.com>
2026-06-30 03:04:52 +00:00
..