Skip to content

fix(ui): mobile hero search centering, touch targets, and portal hardening - #271

Closed
BigSimmo wants to merge 16 commits into
mainfrom
cursor/mobile-ui-fixes-d6c9
Closed

fix(ui): mobile hero search centering, touch targets, and portal hardening#271
BigSimmo wants to merge 16 commits into
mainfrom
cursor/mobile-ui-fixes-d6c9

Conversation

@BigSimmo

@BigSimmoBigSimmo commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Mobile UI fixes plus consolidated platform/access/eval work:

  • Resolved merge conflict markers in composer, CSS, and Playwright specs
  • Centered hero search on phones — portal retry hardening + suppress bottom-dock flash
  • 44px touch targets for composer actions on phones
  • Restored standalone /applications route
  • CI fixes: Prettier formatting, scope-menu Playwright fallbacks, gitleaksignore for medication snapshot false positives

Merge 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-running
  • npm run verify:release — not run (Chromium gate is primary)

Production follow-up

Set OPENAI_API_KEY in hosting env and redeploy after merge.

Open in WebOpen in Cursor

BigSimmoand others added 9 commits July 3, 2026 21:00
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
@supabase

supabaseBot commented Jul 5, 2026

Copy link
Copy Markdown

Updates to Preview Branch (cursor/mobile-ui-fixes-d6c9) ↗︎

DeploymentsStatusUpdated
DatabaseSun, 05 Jul 2026 16:28:46 UTC
ServicesSun, 05 Jul 2026 16:28:46 UTC
APIsSun, 05 Jul 2026 16:28:46 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

TasksStatusUpdated
ConfigurationsSun, 05 Jul 2026 16:28:47 UTC
MigrationsSun, 05 Jul 2026 16:28:49 UTC
SeedingSun, 05 Jul 2026 16:28:50 UTC
Edge FunctionsSun, 05 Jul 2026 16:28:51 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@BigSimmo
BigSimmo marked this pull request as ready for review July 5, 2026 16:45
@cursor

cursorBot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@BigSimmo
BigSimmo enabled auto-merge (squash) July 5, 2026 17:04
@BigSimmo

Copy link
Copy Markdown
OwnerAuthor

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.

@BigSimmoBigSimmo closed this Jul 5, 2026
auto-merge was automatically disabled July 5, 2026 17:45

Pull request was closed

BigSimmo added a commit that referenced this pull request Aug 7, 2026
Resolve outstanding-issues conflict by keeping main's #261-#270 design-system tracks and renumbering this PR's SecondaryNavigation and addon-slot follow-ups to #271/#272. Preserve the confirmed #256 diagnosis from this branch.
Co-authored-by: Cursor <cursoragent@cursor.com>
BigSimmo added a commit that referenced this pull request Aug 9, 2026
… 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>
BigSimmo pushed a commit that referenced this pull request Aug 9, 2026
…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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@BigSimmo