Skip to content

feat: implement section-specific cache busting based on config hash#1052

Open
guitavano wants to merge 3 commits intomainfrom
feat/section-specific-cache-busting
Open

feat: implement section-specific cache busting based on config hash#1052
guitavano wants to merge 3 commits intomainfrom
feat/section-specific-cache-busting

Conversation

@guitavano
Copy link
Copy Markdown
Contributor

@guitavano guitavano commented Feb 6, 2026

Replace global revisionId with section-specific configuration hash in cache-busting parameter (__cb). This ensures only sections with changed configuration get new cache keys, allowing unchanged sections to maintain their cache across deployments.

Changes:

  • Add FieldResolver.resolveFromChain() utility to extract section config from resolveChain
  • Update useSection to hash actual section config instead of global revisionId
  • Refactor render.tsx to use new resolveFromChain utility (DRY)
  • Keep deploymentId in hash for now (to be evaluated for removal)

This addresses the issue where changing one section invalidated cache for all sections site-wide.


Summary by cubic

Implements section-specific cache busting by hashing the resolved section config and props, so only changed sections get new cache keys. Also makes the hash deterministic to avoid unnecessary cache busts.

  • New Features

    • Compute __cb from section config + props + vary + href (+ deploymentId kept for now).
    • Add FieldResolver.resolveFromChain to extract section config from resolveChain; used in render.tsx.
  • Bug Fixes

    • Use stableStringify for deterministic hashing to prevent key-order cache busts.

Written for commit 99d59b3. Summary will update on new commits.

Summary by CodeRabbit

  • Refactor

    • Centralized section resolution into a reusable resolver for more consistent behavior.
    • Simplified rendering flow by delegating nested property traversal to the centralized resolver.
    • Made content-hash computation deterministic by including section-level configuration and partial prop overrides.
  • Chores

    • Removed outdated internal comments.

Replace global revisionId with section-specific configuration hash in cache-busting parameter (__cb). This ensures only sections with changed configuration get new cache keys, allowing unchanged sections to maintain their cache across deployments.

Changes:
- Add FieldResolver.resolveFromChain() utility to extract section config from resolveChain
- Update useSection to hash actual section config instead of global revisionId
- Refactor render.tsx to use new resolveFromChain utility (DRY)
- Keep deploymentId in hash for now (to be evaluated for removal)

This addresses the issue where changing one section invalidated cache for all sections site-wide.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 6, 2026

Tagging Options

Should a new tag be published when this PR is merged?

  • 👍 for Patch 1.137.2 update
  • 🎉 for Minor 1.138.0 update
  • 🚀 for Major 2.0.0 update

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 6, 2026

📝 Walkthrough

Walkthrough

Adds a public FieldResolver.resolveFromChain helper to compute a nested value from a resolve chain and integrates it into the section hook and render flow; also updates the hook's cache hash inputs to include deterministic sectionConfig and props.

Changes

Cohort / File(s) Summary
Core Resolver Enhancement
engine/core/resolver.ts
Adds FieldResolver.resolveFromChain(resolveChain, resolvables) (public) to locate the last "resolvable" in a chain and traverse subsequent "prop" entries to return a nested value or undefined.
Hook Integration
hooks/useSection.ts
Imports stableStringify; computes sectionConfig via FieldResolver.resolveFromChain(resolveChain, resolvables); replaces revision-based hash component with stableStringify(sectionConfig) and stableStringify(props) in cbString; extracts resolvables and resolveChain from context.
Render Layer Refactor
runtime/features/render.tsx
Changes import to import { FieldResolver, type Resolvable } ...; replaces manual resolveChain traversal with FieldResolver.resolveFromChain(resolveChain, resolvables) to derive section before calling state.resolve.
Comment Cleanup
runtime/routes/render.tsx
Removes two explanatory comment lines inside the shouldCacheFromVary branch; no logic change.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • hugo-ccabral

Poem

🐰 I hop the chain, I peek, I pry,
Find the resolvable tucked nearby,
Follow props through leaf and vine,
Return the value, neat and fine,
Little rabbit, helper—sly 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: implement section-specific cache busting based on config hash' directly and accurately summarizes the primary change: replacing global revisionId with section-specific configuration hashing for cache busting.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/section-specific-cache-busting

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
runtime/features/render.tsx (1)

107-110: ⚠️ Potential issue | 🔴 Critical

Potential TypeError on section.__resolveType when section is undefined.

