--- phase: "06.2" plan: "05" type: execute wave: 3 depends_on: - "06.2-04" files_modified: - frontend/src/views/AccountView.vue - frontend/src/components/admin/AdminUsersTab.vue - frontend/src/views/CloudFolderView.vue - frontend/src/components/admin/AuditLogTab.vue autonomous: true gap_closure: true requirements: - SHARE-03 - ADMIN-06 must_haves: truths: - "User can see their own @handle in Account settings — enabling them to share their handle with others for document sharing" - "Admin can see each user's handle in the Users tab — enabling handle lookup for support and sharing" - "Cloud folder browser shows an actionable error when no cloud connection exists — directing user to Settings" - "Audit log entries display @alice style handles (@ prefix present)" - "Export CSV button shows active filter count when filters are set — user understands export scope before clicking" - "Clear filters button in Audit Log tab resets all filters and re-fetches unfiltered data" artifacts: - path: "frontend/src/views/AccountView.vue" provides: "Handle row in Account information section" contains: "authStore.user?.handle" - path: "frontend/src/components/admin/AdminUsersTab.vue" provides: "Handle column in users table" contains: "user.handle" - path: "frontend/src/views/CloudFolderView.vue" provides: "Actionable no-connection error message" contains: "Settings" - path: "frontend/src/components/admin/AuditLogTab.vue" provides: "@ prefix on handles, Clear filters button, active filter count indicator" contains: "clearFilters" key_links: - from: "AccountView.vue" to: "authStore.user" via: "authStore.user?.handle (already present in /api/auth/me response)" pattern: "authStore.user\\?.handle" - from: "AdminUsersTab.vue user row" to: "adminListUsers() response" via: "user.handle (backend returns handle in GET /api/admin/users)" pattern: "user\\.handle" - from: "AuditLogTab.vue entry.user_handle" to: "rendered cell" via: "template expression with @ prefix" pattern: "'@' \\+ entry.user_handle" --- Close the four UAT-diagnosed gaps from 06.2-UAT.md: (1) user handle invisible in account settings and admin user list, (2) cloud folder browser shows unhelpful error when no connection exists, (3) audit log handle entries missing @ prefix, (4) CSV export gives no indication of active filters and no way to clear them. Purpose: These gaps block real usage — users cannot share documents because handles are invisible, cloud storage is unusable with no diagnostic guidance, audit logs look wrong without @ prefixes, and CSV exports silently export filtered (possibly empty) data. Output: Four targeted frontend changes across four files. No backend changes required. @/Users/nik/Documents/Progamming/document_scanner/.planning/phases/06.2-close-v1-sharing-cloud-delete-csv-export-gaps/06.2-UAT.md @/Users/nik/Documents/Progamming/document_scanner/.planning/ROADMAP.md @/Users/nik/Documents/Progamming/document_scanner/.planning/phases/06.2-close-v1-sharing-cloud-delete-csv-export-gaps/06.2-CONTEXT.md From frontend/src/views/AccountView.vue (Account information section, lines 8-24):

Account information

