@timber-js/app 0.2.0-alpha.122 → 0.2.0-alpha.123

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.
Files changed (58) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/{actions-Dy8vOjFt.js → actions-ZlcKx0H9.js} +5 -5
  3. package/dist/_chunks/{actions-Dy8vOjFt.js.map → actions-ZlcKx0H9.js.map} +1 -1
  4. package/dist/_chunks/{define-CPVQaWbx.js → define-C0V_eKiW.js} +6 -18
  5. package/dist/_chunks/define-C0V_eKiW.js.map +1 -0
  6. package/dist/_chunks/{handler-store-CcwNX-gk.js → handler-store-bIAstrxQ.js} +1 -1
  7. package/dist/_chunks/{handler-store-CcwNX-gk.js.map → handler-store-bIAstrxQ.js.map} +1 -1
  8. package/dist/_chunks/{merge-search-params-az6ZiBeU.js → merge-search-params-Cm1j99HC.js} +1 -1
  9. package/dist/_chunks/{merge-search-params-az6ZiBeU.js.map → merge-search-params-Cm1j99HC.js.map} +1 -1
  10. package/dist/_chunks/{navigation-root-DoRTeBm5.js → navigation-root-Ds_hDtRW.js} +2 -2
  11. package/dist/_chunks/{navigation-root-DoRTeBm5.js.map → navigation-root-Ds_hDtRW.js.map} +1 -1
  12. package/dist/_chunks/{resolve-schema-cURaYvaD.js → resolve-schema-DU2nbVOi.js} +1 -1
  13. package/dist/_chunks/{resolve-schema-cURaYvaD.js.map → resolve-schema-DU2nbVOi.js.map} +1 -1
  14. package/dist/_chunks/{router-ref-CpvjBqdu.js → router-ref-B2-2kIIC.js} +2 -2
  15. package/dist/_chunks/{router-ref-CpvjBqdu.js.map → router-ref-B2-2kIIC.js.map} +1 -1
  16. package/dist/_chunks/{segment-context-EsfQpS--.js → segment-context-Bg0pFbLT.js} +1 -1
  17. package/dist/_chunks/{segment-context-EsfQpS--.js.map → segment-context-Bg0pFbLT.js.map} +1 -1
  18. package/dist/_chunks/{ssr-data-m9-cNEIw.js → ssr-data-DVX8S7vj.js} +1 -1
  19. package/dist/_chunks/{ssr-data-m9-cNEIw.js.map → ssr-data-DVX8S7vj.js.map} +1 -1
  20. package/dist/_chunks/{use-segment-params-CtIAjqK0.js → use-segment-params-Du7u25cK.js} +3 -8
  21. package/dist/_chunks/{use-segment-params-CtIAjqK0.js.map → use-segment-params-Du7u25cK.js.map} +1 -1
  22. package/dist/_chunks/{validation-BMU5MPzP.js → validation-AfEFM5ze.js} +1 -1
  23. package/dist/_chunks/{validation-BMU5MPzP.js.map → validation-AfEFM5ze.js.map} +1 -1
  24. package/dist/cache/index.js +1 -1
  25. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  26. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
  27. package/dist/client/error-boundary.js +2 -2
  28. package/dist/client/index.d.ts +1 -0
  29. package/dist/client/index.d.ts.map +1 -1
  30. package/dist/client/index.js +47 -14
  31. package/dist/client/index.js.map +1 -1
  32. package/dist/client/internal.js +67 -32
  33. package/dist/client/internal.js.map +1 -1
  34. package/dist/client/link.d.ts.map +1 -1
  35. package/dist/client/router.d.ts.map +1 -1
  36. package/dist/client/rsc-fetch.d.ts +8 -0
  37. package/dist/client/rsc-fetch.d.ts.map +1 -1
  38. package/dist/client/shallow-url.d.ts +10 -0
  39. package/dist/client/shallow-url.d.ts.map +1 -0
  40. package/dist/cookies/index.js +2 -2
  41. package/dist/index.js +1 -1
  42. package/dist/params/index.js +1 -1
  43. package/dist/segment-params/define.d.ts +0 -6
  44. package/dist/segment-params/define.d.ts.map +1 -1
  45. package/dist/segment-params/index.js +1 -1
  46. package/dist/server/index.js +1 -1
  47. package/dist/server/internal.js +3 -3
  48. package/package.json +6 -7
  49. package/src/cli.ts +0 -0
  50. package/src/client/browser-entry/action-dispatch.ts +9 -10
  51. package/src/client/browser-entry/post-hydration.ts +12 -6
  52. package/src/client/index.ts +1 -5
  53. package/src/client/link.tsx +25 -12
  54. package/src/client/router.ts +74 -25
  55. package/src/client/rsc-fetch.ts +20 -5
  56. package/src/client/shallow-url.ts +22 -0
  57. package/src/segment-params/define.ts +2 -29
  58. package/dist/_chunks/define-CPVQaWbx.js.map +0 -1
