Shared utilities:
- Add src/utils/formatters.js — formatDate, formatSize, providerColor,
providerBg, providerLabel; all components import from here, no inline duplicates
- Add src/components/ui/TreeItem.vue — generic expand/collapse tree node;
FolderTreeItem, CloudFolderTreeItem, CloudProviderTreeItem now wrap it
- Add src/components/storage/StorageBrowser.vue — unified file browser grid
used by both FileManagerView and CloudFolderView
View refactor (thin data-providers):
- FileManagerView.vue: stripped to props + event wiring; all layout moved to StorageBrowser
- CloudFolderView.vue: same treatment — feeds props into StorageBrowser
- All tree sidebar components delegate expand/collapse to TreeItem.vue
Dead code removed:
- Delete HomeView.vue — no active route, replaced by FileManagerView
- Delete FolderView.vue — no active route, logic merged into FileManagerView
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add backend/ai/utils.py — parse_classification, parse_suggestions, strip_code_fences
shared by all AI providers; removes duplicated private functions from
anthropic_provider.py and openai_provider.py
- Add backend/deps/utils.py — get_client_ip, parse_uuid request-parsing helpers;
removes local _ip() variants from admin.py, auth.py, shares.py, folders.py
- Add backend/storage/exceptions.py — canonical CloudConnectionError definition;
all routers and backends import from here instead of redefining
- Move validate_password_strength to backend/services/auth.py; removes duplicated
_validate_password_strength from admin.py and auth.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- auth.py: store attempted_email in metadata_ and link user_id when the account exists (wrong password case); previously logged no PII at all
- AuditLogTab: Email column falls back to metadata_.attempted_email in amber with "(attempted)" label when no confirmed user_email is available
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backend: add user_email to _build_filtered_query_with_handles (UserSubject join) and _audit_to_dict_with_handles; propagate through JSON viewer and CSV export including empty-result path
- Frontend: AuditLogTab adds Email column between User and Action Type; removes @ prefix from handle cell
- Test: update expected CSV header to include user_email
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- shares.py grant_share: include recipient_handle in response so ShareModal shows the name immediately without reload
- FileManagerView: add Shared pill badge next to document name (badge only existed in DocumentCard, not the main file manager view)
- FileManagerView ShareModal: wire @unshared to clear is_shared flag when last recipient is removed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AccountView: remove hardcoded @ prefix so handle matches what share dialog expects
- documents store: set is_shared=true optimistically after successful share so badge shows without refetch
- GET /api/documents/{id}: allow recipients of an active share to view the document (was returning 404 for non-owners)
- ShareModal: move Share button to its own full-width row so it no longer overflows the input area
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Plain MagicMock() failed the isinstance(backend, MinIOBackend) guard in
download_daily_export(), returning 404. spec=MinIOBackend sets __class__
so isinstance passes and the mock path executes correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
str(uuid_obj) produces dashed 36-char format; SQLite stores UUID as 32-char
hex without dashes, so WHERE user_id = :uid never matched. Using .hex fixes
confirm_upload (api/documents.py) and delete_document (services/storage.py).
Removes stale xfail from test_delete_decrements_quota — now passes on SQLite.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- App.vue: conditionally renders AuthLayout for auth routes, app shell otherwise
- router/index.js: meta.layout='auth' on all four auth routes
- router/index.js: meta.requiresAdmin=true on /admin route
- router/index.js: beforeEach role check redirects non-admin to /
- router/index.js: /account redirects to /settings