Skip to content

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
vybe merged 1 commit into
devfrom
feature/3458-workspace-composer-bubble-limits
Oct 9, 2026
Merged

vybe merged 1 commit into
devfrom
feature/3458-workspace-composer-bubble-limits

Conversation

@trinity-ability

Copy link
Copy Markdown
Contributor

Description

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

  • Bug fix (non-breaking change that fixes an issue)

Testing

  • I have tested this locally
  • New tests added (if applicable)
  • All existing tests pass
  • Every new test executes the changed path

Mutation: portalBubbleWrap.mount.spec.js — 3 red with the wrap class emptied, 3 red with it swapped for break-words. portalMessageLimit.mount.spec.js — 1 red with the send() 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 reads client_portal/models.py and compares PortalChatRequest.message's max_length with 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

  • My code follows the project's style guidelines
  • I have updated the documentation (if applicable)
  • I have not committed any sensitive data (API keys, credentials, etc.)

🤖 Generated with Claude Code

…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>

@vybe vybe 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.

merge-train: batch validated on train/20261009-2301-a (#3500)

@vybe
vybe merged commit 28c5a74 into dev Oct 9, 2026
23 checks passed
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.

2 participants