From b9e13b5bca23a13867abdf8a81dd9a1f8cf46653 Mon Sep 17 00:00:00 2001 From: le king fu Date: Mon, 20 Jul 2026 21:21:18 -0400 Subject: [PATCH] fix(gating): keep key-validation errors out of the load lifecycle (#297) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A rejected submitKey dispatched the same ERROR action as a failed boot load, so the CWE-703 retry backoff armed on it and the auto refresh (LOAD_START) cleared the "invalid key" message ~1s after submit — LicenseCard has no local error state, the context is the only source. Split the state: load lifecycle (status/error, retried) vs validation (validating/validationError, never retried). VALIDATE_ERROR leaves status untouched, so a ready license stays ready on a typo'd key (no `ready` regression for gating consumers) and a boot-error retry loop keeps running through a failed validation. Reducer + initial state exported for tests, covered by LicenseContext.test.ts. Co-Authored-By: Claude Fable 5 --- src/components/settings/LicenseCard.tsx | 13 ++- src/contexts/LicenseContext.test.ts | 102 ++++++++++++++++++++++++ src/contexts/LicenseContext.tsx | 61 ++++++++++---- src/hooks/useIsPremium.test.ts | 6 ++ 4 files changed, 164 insertions(+), 18 deletions(-) create mode 100644 src/contexts/LicenseContext.test.ts diff --git a/src/components/settings/LicenseCard.tsx b/src/components/settings/LicenseCard.tsx index 7fb968e..021db1f 100644 --- a/src/components/settings/LicenseCard.tsx +++ b/src/components/settings/LicenseCard.tsx @@ -16,7 +16,8 @@ const PURCHASE_URL = "https://lacompagniemaximus.com/simpl-resultat"; export default function LicenseCard() { const { t } = useTranslation(); - const { status: licenseStatus, edition, info, error, submitKey } = useLicenseContext(); + const { status: licenseStatus, edition, info, error, validating, validationError, submitKey } = + useLicenseContext(); const [keyInput, setKeyInput] = useState(""); const [showInput, setShowInput] = useState(false); const [showMachines, setShowMachines] = useState(false); @@ -178,13 +179,19 @@ export default function LicenseCard() { className="w-full px-3 py-2 bg-[var(--background)] border border-[var(--border)] rounded-lg text-sm font-mono focus:outline-none focus:border-[var(--primary)]" autoFocus /> + {validationError && ( +
+ +

{validationError}

+
+ )}