Skip to content

feat: extract theme module with build-time overrides#79

Merged
overtrue merged 3 commits intomainfrom
codex/theme-module-extraction
Mar 15, 2026
Merged

feat: extract theme module with build-time overrides#79
overtrue merged 3 commits intomainfrom
codex/theme-module-extraction

Conversation

@overtrue
Copy link
Copy Markdown
Collaborator

Pull Request

Description

Extract a theme module for console with a standardized manifest.json and build-time override workflow.

This PR adds:

  • Theme manifest loading for brand/links metadata
  • Build-time same-path overrides for components, assets, and public
  • Key-level locale override merge support (base locale + theme locale)
  • Theme switching safety via automatic rollback before re-apply
  • Built-in themes: default and a full-capability example
  • Theme development guide in English

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • Test improvements
  • Security fix

Testing

  • Unit tests added/updated
  • Manual testing completed
pnpm install --frozen-lockfile
pnpm tsc --noEmit
pnpm lint
pnpm build
NEXT_PUBLIC_THEME_ID=example node scripts/apply-theme-overrides.mjs
NEXT_PUBLIC_THEME_ID=default node scripts/apply-theme-overrides.mjs

Note: pnpm test:run is not available in this repository (Command "test:run" not found).

Checklist

  • Code follows the project's style guidelines
  • Self-review completed
  • TypeScript types are properly defined
  • All commit messages are in English (Conventional Commits)
  • All existing tests pass
  • No new dependencies added, or they are justified

Related Issues

Closes #

Screenshots (if applicable)

N/A (infrastructure/theme system changes).

Additional Notes

Key files:

  • scripts/apply-theme-overrides.mjs
  • config/theme-manifest.json
  • lib/theme/manifest.ts
  • lib/theme/locales.ts
  • themes/README.md
  • themes/default/*
  • themes/example/*

Copilot AI review requested due to automatic review settings March 15, 2026 05:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Extracts a build-time theming system for the console by introducing a standardized theme manifest, build-time same-path overrides, and locale override merging, then wiring theme metadata into core UI entry points.

Changes:

  • Added theme manifest normalization/loading and updated UI surfaces (metadata, nav, auth/config pages, sidebar, social links) to use theme-provided brand/link values.
  • Implemented build/dev-time theme override application (components/assets/public + manifest copy) and added built-in default and example themes.
  • Added theme locale override fetching and key-level merge into i18n resources.

Reviewed changes

Copilot reviewed 25 out of 31 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
types/theme.d.ts Adds ThemeManifest type used by the new theme module.
scripts/apply-theme-overrides.mjs Applies and rolls back theme overrides + copies theme manifest into config at build/dev time.
package.json Runs the theme override script automatically for dev and build.
lib/theme/manifest.ts Loads and normalizes config/theme-manifest.json into a typed manifest API.
lib/theme/locales.ts Loads theme locale overrides from a public URL path.
lib/i18n.ts Merges base locale resources with theme override keys during i18n initialization.
config/theme-manifest.json Provides the default theme manifest used when no overrides are applied.
config/navs.ts Uses the theme manifest to override the Documentation link target.
components/user/dropdown.tsx Uses theme brand name for avatar alt text (and introduces theme access in dropdown).
components/tiers/new-form.tsx Uses theme brand name when selected type is rustfs.
components/sidebars/version.tsx Defaults displayed app name to the theme short name.
components/links/x.tsx Uses theme-provided X link (and hides the link if unset).
components/links/github.tsx Uses theme-provided GitHub link (and hides the link if unset).
components/auth/login-form.tsx Uses theme brand name for logo alt + theme website link for “Get Help”.
components/app-sidebar.tsx Uses theme brand name for sidebar app name and logo alt.
app/layout.tsx Uses theme brand name/description for Next metadata.
app/(auth)/config/page.tsx Uses theme brand name for logo alt + theme docs link for “Need help?”.
themes/default/manifest.json Adds default theme manifest.
themes/default/assets/logo.svg Adds default theme logo asset.
themes/default/components/.gitkeep Placeholder to keep theme components dir in repo.
themes/default/locales/.gitkeep Placeholder to keep theme locales dir in repo.
themes/default/public/.gitkeep Placeholder to keep theme public dir in repo.
themes/example/manifest.json Adds example theme manifest (brand + links).
themes/example/assets/logo.svg Adds example theme logo asset for same-path override.
themes/example/components/auth/heroes/hero-static.tsx Example same-path component override for the auth hero.
themes/example/locales/en-US.json Example locale overrides (en-US).
themes/example/locales/zh-CN.json Example locale overrides (zh-CN).
themes/example/public/backgrounds/scillate.svg Example public asset override.
themes/example/public/backgrounds/ttten.svg Example public asset override.
themes/example/README.md Documents how the example theme validates override capabilities.
.gitignore Ignores runtime theme artifacts and restricts committed themes to default/example.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +12 to +14
const themeId = process.env.NEXT_PUBLIC_THEME_ID?.trim() || process.env.THEME_ID?.trim() || "default"
const themeRoot = path.join(root, "themes", themeId)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Great catch. I added strict themeId validation (^[A-Za-z0-9_-]+$) and resolve/path-boundary checks to ensure it cannot escape the themes/ directory.

Comment on lines +124 to +140
restorePreviousOverrides()

for (const mapping of mappings) {
copyDirRecursive(path.join(themeRoot, mapping.from), path.join(root, mapping.to))
}

const themeManifestPath = path.join(themeRoot, "manifest.json")
const targetManifestPath = path.join(root, "config", "theme-manifest.json")

if (fs.existsSync(themeManifestPath)) {
backupBeforeWrite(targetManifestPath)
ensureParentDir(targetManifestPath)
fs.copyFileSync(themeManifestPath, targetManifestPath)
}

writeState(stateEntries)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed. The script now writes an initial empty state before apply and persists state incrementally on each backup (backupBeforeWrite), so rollback metadata survives mid-run failures.

Comment on lines +27 to +30
return {
schemaVersion: THEME_SCHEMA_VERSION,
id,
name,
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. schemaVersion is now read from manifest and validated strictly; mismatches throw with a clear error message instead of silently normalizing.

Comment on lines 38 to 44
for (const [code, file] of Object.entries(LOCALE_FILE_MAP)) {
const baseLocale = await loadLocale(file)
const themeOverride = await loadThemeLocaleOverride(file)

resources[code] = {
translation: await loadLocale(file),
translation: themeOverride ? { ...baseLocale, ...themeOverride } : baseLocale,
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Improved. Theme locale loading now uses a generated index.json to request only existing override locale files, avoiding per-locale 404 fetches at startup.

overtrue and others added 3 commits March 15, 2026 14:01
@overtrue overtrue force-pushed the codex/theme-module-extraction branch from b55ef04 to 3a264d7 Compare March 15, 2026 06:01
@overtrue overtrue merged commit 5cd22b3 into main Mar 15, 2026
11 checks passed
@overtrue overtrue deleted the codex/theme-module-extraction branch March 15, 2026 06:03
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