@@ -1,10 +1,10 @@
1
1
  import { t as bindUseQueryStates } from "../_chunks/use-query-states-C9G6LP0F.js";
2
- import { c as cachedSearchParams, i as _setCachedSearch, n as getSsrData, r as setSsrData, s as cachedSearch, t as clearSsrData } from "../_chunks/ssr-data-m9-cNEIw.js";
3
- import { n as getRouterOrNull, r as setGlobalRouter, t as getRouter } from "../_chunks/router-ref-CpvjBqdu.js";
2
+ import { c as cachedSearchParams, i as _setCachedSearch, n as getSsrData, r as setSsrData, s as cachedSearch, t as clearSsrData } from "../_chunks/ssr-data-DVX8S7vj.js";
3
+ import { a as setNavigationState, i as getNavigationState, r as NavigationProvider, t as setCurrentParams } from "../_chunks/use-segment-params-Du7u25cK.js";
4
+ import { n as getRouterOrNull, r as setGlobalRouter, t as getRouter } from "../_chunks/router-ref-B2-2kIIC.js";
4
5
  import { TimberErrorBoundary } from "./error-boundary.js";
5
- import { n as useSegmentContext, t as SegmentProvider } from "../_chunks/segment-context-EsfQpS--.js";
6
- import { t as setHardNavigating } from "../_chunks/navigation-root-DoRTeBm5.js";
7
- import { a as getNavigationState, i as NavigationProvider, o as setNavigationState, t as setCurrentParams } from "../_chunks/use-segment-params-CtIAjqK0.js";
6
+ import { n as useSegmentContext, t as SegmentProvider } from "../_chunks/segment-context-Bg0pFbLT.js";
7
+ import { t as setHardNavigating } from "../_chunks/navigation-root-Ds_hDtRW.js";
8
8
  import { cloneElement, isValidElement, useSyncExternalStore } from "react";
9
9
  //#region src/client/segment-cache.ts
10
10
  /**
@@ -558,8 +558,12 @@ async function fetchRscPayload(url, deps, stateTree, currentUrl, signal) {
558
558
  return response;
559
559
  });
560
560
  await wrappedPromise;
561
+ const payload = deps.decodeRsc(wrappedPromise);
562
+ const decodePromise = Promise.resolve(payload).then(() => void 0);
563
+ decodePromise.catch(() => {});
561
564
  return {
562
- payload: await deps.decodeRsc(wrappedPromise),
565
+ payload,
566
+ decodePromise,
563
567
  headElements,
564
568
  segmentInfo,
565
569
  params,
@@ -577,6 +581,7 @@ async function fetchRscPayload(url, deps, stateTree, currentUrl, signal) {
577
581
  }
578
582
  return {
579
583
  payload: await response.text(),
584
+ decodePromise: null,
580
585
  headElements: extractHeadElements(response),
581
586
  segmentInfo: extractSegmentInfo(response),
582
587
  params: extractParams(response),
@@ -600,6 +605,7 @@ function createRouter(deps) {
600
605
  const historyStack = new HistoryStack();
601
606
  const segmentElementCache = new SegmentElementCache();
602
607
  let routerPhase = { phase: "idle" };
608
+ let navigationId = 0;
603
609
  const pendingListeners = /* @__PURE__ */ new Set();
604
610
  let currentNavAbort = null;