Email: {{ authStore.user?.email }}
Role: {{ authStore.user?.role }}
From frontend/src/components/admin/AdminUsersTab.vue (table head, lines 113-119): Email Role Status Created Actions From frontend/src/views/CloudFolderView.vue (load function, lines 126-137): async function load() { loading.value = true error.value = '' try { const data = await api.getCloudFolders(provider.value, folderId.value ?? 'root') items.value = data.items ?? [] } catch (e) { error.value = e.message || 'Failed to load folder contents' } finally { loading.value = false } }
{{ error }}
From frontend/src/components/admin/AuditLogTab.vue (relevant section): {{ entry.user_handle || entry.user_id || '—' }} const filters = reactive({ start: '', end: '', user_handle: '', event_type: '', })
Task 1: Show handle in AccountView and AdminUsersTab frontend/src/views/AccountView.vue, frontend/src/components/admin/AdminUsersTab.vue CHANGE 1 — frontend/src/views/AccountView.vue In the "Account information" section (lines 8-24), add a Username row immediately after the Email row and before the Role row. The new row follows the same pattern as the Email row:
Username: @{{ authStore.user?.handle }}
Place this line between the email div and the role div. The @ is a literal character prepended to the handle value so users immediately recognise it as their sharing handle. No script changes needed — authStore.user?.handle is already available. CHANGE 2 — frontend/src/components/admin/AdminUsersTab.vue Add a "Handle" column to the users table so admins can look up other users' handles. In the `` row (after the Email th and before the Role th), add: Handle In the `` rows (after the email `` and before the role ``), add: {{ user.handle ? '@' + user.handle : '—' }} No script changes needed — adminListUsers() already returns handle in the user object (confirmed in backend/api/admin.py line 63).
grep -n "authStore.user?.handle" /Users/nik/Documents/Progamming/document_scanner/frontend/src/views/AccountView.vue && grep -n "user\.handle" /Users/nik/Documents/Progamming/document_scanner/frontend/src/components/admin/AdminUsersTab.vue | grep -v "handle:" - `grep "authStore.user?.handle" frontend/src/views/AccountView.vue` returns a match in the template section - `grep "user\.handle" frontend/src/components/admin/AdminUsersTab.vue` returns a match in both thead and tbody - `cd frontend && npm run build 2>&1 | grep -i "error" | grep -v "^>"` returns no output
Task 2: Actionable cloud connection error and audit log @ prefix frontend/src/views/CloudFolderView.vue, frontend/src/components/admin/AuditLogTab.vue CHANGE 1 — frontend/src/views/CloudFolderView.vue Replace the generic error handler in the `load()` function with one that distinguishes "no connection" from a general error. The backend returns a response whose error detail contains "No active connection" (or HTTP 404) when no cloud provider is connected. Replace the catch block in load(): } catch (e) { const msg = e.message || '' if (msg.toLowerCase().includes('no active connection') || msg.includes('404') || msg.toLowerCase().includes('not found')) { error.value = 'No cloud provider connected. Go to Settings to connect a cloud storage account.' } else { error.value = msg || 'Failed to load folder contents.' } } Also update the error template block (lines 36-39) to add a Settings link. Replace the existing error div with:

{{ error }}

Go to Settings
Confirm `router-link` is usable here — `useRouter` and `useRoute` are already imported from 'vue-router' in the script setup. CHANGE 2 — frontend/src/components/admin/AuditLogTab.vue Change the user handle cell (line 95) from: {{ entry.user_handle || entry.user_id || '—' }} to: {{ entry.user_handle ? '@' + entry.user_handle : (entry.user_id || '—') }} This is a template-only one-liner change. No script changes required.
grep -n "No cloud provider connected" /Users/nik/Documents/Progamming/document_scanner/frontend/src/views/CloudFolderView.vue && grep -n "'@' + entry.user_handle" /Users/nik/Documents/Progamming/document_scanner/frontend/src/components/admin/AuditLogTab.vue - `grep "No cloud provider connected" frontend/src/views/CloudFolderView.vue` returns a match - `grep "Go to Settings" frontend/src/views/CloudFolderView.vue` returns a match - `grep "'@' + entry.user_handle" frontend/src/components/admin/AuditLogTab.vue` returns a match - `grep "entry.user_handle || entry.user_id" frontend/src/components/admin/AuditLogTab.vue` returns NO match (old pattern gone) - `cd frontend && npm run build 2>&1 | grep -i "error" | grep -v "^>"` returns no output
Task 3: Clear filters button and active filter count indicator in AuditLogTab frontend/src/components/admin/AuditLogTab.vue Add two UX improvements to AuditLogTab.vue to make the CSV export scope transparent. CHANGE 1 — Add clearFilters() function in the script setup section: Add the following function after the existing applyFilters() function: function clearFilters() { filters.start = '' filters.end = '' filters.user_handle = '' filters.event_type = '' page.value = 1 fetchLog() } Also add a computed property (or inline expression) for active filter count. Add this computed after the clearFilters() function: import { computed } from 'vue' // add computed to the existing vue import if not present const activeFilterCount = computed(() => { let count = 0 if (filters.start) count++ if (filters.end) count++ if (filters.user_handle) count++ if (filters.event_type) count++ return count }) NOTE: `computed` must be added to the existing `import { ref, reactive, onMounted }` line at the top of the script. Change it to `import { ref, reactive, onMounted, computed }`. CHANGE 2 — Add "Clear filters" button to filter bar in the template: In the filter bar (the `
` block), add a "Clear filters" button immediately after the existing "Apply filters" button. Only show it when filters are active: CHANGE 3 — Add active filter count indicator near Export CSV button: Wrap the existing Export CSV button in a relative container and add a badge showing the active filter count when non-zero. Replace the standalone Export CSV button block with:
{{ activeFilterCount }} filter{{ activeFilterCount !== 1 ? 's' : '' }} active
The amber text "N filter(s) active" sits directly below the Export CSV button so users see at a glance that the download will be scoped. The existing `

