diff --git a/data/repo-awareness-snapshot.json b/data/repo-awareness-snapshot.json index a4e1ff436e..70c0684779 100644 --- a/data/repo-awareness-snapshot.json +++ b/data/repo-awareness-snapshot.json @@ -1,8 +1,8 @@ { "version": "repo-awareness-snapshot-v1", "captured_revision": { - "sha": "1d9e1234e1c6c3d642efed81ebe65a6056d5d120", - "committed_at": "2026-08-27T11:37:53+00:00" + "sha": "2cfbe5647dc919f367486a4d4c2102524329b7c6", + "committed_at": "2026-08-27T11:41:19+00:00" }, "routes": { "modes": [ @@ -3959,6 +3959,38 @@ "outcome": "Re-synced with origin/main after PR #2416 landed (mechanical conflict only in data/repo-awareness-snapshot.json, regenerated). Also found and fixed a real regression from the earlier sweep's conflict resolution: DsmCompareChrome's onCommit had dropped ids.filter(Boolean) that origin/main carries, producing ids=,bipolar-ii-disorder after removing the first of two selections instead of ids=bipolar-ii-disorder -- exactly the failure tests/ui-route-coverage.spec.ts caught in CI. Restored the filter to match main. Added the required Clinical Governance Preflight section to the PR body since the regenerated snapshot touches data/. No review threads outstanding.", "checks": "npx vitest run tests/compare-catalog.test.ts tests/compare-ids-chrome.dom.test.tsx tests/compare-slot-strip.dom.test.tsx tests/therapy-compare-phone-layout.dom.test.tsx tests/therapy-compare-tray.dom.test.tsx tests/phone-dock-addon-contract.test.ts -- 57 passed; no provider-backed checks run" }, + { + "date": "2026-08-27", + "ref": "codex/therapy-pathways-redesign (PR #2413)", + "head": "05e00976ab04796d7f44593b692f46cb8919943e", + "scope": "Run PR follow-up: second merge-drift + PR-policy fix", + "outcome": "Re-synced with origin/main after PR #2416 landed (mechanical conflict only in data/repo-awareness-snapshot.json, regenerated); added the required Clinical Governance Preflight section to the PR body since the regenerated snapshot touches data/, a path scripts/pr-policy.mjs treats as clinical-risk. No review threads outstanding.", + "checks": "npx vitest run tests/therapy-pathways-mobile.dom.test.tsx tests/therapy-compass-responsive-contract.test.ts tests/playwright-pr-shards.test.ts -- 25 passed; no provider-backed checks run" + }, + { + "date": "2026-08-27", + "ref": "codex/therapy-pathways-redesign (PR #2413)", + "head": "be2d9b0291965258e5933006dc9b914961bb0dc3", + "scope": "Run PR sweep: CI fix + threads + drift", + "outcome": "CI was already green pre-sweep (PR required: success on d3f39c4); branch was already current with main (0 behind), so no drift merge needed. Fixed 3 unresolved CodeRabbit review threads: (1) pathway/step 'linked steps' counts used total steps.length instead of counting only steps with a matched therapySlug — added pathwayLinkedStepCount() and wired it into pathway-review-label.ts, pathway-picker-sheet.tsx (x2), pathway-step-stack.tsx; (2) ui-therapy-pathways.spec.ts readCautionGeometry substituted +/-Infinity for a missing caution/dock element, masking a broken overlap assertion — now throws if either is absent; (3) the 'Change pathway' Playwright test only re-clicked the already-active Anxiety pathway row, which could pass with a broken selectPathway — now selects Mood pathway, asserts the URL/heading changed, then switches back to Anxiety before the anxiety-scoped scroll assertions. All 3 threads replied with commit SHA and resolved.", + "checks": "npx vitest run tests/therapy-pathways-mobile.dom.test.tsx tests/therapy-compass-responsive-contract.test.ts tests/playwright-pr-shards.test.ts tests/therapy-compass-pathways.test.ts -- 34 passed; npm run lint -- exit 0 (gate-receipts pass); npm run typecheck -- exit 0, run foreground twice, gate-receipts recorded pass for typecheck:internal (5613 input files); npm run format -- applied (1 file reformatted, amended into commit); no provider-backed checks run" + }, + { + "date": "2026-08-27", + "ref": "codex/therapy-pathways-redesign (PR #2413)", + "head": "d2bbc44f084707d8844485c67c8cd76d57e04b39", + "scope": "Run PR sweep: CI fix + threads + drift", + "outcome": "Before: mergeable_state dirty (conflict vs main), all real CI green on prior head (19fca0d), 0 unresolved review threads (4 PR comments were all automated bot usage-limit/status noise, no actionable findings). After: merged origin/main (b183dc65) into branch; only conflicts were generated files docs/design-system/COMPONENTS.md and docs/design-system/adoption-manifest.json, resolved by regenerating via 'node scripts/generate-design-system-adoption.mjs --write' (no manual edits); PR mergeability check now succeeds. No review threads needed action (none existed). No CI fix needed beyond the merge itself.", + "checks": "npx vitest run tests/therapy-pathways-mobile.dom.test.tsx tests/therapy-compass-responsive-contract.test.ts tests/playwright-pr-shards.test.ts -- 25 passed; npm run check:design-system-adoption -- design system adoption checked: 55 components, 76 roots; npm run check:playwright-pr-shards -- PR UI shard parity OK: 31 production specs across 3 groups; pre-commit hook ran docs:check-inventory and docs:check-index -- both current. No provider-backed checks run." + }, + { + "date": "2026-08-27", + "ref": "codex/therapy-pathways-redesign (PR #2413)", + "head": "f8537abbef36b5e9f72eb1c745f565253a276153", + "scope": "Run PR sweep: CI fix + threads + drift", + "outcome": "Before: PR required failing (Static PR checks red on check:repo-awareness-snapshot — committed data/repo-awareness-snapshot.json review_state was stale after prior sweep's ledger/docs commits); 0 unresolved review threads; branch already contained origin/main (no drift). After: ran npm run snapshot:repo-awareness to regenerate the snapshot, verified npm run check:repo-awareness-snapshot and npm run format:changed pass, committed and pushed f8537ab. All other required jobs (Build, Unit coverage, Production UI critical, Safety and config checks, Production UI shards 1-3, Lighthouse budget) were already green before this fix and untouched by it. No review threads existed to address. No main-sync needed (branch already up to date).", + "checks": "npm run check:repo-awareness-snapshot (PASS, reproduces prior CI failure then passes after regen); npm run format:changed (PASS); npm run snapshot:repo-awareness (regenerated data/repo-awareness-snapshot.json); no provider-backed checks run" + }, { "date": "2026-08-27", "ref": "codex/tooling-hooks-ci-invariants", @@ -25001,8 +25033,8 @@ } ], "counts": { - "records": 2639, - "refs": 1610 + "records": 2643, + "refs": 1611 } } } diff --git a/docs/branch-review-records/093e552fb22a9e87c899dd093a3c133dd13a262ad6b2fc4fbb5b5a6deee67d93.record.md b/docs/branch-review-records/093e552fb22a9e87c899dd093a3c133dd13a262ad6b2fc4fbb5b5a6deee67d93.record.md new file mode 100644 index 0000000000..c4e5a834b0 --- /dev/null +++ b/docs/branch-review-records/093e552fb22a9e87c899dd093a3c133dd13a262ad6b2fc4fbb5b5a6deee67d93.record.md @@ -0,0 +1 @@ +| 2026-08-27 | codex/therapy-pathways-redesign (PR #2413) | 05e00976ab04796d7f44593b692f46cb8919943e | Run PR follow-up: second merge-drift + PR-policy fix | Re-synced with origin/main after PR #2416 landed (mechanical conflict only in data/repo-awareness-snapshot.json, regenerated); added the required Clinical Governance Preflight section to the PR body since the regenerated snapshot touches data/, a path scripts/pr-policy.mjs treats as clinical-risk. No review threads outstanding. | npx vitest run tests/therapy-pathways-mobile.dom.test.tsx tests/therapy-compass-responsive-contract.test.ts tests/playwright-pr-shards.test.ts -- 25 passed; no provider-backed checks run | diff --git a/docs/branch-review-records/7895a1814c28b2fe53fd6877a91f0bc86535eee70ed77c22032f6989646a2a1d.record.md b/docs/branch-review-records/7895a1814c28b2fe53fd6877a91f0bc86535eee70ed77c22032f6989646a2a1d.record.md new file mode 100644 index 0000000000..1ecd07784f --- /dev/null +++ b/docs/branch-review-records/7895a1814c28b2fe53fd6877a91f0bc86535eee70ed77c22032f6989646a2a1d.record.md @@ -0,0 +1 @@ +| 2026-08-27 | codex/therapy-pathways-redesign (PR #2413) | f8537abbef36b5e9f72eb1c745f565253a276153 | Run PR sweep: CI fix + threads + drift | Before: PR required failing (Static PR checks red on check:repo-awareness-snapshot — committed data/repo-awareness-snapshot.json review_state was stale after prior sweep's ledger/docs commits); 0 unresolved review threads; branch already contained origin/main (no drift). After: ran npm run snapshot:repo-awareness to regenerate the snapshot, verified npm run check:repo-awareness-snapshot and npm run format:changed pass, committed and pushed f8537ab. All other required jobs (Build, Unit coverage, Production UI critical, Safety and config checks, Production UI shards 1-3, Lighthouse budget) were already green before this fix and untouched by it. No review threads existed to address. No main-sync needed (branch already up to date). | npm run check:repo-awareness-snapshot (PASS, reproduces prior CI failure then passes after regen); npm run format:changed (PASS); npm run snapshot:repo-awareness (regenerated data/repo-awareness-snapshot.json); no provider-backed checks run | diff --git a/docs/branch-review-records/ad2f2cc55ed454d781f0501ef25a67e6f7bbaf869908014dfceacbcd2b8838d4.record.md b/docs/branch-review-records/ad2f2cc55ed454d781f0501ef25a67e6f7bbaf869908014dfceacbcd2b8838d4.record.md new file mode 100644 index 0000000000..cb6b6e6697 --- /dev/null +++ b/docs/branch-review-records/ad2f2cc55ed454d781f0501ef25a67e6f7bbaf869908014dfceacbcd2b8838d4.record.md @@ -0,0 +1 @@ +| 2026-08-27 | codex/therapy-pathways-redesign (PR #2413) | be2d9b0291965258e5933006dc9b914961bb0dc3 | Run PR sweep: CI fix + threads + drift | CI was already green pre-sweep (PR required: success on d3f39c4); branch was already current with main (0 behind), so no drift merge needed. Fixed 3 unresolved CodeRabbit review threads: (1) pathway/step 'linked steps' counts used total steps.length instead of counting only steps with a matched therapySlug — added pathwayLinkedStepCount() and wired it into pathway-review-label.ts, pathway-picker-sheet.tsx (x2), pathway-step-stack.tsx; (2) ui-therapy-pathways.spec.ts readCautionGeometry substituted +/-Infinity for a missing caution/dock element, masking a broken overlap assertion — now throws if either is absent; (3) the 'Change pathway' Playwright test only re-clicked the already-active Anxiety pathway row, which could pass with a broken selectPathway — now selects Mood pathway, asserts the URL/heading changed, then switches back to Anxiety before the anxiety-scoped scroll assertions. All 3 threads replied with commit SHA and resolved. | npx vitest run tests/therapy-pathways-mobile.dom.test.tsx tests/therapy-compass-responsive-contract.test.ts tests/playwright-pr-shards.test.ts tests/therapy-compass-pathways.test.ts -- 34 passed; npm run lint -- exit 0 (gate-receipts pass); npm run typecheck -- exit 0, run foreground twice, gate-receipts recorded pass for typecheck:internal (5613 input files); npm run format -- applied (1 file reformatted, amended into commit); no provider-backed checks run | diff --git a/docs/branch-review-records/d99361a8c9a53049ee45227f0230020721e9aebb18ee7548804835c510538765.record.md b/docs/branch-review-records/d99361a8c9a53049ee45227f0230020721e9aebb18ee7548804835c510538765.record.md new file mode 100644 index 0000000000..af8850eb0c --- /dev/null +++ b/docs/branch-review-records/d99361a8c9a53049ee45227f0230020721e9aebb18ee7548804835c510538765.record.md @@ -0,0 +1 @@ +| 2026-08-27 | codex/therapy-pathways-redesign (PR #2413) | d2bbc44f084707d8844485c67c8cd76d57e04b39 | Run PR sweep: CI fix + threads + drift | Before: mergeable_state dirty (conflict vs main), all real CI green on prior head (19fca0d), 0 unresolved review threads (4 PR comments were all automated bot usage-limit/status noise, no actionable findings). After: merged origin/main (b183dc65) into branch; only conflicts were generated files docs/design-system/COMPONENTS.md and docs/design-system/adoption-manifest.json, resolved by regenerating via 'node scripts/generate-design-system-adoption.mjs --write' (no manual edits); PR mergeability check now succeeds. No review threads needed action (none existed). No CI fix needed beyond the merge itself. | npx vitest run tests/therapy-pathways-mobile.dom.test.tsx tests/therapy-compass-responsive-contract.test.ts tests/playwright-pr-shards.test.ts -- 25 passed; npm run check:design-system-adoption -- design system adoption checked: 55 components, 76 roots; npm run check:playwright-pr-shards -- PR UI shard parity OK: 31 production specs across 3 groups; pre-commit hook ran docs:check-inventory and docs:check-index -- both current. No provider-backed checks run. | diff --git a/docs/design-system/COMPONENTS.md b/docs/design-system/COMPONENTS.md index 1f29f11b69..d32e94fa73 100644 --- a/docs/design-system/COMPONENTS.md +++ b/docs/design-system/COMPONENTS.md @@ -976,7 +976,7 @@ This generated snapshot is a local source-derived inventory. It does not assert | `AnswerFooter` | answer | yes | yes | inherited-global-root | yes | no | 1 | | `AsyncButton` | controls | yes | yes | inherited-global-root | yes | no | 4 | | `Breadcrumb` | layout | yes | yes | inherited-global-root | yes | no | 1 | -| `Button` | controls | yes | yes | inherited-global-root | yes | no | 17 | +| `Button` | controls | yes | yes | inherited-global-root | yes | no | 19 | | `Checkbox` | controls | yes | yes | no | yes | no | 0 | | `Chip` | controls | yes | yes | inherited-global-root | yes | no | 5 | | `ChoiceChip` | controls | yes | yes | inherited-global-root | yes | no | 4 | @@ -1012,7 +1012,7 @@ This generated snapshot is a local source-derived inventory. It does not assert | `SearchField` | controls | yes | yes | no | yes | no | 0 | | `SegmentedControl` | controls | yes | yes | inherited-global-root | yes | no | 8 | | `Select` | controls | yes | yes | inherited-global-root | yes | no | 2 | -| `Sheet` | layout | yes | yes | inherited-global-root | yes | no | 31 | +| `Sheet` | layout | yes | yes | inherited-global-root | yes | no | 32 | | `Skeleton` | feedback | yes | yes | inherited-global-root | yes | no | 6 | | `SourceDesignationBadge` | source | yes | yes | inherited-global-root | yes | no | 4 | | `SourceProvenance` | source | yes | yes | inherited-global-root | yes | no | 1 | diff --git a/docs/design-system/adoption-manifest.json b/docs/design-system/adoption-manifest.json index 05be5ff13c..4005a3d674 100644 --- a/docs/design-system/adoption-manifest.json +++ b/docs/design-system/adoption-manifest.json @@ -281,6 +281,8 @@ "src/components/AccessibleTable.tsx", "src/components/clinical-dashboard/signed-image.tsx", "src/components/privacy-quiet-signal-page.tsx", + "src/components/therapy-compass/pathway-picker-sheet.tsx", + "src/components/therapy-compass/pathway-step-stack.tsx", "src/components/therapy-compass/recommend-scenario-control.tsx", "src/components/therapy-compass/record/compare-action.tsx", "src/components/therapy-compass/screens/brief-screen.tsx", @@ -300,6 +302,8 @@ "src/components/AccessibleTable.tsx", "src/components/clinical-dashboard/signed-image.tsx", "src/components/privacy-quiet-signal-page.tsx", + "src/components/therapy-compass/pathway-picker-sheet.tsx", + "src/components/therapy-compass/pathway-step-stack.tsx", "src/components/therapy-compass/recommend-scenario-control.tsx", "src/components/therapy-compass/record/compare-action.tsx", "src/components/therapy-compass/screens/brief-screen.tsx", @@ -1663,6 +1667,7 @@ "src/components/forms/form-priority-facts-section.tsx", "src/components/in-page-nav/in-page-nav-header.tsx", "src/components/mode-nav/mode-nav.tsx", + "src/components/therapy-compass/pathway-picker-sheet.tsx", "src/components/therapy-compass/recommend-scenario-control.tsx", "src/components/therapy-compass/record/key-facts.tsx", "src/components/therapy-compass/therapy-compare-tray.tsx", @@ -1697,6 +1702,7 @@ "src/components/forms/form-priority-facts-section.tsx", "src/components/in-page-nav/in-page-nav-header.tsx", "src/components/mode-nav/mode-nav.tsx", + "src/components/therapy-compass/pathway-picker-sheet.tsx", "src/components/therapy-compass/recommend-scenario-control.tsx", "src/components/therapy-compass/record/key-facts.tsx", "src/components/therapy-compass/therapy-compare-tray.tsx", diff --git a/playwright.config.ts b/playwright.config.ts index 8b02d37438..9bd6a26a04 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -23,7 +23,7 @@ const chromiumExecutablePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH; // `tests/playwright-project-isolation.test.ts` asserts every such file on disk is // matched here. const productionSpecPattern = - /.*(?:answer-progress-ui-smoke|dsm-ui-smoke|ui-(smoke|stress|accessibility|caring-contacts-workspace|clinical-ask|dictionary|document-canvas|tools|overlap|universal-search|specifiers|formulation(?:-result-cards)?|forms-section-nav|chrome-scroll|therapy-nav-scroll|mode-nav-density|phone-motion|phone-scroll(?:-[a-z0-9-]+)?|pwa|route-coverage|style-contract|visual-artifacts|hydration))\.spec\.ts/; + /.*(?:answer-progress-ui-smoke|dsm-ui-smoke|ui-(smoke|stress|accessibility|caring-contacts-workspace|clinical-ask|dictionary|document-canvas|tools|overlap|universal-search|specifiers|formulation(?:-result-cards)?|forms-section-nav|chrome-scroll|therapy-nav-scroll|therapy-pathways|mode-nav-density|phone-motion|phone-scroll(?:-[a-z0-9-]+)?|pwa|route-coverage|style-contract|visual-artifacts|hydration))\.spec\.ts/; const mockupSpecPattern = /.*ui-(answer-chat-perfected-mockup|care-plan-mockup|caring-contact-mockup|document-image-status-mockup|document-top-navigation-mockup|sidebar-live-mockup|therapy-navigation-mockup|tools|tools-collapse|tools-search-mode-mockup|tools-task-directory|ward-management|ward-coordinator|ward-roles|ward-discharges)\.spec\.ts/; const mockupTag = /@mockup/; @@ -31,7 +31,7 @@ const mockupTag = /@mockup/; export default defineConfig({ testDir: "./tests", testMatch: - /.*(?:answer-progress-ui-smoke|dsm-ui-smoke|ui-(smoke|stress|accessibility|answer-chat-perfected-mockup|care-plan-mockup|caring-contact-mockup|caring-contacts-workspace|clinical-ask|dictionary|document-canvas|document-image-status-mockup|document-top-navigation-mockup|sidebar-live-mockup|therapy-navigation-mockup|tools|tools-collapse|tools-search-mode-mockup|tools-task-directory|ward-(?:management|coordinator|roles|discharges)|overlap|universal-search|specifiers|formulation(?:-result-cards)?|forms-section-nav|chrome-scroll|therapy-nav-scroll|mode-nav-density|phone-motion|phone-scroll(?:-[a-z0-9-]+)?|pwa|route-coverage|style-contract|visual-artifacts|hydration))\.spec\.ts/, + /.*(?:answer-progress-ui-smoke|dsm-ui-smoke|ui-(smoke|stress|accessibility|answer-chat-perfected-mockup|care-plan-mockup|caring-contact-mockup|caring-contacts-workspace|clinical-ask|dictionary|document-canvas|document-image-status-mockup|document-top-navigation-mockup|sidebar-live-mockup|therapy-navigation-mockup|tools|tools-collapse|tools-search-mode-mockup|tools-task-directory|ward-(?:management|coordinator|roles|discharges)|overlap|universal-search|specifiers|formulation(?:-result-cards)?|forms-section-nav|chrome-scroll|therapy-nav-scroll|therapy-pathways|mode-nav-density|phone-motion|phone-scroll(?:-[a-z0-9-]+)?|pwa|route-coverage|style-contract|visual-artifacts|hydration))\.spec\.ts/, timeout: 60_000, retries: 0, // Fail the run if a stray `test.only` is committed: otherwise it silently diff --git a/scripts/playwright-pr-shards.mjs b/scripts/playwright-pr-shards.mjs index 0784808898..a1305b1ad9 100644 --- a/scripts/playwright-pr-shards.mjs +++ b/scripts/playwright-pr-shards.mjs @@ -19,7 +19,7 @@ import { childProcessExitCode } from "./child-process-result.mjs"; /** Same matcher as playwright.config.ts `productionSpecPattern` (keep in sync). */ export const productionSpecFilePattern = - /^(?:answer-progress-ui-smoke|dsm-ui-smoke|ui-(?:smoke|stress|accessibility|caring-contacts-workspace|clinical-ask|dictionary|document-canvas|tools|overlap|universal-search|specifiers|formulation(?:-result-cards)?|forms-section-nav|chrome-scroll|therapy-nav-scroll|mode-nav-density|phone-motion|phone-scroll(?:-[a-z0-9-]+)?|pwa|route-coverage|style-contract|visual-artifacts|hydration))\.spec\.ts$/; + /^(?:answer-progress-ui-smoke|dsm-ui-smoke|ui-(?:smoke|stress|accessibility|caring-contacts-workspace|clinical-ask|dictionary|document-canvas|tools|overlap|universal-search|specifiers|formulation(?:-result-cards)?|forms-section-nav|chrome-scroll|therapy-nav-scroll|therapy-pathways|mode-nav-density|phone-motion|phone-scroll(?:-[a-z0-9-]+)?|pwa|route-coverage|style-contract|visual-artifacts|hydration))\.spec\.ts$/; /** * One source of truth for shard membership and its latest hosted timing sample. @@ -44,6 +44,9 @@ export const prUiSpecProfiles = Object.freeze([ // with hosted evidence at the next timing refresh. Moved here from shard 2 by // Task 19 to offset the re-measured Caring Contacts workspace spec. { file: "tests/ui-phone-motion.spec.ts", shard: 1, fullSeconds: 5.0, criticalSeconds: 0 }, + // Added with the Therapy Pathways mobile picker redesign; measured locally at + // ~2 tests. Placed on shard 1 to keep post-critical spread within the 10s ceiling. + { file: "tests/ui-therapy-pathways.spec.ts", shard: 1, fullSeconds: 2.0, criticalSeconds: 0 }, { file: "tests/ui-phone-scroll-routes.spec.ts", shard: 2, fullSeconds: 129.6, criticalSeconds: 0 }, { file: "tests/ui-phone-scroll.spec.ts", shard: 2, fullSeconds: 66.3, criticalSeconds: 0 }, diff --git a/src/components/therapy-compass/pathway-picker-sheet.tsx b/src/components/therapy-compass/pathway-picker-sheet.tsx new file mode 100644 index 0000000000..8cc22a5170 --- /dev/null +++ b/src/components/therapy-compass/pathway-picker-sheet.tsx @@ -0,0 +1,223 @@ +"use client"; + +import { useId, useMemo, useRef, useState } from "react"; +import { Search, Waypoints } from "lucide-react"; + +import { InteractiveRow } from "@/components/ui/interactive-row"; +import { Sheet } from "@/components/ui/sheet"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/components/ui-primitives"; + +import type { Pathway } from "./data/types"; +import { + matchesPathwayFilter, + pathwayLinkedStepCount, + pathwayReviewBadgeClass, + pathwayReviewLabel, + pathwayRowAccessibleName, +} from "./pathway-review-label"; + +type PathwayListRailProps = { + pathways: Pathway[]; + activeSlug: string; + onSelect: (slug: string) => void; +}; + +export function PathwayListRail({ pathways, activeSlug, onSelect }: PathwayListRailProps) { + return ( +
+
Pathways
+
+ {pathways.map((pathway) => { + const active = pathway.slug === activeSlug; + const reviewed = pathway.reviewStatus === "reviewed"; + return ( + onSelect(pathway.slug)} + > + + + + {pathway.name} + + {pathway.clinicalProblem ?? pathway.summary ?? "Therapy workflow"} + + + + {pathwayLinkedStepCount(pathway)} linked steps + + + {pathwayReviewLabel(pathway)} + + + + + ); + })} +
+