605
611
  /**
@@ -636,14 +642,29 @@ function createRouter(deps) {
636
642
  }
637
643
  /**
638
644
  * Merge a partial RSC payload with cached segment elements if segments
639
- * were skipped, then cache segments from the (merged) payload.
640
- * Returns the merged payload ready for rendering.
645
+ * were skipped. Returns the merged payload ready for rendering.
646
+ *
647
+ * Segment element caching is deferred to after paint by the caller
648
+ * so it doesn't block the render path.
649
+ */
650
+ async function mergePayload(payload, skippedSegments) {
651
+ if (skippedSegments && skippedSegments.length > 0) return mergeSegmentTree(payload != null && typeof payload === "object" && "then" in payload ? await payload : payload, skippedSegments, segmentElementCache);
652
+ return payload;
653
+ }
654
+ /**
655
+ * Schedule segment element caching after paint. Resolves thenable
656
+ * payloads first since tree walking requires resolved React elements.
657
+ * Guards against stale writes: if a newer navigation started before
658
+ * this callback fires, the cache write is skipped.
641
659
  */
642
- function mergeAndCachePayload(payload, skippedSegments) {
643
- let merged = payload;
644
- if (skippedSegments && skippedSegments.length > 0) merged = mergeSegmentTree(payload, skippedSegments, segmentElementCache);
645
- cacheSegmentElements(merged, segmentElementCache);
646
- return merged;
660
+ function deferSegmentCaching(payload) {
661
+ const capturedNavId = navigationId;
662
+ afterPaint(async () => {
663
+ if (capturedNavId !== navigationId) return;
664
+ const resolved = payload != null && typeof payload === "object" && "then" in payload ? await payload : payload;
665
+ if (capturedNavId !== navigationId) return;
666
+ cacheSegmentElements(resolved, segmentElementCache);
667
+ });
647
668
  }
648
669
  /**
649
670
  * Update navigation state (params + pathname) for the next render.
@@ -663,37 +684,41 @@ function createRouter(deps) {
663
684
  setNavigationState(navState);
664
685
  return navState;
665
686
  }
666
- /**
667
- * Render a payload via navigateTransition (production) or renderRoot (tests).
668
- * The perform callback should fetch data, update state, and return the
669
- * FetchResult plus the NavigationState (so it can be passed explicitly
670
- * to wrapPayload/renderRoot without temporal coupling).
671
- */
672
687
  async function renderViaTransition(url, perform) {
673
688
  if (deps.navigateTransition) {
674
689
  let headElements = null;
690
+ let decodePromise = null;
675
691
  await deps.navigateTransition(url, async (wrapPayload) => {
676
692
  const result = await perform();
677
693
  headElements = result.headElements;
678
- const merged = mergeAndCachePayload(result.payload, result.skippedSegments);
694
+ decodePromise = result.decodePromise;
695
+ const merged = await mergePayload(result.payload, result.skippedSegments);
679
696
  historyStack.push(url, {
680
697
  payload: merged,
681
698
  headElements: result.headElements,
682
699
  params: result.params
683
700
  });
701
+ deferSegmentCaching(merged);
684
702
  return wrapPayload(merged, result.navState);
685
703
  });
686
- return headElements;
704
+ return {
705
+ headElements,
706
+ decodePromise
707
+ };
687
708
  }
688
709
  const result = await perform();
689
- const merged = mergeAndCachePayload(result.payload, result.skippedSegments);
710
+ const merged = await mergePayload(result.payload, result.skippedSegments);
690
711
  historyStack.push(url, {
691
712
  payload: merged,
692
713
  headElements: result.headElements,
693
714
  params: result.params
694
715
  });
716
+ deferSegmentCaching(merged);
695
717
  renderPayload(merged, result.navState);
696
- return result.headElements;
718
+ return {
719
+ headElements: result.headElements,
720
+ decodePromise: result.decodePromise
721
+ };
697
722
  }
698
723
  /** Apply head elements (title, meta tags) to the DOM if available. */
699
724
  function applyHead(elements) {
@@ -722,6 +747,7 @@ function createRouter(deps) {
722
747
  const prefetched = prefetchCache.consume(url);
723
748
  let result = prefetched ? {
724
749
  payload: prefetched.payload,
750
+ decodePromise: null,
725
751
  headElements: prefetched.headElements,
726
752
  segmentInfo: prefetched.segmentInfo ?? null,
727
753
  params: prefetched.params ?? null,
@@ -756,6 +782,7 @@ function createRouter(deps) {
756
782
  const replace = options.replace === true;
757
783
  const externalSignal = options._signal;
758
784
  const skipHistory = options._skipHistory === true;
785
+ navigationId++;
759
786
  const navAbort = createNavAbort(externalSignal);
760
787
  const currentScrollY = deps.getScrollY();
761
788
  if (deps.saveNavigationEntryScroll) deps.saveNavigationEntryScroll(currentScrollY);
@@ -772,11 +799,13 @@ function createRouter(deps) {
772
799
  }
773
800
  setPending(true, url);
774
801
  try {
775
- applyHead(await renderViaTransition(url, () => performNavigationFetch(url, {
802
+ const { headElements, decodePromise } = await renderViaTransition(url, () => performNavigationFetch(url, {
776
803
  replace,
777
804
  signal: navAbort.signal,
778
805
  skipHistory: effectiveSkipHistory
779
- })));
806
+ }));
807
+ if (decodePromise) await decodePromise;
808
+ applyHead(headElements);
780
809
  window.dispatchEvent(new Event("timber:navigation-end"));
781
810
  restoreScrollAfterPaint(scroll ? 0 : currentScrollY);
782
811
  } catch (error) {
@@ -807,10 +836,11 @@ function createRouter(deps) {
807
836
  }
808
837
  async function refresh() {
809
838
  const currentUrl = deps.getCurrentUrl();
839
+ navigationId++;
810
840
  const navAbort = createNavAbort();
811
841
  setPending(true, currentUrl);
812
842
  try {
813
- applyHead(await renderViaTransition(currentUrl, async () => {
843
+ const { headElements, decodePromise } = await renderViaTransition(currentUrl, async () => {
814
844
  const result = await fetchRscPayload(currentUrl, deps, void 0, void 0, navAbort.signal);
815
845
  updateSegmentCache(result.segmentInfo);
816
846
  const navState = updateNavigationState(result.params, currentUrl);
@@ -818,7 +848,9 @@ function createRouter(deps) {
818
848
  ...result,
819
849
  navState
820
850
  };
821
- }));
851
+ });
852
+ if (decodePromise) await decodePromise;
853
+ applyHead(headElements);
822
854
  } catch (error) {
823
855
  if (isAbortError(error)) return;
824
856
  throw error;
@@ -829,6 +861,7 @@ function createRouter(deps) {
829
861
  }
830
862
  }
831
863
  async function handlePopState(url, scrollY = 0, externalSignal) {
864
+ navigationId++;
832
865
  const entry = historyStack.get(url);
833
866
  if (entry && entry.payload !== null) {
834
867
  const navState = updateNavigationState(entry.params, url);
@@ -839,7 +872,7 @@ function createRouter(deps) {
839
872
  const navAbort = createNavAbort(externalSignal);
840
873
  setPending(true, url);
841
874
  try {
842
- applyHead(await renderViaTransition(url, async () => {
875
+ const { headElements, decodePromise } = await renderViaTransition(url, async () => {
843
876
  const result = await fetchRscPayload(url, deps, segmentCache.serializeStateTree(segmentElementCache.getMergeablePaths()), void 0, navAbort.signal);
844
877
  updateSegmentCache(result.segmentInfo);
845
878
  const navState = updateNavigationState(result.params, url);
@@ -847,7 +880,9 @@ function createRouter(deps) {
847
880
  ...result,
848
881
  navState
849
882
  };
850
- }));
883
+ });
884
+ if (decodePromise) await decodePromise;
885
+ applyHead(headElements);
851
886
  restoreScrollAfterPaint(scrollY);
852
887
  } catch (error) {
853
888
  if (isAbortError(error)) return;
@@ -882,12 +917,12 @@ function createRouter(deps) {
882
917
  prefetch,
883
918
  applyRevalidation(element, headElements) {
884
919
  const currentUrl = deps.getCurrentUrl();
885
- const merged = mergeAndCachePayload(element, null);
886
920
  historyStack.push(currentUrl, {
887
- payload: merged,
921
+ payload: element,
888
922
  headElements
889
923
  });
890
- renderPayload(merged, getNavigationState());
924
+ deferSegmentCaching(element);
925
+ renderPayload(element, getNavigationState());
891
926
  applyHead(headElements);
892
927
  },
893
928
  initSegmentCache: (segments) => updateSegmentCache(segments),