navigation-context-plan.md 9.7 KB

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:

?nav=abc123xyz

In session:

[
    '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:

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:

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:

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:

/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:

$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:

$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.