feat(dashboard): mobile nav - Dashboard first tab; Skills overflow-only
- Dashboard (Command Center) is the first mobile bottom-nav tab (before Tasks), matching the desktop sidebar. - Skills is never a top-level mobile tab; when enabled it lives only in the three-dot More sheet. - Tests updated to the new order/More-sheet behavior (74 pass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -262,7 +262,11 @@ export function MobileNavBar({
|
||||
|
||||
// Keep optional primary tabs limited to preserve touch-target width.
|
||||
// Overflowed destinations remain available in the More sheet.
|
||||
const showSkillsTopLevel = skillsEnabled;
|
||||
/*
|
||||
FNXC:Navigation 2026-06-22-01:40:
|
||||
Skills is never a top-level mobile tab; when enabled it lives only in the three-dot More overflow sheet.
|
||||
*/
|
||||
const showSkillsTopLevel = false;
|
||||
const showSkillsInMore = skillsEnabled && !showSkillsTopLevel;
|
||||
const sortedPrimaryPluginViews = pluginDashboardViews
|
||||
.filter((entry) => entry.view.placement === "primary")
|
||||
@@ -307,6 +311,24 @@ export function MobileNavBar({
|
||||
role="tablist"
|
||||
aria-label={t("nav.primaryNavAriaLabel", "Primary navigation")}
|
||||
>
|
||||
{/*
|
||||
FNXC:Navigation 2026-06-22-01:40:
|
||||
Dashboard (Command Center) is the first mobile tab, before Tasks, matching the desktop sidebar order.
|
||||
*/}
|
||||
<button
|
||||
type="button"
|
||||
className={`mobile-nav-tab${view === "command-center" ? " mobile-nav-tab--active" : ""}`}
|
||||
data-testid="mobile-nav-tab-command-center"
|
||||
role="tab"
|
||||
aria-selected={view === "command-center"}
|
||||
onClick={() => onChangeView("command-center")}
|
||||
>
|
||||
<span className="mobile-nav-tab-icon-wrapper">
|
||||
<Gauge />
|
||||
</span>
|
||||
<span className="mobile-nav-tab-label">{t("nav.commandCenter", "Dashboard")}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`mobile-nav-tab${view === "board" || view === "list" ? " mobile-nav-tab--active" : ""}`}
|
||||
@@ -399,20 +421,6 @@ export function MobileNavBar({
|
||||
)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`mobile-nav-tab${view === "command-center" ? " mobile-nav-tab--active" : ""}`}
|
||||
data-testid="mobile-nav-tab-command-center"
|
||||
role="tab"
|
||||
aria-selected={view === "command-center"}
|
||||
onClick={() => onChangeView("command-center")}
|
||||
>
|
||||
<span className="mobile-nav-tab-icon-wrapper">
|
||||
<Gauge />
|
||||
</span>
|
||||
<span className="mobile-nav-tab-label">{t("nav.commandCenter", "Command Center")}</span>
|
||||
</button>
|
||||
|
||||
{showSkillsTopLevel && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -123,18 +123,21 @@ describe("MobileNavBar", () => {
|
||||
mockViewport("mobile");
|
||||
});
|
||||
|
||||
it("renders eight tab buttons (tasks + agents + missions + chat + mailbox + command center + skills + more) when showSkillsTab is true", () => {
|
||||
it("renders seven top-level tab buttons (command center + tasks + agents + missions + chat + mailbox + more) and keeps skills in More when showSkillsTab is true", () => {
|
||||
render(<MobileNavBar {...createDefaultProps()} showSkillsTab={true} />);
|
||||
|
||||
expect(screen.getByTestId("mobile-nav-tab-command-center")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-nav-tab-tasks")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-nav-tab-agents")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-nav-tab-missions")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-nav-tab-chat")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-nav-tab-mailbox")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-nav-tab-command-center")).toBeDefined();
|
||||
expect(screen.getByTestId("mobile-nav-tab-skills")).toBeDefined();
|
||||
expect(screen.queryByTestId("mobile-nav-tab-skills")).toBeNull();
|
||||
expect(screen.queryByTestId("mobile-nav-tab-roadmaps")).toBeNull();
|
||||
expect(screen.getByTestId("mobile-nav-tab-more")).toBeDefined();
|
||||
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
expect(screen.getByTestId("mobile-more-item-skills")).toBeDefined();
|
||||
});
|
||||
|
||||
it("does not render legacy roadmaps tab", () => {
|
||||
@@ -142,23 +145,24 @@ describe("MobileNavBar", () => {
|
||||
expect(screen.queryByTestId("mobile-nav-tab-roadmaps")).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps skills available without rendering legacy roadmaps destinations", () => {
|
||||
it("keeps skills available in More without rendering legacy roadmaps destinations", () => {
|
||||
render(<MobileNavBar {...createDefaultProps()} showSkillsTab={true} experimentalFeatures={{}} />);
|
||||
|
||||
expect(screen.getByTestId("mobile-nav-tab-skills")).toBeDefined();
|
||||
expect(screen.queryByTestId("mobile-nav-tab-skills")).toBeNull();
|
||||
expect(screen.queryByTestId("mobile-nav-tab-roadmaps")).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
expect(screen.getByTestId("mobile-more-item-skills")).toBeDefined();
|
||||
expect(screen.queryByTestId("mobile-more-item-roadmaps")).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps skills top-level regardless of legacy roadmaps view value", () => {
|
||||
it("keeps skills in the More sheet regardless of legacy roadmaps view value", () => {
|
||||
render(<MobileNavBar {...createDefaultProps()} view="board" showSkillsTab={true} experimentalFeatures={{}} />);
|
||||
|
||||
expect(screen.getByTestId("mobile-nav-tab-skills")).toBeDefined();
|
||||
expect(screen.queryByTestId("mobile-nav-tab-skills")).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
expect(screen.queryByTestId("mobile-more-item-skills")).toBeNull();
|
||||
expect(screen.getByTestId("mobile-more-item-skills")).toBeDefined();
|
||||
});
|
||||
|
||||
it("does not render skills tab when showSkillsTab is false", () => {
|
||||
@@ -189,7 +193,9 @@ describe("MobileNavBar", () => {
|
||||
expect(screen.getByTestId("mobile-nav-tab-mailbox").querySelector(".mobile-nav-tab-badge")?.textContent).toBe("7");
|
||||
sevenTabRender.unmount();
|
||||
|
||||
const eightTabRender = render(
|
||||
// Skills is never a top-level tab, so enabling it keeps the top-level column count at seven
|
||||
// and the skills destination, plus its active view, lives in the More sheet.
|
||||
const skillsEnabledRender = render(
|
||||
<MobileNavBar
|
||||
{...createDefaultProps()}
|
||||
showSkillsTab={true}
|
||||
@@ -199,10 +205,13 @@ describe("MobileNavBar", () => {
|
||||
mailboxPendingApprovalCount={1}
|
||||
/>,
|
||||
);
|
||||
expectUniformMobileNavColumns(eightTabRender.container, 8);
|
||||
expect(screen.getByTestId("mobile-nav-tab-skills").className).toContain("mobile-nav-tab--active");
|
||||
expectUniformMobileNavColumns(skillsEnabledRender.container, 7);
|
||||
expect(screen.queryByTestId("mobile-nav-tab-skills")).toBeNull();
|
||||
expect(screen.getByTestId("mobile-nav-tab-more").className).toContain("mobile-nav-tab--active");
|
||||
expect(screen.getByTestId("mobile-nav-tab-mailbox").querySelector(".mobile-nav-tab-badge")?.textContent).toBe("99+");
|
||||
eightTabRender.unmount();
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
expect(screen.getByTestId("mobile-more-item-skills")).toBeDefined();
|
||||
skillsEnabledRender.unmount();
|
||||
|
||||
const pluginVariantRender = render(
|
||||
<MobileNavBar
|
||||
@@ -216,7 +225,7 @@ describe("MobileNavBar", () => {
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
expectUniformMobileNavColumns(pluginVariantRender.container, 8);
|
||||
expectUniformMobileNavColumns(pluginVariantRender.container, 7);
|
||||
expect(screen.queryByTestId("mobile-nav-tab-plugin-fusion-plugin-spacing-check-wide")).toBeNull();
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
expect(screen.getByTestId("mobile-more-item-plugin-fusion-plugin-spacing-check-wide")).toBeDefined();
|
||||
@@ -415,9 +424,9 @@ describe("MobileNavBar", () => {
|
||||
expect(props.onChangeView).toHaveBeenCalledWith("mailbox");
|
||||
});
|
||||
|
||||
it("places Command Center after Mailbox while primary plugins stay More-only", () => {
|
||||
it("places Command Center as the first mobile tab while primary plugins stay More-only", () => {
|
||||
const props = createDefaultProps();
|
||||
render(
|
||||
const { container } = render(
|
||||
<MobileNavBar
|
||||
{...props}
|
||||
view="board"
|
||||
@@ -434,8 +443,9 @@ describe("MobileNavBar", () => {
|
||||
|
||||
const mailboxTab = screen.getByTestId("mobile-nav-tab-mailbox");
|
||||
const commandCenterTab = screen.getByTestId("mobile-nav-tab-command-center");
|
||||
expect(mailboxTab.compareDocumentPosition(commandCenterTab) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
expect(commandCenterTab.previousElementSibling).toBe(mailboxTab);
|
||||
// Command Center is now the first top-level tab, before Tasks.
|
||||
expect(commandCenterTab).toBe(container.querySelector(".mobile-nav-bar > .mobile-nav-tab"));
|
||||
expect(commandCenterTab.previousElementSibling).toBeNull();
|
||||
expect(mailboxTab.querySelector(".mobile-nav-tab-badge")?.textContent).toBe("3");
|
||||
expect(screen.queryByTestId("mobile-nav-tab-plugin-fusion-plugin-compound-engineering-compound-engineering")).toBeNull();
|
||||
|
||||
@@ -539,22 +549,27 @@ describe("MobileNavBar", () => {
|
||||
expect(screen.queryByLabelText("Unread chat response")).toBeNull();
|
||||
});
|
||||
|
||||
it("skills tab calls onChangeView with 'skills'", () => {
|
||||
it("skills More-sheet item calls onChangeView with 'skills'", () => {
|
||||
const props = createDefaultProps();
|
||||
render(<MobileNavBar {...props} view="board" showSkillsTab={true} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-skills"));
|
||||
expect(screen.queryByTestId("mobile-nav-tab-skills")).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
fireEvent.click(screen.getByTestId("mobile-more-item-skills"));
|
||||
expect(props.onChangeView).toHaveBeenCalledWith("skills");
|
||||
});
|
||||
|
||||
it("skills tab is active when view is 'skills'", () => {
|
||||
it("marks the More tab active when view is 'skills' since skills lives only in More", () => {
|
||||
render(<MobileNavBar {...createDefaultProps()} view="skills" showSkillsTab={true} />);
|
||||
expect(screen.getByTestId("mobile-nav-tab-skills").className).toContain("mobile-nav-tab--active");
|
||||
expect(screen.queryByTestId("mobile-nav-tab-skills")).toBeNull();
|
||||
expect(screen.getByTestId("mobile-nav-tab-more").className).toContain("mobile-nav-tab--active");
|
||||
});
|
||||
|
||||
it("skills tab is not active when view is 'board'", () => {
|
||||
it("does not mark the More tab active for skills when view is 'board'", () => {
|
||||
render(<MobileNavBar {...createDefaultProps()} view="board" showSkillsTab={true} />);
|
||||
expect(screen.getByTestId("mobile-nav-tab-skills").className).not.toContain("mobile-nav-tab--active");
|
||||
expect(screen.queryByTestId("mobile-nav-tab-skills")).toBeNull();
|
||||
expect(screen.getByTestId("mobile-nav-tab-more").className).not.toContain("mobile-nav-tab--active");
|
||||
});
|
||||
|
||||
it("opens and toggles the more sheet", () => {
|
||||
@@ -620,8 +635,8 @@ describe("MobileNavBar", () => {
|
||||
expect(screen.queryByTestId("mobile-more-item-roadmaps")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders Compound Engineering primary plugin only in the More sheet while Command Center follows Mailbox", () => {
|
||||
render(
|
||||
it("renders Compound Engineering primary plugin only in the More sheet while Command Center is the first tab", () => {
|
||||
const { container } = render(
|
||||
<MobileNavBar
|
||||
{...createDefaultProps()}
|
||||
pluginDashboardViews={[
|
||||
@@ -633,7 +648,9 @@ describe("MobileNavBar", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("mobile-nav-tab-command-center").previousElementSibling).toBe(screen.getByTestId("mobile-nav-tab-mailbox"));
|
||||
// Command Center is the first top-level tab, before Tasks.
|
||||
expect(screen.getByTestId("mobile-nav-tab-command-center")).toBe(container.querySelector(".mobile-nav-bar > .mobile-nav-tab"));
|
||||
expect(screen.getByTestId("mobile-nav-tab-command-center").previousElementSibling).toBeNull();
|
||||
expect(screen.queryByTestId("mobile-nav-tab-plugin-fusion-plugin-compound-engineering-compound-engineering")).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||
|
||||
Reference in New Issue
Block a user