Skip to content

Commit 573ea07

Browse files
committed
Keep a ready login form when the prime step is dismissed
startFlow assumed discovery was still running and forced the UI into a loading state. When the session had already reached AWAITING_INPUT while the consent step was on screen, that hid a ready form behind a spinner the backend had no further transition to clear. Derive the UI state from the session state we already hold.
1 parent 620f3db commit 573ea07

3 files changed

Lines changed: 57 additions & 1 deletion

File tree

‎.changeset/quiet-moons-return.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@onkernel/managed-auth-react": patch
3+
---
4+
5+
Keep an already-ready login form visible when the consent step is dismissed, instead of replacing it with a loading state that no later event will clear.

‎packages/managed-auth-react/src/session/useManagedAuthSession.test.ts‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,52 @@ describe("useManagedAuthSession initialization", () => {
180180
expect(value!.isInitializing).toBe(false);
181181
});
182182

183+
test("keeps a ready form when the prime step is dismissed", async () => {
184+
let value: ManagedAuthSessionValue | null = null;
185+
let retrieveRequests = 0;
186+
187+
const fetchImpl = (async (
188+
input: RequestInfo | URL,
189+
init?: RequestInit,
190+
): Promise<Response> => {
191+
const url = String(input);
192+
if (url.endsWith("/exchange")) return response({ jwt: "jwt" });
193+
if (url.endsWith("/events")) return new Promise<Response>(() => {});
194+
if (init?.method === "GET") {
195+
retrieveRequests++;
196+
return response(awaitingInputState());
197+
}
198+
throw new Error(`Unexpected request: ${init?.method} ${url}`);
199+
}) as typeof fetch;
200+
201+
function Harness() {
202+
value = useManagedAuthSession({
203+
sessionId: "session-id",
204+
handoffCode: "handoff-code",
205+
fetch: fetchImpl,
206+
});
207+
return null;
208+
}
209+
210+
await act(async () => {
211+
renderer = create(createElement(Harness));
212+
await flushPromises();
213+
});
214+
215+
expect(value!.uiState).toBe("prime");
216+
217+
// Discovery finished while the prime step was on screen. Continuing has
218+
// to show the fields that are already waiting: the session has no further
219+
// transition to publish, so nothing would ever clear a spinner here.
220+
await act(async () => {
221+
value!.startFlow();
222+
await flushPromises();
223+
});
224+
225+
expect(value!.uiState).toBe("awaiting_input");
226+
expect(retrieveRequests).toBe(1);
227+
});
228+
183229
test("leaves initialization when the handoff exchange fails", async () => {
184230
let value: ManagedAuthSessionValue | null = null;
185231
const fetchImpl = (async (_input: RequestInfo | URL, _init?: RequestInit) =>

‎packages/managed-auth-react/src/session/useManagedAuthSession.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,12 @@ export function useManagedAuthSession(
382382

383383
const startFlow = useCallback(() => {
384384
if (!jwt) return;
385-
setUIState("discovering");
385+
// The prime step can outlive discovery: the session may already be
386+
// awaiting input by the time the user clicks through. Derive from the
387+
// state we hold instead of assuming discovery is still running, or a
388+
// ready form is replaced by a spinner with no event left to clear it.
389+
const current = stateRef.current;
390+
setUIState(current ? deriveUIState(current) : "discovering");
386391
connectStream(jwt);
387392
}, [jwt, connectStream]);
388393

0 commit comments

Comments
 (0)