A context engine for your frontend.
Let an AI agent use your app — safely.
It gives an LLM your app’s own account of where it is, what it can do and what happened — and says so when it does not know. The agent operates your real screens; your rules stay in charge.
The agent works your real screens as the signed-in user, inside the permissions they already have. Nothing new to open up, nothing rewritten. What it does is recorded — and what it could not do is recorded too.
In one end-to-end example run, the agent typed, was stopped for approval, created, navigated, and configured.
A human interacts, then navigates — every step recorded.The agent takes over — where the human stopped.It fills the form.Agent navigates. Same map, same rules.Agent interacts — recorded, attributed.A consequential step — it stops and asks.Approved. One journey, two travellers.
Pages are places. Routes are roads. hcifootprint reads the map your app already has — its route table — and draws nothing you did not declare.
The map comes from the route table you already ship. No new backend, no re-architecture, no second source of truth to keep in step with the first.
A traveller walks. Each step is written as it happens: where it started, where it went, what it touched. The trail is the transcript — there is no second version of events.
Every step is written as it happens, by the app itself. When someone asks what the agent did on a customer’s account, the answer is a record — not a reconstruction from logs after the fact.
The model is handed the four things it can do here. Not the four hundred it could do somewhere else. Nobody hands a tourist every restaurant in the country.
Only what is possible here is sent. On our own demo that is 6.1× fewer tokens per turn than handing over the page’s markup — measured, with the script in the repo so you can run it against your own app rather than take our word for it.
Rules, validation and routing stay exactly where they are. hcifootprint sits outside them: it observes, it reports, it refuses. Nothing is injected. Remove it and the app is untouched.
The agent acts as the signed-in user, through the buttons and permissions they already have. No new endpoints, no new grants. Remove the library and your app is exactly as it was.
The agent reaches for something that was never declared. There is no fallback, no improvised click, no cheerful summary. A row appears: what it wanted, why it couldn’t. Honest absence is the feature.
When the agent cannot do something, that is recorded as a gap the moment it happens — with what it wanted and why it failed. The alternative is a confident summary of work nobody did.
Allow, always allow, or prevent — with the receipts attached. What it read. What it intends to write. Observed on screen, never inferred. Answer the card on the map; the journey waits for you.
Before anything consequential, a person sees exactly what will happen and answers. The library will not take the agent’s word that someone approved: the yes has to point at a decision a human actually recorded.
It grew from three things your app already has: the router, the journeys you name, the controls you expose. Three plugs snap in. Everything above was drawn from them.
Start with the routes you already have and get a map in an afternoon. Add actions when you want the agent to act, and the trust layer when it touches something that matters. Each layer stands on its own.
This is a real integration, rewritten against the current library and compiled before it was published here. It reads like a description of the app rather than a description of the library.
Three declared sources, one session, one navigate. There is no wrapper to keep in sync and no generated file to regenerate.
01 import { buildNavigationGraph, fromRoutes, fromJourneys, fromLiveStore, type LiveActionStore } from 'hcifootprint'; 02 import { ROUTES, router } from './router.js'; 03 import { JOURNEYS } from './funnels.js'; 04 import { legacyStore } from './actionStore.js'; 05 06 // Publish the controls the app already shows. 07 const controls: LiveActionStore = { 08 subscribe: (onChange) => legacyStore.on('change', onChange), 09 actions: () => 10 legacyStore.visibleControls().map((c) => ({ 11 node: c.pageId, 12 name: c.id, 13 does: c.label, 14 handler: c.perform, 15 enabled: !c.disabled, 16 })), 17 }; 18 19 // The router table and the journey list ARE the graph. 20 export const graph = buildNavigationGraph('poc-app', { 21 sources: [fromRoutes(ROUTES), fromJourneys(JOURNEYS), fromLiveStore(controls)], 22 }); 23 24 // One session, driving the app's real router. 25 export const session = graph.createSession({ 26 node: 'home', 27 navigate: (href) => router.push(href), 28 });
the same integration, rewritten and compile-checked
measured against this release: fromRoutes, minified and gzipped — what one graph source adds to a bundle
a version migration, reported by a production integration