* feat(app-router): promote same-layout ancestor persistence
App Router soft commits previously preserved absent non-slot entries through the merge layer and kept root-layout hard-navigation policy split between browser state and planner. That made same-layout ancestor persistence implicit and allowed unknown root identity to behave like proof of reuse.
The planner now returns explicit preserveElementIds for proven same-root transitions, browser state stores layoutIds from wire metadata, and visible commits preserve only planner-approved ancestors while leaving parallel-slot persistence semantics intact.
Adds coverage for layout id metadata, unknown-root fallback, stale element removal, and visible layout persistence.
* fix(app-router): validate layout persistence proofs
App Router payload metadata treated every string in __layoutIds as planner preservation authority. That let a bad producer, stale payload, or future refactor pass a page, slot, or malformed key as proof for layout ancestor reuse.
The violated invariant is that layout persistence proof must be validated before it reaches the commit planner. Parse __layoutIds through the AppElements wire-key codec and accept only layout keys.
Also add dynamic segment browser coverage for /blog/[slug] navigation, proving param-sensitive layout and page content update while the layout client counter persists.
* fix(app-router): scope boundary layout proofs
HTTP access fallback payloads derived layout metadata from the full route definition. Layout-level access fallbacks can render only ancestor layouts, so the old proof could let the planner preserve child layouts that were absent from the response.
Derive boundary layout metadata from the rendered layout module subset and mark the root boundary unknown when no route layout is rendered.
* docs(app-router): document layout id ordering
Document the ancestor-first layoutIds invariant at the planner snapshot boundary so future payload producers do not break same-layout prefix persistence.
* fix: serve metadata routes in dynamic segments (opengraph-image, icon, etc.)
Metadata routes like opengraph-image.tsx inside dynamic segments
(e.g., app/blog/[slug]/opengraph-image.tsx) returned 404 because the
runtime matching used strict equality against the servedUrl which
contains literal bracket notation ([slug]).
Two fixes:
- Generate patternParts for metadata routes with dynamic segments so
the runtime uses matchPattern() instead of strict equality
- Pass extracted params to the metadata function via makeThenableParams()
matching Next.js behavior where dynamic metadata routes receive { params }
* fix: address review — scope patternParts to dynamic routes, fix indentation
- Only generate patternParts for dynamic metadata routes (static files
don't use params)
- Add "keep in sync" comment for segment-to-pattern conversion
- Fix mis-indented try/catch braces in generated static metadata code
* add oxfmt formatter: config, scripts, CI, editor setup, docs
* rebuild lockfile
* fix: add Format to required checks list, remove dead ignore pattern
* run fmt
* add format to agents.md again