Repository navigation
feat(apollo-react): add the Wind chat composer - #1378
Conversation
|
Apollo Coded App preview deployments are ready.
|
Dependency License Review
License distribution
Excluded packages
|
📊 Coverage + size by packagePer-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.
"Coverage" is each package's own |
8da30e9 to
c1d906e
Compare
c1d906e to
7a16f6e
Compare
Replace the Wind composer placeholder with apollo-wind's ChatComposer wired to the service as Material's input is: the draft follows the service prompt, Enter or the send button sends through the Request intercept with the attachments and returns focus to the field, the send button stops an in-flight reply unless stopResponse is disabled, the service error shows above the field as a dismissible markdown alert, and the footer disclaimer comes from the shared labels. Attachments come in through the attach button and a panel-wide DropzoneOverlay (drop and paste), checked against allowedAttachments. Hoist Material's dropzone validation and messages into useAttachmentIntake and its send and stop logic into useComposerSend, and switch Material's dropzone and input to them. MST-16566 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7a16f6e to
be7fcd3
Compare
CalinaCristian
left a comment
There was a problem hiding this comment.
One functionality discrepancy in the storybook for composer vs material:
Screen.Recording.2026-10-20.at.3.40.23.AM.mov
Screen.Recording.2026-10-20.at.3.41.03.AM.mov
In material, we don't allow same attachment twice.
Also, when in compact mode I see no difference for the wind component in the attachments section.
Thanks for catching that. The gaps in compact mode are known and will be addressed via #1403. I will make sure to cover the duplicate attachments in the follow-up to those. |
Summary
Fifth and last PR of the Wind chat renderer stack (MST-16566). The Wind composer placeholder from R-1 is replaced by apollo-wind's
ChatComposer, wired to the service as Material's input is.getPromptat mount,SetPromptafter) and writes edits back withsetPrompt; an empty draft clears it, as in Material.SetInputFocusedfocuses the field.Requestintercept with the prompt text and the attachments, then clears the draft and the attachments.Requestpayload from Wind matches Material's.disabledFeatures.stopResponseis set, in which case it stays a disabled send button and Enter does not send. Same as Material. The button stays filled in both states (variant="default"), as Material's does; the primitive's ghost stop button faded its fill out on every send and read as a flicker.DropzoneOverlaydrop, and paste anywhere on the panel, checked againstallowedAttachments: MIME types and their extensions, max size, and the max count minus what is already attached (maxCount: 0means no limit, as in Material).validateFiles: it takes over the paste only when a file passes, so text pasted alongside a refused file still lands, and the refused file is named in the error.SetAttachmentswith it inremovedand drops it from the draft; finishing the upload never brings it back, so the remove button stays available while uploading.disabledFeatures.attachmentsis set.calc(50cqw - 4px)against the message content (an@container), not a percentage of the row. A user message's row is end-aligned and sizes itself to its chips, so a percentage of it squeezed a lone chip to half its own width. With Material's 8px gap, two capped chips fill the message exactly; the composer row uses the same 8px gap (overriding the primitive's 12px).justify-end-safe) and starts from the leading edge once it overflows, so the whole row stays reachable by scrolling. Reply rows stay start-aligned.warn) shows above the field asChatComposerError(role="alert"), rendered as markdown like Material's, and dismissing it clears the error and keeps focus in the field.useChatLabelsat 10px, as in Material, hidden withdisabledFeatures.footer.spacing.compactModesets the composer's compact density, andspacing.promptBoxsets the textarea rows.Hoisted into
src/chat/headlesshooks/use-attachment-intake.ts:useAttachmentIntake,getAcceptedFileTypesmultipleis off, the whole batch refused when overmaxCount, oversized and wrongly typed files named, the rest parsed and attached, the error cleared when nothing was refused. Takes neutral rejection reasons, so both react-dropzone and apollo-wind's checks feed itdropzone.tsxmaps react-dropzone codes onto it (its own handling removed)hooks/use-composer-send.ts:useComposerSendchat-input.tsxsubmit uses ithooks/use-attachment-intake.ts:checkAttachmentFileschat-input-actions.tsxattach button now validates types, size and count through it and the intake hook, like the dropzone (its size message is now the dropzone's)hooks/use-attachment-limits.ts:useAttachmentLimitschat-input-actions.tsxtooltip and screen-reader descriptionStorybook
pnpm storybook:design, then Apollo React / Chat / Playground, Compare renderers:setPrompt. Attach more with the button, by dropping files on the panel, or by pasting.autoReplyechoorstream: send from either composer. The message mirrors to the other chat and both reply. Stop while streaming, or turn ondisableStopResponseto see a disabled send button instead.compactMode: the compact composer.disableAttachments,disableFooter,inputPlaceholderandfooterDisclaimer.Tests
vitest --run src/chat src/material/components/ap-chat src/material/components/ap-tool-call: 49 files, 280 tests pass. New tests cover:Requestpayload parity test against Material.tsc --noEmit: clean.DropzoneOverlaytakes a single hint string, so Wind's drop overlay cannot list the max count, size and types as Material's does. Composer chips also take no title or tooltip from the primitive, so the truncation tooltip is a nativetitle.biome checkon touched files: clean.en.jsononly:input.label("Message", the field's accessible name) andinput.error.dismiss("Dismiss"). Everything else reuses Material's ids with Material's source messages.Stack
Jira: MST-16566
🤖 Generated with Claude Code