Decisions: Chat Attachment
Append as you go, newest last.
- Separate component, not part of
ChatInput(2026-10-16). The first version was a story-only chip. The design has three looks and a hover remove, and an attachment also belongs in a sentChatMessage, so it is its own proposal.ChatInput'sfieldHeaderslot stays the container. - Name
ChatAttachment, notChatFileUpload. PDS already hasFileUpload(the drop zone); an attached item is a different thing. The designer's Figma frame is calledChatFileUpload. Open: confirm the name. - Fixed size. File chips are one width (
spacing-9xl) and height (spacing-3xl) and the name clamps to two lines; images are aspacing-3xlsquare. Rows wrap predictably. - Middle truncation is done on the string, not with CSS. CSS cannot keep the end of a name while shortening the middle over two lines. Over 24 characters, the name keeps its first 17 and last 6 with an ellipsis (
verylong-filename…123.md, as in the design). Counted in characters, not pixels; with a very wide or narrow font it may fit slightly more or less. The full name is in avisually-hiddenspan (the shortened text isaria-hidden), so screen readers read all of it. - Full name in a tooltip (the PDS
Tooltip, with the item as its custom trigger). It opens on hover and on keyboard focus (focus lands on the remove button, which sits inside the trigger). Tap is turned off so a tap does not fight the remove button. - Remove shows on hover only (designer's call), with fallbacks: also on
:focus-within(keyboard) and always on devices that cannot hover (@media (hover: none)). The button is only rendered whenonRemoveis set;removeLabelcarries the full accessible label, since the component holds no text. - Image without a preview is a grey square with an image icon. Built as "no preview"; open: is it "loading"?
- Marked
new-pattern. Depends on Icon (local wrapper), IconButton and the PDSTooltip. - Type and color (designer, 2026-10-16): text and icon use
foreground-default-secondary, the icon is sizesand the text is2xs. - Border color:
border-default(notborder-default-secondary) on the chip and the remove button. - Remove button stays opaque on hover (2026-10-16). The IconButton hover and pressed fills are translucent, so an image showed through the button. The hover and pressed fill is now the same tint laid over an opaque
surface-default. - Truncation keeps the extension, not the last characters (designer, 2026-10-16). Over 24 characters the name is the start, an ellipsis and the extension after the last dot (
verylong-filename-exam….md). No extension: the last 6 characters. Supersedes the "first 17 and last 6" rule above. - Error state (2026-10-16), first try:
status='error'witherrorMessage. The tile keeps its size and takes the critical background, border and text; the file icon becomes an exclamation icon; an image stays visible, dimmed, with the icon over it. The tooltip reads<file name> — <reason>, and a visually hiddenrole='alert'span announces the reason.statusrather than a boolean so anuploadingvalue can join it. Not built: retry (onRetry), uploading. Open: reason on the chip, retry, and whether Remove should always show on an error. - No tooltip on the remove button (designer, 2026-10-16).
hasTooltip={false}turns off the IconButton tooltip, andtitle={undefined}removes the nativetitleit falls back to. Thearia-labelstays, so screen readers still announce it. The file-name tooltip on the whole item still shows when hovering the X. The port needs the same two props if IconButton still falls back totitle. - How the error type is communicated (2026-10-16): the chip stays quiet (critical look, exclamation icon). The reason is in the tooltip, which now also opens on tap for error chips only (other chips keep tap off so a tap does not fight the remove button), and in a visually hidden alert. The sentence that says what happened and what to do belongs in
ChatInput's helper message, which the screen writes; theWith Attachment Errorstory shows it. Open:errorTypethat drives retry (network) versus remove-only (too large, unsupported). - Error icon is
diamondExclamation(designer, 2026-10-16), on the chip, over an image, and in the helper message in theChatInputstory. - Image error (designer's mock, 2026-10-16): a failed image is the same tile with the critical background and border and the image icon in the critical text color, with no thumbnail (a picture that failed has nothing reliable to show), so the badge and dimming were removed. A failed file chip keeps the
diamondExclamationicon. The tooltip and the hidden alert still carry the reason. - The chip does not announce its own error (2026-10-16). Its hidden reason text is plain (no
role='alert'), so a screen reader reads it when the user reaches the chip. The screen owns the one live announcement (the helper message inChatInput); with several failures, per-chip alerts would be read all at once. Supersedes the "visually hiddenrole='alert'" line above.
Semantic type classes instead of font primitives (2026-10-19)
- Decision: the item now takes the PDS semantic
pds-copy-*class in its markup instead of declaringfont-family,font-sizeandline-heightfrom the typography primitives. Colors were already semantic tokens (foreground, surface, border, status, interactive). - Why: The designer asked for semantic tokens over primitives where they exist. The package has no semantic font token; the semantic layer is the
pds-copy-*utility classes. Computed values are unchanged (Inter, 14px/21px ats). - Port impact: the
pds-copy-*classes are!important, so a CSS rule cannot override size, weight or line height on that element. Components that size text through a CSS variable or override weight on the same element (ChainOfThought, Timeline, TimelineItem, LabeledDivider, ChatInput, IdentityBlock) keep the primitives for now.
Handoff note (2026-10-20)
- The earlier lines that mention a
role='alert'span on the chip, and an error image that is dimmed with a badge, are superseded by the two entries above them that say the chip does not announce its own error and that a failed image has no thumbnail. The code matches the later entries.