Commit graph

3 commits

Author SHA1 Message Date
Daniel
18550e263f feat(client): port age→weight + BSA + dose + GCS; shared/clinical module
First batch of real calculator ports, routed through a new
shared/clinical/calculators.ts module that both the server tree and
the React client can import. Kept strictly to the simplest, closed-form
formulas — tables (Rosner BP, Fenton LMS, AAP 2022 bili, Bhutani, CDC
BMI) stay in the vanilla viewer until per-table vector files land.

shared/clinical/calculators.ts
  Verbatim ports from public/js/calc-math.js:
    • parseAgeMonths / formatAgeMonths — legacy age-string parser
    • estimateWeightFromAgeMonths — APLS (Luscombe 2007) + Best Guess
      (Tinning 2007) weight-for-age. Cross-checked line-by-line against
      calc-math.js:14-39; identical branches, formulas, and roundTo
      behavior.
    • calculateMostellerBsa — sqrt(h·w/3600), Mosteller 1987.
    • calculateWeightBasedDose — generic mg/kg with optional max cap
      and mg/mL → mL conversion.
    • calculateGcs — 1-15 sum with 8/12 severity thresholds.

shared/clinical/calculators.test.ts
  Vitest unit coverage for each helper. Numeric assertions match the
  legacy function outputs (3y APLS → 14 kg; 20 kg, 110 cm → 0.782 m²;
  15 kg × 100 mg/kg capped at 500 mg, etc.). For these closed-form
  formulas the hand-verified expected values are equivalent to a
  vanilla-captured vector file — table-driven calculators still need
  a JSON fixture before they port.

client/src/pages/Bedside.tsx
  Top-level age-to-weight estimator now runs in React
  (BedsideWeightEstimator). Formula dropdown switches between APLS
  and Best Guess live; weight field accepts a manual override. The
  15 clinical dosing sub-modules still fall through to the legacy
  viewer via LegacyPanel.

client/src/pages/Calculators.tsx
  BSA, Weight-Based Dosing, and GCS panels render real React forms
  backed by the shared helpers. PILLS gain a `ported` flag so the
  four covered panels (bsa, dose, gcs, + the already-shipped pills)
  swap out of the legacy fallback while the others remain linked
  out. Result blocks carry data-testid hooks for parity tests.

Config + dep hygiene picked up along the way
  • client/tsconfig.app.json: @shared/* path alias, exclude test files
    from the React tsc pass.
  • tsconfig.json: exclude **/*.test.ts from the backend tsc pass.
  • package.json: declare google-auth-library and jszip explicitly —
    both were already required() in src/utils/ttsGoogle.ts and
    src/routes/learningAI.ts but missing from dependencies, which
    would break a clean `npm install`. Also adds engines: node >=20
    and convenience verify / verify:full scripts.
  • knip.json: quiet now-expected ignoreDependencies / ignoreBinaries
    entries for marp-cli, tiptap, cap, etc.
  • .gitignore: ignore the .codex CLI marker.

e2e/tests/bedside-react.spec.js
  Adds the age→weight parity test: 3y APLS → 14 kg, 3y Best Guess →
  16 kg, weight field mirrors the estimator output.

e2e/tests/calculators-react.spec.js
  Adds BSA (20 kg, 110 cm → 0.782 m²), dose cap (15 kg × 100 mg/kg,
  max 500 mg → 500 mg capped), and GCS (15 → 10 when motor drops
  to 1) parity tests.

Client tsc -b + backend tsc --noEmit + vite build all clean.
Bundle 476.48 kB / 135.44 kB gzipped.

Co-Authored-By: Codex + vendor model Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 01:11:58 +02:00
Daniel
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.
2026-04-23 22:09:04 +02:00
Daniel
3222dacc9a feat(client): day 6 — scaffold React 19 + Vite + Tailwind v4 + shadcn/ui
New client/ directory, standalone from the backend: Vite 8 + React
19.2 + TypeScript 6. Tailwind v4 via @tailwindcss/vite plugin (no
postcss config needed). shadcn/ui compatibility wired via
components.json + lib/utils.ts. @tanstack/react-query + react-router-dom
installed for server state + routing.

Vite config essentials:
  base: '/app/'     — Express mounts SPA at /app/*, vanilla JS stays at /
  outDir: '../public/app/' — build lands next to existing static assets
  @/*     → ./src/*      (client-local imports)
  @shared/* → ../shared/* (typed wire protocol shared with backend)
  server.proxy '/api' → localhost:3000 for `npm run dev`

tsconfig.app.json — added baseUrl + paths + include ../shared so the
shared/types.ts + schemas.ts are typechecked on the client side too.

src/index.css — Tailwind v4 @import, shadcn HSL design tokens (light +
dark schemes). Replaces the vite starter template's decorative styles.

No backend touched. Express route serving /app/* lands in Day 7.
2026-04-23 21:58:37 +02:00