Skip to content

Self-hosted: theme manifests so a template can own its layout - #1442

Merged
feruzm merged 3 commits into
developfrom
feature/self-hosted-theme-manifests
Aug 12, 2026
Merged

Self-hosted: theme manifests so a template can own its layout#1442
feruzm merged 3 commits into
developfrom
feature/self-hosted-theme-manifests

Conversation

@feruzm

@feruzm feruzm commented Aug 11, 2026

Copy link
Copy Markdown
Member

Closes #1418. Independent of the signup chain (base develop); pairs with the merged #1436, whose config-store preview is what lets a structural theme preview correctly.

No component read general.styleTemplate: all five templates rendered the identical component tree and differed only through CSS custom properties, which caps how different two templates can look. This introduces the architecture that lifts that cap without changing a rendered byte today:

  • src/themes/manifest.ts defines a theme as { id, tier, components? } over five named seams: Shell (the page frame), Navigation, Sidebar, ArchiveList (the feed container) and PostCard. Absent entries fall back to shared defaults.
  • src/themes/registry.ts holds one manifest per roster id. All five existing templates carry NO components key: they are CSS-only, so resolution falls back to exactly the components the shell used to hardcode. The registry test pins this no-op (registry total over the roster, no overrides, unknown ids resolving to the default the same way apply-config-dom resolves the attribute).
  • useThemeComponents resolves the active template's components reactively through the config store, so the Configuration Editor's preview restructures live and exiting preview restores the baseline components.
  • BlogLayout becomes the Shell resolver; the previous frame moves to DefaultShell, which itself resolves Navigation and Sidebar through the registry, so a theme can replace the masthead or the rail without owning the whole frame. The feed resolves ArchiveList and PostCard the same way.
  • Standing constraints hold: sidebar placement and visibility stay pure CSS through the existing attribute contract, the full --theme-* token contract stays mandatory per theme and route topology stays global so deep links never fork per theme.

