Skip to content

feat(ui-next): Adds appearance dialog with theme presets and custom theme support#6041

Open
dan-rukas wants to merge 30 commits into
OHIF:masterfrom
dan-rukas:feat/appearance-dialog-simple
Open

feat(ui-next): Adds appearance dialog with theme presets and custom theme support#6041
dan-rukas wants to merge 30 commits into
OHIF:masterfrom
dan-rukas:feat/appearance-dialog-simple

Conversation

@dan-rukas

@dan-rukas dan-rukas commented May 26, 2026

Copy link
Copy Markdown
Member

Context

Adds a theme preset system to the Viewer with an Appearance dialog accessible from the header gear menu. Users can switch between 6 dark-mode color palettes — 3 tonal (full hue-shifted) and 3 neutral (achromatic surfaces with colored accent). Selections persist across sessions via localStorage and can be deep-linked via URL parameter.

This is a minimal-risk approach: master's tailwind.css is completely untouched, no new runtime dependencies are added, and the application renders identically to production when no preset is selected.

Deploy preview: https://ohif-theme-apply.netlify.app/

Appearance-preview

Changes & Results

  • Appearance modal in the header gear menu (between About and Preferences)
  • 6 dark-only theme presets: Orchid, Arctic, Verdant (tonal) + Midnight, Slate, Deep (neutral)
  • ActiveThemeProvider — manages .theme-* class on <body>, persists to localStorage
  • Custom theme paste — allows users to paste CSS variable overrides for fully custom themes
  • URL parameter?theme=orchid, ?theme=slate, etc. for deep-linking and testing
  • Full i18n — all modal strings use the AppearanceModal translation namespace

What's NOT included (intentional scope decisions):

  • No dark/light mode toggle — Can be added in the future and dialog has design options to allow it
  • No changes to tailwind.css — the foundational CSS layer is untouched
  • No light mode CSS values — presets are dark-only
  • No ThemeProvider / next-themes — not needed without a mode toggle

Architecture

Provider Chain

A single new provider manages all theme state — preset selection, custom CSS parsing and injection, persistence, and URL parameter handling:

ActiveThemeProvider    → manages .theme-* class on <body>, imports themes.css, owns all theme state

ActiveThemeProvider is added to the existing provider chain in App.tsx. No ThemeProvider (next-themes) is needed because there is no mode toggle. The app stays dark-only, exactly as it is today.

CSS Cascade

The override mechanism uses standard CSS custom property inheritance — no specificity tricks, no !important, no layer manipulation:

