Skip to content

docs(revalidate): canonical ctx.isAction idioms + shop-demo adoption - #559

Merged
ivogt merged 1 commit into
mainfrom
fix/dx-feedback-d1-d2-d3
Jun 13, 2026
Merged

ivogt merged 1 commit into
mainfrom
fix/dx-feedback-d1-d2-d3

Conversation

@ivogt

@ivogt ivogt commented Jun 12, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

ctx.isAction() is the rename-safe way to match server actions in a revalidate predicate, but the canonical guidance was thin and partly self-contradictory: the ShouldRevalidateFn JSDoc taught a single ambiguous ||-vs-?? example, and the shop demo modeled actionId?.includes("Cart") ?? false — valid, but rename-fragile, and not what a consumer should reach for first.

This PR lands the recommendation where consumers actually look (the JSDoc that shows on IDE hover + the rango skill) and adopts it in the two shop demo predicates where the conversion is a clean win. It deliberately does not sweep the rest of the suite — see Notes.

What a consumer sees differently

The hover doc on revalidate(...) and the rango skill now teach two named idioms instead of one ambiguous example:

Form Own action Unrelated action Idle navigation
ctx.isAction(X) || undefined (defer) revalidate defer to segment default defer to default
ctx.isAction(X) (suppress) revalidate suppress suppress

The first draft of the suppress idiom wrote ctx.isAction(X) || ctx.stale; since stale?: boolean is optional, that evaluates to undefined — i.e. defer, not suppress — whenever stale is absent. Fixed to the bare form, so the JSDoc and skill exemplars are now identical.

Shop demo adoption (the clean wins)

// before
revalidate(({ actionId }) => actionId?.includes("Cart") ?? false)

// after — rename-safe and shorter (isAction is already a strict boolean, so the
// old `?? false` tail was a no-op)
import * as ShopActions from "../handlers/shop/actions/shop.actions.js";
revalidate((ctx) => ctx.isAction(ShopActions))

Both shop sites are pure-suppress, so the conversion is exactly equivalent (verified by e2e).

Code-change summary

  • packages/rangojs-router/src/types/handler-context.ts — ShouldRevalidateFn JSDoc teaches the two named idioms with ctx.isAction.
  • packages/rangojs-router/skills/rango/SKILL.md — matching defer/suppress note.
  • tests/vite-rsc-demo/src/urls/shop.tsx — 2 pure-suppress predicates → ctx.isAction(ShopActions) / ctx.isAction(addToCart, addToCartWithResult), + 1 import * as ShopActions.
  • tests/cloudflare-basic/src/urls.tsx — drop a redundant : ResponseHandlerContext annotation + its now-unused import (inline path.* callbacks are contextually typed; proven clean under strict:true).

Test plan

  • typecheck — vite-rsc-demo + cloudflare-basic
  • unit — 3562 passed (the router change is JSDoc-only)
  • lint / format — clean
  • e2e dev + production — shop-actions + shop-loaders, 22 passed (pins the kept isAction(ShopActions) cart-revalidation and shared-loader-freshness in both modes)

Notes / deliberately not done

  • kanban/todos/loaders demos kept on actionId?.includes(...). actionId substring matching is supported floor API — the typesafety skill calls it the "floor and escape hatch" — not a deprecated pattern, so a demo using it isn't a hygiene violation. Those fixtures keep exercising that path through real action flows, and the isAction rewrite there needed a verbose ctx.actionId == null ? <fallback> : false guard that read worse than the original. The recommendation lives in the docs; the suite keeps its pattern variety.
  • No skill-docs sweep. ~14 other skill snippets still show actionId?.includes in the defer form; the API path has dedicated e2e (action-id-resolution.test.ts, is-action.test.ts), and two snippets must stay substring regardless: the loader return-protocol section (built on actionId's string | undefined shape — isAction would make ?? false dead code) and server-actions' startsWith("src/actions/account/") (matches unimported modules, which isAction cannot express).

@pkg-pr-new

pkg-pr-new Bot commented Jun 12, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@rangojs/router@559

commit: 7263e50

Land the rename-safe revalidation recommendation where consumers look for it -
the ShouldRevalidateFn JSDoc and the rango skill - and adopt it in the two
pure-suppress shop demo predicates. The rest of the demo suite stays on actionId
substring matching on purpose (see D1).

D2 (the recommendation) - the ShouldRevalidateFn JSDoc and rango skill now teach
two named idioms with ctx.isAction instead of one ambiguous example: Idiom A
"mine, else defer" (ctx.isAction(X) || undefined) and Idiom B "mine only", bare
ctx.isAction(X). Both are staleness-free: threading the optional `stale` into
the suppress form (ctx.isAction(X) || ctx.stale) returns undefined - i.e. defer,
not suppress - when stale is absent, so the canonical examples rely only on
isAction's raw boolean.

D1 (scoped to the clean wins) - the two pure-suppress shop predicates (CartLoader,
ModalRecommendationsLoader) move to ctx.isAction(ShopActions) /
ctx.isAction(addToCart, addToCartWithResult), which is both rename-safe and
shorter. kanban/todos/loaders deliberately keep actionId?.includes(...): it is
supported floor API (not a deprecated pattern), those fixtures keep exercising
that path through real action flows, and the isAction rewrite there needed a
verbose `ctx.actionId == null ? <fallback> : false` guard that read worse than
the original.

D3 - drop the redundant `: ResponseHandlerContext` annotation and its now-unused
import from the cloudflare-basic robots.txt path.text handler; inline path.*
callbacks are contextually typed, so inference is clean under strict:true.

Verified: typecheck, unit (3562), lint, format, and the shop revalidation e2e
(shop-actions + shop-loaders) in dev + production (22 passed).
@ivogt
ivogt force-pushed the fix/dx-feedback-d1-d2-d3 branch from c4513cc to 7263e50 Compare June 12, 2026 11:41
@ivogt ivogt changed the title refactor(examples): rename-safe ctx.isAction in revalidate predicates + canonical idioms docs(revalidate): canonical ctx.isAction idioms + shop-demo adoption Jun 12, 2026
@ivogt
ivogt marked this pull request as ready for review June 13, 2026 10:59
@ivogt
ivogt merged commit 44add2a into main Jun 13, 2026
16 checks passed
@ivogt
ivogt deleted the fix/dx-feedback-d1-d2-d3 branch June 13, 2026 10:59
ivogt added a commit that referenced this pull request Jun 13, 2026
#559 (docs(revalidate): ctx.isAction idioms + shop-demo adoption) touched
cloudflare-basic/src/urls.tsx. Sole conflict was the @rangojs/router import line:
resolved to { urls, updateTag, revalidateTag } — dropping both `cookies` (this
branch extracted the inline cookies().set into middleware/cookie-overlay.ts, so the
body no longer uses it) and `ResponseHandlerContext` (#559 removed the
`(ctx: ResponseHandlerContext)` annotation in favor of inference). Verified neither
is referenced in the merged body.

Verified: typecheck 12/12, test:unit:all (router 3816 unit + 45 RSC, cf 42, mini 16,
demo 15), lint 0/0, format.
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