fix(ui): mobile hero search centering, touch targets, and portal hardening - #271
fix(ui): mobile hero search centering, touch targets, and portal hardening#271BigSimmo wants to merge 16 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.
- Degrade invalid bearer tokens to anonymous scope instead of 401 - Allow public document read routes (list, detail, signed-url, search, images) - Align registry routes with medications/differentials auth-signal short-circuit - Let DocumentViewer load public sources without requiring sign-in - Add regression tests and update access-control expectations
Create missing retrieval-support indexes (trgm, composite btree, partial miss log) that were absent or only present under legacy names on live. Update search_schema_health() to accept verified functional equivalents during rollout. Set search_path for pg_trgm gin_trgm_ops in extensions. Verified on linked project: search_schema_health() ok=true, missing=[].
…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.
- Hide footer Evidence/Sources chips on phone hero composers; scope stays in + menu - Suppress bottom-dock composer flash until hero portal slot is ready - Increase composer action/send touch targets to 44px on phones - Update Playwright tests for scope menu, Answer home geometry, and stress fallback
Updates to Preview Branch (cursor/mobile-ui-fixes-d6c9) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
- Remove stray merge conflict markers from master-search-header, ui-stress, globals.css - Restore standalone /applications page and remove redirect to /?mode=tools - Re-export ApplicationsLauncherPage for the restored route - Regenerate docs/site-map.md
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 as superseded by merged work on \main:
Production is live with these fixes. Any remaining unique commits should be cherry-picked onto a fresh branch from current \main. |
Pull request was closed
… 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>
…es (PR 3 of 3) Completes the in-page navigation series. PR 1 (#1740) extracted the template into src/components/in-page-nav/ and converted differentials; PR 2 (#1766) converted the six information routes and deleted the shell-owned pill rail. Three routes were left; this lands two of them and records why the third is not a candidate. Medications (/medications/[slug]) — converted, panel-swap The owner's call: keep the tab swap and drive it from the header's segment track, exactly as differentials/differential-detail-page.tsx does. SectionTabs (a 58-line roving-tabindex tablist) is deleted; activeTab lifts to MedicationRecordPage so the header above the shell can drive it, and the InformationPageBreadcrumbs row goes with it. The tab->section-type grouping moves into the new medication-nav-header.tsx sibling so the segment weights and the rendered panel cannot disagree about what a tab contains. The panel keeps a per-tab id but drops role="tabpanel"/aria-labelledby: the control is now a list of buttons, so claiming the role would name a tab that no longer exists. Factsheets (/factsheets/[slug]) — converted, anchor-scrolling Already mounted InPageNavHeader in its breadcrumb shape; this gives it a real section index. tocFor is deleted rather than ported: it returned display strings with no anchors behind them, painted into an inert <li> "On this page" list, and was wrong in both directions (it named "What is this medicine?" where the page renders "What is <title>?", and never listed the Sources, More-in-topic or Related sections every sheet renders). The replacement derives sections from what each of the five kinds actually renders, with medLite's headings coming from the record. Differentials presentations — recorded exception, not converted The premise did not survive reading the file. It was carried as "a SectionTabs page that swaps panels"; it swaps nothing — MobileTabs is four <Link>s to other routes with "Compare" hardcoded active, which is the multi-route ModeNav pattern the template already carves out. Its candidate sections are also rendered two or three times per breakpoint in different DOM parents, one nested inside another section's anchor, which PageSection.targetIds does not model. Reasoned exception recorded in docs/search-chrome-behaviour.md. Also: delete the orphaned SecondaryNavigation component (/issues #271) Test-only since PR 2 removed the section kind. Its two stated side-conditions turned out not to exist — nothing outside its own test imports it, and tests/mode-nav-contract.test.ts string-matches page-secondary-navigation.tsx, a different file. Its test fixture was literally the medication tab bar, so it lands with the conversion that retired it. Guards tests/in-page-nav-route-sections.dom.test.tsx grows from 7 routes to 12 (one factsheet case per kind) and gains a panel-swap suite for medications, so both halves of /issues #256's stop rule hold: declared ids are proven against rendered DOM, never a source grep. A new `absent` field asserts the therapy and procedure sheets genuinely do not render More-in-topic rather than skipping it. Both new routes are registered in isHeaderAddonSlotOwnedRoute and the claimant enumeration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHmzgER6rv8Va9HbQZw87Q
Summary
Mobile UI fixes plus consolidated platform/access/eval work:
/applicationsrouteMerge strategy
This branch supersedes #269 — merge #271 only for a single handoff, then close#269.
Verification
npm run verify:cheap— pass (1067 tests, lint, typecheck, format)npm run verify:ui— desktop stress scope path fixed; CI re-runningnpm run verify:release— not run (Chromium gate is primary)Production follow-up
Set
OPENAI_API_KEYin hosting env and redeploy after merge.