Skip to content

dogfood: guide AI agents to keep pages/layouts out of the browser network #621

@vivek7405

Description

@vivek7405

Problem

Pages and layouts never hydrate, so the framework can drop their modules from the browser entirely: a page/layout whose closure does no client work is INERT (#179), and one whose only client work is shipping components is IMPORT-ONLY (#605, the boot emits the components directly in its place). When that works, page.ts / layout.ts never appear in the network tab and the app ships only its interactive leaves.

But it is easy for an AI agent (or a human) to silently DEFEAT this without realising it, because nothing surfaces WHY a page started shipping whole. Any of these, introduced anywhere in a page/layout's transitive closure, pins the whole module to the browser:

The cost is invisible in npm test (it is an elision verdict, not a behaviour change), and there is no feedback telling the author "this route ships because of module X." So apps accrete network weight no one asked for, and the "pages are just carriers" model erodes. The blog is the proof: it took manual network-tab inspection to even notice.

Design / approach

Two parts, guidance first, optional diagnostic second.

  1. Encode the pattern for every agent surface webjs ships (the AGENTS.md guardrails-for-all-agents contract): teach "keep pages and layouts as pure carriers." Concretely, the rule of thumb to document:

    • A page/layout should reach the browser ONLY through the components it renders. Do not import a client-global-touching or side-effecting NON-component utility into a page/layout (or into a component chain a page reaches) when it can be avoided; put client-only behaviour inside a component.
    • Server-only work belongs in .server.{js,ts} (it never reaches the client closure at all).
    • A utility that mixes a pure helper with client-global code (the cn.ts shape) should split the client-global part out, so the pure helper does not drag the client reference into every importer's closure.
    • How to self-check: look at the network tab (or the boot script) and confirm page.ts / layout.ts are absent; if present, something in the closure does client work and is not a component.
  2. Optional diagnostic so the feedback is not manual (stretch, scope to taste): a webjs check advisory or a webjs doctor / MCP "explain why this route ships" mode that runs the existing analyzeElision and, for a route that ships whole, names the FIRST client-effecting non-component in its closure (the blocker), e.g. "app/page.ts ships whole: lib/utils/cn.ts references a client global and is not a component." This turns an invisible regression into an actionable line. The analyser already computes everything needed (inertRouteModules / importOnlyRouteModules and the clientGlobalOrBareFiles / reactiveFiles sets in packages/server/src/component-elision.js); the diagnostic is a reporting layer over it.

Implementation notes (for the implementing agent)

  • This is a FOLLOW-UP to dogfood: ship a class-name utility (cn) in @webjsdev/core #619 (the cn.ts declutter) and dogfood: auto-enable client router via webjs-core-browser.js, drop layout imports #620 (automatic client router). Land those first; this issue generalises the lesson so end users do not repeat it.
  • Where to encode the guidance (the scaffold ships the same rule in each agent's format, root AGENTS.md "guardrails for all agents"):
  • Diagnostic (if included):
    • packages/server/src/check.js for a webjs check advisory, OR the webjs doctor path, OR the @webjsdev/mcp introspection tools (packages/mcp/src/*). Reuse analyzeElision rather than re-deriving the closure.
    • It must be ADVISORY, not a correctness error: a page legitimately may need to ship (the analyser biases toward shipping, server AGENTS invariant 7), so this is a "you may not have intended this" hint, not a failure. Do not make it a hard webjs check rule.
  • Landmines:
    • Do NOT turn this into a hard gate. Elision is a conservative optimisation; a forced "must be import-only" rule would break legitimate pages and fight the analyser's safe-by-default posture.
    • The guidance must not contradict the existing "Client navigation: automatic" model once dogfood: auto-enable client router via webjs-core-browser.js, drop layout imports #620 lands (a layout no longer needs the client-router import).
    • Respect AGENTS.md invariant 11 (no banned prose glyphs) in every doc surface.
  • Invariants: elision stays output-identical (server AGENTS invariant 7); guardrails-for-all-agents parity (root AGENTS.md); doc-sync across every surface (run the webjs-doc-sync skill).

Acceptance criteria

  • The carrier-hygiene pattern (keep pages/layouts free of client-effecting non-component imports) is documented in root AGENTS.md, agent-docs, and CONVENTIONS.md
  • The same rule is propagated to every per-agent config the scaffold ships (.cursorrules, .agents/rules/workflow.md, .github/copilot-instructions.md, scaffolded AGENTS.md + CONVENTIONS.md)
  • If the diagnostic is included: a route that ships whole gets an advisory naming the first client-effecting non-component blocker, built over analyzeElision, advisory-only (no hard failure), with a test
  • webjs-doc-sync run so no doc surface repeats the wrong guidance
  • Depends on dogfood: ship a class-name utility (cn) in @webjsdev/core #619 + dogfood: auto-enable client router via webjs-core-browser.js, drop layout imports #620 landing first

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions