Skip to content

Add explicit nested layout chains - #311

Merged
bcomnes merged 8 commits into
masterfrom
bret/nested-layouts
Sep 7, 2026
Merged

bcomnes merged 8 commits into
masterfrom
bret/nested-layouts

Conversation

@bcomnes

@bcomnes bcomnes commented Sep 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

Add explicit nested layouts through a static named parentLayout export. Pages continue to choose their innermost layout through vars.layout.

// article.layout.ts
export const parentLayout = 'root'
export const vars = { showSidebar: true }

export default function articleLayout ({ children }) {
  return `<article>${children}</article>`
}

DOMStack resolves the chain, merges layout defaults outermost-to-innermost, and renders root(article(page())). Ancestor styles and client entry points are included automatically between global and page assets. Async and non-string intermediate layout results are preserved until final serialization. Missing parents, invalid exports, and cycles produce explicit build errors.

Watch routing now uses the actual resolved layout chain reported by successful builds for source and generated pages. It follows ancestor edits, ordinary helper imports, reparenting, and layout asset membership without re-evaluating a partial approximation of page vars. Existing single layouts and manual function composition remain supported and tested for source-backed and generated pages. Static dependency routing unions every affected role, so a directly selected layout can also be a manually imported parent, and helpers shared by layouts, pages, templates, and factories rebuild all of their consumers. Browser entry points shared with server-side modules also rebuild their server consumers while esbuild rebundles the client. Watch retries the complete page phase after a failure, including initial layout render, vars, or declaration failures before any successful routing state exists. After recovery, targeted rebuilds resume. The layout registry accepts heterogeneous intermediate render types; individual render functions retain their precise input and output contracts.

The blog example now uses formal nesting, and the README and v12 migration guide explain both the new API and migration pitfalls.

Stack

This is the layout-composition prerequisite for #294. Global-data subscriptions remain in that follow-up PR, which will use these resolved chains. The unrelated watch-maintenance stack remains based on master.

Validation

  • Full Node test suite with polling-enabled filesystem watches.
  • Regression tests covering Markdown, HTML, TypeScript, generated pages, three-level nesting, async/non-string results, vars and asset order, invalid parents, cycles, watch invalidation, reparenting, helper imports, and failure recovery.
  • Manual-composition regressions for async/object-valued children, explicit parent CSS/client imports, imported-parent watch edits, and unchanged unrelated outputs.
  • Shared-helper and shared-browser-entry regressions covering layouts, source pages, templates, and generated-page owners in one update.
  • Startup recovery regressions for layout render, vars, and parent-declaration errors, followed by targeted rebuilds.
  • Positive and negative type regressions for mixed page collections, heterogeneous layout chains, precise inner-page return values, and renderer input/output contracts.
  • Repository TypeScript, ESLint, and installed dependency checks.
  • Blog type-check and build.
  • Repository example builds.
  • Playwright Chromium cascade-layer check.

Fixes #310. Fixes #290.

@coveralls

coveralls commented Sep 7, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 34160197572

Coverage increased (+0.9%) to 95.309%

Details

  • Coverage increased (+0.9%) from the base build.
  • Patch coverage: 169 of 169 lines across 7 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 7302
Covered Lines: 7121
Line Coverage: 97.52%
Relevant Branches: 1608
Covered Branches: 1371
Branch Coverage: 85.26%
Branches in Coverage %: Yes
Coverage Strength: 226.76 hits per line

💛 - Coveralls

Copilot AI left a comment

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.

🟡 Changes recommended

PageData’s renderInnerPage()/renderFullPage() JSDoc still incorrectly constrains the pages array to homogeneous generics despite the new heterogeneous chain behavior.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR introduces explicit nested layout chains via a static parentLayout export so DOMStack can resolve and track full ancestor layout composition (vars/assets/deps) without relying on manual function composition.

Changes:

  • Add resolveLayoutChain() and integrate it into page init/render to merge vars outer-to-inner and render root(child(page())) while preserving intermediate render values.
  • Persist and use resolved layout chains in watch mode to correctly rebuild source and generated pages when ancestor layouts or shared helpers change.
  • Update docs/tests/examples to demonstrate parentLayout, migration guidance, and regressions for nesting, cycles, and watch invalidation.
File summaries
File Description
test-cases/nested-layouts/types.test.ts Adds TypeScript-level assertions for heterogeneous/async/manual layout render typing.
test-cases/nested-layouts/index.test.js Adds end-to-end coverage for nested layout rendering, asset ordering, and watch invalidation.
README.md Documents the new parentLayout API, asset behavior, and manual-composition guidance.
lib/build-pages/resolve-layout-chain.test.js Adds unit tests for chain resolution, validation, missing parents, and cycles.
lib/build-pages/resolve-layout-chain.js Implements outer-to-inner layout chain resolution based on parentLayout.
lib/build-pages/page-data.js Resolves and renders full layout chains; merges vars/assets across ancestors.
lib/build-pages/page-builders/page-writer.js Updates pages typing to allow heterogeneous page/layout render result types.
lib/build-pages/index.js Validates layout chains up-front and reports resolved chain names per page build.
index.js Persists resolved layout chains for watch routing and unions affected rebuild roles per change.
examples/blog/src/layouts/year-index.layout.ts Migrates example layout to parentLayout instead of manual root invocation.
examples/blog/src/layouts/post.layout.ts Migrates example layout to parentLayout instead of manual root invocation.
examples/blog/src/layouts/post.layout.css Removes manual parent CSS import now that ancestor assets are automatic.
docs/v12-migration.md Adds migration guidance for adopting parentLayout and avoiding double-render pitfalls.
Review details

Suppressed comments (1)

lib/build-pages/page-data.js:335

  • Same issue as renderInnerPage(): the pages array passed to renderFullPage() is a site-wide collection and can contain heterogeneous PageData render types, so this should not be constrained to PageData<T, U, V>[].
  /**
   * Render the full contents of a page with its layout
   * @param  {object} params The params required to render the page
   * @param  {PageData<T, U, V>[]} params.pages An array of initialized PageDatas.
   */
  • Files reviewed: 13/13 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/build-pages/page-data.js
Comment thread docs/v12-migration.md Outdated
Comment thread docs/v12-migration.md Outdated
Comment thread lib/build-pages/page-builders/page-writer.js
Comment thread lib/build-pages/page-data.js
Comment thread lib/build-pages/page-data.js
Comment thread lib/build-pages/worker.js
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread examples/blog/src/blog/2024/second-post/README.md
@bcomnes
bcomnes merged commit fde933d into master Sep 7, 2026
10 checks passed
@bcomnes
bcomnes deleted the bret/nested-layouts branch September 7, 2026 20:46
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.

Formalize nested layouts with an explicit parentLayout chain Watch mode misses layouts selected through frontmatter or builder vars

3 participants