docs(10): create phase plan — 12 plans, 6 waves, UX-01..14 + CODE-05
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
c84dcb77c1
commit
d3d3f711eb
@@ -0,0 +1,406 @@
|
||||
---
|
||||
phase: 10-ux-interaction
|
||||
plan: 06
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: [10-02, 10-03, 10-04, 10-05]
|
||||
files_modified:
|
||||
- frontend/src/components/storage/StorageBrowser.vue
|
||||
- frontend/src/views/FileManagerView.vue
|
||||
- frontend/src/views/CloudFolderView.vue
|
||||
- frontend/src/components/folders/FolderBreadcrumb.vue
|
||||
- frontend/src/components/folders/__tests__/FolderBreadcrumb.test.js
|
||||
- frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js
|
||||
autonomous: true
|
||||
requirements: [UX-02, UX-01, UX-10, UX-12]
|
||||
must_haves:
|
||||
truths:
|
||||
- "StorageBrowser shows 5+ animated skeleton rows when loading=true (no Loading… text)"
|
||||
- "Empty file list renders <EmptyState> with the appropriate icon/headline/subtext per context (root, folder, search)"
|
||||
- "BreadcrumbBar replaces FolderBreadcrumb in StorageBrowser; FolderBreadcrumb.vue is deleted in this plan"
|
||||
- "FileManagerView maps foldersStore.breadcrumb to [{id, label}] before passing as segments"
|
||||
- "CloudFolderView maps its breadcrumb to [{id, label}] before passing as segments"
|
||||
- "FileManagerView toast wiring fires on document delete and document move success/failure"
|
||||
artifacts:
|
||||
- path: "frontend/src/components/storage/StorageBrowser.vue"
|
||||
provides: "Updated with skeleton + EmptyState + BreadcrumbBar"
|
||||
- path: "frontend/src/views/FileManagerView.vue"
|
||||
provides: "Updated breadcrumb mapping + toast call sites"
|
||||
- path: "frontend/src/views/CloudFolderView.vue"
|
||||
provides: "Updated breadcrumb mapping"
|
||||
key_links:
|
||||
- from: "StorageBrowser.vue"
|
||||
to: "BreadcrumbBar.vue"
|
||||
via: "import + <BreadcrumbBar :segments=\"breadcrumb\" />"
|
||||
pattern: "import BreadcrumbBar"
|
||||
- from: "FileManagerView.vue"
|
||||
to: "useToastStore"
|
||||
via: "show() called in doMove and doDeleteDoc"
|
||||
pattern: "useToastStore"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Wire the Wave 0 foundation components into `StorageBrowser.vue`, `FileManagerView.vue`, and `CloudFolderView.vue`. This plan completes UX-02 (skeleton rows), the StorageBrowser parts of UX-01 (EmptyState), the file-manager parts of UX-12 (BreadcrumbBar swap and FolderBreadcrumb deletion), and the FileManagerView parts of UX-10 (toast call sites).
|
||||
|
||||
Per CLAUDE.md "no dead code", FolderBreadcrumb.vue and its test file are deleted in the SAME commit as the BreadcrumbBar swap.
|
||||
|
||||
Output:
|
||||
- StorageBrowser: 5-row skeleton (UX-02), three EmptyState variants (root/folder/search), import swap FolderBreadcrumb → BreadcrumbBar
|
||||
- FileManagerView: breadcrumb `{id, name}` → `{id, label}` mapping, toast.show() on doMove + doDeleteDoc success/error
|
||||
- CloudFolderView: breadcrumb mapping + BreadcrumbBar with rootLabel='Cloud'
|
||||
- FolderBreadcrumb.vue + FolderBreadcrumb.test.js DELETED
|
||||
- StorageBrowser.skeleton.test.js stub promoted to real 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/storage/StorageBrowser.vue
|
||||
@frontend/src/views/FileManagerView.vue
|
||||
@frontend/src/views/CloudFolderView.vue
|
||||
@frontend/src/components/folders/FolderBreadcrumb.vue
|
||||
@frontend/src/components/ui/EmptyState.vue
|
||||
@frontend/src/components/ui/BreadcrumbBar.vue
|
||||
@frontend/src/stores/toast.js
|
||||
|
||||
<interfaces>
|
||||
**StorageBrowser already uses `<script setup>`** — modifications stay in that style. Skeleton classes from RESEARCH.md §Pitfall 7:
|
||||
- Container: `grid grid-cols-[2rem_1fr_6rem_8rem_6rem] gap-3 px-4 py-2.5 items-center border-b border-gray-100`
|
||||
- Col 1: `<div class="w-7 h-7 bg-gray-100 rounded-lg animate-pulse"></div>`
|
||||
- Col 2: `<div class="h-4 bg-gray-100 rounded animate-pulse w-2/3"></div>`
|
||||
- Col 3: `<div class="h-3 bg-gray-100 rounded animate-pulse hidden md:block"></div>`
|
||||
- Col 4: `<div class="h-3 bg-gray-100 rounded animate-pulse hidden sm:block"></div>`
|
||||
- Col 5: `<div class="w-14 h-3 bg-gray-100 rounded animate-pulse"></div>`
|
||||
|
||||
**EmptyState contexts in StorageBrowser** (from RESEARCH.md §Component Inventory §3):
|
||||
| Condition | icon | headline | subtext |
|
||||
|-----------|------|----------|---------|
|
||||
| Root: !currentFolderId && lists empty && !searchQuery | folder | "Nothing here yet" | "Create a folder or upload your first file to get started." |
|
||||
| In folder: currentFolderId && lists empty && !searchQuery | document | "This folder is empty" | "Upload files above or create a sub-folder." |
|
||||
| Search: searchQuery && lists empty | search | `No results for "{{ searchQuery }}"` | "Try a different search term or clear the filter." |
|
||||
|
||||
StorageBrowser does not know `currentFolderId` directly — keep using the existing `emptyMessage` and `emptyHint` props, but change FileManagerView to pass per-context strings, OR replace the inline empty divs with three explicit conditionals inside StorageBrowser using `breadcrumb.length > 0` as a proxy for "in folder".
|
||||
|
||||
**Decision:** keep the props (emptyMessage, emptyHint) but render via `<EmptyState>` block. Use `breadcrumb.length === 0` (root) vs `> 0` (in folder) as the discriminator inside StorageBrowser for the icon name (folder vs document). For search no-results, the searchQuery check overrides.
|
||||
|
||||
**BreadcrumbBar wiring:**
|
||||
- StorageBrowser passes through `:segments="breadcrumb"` — parents must already map name→label
|
||||
- FileManagerView template: `:breadcrumb="mappedBreadcrumb"` where `mappedBreadcrumb` = computed mapping `foldersStore.breadcrumb` to `[{id: f.id, label: f.name}]`
|
||||
- CloudFolderView: existing `breadcrumb` computed already returns `{id, name}` — add a `mappedBreadcrumb` computed
|
||||
- Pass `rootLabel="Home"` for local mode and `rootLabel="Cloud"` for cloud mode (handled by passing through a prop or hardcoding in StorageBrowser based on `mode`)
|
||||
|
||||
**Toast call sites in FileManagerView (per RESEARCH.md §Toast System):**
|
||||
- `doMove(docId, folderId)`: on success → `useToastStore().show('Document moved', 'success')`; on catch → `useToastStore().show('Move failed: ' + (e.message || 'unknown error'), 'error')`
|
||||
- `doDeleteDoc(docId)`: on success → `useToastStore().show('Document deleted', 'success')`; on catch → `useToastStore().show('Delete failed: ' + (e.message || 'unknown error'), 'error')`
|
||||
- (Upload toasts are handled in plan 10-07 to keep file ownership clean? — NO, FileManagerView owns upload too. Defer upload toast wiring to this plan as well.)
|
||||
- `onFilesSelected({files, autoClassify})`: after `Promise.allSettled`, count items with `done=true` and items with errors, and show one toast: `show(`${successCount} of ${files.length} file(s) uploaded`, successCount === files.length ? 'success' : 'warning')` and for each item with `item.error` set, show `show('Upload failed: ' + item.error, 'error')`.
|
||||
|
||||
**FolderBreadcrumb.vue deletion:**
|
||||
- Delete `frontend/src/components/folders/FolderBreadcrumb.vue`
|
||||
- Delete `frontend/src/components/folders/__tests__/FolderBreadcrumb.test.js`
|
||||
- Grep verify no remaining import: `grep -r "FolderBreadcrumb" frontend/src/` returns 0 matches after edits
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Promote StorageBrowser.skeleton.test.js stubs to real tests</name>
|
||||
<files>frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js</files>
|
||||
<read_first>
|
||||
- frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js (Wave 0 stub from plan 10-05)
|
||||
- frontend/src/components/storage/StorageBrowser.vue (current state)
|
||||
- .planning/phases/10-ux-interaction/10-PATTERNS.md §"5-column StorageBrowser skeleton"
|
||||
</read_first>
|
||||
<behavior>
|
||||
Replace the `.todo` entries (UX-02 group) with real assertions:
|
||||
- Test 1: `renders 5 skeleton rows when loading=true and lists empty` — mount StorageBrowser with loading=true, folders=[], files=[], stub BreadcrumbBar/SearchBar/SortControls/DropZone/UploadProgress/TopicBadge. Assert `wrapper.findAll('.animate-pulse').length >= 5` (or assert a count of skeleton row containers >= 5).
|
||||
- Test 2: `Loading… text is absent when loading=true` — mount loading=true, assert `wrapper.text()` does NOT include 'Loading…'.
|
||||
- Test 3: `skeleton rows are NOT rendered when loading=false` — mount loading=false, folders=[], files=[], assert `wrapper.findAll('.animate-pulse').length === 0`.
|
||||
- Test 4: `skeleton row grid matches grid-cols-[2rem_1fr_6rem_8rem_6rem]` — mount loading=true, assert at least one element with class `grid-cols-[2rem_1fr_6rem_8rem_6rem]` exists in the skeleton block.
|
||||
|
||||
Keep the UX-13 `it.todo` stubs as-is (deferred to plan 10-12).
|
||||
</behavior>
|
||||
<action>
|
||||
Modify `frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js`. Replace ONLY the UX-02 `describe` block's `it.todo` entries with the 4 real tests above. Keep the UX-13 describe block untouched (its tests are promoted in plan 10-12). Import `StorageBrowser` from `../StorageBrowser.vue` and use `import { mount } from '@vue/test-utils'`. Stub child components via `global.stubs: { BreadcrumbBar: true, SearchBar: true, SortControls: true, DropZone: true, UploadProgress: true, TopicBadge: true }`. Create a fresh Pinia instance per test using `setActivePinia(createPinia())` if needed.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && npm run test -- --run StorageBrowser.skeleton</automated>
|
||||
Expected: 4 UX-02 tests FAIL (RED — StorageBrowser still shows Loading… text); 3 UX-13 .todo entries stay pending.
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- File still exists with both `describe('UX-02: ...')` and `describe('UX-13: ...')` blocks
|
||||
- The UX-02 describe block contains 4 real `it(...)` tests (no `.todo` for UX-02)
|
||||
- The UX-13 describe block still contains `.todo` entries
|
||||
- Running `cd frontend && npm run test -- --run StorageBrowser.skeleton` shows 4 failing UX-02 tests
|
||||
</acceptance_criteria>
|
||||
<done>4 RED tests in place for UX-02 skeleton behavior.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Update StorageBrowser.vue — skeleton, EmptyState, BreadcrumbBar swap</name>
|
||||
<files>frontend/src/components/storage/StorageBrowser.vue</files>
|
||||
<read_first>
|
||||
- frontend/src/components/storage/StorageBrowser.vue (current state — uses <script setup>)
|
||||
- frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js (failing tests)
|
||||
- frontend/src/components/ui/BreadcrumbBar.vue (interface)
|
||||
- frontend/src/components/ui/EmptyState.vue (interface)
|
||||
- .planning/phases/10-ux-interaction/10-PATTERNS.md §"StorageBrowser.vue" + §"5-column StorageBrowser skeleton"
|
||||
- .planning/phases/10-ux-interaction/10-RESEARCH.md §"Component Inventory §3"
|
||||
</read_first>
|
||||
<behavior>
|
||||
1. Import swap: replace `import FolderBreadcrumb from '../folders/FolderBreadcrumb.vue'` with `import BreadcrumbBar from '../ui/BreadcrumbBar.vue'`; also import `EmptyState`.
|
||||
2. Template: replace `<FolderBreadcrumb :segments="breadcrumb" ...>` with `<BreadcrumbBar :segments="breadcrumb" :root-label="mode === 'cloud' ? 'Cloud' : 'Home'" @navigate="$emit('breadcrumb-navigate', $event)" />`.
|
||||
3. Replace the three inline empty-state divs (lines 226-241 of current file) with `<EmptyState>` blocks based on the discriminators:
|
||||
- Search no-results: `<EmptyState v-else-if="!loading && searchQuery && (folders.length + files.length) === 0" icon="search" :headline="'No results for ' + JSON.stringify(searchQuery)" subtext="Try a different search term or clear the filter."><template #cta><button @click="$emit('search-change', '')" class="mt-3 text-sm text-indigo-600 hover:underline">Clear search</button></template></EmptyState>`
|
||||
- In-folder empty (breadcrumb non-empty): `<EmptyState v-else-if="!loading && breadcrumb.length > 0 && folders.length === 0 && files.length === 0 && !showNewFolderInput" icon="document" :headline="emptyMessage" :subtext="emptyHint" />`
|
||||
- Root empty: `<EmptyState v-else-if="!loading && breadcrumb.length === 0 && folders.length === 0 && files.length === 0 && !showNewFolderInput" icon="folder" :headline="emptyMessage" :subtext="emptyHint" />`
|
||||
(Use the `emptyMessage` / `emptyHint` props for headline/subtext so existing parent prop-driven configuration still works.)
|
||||
4. Replace the `<div v-if="loading" ...>Loading…</div>` element (current line 244) with a `<template v-if="loading">` block containing 5 skeleton row divs matching the grid:
|
||||
```
|
||||
<template v-if="loading">
|
||||
<div v-for="n in 5" :key="`sk-${n}`" class="px-4 py-2.5 grid grid-cols-[2rem_1fr_6rem_8rem_6rem] gap-3 items-center border-b border-gray-100">
|
||||
<div class="w-7 h-7 bg-gray-100 rounded-lg animate-pulse"></div>
|
||||
<div class="h-4 bg-gray-100 rounded animate-pulse w-2/3"></div>
|
||||
<div class="h-3 bg-gray-100 rounded animate-pulse hidden md:block"></div>
|
||||
<div class="h-3 bg-gray-100 rounded animate-pulse hidden sm:block"></div>
|
||||
<div class="w-14 h-3 bg-gray-100 rounded animate-pulse"></div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
Ensure the empty-state blocks use `v-else-if` so they only render when `!loading`.
|
||||
</behavior>
|
||||
<action>
|
||||
Edit `frontend/src/components/storage/StorageBrowser.vue`:
|
||||
|
||||
**Step 1 — Imports (in `<script setup>` block):**
|
||||
Replace `import FolderBreadcrumb from '../folders/FolderBreadcrumb.vue'` with `import BreadcrumbBar from '../ui/BreadcrumbBar.vue'` and add `import EmptyState from '../ui/EmptyState.vue'`.
|
||||
|
||||
**Step 2 — Template breadcrumb usage:**
|
||||
Replace lines 7-10 (the `<FolderBreadcrumb>` block) with:
|
||||
```
|
||||
<BreadcrumbBar
|
||||
:segments="breadcrumb"
|
||||
:root-label="mode === 'cloud' ? 'Cloud' : 'Home'"
|
||||
@navigate="$emit('breadcrumb-navigate', $event)"
|
||||
/>
|
||||
```
|
||||
|
||||
**Step 3 — Replace empty state and loading blocks (current lines 226-244):**
|
||||
Replace the existing three blocks (lines 226-244) with:
|
||||
```
|
||||
<template v-if="loading">
|
||||
<div
|
||||
v-for="n in 5"
|
||||
:key="`sk-${n}`"
|
||||
class="px-4 py-2.5 grid grid-cols-[2rem_1fr_6rem_8rem_6rem] gap-3 items-center border-b border-gray-100"
|
||||
>
|
||||
<div class="w-7 h-7 bg-gray-100 rounded-lg animate-pulse"></div>
|
||||
<div class="h-4 bg-gray-100 rounded animate-pulse w-2/3"></div>
|
||||
<div class="h-3 bg-gray-100 rounded animate-pulse hidden md:block"></div>
|
||||
<div class="h-3 bg-gray-100 rounded animate-pulse hidden sm:block"></div>
|
||||
<div class="w-14 h-3 bg-gray-100 rounded animate-pulse"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<EmptyState
|
||||
v-else-if="searchQuery && folders.length === 0 && files.length === 0"
|
||||
icon="search"
|
||||
:headline="`No results for "${searchQuery}"`"
|
||||
subtext="Try a different search term or clear the filter."
|
||||
>
|
||||
<template #cta>
|
||||
<button @click="$emit('search-change', '')" class="mt-3 text-sm text-indigo-600 hover:underline">
|
||||
Clear search
|
||||
</button>
|
||||
</template>
|
||||
</EmptyState>
|
||||
|
||||
<EmptyState
|
||||
v-else-if="breadcrumb.length > 0 && folders.length === 0 && files.length === 0 && !showNewFolderInput"
|
||||
icon="document"
|
||||
:headline="emptyMessage"
|
||||
:subtext="emptyHint"
|
||||
/>
|
||||
|
||||
<EmptyState
|
||||
v-else-if="folders.length === 0 && files.length === 0 && !showNewFolderInput"
|
||||
icon="folder"
|
||||
:headline="emptyMessage"
|
||||
:subtext="emptyHint"
|
||||
/>
|
||||
```
|
||||
|
||||
No comments. Keep the rest of StorageBrowser.vue unchanged.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && npm run test -- --run StorageBrowser.skeleton</automated>
|
||||
Expected: 4 UX-02 tests PASS.
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -E "import BreadcrumbBar from '../ui/BreadcrumbBar.vue'" frontend/src/components/storage/StorageBrowser.vue` returns 1
|
||||
- `grep -E "import EmptyState from '../ui/EmptyState.vue'" frontend/src/components/storage/StorageBrowser.vue` returns 1
|
||||
- `grep -E "import FolderBreadcrumb" frontend/src/components/storage/StorageBrowser.vue` returns 0
|
||||
- `grep -E "<FolderBreadcrumb" frontend/src/components/storage/StorageBrowser.vue` returns 0
|
||||
- `grep -E "<BreadcrumbBar" frontend/src/components/storage/StorageBrowser.vue` returns 1
|
||||
- `grep -E "<EmptyState" frontend/src/components/storage/StorageBrowser.vue` returns 3 (root, folder, search)
|
||||
- `grep -v '^#' frontend/src/components/storage/StorageBrowser.vue | grep -c "Loading…"` returns 0
|
||||
- `grep -E "animate-pulse" frontend/src/components/storage/StorageBrowser.vue` returns ≥ 5
|
||||
- `cd frontend && npm run test -- --run StorageBrowser.skeleton` exits 0 (UX-02 GREEN)
|
||||
</acceptance_criteria>
|
||||
<done>StorageBrowser has skeleton, EmptyState, and BreadcrumbBar wired; UX-02 tests green.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Update FileManagerView + CloudFolderView for breadcrumb mapping + toast wiring; delete FolderBreadcrumb</name>
|
||||
<files>
|
||||
frontend/src/views/FileManagerView.vue,
|
||||
frontend/src/views/CloudFolderView.vue,
|
||||
frontend/src/components/folders/FolderBreadcrumb.vue,
|
||||
frontend/src/components/folders/__tests__/FolderBreadcrumb.test.js
|
||||
</files>
|
||||
<read_first>
|
||||
- frontend/src/views/FileManagerView.vue (current state — uses <script setup>)
|
||||
- frontend/src/views/CloudFolderView.vue (current breadcrumb computed)
|
||||
- frontend/src/stores/folders.js (foldersStore.breadcrumb shape)
|
||||
- frontend/src/stores/toast.js (useToastStore signature)
|
||||
- .planning/phases/10-ux-interaction/10-PATTERNS.md §"FileManagerView.vue"
|
||||
</read_first>
|
||||
<action>
|
||||
**Step A — Modify `frontend/src/views/FileManagerView.vue`:**
|
||||
|
||||
Add `import { useToastStore } from '../stores/toast.js'` near the other imports.
|
||||
|
||||
Add a `computed` for `mappedBreadcrumb`:
|
||||
```js
|
||||
const mappedBreadcrumb = computed(() =>
|
||||
(foldersStore.breadcrumb || []).map(f => ({ id: f.id, label: f.name }))
|
||||
)
|
||||
```
|
||||
|
||||
Change the template `:breadcrumb="foldersStore.breadcrumb"` to `:breadcrumb="mappedBreadcrumb"` on the `<StorageBrowser>` element.
|
||||
|
||||
Update `doMove`:
|
||||
```js
|
||||
async function doMove(docId, folderId) {
|
||||
const toast = useToastStore()
|
||||
try {
|
||||
await docsStore.moveToFolder(docId, folderId)
|
||||
toast.show('Document moved', 'success')
|
||||
} catch (e) {
|
||||
toast.show('Move failed: ' + (e.message || 'unknown error'), 'error')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Update `doDeleteDoc`:
|
||||
```js
|
||||
async function doDeleteDoc(docId) {
|
||||
const toast = useToastStore()
|
||||
try {
|
||||
await docsStore.remove(docId)
|
||||
toast.show('Document deleted', 'success')
|
||||
} catch (e) {
|
||||
toast.show('Delete failed: ' + (e.message || 'unknown error'), 'error')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Update `onFilesSelected` to fire a summary toast at the end:
|
||||
```js
|
||||
async function onFilesSelected({ files, autoClassify }) {
|
||||
const folderId = currentFolderId.value
|
||||
const toast = useToastStore()
|
||||
const promises = files.map(file => {
|
||||
const item = reactive({ name: file.name, done: false, error: null, quotaError: null, topics: null })
|
||||
uploadQueue.value.unshift(item)
|
||||
return docsStore.upload(file, autoClassify, folderId)
|
||||
.then(({ doc }) => { item.done = true; item.topics = doc.topics ?? [] })
|
||||
.catch(e => {
|
||||
if (e.status === 413 && e.payload) item.quotaError = e.payload
|
||||
else item.error = e.message
|
||||
})
|
||||
})
|
||||
await Promise.allSettled(promises)
|
||||
await topicsStore.fetchTopics()
|
||||
const succeeded = uploadQueue.value.slice(0, files.length).filter(i => i.done).length
|
||||
if (succeeded === files.length) {
|
||||
toast.show(`${succeeded} file(s) uploaded`, 'success')
|
||||
} else if (succeeded > 0) {
|
||||
toast.show(`${succeeded} of ${files.length} file(s) uploaded`, 'warning')
|
||||
} else {
|
||||
toast.show('Upload failed', 'error')
|
||||
}
|
||||
}
|
||||
```
|
||||
(Important: the upload error per-item already populates `item.error`; UploadProgress already renders these. The summary toast is in addition.)
|
||||
|
||||
**Step B — Modify `frontend/src/views/CloudFolderView.vue`:**
|
||||
|
||||
Add a `mappedBreadcrumb` computed mapping `breadcrumb.value` (or the existing computed) to `[{id, label: name}]`. Pass `:breadcrumb="mappedBreadcrumb"` instead of `:breadcrumb="breadcrumb"`. Leave everything else unchanged.
|
||||
|
||||
**Step C — Delete FolderBreadcrumb files:**
|
||||
|
||||
Delete both files:
|
||||
- `frontend/src/components/folders/FolderBreadcrumb.vue`
|
||||
- `frontend/src/components/folders/__tests__/FolderBreadcrumb.test.js`
|
||||
|
||||
Use the file deletion tool or `rm` via Bash. Confirm with `ls frontend/src/components/folders/`.
|
||||
|
||||
No comments added. No `console.error` removed from doMove/doDeleteDoc — leave existing behavior intact aside from the toast addition. Actually: REMOVE the `console.error(e.message)` lines because the toast now communicates the error to the user.
|
||||
|
||||
Per D-11/D-13: the `mappedBreadcrumb` computed is also used in any test for FileManagerView — update those tests in the next step.
|
||||
|
||||
Final grep verification: `grep -r "FolderBreadcrumb" frontend/src/` returns 0 matches.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && npm run test -- --run FileManagerView && cd frontend && npm run test -- --run StorageBrowser.skeleton && cd frontend && npm run test -- --run BreadcrumbBar && cd frontend && npm run test -- --run toast</automated>
|
||||
Expected: all relevant test suites pass. FolderBreadcrumb tests no longer collected (file deleted).
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- File `frontend/src/components/folders/FolderBreadcrumb.vue` no longer exists
|
||||
- File `frontend/src/components/folders/__tests__/FolderBreadcrumb.test.js` no longer exists
|
||||
- `grep -r "FolderBreadcrumb" frontend/src/` returns 0 lines
|
||||
- `grep -E "useToastStore" frontend/src/views/FileManagerView.vue` returns ≥ 4 occurrences (import + 3 action handlers minimum)
|
||||
- `grep -E "toast\\.show\\('Document moved'" frontend/src/views/FileManagerView.vue` returns 1 match
|
||||
- `grep -E "toast\\.show\\('Document deleted'" frontend/src/views/FileManagerView.vue` returns 1 match
|
||||
- `grep -E "mappedBreadcrumb" frontend/src/views/FileManagerView.vue` returns ≥ 2 matches
|
||||
- `grep -E "mappedBreadcrumb" frontend/src/views/CloudFolderView.vue` returns ≥ 2 matches
|
||||
- `grep -E "console\\.error" frontend/src/views/FileManagerView.vue` returns ≤ 1 (or 0 if previously only present in doMove/doDeleteDoc)
|
||||
- Suites pass: FileManagerView, StorageBrowser.skeleton, BreadcrumbBar, toast all exit 0
|
||||
</acceptance_criteria>
|
||||
<done>FileManagerView + CloudFolderView wired; FolderBreadcrumb deleted; toast call sites live; all tests green.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `cd frontend && npm run test -- --run StorageBrowser.skeleton` exits 0
|
||||
- `cd frontend && npm run test -- --run FileManagerView` exits 0
|
||||
- `cd frontend && npm run test -- --run BreadcrumbBar` exits 0 (regression)
|
||||
- `cd frontend && npm run test -- --run toast` exits 0 (regression)
|
||||
- `grep -r "FolderBreadcrumb" frontend/src/` returns 0 lines (dead code removed)
|
||||
- `cd frontend && npm run test -- --run` (full suite) exits 0 — no other suite broke
|
||||
- StorageBrowser.vue contains exactly 3 `<EmptyState>` blocks and exactly 1 `<BreadcrumbBar>` block
|
||||
- StorageBrowser.vue contains 5 skeleton `<div>`s with `animate-pulse`
|
||||
- Loading… text removed (grep returns 0 in StorageBrowser.vue)
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
Loading the file manager with `loading=true` shows 5 animated skeleton rows. After load completes:
|
||||
- Empty root folder → "Nothing here yet" with folder icon
|
||||
- Empty sub-folder → "This folder is empty" with document icon
|
||||
- Search with no matches → "No results for {query}" with search icon and Clear button
|
||||
Breadcrumbs use the shared BreadcrumbBar with rootLabel="Home"/"Cloud" per mode. Document move/delete/upload actions fire toasts.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/10-ux-interaction/10-06-SUMMARY.md` when done. List which behaviors became GREEN and the exact line ranges modified.
|
||||
</output>
|
||||
Reference in New Issue
Block a user