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

268 lines
15 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: 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>