Skip to content

fix(pipeline): preserve enriched SiteData across incremental rebuilds#730

Merged
zeroedin merged 2 commits into
mainfrom
fix/721-pagination-data-loss
May 22, 2026
Merged

fix(pipeline): preserve enriched SiteData across incremental rebuilds#730
zeroedin merged 2 commits into
mainfrom
fix/721-pagination-data-loss

Conversation

@zeroedin

Copy link
Copy Markdown
Owner

Summary

  • Adds SiteData field to BuildResult so dev.go can capture enriched data (external sources + onDataFetched hooks) from Build()
  • Injects enriched SiteData into PipelineState after InitPipelineState() at dev server startup, and after full-rebuild fallback
  • Re-runs onDataFetched hook after data file reload in BuildIncremental, mirroring the enrichment path in Build()

Fixes #721

Test plan

🤖 Generated with Claude Code

…#721)

Paginated pages lost their pagination data after content-only incremental
rebuilds because PipelineState.SiteData was never enriched with external
sources or onDataFetched hook results from the initial Build().

- Add SiteData field to BuildResult so dev.go can capture enriched data
- Inject enriched SiteData into PipelineState after InitPipelineState()
- Update PipelineState.SiteData after full-rebuild fallback
- Re-run onDataFetched hook after data file reload in BuildIncremental

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to fix dev-server incremental rebuild correctness by keeping PipelineState.SiteData aligned with the “enriched” site data produced by Build() (data files + external sources + onDataFetched hook output), so pagination and other site-data consumers don’t lose data across rebuilds.

Changes:

  • Add SiteData to pipeline.BuildResult so callers (notably cmd/dev.go) can reuse the enriched data.
  • Update cmd/dev.go to copy Build().SiteData into the cached PipelineState at startup and after full-rebuild fallbacks.
  • Update BuildIncremental to re-run onDataFetched after data-directory reloads and merge returned keys back into SiteData.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
internal/pipeline/incremental.go Re-runs onDataFetched and merges returned keys after reloading data files during incremental rebuilds.
internal/pipeline/build.go Extends BuildResult to include the enriched SiteData produced during Build().
cmd/dev.go Copies enriched SiteData from full builds into the long-lived PipelineState used for incremental rebuilds.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmd/dev.go
Comment thread internal/pipeline/incremental.go
Comment thread cmd/dev.go Outdated
@zeroedin

Copy link
Copy Markdown
Owner Author

Code Review — PR #730

fix(pipeline): preserve enriched SiteData across incremental rebuilds

8 reviewers dispatched. 14 raw findings deduplicated to 6.


Verdict: Ready with fixes


#1 [P1] Nil dereference of ps at cmd/dev.go:185

ps.SiteData = fullResult.SiteData dereferences ps without a nil check. If InitPipelineState at line 135 fails, ps is nil (the error is logged as a warning and execution continues). When the watcher later triggers a full rebuild, line 185 panics.

The initial SiteData injection at line 139 correctly guards ps != nil. This new line does not.

Fix: Add the nil guard to match line 139:

if ps != nil && fullResult != nil && fullResult.SiteData != nil {
    ps.SiteData = fullResult.SiteData
}

#2 [P2] BuildIncremental does not populate BuildResult.SiteData

Build() populates result.SiteData at both return paths (lines 557, 942). BuildIncremental() never sets it (line 512). Today this works only because BuildIncremental mutates ps.SiteData in-place via the shared PipelineState pointer, so dev.go already sees the updated data. But the contract implied by BuildResult is that SiteData is always available. Any future consumer that reads incrResult.SiteData will silently get nil.

Fix: Add SiteData: ps.SiteData, to the BuildResult literal at incremental.go:512.

#3 [P2] Duplicated onDataFetched hook merge logic — advisory

The type-switch pattern merging hook results (map[string]interface{} vs *ordered.Map) into siteData is copy-pasted between build.go:283-292 and incremental.go:203-214. A future change to the merge logic (e.g., supporting a third return type) must be applied in both places. Consider extracting a helper like mergeSiteDataHookResult(target map[string]interface{}, result interface{}). Each call site retains its own error handling for RunWithTimeout.

Not blocking — the two copies are small and the error semantics intentionally differ (see #4).

#4 [P2] onDataFetched error handling inconsistency — advisory

Build() treats hook failure as fatal (return nil, fmt.Errorf(...)). BuildIncremental() logs a warning and continues. This is a reasonable design choice for dev server resilience, but worth a brief comment documenting the intent. No action required for merge.

#5 [P2] Testing gaps — downstream

Per the project's spec-first workflow, these are for the architect to address in a spec PR.

#6 [P3] Pre-existing: duplicate pages in pagesToRender — advisory

When hasDataChange is true, paginated pages are unconditionally appended to pagesToRender even if already present from content-hash invalidation. The post-pagination renderRelPaths set deduplicates by RelPath, so this is cosmetic (affects only the skipped counter). Pre-existing, not introduced by this PR.

- Add ps != nil guard on full-rebuild SiteData update (nil deref if
  InitPipelineState failed)
- Inject SiteData into plugin runtimes after both startup and
  full-rebuild SiteData updates so QuickJS alloy.data stays consistent
- Populate BuildResult.SiteData in BuildIncremental for contract parity

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zeroedin

Copy link
Copy Markdown
Owner Author

Review response

All actionable items fixed in 6342220.

#1 [P1] — Fixed. Added ps != nil guard on the full-rebuild SiteData update at dev.go:191.

#2 [P2] — Fixed. Added SiteData: ps.SiteData to BuildIncremental's BuildResult at incremental.go:523.

#3 [P2] — Acknowledged. Two small copies, intentionally different error semantics (see #4). Will extract a helper if a third call site appears.

#4 [P2] — Acknowledged. Intentional: Build() is fatal because a broken hook during initial build means the site is wrong. BuildIncremental() logs and continues for dev server resilience — a transient hook failure shouldn't kill the watcher loop.

#5 [P2] — Downstream. BuildResult.SiteData assertions and onDataFetched hook coverage in BuildIncremental need spec tests from the architect.

#6 [P3] — Acknowledged. Pre-existing cosmetic issue, not introduced by this PR.

Additionally addressed Copilot's feedback: both SiteData injection points in dev.go (startup and full-rebuild fallback) now call rt.SetSiteData on all plugin runtimes so QuickJS alloy.data stays consistent.

@zeroedin
zeroedin merged commit 6debb96 into main May 22, 2026
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.

bug: paginated pages lose pagination data on content-only incremental rebuild

2 participants