🎨 Improved theme validation modals#28748
Conversation
WalkthroughThe PR refactors theme validation UI across upload, install, and activation flows. A new Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run @tryghost/admin-x-settings:test:acceptance |
✅ Succeeded | 10m 23s | View ↗ |
nx build @tryghost/announcement-bar |
✅ Succeeded | <1s | View ↗ |
nx build @tryghost/sodo-search |
✅ Succeeded | <1s | View ↗ |
nx build @tryghost/portal |
✅ Succeeded | <1s | View ↗ |
nx build @tryghost/admin-toolbar |
✅ Succeeded | <1s | View ↗ |
nx build @tryghost/comments-ui |
✅ Succeeded | <1s | View ↗ |
nx build @tryghost/activitypub |
✅ Succeeded | 1s | View ↗ |
nx build @tryghost/signup-form |
✅ Succeeded | <1s | View ↗ |
Additional runs (8) |
✅ Succeeded | ... | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-06-19 14:08:24 UTC
Redesigned the invalid/installed/code-editor theme validation modals around a single shared details view: blocking errors are shown prominently with a collapsible "non-blocking issues" disclosure (expanded in development, collapsed in production). The blocking rule is unchanged — only problems flagged `fatal` block saving or activation; everything else is grouped as non-blocking. Severity badges map straight from gscan's `level` (error/warning/recommendation) with no remapping, so a problem's reported level is what's shown.
d8cf993 to
7e58bd8
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/admin-x-settings/src/components/settings/site/theme/theme-installed-modal.tsx (1)
11-17: ⚡ Quick winDrop or wire the
promptprop to avoid a dead public API.
ThemeInstalledModalstill declaresprompt(Line 13), but it is not destructured or rendered. That leaves callers passing copy that has no effect and makes this component contract misleading.Suggested cleanup
const ThemeInstalledModal: React.FC<{ title: string - prompt: ReactNode installedTheme: InstalledTheme; validationDetailsDefaultOpen?: boolean; onActivate?: () => void; -}> = ({title, installedTheme, validationDetailsDefaultOpen, onActivate}) => { +}> = ({title, installedTheme, validationDetailsDefaultOpen, onActivate}) => {Also applies to: 53-72
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/admin-x-settings/src/components/settings/site/theme/theme-installed-modal.tsx` around lines 11 - 17, The ThemeInstalledModal component declares the prompt prop in its type definition on line 13, but it is not destructured from the component's props parameter on line 17 and is never rendered anywhere in the component. Either remove the prompt prop from the component's type definition if it is not needed, or add it to the destructured props in the function signature and render it at the appropriate location in the component (likely in the 53-72 range) to make this a functional part of the component's public API.apps/admin-x-settings/src/components/settings/site/theme/theme-validation-details.tsx (1)
114-117: ⚡ Quick winExpose expanded state on the disclosure buttons.
Both controls toggle hidden content visually, but screen readers do not get the open/closed state. Add
aria-expandedto the toggles.♿ Proposed accessibility tweak
<button + aria-expanded={expanded} className='flex w-full items-start justify-between gap-3 text-left' type='button' onClick={() => setExpanded(!expanded)} @@ <button + aria-expanded={open} className='flex w-full items-center justify-between gap-3 rounded-lg border border-border bg-background p-4 text-left transition-colors hover:bg-muted/40' type='button' onClick={() => setOpen(!open)}Also applies to: 164-167
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/admin-x-settings/src/components/settings/site/theme/theme-validation-details.tsx` around lines 114 - 117, The disclosure buttons that toggle expanded content are missing the aria-expanded attribute needed for screen reader accessibility. Add aria-expanded attribute to both button elements (the ones with onClick={() => setExpanded(!expanded)} and the similar toggle at the second location mentioned) and bind the attribute value to the expanded state variable so it properly communicates the open/closed state to assistive technologies.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/admin-x-settings/src/components/settings/site/theme/theme-validation-details.tsx`:
- Line 90: The problem.details field on line 90 and problem.rule field on line
124 are rendered directly via dangerouslySetInnerHTML without sanitization.
Since these HTML strings come from the Ghost theme validation API, they could
contain malicious scripts that execute in the Admin panel context. Sanitize both
problem.details and problem.rule using DOMPurify before passing them to
dangerouslySetInnerHTML to prevent potential XSS attacks, or alternatively strip
the HTML entirely and render as plain text if HTML formatting is not necessary.
---
Nitpick comments:
In
`@apps/admin-x-settings/src/components/settings/site/theme/theme-installed-modal.tsx`:
- Around line 11-17: The ThemeInstalledModal component declares the prompt prop
in its type definition on line 13, but it is not destructured from the
component's props parameter on line 17 and is never rendered anywhere in the
component. Either remove the prompt prop from the component's type definition if
it is not needed, or add it to the destructured props in the function signature
and render it at the appropriate location in the component (likely in the 53-72
range) to make this a functional part of the component's public API.
In
`@apps/admin-x-settings/src/components/settings/site/theme/theme-validation-details.tsx`:
- Around line 114-117: The disclosure buttons that toggle expanded content are
missing the aria-expanded attribute needed for screen reader accessibility. Add
aria-expanded attribute to both button elements (the ones with onClick={() =>
setExpanded(!expanded)} and the similar toggle at the second location mentioned)
and bind the attribute value to the expanded state variable so it properly
communicates the open/closed state to assistive technologies.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0a507859-7166-41d1-9e4f-43b57e6ab6d0
📒 Files selected for processing (11)
apps/admin-x-design-system/src/global/modal/confirmation-modal.tsxapps/admin-x-design-system/src/global/modal/modal.tsxapps/admin-x-framework/src/api/themes.tsapps/admin-x-settings/src/components/settings/site/theme-modal.tsxapps/admin-x-settings/src/components/settings/site/theme/advanced-theme-settings.tsxapps/admin-x-settings/src/components/settings/site/theme/invalid-theme-modal.tsxapps/admin-x-settings/src/components/settings/site/theme/theme-code-editor-modal.tsxapps/admin-x-settings/src/components/settings/site/theme/theme-installed-modal.tsxapps/admin-x-settings/src/components/settings/site/theme/theme-validation-details.tsxapps/admin-x-settings/test/acceptance/site/design.test.tsapps/admin-x-settings/test/acceptance/site/theme.test.ts
✅ Files skipped from review due to trivial changes (3)
- apps/admin-x-settings/test/acceptance/site/theme.test.ts
- apps/admin-x-framework/src/api/themes.ts
- apps/admin-x-settings/src/components/settings/site/theme/advanced-theme-settings.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
- apps/admin-x-design-system/src/global/modal/confirmation-modal.tsx
- apps/admin-x-design-system/src/global/modal/modal.tsx
- apps/admin-x-settings/src/components/settings/site/theme/theme-code-editor-modal.tsx
- apps/admin-x-settings/src/components/settings/site/theme/invalid-theme-modal.tsx
- apps/admin-x-settings/src/components/settings/site/theme-modal.tsx

Why
Theme validation outcomes weren't clear: blocking and non-blocking issues were mixed together, and titles like "Upload successful with errors" muddled whether an upload had actually worked. This makes the outcome obvious — what blocked the theme vs. what's optional to fix later.
This is the production version of the approved spike #28254 (visual design is unchanged from that PR's screenshots), with the spike scaffolding removed.
What
theme-validation-detailsview drives both, so the modals stay consistent.ConfirmationModalContentgainsstickyFooter+ReactNodetitles so long validation content can't hide the modal actions.Behaviour is unchanged
problem.fatal === trueis the only blocking discriminator. By default in production only fatal errors block; errors/warnings/recommendations are grouped as non-blocking (collapsed in production, expanded in development).problem.level(error/warning/recommendation) with no remapping — a problem's reported level is what's shown.ThemeProblem.levelis widened to includerecommendationto match GScan's actual three levels (it previously only typederror/warning).Difference vs. the spike
Testing
admin-x-settings,admin-x-framework,admin-x-design-system(Node 22.18.0)pnpm devboots and serves Admin