Repository navigation
feat(isr): emit manifest.pages — ordered page-route table for route-aware ISR TTL - #272
Conversation
…ifest Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
runFullDeploy now computes `pages = collectPages(pageScan)` and passes it to buildVoidManifest, so the ordered page-route table lands in manifest.pages on the Void deploy path (pages mode only; the 3rd-party framework path is untouched). Adds an integration assertion driving the real scanPages → collectPages → buildVoidManifest chain over the kitchen-sink playground. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ssion
Adds an integration test that executes the real exported runDeploy ->
runDeployInner -> runFullDeploy (Void/SSR path) against a pre-built minimal
Void pages fixture (playground/named-layouts, plus a tiny /isr page with
`export const revalidate = 60`), with only network, prompts and prerender
path-discovery stubbed, and asserts the captured finalize manifest carries
`pages` (including the ISR entry with its exact TTL).
Closes the gap where reverting runFullDeploy's `assembleVoidManifest(pageScan,
...)` back to `buildVoidManifest({ ... })` -- which drops `pages` from real
deploys -- left every test green. Verified RED-on-removal: the test fails
(manifest.pages undefined) when the seam is reverted, and passes when restored.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… mode)
The `pages` route-aware ISR table is a Void pages-mode concept. Class-A
frameworks (@tanstack/react-start, @react-router/dev, vinext) also route
through runFullDeploy, where a pages/ dir would previously get Void's
`pages` table emitted for their routes — violating plan 0077's Global
Constraint ("pages is Void-pages-mode only; leave frameworks alone").
Gate the emission inside the assembleVoidManifest seam on isFrameworkMode
so it stays cheaply unit-testable. revalidate behavior is unchanged
(collectRevalidate still consumes the same pageScan). Adds unit cases
(void emits, framework omits) plus a framework-mode end-to-end variant
that mocks detectFramework to force the Class-A path on a fixture with a
pages/ dir and asserts manifest.pages is absent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_dcfa4c67-1486-4022-b4d8-c395a1453075) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2fff2ff9a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ve void.json routing.revalidate) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_80ca9433-663a-4e76-8a5a-2f6c335e8e70) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9148de4fb2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… cross-repo import) The parity test imported matchSegmentPattern live from the platform repo (../../../../../packages/dispatch/src/pattern.ts). That path only resolves in the void-platform submodule nesting; in the framework's OWN standalone CI it is absent, so tsgo failed with TS2307 and broke test:all. Replace the live import + dynamic cross-check with an explicit golden truth table of dispatch's segment-anchored semantics (single-segment param, named catch-all + '*' crossing '/', multi-'*', literal dot, and the greedy ':name.ext' single-segment case that mirrors dispatch's '/blog/:slug.xml'). Self-contained; keep in sync with matchSegmentPattern if its semantics change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_7f6db999-91ef-46bd-9766-e4568da3e487) |
…er-path 0 opt-out safety) The interim build-time fix (9148de4) baked an OBJECT routing.revalidate by first-matching each page against a single representative probe path. That is unsafe: a page pattern can straddle several object entries — including a per-path `0` opt-out. Example: page /blog/:slug under { '/blog/private': 0, '/blog/*': 3600 } → probe /blog/_ misses the 0 and bakes 3600, over-caching /blog/private despite the explicit opt-out — a wrong-serve, strictly worse than under-caching. Drop object-config baking entirely. resolvePageBuildTtl now bakes only: (1) a per-page `export const revalidate` (0 = authoritative opt-out), else (2) a global-NUMBER routing.revalidate (whole-site), else 0. Removed the probe machinery (matchConfigPattern, pagePatternToProbePath, sentinels) and its parity cross-check test. Accepted limitation (documented in plan 0077): a pages-mode app that configures ISR via an OBJECT routing.revalidate gets no page ISR through the table (matched pages resolve 0). Per-page exports (baked, correct) and global-number config (baked, correct) are the supported levers. This trades one niche non-idiomatic config's ISR for provable correctness — nothing is collapsed, so a per-path 0 opt-out can never be violated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_28ab1625-dc28-488c-985f-9431d56d074a) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c84480fd6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…state pages table + pathRevalidate) Restores the documented per-path ISR feature for Pages-mode apps (codex P1 on void#272) with zero regression and no wrong-serve, superseding both the probe-baking (over-cached a per-path 0 opt-out) and the drop-baking (lost a documented feature) steps. An OBJECT `routing.revalidate` is a per-PATH rule set; a single dynamic page pattern can straddle several entries incl. a per-path 0 opt-out, so it cannot collapse to one build-time TTL. The concrete request path — which only the edge has — makes resolution exact. So: - collectPages emits a 3-STATE pages entry: `revalidate: N` (ISR on), `revalidate: 0` (EXPLICIT per-page opt-out, honored verbatim), or ABSENT (inherit-eligible). Per-page export and global-number config still bake (each maps 1:1 to a TTL); an object config leaves pages inherit-eligible. - The RAW object config ships as a new `manifest.pathRevalidate` field (object-only; a number is already baked into every page). Dispatch/prewarm resolve a matched inherit-eligible page against pathRevalidate using the actual path (platform side). Two invariants: the explicit-0 vs absent distinction keeps a per-page `export const revalidate = 0` winning over config (precedence 2 > 3); pathRevalidate is the RAW config, not the folded collectRevalidate map, so catch-all shadowing stays fixed. Backward compatible (old dispatch reads the 3-state table as before, ignores pathRevalidate). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_5010f7ca-e9a9-4313-8fac-63438ed8d055) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ad6407557b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…lidate contract
Adversarial-review follow-up: the pathRevalidate producer was covered only by
unit tests of assembleVoidManifest (hand-constructed inputs). Adds an object
routing.revalidate ({ '/docs': 120, '/docs/ref': 0 }) to the named-layouts
fixture and asserts, from a REAL runFullDeploy build, that the finalize
manifest ships pathRevalidate verbatim AND that the inherit-eligible /docs and
/docs/ref pages carry NO baked revalidate (while /isr keeps its per-page 60).
This closes the 'synthetic tests pass but a real build might not emit the
contract dispatch consumes' gap. RED-verified (forcing pathRevalidate:undefined
fails it). The fixture change is deploy-time only (routing.revalidate is read at
deploy, not baked into the build), so named-layouts.test.ts is unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_0fbebddf-0aa7-4f39-93b8-c987e2268588) |
…ion, 0077) Adversarial-review follow-up (codex P1 on #272). Verified: a broad page catch-all TTL can shadow a non-/api routes/ handler at dispatch and ISR-cache its response. PRE-EXISTING (a global-number routing.revalidate already did this on origin/main with looser guards; codex's /api/user example is already blocked by isApiPath), bounded by shouldBypassIsr (no cross-user auth leak). Documented as a bounded known limitation in plan 0077 rather than fixed here: the route-exclusion fix (prepend routes/ patterns as revalidate:0) has method and pattern-overlap caveats and is route-arbitration work outside this route-aware-TTL branch. Inline pointer added at the emission site. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_934f9e92-db81-477c-8711-671e1c25be73) |
What
Adds
manifest.pagesto the Void deploy manifest: an ordered page-route table[{ pattern, revalidate? }]emitted from the Void pages-mode deploy path.patternis the compiled route pattern in the framework'smatchRouteorder (no re-sort);revalidateis the page'sexport const revalidate(omitted when unset).This is Phase A of design 0077 (Route-Aware ISR TTL Resolution). The platform edge router resolves per-request ISR TTL from a flat
pattern → ttlmap that has lost route specificity, so a broad catch-all's TTL can be applied to a more-specific page (or an API route) that never opted into ISR. Shipping the orderedpagestable lets dispatch first-match the request against real route order and resolve the TTL of the page that actually renders.How
collectPages(isr-config.ts) — builds the ordered page-route table from the page scan.buildVoidManifest/assembleVoidManifest— emitpagesonDeployManifest.runFrameworkDeploy) has no page scan and is untouched.Compatibility
Additive, backward-compatible. No manifest
versionbump. Old dispatch/ingest ignore the new field; new dispatch falls back to the existing flatrevalidatemap whenpagesis absent.Tests
isr-config.test.ts—collectPagesordering + revalidate extraction.assemble-manifest.test.ts,package.test.ts— manifest emission + Void-mode gating.full-deploy-manifest.test.ts— end-to-endrunFullDeployguardspagesemission.kitchen-sink-build.test.ts— broad build coverage.named-layouts/pages/isr.*) exercises an ISR page.Cross-repo
Consumed by platform PR
voidzero-dev/void-platform#228(Phase C: dispatch route-aware resolution + api ingest/prewarm). This framework PR merges first; the platform submodule pointer is bumped to the merged SHA as a follow-up (Phase B).🤖 Generated with Claude Code
Note
Medium Risk
Changes deploy manifest shape and ISR TTL baking logic consumed by platform dispatch; mistakes could mis-cache or disable ISR, but fields are additive and heavily tested.
Overview
Adds route-aware ISR data to the Void deploy manifest for pages mode only: an ordered
manifest.pagestable and, whenrouting.revalidateis an object, rawmanifest.pathRevalidatefor edge resolution.collectPagesbuildspagesfrom the page scan with raw route patterns (no catch-all collapse) and a 3-staterevalidate: baked TTL, explicit0opt-out, or omitted (inherit). Per-page exports win; a global numeric config bakes into every page without an export; object configs are not baked so dispatch can honor per-path rules and0opt-outs on dynamic routes.assembleVoidManifestwirespageScan→pages/pathRevalidateinrunFullDeployand suppresses both in Class-A framework mode.buildVoidManifestandDeployManifestgain the new optional fields (additive, backward compatible).Tests cover
collectPages, manifest assembly, package emission, kitchen-sink, and an e2erunDeployfixture (named-layoutsISR page + objectrouting.revalidate).Reviewed by Cursor Bugbot for commit d132293. Bugbot is set up for automated code reviews on this repo. Configure here.