Keys section polish (wave-5 chunk A, owner design): the minimal key-value table

The owner called the old section messy; the redesign is theirs:

- MINIMAL TABLE: provider label left, masked status right (…tail ·
  chars · env), description hints GONE (they took too much space),
  the 'Get a key ↗' link a tiny muted guest on curated rows. Rows
  read as clean key-value pairs.
- ENV-OWNED ROWS ARE READ-ONLY (owner ruling): a real environment
  variable owns the value — the row dims (graph-view graying) and no
  paste field renders, because a paste box on a key the UI cannot
  make live would mislead. Clicking does nothing.
- OPENROUTER UN-PINNED (owner ruling): one provider among 33 — a row
  only when stored, otherwise it's an option in the Add dropdown like
  everyone else. No synthesis, no lifeline description.
- ROW INTERACTION (owner choice): clicking a writable row unfolds a
  paste field + Save (+ Delete for a file-sourced value) beneath it;
  success folds it back to rest; Esc folds without closing the dialog
  (claimed at document CAPTURE phase — the only stopPropagation that
  outruns both React handlers and the dialog's window listener; the
  React-level attempt leaked and closed the dialog, caught live).
- ADD-PROVIDER = the table's last row, in the app's shared dropdown
  language: new ProviderMenuList (fuzzy search + arrow-walk + Enter
  over the 33 pi-ai providers, label + envVar rows) in the shared
  model-menu.tsx, portaled and fixed-positioned like the role menus.
  Picking one unfolds a paste row; Save stores, Cancel/Esc folds.
- Tools & services (Deepgram + the Google pair) keep their own group,
  same table look, same unfold interaction.
- Unknown auth.json vars stay read-only dimmed rows.

Tests: classifyKeys re-pinned (openrouter rides the normal provider
classification; addable drops stored keys), keyStatusLine minimal
('not set' for absent, ' · env' marker for environment-owned). Gates:
typecheck, lint, 759/759, build.

Live-verified: add→save→replace→delete round-trips, fold-on-save,
Esc layering both flows, env-owned rows read-only, addable count
31→30→31.
This commit is contained in:
amosblomqvist
2026-09-22 00:16:23 +03:00
parent f9e3a4c43d
commit 21d288e9f9
3 changed files with 420 additions and 221 deletions
+18 -13
View File
@@ -155,18 +155,20 @@ describe("classifyKeys — the Keys section's two-group projection", () => {
{ id: "openrouter", envVar: "OPENROUTER_API_KEY", label: "OpenRouter" },
];
it("OpenRouter is pinned: synthesized when absent (first boot), found when present", () => {
it("OpenRouter is UN-pinned (wave-5 owner ruling): no row when absent, a row when stored", () => {
const none = classifyKeys([], providers);
assert.equal(none.openrouter.id, "OPENROUTER_API_KEY");
assert.ok(!none.openrouter.present);
assert.equal(none.providerRows.length, 0); // nothing synthesized
assert.ok(none.addable.some((p) => p.id === "openrouter")); // it's just an Add option
const stored = classifyKeys([synth("OPENROUTER_API_KEY", true)], providers);
assert.ok(stored.openrouter.present);
assert.equal(stored.providerRows.length, 1);
assert.equal(stored.providerRows[0]!.id, "OPENROUTER_API_KEY");
assert.ok(!stored.addable.some((p) => p.id === "openrouter"));
});
it("stored provider keys land in providerRows with their provider entry", () => {
it("stored provider keys land in providerRows; the Add dropdown drops them", () => {
const out = classifyKeys([synth("GROQ_API_KEY", true)], providers);
assert.equal(out.providerRows.length, 1);
assert.equal(out.providerRows[0]!.provider?.id, "groq");
assert.equal(out.providerRows[0]!.id, "GROQ_API_KEY");
// the Add dropdown no longer offers groq
assert.ok(!out.addable.some((p) => p.id === "groq"));
assert.ok(out.addable.some((p) => p.id === "deepseek"));
@@ -195,19 +197,22 @@ describe("classifyKeys — the Keys section's two-group projection", () => {
});
});
describe("keyStatusLine — the masked at-a-glance state", () => {
it("present file key: tail, chars, source", () => {
describe("keyStatusLine — the minimal at-a-glance state", () => {
it("present file key: tail + chars, no source suffix", () => {
assert.equal(
keyStatusLine({ id: "X", info: null, present: true, masked: "…ab12 (64 chars)", source: "file", shadowedByEnv: false }),
"…ab12 · 64 chars · saved",
"…ab12 · 64",
);
});
it("env-sourced key reads 'environment'", () => {
assert.ok(keyStatusLine({ id: "X", info: null, present: true, masked: "…ab12 (64 chars)", source: "env", shadowedByEnv: false })?.endsWith("environment"));
it("env-owned key carries the 'env' marker (the row renders read-only)", () => {
assert.equal(
keyStatusLine({ id: "X", info: null, present: true, masked: "…ca10 (73 chars)", source: "env", shadowedByEnv: false }),
"…ca10 · 73 · env",
);
});
it("absent key has no line", () => {
assert.equal(keyStatusLine({ id: "X", info: null, present: false, masked: null, source: "none", shadowedByEnv: false }), null);
it("absent key reads 'not set'", () => {
assert.equal(keyStatusLine({ id: "X", info: null, present: false, masked: null, source: "none", shadowedByEnv: false }), "not set");
});
});