From 7f235f81595d39bd79029946ae915ca82d4bae0b Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Thu, 25 Jun 2026 20:23:32 +0800 Subject: [PATCH 01/11] fix(reborn): keep approval gates visible on busy sends --- .../static/js/pages/chat/hooks/useChat.js | 19 ++- .../js/pages/chat/lib/useChat-send.test.mjs | 86 ++++++++++ tests/e2e/helpers.py | 2 + .../scenarios/test_reborn_webui_v2_smoke.py | 152 ++++++++++++++++++ 4 files changed, 255 insertions(+), 4 deletions(-) diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js index f06b6a05583..21fc7c41270 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js @@ -31,6 +31,7 @@ import { useSSE } from "./useSSE.js"; const AUTH_TOKEN_FLOW_TIMEOUT_MS = 30000; const AUTH_GATE_CREDENTIAL_STORED_ERROR = "credential_stored_gate_resolution_failed"; +const APPROVAL_GATE_PENDING_SEND_ERROR = "approval_gate_pending_send_blocked"; const OAUTH_CALLBACK_CHANNEL = "ironclaw-product-auth"; const OAUTH_CALLBACK_STORAGE_KEY = "ironclaw:product-auth:oauth-complete"; const OAUTH_CALLBACK_MESSAGE_TYPE = "ironclaw:product-auth:oauth-complete"; @@ -52,6 +53,14 @@ function credentialStoredGateResolutionError(cause) { return error; } +function approvalGatePendingSendError() { + const error = new Error( + "Resolve the approval request before sending another message.", + ); + error.safeErrorCode = APPROVAL_GATE_PENDING_SEND_ERROR; + return error; +} + function threadNeedsSidebarRefresh(threadId) { const cached = queryClient.getQueryData?.(["threads"]); const threads = cached?.threads; @@ -344,6 +353,10 @@ export function useChat(threadId) { const wireAttachments = stagedAttachments.map(toWireAttachment); const renderAttachments = stagedAttachments.map(toRenderAttachment); + if (pendingGate) { + throw approvalGatePendingSendError(); + } + // Channel-connect slash commands ("/connect telegram") never carry // attachments; skip that detection when files are staged so an // upload is never misread as a command and dropped. @@ -399,9 +412,7 @@ export function useChat(threadId) { }; updateCurrentThread((prev) => [...prev, pendingRenderMessage]); - if (sendThreadId !== threadId) { - seedThreadMessages(sendThreadId, (prev) => [...prev, pendingRenderMessage]); - } + updateSeededTarget((prev) => [...prev, pendingRenderMessage]); updateCurrentRunState(() => { setIsProcessing(true); @@ -505,7 +516,7 @@ export function useChat(threadId) { removePending(pendingMessagesRef.current, pendingKey, optimisticId); } }, - [threadId, setMessages, seedThreadMessages], + [threadId, pendingGate, setMessages, seedThreadMessages], ); // v2 resolveGate signature: `(resolution, { always?, credentialRef? })`. diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs index cfe8d37c4d8..978761f8c00 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs @@ -559,6 +559,92 @@ test("useChat.send: target-thread thrown errors update seeded cache", async () = assert.deepEqual(stateUpdates.filter((update) => update.index === 4), []); }); +test("useChat.send: pending approval blocks before sendMessage", async () => { + const threadId = "thread-approval"; + const pendingGate = { + runId: "run-gated", + gateRef: "gate-shell", + kind: "gate", + toolName: "builtin.shell", + }; + const stateUpdates = []; + let sendCalls = 0; + let renderedMessages = []; + + const context = { + AbortController, + Date, + Error, + Map, + Math, + React: createReactStub({ + initialByIndex: new Map([ + [4, false], + [5, pendingGate], + ]), + setCalls: stateUpdates, + }), + addPending, + toRenderAttachment, + toWireAttachment, + cancelRunRequest: async () => {}, + clearTimeout, + createThreadRequest: async () => { + throw new Error("thread should already exist"); + }, + globalThis: {}, + listConnectableChannels: async () => { + throw new Error("approval-gated sends should not fetch connectable channels"); + }, + looksLikeChannelConnectCommand, + queryClient: { + fetchQuery: async () => { + throw new Error("approval-gated sends should not fetch connectable channels"); + }, + invalidateQueries: () => {}, + }, + recordAcceptedMessageRef, + removePending, + resolveChannelConnectCommand, + resolveGateRequest: async () => {}, + sendMessage: async () => { + sendCalls += 1; + throw new Error("sendMessage should not be called"); + }, + setInterval, + setTimeout, + submitManualToken: async () => {}, + useChatEvents: () => () => {}, + useHistory: () => ({ + messages: renderedMessages, + hasMore: false, + nextCursor: null, + isLoading: false, + loadHistory: () => {}, + seedThreadMessages: () => {}, + setMessages: (updater) => { + renderedMessages = + typeof updater === "function" ? updater(renderedMessages) : updater; + }, + }), + useSSE: () => ({ status: "idle" }), + }; + + runUseChatSource(context); + + const chat = context.globalThis.__testExports.useChat(threadId); + await assert.rejects( + chat.send("new request while approval is open"), + (error) => + error?.safeErrorCode === "approval_gate_pending_send_blocked" && + /Resolve the approval request/.test(error.message), + ); + + assert.equal(sendCalls, 0); + assert.deepEqual(renderedMessages, []); + assert.deepEqual(stateUpdates, []); +}); + test("useChat.cancelRun clears local state before cancel request resolves", async () => { const threadId = "thread-1"; const stateUpdates = []; diff --git a/tests/e2e/helpers.py b/tests/e2e/helpers.py index acd05ba3754..867b71ab870 100644 --- a/tests/e2e/helpers.py +++ b/tests/e2e/helpers.py @@ -235,6 +235,8 @@ "typing_indicator": "[data-testid='typing-indicator']", "msg_user": "[data-testid='msg-user']", # user message bubble "msg_assistant": "[data-testid='msg-assistant']", # assistant message bubble + "msg_system": "[data-testid='msg-system']", # system notice bubble + "approval_card": "text=Approval required", # approval gate card headline # Download chip for an agent-produced workspace file; `{path}` selects one. # Clicking a chip opens the shared attachment preview modal, whose footer # carries the Download action. diff --git a/tests/e2e/scenarios/test_reborn_webui_v2_smoke.py b/tests/e2e/scenarios/test_reborn_webui_v2_smoke.py index 162ae0b1482..fc1fb05c1cc 100644 --- a/tests/e2e/scenarios/test_reborn_webui_v2_smoke.py +++ b/tests/e2e/scenarios/test_reborn_webui_v2_smoke.py @@ -374,6 +374,158 @@ async def test_reborn_v2_composer_accepts_draft_while_run_is_processing(reborn_v await expect(reborn_v2_page.locator(SEL_V2["msg_user"])).to_have_count(1, timeout=1000) +async def test_reborn_v2_approval_gate_blocks_composer_send( + reborn_v2_server, reborn_v2_browser +): + """An open approval gate shows the warning and blocks new sends locally.""" + thread_id = "thread-approval-blocked" + send_requests: list[dict] = [] + context = await reborn_v2_browser.new_context(viewport={"width": 1280, "height": 720}) + page = await context.new_page() + + await page.add_init_script( + """ + (() => { + const streams = []; + class FakeEventSource extends EventTarget { + constructor(url) { + super(); + this.url = url; + this.readyState = 0; + streams.push(this); + setTimeout(() => { + this.readyState = 1; + if (typeof this.onopen === "function") this.onopen(new Event("open")); + }, 0); + } + close() { + this.readyState = 2; + } + } + window.EventSource = FakeEventSource; + window.__emitV2Sse = (type, frame, id = "cursor-1") => { + const stream = streams[streams.length - 1]; + if (!stream) throw new Error("no EventSource stream is open"); + const event = new MessageEvent(type, { + data: JSON.stringify({ type, ...frame }), + lastEventId: id, + }); + stream.dispatchEvent(event); + }; + })(); + """ + ) + + async def fulfill_json(route, body, status=200): + await route.fulfill( + status=status, + content_type="application/json", + body=json.dumps(body), + ) + + async def handle_session(route): + await fulfill_json( + route, + { + "tenant_id": "reborn-v2-e2e", + "user_id": USER_ID, + "capabilities": {}, + "features": {"reborn_projects": False}, + "attachments": { + "accept": ["text/plain"], + "max_files_per_message": 4, + "max_bytes_per_file": 1048576, + "max_bytes_per_message": 4194304, + }, + }, + ) + + async def handle_threads(route): + await fulfill_json( + route, + { + "threads": [ + { + "thread_id": thread_id, + "title": "Approval blocked regression", + "created_at": "2026-06-25T00:00:00Z", + "updated_at": "2026-06-25T00:00:00Z", + } + ], + "next_cursor": None, + }, + ) + + async def handle_timeline(route): + await fulfill_json( + route, + { + "messages": [ + { + "message_id": "seed-user", + "kind": "user", + "content": "trigger approval", + "sequence": 1, + "status": "accepted", + "created_at": "2026-06-25T00:00:00Z", + } + ], + "next_cursor": None, + }, + ) + + async def handle_send(route): + send_requests.append(json.loads(route.request.post_data or "{}")) + await fulfill_json(route, {"thread_id": thread_id}, status=202) + + await page.route("**/api/webchat/v2/session", handle_session) + await page.route("**/api/webchat/v2/threads", handle_threads) + await page.route(f"**/api/webchat/v2/threads/{thread_id}/timeline**", handle_timeline) + await page.route(f"**/api/webchat/v2/threads/{thread_id}/messages", handle_send) + + try: + await page.goto(f"{reborn_v2_server}/v2/chat/{thread_id}?token={REBORN_V2_AUTH_TOKEN}") + await expect(page.locator(SEL_V2["chat_composer"])).to_be_visible(timeout=15000) + await expect(page.locator(SEL_V2["msg_user"]).first).to_contain_text( + "trigger approval", timeout=15000 + ) + + await page.evaluate( + """ + () => window.__emitV2Sse("gate", { + prompt: { + turn_run_id: "run-gated", + gate_ref: "gate-shell", + invocation_id: "invoke-shell", + headline: "Approval required", + body: "Allow shell to inspect the workspace?", + allow_always: false, + approval_context: { + tool_name: "builtin.shell", + reason: "Allow shell to inspect the workspace?", + action: { label: "Run command" }, + destination: { label: "Local workspace" }, + details: [{ label: "Command", value: "pwd" }] + } + } + }) + """ + ) + + await expect(page.locator(SEL_V2["approval_card"]).first).to_be_visible(timeout=5000) + await expect( + page.get_by_text("Resolve the approval request before sending another message.") + ).to_be_visible(timeout=5000) + + composer = page.locator(SEL_V2["chat_composer"]) + await composer.fill("new message while approval is open") + await composer.press("Enter") + await expect(page.locator(SEL_V2["msg_user"])).to_have_count(1, timeout=1000) + assert send_requests == [] + finally: + await context.close() + + async def test_reborn_v2_desktop_sidebar_can_collapse_and_persist(reborn_v2_page): """Desktop users can collapse the sidebar, and the preference survives reload.""" sidebar = reborn_v2_page.locator(SEL_V2["sidebar"]) From ed4e20899f05f056b5bf94e16d494b7aea7272f6 Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Thu, 25 Jun 2026 23:22:52 +0800 Subject: [PATCH 02/11] fix(webui): stabilize approval gate send state --- .../js/pages/chat/components/approval-card.js | 5 +- .../static/js/pages/chat/hooks/useChat.js | 29 ++- .../js/pages/chat/lib/useChat-send.test.mjs | 227 ++++++++++++++++-- tests/e2e/helpers.py | 2 +- 4 files changed, 239 insertions(+), 24 deletions(-) diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.js index 0202ed87648..b1a4c31e026 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.js @@ -62,7 +62,10 @@ export function ApprovalCard({ gate, onApprove, onDeny, onAlways }) { }, [always, allowAlways, onAlways, onApprove]); return html` -
+
<${Icon} name="lock" className="h-4 w-4" /> diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js index 21fc7c41270..12bcf28065d 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js @@ -184,7 +184,16 @@ export function useChat(threadId) { } = useHistory(threadId, { getPendingMessages, setPendingMessages }); const [isProcessing, setIsProcessing] = React.useState(false); - const [pendingGate, setPendingGate] = React.useState(null); + const [pendingGate, setPendingGateState] = React.useState(null); + const pendingGateRef = React.useRef(pendingGate); + const setPendingGate = React.useCallback((next) => { + const current = pendingGateRef.current; + const value = + typeof next === "function" ? next(current) : next; + if (Object.is(value, current)) return; + pendingGateRef.current = value; + setPendingGateState(value); + }, []); const [stateThreadId, setStateThreadId] = React.useState(threadId); const toolActivityStateRef = React.useRef(createToolActivityState()); const locallyResolvedGatesRef = React.useRef(new Map()); @@ -224,11 +233,15 @@ export function useChat(threadId) { if (stateThreadId !== threadId) { setStateThreadId(threadId); setIsProcessing(false); - setPendingGate(null); + setPendingGateState(null); setActiveRunState(null); setChannelConnectAction(null); } + React.useEffect(() => { + pendingGateRef.current = pendingGate; + }, [pendingGate]); + React.useEffect(() => { resetToolActivityState(toolActivityStateRef); locallyResolvedGatesRef.current.clear(); @@ -353,7 +366,7 @@ export function useChat(threadId) { const wireAttachments = stagedAttachments.map(toWireAttachment); const renderAttachments = stagedAttachments.map(toRenderAttachment); - if (pendingGate) { + if (pendingGateRef.current) { throw approvalGatePendingSendError(); } @@ -380,6 +393,10 @@ export function useChat(threadId) { } } + if (pendingGateRef.current) { + throw approvalGatePendingSendError(); + } + const pendingKey = sendThreadId; const pendingRecord = { id: `pending-${pendingSeqRef.current++}`, @@ -416,7 +433,9 @@ export function useChat(threadId) { updateCurrentRunState(() => { setIsProcessing(true); - setPendingGate(null); + if (!pendingGateRef.current) { + setPendingGate(null); + } }); try { @@ -516,7 +535,7 @@ export function useChat(threadId) { removePending(pendingMessagesRef.current, pendingKey, optimisticId); } }, - [threadId, pendingGate, setMessages, seedThreadMessages], + [threadId, setMessages, seedThreadMessages], ); // v2 resolveGate signature: `(resolution, { always?, credentialRef? })`. diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs index 978761f8c00..8708b15d64e 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs @@ -52,7 +52,11 @@ function runUseChatSource(context) { vm.runInNewContext(useChatSourceForTest(), context); } -function createReactStub({ initialByIndex = new Map(), setCalls = [] } = {}) { +function createReactStub({ + initialByIndex = new Map(), + setCalls = [], + stateSlots = new Map(), +} = {}) { let stateIndex = 0; return { useCallback: (fn) => fn, @@ -60,16 +64,19 @@ function createReactStub({ initialByIndex = new Map(), setCalls = [] } = {}) { useRef: (value) => ({ current: value }), useState: (initial) => { const index = stateIndex++; - let value = initialByIndex.has(index) - ? initialByIndex.get(index) - : typeof initial === "function" - ? initial() - : initial; + const slot = stateSlots.get(index) || { + value: initialByIndex.has(index) + ? initialByIndex.get(index) + : typeof initial === "function" + ? initial() + : initial, + }; + stateSlots.set(index, slot); return [ - value, + slot.value, (next) => { - value = typeof next === "function" ? next(value) : next; - setCalls.push({ index, value }); + slot.value = typeof next === "function" ? next(slot.value) : next; + setCalls.push({ index, value: slot.value }); }, ]; }, @@ -560,7 +567,7 @@ test("useChat.send: target-thread thrown errors update seeded cache", async () = }); test("useChat.send: pending approval blocks before sendMessage", async () => { - const threadId = "thread-approval"; + const threadId = "thread-1"; const pendingGate = { runId: "run-gated", gateRef: "gate-shell", @@ -568,7 +575,6 @@ test("useChat.send: pending approval blocks before sendMessage", async () => { toolName: "builtin.shell", }; const stateUpdates = []; - let sendCalls = 0; let renderedMessages = []; const context = { @@ -594,12 +600,12 @@ test("useChat.send: pending approval blocks before sendMessage", async () => { }, globalThis: {}, listConnectableChannels: async () => { - throw new Error("approval-gated sends should not fetch connectable channels"); + throw new Error("ordinary prompts should not fetch connectable channels"); }, looksLikeChannelConnectCommand, queryClient: { fetchQuery: async () => { - throw new Error("approval-gated sends should not fetch connectable channels"); + throw new Error("ordinary prompts should not fetch connectable channels"); }, invalidateQueries: () => {}, }, @@ -608,7 +614,6 @@ test("useChat.send: pending approval blocks before sendMessage", async () => { resolveChannelConnectCommand, resolveGateRequest: async () => {}, sendMessage: async () => { - sendCalls += 1; throw new Error("sendMessage should not be called"); }, setInterval, @@ -634,15 +639,203 @@ test("useChat.send: pending approval blocks before sendMessage", async () => { const chat = context.globalThis.__testExports.useChat(threadId); await assert.rejects( - chat.send("new request while approval is open"), + chat.send("another request"), (error) => error?.safeErrorCode === "approval_gate_pending_send_blocked" && /Resolve the approval request/.test(error.message), ); - assert.equal(sendCalls, 0); - assert.deepEqual(renderedMessages, []); assert.deepEqual(stateUpdates, []); + assert.equal(renderedMessages.length, 0); +}); + +test("useChat.send: accepted send does not clear a gate received while in flight", async () => { + const threadId = "thread-1"; + const replacementGate = { + runId: "run-replacement", + gateRef: "gate-replacement", + kind: "gate", + toolName: "nearai.web_search", + }; + const stateUpdates = []; + const stateSlots = new Map(); + let renderedMessages = []; + let setPendingGateFromEvents = null; + const context = { + AbortController, + Date, + Error, + Map, + Math, + React: createReactStub({ + initialByIndex: new Map([ + [4, false], + [5, null], + ]), + setCalls: stateUpdates, + stateSlots, + }), + addPending, + toRenderAttachment, + toWireAttachment, + cancelRunRequest: async () => {}, + clearTimeout, + createThreadRequest: async () => { + throw new Error("thread should already exist"); + }, + globalThis: {}, + listConnectableChannels: async () => { + throw new Error("ordinary prompts should not fetch connectable channels"); + }, + looksLikeChannelConnectCommand, + queryClient: { + fetchQuery: async () => { + throw new Error("ordinary prompts should not fetch connectable channels"); + }, + invalidateQueries: () => {}, + }, + recordAcceptedMessageRef, + removePending, + timelineMessageIdFromAcceptedRef, + resolveChannelConnectCommand, + resolveGateRequest: async () => {}, + sendMessage: async () => { + setPendingGateFromEvents(replacementGate); + return { + accepted_message_ref: "msg:message-accepted", + run_id: "run-accepted", + status: "queued", + thread_id: threadId, + }; + }, + setInterval, + setTimeout, + submitManualToken: async () => {}, + useChatEvents: ({ setPendingGate }) => { + setPendingGateFromEvents = setPendingGate; + return () => {}; + }, + useHistory: () => ({ + messages: renderedMessages, + hasMore: false, + nextCursor: null, + isLoading: false, + loadHistory: () => {}, + seedThreadMessages: () => {}, + setMessages: (updater) => { + renderedMessages = + typeof updater === "function" ? updater(renderedMessages) : updater; + }, + }), + useSSE: () => ({ status: "idle" }), + }; + + runUseChatSource(context); + + const chat = context.globalThis.__testExports.useChat(threadId); + await chat.send("accepted after gate changed"); + + assert.deepEqual( + stateUpdates.filter((call) => call.index === 5).map((call) => call.value), + [replacementGate], + "non-busy success must not clear a gate that changed while send was in flight", + ); + assert.equal(stateSlots.get(5).value, replacementGate); +}); + +test("useChat.send: rejected busy does not clear a gate received while in flight", async () => { + const threadId = "thread-1"; + const replacementGate = { + runId: "run-replacement", + gateRef: "gate-replacement", + kind: "gate", + toolName: "nearai.web_search", + }; + const stateUpdates = []; + const stateSlots = new Map(); + let renderedMessages = []; + let setPendingGateFromEvents = null; + + const context = { + AbortController, + Date, + Error, + Map, + Math, + React: createReactStub({ + initialByIndex: new Map([ + [4, false], + [5, null], + ]), + setCalls: stateUpdates, + stateSlots, + }), + addPending, + toRenderAttachment, + toWireAttachment, + cancelRunRequest: async () => {}, + clearTimeout, + createThreadRequest: async () => { + throw new Error("thread should already exist"); + }, + globalThis: {}, + listConnectableChannels: async () => { + throw new Error("ordinary prompts should not fetch connectable channels"); + }, + looksLikeChannelConnectCommand, + queryClient: { + fetchQuery: async () => { + throw new Error("ordinary prompts should not fetch connectable channels"); + }, + invalidateQueries: () => {}, + }, + recordAcceptedMessageRef, + removePending, + timelineMessageIdFromAcceptedRef, + resolveChannelConnectCommand, + resolveGateRequest: async () => {}, + sendMessage: async () => { + setPendingGateFromEvents(replacementGate); + return { + outcome: "rejected_busy", + accepted_message_ref: "msg:busy-message-1", + notice: "Thread is busy, please try again.", + thread_id: threadId, + }; + }, + setInterval, + setTimeout, + submitManualToken: async () => {}, + useChatEvents: ({ setPendingGate }) => { + setPendingGateFromEvents = setPendingGate; + return () => {}; + }, + useHistory: () => ({ + messages: renderedMessages, + hasMore: false, + nextCursor: null, + isLoading: false, + loadHistory: () => {}, + seedThreadMessages: () => {}, + setMessages: (updater) => { + renderedMessages = + typeof updater === "function" ? updater(renderedMessages) : updater; + }, + }), + useSSE: () => ({ status: "idle" }), + }; + + runUseChatSource(context); + + const chat = context.globalThis.__testExports.useChat(threadId); + await chat.send("busy after gate changed"); + + assert.deepEqual( + stateUpdates.filter((call) => call.index === 5).map((call) => call.value), + [replacementGate], + "busy rejection must leave a concurrently replaced gate untouched", + ); + assert.equal(stateSlots.get(5).value, replacementGate); }); test("useChat.cancelRun clears local state before cancel request resolves", async () => { diff --git a/tests/e2e/helpers.py b/tests/e2e/helpers.py index 867b71ab870..073954e2b73 100644 --- a/tests/e2e/helpers.py +++ b/tests/e2e/helpers.py @@ -236,7 +236,7 @@ "msg_user": "[data-testid='msg-user']", # user message bubble "msg_assistant": "[data-testid='msg-assistant']", # assistant message bubble "msg_system": "[data-testid='msg-system']", # system notice bubble - "approval_card": "text=Approval required", # approval gate card headline + "approval_card": "[data-testid='approval-card']", # approval gate card # Download chip for an agent-produced workspace file; `{path}` selects one. # Clicking a chip opens the shared attachment preview modal, whose footer # carries the Download action. From 47307e111f09b517175f58358370891b77e08e67 Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Thu, 25 Jun 2026 23:25:08 +0800 Subject: [PATCH 03/11] test(webui): cover late approval gate busy send --- .../js/pages/chat/lib/useChat-send.test.mjs | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs index 8708b15d64e..88c7fc38b5b 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs @@ -838,6 +838,101 @@ test("useChat.send: rejected busy does not clear a gate received while in flight assert.equal(stateSlots.get(5).value, replacementGate); }); +test("useChat.send: rejected busy keeps a gate received after callback creation", async () => { + const threadId = "thread-1"; + const pendingGate = { + runId: "run-gated", + gateRef: "gate-shell", + kind: "gate", + toolName: "builtin.shell", + }; + const stateUpdates = []; + const stateSlots = new Map(); + let renderedMessages = []; + let setPendingGateFromEvents = null; + + const context = { + AbortController, + Date, + Error, + Map, + Math, + React: createReactStub({ + initialByIndex: new Map([ + [4, false], + [5, null], + ]), + setCalls: stateUpdates, + stateSlots, + }), + addPending, + toRenderAttachment, + toWireAttachment, + cancelRunRequest: async () => {}, + clearTimeout, + createThreadRequest: async () => { + throw new Error("thread should already exist"); + }, + globalThis: {}, + listConnectableChannels: async () => { + throw new Error("ordinary prompts should not fetch connectable channels"); + }, + looksLikeChannelConnectCommand, + queryClient: { + fetchQuery: async () => { + throw new Error("ordinary prompts should not fetch connectable channels"); + }, + invalidateQueries: () => {}, + }, + recordAcceptedMessageRef, + removePending, + timelineMessageIdFromAcceptedRef, + resolveChannelConnectCommand, + resolveGateRequest: async () => {}, + sendMessage: async () => ({ + outcome: "rejected_busy", + accepted_message_ref: "msg:busy-message-1", + notice: "Thread is busy, please try again.", + thread_id: threadId, + }), + setInterval, + setTimeout, + submitManualToken: async () => {}, + useChatEvents: ({ setPendingGate }) => { + setPendingGateFromEvents = setPendingGate; + return () => {}; + }, + useHistory: () => ({ + messages: renderedMessages, + hasMore: false, + nextCursor: null, + isLoading: false, + loadHistory: () => {}, + seedThreadMessages: () => {}, + setMessages: (updater) => { + renderedMessages = + typeof updater === "function" ? updater(renderedMessages) : updater; + }, + }), + useSSE: () => ({ status: "idle" }), + }; + + runUseChatSource(context); + + const chat = context.globalThis.__testExports.useChat(threadId); + setPendingGateFromEvents(pendingGate); + await chat.send("busy after gate arrived"); + + assert.deepEqual( + stateUpdates.filter((call) => call.index === 5).map((call) => call.value), + [pendingGate], + "send must read the latest gate from SSE instead of a stale null callback closure", + ); + assert.equal(stateSlots.get(5).value, pendingGate); + assert.equal(renderedMessages[0].status, "error"); + assert.equal(renderedMessages[1].role, "system"); +}); + test("useChat.cancelRun clears local state before cancel request resolves", async () => { const threadId = "thread-1"; const stateUpdates = []; From 8dbed7e5e1e8aa3cf0c6cd797d0b47422532f5a4 Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Thu, 25 Jun 2026 23:29:59 +0800 Subject: [PATCH 04/11] fix(webui): dedupe busy approval notices --- .../static/js/pages/chat/hooks/useChat.js | 15 +++- .../js/pages/chat/lib/useChat-send.test.mjs | 89 +++++++++++++++++++ 2 files changed, 100 insertions(+), 4 deletions(-) diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js index 12bcf28065d..ca1e13f4dd7 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js @@ -69,6 +69,11 @@ function threadNeedsSidebarRefresh(threadId) { return !thread?.title; } +function busyNoticeKey(threadId, gate) { + if (!threadId || !gate?.runId || !gate?.gateRef) return null; + return `${threadId}\n${gate.runId}\n${gate.gateRef}`; +} + function submitResponseResumedTurnGate(response) { return response?.continuation?.type === "turn_gate_resume"; } @@ -487,17 +492,19 @@ export function useChat(threadId) { updateCurrentThread(markRejected); updateSeededTarget(markRejected); if (response?.notice) { + const noticeKey = busyNoticeKey(sendThreadId, gateBeforeSend); const noticeMessage = { id: `system-rejected-${pendingSeqRef.current++}`, role: "system", content: response.notice, timestamp: new Date().toISOString(), isOptimistic: false, + busyGateKey: noticeKey, }; - const appendNotice = (prev) => [ - ...prev, - noticeMessage, - ]; + const appendNotice = (prev) => + noticeKey && prev.some((m) => m.busyGateKey === noticeKey) + ? prev + : [...prev, noticeMessage]; updateCurrentThread(appendNotice); updateSeededTarget(appendNotice); } diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs index 88c7fc38b5b..0bb64b9b39d 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs @@ -933,6 +933,95 @@ test("useChat.send: rejected busy keeps a gate received after callback creation" assert.equal(renderedMessages[1].role, "system"); }); +test("useChat.send: repeated busy rejection under the same gate dedupes notice", async () => { + const threadId = "thread-1"; + const pendingGate = { + runId: "run-gated", + gateRef: "gate-shell", + kind: "gate", + toolName: "builtin.shell", + }; + const stateUpdates = []; + let renderedMessages = []; + + const context = { + AbortController, + Date, + Error, + Map, + Math, + React: createReactStub({ + initialByIndex: new Map([ + [4, false], + [5, pendingGate], + ]), + setCalls: stateUpdates, + }), + addPending, + toRenderAttachment, + toWireAttachment, + cancelRunRequest: async () => {}, + clearTimeout, + createThreadRequest: async () => { + throw new Error("thread should already exist"); + }, + globalThis: {}, + listConnectableChannels: async () => { + throw new Error("ordinary prompts should not fetch connectable channels"); + }, + looksLikeChannelConnectCommand, + queryClient: { + fetchQuery: async () => { + throw new Error("ordinary prompts should not fetch connectable channels"); + }, + invalidateQueries: () => {}, + }, + recordAcceptedMessageRef, + removePending, + timelineMessageIdFromAcceptedRef, + resolveChannelConnectCommand, + resolveGateRequest: async () => {}, + sendMessage: async () => ({ + outcome: "rejected_busy", + accepted_message_ref: "msg:busy-message-1", + notice: + "An approval gate is open on this thread -- resolve it before continuing.", + thread_id: threadId, + }), + setInterval, + setTimeout, + submitManualToken: async () => {}, + useChatEvents: () => () => {}, + useHistory: () => ({ + messages: renderedMessages, + hasMore: false, + nextCursor: null, + isLoading: false, + loadHistory: () => {}, + seedThreadMessages: () => {}, + setMessages: (updater) => { + renderedMessages = + typeof updater === "function" ? updater(renderedMessages) : updater; + }, + }), + useSSE: () => ({ status: "idle" }), + }; + + runUseChatSource(context); + + const chat = context.globalThis.__testExports.useChat(threadId); + await chat.send("first blocked send"); + await chat.send("second blocked send"); + + const userMessages = renderedMessages.filter((m) => m.role === "user"); + const systemMessages = renderedMessages.filter((m) => m.role === "system"); + assert.equal(userMessages.length, 2); + assert.equal(userMessages[0].status, "error"); + assert.equal(userMessages[1].status, "error"); + assert.equal(systemMessages.length, 1); + assert.match(systemMessages[0].content, /approval gate is open/i); +}); + test("useChat.cancelRun clears local state before cancel request resolves", async () => { const threadId = "thread-1"; const stateUpdates = []; From fa99125debce36c3237a418619f9fd2267ead994 Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Thu, 25 Jun 2026 23:34:19 +0800 Subject: [PATCH 05/11] fix(webui): show busy gate notice below approval card --- .../static/js/pages/chat/chat.js | 11 +++++ .../static/js/pages/chat/hooks/useChat.js | 41 ++++++++++++------- .../js/pages/chat/lib/useChat-send.test.mjs | 23 +++++++---- tests/e2e/helpers.py | 1 + 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js index 9ac58dec029..1bfc9bbddc7 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js @@ -51,6 +51,7 @@ export function Chat({ messages, isProcessing, pendingGate, + busyGateNotice, channelConnectAction, suggestions, sseStatus, @@ -308,6 +309,16 @@ export function Chat({ approve(pendingGate.requestId, "always", pendingGate.kind)} /> `)} + ${busyGateNotice && + html` +
+ ${busyGateNotice.content} +
+ `} <${SuggestionChips} diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js index ca1e13f4dd7..a32f211e454 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js @@ -191,6 +191,7 @@ export function useChat(threadId) { const [isProcessing, setIsProcessing] = React.useState(false); const [pendingGate, setPendingGateState] = React.useState(null); const pendingGateRef = React.useRef(pendingGate); + const [busyGateNotice, setBusyGateNotice] = React.useState(null); const setPendingGate = React.useCallback((next) => { const current = pendingGateRef.current; const value = @@ -239,6 +240,7 @@ export function useChat(threadId) { setStateThreadId(threadId); setIsProcessing(false); setPendingGateState(null); + setBusyGateNotice(null); setActiveRunState(null); setChannelConnectAction(null); } @@ -247,6 +249,13 @@ export function useChat(threadId) { pendingGateRef.current = pendingGate; }, [pendingGate]); + React.useEffect(() => { + const currentKey = busyNoticeKey(threadId, pendingGate); + setBusyGateNotice((current) => + current && current.gateKey !== currentKey ? null : current, + ); + }, [pendingGate, threadId]); + React.useEffect(() => { resetToolActivityState(toolActivityStateRef); locallyResolvedGatesRef.current.clear(); @@ -493,20 +502,23 @@ export function useChat(threadId) { updateSeededTarget(markRejected); if (response?.notice) { const noticeKey = busyNoticeKey(sendThreadId, gateBeforeSend); - const noticeMessage = { - id: `system-rejected-${pendingSeqRef.current++}`, - role: "system", - content: response.notice, - timestamp: new Date().toISOString(), - isOptimistic: false, - busyGateKey: noticeKey, - }; - const appendNotice = (prev) => - noticeKey && prev.some((m) => m.busyGateKey === noticeKey) - ? prev - : [...prev, noticeMessage]; - updateCurrentThread(appendNotice); - updateSeededTarget(appendNotice); + if (noticeKey && shouldRenderInCurrentThread) { + setBusyGateNotice({ gateKey: noticeKey, content: response.notice }); + } else { + const noticeMessage = { + id: `system-rejected-${pendingSeqRef.current++}`, + role: "system", + content: response.notice, + timestamp: new Date().toISOString(), + isOptimistic: false, + }; + const appendNotice = (prev) => [ + ...prev, + noticeMessage, + ]; + updateCurrentThread(appendNotice); + updateSeededTarget(appendNotice); + } } updateCurrentRunState(() => setIsProcessing(false)); } @@ -717,6 +729,7 @@ export function useChat(threadId) { messages, isProcessing, pendingGate, + busyGateNotice, channelConnectAction, activeRun, sseStatus, diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs index 0bb64b9b39d..47d42f34a8f 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs @@ -930,7 +930,10 @@ test("useChat.send: rejected busy keeps a gate received after callback creation" ); assert.equal(stateSlots.get(5).value, pendingGate); assert.equal(renderedMessages[0].status, "error"); - assert.equal(renderedMessages[1].role, "system"); + assert.deepEqual( + stateUpdates.filter((call) => call.index === 6).map((call) => call.value?.content), + ["Thread is busy, please try again."], + ); }); test("useChat.send: repeated busy rejection under the same gate dedupes notice", async () => { @@ -1018,8 +1021,11 @@ test("useChat.send: repeated busy rejection under the same gate dedupes notice", assert.equal(userMessages.length, 2); assert.equal(userMessages[0].status, "error"); assert.equal(userMessages[1].status, "error"); - assert.equal(systemMessages.length, 1); - assert.match(systemMessages[0].content, /approval gate is open/i); + assert.equal(systemMessages.length, 0); + assert.equal( + stateUpdates.filter((call) => call.index === 6).at(-1)?.value?.content, + "An approval gate is open on this thread -- resolve it before continuing.", + ); }); test("useChat.cancelRun clears local state before cancel request resolves", async () => { @@ -1117,13 +1123,15 @@ test("useChat clears transient run and gate state during thread switch render", Math, React: createReactStub({ // useChat state call order: cooldownUntil, now, activeRun, - // channelConnectAction, isProcessing, pendingGate, stateThreadId. + // channelConnectAction, isProcessing, pendingGate, busyGateNotice, + // stateThreadId. initialByIndex: new Map([ [2, { runId: "run-old", threadId: "thread-old", status: "awaiting_gate" }], [3, { channel: "slack" }], [4, true], [5, { runId: "run-old", gateRef: "gate-old" }], - [6, "thread-old"], + [6, { gateKey: "thread-old\nrun-old\ngate-old", content: "busy" }], + [7, "thread-old"], ]), setCalls: stateUpdates, }), @@ -1168,10 +1176,11 @@ test("useChat clears transient run and gate state during thread switch render", runUseChatSource(context); context.globalThis.__testExports.useChat("thread-new"); - assert.deepEqual(stateUpdates.slice(0, 5), [ - { index: 6, value: "thread-new" }, + assert.deepEqual(stateUpdates.slice(0, 6), [ + { index: 7, value: "thread-new" }, { index: 4, value: false }, { index: 5, value: null }, + { index: 6, value: null }, { index: 2, value: null }, { index: 3, value: null }, ]); diff --git a/tests/e2e/helpers.py b/tests/e2e/helpers.py index 073954e2b73..b7be2cd7237 100644 --- a/tests/e2e/helpers.py +++ b/tests/e2e/helpers.py @@ -237,6 +237,7 @@ "msg_assistant": "[data-testid='msg-assistant']", # assistant message bubble "msg_system": "[data-testid='msg-system']", # system notice bubble "approval_card": "[data-testid='approval-card']", # approval gate card + "busy_gate_notice": "[data-testid='busy-gate-notice']", # gate busy notice # Download chip for an agent-produced workspace file; `{path}` selects one. # Clicking a chip opens the shared attachment preview modal, whose footer # carries the Download action. From 45482f30d2535febb8a4d9a2db5eed1b8713da56 Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Thu, 25 Jun 2026 23:49:45 +0800 Subject: [PATCH 06/11] fix(webui): avoid stale busy gate notices --- .../static/js/pages/chat/hooks/useChat.js | 4 +- .../js/pages/chat/lib/useChat-send.test.mjs | 102 ++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js index a32f211e454..da31fc4d5b4 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js @@ -503,7 +503,9 @@ export function useChat(threadId) { if (response?.notice) { const noticeKey = busyNoticeKey(sendThreadId, gateBeforeSend); if (noticeKey && shouldRenderInCurrentThread) { - setBusyGateNotice({ gateKey: noticeKey, content: response.notice }); + if (pendingGateRef.current === gateBeforeSend) { + setBusyGateNotice({ gateKey: noticeKey, content: response.notice }); + } } else { const noticeMessage = { id: `system-rejected-${pendingSeqRef.current++}`, diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs index 47d42f34a8f..cd3c85bba0f 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs @@ -836,6 +836,108 @@ test("useChat.send: rejected busy does not clear a gate received while in flight "busy rejection must leave a concurrently replaced gate untouched", ); assert.equal(stateSlots.get(5).value, replacementGate); + assert.deepEqual( + stateUpdates.filter((call) => call.index === 6).map((call) => call.value), + [], + "busy rejection must not attach a notice to a stale gate snapshot", + ); +}); + +test("useChat.send: rejected busy does not strand notice after gate resolves in flight", async () => { + const threadId = "thread-1"; + const pendingGate = { + runId: "run-gated", + gateRef: "gate-shell", + kind: "gate", + toolName: "builtin.shell", + }; + const stateUpdates = []; + const stateSlots = new Map(); + let renderedMessages = []; + let setPendingGateFromEvents = null; + + const context = { + AbortController, + Date, + Error, + Map, + Math, + React: createReactStub({ + initialByIndex: new Map([ + [4, false], + [5, pendingGate], + ]), + setCalls: stateUpdates, + stateSlots, + }), + addPending, + toRenderAttachment, + toWireAttachment, + cancelRunRequest: async () => {}, + clearTimeout, + createThreadRequest: async () => { + throw new Error("thread should already exist"); + }, + globalThis: {}, + listConnectableChannels: async () => { + throw new Error("ordinary prompts should not fetch connectable channels"); + }, + looksLikeChannelConnectCommand, + queryClient: { + fetchQuery: async () => { + throw new Error("ordinary prompts should not fetch connectable channels"); + }, + invalidateQueries: () => {}, + }, + recordAcceptedMessageRef, + removePending, + timelineMessageIdFromAcceptedRef, + resolveChannelConnectCommand, + resolveGateRequest: async () => {}, + sendMessage: async () => { + setPendingGateFromEvents(null); + return { + outcome: "rejected_busy", + accepted_message_ref: "msg:busy-message-1", + notice: "Thread is busy, please try again.", + thread_id: threadId, + }; + }, + setInterval, + setTimeout, + submitManualToken: async () => {}, + useChatEvents: ({ setPendingGate }) => { + setPendingGateFromEvents = setPendingGate; + return () => {}; + }, + useHistory: () => ({ + messages: renderedMessages, + hasMore: false, + nextCursor: null, + isLoading: false, + loadHistory: () => {}, + seedThreadMessages: () => {}, + setMessages: (updater) => { + renderedMessages = + typeof updater === "function" ? updater(renderedMessages) : updater; + }, + }), + useSSE: () => ({ status: "idle" }), + }; + + runUseChatSource(context); + + const chat = context.globalThis.__testExports.useChat(threadId); + await chat.send("busy after gate resolved"); + + assert.equal(stateSlots.get(5).value, null); + assert.equal(renderedMessages.length, 1); + assert.equal(renderedMessages[0].status, "error"); + assert.deepEqual( + stateUpdates.filter((call) => call.index === 6).map((call) => call.value), + [], + "a resolved gate should not get a lingering busy notice", + ); }); test("useChat.send: rejected busy keeps a gate received after callback creation", async () => { From 3630edb157c6d7fa3ec4bd37a713ace967826751 Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:04:16 +0800 Subject: [PATCH 07/11] fix(webui): keep busy notices visible across gate changes --- .../static/js/pages/chat/hooks/useChat.js | 21 +++-- .../js/pages/chat/lib/useChat-send.test.mjs | 86 +++++++++---------- 2 files changed, 54 insertions(+), 53 deletions(-) diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js index da31fc4d5b4..e00549be0b1 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js @@ -501,12 +501,7 @@ export function useChat(threadId) { updateCurrentThread(markRejected); updateSeededTarget(markRejected); if (response?.notice) { - const noticeKey = busyNoticeKey(sendThreadId, gateBeforeSend); - if (noticeKey && shouldRenderInCurrentThread) { - if (pendingGateRef.current === gateBeforeSend) { - setBusyGateNotice({ gateKey: noticeKey, content: response.notice }); - } - } else { + const appendSystemNotice = () => { const noticeMessage = { id: `system-rejected-${pendingSeqRef.current++}`, role: "system", @@ -520,6 +515,20 @@ export function useChat(threadId) { ]; updateCurrentThread(appendNotice); updateSeededTarget(appendNotice); + }; + const noticeKey = busyNoticeKey(sendThreadId, gateBeforeSend); + if (noticeKey && shouldRenderInCurrentThread) { + const currentNoticeKey = busyNoticeKey(sendThreadId, pendingGateRef.current); + if (currentNoticeKey) { + setBusyGateNotice({ + gateKey: currentNoticeKey, + content: response.notice, + }); + } else { + appendSystemNotice(); + } + } else { + appendSystemNotice(); } } updateCurrentRunState(() => setIsProcessing(false)); diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs index cd3c85bba0f..27de65ef7d9 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs @@ -743,7 +743,7 @@ test("useChat.send: accepted send does not clear a gate received while in flight assert.equal(stateSlots.get(5).value, replacementGate); }); -test("useChat.send: rejected busy does not clear a gate received while in flight", async () => { +test("useChat.send: rejected busy attaches notice to a gate replaced while in flight", async () => { const threadId = "thread-1"; const replacementGate = { runId: "run-replacement", @@ -836,21 +836,16 @@ test("useChat.send: rejected busy does not clear a gate received while in flight "busy rejection must leave a concurrently replaced gate untouched", ); assert.equal(stateSlots.get(5).value, replacementGate); - assert.deepEqual( - stateUpdates.filter((call) => call.index === 6).map((call) => call.value), - [], - "busy rejection must not attach a notice to a stale gate snapshot", - ); + const busyNoticeUpdates = stateUpdates + .filter((call) => call.index === 6) + .map((call) => call.value); + assert.equal(busyNoticeUpdates.length, 1); + assert.equal(busyNoticeUpdates[0].content, "Thread is busy, please try again."); + assert.match(busyNoticeUpdates[0].gateKey, /run-replacement\ngate-replacement$/); }); -test("useChat.send: rejected busy does not strand notice after gate resolves in flight", async () => { +test("useChat.send: rejected busy appends system notice after gate resolves in flight", async () => { const threadId = "thread-1"; - const pendingGate = { - runId: "run-gated", - gateRef: "gate-shell", - kind: "gate", - toolName: "builtin.shell", - }; const stateUpdates = []; const stateSlots = new Map(); let renderedMessages = []; @@ -865,7 +860,7 @@ test("useChat.send: rejected busy does not strand notice after gate resolves in React: createReactStub({ initialByIndex: new Map([ [4, false], - [5, pendingGate], + [5, null], ]), setCalls: stateUpdates, stateSlots, @@ -931,16 +926,18 @@ test("useChat.send: rejected busy does not strand notice after gate resolves in await chat.send("busy after gate resolved"); assert.equal(stateSlots.get(5).value, null); - assert.equal(renderedMessages.length, 1); + assert.equal(renderedMessages.length, 2); assert.equal(renderedMessages[0].status, "error"); + assert.equal(renderedMessages[1].role, "system"); + assert.equal(renderedMessages[1].content, "Thread is busy, please try again."); assert.deepEqual( stateUpdates.filter((call) => call.index === 6).map((call) => call.value), [], - "a resolved gate should not get a lingering busy notice", + "a resolved gate should not get a lingering card-level busy notice", ); }); -test("useChat.send: rejected busy keeps a gate received after callback creation", async () => { +test("useChat.send: gate received after callback creation blocks before send", async () => { const threadId = "thread-1"; const pendingGate = { runId: "run-gated", @@ -991,12 +988,9 @@ test("useChat.send: rejected busy keeps a gate received after callback creation" timelineMessageIdFromAcceptedRef, resolveChannelConnectCommand, resolveGateRequest: async () => {}, - sendMessage: async () => ({ - outcome: "rejected_busy", - accepted_message_ref: "msg:busy-message-1", - notice: "Thread is busy, please try again.", - thread_id: threadId, - }), + sendMessage: async () => { + throw new Error("sendMessage should not run"); + }, setInterval, setTimeout, submitManualToken: async () => {}, @@ -1023,7 +1017,12 @@ test("useChat.send: rejected busy keeps a gate received after callback creation" const chat = context.globalThis.__testExports.useChat(threadId); setPendingGateFromEvents(pendingGate); - await chat.send("busy after gate arrived"); + await assert.rejects( + chat.send("busy after gate arrived"), + (error) => + error?.safeErrorCode === "approval_gate_pending_send_blocked" && + /Resolve the approval request/.test(error.message), + ); assert.deepEqual( stateUpdates.filter((call) => call.index === 5).map((call) => call.value), @@ -1031,14 +1030,14 @@ test("useChat.send: rejected busy keeps a gate received after callback creation" "send must read the latest gate from SSE instead of a stale null callback closure", ); assert.equal(stateSlots.get(5).value, pendingGate); - assert.equal(renderedMessages[0].status, "error"); + assert.equal(renderedMessages.length, 0); assert.deepEqual( stateUpdates.filter((call) => call.index === 6).map((call) => call.value?.content), - ["Thread is busy, please try again."], + [], ); }); -test("useChat.send: repeated busy rejection under the same gate dedupes notice", async () => { +test("useChat.send: repeated sends under the same pending gate stay blocked locally", async () => { const threadId = "thread-1"; const pendingGate = { runId: "run-gated", @@ -1083,16 +1082,11 @@ test("useChat.send: repeated busy rejection under the same gate dedupes notice", }, recordAcceptedMessageRef, removePending, - timelineMessageIdFromAcceptedRef, resolveChannelConnectCommand, resolveGateRequest: async () => {}, - sendMessage: async () => ({ - outcome: "rejected_busy", - accepted_message_ref: "msg:busy-message-1", - notice: - "An approval gate is open on this thread -- resolve it before continuing.", - thread_id: threadId, - }), + sendMessage: async () => { + throw new Error("sendMessage should not run"); + }, setInterval, setTimeout, submitManualToken: async () => {}, @@ -1115,19 +1109,17 @@ test("useChat.send: repeated busy rejection under the same gate dedupes notice", runUseChatSource(context); const chat = context.globalThis.__testExports.useChat(threadId); - await chat.send("first blocked send"); - await chat.send("second blocked send"); - - const userMessages = renderedMessages.filter((m) => m.role === "user"); - const systemMessages = renderedMessages.filter((m) => m.role === "system"); - assert.equal(userMessages.length, 2); - assert.equal(userMessages[0].status, "error"); - assert.equal(userMessages[1].status, "error"); - assert.equal(systemMessages.length, 0); - assert.equal( - stateUpdates.filter((call) => call.index === 6).at(-1)?.value?.content, - "An approval gate is open on this thread -- resolve it before continuing.", + await assert.rejects( + chat.send("first blocked send"), + /Resolve the approval request/, + ); + await assert.rejects( + chat.send("second blocked send"), + /Resolve the approval request/, ); + + assert.equal(renderedMessages.length, 0); + assert.deepEqual(stateUpdates, []); }); test("useChat.cancelRun clears local state before cancel request resolves", async () => { From 33dfd373f944f8f15a12ce417e70889af930209a Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:19:13 +0800 Subject: [PATCH 08/11] fix(webui): block sends while composer stays editable --- .../static/js/pages/chat/chat.js | 3 +- .../static/js/pages/chat/lib/chat.test.mjs | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js index 1bfc9bbddc7..6a920f5b432 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js @@ -111,6 +111,7 @@ export function Chat({ : null; const handleSend = React.useCallback( async (content, { images = [], attachments = [] } = {}) => { + if (composerSendDisabled) return null; const response = await send(content, { images, attachments, @@ -122,7 +123,7 @@ export function Chat({ } return response; }, - [activeThreadId, onSelectThread, send] + [activeThreadId, composerSendDisabled, onSelectThread, send] ); const handleSuggestion = React.useCallback( diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs index 6383f3c4de8..94b3fa4590f 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs @@ -175,6 +175,42 @@ test("Chat leaves the composer editable while a run is processing", () => { assert.equal(props.sendDisabled, true); }); +test("Chat blocks sends while a run is processing", async () => { + let sendCount = 0; + const { tree, components } = renderChat({ + hookState: { + messages: [{ id: "message-1" }], + isProcessing: true, + pendingGate: null, + channelConnectAction: null, + suggestions: [], + sseStatus: "open", + historyLoading: false, + hasMore: false, + cooldownSeconds: 0, + recoveryNotice: null, + activeRun: { runId: "run-1", threadId: "thread-1", status: "running" }, + send: async () => { + sendCount += 1; + return {}; + }, + cancelRun: async () => {}, + retryMessage: () => {}, + approve: () => {}, + recoverHistory: () => {}, + loadMore: () => {}, + setSuggestions: () => {}, + submitAuthToken: async () => {}, + dismissChannelConnectAction: () => {}, + }, + }); + + const chatInput = findComponent(tree, components.ChatInput); + const props = componentProps(chatInput, components.ChatInput); + assert.equal(await props.onSend("draft while running"), null); + assert.equal(sendCount, 0); +}); + test("Chat cancel button ignores active runs from another thread", () => { const { tree, components } = renderChat({ hookState: { From 4b2213b5f4d381f71b0385d2c92c1453cb1786be Mon Sep 17 00:00:00 2001 From: Robert Yan Date: Thu, 25 Jun 2026 16:19:55 +0000 Subject: [PATCH 09/11] fix(webui): block sends while approval is pending --- .../static/js/pages/chat/chat.js | 12 +++++++++--- .../js/pages/chat/components/suggestion-chips.js | 9 ++++++--- .../static/js/pages/chat/lib/chat.test.mjs | 5 +++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js index 6a920f5b432..9434c7f8f6b 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js @@ -86,10 +86,14 @@ export function Chat({ // error banner instead so the user is not misled into thinking the thread // is empty. const showLanding = !historyLoading && !hasMessages && !historyLoadError; + const approvalSubmitWarning = pendingGate + ? "Resolve the approval request before sending another message." + : ""; const composerSendDisabled = - (isProcessing && !pendingGate) || cooldownSeconds > 0; + Boolean(pendingGate) || (isProcessing && !pendingGate) || cooldownSeconds > 0; const composerStatusText = - cooldownSeconds > 0 ? `Retry in ${cooldownSeconds}s` : undefined; + approvalSubmitWarning || + (cooldownSeconds > 0 ? `Retry in ${cooldownSeconds}s` : undefined); // Scope the persisted composer draft to the open thread (or the // shared new-conversation slot when there's no active thread yet). const composerDraftKey = activeThreadId || NEW_DRAFT_KEY; @@ -128,10 +132,11 @@ export function Chat({ const handleSuggestion = React.useCallback( async (text) => { + if (composerSendDisabled) return; setSuggestions([]); await handleSend(text); }, - [handleSend, setSuggestions] + [composerSendDisabled, handleSend, setSuggestions] ); const handleCancelRun = React.useCallback( @@ -325,6 +330,7 @@ export function Chat({ <${SuggestionChips} suggestions=${suggestions} onSelect=${handleSuggestion} + disabled=${composerSendDisabled} /> <${ChatInput} diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/components/suggestion-chips.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/components/suggestion-chips.js index a12a65aa63f..0349e121f04 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/components/suggestion-chips.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/components/suggestion-chips.js @@ -1,6 +1,6 @@ import { html } from "../../../lib/html.js"; -export function SuggestionChips({ suggestions, onSelect }) { +export function SuggestionChips({ suggestions, onSelect, disabled = false }) { if (!suggestions || suggestions.length === 0) return null; return html` @@ -10,8 +10,11 @@ export function SuggestionChips({ suggestions, onSelect }) { (text) => html` diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs index 94b3fa4590f..ff94f2b2546 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs @@ -274,6 +274,11 @@ test("Chat keeps composer cancel disabled while a gate owns the run decision", ( const chatInput = findComponent(tree, components.ChatInput); const props = componentProps(chatInput, components.ChatInput); assert.equal(props.canCancel, false); + assert.equal(props.sendDisabled, true); + assert.equal( + props.statusText, + "Resolve the approval request before sending another message.", + ); }); test("Chat renders a timeline load failure as an alert instead of the empty landing", () => { From 687a0437be19476e0e234e866bf1e7a88c379502 Mon Sep 17 00:00:00 2001 From: Robert Yan Date: Thu, 25 Jun 2026 16:35:48 +0000 Subject: [PATCH 10/11] fix(webui): reject sends during approval gates --- .../static/dist/app.js | 2166 +++++++++-------- .../static/js/pages/chat/chat.js | 12 +- .../static/js/pages/chat/hooks/useChat.js | 3 +- .../static/js/pages/chat/lib/chat.test.mjs | 13 +- .../js/pages/chat/lib/useChat-send.test.mjs | 12 +- 5 files changed, 1123 insertions(+), 1083 deletions(-) diff --git a/crates/ironclaw_webui_v2_static/static/dist/app.js b/crates/ironclaw_webui_v2_static/static/dist/app.js index 0e6aae8e8bf..9eedb7085fc 100644 --- a/crates/ironclaw_webui_v2_static/static/dist/app.js +++ b/crates/ironclaw_webui_v2_static/static/dist/app.js @@ -1,14 +1,14 @@ -import{a as Sn,b as qe,c as Qe,d as h,e as l,f as Ih,g as Kh,h as il,i as k,j as ol}from"./chunks/chunk-IGTNS7XG.js";var uv=Sn(vl=>{"use strict";var hR=Symbol.for("react.transitional.element"),vR=Symbol.for("react.fragment");function lv(e,t,a){var n=null;if(a!==void 0&&(n=""+a),t.key!==void 0&&(n=""+t.key),"key"in t){a={};for(var r in t)r!=="key"&&(a[r]=t[r])}else a=t;return t=a.ref,{$$typeof:hR,type:e,key:n,ref:t!==void 0?t:null,props:a}}vl.Fragment=vR;vl.jsx=lv;vl.jsxs=lv});var wd=Sn((DL,cv)=>{"use strict";cv.exports=uv()});var Nv=Sn(Oe=>{"use strict";function Ed(e,t){var a=e.length;e.push(t);e:for(;0>>1,r=e[n];if(0<_l(r,t))e[n]=t,e[a]=r,a=n;else break e}}function Aa(e){return e.length===0?null:e[0]}function Rl(e){if(e.length===0)return null;var t=e[0],a=e.pop();if(a!==t){e[0]=a;e:for(var n=0,r=e.length,s=r>>>1;n_l(o,a))u_l(c,o)?(e[n]=c,e[u]=a,n=u):(e[n]=o,e[i]=a,n=i);else if(u_l(c,a))e[n]=c,e[u]=a,n=u;else break e}}return t}function _l(e,t){var a=e.sortIndex-t.sortIndex;return a!==0?a:e.id-t.id}Oe.unstable_now=void 0;typeof performance=="object"&&typeof performance.now=="function"?(hv=performance,Oe.unstable_now=function(){return hv.now()}):(kd=Date,vv=kd.now(),Oe.unstable_now=function(){return kd.now()-vv});var hv,kd,vv,Ja=[],kn=[],xR=1,sa=null,yt=3,Td=!1,_i=!1,ki=!1,Ad=!1,bv=typeof setTimeout=="function"?setTimeout:null,xv=typeof clearTimeout=="function"?clearTimeout:null,gv=typeof setImmediate<"u"?setImmediate:null;function kl(e){for(var t=Aa(kn);t!==null;){if(t.callback===null)Rl(kn);else if(t.startTime<=e)Rl(kn),t.sortIndex=t.expirationTime,Ed(Ja,t);else break;t=Aa(kn)}}function Dd(e){if(ki=!1,kl(e),!_i)if(Aa(Ja)!==null)_i=!0,Jr||(Jr=!0,Yr());else{var t=Aa(kn);t!==null&&Md(Dd,t.startTime-e)}}var Jr=!1,Ri=-1,$v=5,wv=-1;function Sv(){return Ad?!0:!(Oe.unstable_now()-wv<$v)}function Rd(){if(Ad=!1,Jr){var e=Oe.unstable_now();wv=e;var t=!0;try{e:{_i=!1,ki&&(ki=!1,xv(Ri),Ri=-1),Td=!0;var a=yt;try{t:{for(kl(e),sa=Aa(Ja);sa!==null&&!(sa.expirationTime>e&&Sv());){var n=sa.callback;if(typeof n=="function"){sa.callback=null,yt=sa.priorityLevel;var r=n(sa.expirationTime<=e);if(e=Oe.unstable_now(),typeof r=="function"){sa.callback=r,kl(e),t=!0;break t}sa===Aa(Ja)&&Rl(Ja),kl(e)}else Rl(Ja);sa=Aa(Ja)}if(sa!==null)t=!0;else{var s=Aa(kn);s!==null&&Md(Dd,s.startTime-e),t=!1}}break e}finally{sa=null,yt=a,Td=!1}t=void 0}}finally{t?Yr():Jr=!1}}}var Yr;typeof gv=="function"?Yr=function(){gv(Rd)}:typeof MessageChannel<"u"?(Cd=new MessageChannel,yv=Cd.port2,Cd.port1.onmessage=Rd,Yr=function(){yv.postMessage(null)}):Yr=function(){bv(Rd,0)};var Cd,yv;function Md(e,t){Ri=bv(function(){e(Oe.unstable_now())},t)}Oe.unstable_IdlePriority=5;Oe.unstable_ImmediatePriority=1;Oe.unstable_LowPriority=4;Oe.unstable_NormalPriority=3;Oe.unstable_Profiling=null;Oe.unstable_UserBlockingPriority=2;Oe.unstable_cancelCallback=function(e){e.callback=null};Oe.unstable_forceFrameRate=function(e){0>e||125n?(e.sortIndex=a,Ed(kn,e),Aa(Ja)===null&&e===Aa(kn)&&(ki?(xv(Ri),Ri=-1):ki=!0,Md(Dd,a-n))):(e.sortIndex=r,Ed(Ja,e),_i||Td||(_i=!0,Jr||(Jr=!0,Yr()))),e};Oe.unstable_shouldYield=Sv;Oe.unstable_wrapCallback=function(e){var t=yt;return function(){var a=yt;yt=t;try{return e.apply(this,arguments)}finally{yt=a}}}});var kv=Sn((p6,_v)=>{"use strict";_v.exports=Nv()});var Cv=Sn(kt=>{"use strict";var $R=Qe();function Rv(e){var t="https://react.dev/errors/"+e;if(1{"use strict";function Ev(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Ev)}catch(e){console.error(e)}}Ev(),Tv.exports=Cv()});var M0=Sn(Ju=>{"use strict";var rt=kv(),Wg=Qe(),NR=Av();function P(e){var t="https://react.dev/errors/"+e;if(1rs||(e.current=vm[rs],vm[rs]=null,rs--)}function Pe(e,t){rs++,vm[rs]=e.current,e.current=t}var La=ja(null),to=ja(null),zn=ja(null),su=ja(null);function iu(e,t){switch(Pe(zn,t),Pe(to,e),Pe(La,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Fg(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Fg(t),e=$0(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}dt(La),Pe(La,e)}function Ss(){dt(La),dt(to),dt(zn)}function gm(e){e.memoizedState!==null&&Pe(su,e);var t=La.current,a=$0(t,e.type);t!==a&&(Pe(to,e),Pe(La,a))}function ou(e){to.current===e&&(dt(La),dt(to)),su.current===e&&(dt(su),mo._currentValue=fr)}var ym=Object.prototype.hasOwnProperty,uf=rt.unstable_scheduleCallback,Od=rt.unstable_cancelCallback,TR=rt.unstable_shouldYield,AR=rt.unstable_requestPaint,Pa=rt.unstable_now,DR=rt.unstable_getCurrentPriorityLevel,sy=rt.unstable_ImmediatePriority,iy=rt.unstable_UserBlockingPriority,lu=rt.unstable_NormalPriority,MR=rt.unstable_LowPriority,oy=rt.unstable_IdlePriority,OR=rt.log,LR=rt.unstable_setDisableYieldValue,go=null,Qt=null;function Pn(e){if(typeof OR=="function"&&LR(e),Qt&&typeof Qt.setStrictMode=="function")try{Qt.setStrictMode(go,e)}catch{}}var Vt=Math.clz32?Math.clz32:jR,PR=Math.log,UR=Math.LN2;function jR(e){return e>>>=0,e===0?32:31-(PR(e)/UR|0)|0}var Tl=256,Al=4194304;function cr(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Lu(e,t,a){var n=e.pendingLanes;if(n===0)return 0;var r=0,s=e.suspendedLanes,i=e.pingedLanes;e=e.warmLanes;var o=n&134217727;return o!==0?(n=o&~s,n!==0?r=cr(n):(i&=o,i!==0?r=cr(i):a||(a=o&~e,a!==0&&(r=cr(a))))):(o=n&~s,o!==0?r=cr(o):i!==0?r=cr(i):a||(a=n&~e,a!==0&&(r=cr(a)))),r===0?0:t!==0&&t!==r&&(t&s)===0&&(s=r&-r,a=t&-t,s>=a||s===32&&(a&4194048)!==0)?t:r}function yo(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function FR(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function ly(){var e=Tl;return Tl<<=1,(Tl&4194048)===0&&(Tl=256),e}function uy(){var e=Al;return Al<<=1,(Al&62914560)===0&&(Al=4194304),e}function Ld(e){for(var t=[],a=0;31>a;a++)t.push(e);return t}function bo(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function zR(e,t,a,n,r,s){var i=e.pendingLanes;e.pendingLanes=a,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=a,e.entangledLanes&=a,e.errorRecoveryDisabledLanes&=a,e.shellSuspendCounter=0;var o=e.entanglements,u=e.expirationTimes,c=e.hiddenUpdates;for(a=i&~a;0{"use strict";var gR=Symbol.for("react.transitional.element"),yR=Symbol.for("react.fragment");function uv(e,t,a){var n=null;if(a!==void 0&&(n=""+a),t.key!==void 0&&(n=""+t.key),"key"in t){a={};for(var r in t)r!=="key"&&(a[r]=t[r])}else a=t;return t=a.ref,{$$typeof:gR,type:e,key:n,ref:t!==void 0?t:null,props:a}}vl.Fragment=yR;vl.jsx=uv;vl.jsxs=uv});var Sd=kn((LL,dv)=>{"use strict";dv.exports=cv()});var _v=kn(Le=>{"use strict";function Td(e,t){var a=e.length;e.push(t);e:for(;0>>1,r=e[n];if(0<_l(r,t))e[n]=t,e[a]=r,a=n;else break e}}function Oa(e){return e.length===0?null:e[0]}function Rl(e){if(e.length===0)return null;var t=e[0],a=e.pop();if(a!==t){e[0]=a;e:for(var n=0,r=e.length,s=r>>>1;n_l(o,a))u_l(c,o)?(e[n]=c,e[u]=a,n=u):(e[n]=o,e[i]=a,n=i);else if(u_l(c,a))e[n]=c,e[u]=a,n=u;else break e}}return t}function _l(e,t){var a=e.sortIndex-t.sortIndex;return a!==0?a:e.id-t.id}Le.unstable_now=void 0;typeof performance=="object"&&typeof performance.now=="function"?(vv=performance,Le.unstable_now=function(){return vv.now()}):(Rd=Date,gv=Rd.now(),Le.unstable_now=function(){return Rd.now()-gv});var vv,Rd,gv,Wa=[],En=[],wR=1,oa=null,St=3,Ad=!1,ki=!1,Ri=!1,Dd=!1,xv=typeof setTimeout=="function"?setTimeout:null,$v=typeof clearTimeout=="function"?clearTimeout:null,yv=typeof setImmediate<"u"?setImmediate:null;function kl(e){for(var t=Oa(En);t!==null;){if(t.callback===null)Rl(En);else if(t.startTime<=e)Rl(En),t.sortIndex=t.expirationTime,Td(Wa,t);else break;t=Oa(En)}}function Md(e){if(Ri=!1,kl(e),!ki)if(Oa(Wa)!==null)ki=!0,Xr||(Xr=!0,Jr());else{var t=Oa(En);t!==null&&Od(Md,t.startTime-e)}}var Xr=!1,Ci=-1,wv=5,Sv=-1;function Nv(){return Dd?!0:!(Le.unstable_now()-Sve&&Nv());){var n=oa.callback;if(typeof n=="function"){oa.callback=null,St=oa.priorityLevel;var r=n(oa.expirationTime<=e);if(e=Le.unstable_now(),typeof r=="function"){oa.callback=r,kl(e),t=!0;break t}oa===Oa(Wa)&&Rl(Wa),kl(e)}else Rl(Wa);oa=Oa(Wa)}if(oa!==null)t=!0;else{var s=Oa(En);s!==null&&Od(Md,s.startTime-e),t=!1}}break e}finally{oa=null,St=a,Ad=!1}t=void 0}}finally{t?Jr():Xr=!1}}}var Jr;typeof yv=="function"?Jr=function(){yv(Cd)}:typeof MessageChannel<"u"?(Ed=new MessageChannel,bv=Ed.port2,Ed.port1.onmessage=Cd,Jr=function(){bv.postMessage(null)}):Jr=function(){xv(Cd,0)};var Ed,bv;function Od(e,t){Ci=xv(function(){e(Le.unstable_now())},t)}Le.unstable_IdlePriority=5;Le.unstable_ImmediatePriority=1;Le.unstable_LowPriority=4;Le.unstable_NormalPriority=3;Le.unstable_Profiling=null;Le.unstable_UserBlockingPriority=2;Le.unstable_cancelCallback=function(e){e.callback=null};Le.unstable_forceFrameRate=function(e){0>e||125n?(e.sortIndex=a,Td(En,e),Oa(Wa)===null&&e===Oa(En)&&(Ri?($v(Ci),Ci=-1):Ri=!0,Od(Md,a-n))):(e.sortIndex=r,Td(Wa,e),ki||Ad||(ki=!0,Xr||(Xr=!0,Jr()))),e};Le.unstable_shouldYield=Nv;Le.unstable_wrapCallback=function(e){var t=St;return function(){var a=St;St=t;try{return e.apply(this,arguments)}finally{St=a}}}});var Rv=kn((g6,kv)=>{"use strict";kv.exports=_v()});var Ev=kn(Et=>{"use strict";var SR=Ve();function Cv(e){var t="https://react.dev/errors/"+e;if(1{"use strict";function Tv(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Tv)}catch(e){console.error(e)}}Tv(),Av.exports=Ev()});var O0=kn(Ju=>{"use strict";var it=Rv(),ey=Ve(),kR=Dv();function U(e){var t="https://react.dev/errors/"+e;if(1ss||(e.current=gm[ss],gm[ss]=null,ss--)}function Ue(e,t){ss++,gm[ss]=e.current,e.current=t}var ja=za(null),ao=za(null),In=za(null),su=za(null);function iu(e,t){switch(Ue(In,t),Ue(ao,e),Ue(ja,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Bg(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Bg(t),e=w0(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}ht(ja),Ue(ja,e)}function Ns(){ht(ja),ht(ao),ht(In)}function ym(e){e.memoizedState!==null&&Ue(su,e);var t=ja.current,a=w0(t,e.type);t!==a&&(Ue(ao,e),Ue(ja,a))}function ou(e){ao.current===e&&(ht(ja),ht(ao)),su.current===e&&(ht(su),fo._currentValue=vr)}var bm=Object.prototype.hasOwnProperty,cf=it.unstable_scheduleCallback,Ld=it.unstable_cancelCallback,DR=it.unstable_shouldYield,MR=it.unstable_requestPaint,Fa=it.unstable_now,OR=it.unstable_getCurrentPriorityLevel,iy=it.unstable_ImmediatePriority,oy=it.unstable_UserBlockingPriority,lu=it.unstable_NormalPriority,LR=it.unstable_LowPriority,ly=it.unstable_IdlePriority,PR=it.log,UR=it.unstable_setDisableYieldValue,yo=null,Yt=null;function Fn(e){if(typeof PR=="function"&&UR(e),Yt&&typeof Yt.setStrictMode=="function")try{Yt.setStrictMode(yo,e)}catch{}}var Jt=Math.clz32?Math.clz32:BR,jR=Math.log,FR=Math.LN2;function BR(e){return e>>>=0,e===0?32:31-(jR(e)/FR|0)|0}var Tl=256,Al=4194304;function fr(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Lu(e,t,a){var n=e.pendingLanes;if(n===0)return 0;var r=0,s=e.suspendedLanes,i=e.pingedLanes;e=e.warmLanes;var o=n&134217727;return o!==0?(n=o&~s,n!==0?r=fr(n):(i&=o,i!==0?r=fr(i):a||(a=o&~e,a!==0&&(r=fr(a))))):(o=n&~s,o!==0?r=fr(o):i!==0?r=fr(i):a||(a=n&~e,a!==0&&(r=fr(a)))),r===0?0:t!==0&&t!==r&&(t&s)===0&&(s=r&-r,a=t&-t,s>=a||s===32&&(a&4194048)!==0)?t:r}function bo(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function zR(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function uy(){var e=Tl;return Tl<<=1,(Tl&4194048)===0&&(Tl=256),e}function cy(){var e=Al;return Al<<=1,(Al&62914560)===0&&(Al=4194304),e}function Pd(e){for(var t=[],a=0;31>a;a++)t.push(e);return t}function xo(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function qR(e,t,a,n,r,s){var i=e.pendingLanes;e.pendingLanes=a,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=a,e.entangledLanes&=a,e.errorRecoveryDisabledLanes&=a,e.shellSuspendCounter=0;var o=e.entanglements,u=e.expirationTimes,c=e.hiddenUpdates;for(a=i&~a;0)":-1r||u[n]!==c[r]){var d=` -`+u[n].replace(" at new "," at ");return e.displayName&&d.includes("")&&(d=d.replace("",e.displayName)),d}while(1<=n&&0<=r);break}}}finally{Ud=!1,Error.prepareStackTrace=a}return(a=e?e.displayName||e.name:"")?es(a):""}function QR(e){switch(e.tag){case 26:case 27:case 5:return es(e.type);case 16:return es("Lazy");case 13:return es("Suspense");case 19:return es("SuspenseList");case 0:case 15:return jd(e.type,!1);case 11:return jd(e.type.render,!1);case 1:return jd(e.type,!0);case 31:return es("Activity");default:return""}}function jv(e){try{var t="";do t+=QR(e),e=e.return;while(e);return t}catch(a){return` +`+u[n].replace(" at new "," at ");return e.displayName&&d.includes("")&&(d=d.replace("",e.displayName)),d}while(1<=n&&0<=r);break}}}finally{jd=!1,Error.prepareStackTrace=a}return(a=e?e.displayName||e.name:"")?ts(a):""}function GR(e){switch(e.tag){case 26:case 27:case 5:return ts(e.type);case 16:return ts("Lazy");case 13:return ts("Suspense");case 19:return ts("SuspenseList");case 0:case 15:return Fd(e.type,!1);case 11:return Fd(e.type.render,!1);case 1:return Fd(e.type,!0);case 31:return ts("Activity");default:return""}}function Fv(e){try{var t="";do t+=GR(e),e=e.return;while(e);return t}catch(a){return` Error generating stack: `+a.message+` -`+a.stack}}function oa(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function hy(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function VR(e){var t=hy(e)?"checked":"value",a=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),n=""+e[t];if(!e.hasOwnProperty(t)&&typeof a<"u"&&typeof a.get=="function"&&typeof a.set=="function"){var r=a.get,s=a.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return r.call(this)},set:function(i){n=""+i,s.call(this,i)}}),Object.defineProperty(e,t,{enumerable:a.enumerable}),{getValue:function(){return n},setValue:function(i){n=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function uu(e){e._valueTracker||(e._valueTracker=VR(e))}function vy(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var a=t.getValue(),n="";return e&&(n=hy(e)?e.checked?"true":"false":e.value),e=n,e!==a?(t.setValue(e),!0):!1}function cu(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var GR=/[\n"\\]/g;function ca(e){return e.replace(GR,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function xm(e,t,a,n,r,s,i,o){e.name="",i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"?e.type=i:e.removeAttribute("type"),t!=null?i==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+oa(t)):e.value!==""+oa(t)&&(e.value=""+oa(t)):i!=="submit"&&i!=="reset"||e.removeAttribute("value"),t!=null?$m(e,i,oa(t)):a!=null?$m(e,i,oa(a)):n!=null&&e.removeAttribute("value"),r==null&&s!=null&&(e.defaultChecked=!!s),r!=null&&(e.checked=r&&typeof r!="function"&&typeof r!="symbol"),o!=null&&typeof o!="function"&&typeof o!="symbol"&&typeof o!="boolean"?e.name=""+oa(o):e.removeAttribute("name")}function gy(e,t,a,n,r,s,i,o){if(s!=null&&typeof s!="function"&&typeof s!="symbol"&&typeof s!="boolean"&&(e.type=s),t!=null||a!=null){if(!(s!=="submit"&&s!=="reset"||t!=null))return;a=a!=null?""+oa(a):"",t=t!=null?""+oa(t):a,o||t===e.value||(e.value=t),e.defaultValue=t}n=n??r,n=typeof n!="function"&&typeof n!="symbol"&&!!n,e.checked=o?e.checked:!!n,e.defaultChecked=!!n,i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(e.name=i)}function $m(e,t,a){t==="number"&&cu(e.ownerDocument)===e||e.defaultValue===""+a||(e.defaultValue=""+a)}function vs(e,t,a,n){if(e=e.options,t){t={};for(var r=0;r"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Sm=!1;if(un)try{Xr={},Object.defineProperty(Xr,"passive",{get:function(){Sm=!0}}),window.addEventListener("test",Xr,Xr),window.removeEventListener("test",Xr,Xr)}catch{Sm=!1}var Xr,Un=null,hf=null,Gl=null;function wy(){if(Gl)return Gl;var e,t=hf,a=t.length,n,r="value"in Un?Un.value:Un.textContent,s=r.length;for(e=0;e=Bi),Hv=" ",Qv=!1;function Ny(e,t){switch(e){case"keyup":return wC.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function _y(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var os=!1;function NC(e,t){switch(e){case"compositionend":return _y(t);case"keypress":return t.which!==32?null:(Qv=!0,Hv);case"textInput":return e=t.data,e===Hv&&Qv?null:e;default:return null}}function _C(e,t){if(os)return e==="compositionend"||!gf&&Ny(e,t)?(e=wy(),Gl=hf=Un=null,os=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:a,offset:t-e};e=n}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=Jv(a)}}function Ey(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Ey(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ty(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=cu(e.document);t instanceof e.HTMLIFrameElement;){try{var a=typeof t.contentWindow.location.href=="string"}catch{a=!1}if(a)e=t.contentWindow;else break;t=cu(e.document)}return t}function yf(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var MC=un&&"documentMode"in document&&11>=document.documentMode,ls=null,Nm=null,Ii=null,_m=!1;function Zv(e,t,a){var n=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;_m||ls==null||ls!==cu(n)||(n=ls,"selectionStart"in n&&yf(n)?n={start:n.selectionStart,end:n.selectionEnd}:(n=(n.ownerDocument&&n.ownerDocument.defaultView||window).getSelection(),n={anchorNode:n.anchorNode,anchorOffset:n.anchorOffset,focusNode:n.focusNode,focusOffset:n.focusOffset}),Ii&&ro(Ii,n)||(Ii=n,n=Cu(Nm,"onSelect"),0>=i,r-=i,an=1<<32-Vt(t)+r|a<s?s:8;var i=ne.T,o={};ne.T=o,Ff(e,!1,t,a);try{var u=r(),c=ne.S;if(c!==null&&c(o,u),u!==null&&typeof u=="object"&&typeof u.then=="function"){var d=qC(u,n);Yi(e,t,d,Gt(e))}else Yi(e,t,n,Gt(e))}catch(m){Yi(e,t,{then:function(){},status:"rejected",reason:m},Gt())}finally{ve.p=s,ne.T=i}}function VC(){}function jm(e,t,a,n){if(e.tag!==5)throw Error(P(476));var r=hb(e).queue;pb(e,r,t,fr,a===null?VC:function(){return vb(e),a(n)})}function hb(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:fr,baseState:fr,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:cn,lastRenderedState:fr},next:null};var a={};return t.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:cn,lastRenderedState:a},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function vb(e){var t=hb(e).next.queue;Yi(e,t,{},Gt())}function jf(){return xt(mo)}function gb(){return Xe().memoizedState}function yb(){return Xe().memoizedState}function GC(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var a=Gt();e=Bn(a);var n=qn(t,e,a);n!==null&&(Yt(n,t,a),Qi(n,t,a)),t={cache:Nf()},e.payload=t;return}t=t.return}}function YC(e,t,a){var n=Gt();a={lane:n,revertLane:0,action:a,hasEagerState:!1,eagerState:null,next:null},Ku(e)?xb(t,a):(a=xf(e,t,a,n),a!==null&&(Yt(a,e,n),$b(a,t,n)))}function bb(e,t,a){var n=Gt();Yi(e,t,a,n)}function Yi(e,t,a,n){var r={lane:n,revertLane:0,action:a,hasEagerState:!1,eagerState:null,next:null};if(Ku(e))xb(t,r);else{var s=e.alternate;if(e.lanes===0&&(s===null||s.lanes===0)&&(s=t.lastRenderedReducer,s!==null))try{var i=t.lastRenderedState,o=s(i,a);if(r.hasEagerState=!0,r.eagerState=o,Jt(o,i))return zu(e,t,r,0),Ee===null&&Fu(),!1}catch{}finally{}if(a=xf(e,t,r,n),a!==null)return Yt(a,e,n),$b(a,t,n),!0}return!1}function Ff(e,t,a,n){if(n={lane:2,revertLane:Vf(),action:n,hasEagerState:!1,eagerState:null,next:null},Ku(e)){if(t)throw Error(P(479))}else t=xf(e,a,n,2),t!==null&&Yt(t,e,2)}function Ku(e){var t=e.alternate;return e===ie||t!==null&&t===ie}function xb(e,t){bs=vu=!0;var a=e.pending;a===null?t.next=t:(t.next=a.next,a.next=t),e.pending=t}function $b(e,t,a){if((a&4194048)!==0){var n=t.lanes;n&=e.pendingLanes,a|=n,t.lanes=a,dy(e,a)}}var yu={readContext:xt,use:qu,useCallback:Ve,useContext:Ve,useEffect:Ve,useImperativeHandle:Ve,useLayoutEffect:Ve,useInsertionEffect:Ve,useMemo:Ve,useReducer:Ve,useRef:Ve,useState:Ve,useDebugValue:Ve,useDeferredValue:Ve,useTransition:Ve,useSyncExternalStore:Ve,useId:Ve,useHostTransitionStatus:Ve,useFormState:Ve,useActionState:Ve,useOptimistic:Ve,useMemoCache:Ve,useCacheRefresh:Ve},wb={readContext:xt,use:qu,useCallback:function(e,t){return Ot().memoizedState=[e,t===void 0?null:t],e},useContext:xt,useEffect:pg,useImperativeHandle:function(e,t,a){a=a!=null?a.concat([e]):null,Wl(4194308,4,ub.bind(null,t,e),a)},useLayoutEffect:function(e,t){return Wl(4194308,4,e,t)},useInsertionEffect:function(e,t){Wl(4,2,e,t)},useMemo:function(e,t){var a=Ot();t=t===void 0?null:t;var n=e();if(wr){Pn(!0);try{e()}finally{Pn(!1)}}return a.memoizedState=[n,t],n},useReducer:function(e,t,a){var n=Ot();if(a!==void 0){var r=a(t);if(wr){Pn(!0);try{a(t)}finally{Pn(!1)}}}else r=t;return n.memoizedState=n.baseState=r,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:r},n.queue=e,e=e.dispatch=YC.bind(null,ie,e),[n.memoizedState,e]},useRef:function(e){var t=Ot();return e={current:e},t.memoizedState=e},useState:function(e){e=Pm(e);var t=e.queue,a=bb.bind(null,ie,t);return t.dispatch=a,[e.memoizedState,a]},useDebugValue:Pf,useDeferredValue:function(e,t){var a=Ot();return Uf(a,e,t)},useTransition:function(){var e=Pm(!1);return e=pb.bind(null,ie,e.queue,!0,!1),Ot().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,a){var n=ie,r=Ot();if(he){if(a===void 0)throw Error(P(407));a=a()}else{if(a=t(),Ee===null)throw Error(P(349));(de&124)!==0||Yy(n,t,a)}r.memoizedState=a;var s={value:a,getSnapshot:t};return r.queue=s,pg(Xy.bind(null,n,s,e),[e]),n.flags|=2048,Cs(9,Iu(),Jy.bind(null,n,s,a,t),null),a},useId:function(){var e=Ot(),t=Ee.identifierPrefix;if(he){var a=nn,n=an;a=(n&~(1<<32-Vt(n)-1)).toString(32)+a,t="\xAB"+t+"R"+a,a=gu++,0T?(A=_,_=null):A=_.sibling;var O=f(g,_,b[T],$);if(O===null){_===null&&(_=A);break}e&&_&&O.alternate===null&&t(g,_),v=s(O,v,T),R===null?S=O:R.sibling=O,R=O,_=A}if(T===b.length)return a(g,_),he&&dr(g,T),S;if(_===null){for(;TT?(A=_,_=null):A=_.sibling;var U=f(g,_,O.value,$);if(U===null){_===null&&(_=A);break}e&&_&&U.alternate===null&&t(g,_),v=s(U,v,T),R===null?S=U:R.sibling=U,R=U,_=A}if(O.done)return a(g,_),he&&dr(g,T),S;if(_===null){for(;!O.done;T++,O=b.next())O=m(g,O.value,$),O!==null&&(v=s(O,v,T),R===null?S=O:R.sibling=O,R=O);return he&&dr(g,T),S}for(_=n(_);!O.done;T++,O=b.next())O=p(_,g,T,O.value,$),O!==null&&(e&&O.alternate!==null&&_.delete(O.key===null?T:O.key),v=s(O,v,T),R===null?S=O:R.sibling=O,R=O);return e&&_.forEach(function(C){return t(g,C)}),he&&dr(g,T),S}function w(g,v,b,$){if(typeof b=="object"&&b!==null&&b.type===ns&&b.key===null&&(b=b.props.children),typeof b=="object"&&b!==null){switch(b.$$typeof){case El:e:{for(var S=b.key;v!==null;){if(v.key===S){if(S=b.type,S===ns){if(v.tag===7){a(g,v.sibling),$=r(v,b.props.children),$.return=g,g=$;break e}}else if(v.elementType===S||typeof S=="object"&&S!==null&&S.$$typeof===Tn&&hg(S)===v.type){a(g,v.sibling),$=r(v,b.props),Di($,b),$.return=g,g=$;break e}a(g,v);break}else t(g,v);v=v.sibling}b.type===ns?($=pr(b.props.children,g.mode,$,b.key),$.return=g,g=$):($=Jl(b.type,b.key,b.props,null,g.mode,$),Di($,b),$.return=g,g=$)}return i(g);case Ui:e:{for(S=b.key;v!==null;){if(v.key===S)if(v.tag===4&&v.stateNode.containerInfo===b.containerInfo&&v.stateNode.implementation===b.implementation){a(g,v.sibling),$=r(v,b.children||[]),$.return=g,g=$;break e}else{a(g,v);break}else t(g,v);v=v.sibling}$=Qd(b,g.mode,$),$.return=g,g=$}return i(g);case Tn:return S=b._init,b=S(b._payload),w(g,v,b,$)}if(ji(b))return x(g,v,b,$);if(Ei(b)){if(S=Ei(b),typeof S!="function")throw Error(P(150));return b=S.call(b),y(g,v,b,$)}if(typeof b.then=="function")return w(g,v,jl(b),$);if(b.$$typeof===tn)return w(g,v,Pl(g,b),$);Fl(g,b)}return typeof b=="string"&&b!==""||typeof b=="number"||typeof b=="bigint"?(b=""+b,v!==null&&v.tag===6?(a(g,v.sibling),$=r(v,b),$.return=g,g=$):(a(g,v),$=Hd(b,g.mode,$),$.return=g,g=$),i(g)):a(g,v)}return function(g,v,b,$){try{oo=0;var S=w(g,v,b,$);return $s=null,S}catch(_){if(_===_o||_===Bu)throw _;var R=Ht(29,_,null,g.mode);return R.lanes=$,R.return=g,R}finally{}}}var Es=Nb(!0),_b=Nb(!1),fa=ja(null),Ua=null;function Mn(e){var t=e.alternate;Pe(nt,nt.current&1),Pe(fa,e),Ua===null&&(t===null||Rs.current!==null||t.memoizedState!==null)&&(Ua=e)}function kb(e){if(e.tag===22){if(Pe(nt,nt.current),Pe(fa,e),Ua===null){var t=e.alternate;t!==null&&t.memoizedState!==null&&(Ua=e)}}else On(e)}function On(){Pe(nt,nt.current),Pe(fa,fa.current)}function sn(e){dt(fa),Ua===e&&(Ua=null),dt(nt)}var nt=ja(0);function bu(e){for(var t=e;t!==null;){if(t.tag===13){var a=t.memoizedState;if(a!==null&&(a=a.dehydrated,a===null||a.data==="$?"||tf(a)))return t}else if(t.tag===19&&t.memoizedProps.revealOrder!==void 0){if((t.flags&128)!==0)return t}else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function Yd(e,t,a,n){t=e.memoizedState,a=a(n,t),a=a==null?t:De({},t,a),e.memoizedState=a,e.lanes===0&&(e.updateQueue.baseState=a)}var Fm={enqueueSetState:function(e,t,a){e=e._reactInternals;var n=Gt(),r=Bn(n);r.payload=t,a!=null&&(r.callback=a),t=qn(e,r,n),t!==null&&(Yt(t,e,n),Qi(t,e,n))},enqueueReplaceState:function(e,t,a){e=e._reactInternals;var n=Gt(),r=Bn(n);r.tag=1,r.payload=t,a!=null&&(r.callback=a),t=qn(e,r,n),t!==null&&(Yt(t,e,n),Qi(t,e,n))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var a=Gt(),n=Bn(a);n.tag=2,t!=null&&(n.callback=t),t=qn(e,n,a),t!==null&&(Yt(t,e,a),Qi(t,e,a))}};function vg(e,t,a,n,r,s,i){return e=e.stateNode,typeof e.shouldComponentUpdate=="function"?e.shouldComponentUpdate(n,s,i):t.prototype&&t.prototype.isPureReactComponent?!ro(a,n)||!ro(r,s):!0}function gg(e,t,a,n){e=t.state,typeof t.componentWillReceiveProps=="function"&&t.componentWillReceiveProps(a,n),typeof t.UNSAFE_componentWillReceiveProps=="function"&&t.UNSAFE_componentWillReceiveProps(a,n),t.state!==e&&Fm.enqueueReplaceState(t,t.state,null)}function Sr(e,t){var a=t;if("ref"in t){a={};for(var n in t)n!=="ref"&&(a[n]=t[n])}if(e=e.defaultProps){a===t&&(a=De({},a));for(var r in e)a[r]===void 0&&(a[r]=e[r])}return a}var xu=typeof reportError=="function"?reportError:function(e){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof e=="object"&&e!==null&&typeof e.message=="string"?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",e);return}console.error(e)};function Rb(e){xu(e)}function Cb(e){console.error(e)}function Eb(e){xu(e)}function $u(e,t){try{var a=e.onUncaughtError;a(t.value,{componentStack:t.stack})}catch(n){setTimeout(function(){throw n})}}function yg(e,t,a){try{var n=e.onCaughtError;n(a.value,{componentStack:a.stack,errorBoundary:t.tag===1?t.stateNode:null})}catch(r){setTimeout(function(){throw r})}}function zm(e,t,a){return a=Bn(a),a.tag=3,a.payload={element:null},a.callback=function(){$u(e,t)},a}function Tb(e){return e=Bn(e),e.tag=3,e}function Ab(e,t,a,n){var r=a.type.getDerivedStateFromError;if(typeof r=="function"){var s=n.value;e.payload=function(){return r(s)},e.callback=function(){yg(t,a,n)}}var i=a.stateNode;i!==null&&typeof i.componentDidCatch=="function"&&(e.callback=function(){yg(t,a,n),typeof r!="function"&&(In===null?In=new Set([this]):In.add(this));var o=n.stack;this.componentDidCatch(n.value,{componentStack:o!==null?o:""})})}function XC(e,t,a,n,r){if(a.flags|=32768,n!==null&&typeof n=="object"&&typeof n.then=="function"){if(t=a.alternate,t!==null&&So(t,a,r,!0),a=fa.current,a!==null){switch(a.tag){case 13:return Ua===null?Gm():a.alternate===null&&Ke===0&&(Ke=3),a.flags&=-257,a.flags|=65536,a.lanes=r,n===Dm?a.flags|=16384:(t=a.updateQueue,t===null?a.updateQueue=new Set([n]):t.add(n),im(e,n,r)),!1;case 22:return a.flags|=65536,n===Dm?a.flags|=16384:(t=a.updateQueue,t===null?(t={transitions:null,markerInstances:null,retryQueue:new Set([n])},a.updateQueue=t):(a=t.retryQueue,a===null?t.retryQueue=new Set([n]):a.add(n)),im(e,n,r)),!1}throw Error(P(435,a.tag))}return im(e,n,r),Gm(),!1}if(he)return t=fa.current,t!==null?((t.flags&65536)===0&&(t.flags|=256),t.flags|=65536,t.lanes=r,n!==Rm&&(e=Error(P(422),{cause:n}),so(da(e,a)))):(n!==Rm&&(t=Error(P(423),{cause:n}),so(da(t,a))),e=e.current.alternate,e.flags|=65536,r&=-r,e.lanes|=r,n=da(n,a),r=zm(e.stateNode,n,r),Vd(e,r),Ke!==4&&(Ke=2)),!1;var s=Error(P(520),{cause:n});if(s=da(s,a),Zi===null?Zi=[s]:Zi.push(s),Ke!==4&&(Ke=2),t===null)return!0;n=da(n,a),a=t;do{switch(a.tag){case 3:return a.flags|=65536,e=r&-r,a.lanes|=e,e=zm(a.stateNode,n,e),Vd(a,e),!1;case 1:if(t=a.type,s=a.stateNode,(a.flags&128)===0&&(typeof t.getDerivedStateFromError=="function"||s!==null&&typeof s.componentDidCatch=="function"&&(In===null||!In.has(s))))return a.flags|=65536,r&=-r,a.lanes|=r,r=Tb(r),Ab(r,e,a,n),Vd(a,r),!1}a=a.return}while(a!==null);return!1}var Db=Error(P(461)),ct=!1;function pt(e,t,a,n){t.child=e===null?_b(t,null,a,n):Es(t,e.child,a,n)}function bg(e,t,a,n,r){a=a.render;var s=t.ref;if("ref"in n){var i={};for(var o in n)o!=="ref"&&(i[o]=n[o])}else i=n;return $r(t),n=Ef(e,t,a,i,s,r),o=Tf(),e!==null&&!ct?(Af(e,t,r),dn(e,t,r)):(he&&o&&wf(t),t.flags|=1,pt(e,t,n,r),t.child)}function xg(e,t,a,n,r){if(e===null){var s=a.type;return typeof s=="function"&&!$f(s)&&s.defaultProps===void 0&&a.compare===null?(t.tag=15,t.type=s,Mb(e,t,s,n,r)):(e=Jl(a.type,null,n,t,t.mode,r),e.ref=t.ref,e.return=t,t.child=e)}if(s=e.child,!zf(e,r)){var i=s.memoizedProps;if(a=a.compare,a=a!==null?a:ro,a(i,n)&&e.ref===t.ref)return dn(e,t,r)}return t.flags|=1,e=on(s,n),e.ref=t.ref,e.return=t,t.child=e}function Mb(e,t,a,n,r){if(e!==null){var s=e.memoizedProps;if(ro(s,n)&&e.ref===t.ref)if(ct=!1,t.pendingProps=n=s,zf(e,r))(e.flags&131072)!==0&&(ct=!0);else return t.lanes=e.lanes,dn(e,t,r)}return Bm(e,t,a,n,r)}function Ob(e,t,a){var n=t.pendingProps,r=n.children,s=e!==null?e.memoizedState:null;if(n.mode==="hidden"){if((t.flags&128)!==0){if(n=s!==null?s.baseLanes|a:a,e!==null){for(r=t.child=e.child,s=0;r!==null;)s=s|r.lanes|r.childLanes,r=r.sibling;t.childLanes=s&~n}else t.childLanes=0,t.child=null;return $g(e,t,n,a)}if((a&536870912)!==0)t.memoizedState={baseLanes:0,cachePool:null},e!==null&&Xl(t,s!==null?s.cachePool:null),s!==null?lg(t,s):Lm(),kb(t);else return t.lanes=t.childLanes=536870912,$g(e,t,s!==null?s.baseLanes|a:a,a)}else s!==null?(Xl(t,s.cachePool),lg(t,s),On(t),t.memoizedState=null):(e!==null&&Xl(t,null),Lm(),On(t));return pt(e,t,r,a),t.child}function $g(e,t,a,n){var r=_f();return r=r===null?null:{parent:at._currentValue,pool:r},t.memoizedState={baseLanes:a,cachePool:r},e!==null&&Xl(t,null),Lm(),kb(t),e!==null&&So(e,t,n,!0),null}function eu(e,t){var a=t.ref;if(a===null)e!==null&&e.ref!==null&&(t.flags|=4194816);else{if(typeof a!="function"&&typeof a!="object")throw Error(P(284));(e===null||e.ref!==a)&&(t.flags|=4194816)}}function Bm(e,t,a,n,r){return $r(t),a=Ef(e,t,a,n,void 0,r),n=Tf(),e!==null&&!ct?(Af(e,t,r),dn(e,t,r)):(he&&n&&wf(t),t.flags|=1,pt(e,t,a,r),t.child)}function wg(e,t,a,n,r,s){return $r(t),t.updateQueue=null,a=Vy(t,n,a,r),Qy(e),n=Tf(),e!==null&&!ct?(Af(e,t,s),dn(e,t,s)):(he&&n&&wf(t),t.flags|=1,pt(e,t,a,s),t.child)}function Sg(e,t,a,n,r){if($r(t),t.stateNode===null){var s=ds,i=a.contextType;typeof i=="object"&&i!==null&&(s=xt(i)),s=new a(n,s),t.memoizedState=s.state!==null&&s.state!==void 0?s.state:null,s.updater=Fm,t.stateNode=s,s._reactInternals=t,s=t.stateNode,s.props=n,s.state=t.memoizedState,s.refs={},kf(t),i=a.contextType,s.context=typeof i=="object"&&i!==null?xt(i):ds,s.state=t.memoizedState,i=a.getDerivedStateFromProps,typeof i=="function"&&(Yd(t,a,i,n),s.state=t.memoizedState),typeof a.getDerivedStateFromProps=="function"||typeof s.getSnapshotBeforeUpdate=="function"||typeof s.UNSAFE_componentWillMount!="function"&&typeof s.componentWillMount!="function"||(i=s.state,typeof s.componentWillMount=="function"&&s.componentWillMount(),typeof s.UNSAFE_componentWillMount=="function"&&s.UNSAFE_componentWillMount(),i!==s.state&&Fm.enqueueReplaceState(s,s.state,null),Gi(t,n,s,r),Vi(),s.state=t.memoizedState),typeof s.componentDidMount=="function"&&(t.flags|=4194308),n=!0}else if(e===null){s=t.stateNode;var o=t.memoizedProps,u=Sr(a,o);s.props=u;var c=s.context,d=a.contextType;i=ds,typeof d=="object"&&d!==null&&(i=xt(d));var m=a.getDerivedStateFromProps;d=typeof m=="function"||typeof s.getSnapshotBeforeUpdate=="function",o=t.pendingProps!==o,d||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(o||c!==i)&&gg(t,s,n,i),An=!1;var f=t.memoizedState;s.state=f,Gi(t,n,s,r),Vi(),c=t.memoizedState,o||f!==c||An?(typeof m=="function"&&(Yd(t,a,m,n),c=t.memoizedState),(u=An||vg(t,a,u,n,f,c,i))?(d||typeof s.UNSAFE_componentWillMount!="function"&&typeof s.componentWillMount!="function"||(typeof s.componentWillMount=="function"&&s.componentWillMount(),typeof s.UNSAFE_componentWillMount=="function"&&s.UNSAFE_componentWillMount()),typeof s.componentDidMount=="function"&&(t.flags|=4194308)):(typeof s.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=n,t.memoizedState=c),s.props=n,s.state=c,s.context=i,n=u):(typeof s.componentDidMount=="function"&&(t.flags|=4194308),n=!1)}else{s=t.stateNode,Mm(e,t),i=t.memoizedProps,d=Sr(a,i),s.props=d,m=t.pendingProps,f=s.context,c=a.contextType,u=ds,typeof c=="object"&&c!==null&&(u=xt(c)),o=a.getDerivedStateFromProps,(c=typeof o=="function"||typeof s.getSnapshotBeforeUpdate=="function")||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(i!==m||f!==u)&&gg(t,s,n,u),An=!1,f=t.memoizedState,s.state=f,Gi(t,n,s,r),Vi();var p=t.memoizedState;i!==m||f!==p||An||e!==null&&e.dependencies!==null&&pu(e.dependencies)?(typeof o=="function"&&(Yd(t,a,o,n),p=t.memoizedState),(d=An||vg(t,a,d,n,f,p,u)||e!==null&&e.dependencies!==null&&pu(e.dependencies))?(c||typeof s.UNSAFE_componentWillUpdate!="function"&&typeof s.componentWillUpdate!="function"||(typeof s.componentWillUpdate=="function"&&s.componentWillUpdate(n,p,u),typeof s.UNSAFE_componentWillUpdate=="function"&&s.UNSAFE_componentWillUpdate(n,p,u)),typeof s.componentDidUpdate=="function"&&(t.flags|=4),typeof s.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof s.componentDidUpdate!="function"||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=1024),t.memoizedProps=n,t.memoizedState=p),s.props=n,s.state=p,s.context=u,n=d):(typeof s.componentDidUpdate!="function"||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=1024),n=!1)}return s=n,eu(e,t),n=(t.flags&128)!==0,s||n?(s=t.stateNode,a=n&&typeof a.getDerivedStateFromError!="function"?null:s.render(),t.flags|=1,e!==null&&n?(t.child=Es(t,e.child,null,r),t.child=Es(t,null,a,r)):pt(e,t,a,r),t.memoizedState=s.state,e=t.child):e=dn(e,t,r),e}function Ng(e,t,a,n){return wo(),t.flags|=256,pt(e,t,a,n),t.child}var Jd={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function Xd(e){return{baseLanes:e,cachePool:By()}}function Zd(e,t,a){return e=e!==null?e.childLanes&~a:0,t&&(e|=ma),e}function Lb(e,t,a){var n=t.pendingProps,r=!1,s=(t.flags&128)!==0,i;if((i=s)||(i=e!==null&&e.memoizedState===null?!1:(nt.current&2)!==0),i&&(r=!0,t.flags&=-129),i=(t.flags&32)!==0,t.flags&=-33,e===null){if(he){if(r?Mn(t):On(t),he){var o=Ie,u;if(u=o){e:{for(u=o,o=Ma;u.nodeType!==8;){if(!o){o=null;break e}if(u=xa(u.nextSibling),u===null){o=null;break e}}o=u}o!==null?(t.memoizedState={dehydrated:o,treeContext:hr!==null?{id:an,overflow:nn}:null,retryLane:536870912,hydrationErrors:null},u=Ht(18,null,null,0),u.stateNode=o,u.return=t,t.child=u,Rt=t,Ie=null,u=!0):u=!1}u||xr(t)}if(o=t.memoizedState,o!==null&&(o=o.dehydrated,o!==null))return tf(o)?t.lanes=32:t.lanes=536870912,null;sn(t)}return o=n.children,n=n.fallback,r?(On(t),r=t.mode,o=wu({mode:"hidden",children:o},r),n=pr(n,r,a,null),o.return=t,n.return=t,o.sibling=n,t.child=o,r=t.child,r.memoizedState=Xd(a),r.childLanes=Zd(e,i,a),t.memoizedState=Jd,n):(Mn(t),qm(t,o))}if(u=e.memoizedState,u!==null&&(o=u.dehydrated,o!==null)){if(s)t.flags&256?(Mn(t),t.flags&=-257,t=Wd(e,t,a)):t.memoizedState!==null?(On(t),t.child=e.child,t.flags|=128,t=null):(On(t),r=n.fallback,o=t.mode,n=wu({mode:"visible",children:n.children},o),r=pr(r,o,a,null),r.flags|=2,n.return=t,r.return=t,n.sibling=r,t.child=n,Es(t,e.child,null,a),n=t.child,n.memoizedState=Xd(a),n.childLanes=Zd(e,i,a),t.memoizedState=Jd,t=r);else if(Mn(t),tf(o)){if(i=o.nextSibling&&o.nextSibling.dataset,i)var c=i.dgst;i=c,n=Error(P(419)),n.stack="",n.digest=i,so({value:n,source:null,stack:null}),t=Wd(e,t,a)}else if(ct||So(e,t,a,!1),i=(a&e.childLanes)!==0,ct||i){if(i=Ee,i!==null&&(n=a&-a,n=(n&42)!==0?1:cf(n),n=(n&(i.suspendedLanes|a))!==0?0:n,n!==0&&n!==u.retryLane))throw u.retryLane=n,Ps(e,n),Yt(i,e,n),Db;o.data==="$?"||Gm(),t=Wd(e,t,a)}else o.data==="$?"?(t.flags|=192,t.child=e.child,t=null):(e=u.treeContext,Ie=xa(o.nextSibling),Rt=t,he=!0,vr=null,Ma=!1,e!==null&&(la[ua++]=an,la[ua++]=nn,la[ua++]=hr,an=e.id,nn=e.overflow,hr=t),t=qm(t,n.children),t.flags|=4096);return t}return r?(On(t),r=n.fallback,o=t.mode,u=e.child,c=u.sibling,n=on(u,{mode:"hidden",children:n.children}),n.subtreeFlags=u.subtreeFlags&65011712,c!==null?r=on(c,r):(r=pr(r,o,a,null),r.flags|=2),r.return=t,n.return=t,n.sibling=r,t.child=n,n=r,r=t.child,o=e.child.memoizedState,o===null?o=Xd(a):(u=o.cachePool,u!==null?(c=at._currentValue,u=u.parent!==c?{parent:c,pool:c}:u):u=By(),o={baseLanes:o.baseLanes|a,cachePool:u}),r.memoizedState=o,r.childLanes=Zd(e,i,a),t.memoizedState=Jd,n):(Mn(t),a=e.child,e=a.sibling,a=on(a,{mode:"visible",children:n.children}),a.return=t,a.sibling=null,e!==null&&(i=t.deletions,i===null?(t.deletions=[e],t.flags|=16):i.push(e)),t.child=a,t.memoizedState=null,a)}function qm(e,t){return t=wu({mode:"visible",children:t},e.mode),t.return=e,e.child=t}function wu(e,t){return e=Ht(22,e,null,t),e.lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function Wd(e,t,a){return Es(t,e.child,null,a),e=qm(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function _g(e,t,a){e.lanes|=t;var n=e.alternate;n!==null&&(n.lanes|=t),Em(e.return,t,a)}function em(e,t,a,n,r){var s=e.memoizedState;s===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:n,tail:a,tailMode:r}:(s.isBackwards=t,s.rendering=null,s.renderingStartTime=0,s.last=n,s.tail=a,s.tailMode=r)}function Pb(e,t,a){var n=t.pendingProps,r=n.revealOrder,s=n.tail;if(pt(e,t,n.children,a),n=nt.current,(n&2)!==0)n=n&1|2,t.flags|=128;else{if(e!==null&&(e.flags&128)!==0)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&_g(e,a,t);else if(e.tag===19)_g(e,a,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}n&=1}switch(Pe(nt,n),r){case"forwards":for(a=t.child,r=null;a!==null;)e=a.alternate,e!==null&&bu(e)===null&&(r=a),a=a.sibling;a=r,a===null?(r=t.child,t.child=null):(r=a.sibling,a.sibling=null),em(t,!1,r,a,s);break;case"backwards":for(a=null,r=t.child,t.child=null;r!==null;){if(e=r.alternate,e!==null&&bu(e)===null){t.child=r;break}e=r.sibling,r.sibling=a,a=r,r=e}em(t,!0,a,null,s);break;case"together":em(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function dn(e,t,a){if(e!==null&&(t.dependencies=e.dependencies),Jn|=t.lanes,(a&t.childLanes)===0)if(e!==null){if(So(e,t,a,!1),(a&t.childLanes)===0)return null}else return null;if(e!==null&&t.child!==e.child)throw Error(P(153));if(t.child!==null){for(e=t.child,a=on(e,e.pendingProps),t.child=a,a.return=t;e.sibling!==null;)e=e.sibling,a=a.sibling=on(e,e.pendingProps),a.return=t;a.sibling=null}return t.child}function zf(e,t){return(e.lanes&t)!==0?!0:(e=e.dependencies,!!(e!==null&&pu(e)))}function ZC(e,t,a){switch(t.tag){case 3:iu(t,t.stateNode.containerInfo),Dn(t,at,e.memoizedState.cache),wo();break;case 27:case 5:gm(t);break;case 4:iu(t,t.stateNode.containerInfo);break;case 10:Dn(t,t.type,t.memoizedProps.value);break;case 13:var n=t.memoizedState;if(n!==null)return n.dehydrated!==null?(Mn(t),t.flags|=128,null):(a&t.child.childLanes)!==0?Lb(e,t,a):(Mn(t),e=dn(e,t,a),e!==null?e.sibling:null);Mn(t);break;case 19:var r=(e.flags&128)!==0;if(n=(a&t.childLanes)!==0,n||(So(e,t,a,!1),n=(a&t.childLanes)!==0),r){if(n)return Pb(e,t,a);t.flags|=128}if(r=t.memoizedState,r!==null&&(r.rendering=null,r.tail=null,r.lastEffect=null),Pe(nt,nt.current),n)break;return null;case 22:case 23:return t.lanes=0,Ob(e,t,a);case 24:Dn(t,at,e.memoizedState.cache)}return dn(e,t,a)}function Ub(e,t,a){if(e!==null)if(e.memoizedProps!==t.pendingProps)ct=!0;else{if(!zf(e,a)&&(t.flags&128)===0)return ct=!1,ZC(e,t,a);ct=(e.flags&131072)!==0}else ct=!1,he&&(t.flags&1048576)!==0&&Fy(t,fu,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var n=t.elementType,r=n._init;if(n=r(n._payload),t.type=n,typeof n=="function")$f(n)?(e=Sr(n,e),t.tag=1,t=Sg(null,t,n,e,a)):(t.tag=0,t=Bm(null,t,n,e,a));else{if(n!=null){if(r=n.$$typeof,r===of){t.tag=11,t=bg(null,t,n,e,a);break e}else if(r===lf){t.tag=14,t=xg(null,t,n,e,a);break e}}throw t=hm(n)||n,Error(P(306,t,""))}}return t;case 0:return Bm(e,t,t.type,t.pendingProps,a);case 1:return n=t.type,r=Sr(n,t.pendingProps),Sg(e,t,n,r,a);case 3:e:{if(iu(t,t.stateNode.containerInfo),e===null)throw Error(P(387));n=t.pendingProps;var s=t.memoizedState;r=s.element,Mm(e,t),Gi(t,n,null,a);var i=t.memoizedState;if(n=i.cache,Dn(t,at,n),n!==s.cache&&Tm(t,[at],a,!0),Vi(),n=i.element,s.isDehydrated)if(s={element:n,isDehydrated:!1,cache:i.cache},t.updateQueue.baseState=s,t.memoizedState=s,t.flags&256){t=Ng(e,t,n,a);break e}else if(n!==r){r=da(Error(P(424)),t),so(r),t=Ng(e,t,n,a);break e}else{switch(e=t.stateNode.containerInfo,e.nodeType){case 9:e=e.body;break;default:e=e.nodeName==="HTML"?e.ownerDocument.body:e}for(Ie=xa(e.firstChild),Rt=t,he=!0,vr=null,Ma=!0,a=_b(t,null,n,a),t.child=a;a;)a.flags=a.flags&-3|4096,a=a.sibling}else{if(wo(),n===r){t=dn(e,t,a);break e}pt(e,t,n,a)}t=t.child}return t;case 26:return eu(e,t),e===null?(a=Kg(t.type,null,t.pendingProps,null))?t.memoizedState=a:he||(a=t.type,e=t.pendingProps,n=Eu(zn.current).createElement(a),n[bt]=t,n[Ut]=e,vt(n,a,e),ut(n),t.stateNode=n):t.memoizedState=Kg(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return gm(t),e===null&&he&&(n=t.stateNode=S0(t.type,t.pendingProps,zn.current),Rt=t,Ma=!0,r=Ie,Zn(t.type)?(af=r,Ie=xa(n.firstChild)):Ie=r),pt(e,t,t.pendingProps.children,a),eu(e,t),e===null&&(t.flags|=4194304),t.child;case 5:return e===null&&he&&((r=n=Ie)&&(n=NE(n,t.type,t.pendingProps,Ma),n!==null?(t.stateNode=n,Rt=t,Ie=xa(n.firstChild),Ma=!1,r=!0):r=!1),r||xr(t)),gm(t),r=t.type,s=t.pendingProps,i=e!==null?e.memoizedProps:null,n=s.children,Wm(r,s)?n=null:i!==null&&Wm(r,i)&&(t.flags|=32),t.memoizedState!==null&&(r=Ef(e,t,KC,null,null,a),mo._currentValue=r),eu(e,t),pt(e,t,n,a),t.child;case 6:return e===null&&he&&((e=a=Ie)&&(a=_E(a,t.pendingProps,Ma),a!==null?(t.stateNode=a,Rt=t,Ie=null,e=!0):e=!1),e||xr(t)),null;case 13:return Lb(e,t,a);case 4:return iu(t,t.stateNode.containerInfo),n=t.pendingProps,e===null?t.child=Es(t,null,n,a):pt(e,t,n,a),t.child;case 11:return bg(e,t,t.type,t.pendingProps,a);case 7:return pt(e,t,t.pendingProps,a),t.child;case 8:return pt(e,t,t.pendingProps.children,a),t.child;case 12:return pt(e,t,t.pendingProps.children,a),t.child;case 10:return n=t.pendingProps,Dn(t,t.type,n.value),pt(e,t,n.children,a),t.child;case 9:return r=t.type._context,n=t.pendingProps.children,$r(t),r=xt(r),n=n(r),t.flags|=1,pt(e,t,n,a),t.child;case 14:return xg(e,t,t.type,t.pendingProps,a);case 15:return Mb(e,t,t.type,t.pendingProps,a);case 19:return Pb(e,t,a);case 31:return n=t.pendingProps,a=t.mode,n={mode:n.mode,children:n.children},e===null?(a=wu(n,a),a.ref=t.ref,t.child=a,a.return=t,t=a):(a=on(e.child,n),a.ref=t.ref,t.child=a,a.return=t,t=a),t;case 22:return Ob(e,t,a);case 24:return $r(t),n=xt(at),e===null?(r=_f(),r===null&&(r=Ee,s=Nf(),r.pooledCache=s,s.refCount++,s!==null&&(r.pooledCacheLanes|=a),r=s),t.memoizedState={parent:n,cache:r},kf(t),Dn(t,at,r)):((e.lanes&a)!==0&&(Mm(e,t),Gi(t,null,null,a),Vi()),r=e.memoizedState,s=t.memoizedState,r.parent!==n?(r={parent:n,cache:n},t.memoizedState=r,t.lanes===0&&(t.memoizedState=t.updateQueue.baseState=r),Dn(t,at,n)):(n=s.cache,Dn(t,at,n),n!==r.cache&&Tm(t,[at],a,!0))),pt(e,t,t.pendingProps.children,a),t.child;case 29:throw t.pendingProps}throw Error(P(156,t.tag))}function Za(e){e.flags|=4}function kg(e,t){if(t.type!=="stylesheet"||(t.state.loading&4)!==0)e.flags&=-16777217;else if(e.flags|=16777216,!k0(t)){if(t=fa.current,t!==null&&((de&4194048)===de?Ua!==null:(de&62914560)!==de&&(de&536870912)===0||t!==Ua))throw Hi=Dm,qy;e.flags|=8192}}function zl(e,t){t!==null&&(e.flags|=4),e.flags&16384&&(t=e.tag!==22?uy():536870912,e.lanes|=t,Ts|=t)}function Mi(e,t){if(!he)switch(e.tailMode){case"hidden":t=e.tail;for(var a=null;t!==null;)t.alternate!==null&&(a=t),t=t.sibling;a===null?e.tail=null:a.sibling=null;break;case"collapsed":a=e.tail;for(var n=null;a!==null;)a.alternate!==null&&(n=a),a=a.sibling;n===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:n.sibling=null}}function Fe(e){var t=e.alternate!==null&&e.alternate.child===e.child,a=0,n=0;if(t)for(var r=e.child;r!==null;)a|=r.lanes|r.childLanes,n|=r.subtreeFlags&65011712,n|=r.flags&65011712,r.return=e,r=r.sibling;else for(r=e.child;r!==null;)a|=r.lanes|r.childLanes,n|=r.subtreeFlags,n|=r.flags,r.return=e,r=r.sibling;return e.subtreeFlags|=n,e.childLanes=a,t}function WC(e,t,a){var n=t.pendingProps;switch(Sf(t),t.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Fe(t),null;case 1:return Fe(t),null;case 3:return a=t.stateNode,n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),ln(at),Ss(),a.pendingContext&&(a.context=a.pendingContext,a.pendingContext=null),(e===null||e.child===null)&&(Ai(t)?Za(t):e===null||e.memoizedState.isDehydrated&&(t.flags&256)===0||(t.flags|=1024,ag())),Fe(t),null;case 26:return a=t.memoizedState,e===null?(Za(t),a!==null?(Fe(t),kg(t,a)):(Fe(t),t.flags&=-16777217)):a?a!==e.memoizedState?(Za(t),Fe(t),kg(t,a)):(Fe(t),t.flags&=-16777217):(e.memoizedProps!==n&&Za(t),Fe(t),t.flags&=-16777217),null;case 27:ou(t),a=zn.current;var r=t.type;if(e!==null&&t.stateNode!=null)e.memoizedProps!==n&&Za(t);else{if(!n){if(t.stateNode===null)throw Error(P(166));return Fe(t),null}e=La.current,Ai(t)?eg(t,e):(e=S0(r,n,a),t.stateNode=e,Za(t))}return Fe(t),null;case 5:if(ou(t),a=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==n&&Za(t);else{if(!n){if(t.stateNode===null)throw Error(P(166));return Fe(t),null}if(e=La.current,Ai(t))eg(t,e);else{switch(r=Eu(zn.current),e){case 1:e=r.createElementNS("http://www.w3.org/2000/svg",a);break;case 2:e=r.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;default:switch(a){case"svg":e=r.createElementNS("http://www.w3.org/2000/svg",a);break;case"math":e=r.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;case"script":e=r.createElement("div"),e.innerHTML="