Top Bar Breadcrumb Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add a desktop-only top bar breadcrumb that is generated from current route paths with centralized override metadata, while preserving existing navigation behavior.

Architecture: Introduce a pure breadcrumb resolver in the navigation layer that converts pathname into cumulative segments, applies pattern-based label overrides, and falls back to token formatting for unknown routes. Integrate resolver output into TopNav using useLocation() + Ant Design Breadcrumb, rendering only on lg breakpoint. Keep route definitions in App.tsx unchanged.

Tech Stack: React 19, TypeScript, React Router, Ant Design Breadcrumb, Vitest + Testing Library.


Phase 0: File map and responsibilities

  • frontend/src/navigation/breadcrumb-resolver.ts (create)

    • Pure resolver and route-pattern matching logic.
    • Centralized breadcrumb override registry.
    • Exposes resolveBreadcrumb(pathname) and related types.
  • frontend/src/navigation/breadcrumb-resolver.spec.ts (modify)

    • Unit tests for resolver behavior (static, param, fallback, normalization, query/hash).
  • frontend/src/components/layouts/TopNav.tsx (modify)

    • Call resolver from current location.
    • Render desktop breadcrumb beside logo.
    • Keep existing menu/hamburger/user behavior unchanged.
  • frontend/src/components/layouts/TopNav.breadcrumb.spec.tsx (create)

    • Component tests for responsive visibility and clickability semantics.

Task 1: Build breadcrumb resolver (TDD)

Files:

  • Create: frontend/src/navigation/breadcrumb-resolver.ts

  • Modify: frontend/src/navigation/nav-resolver.spec.ts (temporary seed only if needed, then move tests)

  • Test: frontend/src/navigation/breadcrumb-resolver.spec.ts

  • Step 1: Create failing resolver tests (core cases)

import { describe, expect, it } from 'vitest';
import { resolveBreadcrumb } from '@/navigation/breadcrumb-resolver';

describe('resolveBreadcrumb', () => {
  it('returns Dashboard for /dashboard', () => {
    expect(resolveBreadcrumb('/dashboard')).toEqual([
      { title: 'Dashboard', path: '/dashboard', clickable: false },
    ]);
  });

  it('resolves static + parameterized pond path', () => {
    expect(resolveBreadcrumb('/farms/ponds/123')).toEqual([
      { title: 'Farms', path: '/farms', clickable: true },
      { title: 'Farms / Ponds', path: '/farms/ponds', clickable: true },
      { title: 'Chi tiết', path: '/farms/ponds/123', clickable: false },
    ]);
  });
});
  • Step 2: Run test to verify it fails

Run: bun --cwd frontend run test -- src/navigation/breadcrumb-resolver.spec.ts
Expected: FAIL with module/function not found.

  • Step 3: Implement minimal resolver with registry + matching
export interface BreadcrumbItem {
  title: string;
  path: string;
  clickable: boolean;
}

const BREADCRUMB_OVERRIDES: Array<{ pattern: string; title: string }> = [
  { pattern: '/dashboard', title: 'Dashboard' },
  { pattern: '/farms/ponds', title: 'Farms / Ponds' },
  { pattern: '/farms/ponds/:id', title: 'Chi tiết' },
  { pattern: '/system/device-profiles', title: 'Device Profiles' },
];

export const resolveBreadcrumb = (pathname: string): BreadcrumbItem[] => {
  // normalize path, build cumulative segments,
  // resolve override first, fallback to token formatting,
  // mark last crumb non-clickable
  return [];
};
  • Step 4: Expand tests for spec behavior
it('normalizes trailing slash', () => {
  expect(resolveBreadcrumb('/devices/list/')).toEqual(resolveBreadcrumb('/devices/list'));
});

it('ignores query and hash', () => {
  expect(resolveBreadcrumb('/system/device-profiles?tab=1#abc')).toEqual(
    resolveBreadcrumb('/system/device-profiles'),
  );
});

it('falls back for unknown paths', () => {
  expect(resolveBreadcrumb('/abc/def')[1]?.title).toBe('Def');
});

it('returns empty array for empty or invalid path', () => {
  expect(resolveBreadcrumb('')).toEqual([]);
});
  • Step 5: Run resolver tests and make them pass

Run: bun --cwd frontend run test -- src/navigation/breadcrumb-resolver.spec.ts
Expected: PASS.

  • Step 6: Commit
git add frontend/src/navigation/breadcrumb-resolver.ts frontend/src/navigation/breadcrumb-resolver.spec.ts
git commit -m "feat(frontend): add pathname breadcrumb resolver with route overrides"

Task 2: Integrate breadcrumb into TopNav (TDD)

Files:

  • Modify: frontend/src/components/layouts/TopNav.tsx

  • Test: frontend/src/components/layouts/TopNav.breadcrumb.spec.tsx

  • Step 1: Create failing TopNav breadcrumb tests

import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { describe, expect, it, vi } from 'vitest';
import { TopNav } from '@/components/layouts/TopNav';

