Skip to content

Move the responses download onto the Responses tab as Download CSV - #562

Merged
richshaw merged 7 commits into
mainfrom
claude/issue-559-responses-download
Oct 23, 2026
Merged

richshaw merged 7 commits into
mainfrom
claude/issue-559-responses-download

Conversation

@richshaw

Copy link
Copy Markdown
Owner

Fixes #559.

The problem

The responses download was an Export button in the signup header, next to Preview and Publish / Close. On a phone it was Export responses in the ⋯ menu. It showed on the Build and Settings tabs and on drafts, and the Responses tab had to tell people where it was ("Export to CSV from the header.").

The fix

  • Responses tab (responses/page.tsx): when there's at least one row, a bar above the table holds a summary line on the left and an outlined Download CSV link with a download icon on the right. It's a plain <a href="/api/signups/[id]/export.csv">, not next/link, because the route already sends the file as an attachment. On a phone the summary and the button sit on separate rows, and the button is full width and 44px tall. The bar sits outside the table's box, and the table markup is unchanged. With no sign-ups there's no bar, only the existing empty-state box. The sentence "Everyone who's signed up so far. Export to CSV from the header." is gone.

  • The summary comes from summarizeResponses (responses/responses-summary.ts), a small pure function that works from the commitments and ended the page already loads, so there's no new query. It reads "5 sign-ups · 1 cancelled · 1 removed":

    • Sign-ups are the active commitments, the same number as the tab's badge. "1 sign-up" is singular.
    • Cancelled rows that howCommitmentsEnded marks removed count as removed. Unmarked ones count as cancelled.
    • The old half of a move isn't counted.
    • Parts that would read 0 are left out.

    The count is semibold in ink, and the rest is muted.

  • Header (SignupHeader): Export is gone. The button grid now holds Preview plus Publish or Close, or Preview alone.

  • Phone ⋯ menu: Export responses is gone from MobileMoreMenuSheet. Publish and Close lost the top border that separated them from it. MobileSignupHeader hides the ⋯ button when the signup is neither a draft nor open, because the sheet would open empty. The now-unused signupId prop was dropped from both.

  • The design board says "5 signed up". I followed the issue's wording, "5 sign-ups", because the issue says why: one person can hold several spots.

  • I left the touched files' existing (non-Prettier) formatting alone, so the diff only shows the change.

  • CHANGELOG line under Unreleased › Changed. The responses-csv.ts doc comment now names the new button.

Tests

  • responses-summary.test.ts checks the singular and plural, that zero parts are left out, that cancelled and removed are counted apart, that a move isn't counted, and that the line reads "0 sign-ups · 2 cancelled" when everyone has dropped out.
  • New and extended checks in tests/e2e/responses.spec.ts:
    • Empty signup: no Download CSV and no header Export.
    • After one sign-up: the link has the export href, and clicking it downloads <slug>-responses.csv.
    • Summary line: reads "0 sign-ups · 1 removed" after a removal, and "1 sign-up" after a move.
    • Phone: the button is below the summary and at least 44px tall.
    • Closed signup on a phone: there's no ⋯ button.
  • Ran and passed:
    • pnpm lint && pnpm typecheck && pnpm knip && pnpm test (1322 tests)
    • pnpm build && pnpm budgets: Responses page at 124.3 kB of 125. Every page's budget still holds, and none came in a whole kB under its budget.
    • Playwright (container config, chromium): responses, organizer, response-headers, a11y and the three help/ walkthroughs, 29 passed.
  • Help: affected-articles.mjs flags only "More actions" in create-and-publish-a-signup. That article covers a draft, which still has the ⋯ menu, and its walkthrough passes. No article screenshot shows the header buttons or the Responses bar, so I didn't re-take any.

https://claude.ai/code/session_01N2PuyFrrvptSz3zh2EXtmX


Generated by Claude Code

