)]}'
{
  "commit": "4333700a557679e2739422a2b06333ee6d673f89",
  "tree": "07898dd3b4af338d149140196f81e52985728159",
  "parents": [
    "c780d2d055bffdf7e2c57fb6b4efd6630d1c9b83"
  ],
  "author": {
    "name": "David Miguel Lozano",
    "email": "me@davidmiguel.com",
    "time": "Thu Apr 09 20:34:25 2026 +0200"
  },
  "committer": {
    "name": "GitHub",
    "email": "noreply@github.com",
    "time": "Thu Apr 09 18:34:25 2026 +0000"
  },
  "message": "[go_router] Fix chained top-level redirects not being fully resolved (#11108)\n\nFixes https://github.com/flutter/flutter/issues/178984\n\nThe `onEnter` refactor (commit `9ec29b6d23`, PR #8339) split the unified `redirect()` method into `applyTopLegacyRedirect()` (top-level, runs once) and `redirect()` (route-level only). This introduced two regressions:\n\n1. **Top-level redirect chains broken**: `applyTopLegacyRedirect()` returned after one hop — a chain like `/ → /a → /b` stopped at `/a` instead of resolving to `/b`.\n2. **Route-level → top-level chains broken**: `processRouteLevelRedirect()` recursed into `redirect()` without re-evaluating the top-level redirect on the new location.\n\n### Fix\n\nUnify top-level and route-level redirects back into `redirect()` as the single entry point for all redirect processing, while keeping the separation of concerns via `applyTopLegacyRedirect()` as an internal helper.\n\n\u003e **Note:** An earlier revision of this PR (v1) kept the split but added recursive calls in both methods. Based on [reviewer feedback](https://github.com/flutter/packages/pull/11108#discussion_r2943354003), the approach was refactored to move `applyTopLegacyRedirect()` inside `redirect()` so the caller (`parseRouteInformationWithDependencies`) doesn\u0027t need to know about the two-phase process.\n\n#### Before (broken)\n\n```mermaid\nsequenceDiagram\n    participant Parser as parseRouteInformationWithDependencies\n    participant TopRedir as applyTopLegacyRedirect\n    participant Redir as redirect (route-level only)\n\n    Parser-\u003e\u003eTopRedir: / (one hop only)\n    TopRedir--\u003e\u003eParser: /a (stops here ✗)\n    Parser-\u003e\u003eRedir: /a\n    Redir-\u003e\u003eRedir: route-level redirects\n    Note right of Redir: No top-level re-evaluation\n    Redir--\u003e\u003eParser: result\n```\n\n#### After (fix)\n\n```mermaid\nsequenceDiagram\n    participant Parser as parseRouteInformationWithDependencies\n    participant Redir as redirect (unified)\n    participant TopRedir as applyTopLegacyRedirect\n\n    Parser-\u003e\u003eRedir: / (initial matches)\n    Redir-\u003e\u003eTopRedir: / → top-level redirect chain\n    TopRedir-\u003e\u003eTopRedir: / → /a → /b (self-recursive)\n    TopRedir--\u003e\u003eRedir: /b (fully resolved ✓)\n    Redir-\u003e\u003eRedir: route-level redirects on /b\n    Note right of Redir: If route-level changes location,\u003cbr/\u003erecurse → top-level re-evaluated\n    Redir--\u003e\u003eParser: final result\n```\n\n#### Key changes\n\n- **`configuration.dart` — `redirect()`**: Now calls `applyTopLegacyRedirect()` first at every cycle, then processes route-level redirects on the post-top-level result. Route-level `_processRouteLevelRedirects` extracted as a helper.\n- **`configuration.dart` — `applyTopLegacyRedirect()`**: Self-recursive to fully resolve top-level chains. No functional change from v1.\n- **`parser.dart` — `parseRouteInformationWithDependencies()`**: Simplified — no longer calls `applyTopLegacyRedirect` separately. Just passes initial matches to `_navigate()`.\n- **`parser.dart` — `_navigate()`**: Removed `preSharedHistory` parameter. Added `context.mounted` guard in the result `.then()` to protect the relocated async boundary.\n\nBoth fixes share the existing `redirectHistory` for loop detection and respect `redirectLimit`. The `onEnter` system is completely unaffected — it runs before redirects in the pipeline.\n\n### Tests\n\n- **19 redirect chain tests** (`redirect_chain_test.dart`): top-level chains, async chains, loop detection (including loop-to-initial), route→top cross-type chains, **async cross-type chains** (async top→route, async route→sync top, sync route→async top), **context disposal** during async top-level and route-level redirects, redirect limit boundary (exact limit succeeds, limit+1 fails), shared limit across redirect types.\n- **3 onEnter interaction tests** (`on_enter_test.dart`): onEnter called once when chains resolve, onEnter block prevents redirect evaluation.\n- **Full suite**: 418 tests pass, 0 regressions.\n\n## Pre-Review Checklist\n\n[^1]: This PR uses `pending_changelogs/` for versioning and changelog, following the go_router batch release process.",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "ef408b442708befb71ab6b315c6c392cfb4117dd",
      "old_mode": 33188,
      "old_path": "packages/go_router/lib/src/configuration.dart",
      "new_id": "e78011f6aa1629949c4f486e3f99844c4c79b67b",
      "new_mode": 33188,
      "new_path": "packages/go_router/lib/src/configuration.dart"
    },
    {
      "type": "modify",
      "old_id": "c900b02d857067010d1f24bba5165bbf18e2ac5d",
      "old_mode": 33188,
      "old_path": "packages/go_router/lib/src/parser.dart",
      "new_id": "5b91b0e81ff9165abddb7cab01ec05868c2e0fbd",
      "new_mode": 33188,
      "new_path": "packages/go_router/lib/src/parser.dart"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "4804674f05ebffbb20acd77438200054c60ae388",
      "new_mode": 33188,
      "new_path": "packages/go_router/pending_changelogs/fix_chained_redirect_regression.yaml"
    },
    {
      "type": "modify",
      "old_id": "c9408ea4183d493ab66acfcb4141df850de7260c",
      "old_mode": 33188,
      "old_path": "packages/go_router/test/on_enter_test.dart",
      "new_id": "2fb09cc051f34c990bab314e772361c27d0a5656",
      "new_mode": 33188,
      "new_path": "packages/go_router/test/on_enter_test.dart"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "7eee1280918955778554bb6413267483d2c9e9cb",
      "new_mode": 33188,
      "new_path": "packages/go_router/test/redirect_chain_test.dart"
    }
  ]
}
