fix(modules): guard the portal's nav icon, resolve MODULES_DIR absolutely
All checks were successful
PR Checks / bot-install (pull_request) Successful in 18s
PR Checks / client-build (pull_request) Successful in 26s
PR Checks / server-tests (pull_request) Successful in 29s

Both found by the §7.7 browser smoke, running the slice-3 pair together, and
neither is visible to any test in either repo.

`PlayerPortalLayout` rendered `<n.icon />` unguarded while `AdminLayout` guarded
its equivalent. `icon` is optional in the nav contract, and every core row in
that sidebar has always had one — so the difference cost nothing until a module
registered a row without, and then it was not a missing glyph, it was React
error #130 and a blank player portal. Guarded now, like its neighbour.

`MODULES_DIR` is resolved absolute. `resolveClient` checks containment by
comparing an absolute `path.resolve(dir, entry)` against the module directory,
so a RELATIVE `MODULES_DIR` — which is what §7.7's own recipe produces when run
from `server/` — failed every module with "client.entry escapes the module
directory". A perfectly-placed entry, and a message pointing at the module.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-11 18:54:31 -05:00
parent f7d27f7a06
commit 5bdb6a7e10
2 changed files with 15 additions and 2 deletions

View File

@@ -137,7 +137,13 @@ export default function PlayerPortalLayout() {
borderLeft: `2px solid ${isActive ? 'var(--accent)' : 'transparent'}`,
})}
>
<n.icon />
{/* Guarded, like AdminLayout's. `icon` is optional in the nav
contract (§3.3) and every CORE row here has always had one, so
an unguarded `<n.icon />` was fine right up until a module
registered a row without — and then it was not a missing glyph,
it was React error #130 and a blank portal. Found by the §7.7
browser smoke; no DOM-less test can see it. */}
{n.icon && <n.icon />}
<span>{n.label}</span>
</NavLink>
))}