Skip to content

feat(themes): let installed packs select loader symbols - #7650

Merged
iamwhatever merged 1 commit into
kirodotdev:mainfrom
Pearcekieser:feat/theme-loader-config
Sep 3, 2026
Merged

feat(themes): let installed packs select loader symbols#7650
iamwhatever merged 1 commit into
kirodotdev:mainfrom
Pearcekieser:feat/theme-loader-config

Conversation

@Pearcekieser

@Pearcekieser Pearcekieser commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Problem / Motivation

Compiled themes can customize the chat loading indicator through registerThemeBranding(), but installed theme.json packs cannot use that TypeScript registration seam.

An installed theme can configure most of the dashboard experience while still being forced to inherit the default Kiro ghost poses. Theme authors currently have no safe, portable way to keep the existing carousel and change only its symbols.

Why it matters

Installed themes are the user-facing distribution format. They can be installed from a local folder or GitHub without rebuilding Kiro Crew.

Loader customization should work in that format without allowing theme packs to inject React components, SVG markup, or executable code.

What changed (motivation → approach → change)

The existing carousel already handles animation, icon sizing, distinct sampling, pool changes, and reduced motion. This change keeps that implementation and adds a declarative installed-pack input.

Level 1 and Level 2 manifests may now declare:

"loaderIcons": ["star", "sparkles", "moon", "cloud"]

The backend:

  • accepts 4 to 8 unique names from a closed stock-symbol allowlist;
  • rejects the field on Level 0 packs;
  • rejects unknown names, duplicates, non-string values, and invalid list sizes;
  • includes validated names in the installed theme asset descriptor.

The frontend:

  • maps manifest names to bundled Lucide components;
  • passes the active installed theme's symbols into the existing carousel;
  • keeps trusted compiled loader components at the highest precedence;
  • fails closed to compiled artwork or Kiro ghost poses for malformed descriptors;
  • keeps ChatFooter usable outside ThemeProvider for isolated tests.

The branch now builds on upstream #7722, which owns the ArtifactsPage memoization cleanup that restored the frontend lint warning budget, and #7761, which repaired the inherited redactor-census baseline.

The authoring skill, frontend contract, and themes system specification document the new field and trust boundary.

Tests

Backend coverage verifies valid Level 1 declarations, Level 0 rejection, type and size bounds, unknown-name and duplicate rejection, backend/frontend allowlist parity, and propagation through GET /api/themes/{slug}.

Frontend coverage verifies stock-symbol resolution, malformed-descriptor fallback, compiled custom-loader precedence, and existing carousel/turn-state behavior.

Validation completed locally:

  • 78,809 backend tests passed in the full profile run; remaining local failures reproduce host/upstream baseline conditions involving /local/home ownership, sensitive-path classification, long Unix-socket paths, and unrelated existing tests
  • full website coverage suite passed
  • 46 ChatFooter tests passed
  • exact CI frontend lint passed with 597 warnings against the 603-warning ceiling
  • the formerly failing redactor-census regression and both session-control tests from the Windows flake pass on current main
  • TypeScript, ESLint, isort, flake8, mypy, production build, docs lint, brand, formatting, subprocess-encoding, i18n, and theme contract gates passed
  • local and server GPT/Opus review found no feature defects on predecessor heads; git range-diff confirms current commit e83a4e24a92bfaa4eb6351e3204d150cd042b0ec is feature-patch-equivalent to 1bf22c2132bbc1c272a9fd9974cd3666ad7bf4ee and builds on current main

Manual verification

Verified through the real Settings flow in an isolated Kiro Crew pod:

  1. Installed a Level 1 theme from a local folder.
  2. Confirmed the installer accepted and selected it.
  3. Started a real agent turn that remained active for five seconds.
  4. Confirmed the footer rendered moon, zap, flower, and heart symbols with no ghost-pose artwork.
  5. Removed the pod and verified zero residual pod state.

Screenshots / video

Installed theme symbols in the running chat loader

Watch the real-turn loader recording

Evidence was captured from the isolated worktree pod with a real server and agent turn, then published on an immutable evidence-only ref. No fixture or binary evidence is included in the feature commit.

Related Issues

Related prior work: #894, #952, #5549.

Fixes #7632

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

@Pearcekieser
Pearcekieser requested a review from a team September 1, 2026 15:03
@Pearcekieser
Pearcekieser requested a review from a team as a code owner September 1, 2026 15:03
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5, fork) — ✅ PASS

Design-level review of 1bf22c2132bbc1c272a9fd9974cd3666ad7bf4ee via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

Closed name-allowlist at the right layer — install-time validation, descriptor surfacing, fail-closed frontend mapping — preserves the installed-pack trust boundary while reusing the carousel.

