feat(compat-v7): /sections, v7's section loader runner and its mixins
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. __pageUrland__pagePathare added to the props. A value already in the props wins.- The loader gets a
GETfor the page URL with the incoming request's headers. Passurlwhen 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
degradedunless it's innonCritical. v7 sentX-Deco-Degradedwhen that list wasn't empty.
runSectionLoader(section, options)does the same for one section (a deferred section).- Mixins:
withDevice,withMobile,withSearchParam,composeandwithSectionLoader, all v7's. They keep v7's__requestDependenttag and v7's catch inwithSectionLoader. - 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 (ascomposedoes) 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__pageUrlvalues. 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
ctxfails: it keeps its saved props and the section is degraded. v7 handed it a compatctx. - 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 besidesprops.
Gates
bun run check: 0. It needsGOMAXPROCS=4in 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 --helpworks. skills:checkandskills: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
nestedfrom 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
3ddc7b5binto 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:checkpass.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
__pageUrland__pagePath(existing values win) and aGETfor 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 innonCritical. - v7's
ctxthird 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.