You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#621 documented the "keep pages/layouts as pure carriers" pattern so a page/layout's module stays out of the browser (import-only #605 / inert #179). But the failure is still INVISIBLE until someone inspects the network tab: when a page/layout ships its own module, nothing tells the author WHY. The cause is always a specific client-effecting NON-component in the route's closure (a util that touches a client global, a module-scope side effect, a bare side-effect import), or the route module's own client work.
A diagnostic that NAMES the blocker turns an invisible regression into an actionable line. This was the optional/stretch half of #621, deferred so the guidance could ship on its own.
Design / approach
An ADVISORY (not a hard failure) that, for each route module the build does NOT drop, reports the first client-effecting member of its closure (or its own flagged signal). Reuse the existing analyzeElision machinery (it already computes inertRouteModules / importOnlyRouteModules and the reactiveFiles / clientRouterFiles / clientGlobalOrBareFiles sets in packages/server/src/component-elision.js); the diagnostic is a reporting layer over the verdict, not new analysis.
Surface options (pick one or more): a webjs doctor check, a webjs check ADVISORY rule (must be advisory, not a hard error: a page legitimately MAY need to ship, the analyser biases toward shipping by design, server AGENTS invariant 7), or a @webjsdev/mcp introspection field. Example output: app/page.ts ships whole: lib/utils/cn.ts references a client global and is not a component (move the client part out, or it stays in the boot).
Implementation notes (for the implementing agent)
Where: packages/server/src/component-elision.js already exposes the verdict via analyzeElision; to name the blocker, expose (or recompute) the first isClientEffecting non-component in a SHIP route module's closure. The webjs doctor path (packages/cli) or webjs check (packages/server/src/check.js) is the host; the MCP (packages/mcp/src/*) could project it too.
MUST be advisory: do NOT make it a hard webjs check correctness rule. A page that genuinely ships (a real module-scope side effect, a client-router import, a non-component the author intends to load) is valid; this is a "you may not have intended this" hint. Respect server AGENTS invariant 7 (elision is conservative, biased to ship).
Reuse the build's verdict so it only fires on modules that genuinely ship (the same posture as no-server-import-in-browser-module).
Tests: a fixture route pinned by a client-effecting non-component reports the blocker by name; a legitimately-shipping route is not falsely alarmed; an import-only / inert route reports nothing. Counterfactual where applicable.
Problem
#621 documented the "keep pages/layouts as pure carriers" pattern so a page/layout's module stays out of the browser (import-only #605 / inert #179). But the failure is still INVISIBLE until someone inspects the network tab: when a page/layout ships its own module, nothing tells the author WHY. The cause is always a specific client-effecting NON-component in the route's closure (a util that touches a client global, a module-scope side effect, a bare side-effect import), or the route module's own client work.
A diagnostic that NAMES the blocker turns an invisible regression into an actionable line. This was the optional/stretch half of #621, deferred so the guidance could ship on its own.
Design / approach
An ADVISORY (not a hard failure) that, for each route module the build does NOT drop, reports the first client-effecting member of its closure (or its own flagged signal). Reuse the existing
analyzeElisionmachinery (it already computesinertRouteModules/importOnlyRouteModulesand thereactiveFiles/clientRouterFiles/clientGlobalOrBareFilessets inpackages/server/src/component-elision.js); the diagnostic is a reporting layer over the verdict, not new analysis.Surface options (pick one or more): a
webjs doctorcheck, awebjs checkADVISORY rule (must be advisory, not a hard error: a page legitimately MAY need to ship, the analyser biases toward shipping by design, server AGENTS invariant 7), or a@webjsdev/mcpintrospection field. Example output:app/page.ts ships whole: lib/utils/cn.ts references a client global and is not a component (move the client part out, or it stays in the boot).Implementation notes (for the implementing agent)
packages/server/src/component-elision.jsalready exposes the verdict viaanalyzeElision; to name the blocker, expose (or recompute) the firstisClientEffectingnon-component in a SHIP route module's closure. Thewebjs doctorpath (packages/cli) orwebjs check(packages/server/src/check.js) is the host; the MCP (packages/mcp/src/*) could project it too.webjs checkcorrectness rule. A page that genuinely ships (a real module-scope side effect, a client-router import, a non-component the author intends to load) is valid; this is a "you may not have intended this" hint. Respect server AGENTS invariant 7 (elision is conservative, biased to ship).no-server-import-in-browser-module).agent-docs/components.md(the carrier-hygiene section dogfood: guide AI agents to keep pages/layouts out of the browser network #621 added) + the relevant CLI doc.Acceptance criteria
webjs check, and never fires on an import-only or inert routeanalyzeElisionverdict (no parallel re-analysis)