Status: backlog Priority: P2 Created: 2026-02-11 Owner: conductor
Browser back/forward navigation creates unique challenges when combined with async state entry. When a user presses "back", the browser immediately updates the URL and fires a popstate event. The routing system must then transition the statechart to match the new URL — but if that transition requires async I/O (loading data), there is a window where the URL and application state are out of sync.
The existing busy? guard and undo-url-change mechanism (ui_routes.cljc:326-358) partially address this, but they were designed for synchronous state transitions and don't account for:
istate child chart teardown/startup during navigationpopstate fires, the system must transition to the URL's target state using the async processor. The URL has already changed (browser did it), so no history manipulation is needed — just state restoration.busy?, the URL must be reverted to match the current state. This already partially works via undo-url-change but must be verified with the async processor.popstate events must be queued, not processed concurrently. The system must not start entering state B while still entering state A.[:routing/loading?] or a dedicated state in the routing statechart.:routing/transitioning state (or similar) that is active while an async transition is in flight.istate route tears down the invoked child chart (exit-states! handles this). If the child chart's teardown is async, back/forward must wait for it to complete.istate route via back/forward starts the child chart's invocation. This follows the same async flow as forward navigation.process-event! calls on the same session.integration/fulcro/ui_routes.cljc — undo-url-change, busy?, apply-external-route, routing statechart structureintegration/fulcro/route_history.cljc — popstate listener, history state trackingintegration/fulcro.cljc — Event processing serialization for asyncalgorithms/v20150901_async_impl.cljc — No changes expected (handles async naturally)invocation/statechart.cljc — Async teardown/startup of child chartsThe Fulcro integration's send! must serialize event processing for a given session. When using the async processor, process-event! may return a promise. A subsequent send! for the same session must wait for the previous promise to resolve before processing.
Implementation options:
send! appends to the chain via p/thenThe core.async event loop (async_event_loop.cljc) may already provide this serialization — verify.
Extend the routing statechart to include transition states:
:routing/idle ──[route-to.*]──> :routing/transitioning ──[transition-complete]──> :routing/idle
│
├──[transition-failed]──> :routing/error ──> :routing/idle
└──[route-to.*]──> (queue/replace pending transition)
This gives the UI a clear signal for loading indicators and prevents concurrent transitions.
1. popstate fires → URL is already at target
2. System attempts transition (async)
3a. Success → URL stays, state matches ✓
3b. Failure → replace-url! back to previous state's URL
3c. Busy → replace-url! back to current state's URL (existing behavior)
When a new popstate arrives while a transition is in progress:
pending-navigation-target — each new popstate overwrites it.:routing/transitioning state necessary, or can loading status be derived from the event processing promise?busy? guard? If transitioning to state A is denied, but then back to state B is requested, should B be attempted?busy? denial reverts URL correctly with async processorprocess-event! calls on the same sessionistate child chart teardown completes before new state entry beginsCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |