[work-u6w7] Add Mossfire Studio web app #2

Merged
keeper merged 2 commits from work-u6w7-theme-studio-web-app-import-design-and into main 2026-10-07 18:12:41 +00:00
Owner

Bead: work-u6w7

Turns the html/ design mockup into studio/, a web app that runs only in the browser. Open studio/index.html. You don't need a build or a server.

What it does

  • Design: edit the 22 palette colors, generate a dark or light theme from a hue, and run helpers that rebuild the ramp, the brights and orange/amber. A contrast panel checks key color pairs. Undo and redo work.
  • Preview: the mockup's i3 desktop. The terminal has tabs for a shell session and for Bash, Python, YAML and Rust shown in Neovim. You can switch tabs with keys 1–5 or the arrow keys. Clicking a swatch jumps to that color's editor.
  • Files: shows each generated file per app, with a color chip next to every hex value, and shows the recolored GTK3 PNGs.
  • Export: all apps as one .zip, or one app at a time (a single file, or a zip when the app has several files). Also exports a .studio.json palette file.
  • Import: Studio JSON, zips (stored or deflated), Alacritty, Kitty, Ghostty, Xresources, Base16/24, Windows Terminal and named-palette files (nvim, starship, i3, regolith, firefox). You can also drag and drop. Colors the file doesn't have are derived from the ones it has.
  • Storage: themes are saved in localStorage only. Nothing is uploaded.

How

studio/tools/build_data.py turns the repo's theme files into templates. Every palette color becomes a {{slot}} token. Other colors (dim ANSI colors, diff backgrounds, the GTK3 CSS and its PNGs) are recolored when you export. Each one is matched to a blend of two palette colors, and the same blend is rebuilt from the new palette.

Tests

  • node studio/tests/identity.test.js: the stock palette gives back every repo file byte for byte. A changed palette leaves no stock color behind.
  • node studio/tests/importers.test.js: the repo's own theme files import back to the palette.
  • Ran a headless Chromium test: preview tabs, editing a color, files view, export zip (199 files, unzip -t passes), undo, the generator, Kitty and deflated-zip import, state kept after reload, and phone width. No console errors.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GbPiGjDtGehuNo8tJ4K3rV

Bead: work-u6w7 Turns the `html/` design mockup into `studio/`, a web app that runs only in the browser. Open `studio/index.html`. You don't need a build or a server. ## What it does - **Design**: edit the 22 palette colors, generate a dark or light theme from a hue, and run helpers that rebuild the ramp, the brights and orange/amber. A contrast panel checks key color pairs. Undo and redo work. - **Preview**: the mockup's i3 desktop. The terminal has tabs for a shell session and for Bash, Python, YAML and Rust shown in Neovim. You can switch tabs with keys 1–5 or the arrow keys. Clicking a swatch jumps to that color's editor. - **Files**: shows each generated file per app, with a color chip next to every hex value, and shows the recolored GTK3 PNGs. - **Export**: all apps as one `.zip`, or one app at a time (a single file, or a zip when the app has several files). Also exports a `.studio.json` palette file. - **Import**: Studio JSON, zips (stored or deflated), Alacritty, Kitty, Ghostty, Xresources, Base16/24, Windows Terminal and named-palette files (nvim, starship, i3, regolith, firefox). You can also drag and drop. Colors the file doesn't have are derived from the ones it has. - **Storage**: themes are saved in localStorage only. Nothing is uploaded. ## How `studio/tools/build_data.py` turns the repo's theme files into templates. Every palette color becomes a `{{slot}}` token. Other colors (dim ANSI colors, diff backgrounds, the GTK3 CSS and its PNGs) are recolored when you export. Each one is matched to a blend of two palette colors, and the same blend is rebuilt from the new palette. ## Tests - `node studio/tests/identity.test.js`: the stock palette gives back every repo file byte for byte. A changed palette leaves no stock color behind. - `node studio/tests/importers.test.js`: the repo's own theme files import back to the palette. - Ran a headless Chromium test: preview tabs, editing a color, files view, export zip (199 files, `unzip -t` passes), undo, the generator, Kitty and deflated-zip import, state kept after reload, and phone width. No console errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01GbPiGjDtGehuNo8tJ4K3rV
A client-side app to design, import and export themes for every app in
this repo. Templates are built from the repo files, so the stock palette
exports them byte for byte. GTK3 CSS and PNGs are recolored in the browser.
Code sample tabs: Bash, Python, YAML, Rust. Themes save to local storage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbPiGjDtGehuNo8tJ4K3rV
keeper requested changes 2026-10-07 18:07:53 +00:00
Dismissed
keeper left a comment

Keeper review of PR #2.

Blocking finding: work-qybp — theme name is put into HTML attributes without escaping quotes.

esc in studio/js/samples.js:143 escapes only &, <, >. renderThemeList() in studio/js/app.js:144 interpolates the theme name into title="Delete ..." / aria-label="Delete ..." and assigns it with innerHTML, while the name comes unsanitised from an imported .studio.json (studio/js/importers.js:55, only length-limited by clean()). A name containing a double quote breaks out of the attribute and injects an event handler. Importing theme files from others is a documented core feature, so this is stored XSS in the page origin (localStorage + network reach; no credentials in this page). Repro and fix are in the bead.

Also filed as a non-blocking follow-up: work-cfx2 — the page unconditionally fetches Google Fonts (studio/index.html:8-9) while the README advertises local-only behaviour.

