feat(compat-v7): /sections, v7's section loader runner and its mixins

TL
5h ago
+2827-11

Stacked on #703 (feat/compat-v7-matchers). Step C3 of @decocms/compat-v7.

What's in it

@decocms/compat-v7/sections. It's a subpath only, not exported from the root, because names like compose are too generic there.

  • runSectionLoaders(sections, { loaders, request, url?, nonCritical?, nested? }) returns { sections, degraded }. It runs v7's (props, req) section loaders the way v7 did after a page resolved:
    • Each section runs its loader, then the sections in its props run theirs. All loaders run in parallel.
    • Nested sections are found anywhere in the props, as latest 7.x does (7.x #648, since 7.77.13). nested: "direct" gives the walk of 7.77.12 and earlier: only sections held directly in a prop or a list prop.
    • __pageUrl and __pagePath are added to the props. A value already in the props wins.
    • The loader gets a GET for the page URL with the incoming request's headers. Pass url when the request is a client-side navigation's server-function call (v7 #280).
    • A loader that throws leaves the saved props. The section is listed in degraded unless it's in nonCritical. v7 sent X-Deco-Degraded when that list wasn't empty.
  • runSectionLoader(section, options) does the same for one section (a deferred section).
  • Mixins: withDevice, withMobile, withSearchParam, compose and withSectionLoader, all v7's. They keep v7's __requestDependent tag and v7's catch in withSectionLoader.
  • No ctx. The third argument is a Proxy, and any read of it throws. The error says what to use instead: detectDevice, the app's client or a server function, settings. The section keeps its saved props and is listed as degraded. Passing the proxy along (as compose does) or awaiting it doesn't throw.
  • Not ported: v7's caches (layout and cacheable sections), its registries and its dev warnings. The loaders are an argument, and nothing here reads the environment.

Pinned to v7

Three v7 files are kept verbatim, with pinned hashes, and run unchanged through loadV7:

  • 7.x 9131e9d9: cms/sectionLoaders.ts
  • 7.x 9131e9d9: cms/sectionMixins.ts
  • 7.77.12 (44952cbe): cms/sectionLoaders.ts

The tests:

  • Generated pages: 4,000 per walk, deep and direct. They mix nested sections in props, lists, plain and null-prototype objects, shared subtrees, decoys, Dates and existing __pageUrl values. The loaders add props, are async, throw, reject, return nested sections, replace their props, return nothing, are the mixins, are composed, or are module loaders. Requests are sometimes server-function calls. For every page, the sections, the degraded list and each loader call (props, URL, user agent, order) equal v7's.
  • Generated requests through the mixins: 20,000, including undecodable q= values. Results, errors and tags equal v7's.
  • Fixed cases: cyclic loader output against v7, deep versus direct (the #648 case), non-critical sections, the page URL on server-function calls, an inherited key like constructor (no loader), and the ctx Proxy.

Mutation run: 20 mutations, 18 caught. The other two can't change a result: one is the cycle guard's depth constant, the other spreads section instead of result where props is overwritten anyway.

Differences from v7, by design

  • A loader that reads ctx fails: it keeps its saved props and the section is degraded. v7 handed it a compat ctx.
  • There's no layout or cacheable cache. To keep v7's degraded result, pass v7's layout sections in nonCritical, since v7 never marked them degraded.
  • v8 sections are { component, props }. v7 wrote nested sections as { Component, props }. A nested section here keeps any keys it has besides props.

Gates

  • bun run check: 0. It needs GOMAXPROCS=4 in this environment because of a process limit.
  • bun run build: 0.
  • Plain-Node import of every built export: 11 packages, compat-v7 8/8. deco --help works.
  • skills:check and skills:readme:check: ok.
  • bun run test (2 GB cap): 2533 passed, 1 failed, 95 skipped. The failure is DS-2, the known flake: it fails the same way when its file runs alone, and that file isn't touched here.

Later steps

  • The migrator should wire this into the block map and choose nested from each site's v7 version. Storefront and demo were on 7.77.1, so they need "direct" to match their v7 baselines.
  • A docs-site PR should replace the "copy the runner into the site" advice.

Main refresh (2026-10-17, 3c1d051c)

  • Merged C2 3ddc7b5b into this branch (normal merge, no conflicts), then merged up the stack to M2 #707.
  • Gates at the top of the stack (M2 5131a3c1): bun run check, bun run build, skills:check, skills:readme:check pass. bun run test (2 GB memory cap): 122 files pass, 1 skipped; 2,745 tests pass, 95 skipped, 0 fail. DS-2 now passes (fixed on main by #709); no timeouts this run.
  • CI on this PR's new head: all checks pass.

🤖 Generated with Claude Code
https://claude.ai/code/session_01WNwbSEePYNcY5YCgqZURig


Summary by cubic

Adds @decocms/compat-v7/sections, a port of v7's section loader runner and its mixins (runSectionLoaders, runSectionLoader, withDevice, withMobile, withSearchParam, compose, withSectionLoader) that reproduces v7's output on v8. It's a subpath export only, since names like compose are too generic for the root.

Behavior

  • Runs loaders in parallel per v7's rules: each section's loader, then the loaders of the sections nested anywhere in its props (latest 7.x); nested: "direct" reproduces 7.77.12's direct-children-only walk.
  • Injects __pageUrl and __pagePath (existing values win) and a GET for the page URL with the incoming request's headers; a loader that throws keeps its saved props and the section is listed as degraded unless in nonCritical.
  • v7's ctx third argument is a Proxy that throws on any read, degrading the section; passing it along or awaiting it does not throw.
  • Not ported: v7's caches, registries, and dev warnings; loaders are passed as an argument and nothing reads the environment.

Testing

  • Pinned v7 fixtures (7.x and 7.77.12) run unchanged; 4,000 generated pages per walk and 20,000 generated requests through the mixins compare sections, degraded lists, and loader calls against v7's.
  • The matchers tests now stub the section runner, which takes no part in matching.