@@ 297,6 297,34 @@ No removed / renamed JSON fields anywhere. No schema modifications to existing t
## Conclusion
+Outcome: shipped — `dcafcb2..HEAD` (HEAD = `8d80e87`); 9 commits across PH1–PH4, two review-fix commits, three doc commits.
+
+Invariants:
+- IV1 — `0002_saved_searches.up.sql` has no FTS / triggers / FK; `savedsearch.Repository` only writes to `saved_searches` (grep on the diff is empty).
+- IV2 — `?owner=` is silently ignored on read (`TestHandler_List_OwnerParamIgnored`) and rejected as `INVALID` on every write path (`TestHandler_WritePaths_OwnerParamRejected` covers POST/PUT/DELETE). Reviewer found the DELETE gap (Important #2); fix in `8d80e87` plus consistency-pass extension to all three write paths.
+- IV3 — composite PK enforced by SQLite; `Create` maps PK collision to `CONFLICT` via `isSQLiteConstraint`; `Update` rename collision tested at `TestHandler_Update_RenameConflict`.
+- IV4 — palette consumes `useProjects`/`useSessions` unchanged; no new query parameters added (grep on `Palette.tsx`).
+- IV5 — `/settings` remains a single route; `find web/src/routes -name 'settings*'` → only `routes/settings.tsx`.
+- IV6 — `Session.id` (composite) and `Session.sessionId` (bare) coexist; URL-path uses (`routes/index.tsx:48`, `routes/project.$.tsx:33`, `Palette.tsx:137`) all pass `s.sessionId`; React `key` in `SessionsTable.tsx:61` keeps composite intentionally.
+- IV7 + UK1 — `validateName` enforces non-empty, ≤64 chars, no `/` on Create + Update; six handler tests cover the cases.
+
+### Assumptions check
+
+- AS1 — held — sessions list is capped client-side at `.slice(0, 50)` (`Palette.tsx:95`); projects list is capped server-side by the existing 50-item default limit (functionally equivalent — reviewer noted but did not flag).
+- AS2 — held — all four saved-search hooks call `invalidateQueries({ queryKey: ['saved-searches'] })` on success; cache-invalidation test `useSavedSearches.test.ts` verifies the round-trip.
+
+### Unknowns outcome
+
+- UK1 — resolved — `validateName` enforces ≤64 chars; chosen as a defensive default for "label" UX. No UI evidence point surfaced during plan or execute that argued for a different cap.
+- UK2 — out of scope — `★ save` button on `/search` (#7) is explicitly that task's design call. This task creates saved searches only from `/settings` per design.
+
+### Review findings
+
+- Critical: none.
+- Important (2 — both resolved):
+ 1. Palette prefetch hooks lacked `enabled: open` and `staleTime: 30_000` (plan 4.2 explicitly required both as RK2's mitigation). Without them, default TanStack Query v5 `staleTime: 0` plus refetch-on-focus/reconnect/mount caused refetch storms on every parent re-render even when the palette was closed. Fixed in `6971b2d` by extending `useProjects`/`useSessions`/`useSavedSearches` with an optional `{ enabled?, staleTime? }` second parameter; existing call sites unchanged (parameter is optional).
+ 2. `Delete` handler did not reject `?owner=`, breaking the consistency of the IV2 write-path rejection contract that `Create` and `Update` enforced. No test guarded any of the three write paths' rejections. Fixed in `8d80e87`: added the same guard to `Delete`, plus a consistency-pass extension that adds `TestHandler_WritePaths_OwnerParamRejected` covering all three verbs.
+
### Deviations from plan
- **PH2 — second `Session.id` call site also fixed (`web/src/routes/project.$.tsx:33`)**: Plan bullet 2.4 already flagged this. Design said "single existing call site"; a second call site shipped in #5 (lethe-web-ui-aggregates) between design approval and execute. User explicitly approved option 1 (fix both) before dispatch. Both call sites now pass `s.sessionId` to `params.id`; React `key` in `SessionsTable.tsx:61` keeps `s.id` (composite) intentionally — IV6's "both coexist" exists for exactly this asymmetry.
@@ 304,6 332,15 @@ No removed / renamed JSON fields anywhere. No schema modifications to existing t
- **PH3 — `internal/server/web/dist/index.html` regen included in the commit** (outside PH3's declared Owns set): Vite stamps content-hash bundle filenames; `npm run build` rewrote `dist/index.html` to point at the new asset hashes. #5's verify recorded the same artifact as a follow-up commit (`f6611d7`); catching it in-phase here is the corrected pattern.
- **Verify — register `savedsearch.Repository`/`savedsearch.Handler` in the e2e test's local steward graph (`cmd/lethe/main_e2e_test.go`)**: PH1 added a `*savedsearch.Handler` `inject:""` field on `Server` and updated `cmd/lethe/main.go` accordingly, but missed the sibling `main_e2e_test.go` which constructs its own steward graph that mirrors `main.go`. `TestEndToEnd_MultiUserIsolation` panicked at `Inject` with `failed to find dependency: target=server.Server targetField=SavedSearches dependencyType=*savedsearch.Handler`. Verify-driven fix-up commit `964d802` adds the import and two `MustServiceAsset` lines, restoring the e2e test. Filed as a deviation because PH1's plan named only `cmd/lethe/main.go` for steward registration and the consistency sweep should have grepped for `MustServiceAsset` siblings before commit.
+### Future work
+
+- **Extract `usePaletteData()` if the prefetch pattern grows a third caller**. Today three call sites pass `{ enabled, staleTime }` to three independent hooks; the duplication is below the rule-of-three threshold. If a fourth surface (e.g. a quick-switcher) wants the same prefetch shape, fold it into one hook.
+- **Add a `★ save` button on `/search` (UK2)** — owned by #7, not this task.
+
+### Verified by
+
+- Browser smoke deferred — see Verify → Notes → "Browser smoke deferred".
+
## Verify
**Result:** passed
@@ 340,4 377,4 @@ Interfaces:
Notes:
- **Browser smoke deferred** — `/settings` saved-search CRUD and the widened palette were not exercised in a real browser. tsc + vitest + the production bundle are clean and the backend is verified by handler tests, but a full Chrome walk requires the running server + an authenticated session. Task #10 (`lethe-oidc-stub`, ✓ Reviewed) ships the `cmd/oidc-stub` binary that unblocks this, but standing it up and driving the UI is out of reach in autonomous mode without user input. Recommended walk: (a) start `lethe` with the OIDC dev stub config, (b) sign in, (c) `⌘K` and confirm projects/sessions/saved-search items appear in fixed group order, (d) `/settings` and create / rename / delete a saved search, (e) press a saved-search row in the palette and confirm it lands on `/search?q=…` (stub).
-- **Verify-driven fix-up commit** `964d802` — PH1 missed `cmd/lethe/main_e2e_test.go`'s steward graph; recorded under Deviations.
+- **Verify-driven fix-up commit** `964d802` — PH1 missed `cmd/lethe/main_e2e_test.go`'s steward graph; recorded under Conclusion → Deviations from plan.