chore: archive v0.2 phase directories to milestones/v0.2-phases/
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>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
e008bf7dae
commit
123ae5b29b
@@ -0,0 +1,267 @@
|
||||
---
|
||||
phase: 10-ux-interaction
|
||||
plan: 07
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: [10-02, 10-03, 10-05]
|
||||
files_modified:
|
||||
- frontend/src/components/layout/AppSidebar.vue
|
||||
- frontend/src/components/layout/__tests__/AppSidebar.empty.test.js
|
||||
autonomous: true
|
||||
requirements: [UX-03, UX-01, UX-14]
|
||||
must_haves:
|
||||
truths:
|
||||
- "AppSidebar renders sidebar-indent skeleton placeholders for folders, topics, and cloud sections while loading"
|
||||
- "AppSidebar renders EmptyState size='sm' for each empty section (folders, topics, cloud)"
|
||||
- "AppSidebar no longer contains a New button for creating root folders (UX-14)"
|
||||
- "AppSidebar no longer contains startNewFolder/cancelNewFolder/submitNewFolder methods or the related state (showNewFolderInput, newFolderName, newFolderError)"
|
||||
- "StorageBrowser's own startNewFolder (file manager) is UNTOUCHED"
|
||||
artifacts:
|
||||
- path: "frontend/src/components/layout/AppSidebar.vue"
|
||||
provides: "Updated sidebar with skeleton, EmptyState, no inline New button"
|
||||
key_links:
|
||||
- from: "AppSidebar.vue"
|
||||
to: "EmptyState.vue"
|
||||
via: "<EmptyState size=\"sm\" :icon=\"...\" />"
|
||||
pattern: "<EmptyState size=\"sm\""
|
||||
---
|
||||
|
||||
<objective>
|
||||
Wire UX-03 (sidebar skeletons), UX-01 (sidebar EmptyState micro states), and UX-14 (remove sidebar "New" folder button + related state/methods) in `frontend/src/components/layout/AppSidebar.vue`.
|
||||
|
||||
Per the planning_guidance, UX-14 removal targets ONLY AppSidebar's own folder-creation flow. The file manager's own inline new-folder UI (`StorageBrowser.startNewFolder`) is UNTOUCHED — it remains the canonical way to create folders.
|
||||
|
||||
Output: AppSidebar.vue with skeleton placeholders matching TreeItem indent (pl-7), three EmptyState size='sm' micro states (folders/topics/cloud), and the "New" button + helper methods + state removed.
|
||||
</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/layout/AppSidebar.vue
|
||||
@frontend/src/components/ui/EmptyState.vue
|
||||
@frontend/src/components/ui/TreeItem.vue
|
||||
@frontend/src/components/storage/StorageBrowser.vue
|
||||
|
||||
<interfaces>
|
||||
**AppSidebar.vue uses Options API** — current file, see PATTERNS.md §"AppSidebar.vue".
|
||||
|
||||
**Sidebar skeleton pattern (PATTERNS.md §3 Sidebar skeleton):**
|
||||
```html
|
||||
<div class="pl-7 py-1 space-y-1">
|
||||
<div v-for="n in 3" :key="n" class="flex items-center gap-2 py-1">
|
||||
<div class="w-4 h-4 bg-gray-100 rounded animate-pulse shrink-0"></div>
|
||||
<div class="h-3 bg-gray-100 rounded animate-pulse" :style="{ width: (50 + n * 15) + 'px' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Three sections requiring skeleton + EmptyState (RESEARCH.md §Component Inventory §3):**
|
||||
|
||||
| Section | Loading condition | Empty condition | EmptyState icon | EmptyState headline | EmptyState CTA |
|
||||
|---------|-------------------|-----------------|-----------------|---------------------|----------------|
|
||||
| Folders | `loadingRoots` | `foldersStore.rootFolders.length === 0` | folder | "Create a folder in the file manager" | (none) |
|
||||
| Topics | `topicsStore.loading` | `topicsStore.topics.length === 0` | tag | "No topics yet" | (none) |
|
||||
| Cloud | `loadingCloudConnections` | `activeCloudConnections.length === 0` | cloud | "Connect in Settings" | router-link to /settings |
|
||||
|
||||
All EmptyStates use `size="sm"`.
|
||||
|
||||
**UX-14 removal targets in AppSidebar.vue:**
|
||||
- Template: the `<button @click="startNewFolder">New</button>` element near the folder section header (current lines 75-82)
|
||||
- Template: the inline new-folder `<div v-if="showNewFolderInput">` block (current lines 87-98)
|
||||
- Script: methods `startNewFolder()`, `cancelNewFolder()`, `submitNewFolder()` (current lines 289-312)
|
||||
- Script: state `showNewFolderInput`, `newFolderName`, `newFolderError`
|
||||
- Update the empty-folder text block (current lines 102-103) to remove the `&& !showNewFolderInput` condition since that variable no longer exists.
|
||||
|
||||
**StorageBrowser invariant:** `frontend/src/components/storage/StorageBrowser.vue` still has its own `startNewFolder` function, `showNewFolderInput` state, and the inline new-folder input row — DO NOT touch any of these.
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Promote AppSidebar.empty.test.js stubs to real tests</name>
|
||||
<files>frontend/src/components/layout/__tests__/AppSidebar.empty.test.js</files>
|
||||
<read_first>
|
||||
- frontend/src/components/layout/__tests__/AppSidebar.empty.test.js (Wave 0 stub)
|
||||
- frontend/src/components/layout/AppSidebar.vue (current state)
|
||||
- frontend/src/stores/folders.js, frontend/src/stores/topics.js, frontend/src/stores/cloudConnections.js (store shapes for mocking)
|
||||
</read_first>
|
||||
<behavior>
|
||||
Replace `.todo` entries with real assertions (skeleton, EmptyState micro, UX-14 absence):
|
||||
- UX-03 group (3 tests):
|
||||
1. `renders folder skeleton rows when loadingRoots is true` — mount AppSidebar with foldersStore.loadingRoots=true (use a setup or component data override), assert `wrapper.findAll('.animate-pulse').length >= 3` within the folders section
|
||||
2. `renders topics skeleton rows when topicsStore.loading is true` — similar assertion for topics
|
||||
3. `renders cloud skeleton rows when loadingCloudConnections is true` — similar assertion for cloud
|
||||
- UX-01 sidebar micro (3 tests):
|
||||
4. `renders <EmptyState size="sm" icon="folder"> in folders section when empty` — mount with all loading=false and empty store arrays, stub EmptyState, assert presence in DOM via component lookup
|
||||
5. `renders <EmptyState size="sm" icon="tag"> in topics section when empty` — similar
|
||||
6. `renders <EmptyState size="sm" icon="cloud"> in cloud section when empty with #cta router-link` — similar
|
||||
- UX-14 group (3 tests):
|
||||
7. `template does NOT include a "New" button in the folder section header` — assert no `<button>` element in the rendered AppSidebar has text content equal to 'New'
|
||||
8. `component does NOT expose startNewFolder method` — Vue Options API: mount component, assert `wrapper.vm.startNewFolder` is undefined
|
||||
9. `component data does NOT include showNewFolderInput` — assert `wrapper.vm.showNewFolderInput` is undefined
|
||||
|
||||
For mocking stores, use a `createMockPinia` helper or use `setActivePinia(createPinia())` and override store state after creation (e.g., `useFoldersStore().rootFolders = []`).
|
||||
</behavior>
|
||||
<action>
|
||||
Modify `frontend/src/components/layout/__tests__/AppSidebar.empty.test.js`. Replace each `it.todo(...)` from Task 1's stub set with the real tests above. Use `import { mount } from '@vue/test-utils'`, `import { setActivePinia, createPinia } from 'pinia'`, and stub `router-link` + `EmptyState` + `AppIcon` + `TreeItem` + `FolderTreeItem` + `CloudProviderTreeItem` via `global.stubs`.
|
||||
|
||||
Tests fail initially because AppSidebar still has the old structure.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && npm run test -- --run AppSidebar.empty</automated>
|
||||
Expected: 9 tests fail (RED).
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- File contains exactly 9 `it(...)` tests across 3 describe blocks (UX-03, UX-01, UX-14)
|
||||
- No `.todo` entries remain
|
||||
- Tests stub child components via `global.stubs`
|
||||
- All 9 tests are RED
|
||||
</acceptance_criteria>
|
||||
<done>9 RED tests describe the AppSidebar contract.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Edit AppSidebar.vue — remove UX-14 elements, add skeleton + EmptyState wiring</name>
|
||||
<files>frontend/src/components/layout/AppSidebar.vue</files>
|
||||
<read_first>
|
||||
- frontend/src/components/layout/AppSidebar.vue (current state)
|
||||
- frontend/src/components/layout/__tests__/AppSidebar.empty.test.js (failing tests)
|
||||
- frontend/src/components/ui/EmptyState.vue (interface)
|
||||
- .planning/phases/10-ux-interaction/10-PATTERNS.md §"AppSidebar.vue"
|
||||
- .planning/phases/10-ux-interaction/10-RESEARCH.md §"Component Inventory §3" (sidebar micro EmptyState specs)
|
||||
</read_first>
|
||||
<behavior>
|
||||
1. UX-14 removals (script and template):
|
||||
- Remove `<button @click="startNewFolder">New</button>` near folder section header
|
||||
- Remove the inline new-folder `<div v-if="showNewFolderInput">` block
|
||||
- Remove the data properties `showNewFolderInput`, `newFolderName`, `newFolderError`
|
||||
- Remove the methods `startNewFolder`, `cancelNewFolder`, `submitNewFolder`
|
||||
- Remove any `nextTick(() => this.$refs.newFolderInputRef?.focus())` or related ref code in mounted/methods
|
||||
2. Skeleton wiring (UX-03):
|
||||
- Replace each `<div ... text-xs text-gray-400>Loading…</div>` placeholder with a `<div class="pl-7 py-1 space-y-1">` skeleton block containing 3 `<div v-for="n in 3">` rows (icon + text block, animate-pulse), per the PATTERNS template.
|
||||
- Three locations: folders section, cloud section, topics section.
|
||||
3. EmptyState wiring (UX-01 sidebar micro):
|
||||
- Folders empty: replace `<div class="pl-7 py-1 text-xs text-gray-400">No folders yet</div>` with `<EmptyState size="sm" icon="folder" headline="Create a folder in the file manager" />`
|
||||
- Topics empty: replace existing text with `<EmptyState size="sm" icon="tag" headline="No topics yet" />`
|
||||
- Cloud empty: replace with:
|
||||
```
|
||||
<EmptyState size="sm" icon="cloud" headline="Connect in Settings">
|
||||
<template #cta>
|
||||
<router-link to="/settings" class="ml-1 text-indigo-600 hover:underline">Settings</router-link>
|
||||
</template>
|
||||
</EmptyState>
|
||||
```
|
||||
4. Register EmptyState as a component import: `import EmptyState from '../ui/EmptyState.vue'` + add to `components: { ... }` in the Options API export.
|
||||
</behavior>
|
||||
<action>
|
||||
Edit `frontend/src/components/layout/AppSidebar.vue`:
|
||||
|
||||
**Step 1 — Imports:**
|
||||
Add `import EmptyState from '../ui/EmptyState.vue'` near the existing imports. Add `EmptyState` to the `components: { ... }` registration object in the Options API `export default`.
|
||||
|
||||
**Step 2 — Remove UX-14 elements:**
|
||||
- Delete the entire `<button @click="startNewFolder">...New...</button>` element (current ~lines 75-82)
|
||||
- Delete the entire `<div v-if="showNewFolderInput">...</div>` inline new-folder block (current ~lines 87-98)
|
||||
- In the `data()` return, remove the three properties: `showNewFolderInput: false`, `newFolderName: ''`, `newFolderError: ''`
|
||||
- In the `methods: { ... }` object, remove `startNewFolder`, `cancelNewFolder`, `submitNewFolder`
|
||||
- Remove the corresponding template `ref="newFolderInputRef"` if present
|
||||
|
||||
**Step 3 — Folder section update:**
|
||||
- Replace the `<div v-if="loadingRoots" class="pl-7 py-1 text-xs text-gray-400">Loading…</div>` element with:
|
||||
```
|
||||
<div v-if="loadingRoots" class="pl-7 py-1 space-y-1">
|
||||
<div v-for="n in 3" :key="`sk-f-${n}`" class="flex items-center gap-2 py-1">
|
||||
<div class="w-4 h-4 bg-gray-100 rounded animate-pulse shrink-0"></div>
|
||||
<div class="h-3 bg-gray-100 rounded animate-pulse" :style="{ width: (50 + n * 15) + 'px' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
- Replace the `<div v-else-if="foldersStore.rootFolders.length === 0 && !showNewFolderInput" class="pl-7 py-1 text-xs text-gray-400">No folders yet</div>` with:
|
||||
```
|
||||
<EmptyState
|
||||
v-else-if="foldersStore.rootFolders.length === 0"
|
||||
size="sm"
|
||||
icon="folder"
|
||||
headline="Create a folder in the file manager"
|
||||
class="pl-7"
|
||||
/>
|
||||
```
|
||||
(Drop the `&& !showNewFolderInput` since the variable no longer exists.)
|
||||
|
||||
**Step 4 — Cloud section update:**
|
||||
- Replace `<div v-if="loadingCloudConnections" class="pl-7 py-1 text-xs text-gray-400">Loading…</div>` with the same 3-row skeleton (with `key="sk-c-${n}"`).
|
||||
- Replace `<div v-else-if="activeCloudConnections.length === 0" class="pl-7 py-1 text-xs text-gray-400">No cloud storage connected</div>` with:
|
||||
```
|
||||
<EmptyState
|
||||
v-else-if="activeCloudConnections.length === 0"
|
||||
size="sm"
|
||||
icon="cloud"
|
||||
headline="Connect in Settings"
|
||||
class="pl-7"
|
||||
>
|
||||
<template #cta>
|
||||
<router-link to="/settings" class="ml-1 text-indigo-600 hover:underline">Settings</router-link>
|
||||
</template>
|
||||
</EmptyState>
|
||||
```
|
||||
|
||||
**Step 5 — Topics section update:**
|
||||
- Replace `<div v-if="topicsStore.loading" class="px-3 py-1 text-xs text-gray-400">Loading…</div>` with a 3-row skeleton (with `key="sk-t-${n}"`), using `class="px-3 py-1 space-y-1"` for the outer wrapper to match the existing topics indent.
|
||||
- Replace `<div v-else-if="topicsStore.topics.length === 0" class="px-3 py-1 text-xs text-gray-400">No topics yet</div>` with:
|
||||
```
|
||||
<EmptyState
|
||||
v-else-if="topicsStore.topics.length === 0"
|
||||
size="sm"
|
||||
icon="tag"
|
||||
headline="No topics yet"
|
||||
class="px-3"
|
||||
/>
|
||||
```
|
||||
|
||||
No comments. Preserve all other functionality (Topics, Shared, Admin, Settings, sign-out, etc.) untouched.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && npm run test -- --run AppSidebar.empty</automated>
|
||||
Expected: all 9 tests PASS.
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -E "import EmptyState" frontend/src/components/layout/AppSidebar.vue` returns 1
|
||||
- `grep -E "<EmptyState\\s+v-else-if" frontend/src/components/layout/AppSidebar.vue` returns 3
|
||||
- `grep -E "size=\"sm\"" frontend/src/components/layout/AppSidebar.vue` returns ≥ 3
|
||||
- `grep -E "icon=\"folder\"|icon=\"tag\"|icon=\"cloud\"" frontend/src/components/layout/AppSidebar.vue` returns 3 (one per section)
|
||||
- `grep -v '^#' frontend/src/components/layout/AppSidebar.vue | grep -c "Loading…"` returns 0
|
||||
- `grep -E "startNewFolder|cancelNewFolder|submitNewFolder" frontend/src/components/layout/AppSidebar.vue` returns 0
|
||||
- `grep -E "showNewFolderInput|newFolderName|newFolderError" frontend/src/components/layout/AppSidebar.vue` returns 0
|
||||
- `grep -E "animate-pulse" frontend/src/components/layout/AppSidebar.vue` returns ≥ 6 (3 sections × 2 elements per skeleton row × at least one row)
|
||||
- `grep -v '^#' frontend/src/components/layout/AppSidebar.vue | grep -E '>\\s*New\\s*</button>'` returns 0 (no "New" button)
|
||||
- StorageBrowser.vue invariant: `grep -E "function startNewFolder" frontend/src/components/storage/StorageBrowser.vue` still returns 1 (untouched)
|
||||
- `cd frontend && npm run test -- --run AppSidebar.empty` exits 0
|
||||
- Full sidebar tests pass: `cd frontend && npm run test -- --run AppSidebar` exits 0
|
||||
</acceptance_criteria>
|
||||
<done>AppSidebar updated; UX-03 + UX-01 (micro) + UX-14 all GREEN; StorageBrowser's own startNewFolder preserved.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `cd frontend && npm run test -- --run AppSidebar.empty` exits 0
|
||||
- StorageBrowser invariant intact: `grep -E "function startNewFolder" frontend/src/components/storage/StorageBrowser.vue` returns 1
|
||||
- `grep -E "Loading…" frontend/src/components/layout/AppSidebar.vue` returns 0 (loading text replaced by skeletons)
|
||||
- AppSidebar EmptyState count: `grep -c "<EmptyState" frontend/src/components/layout/AppSidebar.vue` returns 3
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
The sidebar shows shimmering skeleton rows while loading folders/topics/cloud connections. When loading completes and any section has no items, a compact icon + label appears (with a Settings link for cloud). The inline "New folder" button is gone — folder creation is accessible only from the file manager toolbar, as required by UX-14.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/10-ux-interaction/10-07-SUMMARY.md` when done.
|
||||
</output>
|
||||
Reference in New Issue
Block a user