Change product dialog and Machine field

Task 5746 · Done · Coder · created · updated

Part of the epic "Hosting: let users change their products". Plan = Task 5740, PRD = Task 5738, architecture = Task 5739 (§3.3 and §3.6). It builds IN PARALLEL with the API child against the pinned §3.3 shapes, using a mocked client. ## Original ask (verbatim, operator Task 5722) > "user should be able to change his products: dedicated hosting can change to any other instance type and increase the storage to any other offered option. From billing prospective it means that the old product ended and the new started. But technically it is done without reprovisioning the machine. however some of the instance types might be incompatible, in that case ask the user for confirmation and reprovision" > Follow-up: "Instance type change: no draining the node. change instantly." ## Requirements served (verbatim) - R1 "**Change product** action on a dedicated hosting (console + API). The user picks any other offered instance type and/or any LARGER offered storage option. Storage cannot shrink, because EBS cannot." - R4 (UI part) "Running sessions are interrupted. Say so in the dialog; don't wait for them." - R5 (UI part) "show the user that the change needs a reprovision (new machine; local state not carried over beyond what reprovision normally preserves), and proceed only on explicit confirmation." - D3 "Picking an incompatible type shows a confirmation that names the reprovision. Declining changes nothing; confirming reprovisions." PRD C-2 (verbatim): "The Change product picker reuses the existing `HostingPicker` options and labels (single-home), rather than a second catalogue rendering. Change product is added to `HostingActionButtons`, so the list and the detail page stay consistent." ## Acceptance owned (verbatim) - AC-1.1 "The action is present on eligible hostings only. On the page of an `active`, monthly Dedicated hosting, the actions include **Change product** (Owner or Admin). Change product does not appear on: Shared, bundled, `cancelling`, `past_due`, `stopped`, `pending_payment`, `failed`, `terminated`, or a hosting with a change in progress." - AC-1.2 "The picker is complete and legible. The dialog lists every catalogue instance type with name, vCPU, RAM and monthly price. The current type is marked "Current". Unavailable types are disabled with their reason, and incompatible types are labelled "Needs a new machine". It lists every storage option with GB and price. Options ≤ the current size are disabled with "Storage can't shrink". The new monthly total and the amount due or credited now are shown before confirm. Names are never truncated to unreadable fragments. It is usable at every supported viewport width (`docs/standards/product-testing-requirements.md`) with no horizontal overflow. It follows `docs/standards/ui-copy-standards.md`. Verify: … Run the web component test asserting the disabled and labelled states." - AC-4.2 "The interruption is disclosed. Before confirm, the dialog states that running sessions on this hosting's nodes will be interrupted, with their current count. Confirm does not wait for them." - AC-5.2 "Declining changes nothing. Choosing an incompatible type opens a second confirmation. It names the reprovision ("new machine"), what is lost (local disk: worktrees, checkouts, running sessions) and what is kept (hosting, nodes by name, billing). Declining leaves Stripe, the row and the machine unchanged." Live screenshots on dev belong to the verification child. This child closes these ACs at component-test level and must make them hold live. ## What to build (architecture §3.6, pinned) `apps/web/src/lib/hostings.ts`: - `HostingAction` gains `'change'`. `Hosting` gains `machine {instance_id, instance_type, root_gb, up_since} | null` and `change: ChangeOut | null`. - Add the clients `getChangeOptions` (GET `/api/rentals/{id}/change-options`), `quoteChange` (POST `…/change/quote`), `confirmChange` (POST `…/change/{change_id}/confirm` with {payment_method_id?, reprovision_confirmed}) and `cancelChange` (POST `…/change/{change_id}/cancel`). - Types mirror §3.3 exactly: ChangeOptionsOut, ChangeTypeOption, ChangeStorageOption, ChangeQuoteOut (with `lines`, `mode`, `reprovision {loses, keeps}`), ChangeOut (with `steps`). `components/billing/hostingActions.tsx`: add a **Change product** button only when `actions` includes `change`. It opens `ChangeProductDialog`. The server is the gate. `components/hosting/ChangeProductDialog.tsx` (new): - **Step 1, pick.** Reuse `HostingPicker` with an optional `mode: 'change'` and per-item `current` / `disabledReason` / `badge` inputs ("Current", "Needs a new machine", "Storage can't shrink"). Storage gains the same disabled rendering that instance types already have. No second catalogue rendering. - **Step 2, quote.** Show the new monthly price, "Due now $X" or "Credit $X to your next bill", and the Stripe lines. For any type change, show "N running sessions on this hosting will be interrupted." - **Step 3, reprovision only.** A second confirmation via `useConfirm` that names the new machine and what is lost and kept, from `reprovision`. Declining calls `cancelChange`. - **Confirm** calls `confirmChange`, then `settlePayment(…)` for 3-D Secure (`components/addNode/PaymentStep.tsx:48-65`), as Resume does. Show Stripe's decline sentence inline. `pages/org/billing/HostingDetailPage.tsx`: - a **Machine** field: instance id, instance type, root size, and up since; - the in-progress chip: "Resizing storage" / "Changing instance type" / "Reprovisioning" / "Reverting"; - a `refetchInterval` of 5 s while `hosting.change` is in flight; - the change's `error` sentence when the last change was `reverted` or `stuck`; - the steps timeline from `change.steps`. **Quality bar.** These are the result-quality requirements a poor build fails: - Type names and labels wrap and are never clipped to fragments. - The dialog is a full-screen sheet at phone width, with no horizontal overflow at any supported width. - Copy follows `docs/standards/ui-copy-standards.md`, with short labels. - A reprovision confirmation must still name what is lost. Credentials & reach: "All unit/API/component tests (C1–C6) | none | worktree (`./dc check`) | **verified**: needs nothing beyond the worktree." Out of scope: - Do NOT change the purchase flow's behaviour in HostingPicker. The `mode: 'change'` additions are optional props, and purchase rendering stays identical. - Do NOT touch the API, billing, jaahd, the docs or the workflow. - No list-level bulk action (DEC-12 "This changes ONE hosting's product on that hosting's own page (`/org/billing/hosting/:id`), not a list-level bulk action."). The button rides `HostingActionButtons`, which the list also uses, so it appears wherever the server includes `change`. - Epic (verbatim): "Out of scope: shrinking storage. Shared (co-located) hosting tier changes. Customer-owned cloud connections (non-hosting cloud nodes). Auto-tagging and SSM instance profiles (Task 5725). Any existing hosting's record or billing reconciliation." - PRD NC-7 (verbatim): "No draining step, no "wait for sessions" option, and no scheduled ("at renewal") product change. Changes are immediate only." Done means: - AC-1.1 UI: a component test shows the button only when `actions` includes `change`. - AC-1.2: "Run the web component test asserting the disabled and labelled states." Specifically: - Current, Needs a new machine, an unavailable type with its reason, and "Storage can't shrink" on sizes ≤ current; - every type shows name, vCPU, RAM and price; - the long-name wrap case renders the full name; - the new monthly and the due-now or credit amounts render before confirm. - The existing HostingPicker purchase tests stay green and unchanged in expectation. - AC-1.2 widths: an e2e or visual check at every viewport width in `docs/standards/product-testing-requirements.md` shows no horizontal overflow and a full-screen sheet at phone width (screenshots in the report). - AC-4.2: a test shows the interruption sentence with the count for a type change, and no wait on confirm. - AC-5.2: a test shows that an incompatible pick opens the second confirmation naming new machine, loses and keeps. Declining calls `cancelChange` and never `confirmChange`. - A 3-D Secure path test: requires_action → `settlePayment` → confirm again. - The detail page tests cover the Machine field, the chip per state, the error sentence and polling only while in flight. - `./dc check full` is green. The PR is merged to develop, and the Jaah Deploy of the merge succeeds in GitHub.

Outcome

Built the Change product dialog (pick, quote, reprovision confirm, 3DS), the button, and the Machine field, change chip and 5s poll on the hosting page. Full gate green, #5004 merged, Jaah Deploy succeeded. Develop is red on its alembic head since #5003 (not from this PR).

Pull request

Artifacts

Activity

  • Draft
  • Ready
  • Runner started
  • Runner ended (done)