Search/Build a Compatibility Layer That Copies Another Web Framework's Behavior

Build a Compatibility Layer That Copies Another Web Framework's Behavior

Use when reimplementing another web framework's behavior across several runtimes. The AI ports the copied framework's tests first, keeps request order and runtime boundaries, normalizes file paths, and merges only via pull requests that pass every check.

To use this AI rule
## Use the Copied Framework's Tests and Code as the Spec Treat compatibility as an evidence problem. The target framework's tests and implementation define expected behavior more reliably than intuition. For every feature or bug fix: 1. Search the upstream unit and end-to-end suites broadly, including related names, errors, invalid inputs, and edge cases. 2. Read the corresponding implementation to understand ordering, defaults, and failure behavior. 3. Search issues or prior design discussions when the code does not explain whether a behavior is intentional. 4. Port relevant cases before implementing the change, and link each port to its upstream test. 5. Document the evidence in the change description. Use a local shallow clone for fast source and test searches. Use remote code search only when the clone is unavailable or the question requires current discussion. If the compatibility layer should intentionally differ for security, deployment, or platform reasons, record that decision explicitly. Never let an accidental divergence masquerade as a feature. ## Write a Failing Test First and Fix Every Runtime Entry Point Together Write a failing focused test first, then implement the smallest change that satisfies the verified contract. - Run the test file for the subsystem you changed, plus directly coupled tests. - Add fixture inputs for integration behavior; keep examples for user-facing demonstrations. - Use test-name filters while iterating. - Let CI run the full suite unless the change affects shared routing, build infrastructure, virtual modules, or common test helpers. - When shared infrastructure changes, expand local validation only to the affected cross-cutting surfaces. Map validation to ownership: shims need shim tests, routing needs route-order tests, caching needs cache tests, and build or deployment changes need build tests. Request handling often exists in separate development, production, server-rendering, and edge entry points. Check every implementation that owns the same behavior and fix parity gaps in the same change rather than leaving known duplicates behind. ## Pass Request Data Across Runtime Boundaries and Keep Generated Code Thin Separate build or rendering environments may have independent module graphs and independent module state. Never assume that setting request state in one environment changes another. - Pass pathname, parameters, headers, cookies, and other request context explicitly across environment boundaries. - Initialize context immediately before rendering and clean it up afterward. - Keep request-scoped state and small public facades separate from heavy implementations. - Verify both streaming consumption and cleanup timing. Generated entry modules should contain route-specific imports, manifests, and thin wiring only. Move request orchestration, caching policy, method dispatch, redirects, error handling, header merging, and other testable behavior into normal typed modules. Unit-test extracted helpers directly. Generated-code tests should assert delegation and wiring instead of matching large implementation strings. Maintain a clear ownership boundary between the underlying bundler plugin and the compatibility layer. The plugin may own transforms, serialization, multi-environment builds, CSS handling, and hydration bootstrapping, while the compatibility layer owns routing, request lifecycle, layout composition, navigation, caching, and API shims. ## Normalize File Paths and Build with the Full Multi-Environment Pipeline Filesystem paths used as module IDs or generated imports need one canonical separator policy across operating systems. - In source code, use a path abstraction that preserves native semantics while returning canonical identifiers. - Normalize only external-origin values such as process working directories, resolved modules, real paths, glob output, and bundler-reported IDs. - Do not repeatedly normalize values already produced by the canonical path layer. - Keep URL backslash sanitization separate from filesystem path handling. - Document and lint any genuine native-path exception. Tests should construct inputs with native path APIs so Windows supplies real backslashes. Canonicalize only expected outputs. Cross-platform tests built entirely from forward slashes cannot detect a Windows normalization regression. Production builds must use the framework plugin's complete multi-environment builder pipeline. A generic single-environment build call can silently skip required server, renderer, and client phases. When the toolchain changes major versions, prefer its current transformer, dependency optimizer, and bundler option families. Treat legacy option names as migration targets. Virtual modules also need explicit handling for root-prefixed IDs, already-resolved null prefixes, and absolute imports when no physical module location exists. ## Keep Optional Features Off Common Requests and Define Cache Behavior Keep feature-specific runtimes off the common request path. - Gate optional behavior using cheap request or route metadata before dynamically importing it. - Use type-only imports when runtime code is unnecessary. - Filter bundler hooks before JavaScript handlers run, excluding dependencies, generated runtimes, prebundles, and impossible syntax targets. - Keep lightweight state and registration modules separate from cache, navigation, or rendering implementations. - Reuse computed cache keys and request derivations. - Hoist immutable expressions and tables. - Move deterministic parsing and asset selection to build time when possible. Do not add caching until ownership, invalidation, and runtime parity are explicit. A smaller module graph is not a win if it causes stale code, different runtime conditions, or broken platform bundles. Keep storage handlers simple and put incremental regeneration policy above them. Define stale-while-revalidate behavior, single-flight regeneration, revalidation metadata, and tag invalidation separately. Time-expired entries may be returned stale while regeneration runs; explicitly invalidated entries should be removed. Make backend selection declarative, retain a safe default, and register optional adapters before their first use. ## Keep the Copied Framework's Request Processing Order Model the target framework's request lifecycle as an ordered contract: 1. Static configuration headers evaluate against the original request. 2. Static redirects evaluate against the original request. 3. Middleware runs and may rewrite, redirect, or modify headers. 4. Pre-filesystem rewrites evaluate against post-middleware state. 5. Static and filesystem routes resolve. 6. Post-filesystem rewrites run. 7. Dynamic routes resolve. 8. Fallback rewrites run. Do not move a stage merely because the final response looks equivalent. Conditional matching can change when middleware mutates the request. Record known parity gaps instead of hiding them. Preserve intentional distinctions such as which static assets bypass middleware and which public files pass through it. For evolving APIs, support old and new access patterns only when the compatibility contract requires both. A promise augmented with resolved fields, for example, can support both awaited and direct parameter access. Third-party libraries that use public API imports may work through aliases or extension normalization. Libraries that depend on build plugins or internal APIs require explicit integration; installing and importing them is not sufficient. ## Keep the Lockfile Locked and Merge Only Through Pull Requests with Passing Checks Never bypass a frozen lockfile during an unattended install. If dependency resolution truly requires changing the lockfile, stop and ask the user to perform or authorize that operation. Exact versions and integrity hashes are a supply-chain boundary. Never push directly to the protected default branch, bypass branch protection, or force an administrative merge. Use this sequence: 1. Create a focused feature branch. 2. Make the smallest coherent change and run targeted checks. 3. Commit with the repository's required message convention. 4. Push the branch and open a pull request. 5. Wait for every required format, type, unit, and end-to-end check to pass. 6. Fix failures instead of bypassing them. 7. Merge through the approved method and remove the branch. Let release automation derive changelog metadata from conventional commits. Use documented override mechanisms only for deliberate retroactive reclassification; do not hand-edit generated release artifacts. For large refactors, use small stacked changes that each extract one cohesive seam, add focused helper tests, and update delegation assertions. CI for untrusted contributions must run without secrets and with read-only permissions. Secret-bearing preview workflows must skip forks unless an authorized maintainer explicitly triggers them through an identity-gated command. Every deployed example needs build-matrix registration and a smoke test that verifies the expected route, status, and content. Release or deployment is complete only after required checks and post-deploy evidence pass.