` block remains unchanged immediately after this new wrapper div. grep -n "clearFilters\|activeFilterCount\|Clear filters\|filters active" /Users/nik/Documents/Progamming/document_scanner/frontend/src/components/admin/AuditLogTab.vue | head -15 - `grep "clearFilters" frontend/src/components/admin/AuditLogTab.vue` returns at least 2 matches (definition + @click binding) - `grep "activeFilterCount" frontend/src/components/admin/AuditLogTab.vue` returns at least 3 matches (computed definition + v-if + template text) - `grep "Clear filters" frontend/src/components/admin/AuditLogTab.vue` returns a match in the template - `grep "filters active" frontend/src/components/admin/AuditLogTab.vue` returns a match - `grep "computed" frontend/src/components/admin/AuditLogTab.vue` returns a match in the import line - `cd frontend && npm run build 2>&1 | grep -i "error" | grep -v "^>"` returns no output ## Trust Boundaries | Boundary | Description | |----------|-------------| | AccountView → authStore.user | handle is read from in-memory Pinia store — never from localStorage; no user-supplied input involved | | CloudFolderView → error message | error text originates from backend API response; rendered via Vue template auto-escaping (no innerHTML) — XSS risk mitigated | | AuditLogTab → entry.user_handle | handle value from API response rendered via Vue template auto-escaping — no innerHTML | ## STRIDE Threat Register | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| | T-06.2-05-01 | Information Disclosure | Handle visible in AccountView | accept | Handle is already a public-within-platform identifier (used as share target); displaying it to the owning user is expected and correct | | T-06.2-05-02 | Information Disclosure | Handle visible in AdminUsersTab | accept | Admin already has access to email; handle is lower-sensitivity than email; admin-only endpoint already enforces get_current_admin | | T-06.2-05-03 | XSS | Cloud error message rendered from API response | mitigate | Vue template auto-escaping prevents XSS; the error string is interpolated via {{ }} not v-html — no raw HTML injection possible | | T-06.2-05-04 | XSS | @ + entry.user_handle rendered in table | mitigate | String concatenation in Vue template expression is auto-escaped — not v-html | | T-06.2-05-SC | Tampering | npm/pip/cargo installs | accept | No new packages installed in this plan — frontend-only template and script changes only | After all three tasks complete: Build check (no errors): ``` cd /Users/nik/Documents/Progamming/document_scanner/frontend && npm run build 2>&1 | grep -i "error" | grep -v "^>" | head -10 ``` Expected: no output. Gap 1 — Handle in AccountView: ``` grep -n "authStore.user?.handle" /Users/nik/Documents/Progamming/document_scanner/frontend/src/views/AccountView.vue ``` Expected: match in template section. Gap 1 — Handle in AdminUsersTab: ``` grep -n "user\.handle" /Users/nik/Documents/Progamming/document_scanner/frontend/src/components/admin/AdminUsersTab.vue ``` Expected: at least 2 matches (thead + tbody). Gap 2 — Cloud actionable error: ``` grep -n "No cloud provider connected\|Go to Settings" /Users/nik/Documents/Progamming/document_scanner/frontend/src/views/CloudFolderView.vue ``` Expected: 2 matches. Gap 3 — Audit log @ prefix: ``` grep -n "'@' + entry.user_handle" /Users/nik/Documents/Progamming/document_scanner/frontend/src/components/admin/AuditLogTab.vue ``` Expected: 1 match. Gap 4 — Clear filters + filter count: ``` grep -c "clearFilters\|activeFilterCount" /Users/nik/Documents/Progamming/document_scanner/frontend/src/components/admin/AuditLogTab.vue ``` Expected: 5 or more matches total. Backend test suite unaffected (no backend changes): ``` cd /Users/nik/Documents/Progamming/document_scanner/backend && pytest -x -q 2>&1 | tail -5 ``` Expected: exits 0, same pass count as before this plan. - Account settings page shows the user's own @handle in the Account information section - Admin Users tab includes a Handle column showing @handle for every user row - Cloud folder browser shows "No cloud provider connected. Go to Settings to connect a cloud storage account." (with a Settings link) when backend returns a no-connection error - Audit log table renders @alice style handles (@ prefix present on all non-null handles) - AuditLogTab has a "Clear filters" button (visible only when at least one filter is active) that resets all filters and re-fetches - Export CSV button area shows "N filter(s) active" in amber text when one or more filters are set - `npm run build` exits 0 with no errors - Backend pytest suite still passes (no regressions — this plan touches only frontend files) Create `.planning/phases/06.2-close-v1-sharing-cloud-delete-csv-export-gaps/06.2-05-SUMMARY.md` when done.