From e50d2104233406aa8998c0708d3fd3f1d7f0dcd7 Mon Sep 17 00:00:00 2001 From: Juan Castro Date: Thu, 17 Sep 2026 13:52:03 -0400 Subject: [PATCH] fix: an unreadable plan is not a free plan (PUT-1843) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Home tab read /marketplace/subscriptions/current and folded every failure into the free state: a non-OK response left `subscription` null, and the catch said so explicitly. So a refusal rendered as a confident "Free" with an Upgrade badge. That is reachable: verification gates are default-on for authenticated routes, and a team seat that still owes its password change is refused there — it then reads Free while its team pays for a tier. A rate limit or a blip does the same to anyone. The plan card moves into its own method so a failed read can return without touching it, leaving the card as it was rather than naming a plan the account does not have. A 200 carrying no subscription still reads Free, which is the one case that actually means it. --- src/gui/src/UI/Dashboard/TabHome.js | 47 ++++++++++++++++------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/gui/src/UI/Dashboard/TabHome.js b/src/gui/src/UI/Dashboard/TabHome.js index 607e47f31..f0c3aa35b 100644 --- a/src/gui/src/UI/Dashboard/TabHome.js +++ b/src/gui/src/UI/Dashboard/TabHome.js @@ -427,27 +427,28 @@ const TabHome = { } }, - async _loadUsageDataUncached($el_window) { - // Load plan data — fetch live from /marketplace/subscriptions/current - // rather than reading `window.user.subscription` (which is set once - // from whoami at page-load and goes stale after subscribe / portal - // cancel until a hard refresh). + /** + * The plan card, read live rather than from the whoami copy, which goes + * stale. Not knowing the plan is not the same as being on the free one, + * so a read that fails returns without touching the card. + */ + async _renderPlanCard($el_window) { + let subscription = null; + try { + const resp = await fetch( + `${window.api_origin}/marketplace/subscriptions/current`, + { headers: { Authorization: `Bearer ${puter.authToken}` } }, + ); + // A refusal is not a free plan: a seat owing a password change 403s. + if (!resp.ok) return; + const data = await resp.json(); + subscription = data?.subscription ?? null; + } catch (e) { + console.error('Failed to load plan data:', e); + return; + } + try { - let subscription = null; - try { - const resp = await fetch( - `${window.api_origin}/marketplace/subscriptions/current`, - { - headers: { Authorization: `Bearer ${puter.authToken}` }, - }, - ); - if (resp.ok) { - const data = await resp.json(); - subscription = data?.subscription ?? null; - } - } catch { - // fall through to free state - } const pastDue = !!subscription && subscription.status === 'past_due'; @@ -535,8 +536,12 @@ const TabHome = { } }); } catch (e) { - console.error('Failed to load plan data:', e); + console.error('Failed to render plan card:', e); } + }, + + async _loadUsageDataUncached($el_window) { + await this._renderPlanCard($el_window); // Load storage data try {