feat(FN-1956): merge fusion/fn-1956

This commit is contained in:
gsxdsm
2026-04-17 15:24:37 -07:00
parent 0433ce5dd8
commit 6fbf2871e9
5 changed files with 31 additions and 96 deletions

View File

@@ -320,6 +320,15 @@ export function MailboxModal({
)}
</div>
<div className="mailbox-header-actions">
<button
className="btn-sm btn-primary"
onClick={handleOpenCompose}
title="Compose message"
data-testid="mailbox-header-compose"
>
<MessageSquare size={14} />
<span>Compose</span>
</button>
{activeTab === "inbox" && unreadCount > 0 && (
<button
className="btn-sm btn-secondary"
@@ -631,17 +640,6 @@ export function MailboxModal({
)}
</div>
{/* Compose FAB (only when viewing inbox/outbox, not in detail view or agents tab) */}
{!selectedMessage && !showComposer && activeTab !== "agents" && (
<button
className="mailbox-compose-fab"
onClick={handleOpenCompose}
title="Compose message"
data-testid="mailbox-compose-fab"
>
<MessageSquare size={20} />
</button>
)}
</div>
</div>
);

View File

@@ -379,6 +379,15 @@ export function MailboxView({
)}
</div>
<div className="mailbox-header-actions">
<button
className="btn-sm btn-primary"
onClick={handleOpenCompose}
title="Compose message"
data-testid="mailbox-header-compose"
>
<MessageSquare size={14} />
<span>Compose</span>
</button>
{activeTab === "inbox" && unreadCount > 0 && (
<button
className="btn-sm btn-secondary"
@@ -694,17 +703,6 @@ export function MailboxView({
)}
</div>
{/* Compose FAB (only when viewing inbox/outbox, not in detail view or agents tab) */}
{!selectedMessage && !showComposer && activeTab !== "agents" && (
<button
className="mailbox-compose-fab"
onClick={handleOpenCompose}
title="Compose message"
data-testid="mailbox-compose-fab"
>
<MessageSquare size={20} />
</button>
)}
</div>
);
}

View File

@@ -329,18 +329,18 @@ describe("MailboxModal", () => {
});
});
it("shows compose FAB in inbox tab", async () => {
it("shows compose button in header on inbox tab", async () => {
render(<MailboxModal {...defaultProps} />);
await waitFor(() => {
expect(screen.getByTestId("mailbox-compose-fab")).toBeDefined();
expect(screen.getByTestId("mailbox-header-compose")).toBeDefined();
});
});
it("does not show compose FAB in agents tab", async () => {
it("shows compose button in header on agents tab", async () => {
render(<MailboxModal {...defaultProps} />);
fireEvent.click(screen.getByTestId("mailbox-tab-agents"));
await waitFor(() => {
expect(screen.queryByTestId("mailbox-compose-fab")).toBeNull();
expect(screen.getByTestId("mailbox-header-compose")).toBeDefined();
});
});
@@ -516,9 +516,6 @@ describe("MailboxModal", () => {
expect(mailboxMobileSection).toContain("max-width: 100%;");
expect(mailboxMobileSection).toContain(".mailbox-modal .mailbox-agents");
expect(mailboxMobileSection).toContain("min-height: 200px;");
expect(mailboxMobileSection).toContain(".mailbox-modal .mailbox-compose-fab");
expect(mailboxMobileSection).toContain("bottom: 16px;");
expect(mailboxMobileSection).toContain("right: 16px;");
expect(mailboxMobileSection).toContain(".mailbox-modal .mailbox-empty");
expect(mailboxMobileSection).toContain("padding: 32px 12px;");
});
@@ -564,21 +561,6 @@ describe("MailboxModal", () => {
expect(blockMatch![1]).not.toContain("color: white");
});
it("mailbox compose FAB uses theme-aware tokens", () => {
const blockMatch = css.match(/\.mailbox-compose-fab\s*\{([^}]*)\}/);
expect(blockMatch).toBeTruthy();
expect(blockMatch![1]).toContain("var(--fab-bg)");
expect(blockMatch![1]).toContain("var(--fab-text)");
expect(blockMatch![1]).not.toContain("background: var(--todo)");
expect(blockMatch![1]).not.toContain("color: white");
// Check hover state
const hoverMatch = css.match(/\.mailbox-compose-fab:hover\s*\{([^}]*)\}/);
expect(hoverMatch).toBeTruthy();
expect(hoverMatch![1]).toContain("var(--fab-bg)");
expect(hoverMatch![1]).toContain("var(--fab-text)");
});
it("mission event type error uses CSS custom properties", () => {
const blockMatch = css.match(/\.mission-event__type--error\s*\{([^}]*)\}/);
expect(blockMatch).toBeTruthy();

View File

@@ -361,7 +361,7 @@ describe("MailboxView", () => {
});
});
it("shows compose FAB in inbox tab", async () => {
it("shows compose button in header on inbox tab", async () => {
mockFetchInbox.mockResolvedValue({
messages: [],
unreadCount: 0,
@@ -370,11 +370,11 @@ describe("MailboxView", () => {
render(<MailboxView {...defaultProps} />);
await waitFor(() => {
expect(screen.getByTestId("mailbox-compose-fab")).toBeDefined();
expect(screen.getByTestId("mailbox-header-compose")).toBeDefined();
});
});
it("does not show compose FAB in agents tab", async () => {
it("shows compose button in header on agents tab", async () => {
mockFetchInbox.mockResolvedValue({
messages: [],
unreadCount: 0,
@@ -388,7 +388,7 @@ describe("MailboxView", () => {
});
await waitFor(() => {
expect(screen.queryByTestId("mailbox-compose-fab")).toBeNull();
expect(screen.getByTestId("mailbox-header-compose")).toBeDefined();
});
});
@@ -446,7 +446,7 @@ describe("MailboxView", () => {
});
});
it("shows MessageComposer with agents when clicking compose FAB from inbox tab", async () => {
it("shows MessageComposer with agents when clicking compose button from header", async () => {
mockFetchInbox.mockResolvedValue({
messages: [],
unreadCount: 0,
@@ -454,14 +454,14 @@ describe("MailboxView", () => {
render(<MailboxView {...defaultProps} />);
// Verify compose FAB is visible in inbox tab
// Verify compose button is visible in header
await waitFor(() => {
expect(screen.getByTestId("mailbox-compose-fab")).toBeDefined();
expect(screen.getByTestId("mailbox-header-compose")).toBeDefined();
});
// Click compose FAB
// Click compose button
await act(async () => {
fireEvent.click(screen.getByTestId("mailbox-compose-fab"));
fireEvent.click(screen.getByTestId("mailbox-header-compose"));
});
// Verify MessageComposer is shown
@@ -516,7 +516,6 @@ describe("MailboxView", () => {
expect(mailboxMobileSection).toContain(".mailbox-view .mailbox-header");
expect(mailboxMobileSection).toContain(".mailbox-view .mailbox-tabs");
expect(mailboxMobileSection).toContain(".mailbox-view .mailbox-content");
expect(mailboxMobileSection).toContain(".mailbox-view .mailbox-compose-fab");
expect(mailboxMobileSection).toContain(".mailbox-view .mailbox-empty");
});
@@ -542,13 +541,6 @@ describe("MailboxView", () => {
// Content should have padding-bottom accounting for mobile nav
expect(contentRuleMatch![0]).toContain("padding-bottom");
// FAB should account for mobile nav height
expect(mailboxMobileSection).toContain(".mailbox-view .mailbox-compose-fab");
const fabRuleMatch = mailboxMobileSection.match(/\.mailbox-view\s+\.mailbox-compose-fab\s*\{[^}]*\}/);
expect(fabRuleMatch).toBeTruthy();
expect(fabRuleMatch![0]).toContain("--mobile-nav-height");
expect(fabRuleMatch![0]).toContain("safe-area-inset-bottom");
expect(fabRuleMatch![0]).toContain("--standalone-bottom-gap");
});
it("renders structural elements that mobile CSS targets", async () => {

View File

@@ -28409,31 +28409,6 @@ html .column.drag-over * {
gap: 2px;
}
/* Compose FAB */
.mailbox-compose-fab {
position: absolute;
bottom: 24px;
right: 24px;
width: 44px;
height: 44px;
border-radius: 50%;
border: none;
background: var(--fab-bg);
color: var(--fab-text);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
transition: background-color 0.15s, transform 0.15s;
z-index: 10;
}
.mailbox-compose-fab:hover {
background: color-mix(in srgb, var(--fab-bg) 80%, var(--fab-text));
transform: scale(1.05);
}
/* Skeleton loading */
.mailbox-skeleton {
display: flex;
@@ -28565,11 +28540,6 @@ html .column.drag-over * {
min-height: 200px;
}
.mailbox-modal .mailbox-compose-fab {
bottom: 16px;
right: 16px;
}
.mailbox-modal .mailbox-empty {
padding: 32px 12px;
}
@@ -28638,11 +28608,6 @@ html .column.drag-over * {
min-height: 200px;
}
.mailbox-view .mailbox-compose-fab {
bottom: calc(var(--mobile-nav-height) + env(safe-area-inset-bottom, 0px) + var(--standalone-bottom-gap) + var(--space-lg));
right: var(--space-lg);
}
.mailbox-view .mailbox-empty {
padding: 32px 12px;
}