feat(themes): import a theme from a local .zip (#1012)

* feat(themes): validate and extract locally imported theme packages

Add the backend + validation half of local theme import: users will be
able to load a theme packaged as a .zip (manifest.json + theme.css).

- `import_theme_zip` Tauri command unpacks only manifest.json + theme.css
  from the archive, outside the webview, with an archive-size cap,
  per-entry uncompressed caps, and path-traversal rejection.
- `validateThemePackage` runs the full theme-store contract in-app: the
  manifest schema (field patterns copied from the repo schema), the CSS
  token whitelist with all core tokens required, color-scheme matching
  the declared mode, data-URI restricted to the arrow token, and a guard
  against ids that collide with built-in themes. The existing
  `validateThemeCss` containment guard is reused and runs again at
  injection time. The contract token list is a byte-identical copy of the
  themes repo's allowed-tokens.json.

Covered by validateThemePackage tests for every rejection class.

* feat(themes): add the Import a theme section to the Themes tab

A dedicated section between the theme scheduler and the Theme Store lets
users import a theme from a local .zip. After the package validates, a
confirmation dialog names the theme and its author before it is installed.

A rejected import shows a plain-language explanation aimed at end users;
the raw contract diagnostics (token names, missing fields) are tucked
into a collapsible "Technical details" block for theme authors. Fully
localised across all nine languages.

* docs(themes): changelog + credits for local theme import

Fold the local .zip import (and the store pagination / refresh-scroll
polish) into the still-unreleased Theme Store entry rather than a new
section, and extend the credits line. PRs #1011 and #1012.
This commit is contained in:
Psychotoxical
2026-06-07 11:35:52 +02:00
committed by GitHub
parent 9fb0d5638b
commit b3573e7107
18 changed files with 802 additions and 3 deletions
+10
View File
@@ -353,6 +353,16 @@ export const settings = {
themeStorePagePrev: 'Forrige side',
themeStorePageNext: 'Neste side',
themeStorePageStatus: 'Side {{page}} av {{total}}',
themeImportTitle: 'Importer et tema',
themeImportButton: 'Importer tema…',
themeImporting: 'Importerer…',
themeImportHint: 'Laster inn et tema pakket som .zip (manifest.json + theme.css).',
themeImportSuccess: '«{{name}}» importert.',
themeImportErrorTitle: 'Dette temaet kunne ikke importeres',
themeImportErrorBody: 'Det samsvarer ikke med Psysonic-temaformatet. Det kan være laget for en annen versjon, eller filen er ufullstendig — sjekk at du valgte riktig .zip, eller spør temaets forfatter.',
themeImportErrorDetails: 'Tekniske detaljer',
themeImportConfirmTitle: 'Installere tema?',
themeImportConfirmBody: 'Installere «{{name}}» av {{author}}?',
tabStorage: 'Frakoblet & Cache',
inputKeybindingsTitle: 'Tastatursnarveier',
aboutContributorsCount_one: '{{count}} bidrag',