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.tsModify:
frontend/src/navigation/nav-resolver.spec.ts(temporary seed only if needed, then move tests)Test:
frontend/src/navigation/breadcrumb-resolver.spec.tsStep 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.tsxTest:
frontend/src/components/layouts/TopNav.breadcrumb.spec.tsxStep 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 crumbDashboard/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.tscovers static, dynamic, unknown/fallback, normalization, query/hash. - Component (integration in top bar):
TopNav.breadcrumb.spec.tsxcovers 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).