Open shifts: take back your own request while it waits for approval
User description
Summary
Before: once a doctor asked for an open shift, only a roster manager could clear the request. A doctor who asked by mistake, or whose plans changed, had to message the manager.
After: a request that is still waiting for approval shows "Take back my request" on the shift advert. A confirm sheet explains that the shift goes back on the list for everyone, and that the doctor can ask again while it stays open. An approved shift is already on the roster, so it can't be taken back this way. The doctor gives it away through Roster as for any other shift.
This is item 10 of the Open shifts expansion, which the owner approved on 12 Oct 2026 ("Go ahead with your recommendations", then "Go ahead with all improvements and recommendations"). It is kept apart from the UI work in #3483 because it contains a migration.
How: a new SQL function roster_open_withdraw(actor, service, openShiftId) follows the roster_needs_replace pattern. It is security invoker, executable by service_role only, and uses the same lock order as every team write. It resets a claimed row to open only when the caller is the doctor who asked. Anyone else gets roster_not_found, so nobody learns who asked for what. A row that has been approved, cancelled or expired gets roster_conflict. The app sends a new open.withdraw action through the team route to that function. roster_command is unchanged, and there are no table or data changes.
Verification
-
npx tsc --noEmit: clean. -
npm run test:focusedon the changed source:Test Files 173 passed (173),Tests 2419 passed (2419). - New
tests/open-shifts-withdraw.dom.test.tsxcovers the confirm, the success notice, the conflict message, and the button staying hidden on approved shifts and offline (3 tests passed). -
tests/roster-team-api.test.tscovers routing toroster_open_withdrawand refusing bad payloads (22 tests passed). -
tests/sql/roster-behaviour.sqlsection 8 now covers someone else's take-back (not found), a manager's take-back (not found), the doctor's own take-back (shift reopened and claimer cleared), a claim again, and a take-back after approval (conflict). This runs in CI's Migration replay, not locally. -
npm run check:migration-role: passed.check-migration-immutability --writesealed the new migration. - Drift manifest: no Docker daemon was available here. I computed the new function's
def_hashwith the same expression asschema_drift_snapshoton a local Postgres 16 scratch database. The method reproduced the committedroster_needs_replacehash exactly (c9251a59…).schema_sha256was recomputed fromsupabase/schema.sql. CI's freshness step is the authoritative check.
Risk and rollout
- Risk: low. It adds one function that writes only a single row, and only one the caller themselves claimed. Every refusal path leaves the row untouched.
- Rollback: revert the app change so the button and action disappear. The function can stay unused, or a follow-up migration can drop it.
- Provider or production effects: merging this PR applies the migration to the live database within seconds. Merge only inside an approved window, by the owner. Keep this PR draft until then.
Clinical Governance Preflight
- Source-backed claims still require linked source verification before clinical use: no clinical content changed.
- No patient-identifiable document workflow was introduced or expanded without explicit governance approval: the change touches roster requests only.
- Supabase target remains
PsychSift Production(sjrfecxgysukkwxsowpy) - Service-role keys and private document access remain server-only: the function is granted to service_role only and called from the server route.
- Demo/synthetic content remains clearly separated from real clinical sources: example mode hides the button, and the demo command returns an example result.
- Source metadata, review status, and outdated/unknown-source behavior remain conservative: not touched.
- Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed: no decision-support behaviour changed.
Notes
- No alert is sent on a take-back. The manager's waiting item disappears because it is read from live state.
- If #3483 merges first, a small merge in
open-shifts-advert-page.tsxis expected.
🤖 Generated with Claude Code
https://claude.ai/code/session_01CZwGiwt7eXkD4fwyFM83gY
Generated by Claude Code
Summary by cubic
Lets a doctor take back their own open-shift request while it waits for approval, so a mistaken request or changed plans no longer require messaging a roster manager to clear it.
A waiting request now shows "Take back my request" on the shift advert, with a confirm sheet explaining the shift goes back on the list for everyone. The new roster_open_withdraw function reopens only rows the caller themselves claimed; anyone else gets roster_not_found, and approved, cancelled, expired, or started requests get roster_conflict. The started check reads clock_timestamp() after the locks, so a take-back that waited on a lock is still refused once the start time has passed. If a request is decided before the take-back lands, the page forgets this visit's copy of it and re-reads it, pointing the doctor to My shifts; a take-back only overrides the read it was made against, so a request claimed again from another tab or device shows as waiting, and a "Waiting for approval" state from this visit lapses once a newer read no longer shows the shift as this doctor's. After a take-back, keyboard focus moves to the result, since the button that opened the sheet is gone. Approved shifts are already on the roster, so they're given away through Roster as before.
- Adds a SQL migration that applies to the live database automatically on merge, plus a Playwright spec that drives the stale take-back race and matches the notice by its text so Next.js's route announcer doesn't trip strict mode.
- No table, data, or
roster_commandchanges. - Once a take-back finds a request already decided, the same sheet can't be retried against the old listing.
Summary by Gitar
- Safeguards:
- Refuses a take-back once the shift has started, since a started shift can no longer be claimed
- Decided requests:
- A request decided before the take-back lands is forgotten, re-read, and directs the doctor to My shifts
- Accessibility:
- Keyboard focus moves once to the take-back result instead of on every re-render
This will update automatically on new commits.
CodeAnt-AI Description
Let doctors take back open-shift requests awaiting approval
What Changed
- Doctors can confirm taking back their own request while it awaits approval; the shift returns to the list for others, and they can request it again while it remains open.
- The take-back option is unavailable after approval, once the shift has started, or while offline.
- If the request is decided before the take-back goes through, the page refreshes and explains that it can no longer be taken back.
Impact
✅ Doctors can correct mistaken shift requests
✅ Open shifts return to the list when requests are withdrawn
✅ Clearer outcomes when a request is already decided
💡 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.