TypeScript And React Docs
이 문서는 Hospital-at-Home 예제에서 TypeScript/React가 맡는 frontend와 clinical monitoring UI 책임을 설명합니다. TypeScript는 risk를 다시 계산하는 계층이 아니라, Python API가 만든 monitoring snapshot, alarm state, review queue를 의료진이 오해 없이 볼 수 있게 표현하는 계층입니다.
TypeScript/Node package와 service API reference는 TypeDoc을 기준으로 생성합니다.
pnpm docs:ts
React component 문서는 Storybook을 기준으로 관리합니다.
pnpm docs:storybook
package consumer 문서는 package README.md에 두고, app runtime 설명은 app README.md와 container metadata에 둡니다. 일반 설계 문서는 docs/ 하위 Markdown에 둡니다.
1. Hospital-at-Home 구현 기준
이 시나리오에서 TypeScript는 clinician-facing GUI와 frontend contract를 담당합니다. Web app은 단순 landing page가 아니라 waveform, numeric measurement, risk evidence, alarm state, model metadata를 확인할 수 있는 clinical monitoring 화면이어야 합니다.
초기 구현은 디자인 토큰이나 브랜드 시스템보다 먼저 제품 상태 표현을 고정합니다. 즉 TypeScript package는 색상 팔레트 모음이 아니라, 생체신호와 알람 상태를 안전하게 표현하는 clinical UI component contract입니다.
1-1. 책임
TypeScript가 담당합니다.
- React/Vite clinician monitoring dashboard
- Python API client contract
- API response -> UI view model 변환
- monitoring snapshot freshness/stale state
- waveform, numeric, risk, evidence, data quality, alarm view model
- ward/bed monitoring summary view model
- waveform panel, numeric tile, risk card component
- visual alarm banner, alarm indicator, acknowledge/mute interaction
- ward monitoring grid and per-bed freshness/signal-quality indicator
- clinician review queue panel and priority state
- evidence panel과 data quality warning
- loading, error, empty, stale state
- monitoring snapshot periodic refresh and freshness indicator
- review queue periodic refresh and empty/error state
- Storybook으로 주요 clinical UI state 문서화
- component/unit/contract test
- semantic accessibility smoke for alarm/review states
TypeScript가 담당하지 않습니다.
- risk rule 재계산
- alarm severity 결정
- raw signal heavy preprocessing
- firmware packet 생성
- model threshold policy 결정
- Kubernetes runtime reconcile
- Node API/BFF
1단계에서는 Node API/BFF를 만들지 않습니다. Web app의 기본 runtime은 Python API의 GET /monitoring-snapshot 또는 GET /review-queue를 호출합니다. Fixture/mock API는 Storybook, component test, offline UI 확인에 사용합니다.
1-2. Package 방향
현재 구조는 아래처럼 분리합니다.
packages/
patient-monitor-ui-core framework-independent UI state and policy model
patient-monitor-react-components
React clinical components
apps/
patient-risk-web runtime composition and API integration
patient-monitor-ui-core는 아래를 갖습니다.
WaveformViewportNumericMeasurementViewModelAlarmSeverityAlarmPresentationStateAlarmSoundPolicyPatientMonitoringSnapshotViewModelWardMonitoringViewModel- API response -> view model mapper
현재 src/monitoring.ts는 기존 consumer를 위한 public facade입니다. 실제 monitoring presentation 구현은 src/presenters/monitoring.ts에 둡니다. 이후 contract reader, HTTP client, presenter가 더 커지면 contracts/, adapters/outbound/, presenters/로 추가 분리하되, app은 계속 public facade를 통해 소비하게 합니다.
patient-monitor-react-components는 아래를 갖습니다.
WaveformPanelNumericTileRiskScoreCardAlarmBannerAlarmIndicatorAlarmAudioControllerReviewQueuePanelPatientMonitorDashboard
디자인 시스템은 이 위에 얹습니다. 색상, typography, spacing token은 나중에 도입하더라도, severity와 상태 의미는 package API로 먼저 고정합니다.
1-3. 화면 기준
첫 화면은 의료진이 “지금 이 환자의 상태가 안정적인가, 어떤 신호를 봐야 하는가, 알람을 처리해야 하는가”를 판단할 수 있어야 합니다.
필수 UI 요소는 아래와 같습니다.
- monitored subject summary
- waveform panel
- numeric vital tiles
- risk level과 risk score
- risk horizon
- visual alarm banner
- audible alarm readiness state
- acknowledge/mute/snooze interaction
- 주요 evidence
- signal/data quality warning
- model version과 policy version
- last updated/stale 상태
상세 화면은 “왜 이 환자가 high priority인가”와 “데이터 품질이 충분한가”를 설명해야 합니다.
MonitoringSnapshot
-> WaveformPanel
-> NumericTiles
-> RiskScoreCard
-> AlarmBanner
-> EvidencePanel
-> SignalQualityPanel
-> ReviewQueuePanel
-> ModelMetadataPanel
1-4. API contract
Frontend는 Python API의 결과를 그대로 화면에 뿌리지 않고, view model mapper를 통해 UI에 적합한 형태로 변환합니다.
MonitoringSnapshotResponse
-> PatientMonitoringSnapshotViewModel
ReviewQueueItemResponse
-> ReviewQueueItemViewModel
MonitoringSnapshotResponse[]
-> WardMonitoringViewModel
이 mapper는 package unit test 대상입니다. Frontend는 backend의 risk 결과나 alarm severity를 재계산하지 않고, 표시와 interaction에 집중합니다. Acknowledge, mute, snooze 같은 user action은 API로 다시 전달합니다.
Ward view model도 같은 원칙을 따릅니다. 병동/게이트웨이 단위 처리 결과가 들어오더라도 TypeScript는 risk, alarm, signal quality를 새로 판정하지 않습니다. 여러 bed snapshot을 받아 stale 여부, review 필요 여부, alarm/signal tone, bed count 같은 화면용 summary만 만듭니다.
Runtime app은 Python API의 monitoring/review endpoint를 호출해야 합니다. Fixture/mock은 Storybook, component test, offline UI 확인에만 사용합니다.
Monitoring dashboard는 한 번 fetch하고 멈추지 않습니다. Runtime app은 GET /monitoring-snapshot을 initial load에 호출하고, 이후 주기적으로 같은 endpoint를 refresh합니다. ui-core는 backend timestampMs와 browser time을 비교해 freshness view model을 만들고, React screen은 stale state를 화면에 표시합니다. 이 freshness 판단은 risk score, alarm severity, data quality를 다시 계산하지 않고, “화면이 최신 backend snapshot을 보고 있는가”만 표현합니다.
Snapshot이 stale이면 dashboard summary는 clinical alarm보다 먼저 stale state를 드러냅니다. 오래된 데이터로 표시되는 critical/warning alarm은 실제 현재 상태로 오해될 수 있으므로, frontend는 backend risk를 재계산하지 않더라도 “현재 화면이 오래된 snapshot을 보고 있다”는 operational risk를 명확히 보여줘야 합니다.
1-5. Alarm과 sound 기준
Browser/PWA에서 audible alarm은 autoplay 정책의 영향을 받습니다. 따라서 component가 임의로 소리를 재생하면 안 되고, AlarmAudioController가 아래 상태를 명시적으로 관리해야 합니다.
- user interaction 이후 sound enabled
- alarm severity에 따른 sound request
- muted state
- acknowledged state
- snoozed until
- unavailable or blocked by browser policy
Backend는 alarm severity와 audible request를 결정합니다. Frontend는 그 상태를 표현하고, 사용자의 acknowledge/mute/snooze action을 전달합니다. 현재 구현은 acknowledgeAlarm, muteAlarm helper가 POST /alarms/{alarm_id}/ack, POST /alarms/{alarm_id}/mute를 호출하고, 응답으로 받은 MonitoringSnapshot을 다시 view model로 변환합니다. fetchAlarmAuditEvents는 GET /alarms/{alarm_id}/audit-events를 호출해 backend가 기록한 AlarmAuditEvent contract를 검증합니다. Demo smoke는 이 public ui-core API를 사용해 acknowledge/mute가 audit trail에도 남는지 확인합니다.
ui-core는 silent, ready, requested, suppressed sound state를 만들고, React hook은 이 상태를 browser audio adapter에 전달합니다. Audit event는 frontend가 만들어내는 UI log가 아니라 backend clinical workflow의 기록입니다. Frontend는 audit event를 표시하거나 검토할 수 있지만, event id, actor, previous/current alarm state 의미를 재해석하지 않습니다. 실제 의료기기 수준의 alarm safety, IEC 60601-1-8 같은 표준 적합성은 이 예제 범위 밖이지만, 상태와 책임 경계는 처음부터 분리합니다.
1-6. Storybook 기준
Storybook은 visual docs이자 clinical UI state fixture입니다.
필수 story는 아래와 같습니다.
- normal monitoring snapshot
- watch state with subtle data quality warning
- warning alarm with visual banner
- critical alarm with audible request
- acknowledged critical alarm
- muted alarm
- noisy waveform
- missing data warning
- API error
- empty monitoring state
- stale data
- alarm action failure
- interactive alarm workflow play check
Storybook의 mock은 제품 runtime 대체물이 아닙니다. Runtime app은 Python API contract를 소비하고, Storybook은 상태별 UI 검토와 회귀 테스트를 위한 fixture로만 사용합니다.
1-7. Test strategy
| Test | Scope |
|---|---|
| Unit | API response -> monitoring/review view model mapper |
| Unit | monitoring freshness and stale-state mapper |
| Unit | alarm presentation and sound policy state machine |
| Component | waveform trace, numeric status/tone, alarm banner, evidence panel |
| Component | alarm visual tone/severity, role/live region, disabled action state, review queue semantic label |
| App smoke | dashboard boot, Python API contract/mock render, app-level landmark and alarm semantics |
| Demo accessibility smoke | live API response -> App SSR markup -> clinical landmark/live region/action names/status text/review item semantics |
| Storybook | 주요 clinical UI state 문서화, static build gate, clinical story contract, jsdom axe check, browser role/a11y smoke |
Component test는 visual regression을 대체하지 않습니다. 다만 clinical monitoring UI에서는 알람, API error, review priority가 보조기술에서도 읽히는지가 중요한 기본 품질입니다. 그래서 AlarmBanner는 warning/critical 상태에서 data-tone, data-severity, role="alert", assertive live region, sound state, acknowledge/mute disabled state를 함께 고정합니다. NumericTile은 status/tone hook을 고정하고, WaveformPanel은 trace metadata와 polyline scaling contract를 고정합니다. monitoring/review API error는 각각 구분 가능한 alert label을 갖고, ReviewQueuePanel은 priority와 subject를 article label로 노출합니다.
Storybook은 warning, critical, acknowledged, stale snapshot, alarm action failure, interactive alarm workflow 같은 clinical state가 계속 빌드 가능한지 확인하는 static build gate로도 사용합니다. make docs/storybook/check는 Storybook static build 이후 scripts/check-clinical-storybook-contract.mjs를 실행해 필수 story id, rendered clinical state contract, jsdom axe accessibility smoke를 함께 확인합니다. 이어서 scripts/check-clinical-storybook-browser.mjs가 Storybook iframe을 Playwright Chromium으로 열고, browser DOM 기준으로 main, alarm alert, waveform img, review queue article, monitoring freshness state, alarm action error alert, alarm action button state, keyboard focus order, acknowledge/mute interaction, numeric status hook, axe accessibility smoke를 확인합니다. InteractiveAlarmWorkflowPlayCheck story는 Storybook play function으로 acknowledge/mute interaction을 직접 실행하고, browser check는 play 완료 marker와 최종 alarm state를 확인합니다. 마지막으로 scripts/check-clinical-storybook-visual.mjs가 alarm screenshot을 비교해 warning/critical tone과 acknowledge/mute 이후 visual state가 실제 픽셀 차이로 남아 있는지 확인합니다.
Storybook/Vite 조합에서는 legacy Storybook test-runner보다 Storybook Vitest addon을 기본 gate로 둡니다. patient-risk-web의 test:storybook script는 @storybook/addon-vitest가 stories를 Vitest browser-mode test로 변환하고, Chromium에서 render smoke와 play function interaction을 실행합니다. Custom browser/axe/visual scripts는 이 표준 gate를 대체하지 않고, clinical state에 특화된 추가 contract check로 유지합니다.
이 gate는 full visual regression은 아니지만, alarm severity, visual tone, live region, audible/suppressed state, stale freshness state, alarm action failure alert, acknowledge/mute transition, numeric warning/critical tone, Rust preprocessing provenance 같은 product-facing state가 story fixture에서 사라지는 것을 빠르게 잡습니다. jsdom axe scan은 semantic smoke이고, browser axe scan, Storybook Vitest browser-mode check, Storybook play check, keyboard/action check는 실제 DOM/layout 환경에서 한 단계 더 확인하는 smoke입니다. Visual diff smoke는 baseline image를 commit하지 않고 같은 Chromium 실행 안의 alarm screenshots를 비교하므로 OS별 font/rendering 차이에는 비교적 둔감하지만, clinical labeling review, manual keyboard/focus review, full visual regression을 대체하지는 않습니다. 다음 단계에서 더 정교한 screenshot baseline을 붙이더라도 이 semantic smoke와 browser smoke가 가장 작은 회귀 방지선으로 남습니다.
Browser audio adapter unit test는 Web Audio oscillator 생성뿐 아니라 suspended AudioContext resume, resume rejection cleanup, duplicate playback 방지를 확인합니다. 여기에 scripts/check-browser-alarm-audio.mjs browser smoke를 더해 Chromium user gesture 안에서 실제 AudioContext가 생성되고 oscillator start/stop/close path가 동작하는지 확인합니다. 이 smoke는 의료기기 수준의 sound pressure, alarm priority, browser permission labeling review를 대체하지 않지만, browser Web Audio boundary가 조용히 깨지는 회귀를 막는 최소 guard입니다.
Manual review는 자동 smoke와 별도입니다. 자동 gate가 alarm state regression을 잡더라도, alarm 문구, warning/critical/suppressed labeling, browser sound permission UX, alarm fatigue risk, screen reader reading order는 사람이 확인해야 합니다. 기준은 Clinical UI Review를 따릅니다.
Visual baseline은 모든 story를 무작정 snapshot으로 고정하지 않습니다. 1차 기준은 clinical state가 실제 픽셀 차이로 남는지 보는 visual smoke이고, 다음 단계 baseline은 normal, warning, critical audible, acknowledged, muted, stale, action failure처럼 clinical risk communication에 직접 연결되는 story로 제한합니다. Screenshot diff가 실패하면 단순 baseline 갱신이 아니라 manual labeling review evidence를 함께 남겨야 합니다.
App smoke는 component package의 semantic smoke를 다시 한 번 runtime composition 경계에서 확인합니다. patient-risk-web은 shared React component를 감싸는 얇은 app이지만, clinician-facing shell이므로 main landmark, dashboard title 연결, alarm live region, review queue item label이 app composition 중 사라지면 안 됩니다.
make demo/check는 live Python API 응답을 사용해 App을 server-side render하고, scripts/check-clinical-ui-accessibility.mjs로 landmark, waveform image role, alarm assertive live region, alarm action accessible name, numeric status text, review priority text를 검사합니다. 이 gate는 browser 기반 최종 접근성 검사가 아니라, clinical state semantics가 runtime composition에서 사라지거나 색상만으로 전달되는 것을 막는 가장 작은 회귀 방지선입니다.
Storybook contract는 아래 명령으로 확인합니다.
make docs/storybook/check
pnpm --filter @tirosh/patient-risk-web test:storybook
로컬에서 Playwright browser cache가 비어 있으면 먼저 Chromium을 설치합니다.
pnpm exec playwright install chromium
CI browser contract는 Playwright 공식 container image에서 실행합니다. 이 repo의 self-hosted runner는 browser와 Linux system library를 직접 제공하지 않고, Docker로 Playwright container를 실행하는 책임만 가집니다. 따라서 CI workflow에서는 pnpm exec playwright install chromium을 실행하지 않습니다.
Host runner에서 직접 browser contract를 실행해야 하는 예외적인 경우에만 Playwright dependency 설치가 필요합니다. 이 명령은 Linux package 설치를 위해 sudo 권한을 요구할 수 있으므로, 기본 CI 경로로 두지 않습니다.
pnpm exec playwright install --with-deps chromium
1-8. Clean Architecture 기준
TypeScript에서 Clean Architecture의 핵심은 clinical state 판단과 React rendering을 분리하는 것입니다.
patient-monitor-ui-core는 framework-independent mapper와 presentation policy를 담당합니다. patient-monitor-react-components는 그 view model을 접근 가능한 UI로 렌더링합니다. patient-risk-web은 Python API endpoint, refresh interval, browser runtime, action handler를 조립합니다.
Python API response
-> patient-monitor-ui-core mapper
-> patient-monitor-react-components
-> patient-risk-web runtime composition
단단하게 고정할 것은 alarm severity, sound state, stale state, numeric status, review priority 같은 화면 의미입니다. 느슨하게 둘 것은 styling token, component theme, fetch implementation, Storybook fixture source입니다.
1-9. DDD 기준
TypeScript bounded context는 monitoring UI입니다. Frontend는 risk score나 alarm severity를 계산하지 않고, backend가 만든 clinical state를 의료진이 오해하지 않게 표현합니다.
| Term | Meaning |
|---|---|
MonitoringSnapshot |
backend가 만든 현재 화면 계약 |
ViewModel |
React component가 안전하게 렌더링할 수 있는 presentation state |
freshness |
snapshot이 현재 상태로 믿을 수 있는지에 대한 UI evidence |
alarm presentation |
visual tone, live region, sound request, action state |
review priority |
queue item이 의료진 attention을 요구하는 정도 |
Storybook fixture는 runtime truth가 아니라 UI state language를 검토하기 위한 예제입니다. Runtime app은 Python API contract를 사용합니다.
1-10. TDD 기준
TypeScript TDD는 UI를 먼저 눈으로 맞추는 방식이 아니라, clinical state semantics를 test로 고정하는 방식입니다.
- API response fixture와 view model mapper unit test를 작성합니다.
- alarm/freshness/sound policy unit test로 상태 전이를 고정합니다.
- component test로 role, live region, action disabled state, numeric status text를 확인합니다.
- Storybook story로 clinical states를 문서화합니다.
- Storybook
playfunction으로 핵심 interaction state transition을 story 안에서 실행합니다. - Storybook Vitest addon으로 stories와
playfunction을 browser-mode test로 실행합니다. - Storybook contract/browser/axe/visual smoke로 상태가 실제 DOM과 화면에서 유지되는지 확인합니다.
make demo/check로 live Python API response가 web app composition에서도 같은 semantics를 유지하는지 확인합니다.
이 순서를 지키면 색상이나 layout 변경이 있어도 alarm/review/freshness 의미가 사라지는 회귀를 잡을 수 있습니다.
2. Package Registry
TypeScript package download는 project-local .npmrc를 통해 Nexus npm group repository를 사용합니다. .npmrc는 credential을 포함할 수 있으므로 commit하지 않습니다. Local 개발에서는 scripts/configure-nexus.sh가 생성하고, CI에서는 workflow가 직접 생성합니다.
NEXUS_NPM_REGISTRY=https://nexus.internal.tirosh.ai/repository/npm/ \
NEXUS_NPM_PUBLISH_REGISTRY=https://nexus.internal.tirosh.ai/repository/npm-guide-hosted/ \
NEXUS_USERNAME=github-actions \
NEXUS_PASSWORD="$NEXUS_PASSWORD" \
bash scripts/configure-nexus.sh
publish는 npm guide hosted repository로 보냅니다.
npm publish dist/npm/*.tgz --registry "$NEXUS_NPM_PUBLISH_REGISTRY"
3. Package Tests
pnpm --filter @tirosh/patient-monitor-ui-core test:unit
pnpm --filter @tirosh/patient-monitor-ui-core test:integration
pnpm --filter @tirosh/patient-monitor-ui-core build
pnpm --filter @tirosh/patient-monitor-react-components test:unit
pnpm --filter @tirosh/patient-monitor-react-components build
pnpm --dir packages/patient-monitor-ui-core pack --pack-destination ../../dist/npm
pnpm --dir packages/patient-monitor-react-components pack --pack-destination ../../dist/npm