The shape holds up under scrutiny: the optional manifest field is backward compatible, precedence keeps the compiled loader seam on top, the customThemeDataMap lookup in ChatFooter matches the existing ThemeExperienceLayer idiom, the cross-language allowlist duplication is pinned by a loud-failing parity test, and specs/docs move in the same commit. No design-level concerns.

[DESIGN-REVIEWED] 1bf22c2

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5, fork) — ✅ PASS

UX-level review of 1bf22c2132bbc1c272a9fd9974cd3666ad7bf4ee via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

The base error-message style matches (0..N notation, theme.json 'field' … prefixes), the fallback fails closed to the existing ghost poses, and the docs/skill state the constraint (4–8 names, Level 1+, closed allowlist) before an author ever hits the install error. No committed screenshots to audit; no new end-user strings or controls — the only human surfaces are the theme-author docs, the install-time rejection messages, and the rendered loader, all of which follow established patterns.

UX-Verdict: PASS

Author-facing errors name field, rule, and fix in sibling phrasing; end users just see their theme's symbols, with fail-closed ghost-pose fallback.

[UX-REVIEWED] 1bf22c2

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5, fork) — 🟡 CONCERNS

Premise-level review of 1bf22c2132bbc1c272a9fd9974cd3666ad7bf4ee via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Base-tree evidence is now complete. Writing the review.

First-Principles-Verdict: CONCERNS

One undeclared layer: the read path now 500s a disk-edited pack that the PR's own descriptor filter and frontend fallback already degrade gracefully.

What this change ships

Intent: let installed theme packs pick the chat-loader symbols without granting them code execution — an ADDITION (claims "Fixes #7632").

  1. L1/L2 packs declare loaderIcons in theme.json; installer validates names — justified
  2. L0 packs declaring it are refused at install — justified (descriptor returns {} below L1; silent no-op otherwise)
  3. Theme-detail API descriptor carries the validated names — justified
  4. Chat loader carousel renders the pack's symbols during a turn — justified
  5. Re-reading an installed pack hard-fails (500) on a bad loaderIcons — undeclared; two lenient layers ship alongside
  6. Manifest symbols outrank compiled loaderIcons, stay below compiled loader — justified, declared
  7. New useOptionalTheme export — justified; 1 consumer (ChatFooter.tsx), minimal accessor since ThemeContext is module-private
  8. Spec, contract doc, authoring skill updated — mandated same-commit (AGENTS.md)

Watch

  • _validate_loader_icons runs unconditionally in _validate_theme_dir, which the theme-detail route re-runs on every read (handlers/themes.py:742-752 → 500). The description promises "fails closed to compiled artwork or Kiro ghost poses for malformed descriptors", but a pack hand-edited after install never reaches the descriptor — the whole theme 500s first. The file's own convention (theme_validate.py:1114-1120, and the adjacent font-role rule) gates hard manifest rules on installing; 2 existing rules in this function follow it, this one doesn't.

Subtractions

  • Gate _validate_loader_icons on installing=True, matching the font-role precedent one hunk below it — the descriptor filter (_theme_asset_descriptor) and resolveThemeLoaderIcons already own the read path leniently.

[FIRST-PRINCIPLES-REVIEWED] 1bf22c2

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

Reviewed 1bf22c2132bbc1c272a9fd9974cd3666ad7bf4ee via the fork AI-review pipeline; updated in place on each push.

Review details

The change is well-guarded end to end: the backend validates on install and re-filters on the read path, resolveThemeLoaderIcons fails closed on wrong count/dupes/unknown names, and SwapCarousel explicitly guards variable pool sizes (slotCount = Math.min(...), total === 0 → null, modulo-wrapped indices) so a 5–8 icon pool renders without a <undefined/> crash. No grounded (a)/(b)/(c) defect survives.

No findings.

[OPUS-REVIEWED] 1bf22c2

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — ✅ no blocking findings

Reviewed 1bf22c2132bbc1c272a9fd9974cd3666ad7bf4ee via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 1bf22c2

@Pearcekieser
Pearcekieser force-pushed the feat/theme-loader-config branch from 9d92c4c to 2e4e6e0 Compare September 1, 2026 16:42
@Pearcekieser

Copy link
Copy Markdown
Contributor Author
  • Fixed span=1fdece4f03f1

_validate_loader_icons now checks key presence separately from value type. An omitted loaderIcons field remains valid, while explicit JSON null reaches the array-type validation and is rejected with theme.json 'loaderIcons' must be an array of stock symbol names.

