Skip to content

feat(isr): emit manifest.pages — ordered page-route table for route-aware ISR TTL - #272

Merged
Brooooooklyn merged 12 commits into
mainfrom
feat/isr-pages-manifest
Jul 12, 2026
Merged

Brooooooklyn merged 12 commits into
mainfrom
feat/isr-pages-manifest

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Jul 10, 2026 •

Copy link
Copy Markdown
Member

What

Adds manifest.pages to the Void deploy manifest: an ordered page-route table [{ pattern, revalidate? }] emitted from the Void pages-mode deploy path. pattern is the compiled route pattern in the framework's matchRoute order (no re-sort); revalidate is the page's export 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 → ttl map 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 ordered pages table 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 — emit pages on DeployManifest.
  • Gated to Void pages mode only: the 3rd-party framework deploy path (runFrameworkDeploy) has no page scan and is untouched.

Compatibility

Additive, backward-compatible. No manifest version bump. Old dispatch/ingest ignore the new field; new dispatch falls back to the existing flat revalidate map when pages is absent.

Tests

  • isr-config.test.ts — collectPages ordering + revalidate extraction.
  • assemble-manifest.test.ts, package.test.ts — manifest emission + Void-mode gating.
  • full-deploy-manifest.test.ts — end-to-end runFullDeploy guards pages emission.
  • kitchen-sink-build.test.ts — broad build coverage.
  • Playground fixture (named-layouts/pages/isr.*) exercises an ISR page.
  • 67/67 targeted tests green; fmt + lint clean.

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.pages table and, when routing.revalidate is an object, raw manifest.pathRevalidate for edge resolution.

collectPages builds pages from the page scan with raw route patterns (no catch-all collapse) and a 3-state revalidate: baked TTL, explicit 0 opt-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 and 0 opt-outs on dynamic routes.

assembleVoidManifest wires pageScan → pages / pathRevalidate in runFullDeploy and suppresses both in Class-A framework mode. buildVoidManifest and DeployManifest gain the new optional fields (additive, backward compatible).

Tests cover collectPages, manifest assembly, package emission, kitchen-sink, and an e2e runDeploy fixture (named-layouts ISR page + object routing.revalidate).

Reviewed by Cursor Bugbot for commit d132293. Bugbot is set up for automated code reviews on this repo. Configure here.

Brooooooklyn and others added 6 commits July 10, 2026 15:55
…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>
@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/void/src/isr-config.ts Outdated
…ve void.json routing.revalidate)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/void/src/isr-config.ts Outdated
Comment thread packages/void/test/unit/isr-config.test.ts Outdated
… 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>
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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>
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/void/src/isr-config.ts Outdated
…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>
@cursor

cursor Bot commented Jul 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/void/src/isr-config.ts
…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>
@cursor

cursor Bot commented Jul 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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>
@cursor

cursor Bot commented Jul 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@Brooooooklyn
Brooooooklyn merged commit 2e443b2 into main Jul 12, 2026
7 checks passed
@Brooooooklyn
Brooooooklyn deleted the feat/isr-pages-manifest branch July 12, 2026 07:31
cpojer pushed a commit that referenced this pull request Sep 11, 2026
Route-aware ISR TTL resolution is fully landed and deployed: framework
manifest.pages/pathRevalidate producer (#272) + api ingest/prewarm and dispatch
route-aware resolution (#228). Deferred follow-ups tracked in #230.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant