Settings: show price before PayPal (subscribe + resize) + merge size/subscription sections#36
Open
ClaydeCode wants to merge 5 commits into
Open
Conversation
The resize flow needs to show how much the monthly price changes, not just the new price. formatPrice cannot express the sign, so the caller would have to hand-roll it.
…ice upfront A resize on a subscribed shard is a billing change, so presenting the size controls and the subscription card as separate sections split one decision across two places. They are now one card, size first, so the flow reads "pick a size -> see the price -> subscribe". The monthly gross price was previously never shown in our UI: it first appeared on PayPal's own screen, for both subscribe and resize. German consumer law requires the price to be visible before purchase, so both flows now render it from the client-side pricing mirror before the buyer leaves for PayPal. The resize flow also shows the delta against the current price. The price renders only when the shard's volume size is known; the controller rejects subscribe with 422 in that case, so a price computed against a 0GB volume would be wrong rather than merely missing. Selecting a size on an unsubscribed shard rendered no confirm button at all: e5d25e9 replaced it with the PayPal revise container, which only renders for an active subscription, orphaning resizeShard() and leaving trial shards unable to resize. The confirm button is restored for that path. The revise button gets a deliberate slot under the size buttons with a width cap, instead of popping in fluid-width next to them.
After the buyer approved a resize, the subscribed path set waitingForRestart and polled in place, leaving the user on a busy Settings page while the shard restarted to apply the new VM size. It now routes to the restart screen, which already polls the shard until it comes back, matching the unsubscribed path. Resize is the only thing that routes to /restart, so the screen now returns to Settings instead of the app grid: the user came from Settings and wants to see the applied size.
The price-before-PayPal rule is a legal constraint and the pricing.js/controller lockstep is a silent-failure trap; neither is visible from the code alone. Also adds the previously undocumented src/lib/ helpers.
…mid-selection The PayPal revise button was only rendered from the selectedSize watcher, so a size selected before subscribing left an empty approval slot once the shard became subscribed: resizeNeedsApproval flipped, the container entered the DOM, but nothing rendered into it. Both triggers now go through one sync method.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #27.
Reworks the subscription/resize area of Settings. The subscribe-side price display is the launch blocker; the rest is the polish from the same issue.
What changed
1. Merged the Size and Subscription sections. They are now one card. A resize on a subscribed shard is a billing change, so splitting them across two sections split one decision across two places. Size comes first, so the card reads top-to-bottom as "pick a size → see the price → subscribe". The card is titled Size alone when billing is disabled, and keeps the
#sizeanchor thatAppIcon/AppStoreEntrydeep-link to.2. The monthly price is now shown before the buyer leaves for PayPal, on both flows:
Gross incl. 19% VAT, with the VAT share broken out, matching how the active-subscription card already renders it.
3. The PayPal revise button now has a deliberate slot under the size buttons with a width cap, instead of popping in fluid-width alongside them.
4. Resize approval hands off to the restart screen rather than polling on a busy Settings page. Since resize is the only thing that routes to
/restart, that screen now returns to/settingsinstead of the app grid.Two things worth knowing
The pricing mirror is in lockstep — verified, not assumed. The issue flagged the risk that
pricing.jslagged controller #298 and would preview net while PayPal charges gross. It has not:origin/mainof the controller computes(vm + disk*0.04) * 1.5 * 1.19, identical topricing.js, and deliberately uses half-up rounding (int(eur*100 + 0.5)) to stay bit-identical to JSMath.round. No drift. (The local controller checkout was stale on an old branch and did show a divergent formula — worth knowing if you check this by hand.)A pre-existing bug was blocking resize entirely for unsubscribed shards. e5d25e9 replaced the confirm button with the PayPal revise container, which only renders for an active subscription. That orphaned
resizeShard()— it had zero template callers, so trial shards saw explanatory text, an empty div and a Cancel link, with no way to resize. The confirm button is restored for that path. This is why item 2's "before confirming" had nothing to attach to.Known issues I did not fix (your call)
sizeIsAvailableonly excludes the current size, so a smaller size can be picked, while the controller rejects any non-increasing resize with 409. Pre-existing, but the new price preview makes it worse: it now advertises "New price: €21.78/month (-€15.71 vs. current)" for an operation that cannot succeed. One-line fix (upsize-only), but it changes which buttons are enabled and might pre-empt intended downsize support, so I left it.BILLING.SUBSCRIPTION.UPDATEDwebhook now strands the user on/restart. The old subscribed path polled in place and surfaced a Refresh button after 60s; the restart screen has no such escape hatch, though it does escalate to "contact support" after 50s. This is the behaviour the issue asked for and matches the unsubscribed path, so I kept it — flagging in case the failure mode matters more than the consistency.Verification
npm run test:unit— 33 passing (14 new).npm run lint,npm run build— clean. Note CI only builds; it does not run the unit tests.@vue/test-utilsfelt like a separate decision). I confirmed the tests have teeth by mutatingcomputeMonthlyPrice(p.vm_size, p.volume_size_gb)to|| 0and watching the null-volume test fail.createSubscription/onApprove) are unchanged apart from the/restarthandoff.Recommended reading order
src/lib/pricing.js— addsformatPriceDeltasrc/views/Restart.vue— one-line return targetsrc/views/Settings.vue— the merged card (bulk of the diff)agents.md— documents the price-before-PayPal rule and the mirror lockstep traptests/unit/pricing.spec.js,tests/unit/settings-pricing.spec.js🤖 Generated with Claude Code