Moves phases 08–11 execution artifacts from .planning/phases/ to
.planning/milestones/v0.2-phases/ to keep .planning/phases/ clean
for the next milestone.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create `frontend/src/components/ui/AppIcon.vue` — the single source of truth for all SVG icon paths used in DocuVault. This is the foundation for the CODE-05 SVG migration that happens in Wave 5.
Purpose: Eliminate ~66 inline <svg> blocks across 29 files by centralizing the icon paths into one map. All existing SVGs use the same outline/stroke style (fill="none" stroke="currentColor" viewBox="0 0 24 24" with stroke-linecap="round" stroke-linejoin="round" stroke-width="2") — AppIcon matches this exactly so the migration is a drop-in replacement.
Output: AppIcon.vue (Options API, ~30 named icons including the dual-path cog) + a Vitest unit test covering name→path rendering, class forwarding, dual-path handling, and unknown-name warning.
Project Vitest layout (verified from existing tests at frontend/src/components/folders/tests/FolderBreadcrumb.test.js):
Tests live in tests/ alongside the component
Uses @vue/test-utilsmount
Run command: cd frontend && npm run test
Task 1: Create AppIcon.test.js with failing tests for the contract
frontend/src/components/ui/__tests__/AppIcon.test.js
- frontend/src/components/folders/__tests__/FolderBreadcrumb.test.js (test style — vitest + @vue/test-utils mount)
- .planning/phases/10-ux-interaction/10-PATTERNS.md (AppIcon target structure)
- .planning/phases/10-ux-interaction/10-RESEARCH.md §"Component Inventory §1: SVG Audit" (full icon name list)
- Test 1: `renders
Create `frontend/src/components/ui/__tests__/AppIcon.test.js`. Import AppIcon from `../AppIcon.vue` (file does NOT exist yet — tests will fail on import, that is correct). Use `import { describe, it, expect, vi } from 'vitest'` and `import { mount } from '@vue/test-utils'`. Follow the test-file style in `frontend/src/components/folders/__tests__/FolderBreadcrumb.test.js`. Implement all 6 tests above as concrete assertions. For console.warn, set `import.meta.env.DEV = true` if needed via a `vi.stubGlobal` or rely on Vitest's default DEV=true environment. Do NOT create AppIcon.vue yet — this is the RED phase.
cd frontend && npm run test -- --run AppIcon
Expected: test file is collected; all 6 tests FAIL with "Cannot resolve module ../AppIcon.vue" or "Failed to resolve component". This is the RED state.
- File `frontend/src/components/ui/__tests__/AppIcon.test.js` exists
- File contains exactly 6 `it(...)` blocks inside one `describe('AppIcon', ...)` block
- Running `cd frontend && npm run test -- --run AppIcon` shows 6 failing tests (RED — AppIcon.vue does not exist yet)
- Tests use `mount` from `@vue/test-utils` (not `shallowMount`)
- Tests use `vi.spyOn(console, 'warn')` or `vi.fn()` to assert the dev warning
Test file exists with 6 failing tests describing the AppIcon contract.
Task 2: Implement AppIcon.vue with the full ICON_PATHS map
frontend/src/components/ui/AppIcon.vue
- frontend/src/components/ui/__tests__/AppIcon.test.js (the failing tests from Task 1)
- frontend/src/components/ui/AppSpinner.vue (single-purpose SVG analog)
- .planning/phases/10-ux-interaction/10-PATTERNS.md §"AppIcon.vue" (full target structure)
- .planning/phases/10-ux-interaction/10-RESEARCH.md §"Component Inventory §1: SVG Audit" (complete d-attr values)
- Component is Options API per CLAUDE.md (project convention)
- Component name: `AppIcon`
- `inheritAttrs: false`
- Props: `name: { type: String, required: true }`
- Computed `resolvedPaths()` returns `ICON_PATHS[this.name] ?? null`; if null AND `import.meta.env.DEV`, calls `console.warn('[AppIcon] Unknown icon name: "' + this.name + '"')`
- Template: `` containing either a `` rendering `` (for cog), else a single ``
- All `` elements MUST include `stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="..."`
Create `frontend/src/components/ui/AppIcon.vue` using the Options API template from `10-PATTERNS.md §"AppIcon.vue"`. Declare a module-scope `const ICON_PATHS = { ... }` with EXACTLY these 31 keys (28 single + 1 dual + 2 added):
Single-path keys (use d-values exactly from 10-RESEARCH.md §Component Inventory §1):
`plus`, `folder`, `folderMove`, `pencil`, `trash`, `share`, `document`, `fileDoc`, `chevronRight`, `chevronDown`, `tag`, `inbox`, `cloud`, `shield`, `logout`, `home`, `users`, `chartBar`, `clipboardList`, `upload`, `x`, `checkCircle`, `exclamationCircle`, `warning`, `copy`, `check`, `checkMark`, `refresh`, `pencilEdit`, `lightBulb`, `search`, `dots`.
Dual-path key (Array value, two strings — see RESEARCH.md):
`cog: ['M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z', 'M15 12a3 3 0 11-6 0 3 3 0 016 0z']`
For the `search` key, use d = `'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0'` (per D-05 Heroicons outline search). For `dots`, use the stroke replacement d = `'M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z'` (Pattern: per D-09 outline-only convention — replaces FolderRow fill-based dots).
Use the exact template structure from 10-PATTERNS.md AppIcon.vue section (lines 47-73 of the analog block). NO comments inside the file describing what the code does (CLAUDE.md non-negotiable). Do NOT add a `default` prop value for name; required is the contract.
cd frontend && npm run test -- --run AppIcon
Expected: All 6 tests from Task 1 PASS (GREEN).
- File `frontend/src/components/ui/AppIcon.vue` exists
- File contains module-scope `const ICON_PATHS` with at least 30 keys (verify: `grep -c "':" frontend/src/components/ui/AppIcon.vue` returns ≥ 30 or count keys via parse)
- File contains `inheritAttrs: false`
- File contains `import.meta.env.DEV` reference
- File contains `Array.isArray(resolvedPaths)` check
- Running `cd frontend && npm run test -- --run AppIcon` exits 0 with 6 passing tests (GREEN)
- File uses Options API (`export default { name: 'AppIcon', ... }`) per CLAUDE.md, NOT `<script setup>`
- `cog` key value is an Array of length 2
AppIcon.vue exists with full icon map; all 6 tests pass.
- `cd frontend && npm run test -- --run AppIcon` exits 0
- `grep -E "ICON_PATHS\\s*=" frontend/src/components/ui/AppIcon.vue` returns 1 match
- `grep -E "inheritAttrs:\\s*false" frontend/src/components/ui/AppIcon.vue` returns 1 match
- `grep -E "Array\\.isArray\\(resolvedPaths\\)" frontend/src/components/ui/AppIcon.vue` returns 1 match
- `grep -c "'.*':.*'M" frontend/src/components/ui/AppIcon.vue` returns ≥ 28 (single-path icon count, excluding the array `cog`)
<success_criteria>
AppIcon.vue is the single source of truth for icon paths. Wave 5's SVG migration (10-12) can replace every inline <svg> block with <AppIcon name="..." class="..." /> and the visual result matches the existing rendering pixel-for-pixel (same fill/stroke/viewBox/path conventions).
</success_criteria>
Create `.planning/phases/10-ux-interaction/10-01-SUMMARY.md` when done with: what was built, the final ICON_PATHS key count, and any deviations from the planned key list.