Line 109 accesses section.__resolveType without optional chaining, but section can be undefined (line 63-65 returns undefined when resolveChain is missing or contains no resolvable). If an HttpError is thrown in this scenario, this will crash with a TypeError, masking the original error.

Note the inconsistency: line 98 correctly uses section?.__resolveType, but line 109 does not.

🐛 Proposed fix
       const newResolveChain = [...resolveChain ?? [], {
         type: "resolver",
-        value: section.__resolveType,
+        value: section?.__resolveType,
       }];
🤖 Fix all issues with AI agents
In `@hooks/useSection.ts`:
- Around line 91-92: Remove the extra trailing spaces on the line containing
JSON.stringify(props) that precede the inline comment to satisfy deno fmt; run
`deno fmt` (or fix the spacing so the comment aligns like the previous line) so
the two lines with JSON.stringify(sectionConfig) and JSON.stringify(props) have
consistent spacing and formatting and pass `deno fmt --check`.
- Around line 90-96: The cbString composition uses JSON.stringify(sectionConfig)
and JSON.stringify(props) which return the JS value undefined (coerced to an
empty segment) when those values are undefined (e.g., from resolveFromChain),
causing distinct undefined-reasons to collide; update the cbString construction
to deterministic string fallbacks instead of raw JSON.stringify(undefined) —
e.g., for sectionConfig and props use a nullish check or sentinel (e.g.,
sectionConfig === undefined ? "__absent_sectionConfig__" :
JSON.stringify(sectionConfig)) and the same pattern for props so cbString
reliably differentiates missing vs present-empty values while keeping the
existing vary, stableHref, ctx?.deploymentId parts.
🧹 Nitpick comments (2)
engine/core/resolver.ts (1)

134-157: Loop can start from index + 1 instead of 0 to avoid unnecessary iterations.

The loop at line 150 starts from 0 and skips all entries before index via continue. Starting from index + 1 directly is clearer and avoids the redundant iterations and the multi-condition continue.

Also, the return type any | undefined collapses to just any in TypeScript, so the | undefined part is misleading — it suggests narrowing that doesn't actually exist at the type level.

♻️ Suggested refactor
   resolveFromChain: (
     resolveChain: FieldResolver[],
     resolvables: Record<string, any>,
-  ): any | undefined => {
+  ): any => {
     if (!resolveChain || !resolvables) {
       return undefined;
     }
 
     const index = resolveChain.findLastIndex((x) => x.type === "resolvable");
     if (index < 0) {
       return undefined;
     }
 
     let value = resolvables[resolveChain[index].value];
 
     // Navigate through the resolveChain to get the specific nested value
-    for (let it = 0; it < resolveChain.length; it++) {
+    for (let it = index + 1; it < resolveChain.length; it++) {
       const item = resolveChain[it];
-      if (it < index || item.type !== "prop") continue;
+      if (item.type !== "prop") continue;
       value = value?.[item.value];
     }
 
     return value;
   },
hooks/useSection.ts (1)

95-95: TODO-like comment left in production code.

The comment // Understand if we can remove it reads like an open question. Consider creating a tracking issue and referencing it here, or resolving the question before merging.

Comment thread hooks/useSection.ts
Comment thread hooks/useSection.ts Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 4 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="hooks/useSection.ts">

<violation number="1" location="hooks/useSection.ts:91">
P3: Using `JSON.stringify` for hashing is potentially unstable because object key order is not guaranteed to be deterministic (though often consistent in V8). If the configuration object keys are reordered (e.g. by the CMS), it will cause unnecessary cache busts. Consider using a stable stringify function or ensuring the input is canonical.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread hooks/useSection.ts Outdated
guitavano and others added 2 commits February 6, 2026 12:01
Replace JSON.stringify with stableStringify to ensure deterministic hash generation. JSON.stringify doesn't guarantee object key order, which could cause unnecessary cache busts when the same configuration has keys in different order.

stableStringify recursively sorts object keys, ensuring the same object structure always produces the same string.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread hooks/useSection.ts
const cbString = [
revisionId,
stableStringify(sectionConfig), // Section-specific config (deterministic)
stableStringify(props), // Partial props override (deterministic)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this wasn't taking into consideration before, why do we need this now?

what you want is a deterministic way to check if this specific section has changed somehow, based on straight prop change or global change. this can be done once "onConfigChange" and then remove revision id

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.

3 participants