FN-6765: fix mobile nav mailbox duplication
Keep Mailbox as a single top-level entry while Todos stays in overflow navigation. - Remove the duplicate Mailbox action from the mobile More sheet while preserving its primary-tab badges. - Cover desktop, tablet, and mobile navigation placement expectations for Todos and Mailbox. - Update dashboard docs to describe compact Header and mobile bottom-nav mailbox indicators. Files changed: docs/dashboard-guide.md | 2 +- .../mobile-feature-access-regression.test.tsx | 3 +- packages/dashboard/app/components/MobileNavBar.tsx | 22 +++--------- .../app/components/__tests__/Header.test.tsx | 19 +++++++---- .../app/components/__tests__/MobileNavBar.test.tsx | 39 ++++++++++++++++++---- 5 files changed, 52 insertions(+), 33 deletions(-) Fusion-Task-Id: FN-6765 Fusion-Task-Lineage: e32f271e-7dab-43e0-b824-da598641aa77
This commit is contained in:
@@ -334,7 +334,7 @@ Mailbox view shows inbox/outbox communication threads and unread state.
|
||||
- reply rows in the mailbox modal can expand inline to show the replied-to message context for easier thread reading
|
||||
- mailbox now includes an **Approvals** tab with pending and history filters (`approved` / `denied` / `completed`), approval detail context, and inline approve/deny actions for pending requests
|
||||
- in the **Agents** tab, the agent selector now includes **All agents**, which shows one combined agent-to-agent stream (with sender + recipient labels); selecting a specific agent still shows Inbox/Outbox subtabs
|
||||
- mailbox entry points now show unread/pending indicators: the desktop Header mailbox toggle shows a pending-approval dot first or an unread dot when unread mail exists without pending approvals, while Header overflow + Mobile mailbox entry points continue to surface mailbox badges/dots
|
||||
- mailbox entry points now show unread/pending indicators: the desktop/tablet Header mailbox toggle shows a pending-approval dot first or an unread dot when unread mail exists without pending approvals, the mobile bottom-nav Mailbox tab carries the mobile badges/dots, and the compact Header actions overflow keeps a Mailbox entry only when the mobile bottom nav is disabled
|
||||
- approval lifecycle SSE events (`approval:requested`, `approval:updated`, `approval:decided`) trigger mailbox approvals refresh without manual reload
|
||||
- when a task newly enters `awaiting-approval`, the app shows a persistent approval banner above project content with an **Open Mailbox** CTA; dismissals are remembered per approval item until that item advances or a different one arrives
|
||||
- when a task first transitions into `done`, the dashboard shows a one-time **Enjoying Fusion?** GitHub star prompt in the project view; clicking **Star on GitHub** or dismissing the card marks it shown in browser `localStorage`, so it does not reappear on reload or later task completions
|
||||
|
||||
@@ -170,7 +170,8 @@ describe("Mobile Feature Access Regression Guard", () => {
|
||||
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
|
||||
expect(screen.getByTestId("mobile-more-item-mailbox")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-nav-tab-mailbox")).toBeDefined();
|
||||
expect(screen.queryByTestId("mobile-more-item-mailbox")).toBeNull();
|
||||
expect(screen.getByTestId("mobile-more-item-git")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-more-item-terminal")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-more-item-files")).toBeDefined();
|
||||
|
||||
@@ -138,7 +138,6 @@ export function MobileNavBar({
|
||||
keyboardOpen = false,
|
||||
onOpenSettings,
|
||||
onOpenActivityLog,
|
||||
onOpenMailbox,
|
||||
mailboxUnreadCount = 0,
|
||||
mailboxPendingApprovalCount = 0,
|
||||
chatHasUnreadResponse = false,
|
||||
@@ -375,6 +374,11 @@ export function MobileNavBar({
|
||||
</button>
|
||||
|
||||
|
||||
{/*
|
||||
FNXC:Navigation 2026-06-19-12:30:
|
||||
Mailbox is a top-level mobile tab only and must not be duplicated in the three-dot More sheet; Todos lives only in the three-dot overflow/More menu, never the main tab list.
|
||||
Keep unread and pending-approval indicators on this surviving Mailbox tab so removing the More-sheet duplicate does not hide mailbox state.
|
||||
*/}
|
||||
<button
|
||||
type="button"
|
||||
className={`mobile-nav-tab${view === "mailbox" ? " mobile-nav-tab--active" : ""}`}
|
||||
@@ -470,22 +474,6 @@ export function MobileNavBar({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="mobile-more-item"
|
||||
data-testid="mobile-more-item-mailbox"
|
||||
onClick={() => handleMoreAction(onOpenMailbox)}
|
||||
>
|
||||
<Mail />
|
||||
<span>{t("nav.mailbox", "Mailbox")}</span>
|
||||
{mailboxUnreadCount > 0 && (
|
||||
<span className="mobile-more-item-badge mobile-more-item-badge--unread">{formatCount(mailboxUnreadCount)}</span>
|
||||
)}
|
||||
{mailboxPendingApprovalCount > 0 && (
|
||||
<span className="mobile-more-item-badge">{formatCount(mailboxPendingApprovalCount)}</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="mobile-more-item"
|
||||
|
||||
@@ -540,14 +540,19 @@ describe("Header", () => {
|
||||
});
|
||||
|
||||
describe("todos navigation", () => {
|
||||
it("shows Todos only in More views on desktop when enabled", () => {
|
||||
renderHeader({ onChangeView: noop, onOpenTodos: vi.fn(), todosEnabled: true }, "desktop");
|
||||
expect(screen.queryByTestId("todos-toggle-btn")).toBeNull();
|
||||
for (const tier of ["desktop", "tablet"] as const) {
|
||||
it(`shows Todos only in More views and Mailbox only top-level on ${tier}`, () => {
|
||||
renderHeader({ onChangeView: noop, onOpenTodos: vi.fn(), todosEnabled: true }, tier);
|
||||
|
||||
fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
|
||||
expect(screen.getAllByText("Todos")).toHaveLength(1);
|
||||
expect(screen.getByTestId("view-overflow-todos")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByTestId("todos-toggle-btn")).toBeNull();
|
||||
expect(screen.getByTitle("Mailbox view")).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
|
||||
expect(screen.getAllByText("Todos")).toHaveLength(1);
|
||||
expect(screen.getByTestId("view-overflow-todos")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("view-overflow-mailbox")).toBeNull();
|
||||
});
|
||||
}
|
||||
|
||||
it("does not show Todos entry in More views when disabled", () => {
|
||||
renderHeader({ onChangeView: noop, onOpenTodos: vi.fn(), todosEnabled: false }, "desktop");
|
||||
|
||||
@@ -121,6 +121,30 @@ describe("MobileNavBar", () => {
|
||||
expect(onOpenTodos).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Mailbox is a primary tab and is not duplicated in the More sheet", () => {
|
||||
render(<MobileNavBar {...createDefaultProps()} mailboxUnreadCount={3} mailboxPendingApprovalCount={1} />);
|
||||
|
||||
expect(screen.getByTestId("mobile-nav-tab-mailbox")).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
expect(screen.queryByTestId("mobile-more-item-mailbox")).toBeNull();
|
||||
});
|
||||
|
||||
it("Todos lives only in the More sheet, never a primary tab", () => {
|
||||
render(
|
||||
<MobileNavBar
|
||||
{...createDefaultProps()}
|
||||
onOpenTodos={vi.fn()}
|
||||
experimentalFeatures={{ todoView: true }}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId("mobile-nav-tab-todos")).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
expect(screen.getByTestId("mobile-more-item-todos")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows secrets in More and routes to secrets view", () => {
|
||||
const props = createDefaultProps();
|
||||
render(<MobileNavBar {...props} />);
|
||||
@@ -304,15 +328,15 @@ describe("MobileNavBar", () => {
|
||||
expect(badge?.textContent).toBe("5");
|
||||
});
|
||||
|
||||
it("shows matching mailbox unread badge in the More sheet", () => {
|
||||
it("keeps mailbox unread badge on the primary tab only", () => {
|
||||
render(<MobileNavBar {...createDefaultProps()} mailboxUnreadCount={7} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
const tabBadge = screen.getByTestId("mobile-nav-tab-mailbox").querySelector(".mobile-nav-tab-badge");
|
||||
expect(tabBadge).toBeDefined();
|
||||
expect(tabBadge?.textContent).toBe("7");
|
||||
|
||||
const moreItemBadge = screen.getByTestId("mobile-more-item-mailbox").querySelector(".mobile-more-item-badge");
|
||||
expect(moreItemBadge).toBeDefined();
|
||||
expect(moreItemBadge?.className).toContain("mobile-more-item-badge--unread");
|
||||
expect(moreItemBadge?.textContent).toBe("7");
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
expect(screen.queryByTestId("mobile-more-item-mailbox")).toBeNull();
|
||||
});
|
||||
|
||||
it("tasks tab calls onChangeView with 'board' when coming from a non-tasks view", () => {
|
||||
@@ -423,7 +447,8 @@ describe("MobileNavBar", () => {
|
||||
render(<MobileNavBar {...createDefaultProps()} />);
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
|
||||
expect(screen.getByTestId("mobile-more-item-mailbox")).toBeDefined();
|
||||
expect(screen.queryByTestId("mobile-more-item-mailbox")).toBeNull();
|
||||
expect(screen.getByTestId("mobile-nav-tab-mailbox")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-more-item-activity")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-more-item-git")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-more-item-terminal")).toBeDefined();
|
||||
|
||||
Reference in New Issue
Block a user