---
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>
