diff --git a/src/app/(repairs-app)/repairs-app/layout.tsx b/src/app/(repairs-app)/repairs-app/layout.tsx
index 579cb5f..f628158 100644
--- a/src/app/(repairs-app)/repairs-app/layout.tsx
+++ b/src/app/(repairs-app)/repairs-app/layout.tsx
@@ -6,11 +6,13 @@ import { RepairsTabBar } from "./tab-bar";
/**
* Repairs phone-app shell.
*
- * The fourth instance of the per-route-group PWA pattern already running
- * here (`/m` boss phone, `/counter` till, `/rep` rep visits), so it
- * reuses `MobileAuthGate` and gets its own manifest + service worker.
+ * The fifth instance of the per-route-group PWA pattern already running
+ * here (`/m` boss phone, `/counter` till, `/manager` orders, `/rep` rep
+ * visits), so it reuses `MobileAuthGate` and gets its own manifest +
+ * service worker.
*
- * Deliberately not in the sidebar — matching `/m`, `/counter` and `/rep`.
+ * Deliberately not in the sidebar — matching `/m`, `/counter`, `/manager`
+ * and `/rep`.
* The repairs person installs this on their home screen and never
* navigates to it from a menu.
*/
diff --git a/src/app/(repairs-app)/repairs-app/tab-bar.test.tsx b/src/app/(repairs-app)/repairs-app/tab-bar.test.tsx
index 9acdd04..6b5517c 100644
--- a/src/app/(repairs-app)/repairs-app/tab-bar.test.tsx
+++ b/src/app/(repairs-app)/repairs-app/tab-bar.test.tsx
@@ -1,6 +1,6 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
-import { RepairsTabBar } from "./tab-bar";
+import { RepairsTabBar, pickActiveTabHref } from "./tab-bar";
vi.mock("next/navigation", () => ({
usePathname: () => "/repairs-app",
@@ -13,8 +13,33 @@ describe("RepairsTabBar", () => {
expect(link).toHaveAttribute("href", "/repairs-app");
});
- it("has no dead controls — every element is a link", () => {
+ it("has no dead controls — every control is a link with a real destination", () => {
render();
expect(screen.queryAllByRole("button")).toHaveLength(0);
+ const links = screen.getAllByRole("link");
+ expect(links.length).toBeGreaterThan(0);
+ for (const link of links) {
+ const href = link.getAttribute("href");
+ expect(href).toBeTruthy();
+ expect(href).not.toBe("#");
+ }
+ });
+
+ it("marks only the most specific tab as the current page", () => {
+ // The Phase-4 shape, proven without shipping the Phase-4 tab.
+ const tabs = [
+ { href: "/repairs-app", label: "Repairs" },
+ { href: "/repairs-app/giellie", label: "Ask Giellie" },
+ ];
+ expect(pickActiveTabHref("/repairs-app", tabs)).toBe("/repairs-app");
+ expect(pickActiveTabHref("/repairs-app/book-in", tabs)).toBe("/repairs-app");
+ expect(pickActiveTabHref("/repairs-app/giellie", tabs)).toBe("/repairs-app/giellie");
+ expect(pickActiveTabHref("/repairs-app/giellie/chat", tabs)).toBe("/repairs-app/giellie");
+ });
+
+ it("does not claim a lookalike route as the Repairs tab", () => {
+ const tabs = [{ href: "/repairs-app" }];
+ expect(pickActiveTabHref("/repairs-approval", tabs)).toBeUndefined();
+ expect(pickActiveTabHref("/repairs", tabs)).toBeUndefined();
});
});
diff --git a/src/app/(repairs-app)/repairs-app/tab-bar.tsx b/src/app/(repairs-app)/repairs-app/tab-bar.tsx
index 2f2dfcc..47a1c6a 100644
--- a/src/app/(repairs-app)/repairs-app/tab-bar.tsx
+++ b/src/app/(repairs-app)/repairs-app/tab-bar.tsx
@@ -14,8 +14,36 @@ import { usePathname } from "next/navigation";
*/
const TABS = [{ href: "/repairs-app", label: "Repairs" }];
+/**
+ * The active tab is the one whose href is the LONGEST match for the
+ * current path.
+ *
+ * Phase 4 puts the chat tab at `/repairs-app/giellie`, which also starts
+ * with `/repairs-app` — so a plain `startsWith` would let both tabs claim to be
+ * the current page. Two elements carrying `aria-current="page"` is a broken
+ * state, and colour would be the only thing telling them apart, which
+ * this app must never rely on.
+ *
+ * A path matches a tab when it is the tab's href exactly, or when it
+ * continues past that href on a segment boundary: `/repairs-app/book-in` matches
+ * `/repairs-app`, `/repairs-approval` does not.
+ */
+export function pickActiveTabHref(
+ pathname: string,
+ tabs: { href: string }[] = TABS
+): string | undefined {
+ let best: string | undefined;
+ for (const tab of tabs) {
+ const { href } = tab;
+ if (pathname !== href && !pathname.startsWith(href + "/")) continue;
+ if (best === undefined || href.length > best.length) best = href;
+ }
+ return best;
+}
+
export function RepairsTabBar() {
const pathname = usePathname();
+ const activeHref = pickActiveTabHref(pathname, TABS);
return (