fix(wave-d): dead routes, tier-rank inversion, invoice + limit drift#96
Merged
Conversation
… drift
Wave-D frontend findings from BUGHUNT-REPORT-2026-05-18.
D1 (P1-W5-18, L-02): the nav "Get token →" CTA pointed at /get-token, a
route that doesn't exist — the SPA catch-all silently redirected the
conversion funnel's headline button to /. Repointed at /login, the same
route MarketingPage's "Get a token" CTA already uses.
D2 (P2-W5-02): the footer "Security" link pointed at /security (no such
route → 404 → catch-all to /). Repointed at /docs/public/security.md, the
real security doc, matching how ChangelogPage links /docs/public/dpa.md.
D3 (P1-W4-10): ChangePlanModal and TierChangeModal each carried a private
TIER_RANK with the inverted order growth:4, pro:5 — the admin console
showed "DEMOTE" for a pro→growth upgrade. Added one canonical TIER_RANK
table (pro:4, growth:5) to src/api/index.ts, aligned with the backend's
common/plans/rank.go; both modals now import it. Added a unit test pinning
the ladder ordering.
D4 (P1-W4-09): listInvoices did a blind cast of the API response. The
invoices endpoint emits the Razorpay wire shape {id,amount,currency,
status,date,pdf_url} but the Invoice type expected Stripe-style
{period_start,period_end,plan,amount_cents}, so every row rendered
"Invalid Date"/"$NaN"/blank tier. Added an explicit mapInvoice mapper:
amount→amount_cents (direct copy — already smallest-unit, no ×100),
date→issued_at. period_start/period_end/plan are not on the wire and stay
optional; BillingPage renders only what is present.
D5 (P1-W2-06): BillingPage LIMITS had no growth row, so a growth-tier team
fell through to LIMITS.hobby and saw red over-quota bars on a plan they
were within. Added the growth row with api/plans.yaml values.
D6 (P2-W2-17/18): OverviewPage's hardcoded TIER_LIMIT_GB table drifted
from plans.yaml, and the storage tile mixed a decimal-MB numerator with a
binary-MiB denominator. Dropped TIER_LIMIT_GB in favour of summing the
server-supplied per-resource storage_limit_bytes; all storage math now
uses one binary base. MetricsPanel's formatBytes relabelled KB/MB/GB →
KiB/MiB/GiB to match its /1024 divisors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 18, 2026
Merged
mastermanas805
added a commit
that referenced
this pull request
May 19, 2026
) BugBash 2026-05-18 P2 remediation scoped to instanode-web. Off origin/main, no overlap with PR #96 (invoice mapper / TIER_RANK / MetricsPanel MiB / BillingPage growth row / OverviewPage tier limit / dead-route fixes). API layer (src/api/index.ts, sseStream.ts): - P2-19: mapBillingState no longer infers razorpay_configured from subscription_status — a freshly-claimed paid team is no longer denied checkout. Reads an explicit wire flag, defaults true. - P2-W2-16: APIError now preserves agent_action / upgrade_url (402/403 tier-wall envelope) and Retry-After (429) instead of dropping them. - W3 T5: createDeploy / createStack 401s now run the shared handle401 (token clear + /login redirect) — the multipart path bypassed the central interceptor. SSE log stream surfaces a distinct SSEStreamError with status so a 401 mid-stream shows "session expired", not the misleading "deploy too old" copy. - /stacks/new slug: accept slug / stack_id / stack_slug so the polling loop always has a key. Pages: - P2-29: PricingPage downgrade FAQ reworded to support-only (policy). - P2-30 / L-03: pricing comparison grid corrected to 5 tracks (1 feature + 4 tiers); Team coming-soon CTA no longer renders an empty pill. - P2-31: MarketingPage gets a CSS-only mobile disclosure menu ≤880px — phone visitors had no nav at all. - W3 T5: StackCreatePage stops polling a deleted stack (404 → null) and surfaces an error instead of spinning for the full 5-minute window. - L-01: useDashboardCtx skips the counts/billing fetches when /auth/me 401s — a stale token no longer fires 5 failed requests on public pages. - W5 T10: ForAgentsPage MCP package name corrected to instanode-mcp (was unpublished @instanode/mcp); claude/cursor mcp add gets the `--` separator; playground response sample includes the echoed `env`. - ChangelogPage: 2026-05-18 entry added; 2026-05-17 entry gets the auto-deploy CI milestone. SEO (scripts/prerender.mjs, src/lib/routeMeta.ts): - Per-route <head>: each pre-rendered page now ships its own title, meta description, canonical, and OG/Twitter tags — subpages no longer self-canonicalize to the homepage. - routeMeta.titleForPath wired into RouteTracker so SPA soft navigation updates document.title (WCAG 2.4.2). - sitemap.xml generated at build time from the prerender route set — 121 URLs incl. all blog/use-case detail pages (was a stale static 7). public/llms.txt: Tiers section synced with content/llms.txt (Hobby Plus + Growth were missing). Tests: APIError envelope parsing + routeMeta.titleForPath added. Gate: tsc clean · vitest 639 passed / 3 skipped · vite build + prerender OK. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mastermanas805
added a commit
that referenced
this pull request
May 19, 2026
…drift, SSE, 429 hint (#98) Covers every remaining instanode-web P3 finding from BUGHUNT-REPORT-2026-05-18 not already handled by PR #96 (Wave D) or PR #97 (P2 wave): - Global :focus-visible ring in tokens.css — keyboard/AT users had no consistent focus indicator anywhere (W3 T10, WCAG 2.4.7). - favicon.ico + site.webmanifest — browsers' implicit /favicon.ico request 404'd; no web manifest existed (W5 T10). - P3-08: MarketingPage Pro card "multi-env (dev/staging/prod + custom)" drifted from PricingPage's "dev/staging/prod" — aligned to the honest framing. - P3-09: MarketingPage Team card rendered an empty <a href=""> dead CTA pill and the "talk to us" teaser had no contact link — Team CTA now a disabled "Coming soon" pill, teaser links mailto:hello@instanode.dev. - P3-02: getResource() fired a /credentials fetch that 400'd for webhook/storage/queue resources — gated to db/redis/mongo via a named CREDENTIALED_RESOURCE_TYPES set. - streamSSE only matched `data: ` (with space); the SSE spec makes the space optional — no-space `data:` lines were silently dropped. - markdown renderer: content-repo cross-links written as `/use-cases/x.md` hit the SPA catch-all and dead-ended on the homepage — normalizeInternalHref strips a trailing `.md` from internal links. - TeamPage unguarded Promise.all — no .catch(), no cancellation guard; a failed load left the page silently empty. Now mirrors the ResourcesPage/DeploymentsPage load pattern with an error banner. - _headers: rewrote the comment to make unambiguous that the file is a non-operational migration artifact on GitHub Pages. - 429/Retry-After user-facing hint (P2 deferred as P3): new retryHint.ts helper turns the retry-delay into a human "retry in Ns" string; wired into TeamPage's error banner. Reads the delay defensively so it works with or without PR #97's APIError.retryAfter field. Tests: markdown .md-strip cases, retryHint unit tests added. Gates: tsc --noEmit clean · vite build clean · vitest 645 passed, 3 skipped, 0 failed. Co-authored-by: Claude Opus 4.7 (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.
Fixes the Wave-D frontend findings from
BUGHUNT-REPORT-2026-05-18.md.Fixes
D1 —
/get-tokendead route (P1-W5-18, L-02)PublicShell.tsxnav CTAGet token →pointed at/get-token, a route that doesn't exist — the SPA catch-all silently redirected the funnel's headline button to/, breaking it on ~11 PublicShell pages. Repointed at/login(the same route MarketingPage's "Get a token" CTA already uses; confirmed registered inApp.tsx).D2 —
/securityfooter dead link (P2-W5-02)Footer "Security" link pointed at
/security(no route → 404 → catch-all to/). Repointed at/docs/public/security.md, the real doc (public/docs/public/security.mdexists), matching howChangelogPagelinks/docs/public/dpa.md.D3 — frontend
TIER_RANKinverted (P1-W4-10)ChangePlanModalandTierChangeModaleach had a privateTIER_RANKwith the inverted ordergrowth:4, pro:5— the admin console showed "DEMOTE" for apro→growthupgrade. Added one canonical exportedTIER_RANK(pro:4, growth:5) insrc/api/index.ts, aligned with the backendcommon/plans/rank.go; both modals now import it so they can't re-diverge. Added a unit test (index.test.ts) pinning the ladder ordering.D4 — invoice shape mismatch (P1-W4-09)
listInvoicesdid a blind cast. The API emits the Razorpay wire shape{id, amount, currency, status, date, pdf_url}but theInvoicetype expected Stripe-style{period_start, period_end, plan, amount_cents}→ every row renderedInvalid Date/$NaN/ blank tier. Confirmed againstbilling.go::ListInvoicesAPIand the RazorpayInvoicestruct:amountis already in the currency's smallest unit (paise/cents) soamount → amount_centsis a direct copy (NO ×100);dateis a single charge timestamp. Added an explicitmapInvoicemapper.period_start/period_end/planare not on the wire and stay optional — BillingPage now renders the charge date and only shows a tier pill when one is actually present, never fabricated.D5 — BillingPage
LIMITShad nogrowthrow (P1-W2-06)A growth-tier team fell through to
LIMITS.hobby→ red over-quota bars on a plan they were within. Added thegrowthrow withplans.yamlvalues (postgres 20480MB, redis 1024MB, mongodb/webhooks unlimited, 5 deployments, 10 seats).D6 — OverviewPage stale tier table + MB/MiB mismatch (P2-W2-17/18)
Dropped the hardcoded
TIER_LIMIT_GBtable (drifted fromplans.yaml) in favour of summing the server-supplied per-resourcestorage_limit_bytes. Fixed the unit mismatch — the storage tile mixed a decimal-MB numerator with a binary-MiB denominator; all storage math now uses one binary base (GiB tile, MiB usage bars).MetricsPanel.formatBytesrelabelledKB/MB/GB → KiB/MiB/GiBto match its/1024divisors.Gate
npx tsc --noEmit— cleannpx vite build+prerender.mjs— clean (120 static files)npx vitest run— 633 passed, 3 skipped (35 test files); updated the now-stalelistInvoicesshape tests +FIXTURE_INVOICES, addedTIER_RANKpinning tests🤖 Generated with Claude Code