fix(settings): load Settings without a 404, and bound the MCP key list (#2202) - #2784
Conversation
Review —
|
7445683 to
6fb876a
Compare
db37874 to
0524908
Compare
6fb876a to
e8a2eaa
Compare
0524908 to
32600a5
Compare
/review — post-rebase (stacked on
|
e8a2eaa to
659ac93
Compare
32600a5 to
32415e0
Compare
|
merge-train (#2896, dry run): pushed |
659ac93 to
fd89c63
Compare
32415e0 to
c5d2737
Compare
fd89c63 to
2059649
Compare
c5d2737 to
b2a5fc9
Compare
2059649 to
6dd3b9f
Compare
#2202) Two papercuts found together, on the one page a 31-page sweep flagged as the only source of a page-level console error — and it produced one on every tab. **The 404.** Settings read `public_chat_url` through the generic `GET /api/settings/{key}`, which answers 404 for a key nobody has written. The store already treated 404 as "unset", so nothing was broken; what was lost was the signal — a real failure on that call looked exactly like the ordinary case — and no client-side handling can suppress the browser's own network log, which is why the fix is a route and not a try/catch. `GET /api/settings/public-chat-url` answers 200 with `value: null` when unset, following the `/mcp-url` precedent: a named route for a named setting, declared above `/{key}` (Invariant #4). The generic route's 404 is deliberately unchanged — it is the documented contract for every other key and is read outside this repo. Writing the spec found a SECOND key with the same defect, unnamed in the issue: `platform_default_model`, 404ing eight times per Settings load. It needed no new route — `/api/settings/feature-flags` already carries the resolved value — so the page now reads it from there, which is also more correct: the control shows what the platform will actually use instead of blank. **The unbounded list.** MCP Keys rendered every key an instance had ever minted: measured 306, of which 294 revoked (96%), ~71KB of DOM text, no filter, no bound. Agent keys accumulate structurally — one per agent, one per #1854 rotation, one per ephemeral ghost — so the page grows for the life of the instance and the 12 keys that still work are buried in the 294 that do not. Revoked keys are now hidden behind an explicit toggle that STATES the count, the list is searchable by name/prefix/agent, and rendering is bounded at 25 rows with a "Show more". The rules are pure (`utils/mcpKeyList.js`) because vitest runs `environment: 'node'`; the non-admin agent-key filter is carried through unchanged and asserted, since it is an access rule wearing a filter's clothes. Three empties, three next actions — "No API keys" was a lie to an operator holding 294 revoked ones — but ONE piece of chrome: the wording is computed and only the action row branches, because three copies of the markup would have tripled this file's palette-class count. The new controls are built from the Base* primitives (#2122), and the two container borders they need are paid for by converting the create form's hand-rolled name input and description textarea to `BaseInput`/`BaseTextarea` in the same component: `McpKeysTab.vue` raw_gray 97 -> 83. Baseline edited by hand, not regenerated. Verified against a local dev instance (a key created and revoked to exercise the toggle, removed afterwards): every tab loads with zero 404s and zero console errors, the list renders 2 of 3 keys with "Show revoked (1)" and "2 active", the toggle reveals the revoked row while staying bounded, search narrows to a named empty state that offers the way back, and the converted create form renders and binds in both themes. Red without the fix on both halves. Frontend unit suite 134 files / 2949 tests green. Fixes #2202 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q19uRCksdn4DiRAJ55rfpZ
…route-set guard (#2202) — mechanical, per the merge-train note on the PR Train #2896's regression diff: the four route tests raised 'no current event loop' on the CI interpreter (get_event_loop() on a fresh main thread), and test_1028_settings_package pinned the post-split route set, so the new GET /api/settings/public-chat-url read as a route invented by the split. Neither could surface on this PR's own CI — a PR against a feature branch does not run backend-unit-test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
merge-train: rebased onto |
…pi/settings/public-chat-url (#2202) — mechanical, per the merge-train note on the PR BaseInput declares no size prop, so size="sm" fell through to the <input> as an HTML attribute the browser ignores. The new route joins its /mcp-url precedent in api-endpoints.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Addendum: the note resolution above briefly pushed the baseline without the comma after |
b2a5fc9 to
f7bccc1
Compare
vybe
left a comment
There was a problem hiding this comment.
merge-train: validated (lane B), rebased onto dev, all checks green on the retargeted run.
Stacked on #2783 (
fix/2771-table-cell-markdown) → #2782 → #2781 → #2780 → #2778. Part of epic #1430.Fixes #2202
1. The 404 on every tab
Settings read
public_chat_urlthrough the genericGET /api/settings/{key}, which answers 404 for a key nobody has written. The store already treated 404 as "unset", so nothing was broken — what was lost was the signal: a genuine failure on that call looked exactly like the ordinary case. No client-side handling can suppress the browser's own network log, which is why the fix is a route and not atry/catch.GET /api/settings/public-chat-urlanswers200 {value: null}when unset — the/mcp-urlprecedent, a named route for a named setting, declared above/{key}(Invariant #4, pinned by a test: declared after it, the literal path is swallowed as a setting named "public-chat-url" and 404s again, silently). The generic route's 404 is deliberately unchanged — it is the documented contract for every other key and is read outside this repo.Writing the spec found a second key the issue never named:
platform_default_model, 404ing eight times per Settings load. It needed no new route —/api/settings/feature-flagsalready carries the resolved value — so the page reads it from there. That is also more correct: the control now shows what the platform will actually use instead of blank.2. The unbounded key list
Measured on the reporter's instance: 306 keys, 294 revoked (96%), ~71KB of DOM text, no filter and no bound. Agent keys accumulate structurally — one per agent, one per #1854 rotation, one per ephemeral ghost — so the page grows for the life of the instance and the keys that still work are buried in the ones that do not.
Three empties, three next actions. "No API keys" was a lie to an operator holding 294 revoked ones. But one piece of chrome: the wording is computed and only the action row branches, because three copies of the markup would have tripled this file's palette-class count.
Primitives, and paying for them
The new controls are
BaseInput/BaseToggle/BaseBadge/BaseButton(#2122) rather than another hand-rolled class string. The two container borders the filter bar and footer need are paid for by converting the create form's hand-rolled name input and description textarea toBaseInput/BaseTextareain the same component:McpKeysTab.vueraw_gray 97 → 83. Baseline edited by hand, not regenerated (a wholesale scan run deletes therefrozenblock).Verification
Against a local dev instance, with a key created and revoked to exercise the toggle (removed afterwards — the instance is back to 2 keys):
Revokedbadge in the rows;Negative control: with the three frontend files reverted, both halves fail — the 404 assertion lists
public_chat_url, and the panel assertions cannot find the toggle.The backend route has its own unit test (
tests/unit/test_2202_public_chat_url_route.py): unset →value: null, configured → the value, the same key the writer writes, the admin gate, declaration order, and that the generic getter still 404s. Its two source-reading checks take no fixture so they run without the backend venv; they were executed locally. The route itself was verified through a stand-in on the local proxy, because the running instance is ondev— called out plainly rather than implied.Frontend unit suite: 134 files, 2949 tests green (16 new for the list rules).
One console-error exclusion is documented in the spec: WebSocket handshake noise. The subject is Settings' own errors; the socket is a global concern present on every page and absent from any harness serving the bundle without a WS proxy. What could regress from Settings — the ticket mint — is asserted as an HTTP call instead.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Q19uRCksdn4DiRAJ55rfpZ