--- type: knowledge summary: The sign-in flow end to end, and which layer owns each step — in particular that the broker redirect belongs to @ng-org/web, not to this package --- # How a user gets in ## The steps, and who owns them A newcomer holds nothing. To act they need a wallet imported once into the wallet application, and an identifier naming their virtual space. The `barrier` hands out the first three — the wallet file, its password, a link to the wallet application — and takes the fourth. Then the page is handed to the broker, which opens the wallet and reloads the application **inside an iframe it hosts**. Outside that iframe there is no session. **The redirect is not ours.** `@ng-org/web`'s `init()` performs it as its first statement — same host, same URL shape, same frame check. Verified in its shipped bundle: when `window.self === window.top` it assigns `window.location.href` from a hard-coded `redirect` constant. This package must not reimplement it; doing so would double a function of the layer below and diverge from it in the meantime (see `app-contract/rule_no-divergence-from-nextgraph`). What is genuinely ours is the one thing `init()` cannot do: **put the identifier into the URL before `init()` reads it**. `init()` hands over `window.location.href`; it does not know the parameter exists. ## When the `barrier` shows, and why it is not a question of identity **Top-level: always. Inside the broker iframe: never** — there the identifier arrives in the URL and the page stands aside. The discriminator is the frame, not whether an identity is known, and the difference is not ergonomic. An identity is state we can observe; whether the person still has a wallet in this browser is state we cannot — it lives in another origin's storage. A screen that decides on the identity is therefore *guessing* at the state that matters, and when it guesses "already set up" while NextGraph's side is gone, it hides the only controls that could repair the situation. What that costs is not a degraded experience but a dead end: sent to the broker without a wallet, the person lands on a static page carrying **no return path to the application**. The browser's Back button is the only way out, and it only helps if the `barrier` is there to catch them on arrival. A known identifier therefore **prefills the field; it never skips the screen**. And nothing detects whether the wallet is already there: all steps are always shown, and whoever already has one ignores them. No checkbox, no "I already have it" — knowing whether you imported a wallet into this browser is not something a person can be asked. Two facts, observed on the live sites, close the alternatives: clicking the wallet application's import control opens a file chooser **in place** — no navigation, no new tab; and our own page receives **no signal at all** when the import succeeds (no opener, no message, no storage or focus event, no reload). Detecting the return is impossible, not merely fragile. ## Why the identifier travels by URL The flow runs in **two contexts with separate storage partitions** — the top-level page and the broker iframe — because browsers partition storage by top-level site. A value written top-level is not the value the iframe reads. What crosses is the URL: the redirect embeds the full application URL, query included, and reloads it in the iframe. Hence the resolution order, which must not be "simplified": 1. the identifier in the URL — it wins whenever present, being the only thing that crosses; 2. otherwise storage — same-partition convenience, and prefill on reload. The parameter must therefore be written on **every** path that settles an identity, not only when the `barrier` asked. An identity coming from storage without the parameter being written is the silent failure: the iframe reads an empty identity, provisions a second virtual space, and the returning user lands somewhere empty with no error. Writing to storage and writing to the address bar are two independent attempts, deliberately: a storage that refuses writes must not cost the round-trip its parameter. ## What survives migration The `barrier` evaporates whole — handing out a shared wallet and naming a space exist only because the wallet is shared. The redirect does **not**: upstream's real multi-wallet flow has the same shape, and the barrier becomes the real per-user sign-in. The call site survives too, which is why the published call takes no identifier.