@jsenv/navi 0.16.14 → 0.16.16

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.
@@ -7716,9 +7716,18 @@ const createRoutePattern = (pattern) => {
7716
7716
 
7717
7717
  const parsedPattern = parsePattern(cleanPattern, parameterDefaults);
7718
7718
 
7719
+ // Create signalSet to track all signals this pattern depends on
7720
+ const signalSet = new Set();
7721
+ for (const connection of connections) {
7722
+ if (connection.signal) {
7723
+ signalSet.add(connection.signal);
7724
+ }
7725
+ }
7726
+
7719
7727
  if (DEBUG$2) {
7720
7728
  console.debug(`[CustomPattern] Created pattern:`, parsedPattern);
7721
7729
  console.debug(`[CustomPattern] Signal connections:`, connections);
7730
+ console.debug(`[CustomPattern] SignalSet size:`, signalSet.size);
7722
7731
  }
7723
7732
 
7724
7733
  const applyOn = (url) => {
@@ -7821,13 +7830,6 @@ const createRoutePattern = (pattern) => {
7821
7830
  const childParams = {};
7822
7831
  let isCompatible = true;
7823
7832
 
7824
- if (DEBUG$2) {
7825
- console.debug(
7826
- `[${pattern}] Checking compatibility with child: ${childPatternObj.originalPattern}`,
7827
- );
7828
- console.debug(`[${pattern}] Params passed to buildUrl:`, params);
7829
- }
7830
-
7831
7833
  // CRITICAL: Check if parent route can reach all child route's literal segments
7832
7834
  // A route can only optimize to a descendant if there's a viable path through parameters
7833
7835
  // to reach all the descendant's literal segments (e.g., "/" cannot reach "/admin"
@@ -7863,7 +7865,30 @@ const createRoutePattern = (pattern) => {
7863
7865
  }
7864
7866
  if (parentSegmentAtPosition.type === "param") {
7865
7867
  // Parent has a parameter at this position - child literal can satisfy this parameter
7866
- // This is OK - the child route provides the value for the parent's parameter
7868
+ // BUT we need to check if the parent's parameter value matches the child's literal
7869
+
7870
+ // Find the parent's parameter value from signals or params
7871
+ const paramName = parentSegmentAtPosition.name;
7872
+ let parentParamValue = params[paramName];
7873
+
7874
+ // If not in params, check signals
7875
+ if (parentParamValue === undefined) {
7876
+ const parentConnection = connections.find(
7877
+ (conn) => conn.paramName === paramName,
7878
+ );
7879
+ if (parentConnection && parentConnection.signal) {
7880
+ parentParamValue = parentConnection.signal.value;
7881
+ }
7882
+ }
7883
+
7884
+ // If parent has a specific value for this parameter, it must match the child literal
7885
+ if (
7886
+ parentParamValue !== undefined &&
7887
+ parentParamValue !== literalValue
7888
+ ) {
7889
+ return { isCompatible: false, childParams: {} };
7890
+ }
7891
+
7867
7892
  continue;
7868
7893
  }
7869
7894
  }
@@ -7953,7 +7978,6 @@ const createRoutePattern = (pattern) => {
7953
7978
  paramValue,
7954
7979
  childParsedPattern,
7955
7980
  );
7956
-
7957
7981
  if (matchesChildLiteral) {
7958
7982
  // Compatible - parameter value matches child literal
7959
7983
  return {
@@ -7964,11 +7988,31 @@ const createRoutePattern = (pattern) => {
7964
7988
  };
7965
7989
  }
7966
7990
 
7991
+ // ROBUST FIX: For path parameters, check semantic compatibility by verifying
7992
+ // that parent parameter values can actually produce the child route structure
7993
+ const isParentPathParam = connections.some(
7994
+ (conn) => conn.paramName === paramName,
7995
+ );
7996
+ if (isParentPathParam) {
7997
+ // Check if parent parameter value matches any child literal where it should
7998
+ // The key insight: if parent has a specific parameter value, child route must
7999
+ // be reachable with that value or they're incompatible
8000
+ const parameterCanReachChild = canParameterReachChildRoute(
8001
+ paramName,
8002
+ paramValue,
8003
+ parsedPattern,
8004
+ childParsedPattern,
8005
+ );
8006
+
8007
+ if (!parameterCanReachChild) {
8008
+ return { isCompatible: false };
8009
+ }
8010
+ }
8011
+
7967
8012
  // Check if this is a query parameter in the parent pattern
7968
8013
  const isParentQueryParam = parsedPattern.queryParams.some(
7969
8014
  (qp) => qp.name === paramName,
7970
8015
  );
7971
-
7972
8016
  if (isParentQueryParam) {
7973
8017
  // Query parameters are always compatible and can be inherited by child routes
7974
8018
  return {
@@ -7985,7 +8029,6 @@ const createRoutePattern = (pattern) => {
7985
8029
  const isParentPathParam = connections.some(
7986
8030
  (conn) => conn.paramName === paramName,
7987
8031
  );
7988
-
7989
8032
  if (isParentPathParam) {
7990
8033
  // Parameter value (from user or signal) doesn't match this child's literals
7991
8034
  // Check if child has any literal segments that would conflict with this parameter
@@ -7993,7 +8036,6 @@ const createRoutePattern = (pattern) => {
7993
8036
  (segment) =>
7994
8037
  segment.type === "literal" && segment.value !== paramValue,
7995
8038
  );
7996
-
7997
8039
  if (hasConflictingLiteral) {
7998
8040
  return { isCompatible: false };
7999
8041
  }
@@ -8288,6 +8330,22 @@ const createRoutePattern = (pattern) => {
8288
8330
  console.debug(`[${pattern}] buildMostPreciseUrl called`);
8289
8331
  }
8290
8332
 
8333
+ // Get the updated signalSet from pattern registry (set by setupPatterns)
8334
+ const patternData = patternRegistry.get(pattern);
8335
+ const effectiveSignalSet = patternData?.signalSet || signalSet;
8336
+
8337
+ // Access signal.value to trigger dependency tracking
8338
+ if (DEBUG$2) {
8339
+ console.debug(
8340
+ `[${pattern}] Reading ${effectiveSignalSet.size} signals for reactive dependencies`,
8341
+ );
8342
+ }
8343
+ // for (const signal of effectiveSignalSet) {
8344
+ // // Access signal.value to trigger dependency tracking
8345
+ // // eslint-disable-next-line no-unused-expressions
8346
+ // signal.value; // This line is critical for signal reactivity - when commented out, routes may not update properly
8347
+ // }
8348
+
8291
8349
  // Step 1: Resolve and clean parameters
8292
8350
  const resolvedParams = resolveParams(params);
8293
8351
 
@@ -8958,6 +9016,46 @@ const paramMatchesChildLiteral = (paramValue, childParsedPattern) => {
8958
9016
  );
8959
9017
  };
8960
9018
 
9019
+ /**
9020
+ * Helper: Check if a parent parameter can semantically reach a child route
9021
+ * This replaces the fragile position-based matching with semantic verification
9022
+ */
9023
+ const canParameterReachChildRoute = (
9024
+ paramName,
9025
+ paramValue,
9026
+ parentPattern,
9027
+ childPattern,
9028
+ ) => {
9029
+ // Find the parent parameter segment
9030
+ const parentParamSegment = parentPattern.segments.find(
9031
+ (segment) => segment.type === "param" && segment.name === paramName,
9032
+ );
9033
+
9034
+ if (!parentParamSegment) {
9035
+ return true; // Not a path parameter, no conflict
9036
+ }
9037
+
9038
+ // Get parameter's logical path position (not array index)
9039
+ const paramPathPosition = parentParamSegment.index;
9040
+
9041
+ // Find corresponding child segment at the same logical path position
9042
+ const childSegmentAtSamePosition = childPattern.segments.find(
9043
+ (segment) => segment.index === paramPathPosition,
9044
+ );
9045
+
9046
+ if (!childSegmentAtSamePosition) {
9047
+ return true; // Child doesn't extend to this position, no conflict
9048
+ }
9049
+
9050
+ if (childSegmentAtSamePosition.type === "literal") {
9051
+ // Child has a literal at this position - parent parameter must match exactly
9052
+ return childSegmentAtSamePosition.value === paramValue;
9053
+ }
9054
+
9055
+ // Child has parameter at same position - compatible
9056
+ return true;
9057
+ };
9058
+
8961
9059
  /**
8962
9060
  * Parse a route pattern string into structured segments
8963
9061
  */
@@ -9564,7 +9662,25 @@ const setupPatterns = (patternDefinitions) => {
9564
9662
 
9565
9663
  for (const [key, urlPatternRaw] of Object.entries(patternDefinitions)) {
9566
9664
  const [cleanPattern, connections] = detectSignals(urlPatternRaw);
9567
- const parsedPattern = parsePattern(cleanPattern);
9665
+
9666
+ // Build parameter defaults from signal connections
9667
+ const parameterDefaults = new Map();
9668
+ for (const connection of connections) {
9669
+ const { paramName, options } = connection;
9670
+ if (options.defaultValue !== undefined) {
9671
+ parameterDefaults.set(paramName, options.defaultValue);
9672
+ }
9673
+ }
9674
+
9675
+ const parsedPattern = parsePattern(cleanPattern, parameterDefaults);
9676
+
9677
+ // Create signalSet for this pattern
9678
+ const signalSet = new Set();
9679
+ for (const connection of connections) {
9680
+ if (connection.signal) {
9681
+ signalSet.add(connection.signal);
9682
+ }
9683
+ }
9568
9684
 
9569
9685
  const patternData = {
9570
9686
  key,
@@ -9572,6 +9688,7 @@ const setupPatterns = (patternDefinitions) => {
9572
9688
  cleanPattern,
9573
9689
  connections,
9574
9690
  parsedPattern,
9691
+ signalSet,
9575
9692
  childPatterns: [],
9576
9693
  parent: null,
9577
9694
  };
@@ -9620,6 +9737,60 @@ const setupPatterns = (patternDefinitions) => {
9620
9737
  });
9621
9738
  }
9622
9739
 
9740
+ // Phase 3: Collect all relevant signals for each pattern based on relationships
9741
+ for (const currentPattern of allPatterns) {
9742
+ const currentData = patternRegistry.get(currentPattern);
9743
+ const allRelevantSignals = new Set();
9744
+
9745
+ // Add own signals
9746
+ for (const signal of currentData.signalSet) {
9747
+ allRelevantSignals.add(signal);
9748
+ }
9749
+
9750
+ // Add signals from ancestors (they might be inherited)
9751
+ let parentData = currentData.parent
9752
+ ? patternRegistry.get(currentData.parent.originalPattern)
9753
+ : null;
9754
+ while (parentData) {
9755
+ for (const connection of parentData.connections) {
9756
+ if (connection.signal) {
9757
+ allRelevantSignals.add(connection.signal);
9758
+ }
9759
+ }
9760
+ // Move up the parent chain
9761
+ parentData = parentData.parent
9762
+ ? patternRegistry.get(parentData.parent.originalPattern)
9763
+ : null;
9764
+ }
9765
+
9766
+ // Add signals from descendants (they might be used for optimization)
9767
+ const addDescendantSignals = (patternData) => {
9768
+ for (const childPatternObj of patternData.childPatterns) {
9769
+ const childData = patternRegistry.get(childPatternObj.originalPattern);
9770
+ if (childData) {
9771
+ // Add child's own signals
9772
+ for (const connection of childData.connections) {
9773
+ if (connection.signal) {
9774
+ allRelevantSignals.add(connection.signal);
9775
+ }
9776
+ }
9777
+ // Recursively add grandchildren signals
9778
+ addDescendantSignals(childData);
9779
+ }
9780
+ }
9781
+ };
9782
+ addDescendantSignals(currentData);
9783
+
9784
+ // Update the pattern's signalSet with all relevant signals
9785
+ currentData.signalSet = allRelevantSignals;
9786
+
9787
+ if (DEBUG$2 && allRelevantSignals.size > 0) {
9788
+ console.debug(
9789
+ `[${currentPattern}] Collected ${allRelevantSignals.size} relevant signals`,
9790
+ );
9791
+ }
9792
+ }
9793
+
9623
9794
  if (DEBUG$2) {
9624
9795
  console.debug("Pattern registry updated");
9625
9796
  }
@@ -10846,9 +11017,6 @@ const navTo = (target, options) => {
10846
11017
  }
10847
11018
  return browserIntegration.navTo(url, options);
10848
11019
  };
10849
- const replaceUrl = (target, options = {}) => {
10850
- return navTo(target, { ...options, replace: true });
10851
- };
10852
11020
  const stopLoad = (reason = "stopLoad() called") => {
10853
11021
  const windowIsLoading = windowIsLoadingSignal.value;
10854
11022
  if (windowIsLoading) {
@@ -11231,13 +11399,6 @@ const initRouteObserver = ({
11231
11399
  const Element = element;
11232
11400
  element = jsx(Element, {});
11233
11401
  }
11234
- // ensure we re-render on document url change (useful when navigating from /users/list to /users)
11235
- // so that we re-replace urls back to /users/list when /users/list is an index
11236
- useDocumentUrl();
11237
- if (matchingRouteInfo && matchingRouteInfo.index && !matchingRouteInfo.route.matching) {
11238
- const routeUrl = matchingRouteInfo.route.routeFromProps.buildUrl();
11239
- replaceUrl(routeUrl);
11240
- }
11241
11402
  return jsx(RouteInfoContext.Provider, {
11242
11403
  value: matchingRouteInfo,
11243
11404
  children: jsx(SlotContext.Provider, {