Skip to content

Finish the PsychSift rename and re-cut the sidebar lockup as a header band - #2600

Merged
BigSimmo merged 20 commits into
mainfrom
claude/clinical-guide-redesign-ir5zcj
Sep 4, 2026
Merged

Finish the PsychSift rename and re-cut the sidebar lockup as a header band#2600
BigSimmo merged 20 commits into
mainfrom
claude/clinical-guide-redesign-ir5zcj

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • Rebrand the sidebar and phone-drawer heading to PsychSift, and share one lockup between them. Both surfaces opened on a heading that read "Clinical Guide". The type is now the shared-home-brand setting from master-search-header, matched line for line rather than re-invented — 18px/800 on the display tracking step for the name, 12px/500 in --text-muted for the strapline — so the drawer, the sidebar and the app header are one lockup. That call site's measured rejections carry over and are quoted where the classes live: --text-soft fails contrast at 3.07:1, and an 11px strapline reads bottom-light.
  • The strapline moves to BRAND_CATCHPHRASE_BARE. Under a wordmark it is an identity label, not a sentence, which is the position @/lib/brand documents the unpunctuated variant for.
  • The header becomes a band rather than a row. One corner wash behind the mark, dissolving before it reaches the close control, over a vertical surface fade that only resolves in dark. The desktop header bleeds to the edges of its aside so it reads as a header, not a card inside the padding. The close control keeps its 48px tap target and resolves into the accent on hover.
  • New --brand-band-wash role token rather than a mix written at the call site: the themes need opposite recipes to land on the same read. --clinical-accent-soft is #eff5fc in light and vanishes against the white panel; in dark it is already a deep blue that carries on its own. The divider stays a real border-b — a pseudo-element rule would disappear in forced-colors, which is exactly where a divider matters.
  • Finished the 2026-08-28 rename everywhere else it had been left. BRAND_NAME became "PsychSift" then, but only the module and the surfaces reading it moved; every hand-typed occurrence stayed. Renamed: the sr-only dashboard h1; the five navigation aria-labels (Search…, … collapsed sidebar, … sidebar, Close … menu, Open … menu); the sign-up title; the calculator search empty state; four design-scratch mockups; and public/llms.txt, which had described the product under the old name since 2026-07-07 and is finding L86 of the 2026-09-02 repository audit. Until now the product introduced itself by one name and labelled its own navigation with another.
  • Recurrence guard in tests/brand-copy.test.ts. It cannot work the way that module's other guards work — those forbid retyping a line that has an export to import instead, and "PsychSift" is a literal in over a hundred files, so routing them all through an import would be a migration rather than a guard. It runs the other way round: the retired name may not appear in any tracked src file or in llms.txt. "Clinical Guideline"/"Clinical Guidelines" pass, since those are document titles in the corpus, not the product's name. public/llms.txt also joins the existing retyped-line scan, because living outside src is how it escaped the brand gate for two months.
  • Re-measured the drawer strapline fit now that it renders at 12px rather than 10px: a 167px title column at 5.91px per character is 28 characters at 320px, down from the 32 the previous setting allowed. The bound is tightened to match and now guards the bare variant, which is what that surface actually renders.

RAG impact: no retrieval behaviour change — this diff is brand copy, aria-labels and header styling; it touches no file under src/lib/rag/, no retrieval RPC, no ranking or selection surface, and no golden fixture.

