298 lines
16 KiB
Markdown
298 lines
16 KiB
Markdown
---
|
|
phase: 10-ux-interaction
|
|
plan: 05
|
|
type: execute
|
|
wave: 0
|
|
depends_on: []
|
|
files_modified:
|
|
- frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js
|
|
- frontend/src/components/layout/__tests__/AppSidebar.empty.test.js
|
|
- frontend/src/views/admin/__tests__/AdminAuditView.skeleton.test.js
|
|
- frontend/src/views/admin/__tests__/AdminUsersView.skeleton.test.js
|
|
- frontend/src/__tests__/keyboard.test.js
|
|
- frontend/src/components/layout/__tests__/OsDragOverlay.test.js
|
|
- frontend/src/components/storage/__tests__/StorageBrowser.dragmove.test.js
|
|
- frontend/src/components/ui/__tests__/dropdown.test.js
|
|
autonomous: true
|
|
requirements: [UX-02, UX-03, UX-04, UX-05, UX-06, UX-07, UX-08, UX-09, UX-11, UX-13, UX-14]
|
|
must_haves:
|
|
truths:
|
|
- "Wave 0 xfail test stubs exist for every requirement that is implemented in Waves 1-4"
|
|
- "Each stub is marked .skip or .todo so the test runner reports unrun tests instead of false greens"
|
|
- "Each requirement ID is referenced by at least one stub"
|
|
artifacts:
|
|
- path: "frontend/src/__tests__/keyboard.test.js"
|
|
provides: "Wave 0 xfail stubs for UX-05..08 keyboard shortcuts"
|
|
- path: "frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js"
|
|
provides: "Wave 0 xfail stubs for UX-02 skeleton + UX-13 dropdown teleport"
|
|
key_links:
|
|
- from: "Each stub describe() block"
|
|
to: "Requirement ID in test title or comment"
|
|
via: "describe('UX-XX: ...')"
|
|
pattern: "describe.*UX-"
|
|
---
|
|
|
|
<objective>
|
|
Create Wave 0 test stubs (Nyquist pattern — RED tests before implementation) for the requirements that are wired in Waves 1-4. This ensures:
|
|
1. The test runner reports unrun tests for every requirement (no false greens)
|
|
2. Wave 1-4 implementations have a place to "promote" stubs to real assertions
|
|
3. Coverage of all 15 phase requirements is provable up-front
|
|
|
|
This plan covers the 11 requirements NOT covered by Wave 0 foundation plans (10-01..10-04 already include their tests):
|
|
- Foundation already covered by their own plans: UX-01 (EmptyState — 10-02), UX-10 (Toast — 10-04), UX-12 (BreadcrumbBar — 10-03), CODE-05 (AppIcon — 10-01)
|
|
- This plan covers: UX-02, UX-03, UX-04, UX-05, UX-06, UX-07, UX-08, UX-09, UX-11, UX-13, UX-14
|
|
|
|
Output: 8 stub test files, each with `it.skip` or `it.todo` placeholders annotating the requirement IDs and expected behavior. Wave 1-4 implementing plans will replace `.skip`/`.todo` with 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-VALIDATION.md
|
|
@frontend/src/components/folders/__tests__/FolderBreadcrumb.test.js
|
|
|
|
<interfaces>
|
|
Vitest stubs use `it.todo('description')` or `it.skip('description', ...)`. Each stub file should `describe('UX-XX: requirement summary', ...)` with one or more `it.todo` placeholders for the behaviors that Wave 1-4 will implement.
|
|
|
|
Test file locations (mirror the source structure):
|
|
- `frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js` — UX-02 (skeleton rows) + UX-13 (folder picker dropdown)
|
|
- `frontend/src/components/storage/__tests__/StorageBrowser.dragmove.test.js` — UX-11 (drag-to-move toast trigger + ring highlight)
|
|
- `frontend/src/components/layout/__tests__/AppSidebar.empty.test.js` — UX-03 (sidebar skeleton + empty micro states) + UX-14 (sidebar New button removed)
|
|
- `frontend/src/views/admin/__tests__/AdminAuditView.skeleton.test.js` — UX-04 (audit log skeleton rows)
|
|
- `frontend/src/views/admin/__tests__/AdminUsersView.skeleton.test.js` — UX-04 (users table skeleton rows)
|
|
- `frontend/src/__tests__/keyboard.test.js` — UX-05..08 (global keyboard shortcuts)
|
|
- `frontend/src/components/layout/__tests__/OsDragOverlay.test.js` — UX-09 (OS file drag overlay depth-counter)
|
|
- `frontend/src/components/ui/__tests__/dropdown.test.js` — UX-13 (Teleport+getBoundingClientRect dropdown positioning)
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Create skeleton + dropdown stubs (UX-02, UX-03, UX-04, UX-13, UX-14)</name>
|
|
<files>
|
|
frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js,
|
|
frontend/src/components/layout/__tests__/AppSidebar.empty.test.js,
|
|
frontend/src/views/admin/__tests__/AdminAuditView.skeleton.test.js,
|
|
frontend/src/views/admin/__tests__/AdminUsersView.skeleton.test.js,
|
|
frontend/src/components/ui/__tests__/dropdown.test.js
|
|
</files>
|
|
<read_first>
|
|
- frontend/src/components/folders/__tests__/FolderBreadcrumb.test.js (style reference)
|
|
- .planning/phases/10-ux-interaction/10-VALIDATION.md (test map)
|
|
- .planning/phases/10-ux-interaction/10-RESEARCH.md §"Pitfall 7: Skeleton Row Height" and §"Component Inventory §2: Dropdown Clipping Audit"
|
|
</read_first>
|
|
<action>
|
|
Create 5 stub test files. Each file is a single `describe('UX-XX: requirement', ...)` block with `it.todo(...)` entries. Use `import { describe, it } from 'vitest'` at the top.
|
|
|
|
**File A — `frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js`:**
|
|
```
|
|
import { describe, it } from 'vitest'
|
|
|
|
describe('UX-02: StorageBrowser shows skeleton rows during loading', () => {
|
|
it.todo('renders 5+ skeleton rows when loading=true (replaces Loading… text)')
|
|
it.todo('skeleton rows use grid-cols-[2rem_1fr_6rem_8rem_6rem] matching real row grid')
|
|
it.todo('skeleton rows use animate-pulse')
|
|
it.todo('Loading… text is absent when loading=true (skeleton replaces it)')
|
|
})
|
|
|
|
describe('UX-13: StorageBrowser folder picker uses Teleport + getBoundingClientRect', () => {
|
|
it.todo('folder picker dropdown is teleported to body (escapes overflow container)')
|
|
it.todo('dropdown position reflects getBoundingClientRect of the trigger button')
|
|
it.todo('window scroll while open recalculates position')
|
|
})
|
|
```
|
|
|
|
**File B — `frontend/src/components/layout/__tests__/AppSidebar.empty.test.js`:**
|
|
```
|
|
import { describe, it } from 'vitest'
|
|
|
|
describe('UX-03: AppSidebar shows skeleton placeholders while loading', () => {
|
|
it.todo('renders skeleton rows in folder section when loadingRoots=true')
|
|
it.todo('renders skeleton rows in topics section when topicsStore.loading=true')
|
|
it.todo('renders skeleton rows in cloud section when loadingCloudConnections=true')
|
|
it.todo('Loading… text is removed from all three sections')
|
|
})
|
|
|
|
describe('UX-01 (sidebar micro): EmptyState size=sm appears when each section is empty', () => {
|
|
it.todo('folders empty: EmptyState size=sm with icon=folder')
|
|
it.todo('topics empty: EmptyState size=sm with icon=tag')
|
|
it.todo('cloud empty: EmptyState size=sm with icon=cloud and a Settings link in #cta')
|
|
})
|
|
|
|
describe('UX-14: AppSidebar no longer renders an inline "New" folder button', () => {
|
|
it.todo('no <button> with text "New" exists in the rendered template')
|
|
it.todo('startNewFolder/cancelNewFolder/submitNewFolder methods are removed')
|
|
it.todo('newFolderName/showNewFolderInput state is removed')
|
|
})
|
|
```
|
|
|
|
**File C — `frontend/src/views/admin/__tests__/AdminAuditView.skeleton.test.js`:**
|
|
```
|
|
import { describe, it } from 'vitest'
|
|
|
|
describe('UX-04: AdminAuditView shows skeleton table rows during loading', () => {
|
|
it.todo('renders 5+ skeleton <tr> rows when loading=true')
|
|
it.todo('skeleton rows have 5 columns matching the real table header')
|
|
it.todo('animated spinner Loading audit log… text is removed')
|
|
})
|
|
|
|
describe('UX-01 (audit empty): EmptyState renders when entries is empty after load', () => {
|
|
it.todo('EmptyState icon=clipboardList headline="No entries found" appears when entries.length===0 and not loading')
|
|
it.todo('clear-filters button rendered in #cta slot')
|
|
})
|
|
```
|
|
|
|
**File D — `frontend/src/views/admin/__tests__/AdminUsersView.skeleton.test.js`:**
|
|
```
|
|
import { describe, it } from 'vitest'
|
|
|
|
describe('UX-04: AdminUsersView shows skeleton table rows during loading', () => {
|
|
it.todo('renders 5+ skeleton <tr> rows when loading=true')
|
|
it.todo('skeleton rows have 6 columns matching the real table header')
|
|
it.todo('animated spinner Loading users… text is removed')
|
|
})
|
|
```
|
|
|
|
**File E — `frontend/src/components/ui/__tests__/dropdown.test.js`:**
|
|
```
|
|
import { describe, it } from 'vitest'
|
|
|
|
describe('UX-13: Teleport + getBoundingClientRect dropdown pattern across components', () => {
|
|
it.todo('DocumentCard folder picker uses Teleport to body')
|
|
it.todo('DocumentCard folder picker position matches trigger getBoundingClientRect')
|
|
it.todo('FolderRow three-dot menu uses Teleport to body')
|
|
it.todo('FolderRow three-dot menu repositions on window scroll')
|
|
})
|
|
```
|
|
|
|
Do not implement any real tests — these are stubs Wave 1/3 will promote.
|
|
</action>
|
|
<verify>
|
|
<automated>cd frontend && npm run test -- --run skeleton.test && cd frontend && npm run test -- --run dropdown.test && cd frontend && npm run test -- --run AppSidebar.empty</automated>
|
|
Expected: tests collected, all `.todo` are reported but not failing (Vitest reports `.todo` as "skipped/pending" — they do not fail the suite).
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- All 5 files exist at the specified paths
|
|
- Each file contains at least one `describe('UX-XX: ...')` block whose name starts with the requirement ID
|
|
- Total `it.todo(...)` count across the 5 files ≥ 20
|
|
- Running `cd frontend && npm run test -- --run` does NOT fail because of these files (they only contain `.todo`)
|
|
- Grep verification: `grep -rE "describe\\('UX-0[234]" frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js frontend/src/components/layout/__tests__/AppSidebar.empty.test.js frontend/src/views/admin/__tests__/AdminAuditView.skeleton.test.js frontend/src/views/admin/__tests__/AdminUsersView.skeleton.test.js | wc -l` ≥ 4
|
|
- Grep: `grep -rE "describe\\('UX-1[34]" frontend/src/components/ui/__tests__/dropdown.test.js frontend/src/components/layout/__tests__/AppSidebar.empty.test.js | wc -l` ≥ 2
|
|
</acceptance_criteria>
|
|
<done>5 skeleton/empty/dropdown stub files exist covering UX-02, UX-03, UX-04, UX-13, UX-14.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Create keyboard + OS-drag + drag-to-move stubs (UX-05..09, UX-11)</name>
|
|
<files>
|
|
frontend/src/__tests__/keyboard.test.js,
|
|
frontend/src/components/layout/__tests__/OsDragOverlay.test.js,
|
|
frontend/src/components/storage/__tests__/StorageBrowser.dragmove.test.js
|
|
</files>
|
|
<read_first>
|
|
- .planning/phases/10-ux-interaction/10-VALIDATION.md (behavioral contracts)
|
|
- .planning/phases/10-ux-interaction/10-RESEARCH.md §"Common Pitfalls" 2, 3, 12
|
|
</read_first>
|
|
<action>
|
|
Create 3 stub test files:
|
|
|
|
**File F — `frontend/src/__tests__/keyboard.test.js`:**
|
|
```
|
|
import { describe, it } from 'vitest'
|
|
|
|
describe('UX-05: "/" focuses the search bar', () => {
|
|
it.todo('dispatching keydown "/" calls focus() on SearchBar input via App.vue routeViewRef chain')
|
|
it.todo('"/" does NOT redirect when an INPUT element has focus (guard: document.activeElement.tagName)')
|
|
})
|
|
|
|
describe('UX-06: "Escape" clears active search (when no input focused)', () => {
|
|
it.todo('keydown Escape clears searchQuery via FileManagerView.clearSearch()')
|
|
it.todo('Escape does NOT clear search while inside a focused INPUT')
|
|
it.todo('Escape does NOT double-trigger when DocumentPreviewModal is open (modal owns its Escape handler)')
|
|
})
|
|
|
|
describe('UX-07: "U" triggers the upload picker', () => {
|
|
it.todo('keydown "u" calls DropZone.triggerInput() through the ref chain (App→FileManagerView→StorageBrowser→DropZone)')
|
|
it.todo('"U" does NOT fire when input is focused')
|
|
it.todo('"U" is a no-op on routes that do not expose triggerUpload (optional chain)')
|
|
})
|
|
|
|
describe('UX-08: "N" starts new folder input', () => {
|
|
it.todo('keydown "n" calls StorageBrowser.startNewFolder() via ref chain')
|
|
it.todo('"N" does NOT fire when input is focused')
|
|
it.todo('"N" is a no-op on CloudFolderView (does not expose startNewFolder)')
|
|
})
|
|
```
|
|
|
|
**File G — `frontend/src/components/layout/__tests__/OsDragOverlay.test.js`:**
|
|
```
|
|
import { describe, it } from 'vitest'
|
|
|
|
describe('UX-09: OsDragOverlay shows when OS files are dragged over the window', () => {
|
|
it.todo('overlay hidden by default (dragDepth=0)')
|
|
it.todo('dragenter with dataTransfer.types including "Files" shows overlay')
|
|
it.todo('dragenter without "Files" type is ignored (in-app element drag)')
|
|
it.todo('dragleave decrements depth; overlay hides when depth reaches 0')
|
|
it.todo('drop event emits files-dropped with the file list')
|
|
it.todo('drop resets dragDepth to 0 and hides overlay')
|
|
it.todo('overlay is teleported to body with z-[9998] (below toast z-[9999])')
|
|
})
|
|
```
|
|
|
|
**File H — `frontend/src/components/storage/__tests__/StorageBrowser.dragmove.test.js`:**
|
|
```
|
|
import { describe, it } from 'vitest'
|
|
|
|
describe('UX-11: Drag-to-move document onto folder row', () => {
|
|
it.todo('dragOverFolderId set on @dragover applies bg-amber-50 ring-2 ring-inset ring-amber-300 to that folder row')
|
|
it.todo('drop on folder emits file-move with { fileId, folderId }')
|
|
it.todo('dragend resets draggingFile to null after nextTick (click-after-drag guard)')
|
|
it.todo('click on file row is suppressed when draggingFile is non-null')
|
|
})
|
|
|
|
describe('UX-11 (toast wiring): doMove emits toast', () => {
|
|
it.todo('successful moveToFolder triggers useToastStore.show("Document moved", "success")')
|
|
it.todo('failed moveToFolder triggers useToastStore.show("Move failed: ...", "error")')
|
|
})
|
|
```
|
|
</action>
|
|
<verify>
|
|
<automated>cd frontend && npm run test -- --run keyboard.test && cd frontend && npm run test -- --run OsDragOverlay.test && cd frontend && npm run test -- --run dragmove.test</automated>
|
|
Expected: tests collected; all .todo entries reported as pending; suite does not fail.
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- All 3 files exist at the specified paths
|
|
- `keyboard.test.js` contains describe blocks for UX-05, UX-06, UX-07, UX-08 (4 distinct requirement IDs)
|
|
- `OsDragOverlay.test.js` contains describe for UX-09
|
|
- `StorageBrowser.dragmove.test.js` contains describe for UX-11
|
|
- Total `it.todo` count across the 3 files ≥ 18
|
|
- `cd frontend && npm run test -- --run` does not fail because of these files
|
|
- Grep: `grep -E "describe\\('UX-0[5678]" frontend/src/__tests__/keyboard.test.js | wc -l` returns 4
|
|
</acceptance_criteria>
|
|
<done>3 stub files exist covering UX-05..09 + UX-11.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- All 8 stub test files exist at the specified paths (5 from Task 1 + 3 from Task 2)
|
|
- `cd frontend && npm run test -- --run` continues to pass (no `.todo` produces a failure)
|
|
- Each of UX-02, UX-03, UX-04, UX-05, UX-06, UX-07, UX-08, UX-09, UX-11, UX-13, UX-14 appears in at least one stub `describe(...)` block
|
|
- Coverage check: `grep -rE "UX-0[23456789]|UX-1[1345]|UX-14" frontend/src/__tests__/keyboard.test.js frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js frontend/src/components/storage/__tests__/StorageBrowser.dragmove.test.js frontend/src/components/layout/__tests__/AppSidebar.empty.test.js frontend/src/views/admin/__tests__/AdminAuditView.skeleton.test.js frontend/src/views/admin/__tests__/AdminUsersView.skeleton.test.js frontend/src/components/layout/__tests__/OsDragOverlay.test.js frontend/src/components/ui/__tests__/dropdown.test.js | wc -l` returns ≥ 11
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
Every requirement implemented in Waves 1-4 has a placeholder test that will be promoted to a real assertion when the implementing wave runs. Wave verification can detect when a requirement has been forgotten by checking that the corresponding `.todo` was upgraded to a real `it(...)`.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
Create `.planning/phases/10-ux-interaction/10-05-SUMMARY.md` when done. Include the total count of `it.todo` stubs created.
|
|
</output>
|