feat(appearance): a gallery of shared presets, checked by digest - #671
Open
Adam-Dalloul wants to merge 2 commits into
Open
feat(appearance): a gallery of shared presets, checked by digest#671Adam-Dalloul wants to merge 2 commits into
Adam-Dalloul wants to merge 2 commits into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 throughparseAppearancePresetfrom #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.