The parametrized installer regression now includes (1, None, "must be an array"). The focused theme suite passes 353 tests on amended SHA 2e4e6e078e94700c7dae30c5ca1ecd416b131b00; GPT and Opus local verifier rounds both reported no findings on that SHA.

"manifest.get(\"loaderIcons\")" treats explicit JSON null as absent, so Level 0 installation succeeds.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 1, 2026
@Pearcekieser
Pearcekieser force-pushed the feat/theme-loader-config branch from 2e4e6e0 to 098831a Compare September 1, 2026 20:46
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 1, 2026
@Pearcekieser
Pearcekieser force-pushed the feat/theme-loader-config branch from 098831a to bac707c Compare September 1, 2026 23:21
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: passed Eligible automated validation passed for the current revision labels Sep 1, 2026
@Pearcekieser
Pearcekieser force-pushed the feat/theme-loader-config branch 2 times, most recently from cdb2a7c to 1bf22c2 Compare September 2, 2026 01:11
@Pearcekieser
Pearcekieser force-pushed the feat/theme-loader-config branch from 1bf22c2 to e83a4e2 Compare September 2, 2026 22:01
@iamwhatever
iamwhatever merged commit bfbf2b6 into kirodotdev:main Sep 3, 2026
63 of 70 checks passed
@github-actions github-actions Bot removed the readiness: action required A blocking check or review needs attention label Sep 3, 2026
xuejinT added a commit that referenced this pull request Sep 8, 2026
…xed loader

Installed theme packs could only pick loader icons from a fixed 8-symbol allowlist (PR #7650); custom art or motion needed compiled-in code. Extend the installed-pack surface: loader/*.png|webp (4-8 raster images, Level 1) feed the stock carousel, and loader/loader.html (Level 2) is a fully custom loader served under the overlay CSP in a sandboxed, click-through iframe. resolveLoader precedence: compiled loader -> pack loader.html -> pack images -> loaderIcons -> default poses.
xuejinT added a commit that referenced this pull request Sep 8, 2026
…xed loader

Installed theme packs could only pick loader icons from a fixed 8-symbol allowlist (PR #7650); custom art or motion needed compiled-in code. Extend the installed-pack surface: loader/*.png|webp (4-8 raster images, Level 1) feed the stock carousel, and loader/loader.html (Level 2) is a fully custom loader served under the overlay CSP in a sandboxed, click-through iframe. resolveLoader precedence: compiled loader -> pack loader.html -> pack images -> loaderIcons -> default poses.
xuejinT added a commit that referenced this pull request Sep 8, 2026
…xed loader

Installed theme packs could only pick loader icons from a fixed 8-symbol allowlist (PR #7650); custom art or motion needed compiled-in code. Extend the installed-pack surface: loader/*.png|webp (4-8 raster images, Level 1) feed the stock carousel, and loader/loader.html (Level 2) is a fully custom loader served under the overlay CSP in a sandboxed, click-through iframe. resolveLoader precedence: compiled loader -> pack loader.html -> pack images -> loaderIcons -> default poses.
xuejinT added a commit that referenced this pull request Sep 8, 2026
…xed loader

Installed theme packs could only pick loader icons from a fixed 8-symbol allowlist (PR #7650); custom art or motion needed compiled-in code. Extend the installed-pack surface: loader/*.png|webp (4-8 raster images, Level 1) feed the stock carousel, and loader/loader.html (Level 2) is a fully custom loader served under the overlay CSP in a sandboxed, click-through iframe. resolveLoader precedence: compiled loader -> pack loader.html -> pack images -> loaderIcons -> default poses.
xuejinT added a commit that referenced this pull request Sep 8, 2026
…xed loader

Installed theme packs could only pick loader icons from a fixed 8-symbol allowlist (PR #7650); custom art or motion needed compiled-in code. Extend the installed-pack surface: loader/*.png|webp (4-8 raster images, Level 1) feed the stock carousel, and loader/loader.html (Level 2) is a fully custom loader served under the overlay CSP in a sandboxed, click-through iframe. resolveLoader precedence: compiled loader -> pack loader.html -> pack images -> loaderIcons -> default poses.
xuejinT added a commit that referenced this pull request Sep 8, 2026
…xed loader

Installed theme packs could only pick loader icons from a fixed 8-symbol allowlist (PR #7650); custom art or motion needed compiled-in code. Extend the installed-pack surface: loader/*.png|webp (4-8 raster images, Level 1) feed the stock carousel, and loader/loader.html (Level 2) is a fully custom loader served under the overlay CSP in a sandboxed, click-through iframe. resolveLoader precedence: compiled loader -> pack loader.html -> pack images -> loaderIcons -> default poses.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow installed themes to configure chat loading symbols

2 participants