Repository source: docs/plans/mitrapclub_structural_identity/mitrapclub_structural_identity_step4_implementation_summary.md
Stage 1 - Hide /tools/ from mitrapclub's nav
Changes:
src/ForumRewrite/PresentationSlotRegistry.php: added atoolsNavslot (fallback: 'visible',choices: ['visible', 'hidden']).src/ForumRewrite/SiteProfileRegistry.php: added'toolsNav' => 'hidden'tomitrapclub'spresentationSlotsonly (the other three profiles need no change —PresentationSlotRegistry::resolve()already falls back per-slot when a profile's array omits a key).src/ForumRewrite/View/TemplateRenderer.php(navItems()): resolvestoolsNav; when hidden and not on the qdb nav, filters theToolsitem out of the generic nav array./tools/route itself untouched.
Verification:
./v3 test— full suite: 817 run, 817 passed, 0 failed.Rendered
/for all four profiles viaFrontControllerdirectly (fixture repo + a freshly built read-model sqlite):mitrapclub's nav omitsTools(Board/About/Users/Accountremain);zenmemes/chouseunchanged (Toolspresent);qdbunchanged (its own custom nav never had aToolsitem).Rendered
/tools/directly onmitrapclub: returns the page normally, confirming the route stays reachable by direct URL.
Notes:
Matches the existing precedent of dropping Account/Invite from qdb's nav while keeping the routes reachable directly.
Stage 2 - Per-profile favicon/icon override mechanism (no-op by construction)
Changes:
src/ForumRewrite/PresentationSlotRegistry.php: added afaviconslot (fallback: 'default',choices: ['default', 'mitrapclub']).New
src/ForumRewrite/View/FaviconRegistry.php:resolve(array $profile): stringmaps the resolved slot value to a stable, unfingerprinted asset path ('default'→/favicon.ico,'mitrapclub'→/assets/favicon-mitrapclub.ico); single source of truth for both call sites below.src/ForumRewrite/View/TemplateRenderer.php: bothrenderLayout()andrenderStandalonePage()now pass'faviconPath' => FaviconRegistry::resolve($profile)to their templates;renderStandalonePage()gained aSiteProfileRegistry::active()lookup to support this.templates/layout.phpandtemplates/standalone_layout.php:<link rel="icon">now reads$e($faviconPath)instead of the hardcoded/favicon.ico.src/ForumRewrite/Host/BrowserRuntimeAssetRenderer.php::manifest():icons[0].srcnow readsFaviconRegistry::resolve($profile)instead of the hardcoded literal.No profile opts into the
faviconslot yet (mitrapclub'spresentationSlotsis untouched in this stage), so every profile still resolves to thedefaultchoice.
Verification:
./v3 test— full suite: 817 run, 817 passed, 0 failed.Rendered
/and/manifest.webmanifestfor all four profiles viaFrontControllerdirectly: every profile's<link rel="icon">and manifesticons[0].srcstill read/favicon.ico— confirmed no-op for all four, includingmitrapclub, exactly as planned for this stage.
Notes:
The mechanism is real and wired end-to-end but deliberately unexercised until Stage 3, so this stage's correctness is proven by the absence of change, not the presence of one.
Kept the favicon path unfingerprinted (not routed through
TemplateRenderer::assetPath()) per Step 3's Key Risks, matching today's existing convention for this one asset.
Stage 3 - Exercise the favicon override for mitrapclub
Changes:
New binary asset
public/assets/favicon-mitrapclub.ico: an exact byte-copy ofpublic/favicon.ico(cmpconfirms identical bytes) — no new artwork ships, per Step 2's scope.src/ForumRewrite/SiteProfileRegistry.php: added'favicon' => 'mitrapclub'tomitrapclub'spresentationSlotsonly.src/ForumRewrite/Host/StaticArtifactBuilder.php(copyOfflineRuntimeFiles()): the favicon copy loop now also copies the active profile'sFaviconRegistry::resolve()path (deduplicated against the two existing hardcoded defaults), so a static export references a file that was actually copied into the artifact.
Verification:
./v3 test— full suite: 817 run, 817 passed, 0 failed.Rendered
/and/manifest.webmanifestfor all four profiles viaFrontControllerdirectly:zenmemes/chouse/qdbunchanged (/favicon.ico);mitrapclubnow resolves both to/assets/favicon-mitrapclub.ico.cmp public/favicon.ico public/assets/favicon-mitrapclub.ico— identical, confirming zero visual regression.Built a static export of
mitrapclubviaStaticArtifactBuilderdirectly (fixture repo + the same read-model sqlite): confirmedassets/favicon-mitrapclub.icois present in the exported artifact root. Built a static export ofzenmemesthe same way: confirmed nofavicon-mitrapclub.icoappears anywhere under its artifact root (unaffected).
Notes:
Closes the Step 3 high-risk item (static-export copy loop previously only knew about the two default files) with an explicit before/after check rather than an assumption.
Feature complete per the Step 3 Completion Contract:
mitrapclub's nav omitsTools(reachable by direct URL); theme breadth is unchanged by deliberate decision (no code needed); the favicon/icon override mechanism is real, live-wired, and exercised formitrapclubwith zero new art;zenmemes/chouse/qdbare provably unchanged throughout.