Verification

  • npm run verify:pr-localall 20 stages completed, none failed: check:runtime, check:installed-lock-parity, format:changed, check:diff-integrity, sitemap:check, check:repo-awareness-snapshot, docs:check-index, docs:check-inventory, docs:check-scripts, docs:check-links, check:branch-review-ledger, check:outstanding-issues, check:ledger-write-discipline, lint, typecheck, test, build, check:rag:fixtures, check:medication-interactions, check:medication-lexicon-report.

    A first run of this gate reported three failing test files — clinical-hazard-controls, privacy-readiness-contract, rag-plan-package-parity. All three were the same artefact of this container's shallow clone (75 commits): each failure read reviewedCommit does not exist <sha>, and git cat-file -e confirmed the three SHAs were absent locally while .git/shallow was present. After git fetch --unshallow the three suites pass (27/27) and the whole gate is green. Nothing in this diff touches a governance manifest.

  • UI verification not run: npm run plan:browser escalates to the full suite because src/app/globals.css is a shared foundation, and CI re-runs the complete Chromium suite on this change — so a full local verify:ui would buy a verdict GitHub is about to reach. What ran locally instead is a focused browser proof on Chromium, targeting every spec that referenced a renamed label:

    • tests/ui-smoke.spec.ts (whole file) — 104 passed, 2 failed
    • tests/ui-accessibility.spec.ts, ui-overlap.spec.ts, ui-route-coverage.spec.ts, ui-phone-scroll-submitted-root.spec.ts48 passed, 0 failed
    • tests/ui-sidebar-live-mockup.spec.ts (chromium-mockups) — 2 passed

    The two ui-smoke failures are document frame stretches the canvas owner at phone and desktop and document viewer puts the PDF preview first with pinned evidence after it on mobile, both failing on expect(locator).toBeVisible() for pdf-canvas-scroll canvaselement(s) not found. They are not this PR's. Re-ran both at be15a2c, the commit this branch started from, in a separate worktree with none of these changes: they fail identically there. The PDF canvas does not render under this container's Chromium; the diff touches no document-viewer code.

  • Design gates: check:type-scale, check:icon-scale, check:design-drift-ratchet — green (inline styles 228/232, zero-importer components 16/16; neither ceiling moved).

  • Visual check: drawer and expanded sidebar screenshotted in light and dark; strapline measured at 320/360/390px for truncation.

  • Not run: npm run verify:release (no release or handoff confidence claim), and the live retrieval/answer evals (no retrieval, ranking, chunking or answer-contract surface is touched).

Risk and rollout

  • Risk: low — selector risk rather than behaviour risk. Five aria-labels and one sr-only heading changed, so any spec or helper still matching the old strings breaks; every such reference in tests/ moved in this PR and the affected specs were run. No data, auth, API, migration, or dependency change. The one styling change with reach beyond the header is the new --brand-band-wash token, added in both theme blocks and read by nothing else.
  • Rollback: revert the two commits on this branch; nothing here writes external or persistent state, so no data migration or cleanup follows.
  • Provider or production effects: None. No OpenAI, Supabase, or Railway call was made, and no provider-backed gate was run.
  • RAG impact: no retrieval behaviour change — no file under src/lib/rag/, no retrieval RPC, no ranking, selection or chunking surface, and no golden fixture is touched.

Clinical Governance Preflight

This change is brand copy, aria-labels and header styling. It touches no ingestion, answer generation, search or ranking, source rendering, document access, privacy, production environment, or clinical output — so each item below holds unchanged rather than by new mitigation. In particular, no clinical decision-support behaviour changed, so the deployment classification is unaffected.

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