Deliberately deferred: an EmptyState seam and per-theme option sets (#1419) join when the first layout-level theme (Journal, #1420) actually needs them; the search results card call site joins the PostCard seam at the same time. tier is carried but not yet enforced; server-side enforcement lands with the first premium theme (#1421).

SPA 882 tests, typecheck and production build green.

No component read general.styleTemplate: every template rendered the
identical tree and differed only through CSS custom properties. Themes
are now code-level manifests ({ id, tier, components? }) over named
seams (Shell, Navigation, Sidebar, ArchiveList, PostCard), resolved
reactively through useThemeComponents so the editor's config-store
preview restructures live and exiting preview restores the baseline.

The five existing templates migrate as manifests with NO components
key: resolution falls back to the shared defaults, which are exactly
the components the shell hardcoded before, so rendering is unchanged
by construction and the registry test pins the no-op (every roster id
present, no overrides, unknown ids resolving like apply-config-dom
does). BlogLayout becomes the Shell resolver; the previous frame moves
to DefaultShell, which itself resolves Navigation and Sidebar through
the registry so a theme can replace either without owning the frame.

Sidebar placement and visibility stay pure CSS via the existing
attribute contract; route topology stays global, so layout variants
render inside existing routes and deep links never fork per theme.

Closes #1418
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 11, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (3)

Grey Divider


Remediation recommended

1. useThemeComponents lacks tests ✓ Resolved 📘 Rule violation ▣ Testability
Description
New functional logic was added in useThemeComponents() without a corresponding new/updated test
that exercises its behavior. This reduces confidence in the new theme-component resolution behavior
(especially around config-driven switching).
Code

apps/self-hosted/src/themes/use-theme-components.ts[R31-41]

+export function useThemeComponents(): ThemeComponents {
+  const styleTemplate = InstanceConfigManager.useConfig(
+    ({ configuration }) => configuration.general.styleTemplate,
+  );
+  return useMemo(
+    () => ({
+      ...DEFAULT_THEME_COMPONENTS,
+      ...getThemeManifest(styleTemplate).components,
+    }),
+    [styleTemplate],
+  );
Relevance

●●● Strong

Team commonly accepts adding/strengthening tests to lock in new behavior and prevent regressions.

PR-#1284

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2667972 requires tests for new functional code paths. The PR adds the
useThemeComponents() hook implementation, but the only added test file in this change set targets
the registry behavior and does not cover the hook.

Rule 2667972: Require tests for all new functional code paths
apps/self-hosted/src/themes/use-theme-components.ts[31-41]
apps/self-hosted/src/themes/registry.test.ts[12-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useThemeComponents()` is newly introduced functional logic, but there is no added/updated test that exercises its behavior.
## Issue Context
The hook resolves theme components based on `configuration.general.styleTemplate` via `InstanceConfigManager.useConfig()` and merges manifest overrides. This is a new behavior path that should be covered by automated tests per the compliance checklist.
## Fix Focus Areas
- apps/self-hosted/src/themes/use-theme-components.ts[31-41]
- apps/self-hosted/src/themes/registry.test.ts[12-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Shell bundles feed code 🐞 Bug ➹ Performance
Description
useThemeComponents hardwires the default ArchiveList to BlogPostsList, so importing the hook
pulls BlogPostsList (and its React Query/SDK dependencies) into the module graph. Because
BlogLayout/DefaultShell call useThemeComponents, routes that only render the shell (e.g. the
post page) now lose code-splitting for the archive/feed seam and pay extra JS load cost.
Code

apps/self-hosted/src/themes/use-theme-components.ts[R19-22]

+  Navigation: BlogNavigation,
+  Sidebar: BlogSidebar,
+  ArchiveList: BlogPostsList,
+  PostCard: BlogPostItem,
Relevance

●●● Strong

Team has accepted changes to preserve code-splitting via dynamic imports and avoiding static
dependency chains.

PR-#632

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The performance regression is a direct consequence of the new static dependency chain: BlogLayout
uses useThemeComponents, which defines ArchiveList defaults by importing BlogPostsList. Since
BlogPostPage renders BlogLayout, the post page now necessarily pulls in the archive/feed module
as part of the reachable import graph, even if it never renders the archive list.

apps/self-hosted/src/themes/use-theme-components.ts[1-23]
apps/self-hosted/src/features/blog/layout/blog-layout.tsx[1-13]
apps/self-hosted/src/features/blog/components/blog-post-page.tsx[8-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useThemeComponents` is used by the shared page shell (`BlogLayout`/`DefaultShell`) but it statically imports and exports defaults for *all* seams, including the archive/feed seam (`BlogPostsList`). This makes non-feed routes that render the shell pull the archive/feed code into their bundle, reducing code-splitting and increasing JS payload.
### Issue Context
This PR introduces theme seam resolution (Shell/Navigation/Sidebar/ArchiveList/PostCard). The current implementation puts all defaults into one module (`use-theme-components.ts`), so even pages that never render `ArchiveList` still import it via the shell.
### Fix Focus Areas
- apps/self-hosted/src/themes/use-theme-components.ts[1-42]
- apps/self-hosted/src/features/blog/layout/blog-layout.tsx[1-13]
- apps/self-hosted/src/features/blog/layout/default-shell.tsx[1-33]
- apps/self-hosted/src/routes/blog/route.tsx[45-55]
### Suggested approach
1. Split the resolver into at least two entry points so the shell doesn’t depend on the feed:
- `useThemeShellComponents()` -> `{ Shell, Navigation, Sidebar }`
- `useThemeFeedComponents()` -> `{ ArchiveList, PostCard }`
2. Keep the manifest shape the same, but provide defaults per-entry-point (e.g., `DEFAULT_SHELL_COMPONENTS` and `DEFAULT_FEED_COMPONENTS`) so importing the shell hook doesn’t import `BlogPostsList`.
3. Update call sites:
- `BlogLayout`/`DefaultShell` use the shell hook.
- `/blog` route and `BlogPostsList` use the feed hook.
This preserves the architecture while restoring code-splitting between shell and archive/feed seams.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. useThemeComponents lacks tests ✓ Resolved 📘 Rule violation ▣ Testability
Description
New functional logic was added in useThemeComponents() without a corresponding new/updated test
that exercises its behavior. This reduces confidence in the new theme-component resolution behavior
(especially around config-driven switching).
Code

apps/self-hosted/src/themes/use-theme-components.ts[R31-41]

+export function useThemeComponents(): ThemeComponents {
+  const styleTemplate = InstanceConfigManager.useConfig(
+    ({ configuration }) => configuration.general.styleTemplate,
+  );
+  return useMemo(
+    () => ({
+      ...DEFAULT_THEME_COMPONENTS,
+      ...getThemeManifest(styleTemplate).components,
+    }),
+    [styleTemplate],
+  );
Relevance

●●● Strong

Team commonly accepts adding/strengthening tests to lock in new behavior and prevent regressions.

PR-#1284

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2667972 requires tests for new functional code paths. The PR adds the
useThemeComponents() hook implementation, but the only added test file in this change set targets
the registry behavior and does not cover the hook.

Rule 2667972: Require tests for all new functional code paths
apps/self-hosted/src/themes/use-theme-components.ts[31-41]
apps/self-hosted/src/themes/registry.test.ts[12-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useThemeComponents()` is newly introduced functional logic, but there is no added/updated test that exercises its behavior.
## Issue Context
The hook resolves theme components based on `configuration.general.styleTemplate` via `InstanceConfigManager.useConfig()` and merges manifest overrides. This is a new behavior path that should be covered by automated tests per the compliance checklist.
## Fix Focus Areas
- apps/self-hosted/src/themes/use-theme-components.ts[31-41]
- apps/self-hosted/src/themes/registry.test.ts[12-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View review recommended (3)
4. Shell bundles feed code 🐞 Bug ➹ Performance
Description
useThemeComponents hardwires the default ArchiveList to BlogPostsList, so importing the hook
pulls BlogPostsList (and its React Query/SDK dependencies) into the module graph. Because
BlogLayout/DefaultShell call useThemeComponents, routes that only render the shell (e.g. the
post page) now lose code-splitting for the archive/feed seam and pay extra JS load cost.
Code

apps/self-hosted/src/themes/use-theme-components.ts[R19-22]

+  Navigation: BlogNavigation,
+  Sidebar: BlogSidebar,
+  ArchiveList: BlogPostsList,
+  PostCard: BlogPostItem,
Relevance

●●● Strong

Team has accepted changes to preserve code-splitting via dynamic imports and avoiding static
dependency chains.

PR-#632

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The performance regression is a direct consequence of the new static dependency chain: BlogLayout
uses useThemeComponents, which defines ArchiveList defaults by importing BlogPostsList. Since
BlogPostPage renders BlogLayout, the post page now necessarily pulls in the archive/feed module
as part of the reachable import graph, even if it never renders the archive list.

apps/self-hosted/src/themes/use-theme-components.ts[1-23]
apps/self-hosted/src/features/blog/layout/blog-layout.tsx[1-13]
apps/self-hosted/src/features/blog/components/blog-post-page.tsx[8-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useThemeComponents` is used by the shared page shell (`BlogLayout`/`DefaultShell`) but it statically imports and exports defaults for *all* seams, including the archive/feed seam (`BlogPostsList`). This makes non-feed routes that render the shell pull the archive/feed code into their bundle, reducing code-splitting and increasing JS payload.
### Issue Context
This PR introduces theme seam resolution (Shell/Navigation/Sidebar/ArchiveList/PostCard). The current implementation puts all defaults into one module (`use-theme-components.ts`), so even pages that never render `ArchiveList` still import it via the shell.
### Fix Focus Areas
- apps/self-hosted/src/themes/use-theme-components.ts[1-42]
- apps/self-hosted/src/features/blog/layout/blog-layout.tsx[1-13]
- apps/self-hosted/src/features/blog/layout/default-shell.tsx[1-33]
- apps/self-hosted/src/routes/blog/route.tsx[45-55]
### Suggested approach
1. Split the resolver into at least two entry points so the shell doesn’t depend on the feed:
- `useThemeShellComponents()` -> `{ Shell, Navigation, Sidebar }`
- `useThemeFeedComponents()` -> `{ ArchiveList, PostCard }`
2. Keep the manifest shape the same, but provide defaults per-entry-point (e.g., `DEFAULT_SHELL_COMPONENTS` and `DEFAULT_FEED_COMPONENTS`) so importing the shell hook doesn’t import `BlogPostsList`.
3. Update call sites:
- `BlogLayout`/`DefaultShell` use the shell hook.
- `/blog` route and `BlogPostsList` use the feed hook.
This preserves the architecture while restoring code-splitting between shell and archive/feed seams.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. useThemeComponents lacks tests ✓ Resolved 📘 Rule violation ▣ Testability
Description
New functional logic was added in useThemeComponents() without a corresponding new/updated test
that exercises its behavior. This reduces confidence in the new theme-component resolution behavior
(especially around config-driven switching).
Code

apps/self-hosted/src/themes/use-theme-components.ts[R31-41]

+export function useThemeComponents(): ThemeComponents {
+  const styleTemplate = InstanceConfigManager.useConfig(
+    ({ configuration }) => configuration.general.styleTemplate,
+  );
+  return useMemo(
+    () => ({
+      ...DEFAULT_THEME_COMPONENTS,
+      ...getThemeManifest(styleTemplate).components,
+    }),
+    [styleTemplate],
+  );
Relevance

●●● Strong

Team commonly accepts adding/strengthening tests to lock in new behavior and prevent regressions.

PR-#1284

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2667972 requires tests for new functional code paths. The PR adds the
useThemeComponents() hook implementation, but the only added test file in this change set targets
the registry behavior and does not cover the hook.

Rule 2667972: Require tests for all new functional code paths
apps/self-hosted/src/themes/use-theme-components.ts[31-41]
apps/self-hosted/src/themes/registry.test.ts[12-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useThemeComponents()` is newly introduced functional logic, but there is no added/updated test that exercises its behavior.
## Issue Context
The hook resolves theme components based on `configuration.general.styleTemplate` via `InstanceConfigManager.useConfig()` and merges manifest overrides. This is a new behavior path that should be covered by automated tests per the compliance checklist.
## Fix Focus Areas
- apps/self-hosted/src/themes/use-theme-components.ts[31-41]
- apps/self-hosted/src/themes/registry.test.ts[12-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Shell bundles feed code 🐞 Bug ➹ Performance
Description
useThemeComponents hardwires the default ArchiveList to BlogPostsList, so importing the hook
pulls BlogPostsList (and its React Query/SDK dependencies) into the module graph. Because
BlogLayout/DefaultShell call useThemeComponents, routes that only render the shell (e.g. the
post page) now lose code-splitting for the archive/feed seam and pay extra JS load cost.
Code

apps/self-hosted/src/themes/use-theme-components.ts[R19-22]

+  Navigation: BlogNavigation,
+  Sidebar: BlogSidebar,
+  ArchiveList: BlogPostsList,
+  PostCard: BlogPostItem,
Relevance

●●● Strong

Team has accepted changes to preserve code-splitting via dynamic imports and avoiding static
dependency chains.

PR-#632

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The performance regression is a direct consequence of the new static dependency chain: BlogLayout
uses useThemeComponents, which defines ArchiveList defaults by importing BlogPostsList. Since
BlogPostPage renders BlogLayout, the post page now necessarily pulls in the archive/feed module
as part of the reachable import graph, even if it never renders the archive list.

apps/self-hosted/src/themes/use-theme-components.ts[1-23]
apps/self-hosted/src/features/blog/layout/blog-layout.tsx[1-13]
apps/self-hosted/src/features/blog/components/blog-post-page.tsx[8-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useThemeComponents` is used by the shared page shell (`BlogLayout`/`DefaultShell`) but it statically imports and exports defaults for *all* seams, including the archive/feed seam (`BlogPostsList`). This makes non-feed routes that render the shell pull the archive/feed code into their bundle, reducing code-splitting and increasing JS payload.
### Issue Context
This PR introduces theme seam resolution (Shell/Navigation/Sidebar/ArchiveList/PostCard). The current implementation puts all defaults into one module (`use-theme-components.ts`), so even pages that never render `ArchiveList` still import it via the shell.
### Fix Focus Areas
- apps/self-hosted/src/themes/use-theme-components.ts[1-42]
- apps/self-hosted/src/features/blog/layout/blog-layout.tsx[1-13]
- apps/self-hosted/src/features/blog/layout/default-shell.tsx[1-33]
- apps/self-hosted/src/routes/blog/route.tsx[45-55]
### Suggested approach
1. Split the resolver into at least two entry points so the shell doesn’t depend on the feed:
 - `useThemeShellComponents()` -> `{ Shell, Navigation, Sidebar }`
 - `useThemeFeedComponents()` -> `{ ArchiveList, PostCard }`
2. Keep the manifest shape the same, but provide defaults per-entry-point (e.g., `DEFAULT_SHELL_COMPONENTS` and `DEFAULT_FEED_COMPONENTS`) so importing the shell hook doesn’t import `BlogPostsList`.
3. Update call sites:
 - `BlogLayout`/`DefaultShell` use the shell hook.
 - `/blog` route and `BlogPostsList` use the feed hook.
This preserves the architecture while restoring code-splitting between shell and archive/feed seams.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

7. Registry test misplaced 📜 Skill insight ⌂ Architecture
Description
The new test file is co-located under src/themes/ and uses a .test.ts name, but the checklist
requires tests to live under src/specs/ and use the .spec.ts(x) naming convention. This can
break test discovery/consistency and violates the enforced test layout rules.
Code

apps/self-hosted/src/themes/registry.test.ts[R1-3]

+import { describe, expect, it } from 'vitest';
+import { STYLE_TEMPLATES } from '../../hosting/api/src/style-templates';
+import { allThemeManifests, getThemeManifest } from './registry';
Relevance

● Weak

Repo previously rejected moving/renaming co-located .test.ts files to src/specs/.spec.ts.

PR-#1437

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance IDs 2668281 and 2668286 require tests to be placed under src/specs/ and named with
.spec.ts(x). The added file apps/self-hosted/src/themes/registry.test.ts violates both by
location and filename.

apps/self-hosted/src/themes/registry.test.ts[1-31]
Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new test file was added at `apps/self-hosted/src/themes/registry.test.ts`, but compliance requires tests to be placed under `src/specs/` and named using the `.spec.ts` / `.spec.tsx` convention.
## Issue Context
This PR introduces a new Vitest test for the theme registry. To comply with the repository test organization rules, the file should be moved out of `src/themes/` and renamed.
## Fix Focus Areas
- apps/self-hosted/src/themes/registry.test.ts[1-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Registry test misplaced 📜 Skill insight ⌂ Architecture
Description
The new test file is co-located under src/themes/ and uses a .test.ts name, but the checklist
requires tests to live under src/specs/ and use the .spec.ts(x) naming convention. This can
break test discovery/consistency and violates the enforced test layout rules.
Code

apps/self-hosted/src/themes/registry.test.ts[R1-3]

+import { describe, expect, it } from 'vitest';
+import { STYLE_TEMPLATES } from '../../hosting/api/src/style-templates';
+import { allThemeManifests, getThemeManifest } from './registry';
Relevance

● Weak

Repo previously rejected moving/renaming co-located .test.ts files to src/specs/.spec.ts.

PR-#1437

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance IDs 2668281 and 2668286 require tests to be placed under src/specs/ and named with
.spec.ts(x). The added file apps/self-hosted/src/themes/registry.test.ts violates both by
location and filename.

apps/self-hosted/src/themes/registry.test.ts[1-31]
Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new test file was added at `apps/self-hosted/src/themes/registry.test.ts`, but compliance requires tests to be placed under `src/specs/` and named using the `.spec.ts` / `.spec.tsx` convention.
## Issue Context
This PR introduces a new Vitest test for the theme registry. To comply with the repository test organization rules, the file should be moved out of `src/themes/` and renamed.
## Fix Focus Areas
- apps/self-hosted/src/themes/registry.test.ts[1-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Registry test misplaced 📜 Skill insight ⌂ Architecture
Description
The new test file is co-located under src/themes/ and uses a .test.ts name, but the checklist
requires tests to live under src/specs/ and use the .spec.ts(x) naming convention. This can
break test discovery/consistency and violates the enforced test layout rules.
Code

apps/self-hosted/src/themes/registry.test.ts[R1-3]

+import { describe, expect, it } from 'vitest';
+import { STYLE_TEMPLATES } from '../../hosting/api/src/style-templates';
+import { allThemeManifests, getThemeManifest } from './registry';
Relevance

● Weak

Repo previously rejected moving/renaming co-located .test.ts files to src/specs/.spec.ts.

PR-#1437

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance IDs 2668281 and 2668286 require tests to be placed under src/specs/ and named with
.spec.ts(x). The added file apps/self-hosted/src/themes/registry.test.ts violates both by
location and filename.

apps/self-hosted/src/themes/registry.test.ts[1-31]
Skill: add-test: Skill: add-test

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new test file was added at `apps/self-hosted/src/themes/registry.test.ts`, but compliance requires tests to be placed under `src/specs/` and named using the `.spec.ts` / `.spec.tsx` convention.
## Issue Context
This PR introduces a new Vitest test for the theme registry. To comply with the repository test organization rules, the file should be moved out of `src/themes/` and renamed.
## Fix Focus Areas
- apps/self-hosted/src/themes/registry.test.ts[1-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@feruzm, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8407fee7-c439-498b-8b30-f7a8e12960db

📥 Commits

Reviewing files that changed from the base of the PR and between 68bb4c4 and 532ab01.

📒 Files selected for processing (9)
  • apps/self-hosted/src/features/blog/components/blog-posts-list.tsx
  • apps/self-hosted/src/features/blog/layout/blog-layout.tsx
  • apps/self-hosted/src/features/blog/layout/default-shell.tsx
  • apps/self-hosted/src/routes/blog/route.tsx
  • apps/self-hosted/src/themes/manifest.ts
  • apps/self-hosted/src/themes/registry.test.ts
  • apps/self-hosted/src/themes/registry.ts
  • apps/self-hosted/src/themes/use-theme-components.ts
  • apps/self-hosted/vitest.config.ts

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.

@qodo-code-review

qodo-code-review Bot commented Aug 11, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Self-hosted: add theme manifests and seam-based component resolution

✨ Enhancement 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Introduce theme manifests with named layout seams (Shell, Navigation, Sidebar, ArchiveList,
 PostCard).
• Resolve theme components reactively from the config store for live editor preview.
• Refactor blog layout/feed to render via seam components while keeping current output unchanged.
Diagram

graph TD
  CFG[(Instance config store)] --> HOOK["useThemeComponents"] --> SHELL["Shell seam (BlogLayout)"]
  CFG[(Instance config store)] --> HOOK["useThemeComponents"] --> FEED["ArchiveList seam"]
  CFG[(Instance config store)] --> HOOK["useThemeComponents"] --> CARD["PostCard seam"]
  REG["Theme registry"] --> HOOK["useThemeComponents"]
  DEF["Default components"] --> HOOK["useThemeComponents"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. React context provider for resolved theme seams
  • ➕ Resolves seams once at a high level, avoiding repeated hook calls across components
  • ➕ Makes it explicit which subtree is theme-aware (good for future non-blog pages)
  • ➖ Adds provider wiring and potential SSR/hydration considerations if introduced later
  • ➖ Less direct than importing a hook at call sites; slightly more indirection for readers
2. Code-split theme components per template (dynamic import)
  • ➕ Avoids shipping layout-level theme code to all users when premium/advanced themes arrive
  • ➕ Keeps initial bundle smaller as theme library grows
  • ➖ Adds async loading states and error handling at seam boundaries
  • ➖ More complex to integrate with live config preview without flicker

Recommendation: The current manifest + reactive hook approach is the right baseline: it keeps rendering unchanged for existing CSS-only templates while establishing clear seam boundaries. Consider introducing a ThemeProvider (context) only if seam resolution becomes widely used beyond blog routes, and defer dynamic imports until there are real layout-level themes that materially affect bundle size.

Files changed (8) +215 / -26

Enhancement (6) +175 / -4
blog-posts-list.tsxResolve PostCard via theme seam in the archive list +5/-2

Resolve PostCard via theme seam in the archive list

• Replaces the direct BlogPostItem render with a PostCard component obtained from useThemeComponents. This keeps feed behavior (paging, errors) owned by the list while allowing themes to override per-entry card structure.

apps/self-hosted/src/features/blog/components/blog-posts-list.tsx

default-shell.tsxAdd DefaultShell implementing the shared page frame +33/-0

Add DefaultShell implementing the shared page frame

• Introduces the pre-existing blog frame as DefaultShell. It resolves Navigation and Sidebar via useThemeComponents so themes can override those seams without replacing the entire Shell.

apps/self-hosted/src/features/blog/layout/default-shell.tsx

route.tsxRender the blog feed through the ArchiveList seam +3/-2

Render the blog feed through the ArchiveList seam

• Replaces the direct BlogPostsList usage with an ArchiveList component from useThemeComponents. Enables future themes to own feed container structure while keeping the route topology unchanged.

apps/self-hosted/src/routes/blog/route.tsx

manifest.tsDefine ThemeManifest and seam component contracts +50/-0

Define ThemeManifest and seam component contracts

• Adds types describing a theme as a manifest with id, tier, and optional component overrides across named seams. Documents invariants like global route topology and the required theme token contract.

apps/self-hosted/src/themes/manifest.ts

registry.tsAdd manifest registry with defaulting for unknown template ids +42/-0

Add manifest registry with defaulting for unknown template ids

• Creates a total manifest map over the STYLE_TEMPLATES roster (currently all CSS-only, free-tier). Provides getThemeManifest() with fallback to DEFAULT_STYLE_TEMPLATE, mirroring DOM attribute defaulting behavior.

apps/self-hosted/src/themes/registry.ts

use-theme-components.tsIntroduce useThemeComponents with default seam fallbacks +42/-0

Introduce useThemeComponents with default seam fallbacks

• Adds DEFAULT_THEME_COMPONENTS representing the pre-manifest component tree. Implements a reactive hook subscribing to general.styleTemplate and merging defaults with manifest overrides to support live config preview restructuring.

apps/self-hosted/src/themes/use-theme-components.ts

Refactor (1) +9 / -22
blog-layout.tsxTurn BlogLayout into the Shell seam resolver +9/-22

Turn BlogLayout into the Shell seam resolver

• Removes the hardcoded page frame and delegates to the Shell component from useThemeComponents. Establishes Shell as the top-level layout seam while preserving current rendering via DefaultShell fallback.

apps/self-hosted/src/features/blog/layout/blog-layout.tsx

Tests (1) +31 / -0
registry.test.tsPin registry totality and no-op migration behavior +31/-0

Pin registry totality and no-op migration behavior

• Adds Vitest coverage ensuring every roster template has a manifest, existing templates carry no component overrides, and unknown/absent ids resolve to the default template manifest.

apps/self-hosted/src/themes/registry.test.ts

@qodo-code-review

qodo-code-review Bot commented Aug 11, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (1)

Grey Divider


Remediation recommended

1. useThemeComponents lacks tests ✓ Resolved 📘 Rule violation ▣ Testability
Description
New functional logic was added in useThemeComponents() without a corresponding new/updated test
that exercises its behavior. This reduces confidence in the new theme-component resolution behavior
(especially around config-driven switching).
Code

apps/self-hosted/src/themes/use-theme-components.ts[R31-41]

+export function useThemeComponents(): ThemeComponents {
+  const styleTemplate = InstanceConfigManager.useConfig(
+    ({ configuration }) => configuration.general.styleTemplate,
+  );
+  return useMemo(
+    () => ({
+      ...DEFAULT_THEME_COMPONENTS,
+      ...getThemeManifest(styleTemplate).components,
+    }),
+    [styleTemplate],
+  );
Relevance

●●● Strong

Team commonly accepts adding/strengthening tests to lock in new behavior and prevent regressions.

PR-#1284

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2667972 requires tests for new functional code paths. The PR adds the
useThemeComponents() hook implementation, but the only added test file in this change set targets
the registry behavior and does not cover the hook.

Rule 2667972: Require tests for all new functional code paths
apps/self-hosted/src/themes/use-theme-components.ts[31-41]
apps/self-hosted/src/themes/registry.test.ts[12-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useThemeComponents()` is newly introduced functional logic, but there is no added/updated test that exercises its behavior.

## Issue Context
The hook resolves theme components based on `configuration.general.styleTemplate` via `InstanceConfigManager.useConfig()` and merges manifest overrides. This is a new behavior path that should be covered by automated tests per the compliance checklist.

## Fix Focus Areas
- apps/self-hosted/src/themes/use-theme-components.ts[31-41]
- apps/self-hosted/src/themes/registry.test.ts[12-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Shell bundles feed code 🐞 Bug ➹ Performance
Description
useThemeComponents hardwires the default ArchiveList to BlogPostsList, so importing the hook
pulls BlogPostsList (and its React Query/SDK dependencies) into the module graph. Because
BlogLayout/DefaultShell call useThemeComponents, routes that only render the shell (e.g. the
post page) now lose code-splitting for the archive/feed seam and pay extra JS load cost.
Code

apps/self-hosted/src/themes/use-theme-components.ts[R19-22]

+  Navigation: BlogNavigation,
+  Sidebar: BlogSidebar,
+  ArchiveList: BlogPostsList,
+  PostCard: BlogPostItem,
Relevance

●●● Strong

Team has accepted changes to preserve code-splitting via dynamic imports and avoiding static
dependency chains.

PR-#632

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The performance regression is a direct consequence of the new static dependency chain: BlogLayout
uses useThemeComponents, which defines ArchiveList defaults by importing BlogPostsList. Since
BlogPostPage renders BlogLayout, the post page now necessarily pulls in the archive/feed module
as part of the reachable import graph, even if it never renders the archive list.

apps/self-hosted/src/themes/use-theme-components.ts[1-23]
apps/self-hosted/src/features/blog/layout/blog-layout.tsx[1-13]
apps/self-hosted/src/features/blog/components/blog-post-page.tsx[8-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`useThemeComponents` is used by the shared page shell (`BlogLayout`/`DefaultShell`) but it statically imports and exports defaults for *all* seams, including the archive/feed seam (`BlogPostsList`). This makes non-feed routes that render the shell pull the archive/feed code into their bundle, reducing code-splitting and increasing JS payload.

### Issue Context
This PR introduces theme seam resolution (Shell/Navigation/Sidebar/ArchiveList/PostCard). The current implementation puts all defaults into one module (`use-theme-components.ts`), so even pages that never render `ArchiveList` still import it via the shell.

### Fix Focus Areas
- apps/self-hosted/src/themes/use-theme-components.ts[1-42]
- apps/self-hosted/src/features/blog/layout/blog-layout.tsx[1-13]
- apps/self-hosted/src/features/blog/layout/default-shell.tsx[1-33]
- apps/self-hosted/src/routes/blog/route.tsx[45-55]

### Suggested approach
1. Split the resolver into at least two entry points so the shell doesn’t depend on the feed:
  - `useThemeShellComponents()` -> `{ Shell, Navigation, Sidebar }`
  - `useThemeFeedComponents()` -> `{ ArchiveList, PostCard }`
2. Keep the manifest shape the same, but provide defaults per-entry-point (e.g., `DEFAULT_SHELL_COMPONENTS` and `DEFAULT_FEED_COMPONENTS`) so importing the shell hook doesn’t import `BlogPostsList`.
3. Update call sites:
  - `BlogLayout`/`DefaultShell` use the shell hook.
  - `/blog` route and `BlogPostsList` use the feed hook.

This preserves the architecture while restoring code-splitting between shell and archive/feed seams.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. Registry test misplaced 📜 Skill insight ⌂ Architecture
Description
The new test file is co-located under src/themes/ and uses a .test.ts name, but the checklist
requires tests to live under src/specs/ and use the .spec.ts(x) naming convention. This can
break test discovery/consistency and violates the enforced test layout rules.
Code

apps/self-hosted/src/themes/registry.test.ts[R1-3]

+import { describe, expect, it } from 'vitest';
+import { STYLE_TEMPLATES } from '../../hosting/api/src/style-templates';
+import { allThemeManifests, getThemeManifest } from './registry';
Relevance

● Weak

Repo previously rejected moving/renaming co-located .test.ts files to src/specs/.spec.ts.

PR-#1437

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance IDs 2668281 and 2668286 require tests to be placed under src/specs/ and named with
.spec.ts(x). The added file apps/self-hosted/src/themes/registry.test.ts violates both by
location and filename.

apps/self-hosted/src/themes/registry.test.ts[1-31]
Skill: add-test

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new test file was added at `apps/self-hosted/src/themes/registry.test.ts`, but compliance requires tests to be placed under `src/specs/` and named using the `.spec.ts` / `.spec.tsx` convention.

## Issue Context
This PR introduces a new Vitest test for the theme registry. To comply with the repository test organization rules, the file should be moved out of `src/themes/` and renamed.

## Fix Focus Areas
- apps/self-hosted/src/themes/registry.test.ts[1-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context
✅ Compliance rules (platform): 75 rules
✅ Skills: 6 invoked
  add-feature
  add-query
  add-sdk-mutation
  add-test
  code-review
  debug
✅ Web pages:
  +2 more
Review mode: ⚖️ Balanced: This is a behavior-changing theme-resolution architecture spanning multiple rendering paths and reactive configuration state, so it warrants a complete single-pass review; its logic is substantial but not dense or independent enough to justify scarce extended review.

Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread apps/self-hosted/src/themes/use-theme-components.ts Outdated
Comment thread apps/self-hosted/src/themes/use-theme-components.ts
feruzm added 2 commits August 11, 2026 22:30
Review follow-up: extract the pure half of useThemeComponents so the
resolution is testable without rendering, and pin per-seam IDENTITY for
every roster template: the no-op migration means the very same
component functions render, so nothing remounts.
The identity test imports the real default components, whose chain
touches @ecency/ui; vitest had no resolution for it. Mirror the rsbuild
alias to the committed dist, exactly what the shipped bundle uses.
@feruzm
feruzm merged commit 6b20115 into develop Aug 12, 2026
12 checks passed
@feruzm
feruzm deleted the feature/self-hosted-theme-manifests branch August 12, 2026 06:07
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.

Self-hosted: theme manifests so a template can own its layout

1 participant