Skip to content

Cut an over-long Magic Compose title or description instead of throwing the draft away - #579

Merged
richshaw merged 1 commit into
mainfrom
claude/issue-569-shorten-long-draft-text
Oct 24, 2026
Merged

richshaw merged 1 commit into
mainfrom
claude/issue-569-shorten-long-draft-text

Conversation

@richshaw

Copy link
Copy Markdown
Owner

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

  • New 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 to title and description on the model's raw output and returns one warning per cut. The limits are imported from src/schemas/signups.ts, not typed in.
  • The route (src/app/api/signups/magic-compose/route.ts) runs fitDraftText before MagicComposeDraftSchema.safeParse. The cut warnings come first in the response's warnings, 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.
  • FullDraftSchema is unchanged, so the MCP create_signup tool still refuses an over-long value with invalid_input.
  • The prompt and its golden file are unchanged. Other shape failures (too many slots or fields, a missing title) still get the generic message.
  • CHANGELOG entry under Fixed.

Tests

  • fit-text.test.ts covers:
    • cutting at a space, trimming punctuation, cutting mid-word when there's no space, and not splitting an emoji
    • a long description and a long title, each cut to its limit with a warning
    • the rest of the draft kept as it was
    • no warning for a draft within the limits
    • non-draft input passed through untouched
  • route.db.test.ts has new tests that drive POST with the real parse, persistDraft and Postgres, with only the session and the model stubbed:
    • a description of about 2,100 characters makes the signup with all 3 slots, a saved description of at most 2,000 characters ending in "…", and the warning
    • a title over 120 characters gets the same treatment
    • a draft within the limits gets no warning
    • Without the route change, the first two tests fail.
  • signups-write.test.ts: create_signup refuses a description of 2,001 characters with invalid_input and creates nothing.
  • Ran and passed: pnpm lint && pnpm typecheck && pnpm knip && pnpm test (1390 tests) and pnpm test:db (539 tests). No page or client code changed, so I didn't run pnpm build.

Generated by Claude Code

…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
@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 24, 2026 — with Claude
@richshaw
richshaw requested a balanced review from Copilot October 24, 2026 05:01

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 insufficient remaining quota.

@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 24, 2026 — with Claude

Copy link
Copy Markdown
Owner Author

Tophat: passed

I ran 5675f19 locally with pnpm dev and Postgres 16, in Chromium at 1280×900 and 390×844. Magic Compose was pointed at a local stub model through LLM_BASE_URL. The stub returns a fixed 3-slot draft, with a description of about 2,200 characters, a title of about 140 characters, or no slots, depending on a keyword in the prompt.

Check Result What I saw
Before, on base 6d30a99: a draft with a long description ✅ (bug reproduced) Stayed on /app/signups/new with "AI draft did not match expected shape — try again or adjust your prompt". No signup was made.
After: the same draft, desktop ✅ The draft opened on the Build tab. "A few things to double-check" read "The AI's description was longer than 2,000 characters, so the end was cut off. Check it below." The saved description is 1,997 characters and ends "…with nuts…", with the full stop before the cut dropped. All 3 slots were kept.
After: long title and long description, phone ✅ Both warnings were listed, title first. The saved title is exactly 120 characters, cut at a space and ending in "…". The layout fit at 390px with no sideways scroll.
After: a draft within the limits, phone ✅ Only the "Here's your draft" banner appeared, with no double-check box. The description was saved unchanged.
A draft that's wrong in another way (no slots) ✅ It still shows the generic "did not match expected shape" message. The POST returns 500, the same schema_mismatch → internal mapping as on main, and that 500 is the only console error.
Cut signup published and opened as a visitor ✅ Publish worked. /s/[slug] shows the shortened title and description, and Sign up is offered on all 3 slots.
MCP create_signup still refuses a 2,001-character description ✅ src/mcp/tools/signups-write.test.ts and fit-text.test.ts: 36 passed. route.db.test.ts: 6 passed.

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 579-before-longdesc-desktop shows the old behaviour, and 579-after-longdesc-desktop and 579-after-longtitle-phone show the new one. Nothing changes for a draft within the limits (579-after-normal-phone).

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


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 24, 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)

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 records raw.value, not fitted.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

@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 24, 2026 — with Claude
@richshaw
richshaw merged commit 4ed27a9 into main Oct 24, 2026
5 checks passed
@richshaw
richshaw deleted the claude/issue-569-shorten-long-draft-text branch October 24, 2026 17:46
richshaw pushed a commit that referenced this pull request Oct 24, 2026
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
@richshaw richshaw removed pipeline:ux Changes what a person using OpenSignup sees or does pipeline:owner Passed; waits for the owner (UX or high-risk) labels Oct 24, 2026 — with Claude
richshaw pushed a commit that referenced this pull request Oct 24, 2026
richshaw pushed a commit that referenced this pull request Oct 24, 2026
) 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
richshaw pushed a commit that referenced this pull request Oct 24, 2026
… 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
richshaw pushed a commit that referenced this pull request Oct 24, 2026
…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
richshaw pushed a commit that referenced this pull request Oct 25, 2026
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
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.

A Magic Compose draft is thrown away when its description runs past 2,000 characters

3 participants