get.traylinx.com: render pricing from the live API, not literals - #37
Merged
Conversation
The landing page hardcoded a price ladder that had been retired: Explorer $39, Creator $79, Operator $149 -- three plans that no longer exist -- plus "Save 2 months", "All prices in USD", and a "7-day money-back guarantee" that the terms of sale directly contradict (annual is explicitly non-refundable). None of it had been touched since the storefront moved to Starter / Starter-Managed / Builder / Power, so the page had been selling a product we do not offer at prices we do not charge. The pricing section now renders from https://tytus.traylinx.com/public/pricing -- the same source of truth the main storefront and the OpenClaw landing already use. A price change is now Stripe + catalog only, with zero edits to this file. The Monthly/Yearly toggle passes `interval`, so the annual ladder arrives from the API too. Annual terms are shown ONLY on the annual view and ONLY when the API says annual is sellable, using the API's own consent text -- the same words Stripe Checkout puts above the pay button, so the promise cannot drift between the page and the payment. The toggle badge names no number. The saving is not uniform (0.79 to 1.91 months across the ladder), and a badge above ALL cards may only claim what the WEAKEST card actually delivers. The free tier is not sold through the pricing API, so it stays static. Degradation: an unreachable, non-200 or malformed API falls back to a bundled snapshot of the current live values rather than blanking the page or rendering a partial payload. API-supplied text is escaped. Adds web/pricing.test.mjs (14 assertions, network-free -- it stubs fetch, so CI never depends on the pricing API being up) and a "Landing pricing contract" job in pr.yml. Mutation-checked: reintroducing the money-back promise or a numbered toggle badge each turn the suite red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
tytus-cli | 869d4ed | Jul 26 2026, 08:21 AM |
) Three real defects from the codex review, all the same shape: the page could display a price we do not charge. 1. STALE RESPONSE. loadPricing() read the global billingMode again AFTER the await, so toggling month -> year while the month request was in flight let the late MONTHLY response paint itself under an active Yearly toggle. Requests now carry a monotonic id and only the newest one may render. 2. UNVALIDATED TERM. isValidPricing() checked the payload's shape but not its interval. A proxy that drops `interval` returns a structurally perfect MONTHLY payload for a YEARLY request -- exactly the bug that shipped in the pricing proxy earlier the same day, where nothing failed loudly because the payload was valid and only the term was wrong. The requested interval is now demanded back. 3. UNCONFIRMED ANNUAL LADDER. A failed annual load rendered the bundled annual snapshot, which cannot know whether annual is still sellable -- so an outage would advertise a term we may have switched off. A failed annual load now drops to the SHORTER commitment and moves the toggle with it, so the label always matches the amounts. Monthly is always honourable; that asymmetry mirrors the pricing services, where display leniency falls toward the shorter term and never toward the longer one. Three tests added for exactly these (codex point 5): a wrong-interval payload, a superseded in-flight request landing late, and a failed annual load moving the toggle. 17 assertions, still network-free. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
web/ IS the website. Cloudflare Workers serves ./web directly (wrangler.jsonc "assets.directory") and the Pages job does `cp -rL web/. _site/`, so web/pricing.test.mjs would have been downloadable at https://get.traylinx.com/pricing.test.mjs on both deploy paths -- and it carries internal notes about retired prices and the money-back incident. It also turned the Workers build red, which is how it surfaced: that check is green on main and went red only on this branch. Moved to tests/landing-pricing.test.mjs, outside the asset root, and the pr.yml job follows it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
The problem
get.traylinx.comwas still selling a product we retired.Nothing had touched it since the storefront moved to Starter / Starter·Managed / Builder / Power, and nothing was watching, so it drifted silently.
The fix
The pricing section now renders from
https://tytus.traylinx.com/public/pricing— the same source of truth the main storefront and the OpenClaw landing already use. A price change is now Stripe + catalog only, with zero edits to this file.interval, so the annual ladder comes from the API tooconsent_text— the same words Stripe Checkout shows above the pay button, so the promise can't drift between page and paymentDegradation
Unreachable / non-200 / malformed API → bundled snapshot of the current live values. The page never blanks and never renders a partial payload. API text is escaped.
Verified
web/pricing.test.mjs— 14 assertions, network-free (stubsfetch), wired intopr.ymlas "Landing pricing contract". A red run means the page changed, never that production wobbled.Mutation-checked, so the assertions aren't vacuous:
FAIL no refund promise anywhereFAIL the annual toggle badge names no number of monthsRendered against the live API before shipping:
🤖 Generated with Claude Code