Repository navigation
Cut an over-long Magic Compose title or description instead of throwing the draft away - #579
Conversation
…g the draft A model asked to keep a long announcement word for word went past the 2,000-character description limit (or the 120-character title limit) the same way on every retry, and the route threw the whole draft away. The route now cuts the text at the last space before the limit, ends it with an ellipsis, and adds a warning for each cut to the response, so the build page's banner says so. FullDraftSchema is unchanged, so the MCP create_signup tool still refuses an over-long value with invalid_input. Fixes #569. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DA6naRBuxPEquRNL5hzzfu
Tophat: passedI ran 5675f19 locally with
For the owner (UX): when the AI writes a title over 120 characters or a description over 2,000, the organizer now gets the draft with the text shortened at a word boundary and ending in "…". The yellow "A few things to double-check" box says what was cut. Before this change they got an error, and retrying failed the same way every time. Screenshot Screenshots are in https://claude.ai/code/session_016aQEgj8JvbpAMX1SBKqLMA. Generated by Claude Code |
richshaw
left a comment
There was a problem hiding this comment.
Review: passed (round 1)
Magic Compose used to throw away a whole draft when the model wrote a title over 120 characters or a description over 2,000. Now the route shortens those two fields before MagicComposeDraftSchema.safeParse, and it puts one warning per cut at the top of the response's warnings. shortenToLimit stays within the limit: it keeps max − 1 characters plus "…", never splits a surrogate pair, cuts at a space only when that space is in the second half (so a title can't fall below TITLE_MIN_LENGTH), and falls back to the untrimmed head if trimming punctuation would leave nothing. The limits come from src/schemas/signups.ts, the same constants FullDraftSchema uses, so the cut and the check can't disagree. FullDraftSchema is unchanged, so MCP create_signup still refuses an over-long value with invalid_input, and signups-write.test.ts pins that. Every "Done when" item in #569 is met: the route DB tests cover a long description, a long title and no warning within limits, and the CHANGELOG has an entry under Fixed.
Blocking (0): none
Optional (2):
src/app/api/signups/magic-compose/route.ts:118: when the draft fails Zod for some other reason, the log still recordsraw.value, notfitted.value. That's arguably the right choice for operators. No change needed.- #573 also edits this route, as the description says. Whichever lands second gets a small merge.
Earlier threads: none. Copilot couldn't review because the quota was used up.
Checks: CI is green on 5675f19 (lint-type-test, e2e, CodeQL). The tophat passed on this exact SHA, and no runtime code has changed since.
Gate: owner (UX). Organizers now get a shortened title or description and a yellow "A few things to double-check" warning, where before they got an error. Worth a look: the tophat's screenshots 579-after-longdesc-desktop and 579-after-longtitle-phone, and the warning wording "The AI's description was longer than 2,000 characters, so the end was cut off. Check it below."
https://claude.ai/code/session_0156kgzuGdEEEHhyCuFXvuYv
Generated by Claude Code
Brings 4ed27a9, which cuts an over-long Magic Compose title or description instead of throwing the draft away. Merged without conflict; the CHANGELOG keeps both sides' entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
…panel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
) into the publish button Brings in the share message's shared time and its "A and B" days, the message box's one label and growing rows, Email it's body without the title, the signup's title under the QR code in its picture, a phone's large QR code with Save image, the QR code's new alt, qr_code recorded once per opening, PR 1's "Everyone already signed up keeps their spot." in the close question, and main's #579. Conflicts: - CHANGELOG #505: PR 2's line with its new facts (dates, time, the QR code's title, Save image, the label, the rows, Email it's body, the telemetry), with this branch's final state kept: "After **Publish and share**", a sheet on a phone with no More actions sheet closing first, and Share "on a phone in the bar at the bottom of the screen". - CHANGELOG #457: PR 1's line with "Everyone already signed up keeps their spot.", ending "A draft still shows Preview and **Publish and share**." as before. #499 is this branch's, unchanged. The help article and the walkthrough merged on their own: PR 2's message sentence, QR code sentence (title under it, Save image on a phone), the "Sat, Apr 6 and Sat, Apr 13" message check, the QR code's alt and the phone's QR code and Save image checks, inside this branch's steps (the draft line, the count by the link, the phone's bar). spot-counts.ts keeps both PR 1's close question and this branch's spotsTakenLine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
… into the publish check Brings in, by way of PR 3: the close question's "Everyone already signed up keeps their spot." once someone has signed up; main's #569 cut of an over-long Magic Compose title or description; and PR 2's second owner round (the share message's date and time line, Copy message's widths, Email it's body without the title, one message label, the growing message box, the title under the QR code in its picture, the phone's large QR code with Save image, the new QR alt, and qr_code once per opening). docs/telemetry.md conflicted on the publish, copy and share rows: the publish_refused and link_copied rows keep this branch's words (the check's reasons, and that a draft has no link to copy), and link_shared takes PR 2's qr_code wording. CHANGELOG, the help article, its UI list and the walkthrough merged on their own; each fact is in them once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
…he fix focus PR 4 brings its second tophat round (Publish's spinner and "Checking…" shown only after 300 ms and for 400 ms at least, through useDelayedFlag; a TBD inside a longer value kept in the warning's slot label), and through PR 3, PR 2's second round, PR 1's close question and main's #579. Conflicts: - PublishButton.tsx: the button's class takes PR 4's showBusy for the busy look and keeps this branch's ring. The ring, the hand-back and the held-Enter guard don't read showBusy or busy, so they work the same before the spinner shows. - publish-check.ts: PR 4 added isOnlyPlaceholder beside the regex this branch moved to placeholder-text.ts. isOnlyPlaceholder stays, using the shared placeholderIn; the regex isn't copied back. - CHANGELOG.md, the #503 entry: PR 4's wording (the slot label's values less any that is only the word, the spinner's timing and "Checking…") with PR 5's sentences on Done and Enter handing focus back, the Build tab's mark and the help page put back in their places. PublishButton.test.tsx, the help article, its UI list, the publish walkthrough and organizer.spec.ts merged by themselves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoJtwsyrp6YC4DU9JAnc7H
Through the server, telemetry and screen branches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Fixes #569.
Overlaps #573, which also edits
src/app/api/signups/magic-compose/route.ts(questions from Magic Compose). This PR changes two lines there. Whichever lands second gets a small merge.The problem
Magic Compose's draft schema caps the title at 120 characters and the description at 2,000. If the model went over either one, the route threw the whole draft away, and the organizer saw "AI draft did not match expected shape — try again or adjust your prompt." When an organizer asks for a long announcement to be kept word for word, the model goes over the same way on every retry, so trying again never helps.
The fix
src/lib/magic-compose/fit-text.ts:shortenToLimit(text, max)cuts at the last space before the limit and ends with "…", staying within the limit. It drops any punctuation left at the cut, never splits an emoji, and cuts mid-word only when there's no space in the second half.fitDraftText(raw)applies that totitleanddescriptionon the model's raw output and returns one warning per cut. The limits are imported fromsrc/schemas/signups.ts, not typed in.src/app/api/signups/magic-compose/route.ts) runsfitDraftTextbeforeMagicComposeDraftSchema.safeParse. The cut warnings come first in the response'swarnings, so the build page's banner shows them, for example: "The AI's description was longer than 2,000 characters, so the end was cut off. Check it below." The title and description can be edited right below that banner.FullDraftSchemais unchanged, so the MCPcreate_signuptool still refuses an over-long value withinvalid_input.Tests
fit-text.test.tscovers:route.db.test.tshas new tests that drivePOSTwith the real parse,persistDraftand Postgres, with only the session and the model stubbed:signups-write.test.ts:create_signuprefuses a description of 2,001 characters withinvalid_inputand creates nothing.pnpm lint && pnpm typecheck && pnpm knip && pnpm test(1390 tests) andpnpm test:db(539 tests). No page or client code changed, so I didn't runpnpm build.Generated by Claude Code