Skip to content

[DRAFT][PLAN] AI Credits widget: "Revoke Operator" button with confirmation dialog #157

Description

@goodbounties-nanoclaw-agent

[DRAFT][PLAN] AI Credits widget: "Revoke Operator" button with confirmation dialog

Plan for #154.

Reference files mapped (existing code to build on)

File Role
packages/ai-credits-widget/src/components/manage/BuyerOperatorCard.tsx UI insertion point. Current "Sign Consent"/"Consented" Button lives at ~L195-211, driven by operatorConsented/operatorConsentPending props and actions.signOperatorConsent().
packages/ai-credits-widget/src/operatorConsent.ts Consent payload/signing logic. buildSetOperatorPayload (~L49-78) and signOperatorConsentFromTypedData (~L80-101) are the pattern to mirror for a revoke payload.
packages/ai-credits-widget/src/widgetRuntimeContract.ts Adapter contract. operatorConsented/operatorConsentPending/operatorAddress state (~L45-48) and the actions interface (~L63-96, signOperatorConsent at L85) — add revokeOperatorConsent here.
packages/ai-credits-widget/src/backendClient.ts HTTP client. submitOperatorConsent (~L412-429) posts to ${accountBase(buyer)}/operator-consent via readBridgeResponseBody; OperatorConsentRequest type (~L81-84); AiCreditsBackendClient interface (~L204-230).
packages/ai-credits-widget/src/adapter.ts Wires runtime-contract actions to backend calls (consent handler ~L1688 area) — add a handleRevokeOperatorConsent counterpart.
packages/ai-credits-widget/src/mocked/backendClient.ts Mock backend used by Storybook fixtures/tests — needs a mock revoke implementation so stories/tests can exercise the new state.
packages/ui/src/components/Dialog.tsx Reusable imperative dialog: createDialog(config) / updateDialogStatus / closeDialog (~L45-60). DialogConfig (~L15-28) takes title, body, acceptLabel, rejectLabel, onAccept (may return a Promise, auto-drives pending/success/error status), onReject. No new dialog component needed.
packages/ui/src/components/Button.tsx No built-in "danger" variant (primary/secondary/outline/ghost/pill/text/list, ~L56-108). Existing red-button precedent: packages/streaming-widget PoolCard.tsx (~L103-109) uses variant="secondary" + borderColor="$error" + <ButtonText color="$error">. Reuse this pattern — no new UI primitive.
tests/widgets/ai-credits-widget/states.spec.ts Existing Playwright spec, ~18 states already covered, baseline screenshots in tests/widgets/ai-credits-widget/test-results/. Extend per repo convention (memory: every GoodWidget task needs this spec + committed baselines).

Existing @goodwidget packages to import

  • @goodwidget/ui — already the source of Button, ButtonText, and the createDialog/closeDialog/updateDialogStatus Dialog API; no new import needed beyond what BuyerOperatorCard.tsx already pulls in (L2).
  • @goodwidget/core — already a dependency of ai-credits-widget; no new usage anticipated unless the revoke action needs a shared error/toast utility already living there (verify during implementation).

New components assessed

No new components required — reusable-package placement is not applicable here. The button is a styled Button instance (existing variant + color token), and the dialog reuses the existing generic Dialog.tsx imperative API. Nothing here is generic enough to warrant new packages/ui primitives, and nothing here is specific enough to need a widget-local new component beyond editing BuyerOperatorCard.tsx in place.

Required states, flows, and behaviors

  • operatorConsented === true → "Revoke Operator" button visible, styled red (borderColor="$error", ButtonText color="$error").
  • operatorConsented === false → button not rendered.
  • Click "Revoke Operator" → createDialog(...) opens a confirmation dialog; no revoke call made yet.
    • Dialog copy must state plainly: revoking removes the operator's ability to act on your behalf; any bonus balance will be deducted; any active stream bonuses will stop.
  • Confirm (onAccept) → fires actions.revokeOperatorConsent(). Dialog's built-in async status (pending → success/error) drives the spinner/check/alert icon; on success the card re-renders with operatorConsented: false (mirrors today's consent-grant success update).
  • Cancel/dismiss (onReject or close) → no state change, operator remains active, dialog closes.
  • Revoke failure → surfaced via the dialog's existing error status affordance, consistent with how BuyerOperatorCard.tsx already surfaces consent errors — no new error-handling pattern introduced.
  • Cross-repo dependency: revokeOperatorConsent ultimately needs a live backend endpoint to call. That endpoint is the companion GoodDollar/antseed-integration#22 bounty (POST /v1/accounts/:buyer/operator-reset, planned separately). If that endpoint isn't merged/deployed yet when this is implemented, the backend call in backendClient.ts/mocked/backendClient.ts should be written and tested against the mock immediately, with the real endpoint wired in as soon as it's available — do not block this widget's UI/dialog work on the backend bounty's timeline, but do not fabricate a fake endpoint path either; use the path specified in the antseed-integration plan (POST /v1/accounts/:buyer/operator-reset) so both land in sync.

