Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4a2b8a7
fix(therapies): stop the catalogue generator consuming its own output
claude Aug 12, 2026
8f0c395
perf(therapies): serve catalogue aliases by rewrite instead of duplic…
claude Aug 12, 2026
4935d26
docs(issues): close #180 and #177, and correct the paths they moved
claude Aug 12, 2026
31c8d0d
docs(ledger): record the review for this branch
claude Aug 12, 2026
6e3e1dd
Merge branch 'main' into claude/therapy-catalogue-alias-rewrite
BigSimmo Aug 12, 2026
a3a8100
fix: ship generated therapy assets with app runner
BigSimmo Aug 12, 2026
50ce160
test: cover every next config runner import
BigSimmo Aug 12, 2026
2d0b93d
Merge main into Therapy catalogue asset branch
BigSimmo Aug 13, 2026
a1d1f45
Merge branch 'main' into claude/therapy-catalogue-alias-rewrite
BigSimmo Aug 13, 2026
fd6cabf
Merge branch 'main' into claude/therapy-catalogue-alias-rewrite
BigSimmo Aug 13, 2026
3905c39
Merge branch 'main' into claude/therapy-catalogue-alias-rewrite
BigSimmo Aug 13, 2026
9e711f1
Merge branch 'main' into claude/therapy-catalogue-alias-rewrite
BigSimmo Aug 13, 2026
1abe95f
Merge branch 'main' into claude/therapy-catalogue-alias-rewrite
BigSimmo Aug 13, 2026
b27fed3
Merge branch 'main' into claude/therapy-catalogue-alias-rewrite
BigSimmo Aug 13, 2026
4e7c3ab
fix(docs): update therapy catalogue references
BigSimmo Aug 13, 2026
e804ab3
Merge branch 'main' into claude/therapy-catalogue-alias-rewrite
BigSimmo Aug 13, 2026
40eb83e
Merge branch 'main' into claude/therapy-catalogue-alias-rewrite
BigSimmo Aug 13, 2026
feca6e0
Merge branch 'main' into claude/therapy-catalogue-alias-rewrite
BigSimmo Aug 13, 2026
e24eacf
fix(ci): reconcile therapy docs with ledger inbox
BigSimmo Aug 13, 2026
e0f2e60
Merge branch 'main' into claude/therapy-catalogue-alias-rewrite
BigSimmo Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ src/lib/supabase/database.types.ts
supabase/drift-manifest.json
# Trimmed Therapy Compass content export (generated data assets); keep minified.
public/therapy-compass-data/
# Hand-edited source catalogue for scripts/build-therapies-index.mjs. Compact
# single-line JSON (~2.5 MB), the same shape the generated full catalogue keeps
# — pretty-printing it turns a modality scrub into a ~17k-line reflow, which is
# the churn `#179` fixed on the generated side. It lives here rather than under
# `public/therapy-compass-data/` because the generator writes every file in that
# directory and must never read back what it wrote (`#180`).
src/data/therapies-source.json
# Generated by `scripts/build-ranking-snapshot.ts` from an eval-canary artifact.
# The generator writes JSON.stringify(…, 2), which always expands arrays, while
# Prettier collapses short ones — so the two can never agree and every
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ COPY --from=build /app/.next ./.next
COPY public ./public
COPY --from=build /app/src/lib/security-headers.ts ./src/lib/security-headers.ts
COPY --from=build /app/src/lib/supabase/project.ts ./src/lib/supabase/project.ts
COPY --from=build /app/src/components/therapy-compass/data/generated-assets.ts ./src/components/therapy-compass/data/generated-assets.ts
COPY package.json next.config.ts ./
USER node
EXPOSE 3000
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 1,
"id": "02278dd9-cc4d-4114-8d5e-84414910b886",
"createdOn": "2026-08-13",
"action": "update",
"payload": {
"id": "#175",
"detail": "VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: src/data/therapies-source.json holds 205 records and 0 carry a modality value, exactly as described. This stamp exists so a later reader can tell \"checked and still true\" from \"never looked at\"; the two were indistinguishable before. **Outcome:** the Therapy detail and recommend screens either show a curated modality or stop carrying the field at all. **Detail:** the source catalogue derived `modality` from each record's own tag list — all 205 records had one, every value was also present in that record's `tags`, and the whole catalogue collapsed to CBT/ACT/DBT. It mislabelled the treatments it could not describe: ECT and rTMS as \"ACT\", Psychoanalysis and Psychodynamic Psychotherapy as \"CBT\", MBT and TFP as \"DBT\". Pre-existing on main, surfaced by the PR #1489 review. The generator emits it only when the source curates a value that is not already a tag, which today means null for 205/205 on the index projections *and* the full catalogue the detail/recommend screens load (`catalogue: \"full\"`), so the two chips (`detail-screen.tsx:49`, `recommend-screen.tsx:115`) never render and `select.ts:117` contributes no same-modality point. Removal was provably search-neutral: `src/lib/therapies.ts` scores with boolean `haystack.includes(token)`, not term frequency, and every modality value was already contributed by `tags.join(\" \")` in the same haystack. **Next:** one of two — curate real modality values in `src/data/therapies-source.json` (clinical work, needs the psychiatrist), or drop the field from `types.ts`, `src/lib/therapies.ts`, the two chips and `select.ts`. **Stop:** do not reinstate the tag-derived value to make the chips reappear; a guess rendered as curated fact is the defect. `tests/therapy-compass-pathways.test.ts` pins the echo invariant on both the index and the full catalogue asset. Renumbered from this PR's original `#169` because `main` claimed `#169`–`#174` while the branch was open."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 1,
"id": "3f38592b-8346-48dc-87d9-acc585ffe5b8",
"createdOn": "2026-08-13",
"action": "update",
"payload": {
"id": "#117",
"detail": "**Outcome:** `/therapy-compass` mobile LCP lands near the other mobile routes instead of double them. **Measured 2026-07-30** by the new pre-merge Lighthouse budget: mobile LCP 5229 ms, TBT 612 ms, CLS 0.142, against 2123-2460 ms on every other mobile route and 826 ms on desktop — so it is client-side work under mobile CPU/network throttling, not server latency. **Cause before this PR:** `useTherapyData` fetched the thin browse index (205 records) for the home/search/pathways screens, so the download plus JSON parse sat on the critical path before content painted. **Current split:** home now fetches `public/therapy-compass-data/therapies-home.211dab554c4ec62d.json` (136,288 bytes raw), pathways use the thin browse index, and search loads the full prose corpus (#1471). 90% of the index weight is long-form clinical prose — indications 159 KB (26%), contraindicationsOrCautions 139 KB (23%), bestUsedFor 73 KB (12%), clinicalSummary 67 KB (11%), patientPopulation 59 KB (10%), targetSymptoms 48 KB (8%) — while name, slug, category, tags and setting together are 54 KB (7%). **Remaining decision for search/pathways: rendered on the card, matched by search, or neither.** `therapy-card.tsx` references five of those prose fields and the same index feeds the search screen, so stripping fields could silently change clinical display or search recall. **Next:** settle that per-field question, then either pre-truncate prose that only feeds card display, or move search matching server-side / load prose on first keystroke. **Gate:** `check:therapy-data-index` plus the therapy Playwright journeys; re-measure with `npm run verify:lighthouse`. **Stop:** do not drop a field from the catalogue payload without confirming no card renders it and no search path matches on it. Same class as #013 (route-chunk / catalogue JSON weight), different route and now measured."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 1,
"id": "7336de95-160e-4255-b2c0-abcfd7f4e093",
"createdOn": "2026-08-13",
"action": "done",
"payload": {
"id": "#180",
"outcome": "Resolved by PR #1886: the hand-edited therapy catalogue moved to `src/data/therapies-source.json`, outside the generated public directory, so regeneration can no longer consume its own output; generated assets remain canonical-byte checked."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 1,
"id": "976c4c7a-5c74-47e3-975b-046718c54859",
"createdOn": "2026-08-13",
"action": "done",
"payload": {
"id": "#177",
"outcome": "Resolved by PR #1886: unversioned Therapy Compass catalogue aliases are served by Next afterFiles rewrites to the current content-addressed assets with must-revalidate alias headers, while duplicate public alias files are no longer written and the generator guard prevents them reappearing."
}
}
32 changes: 31 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
import { withSentryConfig } from "@sentry/nextjs";
import { buildSecurityHeaders, resolveRuntimeFlags } from "./src/lib/security-headers";
import { expectedSupabaseProject } from "./src/lib/supabase/project";
import { THERAPY_CATALOGUE_ASSETS } from "./src/components/therapy-compass/data/generated-assets";
Comment thread
BigSimmo marked this conversation as resolved.

const projectRoot = path.dirname(fileURLToPath(import.meta.url));
const requestedDistDir = process.env.NEXT_DIST_DIR?.trim();
Expand Down Expand Up @@ -135,12 +136,41 @@ const nextConfig: NextConfig = {
{
// Compatibility aliases for deployment-straddling clients. These names
// stay stable across regenerations, so force revalidation instead of
// inheriting the hashed-asset immutable policy above.
// inheriting the hashed-asset immutable policy above. Matched on the
// REQUEST path, so the rewrite below does not pull the destination's
// immutable policy onto the alias.
source: "/therapy-compass-data/:asset(therapies(?:-(?:home|index))?\\.json)",
headers: [{ key: "Cache-Control", value: "public, max-age=0, must-revalidate" }],
},
];
},
// Serve the unversioned catalogue aliases from the current content-addressed
// asset instead of writing a byte-identical duplicate to disk. `useTherapyData`
// falls back to these names when a bundle older than the one-deploy grace
// generation names a hashed file that no longer exists, so the URLs must keep
// working — but they cost 2.81 MB of duplicated payload in the working tree and
// every Docker image when they were real files (5.34 MB mid-grace-window).
//
// `afterFiles` rather than `beforeFiles`: the alias files no longer exist, so
// the rewrite is reached once the static handler finds nothing, and nothing
// legitimate at these paths is shadowed. build-therapies-index.mjs --check
// fails if an alias file reappears, since a real file would win over this
// rewrite and then silently go stale on the next regeneration.
async rewrites() {
const alias = (name: string, asset: string) => ({
source: `/therapy-compass-data/${name}`,
destination: `/therapy-compass-data/${asset}`,
});
return {
beforeFiles: [],
afterFiles: [
alias("therapies.json", THERAPY_CATALOGUE_ASSETS.full),
alias("therapies-index.json", THERAPY_CATALOGUE_ASSETS.index),
alias("therapies-home.json", THERAPY_CATALOGUE_ASSETS.home),
],
fallback: [],
};
},
};

function shouldEnableSentrySourceMapUpload() {
Expand Down
Loading
Loading