Repository navigation
Show slots inside the sign-up lockout as closed on the public page - #543
Conversation
The service refused a sign-up for a slot starting within the signup's lockoutHoursBeforeSlot, but /s/[slug] still offered it, so every Confirm was refused and reloading showed the same page. The lockout test moves to slotInLockout in src/lib/signup-closing.ts; whyNotTakingPlaces and toSignupViewSlots both call it, so the page shows such a slot as Closed. Fixes #513. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZTvpEXM4vaLSndhJ7emU7
Tophat: passedI ran aa21e9e locally with
One thing I noticed that this PR didn't cause: the organizer preview showed Juice boxes as "2 left" after the sign-up, while the public page showed "1 left". The preview doesn't seem to count commitments on For the owner (UX): when a signup has a sign-up lockout (set through the API or an AI assistant, since no screen sets it), a slot that starts within the lockout now shows Closed on the public page and in the preview. Before, it offered Sign up and then refused every attempt. Slots outside the window are unchanged. Compare Screenshots are in https://claude.ai/code/session_01P2JxQoyAuwbpRHZkh69Fun. Generated by Claude Code |
richshaw
left a comment
There was a problem hiding this comment.
Review: passed (round 1)
The PR moves the lockout rule out of whyNotTakingPlaces and into slotInLockout in src/lib/signup-closing.ts. It keeps the same slot_at anchor and the same "off when unset, 0 or no date" behaviour. The service calls the helper, and so does toSignupViewSlots, so a slot inside the lockout comes out closed from the one mapper that feeds the live page, the organizer preview and GET /api/public/signups/[slug]. Every item in the issue's outline and "Done when" is done. The new signup-view.test.tsx case fails without the fix. The refusal's detail: 'slot_lockout' and message haven't changed. /s/[slug] is dynamic (it reads cookies()), so the Date.now() default runs per request, not once at build. A returning participant whose slot is now closed still gets the "No one new can sign up, but your spot is still yours" line and their edit link.
Blocking (0): none.
Optional (1):
CHANGELOG.md:57: the entry gives the old refusal as "Sign-ups for this slot have closed. Reload the page to see what's still open." That string isn't in the code. The dialog says "No one new can sign up for this slot now." (src/app/s/[slug]/closed-message.ts:18), as the tophat saw. Worth correcting if the PR gets another push.
Earlier threads: none. Copilot couldn't review because its quota was used up.
Checks: CI is green on aa21e9e (lint-type-test, e2e, CodeQL). The tophat passed on this same SHA, so no runtime code has changed since. I read the diff, every caller of toSignupViewSlots and the remaining uses of lockoutHoursBeforeSlot. I didn't need to run anything locally to prove a finding.
Why owner: this is UX (pipeline:ux). When a signup has a sign-up lockout, a slot inside it now shows Closed on the public page and in the preview, and the public API gives it "status":"closed", which self-hosters reading that API will notice. Compare the tophat's 543-before-public-desktop / 543-before-refused-desktop with 543-after-public-desktop and 543-after-public-phone.
https://claude.ai/code/session_01FwwHciGGPzwcq6fj32dXNe
Generated by Claude Code
|
This need fixing "CHANGELOG.md:57: the entry gives the old refusal as "Sign-ups for this slot have closed. Reload the page to see what's still open." That string isn't in the code. The dialog says "No one new can sign up for this slot now." (src/app/s/[slug]/closed-message.ts:18), as the tophat saw. Worth correcting if the PR gets another push." |
…out-public-page # Conflicts: # CHANGELOG.md
…GELOG entry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115MCPHsyBah5fBHmYV2aVR
|
Confirmed and fixed in
Checked on https://claude.ai/code/session_0115MCPHsyBah5fBHmYV2aVR Generated by Claude Code |
…out-public-page # Conflicts: # CHANGELOG.md
|
Merged
https://claude.ai/code/session_014u7aKHNDyvxAhDHCQui3eJ Generated by Claude Code |
richshaw
left a comment
There was a problem hiding this comment.
Review: passed (round 2)
Since round 1 the PR has one commit of its own, 60aa9bb. It changes the CHANGELOG entry to quote the refusal the dialog really shows ("No one new can sign up for this slot now. Reload the page to see what’s still open.", CLOSED.join in src/app/s/[slug]/closed-message.ts), which is what the owner asked for. The rest is two merges of main whose only conflicts were in CHANGELOG.md. The runtime code is the same as in round 1. slotInLockout in src/lib/signup-closing.ts holds the lockout rule, with the same slot_at anchor and off when the lockout is unset, 0 or the slot has no date. whyNotTakingPlaces and toSignupViewSlots both call it, and toSignupViewSlots is the one mapper behind the live page, the organizer preview and GET /api/public/signups/[slug]. I read the full diff again against current main. toSignupViewSlots still has only those two callers, and neither caches across requests, so the Date.now() default is evaluated on each request.
Blocking (0): none.
Optional (0): none.
Earlier threads: none open. The owner's CHANGELOG comment is addressed in 60aa9bb.
Checks: CI is green on 1ef58e3 (lint-type-test, e2e, CodeQL). The tophat passed on aa21e9e. The shared check for runtime changes since then prints 0. I didn't need to run anything locally to prove a finding.
Why owner: pipeline:ux. When a signup has a sign-up lockout, a slot inside it now shows Closed on the public page and in the preview, and the public API gives it "status":"closed". Compare the tophat's 543-before-public-desktop / 543-before-refused-desktop with 543-after-public-desktop and 543-after-public-phone.
https://claude.ai/code/session_01JzPKsKUTWrw9xQT1XzxEK9
Generated by Claude Code
|
Approved by the owner at https://claude.ai/code/session_01UzuZgVYSekfFbYc7jjF3i7 Generated by Claude Code |
Fixes #513.
The problem
When a signup sets
lockoutHoursBeforeSlot, the service refuses any sign-up for a slot that starts within that many hours. The public page/s/[slug]didn't apply the same rule, so it still showed the slot with a working Sign up button and an "N left" count. Every Confirm was refused with "Sign-ups for this slot have closed. Reload the page to see what's still open.", and reloading showed the same page again. No screen sets the lockout, butPATCH /api/signups/[id]and the MCPupdate_signuptool do.The fix
slotInLockout(settings, slotAt, now)is new insrc/lib/signup-closing.ts, next tosignupShutReason. It holds the lockout rule, unchanged: counted back fromslot_at, so a date-only slot still uses its noon-UTC anchor, and it's off when the lockout is unset or 0 or the slot has no date.whyNotTakingPlacesinsrc/services/commitments.tsnow calls it instead of computing the lockout inline. The refusal, itsdetail: 'slot_lockout'and its message are unchanged.toSignupViewSlots(src/app/s/[slug]/public-signup.ts) gives a slot inside the lockout the statusclosed. It takes an optionalnowfor tests. The row then renders like any other closed slot: "Closed", with no count and no button. I picked this over adding alockedOutflag because the row already has aclosedpath and the page needs nothing new.GET /api/public/signups/[slug]use the same mapper, they also show such a slot asclosed. That's what a participant would actually find.Tests
signup-view.test.tsxbuilds a signup withlockoutHoursBeforeSlot: 24, with one slot 2 hours away and one 72 hours away, throughtoSignupViewSlots. The near slot shows Closed, with no Sign up button and no count, and the far slot still offers an enabled Sign up. Without the fix, this test fails because the near slot's button is still there. A second test checks that no slot closes when the lockout isn't set.signup-closing.test.tscoversslotInLockout: inside the window, past the slot, outside the window, and no lockout, a 0 or null lockout, or no date.pnpm lint,pnpm typecheck,pnpm knip,pnpm test(1292 tests) andpnpm test:db(492 tests, including the existing lockout refusal incommitments.db.test.ts).affected-articles.mjsreports that no help article is touched.Generated by Claude Code