Skip to main content

Navigation Assistance

Navigation Assistance helps an agent answer three questions: where am I, can I get to X from here, and what does it take.

UI Bridge answers them with three distinct mechanisms:

MechanismAnswersEntry point
State-graph pathfinding"Is checkout reachable, and via which transitions?"useUINavigation, useNavigationPath, useCanNavigateTo
Route tracking"What page am I on, and how did I get here?"NavigationTracker, usePageContext, useRouteAwareness
Navigation maps"Do these two apps expose the same navigation?"buildNavigationMap

Note that pathfinding is over the registered state graph, not over URLs. It requires that your app registers states and transitions — see State Machine Integration.

State-Graph Pathfinding​

Find a Path​

useNavigationPath computes the path to a target state set without executing anything. It recomputes when the active states change:

import { useNavigationPath } from '@qontinui/ui-bridge';

function CheckoutHint() {
const path = useNavigationPath(['checkout']);

if (!path.found) {
return <p>Cannot reach checkout from here</p>;
}

return (
<p>
{path.estimatedSteps} steps via {path.transitions.join(' -> ')} (cost {path.totalCost})
</p>
);
}

A PathResult is { found, transitions, totalCost, targetStates, estimatedSteps } — transitions is an array of transition ids, not action descriptors.

Check Reachability​

When you only need a boolean, useCanNavigateTo is the cheap form:

import { useCanNavigateTo } from '@qontinui/ui-bridge';

function DashboardLink() {
const canNavigate = useCanNavigateTo(['dashboard']);
return <button disabled={!canNavigate}>Dashboard</button>;
}

Execute the Path​

useUINavigation bundles pathfinding with execution and exposes the in-flight state:

import { useUINavigation } from '@qontinui/ui-bridge';

function NavigationController() {
const { navigateTo, findPath, isNavigating, activeStates, lastResult, available } =
useUINavigation();

const goToDashboard = async () => {
const path = findPath(['dashboard']);
if (!path.found) return;

const result = await navigateTo(['dashboard']);
console.log(result.success, result.executedTransitions, result.finalActiveStates);
};

return (
<div>
<p>Active: {activeStates.join(', ')}</p>
<button onClick={goToDashboard} disabled={!available || isNavigating}>
Go to Dashboard
</button>
{lastResult?.error && <p role="alert">{lastResult.error}</p>}
</div>
);
}

A NavigationResult is { success, path, executedTransitions, finalActiveStates, error?, durationMs }.

Over HTTP​

POST /control/states/find-path
{ "targetStates": ["checkout"] }

POST /control/states/navigate
{ "targetStates": ["checkout"] }

Both take a targetStates array — multi-target pathfinding is the native shape, not a convenience over a single string. If the registry has no pathfinder wired up, these return NOT_IMPLEMENTED rather than an empty path. Related reads: GET /control/states, GET /control/states/active, GET /control/states/snapshot, GET /control/transitions.

Route Tracking​

NavigationTracker intercepts the History API (pushState / replaceState) and listens for popstate and hashchange, so SPA route changes are recorded with no router integration. UIBridgeProvider installs one for you; construct your own only outside React:

import { NavigationTracker } from '@qontinui/ui-bridge';

const tracker = new NavigationTracker({ maxHistory: 20, observeTitle: true });
tracker.install((data) => console.log('navigated', data));

tracker.getCurrentPage(); // { url, pathname, search, hash, title }
tracker.getRecentNavigations(); // PageNavigationEntry[], most recent last
tracker.getSnapshotPageContext(); // what lands on the control snapshot

tracker.uninstall();

Each navigation also fires a navigation:change bridge event, so a WebSocket subscriber sees route changes in real time.

The tracker knows the URL; it does not know that /tasks/123 is "Task Detail" inside the "Tasks" section. You supply that, and it is where breadcrumbs live:

import { usePageContext, useRouteAwareness } from '@qontinui/ui-bridge';

function TaskDetailPage({ id }: { id: string }) {
usePageContext({
name: 'Task Detail',
section: 'tasks',
breadcrumb: ['Tasks', `Task #${id}`],
});

useRouteAwareness({
// A literal template from the route definition — never the concrete path.
pattern: '/tasks/:id',
patternSource: 'router',
params: { id },
routeStack: ['/', '/tasks', '/tasks/:id'],
});

return <article>…</article>;
}

Both hooks write through to the tracker, and both clear themselves on unmount. The merged result appears as pageContext and route on getSnapshotPageContext(), which is the control snapshot's page. That is the only place the hooks' values go. The semantic snapshot's PageContext does not read the tracker: its pathname / pageName / section / breadcrumb are only what a createSnapshot(controlSnapshot, pageContext) caller passes in. It has no route-pattern field, because a pattern without patternSource cannot be told apart from a concrete path.

There is no automatic breadcrumb inference and no "navigate back N crumbs" helper. Browser history navigation is POST /control/page/back and POST /control/page/forward.

For flows that need "the route change has finished settling" rather than "the URL changed", the tracker exposes an explicit completion signal:

const unsubscribe = tracker.onNavigationComplete((data) => console.log(data));

// from the app, once the destination has rendered:
tracker.markNavigationComplete('/tasks/:id', { itemCount: 42 });

buildNavigationMap is a comparison tool: given the elements of two pages or two apps, it matches their navigation elements by label and reports what is unique to each side. It is the backbone of cross-app parity checks, not a declaration of your app's structure.

import { buildNavigationMap } from '@qontinui/ui-bridge';

const map = buildNavigationMap(sourceElements, targetElements);

console.log(map.pairs); // NavigationPair[] — matched links, with destinationMatch
console.log(map.sourceOnly); // labels present only in the source
console.log(map.targetOnly); // labels present only in the target

Each NavigationPair carries sourceId, targetId, label, the two hrefs and a destinationMatch flag. isNavigationElement is the predicate it filters with, and is exported if you want to reuse it.