Repository navigation
fix(workspace): a long unbroken token wraps inside its bubble, and the composer states the 8,000-character limit before sending (#3458, #3460) - #3480
Merged
Conversation
…r names the message limit before sending (#3458, #3460) #3458 — a 3000-character unbroken token drew a user bubble ~27,900px wide. The bubble is a fit-content box (an items-end column child) and fit-content never goes below min-content, which for an unbreakable token is its whole length. Every Workspace bubble now carries `overflow-wrap: anywhere` (`portalBubble.js::BUBBLE_WRAP_CLASS`) — the user bubble in the chat, the voice-call block, the Inbox pane and the room, and the agent bubble, where it is inherited by the rendered markdown. `anywhere`, not `break-words`: only `anywhere` lowers min-content. Measured in Chrome on the same structure: 22,751px before, 22,751px with break-word, 360px with anywhere. Tables keep their own horizontal scroll (PortalMarkdown already resets the cells). #3460 — the server bounds a chat message at 8000 characters and the composer enforced nothing, so an over-long message came back as "error 422" with the words already cleared. The composer now shows a count for the last 500 characters and, past the limit, a named refusal with Send held (button and Enter both). If the server still refuses a message as invalid, the reason is said in words with the server's own number, no Retry is offered (the same words earn the same answer), and the draft is handed back to the composer. The client number lives in `portalMessageLimit.js` and is pinned to `PortalChatRequest.message` by a test that reads the backend model. Length is counted in code points, as Python's len() counts it. Tests (each went red under mutation, then green on a byte-identical restore): - portalBubbleWrap.mount.spec.js — all 3 red with the wrap class emptied, and with `break-words` in its place. - portalMessageLimit.mount.spec.js — "an over-limit draft cannot be sent, and the limit is named" red without the send() guard, without the button's disabled term, and without the notice; "shows the reason in words, keeps the draft, and offers no pointless Retry" red without the refusal branch and without the draft restore; "equals PortalChatRequest.message max_length" red when the client constant drifts to 8001. portalVoiceMode.spec.js: the source pin on Send's `:disabled` expression is updated for the appended limit term. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
break-wordsdoes not lower min-content, so the token set the width. Bubbles now carryoverflow-wrap: anywherethrough one shared constant (chat, Inbox, room and agent bubbles).The room composer does not get the limit in this change.
Part of the UI sweep epic #3471.
Related Issue
Fixes #3458
Fixes #3460
Journey Impact
Journey Impact: none: bug fix to existing behaviour found by the UI sweep; no journey promise is added or changed
Type of Change
Testing
Mutation:
portalBubbleWrap.mount.spec.js— 3 red with the wrap class emptied, 3 red with it swapped forbreak-words.portalMessageLimit.mount.spec.js— 1 red with thesend()guard removed, 2 with the button's disabled term removed, 1 with the refusal branch removed, 1 with the draft restore disabled, 4 with the client constant set to 8001 (including the backend pin), 2 with the notice hidden. Each restored byte-identical. The pin test readsclient_portal/models.pyand comparesPortalChatRequest.message'smax_lengthwith the client constant; its live consumer is the cross-layer contract.UI verification: on a preview frontend over a live local backend: a 3,000-character token renders an 891 px bubble in a 1048 px column at 1440 and 360 px in 424 px at 768 (exactly 85%), with no horizontal scroll on the page or the conversation, and the same in the Inbox pane; 7,600 characters shows the counter, 8,001 shows the alert, disables Send, blocks Enter and keeps the draft, in light and dark. The server-refusal path is no longer reachable from the UI and is covered by the mounted test only. Not covered by a mounted test: the room and voice-turn bubble sites, which use the same constant.
Checklist
🤖 Generated with Claude Code