Notes

  • The renamed mockups (sidebar-live-mockup, warning-line-mockups, warning-consolidation-mockups, calculator-mockups/search-detail-mockup, app/mockups/mode-dropdown) are excluded from the repo's other gates as design scratch, but were renamed anyway and are in scope for the new guard: a prototype of the sidebar that still carries the old name is exactly what the next version gets copied from.
  • Historical records were deliberately left alone — the branch-review ledger, docs/archive/**, docs/redesign/** and the audit that reported this drift all describe the product as it was named at the time, and rewriting them would falsify the record. One line of live documentation (docs/design-system/ADOPTION.md, "the mobile Clinical Guide sidebar") was updated because it describes current behaviour.
  • origin/main was merged in after the work was assembled (one late sync, clean git merge-tree), and the rename was re-verified against the merged tree.

🤖 Generated with Claude Code

https://claude.ai/code/session_01K8rMooUReCqvW8jLLx3k43


Note

Low Risk
Mostly copy, aria-labels, and header CSS plus test selector/wait updates; no auth, API, or data-path changes. Residual risk is missed stale "Clinical Guide" strings or brief CI timing sensitivity on other streamed shells not yet using the generalized settle wait.

Overview
Completes the PsychSift rename on hand-typed surfaces that still said Clinical Guide—sr-only dashboard heading, sidebar/drawer aria-labels and search copy, auth and calculator empty states, public/llms.txt, and related Playwright expectations—while aligning live docs (ADOPTION.md) with the new name.

Sidebar and mobile drawer now share one brand lockup (wordmark + bare strapline from @/lib/brand, typography matched to master-search-header), a corner accent wash via new --brand-band-wash in globals.css, and quieter close/hover styling so both entry points read as the same product header.

Brand regression guards expand: tests/brand-copy.test.ts scans llms.txt, forbids the retired product name across src + that file, and tightens the drawer strapline limit for BRAND_CATCHPHRASE_BARE after the 12px strapline change.

Flaky UI tests get targeted hardening for Suspense/streamed-shell duplicate DOM: openWorkspace() in caring-contacts waits on both caring-contacts-rail and caring-contacts-phone-dock; Sources specs use expectSingleSettledOwner for catalogue/browse data-testid roots. Inbox JSON records the caring-contacts CI investigation (and cancels a malformed duplicate note).

Reviewed by Cursor Bugbot for commit a90e369. Configure here.

The phone drawer and the expanded desktop sidebar both opened on a heading
that read "Clinical Guide" — the product's old descriptive name — set as a
plain row of text on the panel's own white. The main app header had already
moved to the PsychSift lockup, so the two entry points to the same
navigation disagreed about what the product is called.

The heading is now BRAND_NAME, and the two surfaces share one lockup:

- Type is the `shared-home-brand` setting from master-search-header, matched
  line for line rather than re-invented — 18px/800 on the display tracking
  step for the name, 12px/500 in --text-muted for the strapline. That call
  site's measured rejections carry over and are quoted where the classes
  live: --text-soft fails contrast at 3.07:1, and an 11px strapline reads
  bottom-light.
- The strapline moves to BRAND_CATCHPHRASE_BARE. Under a wordmark it is an
  identity label, not a sentence, which is the position `@/lib/brand`
  documents the unpunctuated variant for.
- The header becomes a band rather than a row: one corner wash behind the
  mark that dissolves before it reaches the close control, over a vertical
  surface fade that only resolves in dark. The desktop header bleeds to the
  edges of its aside so it reads as a header, not a card inside the padding.
- The close control keeps its 48px tap target and resolves into the accent
  on hover instead of a neutral fill.

The wash strength is a new --brand-band-wash role token rather than a mix
written at the call site, because the themes need opposite recipes to land
on the same read: --clinical-accent-soft is #eff5fc in light and vanishes
against the white panel, while in dark it is already a deep blue that
carries on its own. The divider stays a real border-b — a pseudo-element
rule would disappear in forced-colors, which is where a divider matters.

Re-measured the drawer strapline fit at 320px now that it renders at 12px
rather than 10px: a 167px title column at 5.91px per character is 28
characters, down from the 32 the previous setting allowed. The bound in
tests/brand-copy.test.ts is tightened to match and now guards the bare
variant, which is what that surface actually renders.

Verified: lint, typecheck, brand-copy/sidebar/design-system contract suites,
type-scale, icon-scale and design-drift-ratchet all green; drawer and
sidebar screenshotted in light and dark at 320/360/390 and 1440.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8rMooUReCqvW8jLLx3k43
BRAND_NAME became "PsychSift" on 2026-08-28, but only the module and the
surfaces that read it moved. Every hand-typed occurrence of the old name
stayed where it was, so the product introduced itself by one name and
labelled its own navigation with another: a sr-only page heading, five
sidebar and drawer aria-labels, the sign-up title, the calculator empty
state, four design-scratch mockups, and `public/llms.txt` — the file that
tells agents what this product is.

Renamed on every product surface:

- The sr-only h1 on the dashboard, so the accessible page title matches the
  header above it.
- "Search Clinical Guide", "Clinical Guide collapsed sidebar", "Clinical
  Guide sidebar", "Close Clinical Guide menu" and "Open Clinical Guide menu"
  — the labels a screen-reader user hears for the navigation whose visible
  heading already said PsychSift.
- "Create your Clinical Guide account" on the sign-in panel.
- "ask Clinical Guide below" in the calculator search empty state.
- `public/llms.txt`, which had described the product under the old name
  since 2026-07-07 and is finding L86 of the 2026-09-02 repository audit.

The mockups are renamed too, though the gates exclude them: a prototype of
the sidebar that still carries the old name is exactly what the next version
gets copied from.

The recurrence guard is a new case in tests/brand-copy.test.ts. It cannot
work the way the module's other guards work — those forbid retyping a line
that has an export to import instead, and "PsychSift" is a literal in over a
hundred files, so routing them through an import would be a migration rather
than a guard. It runs the other way round: the retired name may not appear in
any tracked src file or in llms.txt. "Clinical Guideline"/"Clinical
Guidelines" pass, since those are document titles in the corpus and not the
product's name. `public/llms.txt` also joins the existing retyped-line scan,
because living outside src is how it escaped the brand gate for two months.

Verified: format, lint, typecheck, brand-copy (now 1,429 cases), the sidebar,
mockup and design-system contract suites, and focused selection over every
changed source file — all green. Browser journeys next.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8rMooUReCqvW8jLLx3k43
@supabase

supabase Bot commented Sep 4, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_596b7123-f460-458e-ac49-85cdf5faaa4f)

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 66e448a9-526b-4940-b8ce-d98174897795


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T08:08:14.454192Z 62e067c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@BigSimmo
BigSimmo enabled auto-merge (squash) September 4, 2026 09:46
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_51b76146-2104-4fc7-9735-0e54ec0a7fd0)

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

CI triage

CI failed on this PR. Automated classification of the 2 failed job(s):

  • Production UI (1)not baselined: this job did NOT run on the main comparison below (path-scoped skip), so that run says nothing about it either way. Treat the comparison as absent, not green, and inspect the failing step.
  • PR requiredneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.

Compared with main CI run #15588 (success). That run's conclusion is an aggregate and did not exercise Production UI (1).

Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger.

Production UI shard 1 went red on this branch after it merged main, on a
test this PR does not touch:

  strict mode violation: getByTestId('sources-publishers-main')
  resolved to 2 elements
    1) …aka getByTestId('mobile-composer-reserve-pad').getByTestId(…)
    2) …aka getByTestId('sources-publishers-main').nth(1)

Not this branch's. The same test fails identically on origin/main at
7ca2ea1 with none of this branch's changes — there on
`sources-topics-main`, which is the same defect landing on whichever browse
route the run reaches first. The Sources redesign (#2593) added this spec,
and its own push run on main was cancelled, so Production UI never returned
a verdict on it.

The mechanism is the one `tests/playwright-settlement.ts` already documents
as #93: Next streams the page, React first renders the `<main>` outside
`mobile-composer-reserve-pad` and then relocates it in, and for that window
both copies are in the tree. Measured on a local production build at 320px:
at `domcontentloaded` the element sits outside the pad with `offsetParent`
null, and by 1.5s the same test id resolves inside the pad. `page.goto` can
return inside that window, and Playwright strict mode fails on the second
element the moment it sees it — it does not retry.

So the seven page-root assertions in this spec now go through
`expectSingleSettledOwner`, which is the repo's own remedy for this class
and is a *stronger* assertion than the `toBeVisible()` it replaces: it
polls until the test id resolves to exactly one element and that element is
visible. A duplicate `<main>` is an accessibility defect in its own right,
so requiring convergence rather than filtering to the visible copy is the
right reading here — `visibleByTestId` would have passed while two landmarks
were still in the tree. Nothing is skipped, disabled or quarantined.

Reproduced and fixed, both on a local production build via the repo runner:
- before, on origin/main 7ca2ea1 in a clean worktree: 1 failed, the
  strict-mode violation quoted above.
- after, on this branch: `tests/ui-sources.spec.ts` 7 passed (51.0s). The
  previously failing case takes 28.1s, which is the settle poll waiting out
  the relocation — evidence the duplicate is real and does converge, not a
  locator dodged around it.

Also green: lint, typecheck, and check:diff-integrity (153 -> 154 cases).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8rMooUReCqvW8jLLx3k43

BigSimmo commented Sep 4, 2026

Copy link
Copy Markdown
Owner Author

Production UI (1) went red on 093ff754, on a test this PR does not touch — fixed in 48edb3c5.

The failure:

strict mode violation: getByTestId('sources-publishers-main') resolved to 2 elements
  1) <main data-testid="sources-publishers-main" …> aka getByTestId('mobile-composer-reserve-pad').getByTestId('sources-publishers-main')
  2) <main data-testid="sources-publishers-main" …> aka getByTestId('sources-publishers-main').nth(1)

tests/ui-sources.spec.ts:140, in "Sources remains operable at phone width and under accessibility media".

It is not this branch's. The same test fails identically on origin/main at 7ca2ea1cb in a clean worktree with none of this branch's changes — there on sources-topics-main, the same defect landing on whichever browse route the run reaches first. This branch's diff is brand copy, aria-labels and header styling; it touches nothing under src/components/sources/**. The spec arrived with the Sources redesign (#2593), whose own push run on main was cancelled, so Production UI never returned a verdict on it — which is why this surfaced here rather than on main.

The mechanism is the one tests/playwright-settlement.ts already documents as #093. Next streams the page; React first renders the <main> outside mobile-composer-reserve-pad and then relocates it in, and for that window both copies are in the tree. Measured on a local production build at 320px: at domcontentloaded the element sits outside the pad with offsetParent null, and by 1.5s the same test id resolves inside the pad. page.goto can return inside that window, and Playwright strict mode fails on the second element the moment it sees it — it does not retry, so this is not something a longer timeout would ride out.

The fix, since no other PR carries one: the seven page-root assertions in that spec now go through expectSingleSettledOwner, the repo's own remedy for this class. It is a stronger assertion than the toBeVisible() it replaces — it polls until the test id resolves to exactly one element and that element is visible. A duplicate <main> is an accessibility defect in its own right, so requiring convergence is the right reading here; visibleByTestId would have passed while two landmarks were still in the tree. Nothing is skipped, disabled or quarantined, and the change no-ops once main carries it.

Evidence, both on a local production build through the repo runner:

  • Before, on origin/main 7ca2ea1cb in a clean worktree — 1 failed, the strict-mode violation above.
  • After, on this branch — tests/ui-sources.spec.ts 7 passed (51.0s). The previously failing case now takes 28.1s, which is the settle poll waiting out the relocation: evidence the duplicate is real and does converge, rather than a locator dodged around it.

Also green on the change: lint, typecheck, and check:diff-integrity (153 → 154 test cases).

No re-run was spent: the failure reproduces deterministically on the base branch, so it was never a candidate for one.


Generated by Claude Code

@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_b55ffd02-51c1-4f94-8c5d-2bd6c13b8063)

BigSimmo pushed a commit that referenced this pull request Sep 4, 2026
Same strict-mode "resolved to 2 elements" shape seen on PR #2613's
queued issue, now also on caring-contacts-phone-dock (PR #2600).
Three unrelated PRs, three different testids — updates the priority
signal for the existing queued investigation without duplicating it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uRvqm44emrLDMyPF7Pame
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_e50ab3ad-1197-4bb3-92aa-2cd9dc3a3354)

BigSimmo and others added 4 commits September 4, 2026 20:57
PR #2600's Production UI failure on caring-contacts-phone-dock is a
recurrence of the shared-shell testid duplication race, confirmed
unrelated to this PR's diff. Unlike the two prior occurrences
(service-actions-trigger, sources-topics-main), this one has a
concrete mechanism: openWorkspace() in
tests/ui-caring-contacts-workspace.spec.ts already documents and
partially guards a streamed/placed shell duplicate for
caring-contacts-rail, but never extends that settle-wait to
caring-contacts-phone-dock, a sibling landmark in the same
next/dynamic-loaded CaringContactsShell.

Includes a cancel for an earlier queued request whose --detail
picked up stray shell output from an unescaped backtick.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uRvqm44emrLDMyPF7Pame
…laude/clinical-guide-redesign-ir5zcj

Sync with the Documents/Medications consolidation (#2614) merged into
this branch by another session, before pushing the CI-failure inbox
notes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uRvqm44emrLDMyPF7Pame
@claude

claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

CI failure diagnosis: caring-contacts-phone-dock duplication (not this PR's fault)

Failing job: Production UI (2), tests/ui-caring-contacts-workspace.spec.ts:1992 — "Template detail at 390px: the phone dock does not own navigation" (CI run 33868074584, job 101007656433, shard 2).

strict mode violation: getByTestId('caring-contacts-phone-dock') resolved to 2 elements:
  1) …aka getByTestId('caring-contacts-phone-dock').first()
  2) …aka getByRole('navigation', { name: 'Phone workspace' })

This is not caused by this PR's diff. PR #2600 touches the PsychSift rename, the sidebar/header-band re-cut, and (after the merge from main) the Documents/Medications routing consolidation — src/proxy.ts, ClinicalSidebar.tsx, global-search-shell.tsx, app-modes.ts, etc. None of it touches src/app/caring-contacts/** or src/components/caring-contacts/**.

Source check: data-testid="caring-contacts-phone-dock" has exactly one render site — src/components/caring-contacts/workspace/shell.tsx:492 (<nav aria-label="Phone workspace">). There is no second render path in source.

This is a recurring pattern (third and fourth known instances, tracked as a "shared-shell testid duplication race" — see service-actions-trigger on PR #2536/ui-tools.spec.ts, and sources-topics-main on PR #2591/ui-sources.spec.ts:88, neither previously root-caused).

This occurrence has a concrete mechanism, stronger evidence than the prior two: openWorkspace() in this same test file (tests/ui-caring-contacts-workspace.spec.ts:315-334) already documents and partially guards this exact defect —

"React streams the segment under loading.tsx's Suspense boundary into a hidden holder before moving it into place, so a production page sampled too early carries a second, inert copy of the whole shell. Settle on exactly one before measuring anything"

— followed by await expect(page.getByTestId("caring-contacts-rail")).toHaveCount(1). Every /caring-contacts/* route loads CaringContactsShell via next/dynamic under that same Suspense boundary. The settle-wait only polls caring-contacts-rail; it never extends to caring-contacts-phone-dock (a sibling landmark in the same streamed shell subtree), so Playwright's retrying assertion can resolve rail to count 1 at a moment when dock — reconciled on a different tick — is still doubled. That would explain a failure at line 1992 despite the openWorkspace() guard having already passed on the same navigation.

I could not verify this live: this sandbox's egress policy returns 403 for cdn.playwright.dev, so no Playwright browser install was possible here — a policy denial, not something to route around, so I'm reporting the hypothesis rather than claiming a proven fix.

No fix pushed to this PR — the fix belongs in tests/ui-caring-contacts-workspace.spec.ts (extend the existing settle-wait to caring-contacts-phone-dock, or generalize it to every shell-internal landmark the suite queries), which this PR does not otherwise touch, and I can't verify it here without a browser. Filed as a P2 issue in the outstanding-issues inbox (docs/outstanding-issues-inbox/4a148cf3-eae0-41f6-8b0a-61f4f318ca99.json, pushed to this branch) with full detail for whoever picks it up — also worth checking whether the same generalized settle-wait explains caring-contacts-guidance (PR #2536) and the other two occurrences, since service-actions-trigger and sources-topics-main may have their own dynamic/Suspense-streamed shells with no equivalent guard at all.

Recommended action: re-run the "Production UI (2)" job. If it's this streaming race, a re-run should pass (consistent with the prior two occurrences, both of which cleared on retry).


Generated by Claude Code

claude and others added 3 commits September 4, 2026 13:31
Production UI shard 2 went red on this branch at `aff60bc3` on a test this
PR does not touch:

  Template detail at 390px: the phone dock does not own navigation
  strict mode violation: getByTestId('caring-contacts-phone-dock')
  resolved to 2 elements

`openWorkspace()` already guards this exact defect, and its comment already
states the right rule: React streams the segment under `loading.tsx`'s
Suspense boundary into a hidden holder before moving it into place, so a page
sampled too early carries a second copy of the whole shell — and "a shell that
genuinely mounted twice would double every landmark on the page".

The implementation never matched that sentence. It settled `caring-contacts-rail`
alone. The two landmarks reconcile on different ticks and `toHaveCount(1)`
retries, so the wait can go green at a moment when the rail has settled and
the dock is still doubled; the frozen-layout sweep then asserts on the dock a
few lines later and trips strict mode on the second copy. That is why the
failure happened *despite* the guard passing on the same navigation.

So the wait now covers both landmarks. Safe at every width because neither is
conditionally rendered: `shell.tsx` draws the rail as `hidden … md:flex` (301)
and the dock as `md:hidden` (492), so exactly one of each is in the DOM
whatever the viewport, and the count is 1 regardless of which is displayed.
The guard says nothing about which is *visible* — that remains the sweep's job,
unchanged. Nothing is skipped, disabled or quarantined.

Credit where due: the mechanism was diagnosed by another Claude session on
this PR, which could not verify it (no browser in that sandbox) and left it
for whoever could. This session has one, so: verified by inspection against
`shell.tsx` for the always-rendered claim, and by running the suite.

Evidence, on a local production build through the repo runner:
- `tests/ui-caring-contacts-workspace.spec.ts` — 126 passed (2.5m).
- lint, typecheck and check:diff-integrity green.

Stated plainly: the race itself is not reproducible here — it needs the
timing of a full CI shard — so this is a reasoned fix against a defect
verified in the guard's own logic, not one demonstrated against a red test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8rMooUReCqvW8jLLx3k43
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_22927a2e-d0d2-4b4d-8c84-6e0cfa8e0735)

BigSimmo added a commit that referenced this pull request Sep 4, 2026
…cation (#2613)

* docs: queue outstanding-issue for recurring shared-shell testid duplication

Playwright strict-mode "resolved to 2 elements" failures on
service-actions-trigger (PR #2536) and sources-topics-main (PR #2591,
tests/ui-sources.spec.ts:88) share the same shape: one element nested
inside GlobalSearchShell's mobile-composer-reserve-pad wrapper, one
outside it. Confirmed unrelated to either PR's own diff. Queuing for a
focused investigation/fix rather than patching a "one owner" shared
shell component inside an unrelated PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uRvqm44emrLDMyPF7Pame

* docs: log a third occurrence of the shared-shell testid duplication bug

Same strict-mode "resolved to 2 elements" shape seen on PR #2613's
queued issue, now also on caring-contacts-phone-dock (PR #2600).
Three unrelated PRs, three different testids — updates the priority
signal for the existing queued investigation without duplicating it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uRvqm44emrLDMyPF7Pame

* docs: correct an unverified detail in the third-occurrence ledger entry

The earlier note claimed the duplicate caring-contacts-phone-dock
element was nested inside mobile-composer-reserve-pad by analogy with
the first two occurrences. The actual job log for this instance only
shows two sibling matches for the same testid; the nesting detail was
not verified and is removed. Cancels and replaces the prior entry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uRvqm44emrLDMyPF7Pame

---------

Co-authored-by: Claude <noreply@anthropic.com>
BigSimmo and others added 3 commits September 5, 2026 00:19
The desktop mode menu focuses its search box inside a requestAnimationFrame
when it opens. The reopen half of this test focused an option immediately
after asserting the dialog was visible, so on a slow runner the queued
autofocus landed afterwards and pulled focus back into the search box —
where Tab is not a dismiss key. The menu stayed open and the final
toBeHidden assertion timed out, as it did on CI run 33892868281.

The first half of the same test already waits for that autofocus; this makes
the reopen do the same, and asserts the option really holds focus before Tab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8rMooUReCqvW8jLLx3k43
@BigSimmo
BigSimmo merged commit 0ee9127 into main Sep 4, 2026
29 checks passed
@BigSimmo
BigSimmo deleted the claude/clinical-guide-redesign-ir5zcj branch September 4, 2026 16:38
Sign up for free to 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.

2 participants