fix(desktop): make OpenAI key re-enterable after first save in card mint dialog - #4140
Draft
wpfleger96 wants to merge 3 commits into
Draft
fix(desktop): make OpenAI key re-enterable after first save in card mint dialog#4140wpfleger96 wants to merge 3 commits into
wpfleger96 wants to merge 3 commits into
Conversation
…int dialog Once any key resolved, AgentCardMintDialog hid the key-setup panel forever (needsKey is false) — a write-once shadow field with no update path. Users with an expired key (e.g. HTTP 401) had no way to replace it. Add an editingKey boolean state. When false and needsKey is false the dialog shows the mint form with a new 'Update API key' link button near the cost note. Clicking it sets editingKey = true, which re-enters the existing key panel reusing the same save mutation, testids, and Rust seam (card_mint_save_openai_key). On successful save, editingKey resets to false and the user is back at the mint form. A Cancel button is shown only in update mode (editingKey && !needsKey) so first-time setup is unchanged. Also improve the 401 / 'Incorrect API key' error branch in cardMintStore: instead of surfacing the raw OpenAI wire message, emit actionable copy that names the problem (invalid/expired key) and points at the new affordance. Tests: 3919 passing, tsc --noEmit clean. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Finding 1 — source-aware key update (Rust):
card_mint_key_status now returns a layer discriminant ("none" | "global" |
"persona" | "agent" | "process") instead of a bool, mirroring the exact
resolution priority of mint_agent_card. The dialog uses the layer to decide
whether to offer a writable input (none/global) or a read-only redirect
(agent/persona/process) pointing the user to the correct settings surface.
The write seam (card_mint_save_openai_key) is only offered when writing global
will actually become the resolved key. New Rust precedence test
key_status_layer_matches_mint_resolution_priority proves the status layer
matches what mint_agent_card would resolve.
Finding 2 — precise 401 matching (cardMintStore.ts):
Replace bare message.includes("401") with startsWith("Card mint failed (HTTP
401 ") to match only the OpenAI-call envelope. Avatar fetch 401 errors now
pass through unchanged. New passthrough test confirms avatar 401 is not
rewritten.
Finding 3 — tri-state key status row (AgentCardMintDialog.tsx):
"Using your saved OpenAI key" now renders only when keyLayer === "global"
(confirmed, writable key). While query is pending or errors, keyLayer is
undefined and the status row is hidden — no false claims about key existence.
Fail-open mint behavior preserved.
Finding 4 — real tests (cardMintKeyUtils.ts + test):
Extract showKeyPanel, isWritableLayer, isReadOnlyLayer, showCancelButton,
showKeyStatusRow, keyPanelTitle into cardMintKeyUtils.ts. The component imports
and uses these functions directly. AgentCardMintDialog.test.mjs now imports the
production module — removing any function will break the tests. Old predicate-
copy approach deleted.
3938 JS tests passing, 16 Rust card tests passing, tsc --noEmit clean.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
- showKeyPanel now gates only on none||editingKey; read-only layers (agent/persona/process) show an inline provenance row on the mint form with a Why? affordance that opens the redirect panel with Cancel — Mint card is reachable for every resolved layer - Extract resolve_key_layer() pure helper in card.rs; card_mint_key_status delegates to it instead of duplicating the classification logic; the Rust precedence test now calls the production function directly (no test-local closure), also covering process and blank-value cases - card.rs: 999 lines (under the 1000-line ratchet) - e2eBridge.ts card_mint_key_status stub returns "global" (not boolean true) - Component now imports showCancelButton and keyPanelTitle from cardMintKeyUtils.ts and uses them; new showReadOnlyRow helper added - Tests: 3959 JS passing, 2089 Rust passing, tsc --noEmit clean Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.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 a write-once dead-end in the card mint dialog where a user with an expired OpenAI key had no way to replace it.
Source-aware key status (Rust + TypeScript).
card_mint_key_statusreturns a layer discriminant ("none" | "global" | "persona" | "agent" | "process") instead of a boolean. A pureresolve_key_layer()helper incard.rsowns the classification logic;card_mint_key_statusdelegates to it, so the production path is under direct test with no duplicate logic.Mint form always reachable. The key panel replaces the mint form only for
none(first-time setup) or when the user explicitly opens the edit panel (editingKey). Keys from agent/persona/process layers show an inline provenance row on the mint form with a "Why?" affordance; clicking it shows the read-only redirect in a panel with a Cancel button that returns to the mint form — never a terminal state.Precise auth-error matching. The 401 handling in
cardMintStore.tsmatchesstartsWith("Card mint failed (HTTP 401 ")plus the specificIncorrect API keytext, so avatar-fetch 401 errors pass through unchanged.Tri-state key status row. "Using your saved OpenAI key · Update" renders only when
keyLayer === "global"(confirmed writable key). Query pending or errored hides the row without asserting key existence.Real tests. Panel visibility derivations live in
cardMintKeyUtils.ts, whichAgentCardMintDialog.tsximports directly. Tests cover all layers including the mint-reachability invariant (Mint reachable for every resolved layer; onlynonegates setup).card.rs— newresolve_key_layer()pure helper;card_mint_key_statusdelegates to it; 999 lines (under the 1000-line ratchet)card/tests.rs— precedence test callsresolve_key_layer()directly (no test-local closure); adds process-layer and blank-value casestauriPersonas.ts—CardMintKeyLayertype; updatedcardMintKeyStatussignaturecardMintKeyUtils.ts—showKeyPanel,showReadOnlyRow,showCancelButton,keyPanelTitle, and helpers; component imports all of themAgentCardMintDialog.tsx— inline provenance rows for all key sources; key panel only for setup/edit; no unused variablescardMintStore.ts— precise 401 prefix matchinge2eBridge.ts—card_mint_key_statusstub returns"global"(not boolean)tsc --noEmitclean