FN-8820: contain mobile message structure picker
Keep long structure selections within the shared Messages composer on narrow layouts. - Make structure picker controls shrinkable and full-width in mobile stacks. - Cover long-label attachment behavior and shared full-page/modal callers. - Add a patch changeset for the mobile containment fix. Files changed: .../fn-8820-message-composer-mobile-width.md | 7 +++++ .../dashboard/app/components/MessageComposer.css | 17 +++++++++++ .../__tests__/MessageComposer.mobile-width.test.ts | 34 ++++++++++++++++++++++ .../components/__tests__/MessageComposer.test.tsx | 14 +++++++++ 4 files changed, 72 insertions(+) Fusion-Task-Id: FN-8820 Fusion-Task-Lineage: a66d51a6-6d92-4402-938c-698c09177583 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8820-message-composer-mobile-width.md
Normal file
7
.changeset/fn-8820-message-composer-mobile-width.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Keep Messages structure selection within narrow mobile composers.
|
||||||
|
category: fix
|
||||||
|
dev: Shared MessageComposer sizing now contains long structure labels in full-page and modal Messages.
|
||||||
@@ -15,7 +15,24 @@
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:MessageComposerMobileWidth 2026-08-07-03:22:
|
||||||
|
Long native-structure kind and label values must not give the shared Messages picker an intrinsic width that escapes a narrow composer. The shared select remains shrinkable at every breakpoint; mobile explicitly fills its stacked field so both full-page and modal callers inherit containment.
|
||||||
|
*/
|
||||||
|
.message-composer-structure-controls .message-composer-select {
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
.message-composer-field--structures,
|
||||||
|
.message-composer-structure-controls {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.message-composer-structure-list > li {
|
.message-composer-structure-list > li {
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { loadComponentCss, readAppFile } from "../../test/cssFixture";
|
||||||
|
|
||||||
|
function getRuleBlock(css: string, selector: string) {
|
||||||
|
const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
return css.match(new RegExp(`${escapedSelector}\\s*\\{([^}]*)\\}`))?.[1] ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("MessageComposer mobile structure picker width", () => {
|
||||||
|
const css = loadComponentCss("MessageComposer.css");
|
||||||
|
|
||||||
|
it("keeps the long-label select shrinkable in the shared desktop base rule", () => {
|
||||||
|
const selectRule = getRuleBlock(css, ".message-composer-structure-controls .message-composer-select");
|
||||||
|
|
||||||
|
expect(selectRule).toMatch(/min-width\s*:\s*0/);
|
||||||
|
expect(selectRule).toMatch(/width\s*:\s*100%/);
|
||||||
|
expect(selectRule).toMatch(/max-width\s*:\s*100%/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fills the stacked mobile field without a caller-specific Messages patch", () => {
|
||||||
|
const mobileCss = css.slice(css.indexOf("@media (max-width: 768px)"));
|
||||||
|
const mobileFieldRule = getRuleBlock(mobileCss, ".message-composer-field--structures,\n .message-composer-structure-controls");
|
||||||
|
|
||||||
|
expect(mobileFieldRule).toMatch(/min-width\s*:\s*0/);
|
||||||
|
expect(mobileFieldRule).toMatch(/width\s*:\s*100%/);
|
||||||
|
expect(mobileFieldRule).toMatch(/max-width\s*:\s*100%/);
|
||||||
|
|
||||||
|
for (const caller of ["MailboxView.tsx", "MailboxModal.tsx"]) {
|
||||||
|
const source = readAppFile(`components/${caller}`);
|
||||||
|
expect(source).toContain('from "./MessageComposer"');
|
||||||
|
expect(source).toContain("nativeStructureCandidates={nativeStructureCandidates}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -147,6 +147,20 @@ describe("MessageComposer", () => {
|
|||||||
expect(screen.getByText("No structures available")).toBeInTheDocument();
|
expect(screen.getByText("No structures available")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("preserves a long structure label when attaching from the shared picker", () => {
|
||||||
|
const longLabel = "A deliberately long structure label that must remain available without changing attachment metadata";
|
||||||
|
render(<MessageComposer {...defaultProps} nativeStructureCandidates={[
|
||||||
|
{ ref: { kind: "mission-with-a-deliberately-long-kind", id: "M-long" }, label: longLabel },
|
||||||
|
]} />);
|
||||||
|
|
||||||
|
const picker = screen.getByTestId("message-composer-attach-structure");
|
||||||
|
expect(picker).not.toBeDisabled();
|
||||||
|
expect(picker).toHaveTextContent(`mission-with-a-deliberately-long-kind: ${longLabel}`);
|
||||||
|
fireEvent.change(picker, { target: { value: "0" } });
|
||||||
|
|
||||||
|
expect(screen.getByTestId("message-composer-attached-structures")).toHaveTextContent(longLabel);
|
||||||
|
});
|
||||||
|
|
||||||
it("disables send button when content is empty", () => {
|
it("disables send button when content is empty", () => {
|
||||||
render(<MessageComposer {...defaultProps} agents={mockAgents} />);
|
render(<MessageComposer {...defaultProps} agents={mockAgents} />);
|
||||||
const sendBtn = screen.getByTestId("message-composer-send");
|
const sendBtn = screen.getByTestId("message-composer-send");
|
||||||
|
|||||||
Reference in New Issue
Block a user