fix(handover,notes,teaching,cli): resolve follow-up adversarial audit findings

Merged
BI
1mo ago
+440-87

User description

Summary

  • Shift scratchpad expiry & manual clear (src/components/on-call/handover/handover-page.tsx, src/lib/on-call/handover.ts): Keep loadOnCallHandoverScratchpad side-effect-free during initial useState evaluation, run the active shift expiry timer and Clear all action whenever scratchpad text is present (even with zero patient rows), and prevent scratchpad edits on an already-expired shift from silently refreshing the 12-hour label TTL.
  • ClinicalNoteEditor debounce stability & status recovery (src/components/clinical-notes/note-editor.tsx, tests/note-editor-caret.dom.test.tsx): Store onChange in a ref so parent re-renders with inline onChange handlers do not cancel the 400ms auto-save debounce timer on every keystroke, and reset saveStatus when edits revert to lastSavedValue instead of leaving "Unsaved changes" permanently stuck.
  • First-publication CLI content pin verification & --execute credential guard (scripts/publish-site-content-first-catalogue.ts, tests/site-content-first-publication-cli.test.ts): Verify reviewedContentSha256 for form sign-offs against recordContentSha256(..., "form", { catalog: formsCatalog }) and fail closed when --execute is invoked without NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.
  • EPA magic link cleanup & clipboard fallback (src/lib/teaching/assessments/magic-link.ts, src/components/teaching/assessments/assessments-help.tsx): Revoke locally minted EPA supervisor magic link tokens if the PUT /api/teaching/assessments persistence request fails, and display a manual copy fallback URL if navigator.clipboard.writeText rejects or is unavailable.
  • Fail-closed display.revoke authorization (src/lib/teaching/repository.ts, tests/teaching-display-revoke-auth.test.ts): Require valid granted_at and joined_at timestamps (grantedMs >= joinedMs) and fail closed if membership/role tables are unavailable when rotating a shared display session without current_display_token.

Verification

  • Checks run:
    • npx vitest run tests/note-editor-caret.dom.test.tsx tests/site-content-first-publication-cli.test.ts --no-file-parallelism --maxWorkers=1 -> Test Files 2 passed (2), Tests 20 passed (20)
    • npx vitest run tests/teaching-display-revoke-auth.test.ts -> Test Files 1 passed (1), Tests 6 passed (6)
    • npx prettier --write ... -> all 10 modified files formatted cleanly
    • Full repository static/UI suites and hosted CI checks: unrun locally by bare-PR publication request (GitHub Actions is the verification surface).

Risk and rollout

  • Risk: Low — scoped bug fixes and fail-closed guards across handover scratchpad privacy, clinical note editor debounce, first-publication CLI validation, EPA magic link error cleanup, and teaching display revocation.
  • Rollback: Revert commit bdbeb841d3.
  • Provider or production effects: None

Summary by cubic

Resolves the follow-up adversarial audit findings across handover privacy, clinical note autosave, teaching magic links and QR check-in, roster swaps, and clinical calculators.

  • Handover scratchpad respects shared-device privacy, loads after hydration, syncs across tabs, runs expiry and Clear all whenever text is present, and stops expired-shift edits from refreshing the label TTL; writes also check the stored shift stamp so a late expiry timer cannot start a fresh shift for stale text. Save now is the deliberate save, so it keeps text on shared devices even though auto-save is suppressed.
  • ClinicalNoteEditor keeps its auto-save debounce stable across parent re-renders, queues changes made while a save is in flight so reverting to stored text mid-save loses nothing, ignores saves that finish after the parent replaces the note, resets when a genuinely new controlled value arrives (canceling the queued save), and clears a stuck Unsaved changes status when edits revert; no false Saved state when onSave is omitted.
  • First-publication CLI verifies form sign-off content hashes and fails closed in production when --execute lacks ALLOW_DESTRUCTIVE_SITE_CONTENT_INIT=true or live Supabase credentials; EPA magic-link assessments are refused in production without a durable store.
  • EPA magic links revoke locally minted tokens when persistence fails, fall back to manual copy when the clipboard API is unavailable, and reject invalid clinician names before sharing; the write route rate-limits before the sign-in lookup.
  • Teaching display revocation fails closed against invalid or crossed timestamps and unavailable membership tables.
  • Clinical calculators parse inputs strictly with no fabricated empty-state results and add adjusted-body-weight support with bounds checks.
  • Roster safe-number editing re-flags conflicts when a third edit arrives after the banner opens; swap rule warnings verify actor role and require a matching acknowledgement on retry.
  • QR check-in accepts /teaching/c/ links and enforces demo-mode guards with manual entry hidden on attendance sheets; attendance gaps show loading and retryable errors instead of empty states.

Summary by CodeRabbit

  • Bug Fixes
    • Clinical note editing now handles in-progress saves and external updates more reliably, reducing the risk of stale or unsaved changes.
    • On-call handover notes now respect shift expiry, with clearer save and clear controls.
    • Magic-link registration now handles failed link creation and clipboard errors more clearly.
    • Display-link revocation now checks organiser access more consistently.

CodeAnt-AI Description

Protect handover privacy and fix note-saving and supervisor-link issues

What Changed

  • Handover scratchpads are cleared on shared devices and when a shift expires. Users can clear a scratchpad even when no patient entries are present.
  • Clinical notes keep saving after parent updates, and reverting to the last saved text clears the unsaved status.
  • Failed supervisor-link registration revokes the link; if copying is unavailable, the full link is shown for manual copying.
  • Display-link revocation now fails closed when organiser access or its timestamps cannot be verified. Live catalogue publishing also requires Supabase credentials.

Impact

✅ Scratchpads cleared on shared devices and at shift end
✅ Clinical note autosaves continue after parent updates
✅ Failed supervisor registrations leave no usable link
✅ Unverified display-link revocations are blocked

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.