diff --git a/.planning/phases/11-visual-design-responsive-layout-cleanup/11-VALIDATION.md b/.planning/phases/11-visual-design-responsive-layout-cleanup/11-VALIDATION.md new file mode 100644 index 0000000..741e654 --- /dev/null +++ b/.planning/phases/11-visual-design-responsive-layout-cleanup/11-VALIDATION.md @@ -0,0 +1,109 @@ +--- +phase: 11 +slug: visual-design-responsive-layout-cleanup +status: partial +nyquist_compliant: false +wave_0_complete: true +created: 2026-06-17 +updated: 2026-06-17 +--- + +# Phase 11 - Validation Strategy + +> Retroactive Nyquist validation for Phase 11: Visual Design, Responsive Layout & Cleanup. + +--- + +## Test Infrastructure + +| Property | Value | +|----------|-------| +| Framework | Vitest 4.1.7 + Vue Test Utils + happy-dom | +| Config file | `frontend/vitest.config.js` | +| Quick run command | `cd frontend && npm run test -- --run src/components/ui/__tests__/TreeItem.test.js` | +| Full suite command | `cd frontend && npm run test -- --run` | +| Build gate | `cd frontend && npm run build` | +| Estimated runtime | ~4 seconds for full Vitest, ~2 seconds for build | + +--- + +## Sampling Rate + +- After every Phase 11 task commit: run the focused component/router test touched by the task. +- After every Phase 11 plan: run `cd frontend && npm run test -- --run`. +- Before `/gsd:verify-work`: run `cd frontend && npm run test -- --run` and `cd frontend && npm run build`. +- Max feedback latency: under 10 seconds locally for the automated frontend gate. + +--- + +## Per-Task Verification Map + +| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status | +|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------| +| 11-01-01 | 11-01 | 1 | PERF-02 | - | Analyzer is opt-in and normal builds do not emit `stats.html` | build/static | `cd frontend && npm run build`; `cd frontend && ANALYZE=true npm run build` | yes: `.planning/perf/phase11-baseline.html`, `.planning/perf/phase11-baseline-summary.md` | green | +| 11-02-01 | 11-02 | 1 | PERF-03 | S-01 | Lazy route components preserve auth/admin guard behavior | unit/integration | `cd frontend && npm run test -- --run src/router/__tests__/router.guard.test.js` | yes | green | +| 11-03-01 | 11-03 | 1 | RESP-01 | - | User drawer opens, closes by backdrop, and closes on route change | unit | `cd frontend && npm run test -- --run src/__tests__/keyboard.test.js` | yes | green | +| 11-03-02 | 11-03 | 1 | RESP-02 | - | Storage list uses mobile-first columns; size hidden below `md`, modified hidden below `sm` | unit | `cd frontend && npm run test -- --run src/components/storage/__tests__/StorageBrowser.skeleton.test.js` | yes | green | +| 11-03-03 | 11-03 | 1 | RESP-03 | - | Storage row action buttons expose 36px mobile touch target classes | unit | `cd frontend && npm run test -- --run src/components/storage/__tests__/StorageBrowser.skeleton.test.js` | yes | green | +| 11-03-04 | 11-03 | 1 | RESP-05 | - | Admin drawer mirrors the user drawer pattern | unit | `cd frontend && npm run test -- --run src/__tests__/keyboard.test.js` | yes | green | +| 11-04-01 | 11-04 | 1 | VISUAL-02 | T-01 | Forms keep normalized focus classes and rely on active Tailwind forms plugin | unit/static | `cd frontend && npm run test -- --run src/components/sharing/__tests__/ShareModal.mobile.test.js src/components/cloud/__tests__/CloudCredentialModal.mobile.test.js` | yes | green | +| 11-04-02 | 11-04 | 1 | RESP-04 | - | Modals fit small viewports with scrollable panels or responsive full-screen header | unit | `cd frontend && npm run test -- --run src/components/sharing/__tests__/ShareModal.mobile.test.js src/components/cloud/__tests__/CloudCredentialModal.mobile.test.js src/components/folders/__tests__/FolderDeleteModal.mobile.test.js src/components/documents/__tests__/DocumentPreviewModal.mobile.test.js` | yes | green | +| 11-05-01 | 11-05 | 1 | VISUAL-01 | - | Skeleton widths use Tailwind width classes, not inline width styles | unit/static | `cd frontend && npm run test -- --run src/components/layout/__tests__/AppSidebar.visual.test.js src/components/ui/__tests__/TreeItem.test.js` | yes | green | +| 11-05-02 | 11-05 | 1 | VISUAL-03 | T-01 | TreeItem interactive elements expose focus-visible rings; sidebar controls retain focus rings | unit/static | `cd frontend && npm run test -- --run src/components/layout/__tests__/AppSidebar.visual.test.js src/components/ui/__tests__/TreeItem.test.js` | yes | green | +| 11-05-03 | 11-05 | 1 | VISUAL-04 | - | Page and panel headings use normalized font size/weight classes | unit/static | `cd frontend && npm run test -- --run src/views/__tests__/typography.visual.test.js` | yes | green | +| 11-06-01 | 11-06 | 1 | CODE-07 | - | Confirmed dead route/components removed and stale tests deleted | static/test | `find frontend/src -name 'AccountView.vue' -o -name 'FolderRow.vue' -o -name 'HomeView.vue' -o -name 'FolderView.vue' -o -name 'AdminView.vue'`; `cd frontend && npm run test -- --run` | yes | green | +| 11-06-02 | 11-06 | 1 | PERF-02 | - | Final bundle report exists and compares baseline to final chunks | build/static | `cd frontend && npm run build`; `cd frontend && ANALYZE=true npm run build` | yes: `.planning/perf/phase11-final.html`, `.planning/perf/phase11-final-summary.md` | green | + +--- + +## Wave 0 Requirements + +Existing infrastructure covered the phase requirements. No Wave 0 framework installation was needed. + +Retroactive validation added direct TreeItem assertions in `frontend/src/components/ui/__tests__/TreeItem.test.js` for: + +- `VISUAL-01`: loading skeleton labels use `w-12`, `w-16`, `w-20` and do not set inline width. +- `VISUAL-03`: expand button, selectable row button, and router-link row include `focus-visible` ring classes. + +--- + +## Manual-Only Verifications + +| Behavior | Requirement | Why Manual | Test Instructions | +|----------|-------------|------------|-------------------| +| Cross-browser form rendering | VISUAL-02 | Vitest can assert classes and plugin wiring, but cannot prove Chrome/Firefox/Safari native control rendering is visually identical. | Open ShareModal, CloudCredentialModal, and SettingsAccountTab in Chrome, Firefox, and Safari. Inspect inputs, selects, textareas, checkboxes, and focus states for consistent appearance. | +| Focus ring visibility at 375px | VISUAL-03, RESP-01, RESP-05 | DOM tests assert focus classes and drawer state, but visual focus contrast and clipping require browser rendering. | In a 375x667 viewport, tab through hamburger buttons, sidebar links, StorageBrowser actions, and TreeItem expand/label controls. Confirm focus rings are visible and not clipped. | +| Touch target usability at 375px | RESP-03 | Tests assert `min-w-[36px] min-h-[36px]`; actual tap comfort requires emulated or physical touch input. | At 375px width, tap Rename, Delete, Share, and Move icon buttons in document/folder rows. Confirm each tap registers without precision targeting. | + +--- + +## Validation Audit 2026-06-17 + +| Metric | Count | +|--------|-------| +| Requirements mapped | 12 | +| Automated coverage green | 12 | +| Automated gaps found | 1 | +| Automated gaps resolved | 1 | +| Manual-only checks remaining | 3 | + +### Commands Run + +| Command | Result | +|---------|--------| +| `cd frontend && npm run test -- --run src/components/ui/__tests__/TreeItem.test.js` | pass: 1 file, 6 tests | +| `cd frontend && npm run test -- --run` | pass: 36 files, 271 tests | +| `cd frontend && npm run build` | pass: production build succeeded; existing `auth.js` mixed static/dynamic import warning remains | + +--- + +## Validation Sign-Off + +- [x] All tasks have automated verify commands or documented build/static evidence. +- [x] Sampling continuity: no 3 consecutive Phase 11 tasks lack automated verification. +- [x] Wave 0 not required; existing Vitest infrastructure covers frontend validation. +- [x] No watch-mode flags used. +- [x] Feedback latency is under 10 seconds for the full local frontend gate. +- [ ] `nyquist_compliant: true` set in frontmatter. + +**Approval:** partial 2026-06-17. Automated Phase 11 coverage is green; final sign-off still requires the three manual visual/touch checks above. diff --git a/frontend/src/components/ui/__tests__/TreeItem.test.js b/frontend/src/components/ui/__tests__/TreeItem.test.js index 732845e..3e5f1e7 100644 --- a/frontend/src/components/ui/__tests__/TreeItem.test.js +++ b/frontend/src/components/ui/__tests__/TreeItem.test.js @@ -66,3 +66,59 @@ describe('Gap 1 regression: non-loading branches unchanged', () => { expect(w.text()).toContain('Empty') }) }) + +describe('Phase 11 validation: TreeItem visual invariants', () => { + it('applies focus-visible rings to the expand control and selectable row button', () => { + const w = mountItem() + const buttons = w.findAll('button') + + expect(buttons).toHaveLength(2) + for (const button of buttons) { + const cls = button.attributes('class') || '' + expect(cls).toContain('focus-visible:outline-none') + expect(cls).toContain('focus-visible:ring-2') + expect(cls).toContain('focus-visible:ring-indigo-500') + expect(cls).toContain('focus-visible:ring-offset-1') + } + }) + + it('applies focus-visible rings to the router-link row', () => { + const w = mountItem({ to: '/folders/archive' }) + const link = w.findComponent({ name: 'RouterLink' }) + + expect(link.exists()).toBe(true) + const cls = link.attributes('class') || '' + expect(cls).toContain('focus-visible:outline-none') + expect(cls).toContain('focus-visible:ring-2') + expect(cls).toContain('focus-visible:ring-indigo-500') + expect(cls).toContain('focus-visible:ring-offset-1') + }) + + it('uses Tailwind width classes for loading skeleton labels without inline width styles', async () => { + let resolveFn + const loadChildren = vi.fn() + .mockReturnValueOnce(Promise.resolve([])) + .mockReturnValue(new Promise(resolve => { resolveFn = resolve })) + const w = mountItem({ loadChildren }) + + await w.vm.$.setupState.toggleExpand() + await flushPromises() + w.vm.$.setupState.refresh() + await nextTick() + + const skeletonLabels = w.findAll('.h-3.animate-pulse') + expect(skeletonLabels).toHaveLength(3) + expect(skeletonLabels.map(el => el.classes().find(cls => /^w-\d+$/.test(cls)))).toEqual([ + 'w-12', + 'w-16', + 'w-20', + ]) + + for (const label of skeletonLabels) { + expect(label.attributes('style') || '').not.toMatch(/width\s*:/) + } + + resolveFn([]) + await flushPromises() + }) +})