Cloud first send: recover inline from a paid-plan refusal (#32045)

Draft
TE
3d ago
+223-9

Purpose — HQ #6236 / brief 02; cmux #32045

A free-plan Cloud first send currently reaches the native Cloud-machine cap dialog, but after the person closes it the inline chat reports only a generic failure. The draft stays in the composer; the card lacks a durable way to upgrade or run here. This patch adds that recovery path without provisioning a VM or inventing another payment flow.

Changes

  • Native: CloudStartChat.reply classifies the existing CloudMachineCreation.proRefusal cases as cloud_plan_required. Uncertain connection, copy, and other errors retain cloud_start_failed; run_on_this_mac stays untouched. The native toast and cap dialog remain responsible for the immediate refusal.
  • Page: the start answer retains the typed refusal in CloudStartProgress; the already-toasted refusal avoids a duplicate composer notice. On this code only, CloudStartView keeps the failed machine step and displays Upgrade, Run on This Mac, Not Now.
  • Actions: Upgrade uses the existing https://cmux.com/pricing route; Run Here takes the same runHereSend → persistent-mode → send-once path as the native cap-card choice. Not Now dismisses the inline card while preserving the composer draft. Both New Tab and fresh chat render paths are connected.
  • Language: Upgrade/Run Here are the 21 existing localized labels from CmuxNextCloudUI/Resources/Cloud.xcstrings, mirrored into a small web module. The pane's 7.4 MB catalog needs synchronization later; no new English-only labels are introduced in the UI.

Regressions added

  • Swift: free-plan refusal gets cloud_plan_required, timeout keeps generic code.
  • Bun: native plan refusal is treated as already toasted; start progress retains the plan code without changing generic errors; React markup includes localized Upgrade/Run Here/Not Now only for plan refusal; locale fallback and several languages are covered.
  • Existing key-scoped completion behavior is retained: a late native response clears only its matching start card. Unknown-create retries still use the same durable start key. The native Run Here answer and the new inline Run Here click both retire that key through cloudStartKeys.ended(..., run_on_this_mac) before switching to This Mac; the failed-plan → local-choice regression covers this.

Validation / limits

Reviewed source on feat-cmux-next at 7ab967bf517776c02bfbf36d479de7cf074a0a21, plus earlier HQ receipts and owning PRs. 12 files in the focused branch; GitHub compare reports 0 base conflicts at branch start. Static review and file-encoding checks were performed through the repository connector. No Mac runtime, authenticated free-plan fixture, or full Swift/Bun test run was available in this session; CI and a DEV capture should verify the end-to-end dialog → card → local send / upgrade journey before merge. This is intentionally a draft PR.

Prior work / ownership

  • #32309 already fixed the signed-out first-send message; left untouched here.
  • #32244 owns durable machine/create/pin identity; #33879 owns web key retention, with later completion/cache review follow-ups; this patch leaves those algorithms untouched.
  • #33881 and #33906 concern provider login transfer, outside this slice.
  • #32045 is the original no-machine/free-plan report; this PR targets that precise gap, while other brief-02 failures remain to be audited separately.

HQ: https://github.com/manaflow-ai/cmuxterm-hq/issues/6236 (brief 02)


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Gives free-plan Cloud first sends a durable recovery path after the native machine-cap dialog closes; previously the inline chat only showed a generic failure with no way to upgrade or run locally.

  • Plan refusals are now classified as cloud_plan_required; other failures keep cloud_start_failed.
  • The inline card shows localized Upgrade, Run on This Mac, and Not Now actions; Run Here reuses the native cap-card path, preserves the composer draft, and ends the retry key exactly as the native answer does.
  • Already-toasted refusals no longer raise a duplicate composer notice.