Add/db and speed (#1243)

* fix(privacy): make privacy policy page dark mode friendly

Replace the hardcoded light gradient background with bg-background and
add dark:prose-invert to the prose blocks so body text is readable on
dark cards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(cloud-backup): sync archives to Dropbox alongside Google Drive

Introduce a CloudStorageProvider interface so performSync builds the
archive set once and talks to storage only through it. Google Drive
keeps its existing behaviour; Dropbox is a second implementation, so
the compliance-relevant half (fingerprints, per-year layout, size
fallback, progressive persistence) cannot drift between targets.

Dropbox uses App folder access, matching the drive.file scope's "only
what the app created" guarantee. Uploads are single-shot under 8 MB and
chunked upload sessions above, every write verified against Dropbox's
content_hash. Call arguments are ASCII-escaped per UTF-16 code unit so
Swedish file names survive the Dropbox-API-Arg header.

Each provider owns its extension_data keys, schedule, failure counter
and alert throttle, so a dead Dropbox token cannot pause a healthy
Drive backup. The google_drive_* keys and the /oauth/callback path are
untouched: both are wire format for already-connected companies.

isConfigured() gates /connect only. A deployment that loses its OAuth
credentials must not trap users with a connection they cannot remove
or a schedule they cannot switch off.

Requires DROPBOX_APP_KEY and DROPBOX_APP_SECRET; the provider row
renders disabled without them. No migration: state is extension_data
JSON throughout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: remove merge-conflict markers committed in DECISIONS.md

The merge that brought main into this branch staged DECISIONS.md while
it still carried conflict markers, so cdc3a513 shipped an unresolved
hunk (compliance swarm ISO 27001 A.8.32).

DECISIONS.md is an append-only log, so both sides are kept: main's
systemdokumentation entry followed by this branch's Dropbox entries.
No decision was dropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-07-27 16:49:24 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent 1a7152a7af
commit fbd4b992f5
27 changed files with 2852 additions and 478 deletions
+18 -18
View File
@@ -387,9 +387,7 @@
"transaction_sync_enabled_toast": "Transaction sync enabled. History is fetched on the next sync.",
"transaction_sync_disabled_toast": "Transaction sync disabled.",
"transaction_sync_toggle_failed": "Could not save the setting. Please try again.",
"sync_done_transactions": "{imported} transaction(s) imported, {linked} linked to vouchers.",
"sync_done_feed": "{fetched} transaction(s) fetched: {imported} new in the inbox, {linked} linked to vouchers.",
"sync_done_empty": "Stripe returned no transactions for the period. If you expected transactions, check that the right account is connected."
"sync_done_transactions": "{imported} transaction(s) imported, {linked} linked to vouchers."
},
"settings_modal": {
"title": "Settings",
@@ -4509,19 +4507,20 @@
"ext_mcp_server_long_description": "Exposes accounted's bookkeeping engine as MCP tools (Model Context Protocol). Connect your MCP client with an API key and do bookkeeping through conversation: list uncategorized transactions, book them, create invoices.",
"ext_cloud_backup_name": "Cloud sync",
"ext_cloud_backup_description": "Sync backups to your own cloud storage",
"ext_cloud_backup_long_description": "Connect your Google Drive account and upload a full backup with one click. Accounted creates a ZIP with SIE files, receipts and processing history and uploads it to a dedicated folder in your Drive.",
"ext_cloud_backup_reauth_title": "Google Drive needs to be reconnected",
"ext_cloud_backup_reauth_description": "Access to your Google account has expired or been revoked. Automatic sync is paused until you reconnect the account.",
"ext_cloud_backup_reauth_action": "Reconnect Google Drive",
"ext_cloud_backup_long_description": "Connect your Google Drive or Dropbox account and upload a full backup with one click. Accounted creates a ZIP with SIE files, receipts and processing history and uploads it to a dedicated folder in your cloud storage.",
"ext_cloud_backup_reauth_title": "{provider} needs to be reconnected",
"ext_cloud_backup_reauth_description": "Access to your {provider} account has expired or been revoked. Automatic sync is paused until you reconnect the account.",
"ext_cloud_backup_reauth_action": "Reconnect {provider}",
"ext_cloud_backup_reauth_needed_short": "reconnect required",
"ext_cloud_backup_too_large_title": "Backup too large",
"ext_cloud_backup_too_large_description": "The archive including documents is {size} MB, above the {limit} MB limit. You can back up the bookkeeping without document files (receipts and attachments).",
"ext_cloud_backup_too_large_confirm": "Sync without documents",
"ext_cloud_backup_no_documents_note": "without documents",
"ext_cloud_backup_last_sync_no_documents": "Latest copy was made without document files",
"ext_cloud_backup_card_tagline": "Backup to your own Drive.",
"ext_cloud_backup_legal_note": "The Drive copy is an extra backup. The legal archive (7 years under the Swedish Bookkeeping Act) remains in Accounted.",
"ext_cloud_backup_card_tagline": "Backup to your own {provider}.",
"ext_cloud_backup_legal_note": "The {provider} copy is an extra backup. The legal archive (7 years under the Swedish Bookkeeping Act) remains in Accounted.",
"ext_cloud_backup_loading": "Loading…",
"ext_cloud_backup_not_configured": "{provider} is not configured in this environment.",
"ext_cloud_backup_account_label": "Account",
"ext_cloud_backup_last_sync_label": "Last sync",
"ext_cloud_backup_never": "Never",
@@ -4530,29 +4529,30 @@
"ext_cloud_backup_disconnect": "Disconnect",
"ext_cloud_backup_disconnecting": "Disconnecting…",
"ext_cloud_backup_redirecting": "Redirecting…",
"ext_cloud_backup_connect": "Connect Google Drive",
"ext_cloud_backup_connect_description": "Connect your Google account to upload the backup to your own Drive. Accounted only gets access to files the app itself creates (scope drive.file).",
"ext_cloud_backup_connected_title": "Google Drive connected",
"ext_cloud_backup_connect": "Connect {provider}",
"ext_cloud_backup_connect_description_google": "Connect your Google account to upload the backup to your own Drive. Accounted only gets access to files the app itself creates (scope drive.file).",
"ext_cloud_backup_connect_description_dropbox": "Connect your Dropbox account to upload the backup to your own Dropbox. Accounted gets its own app folder and cannot reach anything else in your Dropbox.",
"ext_cloud_backup_connected_title": "{provider} connected",
"ext_cloud_backup_connected_description": "A backup is being created in the background.",
"ext_cloud_backup_connected_first_description": "Daily backup is enabled. The first copy is being created in the background.",
"ext_cloud_backup_connect_failed": "Could not connect Google Drive",
"ext_cloud_backup_connect_failed": "Could not connect {provider}",
"ext_cloud_backup_unknown_error": "Unknown error",
"ext_cloud_backup_disconnected": "Google Drive disconnected",
"ext_cloud_backup_disconnected": "{provider} disconnected",
"ext_cloud_backup_disconnect_failed": "Could not disconnect",
"ext_cloud_backup_uploaded": "Uploaded to Google Drive",
"ext_cloud_backup_uploaded": "Uploaded to {provider}",
"ext_cloud_backup_sync_failed": "Sync failed",
"ext_cloud_backup_try_again": "Try again.",
"ext_cloud_backup_status_failed": "Could not fetch status",
"ext_cloud_backup_connect_start_failed": "Could not start the connection",
"ext_cloud_backup_auto_sync_title": "Automatic sync",
"ext_cloud_backup_auto_sync_description": "Daily backup to your Drive.",
"ext_cloud_backup_auto_sync_description": "Daily backup to {provider}.",
"ext_cloud_backup_time_label": "Time (Swedish time)",
"ext_cloud_backup_last_auto_sync": "Last automatic sync:",
"ext_cloud_backup_auto_sync_success": "succeeded",
"ext_cloud_backup_auto_sync_error": "failed",
"ext_cloud_backup_schedule_save_failed": "Could not save schedule",
"ext_cloud_backup_banner_reauth": "The Google Drive backup is paused: the account needs to be reconnected.",
"ext_cloud_backup_banner_failing": "The automatic Google Drive backup is failing.",
"ext_cloud_backup_banner_reauth": "The {provider} backup is paused: the account needs to be reconnected.",
"ext_cloud_backup_banner_failing": "The automatic {provider} backup is failing.",
"ext_cloud_backup_banner_action": "Open backup settings",
"ext_cloud_backup_verified": "Verified",
"ext_cloud_backup_up_to_date": "Already up to date",
+18 -18
View File
@@ -387,9 +387,7 @@
"transaction_sync_enabled_toast": "Transaktionssynk aktiverad. Historiken hämtas vid nästa synkning.",
"transaction_sync_disabled_toast": "Transaktionssynk avaktiverad.",
"transaction_sync_toggle_failed": "Kunde inte spara inställningen. Försök igen.",
"sync_done_transactions": "{imported} transaktion(er) importerade, {linked} länkade till verifikat.",
"sync_done_feed": "{fetched} transaktion(er) hämtade: {imported} nya i inkorgen, {linked} länkade till verifikat.",
"sync_done_empty": "Stripe returnerade inga transaktioner för perioden. Kontrollera att rätt konto är anslutet om du väntade dig transaktioner."
"sync_done_transactions": "{imported} transaktion(er) importerade, {linked} länkade till verifikat."
},
"settings_modal": {
"title": "Inställningar",
@@ -4509,19 +4507,20 @@
"ext_mcp_server_long_description": "Exponerar accounteds bokföringsmotor som MCP-verktyg (Model Context Protocol). Koppla din MCP-klient med en API-nyckel och gör bokföring genom konversation: visa okategoriserade transaktioner, bokför dem, skapa fakturor.",
"ext_cloud_backup_name": "Molnsynkronisering",
"ext_cloud_backup_description": "Synka säkerhetsbackup till din egen molnlagring",
"ext_cloud_backup_long_description": "Koppla ditt Google Drive-konto och ladda upp en fullständig säkerhetsbackup med ett klick. Accounted skapar en ZIP med SIE-filer, kvitton och behandlingshistorik och laddar upp till en egen mapp i din Drive.",
"ext_cloud_backup_reauth_title": "Google Drive behöver kopplas om",
"ext_cloud_backup_reauth_description": "Åtkomsten till ditt Google-konto har gått ut eller återkallats. Automatisk synkronisering är pausad tills du kopplar om kontot.",
"ext_cloud_backup_reauth_action": "Koppla om Google Drive",
"ext_cloud_backup_long_description": "Koppla ditt Google Drive- eller Dropbox-konto och ladda upp en fullständig säkerhetsbackup med ett klick. Accounted skapar en ZIP med SIE-filer, kvitton och behandlingshistorik och laddar upp till en egen mapp i din molnlagring.",
"ext_cloud_backup_reauth_title": "{provider} behöver kopplas om",
"ext_cloud_backup_reauth_description": "Åtkomsten till ditt {provider}-konto har gått ut eller återkallats. Automatisk synkronisering är pausad tills du kopplar om kontot.",
"ext_cloud_backup_reauth_action": "Koppla om {provider}",
"ext_cloud_backup_reauth_needed_short": "kräver omkoppling",
"ext_cloud_backup_too_large_title": "Säkerhetskopian är för stor",
"ext_cloud_backup_too_large_description": "Arkivet med underlag är {size} MB, över gränsen på {limit} MB. Du kan säkerhetskopiera bokföringen utan underlagsfiler (kvitton och dokument).",
"ext_cloud_backup_too_large_confirm": "Synka utan underlag",
"ext_cloud_backup_no_documents_note": "utan underlag",
"ext_cloud_backup_last_sync_no_documents": "Senaste kopian togs utan underlagsfiler",
"ext_cloud_backup_card_tagline": "Säkerhetskopia till din egen Drive.",
"ext_cloud_backup_legal_note": "Drive-kopian är en extra säkerhetskopia. Det lagliga arkivet (7 år enligt bokföringslagen) finns kvar i Accounted.",
"ext_cloud_backup_card_tagline": "Säkerhetskopia till din egen {provider}.",
"ext_cloud_backup_legal_note": "Kopian i {provider} är en extra säkerhetskopia. Det lagliga arkivet (7 år enligt bokföringslagen) finns kvar i Accounted.",
"ext_cloud_backup_loading": "Laddar…",
"ext_cloud_backup_not_configured": "{provider} är inte konfigurerat i den här miljön.",
"ext_cloud_backup_account_label": "Konto",
"ext_cloud_backup_last_sync_label": "Senaste synk",
"ext_cloud_backup_never": "Aldrig",
@@ -4530,29 +4529,30 @@
"ext_cloud_backup_disconnect": "Koppla bort",
"ext_cloud_backup_disconnecting": "Kopplar bort…",
"ext_cloud_backup_redirecting": "Omdirigerar…",
"ext_cloud_backup_connect": "Koppla Google Drive",
"ext_cloud_backup_connect_description": "Koppla ditt Google-konto för att ladda upp säkerhetsbackupen till din egen Drive. Accounted får bara tillgång till filer som appen själv skapar (scope drive.file).",
"ext_cloud_backup_connected_title": "Google Drive kopplat",
"ext_cloud_backup_connect": "Koppla {provider}",
"ext_cloud_backup_connect_description_google": "Koppla ditt Google-konto för att ladda upp säkerhetsbackupen till din egen Drive. Accounted får bara tillgång till filer som appen själv skapar (scope drive.file).",
"ext_cloud_backup_connect_description_dropbox": "Koppla ditt Dropbox-konto för att ladda upp säkerhetsbackupen till din egen Dropbox. Accounted får en egen appmapp och kommer inte åt något annat i din Dropbox.",
"ext_cloud_backup_connected_title": "{provider} kopplat",
"ext_cloud_backup_connected_description": "En säkerhetskopia skapas nu i bakgrunden.",
"ext_cloud_backup_connected_first_description": "Daglig säkerhetskopiering är aktiverad. Den första kopian skapas nu i bakgrunden.",
"ext_cloud_backup_connect_failed": "Kunde inte koppla Google Drive",
"ext_cloud_backup_connect_failed": "Kunde inte koppla {provider}",
"ext_cloud_backup_unknown_error": "Okänt fel",
"ext_cloud_backup_disconnected": "Google Drive bortkopplat",
"ext_cloud_backup_disconnected": "{provider} bortkopplat",
"ext_cloud_backup_disconnect_failed": "Kunde inte koppla bort",
"ext_cloud_backup_uploaded": "Uppladdad till Google Drive",
"ext_cloud_backup_uploaded": "Uppladdad till {provider}",
"ext_cloud_backup_sync_failed": "Synkningen misslyckades",
"ext_cloud_backup_try_again": "Försök igen.",
"ext_cloud_backup_status_failed": "Kunde inte hämta status",
"ext_cloud_backup_connect_start_failed": "Kunde inte starta anslutning",
"ext_cloud_backup_auto_sync_title": "Automatisk synkronisering",
"ext_cloud_backup_auto_sync_description": "Daglig säkerhetsbackup till din Drive.",
"ext_cloud_backup_auto_sync_description": "Daglig säkerhetsbackup till {provider}.",
"ext_cloud_backup_time_label": "Tid (svensk tid)",
"ext_cloud_backup_last_auto_sync": "Senaste automatiska synk:",
"ext_cloud_backup_auto_sync_success": "lyckades",
"ext_cloud_backup_auto_sync_error": "misslyckades",
"ext_cloud_backup_schedule_save_failed": "Kunde inte spara schema",
"ext_cloud_backup_banner_reauth": "Säkerhetskopieringen till Google Drive är pausad: kontot behöver kopplas om.",
"ext_cloud_backup_banner_failing": "Den automatiska säkerhetskopieringen till Google Drive misslyckas.",
"ext_cloud_backup_banner_reauth": "Säkerhetskopieringen till {provider} är pausad: kontot behöver kopplas om.",
"ext_cloud_backup_banner_failing": "Den automatiska säkerhetskopieringen till {provider} misslyckas.",
"ext_cloud_backup_banner_action": "Öppna säkerhetskopiering",
"ext_cloud_backup_verified": "Verifierad",
"ext_cloud_backup_up_to_date": "Redan uppdaterad",