@lovrozagar/flare 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovrozagar/flare",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Solid meta-framework. Server-driven, NDJSON streaming, renderToStream.",
5
5
  "keywords": [
6
6
  "flare",
@@ -585,12 +585,12 @@ function hydrateCachedDeferred(cached: CachedMatch): boolean {
585
585
  function commitCachedShell(
586
586
  c: FlareProviderContext,
587
587
  allModules: LoadedRouteModule[],
588
+ rootLayout: LoadedRouteModule | undefined,
588
589
  search: SearchParams,
589
590
  params: Record<string, string | string[]>,
590
591
  ): { hadShell: boolean; keepMatchIds: string[] } {
591
592
  if (!ctx) return { hadShell: false, keepMatchIds: [] };
592
593
  let found = false;
593
- const heads: PerRouteHead[] = [];
594
594
  const keepMatchIds: string[] = [];
595
595
  for (const mod of allModules) {
596
596
  const matchId = matchIdForModule(mod, search, params);
@@ -600,10 +600,23 @@ function commitCachedShell(
600
600
  if (cached.hasDeferred && hydrateCachedDeferred(cached)) {
601
601
  keepMatchIds.push(matchId);
602
602
  }
603
- if (cached.headConfig) heads.push({ head: cached.headConfig, matchId });
604
603
  }
605
604
  if (!found) return { hadShell: false, keepMatchIds: [] };
606
605
 
606
+ /* applyPerRouteHeads replaces the whole hierarchy, so a partial set would
607
+ * drop the root layout's CSS. Apply only when every route has a cached head;
608
+ * otherwise the post-fetch update applies the full set. */
609
+ const heads: PerRouteHead[] = [];
610
+ for (const mod of rootLayout ? [rootLayout, ...allModules] : allModules) {
611
+ const matchId = matchIdForModule(mod, search, params);
612
+ const head = ctx.matchCache.get(matchId)?.headConfig;
613
+ if (!head) {
614
+ heads.length = 0;
615
+ break;
616
+ }
617
+ heads.push({ head, matchId });
618
+ }
619
+
607
620
  c.setIntercepted(null);
608
621
  c.setNotFound(false);
609
622
  assignMatches(c, buildClientMatches(allModules, search, params));
@@ -1141,6 +1154,9 @@ export async function navigate(options: InternalNavigateOptions, redirectCount =
1141
1154
  */
1142
1155
  const nonRootLayouts = modules.layouts.filter((m) => m._type !== "root-layout");
1143
1156
  const allModules: LoadedRouteModule[] = [...nonRootLayouts, modules.page];
1157
+ /* The root layout still owns head state (title, CSS) on every navigation. */
1158
+ const rootLayout = modules.layouts.find((m) => m._type === "root-layout");
1159
+ const headModules = rootLayout ? [rootLayout, ...allModules] : allModules;
1144
1160
 
1145
1161
  /* Instant navigation: reuse in-flight prefetch and paint a cached shell
1146
1162
  * before the enter NDJSON hop. Skipped when this nav already fetched in
@@ -1160,7 +1176,7 @@ export async function navigate(options: InternalNavigateOptions, redirectCount =
1160
1176
  }
1161
1177
  }
1162
1178
  if (controller.signal.aborted || myVersion !== navigationVersion) return;
1163
- const shell = commitCachedShell(c, allModules, search, modules.params);
1179
+ const shell = commitCachedShell(c, allModules, rootLayout, search, modules.params);
1164
1180
  hadShell = shell.hadShell;
1165
1181
  paintedShell = shell.hadShell;
1166
1182
  if (shell.hadShell) {
@@ -1387,25 +1403,21 @@ export async function navigate(options: InternalNavigateOptions, redirectCount =
1387
1403
  const clientMatches = buildClientMatches(allModules, search, modules.params);
1388
1404
 
1389
1405
  /* Step 11: Compute head data before update — used inside VT callback */
1390
- const freshHeads = fetchResult?.perRouteHeads ?? [];
1391
- const fetchedHeadIds = new Set<string>();
1392
- for (const h of freshHeads) fetchedHeadIds.add(h.matchId);
1393
- const perRouteHeads: PerRouteHead[] = [...freshHeads];
1394
- for (const mod of allModules) {
1395
- const deps = mod.effectsConfig?.loaderDeps?.({ search }) ?? [];
1396
- const matchId = computeMatchId({
1397
- loaderDeps: () => deps,
1398
- params: modules.params,
1399
- routeId: mod.virtualPath,
1400
- search,
1401
- });
1402
- if (!fetchedHeadIds.has(matchId)) {
1403
- const cached = ctx.matchCache.get(matchId);
1404
- if (cached?.headConfig) {
1405
- perRouteHeads.push({ head: cached.headConfig, matchId });
1406
- }
1406
+ const freshHeads = new Map<string, HeadConfig>();
1407
+ for (const h of fetchResult?.perRouteHeads ?? []) freshHeads.set(h.matchId, h.head);
1408
+ const fetchedIds = new Set(fetchResult?.matches.map((m) => m.matchId));
1409
+ /* Hierarchy order, root first. A fetched route uses its fresh head (none
1410
+ * under headReplace); an unfetched one falls back to the cache. */
1411
+ const perRouteHeads: PerRouteHead[] = [];
1412
+ for (const mod of headModules) {
1413
+ const matchId = matchIdForModule(mod, search, modules.params);
1414
+ const head = fetchedIds.has(matchId) ? freshHeads.get(matchId) : ctx.matchCache.get(matchId)?.headConfig;
1415
+ if (head) {
1416
+ perRouteHeads.push({ head, matchId });
1417
+ freshHeads.delete(matchId);
1407
1418
  }
1408
1419
  }
1420
+ for (const [matchId, head] of freshHeads) perRouteHeads.push({ head, matchId });
1409
1421
 
1410
1422
  /* Step 12: Update state + scroll + head + cleanup.
1411
1423
  * Everything inside update() so VT captures the complete state transition. */