What I checked and found good:

  • Reproduced the build: python3 studio/tools/build_data.py regenerates studio/data/templates.js and studio/data/gtk-assets.js byte-identically from the repo files.
  • node studio/tests/identity.test.js and node studio/tests/importers.test.js both pass; node --check clean on every JS file.
  • Exports are byte-for-byte the repo files on the stock palette (identity test), and a changed palette leaves no stock hex, no {{token}}, and no "Mossfire" name behind.
  • The zip writer/reader (CRC32, offsets, stored + deflate-raw) and the colour/recolor engine read correctly; no secrets, tokens or credentials; no other repo files touched beyond the intended README lines.
  • Not sensitive: a local static client-side app, no data loss, auth, network policy or lockout.

Please fix work-qybp (and close it) on this branch, push, then run keeper-review again.

Keeper review of PR #2. **Blocking finding: work-qybp — theme name is put into HTML attributes without escaping quotes.** `esc` in `studio/js/samples.js:143` escapes only `&`, `<`, `>`. `renderThemeList()` in `studio/js/app.js:144` interpolates the theme name into `title="Delete ..."` / `aria-label="Delete ..."` and assigns it with `innerHTML`, while the name comes unsanitised from an imported `.studio.json` (`studio/js/importers.js:55`, only length-limited by `clean()`). A name containing a double quote breaks out of the attribute and injects an event handler. Importing theme files from others is a documented core feature, so this is stored XSS in the page origin (localStorage + network reach; no credentials in this page). Repro and fix are in the bead. Also filed as a non-blocking follow-up: work-cfx2 — the page unconditionally fetches Google Fonts (`studio/index.html:8-9`) while the README advertises local-only behaviour. What I checked and found good: - Reproduced the build: `python3 studio/tools/build_data.py` regenerates `studio/data/templates.js` and `studio/data/gtk-assets.js` byte-identically from the repo files. - `node studio/tests/identity.test.js` and `node studio/tests/importers.test.js` both pass; `node --check` clean on every JS file. - Exports are byte-for-byte the repo files on the stock palette (identity test), and a changed palette leaves no stock hex, no `{{token}}`, and no \"Mossfire\" name behind. - The zip writer/reader (CRC32, offsets, stored + deflate-raw) and the colour/recolor engine read correctly; no secrets, tokens or credentials; no other repo files touched beyond the intended README lines. - Not sensitive: a local static client-side app, no data loss, auth, network policy or lockout. Please fix work-qybp (and close it) on this branch, push, then run `keeper-review` again.
esc() now escapes " and ' so imported theme names cannot break out of
attributes (work-qybp). The page no longer loads Google Fonts, so it makes
no network requests (work-cfx2). A Studio JSON with no colors now imports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbPiGjDtGehuNo8tJ4K3rV
keeper approved these changes 2026-10-07 18:12:38 +00:00
keeper left a comment

Keeper review of PR #2 — routine, approved.

Re-review of head 919f7fe. The earlier request-changes (work-qybp, theme-name attribute injection) is fixed here.

Sensitivity (step 1): routine. Static client-side app, 15 files under studio/ plus a README section, all additions. No data deletion or migration, no secrets, no auth/SSO/RBAC/network surface. It reads only user-chosen local files and writes downloads plus the browser's own localStorage.

What I checked:

  • Diff read in full with fresh eyes.
  • Tests at head: node studio/tests/identity.test.js and node studio/tests/importers.test.js both pass.
  • Regenerated studio/data/* with studio/tools/build_data.py in a scratch copy of the tree: byte-identical, so the checked-in templates are in sync.
  • XSS fix verified in headless Chromium: importing a theme named with quotes/attributes/<img> renders it as text only — no injected element is created, and theme names appear only as text or escaped attributes.
  • No network requests: the only URL in the page is the inline SVG namespace; the Google Fonts CDN is gone.
  • Exported every app in headless Chromium: 198 files, zip round-trips through the reader, 169 GTK PNGs recolored via canvas, Files view renders.
  • Hostile theme names cannot create .. path segments or absolute paths in export file names (fileName strips separators).

Nit (non-blocking): t.id is interpolated into HTML attributes without escaping in renderThemeList. Ids are generated by the app today, so it is not exploitable, but escaping it would remove the latent risk if ids ever come from stored or imported data.

Merging as squash.

Keeper review of PR #2 — routine, approved. Re-review of head 919f7fe. The earlier request-changes (work-qybp, theme-name attribute injection) is fixed here. **Sensitivity (step 1):** routine. Static client-side app, 15 files under `studio/` plus a README section, all additions. No data deletion or migration, no secrets, no auth/SSO/RBAC/network surface. It reads only user-chosen local files and writes downloads plus the browser's own localStorage. **What I checked:** - Diff read in full with fresh eyes. - Tests at head: `node studio/tests/identity.test.js` and `node studio/tests/importers.test.js` both pass. - Regenerated `studio/data/*` with `studio/tools/build_data.py` in a scratch copy of the tree: byte-identical, so the checked-in templates are in sync. - XSS fix verified in headless Chromium: importing a theme named with quotes/attributes/`<img>` renders it as text only — no injected element is created, and theme names appear only as text or escaped attributes. - No network requests: the only URL in the page is the inline SVG namespace; the Google Fonts CDN is gone. - Exported every app in headless Chromium: 198 files, zip round-trips through the reader, 169 GTK PNGs recolored via canvas, Files view renders. - Hostile theme names cannot create `..` path segments or absolute paths in export file names (`fileName` strips separators). **Nit (non-blocking):** `t.id` is interpolated into HTML attributes without escaping in `renderThemeList`. Ids are generated by the app today, so it is not exploitable, but escaping it would remove the latent risk if ids ever come from stored or imported data. Merging as squash.
keeper merged commit aec6995004 into main 2026-10-07 18:12:41 +00:00
keeper deleted branch work-u6w7-theme-studio-web-app-import-design-and 2026-10-07 18:12:42 +00:00
Sign in to join this conversation.
No reviewers
No labels
review:human
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
ember/mossfire!2
No description provided.