diff --git a/AGENTS.md b/AGENTS.md index b078991f2..91b2c98da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,7 +54,7 @@ Property-panel anatomy in `packages/vue/src/primitives/PropertySection/`, `Segme Credential persistence lives under `src/app/settings/credentials/`. Settings components receive `CredentialManager` and may inspect status, replace, or clear credentials; runtime adapters receive `CredentialResolver`. Components must not read saved secrets or keep them in long-lived reactive refs. Non-secret provider preferences remain in normal settings storage. -Tauri stores secrets in the native system credential store through `desktop/src/credentials.rs`; browser sessions default to memory and may explicitly opt into WebCrypto-encrypted IndexedDB storage. Native failures must never silently fall back to browser or plaintext storage. New integration credentials use stable `CredentialRef` values and join the unified Settings surface rather than adding feature-local key forms. +Tauri stores secrets in the native system credential store through `desktop/src/credentials.rs`; browsers default to WebCrypto-encrypted IndexedDB storage and may explicitly opt out to session-only memory. Native failures must never silently fall back to browser or plaintext storage. New integration credentials use stable `CredentialRef` values and join the unified Settings surface rather than adding feature-local key forms. Storage-provider schemas and runtime adapters live under `src/app/integrations/storage/`; non-secret preferences and credential references stay separate, and adapters resolve secrets at operation time. Local-first document caching and outbox synchronization live under `src/app/storage/`. A remote storage binding augments document source state and must not replace local file identity. diff --git a/CHANGELOG.md b/CHANGELOG.md index 142564984..9bcb521af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ - Drag with the Text tool to create a fixed-size text box, or click to create auto-width text. - Target a specific open document and page from live CLI and MCP automation, including sessions with multiple documents. - Test OpenAI-compatible provider connections from AI settings with clearer setup errors. -- Manage AI, agent, and media credentials from unified Settings, using the system credential store on desktop and optional encrypted storage in the browser. +- Manage AI, agent, and media credentials from unified Settings, using the system credential store on desktop and encrypted browser storage by default with a session-only option. - Assign separate Design, Review, Fast, and Vision models, providers, endpoints, and credentials from AI settings. - Connect an S3-compatible storage workspace with local-first saves, background synchronization, and centrally managed credentials. - Convert image layers into editable vector layers with Recraft or fal.ai from the canvas context menu. (#322) diff --git a/src/app/settings/credentials/storage.ts b/src/app/settings/credentials/storage.ts index 7785e8302..1d8a6ea47 100644 --- a/src/app/settings/credentials/storage.ts +++ b/src/app/settings/credentials/storage.ts @@ -20,12 +20,12 @@ export function hasLegacyCredentialStorage(): boolean { } export function browserRemembersCredentials(): boolean { - return browserCredentialStorage()?.getItem(BROWSER_PERSISTENCE_KEY) === 'remembered' + const storage = browserCredentialStorage() + return storage ? storage.getItem(BROWSER_PERSISTENCE_KEY) !== 'session' : false } export function setBrowserRemembersCredentials(remembered: boolean): void { const storage = browserCredentialStorage() if (!storage) return - if (remembered) storage.setItem(BROWSER_PERSISTENCE_KEY, 'remembered') - else storage.removeItem(BROWSER_PERSISTENCE_KEY) + storage.setItem(BROWSER_PERSISTENCE_KEY, remembered ? 'remembered' : 'session') } diff --git a/src/components/settings/provider/ProviderSettingsLink.vue b/src/components/settings/provider/ProviderSettingsLink.vue index 9e3e8067f..682a616aa 100644 --- a/src/components/settings/provider/ProviderSettingsLink.vue +++ b/src/components/settings/provider/ProviderSettingsLink.vue @@ -6,7 +6,12 @@ const { href } = defineProps<{ href: string }>() diff --git a/tests/e2e/chat/panel.spec.ts b/tests/e2e/chat/panel.spec.ts index 8c26173e3..b5847ed0a 100644 --- a/tests/e2e/chat/panel.spec.ts +++ b/tests/e2e/chat/panel.spec.ts @@ -150,9 +150,11 @@ test('saving API key in unified settings shows chat interface', async () => { await expect(page.getByTestId('app-settings-dialog')).toBeVisible() await expect(page.getByTestId('settings-remember-credentials')).toHaveAttribute( 'data-state', - 'unchecked' + 'checked' + ) + await expect(page.getByTestId('settings-credential-backend')).toContainText( + 'encrypted browser storage' ) - await expect(page.getByTestId('settings-credential-backend')).toContainText('this session only') await page.locator('[data-model-id]').first().click() await page.getByTestId('settings-model-provider').click() await page.getByRole('option', { name: 'OpenRouter' }).click() @@ -270,10 +272,12 @@ test('transport errors show an actionable toast', async () => { }) test('"Get API key" link opens external URL via window.open', async () => { - await page.evaluate("localStorage.removeItem('open-pencil:ai-key:openrouter')") - await page.reload() - await canvas.waitForInit() - await chatTab().click() + await page.getByTestId('provider-settings-trigger').click() + await page.locator('[data-model-id]').first().click() + await page.getByTestId('provider-settings-clear-key').click() + await page.getByRole('button', { name: 'Back' }).click() + await page.getByTestId('app-settings-done').click() + await expect(page.getByTestId('provider-setup-open-settings')).toBeVisible() await page.getByTestId('provider-setup-open-settings').click() await page.locator('[data-model-id]').first().click() await page.getByTestId('settings-model-provider').click() diff --git a/tests/e2e/settings/credentials.spec.ts b/tests/e2e/settings/credentials.spec.ts index bf3648ca1..7efa35766 100644 --- a/tests/e2e/settings/credentials.spec.ts +++ b/tests/e2e/settings/credentials.spec.ts @@ -80,8 +80,6 @@ test('remembered browser credentials survive reload and clear centrally', async await page.getByTestId('provider-setup-open-settings').click() const remember = page.getByTestId('settings-remember-credentials') - await expect(remember).toHaveAttribute('data-state', 'unchecked') - await remember.click() await expect(remember).toHaveAttribute('data-state', 'checked') await expect(page.getByTestId('settings-credential-backend')).toContainText( 'encrypted browser storage'