feat(core/chat): add new chat components#2569
Conversation
🦋 Changeset detectedLatest commit: fb07964 The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
✅ Deploy Preview for ix-storybook ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Code Review
This pull request introduces the new ix-chat component suite for AI prompt entry and conversation layouts, including ix-chat-input, ix-chat-prompt-attachment, ix-chat-user-message, and ix-chat-ai-message. It also includes a fix for ix-chip to ensure interactive close buttons render correctly when the chip is inactive. The review identified several critical issues in the new chat components, including missing lifecycle handling for observers on component re-attachment, incorrect state handling for the submit button during processing, premature form submission during IME composition, a missing key prop in dynamic lists, potential runtime TypeErrors with null/undefined file names, and a regression in the ix-chip pointer-events styling.
I am having trouble creating individual review comments. Click here to see my feedback.
packages/core/src/components/chat-input/chat-input.tsx (229-244)
The component does not implement connectedCallback to re-initialize the MutationObserver and ResizeObserver when the component is disconnected and re-attached to the DOM. This causes the observers to remain disconnected and breaks the attachment overflow and follow-up updates on re-attachment. Implementing connectedCallback with a guard flag ensures proper re-initialization.
private hasDisconnected = false;
connectedCallback() {
if (this.hasDisconnected) {
this.initAttachmentMutationObserver();
if (this.attachmentsRef.current) {
this.initAttachmentResizeObserver(this.attachmentsRef.current);
}
this.hasDisconnected = false;
}
}
componentDidLoad() {
this.updateHasFollowUp();
this.initAttachmentMutationObserver();
this.scheduleAttachmentOverflowUpdate();
this.updateTextareaHeight();
}
componentDidRender() {
this.scheduleAttachmentOverflowUpdate();
this.updateTextareaHeight();
}
disconnectedCallback() {
this.attachmentResizeObserver?.disconnect();
this.attachmentMutationObserver?.disconnect();
this.hasDisconnected = true;
}
packages/core/src/components/chat-input/chat-input.tsx (292-315)
When state === 'processing', the submit button acts as a "Stop" button. However, canSubmit() does not check this.state, which means if the input is empty, the "Stop" button is disabled and cannot be clicked. Furthermore, if the input is not empty, clicking it or pressing Enter will submit the prompt again instead of stopping the processing. We should ensure the button is enabled during processing and handles the stop action correctly.
private canSubmit() {
return (
!this.disabled &&
!this.readonly &&
(this.state === 'processing' ||
((this.value || '').trim().length > 0 &&
!this.isCharacterLimitReached()))
);
}
private emitIxChangeIfNeeded() {
if (this.initialValue !== this.value) {
this.ixChange.emit(this.value);
this.initialValue = this.value;
}
}
private submitPrompt() {
if (this.state === 'processing') {
this.promptSubmit.emit('');
return;
}
if (!this.canSubmit()) {
return;
}
this.emitIxChangeIfNeeded();
this.promptSubmit.emit(this.value);
}
packages/core/src/components/chat-input/chat-input.tsx (420-433)
Pressing Enter while composing text in an IME (Input Method Editor) will trigger the keydown event and submit the prompt prematurely. We should check event.isComposing to prevent this.
private handleKeyDown(event: KeyboardEvent) {
if (
event.key !== 'Enter' ||
event.isComposing ||
event.shiftKey ||
event.altKey ||
event.ctrlKey ||
event.metaKey ||
this.insertLineBreakOnEnter
) {
return;
}
event.preventDefault();
this.submitPrompt();
}
packages/core/src/components/chat-input/chat-input.tsx (784-811)
The dynamic list rendered by this.attachmentOverflowEntries.map is missing a key prop on the outer element. This can cause rendering and reconciliation issues when attachments are dynamically added or removed.
private renderAttachmentOverflowItems() {
return this.attachmentOverflowEntries.map((entry) => (
<div
key={entry.index}
class="attachment-overflow-menu-item"
data-attachment-overflow-generated
data-attachment-overflow-label={entry.label}
>
<ix-dropdown-item
class="attachment-overflow-generated-item"
icon={entry.icon}
ariaLabelButton={entry.label}
onClick={() => this.handleAttachmentOverflowItemClick(entry.index)}
>
{entry.label}
</ix-dropdown-item>
{entry.canRemove && (
<ix-icon-button
aria-label={entry.removeAriaLabel}
class="attachment-overflow-remove-button"
icon={iconCloseSmall}
size="24"
variant="subtle-tertiary"
onClick={(event: MouseEvent) =>
this.handleAttachmentOverflowRemoveClick(event, entry.index)
}
></ix-icon-button>
)}
</div>
));
}
packages/core/src/components/chat-prompt-attachment/chat-prompt-attachment.tsx (75-87)
If this.fileName is null or undefined, calling this.fileName.trim() will throw a runtime TypeError. We should guard against null/undefined values to ensure robust defensive programming.
private splitFileName() {
const fileName = (this.fileName || '').trim();
const extensionStart = fileName.lastIndexOf('.');
if (extensionStart <= 0 || extensionStart === fileName.length - 1) {
return { name: fileName, extension: '' };
}
return {
name: fileName.slice(0, extensionStart),
extension: fileName.slice(extensionStart),
};
}
packages/core/src/components/chip/chip.scss (74-76)
Removing pointer-events: none from :host(.inactive) and only applying it to .chip-main allows click events on the chip body to bubble up to the host. This means any click listeners on ix-chip will still be triggered even when the chip is inactive. A cleaner CSS-only solution is to keep pointer-events: none on :host(.inactive) and explicitly set pointer-events: auto on .chip-close.
:host(.inactive) {
pointer-events: none;
.chip-close {
pointer-events: auto;
}
}|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds five chat web components, framework proxies (Angular/React/Vue), preview examples, Storybook stories, Playwright and visual tests, accessibility snapshots, typings, and supporting docs/changesets. ChangesChat UI rollout
Sequence Diagram(s)sequenceDiagram
participant IxChatInput
participant ChatPromptAttachment
participant HTMLForm
participant Consumer
IxChatInput->>Consumer: valueChange / promptSubmit / ixBlur / ixChange
IxChatInput->>HTMLForm: synchronize value via ElementInternals
ChatPromptAttachment->>Consumer: attachmentClick / removeClick
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.changeset/chip-inactive-close-button.md (1)
1-6:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winPlease add a clearer PR description for this user-facing change set.
This PR introduces user-facing chat components and behavior changes, but the current description is mostly checklist-style and lacks concrete motivation/implementation details needed for review/release notes quality.
As per coding guidelines, “Request a clearer PR description when user-facing impact, motivation, or important implementation details are missing.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.changeset/chip-inactive-close-button.md around lines 1 - 6, Update the changeset description to be a clear, user-facing PR summary: explain the problem (closable ix-chip lost interactive close button or overlapped text when inactive), describe the user impact (broken UX for closable/inactive chips and truncation issues), summarize what was changed in implementation (how the close button rendering/positioning or CSS was adjusted to remain interactive and avoid overlap with truncation), and include any upgrade notes or compatibility considerations for consumers; ensure the message is concise and suitable for release notes.Source: Coding guidelines
♻️ Duplicate comments (2)
.changeset/chat-user-message-component.md (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRevise changeset wording to reflect new feature announcement, not incremental changes.
The text uses "existing", "now", and "no longer" when describing NEW components. These words suggest changes to pre-existing behavior, which is confusing in a changeset for brand-new features. A past reviewer raised this same concern. Rewrite to focus on what the components provide, not what changed during development.
📝 Suggested revision
-Add the `ix-chat` component for composing chat layouts with messages and a chat input, add the `ix-chat-user-message` component for displaying right-aligned user messages in chat interfaces with optional contextual actions and sent attachments, and add the `ix-chat-ai-message` component for displaying slotted AI-generated responses with optional actions and sources. The existing `ix-chat-prompt-attachment` component now renders with `ix-chip`, supports a compact `sent` variant, supports preview-enabled attachments with `previewSupported`, emits `attachmentClick` only for those preview interactions, and no longer exposes a retry action for failed attachments. `ix-chat-input` can display prompt attachments and now provides a `follow-up` slot for follow-up prompt actions. +Add the `ix-chat` component for composing chat layouts with messages and a chat input, the `ix-chat-user-message` component for displaying right-aligned user messages with optional contextual actions and sent attachments, and the `ix-chat-ai-message` component for displaying AI-generated responses with optional actions and sources. Add the `ix-chat-prompt-attachment` component with `ix-chip` rendering, a compact `sent` variant, preview-enabled attachment support via `previewSupported`, and `attachmentClick` events for preview interactions. The `ix-chat-input` component displays prompt attachments and provides a `follow-up` slot for follow-up prompt actions.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.changeset/chat-user-message-component.md at line 5, The changeset language frames brand-new components as incremental changes; update the description to announce features rather than imply prior behavior by removing "existing", "now", and "no longer" phrasing and instead describe capabilities and intended usage of each new component: mention ix-chat as a layout/composition component for messages and input, ix-chat-user-message for right-aligned user messages with optional contextual actions and sent attachments, ix-chat-ai-message for slotted AI responses with optional actions and sources, ix-chat-prompt-attachment as a component rendered with ix-chip supporting a compact sent variant and preview-enabled attachments that emit attachmentClick for preview interactions only, and ix-chat-input as capable of displaying prompt attachments and providing a follow-up slot; keep the wording declarative and feature-focused.Source: Coding guidelines
packages/core/src/components/chat-input/chat-input.tsx (1)
71-73:⚠️ Potential issue | 🟠 Major | ⚡ Quick winLocalize hard-coded user-facing strings before release.
This component still embeds fixed English UI text (
placeholder,textareaLabel,disclaimer, character-limit messages, submit buttonaria-label). Please route these through the existing i18n mechanism (or translatable props) so the feature is localization-ready.As per coding guidelines, "Use Siemens IX review rules as the primary policy."
Also applies to: 96-96, 145-147, 368-373, 578-578
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/components/chat-input/chat-input.tsx` around lines 71 - 73, The component currently embeds hard-coded English UI strings (see the `@Prop` placeholders like placeholder, textareaLabel, disclaimer, character-limit messages, and the submit button aria-label) which must be internationalized; replace these literal strings with calls into the project's i18n utility or accept translatable props and pass localized values into the component, updating the properties (placeholder, textareaLabel, disclaimer, any characterLimit/charCount messages, and the submit button ariaLabel) to use i18n keys (or injected/localized props) so all user-facing text is pulled from the i18n layer instead of being hard-coded; ensure defaults still work by using the i18n fallback and update usages at the other noted locations (lines referenced) to the same pattern.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/src/components/chat-ai-message/tests/chat-ai-message.ct.ts`:
- Around line 12-71: Add an accessibility regressionTest to this test file: for
each existing regressionTest suite (the ones using regressionTest with mount in
this file) add a new test named 'accessibility' that mounts the same markup and
uses makeAxeBuilder to run an axe scan and expect no violations; specifically
call regressionTest('accessibility', async ({ mount, makeAxeBuilder }) => {
await mount(`<...>`); const results = await makeAxeBuilder().analyze();
expect(results).toHaveNoViolations(); }); Target the same component markup used
in the neighboring tests and keep the test pattern consistent with existing
regressionTest usage and imports.
In `@packages/core/src/components/chat-input/chat-input.scss`:
- Around line 204-208: The current rule removes the outline on
.chat-input:focus-within which hides keyboard focus for readonly inputs; change
the CSS so readonly chat inputs keep a visible, non-interactive focus indicator
instead of removing it. Concretely, stop applying outline: none unconditionally
in .chat-input:focus-within; instead scope the outline suppression to
interactive inputs (e.g., .chat-input:not([data-readonly]) or
.chat-input:not(.readonly):focus-within) and add a visible focus style for
readonly state (use border-color: var(--theme-input--border-color--readonly) and
a subtle outline or box-shadow) so .chat-input and the :focus-within state
preserve keyboard focus indication for readonly controls. Ensure you reference
and reuse the existing --theme-input--border-color--readonly variable for the
readonly focus appearance.
In `@packages/core/src/components/chat-input/chat-input.types.ts`:
- Line 9: Add a JSDoc comment for the exported type ChatInputAttachmentLayout
that includes an `@since` tag and a short description; specifically, above the
line "export type ChatInputAttachmentLayout = 'wrap' | 'scroll';" add a JSDoc
block that documents the type and sets `@since` to the package version (match the
package.json version for this package or the agreed-next-release version) so the
new public API is traceable.
In
`@packages/core/src/components/chat-prompt-attachment/chat-prompt-attachment.tsx`:
- Around line 17-22: The component test for ChatPromptAttachment is missing
automated axe accessibility checks; update the test file
chat-prompt-attachment.ct.ts to use the existing axe helpers by importing/using
makeAxeBuilder and the matcher toHaveNoViolations and run an accessibility scan
against the rendered ChatPromptAttachment variants (e.g., Default,
PreviewSupported, Loading, Failed, LongFileName) in the test cases; ensure you
call makeAxeBuilder(container or root) and expect(await
builder.getViolations()).toHaveNoViolations() (or use the test runner's async
helper) so the test asserts zero a11y violations for the ChatPromptAttachment
component.
In `@packages/core/src/components/chat-user-message/chat-user-message.tsx`:
- Around line 96-106: The component currently renders both this.message and the
default slot when both exist; change the render logic in ChatUserMessage
(chat-user-message.tsx) to treat the slot as a fallback: render this.message
(inside ix-typography) when message is non-empty, otherwise render the <slot>
content. Update the JSX around the span/slot and the hasMessageContent condition
so the slot is only mounted/shown when this.message is falsy, keeping the
handleMessageSlotChange logic intact to detect slot content when used as
fallback.
In `@packages/core/src/components/chip/chip.scss`:
- Around line 124-130: Stylelint wants an empty line before the declaration
starting at "flex: 0 1 auto;" — update the chip component stylesheet by
inserting a blank line immediately above the "flex: 0 1 auto;" declaration in
the rule that contains "min-width: 0;" and the "slot::slotted(*)" block so the
declaration is separated by one empty line to satisfy the linter.
In `@packages/core/src/components/dropdown-item/dropdown-item.tsx`:
- Around line 198-201: The chevron icon in the DropdownItem component is
decorative and should be hidden from assistive tech; update the ix-icon with
name={iconChevronRightSmall} and class 'submenu-icon' (in dropdown-item.tsx /
DropdownItem) to include aria-hidden="true" so screen readers won't announce it.
In `@packages/core/src/public-api.ts`:
- Around line 33-34: The two identifiers ChatInputAttachmentLayout and
ChatPromptAttachmentStatus are type-only aliases but are exported as value
exports; change their re-exports to type-only exports by using "export type" for
ChatInputAttachmentLayout and ChatPromptAttachmentStatus in the public API so
TypeScript treats them as types (avoid value export emission and broken builds).
In `@packages/react-test-app/src/preview-examples/chat-ai-message.tsx`:
- Around line 69-71: The Sources chip in the preview is missing an accessible
label consistent with Storybook; update the <IxChip slot="sources" ...> instance
inside the ix-chat-ai-message preview to include an aria-label that matches the
visible text (e.g., aria-label="Sources") or remove any differing aria-label in
Storybook so both previews announce the same thing; modify the IxChip element
(slot="sources") to ensure aria-label accurately reflects the chip’s actual
interaction/state.
In `@packages/storybook-docs/src/stories/button-test.stories.tsx`:
- Around line 40-47: The Primary story's render (stencil) currently injects
args.variant as the button's children instead of setting the component prop;
update the Primary Story render so the <ix-button> receives
variant={args.variant} (e.g. <ix-button variant={args.variant}>...)</ix-button>
while keeping any desired children (label) separate; modify the render function
in the Primary Story to pass args.variant as the variant prop to ix-button
rather than as its inner text.
In `@packages/storybook-docs/src/stories/chat-user-message.stories.tsx`:
- Around line 64-72: The function createAttachmentDropdownItem is unused and
looks like leftover from the removed attachment overflow feature; either delete
the createAttachmentDropdownItem function declaration or, if intended to remain,
add a clear JSDoc comment explaining its purpose and where it should be used,
and export or reference it from the relevant story so it is actually consumed;
update any imports/usages to avoid dead code and run the build/lint to ensure no
unused-symbol warnings remain.
In `@packages/storybook-docs/src/stories/utils/stencil-render.ts`:
- Around line 3-9: The stencil function assumes container.firstElementChild is
non-null; add a defensive null guard in stencil(): after render(callback(args),
container) capture const el = container.firstElementChild; if el is null return
the container (or another safe HTMLElement fallback) instead of casting null,
otherwise return el as HTMLElement; update the return path in the stencil
function that references container.firstElementChild so it never casts null to
HTMLElement.
In `@testing/visual-testing/tests/chat/overview/index.html`:
- Around line 137-139: The fixture uses icon="star" while the action labels
indicate different actions (e.g., aria-label="Copy response" and other
attach/copy labels); update the icon attributes to match the action semantics
(replace icon="star" with the appropriate icon names such as "copy" for
aria-label="Copy response" and "attach" (or equivalent) for attach-related
aria-labels) so the visual baseline reflects correct icons for elements
referencing those aria-label values.
---
Outside diff comments:
In @.changeset/chip-inactive-close-button.md:
- Around line 1-6: Update the changeset description to be a clear, user-facing
PR summary: explain the problem (closable ix-chip lost interactive close button
or overlapped text when inactive), describe the user impact (broken UX for
closable/inactive chips and truncation issues), summarize what was changed in
implementation (how the close button rendering/positioning or CSS was adjusted
to remain interactive and avoid overlap with truncation), and include any
upgrade notes or compatibility considerations for consumers; ensure the message
is concise and suitable for release notes.
---
Duplicate comments:
In @.changeset/chat-user-message-component.md:
- Line 5: The changeset language frames brand-new components as incremental
changes; update the description to announce features rather than imply prior
behavior by removing "existing", "now", and "no longer" phrasing and instead
describe capabilities and intended usage of each new component: mention ix-chat
as a layout/composition component for messages and input, ix-chat-user-message
for right-aligned user messages with optional contextual actions and sent
attachments, ix-chat-ai-message for slotted AI responses with optional actions
and sources, ix-chat-prompt-attachment as a component rendered with ix-chip
supporting a compact sent variant and preview-enabled attachments that emit
attachmentClick for preview interactions only, and ix-chat-input as capable of
displaying prompt attachments and providing a follow-up slot; keep the wording
declarative and feature-focused.
In `@packages/core/src/components/chat-input/chat-input.tsx`:
- Around line 71-73: The component currently embeds hard-coded English UI
strings (see the `@Prop` placeholders like placeholder, textareaLabel, disclaimer,
character-limit messages, and the submit button aria-label) which must be
internationalized; replace these literal strings with calls into the project's
i18n utility or accept translatable props and pass localized values into the
component, updating the properties (placeholder, textareaLabel, disclaimer, any
characterLimit/charCount messages, and the submit button ariaLabel) to use i18n
keys (or injected/localized props) so all user-facing text is pulled from the
i18n layer instead of being hard-coded; ensure defaults still work by using the
i18n fallback and update usages at the other noted locations (lines referenced)
to the same pattern.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1fb80626-fae4-4e66-97d6-963a56155d72
⛔ Files ignored due to path filters (18)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamltesting/framework-tests/tests/generated/axe.tsis excluded by!**/generated/**testing/framework-tests/tests/generated/chat-ai-message-axe.spec.tsis excluded by!**/generated/**testing/framework-tests/tests/generated/chat-ai-message.spec.tsis excluded by!**/generated/**testing/framework-tests/tests/generated/chat-axe.spec.tsis excluded by!**/generated/**testing/framework-tests/tests/generated/chat-input-axe.spec.tsis excluded by!**/generated/**testing/framework-tests/tests/generated/chat-input.spec.tsis excluded by!**/generated/**testing/framework-tests/tests/generated/chat-user-message-axe.spec.tsis excluded by!**/generated/**testing/framework-tests/tests/generated/chat-user-message.spec.tsis excluded by!**/generated/**testing/framework-tests/tests/generated/chat.spec.tsis excluded by!**/generated/**testing/framework-tests/tests/generated/index.tsis excluded by!**/generated/**testing/framework-tests/tests/generated/test-ids.autogenerated.tsis excluded by!**/generated/**testing/visual-testing/__screenshots__/tests/chat/chat.e2e.ts/chat-attachment-overflow-1-chromium---classic-dark-linux.pngis excluded by!**/*.pngtesting/visual-testing/__screenshots__/tests/chat/chat.e2e.ts/chat-attachment-overflow-1-chromium---classic-light-linux.pngis excluded by!**/*.pngtesting/visual-testing/__screenshots__/tests/chat/chat.e2e.ts/chat-overview-1-chromium---classic-dark-linux.pngis excluded by!**/*.pngtesting/visual-testing/__screenshots__/tests/chat/chat.e2e.ts/chat-overview-1-chromium---classic-light-linux.pngis excluded by!**/*.pngtesting/visual-testing/__screenshots__/tests/chip/chip.e2e.ts/chip-custom-1-chromium---classic-dark-linux.pngis excluded by!**/*.pngtesting/visual-testing/__screenshots__/tests/chip/chip.e2e.ts/chip-custom-1-chromium---classic-light-linux.pngis excluded by!**/*.png
📒 Files selected for processing (101)
.agents/skills/changeset-decision/SKILL.md.agents/skills/create-changeset/SKILL.md.agents/skills/sdd/SKILL.md.changeset/chat-documentation-examples.md.changeset/chat-input-component.md.changeset/chat-user-message-component.md.changeset/chip-inactive-close-button.md.gitignoreARCHITECTURE.mdpackage.jsonpackages/angular-standalone-test-app/src/app/app.routes.tspackages/angular-standalone-test-app/src/preview-examples/chat-ai-message.htmlpackages/angular-standalone-test-app/src/preview-examples/chat-ai-message.tspackages/angular-standalone-test-app/src/preview-examples/chat-input.htmlpackages/angular-standalone-test-app/src/preview-examples/chat-input.tspackages/angular-standalone-test-app/src/preview-examples/chat-user-message.htmlpackages/angular-standalone-test-app/src/preview-examples/chat-user-message.tspackages/angular-standalone-test-app/src/preview-examples/chat.htmlpackages/angular-standalone-test-app/src/preview-examples/chat.tspackages/angular-test-app/src/app/app-routing.module.tspackages/angular-test-app/src/app/app.module.tspackages/angular-test-app/src/preview-examples/chat-ai-message.htmlpackages/angular-test-app/src/preview-examples/chat-ai-message.tspackages/angular-test-app/src/preview-examples/chat-input.htmlpackages/angular-test-app/src/preview-examples/chat-input.tspackages/angular-test-app/src/preview-examples/chat-user-message.htmlpackages/angular-test-app/src/preview-examples/chat-user-message.tspackages/angular-test-app/src/preview-examples/chat.htmlpackages/angular-test-app/src/preview-examples/chat.tspackages/angular/src/components.tspackages/angular/src/declare-components.tspackages/angular/standalone/src/components.tspackages/core/scss/mixins/_scrollbar.scsspackages/core/src/components.d.tspackages/core/src/components/chat-ai-message/chat-ai-message.scsspackages/core/src/components/chat-ai-message/chat-ai-message.tsxpackages/core/src/components/chat-ai-message/tests/chat-ai-message.ct.tspackages/core/src/components/chat-input/chat-input.scsspackages/core/src/components/chat-input/chat-input.tsxpackages/core/src/components/chat-input/chat-input.types.tspackages/core/src/components/chat-input/tests/chat-input.ct.tspackages/core/src/components/chat-prompt-attachment/chat-prompt-attachment.scsspackages/core/src/components/chat-prompt-attachment/chat-prompt-attachment.tsxpackages/core/src/components/chat-prompt-attachment/chat-prompt-attachment.types.tspackages/core/src/components/chat-prompt-attachment/tests/chat-prompt-attachment.ct.tspackages/core/src/components/chat-user-message/chat-user-message.scsspackages/core/src/components/chat-user-message/chat-user-message.tsxpackages/core/src/components/chat-user-message/mixins/action.mixin.scsspackages/core/src/components/chat-user-message/tests/chat-user-message.ct.tspackages/core/src/components/chat/chat.scsspackages/core/src/components/chat/chat.tsxpackages/core/src/components/chat/tests/chat.ct.tspackages/core/src/components/chip/chip.scsspackages/core/src/components/chip/chip.tsxpackages/core/src/components/chip/test/chip.ct.tspackages/core/src/components/dropdown-item/dropdown-item.scsspackages/core/src/components/dropdown-item/dropdown-item.tsxpackages/core/src/components/ix-playground/ix-playground.tsxpackages/core/src/public-api.tspackages/core/stencil.config.tspackages/html-test-app/src/preview-examples/chat-ai-message.htmlpackages/html-test-app/src/preview-examples/chat-input.htmlpackages/html-test-app/src/preview-examples/chat-user-message.htmlpackages/html-test-app/src/preview-examples/chat.htmlpackages/react-test-app/src/main.tsxpackages/react-test-app/src/preview-examples/chat-ai-message.tsxpackages/react-test-app/src/preview-examples/chat-input.tsxpackages/react-test-app/src/preview-examples/chat-user-message.tsxpackages/react-test-app/src/preview-examples/chat.tsxpackages/react/src/components/components.server.tspackages/react/src/components/components.tspackages/storybook-docs/.storybook/define-custom-elements.tspackages/storybook-docs/package.jsonpackages/storybook-docs/src/stories/button-test.stories.tsxpackages/storybook-docs/src/stories/chat-ai-message.stories.tsxpackages/storybook-docs/src/stories/chat-input.stories.tsxpackages/storybook-docs/src/stories/chat-prompt-attachment.stories.tsxpackages/storybook-docs/src/stories/chat-user-message.stories.tsxpackages/storybook-docs/src/stories/chat.stories.tsxpackages/storybook-docs/src/stories/utils/stencil-render.tspackages/storybook-docs/tsconfig.jsonpackages/vue-test-app/src/Root.vuepackages/vue-test-app/src/preview-examples/chat-ai-message.vuepackages/vue-test-app/src/preview-examples/chat-input.vuepackages/vue-test-app/src/preview-examples/chat-user-message.vuepackages/vue-test-app/src/preview-examples/chat.vuepackages/vue/src/components/components.tspackages/vue/src/components/ix-chat-ai-message.tspackages/vue/src/components/ix-chat-input.tspackages/vue/src/components/ix-chat-prompt-attachment.tspackages/vue/src/components/ix-chat-user-message.tspackages/vue/src/components/ix-chat.tstesting/framework-tests/__snapshots__/chat-ai-message-aria-snapshot.yamltesting/framework-tests/__snapshots__/chat-aria-snapshot.yamltesting/framework-tests/__snapshots__/chat-input-aria-snapshot.yamltesting/framework-tests/__snapshots__/chat-user-message-aria-snapshot.yamltesting/framework-tests/tests/working-with-axe.spec.tstesting/framework-tests/tests/working.spec.tstesting/visual-testing/tests/chat/chat.e2e.tstesting/visual-testing/tests/chat/overflow/index.htmltesting/visual-testing/tests/chat/overview/index.html
💤 Files with no reviewable changes (1)
- package.json
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
packages/core/src/components/chat-input/chat-input.tsx (3)
536-538:⚠️ Potential issue | 🟠 Major | ⚡ Quick winKeep the action button semantics in sync with component state.
When
state === 'processing', the icon changes to a stop glyph, but the control still exposesaria-label="Submit prompt", anddisabledSubmitButtonstops honoringdisabled/readonly. That leaves a disabled chat input with a focusable action button and the wrong accessible name.Also applies to: 601-605
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/components/chat-input/chat-input.tsx` around lines 536 - 538, The action button's enabled/aria semantics must follow the component state: update the disabledSubmitButton calculation to also disable the button when this.state === 'processing' (not just !this.canSubmit() && this.state === 'input'), and ensure submitButtonIcon and the button's accessible name/aria-label change when processing (e.g., set aria-label to "Stop processing" or similar when this.state === 'processing'); modify uses around disabledSubmitButton, submitButtonIcon, and the button render (including the code mirrored at 601-605) so the control is non-focusable/disabled and has the correct aria-label while processing. Ensure references to canSubmit(), submitButtonIcon, and this.state are updated consistently.
490-499:⚠️ Potential issue | 🟠 Major | ⚡ Quick winNarrow the mutation observer to slot-relevant changes.
This observer listens to every attribute mutation in the host subtree. Because
valueis reflected, normal typing can retriggerupdateHasFollowUp()and attachment scrollbar measurement on each keystroke, which pushes unnecessary layout work onto the main input path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/components/chat-input/chat-input.tsx` around lines 490 - 499, The mutation observer in initAttachmentMutationObserver is too broad and reacts to reflected 'value' attribute changes; narrow it so typing doesn't retrigger updateHasFollowUp() and scheduleAttachmentScrollbarUpdate(). Modify the observe call on attachmentMutationObserver to either target the specific attachment slot/container node instead of this.hostElement, or disable attribute watching and rely on childList/subtree only, or supply attributeFilter that excludes 'value' (e.g., attributes: true with attributeFilter: ['class','style', ...] but not 'value'); keep the callback and method names (updateHasFollowUp, scheduleAttachmentScrollbarUpdate, attachmentMutationObserver, initAttachmentMutationObserver) unchanged.
247-251:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDocument or de-publicize
getAssociatedFormElement().
@Method()makes this part of the element API, so/**@internal*/is misleading and the method is missing the required@sincemetadata. Either add the public JSDoc or remove@Method()if this is only for internal/test use. As per coding guidelines, "For new components, properties, methods, or events, include JSDocs with a@sincetag indicating the version number in which it was added."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/components/chat-input/chat-input.tsx` around lines 247 - 251, The method getAssociatedFormElement is currently exposed via the `@Method`() decorator but marked /** `@internal` */ without `@since` metadata; either document it as a public API by replacing the internal JSDoc with a proper public JSDoc including a `@since` tag and description, or de-publicize it by removing the `@Method`() decorator so it remains internal (keeping the implementation that returns this.formInternals.form). Update the JSDoc and/or decorator accordingly to match the intended API surface.Source: Coding guidelines
packages/core/src/components/chat-input/tests/chat-input.ct.ts (1)
29-383:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAdd the required accessibility and hydration smoke coverage.
This new
.ct.tssuite never runs an axe scan, has no'renders'test, and starts querying/interacting withix-chat-inputimmediately aftermount()without first waiting for\bhydrated\b. That misses the repo’s required baseline coverage and makes the interaction tests more race-prone. As per coding guidelines, "Every test file should include an 'accessibility' test usingmakeAxeBuilder", "Every test file should include a 'renders' test verifying basic hydration", and "Always verify components are hydrated before making further assertions usingawait expect(page.locator('ix-component')).toHaveClass(/\bhydrated\b/)."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/components/chat-input/tests/chat-input.ct.ts` around lines 29 - 383, Add the required hydration "renders" test and an accessibility/axe scan, and ensure every test waits for hydration before interacting: after mounting use await expect(page.locator('ix-chat-input')).toHaveClass(/\bhydrated\b/) (used throughout) and add a regressionTest named 'renders' that mounts the component and asserts hydrated, plus an 'accessibility' regressionTest that uses makeAxeBuilder(page).include('ix-chat-input')/analyze to run axe on the component; update other tests to wait for hydration immediately after mount before querying or interacting with the element (references: regressionTest, makeAxeBuilder, and the hydration assertion expect(...).toHaveClass(/\bhydrated\b/)).Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/core/src/components/chat-input/chat-input.tsx`:
- Around line 536-538: The action button's enabled/aria semantics must follow
the component state: update the disabledSubmitButton calculation to also disable
the button when this.state === 'processing' (not just !this.canSubmit() &&
this.state === 'input'), and ensure submitButtonIcon and the button's accessible
name/aria-label change when processing (e.g., set aria-label to "Stop
processing" or similar when this.state === 'processing'); modify uses around
disabledSubmitButton, submitButtonIcon, and the button render (including the
code mirrored at 601-605) so the control is non-focusable/disabled and has the
correct aria-label while processing. Ensure references to canSubmit(),
submitButtonIcon, and this.state are updated consistently.
- Around line 490-499: The mutation observer in initAttachmentMutationObserver
is too broad and reacts to reflected 'value' attribute changes; narrow it so
typing doesn't retrigger updateHasFollowUp() and
scheduleAttachmentScrollbarUpdate(). Modify the observe call on
attachmentMutationObserver to either target the specific attachment
slot/container node instead of this.hostElement, or disable attribute watching
and rely on childList/subtree only, or supply attributeFilter that excludes
'value' (e.g., attributes: true with attributeFilter: ['class','style', ...] but
not 'value'); keep the callback and method names (updateHasFollowUp,
scheduleAttachmentScrollbarUpdate, attachmentMutationObserver,
initAttachmentMutationObserver) unchanged.
- Around line 247-251: The method getAssociatedFormElement is currently exposed
via the `@Method`() decorator but marked /** `@internal` */ without `@since` metadata;
either document it as a public API by replacing the internal JSDoc with a proper
public JSDoc including a `@since` tag and description, or de-publicize it by
removing the `@Method`() decorator so it remains internal (keeping the
implementation that returns this.formInternals.form). Update the JSDoc and/or
decorator accordingly to match the intended API surface.
In `@packages/core/src/components/chat-input/tests/chat-input.ct.ts`:
- Around line 29-383: Add the required hydration "renders" test and an
accessibility/axe scan, and ensure every test waits for hydration before
interacting: after mounting use await
expect(page.locator('ix-chat-input')).toHaveClass(/\bhydrated\b/) (used
throughout) and add a regressionTest named 'renders' that mounts the component
and asserts hydrated, plus an 'accessibility' regressionTest that uses
makeAxeBuilder(page).include('ix-chat-input')/analyze to run axe on the
component; update other tests to wait for hydration immediately after mount
before querying or interacting with the element (references: regressionTest,
makeAxeBuilder, and the hydration assertion
expect(...).toHaveClass(/\bhydrated\b/)).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 94a290fd-805b-4df7-83b0-6c5d8859e8d8
📒 Files selected for processing (7)
packages/angular/src/components.tspackages/angular/standalone/src/components.tspackages/core/src/components.d.tspackages/core/src/components/chat-input/chat-input.tsxpackages/core/src/components/chat-input/tests/chat-input.ct.tspackages/react/src/components/components.server.tspackages/vue/src/components/ix-chat-input.ts
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/core/src/components/chat-input/tests/chat-input.ct.ts (1)
75-75: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winFollow defensive class-matching patterns to prevent partial matches.
Several class assertions do not follow the required patterns from the coding guidelines:
- Non-hyphenated classes (e.g.,
disabled) should use word boundaries:/\bdisabled\b/- Hyphenated classes (e.g.,
has-attachments,character-limit--hard) should use negative lookahead:/has-attachments(?!-)/Current patterns like
/has-attachments/would incorrectly matchhas-attachments-overflowif such a class were added later.Affected lines:
- Line 75:
/disabled/→/\bdisabled\b/- Line 244:
/character-limit--hard/→/character-limit--hard(?!-)/- Line 285:
/has-attachments/→/has-attachments(?!-)/- Line 287:
/attachments--scroll/→/attachments--scroll(?!-)/- Lines 291, 317, 325, 349:
/has-attachment-scrollbar/→/has-attachment-scrollbar(?!-)/- Line 374:
/has-follow-up/→/has-follow-up(?!-)/As per coding guidelines: "Use word boundary
\bin regex patterns to avoid false matches when checking classes likehydrated" and "Use hyphenated class name assertions with negative lookahead to avoid partial matches (e.g.,overflow-shadowmust not matchoverflow-shadow-container)".Also applies to: 244-244, 285-291, 317-317, 325-325, 349-349, 374-374
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/components/chat-input/tests/chat-input.ct.ts` at line 75, Update the class-assertion regexes to use defensive patterns: replace /disabled/ with /\bdisabled\b/ for non-hyphenated classes and replace hyphenated checks like /has-attachments/, /character-limit--hard/, /attachments--scroll/, /has-attachment-scrollbar/, /has-follow-up/ with negative-lookahead forms (e.g., /has-attachments(?!-)/, /character-limit--hard(?!-)/, etc.) in the chat-input.ct.ts tests — locate the assertions using variables like submitButton and other class checks in the file and swap the raw substring regexes for the appropriate /\b...\b/ or ...(?!-)/ forms to avoid partial matches.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/src/components/chat-input/chat-input.tsx`:
- Around line 536-541: handleKeyDown currently allows Enter to call submitPrompt
even when the component is processing; update the handler (handleKeyDown) to
early-return or skip invoking submitPrompt when this.state === 'processing' (in
addition to existing canSubmit() checks) so keyboard submission is blocked
during processing and duplicate promptSubmit events cannot be emitted.
---
Outside diff comments:
In `@packages/core/src/components/chat-input/tests/chat-input.ct.ts`:
- Line 75: Update the class-assertion regexes to use defensive patterns: replace
/disabled/ with /\bdisabled\b/ for non-hyphenated classes and replace hyphenated
checks like /has-attachments/, /character-limit--hard/, /attachments--scroll/,
/has-attachment-scrollbar/, /has-follow-up/ with negative-lookahead forms (e.g.,
/has-attachments(?!-)/, /character-limit--hard(?!-)/, etc.) in the
chat-input.ct.ts tests — locate the assertions using variables like submitButton
and other class checks in the file and swap the raw substring regexes for the
appropriate /\b...\b/ or ...(?!-)/ forms to avoid partial matches.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 19f80e9e-b956-4b46-babc-a6249274b2ea
📒 Files selected for processing (2)
packages/core/src/components/chat-input/chat-input.tsxpackages/core/src/components/chat-input/tests/chat-input.ct.ts
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/core/src/components/chat-input/chat-input.tsx (1)
415-429: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winAdd a regression test for Enter during
processing.This fixes the original bug, but the supplied component tests only cover Enter submission and the processing state separately. Please add a case that sets
state="processing", presses Enter, and asserts thatpromptSubmitis not emitted, so this path cannot regress.🧪 Suggested test shape
regressionTest( 'ix-chat-input does not emit promptSubmit on Enter while processing', async ({ mount, page }) => { await mount( '<ix-chat-input state="processing" value="Analyze alarms"></ix-chat-input>' ); await page.evaluate(() => { globalThis.__promptSubmitValue = undefined; document .querySelector('ix-chat-input') ?.addEventListener('promptSubmit', ((event: CustomEvent<string>) => { globalThis.__promptSubmitValue = event.detail; }) as EventListener); }); const textarea = page.locator('ix-chat-input').locator('textarea'); await textarea.focus(); await textarea.press('Enter'); expect(await page.evaluate(() => globalThis.__promptSubmitValue)).toBe( undefined ); } );As per coding guidelines, "Prioritize correctness, regressions, accessibility, release impact, and missing validation."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/components/chat-input/chat-input.tsx` around lines 415 - 429, Add a regression test that verifies ix-chat-input in the "processing" state ignores Enter: create a test named like "ix-chat-input does not emit promptSubmit on Enter while processing" that mounts '<ix-chat-input state="processing" value="..."></ix-chat-input>', attaches a listener to the promptSubmit event (storing event.detail on globalThis for inspection), focuses the textarea, simulates pressing Enter, and asserts that the stored global value remains undefined; this ensures the behavior guarded by handleKeyDown (which checks this.state === 'processing' and calls submitPrompt only when appropriate) does not regress.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/core/src/components/chat-input/chat-input.tsx`:
- Around line 415-429: Add a regression test that verifies ix-chat-input in the
"processing" state ignores Enter: create a test named like "ix-chat-input does
not emit promptSubmit on Enter while processing" that mounts '<ix-chat-input
state="processing" value="..."></ix-chat-input>', attaches a listener to the
promptSubmit event (storing event.detail on globalThis for inspection), focuses
the textarea, simulates pressing Enter, and asserts that the stored global value
remains undefined; this ensures the behavior guarded by handleKeyDown (which
checks this.state === 'processing' and calls submitPrompt only when appropriate)
does not regress.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 921832be-336a-4547-9aa1-5aa574f6d61d
⛔ Files ignored due to path filters (2)
testing/visual-testing/__screenshots__/tests/chat/chat.e2e.ts/chat-overview-1-chromium---classic-dark-linux.pngis excluded by!**/*.pngtesting/visual-testing/__screenshots__/tests/chat/chat.e2e.ts/chat-overview-1-chromium---classic-light-linux.pngis excluded by!**/*.png
📒 Files selected for processing (1)
packages/core/src/components/chat-input/chat-input.tsx
|



💡 What is the current behavior?
JIRA: IX-4307
🆕 What is the new behavior?
🏁 Checklist
A pull request can only be merged if all of these conditions are met (where applicable):
pnpm test)pnpm lint)pnpm build, changes pushed)👨💻 Help & support
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests