# Navigation Context Plan ## Goal Replace full `previous_url` query parameters with a short `nav` token and keep navigation history in session. This must: - eliminate URL growth; - preserve nested back navigation; - reduce conflicts between unrelated navigation chains; - avoid large `Location` headers on redirects. ## Target Model In URL: ```text ?nav=abc123xyz ``` In session: ```php [ 'navigation' => [ 'abc123xyz' => [ 'updated_at' => 1713945600, 'stack' => [ '/reclamations?filters[comment]=...', '/reclamations/show/848', '/catalog/show/15', ], ], ], ] ``` ## Core Principles 1. Pass only `nav` in links and forms. 2. Store history only in session. 3. Push only GET pages into the stack. 4. Never pass full `previous_url` in redirects again. 5. Keep a fallback route per module if token/session context is missing. ## Implementation Plan ### 1. Add Navigation Service Create: - `app/Services/NavigationContextService.php` Responsibilities: - get existing token or create a new one; - store URL stack in session by token; - return correct back URL for current page; - cap stack size; - prune expired tokens. Planned public API: ```php getOrCreateToken(Request $request): string rememberCurrentPage(Request $request, string $token): void backUrl(Request $request, string $token, ?string $fallback = null): ?string routeParams(array $params, string $token): array pruneExpired(): void forgetToken(string $token): void ``` Internal helpers: ```php normalizeUrl(string $url): string isGetPage(Request $request): bool context(string $token): array saveContext(string $token, array $context): void ``` ### 2. Define Stack Rules Rules for session history: 1. Only GET pages are pushed to stack. 2. POST/PUT/PATCH/DELETE requests are never pushed. 3. Before storing URL: - remove `nav`; - remove service-only params if needed. 4. If URL is already the last stack item, do not push it again. 5. If URL already exists earlier in stack: - remove old occurrence; - append it to the end. 6. Limit stack length to 20 items. 7. Store `updated_at`. 8. Remove expired contexts older than 24 hours. ### 3. Token Format Requirements: - short; - URL-safe; - unique enough for one session. Recommended format: ```php bin2hex(random_bytes(8)) ``` ### 4. Extend Base Controller Current base methods: - `resolvePreviousUrl()` - `previousUrlForRedirect()` Add wrappers over the new service: - `resolveNavToken(Request $request): string` - `rememberNavigation(Request $request, string $token): void` - `navigationBackUrl(Request $request, string $token, ?string $fallback = null): ?string` - `withNav(array $params, string $token): array` For transition period, do not remove old methods immediately. ### 5. Define Fallback Routes per Module If token is missing or stack is empty, each module needs a stable fallback. Initial list: - reclamations -> `route('reclamations.index', session('gp_reclamations'))` - orders -> `route('order.index', session('gp_orders'))` - spare parts -> `route('spare_parts.index')` - spare part orders -> `route('spare_part_orders.index')` - import -> `route('import.index', session('gp_import'))` Need to confirm catalog/product fallback if dedicated index route exists. ### 6. Implement Service Behavior `backUrl()` must return previous page relative to current page, not just latest entry. If stack is: ```text /reclamations?filters=... /reclamations/show/848 /catalog/show/15 ``` Then on `/catalog/show/15` back URL must be `/reclamations/show/848`. Algorithm: 1. Normalize current request URL. 2. Read stack. 3. If current URL equals last stack item, return previous one. 4. Otherwise return last item. 5. If nothing found, return fallback. ### 7. Migrate GET Controllers First Start with pages that render show/edit screens and need back navigation. Priority controllers: 1. `ReclamationController@show` 2. `OrderController@show`, `@edit` 3. `ProductController@show/edit` 4. `ProductSKUController@show/edit` 5. `SparePartController@show/edit` 6. `SparePartOrderController@show/edit` 7. `ImportController@show` Pattern for each GET action: ```php $nav = $this->resolveNavToken($request); $this->rememberNavigation($request, $nav); $this->data['nav'] = $nav; $this->data['back_url'] = $this->navigationBackUrl( $request, $nav, route('reclamations.index', session('gp_reclamations')) ); ``` ### 8. Replace `previous_url` in Blade Links Main rule: - stop passing `previous_url`; - pass only `nav`. Highest-risk files: 1. `resources/views/partials/table.blade.php` 2. `resources/views/orders/show.blade.php` 3. `resources/views/reclamations/edit.blade.php` 4. `resources/views/catalog/edit.blade.php` 5. `resources/views/products_sku/edit.blade.php` 6. `resources/views/spare_parts/edit.blade.php` 7. `resources/views/spare_part_orders/edit.blade.php` Expected view variables: - `$nav` - `$back_url` Buttons/links: - use `$back_url` for "Назад"; - use `route(..., ['nav' => $nav])` for nested transitions. ### 9. Replace `previous_url` in Forms and AJAX Hidden fields: - replace hidden `previous_url` with hidden `nav`. AJAX updates: - send `nav`; - do not send `previous_url`; - return `204 No Content` for AJAX updates where page refresh is not needed. This is especially relevant for: - `resources/views/reclamations/edit.blade.php` ### 10. Replace Redirect Logic in POST Controllers Main rule: - POST handlers keep `nav`; - redirects return either to current page with `nav`, or to resolved back URL; - full `previous_url` must not appear in redirect params. High-priority controllers: 1. `ReclamationController` 2. `ProductController` 3. `OrderController` 4. `ProductSKUController` 5. `SparePartController` 6. `SparePartOrderController` Pattern: ```php $nav = $request->string('nav')->toString(); return redirect()->route('reclamations.show', [ 'reclamation' => $reclamation, 'nav' => $nav, ]); ``` Use `redirect()->to($backUrl)` only when action should return to parent page instead of current card. ### 11. Add Transitional Compatibility For rollout safety: 1. If `nav` exists, use new logic. 2. If `nav` is missing but `previous_url` exists: - create a new token; - import `previous_url` as first stack item; - then append current page. 3. If neither exists: - use fallback route. This allows gradual migration without breaking old entry points. ### 12. Standardize Shared Partials Review and update shared templates: - `resources/views/partials/submit.blade.php` - `resources/views/partials/table.blade.php` Target: - use `$back_url` instead of `$previous_url`; - use `$nav` instead of embedding source URL directly. `url()->previous()` should remain only as a last-resort fallback if needed. ### 13. Session Cleanup Strategy To prevent session growth: - maximum contexts per session: 30 - maximum stack length per context: 20 - context TTL: 24 hours Cleanup should run inside service on: - `getOrCreateToken()` - `rememberCurrentPage()` ### 14. Test Coverage Add feature tests for: 1. token creation when `nav` absent; 2. token reuse when `nav` present; 3. GET page stored in stack; 4. POST page not stored in stack; 5. `nav` removed during URL normalization; 6. duplicate URLs do not accumulate; 7. `list -> show` back navigation; 8. `list -> reclamation -> catalog` nested back navigation; 9. separate `nav` tokens do not conflict; 10. fallback works on empty/missing context; 11. POST redirect does not include full `previous_url`; 12. AJAX update returns `204` and no `Location`. ### 15. Rollout Stages #### Stage 1. Infrastructure - create `NavigationContextService`; - add wrappers in base `Controller`; - add tests for service behavior. #### Stage 2. Reclamations - migrate `ReclamationController`; - migrate `resources/views/reclamations/edit.blade.php`; - migrate `resources/views/partials/table.blade.php`; - verify uploads, details, spare parts, and AJAX update flow. #### Stage 3. Orders and Linked Navigation - migrate `OrderController`; - migrate `resources/views/orders/show.blade.php`. #### Stage 4. Catalog and SKU - migrate `ProductController`; - migrate `ProductSKUController`; - migrate: - `resources/views/catalog/edit.blade.php` - `resources/views/products_sku/edit.blade.php` #### Stage 5. Spare Parts - migrate `SparePartController`; - migrate `SparePartOrderController`; - migrate related edit/show templates. #### Stage 6. Legacy Cleanup - remove `previous_url` from links and hidden inputs; - keep temporary compatibility branch only where needed; - later remove `resolvePreviousUrl()` and `previousUrlForRedirect()`. ### 16. Decisions to Confirm Before Coding 1. TTL for navigation context: recommended 24 hours 2. Max contexts per session: recommended 30 3. Max stack size per context: recommended 20 4. Duplicate policy: move existing URL to end instead of duplicating 5. Fallback route per module: must be explicitly defined ### 17. Risks 1. Mixed old/new navigation while migration is incomplete Mitigation: transitional compatibility 2. Reusing one token across unrelated flows Mitigation: create token only at chain entry, then pass it through 3. Cyclic or noisy back navigation Mitigation: normalize URLs and deduplicate stack 4. Wrong fallback target Mitigation: define fallback per module instead of global fallback ### 18. Definition of Done Task is done when: 1. full `previous_url` is no longer passed in links and redirects; 2. `nav` is used in key transitions; 3. nested back flow works at least for: - `reclamations list -> reclamation -> catalog -> sku` 4. URLs no longer grow from filter chains; 5. tests cover nested back navigation and separate tabs/contexts; 6. redirect responses no longer produce oversized `Location` headers.