The header's Export sat on every tab, away from the table it downloads.
The Responses tab now has a summary line ("5 sign-ups · 1 cancelled ·
1 removed") and a Download CSV link above the table, shown only once
someone has signed up. Export leaves the header and the phone's ⋯ menu,
and the ⋯ button is hidden for a closed or archived signup, whose menu
would now be empty.

Fixes #559.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2PuyFrrvptSz3zh2EXtmX
@richshaw richshaw added pipeline:ux Changes what a person using OpenSignup sees or does pipeline:working A session is working on it right now labels Oct 22, 2026 — with Claude
@richshaw
richshaw requested a balanced review from Copilot October 22, 2026 20:30

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@richshaw richshaw added pipeline:tophat pipeline:working A session is working on it right now and removed pipeline:working A session is working on it right now labels Oct 22, 2026 — with Claude

Copy link
Copy Markdown
Owner Author

Tophat: passed

I ran 1408a7c locally with pnpm dev, Postgres 16, and Chromium at 1280×900 and 390×844. I didn't run the worker, because nothing here touches jobs or email. The test data had four signups, all made through the API:

  • "Snack duty — Spring season": open, one slot with capacity 5, four made-up @example.com sign-ups. One was cancelled through its edit token, and one was removed in the Responses tab.
  • "Bake sale — empty": open, with no sign-ups.
  • "Car wash — draft": a draft.
  • "Coat drive — closed": closed, with one sign-up.
Check Result What I saw
Base (ac4ef66), for comparison ✅ seen The header showed Export next to Preview and Close signup (562-base-build-header), and the Responses tab said "Export to CSV from the header." (562-base-responses). On a phone, a closed signup's ⋯ menu held only Export responses (562-base-closed-phone-menu).
Header has no Export ✅ An open signup's header shows "Preview | Close signup" (562-head-build-header). A draft's shows "Preview | Publish" (562-head-draft-header). A closed signup's shows Preview alone.
Summary line and Download CSV ✅ The line read "3 sign-ups · 1 cancelled". After I removed Alex with Remove → Yes, remove, it read "2 sign-ups · 1 cancelled · 1 removed", and the tab badge read 2 (562-head-responses). The closed signup reads "1 sign-up". The old sentence is gone.
The download ✅ The link's href is /api/signups/<id>/export.csv. Fetched with the organizer's cookie, it returns text/csv as an attachment named snack-duty-spring-season-…-responses.csv, with the usual column headers. The e2e run in CI clicks the link and checks the download.
No sign-ups ✅ The empty signup has no bar and no Export anywhere, only the existing "No signups yet" box (562-head-responses-empty).
Phone layout ✅ At 390 px the summary sits above the button. The button is 358×44 and full width, starting 10px below the summary, and nothing scrolls sideways (562-head-phone-responses).
Phone ⋯ menu ✅ On an open signup the menu holds Close signup only (562-head-phone-open-menu), and on a draft Publish signup only (562-head-phone-draft-menu). Neither has Export responses. A closed signup has no ⋯ button at all (562-head-phone-closed).
Console and server logs ✅ No console errors. The only server errors were the health check's pgboss.job query, because the worker wasn't running.

For the owner (UX): organizers now download responses from the Responses tab, through a Download CSV button next to a line like "2 sign-ups · 1 cancelled · 1 removed". The header's Export button and the phone menu's Export responses are gone, everywhere, drafts included. With no sign-ups there's no button. On a phone a closed or archived signup no longer shows the ⋯ button, because nothing would be left in its menu. Compare 562-base-build-header and 562-base-responses with 562-head-build-header and 562-head-responses, and see 562-head-phone-responses for the phone.

Screenshots are in https://claude.ai/code/session_015b9fVFzh46bDFcFB7sxXRX.


Generated by Claude Code

@richshaw richshaw added pipeline:review pipeline:working A session is working on it right now and removed pipeline:tophat pipeline:working A session is working on it right now labels Oct 22, 2026 — with Claude

@richshaw richshaw left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Review: passed (round 1)

The PR does what #559 asks. The Responses tab now has a bar above the table, outside the table's box. It holds a summary line and a plain <a href> Download CSV link to the unchanged /api/signups/[id]/export.csv. The bar only shows when there's at least one row, so an empty signup still gets just the existing empty-state box. summarizeResponses is a pure function over the commitments and ended the page already loads, so there's no new query. Its sign-up count uses ACTIVE_COMMITMENT_STATUSES, the same rule countActiveCommitmentsForSignup uses for the tab badge in layout.tsx, so the two numbers can't disagree. Cancelled rows are split by howCommitmentsEnded into removed and cancelled, and moved rows are skipped, which matches the table's status pills. Export is gone from SignupHeader (the grid is now 2 columns, or 1 when there's no Publish or Close) and from MobileMoreMenuSheet. MobileSignupHeader hides ⋯ unless the signup is a draft or open, which matches exactly what the sheet can show. Nothing in src still links the header export. The only other reference is the API's exportCsv link, which stays. Every "Done when" item is done.

Blocking (0): none.
Optional (0): none.
Earlier threads: none. Copilot couldn't review because its quota is used up.
Checks: CI is green on 1408a7c (lint-type-test, e2e, CodeQL). The tophat passed on 1408a7c, which is the current head, so no runtime code has changed since. I read the full diff, the Responses page, countActiveCommitmentsForSignup, howCommitmentsEnded and listCommitmentsForSignup, and the new unit and e2e tests. On the checklist: no service, route, schema, policy guard or query changed, and no client JavaScript was added. There's a CHANGELOG line under Changed. No help article mentions Export, and the PR's walkthrough runs cover the Responses tab.

Why owner: pipeline:ux. The download moves from the header (and the phone ⋯ menu) to a Download CSV button on the Responses tab, next to a line like "2 sign-ups · 1 cancelled · 1 removed". It's hidden when there are no sign-ups. On a phone, a closed or archived signup no longer shows ⋯. Compare the tophat's 562-base-build-header and 562-base-responses with 562-head-build-header, 562-head-responses and 562-head-phone-responses.

https://claude.ai/code/session_0135VETqGwB4Kft9ZGEmV9Yy


Generated by Claude Code

@richshaw richshaw added pipeline:owner Passed; waits for the owner (UX or high-risk) and removed pipeline:review pipeline:working A session is working on it right now labels Oct 22, 2026 — with Claude
@richshaw richshaw added pipeline:merge and removed pipeline:owner Passed; waits for the owner (UX or high-risk) labels Oct 22, 2026
@richshaw richshaw added the pipeline:working A session is working on it right now label Oct 22, 2026 — with Claude

Copy link
Copy Markdown
Owner Author

Approved by the owner at 1408a7c.

https://claude.ai/code/session_01Ga7vyEoy6hrHo2caTyqqAk


Generated by Claude Code

@richshaw richshaw added pipeline:fix and removed pipeline:merge pipeline:working A session is working on it right now labels Oct 22, 2026 — with Claude

Copy link
Copy Markdown
Owner Author

Not merged: the PR conflicts with main (mergeable_state: dirty) in src/components/signup/MobileMoreMenuSheet.tsx, after #507 landed. Every other gate passed on 1408a7c: CI green, review pass, tophat pass on this head, no open threads, owner approval recorded above. Moved to pipeline:fix for the merge from main. A conflict resolved in runtime code goes back through tophat, and the owner's approval won't carry over.

https://claude.ai/code/session_01Ga7vyEoy6hrHo2caTyqqAk


Generated by Claude Code

@richshaw richshaw added the pipeline:working A session is working on it right now label Oct 22, 2026 — with Claude
Resolve the MobileMoreMenuSheet conflict with #507: keep main's
StatusActionScope forms that show a refusal, and drop the Export
responses link and the top border that separated it, as this PR does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2gRpBAByqo81TkLboV4UJ
Without the header's Export link the builder page comes to 150.9 kB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2gRpBAByqo81TkLboV4UJ

Copy link
Copy Markdown
Owner Author

I merged main in, which fixes the conflict with #507 (0bd6527, pushed as 9f7b554).

  • MobileMoreMenuSheet.tsx: I kept Tell a viewer why Publish, Close or Delete was refused #507's StatusActionScope / StatusActionButtonForm / StatusActionRefusal forms, so a refused Publish or Close still says why in the ⋯ menu. On top of that I applied this PR's change: the Export responses link is gone, and so is the border-t that separated Publish/Close from it. Compared with main, the only differences in src/components/signup/ are this PR's.
  • budgets.json: with the merge, the builder page comes to 150.9 kB against Tell a viewer why Publish, Close or Delete was refused #507's 152 kB budget, so I lowered it to 151 (9f7b554), as CLAUDE.md asks.
  • Ran and passed:
    • pnpm lint && pnpm typecheck && pnpm knip && pnpm test: 1331 tests.
    • pnpm test:db: 507 tests.
    • pnpm build && pnpm budgets.
    • Playwright (container config, chromium): responses, organizer and a11y, 19 passed.

The conflict was in runtime code, so this goes back through tophat, and the owner's approval at 1408a7c doesn't carry over.

https://claude.ai/code/session_01S2gRpBAByqo81TkLboV4UJ


Generated by Claude Code

@richshaw richshaw removed the pipeline:fix label Oct 22, 2026 — with Claude

Copy link
Copy Markdown
Owner Author

Not merged: CI is red on 52215c1. lint-type-test fails at pnpm budgets: /app/signups/[id]/build loads 151.1 kB against this PR's 151 kB budget. 9f7b554 lowered that budget from main's 152 when the page measured 150.9 kB. The merge from main then brought in #544, which pushed it 0.2 kB over. main itself still has 152 there.

Every other gate passed on this head. main is green, there's no conflict, and there are no open threads. The review (round 2) and the tophat both passed at 9f7b554, and nothing since is the PR's own code. The owner's approval is recorded above.

Moved to pipeline:fix. One way to fix it is to set the build budget back to 152, the same as main, so this PR leaves it unchanged. That changes only budgets.json, which isn't runtime code, but it is a commit of the PR's own, so the owner's approval won't carry over.

https://claude.ai/code/session_01FGRcD1xKZ6wL67VKSCbbVC


Generated by Claude Code

@richshaw richshaw added pipeline:fix pipeline:working A session is working on it right now and removed pipeline:merge pipeline:working A session is working on it right now labels Oct 23, 2026 — with Claude
The merge from main brought in #544, which put the builder page at
151.1 kB, over the 151 kB this branch had lowered it to. Main measures
151.2 kB under 152, and this branch comes in a little under main, so it
keeps main's number rather than one the next merge can tip over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGRcD1xKZ6wL67VKSCbbVC

Copy link
Copy Markdown
Owner Author

Fixed the red lint-type-test (pnpm budgets) in d3d8715, on top of a merge of current main (2fad0aa, which brings in #563, no conflicts).

  • Cause: 9f7b554 lowered the builder page's budget from 152 to 151 when it measured 150.9 kB. The merge of Put a refused cell edit back in the builder, and stop text values at the field's length #544 then put it at 151.1 kB. I reproduced that locally on 52215c1.
  • Fix: /app/signups/[id]/build is back to 152, the same as main. budgets.json now has no diff against main. Measured with CI's build env, main is at 151.2 kB and this branch at 151.0 kB. So the PR still makes the page a little smaller, but 151 would leave no headroom for the next merge.
  • Ran and passed: pnpm lint && pnpm typecheck && pnpm knip && pnpm test (1336 tests), and pnpm build && pnpm budgets.

There's no runtime change of the PR's own. It's a commit of the PR's own, though, so the owner's approval at 52215c1 doesn't carry over by the letter of the rule. Once CI is green, this goes back to review.

https://claude.ai/code/session_01FGRcD1xKZ6wL67VKSCbbVC


Generated by Claude Code

@richshaw richshaw added pipeline:review pipeline:working A session is working on it right now and removed pipeline:fix pipeline:working A session is working on it right now labels Oct 23, 2026 — with Claude

@richshaw richshaw left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Review: passed (round 3)

Since round 2 (at 9f7b554), the PR picked up two merges from main (52215c1, 2fad0aa, bringing in #544 and #563) and one commit of its own, d3d8715, which puts the /app/signups/[id]/build budget back to 152, the same as main. budgets.json now has no diff against main, so nothing is raised. The runtime diff against main is unchanged from what rounds 1 and 2 reviewed: the Responses bar with summarizeResponses and the plain Download CSV link, Export gone from SignupHeader and MobileMoreMenuSheet (with #507's StatusActionScope forms kept), and ⋯ hidden on a phone unless the signup is a draft or open. The head merges cleanly into current main.

Blocking (0): none.
Optional (0): none.
Earlier threads: none open. Copilot couldn't review because the quota is used up.
Checks: CI is green on d3d8715 (lint-type-test including pnpm budgets, e2e, CodeQL). The tophat passed at 9f7b554, and the shared check prints 0 runtime hunks since then. I read the full diff against main again.

Why owner: pipeline:ux. Your approval at 52215c1 doesn't carry over by the rule, because d3d8715 is a commit of the PR's own. That commit only changes budgets.json, back to main's value, and touches no screen. The UX is the same as what you approved: see the tophat's 562b-head-responses, 562b-head-phone-responses and 562b-head-phone-viewer-refused.

https://claude.ai/code/session_01RLY4k28ct66HAHrTaCqPxT


Generated by Claude Code

@richshaw richshaw added pipeline:owner Passed; waits for the owner (UX or high-risk) and removed pipeline:review pipeline:working A session is working on it right now labels Oct 23, 2026 — with Claude
@richshaw richshaw added pipeline:merge and removed pipeline:owner Passed; waits for the owner (UX or high-risk) labels Oct 23, 2026
@richshaw richshaw added the pipeline:working A session is working on it right now label Oct 23, 2026 — with Claude

Copy link
Copy Markdown
Owner Author

Approved by the owner at 1f24161. The owner moved this PR from pipeline:owner to pipeline:merge after review round 3 at d3d8715. The only change since is a merge from main (1f24161, bringing in #566).

Session: https://claude.ai/code/session_014Zo1eizoiYtzTbeo28XM5B


Generated by Claude Code

@richshaw
richshaw merged commit 940962e into main Oct 23, 2026
5 checks passed
@richshaw
richshaw deleted the claude/issue-559-responses-download branch October 23, 2026 22:55
@richshaw richshaw removed pipeline:merge pipeline:ux Changes what a person using OpenSignup sees or does pipeline:working A session is working on it right now labels Oct 23, 2026 — with Claude
richshaw pushed a commit that referenced this pull request Oct 23, 2026
Resolves conflicts with #553, #562 and #563:
- PublicLinkChip keeps this PR's signupId and drops the compact prop
  that #563 removed.
- The Responses page keeps #562's Download icon and drops the `after`
  import that trackOrganizerView replaced.
- signups.ts takes main's row types alongside recordActivityBestEffort.
- MobileSignupHeader gets back the signupId prop #562 dropped, since
  the link chip records copies against it.
- The preview page test from #561 mocks trackOrganizerView, which
  replaced recordOrganizerView here.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The CSV export sits in the signup header, away from the responses it downloads

3 participants