KQ-1710 · RECOVERY/ROUTE

Recovery Route

An account recovery chooser drawn as a map: you on one side, the ways back in on the other — backup code, recovery email, trusted contact, support review — each with how long it takes. Choosing one draws the route from you to its card, revealed along its own length by a mask whose stroke runs on springs.glide with a traveller riding the leading point, and the card's notch answers with a ring on springs.snap; the steps slide in 16px from the side the choice came from under an honest wait counted from now. The methods are a real radio group — arrows choose and skip the ones not set up — and below 44rem of container the route runs down a gutter beside the list.

KQ-1710 · RECOVERY/ROUTE

Locked out of mara@fieldline.io?

Choose the way back in. Each one says how long it takes.

Youmara@fieldline.io

Recovery email

Expected wait: about 5 minutes · by today 09:35

  1. Send a linkTo m••••@coldbrook.co. It works for 30 minutes.
  2. Open it on this deviceThe link signs you in and asks for a new password.

None of these work?

Any eight letters work as a backup code the second time

route · Recovery email · ~5 min

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/recovery-route

Props

methods
RecoveryMethod[]
default defaultRecoveryMethods
The ways back in: { id, kind, label, detail?, wait, waitMinutes?, target?, steps?, available?, reason? }.
account
{ name, email }
default defaultRecoveryAccount
The locked account, named in the heading and on your node.
now
number
default defaultRecoveryNow
The clock (epoch ms) every "expected by" is counted from.
value / defaultValue
string | null
default — / null
The chosen method, controlled or initial.
onValueChange
(id) => void
default —
Fires with the id from the press or arrow that chose a method.
onChoose
(method) => void
default —
Fires with the whole method from the press or arrow that chose it.
onSubmit
(method, { stage, code?, message? }) => void | Promise<void>
default —
A step was submitted. A promise holds its button; resolving advances the step, a rejection keeps it and what was typed, with the message.
onContactSupport
() => void
default —
"Contact support" was pressed under everything.
status
"ready" | "loading" | "error"
default "ready"
Skeleton cards while loading; a message and Try again on error.
onRetry
() => void
default —
Try again was pressed after loading failed.
routeStyle
"curve" | "dotted" | "metro"
default "curve"
A smooth curve, round dots, or an orthogonal metro line with a rail down its middle.
density
"compact" | "cozy" | "roomy"
default "cozy"
Card padding, gaps and type size.
tone
"calm" | "warm" | "plain"
default "calm"
The route, your node's ring, the chosen card's edge and the buttons: cobalt, amber or ink.
formatTime
(at, now) => string
default recoveryTime (UTC)
Writes an expected time; the default reads "today 09:35" or "Thu 09:30" in UTC so server and browser agree.
title
string
default "Locked out of {email}?"
The heading.
maxHeight
number
default 560
Tallest the panel grows, in px, before its body scrolls.
disabled
boolean
default false
Nothing can be chosen or submitted.
className
string
default —
Classes for the wrapper.