<html>    ← :root variables (OHIF's existing dark tokens, inside @layer base)
  <body class="theme-orchid">    ← preset variables (outside @layer, override inherited :root)
    <div>    ← uses variables from nearest ancestor that defines them

Two factors ensure presets always win:

  1. CSS layer prioritythemes.css is outside @layer base, so it has higher priority than tailwind.css's @layer base tokens regardless of source order
  2. Proximity — variables set on <body> override inherited values from <html> (:root)

When the user selects "Default", the .theme-* class is removed from <body>, and all variables fall back to :root. The application looks exactly as it does without this PR installed.

Theme Preset Structure

Each preset is a complete, self-contained dark token set — all ~28 CSS variables are defined in every preset. No partial overrides.

Why complete sets instead of partial overrides:

  • Self-documenting — read one .theme-* block and know exactly what the theme looks like
  • No inheritance surprises — a tonal theme (e.g., Orchid) shifts everything purple; partial overrides would leave some surfaces blue and others purple
  • Safe to extend — adding a new token to the base theme can't silently break a preset
  • Easier to generate — the theme creator tool outputs complete token sets

Each preset is defined in two places:

  • CSS (themes.css) — .theme-* block with all variables. This is what the browser uses at runtime.
  • JSON (themes/*.json) — name, label, and cssVars.dark record. This drives the dropdown UI (label display, preset enumeration) and the theme creator tool.

The JSON files intentionally contain only the 23 core design tokens (not chart, radius, or status tokens). Chart and status tokens inherit from the base theme in tailwind.css because they are currently identical across all presets. If a future preset needs different chart colors, they can be added to both the JSON and CSS at that time.

New Files (14)

File Purpose
ui-next/src/contextProviders/ActiveThemeProvider.tsx Theme preset state, .theme-* class management on <body>, localStorage + URL param, custom CSS parsing + injection
ui-next/src/components/OHIFModals/AppearanceModal.tsx Modal shell — compound component (Body, SectionLabel)
ui-next/src/themes/themes.css All preset CSS (6 .theme-* blocks, dark-only, outside @layer base)
ui-next/src/themes/index.ts ThemePreset interface, themePresets array export
ui-next/src/themes/orchid.json Orchid preset metadata (name, label, cssVars.dark)
ui-next/src/themes/arctic.json Arctic preset metadata
ui-next/src/themes/verdant.json Verdant preset metadata
ui-next/src/themes/midnight.json Midnight preset metadata
ui-next/src/themes/slate.json Slate preset metadata
ui-next/src/themes/deep.json Deep preset metadata
extensions/default/src/customizations/appearanceModalCustomization.tsx Modal content: preset dropdown + custom paste UI
platform/i18n/src/locales/en-US/AppearanceModal.json i18n strings for the Appearance modal

Modified Files (7)

File Change
ui-next/src/contextProviders/index.ts Export ActiveThemeProvider, useActiveTheme, themePresets, ThemePreset
ui-next/src/components/OHIFModals/index.ts Export AppearanceModal
ui-next/src/components/index.ts Export AppearanceModal
platform/app/src/App.tsx Add ActiveThemeProvider to provider chain
platform/i18n/src/locales/en-US/Header.json Add "Appearance" key
platform/i18n/src/locales/en-US/index.js Register AppearanceModal namespace
extensions/default/src/ViewerLayout/ViewerHeader.tsx Add Appearance menu item + modal trigger
extensions/default/src/getCustomizationModule.tsx Register appearanceModalCustomization

Important notes

Why tailwind.css is untouched

tailwind.css defines every CSS variable in the application — it's the foundation of the entire UI. The only CSS this PR adds is in a new themes.css file that only takes effect when a user actively selects a preset. Zero risk of regressions to existing styling.

Why the custom theme injection writes to both :root and .dark

The custom theme paste injects CSS targeting both :root and .dark:

:root { --background: 270 45% 6%; ... }
.dark { --background: 270 45% 6%; ... }

On this branch there is no .dark class on <html>, so the .dark block is currently inert. It exists as forward compatibility — if a dark/light mode toggle is added later (via next-themes, which adds .dark to <html>), the custom theme injection will work without changes.

Why the custom theme paste feature exists

The paste feature lets users paste CSS variable overrides directly into OHIF to create fully custom themes without editing source files. Use cases include institutional branding, accessibility adjustments, and rapid theme prototyping. It also supports the theme creator tool workflow. Users can generate a palette externally and apply it to OHIF via paste.

All custom theme state: parsing, sanitization, injection, and persistence — is centralized in ActiveThemeProvider. The modal is a thin UI layer that calls applyCustomTheme() and clearCustomTheme() from context.

Input sanitization: The parseCssVars function in ActiveThemeProvider validates property names against --[a-zA-Z0-9-]+ and strips {, }, and ; from values before reassembling the CSS. This prevents CSS injection via crafted input that could break out of the :root {} block.

Why ActiveThemeProvider cleanup uses collect-then-remove

The setActiveTheme callback and the useEffect cleanup both need to remove theme-* classes from document.body.classList. Both use a two-phase pattern: collect matching classes into an array first, then remove them. This avoids mutating DOMTokenList during iteration, which has implementation-dependent behavior and could skip elements if multiple theme-* classes were present.

How to Add a New Preset

For a developer who wants to add a new theme preset:

  1. Create the JSONplatform/ui-next/src/themes/{name}.json:

    {
      "name": "{name}",
      "label": "Tonal: {Display Name}",
      "cssVars": {
        "dark": {
          "background": "...",
          "foreground": "...",
          ...all 23 core tokens
        }
      }
    }
  2. Add CSS — Add a .theme-{name} block to themes.css with all variables (core tokens + chart tokens)

  3. Register — Import the JSON in themes/index.ts and add it to the themePresets array

The dropdown, persistence, URL parameter, and class management all work automatically — no other files need to change.

Testing

  • App boots in dark mode (identical to current production)
  • Gear menu shows "Appearance" option with ColorChange icon
  • Appearance modal opens with Theme dropdown
  • Default theme shows "Tonal: OHIF Blue" label
  • Each preset selection changes colors immediately across the entire UI
  • Selecting "Default" restores original OHIF colors exactly
  • Hard refresh preserves selected preset (localStorage persistence)
  • ?theme=orchid URL parameter applies preset on load
  • ?theme=default URL parameter resets to default
  • Invalid ?theme=nonexistent is ignored (falls back to localStorage or default)
  • Custom theme paste: pasting valid CSS variables applies them live
  • Custom theme paste: input with } or { characters is sanitized (no CSS injection)
  • Custom theme clear: resets to default, removes injected style element
  • All modal strings render correctly (no raw i18n key fallbacks)
  • yarn build succeeds with no TypeScript errors
  • No visual regressions when no preset is selected (default state = production)

Checklist

PR

  • My Pull Request title is descriptive, accurate and follows the
    semantic-release format and guidelines.

Code

  • My code has been well-documented (function documentation, inline comments,
    etc.)

Public Documentation Updates

  • [] The documentation page has been updated as necessary for any public API
    additions or removals.

Tested Environment

  • OS: macOS Sequoia 15.7.4 (24G517), Apple M4 Pro
  • Node version: 20.19.1
  • Browser: Chrome (latest) 148.0.7778.179 (Official Build) (arm64)

Summary by CodeRabbit

  • New Features
    • Added an Appearance option in the header and settings menu to open a dedicated Appearance modal.
    • Introduced an Appearance modal to choose a theme preset, edit custom theme tokens, Apply changes, and Clear custom settings.
    • Added built-in theme presets (new tonal/neutral options) with UI theme switching that persists and supports URL-based overrides (including a custom theme mode).
  • Documentation / Localization
    • Added English strings for the Appearance modal and header, and localized About and Preferences labels.

Greptile Summary

This PR adds a theme preset system to the OHIF Viewer — an Appearance dialog accessible from the header gear menu and the worklist settings popover. All theme state is centralized in a new ActiveThemeProvider that manages the .theme-* body class, localStorage persistence, URL parameter handling, and custom CSS injection with sanitization.

  • ActiveThemeProvider initialises theme from URL param or localStorage with well-guarded validation (stale keys and orphaned custom state both fall back to default); the previously reported comment-injection and localStorage-corruption edge cases are addressed in this revision.
  • Six preset CSS blocks in themes.css are placed outside @layer base, correctly overriding :root tokens via CSS proximity — tailwind.css is entirely untouched.
  • Custom theme paste runs through parseCssVars which strips comments, blocks, parentheses, and non----prefixed names before assembling the injected stylesheet.

Confidence Score: 5/5

Safe to merge — changes are additive, the default app state is identical to production when no preset is selected, and the custom CSS sanitization pipeline is solid.

All theme mutations are confined to the new provider; tailwind.css is untouched. The initialization guards handle orphaned localStorage state. The CSS injection sanitizer rejects parentheses, braces, and comment sequences. No breaking changes to existing components or APIs.

No files require special attention.

Important Files Changed

Filename Overview
platform/ui-next/src/contextProviders/ActiveThemeProvider.tsx New provider managing theme state, DOM class manipulation, localStorage/URL param persistence, and custom CSS injection with robust sanitization — well-guarded initialization logic.
extensions/default/src/customizations/appearanceModalCustomization.tsx Modal UI implementing preset dropdown and custom CSS textarea; state wiring with context is clean; previously flagged edge cases are addressed or acknowledged as intentional.
extensions/default/src/ViewerLayout/ViewerHeader.tsx Adds Appearance menu item between About and Preferences using the same customization-service pattern as existing modals, with correct optional chaining.
platform/app/src/routes/WorkList/StudyListSettingsPopover.tsx Adds appearance menu item in the worklist settings popover; label is hardcoded consistent with all other items in this file.
platform/ui-next/src/themes/themes.css Six complete .theme-* CSS blocks with all design tokens; correctly placed outside @layer so they override :root via proximity — well-structured.
platform/ui-next/src/components/OHIFModals/AppearanceModal.tsx Thin compound-component shell (Body + SectionLabel sub-components); follows same pattern as existing modal shells.
platform/app/src/App.tsx Adds ActiveThemeProvider to the provider chain after ThemeWrapperNext; positioning is appropriate.

Comments Outside Diff (1)

  1. platform/ui-next/src/contextProviders/ActiveThemeProvider.tsx, line 478-488 (link)

    P1 Corrupted localStorage state adds unrecognised theme-custom class to <body>

    If localStorage has ohif:theme = "custom" but ohif:custom-theme-css is missing (e.g. the key was set by a URL parameter that got persisted, the CSS key was cleared manually, or the user's storage was partially wiped), then activeTheme initialises to 'custom' while customCss is ''.

    The useEffect branch for this state is else if (activeTheme !== 'default'), which adds theme-custom to <body>. No .theme-custom rule exists in themes.css, so no styles change — but the class leaks into the DOM silently. Additionally, the Select's value="custom" matches no rendered <SelectItem> (the "Custom" item is conditionally rendered only when customCss || draftCss is truthy), leaving the dropdown visually blank or stuck.

    A safe fix is to guard the activeTheme='custom' initialisation path: if activeTheme is 'custom' but customCss is empty, fall back to 'default'.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: platform/ui-next/src/contextProviders/ActiveThemeProvider.tsx
    Line: 478-488
    
    Comment:
    **Corrupted localStorage state adds unrecognised `theme-custom` class to `<body>`**
    
    If `localStorage` has `ohif:theme = "custom"` but `ohif:custom-theme-css` is missing (e.g. the key was set by a URL parameter that got persisted, the CSS key was cleared manually, or the user's storage was partially wiped), then `activeTheme` initialises to `'custom'` while `customCss` is `''`.
    
    The `useEffect` branch for this state is `else if (activeTheme !== 'default')`, which adds `theme-custom` to `<body>`. No `.theme-custom` rule exists in `themes.css`, so no styles change — but the class leaks into the DOM silently. Additionally, the Select's `value="custom"` matches no rendered `<SelectItem>` (the "Custom" item is conditionally rendered only when `customCss || draftCss` is truthy), leaving the dropdown visually blank or stuck.
    
    A safe fix is to guard the `activeTheme='custom'` initialisation path: if `activeTheme` is `'custom'` but `customCss` is empty, fall back to `'default'`.
    
    How can I resolve this? If you propose a fix, please make it concise.

Reviews (6): Last reviewed commit: "Added Appearance dialog to ui-next Study..." | Re-trigger Greptile

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@netlify

netlify Bot commented May 26, 2026

Copy link
Copy Markdown

Deploy Preview for ohif-dev failed. Why did it fail? →

Name Link
🔨 Latest commit 71fd609
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/6a3d5a2c109d9c00080db8f4

@dan-rukas dan-rukas changed the title feat(AppearanceDialog): Adds appearance dialog with theme presets and custom theme support feat(ui-next): Adds appearance dialog with theme presets and custom theme support May 26, 2026
Comment thread platform/ui-next/src/contextProviders/ActiveThemeProvider.tsx
Comment thread platform/ui-next/src/contextProviders/ActiveThemeProvider.tsx
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds active theme context and preset theme assets, an appearance modal UI and customization, app provider integration, and English localization for the new UI.

Changes

Appearance Modal Theme Customization

Layer / File(s) Summary
Theme System Infrastructure
platform/ui-next/src/contextProviders/ActiveThemeProvider.tsx, platform/ui-next/src/contextProviders/index.ts, platform/ui-next/src/themes/index.ts, platform/ui-next/src/themes/{arctic,deep,midnight,orchid,slate,verdant}.json, platform/ui-next/src/themes/themes.css
ActiveThemeProvider and useActiveTheme manage active theme state, URL and storage validation, custom CSS parsing/injection, and export themePresets with ThemePreset.
Appearance Modal UI Component
platform/ui-next/src/components/OHIFModals/AppearanceModal.tsx, platform/ui-next/src/components/OHIFModals/index.ts, platform/ui-next/src/components/index.ts
Adds AppearanceModal with Body and SectionLabel subcomponents and re-exports it through modal and components barrels.
Appearance Modal Customization and Wiring
extensions/default/src/customizations/appearanceModalCustomization.tsx, extensions/default/src/ViewerLayout/ViewerHeader.tsx, extensions/default/src/getCustomizationModule.tsx, platform/app/src/routes/WorkList/StudyListSettingsPopover.tsx, platform/app/public/config/netlify.js
Registers ohif.appearanceModal, implements preset/custom interactions, adds Appearance menu entries in the viewer header and worklist popover, and enables the customization module in the default config.
App Provider Integration
platform/app/src/App.tsx
Inserts ActiveThemeProvider into the app provider chain after ThemeWrapperNext.
Localization Strings
platform/i18n/src/locales/en-US/AppearanceModal.json, platform/i18n/src/locales/en-US/Header.json, platform/i18n/src/locales/en-US/index.js
Adds English translations for the Appearance modal UI and registers the new locale resource in en-US.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • OHIF/Viewers#6005: Touches the same WorkList settings popover menu construction used by the new Appearance entry.

Suggested labels

ohif-integration

Suggested reviewers

  • sedghi

Poem

🐰 I hop through themes from midnight deep,
To orchid glow where bright colors leap.
A modal opens, tidy and keen,
With custom hues and tokens seen.
Hop, hop—new styles bloom and gleam.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is descriptive, accurate, and matches the main change set around the Appearance dialog and theme presets.
Description check ✅ Passed The description follows the template closely with Context, Changes & Results, Testing, and Checklist sections filled out.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@dan-rukas

Copy link
Copy Markdown
Member Author

Pushed a round of fixes addressing the open review comment plus issues found in a deeper review pass:

Custom CSS parsing (parseCssVars)

  • Comments are now removed as full spans, text included — previously only the /* */ delimiters were stripped, so a line like --primary: 222 100% 50%; /* brand blue */ injected a corrupted value that was invalid at computed-value time. This also resolves the unclosed-comment case at the root.
  • Single-line and minified CSS now parses: input splits on ; as well as newlines, and :root { / .dark { prefixes are tolerated. Previously minified input silently parsed to zero vars.
  • Values containing parentheses are rejected. Token values are bare HSL triplets, so nothing legitimate is lost — and this guarantees url(...) can never reach the injected stylesheet, even if a future rule consumes a token outside hsl().

Theme state validation

  • The theme restored from localStorage is now validated against VALID_THEMES (same as the URL param already was), and custom requires saved CSS to exist — fixes the orphan theme-custom body class / blank Select from the earlier review comment.
  • ?theme=custom is no longer accepted from the URL: the CSS behind a custom theme lives only in the visitor's own localStorage, so the link can't reproduce a look and would only strand the app in a custom state with nothing behind it.

Modal UX / a11y

  • Apply now shows an inline error when no valid tokens parse (previously a silent no-op).
  • Selecting "Custom" in the dropdown restores the saved custom theme; drafts only commit via the Apply button.
  • Added aria-label to the theme Select and the CSS textarea.

Header

  • The Appearance menu entry now honors menuTitle / title / containerClassName from the ohif.appearanceModal customization, matching the About/Preferences pattern.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
platform/app/src/routes/WorkList/StudyListSettingsPopover.tsx (1)

43-54: ⚡ Quick win

Consider using i18n for the menu label and checking menuTitle.

The label is hardcoded as 'Appearance', but ViewerHeader.tsx uses AppearanceModal?.menuTitle ?? t('Header:Appearance') for its menu text. For better i18n support and consistency across menu contexts, consider:

 {
   id: 'appearance',
-  label: 'Appearance',
+  label: AppearanceModal?.menuTitle ?? t('Header:Appearance'),
   onClick: () => {
     const AppearanceModal = customizationService.getCustomization('ohif.appearanceModal');

Note: This pattern would also improve the About and User Preferences items in the same file.

🤖 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 `@platform/app/src/routes/WorkList/StudyListSettingsPopover.tsx` around lines
43 - 54, The menu item with id 'appearance' currently hardcodes label:
'Appearance'; update it to use i18n and the modal's menuTitle like ViewerHeader
does by replacing the label with AppearanceModal?.menuTitle ??
t('Header:Appearance') (locate the onClick that calls
customizationService.getCustomization('ohif.appearanceModal') and show(...) and
compute AppearanceModal before building the menu item), and apply the same
pattern to the About and User Preferences items for consistency.
🤖 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.

Nitpick comments:
In `@platform/app/src/routes/WorkList/StudyListSettingsPopover.tsx`:
- Around line 43-54: The menu item with id 'appearance' currently hardcodes
label: 'Appearance'; update it to use i18n and the modal's menuTitle like
ViewerHeader does by replacing the label with AppearanceModal?.menuTitle ??
t('Header:Appearance') (locate the onClick that calls
customizationService.getCustomization('ohif.appearanceModal') and show(...) and
compute AppearanceModal before building the menu item), and apply the same
pattern to the About and User Preferences items for consistency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 300a232d-e853-4575-88c3-b309b79ff409

📥 Commits

Reviewing files that changed from the base of the PR and between 3b8faad and 40b0a94.

📒 Files selected for processing (1)
  • platform/app/src/routes/WorkList/StudyListSettingsPopover.tsx

@sedghi sedghi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we document how to enable/disable this theme in the dropdown, also if we are adding it in our master branch, let's document a migration if someone wants to turn it off

@sedghi

sedghi commented Jun 24, 2026

Copy link
Copy Markdown
Member

ideally should use a customization under default extension that is only on for netlify config that we deploy for our demo app. default js config is what most people use and edit so no changes there so that people don't get this theme unless they enable that customiztaion

- Guard menu entries in ViewerHeader and StudyListSettingsPopover
- Enable for Netlify demo config only
- Fix i18n on StudyListSettingsPopover labels
@dan-rukas

Copy link
Copy Markdown
Member Author

Update: Provider registration moved to extension

Moved ActiveThemeProvider registration out of App.tsx and into the default extension's preRegistration hook via serviceProvidersManager.registerProvider(). This is an existing OHIF mechanism that was designed for extensions to contribute providers dynamically.

What changed:

  • init.ts — checks the config for the theme module and conditionally registers the provider
  • App.tsx — no longer imports ActiveThemeProvider or has any theming awareness

Why: App.tsx shouldn't inspect extension configs to decide whether to mount a specific provider. The extension that owns the theme module now owns the decision to register its provider. Zero footprint when theming is disabled is preserved — ?theme= URL params are still blocked when the module isn't in the config.

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.

2 participants