+ Pathways are generated from imported therapy records. +

+
+ ); +} + +type PathwayPickerSheetProps = { + pathways: Pathway[]; + activePathway: Pathway; + onSelect: (slug: string) => void; +}; + +export function PathwayMobileBar({ pathways, activePathway, onSelect }: PathwayPickerSheetProps) { + const [sheetOpen, setSheetOpen] = useState(false); + const [filter, setFilter] = useState(""); + const panelId = useId(); + const filterId = useId(); + const triggerRef = useRef(null); + const reviewed = activePathway.reviewStatus === "reviewed"; + const filteredPathways = useMemo( + () => pathways.filter((pathway) => matchesPathwayFilter(pathway, filter)), + [pathways, filter], + ); + + const closeSheet = () => { + setSheetOpen(false); + setFilter(""); + }; + + return ( +
+
+
+ + +
+
+

{activePathway.name}

+ + {pathwayReviewLabel(activePathway)} + +
+

+ {activePathway.clinicalProblem ?? activePathway.summary ?? "Therapy workflow"} +

+
+
+ +
+ + + Select a clinical problem workflow. {pathways.length} pathways available. +

+ } + portal + placement="responsive-right" + id={panelId} + testId="therapy-pathway-picker-panel" + footer={ + + } + > + +
+ {filteredPathways.length === 0 ? ( +

No pathways match that filter.

+ ) : null} + {filteredPathways.map((pathway) => { + const active = pathway.slug === activePathway.slug; + const pathwayReviewed = pathway.reviewStatus === "reviewed"; + return ( + { + onSelect(pathway.slug); + closeSheet(); + }} + > + + + + {pathway.name} + + {pathway.clinicalProblem ?? pathway.summary ?? "Therapy workflow"} + + + + {pathwayLinkedStepCount(pathway)} linked steps + + + {pathwayReviewLabel(pathway)} + + + + + ); + })} +
+
+
+ ); +} diff --git a/src/components/therapy-compass/pathway-review-label.ts b/src/components/therapy-compass/pathway-review-label.ts new file mode 100644 index 0000000000..aecae9e8ff --- /dev/null +++ b/src/components/therapy-compass/pathway-review-label.ts @@ -0,0 +1,33 @@ +import type { Pathway } from "./data/types"; + +export function pathwayLinkedStepCount(pathway: Pick): number { + return pathway.steps.filter((step) => Boolean(step.therapySlug)).length; +} + +export function pathwayRowAccessibleName(pathway: Pathway, active: boolean): string { + const parts = [pathway.name, `${pathwayLinkedStepCount(pathway)} linked steps`, pathwayReviewLabel(pathway)]; + if (active) parts.push("currently selected"); + return parts.join(", "); +} + +export function matchesPathwayFilter(pathway: Pathway, query: string): boolean { + const tokens = query.trim().toLowerCase().split(/\s+/).filter(Boolean); + if (tokens.length === 0) return true; + const haystack = [pathway.name, pathway.clinicalProblem, pathway.summary, pathwayReviewLabel(pathway)] + .filter(Boolean) + .join(" ") + .toLowerCase(); + return tokens.every((token) => haystack.includes(token)); +} + +export function pathwayReviewLabel(pathway: Pick): string { + if (pathway.reviewStatus === "reviewed") return "Reviewed"; + if (pathway.incomplete) return "Incomplete"; + return "Needs review"; +} + +export function pathwayReviewBadgeClass(reviewed: boolean): string { + return reviewed + ? "border-[color:var(--success-border)] bg-[color:var(--success-bg)] text-[color:var(--success-text)]" + : "border-[color:var(--warning-border)] bg-[color:var(--warning-bg)] text-[color:var(--warning-text)]"; +} diff --git a/src/components/therapy-compass/pathway-step-stack.tsx b/src/components/therapy-compass/pathway-step-stack.tsx new file mode 100644 index 0000000000..e0d75464ef --- /dev/null +++ b/src/components/therapy-compass/pathway-step-stack.tsx @@ -0,0 +1,197 @@ +"use client"; + +import type { ReactNode } from "react"; +import { ChevronRight, Scale, Waypoints } from "lucide-react"; + +import { cardSurface } from "@/components/card-recipes"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/components/ui-primitives"; + +import type { Pathway, PathwayStep, Therapy } from "./data/types"; +import { pathwayLinkedStepCount } from "./pathway-review-label"; + +type PathwayStepStackProps = { + steps: PathwayStep[]; + bySlug: Map; + onOpenRecord: (slug: string) => void; +}; + +function StepCard({ + step, + index, + last, + therapy, + onOpenRecord, + layout, +}: { + step: PathwayStep; + index: number; + last: boolean; + therapy: Therapy | undefined; + onOpenRecord: (slug: string) => void; + layout: "mobile" | "desktop"; +}) { + const title = therapy?.name ?? step.label ?? "Therapy step"; + const description = step.description ?? therapy?.bestUsedFor ?? "Review fit, contraindications and source status."; + const roleLabel = step.label ?? "STEP"; + + if (layout === "mobile") { + return ( +
+ {!last ? ( +
+ ); + } + + return ( +
+
+ + {index + 1} + + {!last ?
+
+ + +
+
{title}
+
{description}
+
+ + {roleLabel} + + {therapy ? ( + + ) : ( +
+
+ ); +} + +export function PathwayStepStack({ steps, bySlug, onOpenRecord }: PathwayStepStackProps) { + return ( + <> +
+ {steps.map((step, index) => { + const therapy = step.therapySlug ? bySlug.get(step.therapySlug) : undefined; + return ( + + ); + })} +
+
+ {steps.map((step, index) => { + const therapy = step.therapySlug ? bySlug.get(step.therapySlug) : undefined; + return ( + + ); + })} +
+ + ); +} + +export function PathwayDetailHeader({ pathway, reviewBadge }: { pathway: Pathway; reviewBadge?: ReactNode }) { + return ( +
+ + +
+
+

{pathway.name}

+ {reviewBadge} +
+

+ {pathway.summary ?? + "A source-linked workflow for reviewing therapy options, delivery constraints and cautions before choosing a next step."} +

+
+
+
+
+ ); +} diff --git a/src/components/therapy-compass/screens/pathways-screen.tsx b/src/components/therapy-compass/screens/pathways-screen.tsx index 65dae23192..64534c80c3 100644 --- a/src/components/therapy-compass/screens/pathways-screen.tsx +++ b/src/components/therapy-compass/screens/pathways-screen.tsx @@ -1,7 +1,7 @@ "use client"; import { useMemo } from "react"; -import { Check, ChevronRight, Copy, FileText, ListChecks, Scale, TriangleAlert, Waypoints } from "lucide-react"; +import { Check, Copy, FileText, ListChecks, TriangleAlert, Waypoints } from "lucide-react"; import { cardSurface } from "@/components/card-recipes"; import { Chip } from "@/components/ui/chip"; @@ -10,19 +10,17 @@ import { cn, pageContainer } from "@/components/ui-primitives"; import { Button } from "@/components/ui/button"; import { useTcBindings } from "../bindings"; -import { InteractiveRow } from "@/components/ui/interactive-row"; -import type { Therapy } from "../data/types"; import { LoadingState } from "../ui"; import { useClipboard } from "../use-clipboard"; +import { PathwayMobileBar, PathwayListRail } from "../pathway-picker-sheet"; +import { PathwayDetailHeader, PathwayStepStack } from "../pathway-step-stack"; +import { pathwayReviewBadgeClass, pathwayReviewLabel } from "../pathway-review-label"; export function PathwaysScreen() { const b = useTcBindings(); const bySlug = useMemo(() => new Map(b.therapies.map((t) => [t.slug, t])), [b.therapies]); const pathway = b.selectedPathway; - // Header counts. Summed here rather than in the header markup so the chips - // stay declarative and the totals survive a data shape that grows a step. const linkedStepCount = useMemo(() => b.pathways.reduce((total, p) => total + p.steps.length, 0), [b.pathways]); - // Called before the early return below, because it is a hook. const { copied, copy } = useClipboard(); if (b.loading || !pathway) return ; @@ -31,10 +29,8 @@ export function PathwaysScreen() { pathway.steps .map((step) => (step.therapySlug ? bySlug.get(step.therapySlug) : undefined)) .find((therapy) => therapy?.patientSheetAvailable)?.slug ?? null; - // Every other copy affordance in this mode goes through `useClipboard` -> - // `@/lib/copy-to-clipboard`. This one reached for `navigator.clipboard` - // directly, so it had no fallback path, treated a rejected write as success, - // and gave the reader no confirmation that anything had been copied. + const reviewed = pathway.reviewStatus === "reviewed"; + const copyPathway = () => { const lines = pathway.steps.map((st, i) => { const name = (st.therapySlug ? bySlug.get(st.therapySlug)?.name : null) ?? st.label ?? "Step"; @@ -45,21 +41,8 @@ export function PathwaysScreen() { return (
- {/* The Review queue lived here as the page's one action. It is a curation - surface — which records still need source review — not something a - clinician reads a pathway to reach, so the header no longer carries it; - `/therapy-compass/review` still serves it directly. What replaces it is - scale, not another control: the two counts say how much of the - catalogue this page covers, and the accent chip is the only colour a - phone gets here, since `PageHeader` hides its icon tile below `sm`. - - The description loses "generated from imported therapy records" - because that provenance is already stated twice further down the same - screen — the note under the pathway list and the caution banner — and - on a 390px viewport it was the line that wrapped into the drawer - handle. */} -
- {/* pathway list */} -
-
Pathways
-
- {b.pathways.map((p) => { - const active = p.slug === pathway.slug; - return ( - b.selectPathway(p.slug)}> - - - - {p.name} - - {p.clinicalProblem ?? p.summary ?? "Therapy workflow"} - - - {p.steps.length} linked steps - - {p.reviewStatus === "reviewed" ? "Reviewed" : p.incomplete ? "Incomplete" : "Needs review"} - - - - - ); - })} -
-

- Pathways are generated from imported therapy records. -

-
+ - {/* pathway detail */} -
-
- - -
-
-

{pathway.name}

+
+ + +
+
+
-

- {pathway.summary ?? - "A source-linked workflow for reviewing therapy options, delivery constraints and cautions before choosing a next step."} -

-
-
-
+ } + />
-
- {pathway.steps.map((step, i) => { - const therapy: Therapy | undefined = step.therapySlug ? bySlug.get(step.therapySlug) : undefined; - const last = i === pathway.steps.length - 1; - return ( -
- - {i + 1} - -
- - -
-
- {therapy?.name ?? step.label ?? "Therapy step"} -
-
- {step.description ?? therapy?.bestUsedFor ?? "Review fit, contraindications and source status."} -
-
- - {step.label ?? "STEP"} - - {therapy ? ( - - ) : ( -
-
- ); - })} -
+
-
+