web: make theme slots real (portable contract)#73
Conversation
Pull Request Review: Theme Slots ImplementationOverviewThis PR implements theme slot mapping functionality, enabling portable theme contracts where layouts can reference stable slot names (e.g., Code Quality & Best Practices ✅Positives:
Suggestions:
Potential Bugs or Issues 🔍Minor Issues:
Performance Considerations ⚡Overall: Good performance characteristics
Recommendation: The performance is acceptable. The max depth of 8 should handle even complex slot chains efficiently. Security Concerns 🔒No significant security issues identified
Test Coverage 🧪Current State:
Recommendations:Add test cases for:
These tests would ensure the slot resolution behaves correctly in edge cases and prevent regressions. Additional ObservationsPositive Changes:
Documentation Updates:The README changes correctly reflect the new manifest filename. Consider updating any other documentation that references theme structure. SummaryRecommendation: ✅ APPROVE with suggested improvements This is a solid implementation that adds valuable functionality for theme portability. The code is clean, follows existing patterns, and includes proper protections against edge cases. Before merging:
After merging:
Great work on maintaining backward compatibility and following the established architectural patterns! 🎉 Review generated for commit 11d9f3d |
There was a problem hiding this comment.
Pull request overview
This PR makes ThemeManifest.Slots functional during partial resolution so layouts can reference stable “slot” names while themes map those to concrete partials, and updates the scaffolder/sample docs to emit and reference the v2 theme.manifest.json contract.
Changes:
- Implement slot indirection in
ThemeLoader.ResolvePartialPathto allow resolving stable slot names during partial lookup. - Update
WebSiteScaffolderto generatetheme.manifest.json(schemaVersion=2) with default slots and update scaffolded layouts to use slot names. - Update the sample README to reference
theme.manifest.jsonand thetheme-tokenspartial.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| Samples/PowerForge.Web.Sample/README.md | Updates documentation references from legacy theme.json to theme.manifest.json. |
| PowerForge.Web/Services/WebSiteScaffolder.cs | Scaffolds v2 theme manifest with slots and updates generated layouts/partials to use stable slot names. |
| PowerForge.Web/Services/ThemeLoader.cs | Adds slot-based indirection to partial resolution to make ThemeManifest.Slots effective. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (manifest?.Slots is not null && manifest.Slots.Count > 0) | ||
| { | ||
| var current = partialName; | ||
| var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { current }; | ||
| for (var i = 0; i < 8; i++) |
There was a problem hiding this comment.
ResolvePartialPath treats manifest.Slots values as partial names and then later combines them with partialsPath. This breaks the documented/validated contract where slots values are portable relative paths like partials/slots/hero.html (it would resolve to <theme>/partials/partials/slots/hero.html). Update slot handling to support path-style slot targets (relative to themeRoot) before falling back to treating the value as a partial name/alias.
| // Slot indirection allows portable themes: layouts can request a stable "slot" | ||
| // name, and the manifest maps that slot to a concrete partial name/path. | ||
| // Without this, manifest.Slots is effectively unused. | ||
| if (manifest?.Slots is not null && manifest.Slots.Count > 0) | ||
| { | ||
| var current = partialName; | ||
| var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { current }; | ||
| for (var i = 0; i < 8; i++) |
There was a problem hiding this comment.
The new slot indirection behavior in ResolvePartialPath isn’t covered by tests. Add ThemeLoaderTests that verify slot resolution works for (1) slot values that are theme-root-relative paths (as in docs) and (2) alias chains / loop handling so regressions are caught.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 11d9f3d93e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| <div class=""pf-container""> | ||
| <div class=""pf-brand"">Site</div> | ||
| <div class=""pf-container pf-header-inner""> | ||
| <div class=""pf-brand"">{{SITE_NAME}}</div> |
There was a problem hiding this comment.
HTML-encode site name before injecting into header
Using {{SITE_NAME}} in the simple scaffold header renders the site name without escaping, because SimpleTemplateEngine.BuildTokens currently assigns SITE_NAME as raw text. If a site name contains characters like <, >, or & (or malicious HTML), generated pages will emit broken markup or executable HTML in the header. This is a regression introduced by switching the scaffolded header from a static label to SITE_NAME.
Useful? React with 👍 / 👎.
This makes ThemeManifest.Slots actually work during partial resolution, so themes can expose stable slot names (e.g. site-header/site-footer) while mapping to concrete partials.
Also updates the scaffolder to emit theme.manifest.json (schemaVersion=2) with default slots and updates the sample docs.
Build: dotnet build PSPublishModule.sln -c Release
Tests: dotnet test PowerForge.Tests -c Release