diff --git a/frontend/src/components/cloud/CloudCredentialModal.vue b/frontend/src/components/cloud/CloudCredentialModal.vue
new file mode 100644
index 0000000..81f85b7
--- /dev/null
+++ b/frontend/src/components/cloud/CloudCredentialModal.vue
@@ -0,0 +1,195 @@
+
+
+
+
+
+
Connect {{ provider?.label }}
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/settings/SettingsAiTab.vue b/frontend/src/components/settings/SettingsAiTab.vue
new file mode 100644
index 0000000..dcf04f6
--- /dev/null
+++ b/frontend/src/components/settings/SettingsAiTab.vue
@@ -0,0 +1,9 @@
+
+
+ AI configuration
+
+ AI provider and model are managed by your administrator. Contact your admin
+ to request changes to which AI provider is used for your documents.
+
+
+
diff --git a/frontend/src/components/settings/SettingsCloudTab.vue b/frontend/src/components/settings/SettingsCloudTab.vue
new file mode 100644
index 0000000..a2d0cde
--- /dev/null
+++ b/frontend/src/components/settings/SettingsCloudTab.vue
@@ -0,0 +1,260 @@
+
+
+
+ Cloud Storage
+ Connect a cloud storage provider to use as a document destination.
+
+
+ Loading...
+
+
+
+
+
+
+
+
+
+
+
+
{{ provider.label }}
+
+
+ {{ statusBadgeLabel(connectionFor(provider.key)?.status ?? 'not_connected') }}
+
+
+
+ Connected {{ new Date(connectionFor(provider.key).connected_at).toLocaleDateString() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Your {{ provider.label }} connection needs to be re-authorized.
+ Click Reconnect {{ provider.label }} to restore access.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/settings/SettingsPreferencesTab.vue b/frontend/src/components/settings/SettingsPreferencesTab.vue
new file mode 100644
index 0000000..f7b5f33
--- /dev/null
+++ b/frontend/src/components/settings/SettingsPreferencesTab.vue
@@ -0,0 +1,65 @@
+
+
+
+
+
diff --git a/frontend/src/components/settings/__tests__/SettingsCloudTab.test.js b/frontend/src/components/settings/__tests__/SettingsCloudTab.test.js
new file mode 100644
index 0000000..562cac1
--- /dev/null
+++ b/frontend/src/components/settings/__tests__/SettingsCloudTab.test.js
@@ -0,0 +1,59 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { mount } from '@vue/test-utils'
+import { createPinia, setActivePinia } from 'pinia'
+
+// Mock store module before importing component (W4 — CLAUDE.md unit test requirement)
+vi.mock('../../../stores/cloudConnections.js', () => ({
+ useCloudConnectionsStore: () => ({
+ connections: [],
+ loading: false,
+ error: null,
+ fetchConnections: vi.fn(),
+ disconnect: vi.fn(),
+ disconnectAll: vi.fn(),
+ }),
+}))
+
+// Mock api/client.js to avoid HTTP calls
+vi.mock('../../../api/client.js', () => ({
+ connectWebDav: vi.fn(),
+ listCloudConnections: vi.fn(),
+ disconnectCloud: vi.fn(),
+}))
+
+import SettingsCloudTab from '../SettingsCloudTab.vue'
+
+const globalPlugins = {
+ plugins: [createPinia()],
+ stubs: {
+ // Stub CloudCredentialModal to avoid portal/teleport complexity in tests
+ CloudCredentialModal: {
+ template: '
',
+ props: ['show', 'provider'],
+ },
+ },
+}
+
+beforeEach(() => {
+ setActivePinia(createPinia())
+ vi.clearAllMocks()
+})
+
+describe('SettingsCloudTab', () => {
+ it('renders all 4 provider rows', () => {
+ const wrapper = mount(SettingsCloudTab, { global: globalPlugins })
+ expect(wrapper.text()).toContain('Google Drive')
+ expect(wrapper.text()).toContain('OneDrive')
+ expect(wrapper.text()).toContain('Nextcloud')
+ expect(wrapper.text()).toContain('WebDAV')
+ })
+
+ it('shows Connect buttons when no connections active', () => {
+ const wrapper = mount(SettingsCloudTab, { global: globalPlugins })
+ const buttons = wrapper.findAll('button')
+ expect(buttons.length).toBeGreaterThan(0)
+ // At least some "Connect" buttons should be visible when no connections
+ const buttonTexts = buttons.map(b => b.text()).join(' ')
+ expect(buttonTexts).toContain('Connect')
+ })
+})
diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue
index 4c54837..708082c 100644
--- a/frontend/src/views/SettingsView.vue
+++ b/frontend/src/views/SettingsView.vue
@@ -1,79 +1,132 @@
Settings
-
Account-level options for your DocuVault workspace.
+
Account-level options for your DocuVault workspace.
-
- AI configuration
-
- AI provider and model are managed by your administrator. Contact your admin
- to request changes to which AI provider is used for your documents.
-
-
+
+
+
+
+
{{ providerDisplayName(oauthSuccessProvider) }} connected
+
Your files are now available in the sidebar.
+
+
+
-
-
- Document Preferences
- Choose how PDF documents open when you click on them.
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
Connection failed
+
{{ oauthError }}
+
Try connecting again. If the problem persists, check that the app has the correct permissions in your provider's account settings.
+
+
-
-
{{ saveFeedback }}
-
{{ saveError }}
-
+
+
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
index ebd6662..7d0bf9e 100644
--- a/frontend/vite.config.js
+++ b/frontend/vite.config.js
@@ -3,6 +3,10 @@ import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
+ build: {
+ // top-level await in main.js requires esnext target
+ target: 'esnext',
+ },
server: {
host: '0.0.0.0',
port: 5173,