Execution plan

  1. operatorConsent.ts — add a revoke payload/signing counterpart to buildSetOperatorPayload/signOperatorConsentFromTypedData if the reset endpoint ends up requiring a buyer-signed payload (per the antseed-integration plan's contract findings, the on-chain reset call is owner-initiated, not buyer-signed — see antseed-integration plan's "Required states, flows, and behaviors" — so this step may reduce to: no new signing payload needed, revoke request just needs buyer address). Confirm against the final antseed-integration plan before implementing; do not assume a signature is required.
  2. widgetRuntimeContract.ts — add revokeOperatorConsent: () => Promise<void> to the adapter actions interface, alongside signOperatorConsent (~L85).
  3. backendClient.ts — add a revokeOperatorConsent(buyer: string) method to AiCreditsBackendClient (interface + real implementation) that POSTs to ${accountBase(buyer)}/operator-reset, following the exact shape of submitOperatorConsent (~L412-429) and using readBridgeResponseBody.
  4. mocked/backendClient.ts — add the matching mock implementation so Storybook fixtures and the new Playwright spec can exercise revoke without a live backend.
  5. adapter.ts — implement handleRevokeOperatorConsent, wiring the new runtime-contract action to backendClient.revokeOperatorConsent, updating operatorConsented/operatorAddress state on success.
  6. BuyerOperatorCard.tsx — add the red "Revoke Operator" Button (visible only when operatorConsented), wire its onPress to createDialog({...}) with the required warning copy, onAccept: () => actions.revokeOperatorConsent().
  7. Tests — extend tests/widgets/ai-credits-widget/states.spec.ts with states for: button visibility (consented vs not), dialog open/cancel, dialog confirm → success, dialog confirm → error. Commit new baseline screenshots under tests/widgets/ai-credits-widget/test-results/ per repo convention.
  8. Cross-reference — link this PR to antseed-integration#22's PR once both exist, since the backend endpoint path/auth model is a shared contract between them.

Acceptance criteria

  • "Revoke Operator" button is red ($error token) and only visible when operatorConsented === true.
  • Clicking it always opens a confirmation dialog before any revoke call is made.
  • Dialog copy explains bonus-balance deduction and stream-bonus stoppage.
  • Confirming calls the new backend operator-reset endpoint and updates operatorConsented/operatorAddress state on success.
  • Canceling/dismissing the dialog leaves operator state unchanged.
  • Reuses existing Button/Dialog components and the $error color token — no new UI primitives added to packages/ui or the widget.
  • tests/widgets/ai-credits-widget/states.spec.ts covers the new button/dialog states with committed baseline screenshots.

Human-reviewer checklist

  • Confirm the final request shape expected by antseed-integration#22's operator-reset endpoint (buyer address only, vs. a signed payload) before merging step 1/3 above — this plan currently assumes no buyer signature is required based on the antseed-integration contract findings (transferBuyerOperator is onlyOwner, no buyerSig param), but re-verify against that bounty's final implementation.
  • Visually confirm the red button styling matches the streaming-widget PoolCard.tsx precedent and reads consistently with the rest of BuyerOperatorCard.tsx.
  • Confirm dialog copy wording with Bounty Lead/product before merge (exact phrasing of bonus-deduction/stream-stop warning wasn't specified verbatim in the issue).
  • Verify Playwright baseline screenshots were reviewed, not just auto-generated and committed blindly.
  • Confirm no regression to the existing "Sign Consent" flow (shared card, shared dialog primitive).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    Status
    Prepare AI Task

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions