Admin AI assistant screen (mock 1o)
CI / ci (push) Successful in 1m56s
CI / ci (pull_request) Successful in 1m56s

/settings/ai edits the household's AI settings: Provider and Model pill
dropdowns (model list from GET /settings/ai/models plus "Type a model
id"), the API key as its masked last four with a Replace sheet that PUTs
the key then verifies it ("Verified · Oct 1" or the verify error), the
Creativity (temperature) slider, the mono system prompt, Reset (confirms,
then reset-prompt) and Save changes (PATCHes only the edited fields).
A 400 from the endpoint lock says to remove the key before switching
provider; the sheet's Remove key does that.

Unsaved changes are guarded on leaving: the app now runs under a data
router (createBrowserRouter) so the page can use useBlocker, plus
beforeunload for reloads and closing the tab.

Adds the Select pill-dropdown primitive. The test helper declaredStyle
skips pseudo-element rules, which jsdom cannot match.

Maestro task: a0847bb7-54d0-4244-ab15-f82500d0fa7f

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 09:58:56 -04:00
co-authored by Claude Opus 5.5
parent de4f539aea
commit b0e31df099
15 changed files with 1482 additions and 39 deletions
+10
View File
@@ -0,0 +1,10 @@
import { useState, type ReactNode } from 'react'
import { createMemoryRouter, RouterProvider } from 'react-router'
// InDataRouter renders children under a memory data router's splat route at
// path, as main.tsx renders App, so pages that block navigation (useBlocker)
// work in tests. The router is created once, on first render.
export function InDataRouter({ path, children }: { path: string; children: ReactNode }) {
const [router] = useState(() => createMemoryRouter([{ path: '*', element: children }], { initialEntries: [path] }))
return <RouterProvider router={router} />
}