Files
kite/.planning/phases/10-ux-interaction/10-03-PLAN.md
T

170 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 10-ux-interaction
plan: 03
type: execute
wave: 0
depends_on: []
files_modified:
- frontend/src/components/ui/BreadcrumbBar.vue
- frontend/src/components/ui/__tests__/BreadcrumbBar.test.js
autonomous: true
requirements: [UX-12]
must_haves:
truths:
- "BreadcrumbBar renders the rootLabel as the first clickable segment when showRoot is true"
- "BreadcrumbBar does NOT render the root segment when showRoot is false (admin/settings views)"
- "The last segment is always rendered as plain non-clickable text"
- "Segments without an id render as plain text (admin static segments)"
- "Clicking an intermediate segment emits navigate(segment.id); clicking root emits navigate(null)"
- ">4 segments collapse to first + ellipsis + last two"
artifacts:
- path: "frontend/src/components/ui/BreadcrumbBar.vue"
provides: "Shared breadcrumb component used by file manager, cloud, admin, settings views"
- path: "frontend/src/components/ui/__tests__/BreadcrumbBar.test.js"
provides: "BreadcrumbBar unit tests"
key_links:
- from: "BreadcrumbBar.vue"
to: "AppIcon.vue"
via: "import for chevronRight separator"
pattern: "<AppIcon name=\"chevronRight\""
---
<objective>
Create `frontend/src/components/ui/BreadcrumbBar.vue` — a shared breadcrumb component that generalizes `FolderBreadcrumb.vue` to also serve admin, settings, and topics views. The existing `FolderBreadcrumb.vue` will be deleted in Wave 1 (plan 10-06) after BreadcrumbBar is wired everywhere.
Purpose: Per D-11/D-12/D-13, every view computes its own `segments` array. Admin views (e.g. `Admin Users`) and settings (`Settings Account`) need static segments without a "Home" root. File manager and cloud views use folder store breadcrumb data with a "Home" or "Cloud" root.
Output: `BreadcrumbBar.vue` (Options API, props segments/rootLabel/showRoot, emits navigate) + Vitest unit tests adapted from the existing FolderBreadcrumb tests.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@CLAUDE.md
@.planning/phases/10-ux-interaction/10-CONTEXT.md
@.planning/phases/10-ux-interaction/10-RESEARCH.md
@.planning/phases/10-ux-interaction/10-PATTERNS.md
@frontend/src/components/folders/FolderBreadcrumb.vue
@frontend/src/components/folders/__tests__/FolderBreadcrumb.test.js
<interfaces>
Per D-11, D-12, D-13 (CONTEXT.md):
- Props:
- `segments` (Array, default []) — array of `{ id?, label }` objects
- Items without `id` render as plain non-clickable text (admin static segments)
- Last item is ALWAYS plain non-clickable text regardless of `id`
- `rootLabel` (String, default 'Home') — text for the root button when showRoot=true
- `showRoot` (Boolean, default true) — when false, omit the root button entirely
- Emits: `navigate` with `segment.id` (intermediate click) or `null` (root click)
- Last segment: plain `<span>`, no click handler
- >4 segments: collapse pattern `[first, {id:'ellipsis', label:'…'}, ...last_two]`
Reference implementation (FolderBreadcrumb.vue):
- Uses `<script setup>` (we will use Options API per CLAUDE.md for THIS new component — exception in PATTERNS.md says BreadcrumbBar may use script setup since it replaces a setup component; pick Options API anyway for consistency with EmptyState/AppIcon)
- Renders `<nav aria-label>` > `<ol>` > `<li>` segments
Segment shape change:
- FolderBreadcrumb uses `{ id, name }` — BreadcrumbBar uses `{ id?, label }`
- Wave 1 plans will map `{id, name}``{id, label: name}` at the call sites
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Create BreadcrumbBar.test.js with failing tests</name>
<files>frontend/src/components/ui/__tests__/BreadcrumbBar.test.js</files>
<read_first>
- frontend/src/components/folders/__tests__/FolderBreadcrumb.test.js (reference test patterns)
- frontend/src/components/folders/FolderBreadcrumb.vue (current behavior reference)
- .planning/phases/10-ux-interaction/10-PATTERNS.md §"BreadcrumbBar.vue"
</read_first>
<behavior>
- Test 1: `renders rootLabel button when showRoot=true` — mount with rootLabel='Home', segments=[], assert wrapper.find('button').text() === 'Home'
- Test 2: `does NOT render root button when showRoot=false` — mount with showRoot=false, segments=[{label: 'Users'}], assert wrapper.findAll('button').length === 0
- Test 3: `clicking root button emits navigate(null)` — mount default with empty segments, click first button, assert emitted.navigate[0] === [null]
- Test 4: `last segment renders as a non-clickable <span>` — mount with segments=[{id:'a', label:'A'}, {id:'b', label:'B'}], assert text contains 'B' AND wrapper.findAll('button').filter(b => b.text() === 'B').length === 0
- Test 5: `clicking intermediate segment emits navigate(segment.id)` — mount with segments=[{id:'r1', label:'Root'}, {id:'f1', label:'Test'}], click the 'Root' button, assert emitted.navigate[0] === ['r1']
- Test 6: `segments without id render as plain text even when intermediate` — mount with segments=[{label:'Admin'}, {label:'Users'}], showRoot=false, assert NO buttons exist (both render as spans)
- Test 7: `>4 segments collapse to first + ellipsis + last two` — mount with 5 segments, assert text contains the first segment label, '…', and the last 2 labels but NOT segments 2-3
- Test 8: `rootLabel defaults to "Home"` — mount with no rootLabel prop, assert first button text === 'Home'
- Test 9: `custom rootLabel "Cloud" renders correctly` — mount with rootLabel='Cloud', assert first button text === 'Cloud'
- Test 10: `renders chevronRight separator between segments` — mount with segments=[{id:'a', label:'A'}, {id:'b', label:'B'}] and stubs: { AppIcon: true }, assert AppIcon stubs are present (count >= 1)
</behavior>
<action>
Create `frontend/src/components/ui/__tests__/BreadcrumbBar.test.js`. Adapt the test style from `frontend/src/components/folders/__tests__/FolderBreadcrumb.test.js`. Stub AppIcon via `global: { stubs: { AppIcon: true } }`. Use the segment shape `{ id, label }` (NOT `{ id, name }`). Write all 10 tests above. Tests fail until Task 2.
</action>
<verify>
<automated>cd frontend && npm run test -- --run BreadcrumbBar</automated>
Expected: 10 tests collected; all fail (RED).
</verify>
<acceptance_criteria>
- File `frontend/src/components/ui/__tests__/BreadcrumbBar.test.js` exists with 10 `it(...)` blocks
- Tests use segment shape `{ id, label }` exclusively
- Running `cd frontend && npm run test -- --run BreadcrumbBar` shows 10 failing tests
</acceptance_criteria>
<done>10 failing tests in place describing the BreadcrumbBar contract.</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Implement BreadcrumbBar.vue</name>
<files>frontend/src/components/ui/BreadcrumbBar.vue</files>
<read_first>
- frontend/src/components/ui/__tests__/BreadcrumbBar.test.js (failing tests)
- frontend/src/components/folders/FolderBreadcrumb.vue (reference template — lines 1-44)
- .planning/phases/10-ux-interaction/10-PATTERNS.md §"BreadcrumbBar.vue"
</read_first>
<behavior>
- Options API component `BreadcrumbBar`, registers `AppIcon` from `./AppIcon.vue`
- Props: `segments` (Array, default []), `rootLabel` (String, default 'Home'), `showRoot` (Boolean, default true)
- Emits: `['navigate']`
- Computed `visibleSegments`: if `segments.length > 4` returns `[segments[0], { id: 'ellipsis', label: '…' }, ...segments.slice(-2)]`; else returns `segments`
- Template renders `<nav aria-label="Navigation"><ol class="flex items-center gap-1 text-sm flex-wrap">`
- When `showRoot`: render the root `<li><button @click="$emit('navigate', null)">{{ rootLabel }}</button></li>`
- For each segment in visibleSegments with index:
- Separator `<li aria-hidden><AppIcon name="chevronRight" class="w-3 h-3 text-gray-400" /></li>` shown only when there is something to the left (showRoot OR idx > 0)
- If `segment.id === 'ellipsis'`: `<li><span>…</span></li>`
- If `idx === visibleSegments.length - 1`: `<li><span class="text-gray-900 font-medium">{{ segment.label }}</span></li>`
- Else if `segment.id`: `<li><button @click="$emit('navigate', segment.id)" class="text-indigo-600 hover:underline font-medium">{{ segment.label }}</button></li>`
- Else: `<li><span class="text-gray-500">{{ segment.label }}</span></li>` (no id, no click)
</behavior>
<action>
Create `frontend/src/components/ui/BreadcrumbBar.vue` using Options API. Import AppIcon from `./AppIcon.vue`. Implement exactly the template described in the behavior block. Match the FolderBreadcrumb visual style (text-indigo-600, hover:underline for clickable; text-gray-900 font-medium for the last segment; text-gray-400 for separator). Use `<AppIcon name="chevronRight" class="w-3 h-3 text-gray-400" />` for separators (NO inline svg — this is the new icon centralization paradigm). Skip the separator before the first segment when `!showRoot`. No comments inside the file.
</action>
<verify>
<automated>cd frontend && npm run test -- --run BreadcrumbBar</automated>
Expected: all 10 tests PASS.
</verify>
<acceptance_criteria>
- File `frontend/src/components/ui/BreadcrumbBar.vue` exists
- File imports AppIcon from `./AppIcon.vue`
- File contains `name: 'BreadcrumbBar'`
- Template contains `<AppIcon name="chevronRight"` (separator uses AppIcon, not inline svg)
- Template contains `aria-label="Navigation"` (or similar) on the `<nav>`
- All 10 tests pass
- File uses Options API
</acceptance_criteria>
<done>BreadcrumbBar.vue implemented; 10 tests green.</done>
</task>
</tasks>
<verification>
- `cd frontend && npm run test -- --run BreadcrumbBar` exits 0 with 10 passing tests
- `grep -E "name:\\s*'BreadcrumbBar'" frontend/src/components/ui/BreadcrumbBar.vue` returns 1 match
- `grep -E "rootLabel" frontend/src/components/ui/BreadcrumbBar.vue` returns ≥ 2 matches (prop + template)
- `grep -E "showRoot" frontend/src/components/ui/BreadcrumbBar.vue` returns ≥ 2 matches
- `grep -E "<AppIcon name=\"chevronRight\"" frontend/src/components/ui/BreadcrumbBar.vue` returns ≥ 1 match
</verification>
<success_criteria>
BreadcrumbBar.vue is ready to replace FolderBreadcrumb.vue everywhere. Wave 1 plan 10-06 will swap the import in StorageBrowser.vue, update FileManagerView/CloudFolderView to map breadcrumb to `{id, label}`, wire admin/settings views to pass static segments, and delete `FolderBreadcrumb.vue`.
</success_criteria>
<output>
Create `.planning/phases/10-ux-interaction/10-03-SUMMARY.md` when done.
</output>