Skip to content

Web: formalize portable theme contract checks#63

Merged
PrzemyslawKlys merged 1 commit intov2-speedgonzalesfrom
improve/theme-contract-portability-20260206
Feb 6, 2026
Merged

Web: formalize portable theme contract checks#63
PrzemyslawKlys merged 1 commit intov2-speedgonzalesfrom
improve/theme-contract-portability-20260206

Conversation

@PrzemyslawKlys
Copy link
Member

Summary: add theme contract portability checks in verifier, add token partial guard, and align theme docs with actual engine precedence. Validation: dotnet test PowerForge.Tests/PowerForge.Tests.csproj --filter "WebSiteVerifierTests|ThemeLoaderTests".

Copilot AI review requested due to automatic review settings February 6, 2026 11:13
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1bb763d268

ℹ️ 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".

Comment on lines +1109 to +1112
if (Path.IsPathRooted(path))
return false;
if (path.StartsWith("/", StringComparison.Ordinal) || path.StartsWith("\\", StringComparison.Ordinal))
return false;

Choose a reason for hiding this comment

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

P2 Badge Reject Windows-rooted paths in portable-path validator

IsPortableRelativePath currently relies on Path.IsPathRooted plus leading slash checks, which is OS-dependent; when verification runs on Linux/macOS, values like C:\layouts\home.html (and other drive-letter paths) are treated as non-rooted and pass this check. That means ValidateThemeMappedPaths/ValidateThemeAssetPortability can miss exactly the non-portable paths this commit is meant to catch, so portability warnings become inconsistent across CI environments. Add an explicit drive-letter (and ideally URI-scheme) guard before returning true.

Useful? React with 👍 / 👎.

@claude
Copy link

claude bot commented Feb 6, 2026

PR Review: Web: formalize portable theme contract checks

Summary

This PR adds comprehensive validation for theme portability by introducing contract checks in the WebSiteVerifier. The changes enforce that theme manifests use relative paths and follow portability best practices. The implementation includes thorough test coverage and updated documentation.

✅ Strengths

1. Excellent Code Quality

  • Clean, well-structured validation methods following existing patterns in WebSiteVerifier
  • Consistent naming conventions and code style
  • Proper separation of concerns with dedicated helper methods
  • Good use of null-conditional operators and defensive programming

2. Comprehensive Validation Coverage

The new ValidateThemeContract method covers all critical portability concerns:

  • Engine specification validation (PowerForge.Web/Services/WebSiteVerifier.cs:332-397)
  • Path portability for layoutsPath, partialsPath, assetsPath
  • Mapped paths validation for layouts and partials dictionaries
  • Asset bundle path validation (catching hard-coded /themes/... paths)
  • DefaultLayout resolution verification
  • Token partial guard (warns if tokens defined without theme-tokens partial)

3. Well-Designed Helper Methods

  • IsPortableRelativePath() (PowerForge.Web/Services/WebSiteVerifier.cs:1105-1117) correctly validates:
    • Rejects rooted paths
    • Rejects paths starting with / or \
    • Rejects parent directory traversal (..)
    • Cross-platform compatible (handles both / and \)
  • ValidateThemeBundleAssetPath() provides specific, actionable warnings
  • Good reuse of existing IsExternalPath() helper

4. Strong Test Coverage

Two comprehensive test cases:

  • Verify_WarnsWhenThemeManifestUsesNonPortablePaths - validates non-portable path detection
  • Verify_WarnsWhenThemeDefinesTokensWithoutThemeTokensPartial - validates token partial guard
  • Tests properly create temp directories and clean up
  • Tests use realistic theme.json structures

5. Documentation Alignment

The updated Docs/PowerForge.Web.Theme.md:

  • Explicitly states the contract status
  • Documents portability requirements clearly
  • Corrects engine precedence order (site override > theme manifest > default)
  • Provides concrete examples of what NOT to do

🔍 Potential Issues & Recommendations

1. Missing Test for DefaultLayout Resolution (Minor)

The code validates that defaultLayout resolves to an existing file (PowerForge.Web/Services/WebSiteVerifier.cs:352-358), but there's no test covering this scenario. Consider adding a test case.

2. External URL Validation Logic (Minor - Clarification Needed)

In ValidateThemeBundleAssetPath() (PowerForge.Web/Services/WebSiteVerifier.cs:1090-1103), external URLs in theme bundles generate a warning suggesting to "move external URLs to site-level overrides when possible."

Question: Should external URLs be completely disallowed in theme bundles for true portability? The current implementation allows them with a warning, but the documentation emphasizes "portable relative paths."

3. Preload Validation Edge Case (Minor)

In ValidateThemeAssetPortability() (PowerForge.Web/Services/WebSiteVerifier.cs:1062-1077), the preload validation logic allows root-relative paths (/) which may not be portable across different deployment contexts. Consider whether root-relative paths should also warn for portability.

Also, there's a second check for /themes/ hard-coding that could be combined with the first check for clarity.

