···312312 print_title("initializing content sources");
313313314314 // Determine which content sources need to be initialized
315315- // For incremental builds, only re-init sources whose files have changed
316316- let sources_to_init: Option<FxHashSet<String>> = if is_incremental {
315315+ // For incremental builds with specific routes to rebuild, only re-init sources whose files have changed
316316+ // If routes_to_rebuild is None (full rebuild), always init all sources
317317+ let sources_to_init: Option<FxHashSet<String>> = if routes_to_rebuild.is_some() {
317318 if let Some(changed) = changed_files {
318319 build_state.get_affected_content_sources(changed)
319320 } else {
320321 None // Full init
321322 }
322323 } else {
323323- None // Full init
324324+ None // Full init (routes_to_rebuild is None means full rebuild)
324325 };
325326326327 // Initialize content sources (all or selective)
+93
e2e/tests/incremental-build.spec.ts
···928928 expect(after.articleThird).not.toBe(before.articleThird);
929929 expect(after.articles).not.toBe(before.articles);
930930 });
931931+932932+ // ============================================================
933933+ // TEST 15: Full rebuild from untracked file properly initializes content sources
934934+ // ============================================================
935935+ test("full rebuild from untracked file properly initializes content sources", async ({ devServer }) => {
936936+ // This test verifies that when an untracked Rust file (like helpers.rs) changes,
937937+ // triggering a full rebuild (routes_to_rebuild = None), content sources are
938938+ // still properly initialized.
939939+ //
940940+ // This was a bug where the code checked `is_incremental` instead of
941941+ // `routes_to_rebuild.is_some()`, causing content sources to not be initialized
942942+ // during full rebuilds triggered by untracked file changes.
943943+ //
944944+ // Setup:
945945+ // - helpers.rs is a shared module not tracked in source_to_routes
946946+ // - Changing it triggers routes_to_rebuild = None (full rebuild)
947947+ // - Routes like /articles/* use content from the "articles" content source
948948+ // - If content sources aren't initialized, the build would crash
949949+ //
950950+ // This test:
951951+ // 1. First modifies a content file to ensure specific content exists
952952+ // 2. Then modifies helpers.rs to trigger a full rebuild
953953+ // 3. Verifies the content-using routes are properly built with correct content
954954+955955+ const helpersRs = resolve(fixturePath, "src", "pages", "helpers.rs");
956956+ const originalHelpersRs = readFileSync(helpersRs, "utf-8");
957957+ const rsTimeout = 60000;
958958+959959+ try {
960960+ // Step 1: Modify content file to set up specific content we can verify
961961+ const testMarker = `CONTENT-INIT-TEST-${Date.now()}`;
962962+ const newContent = (originals.firstPost as string).replace(
963963+ "first post",
964964+ `first post - ${testMarker}`
965965+ );
966966+ writeFileSync(contentFiles.firstPost, newContent);
967967+968968+ // Wait for the content change to be processed
969969+ const beforeContent = getBuildId(htmlPaths.articleFirst);
970970+ await waitForBuildComplete(devServer, rsTimeout);
971971+ await waitForBuildIdChange(htmlPaths.articleFirst, beforeContent, rsTimeout);
972972+973973+ // Verify the content was updated
974974+ let articleHtml = readFileSync(htmlPaths.articleFirst, "utf-8");
975975+ expect(articleHtml).toContain(testMarker);
976976+977977+ // Record build IDs before the helpers.rs change
978978+ const before = recordBuildIds(htmlPaths);
979979+ expect(before.articleFirst).not.toBeNull();
980980+ expect(before.articles).not.toBeNull();
981981+982982+ // Step 2: Modify helpers.rs to trigger full rebuild
983983+ // This is an untracked file, so it triggers routes_to_rebuild = None
984984+ devServer.clearLogs();
985985+ writeFileSync(helpersRs, originalHelpersRs + `\n// content-init-test-${Date.now()}`);
986986+987987+ await waitForBuildComplete(devServer, rsTimeout);
988988+ await waitForBuildIdChange(htmlPaths.articleFirst, before.articleFirst, rsTimeout);
989989+990990+ // Step 3: Verify the build succeeded and content is still correct
991991+ // If content sources weren't initialized, this would fail or crash
992992+ const after = recordBuildIds(htmlPaths);
993993+994994+ // All routes should be rebuilt (full rebuild)
995995+ expect(after.index).not.toBe(before.index);
996996+ expect(after.about).not.toBe(before.about);
997997+ expect(after.blog).not.toBe(before.blog);
998998+ expect(after.articleFirst).not.toBe(before.articleFirst);
999999+ expect(after.articles).not.toBe(before.articles);
10001000+10011001+ // Most importantly: verify the content-using routes have correct content
10021002+ // This proves content sources were properly initialized during the full rebuild
10031003+ articleHtml = readFileSync(htmlPaths.articleFirst, "utf-8");
10041004+ expect(articleHtml).toContain(testMarker);
10051005+10061006+ // Also verify the articles list page works (uses entries())
10071007+ const articlesHtml = readFileSync(htmlPaths.articles, "utf-8");
10081008+ expect(articlesHtml).toContain("First Post");
10091009+10101010+ } finally {
10111011+ // Restore original content
10121012+ writeFileSync(helpersRs, originalHelpersRs);
10131013+ writeFileSync(contentFiles.firstPost, originals.firstPost as string);
10141014+10151015+ // Wait for restoration build
10161016+ const beforeRestore = getBuildId(htmlPaths.articleFirst);
10171017+ try {
10181018+ await waitForBuildIdChange(htmlPaths.articleFirst, beforeRestore, 60000);
10191019+ } catch {
10201020+ // Restoration build may not always complete, that's ok
10211021+ }
10221022+ }
10231023+ });
9311024});