Repository navigation
Conversation
Closes #457. An open signup's header put a one-click Close signup where Publish had just been, and closing can't be undone. - Desktop: Preview and a ⋯ (More actions) button, outlined like Preview. Its panel holds Close signup, which swaps the panel to a question: where the signup stands ("Nobody has signed up yet.", "18 of 21 spots are still open.", "3 spots are taken."), then "People can't sign up after this, and you can't reopen it." Not yet has focus and hands it back to ⋯; the red Close signup submits through StatusActionButtonForm, so closeAction and statusRefusal are unchanged. A refusal shows in the panel, which stays open while the close is in flight. - Phone: the More actions sheet asks the same question, with 44px+ buttons, and closes once the signup has closed instead of staying open and empty. - After closing, one sr-only role="status" region says "Closed. People can't sign up now." and focus moves to the signup's title, since the ⋯ that had it is gone. - New src/components/ui/Popover.tsx: a small non-modal anchored dialog (Escape, outside mousedown and focus leaving close it; focus returns to the trigger on Escape or a close from inside). useOutsideClose moves to src/hooks and gains an opt-in focus-leaving close; the builder's pickers keep their behaviour. - The layout loads loadCommittedBySlotForOrganizer (cached; the Build tab already calls it) and passes the header spotCounts(), a pure helper in src/lib/spot-counts.ts beside closeSignupQuestion(). - The Settings e2e test scopes its "Saved" status to the form, since the header now has a live region of its own. Budgets: every signup tab's first-load JS grows by 1.3-1.4 kB gzip (the ⋯ menu, the popover, the question and the live region, all in the shared layout chunk), so the five [id] budgets go up by 1 kB each: build 151.1 -> 152.5 (152 -> 153), responses 124.5 -> 125.9 (125 -> 126), settings 124.1 -> 125.4 (125 -> 126), edit and [id] 123.1 -> 124.4 (124 -> 125). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
…is page's close Review fixes for the Close signup change. - A closed signup shows the existing Banner on every tab: "Signup closed" / "People can’t sign up now. Everyone already signed up keeps their spot." It is not a live region (Banner gains `live`, default true), so a screen reader hears the header's "Closed. People can’t sign up now." once rather than both. - Opening ⋯ (or the phone sheet) on an open signup calls router.refresh(), so the question counts sign-ups made since the page loaded. Build's client state keeps its edits through the refresh. - spotCounts gains `open`: each open slot's free spots, never below none, null when an open slot has no limit. A closed slot's free spots no longer count as open, and an over-full slot no longer cancels free spots elsewhere. The question goes by it: "4 of 16 spots are still open.", "No spots are still open." when none are open but not every spot is taken. `taken` and `total` are unchanged. - The announcement and the focus move happen only when this page's Close signup closed the signup: useAnnouncedClose wraps closeAction and marks it on success, and HeaderAnnouncer, now holding both headers, reads the mark on the next status change. A signup closed by another tab or an AI assistant shows the banner without either. - The desktop menu runs Close signup in its own scope, and the question shows a refusal only for a Close signup pressed in that asking, so an earlier refusal doesn't come back. - Not yet and the red Close signup are the same height (both bordered). - The phone ⋯ has aria-haspopup="dialog" and aria-expanded. - The layout skips the spots-taken read for drafts. - Tests: the held-open guard (Escape and an outside click wait while the close is in flight), a clean second asking, the announcer's gating, open spots with a closed or over-full slot, and e2e checks for the banner, fresh counts and a signup closed elsewhere. Budgets: /app/signups/[id]/responses goes from 125.9 to 126.1 kB, over its 126 kB by 0.1 kB, so it rises to 127. The banner is rendered on the server and adds no JavaScript; the 0.2 kB is router.refresh() in both headers, the close-from-here context and the per-asking refusal state. Build is 152.9 of 153 kB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
router.refresh() on every ⋯ open rendered the whole route again, which re-ran the page's reads and its after() telemetry, so each opening wrote another signup.viewed (Build) or signup.editor_opened row. - closeQuestionAction(signupId) in the signup actions reads the slots and spots taken through getSignupForOrganizer (policy guard included) and returns the question, or a refusal through refusalAsResult. It revalidates nothing. - useCloseQuestion asks it when ⋯ opens, on a computer and a phone; the page's question shows until the answer comes, and stays if none does. - Wording: "No spots are open." when no spot is open, and "Every spot is taken." only when every slot is open and full (SpotCounts gains allSlotsOpen). An over-full slot beside a closed slot with free spots no longer reads as every spot taken. - Tests: db tests for the action (fresh counts, a refusal comes back as one, nothing revalidated), the menu asking again as it opens, and the new wording. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
A pure builder for the message the share panel offers to send with the link: the title, then the dates from the reminder anchor (one day, or the earliest to the latest), the number of slots and, when every slot has a limit, the spots, then "Sign up here: <link>". Each part is left out when it doesn't apply, and the middle line too when nothing is left. `withLink` puts the link back on its own line if an edit took it out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
lean-qr 2.7.4 (MIT, no dependencies), imported from `lean-qr/nano` only when the code mounts, so it stays out of every page's first load. The preview is an inline SVG; the PNG for a flyer is drawn on a canvas only when Download is chosen, and nothing is sent anywhere. If the encoder can't load, or the browser can't draw the PNG, the QR code is left out rather than breaking what's around it. Ported from the share step in #531, without its telemetry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
`useClipboardCopy` copies, says so for a moment, and resolves to whether the clipboard took it. The public link chip uses it, with the same 1.4 s it had, so there is one copy implementation for the chip and the share panel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
What the header will open after Publish and from Share: the link with Copy link, an editable message with Copy message and Email it (a mailto: link), and a QR code. On a phone with a share menu, Share… comes first and stands in for Copy message and Email it. The first time it says Published and "Now send people the link"; later "Share the link". The owner keeps the message, so an edit survives the panel closing. `useMediaQuery` will pick the popover or the sheet once the header uses it; it is null until the window can be asked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
PR 1's spotCounts already adds up a signup's capacities, null when a slot has no limit, so the message uses it rather than a second loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Part of #505: recording copies and shares waits for #525. - publishAction returns { published: true } after revalidating the layout instead of redirecting to /build?published=1, the way closeAction stays put. Publish from Responses or Settings stays on that tab, and a reload or a shared address never reopens the panel. - One StatusActionScope runs Publish for both headers, so it outlives the phone's More actions sheet, which closes as the answer lands; Close signup keeps PR 1's scopes in the ⋯ panel and the sheet. ShareScope, inside it, opens the panel in its first-time form when Publish answers that it published, and keeps the message: the server's (built in the layout by shareMessage) until the organizer edits it, then the edit while the page is open. - An open signup's header has Share (Share2): on a computer in the blue spot right of Preview, before ⋯, opening PR 1's Popover (420px, aria-labelledby its heading, aria-haspopup and aria-expanded on the button); on a phone a full-width button under the public link, opening a BottomSheet titled by the heading, with Published as its description the first time. useMediaQuery picks one after mount, so the portalled sheet never opens on a computer. - The panel's body is next/dynamic with ssr: false and renders only while open; the QR encoder loads inside it. - On a phone the More actions sheet closes once the status changes (it remembers the status it was opened on), so after Publish the share sheet takes its place rather than Close signup showing. - Escape or the ✕ puts focus back on Share. The Popover no longer takes focus from a child that put it inside as it mounted, and BottomSheet passes onCloseAutoFocus through. - The header says "Published. Anyone with the link can sign up now." only when this page's Publish answered that it published (useAnnouncePublished, which ShareScope calls), as PR 1 does for a close made here. - PublishedBanner and the Banner's published kind are gone; useDismissibleQueryFlag stays for AiDraftBanner. - The organizer e2e and the run skill's sample script wait for the panel instead of "Signup published". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
"Create and publish your first signup" now says that Publish opens "Now send people the link", and what it offers: Copy link, a message built from the signup that can be changed, Copy message and Email it, Share… on most phones in place of those two, and a QR code to download. Share at the top opens it again. The UI list gains those names in place of "Signup published"; Share…, which the walkthrough's browser can't show, is checked by a unit test. The walkthrough publishes, checks the panel (focus on Copy link, the message's dates, slots and spots, the copies, the mailto: link, the QR code's download), closes it with Escape before the header picture, and reopens it from Share. The phone test checks that the More actions sheet gives way to the share sheet. With HELP_SCREENSHOTS=1 it also takes a picture of the panel, for the article once it's taken. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Shared again after people have signed up, the message said every spot the signup ever had. It now goes by spotCounts' open spots: "21 spots" before anyone signs up, "18 spots left" after, and nothing about spots once none are left or an open slot has no limit. A closed slot's spots don't count. The layout passes the spots taken it already reads (none for a draft), and the help page says "how many spots are left". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
…hen a copy is refused Review fixes. - In the App Router, next/dynamic is React.lazy: a panel chunk that fails to load (a weak signal, a tab open across a deploy) threw to an error boundary there isn't, so the page broke right after Publish. loadSharePanel now resolves to ShareUnavailable instead: the link, Copy link, and "Couldn’t load the other ways to share. Reload to try again." - The heading, "Published" and the ✕ are ShareButton's on a computer too, as the phone's sheet already had them, so the popover is named and can be closed while the body loads. SharePanel loses onClose and headingId. - Pressing Publish starts loading the panel (ShareScope now runs Publish's scope and wraps the action), so it's usually in by the time the answer lands. - One live region says the last copy that worked, so a second copy is announced too. - A copy the clipboard refuses (plain HTTP, a denied permission) says "Couldn’t copy. Select the link and copy it." (or the message) and selects it; on a phone the link's box appears for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Review fix. A browser that couldn't draw the PNG kept a Download that did nothing, and the row showed an inert Download while the encoder loaded. Now the QR code shows once the encoder is in and the browser has a canvas to draw on; if the canvas then won't give the picture back, the click is cancelled and the QR code goes away. The panel goes on without it in each case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Review nits. useMediaQuery made a MediaQueryList on every snapshot; it now makes one per query. The public link chip copies straight from its button, without a wrapper that only awaited. Prettier on the files this change touched, where they were short (SignupHeader keeps PR 1's flat nesting, and the help article its line breaks, to keep the diff to what changed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Review fixes. The walkthrough read the clipboard straight after a click, before the copy had landed; it now polls, also for the public link chip's copy that follows the panel's. The CHANGELOG entry says where focus goes after an outside click (a computer leaves it where it landed), that only this page's Publish is announced, and what the review round added: spots left, refused copies, the QR code only where it can be drawn, and the fallback when the panel can't load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
The Share button's code counts against every signup tab's first load, and next/dynamic and the fallback had put about 1.2 kB gzip into it. - React's lazy and Suspense in place of next/dynamic, which is React's lazy plus a runtime of its own (LoadableComponent, BailoutToCSR, PreloadChunks): the panel renders only once opened, never on the server, so ssr: false bought nothing. loadSharePanel still never rejects, so a failed load still shows the fallback. - ShareUnavailable is the public link chip, with its copy button, and "Couldn’t load the other ways to share. Reload to try again.": the chip is in the first load already, where a link box, a Copy link button and its refusal handling of the fallback's own were not. The panel's link box moves back into the panel, its only user. - The phone header test waits, in its act, for the panel that loads as the sheet mounts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Every /app/signups/[id] tab shares the header's JavaScript, and Share adds 1.4-1.5 kB gzip to it (measured against PR 1 at 5da0166): build 153.0 -> 154.4 kB (153 -> 155) responses 126.1 -> 127.6 kB (127 -> 128) settings 125.7 -> 127.1 kB (126 -> 128) edit 124.7 -> 126.1 kB (125 -> 127) [id] 124.7 -> 126.1 kB (125 -> 127) What it is: ShareButton with the panel's heading, Published mark and ✕ and its Popover and BottomSheet wiring, plus the Share2 icon (about 1.3 kB alone, before shared compression); ShareScope, which runs Publish's scope and keeps the panel's state, with useMediaQuery (0.7); and the loader with its fallback, the public link chip and one line (0.3). The panel itself (3.8 kB gzip) and the QR encoder (2.0 kB) load only when the panel first opens or Publish is pressed. React's lazy in place of next/dynamic, and the chip as the fallback, already took the first-load cost down from 2.6 kB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
The walkthrough's picture of the panel, taken with the stand-in address, goes in the article under "Now send people the link". The walkthrough allows the suffix a slug gets when an earlier run made a signup with the same title, in the message, the clipboard and the QR code's file name. published.png came out the same, so it stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Tophat fixes. - On a phone, the focused Copy link shrank from the full width to half 50-200 ms after the sheet opened, when the QR code arrived beside it. The QR code now keeps its place while the encoder loads, as an inert stand-in hidden from screen readers, and goes away only if the encoder can't load or the browser can't draw the PNG. On a computer its row is there from the start in the same way. - loadSharePanel starts loading the encoder beside the panel's own code (and so as Publish is pressed), rather than once the panel asks. - Download is as tall as Copy message and Email it, with padding, so its focus ring no longer hugs the word. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Tophat fix. The refusal showed below Email it, about 280px under Copy link on a phone. Now a refused link copy shows under the link's row (on a phone, beside the link box that appears, above Copy link), and a refused message copy under Copy message and Email it. Each place is a live region from the start, so what it says is announced; the link's also says, unseen, each copy that worked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Tophat fix: two pieces the review round added had no test. Publish signup's refusal shows for a press in this opening of the sheet and is gone when it opens again; a signup that turns open without this page's Publish opens no panel and announces nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
…walkthrough CI's e2e failed the publish walkthrough twice in a row: the share panel's message read "Sat, Apr 6 · 2 slots · 4 spots" where the steps had moved the copy to Apr 13. The walkthrough changes the copy's date and goes straight on (Done, delete the empty slot, the Fields dialog, Preview, Publish). A cell saves 800 ms after the last keystroke, and "Saved" can be the delete's; on a slow runner the date's PATCH hadn't reached the database when Publish's re-render built the message, and nothing re-renders the layout after a Build save, so the panel kept the duplicate's old date. Delaying slot PATCHes by 2.5 s locally reproduces the failure exactly; with this change the walkthrough passes under that delay. The walkthrough now waits for that PATCH to land, as an organizer sees Saved, before going on. The race itself, Publish while a Build save is pending or in flight, is fixed in the app by the publish check's flushPendingSaves(), in a later PR of this stack. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
spotsTakenLine, beside closeSignupQuestion: "0 of 21 spots taken" when every slot has a limit, "3 spots taken" (and "1 spot taken") when one has none or there are no slots to count against, and "0 of 1 spot taken" for a total of one. A closed signup's says so first, "Closed · 3 of 21 spots taken", and an archived one's "Archived · …": its count won't change now. It goes by spotCounts' taken and total, so it counts confirmed and tentative sign-ups as the public page does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
…ic link Closes #499. Publish was a small text-xs button on a computer and, on a phone, an item inside the More actions sheet, and a draft showed a public link whose page only said it wasn't ready yet. - Computer: Preview and Publish and share (Send icon, "Publishing…" while it runs) at the header's normal size, 36px tall, 13px; Share and ⋯ match on an open signup. A draft has no ⋯. Closed and archived show Preview only. - Phone: a bar fixed to the bottom of the screen while the signup is a draft or open, over the page and under Radix's z-50 sheets, with a top border and the home indicator's inset. A draft's bar is Publish and share, 48px tall, with a refusal just above it; an open signup's is the count and Share, which opens PR 2's share sheet and gets focus back when it closes. PR 2's full-width Share under the link moves into it. Closed and archived have no bar. - The page leaves room for the bar on phones only, and only while it shows (PHONE_BAR_ROOM in the layout: the bar's 73px and the home indicator's inset), not in the Build tab as well. In the browser at 390x844 the Build tab's grey runs to the bar, the last slot, Add a slot and an open slot editor stay clear of it, and so do the ends of Responses and Settings. - The More actions sheet loses Publish signup. It holds Preview (a new tab, as on a computer; choosing it closes the sheet) for a draft, and Preview then Close signup for an open signup; a closed or archived signup still has no ⋯. - Under the title, a draft says "Only you can see this draft until you publish it." in place of the public link. Open and closed signups show the link and, beside it on a computer, spotsTakenLine from the layout's counts; on a phone an open signup's count is in the bar and a closed one's under the link. - Publish behaves as in PR 2: no navigation, the share panel opens once and only for this page's Publish, and the same spot then says Share. - The header's button classes live in header-buttons.ts, so Preview, Publish and share, Share and ⋯ stay the same size. - What never changes on the page (the draft line, the phone's link and closed count, the sheet's Preview with its icon) is rendered on the server in HeaderParts.tsx and handed to the phone's header as nodes, which keeps about 0.2 kB out of every signup tab's first load. - Tests: both headers per status, the bar per status, the ⋯ sheet's items per status, a refusal above the bar's button, and Publish from the bar handing focus to its Share. PR 2's test of Publish signup's refusal in the sheet goes with the item. The organizer e2e and the run skill's sample script press Publish and share. - CHANGELOG: PR 2's line says Share is in the bar on a phone, since both ship together. Budgets: every /app/signups/[id] tab grows by 0.2 kB gzip (PhoneBar, the bar's Share and the phone header's props), all within budget: build 154.4 -> 154.6 (155), responses 127.6 -> 127.8 (128), settings 127.1 -> 127.3 (128), edit and [id] 126.2 -> 126.4 (127). Nothing stored or sent changes, so the legal pages stay as they are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
"Create and publish your first signup" follows the new header.
- Create: the signup is a draft, and under its title it says "Only you
can see this draft until you publish it."; there's no link to send
yet. The paragraph saying a draft's link only says it isn't ready
goes, and so do the walkthrough steps that opened it.
- Preview: at the top on a computer, and on a phone under the three
dots, which phones didn't have before.
- Publish: Publish and share, at the top of the page or at the bottom
of the screen on a phone. publishOnPhone ("Publish signup") goes from
the UI list.
- Share again later: Share is at the top, or at the bottom on a phone,
and how many spots are taken is next to the link (beside Share on a
phone). The header picture's crop takes in the count, and
published.png is re-taken with it (605x110 in the article). The
other pictures came out the same apart from text rendering, so they
stay.
- The phone walkthrough opens Preview from the three dots, checks that
Publish and share fills the bar at the bottom, publishes from it, and
finds focus back on the bar's Share beside "0 of 1 spot taken".
- docs/writing-help.md's example of a phone showing a button somewhere
else is the new one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Review fixes. ShareScope and StatusActionScope said Publish's scope had to outlive the phone's More actions sheet, "which closes as Publish lands"; Publish is in the bar now, and the scope serves the computer's header and the phone's bar. Nothing else existed only for that: the phone header still tracks the status its sheet was opened on, because Close signup in the sheet changes it. The ⋯ shows when hasPhoneBar says the bar does (a draft or an open signup), instead of repeating the rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Review fix. With nobody signed up, the tab said "Share the public link to start collecting." whatever the status, but a draft shows no public link now, and a closed or archived signup takes no more sign-ups. noResponsesLine, beside summarizeResponses and tested with it: - draft: "No signups yet. People can sign up once you publish and share it." - open: "No signups yet. Share the public link to start collecting.", as before. - closed or archived: "No signups. People can’t sign up now." No help page names it, and the responses e2e reads only "No signups yet." on an open signup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Review fixes. - A refusal makes the bar taller (141px with a two-line one, against the 73px the page left), and it stays on every tab until the next press, so the bottom of Settings' Delete signup could end up under it. PhoneBar now measures itself with a ResizeObserver and puts its height on the page as --phone-bar-h, from a ref callback whose cleanup removes it with the bar. PHONE_BAR_ROOM is that height, or 73px and the home indicator's inset until it's measured. - Tab could scroll a focused control to the bottom edge, behind the fixed bar (WCAG 2.2, 2.4.11 Focus Not Obscured). html now has scroll-padding-bottom: var(--phone-bar-h, 0px), so a control brought into view stops above the bar. On a computer the bar is hidden, so its height, and so the padding, is 0; no :has() needed. - Test: the bar reports its height to the page and clears it once a closed signup has no bar. Checked at 390x844 in the browser: with "This signup changed since the page loaded. Reload to see the latest." the bar is 141px and the room and scroll padding follow it; the ends of Build and Settings, Delete signup included, stay clear. Tabbing through Responses (8 sign-ups), Build, and Settings with the refusal showing leaves no focused control behind the bar; with the padding switched off, 6 of Responses' Remove buttons and Build's Add a slot were behind it. Budgets: the signup tabs grow by another 0.13 kB gzip, within budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Review fix. Two Unreleased entries still described a step on the way: #457's ended "A draft still shows Preview and **Publish**.", now **Publish and share**; #390's put Publish's refusal "on a phone under the button in the More actions sheet", now just above Publish and share in the bar, with Close signup's under its buttons in the ⋯ panel or the More actions sheet. #499's line adds the bar's measured room, the scroll padding that keeps focus above the bar, and the Responses tab's new wording with nobody signed up. The other mentions of Publish, More actions and the public link in Unreleased are still true. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
checkBeforePublish reads a draft's fields and slots and returns the
first problem the code can prove, blocking ones first: no slots, no
fields, or every slot empty (only spaces count as empty) block; a slot
value still holding TBD, TBC or TBA as a word, in any case, is a
warning. The warning carries the slots, each word they hold, once and
in order, the field it's in on the first slot, and that slot named by
its other values as the page prints them ("Sat, Nov 14 · 1:00 PM").
It reads only a field's ref, label and type and a slot's id and
values, never the title or description.
publishCheckCopy gives the words the panel shows: "Nothing to sign up
for yet" with Fill it in (or Add a slot; none without fields) and Not
now, or "Before people see it": "<slot> still says “TBD”." or "3 slots
still say “TBD” or “TBA”." and that a published signup can't go back
to being a draft, with Name it first and Publish anyway.
publishCheckTarget says where the fix goes: the slot to open (or a
new one) and the field to focus. PublishCheckAnswer is what Publish
answers with: the check, its copy and its target.
The placeholder regex is the one from the closed draft-notices
branch, near-misses included ("TBDX", "Tbacco", "ÉTBA").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Only CHANGELOG conflicted: it takes #608's corrected entry for the button and keeps this branch's help page entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
|
UX walkthrough on In today's UX walkthrough, a tester followed this page on a computer and then a phone. Their signup had one slot with room and one that was full. They closed it and reopened it using the article alone. Every step's wording matched the screen. The facts also held when we checked them:
The run was on What people see is too small to read on a phoneAt 390px wide, Smaller
Generated by Claude Code |
richshaw
left a comment
There was a problem hiding this comment.
Review: passed (round 3)
Round 2 passed cef5b86. The only change since is e604ee5, which merges #608's c7c9100. That commit corrects a CHANGELOG line and some comments, and was reviewed on #608 (round 6). Nothing in this PR's own files changed.
- The merge brings in exactly c7c9100.
git diff cef5b86 e604ee5matchesgit diff aeb4241 c7c9100line for line, apart from context:CHANGELOG.md, and doc comments inactions.tsandReopenButton.tsx. - This PR's own diff is unchanged.
git diff c7c9100 e604ee5matchesgit diff aeb4241 cef5b86line for line. The only difference is a context line, the web-app line this merge took from #608. That's 10 files, +487/−1, andgit diff 3ba5494 e604ee5touches none of them: notsrc/help,public/help,tests/e2e/helporseo.test.ts. - The CHANGELOG conflict was resolved as described. e604ee5 has #608's corrected web-app line ("A refusal shows on its own line under the header on a computer, …") once, at line 11. This branch's help-page line is at line 12, also once. There are no conflict markers, and the combined diff shows nothing else changed.
- No runtime code changed since 3ba5494. The shared check prints
0, and still does with #608's head excluded too. The only non-merge commits in that range are #608's aeb4241 (e2e only) and c7c9100 (comments and CHANGELOG). This PR has no commits of its own since 3ba5494.
The article doesn't describe refusals, so #608's corrected wording leaves it as it was. Round 2's sentence-by-sentence check against the code still holds.
Blocking (0): none.
Optional (1): Still open from round 1: shot() is copied in all three help walkthroughs under tests/e2e/help/ and could move to tests/e2e/helpers/.
Earlier threads: none. Copilot couldn't review (quota used up).
Checks:
- CI on e604ee5 is green:
lint-type-test,e2e, CodeQL and both Analyze jobs. - On e604ee5 I ran the help and sitemap unit tests (
articles.test.tsx,seo.test.ts): 54 passed. - The tophat passed on 3ba5494 (comment), and no runtime code has changed since.
Why this waits for you: it's UX: a new public help page, and a new description on the /help index.
- Look at
/help/close-or-reopen-a-signupon a phone and on a computer (the tophat's610r-phone-article). - Look at the three pictures in
public/help/close-or-reopen-a-signup/. - Merge it after #608.
https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
Generated by Claude Code
On the owner's call, the grey box no longer shows on every visit to a closed signup. It shows right after Close signup on this page has closed the signup, on every tab, until its ✕ or the next page load. A signup that's closed when the page loads, or is closed some other way, shows none: the header says it's closed every time, with its status, "Closed ·" by the link, and Reopen signup. HeaderAnnouncer, which already knew when this page's own change landed, now shares it (`useChangedHere`), and ClosedNotice reads it. The ✕ gives focus to the title, through the same `focusTitle` the announcer uses. The box and its ✕ now load in the browser on every signup tab, which takes four of them 0.1–0.2 kB over: `/app/signups/[id]`, `/edit` and `/build` rise by 1 kB to 130, 130 and 160, and `/settings` to 131. The owner has said budgets can rise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
Only CHANGELOG conflicted: it takes #608's entry for the button and keeps this branch's help page entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
#608 now shows the grey Signup closed box only right after you close the signup, not on every visit. "While it's closed" says the signup is marked closed at the top of the page, and that right after you close it a grey box says Signup closed; "Reopen your signup" no longer says the box goes away. The walkthrough checks the closed status on every tab and, coming back later, that the box has gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
Only CHANGELOG conflicted: it keeps this branch's reopen and web-app entries and its edit of #571's entry, and adds #602's entry from main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
Tophat: passedRan 2ad6df0 on a production build, Postgres 16, Chromium at 390×844 and in the walkthrough at 1280×900. Since the last tophat (3ba5494), the help page follows #608's change, made on the owner's call: the grey Signup closed box now shows once, right after you close the signup, not on every visit.
UX: two sentences on the help page. Generated by Claude Code |
richshaw
left a comment
There was a problem hiding this comment.
Review: passed (round 4)
Round 3 passed e604ee5. Since then:
- 22103c2 merges #608's f565650, which shows the grey box only once. Only CHANGELOG conflicted.
- 4bff51b brings the article in line with it:
- "While it's closed" now says "Your signup is marked closed at the top of the page. Right after you close it, a grey box under its title says Signup closed."
- "Reopen your signup" now says "Your signup is marked open again…", without "The grey box goes away".
- The
.ui.tscomment forsignupClosedsays when the box shows. - On a computer, the walkthrough checks the box right after closing, the closed status on every tab, and no box when coming back from Your signups. The phone walkthrough still checks the box after closing from the sheet, and that it's gone after Reopen.
- 2ad6df0 merges #608's 2f9c722 cleanly. This PR's own changes are the same before and after it:
git diff f565650 4bff51bandgit diff 2f9c722 2ad6df0add and remove the same lines.
I checked the article against the code at 2ad6df0:
- "marked closed at the top of the page": both headers show
StatusPillbeside the title (SignupHeader.tsx:101,MobileSignupHeader.tsx:58). The layout holds them, so it's on every tab. - "Right after you close it, a grey box under its title says Signup closed":
ClosedNoticeshows the box under the header only after Close signup on this page. It stays on every tab until its ✕ or the next page load.- Coming back to the signup later shows no box.
- The sentence claims no more than that, and it reads naturally straight after the steps.
- "marked open again" after Reopen: the pill reads
openoncereopenActionrevalidates the layout. Dropping "The grey box goes away" was right, because by the time someone comes back to reopen, there's usually no box. - Nothing left in
src/help: no other text says the box is on every tab or that it goes away. I searched for "every tab", "grey box" and "banner". - The pictures don't show the box, so none needed re-taking. Each is still exactly twice the size the article gives it: 672×390, 560×136 and 1408×680.
- The rest of the article: round 2's sentence-by-sentence check still holds. Nothing it relied on changed in 4bff51b or the merges. CI's e2e on this SHA runs the walkthrough with main's #602, which changed the sign-up sheet, and it still finds the public page's "Closed" and the edit link's wording.
#608's round 7 blocker is about a closed signup's empty Responses tab, which this article doesn't describe. Whichever fix #608 takes, this page needs only the merge.
Blocking (0): none.
Optional (2):
- Still open from round 1:
shot()is copied in all three help walkthroughs undertests/e2e/help/, and could move totests/e2e/helpers/. - From the UX walkthrough comment on this PR: on a 390px phone,
what-people-see.png(1408×680) shows 356px wide, so its "Closed" text is about 7px tall. A capture at phone width would read at full size. Its alt text says what it shows, and no step depends on it.
Earlier threads: none. Copilot couldn't review (quota used up).
Checks:
- CI on 2ad6df0 is green:
lint-type-test,e2e(88 passed), CodeQL and both Analyze jobs. - Locally on 2ad6df0, the help and sitemap unit tests pass (
src/helpandseo.test.ts, 59). They includearticles.test.tsx's style rules and picture sizes. - Tophat: passed on 2ad6df0, the head (comment).
Why this waits for you: it's UX: a new public help page, and a new description on the /help index.
- Look at
/help/close-or-reopen-a-signupon a phone and on a computer, and at the three pictures inpublic/help/close-or-reopen-a-signup/. - Merge it after #608, which needs one more round first.
https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
Generated by Claude Code
…ains an empty closed signup's Responses tab The header now records every status change it shows, with whether a button on this page made it. Before, a change from elsewhere left the last one made here in place, so after a close here, a reopen and a close from somewhere else showed the box again. The box now also needs the last change to be this page's. A closed signup's empty Responses tab says only "Nobody signed up.", which counted on the box being there on every visit. It still says that: "closed" in the header already means nobody can sign up. The comment, the test's comment and #578's CHANGELOG line now say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
#648 is #606's change, which this branch already holds, so this branch's files stay as they were. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
Brings in cefdd44 (the box only for this page's own close, and the empty Responses tab's comment) and 3f368c4. No conflicts, and this branch's own changes are the same line for line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
#649 is Reopen signup's branch at 3f368c4, which this branch already holds, so its files stay as they were. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K
Fixes #20. PR 5 of 5. Merge after #608, which it's stacked on, and so after the rest of the stack (#604, #605, #606).
The problem
No help page covers closing a signup. Nothing tells an organizer what Close signup does to the people already signed up, to reminders, or to the link. Now that a closed signup can be reopened (#608), nothing says how either.
The fix
A new page, "Close or reopen a signup" (
src/help/articles/close-or-reopen-a-signup.tsx), scaffolded withnew-article.mjsand registered insrc/help/articles.tsandbodies.tsx. It has three sections:KEEPS_THEIR_SPOT, and can still change their details or cancel with the link they got when they signed up, but can't take more spots;The help index description now mentions closing and reopening.
CHANGELOG: a line under Added.
Facts checked against the code:
notifiableRulestreats closed like open;src/jobsorsrc/emailacts onsignup.closedorsignup.reopened;signup-view.tsx;reopenSignup;signupShutReason.Tests
The walkthrough is
tests/e2e/help/close-or-reopen-a-signup.spec.ts. It finds every control by the article'sUInames, so renaming one fails it.On a computer, it:
It takes the article's three screenshots along the way.
On a phone, the three dots are beside the title. It closes from the sheet, and checks the three dots stay once it's closed. It then reopens from them, where the sheet holds Preview and Reopen signup.
Earlier, the phone walkthrough found two bugs in #608's sheet, now fixed there (bc159d0, 3ca991f).
The picture for the Reopen step,
reopen-button.png, shows just Preview and Reopen signup (280×68). It replaces the grey box's computer-width capture, which was too small to read on a phone.Ran and passed on 3ba5494:
pnpm lint,pnpm typecheck,pnpm knipandpnpm test(1746, includingarticles.test.tsx's style rules and its "every screenshot has a file, alt text and its real size");pnpm build && pnpm budgets(/help/[slug]111.6 of 113 kB);organizer.spec.ts, every help walkthrough anda11y.spec.ts(41).The screenshots, under
public/help/close-or-reopen-a-signup/, were taken by the walkthrough withHELP_SCREENSHOTS=1.Legal
No change. This page describes behaviour the earlier PRs added, and it stores and sends nothing.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LrYa57fvbqWjZj89iH975K