vi.mock('antd', async () => {
  const actual = await vi.importActual<any>('antd');
  return {
    ...actual,
    Grid: {
      ...actual.Grid,
      useBreakpoint: () => ({ lg: true, md: true }),
    },
  };
});

describe('TopNav breadcrumb', () => {
  it('renders breadcrumb on desktop', () => {
    render(
      <MemoryRouter initialEntries={['/farms/ponds/123']}>
        <TopNav />
      </MemoryRouter>,
    );

    expect(screen.getByText('Farms / Ponds')).toBeInTheDocument();
    expect(screen.getByText('Chi tiết')).toBeInTheDocument();
  });
});
  • Step 2: Run test to verify it fails

Run: bun --cwd frontend run test -- src/components/layouts/TopNav.breadcrumb.spec.tsx
Expected: FAIL because TopNav has no breadcrumb rendering.

  • Step 3: Implement TopNav breadcrumb integration
import { Breadcrumb, Grid } from 'antd';
import { useLocation, useNavigate } from 'react-router-dom';
import { resolveBreadcrumb } from '@/navigation/breadcrumb-resolver';

const { useBreakpoint } = Grid;

const { pathname } = useLocation();
const crumbs = resolveBreadcrumb(pathname);

{screens.lg && crumbs.length > 0 && (
  <Breadcrumb
    items={crumbs.map((item) => ({
      title: item.title,
      href: item.clickable ? item.path : undefined,
    }))}
  />
)}
  • Step 4: Add tests for non-desktop and clickable semantics
it('hides breadcrumb on non-desktop breakpoints', () => {
  // mock useBreakpoint => { lg: false, md: true }
  // assert breadcrumb text not present
});

it('marks only final item as non-clickable', () => {
  // assert intermediate items render links
  // assert final item renders plain text
});
  • Step 5: Run TopNav tests and make them pass

Run: bun --cwd frontend run test -- src/components/layouts/TopNav.breadcrumb.spec.tsx
Expected: PASS.

  • Step 6: Commit
git add frontend/src/components/layouts/TopNav.tsx frontend/src/components/layouts/TopNav.breadcrumb.spec.tsx
git commit -m "feat(frontend): show desktop breadcrumb in top navigation"

Task 3: Full verification and regression checks

Files:

  • Verify only (no new files required)

  • Step 1: Run targeted resolver + TopNav tests together

Run:
bun --cwd frontend run test -- src/navigation/breadcrumb-resolver.spec.ts src/components/layouts/TopNav.breadcrumb.spec.tsx
Expected: PASS.

  • Step 2: Run relevant existing frontend tests for route/nav safety

Run:
bun --cwd frontend run test -- src/App.redirect.spec.tsx src/navigation/nav-resolver.spec.ts
Expected: PASS, no routing regression.

  • Step 3: Run lint/format for frontend

Run:
bun --cwd frontend run lint:fix && bun --cwd frontend run format
Expected: no remaining lint/format violations.

  • Step 4: Manual desktop verification checklist

Run app: bun --cwd frontend run dev

Validate on desktop (lg and above):

  • /dashboard => single crumb Dashboard
  • /farms/ponds => readable trail
  • /farms/ponds/123 => last item display-only
  • /system/device-profiles => override label displayed

Validate on mobile/tablet (< lg):

  • Breadcrumb hidden

  • Existing hamburger/menu/user dropdown behavior unchanged

  • Step 5: Final commit

git add frontend/src/navigation/breadcrumb-resolver.ts frontend/src/navigation/breadcrumb-resolver.spec.ts frontend/src/components/layouts/TopNav.tsx frontend/src/components/layouts/TopNav.breadcrumb.spec.tsx
git commit -m "test(frontend): verify breadcrumb resolver and topnav responsiveness"

Test Strategy Summary

  • Unit (pure logic): breadcrumb-resolver.spec.ts covers static, dynamic, unknown/fallback, normalization, query/hash.
  • Component (integration in top bar): TopNav.breadcrumb.spec.tsx covers breakpoint visibility and clickability semantics.
  • Regression: existing route/nav tests remain green.
  • Manual UX: desktop visible, mobile/tablet hidden, layout stable with long labels.

Acceptance Checklist

  • Breadcrumb appears next to top bar logo on desktop only.
  • Breadcrumb hidden on mobile/tablet.
  • Breadcrumb trail generated from current route pathname.
  • Centralized overrides applied where configured.
  • Unknown paths render readable fallback labels (no crash).
  • Existing top navigation behavior unchanged outside breadcrumb addition.

Self-review

  • Spec coverage complete for Goal, Architecture, Resolver Behavior, UX rules, Error Handling, Unit/Component tests, Manual verification, and Acceptance Criteria.
  • No placeholders like TODO/TBD; each task includes explicit files, commands, and expected outcomes.
  • Naming consistency maintained (resolveBreadcrumb, BreadcrumbItem, TopNav.breadcrumb.spec.tsx).