4. Engine Value Case Sensitivity (Minor)

The engine validation (PowerForge.Web/Services/WebSiteVerifier.cs:337-340) uses case-insensitive comparison, which is good. Consider whether the warning message should clarify that values are case-insensitive.

5. Error Recovery in ValidateThemeContract (Minor)

The try-catch around loader.Load() (PowerForge.Web/Services/WebSiteVerifier.cs:342-348) silently swallows all exceptions. Consider adding a warning when theme manifest parsing fails to help debug configuration issues.

6. Test Cleanup Race Condition (Very Minor)

Both test methods use Directory.Delete(root, true) in the finally block. On Windows, this can occasionally fail due to file handles not being released. Consider wrapping in a try-catch for more robust test cleanup.

📊 Performance Considerations

  • ✅ No performance concerns identified
  • File system operations are minimal (only loads manifest when needed)
  • Validation runs once during site verification (not on hot path)
  • Efficient use of early returns to avoid unnecessary work

🔒 Security Considerations

  • ✅ Path traversal protection via IsPortableRelativePath() correctly rejects .. segments
  • ✅ No injection vulnerabilities identified
  • ✅ Proper validation of external URLs
  • ✅ Good defensive programming throughout

📝 Documentation Quality

  • ✅ Updated Docs/PowerForge.Web.Theme.md clearly states contract requirements
  • ✅ Engine precedence order corrected and documented
  • ✅ Good inline code comments explaining the "why" of validations
  • ✅ Warning messages are clear and actionable

🎯 Best Practices Compliance

  • ✅ Follows existing WebSiteVerifier patterns
  • ✅ Consistent error messaging format
  • ✅ Proper null handling throughout
  • ✅ Good separation between validation logic and helper utilities
  • ✅ No code duplication

Final Verdict

APPROVE

This is a well-implemented feature that adds valuable portability guarantees to the theme system. The code quality is high, test coverage is good, and the documentation improvements are clear. The minor issues identified above are suggestions for potential improvements but don't block approval.

Recommended Actions (Optional):

  1. Consider adding test for defaultLayout resolution warning
  2. Decide whether external URLs should be warnings or errors in theme bundles
  3. Consider improving error messages in exception handling
  4. Consider adding test cleanup error handling

Great work on enforcing portable theme contracts! This will prevent many subtle portability issues in theme development.

@PrzemyslawKlys PrzemyslawKlys merged commit f608f71 into v2-speedgonzales Feb 6, 2026
11 checks passed
@PrzemyslawKlys PrzemyslawKlys deleted the improve/theme-contract-portability-20260206 branch February 6, 2026 11:17
Copy link

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

This PR formalizes theme contract portability by adding validation checks to ensure themes remain portable across different installations and configurations. It validates that theme manifests use relative paths instead of absolute or rooted paths, warns when themes define tokens without providing a theme-tokens partial, and corrects the documentation to accurately reflect the template engine precedence order.

Changes:

  • Added ValidateThemeContract method to verify theme manifest portability requirements
  • Implemented helper validation methods for paths, mapped dictionaries, and asset portability
  • Added tests covering non-portable path detection and missing theme-tokens partial warnings
  • Updated documentation to reflect correct engine precedence order (site → theme → default) and portability requirements

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
PowerForge.Web/Services/WebSiteVerifier.cs Added comprehensive theme contract validation with methods to check path portability, engine configuration, layout resolution, and token partial requirements
PowerForge.Tests/WebSiteVerifierTests.cs Added two test cases validating warnings for non-portable paths and missing theme-tokens partial
Docs/PowerForge.Web.Theme.md Updated contract status section, corrected engine precedence order to match implementation, and added guidance on portable paths and theme asset declarations

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

Comment on lines +1062 to +1077
foreach (var preload in manifest.Assets.Preloads ?? Array.Empty<PreloadSpec>())
{
if (string.IsNullOrWhiteSpace(preload.Href))
continue;
if (!IsPortableRelativePath(preload.Href) &&
!preload.Href.StartsWith("/", StringComparison.Ordinal) &&
!IsExternalPath(preload.Href))
{
warnings.Add($"{label} preload href should be relative, absolute web URL, or root-relative URL: {preload.Href}");
}

if (preload.Href.StartsWith("/themes/", StringComparison.OrdinalIgnoreCase))
{
warnings.Add($"{label} preload href hard-codes '/themes/...'. Prefer relative paths in theme assets for portability: {preload.Href}");
}
}
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.

Copilot uses AI. Check for mistakes.
Comment on lines +1079 to +1087
foreach (var critical in manifest.Assets.CriticalCss ?? Array.Empty<CriticalCssSpec>())
{
if (string.IsNullOrWhiteSpace(critical.Path))
continue;
if (!IsPortableRelativePath(critical.Path))
{
warnings.Add($"{label} criticalCss '{critical.Name}' should be a portable relative path: {critical.Path}");
}
}
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.

Copilot uses AI. Check for mistakes.
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