Skip to content

feat(appearance): a gallery of shared presets, checked by digest - #671

Open
Adam-Dalloul wants to merge 2 commits into
xintaofei:mainfrom
Adam-Dalloul:feat/preset-gallery
Open

feat(appearance): a gallery of shared presets, checked by digest#671
Adam-Dalloul wants to merge 2 commits into
xintaofei:mainfrom
Adam-Dalloul:feat/preset-gallery

Conversation

@Adam-Dalloul

Copy link
Copy Markdown
Contributor

Stacks on #668 (the preset format); the diff here is the gallery on top of it.

What it adds

Settings > Appearance gets a Preset gallery: presets other people publish, listed in one static index.json, with search, a card per listing (name, author, mode, a swatch strip drawn from the preset's own colours, so no images are fetched), Preview, Install, Update and Remove. Preview applies the preset for real and Revert brings back the exact previous look, fonts and code colours included; leaving the page or closing the window mid-preview reverts too. Installed presets are plain preset documents kept locally with their source URL and digest, so "Update available" is a digest comparison and Remove deletes only the local copy. The section shows when it was last refreshed and works from its saved copy offline.

Zero infrastructure

The index is a static JSON file in a public GitHub repository, fetched over https the way the ACP registry is. The source is a setting; the default points at https://github.com/Adam-Dalloul/codeg-presets, which holds the seven bundled looks and a README on how to submit one. That URL is a placeholder for a maintainer-owned repository; changing it is one constant.

Security

Fetching is host-side, two stateless commands shaped like the wallpaper market's (_core + Axum handler). https only, no credentials in the URL, no IP-literal or loopback host, every redirect hop re-checked; the index is capped at 256 KiB and a preset file at 32 KiB, on the declared length and on the running total; a preset file is refused unless its SHA-256 equals the digest its listing carries. The frontend then validates the index strictly, keeps only listings on the index's own origin, compares the digest again, and runs every file through parseAppearancePreset from #668 before anything is applied or stored, so a gallery preset can change colours, fonts and spacing and nothing else. There is no background polling: the index loads when the section opens without a fresh saved copy, and on Refresh.

Tests

Rust: URL policy, redirect policy, digest verification, the caps on declared length and running total, and a local server exercising an oversized declared body, an oversized chunked body, a 404 and a clean fetch. Vitest: index parsing (valid, oversize, malformed, wrong shape, eleven kinds of untrustworthy listing skipped individually), digest handling, install/update/remove transitions, the saved copy, and a component suite against the real provider: install, update, remove, preview then revert restoring the previous look exactly, keep, offline with a saved copy, a stale saved copy refreshing on open, an oversized index, a digest mismatch reported by the backend, bytes returned under a different digest, an invalid file, and the source setting. All ten locales carry the new strings.

Route the surfaces that were still hard-coded through theme tokens (message
bubbles, the composer box, the status bar, the code font in messages, message
text size and density), give code blocks a per-mode Shiki theme, and add a
Presets section to Appearance: seven bundled looks, density and text knobs,
and JSON import/export.

A preset is one strictly validated document of tokens, font ids and code
theme ids, with a schema version and a size cap. It cannot carry CSS or
script; custom CSS stays a local escape hatch and is never exported. The
pre-paint script applies the new tokens like the existing ones, so a preset
paints before first paint, and the transcript only re-renders when the code
theme itself changes. Every bundled preset passes a WCAG AA contrast check on
its text and surface pairs in both modes, measured against globals.css.
Settings > Appearance gets a Preset gallery: presets other people publish,
listed in one static index.json in a public repository, with search, a card
per listing drawn from the preset's own colours, Preview (apply, then revert
to the exact previous look), Install, Update and Remove. Nothing is polled;
the index loads when the section opens without a fresh saved copy, and on
Refresh, and the section says when it was last refreshed.

The index is fetched host-side through two stateless commands shaped like the
wallpaper market's (`_core` + Axum handler), with a shape-based URL policy
instead of a host allowlist because the source is a setting: https only, no
credentials, no IP-literal or loopback host, every redirect hop re-checked.
The index is capped at 256 KiB and a preset file at 32 KiB, both on the
declared length and on the running total, and a preset file is refused unless
its SHA-256 equals the digest its listing carries. The frontend validates the
index strictly, keeps only same-origin listings, compares the digest again,
runs every file through parseAppearancePreset before anything is applied or
stored, and refuses a file whose id is not the listing's. Installed presets
are plain preset documents in localStorage with their source URL and digest,
so "update available" is a digest comparison and Remove deletes only the
local copy.

The provider gains a preview: startPresetPreview records what a preset
replaces (fonts and code colours only when the preset sets them), applies it
like applyPreset does, and endPresetPreview keeps it or restores the record;
a window that goes away mid-preview restores it too, storage included.

The default index URL points at a seed repository holding the seven bundled
looks, standing in until a maintainer-owned one exists.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant