Skip to content

fix(clinical,teaching,roster): remediate adversarial audit findings across calculators, handover privacy, EPA magic links, QR check-in, and roster swaps - #3574

Merged
BigSimmo merged 15 commits into
mainfrom
fix/adversarial-remediation-20261016
Oct 16, 2026
Merged

BigSimmo merged 15 commits into
mainfrom
fix/adversarial-remediation-20261016

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

User description

Summary

  • Bedside Clinical Calculators (ClinicalMathPanel, calculators/search-page.tsx, renal.ts, types.ts):
    • Initialize all numeric inputs (age, weightKg, creatinine, heightCm, qtMs, hrBpm, lithiumLevel, currentDoseMg, targetLevel) to "" so no fabricated patient numbers (92.25 mL/min, 875 mg/day) or false Level status: invalid (target 0–0 mmol/L) warnings render before clinician input.
    • Replace permissive Number.parseFloat with strict numeric parsing (parseStrictNumber), use 88.42 (CREATININE_UMOL_PER_MG_DL) for creatinine unit conversion, distinguish weightBasis: "adjusted" (ABW) vs "ideal" (IBW) in Cockcroft-Gault results, and mount only a single <ClinicalMathPanel /> in the DOM across desktop/mobile viewports.
  • On-Call Handover Scratchpad & Clinical Note Editor Privacy (note-editor.tsx, handover-page.tsx, handover.ts):
    • Gate ClinicalNoteEditor changes and saves on checkPatientDetail (src/lib/work-text/patient-detail-check.ts) with an accessible role="alert" privacy banner (data-testid="note-editor-privacy-warning"), and only show "Saved" / "Save now" when an onSave handler is provided.
    • Lift scratchpadText into OnCallHandoverPage backed by loadOnCallHandoverScratchpad / saveOnCallHandoverScratchpad (with writePatientLabels expiry wrapper and onCallDeviceStoreChangedEvent sync) so toggling "Make table" -> "Back to the form", switching specialty, or reloading never drops de-identified shift notes.
  • Teaching Mode, QR Check-In & EPA Magic-Link Security (respond/[token]/route.ts, magic-link.ts, teaching-code-sheet.tsx, qr-check-in-helper.tsx, teaching-qr-checkin-sheet.tsx, teaching-organise.tsx, organise-sheets.tsx, repository.ts):
    • Fail closed (503 assessment_store_unavailable) in production on /api/teaching/assessments/respond/[token] (matching dual-sign-off/route.ts), require requireAuthenticatedUser on PUT, bound magicRateLimitStore eviction, and validate clinician names via checkEpaClinicianName (Dr J. Smith, Ms Taylor) without weakening global checkPatientDetail.
    • Handle /teaching/c/<token> projector QR URLs in both TeachingCodeSheet and TeachingQrCheckinSheet, remove duplicate manual entry / filter inputs, fetch term attendance counts for AttendanceGapsSheet, and verify active on_call_service_members membership before display.revoke.
  • Roster Swap Server Rules & Safe-Number Concurrency (roster/team/repository.ts, roster-decision-sheet.tsx, needs-you-strip.tsx, roster-safe-number.tsx):
    • Scope verifySwapApproveRules to active managers on non-expired 'accepted' swaps with non-superseded shifts so non-managers, swap parties, expired swaps, and superseded shifts reach SQL roster_command's authoritative status/role transitions.
    • Pass acknowledgeRuleWarnings: true when rule warnings are uncheckable or acknowledged, keep RosterDecisionSheet open on 409 roster_swap_rule_warning, and guard RosterSafeNumber "Overwrite with my changes" against a third concurrent edit arriving after the conflict banner opened.
  • Clinical Governance & Sign-Off Hygiene (source-acquisitions.json, forms-content-review.json, service-records-review.json, publish-site-content-first-catalogue.ts, apply-specifier-worksheet.mjs):
    • Keep the 23 candidate TGA Product Information entries (#05WXHX) conservative (validationStatus: "unverified", documentStatus: "review_due", disposition: "rejected") with no unverified dates or attestations, enforce "Psych Sift" display-name sign-off (2026-10-16-psych-sift-display-name-sign-off.md), validate 64-hex reviewedContentSha256 on catalogue publication, and flush blockquote buffers across blank lines.
  • Zero RAG Changes: No RAG, retrieval, embedding, answer-verification, source-text-sanitizer, hazard-controls, or PDF-extractor files are touched (ragRanking: false).

Areas touched: Personal practice, Clinical reference content, App experience, Delivery and assurance, Answer engine, Knowledge and records

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains PsychSift Production (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

Verification

Paste the decisive line from whatever you ran — exit code 0 alone is not proof.

  • Checks run:
    • npm run typecheck: [gate-receipts] recorded a pass for "typecheck:internal" (10171 input files).
    • npx eslint --max-warnings=0 <33 changed code files>: 0 errors, 0 warnings
    • npm run check:diff-integrity -- --base origin/main: [diff-integrity] OK — inspected 39 changed file(s) against origin/main (10 test file(s) checked for floor drops).
    • npm run check:forms-review-sheet: Wrote docs/evidence/forms-operational-guidance-review.md. (clean check)
    • npm run source-acquisitions:check-order & npm run check:source-acquisitions: Source acquisition ledger passed.
    • npx vitest run <14 affected suites>: Test Files 14 passed (14) | Tests 344 passed (344)
    • UI verification not run: Covered locally by deterministic jsdom component suites (tests/note-editor-caret.dom.test.tsx, tests/qr-check-in-helper.dom.test.tsx, tests/attendance-roster.dom.test.tsx, tests/clinical-math-bounds.test.ts) and delegated to hosted GitHub CI browser shards.
    • Verification not run: Provider-backed evaluations (eval:retrieval:quality, eval:rag, eval:quality) not run because zero RAG/retrieval/answer-generation files were changed (ragRanking: false).

Risk and rollout

Worth a line for clinical, data, auth/privacy, migration, dependency, or deployment changes.

  • Risk: Low — hardens bedside calculator empty-input defaults, client/localStorage PII guards on handover notes, EPA magic-link production fail-closed/auth guards, QR check-in URL parsing, and roster swap approval preflight checks without altering database schema or RAG behavior.
  • Rollback: Revert this PR commit cleanly (git revert); no database migrations or external state changes are included.
  • Provider or production effects: None

Notes

  • Includes two-stage adversarial swarm audit (initial 4-agent adversarial assessment + post-fix 4-agent adversarial review) with immutable branch review record docs/branch-review-records/2a6b620065f5371039438fdbff73be97da6baf6c40dace3596e26c7bad0047dd.record.md.

Summary by CodeRabbit

  • New Features

    • Attendance-gap views include loading, error, retry, and demo states, plus roster search and accessible gap details.
    • On-call handover notes can be saved between visits and are cleared when the handover is wiped.
    • Teaching check-in accepts validated QR links and provides clearer feedback for invalid links and demo-mode scans.
  • Improvements

    • Clinical calculators start with blank inputs, distinguish missing from invalid values, show partial results when possible, and use adjusted weight for some kidney-function estimates.
    • Clinical notes flag potential patient-identifying details and prevent saving until corrected.
    • Roster approvals and edits better handle rule warnings and concurrent changes.
    • Assessment submissions and teaching access checks provide clearer errors and stronger validation.
    • Calculator panels display in the appropriate sidebar for desktop and mobile screens.

CodeAnt-AI Description

Prevent unsafe clinical results and protect handover, teaching, and roster workflows

What Changed

  • Clinical calculators start with blank inputs, show prompts until required values are entered, and reject malformed numbers instead of displaying fabricated results.
  • The shift scratchpad warns about detected patient details and blocks saving them. Notes persist across form changes, reloads, and tabs; on shared devices, saving requires an explicit action.
  • EPA supervisor links require sign-in to register and are unavailable in production until responses can be stored reliably. Clinicians can enter names with titles, and errors are shown when registration fails.
  • Teaching check-in rejects invalid or off-site QR links, ignores scans that finish after the scanner stops, and clearly warns that demo check-ins are not saved.
  • Attendance gaps show loading and retryable error states when term records cannot be loaded, rather than showing misleading gaps.
  • Roster approvals keep requests open when rule checks fail and require review of warnings; safe-number edits detect newer changes before overwriting them.
  • Content publishing stops when a reviewed item lacks a valid record tying its sign-off to the content.

Impact

✅ No fabricated clinical results before patient input
✅ Shift notes survive reloads without saving detected patient details
✅ Invalid teaching check-in links are rejected

💡 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.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BigSimmo has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@codeant-ai

codeant-ai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed 415a19b Oct 16, 2026 · 09:21 09:31
✅ Reviewed your PR a9c3002 Oct 16, 2026 · 08:21 08:36

@codeant-ai

codeant-ai Bot commented Oct 16, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Next included review available in 23 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 98 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: BigSimmo/PsychSift/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Team
  • Run ID: f1ad9db7-937d-4f78-b772-a793ac53b574
📥 Commits

Reviewing files that changed from the base of the PR and between ceaaeef and 6432f13.

📒 Files selected for processing: 4
  • data/forms-content-review.json
  • src/components/teaching/attendance-roster.tsx
  • src/components/teaching/qr-check-in-helper.tsx
  • tests/ui-roster-team.spec.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: BigSimmo/PsychSift/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 12ca529c-f13e-406d-b458-c5e9edd76eb7
📥 Commits

Reviewing files that changed from the base of the PR and between 8a74f7e and ceaaeef.

📒 Files selected for processing: 13
  • docs/branch-review-records/ad2c45bc2f3c606a7847421acc887ddf4861ab0563a5b96b4c6315b066837d90.record.md
  • src/components/calculators/clinical-math-panel.tsx
  • src/components/on-call/handover/handover-page.tsx
  • src/components/roster/manage/roster-decision-sheet.tsx
  • src/components/roster/team/calendar/needs-you-strip.tsx
  • src/components/teaching-attendance/teaching-qr-checkin-sheet.tsx
  • src/components/teaching/assessments/assessments-help.tsx
  • src/components/teaching/qr-check-in-helper.tsx
  • src/components/teaching/teaching-organise.tsx
  • src/lib/teaching/assessments/magic-link.ts
  • tests/clinical-math-bounds.test.ts
  • tests/roster-team-calendar-manager.dom.test.tsx
  • tests/teaching-qr-checkin.dom.test.tsx
🚧 Files skipped from review as they are similar to previous changes: 3
  • src/components/teaching/assessments/assessments-help.tsx
  • src/components/teaching/teaching-organise.tsx
  • src/components/calculators/clinical-math-panel.tsx

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

📝 Walkthrough

This pull request updates review records and application flows for content publication, EPA assessments, clinical calculators and notes, handover scratchpad storage, roster operations, and teaching attendance and QR check-in.

Changes: Review records and content processing

Layer / File(s) Summary
Review attribution and records
data/*-review.json, docs/evidence/forms-operational-guidance-review.md, docs/medication-interaction-lexicon-review.md, docs/branch-review-records/*
Form and service review records and related sign-offs name Psych Sift. Review timestamps in the form guidance record also change. Two branch-review records document assessment and merge details.
Publication digest and worksheet parsing
scripts/publish-site-content-first-catalogue.ts, tests/site-content-first-publication-cli.test.ts, scripts/apply-specifier-worksheet.mjs
Publication validation requires a 64-character lowercase hexadecimal review digest; tests cover missing and malformed digests. A blank line now closes a shared worksheet answer block.

EPA assessment links

Layer / File(s) Summary
Clinician names, responses, and route guards
src/lib/teaching/assessments/magic-link.ts, src/app/(search-app)/teaching/assessments/respond/[token]/page.tsx, src/app/api/teaching/assessments/respond/[token]/route.ts, src/components/teaching/assessments/assessments-help.tsx, tests/epa-magic-link.test.ts
Assessment flows validate clinician names, handle unsuccessful responses, and display registration errors. Production routes reject requests when the assessment store is unavailable, and PUT requires authentication.

Clinical calculators

Layer / File(s) Summary
Calculator inputs, calculations, and rendering
src/components/calculators/clinical-math-panel.tsx, src/components/calculators/search-page.tsx, src/lib/calculators/clinical-math/*, tests/clinical-math-*
Calculator inputs start blank and use strict numeric parsing. Renal calculations support adjusted body weight and use a shared creatinine conversion constant. Results distinguish missing inputs from invalid values, and the panel renders in the sidebar matching the viewport.

Clinical note privacy and saving

Layer / File(s) Summary
Note validation and save behavior
src/components/clinical-notes/note-editor.tsx, tests/note-editor-caret.dom.test.tsx
The editor blocks invalid note text from change callbacks and saves, displays privacy warnings, and avoids marking newer edits as saved when an earlier save completes.

On-call handover scratchpad

Layer / File(s) Summary
Scratchpad persistence and page wiring
src/lib/on-call/handover.ts, src/components/on-call/handover/handover-page.tsx
Scratchpad helpers load, validate, save, and clear text. The page loads persisted text, handles storage changes, and skips automatic persistence on shared devices.

Roster operations

Layer / File(s) Summary
Swap-rule approval and conflict-aware overwrite
src/lib/roster/team/repository.ts, src/components/roster/manage/roster-decision-sheet.tsx, src/components/roster/team/calendar/needs-you-strip.tsx, src/components/roster/manage/roster-safe-number.tsx, tests/roster-*, tests/safe-number-concurrency.test.ts
Swap-rule checks account for actor and assignment state. Approval acknowledges reviewed warnings and refreshes review after a server warning. Overwrite saves detect further concurrent edits.

Teaching attendance and check-in

Layer / File(s) Summary
Attendance gaps roster
src/components/teaching/teaching-organise.tsx, src/components/teaching/organise-sheets.tsx, src/components/teaching/attendance-roster.tsx, tests/attendance-roster.dom.test.tsx
The organiser loads attendance counts or assigns demo counts. The gaps sheet passes counts and load states to AttendanceRoster, which provides filtering, search, and gap summaries.
QR scanner behavior and token validation
src/components/teaching-attendance/teaching-qr-checkin-sheet.tsx, src/components/teaching/teaching-code-sheet.tsx, src/components/teaching/qr-check-in-helper.tsx, tests/qr-check-in-helper.dom.test.tsx, tests/teaching-qr-checkin.dom.test.tsx
Scanners validate check-in links and numeric codes, enforce demo-mode behavior, and can hide manual entry. Scan errors are marked as alerts, and pending detections are ignored after scanning stops.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Bug fix

Merge Risk: 🟡 Moderate · up to ceaae

Entered calculator values or unsaved notes may be lost, and a publication sign-off may not verify the content being published. Resolve or explicitly accept these risks before merging.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 51 functions across 36 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title accurately summarizes the pull request's main remediation work across calculators, teaching, handover privacy, and roster swaps. It is long but clear and specific.
Description check Passed The description includes the required Summary, Verification, Risk and rollout, and Notes sections. It documents affected areas, verification results, risks, rollback, production effects, and known fol…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description includes the required Summary, Verification, Risk and rollout, and Notes sections. It documents affected areas, verification results, risks, rollback, production effects, and known follow-up items.

Full details: Docstring Coverage

Explanation

Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 51 functions across 36 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T08:26:25.248801Z a9c3002 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@supabase

supabase Bot commented Oct 16, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@claude

claude Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@codeant-ai codeant-ai Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files label Oct 16, 2026
@codeant-ai

codeant-ai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🏁 CodeAnt Quality Gate Results

Commit: 6432f131
Scan Time: 2026-10-16 10:40:18 UTC

✅ Overall Status: PASSED

Quality Gate Details

Quality Gate Status Details
Secrets ✅ PASSED 0 secrets found, 1 false positive secret suppressed
Duplicate Code ✅ PASSED 1.5% duplicated
SAST ✅ PASSED No security issues
Bugs ✅ PASSED Rating S: No bugs
IAC ✅ PASSED Rating S: No issues

View Full Results

@BigSimmo
BigSimmo enabled auto-merge (squash) October 16, 2026 08:22
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Remediate clinical, teaching, handover, and roster audit findings

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Prevent fabricated calculator results and protect de-identified handover notes from loss or unsafe
 saves.
• Harden teaching check-in, EPA links, attendance reporting, and roster decisions against invalid or
 stale state.
• Strengthen publication sign-offs and add regression coverage for audited workflows.
Diagram

graph TD
  CP["Clinical panel"] --> CE["Calculator engine"]
  HE["Handover editor"] --> LS["Local scratchpad"]
  TU["Teaching UI"] --> TA["Teaching API"]
  RU["Roster UI"] --> RR["Roster repository"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Persist EPA assessments in a durable database
  • ➕ Would permit production magic-link responses across server instances and restarts.
  • ➖ Requires a storage schema, migration, access controls, and a larger security review than this remediation.

Recommendation: Keep the production fail-closed guard while EPA assessments use an ephemeral store. A durable store is the appropriate follow-up if production EPA links are required; removing the guard or treating browser state as authoritative would not address the underlying persistence risk.

Files changed (39) +1494 / -526

Enhancement (1) +18 / -5
attendance-roster.tsxSupport attendance-gap search and summaries +18/-5

Support attendance-gap search and summaries

• Allows callers to set the search label and empty-state test ID, and adds an accessible summary of displayed members with gaps.

src/components/teaching/attendance-roster.tsx

Bug fix (23) +803 / -371
apply-specifier-worksheet.mjsStop worksheet quotes at blank lines +4/-1

Stop worksheet quotes at blank lines

• Ends a collected quote block at a blank line instead of absorbing subsequent worksheet prose.

scripts/apply-specifier-worksheet.mjs

publish-site-content-first-catalogue.tsRequire a sign-off content digest +8/-0

Require a sign-off content digest

• Rejects publication sign-offs whose reviewedContentSha256 is missing or not a 64-character lowercase hexadecimal digest.

scripts/publish-site-content-first-catalogue.ts

page.tsxValidate assessor names and surface response failures +30/-28

Validate assessor names and surface response failures

• Uses clinician-specific name checking and keeps unsuccessful API submissions visible as errors. A local response fallback now runs only when a request throws in test mode.

src/app/(search-app)/teaching/assessments/respond/[token]/page.tsx

route.tsFail closed for production EPA links +20/-0

Fail closed for production EPA links

• Returns an unavailable-store error for GET, PUT, and POST in production. PUT also requires an authenticated user before registering a request.

src/app/api/teaching/assessments/respond/[token]/route.ts

clinical-math-panel.tsxRemove fabricated calculator defaults +196/-113

Remove fabricated calculator defaults

• Starts numeric fields empty, strictly parses entered values, and shows prompts rather than results or errors before sufficient input. It also labels adjusted versus ideal renal weight and uses the shared creatinine conversion constant.

src/components/calculators/clinical-math-panel.tsx

search-page.tsxRender one responsive clinical math panel +19/-2

Render one responsive clinical math panel

• Uses the desktop breakpoint to mount the panel in either the sidebar or mobile area, avoiding duplicate instances in the DOM.

src/components/calculators/search-page.tsx

note-editor.tsxGuard note edits and report real save status +111/-68

Guard note edits and report real save status

• Blocks change callbacks and saves for detected patient details and presents an accessible privacy warning. Save controls appear only when applicable, while shared-device and asynchronous save behavior receive tighter handling.

src/components/clinical-notes/note-editor.tsx

handover-page.tsxRetain the handover scratchpad +24/-3

Retain the handover scratchpad

• Owns scratchpad text at page level and connects the editor to local load and save functions. It resets the text on patient-label clearing and preserves it when the form view remounts.

src/components/on-call/handover/handover-page.tsx

roster-decision-sheet.tsxRefresh swap rule warnings before approval +19/-3

Refresh swap rule warnings before approval

• Handles uncheckable rules as requiring acknowledgement and rechecks a server rule warning so the reviewer sees current decision details.

src/components/roster/manage/roster-decision-sheet.tsx

roster-safe-number.tsxRecheck conflicts before safe-number overwrite +18/-1

Recheck conflicts before safe-number overwrite

• Compares a fresh read with the conflict state the manager acknowledged. A further edit to a changed cell raises another conflict instead of being overwritten.

src/components/roster/manage/roster-safe-number.tsx

needs-you-strip.tsxPass reviewed swap-rule acknowledgement +5/-3

Pass reviewed swap-rule acknowledgement

• Includes the acknowledgement flag when submitting an approved swap after its rule fingerprint has been reviewed.

src/components/roster/team/calendar/needs-you-strip.tsx

teaching-qr-checkin-sheet.tsxRecognize validated check-in QR links +43/-5

Recognize validated check-in QR links

• Routes same-origin, valid teaching check-in tokens to their check-in page instead of extracting arbitrary digits. Typed-code recognition is narrower, and scan errors are announced accessibly.

src/components/teaching-attendance/teaching-qr-checkin-sheet.tsx

assessments-help.tsxApply clinician-name validation to EPA requests +12/-26

Apply clinician-name validation to EPA requests

• Validates guest names with the EPA-specific check. Link registration is initiated without waiting for its response before the generated link is copied.

src/components/teaching/assessments/assessments-help.tsx

organise-sheets.tsxUse one attendance-gap filter +27/-40

Use one attendance-gap filter

• Removes the duplicate sheet-level filter and delegates filtering and empty states to AttendanceRoster. It also distinguishes loading counts and demo data.

src/components/teaching/organise-sheets.tsx

qr-check-in-helper.tsxMake scanner fallback optional +60/-45

Make scanner fallback optional

• Adds an option to hide duplicate manual code entry, keeps scan callbacks current without restarting the camera, and marks errors as alerts.

src/components/teaching/qr-check-in-helper.tsx

teaching-code-sheet.tsxValidate projector QR destinations +21/-7

Validate projector QR destinations

• Hides the scanner's redundant typed-code field, rejects demo check-ins, and navigates only for valid same-origin teaching check-in tokens.

src/components/teaching/teaching-code-sheet.tsx

teaching-organise.tsxLoad term attendance counts +23/-0

Load term attendance counts

• Fetches an attendance export for the term, aggregates check-ins by user, and passes counts and loading state to the gaps sheet.

src/components/teaching/teaching-organise.tsx

renal.tsDistinguish adjusted renal weight +6/-3

Distinguish adjusted renal weight

• Exports the creatinine conversion constant and reports an adjusted weight basis when Cockcroft-Gault uses adjusted body weight.

src/lib/calculators/clinical-math/renal.ts

types.tsType adjusted weight results +1/-1

Type adjusted weight results

• Extends the Cockcroft-Gault weightBasis type to include adjusted body weight.

src/lib/calculators/clinical-math/types.ts

handover.tsPersist de-identified shift scratchpad text +33/-0

Persist de-identified shift scratchpad text

• Adds scratchpad load and save functions using expiring patient-label storage. Saves reject detected patient details, emit a store-change event, and are cleared with the handover.

src/lib/on-call/handover.ts

repository.tsScope pre-approval checks to applicable swaps +69/-15

Scope pre-approval checks to applicable swaps

• Limits swap rule warnings to current accepted swaps reviewed by active managers and excludes superseded assignments. Final roster commands remain responsible for enforcing their own authorization and state rules.

src/lib/roster/team/repository.ts

magic-link.tsConstrain EPA names and rate-limit storage +18/-2

Constrain EPA names and rate-limit storage

• Adds clinician-specific name checking for EPA requests and responses without changing the global privacy checker. Evicts old rate-limit entries when the in-memory store reaches its cap.

src/lib/teaching/assessments/magic-link.ts

repository.tsVerify membership before display revocation +36/-5

Verify membership before display revocation

• Requires an active service membership for an organiser revoking a display link and rejects roles predating a member's rejoin. It also parses the command's returned result.

src/lib/teaching/repository.ts

Tests (10) +542 / -20
attendance-roster.dom.test.tsxCheck attendance filter rendering +2/-0

Check attendance filter rendering

• Asserts that the roster exposes one search input and does not show the gaps empty state when rows are present.

tests/attendance-roster.dom.test.tsx

clinical-math-bounds.test.tsCover empty and populated calculator states +53/-3

Cover empty and populated calculator states

• Checks that renal, QTc, and lithium panels show no fabricated results before entry, then display calculations after the required inputs are supplied.

tests/clinical-math-bounds.test.ts

clinical-math-calculators.test.tsAssert adjusted weight and lithium limits +4/-1

Assert adjusted weight and lithium limits

• Checks adjusted renal weight reporting and verifies errors for lithium targets beyond the toxic threshold.

tests/clinical-math-calculators.test.ts

epa-magic-link.test.tsCover EPA name and route safeguards +89/-1

Cover EPA name and route safeguards

• Tests titled clinician names, unauthenticated registration rejection, and production refusal of the ephemeral assessment store.

tests/epa-magic-link.test.ts

note-editor-caret.dom.test.tsxCover privacy and save-control behavior +67/-1

Cover privacy and save-control behavior

• Checks that editors without onSave show no false save controls and that detected patient details block callbacks and saves until removed.

tests/note-editor-caret.dom.test.tsx

qr-check-in-helper.dom.test.tsxCover scanner QR and demo guards +90/-5

Cover scanner QR and demo guards

• Tests removal of duplicate manual entry, demo-mode rejection, invalid link alerts, and navigation for valid check-in tokens.

tests/qr-check-in-helper.dom.test.tsx

roster-swap-server-rules.test.tsCover inapplicable swap-rule warnings +73/-0

Cover inapplicable swap-rule warnings

• Verifies that expired swaps, swap parties, and non-managers do not receive pre-approval rule warnings before the roster command.

tests/roster-swap-server-rules.test.ts

safe-number-concurrency.test.tsCover third-edit overwrite conflicts +44/-0

Cover third-edit overwrite conflicts

• Simulates another same-cell edit after a conflict appears and verifies that overwrite prompts for review again before saving.

tests/safe-number-concurrency.test.ts

site-content-first-publication-cli.test.tsCover missing and malformed sign-off hashes +46/-5

Cover missing and malformed sign-off hashes

• Adds valid digests to sign-off fixtures and verifies rejection of records with absent or malformed content hashes.

tests/site-content-first-publication-cli.test.ts

teaching-display-revoke-auth.test.tsCover revoked and stale organiser access +74/-4

Cover revoked and stale organiser access

• Verifies that missing service membership or a role granted before rejoining prevents display-link revocation.

tests/teaching-display-revoke-auth.test.ts

Documentation (5) +131 / -130
forms-content-review.jsonRevise form review attribution +54/-54

Revise form review attribution

• Changes the recorded reviewer to Psych Sift across the form review entries while retaining their review timestamps and content hashes.

data/forms-content-review.json

service-records-review.jsonRevise service-record review attribution +21/-21

Revise service-record review attribution

• Changes reviewedBy to Psych Sift for the affected service records without changing their timestamps or content hashes.

data/service-records-review.json

2a6b620065f5371039438fdbff73be97da6baf6c40dace3596e26c7bad0047dd.record.mdRecord the adversarial remediation review +1/-0

Record the adversarial remediation review

• Adds a branch-review ledger entry summarizing the assessment, fixes, and reported verification.

docs/branch-review-records/2a6b620065f5371039438fdbff73be97da6baf6c40dace3596e26c7bad0047dd.record.md

forms-operational-guidance-review.mdAlign form guidance review status +54/-54

Align form guidance review status

• Aligns displayed reviewer attribution and dates with the form review records and their existing sign-off pins.

docs/evidence/forms-operational-guidance-review.md

medication-interaction-lexicon-review.mdRevise lexicon sign-off attribution +1/-1

Revise lexicon sign-off attribution

• Changes the named clinical-owner reviewer in the medication interaction review.

docs/medication-interaction-lexicon-review.md

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a9c3002c4b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 40 files

Confidence score: 3/5

  • Crossing the 1280px breakpoint in src/components/calculators/search-page.tsx remounts the calculator and loses entered values, open state, and active tab. Keep the panel state above the responsive branches.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/components/calculators/search-page.tsx">

<violation number="1" location="src/components/calculators/search-page.tsx:608">
P2: Crossing the 1280px breakpoint unmounts the current calculator panel and mounts a new one, discarding all entered values, the open state, and the active tab. Preserve the panel state above the responsive branches, or keep one instance mounted while changing its layout.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread tests/clinical-math-bounds.test.ts Resolved
Comment thread data/forms-content-review.json Resolved
@cubic-dev-ai

cubic-dev-ai Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Auto-fix isn't included in your plan ↗

Upgrade to Team

@qodo-code-review

qodo-code-review Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

No findings for this group


Remediation recommended

No findings for this group


Informational

No findings for this group

Grey Divider


Resolved findings

1. Stopped scanners can still navigate ✓ Resolved
Description
The post-detection guard checks cancelled but not the scan generation that stopCamera()
invalidates immediately. If a QR detection resolves after the user stops or closes the scanner but
before the effect cleanup runs, the result can still reach window.location.assign() or
submitCode().
Code

src/components/teaching-attendance/teaching-qr-checkin-sheet.tsx[R165-166]

+        // The scanner may have been stopped or closed while detect() was pending.
+        if (cancelled) return;
Relevance

●●● Strong

Async race fixes are explicitly prioritized; accepted precedent requires synchronous generation
invalidation during pending operations.

PR-#3571
PR-#3566

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The review skill directs inspection of changed behavior and realistic failure paths. The new guard
relies on cancelled, which is set by effect cleanup, while stopping the scanner immediately
changes a separate generation ref; the added QR navigation path remains reachable before cleanup.

src/components/teaching-attendance/teaching-qr-checkin-sheet.tsx[39-47]
src/components/teaching-attendance/teaching-qr-checkin-sheet.tsx[159-166]
src/components/teaching-attendance/teaching-qr-checkin-sheet.tsx[189-205]
Skill: review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A pending QR detection can be processed after the user stops or closes the scanner because the post-await guard relies on effect cleanup rather than the synchronously invalidated scan generation.

## Fix Focus Areas
- src/components/teaching-attendance/teaching-qr-checkin-sheet.tsx[159-166]
- src/components/teaching-attendance/teaching-qr-checkin-sheet.tsx[39-47]

## Recommended Fix
Capture the active scan generation when starting the detection loop and, immediately after `detect()` resolves, return if it differs from `scanGenerationRef.current` or the effect has been cancelled. Add a focused test with a pending detection that resolves after Stop or Close, asserting no navigation or check-in occurs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Managers approve unseen swap warnings ✓ Resolved
Description
act in RosterDecisionSheet retries a rejected approval with acknowledgeRuleWarnings: true
after the manager acknowledged only a generic, uncheckable roster review. When client-side roster
reads fail but the server detects a swap rule warning, that retry skips the server’s rule precheck
without presenting the warning to the manager.
Code

src/components/roster/manage/roster-decision-sheet.tsx[R200-203]

+      result = await postRosterAction(
+        serviceId,
+        decisionAction(fresh.current, approve, { acknowledgeRuleWarnings: true }),
+      );
Relevance

●●● Strong

Recent roster precedent accepted requiring fresh warning checks before approval; this finding
identifies the same unseen-warning bypass.

PR-#3198

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Failed assignment or settings reads leave the fresh client review checkable: false with no
specific warnings, so the manager sees only a generic inability-to-check message. The retry then
sends acknowledgeRuleWarnings: true after the server reports a warning, and the repository skips
its rule verifier for approvals carrying that flag.

src/components/roster/manage/roster-decision-sheet.tsx[35-41]
src/components/roster/manage/roster-decision-sheet.tsx[77-99]
src/components/roster/manage/roster-decision-sheet.tsx[187-204]
src/lib/roster/team/repository.ts[283-294]
src/lib/roster/team/repository.ts[351-355]
src/components/roster/manage/roster-decision-sheet.tsx[31-39]
src/components/roster/manage/roster-decision-sheet.tsx[59-95]
src/components/roster/manage/roster-decision-sheet.tsx[188-203]
src/lib/roster/team/repository.ts[283-295]
Skill: access
Skill: api

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An acknowledged generic roster-check failure must not authorize a server-detected swap rule warning that the manager has not seen.

## Fix Focus Areas
- src/components/roster/manage/roster-decision-sheet.tsx[188-218]
- src/lib/roster/team/repository.ts[283-294]
- src/lib/roster/team/repository.ts[351-355]

## Recommended Fix
Remove the automatic acknowledged retry when the client review is uncheckable or the server reports a warning the manager has not seen. Keep approval blocked until the rules can be checked and their warnings presented for explicit manager acknowledgment; refresh the review and require a further confirmation before submitting an acknowledged approval. If the warning cannot be presented, fail closed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Supervisors get links that were never registered ✓ Resolved
Description
RequestEpaSheet sends the magic-link registration PUT with `void fetch(...).catch(() =>
undefined)` and never checks its response before copying the link and showing “Supervisor link
ready.” Signed-out requests now receive 401, while production registration and retrieval receive
503, so the supervisor cannot open a link that the UI presented as ready.
Code

src/components/teaching/assessments/assessments-help.tsx[R301-310]

+              void fetch(`/api/teaching/assessments/respond/${encodeURIComponent(generated.token)}`, {
+                method: "PUT",
+                headers: { "Content-Type": "application/json" },
+                body: JSON.stringify({
+                  epaCode: epa,
+                  traineeName: SAMPLE_DOCTOR.name,
+                  supervisorName,
+                  supervisorRole: guest,
+                }),
+              }).catch(() => undefined);
Relevance

●●● Strong

Accepted precedents require failed writes or unavailable endpoints to prevent falsely reporting
successful UI actions.

PR-#3206
PR-#2371

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The previous UI awaited registration and displayed “Could not register supervisor link. Try again.”
when the response was not OK; the changed UI ignores the PUT outcome before copying the link. The
route now rejects requests without a valid session with 401 and rejects production requests with 503
before registration or retrieval, while the client-created record remains local and inaccessible to
the recipient.

src/components/teaching/assessments/assessments-help.tsx[286-316]
src/app/api/teaching/assessments/respond/[token]/route.ts[22-30]
src/app/api/teaching/assessments/respond/[token]/route.ts[94-118]
src/lib/supabase/auth.ts[287-300]
src/components/teaching/assessments/assessments-help.tsx[293-319]
src/app/api/teaching/assessments/respond/[token]/route.ts[68-72]
src/app/api/teaching/assessments/respond/[token]/route.ts[94-98]
src/lib/teaching/assessments/magic-link.ts[340-360]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The request sheet copies and announces a supervisor link without confirming that its registration PUT succeeded. Signed-out users receive a 401, and production registration and retrieval receive a 503, leaving the recipient unable to open the link.

## Fix Focus Areas
- src/components/teaching/assessments/assessments-help.tsx[301-319]
- src/app/api/teaching/assessments/respond/[token]/route.ts[22-30]

## Recommended Fix
Await the registration `fetch` and check its response before copying or announcing the link. If the request throws or `!registration.ok`, call `setMagicLinkCopied("Could not register supervisor link. Try again.")` and return; consider a sign-in message for 401 and an unavailable-store message for 503 (`assessment_store_unavailable`). While the production store is unavailable, disable link creation and explain why.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View resolved (4)
4. Shared devices retain scratchpad notes ✓ Resolved
Description
OnCallHandoverPage calls saveOnCallHandoverScratchpad from ClinicalNoteEditor’s onChange
callback, writing accepted edits to browser storage before the editor’s shared-device auto-save
guard runs. On a shared device, typing in the scratchpad therefore persists text and triggers
storage-change events even though the editor says background saving is paused and reserves saving
for an intentional action.
Code

src/components/on-call/handover/handover-page.tsx[R990-993]

+                    onChange={(text) => {
+                      setScratchpadText(text);
+                      try {
+                        saveOnCallHandoverScratchpad(text);
Relevance

●●● Strong

Shared-device persistence bypasses the privacy guard, matching accepted findings against unintended
local storage writes.

PR-#3245
PR-#3366

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
ClinicalNoteEditor invokes onChange for changes that pass privacy validation before checking
sharedDevice; that check suppresses only the editor’s subsequent background save and displays the
paused-auto-save notice. The handover page’s callback writes those changes through the scratchpad
storage writer, so the editor’s guard does not prevent local persistence or the associated
storage-change events.

src/components/on-call/handover/handover-page.tsx[986-1000]
src/components/clinical-notes/note-editor.tsx[175-187]
src/lib/on-call/handover.ts[246-268]
src/components/clinical-notes/note-editor.tsx[152-188]
src/components/clinical-notes/note-editor.tsx[311-315]
src/components/on-call/handover/handover-page.tsx[986-1002]
src/components/clinical-notes/note-editor.tsx[152-186]
src/lib/on-call/handover.ts[245-262]
src/lib/patient-label-storage.ts[329-351]
Skill: privacy

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The handover scratchpad writes accepted edits to browser storage on every change, bypassing the editor’s shared-device background-save suppression.

## Fix Focus Areas
- src/components/on-call/handover/handover-page.tsx[986-1002]
- src/components/clinical-notes/note-editor.tsx[152-187]

## Recommended Fix
Update scratchpad state without writing to storage in `onChange` when shared-device mode is enabled. Persist through the editor’s intentional Save-now action on shared devices; if immediate persistence remains for other devices, check `useSharedDevice()` before writing. Preserve privacy validation before persistence and add a focused test confirming that typing on a shared device does not persist the scratchpad.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Scratchpad load causes hydration mismatch ✓ Resolved
Description
useState(() => loadOnCallHandoverScratchpad()) reads localStorage during the first render. That
returns "" on the server and the stored text on the client, so the textarea's initial value
differs between server and client output. readPatientLabels also runs clearExpiredPatientLabels
and clearStaleSessionLabels, which can wipe every patient-label store and dispatch the cleared
event mid-render, before the page's clear listener is subscribed.
Code

src/components/on-call/handover/handover-page.tsx[565]

+  const [scratchpadText, setScratchpadText] = useState<string>(() => loadOnCallHandoverScratchpad());
Relevance

●●● Strong

Hydration-safe deferred storage reads and render side-effect prevention match recent accepted
patterns.

PR-#3480
PR-#3220

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The rest of this page deliberately reads storage through useSyncExternalStore with an undefined
server snapshot, and its comment says "read directly so render never wipes". The new state
initializer goes through readPatientLabels instead, and that function wipes storage as a side
effect.

src/components/on-call/handover/handover-page.tsx[110-139]
src/lib/patient-label-storage.ts[354-373]
src/lib/patient-label-storage.ts[231-249]
src/lib/on-call/handover.ts[240-243]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The scratchpad state is initialised from localStorage during render. This produces an SSR/client hydration mismatch, and the read can wipe patient-label storage mid-render.

## Fix Focus Areas
- src/components/on-call/handover/handover-page.tsx[565-566]
- src/components/on-call/handover/handover-page.tsx[986-990]

## Recommended Fix
Start the state as `""` (or null meaning not yet loaded). Load it in a `useEffect` after mount, or through `useSyncExternalStore` with a server snapshot as the page does elsewhere. Once loaded, bump `scratchpadResetKey` so the uncontrolled editor remounts with the loaded `initialValue`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Failed attendance load marks every doctor as a gap ✓ Resolved
Description
checkInCountsByUserId is built from attendanceExport.data?.rows ?? [], and loadingCounts only
covers the loading/idle statuses. When the export request fails (forbidden, unavailable,
etc.), the sheet renders AttendanceRoster with empty counts. Every enrolled doctor then appears
under "no check-ins recorded this term" with no error shown.
Code

src/components/teaching/teaching-organise.tsx[R556-558]

+            checkInCountsByUserId={checkInCountsByUserId}
+            loadingCounts={!isDemo && (attendanceExport.status === "loading" || attendanceExport.status === "idle")}
+            demoMode={isDemo}
Relevance

●●● Strong

Recent roster precedents distinguish failed reads from confirmed empty data instead of showing
misleading zero-state results.

PR-#3559
PR-#3304

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
On error, useTeachingResource settles with a failure status and data: null. The counts map
therefore stays empty, and AttendanceRoster treats a missing count as 0, which it reports as a
gap.

src/components/teaching/use-teaching-resource.ts[44-68]
src/components/teaching/teaching-organise.tsx[166-185]
src/components/teaching/attendance-roster.tsx[52-65]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
When the attendance export request fails, the Attendance gaps sheet treats every doctor as having zero check-ins.

## Fix Focus Areas
- src/components/teaching/teaching-organise.tsx[553-561]
- src/components/teaching/organise-sheets.tsx[645-690]

## Recommended Fix
Pass a `loadError` (for example `!isDemo && attendanceExport.status !== "ready" && attendanceExport.status !== "loading" && attendanceExport.status !== "idle"`) into `AttendanceGapsSheet`. When it is set, render an error message with a retry (`attendanceExport.retry`) instead of the roster.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Other tabs retain stale scratchpad text ✓ Resolved
Description
OnCallHandoverPage loads the scratchpad once and passes it to an uncontrolled editor as
initialValue, but its storage listener handles only a full clear or expiry-stamp removal. When
another tab changes or removes the scratchpad key while the shift stamp remains, the open tab
continues displaying its old text.
Code

src/components/on-call/handover/handover-page.tsx[R989-991]

+                    initialValue={scratchpadText}
+                    onChange={(text) => {
+                      setScratchpadText(text);
Relevance

●●● Strong

Recent precedent accepted synchronizing drafts when another tab changes or clears storage.

PR-#3220
PR-#3245

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Scratchpad writes and removals use their own patient-label key. The storage handler does not react
to that key, and changes to the parent's initialValue cannot update the editor's internal value
after mount.

src/components/on-call/handover/handover-page.tsx[565-595]
src/components/on-call/handover/handover-page.tsx[986-1002]
src/components/clinical-notes/note-editor.tsx[45-58]
src/lib/on-call/handover.ts[240-268]
src/lib/patient-label-storage.ts[47-48]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Scratchpad edits or deletion in another tab do not update an already-open handover editor.
## Fix Focus Areas
- src/components/on-call/handover/handover-page.tsx[565-595]
- src/components/on-call/handover/handover-page.tsx[986-1002]
## Recommended Fix
Handle storage events for the scratchpad's patient-label key and reload or clear the editor value. Make that value controlled, or remount the editor when an external update arrives.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 9 rules
✅ Skills: 8 invoked
  review
  security
  api
  access
  clinical
  privacy
  ui
  testing-review
✅ Cross-repo context — repo relationships
✅ REVIEW.md
Review mode: Auto: 🚀 Fast: Small, localized accessibility and wording changes with limited behavioral risk.

Grey Divider

Tip of the day
💡 Did you know, you can pick a display mode instead of tweaking every Display option by hand

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo Fixer

✅ Committed (5) · ☑ Fixed (5)

Grey Divider

Commits pushed directly to this PR — no separate fix PR opened.

Process — 5 fixed
  • ☑ Fixed: Supervisors get links that were never registered
  • ☑ Fixed: Shared devices retain scratchpad notes
  • ☑ Fixed: Scratchpad load causes hydration mismatch
  • ☑ Fixed: Failed attendance load marks every doctor as a gap
  • ☑ Fixed: Other tabs retain stale scratchpad text

gitar-bot[bot]
gitar-bot Bot previously approved these changes Oct 16, 2026

@gitar-bot gitar-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gitar has auto-approved this PR (configure)

@codeant-ai

codeant-ai Bot commented Oct 16, 2026

Copy link
Copy Markdown

CodeAnt Nitpicks

5 code suggestions

1. Lowercasing uppercase tokens lets non-whitelisted patient initials in clinicalSetting evade detection and be saved in magic-link records.

Security · src/lib/teaching/assessments/magic-link.ts:304


2. If shared-device mode is enabled after this debounce is queued, the callback still saves the note, so a background write occurs while autosaving is paused.

Race condition · src/components/clinical-notes/note-editor.tsx:185-187


3. This claims a publisher review in July 2026, but the record says NICE’s latest date is 5 December 2018; the catalogue will show a false review date.

Logic error · src/data/source-acquisitions.json:1392


4. This Recommendations URL is for NG225, already registered at its landing-page URL; URL-based identity creates separate catalogue entries and splits the source’s usage and metadata.

Logic error · src/data/source-acquisitions.json:1614


5. This claims a publisher review in September 2026, but the record says NICE last updated this guideline in June 2020; the catalogue will show a false review date.

Logic error · src/data/source-acquisitions.json:3305

gitar-bot[bot]
gitar-bot Bot previously approved these changes Oct 16, 2026

@gitar-bot gitar-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gitar has auto-approved this PR (configure)

Copy link
Copy Markdown
Owner Author

CodeAnt nitpicks and the breakpoint comment:

  • Items 3 and 4 (NICE NG116/CG113 reviewDate, and the NG225 URL identity): these are already on main and identical there. This PR changes only 24 TGA candidate records, all to a more conservative status. They stay as they are here and are logged for a separate source-metadata review.
  • Item 5 is the same pre-existing issue.
  • Items 1 and 2 (lowercased clinicalSetting tokens, and shared-device mode switched on mid-debounce) are minor and stay as they are in this PR.
  • Calculator remount at the 1280px breakpoint (comment 4293042549): the owner has approved a follow-up PR for this. It will be done after this PR lands, because it edits the same file.

Generated by Claude Code

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit c583f57

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016imb8DCuk85z3FWRDDyZx5
gitar-bot[bot]
gitar-bot Bot previously approved these changes Oct 16, 2026

@gitar-bot gitar-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gitar has auto-approved this PR (configure)

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit ceaaeef

The manager reads a checkable rule warning before approving, so the
approval now sends acknowledgeRuleWarnings, matching the decision sheet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016imb8DCuk85z3FWRDDyZx5
gitar-bot[bot]
gitar-bot Bot previously approved these changes Oct 16, 2026

@gitar-bot gitar-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gitar has auto-approved this PR (configure)

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 422c629

- Camera-denied copy no longer points to a code field 'below', which
  the code sheet does not have.
- Hide the test-only gap summary from screen readers, which already
  hear each gap in its roster row.
- Forms review note spells the reviewer attribution as Psych Sift.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016imb8DCuk85z3FWRDDyZx5

@gitar-bot gitar-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gitar has auto-approved this PR (configure)

@gitar-bot

gitar-bot Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor
Code Review ✅ Approved 2 closed / 2 findings

🔴 High risk · Changes renal-clearance and lithium-dose calculations plus handover privacy and assessment access; errors could mislead treatment or expose sensitive notes.

Clinical scratchpad and handover privacy changes are approved. Scratchpad no longer saves every keystroke on shared devices, and the shared-device guard now clears previously stored scratchpad content instead of leaving it behind.

✅ 2 closed
✅ Security: Scratchpad saves every keystroke, even on shared devices

📄 src/components/on-call/handover/handover-page.tsx:986-1000 📄 src/components/clinical-notes/note-editor.tsx:164 📄 src/components/clinical-notes/note-editor.tsx:179-182 📄 src/components/clinical-notes/note-editor.tsx:311-314
ClinicalNoteEditor.handleChange calls onChange(newValue) (note-editor.tsx:164) before it checks sharedDevice, and that check only skips the debounced onSave (lines 179-182). The handover page's onChange calls saveOnCallHandoverScratchpad(text) on every keystroke, which writes the text to localStorage through writePatientLabels. So on a device flagged as shared, the scratchpad is still saved straight away, while the editor tells the user "Shared device mode: background auto-save is paused. Use Save now to save intentionally." The shared-device privacy rule is bypassed, and the next person to open the handover page on that device sees the earlier shift's notes until the retention stamp runs out. To fix it, have onChange only update React state and leave persistence to onSave. Since the editor already respects shared-device mode, auto-save on normal devices and Save now on shared devices then both go through one path.

✅ Edge Case: Shared-device guard skips deletes and leaves old scratchpad stored

📄 src/components/on-call/handover/handover-page.tsx:571-574 📄 src/components/on-call/handover/handover-page.tsx:1001-1006
The new if (isSharedDevice()) return; comes before saveOnCallHandoverScratchpad, so it blocks deletes as well as writes. Suppose a scratchpad was saved before the device was marked shared. The mount effect still loads it from storage. If the clinician then clears or edits the text, the old stored copy is never removed, and it comes back on the next reload or in other tabs. That defeats the shared-device privacy intent. To fix it, when the device is shared, call removePatientLabels(HANDOVER_SCRATCHPAD_STORAGE_KEY) (or saveOnCallHandoverScratchpad("")) instead of returning silently. Optionally, skip loading the stored scratchpad when the device is shared.

Verification

Rules No rules evaluated

Functional validation Not enabled

Set up

Auto-approval Approved

For more verification details, comment gitar display:verbose.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing compact verification results.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Important

Your trial ends in 5 days — upgrade now to keep code review, CI analysis, auto-apply, custom automations, and more.

Was this helpful? React with 👍 / 👎 | Gitar

@BigSimmo
BigSimmo disabled auto-merge October 16, 2026 10:27
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 6432f13

Copy link
Copy Markdown
Owner Author

Production UI (3) failed on 6432f13 at one test, tests/ui-tools.spec.ts:3384 ("prescribing submitted search keeps results above the phone bottom dock"). The page stayed 78 px short of the bottom of the scroll after 10 s. The other 261 tests in that shard passed.

I don't believe this failure comes from this PR. The PR doesn't touch prescribing search, the medication results, the bottom search dock or scrolling. The only change since 422c629, where this same shard passed, is two lines of camera-denied wording, one sr-only attribute and a note in the forms review JSON. I'll re-run the failed job once when the run finishes. If it fails a second time, I'll treat it as real and look into it.


Generated by Claude Code

@BigSimmo
BigSimmo enabled auto-merge (squash) October 16, 2026 10:39
@BigSimmo
BigSimmo merged commit 0f441dc into main Oct 16, 2026
63 of 65 checks passed
@BigSimmo
BigSimmo deleted the fix/adversarial-remediation-20261016 branch October 16, 2026 10:45
BigSimmo pushed a commit that referenced this pull request Oct 16, 2026
Main carries #3574 (squash of this branch's earlier commits) and #3575, which
already ships a stricter sign-off pin check. This merge keeps main's pin check,
adds this branch's --execute credential guard, drops a broken bot commit
(b0adab7), and repairs two others (stray JSX after the handover page's last
line, and a setState-in-effect lint error in the note editor).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016imb8DCuk85z3FWRDDyZx5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔴 Large blast radius size:XXL This PR changes 1000+ lines, ignoring generated files 🕐 40+ Minutes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants