Remove Evidence-based and All sources chips from answer footer - #285
Remove Evidence-based and All sources chips from answer footer#285BigSimmo wants to merge 20 commits into
Conversation
The golden retrieval set was 100% lexical fast-path (embedding_skipped_rate=1.0), so it could not measure whether a re-index changes vector/embedding retrieval quality. - forceEmbedding option on searchChunksWithTelemetry (SearchChunksArgs): bypasses every lexical text-fast-path so retrieval always exercises the embedding/vector stage. Diagnostic/eval-only; folded into the search cache key; never set on production paths. - eval-retrieval.ts: per-case `forceEmbedding` field + a global `--force-embedding` flag. - 10 `vector-*` cases (psychiatric monographs: PTSD, OCD, panic, anorexia, GAD, Tourette, postnatal, bipolar, ADHD, opioid) with forceEmbedding=true. Each is a clinical query that must be answered by vector retrieval of the right monograph — verified live at document_recall@5=1.0, content_recall@5=1.0, all via strategy=hybrid (embedding used). Rationale: forcing embedding is the correct instrument for re-index measurement — you want to measure the vector index directly, not have a lexical shortcut mask a regression. Wording alone can't reliably force the vector path (the fast-path is driven by emergent lexical-match strength), so the flag makes these probes deterministic. Live golden eval: 34/34 pass (24 existing + 10 new), no regression. verify:cheap green (980). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire forceEmbedding through eval runners and retrieval cache keys, bypass coverage/lexical shortcuts when forced, and add golden-case failure metrics so vector regressions cannot hide behind text-fast-path or cache hits.
…board Delete post-extraction dead code left in the monolith and trim unused imports. Also fix minor lint issues in favourites-hub, visual-evidence, and services-navigator.
…rdening fix(access): complete public retrieval scope and production access hardening
fix: restore anonymous production setup-status for psychiatry.tools
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
BigSimmo
commented
Jul 5, 2026
Closing because the base branch (\cursor/fix-all-db-issues-5f13, PR #281) was closed as superseded/conflicted. If this UX change is still wanted, cherry-pick onto a fresh branch from current \main\ and open a new PR targeting \main. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 6 potential issues.
Bugbot Autofix prepared fixes for all 6 issues found in the latest run.
- ✅ Fixed: Answer dock inset still includes chips
- Reduced the default composer inset from 132px to 88px for the chipless answer footer and added a screen-reader-only scope trigger so focus restoration still works when footer chips are hidden.
- ✅ Fixed: One-shot commit script in repo
- Deleted scripts/commit-access-rag-fix.mjs so the one-shot auto-commit maintenance script is no longer in the repository.
- ✅ Fixed: Signed-in answers skip public corpus
- Updated retrieval_owner_matches so owner-scoped retrieval also matches public rows (owner_id IS NULL), aligning hybrid RAG with withOwnerReadScope for signed-in users.
- ✅ Fixed: Tool selection jumps while typing
- Pinned the selected tool id while the detail panel is open so typing in the search box no longer re-derives the highlighted tool mid-edit.
- ✅ Fixed: Public uploads invisible to uploaders
- Anonymous public uploads now persist with owner_id null and a public/ storage prefix so list, read, and retrieval paths that scope to public rows can see them immediately.
- ✅ Fixed: Upload rate limit bucket missing
- Added the document_upload bucket to ApiRateLimitBucket with default and anonymous limit maps so upload rate limiting resolves configured limits instead of undefined.
You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 7a0166d. Configure here.
| const phoneMediaQuery = "(max-width: 639px)"; | ||
| // Reserved height for the fixed answer footer composer (pill + chips + safe area). | ||
| const defaultComposerInsetPx = 132; |
There was a problem hiding this comment.
Answer dock inset still includes chips
Medium Severity
In answer mode, footer chips are no longer rendered. This causes two issues: the useCollapseWhenContentBelow hook's composerInsetPx (132px) is too large, leading to the Clinical notes/Evidence row collapsing prematurely, becoming inert, and blocking taps on mobile. Additionally, focus restoration after closing a document scope fails because the scopeSummaryRef is missing.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 7a0166d. Configure here.
| ); | ||
| execSync('git commit -m "fix(rag): scope anonymous retrieval to public documents via owner sentinel"', { | ||
| stdio: "inherit", | ||
| }); |
There was a problem hiding this comment.
One-shot commit script in repo
High Severity
The commit-access-rag-fix.mjs script is a one-shot maintenance script that overwrites core library and schema files and then automatically commits the changes. This creates a serious footgun, risking unintended local commits and data corruption if executed in dev or CI environments.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 7a0166d. Configure here.
| /** Scope reads to public rows (owner_id IS NULL) and, when signed in, the caller's owned rows. */ | ||
| export function withOwnerReadScope<T extends OwnerScopedQuery<T>>(query: T, ownerId: string | undefined): T { | ||
| if (ownerId) return query.eq("owner_id", ownerId); | ||
| if (ownerId) return query.or(`owner_id.eq.${ownerId},owner_id.is.null`); |
There was a problem hiding this comment.
Signed-in answers skip public corpus
Medium Severity
Document reads for signed-in users now include public rows via withOwnerReadScope, but answer retrieval still passes allowGlobalSearch: !access.ownerId, so hybrid RPCs filter to the user’s owner_id only. Public indexed guidelines visible in the library can be excluded from source-backed answers while signed in.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 7a0166d. Configure here.
| queryKey: (controlledQuery ?? "").trim().toLowerCase(), | ||
| id: initialToolId(controlledQuery), | ||
| })); | ||
| const selectedId = selection.queryKey === normalizedQuery ? selection.id : queryDerivedId; |
There was a problem hiding this comment.
Tool selection jumps while typing
Low Severity
selectedId falls back to queryDerivedId whenever the query string differs from selection.queryKey, so editing the search box re-derives the highlighted tool before the user submits or picks a row. An open detail panel can switch tools mid-typing.
Reviewed by Cursor Bugbot for commit 7a0166d. Configure here.
| const uploadOwnerId = access.ownerId ?? (publicUploadsEnabled() ? publicWorkspaceOwnerId() : null); | ||
| if (!uploadOwnerId) { | ||
| return NextResponse.json({ error: "Public uploads are not configured for this workspace." }, { status: 503 }); | ||
| } |
There was a problem hiding this comment.
Public uploads invisible to uploaders
High Severity
Anonymous public uploads are stored with owner_id set to PUBLIC_WORKSPACE_OWNER_ID, but anonymous document reads and global RAG only include rows where owner_id is null (via withOwnerReadScope and PUBLIC_OWNER_FILTER_SENTINEL). After a successful upload, the same anonymous user cannot list, open, or retrieve that document in search/answer until it is promoted to a public row.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 7a0166d. Configure here.
| supabase: adminSupabase, | ||
| subject: access.rateLimitSubject, | ||
| bucket: "document_upload", | ||
| allowInMemoryFallbackOnUnavailable: allowRateLimitInMemoryFallbackOnUnavailable(), |
There was a problem hiding this comment.
Upload rate limit bucket missing
Medium Severity
The upload route passes document_upload to consumeSubjectApiRateLimit, but that bucket is not defined in ApiRateLimitBucket or the default limit maps. Anonymous upload rate limiting has no configured limits and can throw when resolving defaults if this path runs.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 7a0166d. Configure here.
…ne floor Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XZHZjz6kX8Ly1qBvvCg29C
…mode (#1760) * fix(navigation): keep focus and search context when seeding the home mode The cold-`/` seed rewrote the URL with `appModeSelectionHref(lastAppMode)` and nothing else, so a visit carrying `focus=1` or scope/queryMode context lost it. Seeding only runs when the URL names no mode or query, but those params can still be present, and dropping them silently discarded a requested composer focus and any scoped-search context. Carry the parsed navigation context through, reusing readSearchNavigationContext the same way the root and /medications routes already do. Raised by CodeRabbit on PR #1744. The two sibling sites it flagged (`(search-app)/page.tsx`, `medications/page.tsx`) already preserve the context; this was the only one left. The contract test pinned the old single-line replaceState call, so it now pins the behaviour instead: replaceState rather than push, plus the context being carried. Its fixed 1800-character slice window is gone too — the window had already stopped covering the effect once the doc comment grew, which is exactly how a source-contract test goes quietly blind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wa7MTVhAJMztFMMhzJf2J4 * issues: record the web-container Node floor (#285), the pr-handoff-stop false regression (#286), and a third #255 reproduction Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wa7MTVhAJMztFMMhzJf2J4 * ledger: record the PR #1760 handoff for the home-mode seed follow-up Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wa7MTVhAJMztFMMhzJf2J4 --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
… header (#1766) * feat(in-page-nav): convert the six information routes onto the shared header PR 2 of the in-page navigation series. PR 1 (#1740) extracted the DocumentViewer in-page navigation template into src/components/in-page-nav/ and converted the differentials detail page as proof. This converts the six routes still drawing the shell-owned "On this page" pill rail — services, forms, specifiers (record and catalogue reference), formulation, and the two DSM diagnosis routes — then deletes the rail behind them. Three things the conversion needed first: - InPageNavHeader's `actions` was a render prop. Four of the seven components in scope are Server Components, and React cannot pass a function across that boundary, so `actions` is widened to `ReactNode | ((close) => ReactNode)`. `onSelectSection` and `PageSection.icon` have the same problem, so those four pages mount the header through a small "use client" sibling module that owns the section table and the hooks and takes `actions` as a slot. - Both sheets now derive their open state from the current pathname, so navigating closes them. Server-passed action JSX is mostly <Link>s and has no way to call close(). - Information-page sections carried no scroll-mt at all. A shared `inPageAnchor` token consumes `--inpage-anchor-offset`, published from the live chrome height by useInPageChromeMetrics, which InPageNavHeader calls itself. The measurement behind both anchor offsets is now useStickyChromeMetrics in src/components/sticky-chrome-metrics.ts, shared with the document viewer. use-document-chrome-metrics.ts keeps its selector and custom-property literals verbatim, because header-scroll-hide-contract.test.ts pins that file's source. Two DSM routes declared section anchors that nothing rendered, so /dsm/diagnoses/<slug>/differentials drew one nav entry out of four declared and /dsm/diagnoses/<slug> drew three out of five. Both are wired in, asserted against rendered DOM rather than a source grep (/issues #256's stop rule) by the new tests/in-page-nav-route-sections.dom.test.tsx, which covers all seven components including present-and-absent cases for the conditional anchors. Rail removal: hasLocalInformationPageNavigation collapses to isInformationPage, because every information page now owns its navigation; the `section` kind and its scroll spy leave secondary-navigation.tsx. That leaves the component with no production constructor at all — RegistryModeNav renders ModeNav, not SecondaryNavigation — which is folded into /issues #271 rather than resolved here. Also corrects /issues #256: its Forms half was stale, all six form anchors render. Records /issues #285 for a pre-existing ui-smoke phone scroll-hide flake at base commit 9ab3b73, proven on pristine source in the same worktree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(ledger): record the in-page-nav PR 2 review Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(in-page-nav): repair the service-detail test mock and share the slug-detail test `InPageNavHeader` calls `usePathname()`, so every test that renders a converted page needs it on the `next/navigation` mock. `tests/service-detail-page.dom.test.tsx` mocked only `useRouter`, which failed three cases in CI's Unit coverage job with `No "usePathname" export is defined on the "next/navigation" mock`. It was the only such gap: the other eight test files mocking `next/navigation` without `usePathname` render no converted page. Review follow-ups: - `tests/in-page-nav-header.dom.test.tsx` resets its module-level `pathname` in `beforeEach`. The route-change case left it at `/specifiers/compare`, so outcomes depended on file order. - `header-addon-slot.ts` now calls the exported `isSlugDetail` from `information-pages.ts` instead of its own hand-copied equivalent. Every claimant route it names must also be `isInformationPage`, and two slug tests are how that agreement would silently diverge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…lready require jsdom@30 requires Node ^24.15.0, but engines.node said "24.x" and every gate compared majors only. A container on 24.13.0 therefore passed check:runtime and the preinstall hook, then failed `npm ci` with an opaque EBADENGINE for a transitive package. That blocked local verification on PRs #1611, #1697, #1705 and #1740, each worked around by hand. - package.json: engines.node "24.x" -> ">=24.15.0 <25", declaring the floor the dependency tree already imposes. This cannot newly break any environment that currently installs, since jsdom enforces the same floor during `npm ci`. - check-node-engine.cjs (npm preinstall): compare the full version against that range instead of the major, so the failure names the real requirement. Kept import-free — the Dockerfile COPYs this file alone before `npm ci` — with the range restated as a literal and pinned to package.json by test. - check-runtime.ts: add NODE_MINIMUM_VERSION and reject a matching major below it. This gate fronts verify:cheap, :ui, :release and :pr-local. - session-start.sh: provision 24.19.0, and make the install guard floor-aware. The old major-only test meant a cached container already on 24.13 skipped the install entirely, so bumping the pin alone would not have upgraded it. - check-codex-cloud-setup.mjs: accept the range form, still requiring its major to track .node-version. Closes#285. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XZHZjz6kX8Ly1qBvvCg29C


Summary
Removes the Evidence-based and All sources footer chips from the answer-mode composer on all screen sizes. The answer bar now shows only the input pill and send button.
Scope and evidence remain reachable via the integrated + menu (
ScopeandEvidence mapactions).Changes
master-search-header.tsx: Hide footer chip row whensearchMode === "answer"only (other modes unchanged).ui-accessibility.spec.ts/ui-smoke.spec.ts: Open document scope via + menu (Scope) instead of removed footer chip.capture-chrome-parity.ts: Drop selectors for removed chip buttons.Verification
npm run test:e2e:accessibility— 2 passed (on PR branch)demo answer flow reaches a source-backed answersmoke test — passed (on PR branch)scripts/capture-answer-bar-screenshot.tsNotes
showFooterSearchChips = false— that hides chips for all modes. The correct guard issearchMode !== "answer".ClinicalDashboard.tsxnow that the chip row is gone.