From b67106805fe75128b465b0b4e0482b10ed4ad6c0 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 12:34:19 -0700 Subject: [PATCH 001/137] fix(runtime): eliminate agent-send TOCTOU that rejects live agents during startup (#9166) * fix(runtime): avoid false no-agent send rejection during agent startup * fix(renderer): let guarded note sends resolve agent status races --- src/main/runtime/rpc/methods/terminal.ts | 41 ++---- .../rpc/terminal-agent-send-guard.test.ts | 127 ++++++++++++++++++ .../runtime/rpc/terminal-agent-send-guard.ts | 54 ++++++++ .../src/lib/active-agent-note-send.test.ts | 51 +++++-- .../src/lib/active-agent-note-send.ts | 12 +- 5 files changed, 243 insertions(+), 42 deletions(-) create mode 100644 src/main/runtime/rpc/terminal-agent-send-guard.test.ts create mode 100644 src/main/runtime/rpc/terminal-agent-send-guard.ts diff --git a/src/main/runtime/rpc/methods/terminal.ts b/src/main/runtime/rpc/methods/terminal.ts index 6d5879d9cf8..1be167d6e1a 100644 --- a/src/main/runtime/rpc/methods/terminal.ts +++ b/src/main/runtime/rpc/methods/terminal.ts @@ -37,6 +37,7 @@ import { MOBILE_SNAPSHOT_BYTE_BUDGET, MOBILE_SUBSCRIBE_SCROLLBACK_ROWS } from '../../scrollback-limits' +import { assertTerminalAgentSendable } from '../terminal-agent-send-guard' const REQUESTED_SNAPSHOT_BYTE_BUDGET = 2 * 1024 * 1024 const TERMINAL_STREAM_CHUNK_BYTES = 48 * 1024 @@ -406,16 +407,6 @@ function isTerminalSendGuardNotWritable(error: unknown): boolean { return message.includes('terminal_guard_not_writable') } -function isTerminalAgentStatusNotWritable(error: unknown): boolean { - const message = error instanceof Error ? error.message : String(error) - return [ - 'terminal_not_writable', - 'terminal_handle_stale', - 'terminal_gone', - 'terminal_exited' - ].some((code) => message.includes(code)) -} - function assertTerminalSendExactPtyBinding( runtime: OrcaRuntimeService, handle: string, @@ -1270,28 +1261,16 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [ const assertSendPreconditions = params.requireAgentStatus === 'sendable' ? async (ptyId?: string): Promise => { - assertTerminalSendExactPtyBinding(runtime, params.terminal, ptyId) - if (ptyId && isTerminalInputLockedForClient(runtime, ptyId, params.client)) { - throw new Error('terminal_guard_not_writable') - } - let agentStatus - try { - agentStatus = await runtime.getTerminalAgentStatus(params.terminal) - } catch (error) { - if (isTerminalAgentStatusNotWritable(error)) { - throw new Error('terminal_guard_not_writable') + await assertTerminalAgentSendable({ + runtime, + handle: params.terminal, + assertWritable: () => { + assertTerminalSendExactPtyBinding(runtime, params.terminal, ptyId) + if (ptyId && isTerminalInputLockedForClient(runtime, ptyId, params.client)) { + throw new Error('terminal_guard_not_writable') + } } - throw error - } - // Why: a send callback can race a pane reconnect; status evidence - // must never authorize the callback's replacement PTY. - assertTerminalSendExactPtyBinding(runtime, params.terminal, ptyId) - if (!agentStatus.isRunningAgent) { - throw new Error('terminal_guard_no_agent') - } - if (agentStatus.status === 'permission') { - throw new Error('terminal_guard_permission') - } + }) } : undefined if (params.requireAgentStatus === 'sendable') { diff --git a/src/main/runtime/rpc/terminal-agent-send-guard.test.ts b/src/main/runtime/rpc/terminal-agent-send-guard.test.ts new file mode 100644 index 00000000000..3128e0e2dc0 --- /dev/null +++ b/src/main/runtime/rpc/terminal-agent-send-guard.test.ts @@ -0,0 +1,127 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import type { OrcaRuntimeService } from '../orca-runtime' +import type { RpcRequest } from './core' +import { RpcDispatcher } from './dispatcher' +import { TERMINAL_METHODS } from './methods/terminal' + +function stubRuntime(overrides: Partial): OrcaRuntimeService { + return { + getRuntimeId: () => 'test-runtime', + ...overrides + } as OrcaRuntimeService +} + +function guardedSendRequest(): RpcRequest { + return { + id: 'req-1', + authToken: 'tok', + method: 'terminal.send', + params: { + terminal: 'terminal-1', + enter: true, + requireAgentStatus: 'sendable', + client: { id: 'desktop-1', type: 'desktop' } + } + } +} + +describe('terminal agent send guard', () => { + afterEach(() => { + vi.useRealTimers() + }) + + it('rechecks a transient no-agent result before refusing a guarded send', async () => { + vi.useFakeTimers() + const getTerminalAgentStatus = vi + .fn() + .mockResolvedValueOnce({ handle: 'terminal-1', isRunningAgent: false, status: null }) + .mockResolvedValue({ handle: 'terminal-1', isRunningAgent: true, status: 'working' }) + const runtime = stubRuntime({ + resolveLiveLeafForHandle: vi.fn().mockReturnValue({ ptyId: 'pty-1' }), + getDriver: vi.fn().mockReturnValue({ kind: 'desktop' }), + getTerminalAgentStatus, + sendTerminal: vi.fn().mockResolvedValue({ + handle: 'terminal-1', + accepted: true, + bytesWritten: 1 + }) + }) + const responsePromise = new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }).dispatch( + guardedSendRequest() + ) + + await vi.advanceTimersByTimeAsync(150) + + await expect(responsePromise).resolves.toMatchObject({ + ok: true, + result: { send: { accepted: true, bytesWritten: 1 } } + }) + expect(getTerminalAgentStatus).toHaveBeenCalledTimes(2) + }) + + it('rechecks a transient no-agent result immediately before the PTY write', async () => { + vi.useFakeTimers() + const getTerminalAgentStatus = vi + .fn() + .mockResolvedValueOnce({ handle: 'terminal-1', isRunningAgent: true, status: 'working' }) + .mockResolvedValueOnce({ handle: 'terminal-1', isRunningAgent: false, status: null }) + .mockResolvedValue({ handle: 'terminal-1', isRunningAgent: true, status: 'working' }) + const write = vi.fn() + const runtime = stubRuntime({ + resolveLiveLeafForHandle: vi.fn().mockReturnValue({ ptyId: 'pty-1' }), + getDriver: vi.fn().mockReturnValue({ kind: 'desktop' }), + getTerminalAgentStatus, + sendTerminal: vi.fn().mockImplementation(async (_handle, _action, options) => { + await options.beforeWrite('pty-1') + write() + return { handle: 'terminal-1', accepted: true, bytesWritten: 1 } + }) + }) + const responsePromise = new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }).dispatch( + guardedSendRequest() + ) + + await vi.advanceTimersByTimeAsync(150) + + await expect(responsePromise).resolves.toMatchObject({ + ok: true, + result: { send: { accepted: true, bytesWritten: 1 } } + }) + expect(getTerminalAgentStatus).toHaveBeenCalledTimes(3) + expect(write).toHaveBeenCalledOnce() + }) + + it('still refuses after the bounded recheck window without positive evidence', async () => { + vi.useFakeTimers() + const getTerminalAgentStatus = vi.fn().mockResolvedValue({ + handle: 'terminal-1', + isRunningAgent: false, + status: null + }) + const sendTerminal = vi.fn() + const runtime = stubRuntime({ + resolveLiveLeafForHandle: vi.fn().mockReturnValue({ ptyId: 'pty-1' }), + getDriver: vi.fn().mockReturnValue({ kind: 'desktop' }), + getTerminalAgentStatus, + sendTerminal + }) + const responsePromise = new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }).dispatch( + guardedSendRequest() + ) + + await vi.advanceTimersByTimeAsync(1_050) + + await expect(responsePromise).resolves.toMatchObject({ + ok: true, + result: { + send: { + accepted: false, + bytesWritten: 0, + refusedReason: 'no-agent' + } + } + }) + expect(getTerminalAgentStatus).toHaveBeenCalledTimes(8) + expect(sendTerminal).not.toHaveBeenCalled() + }) +}) diff --git a/src/main/runtime/rpc/terminal-agent-send-guard.ts b/src/main/runtime/rpc/terminal-agent-send-guard.ts new file mode 100644 index 00000000000..ed556a6eb7b --- /dev/null +++ b/src/main/runtime/rpc/terminal-agent-send-guard.ts @@ -0,0 +1,54 @@ +import type { OrcaRuntimeService } from '../orca-runtime' + +const AGENT_STATUS_RECHECK_INTERVAL_MS = 150 +const AGENT_STATUS_RECHECK_TIMEOUT_MS = 1_050 + +type AssertTerminalAgentSendableOptions = { + runtime: OrcaRuntimeService + handle: string + assertWritable: () => void +} + +export async function assertTerminalAgentSendable( + options: AssertTerminalAgentSendableOptions +): Promise { + const deadline = Date.now() + AGENT_STATUS_RECHECK_TIMEOUT_MS + while (true) { + options.assertWritable() + let agentStatus + try { + agentStatus = await options.runtime.getTerminalAgentStatus(options.handle) + } catch (error) { + if (isTerminalAgentStatusNotWritable(error)) { + throw new Error('terminal_guard_not_writable') + } + throw error + } + options.assertWritable() + if (agentStatus.isRunningAgent) { + if (agentStatus.status === 'permission') { + throw new Error('terminal_guard_permission') + } + return + } + const remainingMs = deadline - Date.now() + if (remainingMs <= 0) { + throw new Error('terminal_guard_no_agent') + } + // Why: title and foreground caches refresh asynchronously; require fresh + // positive evidence within a wall-clock bound so slow SSH reads cannot multiply it. + await new Promise((resolve) => + setTimeout(resolve, Math.min(AGENT_STATUS_RECHECK_INTERVAL_MS, remainingMs)) + ) + } +} + +function isTerminalAgentStatusNotWritable(error: unknown): boolean { + const message = error instanceof Error ? error.message : String(error) + return [ + 'terminal_not_writable', + 'terminal_handle_stale', + 'terminal_gone', + 'terminal_exited' + ].some((code) => message.includes(code)) +} diff --git a/src/renderer/src/lib/active-agent-note-send.test.ts b/src/renderer/src/lib/active-agent-note-send.test.ts index 71d1e7ff94e..82083d515ab 100644 --- a/src/renderer/src/lib/active-agent-note-send.test.ts +++ b/src/renderer/src/lib/active-agent-note-send.test.ts @@ -993,8 +993,11 @@ describe('active agent note send', () => { ) }) - it('refuses explicit targets that are not recognized agents before writing', async () => { - testState.callRuntimeRpc.mockImplementation(async (_target, method) => { + it('lets guarded sends decide after transient no-agent snapshots for explicit targets', async () => { + const methods: string[] = [] + let guardedSendAccepted = true + testState.callRuntimeRpc.mockImplementation(async (_target, method, params) => { + methods.push(method) if (method === 'terminal.list') { return { terminals: [ @@ -1005,7 +1008,7 @@ describe('active agent note send', () => { branch: 'main', tabId: 'tab-9', leafId: OTHER_LEAF_ID, - title: 'zsh', + title: 'Codex', connected: true, writable: true, lastOutputAt: 1, @@ -1019,6 +1022,24 @@ describe('active agent note send', () => { if (method === 'terminal.agentStatus') { return { agentStatus: { handle: 'term-2', isRunningAgent: false, status: null } } } + if (method === 'terminal.send') { + expect(params).toMatchObject({ + terminal: 'term-2', + requireAgentStatus: 'sendable' + }) + return { + send: { + handle: 'term-2', + accepted: guardedSendAccepted, + bytesWritten: guardedSendAccepted + ? typeof params.text === 'string' + ? params.text.length + : 1 + : 0, + ...(guardedSendAccepted ? {} : { refusedReason: 'no-agent' }) + } + } + } throw new Error(`unexpected method ${method}`) }) @@ -1028,14 +1049,26 @@ describe('active agent note send', () => { prompt: 'notes', noteTarget: { tabId: 'tab-9', leafId: OTHER_LEAF_ID } }) - ).resolves.toEqual({ status: 'no-agent' }) + ).resolves.toEqual({ status: 'sent' }) - expect(testState.callRuntimeRpc).not.toHaveBeenCalledWith( - expect.anything(), + expect(methods).toEqual([ + 'terminal.list', + 'terminal.agentStatus', 'terminal.send', - expect.anything(), - expect.anything() - ) + 'terminal.agentStatus', + 'terminal.send' + ]) + + methods.length = 0 + guardedSendAccepted = false + await expect( + sendNotesToActiveAgentSession({ + worktreeId: 'wt-1', + prompt: 'notes', + noteTarget: { tabId: 'tab-9', leafId: OTHER_LEAF_ID } + }) + ).resolves.toEqual({ status: 'no-agent' }) + expect(methods).toEqual(['terminal.list', 'terminal.agentStatus', 'terminal.send']) }) it('maps explicit target first-write refusal to not-writable', async () => { diff --git a/src/renderer/src/lib/active-agent-note-send.ts b/src/renderer/src/lib/active-agent-note-send.ts index 44fdd581811..a2eb9c429d5 100644 --- a/src/renderer/src/lib/active-agent-note-send.ts +++ b/src/renderer/src/lib/active-agent-note-send.ts @@ -174,7 +174,12 @@ async function sendPromptWithGuardedPasteAndEnter( const initialAgentStatus = await getTerminalAgentSendReadiness(runtimeTarget, terminalHandle, { allowLegacyFallback: options.allowLegacyFallback }) - if (initialAgentStatus.status !== 'sendable') { + // Why: the readiness probe and write guard can observe different transient + // title/process snapshots; the guard owns the bounded no-agent recheck. + if ( + initialAgentStatus.status !== 'sendable' && + !(initialAgentStatus.status === 'no-agent' && initialAgentStatus.supportsGuardedSend) + ) { return { status: initialAgentStatus.status } } @@ -216,7 +221,10 @@ async function sendPromptWithGuardedPasteAndEnter( const submitAgentStatus = await getTerminalAgentSendReadiness(runtimeTarget, terminalHandle, { allowLegacyFallback: options.allowLegacyFallback }) - if (submitAgentStatus.status !== 'sendable') { + if ( + submitAgentStatus.status !== 'sendable' && + !(submitAgentStatus.status === 'no-agent' && submitAgentStatus.supportsGuardedSend) + ) { return { status: 'partial-submit-failed' } } } catch (error) { From 2cb5d4e149a1ef91c4cce2f9123e82d47229fa8e Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 12:47:04 -0700 Subject: [PATCH 002/137] fix(agent-status): clear answered Claude question waits at answer time (#9074) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(agent-status): clear answered Claude question waits at answer time An answered AskUserQuestion left the amber "waiting" indicator on sidebar rows and tabs until the agent's next tool hook or turn end — unbounded linger while the model thinks or streams after the answer (measured 17s for a 1000-word reply, 44s for 3000 words). Root cause is an event-shape change: newer Claude reports the AskUserQuestion wait as PermissionRequest (not the PreToolUse shape #7852 special-cased), so the wait inherited real-permission stickiness and shouldKeepClaudePermissionVisible swallowed the answer-time PostToolUse(AskUserQuestion) working event — the identity match can never succeed because the question's PermissionRequest carries no inheritable tool_use_id. That silently undid #8311 for questions. Two scoped changes, both keyed on the tool name rather than the hook event name: - Sticky permission hold now exempts AskUserQuestion waits, so the real answer-time hook (when Claude sends one) clears the wait as #8311 intended. - New guarded inference for the hook Claude may never send: the submit keystroke (Enter or digit quick-select) into a pane whose fresh status is a waiting AskUserQuestion synthesizes the post-answer state, exactly mirroring the existing interrupt inference (renderer baseline capture, main-process re-validation, listener lead-state sync so child-driven refreshes cannot resurrect the dismissed question). Real permission waits (other tools) keep their sticky semantics; batched input and pastes never match the submit classifier. Verified live against a real claude CLI: waiting -> working within ~50ms of both Enter and digit answers, question card dropped, unanswered questions still hold amber, permission stickiness covered by tests. * fix(agent-status): guard question answer inference Keep multi-question, multi-select, and free-text selector interactions waiting until the full prompt is submitted. Wire native-chat answers into the same guarded inference only after every paced runtime write succeeds, with cancellation and delivery-failure coverage. * chore(skills): refresh manifest for rc.2 * fix(agent-status): verify native chat answer delivery * fix(agent-status): await verified question delivery * fix(agent-status): pin native-chat answer baseline before delivery The native-chat question-answered inference read the live pane status at settle time (after the paced send + remote acceptance, which can span seconds on SSH). If a replacement AskUserQuestion became current in that window, the settle callback minted a fresh baseline from the new question and the server cleared *its* wait — dismissing a question the user never answered. Capture the answered question's baseline before delivery and have the inference getter return it, so the server re-validates against the pinned baseline and rejects a changed status — the same capture-then-revalidate contract the terminal keystroke path already uses. Also hoist the shouldStepNativeChatAskAnswer predicate to a single evaluation. Regression test swaps the live status between sendAnswer and settle and asserts the answered question's baseline is used (fails against the prior live-read getter). --- ...server.claude-interactive-question.test.ts | 127 +++++++++++++ src/main/agent-hooks/server.ts | 75 +++++++- src/main/ipc/agent-hooks.ts | 8 + src/preload/api-types.ts | 4 + src/preload/index.ts | 3 + .../NativeChatInteractiveCard.test.tsx | 42 ++++- .../native-chat/NativeChatInteractiveCard.tsx | 45 +++-- .../components/native-chat/NativeChatView.tsx | 2 +- .../native-chat-runtime-send.test.ts | 38 ++++ .../native-chat/native-chat-runtime-send.ts | 33 +++- .../use-native-chat-interactive-send.test.tsx | 163 ++++++++++++++-- .../use-native-chat-interactive-send.ts | 79 ++++++-- .../agent-question-answered-inference.test.ts | 175 ++++++++++++++++++ .../agent-question-answered-inference.ts | 85 +++++++++ .../terminal-pane/pty-connection.ts | 14 ++ src/renderer/src/web/web-preload-api.ts | 1 + src/shared/agent-hook-listener.test.ts | 57 ++++++ src/shared/agent-hook-listener.ts | 39 +++- src/shared/agent-question-answered-intent.ts | 83 +++++++++ 19 files changed, 1018 insertions(+), 55 deletions(-) create mode 100644 src/renderer/src/components/terminal-pane/agent-question-answered-inference.test.ts create mode 100644 src/renderer/src/components/terminal-pane/agent-question-answered-inference.ts create mode 100644 src/shared/agent-question-answered-intent.ts diff --git a/src/main/agent-hooks/server.claude-interactive-question.test.ts b/src/main/agent-hooks/server.claude-interactive-question.test.ts index a2c31a8f18b..cc450a3c36d 100644 --- a/src/main/agent-hooks/server.claude-interactive-question.test.ts +++ b/src/main/agent-hooks/server.claude-interactive-question.test.ts @@ -57,6 +57,34 @@ describe('Claude interactive-question status transitions', () => { ]) }) + it('clears a PermissionRequest-shaped AskUserQuestion wait when later tool work starts', () => { + // Why: newer Claude reports the AskUserQuestion wait as PermissionRequest; + // the question must not inherit real-permission stickiness from that shape. + const server = new AgentHookServer() + + ingestClaudeStatus(server, { + state: 'waiting', + hookEventName: 'PermissionRequest', + toolName: 'AskUserQuestion', + toolUseId: 'tool-question' + }) + ingestClaudeStatus(server, { + state: 'working', + hookEventName: 'PreToolUse', + toolName: 'Read', + toolUseId: 'tool-after-answer' + }) + + expect(server.getStatusSnapshot()).toEqual([ + expect.objectContaining({ + paneKey: PANE_KEY, + state: 'working', + agentType: 'claude', + toolName: 'Read' + }) + ]) + }) + it('keeps an actual permission request sticky during unrelated tool work', () => { const server = new AgentHookServer() @@ -83,3 +111,102 @@ describe('Claude interactive-question status transitions', () => { ]) }) }) + +function answeredRequestFromSnapshot( + server: AgentHookServer +): Parameters[0] { + const [entry] = server.getStatusSnapshot() + return { + paneKey: entry.paneKey, + baselineUpdatedAt: entry.receivedAt, + baselineStateStartedAt: entry.stateStartedAt, + // Why: mirror the renderer, which echoes the entry's prompt verbatim — + // these ingests carry none, and the server must strict-match that. + baselinePrompt: entry.prompt as string, + baselineAgentType: entry.agentType + } +} + +describe('inferQuestionAnswered', () => { + it('clears an AskUserQuestion wait when the submit keystroke is reported', () => { + const server = new AgentHookServer() + ingestClaudeStatus(server, { + state: 'waiting', + hookEventName: 'PreToolUse', + toolName: 'AskUserQuestion', + toolUseId: 'tool-question' + }) + + expect(server.inferQuestionAnswered(answeredRequestFromSnapshot(server))).toBe(true) + // Why: the answered question must also drop the tool identity so the + // question card cannot linger on the working row. + const [entry] = server.getStatusSnapshot() + expect(entry).toMatchObject({ paneKey: PANE_KEY, state: 'working', agentType: 'claude' }) + expect(entry.toolName).toBeUndefined() + expect(entry.interactivePrompt).toBeUndefined() + }) + + it('clears a PermissionRequest-shaped AskUserQuestion wait (newer Claude)', () => { + const server = new AgentHookServer() + ingestClaudeStatus(server, { + state: 'waiting', + hookEventName: 'PermissionRequest', + toolName: 'AskUserQuestion', + toolUseId: 'tool-question' + }) + + expect(server.inferQuestionAnswered(answeredRequestFromSnapshot(server))).toBe(true) + expect(server.getStatusSnapshot()).toEqual([ + expect.objectContaining({ paneKey: PANE_KEY, state: 'working', agentType: 'claude' }) + ]) + }) + + it('refuses when the cached status changed since the baseline was captured', () => { + const server = new AgentHookServer() + ingestClaudeStatus(server, { + state: 'waiting', + hookEventName: 'PreToolUse', + toolName: 'AskUserQuestion', + toolUseId: 'tool-question' + }) + const staleRequest = { + ...answeredRequestFromSnapshot(server), + baselineUpdatedAt: 1 + } + + expect(server.inferQuestionAnswered(staleRequest)).toBe(false) + expect(server.getStatusSnapshot()).toEqual([ + expect.objectContaining({ state: 'waiting', toolName: 'AskUserQuestion' }) + ]) + }) + + it('never clears a real permission request', () => { + const server = new AgentHookServer() + ingestClaudeStatus(server, { + state: 'waiting', + hookEventName: 'PermissionRequest', + toolName: 'Bash', + toolUseId: 'tool-needs-permission' + }) + + expect(server.inferQuestionAnswered(answeredRequestFromSnapshot(server))).toBe(false) + expect(server.getStatusSnapshot()).toEqual([ + expect.objectContaining({ state: 'waiting', toolName: 'Bash' }) + ]) + }) + + it('ignores panes that are not waiting on a question', () => { + const server = new AgentHookServer() + ingestClaudeStatus(server, { + state: 'working', + hookEventName: 'PreToolUse', + toolName: 'Read', + toolUseId: 'tool-working' + }) + + expect(server.inferQuestionAnswered(answeredRequestFromSnapshot(server))).toBe(false) + expect(server.getStatusSnapshot()).toEqual([ + expect.objectContaining({ state: 'working', toolName: 'Read' }) + ]) + }) +}) diff --git a/src/main/agent-hooks/server.ts b/src/main/agent-hooks/server.ts index 4011fdabda6..3480378d32c 100644 --- a/src/main/agent-hooks/server.ts +++ b/src/main/agent-hooks/server.ts @@ -22,6 +22,7 @@ import { ORCA_HOOK_PROTOCOL_VERSION } from '../../shared/agent-hook-types' import { clearAllListenerCaches, clearPaneCacheState, + clearClaudeAnsweredQuestionWait, createHookListenerState, getEndpointFileName, hasPendingAgentResultText, @@ -57,6 +58,10 @@ import { isAgentInterruptInputIntent, type AgentInterruptInferenceRequest } from '../../shared/agent-interrupt-intent' +import { + isAskUserQuestionTool, + type AgentQuestionAnsweredInferenceRequest +} from '../../shared/agent-question-answered-intent' import { parseLegacyNumericPaneKey, parsePaneKey } from '../../shared/stable-pane-id' import type { LegacyPaneKeyAliasEntry } from '../../shared/types' import { normalizeAgentProviderSession } from '../../shared/agent-session-resume' @@ -353,7 +358,12 @@ function shouldKeepClaudePermissionVisible( return false } // Why: only real permission requests stay sticky across concurrent subagent - // activity; interactive questions clear on the next working hook. + // activity; interactive questions clear on the next working hook. Newer + // Claude reports the AskUserQuestion wait AS a PermissionRequest, so the + // tool name — not the event name — decides which rule applies. + if (isAskUserQuestionTool(previous.payload.toolName)) { + return false + } return true } @@ -626,6 +636,69 @@ export class AgentHookServer { return true } + /** Guarded fallback for a hook Claude never sends: answering AskUserQuestion + * produces no event, so the amber wait would otherwise linger until the + * agent's next tool or turn end. The renderer reports the submit keystroke; + * this re-validates its baseline against the cached status (a racing real + * hook wins) and synthesizes the post-answer state. */ + inferQuestionAnswered(request: AgentQuestionAnsweredInferenceRequest): boolean { + if (!isValidPaneKey(request.paneKey)) { + return false + } + const existing = this.state.lastStatusByPaneKey.get(request.paneKey) as + | EnrichedAgentHookEventPayload + | undefined + if (!existing) { + return false + } + const payload = existing.payload + // Why: only Claude's interactive question may clear on typed input. The + // tool name is the discriminator, not the hook event — Claude versions + // differ on whether the AskUserQuestion wait arrives as PreToolUse or + // PermissionRequest. Real permission waits (other tools) stay sticky until + // the approved tool resumes — a denied or ignored permission must keep + // demanding attention even though approving is also a keystroke. + if ( + payload.agentType !== 'claude' || + payload.state !== 'waiting' || + !isAskUserQuestionTool(payload.toolName) + ) { + return false + } + if ( + payload.agentType !== request.baselineAgentType || + payload.prompt !== request.baselinePrompt || + existing.receivedAt !== request.baselineUpdatedAt || + existing.stateStartedAt !== request.baselineStateStartedAt || + Date.now() - existing.receivedAt > AGENT_STATUS_STALE_AFTER_MS + ) { + return false + } + // Why: sync the listener's lead-turn record too — a later child lifecycle + // event would otherwise re-emit the stale waiting state and resurrect the + // dismissed question card. + const restored = clearClaudeAnsweredQuestionWait(this.state, existing.paneKey) + const inferred = this.applyNormalizedStatus({ + paneKey: existing.paneKey, + tabId: existing.tabId, + worktreeId: existing.worktreeId, + connectionId: existing.connectionId, + providerSession: existing.providerSession, + payload: { + state: restored.state, + prompt: payload.prompt, + agentType: payload.agentType, + ...(restored.state === 'done' && restored.interrupted ? { interrupted: true } : {}), + ...(payload.subagents ? { subagents: payload.subagents } : {}) + } + }) + console.debug('[agent-hooks] inferred answered question status', { + paneKey: inferred.paneKey, + state: inferred.payload.state + }) + return true + } + getStatusChangeSnapshot(): AgentHookStatusChangeEntry[] { return Array.from(this.state.lastStatusByPaneKey.entries(), ([paneKey, entry]) => { const enriched = entry as EnrichedAgentHookEventPayload diff --git a/src/main/ipc/agent-hooks.ts b/src/main/ipc/agent-hooks.ts index 76b98ccb821..5a8e3f69ae4 100644 --- a/src/main/ipc/agent-hooks.ts +++ b/src/main/ipc/agent-hooks.ts @@ -5,6 +5,7 @@ import type { MigrationUnsupportedPtyEntry } from '../../shared/agent-status-types' import type { AgentInterruptInferenceRequest } from '../../shared/agent-interrupt-intent' +import type { AgentQuestionAnsweredInferenceRequest } from '../../shared/agent-question-answered-intent' import { agentHookServer, isValidPaneKey } from '../agent-hooks/server' import { ampHookService } from '../amp/hook-service' import { @@ -67,6 +68,7 @@ export function registerAgentHookHandlers( ipcMain.removeHandler('agentHooks:kimiStatus') ipcMain.removeHandler('agentStatus:getSnapshot') ipcMain.removeHandler('agentStatus:inferInterrupt') + ipcMain.removeHandler('agentStatus:inferQuestionAnswered') ipcMain.removeHandler('agentStatus:getMigrationUnsupportedSnapshot') // Why: agentStatus:drop is sent fire-and-forget from the renderer via // ipcRenderer.send(); we listen with ipcMain.on (not handle) so we don't @@ -121,6 +123,12 @@ export function registerAgentHookHandlers( } return agentHookServer.inferInterrupt(request as AgentInterruptInferenceRequest) }) + ipcMain.handle('agentStatus:inferQuestionAnswered', (_event, request: unknown): boolean => { + if (typeof request !== 'object' || request === null) { + return false + } + return agentHookServer.inferQuestionAnswered(request as AgentQuestionAnsweredInferenceRequest) + }) ipcMain.handle( 'agentStatus:getMigrationUnsupportedSnapshot', (): MigrationUnsupportedPtyEntry[] => getMigrationUnsupportedPtySnapshot() diff --git a/src/preload/api-types.ts b/src/preload/api-types.ts index 7c03b6f8879..0b2cfd98283 100644 --- a/src/preload/api-types.ts +++ b/src/preload/api-types.ts @@ -304,6 +304,7 @@ import type { MigrationUnsupportedPtyEntry } from '../shared/agent-status-types' import type { AgentInterruptInferenceRequest } from '../shared/agent-interrupt-intent' +import type { AgentQuestionAnsweredInferenceRequest } from '../shared/agent-question-answered-intent' import type { TerminalSideEffectBatch } from '../shared/terminal-side-effect-facts' import type { RuntimeBrowserDriverState, @@ -3181,6 +3182,9 @@ export type PreloadApi = { /** Return the current main-process hook cache after renderer hydration. */ getSnapshot: () => Promise inferInterrupt: (request: AgentInterruptInferenceRequest) => Promise + /** Guarded clear for an answered AskUserQuestion wait — the CLI emits no + * hook at answer time, so the renderer reports the submit keystroke. */ + inferQuestionAnswered: (request: AgentQuestionAnsweredInferenceRequest) => Promise /** Listen for PTYs that still use a legacy numeric pane key but have * registry-backed UUID pane proof. */ onMigrationUnsupported: (callback: (entry: MigrationUnsupportedPtyEntry) => void) => () => void diff --git a/src/preload/index.ts b/src/preload/index.ts index 9f0a30529bd..8084b0b1fc3 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -145,6 +145,7 @@ import type { MigrationUnsupportedPtyEntry } from '../shared/agent-status-types' import type { AgentInterruptInferenceRequest } from '../shared/agent-interrupt-intent' +import type { AgentQuestionAnsweredInferenceRequest } from '../shared/agent-question-answered-intent' import type { TerminalSideEffectBatch } from '../shared/terminal-side-effect-facts' import type { SpeechErrorEvent, @@ -4404,6 +4405,8 @@ const api = { ipcRenderer.invoke('agentStatus:getSnapshot'), inferInterrupt: (request: AgentInterruptInferenceRequest): Promise => ipcRenderer.invoke('agentStatus:inferInterrupt', request), + inferQuestionAnswered: (request: AgentQuestionAnsweredInferenceRequest): Promise => + ipcRenderer.invoke('agentStatus:inferQuestionAnswered', request), onMigrationUnsupported: ( callback: (entry: MigrationUnsupportedPtyEntry) => void ): (() => void) => { diff --git a/src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx b/src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx index 37f5e21db08..26aea01d014 100644 --- a/src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx +++ b/src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx @@ -2,7 +2,7 @@ import '@testing-library/jest-dom/vitest' -import { cleanup, fireEvent, render, screen } from '@testing-library/react' +import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { NativeChatInteractiveSend } from './use-native-chat-interactive-send' @@ -73,7 +73,7 @@ describe('NativeChatInteractiveCard answer lifecycle', () => { }) it('keeps the card retryable when no PTY answer was sent', () => { - mocks.sendAnswer.mockReturnValue(0) + mocks.sendAnswer.mockReturnValue({ settleAfterMs: 0, waitsForVerifiedDelivery: false }) renderCard() chooseSpacesAndSubmit() @@ -84,7 +84,7 @@ describe('NativeChatInteractiveCard answer lifecycle', () => { }) it('cancels delayed PTY writes when the owning card unmounts', () => { - mocks.sendAnswer.mockReturnValue(5_000) + mocks.sendAnswer.mockReturnValue({ settleAfterMs: 5_000, waitsForVerifiedDelivery: false }) const rendered = renderCard() chooseSpacesAndSubmit() @@ -95,7 +95,7 @@ describe('NativeChatInteractiveCard answer lifecycle', () => { }) it('cancels delayed PTY writes when desktop send authority is lost', () => { - mocks.sendAnswer.mockReturnValue(5_000) + mocks.sendAnswer.mockReturnValue({ settleAfterMs: 5_000, waitsForVerifiedDelivery: false }) const rendered = renderCard() chooseSpacesAndSubmit() @@ -105,7 +105,7 @@ describe('NativeChatInteractiveCard answer lifecycle', () => { }) it('shows the paced send as busy and freezes the snapshotted answer', () => { - mocks.sendAnswer.mockReturnValue(5_000) + mocks.sendAnswer.mockReturnValue({ settleAfterMs: 5_000, waitsForVerifiedDelivery: false }) renderCard() chooseSpacesAndSubmit() @@ -117,7 +117,7 @@ describe('NativeChatInteractiveCard answer lifecycle', () => { }) it('cancels the old answer sequence when a replacement prompt arrives', () => { - mocks.sendAnswer.mockReturnValue(5_000) + mocks.sendAnswer.mockReturnValue({ settleAfterMs: 5_000, waitsForVerifiedDelivery: false }) const rendered = renderCard() chooseSpacesAndSubmit() @@ -135,4 +135,34 @@ describe('NativeChatInteractiveCard answer lifecycle', () => { expect(mocks.cancelPending).toHaveBeenCalledOnce() expect(screen.getByText('Choose a shell?')).toBeInTheDocument() }) + + it('keeps a verified send visible until delivery succeeds', () => { + let settleDelivery: ((delivered: boolean) => void) | undefined + mocks.sendAnswer.mockImplementation((_prompt, _selections, onDeliverySettled) => { + settleDelivery = onDeliverySettled + return { settleAfterMs: 500, waitsForVerifiedDelivery: true } + }) + renderCard() + + chooseSpacesAndSubmit() + expect(screen.getByRole('button', { name: 'Sending…' })).toBeDisabled() + + act(() => settleDelivery?.(true)) + expect(screen.queryByText('Tabs or spaces?')).not.toBeInTheDocument() + }) + + it('restores a verified send for retry when delivery is rejected', () => { + let settleDelivery: ((delivered: boolean) => void) | undefined + mocks.sendAnswer.mockImplementation((_prompt, _selections, onDeliverySettled) => { + settleDelivery = onDeliverySettled + return { settleAfterMs: 500, waitsForVerifiedDelivery: true } + }) + renderCard() + + chooseSpacesAndSubmit() + act(() => settleDelivery?.(false)) + + expect(screen.getByRole('button', { name: 'Send answer' })).toBeEnabled() + expect(screen.getByText('Tabs or spaces?')).toBeInTheDocument() + }) }) diff --git a/src/renderer/src/components/native-chat/NativeChatInteractiveCard.tsx b/src/renderer/src/components/native-chat/NativeChatInteractiveCard.tsx index de2d1aa1a98..3c8973e6d7f 100644 --- a/src/renderer/src/components/native-chat/NativeChatInteractiveCard.tsx +++ b/src/renderer/src/components/native-chat/NativeChatInteractiveCard.tsx @@ -107,23 +107,42 @@ export function NativeChatInteractiveCard({ return } submittingRef.current = true - const settleMs = sendAnswer(card.prompt, selections) - if (settleMs <= 0) { - // Keep the actionable card visible when its PTY disappeared between - // render and submit; the next live target update can make it retryable. - submittingRef.current = false - return - } - setSubmitting(true) - // Hold the card until the paced write finishes, then mark it answered - // (which hides it and restores the composer). - dismissTimerRef.current = setTimeout(() => { - cancelPending() + const dismissAnsweredCard = (): void => { setDismissedKey(cardKey) submittingRef.current = false setSubmitting(false) dismissTimerRef.current = null - }, settleMs) + } + const keepRejectedAnswerVisible = (): void => { + submittingRef.current = false + setSubmitting(false) + } + const result = sendAnswer(card.prompt, selections, (delivered) => { + if (delivered) { + dismissAnsweredCard() + } else { + keepRejectedAnswerVisible() + } + }) + if (result.settleAfterMs <= 0) { + // Keep the actionable card visible when its PTY disappeared between + // render and submit; the next live target update can make it retryable. + keepRejectedAnswerVisible() + return + } + setSubmitting(true) + if (result.waitsForVerifiedDelivery) { + // Why: remote acceptance can outlive the keystroke pacing window. + // Keep the card until delivery is proven instead of cancelling the + // inference callback at the old fixed dismissal deadline. + return + } + // Hold the card until the paced write finishes, then mark it answered + // (which hides it and restores the composer). + dismissTimerRef.current = setTimeout(() => { + cancelPending() + dismissAnsweredCard() + }, result.settleAfterMs) }} onCancel={() => { clearDismissTimer() diff --git a/src/renderer/src/components/native-chat/NativeChatView.tsx b/src/renderer/src/components/native-chat/NativeChatView.tsx index d2267281eca..984a4919103 100644 --- a/src/renderer/src/components/native-chat/NativeChatView.tsx +++ b/src/renderer/src/components/native-chat/NativeChatView.tsx @@ -166,7 +166,7 @@ function NativeChatResolvedView({ const canSend = useNativeChatCanSend(targetPtyId) // Reuse the verified composer send path for interactive cards and composer // stop (Stop sends ESC, the agent-TUI interrupt key). - const interactiveSend = useNativeChatInteractiveSend(terminalTabId, targetPtyId, agent) + const interactiveSend = useNativeChatInteractiveSend(terminalTabId, paneKey, targetPtyId, agent) const [workingInterrupted, setWorkingInterrupted] = useState(false) // True while a question card owns the input region, so the composer is hidden. const [questionActive, setQuestionActive] = useState(false) diff --git a/src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts b/src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts index 730ad84f1a6..4982bbda298 100644 --- a/src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts +++ b/src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts @@ -33,6 +33,7 @@ describe('sendNativeChatMessage', () => { beforeEach(() => { vi.useFakeTimers() sendRuntimePtyInput.mockClear() + sendRuntimePtyInput.mockReturnValue(true) }) afterEach(() => { vi.useRealTimers() @@ -205,6 +206,8 @@ describe('sendNativeChatAskAnswer', () => { beforeEach(() => { vi.useFakeTimers() sendRuntimePtyInput.mockClear() + sendRuntimePtyInput.mockReturnValue(true) + sendRuntimePtyInputVerified.mockReset().mockResolvedValue(true) }) afterEach(() => { vi.useRealTimers() @@ -278,4 +281,39 @@ describe('sendNativeChatAskAnswer', () => { // Only the first keystroke landed; the rest were cancelled. expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) }) + + it('reports verified delivery only after settling and suppresses it after cancellation', async () => { + const onSettled = vi.fn() + sendRuntimePtyInputVerified.mockResolvedValueOnce(true).mockResolvedValueOnce(false) + const handle = sendNativeChatAskAnswer(SETTINGS, PTY, [{ raw: '1' }, { raw: '\r' }], onSettled) + + await vi.advanceTimersByTimeAsync(handle.settleAfterMs) + expect(onSettled).toHaveBeenCalledExactlyOnceWith(false) + expect(sendRuntimePtyInput).not.toHaveBeenCalled() + + const canceledSettled = vi.fn() + const canceled = sendNativeChatAskAnswer(SETTINGS, PTY, [{ raw: '1' }], canceledSettled) + canceled.cancel() + await vi.runAllTimersAsync() + expect(canceledSettled).not.toHaveBeenCalled() + }) + + it('waits for remote acceptance before reporting delivery', async () => { + const onSettled = vi.fn() + let resolveAccepted!: (accepted: boolean) => void + sendRuntimePtyInputVerified.mockReturnValueOnce( + new Promise((resolve) => { + resolveAccepted = resolve + }) + ) + + const handle = sendNativeChatAskAnswer(SETTINGS, PTY, [{ raw: '2' }], onSettled) + await vi.advanceTimersByTimeAsync(handle.settleAfterMs) + + expect(sendRuntimePtyInputVerified).toHaveBeenCalledWith(SETTINGS, PTY, '2') + expect(onSettled).not.toHaveBeenCalled() + + resolveAccepted(true) + await vi.waitFor(() => expect(onSettled).toHaveBeenCalledExactlyOnceWith(true)) + }) }) diff --git a/src/renderer/src/components/native-chat/native-chat-runtime-send.ts b/src/renderer/src/components/native-chat/native-chat-runtime-send.ts index a1dca86b65f..f067064e047 100644 --- a/src/renderer/src/components/native-chat/native-chat-runtime-send.ts +++ b/src/renderer/src/components/native-chat/native-chat-runtime-send.ts @@ -155,27 +155,54 @@ export function submitNativeChatPrompt( export function sendNativeChatAskAnswer( settings: ReturnType, ptyId: string, - groups: AskAnswerKeyGroup[] + groups: AskAnswerKeyGroup[], + onSettled?: (delivered: boolean) => void ): NativeChatSendHandle { if (groups.length === 0) { return { cancel: () => {}, settleAfterMs: 0 } } const timers: ReturnType[] = [] + const verifiedWrites: Promise[] = [] + let cancelled = false groups.forEach((group, index) => { timers.push( setTimeout(() => { const bytes = 'raw' in group ? group.raw : buildNativeChatPasteBytes(group.text) - sendRuntimePtyInput(settings, ptyId, bytes) + if (onSettled) { + // Why: inference must use the remote host's acceptance result, not + // the fire-and-forget renderer dispatch result. + verifiedWrites.push( + sendRuntimePtyInputVerified(settings, ptyId, bytes).catch(() => false) + ) + } else { + sendRuntimePtyInput(settings, ptyId, bytes) + } }, index * NATIVE_CHAT_QUESTION_STEP_MS) ) }) + const settleAfterMs = + (groups.length - 1) * NATIVE_CHAT_QUESTION_STEP_MS + NATIVE_CHAT_SUBMIT_DELAY_MS + if (onSettled) { + // Why: status inference must wait for every paced write and must not run + // after cancellation or a rejected runtime write. + timers.push( + setTimeout(() => { + void Promise.all(verifiedWrites).then((results) => { + if (!cancelled) { + onSettled(results.every(Boolean)) + } + }) + }, settleAfterMs) + ) + } return { cancel: () => { + cancelled = true for (const timer of timers) { clearTimeout(timer) } }, // Hold the card until the last keystroke has fired and its submit gap passed. - settleAfterMs: (groups.length - 1) * NATIVE_CHAT_QUESTION_STEP_MS + NATIVE_CHAT_SUBMIT_DELAY_MS + settleAfterMs } } diff --git a/src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx b/src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx index 5a336009c5c..9b886bbbbac 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx +++ b/src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx @@ -5,9 +5,30 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => ({ cancel: vi.fn(), + inferQuestionAnswered: vi.fn(() => Promise.resolve(true)), sendRuntimePtyInput: vi.fn(), sendNativeChatAskAnswer: vi.fn(), - sendNativeChatMessage: vi.fn() + sendNativeChatMessage: vi.fn(), + // Mutable so a test can swap the live status between sendAnswer and settle. + storeState: { agentStatusByPaneKey: {} as Record } +})) + +const PANE_KEY = 'tab-1:11111111-1111-4111-8111-111111111111' +const waitingQuestion = { + state: 'waiting' as const, + prompt: 'pick one', + updatedAt: Date.now(), + stateStartedAt: Date.now(), + agentType: 'claude' as const, + paneKey: PANE_KEY, + stateHistory: [], + toolName: 'AskUserQuestion' +} + +vi.mock('../../store', () => ({ + useAppStore: { + getState: () => mocks.storeState + } })) vi.mock('@/runtime/runtime-terminal-inspection', () => ({ @@ -33,13 +54,20 @@ const PROMPT: AskPrompt = { describe('useNativeChatInteractiveSend', () => { beforeEach(() => { vi.clearAllMocks() + mocks.storeState = { agentStatusByPaneKey: { [PANE_KEY]: waitingQuestion } } + Object.defineProperty(window, 'api', { + configurable: true, + value: { agentStatus: { inferQuestionAnswered: mocks.inferQuestionAnswered } } + }) const handle = { cancel: mocks.cancel, settleAfterMs: 500 } mocks.sendNativeChatAskAnswer.mockReturnValue(handle) mocks.sendNativeChatMessage.mockReturnValue(handle) }) it('routes a non-Claude answer through the pasted-text send path', () => { - const { result } = renderHook(() => useNativeChatInteractiveSend('tab-1', 'pty-1', 'codex')) + const { result } = renderHook(() => + useNativeChatInteractiveSend('tab-1', PANE_KEY, 'pty-1', 'codex') + ) act(() => result.current.sendAnswer(PROMPT, [{ indices: [1] }])) @@ -53,7 +81,9 @@ describe('useNativeChatInteractiveSend', () => { }) it('routes a Claude answer through the option-number keystroke path', () => { - const { result } = renderHook(() => useNativeChatInteractiveSend('tab-1', 'pty-1', 'claude')) + const { result } = renderHook(() => + useNativeChatInteractiveSend('tab-1', PANE_KEY, 'pty-1', 'claude') + ) act(() => result.current.sendAnswer(PROMPT, [{ indices: [1] }])) @@ -61,27 +91,43 @@ describe('useNativeChatInteractiveSend', () => { expect(mocks.sendNativeChatAskAnswer).toHaveBeenCalledWith( { terminalTabId: 'tab-1' }, 'pty-1', - [{ raw: '2' }] + [{ raw: '2' }], + expect.any(Function) ) expect(mocks.sendNativeChatMessage).not.toHaveBeenCalled() }) - it('does nothing when no option is answered', () => { - const { result } = renderHook(() => useNativeChatInteractiveSend('tab-1', 'pty-1', 'claude')) + it('infers OpenClaude answers through its Claude-compatible selector path', () => { + const { result } = renderHook(() => + useNativeChatInteractiveSend('tab-1', PANE_KEY, 'pty-1', 'openclaude') + ) - let settleMs = -1 + act(() => result.current.sendAnswer(PROMPT, [{ indices: [1] }])) + + const onSettled = mocks.sendNativeChatAskAnswer.mock.calls[0]?.[3] + expect(onSettled).toBeTypeOf('function') + onSettled?.(true) + expect(mocks.inferQuestionAnswered).toHaveBeenCalledOnce() + }) + + it('does nothing when no option is answered', () => { + const { result } = renderHook(() => + useNativeChatInteractiveSend('tab-1', PANE_KEY, 'pty-1', 'claude') + ) + + let resultValue: ReturnType | undefined act(() => { - settleMs = result.current.sendAnswer(PROMPT, [{ indices: [] }]) + resultValue = result.current.sendAnswer(PROMPT, [{ indices: [] }]) }) - expect(settleMs).toBe(0) + expect(resultValue).toEqual({ settleAfterMs: 0, waitsForVerifiedDelivery: false }) expect(mocks.sendNativeChatAskAnswer).not.toHaveBeenCalled() expect(mocks.sendNativeChatMessage).not.toHaveBeenCalled() }) it('cancels delayed answer writes when the PTY target changes', () => { const { result, rerender } = renderHook( - ({ targetPtyId }) => useNativeChatInteractiveSend('tab-1', targetPtyId, 'codex'), + ({ targetPtyId }) => useNativeChatInteractiveSend('tab-1', PANE_KEY, targetPtyId, 'codex'), { initialProps: { targetPtyId: 'pty-1' as string | null } } ) @@ -91,8 +137,22 @@ describe('useNativeChatInteractiveSend', () => { expect(mocks.cancel).toHaveBeenCalledOnce() }) + it('cancels delayed answer writes when the pane identity changes', () => { + const { result, rerender } = renderHook( + ({ paneKey }) => useNativeChatInteractiveSend('tab-1', paneKey, 'pty-1', 'claude'), + { initialProps: { paneKey: PANE_KEY } } + ) + + act(() => result.current.sendAnswer(PROMPT, [{ indices: [0] }])) + rerender({ paneKey: 'tab-1:22222222-2222-4222-8222-222222222222' }) + + expect(mocks.cancel).toHaveBeenCalledOnce() + }) + it('cancels delayed answer writes before interrupting the active PTY', () => { - const { result } = renderHook(() => useNativeChatInteractiveSend('tab-1', 'pty-1', 'claude')) + const { result } = renderHook(() => + useNativeChatInteractiveSend('tab-1', PANE_KEY, 'pty-1', 'claude') + ) act(() => result.current.sendAnswer(PROMPT, [{ indices: [1] }])) act(() => result.current.cancel()) @@ -106,7 +166,9 @@ describe('useNativeChatInteractiveSend', () => { }) it('can cancel delayed writes without interrupting the replacement prompt', () => { - const { result } = renderHook(() => useNativeChatInteractiveSend('tab-1', 'pty-1', 'claude')) + const { result } = renderHook(() => + useNativeChatInteractiveSend('tab-1', PANE_KEY, 'pty-1', 'claude') + ) act(() => result.current.sendAnswer(PROMPT, [{ indices: [1] }])) act(() => result.current.cancelPending()) @@ -114,4 +176,81 @@ describe('useNativeChatInteractiveSend', () => { expect(mocks.cancel).toHaveBeenCalledOnce() expect(mocks.sendRuntimePtyInput).not.toHaveBeenCalled() }) + + it('infers a Claude question answer only after every runtime write was delivered', () => { + const { result } = renderHook(() => + useNativeChatInteractiveSend('tab-1', PANE_KEY, 'pty-1', 'claude') + ) + + act(() => result.current.sendAnswer(PROMPT, [{ indices: [1] }])) + expect(mocks.inferQuestionAnswered).not.toHaveBeenCalled() + + const onSettled = mocks.sendNativeChatAskAnswer.mock.calls[0]?.[3] + expect(onSettled).toBeTypeOf('function') + onSettled?.(false) + expect(mocks.inferQuestionAnswered).not.toHaveBeenCalled() + onSettled?.(true) + + expect(mocks.inferQuestionAnswered).toHaveBeenCalledExactlyOnceWith({ + paneKey: PANE_KEY, + baselineUpdatedAt: waitingQuestion.updatedAt, + baselineStateStartedAt: waitingQuestion.stateStartedAt, + baselinePrompt: 'pick one', + baselineAgentType: 'claude' + }) + }) + + it('infers the answered question baseline, not a replacement that became current mid-send', () => { + const { result } = renderHook(() => + useNativeChatInteractiveSend('tab-1', PANE_KEY, 'pty-1', 'claude') + ) + + // Answer question A while it is the current waiting question. + act(() => result.current.sendAnswer(PROMPT, [{ indices: [1] }])) + + // A different AskUserQuestion becomes current before the paced send settles. + mocks.storeState = { + agentStatusByPaneKey: { + [PANE_KEY]: { + ...waitingQuestion, + prompt: 'second question', + updatedAt: waitingQuestion.updatedAt + 5000, + stateStartedAt: waitingQuestion.stateStartedAt + 5000 + } + } + } + + const onSettled = mocks.sendNativeChatAskAnswer.mock.calls[0]?.[3] + onSettled?.(true) + + // The baseline is question A's (captured before delivery), so the server can + // reject it against the now-current question B instead of clearing B's wait. + expect(mocks.inferQuestionAnswered).toHaveBeenCalledExactlyOnceWith({ + paneKey: PANE_KEY, + baselineUpdatedAt: waitingQuestion.updatedAt, + baselineStateStartedAt: waitingQuestion.stateStartedAt, + baselinePrompt: 'pick one', + baselineAgentType: 'claude' + }) + }) + + it('reports verified delivery settlement to the question card', () => { + const onDeliverySettled = vi.fn() + const { result } = renderHook(() => + useNativeChatInteractiveSend('tab-1', PANE_KEY, 'pty-1', 'claude') + ) + + let sendResult: ReturnType | undefined + act(() => { + sendResult = result.current.sendAnswer(PROMPT, [{ indices: [1] }], onDeliverySettled) + }) + expect(sendResult).toEqual({ settleAfterMs: 500, waitsForVerifiedDelivery: true }) + + const onSettled = mocks.sendNativeChatAskAnswer.mock.calls[0]?.[3] + onSettled?.(false) + expect(onDeliverySettled).toHaveBeenCalledExactlyOnceWith(false) + + act(() => result.current.cancelPending()) + expect(mocks.cancel).not.toHaveBeenCalled() + }) }) diff --git a/src/renderer/src/components/native-chat/use-native-chat-interactive-send.ts b/src/renderer/src/components/native-chat/use-native-chat-interactive-send.ts index cb49cd9c57d..9b22253927a 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-interactive-send.ts +++ b/src/renderer/src/components/native-chat/use-native-chat-interactive-send.ts @@ -1,4 +1,5 @@ import { useCallback, useLayoutEffect, useRef } from 'react' +import { useAppStore } from '../../store' import { sendRuntimePtyInput } from '@/runtime/runtime-terminal-inspection' import { getSettingsForAgentTabRuntimeOwner } from '@/lib/agent-paste-draft' import type { AgentType } from '../../../../shared/native-chat-types' @@ -15,16 +16,20 @@ import { sendNativeChatMessage, type NativeChatSendHandle } from './native-chat-runtime-send' +import { inferQuestionAnsweredFromCurrentStatus } from '../terminal-pane/agent-question-answered-inference' // ESC is the agent-TUI interrupt/cancel key over the PTY (matches how the // composer forwards Escape). Used to cancel a question or deny an approval. const ESC = '\x1b' export type NativeChatInteractiveSend = { - /** Deliver the answer to an AskUserQuestion prompt. Returns the ms after which - * every scheduled write has fired (0 if nothing was sent) so the caller can - * keep the card up until the send settles. */ - sendAnswer: (prompt: AskPrompt, selections: AskAnswerSelection[]) => number + /** Deliver the answer to an AskUserQuestion prompt. Claude-format selectors + * verify every runtime write before reporting settlement. */ + sendAnswer: ( + prompt: AskPrompt, + selections: AskAnswerSelection[], + onDeliverySettled?: (delivered: boolean) => void + ) => { settleAfterMs: number; waitsForVerifiedDelivery: boolean } /** Send a raw control string (e.g. an approval option number or ESC) as-is. */ sendRaw: (raw: string) => void /** Stop delayed writes without interrupting the agent. */ @@ -44,6 +49,7 @@ export type NativeChatInteractiveSend = { */ export function useNativeChatInteractiveSend( terminalTabId: string, + paneKey: string, targetPtyId: string | null, agent: AgentType ): NativeChatInteractiveSend { @@ -57,7 +63,10 @@ export function useNativeChatInteractiveSend( }, []) // Why: a split can be rebound without unmounting this view. Cancel during // commit so no delayed answer write can race the replacement PTY. - useLayoutEffect(() => cancelInFlight, [cancelInFlight, targetPtyId, terminalTabId]) + useLayoutEffect( + () => cancelInFlight, + [agent, cancelInFlight, paneKey, targetPtyId, terminalTabId] + ) const sendRaw = useCallback( (raw: string) => { @@ -70,9 +79,13 @@ export function useNativeChatInteractiveSend( ) const sendAnswer = useCallback( - (prompt: AskPrompt, selections: AskAnswerSelection[]): number => { + ( + prompt: AskPrompt, + selections: AskAnswerSelection[], + onDeliverySettled?: (delivered: boolean) => void + ): { settleAfterMs: number; waitsForVerifiedDelivery: boolean } => { if (!targetPtyId || !hasAskAnswer(prompt, selections)) { - return 0 + return { settleAfterMs: 0, waitsForVerifiedDelivery: false } } // Cancel any prior in-flight answer before starting a new one. cancelInFlight() @@ -83,13 +96,57 @@ export function useNativeChatInteractiveSend( // Other agents' question tools commit a pasted answer, so send label text. // Gate on the transcript agent (not `=== 'claude'`) so OpenClaude — which // runs the same selector — takes the keystroke path too. - const handle: NativeChatSendHandle = shouldStepNativeChatAskAnswer(agent) - ? sendNativeChatAskAnswer(settings, targetPtyId, buildAskAnswerKeys(prompt, selections)) + const stepsAnswer = shouldStepNativeChatAskAnswer(agent) + // Why: pin the answered question's baseline BEFORE delivery. A late settle + // callback (paced writes + remote acceptance can span seconds on SSH) must + // not read the live status and mint a fresh baseline for a replacement + // question that became current meanwhile — that would clear the new + // question's wait. The server re-validates this captured baseline and + // rejects a changed status, matching the terminal keystroke path. + const questionStatusBaseline = stepsAnswer + ? useAppStore.getState().agentStatusByPaneKey[paneKey] + : undefined + let settledHandle: NativeChatSendHandle | null = null + const onSettled = stepsAnswer + ? (delivered: boolean): void => { + if (settledHandle && inFlightRef.current === settledHandle) { + // Why: a completed verified send otherwise retains its timers, + // promises, and prompt callback until the next send or unmount. + inFlightRef.current = null + } + if (delivered) { + inferQuestionAnsweredFromCurrentStatus({ + paneKey, + getStatusEntry: () => questionStatusBaseline, + inferQuestionAnswered: (request) => + window.api.agentStatus.inferQuestionAnswered(request).catch((err) => { + console.warn('[agent-question] native-chat inference failed:', err) + return false + }) + }) + } + onDeliverySettled?.(delivered) + } + : undefined + const handle: NativeChatSendHandle = stepsAnswer + ? sendNativeChatAskAnswer( + settings, + targetPtyId, + buildAskAnswerKeys(prompt, selections), + onSettled + ) : sendNativeChatMessage(settings, targetPtyId, formatAskAnswer(prompt, selections)) + // Why: native-chat answer writes bypass xterm.onData. Infer only after + // every paced selector write has fired, so an early digit in a multi-step + // answer cannot dismiss the wait or cancel the remaining writes. + settledHandle = handle inFlightRef.current = handle - return handle.settleAfterMs + return { + settleAfterMs: handle.settleAfterMs, + waitsForVerifiedDelivery: onSettled !== undefined + } }, - [terminalTabId, targetPtyId, agent, cancelInFlight] + [terminalTabId, paneKey, targetPtyId, agent, cancelInFlight] ) // Stop/cancel: drop any pending answer writes, then send ESC to interrupt. diff --git a/src/renderer/src/components/terminal-pane/agent-question-answered-inference.test.ts b/src/renderer/src/components/terminal-pane/agent-question-answered-inference.test.ts new file mode 100644 index 00000000000..1460ef5b5e6 --- /dev/null +++ b/src/renderer/src/components/terminal-pane/agent-question-answered-inference.test.ts @@ -0,0 +1,175 @@ +import { describe, expect, it, vi } from 'vitest' +import type { AgentStatusEntry } from '../../../../shared/agent-status-types' +import { createAgentQuestionAnsweredInference } from './agent-question-answered-inference' + +const PANE_KEY = 'tab-1:11111111-1111-4111-8111-111111111111' +const SINGLE_SELECT_PROMPT = JSON.stringify({ + questions: [ + { + question: 'pick a color', + multiSelect: false, + options: [{ label: 'red' }, { label: 'blue' }] + } + ] +}) + +function makeWaitingQuestionEntry(overrides: Partial = {}): AgentStatusEntry { + return { + state: 'waiting', + prompt: 'pick a color', + updatedAt: 1_000, + stateStartedAt: 900, + agentType: 'claude', + paneKey: PANE_KEY, + stateHistory: [], + toolName: 'AskUserQuestion', + interactivePrompt: SINGLE_SELECT_PROMPT, + ...overrides + } +} + +function makeInference(entry: AgentStatusEntry | undefined) { + const inferQuestionAnswered = vi.fn() + const inference = createAgentQuestionAnsweredInference({ + paneKey: PANE_KEY, + getStatusEntry: () => entry, + inferQuestionAnswered, + now: () => 2_000 + }) + return { inference, inferQuestionAnswered } +} + +describe('agent question-answered inference', () => { + it('reports the baseline when Enter is sent to a waiting question pane', () => { + const { inference, inferQuestionAnswered } = makeInference(makeWaitingQuestionEntry()) + + inference.observeSentTerminalInput('\r') + + expect(inferQuestionAnswered).toHaveBeenCalledExactlyOnceWith({ + paneKey: PANE_KEY, + baselineUpdatedAt: 1_000, + baselineStateStartedAt: 900, + baselinePrompt: 'pick a color', + baselineAgentType: 'claude' + }) + }) + + it('accepts kitty-keyboard Enter encodings', () => { + const { inference, inferQuestionAnswered } = makeInference(makeWaitingQuestionEntry()) + + inference.observeSentTerminalInput('\x1b[13u') + + expect(inferQuestionAnswered).toHaveBeenCalledOnce() + }) + + it('accepts a bare digit quick-select, which submits without Enter', () => { + const { inference, inferQuestionAnswered } = makeInference(makeWaitingQuestionEntry()) + + inference.observeSentTerminalInput('2') + + expect(inferQuestionAnswered).toHaveBeenCalledOnce() + }) + + it('keeps waiting when a digit only advances or toggles a partial answer', () => { + const prompts = [ + JSON.stringify({ + questions: [ + { question: 'first?', multiSelect: false, options: [{ label: 'A' }] }, + { question: 'second?', multiSelect: false, options: [{ label: 'B' }] } + ] + }), + JSON.stringify({ + questions: [ + { + question: 'pick several', + multiSelect: true, + options: [{ label: 'A' }, { label: 'B' }] + } + ] + }) + ] + + for (const interactivePrompt of prompts) { + const { inference, inferQuestionAnswered } = makeInference( + makeWaitingQuestionEntry({ interactivePrompt }) + ) + inference.observeSentTerminalInput('1') + inference.observeSentTerminalInput('\r') + expect(inferQuestionAnswered).not.toHaveBeenCalled() + } + }) + + it('keeps waiting when the synthetic free-text row is selected', () => { + const { inference, inferQuestionAnswered } = makeInference(makeWaitingQuestionEntry()) + + // Two declared options means 3 opens Claude's synthetic "Type something" row. + inference.observeSentTerminalInput('3') + + expect(inferQuestionAnswered).not.toHaveBeenCalled() + }) + + it('keeps waiting when capped or malformed prompt JSON hides the question shape', () => { + const { inference, inferQuestionAnswered } = makeInference( + makeWaitingQuestionEntry({ interactivePrompt: '{"questions":[' }) + ) + + inference.observeSentTerminalInput('\r') + + expect(inferQuestionAnswered).not.toHaveBeenCalled() + }) + + it('keeps the legacy Enter fallback when the hook omitted tool input', () => { + const { inference, inferQuestionAnswered } = makeInference( + makeWaitingQuestionEntry({ interactivePrompt: undefined }) + ) + + inference.observeSentTerminalInput('\r') + + expect(inferQuestionAnswered).toHaveBeenCalledOnce() + }) + + it('does not read status for ordinary terminal input', () => { + const getStatusEntry = vi.fn(() => makeWaitingQuestionEntry()) + const inferQuestionAnswered = vi.fn() + const inference = createAgentQuestionAnsweredInference({ + paneKey: PANE_KEY, + getStatusEntry, + inferQuestionAnswered + }) + + inference.observeSentTerminalInput('ordinary typing') + + expect(getStatusEntry).not.toHaveBeenCalled() + expect(inferQuestionAnswered).not.toHaveBeenCalled() + }) + + it('ignores non-submit input, batched keystrokes, and pastes', () => { + const { inference, inferQuestionAnswered } = makeInference(makeWaitingQuestionEntry()) + + inference.observeSentTerminalInput('\x1b') + inference.observeSentTerminalInput('a') + inference.observeSentTerminalInput('0') + inference.observeSentTerminalInput('12') + inference.observeSentTerminalInput('yes\r') + inference.observeSentTerminalInput('\x1b[200~line one\nline two\x1b[201~') + + expect(inferQuestionAnswered).not.toHaveBeenCalled() + }) + + it('ignores panes without a fresh waiting AskUserQuestion status', () => { + const cases: (AgentStatusEntry | undefined)[] = [ + undefined, + makeWaitingQuestionEntry({ state: 'working' }), + makeWaitingQuestionEntry({ toolName: 'Bash' }), + makeWaitingQuestionEntry({ agentType: 'codex' }), + // Why: a stale wait past the freshness horizon no longer renders amber, + // so a keystroke must not synthesize activity for it. + makeWaitingQuestionEntry({ updatedAt: -100_000_000, stateStartedAt: -100_000_000 }) + ] + for (const entry of cases) { + const { inference, inferQuestionAnswered } = makeInference(entry) + inference.observeSentTerminalInput('\r') + expect(inferQuestionAnswered).not.toHaveBeenCalled() + } + }) +}) diff --git a/src/renderer/src/components/terminal-pane/agent-question-answered-inference.ts b/src/renderer/src/components/terminal-pane/agent-question-answered-inference.ts new file mode 100644 index 00000000000..1469648be2e --- /dev/null +++ b/src/renderer/src/components/terminal-pane/agent-question-answered-inference.ts @@ -0,0 +1,85 @@ +import { + AGENT_STATUS_STALE_AFTER_MS, + type AgentStatusEntry +} from '../../../../shared/agent-status-types' +import { + isAskUserQuestionTool, + isPotentialQuestionAnsweredSubmitInput, + isQuestionAnsweredSubmitInput, + type AgentQuestionAnsweredInferenceRequest +} from '../../../../shared/agent-question-answered-intent' +import { isExplicitAgentStatusFresh } from '@/lib/agent-status' + +export type AgentQuestionAnsweredInference = { + observeSentTerminalInput(data: string): void +} + +type AgentQuestionAnsweredInferenceDeps = { + paneKey: string + getStatusEntry: () => AgentStatusEntry | undefined + inferQuestionAnswered: ( + request: AgentQuestionAnsweredInferenceRequest + ) => boolean | Promise | void + now?: () => number +} + +function inferQuestionAnsweredFromEntry( + deps: AgentQuestionAnsweredInferenceDeps, + entry: AgentStatusEntry | undefined +): boolean { + const now = deps.now ?? Date.now + if ( + !entry || + entry.state !== 'waiting' || + entry.agentType !== 'claude' || + !isAskUserQuestionTool(entry.toolName) || + !isExplicitAgentStatusFresh(entry, now(), AGENT_STATUS_STALE_AFTER_MS) + ) { + return false + } + void deps.inferQuestionAnswered({ + paneKey: deps.paneKey, + baselineUpdatedAt: entry.updatedAt, + baselineStateStartedAt: entry.stateStartedAt, + baselinePrompt: entry.prompt, + baselineAgentType: entry.agentType + }) + return true +} + +/** Completion signal for answer surfaces that write directly to the runtime + * instead of xterm (notably native chat). The same fresh-status baseline is + * used, so a real hook that wins the race prevents the fallback IPC. */ +export function inferQuestionAnsweredFromCurrentStatus( + deps: AgentQuestionAnsweredInferenceDeps +): boolean { + return inferQuestionAnsweredFromEntry(deps, deps.getStatusEntry()) +} + +/** Sibling of the interrupt inference for a hook Claude never sends: answering + * an AskUserQuestion emits no event, so the submit keystroke into the waiting + * pane is the only "question dealt with" signal. Unlike interrupts there is + * no expected real hook to settle for, so the inference fires immediately — + * the main process re-validates the baseline, so a racing hook always wins. */ +export function createAgentQuestionAnsweredInference({ + paneKey, + getStatusEntry, + inferQuestionAnswered, + now = () => Date.now() +}: AgentQuestionAnsweredInferenceDeps): AgentQuestionAnsweredInference { + return { + observeSentTerminalInput(data) { + // Why: ordinary terminal input is the hot path. Reject it with one + // constant-time membership check before reading Zustand or parsing the + // bounded interactive-prompt JSON. + if (!isPotentialQuestionAnsweredSubmitInput(data)) { + return + } + const entry = getStatusEntry() + if (!entry || !isQuestionAnsweredSubmitInput(data, entry.interactivePrompt)) { + return + } + inferQuestionAnsweredFromEntry({ paneKey, getStatusEntry, inferQuestionAnswered, now }, entry) + } + } +} diff --git a/src/renderer/src/components/terminal-pane/pty-connection.ts b/src/renderer/src/components/terminal-pane/pty-connection.ts index 859a71bba1a..5716e535dfa 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.ts @@ -162,6 +162,7 @@ import { isCtrlCKeyEvent, isPlainEscapeKeyEvent } from './agent-interrupt-inference' +import { createAgentQuestionAnsweredInference } from './agent-question-answered-inference' import { AGENT_INTERRUPT_SETTLE_MS, type AgentInterruptInputIntent @@ -1771,6 +1772,15 @@ export function connectPanePty( }) } }) + const questionAnsweredInference = createAgentQuestionAnsweredInference({ + paneKey: cacheKey, + getStatusEntry: () => useAppStore.getState().agentStatusByPaneKey[cacheKey], + inferQuestionAnswered: (request) => + window.api.agentStatus.inferQuestionAnswered(request).catch((err) => { + console.warn('[agent-question] inferQuestionAnswered failed:', err) + return false + }) + }) const dropCommandFinishedStatusIfSameTurn = ( entry: AgentStatusEntry | undefined, options?: { allowInferredInterrupt?: boolean } @@ -1856,6 +1866,10 @@ export function connectPanePty( if (intent === 'ctrl-c' || data === '\x03') { markTerminalBracketedPasteInterrupted(pane.terminal) } + // Why: every delivered-input path funnels through here, so this is where a + // submit keystroke into a waiting AskUserQuestion pane becomes the + // "question answered" signal no hook will ever deliver. + questionAnsweredInference.observeSentTerminalInput(data) } let pendingTerminalInputWrite: Promise | null = null const setPendingTerminalInputWrite = (promise: Promise): void => { diff --git a/src/renderer/src/web/web-preload-api.ts b/src/renderer/src/web/web-preload-api.ts index 29268cee148..cddcfae9346 100644 --- a/src/renderer/src/web/web-preload-api.ts +++ b/src/renderer/src/web/web-preload-api.ts @@ -745,6 +745,7 @@ function createWebPreloadApi(): Partial { onClear: () => noopUnsubscribe, getSnapshot: () => Promise.resolve([]), inferInterrupt: () => Promise.resolve(false), + inferQuestionAnswered: () => Promise.resolve(false), onMigrationUnsupported: () => noopUnsubscribe, onMigrationUnsupportedClear: () => noopUnsubscribe, getMigrationUnsupportedSnapshot: () => Promise.resolve([]), diff --git a/src/shared/agent-hook-listener.test.ts b/src/shared/agent-hook-listener.test.ts index e9f9e265f62..e5c739ab3a7 100644 --- a/src/shared/agent-hook-listener.test.ts +++ b/src/shared/agent-hook-listener.test.ts @@ -6,6 +6,7 @@ import { mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } import { tmpdir } from 'node:os' import { join } from 'node:path' import { + clearClaudeAnsweredQuestionWait, clearPaneCacheState, createHookListenerState, getEndpointFileName, @@ -2781,4 +2782,60 @@ describe('shared agent-hook-listener', () => { expect(ok).toBe(false) }) }) + + describe('clearClaudeAnsweredQuestionWait', () => { + const claudeEvent = ( + payload: Record + ): ReturnType => + normalizeHookPayload(state, 'claude', { paneKey: PANE_KEY, payload }, 'production') + + it('restores working for an answered lead question and drops the card', () => { + claudeEvent({ hook_event_name: 'UserPromptSubmit', prompt: 'pick a color' }) + const wait = claudeEvent({ + hook_event_name: 'PreToolUse', + tool_name: 'AskUserQuestion', + tool_input: { questions: [{ question: 'Red or Blue?' }] } + }) + expect(wait?.payload.state).toBe('waiting') + expect(wait?.payload.interactivePrompt).toBeDefined() + + expect(clearClaudeAnsweredQuestionWait(state, PANE_KEY)).toEqual({ state: 'working' }) + + // Why: a child-driven refresh re-emits the cached lead state; the linger + // bug would come back if it could resurrect the dismissed question. + const childDriven = claudeEvent({ + hook_event_name: 'SubagentStart', + agent_id: 'a1', + agent_type: 'probe' + }) + expect(childDriven?.payload.state).toBe('working') + expect(childDriven?.payload.toolName).toBeUndefined() + expect(childDriven?.payload.interactivePrompt).toBeUndefined() + }) + + it('restores the stashed lead state for an answered child question', () => { + claudeEvent({ hook_event_name: 'UserPromptSubmit', prompt: 'go' }) + claudeEvent({ hook_event_name: 'SubagentStart', agent_id: 'a1', agent_type: 'probe' }) + claudeEvent({ hook_event_name: 'Stop' }) + const wait = claudeEvent({ + hook_event_name: 'PreToolUse', + tool_name: 'AskUserQuestion', + agent_id: 'a1', + tool_input: { questions: [{ question: 'Continue?' }] } + }) + expect(wait?.payload.state).toBe('waiting') + + // Why: the lead already finished; the answer resumes the child, so the + // emitted state is gated up to working only while that child still runs. + expect(clearClaudeAnsweredQuestionWait(state, PANE_KEY)).toEqual({ state: 'working' }) + expect(state.claudeLeadStateByPaneKey.get(PANE_KEY)).toEqual({ state: 'done' }) + + const drained = claudeEvent({ hook_event_name: 'SubagentStop', agent_id: 'a1' }) + expect(drained?.payload.state).toBe('done') + }) + + it('falls back to working when no lead record exists', () => { + expect(clearClaudeAnsweredQuestionWait(state, PANE_KEY)).toEqual({ state: 'working' }) + }) + }) }) diff --git a/src/shared/agent-hook-listener.ts b/src/shared/agent-hook-listener.ts index bde6baf4431..f080ce1cecf 100644 --- a/src/shared/agent-hook-listener.ts +++ b/src/shared/agent-hook-listener.ts @@ -34,6 +34,7 @@ import { type AgentSubagentSnapshot, type ParsedAgentStatusPayload } from './agent-status-types' +import { isAskUserQuestionTool } from './agent-question-answered-intent' import { claudeRosterHasWorkingSubagent, claudeRosterToSnapshots, @@ -744,14 +745,6 @@ function clearActiveToolFieldsUpdate(): ToolSnapshot { ) } -/** True for the AskUserQuestion tool across the casing variants different - * agents emit (`AskUserQuestion` / `ask_user_question` / `askUserQuestion`). - * Why: this is the structured "pick an option" prompt whose full input the - * clients render as a live card. */ -function isAskUserQuestionTool(toolName: string | undefined): boolean { - return toolName?.replaceAll(/[^a-z0-9]/gi, '').toLowerCase() === 'askuserquestion' -} - /** Capture the full AskUserQuestion tool input as a JSON string when the tool * is an AskUserQuestion variant; otherwise undefined so resolveToolState * clears any prior prompt. Kept agent-generic: callers pass whatever raw @@ -2525,6 +2518,36 @@ function clearClaudePendingWaitForAgent( state.claudeLeadStateByPaneKey.set(paneKey, lead.stateBeforeWait ?? { state: 'working' }) } +/** Clear an AskUserQuestion wait after the user's answer was typed into the + * terminal. Answering emits no hook event, so the caller infers it from the + * submit keystroke. Restores the stashed pre-wait lead state (child-induced + * question) or falls back to 'working' (lead question), and drops the cached + * question card so later child-driven refreshes cannot re-emit the stale + * wait. Returns the pane state to emit, gated up to 'working' while children + * still run. */ +export function clearClaudeAnsweredQuestionWait( + state: HookListenerState, + paneKey: string +): Pick { + const lead = state.claudeLeadStateByPaneKey.get(paneKey) + const restored = + lead?.state === 'waiting' + ? (lead.stateBeforeWait ?? { state: 'working' as const }) + : { state: 'working' as const } + state.claudeLeadStateByPaneKey.set(paneKey, { ...restored }) + const previousTool = state.lastToolByPaneKey.get(paneKey) + state.lastToolByPaneKey.set( + paneKey, + previousTool?.lastAssistantMessage + ? { lastAssistantMessage: previousTool.lastAssistantMessage } + : {} + ) + const roster = state.claudeSubagentRosterByPaneKey.get(paneKey) + return restored.state === 'done' && claudeRosterHasWorkingSubagent(roster) + ? { state: 'working' } + : restored +} + /** Emit a pane status refresh driven by child activity (lifecycle events and * child-origin tool events): the lead's cached state is re-emitted — gated up * to 'working' while a child works — without touching the lead's tool/prompt diff --git a/src/shared/agent-question-answered-intent.ts b/src/shared/agent-question-answered-intent.ts new file mode 100644 index 00000000000..de976291a48 --- /dev/null +++ b/src/shared/agent-question-answered-intent.ts @@ -0,0 +1,83 @@ +import type { AgentType } from './agent-status-types' + +/** Baseline snapshot the renderer captured when it observed the submit + * keystroke. The main process re-validates every field against its own + * cached status so a racing real hook always wins over the inference. */ +export type AgentQuestionAnsweredInferenceRequest = { + paneKey: string + baselineUpdatedAt: number + baselineStateStartedAt: number + baselinePrompt: string + baselineAgentType: AgentType | undefined +} + +/** True for the AskUserQuestion tool across the casing variants different + * agents emit (`AskUserQuestion` / `ask_user_question` / `askUserQuestion`). + * Why: this is the structured "pick an option" prompt whose full input the + * clients render as a live card. */ +export function isAskUserQuestionTool(toolName: string | undefined): boolean { + return toolName?.replaceAll(/[^a-z0-9]/gi, '').toLowerCase() === 'askuserquestion' +} + +const QUESTION_ANSWER_ENTER_INPUTS: ReadonlySet = new Set([ + '\r', + '\n', + '\r\n', + '\x1b[13u', + '\x1b[13;1u' +]) +const QUESTION_ANSWER_DIGIT_INPUTS: ReadonlySet = new Set('123456789') + +export function isPotentialQuestionAnsweredSubmitInput(data: string): boolean { + return QUESTION_ANSWER_ENTER_INPUTS.has(data) || QUESTION_ANSWER_DIGIT_INPUTS.has(data) +} + +function readSingleSelectOptionCount(interactivePrompt: string | undefined): number | null { + if (!interactivePrompt) { + return null + } + try { + const parsed = JSON.parse(interactivePrompt) as { questions?: unknown } + if (!Array.isArray(parsed.questions) || parsed.questions.length !== 1) { + return -1 + } + const [question] = parsed.questions as { multiSelect?: unknown; options?: unknown }[] + if (!question || question.multiSelect === true || !Array.isArray(question.options)) { + return -1 + } + return question.options.length + } catch { + // Why: malformed JSON can be a length-capped multi-question payload. It is + // not equivalent to an older hook omitting tool input, so fail closed. + return -1 + } +} + +/** True only when one keystroke is enough to finish the whole prompt. + * Why: digits merely advance multi-question prompts and toggle multi-selects; + * the synthetic "Type something" row also opens an editor instead of + * submitting. Without the prompt-shape gate those partial choices clear the + * waiting indicator while Claude is still blocked on more input. */ +export function isQuestionAnsweredSubmitInput( + data: string, + interactivePrompt: string | undefined +): boolean { + if (!isPotentialQuestionAnsweredSubmitInput(data)) { + return false + } + const optionCount = readSingleSelectOptionCount(interactivePrompt) + if (optionCount === -1) { + return false + } + if (QUESTION_ANSWER_ENTER_INPUTS.has(data)) { + // Older hook payloads can omit tool input; Enter remains the conservative + // fallback because it is the ordinary submit path for a question. + return true + } + if (optionCount === null) { + return false + } + // Claude adds a final "Type something" row after the declared options. + // Only declared option numbers complete a single-select immediately. + return Number(data) <= optionCount +} From 5b6cefa5ab63e45f4624e93ab1b69fb0f0e60f1e Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:25:38 -0700 Subject: [PATCH 003/137] fix(orchestration): emit message timestamps as RFC3339 UTC (#9167) --- .../db-message-timestamp.test.ts | 19 +++ src/main/runtime/orchestration/db.ts | 134 ++++++++++++------ 2 files changed, 111 insertions(+), 42 deletions(-) create mode 100644 src/main/runtime/orchestration/db-message-timestamp.test.ts diff --git a/src/main/runtime/orchestration/db-message-timestamp.test.ts b/src/main/runtime/orchestration/db-message-timestamp.test.ts new file mode 100644 index 00000000000..d4d000c48d3 --- /dev/null +++ b/src/main/runtime/orchestration/db-message-timestamp.test.ts @@ -0,0 +1,19 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { OrchestrationDb } from './db' + +describe('orchestration message timestamps', () => { + let db: OrchestrationDb | undefined + + afterEach(() => db?.close()) + + it('exposes SQLite timestamps with an explicit UTC designator', () => { + db = new OrchestrationDb(':memory:') + const message = db.insertMessage({ from: 'a', to: 'b', subject: 'timestamped' }) + + expect(message.created_at).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/) + db.markAsDelivered([message.id]) + expect(db.getMessageById(message.id)?.delivered_at).toMatch( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/ + ) + }) +}) diff --git a/src/main/runtime/orchestration/db.ts b/src/main/runtime/orchestration/db.ts index 2525d30c851..5e8a46738fa 100644 --- a/src/main/runtime/orchestration/db.ts +++ b/src/main/runtime/orchestration/db.ts @@ -62,6 +62,29 @@ function addLifecycleRejectionMarker(payload: string | null, reason: string): st }) } +const SQLITE_UTC_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?$/ + +function exposeUtcTimestamp(timestamp: string | null): string | null { + if (!timestamp || !SQLITE_UTC_TIMESTAMP_RE.test(timestamp)) { + return timestamp + } + return `${timestamp.replace(' ', 'T')}Z` +} + +function exposeMessageTimestamps(message: MessageRow): MessageRow { + // Why: SQLite stores UTC as a timezone-less space format for internal SQL + // ordering, but RPC/CLI consumers need an explicit offset to interpret it. + return { + ...message, + created_at: exposeUtcTimestamp(message.created_at) ?? message.created_at, + delivered_at: exposeUtcTimestamp(message.delivered_at) + } +} + +function exposeMessageListTimestamps(messages: MessageRow[]): MessageRow[] { + return messages.map(exposeMessageTimestamps) +} + // Why: v1 → v2 added `'heartbeat'` to messages.type CHECK + `last_heartbeat_at` // column (preamble-hardening PR). v2 → v3 adds `delivered_at` column so // push-on-idle can distinguish queued-but-undelivered from user-acknowledged @@ -353,21 +376,27 @@ export class OrchestrationDb { msg.payload ?? null, msg.senderPaneKey ?? null ) - return this.db.prepare('SELECT * FROM messages WHERE id = ?').get(id) as MessageRow + return exposeMessageTimestamps( + this.db.prepare('SELECT * FROM messages WHERE id = ?').get(id) as MessageRow + ) } getUnreadMessages(toHandle: string, types?: MessageType[]): MessageRow[] { if (types && types.length > 0) { const placeholders = types.map(() => '?').join(',') - return this.db - .prepare( - `SELECT * FROM messages WHERE to_handle = ? AND read = 0 AND type IN (${placeholders}) ORDER BY sequence` - ) - .all(toHandle, ...types) as MessageRow[] + return exposeMessageListTimestamps( + this.db + .prepare( + `SELECT * FROM messages WHERE to_handle = ? AND read = 0 AND type IN (${placeholders}) ORDER BY sequence` + ) + .all(toHandle, ...types) as MessageRow[] + ) } - return this.db - .prepare('SELECT * FROM messages WHERE to_handle = ? AND read = 0 ORDER BY sequence') - .all(toHandle) as MessageRow[] + return exposeMessageListTimestamps( + this.db + .prepare('SELECT * FROM messages WHERE to_handle = ? AND read = 0 ORDER BY sequence') + .all(toHandle) as MessageRow[] + ) } convertLifecycleMessageToRejection(messageId: string, reason: string): MessageRow | undefined { @@ -400,27 +429,36 @@ export class OrchestrationDb { getUndeliveredUnreadMessages(toHandle: string, types?: MessageType[]): MessageRow[] { if (types && types.length > 0) { const placeholders = types.map(() => '?').join(',') - return this.db - .prepare( - `SELECT * FROM messages WHERE to_handle = ? AND read = 0 AND delivered_at IS NULL AND type IN (${placeholders}) ORDER BY sequence` - ) - .all(toHandle, ...types) as MessageRow[] - } - return this.db - .prepare( - 'SELECT * FROM messages WHERE to_handle = ? AND read = 0 AND delivered_at IS NULL ORDER BY sequence' + return exposeMessageListTimestamps( + this.db + .prepare( + `SELECT * FROM messages WHERE to_handle = ? AND read = 0 AND delivered_at IS NULL AND type IN (${placeholders}) ORDER BY sequence` + ) + .all(toHandle, ...types) as MessageRow[] ) - .all(toHandle) as MessageRow[] + } + return exposeMessageListTimestamps( + this.db + .prepare( + 'SELECT * FROM messages WHERE to_handle = ? AND read = 0 AND delivered_at IS NULL ORDER BY sequence' + ) + .all(toHandle) as MessageRow[] + ) } getAllMessages(toHandle: string, limit = 20): MessageRow[] { - return this.db - .prepare('SELECT * FROM messages WHERE to_handle = ? ORDER BY sequence DESC LIMIT ?') - .all(toHandle, limit) as MessageRow[] + return exposeMessageListTimestamps( + this.db + .prepare('SELECT * FROM messages WHERE to_handle = ? ORDER BY sequence DESC LIMIT ?') + .all(toHandle, limit) as MessageRow[] + ) } getMessageById(id: string): MessageRow | undefined { - return this.db.prepare('SELECT * FROM messages WHERE id = ?').get(id) as MessageRow | undefined + const message = this.db.prepare('SELECT * FROM messages WHERE id = ?').get(id) as + | MessageRow + | undefined + return message ? exposeMessageTimestamps(message) : undefined } markAsRead(ids: string[]): void { @@ -461,9 +499,11 @@ export class OrchestrationDb { } getInbox(limit = 20): MessageRow[] { - return this.db - .prepare('SELECT * FROM messages ORDER BY sequence DESC LIMIT ?') - .all(limit) as MessageRow[] + return exposeMessageListTimestamps( + this.db + .prepare('SELECT * FROM messages ORDER BY sequence DESC LIMIT ?') + .all(limit) as MessageRow[] + ) } // Why: used by `check --all` and `inbox --terminal ` — returns every @@ -473,15 +513,19 @@ export class OrchestrationDb { getAllMessagesForHandle(toHandle: string, limit = 100, types?: MessageType[]): MessageRow[] { if (types && types.length > 0) { const placeholders = types.map(() => '?').join(',') - return this.db - .prepare( - `SELECT * FROM messages WHERE to_handle = ? AND type IN (${placeholders}) ORDER BY sequence DESC LIMIT ?` - ) - .all(toHandle, ...types, limit) as MessageRow[] + return exposeMessageListTimestamps( + this.db + .prepare( + `SELECT * FROM messages WHERE to_handle = ? AND type IN (${placeholders}) ORDER BY sequence DESC LIMIT ?` + ) + .all(toHandle, ...types, limit) as MessageRow[] + ) } - return this.db - .prepare('SELECT * FROM messages WHERE to_handle = ? ORDER BY sequence DESC LIMIT ?') - .all(toHandle, limit) as MessageRow[] + return exposeMessageListTimestamps( + this.db + .prepare('SELECT * FROM messages WHERE to_handle = ? ORDER BY sequence DESC LIMIT ?') + .all(toHandle, limit) as MessageRow[] + ) } // Why: thread-scoped read for the `orchestration.ask` wait loop. Filtered @@ -492,15 +536,21 @@ export class OrchestrationDb { // the existing idx_thread index (see createTables) — no new index. getThreadMessagesFor(threadId: string, toHandle: string, afterSequence?: number): MessageRow[] { if (afterSequence !== undefined) { - return this.db - .prepare( - 'SELECT * FROM messages WHERE thread_id = ? AND to_handle = ? AND sequence > ? ORDER BY sequence ASC' - ) - .all(threadId, toHandle, afterSequence) as MessageRow[] + return exposeMessageListTimestamps( + this.db + .prepare( + 'SELECT * FROM messages WHERE thread_id = ? AND to_handle = ? AND sequence > ? ORDER BY sequence ASC' + ) + .all(threadId, toHandle, afterSequence) as MessageRow[] + ) } - return this.db - .prepare('SELECT * FROM messages WHERE thread_id = ? AND to_handle = ? ORDER BY sequence ASC') - .all(threadId, toHandle) as MessageRow[] + return exposeMessageListTimestamps( + this.db + .prepare( + 'SELECT * FROM messages WHERE thread_id = ? AND to_handle = ? ORDER BY sequence ASC' + ) + .all(threadId, toHandle) as MessageRow[] + ) } // ── Tasks ── From f544820552e2625624e7c2bdff9bfa08597fd4af Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:27:31 -0700 Subject: [PATCH 004/137] fix(ssh): require a coherent colocated Node/npm toolchain for the relay (#9165) --- .../ssh/ssh-remote-node-resolution.test.ts | 16 +++++ src/main/ssh/ssh-remote-node-resolution.ts | 51 +++++++-------- .../ssh-remote-node-toolchain-probe.test.ts | 47 ++++++++++++++ .../ssh/ssh-remote-node-toolchain-probe.ts | 64 +++++++++++++++++++ 4 files changed, 150 insertions(+), 28 deletions(-) create mode 100644 src/main/ssh/ssh-remote-node-toolchain-probe.test.ts create mode 100644 src/main/ssh/ssh-remote-node-toolchain-probe.ts diff --git a/src/main/ssh/ssh-remote-node-resolution.test.ts b/src/main/ssh/ssh-remote-node-resolution.test.ts index 109725e549c..55a3877ff07 100644 --- a/src/main/ssh/ssh-remote-node-resolution.test.ts +++ b/src/main/ssh/ssh-remote-node-resolution.test.ts @@ -41,6 +41,22 @@ describe('resolveRemoteNodePath', () => { await expect(resolveRemoteNodePath(conn)).resolves.toBe('/usr/local/bin/node') }) + it('skips an incomplete system Node and selects a complete NVM toolchain', async () => { + execCommandMock + .mockResolvedValueOnce('/usr/bin/node\n/home/u/.nvm/versions/node/v22.22.0/bin/node\n') + .mockRejectedValueOnce(new Error('/usr/bin/npm: not found')) + .mockResolvedValueOnce('__ORCA_NODE_VERSION__\nv22.22.0\n__ORCA_NPM_VERSION__\n11.13.0\n') + + await expect(resolveRemoteNodePath(conn)).resolves.toBe( + '/home/u/.nvm/versions/node/v22.22.0/bin/node' + ) + + expect(execCommandMock.mock.calls[1]![1]).toContain("'/usr/bin/npm' --version") + expect(execCommandMock.mock.calls[2]![1]).toContain( + "'/home/u/.nvm/versions/node/v22.22.0/bin/npm' --version" + ) + }) + it('probes mise install directories', async () => { execCommandMock .mockResolvedValueOnce('/home/u/.local/share/mise/installs/node/20/bin/node\n') diff --git a/src/main/ssh/ssh-remote-node-resolution.ts b/src/main/ssh/ssh-remote-node-resolution.ts index a5041ed158d..2dd89e4a2a2 100644 --- a/src/main/ssh/ssh-remote-node-resolution.ts +++ b/src/main/ssh/ssh-remote-node-resolution.ts @@ -1,21 +1,21 @@ import type { SshConnection } from './ssh-connection' -import { createSshOperationAbortError, shellEscape } from './ssh-connection-utils' +import { createSshOperationAbortError } from './ssh-connection-utils' import type { RemoteHostPlatform } from './ssh-remote-platform' import { isWindowsRemoteHost, normalizeWindowsRemotePath } from './ssh-remote-platform' -import { powerShellCommand, powerShellLiteral } from './ssh-remote-powershell' +import { powerShellCommand } from './ssh-remote-powershell' import { buildPosixNodeInstallGuidance, type RemoteNodeResolutionOptions } from './ssh-remote-node-install-guidance' import { execCommand } from './ssh-relay-deploy-helpers' +import { + buildPosixNodeToolchainProbe, + buildWindowsNodeToolchainProbe, + nodeToolchainVersionsMeetRequirements +} from './ssh-remote-node-toolchain-probe' import { isSshSessionLimitError } from './ssh-session-limit-error' import { buildSshLoginShellCommand } from './ssh-login-shell-command' -// Why: the relay requires Node.js 18+. Version managers like nvm keep every -// installed version on disk, so a naive "highest version" glob can hand back -// Node 8/10/12 and crash the relay on launch. Gate every candidate on this. -const MIN_NODE_MAJOR = 18 - // Why: the login-shell fallback catches custom PATH setups in ~/.profile that // the path probes don't cover. Interactive configs (conda prompts, etc.) can // hang a login shell, so keep this short. @@ -53,7 +53,7 @@ export async function resolveRemoteNodePath( // Probe the on-disk install directories of every common Node version manager // plus system package-manager locations. Every probe runs unconditionally so // a missing directory prints nothing rather than short-circuiting later -// probes. Returns the first candidate that meets the minimum version. +// probes. Returns the first candidate with a complete Node/npm toolchain. async function tryResolveViaKnownPaths( conn: SshConnection, options?: RemoteNodeResolutionOptions @@ -113,7 +113,7 @@ true continue } seen.add(candidate) - if (await nodeMeetsVersionRequirement(conn, candidate, options)) { + if (await nodeToolchainMeetsRequirements(conn, candidate, options)) { console.log(`[ssh-relay] Found node via path probe: ${candidate}`) return candidate } @@ -160,7 +160,7 @@ async function tryResolveViaLoginShell( return null } - if (await nodeMeetsVersionRequirement(conn, candidate, options)) { + if (await nodeToolchainMeetsRequirements(conn, candidate, options)) { console.log(`[ssh-relay] Found node via login shell (${shell}): ${candidate}`) return candidate } @@ -174,10 +174,12 @@ async function tryResolveViaLoginShell( return null } -// Returns true if `nodePath` runs and reports Node >= MIN_NODE_MAJOR. +// Returns true if `nodePath` runs, reports Node >= 18, and has a runnable npm +// beside it. Deployment prepends this same directory before invoking npm, so +// accepting a looser pairing would recreate #8450. // Caches nothing — this runs at most a few times per resolution (one per // candidate), and the exec round-trip dominates. -async function nodeMeetsVersionRequirement( +async function nodeToolchainMeetsRequirements( conn: SshConnection, nodePath: string, options?: RemoteNodeResolutionOptions @@ -185,10 +187,12 @@ async function nodeMeetsVersionRequirement( try { const versionOutput = await execCommand( conn, - `${shellEscape(nodePath)} --version`, - commandOptions({ wrapCommand: false }, options) + buildPosixNodeToolchainProbe(nodePath), + // Why: the paired probe uses POSIX PATH assignment syntax, which fish + // and csh cannot parse when sshd delegates directly to the login shell. + commandOptions({ wrapCommand: true }, options) ) - return nodeVersionMeetsRequirement(versionOutput) + return nodeToolchainVersionsMeetRequirements(versionOutput) } catch (err) { if (options?.rethrowSessionLimitErrors && isSshSessionLimitError(err)) { throw err @@ -234,7 +238,7 @@ async function resolveRemoteWindowsNodePath( continue } const normalized = normalizeWindowsRemotePath(nodePath) - if (await windowsNodeMeetsVersionRequirement(conn, normalized, options)) { + if (await windowsNodeToolchainMeetsRequirements(conn, normalized, options)) { console.log(`[ssh-relay] Found Windows node at: ${normalized}`) return normalized } @@ -250,7 +254,7 @@ async function resolveRemoteWindowsNodePath( throwWindowsNodeNotFound(options) } -async function windowsNodeMeetsVersionRequirement( +async function windowsNodeToolchainMeetsRequirements( conn: SshConnection, nodePath: string, options?: RemoteNodeResolutionOptions @@ -258,10 +262,10 @@ async function windowsNodeMeetsVersionRequirement( try { const versionOutput = await execCommand( conn, - powerShellCommand(`& ${powerShellLiteral(nodePath)} --version`), + powerShellCommand(buildWindowsNodeToolchainProbe(nodePath)), commandOptions({ wrapCommand: false }, options) ) - return nodeVersionMeetsRequirement(versionOutput) + return nodeToolchainVersionsMeetRequirements(versionOutput) } catch (err) { if (options?.rethrowSessionLimitErrors && isSshSessionLimitError(err)) { throw err @@ -271,15 +275,6 @@ async function windowsNodeMeetsVersionRequirement( } } -function nodeVersionMeetsRequirement(versionOutput: string): boolean { - const match = versionOutput.trim().match(/^v?(\d+)/) - if (!match) { - return false - } - const major = Number.parseInt(match[1]!, 10) - return major >= MIN_NODE_MAJOR -} - async function throwNodeNotFound( conn: SshConnection, options?: RemoteNodeResolutionOptions diff --git a/src/main/ssh/ssh-remote-node-toolchain-probe.test.ts b/src/main/ssh/ssh-remote-node-toolchain-probe.test.ts new file mode 100644 index 00000000000..4fb3dda56ca --- /dev/null +++ b/src/main/ssh/ssh-remote-node-toolchain-probe.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest' +import { + buildPosixNodeToolchainProbe, + buildWindowsNodeToolchainProbe, + nodeToolchainVersionsMeetRequirements +} from './ssh-remote-node-toolchain-probe' + +describe('remote Node/npm toolchain probe', () => { + it('probes npm beside the selected POSIX Node with that directory on PATH', () => { + expect(buildPosixNodeToolchainProbe('/home/u/My Node/bin/node')).toBe( + "printf '%s\\n' '__ORCA_NODE_VERSION__' && '/home/u/My Node/bin/node' --version && " + + "printf '%s\\n' '__ORCA_NPM_VERSION__' && PATH='/home/u/My Node/bin':$PATH " + + "'/home/u/My Node/bin/npm' --version" + ) + }) + + it('probes npm.cmd beside the selected Windows Node', () => { + const probe = buildWindowsNodeToolchainProbe('C:/Program Files/nodejs/node.exe') + + expect(probe).toContain("Test-Path -LiteralPath 'C:/Program Files/nodejs/npm.cmd'") + expect(probe).toContain("$env:PATH = 'C:/Program Files/nodejs' + ';' + $env:PATH") + expect(probe).toContain("& 'C:/Program Files/nodejs/npm.cmd' --version") + }) + + it('requires marked, parseable Node and npm versions', () => { + expect( + nodeToolchainVersionsMeetRequirements( + 'banner\n__ORCA_NODE_VERSION__\nv22.22.0\n__ORCA_NPM_VERSION__\n11.13.0\n' + ) + ).toBe(true) + expect( + nodeToolchainVersionsMeetRequirements( + '__ORCA_NODE_VERSION__\nv22.22.0\n__ORCA_NPM_VERSION__\nshim did nothing\n' + ) + ).toBe(false) + expect( + nodeToolchainVersionsMeetRequirements( + '__ORCA_NODE_VERSION__\nv16.20.2\n__ORCA_NPM_VERSION__\n10.8.2\n' + ) + ).toBe(false) + }) + + it('accepts legacy Node-only output from existing proxy integrations', () => { + expect(nodeToolchainVersionsMeetRequirements('v18.0.0\n')).toBe(true) + expect(nodeToolchainVersionsMeetRequirements('v16.20.2\n')).toBe(false) + }) +}) diff --git a/src/main/ssh/ssh-remote-node-toolchain-probe.ts b/src/main/ssh/ssh-remote-node-toolchain-probe.ts new file mode 100644 index 00000000000..addf9b5752c --- /dev/null +++ b/src/main/ssh/ssh-remote-node-toolchain-probe.ts @@ -0,0 +1,64 @@ +import { posix as posixPath } from 'node:path' +import { shellEscape } from './ssh-connection-utils' +import { powerShellLiteral } from './ssh-remote-powershell' + +const MIN_NODE_MAJOR = 18 +const NODE_VERSION_MARKER = '__ORCA_NODE_VERSION__' +const NPM_VERSION_MARKER = '__ORCA_NPM_VERSION__' + +export function buildPosixNodeToolchainProbe(nodePath: string): string { + const nodeBinDir = posixPath.dirname(nodePath) + const npmPath = posixPath.join(nodeBinDir, 'npm') + return [ + `printf '%s\\n' '${NODE_VERSION_MARKER}'`, + `${shellEscape(nodePath)} --version`, + `printf '%s\\n' '${NPM_VERSION_MARKER}'`, + `PATH=${shellEscape(nodeBinDir)}:$PATH ${shellEscape(npmPath)} --version` + ].join(' && ') +} + +export function buildWindowsNodeToolchainProbe(nodePath: string): string { + const nodeBinDir = posixPath.dirname(nodePath) + const npmPath = posixPath.join(nodeBinDir, 'npm.cmd') + return [ + `if (!(Test-Path -LiteralPath ${powerShellLiteral(npmPath)} -PathType Leaf)) { exit 1 }`, + `$env:PATH = ${powerShellLiteral(nodeBinDir)} + ';' + $env:PATH`, + `Write-Output ${powerShellLiteral(NODE_VERSION_MARKER)}`, + `& ${powerShellLiteral(nodePath)} --version`, + 'if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }', + `Write-Output ${powerShellLiteral(NPM_VERSION_MARKER)}`, + `& ${powerShellLiteral(npmPath)} --version`, + 'exit $LASTEXITCODE' + ].join('; ') +} + +export function nodeToolchainVersionsMeetRequirements(versionOutput: string): boolean { + const nodeMajor = markedVersionMajor(versionOutput, NODE_VERSION_MARKER) + const npmMajor = markedVersionMajor(versionOutput, NPM_VERSION_MARKER) + if (versionOutput.includes(NODE_VERSION_MARKER)) { + return nodeMajor !== null && nodeMajor >= MIN_NODE_MAJOR && npmMajor !== null + } + + // Why: existing proxy integrations and tests may return only the Node + // version even though production probes now emit both version markers. + const legacyMatch = versionOutput.trim().match(/^v?(\d+)/) + return legacyMatch ? Number.parseInt(legacyMatch[1]!, 10) >= MIN_NODE_MAJOR : false +} + +function markedVersionMajor(output: string, marker: string): number | null { + const lines = output.split(/\r?\n/) + const markerIndex = lines.indexOf(marker) + if (markerIndex < 0) { + return null + } + for (const line of lines.slice(markerIndex + 1)) { + if (line.startsWith('__ORCA_')) { + return null + } + const match = line.trim().match(/^v?(\d+)(?:\.\d+){1,2}(?:[-+].*)?$/) + if (match) { + return Number.parseInt(match[1]!, 10) + } + } + return null +} From e6933fed847ea32817b1f87eaf9c4f993a2e5953 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:09:22 -0700 Subject: [PATCH 005/137] fix(grok): honor zero-usage weekly reset (#9219) * fix(grok): honor zero-usage weekly reset * fix(grok): preserve ambiguous monthly billing fallback --- src/main/rate-limits/grok-fetcher.test.ts | 28 +++++++++++++++++++- src/main/rate-limits/grok-fetcher.ts | 31 ++++++++++++++++++----- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/main/rate-limits/grok-fetcher.test.ts b/src/main/rate-limits/grok-fetcher.test.ts index b077b880576..63d7bc5cfcd 100644 --- a/src/main/rate-limits/grok-fetcher.test.ts +++ b/src/main/rate-limits/grok-fetcher.test.ts @@ -100,6 +100,31 @@ describe('fetchGrokRateLimits', () => { ) }) + it('maps an omitted protobuf percentage as zero for a weekly credits period', async () => { + authState.file = freshAuthJson() + netFetchMock.mockResolvedValueOnce( + jsonResponse({ + config: { + currentPeriod: { + type: 'USAGE_PERIOD_TYPE_WEEKLY', + start: '2026-07-17T19:38:56.948570+00:00', + end: '2026-07-24T19:38:56.948570+00:00' + }, + billingPeriodStart: '2026-07-17T19:38:56.948570+00:00', + billingPeriodEnd: '2026-07-24T19:38:56.948570+00:00', + isUnifiedBillingUser: true + } + }) + ) + + const result = await fetchGrokRateLimits() + expect(result.status).toBe('ok') + expect(result.weekly?.usedPercent).toBe(0) + expect(result.weekly?.resetsAt).toBe(Date.parse('2026-07-24T19:38:56.948570+00:00')) + expect(result.monthly).toBeUndefined() + expect(netFetchMock).toHaveBeenCalledTimes(1) + }) + it('returns unavailable when not signed in even if a token-less auth file exists', async () => { authState.file = JSON.stringify({}) const result = await fetchGrokRateLimits() @@ -119,7 +144,7 @@ describe('fetchGrokRateLimits', () => { expect(result.monthly).toBeUndefined() }) - it('maps monthly included usage for unified-billing accounts without weekly credits', async () => { + it('maps monthly included usage when a unified-billing response has an ambiguous weekly period', async () => { authState.file = freshAuthJson() netFetchMock .mockResolvedValueOnce( @@ -162,6 +187,7 @@ describe('fetchGrokRateLimits', () => { headers: expect.objectContaining({ Authorization: 'Bearer access-token' }) }) ) + expect(netFetchMock).toHaveBeenCalledTimes(2) }) // Why: 'unavailable' would make applyStalePolicy discard the last good diff --git a/src/main/rate-limits/grok-fetcher.ts b/src/main/rate-limits/grok-fetcher.ts index 1e6d20f5cce..366c75c3835 100644 --- a/src/main/rate-limits/grok-fetcher.ts +++ b/src/main/rate-limits/grok-fetcher.ts @@ -16,8 +16,8 @@ const GROK_CLI_PROXY_BASE = process.env.GROK_CLI_CHAT_PROXY_BASE_URL?.trim().replace(/\/$/, '') || 'https://cli-chat-proxy.grok.com/v1' const BILLING_CREDITS_URL = `${GROK_CLI_PROXY_BASE}/billing?format=credits` -// Why: unified-billing accounts have no weekly credits; their included monthly -// budget is only present in the default (format-less) billing view. +// Why: some unified-billing accounts expose only a monthly included budget, +// which is present in the default (format-less) billing view. const BILLING_DEFAULT_URL = `${GROK_CLI_PROXY_BASE}/billing` const API_TIMEOUT_MS = 10_000 const WEEKLY_WINDOW_MINUTES = 10_080 @@ -81,8 +81,28 @@ function parseResetDescription(isoString: string | undefined): string | null { : date.toLocaleDateString(undefined, { weekday: 'short', hour: 'numeric', minute: '2-digit' }) } +function timestampsMatch(left: string | undefined, right: string | undefined): boolean { + const leftTimestamp = left ? Date.parse(left) : Number.NaN + const rightTimestamp = right ? Date.parse(right) : Number.NaN + return Number.isFinite(leftTimestamp) && leftTimestamp === rightTimestamp +} + +function hasConfirmedWeeklyPeriod(config: GrokBillingConfig): boolean { + const period = config.currentPeriod + // Why: monthly unified-billing responses can also carry a weekly currentPeriod; + // matching billing bounds identify Grok's omitted protobuf zero unambiguously. + return ( + period?.type === 'USAGE_PERIOD_TYPE_WEEKLY' && + timestampsMatch(period.start, config.billingPeriodStart) && + timestampsMatch(period.end, config.billingPeriodEnd) + ) +} + function mapWeeklyCredits(config: GrokBillingConfig): RateLimitWindow | null { - const usedPercent = config.creditUsagePercent + const usedPercent = + config.creditUsagePercent === undefined && hasConfirmedWeeklyPeriod(config) + ? 0 + : config.creditUsagePercent if (typeof usedPercent !== 'number' || !Number.isFinite(usedPercent)) { return null } @@ -257,9 +277,8 @@ export async function fetchGrokRateLimits( if (weekly) { return billingUsageResult({ weekly }, config, session) } - // Why: unified-billing accounts report a monthly included-usage budget - // instead of weekly credits; the credits view omits creditUsagePercent - // for them, so read the default billing view before giving up. + // Why: some unified-billing accounts expose only a monthly included budget; + // their credits view omits creditUsagePercent, so read the default view. const fallback = await fetchMonthlyUsageFallback(session, options.signal) if (fallback.kind === 'result') { return fallback.result From e70842fe2c1052f3baf5babf03cc46b2a2f56005 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:10:08 -0700 Subject: [PATCH 006/137] fix(terminal): recover floating input after app refocus (#9233) * fix(terminal): recover floating input after app refocus * fix(terminal): preserve newer focus ownership --- .../FloatingTerminalPanel.test.tsx | 113 +++++++++++++++++- .../FloatingTerminalPanel.tsx | 68 ++++++++++- .../regular-terminal-focus-ownership.test.ts | 98 ++++++++++++++- .../regular-terminal-focus-ownership.ts | 26 +++- ...terminal-ime-input-context-refresh.test.ts | 63 +++++++++- .../terminal-ime-input-context-refresh.ts | 23 +++- .../lib/focus-terminal-tab-surface.test.ts | 53 ++++++++ .../src/lib/focus-terminal-tab-surface.ts | 41 ++++++- 8 files changed, 471 insertions(+), 14 deletions(-) diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.test.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.test.tsx index dfcf8337e3a..a29c76f1d55 100644 --- a/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.test.tsx +++ b/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.test.tsx @@ -106,12 +106,14 @@ const mocks = vi.hoisted(() => ({ getFloatingMarkdownDirectory: vi.fn(), getFloatingTerminalCwd: vi.fn(), getInstallStatus: vi.fn(), + isTerminalImeInputContextRefreshing: vi.fn(), isWebRuntimeSessionActive: vi.fn(), markFileDirty: vi.fn(), makePreviewFilePermanent: vi.fn(), openFile: vi.fn(), pickFloatingMarkdownDocument: vi.fn(), pinFile: vi.fn(), + setFloatingTerminalInputFocused: vi.fn(), setActiveTab: vi.fn(), setRenamingTabId: vi.fn(), setTabColor: vi.fn(), @@ -185,6 +187,10 @@ vi.mock('@/components/terminal-pane/TerminalPane', () => ({ } })) +vi.mock('@/components/terminal-pane/terminal-ime-input-context-refresh', () => ({ + isTerminalImeInputContextRefreshing: mocks.isTerminalImeInputContextRefreshing +})) + vi.mock('@/components/browser-pane/BrowserPane', () => ({ default: function BrowserPane() { return null @@ -744,6 +750,7 @@ describe('FloatingTerminalPanel close behavior', () => { mocks.getFloatingMarkdownDirectory.mockResolvedValue('/tmp/orca/floating-notes') mocks.getFloatingTerminalCwd.mockResolvedValue('/tmp/orca') mocks.getInstallStatus.mockResolvedValue({ state: 'installed', pathConfigured: true }) + mocks.isTerminalImeInputContextRefreshing.mockReturnValue(false) mocks.isWebRuntimeSessionActive.mockReturnValue(false) mocks.pickFloatingMarkdownDocument.mockResolvedValue(null) const localStorage = { @@ -762,7 +769,7 @@ describe('FloatingTerminalPanel close behavior', () => { }, browser: { notifyActiveTabChanged: vi.fn() }, cli: { getInstallStatus: mocks.getInstallStatus }, - ui: { setFloatingTerminalInputFocused: vi.fn() } + ui: { setFloatingTerminalInputFocused: mocks.setFloatingTerminalInputFocused } }, innerHeight: 800, innerWidth: 1200, @@ -799,6 +806,110 @@ describe('FloatingTerminalPanel close behavior', () => { expect(getPanelClassName(element)).toContain('z-30') }) + it('refreshes terminal native input focus when the floating panel opens', async () => { + setFloatingTabs([makeTab({ id: 'tab-1' })]) + + await renderPanel(true) + runEffects() + + expect(mocks.focusTerminalTabSurface).toHaveBeenCalledWith( + 'tab-1', + null, + expect.objectContaining({ + onImeRefocusSkipped: expect.any(Function), + refreshImeContext: true + }) + ) + mocks.setFloatingTerminalInputFocused.mockClear() + mocks.focusTerminalTabSurface.mock.calls[0]?.[2].onImeRefocusSkipped() + expect(mocks.setFloatingTerminalInputFocused).toHaveBeenCalledWith(false) + + const newerFloatingInput = { + classList: { contains: (token: string) => token === 'xterm-helper-textarea' }, + closest: vi.fn().mockReturnValue({}) + } + Object.setPrototypeOf(newerFloatingInput, HTMLElement.prototype) + mocks.focusTerminalTabSurface.mock.calls[0]?.[2].onImeRefocusSkipped(newerFloatingInput) + expect(mocks.setFloatingTerminalInputFocused).toHaveBeenLastCalledWith(true) + }) + + it('preserves and reclaims terminal input ownership across window blur', async () => { + setFloatingTabs([makeTab({ id: 'tab-1' })]) + const element = await renderPanel(true) + const panel = findByProp(element, 'data-floating-terminal-panel') + const panelElement = { contains: vi.fn().mockReturnValue(true), focus: vi.fn() } + const terminalInput = { + blur: vi.fn(), + classList: { contains: vi.fn((token: string) => token === 'xterm-helper-textarea') }, + closest: vi.fn((selector: string) => { + if (selector === '[data-floating-terminal-panel]') { + return panelElement + } + return selector === '[data-leaf-id]' + ? { + getAttribute: (attribute: string) => (attribute === 'data-leaf-id' ? 'leaf-1' : null) + } + : null + }), + isConnected: true + } + Object.setPrototypeOf(panelElement, HTMLElement.prototype) + Object.setPrototypeOf(terminalInput, HTMLElement.prototype) + attachRef(panel.props.ref, panelElement) + mocks.isTerminalImeInputContextRefreshing.mockReturnValueOnce(true) + const onBlurCapture = panel.props.onBlurCapture as (event: unknown) => void + onBlurCapture({ + relatedTarget: null, + target: terminalInput + }) + expect(mocks.setFloatingTerminalInputFocused).not.toHaveBeenCalled() + const documentState = { + activeElement: terminalInput as unknown as HTMLElement | null, + addEventListener: vi.fn(), + body: {} as HTMLElement, + removeEventListener: vi.fn() + } + vi.stubGlobal('document', documentState) + runEffects() + const blurListener = vi + .mocked(window.addEventListener) + .mock.calls.findLast(([type]) => type === 'blur')?.[1] as (() => void) | undefined + const focusListener = vi + .mocked(window.addEventListener) + .mock.calls.find(([type]) => type === 'focus')?.[1] as (() => void) | undefined + if (!blurListener || !focusListener) { + throw new Error('floating terminal window focus listeners not registered') + } + + blurListener() + expect(terminalInput.blur).not.toHaveBeenCalled() + + focusListener() + expect(mocks.setFloatingTerminalInputFocused).toHaveBeenCalledWith(true) + + documentState.activeElement = terminalInput as unknown as HTMLElement + blurListener() + documentState.activeElement = documentState.body + mocks.focusTerminalTabSurface.mockClear() + focusListener() + expect(mocks.focusTerminalTabSurface).not.toHaveBeenCalled() + + documentState.activeElement = terminalInput as unknown as HTMLElement + blurListener() + terminalInput.isConnected = false + documentState.activeElement = documentState.body + focusListener() + expect(mocks.focusTerminalTabSurface).toHaveBeenCalledWith( + 'tab-1', + 'leaf-1', + expect.objectContaining({ + onlyIfFocusUnclaimed: true, + onImeRefocusSkipped: expect.any(Function), + refreshImeContext: true + }) + ) + }) + it('falls back to default bounds when persisted geometry is malformed', async () => { getMockedLocalStorage().getItem.mockImplementation((key: string) => key === FLOATING_TERMINAL_PANEL_BOUNDS_STORAGE_KEY diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx index 4f0b65ea3dc..c5b509264a3 100644 --- a/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx +++ b/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx @@ -12,6 +12,7 @@ import { useContextualTour } from '@/components/contextual-tours/use-contextual- import TabBar from '@/components/tab-bar/TabBar' import { resolveGroupTabFromVisibleId } from '@/components/tab-group/tab-group-visible-id' import TerminalPane from '@/components/terminal-pane/TerminalPane' +import { isTerminalImeInputContextRefreshing } from '@/components/terminal-pane/terminal-ime-input-context-refresh' import { Button } from '@/components/ui/button' import { useMountedRef } from '@/hooks/useMountedRef' import { useShortcutKeyDetails, type ShortcutKeyComboDetails } from '@/hooks/useShortcutLabel' @@ -222,6 +223,10 @@ export function FloatingTerminalPanel({ } const shortcutFocusFrameRef = useRef(null) const shortcutFocusTimeoutRef = useRef(null) + const reclaimTerminalInputOnWindowFocusRef = useRef<{ + helper: HTMLElement + leafId: string | null + } | null>(null) const mountedRef = useMountedRef() const dragRef = useRef<{ pointerId: number @@ -578,7 +583,11 @@ export function FloatingTerminalPanel({ if (!open || !activeTerminalId) { return } - focusTerminalTabSurface(activeTerminalId) + focusTerminalTabSurface(activeTerminalId, null, { + onImeRefocusSkipped: (active) => + setFloatingTerminalInputFocusedInMain(isFloatingWorkspaceTerminalInputTarget(active)), + refreshImeContext: true + }) }, [activeTerminalId, open]) useEffect(() => { @@ -1313,22 +1322,69 @@ export function FloatingTerminalPanel({ const handleWindowBlur = (): void => { const panel = panelRef.current const active = document.activeElement + reclaimTerminalInputOnWindowFocusRef.current = null if (!panel || !(active instanceof HTMLElement) || !panel.contains(active)) { return } // Why: browser webviews focus out-of-process and do not emit renderer // pointerdown events, so release floating ownership on renderer blur too. setFloatingTerminalInputFocusedInMain(false) + if (isFloatingWorkspaceTerminalInputTarget(active)) { + // Why: the terminal focus lifecycle preserves this exact helper across + // app blur so macOS can rebuild its native input context on return. + reclaimTerminalInputOnWindowFocusRef.current = { + helper: active, + leafId: active.closest('[data-leaf-id]')?.getAttribute('data-leaf-id') ?? null + } + return + } active.blur() } + const handleWindowFocus = (): void => { + const reclaim = reclaimTerminalInputOnWindowFocusRef.current + if (!reclaim) { + return + } + reclaimTerminalInputOnWindowFocusRef.current = null + const panel = panelRef.current + const active = document.activeElement + if ( + panel && + active instanceof HTMLElement && + panel.contains(active) && + isFloatingWorkspaceTerminalInputTarget(active) + ) { + setFloatingTerminalInputFocusedInMain(true) + return + } + if ((active === null || active === document.body) && activeTerminalId) { + if (reclaim.helper.isConnected && panel?.contains(reclaim.helper)) { + // Why: TerminalPane owns exact-helper reclaim and IME refresh. Avoid + // racing it with a second floating-layer blur/refocus cycle. + return + } + // Why: only a helper that genuinely remounted while backgrounded needs + // tab/leaf recovery; the shared TerminalPane owner cannot reclaim it. + focusTerminalTabSurface(activeTerminalId, reclaim.leafId, { + onlyIfFocusUnclaimed: true, + onImeRefocusSkipped: (active) => + setFloatingTerminalInputFocusedInMain(isFloatingWorkspaceTerminalInputTarget(active)), + refreshImeContext: true + }) + } + } + document.addEventListener('pointerdown', handleOutsidePointerDown, true) window.addEventListener('blur', handleWindowBlur) + window.addEventListener('focus', handleWindowFocus) return () => { + reclaimTerminalInputOnWindowFocusRef.current = null document.removeEventListener('pointerdown', handleOutsidePointerDown, true) window.removeEventListener('blur', handleWindowBlur) + window.removeEventListener('focus', handleWindowFocus) } - }, [open]) + }, [activeTerminalId, open]) const handleDragStart = (event: React.PointerEvent): void => { if (maximized) { return @@ -1423,7 +1479,13 @@ export function FloatingTerminalPanel({ commitUserBounds({ ...stagedBoundsRef.current, width: rect.width, height: rect.height }) }} onFocusCapture={(event) => setFloatingTerminalInputFocused(event.target)} - onBlurCapture={(event) => setFloatingTerminalInputFocused(event.relatedTarget)} + onBlurCapture={(event) => { + // Why: keep terminal-first shortcut ownership latched during the + // synchronous macOS IME refresh blur; refocus or its skip callback settles it. + if (!isTerminalImeInputContextRefreshing(event.target)) { + setFloatingTerminalInputFocused(event.relatedTarget) + } + }} onKeyDownCapture={handleShortcutSurfaceKeyDown} >
diff --git a/src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.test.ts b/src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.test.ts index 516a19f39c6..c10f4149843 100644 --- a/src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.test.ts +++ b/src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.test.ts @@ -154,12 +154,13 @@ describe('regular terminal focus ownership', () => { }) expect(synced).toBe(true) - expect(syncFocused).toHaveBeenCalledWith(true) + expect(syncFocused).not.toHaveBeenCalled() // Why: reclaim is deferred so a newer focus owner during reactivation wins. expect(focus).not.toHaveBeenCalled() for (const run of scheduled) { run() } + expect(syncFocused).toHaveBeenCalledWith(true) expect(focus).toHaveBeenCalledOnce() expect(document.activeElement).toBe(helper) }) @@ -203,6 +204,39 @@ describe('regular terminal focus ownership', () => { expect(document.activeElement).toBe(secondHelper) }) + it('keeps ownership clear when the released helper cannot accept focus', () => { + const pane = appendPane() + const helper = appendHelper(pane) + const syncFocused = vi.fn() + helper.focus() + + const releasedHelper = releaseTerminalFocusForWindowBlur({ + container: pane, + activeElement: helper, + syncFocused + }) + document.body.focus() + syncFocused.mockClear() + vi.spyOn(helper, 'focus').mockImplementation(() => undefined) + const scheduled: (() => void)[] = [] + + resyncTerminalFocusForWindowFocus({ + container: pane, + activeElement: document.activeElement, + syncFocused, + releasedHelper, + isMac: false, + scheduleRefocus: (callback) => scheduled.push(callback) + }) + for (const run of scheduled) { + run() + } + + expect(document.activeElement).toBe(document.body) + expect(syncFocused).toHaveBeenCalledOnce() + expect(syncFocused).toHaveBeenCalledWith(false) + }) + it('does not yank focus back into the terminal if the user clicked elsewhere during reactivation', () => { // Why: the Linux reclaim path must honor the same "newer focus owner wins" // guard the macOS path uses, so a click into the sidebar/dialog isn't stolen. @@ -220,6 +254,7 @@ describe('regular terminal focus ownership', () => { syncFocused }) document.body.focus() + syncFocused.mockClear() focus.mockClear() const scheduled: (() => void)[] = [] @@ -238,9 +273,44 @@ describe('regular terminal focus ownership', () => { } expect(focus).not.toHaveBeenCalled() + expect(syncFocused).toHaveBeenCalledWith(false) expect(document.activeElement).toBe(outside) }) + it('does not clear ownership published by a newer terminal during deferred reclaim', () => { + const pane = appendPane() + const helper = appendHelper(pane) + const newerPane = appendPane() + const newerHelper = appendHelper(newerPane) + const syncFocused = vi.fn() + helper.focus() + + const releasedHelper = releaseTerminalFocusForWindowBlur({ + container: pane, + activeElement: helper, + syncFocused + }) + document.body.focus() + syncFocused.mockClear() + const scheduled: (() => void)[] = [] + + resyncTerminalFocusForWindowFocus({ + container: pane, + activeElement: document.activeElement, + syncFocused, + releasedHelper, + isMac: false, + scheduleRefocus: (callback) => scheduled.push(callback) + }) + newerHelper.focus() + for (const run of scheduled) { + run() + } + + expect(document.activeElement).toBe(newerHelper) + expect(syncFocused).not.toHaveBeenCalled() + }) + it('does not reclaim a released helper that was detached from the DOM before refocus', () => { const pane = appendPane() const helper = appendHelper(pane) @@ -355,9 +425,35 @@ describe('regular terminal focus ownership', () => { run() } expect(focus).not.toHaveBeenCalled() + expect(syncFocused).toHaveBeenLastCalledWith(false) expect(document.activeElement).toBe(outside) }) + it('does not clear ownership published by a newer terminal during IME refresh', () => { + const pane = appendPane() + const helper = appendHelper(pane) + const newerHelper = appendHelper(appendPane()) + const syncFocused = vi.fn() + helper.focus() + const scheduled: (() => void)[] = [] + + resyncTerminalFocusForWindowFocus({ + container: pane, + activeElement: document.activeElement, + syncFocused, + isMac: true, + scheduleRefocus: (callback) => scheduled.push(callback) + }) + syncFocused.mockClear() + newerHelper.focus() + for (const run of scheduled) { + run() + } + + expect(document.activeElement).toBe(newerHelper) + expect(syncFocused).not.toHaveBeenCalled() + }) + it('skips the blur/refocus cycle on non-macOS platforms', () => { const pane = appendPane() const helper = appendHelper(pane) diff --git a/src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.ts b/src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.ts index 3f50f574f81..768b0dfb3d1 100644 --- a/src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.ts +++ b/src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.ts @@ -102,8 +102,6 @@ export function resyncTerminalFocusForWindowFocus(args: { } } - args.syncFocused(true) - const reclaimedHelper = helper // Why: defer the reclaim refocus to the next frame and only take focus if @@ -115,6 +113,7 @@ export function resyncTerminalFocusForWindowFocus(args: { const schedule = args.scheduleRefocus ?? scheduleNextFrame schedule(() => { if (!reclaimedHelper.isConnected) { + syncFocusAfterFailedReclaim(reclaimedHelper.ownerDocument.activeElement, args.syncFocused) return } const active = reclaimedHelper.ownerDocument.activeElement @@ -123,21 +122,44 @@ export function resyncTerminalFocusForWindowFocus(args: { isDocumentBodyOrNull(active, reclaimedHelper.ownerDocument) ) { reclaimedHelper.focus() + if (reclaimedHelper.ownerDocument.activeElement === reclaimedHelper) { + args.syncFocused(true) + } else { + syncFocusAfterFailedReclaim(reclaimedHelper.ownerDocument.activeElement, args.syncFocused) + } + return } + syncFocusAfterFailedReclaim(active, args.syncFocused) }) return true } + args.syncFocused(true) + // Why: macOS app reactivation leaves a stale NSTextInputContext on the // still-focused helper (electron#32307/#34952); non-mac returns false inside. refreshTerminalImeInputContext(reclaimedHelper, { isMac: args.isMac, + // Why: if another control wins during the refresh frame, the terminal + // mirror must follow that owner instead of remaining latched true. + onRefocusSkipped: (active) => syncFocusAfterFailedReclaim(active, args.syncFocused), scheduleRefocus: args.scheduleRefocus }) return true } +function syncFocusAfterFailedReclaim( + activeElement: Element | null, + syncFocused: TerminalInputFocusSync +): void { + // Why: a later xterm focusin already published terminal ownership; an older + // deferred reclaim must not overwrite that newer process-wide mirror. + if (!isXtermHelperTextarea(activeElement)) { + syncFocused(false) + } +} + function isNode(value: EventTarget | null): value is Node { return typeof Node !== 'undefined' && value instanceof Node } diff --git a/src/renderer/src/components/terminal-pane/terminal-ime-input-context-refresh.test.ts b/src/renderer/src/components/terminal-pane/terminal-ime-input-context-refresh.test.ts index 69b73c586d9..5a31b691cdd 100644 --- a/src/renderer/src/components/terminal-pane/terminal-ime-input-context-refresh.test.ts +++ b/src/renderer/src/components/terminal-pane/terminal-ime-input-context-refresh.test.ts @@ -1,6 +1,9 @@ // @vitest-environment happy-dom import { beforeEach, describe, expect, it, vi } from 'vitest' -import { refreshTerminalImeInputContext } from './terminal-ime-input-context-refresh' +import { + isTerminalImeInputContextRefreshing, + refreshTerminalImeInputContext +} from './terminal-ime-input-context-refresh' describe('refreshTerminalImeInputContext', () => { beforeEach(() => { @@ -38,6 +41,23 @@ describe('refreshTerminalImeInputContext', () => { expect(document.activeElement).toBe(helper) }) + it('marks only the synchronous refresh blur so focus ownership can stay latched', () => { + const helper = appendHelper() + let refreshingDuringBlur = false + helper.addEventListener('blur', (event) => { + refreshingDuringBlur = isTerminalImeInputContextRefreshing(event.target) + }) + helper.focus() + + refreshTerminalImeInputContext(helper, { + isMac: true, + scheduleRefocus: vi.fn() + }) + + expect(refreshingDuringBlur).toBe(true) + expect(isTerminalImeInputContextRefreshing(helper)).toBe(false) + }) + it('does not steal focus if another element grabbed it before the refocus frame', () => { const helper = appendHelper() const outside = document.createElement('input') @@ -60,6 +80,47 @@ describe('refreshTerminalImeInputContext', () => { expect(document.activeElement).toBe(outside) }) + it('reports when a newer focus owner wins the refocus guard', () => { + const helper = appendHelper() + const outside = document.createElement('input') + document.body.appendChild(outside) + const onRefocusSkipped = vi.fn() + const scheduled: (() => void)[] = [] + helper.focus() + + refreshTerminalImeInputContext(helper, { + isMac: true, + onRefocusSkipped, + scheduleRefocus: (callback) => scheduled.push(callback) + }) + + outside.focus() + for (const run of scheduled) { + run() + } + expect(onRefocusSkipped).toHaveBeenCalledWith(outside) + }) + + it('reports when a connected helper cannot accept the scheduled focus', () => { + const helper = appendHelper() + const onRefocusSkipped = vi.fn() + const scheduled: (() => void)[] = [] + helper.focus() + + refreshTerminalImeInputContext(helper, { + isMac: true, + onRefocusSkipped, + scheduleRefocus: (callback) => scheduled.push(callback) + }) + vi.spyOn(helper, 'focus').mockImplementation(() => undefined) + for (const run of scheduled) { + run() + } + + expect(document.activeElement).toBe(document.body) + expect(onRefocusSkipped).toHaveBeenCalledWith(document.body) + }) + it('skips non-macOS platforms', () => { const helper = appendHelper() const blur = vi.spyOn(helper, 'blur') diff --git a/src/renderer/src/components/terminal-pane/terminal-ime-input-context-refresh.ts b/src/renderer/src/components/terminal-pane/terminal-ime-input-context-refresh.ts index e86fc79bde5..debb3f39c2c 100644 --- a/src/renderer/src/components/terminal-pane/terminal-ime-input-context-refresh.ts +++ b/src/renderer/src/components/terminal-pane/terminal-ime-input-context-refresh.ts @@ -3,10 +3,18 @@ export type TerminalImeInputContextRefocusScheduler = (callback: () => void) => export type TerminalImeInputContextRefreshOptions = { /** Override the macOS check (tests). Defaults to the navigator user agent. */ isMac?: boolean + /** Called with the settled owner when the scheduled refocus does not land. */ + onRefocusSkipped?: (activeElement: Element | null) => void /** Override the refocus scheduler (tests). Defaults to requestAnimationFrame. */ scheduleRefocus?: TerminalImeInputContextRefocusScheduler } +const refreshingHelpers = new WeakSet() + +export function isTerminalImeInputContextRefreshing(target: EventTarget | null): boolean { + return target instanceof HTMLElement && refreshingHelpers.has(target) +} + function isMacUserAgent(): boolean { return typeof navigator !== 'undefined' && navigator.userAgent.includes('Mac') } @@ -38,17 +46,28 @@ export function refreshTerminalImeInputContext( const ownerDocument = helper.ownerDocument // Why: Electron/Chromium can keep a stale NSTextInputContext on the xterm // helper after focus handoffs; blur/refocus rebuilds it so CJK IMEs work. - helper.blur() + refreshingHelpers.add(helper) + try { + helper.blur() + } finally { + refreshingHelpers.delete(helper) + } const schedule = options.scheduleRefocus ?? scheduleNextFrame schedule(() => { - const active = ownerDocument.activeElement if (!helper.isConnected) { + options.onRefocusSkipped?.(ownerDocument.activeElement) return } + const active = ownerDocument.activeElement if (active === helper || isDocumentBodyOrNull(active, ownerDocument)) { helper.focus() + if (ownerDocument.activeElement !== helper) { + options.onRefocusSkipped?.(ownerDocument.activeElement) + } + return } + options.onRefocusSkipped?.(active) }) return true diff --git a/src/renderer/src/lib/focus-terminal-tab-surface.test.ts b/src/renderer/src/lib/focus-terminal-tab-surface.test.ts index 9d2313da44a..9df0ef4132d 100644 --- a/src/renderer/src/lib/focus-terminal-tab-surface.test.ts +++ b/src/renderer/src/lib/focus-terminal-tab-surface.test.ts @@ -1,8 +1,17 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import { focusTerminalTabSurface } from './focus-terminal-tab-surface' +const mocks = vi.hoisted(() => ({ + refreshTerminalImeInputContext: vi.fn() +})) + +vi.mock('@/components/terminal-pane/terminal-ime-input-context-refresh', () => ({ + refreshTerminalImeInputContext: mocks.refreshTerminalImeInputContext +})) + describe('focusTerminalTabSurface', () => { afterEach(() => { + mocks.refreshTerminalImeInputContext.mockClear() vi.unstubAllGlobals() }) @@ -28,6 +37,50 @@ describe('focusTerminalTabSurface', () => { expect(textarea.focus).toHaveBeenCalled() }) + it('optionally refreshes the focused helper native input context', () => { + flushAnimationFrames() + const textarea = { focus: vi.fn() } + vi.stubGlobal('document', { + querySelector: vi.fn((selector: string) => + selector === '[data-terminal-tab-id="tab-1"] .xterm-helper-textarea' ? textarea : null + ) + }) + + focusTerminalTabSurface('tab-1', null, { refreshImeContext: true }) + + expect(textarea.focus).toHaveBeenCalledOnce() + expect(mocks.refreshTerminalImeInputContext).toHaveBeenCalledWith(textarea, { + onRefocusSkipped: undefined + }) + }) + + it('does not steal focus from a newer owner during guarded remount recovery', () => { + const frames: FrameRequestCallback[] = [] + vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => { + frames.push(callback) + return frames.length + }) + vi.stubGlobal('cancelAnimationFrame', vi.fn()) + const textarea = { focus: vi.fn() } + const body = {} + const outside = {} + const documentState = { + activeElement: body as unknown, + body, + querySelector: vi.fn((selector: string) => + selector === '[data-terminal-tab-id="tab-1"] .xterm-helper-textarea' ? textarea : null + ) + } + vi.stubGlobal('document', documentState) + + focusTerminalTabSurface('tab-1', null, { onlyIfFocusUnclaimed: true }) + frames.shift()?.(0) + documentState.activeElement = outside + frames.shift()?.(0) + + expect(textarea.focus).not.toHaveBeenCalled() + }) + it('does not steal focus while inline tab rename is open', () => { flushAnimationFrames() const textarea = { focus: vi.fn() } diff --git a/src/renderer/src/lib/focus-terminal-tab-surface.ts b/src/renderer/src/lib/focus-terminal-tab-surface.ts index a41913c2ab8..97ceb97ae61 100644 --- a/src/renderer/src/lib/focus-terminal-tab-surface.ts +++ b/src/renderer/src/lib/focus-terminal-tab-surface.ts @@ -1,3 +1,5 @@ +import { refreshTerminalImeInputContext } from '@/components/terminal-pane/terminal-ime-input-context-refresh' + /** * Move keyboard focus into the xterm instance for a freshly-mounted terminal * tab. Handles the two-step race where React must first mount the new @@ -11,6 +13,29 @@ function cssAttributeString(value: string): string { let pendingFocusFrameIds: number[] = [] +type FocusTerminalTabSurfaceOptions = { + onlyIfFocusUnclaimed?: boolean + onImeRefocusSkipped?: (activeElement: Element | null) => void + refreshImeContext?: boolean +} + +function focusTerminalHelper(helper: HTMLElement, options: FocusTerminalTabSurfaceOptions): void { + if (options.onlyIfFocusUnclaimed) { + const active = document.activeElement + if (active !== helper && active !== null && active !== document.body) { + return + } + } + helper.focus() + if (options.refreshImeContext) { + // Why: a CSS-hidden, long-lived xterm can retain a stale macOS native text + // input context even after DOM focus returns; blur/refocus rebuilds it. + refreshTerminalImeInputContext(helper, { + onRefocusSkipped: options.onImeRefocusSkipped + }) + } +} + function cancelPendingFocusFrames(): void { if (typeof cancelAnimationFrame === 'function') { for (const frameId of pendingFocusFrameIds) { @@ -29,7 +54,11 @@ function canUseSinglePaneStaleLeafFallback(tabId: string, leafId: string): boole return expectedLeafIds?.length === 1 && !expectedLeafIds.includes(leafId) } -export function focusTerminalTabSurface(tabId: string, leafId?: string | null): void { +export function focusTerminalTabSurface( + tabId: string, + leafId?: string | null, + options: FocusTerminalTabSurfaceOptions = {} +): void { cancelPendingFocusFrames() const firstFrameId = requestAnimationFrame(() => { pendingFocusFrameIds = pendingFocusFrameIds.filter((frameId) => frameId !== firstFrameId) @@ -46,7 +75,7 @@ export function focusTerminalTabSurface(tabId: string, leafId?: string | null): : `[data-terminal-tab-id="${escapedTabId}"] .xterm-helper-textarea` const scoped = document.querySelector(scopedSelector) as HTMLElement | null if (scoped) { - scoped.focus() + focusTerminalHelper(scoped, options) return } if (leafId) { @@ -62,13 +91,17 @@ export function focusTerminalTabSurface(tabId: string, leafId?: string | null): ) if (tabScopedHelpers.length === 1) { const fallback = tabScopedHelpers.item(0) as HTMLElement | null - fallback?.focus() + if (fallback) { + focusTerminalHelper(fallback, options) + } return } return } const fallback = document.querySelector('.xterm-helper-textarea') as HTMLElement | null - fallback?.focus() + if (fallback) { + focusTerminalHelper(fallback, options) + } }) pendingFocusFrameIds.push(secondFrameId) }) From ef03a50b1d5a86fdaf1d9e4f0c72d7c89c5e5178 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:16:07 -0700 Subject: [PATCH 007/137] fix(github): attribute GitHub API outages instead of blank/"failed" states (#9106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(github): attribute GitHub API outages instead of blank/"failed" states When GitHub's API is unreachable (5xx outage, network, or rate limit), Orca showed no PR data with no explanation, so it read as an Orca bug rather than a GitHub-side problem. - Add a shared classifier (classifyGitHubUnavailable) reused by the main process and the renderer so every surface attributes an outage identically. A live outage returns HTTP 5xx, which the PR-refresh classifier previously had no branch for (fell through to the un-attributed "refresh failed"). - Right-sidebar Checks panel: show GitHub-attributed copy in the error empty-state, plus an inline banner over stale cached PR data so an outage doesn't look like a normal (silently out-of-date) panel. - Tasks/PR-list page: replace the vague "N of M projects failed to load" with a GitHub-attributed banner when the failure is a reachability problem. Copy names GitHub as the source and reassures it isn't an Orca problem, with no status-page link. Stays GitHub-scoped so GitLab/other providers aren't mislabeled. * fix(github): keep outage attribution accurate * fix(github): preserve outage attribution edge cases * fix(github): preserve Tasks outage attribution * fix(github): avoid false outage attribution * fix(runtime): tolerate absent browser certificate state * fix(ui): preserve exhaustive optional state handling * fix(github): preserve outage attribution for combined queries * fix(github): preserve runtime failure attribution * chore: restore unrelated UI files to main (out of scope) native-chat-session-option-labels.ts and skill-freshness-group.tsx switch tweaks were unrelated to GitHub API outage attribution — they fix pre-existing switch-exhaustiveness lint on main, which this PR's CI (oxlint) doesn't gate on. Restore them to origin/main so this PR's diff stays focused; the exhaustiveness cleanup belongs in its own change. (sync-runtime-graph.ts is already identical to main, so no diff there to revert.) * fix(github): drop Orca self-reference from outage copy * fix(github): drop em-dashes from outage copy --- src/main/github/client-issue-source.test.ts | 31 +++ src/main/github/client.test.ts | 17 ++ src/main/github/client.ts | 53 +++-- src/renderer/src/components/TaskPage.tsx | 37 ++- .../components/right-sidebar/ChecksPanel.tsx | 14 ++ .../checks-panel-empty-state.test.ts | 41 ++++ .../right-sidebar/checks-panel-empty-state.ts | 50 ++-- .../github-refresh-error-copy.ts | 84 +++++++ src/renderer/src/i18n/locales/en.json | 21 +- src/renderer/src/i18n/locales/es.json | 21 +- src/renderer/src/i18n/locales/ja.json | 21 +- src/renderer/src/i18n/locales/ko.json | 21 +- src/renderer/src/i18n/locales/zh.json | 21 +- src/renderer/src/store/slices/github.test.ts | 218 +++++++++++++++++- src/renderer/src/store/slices/github.ts | 53 ++++- src/shared/github-api-availability.test.ts | 67 ++++++ src/shared/github-api-availability.ts | 52 +++++ src/shared/types.ts | 21 +- 18 files changed, 781 insertions(+), 62 deletions(-) create mode 100644 src/renderer/src/components/right-sidebar/github-refresh-error-copy.ts create mode 100644 src/shared/github-api-availability.test.ts create mode 100644 src/shared/github-api-availability.ts diff --git a/src/main/github/client-issue-source.test.ts b/src/main/github/client-issue-source.test.ts index df658007fe5..3cc68232356 100644 --- a/src/main/github/client-issue-source.test.ts +++ b/src/main/github/client-issue-source.test.ts @@ -228,6 +228,37 @@ describe('GitHub issue source split', () => { ) }) + it.each(['is:issue', 'is:pr'])( + 'propagates GitHub outages for scoped %s queries', + async (query) => { + getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' }) + getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' }) + ghExecFileAsyncMock.mockRejectedValueOnce(new Error('HTTP 503: Service Unavailable')) + + await expect(listWorkItems('/repo-root', 10, query)).rejects.toThrow( + 'HTTP 503: Service Unavailable' + ) + + // The outage signal reuses the failed request; it must not add retry subprocesses. + expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(1) + } + ) + + it('propagates an outage when both sides of a combined query are unavailable', async () => { + getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' }) + getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' }) + ghExecFileAsyncMock + .mockRejectedValueOnce(new Error('HTTP 503: Service Unavailable')) + .mockRejectedValueOnce(new Error('HTTP 502: Bad Gateway')) + + await expect(listWorkItems('/repo-root', 10, 'is:open')).rejects.toThrow( + 'HTTP 503: Service Unavailable' + ) + + // Classification must reuse the issue and PR requests, not retry the outage. + expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(2) + }) + it("uses upstream for recent PRs when preference='upstream'", async () => { resolveIssueSourceMock.mockResolvedValueOnce({ source: { owner: 'stablyai', repo: 'orca' }, diff --git a/src/main/github/client.test.ts b/src/main/github/client.test.ts index 9e3de8a15c9..38b38191b44 100644 --- a/src/main/github/client.test.ts +++ b/src/main/github/client.test.ts @@ -1397,6 +1397,23 @@ describe('getPRForBranch', () => { }) }) + it('reports a GitHub server error when fallback branch discovery receives 5xx responses', async () => { + resolvePRRepositoryCandidatesMock.mockResolvedValueOnce({ + candidates: [{ owner: 'stablyai', repo: 'orca' }], + headRepo: null + }) + ghExecFileAsyncMock + .mockRejectedValueOnce(new Error('HTTP 503: Service Unavailable')) + .mockRejectedValueOnce(new Error('HTTP 502: Bad Gateway')) + + const outcome = await getPRForBranchOutcome('/repo-root', 'feature/test') + + expect(outcome).toMatchObject({ + kind: 'upstream-error', + errorType: 'server_error' + }) + }) + it('keeps a pending fallback branch error when a later candidate cleanly misses', async () => { resolvePRRepositoryCandidatesMock.mockResolvedValueOnce({ candidates: [ diff --git a/src/main/github/client.ts b/src/main/github/client.ts index ac85669836a..a2042d4b373 100644 --- a/src/main/github/client.ts +++ b/src/main/github/client.ts @@ -29,6 +29,7 @@ import { } from '../../shared/hosted-review-refs' import { normalizeGitHubPRMergeMethodSettings } from '../../shared/github-pr-merge-methods' import { isGitHubWorkItemsQueryTooLarge } from '../../shared/github-work-items-query-bounds' +import { classifyGitHubUnavailable } from '../../shared/github-api-availability' import { parseTaskQuery, type ParsedTaskQuery } from '../../shared/task-query' import { GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE, @@ -184,18 +185,15 @@ function classifyPRRefreshError( err: unknown ): Extract['errorType'] { const message = err instanceof Error ? err.message : String(err) + // Why: GitHub-unreachable buckets (5xx outage / network / rate limit) share + // one detector with the Tasks work-item path so both surfaces attribute an + // outage to GitHub identically. Rate-limit responses also carry "HTTP 403", + // so this must run before the permission check below. + const unavailable = classifyGitHubUnavailable(message) + if (unavailable) { + return unavailable + } const lower = message.toLowerCase() - if (lower.includes('rate limit')) { - return 'rate_limited' - } - if ( - lower.includes('timeout') || - lower.includes('no such host') || - lower.includes('network') || - lower.includes('could not resolve host') - ) { - return 'network' - } if (lower.includes('http 403') || lower.includes('resource not accessible')) { return 'permission' } @@ -215,6 +213,8 @@ function safePRRefreshErrorMessage( return 'GitHub authentication is unavailable. Check your gh login.' case 'network': return 'GitHub is unreachable right now. Check your network and try again.' + case 'server_error': + return "GitHub's API is temporarily unavailable (server error). This is a GitHub-side issue." case 'permission': return 'GitHub did not allow access to this pull request.' case 'repo_unavailable': @@ -1270,6 +1270,9 @@ async function listQueriedWorkItems( query.reviewedBy !== null const issueScope = query.scope !== 'pr' && !hasPrOnlyFilter const prScope = query.scope !== 'issue' + let successfulRequestCount = 0 + let nonAvailabilityFailureCount = 0 + let availabilityError: unknown // Why: run the issue and PR fetches in parallel but surface the // issue-side error separately so the IPC envelope can carry it up. PR-side @@ -1292,13 +1295,18 @@ async function listQueriedWorkItems( const { stdout } = issueOwnerRepo ? await ghExecFileAsync(request.args, ghOptions) : await ghCwdResolvedExec(repoContext, request.args, ghOptions) - return { - items: (JSON.parse(stdout) as Record[]) - .filter((item) => !('pull_request' in item)) - .map(mapIssueWorkItem) - } + const items = (JSON.parse(stdout) as Record[]) + .filter((item) => !('pull_request' in item)) + .map(mapIssueWorkItem) + successfulRequestCount += 1 + return { items } } catch (err) { const stderr = err instanceof Error ? err.message : String(err) + if (classifyGitHubUnavailable(stderr)) { + availabilityError ??= err + } else { + nonAvailabilityFailureCount += 1 + } return { items: [], issuesError: classifyListIssuesError(stderr) } } })() @@ -1325,17 +1333,30 @@ async function listQueriedWorkItems( .slice(request.offset, request.offset + limit) .map((item) => mapPullRequestWorkItem(item, prOwnerRepo)) const hydrated = await hydrateWorkItemRepositoryMergeMetadata(mapped, prOwnerRepo, ghOptions) + successfulRequestCount += 1 if (query.state === 'closed') { return hydrated.filter((item) => item.state !== 'merged') } return hydrated } catch (err) { console.warn('listQueriedWorkItems PRs partial failure:', err) + const stderr = err instanceof Error ? err.message : String(err) + if (classifyGitHubUnavailable(stderr)) { + availabilityError ??= err + } else { + nonAvailabilityFailureCount += 1 + } return [] } })() const [issueResult, prItems] = await Promise.all([issueFetch, prFetch]) + if (availabilityError && successfulRequestCount === 0 && nonAvailabilityFailureCount === 0) { + // Why: combined queries normally preserve either successful half. When + // every attempted half hit the same availability class, propagate one of + // those existing failures so Tasks can distinguish an outage from no data. + throw availabilityError + } return { items: sortWorkItemsByNumber([...issueResult.items, ...prItems]).slice(0, limit), issuesError: issueResult.issuesError diff --git a/src/renderer/src/components/TaskPage.tsx b/src/renderer/src/components/TaskPage.tsx index e43cc6a8089..51bbc201879 100644 --- a/src/renderer/src/components/TaskPage.tsx +++ b/src/renderer/src/components/TaskPage.tsx @@ -3788,6 +3788,10 @@ export default function TaskPage(): React.JSX.Element { // rejections populate tasksError instead — the two are mutually exclusive so // a successful-with-partial-failure read and a hard-reject don't double-show. const [failedCount, setFailedCount] = useState(0) + // Why: when every selected refresh fails because GitHub is unreachable + // (outage / network / rate limit), attribute it to GitHub instead of making + // an empty or stale cached list look current. + const [githubUnavailable, setGithubUnavailable] = useState(false) const [taskRefreshNonce, setTaskRefreshNonce] = useState(0) // Why: the fetch effect uses this to detect when a nonce bump is from the // user clicking the refresh button (force=true) vs. re-running for any @@ -6414,6 +6418,7 @@ export default function TaskPage(): React.JSX.Element { setCountedTotalPages(null) setTasksError(null) setFailedCount(0) // reset so a prior failure banner doesn't linger + setGithubUnavailable(false) setTasksLoading(anyUncached) // Preserve the existing nonce-gated force behavior. @@ -6456,7 +6461,7 @@ export default function TaskPage(): React.JSX.Element { void fetchWorkItemsAcrossRepos(repoArgs, githubPerRepoPageLimit, githubPageSize, q, { ...deriveTaskPageGitHubWorkItemsFetchOptions(forcedFetch, shouldProbeOnLanding) }) - .then(({ items, failedCount: failed }) => { + .then(({ items, failedCount: failed, githubUnavailable: unavailable }) => { // Why: clear only the sources this effect was responsible for // retrying (the snapshot captured at dispatch time). Overlapping // retries — a second click while a prior fetch is still in flight @@ -6488,6 +6493,7 @@ export default function TaskPage(): React.JSX.Element { setCurrentPage(0) } setFailedCount(failed) + setGithubUnavailable(unavailable) setTasksLoading(false) setTasksRefreshing(false) setTasksFiltering(false) @@ -6516,6 +6522,7 @@ export default function TaskPage(): React.JSX.Element { } setTasksError(err instanceof Error ? err.message : 'Failed to load GitHub work.') setFailedCount(0) // the per-repo banner would be misleading next to tasksError + setGithubUnavailable(false) setTasksLoading(false) setTasksRefreshing(false) setTasksFiltering(false) @@ -9297,7 +9304,22 @@ export default function TaskPage(): React.JSX.Element {
) : null} - {!tasksError && failedCount > 0 ? ( + {!tasksError && githubUnavailable ? ( + // Why: attribute a GitHub availability failure explicitly so + // an empty list doesn't read as an Orca bug. Takes priority + // over the generic count banner below. +
+ {translate( + 'auto.components.TaskPage.75a38d7df8', + 'GitHub data is temporarily unavailable. Its API may be down, rate-limited, or unreachable. Please try again shortly.' + )} +
+ ) : null} + + {!tasksError && !githubUnavailable && failedCount > 0 ? ( // Why: per-repo partial-failure signal — distinct from a hard // IPC reject (tasksError). The two are mutually exclusive.
@@ -9406,14 +9428,15 @@ export default function TaskPage(): React.JSX.Element { ) : null} {/* Why: suppress the generic empty state when any error banner is - visible (IPC reject via tasksError, cross-repo partial failure - via failedCount, or per-repo issue-side error). Showing - "No matching GitHub work" next to "Couldn't load issues from X/Y" - is contradictory and misleads the user into thinking they - typed the wrong query. */} + visible (IPC reject via tasksError, GitHub-unavailable outage, + cross-repo partial failure via failedCount, or per-repo + issue-side error). Showing "No matching GitHub work" next to + "Couldn't load issues from X/Y" is contradictory and misleads + the user into thinking they typed the wrong query. */} {!showGitHubTaskSkeletons && filteredWorkItems.length === 0 && !tasksError && + !githubUnavailable && failedCount === 0 && perRepoSourceState.every((s) => !s.error) ? (
diff --git a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx index 9572ade51d6..ac08b19fdf9 100644 --- a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx +++ b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx @@ -102,6 +102,7 @@ import { getChecksPanelEmptyStateCopy, shouldShowChecksPanelPublishBranchAction } from './checks-panel-empty-state' +import { getChecksPanelRefreshErrorBannerLine } from './github-refresh-error-copy' import { hasAmbiguousGitHubHostedReviewForChecksPanel } from './checks-panel-ambiguous-github-review' import { recordChecksPanelPRRefreshBreadcrumb } from './checks-panel-pr-refresh-breadcrumb' import { @@ -3583,6 +3584,7 @@ export default function ChecksPanel(): React.JSX.Element { const emptyStateCopy = getChecksPanelEmptyStateCopy({ operationLabel, prRefreshStatus: emptyReviewIsGitLab ? undefined : prRefreshState?.status, + prRefreshErrorType: emptyReviewIsGitLab ? undefined : prRefreshState?.errorType, hostedReviewBlockedReason: hostedReviewCreation?.blockedReason, hasUpstream: publishActionRemoteStatus?.hasUpstream, hasCurrentBranch: Boolean(branch), @@ -3699,6 +3701,18 @@ export default function ChecksPanel(): React.JSX.Element { !settings.activeRuntimeEnvironmentId return (
+ {/* Why: a background refresh failed while cached PR data is still shown. + Surface it over the stale data so a GitHub outage doesn't look like a + normal (but silently out-of-date) panel. GitHub-only — GitLab reviews + don't flow through prRefreshState. */} + {activeReview?.provider === 'github' && prRefreshState?.status === 'error' ? ( +
+ {getChecksPanelRefreshErrorBannerLine(prRefreshState.errorType)} +
+ ) : null} {/* Hosted review header */}
{/* Review number + state badge + refresh + open link */} diff --git a/src/renderer/src/components/right-sidebar/checks-panel-empty-state.test.ts b/src/renderer/src/components/right-sidebar/checks-panel-empty-state.test.ts index 7f4242cdb72..7e3f8ca3c41 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel-empty-state.test.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel-empty-state.test.ts @@ -220,6 +220,47 @@ describe('getChecksPanelEmptyStateCopy', () => { ).toBe('Could not refresh pull request') }) + it('attributes a 5xx outage to GitHub instead of the generic refresh error', () => { + expect( + getChecksPanelEmptyStateCopy({ + operationLabel: null, + prRefreshStatus: 'error', + prRefreshErrorType: 'server_error', + hostedReviewBlockedReason: null, + hasUpstream: true + }) + ).toEqual({ + title: 'GitHub is unavailable', + description: + "GitHub's API is temporarily unavailable. This panel reloads automatically once it recovers." + }) + }) + + it('attributes a network failure to GitHub in the ambiguous hosted-review branch', () => { + expect( + getChecksPanelEmptyStateCopy({ + operationLabel: null, + prRefreshStatus: 'error', + prRefreshErrorType: 'network', + hostedReviewBlockedReason: null, + hasUpstream: true, + hasAmbiguousGitHubHostedReview: true + }).title + ).toBe("Can't reach GitHub") + }) + + it('keeps the generic refresh error for non-outage error types', () => { + expect( + getChecksPanelEmptyStateCopy({ + operationLabel: null, + prRefreshStatus: 'error', + prRefreshErrorType: 'auth', + hostedReviewBlockedReason: null, + hasUpstream: true + }).title + ).toBe('Could not refresh pull request') + }) + it('renders a single stable title across a full ambiguous refresh cycle', () => { const cycle = ['queued', 'in-flight', undefined, 'skipped', 'paused'] as const const titles = new Set( diff --git a/src/renderer/src/components/right-sidebar/checks-panel-empty-state.ts b/src/renderer/src/components/right-sidebar/checks-panel-empty-state.ts index 19f033d9ffb..78dd6610ae4 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel-empty-state.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel-empty-state.ts @@ -1,11 +1,17 @@ import type { HostedReviewCreationBlockedReason } from '../../../../shared/hosted-review' +import type { PRRefreshUpstreamErrorType } from '../../../../shared/types' import { translate } from '@/i18n/i18n' +import { getGitHubUnavailableEmptyStateCopy } from './github-refresh-error-copy' type PRRefreshStatus = 'queued' | 'in-flight' | 'paused' | 'error' | 'skipped' | undefined type ChecksPanelEmptyStateInput = { operationLabel: string | null prRefreshStatus: PRRefreshStatus + // Why: lets the error empty-state attribute a GitHub outage (5xx/network/rate + // limit) instead of showing generic "could not refresh" copy that reads like + // an Orca bug. + prRefreshErrorType?: PRRefreshUpstreamErrorType hostedReviewBlockedReason: HostedReviewCreationBlockedReason | undefined hasUpstream: boolean | undefined hasCurrentBranch?: boolean @@ -52,16 +58,18 @@ export function getChecksPanelEmptyStateCopy( // hard refresh error still surfaces a distinct — but equally stable — message. if (input.hasAmbiguousGitHubHostedReview === true) { if (input.prRefreshStatus === 'error') { - return { - title: translate( - 'auto.components.right.sidebar.checks.panel.empty.state.5f478ab3d3', - 'Could not refresh pull request' - ), - description: translate( - 'auto.components.right.sidebar.checks.panel.empty.state.2bdd7aaf2d', - 'GitHub status could not be refreshed. Existing cached data was preserved.' - ) - } + return ( + getGitHubUnavailableEmptyStateCopy(input.prRefreshErrorType) ?? { + title: translate( + 'auto.components.right.sidebar.checks.panel.empty.state.5f478ab3d3', + 'Could not refresh pull request' + ), + description: translate( + 'auto.components.right.sidebar.checks.panel.empty.state.2bdd7aaf2d', + 'GitHub status could not be refreshed. Existing cached data was preserved.' + ) + } + ) } return { title: translate( @@ -113,16 +121,18 @@ export function getChecksPanelEmptyStateCopy( switch (input.prRefreshStatus) { case 'error': - return { - title: translate( - 'auto.components.right.sidebar.checks.panel.empty.state.5f478ab3d3', - 'Could not refresh pull request' - ), - description: translate( - 'auto.components.right.sidebar.checks.panel.empty.state.2bdd7aaf2d', - 'GitHub status could not be refreshed. Existing cached data was preserved.' - ) - } + return ( + getGitHubUnavailableEmptyStateCopy(input.prRefreshErrorType) ?? { + title: translate( + 'auto.components.right.sidebar.checks.panel.empty.state.5f478ab3d3', + 'Could not refresh pull request' + ), + description: translate( + 'auto.components.right.sidebar.checks.panel.empty.state.2bdd7aaf2d', + 'GitHub status could not be refreshed. Existing cached data was preserved.' + ) + } + ) case 'queued': return { title: translate( diff --git a/src/renderer/src/components/right-sidebar/github-refresh-error-copy.ts b/src/renderer/src/components/right-sidebar/github-refresh-error-copy.ts new file mode 100644 index 00000000000..298c892e5b0 --- /dev/null +++ b/src/renderer/src/components/right-sidebar/github-refresh-error-copy.ts @@ -0,0 +1,84 @@ +import type { PRRefreshUpstreamErrorType } from '../../../../shared/types' +import { translate } from '@/i18n/i18n' + +export type ChecksPanelErrorCopy = { title: string; description: string } + +/** + * Attributed empty-state copy for a PR-refresh failure caused by GitHub itself + * being unavailable (5xx outage / network / rate limit). Returns null for + * non-outage errors (auth, permission, unknown) so the caller keeps its generic + * "could not refresh" copy — those are user-actionable, not "GitHub is down". + */ +export function getGitHubUnavailableEmptyStateCopy( + errorType: PRRefreshUpstreamErrorType | undefined +): ChecksPanelErrorCopy | null { + if (errorType === 'server_error') { + return { + title: translate( + 'auto.components.right.sidebar.github.refresh.error.copy.580025e7b7', + 'GitHub is unavailable' + ), + description: translate( + 'auto.components.right.sidebar.github.refresh.error.copy.01c85b5770', + "GitHub's API is temporarily unavailable. This panel reloads automatically once it recovers." + ) + } + } + if (errorType === 'network') { + return { + title: translate( + 'auto.components.right.sidebar.github.refresh.error.copy.d1a9f2b165', + "Can't reach GitHub" + ), + description: translate( + 'auto.components.right.sidebar.github.refresh.error.copy.7d01d42a3a', + 'GitHub is unreachable right now. Check your connection, then try again shortly.' + ) + } + } + if (errorType === 'rate_limited') { + return { + title: translate( + 'auto.components.right.sidebar.github.refresh.error.copy.e9d681894a', + 'GitHub rate limit reached' + ), + description: translate( + 'auto.components.right.sidebar.github.refresh.error.copy.8c77434d6f', + 'GitHub is rate-limiting requests. This panel refreshes once the limit resets.' + ) + } + } + // Non-outage errors (auth, permission, unknown) keep the caller's generic copy. + return null +} + +/** + * One-line banner shown over stale cached PR data when the last refresh failed. + * Always returns a line: GitHub-attributed for outage kinds, generic otherwise. + */ +export function getChecksPanelRefreshErrorBannerLine( + errorType: PRRefreshUpstreamErrorType | undefined +): string { + if (errorType === 'server_error') { + return translate( + 'auto.components.right.sidebar.github.refresh.error.copy.79aa06bb2c', + "Couldn't refresh. GitHub's API is temporarily unavailable. Showing the last known status." + ) + } + if (errorType === 'network') { + return translate( + 'auto.components.right.sidebar.github.refresh.error.copy.6ec12cee0c', + "Couldn't refresh. GitHub is unreachable right now. Showing the last known status." + ) + } + if (errorType === 'rate_limited') { + return translate( + 'auto.components.right.sidebar.github.refresh.error.copy.de088015e8', + "Couldn't refresh. GitHub is rate-limiting requests. Showing the last known status." + ) + } + return translate( + 'auto.components.right.sidebar.github.refresh.error.copy.d9dd7c6687', + "Couldn't refresh from GitHub. Showing the last known status." + ) +} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 0b6f41f8173..ae926cb2ef2 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -1781,7 +1781,8 @@ "jiraSortAscending": "ascending", "jiraSortDescending": "descending", "jiraSortBy": "Sort by", - "jiraToggleSortDirection": "Sort {{value0}}" + "jiraToggleSortDirection": "Sort {{value0}}", + "75a38d7df8": "GitHub data is temporarily unavailable. Its API may be down, rate-limited, or unreachable. Please try again shortly." }, "Terminal": { "73768427cf": "Close", @@ -10298,6 +10299,24 @@ "workspaceGone": "Couldn't open log — workspace is no longer available.", "notAuthorized": "Couldn't open log — path not authorized.", "alreadyEditable": "Log is already open for editing." + }, + "github": { + "refresh": { + "error": { + "copy": { + "580025e7b7": "GitHub is unavailable", + "01c85b5770": "GitHub's API is temporarily unavailable. This panel reloads automatically once it recovers.", + "d1a9f2b165": "Can't reach GitHub", + "7d01d42a3a": "GitHub is unreachable right now. Check your connection, then try again shortly.", + "e9d681894a": "GitHub rate limit reached", + "8c77434d6f": "GitHub is rate-limiting requests. This panel refreshes once the limit resets.", + "79aa06bb2c": "Couldn't refresh. GitHub's API is temporarily unavailable. Showing the last known status.", + "6ec12cee0c": "Couldn't refresh. GitHub is unreachable right now. Showing the last known status.", + "de088015e8": "Couldn't refresh. GitHub is rate-limiting requests. Showing the last known status.", + "d9dd7c6687": "Couldn't refresh from GitHub. Showing the last known status." + } + } + } } } }, diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 2c5a35ddbb4..ca06db5293b 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -1758,7 +1758,8 @@ "jiraSortAscending": "ascendente", "jiraSortDescending": "descendente", "jiraSortBy": "Ordenar por", - "jiraToggleSortDirection": "Ordenar de forma {{value0}}" + "jiraToggleSortDirection": "Ordenar de forma {{value0}}", + "75a38d7df8": "GitHub data is temporarily unavailable. Its API may be down, rate-limited, or unreachable. Please try again shortly." }, "Terminal": { "73768427cf": "Cerrar", @@ -10275,6 +10276,24 @@ "workspaceGone": "Couldn't open log — workspace is no longer available.", "notAuthorized": "Couldn't open log — path not authorized.", "alreadyEditable": "Log is already open for editing." + }, + "github": { + "refresh": { + "error": { + "copy": { + "580025e7b7": "GitHub is unavailable", + "01c85b5770": "GitHub's API is temporarily unavailable. This panel reloads automatically once it recovers.", + "d1a9f2b165": "Can't reach GitHub", + "7d01d42a3a": "GitHub is unreachable right now. Check your connection, then try again shortly.", + "e9d681894a": "GitHub rate limit reached", + "8c77434d6f": "GitHub is rate-limiting requests. This panel refreshes once the limit resets.", + "79aa06bb2c": "Couldn't refresh. GitHub's API is temporarily unavailable. Showing the last known status.", + "6ec12cee0c": "Couldn't refresh. GitHub is unreachable right now. Showing the last known status.", + "de088015e8": "Couldn't refresh. GitHub is rate-limiting requests. Showing the last known status.", + "d9dd7c6687": "Couldn't refresh from GitHub. Showing the last known status." + } + } + } } } }, diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index f622982f00e..107edcfe062 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -1758,7 +1758,8 @@ "jiraSortAscending": "昇順", "jiraSortDescending": "降順", "jiraSortBy": "並べ替え", - "jiraToggleSortDirection": "{{value0}}に並べ替え" + "jiraToggleSortDirection": "{{value0}}に並べ替え", + "75a38d7df8": "GitHub data is temporarily unavailable. Its API may be down, rate-limited, or unreachable. Please try again shortly." }, "Terminal": { "73768427cf": "閉じる", @@ -10275,6 +10276,24 @@ "workspaceGone": "Couldn't open log — workspace is no longer available.", "notAuthorized": "Couldn't open log — path not authorized.", "alreadyEditable": "Log is already open for editing." + }, + "github": { + "refresh": { + "error": { + "copy": { + "580025e7b7": "GitHub is unavailable", + "01c85b5770": "GitHub's API is temporarily unavailable. This panel reloads automatically once it recovers.", + "d1a9f2b165": "Can't reach GitHub", + "7d01d42a3a": "GitHub is unreachable right now. Check your connection, then try again shortly.", + "e9d681894a": "GitHub rate limit reached", + "8c77434d6f": "GitHub is rate-limiting requests. This panel refreshes once the limit resets.", + "79aa06bb2c": "Couldn't refresh. GitHub's API is temporarily unavailable. Showing the last known status.", + "6ec12cee0c": "Couldn't refresh. GitHub is unreachable right now. Showing the last known status.", + "de088015e8": "Couldn't refresh. GitHub is rate-limiting requests. Showing the last known status.", + "d9dd7c6687": "Couldn't refresh from GitHub. Showing the last known status." + } + } + } } } }, diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index ea0603e7c97..c8528226850 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -1758,7 +1758,8 @@ "jiraSortAscending": "오름차순", "jiraSortDescending": "내림차순", "jiraSortBy": "정렬 기준", - "jiraToggleSortDirection": "{{value0}}으로 정렬" + "jiraToggleSortDirection": "{{value0}}으로 정렬", + "75a38d7df8": "GitHub data is temporarily unavailable. Its API may be down, rate-limited, or unreachable. Please try again shortly." }, "Terminal": { "73768427cf": "닫기", @@ -10275,6 +10276,24 @@ "workspaceGone": "Couldn't open log — workspace is no longer available.", "notAuthorized": "Couldn't open log — path not authorized.", "alreadyEditable": "Log is already open for editing." + }, + "github": { + "refresh": { + "error": { + "copy": { + "580025e7b7": "GitHub is unavailable", + "01c85b5770": "GitHub's API is temporarily unavailable. This panel reloads automatically once it recovers.", + "d1a9f2b165": "Can't reach GitHub", + "7d01d42a3a": "GitHub is unreachable right now. Check your connection, then try again shortly.", + "e9d681894a": "GitHub rate limit reached", + "8c77434d6f": "GitHub is rate-limiting requests. This panel refreshes once the limit resets.", + "79aa06bb2c": "Couldn't refresh. GitHub's API is temporarily unavailable. Showing the last known status.", + "6ec12cee0c": "Couldn't refresh. GitHub is unreachable right now. Showing the last known status.", + "de088015e8": "Couldn't refresh. GitHub is rate-limiting requests. Showing the last known status.", + "d9dd7c6687": "Couldn't refresh from GitHub. Showing the last known status." + } + } + } } } }, diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index f14765dabb1..b1293bb9932 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -1758,7 +1758,8 @@ "jiraSortAscending": "升序", "jiraSortDescending": "降序", "jiraSortBy": "排序方式", - "jiraToggleSortDirection": "按{{value0}}排序" + "jiraToggleSortDirection": "按{{value0}}排序", + "75a38d7df8": "GitHub data is temporarily unavailable. Its API may be down, rate-limited, or unreachable. Please try again shortly." }, "Terminal": { "73768427cf": "关闭", @@ -10275,6 +10276,24 @@ "workspaceGone": "无法打开日志 — 工作区已不可用。", "notAuthorized": "无法打开日志 — 路径未授权。", "alreadyEditable": "日志已处于编辑状态。" + }, + "github": { + "refresh": { + "error": { + "copy": { + "580025e7b7": "GitHub is unavailable", + "01c85b5770": "GitHub's API is temporarily unavailable. This panel reloads automatically once it recovers.", + "d1a9f2b165": "Can't reach GitHub", + "7d01d42a3a": "GitHub is unreachable right now. Check your connection, then try again shortly.", + "e9d681894a": "GitHub rate limit reached", + "8c77434d6f": "GitHub is rate-limiting requests. This panel refreshes once the limit resets.", + "79aa06bb2c": "Couldn't refresh. GitHub's API is temporarily unavailable. Showing the last known status.", + "6ec12cee0c": "Couldn't refresh. GitHub is unreachable right now. Showing the last known status.", + "de088015e8": "Couldn't refresh. GitHub is rate-limiting requests. Showing the last known status.", + "d9dd7c6687": "Couldn't refresh from GitHub. Showing the last known status." + } + } + } } } }, diff --git a/src/renderer/src/store/slices/github.test.ts b/src/renderer/src/store/slices/github.test.ts index 6ddba03caec..dd4453c09ec 100644 --- a/src/renderer/src/store/slices/github.test.ts +++ b/src/renderer/src/store/slices/github.test.ts @@ -6299,6 +6299,222 @@ describe('createGitHubSlice.fetchWorkItems source/error envelope', () => { } }) + it('flags githubUnavailable when a GitHub repo fails with a 5xx outage and no cache', async () => { + const store = createTestStore() + const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + mockApi.gh.listWorkItems.mockRejectedValue(new Error('HTTP 503: Service Unavailable')) + + try { + const result = await store + .getState() + .fetchWorkItemsAcrossRepos( + [{ repoId: 'github-repo', path: '/server/github-repo' }], + 24, + 100, + '' + ) + + expect(result.items).toEqual([]) + expect(result.failedCount).toBe(1) + expect(result.githubUnavailable).toBe(true) + } finally { + consoleWarn.mockRestore() + } + }) + + it('flags a GitHub outage returned by a remote runtime method', async () => { + const store = createTestStore() + const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + runtimeEnvironmentCall.mockResolvedValueOnce({ + id: 'rpc-work-items-outage', + ok: false, + error: { code: 'runtime_error', message: 'HTTP 503: Service Unavailable' }, + _meta: { runtimeId: 'remote-runtime' } + }) + store.setState({ + settings: { activeRuntimeEnvironmentId: 'env-1' }, + repos: [{ id: 'runtime-repo-id', path: '/server/repo', name: 'repo', kind: 'git' }] + } as unknown as Partial) + + try { + const result = await store + .getState() + .fetchWorkItemsAcrossRepos( + [{ repoId: 'caller-repo-id', path: '/server/repo' }], + 24, + 100, + '' + ) + + expect(result.githubUnavailable).toBe(true) + expect(result.failedCount).toBe(1) + } finally { + consoleWarn.mockRestore() + } + }) + + it('does not attribute a remote runtime transport timeout to GitHub', async () => { + const store = createTestStore() + const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + runtimeEnvironmentCall.mockResolvedValueOnce({ + id: 'rpc-work-items-runtime-timeout', + ok: false, + error: { + code: 'runtime_unavailable', + message: 'Runtime request timed out before github.listWorkItems completed' + }, + _meta: { runtimeId: null } + }) + store.setState({ + settings: { activeRuntimeEnvironmentId: 'env-1' }, + repos: [{ id: 'runtime-repo-id', path: '/server/repo', name: 'repo', kind: 'git' }] + } as unknown as Partial) + + try { + const result = await store + .getState() + .fetchWorkItemsAcrossRepos( + [{ repoId: 'caller-repo-id', path: '/server/repo' }], + 24, + 100, + '' + ) + + expect(result.githubUnavailable).toBe(false) + expect(result.failedCount).toBe(1) + } finally { + consoleWarn.mockRestore() + } + }) + + it('flags githubUnavailable while serving stale cached rows after a failed refresh', async () => { + const store = createTestStore() + const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const item = { + type: 'pr', + number: 8, + title: 'Cached PR', + url: 'https://example.test/8', + updatedAt: '2026-05-21T00:00:00Z' + } as GitHubWorkItem + mockApi.gh.listWorkItems + .mockResolvedValueOnce({ + items: [item], + sources: { + issues: null, + prs: { owner: 'up', repo: 'r' }, + originCandidate: { owner: 'up', repo: 'r' }, + upstreamCandidate: null + } + }) + .mockRejectedValueOnce(new Error('HTTP 503: Service Unavailable')) + + try { + const repos = [{ repoId: 'github-repo', path: '/server/github-repo' }] + await store.getState().fetchWorkItemsAcrossRepos(repos, 24, 100, '') + + const result = await store + .getState() + .fetchWorkItemsAcrossRepos(repos, 24, 100, '', { force: true }) + + expect(result.items).toEqual([{ ...item, repoId: 'github-repo' }]) + expect(result.failedCount).toBe(0) + expect(result.githubUnavailable).toBe(true) + expect(mockApi.gh.listWorkItems).toHaveBeenCalledTimes(2) + } finally { + consoleWarn.mockRestore() + } + }) + + it('ignores an ineligible SSH repo when every GitHub source is unavailable', async () => { + const store = createTestStore() + const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + mockApi.gh.listWorkItems + .mockRejectedValueOnce(new Error(GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE)) + .mockRejectedValueOnce(new Error('HTTP 503: Service Unavailable')) + + try { + const result = await store.getState().fetchWorkItemsAcrossRepos( + [ + { repoId: 'ssh-repo', path: '/server/ssh-repo' }, + { repoId: 'github-repo', path: '/server/github-repo' } + ], + 24, + 100, + '' + ) + + expect(result.items).toEqual([]) + expect(result.failedCount).toBe(1) + expect(result.githubUnavailable).toBe(true) + } finally { + consoleWarn.mockRestore() + } + }) + + it('keeps the partial-failure count when another GitHub repo still loads', async () => { + const store = createTestStore() + const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const item = { + type: 'pr', + number: 8, + title: 'Loaded PR', + url: 'https://example.test/8', + updatedAt: '2026-05-21T00:00:00Z' + } as GitHubWorkItem + mockApi.gh.listWorkItems + .mockRejectedValueOnce(new Error('HTTP 503: Service Unavailable')) + .mockResolvedValueOnce({ + items: [item], + sources: { + issues: null, + prs: { owner: 'up', repo: 'r' }, + originCandidate: { owner: 'up', repo: 'r' }, + upstreamCandidate: null + } + }) + + try { + const result = await store.getState().fetchWorkItemsAcrossRepos( + [ + { repoId: 'unavailable-repo', path: '/server/unavailable-repo' }, + { repoId: 'loaded-repo', path: '/server/loaded-repo' } + ], + 24, + 100, + '' + ) + + expect(result.items).toEqual([{ ...item, repoId: 'loaded-repo' }]) + expect(result.failedCount).toBe(1) + expect(result.githubUnavailable).toBe(false) + } finally { + consoleWarn.mockRestore() + } + }) + + it('does not flag githubUnavailable for a 404 (not an outage)', async () => { + const store = createTestStore() + const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + mockApi.gh.listWorkItems.mockRejectedValue(new Error('HTTP 404: Not Found')) + + try { + const result = await store + .getState() + .fetchWorkItemsAcrossRepos( + [{ repoId: 'github-repo', path: '/server/github-repo' }], + 24, + 100, + '' + ) + + expect(result.failedCount).toBe(1) + expect(result.githubUnavailable).toBe(false) + } finally { + consoleWarn.mockRestore() + } + }) + it('quietly skips SSH repos without a resolved GitHub remote in next-page fetches', async () => { const store = createTestStore() const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}) @@ -6482,7 +6698,7 @@ describe('createGitHubSlice.fetchWorkItems source/error envelope', () => { 24, oversizedQuery ) - ).resolves.toEqual({ items: [], failedCount: 0 }) + ).resolves.toEqual({ items: [], failedCount: 0, githubUnavailable: false }) await expect( store .getState() diff --git a/src/renderer/src/store/slices/github.ts b/src/renderer/src/store/slices/github.ts index ef5e67111a5..322fc8568c0 100644 --- a/src/renderer/src/store/slices/github.ts +++ b/src/renderer/src/store/slices/github.ts @@ -12,6 +12,7 @@ import type { GitHubPRRefreshCandidate, GitHubPRRefreshEvent, GitHubPRRefreshReason, + PRRefreshUpstreamErrorType, GitHubCommentResult, IssueInfo, PRCheckDetail, @@ -38,7 +39,11 @@ import { PER_REPO_FETCH_LIMIT } from '../../../../shared/work-items' import { deriveCheckStatusFromChecks, syncPRChecksStatus } from './github-checks' -import { callRuntimeRpc, getActiveRuntimeTarget } from '../../runtime/runtime-rpc-client' +import { + callRuntimeRpc, + getActiveRuntimeTarget, + RuntimeRpcCallError +} from '../../runtime/runtime-rpc-client' import { getSettingsForRepoRuntimeOwner } from '@/lib/repo-runtime-owner' import { settingsForProjectRowOwner } from './github-project-row-owner' import { rightSidebarShowsPullRequestData } from '@/lib/right-sidebar-visibility' @@ -46,6 +51,7 @@ import { hostedReviewInfoFromGitHubPRInfo } from '../../../../shared/hosted-revi import { getHostedReviewCacheKey, linkedReviewHintKey } from './hosted-review-cache-identity' import { getGitHubPRCacheKey, getGitHubRepoCacheKey } from './github-cache-key' import { isGitHubWorkItemsQueryTooLarge } from './github-work-items-query-bounds' +import { classifyGitHubUnavailable } from '../../../../shared/github-api-availability' import { isMacAppDataPath } from '@/lib/passive-macos-app-data-access' import { translate } from '@/i18n/i18n' import { @@ -416,6 +422,17 @@ function countGitHubWorkItemsForRepo( }) } +function isGitHubUnavailableWorkItemsError(error: unknown): boolean { + // Why: remote-runtime transport failures can also say "timed out" or + // "unavailable". Only runtime_error came from the GitHub method itself; + // other RPC codes must not be attributed to GitHub. + if (error instanceof RuntimeRpcCallError && error.code !== 'runtime_error') { + return false + } + const message = error instanceof Error ? error.message : String(error) + return classifyGitHubUnavailable(message) !== null +} + export function projectViewCacheKey( ownerType: GetProjectViewTableArgs['ownerType'], owner: string, @@ -659,6 +676,10 @@ export type PRRefreshState = { updatedAt: number pausedUntil?: number message?: string + // Why: lets error surfaces render translated, GitHub-attributed copy keyed on + // the failure kind (e.g. a 5xx outage vs. a rate limit) rather than showing + // the main process's English `message` verbatim. + errorType?: PRRefreshUpstreamErrorType } export type PRRefreshStateClearToken = { @@ -2030,6 +2051,11 @@ export type GitHubSlice = { * the caller surfaces as a "N of M projects failed to load" banner. A repo * served from stale cache on rejection is NOT counted as failed — matching * the single-repo behavior of quietly serving stale data. + * + * `githubUnavailable` is true when every selected GitHub source refresh failed + * because GitHub was unreachable/unavailable (5xx outage, network, or rate + * limit), even when stale cache data remains available. The caller uses it to + * attribute the stale/empty list instead of showing a normal-looking result. */ fetchWorkItemsAcrossRepos: ( repos: { @@ -2042,7 +2068,7 @@ export type GitHubSlice = { displayLimit: number, query: string, options?: FetchOptions - ) => Promise<{ items: GitHubWorkItem[]; failedCount: number }> + ) => Promise<{ items: GitHubWorkItem[]; failedCount: number; githubUnavailable: boolean }> /** Fetch one numbered provider page. Pagination pages remain renderer-local. */ fetchWorkItemsNextPage: ( repos: { @@ -2850,10 +2876,13 @@ export const createGitHubSlice: StateCreator = (s fetchWorkItemsAcrossRepos: async (repos, perRepoLimit, displayLimit, query, options) => { if (isGitHubWorkItemsQueryTooLarge(query)) { - return { items: [], failedCount: 0 } + return { items: [], failedCount: 0, githubUnavailable: false } } const state = get() let failedCount = 0 + let requestFailureCount = 0 + let unavailableFailureCount = 0 + let skippedSourceCount = 0 const perProjectResults = await Promise.all( repos.map(async (r) => { try { @@ -2869,8 +2898,13 @@ export const createGitHubSlice: StateCreator = (s // Why: must use perRepoLimit (not displayLimit) so the cache key // matches what fetchWorkItems wrote. if (isGitHubWorkItemsSshRemoteRequiredError(err)) { + skippedSourceCount += 1 return [] as GitHubWorkItem[] } + requestFailureCount += 1 + if (isGitHubUnavailableWorkItemsError(err)) { + unavailableFailureCount += 1 + } const key = r.sourceContext?.provider === 'github' ? workItemsCacheKey( @@ -2892,7 +2926,15 @@ export const createGitHubSlice: StateCreator = (s }) ) const merged = sortWorkItemsByNumber(perProjectResults.flat()).slice(0, displayLimit) - return { items: merged, failedCount } + // Why: one repo can fail with a 5xx while another succeeds (or fails for a + // permission reason). Only claim a global availability failure when every + // eligible source failed for a GitHub reachability reason; intentionally + // skipped SSH repos are not GitHub sources for this calculation. + const githubUnavailable = + requestFailureCount > 0 && + requestFailureCount === repos.length - skippedSourceCount && + unavailableFailureCount === requestFailureCount + return { items: merged, failedCount, githubUnavailable } }, fetchWorkItemsNextPage: async (repos, perRepoLimit, displayLimit, query, page) => { @@ -4112,7 +4154,8 @@ export const createGitHubSlice: StateCreator = (s status: 'error', reason: event.reason, updatedAt: Date.now(), - message: event.outcome.message + message: event.outcome.message, + errorType: event.outcome.errorType } continue } diff --git a/src/shared/github-api-availability.test.ts b/src/shared/github-api-availability.test.ts new file mode 100644 index 00000000000..6e0ec633ef4 --- /dev/null +++ b/src/shared/github-api-availability.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from 'vitest' +import { classifyGitHubUnavailable, isGitHubUnavailableError } from './github-api-availability' + +describe('classifyGitHubUnavailable', () => { + it('classifies HTTP 5xx outages as server_error', () => { + expect(classifyGitHubUnavailable('HTTP 503: Service Unavailable')).toBe('server_error') + expect(classifyGitHubUnavailable('gh: Command failed: HTTP 502 Bad Gateway')).toBe( + 'server_error' + ) + expect(classifyGitHubUnavailable('GitHub API error: 500 Internal Server Error')).toBe( + 'server_error' + ) + expect(classifyGitHubUnavailable('The service is temporarily unavailable')).toBe('server_error') + }) + + it('classifies transport failures as network', () => { + for (const message of [ + 'request to https://api.github.com failed, reason: getaddrinfo ENOTFOUND api.github.com', + 'error connecting to api.github.com\ncheck your internet connection or GitHub status', + 'dial tcp: lookup api.github.com: no such host', + 'connect ETIMEDOUT 140.82.112.5:443', + 'TimeoutError: request aborted', + 'NetworkError when attempting to fetch resource', + 'network unavailable', + 'fetch failed', + 'socket hang up', + 'could not resolve host: api.github.com', + 'connection refused' + ]) { + expect(classifyGitHubUnavailable(message)).toBe('network') + } + }) + + it('classifies rate limiting as rate_limited (even when it carries HTTP 403)', () => { + expect(classifyGitHubUnavailable('HTTP 403: API rate limit exceeded')).toBe('rate_limited') + expect(classifyGitHubUnavailable('You have exceeded a secondary rate limit')).toBe( + 'rate_limited' + ) + expect(classifyGitHubUnavailable('HTTP 429 Too Many Requests')).toBe('rate_limited') + }) + + it('returns null for non-reachability failures', () => { + expect(classifyGitHubUnavailable('HTTP 403: Resource not accessible by integration')).toBeNull() + expect(classifyGitHubUnavailable('HTTP 404: Not Found')).toBeNull() + expect(classifyGitHubUnavailable('could not resolve to a Repository with the name')).toBeNull() + expect( + classifyGitHubUnavailable( + "GraphQL: Could not resolve to a Repository with the name 'network'." + ) + ).toBeNull() + expect(classifyGitHubUnavailable('gh auth login required')).toBeNull() + expect(classifyGitHubUnavailable('')).toBeNull() + }) + + it('does not misread unrelated 3-digit numbers as a server outage', () => { + expect(classifyGitHubUnavailable('found 512 pull requests')).toBeNull() + }) +}) + +describe('isGitHubUnavailableError', () => { + it('detects reachability failures from Error objects and strings', () => { + expect(isGitHubUnavailableError(new Error('HTTP 503: Service Unavailable'))).toBe(true) + expect(isGitHubUnavailableError('fetch failed')).toBe(true) + expect(isGitHubUnavailableError(new Error('HTTP 404: Not Found'))).toBe(false) + expect(isGitHubUnavailableError(null)).toBe(false) + }) +}) diff --git a/src/shared/github-api-availability.ts b/src/shared/github-api-availability.ts new file mode 100644 index 00000000000..1f9ae6694da --- /dev/null +++ b/src/shared/github-api-availability.ts @@ -0,0 +1,52 @@ +// Why: a GitHub outage, a dropped network, or a rate-limit all surface as +// unstructured gh/Octokit error text. Detecting them from one shared place lets +// both the main process (PR-refresh classification) and the renderer (Tasks +// work-item fan-out) attribute the failure to GitHub — not to Orca — using the +// exact same rules, so the two surfaces never disagree about whether GitHub is +// reachable. Returns null for anything that is NOT a reachability problem +// (auth, permission, 404): those are user-actionable, not "GitHub is down". + +export type GitHubUnavailableKind = 'server_error' | 'network' | 'rate_limited' + +// Rate-limit first: a primary rate-limit response also carries "HTTP 403", so +// it must win over any 4xx/permission interpretation downstream. +const RATE_LIMITED_PATTERN = + /rate limit|secondary rate limit|abuse detection|\bhttp[\s/]*429\b|\b429 too many requests\b/i + +// Server-side outage. Anchor on "HTTP 5xx" or named 5xx statuses rather than a +// bare 3-digit match so unrelated numbers in stderr can't be misread as an +// outage. +const SERVER_ERROR_PATTERN = + /\bhttp[\s/]*5\d\d\b|\b5\d\d\s+(?:internal server error|bad gateway|service unavailable|gateway time-?out)\b|\binternal server error\b|\bbad gateway\b|\bservice unavailable\b|\bgateway time-?out\b|\bserver error\b|\btemporarily unavailable\b/i + +// Transport-level failures — DNS, refused/reset connections, timeouts. Covers +// both Node (ENOTFOUND/ECONNRESET) and the gh Go client ("dial tcp", "i/o +// timeout", "no such host") shapes. +const NETWORK_PATTERN = + /timeout|\btimed out\b|\bno such host\b|could not resolve host|could not resolve to a host|\bnetwork(?:error| (?:error|unavailable|unreachable|request failed))\b|\bconnection (?:refused|reset)\b|\berror connecting to\b|\bfailed to connect to\b|\bdial tcp\b|\bi\/o timeout\b|\bfetch failed\b|\bsocket hang up\b|ENOTFOUND|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|ENETUNREACH/i + +/** + * Classify a gh/Octokit error message as a GitHub-reachability problem, or + * null when it is not one (auth, permission, 404, validation, etc.). + */ +export function classifyGitHubUnavailable(message: string): GitHubUnavailableKind | null { + if (!message) { + return null + } + if (RATE_LIMITED_PATTERN.test(message)) { + return 'rate_limited' + } + if (SERVER_ERROR_PATTERN.test(message)) { + return 'server_error' + } + if (NETWORK_PATTERN.test(message)) { + return 'network' + } + return null +} + +/** True when the message indicates GitHub itself is unreachable/unavailable. */ +export function isGitHubUnavailableError(error: unknown): boolean { + const message = error instanceof Error ? error.message : String(error ?? '') + return classifyGitHubUnavailable(message) !== null +} diff --git a/src/shared/types.ts b/src/shared/types.ts index 154fddc3b01..bbe49dfac81 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1202,19 +1202,24 @@ export type PRInfo = { conflictSummary?: PRConflictSummary } +// server_error covers GitHub-side HTTP 5xx outages (githubstatus.com incidents), +// distinct from a transport-level `network` failure or a `rate_limited` budget. +export type PRRefreshUpstreamErrorType = + | 'rate_limited' + | 'auth' + | 'network' + | 'permission' + | 'repo_unavailable' + | 'gh_unavailable' + | 'server_error' + | 'unknown' + export type PRRefreshOutcome = | { kind: 'found'; pr: PRInfo; fetchedAt: number } | { kind: 'no-pr'; fetchedAt: number } | { kind: 'upstream-error' - errorType: - | 'rate_limited' - | 'auth' - | 'network' - | 'permission' - | 'repo_unavailable' - | 'gh_unavailable' - | 'unknown' + errorType: PRRefreshUpstreamErrorType message: string fetchedAt: number } From 3fd73ee8489a94ae4f44172937cb1cea14fb768f Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:27:39 -0700 Subject: [PATCH 008/137] fix(crash-reporting): disambiguate renderer recovery from app relaunches (#9223) * fix(crash-reporting): disambiguate main process relaunches * fix(crash-reporting): trace all app relaunch causes --- src/main/app-relaunch.test.ts | 35 +++++++++++++++++++ src/main/app-relaunch.ts | 17 +++++++++ .../durable-crash-breadcrumb.test.ts | 27 ++++++++++++-- .../durable-crash-breadcrumb.ts | 13 +++++-- .../main-process-lifecycle-identity.test.ts | 18 ++++++++++ .../main-process-lifecycle-identity.ts | 19 ++++++++++ .../process-gone-recorder.test.ts | 16 ++++++++- .../crash-reporting/process-gone-recorder.ts | 10 +++++- src/main/index.ts | 22 +++++++++--- src/main/ipc/app.test.ts | 10 ++++++ src/main/ipc/app.ts | 5 +-- src/main/ipc/orca-profiles.test.ts | 10 ++++++ src/main/ipc/orca-profiles.ts | 9 ++--- .../updater-lifecycle-diagnostics.test.ts | 30 ++++++++++++++++ src/main/updater-lifecycle-diagnostics.ts | 6 ++-- 15 files changed, 227 insertions(+), 20 deletions(-) create mode 100644 src/main/app-relaunch.test.ts create mode 100644 src/main/app-relaunch.ts create mode 100644 src/main/crash-reporting/main-process-lifecycle-identity.test.ts create mode 100644 src/main/crash-reporting/main-process-lifecycle-identity.ts create mode 100644 src/main/updater-lifecycle-diagnostics.test.ts diff --git a/src/main/app-relaunch.test.ts b/src/main/app-relaunch.test.ts new file mode 100644 index 00000000000..26c98c98bb3 --- /dev/null +++ b/src/main/app-relaunch.test.ts @@ -0,0 +1,35 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { appRelaunchMock, recordDurableCrashBreadcrumbMock } = vi.hoisted(() => ({ + appRelaunchMock: vi.fn(), + recordDurableCrashBreadcrumbMock: vi.fn() +})) + +vi.mock('electron', () => ({ app: { relaunch: appRelaunchMock } })) +vi.mock('./crash-reporting/durable-crash-breadcrumb', () => ({ + recordDurableCrashBreadcrumb: recordDurableCrashBreadcrumbMock +})) + +import { relaunchApp } from './app-relaunch' + +beforeEach(() => { + appRelaunchMock.mockReset() + recordDurableCrashBreadcrumbMock.mockReset() +}) + +describe('relaunchApp', () => { + it('durably records the reason before scheduling the replacement process', () => { + relaunchApp('gpu-fallback', { processReason: 'crashed', exitCode: 5 }) + + expect(recordDurableCrashBreadcrumbMock).toHaveBeenCalledOnce() + expect(recordDurableCrashBreadcrumbMock).toHaveBeenCalledWith('app_relaunch_requested', { + processReason: 'crashed', + exitCode: 5, + reason: 'gpu-fallback' + }) + expect(appRelaunchMock).toHaveBeenCalledOnce() + expect(recordDurableCrashBreadcrumbMock.mock.invocationCallOrder[0]).toBeLessThan( + appRelaunchMock.mock.invocationCallOrder[0] + ) + }) +}) diff --git a/src/main/app-relaunch.ts b/src/main/app-relaunch.ts new file mode 100644 index 00000000000..ab7a725c847 --- /dev/null +++ b/src/main/app-relaunch.ts @@ -0,0 +1,17 @@ +import { app } from 'electron' +import type { CrashReportBreadcrumbData } from '../shared/crash-reporting' +import { recordDurableCrashBreadcrumb } from './crash-reporting/durable-crash-breadcrumb' + +export type AppRelaunchReason = + | 'admin-restart' + | 'gpu-fallback' + | 'profile-switch' + | 'profile-transfer' + | 'renderer-request' + +export function relaunchApp(reason: AppRelaunchReason, data?: CrashReportBreadcrumbData): void { + // Why: the current process can exit immediately after app.relaunch(), so + // persist the cause before Electron schedules the replacement process. + recordDurableCrashBreadcrumb('app_relaunch_requested', { ...data, reason }) + app.relaunch() +} diff --git a/src/main/crash-reporting/durable-crash-breadcrumb.test.ts b/src/main/crash-reporting/durable-crash-breadcrumb.test.ts index 78c04f44d6b..6c7038f4738 100644 --- a/src/main/crash-reporting/durable-crash-breadcrumb.test.ts +++ b/src/main/crash-reporting/durable-crash-breadcrumb.test.ts @@ -31,6 +31,22 @@ afterEach(() => { }) describe('recordDurableCrashBreadcrumb', () => { + it('attaches main-process identity when the event has no other data', () => { + recordDurableCrashBreadcrumb('renderer_recovery_reload') + + expect(getCrashBreadcrumbSnapshot()).toEqual([ + expect.objectContaining({ + name: 'renderer_recovery_reload', + data: { + mainProcessPid: process.pid, + mainProcessLaunchId: expect.any(String), + mainProcessStartedAt: expect.any(String) + } + }) + ]) + expect(sink.flushMock).toHaveBeenCalledOnce() + }) + it('records, traces, and flushes a sanitized crash breadcrumb', () => { recordDurableCrashBreadcrumb('process_gone_suppressed', { source: 'renderer', @@ -40,7 +56,13 @@ describe('recordDurableCrashBreadcrumb', () => { expect(getCrashBreadcrumbSnapshot()).toEqual([ expect.objectContaining({ name: 'process_gone_suppressed', - data: { source: 'renderer', path: '[redacted-path]' } + data: expect.objectContaining({ + source: 'renderer', + path: '[redacted-path]', + mainProcessPid: process.pid, + mainProcessLaunchId: expect.any(String), + mainProcessStartedAt: expect.any(String) + }) }) ]) expect(sink.records).toEqual([ @@ -48,7 +70,8 @@ describe('recordDurableCrashBreadcrumb', () => { name: 'crash.breadcrumb', attributes: expect.objectContaining({ kind: 'crash-breadcrumb', - 'breadcrumb.name': 'process_gone_suppressed' + 'breadcrumb.name': 'process_gone_suppressed', + 'breadcrumb.data': expect.objectContaining({ mainProcessPid: process.pid }) }), exit: { _tag: 'Success' } }) diff --git a/src/main/crash-reporting/durable-crash-breadcrumb.ts b/src/main/crash-reporting/durable-crash-breadcrumb.ts index 14cca5e1bc6..c7fc41bf645 100644 --- a/src/main/crash-reporting/durable-crash-breadcrumb.ts +++ b/src/main/crash-reporting/durable-crash-breadcrumb.ts @@ -5,6 +5,7 @@ import { } from '../../shared/crash-reporting' import { flushActiveSink, startSpan } from '../observability/tracer' import { recordCrashBreadcrumb } from './crash-breadcrumb-store' +import { getMainProcessLifecycleIdentity } from './main-process-lifecycle-identity' export function recordDurableCrashBreadcrumb( name: string, @@ -12,14 +13,20 @@ export function recordDurableCrashBreadcrumb( failureCause?: string ): void { const sanitizedName = sanitizeCrashReportString(name) - const sanitizedData = data ? sanitizeCrashReportDetails(data) : undefined - recordCrashBreadcrumb(sanitizedName, sanitizedData) + const sanitizedData = data ? sanitizeCrashReportDetails(data) : {} + // Why: durable events survive renderer replacement, so carrying the main + // identity here distinguishes renderer recovery from a true app relaunch. + const lifecycleData = { + ...sanitizedData, + ...getMainProcessLifecycleIdentity() + } + recordCrashBreadcrumb(sanitizedName, lifecycleData) const span = startSpan('crash.breadcrumb', { attributes: { kind: 'crash-breadcrumb', 'breadcrumb.name': sanitizedName, - ...(sanitizedData ? { 'breadcrumb.data': sanitizedData } : {}) + 'breadcrumb.data': lifecycleData } }) if (failureCause) { diff --git a/src/main/crash-reporting/main-process-lifecycle-identity.test.ts b/src/main/crash-reporting/main-process-lifecycle-identity.test.ts new file mode 100644 index 00000000000..61b7a6bc436 --- /dev/null +++ b/src/main/crash-reporting/main-process-lifecycle-identity.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest' +import { getMainProcessLifecycleIdentity } from './main-process-lifecycle-identity' + +describe('main process lifecycle identity', () => { + it('stays stable for the lifetime of the main process', () => { + const first = getMainProcessLifecycleIdentity() + const second = getMainProcessLifecycleIdentity() + + expect(second).toBe(first) + expect(first).toEqual({ + mainProcessPid: process.pid, + mainProcessLaunchId: expect.stringMatching( + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i + ), + mainProcessStartedAt: expect.stringMatching(/^\d{4}-\d{2}-\d{2}T/) + }) + }) +}) diff --git a/src/main/crash-reporting/main-process-lifecycle-identity.ts b/src/main/crash-reporting/main-process-lifecycle-identity.ts new file mode 100644 index 00000000000..437bf221545 --- /dev/null +++ b/src/main/crash-reporting/main-process-lifecycle-identity.ts @@ -0,0 +1,19 @@ +import { randomUUID } from 'node:crypto' + +export type MainProcessLifecycleIdentity = Readonly<{ + mainProcessPid: number + mainProcessLaunchId: string + mainProcessStartedAt: string +}> + +// Why: renderer reloads reuse the Electron main process, while true app +// relaunches must receive a new ID even when the OS later recycles its PID. +const mainProcessLifecycleIdentity: MainProcessLifecycleIdentity = Object.freeze({ + mainProcessPid: process.pid, + mainProcessLaunchId: randomUUID(), + mainProcessStartedAt: new Date(Date.now() - process.uptime() * 1_000).toISOString() +}) + +export function getMainProcessLifecycleIdentity(): MainProcessLifecycleIdentity { + return mainProcessLifecycleIdentity +} diff --git a/src/main/crash-reporting/process-gone-recorder.test.ts b/src/main/crash-reporting/process-gone-recorder.test.ts index 94a67982fa8..0226186fc90 100644 --- a/src/main/crash-reporting/process-gone-recorder.test.ts +++ b/src/main/crash-reporting/process-gone-recorder.test.ts @@ -108,11 +108,25 @@ describe('recordProcessGoneCrash', () => { await vi.waitFor(() => expect(record).toHaveBeenCalledOnce()) expect(record).toHaveBeenCalledWith( - expect.objectContaining({ source: 'renderer', reason: 'crashed', exitCode: 5 }) + expect.objectContaining({ + source: 'renderer', + reason: 'crashed', + exitCode: 5, + details: expect.objectContaining({ + mainProcessPid: process.pid, + mainProcessLaunchId: expect.any(String), + mainProcessStartedAt: expect.any(String) + }) + }) ) expect(sink.records).toEqual([ expect.objectContaining({ name: 'electron.process_gone', + attributes: expect.objectContaining({ + 'app.main_process.pid': process.pid, + 'app.main_process.launch_id': expect.any(String), + 'app.main_process.started_at': expect.any(String) + }), exit: expect.objectContaining({ _tag: 'Failure' }) }) ]) diff --git a/src/main/crash-reporting/process-gone-recorder.ts b/src/main/crash-reporting/process-gone-recorder.ts index daec429ae3e..9d6c0edb1a8 100644 --- a/src/main/crash-reporting/process-gone-recorder.ts +++ b/src/main/crash-reporting/process-gone-recorder.ts @@ -18,6 +18,7 @@ import { processGoneDedupe, type ProcessGoneDedupe } from './process-gone-dedupe' +import { getMainProcessLifecycleIdentity } from './main-process-lifecycle-identity' import { flushActiveSink, startSpan } from '../observability/tracer' export type ProcessGoneCrashEvent = { @@ -84,7 +85,11 @@ export function recordProcessGoneCrash( if (!claim) { return } - const crashDetails = buildProcessGoneCrashDetails(event.details) + const mainProcessLifecycle = getMainProcessLifecycleIdentity() + const crashDetails = buildProcessGoneCrashDetails({ + ...event.details, + ...mainProcessLifecycle + }) const breadcrumbs = getCrashBreadcrumbSnapshot() const span = startSpan('electron.process_gone', { attributes: { @@ -98,6 +103,9 @@ export function recordProcessGoneCrash( arch: process.arch, electronVersion: process.versions.electron, chromeVersion: process.versions.chrome, + 'app.main_process.pid': mainProcessLifecycle.mainProcessPid, + 'app.main_process.launch_id': mainProcessLifecycle.mainProcessLaunchId, + 'app.main_process.started_at': mainProcessLifecycle.mainProcessStartedAt, details: crashDetails, breadcrumbs } diff --git a/src/main/index.ts b/src/main/index.ts index cebff02a96f..d69013e5689 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -18,6 +18,7 @@ import { ensureActiveOrcaProfile, initOrcaProfilePaths } from './orca-profiles/p import { getOrcaCloudAuthConfig } from './orca-profiles/profile-cloud-auth-config' import { getProfileUserDataPath } from './orca-profiles/profile-storage-paths' import { applyAppIcon } from './app-icon' +import { relaunchApp } from './app-relaunch' import { StatsCollector, initStatsPath } from './stats/collector' import { ClaudeUsageStore, initClaudeUsagePath } from './claude-usage/store' import { CodexUsageStore, initCodexUsagePath } from './codex-usage/store' @@ -176,6 +177,8 @@ import { recordCoalescedCrashBreadcrumb, recordCrashBreadcrumb } from './crash-reporting/crash-breadcrumb-store' +import { recordDurableCrashBreadcrumb } from './crash-reporting/durable-crash-breadcrumb' +import { getMainProcessLifecycleIdentity } from './crash-reporting/main-process-lifecycle-identity' import { CrashReportStore } from './crash-reporting/crash-report-store' import { shouldRecoverRendererAfterProcessGone, @@ -656,7 +659,8 @@ if (hasSingleInstanceLock) { crashReports = CrashReportStore.fromUserData() recordCrashBreadcrumb('app_started', { packaged: app.isPackaged, - platform: process.platform + platform: process.platform, + ...getMainProcessLifecycleIdentity() }) configureElectronNetworkCompatibility() enableRendererHeapHeadroom() @@ -953,7 +957,7 @@ function openMainWindow(): BrowserWindow { expectedTeardown: getExpectedTeardownScope(webContentsId) }), onRendererRecoveryExhausted: ({ details, recentRecoveryCount }) => { - recordCrashBreadcrumb('renderer_recovery_circuit_breaker_open', { + recordDurableCrashBreadcrumb('renderer_recovery_circuit_breaker_open', { reason: details.reason, exitCode: details.exitCode ?? null, recentRecoveryCount @@ -973,7 +977,7 @@ function openMainWindow(): BrowserWindow { // local-PTY orphan sweep is skipped for that one reload (#5787). onBeforeRecoveryReload: (webContentsId) => { markRecoveryReloadInFlight(webContentsId) - recordCrashBreadcrumb('renderer_recovery_reload') + recordDurableCrashBreadcrumb('renderer_recovery_reload') } }) recordCrashBreadcrumb('main_window_created') @@ -1237,7 +1241,7 @@ async function presentRendererRecoveryPrompt(recentRecoveryCount: number): Promi ? await dialog.showMessageBox(window, options) : await dialog.showMessageBox(options) if (response === 0 && mainWindow && !mainWindow.isDestroyed()) { - recordCrashBreadcrumb('renderer_recovery_manual_retry') + recordDurableCrashBreadcrumb('renderer_recovery_manual_retry') loadMainWindow(mainWindow) } else if (response === 1) { isQuitting = true @@ -1316,7 +1320,11 @@ function handleGpuChildCrash(reason: string, exitCode: number | null): void { return } isQuitting = true - app.relaunch() + relaunchApp('gpu-fallback', { + processReason: reason, + exitCode, + crashesInWindow: result.crashesInWindow + }) app.exit(0) } @@ -1824,6 +1832,10 @@ app.whenReady().then(async () => { // Honors DO_NOT_TRACK / ORCA_TELEMETRY_DISABLED / ORCA_DIAGNOSTICS_DISABLED // / CI internally; those gates do not need to be re-checked here. initObservability() + recordDurableCrashBreadcrumb('main_process_lifecycle_started', { + packaged: app.isPackaged, + platform: process.platform + }) // Why: cohort-classifier reads the repo count synchronously at every emit // for cohort-extended events. The Store has been sync-loaded above, and // this init runs before any IPC handler is registered and before any diff --git a/src/main/ipc/app.test.ts b/src/main/ipc/app.test.ts index 1eb9bbb17a2..de9a13753db 100644 --- a/src/main/ipc/app.test.ts +++ b/src/main/ipc/app.test.ts @@ -8,6 +8,7 @@ const { appRelaunchMock, spawnMock, destroySystemTrayMock, + relaunchAppMock, showOpenDialogMock, grantFloatingWorkspaceDirectoryMock } = vi.hoisted(() => ({ @@ -17,6 +18,7 @@ const { appRelaunchMock: vi.fn(), spawnMock: vi.fn(), destroySystemTrayMock: vi.fn(), + relaunchAppMock: vi.fn(), showOpenDialogMock: vi.fn(), grantFloatingWorkspaceDirectoryMock: vi.fn() })) @@ -91,6 +93,10 @@ vi.mock('../tray/system-tray', () => ({ destroySystemTray: destroySystemTrayMock })) +vi.mock('../app-relaunch', () => ({ + relaunchApp: relaunchAppMock +})) + vi.mock('./floating-workspace-directory', () => ({ ensureDefaultFloatingWorkspacePath: vi.fn(), grantFloatingWorkspaceDirectory: grantFloatingWorkspaceDirectoryMock, @@ -113,6 +119,8 @@ describe('registerAppHandlers', () => { appRelaunchMock.mockReset() spawnMock.mockReset() destroySystemTrayMock.mockReset() + relaunchAppMock.mockReset() + relaunchAppMock.mockImplementation(() => appRelaunchMock()) showOpenDialogMock.mockReset() grantFloatingWorkspaceDirectoryMock.mockReset() processKillSpy = vi.spyOn(process, 'kill').mockReturnValue(true) @@ -139,6 +147,7 @@ describe('registerAppHandlers', () => { await vi.advanceTimersByTimeAsync(150) expect(destroySystemTrayMock).toHaveBeenCalledTimes(1) + expect(relaunchAppMock).toHaveBeenCalledWith('renderer-request') expect(appRelaunchMock).toHaveBeenCalledTimes(1) expect(appExitMock).toHaveBeenCalledWith(0) expect(destroySystemTrayMock.mock.invocationCallOrder[0]).toBeLessThan( @@ -187,6 +196,7 @@ describe('registerAppHandlers', () => { await vi.advanceTimersByTimeAsync(150) expect(appRelaunchMock).toHaveBeenCalledTimes(1) + expect(relaunchAppMock).toHaveBeenCalledWith('admin-restart') expect(appQuitMock).toHaveBeenCalledTimes(1) expect(appExitMock).not.toHaveBeenCalled() }) diff --git a/src/main/ipc/app.ts b/src/main/ipc/app.ts index 2dc3c81f91b..f97ae1e2c24 100644 --- a/src/main/ipc/app.ts +++ b/src/main/ipc/app.ts @@ -6,6 +6,7 @@ import { app, BrowserWindow, dialog, ipcMain, type IpcMainInvokeEvent } from 'el import { is } from '@electron-toolkit/utils' import type { AppIdentity } from '../../shared/app-identity' import type { FloatingTerminalCwdRequest, MarkdownDocument } from '../../shared/types' +import { relaunchApp } from '../app-relaunch' import type { Store } from '../persistence' import { getDevInstanceIdentity } from '../startup/dev-instance-identity' import { isPwshAvailable } from '../pwsh' @@ -314,7 +315,7 @@ export function registerAppHandlers(store: Store, options: RegisterAppHandlersOp // Why: app.exit(0) skips before-quit/will-quit, so clean the Windows tray // explicitly before relaunching to avoid a stale notification-area icon. destroySystemTray() - app.relaunch() + relaunchApp('renderer-request') app.exit(0) }, 150) }) @@ -325,7 +326,7 @@ export function registerAppHandlers(store: Store, options: RegisterAppHandlersOp // checkpoints, runtime metadata, and telemetry flush before exit. await runBeforeRelaunchCleanup(options.onBeforeRelaunch) setTimeout(() => { - app.relaunch() + relaunchApp('admin-restart') app.quit() }, 150) }) diff --git a/src/main/ipc/orca-profiles.test.ts b/src/main/ipc/orca-profiles.test.ts index 291a3dfe2f2..3cad49f00ff 100644 --- a/src/main/ipc/orca-profiles.test.ts +++ b/src/main/ipc/orca-profiles.test.ts @@ -5,6 +5,7 @@ const { appExitMock, appQuitMock, appRelaunchMock, + relaunchAppMock, destroySystemTrayMock, createLocalOrcaProfileMock, getOrcaProfileListStateMock, @@ -16,6 +17,7 @@ const { appExitMock: vi.fn(), appQuitMock: vi.fn(), appRelaunchMock: vi.fn(), + relaunchAppMock: vi.fn(), destroySystemTrayMock: vi.fn(), createLocalOrcaProfileMock: vi.fn(), getOrcaProfileListStateMock: vi.fn(), @@ -42,6 +44,10 @@ vi.mock('../tray/system-tray', () => ({ destroySystemTray: destroySystemTrayMock })) +vi.mock('../app-relaunch', () => ({ + relaunchApp: relaunchAppMock +})) + vi.mock('../orca-profiles/profile-index-store', () => ({ createLocalOrcaProfile: createLocalOrcaProfileMock, getOrcaProfileListState: getOrcaProfileListStateMock, @@ -70,6 +76,8 @@ describe('registerOrcaProfileHandlers', () => { appExitMock.mockReset() appQuitMock.mockReset() appRelaunchMock.mockReset() + relaunchAppMock.mockReset() + relaunchAppMock.mockImplementation(() => appRelaunchMock()) destroySystemTrayMock.mockReset() createLocalOrcaProfileMock.mockReset() getOrcaProfileListStateMock.mockReset() @@ -159,6 +167,7 @@ describe('registerOrcaProfileHandlers', () => { await vi.advanceTimersByTimeAsync(150) expect(appRelaunchMock).toHaveBeenCalledOnce() + expect(relaunchAppMock).toHaveBeenCalledWith('profile-switch') // Why quit, not exit: before-quit/will-quit teardown (scrollback capture, // PTY kill, daemon checkpoints) must run on a profile switch. expect(appQuitMock).toHaveBeenCalledOnce() @@ -294,6 +303,7 @@ describe('registerOrcaProfileHandlers', () => { await vi.advanceTimersByTimeAsync(150) expect(appRelaunchMock).toHaveBeenCalledOnce() + expect(relaunchAppMock).toHaveBeenCalledWith('profile-transfer') expect(appQuitMock).toHaveBeenCalledOnce() expect(appExitMock).not.toHaveBeenCalled() }) diff --git a/src/main/ipc/orca-profiles.ts b/src/main/ipc/orca-profiles.ts index 6fbd877552f..b11478bdf89 100644 --- a/src/main/ipc/orca-profiles.ts +++ b/src/main/ipc/orca-profiles.ts @@ -1,5 +1,6 @@ import { app, ipcMain } from 'electron' import type { Store } from '../persistence' +import { relaunchApp, type AppRelaunchReason } from '../app-relaunch' import type { CreateLocalOrcaProfileArgs, CreateLocalOrcaProfileResult, @@ -153,9 +154,9 @@ async function runBeforeProfileRelaunch( } } -function scheduleProfileRelaunch(): void { +function scheduleProfileRelaunch(reason: Extract): void { setTimeout(() => { - app.relaunch() + relaunchApp(reason) // Why: app.quit() (not app.exit) so before-quit/will-quit still run — // renderer scrollback capture, PTY kill, stats flush, and daemon final // checkpoints must not be skipped on a profile switch. @@ -215,7 +216,7 @@ export function registerOrcaProfileHandlers( store.flush() setActiveOrcaProfile(profileId) - scheduleProfileRelaunch() + scheduleProfileRelaunch('profile-switch') return { status: 'relaunching' } } @@ -244,7 +245,7 @@ export function registerOrcaProfileHandlers( store.freezeWrites() await runBeforeProfileRelaunch(options.onBeforeRelaunch) setActiveOrcaProfile(args.targetProfileId) - scheduleProfileRelaunch() + scheduleProfileRelaunch('profile-transfer') return { ...result, willRelaunch: true } } return result diff --git a/src/main/updater-lifecycle-diagnostics.test.ts b/src/main/updater-lifecycle-diagnostics.test.ts new file mode 100644 index 00000000000..1ad3c26768b --- /dev/null +++ b/src/main/updater-lifecycle-diagnostics.test.ts @@ -0,0 +1,30 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const { recordDurableCrashBreadcrumbMock } = vi.hoisted(() => ({ + recordDurableCrashBreadcrumbMock: vi.fn() +})) + +vi.mock('./crash-reporting/durable-crash-breadcrumb', () => ({ + recordDurableCrashBreadcrumb: recordDurableCrashBreadcrumbMock +})) + +import { recordUpdaterLifecycle } from './updater-lifecycle-diagnostics' + +beforeEach(() => { + recordDurableCrashBreadcrumbMock.mockClear() + vi.spyOn(console, 'info').mockImplementation(() => {}) +}) + +afterEach(() => { + vi.restoreAllMocks() +}) + +describe('recordUpdaterLifecycle', () => { + it('records updater events in the durable lifecycle trace', () => { + recordUpdaterLifecycle('quit_and_install', { version: '1.2.3' }) + + expect(recordDurableCrashBreadcrumbMock).toHaveBeenCalledWith('updater_quit_and_install', { + version: '1.2.3' + }) + }) +}) diff --git a/src/main/updater-lifecycle-diagnostics.ts b/src/main/updater-lifecycle-diagnostics.ts index d5f0a10bfa8..c09f787d94b 100644 --- a/src/main/updater-lifecycle-diagnostics.ts +++ b/src/main/updater-lifecycle-diagnostics.ts @@ -1,12 +1,14 @@ import type { CrashReportBreadcrumbData } from '../shared/crash-reporting' -import { recordCrashBreadcrumb } from './crash-reporting/crash-breadcrumb-store' +import { recordDurableCrashBreadcrumb } from './crash-reporting/durable-crash-breadcrumb' export function recordUpdaterLifecycle( event: string, data?: CrashReportBreadcrumbData, options?: { level?: 'info' | 'warn' | 'error'; message?: string } ): void { - recordCrashBreadcrumb(`updater_${event}`, data) + // Why: update-driven relaunches must remain visible in the same durable lane + // as renderer recovery so support traces can distinguish their lifecycles. + recordDurableCrashBreadcrumb(`updater_${event}`, data) const suffix = data && Object.keys(data).length > 0 ? ` ${JSON.stringify(data)}` : '' console[options?.level ?? 'info'](`[updater] ${options?.message ?? event}${suffix}`) } From b25930003285b4b482e55a0b1368af3d5dafe106 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Fri, 17 Jul 2026 18:34:30 -0400 Subject: [PATCH 009/137] perf(worktrees): bound runtime discovery scans (#8915) * perf(worktrees): bound repeated runtime discovery scans (#8882) * fix(worktrees): invalidate scans on SSH state changes (#8882) * fix(worktrees): key scan reuse by execution runtime (#8882) * test(worktrees): prove cached lineage variants (#8882) * test(worktrees): exercise lineage through the shared scan cache (#8882) * fix(worktrees): prevent stale runtime scans from repopulating cache (#8882) * test(worktrees): prove stale generations stay out of cache (#8882) * fix(worktrees): invalidate hidden SSH scans on relay state (#8882) * fix(worktrees): scope SSH scan invalidation to provider changes (#8882) * fix(worktrees): preserve enriched hidden SSH state notifications (#8882) * test(worktrees): pin SSH provider generation monotonicity (#8882) * test(worktrees): drain cached scan mock responses (#8882) * fix(worktrees): scope resolved SSH invalidation to affected repos (#8882) * fix(worktrees): separate SSH scan invalidation from client events (#8882) * perf(worktrees): preserve raw scan reuse across metadata updates # Conflicts: # src/main/runtime/orca-runtime.ts * perf(worktrees): scope repo-local scan invalidation # Conflicts: # src/main/runtime/orca-runtime.ts * perf(worktrees): scope all repository scan invalidations * fix(worktrees): drop scan cache on orphan-cleanup removal (#8882) The orphan-cleanup fallback in removeManagedWorktree (git worktree remove fails with 'not a working tree', Orca deletes the directory and prunes) invalidated only the resolved cache, so the removed worktree kept being served from the bounded raw scan cache for up to its 30s TTL. Co-authored-by: Orca * Simplify SSH deploy-success handling to use broadcastSshState Route the post-deploy "connected" notification through the shared broadcastSshState helper instead of manually reimplementing window broadcast and runtime cache invalidation inline, keeping SSH state propagation consistent with other call sites. * perf(worktrees): defer SSH enrichment, consolidate cache invalidation - Skip enriching ephemeral-VM SSH state since it's not broadcast to the renderer, avoiding unnecessary withSshRemotePlatform() calls. - Move invalidateResolvedWorktreeCache() outside the conditional branch to eliminate redundant calls (previously called in both if and else). --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Co-authored-by: Orca --- src/main/ipc/ssh.test.ts | 66 ++- src/main/ipc/ssh.ts | 21 +- src/main/providers/ssh-git-dispatch.test.ts | 28 ++ src/main/providers/ssh-git-dispatch.ts | 10 +- src/main/runtime/orca-runtime.test.ts | 442 +++++++++++++++++++- src/main/runtime/orca-runtime.ts | 136 +++++- 6 files changed, 682 insertions(+), 21 deletions(-) create mode 100644 src/main/providers/ssh-git-dispatch.test.ts diff --git a/src/main/ipc/ssh.test.ts b/src/main/ipc/ssh.test.ts index 51801ecdef5..aaeb33f5bbd 100644 --- a/src/main/ipc/ssh.test.ts +++ b/src/main/ipc/ssh.test.ts @@ -214,7 +214,11 @@ vi.mock('../ssh/ssh-port-scanner', () => ({ })) import { getSshConnectionManager, registerSshHandlers, resetSshHandlerStateForTests } from './ssh' -import { SSH_RELAY_CONFIGURE_GRACE_TIME_METHOD, type SshTarget } from '../../shared/ssh-types' +import { + SSH_RELAY_CONFIGURE_GRACE_TIME_METHOD, + type SshConnectionState, + type SshTarget +} from '../../shared/ssh-types' import { clearProviderPtyState, deletePtyOwnership, @@ -840,6 +844,66 @@ describe('SSH IPC handlers', () => { ) }) + it('keeps runtime-owned SSH state off the renderer while invalidating runtime scans', async () => { + const runtime = { + onPtyData: vi.fn(), + onPtyExit: vi.fn(), + invalidateSshWorktreeScanCache: vi.fn(), + notifySshStateChanged: vi.fn() + } + registerSshHandlers(mockStore as never, () => mockWindow as never, runtime as never) + mockSshStore.getTarget.mockReturnValue({ + id: 'runtime-ssh-1', + label: 'Runtime host', + host: 'example.com', + port: 22, + username: 'deploy' + } satisfies SshTarget) + mockConnectionManager.connect.mockResolvedValue({}) + mockConnectionManager.getState.mockReturnValue({ + targetId: 'runtime-ssh-1', + status: 'connected', + error: null, + reconnectAttempt: 0 + }) + + await handlers.get('ssh:connect')!(null, { targetId: 'runtime-ssh-1' }) + + expect(mockWindow.webContents.send).not.toHaveBeenCalledWith( + 'ssh:state-changed', + expect.anything() + ) + expect(runtime.invalidateSshWorktreeScanCache).toHaveBeenCalledWith('runtime-ssh-1') + expect(runtime.notifySshStateChanged).not.toHaveBeenCalled() + }) + + it('invalidates runtime scans from hidden SSH state broadcasts', () => { + const runtime = { + onPtyData: vi.fn(), + onPtyExit: vi.fn(), + invalidateSshWorktreeScanCache: vi.fn(), + notifySshStateChanged: vi.fn() + } + registerSshHandlers(mockStore as never, () => mockWindow as never, runtime as never) + const callbacks = mockConnectionManager.callbacksRef.current as { + onStateChange: (targetId: string, state: SshConnectionState) => void + } + + callbacks.onStateChange('runtime-ssh-1', { + targetId: 'runtime-ssh-1', + status: 'disconnected', + error: null, + reconnectAttempt: 1 + }) + + expect(runtime.invalidateSshWorktreeScanCache).toHaveBeenCalledWith('runtime-ssh-1') + expect(runtime.notifySshStateChanged).not.toHaveBeenCalled() + expect(mockWindow.webContents.send).not.toHaveBeenCalledWith( + 'ssh:state-changed', + expect.anything() + ) + }) + it('preserves active port forwards and live connections across handler re-registration', async () => { const target: SshTarget = { id: 'ssh-1', diff --git a/src/main/ipc/ssh.ts b/src/main/ipc/ssh.ts index affb1aba004..9526303a0bf 100644 --- a/src/main/ipc/ssh.ts +++ b/src/main/ipc/ssh.ts @@ -244,6 +244,7 @@ function broadcastSshState( // has no surface for them. Broadcasting their state would make the renderer fire // a listTargets() lookup per event (incl. each relay-lost reconnect) for nothing. if (isRuntimeOwnedSshTargetId(targetId)) { + currentRuntime?.invalidateSshWorktreeScanCache?.(targetId) return } const enrichedState = withSshRemotePlatform(targetId, state) @@ -919,19 +920,13 @@ export function registerSshHandlers( // state is stuck there. Send `connected` directly to the renderer // instead of going through callbacks.onStateChange, which would // trigger the reconnection logic. - const win = getCurrentMainWindow() - if (win && !win.isDestroyed()) { - clearRelayStateOverride(targetId) - win.webContents.send('ssh:state-changed', { - targetId, - state: withSshRemotePlatform(targetId, { - targetId, - status: 'connected', - error: null, - reconnectAttempt: 0 - }) - }) - } + clearRelayStateOverride(targetId) + broadcastSshState(getCurrentMainWindow, targetId, { + targetId, + status: 'connected', + error: null, + reconnectAttempt: 0 + }) } catch (err) { // Relay deployment failed — disconnect SSH activeSessions.delete(targetId) diff --git a/src/main/providers/ssh-git-dispatch.test.ts b/src/main/providers/ssh-git-dispatch.test.ts new file mode 100644 index 00000000000..a975f3db475 --- /dev/null +++ b/src/main/providers/ssh-git-dispatch.test.ts @@ -0,0 +1,28 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { + getSshGitProviderGeneration, + registerSshGitProvider, + unregisterSshGitProvider +} from './ssh-git-dispatch' + +describe('SSH Git provider registry', () => { + const connectionId = 'ssh-generation-test' + + afterEach(() => { + unregisterSshGitProvider(connectionId) + }) + + it('keeps provider generations monotonic across unregister and re-register', () => { + const before = getSshGitProviderGeneration(connectionId) + registerSshGitProvider(connectionId, {} as never) + const registered = getSshGitProviderGeneration(connectionId) + unregisterSshGitProvider(connectionId) + const unregistered = getSshGitProviderGeneration(connectionId) + registerSshGitProvider(connectionId, {} as never) + const reRegistered = getSshGitProviderGeneration(connectionId) + + expect(registered).toBe(before + 1) + expect(unregistered).toBe(registered + 1) + expect(reRegistered).toBe(unregistered + 1) + }) +}) diff --git a/src/main/providers/ssh-git-dispatch.ts b/src/main/providers/ssh-git-dispatch.ts index a1032331fab..c0acc757ada 100644 --- a/src/main/providers/ssh-git-dispatch.ts +++ b/src/main/providers/ssh-git-dispatch.ts @@ -1,16 +1,24 @@ import type { SshGitProvider } from './ssh-git-provider' const sshProviders = new Map() +const sshProviderGenerations = new Map() export const SSH_GIT_PROVIDER_UNAVAILABLE_MESSAGE = 'Remote connection dropped. Click Reconnect on the SSH target before retrying.' export function registerSshGitProvider(connectionId: string, provider: SshGitProvider): void { sshProviders.set(connectionId, provider) + sshProviderGenerations.set(connectionId, (sshProviderGenerations.get(connectionId) ?? 0) + 1) } export function unregisterSshGitProvider(connectionId: string): void { - sshProviders.delete(connectionId) + if (sshProviders.delete(connectionId)) { + sshProviderGenerations.set(connectionId, (sshProviderGenerations.get(connectionId) ?? 0) + 1) + } +} + +export function getSshGitProviderGeneration(connectionId: string): number { + return sshProviderGenerations.get(connectionId) ?? 0 } export function getSshGitProvider(connectionId: string): SshGitProvider | undefined { diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index 055d1c2ac48..afb3b74770a 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -180,7 +180,9 @@ const { computeWorktreePathMock, ensurePathWithinWorkspaceMock, sshGitProviders, + sshProviderGenerations, getSshGitProviderMock, + getSshGitProviderGenerationMock, registerSshGitProviderMock, unregisterSshGitProviderMock, getActiveMultiplexerMock, @@ -253,6 +255,7 @@ const { // Why: SSH runtime tests register providers through the public dispatcher API, // so the mock needs the same registry semantics as the real module. const sshGitProviders = new Map() + const sshProviderGenerations = new Map() return { MOCK_GIT_WORKTREES: [ @@ -270,12 +273,22 @@ const { computeWorktreePathMock: vi.fn(), ensurePathWithinWorkspaceMock: vi.fn(), sshGitProviders, + sshProviderGenerations, getSshGitProviderMock: vi.fn((connectionId: string) => sshGitProviders.get(connectionId)), + getSshGitProviderGenerationMock: vi.fn( + (connectionId: string) => sshProviderGenerations.get(connectionId) ?? 0 + ), registerSshGitProviderMock: vi.fn((connectionId: string, provider: unknown) => { sshGitProviders.set(connectionId, provider) + sshProviderGenerations.set(connectionId, (sshProviderGenerations.get(connectionId) ?? 0) + 1) }), unregisterSshGitProviderMock: vi.fn((connectionId: string) => { - sshGitProviders.delete(connectionId) + if (sshGitProviders.delete(connectionId)) { + sshProviderGenerations.set( + connectionId, + (sshProviderGenerations.get(connectionId) ?? 0) + 1 + ) + } }), getActiveMultiplexerMock: vi.fn(), muxRequestMock: vi.fn(), @@ -361,6 +374,7 @@ vi.mock('../terminal-history', () => ({ vi.mock('../providers/ssh-git-dispatch', () => ({ getSshGitProvider: getSshGitProviderMock, + getSshGitProviderGeneration: getSshGitProviderGenerationMock, SSH_GIT_PROVIDER_UNAVAILABLE_MESSAGE: 'Remote connection dropped. Click Reconnect on the SSH target before retrying.', requireSshGitProvider: (connectionId: string) => { @@ -594,6 +608,7 @@ function resetRuntimeTestMocks(): void { vi.mocked(forceDeleteLocalBranchMock).mockReset() vi.mocked(forceDeleteLocalBranchMock).mockResolvedValue(undefined) sshGitProviders.clear() + sshProviderGenerations.clear() getSshGitProviderMock.mockReset() getSshGitProviderMock.mockImplementation((connectionId: string) => sshGitProviders.get(connectionId) @@ -601,10 +616,13 @@ function resetRuntimeTestMocks(): void { registerSshGitProviderMock.mockReset() registerSshGitProviderMock.mockImplementation((connectionId: string, provider: unknown) => { sshGitProviders.set(connectionId, provider) + sshProviderGenerations.set(connectionId, (sshProviderGenerations.get(connectionId) ?? 0) + 1) }) unregisterSshGitProviderMock.mockReset() unregisterSshGitProviderMock.mockImplementation((connectionId: string) => { - sshGitProviders.delete(connectionId) + if (sshGitProviders.delete(connectionId)) { + sshProviderGenerations.set(connectionId, (sshProviderGenerations.get(connectionId) ?? 0) + 1) + } }) muxRequestMock.mockReset() muxRequestMock.mockResolvedValue(undefined) @@ -1116,6 +1134,19 @@ function makeWorktreeMeta(overrides: Partial = {}): WorktreeMeta { } } +function makeWorktreeInfo( + path: string, + head = 'head' +): { + path: string + head: string + branch: string + isBare: boolean + isMainWorktree: boolean +} { + return { path, head, branch: 'main', isBare: false, isMainWorktree: path.endsWith('/repo') } +} + function deferred(): { promise: Promise resolve: (value: T) => void @@ -2989,6 +3020,11 @@ describe('OrcaRuntimeService', () => { id: result.worktree.id, path: createdWorktree.path }) + await expect(runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`)).resolves.toMatchObject( + { + worktrees: expect.arrayContaining([expect.objectContaining({ path: createdWorktree.path })]) + } + ) }) it('creates additional workspace metadata for folder-mode repos through runtime create', async () => { @@ -25545,6 +25581,379 @@ describe('OrcaRuntimeService', () => { expect(removeWorktreeLineage).not.toHaveBeenCalled() }) + it('bounds repeated detected worktree scans across the reported 15-repo shape', async () => { + vi.mocked(listWorktrees).mockReset() + const repos = Array.from({ length: 15 }, (_, index) => ({ + id: `repo-${index + 1}`, + path: `/tmp/repo-${index + 1}`, + displayName: `repo-${index + 1}`, + badgeColor: 'blue' as const, + addedAt: 1 + })) + const runtime = new OrcaRuntimeService({ + ...store, + getRepos: () => repos, + getRepo: (id: string) => repos.find((repo) => repo.id === id), + getAllWorktreeMeta: () => ({}), + getWorktreeMeta: () => undefined + } as never) + vi.mocked(listWorktrees).mockImplementation(async (repoPath) => [ + { + path: `${repoPath}/main`, + head: repoPath, + branch: 'main', + isBare: false, + isMainWorktree: true + } + ]) + + const poll = async () => + Promise.all(repos.map((repo) => runtime.listDetectedManagedWorktrees(`id:${repo.id}`))) + const first = await poll() + expect(listWorktrees).toHaveBeenCalledTimes(15) + const second = await poll() + + expect(first.flatMap((result) => result.worktrees)).toHaveLength(15) + expect(second.flatMap((result) => result.worktrees)).toHaveLength(15) + expect(second.flatMap((result) => result.worktrees).map((worktree) => worktree.path)).toEqual( + repos.map((repo) => `${repo.path}/main`) + ) + expect(listWorktrees).toHaveBeenCalledTimes(15) + }) + + it('worktree scan cache: shares one in-flight repo scan across concurrent consumers', async () => { + vi.mocked(listWorktrees).mockClear() + const pending = deferred[]>() + vi.mocked(listWorktrees).mockReturnValueOnce(pending.promise) + const runtime = createRuntime() + + const detected = runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + const resolved = runtime.listManagedWorktrees() + await Promise.resolve() + expect(listWorktrees).toHaveBeenCalledTimes(1) + + pending.resolve([makeWorktreeInfo(TEST_WORKTREE_PATH)]) + await expect(Promise.all([detected, resolved])).resolves.toBeTruthy() + expect(listWorktrees).toHaveBeenCalledTimes(1) + }) + + it('worktree scan cache: rescans immediately after worktree invalidation', async () => { + vi.mocked(listWorktrees).mockClear() + const runtime = createRuntime() + await runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + runtime.notifyBranchRenamed(TEST_REPO_ID) + await runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + + expect(listWorktrees).toHaveBeenCalledTimes(2) + }) + + it('worktree scan cache: repo invalidation preserves sibling raw scans', async () => { + vi.mocked(listWorktrees).mockClear() + const repos = [ + { ...store.getRepos()[0], id: 'repo-a', path: '/tmp/repo-a' }, + { ...store.getRepos()[0], id: 'repo-b', path: '/tmp/repo-b' } + ] + const runtime = new OrcaRuntimeService({ + ...store, + getRepos: () => repos, + getRepo: (id: string) => repos.find((repo) => repo.id === id) + } as never) + vi.mocked(listWorktrees).mockImplementation(async (repoPath) => [makeWorktreeInfo(repoPath)]) + + await runtime.listDetectedManagedWorktrees('id:repo-a') + await runtime.listDetectedManagedWorktrees('id:repo-b') + runtime.notifyBranchRenamed('repo-a') + await runtime.listDetectedManagedWorktrees('id:repo-b') + await runtime.listDetectedManagedWorktrees('id:repo-a') + + expect(listWorktrees).toHaveBeenCalledTimes(3) + expect(listWorktrees).toHaveBeenNthCalledWith(3, '/tmp/repo-a') + }) + + it('worktree scan cache: metadata invalidation preserves raw scans', async () => { + vi.mocked(listWorktrees).mockClear() + const runtime = createRuntime() + await runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + runtime.notifyWorktreesChangedForRemoteClients(TEST_REPO_ID) + await runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + + expect(listWorktrees).toHaveBeenCalledTimes(1) + }) + + it('worktree scan cache: folder metadata invalidation preserves raw scans', async () => { + vi.mocked(listWorktrees).mockClear() + const runtime = createRuntime() + await runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + runtime.notifyFolderWorkspaceChanged() + await runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + + expect(listWorktrees).toHaveBeenCalledTimes(1) + }) + + it('worktree scan cache: expires scans per repo without coupling sibling repos', async () => { + vi.mocked(listWorktrees).mockClear() + vi.useFakeTimers({ now: 0 }) + try { + const repos = [ + { ...store.getRepos()[0], id: 'repo-a', path: '/tmp/repo-a' }, + { ...store.getRepos()[0], id: 'repo-b', path: '/tmp/repo-b' } + ] + const runtime = new OrcaRuntimeService({ + ...store, + getRepos: () => repos, + getRepo: (id: string) => repos.find((repo) => repo.id === id) + } as never) + vi.mocked(listWorktrees).mockImplementation(async (repoPath) => [makeWorktreeInfo(repoPath)]) + + await runtime.listDetectedManagedWorktrees('id:repo-a') + await vi.advanceTimersByTimeAsync(10_000) + await runtime.listDetectedManagedWorktrees('id:repo-b') + await vi.advanceTimersByTimeAsync(20_000) + await runtime.listDetectedManagedWorktrees('id:repo-a') + await runtime.listDetectedManagedWorktrees('id:repo-b') + + expect(listWorktrees).toHaveBeenCalledTimes(3) + expect(listWorktrees).toHaveBeenNthCalledWith(3, '/tmp/repo-a') + } finally { + vi.useRealTimers() + } + }) + + it('worktree scan cache: does not cache non-authoritative scan failures', async () => { + vi.mocked(listWorktrees).mockClear() + const runtime = createRuntime() + vi.mocked(listWorktrees) + .mockRejectedValueOnce(new Error('git unavailable')) + .mockResolvedValueOnce([makeWorktreeInfo(TEST_WORKTREE_PATH)]) + + await expect(runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`)).resolves.toMatchObject( + { + authoritative: false + } + ) + await expect(runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`)).resolves.toMatchObject( + { + authoritative: true + } + ) + expect(listWorktrees).toHaveBeenCalledTimes(2) + }) + + it('worktree scan cache: invalidates when SSH availability changes', async () => { + const remoteRepo = { ...store.getRepo(TEST_REPO_ID)!, connectionId: 'ssh-1' } + const remoteStore = { + ...store, + getRepo: (id: string) => (id === remoteRepo.id ? remoteRepo : undefined), + getRepos: () => [remoteRepo] + } + const provider = { listWorktrees: vi.fn().mockResolvedValue(MOCK_GIT_WORKTREES) } + registerSshGitProvider('ssh-1', provider as never) + const runtime = new OrcaRuntimeService(remoteStore as never) + + try { + await expect( + runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + ).resolves.toMatchObject({ authoritative: true }) + unregisterSshGitProvider('ssh-1') + await expect( + runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + ).resolves.toMatchObject({ authoritative: false }) + expect(provider.listWorktrees).toHaveBeenCalledTimes(1) + } finally { + unregisterSshGitProvider('ssh-1') + } + }) + + it('worktree scan cache: SSH state changes do not evict local repo scans', async () => { + vi.mocked(listWorktrees).mockClear() + const localRepo = { ...store.getRepo(TEST_REPO_ID)!, id: 'local-repo' } + const remoteRepo = { ...store.getRepo(TEST_REPO_ID)!, id: 'remote-repo', connectionId: 'ssh-1' } + const repos = [localRepo, remoteRepo] + const remoteStore = { + ...store, + getRepo: (id: string) => repos.find((repo) => repo.id === id), + getRepos: () => repos + } + const provider = { listWorktrees: vi.fn().mockResolvedValue(MOCK_GIT_WORKTREES) } + registerSshGitProvider('ssh-1', provider as never) + const runtime = new OrcaRuntimeService(remoteStore as never) + + try { + await runtime.listDetectedManagedWorktrees('id:local-repo') + await runtime.listDetectedManagedWorktrees('id:remote-repo') + runtime.notifySshStateChanged('ssh-1', { + targetId: 'ssh-1', + status: 'disconnected', + error: null, + reconnectAttempt: 0 + }) + await runtime.listDetectedManagedWorktrees('id:local-repo') + await runtime.listDetectedManagedWorktrees('id:remote-repo') + + expect(listWorktrees).toHaveBeenCalledTimes(1) + expect(provider.listWorktrees).toHaveBeenCalledTimes(2) + } finally { + unregisterSshGitProvider('ssh-1') + } + }) + + it('worktree scan cache: SSH state changes invalidate empty resolved snapshots', async () => { + const remoteRepo = { ...store.getRepo(TEST_REPO_ID)!, connectionId: 'ssh-empty' } + const remoteStore = { + ...store, + getRepo: (id: string) => (id === remoteRepo.id ? remoteRepo : undefined), + getRepos: () => [remoteRepo] + } + const provider = { listWorktrees: vi.fn().mockResolvedValue([]) } + registerSshGitProvider('ssh-empty', provider as never) + const runtime = new OrcaRuntimeService(remoteStore as never) + + try { + await expect(runtime.listManagedWorktrees()).resolves.toMatchObject({ totalCount: 0 }) + runtime.notifySshStateChanged('ssh-empty', { + targetId: 'ssh-empty', + status: 'connected', + error: null, + reconnectAttempt: 0 + }) + await expect(runtime.listManagedWorktrees()).resolves.toMatchObject({ totalCount: 0 }) + expect(provider.listWorktrees).toHaveBeenCalledTimes(2) + } finally { + unregisterSshGitProvider('ssh-empty') + } + }) + + it('worktree scan cache: SSH state changes for unknown targets keep local snapshots in flight', async () => { + vi.mocked(listWorktrees).mockClear() + const pending = deferred[]>() + vi.mocked(listWorktrees).mockReturnValueOnce(pending.promise) + const runtime = new OrcaRuntimeService(store) + + const first = runtime.listManagedWorktrees() + await Promise.resolve() + runtime.notifySshStateChanged('ssh-unknown', { + targetId: 'ssh-unknown', + status: 'disconnected', + error: null, + reconnectAttempt: 0 + }) + pending.resolve([makeWorktreeInfo(TEST_WORKTREE_PATH)]) + await expect(first).resolves.toMatchObject({ totalCount: 1 }) + await expect(runtime.listManagedWorktrees()).resolves.toMatchObject({ totalCount: 1 }) + + expect(listWorktrees).toHaveBeenCalledTimes(1) + }) + + it('worktree scan cache: separates local project runtime changes', async () => { + vi.mocked(listWorktrees).mockClear() + let runtimeDefault: unknown = { kind: 'windows-host' } + const runtimeStore = { + ...store, + getProjects: () => [{ id: 'project-1', sourceRepoIds: [TEST_REPO_ID] }], + getSettings: () => ({ ...store.getSettings(), localWindowsRuntimeDefault: runtimeDefault }) + } + + await withPlatform('win32', async () => { + const runtime = new OrcaRuntimeService(runtimeStore as never) + await runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + runtimeDefault = { kind: 'wsl', distro: 'Ubuntu' } + await runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + }) + + expect(listWorktrees).toHaveBeenCalledTimes(2) + }) + + it('worktree scan cache: ignores a late result from an older runtime key', async () => { + vi.mocked(listWorktrees).mockClear() + let runtimeDefault: unknown = { kind: 'windows-host' } + const runtimeStore = { + ...store, + getProjects: () => [{ id: 'project-1', sourceRepoIds: [TEST_REPO_ID] }], + getSettings: () => ({ ...store.getSettings(), localWindowsRuntimeDefault: runtimeDefault }) + } + const firstScan = deferred[]>() + const secondScan = deferred[]>() + vi.mocked(listWorktrees) + .mockReturnValueOnce(firstScan.promise) + .mockReturnValueOnce(secondScan.promise) + + await withPlatform('win32', async () => { + const runtime = new OrcaRuntimeService(runtimeStore as never) + const first = runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + await Promise.resolve() + expect(listWorktrees).toHaveBeenCalledTimes(1) + + runtimeDefault = { kind: 'wsl', distro: 'Ubuntu' } + const second = runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + await Promise.resolve() + expect(listWorktrees).toHaveBeenCalledTimes(2) + + secondScan.resolve([makeWorktreeInfo(TEST_WORKTREE_PATH)]) + await second + firstScan.resolve([makeWorktreeInfo(TEST_WORKTREE_PATH)]) + await first + await runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + }) + + expect(listWorktrees).toHaveBeenCalledTimes(2) + }) + + it('worktree scan cache: keeps lineage shaping outside the raw scan cache', async () => { + vi.mocked(listWorktrees).mockClear() + const paths = ['orchestration', 'cli', 'manual'] + const metaById: Record = {} + const lineageById: Record = {} + const worktrees = paths.flatMap((origin, index) => { + const parentPath = `/tmp/${origin}-parent` + const childPath = `/tmp/${origin}-child` + const parentId = `${TEST_REPO_ID}::${parentPath}` + const childId = `${TEST_REPO_ID}::${childPath}` + metaById[parentId] = makeWorktreeMeta({ instanceId: `parent-${index}` }) + metaById[childId] = makeWorktreeMeta({ instanceId: `child-${index}` }) + lineageById[childId] = { + worktreeId: childId, + worktreeInstanceId: `child-${index}`, + parentWorktreeId: parentId, + parentWorktreeInstanceId: `parent-${index}`, + origin: origin === 'orchestration' ? 'orchestration' : origin === 'cli' ? 'cli' : 'manual', + capture: { source: 'manual-action', confidence: 'explicit' }, + createdAt: 1 + } + return [ + { path: parentPath, head: 'parent', branch: origin, isBare: false, isMainWorktree: false }, + { + path: childPath, + head: 'child', + branch: `${origin}-child`, + isBare: false, + isMainWorktree: false + } + ] + }) + const runtimeStore = { + ...store, + getAllWorktreeMeta: () => metaById, + getWorktreeMeta: (worktreeId: string) => metaById[worktreeId], + setWorktreeMeta: (worktreeId: string, meta: Partial) => { + metaById[worktreeId] = { ...metaById[worktreeId], ...meta } + return metaById[worktreeId] + }, + getAllWorktreeLineage: () => lineageById + } + vi.mocked(listWorktrees).mockResolvedValue(worktrees) + const runtime = new OrcaRuntimeService(runtimeStore as never) + + await runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + const listed = await runtime.listManagedWorktrees(`id:${TEST_REPO_ID}`) + + expect( + listed.worktrees + .filter((worktree) => worktree.lineage) + .map((worktree) => worktree.lineage?.origin) + ).toEqual(paths) + expect(listWorktrees).toHaveBeenCalledTimes(1) + }) + it('does not prune lineage when an SSH runtime provider is unavailable', async () => { const remoteRepo = { id: 'remote-repo', @@ -30800,6 +31209,35 @@ describe('OrcaRuntimeService', () => { expect(deleteWorktreeHistoryDirMock).toHaveBeenCalledWith(TEST_WORKTREE_ID) }) + it('drops the bounded scan cache when orphan-cleanup removal completes', async () => { + const runtime = createWorktreeRemovalRuntime() + vi.mocked(getEffectiveHooks).mockReturnValue(null) + vi.mocked(assertWorktreeCleanForRemoval).mockRejectedValue( + Object.assign(new Error('status failed'), { + stderr: 'fatal: not a git repository (or any of the parent directories): .git\n' + }) + ) + vi.mocked(removeWorktree).mockRejectedValue( + Object.assign(new Error('git worktree remove failed'), { + stderr: `fatal: '${TEST_WORKTREE_PATH}' is not a working tree` + }) + ) + vi.spyOn(gitRunner, 'gitExecFileAsync').mockResolvedValue({ stdout: '', stderr: '' }) + + // Prime the bounded raw scan cache with the worktree still present. + await runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`) + + await expect(runtime.removeManagedWorktree(TEST_WORKTREE_ID)).resolves.toEqual({}) + + // Regression (#8882): the orphan-cleanup removal path must also drop the + // bounded raw scan cache, or the removed worktree lingers in listings until + // the 30s TTL. Git tracking is now gone, so a fresh scan is empty. + vi.mocked(listWorktrees).mockResolvedValue([]) + await expect(runtime.listDetectedManagedWorktrees(`id:${TEST_REPO_ID}`)).resolves.toMatchObject( + { worktrees: [] } + ) + }) + it('runs archive hooks for CLI worktree removal when hooks are explicitly enabled', async () => { const runtime = createWorktreeRemovalRuntime() vi.mocked(getEffectiveHooks).mockReturnValue({ diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index 6e1bce48a5a..d919c077be1 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -764,7 +764,11 @@ import { getFolderWorkspacePathStatusForPath, inferFolderWorkspacePathConnection } from '../project-groups/folder-workspace-path-status' -import { getSshGitProvider, requireSshGitProvider } from '../providers/ssh-git-dispatch' +import { + getSshGitProvider, + getSshGitProviderGeneration, + requireSshGitProvider +} from '../providers/ssh-git-dispatch' import { detectRepoIconAndUpstream } from '../repo-icon-autodetect' import { enrichMissingRepoGitRemoteIdentities } from '../repo-git-remote-identity-enrichment' import { githubAvatarIcon } from '../../shared/repo-icon' @@ -2031,6 +2035,19 @@ type RuntimeWorktreeScanResult = | { ok: true; worktrees: GitWorktreeInfo[] } | { ok: false; worktrees: GitWorktreeInfo[] } +type RuntimeWorktreeScanCache = { + generation: number + runtimeKey: string + result: Extract + expiresAt: number +} + +type RuntimeWorktreeScanInFlight = { + generation: number + runtimeKey: string + promise: Promise +} + type WorktreeLineageCandidate = { source: 'env-workspace' | 'cwd-context' | 'terminal-context' | 'orchestration-context' parent: ResolvedWorkspaceParent @@ -2223,6 +2240,9 @@ export class OrcaRuntimeService { private resolvedWorktreeCache: ResolvedWorktreeCache | null = null private resolvedWorktreeInFlight: ResolvedWorktreeInFlight | null = null private resolvedWorktreeGeneration = 0 + private worktreeScanGenerations = new Map() + private worktreeScanCache = new Map() + private worktreeScanInFlight = new Map() private cloneInFlightByPath = new Map>() private agentDetector: AgentDetector | null = null private ptyForegroundAgentRefreshes = new Map() @@ -3138,9 +3158,14 @@ export class OrcaRuntimeService { // Why: SSH state changes originate in main's ssh handlers, not in runtime // methods, so they need a public entry point onto the client-event stream. notifySshStateChanged(targetId: string, state: SshConnectionState): void { + this.invalidateSshWorktreeScanCache(targetId) this.emitClientEvent({ type: 'sshStateChanged', targetId, state }) } + invalidateSshWorktreeScanCache(targetId: string): void { + this.invalidateSshWorktreeScanCacheInternal(targetId) + } + // Why: renderer-initiated meta updates intentionally skip the renderer // notifier (the renderer already applied them optimistically), but remote // clients hold no optimistic copy and need the invalidation event. @@ -12677,6 +12702,11 @@ export class OrcaRuntimeService { } } this.invalidateResolvedWorktreeCache() + for (const project of results) { + if (project.projectId) { + this.invalidateWorktreeScanCacheForRepo(project.projectId) + } + } this.notifyReposChanged() const rootGroup = groupResolver.getRootGroup() return { @@ -12757,6 +12787,7 @@ export class OrcaRuntimeService { this.store.updateRepo(existing.id, { executionHostId }) ?? ({ ...existing, executionHostId } as Repo) this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(existing.id) this.notifyReposChanged() return adopted } @@ -12783,6 +12814,7 @@ export class OrcaRuntimeService { this.store.addRepo(repo) await prepareLocalWorktreeRootForRepo(this.store, repo) this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(repo.id) this.notifyReposChanged() return this.store.getRepo(repo.id) ?? repo } @@ -12904,6 +12936,7 @@ export class OrcaRuntimeService { await prepareLocalWorktreeRootForRepo(this.store, repo) invalidateAuthorizedRootsCache() this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(repo.id) this.notifyReposChanged() return { repo: this.store.getRepo(repo.id) ?? repo } } @@ -13047,6 +13080,7 @@ export class OrcaRuntimeService { await prepareLocalWorktreeRootForRepo(this.store, updated) invalidateAuthorizedRootsCache() this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(updated.id) this.notifyReposChanged() return updated } @@ -13071,6 +13105,7 @@ export class OrcaRuntimeService { await prepareLocalWorktreeRootForRepo(this.store, repo) invalidateAuthorizedRootsCache() this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(repo.id) this.notifyReposChanged() return this.store.getRepo(repo.id) ?? repo } @@ -13149,6 +13184,9 @@ export class OrcaRuntimeService { invalidateAuthorizedRootsCache() } this.invalidateResolvedWorktreeCache() + if ('worktreeBasePath' in updates) { + this.invalidateWorktreeScanCacheForRepo(repo.id) + } this.notifyReposChanged() return updated } @@ -13160,6 +13198,7 @@ export class OrcaRuntimeService { const repo = await this.resolveRepoSelector(repoSelector) this.store.removeProject(repo.id) this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(repo.id) invalidateAuthorizedRootsCache() this.notifyReposChanged() return { removed: true } @@ -16256,6 +16295,7 @@ export class OrcaRuntimeService { } this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(repo.id) // Why: the filesystem-auth layer maintains a separate cache of registered // worktree roots used by git IPC handlers (branchCompare, diff, status, etc.) // to authorize paths. Without invalidating it here, CLI-created worktrees @@ -16569,6 +16609,7 @@ export class OrcaRuntimeService { } this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(repo.id) this.notifyWorktreesChanged(repo.id) let warning = result.warning @@ -17259,6 +17300,10 @@ export class OrcaRuntimeService { } const worktree = await this.resolveWorktreeSelector(worktreeSelector) const { lineage, ...metaUpdates } = updates + if (lineage?.parentWorktree) { + this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(worktree.repoId) + } const shouldClearPushTarget = Object.prototype.hasOwnProperty.call(metaUpdates, 'pushTarget') && metaUpdates.pushTarget === null @@ -17977,6 +18022,7 @@ export class OrcaRuntimeService { this.removeWorktreeMetadataAndHistory(store, removalTarget.id) this.preservedBranchCleanupByWorktreeId.delete(removalTarget.id) this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(removalTarget.repoId) invalidateAuthorizedRootsCache() this.notifyWorktreesChanged(repo.id) return {} @@ -18022,6 +18068,7 @@ export class OrcaRuntimeService { this.removeWorktreeMetadataAndHistory(store, removalTarget.id) this.preservedBranchCleanupByWorktreeId.delete(removalTarget.id) this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(removalTarget.repoId) invalidateAuthorizedRootsCache() this.notifyWorktreesChanged(repo.id) return {} @@ -18055,6 +18102,7 @@ export class OrcaRuntimeService { this.removeWorktreeMetadataAndHistory(store, removalTarget.id) this.preservedBranchCleanupByWorktreeId.delete(removalTarget.id) this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(removalTarget.repoId) invalidateAuthorizedRootsCache() this.notifyWorktreesChanged(repo.id) return {} @@ -18105,6 +18153,7 @@ export class OrcaRuntimeService { this.clearOptimisticReconcileToken(removalTarget.id) this.removeWorktreeMetadataAndHistory(store, removalTarget.id) this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(removalTarget.repoId) invalidateAuthorizedRootsCache() this.notifyWorktreesChanged(repo.id) return removalResult ?? {} @@ -18146,6 +18195,7 @@ export class OrcaRuntimeService { this.clearOptimisticReconcileToken(removalTarget.id) this.removeWorktreeMetadataAndHistory(store, removalTarget.id) this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(removalTarget.repoId) invalidateAuthorizedRootsCache() this.notifyWorktreesChanged(repo.id) return removalResult ?? {} @@ -18294,6 +18344,7 @@ export class OrcaRuntimeService { this.removeWorktreeMetadataAndHistory(store, removalTarget.id) this.preservedBranchCleanupByWorktreeId.delete(removalTarget.id) this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(removalTarget.repoId) invalidateAuthorizedRootsCache() this.notifyWorktreesChanged(repo.id) removalCompleted = true @@ -18325,6 +18376,7 @@ export class OrcaRuntimeService { this.clearOptimisticReconcileToken(removalTarget.id) this.removeWorktreeMetadataAndHistory(store, removalTarget.id) this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(removalTarget.repoId) invalidateAuthorizedRootsCache() this.notifyWorktreesChanged(repo.id) return { @@ -21037,11 +21089,62 @@ export class OrcaRuntimeService { private async listRepoWorktreesForResolution( repo: Repo, projectRuntimeByRepoId?: ReadonlyMap + ): Promise { + const now = Date.now() + const generation = this.worktreeScanGenerations.get(repo.id) ?? 0 + const projectRuntime = projectRuntimeByRepoId + ? projectRuntimeByRepoId.get(repo.id) + : !repo.connectionId + ? resolveLocalProjectRuntimeForRepo(this.requireStore(), repo) + : undefined + const runtimeKey = projectRuntime + ? projectRuntime.status === 'resolved' + ? projectRuntime.runtime.cacheKey + : projectRuntime.repair.cacheKey + : repo.connectionId + ? `ssh:${repo.connectionId}:${getSshGitProviderGeneration(repo.connectionId)}` + : 'local:default' + const cached = this.worktreeScanCache.get(repo.id) + if ( + cached?.generation === generation && + cached.runtimeKey === runtimeKey && + cached.expiresAt > now + ) { + return cached.result + } + const inFlight = this.worktreeScanInFlight.get(repo.id) + if (inFlight?.generation === generation && inFlight.runtimeKey === runtimeKey) { + return inFlight.promise + } + const promise = this.listRepoWorktreesForResolutionUncached(repo, projectRuntime) + this.worktreeScanInFlight.set(repo.id, { generation, runtimeKey, promise }) + try { + const result = await promise + if ( + result.ok && + generation === (this.worktreeScanGenerations.get(repo.id) ?? 0) && + this.worktreeScanInFlight.get(repo.id)?.promise === promise + ) { + this.worktreeScanCache.set(repo.id, { + generation, + runtimeKey, + result, + expiresAt: Date.now() + WORKTREE_SCAN_CACHE_TTL_MS + }) + } + return result + } finally { + if (this.worktreeScanInFlight.get(repo.id)?.promise === promise) { + this.worktreeScanInFlight.delete(repo.id) + } + } + } + + private async listRepoWorktreesForResolutionUncached( + repo: Repo, + projectRuntime: ProjectExecutionRuntimeResolution | undefined ): Promise { if (!repo.connectionId) { - const projectRuntime = projectRuntimeByRepoId - ? projectRuntimeByRepoId.get(repo.id) - : resolveLocalProjectRuntimeForRepo(this.requireStore(), repo) return { ok: true, worktrees: await listRepoWorktrees( @@ -21100,11 +21203,34 @@ export class OrcaRuntimeService { this.resolvedWorktreeCache = null } + private invalidateWorktreeScanCacheForRepo(repoId: string): void { + this.worktreeScanGenerations.set(repoId, (this.worktreeScanGenerations.get(repoId) ?? 0) + 1) + this.worktreeScanCache.delete(repoId) + this.worktreeScanInFlight.delete(repoId) + } + + private invalidateSshWorktreeScanCacheInternal(targetId: string): void { + const repos = this.store?.getRepos() ?? [] + const affectedRepoIds = new Set( + repos.filter((repo) => repo.connectionId === targetId).map((repo) => repo.id) + ) + for (const repoId of affectedRepoIds) { + this.worktreeScanGenerations.set(repoId, (this.worktreeScanGenerations.get(repoId) ?? 0) + 1) + this.worktreeScanCache.delete(repoId) + this.worktreeScanInFlight.delete(repoId) + } + if (affectedRepoIds.size > 0) { + this.resolvedWorktreeGeneration += 1 + this.resolvedWorktreeCache = null + } + } + /** Invalidate the worktree cache and tell the renderer to re-list, after an * out-of-band branch change (e.g. auto-rename-from-work) so the new branch * name surfaces without waiting for the next ambient refresh. */ notifyBranchRenamed(repoId: string): void { this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(repoId) this.notifyWorktreesChanged(repoId) } @@ -21113,6 +21239,7 @@ export class OrcaRuntimeService { * (from a folder rename) as a deletion. Same channel = guaranteed ordering. */ notifyWorktreeFolderRenamed(repoId: string, oldWorktreeId: string, newWorktreeId: string): void { this.invalidateResolvedWorktreeCache() + this.invalidateWorktreeScanCacheForRepo(repoId) this.notifier?.worktreesChanged(repoId, { oldWorktreeId, newWorktreeId }) // Mirror notifyBranchRenamed so in-process onClientEvent listeners also see the rename. this.emitClientEvent({ type: 'worktreesChanged', repoId }) @@ -25882,6 +26009,7 @@ const DEFAULT_WORKTREE_LIST_LIMIT = 200 const DEFAULT_WORKTREE_PS_LIMIT = 200 const DISCONNECTED_PTY_RECORD_MAX = 128 const RESOLVED_WORKTREE_CACHE_TTL_MS = 1000 +const WORKTREE_SCAN_CACHE_TTL_MS = 30_000 const RESOLVED_WORKTREE_REPO_TIMEOUT_MS = 5000 const PTY_CONTROLLER_LIST_TIMEOUT_MS = 3000 // Why (§3.3): 30s freshness window. A second worktree-create or dispatch-probe From e719ef1a57f0767db8a4644970240eacf43ed062 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:56:43 -0700 Subject: [PATCH 010/137] fix(mobile): survive connection-migration cutovers during worktree create (#9234) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(mobile): survive connection-migration cutovers during worktree create A worktree.create in flight when the mobile transport migrates (relay/direct hand-off on shoddy cellular, relay lease rotation, relay recovery) rejects with "RPC interrupted by connection migration" even though the host completed it — leaving the Create Workspace modal stuck while the worktree exists on desktop. A naive retry hits a name collision and spawns a duplicate. Mirror the existing mobile terminal-create idempotency: worktree.create now accepts an optional clientMutationId that the host dedupes (in-flight + brief post-success TTL), and mobile mints one key per candidate name and re-issues the create on a cutover so the retry reconciles instead of duplicating. * fix(mobile): gate worktree cutover replay by capability * fix(mobile): await worktree replay capability --- mobile/src/components/NewWorktreeModal.tsx | 22 +-- .../src/tasks/blank-workspace-create.test.ts | 20 +- mobile/src/tasks/blank-workspace-create.ts | 2 + .../src/tasks/source-workspace-create.test.ts | 3 +- mobile/src/tasks/source-workspace-create.ts | 8 + .../tasks/worktree-create-capability.test.ts | 57 ++++++ .../src/tasks/worktree-create-capability.ts | 104 +++++++++++ .../src/tasks/worktree-create-retry.test.ts | 172 ++++++++++++++++++ mobile/src/tasks/worktree-create-retry.ts | 87 ++++++++- src/main/runtime/orca-runtime.test.ts | 76 ++++++++ src/main/runtime/orca-runtime.ts | 40 ++++ ...ime-request-connection.integration.test.ts | 7 + .../runtime/rpc/methods/worktree-schemas.ts | 3 + src/main/runtime/rpc/methods/worktree.test.ts | 27 +++ src/main/runtime/rpc/methods/worktree.ts | 148 +++++++-------- src/shared/protocol-version.ts | 7 +- 16 files changed, 682 insertions(+), 101 deletions(-) create mode 100644 mobile/src/tasks/worktree-create-capability.test.ts create mode 100644 mobile/src/tasks/worktree-create-capability.ts create mode 100644 mobile/src/tasks/worktree-create-retry.test.ts diff --git a/mobile/src/components/NewWorktreeModal.tsx b/mobile/src/components/NewWorktreeModal.tsx index 09a0914d4aa..61c9b2e711b 100644 --- a/mobile/src/components/NewWorktreeModal.tsx +++ b/mobile/src/components/NewWorktreeModal.tsx @@ -50,7 +50,7 @@ import { } from '../worktree/new-workspace-dialog-repo-selection' import { createBlankWorkspace } from '../tasks/blank-workspace-create' import { createWorkspaceFromComposerSource } from '../tasks/source-workspace-create' -import { MOBILE_TASKS_CAPABILITY } from '../tasks/mobile-tasks-capability' +import { useNewWorktreeRuntimeCapabilities } from '../tasks/worktree-create-capability' import { normalizeWorkspaceAgent } from '../tasks/workspace-agent-selection' import { filterAvailableTaskProviders, @@ -210,7 +210,10 @@ function NewWorktreeModalContent({ const [sshConnectingTargetId, setSshConnectingTargetId] = useState(null) const [note, setNote] = useState('') const [availableProviders, setAvailableProviders] = useState([]) - const [tasksSupported, setTasksSupported] = useState(false) + const { tasksSupported, getWorktreeCreateCutoverSupport } = useNewWorktreeRuntimeCapabilities( + client, + visible + ) const [showAdvanced, setShowAdvanced] = useState(false) const [setupHookDetails, setSetupHookDetails] = useState(null) const [trustedOrcaHooks, setTrustedOrcaHooks] = useState({}) @@ -373,7 +376,6 @@ function NewWorktreeModalContent({ // linear.status timeout, which rejects rather than resolving {ok:false}) // can't discard the already-resolved critical settings/ui results. const probes = Promise.allSettled([ - client.sendRequest('status.get'), client.sendRequest('preflight.check'), client.sendRequest('linear.status') ]) @@ -407,16 +409,10 @@ function NewWorktreeModalContent({ setTrustedOrcaHooks(ui?.trustedOrcaHooks ?? {}) } - const [statusRes, preflightRes, linearRes] = await probes + const [preflightRes, linearRes] = await probes if (stale) { return } - // Tasks is an additive RPC surface, so older paired desktops without the - // capability fall back to branch + blank sources only. - const statusResult = okResult(statusRes) - const capabilities = - (statusResult?.result as { capabilities?: string[] } | undefined)?.capabilities ?? [] - setTasksSupported(capabilities.includes(MOBILE_TASKS_CAPABILITY)) const glabInstalled = (okResult(preflightRes)?.result as { glab?: { installed?: boolean } } | undefined)?.glab ?.installed === true @@ -698,7 +694,8 @@ function NewWorktreeModalContent({ }, workspaceName: trimmedName || undefined, note: trimmedNote, - nameIsAutoManaged: composer.isNameAutoManaged + nameIsAutoManaged: composer.isNameAutoManaged, + supportsIdempotentCutoverRetry: getWorktreeCreateCutoverSupport() }) : await createBlankWorkspace({ client, @@ -707,7 +704,8 @@ function NewWorktreeModalContent({ startupCommand: command, createdWithAgentId, comment: trimmedNote, - setupDecision + setupDecision, + supportsIdempotentCutoverRetry: getWorktreeCreateCutoverSupport() }) if ('error' in result) { setError(result.error) diff --git a/mobile/src/tasks/blank-workspace-create.test.ts b/mobile/src/tasks/blank-workspace-create.test.ts index 1d35d29f758..ba0fc4f1066 100644 --- a/mobile/src/tasks/blank-workspace-create.test.ts +++ b/mobile/src/tasks/blank-workspace-create.test.ts @@ -34,7 +34,8 @@ describe('createBlankWorkspace', () => { startupCommand: undefined, createdWithAgentId: undefined, comment: undefined, - setupDecision: 'inherit' + setupDecision: 'inherit', + supportsIdempotentCutoverRetry: true }) expect(result).toEqual({ worktreeId: 'wt-1', name: 'octopus' }) @@ -45,7 +46,10 @@ describe('createBlankWorkspace', () => { repo: 'id:repo-1', startupCommand: undefined, setupDecision: 'inherit', - name: 'octopus' + name: 'octopus', + // Idempotency key so a create interrupted by a connection migration can be + // safely retried without the host spawning a duplicate worktree. + clientMutationId: expect.any(String) } }) const params = calls[0]?.params as Record @@ -64,7 +68,8 @@ describe('createBlankWorkspace', () => { startupCommand: 'claude', createdWithAgentId: 'claude', comment: 'spike', - setupDecision: 'run' + setupDecision: 'run', + supportsIdempotentCutoverRetry: true }) expect(calls[0]?.params).toMatchObject({ @@ -93,7 +98,8 @@ describe('createBlankWorkspace', () => { startupCommand: undefined, createdWithAgentId: undefined, comment: undefined, - setupDecision: 'inherit' + setupDecision: 'inherit', + supportsIdempotentCutoverRetry: true }) expect(result).toEqual({ worktreeId: 'wt-3', name: 'octopus-2' }) @@ -118,7 +124,8 @@ describe('createBlankWorkspace', () => { startupCommand: undefined, createdWithAgentId: undefined, comment: undefined, - setupDecision: 'inherit' + setupDecision: 'inherit', + supportsIdempotentCutoverRetry: true }) expect(result).toEqual({ worktreeId: 'wt-4', name: 'octopus-2' }) @@ -136,7 +143,8 @@ describe('createBlankWorkspace', () => { startupCommand: undefined, createdWithAgentId: undefined, comment: undefined, - setupDecision: 'skip' + setupDecision: 'skip', + supportsIdempotentCutoverRetry: true }) expect(result).toEqual({ error: 'SSH connection is not available' }) diff --git a/mobile/src/tasks/blank-workspace-create.ts b/mobile/src/tasks/blank-workspace-create.ts index 014af558198..09a2a1b611f 100644 --- a/mobile/src/tasks/blank-workspace-create.ts +++ b/mobile/src/tasks/blank-workspace-create.ts @@ -14,10 +14,12 @@ export async function createBlankWorkspace(args: { createdWithAgentId: TuiAgent | undefined comment: string | undefined setupDecision: WorkspaceCreateSetupDecision + supportsIdempotentCutoverRetry: boolean | Promise }): Promise { return createWorktreeWithNameRetry({ client: args.client, baseName: args.baseName, + supportsIdempotentCutoverRetry: args.supportsIdempotentCutoverRetry, buildParams: (name) => { const params: Record = { repo: `id:${args.repoId}`, diff --git a/mobile/src/tasks/source-workspace-create.test.ts b/mobile/src/tasks/source-workspace-create.test.ts index d23e0c66317..592375a7735 100644 --- a/mobile/src/tasks/source-workspace-create.test.ts +++ b/mobile/src/tasks/source-workspace-create.test.ts @@ -30,7 +30,8 @@ const baseArgs = { setupDecision: 'inherit' as const, agent, workspaceName: undefined, - note: undefined + note: undefined, + supportsIdempotentCutoverRetry: true } describe('createWorkspaceFromComposerSource', () => { diff --git a/mobile/src/tasks/source-workspace-create.ts b/mobile/src/tasks/source-workspace-create.ts index d16cc174142..8daeac0e04c 100644 --- a/mobile/src/tasks/source-workspace-create.ts +++ b/mobile/src/tasks/source-workspace-create.ts @@ -30,6 +30,7 @@ export type CreateWorkspaceFromComposerArgs = { workspaceName: string | undefined note: string | undefined nameIsAutoManaged?: boolean + supportsIdempotentCutoverRetry: boolean | Promise } export async function createWorkspaceFromComposerSource( @@ -92,6 +93,7 @@ async function createWorkItemWorkspace(args: { workspaceName: string | undefined note: string | undefined nameIsAutoManaged?: boolean + supportsIdempotentCutoverRetry: boolean | Promise }): Promise { const { client, selection, targetRepoId, setupDecision, agent, workspaceName, note } = args const item = selection.item @@ -136,6 +138,7 @@ async function createWorkItemWorkspace(args: { return createWorktreeWithNameRetry({ client, baseName, + supportsIdempotentCutoverRetry: args.supportsIdempotentCutoverRetry, buildParams: (name) => ({ ...params, name }) }) } @@ -148,6 +151,7 @@ async function createBranchWorkspace(args: { agent: WorkspaceCreateAgentBundle workspaceName: string | undefined note: string | undefined + supportsIdempotentCutoverRetry: boolean | Promise }): Promise { const { client, selection, targetRepoId, setupDecision, agent, workspaceName, note } = args const createdWithAgentId = agent.choice === 'blank' ? undefined : agent.choice @@ -173,6 +177,7 @@ async function createBranchWorkspace(args: { return createWorktreeWithNameRetry({ client, baseName, + supportsIdempotentCutoverRetry: args.supportsIdempotentCutoverRetry, maxAttempts: 1, buildParams: (name) => applyCommon({ @@ -195,6 +200,7 @@ async function createBranchWorkspace(args: { return createWorktreeWithNameRetry({ client, baseName, + supportsIdempotentCutoverRetry: args.supportsIdempotentCutoverRetry, buildParams: (candidate) => { const params: Record = { repo: `id:${targetRepoId}`, @@ -219,6 +225,7 @@ async function createNewBranchWorkspace(args: { agent: WorkspaceCreateAgentBundle workspaceName: string | undefined note: string | undefined + supportsIdempotentCutoverRetry: boolean | Promise }): Promise { const { client, selection, targetRepoId, setupDecision, agent, note } = args const createdWithAgentId = agent.choice === 'blank' ? undefined : agent.choice @@ -230,6 +237,7 @@ async function createNewBranchWorkspace(args: { return createWorktreeWithNameRetry({ client, baseName: selection.branchName, + supportsIdempotentCutoverRetry: args.supportsIdempotentCutoverRetry, buildParams: (candidate) => { const params: Record = { repo: `id:${targetRepoId}`, diff --git a/mobile/src/tasks/worktree-create-capability.test.ts b/mobile/src/tasks/worktree-create-capability.test.ts new file mode 100644 index 00000000000..cd12cdbefa2 --- /dev/null +++ b/mobile/src/tasks/worktree-create-capability.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from 'vitest' +import type { RpcClient } from '../transport/rpc-client' +import { LogicalClientCutoverError } from '../transport/stable-logical-rpc-client' +import { readNewWorktreeRuntimeCapabilities } from './worktree-create-capability' + +function statusClient(outcomes: Array<'cutover' | 'error' | string[]>): RpcClient { + let call = 0 + return { + sendRequest: async () => { + const outcome = outcomes[Math.min(call, outcomes.length - 1)]! + call += 1 + if (outcome === 'cutover') { + throw new LogicalClientCutoverError() + } + if (outcome === 'error') { + throw new Error('offline') + } + return { + id: '1', + ok: true, + result: { capabilities: outcome }, + _meta: { runtimeId: 'r' } + } + } + } as unknown as RpcClient +} + +describe('readNewWorktreeRuntimeCapabilities', () => { + it('reads task and idempotent-create support from status.get', async () => { + await expect( + readNewWorktreeRuntimeCapabilities( + statusClient([['mobile.tasks.v1', 'worktree.create-idempotency.v1']]) + ) + ).resolves.toEqual({ + tasksSupported: true, + idempotentWorktreeCreateSupported: true + }) + }) + + it('retries the safe status probe after a connection cutover', async () => { + await expect( + readNewWorktreeRuntimeCapabilities( + statusClient(['cutover', ['worktree.create-idempotency.v1']]) + ) + ).resolves.toEqual({ + tasksSupported: false, + idempotentWorktreeCreateSupported: true + }) + }) + + it('fails closed when capability detection is unavailable', async () => { + await expect(readNewWorktreeRuntimeCapabilities(statusClient(['error']))).resolves.toEqual({ + tasksSupported: false, + idempotentWorktreeCreateSupported: false + }) + }) +}) diff --git a/mobile/src/tasks/worktree-create-capability.ts b/mobile/src/tasks/worktree-create-capability.ts new file mode 100644 index 00000000000..20500c10a7d --- /dev/null +++ b/mobile/src/tasks/worktree-create-capability.ts @@ -0,0 +1,104 @@ +import { useCallback, useEffect, useRef, useState } from 'react' +import type { RpcClient } from '../transport/rpc-client' +import { LogicalClientCutoverError } from '../transport/stable-logical-rpc-client' +import type { RpcSuccess } from '../transport/types' +import { MOBILE_TASKS_CAPABILITY } from './mobile-tasks-capability' + +// Why: older hosts strip worktree.create's clientMutationId, so mobile must not +// replay an ambiguous create unless the host advertises idempotency support. +// Mirrors WORKTREE_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY in the shared protocol. +export const MOBILE_WORKTREE_CREATE_IDEMPOTENCY_CAPABILITY = 'worktree.create-idempotency.v1' + +const STATUS_CUTOVER_MAX_RETRIES = 5 + +export type NewWorktreeRuntimeCapabilities = { + tasksSupported: boolean + idempotentWorktreeCreateSupported: boolean +} + +const UNSUPPORTED_CAPABILITIES: NewWorktreeRuntimeCapabilities = { + tasksSupported: false, + idempotentWorktreeCreateSupported: false +} + +// Why: status.get is safe to replay and must settle before create, independently +// of slower provider probes, so ambiguous cutover retries are gated correctly. +export async function readNewWorktreeRuntimeCapabilities( + client: RpcClient +): Promise { + for (let migrationRetry = 0; ; migrationRetry += 1) { + try { + const response = await client.sendRequest('status.get') + if (!response.ok) { + return UNSUPPORTED_CAPABILITIES + } + const capabilities = + ((response as RpcSuccess).result as { capabilities?: string[] }).capabilities ?? [] + return { + tasksSupported: capabilities.includes(MOBILE_TASKS_CAPABILITY), + idempotentWorktreeCreateSupported: capabilities.includes( + MOBILE_WORKTREE_CREATE_IDEMPOTENCY_CAPABILITY + ) + } + } catch (error) { + if (!isLogicalClientCutoverError(error) || migrationRetry >= STATUS_CUTOVER_MAX_RETRIES) { + return UNSUPPORTED_CAPABILITIES + } + } + } +} + +export function useNewWorktreeRuntimeCapabilities( + client: RpcClient | null, + enabled: boolean +): { + tasksSupported: boolean + getWorktreeCreateCutoverSupport: () => Promise +} { + const [tasksSupported, setTasksSupported] = useState(false) + const capabilityProbeRef = useRef<{ + client: RpcClient | null + promise: Promise + } | null>(null) + const getCapabilities = useCallback((): Promise => { + if (!capabilityProbeRef.current || capabilityProbeRef.current.client !== client) { + // Why: a queued tap can reach Create before passive effects run; lazily + // starting one shared probe keeps that path from failing open. + capabilityProbeRef.current = { + client, + promise: client + ? readNewWorktreeRuntimeCapabilities(client) + : Promise.resolve(UNSUPPORTED_CAPABILITIES) + } + } + return capabilityProbeRef.current.promise + }, [client]) + + useEffect(() => { + if (!enabled || !client) { + return + } + let stale = false + void getCapabilities().then((capabilities) => { + if (!stale) { + setTasksSupported(capabilities.tasksSupported) + } + }) + return () => { + stale = true + } + }, [client, enabled, getCapabilities, setTasksSupported]) + + const getWorktreeCreateCutoverSupport = useCallback( + () => getCapabilities().then((capabilities) => capabilities.idempotentWorktreeCreateSupported), + [getCapabilities] + ) + return { tasksSupported, getWorktreeCreateCutoverSupport } +} + +function isLogicalClientCutoverError(error: unknown): boolean { + return ( + error instanceof LogicalClientCutoverError || + (error instanceof Error && error.message === 'RPC interrupted by connection migration') + ) +} diff --git a/mobile/src/tasks/worktree-create-retry.test.ts b/mobile/src/tasks/worktree-create-retry.test.ts new file mode 100644 index 00000000000..a03be2ad24b --- /dev/null +++ b/mobile/src/tasks/worktree-create-retry.test.ts @@ -0,0 +1,172 @@ +import { describe, expect, it } from 'vitest' +import type { RpcClient } from '../transport/rpc-client' +import { LogicalClientCutoverError } from '../transport/stable-logical-rpc-client' +import { createWorktreeWithNameRetry } from './worktree-create-retry' + +type Attempt = { method: string; params: Record } + +// A client whose per-call outcome is scripted: return an id, a server error +// message, or throw (transport-level rejection, e.g. a connection-migration +// cutover). Records every call so tests can assert on the clientMutationId. +function scriptedClient( + outcomes: Array<{ id: string } | { errorMessage: string } | { throws: unknown }>, + attempts: Attempt[] +): RpcClient { + let call = 0 + return { + sendRequest: async (method: string, params?: unknown) => { + attempts.push({ method, params: (params ?? {}) as Record }) + const outcome = outcomes[Math.min(call, outcomes.length - 1)]! + call += 1 + if ('throws' in outcome) { + throw outcome.throws + } + if ('errorMessage' in outcome) { + return { + id: '1', + ok: false, + error: { code: 'x', message: outcome.errorMessage }, + _meta: { runtimeId: 'r' } + } + } + return { + id: '1', + ok: true, + result: { worktree: { id: outcome.id } }, + _meta: { runtimeId: 'r' } + } + } + } as unknown as RpcClient +} + +describe('createWorktreeWithNameRetry', () => { + it('waits for capability detection before sending a create', async () => { + const attempts: Attempt[] = [] + const client = scriptedClient([{ id: 'wt-ready' }], attempts) + let resolveSupport!: (supported: boolean) => void + const support = new Promise((resolve) => { + resolveSupport = resolve + }) + const pending = createWorktreeWithNameRetry({ + client, + baseName: 'puffin', + buildParams: (name) => ({ repo: 'id:r', name }), + supportsIdempotentCutoverRetry: support, + mintMutationId: () => 'key-ready' + }) + + await Promise.resolve() + expect(attempts).toHaveLength(0) + resolveSupport(true) + + await expect(pending).resolves.toEqual({ worktreeId: 'wt-ready', name: 'puffin' }) + expect(attempts).toHaveLength(1) + expect(attempts[0]!.params.clientMutationId).toBe('key-ready') + }) + + it('stamps a clientMutationId on the create request', async () => { + const attempts: Attempt[] = [] + const client = scriptedClient([{ id: 'wt-1' }], attempts) + const result = await createWorktreeWithNameRetry({ + client, + baseName: 'otter', + buildParams: (name) => ({ repo: 'id:r', name }), + supportsIdempotentCutoverRetry: true, + mintMutationId: () => 'key-1' + }) + expect(result).toEqual({ worktreeId: 'wt-1', name: 'otter' }) + expect(attempts).toHaveLength(1) + expect(attempts[0]!.params).toMatchObject({ name: 'otter', clientMutationId: 'key-1' }) + }) + + it('retries a connection-migration cutover with the SAME key, then succeeds', async () => { + const attempts: Attempt[] = [] + const client = scriptedClient( + [{ throws: new LogicalClientCutoverError() }, { id: 'wt-2' }], + attempts + ) + const result = await createWorktreeWithNameRetry({ + client, + baseName: 'seal', + buildParams: (name) => ({ repo: 'id:r', name }), + supportsIdempotentCutoverRetry: true, + mintMutationId: () => 'key-mig' + }) + expect(result).toEqual({ worktreeId: 'wt-2', name: 'seal' }) + expect(attempts).toHaveLength(2) + // Idempotency: both the interrupted send and the retry carry one key so the + // host dedupes instead of creating a duplicate worktree. + expect(attempts[0]!.params.clientMutationId).toBe('key-mig') + expect(attempts[1]!.params.clientMutationId).toBe('key-mig') + expect(attempts[1]!.params.name).toBe('seal') + }) + + it('gives up after the cutover retry budget and rethrows', async () => { + const attempts: Attempt[] = [] + const client = scriptedClient([{ throws: new LogicalClientCutoverError() }], attempts) + await expect( + createWorktreeWithNameRetry({ + client, + baseName: 'crab', + buildParams: (name) => ({ repo: 'id:r', name }), + supportsIdempotentCutoverRetry: true, + mintMutationId: () => 'key-x' + }) + ).rejects.toBeInstanceOf(LogicalClientCutoverError) + // Initial attempt + 5 retries. + expect(attempts).toHaveLength(6) + }) + + it('does not treat an ordinary transport error as a cutover', async () => { + const attempts: Attempt[] = [] + const client = scriptedClient([{ throws: new Error('Request timed out') }], attempts) + await expect( + createWorktreeWithNameRetry({ + client, + baseName: 'eel', + buildParams: (name) => ({ repo: 'id:r', name }), + supportsIdempotentCutoverRetry: true, + mintMutationId: () => 'key-t' + }) + ).rejects.toThrow('Request timed out') + expect(attempts).toHaveLength(1) + }) + + it('mints a fresh key per candidate when a name collision bumps the suffix', async () => { + const attempts: Attempt[] = [] + const client = scriptedClient( + [{ errorMessage: 'already exists locally' }, { id: 'wt-3' }], + attempts + ) + let n = 0 + const result = await createWorktreeWithNameRetry({ + client, + baseName: 'topic', + buildParams: (name) => ({ repo: 'id:r', name }), + supportsIdempotentCutoverRetry: true, + mintMutationId: () => `key-${(n += 1)}` + }) + expect(result).toEqual({ worktreeId: 'wt-3', name: 'topic-2' }) + expect(attempts).toHaveLength(2) + // A collision is a genuinely different create, so it gets a distinct key. + expect(attempts[0]!.params.clientMutationId).toBe('key-1') + expect(attempts[1]!.params.clientMutationId).toBe('key-2') + expect(attempts[1]!.params.name).toBe('topic-2') + }) + + it('does not replay an ambiguous cutover when the host lacks idempotency support', async () => { + const attempts: Attempt[] = [] + const client = scriptedClient([{ throws: new LogicalClientCutoverError() }], attempts) + await expect( + createWorktreeWithNameRetry({ + client, + baseName: 'ray', + buildParams: (name) => ({ repo: 'id:r', name }), + supportsIdempotentCutoverRetry: false, + mintMutationId: () => 'must-not-be-used' + }) + ).rejects.toBeInstanceOf(LogicalClientCutoverError) + expect(attempts).toHaveLength(1) + expect(attempts[0]!.params.clientMutationId).toBeUndefined() + }) +}) diff --git a/mobile/src/tasks/worktree-create-retry.ts b/mobile/src/tasks/worktree-create-retry.ts index 4eb749f5c23..ed59cc6dd01 100644 --- a/mobile/src/tasks/worktree-create-retry.ts +++ b/mobile/src/tasks/worktree-create-retry.ts @@ -1,5 +1,6 @@ import type { RpcClient } from '../transport/rpc-client' -import type { RpcSuccess } from '../transport/types' +import type { RpcResponse, RpcSuccess } from '../transport/types' +import { LogicalClientCutoverError } from '../transport/stable-logical-rpc-client' import { CLIENT_WORKTREE_CREATE_MAX_ATTEMPTS, getClientWorktreeCreateCandidate, @@ -14,25 +15,53 @@ import { WORKTREE_CREATE_TIMEOUT_MS } from './workspace-create-timeout' // loop in src/renderer/src/store/slices/worktrees.ts. export type WorktreeCreateResult = { worktreeId: string; name: string } | { error: string } +// Why: a create in flight when the mobile transport migrates (relay/direct +// hand-off on shoddy cellular, relay lease rotation) rejects with a cutover error +// even though the host may have completed it. The shared clientMutationId makes a +// retry idempotent, so re-issue on the fresh session a bounded number of times +// instead of surfacing "RPC interrupted by connection migration" with the +// worktree silently created. +const WORKTREE_CREATE_CUTOVER_MAX_RETRIES = 5 + +export type CreateWorktreeWithNameRetryArgs = { + client: RpcClient + baseName: string + buildParams: (name: string) => Record + supportsIdempotentCutoverRetry: boolean | Promise + maxAttempts?: number + // Injected in tests; production mints a fresh idempotency key per candidate. + mintMutationId?: () => string +} + // Creates a worktree, retrying with a numeric suffix on a name-collision error. // buildParams receives the candidate name so callers can assemble source-specific // params (linked issue/PR, base branch, etc.) around it. Callers that can't clear // a collision by re-suffixing (e.g. reusing a fixed existing branch) pass // maxAttempts: 1 to fail fast instead of burning the full retry budget. -export async function createWorktreeWithNameRetry(args: { - client: RpcClient - baseName: string - buildParams: (name: string) => Record - maxAttempts?: number -}): Promise { +export async function createWorktreeWithNameRetry( + args: CreateWorktreeWithNameRetryArgs +): Promise { const { client, baseName, buildParams } = args + // Why: creating before status.get settles would silently disable safe replay + // during the exact slow-network window this path is meant to recover from. + const supportsIdempotentCutoverRetry = await args.supportsIdempotentCutoverRetry const maxAttempts = args.maxAttempts ?? CLIENT_WORKTREE_CREATE_MAX_ATTEMPTS + const mintMutationId = args.mintMutationId ?? defaultWorktreeCreateMutationId let lastError: string | null = null for (let attempt = 0; attempt < maxAttempts; attempt += 1) { const candidateName = getClientWorktreeCreateCandidate(baseName, attempt) - const response = await client.sendRequest('worktree.create', buildParams(candidateName), { - timeoutMs: WORKTREE_CREATE_TIMEOUT_MS - }) + const candidateParams = buildParams(candidateName) + // Why: older hosts strip unknown fields, so only stamp and replay when the + // host advertises idempotency. One key per candidate makes cutover retries + // safe while a name-collision bump remains a genuinely new create. + const params = supportsIdempotentCutoverRetry + ? { ...candidateParams, clientMutationId: mintMutationId() } + : candidateParams + const response = await sendWorktreeCreateResilient( + client, + params, + supportsIdempotentCutoverRetry + ) if (response.ok) { const result = (response as RpcSuccess).result as { worktree: { id: string } } return { worktreeId: result.worktree.id, name: candidateName } @@ -44,3 +73,41 @@ export async function createWorktreeWithNameRetry(args: { } return { error: lastError ?? 'Failed to create workspace' } } + +// Sends worktree.create, re-issuing on a connection-migration cutover only. The +// shared clientMutationId in `params` keeps the retry idempotent host-side. +async function sendWorktreeCreateResilient( + client: RpcClient, + params: Record, + supportsIdempotentCutoverRetry: boolean +): Promise { + for (let migrationRetry = 0; ; migrationRetry += 1) { + try { + return await client.sendRequest('worktree.create', params, { + timeoutMs: WORKTREE_CREATE_TIMEOUT_MS + }) + } catch (error) { + if ( + !supportsIdempotentCutoverRetry || + !isLogicalClientCutoverError(error) || + migrationRetry >= WORKTREE_CREATE_CUTOVER_MAX_RETRIES + ) { + throw error + } + // Why: LogicalClientCutoverError is raised only after migrateTo installs an + // authenticated replacement, so retry immediately instead of adding UI lag. + } + } +} + +function isLogicalClientCutoverError(error: unknown): boolean { + return ( + error instanceof LogicalClientCutoverError || + (error instanceof Error && error.message === 'RPC interrupted by connection migration') + ) +} + +function defaultWorktreeCreateMutationId(): string { + const randomPart = Math.random().toString(36).slice(2, 10) + return `worktree-create:${Date.now().toString(36)}:${randomPart}` +} diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index afb3b74770a..e5021aebb90 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -1418,6 +1418,81 @@ computeWorktreePathMock.mockImplementation( ) ensurePathWithinWorkspaceMock.mockImplementation((targetPath: string) => targetPath) +describe('OrcaRuntimeService.dedupeWorktreeCreate', () => { + it('coalesces concurrent creates that share a clientMutationId', async () => { + const runtime = new OrcaRuntimeService(store) + let calls = 0 + const factory = (): Promise<{ worktree: { id: string } }> => { + calls += 1 + return Promise.resolve({ worktree: { id: 'wt' } }) + } + const [a, b] = await Promise.all([ + runtime.dedupeWorktreeCreate('id:r', 'key-1', factory), + runtime.dedupeWorktreeCreate('id:r', 'key-1', factory) + ]) + expect(calls).toBe(1) + expect(a).toBe(b) + }) + + it('reuses a settled success for a retry whose response was lost in a cutover', async () => { + const runtime = new OrcaRuntimeService(store) + let calls = 0 + const factory = (): Promise<{ worktree: { id: string } }> => { + calls += 1 + return Promise.resolve({ worktree: { id: `wt-${calls}` } }) + } + const first = await runtime.dedupeWorktreeCreate('id:r', 'key-1', factory) + const retried = await runtime.dedupeWorktreeCreate('id:r', 'key-1', factory) + expect(calls).toBe(1) + expect(retried).toEqual(first) + }) + + it('expires settled successes after the reconnect window', async () => { + vi.useFakeTimers() + try { + const runtime = new OrcaRuntimeService(store) + let calls = 0 + const factory = (): Promise<{ n: number }> => Promise.resolve({ n: (calls += 1) }) + await runtime.dedupeWorktreeCreate('id:r', 'key-1', factory) + await vi.advanceTimersByTimeAsync(59_999) + await runtime.dedupeWorktreeCreate('id:r', 'key-1', factory) + expect(calls).toBe(1) + + await vi.advanceTimersByTimeAsync(1) + await runtime.dedupeWorktreeCreate('id:r', 'key-1', factory) + expect(calls).toBe(2) + } finally { + vi.useRealTimers() + } + }) + + it('drops a failed create so a genuine retry starts fresh', async () => { + const runtime = new OrcaRuntimeService(store) + let calls = 0 + const factory = (): Promise => { + calls += 1 + return Promise.reject(new Error(`boom-${calls}`)) + } + await expect(runtime.dedupeWorktreeCreate('id:r', 'key-1', factory)).rejects.toThrow('boom-1') + await expect(runtime.dedupeWorktreeCreate('id:r', 'key-1', factory)).rejects.toThrow('boom-2') + expect(calls).toBe(2) + }) + + it('never dedupes across repos or when no clientMutationId is supplied', async () => { + const runtime = new OrcaRuntimeService(store) + let calls = 0 + const factory = (): Promise<{ n: number }> => { + calls += 1 + return Promise.resolve({ n: calls }) + } + await runtime.dedupeWorktreeCreate('id:a', 'key-1', factory) + await runtime.dedupeWorktreeCreate('id:b', 'key-1', factory) + await runtime.dedupeWorktreeCreate('id:a', undefined, factory) + await runtime.dedupeWorktreeCreate('id:a', undefined, factory) + expect(calls).toBe(4) + }) +}) + describe('OrcaRuntimeService', () => { it('projects runtime-backed settings to paired clients', () => { const runtime = new OrcaRuntimeService({ @@ -1530,6 +1605,7 @@ describe('OrcaRuntimeService', () => { expect(status.capabilities).toContain('terminal.binary-stream.v1') expect(status.capabilities).toContain('workspace-ports.v1') expect(status.capabilities).toContain('mobile.tasks.v1') + expect(status.capabilities).toContain('worktree.create-idempotency.v1') expect(status.capabilities).toContain('project-host-setup.v1') expect(status.capabilities).toContain('linear.issue-attribute-filter.v1') expect(status.capabilities).not.toContain('browser.screencast.v1') diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index d919c077be1..c9fcca82eb3 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -1291,6 +1291,10 @@ function getAgentLaunchPlatformForRepo( // Why: long enough for a phone to reconnect and retry a create whose response // was lost, short enough that an intentional later re-resume forks fresh. const MOBILE_TERMINAL_CREATE_RESULT_TTL_MS = 60_000 +// Why: same idempotency window for worktree.create — a phone whose create was +// interrupted by a connection migration retries with the same clientMutationId +// and reuses the just-created worktree instead of spawning a duplicate. +const WORKTREE_CREATE_RESULT_TTL_MS = 60_000 const FOREGROUND_AGENT_WRAPPER_RETRY_INTERVAL_MS = 150 const FOREGROUND_AGENT_WRAPPER_RETRY_TIMEOUT_MS = 6_500 const BRACKETED_PASTE_BEGIN = '\x1b[200~' @@ -2199,6 +2203,10 @@ export class OrcaRuntimeService { string, Promise >() + // Why: idempotency map for worktree.create — a create interrupted by a mobile + // connection migration is retried with the same clientMutationId and returns + // the in-flight (or just-finished) operation instead of a duplicate worktree. + private worktreeCreateByMutationId = new Map>() // Why: a mobile create waits for the renderer to publish the new tab's surface // via graph-sync, but a throttled/hidden renderer can park that past the surface // timeout and the create would then destroy the live PTY (#7587). This lets the @@ -18804,6 +18812,38 @@ export class OrcaRuntimeService { }) } + // Why: dedupes a worktree.create whose response was lost when a mobile + // connection migration (relay/direct hand-off on shoddy cellular) rejected the + // in-flight request. A retry with the same clientMutationId returns the + // in-flight or just-finished create instead of a duplicate worktree; failures + // drop immediately so a genuine retry starts fresh, and successes linger + // briefly so a retry whose response was lost in the cutover still reconciles. + dedupeWorktreeCreate( + repoSelector: string, + clientMutationId: string | undefined, + run: () => Promise + ): Promise { + if (!clientMutationId) { + return run() + } + const key = `${repoSelector}\0${clientMutationId}` + const inflight = this.worktreeCreateByMutationId.get(key) + if (inflight) { + return inflight as Promise + } + const created = run() + this.worktreeCreateByMutationId.set(key, created) + const drop = (): void => { + if (this.worktreeCreateByMutationId.get(key) === created) { + this.worktreeCreateByMutationId.delete(key) + } + } + void created.then(() => { + setTimeout(drop, WORKTREE_CREATE_RESULT_TTL_MS).unref?.() + }, drop) + return created + } + async createMobileSessionTerminal( worktreeSelector: string, opts: { diff --git a/src/main/runtime/remote-runtime-request-connection.integration.test.ts b/src/main/runtime/remote-runtime-request-connection.integration.test.ts index 2715883a426..41b579b07a4 100644 --- a/src/main/runtime/remote-runtime-request-connection.integration.test.ts +++ b/src/main/runtime/remote-runtime-request-connection.integration.test.ts @@ -19,6 +19,11 @@ import { REMOTE_RUNTIME_SHARED_CONTROL_CAPABILITY } from '../../shared/protocol- const REMOTE_RUNTIME_TEST_TIMEOUT_MS = 15_000 const REMOTE_RUNTIME_REQUEST_TIMEOUT_MS = 5_000 +// worktree.create routes through the runtime's clientMutationId idempotency +// wrapper; these stubs run the create straight through (no dedupe). +const passthroughDedupe = (_repo: string, _id: string | undefined, run: () => Promise) => + run() + describe('remote runtime request connection integration', () => { it( 'fetches repos through the real E2EE WebSocket runtime', @@ -145,6 +150,7 @@ describe('remote runtime request connection integration', () => { source: 'git', worktrees }), + dedupeWorktreeCreate: passthroughDedupe, createManagedWorktree: ({ name }: { name?: string }) => { const worktree = { id: `repo-1::${name || 'created'}`, @@ -370,6 +376,7 @@ describe('remote runtime request connection integration', () => { source: 'git', worktrees }), + dedupeWorktreeCreate: passthroughDedupe, createManagedWorktree: ({ name }: { name?: string }) => { const worktree = { id: `repo-1::${name || 'created'}`, diff --git a/src/main/runtime/rpc/methods/worktree-schemas.ts b/src/main/runtime/rpc/methods/worktree-schemas.ts index 96f2813576e..e271432e5f3 100644 --- a/src/main/runtime/rpc/methods/worktree-schemas.ts +++ b/src/main/runtime/rpc/methods/worktree-schemas.ts @@ -143,6 +143,9 @@ export const WorktreeCreate = z .unknown() .transform((value) => (isTuiAgent(value) ? value : undefined)) .optional(), + // Why: mobile retries a create interrupted by a connection migration with the + // same key so the host dedupes instead of spawning a duplicate worktree. + clientMutationId: z.string().min(1).max(128).optional(), automationProvenanceRequest: AutomationWorkspaceProvenanceRequest.optional() }) .superRefine((params, ctx) => { diff --git a/src/main/runtime/rpc/methods/worktree.test.ts b/src/main/runtime/rpc/methods/worktree.test.ts index 8bc68319d2d..ef96f0e1688 100644 --- a/src/main/runtime/rpc/methods/worktree.test.ts +++ b/src/main/runtime/rpc/methods/worktree.test.ts @@ -19,10 +19,16 @@ function makeRequest(method: string, params?: unknown): RpcRequest { return { id: 'req-1', authToken: 'tok', method, params } } +// worktree.create routes through the runtime's clientMutationId idempotency +// wrapper; these unit mocks run the create straight through (no dedupe). +const passthroughDedupe = (_repo: string, _id: string | undefined, run: () => Promise) => + run() + describe('worktree RPC methods', () => { it('routes mobile session-only activation without notifying desktop clients', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, activateManagedWorktree: vi .fn() .mockResolvedValue({ repoId: 'repo-1', worktreeId: 'wt-1', activated: true }) @@ -46,6 +52,7 @@ describe('worktree RPC methods', () => { it('forwards the mobile clientKind to the runtime on session-only activation', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, activateManagedWorktree: vi .fn() .mockResolvedValue({ repoId: 'repo-1', worktreeId: 'wt-1', activated: true }) @@ -70,6 +77,7 @@ describe('worktree RPC methods', () => { it('routes dirty-file force to the runtime server', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, removeManagedWorktree: vi.fn().mockResolvedValue({}) } as unknown as OrcaRuntimeService const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) @@ -89,6 +97,7 @@ describe('worktree RPC methods', () => { it('routes create options to the runtime server', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, showRepo: vi.fn().mockResolvedValue(repo), createManagedWorktree: vi.fn().mockResolvedValue({ worktree: { id: 'wt-1' } }) } as unknown as OrcaRuntimeService @@ -158,6 +167,7 @@ describe('worktree RPC methods', () => { const dispatchToken = createAutomationDispatchToken('automation-1', 'run-1') const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, showRepo: vi.fn().mockResolvedValue(repo), showAutomation: vi.fn(() => ({ id: 'automation-1', @@ -246,6 +256,7 @@ describe('worktree RPC methods', () => { } const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, showRepo: vi.fn().mockResolvedValue(runtimeLocalRepo), showAutomation: vi.fn(() => ({ id: 'automation-runtime', @@ -303,6 +314,7 @@ describe('worktree RPC methods', () => { const dispatchToken = createAutomationDispatchToken('automation-edited', 'run-edited') const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, showRepo: vi.fn().mockResolvedValue(repo), showAutomation: vi.fn(() => ({ id: 'automation-edited', @@ -366,6 +378,7 @@ describe('worktree RPC methods', () => { const dispatchToken = createAutomationDispatchToken('automation-retry', 'run-retry') const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, showRepo: vi.fn().mockResolvedValue(repo), showAutomation: vi.fn(() => ({ id: 'automation-retry', @@ -429,6 +442,7 @@ describe('worktree RPC methods', () => { it('rejects forged automation provenance requests on worktree creation', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, showRepo: vi.fn().mockResolvedValue(repo), showAutomation: vi.fn(() => ({ id: 'automation-1', @@ -471,6 +485,7 @@ describe('worktree RPC methods', () => { it('forwards startup command and env to runtime worktree creation', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, showRepo: vi.fn().mockResolvedValue(repo), createManagedWorktree: vi.fn().mockResolvedValue({ worktree: { id: 'wt-1' }, @@ -524,6 +539,7 @@ describe('worktree RPC methods', () => { it('drops invalid startup launch config env at the runtime RPC boundary', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, showRepo: vi.fn().mockResolvedValue(repo), createManagedWorktree: vi.fn().mockResolvedValue({ worktree: { id: 'wt-1' } }) } as unknown as OrcaRuntimeService @@ -558,6 +574,7 @@ describe('worktree RPC methods', () => { it('forwards task startup drafts to runtime worktree creation', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, showRepo: vi.fn().mockResolvedValue(repo), createManagedWorktree: vi.fn().mockResolvedValue({ worktree: { id: 'wt-1' } }) } as unknown as OrcaRuntimeService @@ -588,6 +605,7 @@ describe('worktree RPC methods', () => { it('routes create-base prefetches to the runtime server', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, prefetchManagedWorktreeCreateBase: vi.fn().mockResolvedValue(undefined) } as unknown as OrcaRuntimeService const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) @@ -609,6 +627,7 @@ describe('worktree RPC methods', () => { it('maps unknown telemetry sources to the runtime default instead of rejecting create', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, showRepo: vi.fn().mockResolvedValue(repo), createManagedWorktree: vi.fn().mockResolvedValue({ worktree: { id: 'wt-1' } }) } as unknown as OrcaRuntimeService @@ -635,6 +654,7 @@ describe('worktree RPC methods', () => { it('rejects worktree.create when both parent and no-parent are supplied', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, createManagedWorktree: vi.fn() } as unknown as OrcaRuntimeService const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) @@ -656,6 +676,7 @@ describe('worktree RPC methods', () => { it('passes explicit repo selectors to PR base resolution and preserves start-point fields', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, resolveManagedPrBase: vi.fn().mockResolvedValue({ baseBranch: 'abc123', headSha: 'abc123', @@ -694,6 +715,7 @@ describe('worktree RPC methods', () => { it('passes explicit repo selectors to MR base resolution', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, resolveManagedMrBase: vi.fn().mockResolvedValue({ baseBranch: 'origin/mr-head' }) } as unknown as OrcaRuntimeService const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) @@ -719,6 +741,7 @@ describe('worktree RPC methods', () => { it('forwards Linear metadata through worktree.set', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, updateManagedWorktreeMeta: vi.fn().mockResolvedValue({ id: 'wt-1' }) } as unknown as OrcaRuntimeService const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) @@ -746,6 +769,7 @@ describe('worktree RPC methods', () => { it('forwards push target clears through worktree.set', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, updateManagedWorktreeMeta: vi.fn().mockResolvedValue({ id: 'wt-1' }) } as unknown as OrcaRuntimeService const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) @@ -771,6 +795,7 @@ describe('worktree RPC methods', () => { it('rejects worktree.set when both parent and no-parent are supplied', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, updateManagedWorktreeMeta: vi.fn() } as unknown as OrcaRuntimeService const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) @@ -802,6 +827,7 @@ describe('worktree RPC methods', () => { } const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, listWorktreeLineage: vi.fn().mockResolvedValue(lineage), listWorkspaceLineage: vi.fn().mockResolvedValue({}) } as unknown as OrcaRuntimeService @@ -817,6 +843,7 @@ describe('worktree RPC methods', () => { it('persists smart sort order on the runtime server', async () => { const runtime = { getRuntimeId: () => 'test-runtime', + dedupeWorktreeCreate: passthroughDedupe, persistManagedWorktreeSortOrder: vi.fn().mockReturnValue({ updated: 2 }) } as unknown as OrcaRuntimeService const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) diff --git a/src/main/runtime/rpc/methods/worktree.ts b/src/main/runtime/rpc/methods/worktree.ts index 8132a96e3f8..4d3382f635d 100644 --- a/src/main/runtime/rpc/methods/worktree.ts +++ b/src/main/runtime/rpc/methods/worktree.ts @@ -70,79 +70,85 @@ export const WORKTREE_METHODS: RpcMethod[] = [ defineMethod({ name: 'worktree.create', params: WorktreeCreate, - handler: async (params, { runtime }) => { - const repo = await runtime.showRepo(params.repo) - const automationProvenance = resolveAutomationWorkspaceProvenance({ - authority: runtime, - repoSelector: params.repo, - repo, - request: params.automationProvenanceRequest - }) - // Why: provenance tokens are reserved before creation so retries can recover, - // but failed create attempts must release the reservation for a safe retry. - try { - const result = await runtime.createManagedWorktree({ + handler: async (params, { runtime }) => + // Why: a mobile create interrupted by a connection migration is retried with + // the same clientMutationId; dedupe so the host returns the in-flight/created + // worktree instead of spawning a duplicate. No key (desktop/CLI) runs plainly. + runtime.dedupeWorktreeCreate(params.repo, params.clientMutationId, async () => { + const repo = await runtime.showRepo(params.repo) + const automationProvenance = resolveAutomationWorkspaceProvenance({ + authority: runtime, repoSelector: params.repo, - name: params.name ?? '', - baseBranch: params.baseBranch, - compareBaseRef: params.compareBaseRef, - branchNameOverride: params.branchNameOverride, - linkedIssue: params.linkedIssue, - linkedPR: params.linkedPR, - linkedLinearIssue: params.linkedLinearIssue, - linkedLinearIssueWorkspaceId: params.linkedLinearIssueWorkspaceId, - linkedLinearIssueOrganizationUrlKey: params.linkedLinearIssueOrganizationUrlKey, - linkedGitLabMR: params.linkedGitLabMR, - linkedGitLabIssue: params.linkedGitLabIssue, - linkedBitbucketPR: params.linkedBitbucketPR, - linkedAzureDevOpsPR: params.linkedAzureDevOpsPR, - linkedGiteaPR: params.linkedGiteaPR, - comment: params.comment, - displayName: params.displayName, - telemetrySource: params.telemetrySource, - workspaceStatus: params.workspaceStatus, - manualOrder: params.manualOrder, - sparseCheckout: params.sparseCheckout, - pushTarget: params.pushTarget, - runHooks: params.runHooks === true, - activate: params.activate === true, - setupDecision: params.setupDecision, - createdWithAgent: params.createdWithAgent ?? params.startupAgent, - automationProvenance, - startup: params.startupCommand - ? { - command: params.startupCommand, - ...(params.startupEnv ? { env: params.startupEnv } : {}), - ...(params.startupLaunchConfig ? { launchConfig: params.startupLaunchConfig } : {}), - ...(params.startupCommandDelivery - ? { startupCommandDelivery: params.startupCommandDelivery } - : {}) - } - : undefined, - ...(params.startupAgent ? { startupAgent: params.startupAgent } : {}), - ...(params.startupPrompt !== undefined ? { startupPrompt: params.startupPrompt } : {}), - startupDraft: params.startupDraft, - lineage: { - parentWorkspace: params.parentWorkspace, - envParentWorkspace: params.envParentWorkspace, - parentWorktree: params.parentWorktree, - ...(params.cwdParentWorktree ? { cwdParentWorktree: params.cwdParentWorktree } : {}), - noParent: params.noParent === true, - callerTerminalHandle: params.callerTerminalHandle, - orchestrationContext: params.orchestrationContext - } + repo, + request: params.automationProvenanceRequest }) - finishAutomationWorkspaceProvenanceRequest(params.automationProvenanceRequest) - // Why: agent callers need a stable dispatch target without traversing - // terminal-list layout duplicates after creating the worktree. - return params.startupAgent && result.startupTerminal?.handle - ? { ...result, agentTerminalHandle: result.startupTerminal.handle } - : result - } catch (error) { - releaseAutomationWorkspaceProvenanceRequest(params.automationProvenanceRequest) - throw error - } - } + // Why: provenance tokens are reserved before creation so retries can recover, + // but failed create attempts must release the reservation for a safe retry. + try { + const result = await runtime.createManagedWorktree({ + repoSelector: params.repo, + name: params.name ?? '', + baseBranch: params.baseBranch, + compareBaseRef: params.compareBaseRef, + branchNameOverride: params.branchNameOverride, + linkedIssue: params.linkedIssue, + linkedPR: params.linkedPR, + linkedLinearIssue: params.linkedLinearIssue, + linkedLinearIssueWorkspaceId: params.linkedLinearIssueWorkspaceId, + linkedLinearIssueOrganizationUrlKey: params.linkedLinearIssueOrganizationUrlKey, + linkedGitLabMR: params.linkedGitLabMR, + linkedGitLabIssue: params.linkedGitLabIssue, + linkedBitbucketPR: params.linkedBitbucketPR, + linkedAzureDevOpsPR: params.linkedAzureDevOpsPR, + linkedGiteaPR: params.linkedGiteaPR, + comment: params.comment, + displayName: params.displayName, + telemetrySource: params.telemetrySource, + workspaceStatus: params.workspaceStatus, + manualOrder: params.manualOrder, + sparseCheckout: params.sparseCheckout, + pushTarget: params.pushTarget, + runHooks: params.runHooks === true, + activate: params.activate === true, + setupDecision: params.setupDecision, + createdWithAgent: params.createdWithAgent ?? params.startupAgent, + automationProvenance, + startup: params.startupCommand + ? { + command: params.startupCommand, + ...(params.startupEnv ? { env: params.startupEnv } : {}), + ...(params.startupLaunchConfig + ? { launchConfig: params.startupLaunchConfig } + : {}), + ...(params.startupCommandDelivery + ? { startupCommandDelivery: params.startupCommandDelivery } + : {}) + } + : undefined, + ...(params.startupAgent ? { startupAgent: params.startupAgent } : {}), + ...(params.startupPrompt !== undefined ? { startupPrompt: params.startupPrompt } : {}), + startupDraft: params.startupDraft, + lineage: { + parentWorkspace: params.parentWorkspace, + envParentWorkspace: params.envParentWorkspace, + parentWorktree: params.parentWorktree, + ...(params.cwdParentWorktree ? { cwdParentWorktree: params.cwdParentWorktree } : {}), + noParent: params.noParent === true, + callerTerminalHandle: params.callerTerminalHandle, + orchestrationContext: params.orchestrationContext + } + }) + finishAutomationWorkspaceProvenanceRequest(params.automationProvenanceRequest) + // Why: agent callers need a stable dispatch target without traversing + // terminal-list layout duplicates after creating the worktree. + return params.startupAgent && result.startupTerminal?.handle + ? { ...result, agentTerminalHandle: result.startupTerminal.handle } + : result + } catch (error) { + releaseAutomationWorkspaceProvenanceRequest(params.automationProvenanceRequest) + throw error + } + }) }), defineMethod({ name: 'worktree.prefetchCreateBase', diff --git a/src/shared/protocol-version.ts b/src/shared/protocol-version.ts index f6242c93e99..a1fc0ae817f 100644 --- a/src/shared/protocol-version.ts +++ b/src/shared/protocol-version.ts @@ -44,6 +44,10 @@ export const BROWSER_CERTIFICATE_TRUST_RUNTIME_CAPABILITY = 'browser.certificate // floor-taking input. Mobile must not forward replies unless advertised. export const TERMINAL_QUERY_REPLY_INPUT_RUNTIME_CAPABILITY = 'terminal.query-reply-input.v1' as const +// Why: older hosts strip worktree.create's clientMutationId, so mobile must only +// replay ambiguous cutovers when the host advertises idempotent create support. +export const WORKTREE_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY = + 'worktree.create-idempotency.v1' as const export const RUNTIME_CAPABILITIES = [ 'runtime.status.compat.v1', @@ -60,7 +64,8 @@ export const RUNTIME_CAPABILITIES = [ FOLDER_WORKSPACE_PATH_STATUS_RUNTIME_CAPABILITY, LINEAR_ISSUE_ATTRIBUTE_FILTER_RUNTIME_CAPABILITY, AI_VAULT_RUNTIME_CAPABILITY, - TERMINAL_QUERY_REPLY_INPUT_RUNTIME_CAPABILITY + TERMINAL_QUERY_REPLY_INPUT_RUNTIME_CAPABILITY, + WORKTREE_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY ] as const export type RuntimeCapability = (typeof RUNTIME_CAPABILITIES)[number] | (string & {}) From ba25e4306c77fd4c2b8181d1d1d86c54e4f72e80 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:59:38 -0700 Subject: [PATCH 011/137] Replace assistant-prose heuristic with explicit turn lifecycle markers (#9121) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Replace assistant-prose heuristic with explicit turn lifecycle markers Extract provider-authored turn boundaries (completion, interruption) directly from Claude/Codex transcripts so the chat view knows when work ends without guessing from message presence. Reconciles live hook state with transcript lifecycle: when a terminal boundary lands, it settles a dropped Stop hook instead of letting prose mislead the UI into showing 'working' after done. * fix(review): cover Claude terminal stop_reasons and RPC lifecycle frames Treat max_tokens/stop_sequence/refusal as completed markers so capable hosts do not stay working after a dropped Stop, and assert lifecycle payloads on runtime subscribe/read frames plus mid-turn non-terminal stop_reason cases. * test(native-chat): clarify that lifecycle field is optional Add type assertion and comment documenting that lifecycle field is optional and can be omitted in truncation-gating test fixtures. * fix(native-chat): settle status on interruption despite working subagent When Claude's turn is explicitly interrupted, the session should show ready immediately — even if background subagents are still running. Add an interruption check before consulting the hook's working-subagents flag so interruptions take precedence. Also normalize omitted lifecycle timestamps to null instead of leaving them undefined, and add test coverage for both cases. * fix(native-chat): settle loading spinner on explicit turn boundaries Explicit transcript turn-lifecycle markers now fully replace the prose-fallback settlement path. Remove the now-unused `turnLifecycleCapable` flag and wire lifecycle to suppress spinner even when hook status lingers. Refine Claude lifecycle detection to distinguish terminal stops from mid-turn tool_use rows, exclude harness noise from new-generation detection, and apply clock-skew slack over SSH/relay. Serialize PTY sends per line to prevent rapid prompts from gluing before Enter, clearing unsubmitted input on cancel. Update working suppression to detect epoch rollovers so interrupt+next-turn without a ready gap resets the spinner correctly. --- .../native-chat-subscribe-lifecycle.test.ts | 30 +- src/main/ipc/native-chat.ts | 54 +++- .../transcript-incremental-reader.ts | 13 +- .../transcript-interruption-message.test.ts | 52 +++ .../transcript-line-decoders-claude.ts | 22 +- .../transcript-line-decoders-codex.ts | 16 +- src/main/native-chat/transcript-reader.ts | 11 +- .../native-chat/transcript-tail-reader.ts | 39 ++- .../transcript-turn-lifecycle.test.ts | 305 ++++++++++++++++++ .../native-chat/transcript-turn-lifecycle.ts | 169 ++++++++++ .../native-chat/transcript-turn-markers.ts | 26 ++ .../native-chat/transcript-watch-contract.ts | 36 +++ .../native-chat/transcript-watch-engine.ts | 85 ++--- src/main/native-chat/transcript-watch.test.ts | 164 +++++++++- src/main/native-chat/transcript-watch.ts | 20 +- .../runtime/rpc/methods/native-chat.test.ts | 117 ++++++- src/main/runtime/rpc/methods/native-chat.ts | 28 +- src/preload/api-types.ts | 32 +- .../components/native-chat/NativeChatView.tsx | 47 ++- .../native-chat/native-chat-live-status.ts | 120 +++++-- .../native-chat/native-chat-noise.test.ts | 1 + .../native-chat-pending-occurrence.ts | 119 +++++++ .../native-chat/native-chat-pending.test.ts | 27 ++ .../native-chat/native-chat-pending.ts | 62 ++-- .../native-chat/native-chat-pty-send-queue.ts | 191 +++++++++++ .../native-chat-runtime-contract.ts | 60 ++++ .../native-chat-runtime-send.test.ts | 258 ++++++++++----- .../native-chat/native-chat-runtime-send.ts | 162 ++++++---- .../native-chat-session-transport.test.ts | 111 +++++++ .../native-chat-session-transport.ts | 33 +- .../native-chat-working-suppression.test.ts | 31 +- .../native-chat-working-suppression.ts | 33 +- .../use-native-chat-hook-status.ts | 27 ++ .../use-native-chat-live-session.test.ts | 295 ++++++++++++++++- .../use-native-chat-live-session.ts | 36 ++- .../use-native-chat-session-option-command.ts | 49 +-- .../use-native-chat-transcript-lifecycle.ts | 54 ++++ .../AppearanceWindowSidebarSection.tsx | 4 +- src/renderer/src/web/web-preload-api.test.ts | 82 +++++ src/renderer/src/web/web-preload-api.ts | 38 ++- src/shared/native-chat-types.ts | 16 + 41 files changed, 2700 insertions(+), 375 deletions(-) create mode 100644 src/main/native-chat/transcript-interruption-message.test.ts create mode 100644 src/main/native-chat/transcript-turn-lifecycle.test.ts create mode 100644 src/main/native-chat/transcript-turn-lifecycle.ts create mode 100644 src/main/native-chat/transcript-turn-markers.ts create mode 100644 src/main/native-chat/transcript-watch-contract.ts create mode 100644 src/renderer/src/components/native-chat/native-chat-pty-send-queue.ts create mode 100644 src/renderer/src/components/native-chat/native-chat-runtime-contract.ts create mode 100644 src/renderer/src/components/native-chat/use-native-chat-hook-status.ts create mode 100644 src/renderer/src/components/native-chat/use-native-chat-transcript-lifecycle.ts diff --git a/src/main/ipc/native-chat-subscribe-lifecycle.test.ts b/src/main/ipc/native-chat-subscribe-lifecycle.test.ts index 516f839fca9..683e0271086 100644 --- a/src/main/ipc/native-chat-subscribe-lifecycle.test.ts +++ b/src/main/ipc/native-chat-subscribe-lifecycle.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { NativeChatTurnLifecycle } from '../../shared/native-chat-types' const { handlers, listeners, subscribeTranscript } = vi.hoisted(() => ({ handlers: new Map unknown>(), @@ -111,7 +112,8 @@ type InitialSnapshotCallback = ( messages: unknown[], hasMore: boolean, beforeOffset: number, - error?: string + error?: string, + lifecycle?: NativeChatTurnLifecycle ) => void // The onInitialSnapshot callback the handler passed into the Nth subscribeTranscript @@ -275,7 +277,31 @@ describe('nativeChat subscribe lifecycle', () => { expect(renderer.sender.send).toHaveBeenCalledWith('nativeChat:appended', { subscriptionId: 'drain-clean', - frame: { type: 'snapshot', messages: [], hasMore: false } + frame: { + type: 'snapshot', + messages: [], + hasMore: false + } + }) + }) + + it('forwards replayable lifecycle on the initial snapshot', () => { + const pending = deferredSubscription() + subscribeTranscript.mockReturnValueOnce(pending.promise) + const renderer = createSender(7) + const lifecycle = { state: 'completed', turnId: 'turn-1', timestamp: 42 } as const + + subscribe(renderer.sender, 'lifecycle') + initialSnapshot(0)([], false, 0, undefined, lifecycle) + + expect(renderer.sender.send).toHaveBeenCalledWith('nativeChat:appended', { + subscriptionId: 'lifecycle', + frame: { + type: 'snapshot', + messages: [], + hasMore: false, + lifecycle + } }) }) }) diff --git a/src/main/ipc/native-chat.ts b/src/main/ipc/native-chat.ts index 227918a68f3..6521bef3964 100644 --- a/src/main/ipc/native-chat.ts +++ b/src/main/ipc/native-chat.ts @@ -1,5 +1,9 @@ import { ipcMain, type IpcMainEvent, type WebContents } from 'electron' -import type { AgentType, NativeChatMessage } from '../../shared/native-chat-types' +import type { + AgentType, + NativeChatMessage, + NativeChatTurnLifecycle +} from '../../shared/native-chat-types' import { clearNativeChatTranscriptCache } from '../native-chat/transcript-read-cache' import type { ReadTranscriptResult } from '../native-chat/transcript-reader' import { @@ -53,9 +57,24 @@ export type NativeChatSubscribeArgs = { export type NativeChatAppendedPayload = { subscriptionId: string frame: - | { type: 'snapshot'; messages: NativeChatMessage[]; hasMore: boolean; error?: string } - | { type: 'replacement'; messages: NativeChatMessage[]; hasMore: boolean } - | { type: 'appended'; messages: NativeChatMessage[] } + | { + type: 'snapshot' + messages: NativeChatMessage[] + hasMore: boolean + error?: string + lifecycle?: NativeChatTurnLifecycle + } + | { + type: 'replacement' + messages: NativeChatMessage[] + hasMore: boolean + lifecycle?: NativeChatTurnLifecycle + } + | { + type: 'appended' + messages: NativeChatMessage[] + lifecycle?: NativeChatTurnLifecycle + } } type LiveSubscription = { @@ -151,7 +170,7 @@ async function handleSubscribe(event: IpcMainEvent, args: NativeChatSubscribeArg sessionId, transcriptPath, initialLimit: limit, - onInitialSnapshot: (messages, hasMore, _beforeOffset, error) => { + onInitialSnapshot: (messages, hasMore, _beforeOffset, error, lifecycle) => { if (sender.isDestroyed()) { return } @@ -159,26 +178,41 @@ async function handleSubscribe(event: IpcMainEvent, args: NativeChatSubscribeArg // instead of stranding the view at 'loading' when the read keeps throwing. const payload: NativeChatAppendedPayload = { subscriptionId, - frame: { type: 'snapshot', messages, hasMore, ...(error ? { error } : {}) } + frame: { + type: 'snapshot', + messages, + hasMore, + ...(error ? { error } : {}), + ...(lifecycle ? { lifecycle } : {}) + } } sender.send('nativeChat:appended', payload) }, - onReplace: (messages, hasMore) => { + onReplace: (messages, hasMore, _beforeOffset, lifecycle) => { if (sender.isDestroyed()) { return } sender.send('nativeChat:appended', { subscriptionId, - frame: { type: 'replacement', messages, hasMore } + frame: { + type: 'replacement', + messages, + hasMore, + ...(lifecycle ? { lifecycle } : {}) + } } satisfies NativeChatAppendedPayload) }, - onAppend: (messages) => { + onAppend: (messages, lifecycle) => { if (sender.isDestroyed()) { return } const payload: NativeChatAppendedPayload = { subscriptionId, - frame: { type: 'appended', messages } + frame: { + type: 'appended', + messages, + ...(lifecycle ? { lifecycle } : {}) + } } sender.send('nativeChat:appended', payload) } diff --git a/src/main/native-chat/transcript-incremental-reader.ts b/src/main/native-chat/transcript-incremental-reader.ts index ffb3ad83cae..8ad8cc335d1 100644 --- a/src/main/native-chat/transcript-incremental-reader.ts +++ b/src/main/native-chat/transcript-incremental-reader.ts @@ -1,5 +1,5 @@ import { open, stat } from 'node:fs/promises' -import type { NativeChatMessage } from '../../shared/native-chat-types' +import type { NativeChatMessage, NativeChatTurnLifecycle } from '../../shared/native-chat-types' import { transcriptFallbackId } from './transcript-fallback-id' import { MAX_NATIVE_CHAT_TRANSCRIPT_RECORD_BYTES, @@ -28,7 +28,9 @@ export async function readIncrementalTranscriptMessages( filePath: string, state: IncrementalTranscriptState, decode: NativeChatLineDecoder, - onBatch?: (messages: NativeChatMessage[]) => void + onBatch?: (messages: NativeChatMessage[]) => void, + decodeLifecycle?: (line: string, fallbackId: string) => NativeChatTurnLifecycle | null, + onLifecycle?: (lifecycle: NativeChatTurnLifecycle) => void ): Promise { const end = (await stat(filePath)).size if (end <= state.offset) { @@ -91,7 +93,12 @@ export async function readIncrementalTranscriptMessages( if (!line) { return } - const message = decode(line, transcriptFallbackId(filePath, state.pendingStart)) + const fallbackId = transcriptFallbackId(filePath, state.pendingStart) + const lifecycle = decodeLifecycle?.(line, fallbackId) + if (lifecycle) { + onLifecycle?.(lifecycle) + } + const message = decode(line, fallbackId) if (!message) { return } diff --git a/src/main/native-chat/transcript-interruption-message.test.ts b/src/main/native-chat/transcript-interruption-message.test.ts new file mode 100644 index 00000000000..a365e574482 --- /dev/null +++ b/src/main/native-chat/transcript-interruption-message.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'vitest' +import { + NATIVE_CHAT_INTERRUPTED_STATUS_TEXT, + type NativeChatMessage +} from '../../shared/native-chat-types' +import { stripNoiseMessages } from '../../shared/native-chat-noise' +import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude' +import { decodeCodexTranscriptLine } from './transcript-line-decoders-codex' + +function expectNormalizedInterruption(message: NativeChatMessage | null): void { + expect(message).toMatchObject({ + role: 'system', + blocks: [{ type: 'text', text: NATIVE_CHAT_INTERRUPTED_STATUS_TEXT }], + source: 'transcript' + }) + expect(stripNoiseMessages(message ? [message] : [])).toEqual([message]) +} + +describe('native chat transcript interruption messages', () => { + it('normalizes Claude interruption boilerplate into one visible status row', () => { + const message = decodeClaudeTranscriptLine( + JSON.stringify({ + type: 'user', + uuid: 'interrupt-row', + interruptedMessageId: 'assistant-request-1', + timestamp: '2026-07-16T23:46:01.000Z', + message: { + role: 'user', + content: [{ type: 'text', text: '[Request interrupted by user]' }] + } + }), + 'fallback' + ) + + expectNormalizedInterruption(message) + expect(message?.id).toBe('interrupt-row') + }) + + it('normalizes Codex turn_aborted into one visible status row', () => { + const message = decodeCodexTranscriptLine( + JSON.stringify({ + type: 'event_msg', + timestamp: '2026-07-16T23:46:01.000Z', + payload: { type: 'turn_aborted', reason: 'interrupted', turn_id: 'turn-2' } + }), + 'fallback' + ) + + expectNormalizedInterruption(message) + expect(message?.id).toBe('fallback') + }) +}) diff --git a/src/main/native-chat/transcript-line-decoders-claude.ts b/src/main/native-chat/transcript-line-decoders-claude.ts index 76c8952580e..2e936874454 100644 --- a/src/main/native-chat/transcript-line-decoders-claude.ts +++ b/src/main/native-chat/transcript-line-decoders-claude.ts @@ -1,6 +1,10 @@ // Claude JSONL line → NativeChatMessage decoder. -import type { NativeChatBlock, NativeChatMessage } from '../../shared/native-chat-types' +import { + NATIVE_CHAT_INTERRUPTED_STATUS_TEXT, + type NativeChatBlock, + type NativeChatMessage +} from '../../shared/native-chat-types' import { asRecord, extractString, @@ -8,6 +12,7 @@ import { timestampMs } from '../ai-vault/session-scanner-values' import { claudeContentBlocks } from './transcript-record-blocks' +import { claudeInterruptedMessageId } from './transcript-turn-markers' export function decodeClaudeTranscriptLine( line: string, @@ -21,6 +26,19 @@ export function decodeClaudeTranscriptLine( if (role !== 'user' && role !== 'assistant') { return null } + const timestamp = parseTimestamp(record.timestamp) + const recordMessageId = extractString(record.uuid) ?? fallbackId + if (claudeInterruptedMessageId(record)) { + // Why: keep Claude's injected boilerplate out of the user-bubble path while + // preserving the interruption as a quiet, replayable conversation status. + return { + id: recordMessageId, + role: 'system', + blocks: [{ type: 'text', text: NATIVE_CHAT_INTERRUPTED_STATUS_TEXT }], + timestamp, + source: 'transcript' + } + } const message = asRecord(record.message) const decodedBlocks = claudeContentBlocks(message?.content) if (decodedBlocks.length === 0) { @@ -42,7 +60,7 @@ export function decodeClaudeTranscriptLine( id: messageId ?? fallbackId, role: claudeMessageRole(role, blocks), blocks, - timestamp: parseTimestamp(record.timestamp), + timestamp, source: 'transcript' } } diff --git a/src/main/native-chat/transcript-line-decoders-codex.ts b/src/main/native-chat/transcript-line-decoders-codex.ts index 64d04fa5ae3..98eeb1cc054 100644 --- a/src/main/native-chat/transcript-line-decoders-codex.ts +++ b/src/main/native-chat/transcript-line-decoders-codex.ts @@ -1,6 +1,10 @@ // Codex JSONL line → NativeChatMessage decoder. -import type { NativeChatBlock, NativeChatMessage } from '../../shared/native-chat-types' +import { + NATIVE_CHAT_INTERRUPTED_STATUS_TEXT, + type NativeChatBlock, + type NativeChatMessage +} from '../../shared/native-chat-types' import { asRecord, extractString, @@ -8,6 +12,7 @@ import { timestampMs } from '../ai-vault/session-scanner-values' import { claudeContentBlocks, toolResultOutput } from './transcript-record-blocks' +import { CODEX_EVENT_TURN_ABORTED } from './transcript-turn-markers' export function decodeCodexTranscriptLine( line: string, @@ -87,6 +92,15 @@ function codexEventMessage( id: string, timestamp: number | null ): NativeChatMessage | null { + if (payload.type === CODEX_EVENT_TURN_ABORTED) { + return { + id, + role: 'system', + blocks: [{ type: 'text', text: NATIVE_CHAT_INTERRUPTED_STATUS_TEXT }], + timestamp, + source: 'transcript' + } + } if (payload.type === 'user_message') { const text = extractString(payload.message) return text diff --git a/src/main/native-chat/transcript-reader.ts b/src/main/native-chat/transcript-reader.ts index 182296bda1d..85cd7357287 100644 --- a/src/main/native-chat/transcript-reader.ts +++ b/src/main/native-chat/transcript-reader.ts @@ -1,5 +1,9 @@ import { createReadStream } from 'node:fs' -import type { AgentType, NativeChatMessage } from '../../shared/native-chat-types' +import type { + AgentType, + NativeChatMessage, + NativeChatTurnLifecycle +} from '../../shared/native-chat-types' import { resolveNativeChatTranscriptAgent } from '../../shared/native-chat-agent-support' import { errorMessage } from '../ai-vault/session-scanner-values' import { resolveSessionFilePath, type ResolveSessionFileOptions } from './session-file-resolver' @@ -11,7 +15,10 @@ import { import { decodeTranscriptStream } from './transcript-stream-lines' export type ReadTranscriptResult = - | { messages: NativeChatMessage[] } + | { + messages: NativeChatMessage[] + lifecycle?: NativeChatTurnLifecycle + } // notFound marks a retry-worthy miss (transcript not flushed to disk yet, // #8401) as opposed to a real parse/IO error callers surface immediately. | { error: string; notFound?: true } diff --git a/src/main/native-chat/transcript-tail-reader.ts b/src/main/native-chat/transcript-tail-reader.ts index 9ac029d5913..2f874e95746 100644 --- a/src/main/native-chat/transcript-tail-reader.ts +++ b/src/main/native-chat/transcript-tail-reader.ts @@ -1,5 +1,9 @@ import { open, stat } from 'node:fs/promises' -import type { AgentType, NativeChatMessage } from '../../shared/native-chat-types' +import type { + AgentType, + NativeChatMessage, + NativeChatTurnLifecycle +} from '../../shared/native-chat-types' import { resolveNativeChatTranscriptAgent } from '../../shared/native-chat-agent-support' import { resolveSessionFilePath, type ResolveSessionFileOptions } from './session-file-resolver' import { @@ -8,6 +12,10 @@ import { decodeGrokTranscriptLine } from './transcript-line-decoders' import { transcriptFallbackId } from './transcript-fallback-id' +import { + nativeChatTurnLifecycleDecoderForAgent, + type NativeChatTurnLifecycleDecoder +} from './transcript-turn-lifecycle' export const MAX_NATIVE_CHAT_TRANSCRIPT_RECORD_BYTES = 2 * 1024 * 1024 const TAIL_CHUNK_BYTES = 64 * 1024 @@ -33,9 +41,11 @@ export async function readNativeChatTranscriptTailFile( limit: number, decode: NativeChatLineDecoder, includeTrailingLine = false, - endOffset?: number + endOffset?: number, + decodeLifecycle?: NativeChatTurnLifecycleDecoder | null ): Promise<{ messages: NativeChatMessage[] + lifecycle?: NativeChatTurnLifecycle consumedTo: number hasMore: boolean beforeOffset: number @@ -48,6 +58,7 @@ export async function readNativeChatTranscriptTailFile( const lineParts: Buffer[] = [] let lineBytes = 0 let lineOversized = false + let lifecycle: NativeChatTurnLifecycle | undefined try { const consumedTo = includeTrailingLine ? end : await findLastCompleteLineEnd(handle, end) if (consumedTo === 0) { @@ -87,6 +98,7 @@ export async function readNativeChatTranscriptTailFile( const selected = limit > 0 ? chronological.slice(Math.max(0, chronological.length - limit)) : [] return { messages: selected.map((entry) => entry.message), + ...(lifecycle ? { lifecycle } : {}), consumedTo, hasMore: limit > 0 && chronological.length > limit, beforeOffset: selected[0]?.offset ?? end @@ -125,7 +137,12 @@ export async function readNativeChatTranscriptTailFile( if (!line) { return } - const message = decode(line, transcriptFallbackId(filePath, lineOffset)) + const fallbackId = transcriptFallbackId(filePath, lineOffset) + // Why: scan the same bounded JSONL window for provider-authored lifecycle + // records so reconnect snapshots can replay completion without guessing + // from the last visible assistant message. + lifecycle ??= decodeLifecycle?.(line, fallbackId) ?? undefined + const message = decode(line, fallbackId) if (message) { messages.push({ message, offset: lineOffset }) } @@ -165,10 +182,16 @@ export async function readNativeChatTranscriptTail( beforeOffset?: number } ): Promise< - | { messages: NativeChatMessage[]; hasMore: boolean; beforeOffset: number } + | { + messages: NativeChatMessage[] + lifecycle?: NativeChatTurnLifecycle + hasMore: boolean + beforeOffset: number + } | { error: string; notFound?: true } > { const decode = nativeChatLineDecoderForAgent(args.agent) + const decodeLifecycle = nativeChatTurnLifecycleDecoderForAgent(args.agent) const filePath = args.filePath ?? (await resolveSessionFilePath(args.agent, args.sessionId, args)) if (!decode) { return { error: 'Transcript unavailable' } @@ -184,10 +207,16 @@ export async function readNativeChatTranscriptTail( args.limit, decode, true, - args.beforeOffset + args.beforeOffset, + decodeLifecycle ) return { messages: result.messages, + // Why: an older pagination page must not rewind the live lifecycle; only + // the current transcript tail can authoritatively describe turn state. + ...(args.beforeOffset === undefined && result.lifecycle + ? { lifecycle: result.lifecycle } + : {}), hasMore: result.hasMore, beforeOffset: result.beforeOffset } diff --git a/src/main/native-chat/transcript-turn-lifecycle.test.ts b/src/main/native-chat/transcript-turn-lifecycle.test.ts new file mode 100644 index 00000000000..e46fcde0020 --- /dev/null +++ b/src/main/native-chat/transcript-turn-lifecycle.test.ts @@ -0,0 +1,305 @@ +import { describe, expect, it } from 'vitest' +import { + decodeClaudeTurnLifecycle, + decodeCodexTurnLifecycle, + nativeChatTurnLifecycleDecoderForAgent +} from './transcript-turn-lifecycle' + +describe('native chat transcript turn lifecycle', () => { + it('exposes a lifecycle decoder only for transcript formats with explicit boundaries', () => { + expect(nativeChatTurnLifecycleDecoderForAgent('claude')).not.toBeNull() + expect(nativeChatTurnLifecycleDecoderForAgent('openclaude')).not.toBeNull() + expect(nativeChatTurnLifecycleDecoderForAgent('codex')).not.toBeNull() + expect(nativeChatTurnLifecycleDecoderForAgent('grok')).toBeNull() + }) + + it('decodes Codex task boundaries with the provider turn id', () => { + expect( + decodeCodexTurnLifecycle( + JSON.stringify({ + timestamp: '2026-07-16T23:40:14.001Z', + type: 'event_msg', + payload: { type: 'task_started', turn_id: 'turn-1' } + }), + 'fallback' + ) + ).toEqual({ + state: 'working', + turnId: 'turn-1', + timestamp: Date.parse('2026-07-16T23:40:14.001Z') + }) + + expect( + decodeCodexTurnLifecycle( + JSON.stringify({ + timestamp: '2026-07-16T23:45:37.608Z', + type: 'event_msg', + payload: { type: 'task_complete', turn_id: 'turn-1' } + }), + 'fallback' + ) + ).toEqual({ + state: 'completed', + turnId: 'turn-1', + timestamp: Date.parse('2026-07-16T23:45:37.608Z') + }) + + expect( + decodeCodexTurnLifecycle( + JSON.stringify({ + timestamp: '2026-07-16T23:46:01.000Z', + type: 'event_msg', + payload: { type: 'turn_aborted', reason: 'interrupted', turn_id: 'turn-2' } + }), + 'fallback' + ) + ).toEqual({ + state: 'interrupted', + turnId: 'turn-2', + timestamp: Date.parse('2026-07-16T23:46:01.000Z') + }) + }) + + it('does not mistake a Codex assistant message for completion', () => { + expect( + decodeCodexTurnLifecycle( + JSON.stringify({ + timestamp: '2026-07-16T23:45:37.472Z', + type: 'event_msg', + payload: { type: 'agent_message', message: 'final-looking prose' } + }), + 'fallback' + ) + ).toBeNull() + }) + + it('uses Claude terminal stop_reasons and excludes tool-result user rows', () => { + for (const stopReason of ['end_turn', 'max_tokens', 'stop_sequence', 'refusal'] as const) { + expect( + decodeClaudeTurnLifecycle( + JSON.stringify({ + type: 'assistant', + uuid: `assistant-${stopReason}`, + timestamp: '2026-07-16T23:45:37.608Z', + message: { role: 'assistant', stop_reason: stopReason, content: [] } + }), + 'fallback' + ) + ).toEqual({ + state: 'completed', + turnId: `assistant-${stopReason}`, + timestamp: Date.parse('2026-07-16T23:45:37.608Z') + }) + } + + expect( + decodeClaudeTurnLifecycle( + JSON.stringify({ + type: 'user', + uuid: 'tool-result-1', + timestamp: '2026-07-16T23:45:38.000Z', + message: { + role: 'user', + content: [{ type: 'tool_result', tool_use_id: 'tool-1', content: 'ok' }] + } + }), + 'fallback' + ) + ).toBeNull() + }) + + it('does not treat Claude mid-turn tool_use assistant rows as completed', () => { + expect( + decodeClaudeTurnLifecycle( + JSON.stringify({ + type: 'assistant', + uuid: 'assistant-tool', + timestamp: '2026-07-16T23:45:37.608Z', + message: { + role: 'assistant', + stop_reason: 'tool_use', + content: [{ type: 'tool_use', id: 't1', name: 'Bash', input: {} }] + } + }), + 'fallback' + ) + ).toBeNull() + }) + + it('treats Claude assistant rows with omitted stop_reason and content as completed', () => { + expect( + decodeClaudeTurnLifecycle( + JSON.stringify({ + type: 'assistant', + uuid: 'assistant-null-stop', + timestamp: '2026-07-16T23:45:37.608Z', + message: { + role: 'assistant', + stop_reason: null, + content: [{ type: 'text', text: 'final answer' }] + } + }), + 'fallback' + ) + ).toEqual({ + state: 'completed', + turnId: 'assistant-null-stop', + timestamp: Date.parse('2026-07-16T23:45:37.608Z') + }) + + expect( + decodeClaudeTurnLifecycle( + JSON.stringify({ + type: 'assistant', + uuid: 'assistant-missing-stop', + timestamp: '2026-07-16T23:45:37.608Z', + message: { + role: 'assistant', + content: [{ type: 'text', text: 'final answer' }] + } + }), + 'fallback' + ) + ).toEqual({ + state: 'completed', + turnId: 'assistant-missing-stop', + timestamp: Date.parse('2026-07-16T23:45:37.608Z') + }) + }) + + it('does not complete a Claude assistant row that omits stop_reason but carries a tool_use block', () => { + // A pre-tool row can hold prose AND a tool_use block; without stop_reason the + // turn is still mid-flight, so it must not settle the spinner before the tool. + for (const stopReason of [null, undefined] as const) { + expect( + decodeClaudeTurnLifecycle( + JSON.stringify({ + type: 'assistant', + uuid: `assistant-pretool-${stopReason}`, + timestamp: '2026-07-16T23:45:37.608Z', + message: { + role: 'assistant', + ...(stopReason === null ? { stop_reason: null } : {}), + content: [ + { type: 'text', text: 'let me check' }, + { type: 'tool_use', id: 't1', name: 'Bash', input: {} } + ] + } + }), + 'fallback' + ) + ).toBeNull() + } + }) + + it('does not complete Claude assistant rows with omitted stop_reason and no content', () => { + expect( + decodeClaudeTurnLifecycle( + JSON.stringify({ + type: 'assistant', + uuid: 'assistant-empty', + timestamp: '2026-07-16T23:45:37.608Z', + message: { role: 'assistant', stop_reason: null, content: [] } + }), + 'fallback' + ) + ).toBeNull() + }) + + it('does not treat harness noise user rows as a new working generation', () => { + expect( + decodeClaudeTurnLifecycle( + JSON.stringify({ + type: 'user', + uuid: 'task-note-1', + timestamp: '2026-07-16T23:46:10.000Z', + message: { + role: 'user', + content: [ + { + type: 'text', + text: 'background task finished' + } + ] + } + }), + 'fallback' + ) + ).toBeNull() + + expect( + decodeClaudeTurnLifecycle( + JSON.stringify({ + type: 'user', + uuid: 'reminder-1', + timestamp: '2026-07-16T23:46:11.000Z', + message: { + role: 'user', + content: [{ type: 'text', text: 'continue' }] + } + }), + 'fallback' + ) + ).toBeNull() + }) + + it('excludes Claude tool-result rows that also carry text sidecars', () => { + expect( + decodeClaudeTurnLifecycle( + JSON.stringify({ + type: 'user', + uuid: 'mixed-tool-result', + timestamp: '2026-07-16T23:45:38.000Z', + message: { + role: 'user', + content: [ + { type: 'tool_result', tool_use_id: 'tool-1', content: 'ok' }, + { type: 'text', text: 'continue' } + ] + } + }), + 'fallback' + ) + ).toBeNull() + }) + + it('treats a real Claude user row as the next working generation', () => { + expect( + decodeClaudeTurnLifecycle( + JSON.stringify({ + type: 'user', + uuid: 'user-2', + timestamp: '2026-07-16T23:46:00.000Z', + message: { role: 'user', content: 'next task' } + }), + 'fallback' + ) + ).toEqual({ + state: 'working', + turnId: 'user-2', + timestamp: Date.parse('2026-07-16T23:46:00.000Z') + }) + }) + + it('treats Claude interruptedMessageId as terminal instead of a user generation', () => { + expect( + decodeClaudeTurnLifecycle( + JSON.stringify({ + type: 'user', + uuid: 'interrupt-row', + interruptedMessageId: 'assistant-request-1', + timestamp: '2026-07-16T23:46:01.000Z', + message: { + role: 'user', + content: [{ type: 'text', text: '[Request interrupted by user]' }] + } + }), + 'fallback' + ) + ).toEqual({ + state: 'interrupted', + turnId: 'assistant-request-1', + timestamp: Date.parse('2026-07-16T23:46:01.000Z') + }) + }) +}) diff --git a/src/main/native-chat/transcript-turn-lifecycle.ts b/src/main/native-chat/transcript-turn-lifecycle.ts new file mode 100644 index 00000000000..b91e4fa7bee --- /dev/null +++ b/src/main/native-chat/transcript-turn-lifecycle.ts @@ -0,0 +1,169 @@ +import type { AgentType, NativeChatTurnLifecycle } from '../../shared/native-chat-types' +import { resolveNativeChatTranscriptAgent } from '../../shared/native-chat-agent-support' +import { isNoiseMessage } from '../../shared/native-chat-noise' +import { + asRecord, + extractString, + parseJsonObject, + timestampMs +} from '../ai-vault/session-scanner-values' +import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude' +import { + claudeInterruptedMessageId, + CODEX_EVENT_TURN_ABORTED, + CODEX_EVENT_TURN_COMPLETE, + CODEX_EVENT_TURN_STARTED +} from './transcript-turn-markers' + +export type NativeChatTurnLifecycleDecoder = ( + line: string, + fallbackId: string +) => NativeChatTurnLifecycle | null + +export function nativeChatTurnLifecycleDecoderForAgent( + agent: AgentType +): NativeChatTurnLifecycleDecoder | null { + const transcriptAgent = resolveNativeChatTranscriptAgent(agent) + if (transcriptAgent === 'codex') { + return decodeCodexTurnLifecycle + } + if (transcriptAgent === 'claude') { + return decodeClaudeTurnLifecycle + } + return null +} + +export function decodeCodexTurnLifecycle( + line: string, + fallbackId: string +): NativeChatTurnLifecycle | null { + const record = parseJsonObject(line) + const payload = asRecord(record?.payload) + if (record?.type !== 'event_msg' || !payload) { + return null + } + if ( + payload.type !== CODEX_EVENT_TURN_STARTED && + payload.type !== CODEX_EVENT_TURN_COMPLETE && + payload.type !== CODEX_EVENT_TURN_ABORTED + ) { + return null + } + const state = + payload.type === CODEX_EVENT_TURN_STARTED + ? 'working' + : payload.type === CODEX_EVENT_TURN_ABORTED + ? 'interrupted' + : 'completed' + return { + state, + turnId: extractString(payload.turn_id) ?? fallbackId, + timestamp: lifecycleTimestamp(record.timestamp) + } +} + +/** Claude stop reasons that end the lead generation (not mid-turn tool_use). */ +const CLAUDE_TERMINAL_STOP_REASONS = new Set(['end_turn', 'max_tokens', 'stop_sequence', 'refusal']) + +function isClaudeTerminalStopReason(value: unknown): boolean { + return typeof value === 'string' && CLAUDE_TERMINAL_STOP_REASONS.has(value) +} + +export function decodeClaudeTurnLifecycle( + line: string, + fallbackId: string +): NativeChatTurnLifecycle | null { + const record = parseJsonObject(line) + if (!record) { + return null + } + const message = asRecord(record.message) + const timestamp = lifecycleTimestamp(record.timestamp) + const interruptedMessageId = claudeInterruptedMessageId(record) + if (interruptedMessageId) { + // Why: Claude stores its interrupt notice as an injected user row; it ends + // the active generation and must not be mistaken for the next user prompt. + return { state: 'interrupted', turnId: interruptedMessageId, timestamp } + } + if (record.type === 'assistant') { + const stopReason = message?.stop_reason + // Why: capable hosts rely on explicit terminals (prose is only a backup when + // the latest lifecycle is not mid-generation). Emit completed for every real + // end marker — including historical/OpenClaude rows that omit stop_reason — + // while tool_use stays non-terminal so mid-turn tool loops keep working. The + // no-stop_reason backup also excludes rows carrying a tool_use block: a + // pre-tool assistant row that omits stop_reason is mid-turn, not done, so it + // must not settle the spinner before the tool runs. + const isTerminal = + isClaudeTerminalStopReason(stopReason) || + (stopReason == null && + assistantHasRenderableContent(message) && + !assistantHasToolUse(message)) + if (isTerminal) { + return { + state: 'completed', + turnId: extractString(record.uuid) ?? extractString(message?.id) ?? fallbackId, + timestamp + } + } + return null + } + if (record.type !== 'user') { + return null + } + const decoded = decodeClaudeTranscriptLine(line, fallbackId) + if (decoded?.role !== 'user' || decoded.blocks.some((block) => block.type === 'tool-result')) { + // Why: Claude can attach text sidecars to tool-result user rows; those are + // continuations of the active turn, not a new user-authored generation. + return null + } + // Why: harness noise (task-notification, system-reminder, …) is user-role in + // the JSONL but not a new generation. Treating it as working would overwrite + // a real terminal marker and re-stick the chat spinner after done/interrupt. + if (isNoiseMessage(decoded)) { + return null + } + return { state: 'working', turnId: decoded.id, timestamp } +} + +function lifecycleTimestamp(value: unknown): number | null { + const parsed = timestampMs(value) + return Number.isFinite(parsed) ? parsed : null +} + +function assistantHasRenderableContent(message: Record | null): boolean { + const content = message?.content + if (typeof content === 'string') { + return content.trim().length > 0 + } + if (!Array.isArray(content)) { + return false + } + return content.some((block) => { + const record = asRecord(block) + if (!record) { + return false + } + if ( + record.type === 'text' && + typeof record.text === 'string' && + record.text.trim().length > 0 + ) { + return true + } + if (record.type === 'thinking' || record.type === 'redacted_thinking') { + return true + } + return false + }) +} + +/** True when an assistant row contains a tool_use block — the turn continues to + * a tool call, so a missing stop_reason must not be read as completion. */ +function assistantHasToolUse(message: Record | null): boolean { + const content = message?.content + if (!Array.isArray(content)) { + return false + } + return content.some((block) => asRecord(block)?.type === 'tool_use') +} diff --git a/src/main/native-chat/transcript-turn-markers.ts b/src/main/native-chat/transcript-turn-markers.ts new file mode 100644 index 00000000000..727bef5d5aa --- /dev/null +++ b/src/main/native-chat/transcript-turn-markers.ts @@ -0,0 +1,26 @@ +// Shared detection of provider-authored turn boundaries. The message decoders +// render a visible status row from these lines; the lifecycle decoders settle +// the chat spinner from the same lines. Keeping the predicates here means the +// two consumers can never disagree about which JSONL line is an interrupt/abort +// or a turn boundary — updating a provider's format touches one place, so a +// rename can't leave a visible "interrupted" row that never settles (or vice +// versa). + +import { extractString } from '../ai-vault/session-scanner-values' + +/** + * The `interruptedMessageId` on a Claude user row when that row is Claude's + * injected interrupt notice rather than a real user prompt. Returns undefined + * for genuine user turns. + */ +export function claudeInterruptedMessageId(record: Record): string | undefined { + if (record.type !== 'user') { + return undefined + } + return extractString(record.interruptedMessageId) ?? undefined +} + +/** Codex `event_msg` payload types that bound a turn's lifecycle. */ +export const CODEX_EVENT_TURN_STARTED = 'task_started' +export const CODEX_EVENT_TURN_COMPLETE = 'task_complete' +export const CODEX_EVENT_TURN_ABORTED = 'turn_aborted' diff --git a/src/main/native-chat/transcript-watch-contract.ts b/src/main/native-chat/transcript-watch-contract.ts new file mode 100644 index 00000000000..2942594b370 --- /dev/null +++ b/src/main/native-chat/transcript-watch-contract.ts @@ -0,0 +1,36 @@ +import type { + AgentType, + NativeChatMessage, + NativeChatTurnLifecycle +} from '../../shared/native-chat-types' +import type { ResolveSessionFileOptions } from './session-file-resolver' + +export type SubscribeNativeChatTranscriptArgs = ResolveSessionFileOptions & { + agent: AgentType + sessionId: string + onAppend: (messages: NativeChatMessage[], lifecycle?: NativeChatTurnLifecycle) => void + onInitialSnapshot?: ( + messages: NativeChatMessage[], + hasMore: boolean, + beforeOffset: number, + /** Set when the initial drain could not deliver a transcript. */ + error?: string, + lifecycle?: NativeChatTurnLifecycle + ) => void + onReplace?: ( + messages: NativeChatMessage[], + hasMore: boolean, + beforeOffset: number, + lifecycle?: NativeChatTurnLifecycle + ) => void + initialLimit?: number + filePath?: string + debounceMs?: number + /** Test-only override for the production resolve-poll backoff. */ + resolvePollIntervalMs?: number +} + +export type NativeChatTranscriptSubscription = { + unsubscribe: () => void + watching: boolean +} diff --git a/src/main/native-chat/transcript-watch-engine.ts b/src/main/native-chat/transcript-watch-engine.ts index d610f5a1536..6ce52de4787 100644 --- a/src/main/native-chat/transcript-watch-engine.ts +++ b/src/main/native-chat/transcript-watch-engine.ts @@ -1,42 +1,18 @@ import { watch, type FSWatcher } from 'node:fs' import { open, stat } from 'node:fs/promises' import { basename, dirname } from 'node:path' -import type { AgentType, NativeChatMessage } from '../../shared/native-chat-types' -import type { ResolveSessionFileOptions } from './session-file-resolver' +import type { NativeChatMessage, NativeChatTurnLifecycle } from '../../shared/native-chat-types' import { readIncrementalTranscriptMessages, resetIncrementalTranscriptState, type IncrementalTranscriptState } from './transcript-incremental-reader' import { readNativeChatTranscriptTailFile } from './transcript-tail-reader' - -export type SubscribeNativeChatTranscriptArgs = ResolveSessionFileOptions & { - agent: AgentType - sessionId: string - onAppend: (messages: NativeChatMessage[]) => void - onInitialSnapshot?: ( - messages: NativeChatMessage[], - hasMore: boolean, - beforeOffset: number, - /** Set when the initial drain could not deliver a transcript; the subscriber - * surfaces it as an error snapshot so a watching client never sticks on - * 'loading'. Empty messages accompany it. */ - error?: string - ) => void - onReplace?: (messages: NativeChatMessage[], hasMore: boolean, beforeOffset: number) => void - initialLimit?: number - filePath?: string - debounceMs?: number - /** Overrides the resolve-poll interval (see subscribeViaResolvePoll) so tests - * don't wait out the production backoff. Production ignores this and backs - * off from 500ms to a 5s cap. */ - resolvePollIntervalMs?: number -} - -export type NativeChatTranscriptSubscription = { - unsubscribe: () => void - watching: boolean -} +import { nativeChatTurnLifecycleDecoderForAgent } from './transcript-turn-lifecycle' +import type { + NativeChatTranscriptSubscription, + SubscribeNativeChatTranscriptArgs +} from './transcript-watch-contract' const DEFAULT_DEBOUNCE_MS = 40 const ROTATION_RETRY_MS = 25 @@ -87,6 +63,7 @@ export async function installTranscriptWatcher( return null } const { onAppend, onInitialSnapshot, onReplace, initialLimit, debounceMs } = args + const decodeLifecycle = nativeChatTurnLifecycleDecoderForAgent(args.agent) const state: IncrementalTranscriptState = { offset: 0, @@ -138,6 +115,7 @@ export async function installTranscriptWatcher( } async function readAndEmitAppends(): Promise { + let lifecycle: NativeChatTurnLifecycle | undefined const remaining = await readIncrementalTranscriptMessages( filePath, state, @@ -146,10 +124,14 @@ export async function installTranscriptWatcher( if (!closed) { onAppend(messages) } + }, + decodeLifecycle ?? undefined, + (nextLifecycle) => { + lifecycle = nextLifecycle } ) - if (!closed && remaining.length > 0) { - onAppend(remaining) + if (!closed && (remaining.length > 0 || lifecycle)) { + onAppend(remaining, lifecycle) } } @@ -176,7 +158,14 @@ export async function installTranscriptWatcher( // Why: 0 is a valid window — an explicit undefined check keeps an empty // snapshot empty instead of falling back to an unbounded incremental read. contentReplaced && !initialDrain && onReplace && initialLimit !== undefined - ? await readNativeChatTranscriptTailFile(filePath, initialLimit, decode) + ? await readNativeChatTranscriptTailFile( + filePath, + initialLimit, + decode, + false, + undefined, + decodeLifecycle + ) : null if (closed) { return @@ -187,7 +176,8 @@ export async function installTranscriptWatcher( onReplace( replacementSnapshot.messages, replacementSnapshot.hasMore, - replacementSnapshot.beforeOffset + replacementSnapshot.beforeOffset, + replacementSnapshot.lifecycle ) await readAndEmitAppends() watchedBoundary = await boundaryFingerprint(filePath, state.offset) @@ -197,7 +187,14 @@ export async function installTranscriptWatcher( const initialSnapshot = initialDrain && onInitialSnapshot && initialLimit !== undefined - ? await readNativeChatTranscriptTailFile(filePath, initialLimit, decode) + ? await readNativeChatTranscriptTailFile( + filePath, + initialLimit, + decode, + false, + undefined, + decodeLifecycle + ) : null if (closed) { return @@ -210,12 +207,24 @@ export async function installTranscriptWatcher( onInitialSnapshot( initialSnapshot.messages, initialSnapshot.hasMore, - initialSnapshot.beforeOffset + initialSnapshot.beforeOffset, + undefined, + initialSnapshot.lifecycle ) await readAndEmitAppends() } else { - const messages = await readIncrementalTranscriptMessages(filePath, state, decode) - onInitialSnapshot(messages, false, 0) + let lifecycle: NativeChatTurnLifecycle | undefined + const messages = await readIncrementalTranscriptMessages( + filePath, + state, + decode, + undefined, + decodeLifecycle ?? undefined, + (nextLifecycle) => { + lifecycle = nextLifecycle + } + ) + onInitialSnapshot(messages, false, 0, undefined, lifecycle) } } else { initialDrain = false diff --git a/src/main/native-chat/transcript-watch.test.ts b/src/main/native-chat/transcript-watch.test.ts index 512da6b014e..1aeced2c3ff 100644 --- a/src/main/native-chat/transcript-watch.test.ts +++ b/src/main/native-chat/transcript-watch.test.ts @@ -2,7 +2,7 @@ import { appendFile, mkdtemp, rename, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it } from 'vitest' -import type { NativeChatMessage } from '../../shared/native-chat-types' +import type { NativeChatMessage, NativeChatTurnLifecycle } from '../../shared/native-chat-types' import { getActiveNativeChatWatcherCount, readNativeChatTranscriptTail, @@ -45,6 +45,30 @@ function claudeLine(uuid: string, role: 'user' | 'assistant', text: string): str })}\n` } +function claudeEndTurnLine(uuid: string, text: string): string { + return `${JSON.stringify({ + type: 'assistant', + uuid, + timestamp: '2026-06-01T10:00:01.000Z', + message: { + role: 'assistant', + stop_reason: 'end_turn', + content: [{ type: 'text', text }] + } + })}\n` +} + +function codexLifecycleLine( + state: 'task_started' | 'task_complete' | 'turn_aborted', + turnId = 'turn-1' +): string { + return `${JSON.stringify({ + type: 'event_msg', + timestamp: state === 'task_started' ? '2026-06-01T10:00:00.000Z' : '2026-06-01T10:00:01.000Z', + payload: { type: state, turn_id: turnId } + })}\n` +} + async function waitFor(predicate: () => boolean, timeoutMs = 2000): Promise { const start = Date.now() while (!predicate()) { @@ -99,6 +123,144 @@ describe('subscribeNativeChatTranscript', () => { expect(snapshots).toEqual([[]]) }) + it('replays and appends provider-authored turn lifecycle markers', async () => { + const filePath = await tempFile(claudeLine('u-1', 'user', 'first')) + const lifecycles: NativeChatTurnLifecycle[] = [] + const sub = await subscribeNativeChatTranscript({ + agent: 'claude', + sessionId: 'ignored', + filePath, + onInitialSnapshot: (_messages, _hasMore, _beforeOffset, _error, lifecycle) => { + if (lifecycle) { + lifecycles.push(lifecycle) + } + }, + onAppend: (_messages, lifecycle) => { + if (lifecycle) { + lifecycles.push(lifecycle) + } + }, + debounceMs: 5 + }) + + await waitFor(() => lifecycles.length === 1) + await appendFile(filePath, claudeEndTurnLine('a-1', 'done')) + await waitFor(() => lifecycles.length === 2) + sub.unsubscribe() + + expect(lifecycles.map((lifecycle) => lifecycle.state)).toEqual(['working', 'completed']) + expect(lifecycles.map((lifecycle) => lifecycle.turnId)).toEqual(['u-1', 'a-1']) + }) + + it('emits Codex task_complete even when the frame has no visible messages', async () => { + const filePath = await tempFile(codexLifecycleLine('task_started')) + const lifecycles: NativeChatTurnLifecycle[] = [] + const sub = await subscribeNativeChatTranscript({ + agent: 'codex', + sessionId: 'ignored', + filePath, + onInitialSnapshot: (_messages, _hasMore, _beforeOffset, _error, lifecycle) => { + if (lifecycle) { + lifecycles.push(lifecycle) + } + }, + onAppend: (messages, lifecycle) => { + expect(messages).toEqual([]) + if (lifecycle) { + lifecycles.push(lifecycle) + } + }, + debounceMs: 5 + }) + + await waitFor(() => lifecycles.length === 1) + await appendFile(filePath, codexLifecycleLine('task_complete')) + await waitFor(() => lifecycles.length === 2) + sub.unsubscribe() + + expect(lifecycles).toMatchObject([ + { state: 'working', turnId: 'turn-1' }, + { state: 'completed', turnId: 'turn-1' } + ]) + }) + + it('replays Codex interruption as a terminal lifecycle and visible status row', async () => { + const filePath = await tempFile( + codexLifecycleLine('task_started') + codexLifecycleLine('turn_aborted') + ) + let snapshot: + | { messages: NativeChatMessage[]; lifecycle: NativeChatTurnLifecycle | undefined } + | undefined + const sub = await subscribeNativeChatTranscript({ + agent: 'codex', + sessionId: 'ignored', + filePath, + initialLimit: 40, + onInitialSnapshot: (messages, _hasMore, _beforeOffset, _error, lifecycle) => { + snapshot = { messages, lifecycle } + }, + onAppend: () => {}, + debounceMs: 5 + }) + + await waitFor(() => snapshot !== undefined) + sub.unsubscribe() + + expect(snapshot?.lifecycle).toMatchObject({ state: 'interrupted', turnId: 'turn-1' }) + expect(snapshot?.messages).toMatchObject([ + { role: 'system', blocks: [{ type: 'text', text: 'Conversation interrupted' }] } + ]) + }) + + it('does not replay an older interruption over a newer working turn', async () => { + const filePath = await tempFile( + codexLifecycleLine('task_started', 'turn-1') + + codexLifecycleLine('turn_aborted', 'turn-1') + + codexLifecycleLine('task_started', 'turn-2') + ) + const result = await readNativeChatTranscriptTail({ + agent: 'codex', + sessionId: 'ignored', + filePath, + limit: 40 + }) + + expect(result).toMatchObject({ lifecycle: { state: 'working', turnId: 'turn-2' } }) + }) + + it('recovers a completion marker even when trailing non-boundary rows follow it', async () => { + // The lifecycle scan walks newest-first; rows that decode to no boundary + // (tool-results, harness noise) must not hide an earlier real completion + // within the window, or a reconnect snapshot would fail to settle. + const toolResult = `${JSON.stringify({ + type: 'user', + uuid: 'tool-result-1', + timestamp: '2026-06-01T10:00:02.000Z', + message: { + role: 'user', + content: [{ type: 'tool_result', tool_use_id: 'tool-1', content: 'ok' }] + } + })}\n` + const noise = `${JSON.stringify({ + type: 'user', + uuid: 'note-1', + timestamp: '2026-06-01T10:00:03.000Z', + message: { + role: 'user', + content: [{ type: 'text', text: 'continue' }] + } + })}\n` + const filePath = await tempFile(claudeEndTurnLine('a-1', 'done') + toolResult + noise) + const result = await readNativeChatTranscriptTail({ + agent: 'claude', + sessionId: 'ignored', + filePath, + limit: 40 + }) + + expect(result).toMatchObject({ lifecycle: { state: 'completed', turnId: 'a-1' } }) + }) + it('emits a bulk append in bounded ordered batches', async () => { const filePath = await tempFile('') const batches: NativeChatMessage[][] = [] diff --git a/src/main/native-chat/transcript-watch.ts b/src/main/native-chat/transcript-watch.ts index f3228d970fc..1d92afe2974 100644 --- a/src/main/native-chat/transcript-watch.ts +++ b/src/main/native-chat/transcript-watch.ts @@ -1,19 +1,19 @@ import { extname } from 'node:path' import type { NativeChatMessage } from '../../shared/native-chat-types' import { resolveSessionFilePath } from './session-file-resolver' -import { - installTranscriptWatcher, - type NativeChatTranscriptSubscription, - type SubscribeNativeChatTranscriptArgs -} from './transcript-watch-engine' +import { installTranscriptWatcher } from './transcript-watch-engine' +import type { + NativeChatTranscriptSubscription, + SubscribeNativeChatTranscriptArgs +} from './transcript-watch-contract' import { nativeChatLineDecoderForAgent } from './transcript-tail-reader' export { readNativeChatTranscriptTail } from './transcript-tail-reader' -export { - getActiveNativeChatWatcherCount, - type NativeChatTranscriptSubscription, - type SubscribeNativeChatTranscriptArgs -} from './transcript-watch-engine' +export { getActiveNativeChatWatcherCount } from './transcript-watch-engine' +export type { + NativeChatTranscriptSubscription, + SubscribeNativeChatTranscriptArgs +} from './transcript-watch-contract' /** One resolve+install attempt. Returns null when the file isn't resolvable * yet, or vanished between resolve and `watch()` — either case is retried by diff --git a/src/main/runtime/rpc/methods/native-chat.test.ts b/src/main/runtime/rpc/methods/native-chat.test.ts index 15bb6b3a24a..2e0358e338d 100644 --- a/src/main/runtime/rpc/methods/native-chat.test.ts +++ b/src/main/runtime/rpc/methods/native-chat.test.ts @@ -5,16 +5,53 @@ import type { RpcContext } from '../core' // Stub the bounded tail reader so the handler returns a deterministic transcript with // one oversized tool-result block; the test then asserts clip behavior per client. const OVERSIZED = 'x'.repeat(5000) -const cachedResult = vi.hoisted(() => ({ value: { messages: [] as NativeChatMessage[] } })) +const cachedResult = vi.hoisted(() => ({ + value: { + messages: [] as NativeChatMessage[], + // Optional so truncation-gating fixtures can omit it; lifecycle tests set it explicitly. + lifecycle: undefined as + | { state: 'working' | 'completed' | 'interrupted'; turnId: string; timestamp: number | null } + | undefined + } as { + messages: NativeChatMessage[] + lifecycle?: { + state: 'working' | 'completed' | 'interrupted' + turnId: string + timestamp: number | null + } + } +})) const watcher = vi.hoisted(() => ({ args: null as null | { onInitialSnapshot?: ( messages: NativeChatMessage[], hasMore: boolean, beforeOffset: number, - error?: string + error?: string, + lifecycle?: { + state: 'working' | 'completed' | 'interrupted' + turnId: string + timestamp: number | null + } + ) => void + onReplace?: ( + messages: NativeChatMessage[], + hasMore: boolean, + beforeOffset: number, + lifecycle?: { + state: 'working' | 'completed' | 'interrupted' + turnId: string + timestamp: number | null + } + ) => void + onAppend: ( + messages: NativeChatMessage[], + lifecycle?: { + state: 'working' | 'completed' | 'interrupted' + turnId: string + timestamp: number | null + } ) => void - onAppend: (messages: NativeChatMessage[]) => void }, watching: true })) @@ -24,7 +61,8 @@ vi.mock('../../../native-chat/transcript-watch', () => ({ return Promise.resolve({ messages: messages.slice(-limit), hasMore: messages.length > limit, - beforeOffset: 123 + beforeOffset: 123, + ...(cachedResult.value.lifecycle ? { lifecycle: cachedResult.value.lifecycle } : {}) }) }, subscribeNativeChatTranscript: (args: NonNullable) => { @@ -331,7 +369,12 @@ describe('nativeChat.subscribe initial snapshot', () => { ) expect(emitted).toEqual([ - { type: 'snapshot', messages: [], hasMore: false, error: 'Transcript unavailable' } + { + type: 'snapshot', + messages: [], + hasMore: false, + error: 'Transcript unavailable' + } ]) }) @@ -379,4 +422,68 @@ describe('nativeChat.subscribe initial snapshot', () => { } ]) }) + + it('forwards lifecycle on snapshot, append, and replacement frames', async () => { + watcher.watching = true + watcher.args = null + const emitted: unknown[] = [] + await subscribeHandler()( + { agent: 'claude', sessionId: 's' }, + streamingContext('runtime'), + (value) => emitted.push(value) + ) + + const completed = { + state: 'completed' as const, + turnId: 'turn-rpc-1', + timestamp: 1_720_000_000_000 + } + const callbacks = activeWatcherArgs() + callbacks.onInitialSnapshot?.([makeMessage('snap')], false, 3, undefined, completed) + callbacks.onAppend([], completed) + callbacks.onReplace?.([makeMessage('repl')], false, 9, completed) + + expect(emitted).toEqual([ + { + type: 'snapshot', + messages: [expect.objectContaining({ id: 'a-1' })], + hasMore: false, + beforeOffset: 3, + lifecycle: completed + }, + { + type: 'appended', + messages: [], + lifecycle: completed + }, + { + type: 'replacement', + messages: [expect.objectContaining({ id: 'a-1' })], + hasMore: false, + beforeOffset: 9, + lifecycle: completed + } + ]) + }) +}) + +describe('nativeChat.readSession lifecycle payload', () => { + it('forwards lifecycle from the tail reader on success', async () => { + const lifecycle = { + state: 'interrupted' as const, + turnId: 'turn-read-1', + timestamp: 1_720_000_000_100 + } + cachedResult.value = { messages: [makeMessage('done')], lifecycle } + const result = await readSessionHandler()( + { agent: 'claude', sessionId: 's' }, + ctxWith('runtime') + ) + expect(result).toMatchObject({ + hasMore: false, + beforeOffset: 123, + lifecycle + }) + expect((result as { messages: NativeChatMessage[] }).messages).toHaveLength(1) + }) }) diff --git a/src/main/runtime/rpc/methods/native-chat.ts b/src/main/runtime/rpc/methods/native-chat.ts index 513a2134011..51758dbc803 100644 --- a/src/main/runtime/rpc/methods/native-chat.ts +++ b/src/main/runtime/rpc/methods/native-chat.ts @@ -194,7 +194,8 @@ export const NATIVE_CHAT_METHODS: readonly RpcAnyMethod[] = [ ? { messages: windowForClient(result.messages, clientKind, limit), hasMore: result.hasMore, - beforeOffset: result.beforeOffset + beforeOffset: result.beforeOffset, + ...(result.lifecycle ? { lifecycle: result.lifecycle } : {}) } : result } @@ -232,7 +233,7 @@ export const NATIVE_CHAT_METHODS: readonly RpcAnyMethod[] = [ sessionId: params.sessionId, transcriptPath: params.transcriptPath, initialLimit: limit, - onInitialSnapshot: (messages, hasMore, beforeOffset, error) => { + onInitialSnapshot: (messages, hasMore, beforeOffset, error, lifecycle) => { if (closed) { return } @@ -243,10 +244,11 @@ export const NATIVE_CHAT_METHODS: readonly RpcAnyMethod[] = [ messages: windowForClient(messages, clientKind, limit), hasMore, beforeOffset, - ...(error ? { error } : {}) + ...(error ? { error } : {}), + ...(lifecycle ? { lifecycle } : {}) }) }, - onReplace: (messages, hasMore, beforeOffset) => { + onReplace: (messages, hasMore, beforeOffset, lifecycle) => { if (closed) { return } @@ -254,14 +256,19 @@ export const NATIVE_CHAT_METHODS: readonly RpcAnyMethod[] = [ type: 'replacement', messages: windowForClient(messages, clientKind, limit), hasMore, - beforeOffset + beforeOffset, + ...(lifecycle ? { lifecycle } : {}) }) }, - onAppend: (messages) => { + onAppend: (messages, lifecycle) => { if (closed) { return } - emit({ type: 'appended', messages: sanitizeAppendForClient(messages, clientKind) }) + emit({ + type: 'appended', + messages: sanitizeAppendForClient(messages, clientKind), + ...(lifecycle ? { lifecycle } : {}) + }) } }) // The connection may have closed while the file was being resolved. @@ -270,7 +277,12 @@ export const NATIVE_CHAT_METHODS: readonly RpcAnyMethod[] = [ return } if (!subscription.watching) { - emit({ type: 'snapshot', messages: [], hasMore: false, error: 'Transcript unavailable' }) + emit({ + type: 'snapshot', + messages: [], + hasMore: false, + error: 'Transcript unavailable' + }) } unsubscribe = subscription.unsubscribe } diff --git a/src/preload/api-types.ts b/src/preload/api-types.ts index 0b2cfd98283..c25c2acbc88 100644 --- a/src/preload/api-types.ts +++ b/src/preload/api-types.ts @@ -428,7 +428,11 @@ import type { AiVaultSubagentListArgs, AiVaultSubagentListResult } from '../shared/ai-vault-types' -import type { AgentType, NativeChatMessage } from '../shared/native-chat-types' +import type { + AgentType, + NativeChatMessage, + NativeChatTurnLifecycle +} from '../shared/native-chat-types' import type { TelemetryConsentState } from '../shared/telemetry-consent-types' import type { AgentKind, LaunchSource, RequestKind } from '../shared/telemetry-events' import type { AppStarSource } from '../shared/gh-star-source' @@ -837,16 +841,34 @@ export type AiVaultApi = { // notFound marks a miss caused by the transcript not existing on disk yet // (retry-worthy), as opposed to a real read/parse error (#8401). export type NativeChatReadSessionResult = - | { messages: NativeChatMessage[] } + | { + messages: NativeChatMessage[] + lifecycle?: NativeChatTurnLifecycle + } | { error: string; notFound?: true } /** Messages appended to a live-tailed transcript since the previous emit. */ export type NativeChatAppendedMessages = NativeChatMessage[] export type NativeChatSubscriptionFrame = - | { type: 'snapshot'; messages: NativeChatMessage[]; hasMore: boolean; error?: string } - | { type: 'replacement'; messages: NativeChatMessage[]; hasMore: boolean } - | { type: 'appended'; messages: NativeChatMessage[] } + | { + type: 'snapshot' + messages: NativeChatMessage[] + hasMore: boolean + error?: string + lifecycle?: NativeChatTurnLifecycle + } + | { + type: 'replacement' + messages: NativeChatMessage[] + hasMore: boolean + lifecycle?: NativeChatTurnLifecycle + } + | { + type: 'appended' + messages: NativeChatMessage[] + lifecycle?: NativeChatTurnLifecycle + } /** Wire payload for the `nativeChat:appended` push channel. */ export type NativeChatAppendedPayload = { diff --git a/src/renderer/src/components/native-chat/NativeChatView.tsx b/src/renderer/src/components/native-chat/NativeChatView.tsx index 984a4919103..945c26701db 100644 --- a/src/renderer/src/components/native-chat/NativeChatView.tsx +++ b/src/renderer/src/components/native-chat/NativeChatView.tsx @@ -156,18 +156,23 @@ function NativeChatResolvedView({ const launchPrompt = useAppStore((s) => s.nativeChatLaunchPromptByTabId[terminalTabId] ?? null) const clearNativeChatLaunchPrompt = useAppStore((s) => s.clearNativeChatLaunchPrompt) const paneLaunchPrompt = launchPrompt?.agent === agent ? launchPrompt : null - // Live hook state for this pane, selected directly so the working indicator - // flips the instant the agent reports 'working' — even when switching to chat - // mid-turn before the transcript merge has caught up. - const hookWorking = useAppStore((s) => s.agentStatusByPaneKey[paneKey]?.state === 'working') + // The live-session merge reconciles hooks with replayable transcript turn + // boundaries; all working consumers must use that one lifecycle decision. + const liveWorking = session.status === 'working' // The agent's in-progress reply preview (hook), shown as a live streaming // bubble while it works — before the completed turn flushes to the transcript. const hookPreview = useAppStore((s) => s.agentStatusByPaneKey[paneKey]?.lastAssistantMessage) + // Why: Stop suppression must clear on a newer working epoch even when status + // never leaves 'working' (interrupt + immediate next turn coalesced). + const hookWorkingEpoch = useAppStore( + (s) => s.agentStatusByPaneKey[paneKey]?.stateStartedAt ?? null + ) const canSend = useNativeChatCanSend(targetPtyId) // Reuse the verified composer send path for interactive cards and composer // stop (Stop sends ESC, the agent-TUI interrupt key). const interactiveSend = useNativeChatInteractiveSend(terminalTabId, paneKey, targetPtyId, agent) const [workingInterrupted, setWorkingInterrupted] = useState(false) + const previousWorkingEpochRef = useRef(null) // True while a question card owns the input region, so the composer is hidden. const [questionActive, setQuestionActive] = useState(false) const rootRef = useRef(null) @@ -308,9 +313,9 @@ function NativeChatResolvedView({ ? [...sessionAfterCommandBoundaries.messages, ...pendingMessages] : sessionAfterCommandBoundaries.messages, previewText: hookPreview, - working: hookWorking + working: liveWorking }) - }, [sessionAfterCommandBoundaries.messages, pendingMessages, hookPreview, hookWorking]) + }, [sessionAfterCommandBoundaries.messages, pendingMessages, hookPreview, liveWorking]) const sessionWithPending = useMemo(() => { if (pending.length === 0 && commandMarkers.length === 0 && !streamingText) { return sessionAfterCommandBoundaries @@ -331,26 +336,38 @@ function NativeChatResolvedView({ const viewState = selectNativeChatViewState(sessionWithPending) const isConversation = viewState.kind === 'ready' - // Drive "working" from the live hook state too: when toggling to chat while the - // agent is mid-turn, the merged transcript may not yet reflect the in-flight - // turn, but the hook already says 'working' — show the indicator immediately. - const viewWorking = viewState.kind === 'ready' && viewState.isWorking useEffect(() => { - if (shouldClearNativeChatWorkingSuppression({ viewWorking, hookWorking })) { + if ( + shouldClearNativeChatWorkingSuppression({ + working: liveWorking, + interrupted: workingInterrupted, + workingEpoch: hookWorkingEpoch, + previousWorkingEpoch: previousWorkingEpochRef.current + }) + ) { setWorkingInterrupted(false) } - }, [viewWorking, hookWorking]) + if (liveWorking && hookWorkingEpoch != null) { + previousWorkingEpochRef.current = hookWorkingEpoch + } + if (!liveWorking) { + previousWorkingEpochRef.current = null + } + }, [liveWorking, workingInterrupted, hookWorkingEpoch]) const isWorking = shouldShowNativeChatWorking({ isConversation, - viewWorking, - hookWorking, + working: liveWorking, interrupted: workingInterrupted }) const stopAgent = useCallback(() => { setWorkingInterrupted(true) + // Why: Stop after a submitted turn drops the delayed-write handle once it + // settles, so cancelPendingSends no longer sees the optimistic id. Clear + // the echo cache here so a cancelled prompt cannot stick as a ghost bubble. + setPending(writePendingSendCache(pendingScope, [])) interactiveSend.cancel() - }, [interactiveSend]) + }, [interactiveSend, pendingScope]) const nativeChatFileLinkClick = useNativeChatFileLinkClick(fileLinkContext) // Chat-only font zoom via Cmd/Ctrl +/-/0, gated to the live conversation so diff --git a/src/renderer/src/components/native-chat/native-chat-live-status.ts b/src/renderer/src/components/native-chat/native-chat-live-status.ts index 7936fd7e231..06efe472dc4 100644 --- a/src/renderer/src/components/native-chat/native-chat-live-status.ts +++ b/src/renderer/src/components/native-chat/native-chat-live-status.ts @@ -1,14 +1,15 @@ // Pure merge of live hook turn-state into a NativeChatSession status override. // Kept separate from the React hook so the precedence rule (live 'working' -// surfaces before the transcript flushes the final assistant message, then is -// superseded once it lands) is unit-testable without IPC or the store. +// surfaces before the transcript flushes its explicit terminal record, then is +// reconciled once that boundary lands) is unit-testable without IPC. import type { AgentStatusState } from '../../../../shared/agent-status-types' import { assembleNativeChatSession, type NativeChatSources } from './native-chat-session-assembler' import type { AgentType, NativeChatSession, - NativeChatSessionStatus + NativeChatSessionStatus, + NativeChatTurnLifecycle } from '../../../../shared/native-chat-types' export type NativeChatLiveMergeInput = { @@ -17,9 +18,12 @@ export type NativeChatLiveMergeInput = { agent: AgentType /** Live hook state for the pane, or null when no hook entry exists. */ hookState: AgentStatusState | null - /** Epoch ms when the current hook state began, or null when unknown. Lets a - * stale 'working' self-heal once this turn's own assistant reply has landed. */ + /** Epoch ms when the current hook state began, or null when unknown. */ stateStartedAt?: number | null + /** Latest provider-authored turn boundary recovered from the transcript. */ + transcriptLifecycle?: NativeChatTurnLifecycle + /** Claude can finish its lead turn while background children remain active. */ + hookHasWorkingSubagents?: boolean /** True before the initial snapshot resolves; forces 'loading'. */ loading?: boolean /** Set when the initial snapshot failed; forces 'error'. */ @@ -28,25 +32,42 @@ export type NativeChatLiveMergeInput = { /** * Decide the session status given the merged transcript/append messages and the - * live hook state. The transcript is the source of truth for content; the hook - * only fills the gap while the agent is mid-turn. + * live hook state. The transcript is the source of truth for content; explicit + * provider lifecycle records reconcile a dropped final hook. * * Precedence: - * - error / loading overrides win outright. - * - hook 'working' stays authoritative until the hook exits that state OR this - * turn's own assistant reply lands (a trailing reply newer than - * stateStartedAt); a trailing reply from an EARLIER turn does not suppress it. + * - errors win outright; live work wins over transcript loading. + * - hook 'working' stays authoritative until the hook exits that state OR an + * explicit terminal marker for this turn lands. + * - design is hook-first: lifecycle is a terminal suppressor for dropped + * Stop hooks, not a full authority for active-turn reconstruction. */ export function mergeNativeChatLiveSession(input: NativeChatLiveMergeInput): NativeChatSession { - const { sources, sessionId, agent, hookState, stateStartedAt, loading, error } = input + const { + sources, + sessionId, + agent, + hookState, + stateStartedAt, + transcriptLifecycle, + hookHasWorkingSubagents, + loading, + error + } = input if (error) { return assembleNativeChatSession({ sources, sessionId, agent, status: 'error', error }) } - if (loading) { + + const status = liveStatusOverride( + hookState, + sources, + stateStartedAt, + transcriptLifecycle, + hookHasWorkingSubagents ?? false + ) + if (loading && status !== 'working') { return assembleNativeChatSession({ sources, sessionId, agent, status: 'loading' }) } - - const status = liveStatusOverride(hookState, sources, stateStartedAt) return assembleNativeChatSession({ sources, sessionId, @@ -55,29 +76,75 @@ export function mergeNativeChatLiveSession(input: NativeChatLiveMergeInput): Nat }) } +/** Slack for comparing transcript timestamps to hook receipt times across hosts. */ +const LIFECYCLE_CLOCK_SKEW_SLACK_MS = 2_000 + function liveStatusOverride( hookState: AgentStatusState | null, sources: NativeChatSources, - stateStartedAt: number | null | undefined + stateStartedAt: number | null | undefined, + transcriptLifecycle: NativeChatTurnLifecycle | undefined, + hookHasWorkingSubagents: boolean ): NativeChatSessionStatus | undefined { // Only 'working' drives a live override; blocked/waiting/done leave the // derived (ready/empty) status alone so completed turns render normally. if (hookState !== 'working') { return undefined } - // Self-heal a stale 'working' (dropped/late Stop hook): if this turn's own - // assistant reply has already landed, the turn is effectively visible — stop - // asserting 'working'. A trailing reply from a PRIOR turn (older than the - // working turn's start) must not suppress it: the agent is working again. - if (trailingAssistantPostDates(sources, stateStartedAt)) { + const terminatesCurrentTurn = lifecycleTerminatesCurrentTurn(transcriptLifecycle, stateStartedAt) + // Why: an explicit interruption ends the whole turn, children included, so it + // settles the session even while a stale child status still reads working. + if (terminatesCurrentTurn && transcriptLifecycle?.state === 'interrupted') { + return undefined + } + // Why: a lead completion does not end Claude's aggregate turn while a + // background child still runs; callers must already scope the roster to the + // current working epoch so prior-turn children cannot veto forever. + if (hookHasWorkingSubagents) { + return 'working' + } + if (terminatesCurrentTurn) { + return undefined + } + // Why: prose recovery stays available whenever the latest lifecycle is not an + // explicit in-progress generation. That covers incapable hosts and capable + // hosts whose transcript never emitted a terminal marker for this window. + // Mid-turn (lifecycle === working) keeps prose off so partial assistant rows + // do not settle early on capable providers. + if ( + transcriptLifecycle?.state !== 'working' && + trailingAssistantPostDates(sources, stateStartedAt) + ) { return undefined } return 'working' } -/** True when the transcript's last message is an assistant reply that landed at - * or after `stateStartedAt`. Unknown timings (no start, no message timestamp) - * return false so the caller keeps 'working' — the safe, non-regressing default. */ +function lifecycleTerminatesCurrentTurn( + lifecycle: NativeChatTurnLifecycle | undefined, + stateStartedAt: number | null | undefined +): boolean { + if (lifecycle?.state !== 'completed' && lifecycle?.state !== 'interrupted') { + return false + } + // Why: omit/null timestamps are valid on the wire. Prefer the terminal marker + // over a stuck spinner — the latest lifecycle is already last-wins from the + // watcher, so a newer user generation would have replaced it with working. + if (stateStartedAt == null || lifecycle.timestamp == null) { + return true + } + if (lifecycle.timestamp >= stateStartedAt) { + return true + } + // Why: transcript clocks and hook receipt times can skew over SSH/runtime. + // Only apply slack to real epoch timestamps so small logical clocks used in + // tests (and any non-wall-clock ids) keep strict ordering. + if (lifecycle.timestamp > 1e11 && stateStartedAt > 1e11) { + return lifecycle.timestamp + LIFECYCLE_CLOCK_SKEW_SLACK_MS >= stateStartedAt + } + return false +} + function trailingAssistantPostDates( sources: NativeChatSources, stateStartedAt: number | null | undefined @@ -86,8 +153,5 @@ function trailingAssistantPostDates( return false } const last = (sources.transcript ?? []).at(-1) - if (last?.role !== 'assistant' || last.timestamp == null) { - return false - } - return last.timestamp >= stateStartedAt + return last?.role === 'assistant' && last.timestamp != null && last.timestamp >= stateStartedAt } diff --git a/src/renderer/src/components/native-chat/native-chat-noise.test.ts b/src/renderer/src/components/native-chat/native-chat-noise.test.ts index 198749b351d..a9942ed82d7 100644 --- a/src/renderer/src/components/native-chat/native-chat-noise.test.ts +++ b/src/renderer/src/components/native-chat/native-chat-noise.test.ts @@ -38,6 +38,7 @@ describe('isNoiseMessage', () => { it('keeps assistant and tool turns', () => { expect(isNoiseMessage(msg('assistant', ' in prose'))).toBe(false) + expect(isNoiseMessage(msg('system', 'Conversation interrupted'))).toBe(false) }) it('keeps a user turn that carries tool results', () => { diff --git a/src/renderer/src/components/native-chat/native-chat-pending-occurrence.ts b/src/renderer/src/components/native-chat/native-chat-pending-occurrence.ts index 3c94e5bfadd..d552747efb0 100644 --- a/src/renderer/src/components/native-chat/native-chat-pending-occurrence.ts +++ b/src/renderer/src/components/native-chat/native-chat-pending-occurrence.ts @@ -80,6 +80,125 @@ export function advancedNativeChatUserContentCounts( return advanced } +function nativeChatUserMessageNormalizedText(message: NativeChatMessage): string | null { + if (message.role !== 'user') { + return null + } + const text = normalizeNativeChatPendingText( + message.blocks + .filter(isTextBlock) + .map((block) => block.text) + .join(' ') + ) + return text.length > 0 ? text : null +} + +/** User texts that already have a later non-user turn (ready to prune echoes). */ +export function advancedNativeChatUserTexts( + messages: readonly NativeChatMessage[] +): readonly string[] { + const advanced: string[] = [] + const waiting: string[] = [] + for (const message of messages) { + if (message.role === 'user') { + const text = nativeChatUserMessageNormalizedText(message) + if (text) { + waiting.push(text) + } + continue + } + advanced.push(...waiting) + waiting.length = 0 + } + return advanced +} + +/** All user texts (for hiding optimistic echoes once the turn exists). */ +export function matchingNativeChatUserTexts( + messages: readonly NativeChatMessage[] +): readonly string[] { + const texts: string[] = [] + for (const message of messages) { + const text = nativeChatUserMessageNormalizedText(message) + if (text) { + texts.push(text) + } + } + return texts +} + +/** + * How many leading pending texts concatenate exactly to `userText`. + * Covers rapid-send glue ("joke"+"continue" → "jokecontinue") without matching + * unrelated prefixes ("hi" ↛ "history"). + */ +export function countLeadingPendingTextsGluedToUserText( + pendingTexts: readonly string[], + userText: string +): number { + if (pendingTexts.length === 0 || userText.length === 0) { + return 0 + } + let combined = '' + for (let index = 0; index < pendingTexts.length; index += 1) { + const piece = pendingTexts[index] + if (!piece) { + return 0 + } + combined += piece + if (combined === userText) { + return index + 1 + } + if (!userText.startsWith(combined)) { + return 0 + } + } + return 0 +} + +/** + * Mark pending entries represented only by multi-send glue (2+ consecutive + * optimistic texts concatenated into one transcript user row). Exact single + * matches stay in the content-key/occurrence path so repeated prompts and + * send boundaries keep their existing semantics. + */ +export function selectPendingIndicesRepresentedByUserTexts( + pending: readonly NativeChatPendingOccurrence[], + userTexts: readonly string[] +): Set { + const represented = new Set() + if (pending.length < 2 || userTexts.length === 0) { + return represented + } + const remaining = pending.map((entry, index) => ({ + index, + text: normalizeNativeChatPendingText(entry.text) + })) + for (const userText of userTexts) { + const open = remaining.filter((entry) => !represented.has(entry.index) && entry.text.length > 0) + const gluedCount = countLeadingPendingTextsGluedToUserText( + open.map((entry) => entry.text), + userText + ) + // Why: gluedCount === 1 is an exact match — leave it to occurrence counting. + if (gluedCount < 2) { + continue + } + for (let i = 0; i < gluedCount; i += 1) { + const entry = open[i] + if (!entry) { + continue + } + represented.add(entry.index) + const at = remaining.findIndex((candidate) => candidate.index === entry.index) + if (at >= 0) { + remaining.splice(at, 1) + } + } + } + return represented +} + export function nativeChatPendingMatchKey(pending: NativeChatPendingOccurrence): string { return `${String(pending.afterMessageId)}\0${nativeChatPendingContentKey(pending)}` } diff --git a/src/renderer/src/components/native-chat/native-chat-pending.test.ts b/src/renderer/src/components/native-chat/native-chat-pending.test.ts index b63dba307b2..78ffa01b78f 100644 --- a/src/renderer/src/components/native-chat/native-chat-pending.test.ts +++ b/src/renderer/src/components/native-chat/native-chat-pending.test.ts @@ -140,6 +140,26 @@ describe('prunePendingSends', () => { prunePendingSends(pending, [userMessage('u1', 'repeat'), assistantMessage('a1', 'done')]) ).toEqual([pendingOf('p2', 'repeat')]) }) + + it('prunes consecutive optimistic sends that were glued into one transcript user turn', () => { + const pending = [pendingOf('p1', 'tell me a joke'), pendingOf('p2', 'continue')] + expect( + prunePendingSends(pending, [ + userMessage('u1', 'tell me a jokecontinue'), + assistantMessage('a1', 'a joke') + ]) + ).toEqual([]) + }) + + it('does not treat an unrelated longer user turn as a glued match', () => { + const pending = [pendingOf('p1', 'hi')] + expect( + prunePendingSends(pending, [ + userMessage('u1', 'history of the project'), + assistantMessage('a1', 'ok') + ]) + ).toEqual(pending) + }) }) describe('pendingSendsAsMessages', () => { @@ -179,6 +199,13 @@ describe('pendingSendsAsMessages', () => { expect(pendingSendsAsMessages(pending, [])).toHaveLength(1) }) + it('hides consecutive optimistic sends once a glued transcript user turn lands', () => { + const pending = [pendingOf('p1', 'tell me a joke'), pendingOf('p2', 'continue')] + expect(pendingSendsAsMessages(pending, [userMessage('u1', 'tell me a jokecontinue')])).toEqual( + [] + ) + }) + it('keeps a repeated prompt visible when its only match predates the send boundary', () => { const history = [userMessage('old-user', 'run tests'), assistantMessage('old-answer', 'passed')] const pending = [{ ...pendingOf('new-send', 'run tests'), afterMessageId: 'old-answer' }] diff --git a/src/renderer/src/components/native-chat/native-chat-pending.ts b/src/renderer/src/components/native-chat/native-chat-pending.ts index 92dbaf17ef7..2fb916c0f73 100644 --- a/src/renderer/src/components/native-chat/native-chat-pending.ts +++ b/src/renderer/src/components/native-chat/native-chat-pending.ts @@ -8,12 +8,15 @@ import { setBoundedScopeCacheEntry } from './native-chat-composer-scope-cache' import type { NativeChatLaunchPrompt } from '@/lib/native-chat-launch-prompt' import { advancedNativeChatUserContentCounts, + advancedNativeChatUserTexts, assignNativeChatPendingOccurrence, matchingNativeChatUserContentCounts, + matchingNativeChatUserTexts, nativeChatPendingContentKey, nativeChatPendingMatchKey, nativeChatPendingMatchingAfter, - nativeChatPendingOccurrence + nativeChatPendingOccurrence, + selectPendingIndicesRepresentedByUserTexts } from './native-chat-pending-occurrence' /** An optimistic, not-yet-confirmed composer send. */ @@ -133,7 +136,7 @@ export function prunePendingSends( return pending } const consumed = new Map() - const next = pending.filter((entry) => { + const exactKeep = pending.map((entry) => { const contentKey = nativeChatPendingContentKey(entry) const key = nativeChatPendingMatchKey(entry) const available = @@ -143,10 +146,22 @@ export function prunePendingSends( const used = consumed.get(key) ?? 0 const occurrence = nativeChatPendingOccurrence(entry, used) consumed.set(key, Math.max(used, occurrence)) - if (occurrence > available) { - return true + return occurrence > available + }) + // Why: when rapid body writes glued two optimistic sends into one transcript + // user row ("joke"+"continue"→"jokecontinue"), exact keys never match. Drop + // those echoes once an assistant turn advances past the glued user text. + const stillOpen = pending.filter((_, index) => exactKeep[index]) + const gluedRepresented = selectPendingIndicesRepresentedByUserTexts( + stillOpen, + advancedNativeChatUserTexts(messages) + ) + const next = pending.filter((entry, index) => { + if (!exactKeep[index]) { + return false } - return false + const openIndex = stillOpen.indexOf(entry) + return openIndex < 0 || !gluedRepresented.has(openIndex) }) return next.length === pending.length ? pending : next } @@ -162,21 +177,32 @@ export function pendingSendsAsMessages( existingMessages: NativeChatMessage[] = [] ): NativeChatMessage[] { const consumed = new Map() + const exactVisible = pending.map((entry) => { + const contentKey = nativeChatPendingContentKey(entry) + const key = nativeChatPendingMatchKey(entry) + const represented = + matchingNativeChatUserContentCounts( + messagesAfterPendingBoundary(existingMessages, entry) + ).get(contentKey) ?? 0 + const used = consumed.get(key) ?? 0 + const occurrence = nativeChatPendingOccurrence(entry, used) + consumed.set(key, Math.max(used, occurrence)) + return occurrence > represented + }) + // Hide optimistic echoes that were glued into a single transcript user row + // even before the assistant reply lands (matching, not advanced). + const stillVisible = pending.filter((_, index) => exactVisible[index]) + const gluedRepresented = selectPendingIndicesRepresentedByUserTexts( + stillVisible, + matchingNativeChatUserTexts(existingMessages) + ) return pending - .filter((entry) => { - const contentKey = nativeChatPendingContentKey(entry) - const key = nativeChatPendingMatchKey(entry) - const represented = - matchingNativeChatUserContentCounts( - messagesAfterPendingBoundary(existingMessages, entry) - ).get(contentKey) ?? 0 - const used = consumed.get(key) ?? 0 - const occurrence = nativeChatPendingOccurrence(entry, used) - consumed.set(key, Math.max(used, occurrence)) - if (occurrence > represented) { - return true + .filter((entry, index) => { + if (!exactVisible[index]) { + return false } - return false + const openIndex = stillVisible.indexOf(entry) + return openIndex < 0 || !gluedRepresented.has(openIndex) }) .map((entry) => ({ id: `pending:${entry.id}`, diff --git a/src/renderer/src/components/native-chat/native-chat-pty-send-queue.ts b/src/renderer/src/components/native-chat/native-chat-pty-send-queue.ts new file mode 100644 index 00000000000..1ccaefc42a5 --- /dev/null +++ b/src/renderer/src/components/native-chat/native-chat-pty-send-queue.ts @@ -0,0 +1,191 @@ +// Per-PTY serialization for native-chat clear+body+Enter sequences. +// Why: Enter is delayed (busy-agent safety). Without a queue, a second send's +// clear/body can interleave with the first Enter window and still glue or race +// the agent composer. Each sequence owns the line until its Enter fires. +// +// Option commands (model switch) cancel/await this queue first so a delayed chat +// Enter cannot land on Claude's model confirmation dialog. + +export type NativeChatPtySendQueueHandle = { + cancel: () => void + settleAfterMs: number + bodyStarted: () => boolean + finished: () => boolean +} + +export type EnqueueNativeChatPtySendOptions = { + /** + * Called when cancel aborts after `start` began but before Enter was marked + * submitted. Used to clear leftover body text from the agent TUI. + */ + onCancelUnsubmitted?: () => void +} + +type PtyQueueState = { + tail: Promise + freeAt: number + depth: number + handles: Set +} + +const ptyQueues = new Map() + +function getOrCreateState(ptyId: string): PtyQueueState { + let state = ptyQueues.get(ptyId) + if (!state) { + state = { tail: Promise.resolve(), freeAt: Date.now(), depth: 0, handles: new Set() } + ptyQueues.set(ptyId, state) + } + return state +} + +export function resetNativeChatPtySendQueuesForTests(): void { + for (const state of ptyQueues.values()) { + for (const handle of state.handles) { + handle.cancel() + } + } + ptyQueues.clear() +} + +/** Abort every in-flight/queued chat send on this PTY (clears delayed Enter). */ +export function cancelNativeChatPtySends(ptyId: string): void { + const state = ptyQueues.get(ptyId) + if (!state) { + return + } + for (const handle of state.handles) { + handle.cancel() + } +} + +/** Wait until every chat sequence on this PTY has finished or been cancelled. */ +export async function waitForNativeChatPtyIdle(ptyId: string): Promise { + const state = ptyQueues.get(ptyId) + if (!state) { + return + } + await state.tail +} + +/** + * Run `start` only after prior sequences for `ptyId` finish. When the queue is + * idle, `start` runs synchronously so the body write is not deferred a tick. + */ +export function enqueueNativeChatPtySend( + ptyId: string, + durationMs: number, + start: (ctx: { + isCancelled: () => boolean + delay: (ms: number, fn: () => void) => void + /** Call when Enter (or the terminal write that completes the send) fires. */ + markSubmitted: () => void + }) => void, + options?: EnqueueNativeChatPtySendOptions +): NativeChatPtySendQueueHandle { + const now = Date.now() + const state = getOrCreateState(ptyId) + const waitMs = Math.max(0, state.freeAt - now) + const settleAfterMs = waitMs + Math.max(0, durationMs) + state.freeAt = Math.max(now, state.freeAt) + Math.max(0, durationMs) + state.depth += 1 + + let cancelled = false + let bodyStarted = false + let finished = false + let submitted = false + const timers: ReturnType[] = [] + let release: (() => void) | null = null + + const delay = (ms: number, fn: () => void): void => { + const timer = setTimeout(() => { + if (!cancelled) { + fn() + } + }, ms) + timers.push(timer) + } + + const markFinished = (): void => { + finished = true + } + + const markSubmitted = (): void => { + submitted = true + } + + const execute = (): Promise => + new Promise((resolve) => { + release = resolve + if (cancelled) { + markFinished() + resolve() + return + } + bodyStarted = true + start({ isCancelled: () => cancelled, delay, markSubmitted }) + if (durationMs <= 0) { + markSubmitted() + markFinished() + resolve() + return + } + // Why: always release after the declared duration so a cancel mid-flight + // cannot stall the per-pty queue forever. + const done = setTimeout(() => { + markFinished() + resolve() + }, durationMs) + timers.push(done) + }) + + const runPromise = + state.depth === 1 && waitMs === 0 ? execute() : state.tail.then(() => execute()) + + const dropHandle = (): void => { + state.handles.delete(handle) + } + + const settleQueueEntry = (): void => { + state.depth = Math.max(0, state.depth - 1) + markFinished() + dropHandle() + // Why: drop the per-pty record once nothing is in flight so the map does not + // accumulate one permanent entry per pty over a long, multi-pane session. + if (state.depth === 0 && state.handles.size === 0 && ptyQueues.get(ptyId) === state) { + ptyQueues.delete(ptyId) + } + } + + state.tail = runPromise.then(settleQueueEntry, settleQueueEntry) + + const handle: NativeChatPtySendQueueHandle = { + cancel: () => { + if (cancelled) { + return + } + cancelled = true + for (const timer of timers) { + clearTimeout(timer) + } + const shouldClear = bodyStarted && !submitted + markFinished() + // Why: refund only THIS sequence's charged window rather than collapsing + // freeAt to now — later queued sends still hold the line, so a blanket + // reset would understate the next enqueue's settle time and let a send + // card drop while a queued Enter is still pending. + state.freeAt = Math.max(Date.now(), state.freeAt - Math.max(0, durationMs)) + release?.() + release = null + dropHandle() + if (shouldClear) { + options?.onCancelUnsubmitted?.() + } + }, + settleAfterMs, + bodyStarted: () => bodyStarted, + finished: () => finished + } + state.handles.add(handle) + return handle +} diff --git a/src/renderer/src/components/native-chat/native-chat-runtime-contract.ts b/src/renderer/src/components/native-chat/native-chat-runtime-contract.ts new file mode 100644 index 00000000000..329c93f852f --- /dev/null +++ b/src/renderer/src/components/native-chat/native-chat-runtime-contract.ts @@ -0,0 +1,60 @@ +import type { + NativeChatAppendedMessages, + NativeChatReadSessionResult +} from '../../../../preload/api-types' +import type { NativeChatTurnLifecycle } from '../../../../shared/native-chat-types' + +export const RUNTIME_NATIVE_CHAT_READ_ERROR = "Couldn't read agent chat from the remote runtime." + +export function parseRuntimeNativeChatTurnLifecycle( + value: unknown +): NativeChatTurnLifecycle | undefined { + if (typeof value !== 'object' || value === null) { + return undefined + } + const record = value as Record + if ( + (record.state !== 'working' && + record.state !== 'completed' && + record.state !== 'interrupted') || + typeof record.turnId !== 'string' || + record.turnId.trim().length === 0 || + (record.timestamp !== null && + record.timestamp !== undefined && + (typeof record.timestamp !== 'number' || + !Number.isFinite(record.timestamp) || + record.timestamp <= 0)) + ) { + return undefined + } + return { + state: record.state, + turnId: record.turnId.trim(), + // Why: an omitted timestamp is a valid payload; normalize it to null rather + // than dropping the whole lifecycle record. + timestamp: record.timestamp ?? null + } +} + +export function parseRuntimeNativeChatReadSessionResult( + value: unknown +): NativeChatReadSessionResult { + if (typeof value !== 'object' || value === null) { + return { error: RUNTIME_NATIVE_CHAT_READ_ERROR } + } + const record = value as Record + if (Array.isArray(record.messages)) { + const lifecycle = parseRuntimeNativeChatTurnLifecycle(record.lifecycle) + return { + messages: record.messages as NativeChatAppendedMessages, + ...(lifecycle ? { lifecycle } : {}) + } + } + if (typeof record.error === 'string') { + return { + error: record.error, + ...(record.notFound === true ? { notFound: true } : {}) + } + } + return { error: RUNTIME_NATIVE_CHAT_READ_ERROR } +} diff --git a/src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts b/src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts index 4982bbda298..e688c225458 100644 --- a/src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts +++ b/src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts @@ -15,10 +15,12 @@ import { sendNativeChatMessageWithImageAttachments, submitNativeChatPrompt, sendNativeChatAskAnswer, + resetNativeChatPtySendQueuesForTests, NATIVE_CHAT_IMAGE_ATTACHMENT_SETTLE_MS, NATIVE_CHAT_SUBMIT_DELAY_MS, NATIVE_CHAT_QUESTION_STEP_MS, - NATIVE_CHAT_ADVANCE_BUFFER_MS + NATIVE_CHAT_ADVANCE_BUFFER_MS, + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT } from './native-chat-runtime-send' import { buildNativeChatImagePasteBytes, @@ -29,25 +31,28 @@ import { const SETTINGS = {} as Parameters[0] const PTY = 'pty-1' +function expectWriteOrder(calls: unknown[][], expected: string[]): void { + expect(calls.map((call) => call[2])).toEqual(expected) +} + describe('sendNativeChatMessage', () => { beforeEach(() => { vi.useFakeTimers() sendRuntimePtyInput.mockClear() + resetNativeChatPtySendQueuesForTests() sendRuntimePtyInput.mockReturnValue(true) }) afterEach(() => { vi.useRealTimers() + resetNativeChatPtySendQueuesForTests() }) - it('writes the framed body immediately, before the Enter', () => { + it('clears the TUI line, then writes the framed body, before the Enter', () => { const handle = sendNativeChatMessage(SETTINGS, PTY, 'hello world') - // Body lands synchronously; Enter is still pending on the timer. - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) - expect(sendRuntimePtyInput).toHaveBeenCalledWith( - SETTINGS, - PTY, + expectWriteOrder(sendRuntimePtyInput.mock.calls, [ + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, buildNativeChatPasteBytes('hello world') - ) + ]) expect(handle.settleAfterMs).toBe(NATIVE_CHAT_SUBMIT_DELAY_MS) }) @@ -56,43 +61,119 @@ describe('sendNativeChatMessage', () => { // A short gap would fire Enter while a busy Codex has not yet landed the // paste, submitting an empty box — so nothing must happen before 500ms. vi.advanceTimersByTime(NATIVE_CHAT_SUBMIT_DELAY_MS - 1) - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) + expect(sendRuntimePtyInput).toHaveBeenCalledTimes(2) }) it('writes the bare carriage-return Enter as a separate delayed write', () => { sendNativeChatMessage(SETTINGS, PTY, 'hi') vi.advanceTimersByTime(NATIVE_CHAT_SUBMIT_DELAY_MS) - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(2) - expect(sendRuntimePtyInput).toHaveBeenLastCalledWith(SETTINGS, PTY, NATIVE_CHAT_SUBMIT) + expectWriteOrder(sendRuntimePtyInput.mock.calls, [ + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, + buildNativeChatPasteBytes('hi'), + NATIVE_CHAT_SUBMIT + ]) }) - it('cancels the delayed Enter when its owning composer is detached', () => { + it('cancels the delayed Enter and re-clears an unsubmitted body', () => { const handle = sendNativeChatMessage(SETTINGS, PTY, 'hi') handle.cancel() vi.advanceTimersByTime(NATIVE_CHAT_SUBMIT_DELAY_MS) - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) + // Pre-send clear + body + cancel clear; Enter must not fire. + expectWriteOrder(sendRuntimePtyInput.mock.calls, [ + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, + buildNativeChatPasteBytes('hi'), + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT + ]) + }) + + it('clears leftover unsubmitted body on cancel so the next send cannot glue', async () => { + const handle = sendNativeChatMessage(SETTINGS, PTY, 'tell me a joke') + handle.cancel() + + sendNativeChatMessage(SETTINGS, PTY, 'continue') + // Queue release after cancel is promise-chained; flush so the next body runs. + await Promise.resolve() + await Promise.resolve() + + expect(sendRuntimePtyInput.mock.calls.map((call) => call[2])).toEqual([ + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, + buildNativeChatPasteBytes('tell me a joke'), + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, // cancel cleanup + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, // next send pre-clear + buildNativeChatPasteBytes('continue') + ]) + }) + + it('does not clear the TUI input when cancel runs after Enter already fired', () => { + const handle = sendNativeChatMessage(SETTINGS, PTY, 'already submitted') + vi.advanceTimersByTime(NATIVE_CHAT_SUBMIT_DELAY_MS) + sendRuntimePtyInput.mockClear() + handle.cancel() + + expect(sendRuntimePtyInput).not.toHaveBeenCalled() }) it('matches orca-runtime writeTerminalAction Enter gap (500ms)', () => { expect(NATIVE_CHAT_SUBMIT_DELAY_MS).toBe(500) }) + + it('serializes rapid sends on the same PTY so bodies cannot glue before Enter', async () => { + sendNativeChatMessage(SETTINGS, PTY, 'tell me a joke') + sendNativeChatMessage(SETTINGS, PTY, 'continue') + + // First clear+body are immediate; second sequence waits for the first Enter. + expectWriteOrder(sendRuntimePtyInput.mock.calls, [ + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, + buildNativeChatPasteBytes('tell me a joke') + ]) + + await vi.advanceTimersByTimeAsync(NATIVE_CHAT_SUBMIT_DELAY_MS) + expectWriteOrder(sendRuntimePtyInput.mock.calls, [ + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, + buildNativeChatPasteBytes('tell me a joke'), + NATIVE_CHAT_SUBMIT, + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, + buildNativeChatPasteBytes('continue') + ]) + + await vi.advanceTimersByTimeAsync(NATIVE_CHAT_SUBMIT_DELAY_MS) + expect(sendRuntimePtyInput).toHaveBeenLastCalledWith(SETTINGS, PTY, NATIVE_CHAT_SUBMIT) + expect(sendRuntimePtyInput).toHaveBeenCalledTimes(6) + }) + + it('does not serialize sends across different PTYs', () => { + sendNativeChatMessage(SETTINGS, 'pty-a', 'one') + sendNativeChatMessage(SETTINGS, 'pty-b', 'two') + expectWriteOrder(sendRuntimePtyInput.mock.calls, [ + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, + buildNativeChatPasteBytes('one'), + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, + buildNativeChatPasteBytes('two') + ]) + expect(sendRuntimePtyInput.mock.calls[1]?.[1]).toBe('pty-a') + expect(sendRuntimePtyInput.mock.calls[3]?.[1]).toBe('pty-b') + }) }) describe('sendNativeChatMessageVerified', () => { beforeEach(() => { vi.useFakeTimers() sendRuntimePtyInputVerified.mockReset().mockResolvedValue(true) + resetNativeChatPtySendQueuesForTests() }) afterEach(() => { vi.useRealTimers() + resetNativeChatPtySendQueuesForTests() }) - it('awaits body acceptance before the delayed Enter write', async () => { + it('awaits body acceptance before the delayed Enter write (no pre-clear)', async () => { + // Why: model-switch confirmation watches the PTY while this send runs; + // verified option commands must not inject Ctrl+U noise. const result = sendNativeChatMessageVerified(SETTINGS, PTY, '/model sonnet') - await Promise.resolve() - - expect(sendRuntimePtyInputVerified).toHaveBeenCalledTimes(1) + await vi.waitFor(() => { + expect(sendRuntimePtyInputVerified).toHaveBeenCalledTimes(1) + }) expect(sendRuntimePtyInputVerified).toHaveBeenCalledWith( SETTINGS, PTY, @@ -103,6 +184,11 @@ describe('sendNativeChatMessageVerified', () => { expect(await result).toBe(true) expect(sendRuntimePtyInputVerified).toHaveBeenLastCalledWith(SETTINGS, PTY, NATIVE_CHAT_SUBMIT) + expect( + sendRuntimePtyInputVerified.mock.calls.some( + (call) => call[2] === NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT + ) + ).toBe(false) }) it('does not send Enter when the body is rejected', async () => { @@ -112,16 +198,40 @@ describe('sendNativeChatMessageVerified', () => { await vi.runAllTimersAsync() expect(sendRuntimePtyInputVerified).toHaveBeenCalledTimes(1) + expect( + sendRuntimePtyInputVerified.mock.calls.some((call) => call[2] === NATIVE_CHAT_SUBMIT) + ).toBe(false) }) - it('cancels the pending Enter when its composer detaches', async () => { + it('cancels an in-flight chat Enter before delivering a verified option command', async () => { + sendNativeChatMessage(SETTINGS, PTY, 'hello') + expect(sendRuntimePtyInput).toHaveBeenCalled() + + const result = sendNativeChatMessageVerified(SETTINGS, PTY, '/model haiku') + // Chat cancel may Ctrl+U the unsubmitted body; Enter from chat must not fire. + await Promise.resolve() + await Promise.resolve() + await vi.advanceTimersByTimeAsync(NATIVE_CHAT_SUBMIT_DELAY_MS) + + expect(await result).toBe(true) + const submits = sendRuntimePtyInput.mock.calls.filter((call) => call[2] === NATIVE_CHAT_SUBMIT) + // Only the verified path's Enter — chat's delayed Enter was cancelled. + expect(submits).toHaveLength(0) + expect(sendRuntimePtyInputVerified).toHaveBeenCalledWith(SETTINGS, PTY, NATIVE_CHAT_SUBMIT) + }) + + it('returns false when the delayed Enter wait is aborted', async () => { const controller = new AbortController() const result = sendNativeChatMessageVerified(SETTINGS, PTY, '/model sonnet', controller.signal) - await Promise.resolve() + await vi.waitFor(() => { + expect(sendRuntimePtyInputVerified).toHaveBeenCalledTimes(1) + }) controller.abort() expect(await result).toBe(false) - expect(sendRuntimePtyInputVerified).toHaveBeenCalledTimes(1) + expect( + sendRuntimePtyInputVerified.mock.calls.some((call) => call[2] === NATIVE_CHAT_SUBMIT) + ).toBe(false) }) }) @@ -129,12 +239,14 @@ describe('sendNativeChatMessageWithImageAttachments', () => { beforeEach(() => { vi.useFakeTimers() sendRuntimePtyInput.mockClear() + resetNativeChatPtySendQueuesForTests() }) afterEach(() => { vi.useRealTimers() + resetNativeChatPtySendQueuesForTests() }) - it('bracket-pastes image paths before prompt text so the TUI creates image chips', () => { + it('clears the line, then bracket-pastes image paths before prompt text', () => { const handle = sendNativeChatMessageWithImageAttachments(SETTINGS, PTY, 'what do you see?', [ '/tmp/orca-paste-image.png' ]) @@ -143,15 +255,12 @@ describe('sendNativeChatMessageWithImageAttachments', () => { NATIVE_CHAT_IMAGE_ATTACHMENT_SETTLE_MS + NATIVE_CHAT_SUBMIT_DELAY_MS ) - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) - expect(sendRuntimePtyInput).toHaveBeenLastCalledWith( - SETTINGS, - PTY, + expectWriteOrder(sendRuntimePtyInput.mock.calls, [ + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, buildNativeChatImagePasteBytes('/tmp/orca-paste-image.png') - ) + ]) vi.advanceTimersByTime(NATIVE_CHAT_IMAGE_ATTACHMENT_SETTLE_MS) - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(2) expect(sendRuntimePtyInput).toHaveBeenLastCalledWith( SETTINGS, PTY, @@ -159,8 +268,8 @@ describe('sendNativeChatMessageWithImageAttachments', () => { ) vi.advanceTimersByTime(NATIVE_CHAT_SUBMIT_DELAY_MS) - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(3) expect(sendRuntimePtyInput).toHaveBeenLastCalledWith(SETTINGS, PTY, NATIVE_CHAT_SUBMIT) + expect(sendRuntimePtyInput).toHaveBeenCalledTimes(4) }) it('waits the normal submit gap for an attachment-only send', () => { @@ -170,11 +279,16 @@ describe('sendNativeChatMessageWithImageAttachments', () => { expect(handle.settleAfterMs).toBe(NATIVE_CHAT_SUBMIT_DELAY_MS) + expectWriteOrder(sendRuntimePtyInput.mock.calls, [ + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, + buildNativeChatImagePasteBytes('/tmp/orca-paste-image.png') + ]) + vi.advanceTimersByTime(NATIVE_CHAT_SUBMIT_DELAY_MS - 1) - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) + expect(sendRuntimePtyInput).toHaveBeenCalledTimes(2) vi.advanceTimersByTime(1) - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(2) + expect(sendRuntimePtyInput).toHaveBeenCalledTimes(3) expect(sendRuntimePtyInput).toHaveBeenLastCalledWith(SETTINGS, PTY, NATIVE_CHAT_SUBMIT) }) @@ -185,7 +299,15 @@ describe('sendNativeChatMessageWithImageAttachments', () => { handle.cancel() vi.runAllTimers() - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) + // Pre-clear + image body + cancel clear; no Enter. + expectWriteOrder(sendRuntimePtyInput.mock.calls, [ + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT, + buildNativeChatImagePasteBytes('/tmp/orca-paste-image.png'), + NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT + ]) + expect(sendRuntimePtyInput.mock.calls.some((call) => call[2] === NATIVE_CHAT_SUBMIT)).toBe( + false + ) }) }) @@ -196,9 +318,8 @@ describe('empty prompt submit', () => { it('submits an empty prompt with a bare Enter', () => { submitNativeChatPrompt(SETTINGS, PTY) - - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) - expect(sendRuntimePtyInput).toHaveBeenLastCalledWith(SETTINGS, PTY, NATIVE_CHAT_SUBMIT) + expect(sendRuntimePtyInput).toHaveBeenCalledOnce() + expect(sendRuntimePtyInput).toHaveBeenCalledWith(SETTINGS, PTY, NATIVE_CHAT_SUBMIT) }) }) @@ -213,72 +334,43 @@ describe('sendNativeChatAskAnswer', () => { vi.useRealTimers() }) - it('paces each keystroke a full step apart (the proven submit gap + advance buffer)', () => { - expect(NATIVE_CHAT_QUESTION_STEP_MS).toBe(1000) - expect(NATIVE_CHAT_ADVANCE_BUFFER_MS).toBe(500) - }) - - it('no writes and 0 settle for an empty keystroke list', () => { + it('returns a no-op handle for an empty key group list', () => { const handle = sendNativeChatAskAnswer(SETTINGS, PTY, []) expect(handle.settleAfterMs).toBe(0) - vi.runAllTimers() - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(0) + handle.cancel() + expect(sendRuntimePtyInput).not.toHaveBeenCalled() }) - it('single option-number keystroke: fires at t=0, settles a submit gap later', () => { - // The STA-1860 fix: a lone single-select pick is delivered as the option - // NUMBER, with no trailing Enter (the number both selects and commits). - const handle = sendNativeChatAskAnswer(SETTINGS, PTY, [{ raw: '2' }]) - expect(handle.settleAfterMs).toBe(NATIVE_CHAT_SUBMIT_DELAY_MS) - - // Scheduled at t=0 (setTimeout 0), not written synchronously. - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(0) - vi.advanceTimersByTime(0) - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) - expect(sendRuntimePtyInput).toHaveBeenLastCalledWith(SETTINGS, PTY, '2') - vi.runAllTimers() - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) - }) - - it('writes each group a step apart; text groups go through the paste framer', () => { - const groups = [{ raw: '3' }, { text: 'custom answer' }, { raw: '\r' }] - const handle = sendNativeChatAskAnswer(SETTINGS, PTY, groups) + it('paces key groups so selector steps render before the next write', () => { + const handle = sendNativeChatAskAnswer(SETTINGS, PTY, [ + { raw: '1' }, + { raw: '2' }, + { text: 'custom answer' } + ]) expect(handle.settleAfterMs).toBe( 2 * NATIVE_CHAT_QUESTION_STEP_MS + NATIVE_CHAT_SUBMIT_DELAY_MS ) vi.advanceTimersByTime(0) - expect(sendRuntimePtyInput).toHaveBeenLastCalledWith(SETTINGS, PTY, '3') + expect(sendRuntimePtyInput).toHaveBeenCalledWith(SETTINGS, PTY, '1') - // The next group must wait a full step so the "Type something" row renders. - vi.advanceTimersByTime(NATIVE_CHAT_QUESTION_STEP_MS - 1) - expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) - vi.advanceTimersByTime(1) + vi.advanceTimersByTime(NATIVE_CHAT_QUESTION_STEP_MS) + expect(sendRuntimePtyInput).toHaveBeenCalledWith(SETTINGS, PTY, '2') + + vi.advanceTimersByTime(NATIVE_CHAT_QUESTION_STEP_MS) expect(sendRuntimePtyInput).toHaveBeenLastCalledWith( SETTINGS, PTY, buildNativeChatPasteBytes('custom answer') ) - - vi.advanceTimersByTime(NATIVE_CHAT_QUESTION_STEP_MS) - expect(sendRuntimePtyInput).toHaveBeenLastCalledWith(SETTINGS, PTY, NATIVE_CHAT_SUBMIT) - - vi.runAllTimers() - const calls = sendRuntimePtyInput.mock.calls.map((c) => c[2]) - expect(calls).toEqual(['3', buildNativeChatPasteBytes('custom answer'), NATIVE_CHAT_SUBMIT]) }) - it('cancel clears every pending keystroke', () => { - const handle = sendNativeChatAskAnswer(SETTINGS, PTY, [ - { raw: '1' }, - { raw: '\x1b[C' }, - { raw: '\r' } - ]) + it('cancels remaining key group timers', () => { + const handle = sendNativeChatAskAnswer(SETTINGS, PTY, [{ raw: '1' }, { raw: '2' }]) vi.advanceTimersByTime(0) expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) handle.cancel() - vi.runAllTimers() - // Only the first keystroke landed; the rest were cancelled. + vi.advanceTimersByTime(NATIVE_CHAT_QUESTION_STEP_MS * 2) expect(sendRuntimePtyInput).toHaveBeenCalledTimes(1) }) @@ -317,3 +409,9 @@ describe('sendNativeChatAskAnswer', () => { await vi.waitFor(() => expect(onSettled).toHaveBeenCalledExactlyOnceWith(true)) }) }) + +describe('constants', () => { + it('exports the ask-answer advance buffer used by interactive cards', () => { + expect(NATIVE_CHAT_ADVANCE_BUFFER_MS).toBeGreaterThan(0) + }) +}) diff --git a/src/renderer/src/components/native-chat/native-chat-runtime-send.ts b/src/renderer/src/components/native-chat/native-chat-runtime-send.ts index f067064e047..33e653d073a 100644 --- a/src/renderer/src/components/native-chat/native-chat-runtime-send.ts +++ b/src/renderer/src/components/native-chat/native-chat-runtime-send.ts @@ -1,6 +1,6 @@ -// Runtime send for native chat: writes the framed message body, then the Enter -// as a SEPARATE delayed pty write. Kept apart from the pure byte builders in -// native-chat-send.ts so those stay IO-free and unit-testable without aliases. +// Runtime send for native chat: clear any unsubmitted TUI line, write the framed +// body, then Enter as a SEPARATE delayed pty write. Kept apart from the pure +// byte builders in native-chat-send.ts so those stay IO-free and unit-testable. import { sendRuntimePtyInput, @@ -18,11 +18,24 @@ import { buildNativeChatPasteBytes, NATIVE_CHAT_SUBMIT } from './native-chat-send' +import { + cancelNativeChatPtySends, + enqueueNativeChatPtySend, + resetNativeChatPtySendQueuesForTests, + waitForNativeChatPtyIdle +} from './native-chat-pty-send-queue' export { NATIVE_CHAT_ADVANCE_BUFFER_MS, NATIVE_CHAT_QUESTION_STEP_MS, NATIVE_CHAT_SUBMIT_DELAY_MS } +export { resetNativeChatPtySendQueuesForTests } export const NATIVE_CHAT_IMAGE_ATTACHMENT_SETTLE_MS = 300 +// Why: agent TUI composers treat Ctrl+U as kill-to-start-of-line. Chat sends +// start from an empty line so a prior cancelled paste cannot glue onto the next +// prompt. Not used on verified option commands — model-switch confirmation +// observes the PTY and Ctrl+U can miss confirmation markers. +export const NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT = '\x15' + /** Cancels an in-flight send's pending pty writes (the delayed Enter, and any * later question bodies/Enters). Safe to call after the send completes. */ export type NativeChatSendHandle = { @@ -31,22 +44,46 @@ export type NativeChatSendHandle = { settleAfterMs: number } +type RuntimeSettings = ReturnType + +function clearUnsubmittedAgentInput(settings: RuntimeSettings, ptyId: string): void { + sendRuntimePtyInput(settings, ptyId, NATIVE_CHAT_CLEAR_UNSUBMITTED_INPUT) +} + /** - * Send a native-chat message through the verified runtime pty path: framed body - * first, then a separate delayed Enter. `sendRuntimePtyInput` branches local - * pty:write vs remote runtime RPC, so this works for SSH panes too. Returns a - * cancel handle so callers can drop the still-pending Enter on unmount/stop. + * Chat message path: + * 1. clear any unsubmitted TUI line + * 2. write framed body + * 3. delayed Enter (separate write — same-write CR can be swallowed by paste) + * + * Serialized per PTY so rapid sends cannot glue before Enter. */ export function sendNativeChatMessage( - settings: ReturnType, + settings: RuntimeSettings, ptyId: string, text: string ): NativeChatSendHandle { - sendRuntimePtyInput(settings, ptyId, buildNativeChatPasteBytes(text)) - const timer = setTimeout(() => { - sendRuntimePtyInput(settings, ptyId, NATIVE_CHAT_SUBMIT) - }, NATIVE_CHAT_SUBMIT_DELAY_MS) - return { cancel: () => clearTimeout(timer), settleAfterMs: NATIVE_CHAT_SUBMIT_DELAY_MS } + return enqueueNativeChatPtySend( + ptyId, + NATIVE_CHAT_SUBMIT_DELAY_MS, + ({ isCancelled, delay, markSubmitted }) => { + if (isCancelled()) { + return + } + clearUnsubmittedAgentInput(settings, ptyId) + if (isCancelled()) { + return + } + sendRuntimePtyInput(settings, ptyId, buildNativeChatPasteBytes(text)) + delay(NATIVE_CHAT_SUBMIT_DELAY_MS, () => { + sendRuntimePtyInput(settings, ptyId, NATIVE_CHAT_SUBMIT) + markSubmitted() + }) + }, + { + onCancelUnsubmitted: () => clearUnsubmittedAgentInput(settings, ptyId) + } + ) } function waitForNativeChatSubmit(signal?: AbortSignal): Promise { @@ -70,12 +107,28 @@ function waitForNativeChatSubmit(signal?: AbortSignal): Promise { }) } +/** + * Session-option / slash command path (model switch, /effort, …). + * + * Does not pre-clear the line (model-switch confirmation watches the PTY). + * Cancels any in-flight chat clear/body/Enter on this PTY first so a delayed + * chat Enter cannot dismiss Claude's "Switch model?" dialog. + */ export async function sendNativeChatMessageVerified( - settings: ReturnType, + settings: RuntimeSettings, ptyId: string, text: string, signal?: AbortSignal ): Promise { + // Why: chat sends hold a delayed Enter for 500ms. Opening the model picker in + // that window used to let that Enter hit Claude's confirmation UI, so + // verification timed out with "Could not verify the model change". + cancelNativeChatPtySends(ptyId) + await waitForNativeChatPtyIdle(ptyId) + if (signal?.aborted) { + return false + } + // Why: option commands await remote/SSH acceptance so the Enter cannot race // ahead of the body while a model-change observer is already armed. const bodyAccepted = await sendRuntimePtyInputVerified( @@ -90,7 +143,7 @@ export async function sendNativeChatMessageVerified( } export function sendNativeChatMessageWithImageAttachments( - settings: ReturnType, + settings: RuntimeSettings, ptyId: string, text: string, imagePaths: readonly string[] @@ -98,62 +151,59 @@ export function sendNativeChatMessageWithImageAttachments( if (imagePaths.length === 0) { return sendNativeChatMessage(settings, ptyId, text) } - const timers: ReturnType[] = [] - for (const imagePath of imagePaths) { - sendRuntimePtyInput(settings, ptyId, buildNativeChatImagePasteBytes(imagePath)) - } const trimmedText = text.trim() - if (trimmedText.length > 0) { - timers.push( - setTimeout(() => { - sendRuntimePtyInput(settings, ptyId, buildNativeChatPasteBytes(text)) - }, NATIVE_CHAT_IMAGE_ATTACHMENT_SETTLE_MS) - ) - } - timers.push( - setTimeout( - () => { - sendRuntimePtyInput(settings, ptyId, NATIVE_CHAT_SUBMIT) - }, - trimmedText.length > 0 - ? NATIVE_CHAT_IMAGE_ATTACHMENT_SETTLE_MS + NATIVE_CHAT_SUBMIT_DELAY_MS - : NATIVE_CHAT_SUBMIT_DELAY_MS - ) - ) - return { - cancel: () => { - for (const timer of timers) { - clearTimeout(timer) + const durationMs = + trimmedText.length > 0 + ? NATIVE_CHAT_IMAGE_ATTACHMENT_SETTLE_MS + NATIVE_CHAT_SUBMIT_DELAY_MS + : NATIVE_CHAT_SUBMIT_DELAY_MS + return enqueueNativeChatPtySend( + ptyId, + durationMs, + ({ isCancelled, delay, markSubmitted }) => { + if (isCancelled()) { + return } + clearUnsubmittedAgentInput(settings, ptyId) + if (isCancelled()) { + return + } + for (const imagePath of imagePaths) { + sendRuntimePtyInput(settings, ptyId, buildNativeChatImagePasteBytes(imagePath)) + } + if (trimmedText.length > 0) { + delay(NATIVE_CHAT_IMAGE_ATTACHMENT_SETTLE_MS, () => { + sendRuntimePtyInput(settings, ptyId, buildNativeChatPasteBytes(text)) + }) + delay(NATIVE_CHAT_IMAGE_ATTACHMENT_SETTLE_MS + NATIVE_CHAT_SUBMIT_DELAY_MS, () => { + sendRuntimePtyInput(settings, ptyId, NATIVE_CHAT_SUBMIT) + markSubmitted() + }) + return + } + delay(NATIVE_CHAT_SUBMIT_DELAY_MS, () => { + sendRuntimePtyInput(settings, ptyId, NATIVE_CHAT_SUBMIT) + markSubmitted() + }) }, - settleAfterMs: - trimmedText.length > 0 - ? NATIVE_CHAT_IMAGE_ATTACHMENT_SETTLE_MS + NATIVE_CHAT_SUBMIT_DELAY_MS - : NATIVE_CHAT_SUBMIT_DELAY_MS - } + { + onCancelUnsubmitted: () => clearUnsubmittedAgentInput(settings, ptyId) + } + ) } /** Submit a TUI prompt with no body (Enter only) — e.g. a plain submit when the * composer is empty. */ -export function submitNativeChatPrompt( - settings: ReturnType, - ptyId: string -): void { +export function submitNativeChatPrompt(settings: RuntimeSettings, ptyId: string): void { sendRuntimePtyInput(settings, ptyId, NATIVE_CHAT_SUBMIT) } /** * Answer Claude's AskUserQuestion by writing its keystroke groups (built by * `buildAskAnswerKeys`) to the PTY, one group per `NATIVE_CHAT_QUESTION_STEP_MS` - * step so the arrow-navigate selector applies each before the next — a - * navigation/number keystroke batched with the Enter that follows would commit - * the wrong (default) option. `raw` groups are written verbatim as keystrokes; - * `text` groups (a free-text answer) go through the composer's paste framing. - * Returns a cancel handle clearing every pending timer so a detached sequence - * can't keep writing PTY bytes after unmount/stop. + * step so the arrow-navigate selector applies each before the next. */ export function sendNativeChatAskAnswer( - settings: ReturnType, + settings: RuntimeSettings, ptyId: string, groups: AskAnswerKeyGroup[], onSettled?: (delivered: boolean) => void diff --git a/src/renderer/src/components/native-chat/native-chat-session-transport.test.ts b/src/renderer/src/components/native-chat/native-chat-session-transport.test.ts index 849897c386b..47832b10897 100644 --- a/src/renderer/src/components/native-chat/native-chat-session-transport.test.ts +++ b/src/renderer/src/components/native-chat/native-chat-session-transport.test.ts @@ -82,6 +82,59 @@ describe('getNativeChatSessionTransport — selection', () => { expect(nativeChatReadSession).not.toHaveBeenCalled() }) + it('validates lifecycle metadata on runtime read responses', async () => { + markRuntimeEnvironmentCompatible(ENV) + const lifecycle = { state: 'completed', turnId: 'turn-1', timestamp: 42 } as const + runtimeEnvironmentsCall + .mockResolvedValueOnce(okEnvelope({ messages: [message('valid')], lifecycle })) + .mockResolvedValueOnce( + okEnvelope({ + messages: [message('invalid')], + lifecycle: { state: 'completed', turnId: '', timestamp: undefined } + }) + ) + const transport = getNativeChatSessionTransport(ENV) + + await expect(transport.readSession('claude', 'sess-1')).resolves.toEqual({ + messages: [message('valid')], + lifecycle + }) + // An invalid lifecycle payload is dropped, leaving prose recovery to settle. + await expect(transport.readSession('claude', 'sess-1')).resolves.toEqual({ + messages: [message('invalid')] + }) + }) + + it('accepts interrupted lifecycle metadata from a remote runtime', async () => { + markRuntimeEnvironmentCompatible(ENV) + const lifecycle = { state: 'interrupted', turnId: 'turn-2', timestamp: 43 } as const + runtimeEnvironmentsCall.mockResolvedValueOnce( + okEnvelope({ messages: [message('interrupted')], lifecycle }) + ) + const transport = getNativeChatSessionTransport(ENV) + + await expect(transport.readSession('codex', 'sess-1')).resolves.toEqual({ + messages: [message('interrupted')], + lifecycle + }) + }) + + it('keeps lifecycle metadata whose timestamp is omitted, normalizing it to null', async () => { + markRuntimeEnvironmentCompatible(ENV) + runtimeEnvironmentsCall.mockResolvedValueOnce( + okEnvelope({ + messages: [message('no-ts')], + lifecycle: { state: 'completed', turnId: 'turn-3' } + }) + ) + const transport = getNativeChatSessionTransport(ENV) + + await expect(transport.readSession('claude', 'sess-1')).resolves.toEqual({ + messages: [message('no-ts')], + lifecycle: { state: 'completed', turnId: 'turn-3', timestamp: null } + }) + }) + it('returns the local adapter on the web client even with an owner (R3 guard)', async () => { ;(window as unknown as { __ORCA_WEB_CLIENT__?: boolean }).__ORCA_WEB_CLIENT__ = true nativeChatReadSession.mockResolvedValue({ messages: [] }) @@ -153,6 +206,64 @@ describe('runtime subscribe', () => { }) }) + it('validates lifecycle metadata on runtime stream frames', async () => { + markRuntimeEnvironmentCompatible(ENV) + const { deliver } = stubSubscribe() + const onFrame = vi.fn() + const transport = getNativeChatSessionTransport(ENV) + const lifecycle = { state: 'completed', turnId: 'turn-1', timestamp: 42 } as const + + transport.subscribe({ subscriptionId: 's-1', agent: 'claude', sessionId: 'sess-1' }, onFrame) + await Promise.resolve() + deliver({ + type: 'snapshot', + messages: [message('valid')], + lifecycle + }) + deliver({ + type: 'appended', + messages: [message('invalid')], + lifecycle: { state: 'completed', turnId: '', timestamp: undefined } + }) + + expect(onFrame).toHaveBeenNthCalledWith(1, { + type: 'snapshot', + messages: [message('valid')], + hasMore: false, + lifecycle + }) + expect(onFrame).toHaveBeenNthCalledWith(2, { + type: 'appended', + messages: [message('invalid')] + }) + }) + + it('omits an invalid lifecycle payload from a stream frame', async () => { + markRuntimeEnvironmentCompatible(ENV) + const { deliver } = stubSubscribe() + const onFrame = vi.fn() + const transport = getNativeChatSessionTransport(ENV) + + transport.subscribe({ subscriptionId: 's-1', agent: 'claude', sessionId: 'sess-1' }, onFrame) + await Promise.resolve() + deliver({ + type: 'snapshot', + messages: [message('seed')], + hasMore: false, + lifecycle: { state: 'completed', turnId: 'turn-ok', timestamp: 1 } + }) + deliver({ + type: 'appended', + messages: [message('bad-lifecycle')], + lifecycle: { state: 'completed', turnId: '', timestamp: undefined } + }) + + expect(onFrame).toHaveBeenNthCalledWith(2, { + type: 'appended', + messages: [message('bad-lifecycle')] + }) + }) + it('settles with an empty snapshot when the first ok frame is an unrecognized shape', async () => { markRuntimeEnvironmentCompatible(ENV) const { deliver } = stubSubscribe() diff --git a/src/renderer/src/components/native-chat/native-chat-session-transport.ts b/src/renderer/src/components/native-chat/native-chat-session-transport.ts index a57d255b6ca..0fe22211db0 100644 --- a/src/renderer/src/components/native-chat/native-chat-session-transport.ts +++ b/src/renderer/src/components/native-chat/native-chat-session-transport.ts @@ -1,8 +1,4 @@ -import type { - NativeChatApi, - NativeChatAppendedMessages, - NativeChatReadSessionResult -} from '../../../../preload/api-types' +import type { NativeChatApi, NativeChatAppendedMessages } from '../../../../preload/api-types' import { isWebClientLocation } from '@/lib/web-client-location' import { callRuntimeRpc, @@ -10,6 +6,11 @@ import { type RuntimeClientTarget } from '@/runtime/runtime-rpc-client' import { isRuntimeCompatBlockError } from '@/runtime/runtime-protocol-compat' +import { + parseRuntimeNativeChatReadSessionResult, + parseRuntimeNativeChatTurnLifecycle, + RUNTIME_NATIVE_CHAT_READ_ERROR +} from './native-chat-runtime-contract' /** The read/subscribe surface the live-session hook needs, decoupled from where * the transcript actually lives. Same shape as `window.api.nativeChat`, so the @@ -34,7 +35,7 @@ export function toRuntimeNativeChatErrorMessage(err: unknown): string { if (isRuntimeCompatBlockError(err)) { return RUNTIME_TOO_OLD } - return "Couldn't read agent chat from the remote runtime." + return RUNTIME_NATIVE_CHAT_READ_ERROR } /** Delegates straight to the local Electron IPC bridge. On the web client @@ -53,12 +54,13 @@ function createRuntimeNativeChatTransport(environmentId: string): NativeChatSess return { readSession: async (agent, sessionId, limit, transcriptPath) => { try { - return await callRuntimeRpc( + const result = await callRuntimeRpc( target, 'nativeChat.readSession', { agent, sessionId, limit, transcriptPath }, { timeoutMs: 15_000 } ) + return parseRuntimeNativeChatReadSessionResult(result) } catch (err) { return { error: toRuntimeNativeChatErrorMessage(err) } } @@ -131,7 +133,9 @@ function createRuntimeNativeChatTransport(environmentId: string): NativeChatSess messages?: NativeChatAppendedMessages hasMore?: boolean error?: string + lifecycle?: unknown } + const lifecycle = parseRuntimeNativeChatTurnLifecycle(frame?.lifecycle) if ( (frame?.type === 'appended' || frame?.type === 'snapshot' || @@ -144,14 +148,16 @@ function createRuntimeNativeChatTransport(environmentId: string): NativeChatSess type: 'snapshot', messages: frame.messages, hasMore: frame.hasMore ?? frame.messages.length >= (limit ?? 300), - ...(frame.error ? { error: frame.error } : {}) + ...(frame.error ? { error: frame.error } : {}), + ...(lifecycle ? { lifecycle } : {}) }) } else if (frame.type === 'snapshot') { onFrame({ type: 'snapshot', messages: frame.messages, hasMore: frame.hasMore ?? false, - ...(frame.error ? { error: frame.error } : {}) + ...(frame.error ? { error: frame.error } : {}), + ...(lifecycle ? { lifecycle } : {}) }) } else { onFrame( @@ -159,9 +165,14 @@ function createRuntimeNativeChatTransport(environmentId: string): NativeChatSess ? { type: 'replacement', messages: frame.messages, - hasMore: frame.hasMore ?? false + hasMore: frame.hasMore ?? false, + ...(lifecycle ? { lifecycle } : {}) + } + : { + type: 'appended', + messages: frame.messages, + ...(lifecycle ? { lifecycle } : {}) } - : { type: 'appended', messages: frame.messages } ) } } else if (!receivedInitial) { diff --git a/src/renderer/src/components/native-chat/native-chat-working-suppression.test.ts b/src/renderer/src/components/native-chat/native-chat-working-suppression.test.ts index a82bb964ec3..a603e31cdc9 100644 --- a/src/renderer/src/components/native-chat/native-chat-working-suppression.test.ts +++ b/src/renderer/src/components/native-chat/native-chat-working-suppression.test.ts @@ -9,8 +9,7 @@ describe('native chat working suppression', () => { expect( shouldShowNativeChatWorking({ isConversation: true, - viewWorking: true, - hookWorking: true, + working: true, interrupted: true }) ).toBe(false) @@ -20,19 +19,33 @@ describe('native chat working suppression', () => { expect( shouldShowNativeChatWorking({ isConversation: true, - viewWorking: false, - hookWorking: true, + working: true, interrupted: false }) ).toBe(true) }) - it('clears suppression only after all working signals clear', () => { - expect(shouldClearNativeChatWorkingSuppression({ viewWorking: true, hookWorking: false })).toBe( - false - ) + it('clears suppression after reconciled working clears', () => { + expect(shouldClearNativeChatWorkingSuppression({ working: true })).toBe(false) + expect(shouldClearNativeChatWorkingSuppression({ working: false })).toBe(true) + }) + + it('clears suppression when a newer working epoch starts while interrupted', () => { expect( - shouldClearNativeChatWorkingSuppression({ viewWorking: false, hookWorking: false }) + shouldClearNativeChatWorkingSuppression({ + working: true, + interrupted: true, + workingEpoch: 20, + previousWorkingEpoch: 10 + }) ).toBe(true) + expect( + shouldClearNativeChatWorkingSuppression({ + working: true, + interrupted: true, + workingEpoch: 10, + previousWorkingEpoch: 10 + }) + ).toBe(false) }) }) diff --git a/src/renderer/src/components/native-chat/native-chat-working-suppression.ts b/src/renderer/src/components/native-chat/native-chat-working-suppression.ts index e265ae517b6..f179798373e 100644 --- a/src/renderer/src/components/native-chat/native-chat-working-suppression.ts +++ b/src/renderer/src/components/native-chat/native-chat-working-suppression.ts @@ -1,16 +1,35 @@ export function shouldShowNativeChatWorking(args: { isConversation: boolean - viewWorking: boolean - hookWorking: boolean + working: boolean interrupted: boolean }): boolean { - const rawWorking = args.isConversation && (args.viewWorking || args.hookWorking) - return rawWorking && !args.interrupted + return args.isConversation && args.working && !args.interrupted } +/** + * Clear local Stop suppression when live work ends, or when a newer working + * epoch starts while suppressed (Stop → immediate next turn without a ready gap). + */ export function shouldClearNativeChatWorkingSuppression(args: { - viewWorking: boolean - hookWorking: boolean + working: boolean + interrupted?: boolean + /** Hook `stateStartedAt` for the current working epoch, when known. */ + workingEpoch?: number | null + /** Previous observed working epoch; used to detect a new generation. */ + previousWorkingEpoch?: number | null }): boolean { - return !args.viewWorking && !args.hookWorking + if (!args.working) { + return true + } + // Why: interrupt + next-turn can coalesce so `working` never goes false; a + // newer epoch means the user started another generation and must see it. + if ( + args.interrupted === true && + args.workingEpoch != null && + args.previousWorkingEpoch != null && + args.workingEpoch > args.previousWorkingEpoch + ) { + return true + } + return false } diff --git a/src/renderer/src/components/native-chat/use-native-chat-hook-status.ts b/src/renderer/src/components/native-chat/use-native-chat-hook-status.ts new file mode 100644 index 00000000000..0d462dd067d --- /dev/null +++ b/src/renderer/src/components/native-chat/use-native-chat-hook-status.ts @@ -0,0 +1,27 @@ +import { useAppStore } from '../../store' +import type { AgentStatusState } from '../../../../shared/agent-status-types' + +export function useNativeChatHookStatus( + paneKey: string +): readonly [AgentStatusState | null, number | null, boolean] { + // Why: primitive selectors keep unrelated pane/status updates from rerendering + // native chat while still exposing the three fields used for reconciliation. + const state = useAppStore((store) => store.agentStatusByPaneKey[paneKey]?.state ?? null) + const stateStartedAt = useAppStore( + (store) => store.agentStatusByPaneKey[paneKey]?.stateStartedAt ?? null + ) + // Why: only children that started during the current parent working epoch can + // keep the session working after lead completion. Prior-turn roster leftovers + // (missed SubagentStop, pane reuse) must not veto settle forever. + const hasWorkingSubagents = useAppStore((store) => { + const entry = store.agentStatusByPaneKey[paneKey] + const epochStart = entry?.stateStartedAt + return ( + entry?.subagents?.some( + (subagent) => + subagent.state === 'working' && (epochStart == null || subagent.startedAt >= epochStart) + ) ?? false + ) + }) + return [state, stateStartedAt, hasWorkingSubagents] +} diff --git a/src/renderer/src/components/native-chat/use-native-chat-live-session.test.ts b/src/renderer/src/components/native-chat/use-native-chat-live-session.test.ts index 89ae267c8e5..821abbe8115 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-live-session.test.ts +++ b/src/renderer/src/components/native-chat/use-native-chat-live-session.test.ts @@ -99,9 +99,19 @@ describe('mergeNativeChatLiveSession', () => { expect(session.status).toBe('working') }) - it("self-heals a stale 'working' once this turn's assistant reply lands", () => { - // Trailing assistant reply (ts 2) post-dates the working turn's start (ts 1), - // so a dropped/late Stop hook must not strand 'working' on a visible reply. + it('does not treat assistant prose as turn completion while lifecycle is mid-generation', () => { + const session = mergeNativeChatLiveSession({ + sources: { transcript: [user('u-1', 'go'), assistant('a-1', 'done')] }, + sessionId: 'sess', + agent: 'claude', + hookState: 'working', + stateStartedAt: 1, + transcriptLifecycle: { state: 'working', turnId: 'u-1', timestamp: 1 } + }) + expect(session.status).toBe('working') + }) + + it('recovers via assistant prose when capable host has no in-progress lifecycle', () => { const session = mergeNativeChatLiveSession({ sources: { transcript: [user('u-1', 'go'), assistant('a-1', 'done')] }, sessionId: 'sess', @@ -112,19 +122,120 @@ describe('mergeNativeChatLiveSession', () => { expect(session.status).toBe('ready') }) - it("keeps 'working' when the trailing assistant reply predates the working turn", () => { - // Trailing reply (ts 2) is older than the new working turn (started at ts 5), - // so the agent is working again — the stale reply must not suppress 'working'. + it('settles a dropped working hook from an explicit completion marker', () => { const session = mergeNativeChatLiveSession({ - sources: { transcript: [user('u-1', 'go'), assistant('a-1', 'prior')] }, + sources: { transcript: [user('u-1', 'go'), assistant('a-1', 'done')] }, sessionId: 'sess', agent: 'claude', hookState: 'working', - stateStartedAt: 5 + stateStartedAt: 1, + transcriptLifecycle: { state: 'completed', turnId: 'turn-1', timestamp: 2 } + }) + expect(session.status).toBe('ready') + }) + + it('settles a dropped working hook from an explicit interruption marker', () => { + const session = mergeNativeChatLiveSession({ + sources: { transcript: [user('u-1', 'go')] }, + sessionId: 'sess', + agent: 'claude', + hookState: 'working', + stateStartedAt: 1, + transcriptLifecycle: { state: 'interrupted', turnId: 'turn-1', timestamp: 2 } + }) + expect(session.status).toBe('ready') + }) + + it('does not apply an older completion marker to a newer working turn', () => { + const session = mergeNativeChatLiveSession({ + sources: { transcript: [assistant('a-1', 'prior')] }, + sessionId: 'sess', + agent: 'claude', + hookState: 'working', + stateStartedAt: 5, + transcriptLifecycle: { state: 'completed', turnId: 'turn-1', timestamp: 2 } }) expect(session.status).toBe('working') }) + it('does not apply an older interruption marker to a newer working turn', () => { + const session = mergeNativeChatLiveSession({ + sources: { transcript: [assistant('a-1', 'prior')] }, + sessionId: 'sess', + agent: 'claude', + hookState: 'working', + stateStartedAt: 5, + transcriptLifecycle: { state: 'interrupted', turnId: 'turn-1', timestamp: 2 } + }) + expect(session.status).toBe('working') + }) + + it('settles an unorderable (null-timestamp) completion marker for live work', () => { + const session = mergeNativeChatLiveSession({ + sources: { transcript: [assistant('a-1', 'prior')] }, + sessionId: 'sess', + agent: 'claude', + hookState: 'working', + stateStartedAt: 5, + transcriptLifecycle: { state: 'completed', turnId: 'turn-1', timestamp: null } + }) + expect(session.status).toBe('ready') + }) + + it('settles a completion slightly before hook receipt within clock-skew slack', () => { + const hookStartedAt = 1_700_000_000_000 + const session = mergeNativeChatLiveSession({ + sources: { transcript: [assistant('a-1', 'done')] }, + sessionId: 'sess', + agent: 'claude', + hookState: 'working', + stateStartedAt: hookStartedAt, + transcriptLifecycle: { + state: 'completed', + turnId: 'turn-1', + timestamp: hookStartedAt - 500 + } + }) + expect(session.status).toBe('ready') + }) + + it('preserves the assistant fallback when the serving host lacks explicit boundaries', () => { + const session = mergeNativeChatLiveSession({ + sources: { transcript: [assistant('a-1', 'done')] }, + sessionId: 'sess', + agent: 'grok', + hookState: 'working', + stateStartedAt: 1 + }) + expect(session.status).toBe('ready') + }) + + it('keeps working while the hook reports a live background child', () => { + const session = mergeNativeChatLiveSession({ + sources: { transcript: [assistant('a-1', 'lead done')] }, + sessionId: 'sess', + agent: 'claude', + hookState: 'working', + stateStartedAt: 1, + transcriptLifecycle: { state: 'completed', turnId: 'turn-1', timestamp: 2 }, + hookHasWorkingSubagents: true + }) + expect(session.status).toBe('working') + }) + + it('settles on an interruption even while the hook reports a live background child', () => { + const session = mergeNativeChatLiveSession({ + sources: { transcript: [assistant('a-1', 'lead done')] }, + sessionId: 'sess', + agent: 'claude', + hookState: 'working', + stateStartedAt: 1, + transcriptLifecycle: { state: 'interrupted', turnId: 'turn-1', timestamp: 2 }, + hookHasWorkingSubagents: true + }) + expect(session.status).toBe('ready') + }) + it('leaves completed states (done/waiting/blocked) on the derived status', () => { const session = mergeNativeChatLiveSession({ sources: { transcript: [user('u-1', 'hi')] }, @@ -135,7 +246,7 @@ describe('mergeNativeChatLiveSession', () => { expect(session.status).toBe('ready') }) - it('honors loading and error overrides outright', () => { + it('surfaces live work while the transcript loads and honors errors outright', () => { expect( mergeNativeChatLiveSession({ sources: { transcript: [] }, @@ -144,7 +255,7 @@ describe('mergeNativeChatLiveSession', () => { hookState: 'working', loading: true }).status - ).toBe('loading') + ).toBe('working') const errored = mergeNativeChatLiveSession({ sources: { transcript: [] }, @@ -475,9 +586,7 @@ describe('useNativeChatLiveSession — transport routing', () => { expect(latest?.messages.map((message) => message.id)).toEqual(['u-live']) }) - it("self-heals a stale 'working' hook once this turn's reply has landed", async () => { - // The hook must read stateStartedAt from the store and thread it into the - // merge, so a dropped Stop hook doesn't strand 'Agent is working'. + it("self-heals a stale 'working' hook once the turn-complete marker lands", async () => { useAppStore.setState({ agentStatusByPaneKey: { [PANE]: { state: 'working', stateStartedAt: 1 } as never } }) @@ -487,11 +596,167 @@ describe('useNativeChatLiveSession — transport routing', () => { transport.emit({ type: 'snapshot', messages: [user('u-1', 'go'), assistant('a-1', 'done')], - hasMore: false + hasMore: false, + lifecycle: { state: 'completed', turnId: 'turn-1', timestamp: 2 } + }) + ) + + expect(latest?.status).toBe('ready') + }) + + it('applies a lifecycle-only append after the final message frame', async () => { + useAppStore.setState({ + agentStatusByPaneKey: { [PANE]: { state: 'working', stateStartedAt: 1 } as never } + }) + const transport = getMockTransport('env-1') + await render({ paneKey: PANE, agent: AGENT, sessionId: SESSION, runtimeEnvironmentId: 'env-1' }) + await act(async () => + transport.emit({ + type: 'snapshot', + messages: [user('u-1', 'go'), assistant('a-1', 'done')], + hasMore: false, + lifecycle: { state: 'working', turnId: 'turn-1', timestamp: 1 } + }) + ) + expect(latest?.status).toBe('working') + + await act(async () => + transport.emit({ + type: 'appended', + messages: [], + lifecycle: { state: 'completed', turnId: 'turn-1', timestamp: 2 } + }) + ) + + expect(latest?.status).toBe('ready') + }) + + it('applies a terminal-side interruption frame without a local Stop action', async () => { + useAppStore.setState({ + agentStatusByPaneKey: { [PANE]: { state: 'working', stateStartedAt: 1 } as never } + }) + const transport = getMockTransport('env-1') + await render({ paneKey: PANE, agent: AGENT, sessionId: SESSION, runtimeEnvironmentId: 'env-1' }) + await act(async () => + transport.emit({ + type: 'snapshot', + messages: [user('u-1', 'go')], + hasMore: false, + lifecycle: { state: 'working', turnId: 'turn-1', timestamp: 1 } + }) + ) + expect(latest?.status).toBe('working') + + await act(async () => + transport.emit({ + type: 'appended', + messages: [], + lifecycle: { state: 'interrupted', turnId: 'turn-1', timestamp: 2 } + }) + ) + + expect(latest?.status).toBe('ready') + }) + + it('does not let an older pagination read rewind a live completion', async () => { + useAppStore.setState({ + agentStatusByPaneKey: { [PANE]: { state: 'working', stateStartedAt: 1 } as never } + }) + const transport = getMockTransport('env-1') + const many = Array.from({ length: NATIVE_CHAT_INITIAL_LIMIT }, (_unused, index) => + assistant(`m-${index}`, 'working') + ) + await render({ paneKey: PANE, agent: AGENT, sessionId: SESSION, runtimeEnvironmentId: 'env-1' }) + await act(async () => + transport.emit({ + type: 'snapshot', + messages: many, + hasMore: true, + lifecycle: { state: 'working', turnId: 'turn-1', timestamp: 1 } + }) + ) + + let resolveEarlier: (result: { + messages: NativeChatMessage[] + lifecycle: { state: 'working'; turnId: string; timestamp: number } + }) => void = () => {} + transport.readSession.mockImplementationOnce( + () => new Promise((resolve) => (resolveEarlier = resolve)) + ) + await act(async () => latest?.loadEarlier()) + await act(async () => + transport.emit({ + type: 'appended', + messages: [], + lifecycle: { state: 'completed', turnId: 'turn-1', timestamp: 2 } + }) + ) + expect(latest?.status).toBe('ready') + + await act(async () => { + resolveEarlier({ + messages: many, + lifecycle: { state: 'working', turnId: 'turn-1', timestamp: 1 } + }) + await Promise.resolve() + }) + + expect(latest?.status).toBe('ready') + }) + + it('reconciles completion from a reconnect snapshot', async () => { + useAppStore.setState({ + agentStatusByPaneKey: { [PANE]: { state: 'working', stateStartedAt: 10 } as never } + }) + const transport = getMockTransport('env-1') + await render({ paneKey: PANE, agent: AGENT, sessionId: SESSION, runtimeEnvironmentId: 'env-1' }) + await act(async () => + transport.emit({ + type: 'snapshot', + messages: [user('u-1', 'go')], + hasMore: false, + lifecycle: { state: 'working', turnId: 'turn-1', timestamp: 10 } + }) + ) + expect(latest?.status).toBe('working') + + await act(async () => + transport.emit({ + type: 'snapshot', + messages: [user('u-1', 'go'), assistant('a-1', 'done')], + hasMore: false, + lifecycle: { state: 'completed', turnId: 'turn-1', timestamp: 20 } + }) + ) + + expect(latest?.status).toBe('ready') + }) + + it('reconciles interruption from a reconnect snapshot', async () => { + useAppStore.setState({ + agentStatusByPaneKey: { [PANE]: { state: 'working', stateStartedAt: 10 } as never } + }) + const transport = getMockTransport('env-1') + await render({ paneKey: PANE, agent: AGENT, sessionId: SESSION, runtimeEnvironmentId: 'env-1' }) + await act(async () => + transport.emit({ + type: 'snapshot', + messages: [user('u-1', 'go')], + hasMore: false, + lifecycle: { state: 'working', turnId: 'turn-1', timestamp: 10 } + }) + ) + expect(latest?.status).toBe('working') + + await act(async () => + transport.emit({ + type: 'snapshot', + messages: [user('u-1', 'go')], + hasMore: false, + lifecycle: { state: 'interrupted', turnId: 'turn-1', timestamp: 20 } }) ) - // The reply (ts 2) post-dates stateStartedAt (1): the stuck 'working' heals. expect(latest?.status).toBe('ready') }) }) diff --git a/src/renderer/src/components/native-chat/use-native-chat-live-session.ts b/src/renderer/src/components/native-chat/use-native-chat-live-session.ts index ec1ebbc8226..bcca032fac7 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-live-session.ts +++ b/src/renderer/src/components/native-chat/use-native-chat-live-session.ts @@ -1,5 +1,4 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useAppStore } from '../../store' import { NATIVE_CHAT_SOURCE_PRIORITY, type AgentType, @@ -23,6 +22,8 @@ import { nextNativeChatLimit } from './native-chat-pagination' import { getNativeChatSessionTransport } from './native-chat-session-transport' +import { useNativeChatTranscriptLifecycle } from './use-native-chat-transcript-lifecycle' +import { useNativeChatHookStatus } from './use-native-chat-hook-status' export type UseNativeChatLiveSessionArgs = { /** Composite `${tabId}:${leafId}` key — selects the live hook entry. */ @@ -126,6 +127,7 @@ export function useNativeChatLiveSession( const [read, setRead] = useState({ phase: 'loading' }) const [hasMore, setHasMore] = useState(false) const [loadingEarlier, setLoadingEarlier] = useState(false) + const [transcriptLifecycle, transcriptLifecycleControl] = useNativeChatTranscriptLifecycle() // The active read window; raised by loadEarlier to page in older history. const limitRef = useRef(NATIVE_CHAT_INITIAL_LIMIT) @@ -139,14 +141,7 @@ export function useNativeChatLiveSession( // live frame costs O(incoming), not O(existing) (#18 parity for desktop). const appendMergerRef = useRef(createNativeChatMerger(NATIVE_CHAT_SOURCE_PRIORITY)) - // Live hook state for this pane, selected narrowly so unrelated status churn - // doesn't re-render the chat view. - const hookState = useAppStore((s) => s.agentStatusByPaneKey[paneKey]?.state ?? null) - // When that state began (epoch ms). A separate primitive selector so it doesn't - // churn renders; lets a stale 'working' self-heal once this turn's reply lands. - const hookStateStartedAt = useAppStore( - (s) => s.agentStatusByPaneKey[paneKey]?.stateStartedAt ?? null - ) + const [hookState, hookStateStartedAt, hookHasWorkingSubagents] = useNativeChatHookStatus(paneKey) const latestSessionId = useRef(sessionId) latestSessionId.current = sessionId @@ -170,6 +165,7 @@ export function useNativeChatLiveSession( // every source generation must invalidate pagination captured before it. transcriptEpochRef.current += 1 setLoadingEarlier(false) + transcriptLifecycleControl.reset() if (!sessionId) { // No session id yet: nothing to read or tail. Surface live hook state on // an empty transcript; backfills once the id arrives (effect re-runs). @@ -225,6 +221,7 @@ export function useNativeChatLiveSession( return } const messages = result?.messages ?? [] + transcriptLifecycleControl.replace(result?.lifecycle) setRead({ phase: 'ready', messages }) setHasMore(hasMoreNativeChatHistory(messages.length, limitRef.current)) }) @@ -258,12 +255,14 @@ export function useNativeChatLiveSession( setRead({ phase: 'error', error: frame.error }) return } + transcriptLifecycleControl.replace(frame.lifecycle) replaceList(appendMergerRef.current, frame.messages) setAppended([]) setRead({ phase: 'ready', messages: appendMergerRef.current.list }) setHasMore(frame.hasMore) return } + transcriptLifecycleControl.append(frame.lifecycle) // Merge by id (re-emits replace in place) then bound to the window so // the bucket can't grow without limit. The base read still holds older // turns, and the assembler re-dedups the concat, so trimming the recent @@ -296,7 +295,7 @@ export function useNativeChatLiveSession( } // `transport` identity changes on an owner flip, re-running this effect to // tear down the old host's subscription and open one against the new host. - }, [agent, sessionId, transcriptPath, transport]) + }, [agent, sessionId, transcriptPath, transport, transcriptLifecycleControl]) const loadEarlier = useCallback(() => { if (!sessionId || loadingEarlier || !hasMore || read.phase !== 'ready') { @@ -304,6 +303,7 @@ export function useNativeChatLiveSession( } const nextLimit = nextNativeChatLimit(limitRef.current) const requestEpoch = transcriptEpochRef.current + const lifecycleRevision = transcriptLifecycleControl.revision() setLoadingEarlier(true) void transport .readSession(agent, sessionId, nextLimit, transcriptPath ?? undefined) @@ -324,6 +324,7 @@ export function useNativeChatLiveSession( // Read results are an ordered tail — replace the base list so the older // page prepends in order; live appends stay in their separate bucket. setRead({ phase: 'ready', messages: result.messages }) + transcriptLifecycleControl.replaceFromPagination(result.lifecycle, lifecycleRevision) setHasMore(hasMoreNativeChatHistory(result.messages.length, nextLimit)) }) .catch(() => { @@ -339,7 +340,16 @@ export function useNativeChatLiveSession( setLoadingEarlier(false) } }) - }, [agent, sessionId, transcriptPath, transport, hasMore, loadingEarlier, read.phase]) + }, [ + agent, + sessionId, + transcriptPath, + transport, + hasMore, + loadingEarlier, + read.phase, + transcriptLifecycleControl + ]) // Assembled messages reuse the incremental assembler across appends. Computed // outside the status memo: hookState changes only the status override, not the @@ -383,6 +393,8 @@ export function useNativeChatLiveSession( agent, hookState, stateStartedAt: hookStateStartedAt, + transcriptLifecycle, + hookHasWorkingSubagents, // Why: a watcher append (fix for #8401) can land content while the read is // still retrying ('loading') or after it settled into 'error' — in both // cases showing the live content beats a spinner or a stale error, so each @@ -398,6 +410,8 @@ export function useNativeChatLiveSession( agent, hookState, hookStateStartedAt, + transcriptLifecycle, + hookHasWorkingSubagents, hasMore, loadingEarlier, loadEarlier, diff --git a/src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts b/src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts index 9720ab9f950..d57fa2110d9 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts +++ b/src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts @@ -7,6 +7,7 @@ import { } from './native-chat-composer-target' import { pushHistory, type HistoryState } from './native-chat-composer-state' import { sendNativeChatMessageVerified } from './native-chat-runtime-send' +import { cancelNativeChatPtySends, waitForNativeChatPtyIdle } from './native-chat-pty-send-queue' import { createClaudeModelSwitchConfirmationObserver, type ClaudeModelSwitchConfirmationObserver @@ -53,30 +54,38 @@ export function useNativeChatSessionOptionCommand(args: { if (!target || disabled) { throw new Error('No live terminal is available.') } - const detectClaudeConfirmation = - options?.detectAgentInteraction === 'claude-model-switch-confirmation' - let observer: ClaudeModelSwitchConfirmationObserver | null = null - if (detectClaudeConfirmation) { - observer = createClaudeModelSwitchConfirmationObserver({ - ptyId: target.ptyId, - settings: target.settings, - expectedModelLabel: options?.expectedChoiceLabel ?? null - }) - activeObserversRef.current.add(observer) - await observer.ready - if (!mountedRef.current) { - activeObserversRef.current.delete(observer) - observer.dispose() - throw new Error('Native chat command was canceled because the composer closed.') - } - // Why: arm only after the observer reaches the live PTY tail, then - // submit immediately so historical output cannot satisfy the match. - observer.arm() - } const sendController = new AbortController() activeSendsRef.current.add(sendController) + // Why: block composer chat sends for the whole drain+observe+verify window. setIsDispatching(true) + let observer: ClaudeModelSwitchConfirmationObserver | null = null try { + // Why: chat sends keep a delayed Enter for 500ms. Drain them *before* + // arming the model-switch observer so (a) that Enter cannot hit Claude's + // confirmation UI and (b) any Ctrl+U cleanup is outside the observation + // window (Ctrl+U mid-observe can miss "Set model to …" markers). + cancelNativeChatPtySends(target.ptyId) + await waitForNativeChatPtyIdle(target.ptyId) + if (!mountedRef.current || sendController.signal.aborted) { + throw new Error('Native chat command was canceled because the composer closed.') + } + const detectClaudeConfirmation = + options?.detectAgentInteraction === 'claude-model-switch-confirmation' + if (detectClaudeConfirmation) { + observer = createClaudeModelSwitchConfirmationObserver({ + ptyId: target.ptyId, + settings: target.settings, + expectedModelLabel: options?.expectedChoiceLabel ?? null + }) + activeObserversRef.current.add(observer) + await observer.ready + if (!mountedRef.current || sendController.signal.aborted) { + throw new Error('Native chat command was canceled because the composer closed.') + } + // Why: arm only after the observer reaches the live PTY tail, then + // submit immediately so historical output cannot satisfy the match. + observer.arm() + } const accepted = await sendNativeChatMessageVerified( target.settings, target.ptyId, diff --git a/src/renderer/src/components/native-chat/use-native-chat-transcript-lifecycle.ts b/src/renderer/src/components/native-chat/use-native-chat-transcript-lifecycle.ts new file mode 100644 index 00000000000..92126528a9a --- /dev/null +++ b/src/renderer/src/components/native-chat/use-native-chat-transcript-lifecycle.ts @@ -0,0 +1,54 @@ +import { useCallback, useMemo, useRef, useState } from 'react' +import type { NativeChatTurnLifecycle } from '../../../../shared/native-chat-types' + +type TranscriptLifecycleState = { + lifecycle?: NativeChatTurnLifecycle +} + +type TranscriptLifecycleControl = { + reset: () => void + replace: (lifecycle: NativeChatTurnLifecycle | undefined) => void + append: (lifecycle: NativeChatTurnLifecycle | undefined) => void + revision: () => number + replaceFromPagination: (lifecycle: NativeChatTurnLifecycle | undefined, revision: number) => void +} + +export function useNativeChatTranscriptLifecycle(): readonly [ + NativeChatTurnLifecycle | undefined, + TranscriptLifecycleControl +] { + const [state, setState] = useState({}) + // Why: pagination may resolve after a live completion; its older boundary + // can update history only when no live lifecycle write won the race. + const revisionRef = useRef(0) + + const replace = useCallback((lifecycle: NativeChatTurnLifecycle | undefined): void => { + revisionRef.current += 1 + setState({ lifecycle }) + }, []) + const reset = useCallback((): void => replace(undefined), [replace]) + const append = useCallback((lifecycle: NativeChatTurnLifecycle | undefined): void => { + if (!lifecycle) { + return + } + revisionRef.current += 1 + setState({ lifecycle }) + }, []) + const revision = useCallback((): number => revisionRef.current, []) + const replaceFromPagination = useCallback( + (lifecycle: NativeChatTurnLifecycle | undefined, expectedRevision: number): void => { + if (!lifecycle || revisionRef.current !== expectedRevision) { + return + } + revisionRef.current += 1 + setState((current) => ({ ...current, lifecycle })) + }, + [] + ) + + const control = useMemo( + () => ({ reset, replace, append, revision, replaceFromPagination }), + [append, replace, replaceFromPagination, reset, revision] + ) + return [state.lifecycle, control] +} diff --git a/src/renderer/src/components/settings/AppearanceWindowSidebarSection.tsx b/src/renderer/src/components/settings/AppearanceWindowSidebarSection.tsx index ad68464b3e1..1d7e5d8ec2c 100644 --- a/src/renderer/src/components/settings/AppearanceWindowSidebarSection.tsx +++ b/src/renderer/src/components/settings/AppearanceWindowSidebarSection.tsx @@ -329,7 +329,9 @@ export function AppearanceWindowSidebarSection({ checked={settings.showPinnedWorktreesInGroups === true} onChange={() => updateSettings({ - showPinnedWorktreesInGroups: !(settings.showPinnedWorktreesInGroups === true) + showPinnedWorktreesInGroups: !( + settings.showPinnedWorktreesInGroups === true + ) }) } /> diff --git a/src/renderer/src/web/web-preload-api.test.ts b/src/renderer/src/web/web-preload-api.test.ts index bb77e236218..102b45c67f3 100644 --- a/src/renderer/src/web/web-preload-api.test.ts +++ b/src/renderer/src/web/web-preload-api.test.ts @@ -702,6 +702,88 @@ describe('web settings preload API', () => { }) }) +describe('web native chat preload API', () => { + beforeEach(() => { + vi.resetModules() + }) + + afterEach(() => { + vi.unstubAllGlobals() + vi.doUnmock('./web-runtime-client') + }) + + it('forwards validated lifecycle metadata from reads and stream frames', async () => { + const lifecycle = { state: 'completed', turnId: 'turn-1', timestamp: 42 } as const + const message = { + id: 'a-1', + role: 'assistant' as const, + blocks: [{ type: 'text' as const, text: 'done' }], + timestamp: 42, + source: 'transcript' as const + } + vi.doMock('./web-runtime-client', () => ({ + WebRuntimeClient: class { + call(): Promise> { + return Promise.resolve({ + id: 'read-1', + ok: true, + result: { messages: [message], lifecycle }, + _meta: { runtimeId: 'runtime-1' } + }) + } + + subscribe( + _method: string, + _params: unknown, + callbacks: { onResponse: (response: RuntimeRpcResponse) => void } + ): Promise<{ unsubscribe: () => void }> { + callbacks.onResponse({ + id: 'stream-1', + ok: true, + result: { + type: 'snapshot', + messages: [message], + hasMore: false, + lifecycle + }, + _meta: { runtimeId: 'runtime-1' } + }) + return Promise.resolve({ unsubscribe: vi.fn() }) + } + + close(): void {} + } + })) + + const globals = installBrowserGlobals('Linux') + writeStoredRuntimeEnvironment(globals.storage) + const { installWebPreloadApi } = await import('./web-preload-api') + installWebPreloadApi() + + await expect(globals.window.api.nativeChat.readSession('claude', 'session-1')).resolves.toEqual( + { + messages: [message], + lifecycle + } + ) + const frames: unknown[] = [] + globals.window.api.nativeChat.subscribe( + { subscriptionId: 'sub-1', agent: 'claude', sessionId: 'session-1' }, + (frame) => frames.push(frame) + ) + await Promise.resolve() + + expect(frames).toEqual([ + { + type: 'snapshot', + messages: [message], + hasMore: false, + lifecycle + } + ]) + }) +}) + describe('web MiniMax preload API', () => { beforeEach(() => { vi.resetModules() diff --git a/src/renderer/src/web/web-preload-api.ts b/src/renderer/src/web/web-preload-api.ts index cddcfae9346..6ae5cfa1c6d 100644 --- a/src/renderer/src/web/web-preload-api.ts +++ b/src/renderer/src/web/web-preload-api.ts @@ -6,7 +6,6 @@ import type { PreflightStatus, RefreshAgentsResult, NativeChatApi, - NativeChatReadSessionResult, NativeChatAppendedMessages } from '../../../preload/api-types' import type { RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope' @@ -127,6 +126,10 @@ import { import { normalizeContextualTourIds, type ContextualTourId } from '../../../shared/contextual-tours' import { translate } from '@/i18n/i18n' import { getDefaultCreateProjectParent } from '@/components/sidebar/create-project-defaults' +import { + parseRuntimeNativeChatReadSessionResult, + parseRuntimeNativeChatTurnLifecycle +} from '@/components/native-chat/native-chat-runtime-contract' const SETTINGS_STORAGE_KEY = 'orca.web.settings.v1' const UI_STORAGE_KEY = 'orca.web.ui.v1' @@ -1085,13 +1088,15 @@ function createWebKeybindingsApi(): WebKeybindingsApi { // undefined on web and the chat view showed no messages. function createNativeChatApi(): NativeChatApi { return { - readSession: (agent, sessionId, limit, transcriptPath) => - callRuntimeResult('nativeChat.readSession', { - agent, - sessionId, - limit, - transcriptPath - }), + readSession: async (agent, sessionId, limit, transcriptPath) => + parseRuntimeNativeChatReadSessionResult( + await callRuntimeResult('nativeChat.readSession', { + agent, + sessionId, + limit, + transcriptPath + }) + ), subscribe: (args, onFrame) => { // No paired runtime yet: nothing to subscribe to, and // requireActiveEnvironment() would throw. Return a no-op teardown so the @@ -1145,7 +1150,9 @@ function createNativeChatApi(): NativeChatApi { messages?: NativeChatAppendedMessages hasMore?: boolean error?: string + lifecycle?: unknown } + const lifecycle = parseRuntimeNativeChatTurnLifecycle(result?.lifecycle) if ( (result?.type === 'appended' || result?.type === 'snapshot' || @@ -1158,14 +1165,16 @@ function createNativeChatApi(): NativeChatApi { type: 'snapshot', messages: result.messages, hasMore: result.hasMore ?? result.messages.length >= (args.limit ?? 300), - ...(result.error ? { error: result.error } : {}) + ...(result.error ? { error: result.error } : {}), + ...(lifecycle ? { lifecycle } : {}) }) } else if (result.type === 'snapshot') { onFrame({ type: 'snapshot', messages: result.messages, hasMore: result.hasMore ?? false, - ...(result.error ? { error: result.error } : {}) + ...(result.error ? { error: result.error } : {}), + ...(lifecycle ? { lifecycle } : {}) }) } else { onFrame( @@ -1173,9 +1182,14 @@ function createNativeChatApi(): NativeChatApi { ? { type: 'replacement', messages: result.messages, - hasMore: result.hasMore ?? false + hasMore: result.hasMore ?? false, + ...(lifecycle ? { lifecycle } : {}) + } + : { + type: 'appended', + messages: result.messages, + ...(lifecycle ? { lifecycle } : {}) } - : { type: 'appended', messages: result.messages } ) } } else if (!receivedInitial) { diff --git a/src/shared/native-chat-types.ts b/src/shared/native-chat-types.ts index aa0539ce42e..f88ff013769 100644 --- a/src/shared/native-chat-types.ts +++ b/src/shared/native-chat-types.ts @@ -79,6 +79,22 @@ export type NativeChatMessage = { turnId?: string } +export const NATIVE_CHAT_TURN_LIFECYCLE_STATES = ['working', 'completed', 'interrupted'] as const +export type NativeChatTurnLifecycleState = (typeof NATIVE_CHAT_TURN_LIFECYCLE_STATES)[number] + +export const NATIVE_CHAT_INTERRUPTED_STATUS_TEXT = 'Conversation interrupted' + +/** A provider-authored turn boundary recovered from the transcript itself. + * Unlike assistant prose, this is explicit lifecycle evidence (completion or + * interruption records) and is safe to replay. */ +export type NativeChatTurnLifecycle = { + state: NativeChatTurnLifecycleState + /** Stable provider id when available, otherwise the JSONL record position. */ + turnId: string + /** Provider timestamp; null only when the transcript omitted one. */ + timestamp: number | null +} + export const NATIVE_CHAT_SESSION_STATUSES = [ 'loading', 'ready', From 14116e357366066498d14cbd201dfa3a12a0b12e Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:15:58 -0700 Subject: [PATCH 012/137] feat(settings): unify skill card status and add Linear agent-skill section (#9218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(settings): unify skill card status and add Linear agent-skill section Show the freshness-aware status pill ("Up to date" / "Update available") on the Orchestration skill card, matching the Computer Use card — it was the only installed-skill card still limited to a presence-only "Installed" pill because it omitted the freshnessSkillName prop. Add a Linear capability section under Settings → Agents that only appears once the Linear provider is connected. It mirrors the other skill panes: an orca-linear skill card (Update / Re-check + freshness pill) and a "How to use it" grid of copyable /orca-linear prompts. The Orchestration and Linear "How to use it" surfaces now share extracted SkillUsageExamplesSection / SkillUsageExampleDialog components (the old orchestration-only dialog is renamed/generalized with a slashCommand prop). The nav registry and Settings page gate the Linear section on one shared useLinearProviderConnected() selector so the sidebar entry and the section appear and disappear together. * fix(settings): tighten Linear skill section behavior * fix(settings): preserve Linear freshness and dialog translations * fix(linear): migrate legacy skill installs * Revert "fix(linear): migrate legacy skill installs" This reverts commit 3ff45f9245eda5c283988f9e6d21f8ce1386d548. * fix(settings): show Linear freshness in sidebar * fix(settings): type Linear navigation target --- .../settings/LinearAgentSkillPane.test.tsx | 139 +++++++++++++++ .../settings/LinearAgentSkillPane.tsx | 159 ++++++++++++++++++ .../components/settings/OrchestrationPane.tsx | 80 +++------ .../src/components/settings/Settings.tsx | 73 +++++--- ...Dialog.tsx => SkillUsageExampleDialog.tsx} | 39 ++--- .../settings/SkillUsageExamplesSection.tsx | 68 ++++++++ ...nt-skill-installed-command-callers.test.ts | 6 + .../settings/linear-agent-skill-search.ts | 35 ++++ .../hooks/useLinearProviderConnected.test.tsx | 90 ++++++++++ .../src/hooks/useLinearProviderConnected.ts | 16 ++ .../useSettingsNavigationMetadata.test.ts | 30 +++- .../hooks/useSettingsNavigationMetadata.ts | 26 ++- src/renderer/src/i18n/locales/en.json | 57 ++++++- src/renderer/src/i18n/locales/es.json | 57 ++++++- src/renderer/src/i18n/locales/ja.json | 57 ++++++- src/renderer/src/i18n/locales/ko.json | 57 ++++++- src/renderer/src/i18n/locales/zh.json | 57 ++++++- .../agent-skill-nav-install-status.test.ts | 141 ++++++++++++++++ .../src/lib/agent-skill-nav-install-status.ts | 38 +++++ .../linear-agent-skill-update-command.test.ts | 50 ++++++ .../lib/linear-agent-skill-update-command.ts | 24 ++- src/renderer/src/lib/linear-usage-examples.ts | 77 +++++++++ .../src/lib/orchestration-usage-examples.ts | 9 +- .../src/lib/settings-navigation-types.ts | 1 + src/renderer/src/lib/skill-usage-example.ts | 6 + 25 files changed, 1238 insertions(+), 154 deletions(-) create mode 100644 src/renderer/src/components/settings/LinearAgentSkillPane.test.tsx create mode 100644 src/renderer/src/components/settings/LinearAgentSkillPane.tsx rename src/renderer/src/components/settings/{OrchestrationExamplesDialog.tsx => SkillUsageExampleDialog.tsx} (74%) create mode 100644 src/renderer/src/components/settings/SkillUsageExamplesSection.tsx create mode 100644 src/renderer/src/components/settings/linear-agent-skill-search.ts create mode 100644 src/renderer/src/hooks/useLinearProviderConnected.test.tsx create mode 100644 src/renderer/src/hooks/useLinearProviderConnected.ts create mode 100644 src/renderer/src/lib/agent-skill-nav-install-status.test.ts create mode 100644 src/renderer/src/lib/agent-skill-nav-install-status.ts create mode 100644 src/renderer/src/lib/linear-agent-skill-update-command.test.ts create mode 100644 src/renderer/src/lib/linear-usage-examples.ts create mode 100644 src/renderer/src/lib/skill-usage-example.ts diff --git a/src/renderer/src/components/settings/LinearAgentSkillPane.test.tsx b/src/renderer/src/components/settings/LinearAgentSkillPane.test.tsx new file mode 100644 index 00000000000..b7d6a9d56bc --- /dev/null +++ b/src/renderer/src/components/settings/LinearAgentSkillPane.test.tsx @@ -0,0 +1,139 @@ +// @vitest-environment happy-dom + +import { act, type ReactNode } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { renderToStaticMarkup } from 'react-dom/server' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { + ORCA_LINEAR_SKILL_INSTALL_COMMAND, + ORCA_LINEAR_SKILL_NAME +} from '@/lib/agent-feature-install-commands' +import { getLinearUsageExamples } from '@/lib/linear-usage-examples' +import { LinearAgentSkillPane } from './LinearAgentSkillPane' + +const UPDATE_COMMAND = 'npx skills update orca-linear --global' + +const mocks = vi.hoisted(() => ({ + panelProps: [] as Record[], + runtime: 'native' as 'native' | 'wsl', + skillInstalled: true, + updateSkillName: 'orca-linear' +})) + +vi.mock('./AgentSkillSetupPanel', () => ({ + AgentSkillSetupPanel: (props: Record & { footer?: ReactNode }) => { + mocks.panelProps.push(props) + return ( +
+

{String(props.title)}

+ {props.installed ? 'Installed' : 'Not installed'} + {String(props.command)} + {String(props.installedCommand)} + {String(props.freshnessSkillName)} +
+ ) + } +})) + +vi.mock('./CliSkillRuntimeSetup', () => ({ + buildSkillCommandForRuntime: (command: string) => command, + ensureWslCliAvailableForAgentSkillTerminal: vi.fn(), + getWslCliDistroRequest: () => undefined +})) + +vi.mock('@/lib/linear-agent-skill-update-command', () => ({ + getLinearAgentSkillUpdateTarget: () => ({ + command: UPDATE_COMMAND, + skillName: mocks.updateSkillName + }) +})) + +vi.mock('@/hooks/useInstalledAgentSkills', () => ({ + GLOBAL_AGENT_SKILL_SOURCE_KINDS: ['home'], + useInstalledAgentSkillNames: () => ({ + installed: mocks.skillInstalled, + loading: false, + error: null, + skills: [], + refresh: vi.fn() + }) +})) + +vi.mock('@/hooks/useActiveProjectSkillRuntime', () => ({ + useActiveProjectSkillRuntime: () => ({ + discoveryTarget: undefined, + agentRuntime: { runtime: mocks.runtime }, + terminalShellOverride: undefined, + installDisabledReason: null + }) +})) + +let root: Root | null = null +let container: HTMLDivElement | null = null + +async function renderPane(): Promise { + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + await act(async () => { + root?.render() + }) + return container +} + +describe('LinearAgentSkillPane', () => { + afterEach(async () => { + if (root) { + await act(async () => { + root?.unmount() + }) + } + root = null + container?.remove() + container = null + mocks.panelProps.length = 0 + mocks.runtime = 'native' + mocks.skillInstalled = true + mocks.updateSkillName = 'orca-linear' + }) + + it('renders the Linear skill card and the usage examples', () => { + const markup = renderToStaticMarkup() + + expect(markup).toContain('Linear skill') + expect(markup).toContain('How to use it') + const examples = getLinearUsageExamples() + expect(examples).toHaveLength(5) + for (const example of examples) { + expect(markup).toContain(example.title) + expect(example.prompt).toContain('/orca-linear') + expect(example.prompt).not.toContain('{{value0}}') + } + }) + + it('passes the orca-linear install/update commands and freshness on a local runtime', async () => { + await renderPane() + + expect(mocks.panelProps.at(-1)).toEqual( + expect.objectContaining({ + command: ORCA_LINEAR_SKILL_INSTALL_COMMAND, + installedCommand: UPDATE_COMMAND, + freshnessSkillName: ORCA_LINEAR_SKILL_NAME + }) + ) + }) + + it('drops freshness on a WSL runtime the local scan cannot vouch for', async () => { + mocks.runtime = 'wsl' + await renderPane() + + expect(mocks.panelProps.at(-1)?.freshnessSkillName).toBeUndefined() + }) + + it('checks freshness under the legacy name when that is the installed update target', async () => { + mocks.updateSkillName = 'linear-tickets' + await renderPane() + + expect(mocks.panelProps.at(-1)?.freshnessSkillName).toBe('linear-tickets') + }) +}) diff --git a/src/renderer/src/components/settings/LinearAgentSkillPane.tsx b/src/renderer/src/components/settings/LinearAgentSkillPane.tsx new file mode 100644 index 00000000000..ad477a9ed8d --- /dev/null +++ b/src/renderer/src/components/settings/LinearAgentSkillPane.tsx @@ -0,0 +1,159 @@ +import { useMemo } from 'react' +import { ArrowRightCircle, BookOpen, Link2, ListTodo, MessageSquarePlus } from 'lucide-react' +import type { LucideIcon } from 'lucide-react' +import { LinearIcon } from '@/components/icons/LinearIcon' +import { + LINEAR_AGENT_SKILL_NAMES, + ORCA_LINEAR_SKILL_INSTALL_COMMAND, + ORCA_LINEAR_SKILL_NAME +} from '@/lib/agent-feature-install-commands' +import { + AGENT_SKILL_CLI_PREREQUISITE_NOTICE, + ensureOrcaCliAvailableForAgentSkillTerminal +} from '@/lib/agent-skill-cli-prerequisite' +import { getLinearAgentSkillUpdateTarget } from '@/lib/linear-agent-skill-update-command' +import { getLinearUsageExamples } from '@/lib/linear-usage-examples' +import type { SkillUsageExample } from '@/lib/skill-usage-example' +import { + GLOBAL_AGENT_SKILL_SOURCE_KINDS, + useInstalledAgentSkillNames +} from '@/hooks/useInstalledAgentSkills' +import { useActiveProjectSkillRuntime } from '@/hooks/useActiveProjectSkillRuntime' +import { AgentSkillSetupPanel } from './AgentSkillSetupPanel' +import { + buildSkillCommandForRuntime, + ensureWslCliAvailableForAgentSkillTerminal, + getWslCliDistroRequest +} from './CliSkillRuntimeSetup' +import { getLinearAgentSkillPaneSearchEntries } from './linear-agent-skill-search' +import { SearchableSetting } from './SearchableSetting' +import { SkillUsageExamplesSection } from './SkillUsageExamplesSection' +import { translate } from '@/i18n/i18n' +export { getLinearAgentSkillPaneSearchEntries } from './linear-agent-skill-search' + +const LINEAR_EXAMPLE_ICONS: Record = { + 'read-ticket': BookOpen, + 'post-update': MessageSquarePlus, + 'move-state': ArrowRightCircle, + 'attach-pr': Link2, + 'triage-followups': ListTodo +} + +function resolveLinearExampleIcon(example: SkillUsageExample): LucideIcon { + return LINEAR_EXAMPLE_ICONS[example.id] ?? LinearIcon +} + +// Why: this section is rendered as a Settings section only when the Linear +// provider is connected, so the orca-linear agent skill sits beside the +// connection that makes it useful. +export function LinearAgentSkillPane(): React.JSX.Element { + const activeSkillRuntime = useActiveProjectSkillRuntime() + const { + installed: linearSkillInstalled, + loading: linearSkillLoading, + error: linearSkillError, + skills: linearSkills, + refresh: refreshLinearSkill + } = useInstalledAgentSkillNames(LINEAR_AGENT_SKILL_NAMES, { + discoveryTarget: activeSkillRuntime.discoveryTarget, + sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS + }) + + const installCommand = useMemo( + () => + activeSkillRuntime.installDisabledReason + ? ORCA_LINEAR_SKILL_INSTALL_COMMAND + : buildSkillCommandForRuntime( + ORCA_LINEAR_SKILL_INSTALL_COMMAND, + activeSkillRuntime.agentRuntime + ), + [activeSkillRuntime.agentRuntime, activeSkillRuntime.installDisabledReason] + ) + const updateTarget = useMemo( + () => getLinearAgentSkillUpdateTarget(linearSkills, linearSkillInstalled), + [linearSkillInstalled, linearSkills] + ) + const updateCommand = useMemo(() => { + const command = updateTarget.command + return activeSkillRuntime.installDisabledReason + ? command + : buildSkillCommandForRuntime(command, activeSkillRuntime.agentRuntime) + }, [ + activeSkillRuntime.agentRuntime, + activeSkillRuntime.installDisabledReason, + updateTarget.command + ]) + + return ( + + } + preInstallNotice={AGENT_SKILL_CLI_PREREQUISITE_NOTICE} + getPrerequisiteStatus={() => + activeSkillRuntime.agentRuntime?.runtime === 'wsl' + ? window.api.cli.getWslInstallStatus( + getWslCliDistroRequest(activeSkillRuntime.agentRuntime) + ) + : window.api.cli.getInstallStatus() + } + onBeforeOpenTerminal={async () => { + await (activeSkillRuntime.agentRuntime?.runtime === 'wsl' + ? ensureWslCliAvailableForAgentSkillTerminal(activeSkillRuntime.agentRuntime) + : ensureOrcaCliAvailableForAgentSkillTerminal()) + }} + onRecheck={refreshLinearSkill} + // Why: the local-host-only freshness scan cannot vouch for a WSL runtime, + // so fall back to the presence-only pill there (mirrors the other skills). + freshnessSkillName={ + activeSkillRuntime.agentRuntime?.runtime === 'wsl' ? undefined : updateTarget.skillName + } + /> + + + + ) +} diff --git a/src/renderer/src/components/settings/OrchestrationPane.tsx b/src/renderer/src/components/settings/OrchestrationPane.tsx index 59aea3347c4..dad05eb048e 100644 --- a/src/renderer/src/components/settings/OrchestrationPane.tsx +++ b/src/renderer/src/components/settings/OrchestrationPane.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' -import { ArrowRightLeft, GitBranch, ListChecks, Workflow } from 'lucide-react' +import { ArrowRightLeft, GitBranch, ListChecks, Workflow, type LucideIcon } from 'lucide-react' import { ORCHESTRATION_SKILL_NAME } from '@/lib/agent-feature-install-commands' +import type { SkillUsageExample } from '@/lib/skill-usage-example' import { AGENT_SKILL_CLI_PREREQUISITE_NOTICE, ensureOrcaCliAvailableForAgentSkillTerminal @@ -26,7 +27,7 @@ import { getWslCliDistroRequest } from './CliSkillRuntimeSetup' import { OrchestrationSkillAgentCoverage } from './OrchestrationSkillAgentCoverage' -import { OrchestrationExampleDialog } from './OrchestrationExamplesDialog' +import { SkillUsageExamplesSection } from './SkillUsageExamplesSection' import { OrchestrationSkillPromptDialog } from './OrchestrationSkillPromptDialog' import { translate } from '@/i18n/i18n' @@ -38,10 +39,13 @@ const EXAMPLE_ICONS = { 'child-worktrees': Workflow } as const +function resolveOrchestrationExampleIcon(example: SkillUsageExample): LucideIcon { + return EXAMPLE_ICONS[example.id as keyof typeof EXAMPLE_ICONS] ?? Workflow +} + export function OrchestrationPane(): React.JSX.Element { const searchQuery = useAppStore((s) => s.settingsSearchQuery) const showOrchestration = matchesSettingsSearch(searchQuery, getOrchestrationPaneSearchEntries()) - const [selectedExampleId, setSelectedExampleId] = useState(null) const [skillPromptOpen, setSkillPromptOpen] = useState(false) const activeSkillRuntime = useActiveProjectSkillRuntime() const orchestrationInstallCommand = !activeSkillRuntime.installDisabledReason @@ -150,6 +154,11 @@ export function OrchestrationPane(): React.JSX.Element { /> } onRecheck={refreshOrchestrationSkill} + // Why: the local-host-only freshness scan cannot vouch for a WSL runtime, + // so fall back to the presence-only pill there (mirrors the Computer Use card). + freshnessSkillName={ + activeSkillRuntime.agentRuntime?.runtime === 'wsl' ? undefined : ORCHESTRATION_SKILL_NAME + } /> -
-
-

- {translate('auto.components.settings.OrchestrationPane.ae79504732', 'How to use it')} -

-

- {translate( - 'auto.components.settings.OrchestrationPane.52e0634e2c', - 'Ask a coordinator agent to use orchestration for handoffs, worktree handovers, and sequential or parallel child agents.' - )} -

-
- -
- {getOrchestrationUsageExamples().map((example) => { - const Icon = EXAMPLE_ICONS[example.id as keyof typeof EXAMPLE_ICONS] ?? Workflow - return ( - - ) - })} -
-
- - {getOrchestrationUsageExamples().map((example) => { - const Icon = EXAMPLE_ICONS[example.id as keyof typeof EXAMPLE_ICONS] ?? Workflow - return ( - setSelectedExampleId(open ? example.id : null)} - /> - ) - })} + ) } diff --git a/src/renderer/src/components/settings/Settings.tsx b/src/renderer/src/components/settings/Settings.tsx index ef50a504a3a..80a4facee4e 100644 --- a/src/renderer/src/components/settings/Settings.tsx +++ b/src/renderer/src/components/settings/Settings.tsx @@ -12,7 +12,6 @@ import { import { toast } from 'sonner' import type { GlobalSettings, OrcaHooks, ProjectHostSetup, Repo } from '../../../../shared/types' import type { SpeechModelState } from '../../../../shared/speech-types' -import type { SkillFreshnessInventory } from '../../../../shared/skill-freshness' import type { SourceControlAiSettings, SourceControlAiSettingsPatch @@ -54,6 +53,7 @@ import { SshPane } from './SshPane' import { ExperimentalPane } from './ExperimentalPane' import { AgentsPane } from './AgentsPane' import { OrchestrationPane } from './OrchestrationPane' +import { LinearAgentSkillPane } from './LinearAgentSkillPane' import { AccountsPane } from './AccountsPane' import { StatsPane } from '../stats/StatsPane' import { IntegrationsPane } from './IntegrationsPane' @@ -94,15 +94,21 @@ import type { } from '@/lib/settings-navigation-types' import { COMPUTER_USE_SKILL_NAME, + LINEAR_AGENT_SKILL_NAMES, ORCHESTRATION_SKILL_NAME } from '@/lib/agent-feature-install-commands' import { GLOBAL_AGENT_SKILL_SOURCE_KINDS, - useInstalledAgentSkill + useInstalledAgentSkill, + useInstalledAgentSkillNames } from '@/hooks/useInstalledAgentSkills' import { useActiveProjectSkillRuntime } from '@/hooks/useActiveProjectSkillRuntime' +import { useLinearProviderConnected } from '@/hooks/useLinearProviderConnected' import { useSkillFreshness } from '@/hooks/useSkillFreshness' -import { getSkillFreshnessDisplayStatus } from '@/lib/skill-freshness-display-status' +import { + getAgentSkillNavInstallStatus, + getLinearAgentSkillNavInstallStatus +} from '@/lib/agent-skill-nav-install-status' import { deriveNeededSectionIds, getInitialMountedSectionIds } from './settings-load-performance' import { translate } from '@/i18n/i18n' import { getProjectHostSetupProjectionFromState } from '../../store/selectors' @@ -207,21 +213,6 @@ function getSettingsNavGroupDefinitionsForSearch( }) } -function getSkillNavInstallStatus(skill: { - name: string - installed: boolean - loading: boolean - inventory: SkillFreshnessInventory | null -}): SettingsNavInstallStatus { - if (skill.loading) { - return 'checking' - } - if (!skill.installed) { - return 'install' - } - return getSkillFreshnessDisplayStatus(skill.inventory, skill.name) -} - function hasReadyVoiceModel( settings: GlobalSettings, modelStates: readonly SpeechModelState[] @@ -344,11 +335,19 @@ function Settings(): React.JSX.Element { const isMac = isMacUserAgent() const isWebClient = isWebClientLocation() const showDesktopOnlySettings = !isWebClient + // Why: the Linear capability section mirrors the nav registry's gate so the + // sidebar entry and the rendered section appear/disappear together. + const linearConnected = useLinearProviderConnected() const activeSkillRuntime = useActiveProjectSkillRuntime() const orchestrationSkill = useInstalledAgentSkill(ORCHESTRATION_SKILL_NAME, { discoveryTarget: activeSkillRuntime.discoveryTarget, sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS }) + const linearSkill = useInstalledAgentSkillNames(LINEAR_AGENT_SKILL_NAMES, { + enabled: linearConnected, + discoveryTarget: activeSkillRuntime.discoveryTarget, + sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS + }) const computerUseSkill = useInstalledAgentSkill(COMPUTER_USE_SKILL_NAME, { enabled: showDesktopOnlySettings, discoveryTarget: activeSkillRuntime.discoveryTarget, @@ -734,6 +733,11 @@ function Settings(): React.JSX.Element { const baseNavSections = useSettingsNavigationMetadata() const { installed: orchestrationSkillInstalled, loading: orchestrationSkillLoading } = orchestrationSkill + const { + installed: linearSkillInstalled, + loading: linearSkillLoading, + skills: linearSkills + } = linearSkill const { installed: computerUseSkillInstalled, loading: computerUseSkillLoading } = computerUseSkill const capabilityInstallStatusBySectionId = useMemo(() => { @@ -741,7 +745,7 @@ function Settings(): React.JSX.Element { const next = new Map([ [ 'orchestration', - getSkillNavInstallStatus({ + getAgentSkillNavInstallStatus({ name: ORCHESTRATION_SKILL_NAME, installed: orchestrationSkillInstalled, loading: orchestrationSkillLoading, @@ -749,10 +753,21 @@ function Settings(): React.JSX.Element { }) ] ]) + if (linearConnected) { + next.set( + 'linear', + getLinearAgentSkillNavInstallStatus({ + skills: linearSkills, + installed: linearSkillInstalled, + loading: linearSkillLoading, + inventory: applicableFreshnessInventory + }) + ) + } if (showDesktopOnlySettings) { next.set( 'computer-use', - getSkillNavInstallStatus({ + getAgentSkillNavInstallStatus({ name: COMPUTER_USE_SKILL_NAME, installed: computerUseSkillInstalled, loading: computerUseSkillLoading, @@ -774,6 +789,10 @@ function Settings(): React.JSX.Element { }, [ computerUseSkillInstalled, computerUseSkillLoading, + linearConnected, + linearSkillInstalled, + linearSkillLoading, + linearSkills, modelStates, orchestrationSkillInstalled, orchestrationSkillLoading, @@ -1256,6 +1275,20 @@ function Settings(): React.JSX.Element { {isSectionMounted('orchestration') ? : null} + {linearConnected ? ( + + {isSectionMounted('linear') ? : null} + + ) : null} + {showDesktopOnlySettings ? ( <> {prompt} @@ -30,9 +30,7 @@ function OrchestrationExamplePromptText(props: { prompt: string }): React.JSX.El {part} {index < parts.length - 1 ? ( - - {ORCHESTRATION_SKILL_SLASH_COMMAND} - + {slashCommand} ) : null} ))} @@ -40,20 +38,23 @@ function OrchestrationExamplePromptText(props: { prompt: string }): React.JSX.El ) } -export function OrchestrationExampleDialog(props: { - example: OrchestrationUsageExample +export function SkillUsageExampleDialog(props: { + example: SkillUsageExample + // Why: the copyable prompt highlights the skill's slash command so the reader + // sees which skill to invoke; each skill passes its own (e.g. /orchestration). + slashCommand: string icon?: LucideIcon open: boolean onOpenChange: (open: boolean) => void }): React.JSX.Element { - const { example, icon: Icon, open, onOpenChange } = props + const { example, slashCommand, icon: Icon, open, onOpenChange } = props const copyPrompt = async (prompt: string): Promise => { try { await window.api.ui.writeClipboardText(prompt) toast.success( translate( - 'auto.components.settings.OrchestrationExamplesDialog.80c6f2feb8', + 'auto.components.settings.SkillUsageExampleDialog.copiedPrompt', 'Copied example prompt.' ) ) @@ -62,7 +63,7 @@ export function OrchestrationExampleDialog(props: { error instanceof Error ? error.message : translate( - 'auto.components.settings.OrchestrationExamplesDialog.4e46da1889', + 'auto.components.settings.SkillUsageExampleDialog.copyFailed', 'Failed to copy prompt.' ) ) @@ -93,7 +94,7 @@ export function OrchestrationExampleDialog(props: {

- +

diff --git a/src/renderer/src/components/settings/SkillUsageExamplesSection.tsx b/src/renderer/src/components/settings/SkillUsageExamplesSection.tsx new file mode 100644 index 00000000000..ecadbb44b6f --- /dev/null +++ b/src/renderer/src/components/settings/SkillUsageExamplesSection.tsx @@ -0,0 +1,68 @@ +import { useState } from 'react' +import type { LucideIcon } from 'lucide-react' +import { Button } from '@/components/ui/button' +import type { SkillUsageExample } from '@/lib/skill-usage-example' +import { SkillUsageExampleDialog } from './SkillUsageExampleDialog' + +// Why: orchestration and Linear both present "How to use it" example cards that +// open a copyable prompt dialog; sharing one section keeps the two in lockstep. +export function SkillUsageExamplesSection({ + heading, + description, + examples, + resolveIcon, + slashCommand +}: { + heading: string + description: string + examples: readonly SkillUsageExample[] + resolveIcon: (example: SkillUsageExample) => LucideIcon + slashCommand: string +}): React.JSX.Element { + const [selectedExampleId, setSelectedExampleId] = useState(null) + + return ( +
+
+

{heading}

+

{description}

+
+ +
+ {examples.map((example) => { + const Icon = resolveIcon(example) + return ( + + ) + })} +
+ + {examples.map((example) => ( + setSelectedExampleId(open ? example.id : null)} + /> + ))} +
+ ) +} diff --git a/src/renderer/src/components/settings/agent-skill-installed-command-callers.test.ts b/src/renderer/src/components/settings/agent-skill-installed-command-callers.test.ts index 8992e73499a..70f6da85fe1 100644 --- a/src/renderer/src/components/settings/agent-skill-installed-command-callers.test.ts +++ b/src/renderer/src/components/settings/agent-skill-installed-command-callers.test.ts @@ -23,6 +23,12 @@ const updateCapableCallers = new Map([ 'src/renderer/src/components/settings/ComputerUseSkillSetupPanel.tsx', ['COMPUTER_USE_SKILL_UPDATE_COMMAND', 'installedCommand={updateCommand}'] ], + [ + // Why: the Linear settings section shares the update-target resolver so + // legacy-only installs keep the command and freshness identity aligned. + 'src/renderer/src/components/settings/LinearAgentSkillPane.tsx', + ['getLinearAgentSkillUpdateTarget', 'installedCommand={updateCommand}'] + ], [ 'src/renderer/src/components/settings/EphemeralVmsPane.tsx', ['EPHEMERAL_VMS_SKILL_UPDATE_COMMAND', 'installedCommand={updateCommand}'] diff --git a/src/renderer/src/components/settings/linear-agent-skill-search.ts b/src/renderer/src/components/settings/linear-agent-skill-search.ts new file mode 100644 index 00000000000..3fdf6dbe2b9 --- /dev/null +++ b/src/renderer/src/components/settings/linear-agent-skill-search.ts @@ -0,0 +1,35 @@ +import { translate } from '@/i18n/i18n' +import { translateSearchKeyword } from './settings-search-keywords' +import { createLocalizedCatalog } from '@/i18n/localized-catalog' + +export const getLinearAgentSkillPaneSearchEntries = createLocalizedCatalog(() => [ + { + title: translate('auto.components.settings.linear.agent.skill.search.title', 'Linear'), + description: translate( + 'auto.components.settings.linear.agent.skill.search.description', + 'Give agents the skill to read and update your linked Linear tickets.' + ), + keywords: [ + ...translateSearchKeyword( + 'auto.components.settings.linear.agent.skill.search.linear', + 'linear' + ), + ...translateSearchKeyword( + 'auto.components.settings.linear.agent.skill.search.tickets', + 'tickets' + ), + ...translateSearchKeyword( + 'auto.components.settings.linear.agent.skill.search.issues', + 'issues' + ), + ...translateSearchKeyword( + 'auto.components.settings.linear.agent.skill.search.skill', + 'skill' + ), + ...translateSearchKeyword( + 'auto.components.settings.linear.agent.skill.search.orcaLinear', + 'orca-linear' + ) + ] + } +]) diff --git a/src/renderer/src/hooks/useLinearProviderConnected.test.tsx b/src/renderer/src/hooks/useLinearProviderConnected.test.tsx new file mode 100644 index 00000000000..c10afed44c5 --- /dev/null +++ b/src/renderer/src/hooks/useLinearProviderConnected.test.tsx @@ -0,0 +1,90 @@ +// @vitest-environment happy-dom + +import { act } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, describe, expect, it } from 'vitest' +import type { LinearConnectionStatus } from '../../../shared/types' +import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context' +import { useAppStore } from '@/store' +import { useLinearProviderConnected } from './useLinearProviderConnected' + +let root: Root | null = null +let container: HTMLDivElement | null = null + +describe('useLinearProviderConnected', () => { + afterEach(() => { + if (root) { + act(() => root?.unmount()) + } + root = null + container?.remove() + container = null + useAppStore.setState({ + linearStatus: { connected: false, viewer: null }, + linearStatusContextKey: null + }) + }) + + it('does not rerender for Linear metadata changes that preserve section visibility', () => { + const settings = useAppStore.getState().settings + useAppStore.setState({ + linearStatus: { connected: true, viewer: null }, + linearStatusContextKey: getProviderRuntimeContextKey(settings) + }) + let renders = 0 + let connected = false + + function Probe(): null { + renders += 1 + connected = useLinearProviderConnected() + return null + } + + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + act(() => root?.render()) + + expect(connected).toBe(true) + expect(renders).toBe(1) + + act(() => { + useAppStore.setState({ + linearStatus: { + ...useAppStore.getState().linearStatus, + selectedWorkspaceId: 'workspace-2' + } satisfies LinearConnectionStatus + }) + }) + + expect(connected).toBe(true) + expect(renders).toBe(1) + + act(() => { + useAppStore.setState({ linearStatus: { connected: false, viewer: null } }) + }) + + expect(connected).toBe(false) + expect(renders).toBe(2) + }) + + it('rejects a connected status from a different runtime context', () => { + useAppStore.setState({ + linearStatus: { connected: true, viewer: null }, + linearStatusContextKey: 'stale-runtime-context' + }) + let connected = true + + function Probe(): null { + connected = useLinearProviderConnected() + return null + } + + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + act(() => root?.render()) + + expect(connected).toBe(false) + }) +}) diff --git a/src/renderer/src/hooks/useLinearProviderConnected.ts b/src/renderer/src/hooks/useLinearProviderConnected.ts new file mode 100644 index 00000000000..01896987a83 --- /dev/null +++ b/src/renderer/src/hooks/useLinearProviderConnected.ts @@ -0,0 +1,16 @@ +import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context' +import { useAppStore } from '@/store' + +// Why: the Settings sidebar registry and the Settings page must agree on whether +// the Linear section is visible; sharing this selector keeps the nav entry and +// the rendered section from drifting. The context-key guard rejects a status +// fetched for a different runtime environment than the active one. +export function useLinearProviderConnected(): boolean { + // Why: Cmd+J keeps this hook mounted globally; project to the visibility bit so + // unrelated Linear metadata and settings updates do not rerender the palette. + return useAppStore( + (state) => + state.linearStatusContextKey === getProviderRuntimeContextKey(state.settings) && + state.linearStatus.connected + ) +} diff --git a/src/renderer/src/hooks/useSettingsNavigationMetadata.test.ts b/src/renderer/src/hooks/useSettingsNavigationMetadata.test.ts index 8d17ab1444f..b827cedcb06 100644 --- a/src/renderer/src/hooks/useSettingsNavigationMetadata.test.ts +++ b/src/renderer/src/hooks/useSettingsNavigationMetadata.test.ts @@ -13,13 +13,20 @@ const repo = { } satisfies Repo function ids( - args: { isMac?: boolean; isWindows?: boolean; isWebClient?: boolean; isDev?: boolean } = {} + args: { + isMac?: boolean + isWindows?: boolean + isWebClient?: boolean + isDev?: boolean + isLinearConnected?: boolean + } = {} ): string[] { return buildSettingsNavigationMetadata({ isMac: args.isMac ?? false, isWindows: args.isWindows ?? false, isWebClient: args.isWebClient ?? false, isDev: args.isDev ?? false, + isLinearConnected: args.isLinearConnected ?? false, repos: [repo] }).map((section) => section.id) } @@ -40,6 +47,27 @@ describe('settings navigation metadata', () => { ]) }) + it('adds the Linear capability section right after Orchestration only when connected', () => { + expect(ids()).not.toContain('linear') + + const connectedIds = ids({ isLinearConnected: true }) + expect(connectedIds).toContain('linear') + expect(connectedIds.indexOf('linear')).toBe(connectedIds.indexOf('orchestration') + 1) + + const linearSection = buildSettingsNavigationMetadata({ + isMac: false, + isWindows: false, + isWebClient: false, + isLinearConnected: true, + repos: [repo] + }).find((section) => section.id === 'linear') + expect(linearSection?.group).toBe('capabilities') + }) + + it('keeps the Linear capability section available on web clients when connected', () => { + expect(ids({ isWebClient: true, isLinearConnected: true })).toContain('linear') + }) + it('places Mobile under Set Up instead of its own sidebar group', () => { const sections = buildSettingsNavigationMetadata({ isMac: false, diff --git a/src/renderer/src/hooks/useSettingsNavigationMetadata.ts b/src/renderer/src/hooks/useSettingsNavigationMetadata.ts index 60ab54ac4c4..b13ee5f9399 100644 --- a/src/renderer/src/hooks/useSettingsNavigationMetadata.ts +++ b/src/renderer/src/hooks/useSettingsNavigationMetadata.ts @@ -34,6 +34,7 @@ import { Wrench } from 'lucide-react' import { OrcaLogoSettingsIcon } from '@/components/settings/orca-logo-settings-icon' +import { LinearIcon } from '@/components/icons/LinearIcon' import type { Repo } from '../../../shared/types' import { getRepoKindLabel } from '../../../shared/repo-kind' import { useAppStore } from '@/store' @@ -55,6 +56,7 @@ import { getQuickCommandsPaneSearchEntries } from '@/components/settings/quick-c import { getBrowserPaneCombinedSearchEntries } from '@/components/settings/browser-pane-search' import { getNotificationsPaneSearchEntries } from '@/components/settings/notifications-search' import { getOrchestrationPaneSearchEntries } from '@/components/settings/orchestration-search' +import { getLinearAgentSkillPaneSearchEntries } from '@/components/settings/linear-agent-skill-search' import { getRuntimeEnvironmentsSearchEntry, getWebRuntimeEnvironmentsSearchEntry @@ -78,6 +80,7 @@ import { useWindowsTerminalCapabilities } from '@/lib/windows-terminal-capabilities' import { getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client' +import { useLinearProviderConnected } from '@/hooks/useLinearProviderConnected' import { translate } from '@/i18n/i18n' export { isWebClientLocation } from '@/lib/web-client-location' @@ -113,6 +116,7 @@ export function buildSettingsNavigationMetadata({ isWindowsTerminalHost = isWindows, isWebClient, isDev = import.meta.env.DEV, + isLinearConnected = false, repos }: { isMac: boolean @@ -120,6 +124,7 @@ export function buildSettingsNavigationMetadata({ isWindowsTerminalHost?: boolean isWebClient: boolean isDev?: boolean + isLinearConnected?: boolean repos: readonly Repo[] }): SettingsNavSection[] { const showDesktopOnlySettings = !isWebClient @@ -179,6 +184,23 @@ export function buildSettingsNavigationMetadata({ searchEntries: getOrchestrationPaneSearchEntries(), group: 'capabilities' }, + // Why: only surfaced once Linear is connected — a capability that needs a + // linked provider before the agent skill has anything to act on. + ...(isLinearConnected + ? [ + { + id: 'linear', + title: translate('auto.hooks.useSettingsNavigationMetadata.linearTitle', 'Linear'), + description: translate( + 'auto.hooks.useSettingsNavigationMetadata.linearDescription', + 'Give agents the skill to read and update your linked Linear tickets.' + ), + icon: LinearIcon, + searchEntries: getLinearAgentSkillPaneSearchEntries(), + group: 'capabilities' + } + ] + : []), ...(showDesktopOnlySettings ? [ { @@ -584,6 +606,7 @@ export function useSettingsNavigationMetadata(): SettingsNavSection[] { const isMac = isMacUserAgent() const isWindows = isWindowsUserAgent() const isWebClient = isWebClientLocation() + const isLinearConnected = useLinearProviderConnected() const windowsTerminalCapabilityOwnerKey = getWindowsTerminalCapabilityOwnerKey( settings?.activeRuntimeEnvironmentId ) @@ -607,9 +630,10 @@ export function useSettingsNavigationMetadata(): SettingsNavSection[] { isWindowsTerminalHost, isWebClient, isDev: import.meta.env.DEV, + isLinearConnected, repos }), // oxlint-disable-next-line react-hooks/exhaustive-deps -- activeLocale is read implicitly by the translate() calls inside buildSettingsNavigationMetadata; without it the memo keeps the previous language's sections. - [isMac, isWindows, isWindowsTerminalHost, isWebClient, repos, activeLocale] + [isMac, isWindows, isWindowsTerminalHost, isWebClient, isLinearConnected, repos, activeLocale] ) } diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index ae926cb2ef2..161e0e4a626 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -611,6 +611,27 @@ "de50bef680": "macOS is blocking Orca notifications" } } + }, + "linear": { + "usage": { + "examples": { + "readTicket": "Read the linked ticket", + "postUpdate": "Post a progress update", + "moveState": "Move the ticket forward", + "attachPr": "Attach the PR link", + "triageFollowups": "Triage and create follow-ups", + "readTicketSummary": "Pull the linked Linear issue's full context before starting work.", + "readTicketPrompt": "Use {{value0}} to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.", + "postUpdateSummary": "Comment progress or a completion summary back to the Linear issue.", + "postUpdatePrompt": "Use {{value0}} to post a completion update on the linked Linear issue with what changed and how it was verified.", + "moveStateSummary": "Advance the Linear workflow state as the work progresses.", + "moveStatePrompt": "Use {{value0}} to move the linked Linear issue to In Review now that the change is ready.", + "attachPrSummary": "Link the pull request to the Linear issue when you open it.", + "attachPrPrompt": "Use {{value0}} to attach this PR to the linked Linear issue with a PR link.", + "triageFollowupsSummary": "Set assignee, priority, or estimate, and file parented follow-up tickets.", + "triageFollowupsPrompt": "Use {{value0}} to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup." + } + } } }, "hooks": { @@ -728,7 +749,9 @@ "devSearchKeywordSonner": "sonner", "devSearchKeywordError": "error", "devSearchKeywordNotification": "notification", - "projectHostsSummary": "{{value0}} hosts" + "projectHostsSummary": "{{value0}} hosts", + "linearTitle": "Linear", + "linearDescription": "Give agents the skill to read and update your linked Linear tickets." }, "useAppMenuPaste": { "pasteTooLarge": "Paste is too large." @@ -6005,13 +6028,6 @@ "3743ed080c": "Set command", "f79084947b": "New app" }, - "OrchestrationExamplesDialog": { - "3d1aa105e3": "Copy prompt", - "9b4c004998": "Done", - "969cec9739": "Copy {{value0}} example prompt", - "4e46da1889": "Failed to copy prompt.", - "80c6f2feb8": "Copied example prompt." - }, "OrchestrationPane": { "52e0634e2c": "Ask a coordinator agent to use orchestration for handoffs, worktree handovers, and sequential or parallel child agents.", "ae79504732": "How to use it", @@ -6523,7 +6539,9 @@ "2309068a6f": "destructive", "65358016ea": "Discard", "dev": "Dev Tools", - "devDescription": "Dev-only tools for exercising UI states." + "devDescription": "Dev-only tools for exercising UI states.", + "linearTitle": "Linear", + "linearDescription": "Give agents the skill to read and update your linked Linear tickets." }, "SettingsFormControls": { "42a4d15a30": "No matching fonts.", @@ -9112,6 +9130,10 @@ "description": "Let your agents read and edit Linear tasks.", "copyCommand": "Copy command" } + }, + "search": { + "title": "Linear", + "description": "Give agents the skill to read and update your linked Linear tickets." } } } @@ -9192,6 +9214,23 @@ "availability": "Available on", "testFlight": "TestFlight", "androidApk": "Android APK" + }, + "SkillUsageExampleDialog": { + "copiedPrompt": "Copied example prompt.", + "copyFailed": "Failed to copy prompt.", + "copyExampleAria": "Copy {{value0}} example prompt", + "done": "Done", + "copyPrompt": "Copy prompt" + }, + "LinearAgentSkillPane": { + "title": "Linear", + "description": "Give agents the skill to read and update your linked Linear tickets.", + "skillTitle": "Linear skill", + "skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.", + "howToUse": "How to use it", + "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR.", + "terminalTitle": "Linear skill setup", + "terminalAriaLabel": "Linear skill install terminal" } }, "right": { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index ca06db5293b..a8f5f8e8003 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -588,6 +588,27 @@ "de50bef680": "macOS está bloqueando las notificaciones de Orca" } } + }, + "linear": { + "usage": { + "examples": { + "readTicket": "Read the linked ticket", + "postUpdate": "Post a progress update", + "moveState": "Move the ticket forward", + "attachPr": "Attach the PR link", + "triageFollowups": "Triage and create follow-ups", + "readTicketSummary": "Pull the linked Linear issue's full context before starting work.", + "readTicketPrompt": "Use {{value0}} to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.", + "postUpdateSummary": "Comment progress or a completion summary back to the Linear issue.", + "postUpdatePrompt": "Use {{value0}} to post a completion update on the linked Linear issue with what changed and how it was verified.", + "moveStateSummary": "Advance the Linear workflow state as the work progresses.", + "moveStatePrompt": "Use {{value0}} to move the linked Linear issue to In Review now that the change is ready.", + "attachPrSummary": "Link the pull request to the Linear issue when you open it.", + "attachPrPrompt": "Use {{value0}} to attach this PR to the linked Linear issue with a PR link.", + "triageFollowupsSummary": "Set assignee, priority, or estimate, and file parented follow-up tickets.", + "triageFollowupsPrompt": "Use {{value0}} to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup." + } + } } }, "hooks": { @@ -705,7 +726,9 @@ "devSearchKeywordSonner": "sonner", "devSearchKeywordError": "error", "devSearchKeywordNotification": "notification", - "projectHostsSummary": "{{value0}} hosts" + "projectHostsSummary": "{{value0}} hosts", + "linearTitle": "Linear", + "linearDescription": "Give agents the skill to read and update your linked Linear tickets." }, "useAppMenuPaste": { "pasteTooLarge": "El contenido pegado es demasiado grande." @@ -5945,13 +5968,6 @@ "3743ed080c": "Establecer comando", "f79084947b": "Nueva aplicación" }, - "OrchestrationExamplesDialog": { - "3d1aa105e3": "Copiar prompt", - "9b4c004998": "Hecho", - "969cec9739": "Copiar prompt de ejemplo {{value0}}", - "4e46da1889": "No se pudo copiar el prompt.", - "80c6f2feb8": "Prompt de ejemplo copiado." - }, "OrchestrationPane": { "52e0634e2c": "Pídele a un agente coordinador que use la orquestación para handoffs, handoffs de worktree y agentes secundarios secuenciales o paralelos.", "ae79504732": "Cómo usarlo", @@ -6463,7 +6479,9 @@ "2309068a6f": "destructivo", "65358016ea": "Desechar", "dev": "Herramientas de desarrollo", - "devDescription": "Herramientas solo de desarrollo para probar estados de la UI." + "devDescription": "Herramientas solo de desarrollo para probar estados de la UI.", + "linearTitle": "Linear", + "linearDescription": "Give agents the skill to read and update your linked Linear tickets." }, "SettingsFormControls": { "42a4d15a30": "No hay fuentes coincidentes.", @@ -9089,6 +9107,10 @@ "description": "Permite que tus agentes lean y editen tareas de Linear.", "copyCommand": "Copiar comando" } + }, + "search": { + "title": "Linear", + "description": "Give agents the skill to read and update your linked Linear tickets." } } } @@ -9169,6 +9191,23 @@ "availability": "Disponible en", "testFlight": "TestFlight", "androidApk": "APK de Android" + }, + "SkillUsageExampleDialog": { + "copiedPrompt": "Prompt de ejemplo copiado.", + "copyFailed": "No se pudo copiar el prompt.", + "copyExampleAria": "Copiar prompt de ejemplo {{value0}}", + "done": "Hecho", + "copyPrompt": "Copiar prompt" + }, + "LinearAgentSkillPane": { + "title": "Linear", + "description": "Give agents the skill to read and update your linked Linear tickets.", + "skillTitle": "Linear skill", + "skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.", + "howToUse": "How to use it", + "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR.", + "terminalTitle": "Linear skill setup", + "terminalAriaLabel": "Linear skill install terminal" } }, "right": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 107edcfe062..c2f1c217840 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -588,6 +588,27 @@ "de50bef680": "macOS が Orca の通知をブロックしています" } } + }, + "linear": { + "usage": { + "examples": { + "readTicket": "Read the linked ticket", + "postUpdate": "Post a progress update", + "moveState": "Move the ticket forward", + "attachPr": "Attach the PR link", + "triageFollowups": "Triage and create follow-ups", + "readTicketSummary": "Pull the linked Linear issue's full context before starting work.", + "readTicketPrompt": "Use {{value0}} to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.", + "postUpdateSummary": "Comment progress or a completion summary back to the Linear issue.", + "postUpdatePrompt": "Use {{value0}} to post a completion update on the linked Linear issue with what changed and how it was verified.", + "moveStateSummary": "Advance the Linear workflow state as the work progresses.", + "moveStatePrompt": "Use {{value0}} to move the linked Linear issue to In Review now that the change is ready.", + "attachPrSummary": "Link the pull request to the Linear issue when you open it.", + "attachPrPrompt": "Use {{value0}} to attach this PR to the linked Linear issue with a PR link.", + "triageFollowupsSummary": "Set assignee, priority, or estimate, and file parented follow-up tickets.", + "triageFollowupsPrompt": "Use {{value0}} to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup." + } + } } }, "hooks": { @@ -705,7 +726,9 @@ "devSearchKeywordSonner": "sonner", "devSearchKeywordError": "error", "devSearchKeywordNotification": "notification", - "projectHostsSummary": "{{value0}} 台のホスト" + "projectHostsSummary": "{{value0}} 台のホスト", + "linearTitle": "Linear", + "linearDescription": "Give agents the skill to read and update your linked Linear tickets." }, "useAppMenuPaste": { "pasteTooLarge": "貼り付け内容が大きすぎます。" @@ -5967,13 +5990,6 @@ "3743ed080c": "セットコマンド", "f79084947b": "新規アプリ" }, - "OrchestrationExamplesDialog": { - "3d1aa105e3": "プロンプトのコピー", - "9b4c004998": "完了", - "969cec9739": "{{value0}} プロンプトの例をコピーします", - "4e46da1889": "プロンプトのコピーに失敗しました。", - "80c6f2feb8": "プロンプトの例をコピーしました。" - }, "OrchestrationPane": { "52e0634e2c": "コーディネーター agents に、ハンドオフ、ワークツリー ハンドオーバー、および順次または並列の子 agents にオーケストレーションを使用するように依頼します。", "ae79504732": "使い方", @@ -6485,7 +6501,9 @@ "2309068a6f": "破壊的な", "65358016ea": "破棄", "dev": "Dev Tools", - "devDescription": "Dev-only tools for exercising UI states." + "devDescription": "Dev-only tools for exercising UI states.", + "linearTitle": "Linear", + "linearDescription": "Give agents the skill to read and update your linked Linear tickets." }, "SettingsFormControls": { "42a4d15a30": "一致するフォントがありません。", @@ -9089,6 +9107,10 @@ "description": "エージェントにLinearタスクの読み取りと編集を許可します。", "copyCommand": "コマンドをコピー" } + }, + "search": { + "title": "Linear", + "description": "Give agents the skill to read and update your linked Linear tickets." } } } @@ -9169,6 +9191,23 @@ "availability": "利用可能:", "testFlight": "TestFlight", "androidApk": "Android APK" + }, + "SkillUsageExampleDialog": { + "copiedPrompt": "プロンプトの例をコピーしました。", + "copyFailed": "プロンプトのコピーに失敗しました。", + "copyExampleAria": "{{value0}} プロンプトの例をコピーします", + "done": "完了", + "copyPrompt": "プロンプトのコピー" + }, + "LinearAgentSkillPane": { + "title": "Linear", + "description": "Give agents the skill to read and update your linked Linear tickets.", + "skillTitle": "Linear skill", + "skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.", + "howToUse": "How to use it", + "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR.", + "terminalTitle": "Linear skill setup", + "terminalAriaLabel": "Linear skill install terminal" } }, "right": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index c8528226850..deeb333b0d7 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -588,6 +588,27 @@ "de50bef680": "macOS가 Orca 알림을 차단하고 있습니다" } } + }, + "linear": { + "usage": { + "examples": { + "readTicket": "Read the linked ticket", + "postUpdate": "Post a progress update", + "moveState": "Move the ticket forward", + "attachPr": "Attach the PR link", + "triageFollowups": "Triage and create follow-ups", + "readTicketSummary": "Pull the linked Linear issue's full context before starting work.", + "readTicketPrompt": "Use {{value0}} to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.", + "postUpdateSummary": "Comment progress or a completion summary back to the Linear issue.", + "postUpdatePrompt": "Use {{value0}} to post a completion update on the linked Linear issue with what changed and how it was verified.", + "moveStateSummary": "Advance the Linear workflow state as the work progresses.", + "moveStatePrompt": "Use {{value0}} to move the linked Linear issue to In Review now that the change is ready.", + "attachPrSummary": "Link the pull request to the Linear issue when you open it.", + "attachPrPrompt": "Use {{value0}} to attach this PR to the linked Linear issue with a PR link.", + "triageFollowupsSummary": "Set assignee, priority, or estimate, and file parented follow-up tickets.", + "triageFollowupsPrompt": "Use {{value0}} to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup." + } + } } }, "hooks": { @@ -705,7 +726,9 @@ "devSearchKeywordSonner": "sonner", "devSearchKeywordError": "error", "devSearchKeywordNotification": "notification", - "projectHostsSummary": "호스트 {{value0}}개" + "projectHostsSummary": "호스트 {{value0}}개", + "linearTitle": "Linear", + "linearDescription": "Give agents the skill to read and update your linked Linear tickets." }, "useAppMenuPaste": { "pasteTooLarge": "붙여넣기 내용이 너무 큽니다." @@ -5930,13 +5953,6 @@ "3743ed080c": "명령 설정", "f79084947b": "새로운 앱" }, - "OrchestrationExamplesDialog": { - "3d1aa105e3": "프롬프트 복사", - "9b4c004998": "완료", - "969cec9739": "{{value0}} 예시 프롬프트 복사", - "4e46da1889": "프롬프트를 복사하지 못했습니다.", - "80c6f2feb8": "예시 프롬프트를 복사했습니다." - }, "OrchestrationPane": { "52e0634e2c": "핸드오프, 작업 트리 핸드오버, 순차 또는 병렬 하위 agents에 대한 오케스트레이션을 사용하도록 코디네이터 agents에 요청하세요.", "ae79504732": "사용방법", @@ -6448,7 +6464,9 @@ "2309068a6f": "파괴적", "65358016ea": "버리기", "dev": "Dev Tools", - "devDescription": "Dev-only tools for exercising UI states." + "devDescription": "Dev-only tools for exercising UI states.", + "linearTitle": "Linear", + "linearDescription": "Give agents the skill to read and update your linked Linear tickets." }, "SettingsFormControls": { "42a4d15a30": "일치하는 글꼴이 없습니다.", @@ -9089,6 +9107,10 @@ "description": "에이전트가 Linear 작업을 읽고 편집할 수 있도록 합니다.", "copyCommand": "명령 복사" } + }, + "search": { + "title": "Linear", + "description": "Give agents the skill to read and update your linked Linear tickets." } } } @@ -9169,6 +9191,23 @@ "availability": "이용 가능:", "testFlight": "TestFlight", "androidApk": "Android APK" + }, + "SkillUsageExampleDialog": { + "copiedPrompt": "예시 프롬프트를 복사했습니다.", + "copyFailed": "프롬프트를 복사하지 못했습니다.", + "copyExampleAria": "{{value0}} 예시 프롬프트 복사", + "done": "완료", + "copyPrompt": "프롬프트 복사" + }, + "LinearAgentSkillPane": { + "title": "Linear", + "description": "Give agents the skill to read and update your linked Linear tickets.", + "skillTitle": "Linear skill", + "skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.", + "howToUse": "How to use it", + "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR.", + "terminalTitle": "Linear skill setup", + "terminalAriaLabel": "Linear skill install terminal" } }, "right": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index b1293bb9932..ba31e9596ca 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -588,6 +588,27 @@ "de50bef680": "macOS 正在阻止 Orca 通知" } } + }, + "linear": { + "usage": { + "examples": { + "readTicket": "Read the linked ticket", + "postUpdate": "Post a progress update", + "moveState": "Move the ticket forward", + "attachPr": "Attach the PR link", + "triageFollowups": "Triage and create follow-ups", + "readTicketSummary": "Pull the linked Linear issue's full context before starting work.", + "readTicketPrompt": "Use {{value0}} to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.", + "postUpdateSummary": "Comment progress or a completion summary back to the Linear issue.", + "postUpdatePrompt": "Use {{value0}} to post a completion update on the linked Linear issue with what changed and how it was verified.", + "moveStateSummary": "Advance the Linear workflow state as the work progresses.", + "moveStatePrompt": "Use {{value0}} to move the linked Linear issue to In Review now that the change is ready.", + "attachPrSummary": "Link the pull request to the Linear issue when you open it.", + "attachPrPrompt": "Use {{value0}} to attach this PR to the linked Linear issue with a PR link.", + "triageFollowupsSummary": "Set assignee, priority, or estimate, and file parented follow-up tickets.", + "triageFollowupsPrompt": "Use {{value0}} to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup." + } + } } }, "hooks": { @@ -705,7 +726,9 @@ "devSearchKeywordSonner": "sonner", "devSearchKeywordError": "error", "devSearchKeywordNotification": "notification", - "projectHostsSummary": "{{value0}} 个主机" + "projectHostsSummary": "{{value0}} 个主机", + "linearTitle": "Linear", + "linearDescription": "Give agents the skill to read and update your linked Linear tickets." }, "useAppMenuPaste": { "pasteTooLarge": "粘贴内容过大。" @@ -5930,13 +5953,6 @@ "3743ed080c": "设置命令", "f79084947b": "新应用程序" }, - "OrchestrationExamplesDialog": { - "3d1aa105e3": "复制提示", - "9b4c004998": "完成", - "969cec9739": "复制 {{value0}} 示例提示", - "4e46da1889": "复制提示失败。", - "80c6f2feb8": "复制示例提示。" - }, "OrchestrationPane": { "52e0634e2c": "要求协调智能体使用编排进行切换、工作树切换以及顺序或并行子智能体。", "ae79504732": "如何使用", @@ -6448,7 +6464,9 @@ "2309068a6f": "destructive", "65358016ea": "放弃", "dev": "开发工具", - "devDescription": "仅用于调试 UI 状态的开发工具。" + "devDescription": "仅用于调试 UI 状态的开发工具。", + "linearTitle": "Linear", + "linearDescription": "Give agents the skill to read and update your linked Linear tickets." }, "SettingsFormControls": { "42a4d15a30": "没有匹配的字体。", @@ -9089,6 +9107,10 @@ "description": "让您的智能体读取和编辑 Linear 任务。", "copyCommand": "复制命令" } + }, + "search": { + "title": "Linear", + "description": "Give agents the skill to read and update your linked Linear tickets." } } } @@ -9169,6 +9191,23 @@ "availability": "可用平台:", "testFlight": "TestFlight", "androidApk": "Android APK" + }, + "SkillUsageExampleDialog": { + "copiedPrompt": "复制示例提示。", + "copyFailed": "复制提示失败。", + "copyExampleAria": "复制 {{value0}} 示例提示", + "done": "完成", + "copyPrompt": "复制提示" + }, + "LinearAgentSkillPane": { + "title": "Linear", + "description": "Give agents the skill to read and update your linked Linear tickets.", + "skillTitle": "Linear skill", + "skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.", + "howToUse": "How to use it", + "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR.", + "terminalTitle": "Linear skill setup", + "terminalAriaLabel": "Linear skill install terminal" } }, "right": { diff --git a/src/renderer/src/lib/agent-skill-nav-install-status.test.ts b/src/renderer/src/lib/agent-skill-nav-install-status.test.ts new file mode 100644 index 00000000000..1960d2b1835 --- /dev/null +++ b/src/renderer/src/lib/agent-skill-nav-install-status.test.ts @@ -0,0 +1,141 @@ +import path from 'node:path' +import { describe, expect, it } from 'vitest' +import type { + SkillFreshnessInstallation, + SkillFreshnessInventory, + SkillFreshnessStatus +} from '../../../shared/skill-freshness' +import type { DiscoveredSkill } from '../../../shared/skills' +import { + getAgentSkillNavInstallStatus, + getLinearAgentSkillNavInstallStatus +} from './agent-skill-nav-install-status' + +function skill(name: string): DiscoveredSkill { + const directoryPath = path.join('home', 'test', '.agents', 'skills', name) + return { + id: name, + name, + description: null, + providers: ['agent-skills'], + sourceKind: 'home', + sourceLabel: 'Agent skills home', + rootPath: path.dirname(directoryPath), + directoryPath, + skillFilePath: path.join(directoryPath, 'SKILL.md'), + installed: true, + fileCount: 1, + updatedAt: null + } +} + +function placement(name: string, status: SkillFreshnessStatus): SkillFreshnessInstallation { + const unresolvedPath = path.join('home', 'test', '.agents', 'skills', name) + return { + id: name, + name, + rootId: 'home-agents', + providers: ['agent-skills'], + sourceKind: 'home', + sourceLabel: 'Agent skills home', + unresolvedPath, + resolvedPath: unresolvedPath, + physicalIdentity: name, + topology: 'canonical-copy', + status, + installedReleaseRevision: 1, + installedAppVersion: '1.0.0', + currentReleaseRevision: 2, + currentPackageDigest: 'current', + currentAppVersion: '2.0.0', + observedPackageDigest: status === 'current' ? 'current' : 'other', + errorCategory: null + } +} + +function inventory( + installations: SkillFreshnessInstallation[], + eligibleUpdateNames: string[] = [] +): SkillFreshnessInventory { + return { schemaVersion: 1, installations, eligibleUpdateNames, scannedAt: 1 } +} + +describe('getAgentSkillNavInstallStatus', () => { + it('keeps loading and missing states ahead of freshness', () => { + expect( + getAgentSkillNavInstallStatus({ + name: 'orca-linear', + installed: true, + loading: true, + inventory: inventory([placement('orca-linear', 'current')]) + }) + ).toBe('checking') + expect( + getAgentSkillNavInstallStatus({ + name: 'orca-linear', + installed: false, + loading: false, + inventory: null + }) + ).toBe('install') + }) +}) + +describe('getLinearAgentSkillNavInstallStatus', () => { + it('reports canonical installs under orca-linear', () => { + const skills = [skill('orca-linear')] + + expect( + getLinearAgentSkillNavInstallStatus({ + skills, + installed: true, + loading: false, + inventory: inventory([placement('orca-linear', 'current')]) + }) + ).toBe('up-to-date') + expect( + getLinearAgentSkillNavInstallStatus({ + skills, + installed: true, + loading: false, + inventory: inventory([placement('orca-linear', 'outdated')], ['orca-linear']) + }) + ).toBe('update-available') + }) + + it('reports legacy-only installs under linear-tickets', () => { + expect( + getLinearAgentSkillNavInstallStatus({ + skills: [skill('linear-tickets')], + installed: true, + loading: false, + inventory: inventory([placement('linear-tickets', 'current')]) + }) + ).toBe('up-to-date') + }) + + it('prefers canonical freshness when both names are installed', () => { + expect( + getLinearAgentSkillNavInstallStatus({ + skills: [skill('orca-linear'), skill('linear-tickets')], + installed: true, + loading: false, + inventory: inventory( + [placement('orca-linear', 'current'), placement('linear-tickets', 'outdated')], + ['linear-tickets'] + ) + }) + ).toBe('up-to-date') + }) + + it('falls back to presence-only status when freshness does not apply', () => { + expect( + getLinearAgentSkillNavInstallStatus({ + skills: [skill('orca-linear')], + installed: true, + loading: false, + inventory: null + }) + ).toBe('installed') + }) +}) diff --git a/src/renderer/src/lib/agent-skill-nav-install-status.ts b/src/renderer/src/lib/agent-skill-nav-install-status.ts new file mode 100644 index 00000000000..b324e4add36 --- /dev/null +++ b/src/renderer/src/lib/agent-skill-nav-install-status.ts @@ -0,0 +1,38 @@ +import type { SkillFreshnessInventory } from '../../../shared/skill-freshness' +import type { DiscoveredSkill } from '../../../shared/skills' +import type { SettingsNavInstallStatus } from './settings-navigation-types' +import { getLinearAgentSkillUpdateTarget } from './linear-agent-skill-update-command' +import { getSkillFreshnessDisplayStatus } from './skill-freshness-display-status' + +type AgentSkillNavInstallStatusInput = { + name: string + installed: boolean + loading: boolean + inventory: SkillFreshnessInventory | null +} + +export function getAgentSkillNavInstallStatus({ + name, + installed, + loading, + inventory +}: AgentSkillNavInstallStatusInput): SettingsNavInstallStatus { + if (loading) { + return 'checking' + } + if (!installed) { + return 'install' + } + return getSkillFreshnessDisplayStatus(inventory, name) +} + +export function getLinearAgentSkillNavInstallStatus( + input: Omit & { + skills: readonly DiscoveredSkill[] + } +): SettingsNavInstallStatus { + // Why: the sidebar must evaluate the same installed name the card will update, + // including legacy-only linear-tickets installs. + const updateTarget = getLinearAgentSkillUpdateTarget(input.skills, input.installed) + return getAgentSkillNavInstallStatus({ ...input, name: updateTarget.skillName }) +} diff --git a/src/renderer/src/lib/linear-agent-skill-update-command.test.ts b/src/renderer/src/lib/linear-agent-skill-update-command.test.ts new file mode 100644 index 00000000000..f4e2c543ced --- /dev/null +++ b/src/renderer/src/lib/linear-agent-skill-update-command.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest' +import type { DiscoveredSkill } from '../../../shared/skills' +import { + LINEAR_TICKETS_SKILL_UPDATE_COMMAND, + ORCA_LINEAR_SKILL_UPDATE_COMMAND +} from './agent-feature-install-commands' +import { getLinearAgentSkillUpdateTarget } from './linear-agent-skill-update-command' + +function skill(name: string): DiscoveredSkill { + return { + id: name, + name, + description: null, + providers: ['agent-skills'], + sourceKind: 'home', + sourceLabel: 'Agent skills home', + rootPath: '/home/test/.agents/skills', + directoryPath: `/home/test/.agents/skills/${name}`, + skillFilePath: `/home/test/.agents/skills/${name}/SKILL.md`, + installed: true, + fileCount: 1, + updatedAt: null + } +} + +describe('getLinearAgentSkillUpdateTarget', () => { + it('targets the canonical skill for missing, canonical, and dual-name installs', () => { + expect(getLinearAgentSkillUpdateTarget([], false)).toEqual({ + skillName: 'orca-linear', + command: ORCA_LINEAR_SKILL_UPDATE_COMMAND + }) + expect(getLinearAgentSkillUpdateTarget([skill('orca-linear')], true)).toEqual({ + skillName: 'orca-linear', + command: ORCA_LINEAR_SKILL_UPDATE_COMMAND + }) + expect( + getLinearAgentSkillUpdateTarget([skill('orca-linear'), skill('linear-tickets')], true) + ).toEqual({ + skillName: 'orca-linear', + command: ORCA_LINEAR_SKILL_UPDATE_COMMAND + }) + }) + + it('keeps the update and freshness identity on a legacy-only install', () => { + expect(getLinearAgentSkillUpdateTarget([skill('linear-tickets')], true)).toEqual({ + skillName: 'linear-tickets', + command: LINEAR_TICKETS_SKILL_UPDATE_COMMAND + }) + }) +}) diff --git a/src/renderer/src/lib/linear-agent-skill-update-command.ts b/src/renderer/src/lib/linear-agent-skill-update-command.ts index 69dbcce84d7..4dc3661d506 100644 --- a/src/renderer/src/lib/linear-agent-skill-update-command.ts +++ b/src/renderer/src/lib/linear-agent-skill-update-command.ts @@ -10,12 +10,17 @@ import { ORCA_LINEAR_SKILL_UPDATE_COMMAND } from '@/lib/agent-feature-install-commands' -// Why: legacy-only installs must update the installed legacy skill, while -// fresh/canonical/both-name states should move through the canonical name. -export function getLinearAgentSkillUpdateCommand( +export type LinearAgentSkillUpdateTarget = { + skillName: typeof ORCA_LINEAR_SKILL_NAME | typeof LINEAR_TICKETS_SKILL_NAME + command: string +} + +// Why: legacy-only installs must update and report freshness for the installed +// legacy skill, while fresh/canonical/both-name states use the canonical name. +export function getLinearAgentSkillUpdateTarget( skills: readonly DiscoveredSkill[], installed: boolean -): string { +): LinearAgentSkillUpdateTarget { const canonicalSkillInstalled = hasInstalledAgentSkill(skills, ORCA_LINEAR_SKILL_NAME, { sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS }) @@ -23,6 +28,13 @@ export function getLinearAgentSkillUpdateCommand( sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS }) return !installed || canonicalSkillInstalled || !legacySkillInstalled - ? ORCA_LINEAR_SKILL_UPDATE_COMMAND - : LINEAR_TICKETS_SKILL_UPDATE_COMMAND + ? { skillName: ORCA_LINEAR_SKILL_NAME, command: ORCA_LINEAR_SKILL_UPDATE_COMMAND } + : { skillName: LINEAR_TICKETS_SKILL_NAME, command: LINEAR_TICKETS_SKILL_UPDATE_COMMAND } +} + +export function getLinearAgentSkillUpdateCommand( + skills: readonly DiscoveredSkill[], + installed: boolean +): string { + return getLinearAgentSkillUpdateTarget(skills, installed).command } diff --git a/src/renderer/src/lib/linear-usage-examples.ts b/src/renderer/src/lib/linear-usage-examples.ts new file mode 100644 index 00000000000..af6b0a5f1e8 --- /dev/null +++ b/src/renderer/src/lib/linear-usage-examples.ts @@ -0,0 +1,77 @@ +import { translate } from '@/i18n/i18n' +import { createLocalizedCatalog } from '@/i18n/localized-catalog' +import { ORCA_LINEAR_SKILL_NAME } from './agent-feature-install-commands' +import type { SkillUsageExample } from './skill-usage-example' + +const LINEAR_SLASH_COMMAND = `/${ORCA_LINEAR_SKILL_NAME}` + +export const getLinearUsageExamples = createLocalizedCatalog((): SkillUsageExample[] => [ + { + id: 'read-ticket', + title: translate('auto.lib.linear.usage.examples.readTicket', 'Read the linked ticket'), + summary: translate( + 'auto.lib.linear.usage.examples.readTicketSummary', + "Pull the linked Linear issue's full context before starting work." + ), + prompt: translate( + 'auto.lib.linear.usage.examples.readTicketPrompt', + 'Use {{value0}} to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.', + { value0: LINEAR_SLASH_COMMAND } + ) + }, + { + id: 'post-update', + title: translate('auto.lib.linear.usage.examples.postUpdate', 'Post a progress update'), + summary: translate( + 'auto.lib.linear.usage.examples.postUpdateSummary', + 'Comment progress or a completion summary back to the Linear issue.' + ), + prompt: translate( + 'auto.lib.linear.usage.examples.postUpdatePrompt', + 'Use {{value0}} to post a completion update on the linked Linear issue with what changed and how it was verified.', + { value0: LINEAR_SLASH_COMMAND } + ) + }, + { + id: 'move-state', + title: translate('auto.lib.linear.usage.examples.moveState', 'Move the ticket forward'), + summary: translate( + 'auto.lib.linear.usage.examples.moveStateSummary', + 'Advance the Linear workflow state as the work progresses.' + ), + prompt: translate( + 'auto.lib.linear.usage.examples.moveStatePrompt', + 'Use {{value0}} to move the linked Linear issue to In Review now that the change is ready.', + { value0: LINEAR_SLASH_COMMAND } + ) + }, + { + id: 'attach-pr', + title: translate('auto.lib.linear.usage.examples.attachPr', 'Attach the PR link'), + summary: translate( + 'auto.lib.linear.usage.examples.attachPrSummary', + 'Link the pull request to the Linear issue when you open it.' + ), + prompt: translate( + 'auto.lib.linear.usage.examples.attachPrPrompt', + 'Use {{value0}} to attach this PR to the linked Linear issue with a PR link.', + { value0: LINEAR_SLASH_COMMAND } + ) + }, + { + id: 'triage-followups', + title: translate( + 'auto.lib.linear.usage.examples.triageFollowups', + 'Triage and create follow-ups' + ), + summary: translate( + 'auto.lib.linear.usage.examples.triageFollowupsSummary', + 'Set assignee, priority, or estimate, and file parented follow-up tickets.' + ), + prompt: translate( + 'auto.lib.linear.usage.examples.triageFollowupsPrompt', + 'Use {{value0}} to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup.', + { value0: LINEAR_SLASH_COMMAND } + ) + } +]) diff --git a/src/renderer/src/lib/orchestration-usage-examples.ts b/src/renderer/src/lib/orchestration-usage-examples.ts index 8840aa01f19..ab40edd5310 100644 --- a/src/renderer/src/lib/orchestration-usage-examples.ts +++ b/src/renderer/src/lib/orchestration-usage-examples.ts @@ -1,13 +1,8 @@ import { translate } from '@/i18n/i18n' import { createLocalizedCatalog } from '@/i18n/localized-catalog' -export type OrchestrationUsageExample = { - id: string - title: string - summary: string - prompt: string -} +import type { SkillUsageExample } from './skill-usage-example' -export const getOrchestrationUsageExamples = createLocalizedCatalog(() => [ +export const getOrchestrationUsageExamples = createLocalizedCatalog((): SkillUsageExample[] => [ { id: 'handoff', title: translate('auto.lib.orchestration.usage.examples.5e0d489fe1', 'Hand off an active task'), diff --git a/src/renderer/src/lib/settings-navigation-types.ts b/src/renderer/src/lib/settings-navigation-types.ts index b59ed0f6768..f3409e69000 100644 --- a/src/renderer/src/lib/settings-navigation-types.ts +++ b/src/renderer/src/lib/settings-navigation-types.ts @@ -35,6 +35,7 @@ export type SettingsNavTarget = | 'experimental' | 'agents' | 'orchestration' + | 'linear' | 'servers' | 'mobile' | 'mobile-emulator' diff --git a/src/renderer/src/lib/skill-usage-example.ts b/src/renderer/src/lib/skill-usage-example.ts new file mode 100644 index 00000000000..b29fb93747c --- /dev/null +++ b/src/renderer/src/lib/skill-usage-example.ts @@ -0,0 +1,6 @@ +export type SkillUsageExample = { + id: string + title: string + summary: string + prompt: string +} From 7c0b84f2b6304c25b9843324f3767d454ddac26f Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:16:17 -0700 Subject: [PATCH 013/137] fix(terminal): invalidate cached glyphs when WebGL atlas changes (#8899) * feat(terminal): add flag-gated render-desync sentinel for WebGL panes Detects the buffer-clean/render-stale glyph garble class in the field: per visible WebGL pane, compare the cells the xterm buffer says hold glyphs against the ink actually present on the canvas, sampled in the same task as a forced synchronous redraw so a divergence proves the render model/atlas is wrong rather than a missed present. A trip requires the same screen cells to stay divergent across three samples (real desync is pinned; scroll lag moves), then records a webgl-render-desync breadcrumb, stashes evidence (canvas PNG + buffer text) for bug reports, and runs the same shared-atlas recovery a tab reveal performs, so a stuck-garbled pane self-heals within seconds. Off by default; arm on any build via localStorage.setItem('orca:render-desync-sentinel', '1') and reload. * fix(terminal): invalidate glyph cache on atlas replacement Reproduce the WebGL atlas identity mismatch with two live terminals and force cached geometry to rebuild whenever a different shared atlas is attached. Persist flag-gated render-desync evidence and retain the investigation tooling used to validate the field signature. * fix(terminal): harden render desync diagnostics * docs(reliability): clarify Linux WebGL evidence gap --- TERMINAL-GARBLE-INVESTIGATION.md | 254 ++++++++ .../@xterm__addon-webgl@0.20.0-beta.286.patch | 74 ++- config/reliability-gates.jsonc | 68 +++ package.json | 1 + pnpm-lock.yaml | 9 +- src/main/ipc/register-core-handlers.test.ts | 7 + src/main/ipc/register-core-handlers.ts | 2 + .../terminal-render-desync-evidence.test.ts | 121 ++++ .../ipc/terminal-render-desync-evidence.ts | 114 ++++ src/main/ipc/ui.ts | 2 +- src/preload/api-types.ts | 8 + src/preload/index.ts | 5 +- .../terminal-freeze-breadcrumbs.ts | 6 + .../terminal-render-desync-frame.test.ts | 75 +++ .../terminal-render-desync-frame.ts | 250 ++++++++ .../terminal-render-desync-sentinel.test.ts | 218 +++++++ .../terminal-render-desync-sentinel.ts | 292 +++++++++ .../pane-manager-registry.test.ts | 25 + .../lib/pane-manager/pane-manager-registry.ts | 34 ++ src/renderer/src/web/web-preload-api.ts | 8 +- src/shared/terminal-render-desync-evidence.ts | 14 + tests/e2e/terminal-webgl-atlas-budget.spec.ts | 172 ++++++ tools/terminal-garble-frame-analysis.mjs | 155 +++++ tools/terminal-garble-production-repro.mjs | 575 ++++++++++++++++++ ...erminal-garble-react-terminal-recovery.mjs | 98 +++ tools/terminal-garble-session-replay.mjs | 79 +++ 26 files changed, 2641 insertions(+), 25 deletions(-) create mode 100644 TERMINAL-GARBLE-INVESTIGATION.md create mode 100644 src/main/ipc/terminal-render-desync-evidence.test.ts create mode 100644 src/main/ipc/terminal-render-desync-evidence.ts create mode 100644 src/renderer/src/components/terminal-pane/terminal-render-desync-frame.test.ts create mode 100644 src/renderer/src/components/terminal-pane/terminal-render-desync-frame.ts create mode 100644 src/renderer/src/components/terminal-pane/terminal-render-desync-sentinel.test.ts create mode 100644 src/renderer/src/components/terminal-pane/terminal-render-desync-sentinel.ts create mode 100644 src/shared/terminal-render-desync-evidence.ts create mode 100644 tools/terminal-garble-frame-analysis.mjs create mode 100644 tools/terminal-garble-production-repro.mjs create mode 100644 tools/terminal-garble-react-terminal-recovery.mjs create mode 100644 tools/terminal-garble-session-replay.mjs diff --git a/TERMINAL-GARBLE-INVESTIGATION.md b/TERMINAL-GARBLE-INVESTIGATION.md new file mode 100644 index 00000000000..9afffeb27ac --- /dev/null +++ b/TERMINAL-GARBLE-INVESTIGATION.md @@ -0,0 +1,254 @@ +# Terminal Glyph Garble — Investigation Summary & Plan + +_2026-07-17 · status: deterministic stale-atlas reproduction fixed; sentinel retained for field confirmation · owner: Brennan + Claude_ + +## TL;DR + +The recurring "terminal turns into garbled glyph fragments" bug is real and is +a WebGL glyph-atlas/model desynchronization. On 2026-07-17 it recurred on +`v1.4.145-rc.2` immediately after a terminal link click. This time the corrupted +frame, byte-exact PTY history, and terminal identity were preserved before the +pane healed. + +Offline replay reconstructs the correct `231x84` xterm buffer from the saved PTY +stream. The corrupted screenshot is missing rendered ink in 2,702 of 4,487 +populated buffer cells (60.2%) and repeatedly shows colored emoji/icon fragments +from Claude's status line across unrelated body cells. A new two-renderer pixel +reproduction now produces the same stale-coordinate failure deterministically: +attaching a renderer to a different shared atlas left its cached vertices marked +as synchronized. The pre-fix frame differed in 327,165 pixels; after forcing a +full model rebuild on atlas identity changes, it is byte-identical to baseline. + +The rc.2 capture did not include renderer internals, so the field occurrence +cannot retrospectively prove that this was its exact transition. The pixel +signature, broken invariant, and upstream xterm implementation all agree, +making this the first evidence-backed fix candidate for the captured failure. + +## Deterministic root-cause reproduction and fix — 2026-07-17 + +`GlyphRenderer.setAtlas()` previously copied the replacement atlas's current +clear generation into `_lastSeenClearModelGeneration`. That treated the new +atlas as synchronized even though every cached vertex still contained texture +page and UV coordinates from the old atlas. The next render could therefore +upload the replacement atlas and draw it with the old coordinates, producing +valid glyph fragments in unrelated cells while the xterm buffer remained +correct. + +The regression uses two real WebGL terminals with separate atlas populations, +then attaches one renderer to the other's atlas without changing its buffer. It +asserts on the rendered canvas, not private generation values: + +- **Before fix:** 327,165 pixels differ from the intact baseline. +- **After fix:** 0 pixels differ; the canvas is byte-identical. +- **Fix:** set the renderer's last-seen generation to `-1` whenever atlas + identity changes, forcing the next frame through the existing full-model + rebuild path. Reusing the same atlas remains a no-op. + +This matches current upstream xterm behavior, which also marks a newly attached +atlas as unseen instead of adopting its current layout generation. + +## Real occurrence captured — 2026-07-17 + +- **Build:** `v1.4.145-rc.2`; app and GPU processes had both been alive for + roughly 11h25m. No GPU restart or WebGL/context-loss log occurred around the + failure. +- **Trigger:** Cmd-clicking `https://github.com/stablyai/orca/pull/9218` in + terminal `term_bc932216-45c6-41f2-94f1-701b702980ea`. +- **PTY/session:** `numbfish@@cc6c08b1`; the 5,044,220 output bytes replay + cleanly into `@xterm/headless`, producing 4,487 populated visible cells. +- **Pixel proof:** with the captured `231x84` geometry (`8x16` device pixels per + cell), 2,702 expected cells have no ink in the corrupted screenshot. The + repeated status-line emoji fragments strongly suggest stale per-pane glyph + UV/model references sampling a repopulated shared atlas. +- **Durable local evidence:** + `~/Library/Application Support/orca/terminal-render-desync-evidence/manual-20260717-125306-term_bc932216/` + contains the screenshot, OCKL terminal log, reconstructed buffer, checksums, + and capture metadata. It is mode `0700`; files are mode `0600` because the + terminal contents may be sensitive. + +The installed rc.2 build did not contain PR #8899, so it was impossible to +extract live renderer internals from this occurrence. The initial #8899 design +also redrew before measuring and waited three five-second samples; either choice +could erase a failure that self-heals in about five seconds. The revised design +below corrects both problems. + +## Reproduction follow-up — 2026-07-15 + +The synthetic repro was rebuilt around the exact reported release +(`v1.4.142-rc.4`) rather than the development build. The driver uses an +isolated profile and throwaway repository, recovers the packaged build's live +xterm buffers from React's retained refs, and compares every populated buffer +cell against the corresponding screenshot cell without forcing a redraw. + +Two sustained dark-theme runs completed: + +- **Production-shaped replay:** 8 tabs / 16 live panes, a 60-second warmup, + one real browser handoff, then 30 verified OSC-8 Cmd-clicks. Across 1,234 + pane-frame samples and 249,661 populated cells, no expected cell was missing + ink. All renderer snapshots were unpaused with no pending full refresh. +- **Accumulated-atlas + focus replay:** colored glyph churn compressed hours of + atlas history until the shared atlas continuously cycled between 10 and 16 + pages. Twenty URL-handler cycles displaced the main window with an isolated + native focus sink, then restored it while replay continued. Across 634 + pane-frame samples and 546,928 populated cells, there were zero missing cells + and zero persistent desyncs. Thirteen clicks retained the exact test URL; + seven acquired adjacent churn text in xterm's link extent, but still exercised + the same link handler and focus transition. + +The oracle was checked by blanking one captured pane in memory: it reported +1,511/1,511 missing cells and tripped after the required three frames. Earlier +whole-pane density suspects were inspected and rejected as legitimate TUI +rewrites; one later one-cell event was a single-frame buffer/screenshot race at +the entering prompt row and appeared normally in the following frame. + +**Result:** the reported garble did not reproduce in the isolated exact-release +environment, including under multipage atlas eviction and native focus churn. +This is a materially stronger negative result, not evidence that the live bug +is gone. The remaining difference is Brennan's long-lived real process state, +GPU/driver history, and real occlusion/parking schedule. + +Reusable tooling now lives in: + +- `tools/terminal-garble-production-repro.mjs` +- `tools/terminal-garble-session-replay.mjs` +- `tools/terminal-garble-react-terminal-recovery.mjs` +- `tools/terminal-garble-frame-analysis.mjs` + +## The bug + +- A terminal pane running a dense TUI (Claude Code is the reliable stress + case) suddenly renders as tiny colored glyph fragments / blank cells while + the pane keeps streaming. A sibling pane sharing the same glyph atlas can + stay perfectly crisp. +- The terminal's _buffer_ is always correct — copying text works, and + scrolling, resizing, selecting, or **switching tabs and back instantly heals + it**. It is purely a rendering-layer desync. +- Observed 2026-07-13 (screen recording: garble ~5s after a ⌘+click on a + link, self-healed) and again 2026-07-15 on v1.4.142-rc.4 with the same + steps. Long history of related reports (#5130 and the partial-fix lineage: + #2281, #1579, #4941, #2708, #5042, #7064, #7554, #8150). + +## What is proven (not theory) + +1. **The failure class is real and demonstrable.** A deterministic harness + shows the _unpatched_ WebGL addon garbling ~20% of a pane's cells while its + buffer is clean (screenshots + per-cell pixel proof, side by side with the + healed render of the same buffer). +2. **Atlas replacement was a real uncovered invalidation gap.** A renderer + could attach to a different shared atlas while retaining vertices from the + previous atlas, then suppress the required full rebuild by adopting the new + atlas's current generation. The real WebGL pixel test is red before the fix + (327,165 pixels changed) and byte-identical after it. +3. **The field occurrence remains retrospective evidence, not a live internal + trace.** rc.2 could not record the renderer generations, so no honest claim + can prove which call replaced its atlas. The failure shape is the same and + the violated invariant is sufficient to cause it. +4. **WebGL must stay.** The DOM renderer fallback is the "easy fix with bad + consequences" the team explicitly rejected before (see PR #5048 history); + GPU rendering is a performance requirement for dense agent TUIs. + +## What got fixed along the way + +### PR #8672 — atlas sampler-budget fix (merged, in rc.4) + +The atlas could grow more texture pages than the shader has sampler slots +(16 on most Macs); glyphs on the extra pages render as undefined pixels, and +one code path could crash the renderer outright. #8672 makes the atlas +evict-and-rebuild rather than exceed capacity — the same design direction the +xterm.js maintainers have in flight upstream (xtermjs/xterm.js#6043). + +**Honest status:** it fixes a real, provable defect and should stay merged — +but it is _not_ the fix for Brennan's repro (that was believed at the time and +was wrong; the recurrence on rc.4 disproved it). It was authored during this +investigation and merged from Brennan's account on 07-14; flagged here because +it hadn't clearly registered. + +### Why the earlier fixes kept "missing" + +Earlier fixes — the team's lineage and this investigation's first two — were +chosen from _plausible mechanisms identified by reading code_. This subsystem +(shared glyph atlas + per-pane renderers + Orca's visibility/recovery +scheduling) offers many plausible failure mechanisms; several were genuinely +broken and genuinely got fixed. But plausibility is not causality, and the +only way to discriminate is data. The 2026-07-17 capture narrowed the failure +to stale atlas/model sampling; a focused audit then found and pixel-reproduced +the atlas-identity transition that the generation logic incorrectly skipped. + +## Remaining uncertainty and field confirmation + +- **The original corpse is incomplete.** The screenshot and replayed buffer + prove the render mismatch, but rc.2 could not record the renderer generation + or atlas identity at the failure moment. Field confirmation on the fixed + build is still necessary before calling the incident closed. +- **The user gesture is not yet deterministic.** The invalid atlas-replacement + state now reproduces exactly, but an isolated Cmd-click soak did not reliably + drive Orca into that state. Long-lived process, parking, reattachment, and + configuration timing may determine when the replacement occurs. +- **A false capture already happened once.** An early detector "reproduced" + a 14.7% divergence that turned out to be an artifact of driving xterm's + pause flag unfaithfully; driven through xterm's real unpause path it healed + to 0%. Lesson encoded: fixes get written against faithful captures only. + +## Field confirmation: renderer-state capture (revised PR #8899) + +**Render-desync sentinel** — off by default, armed per-machine: + +```js +// DevTools console in Orca, then reload: +localStorage.setItem('orca:render-desync-sentinel', '1') +``` + +After a terminal Cmd/Ctrl-click, it samples that pane every 250ms for 10 +seconds. It reads the compositor-presented canvas **without a redraw** and +compares it with the simultaneous xterm buffer. A trip requires the same +screen cells to be divergent in two consecutive samples; real desync is pinned +in place while scroll/frame races move. + +On trip it does three things: + +1. Records a `webgl-render-desync` breadcrumb (joins the existing + freeze-report diagnostics). +2. Writes the garbled canvas PNG, clean buffer text, atlas clear generation, + atlas page versions, glyph texture versions, glyph-renderer generation, + model line lengths, and vertex state under app-owned user data. The flag is + an explicit local opt-in because pixels and buffer text may be sensitive. +3. Only after the corrupt evidence write succeeds, runs the same recovery a tab + switch performs and saves a healed reference PNG beside the capture. + +The renderer keeps at most four capture records and releases their large pixel +and buffer payloads after persistence. The main process independently retains +at most four capture directories under a 96 MiB aggregate budget. + +**Lifetime is bounded:** the sentinel is scaffolding. It comes out once the +root cause is fixed — or in ~a month if it never trips (which would itself be +signal). Review should come from the terminal-rendering owners, not +self-certified. + +## Decisions & discipline + +| Item | Decision | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PR #8672 (sampler budget) | Keep merged — real defect, independent of this repro | +| PR #8899 (sentinel) | Merge with removal deadline; arm on Brennan's machine | +| Atlas replacement fix | Ship with the pixel regression; require field confirmation before declaring the incident closed | +| Disabling WebGL / DOM fallback | Rejected (team decision, performance) | +| More synthetic repro rigs | Retired — negative results already conclusive; a rig also caused a real-browser-popup incident (clicks reached `shell.openExternal`), rules for any future rig are documented in the session notes | + +## If the revised sentinel trips again + +The capture will show whether the renderer was still stale at an atlas identity +change or whether a second invalidation path remains. Preserve that evidence +before recovery and extend the permanent pixel regression around the recorded +transition. + +## Artifacts + +- Proving harness (buffer-vs-render, calibrated to 0% baseline, catches the + old bug at ~20%): `/tmp/glyph-proof/` +- Red/green + regression harnesses for the atlas fixes: `/tmp/glyph-overflow-repro/`, + `/tmp/xterm-garble-repro/` +- Live-rig tooling (replayer for Orca terminal-history logs, CDP driver): + the platform temp directory's `garble-rig/` — `shell.openExternal` is stubbed + by default; pass `--allow-open-url` only when a real browser handoff is required +- PRs: [#8672](https://github.com/stablyai/orca/pull/8672) (merged), + [#8899](https://github.com/stablyai/orca/pull/8899) (open) diff --git a/config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch b/config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch index 57e26c74432..fe1f1a4303a 100644 --- a/config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch +++ b/config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch @@ -1,45 +1,79 @@ diff --git a/lib/addon-webgl.js b/lib/addon-webgl.js -index bd9997910ca3490aa85a140ea22913c6faacacb4..9bcc72589963a82225be093d8b07e35f72485c41 100644 +index bd9997910ca3490aa85a140ea22913c6faacacb4..d1b3ee78f478f55ffa3185e62fe18277dc7b6d09 100644 --- a/lib/addon-webgl.js +++ b/lib/addon-webgl.js -@@ -1,2 +1,2 @@ +@@ -1,2 +1,15 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.WebglAddon=e():t.WebglAddon=e()}(globalThis,()=>(()=>{"use strict";var t={105(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.WindowIntervalTimer=e.eventType=void 0,e.getWindow=r,e.addDisposableListener=o,e.addStandardDisposableListener=function(t,e,i,s){return o(t,e,i,s)},e.getDomNodePagePosition=function(t){const e=t.getBoundingClientRect(),i=r(t);return{left:e.left+i.scrollX,top:e.top+i.scrollY,width:e.width,height:e.height}},e.scheduleAtNextAnimationFrame=function(t,e,i=0){const s=h(t),r=new n(e,i);return s.next.push(r),s.animFrameRequested||(s.animFrameRequested=!0,t.requestAnimationFrame(()=>function(t){const e=h(t);for(e.animFrameRequested=!1,e.current=e.next,e.next=[],e.inAnimationFrameRunner=!0;e.current.length>0;)e.current.sort(n.sort),e.current.shift().execute();e.inAnimationFrameRunner=!1}(t))),r};const s=i(578);function r(t){const e=t;if(e?.ownerDocument?.defaultView)return e.ownerDocument.defaultView;const i=t;return i?.view?i.view:window}class a{constructor(t,e,i,s){this._node=t,this._type=e,this._handler=i,this._options=s,t.addEventListener(e,i,s)}dispose(){this._node&&this._handler&&(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,this._handler=null)}}function o(t,e,i,s){return new a(t,e,i,s)}e.eventType={CLICK:"click",MOUSE_DOWN:"mousedown",MOUSE_OVER:"mouseover",MOUSE_LEAVE:"mouseleave",KEY_DOWN:"keydown",KEY_UP:"keyup",INPUT:"input",BLUR:"blur",FOCUS:"focus",CHANGE:"change",POINTER_DOWN:"pointerdown",POINTER_MOVE:"pointermove",POINTER_UP:"pointerup",MOUSE_WHEEL:"wheel",WHEEL:"wheel"};class n{constructor(t,e){this._runner=t,this.priority=e,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(t){console.error(t)}}static sort(t,e){return e.priority-t.priority}}const L=new Map;function h(t){let e=L.get(t);return e||(e={next:[],current:[],animFrameRequested:!1,inAnimationFrameRunner:!1},L.set(t,e)),e}class T extends s.IntervalTimer{constructor(t){super(),this._defaultTarget=t?r(t):void 0}cancelAndSet(t,e,i){super.cancelAndSet(t,e,i??this._defaultTarget??window)}}e.WindowIntervalTimer=T},814(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.INVERTED_DEFAULT_COLOR=void 0,e.INVERTED_DEFAULT_COLOR=257},959(t,e){function i(t){return 57508<=t&&t<=57558}function s(t){return t>=128512&&t<=128591||t>=127744&&t<=128511||t>=128640&&t<=128767||t>=9728&&t<=9983||t>=9984&&t<=10175||t>=65024&&t<=65039||t>=129280&&t<=129535||t>=127462&&t<=127487}Object.defineProperty(e,"__esModule",{value:!0}),e.throwIfFalsy=function(t){if(!t)throw new Error("value must not be falsy");return t},e.isPowerlineGlyph=i,e.isRestrictedPowerlineGlyph=function(t){return 57520<=t&&t<=57527},e.isEmoji=s,e.allowRescaling=function(t,e,r,a){return 1===e&&r>Math.ceil(1.5*a)&&void 0!==t&&t>255&&!s(t)&&!i(t)&&!function(t){return 57344<=t&&t<=63743}(t)},e.treatGlyphAsBackgroundColor=function(t){return i(t)||function(t){return 9472<=t&&t<=9631}(t)},e.createRenderDimensions=function(){return{css:{canvas:{width:0,height:0},cell:{width:0,height:0}},device:{canvas:{width:0,height:0},cell:{width:0,height:0},char:{width:0,height:0,left:0,top:0}}}},e.computeNextVariantOffset=function(t,e,i=0){return(t-(2*Math.round(e)-i))%(2*Math.round(e))}},948(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.createSelectionRenderModel=function(){return new i};class i{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(t,e,i,s=!1){if(this.selectionStart=e,this.selectionEnd=i,!e||!i||e[0]===i[0]&&e[1]===i[1])return void this.clear();const r=t.buffers.active.ydisp,a=e[1]-r,o=i[1]-r,n=Math.max(a,0),L=Math.min(o,t.rows-1);n>=t.rows||L<0?this.clear():(this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=a,this.viewportEndRow=o,this.viewportCappedStartRow=n,this.viewportCappedEndRow=L,this.startCol=e[0],this.endCol=i[0])}isCellSelected(t,e,i){return!!this.hasSelection&&(i-=t.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?e>=this.startCol&&i>=this.viewportCappedStartRow&&e=this.viewportCappedStartRow&&e>=this.endCol&&i<=this.viewportCappedEndRow:i>this.viewportStartRow&&i=this.startCol&&e=this.startCol)}}},536(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.TextBlinkStateManager=void 0;const s=i(426);class r extends s.Disposable{constructor(t,e,i){super(),this._renderCallback=t,this._coreBrowserService=e,this._optionsService=i,this._intervalDuration=0,this._blinkOn=!0,this._needsBlinkInViewport=!1,this._isViewportVisible=!0,this._register(this._optionsService.onSpecificOptionChange("blinkIntervalDuration",t=>{this.setIntervalDuration(t)})),this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration),this._register((0,s.toDisposable)(()=>this._clearInterval()))}get isBlinkOn(){return this._blinkOn}get isEnabled(){return this._intervalDuration>0}setNeedsBlinkInViewport(t){this._needsBlinkInViewport!==t&&(this._needsBlinkInViewport=t,this._updateIntervalState())}setViewportVisible(t){this._isViewportVisible!==t&&(this._isViewportVisible=t,this._updateIntervalState())}setIntervalDuration(t){t!==this._intervalDuration&&(this._intervalDuration=t,this._clearInterval(),this._updateIntervalState())}_updateIntervalState(){if(this._intervalDuration>0&&this._needsBlinkInViewport&&this._isViewportVisible){if(void 0!==this._interval)return;const t=this._blinkOn;return this._blinkOn=!0,this._interval=this._coreBrowserService.window.setInterval(()=>{this._blinkOn=!this._blinkOn,this._renderCallback()},this._intervalDuration),void(t||this._renderCallback())}this._clearInterval(),this._blinkOn||(this._blinkOn=!0,this._renderCallback())}_clearInterval(){void 0!==this._interval&&(this._coreBrowserService.window.clearInterval(this._interval),this._interval=void 0)}}e.TextBlinkStateManager=r},578(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.IntervalTimer=e.MicrotaskTimer=e.TimeoutTimer=void 0,e.timeout=function(t){return new Promise(e=>setTimeout(e,t))},e.disposableTimeout=function(t,e=0,i){const r=setTimeout(()=>{t(),i&&a.dispose()},e),a=(0,s.toDisposable)(()=>{clearTimeout(r)});return i?.add(a),a};const s=i(426);e.TimeoutTimer=class{constructor(){this._token=-1,this._isDisposed=!1}dispose(){this.cancel(),this._isDisposed=!0}cancel(){-1!==this._token&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(t,e){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout(()=>{this._token=-1,t()},e)}setIfNotSet(t,e){if(this._isDisposed)throw new Error("Calling setIfNotSet on a disposed TimeoutTimer");-1===this._token&&(this._token=setTimeout(()=>{this._token=-1,t()},e))}},e.MicrotaskTimer=class{constructor(){this._isScheduled=!1,this._isDisposed=!1}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._isScheduled=!1}set(t){if(this._isDisposed)throw new Error("Calling set on a disposed MicrotaskTimer");this._isScheduled||(this._isScheduled=!0,queueMicrotask(()=>{this._isScheduled&&(this._isScheduled=!1,t())}))}},e.IntervalTimer=class{constructor(){this._isDisposed=!1}cancel(){this._disposable?.dispose(),this._disposable=void 0}cancelAndSet(t,e,i=globalThis){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed IntervalTimer");this.cancel();const s=i.setInterval(()=>{t()},e);this._disposable={dispose:()=>{i.clearInterval(s),this._disposable=void 0}}}dispose(){this.cancel(),this._isDisposed=!0}}},993(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.rgba=e.rgb=e.css=e.color=e.channels=e.NULL_COLOR=void 0,e.toPaddedHex=l,e.contrastRatio=d;let i=0,s=0,r=0,a=0;var o,n,L,h,T;function l(t){const e=t.toString(16);return e.length<2?"0"+e:e}function d(t,e){return t>>0},t.toColor=function(e,i,s,r){return{css:t.toCss(e,i,s,r),rgba:t.toRgba(e,i,s,r)}}}(o||(e.channels=o={})),function(t){function e(t,e){return a=Math.round(255*e),[i,s,r]=T.toChannels(t.rgba),{css:o.toCss(i,s,r,a),rgba:o.toRgba(i,s,r,a)}}t.blend=function(t,e){if(a=(255&e.rgba)/255,1===a)return{css:e.css,rgba:e.rgba};const n=e.rgba>>24&255,L=e.rgba>>16&255,h=e.rgba>>8&255,T=t.rgba>>24&255,l=t.rgba>>16&255,d=t.rgba>>8&255;return i=T+Math.round((n-T)*a),s=l+Math.round((L-l)*a),r=d+Math.round((h-d)*a),{css:o.toCss(i,s,r),rgba:o.toRgba(i,s,r)}},t.isOpaque=function(t){return!(255&~t.rgba)},t.ensureContrastRatio=function(t,e,i){const s=T.ensureContrastRatio(t.rgba,e.rgba,i);if(s)return o.toColor(s>>24&255,s>>16&255,s>>8&255)},t.opaque=function(t){const e=(255|t.rgba)>>>0;return[i,s,r]=T.toChannels(e),{css:o.toCss(i,s,r),rgba:e}},t.opacity=e,t.multiplyOpacity=function(t,i){return a=255&t.rgba,e(t,a*i/255)},t.toColorRGB=function(t){return[t.rgba>>24&255,t.rgba>>16&255,t.rgba>>8&255]}}(n||(e.color=n={})),function(t){let e,n;try{const t=document.createElement("canvas");t.width=1,t.height=1;const i=t.getContext("2d",{willReadFrequently:!0});i&&(e=i,e.globalCompositeOperation="copy",n=e.createLinearGradient(0,0,1,1))}catch{}t.toColor=function(t){if(t.match(/#[\da-f]{3,8}/i))switch(t.length){case 4:return i=parseInt(t.slice(1,2).repeat(2),16),s=parseInt(t.slice(2,3).repeat(2),16),r=parseInt(t.slice(3,4).repeat(2),16),o.toColor(i,s,r);case 5:return i=parseInt(t.slice(1,2).repeat(2),16),s=parseInt(t.slice(2,3).repeat(2),16),r=parseInt(t.slice(3,4).repeat(2),16),a=parseInt(t.slice(4,5).repeat(2),16),o.toColor(i,s,r,a);case 7:return{css:t,rgba:(parseInt(t.slice(1),16)<<8|255)>>>0};case 9:return{css:t,rgba:parseInt(t.slice(1),16)>>>0}}const L=t.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(L)return i=parseInt(L[1],10),s=parseInt(L[2],10),r=parseInt(L[3],10),a=Math.round(255*(void 0===L[5]?1:parseFloat(L[5]))),o.toColor(i,s,r,a);if("transparent"===t)return{css:"transparent",rgba:0};if(!e||!n)throw new Error("css.toColor: Unsupported css format");if(e.fillStyle=n,e.fillStyle=t,"string"!=typeof e.fillStyle)throw new Error("css.toColor: Unsupported css format");if(e.fillRect(0,0,1,1),[i,s,r,a]=e.getImageData(0,0,1,1).data,255!==a)throw new Error("css.toColor: Unsupported css format");return{rgba:o.toRgba(i,s,r,a),css:t}}}(L||(e.css=L={})),function(t){function e(t,e,i){const s=t/255,r=e/255,a=i/255;return.2126*(s<=.03928?s/12.92:Math.pow((s+.055)/1.055,2.4))+.7152*(r<=.03928?r/12.92:Math.pow((r+.055)/1.055,2.4))+.0722*(a<=.03928?a/12.92:Math.pow((a+.055)/1.055,2.4))}t.relativeLuminance=function(t){return e(t>>16&255,t>>8&255,255&t)},t.relativeLuminance2=e}(h||(e.rgb=h={})),function(t){function e(t,e,i){const s=t>>24&255,r=t>>16&255,a=t>>8&255;let o=e>>24&255,n=e>>16&255,L=e>>8&255,T=d(h.relativeLuminance2(o,n,L),h.relativeLuminance2(s,r,a));for(;T0||n>0||L>0);)o-=Math.max(0,Math.ceil(.1*o)),n-=Math.max(0,Math.ceil(.1*n)),L-=Math.max(0,Math.ceil(.1*L)),T=d(h.relativeLuminance2(o,n,L),h.relativeLuminance2(s,r,a));return(o<<24|n<<16|L<<8|255)>>>0}function n(t,e,i){const s=t>>24&255,r=t>>16&255,a=t>>8&255;let o=e>>24&255,n=e>>16&255,L=e>>8&255,T=d(h.relativeLuminance2(o,n,L),h.relativeLuminance2(s,r,a));for(;T>>0}t.blend=function(t,e){if(a=(255&e)/255,1===a)return e;const n=e>>24&255,L=e>>16&255,h=e>>8&255,T=t>>24&255,l=t>>16&255,d=t>>8&255;return i=T+Math.round((n-T)*a),s=l+Math.round((L-l)*a),r=d+Math.round((h-d)*a),o.toRgba(i,s,r)},t.ensureContrastRatio=function(t,i,s){const r=h.relativeLuminance(t>>8),a=h.relativeLuminance(i>>8);if(d(r,a)>8));if(od(r,h.relativeLuminance(e>>8))?a:e}return a}const o=n(t,i,s),L=d(r,h.relativeLuminance(o>>8));if(Ld(r,h.relativeLuminance(a>>8))?o:a}return o}},t.reduceLuminance=e,t.increaseLuminance=n,t.toChannels=function(t){return[t>>24&255,t>>16&255,t>>8&255,255&t]}}(T||(e.rgba=T={}))},414(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.EventUtils=e.Emitter=void 0;const s=i(426);var r;e.Emitter=class{constructor(){this._listeners=[],this._disposed=!1}get event(){return this._event||(this._event=(t,e,i)=>{if(this._disposed)return(0,s.toDisposable)(()=>{});const r={fn:t,thisArgs:e};this._listeners.push(r);const a=(0,s.toDisposable)(()=>{const t=this._listeners.indexOf(r);-1!==t&&this._listeners.splice(t,1)});return i&&(Array.isArray(i)?i.push(a):i.add(a)),a}),this._event}fire(t){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{const{fn:e,thisArgs:i}=this._listeners[0];return void e.call(i,t)}default:{const e=this._listeners.slice();for(const{fn:i,thisArgs:s}of e)i.call(s,t)}}}dispose(){this._disposed||(this._disposed=!0,this._listeners.length=0)}},function(t){t.forward=function(t,e){return t(t=>e.fire(t))},t.map=function(t,e){return(i,s,r)=>t(t=>i.call(s,e(t)),void 0,r)},t.any=function(...t){return(e,i,r)=>{const a=new s.DisposableStore;for(const s of t)a.add(s(t=>e.call(i,t)));return r&&(Array.isArray(r)?r.push(a):r.add(a)),a}},t.runAndSubscribe=function(t,e,i){return e(i),t(t=>e(t))}}(r||(e.EventUtils=r={}))},426(t,e){function i(t){return{dispose:t}}function s(t){if(!t)return t;if(Array.isArray(t)){for(const e of t)e.dispose();return[]}return t.dispose(),t}Object.defineProperty(e,"__esModule",{value:!0}),e.MutableDisposable=e.Disposable=e.DisposableStore=void 0,e.toDisposable=i,e.dispose=s,e.combinedDisposable=function(...t){return i(()=>s(t))};class r{constructor(){this._disposables=new Set,this._isDisposed=!1}get isDisposed(){return this._isDisposed}add(t){return this._isDisposed?t.dispose():this._disposables.add(t),t}dispose(){if(!this._isDisposed){this._isDisposed=!0;for(const t of this._disposables)t.dispose();this._disposables.clear()}}clear(){for(const t of this._disposables)t.dispose();this._disposables.clear()}}e.DisposableStore=r;class a{constructor(){this._store=new r}dispose(){this._store.dispose()}_register(t){return this._store.add(t)}}e.Disposable=a,a.None=Object.freeze({dispose(){}}),e.MutableDisposable=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(t){this._isDisposed||t===this._value||(this._value?.dispose(),this._value=t)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,this._value?.dispose(),this._value=void 0}}},836(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.FourKeyMap=e.TwoKeyMap=void 0;class i{constructor(){this._data={}}set(t,e,i){this._data[t]||(this._data[t]={}),this._data[t][e]=i}get(t,e){return this._data[t]?this._data[t][e]:void 0}clear(){this._data={}}}e.TwoKeyMap=i,e.FourKeyMap=class{constructor(){this._data=new i}set(t,e,s,r,a){this._data.get(t,e)||this._data.set(t,e,new i),this._data.get(t,e).set(s,r,a)}get(t,e,i,s){return this._data.get(t,e)?.get(i,s)}clear(){this._data.clear()}}},95(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.isChromeOS=e.isLinux=e.isWindows=e.isMac=e.isSafari=e.isLegacyEdge=e.isChrome=e.isFirefox=e.isNode=void 0,e.getZoomFactor=function(t){return 1},e.getSafariVersion=function(){if(!e.isSafari)return 0;const t=i.match(/Version\/(\d+)/);return null===t||t.length<2?0:parseInt(t[1],10)},e.isNode=!("undefined"==typeof process||!("title"in process)||"undefined"!=typeof navigator&&!navigator.userAgent.startsWith("Node.js/"));const i=e.isNode?"node":navigator.userAgent,s=e.isNode?"node":navigator.platform;e.isFirefox=i.includes("Firefox"),e.isChrome=i.includes("Chrome"),e.isLegacyEdge=i.includes("Edge"),e.isSafari=/^((?!chrome|android).)*safari/i.test(i),e.isMac=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(s),e.isWindows=["Windows","Win16","Win32","WinCE"].includes(s),e.isLinux=s.indexOf("Linux")>=0,e.isChromeOS=/\bCrOS\b/.test(i)},930(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.DebouncedIdleTask=e.IdleTaskQueue=e.PriorityTaskQueue=void 0;class i{constructor(t){this._tasks=[],this._i=0,this._logService=t}enqueue(t){this._tasks.push(t),this._start()}flush(){for(;this._ii)return r-e<-20&&this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(r-e))}ms`),void this._start();r=i}this.clear()}}class s extends i{_requestCallback(t){return setTimeout(()=>t(this._createDeadline(16)))}_cancelCallback(t){clearTimeout(t)}_createDeadline(t){const e=performance.now()+t;return{timeRemaining:()=>Math.max(0,e-performance.now())}}}e.PriorityTaskQueue=s,e.IdleTaskQueue="requestIdleCallback"in globalThis?class extends i{_requestCallback(t){return requestIdleCallback(t)}_cancelCallback(t){cancelIdleCallback(t)}}:s,e.DebouncedIdleTask=class{constructor(t){this._queue=new e.IdleTaskQueue(t)}set(t){this._queue.clear(),this._queue.enqueue(t)}flush(){this._queue.flush()}dispose(){this._queue.clear()}}},917(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.ExtendedAttrs=e.AttributeData=void 0;class i{constructor(){this.fg=0,this.bg=0,this.extended=new s}static toColorRGB(t){return[t>>>16&255,t>>>8&255,255&t]}static fromColorRGB(t){return(255&t[0])<<16|(255&t[1])<<8|255&t[2]}clone(){const t=new i;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t}isInverse(){return 67108864&this.fg}isBold(){return 134217728&this.fg}isUnderline(){return this.hasExtendedAttrs()&&0!==this.extended.underlineStyle?1:268435456&this.fg}isBlink(){return 536870912&this.fg}isInvisible(){return 1073741824&this.fg}isItalic(){return 67108864&this.bg}isDim(){return 134217728&this.bg}isStrikethrough(){return 2147483648&this.fg}isProtected(){return 536870912&this.bg}isOverline(){return 1073741824&this.bg}getFgColorMode(){return 50331648&this.fg}getBgColorMode(){return 50331648&this.bg}isFgRGB(){return!(50331648&~this.fg)}isBgRGB(){return!(50331648&~this.bg)}isFgPalette(){return 16777216==(50331648&this.fg)||33554432==(50331648&this.fg)}isBgPalette(){return 16777216==(50331648&this.bg)||33554432==(50331648&this.bg)}isFgDefault(){return!(50331648&this.fg)}isBgDefault(){return!(50331648&this.bg)}isAttributeDefault(){return 0===this.fg&&0===this.bg}getFgColor(){switch(50331648&this.fg){case 16777216:case 33554432:return 255&this.fg;case 50331648:return 16777215&this.fg;default:return-1}}getBgColor(){switch(50331648&this.bg){case 16777216:case 33554432:return 255&this.bg;case 50331648:return 16777215&this.bg;default:return-1}}hasExtendedAttrs(){return 268435456&this.bg}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(268435456&this.bg&&~this.extended.underlineColor)switch(50331648&this.extended.underlineColor){case 16777216:case 33554432:return 255&this.extended.underlineColor;case 50331648:return 16777215&this.extended.underlineColor;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return 268435456&this.bg&&~this.extended.underlineColor?50331648&this.extended.underlineColor:this.getFgColorMode()}isUnderlineColorRGB(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&~this.extended.underlineColor):this.isFgRGB()}isUnderlineColorPalette(){return 268435456&this.bg&&~this.extended.underlineColor?16777216==(50331648&this.extended.underlineColor)||33554432==(50331648&this.extended.underlineColor):this.isFgPalette()}isUnderlineColorDefault(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&this.extended.underlineColor):this.isFgDefault()}getUnderlineStyle(){return 268435456&this.fg?268435456&this.bg?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}}e.AttributeData=i;class s{get ext(){return this._urlId?-469762049&this._ext|this.underlineStyle<<26:this._ext}set ext(t){this._ext=t}get underlineStyle(){return this._urlId?5:(469762048&this._ext)>>26}set underlineStyle(t){this._ext&=-469762049,this._ext|=t<<26&469762048}get underlineColor(){return 67108863&this._ext}set underlineColor(t){this._ext&=-67108864,this._ext|=67108863&t}get urlId(){return this._urlId}set urlId(t){this._urlId=t}get underlineVariantOffset(){const t=(3758096384&this._ext)>>29;return t<0?4294967288^t:t}set underlineVariantOffset(t){this._ext&=536870911,this._ext|=t<<29&3758096384}constructor(t=0,e=0){this._ext=0,this._urlId=0,this._ext=t,this._urlId=e}clone(){return new s(this._ext,this._urlId)}isEmpty(){return 0===this.underlineStyle&&0===this._urlId}}e.ExtendedAttrs=s},721(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.CellData=void 0;const s=i(348),r=i(564),a=i(917);class o extends a.AttributeData{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new a.ExtendedAttrs,this.combinedData=""}static fromCharData(t){const e=new o;return e.setFromCharData(t),e}isCombined(){return 2097152&this.content}getWidth(){return this.content>>22}getChars(){return 2097152&this.content?this.combinedData:2097151&this.content?(0,s.stringFromCodePoint)(2097151&this.content):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):2097151&this.content}setFromCharData(t){this.fg=t[r.CHAR_DATA_ATTR_INDEX],this.bg=0;let e=!1;if(t[r.CHAR_DATA_CHAR_INDEX].length>2)e=!0;else if(2===t[r.CHAR_DATA_CHAR_INDEX].length){const i=t[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0);if(55296<=i&&i<=56319){const s=t[r.CHAR_DATA_CHAR_INDEX].charCodeAt(1);56320<=s&&s<=57343?this.content=1024*(i-55296)+s-56320+65536|t[r.CHAR_DATA_WIDTH_INDEX]<<22:e=!0}else e=!0}else this.content=t[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|t[r.CHAR_DATA_WIDTH_INDEX]<<22;e&&(this.combinedData=t[r.CHAR_DATA_CHAR_INDEX],this.content=2097152|t[r.CHAR_DATA_WIDTH_INDEX]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}attributesEquals(t){if(this.getFgColorMode()!==t.getFgColorMode()||this.getFgColor()!==t.getFgColor())return!1;if(this.getBgColorMode()!==t.getBgColorMode()||this.getBgColor()!==t.getBgColor())return!1;if(this.isInverse()!==t.isInverse())return!1;if(this.isBold()!==t.isBold())return!1;if(this.isUnderline()!==t.isUnderline())return!1;if(this.isUnderline()){if(this.getUnderlineStyle()!==t.getUnderlineStyle())return!1;const e=this.isUnderlineColorDefault(),i=t.isUnderlineColorDefault();if(!e||!i){if(e!==i)return!1;if(this.getUnderlineColor()!==t.getUnderlineColor())return!1;if(this.getUnderlineColorMode()!==t.getUnderlineColorMode())return!1}}return this.isOverline()===t.isOverline()&&this.isBlink()===t.isBlink()&&this.isInvisible()===t.isInvisible()&&this.isItalic()===t.isItalic()&&this.isDim()===t.isDim()&&this.isStrikethrough()===t.isStrikethrough()}}e.CellData=o},564(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.WHITESPACE_CELL_CODE=e.WHITESPACE_CELL_WIDTH=e.WHITESPACE_CELL_CHAR=e.NULL_CELL_CODE=e.NULL_CELL_WIDTH=e.NULL_CELL_CHAR=e.CHAR_DATA_CODE_INDEX=e.CHAR_DATA_WIDTH_INDEX=e.CHAR_DATA_CHAR_INDEX=e.CHAR_DATA_ATTR_INDEX=e.DEFAULT_EXT=e.DEFAULT_ATTR=e.DEFAULT_COLOR=void 0,e.DEFAULT_COLOR=0,e.DEFAULT_ATTR=e.DEFAULT_COLOR<<9|256,e.DEFAULT_EXT=0,e.CHAR_DATA_ATTR_INDEX=0,e.CHAR_DATA_CHAR_INDEX=1,e.CHAR_DATA_WIDTH_INDEX=2,e.CHAR_DATA_CODE_INDEX=3,e.NULL_CELL_CHAR="",e.NULL_CELL_WIDTH=1,e.NULL_CELL_CODE=0,e.WHITESPACE_CELL_CHAR=" ",e.WHITESPACE_CELL_WIDTH=1,e.WHITESPACE_CELL_CODE=32},348(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.Utf8ToUtf32=e.StringToUtf32=void 0,e.stringFromCodePoint=function(t){return t>65535?(t-=65536,String.fromCharCode(55296+(t>>10))+String.fromCharCode(t%1024+56320)):String.fromCharCode(t)},e.utf32ToString=function(t,e=0,i=t.length){let s="";for(let r=e;r65535?(e-=65536,s+=String.fromCharCode(55296+(e>>10))+String.fromCharCode(e%1024+56320)):s+=String.fromCharCode(e)}return s},e.StringToUtf32=class{constructor(){this._interim=0}clear(){this._interim=0}decode(t,e){const i=t.length;if(!i)return 0;let s=0,r=0;if(this._interim){const i=t.charCodeAt(r++);56320<=i&&i<=57343?e[s++]=1024*(this._interim-55296)+i-56320+65536:(e[s++]=this._interim,e[s++]=i),this._interim=0}for(let a=r;a=i)return this._interim=r,s;const o=t.charCodeAt(a);56320<=o&&o<=57343?e[s++]=1024*(r-55296)+o-56320+65536:(e[s++]=r,e[s++]=o);continue}65279!==r&&(e[s++]=r)}return s}},e.Utf8ToUtf32=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(t,e){const i=t.length;if(!i)return 0;let s,r,a,o,n,L=0,h=0;if(this.interim[0]){let s=!1,r=this.interim[0];r&=192==(224&r)?31:224==(240&r)?15:7;let a,o=0;for(;(a=this.interim[++o])&&o<4;)r<<=6,r|=63&a;const n=192==(224&this.interim[0])?2:224==(240&this.interim[0])?3:4,T=n-o;for(;h=i)return 0;if(a=t[h++],128!=(192&a)){h--,s=!0;break}this.interim[o++]=a,r<<=6,r|=63&a}s||(2===n?r<128?h--:e[L++]=r:3===n?r<2048||r>=55296&&r<=57343||65279===r||(e[L++]=r):r<65536||r>1114111||(e[L++]=r)),this.interim.fill(0)}const T=i-4;let l=h;for(;l=i)return this.interim[0]=s,L;if(r=t[l++],128!=(192&r)){l--;continue}if(n=(31&s)<<6|63&r,n<128){l--;continue}e[L++]=n}else if(224==(240&s)){if(l>=i)return this.interim[0]=s,L;if(r=t[l++],128!=(192&r)){l--;continue}if(l>=i)return this.interim[0]=s,this.interim[1]=r,L;if(a=t[l++],128!=(192&a)){l--;continue}if(n=(15&s)<<12|(63&r)<<6|63&a,n<2048||n>=55296&&n<=57343||65279===n)continue;e[L++]=n}else if(240==(248&s)){if(l>=i)return this.interim[0]=s,L;if(r=t[l++],128!=(192&r)){l--;continue}if(l>=i)return this.interim[0]=s,this.interim[1]=r,L;if(a=t[l++],128!=(192&a)){l--;continue}if(l>=i)return this.interim[0]=s,this.interim[1]=r,this.interim[2]=a,L;if(o=t[l++],128!=(192&o)){l--;continue}if(n=(7&s)<<18|(63&r)<<12|(63&a)<<6|63&o,n<65536||n>1114111)continue;e[L++]=n}}return L}}},864(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.CellColorResolver=void 0;const s=i(564),r=i(993),a=i(959),o=i(915);let n,L=0,h=0,T=!1,l=!1,d=!1,c=0;e.CellColorResolver=class{constructor(t,e,i,s,r,a){this._terminal=t,this._optionService=e,this._selectionRenderModel=i,this._decorationService=s,this._coreBrowserService=r,this._themeService=a,this.result={fg:0,bg:0,ext:0}}resolve(t,e,i,_,Q){this.result.bg=t.bg,this.result.fg=t.fg,this.result.ext=268435456&t.bg?t.extended.ext:0,h=0,L=0,l=!1,T=!1,d=!1,n=this._themeService.colors,c=0;const u=t.getCode();if(u!==s.NULL_CELL_CODE&&4===t.extended.underlineStyle){const t=Math.max(1,Math.floor(this._optionService.rawOptions.fontSize*this._coreBrowserService.dpr/15));c=e*_%(2*Math.round(t))}if(0===c&&o.blockPatternCodepoints.has(u)&&(c=e*_%2*2+i*Q%2),this._decorationService.forEachDecorationAtCell(e,i,"bottom",t=>{t.backgroundColorRGB&&(h=t.backgroundColorRGB.rgba>>8&16777215,l=!0),t.foregroundColorRGB&&(L=t.foregroundColorRGB.rgba>>8&16777215,T=!0)}),d=this._selectionRenderModel.isCellSelected(this._terminal,e,i),d){if(67108864&this.result.fg||50331648&this.result.bg){if(67108864&this.result.fg)switch(50331648&this.result.fg){case 16777216:case 33554432:h=this._themeService.colors.ansi[255&this.result.fg].rgba;break;case 50331648:h=(16777215&this.result.fg)<<8|255;break;default:h=this._themeService.colors.foreground.rgba}else switch(50331648&this.result.bg){case 16777216:case 33554432:h=this._themeService.colors.ansi[255&this.result.bg].rgba;break;case 50331648:h=(16777215&this.result.bg)<<8|255}h=r.rgba.blend(h,4294967040&(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba|128)>>8&16777215}else h=(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba>>8&16777215;if(l=!0,n.selectionForeground&&(L=n.selectionForeground.rgba>>8&16777215,T=!0),(0,a.treatGlyphAsBackgroundColor)(t.getCode())){if(67108864&this.result.fg&&!(50331648&this.result.bg))L=(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba>>8&16777215;else{if(67108864&this.result.fg)switch(50331648&this.result.bg){case 16777216:case 33554432:L=this._themeService.colors.ansi[255&this.result.bg].rgba;break;case 50331648:L=(16777215&this.result.bg)<<8|255}else switch(50331648&this.result.fg){case 16777216:case 33554432:L=this._themeService.colors.ansi[255&this.result.fg].rgba;break;case 50331648:L=(16777215&this.result.fg)<<8|255;break;default:L=this._themeService.colors.foreground.rgba}L=r.rgba.blend(L,4294967040&(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba|128)>>8&16777215}T=!0}}this._decorationService.forEachDecorationAtCell(e,i,"top",t=>{t.backgroundColorRGB&&(h=t.backgroundColorRGB.rgba>>8&16777215,l=!0),t.foregroundColorRGB&&(L=t.foregroundColorRGB.rgba>>8&16777215,T=!0)}),l&&(h=d?-16777216&t.bg&-134217729|h|50331648:-16777216&t.bg|h|50331648),T&&(L=-16777216&t.fg&-67108865|L|50331648),67108864&this.result.fg&&(l&&!T&&(L=50331648&this.result.bg?-134217728&this.result.fg|67108863&this.result.bg:-134217728&this.result.fg|16777215&n.background.rgba>>8|50331648,T=!0),!l&&T&&(h=50331648&this.result.fg?-67108864&this.result.bg|67108863&this.result.fg:-67108864&this.result.bg|16777215&n.foreground.rgba>>8|50331648,l=!0)),n=void 0,this.result.bg=l?h:this.result.bg,this.result.fg=T?L:this.result.fg,this.result.ext&=536870911,this.result.ext|=c<<29&3758096384}}},670(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.acquireTextureAtlas=function(t,e,i,o,n,L,h,T,l,d=!0){const c=(0,r.generateConfig)(o,n,L,h,e,i,T,l,d);for(let e=0;e=0){if((0,r.configEquals)(i.config,c))return i.atlas;1===i.ownedBy.length?(i.atlas.dispose(),a.splice(e,1)):i.ownedBy.splice(s,1);break}}for(let e=0;e{this._renderCallback(),this._animationFrame=void 0})))}_restartInterval(t=600){this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout=this._coreBrowserService.window.setTimeout(()=>{if(this._animationTimeRestarted){const t=600-(Date.now()-this._animationTimeRestarted);if(this._animationTimeRestarted=void 0,t>0)return void this._restartInterval(t)}this.isCursorVisible=!1,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._renderCallback(),this._animationFrame=void 0}),this._blinkInterval=this._coreBrowserService.window.setInterval(()=>{if(this._animationTimeRestarted){const t=600-(Date.now()-this._animationTimeRestarted);return this._animationTimeRestarted=void 0,void this._restartInterval(t)}this.isCursorVisible=!this.isCursorVisible,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._renderCallback(),this._animationFrame=void 0})},600)},t)}pause(){this.isCursorVisible=!0,this._isIdlePaused=!1,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0),this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}resume(){this.pause(),this._animationTimeRestarted=void 0,this._restartInterval(),this._resetIdleTimer(),this.restartBlinkAnimation()}_resetIdleTimer(){this._isIdlePaused=!1,this._idleTimeout&&this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_stopBlinkingDueToIdle(){this.isCursorVisible=!0,this._isIdlePaused=!0,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0),this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0,this._renderCallback()}}},697(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.observeDevicePixelDimensions=function(t,e,i){let r=new e.ResizeObserver(e=>{const s=e.find(e=>e.target===t);if(!s)return;if(!("devicePixelContentBoxSize"in s))return r?.disconnect(),void(r=void 0);const a=s.devicePixelContentBoxSize[0].inlineSize,o=s.devicePixelContentBoxSize[0].blockSize;a>0&&o>0&&i(a,o)});try{r.observe(t,{box:["device-pixel-content-box"]})}catch{r.disconnect(),r=void 0}return(0,s.toDisposable)(()=>r?.disconnect())};const s=i(426)},28(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.GlyphRenderer=void 0;const s=i(115),r=i(564),a=i(426),o=i(719),n=i(959);let L,h=0,T=0,l=0;class d extends a.Disposable{constructor(t,e,i,r,L){super(),this._terminal=t,this._gl=e,this._dimensions=i,this._optionsService=r,this._logService=L,this._activeBuffer=0,this._vertices={count:0,attributes:new Float32Array(0),attributesBuffers:[new Float32Array(0),new Float32Array(0)]};const h=this._gl;void 0===s.TextureAtlas.maxAtlasPages&&(s.TextureAtlas.maxAtlasPages=Math.min(32,(0,n.throwIfFalsy)(h.getParameter(h.MAX_TEXTURE_IMAGE_UNITS))),s.TextureAtlas.maxTextureSize=(0,n.throwIfFalsy)(h.getParameter(h.MAX_TEXTURE_SIZE))),this._program=(0,n.throwIfFalsy)((0,o.createProgram)(h,"#version 300 es\nlayout (location = 0) in vec2 a_unitquad;\nlayout (location = 1) in vec2 a_cellpos;\nlayout (location = 2) in vec2 a_offset;\nlayout (location = 3) in vec2 a_size;\nlayout (location = 4) in float a_texpage;\nlayout (location = 5) in vec2 a_texcoord;\nlayout (location = 6) in vec2 a_texsize;\n\nuniform mat4 u_projection;\nuniform vec2 u_resolution;\n\nout vec2 v_texcoord;\nflat out int v_texpage;\n\nvoid main() {\n vec2 zeroToOne = (a_offset / u_resolution) + a_cellpos + (a_unitquad * a_size);\n gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0);\n v_texpage = int(a_texpage);\n v_texcoord = a_texcoord + a_unitquad * a_texsize;\n}",function(t){let e="";for(let i=1;ih.deleteProgram(this._program))),this._projectionLocation=(0,n.throwIfFalsy)(h.getUniformLocation(this._program,"u_projection")),this._resolutionLocation=(0,n.throwIfFalsy)(h.getUniformLocation(this._program,"u_resolution")),this._textureLocation=(0,n.throwIfFalsy)(h.getUniformLocation(this._program,"u_texture"));const T=this._vertexArrayObject=h.createVertexArray();this._register((0,a.toDisposable)(()=>h.deleteVertexArray(T))),h.bindVertexArray(T);const l=new Float32Array([0,0,1,0,0,1,1,1]),d=h.createBuffer();this._register((0,a.toDisposable)(()=>h.deleteBuffer(d))),h.bindBuffer(h.ARRAY_BUFFER,d),h.bufferData(h.ARRAY_BUFFER,l,h.STATIC_DRAW),h.enableVertexAttribArray(0),h.vertexAttribPointer(0,2,this._gl.FLOAT,!1,0,0);const c=new Uint8Array([0,1,2,3]),_=h.createBuffer();this._register((0,a.toDisposable)(()=>h.deleteBuffer(_))),h.bindBuffer(h.ELEMENT_ARRAY_BUFFER,_),h.bufferData(h.ELEMENT_ARRAY_BUFFER,c,h.STATIC_DRAW),this._attributesBuffer=(0,n.throwIfFalsy)(h.createBuffer()),this._register((0,a.toDisposable)(()=>h.deleteBuffer(this._attributesBuffer))),h.bindBuffer(h.ARRAY_BUFFER,this._attributesBuffer),h.enableVertexAttribArray(2),h.vertexAttribPointer(2,2,h.FLOAT,!1,44,0),h.vertexAttribDivisor(2,1),h.enableVertexAttribArray(3),h.vertexAttribPointer(3,2,h.FLOAT,!1,44,2*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(3,1),h.enableVertexAttribArray(4),h.vertexAttribPointer(4,1,h.FLOAT,!1,44,4*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(4,1),h.enableVertexAttribArray(5),h.vertexAttribPointer(5,2,h.FLOAT,!1,44,5*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(5,1),h.enableVertexAttribArray(6),h.vertexAttribPointer(6,2,h.FLOAT,!1,44,7*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(6,1),h.enableVertexAttribArray(1),h.vertexAttribPointer(1,2,h.FLOAT,!1,44,9*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(1,1),h.useProgram(this._program);const Q=new Int32Array(s.TextureAtlas.maxAtlasPages);for(let t=0;th.deleteTexture(e.texture))),h.activeTexture(h.TEXTURE0+t),h.bindTexture(h.TEXTURE_2D,e.texture),h.texParameteri(h.TEXTURE_2D,h.TEXTURE_WRAP_S,h.CLAMP_TO_EDGE),h.texParameteri(h.TEXTURE_2D,h.TEXTURE_WRAP_T,h.CLAMP_TO_EDGE),h.texImage2D(h.TEXTURE_2D,0,h.RGBA,1,1,0,h.RGBA,h.UNSIGNED_BYTE,new Uint8Array([255,0,0,255])),this._atlasTextures[t]=e}h.enable(h.BLEND),h.blendFunc(h.SRC_ALPHA,h.ONE_MINUS_SRC_ALPHA),this.handleResize()}beginFrame(){return!this._atlas||this._atlas.beginFrame()}updateCell(t,e,i,s,r,a,o,n,L){this._updateCell(this._vertices.attributes,t,e,i,s,r,a,o,n,L)}_updateCell(t,e,i,s,a,o,d,c,_,Q){h=11*(i*this._terminal.cols+e),s!==r.NULL_CELL_CODE&&void 0!==s?this._atlas&&(L=c&&c.length>1?this._atlas.getRasterizedGlyphCombinedChar(c,a,o,d,!1,this._terminal.element):this._atlas.getRasterizedGlyph(s,a,o,d,!1,this._terminal.element),T=Math.floor((this._dimensions.device.cell.width-this._dimensions.device.char.width)/2),a!==Q&&L.offset.x>T?(l=L.offset.x-T,t[h]=-(L.offset.x-l)+this._dimensions.device.char.left,t[h+1]=-L.offset.y+this._dimensions.device.char.top,t[h+2]=(L.size.x-l)/this._dimensions.device.canvas.width,t[h+3]=L.size.y/this._dimensions.device.canvas.height,t[h+4]=L.texturePage,t[h+5]=L.texturePositionClipSpace.x+l/this._atlas.pages[L.texturePage].canvas.width,t[h+6]=L.texturePositionClipSpace.y,t[h+7]=L.sizeClipSpace.x-l/this._atlas.pages[L.texturePage].canvas.width,t[h+8]=L.sizeClipSpace.y):(t[h]=-L.offset.x+this._dimensions.device.char.left,t[h+1]=-L.offset.y+this._dimensions.device.char.top,t[h+2]=L.size.x/this._dimensions.device.canvas.width,t[h+3]=L.size.y/this._dimensions.device.canvas.height,t[h+4]=L.texturePage,t[h+5]=L.texturePositionClipSpace.x,t[h+6]=L.texturePositionClipSpace.y,t[h+7]=L.sizeClipSpace.x,t[h+8]=L.sizeClipSpace.y),this._optionsService.rawOptions.rescaleOverlappingGlyphs&&(0,n.allowRescaling)(s,_,L.size.x,this._dimensions.device.cell.width)&&(t[h+2]=(this._dimensions.device.cell.width-1)/this._dimensions.device.canvas.width)):t.fill(0,h,h+11-1-2)}clear(){const t=this._terminal,e=t.cols*t.rows*11;this._vertices.count!==e?this._vertices.attributes=new Float32Array(e):this._vertices.attributes.fill(0);let i=0;for(;iT.deleteProgram(this._program))),this._projectionLocation=(0,a.throwIfFalsy)(T.getUniformLocation(this._program,"u_projection"));const l=this._vertexArrayObject=T.createVertexArray();this._register((0,s.toDisposable)(()=>T.deleteVertexArray(l))),T.bindVertexArray(l);const d=new Float32Array([0,0,1,0,0,1,1,1]),c=T.createBuffer();this._register((0,s.toDisposable)(()=>T.deleteBuffer(c))),T.bindBuffer(T.ARRAY_BUFFER,c),T.bufferData(T.ARRAY_BUFFER,d,T.STATIC_DRAW),T.enableVertexAttribArray(3),T.vertexAttribPointer(3,2,this._gl.FLOAT,!1,0,0);const _=new Uint8Array([0,1,2,3]),Q=T.createBuffer();this._register((0,s.toDisposable)(()=>T.deleteBuffer(Q))),T.bindBuffer(T.ELEMENT_ARRAY_BUFFER,Q),T.bufferData(T.ELEMENT_ARRAY_BUFFER,_,T.STATIC_DRAW),this._attributesBuffer=(0,a.throwIfFalsy)(T.createBuffer()),this._register((0,s.toDisposable)(()=>T.deleteBuffer(this._attributesBuffer))),T.bindBuffer(T.ARRAY_BUFFER,this._attributesBuffer),T.enableVertexAttribArray(0),T.vertexAttribPointer(0,2,T.FLOAT,!1,o,0),T.vertexAttribDivisor(0,1),T.enableVertexAttribArray(1),T.vertexAttribPointer(1,2,T.FLOAT,!1,o,2*Float32Array.BYTES_PER_ELEMENT),T.vertexAttribDivisor(1,1),T.enableVertexAttribArray(2),T.vertexAttribPointer(2,4,T.FLOAT,!1,o,4*Float32Array.BYTES_PER_ELEMENT),T.vertexAttribDivisor(2,1),this._updateCachedColors(L.colors),this._register(this._themeService.onChangeColors(t=>{this._updateCachedColors(t),this._updateViewportRectangle()}))}renderBackgrounds(){this._renderVertices(this._vertices)}renderCursor(){this._renderVertices(this._verticesCursor)}_renderVertices(t){const e=this._gl;e.useProgram(this._program),e.bindVertexArray(this._vertexArrayObject),e.uniformMatrix4fv(this._projectionLocation,!1,r.PROJECTION_MATRIX),e.bindBuffer(e.ARRAY_BUFFER,this._attributesBuffer),e.bufferData(e.ARRAY_BUFFER,t.attributes,e.DYNAMIC_DRAW),e.drawElementsInstanced(this._gl.TRIANGLE_STRIP,4,e.UNSIGNED_BYTE,0,t.count)}handleResize(){this._updateViewportRectangle()}setDimensions(t){this._dimensions=t}_updateCachedColors(t){this._bgFloat=this._colorToFloat32Array(t.background),this._cursorFloat=this._colorToFloat32Array(t.cursor)}_updateViewportRectangle(){this._addRectangleFloat(this._vertices.attributes,0,0,0,this._terminal.cols*this._dimensions.device.cell.width,this._terminal.rows*this._dimensions.device.cell.height,this._bgFloat)}updateBackgrounds(t){const e=this._terminal,i=this._vertices;let s,r,a,o,n,L,h,T,l,d,c,_=1;for(s=0;s>24&255)/255,d=(L>>16&255)/255,c=(L>>8&255)/255,_=1,this._addRectangle(t.attributes,e,h,T,(o-a)*this._dimensions.device.cell.width,this._dimensions.device.cell.height,l,d,c,_)}_addRectangle(t,e,i,s,r,a,o,n,L,h){t[e]=i/this._dimensions.device.canvas.width,t[e+1]=s/this._dimensions.device.canvas.height,t[e+2]=r/this._dimensions.device.canvas.width,t[e+3]=a/this._dimensions.device.canvas.height,t[e+4]=o,t[e+5]=n,t[e+6]=L,t[e+7]=h}_addRectangleFloat(t,e,i,s,r,a,o){t[e]=i/this._dimensions.device.canvas.width,t[e+1]=s/this._dimensions.device.canvas.height,t[e+2]=r/this._dimensions.device.canvas.width,t[e+3]=a/this._dimensions.device.canvas.height,t[e+4]=o[0],t[e+5]=o[1],t[e+6]=o[2],t[e+7]=o[3]}_colorToFloat32Array(t){return new Float32Array([(t.rgba>>24&255)/255,(t.rgba>>16&255)/255,(t.rgba>>8&255)/255,(255&t.rgba)/255])}}e.RectangleRenderer=Q},208(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.RenderModel=e.COMBINED_CHAR_BIT_MASK=void 0;const s=i(948);e.COMBINED_CHAR_BIT_MASK=2147483648,e.RenderModel=class{constructor(){this.cells=new Uint32Array(0),this.lineLengths=new Uint32Array(0),this.selection=(0,s.createSelectionRenderModel)()}resize(t,e){const i=t*e*4;i!==this.cells.length&&(this.cells=new Uint32Array(i),this.lineLengths=new Uint32Array(e))}clear(){this.cells.fill(0,0),this.lineLengths.fill(0,0)}}},115(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.TextureAtlas=void 0;const s=i(0),r=i(183),a=i(959),o=i(993),n=i(836),L=i(930),h=i(917),T=i(564),l=i(414),d={texturePage:0,texturePosition:{x:0,y:0},texturePositionClipSpace:{x:0,y:0},offset:{x:0,y:0},size:{x:0,y:0},sizeClipSpace:{x:0,y:0}};let c;class _{get pages(){return this._pages}constructor(t,e,i,s){this._document=t,this._config=e,this._unicodeService=i,this._logService=s,this._didWarmUp=!1,this._cacheMap=new n.FourKeyMap,this._cacheMapCombined=new n.FourKeyMap,this._pages=[],this._activePages=[],this._workBoundingBox={top:0,left:0,bottom:0,right:0},this._workAttributeData=new h.AttributeData,this._textureSize=512,this._onAddTextureAtlasCanvas=new l.Emitter,this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event,this._onRemoveTextureAtlasCanvas=new l.Emitter,this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event,this._requestClearModel=!1,this._createNewPage(),this._tmpCanvas=p(t,4*this._config.deviceCellWidth+4,this._config.deviceCellHeight+4),this._tmpCtx=(0,a.throwIfFalsy)(this._tmpCanvas.getContext("2d",{alpha:this._config.allowTransparency,willReadFrequently:!0}))}dispose(){this._tmpCanvas.remove();for(const t of this.pages)t.canvas.remove();this._onAddTextureAtlasCanvas.dispose(),this._onRemoveTextureAtlasCanvas.dispose()}warmUp(){this._didWarmUp||(this._doWarmUp(),this._didWarmUp=!0)}_doWarmUp(){const t=new L.IdleTaskQueue(this._logService);for(let e=33;e<126;e++)t.enqueue(()=>{if(!this._cacheMap.get(e,T.DEFAULT_COLOR,T.DEFAULT_COLOR,T.DEFAULT_EXT)){const t=this._drawToCache(e,T.DEFAULT_COLOR,T.DEFAULT_COLOR,T.DEFAULT_EXT,!1,void 0);this._cacheMap.set(e,T.DEFAULT_COLOR,T.DEFAULT_COLOR,T.DEFAULT_EXT,t)}})}beginFrame(){const t=this._requestClearModel;return this._requestClearModel=!1,t}clearTexture(){if(0!==this._pages[0].currentRow.x||0!==this._pages[0].currentRow.y){for(const t of this._pages)t.clear();this._cacheMap.clear(),this._cacheMapCombined.clear(),this._didWarmUp=!1}}_createNewPage(){if(_.maxAtlasPages&&this._pages.length>=Math.max(4,_.maxAtlasPages)){const t=this._pages.filter(t=>2*t.canvas.width<=(_.maxTextureSize||4096)).sort((t,e)=>e.canvas.width!==t.canvas.width?e.canvas.width-t.canvas.width:e.percentageUsed-t.percentageUsed);let e=-1,i=0;for(let s=0;st.canvas.width!==s[0].canvas.width)){const t=new Q(this._document,this._textureSize);return this._pages.push(t),this._activePages.push(t),this._onAddTextureAtlasCanvas.fire(t.canvas),t}const r=s.map(t=>t.glyphs[0].texturePage).sort((t,e)=>t-e),a=this.pages.length-s.length,o=this._mergePages(s,a);o.version=++Q.nextVersion;for(let t=r.length-1;t>=0;t--)this._deletePage(r[t]);this.pages.push(o),this._requestClearModel=!0,this._onAddTextureAtlasCanvas.fire(o.canvas)}const t=new Q(this._document,this._textureSize);return this._pages.push(t),this._activePages.push(t),this._onAddTextureAtlasCanvas.fire(t.canvas),t}_mergePages(t,e){const i=2*t[0].canvas.width,s=new Q(this._document,i,t);for(const[r,a]of t.entries()){const t=r*a.canvas.width%i,o=Math.floor(r/2)*a.canvas.height;s.ctx.drawImage(a.canvas,t,o);for(const s of a.glyphs)s.texturePage=e,s.sizeClipSpace.x=s.size.x/i,s.sizeClipSpace.y=s.size.y/i,s.texturePosition.x+=t,s.texturePosition.y+=o,s.texturePositionClipSpace.x=s.texturePosition.x/i,s.texturePositionClipSpace.y=s.texturePosition.y/i;this._onRemoveTextureAtlasCanvas.fire(a.canvas);const n=this._activePages.indexOf(a);-1!==n&&this._activePages.splice(n,1)}return s}_deletePage(t){this._pages.splice(t,1);for(let e=t;e=this._config.colors.ansi.length)throw new Error("No color found for idx "+t);return this._config.colors.ansi[t]}_getBackgroundColor(t,e,i,s){if(this._config.allowTransparency)return o.NULL_COLOR;let r;switch(t){case 16777216:case 33554432:r=this._getColorFromAnsiIndex(e);break;case 50331648:const t=h.AttributeData.toColorRGB(e);r=o.channels.toColor(t[0],t[1],t[2]);break;default:r=i?o.color.opaque(this._config.colors.foreground):this._config.colors.background}return this._config.allowTransparency||(r=o.color.opaque(r)),r}_getForegroundColor(t,e,i,r,a,n,L,T,l,d){const c=this._getMinimumContrastColor(t,e,i,r,a,n,L,l,T,d);if(c)return c;let _;switch(a){case 16777216:case 33554432:this._config.drawBoldTextInBrightColors&&l&&n<8&&(n+=8),_=this._getColorFromAnsiIndex(n);break;case 50331648:const t=h.AttributeData.toColorRGB(n);_=o.channels.toColor(t[0],t[1],t[2]);break;default:_=L?this._config.colors.background:this._config.colors.foreground}return this._config.allowTransparency&&(_=o.color.opaque(_)),T&&(_=o.color.multiplyOpacity(_,s.DIM_OPACITY)),_}_resolveBackgroundRgba(t,e,i){switch(t){case 16777216:case 33554432:return this._getColorFromAnsiIndex(e).rgba;case 50331648:return e<<8;default:return i?this._config.colors.foreground.rgba:this._config.colors.background.rgba}}_resolveForegroundRgba(t,e,i,s){switch(t){case 16777216:case 33554432:return this._config.drawBoldTextInBrightColors&&s&&e<8&&(e+=8),this._getColorFromAnsiIndex(e).rgba;case 50331648:return e<<8;default:return i?this._config.colors.background.rgba:this._config.colors.foreground.rgba}}_getMinimumContrastColor(t,e,i,s,r,a,n,L,h,T){if(1===this._config.minimumContrastRatio||T)return;const l=this._getContrastCache(h),d=l.getColor(t,s);if(void 0!==d)return d??void 0;const c=this._resolveBackgroundRgba(e,i,n),_=this._resolveForegroundRgba(r,a,n,L),Q=o.rgba.ensureContrastRatio(c,_,this._config.minimumContrastRatio/(h?2:1));if(!Q)return void l.setColor(t,s,null);const u=o.channels.toColor(Q>>24&255,Q>>16&255,Q>>8&255);return l.setColor(t,s,u),u}_getContrastCache(t){return t?this._config.colors.halfContrastCache:this._config.colors.contrastCache}_drawToCache(t,e,i,o,n,L){const T="number"==typeof t?String.fromCharCode(t):t;L&&this._tmpCanvas.parentElement!==L&&(this._tmpCanvas.style.display="none",L.append(this._tmpCanvas));const l=Math.min(this._config.deviceCellWidth*Math.max(T.length,2)+4,this._config.deviceMaxTextureSize);this._tmpCanvas.width=t?2*t-o:t-o;!1==!(o>=t)||0===_?(this._tmpCtx.setLineDash([Math.round(t),Math.round(t)]),this._tmpCtx.moveTo(h+_,s),this._tmpCtx.lineTo(T,s)):(this._tmpCtx.setLineDash([Math.round(t),Math.round(t)]),this._tmpCtx.moveTo(h,s),this._tmpCtx.lineTo(h+_,s),this._tmpCtx.moveTo(h+_+t,s),this._tmpCtx.lineTo(T,s)),o=(0,a.computeNextVariantOffset)(T-h,t,o);break;case 5:const Q=.6,u=.3,p=T-h,g=Math.floor(Q*p),M=Math.floor(u*p),y=p-g-M;this._tmpCtx.setLineDash([g,M,y]),this._tmpCtx.moveTo(h,s),this._tmpCtx.lineTo(T,s);break;default:this._tmpCtx.moveTo(h,s),this._tmpCtx.lineTo(T,s)}n||this._tmpCtx.stroke(),this._tmpCtx.restore()}if(this._tmpCtx.restore(),!O&&this._config.fontSize>=12&&!this._config.allowTransparency&&" "!==T){this._tmpCtx.save(),this._tmpCtx.textBaseline="alphabetic";const e=this._tmpCtx.measureText(T);if(this._tmpCtx.restore(),"actualBoundingBoxDescent"in e&&e.actualBoundingBoxDescent>0){this._tmpCtx.save();const e=new Path2D;e.rect(i,L-Math.ceil(t/2),this._config.deviceCellWidth*I,l-L+Math.ceil(t/2)),this._tmpCtx.clip(e),this._tmpCtx.lineWidth=3*this._config.devicePixelRatio,this._tmpCtx.strokeStyle=E.css,this._tmpCtx.strokeText(T,S,S+this._config.deviceCharHeight),this._tmpCtx.restore()}}}if(C){const t=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/15)),e=t%2==1?.5:0;this._tmpCtx.lineWidth=t,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(S,S+e),this._tmpCtx.lineTo(S+this._config.deviceCharWidth*I,S+e),this._tmpCtx.stroke()}if(O||this._tmpCtx.fillText(T,S,S+this._config.deviceCharHeight),"_"===T&&!this._config.allowTransparency){let t=u(this._tmpCtx.getImageData(S,S,this._config.deviceCellWidth,this._config.deviceCellHeight),E,D,W);if(t)for(let e=1;e<=5&&(this._tmpCtx.save(),this._tmpCtx.fillStyle=E.css,this._tmpCtx.fillRect(0,0,this._tmpCanvas.width,this._tmpCanvas.height),this._tmpCtx.restore(),this._tmpCtx.fillText(T,S,S+this._config.deviceCharHeight-e),t=u(this._tmpCtx.getImageData(S,S,this._config.deviceCellWidth,this._config.deviceCellHeight),E,D,W),t);e++);}if(f){const t=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/10)),e=this._tmpCtx.lineWidth%2==1?.5:0;this._tmpCtx.lineWidth=t,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(S,S+Math.floor(this._config.deviceCharHeight/2)-e),this._tmpCtx.lineTo(S+this._config.deviceCharWidth*I,S+Math.floor(this._config.deviceCharHeight/2)-e),this._tmpCtx.stroke()}this._tmpCtx.restore();const N=this._tmpCtx.getImageData(0,0,this._tmpCanvas.width,this._tmpCanvas.height);let P;if(P=this._config.allowTransparency?function(t){for(let e=0;e0)return!1;return!0}(N):u(N,E,D,W),P)return d;const B=this._findGlyphBoundingBox(N,this._workBoundingBox,l,Z,O,S);let $,F;for(;;){if(0===this._activePages.length){const t=this._createNewPage();$=t,F=t.currentRow,F.height=B.size.y;break}$=this._activePages[this._activePages.length-1],F=$.currentRow;for(const t of this._activePages)B.size.y<=t.currentRow.height&&($=t,F=t.currentRow);for(let t=this._activePages.length-1;t>=0;t--)for(const e of this._activePages[t].fixedRows)e.height<=F.height&&B.size.y<=e.height&&($=this._activePages[t],F=e);if(B.size.x>this._textureSize){this._overflowSizePage||(this._overflowSizePage=new Q(this._document,this._config.deviceMaxTextureSize),this.pages.push(this._overflowSizePage),this._requestClearModel=!0,this._onAddTextureAtlasCanvas.fire(this._overflowSizePage.canvas)),$=this._overflowSizePage,F=this._overflowSizePage.currentRow,F.x+B.size.x>=$.canvas.width&&(F.x=0,F.y+=F.height,F.height=0);break}if(F.y+B.size.y>=$.canvas.height||F.height>B.size.y+2){let t=!1;if($.currentRow.y+$.currentRow.height+B.size.y>=$.canvas.height){let e;for(const t of this._activePages)if(t.currentRow.y+t.currentRow.height+B.size.y=_.maxAtlasPages&&F.y+B.size.y<=$.canvas.height&&F.height>=B.size.y&&F.x+B.size.x<=$.canvas.width)t=!0;else{const e=this._createNewPage();$=e,F=e.currentRow,F.height=B.size.y,t=!0}}t||($.currentRow.height>0&&$.fixedRows.push($.currentRow),F={x:0,y:$.currentRow.y+$.currentRow.height,height:B.size.y},$.fixedRows.push(F),$.currentRow={x:0,y:F.y+F.height,height:0})}if(F.x+B.size.x<=$.canvas.width)break;F===$.currentRow?(F.x=0,F.y+=F.height,F.height=0):$.fixedRows.splice($.fixedRows.indexOf(F),1)}return B.texturePage=this._pages.indexOf($),B.texturePosition.x=F.x,B.texturePosition.y=F.y,B.texturePositionClipSpace.x=F.x/$.canvas.width,B.texturePositionClipSpace.y=F.y/$.canvas.height,B.sizeClipSpace.x/=$.canvas.width,B.sizeClipSpace.y/=$.canvas.height,F.height=Math.max(F.height,B.size.y),F.x+=B.size.x,$.ctx.putImageData(N,B.texturePosition.x-this._workBoundingBox.left,B.texturePosition.y-this._workBoundingBox.top,this._workBoundingBox.left,this._workBoundingBox.top,B.size.x,B.size.y),$.addGlyph(B),$.version=++Q.nextVersion,B}_findGlyphBoundingBox(t,e,i,s,r,a){e.top=0;const o=s?this._config.deviceCellHeight:this._tmpCanvas.height,n=s?this._config.deviceCellWidth:i;let L=!1;for(let i=0;i=a;i--){for(let s=0;s=0;i--){for(let s=0;s>>24,a=e.rgba>>>16&255,o=e.rgba>>>8&255,n=i.rgba>>>24,L=i.rgba>>>16&255,h=i.rgba>>>8&255,T=Math.floor((Math.abs(r-n)+Math.abs(a-L)+Math.abs(o-h))/12);let l=!0;for(let e=0;ethis._handleColorChange())),this._cellColorResolver=new s.CellColorResolver(this._terminal,this._optionsService,this._model.selection,this._decorationService,this._coreBrowserService,this._themeService),this._core=this._terminal._core,this._renderLayers=[new _.LinkRenderLayer(this._core.screenElement,2,this._terminal,this._core.linkifier,this._coreBrowserService,d,this._themeService)],this.dimensions=(0,g.createRenderDimensions)(),this._devicePixelRatio=this._coreBrowserService.dpr,this._updateDimensions(),this._updateCursorBlink(),this._register(d.onOptionChange(()=>this._handleOptionsChanged())),this._textBlinkStateManager=this._register(new T.TextBlinkStateManager(()=>this._requestRedrawViewport(),this._coreBrowserService,this._optionsService)),this._resetBlinkingRowState(),this._deviceMaxTextureSize=this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE),this._register((0,u.addDisposableListener)(this._canvas,"webglcontextlost",t=>{this._logService.debug("webglcontextlost event received"),t.preventDefault(),this._contextRestorationTimeout=setTimeout(()=>{this._contextRestorationTimeout=void 0,this._logService.warn("webgl context not restored; firing onContextLoss"),this._onContextLoss.fire(t)},3e3)})),this._register((0,u.addDisposableListener)(this._canvas,"webglcontextrestored",t=>{this._logService.warn("webglcontextrestored event received"),clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0,(0,r.removeTerminalFromCache)(this._terminal),this._initializeWebGLState(),this._requestRedrawViewport()})),this._observerDisposable.value=(0,o.observeDevicePixelDimensions)(this._canvas,this._coreBrowserService.window,(t,e)=>this._setCanvasDevicePixelDimensions(t,e)),this._register(this._coreBrowserService.onWindowChange(t=>{this._observerDisposable.value=(0,o.observeDevicePixelDimensions)(this._canvas,t,(t,e)=>this._setCanvasDevicePixelDimensions(t,e))})),this._register((0,u.addDisposableListener)(this._coreBrowserService.mainDocument,"mousedown",()=>this._cursorBlinkStateManager.value?.restartBlinkAnimation())),this._core.screenElement.appendChild(this._canvas),[this._rectangleRenderer.value,this._glyphRenderer.value]=this._initializeWebGLState(),this._isAttached=this._core.screenElement.isConnected,this._register((0,p.toDisposable)(()=>{clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0;for(const t of this._renderLayers)t.dispose();this._canvas.parentElement?.removeChild(this._canvas),(0,r.removeTerminalFromCache)(this._terminal)}))}get textureAtlas(){return this._charAtlas?.pages[0].canvas}_handleColorChange(){this._refreshCharAtlas(),this._clearModel(!0)}handleDevicePixelRatioChange(){this._devicePixelRatio!==this._coreBrowserService.dpr&&(this._devicePixelRatio=this._coreBrowserService.dpr,this.handleResize(this._terminal.cols,this._terminal.rows))}handleResize(t,e){this._updateDimensions(),this._model.resize(this._terminal.cols,this._terminal.rows),this._resetBlinkingRowState();for(const t of this._renderLayers)t.resize(this._terminal,this.dimensions);this._canvas.width=this.dimensions.device.canvas.width,this._canvas.height=this.dimensions.device.canvas.height,this._canvas.style.width=`${this.dimensions.css.canvas.width}px`,this._canvas.style.height=`${this.dimensions.css.canvas.height}px`,this._core.screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._core.screenElement.style.height=`${this.dimensions.css.canvas.height}px`,this._rectangleRenderer.value?.setDimensions(this.dimensions),this._rectangleRenderer.value?.handleResize(),this._glyphRenderer.value?.setDimensions(this.dimensions),this._glyphRenderer.value?.handleResize(),this._refreshCharAtlas(),this._clearModel(!1),this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1,sync:!0})}handleCharSizeChanged(){this.handleResize(this._terminal.cols,this._terminal.rows)}handleBlur(){for(const t of this._renderLayers)t.handleBlur(this._terminal);this._cursorBlinkStateManager.value?.pause(),this._requestRedrawViewport()}handleFocus(){for(const t of this._renderLayers)t.handleFocus(this._terminal);this._cursorBlinkStateManager.value?.resume(),this._requestRedrawViewport()}handleViewportVisibilityChange(t){this._textBlinkStateManager.setViewportVisible(t)}handleSelectionChanged(t,e,i){for(const s of this._renderLayers)s.handleSelectionChanged(this._terminal,t,e,i);this._model.selection.update(this._core,t,e,i),this._requestRedrawViewport()}handleCursorMove(){for(const t of this._renderLayers)t.handleCursorMove(this._terminal);this._cursorBlinkStateManager.value?.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._refreshCharAtlas(),this._updateCursorBlink()}_initializeWebGLState(){return this._rectangleRenderer.value=new d.RectangleRenderer(this._terminal,this._gl,this.dimensions,this._themeService,this._logService),this._glyphRenderer.value=new l.GlyphRenderer(this._terminal,this._gl,this.dimensions,this._optionsService,this._logService),this.handleCharSizeChanged(),[this._rectangleRenderer.value,this._glyphRenderer.value]}_refreshCharAtlas(){if(this.dimensions.device.char.width<=0&&this.dimensions.device.char.height<=0)return void(this._isAttached=!1);const t=(0,r.acquireTextureAtlas)(this._terminal,this._optionsService.rawOptions,this._themeService.colors,this.dimensions.device.cell.width,this.dimensions.device.cell.height,this.dimensions.device.char.width,this.dimensions.device.char.height,this._coreBrowserService.dpr,this._deviceMaxTextureSize,this._customGlyphs);this._charAtlas!==t&&(this._onChangeTextureAtlas.fire(t.pages[0].canvas),this._charAtlasDisposable.value=(0,p.combinedDisposable)(Q.EventUtils.forward(t.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas),Q.EventUtils.forward(t.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas))),this._charAtlas=t,this._charAtlas.warmUp(),this._glyphRenderer.value?.setAtlas(this._charAtlas)}_clearModel(t){this._model.clear(),t&&this._glyphRenderer.value?.clear()}clearTextureAtlas(){this._charAtlas?.clearTexture(),this._clearModel(!0),this._requestRedrawViewport()}clear(){this._clearModel(!0);for(const t of this._renderLayers)t.reset(this._terminal);this._resetBlinkingRowState(),this._textBlinkStateManager.setNeedsBlinkInViewport(!1),this._cursorBlinkStateManager.value?.restartBlinkAnimation(),this._updateCursorBlink()}renderRows(t,e){if(!this._isAttached){if(!(this._core.screenElement?.isConnected&&this._charSizeService.width&&this._charSizeService.height))return;this._updateDimensions(),this._refreshCharAtlas(),this._isAttached=!0}for(const i of this._renderLayers)i.handleGridChanged(this._terminal,t,e);if(!this._glyphRenderer.value||!this._rectangleRenderer.value)return;this._glyphRenderer.value.beginFrame()?(this._clearModel(!0),this._updateModel(0,this._terminal.rows-1)):this._updateModel(t,e);let i=!1,s=0;for(;this._charAtlas&&this._glyphRenderer.value.beginFrame()&&s++<32;)i=!0,this._clearModel(!0),this._updateModel(0,this._terminal.rows-1);i&&this._glyphRenderer.value.invalidateAtlasTextures(),this._rectangleRenderer.value.renderBackgrounds(),this._glyphRenderer.value.render(this._model),this._cursorBlinkStateManager.value&&!this._cursorBlinkStateManager.value.isCursorVisible||this._rectangleRenderer.value.renderCursor()}_updateCursorBlink(){this._coreService.decPrivateModes.cursorBlink??this._terminal.options.cursorBlink?this._cursorBlinkStateManager.value=new a.CursorBlinkStateManager(()=>{this._requestRedrawCursor()},this._coreBrowserService):this._cursorBlinkStateManager.clear(),this._requestRedrawCursor()}_updateModel(t,e){const i=this._core;let s,r,a,o,n,L,T,l,d,_,Q,u,p,g,M,f,C,m=this._workCell;t=v(t,i.rows-1,0),e=v(e,i.rows-1,0);const w=this._coreService.decPrivateModes.cursorStyle??i.options.cursorStyle??"block",x=this._terminal.buffer.active.baseY+this._terminal.buffer.active.cursorY,b=x-i.buffer.ydisp,E=Math.min(this._terminal.buffer.active.cursorX,i.cols-1);let R=-1;const k=this._coreService.isCursorInitialized&&!this._coreService.isCursorHidden&&(!this._cursorBlinkStateManager.value||this._cursorBlinkStateManager.value.isCursorVisible);this._model.cursor=void 0;let A=!1;for(r=t;r<=e;r++){a=r+i.buffer.ydisp;const t=i.buffer.lines.get(a);if(!t){for(this._model.lineLengths[r]=0,f=0;f=T,d=f,n.length>0&&f===n[0][0]&&l){_=n.shift();const t=this._model.selection.isCellSelected(this._terminal,_[0],a);for(M=_[0]+1;M<_[1];M++)l&&=t===this._model.selection.isCellSelected(this._terminal,M,a);l&&=!Q||E<_[0]||E>=_[1],l?(L=!0,m=new y(m,o.translateToString(!0,_[0],_[1]),_[1]-_[0]),d=_[1]-1):T=_[1]}if(u=m.getChars(),p=m.getCode(),M=4*(r*i.cols+f),!e&&m.isBlink()&&(e=!0),this._cellColorResolver.resolve(m,f,a,this.dimensions.device.cell.width,this.dimensions.device.cell.height),k&&a===x&&(f===E&&(this._model.cursor={x:E,y:b,width:m.getWidth(),style:this._coreBrowserService.isFocused?w:i.options.cursorInactiveStyle,cursorWidth:i.options.cursorWidth,dpr:this._devicePixelRatio},R=E+m.getWidth()-1),f>=E&&f<=R&&(this._coreBrowserService.isFocused&&"block"===w||!1===this._coreBrowserService.isFocused&&"block"===i.options.cursorInactiveStyle)&&(this._cellColorResolver.result.fg=50331648|this._themeService.colors.cursorAccent.rgba>>8&16777215,this._cellColorResolver.result.bg=50331648|this._themeService.colors.cursor.rgba>>8&16777215)),this._textBlinkStateManager.isEnabled&&!this._textBlinkStateManager.isBlinkOn&&m.isBlink()&&(this._cellColorResolver.result.fg|=1073741824),p!==h.NULL_CELL_CODE&&(this._model.lineLengths[r]=f+1),(this._model.cells[M]!==p||this._model.cells[M+1]!==this._cellColorResolver.result.bg||this._model.cells[M+2]!==this._cellColorResolver.result.fg||this._model.cells[M+3]!==this._cellColorResolver.result.ext)&&(A=!0,u.length>1&&(p|=c.COMBINED_CHAR_BIT_MASK),this._model.cells[M]=p,this._model.cells[M+1]=this._cellColorResolver.result.bg,this._model.cells[M+2]=this._cellColorResolver.result.fg,this._model.cells[M+3]=this._cellColorResolver.result.ext,g=m.getWidth(),this._glyphRenderer.value.updateCell(f,r,p,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext,u,g,s),L)){for(m=this._workCell,f++;f<=d;f++)C=4*(r*i.cols+f),this._nullModelCell(f,r,C,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext);f--}}this._setRowBlinkState(r,e)}A&&this._rectangleRenderer.value.updateBackgrounds(this._model),this._rectangleRenderer.value.updateCursor(this._model),this._updateTextBlinkState()}_nullModelCell(t,e,i,s,r,a){this._glyphRenderer.value.updateCell(t,e,h.NULL_CELL_CODE,s,r,a,h.NULL_CELL_CHAR,0,0),this._model.cells[i]=h.NULL_CELL_CODE,this._model.cells[i+1]=s,this._model.cells[i+2]=r,this._model.cells[i+3]=a}_resetBlinkingRowState(){this._rowHasBlinkingCells=new Array(this._terminal.rows).fill(!1),this._rowHasBlinkingCellsCount=0}_setRowBlinkState(t,e){this._rowHasBlinkingCells[t]!==e&&(this._rowHasBlinkingCells[t]=e,this._rowHasBlinkingCellsCount+=e?1:-1)}_updateTextBlinkState(){this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount>0)}_updateDimensions(){this._charSizeService.width&&this._charSizeService.height&&(this.dimensions.device.char.width=Math.floor(this._charSizeService.width*this._devicePixelRatio),this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*this._devicePixelRatio),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.top=1===this._optionsService.rawOptions.lineHeight?0:Math.round((this.dimensions.device.cell.height-this.dimensions.device.char.height)/2),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.char.left=Math.floor(this._optionsService.rawOptions.letterSpacing/2),this.dimensions.device.canvas.height=this._terminal.rows*this.dimensions.device.cell.height,this.dimensions.device.canvas.width=this._terminal.cols*this.dimensions.device.cell.width,this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/this._devicePixelRatio),this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/this._devicePixelRatio),this.dimensions.css.cell.height=this.dimensions.device.cell.height/this._devicePixelRatio,this.dimensions.css.cell.width=this.dimensions.device.cell.width/this._devicePixelRatio)}_setCanvasDevicePixelDimensions(t,e){this._canvas.width===t&&this._canvas.height===e||(this._canvas.width=t,this._canvas.height=e,this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1,sync:!0}))}_requestRedrawViewport(){this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1})}_requestRedrawCursor(){const t=this._terminal.buffer.active.cursorY;this._onRequestRedraw.fire({start:t,end:t})}}e.WebglRenderer=M;class y extends n.AttributeData{constructor(t,e,i){super(),this.content=0,this.combinedData="",this.fg=t.fg,this.bg=t.bg,this.combinedData=e,this._width=i}isCombined(){return 2097152}getWidth(){return this._width}getChars(){return this.combinedData}getCode(){return 2097151}setFromCharData(t){throw new Error("not implemented")}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}}function v(t,e,i=0){return Math.max(Math.min(t,e),i)}e.JoinedCellData=y},719(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.GLTexture=e.PROJECTION_MATRIX=void 0,e.createProgram=function(t,e,i,a){const o=(0,s.throwIfFalsy)(t.createProgram());if(t.attachShader(o,(0,s.throwIfFalsy)(r(t,t.VERTEX_SHADER,e,a))),t.attachShader(o,(0,s.throwIfFalsy)(r(t,t.FRAGMENT_SHADER,i,a))),t.linkProgram(o),t.getProgramParameter(o,t.LINK_STATUS))return o;a.error(t.getProgramInfoLog(o)),t.deleteProgram(o)},e.createShader=r,e.expandFloat32Array=function(t,e){const i=Math.min(2*t.length,e),s=new Float32Array(i);for(let e=0;e{const e=[],i=[3/8,2/8,3/8],s=[0,3/8,5/8];for(let r=0;r<3;r++){const a=t>>2*r&1,o=t>>2*r+1&1;a&&o?e.push(`M0,${s[r]} L1,${s[r]} L1,${s[r]+i[r]} L0,${s[r]+i[r]} Z`):a?e.push(`M0,${s[r]} L0.5,${s[r]} L0.5,${s[r]+i[r]} L0,${s[r]+i[r]} Z`):o&&e.push(`M0.5,${s[r]} L1,${s[r]} L1,${s[r]+i[r]} L0.5,${s[r]+i[r]} Z`)}return e.join(" ")}}}function r(t){const e=[],i=.15,s=.015,r=.075,a=.0375,o=.95;if(64&t){const t=.175,a=.2125,n=.25,L=.2+s,h=o-i-s;e.push(`M${L},${a} L${L+r},${t} L${h-r},${t} L${h},${a} L${h-r},${n} L${L+r},${n} Z`)}if(32&t){const t=o-i,s=.875,r=o,n=.265,L=.485;e.push(`M${s},${n} L${r},${n+a} L${r},${L-a} L${s},${L} L${t},${L-a} L${t},${n+a} Z`)}if(16&t){const t=o-i,s=.875,r=o,n=.515,L=.735;e.push(`M${s},${n} L${r},${n+a} L${r},${L-a} L${s},${L} L${t},${L-a} L${t},${n+a} Z`)}if(8&t){const t=.75,a=.7875,n=.825,L=.2+s,h=o-i-s;e.push(`M${L},${a} L${L+r},${t} L${h-r},${t} L${h},${a} L${h-r},${n} L${L+r},${n} Z`)}if(4&t){const t=.05,i=.125,s=.2,r=.515,o=.735;e.push(`M${i},${r} L${s},${r+a} L${s},${o-a} L${i},${o} L${t},${o-a} L${t},${r+a} Z`)}if(2&t){const t=.05,i=.125,s=.2,r=.265,o=.485;e.push(`M${i},${r} L${s},${r+a} L${s},${o-a} L${i},${o} L${t},${o-a} L${t},${r+a} Z`)}if(1&t){const t=.4625,a=.5,n=.5375,L=.2+s,h=o-i-s;e.push(`M${L},${a} L${L+r},${t} L${h-r},${t} L${h},${a} L${h-r},${n} L${L+r},${n} Z`)}return e.join(" ")}Object.defineProperty(e,"__esModule",{value:!0}),e.blockPatternCodepoints=e.customGlyphDefinitions=void 0,function(t){t.LINE_H=Object.freeze({type:2,data:"M0,.5 L1,.5",strokeWidth:1}),t.LINE_V=Object.freeze({type:2,data:"M.5,0 L.5,1",strokeWidth:1}),t.FADE_RIGHT=Object.freeze({type:2,data:"M0,.5 L.28,.5 M.32,.5 L.52,.5 M.60,.5 L.72,.5 M.84,.5 L.90,.5",strokeWidth:1}),t.FADE_LEFT=Object.freeze({type:2,data:"M.10,.5 L.16,.5 M.28,.5 L.40,.5 M.48,.5 L.68,.5 M.72,.5 L1,.5",strokeWidth:1}),t.FADE_DOWN=Object.freeze({type:2,data:"M.5,0 L.5,.28 M.5,.32 L.5,.52 M.5,.60 L.5,.72 M.5,.84 L.5,.90",strokeWidth:1}),t.FADE_UP=Object.freeze({type:2,data:"M.5,.10 L.5,.16 M.5,.28 L.5,.40 M.5,.48 L.5,.68 M.5,.72 L.5,1",strokeWidth:1}),t.CURVE_DOWN_RIGHT=Object.freeze({type:2,data:(t,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,1,.5`,strokeWidth:1}),t.CURVE_DOWN_LEFT=Object.freeze({type:2,data:(t,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,0,.5`,strokeWidth:1}),t.CURVE_UP_RIGHT=Object.freeze({type:2,data:(t,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,1,.5`,strokeWidth:1}),t.CURVE_UP_LEFT=Object.freeze({type:2,data:(t,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,0,.5`,strokeWidth:1}),t.NODE_FILL=Object.freeze({type:3,data:"M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5"}),t.NODE_STROKE=Object.freeze({type:3,data:"M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5",strokeWidth:1}),t.NODE_LINE_RIGHT=Object.freeze({type:3,data:"M1,.5 L.85,.5",strokeWidth:1}),t.NODE_LINE_LEFT=Object.freeze({type:3,data:"M0,.5 L.15,.5",strokeWidth:1}),t.NODE_LINE_DOWN=Object.freeze({type:3,data:"M.5,1 L.5,.7",strokeWidth:1}),t.NODE_LINE_UP=Object.freeze({type:3,data:"M.5,0 L.5,.3",strokeWidth:1})}(i||(i={})),e.customGlyphDefinitions={"─":{type:2,data:"M0,.5 L1,.5",strokeWidth:1},"━":{type:2,data:"M0,.5 L1,.5",strokeWidth:3},"│":{type:2,data:"M.5,0 L.5,1",strokeWidth:1},"┃":{type:2,data:"M.5,0 L.5,1",strokeWidth:3},"┄":{type:2,data:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",strokeWidth:1},"┅":{type:2,data:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",strokeWidth:3},"┆":{type:2,data:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",strokeWidth:1},"┇":{type:2,data:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",strokeWidth:3},"┈":{type:2,data:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",strokeWidth:1},"┉":{type:2,data:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",strokeWidth:3},"┊":{type:2,data:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",strokeWidth:1},"┋":{type:2,data:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",strokeWidth:3},"┌":{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},"┍":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"┎":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"┏":{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3},"┐":{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},"┑":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"┒":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"┓":{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3},"└":{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},"┕":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"┖":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"┗":{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3},"┘":{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},"┙":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"┚":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"┛":{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3},"├":{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:1},"┝":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"┞":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"┟":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"┠":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"┡":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"┢":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"┣":{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:3},"┤":{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:1},"┥":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"┦":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"┧":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"┨":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"┩":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"┪":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"┫":{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:3},"┬":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:1},"┭":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"┮":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"┯":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"┰":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"┱":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"┲":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"┳":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:3},"┴":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,0",strokeWidth:1},"┵":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"┶":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"┷":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"┸":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"┹":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"┺":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"┻":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,0",strokeWidth:3},"┼":{type:2,data:"M0,.5 L1,.5 M.5,0 L.5,1",strokeWidth:1},"┽":[{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"┾":[{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"┿":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"╀":[{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"╁":[{type:2,data:"M.5,.5 L.5,0 M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"╂":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"╃":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"╄":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"╅":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"╆":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"╇":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0 M0,.5 L1,.5",strokeWidth:3}],"╈":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:3}],"╉":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:3}],"╊":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:3}],"╋":{type:2,data:"M0,.5 L1,.5 M.5,0 L.5,1",strokeWidth:3},"╌":{type:2,data:"M.1,.5 L.4,.5 M.6,.5 L.9,.5",strokeWidth:1},"╍":{type:2,data:"M.1,.5 L.4,.5 M.6,.5 L.9,.5",strokeWidth:3},"╎":{type:2,data:"M.5,.1 L.5,.4 M.5,.6 L.5,.9",strokeWidth:1},"╏":{type:2,data:"M.5,.1 L.5,.4 M.5,.6 L.5,.9",strokeWidth:3},"═":{type:2,data:(t,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"║":{type:2,data:(t,e)=>`M${.5-t},0 L${.5-t},1 M${.5+t},0 L${.5+t},1`,strokeWidth:1},"╒":{type:2,data:(t,e)=>`M.5,1 L.5,${.5-e} L1,${.5-e} M.5,${.5+e} L1,${.5+e}`,strokeWidth:1},"╓":{type:2,data:(t,e)=>`M${.5-t},1 L${.5-t},.5 L1,.5 M${.5+t},.5 L${.5+t},1`,strokeWidth:1},"╔":{type:2,data:(t,e)=>`M1,${.5-e} L${.5-t},${.5-e} L${.5-t},1 M1,${.5+e} L${.5+t},${.5+e} L${.5+t},1`,strokeWidth:1},"╕":{type:2,data:(t,e)=>`M0,${.5-e} L.5,${.5-e} L.5,1 M0,${.5+e} L.5,${.5+e}`,strokeWidth:1},"╖":{type:2,data:(t,e)=>`M${.5+t},1 L${.5+t},.5 L0,.5 M${.5-t},.5 L${.5-t},1`,strokeWidth:1},"╗":{type:2,data:(t,e)=>`M0,${.5+e} L${.5-t},${.5+e} L${.5-t},1 M0,${.5-e} L${.5+t},${.5-e} L${.5+t},1`,strokeWidth:1},"╘":{type:2,data:(t,e)=>`M.5,0 L.5,${.5+e} L1,${.5+e} M.5,${.5-e} L1,${.5-e}`,strokeWidth:1},"╙":{type:2,data:(t,e)=>`M1,.5 L${.5-t},.5 L${.5-t},0 M${.5+t},.5 L${.5+t},0`,strokeWidth:1},"╚":{type:2,data:(t,e)=>`M1,${.5-e} L${.5+t},${.5-e} L${.5+t},0 M1,${.5+e} L${.5-t},${.5+e} L${.5-t},0`,strokeWidth:1},"╛":{type:2,data:(t,e)=>`M0,${.5+e} L.5,${.5+e} L.5,0 M0,${.5-e} L.5,${.5-e}`,strokeWidth:1},"╜":{type:2,data:(t,e)=>`M0,.5 L${.5+t},.5 L${.5+t},0 M${.5-t},.5 L${.5-t},0`,strokeWidth:1},"╝":{type:2,data:(t,e)=>`M0,${.5-e} L${.5-t},${.5-e} L${.5-t},0 M0,${.5+e} L${.5+t},${.5+e} L${.5+t},0`,strokeWidth:1},"╞":{type:2,data:(t,e)=>`M.5,0 L.5,1 M.5,${.5-e} L1,${.5-e} M.5,${.5+e} L1,${.5+e}`,strokeWidth:1},"╟":{type:2,data:(t,e)=>`M${.5-t},0 L${.5-t},1 M${.5+t},0 L${.5+t},1 M${.5+t},.5 L1,.5`,strokeWidth:1},"╠":{type:2,data:(t,e)=>`M${.5-t},0 L${.5-t},1 M1,${.5+e} L${.5+t},${.5+e} L${.5+t},1 M1,${.5-e} L${.5+t},${.5-e} L${.5+t},0`,strokeWidth:1},"╡":{type:2,data:(t,e)=>`M.5,0 L.5,1 M0,${.5-e} L.5,${.5-e} M0,${.5+e} L.5,${.5+e}`,strokeWidth:1},"╢":{type:2,data:(t,e)=>`M0,.5 L${.5-t},.5 M${.5-t},0 L${.5-t},1 M${.5+t},0 L${.5+t},1`,strokeWidth:1},"╣":{type:2,data:(t,e)=>`M${.5+t},0 L${.5+t},1 M0,${.5+e} L${.5-t},${.5+e} L${.5-t},1 M0,${.5-e} L${.5-t},${.5-e} L${.5-t},0`,strokeWidth:1},"╤":{type:2,data:(t,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e} M.5,${.5+e} L.5,1`,strokeWidth:1},"╥":{type:2,data:(t,e)=>`M0,.5 L1,.5 M${.5-t},.5 L${.5-t},1 M${.5+t},.5 L${.5+t},1`,strokeWidth:1},"╦":{type:2,data:(t,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L${.5-t},${.5+e} L${.5-t},1 M1,${.5+e} L${.5+t},${.5+e} L${.5+t},1`,strokeWidth:1},"╧":{type:2,data:(t,e)=>`M.5,0 L.5,${.5-e} M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"╨":{type:2,data:(t,e)=>`M0,.5 L1,.5 M${.5-t},.5 L${.5-t},0 M${.5+t},.5 L${.5+t},0`,strokeWidth:1},"╩":{type:2,data:(t,e)=>`M0,${.5+e} L1,${.5+e} M0,${.5-e} L${.5-t},${.5-e} L${.5-t},0 M1,${.5-e} L${.5+t},${.5-e} L${.5+t},0`,strokeWidth:1},"╪":{type:2,data:(t,e)=>`M.5,0 L.5,1 M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"╫":{type:2,data:(t,e)=>`M0,.5 L1,.5 M${.5-t},0 L${.5-t},1 M${.5+t},0 L${.5+t},1`,strokeWidth:1},"╬":{type:2,data:(t,e)=>`M0,${.5+e} L${.5-t},${.5+e} L${.5-t},1 M1,${.5+e} L${.5+t},${.5+e} L${.5+t},1 M0,${.5-e} L${.5-t},${.5-e} L${.5-t},0 M1,${.5-e} L${.5+t},${.5-e} L${.5+t},0`,strokeWidth:1},"╭":{type:2,data:(t,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,1,.5`,strokeWidth:1},"╮":{type:2,data:(t,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,0,.5`,strokeWidth:1},"╯":{type:2,data:(t,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,0,.5`,strokeWidth:1},"╰":{type:2,data:(t,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,1,.5`,strokeWidth:1},"╱":{type:2,data:"M1,0 L0,1",strokeWidth:1},"╲":{type:2,data:"M0,0 L1,1",strokeWidth:1},"╳":{type:2,data:"M1,0 L0,1 M0,0 L1,1",strokeWidth:1},"╴":{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},"╵":{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},"╶":{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},"╷":{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},"╸":{type:2,data:"M.5,.5 L0,.5",strokeWidth:3},"╹":{type:2,data:"M.5,.5 L.5,0",strokeWidth:3},"╺":{type:2,data:"M.5,.5 L1,.5",strokeWidth:3},"╻":{type:2,data:"M.5,.5 L.5,1",strokeWidth:3},"╼":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"╽":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"╾":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"╿":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"▀":{type:0,data:[{x:0,y:0,w:8,h:4}]},"▁":{type:0,data:[{x:0,y:7,w:8,h:1}]},"▂":{type:0,data:[{x:0,y:6,w:8,h:2}]},"▃":{type:0,data:[{x:0,y:5,w:8,h:3}]},"▄":{type:0,data:[{x:0,y:4,w:8,h:4}]},"▅":{type:0,data:[{x:0,y:3,w:8,h:5}]},"▆":{type:0,data:[{x:0,y:2,w:8,h:6}]},"▇":{type:0,data:[{x:0,y:1,w:8,h:7}]},"█":{type:0,data:[{x:0,y:0,w:8,h:8}]},"▉":{type:0,data:[{x:0,y:0,w:7,h:8}]},"▊":{type:0,data:[{x:0,y:0,w:6,h:8}]},"▋":{type:0,data:[{x:0,y:0,w:5,h:8}]},"▌":{type:0,data:[{x:0,y:0,w:4,h:8}]},"▍":{type:0,data:[{x:0,y:0,w:3,h:8}]},"▎":{type:0,data:[{x:0,y:0,w:2,h:8}]},"▏":{type:0,data:[{x:0,y:0,w:1,h:8}]},"▐":{type:0,data:[{x:4,y:0,w:4,h:8}]},"░":{type:1,data:[[1,0],[0,0]]},"▒":{type:1,data:[[1,0],[0,1]]},"▓":{type:1,data:[[1,1],[1,0]]},"▔":{type:0,data:[{x:0,y:0,w:8,h:1}]},"▕":{type:0,data:[{x:7,y:0,w:1,h:8}]},"▖":{type:0,data:[{x:0,y:4,w:4,h:4}]},"▗":{type:0,data:[{x:4,y:4,w:4,h:4}]},"▘":{type:0,data:[{x:0,y:0,w:4,h:4}]},"▙":{type:0,data:[{x:0,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}]},"▚":{type:0,data:[{x:0,y:0,w:4,h:4},{x:4,y:4,w:4,h:4}]},"▛":{type:0,data:[{x:0,y:0,w:4,h:8},{x:4,y:0,w:4,h:4}]},"▜":{type:0,data:[{x:0,y:0,w:8,h:4},{x:4,y:0,w:4,h:8}]},"▝":{type:0,data:[{x:4,y:0,w:4,h:4}]},"▞":{type:0,data:[{x:4,y:0,w:4,h:4},{x:0,y:4,w:4,h:4}]},"▟":{type:0,data:[{x:4,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}]},"":{type:5,data:{d:"M.3,1 L.03,1 L.03,.88 C.03,.82,.06,.78,.11,.73 C.15,.7,.2,.68,.28,.65 L.43,.6 C.49,.58,.53,.56,.56,.53 C.59,.5,.6,.47,.6,.43 L.6,.27 L.4,.27 L.69,.1 L.98,.27 L.78,.27 L.78,.46 C.78,.52,.76,.56,.72,.61 C.68,.66,.63,.67,.56,.7 L.48,.72 C.42,.74,.38,.76,.35,.78 C.32,.8,.31,.84,.31,.88 L.31,1 M.3,.5 L.03,.59 L.03,.09 L.3,.09 L.3,.655",type:0}},"":{type:5,data:{d:"M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.355,.03 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5",type:0}},"":{type:5,data:{d:"M.25,.94 C.16,.94,.11,.92,.11,.87 L.11,.53 C.11,.48,.15,.455,.23,.45 L.23,.3 C.23,.25,.26,.22,.31,.19 C.36,.16,.43,.15,.51,.15 C.59,.15,.66,.16,.71,.19 C.77,.22,.79,.26,.79,.3 L.79,.45 C.87,.45,.91,.48,.91,.53 L.91,.87 C.91,.92,.86,.94,.77,.94 L.24,.94 M.53,.2 C.49,.2,.45,.21,.42,.23 C.39,.25,.38,.27,.38,.3 L.38,.45 L.68,.45 L.68,.3 C.68,.27,.67,.25,.64,.23 C.61,.21,.58,.2,.53,.2 M.58,.82 L.58,.66 C.63,.65,.65,.63,.65,.6 C.65,.58,.64,.57,.61,.56 C.58,.55,.56,.54,.52,.54 C.48,.54,.46,.55,.43,.56 C.4,.57,.39,.59,.39,.6 C.39,.63,.41,.64,.46,.66 L.46,.82 L.57,.82",type:0}},"":{type:5,data:{d:"M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.7,.03 L.7,.1 L.355,.1 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5",type:0}},"":{type:5,data:{d:"M0,0 L1,.5 L0,1",type:0,rightPadding:2}},"":{type:5,data:{d:"M-1,-.5 L1,.5 L-1,1.5",type:1,leftPadding:1,rightPadding:1}},"":{type:5,data:{d:"M1,0 L0,.5 L1,1",type:0,leftPadding:2}},"":{type:5,data:{d:"M2,-.5 L0,.5 L2,1.5",type:1,leftPadding:1,rightPadding:1}},"":{type:5,data:{d:"M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0",type:0,rightPadding:1}},"":{type:5,data:{d:"M.2,1 C.422,1,.8,.826,.78,.5 C.8,.174,0.422,0,.2,0",type:1,rightPadding:1}},"":{type:5,data:{d:"M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0",type:0,leftPadding:1}},"":{type:5,data:{d:"M.8,1 C0.578,1,0.2,.826,.22,.5 C0.2,0.174,0.578,0,0.8,0",type:1,leftPadding:1}},"":{type:5,data:{d:"M-.5,-.5 L1.5,1.5 L-.5,1.5",type:0}},"":{type:5,data:{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1}},"":{type:5,data:{d:"M1.5,-.5 L-.5,1.5 L1.5,1.5",type:0}},"":{type:5,data:{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1}},"":{type:5,data:{d:"M1.5,-.5 L-.5,1.5 L-.5,-.5",type:0}},"":{type:5,data:{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1}},"":{type:5,data:{d:"M-.5,-.5 L1.5,1.5 L1.5,-.5",type:0}},"":{type:5,data:{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1}},"":{type:5,data:{d:"M0.8069,0.6075 Q0.7991,0.612,0.7815,0.6218 T0.7542,0.6369 T0.7282,0.6508 T0.7016,0.6644 T0.6778,0.6758 T0.6551,0.6854 T0.637,0.6911 T0.622,0.6936 Q0.6186,0.6936,0.6128,0.6942 T0.6018,0.6952 T0.5894,0.6956 T0.5757,0.6952 T0.561,0.693 T0.5451,0.6879 Q0.5342,0.6834,0.5193,0.6756 T0.4925,0.6614 T0.4673,0.6485 T0.4436,0.6389 T0.4239,0.6363 T0.4089,0.6422 Q0.4045,0.6467,0.3958,0.6554 T0.3843,0.6669 T0.3748,0.6746 T0.3613,0.683 T0.3442,0.6903 L0.3499,0.6956 Q0.3584,0.7042,0.3679,0.7177 T0.3836,0.7403 T0.4019,0.7583 T0.4295,0.7703 Q0.4688,0.7785,0.5203,0.7605 Q0.4902,0.7817,0.449,0.7911 T0.3696,0.8001 Q0.3567,0.7997,0.341,0.7942 T0.3111,0.7813 T0.2805,0.766 T0.2493,0.753 T0.218,0.7472 T0.187,0.754 Q0.1762,0.7589,0.1653,0.7681 T0.1453,0.7895 T0.1308,0.818 T0.1264,0.8507 L0.1264,0.858 Q0.1453,0.8462,0.1607,0.8433 T0.1899,0.8452 T0.2226,0.8625 Q0.2344,0.8707,0.2485,0.8752 T0.2813,0.8831 T0.3089,0.889 Q0.2981,0.8915,0.2846,0.8956 T0.2603,0.9037 T0.2368,0.9115 T0.2129,0.9174 T0.1894,0.9194 T0.1653,0.9155 Q0.1504,0.9115,0.1328,0.9196 T0.0986,0.9421 T0.0655,0.9708 T0.0317,0.9943 T0,1 Q0.001,0.94,0.0017,0.6508 T0.0024,0.2322 Q0.002,0.2109,0.0007,0.1379 T0,0.0249 Q0.0058,0.0253,0.009,0.0257 T0.0156,0.0288 T0.0203,0.0333 T0.0264,0.0422 T0.0349,0.0539 Q0.044,0.0657,0.0542,0.072 T0.0752,0.0789 T0.096,0.0775 T0.1175,0.0694 T0.1379,0.0573 T0.1579,0.0432 Q0.2141,0,0.2673,0.0318 Q0.2588,0.0318,0.2517,0.033 T0.239,0.0361 T0.2276,0.0424 T0.218,0.0504 T0.2085,0.0612 T0.1997,0.0732 T0.1899,0.0875 T0.1795,0.1028 Q0.1748,0.1093,0.1507,0.1234 T0.1186,0.1448 Q0.0999,0.1612,0.1108,0.1783 Q0.1162,0.1877,0.1238,0.1928 T0.1404,0.1979 T0.158,0.1967 T0.1775,0.1895 T0.1963,0.1791 T0.2151,0.1663 T0.2314,0.1534 T0.2458,0.141 T0.2561,0.1322 Q0.2815,0.1106,0.3017,0.101 T0.344,0.0926 T0.3919,0.1077 Q0.4099,0.1175,0.4265,0.1206 T0.4588,0.1202 T0.488,0.1098 T0.5176,0.0914 Q0.5071,0.1032,0.4963,0.1122 T0.4722,0.1285 T0.4497,0.1401 T0.4223,0.1518 T0.3943,0.1636 Q0.374,0.1726,0.3623,0.1863 T0.3482,0.2162 Q0.3445,0.2403,0.3608,0.2605 T0.4072,0.2876 Q0.4194,0.2909,0.4326,0.2899 T0.457,0.2856 T0.4805,0.2764 T0.5032,0.264 T0.5256,0.2497 T0.5478,0.2352 T0.5698,0.2222 T0.5921,0.2118 Q0.6982,0.173,0.81,0.2436 Q0.833,0.2583,0.8496,0.2674 T0.887,0.2846 T0.925,0.294 T0.9617,0.2911 T1,0.2746 Q0.9817,0.2946,0.96,0.3056 T0.9175,0.3191 T0.8731,0.3209 T0.8277,0.3164 T0.7818,0.3109 T0.7359,0.3101 T0.6907,0.3197 T0.647,0.3448 Q0.6443,0.3472,0.6333,0.3562 T0.6194,0.3674 T0.6076,0.3766 T0.594,0.386 T0.5815,0.3931 T0.5661,0.4007 T0.5501,0.4064 L0.5556,0.4121 Q0.5627,0.4198,0.5722,0.4251 T0.5926,0.4337 T0.6131,0.439 T0.6343,0.4431 T0.6521,0.4468 Q0.6762,0.4529,0.7056,0.4463 Q0.6772,0.4623,0.6543,0.471 T0.604,0.4814 Q0.5962,0.4818,0.5815,0.4825 T0.5591,0.4835 T0.5393,0.4857 T0.519,0.4908 T0.5007,0.4996 T0.4812,0.5141 T0.4627,0.5353 L0.4549,0.5451 L0.4661,0.543 Q0.4759,0.541,0.4909,0.55 T0.5315,0.5796 T0.5684,0.6083 Q0.5799,0.6161,0.5915,0.6212 T0.6181,0.6308 L0.6331,0.6353 Q0.6514,0.6418,0.7004,0.633 T0.8069,0.6075 Z",type:0}},"":{type:5,data:{d:"M0.011,0.0469 Q0.0152,0.0412,0.02,0.0361 T0.0292,0.0281 T0.0365,0.0253 Q0.0426,0.0257,0.0461,0.0263 T0.0534,0.03 T0.0582,0.0345 T0.0645,0.0434 T0.0723,0.0546 Q0.0807,0.0653,0.0897,0.0712 T0.1081,0.0775 T0.1255,0.0765 T0.1438,0.0695 T0.1605,0.0593 T0.1772,0.0473 Q0.2388,0,0.2927,0.0326 L0.2614,0.0767 L0.2581,0.0767 Q0.2494,0.0767,0.2444,0.0773 T0.2359,0.0789 T0.2291,0.0842 T0.2233,0.0922 T0.2146,0.1054 T0.2026,0.1236 Q0.1943,0.1354,0.1843,0.1444 T0.1675,0.1574 T0.1526,0.1654 T0.1429,0.1701 Q0.1436,0.1717,0.1446,0.1733 Q0.1504,0.1823,0.1581,0.1866 T0.1733,0.1915 T0.1909,0.1884 T0.2085,0.1809 T0.2265,0.169 T0.2427,0.1564 T0.2575,0.1436 T0.2688,0.1338 Q0.2969,0.1101,0.3195,0.0995 T0.3643,0.0901 T0.4114,0.1048 Q0.4279,0.1138,0.4426,0.1168 T0.4705,0.117 T0.4945,0.1089 T0.5186,0.0942 L0.5211,0.0926 L0.5289,0.0922 Q0.5411,0.095,0.5189,0.1203 Q0.505,0.1358,0.4913,0.1474 T0.4634,0.167 T0.4389,0.1796 T0.4121,0.1909 T0.3866,0.2011 Q0.3782,0.2047,0.3727,0.2092 Q0.3717,0.2117,0.3714,0.2141 Q0.3695,0.2263,0.3751,0.2388 T0.394,0.2616 T0.4253,0.2761 Q0.4398,0.2785,0.4513,0.2779 T0.4756,0.2724 T0.4987,0.2616 T0.5216,0.2476 T0.5453,0.2321 T0.5703,0.217 T0.5976,0.2043 Q0.7015,0.166,0.8099,0.2345 Q0.8361,0.2512,0.8572,0.2616 T0.9006,0.2779 T0.9432,0.281 T0.9819,0.2663 L0.9887,0.2671 Q1,0.272,0.9768,0.2969 Q0.9558,0.3193,0.9319,0.333 T0.8858,0.3513 T0.8391,0.3569 T0.793,0.3546 T0.7485,0.3497 T0.7065,0.3473 T0.6681,0.3522 T0.6344,0.3699 Q0.5963,0.4005,0.5847,0.4082 Q0.595,0.4139,0.6079,0.4174 T0.6367,0.4237 T0.6586,0.4278 Q0.6793,0.4331,0.7044,0.4278 Q0.7057,0.4278,0.707,0.4274 Q0.7067,0.429,0.7062,0.4313 T0.7036,0.4398 T0.6993,0.4513 T0.6931,0.4619 T0.6851,0.4698 Q0.6573,0.4857,0.6336,0.4947 T0.5831,0.5053 Q0.577,0.5057,0.5615,0.5065 T0.5394,0.508 T0.5221,0.51 T0.5042,0.5139 T0.4905,0.5204 Q0.5008,0.5232,0.515,0.5328 T0.549,0.5585 T0.5799,0.5824 Q0.5905,0.5897,0.6013,0.5946 T0.6262,0.6036 T0.6409,0.6081 Q0.6567,0.6134,0.7035,0.605 T0.8028,0.5816 L0.7974,0.5922 T0.7867,0.6134 L0.7812,0.624 Q0.7799,0.6248,0.7786,0.6252 Q0.7709,0.6297,0.7573,0.6372 T0.7346,0.6499 T0.7123,0.6621 T0.6897,0.6741 T0.6686,0.6847 T0.648,0.6941 T0.6297,0.7013 T0.6129,0.7062 T0.5995,0.7076 Q0.5992,0.7076,0.5831,0.7092 T0.5529,0.7092 T0.525,0.7019 Q0.5105,0.6958,0.4824,0.6811 T0.4356,0.6586 T0.4079,0.6533 Q0.3872,0.6741,0.3827,0.6782 Q0.3872,0.6835,0.3934,0.6929 T0.4037,0.7078 T0.4142,0.7198 T0.4284,0.7304 T0.4466,0.7365 Q0.4824,0.7443,0.5292,0.728 L0.5292,0.7294 T0.529,0.7333 T0.5284,0.7392 T0.5266,0.7463 T0.5236,0.7539 T0.5186,0.7612 T0.5111,0.7679 Q0.5086,0.77,0.5056,0.7716 Q0.4466,0.8104,0.3591,0.8091 Q0.344,0.8091,0.3246,0.802 T0.2893,0.7863 T0.2543,0.77 T0.2206,0.76 T0.1894,0.7643 Q0.1804,0.7684,0.1733,0.7753 Q0.1591,0.7892,0.1601,0.8169 Q0.1784,0.8067,0.1938,0.8053 T0.2212,0.8087 T0.2507,0.825 Q0.2614,0.8324,0.2744,0.8365 T0.3053,0.844 T0.332,0.8499 L0.303,0.894 L0.2998,0.8948 Q0.2904,0.8968,0.278,0.9009 T0.2554,0.9086 T0.233,0.9158 T0.2099,0.9213 T0.1868,0.9229 T0.1633,0.9192 Q0.1517,0.916,0.1367,0.9241 T0.1065,0.9462 T0.0753,0.9737 T0.041,0.9955 T0.0061,1 Q0,0.9984,0.0068,0.9845 Q0.009,0.9804,0.0123,0.9755 Q0.0187,0.9666,0.0261,0.9606 T0.0374,0.9555 Q0.0481,0.958,0.0629,0.949 T0.0931,0.9262 T0.1247,0.8987 T0.1597,0.8777 T0.1949,0.8752 Q0.213,0.8805,0.2343,0.8772 Q0.2249,0.8736,0.2172,0.8683 Q0.1914,0.8511,0.1738,0.8497 T0.1349,0.8617 Q0.1304,0.865,0.1273,0.8646 T0.1242,0.8605 L0.1239,0.8536 Q0.1226,0.8295,0.1354,0.8016 T0.1723,0.7496 Q0.1914,0.7308,0.2104,0.7219 Q0.2259,0.7145,0.2425,0.7141 T0.273,0.7182 T0.3022,0.7296 T0.3301,0.7439 T0.3569,0.7571 T0.3821,0.7643 Q0.373,0.7561,0.3598,0.7367 T0.3382,0.7088 L0.3327,0.7035 Q0.3282,0.699,0.3353,0.6884 T0.3517,0.6692 L0.3611,0.6607 Q0.3711,0.657,0.3751,0.6554 T0.3837,0.6507 T0.3898,0.6458 T0.3979,0.637 T0.4111,0.6236 Q0.4172,0.6175,0.4234,0.6138 Q0.4327,0.6081,0.4439,0.6077 T0.4655,0.6105 T0.4884,0.6201 T0.5113,0.633 T0.5347,0.6468 T0.5573,0.6578 Q0.5705,0.6635,0.5833,0.665 T0.6112,0.665 T0.6286,0.6635 Q0.6421,0.6631,0.6718,0.6501 Q0.626,0.6578,0.6089,0.6517 Q0.6063,0.6509,0.6002,0.6491 T0.5884,0.6454 T0.5757,0.6409 T0.561,0.6344 T0.5466,0.6256 Q0.536,0.6183,0.5108,0.5989 T0.4722,0.5712 T0.4527,0.564 L0.4418,0.5661 Q0.4308,0.5681,0.4366,0.5563 Q0.4401,0.5485,0.4476,0.5392 L0.455,0.5298 Q0.4631,0.5188,0.4714,0.51 T0.4866,0.4947 T0.5021,0.4831 T0.516,0.4749 T0.53,0.4694 T0.5428,0.4657 T0.5558,0.4637 T0.5674,0.4629 T0.5795,0.4625 Q0.5489,0.4543,0.534,0.4384 L0.5286,0.4327 Q0.5244,0.4282,0.5318,0.4174 T0.5486,0.398 L0.5579,0.3899 Q0.567,0.387,0.5749,0.3834 T0.5878,0.3772 T0.6012,0.3687 T0.6126,0.3603 T0.6268,0.3487 T0.6421,0.3361 Q0.6567,0.3242,0.6712,0.3159 T0.7009,0.3034 T0.7277,0.2973 T0.7556,0.2961 T0.7809,0.2977 T0.8074,0.301 Q0.8096,0.3014,0.8109,0.3016 T0.8143,0.302 T0.818,0.3026 Q0.8012,0.2936,0.7767,0.2781 Q0.6718,0.2117,0.5744,0.2476 Q0.5637,0.2516,0.5492,0.2604 T0.5218,0.2779 T0.4923,0.2965 T0.461,0.3124 T0.4282,0.3216 T0.394,0.3206 Q0.3637,0.3136,0.3474,0.2936 T0.3353,0.2459 Q0.3385,0.2247,0.355,0.2023 Q0.3646,0.1892,0.3763,0.179 Q0.3898,0.1672,0.4063,0.1595 Q0.3927,0.1562,0.3785,0.1485 Q0.3604,0.1387,0.3453,0.1354 T0.3161,0.1354 T0.2899,0.1458 T0.2627,0.166 Q0.262,0.1664,0.254,0.1733 T0.2414,0.1841 T0.2267,0.196 T0.2094,0.2086 T0.1915,0.2196 T0.1725,0.2288 T0.1546,0.2337 T0.1373,0.2343 T0.1223,0.228 T0.1097,0.2145 Q0.0962,0.1929,0.1258,0.1582 Q0.131,0.1517,0.1371,0.1464 Q0.1462,0.1387,0.156,0.1321 T0.1752,0.1205 T0.1878,0.1134 Q0.1891,0.1117,0.1947,0.103 T0.2035,0.0899 T0.2125,0.0781 T0.2243,0.064 Q0.1949,0.0616,0.1672,0.0828 Q0.153,0.0934,0.1412,0.1009 T0.1146,0.1146 T0.0873,0.1213 T0.0616,0.116 T0.0374,0.0962 Q0.0339,0.0909,0.0295,0.0846 T0.024,0.0767 T0.0202,0.0728 T0.0148,0.0704 T0.0068,0.0697 Q0.0003,0.0693,0.0058,0.0567 Q0.0077,0.0522,0.011,0.0469 Z",type:0}},"":{type:5,data:{d:"M0.1934,0.6075 Q0.2012,0.612,0.2188,0.6218 T0.2459,0.6369 T0.2718,0.6508 T0.2983,0.6644 T0.322,0.6758 T0.3447,0.6854 T0.363,0.6911 T0.3783,0.6936 Q0.3796,0.6936,0.396,0.6952 T0.4265,0.6952 T0.4548,0.6879 Q0.4656,0.6834,0.4805,0.6756 T0.5073,0.6614 T0.5327,0.6485 T0.5564,0.6389 T0.5759,0.6363 T0.5909,0.6422 Q0.5953,0.6467,0.604,0.6554 T0.6155,0.6669 T0.6251,0.6746 T0.6387,0.683 T0.6556,0.6903 L0.6498,0.6956 Q0.6414,0.7042,0.6319,0.7177 T0.6162,0.7403 T0.5979,0.7583 T0.5703,0.7703 Q0.5313,0.7785,0.4799,0.7605 Q0.5097,0.7817,0.5508,0.7911 T0.6302,0.8001 Q0.6431,0.7997,0.659,0.7942 T0.6888,0.7813 T0.7194,0.766 T0.7506,0.753 T0.7819,0.7472 T0.8131,0.754 Q0.8239,0.7589,0.8346,0.7681 T0.8544,0.7895 T0.8689,0.818 T0.8737,0.8507 L0.8733,0.858 Q0.8591,0.849,0.8474,0.845 T0.8242,0.8421 T0.8021,0.8482 T0.7772,0.8625 Q0.7653,0.8707,0.7513,0.8752 T0.7184,0.8831 T0.6908,0.889 Q0.7017,0.8915,0.7152,0.8956 T0.7396,0.9037 T0.7633,0.9115 T0.7872,0.9174 T0.8107,0.9194 T0.8347,0.9155 Q0.8493,0.9115,0.8671,0.9196 T0.9015,0.9421 T0.9345,0.9708 T0.9682,0.9943 T0.9997,1 Q0.999,0.94,0.9983,0.6508 T0.9976,0.2322 Q0.9976,0.2109,0.9992,0.1379 T1,0.0249 Q0.9949,0.0249,0.9917,0.0257 T0.9863,0.0273 T0.9817,0.0308 T0.9776,0.0357 T0.9721,0.0437 T0.9651,0.0539 Q0.956,0.0657,0.9456,0.072 T0.9247,0.0789 T0.904,0.0775 T0.8823,0.0694 T0.862,0.0573 T0.8422,0.0432 Q0.7856,0,0.7328,0.0318 Q0.7392,0.0318,0.7452,0.0324 T0.756,0.0345 T0.7657,0.0386 T0.7741,0.0437 T0.7821,0.0506 T0.7894,0.0588 T0.7966,0.0683 T0.8039,0.0787 T0.8119,0.0904 T0.8202,0.1028 Q0.8249,0.1093,0.849,0.1234 T0.8811,0.1448 Q0.8998,0.1612,0.8893,0.1783 Q0.8835,0.1877,0.8759,0.1928 T0.8593,0.1979 T0.8417,0.1967 T0.8222,0.1895 T0.8034,0.1791 T0.7848,0.1663 T0.7687,0.1534 T0.7543,0.141 T0.744,0.1322 Q0.7186,0.1106,0.6984,0.101 T0.6559,0.0926 T0.6082,0.1077 Q0.5899,0.1175,0.5733,0.1206 T0.5411,0.1202 T0.5122,0.1098 T0.4822,0.0914 Q0.4927,0.1032,0.5036,0.1122 T0.5276,0.1285 T0.5503,0.1401 T0.5777,0.1518 T0.6055,0.1636 Q0.6258,0.1726,0.6375,0.1863 T0.6519,0.2162 Q0.6556,0.2403,0.6392,0.2605 T0.5926,0.2876 Q0.5804,0.2909,0.5672,0.2899 T0.5428,0.2856 T0.5193,0.2764 T0.4966,0.264 T0.4744,0.2497 T0.4523,0.2352 T0.4301,0.2222 T0.4077,0.2118 Q0.3021,0.173,0.1903,0.2436 Q0.1669,0.2583,0.1504,0.2674 T0.1129,0.2846 T0.075,0.294 T0.0384,0.2911 T0,0.2746 Q0.021,0.297,0.0459,0.3082 T0.0953,0.3207 T0.146,0.3193 T0.1988,0.3131 T0.2508,0.3095 T0.3029,0.3176 T0.3529,0.3448 Q0.3566,0.348,0.3652,0.355 T0.3771,0.3645 T0.3877,0.3731 T0.3993,0.3815 T0.4103,0.3884 T0.4226,0.3953 T0.4352,0.4009 T0.4497,0.4064 L0.4443,0.4121 Q0.4372,0.4198,0.4277,0.4251 T0.4072,0.4337 T0.3867,0.439 T0.3656,0.4431 T0.3481,0.4468 Q0.3241,0.4529,0.2943,0.4463 Q0.3227,0.4623,0.3458,0.471 T0.3959,0.4814 Q0.404,0.4818,0.4187,0.4825 T0.4409,0.4835 T0.4605,0.4857 T0.4809,0.4908 T0.4992,0.4996 T0.5186,0.5141 T0.5371,0.5353 L0.5449,0.5451 L0.5337,0.543 Q0.5242,0.541,0.5091,0.55 T0.4683,0.5796 T0.4314,0.6083 Q0.4203,0.6161,0.4086,0.6212 T0.382,0.6308 T0.3667,0.6353 Q0.3485,0.6418,0.2995,0.633 T0.1934,0.6075 Z",type:0}},"":{type:5,data:{d:"M0.989,0.0469 Q0.9829,0.0379,0.9753,0.0316 T0.9635,0.0253 Q0.9574,0.0257,0.9539,0.0263 T0.9466,0.03 T0.9418,0.0345 T0.9355,0.0434 T0.9277,0.0546 Q0.9193,0.0653,0.9103,0.0712 T0.8919,0.0775 T0.8745,0.0765 T0.8564,0.0695 T0.8396,0.0593 T0.8228,0.0473 Q0.7612,0,0.7073,0.0326 L0.7386,0.0767 L0.7419,0.0767 Q0.7506,0.0767,0.7556,0.0773 T0.7641,0.0789 T0.7709,0.0842 T0.7767,0.0922 T0.7854,0.1054 T0.7974,0.1236 Q0.8057,0.1354,0.8157,0.1444 T0.8325,0.1574 T0.8474,0.1654 T0.8571,0.1701 Q0.8564,0.1717,0.8554,0.1733 Q0.8503,0.1815,0.844,0.186 T0.8301,0.1909 T0.8153,0.1903 T0.7993,0.1845 T0.7836,0.1758 T0.768,0.1648 T0.754,0.1535 T0.741,0.1425 T0.7312,0.1338 Q0.7031,0.1101,0.6805,0.0995 T0.6357,0.0901 T0.5886,0.1048 Q0.5721,0.1138,0.5574,0.1168 T0.5295,0.117 T0.5055,0.1089 T0.4814,0.0942 L0.4789,0.0926 L0.4711,0.0922 Q0.4589,0.095,0.4811,0.1203 Q0.495,0.1358,0.5087,0.1474 T0.5366,0.167 T0.5611,0.1796 T0.5879,0.1909 T0.6134,0.2011 Q0.6218,0.2047,0.6273,0.2092 Q0.6283,0.2117,0.6286,0.2141 Q0.6305,0.2263,0.6249,0.2388 T0.606,0.2616 T0.5747,0.2761 Q0.5602,0.2785,0.5487,0.2779 T0.5245,0.2724 T0.5015,0.2616 T0.4784,0.2476 T0.4547,0.2321 T0.4297,0.217 T0.4024,0.2043 Q0.2985,0.166,0.1901,0.2345 Q0.1694,0.248,0.1523,0.2569 T0.1165,0.2726 T0.0818,0.2812 T0.0494,0.2796 T0.0181,0.2663 L0.0116,0.2671 Q0,0.272,0.0232,0.2969 Q0.0468,0.3222,0.0742,0.3365 T0.1278,0.3542 T0.1807,0.3564 T0.233,0.352 T0.2815,0.3473 T0.3269,0.3509 T0.3656,0.3699 Q0.4037,0.4005,0.4153,0.4082 Q0.405,0.4139,0.3921,0.4174 T0.3633,0.4237 T0.3414,0.4278 Q0.3207,0.4331,0.2956,0.4278 Q0.2943,0.4278,0.293,0.4274 Q0.2933,0.429,0.2938,0.4313 T0.2964,0.4398 T0.3007,0.4513 T0.3069,0.4619 T0.3149,0.4698 Q0.3427,0.4857,0.3664,0.4947 T0.4169,0.5053 Q0.423,0.5057,0.4385,0.5065 T0.4606,0.508 T0.4779,0.51 T0.4958,0.5139 T0.5095,0.5204 Q0.4992,0.5232,0.485,0.5328 T0.451,0.5585 T0.4201,0.5824 Q0.4095,0.5897,0.3987,0.5946 T0.3738,0.6036 T0.3591,0.6081 Q0.3433,0.6134,0.2965,0.605 T0.1972,0.5816 L0.2026,0.5922 T0.2133,0.6134 L0.2188,0.624 Q0.2201,0.6248,0.2214,0.6252 Q0.2291,0.6297,0.2486,0.6405 T0.2786,0.657 T0.3066,0.6721 T0.3353,0.6866 T0.3598,0.6972 T0.383,0.7051 T0.4005,0.7076 Q0.4008,0.7076,0.4169,0.7092 T0.4471,0.7092 T0.475,0.7019 Q0.4895,0.6958,0.5176,0.6811 T0.5644,0.6586 T0.5921,0.6533 Q0.6128,0.6741,0.6173,0.6782 Q0.6131,0.6835,0.6068,0.6929 T0.5963,0.7078 T0.5858,0.7198 T0.5716,0.7304 T0.5534,0.7365 Q0.5179,0.7443,0.4708,0.728 L0.4708,0.7294 T0.471,0.7333 T0.4716,0.7392 T0.4734,0.7463 T0.4764,0.7539 T0.4814,0.7612 T0.4889,0.7679 Q0.4914,0.77,0.4944,0.7716 Q0.5534,0.8104,0.6409,0.8091 Q0.6521,0.8091,0.6654,0.8053 T0.6907,0.7959 T0.7157,0.7838 T0.741,0.772 T0.7652,0.7631 T0.7888,0.7594 T0.8106,0.7643 Q0.8196,0.7684,0.8267,0.7753 Q0.8409,0.7892,0.8399,0.8169 Q0.8216,0.8067,0.8062,0.8053 T0.7788,0.8087 T0.7493,0.825 Q0.7386,0.8324,0.7256,0.8365 T0.6947,0.844 T0.668,0.8499 L0.697,0.894 L0.7002,0.8948 Q0.7099,0.8968,0.7222,0.9009 T0.7446,0.9086 T0.767,0.9158 T0.7901,0.9213 T0.8132,0.9229 T0.8367,0.9192 Q0.8483,0.916,0.8633,0.9241 T0.8937,0.9462 T0.9248,0.9737 T0.959,0.9955 T0.9939,1 Q1,0.9984,0.9932,0.9845 Q0.991,0.9804,0.9877,0.9755 Q0.9813,0.9666,0.9739,0.9606 T0.9626,0.9555 Q0.9519,0.958,0.9372,0.949 T0.9071,0.9262 T0.8753,0.8987 T0.8403,0.8777 T0.8051,0.8752 Q0.787,0.8805,0.7657,0.8772 Q0.7751,0.8736,0.7828,0.8683 Q0.8086,0.8511,0.8262,0.8497 T0.8651,0.8617 Q0.8696,0.865,0.8727,0.8646 T0.8758,0.8605 L0.8761,0.8536 Q0.8774,0.8295,0.8646,0.8016 T0.8277,0.7496 Q0.8086,0.7308,0.7896,0.7219 Q0.7741,0.7145,0.7575,0.7141 T0.727,0.7182 T0.6978,0.7296 T0.6699,0.7439 T0.6433,0.7571 T0.6179,0.7643 Q0.627,0.7561,0.6402,0.7367 T0.6618,0.7088 L0.6673,0.7035 Q0.6718,0.699,0.6647,0.6884 T0.6483,0.6692 L0.6389,0.6607 Q0.6289,0.657,0.6249,0.6554 T0.6163,0.6507 T0.6102,0.6458 T0.6021,0.637 T0.5889,0.6236 Q0.5828,0.6175,0.5766,0.6138 Q0.5673,0.6081,0.5561,0.6077 T0.5345,0.6105 T0.5116,0.6201 T0.4887,0.633 T0.4653,0.6468 T0.4427,0.6578 Q0.4295,0.6635,0.4167,0.665 T0.3888,0.665 T0.3714,0.6635 Q0.3579,0.6631,0.3282,0.6501 Q0.374,0.6578,0.3911,0.6517 Q0.3937,0.6509,0.4024,0.6482 T0.4179,0.6434 T0.4348,0.6364 T0.4534,0.6256 Q0.464,0.6183,0.4892,0.5989 T0.5278,0.5712 T0.5476,0.564 L0.5582,0.5661 Q0.5692,0.5681,0.5634,0.5563 Q0.5599,0.5485,0.5524,0.5392 L0.545,0.5298 Q0.5369,0.5188,0.5286,0.51 T0.5134,0.4947 T0.4979,0.4831 T0.484,0.4749 T0.47,0.4694 T0.4572,0.4657 T0.4442,0.4637 T0.4326,0.4629 T0.4205,0.4625 Q0.4511,0.4543,0.466,0.4384 L0.4714,0.4327 Q0.4795,0.4237,0.4469,0.394 L0.4421,0.3899 Q0.433,0.387,0.4251,0.3834 T0.4122,0.3772 T0.3988,0.3687 T0.3874,0.3603 T0.3732,0.3487 T0.3579,0.3361 Q0.3433,0.3242,0.3288,0.3159 T0.2993,0.3034 T0.2725,0.2973 T0.2446,0.2961 T0.2193,0.2977 T0.1926,0.301 Q0.1855,0.3022,0.182,0.3026 Q0.1991,0.2936,0.2233,0.2781 Q0.3282,0.2117,0.4256,0.2476 Q0.4363,0.2516,0.451,0.2604 T0.4784,0.2779 T0.5077,0.2965 T0.539,0.3124 T0.5718,0.3216 T0.606,0.3206 Q0.6363,0.3136,0.6526,0.2936 T0.6647,0.2459 Q0.6615,0.2247,0.645,0.2023 Q0.6354,0.1892,0.6237,0.179 Q0.6102,0.1672,0.5937,0.1595 Q0.6073,0.1562,0.6215,0.1485 Q0.6396,0.1387,0.6547,0.1354 T0.6839,0.1354 T0.7101,0.1458 T0.7373,0.166 Q0.738,0.1664,0.746,0.1733 T0.7586,0.1841 T0.7733,0.196 T0.7907,0.2086 T0.8086,0.2196 T0.8275,0.2288 T0.8454,0.2337 T0.8627,0.2343 T0.8777,0.228 T0.8903,0.2145 Q0.9038,0.1929,0.8742,0.1582 Q0.869,0.1517,0.8629,0.1464 Q0.8538,0.1387,0.844,0.1321 T0.8248,0.1205 T0.8122,0.1134 Q0.8109,0.1117,0.8053,0.103 T0.7965,0.0899 T0.7875,0.0781 T0.7757,0.064 Q0.8051,0.0616,0.8328,0.0828 Q0.847,0.0934,0.8588,0.1009 T0.8854,0.1146 T0.9127,0.1213 T0.9384,0.116 T0.9626,0.0962 Q0.9664,0.0909,0.9706,0.0846 T0.976,0.0767 T0.9798,0.0728 T0.9852,0.0704 T0.9932,0.0697 Q0.9997,0.0693,0.9942,0.0567 Q0.9923,0.0522,0.989,0.0469 Z",type:0}},"":{type:5,data:{d:"M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0.9099,0.1158 L0.9099,0.1888 L0.9951,0.1888 L0.9951,0.1158 L0.9099,0.1158 Z M0.9075,0.1116 L0.9976,0.1116 Q1,0.1116,1,0.1137 L1,0.1909 Q1,0.193,0.9976,0.193 L0.9075,0.193 Q0.905,0.193,0.905,0.1909 L0.905,0.1137 Q0.905,0.1116,0.9075,0.1116 Z M0.7852,0.6086 L0.7852,0.682 L0.8704,0.682 L0.8704,0.6086 L0.7852,0.6086 Z M0.7828,0.6044 L0.8729,0.6044 Q0.8753,0.6044,0.8753,0.6065 L0.8753,0.6841 Q0.8753,0.6862,0.8729,0.6862 L0.7828,0.6862 Q0.7803,0.6862,0.7803,0.6841 L0.7803,0.6065 Q0.7803,0.6044,0.7828,0.6044 Z M0.66,0.1137 L0.7501,0.1137 L0.7501,0.1909 L0.66,0.1909 L0.66,0.1137 Z M0.6624,0.4102 L0.6624,0.4836 L0.7477,0.4836 L0.7477,0.4102 L0.6624,0.4102 Z M0.66,0.406 L0.7501,0.406 Q0.7526,0.406,0.7526,0.4081 L0.7526,0.4857 Q0.7526,0.4878,0.7501,0.4878 L0.66,0.4878 Q0.6576,0.4878,0.6576,0.4857 L0.6576,0.4081 Q0.6576,0.406,0.66,0.406 Z M0.6624,0.7131 L0.6624,0.7865 L0.7477,0.7865 L0.7477,0.7131 L0.6624,0.7131 Z M0.66,0.7089 L0.7501,0.7089 Q0.7526,0.7089,0.7526,0.711 L0.7526,0.7886 Q0.7526,0.7907,0.7501,0.7907 L0.66,0.7907 Q0.6576,0.7907,0.6576,0.7886 L0.6576,0.711 Q0.6576,0.7089,0.66,0.7089 Z M0.5407,0.2227 L0.5407,0.2836 L0.6113,0.2836 L0.6113,0.2227 L0.5407,0.2227 Z M0.5309,0.206 L0.621,0.206 Q0.623,0.206,0.6249,0.2068 T0.6281,0.2089 T0.6301,0.2116 T0.6308,0.2143 L0.6308,0.2919 Q0.6308,0.2957,0.6276,0.298 T0.621,0.3003 L0.5309,0.3003 Q0.5265,0.3003,0.5239,0.2976 T0.5212,0.2919 L0.5212,0.2143 Q0.5212,0.211,0.5244,0.2085 T0.5309,0.206 Z M0.5407,0.3184 L0.5407,0.3792 L0.6113,0.3792 L0.6113,0.3184 L0.5407,0.3184 Z M0.5309,0.3016 L0.621,0.3016 Q0.6254,0.3016,0.6281,0.3043 T0.6308,0.31 L0.6308,0.3876 Q0.6308,0.3914,0.6276,0.3937 T0.621,0.396 L0.5309,0.396 Q0.5265,0.396,0.5239,0.3932 T0.5212,0.3876 L0.5212,0.31 Q0.5212,0.3062,0.5244,0.3039 T0.5309,0.3016 Z M0.5309,0.4081 L0.621,0.4081 L0.621,0.4857 L0.5309,0.4857 L0.5309,0.4081 Z M0.5309,0.5059 L0.621,0.5059 L0.621,0.5835 L0.5309,0.5835 L0.5309,0.5059 Z M0.5407,0.6149 L0.5407,0.6758 L0.6113,0.6758 L0.6113,0.6149 L0.5407,0.6149 Z M0.5309,0.5982 L0.621,0.5982 Q0.6254,0.5982,0.6281,0.6009 T0.6308,0.6065 L0.6308,0.6841 Q0.6308,0.6879,0.6276,0.6902 T0.621,0.6925 L0.5309,0.6925 Q0.5265,0.6925,0.5239,0.6898 T0.5212,0.6841 L0.5212,0.6065 Q0.5212,0.6028,0.5244,0.6005 T0.5309,0.5982 Z M0.4062,0.1137 L0.4963,0.1137 L0.4963,0.1909 L0.4062,0.1909 L0.4062,0.1137 Z M0.4062,0.2143 L0.4963,0.2143 L0.4963,0.2919 L0.4062,0.2919 L0.4062,0.2143 Z M0.4184,0.4186 L0.4184,0.4753 L0.4842,0.4753 L0.4842,0.4186 L0.4184,0.4186 Z M0.4062,0.3977 L0.4963,0.3977 Q0.5017,0.3977,0.5051,0.401 T0.5085,0.4081 L0.5085,0.4857 Q0.5085,0.4904,0.5046,0.4933 T0.4963,0.4962 L0.4062,0.4962 Q0.4009,0.4962,0.3975,0.4929 T0.3941,0.4857 L0.3941,0.4081 Q0.3941,0.4035,0.398,0.4006 T0.4062,0.3977 Z M0.4062,0.5059 L0.4963,0.5059 L0.4963,0.5835 L0.4062,0.5835 L0.4062,0.5059 Z M0.4087,0.7131 L0.4087,0.7865 L0.4939,0.7865 L0.4939,0.7131 L0.4087,0.7131 Z M0.4062,0.7089 L0.4963,0.7089 Q0.4988,0.7089,0.4988,0.711 L0.4988,0.7886 Q0.4988,0.7907,0.4963,0.7907 L0.4062,0.7907 Q0.4038,0.7907,0.4038,0.7886 L0.4038,0.711 Q0.4038,0.7089,0.4062,0.7089 Z M0.2689,0.1137 L0.359,0.1137 L0.359,0.1909 L0.2689,0.1909 L0.2689,0.1137 Z M0.2689,0.2143 L0.359,0.2143 L0.359,0.2919 L0.2689,0.2919 L0.2689,0.2143 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.31 L0.359,0.31 L0.359,0.3876 L0.2689,0.3876 L0.2689,0.31 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.4081 L0.359,0.4081 L0.359,0.4857 L0.2689,0.4857 L0.2689,0.4081 Z M0.2689,0.5059 L0.359,0.5059 L0.359,0.5835 L0.2689,0.5835 L0.2689,0.5059 Z M0.2689,0.711 L0.359,0.711 L0.359,0.7886 L0.2689,0.7886 L0.2689,0.711 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.1374,0.1137 L0.2275,0.1137 L0.2275,0.1909 L0.1374,0.1909 L0.1374,0.1137 Z M0.1374,0.2143 L0.2275,0.2143 L0.2275,0.2919 L0.1374,0.2919 L0.1374,0.2143 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.31 L0.2275,0.31 L0.2275,0.3876 L0.1374,0.3876 L0.1374,0.31 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.4081 L0.2275,0.4081 L0.2275,0.4857 L0.1374,0.4857 L0.1374,0.4081 Z M0.1374,0.5059 L0.2275,0.5059 L0.2275,0.5835 L0.1374,0.5835 L0.1374,0.5059 Z M0.1374,0.6065 L0.2275,0.6065 L0.2275,0.6841 L0.1374,0.6841 L0.1374,0.6065 Z M0.1374,0.711 L0.2275,0.711 L0.2275,0.7886 L0.1374,0.7886 L0.1374,0.711 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0,0.1137 L0.0901,0.1137 L0.0901,0.1909 L0,0.1909 L0,0.1137 Z M0,0.2143 L0.0901,0.2143 L0.0901,0.2919 L0,0.2919 L0,0.2143 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.31 L0.0901,0.31 L0.0901,0.3876 L0,0.3876 L0,0.31 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.4081 L0.0901,0.4081 L0.0901,0.4857 L0,0.4857 L0,0.4081 Z M0,0.5059 L0.0901,0.5059 L0.0901,0.5835 L0,0.5835 L0,0.5059 Z M0,0.6065 L0.0901,0.6065 L0.0901,0.6841 L0,0.6841 L0,0.6065 Z M0,0.711 L0.0901,0.711 L0.0901,0.7886 L0,0.7886 L0,0.711 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z",type:0}},"":{type:5,data:{d:"M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M0.0901,0.1158 L0.0901,0.1888 L0.0049,0.1888 L0.0049,0.1158 L0.0901,0.1158 Z M0.0925,0.1116 L0.0024,0.1116 Q0,0.1116,0,0.1137 L0,0.1909 Q0,0.193,0.0024,0.193 L0.0925,0.193 Q0.095,0.193,0.095,0.1909 L0.095,0.1137 Q0.095,0.1116,0.0925,0.1116 Z M0.2148,0.6086 L0.2148,0.682 L0.1296,0.682 L0.1296,0.6086 L0.2148,0.6086 Z M0.2172,0.6044 L0.1271,0.6044 Q0.1247,0.6044,0.1247,0.6065 L0.1247,0.6841 Q0.1247,0.6862,0.1271,0.6862 L0.2172,0.6862 Q0.2197,0.6862,0.2197,0.6841 L0.2197,0.6065 Q0.2197,0.6044,0.2172,0.6044 Z M0.34,0.1137 L0.2499,0.1137 L0.2499,0.1909 L0.34,0.1909 L0.34,0.1137 Z M0.3376,0.4102 L0.3376,0.4836 L0.2523,0.4836 L0.2523,0.4102 L0.3376,0.4102 Z M0.34,0.406 L0.2499,0.406 Q0.2474,0.406,0.2474,0.4081 L0.2474,0.4857 Q0.2474,0.4878,0.2499,0.4878 L0.34,0.4878 Q0.3424,0.4878,0.3424,0.4857 L0.3424,0.4081 Q0.3424,0.406,0.34,0.406 Z M0.3376,0.7131 L0.3376,0.7865 L0.2523,0.7865 L0.2523,0.7131 L0.3376,0.7131 Z M0.34,0.7089 L0.2499,0.7089 Q0.2474,0.7089,0.2474,0.711 L0.2474,0.7886 Q0.2474,0.7907,0.2499,0.7907 L0.34,0.7907 Q0.3424,0.7907,0.3424,0.7886 L0.3424,0.711 Q0.3424,0.7089,0.34,0.7089 Z M0.4593,0.2227 L0.4593,0.2836 L0.3887,0.2836 L0.3887,0.2227 L0.4593,0.2227 Z M0.4691,0.206 L0.379,0.206 Q0.3746,0.206,0.3719,0.2087 T0.3692,0.2143 L0.3692,0.2919 Q0.3692,0.2957,0.3724,0.298 T0.379,0.3003 L0.4691,0.3003 Q0.4735,0.3003,0.4761,0.2976 T0.4788,0.2919 L0.4788,0.2143 Q0.4788,0.211,0.4756,0.2085 T0.4691,0.206 Z M0.4593,0.3184 L0.4593,0.3792 L0.3887,0.3792 L0.3887,0.3184 L0.4593,0.3184 Z M0.4691,0.3016 L0.379,0.3016 Q0.3746,0.3016,0.3719,0.3043 T0.3692,0.31 L0.3692,0.3876 Q0.3692,0.3914,0.3724,0.3937 T0.379,0.396 L0.4691,0.396 Q0.4735,0.396,0.4761,0.3932 T0.4788,0.3876 L0.4788,0.31 Q0.4788,0.3062,0.4756,0.3039 T0.4691,0.3016 Z M0.4691,0.4081 L0.379,0.4081 L0.379,0.4857 L0.4691,0.4857 L0.4691,0.4081 Z M0.4691,0.5059 L0.379,0.5059 L0.379,0.5835 L0.4691,0.5835 L0.4691,0.5059 Z M0.4593,0.6149 L0.4593,0.6758 L0.3887,0.6758 L0.3887,0.6149 L0.4593,0.6149 Z M0.4691,0.5982 L0.379,0.5982 Q0.3746,0.5982,0.3719,0.6009 T0.3692,0.6065 L0.3692,0.6841 Q0.3692,0.6879,0.3724,0.6902 T0.379,0.6925 L0.4691,0.6925 Q0.4735,0.6925,0.4761,0.6898 T0.4788,0.6841 L0.4788,0.6065 Q0.4788,0.6028,0.4756,0.6005 T0.4691,0.5982 Z M0.5938,0.1137 L0.5037,0.1137 L0.5037,0.1909 L0.5938,0.1909 L0.5938,0.1137 Z M0.5938,0.2143 L0.5037,0.2143 L0.5037,0.2919 L0.5938,0.2919 L0.5938,0.2143 Z M0.5816,0.4186 L0.5816,0.4753 L0.5158,0.4753 L0.5158,0.4186 L0.5816,0.4186 Z M0.5938,0.3977 L0.5037,0.3977 Q0.4983,0.3977,0.4949,0.401 T0.4915,0.4081 L0.4915,0.4857 Q0.4915,0.4904,0.4954,0.4933 T0.5037,0.4962 L0.5938,0.4962 Q0.5991,0.4962,0.6025,0.4929 T0.6059,0.4857 L0.6059,0.4081 Q0.6059,0.4035,0.602,0.4006 T0.5938,0.3977 Z M0.5938,0.5059 L0.5037,0.5059 L0.5037,0.5835 L0.5938,0.5835 L0.5938,0.5059 Z M0.5913,0.7131 L0.5913,0.7865 L0.5061,0.7865 L0.5061,0.7131 L0.5913,0.7131 Z M0.5938,0.7089 L0.5037,0.7089 Q0.5012,0.7089,0.5012,0.711 L0.5012,0.7886 Q0.5012,0.7907,0.5037,0.7907 L0.5938,0.7907 Q0.5962,0.7907,0.5962,0.7886 L0.5962,0.711 Q0.5962,0.7089,0.5938,0.7089 Z M0.7311,0.1137 L0.641,0.1137 L0.641,0.1909 L0.7311,0.1909 L0.7311,0.1137 Z M0.7311,0.2143 L0.641,0.2143 L0.641,0.2919 L0.7311,0.2919 L0.7311,0.2143 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.31 L0.641,0.31 L0.641,0.3876 L0.7311,0.3876 L0.7311,0.31 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.4081 L0.641,0.4081 L0.641,0.4857 L0.7311,0.4857 L0.7311,0.4081 Z M0.7311,0.5059 L0.641,0.5059 L0.641,0.5835 L0.7311,0.5835 L0.7311,0.5059 Z M0.7311,0.711 L0.641,0.711 L0.641,0.7886 L0.7311,0.7886 L0.7311,0.711 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.8626,0.1137 L0.7725,0.1137 L0.7725,0.1909 L0.8626,0.1909 L0.8626,0.1137 Z M0.8626,0.2143 L0.7725,0.2143 L0.7725,0.2919 L0.8626,0.2919 L0.8626,0.2143 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.31 L0.7725,0.31 L0.7725,0.3876 L0.8626,0.3876 L0.8626,0.31 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.4081 L0.7725,0.4081 L0.7725,0.4857 L0.8626,0.4857 L0.8626,0.4081 Z M0.8626,0.5059 L0.7725,0.5059 L0.7725,0.5835 L0.8626,0.5835 L0.8626,0.5059 Z M0.8626,0.6065 L0.7725,0.6065 L0.7725,0.6841 L0.8626,0.6841 L0.8626,0.6065 Z M0.8626,0.711 L0.7725,0.711 L0.7725,0.7886 L0.8626,0.7886 L0.8626,0.711 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M1,0.1137 L0.9099,0.1137 L0.9099,0.1909 L1,0.1909 L1,0.1137 Z M1,0.2143 L0.9099,0.2143 L0.9099,0.2919 L1,0.2919 L1,0.2143 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.31 L0.9099,0.31 L0.9099,0.3876 L1,0.3876 L1,0.31 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.4081 L0.9099,0.4081 L0.9099,0.4857 L1,0.4857 L1,0.4081 Z M1,0.5059 L0.9099,0.5059 L0.9099,0.5835 L1,0.5835 L1,0.5059 Z M1,0.6065 L0.9099,0.6065 L0.9099,0.6841 L1,0.6841 L1,0.6065 Z M1,0.711 L0.9099,0.711 L0.9099,0.7886 L1,0.7886 L1,0.711 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z",type:0}},"":{type:5,data:{d:"M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0.8574,0.3217 L0.8574,0.4098 L0.9691,0.4098 L0.9691,0.3217 L0.8574,0.3217 Z M0.8419,0.2978 L0.9846,0.2978 Q0.9892,0.2978,0.9928,0.2997 T0.9982,0.3043 T1,0.31 L1,0.4216 Q1,0.427,0.9951,0.4304 T0.9846,0.4337 L0.8419,0.4337 Q0.8352,0.4337,0.8308,0.4299 T0.8265,0.4216 L0.8265,0.31 Q0.8265,0.3062,0.829,0.3035 T0.835,0.2993 T0.8419,0.2978 Z M0.8574,0.888 L0.8574,0.9757 L0.9691,0.9757 L0.9691,0.888 L0.8574,0.888 Z M0.8419,0.8637 L0.9846,0.8637 Q0.9892,0.8637,0.9928,0.8658 T0.9982,0.8706 T1,0.8758 L1,0.9878 Q1,0.9929,0.9951,0.9964 T0.9846,1 L0.8419,1 Q0.8352,1,0.8308,0.996 T0.8265,0.9878 L0.8265,0.8758 Q0.8265,0.8708,0.8314,0.8672 T0.8419,0.8637 Z M0.6509,0.169 L0.6509,0.2706 L0.7806,0.2706 L0.7806,0.169 L0.6509,0.169 Z M0.6442,0.159 L0.7868,0.159 Q0.7899,0.159,0.7917,0.1604 T0.7935,0.164 L0.7935,0.2756 Q0.7935,0.2781,0.7915,0.2796 T0.7868,0.281 L0.6442,0.281 Q0.6416,0.281,0.6395,0.2794 T0.6375,0.2756 L0.6375,0.164 Q0.6375,0.1619,0.6398,0.1604 T0.6442,0.159 Z M0.6632,0.6044 L0.6632,0.6858 L0.7678,0.6858 L0.7678,0.6044 L0.6632,0.6044 Z M0.6442,0.5742 L0.7868,0.5742 Q0.7951,0.5742,0.8007,0.5791 T0.8064,0.5893 L0.8064,0.7013 Q0.8064,0.7076,0.8002,0.712 T0.7868,0.7164 L0.6442,0.7164 Q0.6359,0.7164,0.6305,0.7114 T0.6251,0.7013 L0.6251,0.5893 Q0.6251,0.5847,0.6282,0.5812 T0.6357,0.5759 T0.6442,0.5742 Z M0.4264,0.164 L0.5695,0.164 L0.5695,0.2756 L0.4264,0.2756 L0.4264,0.164 Z M0.4325,0.315 L0.4325,0.4165 L0.5628,0.4165 L0.5628,0.315 L0.4325,0.315 Z M0.4264,0.3049 L0.5695,0.3049 Q0.5721,0.3049,0.5739,0.3064 T0.5757,0.31 L0.5757,0.4216 Q0.5757,0.4237,0.5736,0.4251 T0.5695,0.4266 L0.4264,0.4266 Q0.4238,0.4266,0.422,0.4251 T0.4202,0.4216 L0.4202,0.31 Q0.4202,0.3087,0.4207,0.3077 T0.4222,0.306 T0.4243,0.3052 T0.4264,0.3049 Z M0.4264,0.4476 L0.5695,0.4476 L0.5695,0.5596 L0.4264,0.5596 L0.4264,0.4476 Z M0.4392,0.7408 L0.4392,0.8326 L0.5561,0.8326 L0.5561,0.7408 L0.4392,0.7408 Z M0.4264,0.7206 L0.5695,0.7206 Q0.5747,0.7206,0.5783,0.724 T0.5819,0.7307 L0.5819,0.8427 Q0.5819,0.8469,0.5778,0.8498 T0.5695,0.8528 L0.4264,0.8528 Q0.4207,0.8528,0.4171,0.8496 T0.4135,0.8427 L0.4135,0.7307 Q0.4135,0.7265,0.4176,0.7236 T0.4264,0.7206 Z M0.2178,0.164 L0.3605,0.164 L0.3605,0.2756 L0.2178,0.2756 L0.2178,0.164 Z M0.2178,0.31 L0.3605,0.31 L0.3605,0.4216 L0.2178,0.4216 L0.2178,0.31 Z M0.2178,0.5893 L0.3605,0.5893 L0.3605,0.7013 L0.2178,0.7013 L0.2178,0.5893 Z M0.2178,0.7307 L0.3605,0.7307 L0.3605,0.8427 L0.2178,0.8427 L0.2178,0.7307 Z M0.2178,0.8758 L0.3605,0.8758 L0.3605,0.9878 L0.2178,0.9878 L0.2178,0.8758 Z M0,0.164 L0.1432,0.164 L0.1432,0.2756 L0,0.2756 L0,0.164 Z M0,0.31 L0.1432,0.31 L0.1432,0.4216 L0,0.4216 L0,0.31 Z M0,0.4476 L0.1432,0.4476 L0.1432,0.5596 L0,0.5596 L0,0.4476 Z M0,0.5893 L0.1432,0.5893 L0.1432,0.7013 L0,0.7013 L0,0.5893 Z M0,0.7307 L0.1432,0.7307 L0.1432,0.8427 L0,0.8427 L0,0.7307 Z M0,0.8758 L0.1432,0.8758 L0.1432,0.9878 L0,0.9878 L0,0.8758 Z",type:0}},"":{type:5,data:{d:"M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M0.1432,0.3217 L0.1432,0.4098 L0.0309,0.4098 L0.0309,0.3217 L0.1432,0.3217 Z M0.1586,0.2978 L0.0154,0.2978 Q0.0088,0.2978,0.0044,0.3016 T0,0.31 L0,0.4216 Q0,0.427,0.0049,0.4304 T0.0154,0.4337 L0.1586,0.4337 Q0.1648,0.4337,0.1694,0.4299 T0.174,0.4216 L0.174,0.31 Q0.174,0.3062,0.1715,0.3035 T0.1653,0.2993 T0.1586,0.2978 Z M0.1432,0.888 L0.1432,0.9757 L0.0309,0.9757 L0.0309,0.888 L0.1432,0.888 Z M0.1586,0.8637 L0.0154,0.8637 Q0.0108,0.8637,0.0072,0.8658 T0.0018,0.8706 T0,0.8758 L0,0.9878 Q0,0.9929,0.0049,0.9964 T0.0154,1 L0.1586,1 Q0.1648,1,0.1694,0.996 T0.174,0.9878 L0.174,0.8758 Q0.174,0.8725,0.1715,0.8695 T0.1653,0.8651 T0.1586,0.8637 Z M0.3496,0.169 L0.3496,0.2706 L0.2194,0.2706 L0.2194,0.169 L0.3496,0.169 Z M0.3563,0.159 L0.2132,0.159 Q0.2106,0.159,0.2088,0.1604 T0.207,0.164 L0.207,0.2756 Q0.207,0.2781,0.2091,0.2796 T0.2132,0.281 L0.3563,0.281 Q0.3589,0.281,0.3607,0.2794 T0.3625,0.2756 L0.3625,0.164 Q0.3625,0.1619,0.3605,0.1604 T0.3563,0.159 Z M0.3368,0.6044 L0.3368,0.6858 L0.2327,0.6858 L0.2327,0.6044 L0.3368,0.6044 Z M0.3563,0.5742 L0.2132,0.5742 Q0.2049,0.5742,0.1993,0.5791 T0.1936,0.5893 L0.1936,0.7013 Q0.1936,0.7076,0.2001,0.712 T0.2132,0.7164 L0.3563,0.7164 Q0.3641,0.7164,0.3697,0.7114 T0.3754,0.7013 L0.3754,0.5893 Q0.3754,0.5847,0.3723,0.5812 T0.3648,0.5759 T0.3563,0.5742 Z M0.5736,0.164 L0.431,0.164 L0.431,0.2756 L0.5736,0.2756 L0.5736,0.164 Z M0.5675,0.315 L0.5675,0.4165 L0.4377,0.4165 L0.4377,0.315 L0.5675,0.315 Z M0.5736,0.3049 L0.431,0.3049 Q0.4284,0.3049,0.4264,0.3064 T0.4243,0.31 L0.4243,0.4216 Q0.4243,0.4237,0.4266,0.4251 T0.431,0.4266 L0.5736,0.4266 Q0.5767,0.4266,0.5785,0.4251 T0.5803,0.4216 L0.5803,0.31 Q0.5803,0.3087,0.5798,0.3077 T0.5783,0.306 T0.5762,0.3052 T0.5736,0.3049 Z M0.5736,0.4476 L0.431,0.4476 L0.431,0.5596 L0.5736,0.5596 L0.5736,0.4476 Z M0.5613,0.7408 L0.5613,0.8326 L0.4439,0.8326 L0.4439,0.7408 L0.5613,0.7408 Z M0.5736,0.7206 L0.431,0.7206 Q0.4253,0.7206,0.4217,0.724 T0.4181,0.7307 L0.4181,0.8427 Q0.4181,0.8469,0.4222,0.8498 T0.431,0.8528 L0.5736,0.8528 Q0.5778,0.8528,0.5808,0.8511 T0.5855,0.8471 T0.587,0.8427 L0.587,0.7307 Q0.587,0.7265,0.5826,0.7236 T0.5736,0.7206 Z M0.7827,0.164 L0.6395,0.164 L0.6395,0.2756 L0.7827,0.2756 L0.7827,0.164 Z M0.7827,0.31 L0.6395,0.31 L0.6395,0.4216 L0.7827,0.4216 L0.7827,0.31 Z M0.7827,0.5893 L0.6395,0.5893 L0.6395,0.7013 L0.7827,0.7013 L0.7827,0.5893 Z M0.7827,0.7307 L0.6395,0.7307 L0.6395,0.8427 L0.7827,0.8427 L0.7827,0.7307 Z M0.7827,0.8758 L0.6395,0.8758 L0.6395,0.9878 L0.7827,0.9878 L0.7827,0.8758 Z M1,0.164 L0.8574,0.164 L0.8574,0.2756 L1,0.2756 L1,0.164 Z M1,0.31 L0.8574,0.31 L0.8574,0.4216 L1,0.4216 L1,0.31 Z M1,0.4476 L0.8574,0.4476 L0.8574,0.5596 L1,0.5596 L1,0.4476 Z M1,0.5893 L0.8574,0.5893 L0.8574,0.7013 L1,0.7013 L1,0.5893 Z M1,0.7307 L0.8574,0.7307 L0.8574,0.8427 L1,0.8427 L1,0.7307 Z M1,0.8758 L0.8574,0.8758 L0.8574,0.9878 L1,0.9878 L1,0.8758 Z",type:0}},"":{type:5,data:{d:"M0.0008,0.9979 Q0.0008,1,0.0032,1 Q0.0482,1,0.0602,0.9975 Q0.0667,0.9963,0.0847,0.9917 T0.1084,0.9859 Q0.1124,0.9851,0.1221,0.9851 L0.1253,0.9851 L0.1285,0.9851 Q0.1518,0.9851,0.1655,0.9809 Q0.1695,0.9801,0.1731,0.9788 T0.1791,0.9766 T0.1839,0.9747 T0.1888,0.9732 L0.1936,0.972 T0.1988,0.9712 T0.2048,0.971 L0.2096,0.971 Q0.2201,0.9714,0.2233,0.9714 T0.2345,0.9693 Q0.2386,0.9685,0.2502,0.9674 T0.2715,0.966 L0.2811,0.9656 Q0.2916,0.9647,0.2916,0.9573 Q0.2916,0.954,0.2892,0.9515 T0.2819,0.9486 Q0.2755,0.9482,0.2683,0.9482 Q0.2643,0.9482,0.2582,0.9484 T0.2498,0.9486 Q0.2426,0.9486,0.2402,0.9473 Q0.2386,0.9469,0.2317,0.943 T0.2161,0.937 Q0.2104,0.9357,0.1992,0.933 T0.1831,0.9291 Q0.1807,0.9287,0.1783,0.9285 T0.1739,0.928 T0.1703,0.9276 T0.1675,0.9272 T0.1655,0.9266 T0.1639,0.9253 Q0.1598,0.9216,0.1462,0.9216 L0.143,0.9216 L0.1398,0.9216 L0.1365,0.9216 Q0.1197,0.9195,0.1197,0.9154 L0.1197,0.9154 Q0.1205,0.9121,0.1373,0.9088 T0.1703,0.9054 Q0.1791,0.9054,0.1863,0.904 T0.1984,0.9011 T0.2064,0.8992 Q0.2402,0.8967,0.2506,0.8934 Q0.2538,0.8926,0.2602,0.8899 T0.2707,0.8872 Q0.2715,0.8872,0.2731,0.8876 Q0.2795,0.8884,0.2884,0.8884 Q0.3068,0.8884,0.3092,0.8826 L0.3092,0.8797 Q0.3092,0.8772,0.3076,0.8747 T0.3044,0.8706 T0.3028,0.8681 Q0.3076,0.8656,0.3237,0.8656 Q0.3317,0.8656,0.3622,0.8623 Q0.3655,0.8623,0.3867,0.8617 T0.4217,0.8602 Q0.4241,0.8598,0.4289,0.8598 L0.4345,0.8598 L0.441,0.8598 Q0.4578,0.8598,0.4667,0.8586 Q0.4739,0.8573,0.4851,0.8567 T0.5004,0.8552 Q0.5012,0.8552,0.5108,0.855 T0.5269,0.853 T0.5333,0.8461 Q0.5333,0.8445,0.5325,0.8432 T0.5309,0.8409 T0.5289,0.8391 T0.5261,0.8378 L0.5229,0.837 T0.5185,0.8362 T0.5133,0.8355 T0.5076,0.8349 T0.502,0.8341 Q0.4932,0.8324,0.4859,0.8324 Q0.4827,0.8324,0.4787,0.8328 T0.4731,0.8333 Q0.4707,0.8333,0.4683,0.8324 Q0.4482,0.825,0.4394,0.825 Q0.4369,0.825,0.4345,0.8254 Q0.4225,0.827,0.4153,0.827 Q0.4088,0.827,0.4064,0.8258 Q0.3976,0.8204,0.3751,0.8204 Q0.3703,0.8204,0.3667,0.8214 T0.3622,0.8225 Q0.3598,0.8225,0.3558,0.82 Q0.351,0.8175,0.3402,0.8167 T0.3177,0.8156 T0.3028,0.8146 L0.2956,0.8121 T0.2871,0.8094 T0.2771,0.8073 T0.2635,0.8063 Q0.261,0.8063,0.2586,0.8067 Q0.2378,0.8071,0.2273,0.8071 Q0.2072,0.8071,0.2024,0.8051 Q0.1976,0.8034,0.1932,0.8026 T0.1867,0.8013 T0.1847,0.8001 L0.1847,0.7993 Q0.1855,0.7984,0.1855,0.7978 T0.1859,0.7966 T0.1867,0.7959 T0.188,0.7955 T0.1896,0.7951 T0.1916,0.7949 T0.1944,0.7945 T0.1984,0.7941 T0.2032,0.7939 Q0.212,0.7926,0.2325,0.7918 T0.2562,0.7905 Q0.2691,0.7893,0.2787,0.7847 Q0.2835,0.7827,0.2968,0.7791 T0.3149,0.7756 Q0.3189,0.7756,0.3305,0.7733 T0.3446,0.771 L0.3454,0.771 Q0.3462,0.771,0.3498,0.7721 T0.3574,0.774 T0.3655,0.7748 Q0.3703,0.7748,0.3743,0.7731 T0.3823,0.7708 T0.3916,0.77 T0.3992,0.7694 Q0.4008,0.7694,0.4076,0.7688 T0.4205,0.7675 L0.4305,0.7665 Q0.4345,0.7657,0.4402,0.7617 T0.4458,0.7545 Q0.4458,0.7503,0.4353,0.7487 Q0.4297,0.7478,0.4209,0.7462 T0.4044,0.7433 T0.3904,0.7416 L0.3847,0.7416 Q0.3807,0.7416,0.3747,0.7422 T0.3663,0.7428 T0.3606,0.7424 Q0.3582,0.742,0.3562,0.7418 T0.3522,0.7412 T0.3482,0.7406 T0.3446,0.7401 T0.341,0.7397 T0.3373,0.7393 T0.3341,0.7391 L0.3293,0.7391 Q0.3277,0.7395,0.3253,0.7395 Q0.3205,0.7395,0.3052,0.7387 T0.2835,0.7379 Q0.2755,0.7379,0.253,0.7325 Q0.2506,0.7316,0.2462,0.731 T0.2382,0.7296 T0.2305,0.7271 T0.2181,0.7234 T0.2048,0.7196 T0.2,0.7155 Q0.2,0.7142,0.2016,0.713 Q0.208,0.7059,0.2285,0.6978 T0.2618,0.6898 Q0.2739,0.6898,0.2831,0.6848 T0.2956,0.6794 Q0.3028,0.6786,0.3289,0.6765 T0.3566,0.674 Q0.3582,0.674,0.3855,0.6723 T0.4406,0.6688 T0.4755,0.6653 Q0.5004,0.6607,0.5124,0.6599 L0.5165,0.6599 Q0.5213,0.6599,0.5277,0.6605 T0.5373,0.6611 Q0.543,0.6611,0.5494,0.6599 Q0.592,0.6524,0.6,0.6524 Q0.6064,0.6524,0.61,0.6499 T0.6161,0.6474 Q0.6177,0.6474,0.6261,0.6495 T0.6418,0.6516 L0.6482,0.6516 Q0.653,0.6512,0.6651,0.6506 T0.6876,0.6493 T0.7084,0.6477 T0.7229,0.645 Q0.7269,0.6437,0.7333,0.6437 Q0.7373,0.6437,0.7458,0.6443 T0.7574,0.645 Q0.7614,0.645,0.7631,0.6445 T0.7655,0.6441 Q0.7671,0.6441,0.7723,0.6448 T0.7823,0.6454 Q0.788,0.6454,0.7952,0.6445 Q0.8233,0.6408,0.8723,0.6367 Q0.9012,0.6342,0.9092,0.6342 L0.9165,0.6342 L0.9205,0.6342 Q0.9277,0.6342,0.9382,0.6323 T0.9526,0.6304 Q0.9534,0.6304,0.9594,0.6307 T0.9695,0.6309 Q0.9976,0.6309,0.9992,0.6234 Q1,0.6201,1,0.618 Q1,0.6089,0.9775,0.608 Q0.9703,0.6076,0.9582,0.6076 Q0.9518,0.6076,0.9394,0.6078 T0.9213,0.608 Q0.9036,0.608,0.8972,0.6072 Q0.894,0.6068,0.8871,0.6058 T0.8751,0.6039 T0.8627,0.6024 T0.8506,0.6018 L0.8474,0.6018 Q0.841,0.6022,0.8281,0.6041 T0.8112,0.606 Q0.8096,0.606,0.808,0.6056 Q0.7783,0.6002,0.7647,0.6002 Q0.7598,0.6002,0.7454,0.6006 T0.7261,0.601 Q0.7221,0.601,0.7145,0.6022 T0.7036,0.6035 Q0.6996,0.6035,0.6964,0.6022 Q0.6723,0.5902,0.6442,0.5902 Q0.6426,0.5902,0.6386,0.5904 T0.6337,0.5906 Q0.6273,0.5906,0.6249,0.5894 L0.6177,0.5856 T0.6116,0.5838 T0.6016,0.5825 T0.5855,0.5823 Q0.5719,0.5823,0.5574,0.5798 T0.5373,0.5774 L0.5341,0.5774 Q0.5293,0.5778,0.5213,0.5786 T0.51,0.5794 Q0.5036,0.5794,0.4988,0.5769 Q0.4932,0.574,0.4771,0.574 Q0.4723,0.574,0.4631,0.5745 T0.4498,0.5749 Q0.4418,0.5749,0.4394,0.5736 Q0.4353,0.5724,0.4305,0.5715 T0.4225,0.5707 T0.4145,0.5705 T0.4056,0.5699 Q0.4008,0.5691,0.398,0.5689 T0.394,0.5689 T0.3908,0.5686 T0.3855,0.5674 Q0.3831,0.567,0.3811,0.5662 T0.3779,0.5645 T0.3743,0.563 T0.3695,0.5624 Q0.3655,0.5624,0.3594,0.5635 T0.3494,0.5645 T0.3406,0.5635 T0.3325,0.5624 Q0.3309,0.5624,0.3301,0.5628 L0.3141,0.5628 Q0.2635,0.5628,0.2506,0.5612 Q0.2474,0.5608,0.2434,0.5608 Q0.2402,0.5608,0.2349,0.561 T0.2273,0.5612 Q0.2209,0.5612,0.2177,0.5595 Q0.2145,0.557,0.2145,0.5558 Q0.2169,0.5537,0.2345,0.5521 Q0.2876,0.5475,0.3028,0.5425 Q0.3068,0.5413,0.3149,0.5396 T0.3309,0.5367 T0.3478,0.5338 T0.3606,0.5309 Q0.3631,0.5305,0.3755,0.5259 T0.3968,0.5214 Q0.3992,0.5214,0.4024,0.5218 T0.4096,0.5222 Q0.4161,0.5222,0.4213,0.5209 T0.4313,0.5185 T0.4402,0.5172 Q0.4418,0.5168,0.447,0.5168 L0.4558,0.5168 T0.4639,0.5162 T0.4707,0.5141 T0.4731,0.5102 Q0.4731,0.5089,0.4723,0.5081 Q0.4699,0.5019,0.4627,0.4994 T0.4369,0.4965 Q0.4273,0.4961,0.4193,0.4946 T0.4048,0.4915 T0.3952,0.4894 Q0.392,0.4886,0.388,0.4886 Q0.3847,0.4886,0.3775,0.4892 T0.3647,0.4898 Q0.3598,0.4898,0.355,0.4894 Q0.3165,0.4861,0.3141,0.4853 Q0.2779,0.4774,0.2715,0.4774 Q0.2699,0.4774,0.2683,0.4776 T0.2627,0.4778 Q0.2498,0.4778,0.2281,0.4762 Q0.2241,0.4749,0.2233,0.4737 Q0.2249,0.472,0.2321,0.4708 Q0.2402,0.4695,0.2554,0.4695 L0.2675,0.4695 L0.2795,0.4695 Q0.294,0.4695,0.2996,0.4679 Q0.3141,0.4629,0.3373,0.4616 Q0.3414,0.4612,0.3466,0.4606 T0.3582,0.4589 T0.3687,0.4562 T0.3727,0.4525 Q0.3727,0.4509,0.3703,0.4492 Q0.3639,0.4455,0.3474,0.4419 T0.3213,0.4384 L0.3157,0.4384 L0.31,0.4384 Q0.294,0.4384,0.2859,0.4363 Q0.2795,0.4347,0.2691,0.4347 L0.2618,0.4347 L0.2546,0.4347 Q0.2458,0.4347,0.2394,0.4338 Q0.2257,0.4318,0.2193,0.4303 T0.2092,0.4266 T0.2052,0.4224 T0.2024,0.4154 T0.196,0.4052 Q0.1912,0.3994,0.1867,0.3949 T0.1807,0.3884 T0.1791,0.3849 Q0.1791,0.3824,0.1839,0.3745 Q0.1871,0.3687,0.1944,0.3637 T0.2072,0.3561 T0.2221,0.3501 T0.2321,0.3463 Q0.2353,0.3455,0.241,0.3445 T0.2522,0.3428 T0.2639,0.3411 T0.2731,0.3384 T0.2956,0.3351 T0.3197,0.3326 Q0.3213,0.3322,0.3237,0.3322 T0.3325,0.3331 T0.3414,0.3339 Q0.3478,0.3339,0.3518,0.3314 Q0.3606,0.3264,0.3703,0.3264 Q0.3775,0.3264,0.3847,0.3293 Q0.3888,0.331,0.396,0.331 Q0.4024,0.331,0.4116,0.3295 T0.4265,0.3266 T0.4414,0.3227 T0.4514,0.3202 Q0.4578,0.3181,0.4839,0.3152 T0.5112,0.3123 T0.5177,0.3119 T0.5285,0.3111 T0.5398,0.3096 T0.549,0.3065 T0.5526,0.3015 Q0.5526,0.2999,0.5518,0.2982 Q0.547,0.2903,0.5386,0.2883 T0.5165,0.2862 Q0.5116,0.2862,0.5056,0.2866 T0.4972,0.287 Q0.4884,0.287,0.4787,0.2833 Q0.4594,0.2758,0.4265,0.2758 Q0.4169,0.2758,0.4129,0.2766 Q0.4112,0.2771,0.4104,0.2771 Q0.4088,0.2771,0.4056,0.2748 T0.3968,0.2721 Q0.3695,0.2696,0.359,0.2659 Q0.355,0.2646,0.351,0.2646 T0.3426,0.2661 T0.3365,0.2675 T0.3333,0.2659 Q0.3277,0.2605,0.3116,0.2553 T0.2859,0.2501 L0.2843,0.2501 L0.2819,0.2501 Q0.2779,0.2501,0.2723,0.2493 T0.2586,0.2472 T0.2474,0.2455 Q0.1799,0.236,0.1719,0.236 Q0.1663,0.236,0.1606,0.235 T0.1502,0.2316 T0.1454,0.2265 Q0.1454,0.2223,0.1558,0.2173 Q0.1679,0.2115,0.1859,0.2092 T0.2185,0.207 T0.2353,0.2057 Q0.2369,0.2049,0.2502,0.2041 T0.2799,0.2022 T0.302,0.2007 Q0.3084,0.1999,0.3245,0.1954 T0.347,0.1908 Q0.3502,0.1908,0.3534,0.1916 Q0.3663,0.197,0.388,0.197 Q0.404,0.197,0.4088,0.1929 Q0.4096,0.192,0.4108,0.192 T0.4177,0.1945 T0.4277,0.197 T0.4369,0.1949 Q0.4442,0.1908,0.4843,0.1875 Q0.4892,0.1871,0.4968,0.1839 T0.5084,0.1808 L0.5108,0.1808 Q0.5213,0.1825,0.5454,0.1825 Q0.5823,0.1825,0.5992,0.1779 Q0.6096,0.1746,0.6217,0.1746 Q0.6249,0.1746,0.6309,0.1748 T0.6394,0.175 Q0.6434,0.175,0.6466,0.1746 Q0.6474,0.1746,0.6594,0.1738 T0.6787,0.1713 T0.6892,0.1659 Q0.6924,0.1634,0.6924,0.1593 Q0.6924,0.1547,0.6888,0.151 T0.6795,0.1472 Q0.6779,0.1472,0.6763,0.1477 Q0.6699,0.1493,0.6667,0.1493 Q0.661,0.1493,0.6506,0.1456 Q0.6329,0.1394,0.6129,0.1394 Q0.6048,0.1394,0.5992,0.1377 T0.5888,0.136 T0.5803,0.1377 T0.5751,0.1394 Q0.5711,0.1394,0.5639,0.1369 Q0.5365,0.1273,0.5116,0.1273 Q0.4876,0.1273,0.4667,0.1253 Q0.4627,0.1248,0.4542,0.1211 T0.4337,0.1174 L0.4305,0.1174 Q0.4217,0.1178,0.4153,0.119 T0.408,0.1203 Q0.4056,0.1203,0.4,0.117 Q0.3976,0.1153,0.396,0.1136 T0.3932,0.1112 T0.39,0.1099 T0.3847,0.1095 Q0.3799,0.1095,0.3687,0.1103 Q0.355,0.1112,0.3434,0.1141 T0.3301,0.117 Q0.3261,0.117,0.3229,0.1145 Q0.3181,0.1116,0.3052,0.1087 T0.2787,0.1043 T0.2546,0.1029 Q0.2506,0.1029,0.2474,0.1033 Q0.2418,0.1037,0.2349,0.1054 T0.2249,0.107 Q0.2225,0.107,0.2193,0.1066 Q0.2024,0.1024,0.1863,0.1024 Q0.1823,0.1024,0.1562,0.0971 T0.1269,0.0917 T0.1161,0.0896 T0.106,0.0854 Q0.1076,0.0846,0.11,0.0842 Q0.1173,0.0821,0.1261,0.0821 Q0.1301,0.0821,0.1369,0.0825 T0.1462,0.083 Q0.1494,0.083,0.151,0.0825 T0.1831,0.0813 T0.2201,0.0784 T0.243,0.074 T0.2667,0.0718 Q0.2707,0.0718,0.2755,0.0724 T0.2843,0.073 T0.2936,0.0724 T0.3024,0.0705 T0.3072,0.068 T0.3092,0.0657 T0.3108,0.0647 Q0.3116,0.0643,0.3133,0.0643 L0.3157,0.0643 L0.3181,0.0643 Q0.3446,0.0643,0.3446,0.0556 T0.3205,0.0469 L0.3189,0.0469 Q0.3157,0.0469,0.3157,0.0465 Q0.3084,0.0427,0.2835,0.0386 Q0.2739,0.0369,0.2675,0.0342 T0.2538,0.0299 Q0.249,0.029,0.245,0.029 T0.2378,0.0303 T0.2321,0.0315 Q0.2305,0.0315,0.2273,0.0307 Q0.2201,0.0286,0.2004,0.0272 T0.1775,0.0245 Q0.1735,0.0228,0.1663,0.0226 T0.1522,0.0197 T0.1345,0.0145 T0.1124,0.0079 Q0.0996,0.0033,0.0671,0.0044 T0.0297,0.0041 Q0.0169,0,0.0024,0.0017 Q0,0.0021,0,0.0037 Z M0.0064,0.9954 L0.0056,0.0058 Q0.0177,0.005,0.0281,0.0083 Q0.0345,0.01,0.0675,0.0089 T0.1108,0.012 Q0.1221,0.0162,0.1329,0.0187 T0.1498,0.0236 T0.1606,0.0268 T0.1691,0.0274 T0.1759,0.0286 Q0.1807,0.0303,0.2,0.0315 T0.2257,0.0348 Q0.2305,0.0361,0.2337,0.0361 Q0.2361,0.0361,0.2398,0.0348 T0.2466,0.0336 Q0.249,0.0336,0.2522,0.034 Q0.2586,0.0353,0.2655,0.0382 T0.2827,0.0427 Q0.3068,0.0469,0.3124,0.0502 Q0.3157,0.0523,0.3237,0.0523 L0.3277,0.0523 L0.3309,0.0523 Q0.3398,0.0523,0.3398,0.0556 Q0.3398,0.0581,0.3341,0.0587 T0.3205,0.0593 T0.3092,0.0603 T0.3036,0.0643 T0.2932,0.068 Q0.2884,0.0684,0.2843,0.0684 Q0.2811,0.0684,0.2763,0.0678 T0.2667,0.0672 Q0.2586,0.0672,0.2422,0.0697 T0.2177,0.0747 Q0.2137,0.0759,0.1831,0.0767 T0.1494,0.0784 L0.1474,0.0784 T0.1398,0.078 T0.1285,0.0776 Q0.1173,0.0776,0.1084,0.08 Q0.1004,0.0817,0.1004,0.0846 Q0.1004,0.0867,0.104,0.0888 T0.1124,0.0923 T0.1213,0.0948 T0.1269,0.0958 T0.155,0.1014 T0.1863,0.107 Q0.2016,0.107,0.2185,0.1107 Q0.2217,0.1116,0.2257,0.1116 Q0.2289,0.1116,0.2365,0.1099 T0.2482,0.1074 L0.2538,0.1074 Q0.2683,0.1074,0.2912,0.1107 T0.3197,0.1182 Q0.3245,0.1215,0.3301,0.1215 Q0.3333,0.1215,0.3454,0.1184 T0.3687,0.1145 Q0.3735,0.1141,0.3779,0.1139 T0.3843,0.1134 T0.3871,0.1132 Q0.3896,0.1132,0.39,0.1136 T0.392,0.1161 T0.3968,0.1203 Q0.404,0.1248,0.408,0.1248 Q0.4096,0.1248,0.4165,0.1234 T0.4305,0.1215 L0.4337,0.1215 Q0.4442,0.1215,0.4518,0.1251 T0.4659,0.1294 Q0.4876,0.1319,0.5116,0.1319 T0.5622,0.141 Q0.5703,0.1439,0.5751,0.1439 Q0.5791,0.1439,0.5831,0.1423 T0.5888,0.1406 Q0.592,0.1406,0.5944,0.1412 T0.6012,0.1427 T0.6129,0.1435 Q0.6313,0.1435,0.649,0.1497 Q0.6602,0.1535,0.6667,0.1535 Q0.6715,0.1535,0.6779,0.1522 Q0.6787,0.1518,0.6795,0.1518 Q0.6827,0.1518,0.6847,0.1541 T0.6867,0.1593 Q0.6867,0.1618,0.6851,0.1634 Q0.6835,0.1655,0.6775,0.1667 T0.6598,0.169 T0.6458,0.1705 T0.6402,0.1709 Q0.6378,0.1709,0.6321,0.1705 T0.6225,0.1701 Q0.6096,0.1701,0.5968,0.1738 Q0.5823,0.1779,0.5446,0.1779 Q0.5205,0.1779,0.5116,0.1767 Q0.51,0.1763,0.5084,0.1763 Q0.5028,0.1763,0.4944,0.1796 T0.4835,0.1829 L0.4771,0.1833 T0.4659,0.1844 L0.4546,0.1858 T0.443,0.1881 T0.4337,0.1912 Q0.4321,0.1916,0.4305,0.1916 Q0.4273,0.1916,0.4209,0.1895 T0.412,0.1875 Q0.408,0.1875,0.4056,0.19 Q0.4024,0.1925,0.388,0.1925 Q0.3663,0.1925,0.3558,0.1879 Q0.3518,0.1862,0.3462,0.1862 Q0.3373,0.1862,0.3213,0.191 T0.3012,0.1962 Q0.2964,0.1966,0.2799,0.1976 T0.2494,0.1997 T0.2313,0.2024 L0.2313,0.2024 L0.2293,0.2024 L0.2261,0.2024 L0.2217,0.2024 Q0.204,0.2024,0.1851,0.2047 T0.1526,0.2136 Q0.1406,0.2198,0.1406,0.226 Q0.1406,0.2319,0.1518,0.2362 T0.1719,0.2406 Q0.1791,0.2406,0.2466,0.2497 Q0.2498,0.2501,0.2574,0.2516 T0.2711,0.2538 T0.2819,0.2547 L0.2851,0.2547 Q0.2851,0.2543,0.2859,0.2543 Q0.294,0.2543,0.3096,0.2592 T0.3285,0.2684 Q0.3317,0.2721,0.3373,0.2721 Q0.3406,0.2721,0.3462,0.2708 T0.3542,0.2696 Q0.3558,0.2696,0.3574,0.27 Q0.3679,0.2737,0.3968,0.2762 Q0.3984,0.2766,0.4004,0.2779 L0.4044,0.2804 T0.4096,0.2816 Q0.412,0.2816,0.4145,0.2808 Q0.4177,0.2804,0.4249,0.2804 Q0.4586,0.2804,0.4763,0.287 Q0.4867,0.2912,0.4972,0.2912 Q0.5004,0.2912,0.5064,0.2908 T0.5165,0.2903 Q0.5293,0.2903,0.5361,0.2922 T0.547,0.3003 L0.547,0.3011 Q0.547,0.3036,0.5414,0.3049 T0.5249,0.3067 T0.51,0.3082 Q0.5092,0.3082,0.4831,0.3111 T0.4498,0.3161 Q0.408,0.3264,0.3952,0.3264 Q0.3896,0.3264,0.3863,0.3252 Q0.3783,0.3223,0.3703,0.3223 Q0.359,0.3223,0.3494,0.3277 Q0.347,0.3293,0.343,0.3293 Q0.3414,0.3293,0.3353,0.3285 T0.3253,0.3277 T0.3181,0.3285 Q0.3149,0.3289,0.2952,0.3306 T0.2699,0.3347 Q0.2675,0.3364,0.2598,0.3372 T0.2434,0.3395 T0.2297,0.3426 L0.2205,0.3461 T0.2048,0.3523 T0.1908,0.3606 T0.1791,0.3725 Q0.1743,0.3803,0.1743,0.3845 Q0.1743,0.3874,0.1763,0.3901 T0.1827,0.3976 T0.1912,0.4073 T0.198,0.4189 T0.2032,0.4276 T0.2137,0.4326 T0.2386,0.438 Q0.2458,0.4392,0.2562,0.4392 L0.2639,0.4392 L0.2699,0.4392 Q0.2795,0.4392,0.2843,0.4405 Q0.2932,0.443,0.3092,0.443 L0.3157,0.443 L0.3213,0.443 Q0.3293,0.443,0.3458,0.4463 T0.3671,0.4525 Q0.3598,0.4558,0.3373,0.4571 Q0.3124,0.4587,0.298,0.4637 Q0.294,0.465,0.2811,0.465 L0.2703,0.465 L0.2586,0.465 Q0.241,0.465,0.2305,0.4666 Q0.2185,0.4687,0.2185,0.4728 Q0.2185,0.4745,0.2205,0.4764 T0.2241,0.4791 L0.2265,0.4803 L0.2273,0.4803 Q0.2506,0.4824,0.261,0.4824 Q0.2675,0.4824,0.2699,0.482 Q0.2699,0.4815,0.2707,0.4815 Q0.2771,0.4815,0.3124,0.4898 Q0.3173,0.4907,0.3229,0.4913 T0.3386,0.4925 T0.3542,0.4936 T0.3663,0.494 Q0.3711,0.494,0.3787,0.4936 T0.3888,0.4932 Q0.392,0.4932,0.3944,0.4936 T0.4032,0.4959 T0.4181,0.499 T0.4369,0.5006 Q0.449,0.501,0.455,0.5021 T0.4639,0.5048 T0.4675,0.5093 L0.4675,0.5097 Q0.4675,0.5106,0.4663,0.511 T0.4622,0.5116 L0.4566,0.512 T0.4486,0.5124 T0.4402,0.5127 Q0.4345,0.5131,0.4257,0.5153 T0.4096,0.5176 Q0.4064,0.5176,0.4024,0.5172 L0.3968,0.5172 Q0.3863,0.5172,0.3731,0.522 T0.359,0.5268 Q0.355,0.528,0.3317,0.5321 T0.3004,0.5388 Q0.2867,0.5433,0.2337,0.5479 Q0.2096,0.55,0.2096,0.5562 Q0.2096,0.5591,0.2145,0.5628 Q0.2193,0.5662,0.2289,0.5662 Q0.2313,0.5662,0.2373,0.5657 T0.2458,0.5653 L0.2498,0.5653 Q0.2635,0.5674,0.3124,0.5674 Q0.3261,0.5674,0.3301,0.567 L0.3317,0.567 Q0.3341,0.567,0.3394,0.568 T0.3486,0.5691 Q0.3542,0.5691,0.3606,0.5678 T0.3699,0.5666 T0.3759,0.5686 T0.3839,0.5718 T0.3908,0.573 T0.3964,0.5734 T0.4048,0.5742 T0.4165,0.5751 T0.4277,0.5757 T0.4369,0.5778 Q0.4426,0.5794,0.4538,0.5794 Q0.4586,0.5794,0.4679,0.5792 T0.4811,0.579 Q0.4924,0.579,0.4956,0.5807 Q0.502,0.584,0.5108,0.584 L0.5137,0.584 T0.5173,0.5838 L0.5205,0.5834 L0.5237,0.583 L0.5269,0.5825 T0.5305,0.5821 T0.5341,0.5819 L0.5365,0.5819 Q0.5414,0.5819,0.5562,0.5842 T0.5855,0.5865 Q0.5912,0.5865,0.5948,0.5867 T0.6012,0.5869 T0.6064,0.5873 T0.61,0.5877 T0.6133,0.5886 T0.6157,0.5896 L0.6185,0.591 T0.6217,0.5931 Q0.6265,0.5952,0.6369,0.5952 L0.6418,0.5952 L0.6466,0.5952 Q0.6723,0.5952,0.6932,0.606 Q0.698,0.608,0.7044,0.608 Q0.7084,0.608,0.7161,0.6068 T0.7261,0.6056 Q0.7309,0.6056,0.7454,0.6051 T0.7647,0.6047 Q0.7775,0.6047,0.8072,0.6101 Q0.8096,0.6105,0.812,0.6105 Q0.8177,0.6105,0.8297,0.6085 T0.8474,0.606 L0.8506,0.606 Q0.8562,0.606,0.8647,0.607 T0.8831,0.6095 T0.8964,0.6118 Q0.9036,0.6122,0.9221,0.6122 L0.9406,0.6122 L0.959,0.6122 L0.9767,0.6122 Q0.9952,0.613,0.9952,0.6172 Q0.9952,0.6176,0.9944,0.6221 Q0.9936,0.6259,0.9574,0.6259 L0.955,0.6259 L0.9526,0.6259 Q0.9478,0.6259,0.9373,0.628 T0.9205,0.63 L0.9173,0.63 Q0.9133,0.6296,0.9084,0.6296 Q0.9004,0.6296,0.8715,0.6321 Q0.8233,0.6367,0.7944,0.64 Q0.7888,0.6408,0.7839,0.6408 T0.7739,0.6402 T0.7671,0.6396 Q0.7639,0.6396,0.7622,0.64 Q0.7614,0.6404,0.759,0.6404 Q0.7574,0.6404,0.749,0.6398 T0.7357,0.6392 Q0.7261,0.6392,0.7213,0.6412 Q0.7173,0.6425,0.7032,0.6437 T0.6711,0.6458 T0.6474,0.647 Q0.645,0.6474,0.6418,0.6474 Q0.6353,0.6474,0.6277,0.6454 T0.6161,0.6433 Q0.6104,0.6433,0.6072,0.6456 T0.6,0.6479 Q0.5912,0.6479,0.5486,0.6557 Q0.5422,0.657,0.5373,0.657 Q0.5349,0.657,0.5289,0.6564 T0.5173,0.6557 L0.5124,0.6557 Q0.4996,0.6562,0.4739,0.6611 Q0.4675,0.6624,0.4398,0.6645 T0.3851,0.668 T0.3558,0.6698 Q0.355,0.6698,0.3285,0.6721 T0.2948,0.6752 Q0.2892,0.6761,0.2843,0.6786 T0.2747,0.6831 T0.2618,0.6852 Q0.253,0.6852,0.2394,0.6893 T0.2141,0.6993 T0.1968,0.7105 Q0.1944,0.713,0.1944,0.7155 Q0.1944,0.7196,0.2012,0.7229 T0.2177,0.7285 T0.2281,0.7308 Q0.2321,0.7329,0.2365,0.7339 T0.245,0.7356 T0.2514,0.7366 Q0.2522,0.737,0.2554,0.7379 T0.2614,0.7393 T0.2679,0.7406 T0.2755,0.7416 T0.2835,0.742 Q0.29,0.742,0.3048,0.7428 T0.3253,0.7437 L0.3301,0.7437 L0.3333,0.7437 Q0.3341,0.7437,0.3598,0.7466 Q0.3639,0.7474,0.3671,0.7474 T0.3759,0.7466 T0.3855,0.7457 Q0.388,0.7457,0.3904,0.7462 Q0.3984,0.7466,0.4137,0.7495 T0.4349,0.7532 T0.441,0.7557 Q0.441,0.7599,0.4289,0.7619 Q0.4257,0.7628,0.4197,0.7634 T0.4072,0.7644 T0.3984,0.7652 Q0.3968,0.7652,0.3912,0.7655 T0.3811,0.7665 T0.3719,0.7694 Q0.3695,0.7702,0.3655,0.7702 T0.3546,0.7686 T0.3462,0.7665 L0.3446,0.7665 Q0.3406,0.7665,0.3293,0.769 T0.3149,0.7715 Q0.3084,0.7715,0.2952,0.7748 T0.2763,0.781 Q0.2675,0.7847,0.2554,0.7864 Q0.253,0.7864,0.2325,0.7874 T0.2024,0.7893 Q0.2008,0.7893,0.1984,0.7897 T0.1948,0.7901 T0.1916,0.7903 T0.1888,0.7908 T0.1867,0.7914 T0.1847,0.7922 L0.1831,0.793 T0.1819,0.7943 L0.1811,0.7959 T0.1799,0.7976 Q0.1791,0.7993,0.1791,0.8001 Q0.1791,0.8034,0.1831,0.8051 T0.1932,0.8078 T0.2,0.8092 Q0.2072,0.8117,0.2257,0.8117 Q0.2337,0.8117,0.2586,0.8109 L0.2635,0.8109 Q0.2707,0.8109,0.2767,0.8117 T0.2859,0.8136 T0.2936,0.8163 T0.3012,0.8187 Q0.3068,0.82,0.3277,0.8206 T0.3526,0.8237 Q0.3582,0.8266,0.3622,0.8266 Q0.3647,0.8266,0.3687,0.8258 T0.3751,0.825 Q0.396,0.825,0.404,0.8291 Q0.408,0.8316,0.4161,0.8316 Q0.4217,0.8316,0.4353,0.8295 L0.4394,0.8295 Q0.4466,0.8295,0.4659,0.8366 Q0.4699,0.8378,0.4747,0.8378 Q0.4763,0.8378,0.4807,0.8374 T0.4884,0.837 Q0.494,0.837,0.5004,0.8382 Q0.5052,0.8391,0.5124,0.8399 Q0.5221,0.8411,0.5249,0.8422 T0.5277,0.8461 Q0.5277,0.8482,0.5233,0.849 T0.5112,0.8501 T0.4996,0.8511 Q0.4964,0.8519,0.4851,0.8523 T0.4659,0.8544 Q0.4578,0.8557,0.4402,0.8557 L0.4349,0.8557 L0.4297,0.8557 L0.4209,0.8557 Q0.408,0.8565,0.3867,0.8571 T0.3614,0.8582 Q0.3317,0.8611,0.3237,0.8611 L0.3225,0.8611 L0.3213,0.8611 Q0.2972,0.8611,0.2972,0.8681 Q0.2972,0.8714,0.3008,0.8747 T0.3044,0.8805 Q0.3044,0.881,0.3036,0.8814 Q0.3012,0.8839,0.2851,0.8839 Q0.2779,0.8839,0.2739,0.883 Q0.2723,0.8826,0.2699,0.8826 T0.2643,0.8835 T0.259,0.8853 T0.2542,0.8876 T0.249,0.8893 Q0.2386,0.8922,0.2056,0.8946 Q0.2016,0.8951,0.1916,0.898 T0.1703,0.9009 Q0.1518,0.9009,0.1337,0.9044 T0.1149,0.9146 L0.1149,0.9158 Q0.1149,0.9175,0.1157,0.9187 T0.1173,0.921 T0.1201,0.9226 T0.1233,0.9237 T0.1269,0.9245 L0.1305,0.9251 T0.1337,0.9255 L0.1357,0.9258 Q0.139,0.9262,0.1446,0.9262 T0.1542,0.9266 T0.1598,0.9282 Q0.1614,0.9303,0.1647,0.9314 T0.1739,0.9328 T0.1815,0.9336 Q0.1863,0.9345,0.1976,0.9372 T0.2145,0.9415 Q0.2225,0.9432,0.2285,0.9467 T0.2378,0.9515 Q0.2426,0.9531,0.2514,0.9531 Q0.2538,0.9531,0.2602,0.9529 T0.2707,0.9527 Q0.2763,0.9527,0.2815,0.9531 T0.2867,0.9569 T0.2811,0.9614 Q0.2434,0.9627,0.2329,0.9652 Q0.2249,0.9672,0.2241,0.9672 L0.2096,0.9668 L0.2056,0.9668 Q0.2,0.9668,0.196,0.9672 T0.1884,0.9687 T0.1815,0.9708 T0.1731,0.9737 T0.1639,0.9772 Q0.1518,0.9805,0.1285,0.9805 L0.1253,0.9805 Q0.1124,0.9805,0.1076,0.9818 Q0.1012,0.983,0.0831,0.9876 T0.0594,0.9934 Q0.049,0.9954,0.0064,0.9954 Z M0.0032,0.9979 L0.0024,0.0037 Q0.0161,0.0021,0.0289,0.0062 Q0.0337,0.0075,0.0671,0.0066 T0.1116,0.01 T0.1337,0.0166 T0.151,0.022 Q0.1582,0.0249,0.1659,0.0251 T0.1767,0.0265 Q0.1807,0.0278,0.2,0.0292 T0.2265,0.0328 Q0.2305,0.034,0.2329,0.034 T0.239,0.0326 T0.2458,0.0311 T0.253,0.0319 Q0.2578,0.0332,0.2614,0.0342 T0.2667,0.0363 T0.2723,0.0384 T0.2827,0.0406 Q0.3084,0.0448,0.3141,0.0485 Q0.3149,0.0494,0.3225,0.0494 T0.3361,0.0504 T0.3422,0.0554 T0.3357,0.0606 T0.3205,0.0618 T0.31,0.0626 Q0.3092,0.0626,0.3056,0.0659 T0.2932,0.0701 Q0.2884,0.0709,0.2843,0.0709 Q0.2811,0.0709,0.2759,0.0701 T0.2667,0.0693 Q0.2586,0.0693,0.2422,0.0718 T0.2193,0.0763 Q0.2137,0.0784,0.1831,0.079 T0.1502,0.0805 Q0.1494,0.0809,0.147,0.0809 Q0.1454,0.0809,0.1386,0.0803 T0.1277,0.0796 Q0.1173,0.0796,0.1092,0.0821 Q0.1036,0.0834,0.1036,0.0854 Q0.1036,0.0879,0.1129,0.0908 T0.1269,0.0937 Q0.1293,0.0937,0.1558,0.0991 T0.1863,0.1045 Q0.2016,0.1045,0.2193,0.1087 Q0.2225,0.1091,0.2249,0.1091 Q0.2289,0.1091,0.2357,0.1076 T0.2482,0.1054 Q0.2506,0.1049,0.2546,0.1049 Q0.2699,0.1049,0.2924,0.1085 T0.3213,0.1165 Q0.3253,0.119,0.3301,0.119 Q0.3325,0.119,0.3446,0.1161 T0.3687,0.1124 Q0.3831,0.1112,0.3863,0.1112 T0.3908,0.1118 T0.3936,0.1145 T0.3984,0.1186 Q0.4048,0.1228,0.408,0.1228 Q0.4096,0.1228,0.4161,0.1213 T0.4305,0.1195 L0.4337,0.1195 Q0.4426,0.1195,0.4474,0.1211 T0.4566,0.1246 T0.4667,0.1273 Q0.4876,0.1298,0.5116,0.1298 Q0.5365,0.1298,0.5631,0.1389 Q0.5703,0.1414,0.5751,0.1414 Q0.5783,0.1414,0.5819,0.1398 T0.5888,0.1381 Q0.5928,0.1381,0.5984,0.1398 T0.6129,0.1414 Q0.6321,0.1414,0.6498,0.1477 Q0.661,0.1514,0.6667,0.1514 Q0.6707,0.1514,0.6771,0.1501 Q0.6787,0.1497,0.6795,0.1497 Q0.6835,0.1497,0.6863,0.1526 T0.6892,0.1593 Q0.6892,0.1626,0.6876,0.1647 Q0.6851,0.1672,0.6799,0.1686 T0.6695,0.1705 T0.6566,0.1715 T0.6458,0.1725 Q0.6434,0.173,0.6402,0.173 Q0.6378,0.173,0.6317,0.1727 T0.6225,0.1725 Q0.6096,0.1725,0.5984,0.1759 Q0.5823,0.1804,0.5454,0.1804 Q0.5205,0.1804,0.5116,0.1788 L0.5084,0.1788 Q0.5036,0.1788,0.4956,0.1819 T0.4843,0.185 Q0.4434,0.1887,0.4353,0.1929 Q0.4321,0.1945,0.4289,0.1945 T0.4197,0.1922 T0.4112,0.19 T0.4072,0.1912 Q0.4032,0.1949,0.388,0.1949 Q0.3663,0.1949,0.3542,0.19 Q0.351,0.1883,0.3462,0.1883 Q0.3406,0.1883,0.3321,0.1906 T0.3153,0.1954 T0.302,0.1983 Q0.2972,0.1991,0.2859,0.1997 T0.2643,0.201 T0.245,0.2024 T0.2337,0.2041 Q0.2321,0.2045,0.2181,0.2045 T0.1851,0.207 T0.1542,0.2157 Q0.143,0.2211,0.143,0.226 Q0.143,0.2314,0.153,0.2348 T0.1719,0.2381 Q0.1791,0.2381,0.2474,0.2476 Q0.2506,0.248,0.2558,0.2489 T0.2651,0.2505 T0.2739,0.252 T0.2819,0.2526 Q0.2835,0.2526,0.2851,0.2522 L0.2859,0.2522 Q0.2948,0.2522,0.3104,0.2574 T0.3309,0.2671 Q0.3333,0.2696,0.3365,0.2696 Q0.339,0.2696,0.3442,0.2684 T0.3526,0.2671 T0.3582,0.2679 Q0.3687,0.2717,0.3968,0.2742 Q0.4,0.2746,0.4036,0.2771 T0.4096,0.2796 Q0.4112,0.2796,0.4137,0.2787 Q0.4169,0.2779,0.4257,0.2779 Q0.4594,0.2779,0.4779,0.2849 Q0.4876,0.2891,0.4972,0.2891 Q0.4996,0.2891,0.506,0.2887 T0.5165,0.2883 Q0.5253,0.2883,0.5305,0.2889 T0.541,0.292 T0.5494,0.299 L0.5494,0.3015 Q0.5494,0.3036,0.5466,0.3051 T0.5386,0.3073 T0.5285,0.3086 T0.5181,0.3094 T0.5108,0.3102 Q0.5068,0.3107,0.494,0.3121 L0.4683,0.315 T0.4506,0.3181 Q0.449,0.3181,0.4426,0.32 T0.4313,0.3229 T0.4197,0.3256 T0.4064,0.3281 T0.3952,0.3289 Q0.3888,0.3289,0.3855,0.3273 Q0.3783,0.3243,0.3703,0.3243 Q0.3598,0.3243,0.351,0.3297 Q0.3478,0.3314,0.3422,0.3314 Q0.3398,0.3314,0.3337,0.3306 T0.3245,0.3297 T0.3189,0.3306 Q0.3149,0.3314,0.2956,0.3328 T0.2715,0.3368 Q0.2691,0.338,0.2635,0.3391 T0.2522,0.3407 T0.2406,0.3424 T0.2313,0.3447 Q0.2305,0.3447,0.2213,0.348 T0.206,0.354 T0.1924,0.3621 T0.1815,0.3733 Q0.1767,0.3816,0.1767,0.3849 Q0.1767,0.387,0.1783,0.3893 T0.1847,0.3961 T0.1936,0.4063 T0.2004,0.4181 T0.2052,0.4264 T0.2145,0.4309 T0.2386,0.4359 Q0.2458,0.4372,0.2554,0.4372 Q0.2578,0.4372,0.2627,0.437 T0.2699,0.4367 Q0.2795,0.4367,0.2851,0.4384 Q0.294,0.4409,0.3092,0.4409 Q0.3116,0.4409,0.3157,0.4407 T0.3213,0.4405 Q0.3301,0.4405,0.3466,0.444 T0.3687,0.4509 Q0.3703,0.4517,0.3703,0.4525 Q0.3703,0.4575,0.3373,0.4596 Q0.3133,0.4608,0.2988,0.4658 Q0.294,0.4674,0.2803,0.4674 Q0.2763,0.4674,0.2687,0.4672 T0.257,0.467 Q0.2402,0.467,0.2313,0.4687 Q0.2209,0.4708,0.2209,0.4737 Q0.2209,0.4741,0.2213,0.4749 T0.2229,0.4764 L0.2249,0.4774 T0.2265,0.4778 L0.2273,0.4782 Q0.2498,0.4803,0.2618,0.4803 Q0.2667,0.4803,0.2691,0.4799 Q0.2699,0.4795,0.2715,0.4795 Q0.2779,0.4795,0.3133,0.4873 Q0.3181,0.4886,0.3233,0.4892 T0.3386,0.4905 T0.3542,0.4915 T0.3655,0.4919 Q0.3703,0.4919,0.3779,0.4915 T0.3888,0.4911 T0.3952,0.4915 Q0.3976,0.4919,0.4008,0.4927 L0.4072,0.4944 T0.4149,0.4961 T0.4249,0.4975 T0.4369,0.4985 Q0.449,0.499,0.4554,0.5 T0.4651,0.5031 T0.4699,0.5085 Q0.4707,0.5093,0.4707,0.5097 Q0.4707,0.5114,0.4687,0.5124 T0.4631,0.5139 T0.4562,0.5145 T0.4478,0.5147 L0.4402,0.5147 Q0.4353,0.5151,0.4265,0.5174 T0.4096,0.5197 L0.4024,0.5197 Q0.4,0.5193,0.3968,0.5193 Q0.3896,0.5193,0.3831,0.5209 T0.3703,0.5253 T0.3598,0.5288 Q0.3558,0.5301,0.3321,0.5342 T0.302,0.5409 Q0.2876,0.5454,0.2337,0.55 Q0.2112,0.5521,0.2112,0.5558 Q0.2112,0.5574,0.2161,0.5612 Q0.2201,0.5637,0.2281,0.5637 Q0.2305,0.5637,0.2361,0.5633 T0.2442,0.5628 Q0.2474,0.5628,0.2498,0.5633 Q0.2635,0.5653,0.3133,0.5653 Q0.3261,0.5653,0.3301,0.5649 L0.3325,0.5649 Q0.3349,0.5649,0.3402,0.5657 T0.3494,0.5666 Q0.3542,0.5666,0.3598,0.5657 Q0.3663,0.5645,0.3695,0.5645 Q0.3735,0.5645,0.3775,0.5666 T0.3847,0.5695 Q0.3888,0.5707,0.3912,0.5709 T0.3968,0.5711 T0.4048,0.572 Q0.4096,0.5724,0.4165,0.5726 T0.4281,0.5734 T0.4378,0.5757 Q0.4418,0.5774,0.4522,0.5774 Q0.4562,0.5774,0.4655,0.5769 T0.4795,0.5765 Q0.4932,0.5765,0.4972,0.579 Q0.5028,0.5819,0.51,0.5819 Q0.5141,0.5819,0.5221,0.5809 T0.5341,0.5798 L0.5373,0.5798 Q0.5422,0.5798,0.557,0.5821 T0.5855,0.5844 L0.5936,0.5844 L0.5992,0.5844 T0.604,0.5848 T0.6076,0.5852 T0.6108,0.5859 T0.6137,0.5865 T0.6161,0.5873 T0.6181,0.5881 T0.6209,0.5896 L0.6233,0.591 Q0.6265,0.5927,0.6353,0.5927 L0.6402,0.5927 L0.645,0.5927 Q0.6723,0.5927,0.6948,0.6039 Q0.6988,0.606,0.7036,0.606 Q0.7076,0.606,0.7153,0.6045 T0.7261,0.6031 Q0.7309,0.6031,0.7454,0.6027 T0.7647,0.6022 Q0.7783,0.6022,0.808,0.608 L0.812,0.608 Q0.8169,0.608,0.8293,0.6062 T0.8474,0.6039 L0.8506,0.6039 Q0.857,0.6039,0.8655,0.6049 T0.8835,0.6074 T0.8972,0.6093 Q0.9036,0.6101,0.9213,0.6101 L0.9398,0.6101 L0.959,0.6101 L0.9767,0.6101 Q0.9976,0.6109,0.9976,0.6176 Q0.9976,0.6188,0.9968,0.6226 Q0.9952,0.6284,0.9671,0.6284 L0.959,0.6284 L0.9526,0.6284 Q0.9486,0.6284,0.9382,0.6302 T0.9205,0.6321 L0.9173,0.6321 Q0.9133,0.6317,0.9092,0.6317 Q0.9012,0.6317,0.8715,0.6346 Q0.8233,0.6387,0.7944,0.6421 Q0.7888,0.6429,0.7831,0.6429 Q0.7783,0.6429,0.7731,0.6425 T0.7663,0.6421 L0.7622,0.6421 Q0.7614,0.6425,0.7586,0.6425 T0.7474,0.6421 T0.7349,0.6416 Q0.7261,0.6416,0.7221,0.6429 Q0.7173,0.645,0.7032,0.646 T0.6711,0.6479 T0.6482,0.6491 Q0.645,0.6495,0.6418,0.6495 Q0.6353,0.6495,0.6273,0.6474 T0.6161,0.6454 Q0.612,0.6454,0.6088,0.6477 T0.6,0.6499 Q0.5912,0.6499,0.5494,0.6578 Q0.5422,0.6591,0.5373,0.6591 Q0.5341,0.6591,0.5281,0.6584 T0.5173,0.6578 L0.5124,0.6578 Q0.5004,0.6582,0.4747,0.6632 Q0.4675,0.6649,0.4398,0.6667 T0.3851,0.6703 T0.3566,0.6719 Q0.3534,0.6723,0.3406,0.6734 T0.3141,0.6757 T0.2948,0.6773 Q0.2908,0.6781,0.2819,0.6829 T0.2618,0.6877 Q0.2482,0.6877,0.2273,0.6958 T0.1992,0.7117 Q0.1976,0.7138,0.1976,0.7155 Q0.1976,0.7188,0.2032,0.7213 T0.2181,0.7258 T0.2289,0.7292 Q0.2337,0.7308,0.2378,0.7316 L0.2458,0.7333 T0.2522,0.7345 Q0.2747,0.7399,0.2835,0.7399 Q0.29,0.7399,0.3048,0.7408 T0.3253,0.7416 Q0.3277,0.7416,0.3297,0.7414 T0.3341,0.7412 L0.3598,0.7445 Q0.3639,0.7449,0.3663,0.7449 Q0.3695,0.7449,0.3751,0.7443 T0.3855,0.7437 L0.3904,0.7437 Q0.3984,0.7441,0.4141,0.7472 T0.4353,0.7507 Q0.4434,0.752,0.4434,0.7553 Q0.4434,0.7578,0.439,0.7605 T0.4297,0.764 Q0.4257,0.7648,0.4201,0.7655 T0.4076,0.7665 T0.3984,0.7673 Q0.3968,0.7673,0.3912,0.7677 T0.3815,0.7688 T0.3735,0.771 T0.3655,0.7727 Q0.3606,0.7727,0.3534,0.7708 T0.3458,0.769 L0.3446,0.769 Q0.3414,0.769,0.3297,0.7713 T0.3149,0.7735 Q0.3092,0.7735,0.296,0.7771 T0.2771,0.7831 Q0.2683,0.7872,0.2554,0.7885 Q0.253,0.7889,0.2325,0.7897 T0.2032,0.7914 Q0.2008,0.7918,0.198,0.792 T0.194,0.7924 T0.1912,0.7928 T0.1888,0.793 T0.1867,0.7934 T0.1851,0.7943 T0.1843,0.7953 T0.1835,0.7968 T0.1823,0.7984 T0.1815,0.8001 Q0.1815,0.8022,0.1847,0.8032 T0.1932,0.8053 T0.2008,0.8071 Q0.2072,0.8092,0.2265,0.8092 Q0.2361,0.8092,0.2586,0.8088 L0.2635,0.8088 Q0.2731,0.8088,0.2795,0.8098 T0.2928,0.8134 T0.302,0.8167 Q0.306,0.8179,0.3277,0.8183 T0.3542,0.8221 Q0.359,0.8246,0.3622,0.8246 Q0.3639,0.8246,0.3675,0.8237 T0.3751,0.8229 Q0.3968,0.8229,0.4048,0.8275 Q0.4088,0.8291,0.4161,0.8291 Q0.4225,0.8291,0.4353,0.8275 Q0.4369,0.827,0.4394,0.827 Q0.4474,0.827,0.4675,0.8345 Q0.4707,0.8358,0.4739,0.8358 Q0.4755,0.8358,0.4799,0.8353 T0.4876,0.8349 Q0.494,0.8349,0.5012,0.8362 L0.5076,0.837 T0.5133,0.8376 T0.5181,0.8382 T0.5221,0.8391 L0.5253,0.8399 T0.5277,0.8409 L0.5293,0.8422 T0.5301,0.8438 L0.5301,0.8461 Q0.5301,0.8499,0.5249,0.8511 T0.5112,0.8526 T0.5004,0.8532 Q0.4964,0.854,0.4851,0.8546 T0.4667,0.8565 Q0.4578,0.8577,0.441,0.8577 L0.4345,0.8577 L0.4289,0.8577 L0.4209,0.8577 Q0.4153,0.8582,0.4088,0.8586 T0.3968,0.8592 T0.3855,0.8594 T0.3755,0.8596 T0.3675,0.86 T0.3622,0.8602 Q0.3317,0.8635,0.3237,0.8635 Q0.2996,0.8635,0.2996,0.8681 Q0.2996,0.8698,0.3032,0.8733 T0.3068,0.8797 L0.3068,0.8822 Q0.3052,0.8859,0.2859,0.8859 Q0.2787,0.8859,0.2739,0.8851 L0.2699,0.8851 Q0.2659,0.8851,0.259,0.888 T0.2498,0.8913 Q0.2394,0.8946,0.2056,0.8971 Q0.2032,0.8971,0.1928,0.9002 T0.1703,0.9034 Q0.1526,0.9034,0.1353,0.9067 T0.1173,0.915 L0.1173,0.9158 Q0.1173,0.9166,0.1177,0.9175 T0.1189,0.9191 T0.1205,0.9204 T0.1229,0.9212 T0.1257,0.922 T0.1285,0.9226 T0.1309,0.9231 T0.1337,0.9235 T0.1365,0.9237 L0.1446,0.9237 T0.1554,0.9243 T0.1614,0.9268 T0.1659,0.9293 T0.1743,0.9303 T0.1823,0.9316 Q0.1863,0.932,0.1912,0.9332 T0.2028,0.9361 T0.2153,0.939 Q0.2233,0.9411,0.2297,0.9448 T0.2386,0.9494 Q0.2426,0.9506,0.2506,0.9506 Q0.253,0.9506,0.2594,0.9504 T0.2699,0.9502 Q0.2763,0.9502,0.2819,0.9511 Q0.2851,0.9511,0.2871,0.9529 T0.2892,0.9573 Q0.2892,0.9627,0.2811,0.9635 Q0.2434,0.9652,0.2337,0.9672 Q0.2257,0.9693,0.2241,0.9693 Q0.2217,0.9693,0.2096,0.9689 L0.2056,0.9689 Q0.1976,0.9689,0.1924,0.9699 T0.1787,0.9741 L0.1647,0.9793 Q0.1518,0.983,0.1285,0.983 L0.1261,0.983 L0.1237,0.983 Q0.1124,0.983,0.1076,0.9838 Q0.102,0.9851,0.0839,0.9896 T0.0602,0.9954 T0.0406,0.9971 T0.0153,0.9979 L0.0032,0.9979 Z",type:0}},"":{type:5,data:{d:"M0.9992,0.998 Q0.9992,1,0.9968,1 Q0.9521,1,0.9392,0.998 Q0.9335,0.997,0.9152,0.9921 T0.8913,0.9862 Q0.8873,0.9852,0.8783,0.9852 L0.8747,0.9852 L0.871,0.9852 Q0.8483,0.9852,0.8345,0.9812 Q0.8289,0.9792,0.8228,0.9773 T0.8135,0.9743 T0.8054,0.9723 T0.7948,0.9713 L0.7908,0.9713 L0.777,0.9713 T0.7656,0.9693 Q0.7616,0.9683,0.7498,0.9674 T0.7283,0.9664 L0.7186,0.9654 Q0.708,0.9644,0.708,0.9575 Q0.708,0.9545,0.7109,0.952 T0.7178,0.9486 L0.7315,0.9486 L0.7417,0.9486 L0.7502,0.9486 Q0.7575,0.9486,0.7599,0.9476 Q0.7616,0.9466,0.7685,0.9426 T0.7843,0.9377 Q0.7899,0.9357,0.8009,0.9332 T0.8175,0.9298 Q0.82,0.9288,0.8228,0.9283 T0.8277,0.9278 T0.8313,0.9273 T0.8341,0.9263 T0.8362,0.9258 Q0.8402,0.9219,0.854,0.9219 L0.8569,0.9219 L0.8597,0.9219 L0.8637,0.9219 Q0.88,0.9199,0.88,0.9159 L0.88,0.9149 Q0.88,0.913,0.8719,0.911 T0.852,0.9075 T0.8297,0.906 Q0.8224,0.906,0.8167,0.905 T0.8074,0.9031 T0.8001,0.9006 T0.794,0.8991 Q0.7599,0.8971,0.7494,0.8942 Q0.7461,0.8932,0.7397,0.8902 T0.7299,0.8872 L0.7267,0.8872 Q0.721,0.8882,0.7121,0.8882 Q0.6926,0.8882,0.691,0.8833 L0.691,0.8803 Q0.691,0.8773,0.6922,0.8749 T0.6955,0.8704 L0.6975,0.8684 Q0.6926,0.8655,0.6764,0.8655 Q0.6683,0.8655,0.6375,0.8625 Q0.6358,0.8625,0.629,0.862 T0.6148,0.8615 T0.5973,0.861 T0.5791,0.8605 L0.5718,0.8605 L0.5661,0.8605 L0.5596,0.8605 Q0.5426,0.8605,0.5337,0.8586 Q0.5264,0.8576,0.515,0.8571 T0.4996,0.8556 Q0.498,0.8556,0.4947,0.8551 T0.4882,0.8546 T0.4809,0.8541 T0.474,0.8531 T0.4692,0.8506 T0.4672,0.8467 Q0.4672,0.8437,0.468,0.8422 T0.4704,0.8398 T0.4736,0.8383 T0.4781,0.8368 T0.4838,0.8358 T0.4911,0.8353 T0.4988,0.8338 Q0.5069,0.8328,0.5142,0.8328 Q0.5174,0.8328,0.5215,0.8333 T0.5264,0.8338 Q0.5296,0.8338,0.532,0.8328 Q0.5523,0.8249,0.5604,0.8249 L0.5653,0.8249 Q0.5775,0.8269,0.5848,0.8269 Q0.5912,0.8269,0.5937,0.8259 Q0.6026,0.821,0.6253,0.821 Q0.6294,0.821,0.6334,0.822 T0.6383,0.8229 Q0.6399,0.8229,0.644,0.82 Q0.6504,0.817,0.6723,0.816 T0.6975,0.814 Q0.6999,0.814,0.7048,0.8121 T0.7129,0.8091 T0.7226,0.8071 T0.7372,0.8061 L0.7421,0.8061 Q0.7624,0.8071,0.7721,0.8071 Q0.7924,0.8071,0.7981,0.8051 Q0.8021,0.8032,0.807,0.8027 T0.8135,0.8017 T0.8151,0.8002 L0.8151,0.7992 Q0.8143,0.7962,0.8127,0.7957 T0.8045,0.7943 Q0.7997,0.7943,0.7964,0.7933 Q0.7883,0.7933,0.7676,0.7923 T0.7437,0.7903 Q0.7307,0.7893,0.7218,0.7844 Q0.7194,0.7834,0.7141,0.7819 L0.7036,0.7789 T0.693,0.7765 T0.6853,0.7755 Q0.6805,0.7755,0.6691,0.773 T0.6553,0.7705 Q0.6545,0.7705,0.6545,0.7715 Q0.6537,0.7715,0.65,0.7725 T0.6423,0.774 T0.635,0.7745 Q0.6294,0.7745,0.6257,0.773 T0.618,0.771 T0.6083,0.77 T0.601,0.7695 Q0.5985,0.7695,0.5921,0.769 T0.5795,0.7676 T0.5702,0.7666 Q0.5653,0.7656,0.5596,0.7616 T0.5539,0.7547 Q0.5539,0.7498,0.5645,0.7488 Q0.5693,0.7478,0.5852,0.7448 T0.6099,0.7418 L0.6148,0.7418 Q0.6196,0.7418,0.6253,0.7423 T0.6338,0.7428 L0.6399,0.7428 Q0.6431,0.7418,0.6488,0.7409 T0.6586,0.7394 T0.6659,0.7389 L0.6707,0.7389 Q0.6723,0.7399,0.6748,0.7399 Q0.6796,0.7399,0.6946,0.7389 T0.7161,0.7379 Q0.7251,0.7379,0.7478,0.7319 Q0.7494,0.7319,0.7539,0.7315 T0.762,0.73 T0.7697,0.727 T0.7818,0.723 T0.7948,0.7196 T0.7997,0.7151 Q0.7997,0.7141,0.7989,0.7132 Q0.7924,0.7062,0.7717,0.6978 T0.738,0.6894 Q0.7267,0.6894,0.717,0.6845 T0.7048,0.6795 Q0.6975,0.6785,0.6711,0.6766 T0.6431,0.6746 Q0.6415,0.6736,0.6144,0.6721 T0.5596,0.6686 T0.5247,0.6657 Q0.4996,0.6607,0.4874,0.6597 L0.4834,0.6597 Q0.4785,0.6597,0.4724,0.6607 T0.4631,0.6617 Q0.4574,0.6617,0.4501,0.6597 Q0.4088,0.6518,0.4006,0.6518 Q0.3933,0.6518,0.3901,0.6499 T0.3844,0.6479 T0.3739,0.6499 T0.3585,0.6518 L0.352,0.6518 Q0.3471,0.6508,0.335,0.6503 T0.3122,0.6494 T0.2912,0.6479 T0.2766,0.6449 Q0.2733,0.6439,0.2668,0.6439 Q0.2628,0.6439,0.2543,0.6444 T0.2425,0.6449 T0.2368,0.6439 L0.2344,0.6439 Q0.2336,0.6439,0.2283,0.6444 T0.2182,0.6449 Q0.2117,0.6449,0.2052,0.6439 Q0.1768,0.6409,0.1281,0.637 Q0.0989,0.634,0.0916,0.634 L0.0835,0.634 L0.0795,0.634 Q0.0722,0.634,0.0616,0.632 T0.0479,0.6301 Q0.0462,0.6301,0.0406,0.6306 T0.0308,0.6311 Q0.0032,0.6311,0.0008,0.6231 Q0,0.6202,0,0.6182 Q0,0.6083,0.0227,0.6083 Q0.03,0.6073,0.0422,0.6073 Q0.0487,0.6073,0.0608,0.6078 T0.0787,0.6083 Q0.0973,0.6083,0.103,0.6073 Q0.1062,0.6063,0.1131,0.6053 T0.1253,0.6039 T0.1375,0.6024 T0.1492,0.6014 L0.1533,0.6014 Q0.1598,0.6024,0.1723,0.6044 T0.189,0.6063 Q0.1906,0.6063,0.1922,0.6053 Q0.2214,0.6004,0.236,0.6004 Q0.2409,0.6004,0.2551,0.6009 T0.2741,0.6014 Q0.2782,0.6014,0.2859,0.6024 T0.2968,0.6034 Q0.3009,0.6034,0.3041,0.6024 Q0.3277,0.5905,0.356,0.5905 L0.3613,0.5905 L0.3666,0.5905 Q0.3731,0.5905,0.3755,0.5895 Q0.382,0.5856,0.3844,0.5846 T0.3938,0.5831 T0.4144,0.5826 T0.4428,0.5801 T0.4631,0.5776 L0.4663,0.5776 Q0.4704,0.5776,0.4785,0.5786 T0.4899,0.5796 Q0.4964,0.5796,0.5012,0.5767 Q0.5069,0.5737,0.5223,0.5737 Q0.5272,0.5737,0.5369,0.5742 T0.5499,0.5747 Q0.558,0.5747,0.5612,0.5737 Q0.5661,0.5717,0.5714,0.5712 T0.5835,0.5702 T0.5945,0.5692 T0.6018,0.5687 L0.6062,0.5687 T0.6095,0.5682 T0.6148,0.5678 Q0.6172,0.5668,0.6196,0.5653 T0.6245,0.5633 T0.631,0.5628 T0.6407,0.5638 T0.6504,0.5648 T0.6594,0.5638 T0.6675,0.5628 L0.6707,0.5628 L0.6861,0.5628 Q0.7364,0.5628,0.7494,0.5608 L0.7567,0.5608 L0.7652,0.5608 L0.7729,0.5608 Q0.7794,0.5608,0.7818,0.5589 Q0.7851,0.5569,0.7859,0.5559 Q0.7835,0.5539,0.7656,0.5519 Q0.7121,0.547,0.6975,0.543 Q0.6934,0.541,0.6853,0.5396 L0.6691,0.5366 T0.6525,0.5336 T0.6391,0.5312 Q0.6367,0.5302,0.6245,0.5257 T0.6034,0.5213 L0.5977,0.5213 Q0.5945,0.5223,0.5904,0.5223 Q0.5839,0.5223,0.5787,0.5208 T0.5685,0.5183 T0.5596,0.5173 Q0.558,0.5173,0.5552,0.5168 T0.5495,0.5163 L0.5434,0.5163 T0.5373,0.5158 T0.532,0.5148 T0.5284,0.5129 T0.5272,0.5104 L0.5272,0.5074 Q0.5304,0.5015,0.5377,0.499 T0.5629,0.4965 Q0.5734,0.4955,0.5811,0.4946 T0.5953,0.4916 T0.605,0.4896 Q0.6083,0.4886,0.6123,0.4886 Q0.6148,0.4886,0.6225,0.4891 T0.635,0.4896 L0.6456,0.4896 Q0.6837,0.4857,0.6861,0.4857 Q0.7226,0.4768,0.7283,0.4768 Q0.7307,0.4768,0.732,0.4773 T0.7372,0.4777 Q0.7502,0.4777,0.7721,0.4758 Q0.7753,0.4748,0.777,0.4738 Q0.7745,0.4718,0.768,0.4708 Q0.7599,0.4688,0.7445,0.4688 Q0.7405,0.4688,0.7324,0.4693 T0.721,0.4698 Q0.7056,0.4698,0.6999,0.4679 Q0.6861,0.4629,0.6626,0.4619 Q0.6586,0.4609,0.6533,0.4604 T0.6419,0.459 T0.6314,0.456 T0.6269,0.452 Q0.6269,0.451,0.6302,0.4491 Q0.6358,0.4451,0.6525,0.4416 T0.6788,0.4382 L0.6845,0.4382 L0.6902,0.4382 Q0.7064,0.4382,0.7145,0.4362 Q0.7202,0.4342,0.7307,0.4342 L0.7356,0.4342 L0.7409,0.4342 L0.7453,0.4342 Q0.7543,0.4342,0.7607,0.4332 Q0.7778,0.4313,0.7847,0.4288 T0.7932,0.4248 T0.7972,0.4169 T0.8045,0.4045 L0.8094,0.3986 T0.8135,0.3942 L0.8167,0.3912 T0.8191,0.3887 T0.8204,0.3867 T0.8208,0.3848 Q0.8208,0.3818,0.8167,0.3739 Q0.8127,0.3689,0.8054,0.3635 T0.7924,0.3556 T0.7778,0.3497 T0.768,0.3462 Q0.764,0.3442,0.7478,0.3427 T0.7267,0.3383 Q0.7234,0.3363,0.7044,0.3348 T0.6805,0.3323 L0.6764,0.3323 T0.6679,0.3328 T0.6586,0.3333 Q0.6521,0.3333,0.648,0.3314 Q0.6391,0.3264,0.6294,0.3264 Q0.6229,0.3264,0.6156,0.3294 Q0.6115,0.3304,0.6042,0.3304 Q0.5977,0.3304,0.5888,0.3289 T0.5738,0.3264 T0.5584,0.3225 L0.5491,0.3195 Q0.5418,0.3185,0.5162,0.3155 T0.4895,0.3121 T0.4826,0.3116 T0.4716,0.3111 T0.4607,0.3096 T0.4513,0.3066 T0.4477,0.3017 Q0.4477,0.2997,0.4485,0.2977 Q0.4526,0.2898,0.4611,0.2878 T0.4842,0.2859 Q0.4882,0.2859,0.4943,0.2864 T0.5028,0.2868 Q0.5118,0.2868,0.5215,0.2829 Q0.5401,0.276,0.5734,0.276 Q0.5831,0.276,0.5872,0.277 L0.5904,0.277 T0.5949,0.2745 T0.6034,0.272 Q0.631,0.269,0.6407,0.2661 Q0.6448,0.2641,0.6488,0.2641 T0.6573,0.2656 T0.6634,0.2671 Q0.6659,0.2671,0.6675,0.2651 Q0.6723,0.2601,0.6886,0.2552 T0.7137,0.2502 L0.7153,0.2502 L0.7178,0.2502 Q0.7218,0.2502,0.7279,0.2493 T0.7417,0.2468 T0.7526,0.2453 Q0.8208,0.2354,0.8281,0.2354 Q0.8337,0.2354,0.8394,0.2344 T0.8496,0.2315 T0.854,0.2265 Q0.854,0.2216,0.8443,0.2176 Q0.8354,0.2127,0.8216,0.2102 T0.7972,0.2072 T0.777,0.2067 T0.7648,0.2057 Q0.7632,0.2047,0.7498,0.2038 T0.7202,0.2018 T0.6983,0.1998 Q0.6918,0.1998,0.676,0.1949 T0.6537,0.1899 Q0.6496,0.1899,0.6472,0.1919 Q0.6342,0.1968,0.6123,0.1968 Q0.5961,0.1968,0.5912,0.1929 Q0.5904,0.1919,0.5892,0.1919 T0.5823,0.1944 T0.5722,0.1968 T0.5637,0.1949 Q0.5556,0.1909,0.5158,0.1869 Q0.5109,0.1869,0.5032,0.1835 T0.4915,0.18 Q0.4907,0.18,0.4891,0.181 Q0.4793,0.182,0.455,0.182 Q0.4177,0.182,0.4015,0.1771 Q0.3901,0.1741,0.3779,0.1741 Q0.3747,0.1741,0.369,0.1746 T0.3609,0.1751 Q0.3569,0.1751,0.3536,0.1741 Q0.3512,0.1741,0.3459,0.1736 T0.3358,0.1726 T0.3256,0.1716 T0.3167,0.1696 T0.3106,0.1662 Q0.3082,0.1632,0.3082,0.1592 Q0.3082,0.1543,0.3114,0.1508 T0.3204,0.1474 L0.3236,0.1474 Q0.3301,0.1494,0.3333,0.1494 Q0.339,0.1494,0.3496,0.1454 Q0.3674,0.1385,0.3869,0.1385 Q0.395,0.1385,0.4011,0.137 T0.4116,0.1355 T0.4197,0.1375 T0.425,0.1395 Q0.429,0.1395,0.4355,0.1365 Q0.4631,0.1266,0.4882,0.1266 Q0.5126,0.1266,0.5337,0.1246 Q0.5369,0.1246,0.5458,0.1207 T0.5661,0.1167 L0.5693,0.1167 Q0.5791,0.1177,0.5852,0.1187 T0.5921,0.1197 Q0.5945,0.1197,0.6002,0.1167 Q0.6026,0.1147,0.605,0.1123 T0.6091,0.1093 T0.6156,0.1088 T0.6318,0.1098 Q0.6456,0.1108,0.6569,0.1137 T0.6707,0.1167 Q0.674,0.1167,0.6772,0.1147 Q0.6821,0.1108,0.6946,0.1083 T0.721,0.1044 T0.7453,0.1029 L0.7526,0.1029 Q0.7583,0.1039,0.7652,0.1053 T0.7753,0.1068 Q0.7778,0.1068,0.7802,0.1058 Q0.7981,0.1019,0.8143,0.1019 Q0.8175,0.1019,0.8439,0.0964 T0.8735,0.091 Q0.8759,0.091,0.8836,0.089 T0.8938,0.0851 Q0.8929,0.0841,0.8905,0.0841 Q0.8832,0.0821,0.8735,0.0821 Q0.8694,0.0821,0.8629,0.0826 T0.854,0.0831 Q0.8508,0.0831,0.8491,0.0821 Q0.8475,0.0821,0.8171,0.0811 T0.7802,0.0781 T0.7571,0.0737 T0.7332,0.0712 Q0.7299,0.0712,0.7247,0.0717 T0.7161,0.0722 L0.7064,0.0722 Q0.7032,0.0712,0.7003,0.0707 T0.6959,0.0692 T0.693,0.0678 T0.6914,0.0663 T0.6902,0.0648 T0.689,0.0643 L0.6869,0.0643 L0.6845,0.0643 L0.6821,0.0643 Q0.6553,0.0643,0.6553,0.0554 T0.6796,0.0465 L0.6813,0.0465 L0.6845,0.0465 Q0.6918,0.0425,0.717,0.0376 Q0.7226,0.0366,0.7267,0.0356 T0.7324,0.0336 T0.7376,0.0317 T0.747,0.0297 Q0.7518,0.0287,0.7551,0.0287 T0.762,0.0297 T0.768,0.0307 L0.7729,0.0307 Q0.7802,0.0277,0.7997,0.0267 T0.8224,0.0237 Q0.8273,0.0227,0.8341,0.0223 T0.8475,0.0193 T0.8654,0.0143 T0.8873,0.0079 Q0.9002,0.003,0.9331,0.004 T0.97,0.004 Q0.9838,0,0.9976,0.001 Q1,0.001,1,0.003 Z M0.9935,0.996 L0.9951,0.0049 Q0.9822,0.0049,0.9716,0.0079 Q0.9659,0.0099,0.9327,0.0089 T0.8897,0.0119 Q0.8775,0.0158,0.8666,0.0183 T0.85,0.0237 Q0.8418,0.0267,0.8341,0.0272 T0.8248,0.0277 Q0.82,0.0297,0.8001,0.0312 T0.7745,0.0346 Q0.7697,0.0356,0.7664,0.0356 Q0.764,0.0356,0.7603,0.0341 T0.7534,0.0326 Q0.751,0.0326,0.7478,0.0336 Q0.7429,0.0346,0.7397,0.0356 T0.7344,0.0376 T0.7283,0.0401 T0.7178,0.0425 Q0.6926,0.0465,0.6878,0.0495 Q0.6845,0.0514,0.6764,0.0514 L0.6723,0.0514 L0.6691,0.0514 Q0.6602,0.0514,0.6602,0.0554 Q0.6602,0.0574,0.6659,0.0579 T0.6796,0.0589 T0.691,0.0603 T0.6967,0.0643 T0.7072,0.0673 Q0.7121,0.0682,0.7153,0.0682 T0.7238,0.0678 T0.7332,0.0673 Q0.7413,0.0673,0.7579,0.0692 T0.7818,0.0742 Q0.7867,0.0752,0.8171,0.0762 T0.8508,0.0781 L0.852,0.0781 T0.8601,0.0776 T0.8719,0.0772 Q0.8832,0.0772,0.8921,0.0791 Q0.8994,0.0811,0.8994,0.0841 T0.8933,0.0895 T0.8812,0.094 T0.8735,0.0959 T0.8451,0.1014 T0.8143,0.1068 Q0.7989,0.1068,0.7818,0.1098 Q0.7778,0.1108,0.7745,0.1108 T0.768,0.1103 T0.7603,0.1088 T0.7518,0.1068 L0.7461,0.1068 Q0.7315,0.1068,0.7088,0.1103 T0.6805,0.1177 Q0.6756,0.1207,0.6699,0.1207 Q0.6667,0.1207,0.6545,0.1177 T0.631,0.1147 Q0.6269,0.1137,0.6225,0.1133 T0.616,0.1128 L0.6123,0.1128 T0.6103,0.1133 L0.6083,0.1157 T0.6034,0.1197 Q0.5961,0.1246,0.5921,0.1246 Q0.5904,0.1246,0.5835,0.1231 T0.5693,0.1217 L0.5661,0.1217 Q0.558,0.1217,0.5535,0.1231 T0.5446,0.1266 T0.5345,0.1296 Q0.5126,0.1316,0.4882,0.1316 T0.438,0.1405 Q0.4298,0.1434,0.425,0.1434 Q0.4209,0.1434,0.4173,0.1419 T0.4112,0.1405 T0.4027,0.1419 T0.3869,0.1434 Q0.3682,0.1434,0.3512,0.1494 Q0.3398,0.1533,0.3333,0.1533 Q0.3285,0.1533,0.3228,0.1513 L0.3212,0.1513 Q0.3179,0.1513,0.3155,0.1538 T0.3131,0.1592 Q0.3131,0.1612,0.3147,0.1632 Q0.3171,0.1652,0.3232,0.1667 T0.3406,0.1691 T0.3544,0.1701 L0.3601,0.1701 L0.3678,0.1701 L0.3771,0.1701 Q0.3909,0.1701,0.4031,0.1731 Q0.4177,0.178,0.4558,0.178 Q0.4793,0.178,0.4882,0.1761 L0.4915,0.1761 Q0.498,0.1761,0.5061,0.179 T0.5158,0.183 Q0.5166,0.183,0.5231,0.1835 T0.5341,0.1845 T0.545,0.1855 T0.5572,0.1874 T0.5661,0.1909 Q0.5677,0.1919,0.5702,0.1919 T0.5791,0.1894 T0.588,0.1869 Q0.5921,0.1869,0.5945,0.1899 Q0.5977,0.1919,0.6123,0.1919 Q0.6334,0.1919,0.6448,0.1879 Q0.6488,0.186,0.6537,0.186 Q0.6626,0.186,0.6784,0.1909 T0.6983,0.1958 Q0.704,0.1968,0.7206,0.1973 T0.7506,0.1993 T0.7689,0.2018 L0.7705,0.2018 L0.7741,0.2018 L0.7786,0.2018 Q0.8248,0.2018,0.8475,0.2136 Q0.8597,0.2196,0.8597,0.2255 T0.8483,0.2359 T0.8281,0.2404 Q0.8208,0.2404,0.7534,0.2493 Q0.7502,0.2502,0.7425,0.2512 T0.7287,0.2532 T0.7178,0.2542 L0.7153,0.2542 L0.7137,0.2542 Q0.7064,0.2542,0.6906,0.2591 T0.6715,0.2681 T0.6626,0.272 Q0.6594,0.272,0.6541,0.2705 T0.6464,0.269 T0.6431,0.27 Q0.6318,0.273,0.6034,0.276 Q0.6018,0.276,0.5998,0.2774 T0.5957,0.2799 T0.5904,0.2809 L0.5856,0.2809 Q0.5823,0.2799,0.575,0.2799 Q0.541,0.2799,0.5239,0.2868 Q0.5134,0.2908,0.5028,0.2908 Q0.4996,0.2908,0.4935,0.2903 T0.4842,0.2898 Q0.4704,0.2898,0.4635,0.2918 T0.4534,0.2997 L0.4534,0.3007 Q0.4534,0.3037,0.459,0.3046 T0.4753,0.3066 T0.4899,0.3076 Q0.4907,0.3076,0.5166,0.3106 T0.5507,0.3155 Q0.5921,0.3264,0.605,0.3264 Q0.6107,0.3264,0.6131,0.3254 Q0.6212,0.3225,0.6294,0.3225 Q0.6407,0.3225,0.6513,0.3274 Q0.6529,0.3294,0.6569,0.3294 Q0.6594,0.3294,0.665,0.3284 T0.6748,0.3274 T0.6821,0.3284 Q0.6861,0.3294,0.6967,0.3299 T0.7165,0.3314 T0.7299,0.3343 Q0.7324,0.3363,0.7486,0.3383 T0.7697,0.3422 Q0.7713,0.3432,0.7774,0.3452 T0.7887,0.3492 T0.8005,0.3546 T0.8127,0.3625 T0.8208,0.3719 Q0.8256,0.3798,0.8256,0.3848 Q0.8256,0.3877,0.8236,0.3902 T0.8171,0.3976 T0.809,0.4075 T0.8021,0.4189 T0.7968,0.4273 T0.7867,0.4322 T0.7616,0.4382 Q0.7543,0.4392,0.7437,0.4392 L0.7364,0.4392 L0.7299,0.4392 Q0.7202,0.4392,0.7161,0.4402 Q0.7072,0.4431,0.691,0.4431 L0.6845,0.4431 L0.6788,0.4431 Q0.6707,0.4431,0.6545,0.4461 T0.6334,0.452 Q0.6399,0.456,0.6626,0.457 Q0.6878,0.458,0.7024,0.4639 Q0.7064,0.4649,0.7186,0.4649 L0.7295,0.4649 L0.7413,0.4649 Q0.7591,0.4649,0.7697,0.4669 Q0.7818,0.4688,0.7818,0.4728 Q0.7818,0.4748,0.7798,0.4763 T0.7753,0.4787 L0.7737,0.4797 L0.7729,0.4797 Q0.7494,0.4827,0.7388,0.4827 Q0.7332,0.4827,0.7307,0.4817 L0.7291,0.4817 Q0.7226,0.4817,0.6878,0.4896 Q0.6829,0.4906,0.6772,0.4911 T0.6618,0.4921 T0.646,0.4931 T0.6342,0.4936 T0.6212,0.4931 T0.6115,0.4926 Q0.6083,0.4926,0.6058,0.4931 T0.5969,0.4955 T0.5819,0.499 T0.5629,0.5005 Q0.5572,0.5005,0.5527,0.501 T0.545,0.502 T0.5397,0.503 T0.5361,0.5045 T0.5337,0.5064 T0.5328,0.5094 L0.532,0.5094 Q0.532,0.5104,0.5337,0.5109 T0.5381,0.5114 T0.5438,0.5119 T0.5515,0.5124 L0.5604,0.5124 Q0.5661,0.5134,0.5746,0.5153 T0.5912,0.5173 L0.5977,0.5173 L0.6026,0.5173 Q0.6139,0.5173,0.6273,0.5218 T0.6407,0.5272 Q0.6448,0.5282,0.6683,0.5321 T0.6991,0.5381 Q0.7137,0.543,0.7664,0.548 Q0.7908,0.55,0.7908,0.5559 Q0.7908,0.5589,0.7859,0.5628 Q0.781,0.5658,0.7713,0.5658 Q0.7689,0.5658,0.7628,0.5653 T0.7543,0.5648 T0.7502,0.5658 Q0.7364,0.5678,0.6878,0.5678 Q0.674,0.5678,0.6699,0.5668 L0.6683,0.5668 Q0.6659,0.5668,0.6606,0.5678 T0.6513,0.5687 Q0.6464,0.5687,0.6391,0.5678 Q0.6334,0.5668,0.6302,0.5668 T0.6241,0.5687 T0.6164,0.5717 Q0.6123,0.5727,0.6099,0.5727 L0.6062,0.5727 T0.6022,0.5732 T0.5953,0.5737 Q0.5904,0.5747,0.5839,0.5747 T0.5726,0.5752 T0.5629,0.5776 T0.5458,0.5796 Q0.5418,0.5796,0.5324,0.5791 T0.5191,0.5786 Q0.5077,0.5786,0.5045,0.5806 Q0.498,0.5836,0.4899,0.5836 Q0.4858,0.5836,0.4777,0.5826 T0.4655,0.5816 L0.4631,0.5816 Q0.4582,0.5816,0.4436,0.5841 T0.4144,0.5865 L0.4051,0.5865 L0.3986,0.5865 T0.3933,0.587 T0.3897,0.5875 T0.3869,0.5885 T0.3844,0.5895 T0.3812,0.591 T0.3779,0.5925 Q0.3739,0.5955,0.3633,0.5955 L0.3585,0.5955 L0.3536,0.5955 Q0.3277,0.5955,0.3066,0.6053 Q0.3017,0.6083,0.296,0.6083 Q0.2912,0.6083,0.2839,0.6068 T0.2741,0.6053 Q0.2693,0.6053,0.2547,0.6048 T0.236,0.6044 Q0.2222,0.6044,0.193,0.6103 L0.1882,0.6103 Q0.1825,0.6103,0.1703,0.6083 T0.1525,0.6063 L0.15,0.6063 Q0.1436,0.6063,0.135,0.6073 T0.1168,0.6098 T0.1038,0.6113 Q0.0973,0.6123,0.0787,0.6123 L0.06,0.6123 L0.0414,0.6123 L0.0235,0.6123 Q0.0049,0.6133,0.0049,0.6172 L0.0057,0.6222 Q0.0073,0.6261,0.043,0.6261 L0.0454,0.6261 L0.0479,0.6261 Q0.0527,0.6261,0.0633,0.6281 T0.0803,0.6301 L0.0835,0.6301 L0.0916,0.6301 Q0.0998,0.6301,0.129,0.632 Q0.1768,0.636,0.206,0.64 Q0.2117,0.6409,0.2165,0.6409 Q0.2206,0.6409,0.2263,0.6405 T0.2336,0.64 L0.2384,0.64 L0.2409,0.64 T0.251,0.6395 T0.2644,0.639 Q0.2741,0.639,0.279,0.6409 Q0.283,0.6429,0.2972,0.6439 T0.3293,0.6459 T0.3528,0.6469 L0.3577,0.6469 Q0.3642,0.6469,0.3723,0.6449 T0.3844,0.6429 Q0.3901,0.6429,0.3933,0.6454 T0.4006,0.6479 Q0.4088,0.6479,0.4517,0.6558 Q0.4582,0.6568,0.4623,0.6568 Q0.4655,0.6568,0.4716,0.6563 T0.4826,0.6558 L0.4882,0.6558 Q0.5004,0.6558,0.5255,0.6607 Q0.5328,0.6627,0.5604,0.6647 T0.6152,0.6682 T0.644,0.6696 T0.6719,0.6721 T0.7056,0.6756 Q0.7113,0.6756,0.7198,0.6805 T0.738,0.6855 Q0.7453,0.6855,0.7551,0.6879 T0.7741,0.6939 T0.7912,0.7018 T0.8029,0.7102 Q0.8054,0.7132,0.8054,0.7151 Q0.8054,0.7191,0.8009,0.7216 T0.7908,0.7255 T0.779,0.7285 T0.7721,0.731 Q0.7672,0.7329,0.7632,0.7339 T0.7551,0.7354 T0.7486,0.7369 Q0.7478,0.7369,0.7445,0.7379 T0.7388,0.7394 T0.7324,0.7404 L0.7242,0.7413 T0.7161,0.7418 Q0.7105,0.7418,0.6955,0.7428 T0.6748,0.7438 L0.6699,0.7438 L0.6667,0.7438 Q0.6659,0.7438,0.6407,0.7468 Q0.6367,0.7478,0.6334,0.7478 T0.6245,0.7468 T0.6148,0.7458 L0.6099,0.7458 Q0.6018,0.7468,0.5864,0.7498 T0.5653,0.7532 T0.5596,0.7557 Q0.5596,0.7596,0.571,0.7616 Q0.5742,0.7626,0.5803,0.7631 T0.5929,0.7641 T0.6018,0.7656 L0.6087,0.7656 T0.6192,0.7666 T0.6277,0.7695 Q0.631,0.7705,0.6342,0.7705 Q0.6383,0.7705,0.6452,0.7685 T0.6537,0.7666 L0.6553,0.7666 Q0.6602,0.7666,0.6711,0.769 T0.6853,0.7715 Q0.691,0.7715,0.7048,0.775 T0.7242,0.7814 Q0.7324,0.7854,0.7445,0.7864 Q0.7478,0.7864,0.768,0.7873 T0.7972,0.7893 Q0.7989,0.7893,0.8017,0.7898 T0.8054,0.7903 L0.8082,0.7903 T0.811,0.7908 T0.8131,0.7913 T0.8151,0.7918 L0.8167,0.7928 T0.8179,0.7943 T0.8191,0.7957 T0.8204,0.7977 T0.8208,0.8002 Q0.8208,0.8032,0.8171,0.8051 T0.8074,0.8081 T0.7997,0.8091 Q0.7932,0.8121,0.7737,0.8121 Q0.7664,0.8121,0.7413,0.8111 L0.7364,0.8111 Q0.7275,0.8111,0.7214,0.8121 T0.7084,0.8155 T0.6991,0.819 Q0.6934,0.82,0.6723,0.8205 T0.6472,0.8239 Q0.6423,0.8269,0.6375,0.8269 Q0.6358,0.8269,0.6338,0.8264 T0.6294,0.8254 T0.6253,0.8249 Q0.6042,0.8249,0.5961,0.8289 Q0.5921,0.8318,0.5839,0.8318 Q0.5783,0.8318,0.5645,0.8299 L0.5604,0.8299 Q0.5531,0.8299,0.5337,0.8368 Q0.5296,0.8378,0.5255,0.8378 Q0.5239,0.8378,0.5195,0.8373 T0.5118,0.8368 Q0.5061,0.8368,0.4996,0.8388 Q0.4947,0.8388,0.4882,0.8398 Q0.4777,0.8417,0.4753,0.8427 T0.4728,0.8467 Q0.4728,0.8487,0.4769,0.8492 T0.4886,0.8501 T0.5004,0.8516 Q0.5036,0.8516,0.515,0.8521 T0.5345,0.8546 Q0.5426,0.8556,0.5596,0.8556 L0.5649,0.8556 L0.5702,0.8556 L0.5791,0.8556 Q0.5921,0.8566,0.6135,0.8571 T0.6383,0.8586 Q0.6683,0.8615,0.6764,0.8615 L0.6776,0.8615 L0.6788,0.8615 Q0.7032,0.8615,0.7032,0.8684 Q0.7032,0.8714,0.6995,0.8749 T0.6959,0.8803 L0.6959,0.8813 Q0.6991,0.8843,0.7153,0.8843 Q0.7218,0.8843,0.7259,0.8833 L0.7299,0.8833 Q0.7324,0.8833,0.7348,0.8838 T0.7393,0.8848 T0.7433,0.8863 L0.7474,0.8882 T0.751,0.8892 Q0.7616,0.8922,0.7948,0.8952 Q0.7981,0.8952,0.8082,0.8981 T0.8297,0.9011 T0.8528,0.9026 T0.8747,0.907 T0.8856,0.9149 L0.8856,0.9159 Q0.8856,0.9179,0.8848,0.9189 T0.8828,0.9209 T0.88,0.9228 T0.8767,0.9243 T0.8731,0.9248 T0.8698,0.9253 T0.8666,0.9258 L0.8646,0.9258 Q0.8621,0.9258,0.8577,0.9263 T0.85,0.9268 T0.8439,0.9273 T0.8402,0.9278 Q0.8386,0.9298,0.8358,0.9308 T0.8305,0.9322 T0.8244,0.9332 T0.8183,0.9337 Q0.8135,0.9347,0.8025,0.9372 T0.7859,0.9416 Q0.7778,0.9436,0.7717,0.9471 T0.7624,0.9515 Q0.7575,0.9535,0.7486,0.9535 Q0.7461,0.9535,0.7397,0.953 T0.7291,0.9525 Q0.7234,0.9525,0.7186,0.953 T0.7137,0.957 T0.7194,0.9614 Q0.7567,0.9634,0.7664,0.9654 Q0.7745,0.9674,0.7762,0.9674 L0.7908,0.9674 Q0.7924,0.9664,0.7948,0.9664 Q0.8029,0.9664,0.8082,0.9679 T0.8224,0.9723 T0.8362,0.9773 Q0.8483,0.9812,0.871,0.9812 L0.8751,0.9812 Q0.8873,0.9812,0.8929,0.9822 T0.9165,0.9876 T0.9408,0.9931 Q0.9513,0.996,0.9935,0.996 Z M0.9968,0.998 L0.9976,0.003 Q0.9838,0.002,0.9708,0.0059 Q0.9659,0.0069,0.9327,0.0059 T0.8881,0.0099 Q0.8767,0.0138,0.8658,0.0163 T0.8491,0.0218 Q0.8418,0.0247,0.8341,0.0247 T0.8232,0.0257 Q0.8191,0.0277,0.7997,0.0292 T0.7737,0.0326 Q0.7697,0.0336,0.7672,0.0336 T0.7612,0.0321 T0.7543,0.0307 T0.747,0.0317 Q0.7397,0.0326,0.7336,0.0356 T0.717,0.0406 Q0.6918,0.0445,0.6861,0.0475 Q0.6845,0.0485,0.6772,0.0485 T0.6638,0.05 T0.6577,0.0549 T0.6642,0.0598 T0.6796,0.0613 T0.6902,0.0623 Q0.691,0.0623,0.6946,0.0658 T0.7064,0.0702 L0.7161,0.0702 Q0.7194,0.0702,0.7242,0.0697 T0.7332,0.0692 Q0.7413,0.0692,0.7579,0.0717 T0.781,0.0762 Q0.7867,0.0781,0.8171,0.0786 T0.85,0.0801 L0.8532,0.0801 Q0.8548,0.0801,0.8613,0.0796 T0.8727,0.0791 Q0.8832,0.0791,0.8913,0.0811 Q0.897,0.0831,0.897,0.0851 Q0.897,0.088,0.8873,0.0905 T0.8735,0.093 Q0.871,0.093,0.8447,0.0984 T0.8143,0.1039 Q0.7981,0.1039,0.781,0.1078 Q0.7778,0.1088,0.7745,0.1088 T0.7644,0.1073 T0.7518,0.1048 L0.7453,0.1048 Q0.7299,0.1048,0.7076,0.1083 T0.6788,0.1157 Q0.6748,0.1187,0.6699,0.1187 Q0.6675,0.1187,0.6557,0.1157 T0.6318,0.1118 Q0.6164,0.1108,0.6135,0.1108 T0.6095,0.1113 T0.6067,0.1142 T0.6018,0.1187 Q0.5953,0.1227,0.5921,0.1227 Q0.5904,0.1227,0.5839,0.1212 T0.5693,0.1187 L0.5661,0.1187 Q0.558,0.1187,0.5527,0.1207 T0.5434,0.1246 T0.5337,0.1266 Q0.5126,0.1296,0.4882,0.1296 T0.4371,0.1385 Q0.4298,0.1414,0.425,0.1414 Q0.4225,0.1414,0.4189,0.1395 T0.4112,0.1375 Q0.4079,0.1375,0.4019,0.1395 T0.3869,0.1414 Q0.3682,0.1414,0.3504,0.1474 Q0.3398,0.1513,0.3333,0.1513 Q0.3293,0.1513,0.3228,0.1494 L0.3204,0.1494 Q0.3179,0.1494,0.3155,0.1508 T0.3118,0.1543 T0.3106,0.1592 T0.3127,0.1642 T0.32,0.1677 T0.3305,0.1701 T0.3435,0.1716 T0.3544,0.1721 Q0.3569,0.1731,0.3601,0.1731 Q0.3625,0.1731,0.3686,0.1726 T0.3779,0.1721 Q0.3901,0.1721,0.4023,0.1751 Q0.4177,0.18,0.455,0.18 Q0.4793,0.18,0.4882,0.178 L0.4915,0.178 Q0.4964,0.178,0.5045,0.1815 T0.5158,0.185 Q0.5564,0.1889,0.5645,0.1929 Q0.5677,0.1939,0.571,0.1939 Q0.575,0.1939,0.5807,0.1919 T0.5888,0.1899 T0.5929,0.1909 Q0.5969,0.1949,0.6123,0.1949 Q0.6334,0.1949,0.6456,0.1899 Q0.6488,0.1879,0.6537,0.1879 Q0.661,0.1879,0.6772,0.1929 T0.6983,0.1978 Q0.704,0.1988,0.7206,0.1998 T0.7502,0.2018 T0.7664,0.2038 Q0.7672,0.2038,0.7818,0.2043 T0.8151,0.2067 T0.8459,0.2156 Q0.8573,0.2206,0.8573,0.2255 Q0.8573,0.2295,0.8516,0.2324 T0.8394,0.2369 T0.8281,0.2384 Q0.8208,0.2384,0.7526,0.2473 Q0.7494,0.2483,0.7421,0.2493 T0.7287,0.2512 T0.7178,0.2522 L0.7153,0.2522 L0.7137,0.2522 Q0.7056,0.2522,0.6898,0.2572 T0.6691,0.2671 Q0.6667,0.269,0.6634,0.269 Q0.661,0.269,0.6557,0.2681 T0.6472,0.2671 Q0.6448,0.2671,0.6415,0.2681 Q0.631,0.271,0.6034,0.274 Q0.5994,0.274,0.5961,0.2765 T0.5904,0.2789 L0.5864,0.2789 Q0.5831,0.2779,0.5742,0.2779 Q0.541,0.2779,0.5223,0.2849 Q0.5126,0.2888,0.5028,0.2888 Q0.5004,0.2888,0.4943,0.2883 T0.4842,0.2878 Q0.4704,0.2878,0.4627,0.2898 T0.4509,0.2987 Q0.4501,0.2997,0.4501,0.3007 Q0.4501,0.3046,0.4574,0.3066 T0.4765,0.3091 T0.4899,0.3096 Q0.4931,0.3106,0.5061,0.3121 T0.5316,0.315 T0.5499,0.3175 Q0.5507,0.3175,0.5572,0.3195 T0.5685,0.3225 T0.5803,0.3254 T0.5933,0.3279 T0.605,0.3284 T0.6148,0.3274 Q0.6221,0.3244,0.6294,0.3244 Q0.6399,0.3244,0.6496,0.3294 Q0.6529,0.3314,0.6577,0.3314 Q0.6602,0.3314,0.6663,0.3304 T0.6756,0.3294 T0.6813,0.3304 Q0.6837,0.3304,0.6873,0.3309 L0.6946,0.3318 T0.7024,0.3323 T0.7105,0.3328 T0.7178,0.3338 T0.7238,0.3348 T0.7283,0.3363 Q0.7324,0.3383,0.7482,0.3403 T0.7689,0.3442 Q0.7697,0.3442,0.779,0.3477 T0.794,0.3536 T0.8074,0.3615 T0.8183,0.3729 Q0.8232,0.3808,0.8232,0.3848 Q0.8232,0.3867,0.8216,0.3892 T0.8151,0.3961 T0.8062,0.406 T0.7997,0.4179 T0.7952,0.4263 T0.7855,0.4308 T0.7616,0.4362 Q0.7543,0.4372,0.7445,0.4372 L0.7372,0.4372 L0.7307,0.4372 Q0.7202,0.4372,0.7153,0.4382 Q0.7064,0.4402,0.691,0.4402 L0.6845,0.4402 L0.6788,0.4402 Q0.6699,0.4402,0.6533,0.4436 T0.6318,0.451 Q0.6302,0.451,0.6302,0.452 Q0.6302,0.457,0.6626,0.459 Q0.6869,0.4609,0.7015,0.4659 Q0.7064,0.4669,0.7202,0.4669 L0.7311,0.4669 L0.7429,0.4669 Q0.7599,0.4669,0.7689,0.4688 Q0.7794,0.4708,0.7794,0.4738 L0.7786,0.4748 T0.777,0.4763 T0.7749,0.4773 L0.7737,0.4777 L0.7729,0.4777 Q0.7502,0.4797,0.738,0.4797 L0.7315,0.4797 L0.7291,0.4797 Q0.7226,0.4797,0.6869,0.4876 Q0.6821,0.4886,0.6768,0.4891 T0.6614,0.4901 T0.6456,0.4916 L0.6342,0.4916 Q0.6294,0.4916,0.6217,0.4911 T0.6115,0.4906 Q0.6083,0.4906,0.6054,0.4911 T0.5961,0.4936 T0.5815,0.4965 T0.5629,0.4985 Q0.5507,0.4985,0.5442,0.4995 T0.5345,0.5025 T0.5304,0.5084 L0.5296,0.5094 Q0.5296,0.5114,0.5316,0.5124 T0.5373,0.5138 T0.5442,0.5143 L0.5523,0.5143 L0.5604,0.5143 Q0.5637,0.5153,0.5677,0.5158 T0.5742,0.5173 T0.5811,0.5188 T0.5904,0.5193 L0.5977,0.5193 L0.6026,0.5193 Q0.6107,0.5193,0.6168,0.5208 T0.6294,0.5252 T0.6399,0.5292 Q0.6448,0.5302,0.6679,0.5341 T0.6983,0.541 Q0.7129,0.545,0.7664,0.55 Q0.7883,0.5519,0.7883,0.5559 Q0.7883,0.5579,0.7843,0.5608 T0.7721,0.5638 Q0.7697,0.5638,0.764,0.5633 T0.7559,0.5628 L0.7502,0.5628 Q0.7364,0.5648,0.6869,0.5648 L0.6699,0.5648 L0.6675,0.5648 Q0.665,0.5648,0.6602,0.5658 T0.6513,0.5668 Q0.6464,0.5668,0.6399,0.5658 Q0.6342,0.5648,0.6302,0.5648 T0.6225,0.5668 T0.6156,0.5697 Q0.6107,0.5707,0.6087,0.5707 T0.6034,0.5712 T0.5953,0.5717 Q0.5904,0.5727,0.5835,0.5727 T0.5718,0.5732 T0.562,0.5757 Q0.558,0.5767,0.5483,0.5767 L0.5349,0.5767 L0.5207,0.5767 Q0.5069,0.5767,0.5028,0.5786 Q0.4972,0.5816,0.4899,0.5816 Q0.4858,0.5816,0.4781,0.5806 T0.4663,0.5796 L0.4631,0.5796 Q0.4582,0.5796,0.4432,0.5821 T0.4144,0.5846 L0.4071,0.5846 L0.4011,0.5846 T0.3958,0.5851 T0.3921,0.5856 T0.3889,0.5861 T0.3865,0.5865 T0.384,0.5875 L0.382,0.5885 L0.3796,0.5895 L0.3771,0.5905 Q0.3731,0.5925,0.365,0.5925 L0.3601,0.5925 L0.3552,0.5925 Q0.3277,0.5925,0.3049,0.6044 Q0.3017,0.6053,0.296,0.6053 Q0.292,0.6053,0.2847,0.6044 T0.2741,0.6034 Q0.2693,0.6034,0.2547,0.6029 T0.236,0.6024 Q0.2222,0.6024,0.1922,0.6073 Q0.1906,0.6083,0.1882,0.6083 Q0.1833,0.6083,0.1711,0.6063 T0.1533,0.6044 Q0.1517,0.6034,0.1492,0.6034 Q0.1436,0.6034,0.135,0.6048 T0.1168,0.6078 T0.103,0.6093 Q0.0973,0.6103,0.0787,0.6103 L0.0604,0.6103 L0.0414,0.6103 L0.0235,0.6103 Q0.0024,0.6113,0.0024,0.6172 Q0.0024,0.6192,0.0032,0.6231 Q0.0049,0.6281,0.0333,0.6281 L0.0414,0.6281 L0.0479,0.6281 Q0.0519,0.6281,0.0624,0.6301 T0.0803,0.632 L0.0835,0.632 L0.0916,0.632 Q0.0989,0.632,0.1281,0.634 L0.206,0.6419 Q0.2117,0.6429,0.2174,0.6429 Q0.2214,0.6429,0.2271,0.6424 T0.2344,0.6419 L0.2376,0.6419 Q0.2393,0.6429,0.2417,0.6429 T0.2526,0.6419 T0.2652,0.6409 Q0.2741,0.6409,0.2782,0.6429 T0.2968,0.6459 T0.3293,0.6479 T0.352,0.6489 Q0.3552,0.6499,0.3585,0.6499 Q0.365,0.6499,0.3731,0.6474 T0.3844,0.6449 Q0.3885,0.6449,0.3917,0.6474 T0.4006,0.6499 Q0.4088,0.6499,0.4509,0.6578 Q0.4574,0.6588,0.4631,0.6588 Q0.4655,0.6588,0.4716,0.6583 T0.4834,0.6578 L0.4874,0.6578 Q0.5004,0.6588,0.5255,0.6637 Q0.532,0.6647,0.56,0.6667 T0.6148,0.6701 T0.644,0.6716 Q0.6464,0.6726,0.6594,0.6736 T0.6861,0.6756 T0.7048,0.6775 Q0.7097,0.6775,0.7186,0.6825 T0.738,0.6874 Q0.7518,0.6874,0.7729,0.6958 T0.8005,0.7122 Q0.8029,0.7141,0.8029,0.7151 Q0.8029,0.7191,0.7968,0.7216 T0.7818,0.726 T0.7705,0.729 L0.764,0.731 T0.7583,0.7324 T0.753,0.7334 T0.7486,0.7349 Q0.7251,0.7399,0.7161,0.7399 Q0.7097,0.7399,0.6951,0.7409 T0.6748,0.7418 L0.6703,0.7418 L0.6667,0.7418 Q0.6659,0.7418,0.6399,0.7448 L0.6334,0.7448 Q0.631,0.7448,0.6249,0.7443 T0.6148,0.7438 L0.6099,0.7438 Q0.601,0.7448,0.5856,0.7473 T0.5645,0.7507 Q0.5572,0.7517,0.5572,0.7547 T0.5616,0.7606 T0.5702,0.7646 Q0.5734,0.7646,0.5779,0.7651 T0.5864,0.7661 T0.5949,0.7671 T0.6018,0.7676 L0.6071,0.7676 T0.6135,0.7681 T0.62,0.769 T0.6269,0.771 T0.6342,0.7725 Q0.6391,0.7725,0.6464,0.7705 T0.6541,0.7685 L0.6553,0.7685 Q0.6586,0.7685,0.6699,0.771 T0.6853,0.7735 Q0.6902,0.7735,0.704,0.777 T0.7226,0.7834 Q0.7315,0.7873,0.7445,0.7883 Q0.747,0.7883,0.7676,0.7893 T0.7972,0.7913 Q0.7989,0.7913,0.8017,0.7918 T0.8058,0.7923 T0.809,0.7928 T0.8114,0.7933 T0.8131,0.7938 T0.8147,0.7943 T0.8159,0.7953 L0.8171,0.7967 T0.8175,0.7982 Q0.8183,0.7992,0.8183,0.8002 Q0.8183,0.8022,0.8155,0.8032 T0.807,0.8051 T0.7989,0.8071 Q0.7932,0.8091,0.7737,0.8091 L0.7413,0.8091 L0.7372,0.8091 Q0.7275,0.8091,0.721,0.8101 T0.7072,0.8136 T0.6983,0.817 Q0.6942,0.818,0.6825,0.818 T0.6602,0.819 T0.6456,0.822 Q0.6415,0.8249,0.6375,0.8249 Q0.6367,0.8249,0.6326,0.8239 T0.6253,0.8229 Q0.6034,0.8229,0.5953,0.8279 Q0.5912,0.8289,0.5848,0.8289 Q0.5775,0.8289,0.5645,0.8279 Q0.5629,0.8269,0.5604,0.8269 Q0.5523,0.8269,0.5328,0.8348 Q0.5296,0.8358,0.5264,0.8358 Q0.5247,0.8358,0.5203,0.8353 T0.5126,0.8348 Q0.5061,0.8348,0.4988,0.8358 Q0.4947,0.8368,0.4882,0.8378 Q0.4826,0.8388,0.4797,0.8388 T0.474,0.8398 T0.4704,0.8422 T0.4696,0.8462 T0.472,0.8501 T0.4781,0.8521 T0.4854,0.8526 L0.4935,0.8526 T0.4996,0.8536 Q0.5036,0.8536,0.515,0.8546 T0.5337,0.8566 Q0.5426,0.8576,0.5596,0.8576 L0.5657,0.8576 L0.571,0.8576 L0.5791,0.8576 Q0.5921,0.8586,0.6135,0.8591 T0.6383,0.8605 Q0.6683,0.8635,0.6764,0.8635 Q0.6999,0.8635,0.6999,0.8684 Q0.6999,0.8694,0.6967,0.8734 T0.6934,0.8803 L0.6934,0.8823 Q0.6951,0.8863,0.7137,0.8863 Q0.7218,0.8863,0.7267,0.8853 L0.7299,0.8853 Q0.734,0.8853,0.7409,0.8882 T0.7502,0.8912 Q0.7607,0.8942,0.794,0.8971 Q0.7972,0.8971,0.8078,0.9001 T0.8297,0.9031 Q0.8475,0.9031,0.8646,0.9065 T0.8824,0.9149 L0.8824,0.9159 Q0.8824,0.9169,0.882,0.9179 T0.8808,0.9194 L0.8792,0.9204 T0.8767,0.9214 T0.8743,0.9224 T0.8715,0.9228 L0.8686,0.9228 T0.8662,0.9233 T0.8637,0.9238 L0.8556,0.9238 T0.8447,0.9243 T0.8386,0.9268 Q0.837,0.9278,0.835,0.9288 T0.8305,0.9303 T0.8244,0.9308 T0.8175,0.9318 Q0.8127,0.9327,0.8017,0.9352 T0.7851,0.9397 Q0.777,0.9416,0.7705,0.9451 T0.7616,0.9496 Q0.7575,0.9505,0.7494,0.9505 L0.7405,0.9505 L0.7307,0.9505 Q0.7234,0.9505,0.7178,0.9515 Q0.7145,0.9515,0.7125,0.9535 T0.7105,0.9575 Q0.7105,0.9624,0.7194,0.9634 Q0.7567,0.9654,0.7664,0.9674 Q0.7745,0.9693,0.7762,0.9693 L0.7908,0.9693 L0.7948,0.9693 Q0.8005,0.9693,0.8058,0.9698 T0.8143,0.9718 T0.824,0.9753 T0.8354,0.9792 Q0.8483,0.9832,0.871,0.9832 L0.8739,0.9832 L0.8767,0.9832 Q0.8873,0.9832,0.8921,0.9842 Q0.8978,0.9852,0.9157,0.9896 T0.94,0.996 Q0.9424,0.996,0.9469,0.9965 T0.9566,0.997 T0.9676,0.9975 T0.9781,0.998 L0.9874,0.998 L0.9943,0.998 L0.9968,0.998 Z",type:0}},"":{type:5,data:{d:"M1,0.5071 L0.8623,0.7102 L0.5865,0.7102 L0.4489,0.5071 L0.5865,0.3045 L0.8623,0.3045 Z M0.4135,0.7974 L0.2758,1 L0,1 L0,0.5944 L0.2758,0.5944 Z M0.4135,0.2026 L0.2758,0.4056 L0,0.4056 L0,0 L0.2758,0 Z",type:0}},"":{type:5,data:{d:"M0.9726,0.5071 L0.8451,0.3184 L0.59,0.3184 L0.4624,0.5071 L0.59,0.6955 L0.8451,0.6955 Z M1,0.5071 L0.8588,0.716 L0.5768,0.716 L0.4355,0.5071 L0.5768,0.2982 L0.8588,0.2982 Z M0.401,0.7911 L0.2735,0.6028 L0.0236,0.6028 L0.0236,0.9799 L0.2735,0.9799 Z M0.4284,0.7911 L0.2872,1 L0,1 L0,0.5826 L0.2872,0.5826 Z M0.401,0.2089 L0.2735,0.0201 L0.0236,0.0201 L0.0236,0.3972 L0.2735,0.3972 Z M0.4284,0.2089 L0.2872,0.4174 L0,0.4174 L0,0 L0.2872,0 Z",type:0}},"":{type:5,data:{d:"M1,0.4996 Q1,0.5365,0.9924,0.568 T0.9718,0.6177 T0.9433,0.6363 L0.9425,0.6367 Q0.9355,0.6367,0.9285,0.6326 L0.9308,0.6326 Q0.9129,0.6195,0.9013,0.587 T0.8871,0.5164 T0.8943,0.4373 T0.9254,0.3687 L0.9254,0.3691 Q0.9339,0.3628,0.9425,0.3628 Q0.9584,0.3628,0.9716,0.3811 T0.9924,0.431 T1,0.4996 Z M0.9234,0.6321 L0.8776,0.7169 Q0.8791,0.732,0.8791,0.7441 Q0.8791,0.781,0.8716,0.8123 T0.8508,0.862 T0.8224,0.8809 L0.8216,0.8809 Q0.8154,0.8809,0.8092,0.8775 L0.8088,0.8775 Q0.7905,0.8633,0.7792,0.8297 T0.766,0.7571 T0.775,0.6768 T0.8088,0.6086 L0.7431,0.6065 Q0.719,0.6313,0.7072,0.6705 T0.6965,0.7487 T0.7097,0.823 T0.7423,0.8763 L0.7913,0.8775 L0.7314,0.9895 L0.4648,0.5038 L0.7299,0.0176 L0.7882,0.1229 L0.7404,0.1212 Q0.7244,0.138,0.7136,0.1617 T0.6982,0.2108 T0.6943,0.2632 T0.7003,0.3144 T0.7159,0.3586 T0.7396,0.3909 L0.8072,0.3926 Q0.7894,0.3796,0.7777,0.3479 T0.7635,0.2789 T0.7697,0.2011 T0.7987,0.1326 Q0.8092,0.1225,0.8204,0.1225 Q0.8321,0.1225,0.8426,0.1332 T0.8609,0.1623 T0.8731,0.2062 T0.8776,0.2592 Q0.8776,0.268,0.8768,0.2815 L0.9219,0.3628 L0.8644,0.3612 Q0.8403,0.3855,0.8284,0.4247 T0.8177,0.5029 T0.8309,0.5772 T0.8632,0.6305 Z M0.6759,0.6384 Q0.6576,0.625,0.6457,0.5914 T0.6319,0.5185 T0.6407,0.4375 T0.6751,0.3691 L0.609,0.367 Q0.5849,0.3918,0.5731,0.431 T0.5626,0.509 T0.5758,0.5833 T0.6082,0.6367 Z M0.6459,0.0025 L0.391,0.4551 L0.0035,0.4551 L0.246,0 Z M0.6553,1 L0.2421,1 L0,0.5449 L0.3875,0.5449 Z",type:0}},"":{type:5,data:{d:"M0.4996,0 Q0.5562,0,0.5961,0.0166 T0.6364,0.0566 L0.6368,0.0575 Q0.6368,0.0646,0.6324,0.0713 L0.6324,0.0692 Q0.6196,0.0868,0.5872,0.0986 T0.5166,0.1128 T0.4373,0.1057 T0.3689,0.0747 L0.3694,0.0747 Q0.3627,0.0663,0.3627,0.0575 Q0.3627,0.0336,0.4028,0.0168 T0.4996,0 Z M0.6324,0.0768 L0.717,0.1225 Q0.7321,0.1212,0.744,0.1212 Q0.8007,0.1212,0.8406,0.1378 T0.8809,0.1779 L0.8813,0.1783 Q0.8813,0.1846,0.8778,0.1909 Q0.8636,0.2093,0.8299,0.2208 T0.7573,0.2343 T0.6769,0.2253 T0.6089,0.1913 L0.6067,0.2567 Q0.6315,0.281,0.6707,0.2928 T0.7489,0.3035 T0.8231,0.2903 T0.8764,0.258 L0.8778,0.2085 L0.9898,0.2685 L0.504,0.5352 L0.0177,0.2701 L0.1227,0.2118 L0.1213,0.2596 Q0.1457,0.2836,0.1851,0.2955 T0.2635,0.3062 T0.3377,0.293 T0.3911,0.2605 L0.3928,0.193 Q0.38,0.2106,0.3483,0.2221 T0.2792,0.2364 T0.2013,0.2303 T0.1324,0.2013 Q0.1222,0.1909,0.1222,0.1795 Q0.1222,0.156,0.1623,0.1393 T0.2591,0.1225 Q0.2679,0.1225,0.2817,0.1233 L0.3627,0.078 L0.3609,0.1359 Q0.3857,0.1598,0.4249,0.1718 T0.5031,0.1825 T0.5773,0.1693 T0.6306,0.1367 Z M0.6386,0.3242 Q0.6253,0.3427,0.5917,0.3544 T0.5186,0.3683 T0.4376,0.3595 T0.3689,0.3251 L0.3671,0.3909 Q0.3915,0.4148,0.4309,0.4268 T0.5093,0.4375 T0.5835,0.4243 T0.6368,0.3918 Z M0.0027,0.354 L0.4553,0.6091 L0.4553,0.9962 L0,0.7542 Z M1,0.3448 L1,0.7576 L0.5447,1 L0.5447,0.6128 Z",type:0}},"":{type:5,data:{d:"M0,0.7699 Q0,0.7172,0.0277,0.6731 T0.103,0.6032 T0.2066,0.5774 Q0.2487,0.5774,0.2865,0.5922 T0.3526,0.6339 L0.3935,0.6715 Q0.4218,0.6984,0.438,0.7336 T0.4541,0.8075 Q0.4541,0.8602,0.4264,0.9046 T0.3511,0.9745 T0.2475,1 Q0.2054,1,0.1676,0.9852 T0.1016,0.9435 L0.0606,0.9059 Q0.0317,0.879,0.0159,0.8438 T0,0.7699 Z M0.5459,0.7699 Q0.5459,0.7172,0.5736,0.6731 T0.6489,0.6032 T0.7525,0.5774 Q0.7946,0.5774,0.8324,0.5922 T0.8984,0.6339 L0.9394,0.6715 Q0.9579,0.6892,0.9714,0.7105 T0.9925,0.7567 T1,0.8075 Q1,0.8602,0.9723,0.9046 T0.897,0.9745 T0.7934,1 Q0.7513,1,0.7135,0.9852 T0.6474,0.9435 L0.6065,0.9059 Q0.5776,0.879,0.5617,0.8438 T0.5459,0.7699 Z M0,0.1925 Q0,0.1398,0.0277,0.0957 T0.103,0.0258 T0.2066,0 Q0.2487,0,0.2865,0.0148 T0.3526,0.0565 L0.3935,0.0941 Q0.4218,0.121,0.438,0.1562 T0.4541,0.2301 Q0.4541,0.2828,0.4264,0.3269 T0.3511,0.3968 T0.2475,0.4226 Q0.2054,0.4226,0.1676,0.4078 T0.1016,0.3661 L0.0606,0.3285 Q0.0317,0.3016,0.0159,0.2664 T0,0.1925 Z M0.5459,0.1925 Q0.5459,0.1398,0.5736,0.0957 T0.6489,0.0258 T0.7525,0 Q0.7946,0,0.8324,0.0148 T0.8984,0.0565 L0.9394,0.0941 Q0.9677,0.121,0.9838,0.1562 T1,0.2301 Q1,0.2828,0.9723,0.3269 T0.897,0.3968 T0.7934,0.4226 Q0.7513,0.4226,0.7135,0.4078 T0.6474,0.3661 L0.6065,0.3285 Q0.5874,0.3108,0.5741,0.2895 T0.5534,0.2433 T0.5459,0.1925 Z M0.3301,0.6403 Q0.3306,0.6403,0.3312,0.6409 L0.3272,0.6371 L0.3272,0.6371 Z M0.8759,0.6403 Q0.8765,0.6403,0.8771,0.6409 Q0.8748,0.6387,0.8725,0.6371 L0.8731,0.6371 Z M0.3301,0.0629 Q0.3306,0.0629,0.3312,0.0634 L0.3272,0.0597 L0.3272,0.0602 Z M0.8759,0.0629 Q0.8765,0.0629,0.8771,0.0634 Q0.8748,0.0613,0.8725,0.0597 Q0.8731,0.0597,0.8731,0.0602 Z M0.779,0.4113 L0.7501,0.3849 Q0.7046,0.3844,0.6636,0.3661 Q0.7132,0.407,0.779,0.4113 Z M0.7957,0.4118 Q0.8477,0.4113,0.8918,0.3874 T0.9619,0.3223 T0.9885,0.2323 L0.9585,0.2048 Q0.9538,0.2769,0.8987,0.3282 T0.7657,0.3844 Z M0.9879,0.2167 Q0.9827,0.1554,0.9388,0.1097 Q0.9585,0.1478,0.959,0.1898 Z M0.2331,0.4113 L0.2043,0.3849 Q0.1587,0.3844,0.1177,0.3661 Q0.1673,0.407,0.2331,0.4113 Z M0.2499,0.4118 Q0.2885,0.4118,0.324,0.3973 T0.3852,0.3589 T0.4264,0.3019 T0.4426,0.2323 L0.4126,0.2048 Q0.408,0.2769,0.3529,0.3282 T0.2198,0.3844 Z M0.442,0.2167 Q0.4368,0.1554,0.393,0.1097 Q0.4126,0.1478,0.4132,0.1903 Z M0.779,0.9887 L0.7501,0.9624 Q0.7046,0.9618,0.6636,0.9435 Q0.7132,0.9844,0.779,0.9887 Z M0.7957,0.9892 Q0.8477,0.9887,0.8918,0.9648 T0.9619,0.8997 T0.9885,0.8097 L0.9585,0.7823 Q0.9538,0.8543,0.8987,0.9056 T0.7657,0.9618 Z M0.9879,0.7941 Q0.9827,0.7328,0.9388,0.6871 Q0.9585,0.7253,0.959,0.7672 Z M0.2331,0.9887 L0.2043,0.9624 Q0.1587,0.9618,0.1177,0.9435 Q0.1673,0.9844,0.2331,0.9887 Z M0.2499,0.9892 Q0.3018,0.9887,0.3459,0.9648 T0.416,0.8997 T0.4426,0.8102 L0.4126,0.7823 Q0.408,0.8543,0.3529,0.9056 T0.2198,0.9618 Z M0.442,0.7941 Q0.4368,0.7328,0.393,0.6871 Q0.4126,0.7253,0.4132,0.7672 Z M0.3191,0.6452 Q0.2943,0.622,0.2643,0.6091 T0.2066,0.5962 Q0.1795,0.5962,0.1497,0.6091 T0.0949,0.6441 T0.0537,0.6995 T0.0375,0.7699 Q0.0375,0.8419,0.0941,0.8941 Q0.1189,0.9172,0.1489,0.9304 T0.2066,0.9435 Q0.2337,0.9435,0.2634,0.9304 T0.3182,0.8952 T0.3595,0.8398 T0.3756,0.7699 Q0.3756,0.6978,0.3191,0.6452 Z M0.026,0.7731 L0.026,0.7699 Q0.026,0.6935,0.0854,0.6376 Q0.0658,0.6538,0.0514,0.6739 T0.0289,0.718 T0.0202,0.7677 Z M0.0271,0.7892 L0.0208,0.7833 Q0.0254,0.8419,0.0681,0.886 Q0.0664,0.8828,0.0646,0.879 Q0.0329,0.8382,0.0271,0.7892 Z M0.3866,0.7823 Q0.382,0.8511,0.3278,0.9016 Q0.3843,0.8565,0.3918,0.7871 Z M0.3872,0.7677 L0.393,0.7726 L0.393,0.7699 Q0.393,0.7349,0.3791,0.7038 Q0.3739,0.6973,0.3681,0.6914 Q0.3866,0.7274,0.3872,0.7677 Z M0.865,0.6452 Q0.8402,0.622,0.8102,0.6091 T0.7525,0.5962 Q0.7305,0.5962,0.7072,0.6043 T0.6619,0.628 T0.6223,0.6642 T0.5941,0.7124 T0.5834,0.7699 Q0.5834,0.8419,0.6399,0.8941 Q0.6561,0.9097,0.6757,0.9207 T0.7149,0.9376 T0.7525,0.9435 Q0.7796,0.9435,0.8093,0.9304 T0.8641,0.8952 T0.9054,0.8398 T0.9215,0.7699 Q0.9215,0.6978,0.865,0.6452 Z M0.5718,0.7731 L0.5718,0.7699 Q0.5718,0.6935,0.6313,0.6376 Q0.6013,0.6618,0.584,0.6954 T0.5661,0.7677 Z M0.573,0.7892 L0.5666,0.7833 Q0.5713,0.8419,0.614,0.886 Q0.6122,0.8828,0.6105,0.879 Q0.5788,0.8382,0.573,0.7892 Z M0.9325,0.7823 Q0.9279,0.8511,0.8736,0.9016 Q0.9308,0.8565,0.9377,0.7871 Z M0.9331,0.7672 L0.9388,0.7726 L0.9388,0.7699 Q0.9388,0.7349,0.925,0.7038 Q0.9198,0.6973,0.914,0.6914 Q0.9325,0.7274,0.9331,0.7672 Z M0.3191,0.0677 Q0.2943,0.0446,0.2643,0.0317 T0.2066,0.0188 Q0.1795,0.0188,0.1497,0.0317 T0.0949,0.0667 T0.0537,0.122 T0.0375,0.1925 Q0.0375,0.2645,0.0941,0.3167 Q0.1189,0.3398,0.1489,0.353 T0.2066,0.3661 Q0.2337,0.3661,0.2634,0.353 T0.3182,0.3177 T0.3595,0.2624 T0.3756,0.1925 Q0.3756,0.1204,0.3191,0.0677 Z M0.026,0.1957 L0.026,0.1925 Q0.026,0.1161,0.0854,0.0602 Q0.0554,0.0844,0.0381,0.118 T0.0202,0.1903 Z M0.0271,0.2118 L0.0208,0.2059 Q0.0254,0.2645,0.0681,0.3086 Q0.0664,0.3054,0.0646,0.3016 Q0.0329,0.2608,0.0271,0.2118 Z M0.3866,0.2048 Q0.382,0.2737,0.3278,0.3242 Q0.3849,0.279,0.3918,0.2097 Z M0.3872,0.1903 L0.393,0.1952 L0.393,0.1925 Q0.393,0.1581,0.3791,0.1263 Q0.3739,0.1199,0.3681,0.114 Q0.3866,0.15,0.3872,0.1903 Z M0.865,0.0677 Q0.8402,0.0446,0.8102,0.0317 T0.7525,0.0188 Q0.7253,0.0188,0.6956,0.0317 T0.6408,0.0667 T0.5995,0.122 T0.5834,0.1925 Q0.5834,0.2645,0.6399,0.3167 Q0.6647,0.3398,0.6947,0.353 T0.7525,0.3661 Q0.7796,0.3661,0.8093,0.353 T0.8641,0.3177 T0.9054,0.2624 T0.9215,0.1925 Q0.9215,0.1204,0.865,0.0677 Z M0.5718,0.1957 L0.5718,0.1925 Q0.5718,0.1161,0.6313,0.0602 Q0.6013,0.0844,0.584,0.118 T0.5661,0.1903 Z M0.573,0.2118 L0.5666,0.2059 Q0.5713,0.2645,0.614,0.3086 Q0.6122,0.3054,0.6105,0.3016 Q0.5788,0.2608,0.573,0.2118 Z M0.9325,0.2048 Q0.9279,0.2737,0.8736,0.3242 Q0.9308,0.279,0.9377,0.2097 Z M0.9331,0.1898 L0.9388,0.1952 L0.9388,0.1925 Q0.9388,0.1575,0.925,0.1263 Q0.9198,0.1199,0.914,0.114 Q0.9325,0.15,0.9331,0.1898 Z",type:0}},"":{type:5,data:{d:"M0.7639,0.1974 L0.9593,0.1974 L0.9593,0.1443 L0.7639,0.1443 L0.7639,0.1974 Z M0.7586,0.109 L0.9727,0.109 Q0.9839,0.109,0.992,0.1153 T1,0.1291 L1,0.382 Q1,0.3906,0.9912,0.3964 T0.9727,0.4021 L0.7586,0.4021 Q0.7468,0.4021,0.739,0.3956 T0.7313,0.382 L0.7313,0.1291 Q0.7313,0.1246,0.7339,0.1207 T0.7406,0.1143 T0.7495,0.1104 T0.7586,0.109 Z M0.7639,0.6575 L0.9593,0.6575 L0.9593,0.6044 L0.7639,0.6044 L0.7639,0.6575 Z M0.7313,0.8347 L0.7313,0.5843 Q0.7313,0.5757,0.7401,0.5699 T0.7586,0.5641 L0.9706,0.5641 Q0.9818,0.5641,0.9898,0.5705 T0.9979,0.5843 L0.9979,0.8347 Q0.9979,0.8433,0.989,0.8491 T0.9706,0.8549 L0.7586,0.8549 Q0.7468,0.8549,0.739,0.8483 T0.7313,0.8347 Z M0,1 L0,0 L0.7607,0 L0.7607,1 L0,1 Z",type:0}},"":{type:5,data:{d:"M0.9764,0.0029 L0.3179,0.4552 L0,0.4552 L0,0 Z M1,1 L0,1 L0,0.5448 L0.3091,0.5448 Z",type:0}},"":{type:5,data:{d:"M0.0236,0.0029 L0.6824,0.4552 L1,0.4552 L1,0 Z M0,1 L1,1 L1,0.5448 L0.6912,0.5448 Z",type:0}},"":{type:3,data:"M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z"},"":{type:3,data:"M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z"},"":{type:3,data:"M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z"},"":{type:3,data:"M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z M.25,.15 L1,.15 L1,.85 L.25,.85 Z"},"":{type:3,data:"M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z M0,.15 L1,.15 L1,.85 L0,.85 Z"},"":{type:3,data:"M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z M0,.15 L.75,.15 L.75,.85 L0,.85 Z"},"":{type:5,data:{d:"M0.6574,0.303 Q0.623,0.291,0.5852,0.285 T0.5082,0.279 T0.4311,0.285 T0.359,0.303 L0.3082,0.248 Q0.3525,0.232,0.4041,0.2235 T0.5082,0.215 T0.6123,0.2235 T0.7082,0.248 Z",type:0},scaleType:1},"":{type:5,data:{d:"M0.8557,0.582 L0.7656,0.551 Q0.7852,0.53,0.7951,0.507 T0.8049,0.46 Q0.8049,0.424,0.782,0.3905 T0.718,0.332 T0.6221,0.293 T0.5082,0.279 L0.5082,0.215 Q0.5607,0.215,0.6123,0.2235 T0.709,0.248 T0.7918,0.287 T0.8557,0.3375 T0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.582 Z",type:0},scaleType:1},"":{type:5,data:{d:"M0.5082,0.705 Q0.482,0.705,0.4557,0.703 T0.4049,0.697 L0.4311,0.635 Q0.4508,0.638,0.4697,0.6395 T0.5082,0.641 Q0.5672,0.641,0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 Q0.8049,0.436,0.7951,0.413 T0.7656,0.369 L0.8557,0.338 Q0.882,0.365,0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z",type:0},scaleType:1},"":{type:5,data:{d:"M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 L0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 T0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 L0.9098,0.46 Q0.9098,0.492,0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z",type:0},scaleType:1},"":{type:5,data:{d:"M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.338 L0.2508,0.369 Q0.2311,0.39,0.2213,0.413 T0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 Q0.5279,0.641,0.5467,0.6395 T0.5852,0.635 L0.6115,0.697 Q0.5869,0.701,0.5607,0.703 T0.5082,0.705 Z",type:0},scaleType:1},"":{type:5,data:{d:"M0.1607,0.582 Q0.1344,0.555,0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.3375 T0.2246,0.287 T0.3074,0.248 T0.4041,0.2235 T0.5082,0.215 L0.5082,0.279 Q0.4492,0.279,0.3943,0.293 T0.2984,0.332 T0.2344,0.3905 T0.2115,0.46 Q0.2115,0.484,0.2213,0.507 T0.2508,0.551 Z",type:0},scaleType:1},"":i.LINE_H,"":i.LINE_V,"":i.FADE_RIGHT,"":i.FADE_LEFT,"":i.FADE_DOWN,"":i.FADE_UP,"":i.CURVE_DOWN_RIGHT,"":i.CURVE_DOWN_LEFT,"":i.CURVE_UP_RIGHT,"":i.CURVE_UP_LEFT,"":[i.LINE_V,i.CURVE_UP_RIGHT],"":[i.LINE_V,i.CURVE_DOWN_RIGHT],"":[i.CURVE_DOWN_RIGHT,i.CURVE_UP_RIGHT],"":[i.LINE_V,i.CURVE_UP_LEFT],"":[i.LINE_V,i.CURVE_DOWN_LEFT],"":[i.CURVE_DOWN_LEFT,i.CURVE_UP_LEFT],"":[i.LINE_H,i.CURVE_DOWN_LEFT],"":[i.LINE_H,i.CURVE_DOWN_RIGHT],"":[i.CURVE_DOWN_LEFT,i.CURVE_DOWN_RIGHT],"":[i.LINE_H,i.CURVE_UP_LEFT],"":[i.LINE_H,i.CURVE_UP_RIGHT],"":[i.CURVE_UP_LEFT,i.CURVE_UP_RIGHT],"":[i.LINE_V,i.CURVE_UP_LEFT,i.CURVE_UP_RIGHT],"":[i.LINE_V,i.CURVE_DOWN_LEFT,i.CURVE_DOWN_RIGHT],"":[i.LINE_H,i.CURVE_DOWN_LEFT,i.CURVE_UP_LEFT],"":[i.LINE_H,i.CURVE_DOWN_RIGHT,i.CURVE_UP_RIGHT],"":[i.LINE_V,i.CURVE_DOWN_RIGHT,i.CURVE_UP_LEFT],"":[i.LINE_V,i.CURVE_DOWN_LEFT,i.CURVE_UP_RIGHT],"":[i.LINE_H,i.CURVE_DOWN_RIGHT,i.CURVE_UP_LEFT],"":[i.LINE_H,i.CURVE_DOWN_LEFT,i.CURVE_UP_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE],"":i.NODE_STROKE,"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_LEFT],"":[i.NODE_STROKE,i.NODE_LINE_LEFT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_DOWN],"":[i.NODE_STROKE,i.NODE_LINE_DOWN],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP],"":[i.NODE_STROKE,i.NODE_LINE_UP],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_UP],"":[i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_UP],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT],"":[i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_LEFT],"":[i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_LEFT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_DOWN,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_DOWN,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT],"":[i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"🬀":s(1),"🬁":s(2),"🬂":s(3),"🬃":s(4),"🬄":s(5),"🬅":s(6),"🬆":s(7),"🬇":s(8),"🬈":s(9),"🬉":s(10),"🬊":s(11),"🬋":s(12),"🬌":s(13),"🬍":s(14),"🬎":s(15),"🬏":s(16),"🬐":s(17),"🬑":s(18),"🬒":s(19),"🬓":s(20),"🬔":s(22),"🬕":s(23),"🬖":s(24),"🬗":s(25),"🬘":s(26),"🬙":s(27),"🬚":s(28),"🬛":s(29),"🬜":s(30),"🬝":s(31),"🬞":s(32),"🬟":s(33),"🬠":s(34),"🬡":s(35),"🬢":s(36),"🬣":s(37),"🬤":s(38),"🬥":s(39),"🬦":s(40),"🬧":s(41),"🬨":s(43),"🬩":s(44),"🬪":s(45),"🬫":s(46),"🬬":s(47),"🬭":s(48),"🬮":s(49),"🬯":s(50),"🬰":s(51),"🬱":s(52),"🬲":s(53),"🬳":s(54),"🬴":s(55),"🬵":s(56),"🬶":s(57),"🬷":s(58),"🬸":s(59),"🬹":s(60),"🬺":s(61),"🬻":s(62),"🬼":{type:3,data:"M0,0.6667 L0,1 L0.5,1 Z"},"🬽":{type:3,data:"M0,0.6667 L0,1 L1,1 Z"},"🬾":{type:3,data:"M0,0.3333 L0,1 L0.5,1 Z"},"🬿":{type:3,data:"M0,0.3333 L0,1 L1,1 Z"},"🭀":{type:3,data:"M0,0 L0,1 L0.5,1 Z"},"🭁":{type:3,data:"M0,0.3333 L0.5,0 L1,0 L1,1 L0,1 Z"},"🭂":{type:3,data:"M0,0.3333 L1,0 L1,1 L0,1 Z"},"🭃":{type:3,data:"M0,0.6667 L0.5,0 L1,0 L1,1 L0,1 Z"},"🭄":{type:3,data:"M0,0.6667 L1,0 L1,1 L0,1 Z"},"🭅":{type:3,data:"M0,1 L0.5,0 L1,0 L1,1 Z"},"🭆":{type:3,data:"M0,0.6667 L1,0.3333 L1,1 L0,1 Z"},"🭇":{type:3,data:"M0.5,1 L1,0.6667 L1,1 Z"},"🭈":{type:3,data:"M0,1 L1,0.6667 L1,1 Z"},"🭉":{type:3,data:"M0.5,1 L1,0.3333 L1,1 Z"},"🭊":{type:3,data:"M0,1 L1,0.3333 L1,1 Z"},"🭋":{type:3,data:"M0.5,1 L1,0 L1,1 Z"},"🭌":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 L1,0.3333 Z"},"🭍":{type:3,data:"M0,0 L0,1 L1,1 L1,0.3333 Z"},"🭎":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 L1,0.6667 Z"},"🭏":{type:3,data:"M0,0 L0,1 L1,1 L1,0.6667 Z"},"🭐":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 Z"},"🭑":{type:3,data:"M0,0.3333 L0,1 L1,1 L1,0.6667 Z"},"🭒":{type:3,data:"M0,0.6667 L0.5,1 L1,1 L1,0 L0,0 Z"},"🭓":{type:3,data:"M0,0.6667 L1,1 L1,0 L0,0 Z"},"🭔":{type:3,data:"M0,0.3333 L0.5,1 L1,1 L1,0 L0,0 Z"},"🭕":{type:3,data:"M0,0.3333 L1,1 L1,0 L0,0 Z"},"🭖":{type:3,data:"M0,0 L0.5,1 L1,1 L1,0 Z"},"🭗":{type:3,data:"M0,0.3333 L0,0 L0.5,0 Z"},"🭘":{type:3,data:"M0,0.3333 L0,0 L1,0 Z"},"🭙":{type:3,data:"M0,0.6667 L0,0 L0.5,0 Z"},"🭚":{type:3,data:"M0,0.6667 L0,0 L1,0 Z"},"🭛":{type:3,data:"M0,1 L0,0 L0.5,0 Z"},"🭜":{type:3,data:"M0,0.6667 L0,0 L1,0 L1,0.3333 Z"},"🭝":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 L1,0.6667 Z"},"🭞":{type:3,data:"M0,1 L0,0 L1,0 L1,0.6667 Z"},"🭟":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 L1,0.3333 Z"},"🭠":{type:3,data:"M0,1 L0,0 L1,0 L1,0.3333 Z"},"🭡":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 Z"},"🭢":{type:3,data:"M0.5,0 L1,0 L1,0.3333 Z"},"🭣":{type:3,data:"M0,0 L1,0 L1,0.3333 Z"},"🭤":{type:3,data:"M0.5,0 L1,0 L1,0.6667 Z"},"🭥":{type:3,data:"M0,0 L1,0 L1,0.6667 Z"},"🭦":{type:3,data:"M0.5,0 L1,0 L1,1 Z"},"🭧":{type:3,data:"M0,0.3333 L1,0.6667 L1,0 L0,0 Z"},"🭨":{type:3,data:"M0,0 L1,0 L1,1 L0,1 L0.5,0.5 Z"},"🭩":{type:3,data:"M0,0 L0.5,0.5 L1,0 L1,1 L0,1 Z"},"🭪":{type:3,data:"M0,0 L1,0 L0.5,0.5 L1,1 L0,1 Z"},"🭫":{type:3,data:"M0,0 L1,0 L1,1 L0.5,0.5 L0,1 Z"},"🭬":{type:3,data:"M0,0 L0.5,0.5 L0,1 Z"},"🭭":{type:3,data:"M0,0 L1,0 L0.5,0.5 Z"},"🭮":{type:3,data:"M1,0 L1,1 L0.5,0.5 Z"},"🭯":{type:3,data:"M0,1 L1,1 L0.5,0.5 Z"},"🭰":{type:0,data:[{x:1,y:0,w:1,h:8}]},"🭱":{type:0,data:[{x:2,y:0,w:1,h:8}]},"🭲":{type:0,data:[{x:3,y:0,w:1,h:8}]},"🭳":{type:0,data:[{x:4,y:0,w:1,h:8}]},"🭴":{type:0,data:[{x:5,y:0,w:1,h:8}]},"🭵":{type:0,data:[{x:6,y:0,w:1,h:8}]},"🭶":{type:0,data:[{x:0,y:1,w:8,h:1}]},"🭷":{type:0,data:[{x:0,y:2,w:8,h:1}]},"🭸":{type:0,data:[{x:0,y:3,w:8,h:1}]},"🭹":{type:0,data:[{x:0,y:4,w:8,h:1}]},"🭺":{type:0,data:[{x:0,y:5,w:8,h:1}]},"🭻":{type:0,data:[{x:0,y:6,w:8,h:1}]},"🭼":{type:0,data:[{x:0,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}]},"🭽":{type:0,data:[{x:0,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}]},"🭾":{type:0,data:[{x:7,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}]},"🭿":{type:0,data:[{x:7,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}]},"🮀":{type:0,data:[{x:0,y:0,w:8,h:1},{x:0,y:7,w:8,h:1}]},"🮁":{type:0,data:[{x:0,y:0,w:8,h:1},{x:0,y:2,w:8,h:1},{x:0,y:4,w:8,h:1},{x:0,y:7,w:8,h:1}]},"🮂":{type:0,data:[{x:0,y:0,w:8,h:2}]},"🮃":{type:0,data:[{x:0,y:0,w:8,h:3}]},"🮄":{type:0,data:[{x:0,y:0,w:8,h:5}]},"🮅":{type:0,data:[{x:0,y:0,w:8,h:6}]},"🮆":{type:0,data:[{x:0,y:0,w:8,h:7}]},"🮇":{type:0,data:[{x:6,y:0,w:2,h:8}]},"🮈":{type:0,data:[{x:5,y:0,w:3,h:8}]},"🮉":{type:0,data:[{x:3,y:0,w:5,h:8}]},"🮊":{type:0,data:[{x:2,y:0,w:6,h:8}]},"🮋":{type:0,data:[{x:1,y:0,w:7,h:8}]},"🮌":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L0.5,0 L0.5,1 L0,1 Z"},"🮍":{type:1,data:[[1,0],[0,1]],clipPath:"M0.5,0 L1,0 L1,1 L0.5,1 Z"},"🮎":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L1,0.5 L0,0.5 Z"},"🮏":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0.5 L1,0.5 L1,1 L0,1 Z"},"🮐":{type:1,data:[[0,1],[1,0]]},"🮑":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0.5 L1,0.5 L1,1 L0,1 Z"},{type:0,data:[{x:0,y:0,w:8,h:4}]}],"🮒":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0 L1,0 L1,0.5 L0,0.5 Z"},{type:0,data:[{x:0,y:4,w:8,h:4}]}],"🮔":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0 L0.5,0 L0.5,1 L0,1 Z"},{type:0,data:[{x:4,y:0,w:4,h:8}]}],"🮕":{type:0,data:[{x:0,y:0,w:2,h:2},{x:4,y:0,w:2,h:2},{x:2,y:2,w:2,h:2},{x:6,y:2,w:2,h:2},{x:0,y:4,w:2,h:2},{x:4,y:4,w:2,h:2},{x:2,y:6,w:2,h:2},{x:6,y:6,w:2,h:2}]},"🮖":{type:0,data:[{x:2,y:0,w:2,h:2},{x:6,y:0,w:2,h:2},{x:0,y:2,w:2,h:2},{x:4,y:2,w:2,h:2},{x:2,y:4,w:2,h:2},{x:6,y:4,w:2,h:2},{x:0,y:6,w:2,h:2},{x:4,y:6,w:2,h:2}]},"🮗":{type:0,data:[{x:0,y:2,w:8,h:2},{x:0,y:6,w:8,h:2}]},"🮘":{type:2,data:"M-0.25,-0.25 L1.25,1.25 M-0.25,0 L1,1.25 M-0.25,0.25 L0.75,1.25 M-0.25,0.5 L0.5,1.25 M0,-0.25 L1.25,1 M0.25,-0.25 L1.25,0.75 M0.5,-0.25 L1.25,0.5 M-0.25,0.75 L0.25,1.25 M0.75,-0.25 L1.25,0.25",strokeWidth:1},"🮙":{type:2,data:"M-0.25,0.5 L0.5,-0.25 M-0.25,0.75 L0.75,-0.25 M-0.25,1 L1,-0.25 M-0.25,1.25 L1.25,-0.25 M0,1.25 L1.25,0 M0.25,1.25 L1.25,0.25 M0.5,1.25 L1.25,0.5 M-0.25,0.25 L0.25,-0.25 M0.75,1.25 L1.25,0.75",strokeWidth:1},"🮚":{type:5,data:{d:"M0,0 L.5,.5 L0,1 L1,1 L.5,.5 L1,0",type:0}},"🮛":{type:5,data:{d:"M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1",type:0}},"🮜":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L0,1 Z"},"🮝":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L1,1 Z"},"🮞":{type:1,data:[[1,0],[0,1]],clipPath:"M1,0 L1,1 L0,1 Z"},"🮟":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,1 L0,1 Z"},"🮠":{type:2,data:"M.5,0 L0,.5",strokeWidth:1},"🮡":{type:2,data:"M.5,0 L1,.5",strokeWidth:1},"🮢":{type:2,data:"M0,.5 L.5,1",strokeWidth:1},"🮣":{type:2,data:"M1,.5 L.5,1",strokeWidth:1},"🮤":{type:2,data:"M.5,0 L0,.5 L.5,1",strokeWidth:1},"🮥":{type:2,data:"M.5,0 L1,.5 L.5,1",strokeWidth:1},"🮦":{type:2,data:"M0,.5 L.5,1 L1,.5",strokeWidth:1},"🮧":{type:2,data:"M0,.5 L.5,0 L1,.5",strokeWidth:1},"🮨":{type:2,data:"M.5,0 L0,.5 M1,.5 L.5,1",strokeWidth:1},"🮩":{type:2,data:"M.5,0 L1,.5 M0,.5 L.5,1",strokeWidth:1},"🮪":{type:2,data:"M.5,0 L1,.5 L.5,1 L0,.5",strokeWidth:1},"🮫":{type:2,data:"M.5,0 L0,.5 L.5,1 L1,.5",strokeWidth:1},"🮬":{type:2,data:"M0,.5 L.5,0 L1,.5 L.5,1",strokeWidth:1},"🮭":{type:2,data:"M1,.5 L.5,0 L0,.5 L.5,1",strokeWidth:1},"🮮":{type:2,data:"M.5,0 L1,.5 L.5,1 L0,.5 Z",strokeWidth:1},"🮯":{type:2,data:"M0,.5 L1,.5 M.5,.35 L.5,.65",strokeWidth:1},"🮰":{type:3,data:"M0.1,0.2 L0.1,.8 L.4,.6 L.9,0.6 Z",scaleType:1},"🮱":{type:4,data:{d:"M.1,.525 L.35,.675 L.9,.35",type:1},scaleType:1},"🮲":{type:3,data:"M.29,.27 L0.13,.56 L.22,.59 L.35,0.35 L.67,.35 L.57,.57 L.71,.76 L.22,.76 L.42,1 L.53,.98 L.43,.86 L.9,.86 L.71,.6 L1,.6 L1,.52 L.83,.52 L.92,.36 L1,.36 L1,.27Z M.99,.13 A.12,.12,0,1,1,.75,.13 A.12,.12,0,1,1,.99,.13"},"🮳":{type:3,data:"M0,.27 L.3,.27 L.55,.12 L.63,.18 L.33,.36 L0,.36 M0,.52 L.33,.52 L.59,.89 L.73,.89 L.73,.98 L.53,.98 L.28,.6 L0,.6"},"🮴":{type:4,data:{d:"M.15,.55 L.5,.4 L.5,.45 L.65,.45 L.65,.35 L.85,.35 L.85,.625 L.5,.625 L.5,.7 Z",type:0},scaleType:1},"🮵":[{type:5,data:{d:"M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z",type:0}},{type:5,data:{d:"M.15,.5 L.5,.35 L.5,.425 L.85,.425 L.85,.575 L.5,.575 L.5,.65 Z",type:0},scaleType:1}],"🮶":[{type:5,data:{d:"M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z",type:0}},{type:5,data:{d:"M.85,.5 L.5,.35 L.5,.425 L.15,.425 L.15,.575 L.5,.575 L.5,.65 Z",type:0},scaleType:1}],"🮷":[{type:5,data:{d:"M.875,0 L1,0 L1,1 L.875,1 Z",type:0}},{type:5,data:{d:"M.5,.675 L.2,.5 L.35,.5 L.35,.325 L.65,.325 L.65,.5 L.8,.5 Z",type:0},scaleType:1}],"🮸":[{type:5,data:{d:"M.875,0 L1,0 L1,1 L.875,1 Z",type:0}},{type:5,data:{d:"M.5,.325 L.2,.5 L.35,.5 L.35,.675 L.65,.675 L.65,.5 L.8,.5 Z",type:0},scaleType:1}],"🮹":{type:5,data:{d:"M1,.89 L.11,.89 L.11,.37 L.36,.12 L.74,.12 L.96,.34 L1,.34 L1,.405 L.92,.405 L.69,.185 L.41,.185 L.21,.42 L.21,.825 L1,.825 Z",type:0}},"🮺":{type:5,data:{d:"M0,.89 L0,.825 L.78,.825 L.78,.53 L.7,.415 L0,.415 L0,.35 L.75,.35 L.88,.48 L.88,.89 Z",type:0}},"🮻":{type:5,data:{d:"M.31,.275 L.44,.275 L.44,.47 L.05,.47 L.05,.405 L.31,.405 Z M.56,.275 L.69,.275 L.69,.405 L.95,.405 L.95,.47 L.56,.47 Z M.05,.53 L.44,.53 L.44,.725 L.31,.725 L.31,.595 L.05,.595 Z M.56,.53 L.95,.53 L.95,.595 L.69,.595 L.69,.725 L.56,.725 Z",type:0},scaleType:1},"🮼":[{type:3,data:"M0,0 L1,0 L1,1 L0,1 L0,.935 L.885,.935 L.885,.065 L0,.065 Z"},{type:3,data:"M.67,.5 A.17,.085,0,1,1,.33,.5 A.17,.085,0,1,1,.67,.5",scaleType:1}],"🮽":{type:4,data:{d:"M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1 Z",type:1}},"🮾":{type:4,data:{d:"M1,.5 L.5,1",type:1}},"🮿":{type:4,data:{d:"M.5,0 L1,.5 L.5,1 L0,.5 Z",type:1}},"🯀":{type:3,data:"M.16,.445 A.02,.01,0,0,1,.39,.33 L.5,.375 L.61,.33 A.02,.01,0,0,1,.84,.445 L.75,.5 L.84,.555 A.02,.01,0,0,1,.61,.67 L.5,.625 L.39,.67 A.02,.01,0,0,1,.16,.555 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z",scaleType:1},"🯁":{type:3,data:"M0.142308,0.924 Q0.130770,0.924,0.120193,0.922 T0.101924,0.916 T0.089424,0.9065 T0.084616,0.895 V0.025 Q0.084616,0.019,0.089424,0.0135 T0.101924,0.004 T0.120193,-0.002 T0.142308,-0.004 Q0.150000,-0.004,0.157693,-0.003 T0.173078,0.000 L0.430770,0.085 Q0.451924,0.059,0.494232,0.041 T0.587501,0.013 T0.692309,-0.0005 T0.800001,-0.004 H1 V0.055 H0.800001 Q0.771155,0.055,0.742309,0.056 T0.685578,0.060 T0.630770,0.0685 T0.580770,0.0825 T0.543270,0.1045 T0.528847,0.133 V0.647 H0.530770 Q0.596155,0.645,0.659616,0.638 T0.782693,0.6165 T0.893270,0.5805 T1,0.531 V0.623 Q0.934616,0.644,0.880770,0.6595 T0.769232,0.685 T0.650963,0.700 T0.528847,0.707 V0.787 Q0.528847,0.802,0.543270,0.8155 T0.580770,0.8375 T0.630770,0.8515 T0.685578,0.860 T0.742309,0.864 T0.800001,0.865 H1 V0.924 H0.800001 Q0.746155,0.924,0.692309,0.9205 T0.587501,0.907 T0.494232,0.879 T0.430770,0.835 L0.173078,0.920 Q0.165386,0.922,0.157693,0.923 T0.142308,0.924 Z M0.2,0.841 L0.415385,0.770 V0.150 L0.2,0.079 V0.841 Z M1,0.698 Q0.973077,0.694,0.969231,0.6885 T0.965385,0.677 Q0.965385,0.672,0.969231,0.6665 T1,0.657 V0.698 Z"},"🯂":{type:3,data:"M0,0.623 V0.531 Q0.044231,0.512,0.0625,0.4905 T0.092308,0.4465 T0.108654,0.4005 T0.113462,0.354 V0.351 Q0.113462,0.345,0.117308,0.3395 T0.129808,0.3305 T0.148846,0.3245 T0.171154,0.323 H0.501923 Q0.532692,0.323,0.563462,0.3185 T0.617308,0.303 T0.651923,0.2765 T0.663462,0.2435 V0.241 Q0.663462,0.219,0.657692,0.197 T0.639423,0.1545 T0.604808,0.115 T0.549615,0.082 T0.475577,0.0615 T0.392308,0.055 H0 V-0.004 H1 V0.055 H0.667308 Q0.694231,0.071,0.713462,0.09 T0.746154,0.129 T0.766346,0.1705 T0.775,0.213 H1 V0.316 Q0.971154,0.305,0.955769,0.2965 T0.920192,0.2825 T0.876923,0.2745 T0.830769,0.272 H0.773077 Q0.767308,0.297,0.743269,0.319 T0.680769,0.355 T0.595192,0.375 T0.501923,0.381 H0.227308 Q0.223462,0.415,0.211,0.4485 T0.172692,0.5135 T0.108269,0.573 T0,0.623 Z M0.611538,0.924 H0 V0.865 H0.611538 Q0.642308,0.865,0.673077,0.8605 T0.727885,0.845 T0.762308,0.8185 T0.773077,0.787 V0.785 Q0.773077,0.769,0.762308,0.7535 T0.727885,0.727 T0.673077,0.7115 T0.611538,0.707 H0.059615 Q0.048077,0.707,0.037115,0.7045 T0,0.694 V0.653 Q0.026923,0.648,0.037115,0.646 T0.059615,0.644 H0.722692 Q0.753462,0.644,0.784231,0.6395 T0.839038,0.624 T0.873846,0.5975 T0.884615,0.566 V0.564 Q0.884615,0.548,0.873846,0.532 T0.839038,0.5055 T0.784231,0.49 T0.722692,0.4855 H0.392308 Q0.380769,0.4855,0.370192,0.483 T0.351923,0.4765 T0.339423,0.467 T0.334615,0.4555 T0.339423,0.444 T0.351923,0.4345 T0.370192,0.428 T0.392308,0.4255 H0.832692 Q0.855769,0.4255,0.878846,0.4235 T0.922115,0.416 T0.957692,0.4015 T1,0.3815 V0.4685 Q0.975,0.4705,0.966346,0.4725 T0.95,0.4765 Q0.961538,0.4835,0.969231,0.4915 T1,0.5075 V0.6205 Q0.965385,0.6455,0.926923,0.665 T0.84,0.6935 Q0.866923,0.7115,0.877596,0.7345 T0.894231,0.7855 V0.787 Q0.894231,0.815,0.876923,0.8425 T0.820192,0.8895 T0.726923,0.916 T0.611538,0.924 Z"},"🯃":{type:3,data:"M0,0.473 V0.386 Q0.025490,0.378,0.028431,0.3695 T0.031373,0.352 V0.35 Q0.031373,0.342,0.028431,0.333 T0,0.316 V0.213 H0.652941 Q0.684314,0.213,0.715686,0.2085 T0.770588,0.193 T0.804902,0.1665 T0.815686,0.135 V0.133 Q0.815686,0.117,0.804902,0.101 T0.770588,0.0745 T0.715686,0.0595 T0.652941,0.055 H0 V-0.004 H0.652941 Q0.707843,-0.004,0.761765,0.004 T0.856863,0.031 T0.915686,0.0775 T0.933333,0.133 V0.135 Q0.933333,0.163,0.915686,0.1905 T0.856863,0.237 T0.761765,0.264 T0.652941,0.272 H0.109804 Q0.131373,0.29,0.140196,0.31 T0.149020,0.35 V0.352 Q0.149020,0.37,0.142157,0.388 T0.118627,0.4225 T0.076471,0.452 T0,0.473 Z M0,0.625 V0.513 Q0.029412,0.526,0.032353,0.54 T0.035294,0.568 V0.57 Q0.035294,0.584,0.032353,0.598 T0,0.625 Z"},"🯄":{type:3,data:"M0.019231,1.085 V-0.165 H0.980769 V1.085 H0.019231 Z M0.446154,0.527 H0.553846 Q0.553846,0.511,0.5625,0.495 T0.591346,0.466 T0.631731,0.4405 T0.666346,0.4135 T0.688462,0.3825 T0.696154,0.35 Q0.696154,0.33,0.682692,0.311 T0.641346,0.2785 T0.575,0.259 T0.498077,0.253 T0.421154,0.259 T0.356731,0.279 T0.315385,0.3125 T0.301923,0.352 V0.357 H0.409615 V0.354 Q0.409615,0.345,0.414423,0.335 T0.431731,0.318 T0.4625,0.3075 T0.498077,0.304 Q0.517308,0.304,0.534615,0.307 T0.564423,0.3165 T0.582692,0.332 T0.588462,0.35 Q0.588462,0.366,0.572115,0.3805 T0.535577,0.4075 T0.496154,0.4335 T0.465385,0.4625 T0.45,0.4945 T0.446154,0.527 Z M0.5,0.667 Q0.519231,0.667,0.5375,0.664 T0.569231,0.654 T0.588462,0.637 T0.594231,0.617 T0.588462,0.5975 T0.569231,0.581 T0.5375,0.571 T0.5,0.568 T0.4625,0.571 T0.430769,0.581 T0.411538,0.5975 T0.405769,0.617 T0.411538,0.637 T0.430769,0.654 T0.4625,0.664 T0.5,0.667 Z",scaleType:1},"🯅":{type:5,data:{d:"M.44,.425 L.44,.51 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.51 L.56,.425 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"🯆":{type:5,data:{d:"M.44,.425 L.44,.46 L.23,.385 L.19,.43 L.44,.51 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.61,.735 L.7,.71 L.56,.56 L.56,.51 L.81,.43 L.77,.385 L.56,.46 L.56,.425 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"🯇":{type:5,data:{d:"M.44,.425 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.425 Z M.18,.53 L.23,.575 L.81,.43 L.77,.385 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"🯈":{type:5,data:{d:"M.44,.425 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.62,.735 L.71,.71 L.56,.56 L.56,.425 Z M.23,.385 L.18,.43 L.77,.575 L.81,.53 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"🯉":{type:5,data:{d:"M.44,.425 L.45,.495 L.15,.645 L.34,.645 L.34,.7 L.44,.7 L.44,.645 L.56,.645 L.56,.7 L.66,.7 L.66,.645 L.84,.645 L.54,.495 L.56,.425 Z M.39,.6 L.5,.55 L.60,.6 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"🯊":{type:5,data:{d:"M.26,.375 L.5,.255 L.74,.375 L.74,.665 L.5,.55 L.26,.665 Z M.37,.395 L.37,.54 L.5,.475 L.63,.54 L.63,.395 L.5,.33 Z",type:0},scaleType:1},"🯋":{type:5,data:{d:"M.09,.41 L.32,.295 L.5,.375 L.68,.295 L.91,.41 L.75,.5 L.91,.59 L.68,.705 L.5,.625 L.32,.705 L.09,.59 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z",type:0},scaleType:1},"🯌":{type:5,data:{d:"M.55,.305 L.88,.305 L.88,.355 L.65,.355 L.65,.47 L.88,.47 L.88,.52 L.55,.52 Z",type:0},scaleType:1},"🯍":{type:5,data:{d:"M.38,.39 L.5,.33 L.62,.39 L.62,.53 L.5,.47 L.38,.53 Z",type:0},scaleType:1},"🯎":{type:3,data:"M0,0 L0.6667,0 L0.6667,1 L0,1 Z"},"🯏":{type:3,data:"M0,0 L0.3333,0 L0.3333,1 L0,1 Z"},"🯐":{type:2,data:"M1,.5 L0,1",strokeWidth:1},"🯑":{type:2,data:"M1,0 L0,.5",strokeWidth:1},"🯒":{type:2,data:"M0,0 L1,.5",strokeWidth:1},"🯓":{type:2,data:"M0,.5 L1,1",strokeWidth:1},"🯔":{type:2,data:"M0,0 L.5,1",strokeWidth:1},"🯕":{type:2,data:"M.5,0 L1,1",strokeWidth:1},"🯖":{type:2,data:"M1,0 L.5,1",strokeWidth:1},"🯗":{type:2,data:"M.5,0 L0,1",strokeWidth:1},"🯘":{type:2,data:"M0,0 L.5,.5 L1,0",strokeWidth:1},"🯙":{type:2,data:"M1,0 L.5,.5 L1,1",strokeWidth:1},"🯚":{type:2,data:"M0,1 L.5,.5 L1,1",strokeWidth:1},"🯛":{type:2,data:"M0,0 L.5,.5 L0,1",strokeWidth:1},"🯜":{type:2,data:"M0,0 L.5,1 L1,0",strokeWidth:1},"🯝":{type:2,data:"M1,0 L0,.5 L1,1",strokeWidth:1},"🯞":{type:2,data:"M0,1 L.5,0 L1,1",strokeWidth:1},"🯟":{type:2,data:"M0,0 L1,.5 L0,1",strokeWidth:1},"🯠":{type:5,data:{d:"M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0",type:1}},"🯡":{type:5,data:{d:"M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1",type:1}},"🯢":{type:5,data:{d:"M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1",type:1}},"🯣":{type:5,data:{d:"M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1",type:1}},"🯤":{type:0,data:[{x:2,y:0,w:4,h:4}]},"🯥":{type:0,data:[{x:2,y:4,w:4,h:4}]},"🯦":{type:0,data:[{x:0,y:2,w:4,h:4}]},"🯧":{type:0,data:[{x:4,y:2,w:4,h:4}]},"🯨":{type:5,data:{d:"M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0 Z",type:0}},"🯩":{type:5,data:{d:"M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1 Z",type:0}},"🯪":{type:5,data:{d:"M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1 Z",type:0}},"🯫":{type:5,data:{d:"M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1 Z",type:0}},"🯬":{type:5,data:{d:"M1,0 L.5,0 C.5,.276,.724,.5,1,.5 Z",type:0}},"🯭":{type:5,data:{d:"M0,1 L.5,1 C.5,.724,.276,.5,0,.5 Z",type:0}},"🯮":{type:5,data:{d:"M1,1 L1,.5 C.724,.5,.5,.724,.5,1 Z",type:0}},"🯯":{type:5,data:{d:"M0,0 L0,.5 C.276,.5,.5,.276,.5,0 Z",type:0}},"🯰":{type:5,data:{d:r(126),type:0},scaleType:1},"🯱":{type:5,data:{d:r(48),type:0},scaleType:1},"🯲":{type:5,data:{d:r(109),type:0},scaleType:1},"🯳":{type:5,data:{d:r(121),type:0},scaleType:1},"🯴":{type:5,data:{d:r(51),type:0},scaleType:1},"🯵":{type:5,data:{d:r(91),type:0},scaleType:1},"🯶":{type:5,data:{d:r(95),type:0},scaleType:1},"🯷":{type:5,data:{d:r(114),type:0},scaleType:1},"🯸":{type:5,data:{d:r(127),type:0},scaleType:1},"🯹":{type:5,data:{d:r(123),type:0},scaleType:1},"🯺":{type:5,data:{d:"M.5,.175 C.2,.175,.15,.305,.15,.435 L.05,.63 L.35,.63 C.35,.682,.42,.76,.5,.76 C.58,.76,.65,.682,.65,.63 L.95,.63 L.85,.435 C.85,.305,.8,.175,.5,.175 Z",type:0},scaleType:1},...Object.fromEntries(Array.from({length:256},(t,e)=>[String.fromCodePoint(10240+e),{type:6,data:e}]))},e.blockPatternCodepoints=new Set([9617,9618,9619,129932,129933,129934,129935,129936,129937,129938,129940,129948,129949,129950,129951])},183(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.tryDrawCustomGlyph=function(t,e,i,s,o,n,L,h,T,l,d,c,_=0){const Q=r.customGlyphDefinitions[e];if(Q){const e=Array.isArray(Q)?Q:[Q];for(const r of e)a(t,r,i,s,o,n,L,h,T,l,d,c,_);return!0}return!1};const s=i(959),r=i(915);function a(t,e,i,r,a,h,d,c,_,Q,u,p,g=0){let M=a,y=h,v=i,f=r;switch(1===e.scaleType&&(M=d,y=c,v=i+(a-d)/2,f=r+(h-c)/2),e.clipPath&&(t.save(),function(t,e,i,s,r,a){t.beginPath();for(const o of e.split(" ")){const e=o[0];if("Z"===e){t.closePath();continue}const n=o.substring(1).split(",");if(!n[0]||!n[1])continue;const L=i+parseFloat(n[0])*r,h=s+parseFloat(n[1])*a;"M"===e?t.moveTo(L,h):"L"===e&&t.lineTo(L,h)}t.clip()}(t,e.clipPath,v,f,M,y)),e.type){case 0:!function(t,e,i,s,r,a){for(let o=0;o7&&parseInt(T.slice(7,9),16)||1;else{if(!T.startsWith("rgba"))throw new Error(`Unexpected fillStyle color format "${T}" when drawing pattern glyph`);[L,d,c,_]=T.substring(5,T.length-1).split(",").map(t=>parseFloat(t))}for(let t=0;t>1&1,c=1&n;0!==d||0!==c?l.setTransform((new DOMMatrix).translateSelf(-d,-c)):l.setTransform(new DOMMatrix),t.fillStyle=l,t.fillRect(i,r,a,o)}(t,e.data,v,f,M,y,g);break;case 2:!function(t,e,i,s,r,a,o,n,L){let h;t.save(),t.beginPath(),t.rect(i,s,r,a),t.clip(),t.beginPath(),h="function"==typeof e?e(.15,.15/a*r):e;const d={currentX:0,currentY:0,lastControlX:0,lastControlY:0,lastCommand:""};for(const e of h.split(" ")){const L=e[0];if("Z"===L){t.closePath(),d.lastCommand=L;continue}const h=T[L];if(!h){n.error(`Could not find drawing instructions for "${L}"`);continue}const c=e.substring(1).split(",");c[0]&&c[1]&&(h(t,l(c,r,a,i,s,!0,o,0,0,!1),d),d.lastCommand=L)}void 0!==L?(t.strokeStyle=t.fillStyle,t.lineWidth=o*L,t.stroke()):t.fill(),t.closePath(),t.restore()}(t,e.data,v,f,M,y,Q,u,e.strokeWidth);break;case 3:!function(t,e,i,s,r,a,o,L){const h="string"==typeof e?e:e(0,0);t.beginPath();let T=0,l=0,d=0,c=0,_="";for(const e of h.split(" ")){const o=e[0],L=e.substring(1).split(",");if("Z"===o){t.closePath(),_=o;continue}if("V"===o){const e=s+parseFloat(L[0])*a;t.lineTo(T,e),l=e,d=T,c=l,_=o;continue}if("H"===o){const e=i+parseFloat(L[0])*r;t.lineTo(e,l),T=e,d=T,c=l,_=o;continue}if(!L[0]||!L[1])continue;if("A"===o){const e=parseFloat(L[0])*r,o=parseFloat(L[1])*a,h=parseFloat(L[2])*Math.PI/180,d=parseInt(L[3],10),c=parseInt(L[4],10),_=i+parseFloat(L[5])*r,Q=s+parseFloat(L[6])*a;n(t,T,l,e,o,h,d,c,_,Q),T=_,l=Q;continue}const h=L.map((t,e)=>{const o=parseFloat(t);return e%2==0?i+o*r:s+o*a});if("M"===o)t.moveTo(h[0],h[1]),T=h[0],l=h[1],d=T,c=l;else if("L"===o)t.lineTo(h[0],h[1]),T=h[0],l=h[1],d=T,c=l;else if("Q"===o)t.quadraticCurveTo(h[0],h[1],h[2],h[3]),d=h[0],c=h[1],T=h[2],l=h[3];else if("T"===o){let e,i;"Q"===_||"T"===_?(e=2*T-d,i=2*l-c):(e=T,i=l),t.quadraticCurveTo(e,i,h[0],h[1]),d=e,c=i,T=h[0],l=h[1]}else"C"===o&&(t.bezierCurveTo(h[0],h[1],h[2],h[3],h[4],h[5]),d=h[2],c=h[3],T=h[4],l=h[5]);_=o}void 0!==L?(t.strokeStyle=t.fillStyle,t.lineWidth=o*L,t.stroke()):t.fill()}(t,e.data,v,f,M,y,Q,e.strokeWidth);break;case 4:!function(t,e,i,s,r,a,o,n){t.save(),t.fillRect(i,s,r,a),n&&(t.fillStyle=n,t.strokeStyle=n),t.lineWidth=o,t.lineCap="square",t.beginPath();for(const o of e.d.split(" ")){const e=o[0],n=o.substring(1).split(",");if(!n[0]||!n[1]){"Z"===e&&t.closePath();continue}const L=n.map((t,e)=>{const o=parseFloat(t);return e%2==0?i+o*r:s+o*a});"M"===e?t.moveTo(L[0],L[1]):"L"===e&&t.lineTo(L[0],L[1])}1===e.type?t.stroke():t.fill(),t.restore()}(t,e.data,v,f,M,y,Q,p);break;case 5:!function(t,e,i,s,r,a,o,n,L){const h=new Path2D;h.rect(i,s,r,a),t.clip(h),t.beginPath();const d=o/12;t.lineWidth=n*d;const c={currentX:0,currentY:0,lastControlX:0,lastControlY:0,lastCommand:""};for(const o of e.d.split(" ")){const h=o[0];if("Z"===h){t.closePath(),c.lastCommand=h;continue}const _=T[h];if(!_){L.error(`Could not find drawing instructions for "${h}"`);continue}const Q=o.substring(1).split(",");Q[0]&&Q[1]&&(_(t,l(Q,r,a,i,s,!1,n,(e.leftPadding??0)*(d/2),(e.rightPadding??0)*(d/2)),c),c.lastCommand=h)}1===e.type?(t.strokeStyle=t.fillStyle,t.stroke()):t.fill(),t.closePath()}(t,e.data,v,f,M,y,_,Q,u);break;case 6:!function(t,e,i,s,r,a){const n=r/8,L=.1*a,h=.8*a/8,T=Math.min(n,h);for(let r=0;r<8;r++)if(e&1<1){const t=Math.sqrt(y);u=(s*=t)*s,p=(r*=t)*r}let v=(u*p-u*M-p*g)/(u*M+p*g);v<0&&(v=0);const f=(o===n?-1:1)*Math.sqrt(v),C=f*(s*Q/r),m=f*(-r*_/s),w=T*C-l*m+(e+L)/2,x=l*C+T*m+(i+h)/2,b=(_-C)/s,E=(Q-m)/r,R=(-_-C)/s,k=(-Q-m)/r,A=Math.atan2(E,b);let Z=Math.atan2(k,R)-A;0===n&&Z>0?Z-=2*Math.PI:1===n&&Z<0&&(Z+=2*Math.PI);const D=A+Z;t.ellipse(w,x,s,r,a,A,D,0===n)}const L=new Map;function h(t,e,i=0){return Math.max(Math.min(t,e),i)}const T={C:(t,e,i)=>{t.bezierCurveTo(e[0],e[1],e[2],e[3],e[4],e[5]),i.lastControlX=e[2],i.lastControlY=e[3],i.currentX=e[4],i.currentY=e[5]},L:(t,e,i)=>{t.lineTo(e[0],e[1]),i.lastControlX=i.currentX=e[0],i.lastControlY=i.currentY=e[1]},M:(t,e,i)=>{t.moveTo(e[0],e[1]),i.lastControlX=i.currentX=e[0],i.lastControlY=i.currentY=e[1]},Q:(t,e,i)=>{t.quadraticCurveTo(e[0],e[1],e[2],e[3]),i.lastControlX=e[0],i.lastControlY=e[1],i.currentX=e[2],i.currentY=e[3]},T:(t,e,i)=>{let s,r;"Q"===i.lastCommand||"T"===i.lastCommand?(s=2*i.currentX-i.lastControlX,r=2*i.currentY-i.lastControlY):(s=i.currentX,r=i.currentY),t.quadraticCurveTo(s,r,e[0],e[1]),i.lastControlX=s,i.lastControlY=r,i.currentX=e[0],i.currentY=e[1]}};function l(t,e,i,s,r,a,o,n=0,L=0,T=!0){const l=t.map(t=>parseFloat(t)||parseInt(t));if(l.length<2)throw new Error("Too few arguments for instruction");for(let t=0;t{this._refreshCharAtlas(t,e),this.reset(t)})),this._register((0,r.toDisposable)(()=>{this._canvas.remove()}))}_initCanvas(){this._ctx=(0,a.throwIfFalsy)(this._canvas.getContext("2d",{alpha:this._alpha})),this._alpha||this._clearAll()}handleBlur(t){}handleFocus(t){}handleCursorMove(t){}handleGridChanged(t,e,i){}handleSelectionChanged(t,e,i,s=!1){}_setTransparency(t,e){if(e===this._alpha)return;const i=this._canvas;this._alpha=e,this._canvas=this._canvas.cloneNode(),this._initCanvas(),this._container.replaceChild(this._canvas,i),this._refreshCharAtlas(t,this._themeService.colors),this.handleGridChanged(t,0,t.rows-1)}_refreshCharAtlas(t,e){this._deviceCharWidth<=0&&this._deviceCharHeight<=0||(this._charAtlas=(0,s.acquireTextureAtlas)(t,this._optionsService.rawOptions,e,this._deviceCellWidth,this._deviceCellHeight,this._deviceCharWidth,this._deviceCharHeight,this._coreBrowserService.dpr,2048),this._charAtlas.warmUp())}resize(t,e){this._deviceCellWidth=e.device.cell.width,this._deviceCellHeight=e.device.cell.height,this._deviceCharWidth=e.device.char.width,this._deviceCharHeight=e.device.char.height,this._deviceCharLeft=e.device.char.left,this._deviceCharTop=e.device.char.top,this._canvas.width=e.device.canvas.width,this._canvas.height=e.device.canvas.height,this._canvas.style.width=`${e.css.canvas.width}px`,this._canvas.style.height=`${e.css.canvas.height}px`,this._alpha||this._clearAll(),this._refreshCharAtlas(t,this._themeService.colors)}_fillBottomLineAtCells(t,e,i=1){this._ctx.fillRect(t*this._deviceCellWidth,(e+1)*this._deviceCellHeight-this._coreBrowserService.dpr-1,i*this._deviceCellWidth,this._coreBrowserService.dpr)}_clearAll(){this._alpha?this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(0,0,this._canvas.width,this._canvas.height))}_clearCells(t,e,i,s){this._alpha?this._ctx.clearRect(t*this._deviceCellWidth,e*this._deviceCellHeight,i*this._deviceCellWidth,s*this._deviceCellHeight):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(t*this._deviceCellWidth,e*this._deviceCellHeight,i*this._deviceCellWidth,s*this._deviceCellHeight))}_fillCharTrueColor(t,e,i,s){this._ctx.font=this._getFont(t,!1,!1),this._ctx.textBaseline=o.TEXT_BASELINE,this._clipCell(i,s,e.getWidth()),this._ctx.fillText(e.getChars(),i*this._deviceCellWidth+this._deviceCharLeft,s*this._deviceCellHeight+this._deviceCharTop+this._deviceCharHeight)}_clipCell(t,e,i){this._ctx.beginPath(),this._ctx.rect(t*this._deviceCellWidth,e*this._deviceCellHeight,i*this._deviceCellWidth,this._deviceCellHeight),this._ctx.clip()}_getFont(t,e,i){return`${i?"italic":""} ${e?t.options.fontWeightBold:t.options.fontWeight} ${t.options.fontSize*this._coreBrowserService.dpr}px ${t.options.fontFamily}`}}e.BaseRenderLayer=n},306(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.LinkRenderLayer=void 0;const s=i(657),r=i(814),a=i(133);class o extends a.BaseRenderLayer{constructor(t,e,i,s,r,a,o){super(i,t,"link",e,!0,r,a,o),this._register(s.onShowLinkUnderline(t=>this._handleShowLinkUnderline(t))),this._register(s.onHideLinkUnderline(t=>this._handleHideLinkUnderline(t)))}resize(t,e){super.resize(t,e),this._state=void 0}reset(t){this._clearCurrentLink()}_clearCurrentLink(){if(this._state){this._clearCells(this._state.x1,this._state.y1,this._state.cols-this._state.x1,1);const t=this._state.y2-this._state.y1-1;t>0&&this._clearCells(0,this._state.y1+1,this._state.cols,t),this._clearCells(0,this._state.y2,this._state.x2,1),this._state=void 0}}_handleShowLinkUnderline(t){if(t.fg===r.INVERTED_DEFAULT_COLOR?this._ctx.fillStyle=this._themeService.colors.background.css:void 0!==t.fg&&(0,s.is256Color)(t.fg)?this._ctx.fillStyle=this._themeService.colors.ansi[t.fg].css:this._ctx.fillStyle=this._themeService.colors.foreground.css,t.y1===t.y2)this._fillBottomLineAtCells(t.x1,t.y1,t.x2-t.x1);else{this._fillBottomLineAtCells(t.x1,t.y1,t.cols-t.x1);for(let e=t.y1+1;e{var t=s;Object.defineProperty(t,"__esModule",{value:!0}),t.WebglAddon=void 0;const e=i(426),r=i(95),a=i(399),o=i(414);class n extends e.Disposable{constructor(t){if(r.isSafari&&(0,r.getSafariVersion)()<16){const t={antialias:!1,depth:!1,preserveDrawingBuffer:!0};if(!document.createElement("canvas").getContext("webgl2",t))throw new Error("WebGL2 is only supported on Safari 16 and above")}super(),this._onChangeTextureAtlas=this._register(new o.Emitter),this.onChangeTextureAtlas=this._onChangeTextureAtlas.event,this._onAddTextureAtlasCanvas=this._register(new o.Emitter),this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event,this._onRemoveTextureAtlasCanvas=this._register(new o.Emitter),this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event,this._onContextLoss=this._register(new o.Emitter),this.onContextLoss=this._onContextLoss.event,this._customGlyphs=t?.customGlyphs??!0,this._preserveDrawingBuffer=t?.preserveDrawingBuffer}activate(t){const i=t._core;if(!t.element)return void this._register(i.onWillOpen(()=>this.activate(t)));this._terminal=t;const s=i.coreService,r=i.optionsService,n=i,L=n._renderService,h=n._characterJoinerService,T=n._charSizeService,l=n._coreBrowserService,d=n._decorationService,c=n._logService,_=n._themeService;this._renderer=this._register(new a.WebglRenderer(t,h,T,l,s,d,c,r,_,this._customGlyphs,this._preserveDrawingBuffer)),this._register(o.EventUtils.forward(this._renderer.onContextLoss,this._onContextLoss)),this._register(o.EventUtils.forward(this._renderer.onChangeTextureAtlas,this._onChangeTextureAtlas)),this._register(o.EventUtils.forward(this._renderer.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas)),this._register(o.EventUtils.forward(this._renderer.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas)),L.setRenderer(this._renderer),this._register((0,e.toDisposable)(()=>{if(this._terminal._core._store._isDisposed)return;const e=this._terminal._core._renderService;e.setRenderer(this._terminal._core._createRenderer()),e.handleResize(t.cols,t.rows)}))}get textureAtlas(){return this._renderer?.textureAtlas}clearTextureAtlas(){this._renderer?.clearTextureAtlas()}}t.WebglAddon=n})(),s})()); -+!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.WebglAddon=e():t.WebglAddon=e()}(globalThis,()=>(()=>{"use strict";var t={105(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.WindowIntervalTimer=e.eventType=void 0,e.getWindow=r,e.addDisposableListener=o,e.addStandardDisposableListener=function(t,e,i,s){return o(t,e,i,s)},e.getDomNodePagePosition=function(t){const e=t.getBoundingClientRect(),i=r(t);return{left:e.left+i.scrollX,top:e.top+i.scrollY,width:e.width,height:e.height}},e.scheduleAtNextAnimationFrame=function(t,e,i=0){const s=h(t),r=new n(e,i);return s.next.push(r),s.animFrameRequested||(s.animFrameRequested=!0,t.requestAnimationFrame(()=>function(t){const e=h(t);for(e.animFrameRequested=!1,e.current=e.next,e.next=[],e.inAnimationFrameRunner=!0;e.current.length>0;)e.current.sort(n.sort),e.current.shift().execute();e.inAnimationFrameRunner=!1}(t))),r};const s=i(578);function r(t){const e=t;if(e?.ownerDocument?.defaultView)return e.ownerDocument.defaultView;const i=t;return i?.view?i.view:window}class a{constructor(t,e,i,s){this._node=t,this._type=e,this._handler=i,this._options=s,t.addEventListener(e,i,s)}dispose(){this._node&&this._handler&&(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,this._handler=null)}}function o(t,e,i,s){return new a(t,e,i,s)}e.eventType={CLICK:"click",MOUSE_DOWN:"mousedown",MOUSE_OVER:"mouseover",MOUSE_LEAVE:"mouseleave",KEY_DOWN:"keydown",KEY_UP:"keyup",INPUT:"input",BLUR:"blur",FOCUS:"focus",CHANGE:"change",POINTER_DOWN:"pointerdown",POINTER_MOVE:"pointermove",POINTER_UP:"pointerup",MOUSE_WHEEL:"wheel",WHEEL:"wheel"};class n{constructor(t,e){this._runner=t,this.priority=e,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(t){console.error(t)}}static sort(t,e){return e.priority-t.priority}}const L=new Map;function h(t){let e=L.get(t);return e||(e={next:[],current:[],animFrameRequested:!1,inAnimationFrameRunner:!1},L.set(t,e)),e}class T extends s.IntervalTimer{constructor(t){super(),this._defaultTarget=t?r(t):void 0}cancelAndSet(t,e,i){super.cancelAndSet(t,e,i??this._defaultTarget??window)}}e.WindowIntervalTimer=T},814(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.INVERTED_DEFAULT_COLOR=void 0,e.INVERTED_DEFAULT_COLOR=257},959(t,e){function i(t){return 57508<=t&&t<=57558}function s(t){return t>=128512&&t<=128591||t>=127744&&t<=128511||t>=128640&&t<=128767||t>=9728&&t<=9983||t>=9984&&t<=10175||t>=65024&&t<=65039||t>=129280&&t<=129535||t>=127462&&t<=127487}Object.defineProperty(e,"__esModule",{value:!0}),e.throwIfFalsy=function(t){if(!t)throw new Error("value must not be falsy");return t},e.isPowerlineGlyph=i,e.isRestrictedPowerlineGlyph=function(t){return 57520<=t&&t<=57527},e.isEmoji=s,e.allowRescaling=function(t,e,r,a){return 1===e&&r>Math.ceil(1.5*a)&&void 0!==t&&t>255&&!s(t)&&!i(t)&&!function(t){return 57344<=t&&t<=63743}(t)},e.treatGlyphAsBackgroundColor=function(t){return i(t)||function(t){return 9472<=t&&t<=9631}(t)},e.createRenderDimensions=function(){return{css:{canvas:{width:0,height:0},cell:{width:0,height:0}},device:{canvas:{width:0,height:0},cell:{width:0,height:0},char:{width:0,height:0,left:0,top:0}}}},e.computeNextVariantOffset=function(t,e,i=0){return(t-(2*Math.round(e)-i))%(2*Math.round(e))}},948(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.createSelectionRenderModel=function(){return new i};class i{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(t,e,i,s=!1){if(this.selectionStart=e,this.selectionEnd=i,!e||!i||e[0]===i[0]&&e[1]===i[1])return void this.clear();const r=t.buffers.active.ydisp,a=e[1]-r,o=i[1]-r,n=Math.max(a,0),L=Math.min(o,t.rows-1);n>=t.rows||L<0?this.clear():(this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=a,this.viewportEndRow=o,this.viewportCappedStartRow=n,this.viewportCappedEndRow=L,this.startCol=e[0],this.endCol=i[0])}isCellSelected(t,e,i){return!!this.hasSelection&&(i-=t.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?e>=this.startCol&&i>=this.viewportCappedStartRow&&e=this.viewportCappedStartRow&&e>=this.endCol&&i<=this.viewportCappedEndRow:i>this.viewportStartRow&&i=this.startCol&&e=this.startCol)}}},536(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.TextBlinkStateManager=void 0;const s=i(426);class r extends s.Disposable{constructor(t,e,i){super(),this._renderCallback=t,this._coreBrowserService=e,this._optionsService=i,this._intervalDuration=0,this._blinkOn=!0,this._needsBlinkInViewport=!1,this._isViewportVisible=!0,this._register(this._optionsService.onSpecificOptionChange("blinkIntervalDuration",t=>{this.setIntervalDuration(t)})),this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration),this._register((0,s.toDisposable)(()=>this._clearInterval()))}get isBlinkOn(){return this._blinkOn}get isEnabled(){return this._intervalDuration>0}setNeedsBlinkInViewport(t){this._needsBlinkInViewport!==t&&(this._needsBlinkInViewport=t,this._updateIntervalState())}setViewportVisible(t){this._isViewportVisible!==t&&(this._isViewportVisible=t,this._updateIntervalState())}setIntervalDuration(t){t!==this._intervalDuration&&(this._intervalDuration=t,this._clearInterval(),this._updateIntervalState())}_updateIntervalState(){if(this._intervalDuration>0&&this._needsBlinkInViewport&&this._isViewportVisible){if(void 0!==this._interval)return;const t=this._blinkOn;return this._blinkOn=!0,this._interval=this._coreBrowserService.window.setInterval(()=>{this._blinkOn=!this._blinkOn,this._renderCallback()},this._intervalDuration),void(t||this._renderCallback())}this._clearInterval(),this._blinkOn||(this._blinkOn=!0,this._renderCallback())}_clearInterval(){void 0!==this._interval&&(this._coreBrowserService.window.clearInterval(this._interval),this._interval=void 0)}}e.TextBlinkStateManager=r},578(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.IntervalTimer=e.MicrotaskTimer=e.TimeoutTimer=void 0,e.timeout=function(t){return new Promise(e=>setTimeout(e,t))},e.disposableTimeout=function(t,e=0,i){const r=setTimeout(()=>{t(),i&&a.dispose()},e),a=(0,s.toDisposable)(()=>{clearTimeout(r)});return i?.add(a),a};const s=i(426);e.TimeoutTimer=class{constructor(){this._token=-1,this._isDisposed=!1}dispose(){this.cancel(),this._isDisposed=!0}cancel(){-1!==this._token&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(t,e){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout(()=>{this._token=-1,t()},e)}setIfNotSet(t,e){if(this._isDisposed)throw new Error("Calling setIfNotSet on a disposed TimeoutTimer");-1===this._token&&(this._token=setTimeout(()=>{this._token=-1,t()},e))}},e.MicrotaskTimer=class{constructor(){this._isScheduled=!1,this._isDisposed=!1}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._isScheduled=!1}set(t){if(this._isDisposed)throw new Error("Calling set on a disposed MicrotaskTimer");this._isScheduled||(this._isScheduled=!0,queueMicrotask(()=>{this._isScheduled&&(this._isScheduled=!1,t())}))}},e.IntervalTimer=class{constructor(){this._isDisposed=!1}cancel(){this._disposable?.dispose(),this._disposable=void 0}cancelAndSet(t,e,i=globalThis){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed IntervalTimer");this.cancel();const s=i.setInterval(()=>{t()},e);this._disposable={dispose:()=>{i.clearInterval(s),this._disposable=void 0}}}dispose(){this.cancel(),this._isDisposed=!0}}},993(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.rgba=e.rgb=e.css=e.color=e.channels=e.NULL_COLOR=void 0,e.toPaddedHex=l,e.contrastRatio=d;let i=0,s=0,r=0,a=0;var o,n,L,h,T;function l(t){const e=t.toString(16);return e.length<2?"0"+e:e}function d(t,e){return t>>0},t.toColor=function(e,i,s,r){return{css:t.toCss(e,i,s,r),rgba:t.toRgba(e,i,s,r)}}}(o||(e.channels=o={})),function(t){function e(t,e){return a=Math.round(255*e),[i,s,r]=T.toChannels(t.rgba),{css:o.toCss(i,s,r,a),rgba:o.toRgba(i,s,r,a)}}t.blend=function(t,e){if(a=(255&e.rgba)/255,1===a)return{css:e.css,rgba:e.rgba};const n=e.rgba>>24&255,L=e.rgba>>16&255,h=e.rgba>>8&255,T=t.rgba>>24&255,l=t.rgba>>16&255,d=t.rgba>>8&255;return i=T+Math.round((n-T)*a),s=l+Math.round((L-l)*a),r=d+Math.round((h-d)*a),{css:o.toCss(i,s,r),rgba:o.toRgba(i,s,r)}},t.isOpaque=function(t){return!(255&~t.rgba)},t.ensureContrastRatio=function(t,e,i){const s=T.ensureContrastRatio(t.rgba,e.rgba,i);if(s)return o.toColor(s>>24&255,s>>16&255,s>>8&255)},t.opaque=function(t){const e=(255|t.rgba)>>>0;return[i,s,r]=T.toChannels(e),{css:o.toCss(i,s,r),rgba:e}},t.opacity=e,t.multiplyOpacity=function(t,i){return a=255&t.rgba,e(t,a*i/255)},t.toColorRGB=function(t){return[t.rgba>>24&255,t.rgba>>16&255,t.rgba>>8&255]}}(n||(e.color=n={})),function(t){let e,n;try{const t=document.createElement("canvas");t.width=1,t.height=1;const i=t.getContext("2d",{willReadFrequently:!0});i&&(e=i,e.globalCompositeOperation="copy",n=e.createLinearGradient(0,0,1,1))}catch{}t.toColor=function(t){if(t.match(/#[\da-f]{3,8}/i))switch(t.length){case 4:return i=parseInt(t.slice(1,2).repeat(2),16),s=parseInt(t.slice(2,3).repeat(2),16),r=parseInt(t.slice(3,4).repeat(2),16),o.toColor(i,s,r);case 5:return i=parseInt(t.slice(1,2).repeat(2),16),s=parseInt(t.slice(2,3).repeat(2),16),r=parseInt(t.slice(3,4).repeat(2),16),a=parseInt(t.slice(4,5).repeat(2),16),o.toColor(i,s,r,a);case 7:return{css:t,rgba:(parseInt(t.slice(1),16)<<8|255)>>>0};case 9:return{css:t,rgba:parseInt(t.slice(1),16)>>>0}}const L=t.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(L)return i=parseInt(L[1],10),s=parseInt(L[2],10),r=parseInt(L[3],10),a=Math.round(255*(void 0===L[5]?1:parseFloat(L[5]))),o.toColor(i,s,r,a);if("transparent"===t)return{css:"transparent",rgba:0};if(!e||!n)throw new Error("css.toColor: Unsupported css format");if(e.fillStyle=n,e.fillStyle=t,"string"!=typeof e.fillStyle)throw new Error("css.toColor: Unsupported css format");if(e.fillRect(0,0,1,1),[i,s,r,a]=e.getImageData(0,0,1,1).data,255!==a)throw new Error("css.toColor: Unsupported css format");return{rgba:o.toRgba(i,s,r,a),css:t}}}(L||(e.css=L={})),function(t){function e(t,e,i){const s=t/255,r=e/255,a=i/255;return.2126*(s<=.03928?s/12.92:Math.pow((s+.055)/1.055,2.4))+.7152*(r<=.03928?r/12.92:Math.pow((r+.055)/1.055,2.4))+.0722*(a<=.03928?a/12.92:Math.pow((a+.055)/1.055,2.4))}t.relativeLuminance=function(t){return e(t>>16&255,t>>8&255,255&t)},t.relativeLuminance2=e}(h||(e.rgb=h={})),function(t){function e(t,e,i){const s=t>>24&255,r=t>>16&255,a=t>>8&255;let o=e>>24&255,n=e>>16&255,L=e>>8&255,T=d(h.relativeLuminance2(o,n,L),h.relativeLuminance2(s,r,a));for(;T0||n>0||L>0);)o-=Math.max(0,Math.ceil(.1*o)),n-=Math.max(0,Math.ceil(.1*n)),L-=Math.max(0,Math.ceil(.1*L)),T=d(h.relativeLuminance2(o,n,L),h.relativeLuminance2(s,r,a));return(o<<24|n<<16|L<<8|255)>>>0}function n(t,e,i){const s=t>>24&255,r=t>>16&255,a=t>>8&255;let o=e>>24&255,n=e>>16&255,L=e>>8&255,T=d(h.relativeLuminance2(o,n,L),h.relativeLuminance2(s,r,a));for(;T>>0}t.blend=function(t,e){if(a=(255&e)/255,1===a)return e;const n=e>>24&255,L=e>>16&255,h=e>>8&255,T=t>>24&255,l=t>>16&255,d=t>>8&255;return i=T+Math.round((n-T)*a),s=l+Math.round((L-l)*a),r=d+Math.round((h-d)*a),o.toRgba(i,s,r)},t.ensureContrastRatio=function(t,i,s){const r=h.relativeLuminance(t>>8),a=h.relativeLuminance(i>>8);if(d(r,a)>8));if(od(r,h.relativeLuminance(e>>8))?a:e}return a}const o=n(t,i,s),L=d(r,h.relativeLuminance(o>>8));if(Ld(r,h.relativeLuminance(a>>8))?o:a}return o}},t.reduceLuminance=e,t.increaseLuminance=n,t.toChannels=function(t){return[t>>24&255,t>>16&255,t>>8&255,255&t]}}(T||(e.rgba=T={}))},414(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.EventUtils=e.Emitter=void 0;const s=i(426);var r;e.Emitter=class{constructor(){this._listeners=[],this._disposed=!1}get event(){return this._event||(this._event=(t,e,i)=>{if(this._disposed)return(0,s.toDisposable)(()=>{});const r={fn:t,thisArgs:e};this._listeners.push(r);const a=(0,s.toDisposable)(()=>{const t=this._listeners.indexOf(r);-1!==t&&this._listeners.splice(t,1)});return i&&(Array.isArray(i)?i.push(a):i.add(a)),a}),this._event}fire(t){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{const{fn:e,thisArgs:i}=this._listeners[0];return void e.call(i,t)}default:{const e=this._listeners.slice();for(const{fn:i,thisArgs:s}of e)i.call(s,t)}}}dispose(){this._disposed||(this._disposed=!0,this._listeners.length=0)}},function(t){t.forward=function(t,e){return t(t=>e.fire(t))},t.map=function(t,e){return(i,s,r)=>t(t=>i.call(s,e(t)),void 0,r)},t.any=function(...t){return(e,i,r)=>{const a=new s.DisposableStore;for(const s of t)a.add(s(t=>e.call(i,t)));return r&&(Array.isArray(r)?r.push(a):r.add(a)),a}},t.runAndSubscribe=function(t,e,i){return e(i),t(t=>e(t))}}(r||(e.EventUtils=r={}))},426(t,e){function i(t){return{dispose:t}}function s(t){if(!t)return t;if(Array.isArray(t)){for(const e of t)e.dispose();return[]}return t.dispose(),t}Object.defineProperty(e,"__esModule",{value:!0}),e.MutableDisposable=e.Disposable=e.DisposableStore=void 0,e.toDisposable=i,e.dispose=s,e.combinedDisposable=function(...t){return i(()=>s(t))};class r{constructor(){this._disposables=new Set,this._isDisposed=!1}get isDisposed(){return this._isDisposed}add(t){return this._isDisposed?t.dispose():this._disposables.add(t),t}dispose(){if(!this._isDisposed){this._isDisposed=!0;for(const t of this._disposables)t.dispose();this._disposables.clear()}}clear(){for(const t of this._disposables)t.dispose();this._disposables.clear()}}e.DisposableStore=r;class a{constructor(){this._store=new r}dispose(){this._store.dispose()}_register(t){return this._store.add(t)}}e.Disposable=a,a.None=Object.freeze({dispose(){}}),e.MutableDisposable=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(t){this._isDisposed||t===this._value||(this._value?.dispose(),this._value=t)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,this._value?.dispose(),this._value=void 0}}},836(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.FourKeyMap=e.TwoKeyMap=void 0;class i{constructor(){this._data={}}set(t,e,i){this._data[t]||(this._data[t]={}),this._data[t][e]=i}get(t,e){return this._data[t]?this._data[t][e]:void 0}clear(){this._data={}}}e.TwoKeyMap=i,e.FourKeyMap=class{constructor(){this._data=new i}set(t,e,s,r,a){this._data.get(t,e)||this._data.set(t,e,new i),this._data.get(t,e).set(s,r,a)}get(t,e,i,s){return this._data.get(t,e)?.get(i,s)}clear(){this._data.clear()}}},95(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.isChromeOS=e.isLinux=e.isWindows=e.isMac=e.isSafari=e.isLegacyEdge=e.isChrome=e.isFirefox=e.isNode=void 0,e.getZoomFactor=function(t){return 1},e.getSafariVersion=function(){if(!e.isSafari)return 0;const t=i.match(/Version\/(\d+)/);return null===t||t.length<2?0:parseInt(t[1],10)},e.isNode=!("undefined"==typeof process||!("title"in process)||"undefined"!=typeof navigator&&!navigator.userAgent.startsWith("Node.js/"));const i=e.isNode?"node":navigator.userAgent,s=e.isNode?"node":navigator.platform;e.isFirefox=i.includes("Firefox"),e.isChrome=i.includes("Chrome"),e.isLegacyEdge=i.includes("Edge"),e.isSafari=/^((?!chrome|android).)*safari/i.test(i),e.isMac=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(s),e.isWindows=["Windows","Win16","Win32","WinCE"].includes(s),e.isLinux=s.indexOf("Linux")>=0,e.isChromeOS=/\bCrOS\b/.test(i)},930(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.DebouncedIdleTask=e.IdleTaskQueue=e.PriorityTaskQueue=void 0;class i{constructor(t){this._tasks=[],this._i=0,this._logService=t}enqueue(t){this._tasks.push(t),this._start()}flush(){for(;this._ii)return r-e<-20&&this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(r-e))}ms`),void this._start();r=i}this.clear()}}class s extends i{_requestCallback(t){return setTimeout(()=>t(this._createDeadline(16)))}_cancelCallback(t){clearTimeout(t)}_createDeadline(t){const e=performance.now()+t;return{timeRemaining:()=>Math.max(0,e-performance.now())}}}e.PriorityTaskQueue=s,e.IdleTaskQueue="requestIdleCallback"in globalThis?class extends i{_requestCallback(t){return requestIdleCallback(t)}_cancelCallback(t){cancelIdleCallback(t)}}:s,e.DebouncedIdleTask=class{constructor(t){this._queue=new e.IdleTaskQueue(t)}set(t){this._queue.clear(),this._queue.enqueue(t)}flush(){this._queue.flush()}dispose(){this._queue.clear()}}},917(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.ExtendedAttrs=e.AttributeData=void 0;class i{constructor(){this.fg=0,this.bg=0,this.extended=new s}static toColorRGB(t){return[t>>>16&255,t>>>8&255,255&t]}static fromColorRGB(t){return(255&t[0])<<16|(255&t[1])<<8|255&t[2]}clone(){const t=new i;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t}isInverse(){return 67108864&this.fg}isBold(){return 134217728&this.fg}isUnderline(){return this.hasExtendedAttrs()&&0!==this.extended.underlineStyle?1:268435456&this.fg}isBlink(){return 536870912&this.fg}isInvisible(){return 1073741824&this.fg}isItalic(){return 67108864&this.bg}isDim(){return 134217728&this.bg}isStrikethrough(){return 2147483648&this.fg}isProtected(){return 536870912&this.bg}isOverline(){return 1073741824&this.bg}getFgColorMode(){return 50331648&this.fg}getBgColorMode(){return 50331648&this.bg}isFgRGB(){return!(50331648&~this.fg)}isBgRGB(){return!(50331648&~this.bg)}isFgPalette(){return 16777216==(50331648&this.fg)||33554432==(50331648&this.fg)}isBgPalette(){return 16777216==(50331648&this.bg)||33554432==(50331648&this.bg)}isFgDefault(){return!(50331648&this.fg)}isBgDefault(){return!(50331648&this.bg)}isAttributeDefault(){return 0===this.fg&&0===this.bg}getFgColor(){switch(50331648&this.fg){case 16777216:case 33554432:return 255&this.fg;case 50331648:return 16777215&this.fg;default:return-1}}getBgColor(){switch(50331648&this.bg){case 16777216:case 33554432:return 255&this.bg;case 50331648:return 16777215&this.bg;default:return-1}}hasExtendedAttrs(){return 268435456&this.bg}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(268435456&this.bg&&~this.extended.underlineColor)switch(50331648&this.extended.underlineColor){case 16777216:case 33554432:return 255&this.extended.underlineColor;case 50331648:return 16777215&this.extended.underlineColor;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return 268435456&this.bg&&~this.extended.underlineColor?50331648&this.extended.underlineColor:this.getFgColorMode()}isUnderlineColorRGB(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&~this.extended.underlineColor):this.isFgRGB()}isUnderlineColorPalette(){return 268435456&this.bg&&~this.extended.underlineColor?16777216==(50331648&this.extended.underlineColor)||33554432==(50331648&this.extended.underlineColor):this.isFgPalette()}isUnderlineColorDefault(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&this.extended.underlineColor):this.isFgDefault()}getUnderlineStyle(){return 268435456&this.fg?268435456&this.bg?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}}e.AttributeData=i;class s{get ext(){return this._urlId?-469762049&this._ext|this.underlineStyle<<26:this._ext}set ext(t){this._ext=t}get underlineStyle(){return this._urlId?5:(469762048&this._ext)>>26}set underlineStyle(t){this._ext&=-469762049,this._ext|=t<<26&469762048}get underlineColor(){return 67108863&this._ext}set underlineColor(t){this._ext&=-67108864,this._ext|=67108863&t}get urlId(){return this._urlId}set urlId(t){this._urlId=t}get underlineVariantOffset(){const t=(3758096384&this._ext)>>29;return t<0?4294967288^t:t}set underlineVariantOffset(t){this._ext&=536870911,this._ext|=t<<29&3758096384}constructor(t=0,e=0){this._ext=0,this._urlId=0,this._ext=t,this._urlId=e}clone(){return new s(this._ext,this._urlId)}isEmpty(){return 0===this.underlineStyle&&0===this._urlId}}e.ExtendedAttrs=s},721(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.CellData=void 0;const s=i(348),r=i(564),a=i(917);class o extends a.AttributeData{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new a.ExtendedAttrs,this.combinedData=""}static fromCharData(t){const e=new o;return e.setFromCharData(t),e}isCombined(){return 2097152&this.content}getWidth(){return this.content>>22}getChars(){return 2097152&this.content?this.combinedData:2097151&this.content?(0,s.stringFromCodePoint)(2097151&this.content):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):2097151&this.content}setFromCharData(t){this.fg=t[r.CHAR_DATA_ATTR_INDEX],this.bg=0;let e=!1;if(t[r.CHAR_DATA_CHAR_INDEX].length>2)e=!0;else if(2===t[r.CHAR_DATA_CHAR_INDEX].length){const i=t[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0);if(55296<=i&&i<=56319){const s=t[r.CHAR_DATA_CHAR_INDEX].charCodeAt(1);56320<=s&&s<=57343?this.content=1024*(i-55296)+s-56320+65536|t[r.CHAR_DATA_WIDTH_INDEX]<<22:e=!0}else e=!0}else this.content=t[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|t[r.CHAR_DATA_WIDTH_INDEX]<<22;e&&(this.combinedData=t[r.CHAR_DATA_CHAR_INDEX],this.content=2097152|t[r.CHAR_DATA_WIDTH_INDEX]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}attributesEquals(t){if(this.getFgColorMode()!==t.getFgColorMode()||this.getFgColor()!==t.getFgColor())return!1;if(this.getBgColorMode()!==t.getBgColorMode()||this.getBgColor()!==t.getBgColor())return!1;if(this.isInverse()!==t.isInverse())return!1;if(this.isBold()!==t.isBold())return!1;if(this.isUnderline()!==t.isUnderline())return!1;if(this.isUnderline()){if(this.getUnderlineStyle()!==t.getUnderlineStyle())return!1;const e=this.isUnderlineColorDefault(),i=t.isUnderlineColorDefault();if(!e||!i){if(e!==i)return!1;if(this.getUnderlineColor()!==t.getUnderlineColor())return!1;if(this.getUnderlineColorMode()!==t.getUnderlineColorMode())return!1}}return this.isOverline()===t.isOverline()&&this.isBlink()===t.isBlink()&&this.isInvisible()===t.isInvisible()&&this.isItalic()===t.isItalic()&&this.isDim()===t.isDim()&&this.isStrikethrough()===t.isStrikethrough()}}e.CellData=o},564(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.WHITESPACE_CELL_CODE=e.WHITESPACE_CELL_WIDTH=e.WHITESPACE_CELL_CHAR=e.NULL_CELL_CODE=e.NULL_CELL_WIDTH=e.NULL_CELL_CHAR=e.CHAR_DATA_CODE_INDEX=e.CHAR_DATA_WIDTH_INDEX=e.CHAR_DATA_CHAR_INDEX=e.CHAR_DATA_ATTR_INDEX=e.DEFAULT_EXT=e.DEFAULT_ATTR=e.DEFAULT_COLOR=void 0,e.DEFAULT_COLOR=0,e.DEFAULT_ATTR=e.DEFAULT_COLOR<<9|256,e.DEFAULT_EXT=0,e.CHAR_DATA_ATTR_INDEX=0,e.CHAR_DATA_CHAR_INDEX=1,e.CHAR_DATA_WIDTH_INDEX=2,e.CHAR_DATA_CODE_INDEX=3,e.NULL_CELL_CHAR="",e.NULL_CELL_WIDTH=1,e.NULL_CELL_CODE=0,e.WHITESPACE_CELL_CHAR=" ",e.WHITESPACE_CELL_WIDTH=1,e.WHITESPACE_CELL_CODE=32},348(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.Utf8ToUtf32=e.StringToUtf32=void 0,e.stringFromCodePoint=function(t){return t>65535?(t-=65536,String.fromCharCode(55296+(t>>10))+String.fromCharCode(t%1024+56320)):String.fromCharCode(t)},e.utf32ToString=function(t,e=0,i=t.length){let s="";for(let r=e;r65535?(e-=65536,s+=String.fromCharCode(55296+(e>>10))+String.fromCharCode(e%1024+56320)):s+=String.fromCharCode(e)}return s},e.StringToUtf32=class{constructor(){this._interim=0}clear(){this._interim=0}decode(t,e){const i=t.length;if(!i)return 0;let s=0,r=0;if(this._interim){const i=t.charCodeAt(r++);56320<=i&&i<=57343?e[s++]=1024*(this._interim-55296)+i-56320+65536:(e[s++]=this._interim,e[s++]=i),this._interim=0}for(let a=r;a=i)return this._interim=r,s;const o=t.charCodeAt(a);56320<=o&&o<=57343?e[s++]=1024*(r-55296)+o-56320+65536:(e[s++]=r,e[s++]=o);continue}65279!==r&&(e[s++]=r)}return s}},e.Utf8ToUtf32=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(t,e){const i=t.length;if(!i)return 0;let s,r,a,o,n,L=0,h=0;if(this.interim[0]){let s=!1,r=this.interim[0];r&=192==(224&r)?31:224==(240&r)?15:7;let a,o=0;for(;(a=this.interim[++o])&&o<4;)r<<=6,r|=63&a;const n=192==(224&this.interim[0])?2:224==(240&this.interim[0])?3:4,T=n-o;for(;h=i)return 0;if(a=t[h++],128!=(192&a)){h--,s=!0;break}this.interim[o++]=a,r<<=6,r|=63&a}s||(2===n?r<128?h--:e[L++]=r:3===n?r<2048||r>=55296&&r<=57343||65279===r||(e[L++]=r):r<65536||r>1114111||(e[L++]=r)),this.interim.fill(0)}const T=i-4;let l=h;for(;l=i)return this.interim[0]=s,L;if(r=t[l++],128!=(192&r)){l--;continue}if(n=(31&s)<<6|63&r,n<128){l--;continue}e[L++]=n}else if(224==(240&s)){if(l>=i)return this.interim[0]=s,L;if(r=t[l++],128!=(192&r)){l--;continue}if(l>=i)return this.interim[0]=s,this.interim[1]=r,L;if(a=t[l++],128!=(192&a)){l--;continue}if(n=(15&s)<<12|(63&r)<<6|63&a,n<2048||n>=55296&&n<=57343||65279===n)continue;e[L++]=n}else if(240==(248&s)){if(l>=i)return this.interim[0]=s,L;if(r=t[l++],128!=(192&r)){l--;continue}if(l>=i)return this.interim[0]=s,this.interim[1]=r,L;if(a=t[l++],128!=(192&a)){l--;continue}if(l>=i)return this.interim[0]=s,this.interim[1]=r,this.interim[2]=a,L;if(o=t[l++],128!=(192&o)){l--;continue}if(n=(7&s)<<18|(63&r)<<12|(63&a)<<6|63&o,n<65536||n>1114111)continue;e[L++]=n}}return L}}},864(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.CellColorResolver=void 0;const s=i(564),r=i(993),a=i(959),o=i(915);let n,L=0,h=0,T=!1,l=!1,d=!1,c=0;e.CellColorResolver=class{constructor(t,e,i,s,r,a){this._terminal=t,this._optionService=e,this._selectionRenderModel=i,this._decorationService=s,this._coreBrowserService=r,this._themeService=a,this.result={fg:0,bg:0,ext:0}}resolve(t,e,i,_,Q){this.result.bg=t.bg,this.result.fg=t.fg,this.result.ext=268435456&t.bg?t.extended.ext:0,h=0,L=0,l=!1,T=!1,d=!1,n=this._themeService.colors,c=0;const u=t.getCode();if(u!==s.NULL_CELL_CODE&&4===t.extended.underlineStyle){const t=Math.max(1,Math.floor(this._optionService.rawOptions.fontSize*this._coreBrowserService.dpr/15));c=e*_%(2*Math.round(t))}if(0===c&&o.blockPatternCodepoints.has(u)&&(c=e*_%2*2+i*Q%2),this._decorationService.forEachDecorationAtCell(e,i,"bottom",t=>{t.backgroundColorRGB&&(h=t.backgroundColorRGB.rgba>>8&16777215,l=!0),t.foregroundColorRGB&&(L=t.foregroundColorRGB.rgba>>8&16777215,T=!0)}),d=this._selectionRenderModel.isCellSelected(this._terminal,e,i),d){if(67108864&this.result.fg||50331648&this.result.bg){if(67108864&this.result.fg)switch(50331648&this.result.fg){case 16777216:case 33554432:h=this._themeService.colors.ansi[255&this.result.fg].rgba;break;case 50331648:h=(16777215&this.result.fg)<<8|255;break;default:h=this._themeService.colors.foreground.rgba}else switch(50331648&this.result.bg){case 16777216:case 33554432:h=this._themeService.colors.ansi[255&this.result.bg].rgba;break;case 50331648:h=(16777215&this.result.bg)<<8|255}h=r.rgba.blend(h,4294967040&(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba|128)>>8&16777215}else h=(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba>>8&16777215;if(l=!0,n.selectionForeground&&(L=n.selectionForeground.rgba>>8&16777215,T=!0),(0,a.treatGlyphAsBackgroundColor)(t.getCode())){if(67108864&this.result.fg&&!(50331648&this.result.bg))L=(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba>>8&16777215;else{if(67108864&this.result.fg)switch(50331648&this.result.bg){case 16777216:case 33554432:L=this._themeService.colors.ansi[255&this.result.bg].rgba;break;case 50331648:L=(16777215&this.result.bg)<<8|255}else switch(50331648&this.result.fg){case 16777216:case 33554432:L=this._themeService.colors.ansi[255&this.result.fg].rgba;break;case 50331648:L=(16777215&this.result.fg)<<8|255;break;default:L=this._themeService.colors.foreground.rgba}L=r.rgba.blend(L,4294967040&(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba|128)>>8&16777215}T=!0}}this._decorationService.forEachDecorationAtCell(e,i,"top",t=>{t.backgroundColorRGB&&(h=t.backgroundColorRGB.rgba>>8&16777215,l=!0),t.foregroundColorRGB&&(L=t.foregroundColorRGB.rgba>>8&16777215,T=!0)}),l&&(h=d?-16777216&t.bg&-134217729|h|50331648:-16777216&t.bg|h|50331648),T&&(L=-16777216&t.fg&-67108865|L|50331648),67108864&this.result.fg&&(l&&!T&&(L=50331648&this.result.bg?-134217728&this.result.fg|67108863&this.result.bg:-134217728&this.result.fg|16777215&n.background.rgba>>8|50331648,T=!0),!l&&T&&(h=50331648&this.result.fg?-67108864&this.result.bg|67108863&this.result.fg:-67108864&this.result.bg|16777215&n.foreground.rgba>>8|50331648,l=!0)),n=void 0,this.result.bg=l?h:this.result.bg,this.result.fg=T?L:this.result.fg,this.result.ext&=536870911,this.result.ext|=c<<29&3758096384}}},670(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.acquireTextureAtlas=function(t,e,i,o,n,L,h,T,l,d=!0){const c=(0,r.generateConfig)(o,n,L,h,e,i,T,l,d);for(let e=0;e=0){if((0,r.configEquals)(i.config,c))return i.atlas;1===i.ownedBy.length?(i.atlas.dispose(),a.splice(e,1)):i.ownedBy.splice(s,1);break}}for(let e=0;e{this._renderCallback(),this._animationFrame=void 0})))}_restartInterval(t=600){this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout=this._coreBrowserService.window.setTimeout(()=>{if(this._animationTimeRestarted){const t=600-(Date.now()-this._animationTimeRestarted);if(this._animationTimeRestarted=void 0,t>0)return void this._restartInterval(t)}this.isCursorVisible=!1,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._renderCallback(),this._animationFrame=void 0}),this._blinkInterval=this._coreBrowserService.window.setInterval(()=>{if(this._animationTimeRestarted){const t=600-(Date.now()-this._animationTimeRestarted);return this._animationTimeRestarted=void 0,void this._restartInterval(t)}this.isCursorVisible=!this.isCursorVisible,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._renderCallback(),this._animationFrame=void 0})},600)},t)}pause(){this.isCursorVisible=!0,this._isIdlePaused=!1,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0),this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}resume(){this.pause(),this._animationTimeRestarted=void 0,this._restartInterval(),this._resetIdleTimer(),this.restartBlinkAnimation()}_resetIdleTimer(){this._isIdlePaused=!1,this._idleTimeout&&this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_stopBlinkingDueToIdle(){this.isCursorVisible=!0,this._isIdlePaused=!0,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0),this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0,this._renderCallback()}}},697(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.observeDevicePixelDimensions=function(t,e,i){let r=new e.ResizeObserver(e=>{const s=e.find(e=>e.target===t);if(!s)return;if(!("devicePixelContentBoxSize"in s))return r?.disconnect(),void(r=void 0);const a=s.devicePixelContentBoxSize[0].inlineSize,o=s.devicePixelContentBoxSize[0].blockSize;a>0&&o>0&&i(a,o)});try{r.observe(t,{box:["device-pixel-content-box"]})}catch{r.disconnect(),r=void 0}return(0,s.toDisposable)(()=>r?.disconnect())};const s=i(426)},28(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.GlyphRenderer=void 0;const s=i(115),r=i(564),a=i(426),o=i(719),n=i(959);let L,h=0,T=0,l=0;class d extends a.Disposable{constructor(t,e,i,r,L){super(),this._terminal=t,this._gl=e,this._dimensions=i,this._optionsService=r,this._logService=L,this._activeBuffer=0,this._vertices={count:0,attributes:new Float32Array(0),attributesBuffers:[new Float32Array(0),new Float32Array(0)]};const h=this._gl;void 0===s.TextureAtlas.maxAtlasPages&&(s.TextureAtlas.maxAtlasPages=Math.min(32,(0,n.throwIfFalsy)(h.getParameter(h.MAX_TEXTURE_IMAGE_UNITS))),s.TextureAtlas.maxTextureSize=(0,n.throwIfFalsy)(h.getParameter(h.MAX_TEXTURE_SIZE))),this._program=(0,n.throwIfFalsy)((0,o.createProgram)(h,"#version 300 es\nlayout (location = 0) in vec2 a_unitquad;\nlayout (location = 1) in vec2 a_cellpos;\nlayout (location = 2) in vec2 a_offset;\nlayout (location = 3) in vec2 a_size;\nlayout (location = 4) in float a_texpage;\nlayout (location = 5) in vec2 a_texcoord;\nlayout (location = 6) in vec2 a_texsize;\n\nuniform mat4 u_projection;\nuniform vec2 u_resolution;\n\nout vec2 v_texcoord;\nflat out int v_texpage;\n\nvoid main() {\n vec2 zeroToOne = (a_offset / u_resolution) + a_cellpos + (a_unitquad * a_size);\n gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0);\n v_texpage = int(a_texpage);\n v_texcoord = a_texcoord + a_unitquad * a_texsize;\n}",function(t){let e="";for(let i=1;ih.deleteProgram(this._program))),this._projectionLocation=(0,n.throwIfFalsy)(h.getUniformLocation(this._program,"u_projection")),this._resolutionLocation=(0,n.throwIfFalsy)(h.getUniformLocation(this._program,"u_resolution")),this._textureLocation=(0,n.throwIfFalsy)(h.getUniformLocation(this._program,"u_texture"));const T=this._vertexArrayObject=h.createVertexArray();this._register((0,a.toDisposable)(()=>h.deleteVertexArray(T))),h.bindVertexArray(T);const l=new Float32Array([0,0,1,0,0,1,1,1]),d=h.createBuffer();this._register((0,a.toDisposable)(()=>h.deleteBuffer(d))),h.bindBuffer(h.ARRAY_BUFFER,d),h.bufferData(h.ARRAY_BUFFER,l,h.STATIC_DRAW),h.enableVertexAttribArray(0),h.vertexAttribPointer(0,2,this._gl.FLOAT,!1,0,0);const c=new Uint8Array([0,1,2,3]),_=h.createBuffer();this._register((0,a.toDisposable)(()=>h.deleteBuffer(_))),h.bindBuffer(h.ELEMENT_ARRAY_BUFFER,_),h.bufferData(h.ELEMENT_ARRAY_BUFFER,c,h.STATIC_DRAW),this._attributesBuffer=(0,n.throwIfFalsy)(h.createBuffer()),this._register((0,a.toDisposable)(()=>h.deleteBuffer(this._attributesBuffer))),h.bindBuffer(h.ARRAY_BUFFER,this._attributesBuffer),h.enableVertexAttribArray(2),h.vertexAttribPointer(2,2,h.FLOAT,!1,44,0),h.vertexAttribDivisor(2,1),h.enableVertexAttribArray(3),h.vertexAttribPointer(3,2,h.FLOAT,!1,44,2*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(3,1),h.enableVertexAttribArray(4),h.vertexAttribPointer(4,1,h.FLOAT,!1,44,4*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(4,1),h.enableVertexAttribArray(5),h.vertexAttribPointer(5,2,h.FLOAT,!1,44,5*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(5,1),h.enableVertexAttribArray(6),h.vertexAttribPointer(6,2,h.FLOAT,!1,44,7*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(6,1),h.enableVertexAttribArray(1),h.vertexAttribPointer(1,2,h.FLOAT,!1,44,9*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(1,1),h.useProgram(this._program);const Q=new Int32Array(s.TextureAtlas.maxAtlasPages);for(let t=0;th.deleteTexture(e.texture))),h.activeTexture(h.TEXTURE0+t),h.bindTexture(h.TEXTURE_2D,e.texture),h.texParameteri(h.TEXTURE_2D,h.TEXTURE_WRAP_S,h.CLAMP_TO_EDGE),h.texParameteri(h.TEXTURE_2D,h.TEXTURE_WRAP_T,h.CLAMP_TO_EDGE),h.texImage2D(h.TEXTURE_2D,0,h.RGBA,1,1,0,h.RGBA,h.UNSIGNED_BYTE,new Uint8Array([255,0,0,255])),this._atlasTextures[t]=e}h.enable(h.BLEND),h.blendFunc(h.SRC_ALPHA,h.ONE_MINUS_SRC_ALPHA),this.handleResize()}beginFrame(){if(!this._atlas)return!0;const _g=this._atlas._clearModelGeneration||0;return this._lastSeenClearModelGeneration!==_g&&(this._lastSeenClearModelGeneration=_g,!0)}updateCell(t,e,i,s,r,a,o,n,L){this._updateCell(this._vertices.attributes,t,e,i,s,r,a,o,n,L)}_updateCell(t,e,i,s,a,o,d,c,_,Q){h=11*(i*this._terminal.cols+e),s!==r.NULL_CELL_CODE&&void 0!==s?this._atlas&&(L=c&&c.length>1?this._atlas.getRasterizedGlyphCombinedChar(c,a,o,d,!1,this._terminal.element):this._atlas.getRasterizedGlyph(s,a,o,d,!1,this._terminal.element),T=Math.floor((this._dimensions.device.cell.width-this._dimensions.device.char.width)/2),a!==Q&&L.offset.x>T?(l=L.offset.x-T,t[h]=-(L.offset.x-l)+this._dimensions.device.char.left,t[h+1]=-L.offset.y+this._dimensions.device.char.top,t[h+2]=(L.size.x-l)/this._dimensions.device.canvas.width,t[h+3]=L.size.y/this._dimensions.device.canvas.height,t[h+4]=L.texturePage,t[h+5]=L.texturePositionClipSpace.x+l/this._atlas.pages[L.texturePage].canvas.width,t[h+6]=L.texturePositionClipSpace.y,t[h+7]=L.sizeClipSpace.x-l/this._atlas.pages[L.texturePage].canvas.width,t[h+8]=L.sizeClipSpace.y):(t[h]=-L.offset.x+this._dimensions.device.char.left,t[h+1]=-L.offset.y+this._dimensions.device.char.top,t[h+2]=L.size.x/this._dimensions.device.canvas.width,t[h+3]=L.size.y/this._dimensions.device.canvas.height,t[h+4]=L.texturePage,t[h+5]=L.texturePositionClipSpace.x,t[h+6]=L.texturePositionClipSpace.y,t[h+7]=L.sizeClipSpace.x,t[h+8]=L.sizeClipSpace.y),this._optionsService.rawOptions.rescaleOverlappingGlyphs&&(0,n.allowRescaling)(s,_,L.size.x,this._dimensions.device.cell.width)&&(t[h+2]=(this._dimensions.device.cell.width-1)/this._dimensions.device.canvas.width)):t.fill(0,h,h+11-1-2)}clear(){const t=this._terminal,e=t.cols*t.rows*11;this._vertices.count!==e?this._vertices.attributes=new Float32Array(e):this._vertices.attributes.fill(0);let i=0;for(;ithis._atlasTextures.length&&!this._pageOverflowWarned&&(this._pageOverflowWarned=!0,console.warn("xterm webgl: atlas page count ("+this._atlas.pages.length+") exceeds the renderer texture capacity ("+this._atlasTextures.length+"), some glyphs will not render correctly"));for(let t=0;tT.deleteProgram(this._program))),this._projectionLocation=(0,a.throwIfFalsy)(T.getUniformLocation(this._program,"u_projection"));const l=this._vertexArrayObject=T.createVertexArray();this._register((0,s.toDisposable)(()=>T.deleteVertexArray(l))),T.bindVertexArray(l);const d=new Float32Array([0,0,1,0,0,1,1,1]),c=T.createBuffer();this._register((0,s.toDisposable)(()=>T.deleteBuffer(c))),T.bindBuffer(T.ARRAY_BUFFER,c),T.bufferData(T.ARRAY_BUFFER,d,T.STATIC_DRAW),T.enableVertexAttribArray(3),T.vertexAttribPointer(3,2,this._gl.FLOAT,!1,0,0);const _=new Uint8Array([0,1,2,3]),Q=T.createBuffer();this._register((0,s.toDisposable)(()=>T.deleteBuffer(Q))),T.bindBuffer(T.ELEMENT_ARRAY_BUFFER,Q),T.bufferData(T.ELEMENT_ARRAY_BUFFER,_,T.STATIC_DRAW),this._attributesBuffer=(0,a.throwIfFalsy)(T.createBuffer()),this._register((0,s.toDisposable)(()=>T.deleteBuffer(this._attributesBuffer))),T.bindBuffer(T.ARRAY_BUFFER,this._attributesBuffer),T.enableVertexAttribArray(0),T.vertexAttribPointer(0,2,T.FLOAT,!1,o,0),T.vertexAttribDivisor(0,1),T.enableVertexAttribArray(1),T.vertexAttribPointer(1,2,T.FLOAT,!1,o,2*Float32Array.BYTES_PER_ELEMENT),T.vertexAttribDivisor(1,1),T.enableVertexAttribArray(2),T.vertexAttribPointer(2,4,T.FLOAT,!1,o,4*Float32Array.BYTES_PER_ELEMENT),T.vertexAttribDivisor(2,1),this._updateCachedColors(L.colors),this._register(this._themeService.onChangeColors(t=>{this._updateCachedColors(t),this._updateViewportRectangle()}))}renderBackgrounds(){this._renderVertices(this._vertices)}renderCursor(){this._renderVertices(this._verticesCursor)}_renderVertices(t){const e=this._gl;e.useProgram(this._program),e.bindVertexArray(this._vertexArrayObject),e.uniformMatrix4fv(this._projectionLocation,!1,r.PROJECTION_MATRIX),e.bindBuffer(e.ARRAY_BUFFER,this._attributesBuffer),e.bufferData(e.ARRAY_BUFFER,t.attributes,e.DYNAMIC_DRAW),e.drawElementsInstanced(this._gl.TRIANGLE_STRIP,4,e.UNSIGNED_BYTE,0,t.count)}handleResize(){this._updateViewportRectangle()}setDimensions(t){this._dimensions=t}_updateCachedColors(t){this._bgFloat=this._colorToFloat32Array(t.background),this._cursorFloat=this._colorToFloat32Array(t.cursor)}_updateViewportRectangle(){this._addRectangleFloat(this._vertices.attributes,0,0,0,this._terminal.cols*this._dimensions.device.cell.width,this._terminal.rows*this._dimensions.device.cell.height,this._bgFloat)}updateBackgrounds(t){const e=this._terminal,i=this._vertices;let s,r,a,o,n,L,h,T,l,d,c,_=1;for(s=0;s>24&255)/255,d=(L>>16&255)/255,c=(L>>8&255)/255,_=1,this._addRectangle(t.attributes,e,h,T,(o-a)*this._dimensions.device.cell.width,this._dimensions.device.cell.height,l,d,c,_)}_addRectangle(t,e,i,s,r,a,o,n,L,h){t[e]=i/this._dimensions.device.canvas.width,t[e+1]=s/this._dimensions.device.canvas.height,t[e+2]=r/this._dimensions.device.canvas.width,t[e+3]=a/this._dimensions.device.canvas.height,t[e+4]=o,t[e+5]=n,t[e+6]=L,t[e+7]=h}_addRectangleFloat(t,e,i,s,r,a,o){t[e]=i/this._dimensions.device.canvas.width,t[e+1]=s/this._dimensions.device.canvas.height,t[e+2]=r/this._dimensions.device.canvas.width,t[e+3]=a/this._dimensions.device.canvas.height,t[e+4]=o[0],t[e+5]=o[1],t[e+6]=o[2],t[e+7]=o[3]}_colorToFloat32Array(t){return new Float32Array([(t.rgba>>24&255)/255,(t.rgba>>16&255)/255,(t.rgba>>8&255)/255,(255&t.rgba)/255])}}e.RectangleRenderer=Q},208(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.RenderModel=e.COMBINED_CHAR_BIT_MASK=void 0;const s=i(948);e.COMBINED_CHAR_BIT_MASK=2147483648,e.RenderModel=class{constructor(){this.cells=new Uint32Array(0),this.lineLengths=new Uint32Array(0),this.selection=(0,s.createSelectionRenderModel)()}resize(t,e){const i=t*e*4;i!==this.cells.length&&(this.cells=new Uint32Array(i),this.lineLengths=new Uint32Array(e))}clear(){this.cells.fill(0,0),this.lineLengths.fill(0,0)}}},115(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.TextureAtlas=void 0;const s=i(0),r=i(183),a=i(959),o=i(993),n=i(836),L=i(930),h=i(917),T=i(564),l=i(414),d={texturePage:0,texturePosition:{x:0,y:0},texturePositionClipSpace:{x:0,y:0},offset:{x:0,y:0},size:{x:0,y:0},sizeClipSpace:{x:0,y:0}};let c;class _{get pages(){return this._pages}constructor(t,e,i,s){this._document=t,this._config=e,this._unicodeService=i,this._logService=s,this._didWarmUp=!1,this._cacheMap=new n.FourKeyMap,this._cacheMapCombined=new n.FourKeyMap,this._pages=[],this._activePages=[],this._workBoundingBox={top:0,left:0,bottom:0,right:0},this._workAttributeData=new h.AttributeData,this._textureSize=512,this._onAddTextureAtlasCanvas=new l.Emitter,this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event,this._onRemoveTextureAtlasCanvas=new l.Emitter,this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event,this._requestClearModel=!1,this._clearModelGeneration=0,this._createNewPage(),this._tmpCanvas=p(t,4*this._config.deviceCellWidth+4,this._config.deviceCellHeight+4),this._tmpCtx=(0,a.throwIfFalsy)(this._tmpCanvas.getContext("2d",{alpha:this._config.allowTransparency,willReadFrequently:!0}))}dispose(){this._tmpCanvas.remove();for(const t of this.pages)t.canvas.remove();this._onAddTextureAtlasCanvas.dispose(),this._onRemoveTextureAtlasCanvas.dispose()}warmUp(){this._didWarmUp||(this._doWarmUp(),this._didWarmUp=!0)}_doWarmUp(){const t=new L.IdleTaskQueue(this._logService);for(let e=33;e<126;e++)t.enqueue(()=>{if(!this._cacheMap.get(e,T.DEFAULT_COLOR,T.DEFAULT_COLOR,T.DEFAULT_EXT)){const t=this._drawToCache(e,T.DEFAULT_COLOR,T.DEFAULT_COLOR,T.DEFAULT_EXT,!1,void 0);this._cacheMap.set(e,T.DEFAULT_COLOR,T.DEFAULT_COLOR,T.DEFAULT_EXT,t)}})}beginFrame(){const t=this._requestClearModel;return this._requestClearModel=!1,t}clearTexture(){if(this._pages.some(t=>t._glyphs.length>0||0!==t.currentRow.x||0!==t.currentRow.y)){this._evictAllPages();this._createNewPage()}}_evictAllPages(){for(const t of this._pages)this._onRemoveTextureAtlasCanvas.fire(t.canvas),t.canvas.remove();this._pages.length=0,this._activePages.length=0,this._overflowSizePage=void 0,this._cacheMap.clear(),this._cacheMapCombined.clear(),this._didWarmUp=!1,this._requestClearModel=!0,this._clearModelGeneration++}_createNewPage(){if(_.maxAtlasPages&&this._pages.length>=_.maxAtlasPages){const t=this._pages.filter(t=>2*t.canvas.width<=(_.maxTextureSize||4096)).sort((t,e)=>e.canvas.width!==t.canvas.width?e.canvas.width-t.canvas.width:e.percentageUsed-t.percentageUsed);let e=-1,i=0;for(let s=0;st.canvas.width!==s[0].canvas.width))this._evictAllPages();else{const r=s.map(t=>t.glyphs[0].texturePage).sort((t,e)=>t-e),a=this.pages.length-s.length,o=this._mergePages(s,a);o.version=++Q.nextVersion;for(let t=r.length-1;t>=0;t--)this._deletePage(r[t]);this.pages.push(o),this._requestClearModel=!0,this._clearModelGeneration++,this._onAddTextureAtlasCanvas.fire(o.canvas)}}const t=new Q(this._document,this._textureSize);return this._pages.push(t),this._activePages.push(t),this._onAddTextureAtlasCanvas.fire(t.canvas),t}_mergePages(t,e){const i=2*t[0].canvas.width,s=new Q(this._document,i,t);for(const[r,a]of t.entries()){const t=r*a.canvas.width%i,o=Math.floor(r/2)*a.canvas.height;s.ctx.drawImage(a.canvas,t,o);for(const s of a.glyphs)s.texturePage=e,s.sizeClipSpace.x=s.size.x/i,s.sizeClipSpace.y=s.size.y/i,s.texturePosition.x+=t,s.texturePosition.y+=o,s.texturePositionClipSpace.x=s.texturePosition.x/i,s.texturePositionClipSpace.y=s.texturePosition.y/i;this._onRemoveTextureAtlasCanvas.fire(a.canvas);const n=this._activePages.indexOf(a);-1!==n&&this._activePages.splice(n,1)}return s}_deletePage(t){this._pages.splice(t,1);for(let e=t;e=this._config.colors.ansi.length)throw new Error("No color found for idx "+t);return this._config.colors.ansi[t]}_getBackgroundColor(t,e,i,s){if(this._config.allowTransparency)return o.NULL_COLOR;let r;switch(t){case 16777216:case 33554432:r=this._getColorFromAnsiIndex(e);break;case 50331648:const t=h.AttributeData.toColorRGB(e);r=o.channels.toColor(t[0],t[1],t[2]);break;default:r=i?o.color.opaque(this._config.colors.foreground):this._config.colors.background}return this._config.allowTransparency||(r=o.color.opaque(r)),r}_getForegroundColor(t,e,i,r,a,n,L,T,l,d){const c=this._getMinimumContrastColor(t,e,i,r,a,n,L,l,T,d);if(c)return c;let _;switch(a){case 16777216:case 33554432:this._config.drawBoldTextInBrightColors&&l&&n<8&&(n+=8),_=this._getColorFromAnsiIndex(n);break;case 50331648:const t=h.AttributeData.toColorRGB(n);_=o.channels.toColor(t[0],t[1],t[2]);break;default:_=L?this._config.colors.background:this._config.colors.foreground}return this._config.allowTransparency&&(_=o.color.opaque(_)),T&&(_=o.color.multiplyOpacity(_,s.DIM_OPACITY)),_}_resolveBackgroundRgba(t,e,i){switch(t){case 16777216:case 33554432:return this._getColorFromAnsiIndex(e).rgba;case 50331648:return e<<8;default:return i?this._config.colors.foreground.rgba:this._config.colors.background.rgba}}_resolveForegroundRgba(t,e,i,s){switch(t){case 16777216:case 33554432:return this._config.drawBoldTextInBrightColors&&s&&e<8&&(e+=8),this._getColorFromAnsiIndex(e).rgba;case 50331648:return e<<8;default:return i?this._config.colors.background.rgba:this._config.colors.foreground.rgba}}_getMinimumContrastColor(t,e,i,s,r,a,n,L,h,T){if(1===this._config.minimumContrastRatio||T)return;const l=this._getContrastCache(h),d=l.getColor(t,s);if(void 0!==d)return d??void 0;const c=this._resolveBackgroundRgba(e,i,n),_=this._resolveForegroundRgba(r,a,n,L),Q=o.rgba.ensureContrastRatio(c,_,this._config.minimumContrastRatio/(h?2:1));if(!Q)return void l.setColor(t,s,null);const u=o.channels.toColor(Q>>24&255,Q>>16&255,Q>>8&255);return l.setColor(t,s,u),u}_getContrastCache(t){return t?this._config.colors.halfContrastCache:this._config.colors.contrastCache}_drawToCache(t,e,i,o,n,L){const T="number"==typeof t?String.fromCharCode(t):t;L&&this._tmpCanvas.parentElement!==L&&(this._tmpCanvas.style.display="none",L.append(this._tmpCanvas));const l=Math.min(this._config.deviceCellWidth*Math.max(T.length,2)+4,this._config.deviceMaxTextureSize);this._tmpCanvas.width=t?2*t-o:t-o;!1==!(o>=t)||0===_?(this._tmpCtx.setLineDash([Math.round(t),Math.round(t)]),this._tmpCtx.moveTo(h+_,s),this._tmpCtx.lineTo(T,s)):(this._tmpCtx.setLineDash([Math.round(t),Math.round(t)]),this._tmpCtx.moveTo(h,s),this._tmpCtx.lineTo(h+_,s),this._tmpCtx.moveTo(h+_+t,s),this._tmpCtx.lineTo(T,s)),o=(0,a.computeNextVariantOffset)(T-h,t,o);break;case 5:const Q=.6,u=.3,p=T-h,g=Math.floor(Q*p),M=Math.floor(u*p),y=p-g-M;this._tmpCtx.setLineDash([g,M,y]),this._tmpCtx.moveTo(h,s),this._tmpCtx.lineTo(T,s);break;default:this._tmpCtx.moveTo(h,s),this._tmpCtx.lineTo(T,s)}n||this._tmpCtx.stroke(),this._tmpCtx.restore()}if(this._tmpCtx.restore(),!O&&this._config.fontSize>=12&&!this._config.allowTransparency&&" "!==T){this._tmpCtx.save(),this._tmpCtx.textBaseline="alphabetic";const e=this._tmpCtx.measureText(T);if(this._tmpCtx.restore(),"actualBoundingBoxDescent"in e&&e.actualBoundingBoxDescent>0){this._tmpCtx.save();const e=new Path2D;e.rect(i,L-Math.ceil(t/2),this._config.deviceCellWidth*I,l-L+Math.ceil(t/2)),this._tmpCtx.clip(e),this._tmpCtx.lineWidth=3*this._config.devicePixelRatio,this._tmpCtx.strokeStyle=E.css,this._tmpCtx.strokeText(T,S,S+this._config.deviceCharHeight),this._tmpCtx.restore()}}}if(C){const t=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/15)),e=t%2==1?.5:0;this._tmpCtx.lineWidth=t,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(S,S+e),this._tmpCtx.lineTo(S+this._config.deviceCharWidth*I,S+e),this._tmpCtx.stroke()}if(O||this._tmpCtx.fillText(T,S,S+this._config.deviceCharHeight),"_"===T&&!this._config.allowTransparency){let t=u(this._tmpCtx.getImageData(S,S,this._config.deviceCellWidth,this._config.deviceCellHeight),E,D,W);if(t)for(let e=1;e<=5&&(this._tmpCtx.save(),this._tmpCtx.fillStyle=E.css,this._tmpCtx.fillRect(0,0,this._tmpCanvas.width,this._tmpCanvas.height),this._tmpCtx.restore(),this._tmpCtx.fillText(T,S,S+this._config.deviceCharHeight-e),t=u(this._tmpCtx.getImageData(S,S,this._config.deviceCellWidth,this._config.deviceCellHeight),E,D,W),t);e++);}if(f){const t=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/10)),e=this._tmpCtx.lineWidth%2==1?.5:0;this._tmpCtx.lineWidth=t,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(S,S+Math.floor(this._config.deviceCharHeight/2)-e),this._tmpCtx.lineTo(S+this._config.deviceCharWidth*I,S+Math.floor(this._config.deviceCharHeight/2)-e),this._tmpCtx.stroke()}this._tmpCtx.restore();const N=this._tmpCtx.getImageData(0,0,this._tmpCanvas.width,this._tmpCanvas.height);let P;if(P=this._config.allowTransparency?function(t){for(let e=0;e0)return!1;return!0}(N):u(N,E,D,W),P)return d;const B=this._findGlyphBoundingBox(N,this._workBoundingBox,l,Z,O,S);let $,F;for(;;){if(0===this._activePages.length){const t=this._createNewPage();$=t,F=t.currentRow,F.height=B.size.y;break}$=this._activePages[this._activePages.length-1],F=$.currentRow;for(const t of this._activePages)B.size.y<=t.currentRow.height&&($=t,F=t.currentRow);for(let t=this._activePages.length-1;t>=0;t--)for(const e of this._activePages[t].fixedRows)e.height<=F.height&&B.size.y<=e.height&&($=this._activePages[t],F=e);if(B.size.x>this._textureSize){this._overflowSizePage||(_.maxAtlasPages&&this._pages.length>=_.maxAtlasPages&&this._evictAllPages(),this._overflowSizePage=new Q(this._document,this._config.deviceMaxTextureSize),this.pages.push(this._overflowSizePage),this._requestClearModel=!0,this._clearModelGeneration++,this._onAddTextureAtlasCanvas.fire(this._overflowSizePage.canvas)),$=this._overflowSizePage,F=this._overflowSizePage.currentRow,F.x+B.size.x>=$.canvas.width&&(F.x=0,F.y+=F.height,F.height=0);break}if(F.y+B.size.y>=$.canvas.height||F.height>B.size.y+2){let t=!1;if($.currentRow.y+$.currentRow.height+B.size.y>=$.canvas.height){let e;for(const t of this._activePages)if(t.currentRow.y+t.currentRow.height+B.size.y=_.maxAtlasPages&&F.y+B.size.y<=$.canvas.height&&F.height>=B.size.y&&F.x+B.size.x<=$.canvas.width)t=!0;else{const e=this._createNewPage();$=e,F=e.currentRow,F.height=B.size.y,t=!0}}t||($.currentRow.height>0&&$.fixedRows.push($.currentRow),F={x:0,y:$.currentRow.y+$.currentRow.height,height:B.size.y},$.fixedRows.push(F),$.currentRow={x:0,y:F.y+F.height,height:0})}if(F.x+B.size.x<=$.canvas.width)break;F===$.currentRow?(F.x=0,F.y+=F.height,F.height=0):$.fixedRows.splice($.fixedRows.indexOf(F),1)}return B.texturePage=this._pages.indexOf($),B.texturePosition.x=F.x,B.texturePosition.y=F.y,B.texturePositionClipSpace.x=F.x/$.canvas.width,B.texturePositionClipSpace.y=F.y/$.canvas.height,B.sizeClipSpace.x/=$.canvas.width,B.sizeClipSpace.y/=$.canvas.height,F.height=Math.max(F.height,B.size.y),F.x+=B.size.x,$.ctx.putImageData(N,B.texturePosition.x-this._workBoundingBox.left,B.texturePosition.y-this._workBoundingBox.top,this._workBoundingBox.left,this._workBoundingBox.top,B.size.x,B.size.y),$.addGlyph(B),$.version=++Q.nextVersion,B}_findGlyphBoundingBox(t,e,i,s,r,a){e.top=0;const o=s?this._config.deviceCellHeight:this._tmpCanvas.height,n=s?this._config.deviceCellWidth:i;let L=!1;for(let i=0;i=a;i--){for(let s=0;s=0;i--){for(let s=0;s>>24,a=e.rgba>>>16&255,o=e.rgba>>>8&255,n=i.rgba>>>24,L=i.rgba>>>16&255,h=i.rgba>>>8&255,T=Math.floor((Math.abs(r-n)+Math.abs(a-L)+Math.abs(o-h))/12);let l=!0;for(let e=0;ethis._handleColorChange())),this._cellColorResolver=new s.CellColorResolver(this._terminal,this._optionsService,this._model.selection,this._decorationService,this._coreBrowserService,this._themeService),this._core=this._terminal._core,this._renderLayers=[new _.LinkRenderLayer(this._core.screenElement,2,this._terminal,this._core.linkifier,this._coreBrowserService,d,this._themeService)],this.dimensions=(0,g.createRenderDimensions)(),this._devicePixelRatio=this._coreBrowserService.dpr,this._updateDimensions(),this._updateCursorBlink(),this._register(d.onOptionChange(()=>this._handleOptionsChanged())),this._textBlinkStateManager=this._register(new T.TextBlinkStateManager(()=>this._requestRedrawViewport(),this._coreBrowserService,this._optionsService)),this._resetBlinkingRowState(),this._deviceMaxTextureSize=this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE),this._register((0,u.addDisposableListener)(this._canvas,"webglcontextlost",t=>{this._logService.debug("webglcontextlost event received"),t.preventDefault(),this._contextRestorationTimeout=setTimeout(()=>{this._contextRestorationTimeout=void 0,this._logService.warn("webgl context not restored; firing onContextLoss"),this._onContextLoss.fire(t)},3e3)})),this._register((0,u.addDisposableListener)(this._canvas,"webglcontextrestored",t=>{this._logService.warn("webglcontextrestored event received"),clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0,(0,r.removeTerminalFromCache)(this._terminal),this._initializeWebGLState(),this._requestRedrawViewport()})),this._observerDisposable.value=(0,o.observeDevicePixelDimensions)(this._canvas,this._coreBrowserService.window,(t,e)=>this._setCanvasDevicePixelDimensions(t,e)),this._register(this._coreBrowserService.onWindowChange(t=>{this._observerDisposable.value=(0,o.observeDevicePixelDimensions)(this._canvas,t,(t,e)=>this._setCanvasDevicePixelDimensions(t,e))})),this._register((0,u.addDisposableListener)(this._coreBrowserService.mainDocument,"mousedown",()=>this._cursorBlinkStateManager.value?.restartBlinkAnimation())),this._core.screenElement.appendChild(this._canvas),[this._rectangleRenderer.value,this._glyphRenderer.value]=this._initializeWebGLState(),this._isAttached=this._core.screenElement.isConnected,this._register((0,p.toDisposable)(()=>{clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0;for(const t of this._renderLayers)t.dispose();this._canvas.parentElement?.removeChild(this._canvas),(0,r.removeTerminalFromCache)(this._terminal)}))}get textureAtlas(){return this._charAtlas?.pages[0].canvas}_handleColorChange(){this._refreshCharAtlas(),this._clearModel(!0)}handleDevicePixelRatioChange(){this._devicePixelRatio!==this._coreBrowserService.dpr&&(this._devicePixelRatio=this._coreBrowserService.dpr,this.handleResize(this._terminal.cols,this._terminal.rows))}handleResize(t,e){this._updateDimensions(),this._model.resize(this._terminal.cols,this._terminal.rows),this._resetBlinkingRowState();for(const t of this._renderLayers)t.resize(this._terminal,this.dimensions);this._canvas.width=this.dimensions.device.canvas.width,this._canvas.height=this.dimensions.device.canvas.height,this._canvas.style.width=`${this.dimensions.css.canvas.width}px`,this._canvas.style.height=`${this.dimensions.css.canvas.height}px`,this._core.screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._core.screenElement.style.height=`${this.dimensions.css.canvas.height}px`,this._rectangleRenderer.value?.setDimensions(this.dimensions),this._rectangleRenderer.value?.handleResize(),this._glyphRenderer.value?.setDimensions(this.dimensions),this._glyphRenderer.value?.handleResize(),this._refreshCharAtlas(),this._clearModel(!1),this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1,sync:!0})}handleCharSizeChanged(){this.handleResize(this._terminal.cols,this._terminal.rows)}handleBlur(){for(const t of this._renderLayers)t.handleBlur(this._terminal);this._cursorBlinkStateManager.value?.pause(),this._requestRedrawViewport()}handleFocus(){for(const t of this._renderLayers)t.handleFocus(this._terminal);this._cursorBlinkStateManager.value?.resume(),this._requestRedrawViewport()}handleViewportVisibilityChange(t){this._textBlinkStateManager.setViewportVisible(t)}handleSelectionChanged(t,e,i){for(const s of this._renderLayers)s.handleSelectionChanged(this._terminal,t,e,i);this._model.selection.update(this._core,t,e,i),this._requestRedrawViewport()}handleCursorMove(){for(const t of this._renderLayers)t.handleCursorMove(this._terminal);this._cursorBlinkStateManager.value?.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._refreshCharAtlas(),this._updateCursorBlink()}_initializeWebGLState(){return this._rectangleRenderer.value=new d.RectangleRenderer(this._terminal,this._gl,this.dimensions,this._themeService,this._logService),this._glyphRenderer.value=new l.GlyphRenderer(this._terminal,this._gl,this.dimensions,this._optionsService,this._logService),this.handleCharSizeChanged(),[this._rectangleRenderer.value,this._glyphRenderer.value]}_refreshCharAtlas(){if(this.dimensions.device.char.width<=0&&this.dimensions.device.char.height<=0)return void(this._isAttached=!1);const t=(0,r.acquireTextureAtlas)(this._terminal,this._optionsService.rawOptions,this._themeService.colors,this.dimensions.device.cell.width,this.dimensions.device.cell.height,this.dimensions.device.char.width,this.dimensions.device.char.height,this._coreBrowserService.dpr,this._deviceMaxTextureSize,this._customGlyphs);this._charAtlas!==t&&(this._onChangeTextureAtlas.fire(t.pages[0].canvas),this._charAtlasDisposable.value=(0,p.combinedDisposable)(Q.EventUtils.forward(t.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas),Q.EventUtils.forward(t.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas))),this._charAtlas=t,this._charAtlas.warmUp(),this._glyphRenderer.value?.setAtlas(this._charAtlas)}_clearModel(t){this._model.clear(),t&&this._glyphRenderer.value?.clear()}clearTextureAtlas(){this._charAtlas?.clearTexture(),this._clearModel(!0),this._requestRedrawViewport()}clear(){this._clearModel(!0);for(const t of this._renderLayers)t.reset(this._terminal);this._resetBlinkingRowState(),this._textBlinkStateManager.setNeedsBlinkInViewport(!1),this._cursorBlinkStateManager.value?.restartBlinkAnimation(),this._updateCursorBlink()}renderRows(t,e){if(!this._isAttached){if(!(this._core.screenElement?.isConnected&&this._charSizeService.width&&this._charSizeService.height))return;this._updateDimensions(),this._refreshCharAtlas(),this._isAttached=!0}for(const i of this._renderLayers)i.handleGridChanged(this._terminal,t,e);if(!this._glyphRenderer.value||!this._rectangleRenderer.value)return;this._glyphRenderer.value.beginFrame()?(this._clearModel(!0),this._updateModel(0,this._terminal.rows-1)):this._updateModel(t,e);let i=!1,s=0;for(;this._charAtlas&&s++<32&&this._glyphRenderer.value.beginFrame();)i=!0,this._clearModel(!0),this._updateModel(0,this._terminal.rows-1);i&&this._glyphRenderer.value.invalidateAtlasTextures(),this._rectangleRenderer.value.renderBackgrounds(),this._glyphRenderer.value.render(this._model),this._cursorBlinkStateManager.value&&!this._cursorBlinkStateManager.value.isCursorVisible||this._rectangleRenderer.value.renderCursor()}_updateCursorBlink(){this._coreService.decPrivateModes.cursorBlink??this._terminal.options.cursorBlink?this._cursorBlinkStateManager.value=new a.CursorBlinkStateManager(()=>{this._requestRedrawCursor()},this._coreBrowserService):this._cursorBlinkStateManager.clear(),this._requestRedrawCursor()}_updateModel(t,e){const i=this._core;let s,r,a,o,n,L,T,l,d,_,Q,u,p,g,M,f,C,m=this._workCell;t=v(t,i.rows-1,0),e=v(e,i.rows-1,0);const w=this._coreService.decPrivateModes.cursorStyle??i.options.cursorStyle??"block",x=this._terminal.buffer.active.baseY+this._terminal.buffer.active.cursorY,b=x-i.buffer.ydisp,E=Math.min(this._terminal.buffer.active.cursorX,i.cols-1);let R=-1;const k=this._coreService.isCursorInitialized&&!this._coreService.isCursorHidden&&(!this._cursorBlinkStateManager.value||this._cursorBlinkStateManager.value.isCursorVisible);this._model.cursor=void 0;let A=!1;for(r=t;r<=e;r++){a=r+i.buffer.ydisp;const t=i.buffer.lines.get(a);if(!t){for(this._model.lineLengths[r]=0,f=0;f=T,d=f,n.length>0&&f===n[0][0]&&l){_=n.shift();const t=this._model.selection.isCellSelected(this._terminal,_[0],a);for(M=_[0]+1;M<_[1];M++)l&&=t===this._model.selection.isCellSelected(this._terminal,M,a);l&&=!Q||E<_[0]||E>=_[1],l?(L=!0,m=new y(m,o.translateToString(!0,_[0],_[1]),_[1]-_[0]),d=_[1]-1):T=_[1]}if(u=m.getChars(),p=m.getCode(),M=4*(r*i.cols+f),!e&&m.isBlink()&&(e=!0),this._cellColorResolver.resolve(m,f,a,this.dimensions.device.cell.width,this.dimensions.device.cell.height),k&&a===x&&(f===E&&(this._model.cursor={x:E,y:b,width:m.getWidth(),style:this._coreBrowserService.isFocused?w:i.options.cursorInactiveStyle,cursorWidth:i.options.cursorWidth,dpr:this._devicePixelRatio},R=E+m.getWidth()-1),f>=E&&f<=R&&(this._coreBrowserService.isFocused&&"block"===w||!1===this._coreBrowserService.isFocused&&"block"===i.options.cursorInactiveStyle)&&(this._cellColorResolver.result.fg=50331648|this._themeService.colors.cursorAccent.rgba>>8&16777215,this._cellColorResolver.result.bg=50331648|this._themeService.colors.cursor.rgba>>8&16777215)),this._textBlinkStateManager.isEnabled&&!this._textBlinkStateManager.isBlinkOn&&m.isBlink()&&(this._cellColorResolver.result.fg|=1073741824),p!==h.NULL_CELL_CODE&&(this._model.lineLengths[r]=f+1),(this._model.cells[M]!==p||this._model.cells[M+1]!==this._cellColorResolver.result.bg||this._model.cells[M+2]!==this._cellColorResolver.result.fg||this._model.cells[M+3]!==this._cellColorResolver.result.ext)&&(A=!0,u.length>1&&(p|=c.COMBINED_CHAR_BIT_MASK),this._model.cells[M]=p,this._model.cells[M+1]=this._cellColorResolver.result.bg,this._model.cells[M+2]=this._cellColorResolver.result.fg,this._model.cells[M+3]=this._cellColorResolver.result.ext,g=m.getWidth(),this._glyphRenderer.value.updateCell(f,r,p,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext,u,g,s),L)){for(m=this._workCell,f++;f<=d;f++)C=4*(r*i.cols+f),this._nullModelCell(f,r,C,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext);f--}}this._setRowBlinkState(r,e)}A&&this._rectangleRenderer.value.updateBackgrounds(this._model),this._rectangleRenderer.value.updateCursor(this._model),this._updateTextBlinkState()}_nullModelCell(t,e,i,s,r,a){this._glyphRenderer.value.updateCell(t,e,h.NULL_CELL_CODE,s,r,a,h.NULL_CELL_CHAR,0,0),this._model.cells[i]=h.NULL_CELL_CODE,this._model.cells[i+1]=s,this._model.cells[i+2]=r,this._model.cells[i+3]=a}_resetBlinkingRowState(){this._rowHasBlinkingCells=new Array(this._terminal.rows).fill(!1),this._rowHasBlinkingCellsCount=0}_setRowBlinkState(t,e){this._rowHasBlinkingCells[t]!==e&&(this._rowHasBlinkingCells[t]=e,this._rowHasBlinkingCellsCount+=e?1:-1)}_updateTextBlinkState(){this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount>0)}_updateDimensions(){this._charSizeService.width&&this._charSizeService.height&&(this.dimensions.device.char.width=Math.floor(this._charSizeService.width*this._devicePixelRatio),this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*this._devicePixelRatio),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.top=1===this._optionsService.rawOptions.lineHeight?0:Math.round((this.dimensions.device.cell.height-this.dimensions.device.char.height)/2),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.char.left=Math.floor(this._optionsService.rawOptions.letterSpacing/2),this.dimensions.device.canvas.height=this._terminal.rows*this.dimensions.device.cell.height,this.dimensions.device.canvas.width=this._terminal.cols*this.dimensions.device.cell.width,this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/this._devicePixelRatio),this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/this._devicePixelRatio),this.dimensions.css.cell.height=this.dimensions.device.cell.height/this._devicePixelRatio,this.dimensions.css.cell.width=this.dimensions.device.cell.width/this._devicePixelRatio)}_setCanvasDevicePixelDimensions(t,e){this._canvas.width===t&&this._canvas.height===e||(this._canvas.width=t,this._canvas.height=e,this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1,sync:!0}))}_requestRedrawViewport(){this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1})}_requestRedrawCursor(){const t=this._terminal.buffer.active.cursorY;this._onRequestRedraw.fire({start:t,end:t})}}e.WebglRenderer=M;class y extends n.AttributeData{constructor(t,e,i){super(),this.content=0,this.combinedData="",this.fg=t.fg,this.bg=t.bg,this.combinedData=e,this._width=i}isCombined(){return 2097152}getWidth(){return this._width}getChars(){return this.combinedData}getCode(){return 2097151}setFromCharData(t){throw new Error("not implemented")}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}}function v(t,e,i=0){return Math.max(Math.min(t,e),i)}e.JoinedCellData=y},719(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.GLTexture=e.PROJECTION_MATRIX=void 0,e.createProgram=function(t,e,i,a){const o=(0,s.throwIfFalsy)(t.createProgram());if(t.attachShader(o,(0,s.throwIfFalsy)(r(t,t.VERTEX_SHADER,e,a))),t.attachShader(o,(0,s.throwIfFalsy)(r(t,t.FRAGMENT_SHADER,i,a))),t.linkProgram(o),t.getProgramParameter(o,t.LINK_STATUS))return o;a.error(t.getProgramInfoLog(o)),t.deleteProgram(o)},e.createShader=r,e.expandFloat32Array=function(t,e){const i=Math.min(2*t.length,e),s=new Float32Array(i);for(let e=0;e{const e=[],i=[3/8,2/8,3/8],s=[0,3/8,5/8];for(let r=0;r<3;r++){const a=t>>2*r&1,o=t>>2*r+1&1;a&&o?e.push(`M0,${s[r]} L1,${s[r]} L1,${s[r]+i[r]} L0,${s[r]+i[r]} Z`):a?e.push(`M0,${s[r]} L0.5,${s[r]} L0.5,${s[r]+i[r]} L0,${s[r]+i[r]} Z`):o&&e.push(`M0.5,${s[r]} L1,${s[r]} L1,${s[r]+i[r]} L0.5,${s[r]+i[r]} Z`)}return e.join(" ")}}}function r(t){const e=[],i=.15,s=.015,r=.075,a=.0375,o=.95;if(64&t){const t=.175,a=.2125,n=.25,L=.2+s,h=o-i-s;e.push(`M${L},${a} L${L+r},${t} L${h-r},${t} L${h},${a} L${h-r},${n} L${L+r},${n} Z`)}if(32&t){const t=o-i,s=.875,r=o,n=.265,L=.485;e.push(`M${s},${n} L${r},${n+a} L${r},${L-a} L${s},${L} L${t},${L-a} L${t},${n+a} Z`)}if(16&t){const t=o-i,s=.875,r=o,n=.515,L=.735;e.push(`M${s},${n} L${r},${n+a} L${r},${L-a} L${s},${L} L${t},${L-a} L${t},${n+a} Z`)}if(8&t){const t=.75,a=.7875,n=.825,L=.2+s,h=o-i-s;e.push(`M${L},${a} L${L+r},${t} L${h-r},${t} L${h},${a} L${h-r},${n} L${L+r},${n} Z`)}if(4&t){const t=.05,i=.125,s=.2,r=.515,o=.735;e.push(`M${i},${r} L${s},${r+a} L${s},${o-a} L${i},${o} L${t},${o-a} L${t},${r+a} Z`)}if(2&t){const t=.05,i=.125,s=.2,r=.265,o=.485;e.push(`M${i},${r} L${s},${r+a} L${s},${o-a} L${i},${o} L${t},${o-a} L${t},${r+a} Z`)}if(1&t){const t=.4625,a=.5,n=.5375,L=.2+s,h=o-i-s;e.push(`M${L},${a} L${L+r},${t} L${h-r},${t} L${h},${a} L${h-r},${n} L${L+r},${n} Z`)}return e.join(" ")}Object.defineProperty(e,"__esModule",{value:!0}),e.blockPatternCodepoints=e.customGlyphDefinitions=void 0,function(t){t.LINE_H=Object.freeze({type:2,data:"M0,.5 L1,.5",strokeWidth:1}),t.LINE_V=Object.freeze({type:2,data:"M.5,0 L.5,1",strokeWidth:1}),t.FADE_RIGHT=Object.freeze({type:2,data:"M0,.5 L.28,.5 M.32,.5 L.52,.5 M.60,.5 L.72,.5 M.84,.5 L.90,.5",strokeWidth:1}),t.FADE_LEFT=Object.freeze({type:2,data:"M.10,.5 L.16,.5 M.28,.5 L.40,.5 M.48,.5 L.68,.5 M.72,.5 L1,.5",strokeWidth:1}),t.FADE_DOWN=Object.freeze({type:2,data:"M.5,0 L.5,.28 M.5,.32 L.5,.52 M.5,.60 L.5,.72 M.5,.84 L.5,.90",strokeWidth:1}),t.FADE_UP=Object.freeze({type:2,data:"M.5,.10 L.5,.16 M.5,.28 L.5,.40 M.5,.48 L.5,.68 M.5,.72 L.5,1",strokeWidth:1}),t.CURVE_DOWN_RIGHT=Object.freeze({type:2,data:(t,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,1,.5`,strokeWidth:1}),t.CURVE_DOWN_LEFT=Object.freeze({type:2,data:(t,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,0,.5`,strokeWidth:1}),t.CURVE_UP_RIGHT=Object.freeze({type:2,data:(t,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,1,.5`,strokeWidth:1}),t.CURVE_UP_LEFT=Object.freeze({type:2,data:(t,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,0,.5`,strokeWidth:1}),t.NODE_FILL=Object.freeze({type:3,data:"M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5"}),t.NODE_STROKE=Object.freeze({type:3,data:"M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5",strokeWidth:1}),t.NODE_LINE_RIGHT=Object.freeze({type:3,data:"M1,.5 L.85,.5",strokeWidth:1}),t.NODE_LINE_LEFT=Object.freeze({type:3,data:"M0,.5 L.15,.5",strokeWidth:1}),t.NODE_LINE_DOWN=Object.freeze({type:3,data:"M.5,1 L.5,.7",strokeWidth:1}),t.NODE_LINE_UP=Object.freeze({type:3,data:"M.5,0 L.5,.3",strokeWidth:1})}(i||(i={})),e.customGlyphDefinitions={"─":{type:2,data:"M0,.5 L1,.5",strokeWidth:1},"━":{type:2,data:"M0,.5 L1,.5",strokeWidth:3},"│":{type:2,data:"M.5,0 L.5,1",strokeWidth:1},"┃":{type:2,data:"M.5,0 L.5,1",strokeWidth:3},"┄":{type:2,data:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",strokeWidth:1},"┅":{type:2,data:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",strokeWidth:3},"┆":{type:2,data:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",strokeWidth:1},"┇":{type:2,data:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",strokeWidth:3},"┈":{type:2,data:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",strokeWidth:1},"┉":{type:2,data:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",strokeWidth:3},"┊":{type:2,data:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",strokeWidth:1},"┋":{type:2,data:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",strokeWidth:3},"┌":{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},"┍":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"┎":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"┏":{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3},"┐":{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},"┑":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"┒":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"┓":{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3},"└":{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},"┕":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"┖":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"┗":{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3},"┘":{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},"┙":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"┚":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"┛":{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3},"├":{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:1},"┝":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"┞":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"┟":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"┠":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"┡":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"┢":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"┣":{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:3},"┤":{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:1},"┥":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"┦":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"┧":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"┨":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"┩":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"┪":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"┫":{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:3},"┬":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:1},"┭":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"┮":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"┯":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"┰":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"┱":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"┲":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"┳":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:3},"┴":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,0",strokeWidth:1},"┵":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"┶":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"┷":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"┸":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"┹":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"┺":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"┻":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,0",strokeWidth:3},"┼":{type:2,data:"M0,.5 L1,.5 M.5,0 L.5,1",strokeWidth:1},"┽":[{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"┾":[{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"┿":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"╀":[{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"╁":[{type:2,data:"M.5,.5 L.5,0 M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"╂":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"╃":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"╄":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"╅":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"╆":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"╇":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0 M0,.5 L1,.5",strokeWidth:3}],"╈":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:3}],"╉":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:3}],"╊":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:3}],"╋":{type:2,data:"M0,.5 L1,.5 M.5,0 L.5,1",strokeWidth:3},"╌":{type:2,data:"M.1,.5 L.4,.5 M.6,.5 L.9,.5",strokeWidth:1},"╍":{type:2,data:"M.1,.5 L.4,.5 M.6,.5 L.9,.5",strokeWidth:3},"╎":{type:2,data:"M.5,.1 L.5,.4 M.5,.6 L.5,.9",strokeWidth:1},"╏":{type:2,data:"M.5,.1 L.5,.4 M.5,.6 L.5,.9",strokeWidth:3},"═":{type:2,data:(t,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"║":{type:2,data:(t,e)=>`M${.5-t},0 L${.5-t},1 M${.5+t},0 L${.5+t},1`,strokeWidth:1},"╒":{type:2,data:(t,e)=>`M.5,1 L.5,${.5-e} L1,${.5-e} M.5,${.5+e} L1,${.5+e}`,strokeWidth:1},"╓":{type:2,data:(t,e)=>`M${.5-t},1 L${.5-t},.5 L1,.5 M${.5+t},.5 L${.5+t},1`,strokeWidth:1},"╔":{type:2,data:(t,e)=>`M1,${.5-e} L${.5-t},${.5-e} L${.5-t},1 M1,${.5+e} L${.5+t},${.5+e} L${.5+t},1`,strokeWidth:1},"╕":{type:2,data:(t,e)=>`M0,${.5-e} L.5,${.5-e} L.5,1 M0,${.5+e} L.5,${.5+e}`,strokeWidth:1},"╖":{type:2,data:(t,e)=>`M${.5+t},1 L${.5+t},.5 L0,.5 M${.5-t},.5 L${.5-t},1`,strokeWidth:1},"╗":{type:2,data:(t,e)=>`M0,${.5+e} L${.5-t},${.5+e} L${.5-t},1 M0,${.5-e} L${.5+t},${.5-e} L${.5+t},1`,strokeWidth:1},"╘":{type:2,data:(t,e)=>`M.5,0 L.5,${.5+e} L1,${.5+e} M.5,${.5-e} L1,${.5-e}`,strokeWidth:1},"╙":{type:2,data:(t,e)=>`M1,.5 L${.5-t},.5 L${.5-t},0 M${.5+t},.5 L${.5+t},0`,strokeWidth:1},"╚":{type:2,data:(t,e)=>`M1,${.5-e} L${.5+t},${.5-e} L${.5+t},0 M1,${.5+e} L${.5-t},${.5+e} L${.5-t},0`,strokeWidth:1},"╛":{type:2,data:(t,e)=>`M0,${.5+e} L.5,${.5+e} L.5,0 M0,${.5-e} L.5,${.5-e}`,strokeWidth:1},"╜":{type:2,data:(t,e)=>`M0,.5 L${.5+t},.5 L${.5+t},0 M${.5-t},.5 L${.5-t},0`,strokeWidth:1},"╝":{type:2,data:(t,e)=>`M0,${.5-e} L${.5-t},${.5-e} L${.5-t},0 M0,${.5+e} L${.5+t},${.5+e} L${.5+t},0`,strokeWidth:1},"╞":{type:2,data:(t,e)=>`M.5,0 L.5,1 M.5,${.5-e} L1,${.5-e} M.5,${.5+e} L1,${.5+e}`,strokeWidth:1},"╟":{type:2,data:(t,e)=>`M${.5-t},0 L${.5-t},1 M${.5+t},0 L${.5+t},1 M${.5+t},.5 L1,.5`,strokeWidth:1},"╠":{type:2,data:(t,e)=>`M${.5-t},0 L${.5-t},1 M1,${.5+e} L${.5+t},${.5+e} L${.5+t},1 M1,${.5-e} L${.5+t},${.5-e} L${.5+t},0`,strokeWidth:1},"╡":{type:2,data:(t,e)=>`M.5,0 L.5,1 M0,${.5-e} L.5,${.5-e} M0,${.5+e} L.5,${.5+e}`,strokeWidth:1},"╢":{type:2,data:(t,e)=>`M0,.5 L${.5-t},.5 M${.5-t},0 L${.5-t},1 M${.5+t},0 L${.5+t},1`,strokeWidth:1},"╣":{type:2,data:(t,e)=>`M${.5+t},0 L${.5+t},1 M0,${.5+e} L${.5-t},${.5+e} L${.5-t},1 M0,${.5-e} L${.5-t},${.5-e} L${.5-t},0`,strokeWidth:1},"╤":{type:2,data:(t,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e} M.5,${.5+e} L.5,1`,strokeWidth:1},"╥":{type:2,data:(t,e)=>`M0,.5 L1,.5 M${.5-t},.5 L${.5-t},1 M${.5+t},.5 L${.5+t},1`,strokeWidth:1},"╦":{type:2,data:(t,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L${.5-t},${.5+e} L${.5-t},1 M1,${.5+e} L${.5+t},${.5+e} L${.5+t},1`,strokeWidth:1},"╧":{type:2,data:(t,e)=>`M.5,0 L.5,${.5-e} M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"╨":{type:2,data:(t,e)=>`M0,.5 L1,.5 M${.5-t},.5 L${.5-t},0 M${.5+t},.5 L${.5+t},0`,strokeWidth:1},"╩":{type:2,data:(t,e)=>`M0,${.5+e} L1,${.5+e} M0,${.5-e} L${.5-t},${.5-e} L${.5-t},0 M1,${.5-e} L${.5+t},${.5-e} L${.5+t},0`,strokeWidth:1},"╪":{type:2,data:(t,e)=>`M.5,0 L.5,1 M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"╫":{type:2,data:(t,e)=>`M0,.5 L1,.5 M${.5-t},0 L${.5-t},1 M${.5+t},0 L${.5+t},1`,strokeWidth:1},"╬":{type:2,data:(t,e)=>`M0,${.5+e} L${.5-t},${.5+e} L${.5-t},1 M1,${.5+e} L${.5+t},${.5+e} L${.5+t},1 M0,${.5-e} L${.5-t},${.5-e} L${.5-t},0 M1,${.5-e} L${.5+t},${.5-e} L${.5+t},0`,strokeWidth:1},"╭":{type:2,data:(t,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,1,.5`,strokeWidth:1},"╮":{type:2,data:(t,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,0,.5`,strokeWidth:1},"╯":{type:2,data:(t,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,0,.5`,strokeWidth:1},"╰":{type:2,data:(t,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,1,.5`,strokeWidth:1},"╱":{type:2,data:"M1,0 L0,1",strokeWidth:1},"╲":{type:2,data:"M0,0 L1,1",strokeWidth:1},"╳":{type:2,data:"M1,0 L0,1 M0,0 L1,1",strokeWidth:1},"╴":{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},"╵":{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},"╶":{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},"╷":{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},"╸":{type:2,data:"M.5,.5 L0,.5",strokeWidth:3},"╹":{type:2,data:"M.5,.5 L.5,0",strokeWidth:3},"╺":{type:2,data:"M.5,.5 L1,.5",strokeWidth:3},"╻":{type:2,data:"M.5,.5 L.5,1",strokeWidth:3},"╼":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"╽":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"╾":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"╿":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"▀":{type:0,data:[{x:0,y:0,w:8,h:4}]},"▁":{type:0,data:[{x:0,y:7,w:8,h:1}]},"▂":{type:0,data:[{x:0,y:6,w:8,h:2}]},"▃":{type:0,data:[{x:0,y:5,w:8,h:3}]},"▄":{type:0,data:[{x:0,y:4,w:8,h:4}]},"▅":{type:0,data:[{x:0,y:3,w:8,h:5}]},"▆":{type:0,data:[{x:0,y:2,w:8,h:6}]},"▇":{type:0,data:[{x:0,y:1,w:8,h:7}]},"█":{type:0,data:[{x:0,y:0,w:8,h:8}]},"▉":{type:0,data:[{x:0,y:0,w:7,h:8}]},"▊":{type:0,data:[{x:0,y:0,w:6,h:8}]},"▋":{type:0,data:[{x:0,y:0,w:5,h:8}]},"▌":{type:0,data:[{x:0,y:0,w:4,h:8}]},"▍":{type:0,data:[{x:0,y:0,w:3,h:8}]},"▎":{type:0,data:[{x:0,y:0,w:2,h:8}]},"▏":{type:0,data:[{x:0,y:0,w:1,h:8}]},"▐":{type:0,data:[{x:4,y:0,w:4,h:8}]},"░":{type:1,data:[[1,0],[0,0]]},"▒":{type:1,data:[[1,0],[0,1]]},"▓":{type:1,data:[[1,1],[1,0]]},"▔":{type:0,data:[{x:0,y:0,w:8,h:1}]},"▕":{type:0,data:[{x:7,y:0,w:1,h:8}]},"▖":{type:0,data:[{x:0,y:4,w:4,h:4}]},"▗":{type:0,data:[{x:4,y:4,w:4,h:4}]},"▘":{type:0,data:[{x:0,y:0,w:4,h:4}]},"▙":{type:0,data:[{x:0,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}]},"▚":{type:0,data:[{x:0,y:0,w:4,h:4},{x:4,y:4,w:4,h:4}]},"▛":{type:0,data:[{x:0,y:0,w:4,h:8},{x:4,y:0,w:4,h:4}]},"▜":{type:0,data:[{x:0,y:0,w:8,h:4},{x:4,y:0,w:4,h:8}]},"▝":{type:0,data:[{x:4,y:0,w:4,h:4}]},"▞":{type:0,data:[{x:4,y:0,w:4,h:4},{x:0,y:4,w:4,h:4}]},"▟":{type:0,data:[{x:4,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}]},"":{type:5,data:{d:"M.3,1 L.03,1 L.03,.88 C.03,.82,.06,.78,.11,.73 C.15,.7,.2,.68,.28,.65 L.43,.6 C.49,.58,.53,.56,.56,.53 C.59,.5,.6,.47,.6,.43 L.6,.27 L.4,.27 L.69,.1 L.98,.27 L.78,.27 L.78,.46 C.78,.52,.76,.56,.72,.61 C.68,.66,.63,.67,.56,.7 L.48,.72 C.42,.74,.38,.76,.35,.78 C.32,.8,.31,.84,.31,.88 L.31,1 M.3,.5 L.03,.59 L.03,.09 L.3,.09 L.3,.655",type:0}},"":{type:5,data:{d:"M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.355,.03 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5",type:0}},"":{type:5,data:{d:"M.25,.94 C.16,.94,.11,.92,.11,.87 L.11,.53 C.11,.48,.15,.455,.23,.45 L.23,.3 C.23,.25,.26,.22,.31,.19 C.36,.16,.43,.15,.51,.15 C.59,.15,.66,.16,.71,.19 C.77,.22,.79,.26,.79,.3 L.79,.45 C.87,.45,.91,.48,.91,.53 L.91,.87 C.91,.92,.86,.94,.77,.94 L.24,.94 M.53,.2 C.49,.2,.45,.21,.42,.23 C.39,.25,.38,.27,.38,.3 L.38,.45 L.68,.45 L.68,.3 C.68,.27,.67,.25,.64,.23 C.61,.21,.58,.2,.53,.2 M.58,.82 L.58,.66 C.63,.65,.65,.63,.65,.6 C.65,.58,.64,.57,.61,.56 C.58,.55,.56,.54,.52,.54 C.48,.54,.46,.55,.43,.56 C.4,.57,.39,.59,.39,.6 C.39,.63,.41,.64,.46,.66 L.46,.82 L.57,.82",type:0}},"":{type:5,data:{d:"M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.7,.03 L.7,.1 L.355,.1 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5",type:0}},"":{type:5,data:{d:"M0,0 L1,.5 L0,1",type:0,rightPadding:2}},"":{type:5,data:{d:"M-1,-.5 L1,.5 L-1,1.5",type:1,leftPadding:1,rightPadding:1}},"":{type:5,data:{d:"M1,0 L0,.5 L1,1",type:0,leftPadding:2}},"":{type:5,data:{d:"M2,-.5 L0,.5 L2,1.5",type:1,leftPadding:1,rightPadding:1}},"":{type:5,data:{d:"M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0",type:0,rightPadding:1}},"":{type:5,data:{d:"M.2,1 C.422,1,.8,.826,.78,.5 C.8,.174,0.422,0,.2,0",type:1,rightPadding:1}},"":{type:5,data:{d:"M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0",type:0,leftPadding:1}},"":{type:5,data:{d:"M.8,1 C0.578,1,0.2,.826,.22,.5 C0.2,0.174,0.578,0,0.8,0",type:1,leftPadding:1}},"":{type:5,data:{d:"M-.5,-.5 L1.5,1.5 L-.5,1.5",type:0}},"":{type:5,data:{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1}},"":{type:5,data:{d:"M1.5,-.5 L-.5,1.5 L1.5,1.5",type:0}},"":{type:5,data:{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1}},"":{type:5,data:{d:"M1.5,-.5 L-.5,1.5 L-.5,-.5",type:0}},"":{type:5,data:{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1}},"":{type:5,data:{d:"M-.5,-.5 L1.5,1.5 L1.5,-.5",type:0}},"":{type:5,data:{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1}},"":{type:5,data:{d:"M0.8069,0.6075 Q0.7991,0.612,0.7815,0.6218 T0.7542,0.6369 T0.7282,0.6508 T0.7016,0.6644 T0.6778,0.6758 T0.6551,0.6854 T0.637,0.6911 T0.622,0.6936 Q0.6186,0.6936,0.6128,0.6942 T0.6018,0.6952 T0.5894,0.6956 T0.5757,0.6952 T0.561,0.693 T0.5451,0.6879 Q0.5342,0.6834,0.5193,0.6756 T0.4925,0.6614 T0.4673,0.6485 T0.4436,0.6389 T0.4239,0.6363 T0.4089,0.6422 Q0.4045,0.6467,0.3958,0.6554 T0.3843,0.6669 T0.3748,0.6746 T0.3613,0.683 T0.3442,0.6903 L0.3499,0.6956 Q0.3584,0.7042,0.3679,0.7177 T0.3836,0.7403 T0.4019,0.7583 T0.4295,0.7703 Q0.4688,0.7785,0.5203,0.7605 Q0.4902,0.7817,0.449,0.7911 T0.3696,0.8001 Q0.3567,0.7997,0.341,0.7942 T0.3111,0.7813 T0.2805,0.766 T0.2493,0.753 T0.218,0.7472 T0.187,0.754 Q0.1762,0.7589,0.1653,0.7681 T0.1453,0.7895 T0.1308,0.818 T0.1264,0.8507 L0.1264,0.858 Q0.1453,0.8462,0.1607,0.8433 T0.1899,0.8452 T0.2226,0.8625 Q0.2344,0.8707,0.2485,0.8752 T0.2813,0.8831 T0.3089,0.889 Q0.2981,0.8915,0.2846,0.8956 T0.2603,0.9037 T0.2368,0.9115 T0.2129,0.9174 T0.1894,0.9194 T0.1653,0.9155 Q0.1504,0.9115,0.1328,0.9196 T0.0986,0.9421 T0.0655,0.9708 T0.0317,0.9943 T0,1 Q0.001,0.94,0.0017,0.6508 T0.0024,0.2322 Q0.002,0.2109,0.0007,0.1379 T0,0.0249 Q0.0058,0.0253,0.009,0.0257 T0.0156,0.0288 T0.0203,0.0333 T0.0264,0.0422 T0.0349,0.0539 Q0.044,0.0657,0.0542,0.072 T0.0752,0.0789 T0.096,0.0775 T0.1175,0.0694 T0.1379,0.0573 T0.1579,0.0432 Q0.2141,0,0.2673,0.0318 Q0.2588,0.0318,0.2517,0.033 T0.239,0.0361 T0.2276,0.0424 T0.218,0.0504 T0.2085,0.0612 T0.1997,0.0732 T0.1899,0.0875 T0.1795,0.1028 Q0.1748,0.1093,0.1507,0.1234 T0.1186,0.1448 Q0.0999,0.1612,0.1108,0.1783 Q0.1162,0.1877,0.1238,0.1928 T0.1404,0.1979 T0.158,0.1967 T0.1775,0.1895 T0.1963,0.1791 T0.2151,0.1663 T0.2314,0.1534 T0.2458,0.141 T0.2561,0.1322 Q0.2815,0.1106,0.3017,0.101 T0.344,0.0926 T0.3919,0.1077 Q0.4099,0.1175,0.4265,0.1206 T0.4588,0.1202 T0.488,0.1098 T0.5176,0.0914 Q0.5071,0.1032,0.4963,0.1122 T0.4722,0.1285 T0.4497,0.1401 T0.4223,0.1518 T0.3943,0.1636 Q0.374,0.1726,0.3623,0.1863 T0.3482,0.2162 Q0.3445,0.2403,0.3608,0.2605 T0.4072,0.2876 Q0.4194,0.2909,0.4326,0.2899 T0.457,0.2856 T0.4805,0.2764 T0.5032,0.264 T0.5256,0.2497 T0.5478,0.2352 T0.5698,0.2222 T0.5921,0.2118 Q0.6982,0.173,0.81,0.2436 Q0.833,0.2583,0.8496,0.2674 T0.887,0.2846 T0.925,0.294 T0.9617,0.2911 T1,0.2746 Q0.9817,0.2946,0.96,0.3056 T0.9175,0.3191 T0.8731,0.3209 T0.8277,0.3164 T0.7818,0.3109 T0.7359,0.3101 T0.6907,0.3197 T0.647,0.3448 Q0.6443,0.3472,0.6333,0.3562 T0.6194,0.3674 T0.6076,0.3766 T0.594,0.386 T0.5815,0.3931 T0.5661,0.4007 T0.5501,0.4064 L0.5556,0.4121 Q0.5627,0.4198,0.5722,0.4251 T0.5926,0.4337 T0.6131,0.439 T0.6343,0.4431 T0.6521,0.4468 Q0.6762,0.4529,0.7056,0.4463 Q0.6772,0.4623,0.6543,0.471 T0.604,0.4814 Q0.5962,0.4818,0.5815,0.4825 T0.5591,0.4835 T0.5393,0.4857 T0.519,0.4908 T0.5007,0.4996 T0.4812,0.5141 T0.4627,0.5353 L0.4549,0.5451 L0.4661,0.543 Q0.4759,0.541,0.4909,0.55 T0.5315,0.5796 T0.5684,0.6083 Q0.5799,0.6161,0.5915,0.6212 T0.6181,0.6308 L0.6331,0.6353 Q0.6514,0.6418,0.7004,0.633 T0.8069,0.6075 Z",type:0}},"":{type:5,data:{d:"M0.011,0.0469 Q0.0152,0.0412,0.02,0.0361 T0.0292,0.0281 T0.0365,0.0253 Q0.0426,0.0257,0.0461,0.0263 T0.0534,0.03 T0.0582,0.0345 T0.0645,0.0434 T0.0723,0.0546 Q0.0807,0.0653,0.0897,0.0712 T0.1081,0.0775 T0.1255,0.0765 T0.1438,0.0695 T0.1605,0.0593 T0.1772,0.0473 Q0.2388,0,0.2927,0.0326 L0.2614,0.0767 L0.2581,0.0767 Q0.2494,0.0767,0.2444,0.0773 T0.2359,0.0789 T0.2291,0.0842 T0.2233,0.0922 T0.2146,0.1054 T0.2026,0.1236 Q0.1943,0.1354,0.1843,0.1444 T0.1675,0.1574 T0.1526,0.1654 T0.1429,0.1701 Q0.1436,0.1717,0.1446,0.1733 Q0.1504,0.1823,0.1581,0.1866 T0.1733,0.1915 T0.1909,0.1884 T0.2085,0.1809 T0.2265,0.169 T0.2427,0.1564 T0.2575,0.1436 T0.2688,0.1338 Q0.2969,0.1101,0.3195,0.0995 T0.3643,0.0901 T0.4114,0.1048 Q0.4279,0.1138,0.4426,0.1168 T0.4705,0.117 T0.4945,0.1089 T0.5186,0.0942 L0.5211,0.0926 L0.5289,0.0922 Q0.5411,0.095,0.5189,0.1203 Q0.505,0.1358,0.4913,0.1474 T0.4634,0.167 T0.4389,0.1796 T0.4121,0.1909 T0.3866,0.2011 Q0.3782,0.2047,0.3727,0.2092 Q0.3717,0.2117,0.3714,0.2141 Q0.3695,0.2263,0.3751,0.2388 T0.394,0.2616 T0.4253,0.2761 Q0.4398,0.2785,0.4513,0.2779 T0.4756,0.2724 T0.4987,0.2616 T0.5216,0.2476 T0.5453,0.2321 T0.5703,0.217 T0.5976,0.2043 Q0.7015,0.166,0.8099,0.2345 Q0.8361,0.2512,0.8572,0.2616 T0.9006,0.2779 T0.9432,0.281 T0.9819,0.2663 L0.9887,0.2671 Q1,0.272,0.9768,0.2969 Q0.9558,0.3193,0.9319,0.333 T0.8858,0.3513 T0.8391,0.3569 T0.793,0.3546 T0.7485,0.3497 T0.7065,0.3473 T0.6681,0.3522 T0.6344,0.3699 Q0.5963,0.4005,0.5847,0.4082 Q0.595,0.4139,0.6079,0.4174 T0.6367,0.4237 T0.6586,0.4278 Q0.6793,0.4331,0.7044,0.4278 Q0.7057,0.4278,0.707,0.4274 Q0.7067,0.429,0.7062,0.4313 T0.7036,0.4398 T0.6993,0.4513 T0.6931,0.4619 T0.6851,0.4698 Q0.6573,0.4857,0.6336,0.4947 T0.5831,0.5053 Q0.577,0.5057,0.5615,0.5065 T0.5394,0.508 T0.5221,0.51 T0.5042,0.5139 T0.4905,0.5204 Q0.5008,0.5232,0.515,0.5328 T0.549,0.5585 T0.5799,0.5824 Q0.5905,0.5897,0.6013,0.5946 T0.6262,0.6036 T0.6409,0.6081 Q0.6567,0.6134,0.7035,0.605 T0.8028,0.5816 L0.7974,0.5922 T0.7867,0.6134 L0.7812,0.624 Q0.7799,0.6248,0.7786,0.6252 Q0.7709,0.6297,0.7573,0.6372 T0.7346,0.6499 T0.7123,0.6621 T0.6897,0.6741 T0.6686,0.6847 T0.648,0.6941 T0.6297,0.7013 T0.6129,0.7062 T0.5995,0.7076 Q0.5992,0.7076,0.5831,0.7092 T0.5529,0.7092 T0.525,0.7019 Q0.5105,0.6958,0.4824,0.6811 T0.4356,0.6586 T0.4079,0.6533 Q0.3872,0.6741,0.3827,0.6782 Q0.3872,0.6835,0.3934,0.6929 T0.4037,0.7078 T0.4142,0.7198 T0.4284,0.7304 T0.4466,0.7365 Q0.4824,0.7443,0.5292,0.728 L0.5292,0.7294 T0.529,0.7333 T0.5284,0.7392 T0.5266,0.7463 T0.5236,0.7539 T0.5186,0.7612 T0.5111,0.7679 Q0.5086,0.77,0.5056,0.7716 Q0.4466,0.8104,0.3591,0.8091 Q0.344,0.8091,0.3246,0.802 T0.2893,0.7863 T0.2543,0.77 T0.2206,0.76 T0.1894,0.7643 Q0.1804,0.7684,0.1733,0.7753 Q0.1591,0.7892,0.1601,0.8169 Q0.1784,0.8067,0.1938,0.8053 T0.2212,0.8087 T0.2507,0.825 Q0.2614,0.8324,0.2744,0.8365 T0.3053,0.844 T0.332,0.8499 L0.303,0.894 L0.2998,0.8948 Q0.2904,0.8968,0.278,0.9009 T0.2554,0.9086 T0.233,0.9158 T0.2099,0.9213 T0.1868,0.9229 T0.1633,0.9192 Q0.1517,0.916,0.1367,0.9241 T0.1065,0.9462 T0.0753,0.9737 T0.041,0.9955 T0.0061,1 Q0,0.9984,0.0068,0.9845 Q0.009,0.9804,0.0123,0.9755 Q0.0187,0.9666,0.0261,0.9606 T0.0374,0.9555 Q0.0481,0.958,0.0629,0.949 T0.0931,0.9262 T0.1247,0.8987 T0.1597,0.8777 T0.1949,0.8752 Q0.213,0.8805,0.2343,0.8772 Q0.2249,0.8736,0.2172,0.8683 Q0.1914,0.8511,0.1738,0.8497 T0.1349,0.8617 Q0.1304,0.865,0.1273,0.8646 T0.1242,0.8605 L0.1239,0.8536 Q0.1226,0.8295,0.1354,0.8016 T0.1723,0.7496 Q0.1914,0.7308,0.2104,0.7219 Q0.2259,0.7145,0.2425,0.7141 T0.273,0.7182 T0.3022,0.7296 T0.3301,0.7439 T0.3569,0.7571 T0.3821,0.7643 Q0.373,0.7561,0.3598,0.7367 T0.3382,0.7088 L0.3327,0.7035 Q0.3282,0.699,0.3353,0.6884 T0.3517,0.6692 L0.3611,0.6607 Q0.3711,0.657,0.3751,0.6554 T0.3837,0.6507 T0.3898,0.6458 T0.3979,0.637 T0.4111,0.6236 Q0.4172,0.6175,0.4234,0.6138 Q0.4327,0.6081,0.4439,0.6077 T0.4655,0.6105 T0.4884,0.6201 T0.5113,0.633 T0.5347,0.6468 T0.5573,0.6578 Q0.5705,0.6635,0.5833,0.665 T0.6112,0.665 T0.6286,0.6635 Q0.6421,0.6631,0.6718,0.6501 Q0.626,0.6578,0.6089,0.6517 Q0.6063,0.6509,0.6002,0.6491 T0.5884,0.6454 T0.5757,0.6409 T0.561,0.6344 T0.5466,0.6256 Q0.536,0.6183,0.5108,0.5989 T0.4722,0.5712 T0.4527,0.564 L0.4418,0.5661 Q0.4308,0.5681,0.4366,0.5563 Q0.4401,0.5485,0.4476,0.5392 L0.455,0.5298 Q0.4631,0.5188,0.4714,0.51 T0.4866,0.4947 T0.5021,0.4831 T0.516,0.4749 T0.53,0.4694 T0.5428,0.4657 T0.5558,0.4637 T0.5674,0.4629 T0.5795,0.4625 Q0.5489,0.4543,0.534,0.4384 L0.5286,0.4327 Q0.5244,0.4282,0.5318,0.4174 T0.5486,0.398 L0.5579,0.3899 Q0.567,0.387,0.5749,0.3834 T0.5878,0.3772 T0.6012,0.3687 T0.6126,0.3603 T0.6268,0.3487 T0.6421,0.3361 Q0.6567,0.3242,0.6712,0.3159 T0.7009,0.3034 T0.7277,0.2973 T0.7556,0.2961 T0.7809,0.2977 T0.8074,0.301 Q0.8096,0.3014,0.8109,0.3016 T0.8143,0.302 T0.818,0.3026 Q0.8012,0.2936,0.7767,0.2781 Q0.6718,0.2117,0.5744,0.2476 Q0.5637,0.2516,0.5492,0.2604 T0.5218,0.2779 T0.4923,0.2965 T0.461,0.3124 T0.4282,0.3216 T0.394,0.3206 Q0.3637,0.3136,0.3474,0.2936 T0.3353,0.2459 Q0.3385,0.2247,0.355,0.2023 Q0.3646,0.1892,0.3763,0.179 Q0.3898,0.1672,0.4063,0.1595 Q0.3927,0.1562,0.3785,0.1485 Q0.3604,0.1387,0.3453,0.1354 T0.3161,0.1354 T0.2899,0.1458 T0.2627,0.166 Q0.262,0.1664,0.254,0.1733 T0.2414,0.1841 T0.2267,0.196 T0.2094,0.2086 T0.1915,0.2196 T0.1725,0.2288 T0.1546,0.2337 T0.1373,0.2343 T0.1223,0.228 T0.1097,0.2145 Q0.0962,0.1929,0.1258,0.1582 Q0.131,0.1517,0.1371,0.1464 Q0.1462,0.1387,0.156,0.1321 T0.1752,0.1205 T0.1878,0.1134 Q0.1891,0.1117,0.1947,0.103 T0.2035,0.0899 T0.2125,0.0781 T0.2243,0.064 Q0.1949,0.0616,0.1672,0.0828 Q0.153,0.0934,0.1412,0.1009 T0.1146,0.1146 T0.0873,0.1213 T0.0616,0.116 T0.0374,0.0962 Q0.0339,0.0909,0.0295,0.0846 T0.024,0.0767 T0.0202,0.0728 T0.0148,0.0704 T0.0068,0.0697 Q0.0003,0.0693,0.0058,0.0567 Q0.0077,0.0522,0.011,0.0469 Z",type:0}},"":{type:5,data:{d:"M0.1934,0.6075 Q0.2012,0.612,0.2188,0.6218 T0.2459,0.6369 T0.2718,0.6508 T0.2983,0.6644 T0.322,0.6758 T0.3447,0.6854 T0.363,0.6911 T0.3783,0.6936 Q0.3796,0.6936,0.396,0.6952 T0.4265,0.6952 T0.4548,0.6879 Q0.4656,0.6834,0.4805,0.6756 T0.5073,0.6614 T0.5327,0.6485 T0.5564,0.6389 T0.5759,0.6363 T0.5909,0.6422 Q0.5953,0.6467,0.604,0.6554 T0.6155,0.6669 T0.6251,0.6746 T0.6387,0.683 T0.6556,0.6903 L0.6498,0.6956 Q0.6414,0.7042,0.6319,0.7177 T0.6162,0.7403 T0.5979,0.7583 T0.5703,0.7703 Q0.5313,0.7785,0.4799,0.7605 Q0.5097,0.7817,0.5508,0.7911 T0.6302,0.8001 Q0.6431,0.7997,0.659,0.7942 T0.6888,0.7813 T0.7194,0.766 T0.7506,0.753 T0.7819,0.7472 T0.8131,0.754 Q0.8239,0.7589,0.8346,0.7681 T0.8544,0.7895 T0.8689,0.818 T0.8737,0.8507 L0.8733,0.858 Q0.8591,0.849,0.8474,0.845 T0.8242,0.8421 T0.8021,0.8482 T0.7772,0.8625 Q0.7653,0.8707,0.7513,0.8752 T0.7184,0.8831 T0.6908,0.889 Q0.7017,0.8915,0.7152,0.8956 T0.7396,0.9037 T0.7633,0.9115 T0.7872,0.9174 T0.8107,0.9194 T0.8347,0.9155 Q0.8493,0.9115,0.8671,0.9196 T0.9015,0.9421 T0.9345,0.9708 T0.9682,0.9943 T0.9997,1 Q0.999,0.94,0.9983,0.6508 T0.9976,0.2322 Q0.9976,0.2109,0.9992,0.1379 T1,0.0249 Q0.9949,0.0249,0.9917,0.0257 T0.9863,0.0273 T0.9817,0.0308 T0.9776,0.0357 T0.9721,0.0437 T0.9651,0.0539 Q0.956,0.0657,0.9456,0.072 T0.9247,0.0789 T0.904,0.0775 T0.8823,0.0694 T0.862,0.0573 T0.8422,0.0432 Q0.7856,0,0.7328,0.0318 Q0.7392,0.0318,0.7452,0.0324 T0.756,0.0345 T0.7657,0.0386 T0.7741,0.0437 T0.7821,0.0506 T0.7894,0.0588 T0.7966,0.0683 T0.8039,0.0787 T0.8119,0.0904 T0.8202,0.1028 Q0.8249,0.1093,0.849,0.1234 T0.8811,0.1448 Q0.8998,0.1612,0.8893,0.1783 Q0.8835,0.1877,0.8759,0.1928 T0.8593,0.1979 T0.8417,0.1967 T0.8222,0.1895 T0.8034,0.1791 T0.7848,0.1663 T0.7687,0.1534 T0.7543,0.141 T0.744,0.1322 Q0.7186,0.1106,0.6984,0.101 T0.6559,0.0926 T0.6082,0.1077 Q0.5899,0.1175,0.5733,0.1206 T0.5411,0.1202 T0.5122,0.1098 T0.4822,0.0914 Q0.4927,0.1032,0.5036,0.1122 T0.5276,0.1285 T0.5503,0.1401 T0.5777,0.1518 T0.6055,0.1636 Q0.6258,0.1726,0.6375,0.1863 T0.6519,0.2162 Q0.6556,0.2403,0.6392,0.2605 T0.5926,0.2876 Q0.5804,0.2909,0.5672,0.2899 T0.5428,0.2856 T0.5193,0.2764 T0.4966,0.264 T0.4744,0.2497 T0.4523,0.2352 T0.4301,0.2222 T0.4077,0.2118 Q0.3021,0.173,0.1903,0.2436 Q0.1669,0.2583,0.1504,0.2674 T0.1129,0.2846 T0.075,0.294 T0.0384,0.2911 T0,0.2746 Q0.021,0.297,0.0459,0.3082 T0.0953,0.3207 T0.146,0.3193 T0.1988,0.3131 T0.2508,0.3095 T0.3029,0.3176 T0.3529,0.3448 Q0.3566,0.348,0.3652,0.355 T0.3771,0.3645 T0.3877,0.3731 T0.3993,0.3815 T0.4103,0.3884 T0.4226,0.3953 T0.4352,0.4009 T0.4497,0.4064 L0.4443,0.4121 Q0.4372,0.4198,0.4277,0.4251 T0.4072,0.4337 T0.3867,0.439 T0.3656,0.4431 T0.3481,0.4468 Q0.3241,0.4529,0.2943,0.4463 Q0.3227,0.4623,0.3458,0.471 T0.3959,0.4814 Q0.404,0.4818,0.4187,0.4825 T0.4409,0.4835 T0.4605,0.4857 T0.4809,0.4908 T0.4992,0.4996 T0.5186,0.5141 T0.5371,0.5353 L0.5449,0.5451 L0.5337,0.543 Q0.5242,0.541,0.5091,0.55 T0.4683,0.5796 T0.4314,0.6083 Q0.4203,0.6161,0.4086,0.6212 T0.382,0.6308 T0.3667,0.6353 Q0.3485,0.6418,0.2995,0.633 T0.1934,0.6075 Z",type:0}},"":{type:5,data:{d:"M0.989,0.0469 Q0.9829,0.0379,0.9753,0.0316 T0.9635,0.0253 Q0.9574,0.0257,0.9539,0.0263 T0.9466,0.03 T0.9418,0.0345 T0.9355,0.0434 T0.9277,0.0546 Q0.9193,0.0653,0.9103,0.0712 T0.8919,0.0775 T0.8745,0.0765 T0.8564,0.0695 T0.8396,0.0593 T0.8228,0.0473 Q0.7612,0,0.7073,0.0326 L0.7386,0.0767 L0.7419,0.0767 Q0.7506,0.0767,0.7556,0.0773 T0.7641,0.0789 T0.7709,0.0842 T0.7767,0.0922 T0.7854,0.1054 T0.7974,0.1236 Q0.8057,0.1354,0.8157,0.1444 T0.8325,0.1574 T0.8474,0.1654 T0.8571,0.1701 Q0.8564,0.1717,0.8554,0.1733 Q0.8503,0.1815,0.844,0.186 T0.8301,0.1909 T0.8153,0.1903 T0.7993,0.1845 T0.7836,0.1758 T0.768,0.1648 T0.754,0.1535 T0.741,0.1425 T0.7312,0.1338 Q0.7031,0.1101,0.6805,0.0995 T0.6357,0.0901 T0.5886,0.1048 Q0.5721,0.1138,0.5574,0.1168 T0.5295,0.117 T0.5055,0.1089 T0.4814,0.0942 L0.4789,0.0926 L0.4711,0.0922 Q0.4589,0.095,0.4811,0.1203 Q0.495,0.1358,0.5087,0.1474 T0.5366,0.167 T0.5611,0.1796 T0.5879,0.1909 T0.6134,0.2011 Q0.6218,0.2047,0.6273,0.2092 Q0.6283,0.2117,0.6286,0.2141 Q0.6305,0.2263,0.6249,0.2388 T0.606,0.2616 T0.5747,0.2761 Q0.5602,0.2785,0.5487,0.2779 T0.5245,0.2724 T0.5015,0.2616 T0.4784,0.2476 T0.4547,0.2321 T0.4297,0.217 T0.4024,0.2043 Q0.2985,0.166,0.1901,0.2345 Q0.1694,0.248,0.1523,0.2569 T0.1165,0.2726 T0.0818,0.2812 T0.0494,0.2796 T0.0181,0.2663 L0.0116,0.2671 Q0,0.272,0.0232,0.2969 Q0.0468,0.3222,0.0742,0.3365 T0.1278,0.3542 T0.1807,0.3564 T0.233,0.352 T0.2815,0.3473 T0.3269,0.3509 T0.3656,0.3699 Q0.4037,0.4005,0.4153,0.4082 Q0.405,0.4139,0.3921,0.4174 T0.3633,0.4237 T0.3414,0.4278 Q0.3207,0.4331,0.2956,0.4278 Q0.2943,0.4278,0.293,0.4274 Q0.2933,0.429,0.2938,0.4313 T0.2964,0.4398 T0.3007,0.4513 T0.3069,0.4619 T0.3149,0.4698 Q0.3427,0.4857,0.3664,0.4947 T0.4169,0.5053 Q0.423,0.5057,0.4385,0.5065 T0.4606,0.508 T0.4779,0.51 T0.4958,0.5139 T0.5095,0.5204 Q0.4992,0.5232,0.485,0.5328 T0.451,0.5585 T0.4201,0.5824 Q0.4095,0.5897,0.3987,0.5946 T0.3738,0.6036 T0.3591,0.6081 Q0.3433,0.6134,0.2965,0.605 T0.1972,0.5816 L0.2026,0.5922 T0.2133,0.6134 L0.2188,0.624 Q0.2201,0.6248,0.2214,0.6252 Q0.2291,0.6297,0.2486,0.6405 T0.2786,0.657 T0.3066,0.6721 T0.3353,0.6866 T0.3598,0.6972 T0.383,0.7051 T0.4005,0.7076 Q0.4008,0.7076,0.4169,0.7092 T0.4471,0.7092 T0.475,0.7019 Q0.4895,0.6958,0.5176,0.6811 T0.5644,0.6586 T0.5921,0.6533 Q0.6128,0.6741,0.6173,0.6782 Q0.6131,0.6835,0.6068,0.6929 T0.5963,0.7078 T0.5858,0.7198 T0.5716,0.7304 T0.5534,0.7365 Q0.5179,0.7443,0.4708,0.728 L0.4708,0.7294 T0.471,0.7333 T0.4716,0.7392 T0.4734,0.7463 T0.4764,0.7539 T0.4814,0.7612 T0.4889,0.7679 Q0.4914,0.77,0.4944,0.7716 Q0.5534,0.8104,0.6409,0.8091 Q0.6521,0.8091,0.6654,0.8053 T0.6907,0.7959 T0.7157,0.7838 T0.741,0.772 T0.7652,0.7631 T0.7888,0.7594 T0.8106,0.7643 Q0.8196,0.7684,0.8267,0.7753 Q0.8409,0.7892,0.8399,0.8169 Q0.8216,0.8067,0.8062,0.8053 T0.7788,0.8087 T0.7493,0.825 Q0.7386,0.8324,0.7256,0.8365 T0.6947,0.844 T0.668,0.8499 L0.697,0.894 L0.7002,0.8948 Q0.7099,0.8968,0.7222,0.9009 T0.7446,0.9086 T0.767,0.9158 T0.7901,0.9213 T0.8132,0.9229 T0.8367,0.9192 Q0.8483,0.916,0.8633,0.9241 T0.8937,0.9462 T0.9248,0.9737 T0.959,0.9955 T0.9939,1 Q1,0.9984,0.9932,0.9845 Q0.991,0.9804,0.9877,0.9755 Q0.9813,0.9666,0.9739,0.9606 T0.9626,0.9555 Q0.9519,0.958,0.9372,0.949 T0.9071,0.9262 T0.8753,0.8987 T0.8403,0.8777 T0.8051,0.8752 Q0.787,0.8805,0.7657,0.8772 Q0.7751,0.8736,0.7828,0.8683 Q0.8086,0.8511,0.8262,0.8497 T0.8651,0.8617 Q0.8696,0.865,0.8727,0.8646 T0.8758,0.8605 L0.8761,0.8536 Q0.8774,0.8295,0.8646,0.8016 T0.8277,0.7496 Q0.8086,0.7308,0.7896,0.7219 Q0.7741,0.7145,0.7575,0.7141 T0.727,0.7182 T0.6978,0.7296 T0.6699,0.7439 T0.6433,0.7571 T0.6179,0.7643 Q0.627,0.7561,0.6402,0.7367 T0.6618,0.7088 L0.6673,0.7035 Q0.6718,0.699,0.6647,0.6884 T0.6483,0.6692 L0.6389,0.6607 Q0.6289,0.657,0.6249,0.6554 T0.6163,0.6507 T0.6102,0.6458 T0.6021,0.637 T0.5889,0.6236 Q0.5828,0.6175,0.5766,0.6138 Q0.5673,0.6081,0.5561,0.6077 T0.5345,0.6105 T0.5116,0.6201 T0.4887,0.633 T0.4653,0.6468 T0.4427,0.6578 Q0.4295,0.6635,0.4167,0.665 T0.3888,0.665 T0.3714,0.6635 Q0.3579,0.6631,0.3282,0.6501 Q0.374,0.6578,0.3911,0.6517 Q0.3937,0.6509,0.4024,0.6482 T0.4179,0.6434 T0.4348,0.6364 T0.4534,0.6256 Q0.464,0.6183,0.4892,0.5989 T0.5278,0.5712 T0.5476,0.564 L0.5582,0.5661 Q0.5692,0.5681,0.5634,0.5563 Q0.5599,0.5485,0.5524,0.5392 L0.545,0.5298 Q0.5369,0.5188,0.5286,0.51 T0.5134,0.4947 T0.4979,0.4831 T0.484,0.4749 T0.47,0.4694 T0.4572,0.4657 T0.4442,0.4637 T0.4326,0.4629 T0.4205,0.4625 Q0.4511,0.4543,0.466,0.4384 L0.4714,0.4327 Q0.4795,0.4237,0.4469,0.394 L0.4421,0.3899 Q0.433,0.387,0.4251,0.3834 T0.4122,0.3772 T0.3988,0.3687 T0.3874,0.3603 T0.3732,0.3487 T0.3579,0.3361 Q0.3433,0.3242,0.3288,0.3159 T0.2993,0.3034 T0.2725,0.2973 T0.2446,0.2961 T0.2193,0.2977 T0.1926,0.301 Q0.1855,0.3022,0.182,0.3026 Q0.1991,0.2936,0.2233,0.2781 Q0.3282,0.2117,0.4256,0.2476 Q0.4363,0.2516,0.451,0.2604 T0.4784,0.2779 T0.5077,0.2965 T0.539,0.3124 T0.5718,0.3216 T0.606,0.3206 Q0.6363,0.3136,0.6526,0.2936 T0.6647,0.2459 Q0.6615,0.2247,0.645,0.2023 Q0.6354,0.1892,0.6237,0.179 Q0.6102,0.1672,0.5937,0.1595 Q0.6073,0.1562,0.6215,0.1485 Q0.6396,0.1387,0.6547,0.1354 T0.6839,0.1354 T0.7101,0.1458 T0.7373,0.166 Q0.738,0.1664,0.746,0.1733 T0.7586,0.1841 T0.7733,0.196 T0.7907,0.2086 T0.8086,0.2196 T0.8275,0.2288 T0.8454,0.2337 T0.8627,0.2343 T0.8777,0.228 T0.8903,0.2145 Q0.9038,0.1929,0.8742,0.1582 Q0.869,0.1517,0.8629,0.1464 Q0.8538,0.1387,0.844,0.1321 T0.8248,0.1205 T0.8122,0.1134 Q0.8109,0.1117,0.8053,0.103 T0.7965,0.0899 T0.7875,0.0781 T0.7757,0.064 Q0.8051,0.0616,0.8328,0.0828 Q0.847,0.0934,0.8588,0.1009 T0.8854,0.1146 T0.9127,0.1213 T0.9384,0.116 T0.9626,0.0962 Q0.9664,0.0909,0.9706,0.0846 T0.976,0.0767 T0.9798,0.0728 T0.9852,0.0704 T0.9932,0.0697 Q0.9997,0.0693,0.9942,0.0567 Q0.9923,0.0522,0.989,0.0469 Z",type:0}},"":{type:5,data:{d:"M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0.9099,0.1158 L0.9099,0.1888 L0.9951,0.1888 L0.9951,0.1158 L0.9099,0.1158 Z M0.9075,0.1116 L0.9976,0.1116 Q1,0.1116,1,0.1137 L1,0.1909 Q1,0.193,0.9976,0.193 L0.9075,0.193 Q0.905,0.193,0.905,0.1909 L0.905,0.1137 Q0.905,0.1116,0.9075,0.1116 Z M0.7852,0.6086 L0.7852,0.682 L0.8704,0.682 L0.8704,0.6086 L0.7852,0.6086 Z M0.7828,0.6044 L0.8729,0.6044 Q0.8753,0.6044,0.8753,0.6065 L0.8753,0.6841 Q0.8753,0.6862,0.8729,0.6862 L0.7828,0.6862 Q0.7803,0.6862,0.7803,0.6841 L0.7803,0.6065 Q0.7803,0.6044,0.7828,0.6044 Z M0.66,0.1137 L0.7501,0.1137 L0.7501,0.1909 L0.66,0.1909 L0.66,0.1137 Z M0.6624,0.4102 L0.6624,0.4836 L0.7477,0.4836 L0.7477,0.4102 L0.6624,0.4102 Z M0.66,0.406 L0.7501,0.406 Q0.7526,0.406,0.7526,0.4081 L0.7526,0.4857 Q0.7526,0.4878,0.7501,0.4878 L0.66,0.4878 Q0.6576,0.4878,0.6576,0.4857 L0.6576,0.4081 Q0.6576,0.406,0.66,0.406 Z M0.6624,0.7131 L0.6624,0.7865 L0.7477,0.7865 L0.7477,0.7131 L0.6624,0.7131 Z M0.66,0.7089 L0.7501,0.7089 Q0.7526,0.7089,0.7526,0.711 L0.7526,0.7886 Q0.7526,0.7907,0.7501,0.7907 L0.66,0.7907 Q0.6576,0.7907,0.6576,0.7886 L0.6576,0.711 Q0.6576,0.7089,0.66,0.7089 Z M0.5407,0.2227 L0.5407,0.2836 L0.6113,0.2836 L0.6113,0.2227 L0.5407,0.2227 Z M0.5309,0.206 L0.621,0.206 Q0.623,0.206,0.6249,0.2068 T0.6281,0.2089 T0.6301,0.2116 T0.6308,0.2143 L0.6308,0.2919 Q0.6308,0.2957,0.6276,0.298 T0.621,0.3003 L0.5309,0.3003 Q0.5265,0.3003,0.5239,0.2976 T0.5212,0.2919 L0.5212,0.2143 Q0.5212,0.211,0.5244,0.2085 T0.5309,0.206 Z M0.5407,0.3184 L0.5407,0.3792 L0.6113,0.3792 L0.6113,0.3184 L0.5407,0.3184 Z M0.5309,0.3016 L0.621,0.3016 Q0.6254,0.3016,0.6281,0.3043 T0.6308,0.31 L0.6308,0.3876 Q0.6308,0.3914,0.6276,0.3937 T0.621,0.396 L0.5309,0.396 Q0.5265,0.396,0.5239,0.3932 T0.5212,0.3876 L0.5212,0.31 Q0.5212,0.3062,0.5244,0.3039 T0.5309,0.3016 Z M0.5309,0.4081 L0.621,0.4081 L0.621,0.4857 L0.5309,0.4857 L0.5309,0.4081 Z M0.5309,0.5059 L0.621,0.5059 L0.621,0.5835 L0.5309,0.5835 L0.5309,0.5059 Z M0.5407,0.6149 L0.5407,0.6758 L0.6113,0.6758 L0.6113,0.6149 L0.5407,0.6149 Z M0.5309,0.5982 L0.621,0.5982 Q0.6254,0.5982,0.6281,0.6009 T0.6308,0.6065 L0.6308,0.6841 Q0.6308,0.6879,0.6276,0.6902 T0.621,0.6925 L0.5309,0.6925 Q0.5265,0.6925,0.5239,0.6898 T0.5212,0.6841 L0.5212,0.6065 Q0.5212,0.6028,0.5244,0.6005 T0.5309,0.5982 Z M0.4062,0.1137 L0.4963,0.1137 L0.4963,0.1909 L0.4062,0.1909 L0.4062,0.1137 Z M0.4062,0.2143 L0.4963,0.2143 L0.4963,0.2919 L0.4062,0.2919 L0.4062,0.2143 Z M0.4184,0.4186 L0.4184,0.4753 L0.4842,0.4753 L0.4842,0.4186 L0.4184,0.4186 Z M0.4062,0.3977 L0.4963,0.3977 Q0.5017,0.3977,0.5051,0.401 T0.5085,0.4081 L0.5085,0.4857 Q0.5085,0.4904,0.5046,0.4933 T0.4963,0.4962 L0.4062,0.4962 Q0.4009,0.4962,0.3975,0.4929 T0.3941,0.4857 L0.3941,0.4081 Q0.3941,0.4035,0.398,0.4006 T0.4062,0.3977 Z M0.4062,0.5059 L0.4963,0.5059 L0.4963,0.5835 L0.4062,0.5835 L0.4062,0.5059 Z M0.4087,0.7131 L0.4087,0.7865 L0.4939,0.7865 L0.4939,0.7131 L0.4087,0.7131 Z M0.4062,0.7089 L0.4963,0.7089 Q0.4988,0.7089,0.4988,0.711 L0.4988,0.7886 Q0.4988,0.7907,0.4963,0.7907 L0.4062,0.7907 Q0.4038,0.7907,0.4038,0.7886 L0.4038,0.711 Q0.4038,0.7089,0.4062,0.7089 Z M0.2689,0.1137 L0.359,0.1137 L0.359,0.1909 L0.2689,0.1909 L0.2689,0.1137 Z M0.2689,0.2143 L0.359,0.2143 L0.359,0.2919 L0.2689,0.2919 L0.2689,0.2143 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.31 L0.359,0.31 L0.359,0.3876 L0.2689,0.3876 L0.2689,0.31 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.4081 L0.359,0.4081 L0.359,0.4857 L0.2689,0.4857 L0.2689,0.4081 Z M0.2689,0.5059 L0.359,0.5059 L0.359,0.5835 L0.2689,0.5835 L0.2689,0.5059 Z M0.2689,0.711 L0.359,0.711 L0.359,0.7886 L0.2689,0.7886 L0.2689,0.711 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.1374,0.1137 L0.2275,0.1137 L0.2275,0.1909 L0.1374,0.1909 L0.1374,0.1137 Z M0.1374,0.2143 L0.2275,0.2143 L0.2275,0.2919 L0.1374,0.2919 L0.1374,0.2143 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.31 L0.2275,0.31 L0.2275,0.3876 L0.1374,0.3876 L0.1374,0.31 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.4081 L0.2275,0.4081 L0.2275,0.4857 L0.1374,0.4857 L0.1374,0.4081 Z M0.1374,0.5059 L0.2275,0.5059 L0.2275,0.5835 L0.1374,0.5835 L0.1374,0.5059 Z M0.1374,0.6065 L0.2275,0.6065 L0.2275,0.6841 L0.1374,0.6841 L0.1374,0.6065 Z M0.1374,0.711 L0.2275,0.711 L0.2275,0.7886 L0.1374,0.7886 L0.1374,0.711 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0,0.1137 L0.0901,0.1137 L0.0901,0.1909 L0,0.1909 L0,0.1137 Z M0,0.2143 L0.0901,0.2143 L0.0901,0.2919 L0,0.2919 L0,0.2143 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.31 L0.0901,0.31 L0.0901,0.3876 L0,0.3876 L0,0.31 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.4081 L0.0901,0.4081 L0.0901,0.4857 L0,0.4857 L0,0.4081 Z M0,0.5059 L0.0901,0.5059 L0.0901,0.5835 L0,0.5835 L0,0.5059 Z M0,0.6065 L0.0901,0.6065 L0.0901,0.6841 L0,0.6841 L0,0.6065 Z M0,0.711 L0.0901,0.711 L0.0901,0.7886 L0,0.7886 L0,0.711 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z",type:0}},"":{type:5,data:{d:"M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M0.0901,0.1158 L0.0901,0.1888 L0.0049,0.1888 L0.0049,0.1158 L0.0901,0.1158 Z M0.0925,0.1116 L0.0024,0.1116 Q0,0.1116,0,0.1137 L0,0.1909 Q0,0.193,0.0024,0.193 L0.0925,0.193 Q0.095,0.193,0.095,0.1909 L0.095,0.1137 Q0.095,0.1116,0.0925,0.1116 Z M0.2148,0.6086 L0.2148,0.682 L0.1296,0.682 L0.1296,0.6086 L0.2148,0.6086 Z M0.2172,0.6044 L0.1271,0.6044 Q0.1247,0.6044,0.1247,0.6065 L0.1247,0.6841 Q0.1247,0.6862,0.1271,0.6862 L0.2172,0.6862 Q0.2197,0.6862,0.2197,0.6841 L0.2197,0.6065 Q0.2197,0.6044,0.2172,0.6044 Z M0.34,0.1137 L0.2499,0.1137 L0.2499,0.1909 L0.34,0.1909 L0.34,0.1137 Z M0.3376,0.4102 L0.3376,0.4836 L0.2523,0.4836 L0.2523,0.4102 L0.3376,0.4102 Z M0.34,0.406 L0.2499,0.406 Q0.2474,0.406,0.2474,0.4081 L0.2474,0.4857 Q0.2474,0.4878,0.2499,0.4878 L0.34,0.4878 Q0.3424,0.4878,0.3424,0.4857 L0.3424,0.4081 Q0.3424,0.406,0.34,0.406 Z M0.3376,0.7131 L0.3376,0.7865 L0.2523,0.7865 L0.2523,0.7131 L0.3376,0.7131 Z M0.34,0.7089 L0.2499,0.7089 Q0.2474,0.7089,0.2474,0.711 L0.2474,0.7886 Q0.2474,0.7907,0.2499,0.7907 L0.34,0.7907 Q0.3424,0.7907,0.3424,0.7886 L0.3424,0.711 Q0.3424,0.7089,0.34,0.7089 Z M0.4593,0.2227 L0.4593,0.2836 L0.3887,0.2836 L0.3887,0.2227 L0.4593,0.2227 Z M0.4691,0.206 L0.379,0.206 Q0.3746,0.206,0.3719,0.2087 T0.3692,0.2143 L0.3692,0.2919 Q0.3692,0.2957,0.3724,0.298 T0.379,0.3003 L0.4691,0.3003 Q0.4735,0.3003,0.4761,0.2976 T0.4788,0.2919 L0.4788,0.2143 Q0.4788,0.211,0.4756,0.2085 T0.4691,0.206 Z M0.4593,0.3184 L0.4593,0.3792 L0.3887,0.3792 L0.3887,0.3184 L0.4593,0.3184 Z M0.4691,0.3016 L0.379,0.3016 Q0.3746,0.3016,0.3719,0.3043 T0.3692,0.31 L0.3692,0.3876 Q0.3692,0.3914,0.3724,0.3937 T0.379,0.396 L0.4691,0.396 Q0.4735,0.396,0.4761,0.3932 T0.4788,0.3876 L0.4788,0.31 Q0.4788,0.3062,0.4756,0.3039 T0.4691,0.3016 Z M0.4691,0.4081 L0.379,0.4081 L0.379,0.4857 L0.4691,0.4857 L0.4691,0.4081 Z M0.4691,0.5059 L0.379,0.5059 L0.379,0.5835 L0.4691,0.5835 L0.4691,0.5059 Z M0.4593,0.6149 L0.4593,0.6758 L0.3887,0.6758 L0.3887,0.6149 L0.4593,0.6149 Z M0.4691,0.5982 L0.379,0.5982 Q0.3746,0.5982,0.3719,0.6009 T0.3692,0.6065 L0.3692,0.6841 Q0.3692,0.6879,0.3724,0.6902 T0.379,0.6925 L0.4691,0.6925 Q0.4735,0.6925,0.4761,0.6898 T0.4788,0.6841 L0.4788,0.6065 Q0.4788,0.6028,0.4756,0.6005 T0.4691,0.5982 Z M0.5938,0.1137 L0.5037,0.1137 L0.5037,0.1909 L0.5938,0.1909 L0.5938,0.1137 Z M0.5938,0.2143 L0.5037,0.2143 L0.5037,0.2919 L0.5938,0.2919 L0.5938,0.2143 Z M0.5816,0.4186 L0.5816,0.4753 L0.5158,0.4753 L0.5158,0.4186 L0.5816,0.4186 Z M0.5938,0.3977 L0.5037,0.3977 Q0.4983,0.3977,0.4949,0.401 T0.4915,0.4081 L0.4915,0.4857 Q0.4915,0.4904,0.4954,0.4933 T0.5037,0.4962 L0.5938,0.4962 Q0.5991,0.4962,0.6025,0.4929 T0.6059,0.4857 L0.6059,0.4081 Q0.6059,0.4035,0.602,0.4006 T0.5938,0.3977 Z M0.5938,0.5059 L0.5037,0.5059 L0.5037,0.5835 L0.5938,0.5835 L0.5938,0.5059 Z M0.5913,0.7131 L0.5913,0.7865 L0.5061,0.7865 L0.5061,0.7131 L0.5913,0.7131 Z M0.5938,0.7089 L0.5037,0.7089 Q0.5012,0.7089,0.5012,0.711 L0.5012,0.7886 Q0.5012,0.7907,0.5037,0.7907 L0.5938,0.7907 Q0.5962,0.7907,0.5962,0.7886 L0.5962,0.711 Q0.5962,0.7089,0.5938,0.7089 Z M0.7311,0.1137 L0.641,0.1137 L0.641,0.1909 L0.7311,0.1909 L0.7311,0.1137 Z M0.7311,0.2143 L0.641,0.2143 L0.641,0.2919 L0.7311,0.2919 L0.7311,0.2143 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.31 L0.641,0.31 L0.641,0.3876 L0.7311,0.3876 L0.7311,0.31 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.4081 L0.641,0.4081 L0.641,0.4857 L0.7311,0.4857 L0.7311,0.4081 Z M0.7311,0.5059 L0.641,0.5059 L0.641,0.5835 L0.7311,0.5835 L0.7311,0.5059 Z M0.7311,0.711 L0.641,0.711 L0.641,0.7886 L0.7311,0.7886 L0.7311,0.711 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.8626,0.1137 L0.7725,0.1137 L0.7725,0.1909 L0.8626,0.1909 L0.8626,0.1137 Z M0.8626,0.2143 L0.7725,0.2143 L0.7725,0.2919 L0.8626,0.2919 L0.8626,0.2143 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.31 L0.7725,0.31 L0.7725,0.3876 L0.8626,0.3876 L0.8626,0.31 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.4081 L0.7725,0.4081 L0.7725,0.4857 L0.8626,0.4857 L0.8626,0.4081 Z M0.8626,0.5059 L0.7725,0.5059 L0.7725,0.5835 L0.8626,0.5835 L0.8626,0.5059 Z M0.8626,0.6065 L0.7725,0.6065 L0.7725,0.6841 L0.8626,0.6841 L0.8626,0.6065 Z M0.8626,0.711 L0.7725,0.711 L0.7725,0.7886 L0.8626,0.7886 L0.8626,0.711 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M1,0.1137 L0.9099,0.1137 L0.9099,0.1909 L1,0.1909 L1,0.1137 Z M1,0.2143 L0.9099,0.2143 L0.9099,0.2919 L1,0.2919 L1,0.2143 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.31 L0.9099,0.31 L0.9099,0.3876 L1,0.3876 L1,0.31 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.4081 L0.9099,0.4081 L0.9099,0.4857 L1,0.4857 L1,0.4081 Z M1,0.5059 L0.9099,0.5059 L0.9099,0.5835 L1,0.5835 L1,0.5059 Z M1,0.6065 L0.9099,0.6065 L0.9099,0.6841 L1,0.6841 L1,0.6065 Z M1,0.711 L0.9099,0.711 L0.9099,0.7886 L1,0.7886 L1,0.711 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z",type:0}},"":{type:5,data:{d:"M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0.8574,0.3217 L0.8574,0.4098 L0.9691,0.4098 L0.9691,0.3217 L0.8574,0.3217 Z M0.8419,0.2978 L0.9846,0.2978 Q0.9892,0.2978,0.9928,0.2997 T0.9982,0.3043 T1,0.31 L1,0.4216 Q1,0.427,0.9951,0.4304 T0.9846,0.4337 L0.8419,0.4337 Q0.8352,0.4337,0.8308,0.4299 T0.8265,0.4216 L0.8265,0.31 Q0.8265,0.3062,0.829,0.3035 T0.835,0.2993 T0.8419,0.2978 Z M0.8574,0.888 L0.8574,0.9757 L0.9691,0.9757 L0.9691,0.888 L0.8574,0.888 Z M0.8419,0.8637 L0.9846,0.8637 Q0.9892,0.8637,0.9928,0.8658 T0.9982,0.8706 T1,0.8758 L1,0.9878 Q1,0.9929,0.9951,0.9964 T0.9846,1 L0.8419,1 Q0.8352,1,0.8308,0.996 T0.8265,0.9878 L0.8265,0.8758 Q0.8265,0.8708,0.8314,0.8672 T0.8419,0.8637 Z M0.6509,0.169 L0.6509,0.2706 L0.7806,0.2706 L0.7806,0.169 L0.6509,0.169 Z M0.6442,0.159 L0.7868,0.159 Q0.7899,0.159,0.7917,0.1604 T0.7935,0.164 L0.7935,0.2756 Q0.7935,0.2781,0.7915,0.2796 T0.7868,0.281 L0.6442,0.281 Q0.6416,0.281,0.6395,0.2794 T0.6375,0.2756 L0.6375,0.164 Q0.6375,0.1619,0.6398,0.1604 T0.6442,0.159 Z M0.6632,0.6044 L0.6632,0.6858 L0.7678,0.6858 L0.7678,0.6044 L0.6632,0.6044 Z M0.6442,0.5742 L0.7868,0.5742 Q0.7951,0.5742,0.8007,0.5791 T0.8064,0.5893 L0.8064,0.7013 Q0.8064,0.7076,0.8002,0.712 T0.7868,0.7164 L0.6442,0.7164 Q0.6359,0.7164,0.6305,0.7114 T0.6251,0.7013 L0.6251,0.5893 Q0.6251,0.5847,0.6282,0.5812 T0.6357,0.5759 T0.6442,0.5742 Z M0.4264,0.164 L0.5695,0.164 L0.5695,0.2756 L0.4264,0.2756 L0.4264,0.164 Z M0.4325,0.315 L0.4325,0.4165 L0.5628,0.4165 L0.5628,0.315 L0.4325,0.315 Z M0.4264,0.3049 L0.5695,0.3049 Q0.5721,0.3049,0.5739,0.3064 T0.5757,0.31 L0.5757,0.4216 Q0.5757,0.4237,0.5736,0.4251 T0.5695,0.4266 L0.4264,0.4266 Q0.4238,0.4266,0.422,0.4251 T0.4202,0.4216 L0.4202,0.31 Q0.4202,0.3087,0.4207,0.3077 T0.4222,0.306 T0.4243,0.3052 T0.4264,0.3049 Z M0.4264,0.4476 L0.5695,0.4476 L0.5695,0.5596 L0.4264,0.5596 L0.4264,0.4476 Z M0.4392,0.7408 L0.4392,0.8326 L0.5561,0.8326 L0.5561,0.7408 L0.4392,0.7408 Z M0.4264,0.7206 L0.5695,0.7206 Q0.5747,0.7206,0.5783,0.724 T0.5819,0.7307 L0.5819,0.8427 Q0.5819,0.8469,0.5778,0.8498 T0.5695,0.8528 L0.4264,0.8528 Q0.4207,0.8528,0.4171,0.8496 T0.4135,0.8427 L0.4135,0.7307 Q0.4135,0.7265,0.4176,0.7236 T0.4264,0.7206 Z M0.2178,0.164 L0.3605,0.164 L0.3605,0.2756 L0.2178,0.2756 L0.2178,0.164 Z M0.2178,0.31 L0.3605,0.31 L0.3605,0.4216 L0.2178,0.4216 L0.2178,0.31 Z M0.2178,0.5893 L0.3605,0.5893 L0.3605,0.7013 L0.2178,0.7013 L0.2178,0.5893 Z M0.2178,0.7307 L0.3605,0.7307 L0.3605,0.8427 L0.2178,0.8427 L0.2178,0.7307 Z M0.2178,0.8758 L0.3605,0.8758 L0.3605,0.9878 L0.2178,0.9878 L0.2178,0.8758 Z M0,0.164 L0.1432,0.164 L0.1432,0.2756 L0,0.2756 L0,0.164 Z M0,0.31 L0.1432,0.31 L0.1432,0.4216 L0,0.4216 L0,0.31 Z M0,0.4476 L0.1432,0.4476 L0.1432,0.5596 L0,0.5596 L0,0.4476 Z M0,0.5893 L0.1432,0.5893 L0.1432,0.7013 L0,0.7013 L0,0.5893 Z M0,0.7307 L0.1432,0.7307 L0.1432,0.8427 L0,0.8427 L0,0.7307 Z M0,0.8758 L0.1432,0.8758 L0.1432,0.9878 L0,0.9878 L0,0.8758 Z",type:0}},"":{type:5,data:{d:"M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M0.1432,0.3217 L0.1432,0.4098 L0.0309,0.4098 L0.0309,0.3217 L0.1432,0.3217 Z M0.1586,0.2978 L0.0154,0.2978 Q0.0088,0.2978,0.0044,0.3016 T0,0.31 L0,0.4216 Q0,0.427,0.0049,0.4304 T0.0154,0.4337 L0.1586,0.4337 Q0.1648,0.4337,0.1694,0.4299 T0.174,0.4216 L0.174,0.31 Q0.174,0.3062,0.1715,0.3035 T0.1653,0.2993 T0.1586,0.2978 Z M0.1432,0.888 L0.1432,0.9757 L0.0309,0.9757 L0.0309,0.888 L0.1432,0.888 Z M0.1586,0.8637 L0.0154,0.8637 Q0.0108,0.8637,0.0072,0.8658 T0.0018,0.8706 T0,0.8758 L0,0.9878 Q0,0.9929,0.0049,0.9964 T0.0154,1 L0.1586,1 Q0.1648,1,0.1694,0.996 T0.174,0.9878 L0.174,0.8758 Q0.174,0.8725,0.1715,0.8695 T0.1653,0.8651 T0.1586,0.8637 Z M0.3496,0.169 L0.3496,0.2706 L0.2194,0.2706 L0.2194,0.169 L0.3496,0.169 Z M0.3563,0.159 L0.2132,0.159 Q0.2106,0.159,0.2088,0.1604 T0.207,0.164 L0.207,0.2756 Q0.207,0.2781,0.2091,0.2796 T0.2132,0.281 L0.3563,0.281 Q0.3589,0.281,0.3607,0.2794 T0.3625,0.2756 L0.3625,0.164 Q0.3625,0.1619,0.3605,0.1604 T0.3563,0.159 Z M0.3368,0.6044 L0.3368,0.6858 L0.2327,0.6858 L0.2327,0.6044 L0.3368,0.6044 Z M0.3563,0.5742 L0.2132,0.5742 Q0.2049,0.5742,0.1993,0.5791 T0.1936,0.5893 L0.1936,0.7013 Q0.1936,0.7076,0.2001,0.712 T0.2132,0.7164 L0.3563,0.7164 Q0.3641,0.7164,0.3697,0.7114 T0.3754,0.7013 L0.3754,0.5893 Q0.3754,0.5847,0.3723,0.5812 T0.3648,0.5759 T0.3563,0.5742 Z M0.5736,0.164 L0.431,0.164 L0.431,0.2756 L0.5736,0.2756 L0.5736,0.164 Z M0.5675,0.315 L0.5675,0.4165 L0.4377,0.4165 L0.4377,0.315 L0.5675,0.315 Z M0.5736,0.3049 L0.431,0.3049 Q0.4284,0.3049,0.4264,0.3064 T0.4243,0.31 L0.4243,0.4216 Q0.4243,0.4237,0.4266,0.4251 T0.431,0.4266 L0.5736,0.4266 Q0.5767,0.4266,0.5785,0.4251 T0.5803,0.4216 L0.5803,0.31 Q0.5803,0.3087,0.5798,0.3077 T0.5783,0.306 T0.5762,0.3052 T0.5736,0.3049 Z M0.5736,0.4476 L0.431,0.4476 L0.431,0.5596 L0.5736,0.5596 L0.5736,0.4476 Z M0.5613,0.7408 L0.5613,0.8326 L0.4439,0.8326 L0.4439,0.7408 L0.5613,0.7408 Z M0.5736,0.7206 L0.431,0.7206 Q0.4253,0.7206,0.4217,0.724 T0.4181,0.7307 L0.4181,0.8427 Q0.4181,0.8469,0.4222,0.8498 T0.431,0.8528 L0.5736,0.8528 Q0.5778,0.8528,0.5808,0.8511 T0.5855,0.8471 T0.587,0.8427 L0.587,0.7307 Q0.587,0.7265,0.5826,0.7236 T0.5736,0.7206 Z M0.7827,0.164 L0.6395,0.164 L0.6395,0.2756 L0.7827,0.2756 L0.7827,0.164 Z M0.7827,0.31 L0.6395,0.31 L0.6395,0.4216 L0.7827,0.4216 L0.7827,0.31 Z M0.7827,0.5893 L0.6395,0.5893 L0.6395,0.7013 L0.7827,0.7013 L0.7827,0.5893 Z M0.7827,0.7307 L0.6395,0.7307 L0.6395,0.8427 L0.7827,0.8427 L0.7827,0.7307 Z M0.7827,0.8758 L0.6395,0.8758 L0.6395,0.9878 L0.7827,0.9878 L0.7827,0.8758 Z M1,0.164 L0.8574,0.164 L0.8574,0.2756 L1,0.2756 L1,0.164 Z M1,0.31 L0.8574,0.31 L0.8574,0.4216 L1,0.4216 L1,0.31 Z M1,0.4476 L0.8574,0.4476 L0.8574,0.5596 L1,0.5596 L1,0.4476 Z M1,0.5893 L0.8574,0.5893 L0.8574,0.7013 L1,0.7013 L1,0.5893 Z M1,0.7307 L0.8574,0.7307 L0.8574,0.8427 L1,0.8427 L1,0.7307 Z M1,0.8758 L0.8574,0.8758 L0.8574,0.9878 L1,0.9878 L1,0.8758 Z",type:0}},"":{type:5,data:{d:"M0.0008,0.9979 Q0.0008,1,0.0032,1 Q0.0482,1,0.0602,0.9975 Q0.0667,0.9963,0.0847,0.9917 T0.1084,0.9859 Q0.1124,0.9851,0.1221,0.9851 L0.1253,0.9851 L0.1285,0.9851 Q0.1518,0.9851,0.1655,0.9809 Q0.1695,0.9801,0.1731,0.9788 T0.1791,0.9766 T0.1839,0.9747 T0.1888,0.9732 L0.1936,0.972 T0.1988,0.9712 T0.2048,0.971 L0.2096,0.971 Q0.2201,0.9714,0.2233,0.9714 T0.2345,0.9693 Q0.2386,0.9685,0.2502,0.9674 T0.2715,0.966 L0.2811,0.9656 Q0.2916,0.9647,0.2916,0.9573 Q0.2916,0.954,0.2892,0.9515 T0.2819,0.9486 Q0.2755,0.9482,0.2683,0.9482 Q0.2643,0.9482,0.2582,0.9484 T0.2498,0.9486 Q0.2426,0.9486,0.2402,0.9473 Q0.2386,0.9469,0.2317,0.943 T0.2161,0.937 Q0.2104,0.9357,0.1992,0.933 T0.1831,0.9291 Q0.1807,0.9287,0.1783,0.9285 T0.1739,0.928 T0.1703,0.9276 T0.1675,0.9272 T0.1655,0.9266 T0.1639,0.9253 Q0.1598,0.9216,0.1462,0.9216 L0.143,0.9216 L0.1398,0.9216 L0.1365,0.9216 Q0.1197,0.9195,0.1197,0.9154 L0.1197,0.9154 Q0.1205,0.9121,0.1373,0.9088 T0.1703,0.9054 Q0.1791,0.9054,0.1863,0.904 T0.1984,0.9011 T0.2064,0.8992 Q0.2402,0.8967,0.2506,0.8934 Q0.2538,0.8926,0.2602,0.8899 T0.2707,0.8872 Q0.2715,0.8872,0.2731,0.8876 Q0.2795,0.8884,0.2884,0.8884 Q0.3068,0.8884,0.3092,0.8826 L0.3092,0.8797 Q0.3092,0.8772,0.3076,0.8747 T0.3044,0.8706 T0.3028,0.8681 Q0.3076,0.8656,0.3237,0.8656 Q0.3317,0.8656,0.3622,0.8623 Q0.3655,0.8623,0.3867,0.8617 T0.4217,0.8602 Q0.4241,0.8598,0.4289,0.8598 L0.4345,0.8598 L0.441,0.8598 Q0.4578,0.8598,0.4667,0.8586 Q0.4739,0.8573,0.4851,0.8567 T0.5004,0.8552 Q0.5012,0.8552,0.5108,0.855 T0.5269,0.853 T0.5333,0.8461 Q0.5333,0.8445,0.5325,0.8432 T0.5309,0.8409 T0.5289,0.8391 T0.5261,0.8378 L0.5229,0.837 T0.5185,0.8362 T0.5133,0.8355 T0.5076,0.8349 T0.502,0.8341 Q0.4932,0.8324,0.4859,0.8324 Q0.4827,0.8324,0.4787,0.8328 T0.4731,0.8333 Q0.4707,0.8333,0.4683,0.8324 Q0.4482,0.825,0.4394,0.825 Q0.4369,0.825,0.4345,0.8254 Q0.4225,0.827,0.4153,0.827 Q0.4088,0.827,0.4064,0.8258 Q0.3976,0.8204,0.3751,0.8204 Q0.3703,0.8204,0.3667,0.8214 T0.3622,0.8225 Q0.3598,0.8225,0.3558,0.82 Q0.351,0.8175,0.3402,0.8167 T0.3177,0.8156 T0.3028,0.8146 L0.2956,0.8121 T0.2871,0.8094 T0.2771,0.8073 T0.2635,0.8063 Q0.261,0.8063,0.2586,0.8067 Q0.2378,0.8071,0.2273,0.8071 Q0.2072,0.8071,0.2024,0.8051 Q0.1976,0.8034,0.1932,0.8026 T0.1867,0.8013 T0.1847,0.8001 L0.1847,0.7993 Q0.1855,0.7984,0.1855,0.7978 T0.1859,0.7966 T0.1867,0.7959 T0.188,0.7955 T0.1896,0.7951 T0.1916,0.7949 T0.1944,0.7945 T0.1984,0.7941 T0.2032,0.7939 Q0.212,0.7926,0.2325,0.7918 T0.2562,0.7905 Q0.2691,0.7893,0.2787,0.7847 Q0.2835,0.7827,0.2968,0.7791 T0.3149,0.7756 Q0.3189,0.7756,0.3305,0.7733 T0.3446,0.771 L0.3454,0.771 Q0.3462,0.771,0.3498,0.7721 T0.3574,0.774 T0.3655,0.7748 Q0.3703,0.7748,0.3743,0.7731 T0.3823,0.7708 T0.3916,0.77 T0.3992,0.7694 Q0.4008,0.7694,0.4076,0.7688 T0.4205,0.7675 L0.4305,0.7665 Q0.4345,0.7657,0.4402,0.7617 T0.4458,0.7545 Q0.4458,0.7503,0.4353,0.7487 Q0.4297,0.7478,0.4209,0.7462 T0.4044,0.7433 T0.3904,0.7416 L0.3847,0.7416 Q0.3807,0.7416,0.3747,0.7422 T0.3663,0.7428 T0.3606,0.7424 Q0.3582,0.742,0.3562,0.7418 T0.3522,0.7412 T0.3482,0.7406 T0.3446,0.7401 T0.341,0.7397 T0.3373,0.7393 T0.3341,0.7391 L0.3293,0.7391 Q0.3277,0.7395,0.3253,0.7395 Q0.3205,0.7395,0.3052,0.7387 T0.2835,0.7379 Q0.2755,0.7379,0.253,0.7325 Q0.2506,0.7316,0.2462,0.731 T0.2382,0.7296 T0.2305,0.7271 T0.2181,0.7234 T0.2048,0.7196 T0.2,0.7155 Q0.2,0.7142,0.2016,0.713 Q0.208,0.7059,0.2285,0.6978 T0.2618,0.6898 Q0.2739,0.6898,0.2831,0.6848 T0.2956,0.6794 Q0.3028,0.6786,0.3289,0.6765 T0.3566,0.674 Q0.3582,0.674,0.3855,0.6723 T0.4406,0.6688 T0.4755,0.6653 Q0.5004,0.6607,0.5124,0.6599 L0.5165,0.6599 Q0.5213,0.6599,0.5277,0.6605 T0.5373,0.6611 Q0.543,0.6611,0.5494,0.6599 Q0.592,0.6524,0.6,0.6524 Q0.6064,0.6524,0.61,0.6499 T0.6161,0.6474 Q0.6177,0.6474,0.6261,0.6495 T0.6418,0.6516 L0.6482,0.6516 Q0.653,0.6512,0.6651,0.6506 T0.6876,0.6493 T0.7084,0.6477 T0.7229,0.645 Q0.7269,0.6437,0.7333,0.6437 Q0.7373,0.6437,0.7458,0.6443 T0.7574,0.645 Q0.7614,0.645,0.7631,0.6445 T0.7655,0.6441 Q0.7671,0.6441,0.7723,0.6448 T0.7823,0.6454 Q0.788,0.6454,0.7952,0.6445 Q0.8233,0.6408,0.8723,0.6367 Q0.9012,0.6342,0.9092,0.6342 L0.9165,0.6342 L0.9205,0.6342 Q0.9277,0.6342,0.9382,0.6323 T0.9526,0.6304 Q0.9534,0.6304,0.9594,0.6307 T0.9695,0.6309 Q0.9976,0.6309,0.9992,0.6234 Q1,0.6201,1,0.618 Q1,0.6089,0.9775,0.608 Q0.9703,0.6076,0.9582,0.6076 Q0.9518,0.6076,0.9394,0.6078 T0.9213,0.608 Q0.9036,0.608,0.8972,0.6072 Q0.894,0.6068,0.8871,0.6058 T0.8751,0.6039 T0.8627,0.6024 T0.8506,0.6018 L0.8474,0.6018 Q0.841,0.6022,0.8281,0.6041 T0.8112,0.606 Q0.8096,0.606,0.808,0.6056 Q0.7783,0.6002,0.7647,0.6002 Q0.7598,0.6002,0.7454,0.6006 T0.7261,0.601 Q0.7221,0.601,0.7145,0.6022 T0.7036,0.6035 Q0.6996,0.6035,0.6964,0.6022 Q0.6723,0.5902,0.6442,0.5902 Q0.6426,0.5902,0.6386,0.5904 T0.6337,0.5906 Q0.6273,0.5906,0.6249,0.5894 L0.6177,0.5856 T0.6116,0.5838 T0.6016,0.5825 T0.5855,0.5823 Q0.5719,0.5823,0.5574,0.5798 T0.5373,0.5774 L0.5341,0.5774 Q0.5293,0.5778,0.5213,0.5786 T0.51,0.5794 Q0.5036,0.5794,0.4988,0.5769 Q0.4932,0.574,0.4771,0.574 Q0.4723,0.574,0.4631,0.5745 T0.4498,0.5749 Q0.4418,0.5749,0.4394,0.5736 Q0.4353,0.5724,0.4305,0.5715 T0.4225,0.5707 T0.4145,0.5705 T0.4056,0.5699 Q0.4008,0.5691,0.398,0.5689 T0.394,0.5689 T0.3908,0.5686 T0.3855,0.5674 Q0.3831,0.567,0.3811,0.5662 T0.3779,0.5645 T0.3743,0.563 T0.3695,0.5624 Q0.3655,0.5624,0.3594,0.5635 T0.3494,0.5645 T0.3406,0.5635 T0.3325,0.5624 Q0.3309,0.5624,0.3301,0.5628 L0.3141,0.5628 Q0.2635,0.5628,0.2506,0.5612 Q0.2474,0.5608,0.2434,0.5608 Q0.2402,0.5608,0.2349,0.561 T0.2273,0.5612 Q0.2209,0.5612,0.2177,0.5595 Q0.2145,0.557,0.2145,0.5558 Q0.2169,0.5537,0.2345,0.5521 Q0.2876,0.5475,0.3028,0.5425 Q0.3068,0.5413,0.3149,0.5396 T0.3309,0.5367 T0.3478,0.5338 T0.3606,0.5309 Q0.3631,0.5305,0.3755,0.5259 T0.3968,0.5214 Q0.3992,0.5214,0.4024,0.5218 T0.4096,0.5222 Q0.4161,0.5222,0.4213,0.5209 T0.4313,0.5185 T0.4402,0.5172 Q0.4418,0.5168,0.447,0.5168 L0.4558,0.5168 T0.4639,0.5162 T0.4707,0.5141 T0.4731,0.5102 Q0.4731,0.5089,0.4723,0.5081 Q0.4699,0.5019,0.4627,0.4994 T0.4369,0.4965 Q0.4273,0.4961,0.4193,0.4946 T0.4048,0.4915 T0.3952,0.4894 Q0.392,0.4886,0.388,0.4886 Q0.3847,0.4886,0.3775,0.4892 T0.3647,0.4898 Q0.3598,0.4898,0.355,0.4894 Q0.3165,0.4861,0.3141,0.4853 Q0.2779,0.4774,0.2715,0.4774 Q0.2699,0.4774,0.2683,0.4776 T0.2627,0.4778 Q0.2498,0.4778,0.2281,0.4762 Q0.2241,0.4749,0.2233,0.4737 Q0.2249,0.472,0.2321,0.4708 Q0.2402,0.4695,0.2554,0.4695 L0.2675,0.4695 L0.2795,0.4695 Q0.294,0.4695,0.2996,0.4679 Q0.3141,0.4629,0.3373,0.4616 Q0.3414,0.4612,0.3466,0.4606 T0.3582,0.4589 T0.3687,0.4562 T0.3727,0.4525 Q0.3727,0.4509,0.3703,0.4492 Q0.3639,0.4455,0.3474,0.4419 T0.3213,0.4384 L0.3157,0.4384 L0.31,0.4384 Q0.294,0.4384,0.2859,0.4363 Q0.2795,0.4347,0.2691,0.4347 L0.2618,0.4347 L0.2546,0.4347 Q0.2458,0.4347,0.2394,0.4338 Q0.2257,0.4318,0.2193,0.4303 T0.2092,0.4266 T0.2052,0.4224 T0.2024,0.4154 T0.196,0.4052 Q0.1912,0.3994,0.1867,0.3949 T0.1807,0.3884 T0.1791,0.3849 Q0.1791,0.3824,0.1839,0.3745 Q0.1871,0.3687,0.1944,0.3637 T0.2072,0.3561 T0.2221,0.3501 T0.2321,0.3463 Q0.2353,0.3455,0.241,0.3445 T0.2522,0.3428 T0.2639,0.3411 T0.2731,0.3384 T0.2956,0.3351 T0.3197,0.3326 Q0.3213,0.3322,0.3237,0.3322 T0.3325,0.3331 T0.3414,0.3339 Q0.3478,0.3339,0.3518,0.3314 Q0.3606,0.3264,0.3703,0.3264 Q0.3775,0.3264,0.3847,0.3293 Q0.3888,0.331,0.396,0.331 Q0.4024,0.331,0.4116,0.3295 T0.4265,0.3266 T0.4414,0.3227 T0.4514,0.3202 Q0.4578,0.3181,0.4839,0.3152 T0.5112,0.3123 T0.5177,0.3119 T0.5285,0.3111 T0.5398,0.3096 T0.549,0.3065 T0.5526,0.3015 Q0.5526,0.2999,0.5518,0.2982 Q0.547,0.2903,0.5386,0.2883 T0.5165,0.2862 Q0.5116,0.2862,0.5056,0.2866 T0.4972,0.287 Q0.4884,0.287,0.4787,0.2833 Q0.4594,0.2758,0.4265,0.2758 Q0.4169,0.2758,0.4129,0.2766 Q0.4112,0.2771,0.4104,0.2771 Q0.4088,0.2771,0.4056,0.2748 T0.3968,0.2721 Q0.3695,0.2696,0.359,0.2659 Q0.355,0.2646,0.351,0.2646 T0.3426,0.2661 T0.3365,0.2675 T0.3333,0.2659 Q0.3277,0.2605,0.3116,0.2553 T0.2859,0.2501 L0.2843,0.2501 L0.2819,0.2501 Q0.2779,0.2501,0.2723,0.2493 T0.2586,0.2472 T0.2474,0.2455 Q0.1799,0.236,0.1719,0.236 Q0.1663,0.236,0.1606,0.235 T0.1502,0.2316 T0.1454,0.2265 Q0.1454,0.2223,0.1558,0.2173 Q0.1679,0.2115,0.1859,0.2092 T0.2185,0.207 T0.2353,0.2057 Q0.2369,0.2049,0.2502,0.2041 T0.2799,0.2022 T0.302,0.2007 Q0.3084,0.1999,0.3245,0.1954 T0.347,0.1908 Q0.3502,0.1908,0.3534,0.1916 Q0.3663,0.197,0.388,0.197 Q0.404,0.197,0.4088,0.1929 Q0.4096,0.192,0.4108,0.192 T0.4177,0.1945 T0.4277,0.197 T0.4369,0.1949 Q0.4442,0.1908,0.4843,0.1875 Q0.4892,0.1871,0.4968,0.1839 T0.5084,0.1808 L0.5108,0.1808 Q0.5213,0.1825,0.5454,0.1825 Q0.5823,0.1825,0.5992,0.1779 Q0.6096,0.1746,0.6217,0.1746 Q0.6249,0.1746,0.6309,0.1748 T0.6394,0.175 Q0.6434,0.175,0.6466,0.1746 Q0.6474,0.1746,0.6594,0.1738 T0.6787,0.1713 T0.6892,0.1659 Q0.6924,0.1634,0.6924,0.1593 Q0.6924,0.1547,0.6888,0.151 T0.6795,0.1472 Q0.6779,0.1472,0.6763,0.1477 Q0.6699,0.1493,0.6667,0.1493 Q0.661,0.1493,0.6506,0.1456 Q0.6329,0.1394,0.6129,0.1394 Q0.6048,0.1394,0.5992,0.1377 T0.5888,0.136 T0.5803,0.1377 T0.5751,0.1394 Q0.5711,0.1394,0.5639,0.1369 Q0.5365,0.1273,0.5116,0.1273 Q0.4876,0.1273,0.4667,0.1253 Q0.4627,0.1248,0.4542,0.1211 T0.4337,0.1174 L0.4305,0.1174 Q0.4217,0.1178,0.4153,0.119 T0.408,0.1203 Q0.4056,0.1203,0.4,0.117 Q0.3976,0.1153,0.396,0.1136 T0.3932,0.1112 T0.39,0.1099 T0.3847,0.1095 Q0.3799,0.1095,0.3687,0.1103 Q0.355,0.1112,0.3434,0.1141 T0.3301,0.117 Q0.3261,0.117,0.3229,0.1145 Q0.3181,0.1116,0.3052,0.1087 T0.2787,0.1043 T0.2546,0.1029 Q0.2506,0.1029,0.2474,0.1033 Q0.2418,0.1037,0.2349,0.1054 T0.2249,0.107 Q0.2225,0.107,0.2193,0.1066 Q0.2024,0.1024,0.1863,0.1024 Q0.1823,0.1024,0.1562,0.0971 T0.1269,0.0917 T0.1161,0.0896 T0.106,0.0854 Q0.1076,0.0846,0.11,0.0842 Q0.1173,0.0821,0.1261,0.0821 Q0.1301,0.0821,0.1369,0.0825 T0.1462,0.083 Q0.1494,0.083,0.151,0.0825 T0.1831,0.0813 T0.2201,0.0784 T0.243,0.074 T0.2667,0.0718 Q0.2707,0.0718,0.2755,0.0724 T0.2843,0.073 T0.2936,0.0724 T0.3024,0.0705 T0.3072,0.068 T0.3092,0.0657 T0.3108,0.0647 Q0.3116,0.0643,0.3133,0.0643 L0.3157,0.0643 L0.3181,0.0643 Q0.3446,0.0643,0.3446,0.0556 T0.3205,0.0469 L0.3189,0.0469 Q0.3157,0.0469,0.3157,0.0465 Q0.3084,0.0427,0.2835,0.0386 Q0.2739,0.0369,0.2675,0.0342 T0.2538,0.0299 Q0.249,0.029,0.245,0.029 T0.2378,0.0303 T0.2321,0.0315 Q0.2305,0.0315,0.2273,0.0307 Q0.2201,0.0286,0.2004,0.0272 T0.1775,0.0245 Q0.1735,0.0228,0.1663,0.0226 T0.1522,0.0197 T0.1345,0.0145 T0.1124,0.0079 Q0.0996,0.0033,0.0671,0.0044 T0.0297,0.0041 Q0.0169,0,0.0024,0.0017 Q0,0.0021,0,0.0037 Z M0.0064,0.9954 L0.0056,0.0058 Q0.0177,0.005,0.0281,0.0083 Q0.0345,0.01,0.0675,0.0089 T0.1108,0.012 Q0.1221,0.0162,0.1329,0.0187 T0.1498,0.0236 T0.1606,0.0268 T0.1691,0.0274 T0.1759,0.0286 Q0.1807,0.0303,0.2,0.0315 T0.2257,0.0348 Q0.2305,0.0361,0.2337,0.0361 Q0.2361,0.0361,0.2398,0.0348 T0.2466,0.0336 Q0.249,0.0336,0.2522,0.034 Q0.2586,0.0353,0.2655,0.0382 T0.2827,0.0427 Q0.3068,0.0469,0.3124,0.0502 Q0.3157,0.0523,0.3237,0.0523 L0.3277,0.0523 L0.3309,0.0523 Q0.3398,0.0523,0.3398,0.0556 Q0.3398,0.0581,0.3341,0.0587 T0.3205,0.0593 T0.3092,0.0603 T0.3036,0.0643 T0.2932,0.068 Q0.2884,0.0684,0.2843,0.0684 Q0.2811,0.0684,0.2763,0.0678 T0.2667,0.0672 Q0.2586,0.0672,0.2422,0.0697 T0.2177,0.0747 Q0.2137,0.0759,0.1831,0.0767 T0.1494,0.0784 L0.1474,0.0784 T0.1398,0.078 T0.1285,0.0776 Q0.1173,0.0776,0.1084,0.08 Q0.1004,0.0817,0.1004,0.0846 Q0.1004,0.0867,0.104,0.0888 T0.1124,0.0923 T0.1213,0.0948 T0.1269,0.0958 T0.155,0.1014 T0.1863,0.107 Q0.2016,0.107,0.2185,0.1107 Q0.2217,0.1116,0.2257,0.1116 Q0.2289,0.1116,0.2365,0.1099 T0.2482,0.1074 L0.2538,0.1074 Q0.2683,0.1074,0.2912,0.1107 T0.3197,0.1182 Q0.3245,0.1215,0.3301,0.1215 Q0.3333,0.1215,0.3454,0.1184 T0.3687,0.1145 Q0.3735,0.1141,0.3779,0.1139 T0.3843,0.1134 T0.3871,0.1132 Q0.3896,0.1132,0.39,0.1136 T0.392,0.1161 T0.3968,0.1203 Q0.404,0.1248,0.408,0.1248 Q0.4096,0.1248,0.4165,0.1234 T0.4305,0.1215 L0.4337,0.1215 Q0.4442,0.1215,0.4518,0.1251 T0.4659,0.1294 Q0.4876,0.1319,0.5116,0.1319 T0.5622,0.141 Q0.5703,0.1439,0.5751,0.1439 Q0.5791,0.1439,0.5831,0.1423 T0.5888,0.1406 Q0.592,0.1406,0.5944,0.1412 T0.6012,0.1427 T0.6129,0.1435 Q0.6313,0.1435,0.649,0.1497 Q0.6602,0.1535,0.6667,0.1535 Q0.6715,0.1535,0.6779,0.1522 Q0.6787,0.1518,0.6795,0.1518 Q0.6827,0.1518,0.6847,0.1541 T0.6867,0.1593 Q0.6867,0.1618,0.6851,0.1634 Q0.6835,0.1655,0.6775,0.1667 T0.6598,0.169 T0.6458,0.1705 T0.6402,0.1709 Q0.6378,0.1709,0.6321,0.1705 T0.6225,0.1701 Q0.6096,0.1701,0.5968,0.1738 Q0.5823,0.1779,0.5446,0.1779 Q0.5205,0.1779,0.5116,0.1767 Q0.51,0.1763,0.5084,0.1763 Q0.5028,0.1763,0.4944,0.1796 T0.4835,0.1829 L0.4771,0.1833 T0.4659,0.1844 L0.4546,0.1858 T0.443,0.1881 T0.4337,0.1912 Q0.4321,0.1916,0.4305,0.1916 Q0.4273,0.1916,0.4209,0.1895 T0.412,0.1875 Q0.408,0.1875,0.4056,0.19 Q0.4024,0.1925,0.388,0.1925 Q0.3663,0.1925,0.3558,0.1879 Q0.3518,0.1862,0.3462,0.1862 Q0.3373,0.1862,0.3213,0.191 T0.3012,0.1962 Q0.2964,0.1966,0.2799,0.1976 T0.2494,0.1997 T0.2313,0.2024 L0.2313,0.2024 L0.2293,0.2024 L0.2261,0.2024 L0.2217,0.2024 Q0.204,0.2024,0.1851,0.2047 T0.1526,0.2136 Q0.1406,0.2198,0.1406,0.226 Q0.1406,0.2319,0.1518,0.2362 T0.1719,0.2406 Q0.1791,0.2406,0.2466,0.2497 Q0.2498,0.2501,0.2574,0.2516 T0.2711,0.2538 T0.2819,0.2547 L0.2851,0.2547 Q0.2851,0.2543,0.2859,0.2543 Q0.294,0.2543,0.3096,0.2592 T0.3285,0.2684 Q0.3317,0.2721,0.3373,0.2721 Q0.3406,0.2721,0.3462,0.2708 T0.3542,0.2696 Q0.3558,0.2696,0.3574,0.27 Q0.3679,0.2737,0.3968,0.2762 Q0.3984,0.2766,0.4004,0.2779 L0.4044,0.2804 T0.4096,0.2816 Q0.412,0.2816,0.4145,0.2808 Q0.4177,0.2804,0.4249,0.2804 Q0.4586,0.2804,0.4763,0.287 Q0.4867,0.2912,0.4972,0.2912 Q0.5004,0.2912,0.5064,0.2908 T0.5165,0.2903 Q0.5293,0.2903,0.5361,0.2922 T0.547,0.3003 L0.547,0.3011 Q0.547,0.3036,0.5414,0.3049 T0.5249,0.3067 T0.51,0.3082 Q0.5092,0.3082,0.4831,0.3111 T0.4498,0.3161 Q0.408,0.3264,0.3952,0.3264 Q0.3896,0.3264,0.3863,0.3252 Q0.3783,0.3223,0.3703,0.3223 Q0.359,0.3223,0.3494,0.3277 Q0.347,0.3293,0.343,0.3293 Q0.3414,0.3293,0.3353,0.3285 T0.3253,0.3277 T0.3181,0.3285 Q0.3149,0.3289,0.2952,0.3306 T0.2699,0.3347 Q0.2675,0.3364,0.2598,0.3372 T0.2434,0.3395 T0.2297,0.3426 L0.2205,0.3461 T0.2048,0.3523 T0.1908,0.3606 T0.1791,0.3725 Q0.1743,0.3803,0.1743,0.3845 Q0.1743,0.3874,0.1763,0.3901 T0.1827,0.3976 T0.1912,0.4073 T0.198,0.4189 T0.2032,0.4276 T0.2137,0.4326 T0.2386,0.438 Q0.2458,0.4392,0.2562,0.4392 L0.2639,0.4392 L0.2699,0.4392 Q0.2795,0.4392,0.2843,0.4405 Q0.2932,0.443,0.3092,0.443 L0.3157,0.443 L0.3213,0.443 Q0.3293,0.443,0.3458,0.4463 T0.3671,0.4525 Q0.3598,0.4558,0.3373,0.4571 Q0.3124,0.4587,0.298,0.4637 Q0.294,0.465,0.2811,0.465 L0.2703,0.465 L0.2586,0.465 Q0.241,0.465,0.2305,0.4666 Q0.2185,0.4687,0.2185,0.4728 Q0.2185,0.4745,0.2205,0.4764 T0.2241,0.4791 L0.2265,0.4803 L0.2273,0.4803 Q0.2506,0.4824,0.261,0.4824 Q0.2675,0.4824,0.2699,0.482 Q0.2699,0.4815,0.2707,0.4815 Q0.2771,0.4815,0.3124,0.4898 Q0.3173,0.4907,0.3229,0.4913 T0.3386,0.4925 T0.3542,0.4936 T0.3663,0.494 Q0.3711,0.494,0.3787,0.4936 T0.3888,0.4932 Q0.392,0.4932,0.3944,0.4936 T0.4032,0.4959 T0.4181,0.499 T0.4369,0.5006 Q0.449,0.501,0.455,0.5021 T0.4639,0.5048 T0.4675,0.5093 L0.4675,0.5097 Q0.4675,0.5106,0.4663,0.511 T0.4622,0.5116 L0.4566,0.512 T0.4486,0.5124 T0.4402,0.5127 Q0.4345,0.5131,0.4257,0.5153 T0.4096,0.5176 Q0.4064,0.5176,0.4024,0.5172 L0.3968,0.5172 Q0.3863,0.5172,0.3731,0.522 T0.359,0.5268 Q0.355,0.528,0.3317,0.5321 T0.3004,0.5388 Q0.2867,0.5433,0.2337,0.5479 Q0.2096,0.55,0.2096,0.5562 Q0.2096,0.5591,0.2145,0.5628 Q0.2193,0.5662,0.2289,0.5662 Q0.2313,0.5662,0.2373,0.5657 T0.2458,0.5653 L0.2498,0.5653 Q0.2635,0.5674,0.3124,0.5674 Q0.3261,0.5674,0.3301,0.567 L0.3317,0.567 Q0.3341,0.567,0.3394,0.568 T0.3486,0.5691 Q0.3542,0.5691,0.3606,0.5678 T0.3699,0.5666 T0.3759,0.5686 T0.3839,0.5718 T0.3908,0.573 T0.3964,0.5734 T0.4048,0.5742 T0.4165,0.5751 T0.4277,0.5757 T0.4369,0.5778 Q0.4426,0.5794,0.4538,0.5794 Q0.4586,0.5794,0.4679,0.5792 T0.4811,0.579 Q0.4924,0.579,0.4956,0.5807 Q0.502,0.584,0.5108,0.584 L0.5137,0.584 T0.5173,0.5838 L0.5205,0.5834 L0.5237,0.583 L0.5269,0.5825 T0.5305,0.5821 T0.5341,0.5819 L0.5365,0.5819 Q0.5414,0.5819,0.5562,0.5842 T0.5855,0.5865 Q0.5912,0.5865,0.5948,0.5867 T0.6012,0.5869 T0.6064,0.5873 T0.61,0.5877 T0.6133,0.5886 T0.6157,0.5896 L0.6185,0.591 T0.6217,0.5931 Q0.6265,0.5952,0.6369,0.5952 L0.6418,0.5952 L0.6466,0.5952 Q0.6723,0.5952,0.6932,0.606 Q0.698,0.608,0.7044,0.608 Q0.7084,0.608,0.7161,0.6068 T0.7261,0.6056 Q0.7309,0.6056,0.7454,0.6051 T0.7647,0.6047 Q0.7775,0.6047,0.8072,0.6101 Q0.8096,0.6105,0.812,0.6105 Q0.8177,0.6105,0.8297,0.6085 T0.8474,0.606 L0.8506,0.606 Q0.8562,0.606,0.8647,0.607 T0.8831,0.6095 T0.8964,0.6118 Q0.9036,0.6122,0.9221,0.6122 L0.9406,0.6122 L0.959,0.6122 L0.9767,0.6122 Q0.9952,0.613,0.9952,0.6172 Q0.9952,0.6176,0.9944,0.6221 Q0.9936,0.6259,0.9574,0.6259 L0.955,0.6259 L0.9526,0.6259 Q0.9478,0.6259,0.9373,0.628 T0.9205,0.63 L0.9173,0.63 Q0.9133,0.6296,0.9084,0.6296 Q0.9004,0.6296,0.8715,0.6321 Q0.8233,0.6367,0.7944,0.64 Q0.7888,0.6408,0.7839,0.6408 T0.7739,0.6402 T0.7671,0.6396 Q0.7639,0.6396,0.7622,0.64 Q0.7614,0.6404,0.759,0.6404 Q0.7574,0.6404,0.749,0.6398 T0.7357,0.6392 Q0.7261,0.6392,0.7213,0.6412 Q0.7173,0.6425,0.7032,0.6437 T0.6711,0.6458 T0.6474,0.647 Q0.645,0.6474,0.6418,0.6474 Q0.6353,0.6474,0.6277,0.6454 T0.6161,0.6433 Q0.6104,0.6433,0.6072,0.6456 T0.6,0.6479 Q0.5912,0.6479,0.5486,0.6557 Q0.5422,0.657,0.5373,0.657 Q0.5349,0.657,0.5289,0.6564 T0.5173,0.6557 L0.5124,0.6557 Q0.4996,0.6562,0.4739,0.6611 Q0.4675,0.6624,0.4398,0.6645 T0.3851,0.668 T0.3558,0.6698 Q0.355,0.6698,0.3285,0.6721 T0.2948,0.6752 Q0.2892,0.6761,0.2843,0.6786 T0.2747,0.6831 T0.2618,0.6852 Q0.253,0.6852,0.2394,0.6893 T0.2141,0.6993 T0.1968,0.7105 Q0.1944,0.713,0.1944,0.7155 Q0.1944,0.7196,0.2012,0.7229 T0.2177,0.7285 T0.2281,0.7308 Q0.2321,0.7329,0.2365,0.7339 T0.245,0.7356 T0.2514,0.7366 Q0.2522,0.737,0.2554,0.7379 T0.2614,0.7393 T0.2679,0.7406 T0.2755,0.7416 T0.2835,0.742 Q0.29,0.742,0.3048,0.7428 T0.3253,0.7437 L0.3301,0.7437 L0.3333,0.7437 Q0.3341,0.7437,0.3598,0.7466 Q0.3639,0.7474,0.3671,0.7474 T0.3759,0.7466 T0.3855,0.7457 Q0.388,0.7457,0.3904,0.7462 Q0.3984,0.7466,0.4137,0.7495 T0.4349,0.7532 T0.441,0.7557 Q0.441,0.7599,0.4289,0.7619 Q0.4257,0.7628,0.4197,0.7634 T0.4072,0.7644 T0.3984,0.7652 Q0.3968,0.7652,0.3912,0.7655 T0.3811,0.7665 T0.3719,0.7694 Q0.3695,0.7702,0.3655,0.7702 T0.3546,0.7686 T0.3462,0.7665 L0.3446,0.7665 Q0.3406,0.7665,0.3293,0.769 T0.3149,0.7715 Q0.3084,0.7715,0.2952,0.7748 T0.2763,0.781 Q0.2675,0.7847,0.2554,0.7864 Q0.253,0.7864,0.2325,0.7874 T0.2024,0.7893 Q0.2008,0.7893,0.1984,0.7897 T0.1948,0.7901 T0.1916,0.7903 T0.1888,0.7908 T0.1867,0.7914 T0.1847,0.7922 L0.1831,0.793 T0.1819,0.7943 L0.1811,0.7959 T0.1799,0.7976 Q0.1791,0.7993,0.1791,0.8001 Q0.1791,0.8034,0.1831,0.8051 T0.1932,0.8078 T0.2,0.8092 Q0.2072,0.8117,0.2257,0.8117 Q0.2337,0.8117,0.2586,0.8109 L0.2635,0.8109 Q0.2707,0.8109,0.2767,0.8117 T0.2859,0.8136 T0.2936,0.8163 T0.3012,0.8187 Q0.3068,0.82,0.3277,0.8206 T0.3526,0.8237 Q0.3582,0.8266,0.3622,0.8266 Q0.3647,0.8266,0.3687,0.8258 T0.3751,0.825 Q0.396,0.825,0.404,0.8291 Q0.408,0.8316,0.4161,0.8316 Q0.4217,0.8316,0.4353,0.8295 L0.4394,0.8295 Q0.4466,0.8295,0.4659,0.8366 Q0.4699,0.8378,0.4747,0.8378 Q0.4763,0.8378,0.4807,0.8374 T0.4884,0.837 Q0.494,0.837,0.5004,0.8382 Q0.5052,0.8391,0.5124,0.8399 Q0.5221,0.8411,0.5249,0.8422 T0.5277,0.8461 Q0.5277,0.8482,0.5233,0.849 T0.5112,0.8501 T0.4996,0.8511 Q0.4964,0.8519,0.4851,0.8523 T0.4659,0.8544 Q0.4578,0.8557,0.4402,0.8557 L0.4349,0.8557 L0.4297,0.8557 L0.4209,0.8557 Q0.408,0.8565,0.3867,0.8571 T0.3614,0.8582 Q0.3317,0.8611,0.3237,0.8611 L0.3225,0.8611 L0.3213,0.8611 Q0.2972,0.8611,0.2972,0.8681 Q0.2972,0.8714,0.3008,0.8747 T0.3044,0.8805 Q0.3044,0.881,0.3036,0.8814 Q0.3012,0.8839,0.2851,0.8839 Q0.2779,0.8839,0.2739,0.883 Q0.2723,0.8826,0.2699,0.8826 T0.2643,0.8835 T0.259,0.8853 T0.2542,0.8876 T0.249,0.8893 Q0.2386,0.8922,0.2056,0.8946 Q0.2016,0.8951,0.1916,0.898 T0.1703,0.9009 Q0.1518,0.9009,0.1337,0.9044 T0.1149,0.9146 L0.1149,0.9158 Q0.1149,0.9175,0.1157,0.9187 T0.1173,0.921 T0.1201,0.9226 T0.1233,0.9237 T0.1269,0.9245 L0.1305,0.9251 T0.1337,0.9255 L0.1357,0.9258 Q0.139,0.9262,0.1446,0.9262 T0.1542,0.9266 T0.1598,0.9282 Q0.1614,0.9303,0.1647,0.9314 T0.1739,0.9328 T0.1815,0.9336 Q0.1863,0.9345,0.1976,0.9372 T0.2145,0.9415 Q0.2225,0.9432,0.2285,0.9467 T0.2378,0.9515 Q0.2426,0.9531,0.2514,0.9531 Q0.2538,0.9531,0.2602,0.9529 T0.2707,0.9527 Q0.2763,0.9527,0.2815,0.9531 T0.2867,0.9569 T0.2811,0.9614 Q0.2434,0.9627,0.2329,0.9652 Q0.2249,0.9672,0.2241,0.9672 L0.2096,0.9668 L0.2056,0.9668 Q0.2,0.9668,0.196,0.9672 T0.1884,0.9687 T0.1815,0.9708 T0.1731,0.9737 T0.1639,0.9772 Q0.1518,0.9805,0.1285,0.9805 L0.1253,0.9805 Q0.1124,0.9805,0.1076,0.9818 Q0.1012,0.983,0.0831,0.9876 T0.0594,0.9934 Q0.049,0.9954,0.0064,0.9954 Z M0.0032,0.9979 L0.0024,0.0037 Q0.0161,0.0021,0.0289,0.0062 Q0.0337,0.0075,0.0671,0.0066 T0.1116,0.01 T0.1337,0.0166 T0.151,0.022 Q0.1582,0.0249,0.1659,0.0251 T0.1767,0.0265 Q0.1807,0.0278,0.2,0.0292 T0.2265,0.0328 Q0.2305,0.034,0.2329,0.034 T0.239,0.0326 T0.2458,0.0311 T0.253,0.0319 Q0.2578,0.0332,0.2614,0.0342 T0.2667,0.0363 T0.2723,0.0384 T0.2827,0.0406 Q0.3084,0.0448,0.3141,0.0485 Q0.3149,0.0494,0.3225,0.0494 T0.3361,0.0504 T0.3422,0.0554 T0.3357,0.0606 T0.3205,0.0618 T0.31,0.0626 Q0.3092,0.0626,0.3056,0.0659 T0.2932,0.0701 Q0.2884,0.0709,0.2843,0.0709 Q0.2811,0.0709,0.2759,0.0701 T0.2667,0.0693 Q0.2586,0.0693,0.2422,0.0718 T0.2193,0.0763 Q0.2137,0.0784,0.1831,0.079 T0.1502,0.0805 Q0.1494,0.0809,0.147,0.0809 Q0.1454,0.0809,0.1386,0.0803 T0.1277,0.0796 Q0.1173,0.0796,0.1092,0.0821 Q0.1036,0.0834,0.1036,0.0854 Q0.1036,0.0879,0.1129,0.0908 T0.1269,0.0937 Q0.1293,0.0937,0.1558,0.0991 T0.1863,0.1045 Q0.2016,0.1045,0.2193,0.1087 Q0.2225,0.1091,0.2249,0.1091 Q0.2289,0.1091,0.2357,0.1076 T0.2482,0.1054 Q0.2506,0.1049,0.2546,0.1049 Q0.2699,0.1049,0.2924,0.1085 T0.3213,0.1165 Q0.3253,0.119,0.3301,0.119 Q0.3325,0.119,0.3446,0.1161 T0.3687,0.1124 Q0.3831,0.1112,0.3863,0.1112 T0.3908,0.1118 T0.3936,0.1145 T0.3984,0.1186 Q0.4048,0.1228,0.408,0.1228 Q0.4096,0.1228,0.4161,0.1213 T0.4305,0.1195 L0.4337,0.1195 Q0.4426,0.1195,0.4474,0.1211 T0.4566,0.1246 T0.4667,0.1273 Q0.4876,0.1298,0.5116,0.1298 Q0.5365,0.1298,0.5631,0.1389 Q0.5703,0.1414,0.5751,0.1414 Q0.5783,0.1414,0.5819,0.1398 T0.5888,0.1381 Q0.5928,0.1381,0.5984,0.1398 T0.6129,0.1414 Q0.6321,0.1414,0.6498,0.1477 Q0.661,0.1514,0.6667,0.1514 Q0.6707,0.1514,0.6771,0.1501 Q0.6787,0.1497,0.6795,0.1497 Q0.6835,0.1497,0.6863,0.1526 T0.6892,0.1593 Q0.6892,0.1626,0.6876,0.1647 Q0.6851,0.1672,0.6799,0.1686 T0.6695,0.1705 T0.6566,0.1715 T0.6458,0.1725 Q0.6434,0.173,0.6402,0.173 Q0.6378,0.173,0.6317,0.1727 T0.6225,0.1725 Q0.6096,0.1725,0.5984,0.1759 Q0.5823,0.1804,0.5454,0.1804 Q0.5205,0.1804,0.5116,0.1788 L0.5084,0.1788 Q0.5036,0.1788,0.4956,0.1819 T0.4843,0.185 Q0.4434,0.1887,0.4353,0.1929 Q0.4321,0.1945,0.4289,0.1945 T0.4197,0.1922 T0.4112,0.19 T0.4072,0.1912 Q0.4032,0.1949,0.388,0.1949 Q0.3663,0.1949,0.3542,0.19 Q0.351,0.1883,0.3462,0.1883 Q0.3406,0.1883,0.3321,0.1906 T0.3153,0.1954 T0.302,0.1983 Q0.2972,0.1991,0.2859,0.1997 T0.2643,0.201 T0.245,0.2024 T0.2337,0.2041 Q0.2321,0.2045,0.2181,0.2045 T0.1851,0.207 T0.1542,0.2157 Q0.143,0.2211,0.143,0.226 Q0.143,0.2314,0.153,0.2348 T0.1719,0.2381 Q0.1791,0.2381,0.2474,0.2476 Q0.2506,0.248,0.2558,0.2489 T0.2651,0.2505 T0.2739,0.252 T0.2819,0.2526 Q0.2835,0.2526,0.2851,0.2522 L0.2859,0.2522 Q0.2948,0.2522,0.3104,0.2574 T0.3309,0.2671 Q0.3333,0.2696,0.3365,0.2696 Q0.339,0.2696,0.3442,0.2684 T0.3526,0.2671 T0.3582,0.2679 Q0.3687,0.2717,0.3968,0.2742 Q0.4,0.2746,0.4036,0.2771 T0.4096,0.2796 Q0.4112,0.2796,0.4137,0.2787 Q0.4169,0.2779,0.4257,0.2779 Q0.4594,0.2779,0.4779,0.2849 Q0.4876,0.2891,0.4972,0.2891 Q0.4996,0.2891,0.506,0.2887 T0.5165,0.2883 Q0.5253,0.2883,0.5305,0.2889 T0.541,0.292 T0.5494,0.299 L0.5494,0.3015 Q0.5494,0.3036,0.5466,0.3051 T0.5386,0.3073 T0.5285,0.3086 T0.5181,0.3094 T0.5108,0.3102 Q0.5068,0.3107,0.494,0.3121 L0.4683,0.315 T0.4506,0.3181 Q0.449,0.3181,0.4426,0.32 T0.4313,0.3229 T0.4197,0.3256 T0.4064,0.3281 T0.3952,0.3289 Q0.3888,0.3289,0.3855,0.3273 Q0.3783,0.3243,0.3703,0.3243 Q0.3598,0.3243,0.351,0.3297 Q0.3478,0.3314,0.3422,0.3314 Q0.3398,0.3314,0.3337,0.3306 T0.3245,0.3297 T0.3189,0.3306 Q0.3149,0.3314,0.2956,0.3328 T0.2715,0.3368 Q0.2691,0.338,0.2635,0.3391 T0.2522,0.3407 T0.2406,0.3424 T0.2313,0.3447 Q0.2305,0.3447,0.2213,0.348 T0.206,0.354 T0.1924,0.3621 T0.1815,0.3733 Q0.1767,0.3816,0.1767,0.3849 Q0.1767,0.387,0.1783,0.3893 T0.1847,0.3961 T0.1936,0.4063 T0.2004,0.4181 T0.2052,0.4264 T0.2145,0.4309 T0.2386,0.4359 Q0.2458,0.4372,0.2554,0.4372 Q0.2578,0.4372,0.2627,0.437 T0.2699,0.4367 Q0.2795,0.4367,0.2851,0.4384 Q0.294,0.4409,0.3092,0.4409 Q0.3116,0.4409,0.3157,0.4407 T0.3213,0.4405 Q0.3301,0.4405,0.3466,0.444 T0.3687,0.4509 Q0.3703,0.4517,0.3703,0.4525 Q0.3703,0.4575,0.3373,0.4596 Q0.3133,0.4608,0.2988,0.4658 Q0.294,0.4674,0.2803,0.4674 Q0.2763,0.4674,0.2687,0.4672 T0.257,0.467 Q0.2402,0.467,0.2313,0.4687 Q0.2209,0.4708,0.2209,0.4737 Q0.2209,0.4741,0.2213,0.4749 T0.2229,0.4764 L0.2249,0.4774 T0.2265,0.4778 L0.2273,0.4782 Q0.2498,0.4803,0.2618,0.4803 Q0.2667,0.4803,0.2691,0.4799 Q0.2699,0.4795,0.2715,0.4795 Q0.2779,0.4795,0.3133,0.4873 Q0.3181,0.4886,0.3233,0.4892 T0.3386,0.4905 T0.3542,0.4915 T0.3655,0.4919 Q0.3703,0.4919,0.3779,0.4915 T0.3888,0.4911 T0.3952,0.4915 Q0.3976,0.4919,0.4008,0.4927 L0.4072,0.4944 T0.4149,0.4961 T0.4249,0.4975 T0.4369,0.4985 Q0.449,0.499,0.4554,0.5 T0.4651,0.5031 T0.4699,0.5085 Q0.4707,0.5093,0.4707,0.5097 Q0.4707,0.5114,0.4687,0.5124 T0.4631,0.5139 T0.4562,0.5145 T0.4478,0.5147 L0.4402,0.5147 Q0.4353,0.5151,0.4265,0.5174 T0.4096,0.5197 L0.4024,0.5197 Q0.4,0.5193,0.3968,0.5193 Q0.3896,0.5193,0.3831,0.5209 T0.3703,0.5253 T0.3598,0.5288 Q0.3558,0.5301,0.3321,0.5342 T0.302,0.5409 Q0.2876,0.5454,0.2337,0.55 Q0.2112,0.5521,0.2112,0.5558 Q0.2112,0.5574,0.2161,0.5612 Q0.2201,0.5637,0.2281,0.5637 Q0.2305,0.5637,0.2361,0.5633 T0.2442,0.5628 Q0.2474,0.5628,0.2498,0.5633 Q0.2635,0.5653,0.3133,0.5653 Q0.3261,0.5653,0.3301,0.5649 L0.3325,0.5649 Q0.3349,0.5649,0.3402,0.5657 T0.3494,0.5666 Q0.3542,0.5666,0.3598,0.5657 Q0.3663,0.5645,0.3695,0.5645 Q0.3735,0.5645,0.3775,0.5666 T0.3847,0.5695 Q0.3888,0.5707,0.3912,0.5709 T0.3968,0.5711 T0.4048,0.572 Q0.4096,0.5724,0.4165,0.5726 T0.4281,0.5734 T0.4378,0.5757 Q0.4418,0.5774,0.4522,0.5774 Q0.4562,0.5774,0.4655,0.5769 T0.4795,0.5765 Q0.4932,0.5765,0.4972,0.579 Q0.5028,0.5819,0.51,0.5819 Q0.5141,0.5819,0.5221,0.5809 T0.5341,0.5798 L0.5373,0.5798 Q0.5422,0.5798,0.557,0.5821 T0.5855,0.5844 L0.5936,0.5844 L0.5992,0.5844 T0.604,0.5848 T0.6076,0.5852 T0.6108,0.5859 T0.6137,0.5865 T0.6161,0.5873 T0.6181,0.5881 T0.6209,0.5896 L0.6233,0.591 Q0.6265,0.5927,0.6353,0.5927 L0.6402,0.5927 L0.645,0.5927 Q0.6723,0.5927,0.6948,0.6039 Q0.6988,0.606,0.7036,0.606 Q0.7076,0.606,0.7153,0.6045 T0.7261,0.6031 Q0.7309,0.6031,0.7454,0.6027 T0.7647,0.6022 Q0.7783,0.6022,0.808,0.608 L0.812,0.608 Q0.8169,0.608,0.8293,0.6062 T0.8474,0.6039 L0.8506,0.6039 Q0.857,0.6039,0.8655,0.6049 T0.8835,0.6074 T0.8972,0.6093 Q0.9036,0.6101,0.9213,0.6101 L0.9398,0.6101 L0.959,0.6101 L0.9767,0.6101 Q0.9976,0.6109,0.9976,0.6176 Q0.9976,0.6188,0.9968,0.6226 Q0.9952,0.6284,0.9671,0.6284 L0.959,0.6284 L0.9526,0.6284 Q0.9486,0.6284,0.9382,0.6302 T0.9205,0.6321 L0.9173,0.6321 Q0.9133,0.6317,0.9092,0.6317 Q0.9012,0.6317,0.8715,0.6346 Q0.8233,0.6387,0.7944,0.6421 Q0.7888,0.6429,0.7831,0.6429 Q0.7783,0.6429,0.7731,0.6425 T0.7663,0.6421 L0.7622,0.6421 Q0.7614,0.6425,0.7586,0.6425 T0.7474,0.6421 T0.7349,0.6416 Q0.7261,0.6416,0.7221,0.6429 Q0.7173,0.645,0.7032,0.646 T0.6711,0.6479 T0.6482,0.6491 Q0.645,0.6495,0.6418,0.6495 Q0.6353,0.6495,0.6273,0.6474 T0.6161,0.6454 Q0.612,0.6454,0.6088,0.6477 T0.6,0.6499 Q0.5912,0.6499,0.5494,0.6578 Q0.5422,0.6591,0.5373,0.6591 Q0.5341,0.6591,0.5281,0.6584 T0.5173,0.6578 L0.5124,0.6578 Q0.5004,0.6582,0.4747,0.6632 Q0.4675,0.6649,0.4398,0.6667 T0.3851,0.6703 T0.3566,0.6719 Q0.3534,0.6723,0.3406,0.6734 T0.3141,0.6757 T0.2948,0.6773 Q0.2908,0.6781,0.2819,0.6829 T0.2618,0.6877 Q0.2482,0.6877,0.2273,0.6958 T0.1992,0.7117 Q0.1976,0.7138,0.1976,0.7155 Q0.1976,0.7188,0.2032,0.7213 T0.2181,0.7258 T0.2289,0.7292 Q0.2337,0.7308,0.2378,0.7316 L0.2458,0.7333 T0.2522,0.7345 Q0.2747,0.7399,0.2835,0.7399 Q0.29,0.7399,0.3048,0.7408 T0.3253,0.7416 Q0.3277,0.7416,0.3297,0.7414 T0.3341,0.7412 L0.3598,0.7445 Q0.3639,0.7449,0.3663,0.7449 Q0.3695,0.7449,0.3751,0.7443 T0.3855,0.7437 L0.3904,0.7437 Q0.3984,0.7441,0.4141,0.7472 T0.4353,0.7507 Q0.4434,0.752,0.4434,0.7553 Q0.4434,0.7578,0.439,0.7605 T0.4297,0.764 Q0.4257,0.7648,0.4201,0.7655 T0.4076,0.7665 T0.3984,0.7673 Q0.3968,0.7673,0.3912,0.7677 T0.3815,0.7688 T0.3735,0.771 T0.3655,0.7727 Q0.3606,0.7727,0.3534,0.7708 T0.3458,0.769 L0.3446,0.769 Q0.3414,0.769,0.3297,0.7713 T0.3149,0.7735 Q0.3092,0.7735,0.296,0.7771 T0.2771,0.7831 Q0.2683,0.7872,0.2554,0.7885 Q0.253,0.7889,0.2325,0.7897 T0.2032,0.7914 Q0.2008,0.7918,0.198,0.792 T0.194,0.7924 T0.1912,0.7928 T0.1888,0.793 T0.1867,0.7934 T0.1851,0.7943 T0.1843,0.7953 T0.1835,0.7968 T0.1823,0.7984 T0.1815,0.8001 Q0.1815,0.8022,0.1847,0.8032 T0.1932,0.8053 T0.2008,0.8071 Q0.2072,0.8092,0.2265,0.8092 Q0.2361,0.8092,0.2586,0.8088 L0.2635,0.8088 Q0.2731,0.8088,0.2795,0.8098 T0.2928,0.8134 T0.302,0.8167 Q0.306,0.8179,0.3277,0.8183 T0.3542,0.8221 Q0.359,0.8246,0.3622,0.8246 Q0.3639,0.8246,0.3675,0.8237 T0.3751,0.8229 Q0.3968,0.8229,0.4048,0.8275 Q0.4088,0.8291,0.4161,0.8291 Q0.4225,0.8291,0.4353,0.8275 Q0.4369,0.827,0.4394,0.827 Q0.4474,0.827,0.4675,0.8345 Q0.4707,0.8358,0.4739,0.8358 Q0.4755,0.8358,0.4799,0.8353 T0.4876,0.8349 Q0.494,0.8349,0.5012,0.8362 L0.5076,0.837 T0.5133,0.8376 T0.5181,0.8382 T0.5221,0.8391 L0.5253,0.8399 T0.5277,0.8409 L0.5293,0.8422 T0.5301,0.8438 L0.5301,0.8461 Q0.5301,0.8499,0.5249,0.8511 T0.5112,0.8526 T0.5004,0.8532 Q0.4964,0.854,0.4851,0.8546 T0.4667,0.8565 Q0.4578,0.8577,0.441,0.8577 L0.4345,0.8577 L0.4289,0.8577 L0.4209,0.8577 Q0.4153,0.8582,0.4088,0.8586 T0.3968,0.8592 T0.3855,0.8594 T0.3755,0.8596 T0.3675,0.86 T0.3622,0.8602 Q0.3317,0.8635,0.3237,0.8635 Q0.2996,0.8635,0.2996,0.8681 Q0.2996,0.8698,0.3032,0.8733 T0.3068,0.8797 L0.3068,0.8822 Q0.3052,0.8859,0.2859,0.8859 Q0.2787,0.8859,0.2739,0.8851 L0.2699,0.8851 Q0.2659,0.8851,0.259,0.888 T0.2498,0.8913 Q0.2394,0.8946,0.2056,0.8971 Q0.2032,0.8971,0.1928,0.9002 T0.1703,0.9034 Q0.1526,0.9034,0.1353,0.9067 T0.1173,0.915 L0.1173,0.9158 Q0.1173,0.9166,0.1177,0.9175 T0.1189,0.9191 T0.1205,0.9204 T0.1229,0.9212 T0.1257,0.922 T0.1285,0.9226 T0.1309,0.9231 T0.1337,0.9235 T0.1365,0.9237 L0.1446,0.9237 T0.1554,0.9243 T0.1614,0.9268 T0.1659,0.9293 T0.1743,0.9303 T0.1823,0.9316 Q0.1863,0.932,0.1912,0.9332 T0.2028,0.9361 T0.2153,0.939 Q0.2233,0.9411,0.2297,0.9448 T0.2386,0.9494 Q0.2426,0.9506,0.2506,0.9506 Q0.253,0.9506,0.2594,0.9504 T0.2699,0.9502 Q0.2763,0.9502,0.2819,0.9511 Q0.2851,0.9511,0.2871,0.9529 T0.2892,0.9573 Q0.2892,0.9627,0.2811,0.9635 Q0.2434,0.9652,0.2337,0.9672 Q0.2257,0.9693,0.2241,0.9693 Q0.2217,0.9693,0.2096,0.9689 L0.2056,0.9689 Q0.1976,0.9689,0.1924,0.9699 T0.1787,0.9741 L0.1647,0.9793 Q0.1518,0.983,0.1285,0.983 L0.1261,0.983 L0.1237,0.983 Q0.1124,0.983,0.1076,0.9838 Q0.102,0.9851,0.0839,0.9896 T0.0602,0.9954 T0.0406,0.9971 T0.0153,0.9979 L0.0032,0.9979 Z",type:0}},"":{type:5,data:{d:"M0.9992,0.998 Q0.9992,1,0.9968,1 Q0.9521,1,0.9392,0.998 Q0.9335,0.997,0.9152,0.9921 T0.8913,0.9862 Q0.8873,0.9852,0.8783,0.9852 L0.8747,0.9852 L0.871,0.9852 Q0.8483,0.9852,0.8345,0.9812 Q0.8289,0.9792,0.8228,0.9773 T0.8135,0.9743 T0.8054,0.9723 T0.7948,0.9713 L0.7908,0.9713 L0.777,0.9713 T0.7656,0.9693 Q0.7616,0.9683,0.7498,0.9674 T0.7283,0.9664 L0.7186,0.9654 Q0.708,0.9644,0.708,0.9575 Q0.708,0.9545,0.7109,0.952 T0.7178,0.9486 L0.7315,0.9486 L0.7417,0.9486 L0.7502,0.9486 Q0.7575,0.9486,0.7599,0.9476 Q0.7616,0.9466,0.7685,0.9426 T0.7843,0.9377 Q0.7899,0.9357,0.8009,0.9332 T0.8175,0.9298 Q0.82,0.9288,0.8228,0.9283 T0.8277,0.9278 T0.8313,0.9273 T0.8341,0.9263 T0.8362,0.9258 Q0.8402,0.9219,0.854,0.9219 L0.8569,0.9219 L0.8597,0.9219 L0.8637,0.9219 Q0.88,0.9199,0.88,0.9159 L0.88,0.9149 Q0.88,0.913,0.8719,0.911 T0.852,0.9075 T0.8297,0.906 Q0.8224,0.906,0.8167,0.905 T0.8074,0.9031 T0.8001,0.9006 T0.794,0.8991 Q0.7599,0.8971,0.7494,0.8942 Q0.7461,0.8932,0.7397,0.8902 T0.7299,0.8872 L0.7267,0.8872 Q0.721,0.8882,0.7121,0.8882 Q0.6926,0.8882,0.691,0.8833 L0.691,0.8803 Q0.691,0.8773,0.6922,0.8749 T0.6955,0.8704 L0.6975,0.8684 Q0.6926,0.8655,0.6764,0.8655 Q0.6683,0.8655,0.6375,0.8625 Q0.6358,0.8625,0.629,0.862 T0.6148,0.8615 T0.5973,0.861 T0.5791,0.8605 L0.5718,0.8605 L0.5661,0.8605 L0.5596,0.8605 Q0.5426,0.8605,0.5337,0.8586 Q0.5264,0.8576,0.515,0.8571 T0.4996,0.8556 Q0.498,0.8556,0.4947,0.8551 T0.4882,0.8546 T0.4809,0.8541 T0.474,0.8531 T0.4692,0.8506 T0.4672,0.8467 Q0.4672,0.8437,0.468,0.8422 T0.4704,0.8398 T0.4736,0.8383 T0.4781,0.8368 T0.4838,0.8358 T0.4911,0.8353 T0.4988,0.8338 Q0.5069,0.8328,0.5142,0.8328 Q0.5174,0.8328,0.5215,0.8333 T0.5264,0.8338 Q0.5296,0.8338,0.532,0.8328 Q0.5523,0.8249,0.5604,0.8249 L0.5653,0.8249 Q0.5775,0.8269,0.5848,0.8269 Q0.5912,0.8269,0.5937,0.8259 Q0.6026,0.821,0.6253,0.821 Q0.6294,0.821,0.6334,0.822 T0.6383,0.8229 Q0.6399,0.8229,0.644,0.82 Q0.6504,0.817,0.6723,0.816 T0.6975,0.814 Q0.6999,0.814,0.7048,0.8121 T0.7129,0.8091 T0.7226,0.8071 T0.7372,0.8061 L0.7421,0.8061 Q0.7624,0.8071,0.7721,0.8071 Q0.7924,0.8071,0.7981,0.8051 Q0.8021,0.8032,0.807,0.8027 T0.8135,0.8017 T0.8151,0.8002 L0.8151,0.7992 Q0.8143,0.7962,0.8127,0.7957 T0.8045,0.7943 Q0.7997,0.7943,0.7964,0.7933 Q0.7883,0.7933,0.7676,0.7923 T0.7437,0.7903 Q0.7307,0.7893,0.7218,0.7844 Q0.7194,0.7834,0.7141,0.7819 L0.7036,0.7789 T0.693,0.7765 T0.6853,0.7755 Q0.6805,0.7755,0.6691,0.773 T0.6553,0.7705 Q0.6545,0.7705,0.6545,0.7715 Q0.6537,0.7715,0.65,0.7725 T0.6423,0.774 T0.635,0.7745 Q0.6294,0.7745,0.6257,0.773 T0.618,0.771 T0.6083,0.77 T0.601,0.7695 Q0.5985,0.7695,0.5921,0.769 T0.5795,0.7676 T0.5702,0.7666 Q0.5653,0.7656,0.5596,0.7616 T0.5539,0.7547 Q0.5539,0.7498,0.5645,0.7488 Q0.5693,0.7478,0.5852,0.7448 T0.6099,0.7418 L0.6148,0.7418 Q0.6196,0.7418,0.6253,0.7423 T0.6338,0.7428 L0.6399,0.7428 Q0.6431,0.7418,0.6488,0.7409 T0.6586,0.7394 T0.6659,0.7389 L0.6707,0.7389 Q0.6723,0.7399,0.6748,0.7399 Q0.6796,0.7399,0.6946,0.7389 T0.7161,0.7379 Q0.7251,0.7379,0.7478,0.7319 Q0.7494,0.7319,0.7539,0.7315 T0.762,0.73 T0.7697,0.727 T0.7818,0.723 T0.7948,0.7196 T0.7997,0.7151 Q0.7997,0.7141,0.7989,0.7132 Q0.7924,0.7062,0.7717,0.6978 T0.738,0.6894 Q0.7267,0.6894,0.717,0.6845 T0.7048,0.6795 Q0.6975,0.6785,0.6711,0.6766 T0.6431,0.6746 Q0.6415,0.6736,0.6144,0.6721 T0.5596,0.6686 T0.5247,0.6657 Q0.4996,0.6607,0.4874,0.6597 L0.4834,0.6597 Q0.4785,0.6597,0.4724,0.6607 T0.4631,0.6617 Q0.4574,0.6617,0.4501,0.6597 Q0.4088,0.6518,0.4006,0.6518 Q0.3933,0.6518,0.3901,0.6499 T0.3844,0.6479 T0.3739,0.6499 T0.3585,0.6518 L0.352,0.6518 Q0.3471,0.6508,0.335,0.6503 T0.3122,0.6494 T0.2912,0.6479 T0.2766,0.6449 Q0.2733,0.6439,0.2668,0.6439 Q0.2628,0.6439,0.2543,0.6444 T0.2425,0.6449 T0.2368,0.6439 L0.2344,0.6439 Q0.2336,0.6439,0.2283,0.6444 T0.2182,0.6449 Q0.2117,0.6449,0.2052,0.6439 Q0.1768,0.6409,0.1281,0.637 Q0.0989,0.634,0.0916,0.634 L0.0835,0.634 L0.0795,0.634 Q0.0722,0.634,0.0616,0.632 T0.0479,0.6301 Q0.0462,0.6301,0.0406,0.6306 T0.0308,0.6311 Q0.0032,0.6311,0.0008,0.6231 Q0,0.6202,0,0.6182 Q0,0.6083,0.0227,0.6083 Q0.03,0.6073,0.0422,0.6073 Q0.0487,0.6073,0.0608,0.6078 T0.0787,0.6083 Q0.0973,0.6083,0.103,0.6073 Q0.1062,0.6063,0.1131,0.6053 T0.1253,0.6039 T0.1375,0.6024 T0.1492,0.6014 L0.1533,0.6014 Q0.1598,0.6024,0.1723,0.6044 T0.189,0.6063 Q0.1906,0.6063,0.1922,0.6053 Q0.2214,0.6004,0.236,0.6004 Q0.2409,0.6004,0.2551,0.6009 T0.2741,0.6014 Q0.2782,0.6014,0.2859,0.6024 T0.2968,0.6034 Q0.3009,0.6034,0.3041,0.6024 Q0.3277,0.5905,0.356,0.5905 L0.3613,0.5905 L0.3666,0.5905 Q0.3731,0.5905,0.3755,0.5895 Q0.382,0.5856,0.3844,0.5846 T0.3938,0.5831 T0.4144,0.5826 T0.4428,0.5801 T0.4631,0.5776 L0.4663,0.5776 Q0.4704,0.5776,0.4785,0.5786 T0.4899,0.5796 Q0.4964,0.5796,0.5012,0.5767 Q0.5069,0.5737,0.5223,0.5737 Q0.5272,0.5737,0.5369,0.5742 T0.5499,0.5747 Q0.558,0.5747,0.5612,0.5737 Q0.5661,0.5717,0.5714,0.5712 T0.5835,0.5702 T0.5945,0.5692 T0.6018,0.5687 L0.6062,0.5687 T0.6095,0.5682 T0.6148,0.5678 Q0.6172,0.5668,0.6196,0.5653 T0.6245,0.5633 T0.631,0.5628 T0.6407,0.5638 T0.6504,0.5648 T0.6594,0.5638 T0.6675,0.5628 L0.6707,0.5628 L0.6861,0.5628 Q0.7364,0.5628,0.7494,0.5608 L0.7567,0.5608 L0.7652,0.5608 L0.7729,0.5608 Q0.7794,0.5608,0.7818,0.5589 Q0.7851,0.5569,0.7859,0.5559 Q0.7835,0.5539,0.7656,0.5519 Q0.7121,0.547,0.6975,0.543 Q0.6934,0.541,0.6853,0.5396 L0.6691,0.5366 T0.6525,0.5336 T0.6391,0.5312 Q0.6367,0.5302,0.6245,0.5257 T0.6034,0.5213 L0.5977,0.5213 Q0.5945,0.5223,0.5904,0.5223 Q0.5839,0.5223,0.5787,0.5208 T0.5685,0.5183 T0.5596,0.5173 Q0.558,0.5173,0.5552,0.5168 T0.5495,0.5163 L0.5434,0.5163 T0.5373,0.5158 T0.532,0.5148 T0.5284,0.5129 T0.5272,0.5104 L0.5272,0.5074 Q0.5304,0.5015,0.5377,0.499 T0.5629,0.4965 Q0.5734,0.4955,0.5811,0.4946 T0.5953,0.4916 T0.605,0.4896 Q0.6083,0.4886,0.6123,0.4886 Q0.6148,0.4886,0.6225,0.4891 T0.635,0.4896 L0.6456,0.4896 Q0.6837,0.4857,0.6861,0.4857 Q0.7226,0.4768,0.7283,0.4768 Q0.7307,0.4768,0.732,0.4773 T0.7372,0.4777 Q0.7502,0.4777,0.7721,0.4758 Q0.7753,0.4748,0.777,0.4738 Q0.7745,0.4718,0.768,0.4708 Q0.7599,0.4688,0.7445,0.4688 Q0.7405,0.4688,0.7324,0.4693 T0.721,0.4698 Q0.7056,0.4698,0.6999,0.4679 Q0.6861,0.4629,0.6626,0.4619 Q0.6586,0.4609,0.6533,0.4604 T0.6419,0.459 T0.6314,0.456 T0.6269,0.452 Q0.6269,0.451,0.6302,0.4491 Q0.6358,0.4451,0.6525,0.4416 T0.6788,0.4382 L0.6845,0.4382 L0.6902,0.4382 Q0.7064,0.4382,0.7145,0.4362 Q0.7202,0.4342,0.7307,0.4342 L0.7356,0.4342 L0.7409,0.4342 L0.7453,0.4342 Q0.7543,0.4342,0.7607,0.4332 Q0.7778,0.4313,0.7847,0.4288 T0.7932,0.4248 T0.7972,0.4169 T0.8045,0.4045 L0.8094,0.3986 T0.8135,0.3942 L0.8167,0.3912 T0.8191,0.3887 T0.8204,0.3867 T0.8208,0.3848 Q0.8208,0.3818,0.8167,0.3739 Q0.8127,0.3689,0.8054,0.3635 T0.7924,0.3556 T0.7778,0.3497 T0.768,0.3462 Q0.764,0.3442,0.7478,0.3427 T0.7267,0.3383 Q0.7234,0.3363,0.7044,0.3348 T0.6805,0.3323 L0.6764,0.3323 T0.6679,0.3328 T0.6586,0.3333 Q0.6521,0.3333,0.648,0.3314 Q0.6391,0.3264,0.6294,0.3264 Q0.6229,0.3264,0.6156,0.3294 Q0.6115,0.3304,0.6042,0.3304 Q0.5977,0.3304,0.5888,0.3289 T0.5738,0.3264 T0.5584,0.3225 L0.5491,0.3195 Q0.5418,0.3185,0.5162,0.3155 T0.4895,0.3121 T0.4826,0.3116 T0.4716,0.3111 T0.4607,0.3096 T0.4513,0.3066 T0.4477,0.3017 Q0.4477,0.2997,0.4485,0.2977 Q0.4526,0.2898,0.4611,0.2878 T0.4842,0.2859 Q0.4882,0.2859,0.4943,0.2864 T0.5028,0.2868 Q0.5118,0.2868,0.5215,0.2829 Q0.5401,0.276,0.5734,0.276 Q0.5831,0.276,0.5872,0.277 L0.5904,0.277 T0.5949,0.2745 T0.6034,0.272 Q0.631,0.269,0.6407,0.2661 Q0.6448,0.2641,0.6488,0.2641 T0.6573,0.2656 T0.6634,0.2671 Q0.6659,0.2671,0.6675,0.2651 Q0.6723,0.2601,0.6886,0.2552 T0.7137,0.2502 L0.7153,0.2502 L0.7178,0.2502 Q0.7218,0.2502,0.7279,0.2493 T0.7417,0.2468 T0.7526,0.2453 Q0.8208,0.2354,0.8281,0.2354 Q0.8337,0.2354,0.8394,0.2344 T0.8496,0.2315 T0.854,0.2265 Q0.854,0.2216,0.8443,0.2176 Q0.8354,0.2127,0.8216,0.2102 T0.7972,0.2072 T0.777,0.2067 T0.7648,0.2057 Q0.7632,0.2047,0.7498,0.2038 T0.7202,0.2018 T0.6983,0.1998 Q0.6918,0.1998,0.676,0.1949 T0.6537,0.1899 Q0.6496,0.1899,0.6472,0.1919 Q0.6342,0.1968,0.6123,0.1968 Q0.5961,0.1968,0.5912,0.1929 Q0.5904,0.1919,0.5892,0.1919 T0.5823,0.1944 T0.5722,0.1968 T0.5637,0.1949 Q0.5556,0.1909,0.5158,0.1869 Q0.5109,0.1869,0.5032,0.1835 T0.4915,0.18 Q0.4907,0.18,0.4891,0.181 Q0.4793,0.182,0.455,0.182 Q0.4177,0.182,0.4015,0.1771 Q0.3901,0.1741,0.3779,0.1741 Q0.3747,0.1741,0.369,0.1746 T0.3609,0.1751 Q0.3569,0.1751,0.3536,0.1741 Q0.3512,0.1741,0.3459,0.1736 T0.3358,0.1726 T0.3256,0.1716 T0.3167,0.1696 T0.3106,0.1662 Q0.3082,0.1632,0.3082,0.1592 Q0.3082,0.1543,0.3114,0.1508 T0.3204,0.1474 L0.3236,0.1474 Q0.3301,0.1494,0.3333,0.1494 Q0.339,0.1494,0.3496,0.1454 Q0.3674,0.1385,0.3869,0.1385 Q0.395,0.1385,0.4011,0.137 T0.4116,0.1355 T0.4197,0.1375 T0.425,0.1395 Q0.429,0.1395,0.4355,0.1365 Q0.4631,0.1266,0.4882,0.1266 Q0.5126,0.1266,0.5337,0.1246 Q0.5369,0.1246,0.5458,0.1207 T0.5661,0.1167 L0.5693,0.1167 Q0.5791,0.1177,0.5852,0.1187 T0.5921,0.1197 Q0.5945,0.1197,0.6002,0.1167 Q0.6026,0.1147,0.605,0.1123 T0.6091,0.1093 T0.6156,0.1088 T0.6318,0.1098 Q0.6456,0.1108,0.6569,0.1137 T0.6707,0.1167 Q0.674,0.1167,0.6772,0.1147 Q0.6821,0.1108,0.6946,0.1083 T0.721,0.1044 T0.7453,0.1029 L0.7526,0.1029 Q0.7583,0.1039,0.7652,0.1053 T0.7753,0.1068 Q0.7778,0.1068,0.7802,0.1058 Q0.7981,0.1019,0.8143,0.1019 Q0.8175,0.1019,0.8439,0.0964 T0.8735,0.091 Q0.8759,0.091,0.8836,0.089 T0.8938,0.0851 Q0.8929,0.0841,0.8905,0.0841 Q0.8832,0.0821,0.8735,0.0821 Q0.8694,0.0821,0.8629,0.0826 T0.854,0.0831 Q0.8508,0.0831,0.8491,0.0821 Q0.8475,0.0821,0.8171,0.0811 T0.7802,0.0781 T0.7571,0.0737 T0.7332,0.0712 Q0.7299,0.0712,0.7247,0.0717 T0.7161,0.0722 L0.7064,0.0722 Q0.7032,0.0712,0.7003,0.0707 T0.6959,0.0692 T0.693,0.0678 T0.6914,0.0663 T0.6902,0.0648 T0.689,0.0643 L0.6869,0.0643 L0.6845,0.0643 L0.6821,0.0643 Q0.6553,0.0643,0.6553,0.0554 T0.6796,0.0465 L0.6813,0.0465 L0.6845,0.0465 Q0.6918,0.0425,0.717,0.0376 Q0.7226,0.0366,0.7267,0.0356 T0.7324,0.0336 T0.7376,0.0317 T0.747,0.0297 Q0.7518,0.0287,0.7551,0.0287 T0.762,0.0297 T0.768,0.0307 L0.7729,0.0307 Q0.7802,0.0277,0.7997,0.0267 T0.8224,0.0237 Q0.8273,0.0227,0.8341,0.0223 T0.8475,0.0193 T0.8654,0.0143 T0.8873,0.0079 Q0.9002,0.003,0.9331,0.004 T0.97,0.004 Q0.9838,0,0.9976,0.001 Q1,0.001,1,0.003 Z M0.9935,0.996 L0.9951,0.0049 Q0.9822,0.0049,0.9716,0.0079 Q0.9659,0.0099,0.9327,0.0089 T0.8897,0.0119 Q0.8775,0.0158,0.8666,0.0183 T0.85,0.0237 Q0.8418,0.0267,0.8341,0.0272 T0.8248,0.0277 Q0.82,0.0297,0.8001,0.0312 T0.7745,0.0346 Q0.7697,0.0356,0.7664,0.0356 Q0.764,0.0356,0.7603,0.0341 T0.7534,0.0326 Q0.751,0.0326,0.7478,0.0336 Q0.7429,0.0346,0.7397,0.0356 T0.7344,0.0376 T0.7283,0.0401 T0.7178,0.0425 Q0.6926,0.0465,0.6878,0.0495 Q0.6845,0.0514,0.6764,0.0514 L0.6723,0.0514 L0.6691,0.0514 Q0.6602,0.0514,0.6602,0.0554 Q0.6602,0.0574,0.6659,0.0579 T0.6796,0.0589 T0.691,0.0603 T0.6967,0.0643 T0.7072,0.0673 Q0.7121,0.0682,0.7153,0.0682 T0.7238,0.0678 T0.7332,0.0673 Q0.7413,0.0673,0.7579,0.0692 T0.7818,0.0742 Q0.7867,0.0752,0.8171,0.0762 T0.8508,0.0781 L0.852,0.0781 T0.8601,0.0776 T0.8719,0.0772 Q0.8832,0.0772,0.8921,0.0791 Q0.8994,0.0811,0.8994,0.0841 T0.8933,0.0895 T0.8812,0.094 T0.8735,0.0959 T0.8451,0.1014 T0.8143,0.1068 Q0.7989,0.1068,0.7818,0.1098 Q0.7778,0.1108,0.7745,0.1108 T0.768,0.1103 T0.7603,0.1088 T0.7518,0.1068 L0.7461,0.1068 Q0.7315,0.1068,0.7088,0.1103 T0.6805,0.1177 Q0.6756,0.1207,0.6699,0.1207 Q0.6667,0.1207,0.6545,0.1177 T0.631,0.1147 Q0.6269,0.1137,0.6225,0.1133 T0.616,0.1128 L0.6123,0.1128 T0.6103,0.1133 L0.6083,0.1157 T0.6034,0.1197 Q0.5961,0.1246,0.5921,0.1246 Q0.5904,0.1246,0.5835,0.1231 T0.5693,0.1217 L0.5661,0.1217 Q0.558,0.1217,0.5535,0.1231 T0.5446,0.1266 T0.5345,0.1296 Q0.5126,0.1316,0.4882,0.1316 T0.438,0.1405 Q0.4298,0.1434,0.425,0.1434 Q0.4209,0.1434,0.4173,0.1419 T0.4112,0.1405 T0.4027,0.1419 T0.3869,0.1434 Q0.3682,0.1434,0.3512,0.1494 Q0.3398,0.1533,0.3333,0.1533 Q0.3285,0.1533,0.3228,0.1513 L0.3212,0.1513 Q0.3179,0.1513,0.3155,0.1538 T0.3131,0.1592 Q0.3131,0.1612,0.3147,0.1632 Q0.3171,0.1652,0.3232,0.1667 T0.3406,0.1691 T0.3544,0.1701 L0.3601,0.1701 L0.3678,0.1701 L0.3771,0.1701 Q0.3909,0.1701,0.4031,0.1731 Q0.4177,0.178,0.4558,0.178 Q0.4793,0.178,0.4882,0.1761 L0.4915,0.1761 Q0.498,0.1761,0.5061,0.179 T0.5158,0.183 Q0.5166,0.183,0.5231,0.1835 T0.5341,0.1845 T0.545,0.1855 T0.5572,0.1874 T0.5661,0.1909 Q0.5677,0.1919,0.5702,0.1919 T0.5791,0.1894 T0.588,0.1869 Q0.5921,0.1869,0.5945,0.1899 Q0.5977,0.1919,0.6123,0.1919 Q0.6334,0.1919,0.6448,0.1879 Q0.6488,0.186,0.6537,0.186 Q0.6626,0.186,0.6784,0.1909 T0.6983,0.1958 Q0.704,0.1968,0.7206,0.1973 T0.7506,0.1993 T0.7689,0.2018 L0.7705,0.2018 L0.7741,0.2018 L0.7786,0.2018 Q0.8248,0.2018,0.8475,0.2136 Q0.8597,0.2196,0.8597,0.2255 T0.8483,0.2359 T0.8281,0.2404 Q0.8208,0.2404,0.7534,0.2493 Q0.7502,0.2502,0.7425,0.2512 T0.7287,0.2532 T0.7178,0.2542 L0.7153,0.2542 L0.7137,0.2542 Q0.7064,0.2542,0.6906,0.2591 T0.6715,0.2681 T0.6626,0.272 Q0.6594,0.272,0.6541,0.2705 T0.6464,0.269 T0.6431,0.27 Q0.6318,0.273,0.6034,0.276 Q0.6018,0.276,0.5998,0.2774 T0.5957,0.2799 T0.5904,0.2809 L0.5856,0.2809 Q0.5823,0.2799,0.575,0.2799 Q0.541,0.2799,0.5239,0.2868 Q0.5134,0.2908,0.5028,0.2908 Q0.4996,0.2908,0.4935,0.2903 T0.4842,0.2898 Q0.4704,0.2898,0.4635,0.2918 T0.4534,0.2997 L0.4534,0.3007 Q0.4534,0.3037,0.459,0.3046 T0.4753,0.3066 T0.4899,0.3076 Q0.4907,0.3076,0.5166,0.3106 T0.5507,0.3155 Q0.5921,0.3264,0.605,0.3264 Q0.6107,0.3264,0.6131,0.3254 Q0.6212,0.3225,0.6294,0.3225 Q0.6407,0.3225,0.6513,0.3274 Q0.6529,0.3294,0.6569,0.3294 Q0.6594,0.3294,0.665,0.3284 T0.6748,0.3274 T0.6821,0.3284 Q0.6861,0.3294,0.6967,0.3299 T0.7165,0.3314 T0.7299,0.3343 Q0.7324,0.3363,0.7486,0.3383 T0.7697,0.3422 Q0.7713,0.3432,0.7774,0.3452 T0.7887,0.3492 T0.8005,0.3546 T0.8127,0.3625 T0.8208,0.3719 Q0.8256,0.3798,0.8256,0.3848 Q0.8256,0.3877,0.8236,0.3902 T0.8171,0.3976 T0.809,0.4075 T0.8021,0.4189 T0.7968,0.4273 T0.7867,0.4322 T0.7616,0.4382 Q0.7543,0.4392,0.7437,0.4392 L0.7364,0.4392 L0.7299,0.4392 Q0.7202,0.4392,0.7161,0.4402 Q0.7072,0.4431,0.691,0.4431 L0.6845,0.4431 L0.6788,0.4431 Q0.6707,0.4431,0.6545,0.4461 T0.6334,0.452 Q0.6399,0.456,0.6626,0.457 Q0.6878,0.458,0.7024,0.4639 Q0.7064,0.4649,0.7186,0.4649 L0.7295,0.4649 L0.7413,0.4649 Q0.7591,0.4649,0.7697,0.4669 Q0.7818,0.4688,0.7818,0.4728 Q0.7818,0.4748,0.7798,0.4763 T0.7753,0.4787 L0.7737,0.4797 L0.7729,0.4797 Q0.7494,0.4827,0.7388,0.4827 Q0.7332,0.4827,0.7307,0.4817 L0.7291,0.4817 Q0.7226,0.4817,0.6878,0.4896 Q0.6829,0.4906,0.6772,0.4911 T0.6618,0.4921 T0.646,0.4931 T0.6342,0.4936 T0.6212,0.4931 T0.6115,0.4926 Q0.6083,0.4926,0.6058,0.4931 T0.5969,0.4955 T0.5819,0.499 T0.5629,0.5005 Q0.5572,0.5005,0.5527,0.501 T0.545,0.502 T0.5397,0.503 T0.5361,0.5045 T0.5337,0.5064 T0.5328,0.5094 L0.532,0.5094 Q0.532,0.5104,0.5337,0.5109 T0.5381,0.5114 T0.5438,0.5119 T0.5515,0.5124 L0.5604,0.5124 Q0.5661,0.5134,0.5746,0.5153 T0.5912,0.5173 L0.5977,0.5173 L0.6026,0.5173 Q0.6139,0.5173,0.6273,0.5218 T0.6407,0.5272 Q0.6448,0.5282,0.6683,0.5321 T0.6991,0.5381 Q0.7137,0.543,0.7664,0.548 Q0.7908,0.55,0.7908,0.5559 Q0.7908,0.5589,0.7859,0.5628 Q0.781,0.5658,0.7713,0.5658 Q0.7689,0.5658,0.7628,0.5653 T0.7543,0.5648 T0.7502,0.5658 Q0.7364,0.5678,0.6878,0.5678 Q0.674,0.5678,0.6699,0.5668 L0.6683,0.5668 Q0.6659,0.5668,0.6606,0.5678 T0.6513,0.5687 Q0.6464,0.5687,0.6391,0.5678 Q0.6334,0.5668,0.6302,0.5668 T0.6241,0.5687 T0.6164,0.5717 Q0.6123,0.5727,0.6099,0.5727 L0.6062,0.5727 T0.6022,0.5732 T0.5953,0.5737 Q0.5904,0.5747,0.5839,0.5747 T0.5726,0.5752 T0.5629,0.5776 T0.5458,0.5796 Q0.5418,0.5796,0.5324,0.5791 T0.5191,0.5786 Q0.5077,0.5786,0.5045,0.5806 Q0.498,0.5836,0.4899,0.5836 Q0.4858,0.5836,0.4777,0.5826 T0.4655,0.5816 L0.4631,0.5816 Q0.4582,0.5816,0.4436,0.5841 T0.4144,0.5865 L0.4051,0.5865 L0.3986,0.5865 T0.3933,0.587 T0.3897,0.5875 T0.3869,0.5885 T0.3844,0.5895 T0.3812,0.591 T0.3779,0.5925 Q0.3739,0.5955,0.3633,0.5955 L0.3585,0.5955 L0.3536,0.5955 Q0.3277,0.5955,0.3066,0.6053 Q0.3017,0.6083,0.296,0.6083 Q0.2912,0.6083,0.2839,0.6068 T0.2741,0.6053 Q0.2693,0.6053,0.2547,0.6048 T0.236,0.6044 Q0.2222,0.6044,0.193,0.6103 L0.1882,0.6103 Q0.1825,0.6103,0.1703,0.6083 T0.1525,0.6063 L0.15,0.6063 Q0.1436,0.6063,0.135,0.6073 T0.1168,0.6098 T0.1038,0.6113 Q0.0973,0.6123,0.0787,0.6123 L0.06,0.6123 L0.0414,0.6123 L0.0235,0.6123 Q0.0049,0.6133,0.0049,0.6172 L0.0057,0.6222 Q0.0073,0.6261,0.043,0.6261 L0.0454,0.6261 L0.0479,0.6261 Q0.0527,0.6261,0.0633,0.6281 T0.0803,0.6301 L0.0835,0.6301 L0.0916,0.6301 Q0.0998,0.6301,0.129,0.632 Q0.1768,0.636,0.206,0.64 Q0.2117,0.6409,0.2165,0.6409 Q0.2206,0.6409,0.2263,0.6405 T0.2336,0.64 L0.2384,0.64 L0.2409,0.64 T0.251,0.6395 T0.2644,0.639 Q0.2741,0.639,0.279,0.6409 Q0.283,0.6429,0.2972,0.6439 T0.3293,0.6459 T0.3528,0.6469 L0.3577,0.6469 Q0.3642,0.6469,0.3723,0.6449 T0.3844,0.6429 Q0.3901,0.6429,0.3933,0.6454 T0.4006,0.6479 Q0.4088,0.6479,0.4517,0.6558 Q0.4582,0.6568,0.4623,0.6568 Q0.4655,0.6568,0.4716,0.6563 T0.4826,0.6558 L0.4882,0.6558 Q0.5004,0.6558,0.5255,0.6607 Q0.5328,0.6627,0.5604,0.6647 T0.6152,0.6682 T0.644,0.6696 T0.6719,0.6721 T0.7056,0.6756 Q0.7113,0.6756,0.7198,0.6805 T0.738,0.6855 Q0.7453,0.6855,0.7551,0.6879 T0.7741,0.6939 T0.7912,0.7018 T0.8029,0.7102 Q0.8054,0.7132,0.8054,0.7151 Q0.8054,0.7191,0.8009,0.7216 T0.7908,0.7255 T0.779,0.7285 T0.7721,0.731 Q0.7672,0.7329,0.7632,0.7339 T0.7551,0.7354 T0.7486,0.7369 Q0.7478,0.7369,0.7445,0.7379 T0.7388,0.7394 T0.7324,0.7404 L0.7242,0.7413 T0.7161,0.7418 Q0.7105,0.7418,0.6955,0.7428 T0.6748,0.7438 L0.6699,0.7438 L0.6667,0.7438 Q0.6659,0.7438,0.6407,0.7468 Q0.6367,0.7478,0.6334,0.7478 T0.6245,0.7468 T0.6148,0.7458 L0.6099,0.7458 Q0.6018,0.7468,0.5864,0.7498 T0.5653,0.7532 T0.5596,0.7557 Q0.5596,0.7596,0.571,0.7616 Q0.5742,0.7626,0.5803,0.7631 T0.5929,0.7641 T0.6018,0.7656 L0.6087,0.7656 T0.6192,0.7666 T0.6277,0.7695 Q0.631,0.7705,0.6342,0.7705 Q0.6383,0.7705,0.6452,0.7685 T0.6537,0.7666 L0.6553,0.7666 Q0.6602,0.7666,0.6711,0.769 T0.6853,0.7715 Q0.691,0.7715,0.7048,0.775 T0.7242,0.7814 Q0.7324,0.7854,0.7445,0.7864 Q0.7478,0.7864,0.768,0.7873 T0.7972,0.7893 Q0.7989,0.7893,0.8017,0.7898 T0.8054,0.7903 L0.8082,0.7903 T0.811,0.7908 T0.8131,0.7913 T0.8151,0.7918 L0.8167,0.7928 T0.8179,0.7943 T0.8191,0.7957 T0.8204,0.7977 T0.8208,0.8002 Q0.8208,0.8032,0.8171,0.8051 T0.8074,0.8081 T0.7997,0.8091 Q0.7932,0.8121,0.7737,0.8121 Q0.7664,0.8121,0.7413,0.8111 L0.7364,0.8111 Q0.7275,0.8111,0.7214,0.8121 T0.7084,0.8155 T0.6991,0.819 Q0.6934,0.82,0.6723,0.8205 T0.6472,0.8239 Q0.6423,0.8269,0.6375,0.8269 Q0.6358,0.8269,0.6338,0.8264 T0.6294,0.8254 T0.6253,0.8249 Q0.6042,0.8249,0.5961,0.8289 Q0.5921,0.8318,0.5839,0.8318 Q0.5783,0.8318,0.5645,0.8299 L0.5604,0.8299 Q0.5531,0.8299,0.5337,0.8368 Q0.5296,0.8378,0.5255,0.8378 Q0.5239,0.8378,0.5195,0.8373 T0.5118,0.8368 Q0.5061,0.8368,0.4996,0.8388 Q0.4947,0.8388,0.4882,0.8398 Q0.4777,0.8417,0.4753,0.8427 T0.4728,0.8467 Q0.4728,0.8487,0.4769,0.8492 T0.4886,0.8501 T0.5004,0.8516 Q0.5036,0.8516,0.515,0.8521 T0.5345,0.8546 Q0.5426,0.8556,0.5596,0.8556 L0.5649,0.8556 L0.5702,0.8556 L0.5791,0.8556 Q0.5921,0.8566,0.6135,0.8571 T0.6383,0.8586 Q0.6683,0.8615,0.6764,0.8615 L0.6776,0.8615 L0.6788,0.8615 Q0.7032,0.8615,0.7032,0.8684 Q0.7032,0.8714,0.6995,0.8749 T0.6959,0.8803 L0.6959,0.8813 Q0.6991,0.8843,0.7153,0.8843 Q0.7218,0.8843,0.7259,0.8833 L0.7299,0.8833 Q0.7324,0.8833,0.7348,0.8838 T0.7393,0.8848 T0.7433,0.8863 L0.7474,0.8882 T0.751,0.8892 Q0.7616,0.8922,0.7948,0.8952 Q0.7981,0.8952,0.8082,0.8981 T0.8297,0.9011 T0.8528,0.9026 T0.8747,0.907 T0.8856,0.9149 L0.8856,0.9159 Q0.8856,0.9179,0.8848,0.9189 T0.8828,0.9209 T0.88,0.9228 T0.8767,0.9243 T0.8731,0.9248 T0.8698,0.9253 T0.8666,0.9258 L0.8646,0.9258 Q0.8621,0.9258,0.8577,0.9263 T0.85,0.9268 T0.8439,0.9273 T0.8402,0.9278 Q0.8386,0.9298,0.8358,0.9308 T0.8305,0.9322 T0.8244,0.9332 T0.8183,0.9337 Q0.8135,0.9347,0.8025,0.9372 T0.7859,0.9416 Q0.7778,0.9436,0.7717,0.9471 T0.7624,0.9515 Q0.7575,0.9535,0.7486,0.9535 Q0.7461,0.9535,0.7397,0.953 T0.7291,0.9525 Q0.7234,0.9525,0.7186,0.953 T0.7137,0.957 T0.7194,0.9614 Q0.7567,0.9634,0.7664,0.9654 Q0.7745,0.9674,0.7762,0.9674 L0.7908,0.9674 Q0.7924,0.9664,0.7948,0.9664 Q0.8029,0.9664,0.8082,0.9679 T0.8224,0.9723 T0.8362,0.9773 Q0.8483,0.9812,0.871,0.9812 L0.8751,0.9812 Q0.8873,0.9812,0.8929,0.9822 T0.9165,0.9876 T0.9408,0.9931 Q0.9513,0.996,0.9935,0.996 Z M0.9968,0.998 L0.9976,0.003 Q0.9838,0.002,0.9708,0.0059 Q0.9659,0.0069,0.9327,0.0059 T0.8881,0.0099 Q0.8767,0.0138,0.8658,0.0163 T0.8491,0.0218 Q0.8418,0.0247,0.8341,0.0247 T0.8232,0.0257 Q0.8191,0.0277,0.7997,0.0292 T0.7737,0.0326 Q0.7697,0.0336,0.7672,0.0336 T0.7612,0.0321 T0.7543,0.0307 T0.747,0.0317 Q0.7397,0.0326,0.7336,0.0356 T0.717,0.0406 Q0.6918,0.0445,0.6861,0.0475 Q0.6845,0.0485,0.6772,0.0485 T0.6638,0.05 T0.6577,0.0549 T0.6642,0.0598 T0.6796,0.0613 T0.6902,0.0623 Q0.691,0.0623,0.6946,0.0658 T0.7064,0.0702 L0.7161,0.0702 Q0.7194,0.0702,0.7242,0.0697 T0.7332,0.0692 Q0.7413,0.0692,0.7579,0.0717 T0.781,0.0762 Q0.7867,0.0781,0.8171,0.0786 T0.85,0.0801 L0.8532,0.0801 Q0.8548,0.0801,0.8613,0.0796 T0.8727,0.0791 Q0.8832,0.0791,0.8913,0.0811 Q0.897,0.0831,0.897,0.0851 Q0.897,0.088,0.8873,0.0905 T0.8735,0.093 Q0.871,0.093,0.8447,0.0984 T0.8143,0.1039 Q0.7981,0.1039,0.781,0.1078 Q0.7778,0.1088,0.7745,0.1088 T0.7644,0.1073 T0.7518,0.1048 L0.7453,0.1048 Q0.7299,0.1048,0.7076,0.1083 T0.6788,0.1157 Q0.6748,0.1187,0.6699,0.1187 Q0.6675,0.1187,0.6557,0.1157 T0.6318,0.1118 Q0.6164,0.1108,0.6135,0.1108 T0.6095,0.1113 T0.6067,0.1142 T0.6018,0.1187 Q0.5953,0.1227,0.5921,0.1227 Q0.5904,0.1227,0.5839,0.1212 T0.5693,0.1187 L0.5661,0.1187 Q0.558,0.1187,0.5527,0.1207 T0.5434,0.1246 T0.5337,0.1266 Q0.5126,0.1296,0.4882,0.1296 T0.4371,0.1385 Q0.4298,0.1414,0.425,0.1414 Q0.4225,0.1414,0.4189,0.1395 T0.4112,0.1375 Q0.4079,0.1375,0.4019,0.1395 T0.3869,0.1414 Q0.3682,0.1414,0.3504,0.1474 Q0.3398,0.1513,0.3333,0.1513 Q0.3293,0.1513,0.3228,0.1494 L0.3204,0.1494 Q0.3179,0.1494,0.3155,0.1508 T0.3118,0.1543 T0.3106,0.1592 T0.3127,0.1642 T0.32,0.1677 T0.3305,0.1701 T0.3435,0.1716 T0.3544,0.1721 Q0.3569,0.1731,0.3601,0.1731 Q0.3625,0.1731,0.3686,0.1726 T0.3779,0.1721 Q0.3901,0.1721,0.4023,0.1751 Q0.4177,0.18,0.455,0.18 Q0.4793,0.18,0.4882,0.178 L0.4915,0.178 Q0.4964,0.178,0.5045,0.1815 T0.5158,0.185 Q0.5564,0.1889,0.5645,0.1929 Q0.5677,0.1939,0.571,0.1939 Q0.575,0.1939,0.5807,0.1919 T0.5888,0.1899 T0.5929,0.1909 Q0.5969,0.1949,0.6123,0.1949 Q0.6334,0.1949,0.6456,0.1899 Q0.6488,0.1879,0.6537,0.1879 Q0.661,0.1879,0.6772,0.1929 T0.6983,0.1978 Q0.704,0.1988,0.7206,0.1998 T0.7502,0.2018 T0.7664,0.2038 Q0.7672,0.2038,0.7818,0.2043 T0.8151,0.2067 T0.8459,0.2156 Q0.8573,0.2206,0.8573,0.2255 Q0.8573,0.2295,0.8516,0.2324 T0.8394,0.2369 T0.8281,0.2384 Q0.8208,0.2384,0.7526,0.2473 Q0.7494,0.2483,0.7421,0.2493 T0.7287,0.2512 T0.7178,0.2522 L0.7153,0.2522 L0.7137,0.2522 Q0.7056,0.2522,0.6898,0.2572 T0.6691,0.2671 Q0.6667,0.269,0.6634,0.269 Q0.661,0.269,0.6557,0.2681 T0.6472,0.2671 Q0.6448,0.2671,0.6415,0.2681 Q0.631,0.271,0.6034,0.274 Q0.5994,0.274,0.5961,0.2765 T0.5904,0.2789 L0.5864,0.2789 Q0.5831,0.2779,0.5742,0.2779 Q0.541,0.2779,0.5223,0.2849 Q0.5126,0.2888,0.5028,0.2888 Q0.5004,0.2888,0.4943,0.2883 T0.4842,0.2878 Q0.4704,0.2878,0.4627,0.2898 T0.4509,0.2987 Q0.4501,0.2997,0.4501,0.3007 Q0.4501,0.3046,0.4574,0.3066 T0.4765,0.3091 T0.4899,0.3096 Q0.4931,0.3106,0.5061,0.3121 T0.5316,0.315 T0.5499,0.3175 Q0.5507,0.3175,0.5572,0.3195 T0.5685,0.3225 T0.5803,0.3254 T0.5933,0.3279 T0.605,0.3284 T0.6148,0.3274 Q0.6221,0.3244,0.6294,0.3244 Q0.6399,0.3244,0.6496,0.3294 Q0.6529,0.3314,0.6577,0.3314 Q0.6602,0.3314,0.6663,0.3304 T0.6756,0.3294 T0.6813,0.3304 Q0.6837,0.3304,0.6873,0.3309 L0.6946,0.3318 T0.7024,0.3323 T0.7105,0.3328 T0.7178,0.3338 T0.7238,0.3348 T0.7283,0.3363 Q0.7324,0.3383,0.7482,0.3403 T0.7689,0.3442 Q0.7697,0.3442,0.779,0.3477 T0.794,0.3536 T0.8074,0.3615 T0.8183,0.3729 Q0.8232,0.3808,0.8232,0.3848 Q0.8232,0.3867,0.8216,0.3892 T0.8151,0.3961 T0.8062,0.406 T0.7997,0.4179 T0.7952,0.4263 T0.7855,0.4308 T0.7616,0.4362 Q0.7543,0.4372,0.7445,0.4372 L0.7372,0.4372 L0.7307,0.4372 Q0.7202,0.4372,0.7153,0.4382 Q0.7064,0.4402,0.691,0.4402 L0.6845,0.4402 L0.6788,0.4402 Q0.6699,0.4402,0.6533,0.4436 T0.6318,0.451 Q0.6302,0.451,0.6302,0.452 Q0.6302,0.457,0.6626,0.459 Q0.6869,0.4609,0.7015,0.4659 Q0.7064,0.4669,0.7202,0.4669 L0.7311,0.4669 L0.7429,0.4669 Q0.7599,0.4669,0.7689,0.4688 Q0.7794,0.4708,0.7794,0.4738 L0.7786,0.4748 T0.777,0.4763 T0.7749,0.4773 L0.7737,0.4777 L0.7729,0.4777 Q0.7502,0.4797,0.738,0.4797 L0.7315,0.4797 L0.7291,0.4797 Q0.7226,0.4797,0.6869,0.4876 Q0.6821,0.4886,0.6768,0.4891 T0.6614,0.4901 T0.6456,0.4916 L0.6342,0.4916 Q0.6294,0.4916,0.6217,0.4911 T0.6115,0.4906 Q0.6083,0.4906,0.6054,0.4911 T0.5961,0.4936 T0.5815,0.4965 T0.5629,0.4985 Q0.5507,0.4985,0.5442,0.4995 T0.5345,0.5025 T0.5304,0.5084 L0.5296,0.5094 Q0.5296,0.5114,0.5316,0.5124 T0.5373,0.5138 T0.5442,0.5143 L0.5523,0.5143 L0.5604,0.5143 Q0.5637,0.5153,0.5677,0.5158 T0.5742,0.5173 T0.5811,0.5188 T0.5904,0.5193 L0.5977,0.5193 L0.6026,0.5193 Q0.6107,0.5193,0.6168,0.5208 T0.6294,0.5252 T0.6399,0.5292 Q0.6448,0.5302,0.6679,0.5341 T0.6983,0.541 Q0.7129,0.545,0.7664,0.55 Q0.7883,0.5519,0.7883,0.5559 Q0.7883,0.5579,0.7843,0.5608 T0.7721,0.5638 Q0.7697,0.5638,0.764,0.5633 T0.7559,0.5628 L0.7502,0.5628 Q0.7364,0.5648,0.6869,0.5648 L0.6699,0.5648 L0.6675,0.5648 Q0.665,0.5648,0.6602,0.5658 T0.6513,0.5668 Q0.6464,0.5668,0.6399,0.5658 Q0.6342,0.5648,0.6302,0.5648 T0.6225,0.5668 T0.6156,0.5697 Q0.6107,0.5707,0.6087,0.5707 T0.6034,0.5712 T0.5953,0.5717 Q0.5904,0.5727,0.5835,0.5727 T0.5718,0.5732 T0.562,0.5757 Q0.558,0.5767,0.5483,0.5767 L0.5349,0.5767 L0.5207,0.5767 Q0.5069,0.5767,0.5028,0.5786 Q0.4972,0.5816,0.4899,0.5816 Q0.4858,0.5816,0.4781,0.5806 T0.4663,0.5796 L0.4631,0.5796 Q0.4582,0.5796,0.4432,0.5821 T0.4144,0.5846 L0.4071,0.5846 L0.4011,0.5846 T0.3958,0.5851 T0.3921,0.5856 T0.3889,0.5861 T0.3865,0.5865 T0.384,0.5875 L0.382,0.5885 L0.3796,0.5895 L0.3771,0.5905 Q0.3731,0.5925,0.365,0.5925 L0.3601,0.5925 L0.3552,0.5925 Q0.3277,0.5925,0.3049,0.6044 Q0.3017,0.6053,0.296,0.6053 Q0.292,0.6053,0.2847,0.6044 T0.2741,0.6034 Q0.2693,0.6034,0.2547,0.6029 T0.236,0.6024 Q0.2222,0.6024,0.1922,0.6073 Q0.1906,0.6083,0.1882,0.6083 Q0.1833,0.6083,0.1711,0.6063 T0.1533,0.6044 Q0.1517,0.6034,0.1492,0.6034 Q0.1436,0.6034,0.135,0.6048 T0.1168,0.6078 T0.103,0.6093 Q0.0973,0.6103,0.0787,0.6103 L0.0604,0.6103 L0.0414,0.6103 L0.0235,0.6103 Q0.0024,0.6113,0.0024,0.6172 Q0.0024,0.6192,0.0032,0.6231 Q0.0049,0.6281,0.0333,0.6281 L0.0414,0.6281 L0.0479,0.6281 Q0.0519,0.6281,0.0624,0.6301 T0.0803,0.632 L0.0835,0.632 L0.0916,0.632 Q0.0989,0.632,0.1281,0.634 L0.206,0.6419 Q0.2117,0.6429,0.2174,0.6429 Q0.2214,0.6429,0.2271,0.6424 T0.2344,0.6419 L0.2376,0.6419 Q0.2393,0.6429,0.2417,0.6429 T0.2526,0.6419 T0.2652,0.6409 Q0.2741,0.6409,0.2782,0.6429 T0.2968,0.6459 T0.3293,0.6479 T0.352,0.6489 Q0.3552,0.6499,0.3585,0.6499 Q0.365,0.6499,0.3731,0.6474 T0.3844,0.6449 Q0.3885,0.6449,0.3917,0.6474 T0.4006,0.6499 Q0.4088,0.6499,0.4509,0.6578 Q0.4574,0.6588,0.4631,0.6588 Q0.4655,0.6588,0.4716,0.6583 T0.4834,0.6578 L0.4874,0.6578 Q0.5004,0.6588,0.5255,0.6637 Q0.532,0.6647,0.56,0.6667 T0.6148,0.6701 T0.644,0.6716 Q0.6464,0.6726,0.6594,0.6736 T0.6861,0.6756 T0.7048,0.6775 Q0.7097,0.6775,0.7186,0.6825 T0.738,0.6874 Q0.7518,0.6874,0.7729,0.6958 T0.8005,0.7122 Q0.8029,0.7141,0.8029,0.7151 Q0.8029,0.7191,0.7968,0.7216 T0.7818,0.726 T0.7705,0.729 L0.764,0.731 T0.7583,0.7324 T0.753,0.7334 T0.7486,0.7349 Q0.7251,0.7399,0.7161,0.7399 Q0.7097,0.7399,0.6951,0.7409 T0.6748,0.7418 L0.6703,0.7418 L0.6667,0.7418 Q0.6659,0.7418,0.6399,0.7448 L0.6334,0.7448 Q0.631,0.7448,0.6249,0.7443 T0.6148,0.7438 L0.6099,0.7438 Q0.601,0.7448,0.5856,0.7473 T0.5645,0.7507 Q0.5572,0.7517,0.5572,0.7547 T0.5616,0.7606 T0.5702,0.7646 Q0.5734,0.7646,0.5779,0.7651 T0.5864,0.7661 T0.5949,0.7671 T0.6018,0.7676 L0.6071,0.7676 T0.6135,0.7681 T0.62,0.769 T0.6269,0.771 T0.6342,0.7725 Q0.6391,0.7725,0.6464,0.7705 T0.6541,0.7685 L0.6553,0.7685 Q0.6586,0.7685,0.6699,0.771 T0.6853,0.7735 Q0.6902,0.7735,0.704,0.777 T0.7226,0.7834 Q0.7315,0.7873,0.7445,0.7883 Q0.747,0.7883,0.7676,0.7893 T0.7972,0.7913 Q0.7989,0.7913,0.8017,0.7918 T0.8058,0.7923 T0.809,0.7928 T0.8114,0.7933 T0.8131,0.7938 T0.8147,0.7943 T0.8159,0.7953 L0.8171,0.7967 T0.8175,0.7982 Q0.8183,0.7992,0.8183,0.8002 Q0.8183,0.8022,0.8155,0.8032 T0.807,0.8051 T0.7989,0.8071 Q0.7932,0.8091,0.7737,0.8091 L0.7413,0.8091 L0.7372,0.8091 Q0.7275,0.8091,0.721,0.8101 T0.7072,0.8136 T0.6983,0.817 Q0.6942,0.818,0.6825,0.818 T0.6602,0.819 T0.6456,0.822 Q0.6415,0.8249,0.6375,0.8249 Q0.6367,0.8249,0.6326,0.8239 T0.6253,0.8229 Q0.6034,0.8229,0.5953,0.8279 Q0.5912,0.8289,0.5848,0.8289 Q0.5775,0.8289,0.5645,0.8279 Q0.5629,0.8269,0.5604,0.8269 Q0.5523,0.8269,0.5328,0.8348 Q0.5296,0.8358,0.5264,0.8358 Q0.5247,0.8358,0.5203,0.8353 T0.5126,0.8348 Q0.5061,0.8348,0.4988,0.8358 Q0.4947,0.8368,0.4882,0.8378 Q0.4826,0.8388,0.4797,0.8388 T0.474,0.8398 T0.4704,0.8422 T0.4696,0.8462 T0.472,0.8501 T0.4781,0.8521 T0.4854,0.8526 L0.4935,0.8526 T0.4996,0.8536 Q0.5036,0.8536,0.515,0.8546 T0.5337,0.8566 Q0.5426,0.8576,0.5596,0.8576 L0.5657,0.8576 L0.571,0.8576 L0.5791,0.8576 Q0.5921,0.8586,0.6135,0.8591 T0.6383,0.8605 Q0.6683,0.8635,0.6764,0.8635 Q0.6999,0.8635,0.6999,0.8684 Q0.6999,0.8694,0.6967,0.8734 T0.6934,0.8803 L0.6934,0.8823 Q0.6951,0.8863,0.7137,0.8863 Q0.7218,0.8863,0.7267,0.8853 L0.7299,0.8853 Q0.734,0.8853,0.7409,0.8882 T0.7502,0.8912 Q0.7607,0.8942,0.794,0.8971 Q0.7972,0.8971,0.8078,0.9001 T0.8297,0.9031 Q0.8475,0.9031,0.8646,0.9065 T0.8824,0.9149 L0.8824,0.9159 Q0.8824,0.9169,0.882,0.9179 T0.8808,0.9194 L0.8792,0.9204 T0.8767,0.9214 T0.8743,0.9224 T0.8715,0.9228 L0.8686,0.9228 T0.8662,0.9233 T0.8637,0.9238 L0.8556,0.9238 T0.8447,0.9243 T0.8386,0.9268 Q0.837,0.9278,0.835,0.9288 T0.8305,0.9303 T0.8244,0.9308 T0.8175,0.9318 Q0.8127,0.9327,0.8017,0.9352 T0.7851,0.9397 Q0.777,0.9416,0.7705,0.9451 T0.7616,0.9496 Q0.7575,0.9505,0.7494,0.9505 L0.7405,0.9505 L0.7307,0.9505 Q0.7234,0.9505,0.7178,0.9515 Q0.7145,0.9515,0.7125,0.9535 T0.7105,0.9575 Q0.7105,0.9624,0.7194,0.9634 Q0.7567,0.9654,0.7664,0.9674 Q0.7745,0.9693,0.7762,0.9693 L0.7908,0.9693 L0.7948,0.9693 Q0.8005,0.9693,0.8058,0.9698 T0.8143,0.9718 T0.824,0.9753 T0.8354,0.9792 Q0.8483,0.9832,0.871,0.9832 L0.8739,0.9832 L0.8767,0.9832 Q0.8873,0.9832,0.8921,0.9842 Q0.8978,0.9852,0.9157,0.9896 T0.94,0.996 Q0.9424,0.996,0.9469,0.9965 T0.9566,0.997 T0.9676,0.9975 T0.9781,0.998 L0.9874,0.998 L0.9943,0.998 L0.9968,0.998 Z",type:0}},"":{type:5,data:{d:"M1,0.5071 L0.8623,0.7102 L0.5865,0.7102 L0.4489,0.5071 L0.5865,0.3045 L0.8623,0.3045 Z M0.4135,0.7974 L0.2758,1 L0,1 L0,0.5944 L0.2758,0.5944 Z M0.4135,0.2026 L0.2758,0.4056 L0,0.4056 L0,0 L0.2758,0 Z",type:0}},"":{type:5,data:{d:"M0.9726,0.5071 L0.8451,0.3184 L0.59,0.3184 L0.4624,0.5071 L0.59,0.6955 L0.8451,0.6955 Z M1,0.5071 L0.8588,0.716 L0.5768,0.716 L0.4355,0.5071 L0.5768,0.2982 L0.8588,0.2982 Z M0.401,0.7911 L0.2735,0.6028 L0.0236,0.6028 L0.0236,0.9799 L0.2735,0.9799 Z M0.4284,0.7911 L0.2872,1 L0,1 L0,0.5826 L0.2872,0.5826 Z M0.401,0.2089 L0.2735,0.0201 L0.0236,0.0201 L0.0236,0.3972 L0.2735,0.3972 Z M0.4284,0.2089 L0.2872,0.4174 L0,0.4174 L0,0 L0.2872,0 Z",type:0}},"":{type:5,data:{d:"M1,0.4996 Q1,0.5365,0.9924,0.568 T0.9718,0.6177 T0.9433,0.6363 L0.9425,0.6367 Q0.9355,0.6367,0.9285,0.6326 L0.9308,0.6326 Q0.9129,0.6195,0.9013,0.587 T0.8871,0.5164 T0.8943,0.4373 T0.9254,0.3687 L0.9254,0.3691 Q0.9339,0.3628,0.9425,0.3628 Q0.9584,0.3628,0.9716,0.3811 T0.9924,0.431 T1,0.4996 Z M0.9234,0.6321 L0.8776,0.7169 Q0.8791,0.732,0.8791,0.7441 Q0.8791,0.781,0.8716,0.8123 T0.8508,0.862 T0.8224,0.8809 L0.8216,0.8809 Q0.8154,0.8809,0.8092,0.8775 L0.8088,0.8775 Q0.7905,0.8633,0.7792,0.8297 T0.766,0.7571 T0.775,0.6768 T0.8088,0.6086 L0.7431,0.6065 Q0.719,0.6313,0.7072,0.6705 T0.6965,0.7487 T0.7097,0.823 T0.7423,0.8763 L0.7913,0.8775 L0.7314,0.9895 L0.4648,0.5038 L0.7299,0.0176 L0.7882,0.1229 L0.7404,0.1212 Q0.7244,0.138,0.7136,0.1617 T0.6982,0.2108 T0.6943,0.2632 T0.7003,0.3144 T0.7159,0.3586 T0.7396,0.3909 L0.8072,0.3926 Q0.7894,0.3796,0.7777,0.3479 T0.7635,0.2789 T0.7697,0.2011 T0.7987,0.1326 Q0.8092,0.1225,0.8204,0.1225 Q0.8321,0.1225,0.8426,0.1332 T0.8609,0.1623 T0.8731,0.2062 T0.8776,0.2592 Q0.8776,0.268,0.8768,0.2815 L0.9219,0.3628 L0.8644,0.3612 Q0.8403,0.3855,0.8284,0.4247 T0.8177,0.5029 T0.8309,0.5772 T0.8632,0.6305 Z M0.6759,0.6384 Q0.6576,0.625,0.6457,0.5914 T0.6319,0.5185 T0.6407,0.4375 T0.6751,0.3691 L0.609,0.367 Q0.5849,0.3918,0.5731,0.431 T0.5626,0.509 T0.5758,0.5833 T0.6082,0.6367 Z M0.6459,0.0025 L0.391,0.4551 L0.0035,0.4551 L0.246,0 Z M0.6553,1 L0.2421,1 L0,0.5449 L0.3875,0.5449 Z",type:0}},"":{type:5,data:{d:"M0.4996,0 Q0.5562,0,0.5961,0.0166 T0.6364,0.0566 L0.6368,0.0575 Q0.6368,0.0646,0.6324,0.0713 L0.6324,0.0692 Q0.6196,0.0868,0.5872,0.0986 T0.5166,0.1128 T0.4373,0.1057 T0.3689,0.0747 L0.3694,0.0747 Q0.3627,0.0663,0.3627,0.0575 Q0.3627,0.0336,0.4028,0.0168 T0.4996,0 Z M0.6324,0.0768 L0.717,0.1225 Q0.7321,0.1212,0.744,0.1212 Q0.8007,0.1212,0.8406,0.1378 T0.8809,0.1779 L0.8813,0.1783 Q0.8813,0.1846,0.8778,0.1909 Q0.8636,0.2093,0.8299,0.2208 T0.7573,0.2343 T0.6769,0.2253 T0.6089,0.1913 L0.6067,0.2567 Q0.6315,0.281,0.6707,0.2928 T0.7489,0.3035 T0.8231,0.2903 T0.8764,0.258 L0.8778,0.2085 L0.9898,0.2685 L0.504,0.5352 L0.0177,0.2701 L0.1227,0.2118 L0.1213,0.2596 Q0.1457,0.2836,0.1851,0.2955 T0.2635,0.3062 T0.3377,0.293 T0.3911,0.2605 L0.3928,0.193 Q0.38,0.2106,0.3483,0.2221 T0.2792,0.2364 T0.2013,0.2303 T0.1324,0.2013 Q0.1222,0.1909,0.1222,0.1795 Q0.1222,0.156,0.1623,0.1393 T0.2591,0.1225 Q0.2679,0.1225,0.2817,0.1233 L0.3627,0.078 L0.3609,0.1359 Q0.3857,0.1598,0.4249,0.1718 T0.5031,0.1825 T0.5773,0.1693 T0.6306,0.1367 Z M0.6386,0.3242 Q0.6253,0.3427,0.5917,0.3544 T0.5186,0.3683 T0.4376,0.3595 T0.3689,0.3251 L0.3671,0.3909 Q0.3915,0.4148,0.4309,0.4268 T0.5093,0.4375 T0.5835,0.4243 T0.6368,0.3918 Z M0.0027,0.354 L0.4553,0.6091 L0.4553,0.9962 L0,0.7542 Z M1,0.3448 L1,0.7576 L0.5447,1 L0.5447,0.6128 Z",type:0}},"":{type:5,data:{d:"M0,0.7699 Q0,0.7172,0.0277,0.6731 T0.103,0.6032 T0.2066,0.5774 Q0.2487,0.5774,0.2865,0.5922 T0.3526,0.6339 L0.3935,0.6715 Q0.4218,0.6984,0.438,0.7336 T0.4541,0.8075 Q0.4541,0.8602,0.4264,0.9046 T0.3511,0.9745 T0.2475,1 Q0.2054,1,0.1676,0.9852 T0.1016,0.9435 L0.0606,0.9059 Q0.0317,0.879,0.0159,0.8438 T0,0.7699 Z M0.5459,0.7699 Q0.5459,0.7172,0.5736,0.6731 T0.6489,0.6032 T0.7525,0.5774 Q0.7946,0.5774,0.8324,0.5922 T0.8984,0.6339 L0.9394,0.6715 Q0.9579,0.6892,0.9714,0.7105 T0.9925,0.7567 T1,0.8075 Q1,0.8602,0.9723,0.9046 T0.897,0.9745 T0.7934,1 Q0.7513,1,0.7135,0.9852 T0.6474,0.9435 L0.6065,0.9059 Q0.5776,0.879,0.5617,0.8438 T0.5459,0.7699 Z M0,0.1925 Q0,0.1398,0.0277,0.0957 T0.103,0.0258 T0.2066,0 Q0.2487,0,0.2865,0.0148 T0.3526,0.0565 L0.3935,0.0941 Q0.4218,0.121,0.438,0.1562 T0.4541,0.2301 Q0.4541,0.2828,0.4264,0.3269 T0.3511,0.3968 T0.2475,0.4226 Q0.2054,0.4226,0.1676,0.4078 T0.1016,0.3661 L0.0606,0.3285 Q0.0317,0.3016,0.0159,0.2664 T0,0.1925 Z M0.5459,0.1925 Q0.5459,0.1398,0.5736,0.0957 T0.6489,0.0258 T0.7525,0 Q0.7946,0,0.8324,0.0148 T0.8984,0.0565 L0.9394,0.0941 Q0.9677,0.121,0.9838,0.1562 T1,0.2301 Q1,0.2828,0.9723,0.3269 T0.897,0.3968 T0.7934,0.4226 Q0.7513,0.4226,0.7135,0.4078 T0.6474,0.3661 L0.6065,0.3285 Q0.5874,0.3108,0.5741,0.2895 T0.5534,0.2433 T0.5459,0.1925 Z M0.3301,0.6403 Q0.3306,0.6403,0.3312,0.6409 L0.3272,0.6371 L0.3272,0.6371 Z M0.8759,0.6403 Q0.8765,0.6403,0.8771,0.6409 Q0.8748,0.6387,0.8725,0.6371 L0.8731,0.6371 Z M0.3301,0.0629 Q0.3306,0.0629,0.3312,0.0634 L0.3272,0.0597 L0.3272,0.0602 Z M0.8759,0.0629 Q0.8765,0.0629,0.8771,0.0634 Q0.8748,0.0613,0.8725,0.0597 Q0.8731,0.0597,0.8731,0.0602 Z M0.779,0.4113 L0.7501,0.3849 Q0.7046,0.3844,0.6636,0.3661 Q0.7132,0.407,0.779,0.4113 Z M0.7957,0.4118 Q0.8477,0.4113,0.8918,0.3874 T0.9619,0.3223 T0.9885,0.2323 L0.9585,0.2048 Q0.9538,0.2769,0.8987,0.3282 T0.7657,0.3844 Z M0.9879,0.2167 Q0.9827,0.1554,0.9388,0.1097 Q0.9585,0.1478,0.959,0.1898 Z M0.2331,0.4113 L0.2043,0.3849 Q0.1587,0.3844,0.1177,0.3661 Q0.1673,0.407,0.2331,0.4113 Z M0.2499,0.4118 Q0.2885,0.4118,0.324,0.3973 T0.3852,0.3589 T0.4264,0.3019 T0.4426,0.2323 L0.4126,0.2048 Q0.408,0.2769,0.3529,0.3282 T0.2198,0.3844 Z M0.442,0.2167 Q0.4368,0.1554,0.393,0.1097 Q0.4126,0.1478,0.4132,0.1903 Z M0.779,0.9887 L0.7501,0.9624 Q0.7046,0.9618,0.6636,0.9435 Q0.7132,0.9844,0.779,0.9887 Z M0.7957,0.9892 Q0.8477,0.9887,0.8918,0.9648 T0.9619,0.8997 T0.9885,0.8097 L0.9585,0.7823 Q0.9538,0.8543,0.8987,0.9056 T0.7657,0.9618 Z M0.9879,0.7941 Q0.9827,0.7328,0.9388,0.6871 Q0.9585,0.7253,0.959,0.7672 Z M0.2331,0.9887 L0.2043,0.9624 Q0.1587,0.9618,0.1177,0.9435 Q0.1673,0.9844,0.2331,0.9887 Z M0.2499,0.9892 Q0.3018,0.9887,0.3459,0.9648 T0.416,0.8997 T0.4426,0.8102 L0.4126,0.7823 Q0.408,0.8543,0.3529,0.9056 T0.2198,0.9618 Z M0.442,0.7941 Q0.4368,0.7328,0.393,0.6871 Q0.4126,0.7253,0.4132,0.7672 Z M0.3191,0.6452 Q0.2943,0.622,0.2643,0.6091 T0.2066,0.5962 Q0.1795,0.5962,0.1497,0.6091 T0.0949,0.6441 T0.0537,0.6995 T0.0375,0.7699 Q0.0375,0.8419,0.0941,0.8941 Q0.1189,0.9172,0.1489,0.9304 T0.2066,0.9435 Q0.2337,0.9435,0.2634,0.9304 T0.3182,0.8952 T0.3595,0.8398 T0.3756,0.7699 Q0.3756,0.6978,0.3191,0.6452 Z M0.026,0.7731 L0.026,0.7699 Q0.026,0.6935,0.0854,0.6376 Q0.0658,0.6538,0.0514,0.6739 T0.0289,0.718 T0.0202,0.7677 Z M0.0271,0.7892 L0.0208,0.7833 Q0.0254,0.8419,0.0681,0.886 Q0.0664,0.8828,0.0646,0.879 Q0.0329,0.8382,0.0271,0.7892 Z M0.3866,0.7823 Q0.382,0.8511,0.3278,0.9016 Q0.3843,0.8565,0.3918,0.7871 Z M0.3872,0.7677 L0.393,0.7726 L0.393,0.7699 Q0.393,0.7349,0.3791,0.7038 Q0.3739,0.6973,0.3681,0.6914 Q0.3866,0.7274,0.3872,0.7677 Z M0.865,0.6452 Q0.8402,0.622,0.8102,0.6091 T0.7525,0.5962 Q0.7305,0.5962,0.7072,0.6043 T0.6619,0.628 T0.6223,0.6642 T0.5941,0.7124 T0.5834,0.7699 Q0.5834,0.8419,0.6399,0.8941 Q0.6561,0.9097,0.6757,0.9207 T0.7149,0.9376 T0.7525,0.9435 Q0.7796,0.9435,0.8093,0.9304 T0.8641,0.8952 T0.9054,0.8398 T0.9215,0.7699 Q0.9215,0.6978,0.865,0.6452 Z M0.5718,0.7731 L0.5718,0.7699 Q0.5718,0.6935,0.6313,0.6376 Q0.6013,0.6618,0.584,0.6954 T0.5661,0.7677 Z M0.573,0.7892 L0.5666,0.7833 Q0.5713,0.8419,0.614,0.886 Q0.6122,0.8828,0.6105,0.879 Q0.5788,0.8382,0.573,0.7892 Z M0.9325,0.7823 Q0.9279,0.8511,0.8736,0.9016 Q0.9308,0.8565,0.9377,0.7871 Z M0.9331,0.7672 L0.9388,0.7726 L0.9388,0.7699 Q0.9388,0.7349,0.925,0.7038 Q0.9198,0.6973,0.914,0.6914 Q0.9325,0.7274,0.9331,0.7672 Z M0.3191,0.0677 Q0.2943,0.0446,0.2643,0.0317 T0.2066,0.0188 Q0.1795,0.0188,0.1497,0.0317 T0.0949,0.0667 T0.0537,0.122 T0.0375,0.1925 Q0.0375,0.2645,0.0941,0.3167 Q0.1189,0.3398,0.1489,0.353 T0.2066,0.3661 Q0.2337,0.3661,0.2634,0.353 T0.3182,0.3177 T0.3595,0.2624 T0.3756,0.1925 Q0.3756,0.1204,0.3191,0.0677 Z M0.026,0.1957 L0.026,0.1925 Q0.026,0.1161,0.0854,0.0602 Q0.0554,0.0844,0.0381,0.118 T0.0202,0.1903 Z M0.0271,0.2118 L0.0208,0.2059 Q0.0254,0.2645,0.0681,0.3086 Q0.0664,0.3054,0.0646,0.3016 Q0.0329,0.2608,0.0271,0.2118 Z M0.3866,0.2048 Q0.382,0.2737,0.3278,0.3242 Q0.3849,0.279,0.3918,0.2097 Z M0.3872,0.1903 L0.393,0.1952 L0.393,0.1925 Q0.393,0.1581,0.3791,0.1263 Q0.3739,0.1199,0.3681,0.114 Q0.3866,0.15,0.3872,0.1903 Z M0.865,0.0677 Q0.8402,0.0446,0.8102,0.0317 T0.7525,0.0188 Q0.7253,0.0188,0.6956,0.0317 T0.6408,0.0667 T0.5995,0.122 T0.5834,0.1925 Q0.5834,0.2645,0.6399,0.3167 Q0.6647,0.3398,0.6947,0.353 T0.7525,0.3661 Q0.7796,0.3661,0.8093,0.353 T0.8641,0.3177 T0.9054,0.2624 T0.9215,0.1925 Q0.9215,0.1204,0.865,0.0677 Z M0.5718,0.1957 L0.5718,0.1925 Q0.5718,0.1161,0.6313,0.0602 Q0.6013,0.0844,0.584,0.118 T0.5661,0.1903 Z M0.573,0.2118 L0.5666,0.2059 Q0.5713,0.2645,0.614,0.3086 Q0.6122,0.3054,0.6105,0.3016 Q0.5788,0.2608,0.573,0.2118 Z M0.9325,0.2048 Q0.9279,0.2737,0.8736,0.3242 Q0.9308,0.279,0.9377,0.2097 Z M0.9331,0.1898 L0.9388,0.1952 L0.9388,0.1925 Q0.9388,0.1575,0.925,0.1263 Q0.9198,0.1199,0.914,0.114 Q0.9325,0.15,0.9331,0.1898 Z",type:0}},"":{type:5,data:{d:"M0.7639,0.1974 L0.9593,0.1974 L0.9593,0.1443 L0.7639,0.1443 L0.7639,0.1974 Z M0.7586,0.109 L0.9727,0.109 Q0.9839,0.109,0.992,0.1153 T1,0.1291 L1,0.382 Q1,0.3906,0.9912,0.3964 T0.9727,0.4021 L0.7586,0.4021 Q0.7468,0.4021,0.739,0.3956 T0.7313,0.382 L0.7313,0.1291 Q0.7313,0.1246,0.7339,0.1207 T0.7406,0.1143 T0.7495,0.1104 T0.7586,0.109 Z M0.7639,0.6575 L0.9593,0.6575 L0.9593,0.6044 L0.7639,0.6044 L0.7639,0.6575 Z M0.7313,0.8347 L0.7313,0.5843 Q0.7313,0.5757,0.7401,0.5699 T0.7586,0.5641 L0.9706,0.5641 Q0.9818,0.5641,0.9898,0.5705 T0.9979,0.5843 L0.9979,0.8347 Q0.9979,0.8433,0.989,0.8491 T0.9706,0.8549 L0.7586,0.8549 Q0.7468,0.8549,0.739,0.8483 T0.7313,0.8347 Z M0,1 L0,0 L0.7607,0 L0.7607,1 L0,1 Z",type:0}},"":{type:5,data:{d:"M0.9764,0.0029 L0.3179,0.4552 L0,0.4552 L0,0 Z M1,1 L0,1 L0,0.5448 L0.3091,0.5448 Z",type:0}},"":{type:5,data:{d:"M0.0236,0.0029 L0.6824,0.4552 L1,0.4552 L1,0 Z M0,1 L1,1 L1,0.5448 L0.6912,0.5448 Z",type:0}},"":{type:3,data:"M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z"},"":{type:3,data:"M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z"},"":{type:3,data:"M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z"},"":{type:3,data:"M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z M.25,.15 L1,.15 L1,.85 L.25,.85 Z"},"":{type:3,data:"M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z M0,.15 L1,.15 L1,.85 L0,.85 Z"},"":{type:3,data:"M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z M0,.15 L.75,.15 L.75,.85 L0,.85 Z"},"":{type:5,data:{d:"M0.6574,0.303 Q0.623,0.291,0.5852,0.285 T0.5082,0.279 T0.4311,0.285 T0.359,0.303 L0.3082,0.248 Q0.3525,0.232,0.4041,0.2235 T0.5082,0.215 T0.6123,0.2235 T0.7082,0.248 Z",type:0},scaleType:1},"":{type:5,data:{d:"M0.8557,0.582 L0.7656,0.551 Q0.7852,0.53,0.7951,0.507 T0.8049,0.46 Q0.8049,0.424,0.782,0.3905 T0.718,0.332 T0.6221,0.293 T0.5082,0.279 L0.5082,0.215 Q0.5607,0.215,0.6123,0.2235 T0.709,0.248 T0.7918,0.287 T0.8557,0.3375 T0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.582 Z",type:0},scaleType:1},"":{type:5,data:{d:"M0.5082,0.705 Q0.482,0.705,0.4557,0.703 T0.4049,0.697 L0.4311,0.635 Q0.4508,0.638,0.4697,0.6395 T0.5082,0.641 Q0.5672,0.641,0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 Q0.8049,0.436,0.7951,0.413 T0.7656,0.369 L0.8557,0.338 Q0.882,0.365,0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z",type:0},scaleType:1},"":{type:5,data:{d:"M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 L0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 T0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 L0.9098,0.46 Q0.9098,0.492,0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z",type:0},scaleType:1},"":{type:5,data:{d:"M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.338 L0.2508,0.369 Q0.2311,0.39,0.2213,0.413 T0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 Q0.5279,0.641,0.5467,0.6395 T0.5852,0.635 L0.6115,0.697 Q0.5869,0.701,0.5607,0.703 T0.5082,0.705 Z",type:0},scaleType:1},"":{type:5,data:{d:"M0.1607,0.582 Q0.1344,0.555,0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.3375 T0.2246,0.287 T0.3074,0.248 T0.4041,0.2235 T0.5082,0.215 L0.5082,0.279 Q0.4492,0.279,0.3943,0.293 T0.2984,0.332 T0.2344,0.3905 T0.2115,0.46 Q0.2115,0.484,0.2213,0.507 T0.2508,0.551 Z",type:0},scaleType:1},"":i.LINE_H,"":i.LINE_V,"":i.FADE_RIGHT,"":i.FADE_LEFT,"":i.FADE_DOWN,"":i.FADE_UP,"":i.CURVE_DOWN_RIGHT,"":i.CURVE_DOWN_LEFT,"":i.CURVE_UP_RIGHT,"":i.CURVE_UP_LEFT,"":[i.LINE_V,i.CURVE_UP_RIGHT],"":[i.LINE_V,i.CURVE_DOWN_RIGHT],"":[i.CURVE_DOWN_RIGHT,i.CURVE_UP_RIGHT],"":[i.LINE_V,i.CURVE_UP_LEFT],"":[i.LINE_V,i.CURVE_DOWN_LEFT],"":[i.CURVE_DOWN_LEFT,i.CURVE_UP_LEFT],"":[i.LINE_H,i.CURVE_DOWN_LEFT],"":[i.LINE_H,i.CURVE_DOWN_RIGHT],"":[i.CURVE_DOWN_LEFT,i.CURVE_DOWN_RIGHT],"":[i.LINE_H,i.CURVE_UP_LEFT],"":[i.LINE_H,i.CURVE_UP_RIGHT],"":[i.CURVE_UP_LEFT,i.CURVE_UP_RIGHT],"":[i.LINE_V,i.CURVE_UP_LEFT,i.CURVE_UP_RIGHT],"":[i.LINE_V,i.CURVE_DOWN_LEFT,i.CURVE_DOWN_RIGHT],"":[i.LINE_H,i.CURVE_DOWN_LEFT,i.CURVE_UP_LEFT],"":[i.LINE_H,i.CURVE_DOWN_RIGHT,i.CURVE_UP_RIGHT],"":[i.LINE_V,i.CURVE_DOWN_RIGHT,i.CURVE_UP_LEFT],"":[i.LINE_V,i.CURVE_DOWN_LEFT,i.CURVE_UP_RIGHT],"":[i.LINE_H,i.CURVE_DOWN_RIGHT,i.CURVE_UP_LEFT],"":[i.LINE_H,i.CURVE_DOWN_LEFT,i.CURVE_UP_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE],"":i.NODE_STROKE,"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_LEFT],"":[i.NODE_STROKE,i.NODE_LINE_LEFT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_DOWN],"":[i.NODE_STROKE,i.NODE_LINE_DOWN],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP],"":[i.NODE_STROKE,i.NODE_LINE_UP],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_UP],"":[i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_UP],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT],"":[i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_LEFT],"":[i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_LEFT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_DOWN,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_DOWN,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT],"":[i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_FILL,i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"":[i.NODE_STROKE,i.NODE_LINE_UP,i.NODE_LINE_DOWN,i.NODE_LINE_LEFT,i.NODE_LINE_RIGHT],"🬀":s(1),"🬁":s(2),"🬂":s(3),"🬃":s(4),"🬄":s(5),"🬅":s(6),"🬆":s(7),"🬇":s(8),"🬈":s(9),"🬉":s(10),"🬊":s(11),"🬋":s(12),"🬌":s(13),"🬍":s(14),"🬎":s(15),"🬏":s(16),"🬐":s(17),"🬑":s(18),"🬒":s(19),"🬓":s(20),"🬔":s(22),"🬕":s(23),"🬖":s(24),"🬗":s(25),"🬘":s(26),"🬙":s(27),"🬚":s(28),"🬛":s(29),"🬜":s(30),"🬝":s(31),"🬞":s(32),"🬟":s(33),"🬠":s(34),"🬡":s(35),"🬢":s(36),"🬣":s(37),"🬤":s(38),"🬥":s(39),"🬦":s(40),"🬧":s(41),"🬨":s(43),"🬩":s(44),"🬪":s(45),"🬫":s(46),"🬬":s(47),"🬭":s(48),"🬮":s(49),"🬯":s(50),"🬰":s(51),"🬱":s(52),"🬲":s(53),"🬳":s(54),"🬴":s(55),"🬵":s(56),"🬶":s(57),"🬷":s(58),"🬸":s(59),"🬹":s(60),"🬺":s(61),"🬻":s(62),"🬼":{type:3,data:"M0,0.6667 L0,1 L0.5,1 Z"},"🬽":{type:3,data:"M0,0.6667 L0,1 L1,1 Z"},"🬾":{type:3,data:"M0,0.3333 L0,1 L0.5,1 Z"},"🬿":{type:3,data:"M0,0.3333 L0,1 L1,1 Z"},"🭀":{type:3,data:"M0,0 L0,1 L0.5,1 Z"},"🭁":{type:3,data:"M0,0.3333 L0.5,0 L1,0 L1,1 L0,1 Z"},"🭂":{type:3,data:"M0,0.3333 L1,0 L1,1 L0,1 Z"},"🭃":{type:3,data:"M0,0.6667 L0.5,0 L1,0 L1,1 L0,1 Z"},"🭄":{type:3,data:"M0,0.6667 L1,0 L1,1 L0,1 Z"},"🭅":{type:3,data:"M0,1 L0.5,0 L1,0 L1,1 Z"},"🭆":{type:3,data:"M0,0.6667 L1,0.3333 L1,1 L0,1 Z"},"🭇":{type:3,data:"M0.5,1 L1,0.6667 L1,1 Z"},"🭈":{type:3,data:"M0,1 L1,0.6667 L1,1 Z"},"🭉":{type:3,data:"M0.5,1 L1,0.3333 L1,1 Z"},"🭊":{type:3,data:"M0,1 L1,0.3333 L1,1 Z"},"🭋":{type:3,data:"M0.5,1 L1,0 L1,1 Z"},"🭌":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 L1,0.3333 Z"},"🭍":{type:3,data:"M0,0 L0,1 L1,1 L1,0.3333 Z"},"🭎":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 L1,0.6667 Z"},"🭏":{type:3,data:"M0,0 L0,1 L1,1 L1,0.6667 Z"},"🭐":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 Z"},"🭑":{type:3,data:"M0,0.3333 L0,1 L1,1 L1,0.6667 Z"},"🭒":{type:3,data:"M0,0.6667 L0.5,1 L1,1 L1,0 L0,0 Z"},"🭓":{type:3,data:"M0,0.6667 L1,1 L1,0 L0,0 Z"},"🭔":{type:3,data:"M0,0.3333 L0.5,1 L1,1 L1,0 L0,0 Z"},"🭕":{type:3,data:"M0,0.3333 L1,1 L1,0 L0,0 Z"},"🭖":{type:3,data:"M0,0 L0.5,1 L1,1 L1,0 Z"},"🭗":{type:3,data:"M0,0.3333 L0,0 L0.5,0 Z"},"🭘":{type:3,data:"M0,0.3333 L0,0 L1,0 Z"},"🭙":{type:3,data:"M0,0.6667 L0,0 L0.5,0 Z"},"🭚":{type:3,data:"M0,0.6667 L0,0 L1,0 Z"},"🭛":{type:3,data:"M0,1 L0,0 L0.5,0 Z"},"🭜":{type:3,data:"M0,0.6667 L0,0 L1,0 L1,0.3333 Z"},"🭝":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 L1,0.6667 Z"},"🭞":{type:3,data:"M0,1 L0,0 L1,0 L1,0.6667 Z"},"🭟":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 L1,0.3333 Z"},"🭠":{type:3,data:"M0,1 L0,0 L1,0 L1,0.3333 Z"},"🭡":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 Z"},"🭢":{type:3,data:"M0.5,0 L1,0 L1,0.3333 Z"},"🭣":{type:3,data:"M0,0 L1,0 L1,0.3333 Z"},"🭤":{type:3,data:"M0.5,0 L1,0 L1,0.6667 Z"},"🭥":{type:3,data:"M0,0 L1,0 L1,0.6667 Z"},"🭦":{type:3,data:"M0.5,0 L1,0 L1,1 Z"},"🭧":{type:3,data:"M0,0.3333 L1,0.6667 L1,0 L0,0 Z"},"🭨":{type:3,data:"M0,0 L1,0 L1,1 L0,1 L0.5,0.5 Z"},"🭩":{type:3,data:"M0,0 L0.5,0.5 L1,0 L1,1 L0,1 Z"},"🭪":{type:3,data:"M0,0 L1,0 L0.5,0.5 L1,1 L0,1 Z"},"🭫":{type:3,data:"M0,0 L1,0 L1,1 L0.5,0.5 L0,1 Z"},"🭬":{type:3,data:"M0,0 L0.5,0.5 L0,1 Z"},"🭭":{type:3,data:"M0,0 L1,0 L0.5,0.5 Z"},"🭮":{type:3,data:"M1,0 L1,1 L0.5,0.5 Z"},"🭯":{type:3,data:"M0,1 L1,1 L0.5,0.5 Z"},"🭰":{type:0,data:[{x:1,y:0,w:1,h:8}]},"🭱":{type:0,data:[{x:2,y:0,w:1,h:8}]},"🭲":{type:0,data:[{x:3,y:0,w:1,h:8}]},"🭳":{type:0,data:[{x:4,y:0,w:1,h:8}]},"🭴":{type:0,data:[{x:5,y:0,w:1,h:8}]},"🭵":{type:0,data:[{x:6,y:0,w:1,h:8}]},"🭶":{type:0,data:[{x:0,y:1,w:8,h:1}]},"🭷":{type:0,data:[{x:0,y:2,w:8,h:1}]},"🭸":{type:0,data:[{x:0,y:3,w:8,h:1}]},"🭹":{type:0,data:[{x:0,y:4,w:8,h:1}]},"🭺":{type:0,data:[{x:0,y:5,w:8,h:1}]},"🭻":{type:0,data:[{x:0,y:6,w:8,h:1}]},"🭼":{type:0,data:[{x:0,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}]},"🭽":{type:0,data:[{x:0,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}]},"🭾":{type:0,data:[{x:7,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}]},"🭿":{type:0,data:[{x:7,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}]},"🮀":{type:0,data:[{x:0,y:0,w:8,h:1},{x:0,y:7,w:8,h:1}]},"🮁":{type:0,data:[{x:0,y:0,w:8,h:1},{x:0,y:2,w:8,h:1},{x:0,y:4,w:8,h:1},{x:0,y:7,w:8,h:1}]},"🮂":{type:0,data:[{x:0,y:0,w:8,h:2}]},"🮃":{type:0,data:[{x:0,y:0,w:8,h:3}]},"🮄":{type:0,data:[{x:0,y:0,w:8,h:5}]},"🮅":{type:0,data:[{x:0,y:0,w:8,h:6}]},"🮆":{type:0,data:[{x:0,y:0,w:8,h:7}]},"🮇":{type:0,data:[{x:6,y:0,w:2,h:8}]},"🮈":{type:0,data:[{x:5,y:0,w:3,h:8}]},"🮉":{type:0,data:[{x:3,y:0,w:5,h:8}]},"🮊":{type:0,data:[{x:2,y:0,w:6,h:8}]},"🮋":{type:0,data:[{x:1,y:0,w:7,h:8}]},"🮌":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L0.5,0 L0.5,1 L0,1 Z"},"🮍":{type:1,data:[[1,0],[0,1]],clipPath:"M0.5,0 L1,0 L1,1 L0.5,1 Z"},"🮎":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L1,0.5 L0,0.5 Z"},"🮏":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0.5 L1,0.5 L1,1 L0,1 Z"},"🮐":{type:1,data:[[0,1],[1,0]]},"🮑":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0.5 L1,0.5 L1,1 L0,1 Z"},{type:0,data:[{x:0,y:0,w:8,h:4}]}],"🮒":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0 L1,0 L1,0.5 L0,0.5 Z"},{type:0,data:[{x:0,y:4,w:8,h:4}]}],"🮔":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0 L0.5,0 L0.5,1 L0,1 Z"},{type:0,data:[{x:4,y:0,w:4,h:8}]}],"🮕":{type:0,data:[{x:0,y:0,w:2,h:2},{x:4,y:0,w:2,h:2},{x:2,y:2,w:2,h:2},{x:6,y:2,w:2,h:2},{x:0,y:4,w:2,h:2},{x:4,y:4,w:2,h:2},{x:2,y:6,w:2,h:2},{x:6,y:6,w:2,h:2}]},"🮖":{type:0,data:[{x:2,y:0,w:2,h:2},{x:6,y:0,w:2,h:2},{x:0,y:2,w:2,h:2},{x:4,y:2,w:2,h:2},{x:2,y:4,w:2,h:2},{x:6,y:4,w:2,h:2},{x:0,y:6,w:2,h:2},{x:4,y:6,w:2,h:2}]},"🮗":{type:0,data:[{x:0,y:2,w:8,h:2},{x:0,y:6,w:8,h:2}]},"🮘":{type:2,data:"M-0.25,-0.25 L1.25,1.25 M-0.25,0 L1,1.25 M-0.25,0.25 L0.75,1.25 M-0.25,0.5 L0.5,1.25 M0,-0.25 L1.25,1 M0.25,-0.25 L1.25,0.75 M0.5,-0.25 L1.25,0.5 M-0.25,0.75 L0.25,1.25 M0.75,-0.25 L1.25,0.25",strokeWidth:1},"🮙":{type:2,data:"M-0.25,0.5 L0.5,-0.25 M-0.25,0.75 L0.75,-0.25 M-0.25,1 L1,-0.25 M-0.25,1.25 L1.25,-0.25 M0,1.25 L1.25,0 M0.25,1.25 L1.25,0.25 M0.5,1.25 L1.25,0.5 M-0.25,0.25 L0.25,-0.25 M0.75,1.25 L1.25,0.75",strokeWidth:1},"🮚":{type:5,data:{d:"M0,0 L.5,.5 L0,1 L1,1 L.5,.5 L1,0",type:0}},"🮛":{type:5,data:{d:"M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1",type:0}},"🮜":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L0,1 Z"},"🮝":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L1,1 Z"},"🮞":{type:1,data:[[1,0],[0,1]],clipPath:"M1,0 L1,1 L0,1 Z"},"🮟":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,1 L0,1 Z"},"🮠":{type:2,data:"M.5,0 L0,.5",strokeWidth:1},"🮡":{type:2,data:"M.5,0 L1,.5",strokeWidth:1},"🮢":{type:2,data:"M0,.5 L.5,1",strokeWidth:1},"🮣":{type:2,data:"M1,.5 L.5,1",strokeWidth:1},"🮤":{type:2,data:"M.5,0 L0,.5 L.5,1",strokeWidth:1},"🮥":{type:2,data:"M.5,0 L1,.5 L.5,1",strokeWidth:1},"🮦":{type:2,data:"M0,.5 L.5,1 L1,.5",strokeWidth:1},"🮧":{type:2,data:"M0,.5 L.5,0 L1,.5",strokeWidth:1},"🮨":{type:2,data:"M.5,0 L0,.5 M1,.5 L.5,1",strokeWidth:1},"🮩":{type:2,data:"M.5,0 L1,.5 M0,.5 L.5,1",strokeWidth:1},"🮪":{type:2,data:"M.5,0 L1,.5 L.5,1 L0,.5",strokeWidth:1},"🮫":{type:2,data:"M.5,0 L0,.5 L.5,1 L1,.5",strokeWidth:1},"🮬":{type:2,data:"M0,.5 L.5,0 L1,.5 L.5,1",strokeWidth:1},"🮭":{type:2,data:"M1,.5 L.5,0 L0,.5 L.5,1",strokeWidth:1},"🮮":{type:2,data:"M.5,0 L1,.5 L.5,1 L0,.5 Z",strokeWidth:1},"🮯":{type:2,data:"M0,.5 L1,.5 M.5,.35 L.5,.65",strokeWidth:1},"🮰":{type:3,data:"M0.1,0.2 L0.1,.8 L.4,.6 L.9,0.6 Z",scaleType:1},"🮱":{type:4,data:{d:"M.1,.525 L.35,.675 L.9,.35",type:1},scaleType:1},"🮲":{type:3,data:"M.29,.27 L0.13,.56 L.22,.59 L.35,0.35 L.67,.35 L.57,.57 L.71,.76 L.22,.76 L.42,1 L.53,.98 L.43,.86 L.9,.86 L.71,.6 L1,.6 L1,.52 L.83,.52 L.92,.36 L1,.36 L1,.27Z M.99,.13 A.12,.12,0,1,1,.75,.13 A.12,.12,0,1,1,.99,.13"},"🮳":{type:3,data:"M0,.27 L.3,.27 L.55,.12 L.63,.18 L.33,.36 L0,.36 M0,.52 L.33,.52 L.59,.89 L.73,.89 L.73,.98 L.53,.98 L.28,.6 L0,.6"},"🮴":{type:4,data:{d:"M.15,.55 L.5,.4 L.5,.45 L.65,.45 L.65,.35 L.85,.35 L.85,.625 L.5,.625 L.5,.7 Z",type:0},scaleType:1},"🮵":[{type:5,data:{d:"M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z",type:0}},{type:5,data:{d:"M.15,.5 L.5,.35 L.5,.425 L.85,.425 L.85,.575 L.5,.575 L.5,.65 Z",type:0},scaleType:1}],"🮶":[{type:5,data:{d:"M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z",type:0}},{type:5,data:{d:"M.85,.5 L.5,.35 L.5,.425 L.15,.425 L.15,.575 L.5,.575 L.5,.65 Z",type:0},scaleType:1}],"🮷":[{type:5,data:{d:"M.875,0 L1,0 L1,1 L.875,1 Z",type:0}},{type:5,data:{d:"M.5,.675 L.2,.5 L.35,.5 L.35,.325 L.65,.325 L.65,.5 L.8,.5 Z",type:0},scaleType:1}],"🮸":[{type:5,data:{d:"M.875,0 L1,0 L1,1 L.875,1 Z",type:0}},{type:5,data:{d:"M.5,.325 L.2,.5 L.35,.5 L.35,.675 L.65,.675 L.65,.5 L.8,.5 Z",type:0},scaleType:1}],"🮹":{type:5,data:{d:"M1,.89 L.11,.89 L.11,.37 L.36,.12 L.74,.12 L.96,.34 L1,.34 L1,.405 L.92,.405 L.69,.185 L.41,.185 L.21,.42 L.21,.825 L1,.825 Z",type:0}},"🮺":{type:5,data:{d:"M0,.89 L0,.825 L.78,.825 L.78,.53 L.7,.415 L0,.415 L0,.35 L.75,.35 L.88,.48 L.88,.89 Z",type:0}},"🮻":{type:5,data:{d:"M.31,.275 L.44,.275 L.44,.47 L.05,.47 L.05,.405 L.31,.405 Z M.56,.275 L.69,.275 L.69,.405 L.95,.405 L.95,.47 L.56,.47 Z M.05,.53 L.44,.53 L.44,.725 L.31,.725 L.31,.595 L.05,.595 Z M.56,.53 L.95,.53 L.95,.595 L.69,.595 L.69,.725 L.56,.725 Z",type:0},scaleType:1},"🮼":[{type:3,data:"M0,0 L1,0 L1,1 L0,1 L0,.935 L.885,.935 L.885,.065 L0,.065 Z"},{type:3,data:"M.67,.5 A.17,.085,0,1,1,.33,.5 A.17,.085,0,1,1,.67,.5",scaleType:1}],"🮽":{type:4,data:{d:"M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1 Z",type:1}},"🮾":{type:4,data:{d:"M1,.5 L.5,1",type:1}},"🮿":{type:4,data:{d:"M.5,0 L1,.5 L.5,1 L0,.5 Z",type:1}},"🯀":{type:3,data:"M.16,.445 A.02,.01,0,0,1,.39,.33 L.5,.375 L.61,.33 A.02,.01,0,0,1,.84,.445 L.75,.5 L.84,.555 A.02,.01,0,0,1,.61,.67 L.5,.625 L.39,.67 A.02,.01,0,0,1,.16,.555 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z",scaleType:1},"🯁":{type:3,data:"M0.142308,0.924 Q0.130770,0.924,0.120193,0.922 T0.101924,0.916 T0.089424,0.9065 T0.084616,0.895 V0.025 Q0.084616,0.019,0.089424,0.0135 T0.101924,0.004 T0.120193,-0.002 T0.142308,-0.004 Q0.150000,-0.004,0.157693,-0.003 T0.173078,0.000 L0.430770,0.085 Q0.451924,0.059,0.494232,0.041 T0.587501,0.013 T0.692309,-0.0005 T0.800001,-0.004 H1 V0.055 H0.800001 Q0.771155,0.055,0.742309,0.056 T0.685578,0.060 T0.630770,0.0685 T0.580770,0.0825 T0.543270,0.1045 T0.528847,0.133 V0.647 H0.530770 Q0.596155,0.645,0.659616,0.638 T0.782693,0.6165 T0.893270,0.5805 T1,0.531 V0.623 Q0.934616,0.644,0.880770,0.6595 T0.769232,0.685 T0.650963,0.700 T0.528847,0.707 V0.787 Q0.528847,0.802,0.543270,0.8155 T0.580770,0.8375 T0.630770,0.8515 T0.685578,0.860 T0.742309,0.864 T0.800001,0.865 H1 V0.924 H0.800001 Q0.746155,0.924,0.692309,0.9205 T0.587501,0.907 T0.494232,0.879 T0.430770,0.835 L0.173078,0.920 Q0.165386,0.922,0.157693,0.923 T0.142308,0.924 Z M0.2,0.841 L0.415385,0.770 V0.150 L0.2,0.079 V0.841 Z M1,0.698 Q0.973077,0.694,0.969231,0.6885 T0.965385,0.677 Q0.965385,0.672,0.969231,0.6665 T1,0.657 V0.698 Z"},"🯂":{type:3,data:"M0,0.623 V0.531 Q0.044231,0.512,0.0625,0.4905 T0.092308,0.4465 T0.108654,0.4005 T0.113462,0.354 V0.351 Q0.113462,0.345,0.117308,0.3395 T0.129808,0.3305 T0.148846,0.3245 T0.171154,0.323 H0.501923 Q0.532692,0.323,0.563462,0.3185 T0.617308,0.303 T0.651923,0.2765 T0.663462,0.2435 V0.241 Q0.663462,0.219,0.657692,0.197 T0.639423,0.1545 T0.604808,0.115 T0.549615,0.082 T0.475577,0.0615 T0.392308,0.055 H0 V-0.004 H1 V0.055 H0.667308 Q0.694231,0.071,0.713462,0.09 T0.746154,0.129 T0.766346,0.1705 T0.775,0.213 H1 V0.316 Q0.971154,0.305,0.955769,0.2965 T0.920192,0.2825 T0.876923,0.2745 T0.830769,0.272 H0.773077 Q0.767308,0.297,0.743269,0.319 T0.680769,0.355 T0.595192,0.375 T0.501923,0.381 H0.227308 Q0.223462,0.415,0.211,0.4485 T0.172692,0.5135 T0.108269,0.573 T0,0.623 Z M0.611538,0.924 H0 V0.865 H0.611538 Q0.642308,0.865,0.673077,0.8605 T0.727885,0.845 T0.762308,0.8185 T0.773077,0.787 V0.785 Q0.773077,0.769,0.762308,0.7535 T0.727885,0.727 T0.673077,0.7115 T0.611538,0.707 H0.059615 Q0.048077,0.707,0.037115,0.7045 T0,0.694 V0.653 Q0.026923,0.648,0.037115,0.646 T0.059615,0.644 H0.722692 Q0.753462,0.644,0.784231,0.6395 T0.839038,0.624 T0.873846,0.5975 T0.884615,0.566 V0.564 Q0.884615,0.548,0.873846,0.532 T0.839038,0.5055 T0.784231,0.49 T0.722692,0.4855 H0.392308 Q0.380769,0.4855,0.370192,0.483 T0.351923,0.4765 T0.339423,0.467 T0.334615,0.4555 T0.339423,0.444 T0.351923,0.4345 T0.370192,0.428 T0.392308,0.4255 H0.832692 Q0.855769,0.4255,0.878846,0.4235 T0.922115,0.416 T0.957692,0.4015 T1,0.3815 V0.4685 Q0.975,0.4705,0.966346,0.4725 T0.95,0.4765 Q0.961538,0.4835,0.969231,0.4915 T1,0.5075 V0.6205 Q0.965385,0.6455,0.926923,0.665 T0.84,0.6935 Q0.866923,0.7115,0.877596,0.7345 T0.894231,0.7855 V0.787 Q0.894231,0.815,0.876923,0.8425 T0.820192,0.8895 T0.726923,0.916 T0.611538,0.924 Z"},"🯃":{type:3,data:"M0,0.473 V0.386 Q0.025490,0.378,0.028431,0.3695 T0.031373,0.352 V0.35 Q0.031373,0.342,0.028431,0.333 T0,0.316 V0.213 H0.652941 Q0.684314,0.213,0.715686,0.2085 T0.770588,0.193 T0.804902,0.1665 T0.815686,0.135 V0.133 Q0.815686,0.117,0.804902,0.101 T0.770588,0.0745 T0.715686,0.0595 T0.652941,0.055 H0 V-0.004 H0.652941 Q0.707843,-0.004,0.761765,0.004 T0.856863,0.031 T0.915686,0.0775 T0.933333,0.133 V0.135 Q0.933333,0.163,0.915686,0.1905 T0.856863,0.237 T0.761765,0.264 T0.652941,0.272 H0.109804 Q0.131373,0.29,0.140196,0.31 T0.149020,0.35 V0.352 Q0.149020,0.37,0.142157,0.388 T0.118627,0.4225 T0.076471,0.452 T0,0.473 Z M0,0.625 V0.513 Q0.029412,0.526,0.032353,0.54 T0.035294,0.568 V0.57 Q0.035294,0.584,0.032353,0.598 T0,0.625 Z"},"🯄":{type:3,data:"M0.019231,1.085 V-0.165 H0.980769 V1.085 H0.019231 Z M0.446154,0.527 H0.553846 Q0.553846,0.511,0.5625,0.495 T0.591346,0.466 T0.631731,0.4405 T0.666346,0.4135 T0.688462,0.3825 T0.696154,0.35 Q0.696154,0.33,0.682692,0.311 T0.641346,0.2785 T0.575,0.259 T0.498077,0.253 T0.421154,0.259 T0.356731,0.279 T0.315385,0.3125 T0.301923,0.352 V0.357 H0.409615 V0.354 Q0.409615,0.345,0.414423,0.335 T0.431731,0.318 T0.4625,0.3075 T0.498077,0.304 Q0.517308,0.304,0.534615,0.307 T0.564423,0.3165 T0.582692,0.332 T0.588462,0.35 Q0.588462,0.366,0.572115,0.3805 T0.535577,0.4075 T0.496154,0.4335 T0.465385,0.4625 T0.45,0.4945 T0.446154,0.527 Z M0.5,0.667 Q0.519231,0.667,0.5375,0.664 T0.569231,0.654 T0.588462,0.637 T0.594231,0.617 T0.588462,0.5975 T0.569231,0.581 T0.5375,0.571 T0.5,0.568 T0.4625,0.571 T0.430769,0.581 T0.411538,0.5975 T0.405769,0.617 T0.411538,0.637 T0.430769,0.654 T0.4625,0.664 T0.5,0.667 Z",scaleType:1},"🯅":{type:5,data:{d:"M.44,.425 L.44,.51 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.51 L.56,.425 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"🯆":{type:5,data:{d:"M.44,.425 L.44,.46 L.23,.385 L.19,.43 L.44,.51 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.61,.735 L.7,.71 L.56,.56 L.56,.51 L.81,.43 L.77,.385 L.56,.46 L.56,.425 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"🯇":{type:5,data:{d:"M.44,.425 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.425 Z M.18,.53 L.23,.575 L.81,.43 L.77,.385 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"🯈":{type:5,data:{d:"M.44,.425 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.62,.735 L.71,.71 L.56,.56 L.56,.425 Z M.23,.385 L.18,.43 L.77,.575 L.81,.53 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"🯉":{type:5,data:{d:"M.44,.425 L.45,.495 L.15,.645 L.34,.645 L.34,.7 L.44,.7 L.44,.645 L.56,.645 L.56,.7 L.66,.7 L.66,.645 L.84,.645 L.54,.495 L.56,.425 Z M.39,.6 L.5,.55 L.60,.6 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"🯊":{type:5,data:{d:"M.26,.375 L.5,.255 L.74,.375 L.74,.665 L.5,.55 L.26,.665 Z M.37,.395 L.37,.54 L.5,.475 L.63,.54 L.63,.395 L.5,.33 Z",type:0},scaleType:1},"🯋":{type:5,data:{d:"M.09,.41 L.32,.295 L.5,.375 L.68,.295 L.91,.41 L.75,.5 L.91,.59 L.68,.705 L.5,.625 L.32,.705 L.09,.59 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z",type:0},scaleType:1},"🯌":{type:5,data:{d:"M.55,.305 L.88,.305 L.88,.355 L.65,.355 L.65,.47 L.88,.47 L.88,.52 L.55,.52 Z",type:0},scaleType:1},"🯍":{type:5,data:{d:"M.38,.39 L.5,.33 L.62,.39 L.62,.53 L.5,.47 L.38,.53 Z",type:0},scaleType:1},"🯎":{type:3,data:"M0,0 L0.6667,0 L0.6667,1 L0,1 Z"},"🯏":{type:3,data:"M0,0 L0.3333,0 L0.3333,1 L0,1 Z"},"🯐":{type:2,data:"M1,.5 L0,1",strokeWidth:1},"🯑":{type:2,data:"M1,0 L0,.5",strokeWidth:1},"🯒":{type:2,data:"M0,0 L1,.5",strokeWidth:1},"🯓":{type:2,data:"M0,.5 L1,1",strokeWidth:1},"🯔":{type:2,data:"M0,0 L.5,1",strokeWidth:1},"🯕":{type:2,data:"M.5,0 L1,1",strokeWidth:1},"🯖":{type:2,data:"M1,0 L.5,1",strokeWidth:1},"🯗":{type:2,data:"M.5,0 L0,1",strokeWidth:1},"🯘":{type:2,data:"M0,0 L.5,.5 L1,0",strokeWidth:1},"🯙":{type:2,data:"M1,0 L.5,.5 L1,1",strokeWidth:1},"🯚":{type:2,data:"M0,1 L.5,.5 L1,1",strokeWidth:1},"🯛":{type:2,data:"M0,0 L.5,.5 L0,1",strokeWidth:1},"🯜":{type:2,data:"M0,0 L.5,1 L1,0",strokeWidth:1},"🯝":{type:2,data:"M1,0 L0,.5 L1,1",strokeWidth:1},"🯞":{type:2,data:"M0,1 L.5,0 L1,1",strokeWidth:1},"🯟":{type:2,data:"M0,0 L1,.5 L0,1",strokeWidth:1},"🯠":{type:5,data:{d:"M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0",type:1}},"🯡":{type:5,data:{d:"M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1",type:1}},"🯢":{type:5,data:{d:"M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1",type:1}},"🯣":{type:5,data:{d:"M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1",type:1}},"🯤":{type:0,data:[{x:2,y:0,w:4,h:4}]},"🯥":{type:0,data:[{x:2,y:4,w:4,h:4}]},"🯦":{type:0,data:[{x:0,y:2,w:4,h:4}]},"🯧":{type:0,data:[{x:4,y:2,w:4,h:4}]},"🯨":{type:5,data:{d:"M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0 Z",type:0}},"🯩":{type:5,data:{d:"M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1 Z",type:0}},"🯪":{type:5,data:{d:"M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1 Z",type:0}},"🯫":{type:5,data:{d:"M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1 Z",type:0}},"🯬":{type:5,data:{d:"M1,0 L.5,0 C.5,.276,.724,.5,1,.5 Z",type:0}},"🯭":{type:5,data:{d:"M0,1 L.5,1 C.5,.724,.276,.5,0,.5 Z",type:0}},"🯮":{type:5,data:{d:"M1,1 L1,.5 C.724,.5,.5,.724,.5,1 Z",type:0}},"🯯":{type:5,data:{d:"M0,0 L0,.5 C.276,.5,.5,.276,.5,0 Z",type:0}},"🯰":{type:5,data:{d:r(126),type:0},scaleType:1},"🯱":{type:5,data:{d:r(48),type:0},scaleType:1},"🯲":{type:5,data:{d:r(109),type:0},scaleType:1},"🯳":{type:5,data:{d:r(121),type:0},scaleType:1},"🯴":{type:5,data:{d:r(51),type:0},scaleType:1},"🯵":{type:5,data:{d:r(91),type:0},scaleType:1},"🯶":{type:5,data:{d:r(95),type:0},scaleType:1},"🯷":{type:5,data:{d:r(114),type:0},scaleType:1},"🯸":{type:5,data:{d:r(127),type:0},scaleType:1},"🯹":{type:5,data:{d:r(123),type:0},scaleType:1},"🯺":{type:5,data:{d:"M.5,.175 C.2,.175,.15,.305,.15,.435 L.05,.63 L.35,.63 C.35,.682,.42,.76,.5,.76 C.58,.76,.65,.682,.65,.63 L.95,.63 L.85,.435 C.85,.305,.8,.175,.5,.175 Z",type:0},scaleType:1},...Object.fromEntries(Array.from({length:256},(t,e)=>[String.fromCodePoint(10240+e),{type:6,data:e}]))},e.blockPatternCodepoints=new Set([9617,9618,9619,129932,129933,129934,129935,129936,129937,129938,129940,129948,129949,129950,129951])},183(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.tryDrawCustomGlyph=function(t,e,i,s,o,n,L,h,T,l,d,c,_=0){const Q=r.customGlyphDefinitions[e];if(Q){const e=Array.isArray(Q)?Q:[Q];for(const r of e)a(t,r,i,s,o,n,L,h,T,l,d,c,_);return!0}return!1};const s=i(959),r=i(915);function a(t,e,i,r,a,h,d,c,_,Q,u,p,g=0){let M=a,y=h,v=i,f=r;switch(1===e.scaleType&&(M=d,y=c,v=i+(a-d)/2,f=r+(h-c)/2),e.clipPath&&(t.save(),function(t,e,i,s,r,a){t.beginPath();for(const o of e.split(" ")){const e=o[0];if("Z"===e){t.closePath();continue}const n=o.substring(1).split(",");if(!n[0]||!n[1])continue;const L=i+parseFloat(n[0])*r,h=s+parseFloat(n[1])*a;"M"===e?t.moveTo(L,h):"L"===e&&t.lineTo(L,h)}t.clip()}(t,e.clipPath,v,f,M,y)),e.type){case 0:!function(t,e,i,s,r,a){for(let o=0;o7&&parseInt(T.slice(7,9),16)||1;else{if(!T.startsWith("rgba"))throw new Error(`Unexpected fillStyle color format "${T}" when drawing pattern glyph`);[L,d,c,_]=T.substring(5,T.length-1).split(",").map(t=>parseFloat(t))}for(let t=0;t>1&1,c=1&n;0!==d||0!==c?l.setTransform((new DOMMatrix).translateSelf(-d,-c)):l.setTransform(new DOMMatrix),t.fillStyle=l,t.fillRect(i,r,a,o)}(t,e.data,v,f,M,y,g);break;case 2:!function(t,e,i,s,r,a,o,n,L){let h;t.save(),t.beginPath(),t.rect(i,s,r,a),t.clip(),t.beginPath(),h="function"==typeof e?e(.15,.15/a*r):e;const d={currentX:0,currentY:0,lastControlX:0,lastControlY:0,lastCommand:""};for(const e of h.split(" ")){const L=e[0];if("Z"===L){t.closePath(),d.lastCommand=L;continue}const h=T[L];if(!h){n.error(`Could not find drawing instructions for "${L}"`);continue}const c=e.substring(1).split(",");c[0]&&c[1]&&(h(t,l(c,r,a,i,s,!0,o,0,0,!1),d),d.lastCommand=L)}void 0!==L?(t.strokeStyle=t.fillStyle,t.lineWidth=o*L,t.stroke()):t.fill(),t.closePath(),t.restore()}(t,e.data,v,f,M,y,Q,u,e.strokeWidth);break;case 3:!function(t,e,i,s,r,a,o,L){const h="string"==typeof e?e:e(0,0);t.beginPath();let T=0,l=0,d=0,c=0,_="";for(const e of h.split(" ")){const o=e[0],L=e.substring(1).split(",");if("Z"===o){t.closePath(),_=o;continue}if("V"===o){const e=s+parseFloat(L[0])*a;t.lineTo(T,e),l=e,d=T,c=l,_=o;continue}if("H"===o){const e=i+parseFloat(L[0])*r;t.lineTo(e,l),T=e,d=T,c=l,_=o;continue}if(!L[0]||!L[1])continue;if("A"===o){const e=parseFloat(L[0])*r,o=parseFloat(L[1])*a,h=parseFloat(L[2])*Math.PI/180,d=parseInt(L[3],10),c=parseInt(L[4],10),_=i+parseFloat(L[5])*r,Q=s+parseFloat(L[6])*a;n(t,T,l,e,o,h,d,c,_,Q),T=_,l=Q;continue}const h=L.map((t,e)=>{const o=parseFloat(t);return e%2==0?i+o*r:s+o*a});if("M"===o)t.moveTo(h[0],h[1]),T=h[0],l=h[1],d=T,c=l;else if("L"===o)t.lineTo(h[0],h[1]),T=h[0],l=h[1],d=T,c=l;else if("Q"===o)t.quadraticCurveTo(h[0],h[1],h[2],h[3]),d=h[0],c=h[1],T=h[2],l=h[3];else if("T"===o){let e,i;"Q"===_||"T"===_?(e=2*T-d,i=2*l-c):(e=T,i=l),t.quadraticCurveTo(e,i,h[0],h[1]),d=e,c=i,T=h[0],l=h[1]}else"C"===o&&(t.bezierCurveTo(h[0],h[1],h[2],h[3],h[4],h[5]),d=h[2],c=h[3],T=h[4],l=h[5]);_=o}void 0!==L?(t.strokeStyle=t.fillStyle,t.lineWidth=o*L,t.stroke()):t.fill()}(t,e.data,v,f,M,y,Q,e.strokeWidth);break;case 4:!function(t,e,i,s,r,a,o,n){t.save(),t.fillRect(i,s,r,a),n&&(t.fillStyle=n,t.strokeStyle=n),t.lineWidth=o,t.lineCap="square",t.beginPath();for(const o of e.d.split(" ")){const e=o[0],n=o.substring(1).split(",");if(!n[0]||!n[1]){"Z"===e&&t.closePath();continue}const L=n.map((t,e)=>{const o=parseFloat(t);return e%2==0?i+o*r:s+o*a});"M"===e?t.moveTo(L[0],L[1]):"L"===e&&t.lineTo(L[0],L[1])}1===e.type?t.stroke():t.fill(),t.restore()}(t,e.data,v,f,M,y,Q,p);break;case 5:!function(t,e,i,s,r,a,o,n,L){const h=new Path2D;h.rect(i,s,r,a),t.clip(h),t.beginPath();const d=o/12;t.lineWidth=n*d;const c={currentX:0,currentY:0,lastControlX:0,lastControlY:0,lastCommand:""};for(const o of e.d.split(" ")){const h=o[0];if("Z"===h){t.closePath(),c.lastCommand=h;continue}const _=T[h];if(!_){L.error(`Could not find drawing instructions for "${h}"`);continue}const Q=o.substring(1).split(",");Q[0]&&Q[1]&&(_(t,l(Q,r,a,i,s,!1,n,(e.leftPadding??0)*(d/2),(e.rightPadding??0)*(d/2)),c),c.lastCommand=h)}1===e.type?(t.strokeStyle=t.fillStyle,t.stroke()):t.fill(),t.closePath()}(t,e.data,v,f,M,y,_,Q,u);break;case 6:!function(t,e,i,s,r,a){const n=r/8,L=.1*a,h=.8*a/8,T=Math.min(n,h);for(let r=0;r<8;r++)if(e&1<1){const t=Math.sqrt(y);u=(s*=t)*s,p=(r*=t)*r}let v=(u*p-u*M-p*g)/(u*M+p*g);v<0&&(v=0);const f=(o===n?-1:1)*Math.sqrt(v),C=f*(s*Q/r),m=f*(-r*_/s),w=T*C-l*m+(e+L)/2,x=l*C+T*m+(i+h)/2,b=(_-C)/s,E=(Q-m)/r,R=(-_-C)/s,k=(-Q-m)/r,A=Math.atan2(E,b);let Z=Math.atan2(k,R)-A;0===n&&Z>0?Z-=2*Math.PI:1===n&&Z<0&&(Z+=2*Math.PI);const D=A+Z;t.ellipse(w,x,s,r,a,A,D,0===n)}const L=new Map;function h(t,e,i=0){return Math.max(Math.min(t,e),i)}const T={C:(t,e,i)=>{t.bezierCurveTo(e[0],e[1],e[2],e[3],e[4],e[5]),i.lastControlX=e[2],i.lastControlY=e[3],i.currentX=e[4],i.currentY=e[5]},L:(t,e,i)=>{t.lineTo(e[0],e[1]),i.lastControlX=i.currentX=e[0],i.lastControlY=i.currentY=e[1]},M:(t,e,i)=>{t.moveTo(e[0],e[1]),i.lastControlX=i.currentX=e[0],i.lastControlY=i.currentY=e[1]},Q:(t,e,i)=>{t.quadraticCurveTo(e[0],e[1],e[2],e[3]),i.lastControlX=e[0],i.lastControlY=e[1],i.currentX=e[2],i.currentY=e[3]},T:(t,e,i)=>{let s,r;"Q"===i.lastCommand||"T"===i.lastCommand?(s=2*i.currentX-i.lastControlX,r=2*i.currentY-i.lastControlY):(s=i.currentX,r=i.currentY),t.quadraticCurveTo(s,r,e[0],e[1]),i.lastControlX=s,i.lastControlY=r,i.currentX=e[0],i.currentY=e[1]}};function l(t,e,i,s,r,a,o,n=0,L=0,T=!0){const l=t.map(t=>parseFloat(t)||parseInt(t));if(l.length<2)throw new Error("Too few arguments for instruction");for(let t=0;t{this._refreshCharAtlas(t,e),this.reset(t)})),this._register((0,r.toDisposable)(()=>{this._canvas.remove()}))}_initCanvas(){this._ctx=(0,a.throwIfFalsy)(this._canvas.getContext("2d",{alpha:this._alpha})),this._alpha||this._clearAll()}handleBlur(t){}handleFocus(t){}handleCursorMove(t){}handleGridChanged(t,e,i){}handleSelectionChanged(t,e,i,s=!1){}_setTransparency(t,e){if(e===this._alpha)return;const i=this._canvas;this._alpha=e,this._canvas=this._canvas.cloneNode(),this._initCanvas(),this._container.replaceChild(this._canvas,i),this._refreshCharAtlas(t,this._themeService.colors),this.handleGridChanged(t,0,t.rows-1)}_refreshCharAtlas(t,e){this._deviceCharWidth<=0&&this._deviceCharHeight<=0||(this._charAtlas=(0,s.acquireTextureAtlas)(t,this._optionsService.rawOptions,e,this._deviceCellWidth,this._deviceCellHeight,this._deviceCharWidth,this._deviceCharHeight,this._coreBrowserService.dpr,2048),this._charAtlas.warmUp())}resize(t,e){this._deviceCellWidth=e.device.cell.width,this._deviceCellHeight=e.device.cell.height,this._deviceCharWidth=e.device.char.width,this._deviceCharHeight=e.device.char.height,this._deviceCharLeft=e.device.char.left,this._deviceCharTop=e.device.char.top,this._canvas.width=e.device.canvas.width,this._canvas.height=e.device.canvas.height,this._canvas.style.width=`${e.css.canvas.width}px`,this._canvas.style.height=`${e.css.canvas.height}px`,this._alpha||this._clearAll(),this._refreshCharAtlas(t,this._themeService.colors)}_fillBottomLineAtCells(t,e,i=1){this._ctx.fillRect(t*this._deviceCellWidth,(e+1)*this._deviceCellHeight-this._coreBrowserService.dpr-1,i*this._deviceCellWidth,this._coreBrowserService.dpr)}_clearAll(){this._alpha?this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(0,0,this._canvas.width,this._canvas.height))}_clearCells(t,e,i,s){this._alpha?this._ctx.clearRect(t*this._deviceCellWidth,e*this._deviceCellHeight,i*this._deviceCellWidth,s*this._deviceCellHeight):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(t*this._deviceCellWidth,e*this._deviceCellHeight,i*this._deviceCellWidth,s*this._deviceCellHeight))}_fillCharTrueColor(t,e,i,s){this._ctx.font=this._getFont(t,!1,!1),this._ctx.textBaseline=o.TEXT_BASELINE,this._clipCell(i,s,e.getWidth()),this._ctx.fillText(e.getChars(),i*this._deviceCellWidth+this._deviceCharLeft,s*this._deviceCellHeight+this._deviceCharTop+this._deviceCharHeight)}_clipCell(t,e,i){this._ctx.beginPath(),this._ctx.rect(t*this._deviceCellWidth,e*this._deviceCellHeight,i*this._deviceCellWidth,this._deviceCellHeight),this._ctx.clip()}_getFont(t,e,i){return`${i?"italic":""} ${e?t.options.fontWeightBold:t.options.fontWeight} ${t.options.fontSize*this._coreBrowserService.dpr}px ${t.options.fontFamily}`}}e.BaseRenderLayer=n},306(t,e,i){Object.defineProperty(e,"__esModule",{value:!0}),e.LinkRenderLayer=void 0;const s=i(657),r=i(814),a=i(133);class o extends a.BaseRenderLayer{constructor(t,e,i,s,r,a,o){super(i,t,"link",e,!0,r,a,o),this._register(s.onShowLinkUnderline(t=>this._handleShowLinkUnderline(t))),this._register(s.onHideLinkUnderline(t=>this._handleHideLinkUnderline(t)))}resize(t,e){super.resize(t,e),this._state=void 0}reset(t){this._clearCurrentLink()}_clearCurrentLink(){if(this._state){this._clearCells(this._state.x1,this._state.y1,this._state.cols-this._state.x1,1);const t=this._state.y2-this._state.y1-1;t>0&&this._clearCells(0,this._state.y1+1,this._state.cols,t),this._clearCells(0,this._state.y2,this._state.x2,1),this._state=void 0}}_handleShowLinkUnderline(t){if(t.fg===r.INVERTED_DEFAULT_COLOR?this._ctx.fillStyle=this._themeService.colors.background.css:void 0!==t.fg&&(0,s.is256Color)(t.fg)?this._ctx.fillStyle=this._themeService.colors.ansi[t.fg].css:this._ctx.fillStyle=this._themeService.colors.foreground.css,t.y1===t.y2)this._fillBottomLineAtCells(t.x1,t.y1,t.x2-t.x1);else{this._fillBottomLineAtCells(t.x1,t.y1,t.cols-t.x1);for(let e=t.y1+1;e{var t=s;Object.defineProperty(t,"__esModule",{value:!0}),t.WebglAddon=void 0;const e=i(426),r=i(95),a=i(399),o=i(414);class n extends e.Disposable{constructor(t){if(r.isSafari&&(0,r.getSafariVersion)()<16){const t={antialias:!1,depth:!1,preserveDrawingBuffer:!0};if(!document.createElement("canvas").getContext("webgl2",t))throw new Error("WebGL2 is only supported on Safari 16 and above")}super(),this._onChangeTextureAtlas=this._register(new o.Emitter),this.onChangeTextureAtlas=this._onChangeTextureAtlas.event,this._onAddTextureAtlasCanvas=this._register(new o.Emitter),this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event,this._onRemoveTextureAtlasCanvas=this._register(new o.Emitter),this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event,this._onContextLoss=this._register(new o.Emitter),this.onContextLoss=this._onContextLoss.event,this._customGlyphs=t?.customGlyphs??!0,this._preserveDrawingBuffer=t?.preserveDrawingBuffer}activate(t){const i=t._core;if(!t.element)return void this._register(i.onWillOpen(()=>this.activate(t)));this._terminal=t;const s=i.coreService,r=i.optionsService,n=i,L=n._renderService,h=n._characterJoinerService,T=n._charSizeService,l=n._coreBrowserService,d=n._decorationService,c=n._logService,_=n._themeService;this._renderer=this._register(new a.WebglRenderer(t,h,T,l,s,d,c,r,_,this._customGlyphs,this._preserveDrawingBuffer)),this._register(o.EventUtils.forward(this._renderer.onContextLoss,this._onContextLoss)),this._register(o.EventUtils.forward(this._renderer.onChangeTextureAtlas,this._onChangeTextureAtlas)),this._register(o.EventUtils.forward(this._renderer.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas)),this._register(o.EventUtils.forward(this._renderer.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas)),L.setRenderer(this._renderer),this._register((0,e.toDisposable)(()=>{if(this._terminal._core._store._isDisposed)return;const e=this._terminal._core._renderService;e.setRenderer(this._terminal._core._createRenderer()),e.handleResize(t.cols,t.rows)}))}get textureAtlas(){return this._renderer?.textureAtlas}clearTextureAtlas(){this._renderer?.clearTextureAtlas()}}t.WebglAddon=n})(),s})()); - //# sourceMappingURL=addon-webgl.js.map +-//# sourceMappingURL=addon-webgl.js.map \ No newline at end of file ++!(function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.WebglAddon=e():t.WebglAddon=e()})(globalThis,()=>(()=>{"use strict";var t={105(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.WindowIntervalTimer=e2.eventType=void 0,e2.getWindow=r,e2.addDisposableListener=o,e2.addStandardDisposableListener=function(t3,e3,i3,s3){return o(t3,e3,i3,s3)},e2.getDomNodePagePosition=function(t3){const e3=t3.getBoundingClientRect(),i3=r(t3);return{left:e3.left+i3.scrollX,top:e3.top+i3.scrollY,width:e3.width,height:e3.height}},e2.scheduleAtNextAnimationFrame=function(t3,e3,i3=0){const s3=h(t3),r2=new n(e3,i3);return s3.next.push(r2),s3.animFrameRequested||(s3.animFrameRequested=true,t3.requestAnimationFrame(()=>(function(t4){const e4=h(t4);for(e4.animFrameRequested=false,e4.current=e4.next,e4.next=[],e4.inAnimationFrameRunner=true;e4.current.length>0;)e4.current.sort(n.sort),e4.current.shift().execute();e4.inAnimationFrameRunner=false})(t3))),r2};const s2=i2(578);function r(t3){const e3=t3;if(e3?.ownerDocument?.defaultView)return e3.ownerDocument.defaultView;const i3=t3;return i3?.view?i3.view:window}class a{constructor(t3,e3,i3,s3){;this._node=t3,this._type=e3,this._handler=i3,this._options=s3,t3.addEventListener(e3,i3,s3)}dispose(){this._node&&this._handler&&(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,this._handler=null)}}function o(t3,e3,i3,s3){return new a(t3,e3,i3,s3)}e2.eventType={CLICK:"click",MOUSE_DOWN:"mousedown",MOUSE_OVER:"mouseover",MOUSE_LEAVE:"mouseleave",KEY_DOWN:"keydown",KEY_UP:"keyup",INPUT:"input",BLUR:"blur",FOCUS:"focus",CHANGE:"change",POINTER_DOWN:"pointerdown",POINTER_MOVE:"pointermove",POINTER_UP:"pointerup",MOUSE_WHEEL:"wheel",WHEEL:"wheel"};class n{constructor(t3,e3){;this._runner=t3,this.priority=e3,this._canceled=false}dispose(){this._canceled=true}execute(){if(!this._canceled)try{this._runner()}catch(t3){console.error(t3)}}static sort(t3,e3){return e3.priority-t3.priority}}const L=new Map;function h(t3){let e3=L.get(t3);return e3||(e3={next:[],current:[],animFrameRequested:false,inAnimationFrameRunner:false},L.set(t3,e3)),e3}class T extends s2.IntervalTimer{constructor(t3){;super(),this._defaultTarget=t3?r(t3):void 0}cancelAndSet(t3,e3,i3){super.cancelAndSet(t3,e3,i3??this._defaultTarget??window)}}e2.WindowIntervalTimer=T},814(t2,e2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.INVERTED_DEFAULT_COLOR=void 0,e2.INVERTED_DEFAULT_COLOR=257},959(t2,e2){function i2(t3){return 57508<=t3&&t3<=57558}function s2(t3){return t3>=128512&&t3<=128591||t3>=127744&&t3<=128511||t3>=128640&&t3<=128767||t3>=9728&&t3<=9983||t3>=9984&&t3<=10175||t3>=65024&&t3<=65039||t3>=129280&&t3<=129535||t3>=127462&&t3<=127487};Object.defineProperty(e2,"__esModule",{value:true}),e2.throwIfFalsy=function(t3){if(!t3)throw new Error("value must not be falsy");return t3},e2.isPowerlineGlyph=i2,e2.isRestrictedPowerlineGlyph=function(t3){return 57520<=t3&&t3<=57527},e2.isEmoji=s2,e2.allowRescaling=function(t3,e3,r,a){return 1===e3&&r>Math.ceil(1.5*a)&&void 0!==t3&&t3>255&&!s2(t3)&&!i2(t3)&&!(function(t4){return 57344<=t4&&t4<=63743})(t3)},e2.treatGlyphAsBackgroundColor=function(t3){return i2(t3)||(function(t4){return 9472<=t4&&t4<=9631})(t3)},e2.createRenderDimensions=function(){return{css:{canvas:{width:0,height:0},cell:{width:0,height:0}},device:{canvas:{width:0,height:0},cell:{width:0,height:0},char:{width:0,height:0,left:0,top:0}}}},e2.computeNextVariantOffset=function(t3,e3,i3=0){return(t3-(2*Math.round(e3)-i3))%(2*Math.round(e3))}},948(t2,e2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.createSelectionRenderModel=function(){return new i2};class i2{constructor(){this.clear()}clear(){;this.hasSelection=false,this.columnSelectMode=false,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(t3,e3,i3,s2=false){if(this.selectionStart=e3,this.selectionEnd=i3,!e3||!i3||e3[0]===i3[0]&&e3[1]===i3[1])return void this.clear();const r=t3.buffers.active.ydisp,a=e3[1]-r,o=i3[1]-r,n=Math.max(a,0),L=Math.min(o,t3.rows-1);n>=t3.rows||L<0?this.clear():(this.hasSelection=true,this.columnSelectMode=s2,this.viewportStartRow=a,this.viewportEndRow=o,this.viewportCappedStartRow=n,this.viewportCappedEndRow=L,this.startCol=e3[0],this.endCol=i3[0])}isCellSelected(t3,e3,i3){return!!this.hasSelection&&(i3-=t3.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?e3>=this.startCol&&i3>=this.viewportCappedStartRow&&e3=this.viewportCappedStartRow&&e3>=this.endCol&&i3<=this.viewportCappedEndRow:i3>this.viewportStartRow&&i3=this.startCol&&e3=this.startCol)}}},536(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.TextBlinkStateManager=void 0;const s2=i2(426);class r extends s2.Disposable{constructor(t3,e3,i3){;super(),this._renderCallback=t3,this._coreBrowserService=e3,this._optionsService=i3,this._intervalDuration=0,this._blinkOn=true,this._needsBlinkInViewport=false,this._isViewportVisible=true,this._register(this._optionsService.onSpecificOptionChange("blinkIntervalDuration",t4=>{this.setIntervalDuration(t4)})),this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration),this._register((0,s2.toDisposable)(()=>this._clearInterval()))}get isBlinkOn(){return this._blinkOn}get isEnabled(){return this._intervalDuration>0}setNeedsBlinkInViewport(t3){this._needsBlinkInViewport!==t3&&(this._needsBlinkInViewport=t3,this._updateIntervalState())}setViewportVisible(t3){this._isViewportVisible!==t3&&(this._isViewportVisible=t3,this._updateIntervalState())}setIntervalDuration(t3){t3!==this._intervalDuration&&(this._intervalDuration=t3,this._clearInterval(),this._updateIntervalState())}_updateIntervalState(){if(this._intervalDuration>0&&this._needsBlinkInViewport&&this._isViewportVisible){if(void 0!==this._interval)return;const t3=this._blinkOn;return this._blinkOn=true,this._interval=this._coreBrowserService.window.setInterval(()=>{;this._blinkOn=!this._blinkOn,this._renderCallback()},this._intervalDuration),void(t3||this._renderCallback())};this._clearInterval(),this._blinkOn||(this._blinkOn=true,this._renderCallback())}_clearInterval(){void 0!==this._interval&&(this._coreBrowserService.window.clearInterval(this._interval),this._interval=void 0)}}e2.TextBlinkStateManager=r},578(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.IntervalTimer=e2.MicrotaskTimer=e2.TimeoutTimer=void 0,e2.timeout=function(t3){return new Promise(e3=>setTimeout(e3,t3))},e2.disposableTimeout=function(t3,e3=0,i3){const r=setTimeout(()=>{;t3(),i3&&a.dispose()},e3),a=(0,s2.toDisposable)(()=>{clearTimeout(r)});return i3?.add(a),a};const s2=i2(426);e2.TimeoutTimer=class{constructor(){;this._token=-1,this._isDisposed=false}dispose(){;this.cancel(),this._isDisposed=true}cancel(){;-1!==this._token&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(t3,e3){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout(()=>{;this._token=-1,t3()},e3)}setIfNotSet(t3,e3){if(this._isDisposed)throw new Error("Calling setIfNotSet on a disposed TimeoutTimer");-1===this._token&&(this._token=setTimeout(()=>{;this._token=-1,t3()},e3))}},e2.MicrotaskTimer=class{constructor(){;this._isScheduled=false,this._isDisposed=false}dispose(){;this.cancel(),this._isDisposed=true}cancel(){this._isScheduled=false}set(t3){if(this._isDisposed)throw new Error("Calling set on a disposed MicrotaskTimer");this._isScheduled||(this._isScheduled=true,queueMicrotask(()=>{this._isScheduled&&(this._isScheduled=false,t3())}))}},e2.IntervalTimer=class{constructor(){this._isDisposed=false}cancel(){;this._disposable?.dispose(),this._disposable=void 0}cancelAndSet(t3,e3,i3=globalThis){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed IntervalTimer");this.cancel();const s3=i3.setInterval(()=>{t3()},e3);this._disposable={dispose:()=>{;i3.clearInterval(s3),this._disposable=void 0}}}dispose(){;this.cancel(),this._isDisposed=true}}},993(t2,e2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.rgba=e2.rgb=e2.css=e2.color=e2.channels=e2.NULL_COLOR=void 0,e2.toPaddedHex=l,e2.contrastRatio=d;let i2=0,s2=0,r=0,a=0;var o,n,L,h,T;function l(t3){const e3=t3.toString(16);return e3.length<2?"0"+e3:e3}function d(t3,e3){return t3>>0},t3.toColor=function(e3,i3,s3,r2){return{css:t3.toCss(e3,i3,s3,r2),rgba:t3.toRgba(e3,i3,s3,r2)}}})(o||(e2.channels=o={})),(function(t3){function e3(t4,e4){return a=Math.round(255*e4),[i2,s2,r]=T.toChannels(t4.rgba),{css:o.toCss(i2,s2,r,a),rgba:o.toRgba(i2,s2,r,a)}};t3.blend=function(t4,e4){if(a=(255&e4.rgba)/255,1===a)return{css:e4.css,rgba:e4.rgba};const n2=e4.rgba>>24&255,L2=e4.rgba>>16&255,h2=e4.rgba>>8&255,T2=t4.rgba>>24&255,l2=t4.rgba>>16&255,d2=t4.rgba>>8&255;return i2=T2+Math.round((n2-T2)*a),s2=l2+Math.round((L2-l2)*a),r=d2+Math.round((h2-d2)*a),{css:o.toCss(i2,s2,r),rgba:o.toRgba(i2,s2,r)}},t3.isOpaque=function(t4){return!(255&~t4.rgba)},t3.ensureContrastRatio=function(t4,e4,i3){const s3=T.ensureContrastRatio(t4.rgba,e4.rgba,i3);if(s3)return o.toColor(s3>>24&255,s3>>16&255,s3>>8&255)},t3.opaque=function(t4){const e4=(255|t4.rgba)>>>0;return[i2,s2,r]=T.toChannels(e4),{css:o.toCss(i2,s2,r),rgba:e4}},t3.opacity=e3,t3.multiplyOpacity=function(t4,i3){return a=255&t4.rgba,e3(t4,a*i3/255)},t3.toColorRGB=function(t4){return[t4.rgba>>24&255,t4.rgba>>16&255,t4.rgba>>8&255]}})(n||(e2.color=n={})),(function(t3){let e3,n2;try{const t4=document.createElement("canvas");t4.width=1,t4.height=1;const i3=t4.getContext("2d",{willReadFrequently:true});i3&&(e3=i3,e3.globalCompositeOperation="copy",n2=e3.createLinearGradient(0,0,1,1))}catch{}t3.toColor=function(t4){if(t4.match(/#[\da-f]{3,8}/i))switch(t4.length){case 4:return i2=parseInt(t4.slice(1,2).repeat(2),16),s2=parseInt(t4.slice(2,3).repeat(2),16),r=parseInt(t4.slice(3,4).repeat(2),16),o.toColor(i2,s2,r);case 5:return i2=parseInt(t4.slice(1,2).repeat(2),16),s2=parseInt(t4.slice(2,3).repeat(2),16),r=parseInt(t4.slice(3,4).repeat(2),16),a=parseInt(t4.slice(4,5).repeat(2),16),o.toColor(i2,s2,r,a);case 7:return{css:t4,rgba:(parseInt(t4.slice(1),16)<<8|255)>>>0};case 9:return{css:t4,rgba:parseInt(t4.slice(1),16)>>>0}}const L2=t4.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(L2)return i2=parseInt(L2[1],10),s2=parseInt(L2[2],10),r=parseInt(L2[3],10),a=Math.round(255*(void 0===L2[5]?1:parseFloat(L2[5]))),o.toColor(i2,s2,r,a);if("transparent"===t4)return{css:"transparent",rgba:0};if(!e3||!n2)throw new Error("css.toColor: Unsupported css format");if(e3.fillStyle=n2,e3.fillStyle=t4,"string"!=typeof e3.fillStyle)throw new Error("css.toColor: Unsupported css format");if(e3.fillRect(0,0,1,1),[i2,s2,r,a]=e3.getImageData(0,0,1,1).data,255!==a)throw new Error("css.toColor: Unsupported css format");return{rgba:o.toRgba(i2,s2,r,a),css:t4}}})(L||(e2.css=L={})),(function(t3){function e3(t4,e4,i3){const s3=t4/255,r2=e4/255,a2=i3/255;return .2126*(s3<=.03928?s3/12.92:Math.pow((s3+.055)/1.055,2.4))+.7152*(r2<=.03928?r2/12.92:Math.pow((r2+.055)/1.055,2.4))+.0722*(a2<=.03928?a2/12.92:Math.pow((a2+.055)/1.055,2.4))};t3.relativeLuminance=function(t4){return e3(t4>>16&255,t4>>8&255,255&t4)},t3.relativeLuminance2=e3})(h||(e2.rgb=h={})),(function(t3){function e3(t4,e4,i3){const s3=t4>>24&255,r2=t4>>16&255,a2=t4>>8&255;let o2=e4>>24&255,n3=e4>>16&255,L2=e4>>8&255,T2=d(h.relativeLuminance2(o2,n3,L2),h.relativeLuminance2(s3,r2,a2));for(;T20||n3>0||L2>0);)o2-=Math.max(0,Math.ceil(.1*o2)),n3-=Math.max(0,Math.ceil(.1*n3)),L2-=Math.max(0,Math.ceil(.1*L2)),T2=d(h.relativeLuminance2(o2,n3,L2),h.relativeLuminance2(s3,r2,a2));return(o2<<24|n3<<16|L2<<8|255)>>>0}function n2(t4,e4,i3){const s3=t4>>24&255,r2=t4>>16&255,a2=t4>>8&255;let o2=e4>>24&255,n3=e4>>16&255,L2=e4>>8&255,T2=d(h.relativeLuminance2(o2,n3,L2),h.relativeLuminance2(s3,r2,a2));for(;T2>>0};t3.blend=function(t4,e4){if(a=(255&e4)/255,1===a)return e4;const n3=e4>>24&255,L2=e4>>16&255,h2=e4>>8&255,T2=t4>>24&255,l2=t4>>16&255,d2=t4>>8&255;return i2=T2+Math.round((n3-T2)*a),s2=l2+Math.round((L2-l2)*a),r=d2+Math.round((h2-d2)*a),o.toRgba(i2,s2,r)},t3.ensureContrastRatio=function(t4,i3,s3){const r2=h.relativeLuminance(t4>>8),a2=h.relativeLuminance(i3>>8);if(d(r2,a2)>8));if(o3d(r2,h.relativeLuminance(e4>>8))?a3:e4}return a3}const o2=n2(t4,i3,s3),L2=d(r2,h.relativeLuminance(o2>>8));if(L2d(r2,h.relativeLuminance(a3>>8))?o2:a3}return o2}},t3.reduceLuminance=e3,t3.increaseLuminance=n2,t3.toChannels=function(t4){return[t4>>24&255,t4>>16&255,t4>>8&255,255&t4]}})(T||(e2.rgba=T={}))},414(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.EventUtils=e2.Emitter=void 0;const s2=i2(426);var r;e2.Emitter=class{constructor(){;this._listeners=[],this._disposed=false}get event(){return this._event||(this._event=(t3,e3,i3)=>{if(this._disposed)return(0,s2.toDisposable)(()=>{});const r2={fn:t3,thisArgs:e3};this._listeners.push(r2);const a=(0,s2.toDisposable)(()=>{const t4=this._listeners.indexOf(r2);-1!==t4&&this._listeners.splice(t4,1)});return i3&&(Array.isArray(i3)?i3.push(a):i3.add(a)),a}),this._event}fire(t3){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{const{fn:e3,thisArgs:i3}=this._listeners[0];return void e3.call(i3,t3)}default:{const e3=this._listeners.slice();for(const{fn:i3,thisArgs:s3}of e3)i3.call(s3,t3)}}}dispose(){this._disposed||(this._disposed=true,this._listeners.length=0)}},(function(t3){;t3.forward=function(t4,e3){return t4(t5=>e3.fire(t5))},t3.map=function(t4,e3){return(i3,s3,r2)=>t4(t5=>i3.call(s3,e3(t5)),void 0,r2)},t3.any=function(...t4){return(e3,i3,r2)=>{const a=new s2.DisposableStore;for(const s3 of t4)a.add(s3(t5=>e3.call(i3,t5)));return r2&&(Array.isArray(r2)?r2.push(a):r2.add(a)),a}},t3.runAndSubscribe=function(t4,e3,i3){return e3(i3),t4(t5=>e3(t5))}})(r||(e2.EventUtils=r={}))},426(t2,e2){function i2(t3){return{dispose:t3}}function s2(t3){if(!t3)return t3;if(Array.isArray(t3)){for(const e3 of t3)e3.dispose();return[]}return t3.dispose(),t3};Object.defineProperty(e2,"__esModule",{value:true}),e2.MutableDisposable=e2.Disposable=e2.DisposableStore=void 0,e2.toDisposable=i2,e2.dispose=s2,e2.combinedDisposable=function(...t3){return i2(()=>s2(t3))};class r{constructor(){;this._disposables=new Set,this._isDisposed=false}get isDisposed(){return this._isDisposed}add(t3){return this._isDisposed?t3.dispose():this._disposables.add(t3),t3}dispose(){if(!this._isDisposed){this._isDisposed=true;for(const t3 of this._disposables)t3.dispose();this._disposables.clear()}}clear(){for(const t3 of this._disposables)t3.dispose();this._disposables.clear()}}e2.DisposableStore=r;class a{constructor(){this._store=new r}dispose(){this._store.dispose()}_register(t3){return this._store.add(t3)}};e2.Disposable=a,a.None=Object.freeze({dispose(){}}),e2.MutableDisposable=class{constructor(){this._isDisposed=false}get value(){return this._isDisposed?void 0:this._value}set value(t3){this._isDisposed||t3===this._value||(this._value?.dispose(),this._value=t3)}clear(){this.value=void 0}dispose(){;this._isDisposed=true,this._value?.dispose(),this._value=void 0}}},836(t2,e2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.FourKeyMap=e2.TwoKeyMap=void 0;class i2{constructor(){this._data={}}set(t3,e3,i3){;this._data[t3]||(this._data[t3]={}),this._data[t3][e3]=i3}get(t3,e3){return this._data[t3]?this._data[t3][e3]:void 0}clear(){this._data={}}};e2.TwoKeyMap=i2,e2.FourKeyMap=class{constructor(){this._data=new i2}set(t3,e3,s2,r,a){;this._data.get(t3,e3)||this._data.set(t3,e3,new i2),this._data.get(t3,e3).set(s2,r,a)}get(t3,e3,i3,s2){return this._data.get(t3,e3)?.get(i3,s2)}clear(){this._data.clear()}}},95(t2,e2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.isChromeOS=e2.isLinux=e2.isWindows=e2.isMac=e2.isSafari=e2.isLegacyEdge=e2.isChrome=e2.isFirefox=e2.isNode=void 0,e2.getZoomFactor=function(t3){return 1},e2.getSafariVersion=function(){if(!e2.isSafari)return 0;const t3=i2.match(/Version\/(\d+)/);return null===t3||t3.length<2?0:parseInt(t3[1],10)},e2.isNode=!("undefined"==typeof process||!("title"in process)||"undefined"!=typeof navigator&&!navigator.userAgent.startsWith("Node.js/"));const i2=e2.isNode?"node":navigator.userAgent,s2=e2.isNode?"node":navigator.platform;e2.isFirefox=i2.includes("Firefox"),e2.isChrome=i2.includes("Chrome"),e2.isLegacyEdge=i2.includes("Edge"),e2.isSafari=/^((?!chrome|android).)*safari/i.test(i2),e2.isMac=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(s2),e2.isWindows=["Windows","Win16","Win32","WinCE"].includes(s2),e2.isLinux=s2.indexOf("Linux")>=0,e2.isChromeOS=/\bCrOS\b/.test(i2)},930(t2,e2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.DebouncedIdleTask=e2.IdleTaskQueue=e2.PriorityTaskQueue=void 0;class i2{constructor(t3){;this._tasks=[],this._i=0,this._logService=t3}enqueue(t3){;this._tasks.push(t3),this._start()}flush(){for(;this._ii3)return r-e3<-20&&this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(r-e3))}ms`),void this._start();r=i3}this.clear()}}class s2 extends i2{_requestCallback(t3){return setTimeout(()=>t3(this._createDeadline(16)))}_cancelCallback(t3){clearTimeout(t3)}_createDeadline(t3){const e3=performance.now()+t3;return{timeRemaining:()=>Math.max(0,e3-performance.now())}}};e2.PriorityTaskQueue=s2,e2.IdleTaskQueue="requestIdleCallback"in globalThis?class extends i2{_requestCallback(t3){return requestIdleCallback(t3)}_cancelCallback(t3){cancelIdleCallback(t3)}}:s2,e2.DebouncedIdleTask=class{constructor(t3){this._queue=new e2.IdleTaskQueue(t3)}set(t3){;this._queue.clear(),this._queue.enqueue(t3)}flush(){this._queue.flush()}dispose(){this._queue.clear()}}},917(t2,e2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.ExtendedAttrs=e2.AttributeData=void 0;class i2{constructor(){;this.fg=0,this.bg=0,this.extended=new s2}static toColorRGB(t3){return[t3>>>16&255,t3>>>8&255,255&t3]}static fromColorRGB(t3){return(255&t3[0])<<16|(255&t3[1])<<8|255&t3[2]}clone(){const t3=new i2;return t3.fg=this.fg,t3.bg=this.bg,t3.extended=this.extended.clone(),t3}isInverse(){return 67108864&this.fg}isBold(){return 134217728&this.fg}isUnderline(){return this.hasExtendedAttrs()&&0!==this.extended.underlineStyle?1:268435456&this.fg}isBlink(){return 536870912&this.fg}isInvisible(){return 1073741824&this.fg}isItalic(){return 67108864&this.bg}isDim(){return 134217728&this.bg}isStrikethrough(){return 2147483648&this.fg}isProtected(){return 536870912&this.bg}isOverline(){return 1073741824&this.bg}getFgColorMode(){return 50331648&this.fg}getBgColorMode(){return 50331648&this.bg}isFgRGB(){return!(50331648&~this.fg)}isBgRGB(){return!(50331648&~this.bg)}isFgPalette(){return 16777216==(50331648&this.fg)||33554432==(50331648&this.fg)}isBgPalette(){return 16777216==(50331648&this.bg)||33554432==(50331648&this.bg)}isFgDefault(){return!(50331648&this.fg)}isBgDefault(){return!(50331648&this.bg)}isAttributeDefault(){return 0===this.fg&&0===this.bg}getFgColor(){switch(50331648&this.fg){case 16777216:case 33554432:return 255&this.fg;case 50331648:return 16777215&this.fg;default:return-1}}getBgColor(){switch(50331648&this.bg){case 16777216:case 33554432:return 255&this.bg;case 50331648:return 16777215&this.bg;default:return-1}}hasExtendedAttrs(){return 268435456&this.bg}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(268435456&this.bg&&~this.extended.underlineColor)switch(50331648&this.extended.underlineColor){case 16777216:case 33554432:return 255&this.extended.underlineColor;case 50331648:return 16777215&this.extended.underlineColor;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return 268435456&this.bg&&~this.extended.underlineColor?50331648&this.extended.underlineColor:this.getFgColorMode()}isUnderlineColorRGB(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&~this.extended.underlineColor):this.isFgRGB()}isUnderlineColorPalette(){return 268435456&this.bg&&~this.extended.underlineColor?16777216==(50331648&this.extended.underlineColor)||33554432==(50331648&this.extended.underlineColor):this.isFgPalette()}isUnderlineColorDefault(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&this.extended.underlineColor):this.isFgDefault()}getUnderlineStyle(){return 268435456&this.fg?268435456&this.bg?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}}e2.AttributeData=i2;class s2{get ext(){return this._urlId?-469762049&this._ext|this.underlineStyle<<26:this._ext}set ext(t3){this._ext=t3}get underlineStyle(){return this._urlId?5:(469762048&this._ext)>>26}set underlineStyle(t3){;this._ext&=-469762049,this._ext|=t3<<26&469762048}get underlineColor(){return 67108863&this._ext}set underlineColor(t3){;this._ext&=-67108864,this._ext|=67108863&t3}get urlId(){return this._urlId}set urlId(t3){this._urlId=t3}get underlineVariantOffset(){const t3=(3758096384&this._ext)>>29;return t3<0?4294967288^t3:t3}set underlineVariantOffset(t3){;this._ext&=536870911,this._ext|=t3<<29&3758096384}constructor(t3=0,e3=0){;this._ext=0,this._urlId=0,this._ext=t3,this._urlId=e3}clone(){return new s2(this._ext,this._urlId)}isEmpty(){return 0===this.underlineStyle&&0===this._urlId}}e2.ExtendedAttrs=s2},721(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.CellData=void 0;const s2=i2(348),r=i2(564),a=i2(917);class o extends a.AttributeData{constructor(){;super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new a.ExtendedAttrs,this.combinedData=""}static fromCharData(t3){const e3=new o;return e3.setFromCharData(t3),e3}isCombined(){return 2097152&this.content}getWidth(){return this.content>>22}getChars(){return 2097152&this.content?this.combinedData:2097151&this.content?(0,s2.stringFromCodePoint)(2097151&this.content):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):2097151&this.content}setFromCharData(t3){;this.fg=t3[r.CHAR_DATA_ATTR_INDEX],this.bg=0;let e3=false;if(t3[r.CHAR_DATA_CHAR_INDEX].length>2)e3=true;else if(2===t3[r.CHAR_DATA_CHAR_INDEX].length){const i3=t3[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0);if(55296<=i3&&i3<=56319){const s3=t3[r.CHAR_DATA_CHAR_INDEX].charCodeAt(1);56320<=s3&&s3<=57343?this.content=1024*(i3-55296)+s3-56320+65536|t3[r.CHAR_DATA_WIDTH_INDEX]<<22:e3=true}else e3=true}else this.content=t3[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|t3[r.CHAR_DATA_WIDTH_INDEX]<<22;e3&&(this.combinedData=t3[r.CHAR_DATA_CHAR_INDEX],this.content=2097152|t3[r.CHAR_DATA_WIDTH_INDEX]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}attributesEquals(t3){if(this.getFgColorMode()!==t3.getFgColorMode()||this.getFgColor()!==t3.getFgColor())return false;if(this.getBgColorMode()!==t3.getBgColorMode()||this.getBgColor()!==t3.getBgColor())return false;if(this.isInverse()!==t3.isInverse())return false;if(this.isBold()!==t3.isBold())return false;if(this.isUnderline()!==t3.isUnderline())return false;if(this.isUnderline()){if(this.getUnderlineStyle()!==t3.getUnderlineStyle())return false;const e3=this.isUnderlineColorDefault(),i3=t3.isUnderlineColorDefault();if(!e3||!i3){if(e3!==i3)return false;if(this.getUnderlineColor()!==t3.getUnderlineColor())return false;if(this.getUnderlineColorMode()!==t3.getUnderlineColorMode())return false}}return this.isOverline()===t3.isOverline()&&this.isBlink()===t3.isBlink()&&this.isInvisible()===t3.isInvisible()&&this.isItalic()===t3.isItalic()&&this.isDim()===t3.isDim()&&this.isStrikethrough()===t3.isStrikethrough()}}e2.CellData=o},564(t2,e2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.WHITESPACE_CELL_CODE=e2.WHITESPACE_CELL_WIDTH=e2.WHITESPACE_CELL_CHAR=e2.NULL_CELL_CODE=e2.NULL_CELL_WIDTH=e2.NULL_CELL_CHAR=e2.CHAR_DATA_CODE_INDEX=e2.CHAR_DATA_WIDTH_INDEX=e2.CHAR_DATA_CHAR_INDEX=e2.CHAR_DATA_ATTR_INDEX=e2.DEFAULT_EXT=e2.DEFAULT_ATTR=e2.DEFAULT_COLOR=void 0,e2.DEFAULT_COLOR=0,e2.DEFAULT_ATTR=e2.DEFAULT_COLOR<<9|256,e2.DEFAULT_EXT=0,e2.CHAR_DATA_ATTR_INDEX=0,e2.CHAR_DATA_CHAR_INDEX=1,e2.CHAR_DATA_WIDTH_INDEX=2,e2.CHAR_DATA_CODE_INDEX=3,e2.NULL_CELL_CHAR="",e2.NULL_CELL_WIDTH=1,e2.NULL_CELL_CODE=0,e2.WHITESPACE_CELL_CHAR=" ",e2.WHITESPACE_CELL_WIDTH=1,e2.WHITESPACE_CELL_CODE=32},348(t2,e2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.Utf8ToUtf32=e2.StringToUtf32=void 0,e2.stringFromCodePoint=function(t3){return t3>65535?(t3-=65536,String.fromCharCode(55296+(t3>>10))+String.fromCharCode(t3%1024+56320)):String.fromCharCode(t3)},e2.utf32ToString=function(t3,e3=0,i2=t3.length){let s2="";for(let r=e3;r65535?(e4-=65536,s2+=String.fromCharCode(55296+(e4>>10))+String.fromCharCode(e4%1024+56320)):s2+=String.fromCharCode(e4)}return s2},e2.StringToUtf32=class{constructor(){this._interim=0}clear(){this._interim=0}decode(t3,e3){const i2=t3.length;if(!i2)return 0;let s2=0,r=0;if(this._interim){const i3=t3.charCodeAt(r++);56320<=i3&&i3<=57343?e3[s2++]=1024*(this._interim-55296)+i3-56320+65536:(e3[s2++]=this._interim,e3[s2++]=i3),this._interim=0}for(let a=r;a=i2)return this._interim=r2,s2;const o=t3.charCodeAt(a);56320<=o&&o<=57343?e3[s2++]=1024*(r2-55296)+o-56320+65536:(e3[s2++]=r2,e3[s2++]=o);continue}65279!==r2&&(e3[s2++]=r2)}return s2}},e2.Utf8ToUtf32=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(t3,e3){const i2=t3.length;if(!i2)return 0;let s2,r,a,o,n,L=0,h=0;if(this.interim[0]){let s3=false,r2=this.interim[0];r2&=192==(224&r2)?31:224==(240&r2)?15:7;let a2,o2=0;for(;(a2=this.interim[++o2])&&o2<4;)r2<<=6,r2|=63&a2;const n2=192==(224&this.interim[0])?2:224==(240&this.interim[0])?3:4,T2=n2-o2;for(;h=i2)return 0;if(a2=t3[h++],128!=(192&a2)){;h--,s3=true;break};this.interim[o2++]=a2,r2<<=6,r2|=63&a2};s3||(2===n2?r2<128?h--:e3[L++]=r2:3===n2?r2<2048||r2>=55296&&r2<=57343||65279===r2||(e3[L++]=r2):r2<65536||r2>1114111||(e3[L++]=r2)),this.interim.fill(0)}const T=i2-4;let l=h;for(;l=i2)return this.interim[0]=s2,L;if(r=t3[l++],128!=(192&r)){l--;continue}if(n=(31&s2)<<6|63&r,n<128){l--;continue}e3[L++]=n}else if(224==(240&s2)){if(l>=i2)return this.interim[0]=s2,L;if(r=t3[l++],128!=(192&r)){l--;continue}if(l>=i2)return this.interim[0]=s2,this.interim[1]=r,L;if(a=t3[l++],128!=(192&a)){l--;continue}if(n=(15&s2)<<12|(63&r)<<6|63&a,n<2048||n>=55296&&n<=57343||65279===n)continue;e3[L++]=n}else if(240==(248&s2)){if(l>=i2)return this.interim[0]=s2,L;if(r=t3[l++],128!=(192&r)){l--;continue}if(l>=i2)return this.interim[0]=s2,this.interim[1]=r,L;if(a=t3[l++],128!=(192&a)){l--;continue}if(l>=i2)return this.interim[0]=s2,this.interim[1]=r,this.interim[2]=a,L;if(o=t3[l++],128!=(192&o)){l--;continue}if(n=(7&s2)<<18|(63&r)<<12|(63&a)<<6|63&o,n<65536||n>1114111)continue;e3[L++]=n}}return L}}},864(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.CellColorResolver=void 0;const s2=i2(564),r=i2(993),a=i2(959),o=i2(915);let n,L=0,h=0,T=false,l=false,d=false,c=0;e2.CellColorResolver=class{constructor(t3,e3,i3,s3,r2,a2){;this._terminal=t3,this._optionService=e3,this._selectionRenderModel=i3,this._decorationService=s3,this._coreBrowserService=r2,this._themeService=a2,this.result={fg:0,bg:0,ext:0}}resolve(t3,e3,i3,_,Q){;this.result.bg=t3.bg,this.result.fg=t3.fg,this.result.ext=268435456&t3.bg?t3.extended.ext:0,h=0,L=0,l=false,T=false,d=false,n=this._themeService.colors,c=0;const u=t3.getCode();if(u!==s2.NULL_CELL_CODE&&4===t3.extended.underlineStyle){const t4=Math.max(1,Math.floor(this._optionService.rawOptions.fontSize*this._coreBrowserService.dpr/15));c=e3*_%(2*Math.round(t4))}if(0===c&&o.blockPatternCodepoints.has(u)&&(c=e3*_%2*2+i3*Q%2),this._decorationService.forEachDecorationAtCell(e3,i3,"bottom",t4=>{;t4.backgroundColorRGB&&(h=t4.backgroundColorRGB.rgba>>8&16777215,l=true),t4.foregroundColorRGB&&(L=t4.foregroundColorRGB.rgba>>8&16777215,T=true)}),d=this._selectionRenderModel.isCellSelected(this._terminal,e3,i3),d){if(67108864&this.result.fg||50331648&this.result.bg){if(67108864&this.result.fg)switch(50331648&this.result.fg){case 16777216:case 33554432:h=this._themeService.colors.ansi[255&this.result.fg].rgba;break;case 50331648:h=(16777215&this.result.fg)<<8|255;break;default:h=this._themeService.colors.foreground.rgba}else switch(50331648&this.result.bg){case 16777216:case 33554432:h=this._themeService.colors.ansi[255&this.result.bg].rgba;break;case 50331648:h=(16777215&this.result.bg)<<8|255}h=r.rgba.blend(h,4294967040&(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba|128)>>8&16777215}else h=(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba>>8&16777215;if(l=true,n.selectionForeground&&(L=n.selectionForeground.rgba>>8&16777215,T=true),(0,a.treatGlyphAsBackgroundColor)(t3.getCode())){if(67108864&this.result.fg&&!(50331648&this.result.bg))L=(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba>>8&16777215;else{if(67108864&this.result.fg)switch(50331648&this.result.bg){case 16777216:case 33554432:L=this._themeService.colors.ansi[255&this.result.bg].rgba;break;case 50331648:L=(16777215&this.result.bg)<<8|255}else switch(50331648&this.result.fg){case 16777216:case 33554432:L=this._themeService.colors.ansi[255&this.result.fg].rgba;break;case 50331648:L=(16777215&this.result.fg)<<8|255;break;default:L=this._themeService.colors.foreground.rgba}L=r.rgba.blend(L,4294967040&(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba|128)>>8&16777215}T=true}};this._decorationService.forEachDecorationAtCell(e3,i3,"top",t4=>{;t4.backgroundColorRGB&&(h=t4.backgroundColorRGB.rgba>>8&16777215,l=true),t4.foregroundColorRGB&&(L=t4.foregroundColorRGB.rgba>>8&16777215,T=true)}),l&&(h=d?-16777216&t3.bg&-134217729|h|50331648:-16777216&t3.bg|h|50331648),T&&(L=-16777216&t3.fg&-67108865|L|50331648),67108864&this.result.fg&&(l&&!T&&(L=50331648&this.result.bg?-134217728&this.result.fg|67108863&this.result.bg:-134217728&this.result.fg|16777215&n.background.rgba>>8|50331648,T=true),!l&&T&&(h=50331648&this.result.fg?-67108864&this.result.bg|67108863&this.result.fg:-67108864&this.result.bg|16777215&n.foreground.rgba>>8|50331648,l=true)),n=void 0,this.result.bg=l?h:this.result.bg,this.result.fg=T?L:this.result.fg,this.result.ext&=536870911,this.result.ext|=c<<29&3758096384}}},670(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.acquireTextureAtlas=function(t3,e3,i3,o,n,L,h,T,l,d=true){const c=(0,r.generateConfig)(o,n,L,h,e3,i3,T,l,d);for(let e4=0;e4=0){if((0,r.configEquals)(i4.config,c))return i4.atlas;1===i4.ownedBy.length?(i4.atlas.dispose(),a.splice(e4,1)):i4.ownedBy.splice(s3,1);break}}for(let e4=0;e4{;this._renderCallback(),this._animationFrame=void 0})))}_restartInterval(t3=600){;this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout=this._coreBrowserService.window.setTimeout(()=>{if(this._animationTimeRestarted){const t4=600-(Date.now()-this._animationTimeRestarted);if(this._animationTimeRestarted=void 0,t4>0)return void this._restartInterval(t4)};this.isCursorVisible=false,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{;this._renderCallback(),this._animationFrame=void 0}),this._blinkInterval=this._coreBrowserService.window.setInterval(()=>{if(this._animationTimeRestarted){const t4=600-(Date.now()-this._animationTimeRestarted);return this._animationTimeRestarted=void 0,void this._restartInterval(t4)};this.isCursorVisible=!this.isCursorVisible,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{;this._renderCallback(),this._animationFrame=void 0})},600)},t3)}pause(){;this.isCursorVisible=true,this._isIdlePaused=false,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0),this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}resume(){;this.pause(),this._animationTimeRestarted=void 0,this._restartInterval(),this._resetIdleTimer(),this.restartBlinkAnimation()}_resetIdleTimer(){;this._isIdlePaused=false,this._idleTimeout&&this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_stopBlinkingDueToIdle(){;this.isCursorVisible=true,this._isIdlePaused=true,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0),this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0,this._renderCallback()}}},697(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.observeDevicePixelDimensions=function(t3,e3,i3){let r=new e3.ResizeObserver(e4=>{const s3=e4.find(e5=>e5.target===t3);if(!s3)return;if(!("devicePixelContentBoxSize"in s3))return r?.disconnect(),void(r=void 0);const a=s3.devicePixelContentBoxSize[0].inlineSize,o=s3.devicePixelContentBoxSize[0].blockSize;a>0&&o>0&&i3(a,o)});try{r.observe(t3,{box:["device-pixel-content-box"]})}catch{;r.disconnect(),r=void 0}return(0,s2.toDisposable)(()=>r?.disconnect())};const s2=i2(426)},28(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.GlyphRenderer=void 0;const s2=i2(115),r=i2(564),a=i2(426),o=i2(719),n=i2(959);let L,h=0,T=0,l=0;class d extends a.Disposable{constructor(t3,e3,i3,r2,L2){;super(),this._terminal=t3,this._gl=e3,this._dimensions=i3,this._optionsService=r2,this._logService=L2,this._activeBuffer=0,this._vertices={count:0,attributes:new Float32Array(0),attributesBuffers:[new Float32Array(0),new Float32Array(0)]};const h2=this._gl;void 0===s2.TextureAtlas.maxAtlasPages&&(s2.TextureAtlas.maxAtlasPages=Math.min(32,(0,n.throwIfFalsy)(h2.getParameter(h2.MAX_TEXTURE_IMAGE_UNITS))),s2.TextureAtlas.maxTextureSize=(0,n.throwIfFalsy)(h2.getParameter(h2.MAX_TEXTURE_SIZE))),this._program=(0,n.throwIfFalsy)((0,o.createProgram)(h2,"#version 300 es\nlayout (location = 0) in vec2 a_unitquad;\nlayout (location = 1) in vec2 a_cellpos;\nlayout (location = 2) in vec2 a_offset;\nlayout (location = 3) in vec2 a_size;\nlayout (location = 4) in float a_texpage;\nlayout (location = 5) in vec2 a_texcoord;\nlayout (location = 6) in vec2 a_texsize;\n\nuniform mat4 u_projection;\nuniform vec2 u_resolution;\n\nout vec2 v_texcoord;\nflat out int v_texpage;\n\nvoid main() {\n vec2 zeroToOne = (a_offset / u_resolution) + a_cellpos + (a_unitquad * a_size);\n gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0);\n v_texpage = int(a_texpage);\n v_texcoord = a_texcoord + a_unitquad * a_texsize;\n}",(function(t4){let e4="";for(let i4=1;i4h2.deleteProgram(this._program))),this._projectionLocation=(0,n.throwIfFalsy)(h2.getUniformLocation(this._program,"u_projection")),this._resolutionLocation=(0,n.throwIfFalsy)(h2.getUniformLocation(this._program,"u_resolution")),this._textureLocation=(0,n.throwIfFalsy)(h2.getUniformLocation(this._program,"u_texture"));const T2=this._vertexArrayObject=h2.createVertexArray();this._register((0,a.toDisposable)(()=>h2.deleteVertexArray(T2))),h2.bindVertexArray(T2);const l2=new Float32Array([0,0,1,0,0,1,1,1]),d2=h2.createBuffer();this._register((0,a.toDisposable)(()=>h2.deleteBuffer(d2))),h2.bindBuffer(h2.ARRAY_BUFFER,d2),h2.bufferData(h2.ARRAY_BUFFER,l2,h2.STATIC_DRAW),h2.enableVertexAttribArray(0),h2.vertexAttribPointer(0,2,this._gl.FLOAT,false,0,0);const c=new Uint8Array([0,1,2,3]),_=h2.createBuffer();this._register((0,a.toDisposable)(()=>h2.deleteBuffer(_))),h2.bindBuffer(h2.ELEMENT_ARRAY_BUFFER,_),h2.bufferData(h2.ELEMENT_ARRAY_BUFFER,c,h2.STATIC_DRAW),this._attributesBuffer=(0,n.throwIfFalsy)(h2.createBuffer()),this._register((0,a.toDisposable)(()=>h2.deleteBuffer(this._attributesBuffer))),h2.bindBuffer(h2.ARRAY_BUFFER,this._attributesBuffer),h2.enableVertexAttribArray(2),h2.vertexAttribPointer(2,2,h2.FLOAT,false,44,0),h2.vertexAttribDivisor(2,1),h2.enableVertexAttribArray(3),h2.vertexAttribPointer(3,2,h2.FLOAT,false,44,2*Float32Array.BYTES_PER_ELEMENT),h2.vertexAttribDivisor(3,1),h2.enableVertexAttribArray(4),h2.vertexAttribPointer(4,1,h2.FLOAT,false,44,4*Float32Array.BYTES_PER_ELEMENT),h2.vertexAttribDivisor(4,1),h2.enableVertexAttribArray(5),h2.vertexAttribPointer(5,2,h2.FLOAT,false,44,5*Float32Array.BYTES_PER_ELEMENT),h2.vertexAttribDivisor(5,1),h2.enableVertexAttribArray(6),h2.vertexAttribPointer(6,2,h2.FLOAT,false,44,7*Float32Array.BYTES_PER_ELEMENT),h2.vertexAttribDivisor(6,1),h2.enableVertexAttribArray(1),h2.vertexAttribPointer(1,2,h2.FLOAT,false,44,9*Float32Array.BYTES_PER_ELEMENT),h2.vertexAttribDivisor(1,1),h2.useProgram(this._program);const Q=new Int32Array(s2.TextureAtlas.maxAtlasPages);for(let t4=0;t4h2.deleteTexture(e4.texture))),h2.activeTexture(h2.TEXTURE0+t4),h2.bindTexture(h2.TEXTURE_2D,e4.texture),h2.texParameteri(h2.TEXTURE_2D,h2.TEXTURE_WRAP_S,h2.CLAMP_TO_EDGE),h2.texParameteri(h2.TEXTURE_2D,h2.TEXTURE_WRAP_T,h2.CLAMP_TO_EDGE),h2.texImage2D(h2.TEXTURE_2D,0,h2.RGBA,1,1,0,h2.RGBA,h2.UNSIGNED_BYTE,new Uint8Array([255,0,0,255])),this._atlasTextures[t4]=e4};h2.enable(h2.BLEND),h2.blendFunc(h2.SRC_ALPHA,h2.ONE_MINUS_SRC_ALPHA),this.handleResize()}beginFrame(){if(!this._atlas)return true;const _g=this._atlas._clearModelGeneration||0;return this._lastSeenClearModelGeneration!==_g&&(this._lastSeenClearModelGeneration=_g,true)}updateCell(t3,e3,i3,s3,r2,a2,o2,n2,L2){this._updateCell(this._vertices.attributes,t3,e3,i3,s3,r2,a2,o2,n2,L2)}_updateCell(t3,e3,i3,s3,a2,o2,d2,c,_,Q){;h=11*(i3*this._terminal.cols+e3),s3!==r.NULL_CELL_CODE&&void 0!==s3?this._atlas&&(L=c&&c.length>1?this._atlas.getRasterizedGlyphCombinedChar(c,a2,o2,d2,false,this._terminal.element):this._atlas.getRasterizedGlyph(s3,a2,o2,d2,false,this._terminal.element),T=Math.floor((this._dimensions.device.cell.width-this._dimensions.device.char.width)/2),a2!==Q&&L.offset.x>T?(l=L.offset.x-T,t3[h]=-(L.offset.x-l)+this._dimensions.device.char.left,t3[h+1]=-L.offset.y+this._dimensions.device.char.top,t3[h+2]=(L.size.x-l)/this._dimensions.device.canvas.width,t3[h+3]=L.size.y/this._dimensions.device.canvas.height,t3[h+4]=L.texturePage,t3[h+5]=L.texturePositionClipSpace.x+l/this._atlas.pages[L.texturePage].canvas.width,t3[h+6]=L.texturePositionClipSpace.y,t3[h+7]=L.sizeClipSpace.x-l/this._atlas.pages[L.texturePage].canvas.width,t3[h+8]=L.sizeClipSpace.y):(t3[h]=-L.offset.x+this._dimensions.device.char.left,t3[h+1]=-L.offset.y+this._dimensions.device.char.top,t3[h+2]=L.size.x/this._dimensions.device.canvas.width,t3[h+3]=L.size.y/this._dimensions.device.canvas.height,t3[h+4]=L.texturePage,t3[h+5]=L.texturePositionClipSpace.x,t3[h+6]=L.texturePositionClipSpace.y,t3[h+7]=L.sizeClipSpace.x,t3[h+8]=L.sizeClipSpace.y),this._optionsService.rawOptions.rescaleOverlappingGlyphs&&(0,n.allowRescaling)(s3,_,L.size.x,this._dimensions.device.cell.width)&&(t3[h+2]=(this._dimensions.device.cell.width-1)/this._dimensions.device.canvas.width)):t3.fill(0,h,h+11-1-2)}clear(){const t3=this._terminal,e3=t3.cols*t3.rows*11;this._vertices.count!==e3?this._vertices.attributes=new Float32Array(e3):this._vertices.attributes.fill(0);let i3=0;for(;i3this._atlasTextures.length&&!this._pageOverflowWarned&&(this._pageOverflowWarned=true,console.warn("xterm webgl: atlas page count ("+this._atlas.pages.length+") exceeds the renderer texture capacity ("+this._atlasTextures.length+"), some glyphs will not render correctly"));for(let t4=0;t4T2.deleteProgram(this._program))),this._projectionLocation=(0,a.throwIfFalsy)(T2.getUniformLocation(this._program,"u_projection"));const l2=this._vertexArrayObject=T2.createVertexArray();this._register((0,s2.toDisposable)(()=>T2.deleteVertexArray(l2))),T2.bindVertexArray(l2);const d2=new Float32Array([0,0,1,0,0,1,1,1]),c2=T2.createBuffer();this._register((0,s2.toDisposable)(()=>T2.deleteBuffer(c2))),T2.bindBuffer(T2.ARRAY_BUFFER,c2),T2.bufferData(T2.ARRAY_BUFFER,d2,T2.STATIC_DRAW),T2.enableVertexAttribArray(3),T2.vertexAttribPointer(3,2,this._gl.FLOAT,false,0,0);const _2=new Uint8Array([0,1,2,3]),Q2=T2.createBuffer();this._register((0,s2.toDisposable)(()=>T2.deleteBuffer(Q2))),T2.bindBuffer(T2.ELEMENT_ARRAY_BUFFER,Q2),T2.bufferData(T2.ELEMENT_ARRAY_BUFFER,_2,T2.STATIC_DRAW),this._attributesBuffer=(0,a.throwIfFalsy)(T2.createBuffer()),this._register((0,s2.toDisposable)(()=>T2.deleteBuffer(this._attributesBuffer))),T2.bindBuffer(T2.ARRAY_BUFFER,this._attributesBuffer),T2.enableVertexAttribArray(0),T2.vertexAttribPointer(0,2,T2.FLOAT,false,o,0),T2.vertexAttribDivisor(0,1),T2.enableVertexAttribArray(1),T2.vertexAttribPointer(1,2,T2.FLOAT,false,o,2*Float32Array.BYTES_PER_ELEMENT),T2.vertexAttribDivisor(1,1),T2.enableVertexAttribArray(2),T2.vertexAttribPointer(2,4,T2.FLOAT,false,o,4*Float32Array.BYTES_PER_ELEMENT),T2.vertexAttribDivisor(2,1),this._updateCachedColors(L2.colors),this._register(this._themeService.onChangeColors(t4=>{;this._updateCachedColors(t4),this._updateViewportRectangle()}))}renderBackgrounds(){this._renderVertices(this._vertices)}renderCursor(){this._renderVertices(this._verticesCursor)}_renderVertices(t3){const e3=this._gl;e3.useProgram(this._program),e3.bindVertexArray(this._vertexArrayObject),e3.uniformMatrix4fv(this._projectionLocation,false,r.PROJECTION_MATRIX),e3.bindBuffer(e3.ARRAY_BUFFER,this._attributesBuffer),e3.bufferData(e3.ARRAY_BUFFER,t3.attributes,e3.DYNAMIC_DRAW),e3.drawElementsInstanced(this._gl.TRIANGLE_STRIP,4,e3.UNSIGNED_BYTE,0,t3.count)}handleResize(){this._updateViewportRectangle()}setDimensions(t3){this._dimensions=t3}_updateCachedColors(t3){;this._bgFloat=this._colorToFloat32Array(t3.background),this._cursorFloat=this._colorToFloat32Array(t3.cursor)}_updateViewportRectangle(){this._addRectangleFloat(this._vertices.attributes,0,0,0,this._terminal.cols*this._dimensions.device.cell.width,this._terminal.rows*this._dimensions.device.cell.height,this._bgFloat)}updateBackgrounds(t3){const e3=this._terminal,i3=this._vertices;let s3,r2,a2,o2,n2,L2,h2,T2,l2,d2,c2,_2=1;for(s3=0;s3>24&255)/255,d=(L>>16&255)/255,c=(L>>8&255)/255,_=1,this._addRectangle(t3.attributes,e3,h,T,(o2-a2)*this._dimensions.device.cell.width,this._dimensions.device.cell.height,l,d,c,_)}_addRectangle(t3,e3,i3,s3,r2,a2,o2,n2,L2,h2){;t3[e3]=i3/this._dimensions.device.canvas.width,t3[e3+1]=s3/this._dimensions.device.canvas.height,t3[e3+2]=r2/this._dimensions.device.canvas.width,t3[e3+3]=a2/this._dimensions.device.canvas.height,t3[e3+4]=o2,t3[e3+5]=n2,t3[e3+6]=L2,t3[e3+7]=h2}_addRectangleFloat(t3,e3,i3,s3,r2,a2,o2){;t3[e3]=i3/this._dimensions.device.canvas.width,t3[e3+1]=s3/this._dimensions.device.canvas.height,t3[e3+2]=r2/this._dimensions.device.canvas.width,t3[e3+3]=a2/this._dimensions.device.canvas.height,t3[e3+4]=o2[0],t3[e3+5]=o2[1],t3[e3+6]=o2[2],t3[e3+7]=o2[3]}_colorToFloat32Array(t3){return new Float32Array([(t3.rgba>>24&255)/255,(t3.rgba>>16&255)/255,(t3.rgba>>8&255)/255,(255&t3.rgba)/255])}}e2.RectangleRenderer=Q},208(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.RenderModel=e2.COMBINED_CHAR_BIT_MASK=void 0;const s2=i2(948);e2.COMBINED_CHAR_BIT_MASK=2147483648,e2.RenderModel=class{constructor(){;this.cells=new Uint32Array(0),this.lineLengths=new Uint32Array(0),this.selection=(0,s2.createSelectionRenderModel)()}resize(t3,e3){const i3=t3*e3*4;i3!==this.cells.length&&(this.cells=new Uint32Array(i3),this.lineLengths=new Uint32Array(e3))}clear(){;this.cells.fill(0,0),this.lineLengths.fill(0,0)}}},115(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.TextureAtlas=void 0;const s2=i2(0),r=i2(183),a=i2(959),o=i2(993),n=i2(836),L=i2(930),h=i2(917),T=i2(564),l=i2(414),d={texturePage:0,texturePosition:{x:0,y:0},texturePositionClipSpace:{x:0,y:0},offset:{x:0,y:0},size:{x:0,y:0},sizeClipSpace:{x:0,y:0}};let c;class _{get pages(){return this._pages}constructor(t3,e3,i3,s3){;this._document=t3,this._config=e3,this._unicodeService=i3,this._logService=s3,this._didWarmUp=false,this._cacheMap=new n.FourKeyMap,this._cacheMapCombined=new n.FourKeyMap,this._pages=[],this._activePages=[],this._workBoundingBox={top:0,left:0,bottom:0,right:0},this._workAttributeData=new h.AttributeData,this._textureSize=512,this._onAddTextureAtlasCanvas=new l.Emitter,this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event,this._onRemoveTextureAtlasCanvas=new l.Emitter,this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event,this._requestClearModel=false,this._clearModelGeneration=0,this._createNewPage(),this._tmpCanvas=p(t3,4*this._config.deviceCellWidth+4,this._config.deviceCellHeight+4),this._tmpCtx=(0,a.throwIfFalsy)(this._tmpCanvas.getContext("2d",{alpha:this._config.allowTransparency,willReadFrequently:true}))}dispose(){this._tmpCanvas.remove();for(const t3 of this.pages)t3.canvas.remove();this._onAddTextureAtlasCanvas.dispose(),this._onRemoveTextureAtlasCanvas.dispose()}warmUp(){this._didWarmUp||(this._doWarmUp(),this._didWarmUp=true)}_doWarmUp(){const t3=new L.IdleTaskQueue(this._logService);for(let e3=33;e3<126;e3++)t3.enqueue(()=>{if(!this._cacheMap.get(e3,T.DEFAULT_COLOR,T.DEFAULT_COLOR,T.DEFAULT_EXT)){const t4=this._drawToCache(e3,T.DEFAULT_COLOR,T.DEFAULT_COLOR,T.DEFAULT_EXT,false,void 0);this._cacheMap.set(e3,T.DEFAULT_COLOR,T.DEFAULT_COLOR,T.DEFAULT_EXT,t4)}})}beginFrame(){const t3=this._requestClearModel;return this._requestClearModel=false,t3}clearTexture(){if(this._pages.some(t3=>t3._glyphs.length>0||0!==t3.currentRow.x||0!==t3.currentRow.y)){this._evictAllPages();this._createNewPage()}}_evictAllPages(){for(const t3 of this._pages)this._onRemoveTextureAtlasCanvas.fire(t3.canvas),t3.canvas.remove();this._pages.length=0,this._activePages.length=0,this._overflowSizePage=void 0,this._cacheMap.clear(),this._cacheMapCombined.clear(),this._didWarmUp=false,this._requestClearModel=true,this._clearModelGeneration++}_createNewPage(){if(_.maxAtlasPages&&this._pages.length>=_.maxAtlasPages){const t4=this._pages.filter(t5=>2*t5.canvas.width<=(_.maxTextureSize||4096)).sort((t5,e4)=>e4.canvas.width!==t5.canvas.width?e4.canvas.width-t5.canvas.width:e4.percentageUsed-t5.percentageUsed);let e3=-1,i3=0;for(let s4=0;s4t5.canvas.width!==s3[0].canvas.width))this._evictAllPages();else{const r2=s3.map(t5=>t5.glyphs[0].texturePage).sort((t5,e4)=>t5-e4),a2=this.pages.length-s3.length,o2=this._mergePages(s3,a2);o2.version=++Q.nextVersion;for(let t5=r2.length-1;t5>=0;t5--)this._deletePage(r2[t5]);this.pages.push(o2),this._requestClearModel=true,this._clearModelGeneration++,this._onAddTextureAtlasCanvas.fire(o2.canvas)}}const t3=new Q(this._document,this._textureSize);return this._pages.push(t3),this._activePages.push(t3),this._onAddTextureAtlasCanvas.fire(t3.canvas),t3}_mergePages(t3,e3){const i3=2*t3[0].canvas.width,s3=new Q(this._document,i3,t3);for(const[r2,a2]of t3.entries()){const t4=r2*a2.canvas.width%i3,o2=Math.floor(r2/2)*a2.canvas.height;s3.ctx.drawImage(a2.canvas,t4,o2);for(const s4 of a2.glyphs)s4.texturePage=e3,s4.sizeClipSpace.x=s4.size.x/i3,s4.sizeClipSpace.y=s4.size.y/i3,s4.texturePosition.x+=t4,s4.texturePosition.y+=o2,s4.texturePositionClipSpace.x=s4.texturePosition.x/i3,s4.texturePositionClipSpace.y=s4.texturePosition.y/i3;this._onRemoveTextureAtlasCanvas.fire(a2.canvas);const n2=this._activePages.indexOf(a2);-1!==n2&&this._activePages.splice(n2,1)}return s3}_deletePage(t3){this._pages.splice(t3,1);for(let e3=t3;e3=this._config.colors.ansi.length)throw new Error("No color found for idx "+t3);return this._config.colors.ansi[t3]}_getBackgroundColor(t3,e3,i3,s3){if(this._config.allowTransparency)return o.NULL_COLOR;let r2;switch(t3){case 16777216:case 33554432:r2=this._getColorFromAnsiIndex(e3);break;case 50331648:const t4=h.AttributeData.toColorRGB(e3);r2=o.channels.toColor(t4[0],t4[1],t4[2]);break;default:r2=i3?o.color.opaque(this._config.colors.foreground):this._config.colors.background}return this._config.allowTransparency||(r2=o.color.opaque(r2)),r2}_getForegroundColor(t3,e3,i3,r2,a2,n2,L2,T2,l2,d2){const c2=this._getMinimumContrastColor(t3,e3,i3,r2,a2,n2,L2,l2,T2,d2);if(c2)return c2;let _2;switch(a2){case 16777216:case 33554432:;this._config.drawBoldTextInBrightColors&&l2&&n2<8&&(n2+=8),_2=this._getColorFromAnsiIndex(n2);break;case 50331648:const t4=h.AttributeData.toColorRGB(n2);_2=o.channels.toColor(t4[0],t4[1],t4[2]);break;default:_2=L2?this._config.colors.background:this._config.colors.foreground}return this._config.allowTransparency&&(_2=o.color.opaque(_2)),T2&&(_2=o.color.multiplyOpacity(_2,s2.DIM_OPACITY)),_2}_resolveBackgroundRgba(t3,e3,i3){switch(t3){case 16777216:case 33554432:return this._getColorFromAnsiIndex(e3).rgba;case 50331648:return e3<<8;default:return i3?this._config.colors.foreground.rgba:this._config.colors.background.rgba}}_resolveForegroundRgba(t3,e3,i3,s3){switch(t3){case 16777216:case 33554432:return this._config.drawBoldTextInBrightColors&&s3&&e3<8&&(e3+=8),this._getColorFromAnsiIndex(e3).rgba;case 50331648:return e3<<8;default:return i3?this._config.colors.background.rgba:this._config.colors.foreground.rgba}}_getMinimumContrastColor(t3,e3,i3,s3,r2,a2,n2,L2,h2,T2){if(1===this._config.minimumContrastRatio||T2)return;const l2=this._getContrastCache(h2),d2=l2.getColor(t3,s3);if(void 0!==d2)return d2??void 0;const c2=this._resolveBackgroundRgba(e3,i3,n2),_2=this._resolveForegroundRgba(r2,a2,n2,L2),Q2=o.rgba.ensureContrastRatio(c2,_2,this._config.minimumContrastRatio/(h2?2:1));if(!Q2)return void l2.setColor(t3,s3,null);const u2=o.channels.toColor(Q2>>24&255,Q2>>16&255,Q2>>8&255);return l2.setColor(t3,s3,u2),u2}_getContrastCache(t3){return t3?this._config.colors.halfContrastCache:this._config.colors.contrastCache}_drawToCache(t3,e3,i3,o2,n2,L2){const T2="number"==typeof t3?String.fromCharCode(t3):t3;L2&&this._tmpCanvas.parentElement!==L2&&(this._tmpCanvas.style.display="none",L2.append(this._tmpCanvas));const l2=Math.min(this._config.deviceCellWidth*Math.max(T2.length,2)+4,this._config.deviceMaxTextureSize);this._tmpCanvas.width=t4?2*t4-o3:t4-o3;false==!(o3>=t4)||0===_2?(this._tmpCtx.setLineDash([Math.round(t4),Math.round(t4)]),this._tmpCtx.moveTo(h2+_2,s3),this._tmpCtx.lineTo(T3,s3)):(this._tmpCtx.setLineDash([Math.round(t4),Math.round(t4)]),this._tmpCtx.moveTo(h2,s3),this._tmpCtx.lineTo(h2+_2,s3),this._tmpCtx.moveTo(h2+_2+t4,s3),this._tmpCtx.lineTo(T3,s3)),o3=(0,a.computeNextVariantOffset)(T3-h2,t4,o3);break;case 5:const Q2=.6,u2=.3,p3=T3-h2,g2=Math.floor(Q2*p3),M2=Math.floor(u2*p3),y2=p3-g2-M2;this._tmpCtx.setLineDash([g2,M2,y2]),this._tmpCtx.moveTo(h2,s3),this._tmpCtx.lineTo(T3,s3);break;default:;this._tmpCtx.moveTo(h2,s3),this._tmpCtx.lineTo(T3,s3)};n3||this._tmpCtx.stroke(),this._tmpCtx.restore()}if(this._tmpCtx.restore(),!O&&this._config.fontSize>=12&&!this._config.allowTransparency&&" "!==T2){;this._tmpCtx.save(),this._tmpCtx.textBaseline="alphabetic";const e5=this._tmpCtx.measureText(T2);if(this._tmpCtx.restore(),"actualBoundingBoxDescent"in e5&&e5.actualBoundingBoxDescent>0){this._tmpCtx.save();const e6=new Path2D;e6.rect(i4,L3-Math.ceil(t4/2),this._config.deviceCellWidth*I,l3-L3+Math.ceil(t4/2)),this._tmpCtx.clip(e6),this._tmpCtx.lineWidth=3*this._config.devicePixelRatio,this._tmpCtx.strokeStyle=E.css,this._tmpCtx.strokeText(T2,S,S+this._config.deviceCharHeight),this._tmpCtx.restore()}}}if(C){const t4=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/15)),e4=t4%2==1?.5:0;this._tmpCtx.lineWidth=t4,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(S,S+e4),this._tmpCtx.lineTo(S+this._config.deviceCharWidth*I,S+e4),this._tmpCtx.stroke()}if(O||this._tmpCtx.fillText(T2,S,S+this._config.deviceCharHeight),"_"===T2&&!this._config.allowTransparency){let t4=u(this._tmpCtx.getImageData(S,S,this._config.deviceCellWidth,this._config.deviceCellHeight),E,D,W);if(t4)for(let e4=1;e4<=5&&(this._tmpCtx.save(),this._tmpCtx.fillStyle=E.css,this._tmpCtx.fillRect(0,0,this._tmpCanvas.width,this._tmpCanvas.height),this._tmpCtx.restore(),this._tmpCtx.fillText(T2,S,S+this._config.deviceCharHeight-e4),t4=u(this._tmpCtx.getImageData(S,S,this._config.deviceCellWidth,this._config.deviceCellHeight),E,D,W),t4);e4++);}if(f){const t4=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/10)),e4=this._tmpCtx.lineWidth%2==1?.5:0;this._tmpCtx.lineWidth=t4,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(S,S+Math.floor(this._config.deviceCharHeight/2)-e4),this._tmpCtx.lineTo(S+this._config.deviceCharWidth*I,S+Math.floor(this._config.deviceCharHeight/2)-e4),this._tmpCtx.stroke()}this._tmpCtx.restore();const N=this._tmpCtx.getImageData(0,0,this._tmpCanvas.width,this._tmpCanvas.height);let P;if(P=this._config.allowTransparency?(function(t4){for(let e4=0;e40)return false;return true})(N):u(N,E,D,W),P)return d;const B=this._findGlyphBoundingBox(N,this._workBoundingBox,l2,Z,O,S);let $,F;for(;;){if(0===this._activePages.length){const t4=this._createNewPage();$=t4,F=t4.currentRow,F.height=B.size.y;break};$=this._activePages[this._activePages.length-1],F=$.currentRow;for(const t4 of this._activePages)B.size.y<=t4.currentRow.height&&($=t4,F=t4.currentRow);for(let t4=this._activePages.length-1;t4>=0;t4--)for(const e4 of this._activePages[t4].fixedRows)e4.height<=F.height&&B.size.y<=e4.height&&($=this._activePages[t4],F=e4);if(B.size.x>this._textureSize){;this._overflowSizePage||(_.maxAtlasPages&&this._pages.length>=_.maxAtlasPages&&this._evictAllPages(),this._overflowSizePage=new Q(this._document,this._config.deviceMaxTextureSize),this.pages.push(this._overflowSizePage),this._requestClearModel=true,this._clearModelGeneration++,this._onAddTextureAtlasCanvas.fire(this._overflowSizePage.canvas)),$=this._overflowSizePage,F=this._overflowSizePage.currentRow,F.x+B.size.x>=$.canvas.width&&(F.x=0,F.y+=F.height,F.height=0);break}if(F.y+B.size.y>=$.canvas.height||F.height>B.size.y+2){let t4=false;if($.currentRow.y+$.currentRow.height+B.size.y>=$.canvas.height){let e4;for(const t5 of this._activePages)if(t5.currentRow.y+t5.currentRow.height+B.size.y=_.maxAtlasPages&&F.y+B.size.y<=$.canvas.height&&F.height>=B.size.y&&F.x+B.size.x<=$.canvas.width)t4=true;else{const e5=this._createNewPage();$=e5,F=e5.currentRow,F.height=B.size.y,t4=true}}t4||($.currentRow.height>0&&$.fixedRows.push($.currentRow),F={x:0,y:$.currentRow.y+$.currentRow.height,height:B.size.y},$.fixedRows.push(F),$.currentRow={x:0,y:F.y+F.height,height:0})}if(F.x+B.size.x<=$.canvas.width)break;F===$.currentRow?(F.x=0,F.y+=F.height,F.height=0):$.fixedRows.splice($.fixedRows.indexOf(F),1)}return B.texturePage=this._pages.indexOf($),B.texturePosition.x=F.x,B.texturePosition.y=F.y,B.texturePositionClipSpace.x=F.x/$.canvas.width,B.texturePositionClipSpace.y=F.y/$.canvas.height,B.sizeClipSpace.x/=$.canvas.width,B.sizeClipSpace.y/=$.canvas.height,F.height=Math.max(F.height,B.size.y),F.x+=B.size.x,$.ctx.putImageData(N,B.texturePosition.x-this._workBoundingBox.left,B.texturePosition.y-this._workBoundingBox.top,this._workBoundingBox.left,this._workBoundingBox.top,B.size.x,B.size.y),$.addGlyph(B),$.version=++Q.nextVersion,B}_findGlyphBoundingBox(t3,e3,i3,s3,r2,a2){e3.top=0;const o2=s3?this._config.deviceCellHeight:this._tmpCanvas.height,n2=s3?this._config.deviceCellWidth:i3;let L2=false;for(let i4=0;i4=a2;i4--){for(let s4=0;s4=0;i4--){for(let s4=0;s4>>24,a2=e3.rgba>>>16&255,o2=e3.rgba>>>8&255,n2=i3.rgba>>>24,L2=i3.rgba>>>16&255,h2=i3.rgba>>>8&255,T2=Math.floor((Math.abs(r2-n2)+Math.abs(a2-L2)+Math.abs(o2-h2))/12);let l2=true;for(let e4=0;e4this._handleColorChange())),this._cellColorResolver=new s2.CellColorResolver(this._terminal,this._optionsService,this._model.selection,this._decorationService,this._coreBrowserService,this._themeService),this._core=this._terminal._core,this._renderLayers=[new _.LinkRenderLayer(this._core.screenElement,2,this._terminal,this._core.linkifier,this._coreBrowserService,d2,this._themeService)],this.dimensions=(0,g.createRenderDimensions)(),this._devicePixelRatio=this._coreBrowserService.dpr,this._updateDimensions(),this._updateCursorBlink(),this._register(d2.onOptionChange(()=>this._handleOptionsChanged())),this._textBlinkStateManager=this._register(new T.TextBlinkStateManager(()=>this._requestRedrawViewport(),this._coreBrowserService,this._optionsService)),this._resetBlinkingRowState(),this._deviceMaxTextureSize=this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE),this._register((0,u.addDisposableListener)(this._canvas,"webglcontextlost",t4=>{;this._logService.debug("webglcontextlost event received"),t4.preventDefault(),this._contextRestorationTimeout=setTimeout(()=>{;this._contextRestorationTimeout=void 0,this._logService.warn("webgl context not restored; firing onContextLoss"),this._onContextLoss.fire(t4)},3e3)})),this._register((0,u.addDisposableListener)(this._canvas,"webglcontextrestored",t4=>{;this._logService.warn("webglcontextrestored event received"),clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0,(0,r.removeTerminalFromCache)(this._terminal),this._initializeWebGLState(),this._requestRedrawViewport()})),this._observerDisposable.value=(0,o.observeDevicePixelDimensions)(this._canvas,this._coreBrowserService.window,(t4,e4)=>this._setCanvasDevicePixelDimensions(t4,e4)),this._register(this._coreBrowserService.onWindowChange(t4=>{this._observerDisposable.value=(0,o.observeDevicePixelDimensions)(this._canvas,t4,(t5,e4)=>this._setCanvasDevicePixelDimensions(t5,e4))})),this._register((0,u.addDisposableListener)(this._coreBrowserService.mainDocument,"mousedown",()=>this._cursorBlinkStateManager.value?.restartBlinkAnimation())),this._core.screenElement.appendChild(this._canvas),[this._rectangleRenderer.value,this._glyphRenderer.value]=this._initializeWebGLState(),this._isAttached=this._core.screenElement.isConnected,this._register((0,p.toDisposable)(()=>{;clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0;for(const t4 of this._renderLayers)t4.dispose();this._canvas.parentElement?.removeChild(this._canvas),(0,r.removeTerminalFromCache)(this._terminal)}))}get textureAtlas(){return this._charAtlas?.pages[0].canvas}_handleColorChange(){;this._refreshCharAtlas(),this._clearModel(true)}handleDevicePixelRatioChange(){this._devicePixelRatio!==this._coreBrowserService.dpr&&(this._devicePixelRatio=this._coreBrowserService.dpr,this.handleResize(this._terminal.cols,this._terminal.rows))}handleResize(t3,e3){;this._updateDimensions(),this._model.resize(this._terminal.cols,this._terminal.rows),this._resetBlinkingRowState();for(const t4 of this._renderLayers)t4.resize(this._terminal,this.dimensions);this._canvas.width=this.dimensions.device.canvas.width,this._canvas.height=this.dimensions.device.canvas.height,this._canvas.style.width=`${this.dimensions.css.canvas.width}px`,this._canvas.style.height=`${this.dimensions.css.canvas.height}px`,this._core.screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._core.screenElement.style.height=`${this.dimensions.css.canvas.height}px`,this._rectangleRenderer.value?.setDimensions(this.dimensions),this._rectangleRenderer.value?.handleResize(),this._glyphRenderer.value?.setDimensions(this.dimensions),this._glyphRenderer.value?.handleResize(),this._refreshCharAtlas(),this._clearModel(false),this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1,sync:true})}handleCharSizeChanged(){this.handleResize(this._terminal.cols,this._terminal.rows)}handleBlur(){for(const t3 of this._renderLayers)t3.handleBlur(this._terminal);this._cursorBlinkStateManager.value?.pause(),this._requestRedrawViewport()}handleFocus(){for(const t3 of this._renderLayers)t3.handleFocus(this._terminal);this._cursorBlinkStateManager.value?.resume(),this._requestRedrawViewport()}handleViewportVisibilityChange(t3){this._textBlinkStateManager.setViewportVisible(t3)}handleSelectionChanged(t3,e3,i3){for(const s3 of this._renderLayers)s3.handleSelectionChanged(this._terminal,t3,e3,i3);this._model.selection.update(this._core,t3,e3,i3),this._requestRedrawViewport()}handleCursorMove(){for(const t3 of this._renderLayers)t3.handleCursorMove(this._terminal);this._cursorBlinkStateManager.value?.restartBlinkAnimation()}_handleOptionsChanged(){;this._updateDimensions(),this._refreshCharAtlas(),this._updateCursorBlink()}_initializeWebGLState(){return this._rectangleRenderer.value=new d.RectangleRenderer(this._terminal,this._gl,this.dimensions,this._themeService,this._logService),this._glyphRenderer.value=new l.GlyphRenderer(this._terminal,this._gl,this.dimensions,this._optionsService,this._logService),this.handleCharSizeChanged(),[this._rectangleRenderer.value,this._glyphRenderer.value]}_refreshCharAtlas(){if(this.dimensions.device.char.width<=0&&this.dimensions.device.char.height<=0)return void(this._isAttached=false);const t3=(0,r.acquireTextureAtlas)(this._terminal,this._optionsService.rawOptions,this._themeService.colors,this.dimensions.device.cell.width,this.dimensions.device.cell.height,this.dimensions.device.char.width,this.dimensions.device.char.height,this._coreBrowserService.dpr,this._deviceMaxTextureSize,this._customGlyphs);this._charAtlas!==t3&&(this._onChangeTextureAtlas.fire(t3.pages[0].canvas),this._charAtlasDisposable.value=(0,p.combinedDisposable)(Q.EventUtils.forward(t3.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas),Q.EventUtils.forward(t3.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas))),this._charAtlas=t3,this._charAtlas.warmUp(),this._glyphRenderer.value?.setAtlas(this._charAtlas)}_clearModel(t3){;this._model.clear(),t3&&this._glyphRenderer.value?.clear()}clearTextureAtlas(){;this._charAtlas?.clearTexture(),this._clearModel(true),this._requestRedrawViewport()}clear(){this._clearModel(true);for(const t3 of this._renderLayers)t3.reset(this._terminal);this._resetBlinkingRowState(),this._textBlinkStateManager.setNeedsBlinkInViewport(false),this._cursorBlinkStateManager.value?.restartBlinkAnimation(),this._updateCursorBlink()}renderRows(t3,e3){if(!this._isAttached){if(!(this._core.screenElement?.isConnected&&this._charSizeService.width&&this._charSizeService.height))return;this._updateDimensions(),this._refreshCharAtlas(),this._isAttached=true}for(const i4 of this._renderLayers)i4.handleGridChanged(this._terminal,t3,e3);if(!this._glyphRenderer.value||!this._rectangleRenderer.value)return;this._glyphRenderer.value.beginFrame()?(this._clearModel(true),this._updateModel(0,this._terminal.rows-1)):this._updateModel(t3,e3);let i3=false,s3=0;for(;this._charAtlas&&s3++<32&&this._glyphRenderer.value.beginFrame();)i3=true,this._clearModel(true),this._updateModel(0,this._terminal.rows-1);i3&&this._glyphRenderer.value.invalidateAtlasTextures(),this._rectangleRenderer.value.renderBackgrounds(),this._glyphRenderer.value.render(this._model),this._cursorBlinkStateManager.value&&!this._cursorBlinkStateManager.value.isCursorVisible||this._rectangleRenderer.value.renderCursor()}_updateCursorBlink(){;this._coreService.decPrivateModes.cursorBlink??this._terminal.options.cursorBlink?this._cursorBlinkStateManager.value=new a.CursorBlinkStateManager(()=>{this._requestRedrawCursor()},this._coreBrowserService):this._cursorBlinkStateManager.clear(),this._requestRedrawCursor()}_updateModel(t3,e3){const i3=this._core;let s3,r2,a2,o2,n2,L2,T2,l2,d2,_2,Q2,u2,p2,g2,M2,f,C,m=this._workCell;t3=v(t3,i3.rows-1,0),e3=v(e3,i3.rows-1,0);const w=this._coreService.decPrivateModes.cursorStyle??i3.options.cursorStyle??"block",x=this._terminal.buffer.active.baseY+this._terminal.buffer.active.cursorY,b=x-i3.buffer.ydisp,E=Math.min(this._terminal.buffer.active.cursorX,i3.cols-1);let R=-1;const k=this._coreService.isCursorInitialized&&!this._coreService.isCursorHidden&&(!this._cursorBlinkStateManager.value||this._cursorBlinkStateManager.value.isCursorVisible);this._model.cursor=void 0;let A=false;for(r2=t3;r2<=e3;r2++){a2=r2+i3.buffer.ydisp;const t4=i3.buffer.lines.get(a2);if(!t4){for(this._model.lineLengths[r2]=0,f=0;f=T2,d2=f,n2.length>0&&f===n2[0][0]&&l2){_2=n2.shift();const t5=this._model.selection.isCellSelected(this._terminal,_2[0],a2);for(M2=_2[0]+1;M2<_2[1];M2++)l2&&=t5===this._model.selection.isCellSelected(this._terminal,M2,a2);l2&&=!Q2||E<_2[0]||E>=_2[1],l2?(L2=true,m=new y(m,o2.translateToString(true,_2[0],_2[1]),_2[1]-_2[0]),d2=_2[1]-1):T2=_2[1]}if(u2=m.getChars(),p2=m.getCode(),M2=4*(r2*i3.cols+f),!e4&&m.isBlink()&&(e4=true),this._cellColorResolver.resolve(m,f,a2,this.dimensions.device.cell.width,this.dimensions.device.cell.height),k&&a2===x&&(f===E&&(this._model.cursor={x:E,y:b,width:m.getWidth(),style:this._coreBrowserService.isFocused?w:i3.options.cursorInactiveStyle,cursorWidth:i3.options.cursorWidth,dpr:this._devicePixelRatio},R=E+m.getWidth()-1),f>=E&&f<=R&&(this._coreBrowserService.isFocused&&"block"===w||false===this._coreBrowserService.isFocused&&"block"===i3.options.cursorInactiveStyle)&&(this._cellColorResolver.result.fg=50331648|this._themeService.colors.cursorAccent.rgba>>8&16777215,this._cellColorResolver.result.bg=50331648|this._themeService.colors.cursor.rgba>>8&16777215)),this._textBlinkStateManager.isEnabled&&!this._textBlinkStateManager.isBlinkOn&&m.isBlink()&&(this._cellColorResolver.result.fg|=1073741824),p2!==h.NULL_CELL_CODE&&(this._model.lineLengths[r2]=f+1),(this._model.cells[M2]!==p2||this._model.cells[M2+1]!==this._cellColorResolver.result.bg||this._model.cells[M2+2]!==this._cellColorResolver.result.fg||this._model.cells[M2+3]!==this._cellColorResolver.result.ext)&&(A=true,u2.length>1&&(p2|=c.COMBINED_CHAR_BIT_MASK),this._model.cells[M2]=p2,this._model.cells[M2+1]=this._cellColorResolver.result.bg,this._model.cells[M2+2]=this._cellColorResolver.result.fg,this._model.cells[M2+3]=this._cellColorResolver.result.ext,g2=m.getWidth(),this._glyphRenderer.value.updateCell(f,r2,p2,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext,u2,g2,s3),L2)){for(m=this._workCell,f++;f<=d2;f++)C=4*(r2*i3.cols+f),this._nullModelCell(f,r2,C,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext);f--}}this._setRowBlinkState(r2,e4)};A&&this._rectangleRenderer.value.updateBackgrounds(this._model),this._rectangleRenderer.value.updateCursor(this._model),this._updateTextBlinkState()}_nullModelCell(t3,e3,i3,s3,r2,a2){;this._glyphRenderer.value.updateCell(t3,e3,h.NULL_CELL_CODE,s3,r2,a2,h.NULL_CELL_CHAR,0,0),this._model.cells[i3]=h.NULL_CELL_CODE,this._model.cells[i3+1]=s3,this._model.cells[i3+2]=r2,this._model.cells[i3+3]=a2}_resetBlinkingRowState(){;this._rowHasBlinkingCells=new Array(this._terminal.rows).fill(false),this._rowHasBlinkingCellsCount=0}_setRowBlinkState(t3,e3){this._rowHasBlinkingCells[t3]!==e3&&(this._rowHasBlinkingCells[t3]=e3,this._rowHasBlinkingCellsCount+=e3?1:-1)}_updateTextBlinkState(){this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount>0)}_updateDimensions(){this._charSizeService.width&&this._charSizeService.height&&(this.dimensions.device.char.width=Math.floor(this._charSizeService.width*this._devicePixelRatio),this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*this._devicePixelRatio),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.top=1===this._optionsService.rawOptions.lineHeight?0:Math.round((this.dimensions.device.cell.height-this.dimensions.device.char.height)/2),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.char.left=Math.floor(this._optionsService.rawOptions.letterSpacing/2),this.dimensions.device.canvas.height=this._terminal.rows*this.dimensions.device.cell.height,this.dimensions.device.canvas.width=this._terminal.cols*this.dimensions.device.cell.width,this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/this._devicePixelRatio),this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/this._devicePixelRatio),this.dimensions.css.cell.height=this.dimensions.device.cell.height/this._devicePixelRatio,this.dimensions.css.cell.width=this.dimensions.device.cell.width/this._devicePixelRatio)}_setCanvasDevicePixelDimensions(t3,e3){;this._canvas.width===t3&&this._canvas.height===e3||(this._canvas.width=t3,this._canvas.height=e3,this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1,sync:true}))}_requestRedrawViewport(){this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1})}_requestRedrawCursor(){const t3=this._terminal.buffer.active.cursorY;this._onRequestRedraw.fire({start:t3,end:t3})}}e2.WebglRenderer=M;class y extends n.AttributeData{constructor(t3,e3,i3){;super(),this.content=0,this.combinedData="",this.fg=t3.fg,this.bg=t3.bg,this.combinedData=e3,this._width=i3}isCombined(){return 2097152}getWidth(){return this._width}getChars(){return this.combinedData}getCode(){return 2097151}setFromCharData(t3){throw new Error("not implemented")}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}}function v(t3,e3,i3=0){return Math.max(Math.min(t3,e3),i3)}e2.JoinedCellData=y},719(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.GLTexture=e2.PROJECTION_MATRIX=void 0,e2.createProgram=function(t3,e3,i3,a){const o=(0,s2.throwIfFalsy)(t3.createProgram());if(t3.attachShader(o,(0,s2.throwIfFalsy)(r(t3,t3.VERTEX_SHADER,e3,a))),t3.attachShader(o,(0,s2.throwIfFalsy)(r(t3,t3.FRAGMENT_SHADER,i3,a))),t3.linkProgram(o),t3.getProgramParameter(o,t3.LINK_STATUS))return o;a.error(t3.getProgramInfoLog(o)),t3.deleteProgram(o)},e2.createShader=r,e2.expandFloat32Array=function(t3,e3){const i3=Math.min(2*t3.length,e3),s3=new Float32Array(i3);for(let e4=0;e4{const e3=[],i3=[3/8,2/8,3/8],s3=[0,3/8,5/8];for(let r2=0;r2<3;r2++){const a=t3>>2*r2&1,o=t3>>2*r2+1&1;a&&o?e3.push(`M0,${s3[r2]} L1,${s3[r2]} L1,${s3[r2]+i3[r2]} L0,${s3[r2]+i3[r2]} Z`):a?e3.push(`M0,${s3[r2]} L0.5,${s3[r2]} L0.5,${s3[r2]+i3[r2]} L0,${s3[r2]+i3[r2]} Z`):o&&e3.push(`M0.5,${s3[r2]} L1,${s3[r2]} L1,${s3[r2]+i3[r2]} L0.5,${s3[r2]+i3[r2]} Z`)}return e3.join(" ")}}}function r(t3){const e3=[],i3=.15,s3=.015,r2=.075,a=.0375,o=.95;if(64&t3){const t4=.175,a2=.2125,n=.25,L=.2+s3,h=o-i3-s3;e3.push(`M${L},${a2} L${L+r2},${t4} L${h-r2},${t4} L${h},${a2} L${h-r2},${n} L${L+r2},${n} Z`)}if(32&t3){const t4=o-i3,s4=.875,r3=o,n=.265,L=.485;e3.push(`M${s4},${n} L${r3},${n+a} L${r3},${L-a} L${s4},${L} L${t4},${L-a} L${t4},${n+a} Z`)}if(16&t3){const t4=o-i3,s4=.875,r3=o,n=.515,L=.735;e3.push(`M${s4},${n} L${r3},${n+a} L${r3},${L-a} L${s4},${L} L${t4},${L-a} L${t4},${n+a} Z`)}if(8&t3){const t4=.75,a2=.7875,n=.825,L=.2+s3,h=o-i3-s3;e3.push(`M${L},${a2} L${L+r2},${t4} L${h-r2},${t4} L${h},${a2} L${h-r2},${n} L${L+r2},${n} Z`)}if(4&t3){const t4=.05,i4=.125,s4=.2,r3=.515,o2=.735;e3.push(`M${i4},${r3} L${s4},${r3+a} L${s4},${o2-a} L${i4},${o2} L${t4},${o2-a} L${t4},${r3+a} Z`)}if(2&t3){const t4=.05,i4=.125,s4=.2,r3=.265,o2=.485;e3.push(`M${i4},${r3} L${s4},${r3+a} L${s4},${o2-a} L${i4},${o2} L${t4},${o2-a} L${t4},${r3+a} Z`)}if(1&t3){const t4=.4625,a2=.5,n=.5375,L=.2+s3,h=o-i3-s3;e3.push(`M${L},${a2} L${L+r2},${t4} L${h-r2},${t4} L${h},${a2} L${h-r2},${n} L${L+r2},${n} Z`)}return e3.join(" ")};Object.defineProperty(e2,"__esModule",{value:true}),e2.blockPatternCodepoints=e2.customGlyphDefinitions=void 0,(function(t3){;t3.LINE_H=Object.freeze({type:2,data:"M0,.5 L1,.5",strokeWidth:1}),t3.LINE_V=Object.freeze({type:2,data:"M.5,0 L.5,1",strokeWidth:1}),t3.FADE_RIGHT=Object.freeze({type:2,data:"M0,.5 L.28,.5 M.32,.5 L.52,.5 M.60,.5 L.72,.5 M.84,.5 L.90,.5",strokeWidth:1}),t3.FADE_LEFT=Object.freeze({type:2,data:"M.10,.5 L.16,.5 M.28,.5 L.40,.5 M.48,.5 L.68,.5 M.72,.5 L1,.5",strokeWidth:1}),t3.FADE_DOWN=Object.freeze({type:2,data:"M.5,0 L.5,.28 M.5,.32 L.5,.52 M.5,.60 L.5,.72 M.5,.84 L.5,.90",strokeWidth:1}),t3.FADE_UP=Object.freeze({type:2,data:"M.5,.10 L.5,.16 M.5,.28 L.5,.40 M.5,.48 L.5,.68 M.5,.72 L.5,1",strokeWidth:1}),t3.CURVE_DOWN_RIGHT=Object.freeze({type:2,data:(t4,e3)=>`M.5,1 L.5,${.5+e3/.15*.5} C.5,${.5+e3/.15*.5},.5,.5,1,.5`,strokeWidth:1}),t3.CURVE_DOWN_LEFT=Object.freeze({type:2,data:(t4,e3)=>`M.5,1 L.5,${.5+e3/.15*.5} C.5,${.5+e3/.15*.5},.5,.5,0,.5`,strokeWidth:1}),t3.CURVE_UP_RIGHT=Object.freeze({type:2,data:(t4,e3)=>`M.5,0 L.5,${.5-e3/.15*.5} C.5,${.5-e3/.15*.5},.5,.5,1,.5`,strokeWidth:1}),t3.CURVE_UP_LEFT=Object.freeze({type:2,data:(t4,e3)=>`M.5,0 L.5,${.5-e3/.15*.5} C.5,${.5-e3/.15*.5},.5,.5,0,.5`,strokeWidth:1}),t3.NODE_FILL=Object.freeze({type:3,data:"M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5"}),t3.NODE_STROKE=Object.freeze({type:3,data:"M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5",strokeWidth:1}),t3.NODE_LINE_RIGHT=Object.freeze({type:3,data:"M1,.5 L.85,.5",strokeWidth:1}),t3.NODE_LINE_LEFT=Object.freeze({type:3,data:"M0,.5 L.15,.5",strokeWidth:1}),t3.NODE_LINE_DOWN=Object.freeze({type:3,data:"M.5,1 L.5,.7",strokeWidth:1}),t3.NODE_LINE_UP=Object.freeze({type:3,data:"M.5,0 L.5,.3",strokeWidth:1})})(i2||(i2={})),e2.customGlyphDefinitions={"\u2500":{type:2,data:"M0,.5 L1,.5",strokeWidth:1},"\u2501":{type:2,data:"M0,.5 L1,.5",strokeWidth:3},"\u2502":{type:2,data:"M.5,0 L.5,1",strokeWidth:1},"\u2503":{type:2,data:"M.5,0 L.5,1",strokeWidth:3},"\u2504":{type:2,data:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",strokeWidth:1},"\u2505":{type:2,data:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",strokeWidth:3},"\u2506":{type:2,data:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",strokeWidth:1},"\u2507":{type:2,data:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",strokeWidth:3},"\u2508":{type:2,data:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",strokeWidth:1},"\u2509":{type:2,data:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",strokeWidth:3},"\u250A":{type:2,data:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",strokeWidth:1},"\u250B":{type:2,data:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",strokeWidth:3},"\u250C":{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},"\u250D":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u250E":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u250F":{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3},"\u2510":{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},"\u2511":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u2512":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2513":{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3},"\u2514":{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},"\u2515":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u2516":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2517":{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3},"\u2518":{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},"\u2519":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u251A":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u251B":{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3},"\u251C":{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:1},"\u251D":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u251E":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u251F":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2520":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"\u2521":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"\u2522":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"\u2523":{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:3},"\u2524":{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:1},"\u2525":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u2526":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2527":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2528":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"\u2529":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"\u252A":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"\u252B":{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:3},"\u252C":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:1},"\u252D":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u252E":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u252F":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"\u2530":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2531":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"\u2532":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"\u2533":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:3},"\u2534":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,0",strokeWidth:1},"\u2535":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u2536":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u2537":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"\u2538":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2539":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"\u253A":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"\u253B":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,0",strokeWidth:3},"\u253C":{type:2,data:"M0,.5 L1,.5 M.5,0 L.5,1",strokeWidth:1},"\u253D":[{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u253E":[{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u253F":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"\u2540":[{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2541":[{type:2,data:"M.5,.5 L.5,0 M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2542":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"\u2543":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"\u2544":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"\u2545":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"\u2546":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"\u2547":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0 M0,.5 L1,.5",strokeWidth:3}],"\u2548":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:3}],"\u2549":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:3}],"\u254A":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:3}],"\u254B":{type:2,data:"M0,.5 L1,.5 M.5,0 L.5,1",strokeWidth:3},"\u254C":{type:2,data:"M.1,.5 L.4,.5 M.6,.5 L.9,.5",strokeWidth:1},"\u254D":{type:2,data:"M.1,.5 L.4,.5 M.6,.5 L.9,.5",strokeWidth:3},"\u254E":{type:2,data:"M.5,.1 L.5,.4 M.5,.6 L.5,.9",strokeWidth:1},"\u254F":{type:2,data:"M.5,.1 L.5,.4 M.5,.6 L.5,.9",strokeWidth:3},"\u2550":{type:2,data:(t3,e3)=>`M0,${.5-e3} L1,${.5-e3} M0,${.5+e3} L1,${.5+e3}`,strokeWidth:1},"\u2551":{type:2,data:(t3,e3)=>`M${.5-t3},0 L${.5-t3},1 M${.5+t3},0 L${.5+t3},1`,strokeWidth:1},"\u2552":{type:2,data:(t3,e3)=>`M.5,1 L.5,${.5-e3} L1,${.5-e3} M.5,${.5+e3} L1,${.5+e3}`,strokeWidth:1},"\u2553":{type:2,data:(t3,e3)=>`M${.5-t3},1 L${.5-t3},.5 L1,.5 M${.5+t3},.5 L${.5+t3},1`,strokeWidth:1},"\u2554":{type:2,data:(t3,e3)=>`M1,${.5-e3} L${.5-t3},${.5-e3} L${.5-t3},1 M1,${.5+e3} L${.5+t3},${.5+e3} L${.5+t3},1`,strokeWidth:1},"\u2555":{type:2,data:(t3,e3)=>`M0,${.5-e3} L.5,${.5-e3} L.5,1 M0,${.5+e3} L.5,${.5+e3}`,strokeWidth:1},"\u2556":{type:2,data:(t3,e3)=>`M${.5+t3},1 L${.5+t3},.5 L0,.5 M${.5-t3},.5 L${.5-t3},1`,strokeWidth:1},"\u2557":{type:2,data:(t3,e3)=>`M0,${.5+e3} L${.5-t3},${.5+e3} L${.5-t3},1 M0,${.5-e3} L${.5+t3},${.5-e3} L${.5+t3},1`,strokeWidth:1},"\u2558":{type:2,data:(t3,e3)=>`M.5,0 L.5,${.5+e3} L1,${.5+e3} M.5,${.5-e3} L1,${.5-e3}`,strokeWidth:1},"\u2559":{type:2,data:(t3,e3)=>`M1,.5 L${.5-t3},.5 L${.5-t3},0 M${.5+t3},.5 L${.5+t3},0`,strokeWidth:1},"\u255A":{type:2,data:(t3,e3)=>`M1,${.5-e3} L${.5+t3},${.5-e3} L${.5+t3},0 M1,${.5+e3} L${.5-t3},${.5+e3} L${.5-t3},0`,strokeWidth:1},"\u255B":{type:2,data:(t3,e3)=>`M0,${.5+e3} L.5,${.5+e3} L.5,0 M0,${.5-e3} L.5,${.5-e3}`,strokeWidth:1},"\u255C":{type:2,data:(t3,e3)=>`M0,.5 L${.5+t3},.5 L${.5+t3},0 M${.5-t3},.5 L${.5-t3},0`,strokeWidth:1},"\u255D":{type:2,data:(t3,e3)=>`M0,${.5-e3} L${.5-t3},${.5-e3} L${.5-t3},0 M0,${.5+e3} L${.5+t3},${.5+e3} L${.5+t3},0`,strokeWidth:1},"\u255E":{type:2,data:(t3,e3)=>`M.5,0 L.5,1 M.5,${.5-e3} L1,${.5-e3} M.5,${.5+e3} L1,${.5+e3}`,strokeWidth:1},"\u255F":{type:2,data:(t3,e3)=>`M${.5-t3},0 L${.5-t3},1 M${.5+t3},0 L${.5+t3},1 M${.5+t3},.5 L1,.5`,strokeWidth:1},"\u2560":{type:2,data:(t3,e3)=>`M${.5-t3},0 L${.5-t3},1 M1,${.5+e3} L${.5+t3},${.5+e3} L${.5+t3},1 M1,${.5-e3} L${.5+t3},${.5-e3} L${.5+t3},0`,strokeWidth:1},"\u2561":{type:2,data:(t3,e3)=>`M.5,0 L.5,1 M0,${.5-e3} L.5,${.5-e3} M0,${.5+e3} L.5,${.5+e3}`,strokeWidth:1},"\u2562":{type:2,data:(t3,e3)=>`M0,.5 L${.5-t3},.5 M${.5-t3},0 L${.5-t3},1 M${.5+t3},0 L${.5+t3},1`,strokeWidth:1},"\u2563":{type:2,data:(t3,e3)=>`M${.5+t3},0 L${.5+t3},1 M0,${.5+e3} L${.5-t3},${.5+e3} L${.5-t3},1 M0,${.5-e3} L${.5-t3},${.5-e3} L${.5-t3},0`,strokeWidth:1},"\u2564":{type:2,data:(t3,e3)=>`M0,${.5-e3} L1,${.5-e3} M0,${.5+e3} L1,${.5+e3} M.5,${.5+e3} L.5,1`,strokeWidth:1},"\u2565":{type:2,data:(t3,e3)=>`M0,.5 L1,.5 M${.5-t3},.5 L${.5-t3},1 M${.5+t3},.5 L${.5+t3},1`,strokeWidth:1},"\u2566":{type:2,data:(t3,e3)=>`M0,${.5-e3} L1,${.5-e3} M0,${.5+e3} L${.5-t3},${.5+e3} L${.5-t3},1 M1,${.5+e3} L${.5+t3},${.5+e3} L${.5+t3},1`,strokeWidth:1},"\u2567":{type:2,data:(t3,e3)=>`M.5,0 L.5,${.5-e3} M0,${.5-e3} L1,${.5-e3} M0,${.5+e3} L1,${.5+e3}`,strokeWidth:1},"\u2568":{type:2,data:(t3,e3)=>`M0,.5 L1,.5 M${.5-t3},.5 L${.5-t3},0 M${.5+t3},.5 L${.5+t3},0`,strokeWidth:1},"\u2569":{type:2,data:(t3,e3)=>`M0,${.5+e3} L1,${.5+e3} M0,${.5-e3} L${.5-t3},${.5-e3} L${.5-t3},0 M1,${.5-e3} L${.5+t3},${.5-e3} L${.5+t3},0`,strokeWidth:1},"\u256A":{type:2,data:(t3,e3)=>`M.5,0 L.5,1 M0,${.5-e3} L1,${.5-e3} M0,${.5+e3} L1,${.5+e3}`,strokeWidth:1},"\u256B":{type:2,data:(t3,e3)=>`M0,.5 L1,.5 M${.5-t3},0 L${.5-t3},1 M${.5+t3},0 L${.5+t3},1`,strokeWidth:1},"\u256C":{type:2,data:(t3,e3)=>`M0,${.5+e3} L${.5-t3},${.5+e3} L${.5-t3},1 M1,${.5+e3} L${.5+t3},${.5+e3} L${.5+t3},1 M0,${.5-e3} L${.5-t3},${.5-e3} L${.5-t3},0 M1,${.5-e3} L${.5+t3},${.5-e3} L${.5+t3},0`,strokeWidth:1},"\u256D":{type:2,data:(t3,e3)=>`M.5,1 L.5,${.5+e3/.15*.5} C.5,${.5+e3/.15*.5},.5,.5,1,.5`,strokeWidth:1},"\u256E":{type:2,data:(t3,e3)=>`M.5,1 L.5,${.5+e3/.15*.5} C.5,${.5+e3/.15*.5},.5,.5,0,.5`,strokeWidth:1},"\u256F":{type:2,data:(t3,e3)=>`M.5,0 L.5,${.5-e3/.15*.5} C.5,${.5-e3/.15*.5},.5,.5,0,.5`,strokeWidth:1},"\u2570":{type:2,data:(t3,e3)=>`M.5,0 L.5,${.5-e3/.15*.5} C.5,${.5-e3/.15*.5},.5,.5,1,.5`,strokeWidth:1},"\u2571":{type:2,data:"M1,0 L0,1",strokeWidth:1},"\u2572":{type:2,data:"M0,0 L1,1",strokeWidth:1},"\u2573":{type:2,data:"M1,0 L0,1 M0,0 L1,1",strokeWidth:1},"\u2574":{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},"\u2575":{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},"\u2576":{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},"\u2577":{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},"\u2578":{type:2,data:"M.5,.5 L0,.5",strokeWidth:3},"\u2579":{type:2,data:"M.5,.5 L.5,0",strokeWidth:3},"\u257A":{type:2,data:"M.5,.5 L1,.5",strokeWidth:3},"\u257B":{type:2,data:"M.5,.5 L.5,1",strokeWidth:3},"\u257C":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u257D":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u257E":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u257F":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2580":{type:0,data:[{x:0,y:0,w:8,h:4}]},"\u2581":{type:0,data:[{x:0,y:7,w:8,h:1}]},"\u2582":{type:0,data:[{x:0,y:6,w:8,h:2}]},"\u2583":{type:0,data:[{x:0,y:5,w:8,h:3}]},"\u2584":{type:0,data:[{x:0,y:4,w:8,h:4}]},"\u2585":{type:0,data:[{x:0,y:3,w:8,h:5}]},"\u2586":{type:0,data:[{x:0,y:2,w:8,h:6}]},"\u2587":{type:0,data:[{x:0,y:1,w:8,h:7}]},"\u2588":{type:0,data:[{x:0,y:0,w:8,h:8}]},"\u2589":{type:0,data:[{x:0,y:0,w:7,h:8}]},"\u258A":{type:0,data:[{x:0,y:0,w:6,h:8}]},"\u258B":{type:0,data:[{x:0,y:0,w:5,h:8}]},"\u258C":{type:0,data:[{x:0,y:0,w:4,h:8}]},"\u258D":{type:0,data:[{x:0,y:0,w:3,h:8}]},"\u258E":{type:0,data:[{x:0,y:0,w:2,h:8}]},"\u258F":{type:0,data:[{x:0,y:0,w:1,h:8}]},"\u2590":{type:0,data:[{x:4,y:0,w:4,h:8}]},"\u2591":{type:1,data:[[1,0],[0,0]]},"\u2592":{type:1,data:[[1,0],[0,1]]},"\u2593":{type:1,data:[[1,1],[1,0]]},"\u2594":{type:0,data:[{x:0,y:0,w:8,h:1}]},"\u2595":{type:0,data:[{x:7,y:0,w:1,h:8}]},"\u2596":{type:0,data:[{x:0,y:4,w:4,h:4}]},"\u2597":{type:0,data:[{x:4,y:4,w:4,h:4}]},"\u2598":{type:0,data:[{x:0,y:0,w:4,h:4}]},"\u2599":{type:0,data:[{x:0,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}]},"\u259A":{type:0,data:[{x:0,y:0,w:4,h:4},{x:4,y:4,w:4,h:4}]},"\u259B":{type:0,data:[{x:0,y:0,w:4,h:8},{x:4,y:0,w:4,h:4}]},"\u259C":{type:0,data:[{x:0,y:0,w:8,h:4},{x:4,y:0,w:4,h:8}]},"\u259D":{type:0,data:[{x:4,y:0,w:4,h:4}]},"\u259E":{type:0,data:[{x:4,y:0,w:4,h:4},{x:0,y:4,w:4,h:4}]},"\u259F":{type:0,data:[{x:4,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}]},"\uE0A0":{type:5,data:{d:"M.3,1 L.03,1 L.03,.88 C.03,.82,.06,.78,.11,.73 C.15,.7,.2,.68,.28,.65 L.43,.6 C.49,.58,.53,.56,.56,.53 C.59,.5,.6,.47,.6,.43 L.6,.27 L.4,.27 L.69,.1 L.98,.27 L.78,.27 L.78,.46 C.78,.52,.76,.56,.72,.61 C.68,.66,.63,.67,.56,.7 L.48,.72 C.42,.74,.38,.76,.35,.78 C.32,.8,.31,.84,.31,.88 L.31,1 M.3,.5 L.03,.59 L.03,.09 L.3,.09 L.3,.655",type:0}},"\uE0A1":{type:5,data:{d:"M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.355,.03 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5",type:0}},"\uE0A2":{type:5,data:{d:"M.25,.94 C.16,.94,.11,.92,.11,.87 L.11,.53 C.11,.48,.15,.455,.23,.45 L.23,.3 C.23,.25,.26,.22,.31,.19 C.36,.16,.43,.15,.51,.15 C.59,.15,.66,.16,.71,.19 C.77,.22,.79,.26,.79,.3 L.79,.45 C.87,.45,.91,.48,.91,.53 L.91,.87 C.91,.92,.86,.94,.77,.94 L.24,.94 M.53,.2 C.49,.2,.45,.21,.42,.23 C.39,.25,.38,.27,.38,.3 L.38,.45 L.68,.45 L.68,.3 C.68,.27,.67,.25,.64,.23 C.61,.21,.58,.2,.53,.2 M.58,.82 L.58,.66 C.63,.65,.65,.63,.65,.6 C.65,.58,.64,.57,.61,.56 C.58,.55,.56,.54,.52,.54 C.48,.54,.46,.55,.43,.56 C.4,.57,.39,.59,.39,.6 C.39,.63,.41,.64,.46,.66 L.46,.82 L.57,.82",type:0}},"\uE0A3":{type:5,data:{d:"M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.7,.03 L.7,.1 L.355,.1 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5",type:0}},"\uE0B0":{type:5,data:{d:"M0,0 L1,.5 L0,1",type:0,rightPadding:2}},"\uE0B1":{type:5,data:{d:"M-1,-.5 L1,.5 L-1,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0B2":{type:5,data:{d:"M1,0 L0,.5 L1,1",type:0,leftPadding:2}},"\uE0B3":{type:5,data:{d:"M2,-.5 L0,.5 L2,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0B4":{type:5,data:{d:"M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0",type:0,rightPadding:1}},"\uE0B5":{type:5,data:{d:"M.2,1 C.422,1,.8,.826,.78,.5 C.8,.174,0.422,0,.2,0",type:1,rightPadding:1}},"\uE0B6":{type:5,data:{d:"M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0",type:0,leftPadding:1}},"\uE0B7":{type:5,data:{d:"M.8,1 C0.578,1,0.2,.826,.22,.5 C0.2,0.174,0.578,0,0.8,0",type:1,leftPadding:1}},"\uE0B8":{type:5,data:{d:"M-.5,-.5 L1.5,1.5 L-.5,1.5",type:0}},"\uE0B9":{type:5,data:{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0BA":{type:5,data:{d:"M1.5,-.5 L-.5,1.5 L1.5,1.5",type:0}},"\uE0BB":{type:5,data:{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0BC":{type:5,data:{d:"M1.5,-.5 L-.5,1.5 L-.5,-.5",type:0}},"\uE0BD":{type:5,data:{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0BE":{type:5,data:{d:"M-.5,-.5 L1.5,1.5 L1.5,-.5",type:0}},"\uE0BF":{type:5,data:{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0C0":{type:5,data:{d:"M0.8069,0.6075 Q0.7991,0.612,0.7815,0.6218 T0.7542,0.6369 T0.7282,0.6508 T0.7016,0.6644 T0.6778,0.6758 T0.6551,0.6854 T0.637,0.6911 T0.622,0.6936 Q0.6186,0.6936,0.6128,0.6942 T0.6018,0.6952 T0.5894,0.6956 T0.5757,0.6952 T0.561,0.693 T0.5451,0.6879 Q0.5342,0.6834,0.5193,0.6756 T0.4925,0.6614 T0.4673,0.6485 T0.4436,0.6389 T0.4239,0.6363 T0.4089,0.6422 Q0.4045,0.6467,0.3958,0.6554 T0.3843,0.6669 T0.3748,0.6746 T0.3613,0.683 T0.3442,0.6903 L0.3499,0.6956 Q0.3584,0.7042,0.3679,0.7177 T0.3836,0.7403 T0.4019,0.7583 T0.4295,0.7703 Q0.4688,0.7785,0.5203,0.7605 Q0.4902,0.7817,0.449,0.7911 T0.3696,0.8001 Q0.3567,0.7997,0.341,0.7942 T0.3111,0.7813 T0.2805,0.766 T0.2493,0.753 T0.218,0.7472 T0.187,0.754 Q0.1762,0.7589,0.1653,0.7681 T0.1453,0.7895 T0.1308,0.818 T0.1264,0.8507 L0.1264,0.858 Q0.1453,0.8462,0.1607,0.8433 T0.1899,0.8452 T0.2226,0.8625 Q0.2344,0.8707,0.2485,0.8752 T0.2813,0.8831 T0.3089,0.889 Q0.2981,0.8915,0.2846,0.8956 T0.2603,0.9037 T0.2368,0.9115 T0.2129,0.9174 T0.1894,0.9194 T0.1653,0.9155 Q0.1504,0.9115,0.1328,0.9196 T0.0986,0.9421 T0.0655,0.9708 T0.0317,0.9943 T0,1 Q0.001,0.94,0.0017,0.6508 T0.0024,0.2322 Q0.002,0.2109,0.0007,0.1379 T0,0.0249 Q0.0058,0.0253,0.009,0.0257 T0.0156,0.0288 T0.0203,0.0333 T0.0264,0.0422 T0.0349,0.0539 Q0.044,0.0657,0.0542,0.072 T0.0752,0.0789 T0.096,0.0775 T0.1175,0.0694 T0.1379,0.0573 T0.1579,0.0432 Q0.2141,0,0.2673,0.0318 Q0.2588,0.0318,0.2517,0.033 T0.239,0.0361 T0.2276,0.0424 T0.218,0.0504 T0.2085,0.0612 T0.1997,0.0732 T0.1899,0.0875 T0.1795,0.1028 Q0.1748,0.1093,0.1507,0.1234 T0.1186,0.1448 Q0.0999,0.1612,0.1108,0.1783 Q0.1162,0.1877,0.1238,0.1928 T0.1404,0.1979 T0.158,0.1967 T0.1775,0.1895 T0.1963,0.1791 T0.2151,0.1663 T0.2314,0.1534 T0.2458,0.141 T0.2561,0.1322 Q0.2815,0.1106,0.3017,0.101 T0.344,0.0926 T0.3919,0.1077 Q0.4099,0.1175,0.4265,0.1206 T0.4588,0.1202 T0.488,0.1098 T0.5176,0.0914 Q0.5071,0.1032,0.4963,0.1122 T0.4722,0.1285 T0.4497,0.1401 T0.4223,0.1518 T0.3943,0.1636 Q0.374,0.1726,0.3623,0.1863 T0.3482,0.2162 Q0.3445,0.2403,0.3608,0.2605 T0.4072,0.2876 Q0.4194,0.2909,0.4326,0.2899 T0.457,0.2856 T0.4805,0.2764 T0.5032,0.264 T0.5256,0.2497 T0.5478,0.2352 T0.5698,0.2222 T0.5921,0.2118 Q0.6982,0.173,0.81,0.2436 Q0.833,0.2583,0.8496,0.2674 T0.887,0.2846 T0.925,0.294 T0.9617,0.2911 T1,0.2746 Q0.9817,0.2946,0.96,0.3056 T0.9175,0.3191 T0.8731,0.3209 T0.8277,0.3164 T0.7818,0.3109 T0.7359,0.3101 T0.6907,0.3197 T0.647,0.3448 Q0.6443,0.3472,0.6333,0.3562 T0.6194,0.3674 T0.6076,0.3766 T0.594,0.386 T0.5815,0.3931 T0.5661,0.4007 T0.5501,0.4064 L0.5556,0.4121 Q0.5627,0.4198,0.5722,0.4251 T0.5926,0.4337 T0.6131,0.439 T0.6343,0.4431 T0.6521,0.4468 Q0.6762,0.4529,0.7056,0.4463 Q0.6772,0.4623,0.6543,0.471 T0.604,0.4814 Q0.5962,0.4818,0.5815,0.4825 T0.5591,0.4835 T0.5393,0.4857 T0.519,0.4908 T0.5007,0.4996 T0.4812,0.5141 T0.4627,0.5353 L0.4549,0.5451 L0.4661,0.543 Q0.4759,0.541,0.4909,0.55 T0.5315,0.5796 T0.5684,0.6083 Q0.5799,0.6161,0.5915,0.6212 T0.6181,0.6308 L0.6331,0.6353 Q0.6514,0.6418,0.7004,0.633 T0.8069,0.6075 Z",type:0}},"\uE0C1":{type:5,data:{d:"M0.011,0.0469 Q0.0152,0.0412,0.02,0.0361 T0.0292,0.0281 T0.0365,0.0253 Q0.0426,0.0257,0.0461,0.0263 T0.0534,0.03 T0.0582,0.0345 T0.0645,0.0434 T0.0723,0.0546 Q0.0807,0.0653,0.0897,0.0712 T0.1081,0.0775 T0.1255,0.0765 T0.1438,0.0695 T0.1605,0.0593 T0.1772,0.0473 Q0.2388,0,0.2927,0.0326 L0.2614,0.0767 L0.2581,0.0767 Q0.2494,0.0767,0.2444,0.0773 T0.2359,0.0789 T0.2291,0.0842 T0.2233,0.0922 T0.2146,0.1054 T0.2026,0.1236 Q0.1943,0.1354,0.1843,0.1444 T0.1675,0.1574 T0.1526,0.1654 T0.1429,0.1701 Q0.1436,0.1717,0.1446,0.1733 Q0.1504,0.1823,0.1581,0.1866 T0.1733,0.1915 T0.1909,0.1884 T0.2085,0.1809 T0.2265,0.169 T0.2427,0.1564 T0.2575,0.1436 T0.2688,0.1338 Q0.2969,0.1101,0.3195,0.0995 T0.3643,0.0901 T0.4114,0.1048 Q0.4279,0.1138,0.4426,0.1168 T0.4705,0.117 T0.4945,0.1089 T0.5186,0.0942 L0.5211,0.0926 L0.5289,0.0922 Q0.5411,0.095,0.5189,0.1203 Q0.505,0.1358,0.4913,0.1474 T0.4634,0.167 T0.4389,0.1796 T0.4121,0.1909 T0.3866,0.2011 Q0.3782,0.2047,0.3727,0.2092 Q0.3717,0.2117,0.3714,0.2141 Q0.3695,0.2263,0.3751,0.2388 T0.394,0.2616 T0.4253,0.2761 Q0.4398,0.2785,0.4513,0.2779 T0.4756,0.2724 T0.4987,0.2616 T0.5216,0.2476 T0.5453,0.2321 T0.5703,0.217 T0.5976,0.2043 Q0.7015,0.166,0.8099,0.2345 Q0.8361,0.2512,0.8572,0.2616 T0.9006,0.2779 T0.9432,0.281 T0.9819,0.2663 L0.9887,0.2671 Q1,0.272,0.9768,0.2969 Q0.9558,0.3193,0.9319,0.333 T0.8858,0.3513 T0.8391,0.3569 T0.793,0.3546 T0.7485,0.3497 T0.7065,0.3473 T0.6681,0.3522 T0.6344,0.3699 Q0.5963,0.4005,0.5847,0.4082 Q0.595,0.4139,0.6079,0.4174 T0.6367,0.4237 T0.6586,0.4278 Q0.6793,0.4331,0.7044,0.4278 Q0.7057,0.4278,0.707,0.4274 Q0.7067,0.429,0.7062,0.4313 T0.7036,0.4398 T0.6993,0.4513 T0.6931,0.4619 T0.6851,0.4698 Q0.6573,0.4857,0.6336,0.4947 T0.5831,0.5053 Q0.577,0.5057,0.5615,0.5065 T0.5394,0.508 T0.5221,0.51 T0.5042,0.5139 T0.4905,0.5204 Q0.5008,0.5232,0.515,0.5328 T0.549,0.5585 T0.5799,0.5824 Q0.5905,0.5897,0.6013,0.5946 T0.6262,0.6036 T0.6409,0.6081 Q0.6567,0.6134,0.7035,0.605 T0.8028,0.5816 L0.7974,0.5922 T0.7867,0.6134 L0.7812,0.624 Q0.7799,0.6248,0.7786,0.6252 Q0.7709,0.6297,0.7573,0.6372 T0.7346,0.6499 T0.7123,0.6621 T0.6897,0.6741 T0.6686,0.6847 T0.648,0.6941 T0.6297,0.7013 T0.6129,0.7062 T0.5995,0.7076 Q0.5992,0.7076,0.5831,0.7092 T0.5529,0.7092 T0.525,0.7019 Q0.5105,0.6958,0.4824,0.6811 T0.4356,0.6586 T0.4079,0.6533 Q0.3872,0.6741,0.3827,0.6782 Q0.3872,0.6835,0.3934,0.6929 T0.4037,0.7078 T0.4142,0.7198 T0.4284,0.7304 T0.4466,0.7365 Q0.4824,0.7443,0.5292,0.728 L0.5292,0.7294 T0.529,0.7333 T0.5284,0.7392 T0.5266,0.7463 T0.5236,0.7539 T0.5186,0.7612 T0.5111,0.7679 Q0.5086,0.77,0.5056,0.7716 Q0.4466,0.8104,0.3591,0.8091 Q0.344,0.8091,0.3246,0.802 T0.2893,0.7863 T0.2543,0.77 T0.2206,0.76 T0.1894,0.7643 Q0.1804,0.7684,0.1733,0.7753 Q0.1591,0.7892,0.1601,0.8169 Q0.1784,0.8067,0.1938,0.8053 T0.2212,0.8087 T0.2507,0.825 Q0.2614,0.8324,0.2744,0.8365 T0.3053,0.844 T0.332,0.8499 L0.303,0.894 L0.2998,0.8948 Q0.2904,0.8968,0.278,0.9009 T0.2554,0.9086 T0.233,0.9158 T0.2099,0.9213 T0.1868,0.9229 T0.1633,0.9192 Q0.1517,0.916,0.1367,0.9241 T0.1065,0.9462 T0.0753,0.9737 T0.041,0.9955 T0.0061,1 Q0,0.9984,0.0068,0.9845 Q0.009,0.9804,0.0123,0.9755 Q0.0187,0.9666,0.0261,0.9606 T0.0374,0.9555 Q0.0481,0.958,0.0629,0.949 T0.0931,0.9262 T0.1247,0.8987 T0.1597,0.8777 T0.1949,0.8752 Q0.213,0.8805,0.2343,0.8772 Q0.2249,0.8736,0.2172,0.8683 Q0.1914,0.8511,0.1738,0.8497 T0.1349,0.8617 Q0.1304,0.865,0.1273,0.8646 T0.1242,0.8605 L0.1239,0.8536 Q0.1226,0.8295,0.1354,0.8016 T0.1723,0.7496 Q0.1914,0.7308,0.2104,0.7219 Q0.2259,0.7145,0.2425,0.7141 T0.273,0.7182 T0.3022,0.7296 T0.3301,0.7439 T0.3569,0.7571 T0.3821,0.7643 Q0.373,0.7561,0.3598,0.7367 T0.3382,0.7088 L0.3327,0.7035 Q0.3282,0.699,0.3353,0.6884 T0.3517,0.6692 L0.3611,0.6607 Q0.3711,0.657,0.3751,0.6554 T0.3837,0.6507 T0.3898,0.6458 T0.3979,0.637 T0.4111,0.6236 Q0.4172,0.6175,0.4234,0.6138 Q0.4327,0.6081,0.4439,0.6077 T0.4655,0.6105 T0.4884,0.6201 T0.5113,0.633 T0.5347,0.6468 T0.5573,0.6578 Q0.5705,0.6635,0.5833,0.665 T0.6112,0.665 T0.6286,0.6635 Q0.6421,0.6631,0.6718,0.6501 Q0.626,0.6578,0.6089,0.6517 Q0.6063,0.6509,0.6002,0.6491 T0.5884,0.6454 T0.5757,0.6409 T0.561,0.6344 T0.5466,0.6256 Q0.536,0.6183,0.5108,0.5989 T0.4722,0.5712 T0.4527,0.564 L0.4418,0.5661 Q0.4308,0.5681,0.4366,0.5563 Q0.4401,0.5485,0.4476,0.5392 L0.455,0.5298 Q0.4631,0.5188,0.4714,0.51 T0.4866,0.4947 T0.5021,0.4831 T0.516,0.4749 T0.53,0.4694 T0.5428,0.4657 T0.5558,0.4637 T0.5674,0.4629 T0.5795,0.4625 Q0.5489,0.4543,0.534,0.4384 L0.5286,0.4327 Q0.5244,0.4282,0.5318,0.4174 T0.5486,0.398 L0.5579,0.3899 Q0.567,0.387,0.5749,0.3834 T0.5878,0.3772 T0.6012,0.3687 T0.6126,0.3603 T0.6268,0.3487 T0.6421,0.3361 Q0.6567,0.3242,0.6712,0.3159 T0.7009,0.3034 T0.7277,0.2973 T0.7556,0.2961 T0.7809,0.2977 T0.8074,0.301 Q0.8096,0.3014,0.8109,0.3016 T0.8143,0.302 T0.818,0.3026 Q0.8012,0.2936,0.7767,0.2781 Q0.6718,0.2117,0.5744,0.2476 Q0.5637,0.2516,0.5492,0.2604 T0.5218,0.2779 T0.4923,0.2965 T0.461,0.3124 T0.4282,0.3216 T0.394,0.3206 Q0.3637,0.3136,0.3474,0.2936 T0.3353,0.2459 Q0.3385,0.2247,0.355,0.2023 Q0.3646,0.1892,0.3763,0.179 Q0.3898,0.1672,0.4063,0.1595 Q0.3927,0.1562,0.3785,0.1485 Q0.3604,0.1387,0.3453,0.1354 T0.3161,0.1354 T0.2899,0.1458 T0.2627,0.166 Q0.262,0.1664,0.254,0.1733 T0.2414,0.1841 T0.2267,0.196 T0.2094,0.2086 T0.1915,0.2196 T0.1725,0.2288 T0.1546,0.2337 T0.1373,0.2343 T0.1223,0.228 T0.1097,0.2145 Q0.0962,0.1929,0.1258,0.1582 Q0.131,0.1517,0.1371,0.1464 Q0.1462,0.1387,0.156,0.1321 T0.1752,0.1205 T0.1878,0.1134 Q0.1891,0.1117,0.1947,0.103 T0.2035,0.0899 T0.2125,0.0781 T0.2243,0.064 Q0.1949,0.0616,0.1672,0.0828 Q0.153,0.0934,0.1412,0.1009 T0.1146,0.1146 T0.0873,0.1213 T0.0616,0.116 T0.0374,0.0962 Q0.0339,0.0909,0.0295,0.0846 T0.024,0.0767 T0.0202,0.0728 T0.0148,0.0704 T0.0068,0.0697 Q0.0003,0.0693,0.0058,0.0567 Q0.0077,0.0522,0.011,0.0469 Z",type:0}},"\uE0C2":{type:5,data:{d:"M0.1934,0.6075 Q0.2012,0.612,0.2188,0.6218 T0.2459,0.6369 T0.2718,0.6508 T0.2983,0.6644 T0.322,0.6758 T0.3447,0.6854 T0.363,0.6911 T0.3783,0.6936 Q0.3796,0.6936,0.396,0.6952 T0.4265,0.6952 T0.4548,0.6879 Q0.4656,0.6834,0.4805,0.6756 T0.5073,0.6614 T0.5327,0.6485 T0.5564,0.6389 T0.5759,0.6363 T0.5909,0.6422 Q0.5953,0.6467,0.604,0.6554 T0.6155,0.6669 T0.6251,0.6746 T0.6387,0.683 T0.6556,0.6903 L0.6498,0.6956 Q0.6414,0.7042,0.6319,0.7177 T0.6162,0.7403 T0.5979,0.7583 T0.5703,0.7703 Q0.5313,0.7785,0.4799,0.7605 Q0.5097,0.7817,0.5508,0.7911 T0.6302,0.8001 Q0.6431,0.7997,0.659,0.7942 T0.6888,0.7813 T0.7194,0.766 T0.7506,0.753 T0.7819,0.7472 T0.8131,0.754 Q0.8239,0.7589,0.8346,0.7681 T0.8544,0.7895 T0.8689,0.818 T0.8737,0.8507 L0.8733,0.858 Q0.8591,0.849,0.8474,0.845 T0.8242,0.8421 T0.8021,0.8482 T0.7772,0.8625 Q0.7653,0.8707,0.7513,0.8752 T0.7184,0.8831 T0.6908,0.889 Q0.7017,0.8915,0.7152,0.8956 T0.7396,0.9037 T0.7633,0.9115 T0.7872,0.9174 T0.8107,0.9194 T0.8347,0.9155 Q0.8493,0.9115,0.8671,0.9196 T0.9015,0.9421 T0.9345,0.9708 T0.9682,0.9943 T0.9997,1 Q0.999,0.94,0.9983,0.6508 T0.9976,0.2322 Q0.9976,0.2109,0.9992,0.1379 T1,0.0249 Q0.9949,0.0249,0.9917,0.0257 T0.9863,0.0273 T0.9817,0.0308 T0.9776,0.0357 T0.9721,0.0437 T0.9651,0.0539 Q0.956,0.0657,0.9456,0.072 T0.9247,0.0789 T0.904,0.0775 T0.8823,0.0694 T0.862,0.0573 T0.8422,0.0432 Q0.7856,0,0.7328,0.0318 Q0.7392,0.0318,0.7452,0.0324 T0.756,0.0345 T0.7657,0.0386 T0.7741,0.0437 T0.7821,0.0506 T0.7894,0.0588 T0.7966,0.0683 T0.8039,0.0787 T0.8119,0.0904 T0.8202,0.1028 Q0.8249,0.1093,0.849,0.1234 T0.8811,0.1448 Q0.8998,0.1612,0.8893,0.1783 Q0.8835,0.1877,0.8759,0.1928 T0.8593,0.1979 T0.8417,0.1967 T0.8222,0.1895 T0.8034,0.1791 T0.7848,0.1663 T0.7687,0.1534 T0.7543,0.141 T0.744,0.1322 Q0.7186,0.1106,0.6984,0.101 T0.6559,0.0926 T0.6082,0.1077 Q0.5899,0.1175,0.5733,0.1206 T0.5411,0.1202 T0.5122,0.1098 T0.4822,0.0914 Q0.4927,0.1032,0.5036,0.1122 T0.5276,0.1285 T0.5503,0.1401 T0.5777,0.1518 T0.6055,0.1636 Q0.6258,0.1726,0.6375,0.1863 T0.6519,0.2162 Q0.6556,0.2403,0.6392,0.2605 T0.5926,0.2876 Q0.5804,0.2909,0.5672,0.2899 T0.5428,0.2856 T0.5193,0.2764 T0.4966,0.264 T0.4744,0.2497 T0.4523,0.2352 T0.4301,0.2222 T0.4077,0.2118 Q0.3021,0.173,0.1903,0.2436 Q0.1669,0.2583,0.1504,0.2674 T0.1129,0.2846 T0.075,0.294 T0.0384,0.2911 T0,0.2746 Q0.021,0.297,0.0459,0.3082 T0.0953,0.3207 T0.146,0.3193 T0.1988,0.3131 T0.2508,0.3095 T0.3029,0.3176 T0.3529,0.3448 Q0.3566,0.348,0.3652,0.355 T0.3771,0.3645 T0.3877,0.3731 T0.3993,0.3815 T0.4103,0.3884 T0.4226,0.3953 T0.4352,0.4009 T0.4497,0.4064 L0.4443,0.4121 Q0.4372,0.4198,0.4277,0.4251 T0.4072,0.4337 T0.3867,0.439 T0.3656,0.4431 T0.3481,0.4468 Q0.3241,0.4529,0.2943,0.4463 Q0.3227,0.4623,0.3458,0.471 T0.3959,0.4814 Q0.404,0.4818,0.4187,0.4825 T0.4409,0.4835 T0.4605,0.4857 T0.4809,0.4908 T0.4992,0.4996 T0.5186,0.5141 T0.5371,0.5353 L0.5449,0.5451 L0.5337,0.543 Q0.5242,0.541,0.5091,0.55 T0.4683,0.5796 T0.4314,0.6083 Q0.4203,0.6161,0.4086,0.6212 T0.382,0.6308 T0.3667,0.6353 Q0.3485,0.6418,0.2995,0.633 T0.1934,0.6075 Z",type:0}},"\uE0C3":{type:5,data:{d:"M0.989,0.0469 Q0.9829,0.0379,0.9753,0.0316 T0.9635,0.0253 Q0.9574,0.0257,0.9539,0.0263 T0.9466,0.03 T0.9418,0.0345 T0.9355,0.0434 T0.9277,0.0546 Q0.9193,0.0653,0.9103,0.0712 T0.8919,0.0775 T0.8745,0.0765 T0.8564,0.0695 T0.8396,0.0593 T0.8228,0.0473 Q0.7612,0,0.7073,0.0326 L0.7386,0.0767 L0.7419,0.0767 Q0.7506,0.0767,0.7556,0.0773 T0.7641,0.0789 T0.7709,0.0842 T0.7767,0.0922 T0.7854,0.1054 T0.7974,0.1236 Q0.8057,0.1354,0.8157,0.1444 T0.8325,0.1574 T0.8474,0.1654 T0.8571,0.1701 Q0.8564,0.1717,0.8554,0.1733 Q0.8503,0.1815,0.844,0.186 T0.8301,0.1909 T0.8153,0.1903 T0.7993,0.1845 T0.7836,0.1758 T0.768,0.1648 T0.754,0.1535 T0.741,0.1425 T0.7312,0.1338 Q0.7031,0.1101,0.6805,0.0995 T0.6357,0.0901 T0.5886,0.1048 Q0.5721,0.1138,0.5574,0.1168 T0.5295,0.117 T0.5055,0.1089 T0.4814,0.0942 L0.4789,0.0926 L0.4711,0.0922 Q0.4589,0.095,0.4811,0.1203 Q0.495,0.1358,0.5087,0.1474 T0.5366,0.167 T0.5611,0.1796 T0.5879,0.1909 T0.6134,0.2011 Q0.6218,0.2047,0.6273,0.2092 Q0.6283,0.2117,0.6286,0.2141 Q0.6305,0.2263,0.6249,0.2388 T0.606,0.2616 T0.5747,0.2761 Q0.5602,0.2785,0.5487,0.2779 T0.5245,0.2724 T0.5015,0.2616 T0.4784,0.2476 T0.4547,0.2321 T0.4297,0.217 T0.4024,0.2043 Q0.2985,0.166,0.1901,0.2345 Q0.1694,0.248,0.1523,0.2569 T0.1165,0.2726 T0.0818,0.2812 T0.0494,0.2796 T0.0181,0.2663 L0.0116,0.2671 Q0,0.272,0.0232,0.2969 Q0.0468,0.3222,0.0742,0.3365 T0.1278,0.3542 T0.1807,0.3564 T0.233,0.352 T0.2815,0.3473 T0.3269,0.3509 T0.3656,0.3699 Q0.4037,0.4005,0.4153,0.4082 Q0.405,0.4139,0.3921,0.4174 T0.3633,0.4237 T0.3414,0.4278 Q0.3207,0.4331,0.2956,0.4278 Q0.2943,0.4278,0.293,0.4274 Q0.2933,0.429,0.2938,0.4313 T0.2964,0.4398 T0.3007,0.4513 T0.3069,0.4619 T0.3149,0.4698 Q0.3427,0.4857,0.3664,0.4947 T0.4169,0.5053 Q0.423,0.5057,0.4385,0.5065 T0.4606,0.508 T0.4779,0.51 T0.4958,0.5139 T0.5095,0.5204 Q0.4992,0.5232,0.485,0.5328 T0.451,0.5585 T0.4201,0.5824 Q0.4095,0.5897,0.3987,0.5946 T0.3738,0.6036 T0.3591,0.6081 Q0.3433,0.6134,0.2965,0.605 T0.1972,0.5816 L0.2026,0.5922 T0.2133,0.6134 L0.2188,0.624 Q0.2201,0.6248,0.2214,0.6252 Q0.2291,0.6297,0.2486,0.6405 T0.2786,0.657 T0.3066,0.6721 T0.3353,0.6866 T0.3598,0.6972 T0.383,0.7051 T0.4005,0.7076 Q0.4008,0.7076,0.4169,0.7092 T0.4471,0.7092 T0.475,0.7019 Q0.4895,0.6958,0.5176,0.6811 T0.5644,0.6586 T0.5921,0.6533 Q0.6128,0.6741,0.6173,0.6782 Q0.6131,0.6835,0.6068,0.6929 T0.5963,0.7078 T0.5858,0.7198 T0.5716,0.7304 T0.5534,0.7365 Q0.5179,0.7443,0.4708,0.728 L0.4708,0.7294 T0.471,0.7333 T0.4716,0.7392 T0.4734,0.7463 T0.4764,0.7539 T0.4814,0.7612 T0.4889,0.7679 Q0.4914,0.77,0.4944,0.7716 Q0.5534,0.8104,0.6409,0.8091 Q0.6521,0.8091,0.6654,0.8053 T0.6907,0.7959 T0.7157,0.7838 T0.741,0.772 T0.7652,0.7631 T0.7888,0.7594 T0.8106,0.7643 Q0.8196,0.7684,0.8267,0.7753 Q0.8409,0.7892,0.8399,0.8169 Q0.8216,0.8067,0.8062,0.8053 T0.7788,0.8087 T0.7493,0.825 Q0.7386,0.8324,0.7256,0.8365 T0.6947,0.844 T0.668,0.8499 L0.697,0.894 L0.7002,0.8948 Q0.7099,0.8968,0.7222,0.9009 T0.7446,0.9086 T0.767,0.9158 T0.7901,0.9213 T0.8132,0.9229 T0.8367,0.9192 Q0.8483,0.916,0.8633,0.9241 T0.8937,0.9462 T0.9248,0.9737 T0.959,0.9955 T0.9939,1 Q1,0.9984,0.9932,0.9845 Q0.991,0.9804,0.9877,0.9755 Q0.9813,0.9666,0.9739,0.9606 T0.9626,0.9555 Q0.9519,0.958,0.9372,0.949 T0.9071,0.9262 T0.8753,0.8987 T0.8403,0.8777 T0.8051,0.8752 Q0.787,0.8805,0.7657,0.8772 Q0.7751,0.8736,0.7828,0.8683 Q0.8086,0.8511,0.8262,0.8497 T0.8651,0.8617 Q0.8696,0.865,0.8727,0.8646 T0.8758,0.8605 L0.8761,0.8536 Q0.8774,0.8295,0.8646,0.8016 T0.8277,0.7496 Q0.8086,0.7308,0.7896,0.7219 Q0.7741,0.7145,0.7575,0.7141 T0.727,0.7182 T0.6978,0.7296 T0.6699,0.7439 T0.6433,0.7571 T0.6179,0.7643 Q0.627,0.7561,0.6402,0.7367 T0.6618,0.7088 L0.6673,0.7035 Q0.6718,0.699,0.6647,0.6884 T0.6483,0.6692 L0.6389,0.6607 Q0.6289,0.657,0.6249,0.6554 T0.6163,0.6507 T0.6102,0.6458 T0.6021,0.637 T0.5889,0.6236 Q0.5828,0.6175,0.5766,0.6138 Q0.5673,0.6081,0.5561,0.6077 T0.5345,0.6105 T0.5116,0.6201 T0.4887,0.633 T0.4653,0.6468 T0.4427,0.6578 Q0.4295,0.6635,0.4167,0.665 T0.3888,0.665 T0.3714,0.6635 Q0.3579,0.6631,0.3282,0.6501 Q0.374,0.6578,0.3911,0.6517 Q0.3937,0.6509,0.4024,0.6482 T0.4179,0.6434 T0.4348,0.6364 T0.4534,0.6256 Q0.464,0.6183,0.4892,0.5989 T0.5278,0.5712 T0.5476,0.564 L0.5582,0.5661 Q0.5692,0.5681,0.5634,0.5563 Q0.5599,0.5485,0.5524,0.5392 L0.545,0.5298 Q0.5369,0.5188,0.5286,0.51 T0.5134,0.4947 T0.4979,0.4831 T0.484,0.4749 T0.47,0.4694 T0.4572,0.4657 T0.4442,0.4637 T0.4326,0.4629 T0.4205,0.4625 Q0.4511,0.4543,0.466,0.4384 L0.4714,0.4327 Q0.4795,0.4237,0.4469,0.394 L0.4421,0.3899 Q0.433,0.387,0.4251,0.3834 T0.4122,0.3772 T0.3988,0.3687 T0.3874,0.3603 T0.3732,0.3487 T0.3579,0.3361 Q0.3433,0.3242,0.3288,0.3159 T0.2993,0.3034 T0.2725,0.2973 T0.2446,0.2961 T0.2193,0.2977 T0.1926,0.301 Q0.1855,0.3022,0.182,0.3026 Q0.1991,0.2936,0.2233,0.2781 Q0.3282,0.2117,0.4256,0.2476 Q0.4363,0.2516,0.451,0.2604 T0.4784,0.2779 T0.5077,0.2965 T0.539,0.3124 T0.5718,0.3216 T0.606,0.3206 Q0.6363,0.3136,0.6526,0.2936 T0.6647,0.2459 Q0.6615,0.2247,0.645,0.2023 Q0.6354,0.1892,0.6237,0.179 Q0.6102,0.1672,0.5937,0.1595 Q0.6073,0.1562,0.6215,0.1485 Q0.6396,0.1387,0.6547,0.1354 T0.6839,0.1354 T0.7101,0.1458 T0.7373,0.166 Q0.738,0.1664,0.746,0.1733 T0.7586,0.1841 T0.7733,0.196 T0.7907,0.2086 T0.8086,0.2196 T0.8275,0.2288 T0.8454,0.2337 T0.8627,0.2343 T0.8777,0.228 T0.8903,0.2145 Q0.9038,0.1929,0.8742,0.1582 Q0.869,0.1517,0.8629,0.1464 Q0.8538,0.1387,0.844,0.1321 T0.8248,0.1205 T0.8122,0.1134 Q0.8109,0.1117,0.8053,0.103 T0.7965,0.0899 T0.7875,0.0781 T0.7757,0.064 Q0.8051,0.0616,0.8328,0.0828 Q0.847,0.0934,0.8588,0.1009 T0.8854,0.1146 T0.9127,0.1213 T0.9384,0.116 T0.9626,0.0962 Q0.9664,0.0909,0.9706,0.0846 T0.976,0.0767 T0.9798,0.0728 T0.9852,0.0704 T0.9932,0.0697 Q0.9997,0.0693,0.9942,0.0567 Q0.9923,0.0522,0.989,0.0469 Z",type:0}},"\uE0C4":{type:5,data:{d:"M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0.9099,0.1158 L0.9099,0.1888 L0.9951,0.1888 L0.9951,0.1158 L0.9099,0.1158 Z M0.9075,0.1116 L0.9976,0.1116 Q1,0.1116,1,0.1137 L1,0.1909 Q1,0.193,0.9976,0.193 L0.9075,0.193 Q0.905,0.193,0.905,0.1909 L0.905,0.1137 Q0.905,0.1116,0.9075,0.1116 Z M0.7852,0.6086 L0.7852,0.682 L0.8704,0.682 L0.8704,0.6086 L0.7852,0.6086 Z M0.7828,0.6044 L0.8729,0.6044 Q0.8753,0.6044,0.8753,0.6065 L0.8753,0.6841 Q0.8753,0.6862,0.8729,0.6862 L0.7828,0.6862 Q0.7803,0.6862,0.7803,0.6841 L0.7803,0.6065 Q0.7803,0.6044,0.7828,0.6044 Z M0.66,0.1137 L0.7501,0.1137 L0.7501,0.1909 L0.66,0.1909 L0.66,0.1137 Z M0.6624,0.4102 L0.6624,0.4836 L0.7477,0.4836 L0.7477,0.4102 L0.6624,0.4102 Z M0.66,0.406 L0.7501,0.406 Q0.7526,0.406,0.7526,0.4081 L0.7526,0.4857 Q0.7526,0.4878,0.7501,0.4878 L0.66,0.4878 Q0.6576,0.4878,0.6576,0.4857 L0.6576,0.4081 Q0.6576,0.406,0.66,0.406 Z M0.6624,0.7131 L0.6624,0.7865 L0.7477,0.7865 L0.7477,0.7131 L0.6624,0.7131 Z M0.66,0.7089 L0.7501,0.7089 Q0.7526,0.7089,0.7526,0.711 L0.7526,0.7886 Q0.7526,0.7907,0.7501,0.7907 L0.66,0.7907 Q0.6576,0.7907,0.6576,0.7886 L0.6576,0.711 Q0.6576,0.7089,0.66,0.7089 Z M0.5407,0.2227 L0.5407,0.2836 L0.6113,0.2836 L0.6113,0.2227 L0.5407,0.2227 Z M0.5309,0.206 L0.621,0.206 Q0.623,0.206,0.6249,0.2068 T0.6281,0.2089 T0.6301,0.2116 T0.6308,0.2143 L0.6308,0.2919 Q0.6308,0.2957,0.6276,0.298 T0.621,0.3003 L0.5309,0.3003 Q0.5265,0.3003,0.5239,0.2976 T0.5212,0.2919 L0.5212,0.2143 Q0.5212,0.211,0.5244,0.2085 T0.5309,0.206 Z M0.5407,0.3184 L0.5407,0.3792 L0.6113,0.3792 L0.6113,0.3184 L0.5407,0.3184 Z M0.5309,0.3016 L0.621,0.3016 Q0.6254,0.3016,0.6281,0.3043 T0.6308,0.31 L0.6308,0.3876 Q0.6308,0.3914,0.6276,0.3937 T0.621,0.396 L0.5309,0.396 Q0.5265,0.396,0.5239,0.3932 T0.5212,0.3876 L0.5212,0.31 Q0.5212,0.3062,0.5244,0.3039 T0.5309,0.3016 Z M0.5309,0.4081 L0.621,0.4081 L0.621,0.4857 L0.5309,0.4857 L0.5309,0.4081 Z M0.5309,0.5059 L0.621,0.5059 L0.621,0.5835 L0.5309,0.5835 L0.5309,0.5059 Z M0.5407,0.6149 L0.5407,0.6758 L0.6113,0.6758 L0.6113,0.6149 L0.5407,0.6149 Z M0.5309,0.5982 L0.621,0.5982 Q0.6254,0.5982,0.6281,0.6009 T0.6308,0.6065 L0.6308,0.6841 Q0.6308,0.6879,0.6276,0.6902 T0.621,0.6925 L0.5309,0.6925 Q0.5265,0.6925,0.5239,0.6898 T0.5212,0.6841 L0.5212,0.6065 Q0.5212,0.6028,0.5244,0.6005 T0.5309,0.5982 Z M0.4062,0.1137 L0.4963,0.1137 L0.4963,0.1909 L0.4062,0.1909 L0.4062,0.1137 Z M0.4062,0.2143 L0.4963,0.2143 L0.4963,0.2919 L0.4062,0.2919 L0.4062,0.2143 Z M0.4184,0.4186 L0.4184,0.4753 L0.4842,0.4753 L0.4842,0.4186 L0.4184,0.4186 Z M0.4062,0.3977 L0.4963,0.3977 Q0.5017,0.3977,0.5051,0.401 T0.5085,0.4081 L0.5085,0.4857 Q0.5085,0.4904,0.5046,0.4933 T0.4963,0.4962 L0.4062,0.4962 Q0.4009,0.4962,0.3975,0.4929 T0.3941,0.4857 L0.3941,0.4081 Q0.3941,0.4035,0.398,0.4006 T0.4062,0.3977 Z M0.4062,0.5059 L0.4963,0.5059 L0.4963,0.5835 L0.4062,0.5835 L0.4062,0.5059 Z M0.4087,0.7131 L0.4087,0.7865 L0.4939,0.7865 L0.4939,0.7131 L0.4087,0.7131 Z M0.4062,0.7089 L0.4963,0.7089 Q0.4988,0.7089,0.4988,0.711 L0.4988,0.7886 Q0.4988,0.7907,0.4963,0.7907 L0.4062,0.7907 Q0.4038,0.7907,0.4038,0.7886 L0.4038,0.711 Q0.4038,0.7089,0.4062,0.7089 Z M0.2689,0.1137 L0.359,0.1137 L0.359,0.1909 L0.2689,0.1909 L0.2689,0.1137 Z M0.2689,0.2143 L0.359,0.2143 L0.359,0.2919 L0.2689,0.2919 L0.2689,0.2143 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.31 L0.359,0.31 L0.359,0.3876 L0.2689,0.3876 L0.2689,0.31 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.4081 L0.359,0.4081 L0.359,0.4857 L0.2689,0.4857 L0.2689,0.4081 Z M0.2689,0.5059 L0.359,0.5059 L0.359,0.5835 L0.2689,0.5835 L0.2689,0.5059 Z M0.2689,0.711 L0.359,0.711 L0.359,0.7886 L0.2689,0.7886 L0.2689,0.711 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.1374,0.1137 L0.2275,0.1137 L0.2275,0.1909 L0.1374,0.1909 L0.1374,0.1137 Z M0.1374,0.2143 L0.2275,0.2143 L0.2275,0.2919 L0.1374,0.2919 L0.1374,0.2143 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.31 L0.2275,0.31 L0.2275,0.3876 L0.1374,0.3876 L0.1374,0.31 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.4081 L0.2275,0.4081 L0.2275,0.4857 L0.1374,0.4857 L0.1374,0.4081 Z M0.1374,0.5059 L0.2275,0.5059 L0.2275,0.5835 L0.1374,0.5835 L0.1374,0.5059 Z M0.1374,0.6065 L0.2275,0.6065 L0.2275,0.6841 L0.1374,0.6841 L0.1374,0.6065 Z M0.1374,0.711 L0.2275,0.711 L0.2275,0.7886 L0.1374,0.7886 L0.1374,0.711 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0,0.1137 L0.0901,0.1137 L0.0901,0.1909 L0,0.1909 L0,0.1137 Z M0,0.2143 L0.0901,0.2143 L0.0901,0.2919 L0,0.2919 L0,0.2143 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.31 L0.0901,0.31 L0.0901,0.3876 L0,0.3876 L0,0.31 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.4081 L0.0901,0.4081 L0.0901,0.4857 L0,0.4857 L0,0.4081 Z M0,0.5059 L0.0901,0.5059 L0.0901,0.5835 L0,0.5835 L0,0.5059 Z M0,0.6065 L0.0901,0.6065 L0.0901,0.6841 L0,0.6841 L0,0.6065 Z M0,0.711 L0.0901,0.711 L0.0901,0.7886 L0,0.7886 L0,0.711 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z",type:0}},"\uE0C5":{type:5,data:{d:"M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M0.0901,0.1158 L0.0901,0.1888 L0.0049,0.1888 L0.0049,0.1158 L0.0901,0.1158 Z M0.0925,0.1116 L0.0024,0.1116 Q0,0.1116,0,0.1137 L0,0.1909 Q0,0.193,0.0024,0.193 L0.0925,0.193 Q0.095,0.193,0.095,0.1909 L0.095,0.1137 Q0.095,0.1116,0.0925,0.1116 Z M0.2148,0.6086 L0.2148,0.682 L0.1296,0.682 L0.1296,0.6086 L0.2148,0.6086 Z M0.2172,0.6044 L0.1271,0.6044 Q0.1247,0.6044,0.1247,0.6065 L0.1247,0.6841 Q0.1247,0.6862,0.1271,0.6862 L0.2172,0.6862 Q0.2197,0.6862,0.2197,0.6841 L0.2197,0.6065 Q0.2197,0.6044,0.2172,0.6044 Z M0.34,0.1137 L0.2499,0.1137 L0.2499,0.1909 L0.34,0.1909 L0.34,0.1137 Z M0.3376,0.4102 L0.3376,0.4836 L0.2523,0.4836 L0.2523,0.4102 L0.3376,0.4102 Z M0.34,0.406 L0.2499,0.406 Q0.2474,0.406,0.2474,0.4081 L0.2474,0.4857 Q0.2474,0.4878,0.2499,0.4878 L0.34,0.4878 Q0.3424,0.4878,0.3424,0.4857 L0.3424,0.4081 Q0.3424,0.406,0.34,0.406 Z M0.3376,0.7131 L0.3376,0.7865 L0.2523,0.7865 L0.2523,0.7131 L0.3376,0.7131 Z M0.34,0.7089 L0.2499,0.7089 Q0.2474,0.7089,0.2474,0.711 L0.2474,0.7886 Q0.2474,0.7907,0.2499,0.7907 L0.34,0.7907 Q0.3424,0.7907,0.3424,0.7886 L0.3424,0.711 Q0.3424,0.7089,0.34,0.7089 Z M0.4593,0.2227 L0.4593,0.2836 L0.3887,0.2836 L0.3887,0.2227 L0.4593,0.2227 Z M0.4691,0.206 L0.379,0.206 Q0.3746,0.206,0.3719,0.2087 T0.3692,0.2143 L0.3692,0.2919 Q0.3692,0.2957,0.3724,0.298 T0.379,0.3003 L0.4691,0.3003 Q0.4735,0.3003,0.4761,0.2976 T0.4788,0.2919 L0.4788,0.2143 Q0.4788,0.211,0.4756,0.2085 T0.4691,0.206 Z M0.4593,0.3184 L0.4593,0.3792 L0.3887,0.3792 L0.3887,0.3184 L0.4593,0.3184 Z M0.4691,0.3016 L0.379,0.3016 Q0.3746,0.3016,0.3719,0.3043 T0.3692,0.31 L0.3692,0.3876 Q0.3692,0.3914,0.3724,0.3937 T0.379,0.396 L0.4691,0.396 Q0.4735,0.396,0.4761,0.3932 T0.4788,0.3876 L0.4788,0.31 Q0.4788,0.3062,0.4756,0.3039 T0.4691,0.3016 Z M0.4691,0.4081 L0.379,0.4081 L0.379,0.4857 L0.4691,0.4857 L0.4691,0.4081 Z M0.4691,0.5059 L0.379,0.5059 L0.379,0.5835 L0.4691,0.5835 L0.4691,0.5059 Z M0.4593,0.6149 L0.4593,0.6758 L0.3887,0.6758 L0.3887,0.6149 L0.4593,0.6149 Z M0.4691,0.5982 L0.379,0.5982 Q0.3746,0.5982,0.3719,0.6009 T0.3692,0.6065 L0.3692,0.6841 Q0.3692,0.6879,0.3724,0.6902 T0.379,0.6925 L0.4691,0.6925 Q0.4735,0.6925,0.4761,0.6898 T0.4788,0.6841 L0.4788,0.6065 Q0.4788,0.6028,0.4756,0.6005 T0.4691,0.5982 Z M0.5938,0.1137 L0.5037,0.1137 L0.5037,0.1909 L0.5938,0.1909 L0.5938,0.1137 Z M0.5938,0.2143 L0.5037,0.2143 L0.5037,0.2919 L0.5938,0.2919 L0.5938,0.2143 Z M0.5816,0.4186 L0.5816,0.4753 L0.5158,0.4753 L0.5158,0.4186 L0.5816,0.4186 Z M0.5938,0.3977 L0.5037,0.3977 Q0.4983,0.3977,0.4949,0.401 T0.4915,0.4081 L0.4915,0.4857 Q0.4915,0.4904,0.4954,0.4933 T0.5037,0.4962 L0.5938,0.4962 Q0.5991,0.4962,0.6025,0.4929 T0.6059,0.4857 L0.6059,0.4081 Q0.6059,0.4035,0.602,0.4006 T0.5938,0.3977 Z M0.5938,0.5059 L0.5037,0.5059 L0.5037,0.5835 L0.5938,0.5835 L0.5938,0.5059 Z M0.5913,0.7131 L0.5913,0.7865 L0.5061,0.7865 L0.5061,0.7131 L0.5913,0.7131 Z M0.5938,0.7089 L0.5037,0.7089 Q0.5012,0.7089,0.5012,0.711 L0.5012,0.7886 Q0.5012,0.7907,0.5037,0.7907 L0.5938,0.7907 Q0.5962,0.7907,0.5962,0.7886 L0.5962,0.711 Q0.5962,0.7089,0.5938,0.7089 Z M0.7311,0.1137 L0.641,0.1137 L0.641,0.1909 L0.7311,0.1909 L0.7311,0.1137 Z M0.7311,0.2143 L0.641,0.2143 L0.641,0.2919 L0.7311,0.2919 L0.7311,0.2143 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.31 L0.641,0.31 L0.641,0.3876 L0.7311,0.3876 L0.7311,0.31 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.4081 L0.641,0.4081 L0.641,0.4857 L0.7311,0.4857 L0.7311,0.4081 Z M0.7311,0.5059 L0.641,0.5059 L0.641,0.5835 L0.7311,0.5835 L0.7311,0.5059 Z M0.7311,0.711 L0.641,0.711 L0.641,0.7886 L0.7311,0.7886 L0.7311,0.711 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.8626,0.1137 L0.7725,0.1137 L0.7725,0.1909 L0.8626,0.1909 L0.8626,0.1137 Z M0.8626,0.2143 L0.7725,0.2143 L0.7725,0.2919 L0.8626,0.2919 L0.8626,0.2143 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.31 L0.7725,0.31 L0.7725,0.3876 L0.8626,0.3876 L0.8626,0.31 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.4081 L0.7725,0.4081 L0.7725,0.4857 L0.8626,0.4857 L0.8626,0.4081 Z M0.8626,0.5059 L0.7725,0.5059 L0.7725,0.5835 L0.8626,0.5835 L0.8626,0.5059 Z M0.8626,0.6065 L0.7725,0.6065 L0.7725,0.6841 L0.8626,0.6841 L0.8626,0.6065 Z M0.8626,0.711 L0.7725,0.711 L0.7725,0.7886 L0.8626,0.7886 L0.8626,0.711 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M1,0.1137 L0.9099,0.1137 L0.9099,0.1909 L1,0.1909 L1,0.1137 Z M1,0.2143 L0.9099,0.2143 L0.9099,0.2919 L1,0.2919 L1,0.2143 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.31 L0.9099,0.31 L0.9099,0.3876 L1,0.3876 L1,0.31 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.4081 L0.9099,0.4081 L0.9099,0.4857 L1,0.4857 L1,0.4081 Z M1,0.5059 L0.9099,0.5059 L0.9099,0.5835 L1,0.5835 L1,0.5059 Z M1,0.6065 L0.9099,0.6065 L0.9099,0.6841 L1,0.6841 L1,0.6065 Z M1,0.711 L0.9099,0.711 L0.9099,0.7886 L1,0.7886 L1,0.711 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z",type:0}},"\uE0C6":{type:5,data:{d:"M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0.8574,0.3217 L0.8574,0.4098 L0.9691,0.4098 L0.9691,0.3217 L0.8574,0.3217 Z M0.8419,0.2978 L0.9846,0.2978 Q0.9892,0.2978,0.9928,0.2997 T0.9982,0.3043 T1,0.31 L1,0.4216 Q1,0.427,0.9951,0.4304 T0.9846,0.4337 L0.8419,0.4337 Q0.8352,0.4337,0.8308,0.4299 T0.8265,0.4216 L0.8265,0.31 Q0.8265,0.3062,0.829,0.3035 T0.835,0.2993 T0.8419,0.2978 Z M0.8574,0.888 L0.8574,0.9757 L0.9691,0.9757 L0.9691,0.888 L0.8574,0.888 Z M0.8419,0.8637 L0.9846,0.8637 Q0.9892,0.8637,0.9928,0.8658 T0.9982,0.8706 T1,0.8758 L1,0.9878 Q1,0.9929,0.9951,0.9964 T0.9846,1 L0.8419,1 Q0.8352,1,0.8308,0.996 T0.8265,0.9878 L0.8265,0.8758 Q0.8265,0.8708,0.8314,0.8672 T0.8419,0.8637 Z M0.6509,0.169 L0.6509,0.2706 L0.7806,0.2706 L0.7806,0.169 L0.6509,0.169 Z M0.6442,0.159 L0.7868,0.159 Q0.7899,0.159,0.7917,0.1604 T0.7935,0.164 L0.7935,0.2756 Q0.7935,0.2781,0.7915,0.2796 T0.7868,0.281 L0.6442,0.281 Q0.6416,0.281,0.6395,0.2794 T0.6375,0.2756 L0.6375,0.164 Q0.6375,0.1619,0.6398,0.1604 T0.6442,0.159 Z M0.6632,0.6044 L0.6632,0.6858 L0.7678,0.6858 L0.7678,0.6044 L0.6632,0.6044 Z M0.6442,0.5742 L0.7868,0.5742 Q0.7951,0.5742,0.8007,0.5791 T0.8064,0.5893 L0.8064,0.7013 Q0.8064,0.7076,0.8002,0.712 T0.7868,0.7164 L0.6442,0.7164 Q0.6359,0.7164,0.6305,0.7114 T0.6251,0.7013 L0.6251,0.5893 Q0.6251,0.5847,0.6282,0.5812 T0.6357,0.5759 T0.6442,0.5742 Z M0.4264,0.164 L0.5695,0.164 L0.5695,0.2756 L0.4264,0.2756 L0.4264,0.164 Z M0.4325,0.315 L0.4325,0.4165 L0.5628,0.4165 L0.5628,0.315 L0.4325,0.315 Z M0.4264,0.3049 L0.5695,0.3049 Q0.5721,0.3049,0.5739,0.3064 T0.5757,0.31 L0.5757,0.4216 Q0.5757,0.4237,0.5736,0.4251 T0.5695,0.4266 L0.4264,0.4266 Q0.4238,0.4266,0.422,0.4251 T0.4202,0.4216 L0.4202,0.31 Q0.4202,0.3087,0.4207,0.3077 T0.4222,0.306 T0.4243,0.3052 T0.4264,0.3049 Z M0.4264,0.4476 L0.5695,0.4476 L0.5695,0.5596 L0.4264,0.5596 L0.4264,0.4476 Z M0.4392,0.7408 L0.4392,0.8326 L0.5561,0.8326 L0.5561,0.7408 L0.4392,0.7408 Z M0.4264,0.7206 L0.5695,0.7206 Q0.5747,0.7206,0.5783,0.724 T0.5819,0.7307 L0.5819,0.8427 Q0.5819,0.8469,0.5778,0.8498 T0.5695,0.8528 L0.4264,0.8528 Q0.4207,0.8528,0.4171,0.8496 T0.4135,0.8427 L0.4135,0.7307 Q0.4135,0.7265,0.4176,0.7236 T0.4264,0.7206 Z M0.2178,0.164 L0.3605,0.164 L0.3605,0.2756 L0.2178,0.2756 L0.2178,0.164 Z M0.2178,0.31 L0.3605,0.31 L0.3605,0.4216 L0.2178,0.4216 L0.2178,0.31 Z M0.2178,0.5893 L0.3605,0.5893 L0.3605,0.7013 L0.2178,0.7013 L0.2178,0.5893 Z M0.2178,0.7307 L0.3605,0.7307 L0.3605,0.8427 L0.2178,0.8427 L0.2178,0.7307 Z M0.2178,0.8758 L0.3605,0.8758 L0.3605,0.9878 L0.2178,0.9878 L0.2178,0.8758 Z M0,0.164 L0.1432,0.164 L0.1432,0.2756 L0,0.2756 L0,0.164 Z M0,0.31 L0.1432,0.31 L0.1432,0.4216 L0,0.4216 L0,0.31 Z M0,0.4476 L0.1432,0.4476 L0.1432,0.5596 L0,0.5596 L0,0.4476 Z M0,0.5893 L0.1432,0.5893 L0.1432,0.7013 L0,0.7013 L0,0.5893 Z M0,0.7307 L0.1432,0.7307 L0.1432,0.8427 L0,0.8427 L0,0.7307 Z M0,0.8758 L0.1432,0.8758 L0.1432,0.9878 L0,0.9878 L0,0.8758 Z",type:0}},"\uE0C7":{type:5,data:{d:"M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M0.1432,0.3217 L0.1432,0.4098 L0.0309,0.4098 L0.0309,0.3217 L0.1432,0.3217 Z M0.1586,0.2978 L0.0154,0.2978 Q0.0088,0.2978,0.0044,0.3016 T0,0.31 L0,0.4216 Q0,0.427,0.0049,0.4304 T0.0154,0.4337 L0.1586,0.4337 Q0.1648,0.4337,0.1694,0.4299 T0.174,0.4216 L0.174,0.31 Q0.174,0.3062,0.1715,0.3035 T0.1653,0.2993 T0.1586,0.2978 Z M0.1432,0.888 L0.1432,0.9757 L0.0309,0.9757 L0.0309,0.888 L0.1432,0.888 Z M0.1586,0.8637 L0.0154,0.8637 Q0.0108,0.8637,0.0072,0.8658 T0.0018,0.8706 T0,0.8758 L0,0.9878 Q0,0.9929,0.0049,0.9964 T0.0154,1 L0.1586,1 Q0.1648,1,0.1694,0.996 T0.174,0.9878 L0.174,0.8758 Q0.174,0.8725,0.1715,0.8695 T0.1653,0.8651 T0.1586,0.8637 Z M0.3496,0.169 L0.3496,0.2706 L0.2194,0.2706 L0.2194,0.169 L0.3496,0.169 Z M0.3563,0.159 L0.2132,0.159 Q0.2106,0.159,0.2088,0.1604 T0.207,0.164 L0.207,0.2756 Q0.207,0.2781,0.2091,0.2796 T0.2132,0.281 L0.3563,0.281 Q0.3589,0.281,0.3607,0.2794 T0.3625,0.2756 L0.3625,0.164 Q0.3625,0.1619,0.3605,0.1604 T0.3563,0.159 Z M0.3368,0.6044 L0.3368,0.6858 L0.2327,0.6858 L0.2327,0.6044 L0.3368,0.6044 Z M0.3563,0.5742 L0.2132,0.5742 Q0.2049,0.5742,0.1993,0.5791 T0.1936,0.5893 L0.1936,0.7013 Q0.1936,0.7076,0.2001,0.712 T0.2132,0.7164 L0.3563,0.7164 Q0.3641,0.7164,0.3697,0.7114 T0.3754,0.7013 L0.3754,0.5893 Q0.3754,0.5847,0.3723,0.5812 T0.3648,0.5759 T0.3563,0.5742 Z M0.5736,0.164 L0.431,0.164 L0.431,0.2756 L0.5736,0.2756 L0.5736,0.164 Z M0.5675,0.315 L0.5675,0.4165 L0.4377,0.4165 L0.4377,0.315 L0.5675,0.315 Z M0.5736,0.3049 L0.431,0.3049 Q0.4284,0.3049,0.4264,0.3064 T0.4243,0.31 L0.4243,0.4216 Q0.4243,0.4237,0.4266,0.4251 T0.431,0.4266 L0.5736,0.4266 Q0.5767,0.4266,0.5785,0.4251 T0.5803,0.4216 L0.5803,0.31 Q0.5803,0.3087,0.5798,0.3077 T0.5783,0.306 T0.5762,0.3052 T0.5736,0.3049 Z M0.5736,0.4476 L0.431,0.4476 L0.431,0.5596 L0.5736,0.5596 L0.5736,0.4476 Z M0.5613,0.7408 L0.5613,0.8326 L0.4439,0.8326 L0.4439,0.7408 L0.5613,0.7408 Z M0.5736,0.7206 L0.431,0.7206 Q0.4253,0.7206,0.4217,0.724 T0.4181,0.7307 L0.4181,0.8427 Q0.4181,0.8469,0.4222,0.8498 T0.431,0.8528 L0.5736,0.8528 Q0.5778,0.8528,0.5808,0.8511 T0.5855,0.8471 T0.587,0.8427 L0.587,0.7307 Q0.587,0.7265,0.5826,0.7236 T0.5736,0.7206 Z M0.7827,0.164 L0.6395,0.164 L0.6395,0.2756 L0.7827,0.2756 L0.7827,0.164 Z M0.7827,0.31 L0.6395,0.31 L0.6395,0.4216 L0.7827,0.4216 L0.7827,0.31 Z M0.7827,0.5893 L0.6395,0.5893 L0.6395,0.7013 L0.7827,0.7013 L0.7827,0.5893 Z M0.7827,0.7307 L0.6395,0.7307 L0.6395,0.8427 L0.7827,0.8427 L0.7827,0.7307 Z M0.7827,0.8758 L0.6395,0.8758 L0.6395,0.9878 L0.7827,0.9878 L0.7827,0.8758 Z M1,0.164 L0.8574,0.164 L0.8574,0.2756 L1,0.2756 L1,0.164 Z M1,0.31 L0.8574,0.31 L0.8574,0.4216 L1,0.4216 L1,0.31 Z M1,0.4476 L0.8574,0.4476 L0.8574,0.5596 L1,0.5596 L1,0.4476 Z M1,0.5893 L0.8574,0.5893 L0.8574,0.7013 L1,0.7013 L1,0.5893 Z M1,0.7307 L0.8574,0.7307 L0.8574,0.8427 L1,0.8427 L1,0.7307 Z M1,0.8758 L0.8574,0.8758 L0.8574,0.9878 L1,0.9878 L1,0.8758 Z",type:0}},"\uE0C8":{type:5,data:{d:"M0.0008,0.9979 Q0.0008,1,0.0032,1 Q0.0482,1,0.0602,0.9975 Q0.0667,0.9963,0.0847,0.9917 T0.1084,0.9859 Q0.1124,0.9851,0.1221,0.9851 L0.1253,0.9851 L0.1285,0.9851 Q0.1518,0.9851,0.1655,0.9809 Q0.1695,0.9801,0.1731,0.9788 T0.1791,0.9766 T0.1839,0.9747 T0.1888,0.9732 L0.1936,0.972 T0.1988,0.9712 T0.2048,0.971 L0.2096,0.971 Q0.2201,0.9714,0.2233,0.9714 T0.2345,0.9693 Q0.2386,0.9685,0.2502,0.9674 T0.2715,0.966 L0.2811,0.9656 Q0.2916,0.9647,0.2916,0.9573 Q0.2916,0.954,0.2892,0.9515 T0.2819,0.9486 Q0.2755,0.9482,0.2683,0.9482 Q0.2643,0.9482,0.2582,0.9484 T0.2498,0.9486 Q0.2426,0.9486,0.2402,0.9473 Q0.2386,0.9469,0.2317,0.943 T0.2161,0.937 Q0.2104,0.9357,0.1992,0.933 T0.1831,0.9291 Q0.1807,0.9287,0.1783,0.9285 T0.1739,0.928 T0.1703,0.9276 T0.1675,0.9272 T0.1655,0.9266 T0.1639,0.9253 Q0.1598,0.9216,0.1462,0.9216 L0.143,0.9216 L0.1398,0.9216 L0.1365,0.9216 Q0.1197,0.9195,0.1197,0.9154 L0.1197,0.9154 Q0.1205,0.9121,0.1373,0.9088 T0.1703,0.9054 Q0.1791,0.9054,0.1863,0.904 T0.1984,0.9011 T0.2064,0.8992 Q0.2402,0.8967,0.2506,0.8934 Q0.2538,0.8926,0.2602,0.8899 T0.2707,0.8872 Q0.2715,0.8872,0.2731,0.8876 Q0.2795,0.8884,0.2884,0.8884 Q0.3068,0.8884,0.3092,0.8826 L0.3092,0.8797 Q0.3092,0.8772,0.3076,0.8747 T0.3044,0.8706 T0.3028,0.8681 Q0.3076,0.8656,0.3237,0.8656 Q0.3317,0.8656,0.3622,0.8623 Q0.3655,0.8623,0.3867,0.8617 T0.4217,0.8602 Q0.4241,0.8598,0.4289,0.8598 L0.4345,0.8598 L0.441,0.8598 Q0.4578,0.8598,0.4667,0.8586 Q0.4739,0.8573,0.4851,0.8567 T0.5004,0.8552 Q0.5012,0.8552,0.5108,0.855 T0.5269,0.853 T0.5333,0.8461 Q0.5333,0.8445,0.5325,0.8432 T0.5309,0.8409 T0.5289,0.8391 T0.5261,0.8378 L0.5229,0.837 T0.5185,0.8362 T0.5133,0.8355 T0.5076,0.8349 T0.502,0.8341 Q0.4932,0.8324,0.4859,0.8324 Q0.4827,0.8324,0.4787,0.8328 T0.4731,0.8333 Q0.4707,0.8333,0.4683,0.8324 Q0.4482,0.825,0.4394,0.825 Q0.4369,0.825,0.4345,0.8254 Q0.4225,0.827,0.4153,0.827 Q0.4088,0.827,0.4064,0.8258 Q0.3976,0.8204,0.3751,0.8204 Q0.3703,0.8204,0.3667,0.8214 T0.3622,0.8225 Q0.3598,0.8225,0.3558,0.82 Q0.351,0.8175,0.3402,0.8167 T0.3177,0.8156 T0.3028,0.8146 L0.2956,0.8121 T0.2871,0.8094 T0.2771,0.8073 T0.2635,0.8063 Q0.261,0.8063,0.2586,0.8067 Q0.2378,0.8071,0.2273,0.8071 Q0.2072,0.8071,0.2024,0.8051 Q0.1976,0.8034,0.1932,0.8026 T0.1867,0.8013 T0.1847,0.8001 L0.1847,0.7993 Q0.1855,0.7984,0.1855,0.7978 T0.1859,0.7966 T0.1867,0.7959 T0.188,0.7955 T0.1896,0.7951 T0.1916,0.7949 T0.1944,0.7945 T0.1984,0.7941 T0.2032,0.7939 Q0.212,0.7926,0.2325,0.7918 T0.2562,0.7905 Q0.2691,0.7893,0.2787,0.7847 Q0.2835,0.7827,0.2968,0.7791 T0.3149,0.7756 Q0.3189,0.7756,0.3305,0.7733 T0.3446,0.771 L0.3454,0.771 Q0.3462,0.771,0.3498,0.7721 T0.3574,0.774 T0.3655,0.7748 Q0.3703,0.7748,0.3743,0.7731 T0.3823,0.7708 T0.3916,0.77 T0.3992,0.7694 Q0.4008,0.7694,0.4076,0.7688 T0.4205,0.7675 L0.4305,0.7665 Q0.4345,0.7657,0.4402,0.7617 T0.4458,0.7545 Q0.4458,0.7503,0.4353,0.7487 Q0.4297,0.7478,0.4209,0.7462 T0.4044,0.7433 T0.3904,0.7416 L0.3847,0.7416 Q0.3807,0.7416,0.3747,0.7422 T0.3663,0.7428 T0.3606,0.7424 Q0.3582,0.742,0.3562,0.7418 T0.3522,0.7412 T0.3482,0.7406 T0.3446,0.7401 T0.341,0.7397 T0.3373,0.7393 T0.3341,0.7391 L0.3293,0.7391 Q0.3277,0.7395,0.3253,0.7395 Q0.3205,0.7395,0.3052,0.7387 T0.2835,0.7379 Q0.2755,0.7379,0.253,0.7325 Q0.2506,0.7316,0.2462,0.731 T0.2382,0.7296 T0.2305,0.7271 T0.2181,0.7234 T0.2048,0.7196 T0.2,0.7155 Q0.2,0.7142,0.2016,0.713 Q0.208,0.7059,0.2285,0.6978 T0.2618,0.6898 Q0.2739,0.6898,0.2831,0.6848 T0.2956,0.6794 Q0.3028,0.6786,0.3289,0.6765 T0.3566,0.674 Q0.3582,0.674,0.3855,0.6723 T0.4406,0.6688 T0.4755,0.6653 Q0.5004,0.6607,0.5124,0.6599 L0.5165,0.6599 Q0.5213,0.6599,0.5277,0.6605 T0.5373,0.6611 Q0.543,0.6611,0.5494,0.6599 Q0.592,0.6524,0.6,0.6524 Q0.6064,0.6524,0.61,0.6499 T0.6161,0.6474 Q0.6177,0.6474,0.6261,0.6495 T0.6418,0.6516 L0.6482,0.6516 Q0.653,0.6512,0.6651,0.6506 T0.6876,0.6493 T0.7084,0.6477 T0.7229,0.645 Q0.7269,0.6437,0.7333,0.6437 Q0.7373,0.6437,0.7458,0.6443 T0.7574,0.645 Q0.7614,0.645,0.7631,0.6445 T0.7655,0.6441 Q0.7671,0.6441,0.7723,0.6448 T0.7823,0.6454 Q0.788,0.6454,0.7952,0.6445 Q0.8233,0.6408,0.8723,0.6367 Q0.9012,0.6342,0.9092,0.6342 L0.9165,0.6342 L0.9205,0.6342 Q0.9277,0.6342,0.9382,0.6323 T0.9526,0.6304 Q0.9534,0.6304,0.9594,0.6307 T0.9695,0.6309 Q0.9976,0.6309,0.9992,0.6234 Q1,0.6201,1,0.618 Q1,0.6089,0.9775,0.608 Q0.9703,0.6076,0.9582,0.6076 Q0.9518,0.6076,0.9394,0.6078 T0.9213,0.608 Q0.9036,0.608,0.8972,0.6072 Q0.894,0.6068,0.8871,0.6058 T0.8751,0.6039 T0.8627,0.6024 T0.8506,0.6018 L0.8474,0.6018 Q0.841,0.6022,0.8281,0.6041 T0.8112,0.606 Q0.8096,0.606,0.808,0.6056 Q0.7783,0.6002,0.7647,0.6002 Q0.7598,0.6002,0.7454,0.6006 T0.7261,0.601 Q0.7221,0.601,0.7145,0.6022 T0.7036,0.6035 Q0.6996,0.6035,0.6964,0.6022 Q0.6723,0.5902,0.6442,0.5902 Q0.6426,0.5902,0.6386,0.5904 T0.6337,0.5906 Q0.6273,0.5906,0.6249,0.5894 L0.6177,0.5856 T0.6116,0.5838 T0.6016,0.5825 T0.5855,0.5823 Q0.5719,0.5823,0.5574,0.5798 T0.5373,0.5774 L0.5341,0.5774 Q0.5293,0.5778,0.5213,0.5786 T0.51,0.5794 Q0.5036,0.5794,0.4988,0.5769 Q0.4932,0.574,0.4771,0.574 Q0.4723,0.574,0.4631,0.5745 T0.4498,0.5749 Q0.4418,0.5749,0.4394,0.5736 Q0.4353,0.5724,0.4305,0.5715 T0.4225,0.5707 T0.4145,0.5705 T0.4056,0.5699 Q0.4008,0.5691,0.398,0.5689 T0.394,0.5689 T0.3908,0.5686 T0.3855,0.5674 Q0.3831,0.567,0.3811,0.5662 T0.3779,0.5645 T0.3743,0.563 T0.3695,0.5624 Q0.3655,0.5624,0.3594,0.5635 T0.3494,0.5645 T0.3406,0.5635 T0.3325,0.5624 Q0.3309,0.5624,0.3301,0.5628 L0.3141,0.5628 Q0.2635,0.5628,0.2506,0.5612 Q0.2474,0.5608,0.2434,0.5608 Q0.2402,0.5608,0.2349,0.561 T0.2273,0.5612 Q0.2209,0.5612,0.2177,0.5595 Q0.2145,0.557,0.2145,0.5558 Q0.2169,0.5537,0.2345,0.5521 Q0.2876,0.5475,0.3028,0.5425 Q0.3068,0.5413,0.3149,0.5396 T0.3309,0.5367 T0.3478,0.5338 T0.3606,0.5309 Q0.3631,0.5305,0.3755,0.5259 T0.3968,0.5214 Q0.3992,0.5214,0.4024,0.5218 T0.4096,0.5222 Q0.4161,0.5222,0.4213,0.5209 T0.4313,0.5185 T0.4402,0.5172 Q0.4418,0.5168,0.447,0.5168 L0.4558,0.5168 T0.4639,0.5162 T0.4707,0.5141 T0.4731,0.5102 Q0.4731,0.5089,0.4723,0.5081 Q0.4699,0.5019,0.4627,0.4994 T0.4369,0.4965 Q0.4273,0.4961,0.4193,0.4946 T0.4048,0.4915 T0.3952,0.4894 Q0.392,0.4886,0.388,0.4886 Q0.3847,0.4886,0.3775,0.4892 T0.3647,0.4898 Q0.3598,0.4898,0.355,0.4894 Q0.3165,0.4861,0.3141,0.4853 Q0.2779,0.4774,0.2715,0.4774 Q0.2699,0.4774,0.2683,0.4776 T0.2627,0.4778 Q0.2498,0.4778,0.2281,0.4762 Q0.2241,0.4749,0.2233,0.4737 Q0.2249,0.472,0.2321,0.4708 Q0.2402,0.4695,0.2554,0.4695 L0.2675,0.4695 L0.2795,0.4695 Q0.294,0.4695,0.2996,0.4679 Q0.3141,0.4629,0.3373,0.4616 Q0.3414,0.4612,0.3466,0.4606 T0.3582,0.4589 T0.3687,0.4562 T0.3727,0.4525 Q0.3727,0.4509,0.3703,0.4492 Q0.3639,0.4455,0.3474,0.4419 T0.3213,0.4384 L0.3157,0.4384 L0.31,0.4384 Q0.294,0.4384,0.2859,0.4363 Q0.2795,0.4347,0.2691,0.4347 L0.2618,0.4347 L0.2546,0.4347 Q0.2458,0.4347,0.2394,0.4338 Q0.2257,0.4318,0.2193,0.4303 T0.2092,0.4266 T0.2052,0.4224 T0.2024,0.4154 T0.196,0.4052 Q0.1912,0.3994,0.1867,0.3949 T0.1807,0.3884 T0.1791,0.3849 Q0.1791,0.3824,0.1839,0.3745 Q0.1871,0.3687,0.1944,0.3637 T0.2072,0.3561 T0.2221,0.3501 T0.2321,0.3463 Q0.2353,0.3455,0.241,0.3445 T0.2522,0.3428 T0.2639,0.3411 T0.2731,0.3384 T0.2956,0.3351 T0.3197,0.3326 Q0.3213,0.3322,0.3237,0.3322 T0.3325,0.3331 T0.3414,0.3339 Q0.3478,0.3339,0.3518,0.3314 Q0.3606,0.3264,0.3703,0.3264 Q0.3775,0.3264,0.3847,0.3293 Q0.3888,0.331,0.396,0.331 Q0.4024,0.331,0.4116,0.3295 T0.4265,0.3266 T0.4414,0.3227 T0.4514,0.3202 Q0.4578,0.3181,0.4839,0.3152 T0.5112,0.3123 T0.5177,0.3119 T0.5285,0.3111 T0.5398,0.3096 T0.549,0.3065 T0.5526,0.3015 Q0.5526,0.2999,0.5518,0.2982 Q0.547,0.2903,0.5386,0.2883 T0.5165,0.2862 Q0.5116,0.2862,0.5056,0.2866 T0.4972,0.287 Q0.4884,0.287,0.4787,0.2833 Q0.4594,0.2758,0.4265,0.2758 Q0.4169,0.2758,0.4129,0.2766 Q0.4112,0.2771,0.4104,0.2771 Q0.4088,0.2771,0.4056,0.2748 T0.3968,0.2721 Q0.3695,0.2696,0.359,0.2659 Q0.355,0.2646,0.351,0.2646 T0.3426,0.2661 T0.3365,0.2675 T0.3333,0.2659 Q0.3277,0.2605,0.3116,0.2553 T0.2859,0.2501 L0.2843,0.2501 L0.2819,0.2501 Q0.2779,0.2501,0.2723,0.2493 T0.2586,0.2472 T0.2474,0.2455 Q0.1799,0.236,0.1719,0.236 Q0.1663,0.236,0.1606,0.235 T0.1502,0.2316 T0.1454,0.2265 Q0.1454,0.2223,0.1558,0.2173 Q0.1679,0.2115,0.1859,0.2092 T0.2185,0.207 T0.2353,0.2057 Q0.2369,0.2049,0.2502,0.2041 T0.2799,0.2022 T0.302,0.2007 Q0.3084,0.1999,0.3245,0.1954 T0.347,0.1908 Q0.3502,0.1908,0.3534,0.1916 Q0.3663,0.197,0.388,0.197 Q0.404,0.197,0.4088,0.1929 Q0.4096,0.192,0.4108,0.192 T0.4177,0.1945 T0.4277,0.197 T0.4369,0.1949 Q0.4442,0.1908,0.4843,0.1875 Q0.4892,0.1871,0.4968,0.1839 T0.5084,0.1808 L0.5108,0.1808 Q0.5213,0.1825,0.5454,0.1825 Q0.5823,0.1825,0.5992,0.1779 Q0.6096,0.1746,0.6217,0.1746 Q0.6249,0.1746,0.6309,0.1748 T0.6394,0.175 Q0.6434,0.175,0.6466,0.1746 Q0.6474,0.1746,0.6594,0.1738 T0.6787,0.1713 T0.6892,0.1659 Q0.6924,0.1634,0.6924,0.1593 Q0.6924,0.1547,0.6888,0.151 T0.6795,0.1472 Q0.6779,0.1472,0.6763,0.1477 Q0.6699,0.1493,0.6667,0.1493 Q0.661,0.1493,0.6506,0.1456 Q0.6329,0.1394,0.6129,0.1394 Q0.6048,0.1394,0.5992,0.1377 T0.5888,0.136 T0.5803,0.1377 T0.5751,0.1394 Q0.5711,0.1394,0.5639,0.1369 Q0.5365,0.1273,0.5116,0.1273 Q0.4876,0.1273,0.4667,0.1253 Q0.4627,0.1248,0.4542,0.1211 T0.4337,0.1174 L0.4305,0.1174 Q0.4217,0.1178,0.4153,0.119 T0.408,0.1203 Q0.4056,0.1203,0.4,0.117 Q0.3976,0.1153,0.396,0.1136 T0.3932,0.1112 T0.39,0.1099 T0.3847,0.1095 Q0.3799,0.1095,0.3687,0.1103 Q0.355,0.1112,0.3434,0.1141 T0.3301,0.117 Q0.3261,0.117,0.3229,0.1145 Q0.3181,0.1116,0.3052,0.1087 T0.2787,0.1043 T0.2546,0.1029 Q0.2506,0.1029,0.2474,0.1033 Q0.2418,0.1037,0.2349,0.1054 T0.2249,0.107 Q0.2225,0.107,0.2193,0.1066 Q0.2024,0.1024,0.1863,0.1024 Q0.1823,0.1024,0.1562,0.0971 T0.1269,0.0917 T0.1161,0.0896 T0.106,0.0854 Q0.1076,0.0846,0.11,0.0842 Q0.1173,0.0821,0.1261,0.0821 Q0.1301,0.0821,0.1369,0.0825 T0.1462,0.083 Q0.1494,0.083,0.151,0.0825 T0.1831,0.0813 T0.2201,0.0784 T0.243,0.074 T0.2667,0.0718 Q0.2707,0.0718,0.2755,0.0724 T0.2843,0.073 T0.2936,0.0724 T0.3024,0.0705 T0.3072,0.068 T0.3092,0.0657 T0.3108,0.0647 Q0.3116,0.0643,0.3133,0.0643 L0.3157,0.0643 L0.3181,0.0643 Q0.3446,0.0643,0.3446,0.0556 T0.3205,0.0469 L0.3189,0.0469 Q0.3157,0.0469,0.3157,0.0465 Q0.3084,0.0427,0.2835,0.0386 Q0.2739,0.0369,0.2675,0.0342 T0.2538,0.0299 Q0.249,0.029,0.245,0.029 T0.2378,0.0303 T0.2321,0.0315 Q0.2305,0.0315,0.2273,0.0307 Q0.2201,0.0286,0.2004,0.0272 T0.1775,0.0245 Q0.1735,0.0228,0.1663,0.0226 T0.1522,0.0197 T0.1345,0.0145 T0.1124,0.0079 Q0.0996,0.0033,0.0671,0.0044 T0.0297,0.0041 Q0.0169,0,0.0024,0.0017 Q0,0.0021,0,0.0037 Z M0.0064,0.9954 L0.0056,0.0058 Q0.0177,0.005,0.0281,0.0083 Q0.0345,0.01,0.0675,0.0089 T0.1108,0.012 Q0.1221,0.0162,0.1329,0.0187 T0.1498,0.0236 T0.1606,0.0268 T0.1691,0.0274 T0.1759,0.0286 Q0.1807,0.0303,0.2,0.0315 T0.2257,0.0348 Q0.2305,0.0361,0.2337,0.0361 Q0.2361,0.0361,0.2398,0.0348 T0.2466,0.0336 Q0.249,0.0336,0.2522,0.034 Q0.2586,0.0353,0.2655,0.0382 T0.2827,0.0427 Q0.3068,0.0469,0.3124,0.0502 Q0.3157,0.0523,0.3237,0.0523 L0.3277,0.0523 L0.3309,0.0523 Q0.3398,0.0523,0.3398,0.0556 Q0.3398,0.0581,0.3341,0.0587 T0.3205,0.0593 T0.3092,0.0603 T0.3036,0.0643 T0.2932,0.068 Q0.2884,0.0684,0.2843,0.0684 Q0.2811,0.0684,0.2763,0.0678 T0.2667,0.0672 Q0.2586,0.0672,0.2422,0.0697 T0.2177,0.0747 Q0.2137,0.0759,0.1831,0.0767 T0.1494,0.0784 L0.1474,0.0784 T0.1398,0.078 T0.1285,0.0776 Q0.1173,0.0776,0.1084,0.08 Q0.1004,0.0817,0.1004,0.0846 Q0.1004,0.0867,0.104,0.0888 T0.1124,0.0923 T0.1213,0.0948 T0.1269,0.0958 T0.155,0.1014 T0.1863,0.107 Q0.2016,0.107,0.2185,0.1107 Q0.2217,0.1116,0.2257,0.1116 Q0.2289,0.1116,0.2365,0.1099 T0.2482,0.1074 L0.2538,0.1074 Q0.2683,0.1074,0.2912,0.1107 T0.3197,0.1182 Q0.3245,0.1215,0.3301,0.1215 Q0.3333,0.1215,0.3454,0.1184 T0.3687,0.1145 Q0.3735,0.1141,0.3779,0.1139 T0.3843,0.1134 T0.3871,0.1132 Q0.3896,0.1132,0.39,0.1136 T0.392,0.1161 T0.3968,0.1203 Q0.404,0.1248,0.408,0.1248 Q0.4096,0.1248,0.4165,0.1234 T0.4305,0.1215 L0.4337,0.1215 Q0.4442,0.1215,0.4518,0.1251 T0.4659,0.1294 Q0.4876,0.1319,0.5116,0.1319 T0.5622,0.141 Q0.5703,0.1439,0.5751,0.1439 Q0.5791,0.1439,0.5831,0.1423 T0.5888,0.1406 Q0.592,0.1406,0.5944,0.1412 T0.6012,0.1427 T0.6129,0.1435 Q0.6313,0.1435,0.649,0.1497 Q0.6602,0.1535,0.6667,0.1535 Q0.6715,0.1535,0.6779,0.1522 Q0.6787,0.1518,0.6795,0.1518 Q0.6827,0.1518,0.6847,0.1541 T0.6867,0.1593 Q0.6867,0.1618,0.6851,0.1634 Q0.6835,0.1655,0.6775,0.1667 T0.6598,0.169 T0.6458,0.1705 T0.6402,0.1709 Q0.6378,0.1709,0.6321,0.1705 T0.6225,0.1701 Q0.6096,0.1701,0.5968,0.1738 Q0.5823,0.1779,0.5446,0.1779 Q0.5205,0.1779,0.5116,0.1767 Q0.51,0.1763,0.5084,0.1763 Q0.5028,0.1763,0.4944,0.1796 T0.4835,0.1829 L0.4771,0.1833 T0.4659,0.1844 L0.4546,0.1858 T0.443,0.1881 T0.4337,0.1912 Q0.4321,0.1916,0.4305,0.1916 Q0.4273,0.1916,0.4209,0.1895 T0.412,0.1875 Q0.408,0.1875,0.4056,0.19 Q0.4024,0.1925,0.388,0.1925 Q0.3663,0.1925,0.3558,0.1879 Q0.3518,0.1862,0.3462,0.1862 Q0.3373,0.1862,0.3213,0.191 T0.3012,0.1962 Q0.2964,0.1966,0.2799,0.1976 T0.2494,0.1997 T0.2313,0.2024 L0.2313,0.2024 L0.2293,0.2024 L0.2261,0.2024 L0.2217,0.2024 Q0.204,0.2024,0.1851,0.2047 T0.1526,0.2136 Q0.1406,0.2198,0.1406,0.226 Q0.1406,0.2319,0.1518,0.2362 T0.1719,0.2406 Q0.1791,0.2406,0.2466,0.2497 Q0.2498,0.2501,0.2574,0.2516 T0.2711,0.2538 T0.2819,0.2547 L0.2851,0.2547 Q0.2851,0.2543,0.2859,0.2543 Q0.294,0.2543,0.3096,0.2592 T0.3285,0.2684 Q0.3317,0.2721,0.3373,0.2721 Q0.3406,0.2721,0.3462,0.2708 T0.3542,0.2696 Q0.3558,0.2696,0.3574,0.27 Q0.3679,0.2737,0.3968,0.2762 Q0.3984,0.2766,0.4004,0.2779 L0.4044,0.2804 T0.4096,0.2816 Q0.412,0.2816,0.4145,0.2808 Q0.4177,0.2804,0.4249,0.2804 Q0.4586,0.2804,0.4763,0.287 Q0.4867,0.2912,0.4972,0.2912 Q0.5004,0.2912,0.5064,0.2908 T0.5165,0.2903 Q0.5293,0.2903,0.5361,0.2922 T0.547,0.3003 L0.547,0.3011 Q0.547,0.3036,0.5414,0.3049 T0.5249,0.3067 T0.51,0.3082 Q0.5092,0.3082,0.4831,0.3111 T0.4498,0.3161 Q0.408,0.3264,0.3952,0.3264 Q0.3896,0.3264,0.3863,0.3252 Q0.3783,0.3223,0.3703,0.3223 Q0.359,0.3223,0.3494,0.3277 Q0.347,0.3293,0.343,0.3293 Q0.3414,0.3293,0.3353,0.3285 T0.3253,0.3277 T0.3181,0.3285 Q0.3149,0.3289,0.2952,0.3306 T0.2699,0.3347 Q0.2675,0.3364,0.2598,0.3372 T0.2434,0.3395 T0.2297,0.3426 L0.2205,0.3461 T0.2048,0.3523 T0.1908,0.3606 T0.1791,0.3725 Q0.1743,0.3803,0.1743,0.3845 Q0.1743,0.3874,0.1763,0.3901 T0.1827,0.3976 T0.1912,0.4073 T0.198,0.4189 T0.2032,0.4276 T0.2137,0.4326 T0.2386,0.438 Q0.2458,0.4392,0.2562,0.4392 L0.2639,0.4392 L0.2699,0.4392 Q0.2795,0.4392,0.2843,0.4405 Q0.2932,0.443,0.3092,0.443 L0.3157,0.443 L0.3213,0.443 Q0.3293,0.443,0.3458,0.4463 T0.3671,0.4525 Q0.3598,0.4558,0.3373,0.4571 Q0.3124,0.4587,0.298,0.4637 Q0.294,0.465,0.2811,0.465 L0.2703,0.465 L0.2586,0.465 Q0.241,0.465,0.2305,0.4666 Q0.2185,0.4687,0.2185,0.4728 Q0.2185,0.4745,0.2205,0.4764 T0.2241,0.4791 L0.2265,0.4803 L0.2273,0.4803 Q0.2506,0.4824,0.261,0.4824 Q0.2675,0.4824,0.2699,0.482 Q0.2699,0.4815,0.2707,0.4815 Q0.2771,0.4815,0.3124,0.4898 Q0.3173,0.4907,0.3229,0.4913 T0.3386,0.4925 T0.3542,0.4936 T0.3663,0.494 Q0.3711,0.494,0.3787,0.4936 T0.3888,0.4932 Q0.392,0.4932,0.3944,0.4936 T0.4032,0.4959 T0.4181,0.499 T0.4369,0.5006 Q0.449,0.501,0.455,0.5021 T0.4639,0.5048 T0.4675,0.5093 L0.4675,0.5097 Q0.4675,0.5106,0.4663,0.511 T0.4622,0.5116 L0.4566,0.512 T0.4486,0.5124 T0.4402,0.5127 Q0.4345,0.5131,0.4257,0.5153 T0.4096,0.5176 Q0.4064,0.5176,0.4024,0.5172 L0.3968,0.5172 Q0.3863,0.5172,0.3731,0.522 T0.359,0.5268 Q0.355,0.528,0.3317,0.5321 T0.3004,0.5388 Q0.2867,0.5433,0.2337,0.5479 Q0.2096,0.55,0.2096,0.5562 Q0.2096,0.5591,0.2145,0.5628 Q0.2193,0.5662,0.2289,0.5662 Q0.2313,0.5662,0.2373,0.5657 T0.2458,0.5653 L0.2498,0.5653 Q0.2635,0.5674,0.3124,0.5674 Q0.3261,0.5674,0.3301,0.567 L0.3317,0.567 Q0.3341,0.567,0.3394,0.568 T0.3486,0.5691 Q0.3542,0.5691,0.3606,0.5678 T0.3699,0.5666 T0.3759,0.5686 T0.3839,0.5718 T0.3908,0.573 T0.3964,0.5734 T0.4048,0.5742 T0.4165,0.5751 T0.4277,0.5757 T0.4369,0.5778 Q0.4426,0.5794,0.4538,0.5794 Q0.4586,0.5794,0.4679,0.5792 T0.4811,0.579 Q0.4924,0.579,0.4956,0.5807 Q0.502,0.584,0.5108,0.584 L0.5137,0.584 T0.5173,0.5838 L0.5205,0.5834 L0.5237,0.583 L0.5269,0.5825 T0.5305,0.5821 T0.5341,0.5819 L0.5365,0.5819 Q0.5414,0.5819,0.5562,0.5842 T0.5855,0.5865 Q0.5912,0.5865,0.5948,0.5867 T0.6012,0.5869 T0.6064,0.5873 T0.61,0.5877 T0.6133,0.5886 T0.6157,0.5896 L0.6185,0.591 T0.6217,0.5931 Q0.6265,0.5952,0.6369,0.5952 L0.6418,0.5952 L0.6466,0.5952 Q0.6723,0.5952,0.6932,0.606 Q0.698,0.608,0.7044,0.608 Q0.7084,0.608,0.7161,0.6068 T0.7261,0.6056 Q0.7309,0.6056,0.7454,0.6051 T0.7647,0.6047 Q0.7775,0.6047,0.8072,0.6101 Q0.8096,0.6105,0.812,0.6105 Q0.8177,0.6105,0.8297,0.6085 T0.8474,0.606 L0.8506,0.606 Q0.8562,0.606,0.8647,0.607 T0.8831,0.6095 T0.8964,0.6118 Q0.9036,0.6122,0.9221,0.6122 L0.9406,0.6122 L0.959,0.6122 L0.9767,0.6122 Q0.9952,0.613,0.9952,0.6172 Q0.9952,0.6176,0.9944,0.6221 Q0.9936,0.6259,0.9574,0.6259 L0.955,0.6259 L0.9526,0.6259 Q0.9478,0.6259,0.9373,0.628 T0.9205,0.63 L0.9173,0.63 Q0.9133,0.6296,0.9084,0.6296 Q0.9004,0.6296,0.8715,0.6321 Q0.8233,0.6367,0.7944,0.64 Q0.7888,0.6408,0.7839,0.6408 T0.7739,0.6402 T0.7671,0.6396 Q0.7639,0.6396,0.7622,0.64 Q0.7614,0.6404,0.759,0.6404 Q0.7574,0.6404,0.749,0.6398 T0.7357,0.6392 Q0.7261,0.6392,0.7213,0.6412 Q0.7173,0.6425,0.7032,0.6437 T0.6711,0.6458 T0.6474,0.647 Q0.645,0.6474,0.6418,0.6474 Q0.6353,0.6474,0.6277,0.6454 T0.6161,0.6433 Q0.6104,0.6433,0.6072,0.6456 T0.6,0.6479 Q0.5912,0.6479,0.5486,0.6557 Q0.5422,0.657,0.5373,0.657 Q0.5349,0.657,0.5289,0.6564 T0.5173,0.6557 L0.5124,0.6557 Q0.4996,0.6562,0.4739,0.6611 Q0.4675,0.6624,0.4398,0.6645 T0.3851,0.668 T0.3558,0.6698 Q0.355,0.6698,0.3285,0.6721 T0.2948,0.6752 Q0.2892,0.6761,0.2843,0.6786 T0.2747,0.6831 T0.2618,0.6852 Q0.253,0.6852,0.2394,0.6893 T0.2141,0.6993 T0.1968,0.7105 Q0.1944,0.713,0.1944,0.7155 Q0.1944,0.7196,0.2012,0.7229 T0.2177,0.7285 T0.2281,0.7308 Q0.2321,0.7329,0.2365,0.7339 T0.245,0.7356 T0.2514,0.7366 Q0.2522,0.737,0.2554,0.7379 T0.2614,0.7393 T0.2679,0.7406 T0.2755,0.7416 T0.2835,0.742 Q0.29,0.742,0.3048,0.7428 T0.3253,0.7437 L0.3301,0.7437 L0.3333,0.7437 Q0.3341,0.7437,0.3598,0.7466 Q0.3639,0.7474,0.3671,0.7474 T0.3759,0.7466 T0.3855,0.7457 Q0.388,0.7457,0.3904,0.7462 Q0.3984,0.7466,0.4137,0.7495 T0.4349,0.7532 T0.441,0.7557 Q0.441,0.7599,0.4289,0.7619 Q0.4257,0.7628,0.4197,0.7634 T0.4072,0.7644 T0.3984,0.7652 Q0.3968,0.7652,0.3912,0.7655 T0.3811,0.7665 T0.3719,0.7694 Q0.3695,0.7702,0.3655,0.7702 T0.3546,0.7686 T0.3462,0.7665 L0.3446,0.7665 Q0.3406,0.7665,0.3293,0.769 T0.3149,0.7715 Q0.3084,0.7715,0.2952,0.7748 T0.2763,0.781 Q0.2675,0.7847,0.2554,0.7864 Q0.253,0.7864,0.2325,0.7874 T0.2024,0.7893 Q0.2008,0.7893,0.1984,0.7897 T0.1948,0.7901 T0.1916,0.7903 T0.1888,0.7908 T0.1867,0.7914 T0.1847,0.7922 L0.1831,0.793 T0.1819,0.7943 L0.1811,0.7959 T0.1799,0.7976 Q0.1791,0.7993,0.1791,0.8001 Q0.1791,0.8034,0.1831,0.8051 T0.1932,0.8078 T0.2,0.8092 Q0.2072,0.8117,0.2257,0.8117 Q0.2337,0.8117,0.2586,0.8109 L0.2635,0.8109 Q0.2707,0.8109,0.2767,0.8117 T0.2859,0.8136 T0.2936,0.8163 T0.3012,0.8187 Q0.3068,0.82,0.3277,0.8206 T0.3526,0.8237 Q0.3582,0.8266,0.3622,0.8266 Q0.3647,0.8266,0.3687,0.8258 T0.3751,0.825 Q0.396,0.825,0.404,0.8291 Q0.408,0.8316,0.4161,0.8316 Q0.4217,0.8316,0.4353,0.8295 L0.4394,0.8295 Q0.4466,0.8295,0.4659,0.8366 Q0.4699,0.8378,0.4747,0.8378 Q0.4763,0.8378,0.4807,0.8374 T0.4884,0.837 Q0.494,0.837,0.5004,0.8382 Q0.5052,0.8391,0.5124,0.8399 Q0.5221,0.8411,0.5249,0.8422 T0.5277,0.8461 Q0.5277,0.8482,0.5233,0.849 T0.5112,0.8501 T0.4996,0.8511 Q0.4964,0.8519,0.4851,0.8523 T0.4659,0.8544 Q0.4578,0.8557,0.4402,0.8557 L0.4349,0.8557 L0.4297,0.8557 L0.4209,0.8557 Q0.408,0.8565,0.3867,0.8571 T0.3614,0.8582 Q0.3317,0.8611,0.3237,0.8611 L0.3225,0.8611 L0.3213,0.8611 Q0.2972,0.8611,0.2972,0.8681 Q0.2972,0.8714,0.3008,0.8747 T0.3044,0.8805 Q0.3044,0.881,0.3036,0.8814 Q0.3012,0.8839,0.2851,0.8839 Q0.2779,0.8839,0.2739,0.883 Q0.2723,0.8826,0.2699,0.8826 T0.2643,0.8835 T0.259,0.8853 T0.2542,0.8876 T0.249,0.8893 Q0.2386,0.8922,0.2056,0.8946 Q0.2016,0.8951,0.1916,0.898 T0.1703,0.9009 Q0.1518,0.9009,0.1337,0.9044 T0.1149,0.9146 L0.1149,0.9158 Q0.1149,0.9175,0.1157,0.9187 T0.1173,0.921 T0.1201,0.9226 T0.1233,0.9237 T0.1269,0.9245 L0.1305,0.9251 T0.1337,0.9255 L0.1357,0.9258 Q0.139,0.9262,0.1446,0.9262 T0.1542,0.9266 T0.1598,0.9282 Q0.1614,0.9303,0.1647,0.9314 T0.1739,0.9328 T0.1815,0.9336 Q0.1863,0.9345,0.1976,0.9372 T0.2145,0.9415 Q0.2225,0.9432,0.2285,0.9467 T0.2378,0.9515 Q0.2426,0.9531,0.2514,0.9531 Q0.2538,0.9531,0.2602,0.9529 T0.2707,0.9527 Q0.2763,0.9527,0.2815,0.9531 T0.2867,0.9569 T0.2811,0.9614 Q0.2434,0.9627,0.2329,0.9652 Q0.2249,0.9672,0.2241,0.9672 L0.2096,0.9668 L0.2056,0.9668 Q0.2,0.9668,0.196,0.9672 T0.1884,0.9687 T0.1815,0.9708 T0.1731,0.9737 T0.1639,0.9772 Q0.1518,0.9805,0.1285,0.9805 L0.1253,0.9805 Q0.1124,0.9805,0.1076,0.9818 Q0.1012,0.983,0.0831,0.9876 T0.0594,0.9934 Q0.049,0.9954,0.0064,0.9954 Z M0.0032,0.9979 L0.0024,0.0037 Q0.0161,0.0021,0.0289,0.0062 Q0.0337,0.0075,0.0671,0.0066 T0.1116,0.01 T0.1337,0.0166 T0.151,0.022 Q0.1582,0.0249,0.1659,0.0251 T0.1767,0.0265 Q0.1807,0.0278,0.2,0.0292 T0.2265,0.0328 Q0.2305,0.034,0.2329,0.034 T0.239,0.0326 T0.2458,0.0311 T0.253,0.0319 Q0.2578,0.0332,0.2614,0.0342 T0.2667,0.0363 T0.2723,0.0384 T0.2827,0.0406 Q0.3084,0.0448,0.3141,0.0485 Q0.3149,0.0494,0.3225,0.0494 T0.3361,0.0504 T0.3422,0.0554 T0.3357,0.0606 T0.3205,0.0618 T0.31,0.0626 Q0.3092,0.0626,0.3056,0.0659 T0.2932,0.0701 Q0.2884,0.0709,0.2843,0.0709 Q0.2811,0.0709,0.2759,0.0701 T0.2667,0.0693 Q0.2586,0.0693,0.2422,0.0718 T0.2193,0.0763 Q0.2137,0.0784,0.1831,0.079 T0.1502,0.0805 Q0.1494,0.0809,0.147,0.0809 Q0.1454,0.0809,0.1386,0.0803 T0.1277,0.0796 Q0.1173,0.0796,0.1092,0.0821 Q0.1036,0.0834,0.1036,0.0854 Q0.1036,0.0879,0.1129,0.0908 T0.1269,0.0937 Q0.1293,0.0937,0.1558,0.0991 T0.1863,0.1045 Q0.2016,0.1045,0.2193,0.1087 Q0.2225,0.1091,0.2249,0.1091 Q0.2289,0.1091,0.2357,0.1076 T0.2482,0.1054 Q0.2506,0.1049,0.2546,0.1049 Q0.2699,0.1049,0.2924,0.1085 T0.3213,0.1165 Q0.3253,0.119,0.3301,0.119 Q0.3325,0.119,0.3446,0.1161 T0.3687,0.1124 Q0.3831,0.1112,0.3863,0.1112 T0.3908,0.1118 T0.3936,0.1145 T0.3984,0.1186 Q0.4048,0.1228,0.408,0.1228 Q0.4096,0.1228,0.4161,0.1213 T0.4305,0.1195 L0.4337,0.1195 Q0.4426,0.1195,0.4474,0.1211 T0.4566,0.1246 T0.4667,0.1273 Q0.4876,0.1298,0.5116,0.1298 Q0.5365,0.1298,0.5631,0.1389 Q0.5703,0.1414,0.5751,0.1414 Q0.5783,0.1414,0.5819,0.1398 T0.5888,0.1381 Q0.5928,0.1381,0.5984,0.1398 T0.6129,0.1414 Q0.6321,0.1414,0.6498,0.1477 Q0.661,0.1514,0.6667,0.1514 Q0.6707,0.1514,0.6771,0.1501 Q0.6787,0.1497,0.6795,0.1497 Q0.6835,0.1497,0.6863,0.1526 T0.6892,0.1593 Q0.6892,0.1626,0.6876,0.1647 Q0.6851,0.1672,0.6799,0.1686 T0.6695,0.1705 T0.6566,0.1715 T0.6458,0.1725 Q0.6434,0.173,0.6402,0.173 Q0.6378,0.173,0.6317,0.1727 T0.6225,0.1725 Q0.6096,0.1725,0.5984,0.1759 Q0.5823,0.1804,0.5454,0.1804 Q0.5205,0.1804,0.5116,0.1788 L0.5084,0.1788 Q0.5036,0.1788,0.4956,0.1819 T0.4843,0.185 Q0.4434,0.1887,0.4353,0.1929 Q0.4321,0.1945,0.4289,0.1945 T0.4197,0.1922 T0.4112,0.19 T0.4072,0.1912 Q0.4032,0.1949,0.388,0.1949 Q0.3663,0.1949,0.3542,0.19 Q0.351,0.1883,0.3462,0.1883 Q0.3406,0.1883,0.3321,0.1906 T0.3153,0.1954 T0.302,0.1983 Q0.2972,0.1991,0.2859,0.1997 T0.2643,0.201 T0.245,0.2024 T0.2337,0.2041 Q0.2321,0.2045,0.2181,0.2045 T0.1851,0.207 T0.1542,0.2157 Q0.143,0.2211,0.143,0.226 Q0.143,0.2314,0.153,0.2348 T0.1719,0.2381 Q0.1791,0.2381,0.2474,0.2476 Q0.2506,0.248,0.2558,0.2489 T0.2651,0.2505 T0.2739,0.252 T0.2819,0.2526 Q0.2835,0.2526,0.2851,0.2522 L0.2859,0.2522 Q0.2948,0.2522,0.3104,0.2574 T0.3309,0.2671 Q0.3333,0.2696,0.3365,0.2696 Q0.339,0.2696,0.3442,0.2684 T0.3526,0.2671 T0.3582,0.2679 Q0.3687,0.2717,0.3968,0.2742 Q0.4,0.2746,0.4036,0.2771 T0.4096,0.2796 Q0.4112,0.2796,0.4137,0.2787 Q0.4169,0.2779,0.4257,0.2779 Q0.4594,0.2779,0.4779,0.2849 Q0.4876,0.2891,0.4972,0.2891 Q0.4996,0.2891,0.506,0.2887 T0.5165,0.2883 Q0.5253,0.2883,0.5305,0.2889 T0.541,0.292 T0.5494,0.299 L0.5494,0.3015 Q0.5494,0.3036,0.5466,0.3051 T0.5386,0.3073 T0.5285,0.3086 T0.5181,0.3094 T0.5108,0.3102 Q0.5068,0.3107,0.494,0.3121 L0.4683,0.315 T0.4506,0.3181 Q0.449,0.3181,0.4426,0.32 T0.4313,0.3229 T0.4197,0.3256 T0.4064,0.3281 T0.3952,0.3289 Q0.3888,0.3289,0.3855,0.3273 Q0.3783,0.3243,0.3703,0.3243 Q0.3598,0.3243,0.351,0.3297 Q0.3478,0.3314,0.3422,0.3314 Q0.3398,0.3314,0.3337,0.3306 T0.3245,0.3297 T0.3189,0.3306 Q0.3149,0.3314,0.2956,0.3328 T0.2715,0.3368 Q0.2691,0.338,0.2635,0.3391 T0.2522,0.3407 T0.2406,0.3424 T0.2313,0.3447 Q0.2305,0.3447,0.2213,0.348 T0.206,0.354 T0.1924,0.3621 T0.1815,0.3733 Q0.1767,0.3816,0.1767,0.3849 Q0.1767,0.387,0.1783,0.3893 T0.1847,0.3961 T0.1936,0.4063 T0.2004,0.4181 T0.2052,0.4264 T0.2145,0.4309 T0.2386,0.4359 Q0.2458,0.4372,0.2554,0.4372 Q0.2578,0.4372,0.2627,0.437 T0.2699,0.4367 Q0.2795,0.4367,0.2851,0.4384 Q0.294,0.4409,0.3092,0.4409 Q0.3116,0.4409,0.3157,0.4407 T0.3213,0.4405 Q0.3301,0.4405,0.3466,0.444 T0.3687,0.4509 Q0.3703,0.4517,0.3703,0.4525 Q0.3703,0.4575,0.3373,0.4596 Q0.3133,0.4608,0.2988,0.4658 Q0.294,0.4674,0.2803,0.4674 Q0.2763,0.4674,0.2687,0.4672 T0.257,0.467 Q0.2402,0.467,0.2313,0.4687 Q0.2209,0.4708,0.2209,0.4737 Q0.2209,0.4741,0.2213,0.4749 T0.2229,0.4764 L0.2249,0.4774 T0.2265,0.4778 L0.2273,0.4782 Q0.2498,0.4803,0.2618,0.4803 Q0.2667,0.4803,0.2691,0.4799 Q0.2699,0.4795,0.2715,0.4795 Q0.2779,0.4795,0.3133,0.4873 Q0.3181,0.4886,0.3233,0.4892 T0.3386,0.4905 T0.3542,0.4915 T0.3655,0.4919 Q0.3703,0.4919,0.3779,0.4915 T0.3888,0.4911 T0.3952,0.4915 Q0.3976,0.4919,0.4008,0.4927 L0.4072,0.4944 T0.4149,0.4961 T0.4249,0.4975 T0.4369,0.4985 Q0.449,0.499,0.4554,0.5 T0.4651,0.5031 T0.4699,0.5085 Q0.4707,0.5093,0.4707,0.5097 Q0.4707,0.5114,0.4687,0.5124 T0.4631,0.5139 T0.4562,0.5145 T0.4478,0.5147 L0.4402,0.5147 Q0.4353,0.5151,0.4265,0.5174 T0.4096,0.5197 L0.4024,0.5197 Q0.4,0.5193,0.3968,0.5193 Q0.3896,0.5193,0.3831,0.5209 T0.3703,0.5253 T0.3598,0.5288 Q0.3558,0.5301,0.3321,0.5342 T0.302,0.5409 Q0.2876,0.5454,0.2337,0.55 Q0.2112,0.5521,0.2112,0.5558 Q0.2112,0.5574,0.2161,0.5612 Q0.2201,0.5637,0.2281,0.5637 Q0.2305,0.5637,0.2361,0.5633 T0.2442,0.5628 Q0.2474,0.5628,0.2498,0.5633 Q0.2635,0.5653,0.3133,0.5653 Q0.3261,0.5653,0.3301,0.5649 L0.3325,0.5649 Q0.3349,0.5649,0.3402,0.5657 T0.3494,0.5666 Q0.3542,0.5666,0.3598,0.5657 Q0.3663,0.5645,0.3695,0.5645 Q0.3735,0.5645,0.3775,0.5666 T0.3847,0.5695 Q0.3888,0.5707,0.3912,0.5709 T0.3968,0.5711 T0.4048,0.572 Q0.4096,0.5724,0.4165,0.5726 T0.4281,0.5734 T0.4378,0.5757 Q0.4418,0.5774,0.4522,0.5774 Q0.4562,0.5774,0.4655,0.5769 T0.4795,0.5765 Q0.4932,0.5765,0.4972,0.579 Q0.5028,0.5819,0.51,0.5819 Q0.5141,0.5819,0.5221,0.5809 T0.5341,0.5798 L0.5373,0.5798 Q0.5422,0.5798,0.557,0.5821 T0.5855,0.5844 L0.5936,0.5844 L0.5992,0.5844 T0.604,0.5848 T0.6076,0.5852 T0.6108,0.5859 T0.6137,0.5865 T0.6161,0.5873 T0.6181,0.5881 T0.6209,0.5896 L0.6233,0.591 Q0.6265,0.5927,0.6353,0.5927 L0.6402,0.5927 L0.645,0.5927 Q0.6723,0.5927,0.6948,0.6039 Q0.6988,0.606,0.7036,0.606 Q0.7076,0.606,0.7153,0.6045 T0.7261,0.6031 Q0.7309,0.6031,0.7454,0.6027 T0.7647,0.6022 Q0.7783,0.6022,0.808,0.608 L0.812,0.608 Q0.8169,0.608,0.8293,0.6062 T0.8474,0.6039 L0.8506,0.6039 Q0.857,0.6039,0.8655,0.6049 T0.8835,0.6074 T0.8972,0.6093 Q0.9036,0.6101,0.9213,0.6101 L0.9398,0.6101 L0.959,0.6101 L0.9767,0.6101 Q0.9976,0.6109,0.9976,0.6176 Q0.9976,0.6188,0.9968,0.6226 Q0.9952,0.6284,0.9671,0.6284 L0.959,0.6284 L0.9526,0.6284 Q0.9486,0.6284,0.9382,0.6302 T0.9205,0.6321 L0.9173,0.6321 Q0.9133,0.6317,0.9092,0.6317 Q0.9012,0.6317,0.8715,0.6346 Q0.8233,0.6387,0.7944,0.6421 Q0.7888,0.6429,0.7831,0.6429 Q0.7783,0.6429,0.7731,0.6425 T0.7663,0.6421 L0.7622,0.6421 Q0.7614,0.6425,0.7586,0.6425 T0.7474,0.6421 T0.7349,0.6416 Q0.7261,0.6416,0.7221,0.6429 Q0.7173,0.645,0.7032,0.646 T0.6711,0.6479 T0.6482,0.6491 Q0.645,0.6495,0.6418,0.6495 Q0.6353,0.6495,0.6273,0.6474 T0.6161,0.6454 Q0.612,0.6454,0.6088,0.6477 T0.6,0.6499 Q0.5912,0.6499,0.5494,0.6578 Q0.5422,0.6591,0.5373,0.6591 Q0.5341,0.6591,0.5281,0.6584 T0.5173,0.6578 L0.5124,0.6578 Q0.5004,0.6582,0.4747,0.6632 Q0.4675,0.6649,0.4398,0.6667 T0.3851,0.6703 T0.3566,0.6719 Q0.3534,0.6723,0.3406,0.6734 T0.3141,0.6757 T0.2948,0.6773 Q0.2908,0.6781,0.2819,0.6829 T0.2618,0.6877 Q0.2482,0.6877,0.2273,0.6958 T0.1992,0.7117 Q0.1976,0.7138,0.1976,0.7155 Q0.1976,0.7188,0.2032,0.7213 T0.2181,0.7258 T0.2289,0.7292 Q0.2337,0.7308,0.2378,0.7316 L0.2458,0.7333 T0.2522,0.7345 Q0.2747,0.7399,0.2835,0.7399 Q0.29,0.7399,0.3048,0.7408 T0.3253,0.7416 Q0.3277,0.7416,0.3297,0.7414 T0.3341,0.7412 L0.3598,0.7445 Q0.3639,0.7449,0.3663,0.7449 Q0.3695,0.7449,0.3751,0.7443 T0.3855,0.7437 L0.3904,0.7437 Q0.3984,0.7441,0.4141,0.7472 T0.4353,0.7507 Q0.4434,0.752,0.4434,0.7553 Q0.4434,0.7578,0.439,0.7605 T0.4297,0.764 Q0.4257,0.7648,0.4201,0.7655 T0.4076,0.7665 T0.3984,0.7673 Q0.3968,0.7673,0.3912,0.7677 T0.3815,0.7688 T0.3735,0.771 T0.3655,0.7727 Q0.3606,0.7727,0.3534,0.7708 T0.3458,0.769 L0.3446,0.769 Q0.3414,0.769,0.3297,0.7713 T0.3149,0.7735 Q0.3092,0.7735,0.296,0.7771 T0.2771,0.7831 Q0.2683,0.7872,0.2554,0.7885 Q0.253,0.7889,0.2325,0.7897 T0.2032,0.7914 Q0.2008,0.7918,0.198,0.792 T0.194,0.7924 T0.1912,0.7928 T0.1888,0.793 T0.1867,0.7934 T0.1851,0.7943 T0.1843,0.7953 T0.1835,0.7968 T0.1823,0.7984 T0.1815,0.8001 Q0.1815,0.8022,0.1847,0.8032 T0.1932,0.8053 T0.2008,0.8071 Q0.2072,0.8092,0.2265,0.8092 Q0.2361,0.8092,0.2586,0.8088 L0.2635,0.8088 Q0.2731,0.8088,0.2795,0.8098 T0.2928,0.8134 T0.302,0.8167 Q0.306,0.8179,0.3277,0.8183 T0.3542,0.8221 Q0.359,0.8246,0.3622,0.8246 Q0.3639,0.8246,0.3675,0.8237 T0.3751,0.8229 Q0.3968,0.8229,0.4048,0.8275 Q0.4088,0.8291,0.4161,0.8291 Q0.4225,0.8291,0.4353,0.8275 Q0.4369,0.827,0.4394,0.827 Q0.4474,0.827,0.4675,0.8345 Q0.4707,0.8358,0.4739,0.8358 Q0.4755,0.8358,0.4799,0.8353 T0.4876,0.8349 Q0.494,0.8349,0.5012,0.8362 L0.5076,0.837 T0.5133,0.8376 T0.5181,0.8382 T0.5221,0.8391 L0.5253,0.8399 T0.5277,0.8409 L0.5293,0.8422 T0.5301,0.8438 L0.5301,0.8461 Q0.5301,0.8499,0.5249,0.8511 T0.5112,0.8526 T0.5004,0.8532 Q0.4964,0.854,0.4851,0.8546 T0.4667,0.8565 Q0.4578,0.8577,0.441,0.8577 L0.4345,0.8577 L0.4289,0.8577 L0.4209,0.8577 Q0.4153,0.8582,0.4088,0.8586 T0.3968,0.8592 T0.3855,0.8594 T0.3755,0.8596 T0.3675,0.86 T0.3622,0.8602 Q0.3317,0.8635,0.3237,0.8635 Q0.2996,0.8635,0.2996,0.8681 Q0.2996,0.8698,0.3032,0.8733 T0.3068,0.8797 L0.3068,0.8822 Q0.3052,0.8859,0.2859,0.8859 Q0.2787,0.8859,0.2739,0.8851 L0.2699,0.8851 Q0.2659,0.8851,0.259,0.888 T0.2498,0.8913 Q0.2394,0.8946,0.2056,0.8971 Q0.2032,0.8971,0.1928,0.9002 T0.1703,0.9034 Q0.1526,0.9034,0.1353,0.9067 T0.1173,0.915 L0.1173,0.9158 Q0.1173,0.9166,0.1177,0.9175 T0.1189,0.9191 T0.1205,0.9204 T0.1229,0.9212 T0.1257,0.922 T0.1285,0.9226 T0.1309,0.9231 T0.1337,0.9235 T0.1365,0.9237 L0.1446,0.9237 T0.1554,0.9243 T0.1614,0.9268 T0.1659,0.9293 T0.1743,0.9303 T0.1823,0.9316 Q0.1863,0.932,0.1912,0.9332 T0.2028,0.9361 T0.2153,0.939 Q0.2233,0.9411,0.2297,0.9448 T0.2386,0.9494 Q0.2426,0.9506,0.2506,0.9506 Q0.253,0.9506,0.2594,0.9504 T0.2699,0.9502 Q0.2763,0.9502,0.2819,0.9511 Q0.2851,0.9511,0.2871,0.9529 T0.2892,0.9573 Q0.2892,0.9627,0.2811,0.9635 Q0.2434,0.9652,0.2337,0.9672 Q0.2257,0.9693,0.2241,0.9693 Q0.2217,0.9693,0.2096,0.9689 L0.2056,0.9689 Q0.1976,0.9689,0.1924,0.9699 T0.1787,0.9741 L0.1647,0.9793 Q0.1518,0.983,0.1285,0.983 L0.1261,0.983 L0.1237,0.983 Q0.1124,0.983,0.1076,0.9838 Q0.102,0.9851,0.0839,0.9896 T0.0602,0.9954 T0.0406,0.9971 T0.0153,0.9979 L0.0032,0.9979 Z",type:0}},"\uE0CA":{type:5,data:{d:"M0.9992,0.998 Q0.9992,1,0.9968,1 Q0.9521,1,0.9392,0.998 Q0.9335,0.997,0.9152,0.9921 T0.8913,0.9862 Q0.8873,0.9852,0.8783,0.9852 L0.8747,0.9852 L0.871,0.9852 Q0.8483,0.9852,0.8345,0.9812 Q0.8289,0.9792,0.8228,0.9773 T0.8135,0.9743 T0.8054,0.9723 T0.7948,0.9713 L0.7908,0.9713 L0.777,0.9713 T0.7656,0.9693 Q0.7616,0.9683,0.7498,0.9674 T0.7283,0.9664 L0.7186,0.9654 Q0.708,0.9644,0.708,0.9575 Q0.708,0.9545,0.7109,0.952 T0.7178,0.9486 L0.7315,0.9486 L0.7417,0.9486 L0.7502,0.9486 Q0.7575,0.9486,0.7599,0.9476 Q0.7616,0.9466,0.7685,0.9426 T0.7843,0.9377 Q0.7899,0.9357,0.8009,0.9332 T0.8175,0.9298 Q0.82,0.9288,0.8228,0.9283 T0.8277,0.9278 T0.8313,0.9273 T0.8341,0.9263 T0.8362,0.9258 Q0.8402,0.9219,0.854,0.9219 L0.8569,0.9219 L0.8597,0.9219 L0.8637,0.9219 Q0.88,0.9199,0.88,0.9159 L0.88,0.9149 Q0.88,0.913,0.8719,0.911 T0.852,0.9075 T0.8297,0.906 Q0.8224,0.906,0.8167,0.905 T0.8074,0.9031 T0.8001,0.9006 T0.794,0.8991 Q0.7599,0.8971,0.7494,0.8942 Q0.7461,0.8932,0.7397,0.8902 T0.7299,0.8872 L0.7267,0.8872 Q0.721,0.8882,0.7121,0.8882 Q0.6926,0.8882,0.691,0.8833 L0.691,0.8803 Q0.691,0.8773,0.6922,0.8749 T0.6955,0.8704 L0.6975,0.8684 Q0.6926,0.8655,0.6764,0.8655 Q0.6683,0.8655,0.6375,0.8625 Q0.6358,0.8625,0.629,0.862 T0.6148,0.8615 T0.5973,0.861 T0.5791,0.8605 L0.5718,0.8605 L0.5661,0.8605 L0.5596,0.8605 Q0.5426,0.8605,0.5337,0.8586 Q0.5264,0.8576,0.515,0.8571 T0.4996,0.8556 Q0.498,0.8556,0.4947,0.8551 T0.4882,0.8546 T0.4809,0.8541 T0.474,0.8531 T0.4692,0.8506 T0.4672,0.8467 Q0.4672,0.8437,0.468,0.8422 T0.4704,0.8398 T0.4736,0.8383 T0.4781,0.8368 T0.4838,0.8358 T0.4911,0.8353 T0.4988,0.8338 Q0.5069,0.8328,0.5142,0.8328 Q0.5174,0.8328,0.5215,0.8333 T0.5264,0.8338 Q0.5296,0.8338,0.532,0.8328 Q0.5523,0.8249,0.5604,0.8249 L0.5653,0.8249 Q0.5775,0.8269,0.5848,0.8269 Q0.5912,0.8269,0.5937,0.8259 Q0.6026,0.821,0.6253,0.821 Q0.6294,0.821,0.6334,0.822 T0.6383,0.8229 Q0.6399,0.8229,0.644,0.82 Q0.6504,0.817,0.6723,0.816 T0.6975,0.814 Q0.6999,0.814,0.7048,0.8121 T0.7129,0.8091 T0.7226,0.8071 T0.7372,0.8061 L0.7421,0.8061 Q0.7624,0.8071,0.7721,0.8071 Q0.7924,0.8071,0.7981,0.8051 Q0.8021,0.8032,0.807,0.8027 T0.8135,0.8017 T0.8151,0.8002 L0.8151,0.7992 Q0.8143,0.7962,0.8127,0.7957 T0.8045,0.7943 Q0.7997,0.7943,0.7964,0.7933 Q0.7883,0.7933,0.7676,0.7923 T0.7437,0.7903 Q0.7307,0.7893,0.7218,0.7844 Q0.7194,0.7834,0.7141,0.7819 L0.7036,0.7789 T0.693,0.7765 T0.6853,0.7755 Q0.6805,0.7755,0.6691,0.773 T0.6553,0.7705 Q0.6545,0.7705,0.6545,0.7715 Q0.6537,0.7715,0.65,0.7725 T0.6423,0.774 T0.635,0.7745 Q0.6294,0.7745,0.6257,0.773 T0.618,0.771 T0.6083,0.77 T0.601,0.7695 Q0.5985,0.7695,0.5921,0.769 T0.5795,0.7676 T0.5702,0.7666 Q0.5653,0.7656,0.5596,0.7616 T0.5539,0.7547 Q0.5539,0.7498,0.5645,0.7488 Q0.5693,0.7478,0.5852,0.7448 T0.6099,0.7418 L0.6148,0.7418 Q0.6196,0.7418,0.6253,0.7423 T0.6338,0.7428 L0.6399,0.7428 Q0.6431,0.7418,0.6488,0.7409 T0.6586,0.7394 T0.6659,0.7389 L0.6707,0.7389 Q0.6723,0.7399,0.6748,0.7399 Q0.6796,0.7399,0.6946,0.7389 T0.7161,0.7379 Q0.7251,0.7379,0.7478,0.7319 Q0.7494,0.7319,0.7539,0.7315 T0.762,0.73 T0.7697,0.727 T0.7818,0.723 T0.7948,0.7196 T0.7997,0.7151 Q0.7997,0.7141,0.7989,0.7132 Q0.7924,0.7062,0.7717,0.6978 T0.738,0.6894 Q0.7267,0.6894,0.717,0.6845 T0.7048,0.6795 Q0.6975,0.6785,0.6711,0.6766 T0.6431,0.6746 Q0.6415,0.6736,0.6144,0.6721 T0.5596,0.6686 T0.5247,0.6657 Q0.4996,0.6607,0.4874,0.6597 L0.4834,0.6597 Q0.4785,0.6597,0.4724,0.6607 T0.4631,0.6617 Q0.4574,0.6617,0.4501,0.6597 Q0.4088,0.6518,0.4006,0.6518 Q0.3933,0.6518,0.3901,0.6499 T0.3844,0.6479 T0.3739,0.6499 T0.3585,0.6518 L0.352,0.6518 Q0.3471,0.6508,0.335,0.6503 T0.3122,0.6494 T0.2912,0.6479 T0.2766,0.6449 Q0.2733,0.6439,0.2668,0.6439 Q0.2628,0.6439,0.2543,0.6444 T0.2425,0.6449 T0.2368,0.6439 L0.2344,0.6439 Q0.2336,0.6439,0.2283,0.6444 T0.2182,0.6449 Q0.2117,0.6449,0.2052,0.6439 Q0.1768,0.6409,0.1281,0.637 Q0.0989,0.634,0.0916,0.634 L0.0835,0.634 L0.0795,0.634 Q0.0722,0.634,0.0616,0.632 T0.0479,0.6301 Q0.0462,0.6301,0.0406,0.6306 T0.0308,0.6311 Q0.0032,0.6311,0.0008,0.6231 Q0,0.6202,0,0.6182 Q0,0.6083,0.0227,0.6083 Q0.03,0.6073,0.0422,0.6073 Q0.0487,0.6073,0.0608,0.6078 T0.0787,0.6083 Q0.0973,0.6083,0.103,0.6073 Q0.1062,0.6063,0.1131,0.6053 T0.1253,0.6039 T0.1375,0.6024 T0.1492,0.6014 L0.1533,0.6014 Q0.1598,0.6024,0.1723,0.6044 T0.189,0.6063 Q0.1906,0.6063,0.1922,0.6053 Q0.2214,0.6004,0.236,0.6004 Q0.2409,0.6004,0.2551,0.6009 T0.2741,0.6014 Q0.2782,0.6014,0.2859,0.6024 T0.2968,0.6034 Q0.3009,0.6034,0.3041,0.6024 Q0.3277,0.5905,0.356,0.5905 L0.3613,0.5905 L0.3666,0.5905 Q0.3731,0.5905,0.3755,0.5895 Q0.382,0.5856,0.3844,0.5846 T0.3938,0.5831 T0.4144,0.5826 T0.4428,0.5801 T0.4631,0.5776 L0.4663,0.5776 Q0.4704,0.5776,0.4785,0.5786 T0.4899,0.5796 Q0.4964,0.5796,0.5012,0.5767 Q0.5069,0.5737,0.5223,0.5737 Q0.5272,0.5737,0.5369,0.5742 T0.5499,0.5747 Q0.558,0.5747,0.5612,0.5737 Q0.5661,0.5717,0.5714,0.5712 T0.5835,0.5702 T0.5945,0.5692 T0.6018,0.5687 L0.6062,0.5687 T0.6095,0.5682 T0.6148,0.5678 Q0.6172,0.5668,0.6196,0.5653 T0.6245,0.5633 T0.631,0.5628 T0.6407,0.5638 T0.6504,0.5648 T0.6594,0.5638 T0.6675,0.5628 L0.6707,0.5628 L0.6861,0.5628 Q0.7364,0.5628,0.7494,0.5608 L0.7567,0.5608 L0.7652,0.5608 L0.7729,0.5608 Q0.7794,0.5608,0.7818,0.5589 Q0.7851,0.5569,0.7859,0.5559 Q0.7835,0.5539,0.7656,0.5519 Q0.7121,0.547,0.6975,0.543 Q0.6934,0.541,0.6853,0.5396 L0.6691,0.5366 T0.6525,0.5336 T0.6391,0.5312 Q0.6367,0.5302,0.6245,0.5257 T0.6034,0.5213 L0.5977,0.5213 Q0.5945,0.5223,0.5904,0.5223 Q0.5839,0.5223,0.5787,0.5208 T0.5685,0.5183 T0.5596,0.5173 Q0.558,0.5173,0.5552,0.5168 T0.5495,0.5163 L0.5434,0.5163 T0.5373,0.5158 T0.532,0.5148 T0.5284,0.5129 T0.5272,0.5104 L0.5272,0.5074 Q0.5304,0.5015,0.5377,0.499 T0.5629,0.4965 Q0.5734,0.4955,0.5811,0.4946 T0.5953,0.4916 T0.605,0.4896 Q0.6083,0.4886,0.6123,0.4886 Q0.6148,0.4886,0.6225,0.4891 T0.635,0.4896 L0.6456,0.4896 Q0.6837,0.4857,0.6861,0.4857 Q0.7226,0.4768,0.7283,0.4768 Q0.7307,0.4768,0.732,0.4773 T0.7372,0.4777 Q0.7502,0.4777,0.7721,0.4758 Q0.7753,0.4748,0.777,0.4738 Q0.7745,0.4718,0.768,0.4708 Q0.7599,0.4688,0.7445,0.4688 Q0.7405,0.4688,0.7324,0.4693 T0.721,0.4698 Q0.7056,0.4698,0.6999,0.4679 Q0.6861,0.4629,0.6626,0.4619 Q0.6586,0.4609,0.6533,0.4604 T0.6419,0.459 T0.6314,0.456 T0.6269,0.452 Q0.6269,0.451,0.6302,0.4491 Q0.6358,0.4451,0.6525,0.4416 T0.6788,0.4382 L0.6845,0.4382 L0.6902,0.4382 Q0.7064,0.4382,0.7145,0.4362 Q0.7202,0.4342,0.7307,0.4342 L0.7356,0.4342 L0.7409,0.4342 L0.7453,0.4342 Q0.7543,0.4342,0.7607,0.4332 Q0.7778,0.4313,0.7847,0.4288 T0.7932,0.4248 T0.7972,0.4169 T0.8045,0.4045 L0.8094,0.3986 T0.8135,0.3942 L0.8167,0.3912 T0.8191,0.3887 T0.8204,0.3867 T0.8208,0.3848 Q0.8208,0.3818,0.8167,0.3739 Q0.8127,0.3689,0.8054,0.3635 T0.7924,0.3556 T0.7778,0.3497 T0.768,0.3462 Q0.764,0.3442,0.7478,0.3427 T0.7267,0.3383 Q0.7234,0.3363,0.7044,0.3348 T0.6805,0.3323 L0.6764,0.3323 T0.6679,0.3328 T0.6586,0.3333 Q0.6521,0.3333,0.648,0.3314 Q0.6391,0.3264,0.6294,0.3264 Q0.6229,0.3264,0.6156,0.3294 Q0.6115,0.3304,0.6042,0.3304 Q0.5977,0.3304,0.5888,0.3289 T0.5738,0.3264 T0.5584,0.3225 L0.5491,0.3195 Q0.5418,0.3185,0.5162,0.3155 T0.4895,0.3121 T0.4826,0.3116 T0.4716,0.3111 T0.4607,0.3096 T0.4513,0.3066 T0.4477,0.3017 Q0.4477,0.2997,0.4485,0.2977 Q0.4526,0.2898,0.4611,0.2878 T0.4842,0.2859 Q0.4882,0.2859,0.4943,0.2864 T0.5028,0.2868 Q0.5118,0.2868,0.5215,0.2829 Q0.5401,0.276,0.5734,0.276 Q0.5831,0.276,0.5872,0.277 L0.5904,0.277 T0.5949,0.2745 T0.6034,0.272 Q0.631,0.269,0.6407,0.2661 Q0.6448,0.2641,0.6488,0.2641 T0.6573,0.2656 T0.6634,0.2671 Q0.6659,0.2671,0.6675,0.2651 Q0.6723,0.2601,0.6886,0.2552 T0.7137,0.2502 L0.7153,0.2502 L0.7178,0.2502 Q0.7218,0.2502,0.7279,0.2493 T0.7417,0.2468 T0.7526,0.2453 Q0.8208,0.2354,0.8281,0.2354 Q0.8337,0.2354,0.8394,0.2344 T0.8496,0.2315 T0.854,0.2265 Q0.854,0.2216,0.8443,0.2176 Q0.8354,0.2127,0.8216,0.2102 T0.7972,0.2072 T0.777,0.2067 T0.7648,0.2057 Q0.7632,0.2047,0.7498,0.2038 T0.7202,0.2018 T0.6983,0.1998 Q0.6918,0.1998,0.676,0.1949 T0.6537,0.1899 Q0.6496,0.1899,0.6472,0.1919 Q0.6342,0.1968,0.6123,0.1968 Q0.5961,0.1968,0.5912,0.1929 Q0.5904,0.1919,0.5892,0.1919 T0.5823,0.1944 T0.5722,0.1968 T0.5637,0.1949 Q0.5556,0.1909,0.5158,0.1869 Q0.5109,0.1869,0.5032,0.1835 T0.4915,0.18 Q0.4907,0.18,0.4891,0.181 Q0.4793,0.182,0.455,0.182 Q0.4177,0.182,0.4015,0.1771 Q0.3901,0.1741,0.3779,0.1741 Q0.3747,0.1741,0.369,0.1746 T0.3609,0.1751 Q0.3569,0.1751,0.3536,0.1741 Q0.3512,0.1741,0.3459,0.1736 T0.3358,0.1726 T0.3256,0.1716 T0.3167,0.1696 T0.3106,0.1662 Q0.3082,0.1632,0.3082,0.1592 Q0.3082,0.1543,0.3114,0.1508 T0.3204,0.1474 L0.3236,0.1474 Q0.3301,0.1494,0.3333,0.1494 Q0.339,0.1494,0.3496,0.1454 Q0.3674,0.1385,0.3869,0.1385 Q0.395,0.1385,0.4011,0.137 T0.4116,0.1355 T0.4197,0.1375 T0.425,0.1395 Q0.429,0.1395,0.4355,0.1365 Q0.4631,0.1266,0.4882,0.1266 Q0.5126,0.1266,0.5337,0.1246 Q0.5369,0.1246,0.5458,0.1207 T0.5661,0.1167 L0.5693,0.1167 Q0.5791,0.1177,0.5852,0.1187 T0.5921,0.1197 Q0.5945,0.1197,0.6002,0.1167 Q0.6026,0.1147,0.605,0.1123 T0.6091,0.1093 T0.6156,0.1088 T0.6318,0.1098 Q0.6456,0.1108,0.6569,0.1137 T0.6707,0.1167 Q0.674,0.1167,0.6772,0.1147 Q0.6821,0.1108,0.6946,0.1083 T0.721,0.1044 T0.7453,0.1029 L0.7526,0.1029 Q0.7583,0.1039,0.7652,0.1053 T0.7753,0.1068 Q0.7778,0.1068,0.7802,0.1058 Q0.7981,0.1019,0.8143,0.1019 Q0.8175,0.1019,0.8439,0.0964 T0.8735,0.091 Q0.8759,0.091,0.8836,0.089 T0.8938,0.0851 Q0.8929,0.0841,0.8905,0.0841 Q0.8832,0.0821,0.8735,0.0821 Q0.8694,0.0821,0.8629,0.0826 T0.854,0.0831 Q0.8508,0.0831,0.8491,0.0821 Q0.8475,0.0821,0.8171,0.0811 T0.7802,0.0781 T0.7571,0.0737 T0.7332,0.0712 Q0.7299,0.0712,0.7247,0.0717 T0.7161,0.0722 L0.7064,0.0722 Q0.7032,0.0712,0.7003,0.0707 T0.6959,0.0692 T0.693,0.0678 T0.6914,0.0663 T0.6902,0.0648 T0.689,0.0643 L0.6869,0.0643 L0.6845,0.0643 L0.6821,0.0643 Q0.6553,0.0643,0.6553,0.0554 T0.6796,0.0465 L0.6813,0.0465 L0.6845,0.0465 Q0.6918,0.0425,0.717,0.0376 Q0.7226,0.0366,0.7267,0.0356 T0.7324,0.0336 T0.7376,0.0317 T0.747,0.0297 Q0.7518,0.0287,0.7551,0.0287 T0.762,0.0297 T0.768,0.0307 L0.7729,0.0307 Q0.7802,0.0277,0.7997,0.0267 T0.8224,0.0237 Q0.8273,0.0227,0.8341,0.0223 T0.8475,0.0193 T0.8654,0.0143 T0.8873,0.0079 Q0.9002,0.003,0.9331,0.004 T0.97,0.004 Q0.9838,0,0.9976,0.001 Q1,0.001,1,0.003 Z M0.9935,0.996 L0.9951,0.0049 Q0.9822,0.0049,0.9716,0.0079 Q0.9659,0.0099,0.9327,0.0089 T0.8897,0.0119 Q0.8775,0.0158,0.8666,0.0183 T0.85,0.0237 Q0.8418,0.0267,0.8341,0.0272 T0.8248,0.0277 Q0.82,0.0297,0.8001,0.0312 T0.7745,0.0346 Q0.7697,0.0356,0.7664,0.0356 Q0.764,0.0356,0.7603,0.0341 T0.7534,0.0326 Q0.751,0.0326,0.7478,0.0336 Q0.7429,0.0346,0.7397,0.0356 T0.7344,0.0376 T0.7283,0.0401 T0.7178,0.0425 Q0.6926,0.0465,0.6878,0.0495 Q0.6845,0.0514,0.6764,0.0514 L0.6723,0.0514 L0.6691,0.0514 Q0.6602,0.0514,0.6602,0.0554 Q0.6602,0.0574,0.6659,0.0579 T0.6796,0.0589 T0.691,0.0603 T0.6967,0.0643 T0.7072,0.0673 Q0.7121,0.0682,0.7153,0.0682 T0.7238,0.0678 T0.7332,0.0673 Q0.7413,0.0673,0.7579,0.0692 T0.7818,0.0742 Q0.7867,0.0752,0.8171,0.0762 T0.8508,0.0781 L0.852,0.0781 T0.8601,0.0776 T0.8719,0.0772 Q0.8832,0.0772,0.8921,0.0791 Q0.8994,0.0811,0.8994,0.0841 T0.8933,0.0895 T0.8812,0.094 T0.8735,0.0959 T0.8451,0.1014 T0.8143,0.1068 Q0.7989,0.1068,0.7818,0.1098 Q0.7778,0.1108,0.7745,0.1108 T0.768,0.1103 T0.7603,0.1088 T0.7518,0.1068 L0.7461,0.1068 Q0.7315,0.1068,0.7088,0.1103 T0.6805,0.1177 Q0.6756,0.1207,0.6699,0.1207 Q0.6667,0.1207,0.6545,0.1177 T0.631,0.1147 Q0.6269,0.1137,0.6225,0.1133 T0.616,0.1128 L0.6123,0.1128 T0.6103,0.1133 L0.6083,0.1157 T0.6034,0.1197 Q0.5961,0.1246,0.5921,0.1246 Q0.5904,0.1246,0.5835,0.1231 T0.5693,0.1217 L0.5661,0.1217 Q0.558,0.1217,0.5535,0.1231 T0.5446,0.1266 T0.5345,0.1296 Q0.5126,0.1316,0.4882,0.1316 T0.438,0.1405 Q0.4298,0.1434,0.425,0.1434 Q0.4209,0.1434,0.4173,0.1419 T0.4112,0.1405 T0.4027,0.1419 T0.3869,0.1434 Q0.3682,0.1434,0.3512,0.1494 Q0.3398,0.1533,0.3333,0.1533 Q0.3285,0.1533,0.3228,0.1513 L0.3212,0.1513 Q0.3179,0.1513,0.3155,0.1538 T0.3131,0.1592 Q0.3131,0.1612,0.3147,0.1632 Q0.3171,0.1652,0.3232,0.1667 T0.3406,0.1691 T0.3544,0.1701 L0.3601,0.1701 L0.3678,0.1701 L0.3771,0.1701 Q0.3909,0.1701,0.4031,0.1731 Q0.4177,0.178,0.4558,0.178 Q0.4793,0.178,0.4882,0.1761 L0.4915,0.1761 Q0.498,0.1761,0.5061,0.179 T0.5158,0.183 Q0.5166,0.183,0.5231,0.1835 T0.5341,0.1845 T0.545,0.1855 T0.5572,0.1874 T0.5661,0.1909 Q0.5677,0.1919,0.5702,0.1919 T0.5791,0.1894 T0.588,0.1869 Q0.5921,0.1869,0.5945,0.1899 Q0.5977,0.1919,0.6123,0.1919 Q0.6334,0.1919,0.6448,0.1879 Q0.6488,0.186,0.6537,0.186 Q0.6626,0.186,0.6784,0.1909 T0.6983,0.1958 Q0.704,0.1968,0.7206,0.1973 T0.7506,0.1993 T0.7689,0.2018 L0.7705,0.2018 L0.7741,0.2018 L0.7786,0.2018 Q0.8248,0.2018,0.8475,0.2136 Q0.8597,0.2196,0.8597,0.2255 T0.8483,0.2359 T0.8281,0.2404 Q0.8208,0.2404,0.7534,0.2493 Q0.7502,0.2502,0.7425,0.2512 T0.7287,0.2532 T0.7178,0.2542 L0.7153,0.2542 L0.7137,0.2542 Q0.7064,0.2542,0.6906,0.2591 T0.6715,0.2681 T0.6626,0.272 Q0.6594,0.272,0.6541,0.2705 T0.6464,0.269 T0.6431,0.27 Q0.6318,0.273,0.6034,0.276 Q0.6018,0.276,0.5998,0.2774 T0.5957,0.2799 T0.5904,0.2809 L0.5856,0.2809 Q0.5823,0.2799,0.575,0.2799 Q0.541,0.2799,0.5239,0.2868 Q0.5134,0.2908,0.5028,0.2908 Q0.4996,0.2908,0.4935,0.2903 T0.4842,0.2898 Q0.4704,0.2898,0.4635,0.2918 T0.4534,0.2997 L0.4534,0.3007 Q0.4534,0.3037,0.459,0.3046 T0.4753,0.3066 T0.4899,0.3076 Q0.4907,0.3076,0.5166,0.3106 T0.5507,0.3155 Q0.5921,0.3264,0.605,0.3264 Q0.6107,0.3264,0.6131,0.3254 Q0.6212,0.3225,0.6294,0.3225 Q0.6407,0.3225,0.6513,0.3274 Q0.6529,0.3294,0.6569,0.3294 Q0.6594,0.3294,0.665,0.3284 T0.6748,0.3274 T0.6821,0.3284 Q0.6861,0.3294,0.6967,0.3299 T0.7165,0.3314 T0.7299,0.3343 Q0.7324,0.3363,0.7486,0.3383 T0.7697,0.3422 Q0.7713,0.3432,0.7774,0.3452 T0.7887,0.3492 T0.8005,0.3546 T0.8127,0.3625 T0.8208,0.3719 Q0.8256,0.3798,0.8256,0.3848 Q0.8256,0.3877,0.8236,0.3902 T0.8171,0.3976 T0.809,0.4075 T0.8021,0.4189 T0.7968,0.4273 T0.7867,0.4322 T0.7616,0.4382 Q0.7543,0.4392,0.7437,0.4392 L0.7364,0.4392 L0.7299,0.4392 Q0.7202,0.4392,0.7161,0.4402 Q0.7072,0.4431,0.691,0.4431 L0.6845,0.4431 L0.6788,0.4431 Q0.6707,0.4431,0.6545,0.4461 T0.6334,0.452 Q0.6399,0.456,0.6626,0.457 Q0.6878,0.458,0.7024,0.4639 Q0.7064,0.4649,0.7186,0.4649 L0.7295,0.4649 L0.7413,0.4649 Q0.7591,0.4649,0.7697,0.4669 Q0.7818,0.4688,0.7818,0.4728 Q0.7818,0.4748,0.7798,0.4763 T0.7753,0.4787 L0.7737,0.4797 L0.7729,0.4797 Q0.7494,0.4827,0.7388,0.4827 Q0.7332,0.4827,0.7307,0.4817 L0.7291,0.4817 Q0.7226,0.4817,0.6878,0.4896 Q0.6829,0.4906,0.6772,0.4911 T0.6618,0.4921 T0.646,0.4931 T0.6342,0.4936 T0.6212,0.4931 T0.6115,0.4926 Q0.6083,0.4926,0.6058,0.4931 T0.5969,0.4955 T0.5819,0.499 T0.5629,0.5005 Q0.5572,0.5005,0.5527,0.501 T0.545,0.502 T0.5397,0.503 T0.5361,0.5045 T0.5337,0.5064 T0.5328,0.5094 L0.532,0.5094 Q0.532,0.5104,0.5337,0.5109 T0.5381,0.5114 T0.5438,0.5119 T0.5515,0.5124 L0.5604,0.5124 Q0.5661,0.5134,0.5746,0.5153 T0.5912,0.5173 L0.5977,0.5173 L0.6026,0.5173 Q0.6139,0.5173,0.6273,0.5218 T0.6407,0.5272 Q0.6448,0.5282,0.6683,0.5321 T0.6991,0.5381 Q0.7137,0.543,0.7664,0.548 Q0.7908,0.55,0.7908,0.5559 Q0.7908,0.5589,0.7859,0.5628 Q0.781,0.5658,0.7713,0.5658 Q0.7689,0.5658,0.7628,0.5653 T0.7543,0.5648 T0.7502,0.5658 Q0.7364,0.5678,0.6878,0.5678 Q0.674,0.5678,0.6699,0.5668 L0.6683,0.5668 Q0.6659,0.5668,0.6606,0.5678 T0.6513,0.5687 Q0.6464,0.5687,0.6391,0.5678 Q0.6334,0.5668,0.6302,0.5668 T0.6241,0.5687 T0.6164,0.5717 Q0.6123,0.5727,0.6099,0.5727 L0.6062,0.5727 T0.6022,0.5732 T0.5953,0.5737 Q0.5904,0.5747,0.5839,0.5747 T0.5726,0.5752 T0.5629,0.5776 T0.5458,0.5796 Q0.5418,0.5796,0.5324,0.5791 T0.5191,0.5786 Q0.5077,0.5786,0.5045,0.5806 Q0.498,0.5836,0.4899,0.5836 Q0.4858,0.5836,0.4777,0.5826 T0.4655,0.5816 L0.4631,0.5816 Q0.4582,0.5816,0.4436,0.5841 T0.4144,0.5865 L0.4051,0.5865 L0.3986,0.5865 T0.3933,0.587 T0.3897,0.5875 T0.3869,0.5885 T0.3844,0.5895 T0.3812,0.591 T0.3779,0.5925 Q0.3739,0.5955,0.3633,0.5955 L0.3585,0.5955 L0.3536,0.5955 Q0.3277,0.5955,0.3066,0.6053 Q0.3017,0.6083,0.296,0.6083 Q0.2912,0.6083,0.2839,0.6068 T0.2741,0.6053 Q0.2693,0.6053,0.2547,0.6048 T0.236,0.6044 Q0.2222,0.6044,0.193,0.6103 L0.1882,0.6103 Q0.1825,0.6103,0.1703,0.6083 T0.1525,0.6063 L0.15,0.6063 Q0.1436,0.6063,0.135,0.6073 T0.1168,0.6098 T0.1038,0.6113 Q0.0973,0.6123,0.0787,0.6123 L0.06,0.6123 L0.0414,0.6123 L0.0235,0.6123 Q0.0049,0.6133,0.0049,0.6172 L0.0057,0.6222 Q0.0073,0.6261,0.043,0.6261 L0.0454,0.6261 L0.0479,0.6261 Q0.0527,0.6261,0.0633,0.6281 T0.0803,0.6301 L0.0835,0.6301 L0.0916,0.6301 Q0.0998,0.6301,0.129,0.632 Q0.1768,0.636,0.206,0.64 Q0.2117,0.6409,0.2165,0.6409 Q0.2206,0.6409,0.2263,0.6405 T0.2336,0.64 L0.2384,0.64 L0.2409,0.64 T0.251,0.6395 T0.2644,0.639 Q0.2741,0.639,0.279,0.6409 Q0.283,0.6429,0.2972,0.6439 T0.3293,0.6459 T0.3528,0.6469 L0.3577,0.6469 Q0.3642,0.6469,0.3723,0.6449 T0.3844,0.6429 Q0.3901,0.6429,0.3933,0.6454 T0.4006,0.6479 Q0.4088,0.6479,0.4517,0.6558 Q0.4582,0.6568,0.4623,0.6568 Q0.4655,0.6568,0.4716,0.6563 T0.4826,0.6558 L0.4882,0.6558 Q0.5004,0.6558,0.5255,0.6607 Q0.5328,0.6627,0.5604,0.6647 T0.6152,0.6682 T0.644,0.6696 T0.6719,0.6721 T0.7056,0.6756 Q0.7113,0.6756,0.7198,0.6805 T0.738,0.6855 Q0.7453,0.6855,0.7551,0.6879 T0.7741,0.6939 T0.7912,0.7018 T0.8029,0.7102 Q0.8054,0.7132,0.8054,0.7151 Q0.8054,0.7191,0.8009,0.7216 T0.7908,0.7255 T0.779,0.7285 T0.7721,0.731 Q0.7672,0.7329,0.7632,0.7339 T0.7551,0.7354 T0.7486,0.7369 Q0.7478,0.7369,0.7445,0.7379 T0.7388,0.7394 T0.7324,0.7404 L0.7242,0.7413 T0.7161,0.7418 Q0.7105,0.7418,0.6955,0.7428 T0.6748,0.7438 L0.6699,0.7438 L0.6667,0.7438 Q0.6659,0.7438,0.6407,0.7468 Q0.6367,0.7478,0.6334,0.7478 T0.6245,0.7468 T0.6148,0.7458 L0.6099,0.7458 Q0.6018,0.7468,0.5864,0.7498 T0.5653,0.7532 T0.5596,0.7557 Q0.5596,0.7596,0.571,0.7616 Q0.5742,0.7626,0.5803,0.7631 T0.5929,0.7641 T0.6018,0.7656 L0.6087,0.7656 T0.6192,0.7666 T0.6277,0.7695 Q0.631,0.7705,0.6342,0.7705 Q0.6383,0.7705,0.6452,0.7685 T0.6537,0.7666 L0.6553,0.7666 Q0.6602,0.7666,0.6711,0.769 T0.6853,0.7715 Q0.691,0.7715,0.7048,0.775 T0.7242,0.7814 Q0.7324,0.7854,0.7445,0.7864 Q0.7478,0.7864,0.768,0.7873 T0.7972,0.7893 Q0.7989,0.7893,0.8017,0.7898 T0.8054,0.7903 L0.8082,0.7903 T0.811,0.7908 T0.8131,0.7913 T0.8151,0.7918 L0.8167,0.7928 T0.8179,0.7943 T0.8191,0.7957 T0.8204,0.7977 T0.8208,0.8002 Q0.8208,0.8032,0.8171,0.8051 T0.8074,0.8081 T0.7997,0.8091 Q0.7932,0.8121,0.7737,0.8121 Q0.7664,0.8121,0.7413,0.8111 L0.7364,0.8111 Q0.7275,0.8111,0.7214,0.8121 T0.7084,0.8155 T0.6991,0.819 Q0.6934,0.82,0.6723,0.8205 T0.6472,0.8239 Q0.6423,0.8269,0.6375,0.8269 Q0.6358,0.8269,0.6338,0.8264 T0.6294,0.8254 T0.6253,0.8249 Q0.6042,0.8249,0.5961,0.8289 Q0.5921,0.8318,0.5839,0.8318 Q0.5783,0.8318,0.5645,0.8299 L0.5604,0.8299 Q0.5531,0.8299,0.5337,0.8368 Q0.5296,0.8378,0.5255,0.8378 Q0.5239,0.8378,0.5195,0.8373 T0.5118,0.8368 Q0.5061,0.8368,0.4996,0.8388 Q0.4947,0.8388,0.4882,0.8398 Q0.4777,0.8417,0.4753,0.8427 T0.4728,0.8467 Q0.4728,0.8487,0.4769,0.8492 T0.4886,0.8501 T0.5004,0.8516 Q0.5036,0.8516,0.515,0.8521 T0.5345,0.8546 Q0.5426,0.8556,0.5596,0.8556 L0.5649,0.8556 L0.5702,0.8556 L0.5791,0.8556 Q0.5921,0.8566,0.6135,0.8571 T0.6383,0.8586 Q0.6683,0.8615,0.6764,0.8615 L0.6776,0.8615 L0.6788,0.8615 Q0.7032,0.8615,0.7032,0.8684 Q0.7032,0.8714,0.6995,0.8749 T0.6959,0.8803 L0.6959,0.8813 Q0.6991,0.8843,0.7153,0.8843 Q0.7218,0.8843,0.7259,0.8833 L0.7299,0.8833 Q0.7324,0.8833,0.7348,0.8838 T0.7393,0.8848 T0.7433,0.8863 L0.7474,0.8882 T0.751,0.8892 Q0.7616,0.8922,0.7948,0.8952 Q0.7981,0.8952,0.8082,0.8981 T0.8297,0.9011 T0.8528,0.9026 T0.8747,0.907 T0.8856,0.9149 L0.8856,0.9159 Q0.8856,0.9179,0.8848,0.9189 T0.8828,0.9209 T0.88,0.9228 T0.8767,0.9243 T0.8731,0.9248 T0.8698,0.9253 T0.8666,0.9258 L0.8646,0.9258 Q0.8621,0.9258,0.8577,0.9263 T0.85,0.9268 T0.8439,0.9273 T0.8402,0.9278 Q0.8386,0.9298,0.8358,0.9308 T0.8305,0.9322 T0.8244,0.9332 T0.8183,0.9337 Q0.8135,0.9347,0.8025,0.9372 T0.7859,0.9416 Q0.7778,0.9436,0.7717,0.9471 T0.7624,0.9515 Q0.7575,0.9535,0.7486,0.9535 Q0.7461,0.9535,0.7397,0.953 T0.7291,0.9525 Q0.7234,0.9525,0.7186,0.953 T0.7137,0.957 T0.7194,0.9614 Q0.7567,0.9634,0.7664,0.9654 Q0.7745,0.9674,0.7762,0.9674 L0.7908,0.9674 Q0.7924,0.9664,0.7948,0.9664 Q0.8029,0.9664,0.8082,0.9679 T0.8224,0.9723 T0.8362,0.9773 Q0.8483,0.9812,0.871,0.9812 L0.8751,0.9812 Q0.8873,0.9812,0.8929,0.9822 T0.9165,0.9876 T0.9408,0.9931 Q0.9513,0.996,0.9935,0.996 Z M0.9968,0.998 L0.9976,0.003 Q0.9838,0.002,0.9708,0.0059 Q0.9659,0.0069,0.9327,0.0059 T0.8881,0.0099 Q0.8767,0.0138,0.8658,0.0163 T0.8491,0.0218 Q0.8418,0.0247,0.8341,0.0247 T0.8232,0.0257 Q0.8191,0.0277,0.7997,0.0292 T0.7737,0.0326 Q0.7697,0.0336,0.7672,0.0336 T0.7612,0.0321 T0.7543,0.0307 T0.747,0.0317 Q0.7397,0.0326,0.7336,0.0356 T0.717,0.0406 Q0.6918,0.0445,0.6861,0.0475 Q0.6845,0.0485,0.6772,0.0485 T0.6638,0.05 T0.6577,0.0549 T0.6642,0.0598 T0.6796,0.0613 T0.6902,0.0623 Q0.691,0.0623,0.6946,0.0658 T0.7064,0.0702 L0.7161,0.0702 Q0.7194,0.0702,0.7242,0.0697 T0.7332,0.0692 Q0.7413,0.0692,0.7579,0.0717 T0.781,0.0762 Q0.7867,0.0781,0.8171,0.0786 T0.85,0.0801 L0.8532,0.0801 Q0.8548,0.0801,0.8613,0.0796 T0.8727,0.0791 Q0.8832,0.0791,0.8913,0.0811 Q0.897,0.0831,0.897,0.0851 Q0.897,0.088,0.8873,0.0905 T0.8735,0.093 Q0.871,0.093,0.8447,0.0984 T0.8143,0.1039 Q0.7981,0.1039,0.781,0.1078 Q0.7778,0.1088,0.7745,0.1088 T0.7644,0.1073 T0.7518,0.1048 L0.7453,0.1048 Q0.7299,0.1048,0.7076,0.1083 T0.6788,0.1157 Q0.6748,0.1187,0.6699,0.1187 Q0.6675,0.1187,0.6557,0.1157 T0.6318,0.1118 Q0.6164,0.1108,0.6135,0.1108 T0.6095,0.1113 T0.6067,0.1142 T0.6018,0.1187 Q0.5953,0.1227,0.5921,0.1227 Q0.5904,0.1227,0.5839,0.1212 T0.5693,0.1187 L0.5661,0.1187 Q0.558,0.1187,0.5527,0.1207 T0.5434,0.1246 T0.5337,0.1266 Q0.5126,0.1296,0.4882,0.1296 T0.4371,0.1385 Q0.4298,0.1414,0.425,0.1414 Q0.4225,0.1414,0.4189,0.1395 T0.4112,0.1375 Q0.4079,0.1375,0.4019,0.1395 T0.3869,0.1414 Q0.3682,0.1414,0.3504,0.1474 Q0.3398,0.1513,0.3333,0.1513 Q0.3293,0.1513,0.3228,0.1494 L0.3204,0.1494 Q0.3179,0.1494,0.3155,0.1508 T0.3118,0.1543 T0.3106,0.1592 T0.3127,0.1642 T0.32,0.1677 T0.3305,0.1701 T0.3435,0.1716 T0.3544,0.1721 Q0.3569,0.1731,0.3601,0.1731 Q0.3625,0.1731,0.3686,0.1726 T0.3779,0.1721 Q0.3901,0.1721,0.4023,0.1751 Q0.4177,0.18,0.455,0.18 Q0.4793,0.18,0.4882,0.178 L0.4915,0.178 Q0.4964,0.178,0.5045,0.1815 T0.5158,0.185 Q0.5564,0.1889,0.5645,0.1929 Q0.5677,0.1939,0.571,0.1939 Q0.575,0.1939,0.5807,0.1919 T0.5888,0.1899 T0.5929,0.1909 Q0.5969,0.1949,0.6123,0.1949 Q0.6334,0.1949,0.6456,0.1899 Q0.6488,0.1879,0.6537,0.1879 Q0.661,0.1879,0.6772,0.1929 T0.6983,0.1978 Q0.704,0.1988,0.7206,0.1998 T0.7502,0.2018 T0.7664,0.2038 Q0.7672,0.2038,0.7818,0.2043 T0.8151,0.2067 T0.8459,0.2156 Q0.8573,0.2206,0.8573,0.2255 Q0.8573,0.2295,0.8516,0.2324 T0.8394,0.2369 T0.8281,0.2384 Q0.8208,0.2384,0.7526,0.2473 Q0.7494,0.2483,0.7421,0.2493 T0.7287,0.2512 T0.7178,0.2522 L0.7153,0.2522 L0.7137,0.2522 Q0.7056,0.2522,0.6898,0.2572 T0.6691,0.2671 Q0.6667,0.269,0.6634,0.269 Q0.661,0.269,0.6557,0.2681 T0.6472,0.2671 Q0.6448,0.2671,0.6415,0.2681 Q0.631,0.271,0.6034,0.274 Q0.5994,0.274,0.5961,0.2765 T0.5904,0.2789 L0.5864,0.2789 Q0.5831,0.2779,0.5742,0.2779 Q0.541,0.2779,0.5223,0.2849 Q0.5126,0.2888,0.5028,0.2888 Q0.5004,0.2888,0.4943,0.2883 T0.4842,0.2878 Q0.4704,0.2878,0.4627,0.2898 T0.4509,0.2987 Q0.4501,0.2997,0.4501,0.3007 Q0.4501,0.3046,0.4574,0.3066 T0.4765,0.3091 T0.4899,0.3096 Q0.4931,0.3106,0.5061,0.3121 T0.5316,0.315 T0.5499,0.3175 Q0.5507,0.3175,0.5572,0.3195 T0.5685,0.3225 T0.5803,0.3254 T0.5933,0.3279 T0.605,0.3284 T0.6148,0.3274 Q0.6221,0.3244,0.6294,0.3244 Q0.6399,0.3244,0.6496,0.3294 Q0.6529,0.3314,0.6577,0.3314 Q0.6602,0.3314,0.6663,0.3304 T0.6756,0.3294 T0.6813,0.3304 Q0.6837,0.3304,0.6873,0.3309 L0.6946,0.3318 T0.7024,0.3323 T0.7105,0.3328 T0.7178,0.3338 T0.7238,0.3348 T0.7283,0.3363 Q0.7324,0.3383,0.7482,0.3403 T0.7689,0.3442 Q0.7697,0.3442,0.779,0.3477 T0.794,0.3536 T0.8074,0.3615 T0.8183,0.3729 Q0.8232,0.3808,0.8232,0.3848 Q0.8232,0.3867,0.8216,0.3892 T0.8151,0.3961 T0.8062,0.406 T0.7997,0.4179 T0.7952,0.4263 T0.7855,0.4308 T0.7616,0.4362 Q0.7543,0.4372,0.7445,0.4372 L0.7372,0.4372 L0.7307,0.4372 Q0.7202,0.4372,0.7153,0.4382 Q0.7064,0.4402,0.691,0.4402 L0.6845,0.4402 L0.6788,0.4402 Q0.6699,0.4402,0.6533,0.4436 T0.6318,0.451 Q0.6302,0.451,0.6302,0.452 Q0.6302,0.457,0.6626,0.459 Q0.6869,0.4609,0.7015,0.4659 Q0.7064,0.4669,0.7202,0.4669 L0.7311,0.4669 L0.7429,0.4669 Q0.7599,0.4669,0.7689,0.4688 Q0.7794,0.4708,0.7794,0.4738 L0.7786,0.4748 T0.777,0.4763 T0.7749,0.4773 L0.7737,0.4777 L0.7729,0.4777 Q0.7502,0.4797,0.738,0.4797 L0.7315,0.4797 L0.7291,0.4797 Q0.7226,0.4797,0.6869,0.4876 Q0.6821,0.4886,0.6768,0.4891 T0.6614,0.4901 T0.6456,0.4916 L0.6342,0.4916 Q0.6294,0.4916,0.6217,0.4911 T0.6115,0.4906 Q0.6083,0.4906,0.6054,0.4911 T0.5961,0.4936 T0.5815,0.4965 T0.5629,0.4985 Q0.5507,0.4985,0.5442,0.4995 T0.5345,0.5025 T0.5304,0.5084 L0.5296,0.5094 Q0.5296,0.5114,0.5316,0.5124 T0.5373,0.5138 T0.5442,0.5143 L0.5523,0.5143 L0.5604,0.5143 Q0.5637,0.5153,0.5677,0.5158 T0.5742,0.5173 T0.5811,0.5188 T0.5904,0.5193 L0.5977,0.5193 L0.6026,0.5193 Q0.6107,0.5193,0.6168,0.5208 T0.6294,0.5252 T0.6399,0.5292 Q0.6448,0.5302,0.6679,0.5341 T0.6983,0.541 Q0.7129,0.545,0.7664,0.55 Q0.7883,0.5519,0.7883,0.5559 Q0.7883,0.5579,0.7843,0.5608 T0.7721,0.5638 Q0.7697,0.5638,0.764,0.5633 T0.7559,0.5628 L0.7502,0.5628 Q0.7364,0.5648,0.6869,0.5648 L0.6699,0.5648 L0.6675,0.5648 Q0.665,0.5648,0.6602,0.5658 T0.6513,0.5668 Q0.6464,0.5668,0.6399,0.5658 Q0.6342,0.5648,0.6302,0.5648 T0.6225,0.5668 T0.6156,0.5697 Q0.6107,0.5707,0.6087,0.5707 T0.6034,0.5712 T0.5953,0.5717 Q0.5904,0.5727,0.5835,0.5727 T0.5718,0.5732 T0.562,0.5757 Q0.558,0.5767,0.5483,0.5767 L0.5349,0.5767 L0.5207,0.5767 Q0.5069,0.5767,0.5028,0.5786 Q0.4972,0.5816,0.4899,0.5816 Q0.4858,0.5816,0.4781,0.5806 T0.4663,0.5796 L0.4631,0.5796 Q0.4582,0.5796,0.4432,0.5821 T0.4144,0.5846 L0.4071,0.5846 L0.4011,0.5846 T0.3958,0.5851 T0.3921,0.5856 T0.3889,0.5861 T0.3865,0.5865 T0.384,0.5875 L0.382,0.5885 L0.3796,0.5895 L0.3771,0.5905 Q0.3731,0.5925,0.365,0.5925 L0.3601,0.5925 L0.3552,0.5925 Q0.3277,0.5925,0.3049,0.6044 Q0.3017,0.6053,0.296,0.6053 Q0.292,0.6053,0.2847,0.6044 T0.2741,0.6034 Q0.2693,0.6034,0.2547,0.6029 T0.236,0.6024 Q0.2222,0.6024,0.1922,0.6073 Q0.1906,0.6083,0.1882,0.6083 Q0.1833,0.6083,0.1711,0.6063 T0.1533,0.6044 Q0.1517,0.6034,0.1492,0.6034 Q0.1436,0.6034,0.135,0.6048 T0.1168,0.6078 T0.103,0.6093 Q0.0973,0.6103,0.0787,0.6103 L0.0604,0.6103 L0.0414,0.6103 L0.0235,0.6103 Q0.0024,0.6113,0.0024,0.6172 Q0.0024,0.6192,0.0032,0.6231 Q0.0049,0.6281,0.0333,0.6281 L0.0414,0.6281 L0.0479,0.6281 Q0.0519,0.6281,0.0624,0.6301 T0.0803,0.632 L0.0835,0.632 L0.0916,0.632 Q0.0989,0.632,0.1281,0.634 L0.206,0.6419 Q0.2117,0.6429,0.2174,0.6429 Q0.2214,0.6429,0.2271,0.6424 T0.2344,0.6419 L0.2376,0.6419 Q0.2393,0.6429,0.2417,0.6429 T0.2526,0.6419 T0.2652,0.6409 Q0.2741,0.6409,0.2782,0.6429 T0.2968,0.6459 T0.3293,0.6479 T0.352,0.6489 Q0.3552,0.6499,0.3585,0.6499 Q0.365,0.6499,0.3731,0.6474 T0.3844,0.6449 Q0.3885,0.6449,0.3917,0.6474 T0.4006,0.6499 Q0.4088,0.6499,0.4509,0.6578 Q0.4574,0.6588,0.4631,0.6588 Q0.4655,0.6588,0.4716,0.6583 T0.4834,0.6578 L0.4874,0.6578 Q0.5004,0.6588,0.5255,0.6637 Q0.532,0.6647,0.56,0.6667 T0.6148,0.6701 T0.644,0.6716 Q0.6464,0.6726,0.6594,0.6736 T0.6861,0.6756 T0.7048,0.6775 Q0.7097,0.6775,0.7186,0.6825 T0.738,0.6874 Q0.7518,0.6874,0.7729,0.6958 T0.8005,0.7122 Q0.8029,0.7141,0.8029,0.7151 Q0.8029,0.7191,0.7968,0.7216 T0.7818,0.726 T0.7705,0.729 L0.764,0.731 T0.7583,0.7324 T0.753,0.7334 T0.7486,0.7349 Q0.7251,0.7399,0.7161,0.7399 Q0.7097,0.7399,0.6951,0.7409 T0.6748,0.7418 L0.6703,0.7418 L0.6667,0.7418 Q0.6659,0.7418,0.6399,0.7448 L0.6334,0.7448 Q0.631,0.7448,0.6249,0.7443 T0.6148,0.7438 L0.6099,0.7438 Q0.601,0.7448,0.5856,0.7473 T0.5645,0.7507 Q0.5572,0.7517,0.5572,0.7547 T0.5616,0.7606 T0.5702,0.7646 Q0.5734,0.7646,0.5779,0.7651 T0.5864,0.7661 T0.5949,0.7671 T0.6018,0.7676 L0.6071,0.7676 T0.6135,0.7681 T0.62,0.769 T0.6269,0.771 T0.6342,0.7725 Q0.6391,0.7725,0.6464,0.7705 T0.6541,0.7685 L0.6553,0.7685 Q0.6586,0.7685,0.6699,0.771 T0.6853,0.7735 Q0.6902,0.7735,0.704,0.777 T0.7226,0.7834 Q0.7315,0.7873,0.7445,0.7883 Q0.747,0.7883,0.7676,0.7893 T0.7972,0.7913 Q0.7989,0.7913,0.8017,0.7918 T0.8058,0.7923 T0.809,0.7928 T0.8114,0.7933 T0.8131,0.7938 T0.8147,0.7943 T0.8159,0.7953 L0.8171,0.7967 T0.8175,0.7982 Q0.8183,0.7992,0.8183,0.8002 Q0.8183,0.8022,0.8155,0.8032 T0.807,0.8051 T0.7989,0.8071 Q0.7932,0.8091,0.7737,0.8091 L0.7413,0.8091 L0.7372,0.8091 Q0.7275,0.8091,0.721,0.8101 T0.7072,0.8136 T0.6983,0.817 Q0.6942,0.818,0.6825,0.818 T0.6602,0.819 T0.6456,0.822 Q0.6415,0.8249,0.6375,0.8249 Q0.6367,0.8249,0.6326,0.8239 T0.6253,0.8229 Q0.6034,0.8229,0.5953,0.8279 Q0.5912,0.8289,0.5848,0.8289 Q0.5775,0.8289,0.5645,0.8279 Q0.5629,0.8269,0.5604,0.8269 Q0.5523,0.8269,0.5328,0.8348 Q0.5296,0.8358,0.5264,0.8358 Q0.5247,0.8358,0.5203,0.8353 T0.5126,0.8348 Q0.5061,0.8348,0.4988,0.8358 Q0.4947,0.8368,0.4882,0.8378 Q0.4826,0.8388,0.4797,0.8388 T0.474,0.8398 T0.4704,0.8422 T0.4696,0.8462 T0.472,0.8501 T0.4781,0.8521 T0.4854,0.8526 L0.4935,0.8526 T0.4996,0.8536 Q0.5036,0.8536,0.515,0.8546 T0.5337,0.8566 Q0.5426,0.8576,0.5596,0.8576 L0.5657,0.8576 L0.571,0.8576 L0.5791,0.8576 Q0.5921,0.8586,0.6135,0.8591 T0.6383,0.8605 Q0.6683,0.8635,0.6764,0.8635 Q0.6999,0.8635,0.6999,0.8684 Q0.6999,0.8694,0.6967,0.8734 T0.6934,0.8803 L0.6934,0.8823 Q0.6951,0.8863,0.7137,0.8863 Q0.7218,0.8863,0.7267,0.8853 L0.7299,0.8853 Q0.734,0.8853,0.7409,0.8882 T0.7502,0.8912 Q0.7607,0.8942,0.794,0.8971 Q0.7972,0.8971,0.8078,0.9001 T0.8297,0.9031 Q0.8475,0.9031,0.8646,0.9065 T0.8824,0.9149 L0.8824,0.9159 Q0.8824,0.9169,0.882,0.9179 T0.8808,0.9194 L0.8792,0.9204 T0.8767,0.9214 T0.8743,0.9224 T0.8715,0.9228 L0.8686,0.9228 T0.8662,0.9233 T0.8637,0.9238 L0.8556,0.9238 T0.8447,0.9243 T0.8386,0.9268 Q0.837,0.9278,0.835,0.9288 T0.8305,0.9303 T0.8244,0.9308 T0.8175,0.9318 Q0.8127,0.9327,0.8017,0.9352 T0.7851,0.9397 Q0.777,0.9416,0.7705,0.9451 T0.7616,0.9496 Q0.7575,0.9505,0.7494,0.9505 L0.7405,0.9505 L0.7307,0.9505 Q0.7234,0.9505,0.7178,0.9515 Q0.7145,0.9515,0.7125,0.9535 T0.7105,0.9575 Q0.7105,0.9624,0.7194,0.9634 Q0.7567,0.9654,0.7664,0.9674 Q0.7745,0.9693,0.7762,0.9693 L0.7908,0.9693 L0.7948,0.9693 Q0.8005,0.9693,0.8058,0.9698 T0.8143,0.9718 T0.824,0.9753 T0.8354,0.9792 Q0.8483,0.9832,0.871,0.9832 L0.8739,0.9832 L0.8767,0.9832 Q0.8873,0.9832,0.8921,0.9842 Q0.8978,0.9852,0.9157,0.9896 T0.94,0.996 Q0.9424,0.996,0.9469,0.9965 T0.9566,0.997 T0.9676,0.9975 T0.9781,0.998 L0.9874,0.998 L0.9943,0.998 L0.9968,0.998 Z",type:0}},"\uE0CC":{type:5,data:{d:"M1,0.5071 L0.8623,0.7102 L0.5865,0.7102 L0.4489,0.5071 L0.5865,0.3045 L0.8623,0.3045 Z M0.4135,0.7974 L0.2758,1 L0,1 L0,0.5944 L0.2758,0.5944 Z M0.4135,0.2026 L0.2758,0.4056 L0,0.4056 L0,0 L0.2758,0 Z",type:0}},"\uE0CD":{type:5,data:{d:"M0.9726,0.5071 L0.8451,0.3184 L0.59,0.3184 L0.4624,0.5071 L0.59,0.6955 L0.8451,0.6955 Z M1,0.5071 L0.8588,0.716 L0.5768,0.716 L0.4355,0.5071 L0.5768,0.2982 L0.8588,0.2982 Z M0.401,0.7911 L0.2735,0.6028 L0.0236,0.6028 L0.0236,0.9799 L0.2735,0.9799 Z M0.4284,0.7911 L0.2872,1 L0,1 L0,0.5826 L0.2872,0.5826 Z M0.401,0.2089 L0.2735,0.0201 L0.0236,0.0201 L0.0236,0.3972 L0.2735,0.3972 Z M0.4284,0.2089 L0.2872,0.4174 L0,0.4174 L0,0 L0.2872,0 Z",type:0}},"\uE0CE":{type:5,data:{d:"M1,0.4996 Q1,0.5365,0.9924,0.568 T0.9718,0.6177 T0.9433,0.6363 L0.9425,0.6367 Q0.9355,0.6367,0.9285,0.6326 L0.9308,0.6326 Q0.9129,0.6195,0.9013,0.587 T0.8871,0.5164 T0.8943,0.4373 T0.9254,0.3687 L0.9254,0.3691 Q0.9339,0.3628,0.9425,0.3628 Q0.9584,0.3628,0.9716,0.3811 T0.9924,0.431 T1,0.4996 Z M0.9234,0.6321 L0.8776,0.7169 Q0.8791,0.732,0.8791,0.7441 Q0.8791,0.781,0.8716,0.8123 T0.8508,0.862 T0.8224,0.8809 L0.8216,0.8809 Q0.8154,0.8809,0.8092,0.8775 L0.8088,0.8775 Q0.7905,0.8633,0.7792,0.8297 T0.766,0.7571 T0.775,0.6768 T0.8088,0.6086 L0.7431,0.6065 Q0.719,0.6313,0.7072,0.6705 T0.6965,0.7487 T0.7097,0.823 T0.7423,0.8763 L0.7913,0.8775 L0.7314,0.9895 L0.4648,0.5038 L0.7299,0.0176 L0.7882,0.1229 L0.7404,0.1212 Q0.7244,0.138,0.7136,0.1617 T0.6982,0.2108 T0.6943,0.2632 T0.7003,0.3144 T0.7159,0.3586 T0.7396,0.3909 L0.8072,0.3926 Q0.7894,0.3796,0.7777,0.3479 T0.7635,0.2789 T0.7697,0.2011 T0.7987,0.1326 Q0.8092,0.1225,0.8204,0.1225 Q0.8321,0.1225,0.8426,0.1332 T0.8609,0.1623 T0.8731,0.2062 T0.8776,0.2592 Q0.8776,0.268,0.8768,0.2815 L0.9219,0.3628 L0.8644,0.3612 Q0.8403,0.3855,0.8284,0.4247 T0.8177,0.5029 T0.8309,0.5772 T0.8632,0.6305 Z M0.6759,0.6384 Q0.6576,0.625,0.6457,0.5914 T0.6319,0.5185 T0.6407,0.4375 T0.6751,0.3691 L0.609,0.367 Q0.5849,0.3918,0.5731,0.431 T0.5626,0.509 T0.5758,0.5833 T0.6082,0.6367 Z M0.6459,0.0025 L0.391,0.4551 L0.0035,0.4551 L0.246,0 Z M0.6553,1 L0.2421,1 L0,0.5449 L0.3875,0.5449 Z",type:0}},"\uE0CF":{type:5,data:{d:"M0.4996,0 Q0.5562,0,0.5961,0.0166 T0.6364,0.0566 L0.6368,0.0575 Q0.6368,0.0646,0.6324,0.0713 L0.6324,0.0692 Q0.6196,0.0868,0.5872,0.0986 T0.5166,0.1128 T0.4373,0.1057 T0.3689,0.0747 L0.3694,0.0747 Q0.3627,0.0663,0.3627,0.0575 Q0.3627,0.0336,0.4028,0.0168 T0.4996,0 Z M0.6324,0.0768 L0.717,0.1225 Q0.7321,0.1212,0.744,0.1212 Q0.8007,0.1212,0.8406,0.1378 T0.8809,0.1779 L0.8813,0.1783 Q0.8813,0.1846,0.8778,0.1909 Q0.8636,0.2093,0.8299,0.2208 T0.7573,0.2343 T0.6769,0.2253 T0.6089,0.1913 L0.6067,0.2567 Q0.6315,0.281,0.6707,0.2928 T0.7489,0.3035 T0.8231,0.2903 T0.8764,0.258 L0.8778,0.2085 L0.9898,0.2685 L0.504,0.5352 L0.0177,0.2701 L0.1227,0.2118 L0.1213,0.2596 Q0.1457,0.2836,0.1851,0.2955 T0.2635,0.3062 T0.3377,0.293 T0.3911,0.2605 L0.3928,0.193 Q0.38,0.2106,0.3483,0.2221 T0.2792,0.2364 T0.2013,0.2303 T0.1324,0.2013 Q0.1222,0.1909,0.1222,0.1795 Q0.1222,0.156,0.1623,0.1393 T0.2591,0.1225 Q0.2679,0.1225,0.2817,0.1233 L0.3627,0.078 L0.3609,0.1359 Q0.3857,0.1598,0.4249,0.1718 T0.5031,0.1825 T0.5773,0.1693 T0.6306,0.1367 Z M0.6386,0.3242 Q0.6253,0.3427,0.5917,0.3544 T0.5186,0.3683 T0.4376,0.3595 T0.3689,0.3251 L0.3671,0.3909 Q0.3915,0.4148,0.4309,0.4268 T0.5093,0.4375 T0.5835,0.4243 T0.6368,0.3918 Z M0.0027,0.354 L0.4553,0.6091 L0.4553,0.9962 L0,0.7542 Z M1,0.3448 L1,0.7576 L0.5447,1 L0.5447,0.6128 Z",type:0}},"\uE0D0":{type:5,data:{d:"M0,0.7699 Q0,0.7172,0.0277,0.6731 T0.103,0.6032 T0.2066,0.5774 Q0.2487,0.5774,0.2865,0.5922 T0.3526,0.6339 L0.3935,0.6715 Q0.4218,0.6984,0.438,0.7336 T0.4541,0.8075 Q0.4541,0.8602,0.4264,0.9046 T0.3511,0.9745 T0.2475,1 Q0.2054,1,0.1676,0.9852 T0.1016,0.9435 L0.0606,0.9059 Q0.0317,0.879,0.0159,0.8438 T0,0.7699 Z M0.5459,0.7699 Q0.5459,0.7172,0.5736,0.6731 T0.6489,0.6032 T0.7525,0.5774 Q0.7946,0.5774,0.8324,0.5922 T0.8984,0.6339 L0.9394,0.6715 Q0.9579,0.6892,0.9714,0.7105 T0.9925,0.7567 T1,0.8075 Q1,0.8602,0.9723,0.9046 T0.897,0.9745 T0.7934,1 Q0.7513,1,0.7135,0.9852 T0.6474,0.9435 L0.6065,0.9059 Q0.5776,0.879,0.5617,0.8438 T0.5459,0.7699 Z M0,0.1925 Q0,0.1398,0.0277,0.0957 T0.103,0.0258 T0.2066,0 Q0.2487,0,0.2865,0.0148 T0.3526,0.0565 L0.3935,0.0941 Q0.4218,0.121,0.438,0.1562 T0.4541,0.2301 Q0.4541,0.2828,0.4264,0.3269 T0.3511,0.3968 T0.2475,0.4226 Q0.2054,0.4226,0.1676,0.4078 T0.1016,0.3661 L0.0606,0.3285 Q0.0317,0.3016,0.0159,0.2664 T0,0.1925 Z M0.5459,0.1925 Q0.5459,0.1398,0.5736,0.0957 T0.6489,0.0258 T0.7525,0 Q0.7946,0,0.8324,0.0148 T0.8984,0.0565 L0.9394,0.0941 Q0.9677,0.121,0.9838,0.1562 T1,0.2301 Q1,0.2828,0.9723,0.3269 T0.897,0.3968 T0.7934,0.4226 Q0.7513,0.4226,0.7135,0.4078 T0.6474,0.3661 L0.6065,0.3285 Q0.5874,0.3108,0.5741,0.2895 T0.5534,0.2433 T0.5459,0.1925 Z M0.3301,0.6403 Q0.3306,0.6403,0.3312,0.6409 L0.3272,0.6371 L0.3272,0.6371 Z M0.8759,0.6403 Q0.8765,0.6403,0.8771,0.6409 Q0.8748,0.6387,0.8725,0.6371 L0.8731,0.6371 Z M0.3301,0.0629 Q0.3306,0.0629,0.3312,0.0634 L0.3272,0.0597 L0.3272,0.0602 Z M0.8759,0.0629 Q0.8765,0.0629,0.8771,0.0634 Q0.8748,0.0613,0.8725,0.0597 Q0.8731,0.0597,0.8731,0.0602 Z M0.779,0.4113 L0.7501,0.3849 Q0.7046,0.3844,0.6636,0.3661 Q0.7132,0.407,0.779,0.4113 Z M0.7957,0.4118 Q0.8477,0.4113,0.8918,0.3874 T0.9619,0.3223 T0.9885,0.2323 L0.9585,0.2048 Q0.9538,0.2769,0.8987,0.3282 T0.7657,0.3844 Z M0.9879,0.2167 Q0.9827,0.1554,0.9388,0.1097 Q0.9585,0.1478,0.959,0.1898 Z M0.2331,0.4113 L0.2043,0.3849 Q0.1587,0.3844,0.1177,0.3661 Q0.1673,0.407,0.2331,0.4113 Z M0.2499,0.4118 Q0.2885,0.4118,0.324,0.3973 T0.3852,0.3589 T0.4264,0.3019 T0.4426,0.2323 L0.4126,0.2048 Q0.408,0.2769,0.3529,0.3282 T0.2198,0.3844 Z M0.442,0.2167 Q0.4368,0.1554,0.393,0.1097 Q0.4126,0.1478,0.4132,0.1903 Z M0.779,0.9887 L0.7501,0.9624 Q0.7046,0.9618,0.6636,0.9435 Q0.7132,0.9844,0.779,0.9887 Z M0.7957,0.9892 Q0.8477,0.9887,0.8918,0.9648 T0.9619,0.8997 T0.9885,0.8097 L0.9585,0.7823 Q0.9538,0.8543,0.8987,0.9056 T0.7657,0.9618 Z M0.9879,0.7941 Q0.9827,0.7328,0.9388,0.6871 Q0.9585,0.7253,0.959,0.7672 Z M0.2331,0.9887 L0.2043,0.9624 Q0.1587,0.9618,0.1177,0.9435 Q0.1673,0.9844,0.2331,0.9887 Z M0.2499,0.9892 Q0.3018,0.9887,0.3459,0.9648 T0.416,0.8997 T0.4426,0.8102 L0.4126,0.7823 Q0.408,0.8543,0.3529,0.9056 T0.2198,0.9618 Z M0.442,0.7941 Q0.4368,0.7328,0.393,0.6871 Q0.4126,0.7253,0.4132,0.7672 Z M0.3191,0.6452 Q0.2943,0.622,0.2643,0.6091 T0.2066,0.5962 Q0.1795,0.5962,0.1497,0.6091 T0.0949,0.6441 T0.0537,0.6995 T0.0375,0.7699 Q0.0375,0.8419,0.0941,0.8941 Q0.1189,0.9172,0.1489,0.9304 T0.2066,0.9435 Q0.2337,0.9435,0.2634,0.9304 T0.3182,0.8952 T0.3595,0.8398 T0.3756,0.7699 Q0.3756,0.6978,0.3191,0.6452 Z M0.026,0.7731 L0.026,0.7699 Q0.026,0.6935,0.0854,0.6376 Q0.0658,0.6538,0.0514,0.6739 T0.0289,0.718 T0.0202,0.7677 Z M0.0271,0.7892 L0.0208,0.7833 Q0.0254,0.8419,0.0681,0.886 Q0.0664,0.8828,0.0646,0.879 Q0.0329,0.8382,0.0271,0.7892 Z M0.3866,0.7823 Q0.382,0.8511,0.3278,0.9016 Q0.3843,0.8565,0.3918,0.7871 Z M0.3872,0.7677 L0.393,0.7726 L0.393,0.7699 Q0.393,0.7349,0.3791,0.7038 Q0.3739,0.6973,0.3681,0.6914 Q0.3866,0.7274,0.3872,0.7677 Z M0.865,0.6452 Q0.8402,0.622,0.8102,0.6091 T0.7525,0.5962 Q0.7305,0.5962,0.7072,0.6043 T0.6619,0.628 T0.6223,0.6642 T0.5941,0.7124 T0.5834,0.7699 Q0.5834,0.8419,0.6399,0.8941 Q0.6561,0.9097,0.6757,0.9207 T0.7149,0.9376 T0.7525,0.9435 Q0.7796,0.9435,0.8093,0.9304 T0.8641,0.8952 T0.9054,0.8398 T0.9215,0.7699 Q0.9215,0.6978,0.865,0.6452 Z M0.5718,0.7731 L0.5718,0.7699 Q0.5718,0.6935,0.6313,0.6376 Q0.6013,0.6618,0.584,0.6954 T0.5661,0.7677 Z M0.573,0.7892 L0.5666,0.7833 Q0.5713,0.8419,0.614,0.886 Q0.6122,0.8828,0.6105,0.879 Q0.5788,0.8382,0.573,0.7892 Z M0.9325,0.7823 Q0.9279,0.8511,0.8736,0.9016 Q0.9308,0.8565,0.9377,0.7871 Z M0.9331,0.7672 L0.9388,0.7726 L0.9388,0.7699 Q0.9388,0.7349,0.925,0.7038 Q0.9198,0.6973,0.914,0.6914 Q0.9325,0.7274,0.9331,0.7672 Z M0.3191,0.0677 Q0.2943,0.0446,0.2643,0.0317 T0.2066,0.0188 Q0.1795,0.0188,0.1497,0.0317 T0.0949,0.0667 T0.0537,0.122 T0.0375,0.1925 Q0.0375,0.2645,0.0941,0.3167 Q0.1189,0.3398,0.1489,0.353 T0.2066,0.3661 Q0.2337,0.3661,0.2634,0.353 T0.3182,0.3177 T0.3595,0.2624 T0.3756,0.1925 Q0.3756,0.1204,0.3191,0.0677 Z M0.026,0.1957 L0.026,0.1925 Q0.026,0.1161,0.0854,0.0602 Q0.0554,0.0844,0.0381,0.118 T0.0202,0.1903 Z M0.0271,0.2118 L0.0208,0.2059 Q0.0254,0.2645,0.0681,0.3086 Q0.0664,0.3054,0.0646,0.3016 Q0.0329,0.2608,0.0271,0.2118 Z M0.3866,0.2048 Q0.382,0.2737,0.3278,0.3242 Q0.3849,0.279,0.3918,0.2097 Z M0.3872,0.1903 L0.393,0.1952 L0.393,0.1925 Q0.393,0.1581,0.3791,0.1263 Q0.3739,0.1199,0.3681,0.114 Q0.3866,0.15,0.3872,0.1903 Z M0.865,0.0677 Q0.8402,0.0446,0.8102,0.0317 T0.7525,0.0188 Q0.7253,0.0188,0.6956,0.0317 T0.6408,0.0667 T0.5995,0.122 T0.5834,0.1925 Q0.5834,0.2645,0.6399,0.3167 Q0.6647,0.3398,0.6947,0.353 T0.7525,0.3661 Q0.7796,0.3661,0.8093,0.353 T0.8641,0.3177 T0.9054,0.2624 T0.9215,0.1925 Q0.9215,0.1204,0.865,0.0677 Z M0.5718,0.1957 L0.5718,0.1925 Q0.5718,0.1161,0.6313,0.0602 Q0.6013,0.0844,0.584,0.118 T0.5661,0.1903 Z M0.573,0.2118 L0.5666,0.2059 Q0.5713,0.2645,0.614,0.3086 Q0.6122,0.3054,0.6105,0.3016 Q0.5788,0.2608,0.573,0.2118 Z M0.9325,0.2048 Q0.9279,0.2737,0.8736,0.3242 Q0.9308,0.279,0.9377,0.2097 Z M0.9331,0.1898 L0.9388,0.1952 L0.9388,0.1925 Q0.9388,0.1575,0.925,0.1263 Q0.9198,0.1199,0.914,0.114 Q0.9325,0.15,0.9331,0.1898 Z",type:0}},"\uE0D1":{type:5,data:{d:"M0.7639,0.1974 L0.9593,0.1974 L0.9593,0.1443 L0.7639,0.1443 L0.7639,0.1974 Z M0.7586,0.109 L0.9727,0.109 Q0.9839,0.109,0.992,0.1153 T1,0.1291 L1,0.382 Q1,0.3906,0.9912,0.3964 T0.9727,0.4021 L0.7586,0.4021 Q0.7468,0.4021,0.739,0.3956 T0.7313,0.382 L0.7313,0.1291 Q0.7313,0.1246,0.7339,0.1207 T0.7406,0.1143 T0.7495,0.1104 T0.7586,0.109 Z M0.7639,0.6575 L0.9593,0.6575 L0.9593,0.6044 L0.7639,0.6044 L0.7639,0.6575 Z M0.7313,0.8347 L0.7313,0.5843 Q0.7313,0.5757,0.7401,0.5699 T0.7586,0.5641 L0.9706,0.5641 Q0.9818,0.5641,0.9898,0.5705 T0.9979,0.5843 L0.9979,0.8347 Q0.9979,0.8433,0.989,0.8491 T0.9706,0.8549 L0.7586,0.8549 Q0.7468,0.8549,0.739,0.8483 T0.7313,0.8347 Z M0,1 L0,0 L0.7607,0 L0.7607,1 L0,1 Z",type:0}},"\uE0D2":{type:5,data:{d:"M0.9764,0.0029 L0.3179,0.4552 L0,0.4552 L0,0 Z M1,1 L0,1 L0,0.5448 L0.3091,0.5448 Z",type:0}},"\uE0D4":{type:5,data:{d:"M0.0236,0.0029 L0.6824,0.4552 L1,0.4552 L1,0 Z M0,1 L1,1 L1,0.5448 L0.6912,0.5448 Z",type:0}},"\uEE00":{type:3,data:"M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z"},"\uEE01":{type:3,data:"M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z"},"\uEE02":{type:3,data:"M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z"},"\uEE03":{type:3,data:"M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z M.25,.15 L1,.15 L1,.85 L.25,.85 Z"},"\uEE04":{type:3,data:"M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z M0,.15 L1,.15 L1,.85 L0,.85 Z"},"\uEE05":{type:3,data:"M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z M0,.15 L.75,.15 L.75,.85 L0,.85 Z"},"\uEE06":{type:5,data:{d:"M0.6574,0.303 Q0.623,0.291,0.5852,0.285 T0.5082,0.279 T0.4311,0.285 T0.359,0.303 L0.3082,0.248 Q0.3525,0.232,0.4041,0.2235 T0.5082,0.215 T0.6123,0.2235 T0.7082,0.248 Z",type:0},scaleType:1},"\uEE07":{type:5,data:{d:"M0.8557,0.582 L0.7656,0.551 Q0.7852,0.53,0.7951,0.507 T0.8049,0.46 Q0.8049,0.424,0.782,0.3905 T0.718,0.332 T0.6221,0.293 T0.5082,0.279 L0.5082,0.215 Q0.5607,0.215,0.6123,0.2235 T0.709,0.248 T0.7918,0.287 T0.8557,0.3375 T0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.582 Z",type:0},scaleType:1},"\uEE08":{type:5,data:{d:"M0.5082,0.705 Q0.482,0.705,0.4557,0.703 T0.4049,0.697 L0.4311,0.635 Q0.4508,0.638,0.4697,0.6395 T0.5082,0.641 Q0.5672,0.641,0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 Q0.8049,0.436,0.7951,0.413 T0.7656,0.369 L0.8557,0.338 Q0.882,0.365,0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z",type:0},scaleType:1},"\uEE09":{type:5,data:{d:"M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 L0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 T0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 L0.9098,0.46 Q0.9098,0.492,0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z",type:0},scaleType:1},"\uEE0A":{type:5,data:{d:"M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.338 L0.2508,0.369 Q0.2311,0.39,0.2213,0.413 T0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 Q0.5279,0.641,0.5467,0.6395 T0.5852,0.635 L0.6115,0.697 Q0.5869,0.701,0.5607,0.703 T0.5082,0.705 Z",type:0},scaleType:1},"\uEE0B":{type:5,data:{d:"M0.1607,0.582 Q0.1344,0.555,0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.3375 T0.2246,0.287 T0.3074,0.248 T0.4041,0.2235 T0.5082,0.215 L0.5082,0.279 Q0.4492,0.279,0.3943,0.293 T0.2984,0.332 T0.2344,0.3905 T0.2115,0.46 Q0.2115,0.484,0.2213,0.507 T0.2508,0.551 Z",type:0},scaleType:1},"\uF5D0":i2.LINE_H,"\uF5D1":i2.LINE_V,"\uF5D2":i2.FADE_RIGHT,"\uF5D3":i2.FADE_LEFT,"\uF5D4":i2.FADE_DOWN,"\uF5D5":i2.FADE_UP,"\uF5D6":i2.CURVE_DOWN_RIGHT,"\uF5D7":i2.CURVE_DOWN_LEFT,"\uF5D8":i2.CURVE_UP_RIGHT,"\uF5D9":i2.CURVE_UP_LEFT,"\uF5DA":[i2.LINE_V,i2.CURVE_UP_RIGHT],"\uF5DB":[i2.LINE_V,i2.CURVE_DOWN_RIGHT],"\uF5DC":[i2.CURVE_DOWN_RIGHT,i2.CURVE_UP_RIGHT],"\uF5DD":[i2.LINE_V,i2.CURVE_UP_LEFT],"\uF5DE":[i2.LINE_V,i2.CURVE_DOWN_LEFT],"\uF5DF":[i2.CURVE_DOWN_LEFT,i2.CURVE_UP_LEFT],"\uF5E0":[i2.LINE_H,i2.CURVE_DOWN_LEFT],"\uF5E1":[i2.LINE_H,i2.CURVE_DOWN_RIGHT],"\uF5E2":[i2.CURVE_DOWN_LEFT,i2.CURVE_DOWN_RIGHT],"\uF5E3":[i2.LINE_H,i2.CURVE_UP_LEFT],"\uF5E4":[i2.LINE_H,i2.CURVE_UP_RIGHT],"\uF5E5":[i2.CURVE_UP_LEFT,i2.CURVE_UP_RIGHT],"\uF5E6":[i2.LINE_V,i2.CURVE_UP_LEFT,i2.CURVE_UP_RIGHT],"\uF5E7":[i2.LINE_V,i2.CURVE_DOWN_LEFT,i2.CURVE_DOWN_RIGHT],"\uF5E8":[i2.LINE_H,i2.CURVE_DOWN_LEFT,i2.CURVE_UP_LEFT],"\uF5E9":[i2.LINE_H,i2.CURVE_DOWN_RIGHT,i2.CURVE_UP_RIGHT],"\uF5EA":[i2.LINE_V,i2.CURVE_DOWN_RIGHT,i2.CURVE_UP_LEFT],"\uF5EB":[i2.LINE_V,i2.CURVE_DOWN_LEFT,i2.CURVE_UP_RIGHT],"\uF5EC":[i2.LINE_H,i2.CURVE_DOWN_RIGHT,i2.CURVE_UP_LEFT],"\uF5ED":[i2.LINE_H,i2.CURVE_DOWN_LEFT,i2.CURVE_UP_RIGHT],"\uF5EE":[i2.NODE_FILL,i2.NODE_STROKE],"\uF5EF":i2.NODE_STROKE,"\uF5F0":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_RIGHT],"\uF5F1":[i2.NODE_STROKE,i2.NODE_LINE_RIGHT],"\uF5F2":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_LEFT],"\uF5F3":[i2.NODE_STROKE,i2.NODE_LINE_LEFT],"\uF5F4":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_LEFT,i2.NODE_LINE_RIGHT],"\uF5F5":[i2.NODE_STROKE,i2.NODE_LINE_LEFT,i2.NODE_LINE_RIGHT],"\uF5F6":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_DOWN],"\uF5F7":[i2.NODE_STROKE,i2.NODE_LINE_DOWN],"\uF5F8":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_UP],"\uF5F9":[i2.NODE_STROKE,i2.NODE_LINE_UP],"\uF5FA":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_DOWN,i2.NODE_LINE_UP],"\uF5FB":[i2.NODE_STROKE,i2.NODE_LINE_DOWN,i2.NODE_LINE_UP],"\uF5FC":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_DOWN,i2.NODE_LINE_RIGHT],"\uF5FD":[i2.NODE_STROKE,i2.NODE_LINE_DOWN,i2.NODE_LINE_RIGHT],"\uF5FE":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_DOWN,i2.NODE_LINE_LEFT],"\uF5FF":[i2.NODE_STROKE,i2.NODE_LINE_DOWN,i2.NODE_LINE_LEFT],"\uF600":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_UP,i2.NODE_LINE_RIGHT],"\uF601":[i2.NODE_STROKE,i2.NODE_LINE_UP,i2.NODE_LINE_RIGHT],"\uF602":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_UP,i2.NODE_LINE_LEFT],"\uF603":[i2.NODE_STROKE,i2.NODE_LINE_UP,i2.NODE_LINE_LEFT],"\uF604":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_UP,i2.NODE_LINE_DOWN,i2.NODE_LINE_RIGHT],"\uF605":[i2.NODE_STROKE,i2.NODE_LINE_UP,i2.NODE_LINE_DOWN,i2.NODE_LINE_RIGHT],"\uF606":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_UP,i2.NODE_LINE_DOWN,i2.NODE_LINE_LEFT],"\uF607":[i2.NODE_STROKE,i2.NODE_LINE_UP,i2.NODE_LINE_DOWN,i2.NODE_LINE_LEFT],"\uF608":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_DOWN,i2.NODE_LINE_LEFT,i2.NODE_LINE_RIGHT],"\uF609":[i2.NODE_STROKE,i2.NODE_LINE_DOWN,i2.NODE_LINE_LEFT,i2.NODE_LINE_RIGHT],"\uF60A":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_UP,i2.NODE_LINE_LEFT,i2.NODE_LINE_RIGHT],"\uF60B":[i2.NODE_STROKE,i2.NODE_LINE_UP,i2.NODE_LINE_LEFT,i2.NODE_LINE_RIGHT],"\uF60C":[i2.NODE_FILL,i2.NODE_STROKE,i2.NODE_LINE_UP,i2.NODE_LINE_DOWN,i2.NODE_LINE_LEFT,i2.NODE_LINE_RIGHT],"\uF60D":[i2.NODE_STROKE,i2.NODE_LINE_UP,i2.NODE_LINE_DOWN,i2.NODE_LINE_LEFT,i2.NODE_LINE_RIGHT],"\u{1FB00}":s2(1),"\u{1FB01}":s2(2),"\u{1FB02}":s2(3),"\u{1FB03}":s2(4),"\u{1FB04}":s2(5),"\u{1FB05}":s2(6),"\u{1FB06}":s2(7),"\u{1FB07}":s2(8),"\u{1FB08}":s2(9),"\u{1FB09}":s2(10),"\u{1FB0A}":s2(11),"\u{1FB0B}":s2(12),"\u{1FB0C}":s2(13),"\u{1FB0D}":s2(14),"\u{1FB0E}":s2(15),"\u{1FB0F}":s2(16),"\u{1FB10}":s2(17),"\u{1FB11}":s2(18),"\u{1FB12}":s2(19),"\u{1FB13}":s2(20),"\u{1FB14}":s2(22),"\u{1FB15}":s2(23),"\u{1FB16}":s2(24),"\u{1FB17}":s2(25),"\u{1FB18}":s2(26),"\u{1FB19}":s2(27),"\u{1FB1A}":s2(28),"\u{1FB1B}":s2(29),"\u{1FB1C}":s2(30),"\u{1FB1D}":s2(31),"\u{1FB1E}":s2(32),"\u{1FB1F}":s2(33),"\u{1FB20}":s2(34),"\u{1FB21}":s2(35),"\u{1FB22}":s2(36),"\u{1FB23}":s2(37),"\u{1FB24}":s2(38),"\u{1FB25}":s2(39),"\u{1FB26}":s2(40),"\u{1FB27}":s2(41),"\u{1FB28}":s2(43),"\u{1FB29}":s2(44),"\u{1FB2A}":s2(45),"\u{1FB2B}":s2(46),"\u{1FB2C}":s2(47),"\u{1FB2D}":s2(48),"\u{1FB2E}":s2(49),"\u{1FB2F}":s2(50),"\u{1FB30}":s2(51),"\u{1FB31}":s2(52),"\u{1FB32}":s2(53),"\u{1FB33}":s2(54),"\u{1FB34}":s2(55),"\u{1FB35}":s2(56),"\u{1FB36}":s2(57),"\u{1FB37}":s2(58),"\u{1FB38}":s2(59),"\u{1FB39}":s2(60),"\u{1FB3A}":s2(61),"\u{1FB3B}":s2(62),"\u{1FB3C}":{type:3,data:"M0,0.6667 L0,1 L0.5,1 Z"},"\u{1FB3D}":{type:3,data:"M0,0.6667 L0,1 L1,1 Z"},"\u{1FB3E}":{type:3,data:"M0,0.3333 L0,1 L0.5,1 Z"},"\u{1FB3F}":{type:3,data:"M0,0.3333 L0,1 L1,1 Z"},"\u{1FB40}":{type:3,data:"M0,0 L0,1 L0.5,1 Z"},"\u{1FB41}":{type:3,data:"M0,0.3333 L0.5,0 L1,0 L1,1 L0,1 Z"},"\u{1FB42}":{type:3,data:"M0,0.3333 L1,0 L1,1 L0,1 Z"},"\u{1FB43}":{type:3,data:"M0,0.6667 L0.5,0 L1,0 L1,1 L0,1 Z"},"\u{1FB44}":{type:3,data:"M0,0.6667 L1,0 L1,1 L0,1 Z"},"\u{1FB45}":{type:3,data:"M0,1 L0.5,0 L1,0 L1,1 Z"},"\u{1FB46}":{type:3,data:"M0,0.6667 L1,0.3333 L1,1 L0,1 Z"},"\u{1FB47}":{type:3,data:"M0.5,1 L1,0.6667 L1,1 Z"},"\u{1FB48}":{type:3,data:"M0,1 L1,0.6667 L1,1 Z"},"\u{1FB49}":{type:3,data:"M0.5,1 L1,0.3333 L1,1 Z"},"\u{1FB4A}":{type:3,data:"M0,1 L1,0.3333 L1,1 Z"},"\u{1FB4B}":{type:3,data:"M0.5,1 L1,0 L1,1 Z"},"\u{1FB4C}":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 L1,0.3333 Z"},"\u{1FB4D}":{type:3,data:"M0,0 L0,1 L1,1 L1,0.3333 Z"},"\u{1FB4E}":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 L1,0.6667 Z"},"\u{1FB4F}":{type:3,data:"M0,0 L0,1 L1,1 L1,0.6667 Z"},"\u{1FB50}":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 Z"},"\u{1FB51}":{type:3,data:"M0,0.3333 L0,1 L1,1 L1,0.6667 Z"},"\u{1FB52}":{type:3,data:"M0,0.6667 L0.5,1 L1,1 L1,0 L0,0 Z"},"\u{1FB53}":{type:3,data:"M0,0.6667 L1,1 L1,0 L0,0 Z"},"\u{1FB54}":{type:3,data:"M0,0.3333 L0.5,1 L1,1 L1,0 L0,0 Z"},"\u{1FB55}":{type:3,data:"M0,0.3333 L1,1 L1,0 L0,0 Z"},"\u{1FB56}":{type:3,data:"M0,0 L0.5,1 L1,1 L1,0 Z"},"\u{1FB57}":{type:3,data:"M0,0.3333 L0,0 L0.5,0 Z"},"\u{1FB58}":{type:3,data:"M0,0.3333 L0,0 L1,0 Z"},"\u{1FB59}":{type:3,data:"M0,0.6667 L0,0 L0.5,0 Z"},"\u{1FB5A}":{type:3,data:"M0,0.6667 L0,0 L1,0 Z"},"\u{1FB5B}":{type:3,data:"M0,1 L0,0 L0.5,0 Z"},"\u{1FB5C}":{type:3,data:"M0,0.6667 L0,0 L1,0 L1,0.3333 Z"},"\u{1FB5D}":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 L1,0.6667 Z"},"\u{1FB5E}":{type:3,data:"M0,1 L0,0 L1,0 L1,0.6667 Z"},"\u{1FB5F}":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 L1,0.3333 Z"},"\u{1FB60}":{type:3,data:"M0,1 L0,0 L1,0 L1,0.3333 Z"},"\u{1FB61}":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 Z"},"\u{1FB62}":{type:3,data:"M0.5,0 L1,0 L1,0.3333 Z"},"\u{1FB63}":{type:3,data:"M0,0 L1,0 L1,0.3333 Z"},"\u{1FB64}":{type:3,data:"M0.5,0 L1,0 L1,0.6667 Z"},"\u{1FB65}":{type:3,data:"M0,0 L1,0 L1,0.6667 Z"},"\u{1FB66}":{type:3,data:"M0.5,0 L1,0 L1,1 Z"},"\u{1FB67}":{type:3,data:"M0,0.3333 L1,0.6667 L1,0 L0,0 Z"},"\u{1FB68}":{type:3,data:"M0,0 L1,0 L1,1 L0,1 L0.5,0.5 Z"},"\u{1FB69}":{type:3,data:"M0,0 L0.5,0.5 L1,0 L1,1 L0,1 Z"},"\u{1FB6A}":{type:3,data:"M0,0 L1,0 L0.5,0.5 L1,1 L0,1 Z"},"\u{1FB6B}":{type:3,data:"M0,0 L1,0 L1,1 L0.5,0.5 L0,1 Z"},"\u{1FB6C}":{type:3,data:"M0,0 L0.5,0.5 L0,1 Z"},"\u{1FB6D}":{type:3,data:"M0,0 L1,0 L0.5,0.5 Z"},"\u{1FB6E}":{type:3,data:"M1,0 L1,1 L0.5,0.5 Z"},"\u{1FB6F}":{type:3,data:"M0,1 L1,1 L0.5,0.5 Z"},"\u{1FB70}":{type:0,data:[{x:1,y:0,w:1,h:8}]},"\u{1FB71}":{type:0,data:[{x:2,y:0,w:1,h:8}]},"\u{1FB72}":{type:0,data:[{x:3,y:0,w:1,h:8}]},"\u{1FB73}":{type:0,data:[{x:4,y:0,w:1,h:8}]},"\u{1FB74}":{type:0,data:[{x:5,y:0,w:1,h:8}]},"\u{1FB75}":{type:0,data:[{x:6,y:0,w:1,h:8}]},"\u{1FB76}":{type:0,data:[{x:0,y:1,w:8,h:1}]},"\u{1FB77}":{type:0,data:[{x:0,y:2,w:8,h:1}]},"\u{1FB78}":{type:0,data:[{x:0,y:3,w:8,h:1}]},"\u{1FB79}":{type:0,data:[{x:0,y:4,w:8,h:1}]},"\u{1FB7A}":{type:0,data:[{x:0,y:5,w:8,h:1}]},"\u{1FB7B}":{type:0,data:[{x:0,y:6,w:8,h:1}]},"\u{1FB7C}":{type:0,data:[{x:0,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}]},"\u{1FB7D}":{type:0,data:[{x:0,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}]},"\u{1FB7E}":{type:0,data:[{x:7,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}]},"\u{1FB7F}":{type:0,data:[{x:7,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}]},"\u{1FB80}":{type:0,data:[{x:0,y:0,w:8,h:1},{x:0,y:7,w:8,h:1}]},"\u{1FB81}":{type:0,data:[{x:0,y:0,w:8,h:1},{x:0,y:2,w:8,h:1},{x:0,y:4,w:8,h:1},{x:0,y:7,w:8,h:1}]},"\u{1FB82}":{type:0,data:[{x:0,y:0,w:8,h:2}]},"\u{1FB83}":{type:0,data:[{x:0,y:0,w:8,h:3}]},"\u{1FB84}":{type:0,data:[{x:0,y:0,w:8,h:5}]},"\u{1FB85}":{type:0,data:[{x:0,y:0,w:8,h:6}]},"\u{1FB86}":{type:0,data:[{x:0,y:0,w:8,h:7}]},"\u{1FB87}":{type:0,data:[{x:6,y:0,w:2,h:8}]},"\u{1FB88}":{type:0,data:[{x:5,y:0,w:3,h:8}]},"\u{1FB89}":{type:0,data:[{x:3,y:0,w:5,h:8}]},"\u{1FB8A}":{type:0,data:[{x:2,y:0,w:6,h:8}]},"\u{1FB8B}":{type:0,data:[{x:1,y:0,w:7,h:8}]},"\u{1FB8C}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L0.5,0 L0.5,1 L0,1 Z"},"\u{1FB8D}":{type:1,data:[[1,0],[0,1]],clipPath:"M0.5,0 L1,0 L1,1 L0.5,1 Z"},"\u{1FB8E}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L1,0.5 L0,0.5 Z"},"\u{1FB8F}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0.5 L1,0.5 L1,1 L0,1 Z"},"\u{1FB90}":{type:1,data:[[0,1],[1,0]]},"\u{1FB91}":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0.5 L1,0.5 L1,1 L0,1 Z"},{type:0,data:[{x:0,y:0,w:8,h:4}]}],"\u{1FB92}":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0 L1,0 L1,0.5 L0,0.5 Z"},{type:0,data:[{x:0,y:4,w:8,h:4}]}],"\u{1FB94}":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0 L0.5,0 L0.5,1 L0,1 Z"},{type:0,data:[{x:4,y:0,w:4,h:8}]}],"\u{1FB95}":{type:0,data:[{x:0,y:0,w:2,h:2},{x:4,y:0,w:2,h:2},{x:2,y:2,w:2,h:2},{x:6,y:2,w:2,h:2},{x:0,y:4,w:2,h:2},{x:4,y:4,w:2,h:2},{x:2,y:6,w:2,h:2},{x:6,y:6,w:2,h:2}]},"\u{1FB96}":{type:0,data:[{x:2,y:0,w:2,h:2},{x:6,y:0,w:2,h:2},{x:0,y:2,w:2,h:2},{x:4,y:2,w:2,h:2},{x:2,y:4,w:2,h:2},{x:6,y:4,w:2,h:2},{x:0,y:6,w:2,h:2},{x:4,y:6,w:2,h:2}]},"\u{1FB97}":{type:0,data:[{x:0,y:2,w:8,h:2},{x:0,y:6,w:8,h:2}]},"\u{1FB98}":{type:2,data:"M-0.25,-0.25 L1.25,1.25 M-0.25,0 L1,1.25 M-0.25,0.25 L0.75,1.25 M-0.25,0.5 L0.5,1.25 M0,-0.25 L1.25,1 M0.25,-0.25 L1.25,0.75 M0.5,-0.25 L1.25,0.5 M-0.25,0.75 L0.25,1.25 M0.75,-0.25 L1.25,0.25",strokeWidth:1},"\u{1FB99}":{type:2,data:"M-0.25,0.5 L0.5,-0.25 M-0.25,0.75 L0.75,-0.25 M-0.25,1 L1,-0.25 M-0.25,1.25 L1.25,-0.25 M0,1.25 L1.25,0 M0.25,1.25 L1.25,0.25 M0.5,1.25 L1.25,0.5 M-0.25,0.25 L0.25,-0.25 M0.75,1.25 L1.25,0.75",strokeWidth:1},"\u{1FB9A}":{type:5,data:{d:"M0,0 L.5,.5 L0,1 L1,1 L.5,.5 L1,0",type:0}},"\u{1FB9B}":{type:5,data:{d:"M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1",type:0}},"\u{1FB9C}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L0,1 Z"},"\u{1FB9D}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L1,1 Z"},"\u{1FB9E}":{type:1,data:[[1,0],[0,1]],clipPath:"M1,0 L1,1 L0,1 Z"},"\u{1FB9F}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,1 L0,1 Z"},"\u{1FBA0}":{type:2,data:"M.5,0 L0,.5",strokeWidth:1},"\u{1FBA1}":{type:2,data:"M.5,0 L1,.5",strokeWidth:1},"\u{1FBA2}":{type:2,data:"M0,.5 L.5,1",strokeWidth:1},"\u{1FBA3}":{type:2,data:"M1,.5 L.5,1",strokeWidth:1},"\u{1FBA4}":{type:2,data:"M.5,0 L0,.5 L.5,1",strokeWidth:1},"\u{1FBA5}":{type:2,data:"M.5,0 L1,.5 L.5,1",strokeWidth:1},"\u{1FBA6}":{type:2,data:"M0,.5 L.5,1 L1,.5",strokeWidth:1},"\u{1FBA7}":{type:2,data:"M0,.5 L.5,0 L1,.5",strokeWidth:1},"\u{1FBA8}":{type:2,data:"M.5,0 L0,.5 M1,.5 L.5,1",strokeWidth:1},"\u{1FBA9}":{type:2,data:"M.5,0 L1,.5 M0,.5 L.5,1",strokeWidth:1},"\u{1FBAA}":{type:2,data:"M.5,0 L1,.5 L.5,1 L0,.5",strokeWidth:1},"\u{1FBAB}":{type:2,data:"M.5,0 L0,.5 L.5,1 L1,.5",strokeWidth:1},"\u{1FBAC}":{type:2,data:"M0,.5 L.5,0 L1,.5 L.5,1",strokeWidth:1},"\u{1FBAD}":{type:2,data:"M1,.5 L.5,0 L0,.5 L.5,1",strokeWidth:1},"\u{1FBAE}":{type:2,data:"M.5,0 L1,.5 L.5,1 L0,.5 Z",strokeWidth:1},"\u{1FBAF}":{type:2,data:"M0,.5 L1,.5 M.5,.35 L.5,.65",strokeWidth:1},"\u{1FBB0}":{type:3,data:"M0.1,0.2 L0.1,.8 L.4,.6 L.9,0.6 Z",scaleType:1},"\u{1FBB1}":{type:4,data:{d:"M.1,.525 L.35,.675 L.9,.35",type:1},scaleType:1},"\u{1FBB2}":{type:3,data:"M.29,.27 L0.13,.56 L.22,.59 L.35,0.35 L.67,.35 L.57,.57 L.71,.76 L.22,.76 L.42,1 L.53,.98 L.43,.86 L.9,.86 L.71,.6 L1,.6 L1,.52 L.83,.52 L.92,.36 L1,.36 L1,.27Z M.99,.13 A.12,.12,0,1,1,.75,.13 A.12,.12,0,1,1,.99,.13"},"\u{1FBB3}":{type:3,data:"M0,.27 L.3,.27 L.55,.12 L.63,.18 L.33,.36 L0,.36 M0,.52 L.33,.52 L.59,.89 L.73,.89 L.73,.98 L.53,.98 L.28,.6 L0,.6"},"\u{1FBB4}":{type:4,data:{d:"M.15,.55 L.5,.4 L.5,.45 L.65,.45 L.65,.35 L.85,.35 L.85,.625 L.5,.625 L.5,.7 Z",type:0},scaleType:1},"\u{1FBB5}":[{type:5,data:{d:"M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z",type:0}},{type:5,data:{d:"M.15,.5 L.5,.35 L.5,.425 L.85,.425 L.85,.575 L.5,.575 L.5,.65 Z",type:0},scaleType:1}],"\u{1FBB6}":[{type:5,data:{d:"M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z",type:0}},{type:5,data:{d:"M.85,.5 L.5,.35 L.5,.425 L.15,.425 L.15,.575 L.5,.575 L.5,.65 Z",type:0},scaleType:1}],"\u{1FBB7}":[{type:5,data:{d:"M.875,0 L1,0 L1,1 L.875,1 Z",type:0}},{type:5,data:{d:"M.5,.675 L.2,.5 L.35,.5 L.35,.325 L.65,.325 L.65,.5 L.8,.5 Z",type:0},scaleType:1}],"\u{1FBB8}":[{type:5,data:{d:"M.875,0 L1,0 L1,1 L.875,1 Z",type:0}},{type:5,data:{d:"M.5,.325 L.2,.5 L.35,.5 L.35,.675 L.65,.675 L.65,.5 L.8,.5 Z",type:0},scaleType:1}],"\u{1FBB9}":{type:5,data:{d:"M1,.89 L.11,.89 L.11,.37 L.36,.12 L.74,.12 L.96,.34 L1,.34 L1,.405 L.92,.405 L.69,.185 L.41,.185 L.21,.42 L.21,.825 L1,.825 Z",type:0}},"\u{1FBBA}":{type:5,data:{d:"M0,.89 L0,.825 L.78,.825 L.78,.53 L.7,.415 L0,.415 L0,.35 L.75,.35 L.88,.48 L.88,.89 Z",type:0}},"\u{1FBBB}":{type:5,data:{d:"M.31,.275 L.44,.275 L.44,.47 L.05,.47 L.05,.405 L.31,.405 Z M.56,.275 L.69,.275 L.69,.405 L.95,.405 L.95,.47 L.56,.47 Z M.05,.53 L.44,.53 L.44,.725 L.31,.725 L.31,.595 L.05,.595 Z M.56,.53 L.95,.53 L.95,.595 L.69,.595 L.69,.725 L.56,.725 Z",type:0},scaleType:1},"\u{1FBBC}":[{type:3,data:"M0,0 L1,0 L1,1 L0,1 L0,.935 L.885,.935 L.885,.065 L0,.065 Z"},{type:3,data:"M.67,.5 A.17,.085,0,1,1,.33,.5 A.17,.085,0,1,1,.67,.5",scaleType:1}],"\u{1FBBD}":{type:4,data:{d:"M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1 Z",type:1}},"\u{1FBBE}":{type:4,data:{d:"M1,.5 L.5,1",type:1}},"\u{1FBBF}":{type:4,data:{d:"M.5,0 L1,.5 L.5,1 L0,.5 Z",type:1}},"\u{1FBC0}":{type:3,data:"M.16,.445 A.02,.01,0,0,1,.39,.33 L.5,.375 L.61,.33 A.02,.01,0,0,1,.84,.445 L.75,.5 L.84,.555 A.02,.01,0,0,1,.61,.67 L.5,.625 L.39,.67 A.02,.01,0,0,1,.16,.555 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z",scaleType:1},"\u{1FBC1}":{type:3,data:"M0.142308,0.924 Q0.130770,0.924,0.120193,0.922 T0.101924,0.916 T0.089424,0.9065 T0.084616,0.895 V0.025 Q0.084616,0.019,0.089424,0.0135 T0.101924,0.004 T0.120193,-0.002 T0.142308,-0.004 Q0.150000,-0.004,0.157693,-0.003 T0.173078,0.000 L0.430770,0.085 Q0.451924,0.059,0.494232,0.041 T0.587501,0.013 T0.692309,-0.0005 T0.800001,-0.004 H1 V0.055 H0.800001 Q0.771155,0.055,0.742309,0.056 T0.685578,0.060 T0.630770,0.0685 T0.580770,0.0825 T0.543270,0.1045 T0.528847,0.133 V0.647 H0.530770 Q0.596155,0.645,0.659616,0.638 T0.782693,0.6165 T0.893270,0.5805 T1,0.531 V0.623 Q0.934616,0.644,0.880770,0.6595 T0.769232,0.685 T0.650963,0.700 T0.528847,0.707 V0.787 Q0.528847,0.802,0.543270,0.8155 T0.580770,0.8375 T0.630770,0.8515 T0.685578,0.860 T0.742309,0.864 T0.800001,0.865 H1 V0.924 H0.800001 Q0.746155,0.924,0.692309,0.9205 T0.587501,0.907 T0.494232,0.879 T0.430770,0.835 L0.173078,0.920 Q0.165386,0.922,0.157693,0.923 T0.142308,0.924 Z M0.2,0.841 L0.415385,0.770 V0.150 L0.2,0.079 V0.841 Z M1,0.698 Q0.973077,0.694,0.969231,0.6885 T0.965385,0.677 Q0.965385,0.672,0.969231,0.6665 T1,0.657 V0.698 Z"},"\u{1FBC2}":{type:3,data:"M0,0.623 V0.531 Q0.044231,0.512,0.0625,0.4905 T0.092308,0.4465 T0.108654,0.4005 T0.113462,0.354 V0.351 Q0.113462,0.345,0.117308,0.3395 T0.129808,0.3305 T0.148846,0.3245 T0.171154,0.323 H0.501923 Q0.532692,0.323,0.563462,0.3185 T0.617308,0.303 T0.651923,0.2765 T0.663462,0.2435 V0.241 Q0.663462,0.219,0.657692,0.197 T0.639423,0.1545 T0.604808,0.115 T0.549615,0.082 T0.475577,0.0615 T0.392308,0.055 H0 V-0.004 H1 V0.055 H0.667308 Q0.694231,0.071,0.713462,0.09 T0.746154,0.129 T0.766346,0.1705 T0.775,0.213 H1 V0.316 Q0.971154,0.305,0.955769,0.2965 T0.920192,0.2825 T0.876923,0.2745 T0.830769,0.272 H0.773077 Q0.767308,0.297,0.743269,0.319 T0.680769,0.355 T0.595192,0.375 T0.501923,0.381 H0.227308 Q0.223462,0.415,0.211,0.4485 T0.172692,0.5135 T0.108269,0.573 T0,0.623 Z M0.611538,0.924 H0 V0.865 H0.611538 Q0.642308,0.865,0.673077,0.8605 T0.727885,0.845 T0.762308,0.8185 T0.773077,0.787 V0.785 Q0.773077,0.769,0.762308,0.7535 T0.727885,0.727 T0.673077,0.7115 T0.611538,0.707 H0.059615 Q0.048077,0.707,0.037115,0.7045 T0,0.694 V0.653 Q0.026923,0.648,0.037115,0.646 T0.059615,0.644 H0.722692 Q0.753462,0.644,0.784231,0.6395 T0.839038,0.624 T0.873846,0.5975 T0.884615,0.566 V0.564 Q0.884615,0.548,0.873846,0.532 T0.839038,0.5055 T0.784231,0.49 T0.722692,0.4855 H0.392308 Q0.380769,0.4855,0.370192,0.483 T0.351923,0.4765 T0.339423,0.467 T0.334615,0.4555 T0.339423,0.444 T0.351923,0.4345 T0.370192,0.428 T0.392308,0.4255 H0.832692 Q0.855769,0.4255,0.878846,0.4235 T0.922115,0.416 T0.957692,0.4015 T1,0.3815 V0.4685 Q0.975,0.4705,0.966346,0.4725 T0.95,0.4765 Q0.961538,0.4835,0.969231,0.4915 T1,0.5075 V0.6205 Q0.965385,0.6455,0.926923,0.665 T0.84,0.6935 Q0.866923,0.7115,0.877596,0.7345 T0.894231,0.7855 V0.787 Q0.894231,0.815,0.876923,0.8425 T0.820192,0.8895 T0.726923,0.916 T0.611538,0.924 Z"},"\u{1FBC3}":{type:3,data:"M0,0.473 V0.386 Q0.025490,0.378,0.028431,0.3695 T0.031373,0.352 V0.35 Q0.031373,0.342,0.028431,0.333 T0,0.316 V0.213 H0.652941 Q0.684314,0.213,0.715686,0.2085 T0.770588,0.193 T0.804902,0.1665 T0.815686,0.135 V0.133 Q0.815686,0.117,0.804902,0.101 T0.770588,0.0745 T0.715686,0.0595 T0.652941,0.055 H0 V-0.004 H0.652941 Q0.707843,-0.004,0.761765,0.004 T0.856863,0.031 T0.915686,0.0775 T0.933333,0.133 V0.135 Q0.933333,0.163,0.915686,0.1905 T0.856863,0.237 T0.761765,0.264 T0.652941,0.272 H0.109804 Q0.131373,0.29,0.140196,0.31 T0.149020,0.35 V0.352 Q0.149020,0.37,0.142157,0.388 T0.118627,0.4225 T0.076471,0.452 T0,0.473 Z M0,0.625 V0.513 Q0.029412,0.526,0.032353,0.54 T0.035294,0.568 V0.57 Q0.035294,0.584,0.032353,0.598 T0,0.625 Z"},"\u{1FBC4}":{type:3,data:"M0.019231,1.085 V-0.165 H0.980769 V1.085 H0.019231 Z M0.446154,0.527 H0.553846 Q0.553846,0.511,0.5625,0.495 T0.591346,0.466 T0.631731,0.4405 T0.666346,0.4135 T0.688462,0.3825 T0.696154,0.35 Q0.696154,0.33,0.682692,0.311 T0.641346,0.2785 T0.575,0.259 T0.498077,0.253 T0.421154,0.259 T0.356731,0.279 T0.315385,0.3125 T0.301923,0.352 V0.357 H0.409615 V0.354 Q0.409615,0.345,0.414423,0.335 T0.431731,0.318 T0.4625,0.3075 T0.498077,0.304 Q0.517308,0.304,0.534615,0.307 T0.564423,0.3165 T0.582692,0.332 T0.588462,0.35 Q0.588462,0.366,0.572115,0.3805 T0.535577,0.4075 T0.496154,0.4335 T0.465385,0.4625 T0.45,0.4945 T0.446154,0.527 Z M0.5,0.667 Q0.519231,0.667,0.5375,0.664 T0.569231,0.654 T0.588462,0.637 T0.594231,0.617 T0.588462,0.5975 T0.569231,0.581 T0.5375,0.571 T0.5,0.568 T0.4625,0.571 T0.430769,0.581 T0.411538,0.5975 T0.405769,0.617 T0.411538,0.637 T0.430769,0.654 T0.4625,0.664 T0.5,0.667 Z",scaleType:1},"\u{1FBC5}":{type:5,data:{d:"M.44,.425 L.44,.51 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.51 L.56,.425 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC6}":{type:5,data:{d:"M.44,.425 L.44,.46 L.23,.385 L.19,.43 L.44,.51 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.61,.735 L.7,.71 L.56,.56 L.56,.51 L.81,.43 L.77,.385 L.56,.46 L.56,.425 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC7}":{type:5,data:{d:"M.44,.425 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.425 Z M.18,.53 L.23,.575 L.81,.43 L.77,.385 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC8}":{type:5,data:{d:"M.44,.425 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.62,.735 L.71,.71 L.56,.56 L.56,.425 Z M.23,.385 L.18,.43 L.77,.575 L.81,.53 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC9}":{type:5,data:{d:"M.44,.425 L.45,.495 L.15,.645 L.34,.645 L.34,.7 L.44,.7 L.44,.645 L.56,.645 L.56,.7 L.66,.7 L.66,.645 L.84,.645 L.54,.495 L.56,.425 Z M.39,.6 L.5,.55 L.60,.6 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBCA}":{type:5,data:{d:"M.26,.375 L.5,.255 L.74,.375 L.74,.665 L.5,.55 L.26,.665 Z M.37,.395 L.37,.54 L.5,.475 L.63,.54 L.63,.395 L.5,.33 Z",type:0},scaleType:1},"\u{1FBCB}":{type:5,data:{d:"M.09,.41 L.32,.295 L.5,.375 L.68,.295 L.91,.41 L.75,.5 L.91,.59 L.68,.705 L.5,.625 L.32,.705 L.09,.59 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z",type:0},scaleType:1},"\u{1FBCC}":{type:5,data:{d:"M.55,.305 L.88,.305 L.88,.355 L.65,.355 L.65,.47 L.88,.47 L.88,.52 L.55,.52 Z",type:0},scaleType:1},"\u{1FBCD}":{type:5,data:{d:"M.38,.39 L.5,.33 L.62,.39 L.62,.53 L.5,.47 L.38,.53 Z",type:0},scaleType:1},"\u{1FBCE}":{type:3,data:"M0,0 L0.6667,0 L0.6667,1 L0,1 Z"},"\u{1FBCF}":{type:3,data:"M0,0 L0.3333,0 L0.3333,1 L0,1 Z"},"\u{1FBD0}":{type:2,data:"M1,.5 L0,1",strokeWidth:1},"\u{1FBD1}":{type:2,data:"M1,0 L0,.5",strokeWidth:1},"\u{1FBD2}":{type:2,data:"M0,0 L1,.5",strokeWidth:1},"\u{1FBD3}":{type:2,data:"M0,.5 L1,1",strokeWidth:1},"\u{1FBD4}":{type:2,data:"M0,0 L.5,1",strokeWidth:1},"\u{1FBD5}":{type:2,data:"M.5,0 L1,1",strokeWidth:1},"\u{1FBD6}":{type:2,data:"M1,0 L.5,1",strokeWidth:1},"\u{1FBD7}":{type:2,data:"M.5,0 L0,1",strokeWidth:1},"\u{1FBD8}":{type:2,data:"M0,0 L.5,.5 L1,0",strokeWidth:1},"\u{1FBD9}":{type:2,data:"M1,0 L.5,.5 L1,1",strokeWidth:1},"\u{1FBDA}":{type:2,data:"M0,1 L.5,.5 L1,1",strokeWidth:1},"\u{1FBDB}":{type:2,data:"M0,0 L.5,.5 L0,1",strokeWidth:1},"\u{1FBDC}":{type:2,data:"M0,0 L.5,1 L1,0",strokeWidth:1},"\u{1FBDD}":{type:2,data:"M1,0 L0,.5 L1,1",strokeWidth:1},"\u{1FBDE}":{type:2,data:"M0,1 L.5,0 L1,1",strokeWidth:1},"\u{1FBDF}":{type:2,data:"M0,0 L1,.5 L0,1",strokeWidth:1},"\u{1FBE0}":{type:5,data:{d:"M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0",type:1}},"\u{1FBE1}":{type:5,data:{d:"M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1",type:1}},"\u{1FBE2}":{type:5,data:{d:"M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1",type:1}},"\u{1FBE3}":{type:5,data:{d:"M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1",type:1}},"\u{1FBE4}":{type:0,data:[{x:2,y:0,w:4,h:4}]},"\u{1FBE5}":{type:0,data:[{x:2,y:4,w:4,h:4}]},"\u{1FBE6}":{type:0,data:[{x:0,y:2,w:4,h:4}]},"\u{1FBE7}":{type:0,data:[{x:4,y:2,w:4,h:4}]},"\u{1FBE8}":{type:5,data:{d:"M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0 Z",type:0}},"\u{1FBE9}":{type:5,data:{d:"M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1 Z",type:0}},"\u{1FBEA}":{type:5,data:{d:"M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1 Z",type:0}},"\u{1FBEB}":{type:5,data:{d:"M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1 Z",type:0}},"\u{1FBEC}":{type:5,data:{d:"M1,0 L.5,0 C.5,.276,.724,.5,1,.5 Z",type:0}},"\u{1FBED}":{type:5,data:{d:"M0,1 L.5,1 C.5,.724,.276,.5,0,.5 Z",type:0}},"\u{1FBEE}":{type:5,data:{d:"M1,1 L1,.5 C.724,.5,.5,.724,.5,1 Z",type:0}},"\u{1FBEF}":{type:5,data:{d:"M0,0 L0,.5 C.276,.5,.5,.276,.5,0 Z",type:0}},"\u{1FBF0}":{type:5,data:{d:r(126),type:0},scaleType:1},"\u{1FBF1}":{type:5,data:{d:r(48),type:0},scaleType:1},"\u{1FBF2}":{type:5,data:{d:r(109),type:0},scaleType:1},"\u{1FBF3}":{type:5,data:{d:r(121),type:0},scaleType:1},"\u{1FBF4}":{type:5,data:{d:r(51),type:0},scaleType:1},"\u{1FBF5}":{type:5,data:{d:r(91),type:0},scaleType:1},"\u{1FBF6}":{type:5,data:{d:r(95),type:0},scaleType:1},"\u{1FBF7}":{type:5,data:{d:r(114),type:0},scaleType:1},"\u{1FBF8}":{type:5,data:{d:r(127),type:0},scaleType:1},"\u{1FBF9}":{type:5,data:{d:r(123),type:0},scaleType:1},"\u{1FBFA}":{type:5,data:{d:"M.5,.175 C.2,.175,.15,.305,.15,.435 L.05,.63 L.35,.63 C.35,.682,.42,.76,.5,.76 C.58,.76,.65,.682,.65,.63 L.95,.63 L.85,.435 C.85,.305,.8,.175,.5,.175 Z",type:0},scaleType:1},...Object.fromEntries(Array.from({length:256},(t3,e3)=>[String.fromCodePoint(10240+e3),{type:6,data:e3}]))},e2.blockPatternCodepoints=new Set([9617,9618,9619,129932,129933,129934,129935,129936,129937,129938,129940,129948,129949,129950,129951])},183(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.tryDrawCustomGlyph=function(t3,e3,i3,s3,o2,n2,L2,h2,T2,l2,d,c,_=0){const Q=r.customGlyphDefinitions[e3];if(Q){const e4=Array.isArray(Q)?Q:[Q];for(const r2 of e4)a(t3,r2,i3,s3,o2,n2,L2,h2,T2,l2,d,c,_);return true}return false};const s2=i2(959),r=i2(915);function a(t3,e3,i3,r2,a2,h2,d,c,_,Q,u,p,g=0){let M=a2,y=h2,v=i3,f=r2;switch(1===e3.scaleType&&(M=d,y=c,v=i3+(a2-d)/2,f=r2+(h2-c)/2),e3.clipPath&&(t3.save(),(function(t4,e4,i4,s3,r3,a3){t4.beginPath();for(const o2 of e4.split(" ")){const e5=o2[0];if("Z"===e5){t4.closePath();continue}const n2=o2.substring(1).split(",");if(!n2[0]||!n2[1])continue;const L2=i4+parseFloat(n2[0])*r3,h3=s3+parseFloat(n2[1])*a3;"M"===e5?t4.moveTo(L2,h3):"L"===e5&&t4.lineTo(L2,h3)}t4.clip()})(t3,e3.clipPath,v,f,M,y)),e3.type){case 0:!(function(t4,e4,i4,s3,r3,a3){for(let o2=0;o27&&parseInt(T2.slice(7,9),16)||1;else{if(!T2.startsWith("rgba"))throw new Error(`Unexpected fillStyle color format "${T2}" when drawing pattern glyph`);[L2,d3,c3,_2]=T2.substring(5,T2.length-1).split(",").map(t5=>parseFloat(t5))}for(let t5=0;t5>1&1,c2=1&n2;0!==d2||0!==c2?l2.setTransform(new DOMMatrix().translateSelf(-d2,-c2)):l2.setTransform(new DOMMatrix),t4.fillStyle=l2,t4.fillRect(i4,r3,a3,o2)})(t3,e3.data,v,f,M,y,g);break;case 2:!(function(t4,e4,i4,s3,r3,a3,o2,n2,L2){let h3;t4.save(),t4.beginPath(),t4.rect(i4,s3,r3,a3),t4.clip(),t4.beginPath(),h3="function"==typeof e4?e4(.15,.15/a3*r3):e4;const d2={currentX:0,currentY:0,lastControlX:0,lastControlY:0,lastCommand:""};for(const e5 of h3.split(" ")){const L3=e5[0];if("Z"===L3){;t4.closePath(),d2.lastCommand=L3;continue}const h4=T[L3];if(!h4){n2.error(`Could not find drawing instructions for "${L3}"`);continue}const c2=e5.substring(1).split(",");c2[0]&&c2[1]&&(h4(t4,l(c2,r3,a3,i4,s3,true,o2,0,0,false),d2),d2.lastCommand=L3)};void 0!==L2?(t4.strokeStyle=t4.fillStyle,t4.lineWidth=o2*L2,t4.stroke()):t4.fill(),t4.closePath(),t4.restore()})(t3,e3.data,v,f,M,y,Q,u,e3.strokeWidth);break;case 3:!(function(t4,e4,i4,s3,r3,a3,o2,L2){const h3="string"==typeof e4?e4:e4(0,0);t4.beginPath();let T2=0,l2=0,d2=0,c2=0,_2="";for(const e5 of h3.split(" ")){const o3=e5[0],L3=e5.substring(1).split(",");if("Z"===o3){;t4.closePath(),_2=o3;continue}if("V"===o3){const e6=s3+parseFloat(L3[0])*a3;t4.lineTo(T2,e6),l2=e6,d2=T2,c2=l2,_2=o3;continue}if("H"===o3){const e6=i4+parseFloat(L3[0])*r3;t4.lineTo(e6,l2),T2=e6,d2=T2,c2=l2,_2=o3;continue}if(!L3[0]||!L3[1])continue;if("A"===o3){const e6=parseFloat(L3[0])*r3,o4=parseFloat(L3[1])*a3,h5=parseFloat(L3[2])*Math.PI/180,d3=parseInt(L3[3],10),c3=parseInt(L3[4],10),_3=i4+parseFloat(L3[5])*r3,Q2=s3+parseFloat(L3[6])*a3;n(t4,T2,l2,e6,o4,h5,d3,c3,_3,Q2),T2=_3,l2=Q2;continue}const h4=L3.map((t5,e6)=>{const o4=parseFloat(t5);return e6%2==0?i4+o4*r3:s3+o4*a3});if("M"===o3)t4.moveTo(h4[0],h4[1]),T2=h4[0],l2=h4[1],d2=T2,c2=l2;else if("L"===o3)t4.lineTo(h4[0],h4[1]),T2=h4[0],l2=h4[1],d2=T2,c2=l2;else if("Q"===o3)t4.quadraticCurveTo(h4[0],h4[1],h4[2],h4[3]),d2=h4[0],c2=h4[1],T2=h4[2],l2=h4[3];else if("T"===o3){let e6,i5;"Q"===_2||"T"===_2?(e6=2*T2-d2,i5=2*l2-c2):(e6=T2,i5=l2),t4.quadraticCurveTo(e6,i5,h4[0],h4[1]),d2=e6,c2=i5,T2=h4[0],l2=h4[1]}else"C"===o3&&(t4.bezierCurveTo(h4[0],h4[1],h4[2],h4[3],h4[4],h4[5]),d2=h4[2],c2=h4[3],T2=h4[4],l2=h4[5]);_2=o3}void 0!==L2?(t4.strokeStyle=t4.fillStyle,t4.lineWidth=o2*L2,t4.stroke()):t4.fill()})(t3,e3.data,v,f,M,y,Q,e3.strokeWidth);break;case 4:!(function(t4,e4,i4,s3,r3,a3,o2,n2){;t4.save(),t4.fillRect(i4,s3,r3,a3),n2&&(t4.fillStyle=n2,t4.strokeStyle=n2),t4.lineWidth=o2,t4.lineCap="square",t4.beginPath();for(const o3 of e4.d.split(" ")){const e5=o3[0],n3=o3.substring(1).split(",");if(!n3[0]||!n3[1]){"Z"===e5&&t4.closePath();continue}const L2=n3.map((t5,e6)=>{const o4=parseFloat(t5);return e6%2==0?i4+o4*r3:s3+o4*a3});"M"===e5?t4.moveTo(L2[0],L2[1]):"L"===e5&&t4.lineTo(L2[0],L2[1])};1===e4.type?t4.stroke():t4.fill(),t4.restore()})(t3,e3.data,v,f,M,y,Q,p);break;case 5:!(function(t4,e4,i4,s3,r3,a3,o2,n2,L2){const h3=new Path2D;h3.rect(i4,s3,r3,a3),t4.clip(h3),t4.beginPath();const d2=o2/12;t4.lineWidth=n2*d2;const c2={currentX:0,currentY:0,lastControlX:0,lastControlY:0,lastCommand:""};for(const o3 of e4.d.split(" ")){const h4=o3[0];if("Z"===h4){;t4.closePath(),c2.lastCommand=h4;continue}const _2=T[h4];if(!_2){L2.error(`Could not find drawing instructions for "${h4}"`);continue}const Q2=o3.substring(1).split(",");Q2[0]&&Q2[1]&&(_2(t4,l(Q2,r3,a3,i4,s3,false,n2,(e4.leftPadding??0)*(d2/2),(e4.rightPadding??0)*(d2/2)),c2),c2.lastCommand=h4)};1===e4.type?(t4.strokeStyle=t4.fillStyle,t4.stroke()):t4.fill(),t4.closePath()})(t3,e3.data,v,f,M,y,_,Q,u);break;case 6:!(function(t4,e4,i4,s3,r3,a3){const n2=r3/8,L2=.1*a3,h3=.8*a3/8,T2=Math.min(n2,h3);for(let r4=0;r4<8;r4++)if(e4&1<1){const t4=Math.sqrt(y);u=(s3*=t4)*s3,p=(r2*=t4)*r2}let v=(u*p-u*M-p*g)/(u*M+p*g);v<0&&(v=0);const f=(o2===n2?-1:1)*Math.sqrt(v),C=f*(s3*Q/r2),m=f*(-r2*_/s3),w=T2*C-l2*m+(e3+L2)/2,x=l2*C+T2*m+(i3+h2)/2,b=(_-C)/s3,E=(Q-m)/r2,R=(-_-C)/s3,k=(-Q-m)/r2,A=Math.atan2(E,b);let Z=Math.atan2(k,R)-A;0===n2&&Z>0?Z-=2*Math.PI:1===n2&&Z<0&&(Z+=2*Math.PI);const D=A+Z;t3.ellipse(w,x,s3,r2,a2,A,D,0===n2)}const L=new Map;function h(t3,e3,i3=0){return Math.max(Math.min(t3,e3),i3)}const T={C:(t3,e3,i3)=>{;t3.bezierCurveTo(e3[0],e3[1],e3[2],e3[3],e3[4],e3[5]),i3.lastControlX=e3[2],i3.lastControlY=e3[3],i3.currentX=e3[4],i3.currentY=e3[5]},L:(t3,e3,i3)=>{;t3.lineTo(e3[0],e3[1]),i3.lastControlX=i3.currentX=e3[0],i3.lastControlY=i3.currentY=e3[1]},M:(t3,e3,i3)=>{;t3.moveTo(e3[0],e3[1]),i3.lastControlX=i3.currentX=e3[0],i3.lastControlY=i3.currentY=e3[1]},Q:(t3,e3,i3)=>{;t3.quadraticCurveTo(e3[0],e3[1],e3[2],e3[3]),i3.lastControlX=e3[0],i3.lastControlY=e3[1],i3.currentX=e3[2],i3.currentY=e3[3]},T:(t3,e3,i3)=>{let s3,r2;"Q"===i3.lastCommand||"T"===i3.lastCommand?(s3=2*i3.currentX-i3.lastControlX,r2=2*i3.currentY-i3.lastControlY):(s3=i3.currentX,r2=i3.currentY),t3.quadraticCurveTo(s3,r2,e3[0],e3[1]),i3.lastControlX=s3,i3.lastControlY=r2,i3.currentX=e3[0],i3.currentY=e3[1]}};function l(t3,e3,i3,s3,r2,a2,o2,n2=0,L2=0,T2=true){const l2=t3.map(t4=>parseFloat(t4)||parseInt(t4));if(l2.length<2)throw new Error("Too few arguments for instruction");for(let t4=0;t4{;this._refreshCharAtlas(t3,e4),this.reset(t3)})),this._register((0,r.toDisposable)(()=>{this._canvas.remove()}))}_initCanvas(){;this._ctx=(0,a.throwIfFalsy)(this._canvas.getContext("2d",{alpha:this._alpha})),this._alpha||this._clearAll()}handleBlur(t3){}handleFocus(t3){}handleCursorMove(t3){}handleGridChanged(t3,e3,i3){}handleSelectionChanged(t3,e3,i3,s3=false){}_setTransparency(t3,e3){if(e3===this._alpha)return;const i3=this._canvas;this._alpha=e3,this._canvas=this._canvas.cloneNode(),this._initCanvas(),this._container.replaceChild(this._canvas,i3),this._refreshCharAtlas(t3,this._themeService.colors),this.handleGridChanged(t3,0,t3.rows-1)}_refreshCharAtlas(t3,e3){;this._deviceCharWidth<=0&&this._deviceCharHeight<=0||(this._charAtlas=(0,s2.acquireTextureAtlas)(t3,this._optionsService.rawOptions,e3,this._deviceCellWidth,this._deviceCellHeight,this._deviceCharWidth,this._deviceCharHeight,this._coreBrowserService.dpr,2048),this._charAtlas.warmUp())}resize(t3,e3){;this._deviceCellWidth=e3.device.cell.width,this._deviceCellHeight=e3.device.cell.height,this._deviceCharWidth=e3.device.char.width,this._deviceCharHeight=e3.device.char.height,this._deviceCharLeft=e3.device.char.left,this._deviceCharTop=e3.device.char.top,this._canvas.width=e3.device.canvas.width,this._canvas.height=e3.device.canvas.height,this._canvas.style.width=`${e3.css.canvas.width}px`,this._canvas.style.height=`${e3.css.canvas.height}px`,this._alpha||this._clearAll(),this._refreshCharAtlas(t3,this._themeService.colors)}_fillBottomLineAtCells(t3,e3,i3=1){this._ctx.fillRect(t3*this._deviceCellWidth,(e3+1)*this._deviceCellHeight-this._coreBrowserService.dpr-1,i3*this._deviceCellWidth,this._coreBrowserService.dpr)}_clearAll(){this._alpha?this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(0,0,this._canvas.width,this._canvas.height))}_clearCells(t3,e3,i3,s3){this._alpha?this._ctx.clearRect(t3*this._deviceCellWidth,e3*this._deviceCellHeight,i3*this._deviceCellWidth,s3*this._deviceCellHeight):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(t3*this._deviceCellWidth,e3*this._deviceCellHeight,i3*this._deviceCellWidth,s3*this._deviceCellHeight))}_fillCharTrueColor(t3,e3,i3,s3){;this._ctx.font=this._getFont(t3,false,false),this._ctx.textBaseline=o.TEXT_BASELINE,this._clipCell(i3,s3,e3.getWidth()),this._ctx.fillText(e3.getChars(),i3*this._deviceCellWidth+this._deviceCharLeft,s3*this._deviceCellHeight+this._deviceCharTop+this._deviceCharHeight)}_clipCell(t3,e3,i3){;this._ctx.beginPath(),this._ctx.rect(t3*this._deviceCellWidth,e3*this._deviceCellHeight,i3*this._deviceCellWidth,this._deviceCellHeight),this._ctx.clip()}_getFont(t3,e3,i3){return`${i3?"italic":""} ${e3?t3.options.fontWeightBold:t3.options.fontWeight} ${t3.options.fontSize*this._coreBrowserService.dpr}px ${t3.options.fontFamily}`}}e2.BaseRenderLayer=n},306(t2,e2,i2){;Object.defineProperty(e2,"__esModule",{value:true}),e2.LinkRenderLayer=void 0;const s2=i2(657),r=i2(814),a=i2(133);class o extends a.BaseRenderLayer{constructor(t3,e3,i3,s3,r2,a2,o2){;super(i3,t3,"link",e3,true,r2,a2,o2),this._register(s3.onShowLinkUnderline(t4=>this._handleShowLinkUnderline(t4))),this._register(s3.onHideLinkUnderline(t4=>this._handleHideLinkUnderline(t4)))}resize(t3,e3){;super.resize(t3,e3),this._state=void 0}reset(t3){this._clearCurrentLink()}_clearCurrentLink(){if(this._state){this._clearCells(this._state.x1,this._state.y1,this._state.cols-this._state.x1,1);const t3=this._state.y2-this._state.y1-1;t3>0&&this._clearCells(0,this._state.y1+1,this._state.cols,t3),this._clearCells(0,this._state.y2,this._state.x2,1),this._state=void 0}}_handleShowLinkUnderline(t3){if(t3.fg===r.INVERTED_DEFAULT_COLOR?this._ctx.fillStyle=this._themeService.colors.background.css:void 0!==t3.fg&&(0,s2.is256Color)(t3.fg)?this._ctx.fillStyle=this._themeService.colors.ansi[t3.fg].css:this._ctx.fillStyle=this._themeService.colors.foreground.css,t3.y1===t3.y2)this._fillBottomLineAtCells(t3.x1,t3.y1,t3.x2-t3.x1);else{this._fillBottomLineAtCells(t3.x1,t3.y1,t3.cols-t3.x1);for(let e3=t3.y1+1;e3{var t2=s;Object.defineProperty(t2,"__esModule",{value:true}),t2.WebglAddon=void 0;const e2=i(426),r=i(95),a=i(399),o=i(414);class n extends e2.Disposable{constructor(t3){if(r.isSafari&&(0,r.getSafariVersion)()<16){const t4={antialias:false,depth:false,preserveDrawingBuffer:true};if(!document.createElement("canvas").getContext("webgl2",t4))throw new Error("WebGL2 is only supported on Safari 16 and above")};super(),this._onChangeTextureAtlas=this._register(new o.Emitter),this.onChangeTextureAtlas=this._onChangeTextureAtlas.event,this._onAddTextureAtlasCanvas=this._register(new o.Emitter),this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event,this._onRemoveTextureAtlasCanvas=this._register(new o.Emitter),this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event,this._onContextLoss=this._register(new o.Emitter),this.onContextLoss=this._onContextLoss.event,this._customGlyphs=t3?.customGlyphs??true,this._preserveDrawingBuffer=t3?.preserveDrawingBuffer}activate(t3){const i2=t3._core;if(!t3.element)return void this._register(i2.onWillOpen(()=>this.activate(t3)));this._terminal=t3;const s2=i2.coreService,r2=i2.optionsService,n2=i2,L=n2._renderService,h=n2._characterJoinerService,T=n2._charSizeService,l=n2._coreBrowserService,d=n2._decorationService,c=n2._logService,_=n2._themeService;this._renderer=this._register(new a.WebglRenderer(t3,h,T,l,s2,d,c,r2,_,this._customGlyphs,this._preserveDrawingBuffer)),this._register(o.EventUtils.forward(this._renderer.onContextLoss,this._onContextLoss)),this._register(o.EventUtils.forward(this._renderer.onChangeTextureAtlas,this._onChangeTextureAtlas)),this._register(o.EventUtils.forward(this._renderer.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas)),this._register(o.EventUtils.forward(this._renderer.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas)),L.setRenderer(this._renderer),this._register((0,e2.toDisposable)(()=>{if(this._terminal._core._store._isDisposed)return;const e3=this._terminal._core._renderService;e3.setRenderer(this._terminal._core._createRenderer()),e3.handleResize(t3.cols,t3.rows)}))}get textureAtlas(){return this._renderer?.textureAtlas}clearTextureAtlas(){this._renderer?.clearTextureAtlas()}}t2.WebglAddon=n})(),s})()); diff --git a/lib/addon-webgl.mjs b/lib/addon-webgl.mjs -index 73e0de80affdf478c39b7ac1d8666e34bd3d6e31..deb7b3b572cd46b7b242bc13cbe241d7eaaa36e8 100644 +index 73e0de80affdf478c39b7ac1d8666e34bd3d6e31..0921a9a2b4e3b29d5168066ce0f255101189fd40 100644 --- a/lib/addon-webgl.mjs +++ b/lib/addon-webgl.mjs -@@ -14,7 +14,7 @@ - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +@@ -9,12 +9,7 @@ + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard +- */ +-/*--------------------------------------------------------------------------------------------- +- * Copyright (c) Microsoft Corporation. All rights reserved. +- * Licensed under the MIT License. See License.txt in the project root for license information. +- *--------------------------------------------------------------------------------------------*/ -function M(i){return{dispose:i}}function Ft(i){if(!i)return i;if(Array.isArray(i)){for(let e of i)e.dispose();return[]}return i.dispose(),i}function De(...i){return M(()=>Ft(i))}var R0=class{constructor(){this._disposables=new Set;this._isDisposed=!1}get isDisposed(){return this._isDisposed}add(e){return this._isDisposed?e.dispose():this._disposables.add(e),e}dispose(){if(!this._isDisposed){this._isDisposed=!0;for(let e of this._disposables)e.dispose();this._disposables.clear()}}clear(){for(let e of this._disposables)e.dispose();this._disposables.clear()}},W=class{constructor(){this._store=new R0}dispose(){this._store.dispose()}_register(e){return this._store.add(e)}};W.None=Object.freeze({dispose(){}});var m0=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,this._value?.dispose(),this._value=void 0}};var Pe=!!(typeof process<"u"&&"title"in process&&(typeof navigator>"u"||navigator.userAgent.startsWith("Node.js/"))),O0=Pe?"node":navigator.userAgent,ce=Pe?"node":navigator.platform,Be=O0.includes("Firefox"),n1=O0.includes("Chrome"),we=O0.includes("Edge"),ue=/^((?!chrome|android).)*safari/i.test(O0);function He(){if(!ue)return 0;let i=O0.match(/Version\/(\d+)/);return i===null||i.length<2?0:parseInt(i[1],10)}var a1=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(ce),o1=["Windows","Win16","Win32","WinCE"].includes(ce),T1=ce.indexOf("Linux")>=0,l1=/\bCrOS\b/.test(O0);var P=0,B=0,w=0,x=0,K={css:"#00000000",rgba:0},Z;(r=>{function i(s,a,o,n){return n!==void 0?`#${E0(s)}${E0(a)}${E0(o)}${E0(n)}`:`#${E0(s)}${E0(a)}${E0(o)}`}r.toCss=i;function e(s,a,o,n=255){return(s<<24|a<<16|o<<8|n)>>>0}r.toRgba=e;function t(s,a,o,n){return{css:r.toCss(s,a,o,n),rgba:r.toRgba(s,a,o,n)}}r.toColor=t})(Z||={});var I0;(n=>{function i(L,T){if(x=(T.rgba&255)/255,x===1)return{css:T.css,rgba:T.rgba};let h=T.rgba>>24&255,d=T.rgba>>16&255,_=T.rgba>>8&255,p=L.rgba>>24&255,c=L.rgba>>16&255,u=L.rgba>>8&255;P=p+Math.round((h-p)*x),B=c+Math.round((d-c)*x),w=u+Math.round((_-u)*x);let C=Z.toCss(P,B,w),Q=Z.toRgba(P,B,w);return{css:C,rgba:Q}}n.blend=i;function e(L){return(L.rgba&255)===255}n.isOpaque=e;function t(L,T,h){let d=d0.ensureContrastRatio(L.rgba,T.rgba,h);if(d)return Z.toColor(d>>24&255,d>>16&255,d>>8&255)}n.ensureContrastRatio=t;function r(L){let T=(L.rgba|255)>>>0;return[P,B,w]=d0.toChannels(T),{css:Z.toCss(P,B,w),rgba:T}}n.opaque=r;function s(L,T){return x=Math.round(T*255),[P,B,w]=d0.toChannels(L.rgba),{css:Z.toCss(P,B,w,x),rgba:Z.toRgba(P,B,w,x)}}n.opacity=s;function a(L,T){return x=L.rgba&255,s(L,x*T/255)}n.multiplyOpacity=a;function o(L){return[L.rgba>>24&255,L.rgba>>16&255,L.rgba>>8&255]}n.toColorRGB=o})(I0||={});var Mt;(r=>{let i,e;try{let s=document.createElement("canvas");s.width=1,s.height=1;let a=s.getContext("2d",{willReadFrequently:!0});a&&(i=a,i.globalCompositeOperation="copy",e=i.createLinearGradient(0,0,1,1))}catch{}function t(s){if(s.match(/#[\da-f]{3,8}/i))switch(s.length){case 4:return P=parseInt(s.slice(1,2).repeat(2),16),B=parseInt(s.slice(2,3).repeat(2),16),w=parseInt(s.slice(3,4).repeat(2),16),Z.toColor(P,B,w);case 5:return P=parseInt(s.slice(1,2).repeat(2),16),B=parseInt(s.slice(2,3).repeat(2),16),w=parseInt(s.slice(3,4).repeat(2),16),x=parseInt(s.slice(4,5).repeat(2),16),Z.toColor(P,B,w,x);case 7:return{css:s,rgba:(parseInt(s.slice(1),16)<<8|255)>>>0};case 9:return{css:s,rgba:parseInt(s.slice(1),16)>>>0}}let a=s.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(a)return P=parseInt(a[1],10),B=parseInt(a[2],10),w=parseInt(a[3],10),x=Math.round((a[5]===void 0?1:parseFloat(a[5]))*255),Z.toColor(P,B,w,x);if(s==="transparent")return{css:"transparent",rgba:0};if(!i||!e)throw new Error("css.toColor: Unsupported css format");if(i.fillStyle=e,i.fillStyle=s,typeof i.fillStyle!="string")throw new Error("css.toColor: Unsupported css format");if(i.fillRect(0,0,1,1),[P,B,w,x]=i.getImageData(0,0,1,1).data,x!==255)throw new Error("css.toColor: Unsupported css format");return{rgba:Z.toRgba(P,B,w,x),css:s}}r.toColor=t})(Mt||={});var V;(t=>{function i(r){return e(r>>16&255,r>>8&255,r&255)}t.relativeLuminance=i;function e(r,s,a){let o=r/255,n=s/255,L=a/255,T=o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4),h=n<=.03928?n/12.92:Math.pow((n+.055)/1.055,2.4),d=L<=.03928?L/12.92:Math.pow((L+.055)/1.055,2.4);return T*.2126+h*.7152+d*.0722}t.relativeLuminance2=e})(V||={});var d0;(a=>{function i(o,n){if(x=(n&255)/255,x===1)return n;let L=n>>24&255,T=n>>16&255,h=n>>8&255,d=o>>24&255,_=o>>16&255,p=o>>8&255;return P=d+Math.round((L-d)*x),B=_+Math.round((T-_)*x),w=p+Math.round((h-p)*x),Z.toRgba(P,B,w)}a.blend=i;function e(o,n,L){let T=V.relativeLuminance(o>>8),h=V.relativeLuminance(n>>8);if(h0(T,h)>8));if(u>8));return u>Q?c:C}return c}let _=r(o,n,L),p=h0(T,V.relativeLuminance(_>>8));if(p>8));return p>u?_:c}return _}}a.ensureContrastRatio=e;function t(o,n,L){let T=o>>24&255,h=o>>16&255,d=o>>8&255,_=n>>24&255,p=n>>16&255,c=n>>8&255,u=h0(V.relativeLuminance2(_,p,c),V.relativeLuminance2(T,h,d));for(;u0||p>0||c>0);)_-=Math.max(0,Math.ceil(_*.1)),p-=Math.max(0,Math.ceil(p*.1)),c-=Math.max(0,Math.ceil(c*.1)),u=h0(V.relativeLuminance2(_,p,c),V.relativeLuminance2(T,h,d));return(_<<24|p<<16|c<<8|255)>>>0}a.reduceLuminance=t;function r(o,n,L){let T=o>>24&255,h=o>>16&255,d=o>>8&255,_=n>>24&255,p=n>>16&255,c=n>>8&255,u=h0(V.relativeLuminance2(_,p,c),V.relativeLuminance2(T,h,d));for(;u>>0}a.increaseLuminance=r;function s(o){return[o>>24&255,o>>16&255,o>>8&255,o&255]}a.toChannels=s})(d0||={});function E0(i){let e=i.toString(16);return e.length<2?"0"+e:e}function h0(i,e){return i=128512&&i<=128591||i>=127744&&i<=128511||i>=128640&&i<=128767||i>=9728&&i<=9983||i>=9984&&i<=10175||i>=65024&&i<=65039||i>=129280&&i<=129535||i>=127462&&i<=127487}function Ze(i,e,t,r){return e===1&&t>Math.ceil(r*1.5)&&i!==void 0&&i>255&&!yt(i)&&!W0(i)&&!At(i)}function V0(i){return W0(i)||gt(i)}function Ue(){return{css:{canvas:U0(),cell:U0()},device:{canvas:U0(),cell:U0(),char:{width:0,height:0,left:0,top:0}}}}function U0(){return{width:0,height:0}}function We(i,e,t=0){return(i-(Math.round(e)*2-t))%(Math.round(e)*2)}var l;(C=>(C.LINE_H=Object.freeze({type:2,data:"M0,.5 L1,.5",strokeWidth:1}),C.LINE_V=Object.freeze({type:2,data:"M.5,0 L.5,1",strokeWidth:1}),C.FADE_RIGHT=Object.freeze({type:2,data:"M0,.5 L.28,.5 M.32,.5 L.52,.5 M.60,.5 L.72,.5 M.84,.5 L.90,.5",strokeWidth:1}),C.FADE_LEFT=Object.freeze({type:2,data:"M.10,.5 L.16,.5 M.28,.5 L.40,.5 M.48,.5 L.68,.5 M.72,.5 L1,.5",strokeWidth:1}),C.FADE_DOWN=Object.freeze({type:2,data:"M.5,0 L.5,.28 M.5,.32 L.5,.52 M.5,.60 L.5,.72 M.5,.84 L.5,.90",strokeWidth:1}),C.FADE_UP=Object.freeze({type:2,data:"M.5,.10 L.5,.16 M.5,.28 L.5,.40 M.5,.48 L.5,.68 M.5,.72 L.5,1",strokeWidth:1}),C.CURVE_DOWN_RIGHT=Object.freeze({type:2,data:(Q,m)=>`M.5,1 L.5,${.5+m/.15*.5} C.5,${.5+m/.15*.5},.5,.5,1,.5`,strokeWidth:1}),C.CURVE_DOWN_LEFT=Object.freeze({type:2,data:(Q,m)=>`M.5,1 L.5,${.5+m/.15*.5} C.5,${.5+m/.15*.5},.5,.5,0,.5`,strokeWidth:1}),C.CURVE_UP_RIGHT=Object.freeze({type:2,data:(Q,m)=>`M.5,0 L.5,${.5-m/.15*.5} C.5,${.5-m/.15*.5},.5,.5,1,.5`,strokeWidth:1}),C.CURVE_UP_LEFT=Object.freeze({type:2,data:(Q,m)=>`M.5,0 L.5,${.5-m/.15*.5} C.5,${.5-m/.15*.5},.5,.5,0,.5`,strokeWidth:1}),C.NODE_FILL=Object.freeze({type:3,data:"M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5"}),C.NODE_STROKE=Object.freeze({type:3,data:"M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5",strokeWidth:1}),C.NODE_LINE_RIGHT=Object.freeze({type:3,data:"M1,.5 L.85,.5",strokeWidth:1}),C.NODE_LINE_LEFT=Object.freeze({type:3,data:"M0,.5 L.15,.5",strokeWidth:1}),C.NODE_LINE_DOWN=Object.freeze({type:3,data:"M.5,1 L.5,.7",strokeWidth:1}),C.NODE_LINE_UP=Object.freeze({type:3,data:"M.5,0 L.5,.3",strokeWidth:1})))(l||={});var Ve={"\u2500":{type:2,data:"M0,.5 L1,.5",strokeWidth:1},"\u2501":{type:2,data:"M0,.5 L1,.5",strokeWidth:3},"\u2502":{type:2,data:"M.5,0 L.5,1",strokeWidth:1},"\u2503":{type:2,data:"M.5,0 L.5,1",strokeWidth:3},"\u2504":{type:2,data:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",strokeWidth:1},"\u2505":{type:2,data:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",strokeWidth:3},"\u2506":{type:2,data:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",strokeWidth:1},"\u2507":{type:2,data:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",strokeWidth:3},"\u2508":{type:2,data:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",strokeWidth:1},"\u2509":{type:2,data:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",strokeWidth:3},"\u250A":{type:2,data:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",strokeWidth:1},"\u250B":{type:2,data:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",strokeWidth:3},"\u250C":{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},"\u250D":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u250E":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u250F":{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3},"\u2510":{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},"\u2511":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u2512":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2513":{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3},"\u2514":{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},"\u2515":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u2516":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2517":{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3},"\u2518":{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},"\u2519":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u251A":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u251B":{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3},"\u251C":{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:1},"\u251D":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u251E":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u251F":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2520":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"\u2521":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"\u2522":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"\u2523":{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:3},"\u2524":{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:1},"\u2525":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u2526":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2527":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2528":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"\u2529":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"\u252A":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"\u252B":{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:3},"\u252C":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:1},"\u252D":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u252E":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u252F":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"\u2530":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2531":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"\u2532":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"\u2533":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:3},"\u2534":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,0",strokeWidth:1},"\u2535":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u2536":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u2537":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"\u2538":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2539":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"\u253A":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"\u253B":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,0",strokeWidth:3},"\u253C":{type:2,data:"M0,.5 L1,.5 M.5,0 L.5,1",strokeWidth:1},"\u253D":[{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u253E":[{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u253F":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"\u2540":[{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2541":[{type:2,data:"M.5,.5 L.5,0 M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2542":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"\u2543":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"\u2544":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"\u2545":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"\u2546":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"\u2547":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0 M0,.5 L1,.5",strokeWidth:3}],"\u2548":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:3}],"\u2549":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:3}],"\u254A":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:3}],"\u254B":{type:2,data:"M0,.5 L1,.5 M.5,0 L.5,1",strokeWidth:3},"\u254C":{type:2,data:"M.1,.5 L.4,.5 M.6,.5 L.9,.5",strokeWidth:1},"\u254D":{type:2,data:"M.1,.5 L.4,.5 M.6,.5 L.9,.5",strokeWidth:3},"\u254E":{type:2,data:"M.5,.1 L.5,.4 M.5,.6 L.5,.9",strokeWidth:1},"\u254F":{type:2,data:"M.5,.1 L.5,.4 M.5,.6 L.5,.9",strokeWidth:3},"\u2550":{type:2,data:(i,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u2551":{type:2,data:(i,e)=>`M${.5-i},0 L${.5-i},1 M${.5+i},0 L${.5+i},1`,strokeWidth:1},"\u2552":{type:2,data:(i,e)=>`M.5,1 L.5,${.5-e} L1,${.5-e} M.5,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u2553":{type:2,data:(i,e)=>`M${.5-i},1 L${.5-i},.5 L1,.5 M${.5+i},.5 L${.5+i},1`,strokeWidth:1},"\u2554":{type:2,data:(i,e)=>`M1,${.5-e} L${.5-i},${.5-e} L${.5-i},1 M1,${.5+e} L${.5+i},${.5+e} L${.5+i},1`,strokeWidth:1},"\u2555":{type:2,data:(i,e)=>`M0,${.5-e} L.5,${.5-e} L.5,1 M0,${.5+e} L.5,${.5+e}`,strokeWidth:1},"\u2556":{type:2,data:(i,e)=>`M${.5+i},1 L${.5+i},.5 L0,.5 M${.5-i},.5 L${.5-i},1`,strokeWidth:1},"\u2557":{type:2,data:(i,e)=>`M0,${.5+e} L${.5-i},${.5+e} L${.5-i},1 M0,${.5-e} L${.5+i},${.5-e} L${.5+i},1`,strokeWidth:1},"\u2558":{type:2,data:(i,e)=>`M.5,0 L.5,${.5+e} L1,${.5+e} M.5,${.5-e} L1,${.5-e}`,strokeWidth:1},"\u2559":{type:2,data:(i,e)=>`M1,.5 L${.5-i},.5 L${.5-i},0 M${.5+i},.5 L${.5+i},0`,strokeWidth:1},"\u255A":{type:2,data:(i,e)=>`M1,${.5-e} L${.5+i},${.5-e} L${.5+i},0 M1,${.5+e} L${.5-i},${.5+e} L${.5-i},0`,strokeWidth:1},"\u255B":{type:2,data:(i,e)=>`M0,${.5+e} L.5,${.5+e} L.5,0 M0,${.5-e} L.5,${.5-e}`,strokeWidth:1},"\u255C":{type:2,data:(i,e)=>`M0,.5 L${.5+i},.5 L${.5+i},0 M${.5-i},.5 L${.5-i},0`,strokeWidth:1},"\u255D":{type:2,data:(i,e)=>`M0,${.5-e} L${.5-i},${.5-e} L${.5-i},0 M0,${.5+e} L${.5+i},${.5+e} L${.5+i},0`,strokeWidth:1},"\u255E":{type:2,data:(i,e)=>`M.5,0 L.5,1 M.5,${.5-e} L1,${.5-e} M.5,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u255F":{type:2,data:(i,e)=>`M${.5-i},0 L${.5-i},1 M${.5+i},0 L${.5+i},1 M${.5+i},.5 L1,.5`,strokeWidth:1},"\u2560":{type:2,data:(i,e)=>`M${.5-i},0 L${.5-i},1 M1,${.5+e} L${.5+i},${.5+e} L${.5+i},1 M1,${.5-e} L${.5+i},${.5-e} L${.5+i},0`,strokeWidth:1},"\u2561":{type:2,data:(i,e)=>`M.5,0 L.5,1 M0,${.5-e} L.5,${.5-e} M0,${.5+e} L.5,${.5+e}`,strokeWidth:1},"\u2562":{type:2,data:(i,e)=>`M0,.5 L${.5-i},.5 M${.5-i},0 L${.5-i},1 M${.5+i},0 L${.5+i},1`,strokeWidth:1},"\u2563":{type:2,data:(i,e)=>`M${.5+i},0 L${.5+i},1 M0,${.5+e} L${.5-i},${.5+e} L${.5-i},1 M0,${.5-e} L${.5-i},${.5-e} L${.5-i},0`,strokeWidth:1},"\u2564":{type:2,data:(i,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e} M.5,${.5+e} L.5,1`,strokeWidth:1},"\u2565":{type:2,data:(i,e)=>`M0,.5 L1,.5 M${.5-i},.5 L${.5-i},1 M${.5+i},.5 L${.5+i},1`,strokeWidth:1},"\u2566":{type:2,data:(i,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L${.5-i},${.5+e} L${.5-i},1 M1,${.5+e} L${.5+i},${.5+e} L${.5+i},1`,strokeWidth:1},"\u2567":{type:2,data:(i,e)=>`M.5,0 L.5,${.5-e} M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u2568":{type:2,data:(i,e)=>`M0,.5 L1,.5 M${.5-i},.5 L${.5-i},0 M${.5+i},.5 L${.5+i},0`,strokeWidth:1},"\u2569":{type:2,data:(i,e)=>`M0,${.5+e} L1,${.5+e} M0,${.5-e} L${.5-i},${.5-e} L${.5-i},0 M1,${.5-e} L${.5+i},${.5-e} L${.5+i},0`,strokeWidth:1},"\u256A":{type:2,data:(i,e)=>`M.5,0 L.5,1 M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u256B":{type:2,data:(i,e)=>`M0,.5 L1,.5 M${.5-i},0 L${.5-i},1 M${.5+i},0 L${.5+i},1`,strokeWidth:1},"\u256C":{type:2,data:(i,e)=>`M0,${.5+e} L${.5-i},${.5+e} L${.5-i},1 M1,${.5+e} L${.5+i},${.5+e} L${.5+i},1 M0,${.5-e} L${.5-i},${.5-e} L${.5-i},0 M1,${.5-e} L${.5+i},${.5-e} L${.5+i},0`,strokeWidth:1},"\u256D":{type:2,data:(i,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,1,.5`,strokeWidth:1},"\u256E":{type:2,data:(i,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,0,.5`,strokeWidth:1},"\u256F":{type:2,data:(i,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,0,.5`,strokeWidth:1},"\u2570":{type:2,data:(i,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,1,.5`,strokeWidth:1},"\u2571":{type:2,data:"M1,0 L0,1",strokeWidth:1},"\u2572":{type:2,data:"M0,0 L1,1",strokeWidth:1},"\u2573":{type:2,data:"M1,0 L0,1 M0,0 L1,1",strokeWidth:1},"\u2574":{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},"\u2575":{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},"\u2576":{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},"\u2577":{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},"\u2578":{type:2,data:"M.5,.5 L0,.5",strokeWidth:3},"\u2579":{type:2,data:"M.5,.5 L.5,0",strokeWidth:3},"\u257A":{type:2,data:"M.5,.5 L1,.5",strokeWidth:3},"\u257B":{type:2,data:"M.5,.5 L.5,1",strokeWidth:3},"\u257C":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u257D":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u257E":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u257F":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2580":{type:0,data:[{x:0,y:0,w:8,h:4}]},"\u2581":{type:0,data:[{x:0,y:7,w:8,h:1}]},"\u2582":{type:0,data:[{x:0,y:6,w:8,h:2}]},"\u2583":{type:0,data:[{x:0,y:5,w:8,h:3}]},"\u2584":{type:0,data:[{x:0,y:4,w:8,h:4}]},"\u2585":{type:0,data:[{x:0,y:3,w:8,h:5}]},"\u2586":{type:0,data:[{x:0,y:2,w:8,h:6}]},"\u2587":{type:0,data:[{x:0,y:1,w:8,h:7}]},"\u2588":{type:0,data:[{x:0,y:0,w:8,h:8}]},"\u2589":{type:0,data:[{x:0,y:0,w:7,h:8}]},"\u258A":{type:0,data:[{x:0,y:0,w:6,h:8}]},"\u258B":{type:0,data:[{x:0,y:0,w:5,h:8}]},"\u258C":{type:0,data:[{x:0,y:0,w:4,h:8}]},"\u258D":{type:0,data:[{x:0,y:0,w:3,h:8}]},"\u258E":{type:0,data:[{x:0,y:0,w:2,h:8}]},"\u258F":{type:0,data:[{x:0,y:0,w:1,h:8}]},"\u2590":{type:0,data:[{x:4,y:0,w:4,h:8}]},"\u2591":{type:1,data:[[1,0],[0,0]]},"\u2592":{type:1,data:[[1,0],[0,1]]},"\u2593":{type:1,data:[[1,1],[1,0]]},"\u2594":{type:0,data:[{x:0,y:0,w:8,h:1}]},"\u2595":{type:0,data:[{x:7,y:0,w:1,h:8}]},"\u2596":{type:0,data:[{x:0,y:4,w:4,h:4}]},"\u2597":{type:0,data:[{x:4,y:4,w:4,h:4}]},"\u2598":{type:0,data:[{x:0,y:0,w:4,h:4}]},"\u2599":{type:0,data:[{x:0,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}]},"\u259A":{type:0,data:[{x:0,y:0,w:4,h:4},{x:4,y:4,w:4,h:4}]},"\u259B":{type:0,data:[{x:0,y:0,w:4,h:8},{x:4,y:0,w:4,h:4}]},"\u259C":{type:0,data:[{x:0,y:0,w:8,h:4},{x:4,y:0,w:4,h:8}]},"\u259D":{type:0,data:[{x:4,y:0,w:4,h:4}]},"\u259E":{type:0,data:[{x:4,y:0,w:4,h:4},{x:0,y:4,w:4,h:4}]},"\u259F":{type:0,data:[{x:4,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}]},"\uE0A0":{type:5,data:{d:"M.3,1 L.03,1 L.03,.88 C.03,.82,.06,.78,.11,.73 C.15,.7,.2,.68,.28,.65 L.43,.6 C.49,.58,.53,.56,.56,.53 C.59,.5,.6,.47,.6,.43 L.6,.27 L.4,.27 L.69,.1 L.98,.27 L.78,.27 L.78,.46 C.78,.52,.76,.56,.72,.61 C.68,.66,.63,.67,.56,.7 L.48,.72 C.42,.74,.38,.76,.35,.78 C.32,.8,.31,.84,.31,.88 L.31,1 M.3,.5 L.03,.59 L.03,.09 L.3,.09 L.3,.655",type:0}},"\uE0A1":{type:5,data:{d:"M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.355,.03 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5",type:0}},"\uE0A2":{type:5,data:{d:"M.25,.94 C.16,.94,.11,.92,.11,.87 L.11,.53 C.11,.48,.15,.455,.23,.45 L.23,.3 C.23,.25,.26,.22,.31,.19 C.36,.16,.43,.15,.51,.15 C.59,.15,.66,.16,.71,.19 C.77,.22,.79,.26,.79,.3 L.79,.45 C.87,.45,.91,.48,.91,.53 L.91,.87 C.91,.92,.86,.94,.77,.94 L.24,.94 M.53,.2 C.49,.2,.45,.21,.42,.23 C.39,.25,.38,.27,.38,.3 L.38,.45 L.68,.45 L.68,.3 C.68,.27,.67,.25,.64,.23 C.61,.21,.58,.2,.53,.2 M.58,.82 L.58,.66 C.63,.65,.65,.63,.65,.6 C.65,.58,.64,.57,.61,.56 C.58,.55,.56,.54,.52,.54 C.48,.54,.46,.55,.43,.56 C.4,.57,.39,.59,.39,.6 C.39,.63,.41,.64,.46,.66 L.46,.82 L.57,.82",type:0}},"\uE0A3":{type:5,data:{d:"M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.7,.03 L.7,.1 L.355,.1 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5",type:0}},"\uE0B0":{type:5,data:{d:"M0,0 L1,.5 L0,1",type:0,rightPadding:2}},"\uE0B1":{type:5,data:{d:"M-1,-.5 L1,.5 L-1,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0B2":{type:5,data:{d:"M1,0 L0,.5 L1,1",type:0,leftPadding:2}},"\uE0B3":{type:5,data:{d:"M2,-.5 L0,.5 L2,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0B4":{type:5,data:{d:"M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0",type:0,rightPadding:1}},"\uE0B5":{type:5,data:{d:"M.2,1 C.422,1,.8,.826,.78,.5 C.8,.174,0.422,0,.2,0",type:1,rightPadding:1}},"\uE0B6":{type:5,data:{d:"M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0",type:0,leftPadding:1}},"\uE0B7":{type:5,data:{d:"M.8,1 C0.578,1,0.2,.826,.22,.5 C0.2,0.174,0.578,0,0.8,0",type:1,leftPadding:1}},"\uE0B8":{type:5,data:{d:"M-.5,-.5 L1.5,1.5 L-.5,1.5",type:0}},"\uE0B9":{type:5,data:{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0BA":{type:5,data:{d:"M1.5,-.5 L-.5,1.5 L1.5,1.5",type:0}},"\uE0BB":{type:5,data:{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0BC":{type:5,data:{d:"M1.5,-.5 L-.5,1.5 L-.5,-.5",type:0}},"\uE0BD":{type:5,data:{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0BE":{type:5,data:{d:"M-.5,-.5 L1.5,1.5 L1.5,-.5",type:0}},"\uE0BF":{type:5,data:{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0C0":{type:5,data:{d:"M0.8069,0.6075 Q0.7991,0.612,0.7815,0.6218 T0.7542,0.6369 T0.7282,0.6508 T0.7016,0.6644 T0.6778,0.6758 T0.6551,0.6854 T0.637,0.6911 T0.622,0.6936 Q0.6186,0.6936,0.6128,0.6942 T0.6018,0.6952 T0.5894,0.6956 T0.5757,0.6952 T0.561,0.693 T0.5451,0.6879 Q0.5342,0.6834,0.5193,0.6756 T0.4925,0.6614 T0.4673,0.6485 T0.4436,0.6389 T0.4239,0.6363 T0.4089,0.6422 Q0.4045,0.6467,0.3958,0.6554 T0.3843,0.6669 T0.3748,0.6746 T0.3613,0.683 T0.3442,0.6903 L0.3499,0.6956 Q0.3584,0.7042,0.3679,0.7177 T0.3836,0.7403 T0.4019,0.7583 T0.4295,0.7703 Q0.4688,0.7785,0.5203,0.7605 Q0.4902,0.7817,0.449,0.7911 T0.3696,0.8001 Q0.3567,0.7997,0.341,0.7942 T0.3111,0.7813 T0.2805,0.766 T0.2493,0.753 T0.218,0.7472 T0.187,0.754 Q0.1762,0.7589,0.1653,0.7681 T0.1453,0.7895 T0.1308,0.818 T0.1264,0.8507 L0.1264,0.858 Q0.1453,0.8462,0.1607,0.8433 T0.1899,0.8452 T0.2226,0.8625 Q0.2344,0.8707,0.2485,0.8752 T0.2813,0.8831 T0.3089,0.889 Q0.2981,0.8915,0.2846,0.8956 T0.2603,0.9037 T0.2368,0.9115 T0.2129,0.9174 T0.1894,0.9194 T0.1653,0.9155 Q0.1504,0.9115,0.1328,0.9196 T0.0986,0.9421 T0.0655,0.9708 T0.0317,0.9943 T0,1 Q0.001,0.94,0.0017,0.6508 T0.0024,0.2322 Q0.002,0.2109,0.0007,0.1379 T0,0.0249 Q0.0058,0.0253,0.009,0.0257 T0.0156,0.0288 T0.0203,0.0333 T0.0264,0.0422 T0.0349,0.0539 Q0.044,0.0657,0.0542,0.072 T0.0752,0.0789 T0.096,0.0775 T0.1175,0.0694 T0.1379,0.0573 T0.1579,0.0432 Q0.2141,0,0.2673,0.0318 Q0.2588,0.0318,0.2517,0.033 T0.239,0.0361 T0.2276,0.0424 T0.218,0.0504 T0.2085,0.0612 T0.1997,0.0732 T0.1899,0.0875 T0.1795,0.1028 Q0.1748,0.1093,0.1507,0.1234 T0.1186,0.1448 Q0.0999,0.1612,0.1108,0.1783 Q0.1162,0.1877,0.1238,0.1928 T0.1404,0.1979 T0.158,0.1967 T0.1775,0.1895 T0.1963,0.1791 T0.2151,0.1663 T0.2314,0.1534 T0.2458,0.141 T0.2561,0.1322 Q0.2815,0.1106,0.3017,0.101 T0.344,0.0926 T0.3919,0.1077 Q0.4099,0.1175,0.4265,0.1206 T0.4588,0.1202 T0.488,0.1098 T0.5176,0.0914 Q0.5071,0.1032,0.4963,0.1122 T0.4722,0.1285 T0.4497,0.1401 T0.4223,0.1518 T0.3943,0.1636 Q0.374,0.1726,0.3623,0.1863 T0.3482,0.2162 Q0.3445,0.2403,0.3608,0.2605 T0.4072,0.2876 Q0.4194,0.2909,0.4326,0.2899 T0.457,0.2856 T0.4805,0.2764 T0.5032,0.264 T0.5256,0.2497 T0.5478,0.2352 T0.5698,0.2222 T0.5921,0.2118 Q0.6982,0.173,0.81,0.2436 Q0.833,0.2583,0.8496,0.2674 T0.887,0.2846 T0.925,0.294 T0.9617,0.2911 T1,0.2746 Q0.9817,0.2946,0.96,0.3056 T0.9175,0.3191 T0.8731,0.3209 T0.8277,0.3164 T0.7818,0.3109 T0.7359,0.3101 T0.6907,0.3197 T0.647,0.3448 Q0.6443,0.3472,0.6333,0.3562 T0.6194,0.3674 T0.6076,0.3766 T0.594,0.386 T0.5815,0.3931 T0.5661,0.4007 T0.5501,0.4064 L0.5556,0.4121 Q0.5627,0.4198,0.5722,0.4251 T0.5926,0.4337 T0.6131,0.439 T0.6343,0.4431 T0.6521,0.4468 Q0.6762,0.4529,0.7056,0.4463 Q0.6772,0.4623,0.6543,0.471 T0.604,0.4814 Q0.5962,0.4818,0.5815,0.4825 T0.5591,0.4835 T0.5393,0.4857 T0.519,0.4908 T0.5007,0.4996 T0.4812,0.5141 T0.4627,0.5353 L0.4549,0.5451 L0.4661,0.543 Q0.4759,0.541,0.4909,0.55 T0.5315,0.5796 T0.5684,0.6083 Q0.5799,0.6161,0.5915,0.6212 T0.6181,0.6308 L0.6331,0.6353 Q0.6514,0.6418,0.7004,0.633 T0.8069,0.6075 Z",type:0}},"\uE0C1":{type:5,data:{d:"M0.011,0.0469 Q0.0152,0.0412,0.02,0.0361 T0.0292,0.0281 T0.0365,0.0253 Q0.0426,0.0257,0.0461,0.0263 T0.0534,0.03 T0.0582,0.0345 T0.0645,0.0434 T0.0723,0.0546 Q0.0807,0.0653,0.0897,0.0712 T0.1081,0.0775 T0.1255,0.0765 T0.1438,0.0695 T0.1605,0.0593 T0.1772,0.0473 Q0.2388,0,0.2927,0.0326 L0.2614,0.0767 L0.2581,0.0767 Q0.2494,0.0767,0.2444,0.0773 T0.2359,0.0789 T0.2291,0.0842 T0.2233,0.0922 T0.2146,0.1054 T0.2026,0.1236 Q0.1943,0.1354,0.1843,0.1444 T0.1675,0.1574 T0.1526,0.1654 T0.1429,0.1701 Q0.1436,0.1717,0.1446,0.1733 Q0.1504,0.1823,0.1581,0.1866 T0.1733,0.1915 T0.1909,0.1884 T0.2085,0.1809 T0.2265,0.169 T0.2427,0.1564 T0.2575,0.1436 T0.2688,0.1338 Q0.2969,0.1101,0.3195,0.0995 T0.3643,0.0901 T0.4114,0.1048 Q0.4279,0.1138,0.4426,0.1168 T0.4705,0.117 T0.4945,0.1089 T0.5186,0.0942 L0.5211,0.0926 L0.5289,0.0922 Q0.5411,0.095,0.5189,0.1203 Q0.505,0.1358,0.4913,0.1474 T0.4634,0.167 T0.4389,0.1796 T0.4121,0.1909 T0.3866,0.2011 Q0.3782,0.2047,0.3727,0.2092 Q0.3717,0.2117,0.3714,0.2141 Q0.3695,0.2263,0.3751,0.2388 T0.394,0.2616 T0.4253,0.2761 Q0.4398,0.2785,0.4513,0.2779 T0.4756,0.2724 T0.4987,0.2616 T0.5216,0.2476 T0.5453,0.2321 T0.5703,0.217 T0.5976,0.2043 Q0.7015,0.166,0.8099,0.2345 Q0.8361,0.2512,0.8572,0.2616 T0.9006,0.2779 T0.9432,0.281 T0.9819,0.2663 L0.9887,0.2671 Q1,0.272,0.9768,0.2969 Q0.9558,0.3193,0.9319,0.333 T0.8858,0.3513 T0.8391,0.3569 T0.793,0.3546 T0.7485,0.3497 T0.7065,0.3473 T0.6681,0.3522 T0.6344,0.3699 Q0.5963,0.4005,0.5847,0.4082 Q0.595,0.4139,0.6079,0.4174 T0.6367,0.4237 T0.6586,0.4278 Q0.6793,0.4331,0.7044,0.4278 Q0.7057,0.4278,0.707,0.4274 Q0.7067,0.429,0.7062,0.4313 T0.7036,0.4398 T0.6993,0.4513 T0.6931,0.4619 T0.6851,0.4698 Q0.6573,0.4857,0.6336,0.4947 T0.5831,0.5053 Q0.577,0.5057,0.5615,0.5065 T0.5394,0.508 T0.5221,0.51 T0.5042,0.5139 T0.4905,0.5204 Q0.5008,0.5232,0.515,0.5328 T0.549,0.5585 T0.5799,0.5824 Q0.5905,0.5897,0.6013,0.5946 T0.6262,0.6036 T0.6409,0.6081 Q0.6567,0.6134,0.7035,0.605 T0.8028,0.5816 L0.7974,0.5922 T0.7867,0.6134 L0.7812,0.624 Q0.7799,0.6248,0.7786,0.6252 Q0.7709,0.6297,0.7573,0.6372 T0.7346,0.6499 T0.7123,0.6621 T0.6897,0.6741 T0.6686,0.6847 T0.648,0.6941 T0.6297,0.7013 T0.6129,0.7062 T0.5995,0.7076 Q0.5992,0.7076,0.5831,0.7092 T0.5529,0.7092 T0.525,0.7019 Q0.5105,0.6958,0.4824,0.6811 T0.4356,0.6586 T0.4079,0.6533 Q0.3872,0.6741,0.3827,0.6782 Q0.3872,0.6835,0.3934,0.6929 T0.4037,0.7078 T0.4142,0.7198 T0.4284,0.7304 T0.4466,0.7365 Q0.4824,0.7443,0.5292,0.728 L0.5292,0.7294 T0.529,0.7333 T0.5284,0.7392 T0.5266,0.7463 T0.5236,0.7539 T0.5186,0.7612 T0.5111,0.7679 Q0.5086,0.77,0.5056,0.7716 Q0.4466,0.8104,0.3591,0.8091 Q0.344,0.8091,0.3246,0.802 T0.2893,0.7863 T0.2543,0.77 T0.2206,0.76 T0.1894,0.7643 Q0.1804,0.7684,0.1733,0.7753 Q0.1591,0.7892,0.1601,0.8169 Q0.1784,0.8067,0.1938,0.8053 T0.2212,0.8087 T0.2507,0.825 Q0.2614,0.8324,0.2744,0.8365 T0.3053,0.844 T0.332,0.8499 L0.303,0.894 L0.2998,0.8948 Q0.2904,0.8968,0.278,0.9009 T0.2554,0.9086 T0.233,0.9158 T0.2099,0.9213 T0.1868,0.9229 T0.1633,0.9192 Q0.1517,0.916,0.1367,0.9241 T0.1065,0.9462 T0.0753,0.9737 T0.041,0.9955 T0.0061,1 Q0,0.9984,0.0068,0.9845 Q0.009,0.9804,0.0123,0.9755 Q0.0187,0.9666,0.0261,0.9606 T0.0374,0.9555 Q0.0481,0.958,0.0629,0.949 T0.0931,0.9262 T0.1247,0.8987 T0.1597,0.8777 T0.1949,0.8752 Q0.213,0.8805,0.2343,0.8772 Q0.2249,0.8736,0.2172,0.8683 Q0.1914,0.8511,0.1738,0.8497 T0.1349,0.8617 Q0.1304,0.865,0.1273,0.8646 T0.1242,0.8605 L0.1239,0.8536 Q0.1226,0.8295,0.1354,0.8016 T0.1723,0.7496 Q0.1914,0.7308,0.2104,0.7219 Q0.2259,0.7145,0.2425,0.7141 T0.273,0.7182 T0.3022,0.7296 T0.3301,0.7439 T0.3569,0.7571 T0.3821,0.7643 Q0.373,0.7561,0.3598,0.7367 T0.3382,0.7088 L0.3327,0.7035 Q0.3282,0.699,0.3353,0.6884 T0.3517,0.6692 L0.3611,0.6607 Q0.3711,0.657,0.3751,0.6554 T0.3837,0.6507 T0.3898,0.6458 T0.3979,0.637 T0.4111,0.6236 Q0.4172,0.6175,0.4234,0.6138 Q0.4327,0.6081,0.4439,0.6077 T0.4655,0.6105 T0.4884,0.6201 T0.5113,0.633 T0.5347,0.6468 T0.5573,0.6578 Q0.5705,0.6635,0.5833,0.665 T0.6112,0.665 T0.6286,0.6635 Q0.6421,0.6631,0.6718,0.6501 Q0.626,0.6578,0.6089,0.6517 Q0.6063,0.6509,0.6002,0.6491 T0.5884,0.6454 T0.5757,0.6409 T0.561,0.6344 T0.5466,0.6256 Q0.536,0.6183,0.5108,0.5989 T0.4722,0.5712 T0.4527,0.564 L0.4418,0.5661 Q0.4308,0.5681,0.4366,0.5563 Q0.4401,0.5485,0.4476,0.5392 L0.455,0.5298 Q0.4631,0.5188,0.4714,0.51 T0.4866,0.4947 T0.5021,0.4831 T0.516,0.4749 T0.53,0.4694 T0.5428,0.4657 T0.5558,0.4637 T0.5674,0.4629 T0.5795,0.4625 Q0.5489,0.4543,0.534,0.4384 L0.5286,0.4327 Q0.5244,0.4282,0.5318,0.4174 T0.5486,0.398 L0.5579,0.3899 Q0.567,0.387,0.5749,0.3834 T0.5878,0.3772 T0.6012,0.3687 T0.6126,0.3603 T0.6268,0.3487 T0.6421,0.3361 Q0.6567,0.3242,0.6712,0.3159 T0.7009,0.3034 T0.7277,0.2973 T0.7556,0.2961 T0.7809,0.2977 T0.8074,0.301 Q0.8096,0.3014,0.8109,0.3016 T0.8143,0.302 T0.818,0.3026 Q0.8012,0.2936,0.7767,0.2781 Q0.6718,0.2117,0.5744,0.2476 Q0.5637,0.2516,0.5492,0.2604 T0.5218,0.2779 T0.4923,0.2965 T0.461,0.3124 T0.4282,0.3216 T0.394,0.3206 Q0.3637,0.3136,0.3474,0.2936 T0.3353,0.2459 Q0.3385,0.2247,0.355,0.2023 Q0.3646,0.1892,0.3763,0.179 Q0.3898,0.1672,0.4063,0.1595 Q0.3927,0.1562,0.3785,0.1485 Q0.3604,0.1387,0.3453,0.1354 T0.3161,0.1354 T0.2899,0.1458 T0.2627,0.166 Q0.262,0.1664,0.254,0.1733 T0.2414,0.1841 T0.2267,0.196 T0.2094,0.2086 T0.1915,0.2196 T0.1725,0.2288 T0.1546,0.2337 T0.1373,0.2343 T0.1223,0.228 T0.1097,0.2145 Q0.0962,0.1929,0.1258,0.1582 Q0.131,0.1517,0.1371,0.1464 Q0.1462,0.1387,0.156,0.1321 T0.1752,0.1205 T0.1878,0.1134 Q0.1891,0.1117,0.1947,0.103 T0.2035,0.0899 T0.2125,0.0781 T0.2243,0.064 Q0.1949,0.0616,0.1672,0.0828 Q0.153,0.0934,0.1412,0.1009 T0.1146,0.1146 T0.0873,0.1213 T0.0616,0.116 T0.0374,0.0962 Q0.0339,0.0909,0.0295,0.0846 T0.024,0.0767 T0.0202,0.0728 T0.0148,0.0704 T0.0068,0.0697 Q0.0003,0.0693,0.0058,0.0567 Q0.0077,0.0522,0.011,0.0469 Z",type:0}},"\uE0C2":{type:5,data:{d:"M0.1934,0.6075 Q0.2012,0.612,0.2188,0.6218 T0.2459,0.6369 T0.2718,0.6508 T0.2983,0.6644 T0.322,0.6758 T0.3447,0.6854 T0.363,0.6911 T0.3783,0.6936 Q0.3796,0.6936,0.396,0.6952 T0.4265,0.6952 T0.4548,0.6879 Q0.4656,0.6834,0.4805,0.6756 T0.5073,0.6614 T0.5327,0.6485 T0.5564,0.6389 T0.5759,0.6363 T0.5909,0.6422 Q0.5953,0.6467,0.604,0.6554 T0.6155,0.6669 T0.6251,0.6746 T0.6387,0.683 T0.6556,0.6903 L0.6498,0.6956 Q0.6414,0.7042,0.6319,0.7177 T0.6162,0.7403 T0.5979,0.7583 T0.5703,0.7703 Q0.5313,0.7785,0.4799,0.7605 Q0.5097,0.7817,0.5508,0.7911 T0.6302,0.8001 Q0.6431,0.7997,0.659,0.7942 T0.6888,0.7813 T0.7194,0.766 T0.7506,0.753 T0.7819,0.7472 T0.8131,0.754 Q0.8239,0.7589,0.8346,0.7681 T0.8544,0.7895 T0.8689,0.818 T0.8737,0.8507 L0.8733,0.858 Q0.8591,0.849,0.8474,0.845 T0.8242,0.8421 T0.8021,0.8482 T0.7772,0.8625 Q0.7653,0.8707,0.7513,0.8752 T0.7184,0.8831 T0.6908,0.889 Q0.7017,0.8915,0.7152,0.8956 T0.7396,0.9037 T0.7633,0.9115 T0.7872,0.9174 T0.8107,0.9194 T0.8347,0.9155 Q0.8493,0.9115,0.8671,0.9196 T0.9015,0.9421 T0.9345,0.9708 T0.9682,0.9943 T0.9997,1 Q0.999,0.94,0.9983,0.6508 T0.9976,0.2322 Q0.9976,0.2109,0.9992,0.1379 T1,0.0249 Q0.9949,0.0249,0.9917,0.0257 T0.9863,0.0273 T0.9817,0.0308 T0.9776,0.0357 T0.9721,0.0437 T0.9651,0.0539 Q0.956,0.0657,0.9456,0.072 T0.9247,0.0789 T0.904,0.0775 T0.8823,0.0694 T0.862,0.0573 T0.8422,0.0432 Q0.7856,0,0.7328,0.0318 Q0.7392,0.0318,0.7452,0.0324 T0.756,0.0345 T0.7657,0.0386 T0.7741,0.0437 T0.7821,0.0506 T0.7894,0.0588 T0.7966,0.0683 T0.8039,0.0787 T0.8119,0.0904 T0.8202,0.1028 Q0.8249,0.1093,0.849,0.1234 T0.8811,0.1448 Q0.8998,0.1612,0.8893,0.1783 Q0.8835,0.1877,0.8759,0.1928 T0.8593,0.1979 T0.8417,0.1967 T0.8222,0.1895 T0.8034,0.1791 T0.7848,0.1663 T0.7687,0.1534 T0.7543,0.141 T0.744,0.1322 Q0.7186,0.1106,0.6984,0.101 T0.6559,0.0926 T0.6082,0.1077 Q0.5899,0.1175,0.5733,0.1206 T0.5411,0.1202 T0.5122,0.1098 T0.4822,0.0914 Q0.4927,0.1032,0.5036,0.1122 T0.5276,0.1285 T0.5503,0.1401 T0.5777,0.1518 T0.6055,0.1636 Q0.6258,0.1726,0.6375,0.1863 T0.6519,0.2162 Q0.6556,0.2403,0.6392,0.2605 T0.5926,0.2876 Q0.5804,0.2909,0.5672,0.2899 T0.5428,0.2856 T0.5193,0.2764 T0.4966,0.264 T0.4744,0.2497 T0.4523,0.2352 T0.4301,0.2222 T0.4077,0.2118 Q0.3021,0.173,0.1903,0.2436 Q0.1669,0.2583,0.1504,0.2674 T0.1129,0.2846 T0.075,0.294 T0.0384,0.2911 T0,0.2746 Q0.021,0.297,0.0459,0.3082 T0.0953,0.3207 T0.146,0.3193 T0.1988,0.3131 T0.2508,0.3095 T0.3029,0.3176 T0.3529,0.3448 Q0.3566,0.348,0.3652,0.355 T0.3771,0.3645 T0.3877,0.3731 T0.3993,0.3815 T0.4103,0.3884 T0.4226,0.3953 T0.4352,0.4009 T0.4497,0.4064 L0.4443,0.4121 Q0.4372,0.4198,0.4277,0.4251 T0.4072,0.4337 T0.3867,0.439 T0.3656,0.4431 T0.3481,0.4468 Q0.3241,0.4529,0.2943,0.4463 Q0.3227,0.4623,0.3458,0.471 T0.3959,0.4814 Q0.404,0.4818,0.4187,0.4825 T0.4409,0.4835 T0.4605,0.4857 T0.4809,0.4908 T0.4992,0.4996 T0.5186,0.5141 T0.5371,0.5353 L0.5449,0.5451 L0.5337,0.543 Q0.5242,0.541,0.5091,0.55 T0.4683,0.5796 T0.4314,0.6083 Q0.4203,0.6161,0.4086,0.6212 T0.382,0.6308 T0.3667,0.6353 Q0.3485,0.6418,0.2995,0.633 T0.1934,0.6075 Z",type:0}},"\uE0C3":{type:5,data:{d:"M0.989,0.0469 Q0.9829,0.0379,0.9753,0.0316 T0.9635,0.0253 Q0.9574,0.0257,0.9539,0.0263 T0.9466,0.03 T0.9418,0.0345 T0.9355,0.0434 T0.9277,0.0546 Q0.9193,0.0653,0.9103,0.0712 T0.8919,0.0775 T0.8745,0.0765 T0.8564,0.0695 T0.8396,0.0593 T0.8228,0.0473 Q0.7612,0,0.7073,0.0326 L0.7386,0.0767 L0.7419,0.0767 Q0.7506,0.0767,0.7556,0.0773 T0.7641,0.0789 T0.7709,0.0842 T0.7767,0.0922 T0.7854,0.1054 T0.7974,0.1236 Q0.8057,0.1354,0.8157,0.1444 T0.8325,0.1574 T0.8474,0.1654 T0.8571,0.1701 Q0.8564,0.1717,0.8554,0.1733 Q0.8503,0.1815,0.844,0.186 T0.8301,0.1909 T0.8153,0.1903 T0.7993,0.1845 T0.7836,0.1758 T0.768,0.1648 T0.754,0.1535 T0.741,0.1425 T0.7312,0.1338 Q0.7031,0.1101,0.6805,0.0995 T0.6357,0.0901 T0.5886,0.1048 Q0.5721,0.1138,0.5574,0.1168 T0.5295,0.117 T0.5055,0.1089 T0.4814,0.0942 L0.4789,0.0926 L0.4711,0.0922 Q0.4589,0.095,0.4811,0.1203 Q0.495,0.1358,0.5087,0.1474 T0.5366,0.167 T0.5611,0.1796 T0.5879,0.1909 T0.6134,0.2011 Q0.6218,0.2047,0.6273,0.2092 Q0.6283,0.2117,0.6286,0.2141 Q0.6305,0.2263,0.6249,0.2388 T0.606,0.2616 T0.5747,0.2761 Q0.5602,0.2785,0.5487,0.2779 T0.5245,0.2724 T0.5015,0.2616 T0.4784,0.2476 T0.4547,0.2321 T0.4297,0.217 T0.4024,0.2043 Q0.2985,0.166,0.1901,0.2345 Q0.1694,0.248,0.1523,0.2569 T0.1165,0.2726 T0.0818,0.2812 T0.0494,0.2796 T0.0181,0.2663 L0.0116,0.2671 Q0,0.272,0.0232,0.2969 Q0.0468,0.3222,0.0742,0.3365 T0.1278,0.3542 T0.1807,0.3564 T0.233,0.352 T0.2815,0.3473 T0.3269,0.3509 T0.3656,0.3699 Q0.4037,0.4005,0.4153,0.4082 Q0.405,0.4139,0.3921,0.4174 T0.3633,0.4237 T0.3414,0.4278 Q0.3207,0.4331,0.2956,0.4278 Q0.2943,0.4278,0.293,0.4274 Q0.2933,0.429,0.2938,0.4313 T0.2964,0.4398 T0.3007,0.4513 T0.3069,0.4619 T0.3149,0.4698 Q0.3427,0.4857,0.3664,0.4947 T0.4169,0.5053 Q0.423,0.5057,0.4385,0.5065 T0.4606,0.508 T0.4779,0.51 T0.4958,0.5139 T0.5095,0.5204 Q0.4992,0.5232,0.485,0.5328 T0.451,0.5585 T0.4201,0.5824 Q0.4095,0.5897,0.3987,0.5946 T0.3738,0.6036 T0.3591,0.6081 Q0.3433,0.6134,0.2965,0.605 T0.1972,0.5816 L0.2026,0.5922 T0.2133,0.6134 L0.2188,0.624 Q0.2201,0.6248,0.2214,0.6252 Q0.2291,0.6297,0.2486,0.6405 T0.2786,0.657 T0.3066,0.6721 T0.3353,0.6866 T0.3598,0.6972 T0.383,0.7051 T0.4005,0.7076 Q0.4008,0.7076,0.4169,0.7092 T0.4471,0.7092 T0.475,0.7019 Q0.4895,0.6958,0.5176,0.6811 T0.5644,0.6586 T0.5921,0.6533 Q0.6128,0.6741,0.6173,0.6782 Q0.6131,0.6835,0.6068,0.6929 T0.5963,0.7078 T0.5858,0.7198 T0.5716,0.7304 T0.5534,0.7365 Q0.5179,0.7443,0.4708,0.728 L0.4708,0.7294 T0.471,0.7333 T0.4716,0.7392 T0.4734,0.7463 T0.4764,0.7539 T0.4814,0.7612 T0.4889,0.7679 Q0.4914,0.77,0.4944,0.7716 Q0.5534,0.8104,0.6409,0.8091 Q0.6521,0.8091,0.6654,0.8053 T0.6907,0.7959 T0.7157,0.7838 T0.741,0.772 T0.7652,0.7631 T0.7888,0.7594 T0.8106,0.7643 Q0.8196,0.7684,0.8267,0.7753 Q0.8409,0.7892,0.8399,0.8169 Q0.8216,0.8067,0.8062,0.8053 T0.7788,0.8087 T0.7493,0.825 Q0.7386,0.8324,0.7256,0.8365 T0.6947,0.844 T0.668,0.8499 L0.697,0.894 L0.7002,0.8948 Q0.7099,0.8968,0.7222,0.9009 T0.7446,0.9086 T0.767,0.9158 T0.7901,0.9213 T0.8132,0.9229 T0.8367,0.9192 Q0.8483,0.916,0.8633,0.9241 T0.8937,0.9462 T0.9248,0.9737 T0.959,0.9955 T0.9939,1 Q1,0.9984,0.9932,0.9845 Q0.991,0.9804,0.9877,0.9755 Q0.9813,0.9666,0.9739,0.9606 T0.9626,0.9555 Q0.9519,0.958,0.9372,0.949 T0.9071,0.9262 T0.8753,0.8987 T0.8403,0.8777 T0.8051,0.8752 Q0.787,0.8805,0.7657,0.8772 Q0.7751,0.8736,0.7828,0.8683 Q0.8086,0.8511,0.8262,0.8497 T0.8651,0.8617 Q0.8696,0.865,0.8727,0.8646 T0.8758,0.8605 L0.8761,0.8536 Q0.8774,0.8295,0.8646,0.8016 T0.8277,0.7496 Q0.8086,0.7308,0.7896,0.7219 Q0.7741,0.7145,0.7575,0.7141 T0.727,0.7182 T0.6978,0.7296 T0.6699,0.7439 T0.6433,0.7571 T0.6179,0.7643 Q0.627,0.7561,0.6402,0.7367 T0.6618,0.7088 L0.6673,0.7035 Q0.6718,0.699,0.6647,0.6884 T0.6483,0.6692 L0.6389,0.6607 Q0.6289,0.657,0.6249,0.6554 T0.6163,0.6507 T0.6102,0.6458 T0.6021,0.637 T0.5889,0.6236 Q0.5828,0.6175,0.5766,0.6138 Q0.5673,0.6081,0.5561,0.6077 T0.5345,0.6105 T0.5116,0.6201 T0.4887,0.633 T0.4653,0.6468 T0.4427,0.6578 Q0.4295,0.6635,0.4167,0.665 T0.3888,0.665 T0.3714,0.6635 Q0.3579,0.6631,0.3282,0.6501 Q0.374,0.6578,0.3911,0.6517 Q0.3937,0.6509,0.4024,0.6482 T0.4179,0.6434 T0.4348,0.6364 T0.4534,0.6256 Q0.464,0.6183,0.4892,0.5989 T0.5278,0.5712 T0.5476,0.564 L0.5582,0.5661 Q0.5692,0.5681,0.5634,0.5563 Q0.5599,0.5485,0.5524,0.5392 L0.545,0.5298 Q0.5369,0.5188,0.5286,0.51 T0.5134,0.4947 T0.4979,0.4831 T0.484,0.4749 T0.47,0.4694 T0.4572,0.4657 T0.4442,0.4637 T0.4326,0.4629 T0.4205,0.4625 Q0.4511,0.4543,0.466,0.4384 L0.4714,0.4327 Q0.4795,0.4237,0.4469,0.394 L0.4421,0.3899 Q0.433,0.387,0.4251,0.3834 T0.4122,0.3772 T0.3988,0.3687 T0.3874,0.3603 T0.3732,0.3487 T0.3579,0.3361 Q0.3433,0.3242,0.3288,0.3159 T0.2993,0.3034 T0.2725,0.2973 T0.2446,0.2961 T0.2193,0.2977 T0.1926,0.301 Q0.1855,0.3022,0.182,0.3026 Q0.1991,0.2936,0.2233,0.2781 Q0.3282,0.2117,0.4256,0.2476 Q0.4363,0.2516,0.451,0.2604 T0.4784,0.2779 T0.5077,0.2965 T0.539,0.3124 T0.5718,0.3216 T0.606,0.3206 Q0.6363,0.3136,0.6526,0.2936 T0.6647,0.2459 Q0.6615,0.2247,0.645,0.2023 Q0.6354,0.1892,0.6237,0.179 Q0.6102,0.1672,0.5937,0.1595 Q0.6073,0.1562,0.6215,0.1485 Q0.6396,0.1387,0.6547,0.1354 T0.6839,0.1354 T0.7101,0.1458 T0.7373,0.166 Q0.738,0.1664,0.746,0.1733 T0.7586,0.1841 T0.7733,0.196 T0.7907,0.2086 T0.8086,0.2196 T0.8275,0.2288 T0.8454,0.2337 T0.8627,0.2343 T0.8777,0.228 T0.8903,0.2145 Q0.9038,0.1929,0.8742,0.1582 Q0.869,0.1517,0.8629,0.1464 Q0.8538,0.1387,0.844,0.1321 T0.8248,0.1205 T0.8122,0.1134 Q0.8109,0.1117,0.8053,0.103 T0.7965,0.0899 T0.7875,0.0781 T0.7757,0.064 Q0.8051,0.0616,0.8328,0.0828 Q0.847,0.0934,0.8588,0.1009 T0.8854,0.1146 T0.9127,0.1213 T0.9384,0.116 T0.9626,0.0962 Q0.9664,0.0909,0.9706,0.0846 T0.976,0.0767 T0.9798,0.0728 T0.9852,0.0704 T0.9932,0.0697 Q0.9997,0.0693,0.9942,0.0567 Q0.9923,0.0522,0.989,0.0469 Z",type:0}},"\uE0C4":{type:5,data:{d:"M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0.9099,0.1158 L0.9099,0.1888 L0.9951,0.1888 L0.9951,0.1158 L0.9099,0.1158 Z M0.9075,0.1116 L0.9976,0.1116 Q1,0.1116,1,0.1137 L1,0.1909 Q1,0.193,0.9976,0.193 L0.9075,0.193 Q0.905,0.193,0.905,0.1909 L0.905,0.1137 Q0.905,0.1116,0.9075,0.1116 Z M0.7852,0.6086 L0.7852,0.682 L0.8704,0.682 L0.8704,0.6086 L0.7852,0.6086 Z M0.7828,0.6044 L0.8729,0.6044 Q0.8753,0.6044,0.8753,0.6065 L0.8753,0.6841 Q0.8753,0.6862,0.8729,0.6862 L0.7828,0.6862 Q0.7803,0.6862,0.7803,0.6841 L0.7803,0.6065 Q0.7803,0.6044,0.7828,0.6044 Z M0.66,0.1137 L0.7501,0.1137 L0.7501,0.1909 L0.66,0.1909 L0.66,0.1137 Z M0.6624,0.4102 L0.6624,0.4836 L0.7477,0.4836 L0.7477,0.4102 L0.6624,0.4102 Z M0.66,0.406 L0.7501,0.406 Q0.7526,0.406,0.7526,0.4081 L0.7526,0.4857 Q0.7526,0.4878,0.7501,0.4878 L0.66,0.4878 Q0.6576,0.4878,0.6576,0.4857 L0.6576,0.4081 Q0.6576,0.406,0.66,0.406 Z M0.6624,0.7131 L0.6624,0.7865 L0.7477,0.7865 L0.7477,0.7131 L0.6624,0.7131 Z M0.66,0.7089 L0.7501,0.7089 Q0.7526,0.7089,0.7526,0.711 L0.7526,0.7886 Q0.7526,0.7907,0.7501,0.7907 L0.66,0.7907 Q0.6576,0.7907,0.6576,0.7886 L0.6576,0.711 Q0.6576,0.7089,0.66,0.7089 Z M0.5407,0.2227 L0.5407,0.2836 L0.6113,0.2836 L0.6113,0.2227 L0.5407,0.2227 Z M0.5309,0.206 L0.621,0.206 Q0.623,0.206,0.6249,0.2068 T0.6281,0.2089 T0.6301,0.2116 T0.6308,0.2143 L0.6308,0.2919 Q0.6308,0.2957,0.6276,0.298 T0.621,0.3003 L0.5309,0.3003 Q0.5265,0.3003,0.5239,0.2976 T0.5212,0.2919 L0.5212,0.2143 Q0.5212,0.211,0.5244,0.2085 T0.5309,0.206 Z M0.5407,0.3184 L0.5407,0.3792 L0.6113,0.3792 L0.6113,0.3184 L0.5407,0.3184 Z M0.5309,0.3016 L0.621,0.3016 Q0.6254,0.3016,0.6281,0.3043 T0.6308,0.31 L0.6308,0.3876 Q0.6308,0.3914,0.6276,0.3937 T0.621,0.396 L0.5309,0.396 Q0.5265,0.396,0.5239,0.3932 T0.5212,0.3876 L0.5212,0.31 Q0.5212,0.3062,0.5244,0.3039 T0.5309,0.3016 Z M0.5309,0.4081 L0.621,0.4081 L0.621,0.4857 L0.5309,0.4857 L0.5309,0.4081 Z M0.5309,0.5059 L0.621,0.5059 L0.621,0.5835 L0.5309,0.5835 L0.5309,0.5059 Z M0.5407,0.6149 L0.5407,0.6758 L0.6113,0.6758 L0.6113,0.6149 L0.5407,0.6149 Z M0.5309,0.5982 L0.621,0.5982 Q0.6254,0.5982,0.6281,0.6009 T0.6308,0.6065 L0.6308,0.6841 Q0.6308,0.6879,0.6276,0.6902 T0.621,0.6925 L0.5309,0.6925 Q0.5265,0.6925,0.5239,0.6898 T0.5212,0.6841 L0.5212,0.6065 Q0.5212,0.6028,0.5244,0.6005 T0.5309,0.5982 Z M0.4062,0.1137 L0.4963,0.1137 L0.4963,0.1909 L0.4062,0.1909 L0.4062,0.1137 Z M0.4062,0.2143 L0.4963,0.2143 L0.4963,0.2919 L0.4062,0.2919 L0.4062,0.2143 Z M0.4184,0.4186 L0.4184,0.4753 L0.4842,0.4753 L0.4842,0.4186 L0.4184,0.4186 Z M0.4062,0.3977 L0.4963,0.3977 Q0.5017,0.3977,0.5051,0.401 T0.5085,0.4081 L0.5085,0.4857 Q0.5085,0.4904,0.5046,0.4933 T0.4963,0.4962 L0.4062,0.4962 Q0.4009,0.4962,0.3975,0.4929 T0.3941,0.4857 L0.3941,0.4081 Q0.3941,0.4035,0.398,0.4006 T0.4062,0.3977 Z M0.4062,0.5059 L0.4963,0.5059 L0.4963,0.5835 L0.4062,0.5835 L0.4062,0.5059 Z M0.4087,0.7131 L0.4087,0.7865 L0.4939,0.7865 L0.4939,0.7131 L0.4087,0.7131 Z M0.4062,0.7089 L0.4963,0.7089 Q0.4988,0.7089,0.4988,0.711 L0.4988,0.7886 Q0.4988,0.7907,0.4963,0.7907 L0.4062,0.7907 Q0.4038,0.7907,0.4038,0.7886 L0.4038,0.711 Q0.4038,0.7089,0.4062,0.7089 Z M0.2689,0.1137 L0.359,0.1137 L0.359,0.1909 L0.2689,0.1909 L0.2689,0.1137 Z M0.2689,0.2143 L0.359,0.2143 L0.359,0.2919 L0.2689,0.2919 L0.2689,0.2143 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.31 L0.359,0.31 L0.359,0.3876 L0.2689,0.3876 L0.2689,0.31 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.4081 L0.359,0.4081 L0.359,0.4857 L0.2689,0.4857 L0.2689,0.4081 Z M0.2689,0.5059 L0.359,0.5059 L0.359,0.5835 L0.2689,0.5835 L0.2689,0.5059 Z M0.2689,0.711 L0.359,0.711 L0.359,0.7886 L0.2689,0.7886 L0.2689,0.711 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.1374,0.1137 L0.2275,0.1137 L0.2275,0.1909 L0.1374,0.1909 L0.1374,0.1137 Z M0.1374,0.2143 L0.2275,0.2143 L0.2275,0.2919 L0.1374,0.2919 L0.1374,0.2143 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.31 L0.2275,0.31 L0.2275,0.3876 L0.1374,0.3876 L0.1374,0.31 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.4081 L0.2275,0.4081 L0.2275,0.4857 L0.1374,0.4857 L0.1374,0.4081 Z M0.1374,0.5059 L0.2275,0.5059 L0.2275,0.5835 L0.1374,0.5835 L0.1374,0.5059 Z M0.1374,0.6065 L0.2275,0.6065 L0.2275,0.6841 L0.1374,0.6841 L0.1374,0.6065 Z M0.1374,0.711 L0.2275,0.711 L0.2275,0.7886 L0.1374,0.7886 L0.1374,0.711 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0,0.1137 L0.0901,0.1137 L0.0901,0.1909 L0,0.1909 L0,0.1137 Z M0,0.2143 L0.0901,0.2143 L0.0901,0.2919 L0,0.2919 L0,0.2143 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.31 L0.0901,0.31 L0.0901,0.3876 L0,0.3876 L0,0.31 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.4081 L0.0901,0.4081 L0.0901,0.4857 L0,0.4857 L0,0.4081 Z M0,0.5059 L0.0901,0.5059 L0.0901,0.5835 L0,0.5835 L0,0.5059 Z M0,0.6065 L0.0901,0.6065 L0.0901,0.6841 L0,0.6841 L0,0.6065 Z M0,0.711 L0.0901,0.711 L0.0901,0.7886 L0,0.7886 L0,0.711 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z",type:0}},"\uE0C5":{type:5,data:{d:"M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M0.0901,0.1158 L0.0901,0.1888 L0.0049,0.1888 L0.0049,0.1158 L0.0901,0.1158 Z M0.0925,0.1116 L0.0024,0.1116 Q0,0.1116,0,0.1137 L0,0.1909 Q0,0.193,0.0024,0.193 L0.0925,0.193 Q0.095,0.193,0.095,0.1909 L0.095,0.1137 Q0.095,0.1116,0.0925,0.1116 Z M0.2148,0.6086 L0.2148,0.682 L0.1296,0.682 L0.1296,0.6086 L0.2148,0.6086 Z M0.2172,0.6044 L0.1271,0.6044 Q0.1247,0.6044,0.1247,0.6065 L0.1247,0.6841 Q0.1247,0.6862,0.1271,0.6862 L0.2172,0.6862 Q0.2197,0.6862,0.2197,0.6841 L0.2197,0.6065 Q0.2197,0.6044,0.2172,0.6044 Z M0.34,0.1137 L0.2499,0.1137 L0.2499,0.1909 L0.34,0.1909 L0.34,0.1137 Z M0.3376,0.4102 L0.3376,0.4836 L0.2523,0.4836 L0.2523,0.4102 L0.3376,0.4102 Z M0.34,0.406 L0.2499,0.406 Q0.2474,0.406,0.2474,0.4081 L0.2474,0.4857 Q0.2474,0.4878,0.2499,0.4878 L0.34,0.4878 Q0.3424,0.4878,0.3424,0.4857 L0.3424,0.4081 Q0.3424,0.406,0.34,0.406 Z M0.3376,0.7131 L0.3376,0.7865 L0.2523,0.7865 L0.2523,0.7131 L0.3376,0.7131 Z M0.34,0.7089 L0.2499,0.7089 Q0.2474,0.7089,0.2474,0.711 L0.2474,0.7886 Q0.2474,0.7907,0.2499,0.7907 L0.34,0.7907 Q0.3424,0.7907,0.3424,0.7886 L0.3424,0.711 Q0.3424,0.7089,0.34,0.7089 Z M0.4593,0.2227 L0.4593,0.2836 L0.3887,0.2836 L0.3887,0.2227 L0.4593,0.2227 Z M0.4691,0.206 L0.379,0.206 Q0.3746,0.206,0.3719,0.2087 T0.3692,0.2143 L0.3692,0.2919 Q0.3692,0.2957,0.3724,0.298 T0.379,0.3003 L0.4691,0.3003 Q0.4735,0.3003,0.4761,0.2976 T0.4788,0.2919 L0.4788,0.2143 Q0.4788,0.211,0.4756,0.2085 T0.4691,0.206 Z M0.4593,0.3184 L0.4593,0.3792 L0.3887,0.3792 L0.3887,0.3184 L0.4593,0.3184 Z M0.4691,0.3016 L0.379,0.3016 Q0.3746,0.3016,0.3719,0.3043 T0.3692,0.31 L0.3692,0.3876 Q0.3692,0.3914,0.3724,0.3937 T0.379,0.396 L0.4691,0.396 Q0.4735,0.396,0.4761,0.3932 T0.4788,0.3876 L0.4788,0.31 Q0.4788,0.3062,0.4756,0.3039 T0.4691,0.3016 Z M0.4691,0.4081 L0.379,0.4081 L0.379,0.4857 L0.4691,0.4857 L0.4691,0.4081 Z M0.4691,0.5059 L0.379,0.5059 L0.379,0.5835 L0.4691,0.5835 L0.4691,0.5059 Z M0.4593,0.6149 L0.4593,0.6758 L0.3887,0.6758 L0.3887,0.6149 L0.4593,0.6149 Z M0.4691,0.5982 L0.379,0.5982 Q0.3746,0.5982,0.3719,0.6009 T0.3692,0.6065 L0.3692,0.6841 Q0.3692,0.6879,0.3724,0.6902 T0.379,0.6925 L0.4691,0.6925 Q0.4735,0.6925,0.4761,0.6898 T0.4788,0.6841 L0.4788,0.6065 Q0.4788,0.6028,0.4756,0.6005 T0.4691,0.5982 Z M0.5938,0.1137 L0.5037,0.1137 L0.5037,0.1909 L0.5938,0.1909 L0.5938,0.1137 Z M0.5938,0.2143 L0.5037,0.2143 L0.5037,0.2919 L0.5938,0.2919 L0.5938,0.2143 Z M0.5816,0.4186 L0.5816,0.4753 L0.5158,0.4753 L0.5158,0.4186 L0.5816,0.4186 Z M0.5938,0.3977 L0.5037,0.3977 Q0.4983,0.3977,0.4949,0.401 T0.4915,0.4081 L0.4915,0.4857 Q0.4915,0.4904,0.4954,0.4933 T0.5037,0.4962 L0.5938,0.4962 Q0.5991,0.4962,0.6025,0.4929 T0.6059,0.4857 L0.6059,0.4081 Q0.6059,0.4035,0.602,0.4006 T0.5938,0.3977 Z M0.5938,0.5059 L0.5037,0.5059 L0.5037,0.5835 L0.5938,0.5835 L0.5938,0.5059 Z M0.5913,0.7131 L0.5913,0.7865 L0.5061,0.7865 L0.5061,0.7131 L0.5913,0.7131 Z M0.5938,0.7089 L0.5037,0.7089 Q0.5012,0.7089,0.5012,0.711 L0.5012,0.7886 Q0.5012,0.7907,0.5037,0.7907 L0.5938,0.7907 Q0.5962,0.7907,0.5962,0.7886 L0.5962,0.711 Q0.5962,0.7089,0.5938,0.7089 Z M0.7311,0.1137 L0.641,0.1137 L0.641,0.1909 L0.7311,0.1909 L0.7311,0.1137 Z M0.7311,0.2143 L0.641,0.2143 L0.641,0.2919 L0.7311,0.2919 L0.7311,0.2143 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.31 L0.641,0.31 L0.641,0.3876 L0.7311,0.3876 L0.7311,0.31 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.4081 L0.641,0.4081 L0.641,0.4857 L0.7311,0.4857 L0.7311,0.4081 Z M0.7311,0.5059 L0.641,0.5059 L0.641,0.5835 L0.7311,0.5835 L0.7311,0.5059 Z M0.7311,0.711 L0.641,0.711 L0.641,0.7886 L0.7311,0.7886 L0.7311,0.711 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.8626,0.1137 L0.7725,0.1137 L0.7725,0.1909 L0.8626,0.1909 L0.8626,0.1137 Z M0.8626,0.2143 L0.7725,0.2143 L0.7725,0.2919 L0.8626,0.2919 L0.8626,0.2143 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.31 L0.7725,0.31 L0.7725,0.3876 L0.8626,0.3876 L0.8626,0.31 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.4081 L0.7725,0.4081 L0.7725,0.4857 L0.8626,0.4857 L0.8626,0.4081 Z M0.8626,0.5059 L0.7725,0.5059 L0.7725,0.5835 L0.8626,0.5835 L0.8626,0.5059 Z M0.8626,0.6065 L0.7725,0.6065 L0.7725,0.6841 L0.8626,0.6841 L0.8626,0.6065 Z M0.8626,0.711 L0.7725,0.711 L0.7725,0.7886 L0.8626,0.7886 L0.8626,0.711 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M1,0.1137 L0.9099,0.1137 L0.9099,0.1909 L1,0.1909 L1,0.1137 Z M1,0.2143 L0.9099,0.2143 L0.9099,0.2919 L1,0.2919 L1,0.2143 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.31 L0.9099,0.31 L0.9099,0.3876 L1,0.3876 L1,0.31 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.4081 L0.9099,0.4081 L0.9099,0.4857 L1,0.4857 L1,0.4081 Z M1,0.5059 L0.9099,0.5059 L0.9099,0.5835 L1,0.5835 L1,0.5059 Z M1,0.6065 L0.9099,0.6065 L0.9099,0.6841 L1,0.6841 L1,0.6065 Z M1,0.711 L0.9099,0.711 L0.9099,0.7886 L1,0.7886 L1,0.711 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z",type:0}},"\uE0C6":{type:5,data:{d:"M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0.8574,0.3217 L0.8574,0.4098 L0.9691,0.4098 L0.9691,0.3217 L0.8574,0.3217 Z M0.8419,0.2978 L0.9846,0.2978 Q0.9892,0.2978,0.9928,0.2997 T0.9982,0.3043 T1,0.31 L1,0.4216 Q1,0.427,0.9951,0.4304 T0.9846,0.4337 L0.8419,0.4337 Q0.8352,0.4337,0.8308,0.4299 T0.8265,0.4216 L0.8265,0.31 Q0.8265,0.3062,0.829,0.3035 T0.835,0.2993 T0.8419,0.2978 Z M0.8574,0.888 L0.8574,0.9757 L0.9691,0.9757 L0.9691,0.888 L0.8574,0.888 Z M0.8419,0.8637 L0.9846,0.8637 Q0.9892,0.8637,0.9928,0.8658 T0.9982,0.8706 T1,0.8758 L1,0.9878 Q1,0.9929,0.9951,0.9964 T0.9846,1 L0.8419,1 Q0.8352,1,0.8308,0.996 T0.8265,0.9878 L0.8265,0.8758 Q0.8265,0.8708,0.8314,0.8672 T0.8419,0.8637 Z M0.6509,0.169 L0.6509,0.2706 L0.7806,0.2706 L0.7806,0.169 L0.6509,0.169 Z M0.6442,0.159 L0.7868,0.159 Q0.7899,0.159,0.7917,0.1604 T0.7935,0.164 L0.7935,0.2756 Q0.7935,0.2781,0.7915,0.2796 T0.7868,0.281 L0.6442,0.281 Q0.6416,0.281,0.6395,0.2794 T0.6375,0.2756 L0.6375,0.164 Q0.6375,0.1619,0.6398,0.1604 T0.6442,0.159 Z M0.6632,0.6044 L0.6632,0.6858 L0.7678,0.6858 L0.7678,0.6044 L0.6632,0.6044 Z M0.6442,0.5742 L0.7868,0.5742 Q0.7951,0.5742,0.8007,0.5791 T0.8064,0.5893 L0.8064,0.7013 Q0.8064,0.7076,0.8002,0.712 T0.7868,0.7164 L0.6442,0.7164 Q0.6359,0.7164,0.6305,0.7114 T0.6251,0.7013 L0.6251,0.5893 Q0.6251,0.5847,0.6282,0.5812 T0.6357,0.5759 T0.6442,0.5742 Z M0.4264,0.164 L0.5695,0.164 L0.5695,0.2756 L0.4264,0.2756 L0.4264,0.164 Z M0.4325,0.315 L0.4325,0.4165 L0.5628,0.4165 L0.5628,0.315 L0.4325,0.315 Z M0.4264,0.3049 L0.5695,0.3049 Q0.5721,0.3049,0.5739,0.3064 T0.5757,0.31 L0.5757,0.4216 Q0.5757,0.4237,0.5736,0.4251 T0.5695,0.4266 L0.4264,0.4266 Q0.4238,0.4266,0.422,0.4251 T0.4202,0.4216 L0.4202,0.31 Q0.4202,0.3087,0.4207,0.3077 T0.4222,0.306 T0.4243,0.3052 T0.4264,0.3049 Z M0.4264,0.4476 L0.5695,0.4476 L0.5695,0.5596 L0.4264,0.5596 L0.4264,0.4476 Z M0.4392,0.7408 L0.4392,0.8326 L0.5561,0.8326 L0.5561,0.7408 L0.4392,0.7408 Z M0.4264,0.7206 L0.5695,0.7206 Q0.5747,0.7206,0.5783,0.724 T0.5819,0.7307 L0.5819,0.8427 Q0.5819,0.8469,0.5778,0.8498 T0.5695,0.8528 L0.4264,0.8528 Q0.4207,0.8528,0.4171,0.8496 T0.4135,0.8427 L0.4135,0.7307 Q0.4135,0.7265,0.4176,0.7236 T0.4264,0.7206 Z M0.2178,0.164 L0.3605,0.164 L0.3605,0.2756 L0.2178,0.2756 L0.2178,0.164 Z M0.2178,0.31 L0.3605,0.31 L0.3605,0.4216 L0.2178,0.4216 L0.2178,0.31 Z M0.2178,0.5893 L0.3605,0.5893 L0.3605,0.7013 L0.2178,0.7013 L0.2178,0.5893 Z M0.2178,0.7307 L0.3605,0.7307 L0.3605,0.8427 L0.2178,0.8427 L0.2178,0.7307 Z M0.2178,0.8758 L0.3605,0.8758 L0.3605,0.9878 L0.2178,0.9878 L0.2178,0.8758 Z M0,0.164 L0.1432,0.164 L0.1432,0.2756 L0,0.2756 L0,0.164 Z M0,0.31 L0.1432,0.31 L0.1432,0.4216 L0,0.4216 L0,0.31 Z M0,0.4476 L0.1432,0.4476 L0.1432,0.5596 L0,0.5596 L0,0.4476 Z M0,0.5893 L0.1432,0.5893 L0.1432,0.7013 L0,0.7013 L0,0.5893 Z M0,0.7307 L0.1432,0.7307 L0.1432,0.8427 L0,0.8427 L0,0.7307 Z M0,0.8758 L0.1432,0.8758 L0.1432,0.9878 L0,0.9878 L0,0.8758 Z",type:0}},"\uE0C7":{type:5,data:{d:"M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M0.1432,0.3217 L0.1432,0.4098 L0.0309,0.4098 L0.0309,0.3217 L0.1432,0.3217 Z M0.1586,0.2978 L0.0154,0.2978 Q0.0088,0.2978,0.0044,0.3016 T0,0.31 L0,0.4216 Q0,0.427,0.0049,0.4304 T0.0154,0.4337 L0.1586,0.4337 Q0.1648,0.4337,0.1694,0.4299 T0.174,0.4216 L0.174,0.31 Q0.174,0.3062,0.1715,0.3035 T0.1653,0.2993 T0.1586,0.2978 Z M0.1432,0.888 L0.1432,0.9757 L0.0309,0.9757 L0.0309,0.888 L0.1432,0.888 Z M0.1586,0.8637 L0.0154,0.8637 Q0.0108,0.8637,0.0072,0.8658 T0.0018,0.8706 T0,0.8758 L0,0.9878 Q0,0.9929,0.0049,0.9964 T0.0154,1 L0.1586,1 Q0.1648,1,0.1694,0.996 T0.174,0.9878 L0.174,0.8758 Q0.174,0.8725,0.1715,0.8695 T0.1653,0.8651 T0.1586,0.8637 Z M0.3496,0.169 L0.3496,0.2706 L0.2194,0.2706 L0.2194,0.169 L0.3496,0.169 Z M0.3563,0.159 L0.2132,0.159 Q0.2106,0.159,0.2088,0.1604 T0.207,0.164 L0.207,0.2756 Q0.207,0.2781,0.2091,0.2796 T0.2132,0.281 L0.3563,0.281 Q0.3589,0.281,0.3607,0.2794 T0.3625,0.2756 L0.3625,0.164 Q0.3625,0.1619,0.3605,0.1604 T0.3563,0.159 Z M0.3368,0.6044 L0.3368,0.6858 L0.2327,0.6858 L0.2327,0.6044 L0.3368,0.6044 Z M0.3563,0.5742 L0.2132,0.5742 Q0.2049,0.5742,0.1993,0.5791 T0.1936,0.5893 L0.1936,0.7013 Q0.1936,0.7076,0.2001,0.712 T0.2132,0.7164 L0.3563,0.7164 Q0.3641,0.7164,0.3697,0.7114 T0.3754,0.7013 L0.3754,0.5893 Q0.3754,0.5847,0.3723,0.5812 T0.3648,0.5759 T0.3563,0.5742 Z M0.5736,0.164 L0.431,0.164 L0.431,0.2756 L0.5736,0.2756 L0.5736,0.164 Z M0.5675,0.315 L0.5675,0.4165 L0.4377,0.4165 L0.4377,0.315 L0.5675,0.315 Z M0.5736,0.3049 L0.431,0.3049 Q0.4284,0.3049,0.4264,0.3064 T0.4243,0.31 L0.4243,0.4216 Q0.4243,0.4237,0.4266,0.4251 T0.431,0.4266 L0.5736,0.4266 Q0.5767,0.4266,0.5785,0.4251 T0.5803,0.4216 L0.5803,0.31 Q0.5803,0.3087,0.5798,0.3077 T0.5783,0.306 T0.5762,0.3052 T0.5736,0.3049 Z M0.5736,0.4476 L0.431,0.4476 L0.431,0.5596 L0.5736,0.5596 L0.5736,0.4476 Z M0.5613,0.7408 L0.5613,0.8326 L0.4439,0.8326 L0.4439,0.7408 L0.5613,0.7408 Z M0.5736,0.7206 L0.431,0.7206 Q0.4253,0.7206,0.4217,0.724 T0.4181,0.7307 L0.4181,0.8427 Q0.4181,0.8469,0.4222,0.8498 T0.431,0.8528 L0.5736,0.8528 Q0.5778,0.8528,0.5808,0.8511 T0.5855,0.8471 T0.587,0.8427 L0.587,0.7307 Q0.587,0.7265,0.5826,0.7236 T0.5736,0.7206 Z M0.7827,0.164 L0.6395,0.164 L0.6395,0.2756 L0.7827,0.2756 L0.7827,0.164 Z M0.7827,0.31 L0.6395,0.31 L0.6395,0.4216 L0.7827,0.4216 L0.7827,0.31 Z M0.7827,0.5893 L0.6395,0.5893 L0.6395,0.7013 L0.7827,0.7013 L0.7827,0.5893 Z M0.7827,0.7307 L0.6395,0.7307 L0.6395,0.8427 L0.7827,0.8427 L0.7827,0.7307 Z M0.7827,0.8758 L0.6395,0.8758 L0.6395,0.9878 L0.7827,0.9878 L0.7827,0.8758 Z M1,0.164 L0.8574,0.164 L0.8574,0.2756 L1,0.2756 L1,0.164 Z M1,0.31 L0.8574,0.31 L0.8574,0.4216 L1,0.4216 L1,0.31 Z M1,0.4476 L0.8574,0.4476 L0.8574,0.5596 L1,0.5596 L1,0.4476 Z M1,0.5893 L0.8574,0.5893 L0.8574,0.7013 L1,0.7013 L1,0.5893 Z M1,0.7307 L0.8574,0.7307 L0.8574,0.8427 L1,0.8427 L1,0.7307 Z M1,0.8758 L0.8574,0.8758 L0.8574,0.9878 L1,0.9878 L1,0.8758 Z",type:0}},"\uE0C8":{type:5,data:{d:"M0.0008,0.9979 Q0.0008,1,0.0032,1 Q0.0482,1,0.0602,0.9975 Q0.0667,0.9963,0.0847,0.9917 T0.1084,0.9859 Q0.1124,0.9851,0.1221,0.9851 L0.1253,0.9851 L0.1285,0.9851 Q0.1518,0.9851,0.1655,0.9809 Q0.1695,0.9801,0.1731,0.9788 T0.1791,0.9766 T0.1839,0.9747 T0.1888,0.9732 L0.1936,0.972 T0.1988,0.9712 T0.2048,0.971 L0.2096,0.971 Q0.2201,0.9714,0.2233,0.9714 T0.2345,0.9693 Q0.2386,0.9685,0.2502,0.9674 T0.2715,0.966 L0.2811,0.9656 Q0.2916,0.9647,0.2916,0.9573 Q0.2916,0.954,0.2892,0.9515 T0.2819,0.9486 Q0.2755,0.9482,0.2683,0.9482 Q0.2643,0.9482,0.2582,0.9484 T0.2498,0.9486 Q0.2426,0.9486,0.2402,0.9473 Q0.2386,0.9469,0.2317,0.943 T0.2161,0.937 Q0.2104,0.9357,0.1992,0.933 T0.1831,0.9291 Q0.1807,0.9287,0.1783,0.9285 T0.1739,0.928 T0.1703,0.9276 T0.1675,0.9272 T0.1655,0.9266 T0.1639,0.9253 Q0.1598,0.9216,0.1462,0.9216 L0.143,0.9216 L0.1398,0.9216 L0.1365,0.9216 Q0.1197,0.9195,0.1197,0.9154 L0.1197,0.9154 Q0.1205,0.9121,0.1373,0.9088 T0.1703,0.9054 Q0.1791,0.9054,0.1863,0.904 T0.1984,0.9011 T0.2064,0.8992 Q0.2402,0.8967,0.2506,0.8934 Q0.2538,0.8926,0.2602,0.8899 T0.2707,0.8872 Q0.2715,0.8872,0.2731,0.8876 Q0.2795,0.8884,0.2884,0.8884 Q0.3068,0.8884,0.3092,0.8826 L0.3092,0.8797 Q0.3092,0.8772,0.3076,0.8747 T0.3044,0.8706 T0.3028,0.8681 Q0.3076,0.8656,0.3237,0.8656 Q0.3317,0.8656,0.3622,0.8623 Q0.3655,0.8623,0.3867,0.8617 T0.4217,0.8602 Q0.4241,0.8598,0.4289,0.8598 L0.4345,0.8598 L0.441,0.8598 Q0.4578,0.8598,0.4667,0.8586 Q0.4739,0.8573,0.4851,0.8567 T0.5004,0.8552 Q0.5012,0.8552,0.5108,0.855 T0.5269,0.853 T0.5333,0.8461 Q0.5333,0.8445,0.5325,0.8432 T0.5309,0.8409 T0.5289,0.8391 T0.5261,0.8378 L0.5229,0.837 T0.5185,0.8362 T0.5133,0.8355 T0.5076,0.8349 T0.502,0.8341 Q0.4932,0.8324,0.4859,0.8324 Q0.4827,0.8324,0.4787,0.8328 T0.4731,0.8333 Q0.4707,0.8333,0.4683,0.8324 Q0.4482,0.825,0.4394,0.825 Q0.4369,0.825,0.4345,0.8254 Q0.4225,0.827,0.4153,0.827 Q0.4088,0.827,0.4064,0.8258 Q0.3976,0.8204,0.3751,0.8204 Q0.3703,0.8204,0.3667,0.8214 T0.3622,0.8225 Q0.3598,0.8225,0.3558,0.82 Q0.351,0.8175,0.3402,0.8167 T0.3177,0.8156 T0.3028,0.8146 L0.2956,0.8121 T0.2871,0.8094 T0.2771,0.8073 T0.2635,0.8063 Q0.261,0.8063,0.2586,0.8067 Q0.2378,0.8071,0.2273,0.8071 Q0.2072,0.8071,0.2024,0.8051 Q0.1976,0.8034,0.1932,0.8026 T0.1867,0.8013 T0.1847,0.8001 L0.1847,0.7993 Q0.1855,0.7984,0.1855,0.7978 T0.1859,0.7966 T0.1867,0.7959 T0.188,0.7955 T0.1896,0.7951 T0.1916,0.7949 T0.1944,0.7945 T0.1984,0.7941 T0.2032,0.7939 Q0.212,0.7926,0.2325,0.7918 T0.2562,0.7905 Q0.2691,0.7893,0.2787,0.7847 Q0.2835,0.7827,0.2968,0.7791 T0.3149,0.7756 Q0.3189,0.7756,0.3305,0.7733 T0.3446,0.771 L0.3454,0.771 Q0.3462,0.771,0.3498,0.7721 T0.3574,0.774 T0.3655,0.7748 Q0.3703,0.7748,0.3743,0.7731 T0.3823,0.7708 T0.3916,0.77 T0.3992,0.7694 Q0.4008,0.7694,0.4076,0.7688 T0.4205,0.7675 L0.4305,0.7665 Q0.4345,0.7657,0.4402,0.7617 T0.4458,0.7545 Q0.4458,0.7503,0.4353,0.7487 Q0.4297,0.7478,0.4209,0.7462 T0.4044,0.7433 T0.3904,0.7416 L0.3847,0.7416 Q0.3807,0.7416,0.3747,0.7422 T0.3663,0.7428 T0.3606,0.7424 Q0.3582,0.742,0.3562,0.7418 T0.3522,0.7412 T0.3482,0.7406 T0.3446,0.7401 T0.341,0.7397 T0.3373,0.7393 T0.3341,0.7391 L0.3293,0.7391 Q0.3277,0.7395,0.3253,0.7395 Q0.3205,0.7395,0.3052,0.7387 T0.2835,0.7379 Q0.2755,0.7379,0.253,0.7325 Q0.2506,0.7316,0.2462,0.731 T0.2382,0.7296 T0.2305,0.7271 T0.2181,0.7234 T0.2048,0.7196 T0.2,0.7155 Q0.2,0.7142,0.2016,0.713 Q0.208,0.7059,0.2285,0.6978 T0.2618,0.6898 Q0.2739,0.6898,0.2831,0.6848 T0.2956,0.6794 Q0.3028,0.6786,0.3289,0.6765 T0.3566,0.674 Q0.3582,0.674,0.3855,0.6723 T0.4406,0.6688 T0.4755,0.6653 Q0.5004,0.6607,0.5124,0.6599 L0.5165,0.6599 Q0.5213,0.6599,0.5277,0.6605 T0.5373,0.6611 Q0.543,0.6611,0.5494,0.6599 Q0.592,0.6524,0.6,0.6524 Q0.6064,0.6524,0.61,0.6499 T0.6161,0.6474 Q0.6177,0.6474,0.6261,0.6495 T0.6418,0.6516 L0.6482,0.6516 Q0.653,0.6512,0.6651,0.6506 T0.6876,0.6493 T0.7084,0.6477 T0.7229,0.645 Q0.7269,0.6437,0.7333,0.6437 Q0.7373,0.6437,0.7458,0.6443 T0.7574,0.645 Q0.7614,0.645,0.7631,0.6445 T0.7655,0.6441 Q0.7671,0.6441,0.7723,0.6448 T0.7823,0.6454 Q0.788,0.6454,0.7952,0.6445 Q0.8233,0.6408,0.8723,0.6367 Q0.9012,0.6342,0.9092,0.6342 L0.9165,0.6342 L0.9205,0.6342 Q0.9277,0.6342,0.9382,0.6323 T0.9526,0.6304 Q0.9534,0.6304,0.9594,0.6307 T0.9695,0.6309 Q0.9976,0.6309,0.9992,0.6234 Q1,0.6201,1,0.618 Q1,0.6089,0.9775,0.608 Q0.9703,0.6076,0.9582,0.6076 Q0.9518,0.6076,0.9394,0.6078 T0.9213,0.608 Q0.9036,0.608,0.8972,0.6072 Q0.894,0.6068,0.8871,0.6058 T0.8751,0.6039 T0.8627,0.6024 T0.8506,0.6018 L0.8474,0.6018 Q0.841,0.6022,0.8281,0.6041 T0.8112,0.606 Q0.8096,0.606,0.808,0.6056 Q0.7783,0.6002,0.7647,0.6002 Q0.7598,0.6002,0.7454,0.6006 T0.7261,0.601 Q0.7221,0.601,0.7145,0.6022 T0.7036,0.6035 Q0.6996,0.6035,0.6964,0.6022 Q0.6723,0.5902,0.6442,0.5902 Q0.6426,0.5902,0.6386,0.5904 T0.6337,0.5906 Q0.6273,0.5906,0.6249,0.5894 L0.6177,0.5856 T0.6116,0.5838 T0.6016,0.5825 T0.5855,0.5823 Q0.5719,0.5823,0.5574,0.5798 T0.5373,0.5774 L0.5341,0.5774 Q0.5293,0.5778,0.5213,0.5786 T0.51,0.5794 Q0.5036,0.5794,0.4988,0.5769 Q0.4932,0.574,0.4771,0.574 Q0.4723,0.574,0.4631,0.5745 T0.4498,0.5749 Q0.4418,0.5749,0.4394,0.5736 Q0.4353,0.5724,0.4305,0.5715 T0.4225,0.5707 T0.4145,0.5705 T0.4056,0.5699 Q0.4008,0.5691,0.398,0.5689 T0.394,0.5689 T0.3908,0.5686 T0.3855,0.5674 Q0.3831,0.567,0.3811,0.5662 T0.3779,0.5645 T0.3743,0.563 T0.3695,0.5624 Q0.3655,0.5624,0.3594,0.5635 T0.3494,0.5645 T0.3406,0.5635 T0.3325,0.5624 Q0.3309,0.5624,0.3301,0.5628 L0.3141,0.5628 Q0.2635,0.5628,0.2506,0.5612 Q0.2474,0.5608,0.2434,0.5608 Q0.2402,0.5608,0.2349,0.561 T0.2273,0.5612 Q0.2209,0.5612,0.2177,0.5595 Q0.2145,0.557,0.2145,0.5558 Q0.2169,0.5537,0.2345,0.5521 Q0.2876,0.5475,0.3028,0.5425 Q0.3068,0.5413,0.3149,0.5396 T0.3309,0.5367 T0.3478,0.5338 T0.3606,0.5309 Q0.3631,0.5305,0.3755,0.5259 T0.3968,0.5214 Q0.3992,0.5214,0.4024,0.5218 T0.4096,0.5222 Q0.4161,0.5222,0.4213,0.5209 T0.4313,0.5185 T0.4402,0.5172 Q0.4418,0.5168,0.447,0.5168 L0.4558,0.5168 T0.4639,0.5162 T0.4707,0.5141 T0.4731,0.5102 Q0.4731,0.5089,0.4723,0.5081 Q0.4699,0.5019,0.4627,0.4994 T0.4369,0.4965 Q0.4273,0.4961,0.4193,0.4946 T0.4048,0.4915 T0.3952,0.4894 Q0.392,0.4886,0.388,0.4886 Q0.3847,0.4886,0.3775,0.4892 T0.3647,0.4898 Q0.3598,0.4898,0.355,0.4894 Q0.3165,0.4861,0.3141,0.4853 Q0.2779,0.4774,0.2715,0.4774 Q0.2699,0.4774,0.2683,0.4776 T0.2627,0.4778 Q0.2498,0.4778,0.2281,0.4762 Q0.2241,0.4749,0.2233,0.4737 Q0.2249,0.472,0.2321,0.4708 Q0.2402,0.4695,0.2554,0.4695 L0.2675,0.4695 L0.2795,0.4695 Q0.294,0.4695,0.2996,0.4679 Q0.3141,0.4629,0.3373,0.4616 Q0.3414,0.4612,0.3466,0.4606 T0.3582,0.4589 T0.3687,0.4562 T0.3727,0.4525 Q0.3727,0.4509,0.3703,0.4492 Q0.3639,0.4455,0.3474,0.4419 T0.3213,0.4384 L0.3157,0.4384 L0.31,0.4384 Q0.294,0.4384,0.2859,0.4363 Q0.2795,0.4347,0.2691,0.4347 L0.2618,0.4347 L0.2546,0.4347 Q0.2458,0.4347,0.2394,0.4338 Q0.2257,0.4318,0.2193,0.4303 T0.2092,0.4266 T0.2052,0.4224 T0.2024,0.4154 T0.196,0.4052 Q0.1912,0.3994,0.1867,0.3949 T0.1807,0.3884 T0.1791,0.3849 Q0.1791,0.3824,0.1839,0.3745 Q0.1871,0.3687,0.1944,0.3637 T0.2072,0.3561 T0.2221,0.3501 T0.2321,0.3463 Q0.2353,0.3455,0.241,0.3445 T0.2522,0.3428 T0.2639,0.3411 T0.2731,0.3384 T0.2956,0.3351 T0.3197,0.3326 Q0.3213,0.3322,0.3237,0.3322 T0.3325,0.3331 T0.3414,0.3339 Q0.3478,0.3339,0.3518,0.3314 Q0.3606,0.3264,0.3703,0.3264 Q0.3775,0.3264,0.3847,0.3293 Q0.3888,0.331,0.396,0.331 Q0.4024,0.331,0.4116,0.3295 T0.4265,0.3266 T0.4414,0.3227 T0.4514,0.3202 Q0.4578,0.3181,0.4839,0.3152 T0.5112,0.3123 T0.5177,0.3119 T0.5285,0.3111 T0.5398,0.3096 T0.549,0.3065 T0.5526,0.3015 Q0.5526,0.2999,0.5518,0.2982 Q0.547,0.2903,0.5386,0.2883 T0.5165,0.2862 Q0.5116,0.2862,0.5056,0.2866 T0.4972,0.287 Q0.4884,0.287,0.4787,0.2833 Q0.4594,0.2758,0.4265,0.2758 Q0.4169,0.2758,0.4129,0.2766 Q0.4112,0.2771,0.4104,0.2771 Q0.4088,0.2771,0.4056,0.2748 T0.3968,0.2721 Q0.3695,0.2696,0.359,0.2659 Q0.355,0.2646,0.351,0.2646 T0.3426,0.2661 T0.3365,0.2675 T0.3333,0.2659 Q0.3277,0.2605,0.3116,0.2553 T0.2859,0.2501 L0.2843,0.2501 L0.2819,0.2501 Q0.2779,0.2501,0.2723,0.2493 T0.2586,0.2472 T0.2474,0.2455 Q0.1799,0.236,0.1719,0.236 Q0.1663,0.236,0.1606,0.235 T0.1502,0.2316 T0.1454,0.2265 Q0.1454,0.2223,0.1558,0.2173 Q0.1679,0.2115,0.1859,0.2092 T0.2185,0.207 T0.2353,0.2057 Q0.2369,0.2049,0.2502,0.2041 T0.2799,0.2022 T0.302,0.2007 Q0.3084,0.1999,0.3245,0.1954 T0.347,0.1908 Q0.3502,0.1908,0.3534,0.1916 Q0.3663,0.197,0.388,0.197 Q0.404,0.197,0.4088,0.1929 Q0.4096,0.192,0.4108,0.192 T0.4177,0.1945 T0.4277,0.197 T0.4369,0.1949 Q0.4442,0.1908,0.4843,0.1875 Q0.4892,0.1871,0.4968,0.1839 T0.5084,0.1808 L0.5108,0.1808 Q0.5213,0.1825,0.5454,0.1825 Q0.5823,0.1825,0.5992,0.1779 Q0.6096,0.1746,0.6217,0.1746 Q0.6249,0.1746,0.6309,0.1748 T0.6394,0.175 Q0.6434,0.175,0.6466,0.1746 Q0.6474,0.1746,0.6594,0.1738 T0.6787,0.1713 T0.6892,0.1659 Q0.6924,0.1634,0.6924,0.1593 Q0.6924,0.1547,0.6888,0.151 T0.6795,0.1472 Q0.6779,0.1472,0.6763,0.1477 Q0.6699,0.1493,0.6667,0.1493 Q0.661,0.1493,0.6506,0.1456 Q0.6329,0.1394,0.6129,0.1394 Q0.6048,0.1394,0.5992,0.1377 T0.5888,0.136 T0.5803,0.1377 T0.5751,0.1394 Q0.5711,0.1394,0.5639,0.1369 Q0.5365,0.1273,0.5116,0.1273 Q0.4876,0.1273,0.4667,0.1253 Q0.4627,0.1248,0.4542,0.1211 T0.4337,0.1174 L0.4305,0.1174 Q0.4217,0.1178,0.4153,0.119 T0.408,0.1203 Q0.4056,0.1203,0.4,0.117 Q0.3976,0.1153,0.396,0.1136 T0.3932,0.1112 T0.39,0.1099 T0.3847,0.1095 Q0.3799,0.1095,0.3687,0.1103 Q0.355,0.1112,0.3434,0.1141 T0.3301,0.117 Q0.3261,0.117,0.3229,0.1145 Q0.3181,0.1116,0.3052,0.1087 T0.2787,0.1043 T0.2546,0.1029 Q0.2506,0.1029,0.2474,0.1033 Q0.2418,0.1037,0.2349,0.1054 T0.2249,0.107 Q0.2225,0.107,0.2193,0.1066 Q0.2024,0.1024,0.1863,0.1024 Q0.1823,0.1024,0.1562,0.0971 T0.1269,0.0917 T0.1161,0.0896 T0.106,0.0854 Q0.1076,0.0846,0.11,0.0842 Q0.1173,0.0821,0.1261,0.0821 Q0.1301,0.0821,0.1369,0.0825 T0.1462,0.083 Q0.1494,0.083,0.151,0.0825 T0.1831,0.0813 T0.2201,0.0784 T0.243,0.074 T0.2667,0.0718 Q0.2707,0.0718,0.2755,0.0724 T0.2843,0.073 T0.2936,0.0724 T0.3024,0.0705 T0.3072,0.068 T0.3092,0.0657 T0.3108,0.0647 Q0.3116,0.0643,0.3133,0.0643 L0.3157,0.0643 L0.3181,0.0643 Q0.3446,0.0643,0.3446,0.0556 T0.3205,0.0469 L0.3189,0.0469 Q0.3157,0.0469,0.3157,0.0465 Q0.3084,0.0427,0.2835,0.0386 Q0.2739,0.0369,0.2675,0.0342 T0.2538,0.0299 Q0.249,0.029,0.245,0.029 T0.2378,0.0303 T0.2321,0.0315 Q0.2305,0.0315,0.2273,0.0307 Q0.2201,0.0286,0.2004,0.0272 T0.1775,0.0245 Q0.1735,0.0228,0.1663,0.0226 T0.1522,0.0197 T0.1345,0.0145 T0.1124,0.0079 Q0.0996,0.0033,0.0671,0.0044 T0.0297,0.0041 Q0.0169,0,0.0024,0.0017 Q0,0.0021,0,0.0037 Z M0.0064,0.9954 L0.0056,0.0058 Q0.0177,0.005,0.0281,0.0083 Q0.0345,0.01,0.0675,0.0089 T0.1108,0.012 Q0.1221,0.0162,0.1329,0.0187 T0.1498,0.0236 T0.1606,0.0268 T0.1691,0.0274 T0.1759,0.0286 Q0.1807,0.0303,0.2,0.0315 T0.2257,0.0348 Q0.2305,0.0361,0.2337,0.0361 Q0.2361,0.0361,0.2398,0.0348 T0.2466,0.0336 Q0.249,0.0336,0.2522,0.034 Q0.2586,0.0353,0.2655,0.0382 T0.2827,0.0427 Q0.3068,0.0469,0.3124,0.0502 Q0.3157,0.0523,0.3237,0.0523 L0.3277,0.0523 L0.3309,0.0523 Q0.3398,0.0523,0.3398,0.0556 Q0.3398,0.0581,0.3341,0.0587 T0.3205,0.0593 T0.3092,0.0603 T0.3036,0.0643 T0.2932,0.068 Q0.2884,0.0684,0.2843,0.0684 Q0.2811,0.0684,0.2763,0.0678 T0.2667,0.0672 Q0.2586,0.0672,0.2422,0.0697 T0.2177,0.0747 Q0.2137,0.0759,0.1831,0.0767 T0.1494,0.0784 L0.1474,0.0784 T0.1398,0.078 T0.1285,0.0776 Q0.1173,0.0776,0.1084,0.08 Q0.1004,0.0817,0.1004,0.0846 Q0.1004,0.0867,0.104,0.0888 T0.1124,0.0923 T0.1213,0.0948 T0.1269,0.0958 T0.155,0.1014 T0.1863,0.107 Q0.2016,0.107,0.2185,0.1107 Q0.2217,0.1116,0.2257,0.1116 Q0.2289,0.1116,0.2365,0.1099 T0.2482,0.1074 L0.2538,0.1074 Q0.2683,0.1074,0.2912,0.1107 T0.3197,0.1182 Q0.3245,0.1215,0.3301,0.1215 Q0.3333,0.1215,0.3454,0.1184 T0.3687,0.1145 Q0.3735,0.1141,0.3779,0.1139 T0.3843,0.1134 T0.3871,0.1132 Q0.3896,0.1132,0.39,0.1136 T0.392,0.1161 T0.3968,0.1203 Q0.404,0.1248,0.408,0.1248 Q0.4096,0.1248,0.4165,0.1234 T0.4305,0.1215 L0.4337,0.1215 Q0.4442,0.1215,0.4518,0.1251 T0.4659,0.1294 Q0.4876,0.1319,0.5116,0.1319 T0.5622,0.141 Q0.5703,0.1439,0.5751,0.1439 Q0.5791,0.1439,0.5831,0.1423 T0.5888,0.1406 Q0.592,0.1406,0.5944,0.1412 T0.6012,0.1427 T0.6129,0.1435 Q0.6313,0.1435,0.649,0.1497 Q0.6602,0.1535,0.6667,0.1535 Q0.6715,0.1535,0.6779,0.1522 Q0.6787,0.1518,0.6795,0.1518 Q0.6827,0.1518,0.6847,0.1541 T0.6867,0.1593 Q0.6867,0.1618,0.6851,0.1634 Q0.6835,0.1655,0.6775,0.1667 T0.6598,0.169 T0.6458,0.1705 T0.6402,0.1709 Q0.6378,0.1709,0.6321,0.1705 T0.6225,0.1701 Q0.6096,0.1701,0.5968,0.1738 Q0.5823,0.1779,0.5446,0.1779 Q0.5205,0.1779,0.5116,0.1767 Q0.51,0.1763,0.5084,0.1763 Q0.5028,0.1763,0.4944,0.1796 T0.4835,0.1829 L0.4771,0.1833 T0.4659,0.1844 L0.4546,0.1858 T0.443,0.1881 T0.4337,0.1912 Q0.4321,0.1916,0.4305,0.1916 Q0.4273,0.1916,0.4209,0.1895 T0.412,0.1875 Q0.408,0.1875,0.4056,0.19 Q0.4024,0.1925,0.388,0.1925 Q0.3663,0.1925,0.3558,0.1879 Q0.3518,0.1862,0.3462,0.1862 Q0.3373,0.1862,0.3213,0.191 T0.3012,0.1962 Q0.2964,0.1966,0.2799,0.1976 T0.2494,0.1997 T0.2313,0.2024 L0.2313,0.2024 L0.2293,0.2024 L0.2261,0.2024 L0.2217,0.2024 Q0.204,0.2024,0.1851,0.2047 T0.1526,0.2136 Q0.1406,0.2198,0.1406,0.226 Q0.1406,0.2319,0.1518,0.2362 T0.1719,0.2406 Q0.1791,0.2406,0.2466,0.2497 Q0.2498,0.2501,0.2574,0.2516 T0.2711,0.2538 T0.2819,0.2547 L0.2851,0.2547 Q0.2851,0.2543,0.2859,0.2543 Q0.294,0.2543,0.3096,0.2592 T0.3285,0.2684 Q0.3317,0.2721,0.3373,0.2721 Q0.3406,0.2721,0.3462,0.2708 T0.3542,0.2696 Q0.3558,0.2696,0.3574,0.27 Q0.3679,0.2737,0.3968,0.2762 Q0.3984,0.2766,0.4004,0.2779 L0.4044,0.2804 T0.4096,0.2816 Q0.412,0.2816,0.4145,0.2808 Q0.4177,0.2804,0.4249,0.2804 Q0.4586,0.2804,0.4763,0.287 Q0.4867,0.2912,0.4972,0.2912 Q0.5004,0.2912,0.5064,0.2908 T0.5165,0.2903 Q0.5293,0.2903,0.5361,0.2922 T0.547,0.3003 L0.547,0.3011 Q0.547,0.3036,0.5414,0.3049 T0.5249,0.3067 T0.51,0.3082 Q0.5092,0.3082,0.4831,0.3111 T0.4498,0.3161 Q0.408,0.3264,0.3952,0.3264 Q0.3896,0.3264,0.3863,0.3252 Q0.3783,0.3223,0.3703,0.3223 Q0.359,0.3223,0.3494,0.3277 Q0.347,0.3293,0.343,0.3293 Q0.3414,0.3293,0.3353,0.3285 T0.3253,0.3277 T0.3181,0.3285 Q0.3149,0.3289,0.2952,0.3306 T0.2699,0.3347 Q0.2675,0.3364,0.2598,0.3372 T0.2434,0.3395 T0.2297,0.3426 L0.2205,0.3461 T0.2048,0.3523 T0.1908,0.3606 T0.1791,0.3725 Q0.1743,0.3803,0.1743,0.3845 Q0.1743,0.3874,0.1763,0.3901 T0.1827,0.3976 T0.1912,0.4073 T0.198,0.4189 T0.2032,0.4276 T0.2137,0.4326 T0.2386,0.438 Q0.2458,0.4392,0.2562,0.4392 L0.2639,0.4392 L0.2699,0.4392 Q0.2795,0.4392,0.2843,0.4405 Q0.2932,0.443,0.3092,0.443 L0.3157,0.443 L0.3213,0.443 Q0.3293,0.443,0.3458,0.4463 T0.3671,0.4525 Q0.3598,0.4558,0.3373,0.4571 Q0.3124,0.4587,0.298,0.4637 Q0.294,0.465,0.2811,0.465 L0.2703,0.465 L0.2586,0.465 Q0.241,0.465,0.2305,0.4666 Q0.2185,0.4687,0.2185,0.4728 Q0.2185,0.4745,0.2205,0.4764 T0.2241,0.4791 L0.2265,0.4803 L0.2273,0.4803 Q0.2506,0.4824,0.261,0.4824 Q0.2675,0.4824,0.2699,0.482 Q0.2699,0.4815,0.2707,0.4815 Q0.2771,0.4815,0.3124,0.4898 Q0.3173,0.4907,0.3229,0.4913 T0.3386,0.4925 T0.3542,0.4936 T0.3663,0.494 Q0.3711,0.494,0.3787,0.4936 T0.3888,0.4932 Q0.392,0.4932,0.3944,0.4936 T0.4032,0.4959 T0.4181,0.499 T0.4369,0.5006 Q0.449,0.501,0.455,0.5021 T0.4639,0.5048 T0.4675,0.5093 L0.4675,0.5097 Q0.4675,0.5106,0.4663,0.511 T0.4622,0.5116 L0.4566,0.512 T0.4486,0.5124 T0.4402,0.5127 Q0.4345,0.5131,0.4257,0.5153 T0.4096,0.5176 Q0.4064,0.5176,0.4024,0.5172 L0.3968,0.5172 Q0.3863,0.5172,0.3731,0.522 T0.359,0.5268 Q0.355,0.528,0.3317,0.5321 T0.3004,0.5388 Q0.2867,0.5433,0.2337,0.5479 Q0.2096,0.55,0.2096,0.5562 Q0.2096,0.5591,0.2145,0.5628 Q0.2193,0.5662,0.2289,0.5662 Q0.2313,0.5662,0.2373,0.5657 T0.2458,0.5653 L0.2498,0.5653 Q0.2635,0.5674,0.3124,0.5674 Q0.3261,0.5674,0.3301,0.567 L0.3317,0.567 Q0.3341,0.567,0.3394,0.568 T0.3486,0.5691 Q0.3542,0.5691,0.3606,0.5678 T0.3699,0.5666 T0.3759,0.5686 T0.3839,0.5718 T0.3908,0.573 T0.3964,0.5734 T0.4048,0.5742 T0.4165,0.5751 T0.4277,0.5757 T0.4369,0.5778 Q0.4426,0.5794,0.4538,0.5794 Q0.4586,0.5794,0.4679,0.5792 T0.4811,0.579 Q0.4924,0.579,0.4956,0.5807 Q0.502,0.584,0.5108,0.584 L0.5137,0.584 T0.5173,0.5838 L0.5205,0.5834 L0.5237,0.583 L0.5269,0.5825 T0.5305,0.5821 T0.5341,0.5819 L0.5365,0.5819 Q0.5414,0.5819,0.5562,0.5842 T0.5855,0.5865 Q0.5912,0.5865,0.5948,0.5867 T0.6012,0.5869 T0.6064,0.5873 T0.61,0.5877 T0.6133,0.5886 T0.6157,0.5896 L0.6185,0.591 T0.6217,0.5931 Q0.6265,0.5952,0.6369,0.5952 L0.6418,0.5952 L0.6466,0.5952 Q0.6723,0.5952,0.6932,0.606 Q0.698,0.608,0.7044,0.608 Q0.7084,0.608,0.7161,0.6068 T0.7261,0.6056 Q0.7309,0.6056,0.7454,0.6051 T0.7647,0.6047 Q0.7775,0.6047,0.8072,0.6101 Q0.8096,0.6105,0.812,0.6105 Q0.8177,0.6105,0.8297,0.6085 T0.8474,0.606 L0.8506,0.606 Q0.8562,0.606,0.8647,0.607 T0.8831,0.6095 T0.8964,0.6118 Q0.9036,0.6122,0.9221,0.6122 L0.9406,0.6122 L0.959,0.6122 L0.9767,0.6122 Q0.9952,0.613,0.9952,0.6172 Q0.9952,0.6176,0.9944,0.6221 Q0.9936,0.6259,0.9574,0.6259 L0.955,0.6259 L0.9526,0.6259 Q0.9478,0.6259,0.9373,0.628 T0.9205,0.63 L0.9173,0.63 Q0.9133,0.6296,0.9084,0.6296 Q0.9004,0.6296,0.8715,0.6321 Q0.8233,0.6367,0.7944,0.64 Q0.7888,0.6408,0.7839,0.6408 T0.7739,0.6402 T0.7671,0.6396 Q0.7639,0.6396,0.7622,0.64 Q0.7614,0.6404,0.759,0.6404 Q0.7574,0.6404,0.749,0.6398 T0.7357,0.6392 Q0.7261,0.6392,0.7213,0.6412 Q0.7173,0.6425,0.7032,0.6437 T0.6711,0.6458 T0.6474,0.647 Q0.645,0.6474,0.6418,0.6474 Q0.6353,0.6474,0.6277,0.6454 T0.6161,0.6433 Q0.6104,0.6433,0.6072,0.6456 T0.6,0.6479 Q0.5912,0.6479,0.5486,0.6557 Q0.5422,0.657,0.5373,0.657 Q0.5349,0.657,0.5289,0.6564 T0.5173,0.6557 L0.5124,0.6557 Q0.4996,0.6562,0.4739,0.6611 Q0.4675,0.6624,0.4398,0.6645 T0.3851,0.668 T0.3558,0.6698 Q0.355,0.6698,0.3285,0.6721 T0.2948,0.6752 Q0.2892,0.6761,0.2843,0.6786 T0.2747,0.6831 T0.2618,0.6852 Q0.253,0.6852,0.2394,0.6893 T0.2141,0.6993 T0.1968,0.7105 Q0.1944,0.713,0.1944,0.7155 Q0.1944,0.7196,0.2012,0.7229 T0.2177,0.7285 T0.2281,0.7308 Q0.2321,0.7329,0.2365,0.7339 T0.245,0.7356 T0.2514,0.7366 Q0.2522,0.737,0.2554,0.7379 T0.2614,0.7393 T0.2679,0.7406 T0.2755,0.7416 T0.2835,0.742 Q0.29,0.742,0.3048,0.7428 T0.3253,0.7437 L0.3301,0.7437 L0.3333,0.7437 Q0.3341,0.7437,0.3598,0.7466 Q0.3639,0.7474,0.3671,0.7474 T0.3759,0.7466 T0.3855,0.7457 Q0.388,0.7457,0.3904,0.7462 Q0.3984,0.7466,0.4137,0.7495 T0.4349,0.7532 T0.441,0.7557 Q0.441,0.7599,0.4289,0.7619 Q0.4257,0.7628,0.4197,0.7634 T0.4072,0.7644 T0.3984,0.7652 Q0.3968,0.7652,0.3912,0.7655 T0.3811,0.7665 T0.3719,0.7694 Q0.3695,0.7702,0.3655,0.7702 T0.3546,0.7686 T0.3462,0.7665 L0.3446,0.7665 Q0.3406,0.7665,0.3293,0.769 T0.3149,0.7715 Q0.3084,0.7715,0.2952,0.7748 T0.2763,0.781 Q0.2675,0.7847,0.2554,0.7864 Q0.253,0.7864,0.2325,0.7874 T0.2024,0.7893 Q0.2008,0.7893,0.1984,0.7897 T0.1948,0.7901 T0.1916,0.7903 T0.1888,0.7908 T0.1867,0.7914 T0.1847,0.7922 L0.1831,0.793 T0.1819,0.7943 L0.1811,0.7959 T0.1799,0.7976 Q0.1791,0.7993,0.1791,0.8001 Q0.1791,0.8034,0.1831,0.8051 T0.1932,0.8078 T0.2,0.8092 Q0.2072,0.8117,0.2257,0.8117 Q0.2337,0.8117,0.2586,0.8109 L0.2635,0.8109 Q0.2707,0.8109,0.2767,0.8117 T0.2859,0.8136 T0.2936,0.8163 T0.3012,0.8187 Q0.3068,0.82,0.3277,0.8206 T0.3526,0.8237 Q0.3582,0.8266,0.3622,0.8266 Q0.3647,0.8266,0.3687,0.8258 T0.3751,0.825 Q0.396,0.825,0.404,0.8291 Q0.408,0.8316,0.4161,0.8316 Q0.4217,0.8316,0.4353,0.8295 L0.4394,0.8295 Q0.4466,0.8295,0.4659,0.8366 Q0.4699,0.8378,0.4747,0.8378 Q0.4763,0.8378,0.4807,0.8374 T0.4884,0.837 Q0.494,0.837,0.5004,0.8382 Q0.5052,0.8391,0.5124,0.8399 Q0.5221,0.8411,0.5249,0.8422 T0.5277,0.8461 Q0.5277,0.8482,0.5233,0.849 T0.5112,0.8501 T0.4996,0.8511 Q0.4964,0.8519,0.4851,0.8523 T0.4659,0.8544 Q0.4578,0.8557,0.4402,0.8557 L0.4349,0.8557 L0.4297,0.8557 L0.4209,0.8557 Q0.408,0.8565,0.3867,0.8571 T0.3614,0.8582 Q0.3317,0.8611,0.3237,0.8611 L0.3225,0.8611 L0.3213,0.8611 Q0.2972,0.8611,0.2972,0.8681 Q0.2972,0.8714,0.3008,0.8747 T0.3044,0.8805 Q0.3044,0.881,0.3036,0.8814 Q0.3012,0.8839,0.2851,0.8839 Q0.2779,0.8839,0.2739,0.883 Q0.2723,0.8826,0.2699,0.8826 T0.2643,0.8835 T0.259,0.8853 T0.2542,0.8876 T0.249,0.8893 Q0.2386,0.8922,0.2056,0.8946 Q0.2016,0.8951,0.1916,0.898 T0.1703,0.9009 Q0.1518,0.9009,0.1337,0.9044 T0.1149,0.9146 L0.1149,0.9158 Q0.1149,0.9175,0.1157,0.9187 T0.1173,0.921 T0.1201,0.9226 T0.1233,0.9237 T0.1269,0.9245 L0.1305,0.9251 T0.1337,0.9255 L0.1357,0.9258 Q0.139,0.9262,0.1446,0.9262 T0.1542,0.9266 T0.1598,0.9282 Q0.1614,0.9303,0.1647,0.9314 T0.1739,0.9328 T0.1815,0.9336 Q0.1863,0.9345,0.1976,0.9372 T0.2145,0.9415 Q0.2225,0.9432,0.2285,0.9467 T0.2378,0.9515 Q0.2426,0.9531,0.2514,0.9531 Q0.2538,0.9531,0.2602,0.9529 T0.2707,0.9527 Q0.2763,0.9527,0.2815,0.9531 T0.2867,0.9569 T0.2811,0.9614 Q0.2434,0.9627,0.2329,0.9652 Q0.2249,0.9672,0.2241,0.9672 L0.2096,0.9668 L0.2056,0.9668 Q0.2,0.9668,0.196,0.9672 T0.1884,0.9687 T0.1815,0.9708 T0.1731,0.9737 T0.1639,0.9772 Q0.1518,0.9805,0.1285,0.9805 L0.1253,0.9805 Q0.1124,0.9805,0.1076,0.9818 Q0.1012,0.983,0.0831,0.9876 T0.0594,0.9934 Q0.049,0.9954,0.0064,0.9954 Z M0.0032,0.9979 L0.0024,0.0037 Q0.0161,0.0021,0.0289,0.0062 Q0.0337,0.0075,0.0671,0.0066 T0.1116,0.01 T0.1337,0.0166 T0.151,0.022 Q0.1582,0.0249,0.1659,0.0251 T0.1767,0.0265 Q0.1807,0.0278,0.2,0.0292 T0.2265,0.0328 Q0.2305,0.034,0.2329,0.034 T0.239,0.0326 T0.2458,0.0311 T0.253,0.0319 Q0.2578,0.0332,0.2614,0.0342 T0.2667,0.0363 T0.2723,0.0384 T0.2827,0.0406 Q0.3084,0.0448,0.3141,0.0485 Q0.3149,0.0494,0.3225,0.0494 T0.3361,0.0504 T0.3422,0.0554 T0.3357,0.0606 T0.3205,0.0618 T0.31,0.0626 Q0.3092,0.0626,0.3056,0.0659 T0.2932,0.0701 Q0.2884,0.0709,0.2843,0.0709 Q0.2811,0.0709,0.2759,0.0701 T0.2667,0.0693 Q0.2586,0.0693,0.2422,0.0718 T0.2193,0.0763 Q0.2137,0.0784,0.1831,0.079 T0.1502,0.0805 Q0.1494,0.0809,0.147,0.0809 Q0.1454,0.0809,0.1386,0.0803 T0.1277,0.0796 Q0.1173,0.0796,0.1092,0.0821 Q0.1036,0.0834,0.1036,0.0854 Q0.1036,0.0879,0.1129,0.0908 T0.1269,0.0937 Q0.1293,0.0937,0.1558,0.0991 T0.1863,0.1045 Q0.2016,0.1045,0.2193,0.1087 Q0.2225,0.1091,0.2249,0.1091 Q0.2289,0.1091,0.2357,0.1076 T0.2482,0.1054 Q0.2506,0.1049,0.2546,0.1049 Q0.2699,0.1049,0.2924,0.1085 T0.3213,0.1165 Q0.3253,0.119,0.3301,0.119 Q0.3325,0.119,0.3446,0.1161 T0.3687,0.1124 Q0.3831,0.1112,0.3863,0.1112 T0.3908,0.1118 T0.3936,0.1145 T0.3984,0.1186 Q0.4048,0.1228,0.408,0.1228 Q0.4096,0.1228,0.4161,0.1213 T0.4305,0.1195 L0.4337,0.1195 Q0.4426,0.1195,0.4474,0.1211 T0.4566,0.1246 T0.4667,0.1273 Q0.4876,0.1298,0.5116,0.1298 Q0.5365,0.1298,0.5631,0.1389 Q0.5703,0.1414,0.5751,0.1414 Q0.5783,0.1414,0.5819,0.1398 T0.5888,0.1381 Q0.5928,0.1381,0.5984,0.1398 T0.6129,0.1414 Q0.6321,0.1414,0.6498,0.1477 Q0.661,0.1514,0.6667,0.1514 Q0.6707,0.1514,0.6771,0.1501 Q0.6787,0.1497,0.6795,0.1497 Q0.6835,0.1497,0.6863,0.1526 T0.6892,0.1593 Q0.6892,0.1626,0.6876,0.1647 Q0.6851,0.1672,0.6799,0.1686 T0.6695,0.1705 T0.6566,0.1715 T0.6458,0.1725 Q0.6434,0.173,0.6402,0.173 Q0.6378,0.173,0.6317,0.1727 T0.6225,0.1725 Q0.6096,0.1725,0.5984,0.1759 Q0.5823,0.1804,0.5454,0.1804 Q0.5205,0.1804,0.5116,0.1788 L0.5084,0.1788 Q0.5036,0.1788,0.4956,0.1819 T0.4843,0.185 Q0.4434,0.1887,0.4353,0.1929 Q0.4321,0.1945,0.4289,0.1945 T0.4197,0.1922 T0.4112,0.19 T0.4072,0.1912 Q0.4032,0.1949,0.388,0.1949 Q0.3663,0.1949,0.3542,0.19 Q0.351,0.1883,0.3462,0.1883 Q0.3406,0.1883,0.3321,0.1906 T0.3153,0.1954 T0.302,0.1983 Q0.2972,0.1991,0.2859,0.1997 T0.2643,0.201 T0.245,0.2024 T0.2337,0.2041 Q0.2321,0.2045,0.2181,0.2045 T0.1851,0.207 T0.1542,0.2157 Q0.143,0.2211,0.143,0.226 Q0.143,0.2314,0.153,0.2348 T0.1719,0.2381 Q0.1791,0.2381,0.2474,0.2476 Q0.2506,0.248,0.2558,0.2489 T0.2651,0.2505 T0.2739,0.252 T0.2819,0.2526 Q0.2835,0.2526,0.2851,0.2522 L0.2859,0.2522 Q0.2948,0.2522,0.3104,0.2574 T0.3309,0.2671 Q0.3333,0.2696,0.3365,0.2696 Q0.339,0.2696,0.3442,0.2684 T0.3526,0.2671 T0.3582,0.2679 Q0.3687,0.2717,0.3968,0.2742 Q0.4,0.2746,0.4036,0.2771 T0.4096,0.2796 Q0.4112,0.2796,0.4137,0.2787 Q0.4169,0.2779,0.4257,0.2779 Q0.4594,0.2779,0.4779,0.2849 Q0.4876,0.2891,0.4972,0.2891 Q0.4996,0.2891,0.506,0.2887 T0.5165,0.2883 Q0.5253,0.2883,0.5305,0.2889 T0.541,0.292 T0.5494,0.299 L0.5494,0.3015 Q0.5494,0.3036,0.5466,0.3051 T0.5386,0.3073 T0.5285,0.3086 T0.5181,0.3094 T0.5108,0.3102 Q0.5068,0.3107,0.494,0.3121 L0.4683,0.315 T0.4506,0.3181 Q0.449,0.3181,0.4426,0.32 T0.4313,0.3229 T0.4197,0.3256 T0.4064,0.3281 T0.3952,0.3289 Q0.3888,0.3289,0.3855,0.3273 Q0.3783,0.3243,0.3703,0.3243 Q0.3598,0.3243,0.351,0.3297 Q0.3478,0.3314,0.3422,0.3314 Q0.3398,0.3314,0.3337,0.3306 T0.3245,0.3297 T0.3189,0.3306 Q0.3149,0.3314,0.2956,0.3328 T0.2715,0.3368 Q0.2691,0.338,0.2635,0.3391 T0.2522,0.3407 T0.2406,0.3424 T0.2313,0.3447 Q0.2305,0.3447,0.2213,0.348 T0.206,0.354 T0.1924,0.3621 T0.1815,0.3733 Q0.1767,0.3816,0.1767,0.3849 Q0.1767,0.387,0.1783,0.3893 T0.1847,0.3961 T0.1936,0.4063 T0.2004,0.4181 T0.2052,0.4264 T0.2145,0.4309 T0.2386,0.4359 Q0.2458,0.4372,0.2554,0.4372 Q0.2578,0.4372,0.2627,0.437 T0.2699,0.4367 Q0.2795,0.4367,0.2851,0.4384 Q0.294,0.4409,0.3092,0.4409 Q0.3116,0.4409,0.3157,0.4407 T0.3213,0.4405 Q0.3301,0.4405,0.3466,0.444 T0.3687,0.4509 Q0.3703,0.4517,0.3703,0.4525 Q0.3703,0.4575,0.3373,0.4596 Q0.3133,0.4608,0.2988,0.4658 Q0.294,0.4674,0.2803,0.4674 Q0.2763,0.4674,0.2687,0.4672 T0.257,0.467 Q0.2402,0.467,0.2313,0.4687 Q0.2209,0.4708,0.2209,0.4737 Q0.2209,0.4741,0.2213,0.4749 T0.2229,0.4764 L0.2249,0.4774 T0.2265,0.4778 L0.2273,0.4782 Q0.2498,0.4803,0.2618,0.4803 Q0.2667,0.4803,0.2691,0.4799 Q0.2699,0.4795,0.2715,0.4795 Q0.2779,0.4795,0.3133,0.4873 Q0.3181,0.4886,0.3233,0.4892 T0.3386,0.4905 T0.3542,0.4915 T0.3655,0.4919 Q0.3703,0.4919,0.3779,0.4915 T0.3888,0.4911 T0.3952,0.4915 Q0.3976,0.4919,0.4008,0.4927 L0.4072,0.4944 T0.4149,0.4961 T0.4249,0.4975 T0.4369,0.4985 Q0.449,0.499,0.4554,0.5 T0.4651,0.5031 T0.4699,0.5085 Q0.4707,0.5093,0.4707,0.5097 Q0.4707,0.5114,0.4687,0.5124 T0.4631,0.5139 T0.4562,0.5145 T0.4478,0.5147 L0.4402,0.5147 Q0.4353,0.5151,0.4265,0.5174 T0.4096,0.5197 L0.4024,0.5197 Q0.4,0.5193,0.3968,0.5193 Q0.3896,0.5193,0.3831,0.5209 T0.3703,0.5253 T0.3598,0.5288 Q0.3558,0.5301,0.3321,0.5342 T0.302,0.5409 Q0.2876,0.5454,0.2337,0.55 Q0.2112,0.5521,0.2112,0.5558 Q0.2112,0.5574,0.2161,0.5612 Q0.2201,0.5637,0.2281,0.5637 Q0.2305,0.5637,0.2361,0.5633 T0.2442,0.5628 Q0.2474,0.5628,0.2498,0.5633 Q0.2635,0.5653,0.3133,0.5653 Q0.3261,0.5653,0.3301,0.5649 L0.3325,0.5649 Q0.3349,0.5649,0.3402,0.5657 T0.3494,0.5666 Q0.3542,0.5666,0.3598,0.5657 Q0.3663,0.5645,0.3695,0.5645 Q0.3735,0.5645,0.3775,0.5666 T0.3847,0.5695 Q0.3888,0.5707,0.3912,0.5709 T0.3968,0.5711 T0.4048,0.572 Q0.4096,0.5724,0.4165,0.5726 T0.4281,0.5734 T0.4378,0.5757 Q0.4418,0.5774,0.4522,0.5774 Q0.4562,0.5774,0.4655,0.5769 T0.4795,0.5765 Q0.4932,0.5765,0.4972,0.579 Q0.5028,0.5819,0.51,0.5819 Q0.5141,0.5819,0.5221,0.5809 T0.5341,0.5798 L0.5373,0.5798 Q0.5422,0.5798,0.557,0.5821 T0.5855,0.5844 L0.5936,0.5844 L0.5992,0.5844 T0.604,0.5848 T0.6076,0.5852 T0.6108,0.5859 T0.6137,0.5865 T0.6161,0.5873 T0.6181,0.5881 T0.6209,0.5896 L0.6233,0.591 Q0.6265,0.5927,0.6353,0.5927 L0.6402,0.5927 L0.645,0.5927 Q0.6723,0.5927,0.6948,0.6039 Q0.6988,0.606,0.7036,0.606 Q0.7076,0.606,0.7153,0.6045 T0.7261,0.6031 Q0.7309,0.6031,0.7454,0.6027 T0.7647,0.6022 Q0.7783,0.6022,0.808,0.608 L0.812,0.608 Q0.8169,0.608,0.8293,0.6062 T0.8474,0.6039 L0.8506,0.6039 Q0.857,0.6039,0.8655,0.6049 T0.8835,0.6074 T0.8972,0.6093 Q0.9036,0.6101,0.9213,0.6101 L0.9398,0.6101 L0.959,0.6101 L0.9767,0.6101 Q0.9976,0.6109,0.9976,0.6176 Q0.9976,0.6188,0.9968,0.6226 Q0.9952,0.6284,0.9671,0.6284 L0.959,0.6284 L0.9526,0.6284 Q0.9486,0.6284,0.9382,0.6302 T0.9205,0.6321 L0.9173,0.6321 Q0.9133,0.6317,0.9092,0.6317 Q0.9012,0.6317,0.8715,0.6346 Q0.8233,0.6387,0.7944,0.6421 Q0.7888,0.6429,0.7831,0.6429 Q0.7783,0.6429,0.7731,0.6425 T0.7663,0.6421 L0.7622,0.6421 Q0.7614,0.6425,0.7586,0.6425 T0.7474,0.6421 T0.7349,0.6416 Q0.7261,0.6416,0.7221,0.6429 Q0.7173,0.645,0.7032,0.646 T0.6711,0.6479 T0.6482,0.6491 Q0.645,0.6495,0.6418,0.6495 Q0.6353,0.6495,0.6273,0.6474 T0.6161,0.6454 Q0.612,0.6454,0.6088,0.6477 T0.6,0.6499 Q0.5912,0.6499,0.5494,0.6578 Q0.5422,0.6591,0.5373,0.6591 Q0.5341,0.6591,0.5281,0.6584 T0.5173,0.6578 L0.5124,0.6578 Q0.5004,0.6582,0.4747,0.6632 Q0.4675,0.6649,0.4398,0.6667 T0.3851,0.6703 T0.3566,0.6719 Q0.3534,0.6723,0.3406,0.6734 T0.3141,0.6757 T0.2948,0.6773 Q0.2908,0.6781,0.2819,0.6829 T0.2618,0.6877 Q0.2482,0.6877,0.2273,0.6958 T0.1992,0.7117 Q0.1976,0.7138,0.1976,0.7155 Q0.1976,0.7188,0.2032,0.7213 T0.2181,0.7258 T0.2289,0.7292 Q0.2337,0.7308,0.2378,0.7316 L0.2458,0.7333 T0.2522,0.7345 Q0.2747,0.7399,0.2835,0.7399 Q0.29,0.7399,0.3048,0.7408 T0.3253,0.7416 Q0.3277,0.7416,0.3297,0.7414 T0.3341,0.7412 L0.3598,0.7445 Q0.3639,0.7449,0.3663,0.7449 Q0.3695,0.7449,0.3751,0.7443 T0.3855,0.7437 L0.3904,0.7437 Q0.3984,0.7441,0.4141,0.7472 T0.4353,0.7507 Q0.4434,0.752,0.4434,0.7553 Q0.4434,0.7578,0.439,0.7605 T0.4297,0.764 Q0.4257,0.7648,0.4201,0.7655 T0.4076,0.7665 T0.3984,0.7673 Q0.3968,0.7673,0.3912,0.7677 T0.3815,0.7688 T0.3735,0.771 T0.3655,0.7727 Q0.3606,0.7727,0.3534,0.7708 T0.3458,0.769 L0.3446,0.769 Q0.3414,0.769,0.3297,0.7713 T0.3149,0.7735 Q0.3092,0.7735,0.296,0.7771 T0.2771,0.7831 Q0.2683,0.7872,0.2554,0.7885 Q0.253,0.7889,0.2325,0.7897 T0.2032,0.7914 Q0.2008,0.7918,0.198,0.792 T0.194,0.7924 T0.1912,0.7928 T0.1888,0.793 T0.1867,0.7934 T0.1851,0.7943 T0.1843,0.7953 T0.1835,0.7968 T0.1823,0.7984 T0.1815,0.8001 Q0.1815,0.8022,0.1847,0.8032 T0.1932,0.8053 T0.2008,0.8071 Q0.2072,0.8092,0.2265,0.8092 Q0.2361,0.8092,0.2586,0.8088 L0.2635,0.8088 Q0.2731,0.8088,0.2795,0.8098 T0.2928,0.8134 T0.302,0.8167 Q0.306,0.8179,0.3277,0.8183 T0.3542,0.8221 Q0.359,0.8246,0.3622,0.8246 Q0.3639,0.8246,0.3675,0.8237 T0.3751,0.8229 Q0.3968,0.8229,0.4048,0.8275 Q0.4088,0.8291,0.4161,0.8291 Q0.4225,0.8291,0.4353,0.8275 Q0.4369,0.827,0.4394,0.827 Q0.4474,0.827,0.4675,0.8345 Q0.4707,0.8358,0.4739,0.8358 Q0.4755,0.8358,0.4799,0.8353 T0.4876,0.8349 Q0.494,0.8349,0.5012,0.8362 L0.5076,0.837 T0.5133,0.8376 T0.5181,0.8382 T0.5221,0.8391 L0.5253,0.8399 T0.5277,0.8409 L0.5293,0.8422 T0.5301,0.8438 L0.5301,0.8461 Q0.5301,0.8499,0.5249,0.8511 T0.5112,0.8526 T0.5004,0.8532 Q0.4964,0.854,0.4851,0.8546 T0.4667,0.8565 Q0.4578,0.8577,0.441,0.8577 L0.4345,0.8577 L0.4289,0.8577 L0.4209,0.8577 Q0.4153,0.8582,0.4088,0.8586 T0.3968,0.8592 T0.3855,0.8594 T0.3755,0.8596 T0.3675,0.86 T0.3622,0.8602 Q0.3317,0.8635,0.3237,0.8635 Q0.2996,0.8635,0.2996,0.8681 Q0.2996,0.8698,0.3032,0.8733 T0.3068,0.8797 L0.3068,0.8822 Q0.3052,0.8859,0.2859,0.8859 Q0.2787,0.8859,0.2739,0.8851 L0.2699,0.8851 Q0.2659,0.8851,0.259,0.888 T0.2498,0.8913 Q0.2394,0.8946,0.2056,0.8971 Q0.2032,0.8971,0.1928,0.9002 T0.1703,0.9034 Q0.1526,0.9034,0.1353,0.9067 T0.1173,0.915 L0.1173,0.9158 Q0.1173,0.9166,0.1177,0.9175 T0.1189,0.9191 T0.1205,0.9204 T0.1229,0.9212 T0.1257,0.922 T0.1285,0.9226 T0.1309,0.9231 T0.1337,0.9235 T0.1365,0.9237 L0.1446,0.9237 T0.1554,0.9243 T0.1614,0.9268 T0.1659,0.9293 T0.1743,0.9303 T0.1823,0.9316 Q0.1863,0.932,0.1912,0.9332 T0.2028,0.9361 T0.2153,0.939 Q0.2233,0.9411,0.2297,0.9448 T0.2386,0.9494 Q0.2426,0.9506,0.2506,0.9506 Q0.253,0.9506,0.2594,0.9504 T0.2699,0.9502 Q0.2763,0.9502,0.2819,0.9511 Q0.2851,0.9511,0.2871,0.9529 T0.2892,0.9573 Q0.2892,0.9627,0.2811,0.9635 Q0.2434,0.9652,0.2337,0.9672 Q0.2257,0.9693,0.2241,0.9693 Q0.2217,0.9693,0.2096,0.9689 L0.2056,0.9689 Q0.1976,0.9689,0.1924,0.9699 T0.1787,0.9741 L0.1647,0.9793 Q0.1518,0.983,0.1285,0.983 L0.1261,0.983 L0.1237,0.983 Q0.1124,0.983,0.1076,0.9838 Q0.102,0.9851,0.0839,0.9896 T0.0602,0.9954 T0.0406,0.9971 T0.0153,0.9979 L0.0032,0.9979 Z",type:0}},"\uE0CA":{type:5,data:{d:"M0.9992,0.998 Q0.9992,1,0.9968,1 Q0.9521,1,0.9392,0.998 Q0.9335,0.997,0.9152,0.9921 T0.8913,0.9862 Q0.8873,0.9852,0.8783,0.9852 L0.8747,0.9852 L0.871,0.9852 Q0.8483,0.9852,0.8345,0.9812 Q0.8289,0.9792,0.8228,0.9773 T0.8135,0.9743 T0.8054,0.9723 T0.7948,0.9713 L0.7908,0.9713 L0.777,0.9713 T0.7656,0.9693 Q0.7616,0.9683,0.7498,0.9674 T0.7283,0.9664 L0.7186,0.9654 Q0.708,0.9644,0.708,0.9575 Q0.708,0.9545,0.7109,0.952 T0.7178,0.9486 L0.7315,0.9486 L0.7417,0.9486 L0.7502,0.9486 Q0.7575,0.9486,0.7599,0.9476 Q0.7616,0.9466,0.7685,0.9426 T0.7843,0.9377 Q0.7899,0.9357,0.8009,0.9332 T0.8175,0.9298 Q0.82,0.9288,0.8228,0.9283 T0.8277,0.9278 T0.8313,0.9273 T0.8341,0.9263 T0.8362,0.9258 Q0.8402,0.9219,0.854,0.9219 L0.8569,0.9219 L0.8597,0.9219 L0.8637,0.9219 Q0.88,0.9199,0.88,0.9159 L0.88,0.9149 Q0.88,0.913,0.8719,0.911 T0.852,0.9075 T0.8297,0.906 Q0.8224,0.906,0.8167,0.905 T0.8074,0.9031 T0.8001,0.9006 T0.794,0.8991 Q0.7599,0.8971,0.7494,0.8942 Q0.7461,0.8932,0.7397,0.8902 T0.7299,0.8872 L0.7267,0.8872 Q0.721,0.8882,0.7121,0.8882 Q0.6926,0.8882,0.691,0.8833 L0.691,0.8803 Q0.691,0.8773,0.6922,0.8749 T0.6955,0.8704 L0.6975,0.8684 Q0.6926,0.8655,0.6764,0.8655 Q0.6683,0.8655,0.6375,0.8625 Q0.6358,0.8625,0.629,0.862 T0.6148,0.8615 T0.5973,0.861 T0.5791,0.8605 L0.5718,0.8605 L0.5661,0.8605 L0.5596,0.8605 Q0.5426,0.8605,0.5337,0.8586 Q0.5264,0.8576,0.515,0.8571 T0.4996,0.8556 Q0.498,0.8556,0.4947,0.8551 T0.4882,0.8546 T0.4809,0.8541 T0.474,0.8531 T0.4692,0.8506 T0.4672,0.8467 Q0.4672,0.8437,0.468,0.8422 T0.4704,0.8398 T0.4736,0.8383 T0.4781,0.8368 T0.4838,0.8358 T0.4911,0.8353 T0.4988,0.8338 Q0.5069,0.8328,0.5142,0.8328 Q0.5174,0.8328,0.5215,0.8333 T0.5264,0.8338 Q0.5296,0.8338,0.532,0.8328 Q0.5523,0.8249,0.5604,0.8249 L0.5653,0.8249 Q0.5775,0.8269,0.5848,0.8269 Q0.5912,0.8269,0.5937,0.8259 Q0.6026,0.821,0.6253,0.821 Q0.6294,0.821,0.6334,0.822 T0.6383,0.8229 Q0.6399,0.8229,0.644,0.82 Q0.6504,0.817,0.6723,0.816 T0.6975,0.814 Q0.6999,0.814,0.7048,0.8121 T0.7129,0.8091 T0.7226,0.8071 T0.7372,0.8061 L0.7421,0.8061 Q0.7624,0.8071,0.7721,0.8071 Q0.7924,0.8071,0.7981,0.8051 Q0.8021,0.8032,0.807,0.8027 T0.8135,0.8017 T0.8151,0.8002 L0.8151,0.7992 Q0.8143,0.7962,0.8127,0.7957 T0.8045,0.7943 Q0.7997,0.7943,0.7964,0.7933 Q0.7883,0.7933,0.7676,0.7923 T0.7437,0.7903 Q0.7307,0.7893,0.7218,0.7844 Q0.7194,0.7834,0.7141,0.7819 L0.7036,0.7789 T0.693,0.7765 T0.6853,0.7755 Q0.6805,0.7755,0.6691,0.773 T0.6553,0.7705 Q0.6545,0.7705,0.6545,0.7715 Q0.6537,0.7715,0.65,0.7725 T0.6423,0.774 T0.635,0.7745 Q0.6294,0.7745,0.6257,0.773 T0.618,0.771 T0.6083,0.77 T0.601,0.7695 Q0.5985,0.7695,0.5921,0.769 T0.5795,0.7676 T0.5702,0.7666 Q0.5653,0.7656,0.5596,0.7616 T0.5539,0.7547 Q0.5539,0.7498,0.5645,0.7488 Q0.5693,0.7478,0.5852,0.7448 T0.6099,0.7418 L0.6148,0.7418 Q0.6196,0.7418,0.6253,0.7423 T0.6338,0.7428 L0.6399,0.7428 Q0.6431,0.7418,0.6488,0.7409 T0.6586,0.7394 T0.6659,0.7389 L0.6707,0.7389 Q0.6723,0.7399,0.6748,0.7399 Q0.6796,0.7399,0.6946,0.7389 T0.7161,0.7379 Q0.7251,0.7379,0.7478,0.7319 Q0.7494,0.7319,0.7539,0.7315 T0.762,0.73 T0.7697,0.727 T0.7818,0.723 T0.7948,0.7196 T0.7997,0.7151 Q0.7997,0.7141,0.7989,0.7132 Q0.7924,0.7062,0.7717,0.6978 T0.738,0.6894 Q0.7267,0.6894,0.717,0.6845 T0.7048,0.6795 Q0.6975,0.6785,0.6711,0.6766 T0.6431,0.6746 Q0.6415,0.6736,0.6144,0.6721 T0.5596,0.6686 T0.5247,0.6657 Q0.4996,0.6607,0.4874,0.6597 L0.4834,0.6597 Q0.4785,0.6597,0.4724,0.6607 T0.4631,0.6617 Q0.4574,0.6617,0.4501,0.6597 Q0.4088,0.6518,0.4006,0.6518 Q0.3933,0.6518,0.3901,0.6499 T0.3844,0.6479 T0.3739,0.6499 T0.3585,0.6518 L0.352,0.6518 Q0.3471,0.6508,0.335,0.6503 T0.3122,0.6494 T0.2912,0.6479 T0.2766,0.6449 Q0.2733,0.6439,0.2668,0.6439 Q0.2628,0.6439,0.2543,0.6444 T0.2425,0.6449 T0.2368,0.6439 L0.2344,0.6439 Q0.2336,0.6439,0.2283,0.6444 T0.2182,0.6449 Q0.2117,0.6449,0.2052,0.6439 Q0.1768,0.6409,0.1281,0.637 Q0.0989,0.634,0.0916,0.634 L0.0835,0.634 L0.0795,0.634 Q0.0722,0.634,0.0616,0.632 T0.0479,0.6301 Q0.0462,0.6301,0.0406,0.6306 T0.0308,0.6311 Q0.0032,0.6311,0.0008,0.6231 Q0,0.6202,0,0.6182 Q0,0.6083,0.0227,0.6083 Q0.03,0.6073,0.0422,0.6073 Q0.0487,0.6073,0.0608,0.6078 T0.0787,0.6083 Q0.0973,0.6083,0.103,0.6073 Q0.1062,0.6063,0.1131,0.6053 T0.1253,0.6039 T0.1375,0.6024 T0.1492,0.6014 L0.1533,0.6014 Q0.1598,0.6024,0.1723,0.6044 T0.189,0.6063 Q0.1906,0.6063,0.1922,0.6053 Q0.2214,0.6004,0.236,0.6004 Q0.2409,0.6004,0.2551,0.6009 T0.2741,0.6014 Q0.2782,0.6014,0.2859,0.6024 T0.2968,0.6034 Q0.3009,0.6034,0.3041,0.6024 Q0.3277,0.5905,0.356,0.5905 L0.3613,0.5905 L0.3666,0.5905 Q0.3731,0.5905,0.3755,0.5895 Q0.382,0.5856,0.3844,0.5846 T0.3938,0.5831 T0.4144,0.5826 T0.4428,0.5801 T0.4631,0.5776 L0.4663,0.5776 Q0.4704,0.5776,0.4785,0.5786 T0.4899,0.5796 Q0.4964,0.5796,0.5012,0.5767 Q0.5069,0.5737,0.5223,0.5737 Q0.5272,0.5737,0.5369,0.5742 T0.5499,0.5747 Q0.558,0.5747,0.5612,0.5737 Q0.5661,0.5717,0.5714,0.5712 T0.5835,0.5702 T0.5945,0.5692 T0.6018,0.5687 L0.6062,0.5687 T0.6095,0.5682 T0.6148,0.5678 Q0.6172,0.5668,0.6196,0.5653 T0.6245,0.5633 T0.631,0.5628 T0.6407,0.5638 T0.6504,0.5648 T0.6594,0.5638 T0.6675,0.5628 L0.6707,0.5628 L0.6861,0.5628 Q0.7364,0.5628,0.7494,0.5608 L0.7567,0.5608 L0.7652,0.5608 L0.7729,0.5608 Q0.7794,0.5608,0.7818,0.5589 Q0.7851,0.5569,0.7859,0.5559 Q0.7835,0.5539,0.7656,0.5519 Q0.7121,0.547,0.6975,0.543 Q0.6934,0.541,0.6853,0.5396 L0.6691,0.5366 T0.6525,0.5336 T0.6391,0.5312 Q0.6367,0.5302,0.6245,0.5257 T0.6034,0.5213 L0.5977,0.5213 Q0.5945,0.5223,0.5904,0.5223 Q0.5839,0.5223,0.5787,0.5208 T0.5685,0.5183 T0.5596,0.5173 Q0.558,0.5173,0.5552,0.5168 T0.5495,0.5163 L0.5434,0.5163 T0.5373,0.5158 T0.532,0.5148 T0.5284,0.5129 T0.5272,0.5104 L0.5272,0.5074 Q0.5304,0.5015,0.5377,0.499 T0.5629,0.4965 Q0.5734,0.4955,0.5811,0.4946 T0.5953,0.4916 T0.605,0.4896 Q0.6083,0.4886,0.6123,0.4886 Q0.6148,0.4886,0.6225,0.4891 T0.635,0.4896 L0.6456,0.4896 Q0.6837,0.4857,0.6861,0.4857 Q0.7226,0.4768,0.7283,0.4768 Q0.7307,0.4768,0.732,0.4773 T0.7372,0.4777 Q0.7502,0.4777,0.7721,0.4758 Q0.7753,0.4748,0.777,0.4738 Q0.7745,0.4718,0.768,0.4708 Q0.7599,0.4688,0.7445,0.4688 Q0.7405,0.4688,0.7324,0.4693 T0.721,0.4698 Q0.7056,0.4698,0.6999,0.4679 Q0.6861,0.4629,0.6626,0.4619 Q0.6586,0.4609,0.6533,0.4604 T0.6419,0.459 T0.6314,0.456 T0.6269,0.452 Q0.6269,0.451,0.6302,0.4491 Q0.6358,0.4451,0.6525,0.4416 T0.6788,0.4382 L0.6845,0.4382 L0.6902,0.4382 Q0.7064,0.4382,0.7145,0.4362 Q0.7202,0.4342,0.7307,0.4342 L0.7356,0.4342 L0.7409,0.4342 L0.7453,0.4342 Q0.7543,0.4342,0.7607,0.4332 Q0.7778,0.4313,0.7847,0.4288 T0.7932,0.4248 T0.7972,0.4169 T0.8045,0.4045 L0.8094,0.3986 T0.8135,0.3942 L0.8167,0.3912 T0.8191,0.3887 T0.8204,0.3867 T0.8208,0.3848 Q0.8208,0.3818,0.8167,0.3739 Q0.8127,0.3689,0.8054,0.3635 T0.7924,0.3556 T0.7778,0.3497 T0.768,0.3462 Q0.764,0.3442,0.7478,0.3427 T0.7267,0.3383 Q0.7234,0.3363,0.7044,0.3348 T0.6805,0.3323 L0.6764,0.3323 T0.6679,0.3328 T0.6586,0.3333 Q0.6521,0.3333,0.648,0.3314 Q0.6391,0.3264,0.6294,0.3264 Q0.6229,0.3264,0.6156,0.3294 Q0.6115,0.3304,0.6042,0.3304 Q0.5977,0.3304,0.5888,0.3289 T0.5738,0.3264 T0.5584,0.3225 L0.5491,0.3195 Q0.5418,0.3185,0.5162,0.3155 T0.4895,0.3121 T0.4826,0.3116 T0.4716,0.3111 T0.4607,0.3096 T0.4513,0.3066 T0.4477,0.3017 Q0.4477,0.2997,0.4485,0.2977 Q0.4526,0.2898,0.4611,0.2878 T0.4842,0.2859 Q0.4882,0.2859,0.4943,0.2864 T0.5028,0.2868 Q0.5118,0.2868,0.5215,0.2829 Q0.5401,0.276,0.5734,0.276 Q0.5831,0.276,0.5872,0.277 L0.5904,0.277 T0.5949,0.2745 T0.6034,0.272 Q0.631,0.269,0.6407,0.2661 Q0.6448,0.2641,0.6488,0.2641 T0.6573,0.2656 T0.6634,0.2671 Q0.6659,0.2671,0.6675,0.2651 Q0.6723,0.2601,0.6886,0.2552 T0.7137,0.2502 L0.7153,0.2502 L0.7178,0.2502 Q0.7218,0.2502,0.7279,0.2493 T0.7417,0.2468 T0.7526,0.2453 Q0.8208,0.2354,0.8281,0.2354 Q0.8337,0.2354,0.8394,0.2344 T0.8496,0.2315 T0.854,0.2265 Q0.854,0.2216,0.8443,0.2176 Q0.8354,0.2127,0.8216,0.2102 T0.7972,0.2072 T0.777,0.2067 T0.7648,0.2057 Q0.7632,0.2047,0.7498,0.2038 T0.7202,0.2018 T0.6983,0.1998 Q0.6918,0.1998,0.676,0.1949 T0.6537,0.1899 Q0.6496,0.1899,0.6472,0.1919 Q0.6342,0.1968,0.6123,0.1968 Q0.5961,0.1968,0.5912,0.1929 Q0.5904,0.1919,0.5892,0.1919 T0.5823,0.1944 T0.5722,0.1968 T0.5637,0.1949 Q0.5556,0.1909,0.5158,0.1869 Q0.5109,0.1869,0.5032,0.1835 T0.4915,0.18 Q0.4907,0.18,0.4891,0.181 Q0.4793,0.182,0.455,0.182 Q0.4177,0.182,0.4015,0.1771 Q0.3901,0.1741,0.3779,0.1741 Q0.3747,0.1741,0.369,0.1746 T0.3609,0.1751 Q0.3569,0.1751,0.3536,0.1741 Q0.3512,0.1741,0.3459,0.1736 T0.3358,0.1726 T0.3256,0.1716 T0.3167,0.1696 T0.3106,0.1662 Q0.3082,0.1632,0.3082,0.1592 Q0.3082,0.1543,0.3114,0.1508 T0.3204,0.1474 L0.3236,0.1474 Q0.3301,0.1494,0.3333,0.1494 Q0.339,0.1494,0.3496,0.1454 Q0.3674,0.1385,0.3869,0.1385 Q0.395,0.1385,0.4011,0.137 T0.4116,0.1355 T0.4197,0.1375 T0.425,0.1395 Q0.429,0.1395,0.4355,0.1365 Q0.4631,0.1266,0.4882,0.1266 Q0.5126,0.1266,0.5337,0.1246 Q0.5369,0.1246,0.5458,0.1207 T0.5661,0.1167 L0.5693,0.1167 Q0.5791,0.1177,0.5852,0.1187 T0.5921,0.1197 Q0.5945,0.1197,0.6002,0.1167 Q0.6026,0.1147,0.605,0.1123 T0.6091,0.1093 T0.6156,0.1088 T0.6318,0.1098 Q0.6456,0.1108,0.6569,0.1137 T0.6707,0.1167 Q0.674,0.1167,0.6772,0.1147 Q0.6821,0.1108,0.6946,0.1083 T0.721,0.1044 T0.7453,0.1029 L0.7526,0.1029 Q0.7583,0.1039,0.7652,0.1053 T0.7753,0.1068 Q0.7778,0.1068,0.7802,0.1058 Q0.7981,0.1019,0.8143,0.1019 Q0.8175,0.1019,0.8439,0.0964 T0.8735,0.091 Q0.8759,0.091,0.8836,0.089 T0.8938,0.0851 Q0.8929,0.0841,0.8905,0.0841 Q0.8832,0.0821,0.8735,0.0821 Q0.8694,0.0821,0.8629,0.0826 T0.854,0.0831 Q0.8508,0.0831,0.8491,0.0821 Q0.8475,0.0821,0.8171,0.0811 T0.7802,0.0781 T0.7571,0.0737 T0.7332,0.0712 Q0.7299,0.0712,0.7247,0.0717 T0.7161,0.0722 L0.7064,0.0722 Q0.7032,0.0712,0.7003,0.0707 T0.6959,0.0692 T0.693,0.0678 T0.6914,0.0663 T0.6902,0.0648 T0.689,0.0643 L0.6869,0.0643 L0.6845,0.0643 L0.6821,0.0643 Q0.6553,0.0643,0.6553,0.0554 T0.6796,0.0465 L0.6813,0.0465 L0.6845,0.0465 Q0.6918,0.0425,0.717,0.0376 Q0.7226,0.0366,0.7267,0.0356 T0.7324,0.0336 T0.7376,0.0317 T0.747,0.0297 Q0.7518,0.0287,0.7551,0.0287 T0.762,0.0297 T0.768,0.0307 L0.7729,0.0307 Q0.7802,0.0277,0.7997,0.0267 T0.8224,0.0237 Q0.8273,0.0227,0.8341,0.0223 T0.8475,0.0193 T0.8654,0.0143 T0.8873,0.0079 Q0.9002,0.003,0.9331,0.004 T0.97,0.004 Q0.9838,0,0.9976,0.001 Q1,0.001,1,0.003 Z M0.9935,0.996 L0.9951,0.0049 Q0.9822,0.0049,0.9716,0.0079 Q0.9659,0.0099,0.9327,0.0089 T0.8897,0.0119 Q0.8775,0.0158,0.8666,0.0183 T0.85,0.0237 Q0.8418,0.0267,0.8341,0.0272 T0.8248,0.0277 Q0.82,0.0297,0.8001,0.0312 T0.7745,0.0346 Q0.7697,0.0356,0.7664,0.0356 Q0.764,0.0356,0.7603,0.0341 T0.7534,0.0326 Q0.751,0.0326,0.7478,0.0336 Q0.7429,0.0346,0.7397,0.0356 T0.7344,0.0376 T0.7283,0.0401 T0.7178,0.0425 Q0.6926,0.0465,0.6878,0.0495 Q0.6845,0.0514,0.6764,0.0514 L0.6723,0.0514 L0.6691,0.0514 Q0.6602,0.0514,0.6602,0.0554 Q0.6602,0.0574,0.6659,0.0579 T0.6796,0.0589 T0.691,0.0603 T0.6967,0.0643 T0.7072,0.0673 Q0.7121,0.0682,0.7153,0.0682 T0.7238,0.0678 T0.7332,0.0673 Q0.7413,0.0673,0.7579,0.0692 T0.7818,0.0742 Q0.7867,0.0752,0.8171,0.0762 T0.8508,0.0781 L0.852,0.0781 T0.8601,0.0776 T0.8719,0.0772 Q0.8832,0.0772,0.8921,0.0791 Q0.8994,0.0811,0.8994,0.0841 T0.8933,0.0895 T0.8812,0.094 T0.8735,0.0959 T0.8451,0.1014 T0.8143,0.1068 Q0.7989,0.1068,0.7818,0.1098 Q0.7778,0.1108,0.7745,0.1108 T0.768,0.1103 T0.7603,0.1088 T0.7518,0.1068 L0.7461,0.1068 Q0.7315,0.1068,0.7088,0.1103 T0.6805,0.1177 Q0.6756,0.1207,0.6699,0.1207 Q0.6667,0.1207,0.6545,0.1177 T0.631,0.1147 Q0.6269,0.1137,0.6225,0.1133 T0.616,0.1128 L0.6123,0.1128 T0.6103,0.1133 L0.6083,0.1157 T0.6034,0.1197 Q0.5961,0.1246,0.5921,0.1246 Q0.5904,0.1246,0.5835,0.1231 T0.5693,0.1217 L0.5661,0.1217 Q0.558,0.1217,0.5535,0.1231 T0.5446,0.1266 T0.5345,0.1296 Q0.5126,0.1316,0.4882,0.1316 T0.438,0.1405 Q0.4298,0.1434,0.425,0.1434 Q0.4209,0.1434,0.4173,0.1419 T0.4112,0.1405 T0.4027,0.1419 T0.3869,0.1434 Q0.3682,0.1434,0.3512,0.1494 Q0.3398,0.1533,0.3333,0.1533 Q0.3285,0.1533,0.3228,0.1513 L0.3212,0.1513 Q0.3179,0.1513,0.3155,0.1538 T0.3131,0.1592 Q0.3131,0.1612,0.3147,0.1632 Q0.3171,0.1652,0.3232,0.1667 T0.3406,0.1691 T0.3544,0.1701 L0.3601,0.1701 L0.3678,0.1701 L0.3771,0.1701 Q0.3909,0.1701,0.4031,0.1731 Q0.4177,0.178,0.4558,0.178 Q0.4793,0.178,0.4882,0.1761 L0.4915,0.1761 Q0.498,0.1761,0.5061,0.179 T0.5158,0.183 Q0.5166,0.183,0.5231,0.1835 T0.5341,0.1845 T0.545,0.1855 T0.5572,0.1874 T0.5661,0.1909 Q0.5677,0.1919,0.5702,0.1919 T0.5791,0.1894 T0.588,0.1869 Q0.5921,0.1869,0.5945,0.1899 Q0.5977,0.1919,0.6123,0.1919 Q0.6334,0.1919,0.6448,0.1879 Q0.6488,0.186,0.6537,0.186 Q0.6626,0.186,0.6784,0.1909 T0.6983,0.1958 Q0.704,0.1968,0.7206,0.1973 T0.7506,0.1993 T0.7689,0.2018 L0.7705,0.2018 L0.7741,0.2018 L0.7786,0.2018 Q0.8248,0.2018,0.8475,0.2136 Q0.8597,0.2196,0.8597,0.2255 T0.8483,0.2359 T0.8281,0.2404 Q0.8208,0.2404,0.7534,0.2493 Q0.7502,0.2502,0.7425,0.2512 T0.7287,0.2532 T0.7178,0.2542 L0.7153,0.2542 L0.7137,0.2542 Q0.7064,0.2542,0.6906,0.2591 T0.6715,0.2681 T0.6626,0.272 Q0.6594,0.272,0.6541,0.2705 T0.6464,0.269 T0.6431,0.27 Q0.6318,0.273,0.6034,0.276 Q0.6018,0.276,0.5998,0.2774 T0.5957,0.2799 T0.5904,0.2809 L0.5856,0.2809 Q0.5823,0.2799,0.575,0.2799 Q0.541,0.2799,0.5239,0.2868 Q0.5134,0.2908,0.5028,0.2908 Q0.4996,0.2908,0.4935,0.2903 T0.4842,0.2898 Q0.4704,0.2898,0.4635,0.2918 T0.4534,0.2997 L0.4534,0.3007 Q0.4534,0.3037,0.459,0.3046 T0.4753,0.3066 T0.4899,0.3076 Q0.4907,0.3076,0.5166,0.3106 T0.5507,0.3155 Q0.5921,0.3264,0.605,0.3264 Q0.6107,0.3264,0.6131,0.3254 Q0.6212,0.3225,0.6294,0.3225 Q0.6407,0.3225,0.6513,0.3274 Q0.6529,0.3294,0.6569,0.3294 Q0.6594,0.3294,0.665,0.3284 T0.6748,0.3274 T0.6821,0.3284 Q0.6861,0.3294,0.6967,0.3299 T0.7165,0.3314 T0.7299,0.3343 Q0.7324,0.3363,0.7486,0.3383 T0.7697,0.3422 Q0.7713,0.3432,0.7774,0.3452 T0.7887,0.3492 T0.8005,0.3546 T0.8127,0.3625 T0.8208,0.3719 Q0.8256,0.3798,0.8256,0.3848 Q0.8256,0.3877,0.8236,0.3902 T0.8171,0.3976 T0.809,0.4075 T0.8021,0.4189 T0.7968,0.4273 T0.7867,0.4322 T0.7616,0.4382 Q0.7543,0.4392,0.7437,0.4392 L0.7364,0.4392 L0.7299,0.4392 Q0.7202,0.4392,0.7161,0.4402 Q0.7072,0.4431,0.691,0.4431 L0.6845,0.4431 L0.6788,0.4431 Q0.6707,0.4431,0.6545,0.4461 T0.6334,0.452 Q0.6399,0.456,0.6626,0.457 Q0.6878,0.458,0.7024,0.4639 Q0.7064,0.4649,0.7186,0.4649 L0.7295,0.4649 L0.7413,0.4649 Q0.7591,0.4649,0.7697,0.4669 Q0.7818,0.4688,0.7818,0.4728 Q0.7818,0.4748,0.7798,0.4763 T0.7753,0.4787 L0.7737,0.4797 L0.7729,0.4797 Q0.7494,0.4827,0.7388,0.4827 Q0.7332,0.4827,0.7307,0.4817 L0.7291,0.4817 Q0.7226,0.4817,0.6878,0.4896 Q0.6829,0.4906,0.6772,0.4911 T0.6618,0.4921 T0.646,0.4931 T0.6342,0.4936 T0.6212,0.4931 T0.6115,0.4926 Q0.6083,0.4926,0.6058,0.4931 T0.5969,0.4955 T0.5819,0.499 T0.5629,0.5005 Q0.5572,0.5005,0.5527,0.501 T0.545,0.502 T0.5397,0.503 T0.5361,0.5045 T0.5337,0.5064 T0.5328,0.5094 L0.532,0.5094 Q0.532,0.5104,0.5337,0.5109 T0.5381,0.5114 T0.5438,0.5119 T0.5515,0.5124 L0.5604,0.5124 Q0.5661,0.5134,0.5746,0.5153 T0.5912,0.5173 L0.5977,0.5173 L0.6026,0.5173 Q0.6139,0.5173,0.6273,0.5218 T0.6407,0.5272 Q0.6448,0.5282,0.6683,0.5321 T0.6991,0.5381 Q0.7137,0.543,0.7664,0.548 Q0.7908,0.55,0.7908,0.5559 Q0.7908,0.5589,0.7859,0.5628 Q0.781,0.5658,0.7713,0.5658 Q0.7689,0.5658,0.7628,0.5653 T0.7543,0.5648 T0.7502,0.5658 Q0.7364,0.5678,0.6878,0.5678 Q0.674,0.5678,0.6699,0.5668 L0.6683,0.5668 Q0.6659,0.5668,0.6606,0.5678 T0.6513,0.5687 Q0.6464,0.5687,0.6391,0.5678 Q0.6334,0.5668,0.6302,0.5668 T0.6241,0.5687 T0.6164,0.5717 Q0.6123,0.5727,0.6099,0.5727 L0.6062,0.5727 T0.6022,0.5732 T0.5953,0.5737 Q0.5904,0.5747,0.5839,0.5747 T0.5726,0.5752 T0.5629,0.5776 T0.5458,0.5796 Q0.5418,0.5796,0.5324,0.5791 T0.5191,0.5786 Q0.5077,0.5786,0.5045,0.5806 Q0.498,0.5836,0.4899,0.5836 Q0.4858,0.5836,0.4777,0.5826 T0.4655,0.5816 L0.4631,0.5816 Q0.4582,0.5816,0.4436,0.5841 T0.4144,0.5865 L0.4051,0.5865 L0.3986,0.5865 T0.3933,0.587 T0.3897,0.5875 T0.3869,0.5885 T0.3844,0.5895 T0.3812,0.591 T0.3779,0.5925 Q0.3739,0.5955,0.3633,0.5955 L0.3585,0.5955 L0.3536,0.5955 Q0.3277,0.5955,0.3066,0.6053 Q0.3017,0.6083,0.296,0.6083 Q0.2912,0.6083,0.2839,0.6068 T0.2741,0.6053 Q0.2693,0.6053,0.2547,0.6048 T0.236,0.6044 Q0.2222,0.6044,0.193,0.6103 L0.1882,0.6103 Q0.1825,0.6103,0.1703,0.6083 T0.1525,0.6063 L0.15,0.6063 Q0.1436,0.6063,0.135,0.6073 T0.1168,0.6098 T0.1038,0.6113 Q0.0973,0.6123,0.0787,0.6123 L0.06,0.6123 L0.0414,0.6123 L0.0235,0.6123 Q0.0049,0.6133,0.0049,0.6172 L0.0057,0.6222 Q0.0073,0.6261,0.043,0.6261 L0.0454,0.6261 L0.0479,0.6261 Q0.0527,0.6261,0.0633,0.6281 T0.0803,0.6301 L0.0835,0.6301 L0.0916,0.6301 Q0.0998,0.6301,0.129,0.632 Q0.1768,0.636,0.206,0.64 Q0.2117,0.6409,0.2165,0.6409 Q0.2206,0.6409,0.2263,0.6405 T0.2336,0.64 L0.2384,0.64 L0.2409,0.64 T0.251,0.6395 T0.2644,0.639 Q0.2741,0.639,0.279,0.6409 Q0.283,0.6429,0.2972,0.6439 T0.3293,0.6459 T0.3528,0.6469 L0.3577,0.6469 Q0.3642,0.6469,0.3723,0.6449 T0.3844,0.6429 Q0.3901,0.6429,0.3933,0.6454 T0.4006,0.6479 Q0.4088,0.6479,0.4517,0.6558 Q0.4582,0.6568,0.4623,0.6568 Q0.4655,0.6568,0.4716,0.6563 T0.4826,0.6558 L0.4882,0.6558 Q0.5004,0.6558,0.5255,0.6607 Q0.5328,0.6627,0.5604,0.6647 T0.6152,0.6682 T0.644,0.6696 T0.6719,0.6721 T0.7056,0.6756 Q0.7113,0.6756,0.7198,0.6805 T0.738,0.6855 Q0.7453,0.6855,0.7551,0.6879 T0.7741,0.6939 T0.7912,0.7018 T0.8029,0.7102 Q0.8054,0.7132,0.8054,0.7151 Q0.8054,0.7191,0.8009,0.7216 T0.7908,0.7255 T0.779,0.7285 T0.7721,0.731 Q0.7672,0.7329,0.7632,0.7339 T0.7551,0.7354 T0.7486,0.7369 Q0.7478,0.7369,0.7445,0.7379 T0.7388,0.7394 T0.7324,0.7404 L0.7242,0.7413 T0.7161,0.7418 Q0.7105,0.7418,0.6955,0.7428 T0.6748,0.7438 L0.6699,0.7438 L0.6667,0.7438 Q0.6659,0.7438,0.6407,0.7468 Q0.6367,0.7478,0.6334,0.7478 T0.6245,0.7468 T0.6148,0.7458 L0.6099,0.7458 Q0.6018,0.7468,0.5864,0.7498 T0.5653,0.7532 T0.5596,0.7557 Q0.5596,0.7596,0.571,0.7616 Q0.5742,0.7626,0.5803,0.7631 T0.5929,0.7641 T0.6018,0.7656 L0.6087,0.7656 T0.6192,0.7666 T0.6277,0.7695 Q0.631,0.7705,0.6342,0.7705 Q0.6383,0.7705,0.6452,0.7685 T0.6537,0.7666 L0.6553,0.7666 Q0.6602,0.7666,0.6711,0.769 T0.6853,0.7715 Q0.691,0.7715,0.7048,0.775 T0.7242,0.7814 Q0.7324,0.7854,0.7445,0.7864 Q0.7478,0.7864,0.768,0.7873 T0.7972,0.7893 Q0.7989,0.7893,0.8017,0.7898 T0.8054,0.7903 L0.8082,0.7903 T0.811,0.7908 T0.8131,0.7913 T0.8151,0.7918 L0.8167,0.7928 T0.8179,0.7943 T0.8191,0.7957 T0.8204,0.7977 T0.8208,0.8002 Q0.8208,0.8032,0.8171,0.8051 T0.8074,0.8081 T0.7997,0.8091 Q0.7932,0.8121,0.7737,0.8121 Q0.7664,0.8121,0.7413,0.8111 L0.7364,0.8111 Q0.7275,0.8111,0.7214,0.8121 T0.7084,0.8155 T0.6991,0.819 Q0.6934,0.82,0.6723,0.8205 T0.6472,0.8239 Q0.6423,0.8269,0.6375,0.8269 Q0.6358,0.8269,0.6338,0.8264 T0.6294,0.8254 T0.6253,0.8249 Q0.6042,0.8249,0.5961,0.8289 Q0.5921,0.8318,0.5839,0.8318 Q0.5783,0.8318,0.5645,0.8299 L0.5604,0.8299 Q0.5531,0.8299,0.5337,0.8368 Q0.5296,0.8378,0.5255,0.8378 Q0.5239,0.8378,0.5195,0.8373 T0.5118,0.8368 Q0.5061,0.8368,0.4996,0.8388 Q0.4947,0.8388,0.4882,0.8398 Q0.4777,0.8417,0.4753,0.8427 T0.4728,0.8467 Q0.4728,0.8487,0.4769,0.8492 T0.4886,0.8501 T0.5004,0.8516 Q0.5036,0.8516,0.515,0.8521 T0.5345,0.8546 Q0.5426,0.8556,0.5596,0.8556 L0.5649,0.8556 L0.5702,0.8556 L0.5791,0.8556 Q0.5921,0.8566,0.6135,0.8571 T0.6383,0.8586 Q0.6683,0.8615,0.6764,0.8615 L0.6776,0.8615 L0.6788,0.8615 Q0.7032,0.8615,0.7032,0.8684 Q0.7032,0.8714,0.6995,0.8749 T0.6959,0.8803 L0.6959,0.8813 Q0.6991,0.8843,0.7153,0.8843 Q0.7218,0.8843,0.7259,0.8833 L0.7299,0.8833 Q0.7324,0.8833,0.7348,0.8838 T0.7393,0.8848 T0.7433,0.8863 L0.7474,0.8882 T0.751,0.8892 Q0.7616,0.8922,0.7948,0.8952 Q0.7981,0.8952,0.8082,0.8981 T0.8297,0.9011 T0.8528,0.9026 T0.8747,0.907 T0.8856,0.9149 L0.8856,0.9159 Q0.8856,0.9179,0.8848,0.9189 T0.8828,0.9209 T0.88,0.9228 T0.8767,0.9243 T0.8731,0.9248 T0.8698,0.9253 T0.8666,0.9258 L0.8646,0.9258 Q0.8621,0.9258,0.8577,0.9263 T0.85,0.9268 T0.8439,0.9273 T0.8402,0.9278 Q0.8386,0.9298,0.8358,0.9308 T0.8305,0.9322 T0.8244,0.9332 T0.8183,0.9337 Q0.8135,0.9347,0.8025,0.9372 T0.7859,0.9416 Q0.7778,0.9436,0.7717,0.9471 T0.7624,0.9515 Q0.7575,0.9535,0.7486,0.9535 Q0.7461,0.9535,0.7397,0.953 T0.7291,0.9525 Q0.7234,0.9525,0.7186,0.953 T0.7137,0.957 T0.7194,0.9614 Q0.7567,0.9634,0.7664,0.9654 Q0.7745,0.9674,0.7762,0.9674 L0.7908,0.9674 Q0.7924,0.9664,0.7948,0.9664 Q0.8029,0.9664,0.8082,0.9679 T0.8224,0.9723 T0.8362,0.9773 Q0.8483,0.9812,0.871,0.9812 L0.8751,0.9812 Q0.8873,0.9812,0.8929,0.9822 T0.9165,0.9876 T0.9408,0.9931 Q0.9513,0.996,0.9935,0.996 Z M0.9968,0.998 L0.9976,0.003 Q0.9838,0.002,0.9708,0.0059 Q0.9659,0.0069,0.9327,0.0059 T0.8881,0.0099 Q0.8767,0.0138,0.8658,0.0163 T0.8491,0.0218 Q0.8418,0.0247,0.8341,0.0247 T0.8232,0.0257 Q0.8191,0.0277,0.7997,0.0292 T0.7737,0.0326 Q0.7697,0.0336,0.7672,0.0336 T0.7612,0.0321 T0.7543,0.0307 T0.747,0.0317 Q0.7397,0.0326,0.7336,0.0356 T0.717,0.0406 Q0.6918,0.0445,0.6861,0.0475 Q0.6845,0.0485,0.6772,0.0485 T0.6638,0.05 T0.6577,0.0549 T0.6642,0.0598 T0.6796,0.0613 T0.6902,0.0623 Q0.691,0.0623,0.6946,0.0658 T0.7064,0.0702 L0.7161,0.0702 Q0.7194,0.0702,0.7242,0.0697 T0.7332,0.0692 Q0.7413,0.0692,0.7579,0.0717 T0.781,0.0762 Q0.7867,0.0781,0.8171,0.0786 T0.85,0.0801 L0.8532,0.0801 Q0.8548,0.0801,0.8613,0.0796 T0.8727,0.0791 Q0.8832,0.0791,0.8913,0.0811 Q0.897,0.0831,0.897,0.0851 Q0.897,0.088,0.8873,0.0905 T0.8735,0.093 Q0.871,0.093,0.8447,0.0984 T0.8143,0.1039 Q0.7981,0.1039,0.781,0.1078 Q0.7778,0.1088,0.7745,0.1088 T0.7644,0.1073 T0.7518,0.1048 L0.7453,0.1048 Q0.7299,0.1048,0.7076,0.1083 T0.6788,0.1157 Q0.6748,0.1187,0.6699,0.1187 Q0.6675,0.1187,0.6557,0.1157 T0.6318,0.1118 Q0.6164,0.1108,0.6135,0.1108 T0.6095,0.1113 T0.6067,0.1142 T0.6018,0.1187 Q0.5953,0.1227,0.5921,0.1227 Q0.5904,0.1227,0.5839,0.1212 T0.5693,0.1187 L0.5661,0.1187 Q0.558,0.1187,0.5527,0.1207 T0.5434,0.1246 T0.5337,0.1266 Q0.5126,0.1296,0.4882,0.1296 T0.4371,0.1385 Q0.4298,0.1414,0.425,0.1414 Q0.4225,0.1414,0.4189,0.1395 T0.4112,0.1375 Q0.4079,0.1375,0.4019,0.1395 T0.3869,0.1414 Q0.3682,0.1414,0.3504,0.1474 Q0.3398,0.1513,0.3333,0.1513 Q0.3293,0.1513,0.3228,0.1494 L0.3204,0.1494 Q0.3179,0.1494,0.3155,0.1508 T0.3118,0.1543 T0.3106,0.1592 T0.3127,0.1642 T0.32,0.1677 T0.3305,0.1701 T0.3435,0.1716 T0.3544,0.1721 Q0.3569,0.1731,0.3601,0.1731 Q0.3625,0.1731,0.3686,0.1726 T0.3779,0.1721 Q0.3901,0.1721,0.4023,0.1751 Q0.4177,0.18,0.455,0.18 Q0.4793,0.18,0.4882,0.178 L0.4915,0.178 Q0.4964,0.178,0.5045,0.1815 T0.5158,0.185 Q0.5564,0.1889,0.5645,0.1929 Q0.5677,0.1939,0.571,0.1939 Q0.575,0.1939,0.5807,0.1919 T0.5888,0.1899 T0.5929,0.1909 Q0.5969,0.1949,0.6123,0.1949 Q0.6334,0.1949,0.6456,0.1899 Q0.6488,0.1879,0.6537,0.1879 Q0.661,0.1879,0.6772,0.1929 T0.6983,0.1978 Q0.704,0.1988,0.7206,0.1998 T0.7502,0.2018 T0.7664,0.2038 Q0.7672,0.2038,0.7818,0.2043 T0.8151,0.2067 T0.8459,0.2156 Q0.8573,0.2206,0.8573,0.2255 Q0.8573,0.2295,0.8516,0.2324 T0.8394,0.2369 T0.8281,0.2384 Q0.8208,0.2384,0.7526,0.2473 Q0.7494,0.2483,0.7421,0.2493 T0.7287,0.2512 T0.7178,0.2522 L0.7153,0.2522 L0.7137,0.2522 Q0.7056,0.2522,0.6898,0.2572 T0.6691,0.2671 Q0.6667,0.269,0.6634,0.269 Q0.661,0.269,0.6557,0.2681 T0.6472,0.2671 Q0.6448,0.2671,0.6415,0.2681 Q0.631,0.271,0.6034,0.274 Q0.5994,0.274,0.5961,0.2765 T0.5904,0.2789 L0.5864,0.2789 Q0.5831,0.2779,0.5742,0.2779 Q0.541,0.2779,0.5223,0.2849 Q0.5126,0.2888,0.5028,0.2888 Q0.5004,0.2888,0.4943,0.2883 T0.4842,0.2878 Q0.4704,0.2878,0.4627,0.2898 T0.4509,0.2987 Q0.4501,0.2997,0.4501,0.3007 Q0.4501,0.3046,0.4574,0.3066 T0.4765,0.3091 T0.4899,0.3096 Q0.4931,0.3106,0.5061,0.3121 T0.5316,0.315 T0.5499,0.3175 Q0.5507,0.3175,0.5572,0.3195 T0.5685,0.3225 T0.5803,0.3254 T0.5933,0.3279 T0.605,0.3284 T0.6148,0.3274 Q0.6221,0.3244,0.6294,0.3244 Q0.6399,0.3244,0.6496,0.3294 Q0.6529,0.3314,0.6577,0.3314 Q0.6602,0.3314,0.6663,0.3304 T0.6756,0.3294 T0.6813,0.3304 Q0.6837,0.3304,0.6873,0.3309 L0.6946,0.3318 T0.7024,0.3323 T0.7105,0.3328 T0.7178,0.3338 T0.7238,0.3348 T0.7283,0.3363 Q0.7324,0.3383,0.7482,0.3403 T0.7689,0.3442 Q0.7697,0.3442,0.779,0.3477 T0.794,0.3536 T0.8074,0.3615 T0.8183,0.3729 Q0.8232,0.3808,0.8232,0.3848 Q0.8232,0.3867,0.8216,0.3892 T0.8151,0.3961 T0.8062,0.406 T0.7997,0.4179 T0.7952,0.4263 T0.7855,0.4308 T0.7616,0.4362 Q0.7543,0.4372,0.7445,0.4372 L0.7372,0.4372 L0.7307,0.4372 Q0.7202,0.4372,0.7153,0.4382 Q0.7064,0.4402,0.691,0.4402 L0.6845,0.4402 L0.6788,0.4402 Q0.6699,0.4402,0.6533,0.4436 T0.6318,0.451 Q0.6302,0.451,0.6302,0.452 Q0.6302,0.457,0.6626,0.459 Q0.6869,0.4609,0.7015,0.4659 Q0.7064,0.4669,0.7202,0.4669 L0.7311,0.4669 L0.7429,0.4669 Q0.7599,0.4669,0.7689,0.4688 Q0.7794,0.4708,0.7794,0.4738 L0.7786,0.4748 T0.777,0.4763 T0.7749,0.4773 L0.7737,0.4777 L0.7729,0.4777 Q0.7502,0.4797,0.738,0.4797 L0.7315,0.4797 L0.7291,0.4797 Q0.7226,0.4797,0.6869,0.4876 Q0.6821,0.4886,0.6768,0.4891 T0.6614,0.4901 T0.6456,0.4916 L0.6342,0.4916 Q0.6294,0.4916,0.6217,0.4911 T0.6115,0.4906 Q0.6083,0.4906,0.6054,0.4911 T0.5961,0.4936 T0.5815,0.4965 T0.5629,0.4985 Q0.5507,0.4985,0.5442,0.4995 T0.5345,0.5025 T0.5304,0.5084 L0.5296,0.5094 Q0.5296,0.5114,0.5316,0.5124 T0.5373,0.5138 T0.5442,0.5143 L0.5523,0.5143 L0.5604,0.5143 Q0.5637,0.5153,0.5677,0.5158 T0.5742,0.5173 T0.5811,0.5188 T0.5904,0.5193 L0.5977,0.5193 L0.6026,0.5193 Q0.6107,0.5193,0.6168,0.5208 T0.6294,0.5252 T0.6399,0.5292 Q0.6448,0.5302,0.6679,0.5341 T0.6983,0.541 Q0.7129,0.545,0.7664,0.55 Q0.7883,0.5519,0.7883,0.5559 Q0.7883,0.5579,0.7843,0.5608 T0.7721,0.5638 Q0.7697,0.5638,0.764,0.5633 T0.7559,0.5628 L0.7502,0.5628 Q0.7364,0.5648,0.6869,0.5648 L0.6699,0.5648 L0.6675,0.5648 Q0.665,0.5648,0.6602,0.5658 T0.6513,0.5668 Q0.6464,0.5668,0.6399,0.5658 Q0.6342,0.5648,0.6302,0.5648 T0.6225,0.5668 T0.6156,0.5697 Q0.6107,0.5707,0.6087,0.5707 T0.6034,0.5712 T0.5953,0.5717 Q0.5904,0.5727,0.5835,0.5727 T0.5718,0.5732 T0.562,0.5757 Q0.558,0.5767,0.5483,0.5767 L0.5349,0.5767 L0.5207,0.5767 Q0.5069,0.5767,0.5028,0.5786 Q0.4972,0.5816,0.4899,0.5816 Q0.4858,0.5816,0.4781,0.5806 T0.4663,0.5796 L0.4631,0.5796 Q0.4582,0.5796,0.4432,0.5821 T0.4144,0.5846 L0.4071,0.5846 L0.4011,0.5846 T0.3958,0.5851 T0.3921,0.5856 T0.3889,0.5861 T0.3865,0.5865 T0.384,0.5875 L0.382,0.5885 L0.3796,0.5895 L0.3771,0.5905 Q0.3731,0.5925,0.365,0.5925 L0.3601,0.5925 L0.3552,0.5925 Q0.3277,0.5925,0.3049,0.6044 Q0.3017,0.6053,0.296,0.6053 Q0.292,0.6053,0.2847,0.6044 T0.2741,0.6034 Q0.2693,0.6034,0.2547,0.6029 T0.236,0.6024 Q0.2222,0.6024,0.1922,0.6073 Q0.1906,0.6083,0.1882,0.6083 Q0.1833,0.6083,0.1711,0.6063 T0.1533,0.6044 Q0.1517,0.6034,0.1492,0.6034 Q0.1436,0.6034,0.135,0.6048 T0.1168,0.6078 T0.103,0.6093 Q0.0973,0.6103,0.0787,0.6103 L0.0604,0.6103 L0.0414,0.6103 L0.0235,0.6103 Q0.0024,0.6113,0.0024,0.6172 Q0.0024,0.6192,0.0032,0.6231 Q0.0049,0.6281,0.0333,0.6281 L0.0414,0.6281 L0.0479,0.6281 Q0.0519,0.6281,0.0624,0.6301 T0.0803,0.632 L0.0835,0.632 L0.0916,0.632 Q0.0989,0.632,0.1281,0.634 L0.206,0.6419 Q0.2117,0.6429,0.2174,0.6429 Q0.2214,0.6429,0.2271,0.6424 T0.2344,0.6419 L0.2376,0.6419 Q0.2393,0.6429,0.2417,0.6429 T0.2526,0.6419 T0.2652,0.6409 Q0.2741,0.6409,0.2782,0.6429 T0.2968,0.6459 T0.3293,0.6479 T0.352,0.6489 Q0.3552,0.6499,0.3585,0.6499 Q0.365,0.6499,0.3731,0.6474 T0.3844,0.6449 Q0.3885,0.6449,0.3917,0.6474 T0.4006,0.6499 Q0.4088,0.6499,0.4509,0.6578 Q0.4574,0.6588,0.4631,0.6588 Q0.4655,0.6588,0.4716,0.6583 T0.4834,0.6578 L0.4874,0.6578 Q0.5004,0.6588,0.5255,0.6637 Q0.532,0.6647,0.56,0.6667 T0.6148,0.6701 T0.644,0.6716 Q0.6464,0.6726,0.6594,0.6736 T0.6861,0.6756 T0.7048,0.6775 Q0.7097,0.6775,0.7186,0.6825 T0.738,0.6874 Q0.7518,0.6874,0.7729,0.6958 T0.8005,0.7122 Q0.8029,0.7141,0.8029,0.7151 Q0.8029,0.7191,0.7968,0.7216 T0.7818,0.726 T0.7705,0.729 L0.764,0.731 T0.7583,0.7324 T0.753,0.7334 T0.7486,0.7349 Q0.7251,0.7399,0.7161,0.7399 Q0.7097,0.7399,0.6951,0.7409 T0.6748,0.7418 L0.6703,0.7418 L0.6667,0.7418 Q0.6659,0.7418,0.6399,0.7448 L0.6334,0.7448 Q0.631,0.7448,0.6249,0.7443 T0.6148,0.7438 L0.6099,0.7438 Q0.601,0.7448,0.5856,0.7473 T0.5645,0.7507 Q0.5572,0.7517,0.5572,0.7547 T0.5616,0.7606 T0.5702,0.7646 Q0.5734,0.7646,0.5779,0.7651 T0.5864,0.7661 T0.5949,0.7671 T0.6018,0.7676 L0.6071,0.7676 T0.6135,0.7681 T0.62,0.769 T0.6269,0.771 T0.6342,0.7725 Q0.6391,0.7725,0.6464,0.7705 T0.6541,0.7685 L0.6553,0.7685 Q0.6586,0.7685,0.6699,0.771 T0.6853,0.7735 Q0.6902,0.7735,0.704,0.777 T0.7226,0.7834 Q0.7315,0.7873,0.7445,0.7883 Q0.747,0.7883,0.7676,0.7893 T0.7972,0.7913 Q0.7989,0.7913,0.8017,0.7918 T0.8058,0.7923 T0.809,0.7928 T0.8114,0.7933 T0.8131,0.7938 T0.8147,0.7943 T0.8159,0.7953 L0.8171,0.7967 T0.8175,0.7982 Q0.8183,0.7992,0.8183,0.8002 Q0.8183,0.8022,0.8155,0.8032 T0.807,0.8051 T0.7989,0.8071 Q0.7932,0.8091,0.7737,0.8091 L0.7413,0.8091 L0.7372,0.8091 Q0.7275,0.8091,0.721,0.8101 T0.7072,0.8136 T0.6983,0.817 Q0.6942,0.818,0.6825,0.818 T0.6602,0.819 T0.6456,0.822 Q0.6415,0.8249,0.6375,0.8249 Q0.6367,0.8249,0.6326,0.8239 T0.6253,0.8229 Q0.6034,0.8229,0.5953,0.8279 Q0.5912,0.8289,0.5848,0.8289 Q0.5775,0.8289,0.5645,0.8279 Q0.5629,0.8269,0.5604,0.8269 Q0.5523,0.8269,0.5328,0.8348 Q0.5296,0.8358,0.5264,0.8358 Q0.5247,0.8358,0.5203,0.8353 T0.5126,0.8348 Q0.5061,0.8348,0.4988,0.8358 Q0.4947,0.8368,0.4882,0.8378 Q0.4826,0.8388,0.4797,0.8388 T0.474,0.8398 T0.4704,0.8422 T0.4696,0.8462 T0.472,0.8501 T0.4781,0.8521 T0.4854,0.8526 L0.4935,0.8526 T0.4996,0.8536 Q0.5036,0.8536,0.515,0.8546 T0.5337,0.8566 Q0.5426,0.8576,0.5596,0.8576 L0.5657,0.8576 L0.571,0.8576 L0.5791,0.8576 Q0.5921,0.8586,0.6135,0.8591 T0.6383,0.8605 Q0.6683,0.8635,0.6764,0.8635 Q0.6999,0.8635,0.6999,0.8684 Q0.6999,0.8694,0.6967,0.8734 T0.6934,0.8803 L0.6934,0.8823 Q0.6951,0.8863,0.7137,0.8863 Q0.7218,0.8863,0.7267,0.8853 L0.7299,0.8853 Q0.734,0.8853,0.7409,0.8882 T0.7502,0.8912 Q0.7607,0.8942,0.794,0.8971 Q0.7972,0.8971,0.8078,0.9001 T0.8297,0.9031 Q0.8475,0.9031,0.8646,0.9065 T0.8824,0.9149 L0.8824,0.9159 Q0.8824,0.9169,0.882,0.9179 T0.8808,0.9194 L0.8792,0.9204 T0.8767,0.9214 T0.8743,0.9224 T0.8715,0.9228 L0.8686,0.9228 T0.8662,0.9233 T0.8637,0.9238 L0.8556,0.9238 T0.8447,0.9243 T0.8386,0.9268 Q0.837,0.9278,0.835,0.9288 T0.8305,0.9303 T0.8244,0.9308 T0.8175,0.9318 Q0.8127,0.9327,0.8017,0.9352 T0.7851,0.9397 Q0.777,0.9416,0.7705,0.9451 T0.7616,0.9496 Q0.7575,0.9505,0.7494,0.9505 L0.7405,0.9505 L0.7307,0.9505 Q0.7234,0.9505,0.7178,0.9515 Q0.7145,0.9515,0.7125,0.9535 T0.7105,0.9575 Q0.7105,0.9624,0.7194,0.9634 Q0.7567,0.9654,0.7664,0.9674 Q0.7745,0.9693,0.7762,0.9693 L0.7908,0.9693 L0.7948,0.9693 Q0.8005,0.9693,0.8058,0.9698 T0.8143,0.9718 T0.824,0.9753 T0.8354,0.9792 Q0.8483,0.9832,0.871,0.9832 L0.8739,0.9832 L0.8767,0.9832 Q0.8873,0.9832,0.8921,0.9842 Q0.8978,0.9852,0.9157,0.9896 T0.94,0.996 Q0.9424,0.996,0.9469,0.9965 T0.9566,0.997 T0.9676,0.9975 T0.9781,0.998 L0.9874,0.998 L0.9943,0.998 L0.9968,0.998 Z",type:0}},"\uE0CC":{type:5,data:{d:"M1,0.5071 L0.8623,0.7102 L0.5865,0.7102 L0.4489,0.5071 L0.5865,0.3045 L0.8623,0.3045 Z M0.4135,0.7974 L0.2758,1 L0,1 L0,0.5944 L0.2758,0.5944 Z M0.4135,0.2026 L0.2758,0.4056 L0,0.4056 L0,0 L0.2758,0 Z",type:0}},"\uE0CD":{type:5,data:{d:"M0.9726,0.5071 L0.8451,0.3184 L0.59,0.3184 L0.4624,0.5071 L0.59,0.6955 L0.8451,0.6955 Z M1,0.5071 L0.8588,0.716 L0.5768,0.716 L0.4355,0.5071 L0.5768,0.2982 L0.8588,0.2982 Z M0.401,0.7911 L0.2735,0.6028 L0.0236,0.6028 L0.0236,0.9799 L0.2735,0.9799 Z M0.4284,0.7911 L0.2872,1 L0,1 L0,0.5826 L0.2872,0.5826 Z M0.401,0.2089 L0.2735,0.0201 L0.0236,0.0201 L0.0236,0.3972 L0.2735,0.3972 Z M0.4284,0.2089 L0.2872,0.4174 L0,0.4174 L0,0 L0.2872,0 Z",type:0}},"\uE0CE":{type:5,data:{d:"M1,0.4996 Q1,0.5365,0.9924,0.568 T0.9718,0.6177 T0.9433,0.6363 L0.9425,0.6367 Q0.9355,0.6367,0.9285,0.6326 L0.9308,0.6326 Q0.9129,0.6195,0.9013,0.587 T0.8871,0.5164 T0.8943,0.4373 T0.9254,0.3687 L0.9254,0.3691 Q0.9339,0.3628,0.9425,0.3628 Q0.9584,0.3628,0.9716,0.3811 T0.9924,0.431 T1,0.4996 Z M0.9234,0.6321 L0.8776,0.7169 Q0.8791,0.732,0.8791,0.7441 Q0.8791,0.781,0.8716,0.8123 T0.8508,0.862 T0.8224,0.8809 L0.8216,0.8809 Q0.8154,0.8809,0.8092,0.8775 L0.8088,0.8775 Q0.7905,0.8633,0.7792,0.8297 T0.766,0.7571 T0.775,0.6768 T0.8088,0.6086 L0.7431,0.6065 Q0.719,0.6313,0.7072,0.6705 T0.6965,0.7487 T0.7097,0.823 T0.7423,0.8763 L0.7913,0.8775 L0.7314,0.9895 L0.4648,0.5038 L0.7299,0.0176 L0.7882,0.1229 L0.7404,0.1212 Q0.7244,0.138,0.7136,0.1617 T0.6982,0.2108 T0.6943,0.2632 T0.7003,0.3144 T0.7159,0.3586 T0.7396,0.3909 L0.8072,0.3926 Q0.7894,0.3796,0.7777,0.3479 T0.7635,0.2789 T0.7697,0.2011 T0.7987,0.1326 Q0.8092,0.1225,0.8204,0.1225 Q0.8321,0.1225,0.8426,0.1332 T0.8609,0.1623 T0.8731,0.2062 T0.8776,0.2592 Q0.8776,0.268,0.8768,0.2815 L0.9219,0.3628 L0.8644,0.3612 Q0.8403,0.3855,0.8284,0.4247 T0.8177,0.5029 T0.8309,0.5772 T0.8632,0.6305 Z M0.6759,0.6384 Q0.6576,0.625,0.6457,0.5914 T0.6319,0.5185 T0.6407,0.4375 T0.6751,0.3691 L0.609,0.367 Q0.5849,0.3918,0.5731,0.431 T0.5626,0.509 T0.5758,0.5833 T0.6082,0.6367 Z M0.6459,0.0025 L0.391,0.4551 L0.0035,0.4551 L0.246,0 Z M0.6553,1 L0.2421,1 L0,0.5449 L0.3875,0.5449 Z",type:0}},"\uE0CF":{type:5,data:{d:"M0.4996,0 Q0.5562,0,0.5961,0.0166 T0.6364,0.0566 L0.6368,0.0575 Q0.6368,0.0646,0.6324,0.0713 L0.6324,0.0692 Q0.6196,0.0868,0.5872,0.0986 T0.5166,0.1128 T0.4373,0.1057 T0.3689,0.0747 L0.3694,0.0747 Q0.3627,0.0663,0.3627,0.0575 Q0.3627,0.0336,0.4028,0.0168 T0.4996,0 Z M0.6324,0.0768 L0.717,0.1225 Q0.7321,0.1212,0.744,0.1212 Q0.8007,0.1212,0.8406,0.1378 T0.8809,0.1779 L0.8813,0.1783 Q0.8813,0.1846,0.8778,0.1909 Q0.8636,0.2093,0.8299,0.2208 T0.7573,0.2343 T0.6769,0.2253 T0.6089,0.1913 L0.6067,0.2567 Q0.6315,0.281,0.6707,0.2928 T0.7489,0.3035 T0.8231,0.2903 T0.8764,0.258 L0.8778,0.2085 L0.9898,0.2685 L0.504,0.5352 L0.0177,0.2701 L0.1227,0.2118 L0.1213,0.2596 Q0.1457,0.2836,0.1851,0.2955 T0.2635,0.3062 T0.3377,0.293 T0.3911,0.2605 L0.3928,0.193 Q0.38,0.2106,0.3483,0.2221 T0.2792,0.2364 T0.2013,0.2303 T0.1324,0.2013 Q0.1222,0.1909,0.1222,0.1795 Q0.1222,0.156,0.1623,0.1393 T0.2591,0.1225 Q0.2679,0.1225,0.2817,0.1233 L0.3627,0.078 L0.3609,0.1359 Q0.3857,0.1598,0.4249,0.1718 T0.5031,0.1825 T0.5773,0.1693 T0.6306,0.1367 Z M0.6386,0.3242 Q0.6253,0.3427,0.5917,0.3544 T0.5186,0.3683 T0.4376,0.3595 T0.3689,0.3251 L0.3671,0.3909 Q0.3915,0.4148,0.4309,0.4268 T0.5093,0.4375 T0.5835,0.4243 T0.6368,0.3918 Z M0.0027,0.354 L0.4553,0.6091 L0.4553,0.9962 L0,0.7542 Z M1,0.3448 L1,0.7576 L0.5447,1 L0.5447,0.6128 Z",type:0}},"\uE0D0":{type:5,data:{d:"M0,0.7699 Q0,0.7172,0.0277,0.6731 T0.103,0.6032 T0.2066,0.5774 Q0.2487,0.5774,0.2865,0.5922 T0.3526,0.6339 L0.3935,0.6715 Q0.4218,0.6984,0.438,0.7336 T0.4541,0.8075 Q0.4541,0.8602,0.4264,0.9046 T0.3511,0.9745 T0.2475,1 Q0.2054,1,0.1676,0.9852 T0.1016,0.9435 L0.0606,0.9059 Q0.0317,0.879,0.0159,0.8438 T0,0.7699 Z M0.5459,0.7699 Q0.5459,0.7172,0.5736,0.6731 T0.6489,0.6032 T0.7525,0.5774 Q0.7946,0.5774,0.8324,0.5922 T0.8984,0.6339 L0.9394,0.6715 Q0.9579,0.6892,0.9714,0.7105 T0.9925,0.7567 T1,0.8075 Q1,0.8602,0.9723,0.9046 T0.897,0.9745 T0.7934,1 Q0.7513,1,0.7135,0.9852 T0.6474,0.9435 L0.6065,0.9059 Q0.5776,0.879,0.5617,0.8438 T0.5459,0.7699 Z M0,0.1925 Q0,0.1398,0.0277,0.0957 T0.103,0.0258 T0.2066,0 Q0.2487,0,0.2865,0.0148 T0.3526,0.0565 L0.3935,0.0941 Q0.4218,0.121,0.438,0.1562 T0.4541,0.2301 Q0.4541,0.2828,0.4264,0.3269 T0.3511,0.3968 T0.2475,0.4226 Q0.2054,0.4226,0.1676,0.4078 T0.1016,0.3661 L0.0606,0.3285 Q0.0317,0.3016,0.0159,0.2664 T0,0.1925 Z M0.5459,0.1925 Q0.5459,0.1398,0.5736,0.0957 T0.6489,0.0258 T0.7525,0 Q0.7946,0,0.8324,0.0148 T0.8984,0.0565 L0.9394,0.0941 Q0.9677,0.121,0.9838,0.1562 T1,0.2301 Q1,0.2828,0.9723,0.3269 T0.897,0.3968 T0.7934,0.4226 Q0.7513,0.4226,0.7135,0.4078 T0.6474,0.3661 L0.6065,0.3285 Q0.5874,0.3108,0.5741,0.2895 T0.5534,0.2433 T0.5459,0.1925 Z M0.3301,0.6403 Q0.3306,0.6403,0.3312,0.6409 L0.3272,0.6371 L0.3272,0.6371 Z M0.8759,0.6403 Q0.8765,0.6403,0.8771,0.6409 Q0.8748,0.6387,0.8725,0.6371 L0.8731,0.6371 Z M0.3301,0.0629 Q0.3306,0.0629,0.3312,0.0634 L0.3272,0.0597 L0.3272,0.0602 Z M0.8759,0.0629 Q0.8765,0.0629,0.8771,0.0634 Q0.8748,0.0613,0.8725,0.0597 Q0.8731,0.0597,0.8731,0.0602 Z M0.779,0.4113 L0.7501,0.3849 Q0.7046,0.3844,0.6636,0.3661 Q0.7132,0.407,0.779,0.4113 Z M0.7957,0.4118 Q0.8477,0.4113,0.8918,0.3874 T0.9619,0.3223 T0.9885,0.2323 L0.9585,0.2048 Q0.9538,0.2769,0.8987,0.3282 T0.7657,0.3844 Z M0.9879,0.2167 Q0.9827,0.1554,0.9388,0.1097 Q0.9585,0.1478,0.959,0.1898 Z M0.2331,0.4113 L0.2043,0.3849 Q0.1587,0.3844,0.1177,0.3661 Q0.1673,0.407,0.2331,0.4113 Z M0.2499,0.4118 Q0.2885,0.4118,0.324,0.3973 T0.3852,0.3589 T0.4264,0.3019 T0.4426,0.2323 L0.4126,0.2048 Q0.408,0.2769,0.3529,0.3282 T0.2198,0.3844 Z M0.442,0.2167 Q0.4368,0.1554,0.393,0.1097 Q0.4126,0.1478,0.4132,0.1903 Z M0.779,0.9887 L0.7501,0.9624 Q0.7046,0.9618,0.6636,0.9435 Q0.7132,0.9844,0.779,0.9887 Z M0.7957,0.9892 Q0.8477,0.9887,0.8918,0.9648 T0.9619,0.8997 T0.9885,0.8097 L0.9585,0.7823 Q0.9538,0.8543,0.8987,0.9056 T0.7657,0.9618 Z M0.9879,0.7941 Q0.9827,0.7328,0.9388,0.6871 Q0.9585,0.7253,0.959,0.7672 Z M0.2331,0.9887 L0.2043,0.9624 Q0.1587,0.9618,0.1177,0.9435 Q0.1673,0.9844,0.2331,0.9887 Z M0.2499,0.9892 Q0.3018,0.9887,0.3459,0.9648 T0.416,0.8997 T0.4426,0.8102 L0.4126,0.7823 Q0.408,0.8543,0.3529,0.9056 T0.2198,0.9618 Z M0.442,0.7941 Q0.4368,0.7328,0.393,0.6871 Q0.4126,0.7253,0.4132,0.7672 Z M0.3191,0.6452 Q0.2943,0.622,0.2643,0.6091 T0.2066,0.5962 Q0.1795,0.5962,0.1497,0.6091 T0.0949,0.6441 T0.0537,0.6995 T0.0375,0.7699 Q0.0375,0.8419,0.0941,0.8941 Q0.1189,0.9172,0.1489,0.9304 T0.2066,0.9435 Q0.2337,0.9435,0.2634,0.9304 T0.3182,0.8952 T0.3595,0.8398 T0.3756,0.7699 Q0.3756,0.6978,0.3191,0.6452 Z M0.026,0.7731 L0.026,0.7699 Q0.026,0.6935,0.0854,0.6376 Q0.0658,0.6538,0.0514,0.6739 T0.0289,0.718 T0.0202,0.7677 Z M0.0271,0.7892 L0.0208,0.7833 Q0.0254,0.8419,0.0681,0.886 Q0.0664,0.8828,0.0646,0.879 Q0.0329,0.8382,0.0271,0.7892 Z M0.3866,0.7823 Q0.382,0.8511,0.3278,0.9016 Q0.3843,0.8565,0.3918,0.7871 Z M0.3872,0.7677 L0.393,0.7726 L0.393,0.7699 Q0.393,0.7349,0.3791,0.7038 Q0.3739,0.6973,0.3681,0.6914 Q0.3866,0.7274,0.3872,0.7677 Z M0.865,0.6452 Q0.8402,0.622,0.8102,0.6091 T0.7525,0.5962 Q0.7305,0.5962,0.7072,0.6043 T0.6619,0.628 T0.6223,0.6642 T0.5941,0.7124 T0.5834,0.7699 Q0.5834,0.8419,0.6399,0.8941 Q0.6561,0.9097,0.6757,0.9207 T0.7149,0.9376 T0.7525,0.9435 Q0.7796,0.9435,0.8093,0.9304 T0.8641,0.8952 T0.9054,0.8398 T0.9215,0.7699 Q0.9215,0.6978,0.865,0.6452 Z M0.5718,0.7731 L0.5718,0.7699 Q0.5718,0.6935,0.6313,0.6376 Q0.6013,0.6618,0.584,0.6954 T0.5661,0.7677 Z M0.573,0.7892 L0.5666,0.7833 Q0.5713,0.8419,0.614,0.886 Q0.6122,0.8828,0.6105,0.879 Q0.5788,0.8382,0.573,0.7892 Z M0.9325,0.7823 Q0.9279,0.8511,0.8736,0.9016 Q0.9308,0.8565,0.9377,0.7871 Z M0.9331,0.7672 L0.9388,0.7726 L0.9388,0.7699 Q0.9388,0.7349,0.925,0.7038 Q0.9198,0.6973,0.914,0.6914 Q0.9325,0.7274,0.9331,0.7672 Z M0.3191,0.0677 Q0.2943,0.0446,0.2643,0.0317 T0.2066,0.0188 Q0.1795,0.0188,0.1497,0.0317 T0.0949,0.0667 T0.0537,0.122 T0.0375,0.1925 Q0.0375,0.2645,0.0941,0.3167 Q0.1189,0.3398,0.1489,0.353 T0.2066,0.3661 Q0.2337,0.3661,0.2634,0.353 T0.3182,0.3177 T0.3595,0.2624 T0.3756,0.1925 Q0.3756,0.1204,0.3191,0.0677 Z M0.026,0.1957 L0.026,0.1925 Q0.026,0.1161,0.0854,0.0602 Q0.0554,0.0844,0.0381,0.118 T0.0202,0.1903 Z M0.0271,0.2118 L0.0208,0.2059 Q0.0254,0.2645,0.0681,0.3086 Q0.0664,0.3054,0.0646,0.3016 Q0.0329,0.2608,0.0271,0.2118 Z M0.3866,0.2048 Q0.382,0.2737,0.3278,0.3242 Q0.3849,0.279,0.3918,0.2097 Z M0.3872,0.1903 L0.393,0.1952 L0.393,0.1925 Q0.393,0.1581,0.3791,0.1263 Q0.3739,0.1199,0.3681,0.114 Q0.3866,0.15,0.3872,0.1903 Z M0.865,0.0677 Q0.8402,0.0446,0.8102,0.0317 T0.7525,0.0188 Q0.7253,0.0188,0.6956,0.0317 T0.6408,0.0667 T0.5995,0.122 T0.5834,0.1925 Q0.5834,0.2645,0.6399,0.3167 Q0.6647,0.3398,0.6947,0.353 T0.7525,0.3661 Q0.7796,0.3661,0.8093,0.353 T0.8641,0.3177 T0.9054,0.2624 T0.9215,0.1925 Q0.9215,0.1204,0.865,0.0677 Z M0.5718,0.1957 L0.5718,0.1925 Q0.5718,0.1161,0.6313,0.0602 Q0.6013,0.0844,0.584,0.118 T0.5661,0.1903 Z M0.573,0.2118 L0.5666,0.2059 Q0.5713,0.2645,0.614,0.3086 Q0.6122,0.3054,0.6105,0.3016 Q0.5788,0.2608,0.573,0.2118 Z M0.9325,0.2048 Q0.9279,0.2737,0.8736,0.3242 Q0.9308,0.279,0.9377,0.2097 Z M0.9331,0.1898 L0.9388,0.1952 L0.9388,0.1925 Q0.9388,0.1575,0.925,0.1263 Q0.9198,0.1199,0.914,0.114 Q0.9325,0.15,0.9331,0.1898 Z",type:0}},"\uE0D1":{type:5,data:{d:"M0.7639,0.1974 L0.9593,0.1974 L0.9593,0.1443 L0.7639,0.1443 L0.7639,0.1974 Z M0.7586,0.109 L0.9727,0.109 Q0.9839,0.109,0.992,0.1153 T1,0.1291 L1,0.382 Q1,0.3906,0.9912,0.3964 T0.9727,0.4021 L0.7586,0.4021 Q0.7468,0.4021,0.739,0.3956 T0.7313,0.382 L0.7313,0.1291 Q0.7313,0.1246,0.7339,0.1207 T0.7406,0.1143 T0.7495,0.1104 T0.7586,0.109 Z M0.7639,0.6575 L0.9593,0.6575 L0.9593,0.6044 L0.7639,0.6044 L0.7639,0.6575 Z M0.7313,0.8347 L0.7313,0.5843 Q0.7313,0.5757,0.7401,0.5699 T0.7586,0.5641 L0.9706,0.5641 Q0.9818,0.5641,0.9898,0.5705 T0.9979,0.5843 L0.9979,0.8347 Q0.9979,0.8433,0.989,0.8491 T0.9706,0.8549 L0.7586,0.8549 Q0.7468,0.8549,0.739,0.8483 T0.7313,0.8347 Z M0,1 L0,0 L0.7607,0 L0.7607,1 L0,1 Z",type:0}},"\uE0D2":{type:5,data:{d:"M0.9764,0.0029 L0.3179,0.4552 L0,0.4552 L0,0 Z M1,1 L0,1 L0,0.5448 L0.3091,0.5448 Z",type:0}},"\uE0D4":{type:5,data:{d:"M0.0236,0.0029 L0.6824,0.4552 L1,0.4552 L1,0 Z M0,1 L1,1 L1,0.5448 L0.6912,0.5448 Z",type:0}},"\uEE00":{type:3,data:"M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z"},"\uEE01":{type:3,data:"M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z"},"\uEE02":{type:3,data:"M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z"},"\uEE03":{type:3,data:"M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z M.25,.15 L1,.15 L1,.85 L.25,.85 Z"},"\uEE04":{type:3,data:"M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z M0,.15 L1,.15 L1,.85 L0,.85 Z"},"\uEE05":{type:3,data:"M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z M0,.15 L.75,.15 L.75,.85 L0,.85 Z"},"\uEE06":{type:5,data:{d:"M0.6574,0.303 Q0.623,0.291,0.5852,0.285 T0.5082,0.279 T0.4311,0.285 T0.359,0.303 L0.3082,0.248 Q0.3525,0.232,0.4041,0.2235 T0.5082,0.215 T0.6123,0.2235 T0.7082,0.248 Z",type:0},scaleType:1},"\uEE07":{type:5,data:{d:"M0.8557,0.582 L0.7656,0.551 Q0.7852,0.53,0.7951,0.507 T0.8049,0.46 Q0.8049,0.424,0.782,0.3905 T0.718,0.332 T0.6221,0.293 T0.5082,0.279 L0.5082,0.215 Q0.5607,0.215,0.6123,0.2235 T0.709,0.248 T0.7918,0.287 T0.8557,0.3375 T0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.582 Z",type:0},scaleType:1},"\uEE08":{type:5,data:{d:"M0.5082,0.705 Q0.482,0.705,0.4557,0.703 T0.4049,0.697 L0.4311,0.635 Q0.4508,0.638,0.4697,0.6395 T0.5082,0.641 Q0.5672,0.641,0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 Q0.8049,0.436,0.7951,0.413 T0.7656,0.369 L0.8557,0.338 Q0.882,0.365,0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z",type:0},scaleType:1},"\uEE09":{type:5,data:{d:"M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 L0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 T0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 L0.9098,0.46 Q0.9098,0.492,0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z",type:0},scaleType:1},"\uEE0A":{type:5,data:{d:"M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.338 L0.2508,0.369 Q0.2311,0.39,0.2213,0.413 T0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 Q0.5279,0.641,0.5467,0.6395 T0.5852,0.635 L0.6115,0.697 Q0.5869,0.701,0.5607,0.703 T0.5082,0.705 Z",type:0},scaleType:1},"\uEE0B":{type:5,data:{d:"M0.1607,0.582 Q0.1344,0.555,0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.3375 T0.2246,0.287 T0.3074,0.248 T0.4041,0.2235 T0.5082,0.215 L0.5082,0.279 Q0.4492,0.279,0.3943,0.293 T0.2984,0.332 T0.2344,0.3905 T0.2115,0.46 Q0.2115,0.484,0.2213,0.507 T0.2508,0.551 Z",type:0},scaleType:1},"\uF5D0":l.LINE_H,"\uF5D1":l.LINE_V,"\uF5D2":l.FADE_RIGHT,"\uF5D3":l.FADE_LEFT,"\uF5D4":l.FADE_DOWN,"\uF5D5":l.FADE_UP,"\uF5D6":l.CURVE_DOWN_RIGHT,"\uF5D7":l.CURVE_DOWN_LEFT,"\uF5D8":l.CURVE_UP_RIGHT,"\uF5D9":l.CURVE_UP_LEFT,"\uF5DA":[l.LINE_V,l.CURVE_UP_RIGHT],"\uF5DB":[l.LINE_V,l.CURVE_DOWN_RIGHT],"\uF5DC":[l.CURVE_DOWN_RIGHT,l.CURVE_UP_RIGHT],"\uF5DD":[l.LINE_V,l.CURVE_UP_LEFT],"\uF5DE":[l.LINE_V,l.CURVE_DOWN_LEFT],"\uF5DF":[l.CURVE_DOWN_LEFT,l.CURVE_UP_LEFT],"\uF5E0":[l.LINE_H,l.CURVE_DOWN_LEFT],"\uF5E1":[l.LINE_H,l.CURVE_DOWN_RIGHT],"\uF5E2":[l.CURVE_DOWN_LEFT,l.CURVE_DOWN_RIGHT],"\uF5E3":[l.LINE_H,l.CURVE_UP_LEFT],"\uF5E4":[l.LINE_H,l.CURVE_UP_RIGHT],"\uF5E5":[l.CURVE_UP_LEFT,l.CURVE_UP_RIGHT],"\uF5E6":[l.LINE_V,l.CURVE_UP_LEFT,l.CURVE_UP_RIGHT],"\uF5E7":[l.LINE_V,l.CURVE_DOWN_LEFT,l.CURVE_DOWN_RIGHT],"\uF5E8":[l.LINE_H,l.CURVE_DOWN_LEFT,l.CURVE_UP_LEFT],"\uF5E9":[l.LINE_H,l.CURVE_DOWN_RIGHT,l.CURVE_UP_RIGHT],"\uF5EA":[l.LINE_V,l.CURVE_DOWN_RIGHT,l.CURVE_UP_LEFT],"\uF5EB":[l.LINE_V,l.CURVE_DOWN_LEFT,l.CURVE_UP_RIGHT],"\uF5EC":[l.LINE_H,l.CURVE_DOWN_RIGHT,l.CURVE_UP_LEFT],"\uF5ED":[l.LINE_H,l.CURVE_DOWN_LEFT,l.CURVE_UP_RIGHT],"\uF5EE":[l.NODE_FILL,l.NODE_STROKE],"\uF5EF":l.NODE_STROKE,"\uF5F0":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_RIGHT],"\uF5F1":[l.NODE_STROKE,l.NODE_LINE_RIGHT],"\uF5F2":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_LEFT],"\uF5F3":[l.NODE_STROKE,l.NODE_LINE_LEFT],"\uF5F4":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF5F5":[l.NODE_STROKE,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF5F6":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN],"\uF5F7":[l.NODE_STROKE,l.NODE_LINE_DOWN],"\uF5F8":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP],"\uF5F9":[l.NODE_STROKE,l.NODE_LINE_UP],"\uF5FA":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_UP],"\uF5FB":[l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_UP],"\uF5FC":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_RIGHT],"\uF5FD":[l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_RIGHT],"\uF5FE":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT],"\uF5FF":[l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT],"\uF600":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_RIGHT],"\uF601":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_RIGHT],"\uF602":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_LEFT],"\uF603":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_LEFT],"\uF604":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_RIGHT],"\uF605":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_RIGHT],"\uF606":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT],"\uF607":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT],"\uF608":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF609":[l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF60A":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF60B":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF60C":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF60D":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\u{1FB00}":f(1),"\u{1FB01}":f(2),"\u{1FB02}":f(3),"\u{1FB03}":f(4),"\u{1FB04}":f(5),"\u{1FB05}":f(6),"\u{1FB06}":f(7),"\u{1FB07}":f(8),"\u{1FB08}":f(9),"\u{1FB09}":f(10),"\u{1FB0A}":f(11),"\u{1FB0B}":f(12),"\u{1FB0C}":f(13),"\u{1FB0D}":f(14),"\u{1FB0E}":f(15),"\u{1FB0F}":f(16),"\u{1FB10}":f(17),"\u{1FB11}":f(18),"\u{1FB12}":f(19),"\u{1FB13}":f(20),"\u{1FB14}":f(22),"\u{1FB15}":f(23),"\u{1FB16}":f(24),"\u{1FB17}":f(25),"\u{1FB18}":f(26),"\u{1FB19}":f(27),"\u{1FB1A}":f(28),"\u{1FB1B}":f(29),"\u{1FB1C}":f(30),"\u{1FB1D}":f(31),"\u{1FB1E}":f(32),"\u{1FB1F}":f(33),"\u{1FB20}":f(34),"\u{1FB21}":f(35),"\u{1FB22}":f(36),"\u{1FB23}":f(37),"\u{1FB24}":f(38),"\u{1FB25}":f(39),"\u{1FB26}":f(40),"\u{1FB27}":f(41),"\u{1FB28}":f(43),"\u{1FB29}":f(44),"\u{1FB2A}":f(45),"\u{1FB2B}":f(46),"\u{1FB2C}":f(47),"\u{1FB2D}":f(48),"\u{1FB2E}":f(49),"\u{1FB2F}":f(50),"\u{1FB30}":f(51),"\u{1FB31}":f(52),"\u{1FB32}":f(53),"\u{1FB33}":f(54),"\u{1FB34}":f(55),"\u{1FB35}":f(56),"\u{1FB36}":f(57),"\u{1FB37}":f(58),"\u{1FB38}":f(59),"\u{1FB39}":f(60),"\u{1FB3A}":f(61),"\u{1FB3B}":f(62),"\u{1FB3C}":{type:3,data:"M0,0.6667 L0,1 L0.5,1 Z"},"\u{1FB3D}":{type:3,data:"M0,0.6667 L0,1 L1,1 Z"},"\u{1FB3E}":{type:3,data:"M0,0.3333 L0,1 L0.5,1 Z"},"\u{1FB3F}":{type:3,data:"M0,0.3333 L0,1 L1,1 Z"},"\u{1FB40}":{type:3,data:"M0,0 L0,1 L0.5,1 Z"},"\u{1FB41}":{type:3,data:"M0,0.3333 L0.5,0 L1,0 L1,1 L0,1 Z"},"\u{1FB42}":{type:3,data:"M0,0.3333 L1,0 L1,1 L0,1 Z"},"\u{1FB43}":{type:3,data:"M0,0.6667 L0.5,0 L1,0 L1,1 L0,1 Z"},"\u{1FB44}":{type:3,data:"M0,0.6667 L1,0 L1,1 L0,1 Z"},"\u{1FB45}":{type:3,data:"M0,1 L0.5,0 L1,0 L1,1 Z"},"\u{1FB46}":{type:3,data:"M0,0.6667 L1,0.3333 L1,1 L0,1 Z"},"\u{1FB47}":{type:3,data:"M0.5,1 L1,0.6667 L1,1 Z"},"\u{1FB48}":{type:3,data:"M0,1 L1,0.6667 L1,1 Z"},"\u{1FB49}":{type:3,data:"M0.5,1 L1,0.3333 L1,1 Z"},"\u{1FB4A}":{type:3,data:"M0,1 L1,0.3333 L1,1 Z"},"\u{1FB4B}":{type:3,data:"M0.5,1 L1,0 L1,1 Z"},"\u{1FB4C}":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 L1,0.3333 Z"},"\u{1FB4D}":{type:3,data:"M0,0 L0,1 L1,1 L1,0.3333 Z"},"\u{1FB4E}":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 L1,0.6667 Z"},"\u{1FB4F}":{type:3,data:"M0,0 L0,1 L1,1 L1,0.6667 Z"},"\u{1FB50}":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 Z"},"\u{1FB51}":{type:3,data:"M0,0.3333 L0,1 L1,1 L1,0.6667 Z"},"\u{1FB52}":{type:3,data:"M0,0.6667 L0.5,1 L1,1 L1,0 L0,0 Z"},"\u{1FB53}":{type:3,data:"M0,0.6667 L1,1 L1,0 L0,0 Z"},"\u{1FB54}":{type:3,data:"M0,0.3333 L0.5,1 L1,1 L1,0 L0,0 Z"},"\u{1FB55}":{type:3,data:"M0,0.3333 L1,1 L1,0 L0,0 Z"},"\u{1FB56}":{type:3,data:"M0,0 L0.5,1 L1,1 L1,0 Z"},"\u{1FB57}":{type:3,data:"M0,0.3333 L0,0 L0.5,0 Z"},"\u{1FB58}":{type:3,data:"M0,0.3333 L0,0 L1,0 Z"},"\u{1FB59}":{type:3,data:"M0,0.6667 L0,0 L0.5,0 Z"},"\u{1FB5A}":{type:3,data:"M0,0.6667 L0,0 L1,0 Z"},"\u{1FB5B}":{type:3,data:"M0,1 L0,0 L0.5,0 Z"},"\u{1FB5C}":{type:3,data:"M0,0.6667 L0,0 L1,0 L1,0.3333 Z"},"\u{1FB5D}":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 L1,0.6667 Z"},"\u{1FB5E}":{type:3,data:"M0,1 L0,0 L1,0 L1,0.6667 Z"},"\u{1FB5F}":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 L1,0.3333 Z"},"\u{1FB60}":{type:3,data:"M0,1 L0,0 L1,0 L1,0.3333 Z"},"\u{1FB61}":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 Z"},"\u{1FB62}":{type:3,data:"M0.5,0 L1,0 L1,0.3333 Z"},"\u{1FB63}":{type:3,data:"M0,0 L1,0 L1,0.3333 Z"},"\u{1FB64}":{type:3,data:"M0.5,0 L1,0 L1,0.6667 Z"},"\u{1FB65}":{type:3,data:"M0,0 L1,0 L1,0.6667 Z"},"\u{1FB66}":{type:3,data:"M0.5,0 L1,0 L1,1 Z"},"\u{1FB67}":{type:3,data:"M0,0.3333 L1,0.6667 L1,0 L0,0 Z"},"\u{1FB68}":{type:3,data:"M0,0 L1,0 L1,1 L0,1 L0.5,0.5 Z"},"\u{1FB69}":{type:3,data:"M0,0 L0.5,0.5 L1,0 L1,1 L0,1 Z"},"\u{1FB6A}":{type:3,data:"M0,0 L1,0 L0.5,0.5 L1,1 L0,1 Z"},"\u{1FB6B}":{type:3,data:"M0,0 L1,0 L1,1 L0.5,0.5 L0,1 Z"},"\u{1FB6C}":{type:3,data:"M0,0 L0.5,0.5 L0,1 Z"},"\u{1FB6D}":{type:3,data:"M0,0 L1,0 L0.5,0.5 Z"},"\u{1FB6E}":{type:3,data:"M1,0 L1,1 L0.5,0.5 Z"},"\u{1FB6F}":{type:3,data:"M0,1 L1,1 L0.5,0.5 Z"},"\u{1FB70}":{type:0,data:[{x:1,y:0,w:1,h:8}]},"\u{1FB71}":{type:0,data:[{x:2,y:0,w:1,h:8}]},"\u{1FB72}":{type:0,data:[{x:3,y:0,w:1,h:8}]},"\u{1FB73}":{type:0,data:[{x:4,y:0,w:1,h:8}]},"\u{1FB74}":{type:0,data:[{x:5,y:0,w:1,h:8}]},"\u{1FB75}":{type:0,data:[{x:6,y:0,w:1,h:8}]},"\u{1FB76}":{type:0,data:[{x:0,y:1,w:8,h:1}]},"\u{1FB77}":{type:0,data:[{x:0,y:2,w:8,h:1}]},"\u{1FB78}":{type:0,data:[{x:0,y:3,w:8,h:1}]},"\u{1FB79}":{type:0,data:[{x:0,y:4,w:8,h:1}]},"\u{1FB7A}":{type:0,data:[{x:0,y:5,w:8,h:1}]},"\u{1FB7B}":{type:0,data:[{x:0,y:6,w:8,h:1}]},"\u{1FB7C}":{type:0,data:[{x:0,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}]},"\u{1FB7D}":{type:0,data:[{x:0,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}]},"\u{1FB7E}":{type:0,data:[{x:7,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}]},"\u{1FB7F}":{type:0,data:[{x:7,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}]},"\u{1FB80}":{type:0,data:[{x:0,y:0,w:8,h:1},{x:0,y:7,w:8,h:1}]},"\u{1FB81}":{type:0,data:[{x:0,y:0,w:8,h:1},{x:0,y:2,w:8,h:1},{x:0,y:4,w:8,h:1},{x:0,y:7,w:8,h:1}]},"\u{1FB82}":{type:0,data:[{x:0,y:0,w:8,h:2}]},"\u{1FB83}":{type:0,data:[{x:0,y:0,w:8,h:3}]},"\u{1FB84}":{type:0,data:[{x:0,y:0,w:8,h:5}]},"\u{1FB85}":{type:0,data:[{x:0,y:0,w:8,h:6}]},"\u{1FB86}":{type:0,data:[{x:0,y:0,w:8,h:7}]},"\u{1FB87}":{type:0,data:[{x:6,y:0,w:2,h:8}]},"\u{1FB88}":{type:0,data:[{x:5,y:0,w:3,h:8}]},"\u{1FB89}":{type:0,data:[{x:3,y:0,w:5,h:8}]},"\u{1FB8A}":{type:0,data:[{x:2,y:0,w:6,h:8}]},"\u{1FB8B}":{type:0,data:[{x:1,y:0,w:7,h:8}]},"\u{1FB8C}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L0.5,0 L0.5,1 L0,1 Z"},"\u{1FB8D}":{type:1,data:[[1,0],[0,1]],clipPath:"M0.5,0 L1,0 L1,1 L0.5,1 Z"},"\u{1FB8E}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L1,0.5 L0,0.5 Z"},"\u{1FB8F}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0.5 L1,0.5 L1,1 L0,1 Z"},"\u{1FB90}":{type:1,data:[[0,1],[1,0]]},"\u{1FB91}":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0.5 L1,0.5 L1,1 L0,1 Z"},{type:0,data:[{x:0,y:0,w:8,h:4}]}],"\u{1FB92}":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0 L1,0 L1,0.5 L0,0.5 Z"},{type:0,data:[{x:0,y:4,w:8,h:4}]}],"\u{1FB94}":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0 L0.5,0 L0.5,1 L0,1 Z"},{type:0,data:[{x:4,y:0,w:4,h:8}]}],"\u{1FB95}":{type:0,data:[{x:0,y:0,w:2,h:2},{x:4,y:0,w:2,h:2},{x:2,y:2,w:2,h:2},{x:6,y:2,w:2,h:2},{x:0,y:4,w:2,h:2},{x:4,y:4,w:2,h:2},{x:2,y:6,w:2,h:2},{x:6,y:6,w:2,h:2}]},"\u{1FB96}":{type:0,data:[{x:2,y:0,w:2,h:2},{x:6,y:0,w:2,h:2},{x:0,y:2,w:2,h:2},{x:4,y:2,w:2,h:2},{x:2,y:4,w:2,h:2},{x:6,y:4,w:2,h:2},{x:0,y:6,w:2,h:2},{x:4,y:6,w:2,h:2}]},"\u{1FB97}":{type:0,data:[{x:0,y:2,w:8,h:2},{x:0,y:6,w:8,h:2}]},"\u{1FB98}":{type:2,data:"M-0.25,-0.25 L1.25,1.25 M-0.25,0 L1,1.25 M-0.25,0.25 L0.75,1.25 M-0.25,0.5 L0.5,1.25 M0,-0.25 L1.25,1 M0.25,-0.25 L1.25,0.75 M0.5,-0.25 L1.25,0.5 M-0.25,0.75 L0.25,1.25 M0.75,-0.25 L1.25,0.25",strokeWidth:1},"\u{1FB99}":{type:2,data:"M-0.25,0.5 L0.5,-0.25 M-0.25,0.75 L0.75,-0.25 M-0.25,1 L1,-0.25 M-0.25,1.25 L1.25,-0.25 M0,1.25 L1.25,0 M0.25,1.25 L1.25,0.25 M0.5,1.25 L1.25,0.5 M-0.25,0.25 L0.25,-0.25 M0.75,1.25 L1.25,0.75",strokeWidth:1},"\u{1FB9A}":{type:5,data:{d:"M0,0 L.5,.5 L0,1 L1,1 L.5,.5 L1,0",type:0}},"\u{1FB9B}":{type:5,data:{d:"M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1",type:0}},"\u{1FB9C}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L0,1 Z"},"\u{1FB9D}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L1,1 Z"},"\u{1FB9E}":{type:1,data:[[1,0],[0,1]],clipPath:"M1,0 L1,1 L0,1 Z"},"\u{1FB9F}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,1 L0,1 Z"},"\u{1FBA0}":{type:2,data:"M.5,0 L0,.5",strokeWidth:1},"\u{1FBA1}":{type:2,data:"M.5,0 L1,.5",strokeWidth:1},"\u{1FBA2}":{type:2,data:"M0,.5 L.5,1",strokeWidth:1},"\u{1FBA3}":{type:2,data:"M1,.5 L.5,1",strokeWidth:1},"\u{1FBA4}":{type:2,data:"M.5,0 L0,.5 L.5,1",strokeWidth:1},"\u{1FBA5}":{type:2,data:"M.5,0 L1,.5 L.5,1",strokeWidth:1},"\u{1FBA6}":{type:2,data:"M0,.5 L.5,1 L1,.5",strokeWidth:1},"\u{1FBA7}":{type:2,data:"M0,.5 L.5,0 L1,.5",strokeWidth:1},"\u{1FBA8}":{type:2,data:"M.5,0 L0,.5 M1,.5 L.5,1",strokeWidth:1},"\u{1FBA9}":{type:2,data:"M.5,0 L1,.5 M0,.5 L.5,1",strokeWidth:1},"\u{1FBAA}":{type:2,data:"M.5,0 L1,.5 L.5,1 L0,.5",strokeWidth:1},"\u{1FBAB}":{type:2,data:"M.5,0 L0,.5 L.5,1 L1,.5",strokeWidth:1},"\u{1FBAC}":{type:2,data:"M0,.5 L.5,0 L1,.5 L.5,1",strokeWidth:1},"\u{1FBAD}":{type:2,data:"M1,.5 L.5,0 L0,.5 L.5,1",strokeWidth:1},"\u{1FBAE}":{type:2,data:"M.5,0 L1,.5 L.5,1 L0,.5 Z",strokeWidth:1},"\u{1FBAF}":{type:2,data:"M0,.5 L1,.5 M.5,.35 L.5,.65",strokeWidth:1},"\u{1FBB0}":{type:3,data:"M0.1,0.2 L0.1,.8 L.4,.6 L.9,0.6 Z",scaleType:1},"\u{1FBB1}":{type:4,data:{d:"M.1,.525 L.35,.675 L.9,.35",type:1},scaleType:1},"\u{1FBB2}":{type:3,data:"M.29,.27 L0.13,.56 L.22,.59 L.35,0.35 L.67,.35 L.57,.57 L.71,.76 L.22,.76 L.42,1 L.53,.98 L.43,.86 L.9,.86 L.71,.6 L1,.6 L1,.52 L.83,.52 L.92,.36 L1,.36 L1,.27Z M.99,.13 A.12,.12,0,1,1,.75,.13 A.12,.12,0,1,1,.99,.13"},"\u{1FBB3}":{type:3,data:"M0,.27 L.3,.27 L.55,.12 L.63,.18 L.33,.36 L0,.36 M0,.52 L.33,.52 L.59,.89 L.73,.89 L.73,.98 L.53,.98 L.28,.6 L0,.6"},"\u{1FBB4}":{type:4,data:{d:"M.15,.55 L.5,.4 L.5,.45 L.65,.45 L.65,.35 L.85,.35 L.85,.625 L.5,.625 L.5,.7 Z",type:0},scaleType:1},"\u{1FBB5}":[{type:5,data:{d:"M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z",type:0}},{type:5,data:{d:"M.15,.5 L.5,.35 L.5,.425 L.85,.425 L.85,.575 L.5,.575 L.5,.65 Z",type:0},scaleType:1}],"\u{1FBB6}":[{type:5,data:{d:"M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z",type:0}},{type:5,data:{d:"M.85,.5 L.5,.35 L.5,.425 L.15,.425 L.15,.575 L.5,.575 L.5,.65 Z",type:0},scaleType:1}],"\u{1FBB7}":[{type:5,data:{d:"M.875,0 L1,0 L1,1 L.875,1 Z",type:0}},{type:5,data:{d:"M.5,.675 L.2,.5 L.35,.5 L.35,.325 L.65,.325 L.65,.5 L.8,.5 Z",type:0},scaleType:1}],"\u{1FBB8}":[{type:5,data:{d:"M.875,0 L1,0 L1,1 L.875,1 Z",type:0}},{type:5,data:{d:"M.5,.325 L.2,.5 L.35,.5 L.35,.675 L.65,.675 L.65,.5 L.8,.5 Z",type:0},scaleType:1}],"\u{1FBB9}":{type:5,data:{d:"M1,.89 L.11,.89 L.11,.37 L.36,.12 L.74,.12 L.96,.34 L1,.34 L1,.405 L.92,.405 L.69,.185 L.41,.185 L.21,.42 L.21,.825 L1,.825 Z",type:0}},"\u{1FBBA}":{type:5,data:{d:"M0,.89 L0,.825 L.78,.825 L.78,.53 L.7,.415 L0,.415 L0,.35 L.75,.35 L.88,.48 L.88,.89 Z",type:0}},"\u{1FBBB}":{type:5,data:{d:"M.31,.275 L.44,.275 L.44,.47 L.05,.47 L.05,.405 L.31,.405 Z M.56,.275 L.69,.275 L.69,.405 L.95,.405 L.95,.47 L.56,.47 Z M.05,.53 L.44,.53 L.44,.725 L.31,.725 L.31,.595 L.05,.595 Z M.56,.53 L.95,.53 L.95,.595 L.69,.595 L.69,.725 L.56,.725 Z",type:0},scaleType:1},"\u{1FBBC}":[{type:3,data:"M0,0 L1,0 L1,1 L0,1 L0,.935 L.885,.935 L.885,.065 L0,.065 Z"},{type:3,data:"M.67,.5 A.17,.085,0,1,1,.33,.5 A.17,.085,0,1,1,.67,.5",scaleType:1}],"\u{1FBBD}":{type:4,data:{d:"M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1 Z",type:1}},"\u{1FBBE}":{type:4,data:{d:"M1,.5 L.5,1",type:1}},"\u{1FBBF}":{type:4,data:{d:"M.5,0 L1,.5 L.5,1 L0,.5 Z",type:1}},"\u{1FBC0}":{type:3,data:"M.16,.445 A.02,.01,0,0,1,.39,.33 L.5,.375 L.61,.33 A.02,.01,0,0,1,.84,.445 L.75,.5 L.84,.555 A.02,.01,0,0,1,.61,.67 L.5,.625 L.39,.67 A.02,.01,0,0,1,.16,.555 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z",scaleType:1},"\u{1FBC1}":{type:3,data:"M0.142308,0.924 Q0.130770,0.924,0.120193,0.922 T0.101924,0.916 T0.089424,0.9065 T0.084616,0.895 V0.025 Q0.084616,0.019,0.089424,0.0135 T0.101924,0.004 T0.120193,-0.002 T0.142308,-0.004 Q0.150000,-0.004,0.157693,-0.003 T0.173078,0.000 L0.430770,0.085 Q0.451924,0.059,0.494232,0.041 T0.587501,0.013 T0.692309,-0.0005 T0.800001,-0.004 H1 V0.055 H0.800001 Q0.771155,0.055,0.742309,0.056 T0.685578,0.060 T0.630770,0.0685 T0.580770,0.0825 T0.543270,0.1045 T0.528847,0.133 V0.647 H0.530770 Q0.596155,0.645,0.659616,0.638 T0.782693,0.6165 T0.893270,0.5805 T1,0.531 V0.623 Q0.934616,0.644,0.880770,0.6595 T0.769232,0.685 T0.650963,0.700 T0.528847,0.707 V0.787 Q0.528847,0.802,0.543270,0.8155 T0.580770,0.8375 T0.630770,0.8515 T0.685578,0.860 T0.742309,0.864 T0.800001,0.865 H1 V0.924 H0.800001 Q0.746155,0.924,0.692309,0.9205 T0.587501,0.907 T0.494232,0.879 T0.430770,0.835 L0.173078,0.920 Q0.165386,0.922,0.157693,0.923 T0.142308,0.924 Z M0.2,0.841 L0.415385,0.770 V0.150 L0.2,0.079 V0.841 Z M1,0.698 Q0.973077,0.694,0.969231,0.6885 T0.965385,0.677 Q0.965385,0.672,0.969231,0.6665 T1,0.657 V0.698 Z"},"\u{1FBC2}":{type:3,data:"M0,0.623 V0.531 Q0.044231,0.512,0.0625,0.4905 T0.092308,0.4465 T0.108654,0.4005 T0.113462,0.354 V0.351 Q0.113462,0.345,0.117308,0.3395 T0.129808,0.3305 T0.148846,0.3245 T0.171154,0.323 H0.501923 Q0.532692,0.323,0.563462,0.3185 T0.617308,0.303 T0.651923,0.2765 T0.663462,0.2435 V0.241 Q0.663462,0.219,0.657692,0.197 T0.639423,0.1545 T0.604808,0.115 T0.549615,0.082 T0.475577,0.0615 T0.392308,0.055 H0 V-0.004 H1 V0.055 H0.667308 Q0.694231,0.071,0.713462,0.09 T0.746154,0.129 T0.766346,0.1705 T0.775,0.213 H1 V0.316 Q0.971154,0.305,0.955769,0.2965 T0.920192,0.2825 T0.876923,0.2745 T0.830769,0.272 H0.773077 Q0.767308,0.297,0.743269,0.319 T0.680769,0.355 T0.595192,0.375 T0.501923,0.381 H0.227308 Q0.223462,0.415,0.211,0.4485 T0.172692,0.5135 T0.108269,0.573 T0,0.623 Z M0.611538,0.924 H0 V0.865 H0.611538 Q0.642308,0.865,0.673077,0.8605 T0.727885,0.845 T0.762308,0.8185 T0.773077,0.787 V0.785 Q0.773077,0.769,0.762308,0.7535 T0.727885,0.727 T0.673077,0.7115 T0.611538,0.707 H0.059615 Q0.048077,0.707,0.037115,0.7045 T0,0.694 V0.653 Q0.026923,0.648,0.037115,0.646 T0.059615,0.644 H0.722692 Q0.753462,0.644,0.784231,0.6395 T0.839038,0.624 T0.873846,0.5975 T0.884615,0.566 V0.564 Q0.884615,0.548,0.873846,0.532 T0.839038,0.5055 T0.784231,0.49 T0.722692,0.4855 H0.392308 Q0.380769,0.4855,0.370192,0.483 T0.351923,0.4765 T0.339423,0.467 T0.334615,0.4555 T0.339423,0.444 T0.351923,0.4345 T0.370192,0.428 T0.392308,0.4255 H0.832692 Q0.855769,0.4255,0.878846,0.4235 T0.922115,0.416 T0.957692,0.4015 T1,0.3815 V0.4685 Q0.975,0.4705,0.966346,0.4725 T0.95,0.4765 Q0.961538,0.4835,0.969231,0.4915 T1,0.5075 V0.6205 Q0.965385,0.6455,0.926923,0.665 T0.84,0.6935 Q0.866923,0.7115,0.877596,0.7345 T0.894231,0.7855 V0.787 Q0.894231,0.815,0.876923,0.8425 T0.820192,0.8895 T0.726923,0.916 T0.611538,0.924 Z"},"\u{1FBC3}":{type:3,data:"M0,0.473 V0.386 Q0.025490,0.378,0.028431,0.3695 T0.031373,0.352 V0.35 Q0.031373,0.342,0.028431,0.333 T0,0.316 V0.213 H0.652941 Q0.684314,0.213,0.715686,0.2085 T0.770588,0.193 T0.804902,0.1665 T0.815686,0.135 V0.133 Q0.815686,0.117,0.804902,0.101 T0.770588,0.0745 T0.715686,0.0595 T0.652941,0.055 H0 V-0.004 H0.652941 Q0.707843,-0.004,0.761765,0.004 T0.856863,0.031 T0.915686,0.0775 T0.933333,0.133 V0.135 Q0.933333,0.163,0.915686,0.1905 T0.856863,0.237 T0.761765,0.264 T0.652941,0.272 H0.109804 Q0.131373,0.29,0.140196,0.31 T0.149020,0.35 V0.352 Q0.149020,0.37,0.142157,0.388 T0.118627,0.4225 T0.076471,0.452 T0,0.473 Z M0,0.625 V0.513 Q0.029412,0.526,0.032353,0.54 T0.035294,0.568 V0.57 Q0.035294,0.584,0.032353,0.598 T0,0.625 Z"},"\u{1FBC4}":{type:3,data:"M0.019231,1.085 V-0.165 H0.980769 V1.085 H0.019231 Z M0.446154,0.527 H0.553846 Q0.553846,0.511,0.5625,0.495 T0.591346,0.466 T0.631731,0.4405 T0.666346,0.4135 T0.688462,0.3825 T0.696154,0.35 Q0.696154,0.33,0.682692,0.311 T0.641346,0.2785 T0.575,0.259 T0.498077,0.253 T0.421154,0.259 T0.356731,0.279 T0.315385,0.3125 T0.301923,0.352 V0.357 H0.409615 V0.354 Q0.409615,0.345,0.414423,0.335 T0.431731,0.318 T0.4625,0.3075 T0.498077,0.304 Q0.517308,0.304,0.534615,0.307 T0.564423,0.3165 T0.582692,0.332 T0.588462,0.35 Q0.588462,0.366,0.572115,0.3805 T0.535577,0.4075 T0.496154,0.4335 T0.465385,0.4625 T0.45,0.4945 T0.446154,0.527 Z M0.5,0.667 Q0.519231,0.667,0.5375,0.664 T0.569231,0.654 T0.588462,0.637 T0.594231,0.617 T0.588462,0.5975 T0.569231,0.581 T0.5375,0.571 T0.5,0.568 T0.4625,0.571 T0.430769,0.581 T0.411538,0.5975 T0.405769,0.617 T0.411538,0.637 T0.430769,0.654 T0.4625,0.664 T0.5,0.667 Z",scaleType:1},"\u{1FBC5}":{type:5,data:{d:"M.44,.425 L.44,.51 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.51 L.56,.425 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC6}":{type:5,data:{d:"M.44,.425 L.44,.46 L.23,.385 L.19,.43 L.44,.51 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.61,.735 L.7,.71 L.56,.56 L.56,.51 L.81,.43 L.77,.385 L.56,.46 L.56,.425 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC7}":{type:5,data:{d:"M.44,.425 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.425 Z M.18,.53 L.23,.575 L.81,.43 L.77,.385 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC8}":{type:5,data:{d:"M.44,.425 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.62,.735 L.71,.71 L.56,.56 L.56,.425 Z M.23,.385 L.18,.43 L.77,.575 L.81,.53 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC9}":{type:5,data:{d:"M.44,.425 L.45,.495 L.15,.645 L.34,.645 L.34,.7 L.44,.7 L.44,.645 L.56,.645 L.56,.7 L.66,.7 L.66,.645 L.84,.645 L.54,.495 L.56,.425 Z M.39,.6 L.5,.55 L.60,.6 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBCA}":{type:5,data:{d:"M.26,.375 L.5,.255 L.74,.375 L.74,.665 L.5,.55 L.26,.665 Z M.37,.395 L.37,.54 L.5,.475 L.63,.54 L.63,.395 L.5,.33 Z",type:0},scaleType:1},"\u{1FBCB}":{type:5,data:{d:"M.09,.41 L.32,.295 L.5,.375 L.68,.295 L.91,.41 L.75,.5 L.91,.59 L.68,.705 L.5,.625 L.32,.705 L.09,.59 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z",type:0},scaleType:1},"\u{1FBCC}":{type:5,data:{d:"M.55,.305 L.88,.305 L.88,.355 L.65,.355 L.65,.47 L.88,.47 L.88,.52 L.55,.52 Z",type:0},scaleType:1},"\u{1FBCD}":{type:5,data:{d:"M.38,.39 L.5,.33 L.62,.39 L.62,.53 L.5,.47 L.38,.53 Z",type:0},scaleType:1},"\u{1FBCE}":{type:3,data:"M0,0 L0.6667,0 L0.6667,1 L0,1 Z"},"\u{1FBCF}":{type:3,data:"M0,0 L0.3333,0 L0.3333,1 L0,1 Z"},"\u{1FBD0}":{type:2,data:"M1,.5 L0,1",strokeWidth:1},"\u{1FBD1}":{type:2,data:"M1,0 L0,.5",strokeWidth:1},"\u{1FBD2}":{type:2,data:"M0,0 L1,.5",strokeWidth:1},"\u{1FBD3}":{type:2,data:"M0,.5 L1,1",strokeWidth:1},"\u{1FBD4}":{type:2,data:"M0,0 L.5,1",strokeWidth:1},"\u{1FBD5}":{type:2,data:"M.5,0 L1,1",strokeWidth:1},"\u{1FBD6}":{type:2,data:"M1,0 L.5,1",strokeWidth:1},"\u{1FBD7}":{type:2,data:"M.5,0 L0,1",strokeWidth:1},"\u{1FBD8}":{type:2,data:"M0,0 L.5,.5 L1,0",strokeWidth:1},"\u{1FBD9}":{type:2,data:"M1,0 L.5,.5 L1,1",strokeWidth:1},"\u{1FBDA}":{type:2,data:"M0,1 L.5,.5 L1,1",strokeWidth:1},"\u{1FBDB}":{type:2,data:"M0,0 L.5,.5 L0,1",strokeWidth:1},"\u{1FBDC}":{type:2,data:"M0,0 L.5,1 L1,0",strokeWidth:1},"\u{1FBDD}":{type:2,data:"M1,0 L0,.5 L1,1",strokeWidth:1},"\u{1FBDE}":{type:2,data:"M0,1 L.5,0 L1,1",strokeWidth:1},"\u{1FBDF}":{type:2,data:"M0,0 L1,.5 L0,1",strokeWidth:1},"\u{1FBE0}":{type:5,data:{d:"M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0",type:1}},"\u{1FBE1}":{type:5,data:{d:"M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1",type:1}},"\u{1FBE2}":{type:5,data:{d:"M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1",type:1}},"\u{1FBE3}":{type:5,data:{d:"M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1",type:1}},"\u{1FBE4}":{type:0,data:[{x:2,y:0,w:4,h:4}]},"\u{1FBE5}":{type:0,data:[{x:2,y:4,w:4,h:4}]},"\u{1FBE6}":{type:0,data:[{x:0,y:2,w:4,h:4}]},"\u{1FBE7}":{type:0,data:[{x:4,y:2,w:4,h:4}]},"\u{1FBE8}":{type:5,data:{d:"M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0 Z",type:0}},"\u{1FBE9}":{type:5,data:{d:"M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1 Z",type:0}},"\u{1FBEA}":{type:5,data:{d:"M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1 Z",type:0}},"\u{1FBEB}":{type:5,data:{d:"M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1 Z",type:0}},"\u{1FBEC}":{type:5,data:{d:"M1,0 L.5,0 C.5,.276,.724,.5,1,.5 Z",type:0}},"\u{1FBED}":{type:5,data:{d:"M0,1 L.5,1 C.5,.724,.276,.5,0,.5 Z",type:0}},"\u{1FBEE}":{type:5,data:{d:"M1,1 L1,.5 C.724,.5,.5,.724,.5,1 Z",type:0}},"\u{1FBEF}":{type:5,data:{d:"M0,0 L0,.5 C.276,.5,.5,.276,.5,0 Z",type:0}},"\u{1FBF0}":{type:5,data:{d:o0(126),type:0},scaleType:1},"\u{1FBF1}":{type:5,data:{d:o0(48),type:0},scaleType:1},"\u{1FBF2}":{type:5,data:{d:o0(109),type:0},scaleType:1},"\u{1FBF3}":{type:5,data:{d:o0(121),type:0},scaleType:1},"\u{1FBF4}":{type:5,data:{d:o0(51),type:0},scaleType:1},"\u{1FBF5}":{type:5,data:{d:o0(91),type:0},scaleType:1},"\u{1FBF6}":{type:5,data:{d:o0(95),type:0},scaleType:1},"\u{1FBF7}":{type:5,data:{d:o0(114),type:0},scaleType:1},"\u{1FBF8}":{type:5,data:{d:o0(127),type:0},scaleType:1},"\u{1FBF9}":{type:5,data:{d:o0(123),type:0},scaleType:1},"\u{1FBFA}":{type:5,data:{d:"M.5,.175 C.2,.175,.15,.305,.15,.435 L.05,.63 L.35,.63 C.35,.682,.42,.76,.5,.76 C.58,.76,.65,.682,.65,.63 L.95,.63 L.85,.435 C.85,.305,.8,.175,.5,.175 Z",type:0},scaleType:1},...Object.fromEntries(Array.from({length:256},(i,e)=>[String.fromCodePoint(10240+e),{type:6,data:e}]))},$e=new Set([9617,9618,9619,129932,129933,129934,129935,129936,129937,129938,129940,129948,129949,129950,129951]);function f(i){return{type:2,data:()=>{let e=[],r=[.375,.25,.375],s=[0,3/8,5/8];for(let a=0;a<3;a++){let o=i>>a*2&1,n=i>>a*2+1&1;o&&n?e.push(`M0,${s[a]} L1,${s[a]} L1,${s[a]+r[a]} L0,${s[a]+r[a]} Z`):o?e.push(`M0,${s[a]} L${.5},${s[a]} L${.5},${s[a]+r[a]} L0,${s[a]+r[a]} Z`):n&&e.push(`M${.5},${s[a]} L1,${s[a]} L1,${s[a]+r[a]} L${.5},${s[a]+r[a]} Z`)}return e.join(" ")}}}function o0(i){let e=[];if(i&64){let Q=.21500000000000002,m=.95-.15-.015;e.push(`M${Q},${.2125} L${Q+.075},${.175} L${m-.075},${.175} L${m},${.2125} L${m-.075},${.25} L${Q+.075},${.25} Z`)}if(i&32){let c=.7999999999999999,u=.95-.15/2,C=.95,Q=.175+.075+.015,m=.5-.015;e.push(`M${u},${Q} L${C},${Q+.0375} L${C},${m-.0375} L${u},${m} L${c},${m-.0375} L${c},${Q+.0375} Z`)}if(i&16){let c=.7999999999999999,u=.95-.15/2,C=.95,Q=.5+.015,m=.825-.075-.015;e.push(`M${u},${Q} L${C},${Q+.0375} L${C},${m-.0375} L${u},${m} L${c},${m-.0375} L${c},${Q+.0375} Z`)}if(i&8){let Q=.21500000000000002,m=.95-.15-.015;e.push(`M${Q},${.7875} L${Q+.075},${.75} L${m-.075},${.75} L${m},${.7875} L${m-.075},${.825} L${Q+.075},${.825} Z`)}if(i&4&&e.push(`M${.125},${.515} L${.2},${.515+.0375} L${.2},${.735-.0375} L${.125},${.735} L${.05},${.735-.0375} L${.05},${.515+.0375} Z`),i&2&&e.push(`M${.125},${.265} L${.2},${.265+.0375} L${.2},${.485-.0375} L${.125},${.485} L${.05},${.485-.0375} L${.05},${.265+.0375} Z`),i&1){let Q=.21500000000000002,m=.95-.15-.015;e.push(`M${Q},${.5} L${Q+.075},${.4625} L${m-.075},${.4625} L${m},${.5} L${m-.075},${.5375} L${Q+.075},${.5375} Z`)}return e.join(" ")}var k=0,H=0,T0=!1,_0=!1,$0=!1,z,x0=0,G0=class{constructor(e,t,r,s,a,o){this._terminal=e;this._optionService=t;this._selectionRenderModel=r;this._decorationService=s;this._coreBrowserService=a;this._themeService=o;this.result={fg:0,bg:0,ext:0}}resolve(e,t,r,s,a){this.result.bg=e.bg,this.result.fg=e.fg,this.result.ext=e.bg&268435456?e.extended.ext:0,H=0,k=0,_0=!1,T0=!1,$0=!1,z=this._themeService.colors,x0=0;let o=e.getCode();if(o!==0&&e.extended.underlineStyle===4){let n=Math.max(1,Math.floor(this._optionService.rawOptions.fontSize*this._coreBrowserService.dpr/15));x0=t*s%(Math.round(n)*2)}if(x0===0&&$e.has(o)&&(x0=t*s%2*2+r*a%2),this._decorationService.forEachDecorationAtCell(t,r,"bottom",n=>{n.backgroundColorRGB&&(H=n.backgroundColorRGB.rgba>>8&16777215,_0=!0),n.foregroundColorRGB&&(k=n.foregroundColorRGB.rgba>>8&16777215,T0=!0)}),$0=this._selectionRenderModel.isCellSelected(this._terminal,t,r),$0){if(this.result.fg&67108864||(this.result.bg&50331648)!==0){if(this.result.fg&67108864)switch(this.result.fg&50331648){case 16777216:case 33554432:H=this._themeService.colors.ansi[this.result.fg&255].rgba;break;case 50331648:H=(this.result.fg&16777215)<<8|255;break;case 0:default:H=this._themeService.colors.foreground.rgba}else switch(this.result.bg&50331648){case 16777216:case 33554432:H=this._themeService.colors.ansi[this.result.bg&255].rgba;break;case 50331648:H=(this.result.bg&16777215)<<8|255;break}H=d0.blend(H,(this._coreBrowserService.isFocused?z.selectionBackgroundOpaque:z.selectionInactiveBackgroundOpaque).rgba&4294967040|128)>>8&16777215}else H=(this._coreBrowserService.isFocused?z.selectionBackgroundOpaque:z.selectionInactiveBackgroundOpaque).rgba>>8&16777215;if(_0=!0,z.selectionForeground&&(k=z.selectionForeground.rgba>>8&16777215,T0=!0),V0(e.getCode())){if(this.result.fg&67108864&&(this.result.bg&50331648)===0)k=(this._coreBrowserService.isFocused?z.selectionBackgroundOpaque:z.selectionInactiveBackgroundOpaque).rgba>>8&16777215;else{if(this.result.fg&67108864)switch(this.result.bg&50331648){case 16777216:case 33554432:k=this._themeService.colors.ansi[this.result.bg&255].rgba;break;case 50331648:k=(this.result.bg&16777215)<<8|255;break}else switch(this.result.fg&50331648){case 16777216:case 33554432:k=this._themeService.colors.ansi[this.result.fg&255].rgba;break;case 50331648:k=(this.result.fg&16777215)<<8|255;break;case 0:default:k=this._themeService.colors.foreground.rgba}k=d0.blend(k,(this._coreBrowserService.isFocused?z.selectionBackgroundOpaque:z.selectionInactiveBackgroundOpaque).rgba&4294967040|128)>>8&16777215}T0=!0}}this._decorationService.forEachDecorationAtCell(t,r,"top",n=>{n.backgroundColorRGB&&(H=n.backgroundColorRGB.rgba>>8&16777215,_0=!0),n.foregroundColorRGB&&(k=n.foregroundColorRGB.rgba>>8&16777215,T0=!0)}),_0&&($0?H=e.bg&-16777216&-134217729|H|50331648:H=e.bg&-16777216|H|50331648),T0&&(k=e.fg&-16777216&-67108865|k|50331648),this.result.fg&67108864&&(_0&&!T0&&((this.result.bg&50331648)===0?k=this.result.fg&-134217728|z.background.rgba>>8&16777215&16777215|50331648:k=this.result.fg&-134217728|this.result.bg&67108863,T0=!0),!_0&&T0&&((this.result.fg&50331648)===0?H=this.result.bg&-67108864|z.foreground.rgba>>8&16777215&16777215|50331648:H=this.result.bg&-67108864|this.result.fg&67108863,_0=!0)),z=void 0,this.result.bg=_0?H:this.result.bg,this.result.fg=T0?k:this.result.fg,this.result.ext&=536870911,this.result.ext|=x0<<29&3758096384}};var Ke=.5,K0=Be||we?"bottom":"ideographic";function qe(i,e,t,r,s,a,o,n,L,T,h,d,_=0){let p=Ve[e];if(p){let c=Array.isArray(p)?p:[p];for(let u of c)Pt(i,u,t,r,s,a,o,n,L,T,h,d,_);return!0}return!1}function Pt(i,e,t,r,s,a,o,n,L,T,h,d,_=0){let p=s,c=a,u=t,C=r;switch(e.scaleType===1&&(p=o,c=n,u=t+(s-o)/2,C=r+(a-n)/2),e.clipPath&&(i.save(),Vt(i,e.clipPath,u,C,p,c)),e.type){case 0:Bt(i,e.data,u,C,p,c);break;case 1:Ut(i,e.data,u,C,p,c,_);break;case 2:Wt(i,e.data,u,C,p,c,T,h,e.strokeWidth);break;case 3:Ht(i,e.data,u,C,p,c,T,e.strokeWidth);break;case 4:Zt(i,e.data,u,C,p,c,T,d);break;case 5:$t(i,e.data,u,C,p,c,L,T,h);break;case 6:wt(i,e.data,u,C,p,c);break}e.clipPath&&i.restore()}function Bt(i,e,t,r,s,a){for(let o=0;o{let F=parseFloat(m);return R%2===0?t+F*s:r+F*a});if(u==="M")i.moveTo(Q[0],Q[1]),T=Q[0],h=Q[1],d=T,_=h;else if(u==="L")i.lineTo(Q[0],Q[1]),T=Q[0],h=Q[1],d=T,_=h;else if(u==="Q")i.quadraticCurveTo(Q[0],Q[1],Q[2],Q[3]),d=Q[0],_=Q[1],T=Q[2],h=Q[3];else if(u==="T"){let m,R;p==="Q"||p==="T"?(m=2*T-d,R=2*h-_):(m=T,R=h),i.quadraticCurveTo(m,R,Q[0],Q[1]),d=m,_=R,T=Q[0],h=Q[1]}else u==="C"&&(i.bezierCurveTo(Q[0],Q[1],Q[2],Q[3],Q[4],Q[5]),d=Q[2],_=Q[3],T=Q[4],h=Q[5]);p=u}n!==void 0?(i.strokeStyle=i.fillStyle,i.lineWidth=o*n,i.stroke()):i.fill()}function kt(i,e,t,r,s,a,o,n,L,T){if(r===0||s===0){i.lineTo(L,T);return}r=Math.abs(r),s=Math.abs(s);let h=Math.cos(a),d=Math.sin(a),_=(e-L)/2,p=(t-T)/2,c=h*_+d*p,u=-d*_+h*p,C=r*r,Q=s*s,m=c*c,R=u*u,F=m/C+R/Q;if(F>1){let I=Math.sqrt(F);r*=I,s*=I,C=r*r,Q=s*s}let E=(C*Q-C*R-Q*m)/(C*R+Q*m);E<0&&(E=0);let y=(o===n?-1:1)*Math.sqrt(E),Y=y*(r*u/s),X=y*-(s*c/r),A0=h*Y-d*X+(e+L)/2,t0=d*Y+h*X+(t+T)/2,L0=(c-Y)/r,A=(u-X)/s,n0=(-c-Y)/r,a0=(-u-X)/s,$=Math.atan2(A,L0),q=Math.atan2(a0,n0)-$;n===0&&q>0?q-=2*Math.PI:n===1&&q<0&&(q+=2*Math.PI);let g0=$+q;i.ellipse(A0,t0,r,s,a,$,g0,n===0)}function Zt(i,e,t,r,s,a,o,n){i.save(),i.fillRect(t,r,s,a),n&&(i.fillStyle=n,i.strokeStyle=n),i.lineWidth=o,i.lineCap="square",i.beginPath();for(let L of e.d.split(" ")){let T=L[0],h=L.substring(1).split(",");if(!h[0]||!h[1]){T==="Z"&&i.closePath();continue}let d=h.map((_,p)=>{let c=parseFloat(_);return p%2===0?t+c*s:r+c*a});T==="M"?i.moveTo(d[0],d[1]):T==="L"&&i.lineTo(d[0],d[1])}e.type===1?i.stroke():i.fill(),i.restore()}var Xe=new Map;function Ut(i,e,t,r,s,a,o=0){let n=Xe.get(e);n||(n=new Map,Xe.set(e,n));let L=i.fillStyle;if(typeof L!="string")throw new Error(`Unexpected fillStyle type "${L}"`);let T=n.get(L);if(!T){let _=e[0].length,p=e.length,c=i.canvas.ownerDocument.createElement("canvas");c.width=_,c.height=p;let u=g(c.getContext("2d")),C=new ImageData(_,p),Q,m,R,F;if(L.startsWith("#"))Q=parseInt(L.slice(1,3),16),m=parseInt(L.slice(3,5),16),R=parseInt(L.slice(5,7),16),F=L.length>7&&parseInt(L.slice(7,9),16)||1;else if(L.startsWith("rgba"))[Q,m,R,F]=L.substring(5,L.length-1).split(",").map(E=>parseFloat(E));else throw new Error(`Unexpected fillStyle color format "${L}" when drawing pattern glyph`);for(let E=0;E>1&1,d=o&1;h!==0||d!==0?T.setTransform(new DOMMatrix().translateSelf(-h,-d)):T.setTransform(new DOMMatrix),i.fillStyle=T,i.fillRect(t,r,s,a)}function Wt(i,e,t,r,s,a,o,n,L){i.save(),i.beginPath(),i.rect(t,r,s,a),i.clip(),i.beginPath();let T;if(typeof e=="function"){let _=.15/a*s;T=e(.15,_)}else T=e;let h={currentX:0,currentY:0,lastControlX:0,lastControlY:0,lastCommand:""};for(let d of T.split(" ")){let _=d[0];if(_==="Z"){i.closePath(),h.lastCommand=_;continue}let p=je[_];if(!p){n.error(`Could not find drawing instructions for "${_}"`);continue}let c=d.substring(1).split(",");!c[0]||!c[1]||(p(i,Je(c,s,a,t,r,!0,o,0,0,!1),h),h.lastCommand=_)}L!==void 0?(i.strokeStyle=i.fillStyle,i.lineWidth=o*L,i.stroke()):i.fill(),i.closePath(),i.restore()}function Vt(i,e,t,r,s,a){i.beginPath();for(let o of e.split(" ")){let n=o[0];if(n==="Z"){i.closePath();continue}let L=o.substring(1).split(",");if(!L[0]||!L[1])continue;let T=t+parseFloat(L[0])*s,h=r+parseFloat(L[1])*a;n==="M"?i.moveTo(T,h):n==="L"&&i.lineTo(T,h)}i.clip()}function $t(i,e,t,r,s,a,o,n,L){let T=new Path2D;T.rect(t,r,s,a),i.clip(T),i.beginPath();let h=o/12;i.lineWidth=n*h;let d={currentX:0,currentY:0,lastControlX:0,lastControlY:0,lastCommand:""};for(let _ of e.d.split(" ")){let p=_[0];if(p==="Z"){i.closePath(),d.lastCommand=p;continue}let c=je[p];if(!c){L.error(`Could not find drawing instructions for "${p}"`);continue}let u=_.substring(1).split(",");!u[0]||!u[1]||(c(i,Je(u,s,a,t,r,!1,n,(e.leftPadding??0)*(h/2),(e.rightPadding??0)*(h/2)),d),d.lastCommand=p)}e.type===1?(i.strokeStyle=i.fillStyle,i.stroke()):i.fill(),i.closePath()}function Ye(i,e,t=0){return Math.max(Math.min(i,e),t)}var je={C:(i,e,t)=>{i.bezierCurveTo(e[0],e[1],e[2],e[3],e[4],e[5]),t.lastControlX=e[2],t.lastControlY=e[3],t.currentX=e[4],t.currentY=e[5]},L:(i,e,t)=>{i.lineTo(e[0],e[1]),t.lastControlX=t.currentX=e[0],t.lastControlY=t.currentY=e[1]},M:(i,e,t)=>{i.moveTo(e[0],e[1]),t.lastControlX=t.currentX=e[0],t.lastControlY=t.currentY=e[1]},Q:(i,e,t)=>{i.quadraticCurveTo(e[0],e[1],e[2],e[3]),t.lastControlX=e[0],t.lastControlY=e[1],t.currentX=e[2],t.currentY=e[3]},T:(i,e,t)=>{let r,s;t.lastCommand==="Q"||t.lastCommand==="T"?(r=2*t.currentX-t.lastControlX,s=2*t.currentY-t.lastControlY):(r=t.currentX,s=t.currentY),i.quadraticCurveTo(r,s,e[0],e[1]),t.lastControlX=r,t.lastControlY=s,t.currentX=e[0],t.currentY=e[1]}};function Je(i,e,t,r,s,a,o,n=0,L=0,T=!0){let h=i.map(d=>parseFloat(d)||parseInt(d));if(h.length<2)throw new Error("Too few arguments for instruction");for(let d=0;da){s-t<-20&&this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(s-t))}ms`),this._start();return}s=a}this.clear()}},Ce=class extends X0{_requestCallback(e){return setTimeout(()=>e(this._createDeadline(16)))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){let t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}},Qe=class extends X0{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}},et="requestIdleCallback"in globalThis?Qe:Ce;var s0=class i{constructor(){this.fg=0;this.bg=0;this.extended=new P0}static toColorRGB(e){return[e>>>16&255,e>>>8&255,e&255]}static fromColorRGB(e){return(e[0]&255)<<16|(e[1]&255)<<8|e[2]&255}clone(){let e=new i;return e.fg=this.fg,e.bg=this.bg,e.extended=this.extended.clone(),e}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)===50331648}isBgRGB(){return(this.bg&50331648)===50331648}isFgPalette(){return(this.fg&50331648)===16777216||(this.fg&50331648)===33554432}isBgPalette(){return(this.bg&50331648)===16777216||(this.bg&50331648)===33554432}isFgDefault(){return(this.fg&50331648)===0}isBgDefault(){return(this.bg&50331648)===0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===16777216||(this.extended.underlineColor&50331648)===33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===0:this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},P0=class i{constructor(e=0,t=0){this._ext=0;this._urlId=0;this._ext=e,this._urlId=t}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(e){this._ext=e}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(e){this._ext&=-469762049,this._ext|=e<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(e){this._ext&=-67108864,this._ext|=e&67108863}get urlId(){return this._urlId}set urlId(e){this._urlId=e}get underlineVariantOffset(){let e=(this._ext&3758096384)>>29;return e<0?e^4294967288:e}set underlineVariantOffset(e){this._ext&=536870911,this._ext|=e<<29&3758096384}clone(){return new i(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}};var U=class{constructor(){this._listeners=[];this._disposed=!1}get event(){return this._event?this._event:(this._event=(e,t,r)=>{if(this._disposed)return M(()=>{});let s={fn:e,thisArgs:t};this._listeners.push(s);let a=M(()=>{let o=this._listeners.indexOf(s);o!==-1&&this._listeners.splice(o,1)});return r&&(Array.isArray(r)?r.push(a):r.add(a)),a},this._event)}fire(e){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{let{fn:t,thisArgs:r}=this._listeners[0];t.call(r,e);return}default:{let t=this._listeners.slice();for(let{fn:r,thisArgs:s}of t)r.call(s,e)}}}dispose(){this._disposed||(this._disposed=!0,this._listeners.length=0)}},c0;(s=>{function i(a,o){return a(n=>o.fire(n))}s.forward=i;function e(a,o){return(n,L,T)=>a(h=>n.call(L,o(h)),void 0,T)}s.map=e;function t(...a){return(o,n,L)=>{let T=new R0;for(let h of a)T.add(h(d=>o.call(n,d)));return L&&(Array.isArray(L)?L.push(T):L.add(T)),T}}s.any=t;function r(a,o,n){return o(n),a(L=>o(L))}s.runAndSubscribe=r})(c0||={});var tt={texturePage:0,texturePosition:{x:0,y:0},texturePositionClipSpace:{x:0,y:0},offset:{x:0,y:0},size:{x:0,y:0},sizeClipSpace:{x:0,y:0}},B0=2;var w0,J=class i{constructor(e,t,r,s){this._document=e;this._config=t;this._unicodeService=r;this._logService=s;this._didWarmUp=!1;this._cacheMap=new D0;this._cacheMapCombined=new D0;this._pages=[];this._activePages=[];this._workBoundingBox={top:0,left:0,bottom:0,right:0};this._workAttributeData=new s0;this._textureSize=512;this._onAddTextureAtlasCanvas=new U;this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event;this._onRemoveTextureAtlasCanvas=new U;this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event;this._requestClearModel=!1;this._createNewPage(),this._tmpCanvas=it(e,this._config.deviceCellWidth*4+B0*2,this._config.deviceCellHeight+B0*2),this._tmpCtx=g(this._tmpCanvas.getContext("2d",{alpha:this._config.allowTransparency,willReadFrequently:!0}))}get pages(){return this._pages}dispose(){this._tmpCanvas.remove();for(let e of this.pages)e.canvas.remove();this._onAddTextureAtlasCanvas.dispose(),this._onRemoveTextureAtlasCanvas.dispose()}warmUp(){this._didWarmUp||(this._doWarmUp(),this._didWarmUp=!0)}_doWarmUp(){let e=new et(this._logService);for(let t=33;t<126;t++)e.enqueue(()=>{if(!this._cacheMap.get(t,0,0,0)){let r=this._drawToCache(t,0,0,0,!1,void 0);this._cacheMap.set(t,0,0,0,r)}})}beginFrame(){let e=this._requestClearModel;return this._requestClearModel=!1,e}clearTexture(){if(!(this._pages[0].currentRow.x===0&&this._pages[0].currentRow.y===0)){for(let e of this._pages)e.clear();this._cacheMap.clear(),this._cacheMapCombined.clear(),this._didWarmUp=!1}}_createNewPage(){if(i.maxAtlasPages&&this._pages.length>=Math.max(4,i.maxAtlasPages)){let t=this._pages.filter(T=>T.canvas.width*2<=(i.maxTextureSize||4096)).sort((T,h)=>h.canvas.width!==T.canvas.width?h.canvas.width-T.canvas.width:h.percentageUsed-T.percentageUsed),r=-1,s=0;for(let T=0;TT.canvas.width!==a[0].canvas.width)){let T=new u0(this._document,this._textureSize);return this._pages.push(T),this._activePages.push(T),this._onAddTextureAtlasCanvas.fire(T.canvas),T}let o=a.map(T=>T.glyphs[0].texturePage).sort((T,h)=>T-h),n=this.pages.length-a.length,L=this._mergePages(a,n);L.version=++u0.nextVersion;for(let T=o.length-1;T>=0;T--)this._deletePage(o[T]);this.pages.push(L),this._requestClearModel=!0,this._onAddTextureAtlasCanvas.fire(L.canvas)}let e=new u0(this._document,this._textureSize);return this._pages.push(e),this._activePages.push(e),this._onAddTextureAtlasCanvas.fire(e.canvas),e}_mergePages(e,t){let r=e[0].canvas.width*2,s=new u0(this._document,r,e);for(let[a,o]of e.entries()){let n=a*o.canvas.width%r,L=Math.floor(a/2)*o.canvas.height;s.ctx.drawImage(o.canvas,n,L);for(let h of o.glyphs)h.texturePage=t,h.sizeClipSpace.x=h.size.x/r,h.sizeClipSpace.y=h.size.y/r,h.texturePosition.x+=n,h.texturePosition.y+=L,h.texturePositionClipSpace.x=h.texturePosition.x/r,h.texturePositionClipSpace.y=h.texturePosition.y/r;this._onRemoveTextureAtlasCanvas.fire(o.canvas);let T=this._activePages.indexOf(o);T!==-1&&this._activePages.splice(T,1)}return s}_deletePage(e){this._pages.splice(e,1);for(let t=e;t=this._config.colors.ansi.length)throw new Error("No color found for idx "+e);return this._config.colors.ansi[e]}_getBackgroundColor(e,t,r,s){if(this._config.allowTransparency)return K;let a;switch(e){case 16777216:case 33554432:a=this._getColorFromAnsiIndex(t);break;case 50331648:let o=s0.toColorRGB(t);a=Z.toColor(o[0],o[1],o[2]);break;case 0:default:r?a=I0.opaque(this._config.colors.foreground):a=this._config.colors.background;break}return this._config.allowTransparency||(a=I0.opaque(a)),a}_getForegroundColor(e,t,r,s,a,o,n,L,T,h){let d=this._getMinimumContrastColor(e,t,r,s,a,o,n,T,L,h);if(d)return d;let _;switch(a){case 16777216:case 33554432:this._config.drawBoldTextInBrightColors&&T&&o<8&&(o+=8),_=this._getColorFromAnsiIndex(o);break;case 50331648:let p=s0.toColorRGB(o);_=Z.toColor(p[0],p[1],p[2]);break;case 0:default:n?_=this._config.colors.background:_=this._config.colors.foreground}return this._config.allowTransparency&&(_=I0.opaque(_)),L&&(_=I0.multiplyOpacity(_,Ke)),_}_resolveBackgroundRgba(e,t,r){switch(e){case 16777216:case 33554432:return this._getColorFromAnsiIndex(t).rgba;case 50331648:return t<<8;case 0:default:return r?this._config.colors.foreground.rgba:this._config.colors.background.rgba}}_resolveForegroundRgba(e,t,r,s){switch(e){case 16777216:case 33554432:return this._config.drawBoldTextInBrightColors&&s&&t<8&&(t+=8),this._getColorFromAnsiIndex(t).rgba;case 50331648:return t<<8;case 0:default:return r?this._config.colors.background.rgba:this._config.colors.foreground.rgba}}_getMinimumContrastColor(e,t,r,s,a,o,n,L,T,h){if(this._config.minimumContrastRatio===1||h)return;let d=this._getContrastCache(T),_=d.getColor(e,s);if(_!==void 0)return _??void 0;let p=this._resolveBackgroundRgba(t,r,n),c=this._resolveForegroundRgba(a,o,n,L),u=d0.ensureContrastRatio(p,c,this._config.minimumContrastRatio/(T?2:1));if(!u){d.setColor(e,s,null);return}let C=Z.toColor(u>>24&255,u>>16&255,u>>8&255);return d.setColor(e,s,C),C}_getContrastCache(e){return e?this._config.colors.halfContrastCache:this._config.colors.contrastCache}_drawToCache(e,t,r,s,a,o){let n=typeof e=="number"?String.fromCharCode(e):e;o&&this._tmpCanvas.parentElement!==o&&(this._tmpCanvas.style.display="none",o.append(this._tmpCanvas));let L=Math.min(this._config.deviceCellWidth*Math.max(n.length,2)+B0*2,this._config.deviceMaxTextureSize);this._tmpCanvas.width=b?b*2-f0:b-f0;!(f0>=b)===!1||Z0===0?(this._tmpCtx.setLineDash([Math.round(b),Math.round(b)]),this._tmpCtx.moveTo(j+Z0,G),this._tmpCtx.lineTo(Q0,G)):(this._tmpCtx.setLineDash([Math.round(b),Math.round(b)]),this._tmpCtx.moveTo(j,G),this._tmpCtx.lineTo(j+Z0,G),this._tmpCtx.moveTo(j+Z0+b,G),this._tmpCtx.lineTo(Q0,G)),f0=We(Q0-j,b,f0);break;case 5:let vt=.6,Ot=.3,de=Q0-j,Se=Math.floor(vt*de),Re=Math.floor(Ot*de),It=de-Se-Re;this._tmpCtx.setLineDash([Se,Re,It]),this._tmpCtx.moveTo(j,G),this._tmpCtx.lineTo(Q0,G);break;case 1:default:this._tmpCtx.moveTo(j,G),this._tmpCtx.lineTo(Q0,G);break}y0||this._tmpCtx.stroke(),this._tmpCtx.restore()}if(this._tmpCtx.restore(),!n0&&this._config.fontSize>=12&&!this._config.allowTransparency&&n!==" "){this._tmpCtx.save(),this._tmpCtx.textBaseline="alphabetic";let r0=this._tmpCtx.measureText(n);if(this._tmpCtx.restore(),"actualBoundingBoxDescent"in r0&&r0.actualBoundingBoxDescent>0){this._tmpCtx.save();let y0=new Path2D;y0.rect(i0,C0-Math.ceil(b/2),this._config.deviceCellWidth*$,v0-C0+Math.ceil(b/2)),this._tmpCtx.clip(y0),this._tmpCtx.lineWidth=this._config.devicePixelRatio*3,this._tmpCtx.strokeStyle=y.css,this._tmpCtx.strokeText(n,A,A+this._config.deviceCharHeight),this._tmpCtx.restore()}}}if(Q){let b=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/15)),D=b%2===1?.5:0;this._tmpCtx.lineWidth=b,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(A,A+D),this._tmpCtx.lineTo(A+this._config.deviceCharWidth*$,A+D),this._tmpCtx.stroke()}if(n0||this._tmpCtx.fillText(n,A,A+this._config.deviceCharHeight),n==="_"&&!this._config.allowTransparency){let b=fe(this._tmpCtx.getImageData(A,A,this._config.deviceCellWidth,this._config.deviceCellHeight),y,L0,a0);if(b)for(let D=1;D<=5&&(this._tmpCtx.save(),this._tmpCtx.fillStyle=y.css,this._tmpCtx.fillRect(0,0,this._tmpCanvas.width,this._tmpCanvas.height),this._tmpCtx.restore(),this._tmpCtx.fillText(n,A,A+this._config.deviceCharHeight-D),b=fe(this._tmpCtx.getImageData(A,A,this._config.deviceCellWidth,this._config.deviceCellHeight),y,L0,a0),!!b);D++);}if(C){let b=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/10)),D=this._tmpCtx.lineWidth%2===1?.5:0;this._tmpCtx.lineWidth=b,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(A,A+Math.floor(this._config.deviceCharHeight/2)-D),this._tmpCtx.lineTo(A+this._config.deviceCharWidth*$,A+Math.floor(this._config.deviceCharHeight/2)-D),this._tmpCtx.stroke()}this._tmpCtx.restore();let q=this._tmpCtx.getImageData(0,0,this._tmpCanvas.width,this._tmpCanvas.height),g0;if(this._config.allowTransparency?g0=Gt(q):g0=fe(q,y,L0,a0),g0)return tt;let I=this._findGlyphBoundingBox(q,this._workBoundingBox,L,t0,n0,A),O,v;for(;;){if(this._activePages.length===0){let b=this._createNewPage();O=b,v=b.currentRow,v.height=I.size.y;break}O=this._activePages[this._activePages.length-1],v=O.currentRow;for(let b of this._activePages)I.size.y<=b.currentRow.height&&(O=b,v=b.currentRow);for(let b=this._activePages.length-1;b>=0;b--)for(let D of this._activePages[b].fixedRows)D.height<=v.height&&I.size.y<=D.height&&(O=this._activePages[b],v=D);if(I.size.x>this._textureSize){this._overflowSizePage||(this._overflowSizePage=new u0(this._document,this._config.deviceMaxTextureSize),this.pages.push(this._overflowSizePage),this._requestClearModel=!0,this._onAddTextureAtlasCanvas.fire(this._overflowSizePage.canvas)),O=this._overflowSizePage,v=this._overflowSizePage.currentRow,v.x+I.size.x>=O.canvas.width&&(v.x=0,v.y+=v.height,v.height=0);break}if(v.y+I.size.y>=O.canvas.height||v.height>I.size.y+2){let b=!1;if(O.currentRow.y+O.currentRow.height+I.size.y>=O.canvas.height){let D;for(let i0 of this._activePages)if(i0.currentRow.y+i0.currentRow.height+I.size.y=i.maxAtlasPages&&v.y+I.size.y<=O.canvas.height&&v.height>=I.size.y&&v.x+I.size.x<=O.canvas.width)b=!0;else{let i0=this._createNewPage();O=i0,v=i0.currentRow,v.height=I.size.y,b=!0}}b||(O.currentRow.height>0&&O.fixedRows.push(O.currentRow),v={x:0,y:O.currentRow.y+O.currentRow.height,height:I.size.y},O.fixedRows.push(v),O.currentRow={x:0,y:v.y+v.height,height:0})}if(v.x+I.size.x<=O.canvas.width)break;v===O.currentRow?(v.x=0,v.y+=v.height,v.height=0):O.fixedRows.splice(O.fixedRows.indexOf(v),1)}return I.texturePage=this._pages.indexOf(O),I.texturePosition.x=v.x,I.texturePosition.y=v.y,I.texturePositionClipSpace.x=v.x/O.canvas.width,I.texturePositionClipSpace.y=v.y/O.canvas.height,I.sizeClipSpace.x/=O.canvas.width,I.sizeClipSpace.y/=O.canvas.height,v.height=Math.max(v.height,I.size.y),v.x+=I.size.x,O.ctx.putImageData(q,I.texturePosition.x-this._workBoundingBox.left,I.texturePosition.y-this._workBoundingBox.top,this._workBoundingBox.left,this._workBoundingBox.top,I.size.x,I.size.y),O.addGlyph(I),O.version=++u0.nextVersion,I}_findGlyphBoundingBox(e,t,r,s,a,o){t.top=0;let n=s?this._config.deviceCellHeight:this._tmpCanvas.height,L=s?this._config.deviceCellWidth:r,T=!1;for(let h=0;h=o;h--){for(let d=0;d=0;h--){for(let d=0;d>>24,a=e.rgba>>>16&255,o=e.rgba>>>8&255,n=t.rgba>>>24,L=t.rgba>>>16&255,T=t.rgba>>>8&255,h=Math.floor((Math.abs(s-n)+Math.abs(a-L)+Math.abs(o-T))/12),d=!0;for(let _=0;_0)return!1;return!0}function it(i,e,t){let r=i.createElement("canvas");return r.width=e,r.height=t,r}function rt(i,e,t,r,s,a,o,n,L=!0){let T={foreground:a.foreground,background:a.background,cursor:K,cursorAccent:K,selectionForeground:K,selectionBackgroundTransparent:K,selectionBackgroundOpaque:K,selectionInactiveBackgroundTransparent:K,selectionInactiveBackgroundOpaque:K,overviewRulerBorder:K,scrollbarSliderBackground:K,scrollbarSliderHoverBackground:K,scrollbarSliderActiveBackground:K,ansi:a.ansi.slice(),contrastCache:a.contrastCache,halfContrastCache:a.halfContrastCache};return{customGlyphs:L,devicePixelRatio:o,deviceMaxTextureSize:n,letterSpacing:s.letterSpacing,lineHeight:s.lineHeight,deviceCellWidth:i,deviceCellHeight:e,deviceCharWidth:t,deviceCharHeight:r,fontFamily:s.fontFamily,fontSize:s.fontSize,fontWeight:s.fontWeight,fontWeightBold:s.fontWeightBold,allowTransparency:s.allowTransparency,drawBoldTextInBrightColors:s.drawBoldTextInBrightColors,minimumContrastRatio:s.minimumContrastRatio,colors:T}}function Ee(i,e){for(let t=0;t=0){if(Ee(u.config,h))return u.atlas;u.ownedBy.length===1?(u.atlas.dispose(),e0.splice(c,1)):u.ownedBy.splice(C,1);break}}for(let c=0;c{this._renderCallback(),this._animationFrame=void 0})))}_restartInterval(e=600){this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout=this._coreBrowserService.window.setTimeout(()=>{if(this._animationTimeRestarted){let t=600-(Date.now()-this._animationTimeRestarted);if(this._animationTimeRestarted=void 0,t>0){this._restartInterval(t);return}}this.isCursorVisible=!1,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._renderCallback(),this._animationFrame=void 0}),this._blinkInterval=this._coreBrowserService.window.setInterval(()=>{if(this._animationTimeRestarted){let t=600-(Date.now()-this._animationTimeRestarted);this._animationTimeRestarted=void 0,this._restartInterval(t);return}this.isCursorVisible=!this.isCursorVisible,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._renderCallback(),this._animationFrame=void 0})},600)},e)}pause(){this.isCursorVisible=!0,this._isIdlePaused=!1,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0),this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}resume(){this.pause(),this._animationTimeRestarted=void 0,this._restartInterval(),this._resetIdleTimer(),this.restartBlinkAnimation()}_resetIdleTimer(){this._isIdlePaused=!1,this._idleTimeout&&this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_stopBlinkingDueToIdle(){this.isCursorVisible=!0,this._isIdlePaused=!0,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0),this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0,this._renderCallback()}};function ve(i,e,t){let r=new e.ResizeObserver(s=>{let a=s.find(L=>L.target===i);if(!a)return;if(!("devicePixelContentBoxSize"in a)){r?.disconnect(),r=void 0;return}let o=a.devicePixelContentBoxSize[0].inlineSize,n=a.devicePixelContentBoxSize[0].blockSize;o>0&&n>0&&t(o,n)});try{r.observe(i,{box:["device-pixel-content-box"]})}catch{r.disconnect(),r=void 0}return M(()=>r?.disconnect())}function nt(i){return i>65535?(i-=65536,String.fromCharCode((i>>10)+55296)+String.fromCharCode(i%1024+56320)):String.fromCharCode(i)}var j0=class i extends s0{constructor(){super(...arguments);this.content=0;this.fg=0;this.bg=0;this.extended=new P0;this.combinedData=""}static fromCharData(t){let r=new i;return r.setFromCharData(t),r}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?nt(this.content&2097151):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(t){this.fg=t[0],this.bg=0;let r=!1;if(t[1].length>2)r=!0;else if(t[1].length===2){let s=t[1].charCodeAt(0);if(55296<=s&&s<=56319){let a=t[1].charCodeAt(1);56320<=a&&a<=57343?this.content=(s-55296)*1024+a-56320+65536|t[2]<<22:r=!0}else r=!0}else this.content=t[1].charCodeAt(0)|t[2]<<22;r&&(this.combinedData=t[1],this.content=2097152|t[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}attributesEquals(t){if(this.getFgColorMode()!==t.getFgColorMode()||this.getFgColor()!==t.getFgColor()||this.getBgColorMode()!==t.getBgColorMode()||this.getBgColor()!==t.getBgColor()||this.isInverse()!==t.isInverse()||this.isBold()!==t.isBold()||this.isUnderline()!==t.isUnderline())return!1;if(this.isUnderline()){if(this.getUnderlineStyle()!==t.getUnderlineStyle())return!1;let r=this.isUnderlineColorDefault(),s=t.isUnderlineColorDefault();if(!(r&&s)&&(r!==s||this.getUnderlineColor()!==t.getUnderlineColor()||this.getUnderlineColorMode()!==t.getUnderlineColorMode()))return!1}return!(this.isOverline()!==t.isOverline()||this.isBlink()!==t.isBlink()||this.isInvisible()!==t.isInvisible()||this.isItalic()!==t.isItalic()||this.isDim()!==t.isDim()||this.isStrikethrough()!==t.isStrikethrough())}};var J0=class extends W{constructor(t,r,s){super();this._renderCallback=t;this._coreBrowserService=r;this._optionsService=s;this._intervalDuration=0;this._blinkOn=!0;this._needsBlinkInViewport=!1;this._isViewportVisible=!0;this._register(this._optionsService.onSpecificOptionChange("blinkIntervalDuration",a=>{this.setIntervalDuration(a)})),this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration),this._register(M(()=>this._clearInterval()))}get isBlinkOn(){return this._blinkOn}get isEnabled(){return this._intervalDuration>0}setNeedsBlinkInViewport(t){this._needsBlinkInViewport!==t&&(this._needsBlinkInViewport=t,this._updateIntervalState())}setViewportVisible(t){this._isViewportVisible!==t&&(this._isViewportVisible=t,this._updateIntervalState())}setIntervalDuration(t){t!==this._intervalDuration&&(this._intervalDuration=t,this._clearInterval(),this._updateIntervalState())}_updateIntervalState(){if(this._intervalDuration>0&&this._needsBlinkInViewport&&this._isViewportVisible){if(this._interval!==void 0)return;let r=this._blinkOn;this._blinkOn=!0,this._interval=this._coreBrowserService.window.setInterval(()=>{this._blinkOn=!this._blinkOn,this._renderCallback()},this._intervalDuration),r||this._renderCallback();return}this._clearInterval(),this._blinkOn||(this._blinkOn=!0,this._renderCallback())}_clearInterval(){this._interval!==void 0&&(this._coreBrowserService.window.clearInterval(this._interval),this._interval=void 0)}};var te=new Float32Array([2,0,0,0,0,-2,0,0,0,0,1,0,-1,1,0,1]);function ie(i,e,t,r){let s=g(i.createProgram());if(i.attachShader(s,g(at(i,i.VERTEX_SHADER,e,r))),i.attachShader(s,g(at(i,i.FRAGMENT_SHADER,t,r))),i.linkProgram(s),i.getProgramParameter(s,i.LINK_STATUS))return s;r.error(i.getProgramInfoLog(s)),i.deleteProgram(s)}function at(i,e,t,r){let s=g(i.createShader(e));if(i.shaderSource(s,t),i.compileShader(s),i.getShaderParameter(s,i.COMPILE_STATUS))return s;r.error(i.getShaderInfoLog(s)),i.deleteShader(s)}function ot(i,e){let t=Math.min(i.length*2,e),r=new Float32Array(t);for(let s=0;sFt(i))}var R0=class{constructor(){this._disposables=new Set;this._isDisposed=!1}get isDisposed(){return this._isDisposed}add(e){return this._isDisposed?e.dispose():this._disposables.add(e),e}dispose(){if(!this._isDisposed){this._isDisposed=!0;for(let e of this._disposables)e.dispose();this._disposables.clear()}}clear(){for(let e of this._disposables)e.dispose();this._disposables.clear()}},W=class{constructor(){this._store=new R0}dispose(){this._store.dispose()}_register(e){return this._store.add(e)}};W.None=Object.freeze({dispose(){}});var m0=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,this._value?.dispose(),this._value=void 0}};var Pe=!!(typeof process<"u"&&"title"in process&&(typeof navigator>"u"||navigator.userAgent.startsWith("Node.js/"))),O0=Pe?"node":navigator.userAgent,ce=Pe?"node":navigator.platform,Be=O0.includes("Firefox"),n1=O0.includes("Chrome"),we=O0.includes("Edge"),ue=/^((?!chrome|android).)*safari/i.test(O0);function He(){if(!ue)return 0;let i=O0.match(/Version\/(\d+)/);return i===null||i.length<2?0:parseInt(i[1],10)}var a1=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(ce),o1=["Windows","Win16","Win32","WinCE"].includes(ce),T1=ce.indexOf("Linux")>=0,l1=/\bCrOS\b/.test(O0);var P=0,B=0,w=0,x=0,K={css:"#00000000",rgba:0},Z;(r=>{function i(s,a,o,n){return n!==void 0?`#${E0(s)}${E0(a)}${E0(o)}${E0(n)}`:`#${E0(s)}${E0(a)}${E0(o)}`}r.toCss=i;function e(s,a,o,n=255){return(s<<24|a<<16|o<<8|n)>>>0}r.toRgba=e;function t(s,a,o,n){return{css:r.toCss(s,a,o,n),rgba:r.toRgba(s,a,o,n)}}r.toColor=t})(Z||={});var I0;(n=>{function i(L,T){if(x=(T.rgba&255)/255,x===1)return{css:T.css,rgba:T.rgba};let h=T.rgba>>24&255,d=T.rgba>>16&255,_=T.rgba>>8&255,p=L.rgba>>24&255,c=L.rgba>>16&255,u=L.rgba>>8&255;P=p+Math.round((h-p)*x),B=c+Math.round((d-c)*x),w=u+Math.round((_-u)*x);let C=Z.toCss(P,B,w),Q=Z.toRgba(P,B,w);return{css:C,rgba:Q}}n.blend=i;function e(L){return(L.rgba&255)===255}n.isOpaque=e;function t(L,T,h){let d=d0.ensureContrastRatio(L.rgba,T.rgba,h);if(d)return Z.toColor(d>>24&255,d>>16&255,d>>8&255)}n.ensureContrastRatio=t;function r(L){let T=(L.rgba|255)>>>0;return[P,B,w]=d0.toChannels(T),{css:Z.toCss(P,B,w),rgba:T}}n.opaque=r;function s(L,T){return x=Math.round(T*255),[P,B,w]=d0.toChannels(L.rgba),{css:Z.toCss(P,B,w,x),rgba:Z.toRgba(P,B,w,x)}}n.opacity=s;function a(L,T){return x=L.rgba&255,s(L,x*T/255)}n.multiplyOpacity=a;function o(L){return[L.rgba>>24&255,L.rgba>>16&255,L.rgba>>8&255]}n.toColorRGB=o})(I0||={});var Mt;(r=>{let i,e;try{let s=document.createElement("canvas");s.width=1,s.height=1;let a=s.getContext("2d",{willReadFrequently:!0});a&&(i=a,i.globalCompositeOperation="copy",e=i.createLinearGradient(0,0,1,1))}catch{}function t(s){if(s.match(/#[\da-f]{3,8}/i))switch(s.length){case 4:return P=parseInt(s.slice(1,2).repeat(2),16),B=parseInt(s.slice(2,3).repeat(2),16),w=parseInt(s.slice(3,4).repeat(2),16),Z.toColor(P,B,w);case 5:return P=parseInt(s.slice(1,2).repeat(2),16),B=parseInt(s.slice(2,3).repeat(2),16),w=parseInt(s.slice(3,4).repeat(2),16),x=parseInt(s.slice(4,5).repeat(2),16),Z.toColor(P,B,w,x);case 7:return{css:s,rgba:(parseInt(s.slice(1),16)<<8|255)>>>0};case 9:return{css:s,rgba:parseInt(s.slice(1),16)>>>0}}let a=s.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(a)return P=parseInt(a[1],10),B=parseInt(a[2],10),w=parseInt(a[3],10),x=Math.round((a[5]===void 0?1:parseFloat(a[5]))*255),Z.toColor(P,B,w,x);if(s==="transparent")return{css:"transparent",rgba:0};if(!i||!e)throw new Error("css.toColor: Unsupported css format");if(i.fillStyle=e,i.fillStyle=s,typeof i.fillStyle!="string")throw new Error("css.toColor: Unsupported css format");if(i.fillRect(0,0,1,1),[P,B,w,x]=i.getImageData(0,0,1,1).data,x!==255)throw new Error("css.toColor: Unsupported css format");return{rgba:Z.toRgba(P,B,w,x),css:s}}r.toColor=t})(Mt||={});var V;(t=>{function i(r){return e(r>>16&255,r>>8&255,r&255)}t.relativeLuminance=i;function e(r,s,a){let o=r/255,n=s/255,L=a/255,T=o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4),h=n<=.03928?n/12.92:Math.pow((n+.055)/1.055,2.4),d=L<=.03928?L/12.92:Math.pow((L+.055)/1.055,2.4);return T*.2126+h*.7152+d*.0722}t.relativeLuminance2=e})(V||={});var d0;(a=>{function i(o,n){if(x=(n&255)/255,x===1)return n;let L=n>>24&255,T=n>>16&255,h=n>>8&255,d=o>>24&255,_=o>>16&255,p=o>>8&255;return P=d+Math.round((L-d)*x),B=_+Math.round((T-_)*x),w=p+Math.round((h-p)*x),Z.toRgba(P,B,w)}a.blend=i;function e(o,n,L){let T=V.relativeLuminance(o>>8),h=V.relativeLuminance(n>>8);if(h0(T,h)>8));if(u>8));return u>Q?c:C}return c}let _=r(o,n,L),p=h0(T,V.relativeLuminance(_>>8));if(p>8));return p>u?_:c}return _}}a.ensureContrastRatio=e;function t(o,n,L){let T=o>>24&255,h=o>>16&255,d=o>>8&255,_=n>>24&255,p=n>>16&255,c=n>>8&255,u=h0(V.relativeLuminance2(_,p,c),V.relativeLuminance2(T,h,d));for(;u0||p>0||c>0);)_-=Math.max(0,Math.ceil(_*.1)),p-=Math.max(0,Math.ceil(p*.1)),c-=Math.max(0,Math.ceil(c*.1)),u=h0(V.relativeLuminance2(_,p,c),V.relativeLuminance2(T,h,d));return(_<<24|p<<16|c<<8|255)>>>0}a.reduceLuminance=t;function r(o,n,L){let T=o>>24&255,h=o>>16&255,d=o>>8&255,_=n>>24&255,p=n>>16&255,c=n>>8&255,u=h0(V.relativeLuminance2(_,p,c),V.relativeLuminance2(T,h,d));for(;u>>0}a.increaseLuminance=r;function s(o){return[o>>24&255,o>>16&255,o>>8&255,o&255]}a.toChannels=s})(d0||={});function E0(i){let e=i.toString(16);return e.length<2?"0"+e:e}function h0(i,e){return i=128512&&i<=128591||i>=127744&&i<=128511||i>=128640&&i<=128767||i>=9728&&i<=9983||i>=9984&&i<=10175||i>=65024&&i<=65039||i>=129280&&i<=129535||i>=127462&&i<=127487}function Ze(i,e,t,r){return e===1&&t>Math.ceil(r*1.5)&&i!==void 0&&i>255&&!yt(i)&&!W0(i)&&!At(i)}function V0(i){return W0(i)||gt(i)}function Ue(){return{css:{canvas:U0(),cell:U0()},device:{canvas:U0(),cell:U0(),char:{width:0,height:0,left:0,top:0}}}}function U0(){return{width:0,height:0}}function We(i,e,t=0){return(i-(Math.round(e)*2-t))%(Math.round(e)*2)}var l;(C=>(C.LINE_H=Object.freeze({type:2,data:"M0,.5 L1,.5",strokeWidth:1}),C.LINE_V=Object.freeze({type:2,data:"M.5,0 L.5,1",strokeWidth:1}),C.FADE_RIGHT=Object.freeze({type:2,data:"M0,.5 L.28,.5 M.32,.5 L.52,.5 M.60,.5 L.72,.5 M.84,.5 L.90,.5",strokeWidth:1}),C.FADE_LEFT=Object.freeze({type:2,data:"M.10,.5 L.16,.5 M.28,.5 L.40,.5 M.48,.5 L.68,.5 M.72,.5 L1,.5",strokeWidth:1}),C.FADE_DOWN=Object.freeze({type:2,data:"M.5,0 L.5,.28 M.5,.32 L.5,.52 M.5,.60 L.5,.72 M.5,.84 L.5,.90",strokeWidth:1}),C.FADE_UP=Object.freeze({type:2,data:"M.5,.10 L.5,.16 M.5,.28 L.5,.40 M.5,.48 L.5,.68 M.5,.72 L.5,1",strokeWidth:1}),C.CURVE_DOWN_RIGHT=Object.freeze({type:2,data:(Q,m)=>`M.5,1 L.5,${.5+m/.15*.5} C.5,${.5+m/.15*.5},.5,.5,1,.5`,strokeWidth:1}),C.CURVE_DOWN_LEFT=Object.freeze({type:2,data:(Q,m)=>`M.5,1 L.5,${.5+m/.15*.5} C.5,${.5+m/.15*.5},.5,.5,0,.5`,strokeWidth:1}),C.CURVE_UP_RIGHT=Object.freeze({type:2,data:(Q,m)=>`M.5,0 L.5,${.5-m/.15*.5} C.5,${.5-m/.15*.5},.5,.5,1,.5`,strokeWidth:1}),C.CURVE_UP_LEFT=Object.freeze({type:2,data:(Q,m)=>`M.5,0 L.5,${.5-m/.15*.5} C.5,${.5-m/.15*.5},.5,.5,0,.5`,strokeWidth:1}),C.NODE_FILL=Object.freeze({type:3,data:"M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5"}),C.NODE_STROKE=Object.freeze({type:3,data:"M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5",strokeWidth:1}),C.NODE_LINE_RIGHT=Object.freeze({type:3,data:"M1,.5 L.85,.5",strokeWidth:1}),C.NODE_LINE_LEFT=Object.freeze({type:3,data:"M0,.5 L.15,.5",strokeWidth:1}),C.NODE_LINE_DOWN=Object.freeze({type:3,data:"M.5,1 L.5,.7",strokeWidth:1}),C.NODE_LINE_UP=Object.freeze({type:3,data:"M.5,0 L.5,.3",strokeWidth:1})))(l||={});var Ve={"\u2500":{type:2,data:"M0,.5 L1,.5",strokeWidth:1},"\u2501":{type:2,data:"M0,.5 L1,.5",strokeWidth:3},"\u2502":{type:2,data:"M.5,0 L.5,1",strokeWidth:1},"\u2503":{type:2,data:"M.5,0 L.5,1",strokeWidth:3},"\u2504":{type:2,data:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",strokeWidth:1},"\u2505":{type:2,data:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",strokeWidth:3},"\u2506":{type:2,data:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",strokeWidth:1},"\u2507":{type:2,data:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",strokeWidth:3},"\u2508":{type:2,data:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",strokeWidth:1},"\u2509":{type:2,data:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",strokeWidth:3},"\u250A":{type:2,data:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",strokeWidth:1},"\u250B":{type:2,data:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",strokeWidth:3},"\u250C":{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},"\u250D":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u250E":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u250F":{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3},"\u2510":{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},"\u2511":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u2512":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2513":{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3},"\u2514":{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},"\u2515":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u2516":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2517":{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3},"\u2518":{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},"\u2519":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u251A":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u251B":{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3},"\u251C":{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:1},"\u251D":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u251E":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u251F":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2520":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"\u2521":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"\u2522":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"\u2523":{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:3},"\u2524":{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:1},"\u2525":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u2526":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2527":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2528":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"\u2529":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"\u252A":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"\u252B":{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:3},"\u252C":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:1},"\u252D":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u252E":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u252F":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"\u2530":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2531":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"\u2532":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"\u2533":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:3},"\u2534":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,0",strokeWidth:1},"\u2535":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u2536":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u2537":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"\u2538":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2539":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"\u253A":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"\u253B":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,0",strokeWidth:3},"\u253C":{type:2,data:"M0,.5 L1,.5 M.5,0 L.5,1",strokeWidth:1},"\u253D":[{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u253E":[{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u253F":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"\u2540":[{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2541":[{type:2,data:"M.5,.5 L.5,0 M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2542":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"\u2543":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"\u2544":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"\u2545":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"\u2546":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"\u2547":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0 M0,.5 L1,.5",strokeWidth:3}],"\u2548":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:3}],"\u2549":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:3}],"\u254A":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:3}],"\u254B":{type:2,data:"M0,.5 L1,.5 M.5,0 L.5,1",strokeWidth:3},"\u254C":{type:2,data:"M.1,.5 L.4,.5 M.6,.5 L.9,.5",strokeWidth:1},"\u254D":{type:2,data:"M.1,.5 L.4,.5 M.6,.5 L.9,.5",strokeWidth:3},"\u254E":{type:2,data:"M.5,.1 L.5,.4 M.5,.6 L.5,.9",strokeWidth:1},"\u254F":{type:2,data:"M.5,.1 L.5,.4 M.5,.6 L.5,.9",strokeWidth:3},"\u2550":{type:2,data:(i,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u2551":{type:2,data:(i,e)=>`M${.5-i},0 L${.5-i},1 M${.5+i},0 L${.5+i},1`,strokeWidth:1},"\u2552":{type:2,data:(i,e)=>`M.5,1 L.5,${.5-e} L1,${.5-e} M.5,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u2553":{type:2,data:(i,e)=>`M${.5-i},1 L${.5-i},.5 L1,.5 M${.5+i},.5 L${.5+i},1`,strokeWidth:1},"\u2554":{type:2,data:(i,e)=>`M1,${.5-e} L${.5-i},${.5-e} L${.5-i},1 M1,${.5+e} L${.5+i},${.5+e} L${.5+i},1`,strokeWidth:1},"\u2555":{type:2,data:(i,e)=>`M0,${.5-e} L.5,${.5-e} L.5,1 M0,${.5+e} L.5,${.5+e}`,strokeWidth:1},"\u2556":{type:2,data:(i,e)=>`M${.5+i},1 L${.5+i},.5 L0,.5 M${.5-i},.5 L${.5-i},1`,strokeWidth:1},"\u2557":{type:2,data:(i,e)=>`M0,${.5+e} L${.5-i},${.5+e} L${.5-i},1 M0,${.5-e} L${.5+i},${.5-e} L${.5+i},1`,strokeWidth:1},"\u2558":{type:2,data:(i,e)=>`M.5,0 L.5,${.5+e} L1,${.5+e} M.5,${.5-e} L1,${.5-e}`,strokeWidth:1},"\u2559":{type:2,data:(i,e)=>`M1,.5 L${.5-i},.5 L${.5-i},0 M${.5+i},.5 L${.5+i},0`,strokeWidth:1},"\u255A":{type:2,data:(i,e)=>`M1,${.5-e} L${.5+i},${.5-e} L${.5+i},0 M1,${.5+e} L${.5-i},${.5+e} L${.5-i},0`,strokeWidth:1},"\u255B":{type:2,data:(i,e)=>`M0,${.5+e} L.5,${.5+e} L.5,0 M0,${.5-e} L.5,${.5-e}`,strokeWidth:1},"\u255C":{type:2,data:(i,e)=>`M0,.5 L${.5+i},.5 L${.5+i},0 M${.5-i},.5 L${.5-i},0`,strokeWidth:1},"\u255D":{type:2,data:(i,e)=>`M0,${.5-e} L${.5-i},${.5-e} L${.5-i},0 M0,${.5+e} L${.5+i},${.5+e} L${.5+i},0`,strokeWidth:1},"\u255E":{type:2,data:(i,e)=>`M.5,0 L.5,1 M.5,${.5-e} L1,${.5-e} M.5,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u255F":{type:2,data:(i,e)=>`M${.5-i},0 L${.5-i},1 M${.5+i},0 L${.5+i},1 M${.5+i},.5 L1,.5`,strokeWidth:1},"\u2560":{type:2,data:(i,e)=>`M${.5-i},0 L${.5-i},1 M1,${.5+e} L${.5+i},${.5+e} L${.5+i},1 M1,${.5-e} L${.5+i},${.5-e} L${.5+i},0`,strokeWidth:1},"\u2561":{type:2,data:(i,e)=>`M.5,0 L.5,1 M0,${.5-e} L.5,${.5-e} M0,${.5+e} L.5,${.5+e}`,strokeWidth:1},"\u2562":{type:2,data:(i,e)=>`M0,.5 L${.5-i},.5 M${.5-i},0 L${.5-i},1 M${.5+i},0 L${.5+i},1`,strokeWidth:1},"\u2563":{type:2,data:(i,e)=>`M${.5+i},0 L${.5+i},1 M0,${.5+e} L${.5-i},${.5+e} L${.5-i},1 M0,${.5-e} L${.5-i},${.5-e} L${.5-i},0`,strokeWidth:1},"\u2564":{type:2,data:(i,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e} M.5,${.5+e} L.5,1`,strokeWidth:1},"\u2565":{type:2,data:(i,e)=>`M0,.5 L1,.5 M${.5-i},.5 L${.5-i},1 M${.5+i},.5 L${.5+i},1`,strokeWidth:1},"\u2566":{type:2,data:(i,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L${.5-i},${.5+e} L${.5-i},1 M1,${.5+e} L${.5+i},${.5+e} L${.5+i},1`,strokeWidth:1},"\u2567":{type:2,data:(i,e)=>`M.5,0 L.5,${.5-e} M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u2568":{type:2,data:(i,e)=>`M0,.5 L1,.5 M${.5-i},.5 L${.5-i},0 M${.5+i},.5 L${.5+i},0`,strokeWidth:1},"\u2569":{type:2,data:(i,e)=>`M0,${.5+e} L1,${.5+e} M0,${.5-e} L${.5-i},${.5-e} L${.5-i},0 M1,${.5-e} L${.5+i},${.5-e} L${.5+i},0`,strokeWidth:1},"\u256A":{type:2,data:(i,e)=>`M.5,0 L.5,1 M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u256B":{type:2,data:(i,e)=>`M0,.5 L1,.5 M${.5-i},0 L${.5-i},1 M${.5+i},0 L${.5+i},1`,strokeWidth:1},"\u256C":{type:2,data:(i,e)=>`M0,${.5+e} L${.5-i},${.5+e} L${.5-i},1 M1,${.5+e} L${.5+i},${.5+e} L${.5+i},1 M0,${.5-e} L${.5-i},${.5-e} L${.5-i},0 M1,${.5-e} L${.5+i},${.5-e} L${.5+i},0`,strokeWidth:1},"\u256D":{type:2,data:(i,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,1,.5`,strokeWidth:1},"\u256E":{type:2,data:(i,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,0,.5`,strokeWidth:1},"\u256F":{type:2,data:(i,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,0,.5`,strokeWidth:1},"\u2570":{type:2,data:(i,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,1,.5`,strokeWidth:1},"\u2571":{type:2,data:"M1,0 L0,1",strokeWidth:1},"\u2572":{type:2,data:"M0,0 L1,1",strokeWidth:1},"\u2573":{type:2,data:"M1,0 L0,1 M0,0 L1,1",strokeWidth:1},"\u2574":{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},"\u2575":{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},"\u2576":{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},"\u2577":{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},"\u2578":{type:2,data:"M.5,.5 L0,.5",strokeWidth:3},"\u2579":{type:2,data:"M.5,.5 L.5,0",strokeWidth:3},"\u257A":{type:2,data:"M.5,.5 L1,.5",strokeWidth:3},"\u257B":{type:2,data:"M.5,.5 L.5,1",strokeWidth:3},"\u257C":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u257D":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u257E":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u257F":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2580":{type:0,data:[{x:0,y:0,w:8,h:4}]},"\u2581":{type:0,data:[{x:0,y:7,w:8,h:1}]},"\u2582":{type:0,data:[{x:0,y:6,w:8,h:2}]},"\u2583":{type:0,data:[{x:0,y:5,w:8,h:3}]},"\u2584":{type:0,data:[{x:0,y:4,w:8,h:4}]},"\u2585":{type:0,data:[{x:0,y:3,w:8,h:5}]},"\u2586":{type:0,data:[{x:0,y:2,w:8,h:6}]},"\u2587":{type:0,data:[{x:0,y:1,w:8,h:7}]},"\u2588":{type:0,data:[{x:0,y:0,w:8,h:8}]},"\u2589":{type:0,data:[{x:0,y:0,w:7,h:8}]},"\u258A":{type:0,data:[{x:0,y:0,w:6,h:8}]},"\u258B":{type:0,data:[{x:0,y:0,w:5,h:8}]},"\u258C":{type:0,data:[{x:0,y:0,w:4,h:8}]},"\u258D":{type:0,data:[{x:0,y:0,w:3,h:8}]},"\u258E":{type:0,data:[{x:0,y:0,w:2,h:8}]},"\u258F":{type:0,data:[{x:0,y:0,w:1,h:8}]},"\u2590":{type:0,data:[{x:4,y:0,w:4,h:8}]},"\u2591":{type:1,data:[[1,0],[0,0]]},"\u2592":{type:1,data:[[1,0],[0,1]]},"\u2593":{type:1,data:[[1,1],[1,0]]},"\u2594":{type:0,data:[{x:0,y:0,w:8,h:1}]},"\u2595":{type:0,data:[{x:7,y:0,w:1,h:8}]},"\u2596":{type:0,data:[{x:0,y:4,w:4,h:4}]},"\u2597":{type:0,data:[{x:4,y:4,w:4,h:4}]},"\u2598":{type:0,data:[{x:0,y:0,w:4,h:4}]},"\u2599":{type:0,data:[{x:0,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}]},"\u259A":{type:0,data:[{x:0,y:0,w:4,h:4},{x:4,y:4,w:4,h:4}]},"\u259B":{type:0,data:[{x:0,y:0,w:4,h:8},{x:4,y:0,w:4,h:4}]},"\u259C":{type:0,data:[{x:0,y:0,w:8,h:4},{x:4,y:0,w:4,h:8}]},"\u259D":{type:0,data:[{x:4,y:0,w:4,h:4}]},"\u259E":{type:0,data:[{x:4,y:0,w:4,h:4},{x:0,y:4,w:4,h:4}]},"\u259F":{type:0,data:[{x:4,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}]},"\uE0A0":{type:5,data:{d:"M.3,1 L.03,1 L.03,.88 C.03,.82,.06,.78,.11,.73 C.15,.7,.2,.68,.28,.65 L.43,.6 C.49,.58,.53,.56,.56,.53 C.59,.5,.6,.47,.6,.43 L.6,.27 L.4,.27 L.69,.1 L.98,.27 L.78,.27 L.78,.46 C.78,.52,.76,.56,.72,.61 C.68,.66,.63,.67,.56,.7 L.48,.72 C.42,.74,.38,.76,.35,.78 C.32,.8,.31,.84,.31,.88 L.31,1 M.3,.5 L.03,.59 L.03,.09 L.3,.09 L.3,.655",type:0}},"\uE0A1":{type:5,data:{d:"M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.355,.03 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5",type:0}},"\uE0A2":{type:5,data:{d:"M.25,.94 C.16,.94,.11,.92,.11,.87 L.11,.53 C.11,.48,.15,.455,.23,.45 L.23,.3 C.23,.25,.26,.22,.31,.19 C.36,.16,.43,.15,.51,.15 C.59,.15,.66,.16,.71,.19 C.77,.22,.79,.26,.79,.3 L.79,.45 C.87,.45,.91,.48,.91,.53 L.91,.87 C.91,.92,.86,.94,.77,.94 L.24,.94 M.53,.2 C.49,.2,.45,.21,.42,.23 C.39,.25,.38,.27,.38,.3 L.38,.45 L.68,.45 L.68,.3 C.68,.27,.67,.25,.64,.23 C.61,.21,.58,.2,.53,.2 M.58,.82 L.58,.66 C.63,.65,.65,.63,.65,.6 C.65,.58,.64,.57,.61,.56 C.58,.55,.56,.54,.52,.54 C.48,.54,.46,.55,.43,.56 C.4,.57,.39,.59,.39,.6 C.39,.63,.41,.64,.46,.66 L.46,.82 L.57,.82",type:0}},"\uE0A3":{type:5,data:{d:"M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.7,.03 L.7,.1 L.355,.1 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5",type:0}},"\uE0B0":{type:5,data:{d:"M0,0 L1,.5 L0,1",type:0,rightPadding:2}},"\uE0B1":{type:5,data:{d:"M-1,-.5 L1,.5 L-1,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0B2":{type:5,data:{d:"M1,0 L0,.5 L1,1",type:0,leftPadding:2}},"\uE0B3":{type:5,data:{d:"M2,-.5 L0,.5 L2,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0B4":{type:5,data:{d:"M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0",type:0,rightPadding:1}},"\uE0B5":{type:5,data:{d:"M.2,1 C.422,1,.8,.826,.78,.5 C.8,.174,0.422,0,.2,0",type:1,rightPadding:1}},"\uE0B6":{type:5,data:{d:"M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0",type:0,leftPadding:1}},"\uE0B7":{type:5,data:{d:"M.8,1 C0.578,1,0.2,.826,.22,.5 C0.2,0.174,0.578,0,0.8,0",type:1,leftPadding:1}},"\uE0B8":{type:5,data:{d:"M-.5,-.5 L1.5,1.5 L-.5,1.5",type:0}},"\uE0B9":{type:5,data:{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0BA":{type:5,data:{d:"M1.5,-.5 L-.5,1.5 L1.5,1.5",type:0}},"\uE0BB":{type:5,data:{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0BC":{type:5,data:{d:"M1.5,-.5 L-.5,1.5 L-.5,-.5",type:0}},"\uE0BD":{type:5,data:{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0BE":{type:5,data:{d:"M-.5,-.5 L1.5,1.5 L1.5,-.5",type:0}},"\uE0BF":{type:5,data:{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0C0":{type:5,data:{d:"M0.8069,0.6075 Q0.7991,0.612,0.7815,0.6218 T0.7542,0.6369 T0.7282,0.6508 T0.7016,0.6644 T0.6778,0.6758 T0.6551,0.6854 T0.637,0.6911 T0.622,0.6936 Q0.6186,0.6936,0.6128,0.6942 T0.6018,0.6952 T0.5894,0.6956 T0.5757,0.6952 T0.561,0.693 T0.5451,0.6879 Q0.5342,0.6834,0.5193,0.6756 T0.4925,0.6614 T0.4673,0.6485 T0.4436,0.6389 T0.4239,0.6363 T0.4089,0.6422 Q0.4045,0.6467,0.3958,0.6554 T0.3843,0.6669 T0.3748,0.6746 T0.3613,0.683 T0.3442,0.6903 L0.3499,0.6956 Q0.3584,0.7042,0.3679,0.7177 T0.3836,0.7403 T0.4019,0.7583 T0.4295,0.7703 Q0.4688,0.7785,0.5203,0.7605 Q0.4902,0.7817,0.449,0.7911 T0.3696,0.8001 Q0.3567,0.7997,0.341,0.7942 T0.3111,0.7813 T0.2805,0.766 T0.2493,0.753 T0.218,0.7472 T0.187,0.754 Q0.1762,0.7589,0.1653,0.7681 T0.1453,0.7895 T0.1308,0.818 T0.1264,0.8507 L0.1264,0.858 Q0.1453,0.8462,0.1607,0.8433 T0.1899,0.8452 T0.2226,0.8625 Q0.2344,0.8707,0.2485,0.8752 T0.2813,0.8831 T0.3089,0.889 Q0.2981,0.8915,0.2846,0.8956 T0.2603,0.9037 T0.2368,0.9115 T0.2129,0.9174 T0.1894,0.9194 T0.1653,0.9155 Q0.1504,0.9115,0.1328,0.9196 T0.0986,0.9421 T0.0655,0.9708 T0.0317,0.9943 T0,1 Q0.001,0.94,0.0017,0.6508 T0.0024,0.2322 Q0.002,0.2109,0.0007,0.1379 T0,0.0249 Q0.0058,0.0253,0.009,0.0257 T0.0156,0.0288 T0.0203,0.0333 T0.0264,0.0422 T0.0349,0.0539 Q0.044,0.0657,0.0542,0.072 T0.0752,0.0789 T0.096,0.0775 T0.1175,0.0694 T0.1379,0.0573 T0.1579,0.0432 Q0.2141,0,0.2673,0.0318 Q0.2588,0.0318,0.2517,0.033 T0.239,0.0361 T0.2276,0.0424 T0.218,0.0504 T0.2085,0.0612 T0.1997,0.0732 T0.1899,0.0875 T0.1795,0.1028 Q0.1748,0.1093,0.1507,0.1234 T0.1186,0.1448 Q0.0999,0.1612,0.1108,0.1783 Q0.1162,0.1877,0.1238,0.1928 T0.1404,0.1979 T0.158,0.1967 T0.1775,0.1895 T0.1963,0.1791 T0.2151,0.1663 T0.2314,0.1534 T0.2458,0.141 T0.2561,0.1322 Q0.2815,0.1106,0.3017,0.101 T0.344,0.0926 T0.3919,0.1077 Q0.4099,0.1175,0.4265,0.1206 T0.4588,0.1202 T0.488,0.1098 T0.5176,0.0914 Q0.5071,0.1032,0.4963,0.1122 T0.4722,0.1285 T0.4497,0.1401 T0.4223,0.1518 T0.3943,0.1636 Q0.374,0.1726,0.3623,0.1863 T0.3482,0.2162 Q0.3445,0.2403,0.3608,0.2605 T0.4072,0.2876 Q0.4194,0.2909,0.4326,0.2899 T0.457,0.2856 T0.4805,0.2764 T0.5032,0.264 T0.5256,0.2497 T0.5478,0.2352 T0.5698,0.2222 T0.5921,0.2118 Q0.6982,0.173,0.81,0.2436 Q0.833,0.2583,0.8496,0.2674 T0.887,0.2846 T0.925,0.294 T0.9617,0.2911 T1,0.2746 Q0.9817,0.2946,0.96,0.3056 T0.9175,0.3191 T0.8731,0.3209 T0.8277,0.3164 T0.7818,0.3109 T0.7359,0.3101 T0.6907,0.3197 T0.647,0.3448 Q0.6443,0.3472,0.6333,0.3562 T0.6194,0.3674 T0.6076,0.3766 T0.594,0.386 T0.5815,0.3931 T0.5661,0.4007 T0.5501,0.4064 L0.5556,0.4121 Q0.5627,0.4198,0.5722,0.4251 T0.5926,0.4337 T0.6131,0.439 T0.6343,0.4431 T0.6521,0.4468 Q0.6762,0.4529,0.7056,0.4463 Q0.6772,0.4623,0.6543,0.471 T0.604,0.4814 Q0.5962,0.4818,0.5815,0.4825 T0.5591,0.4835 T0.5393,0.4857 T0.519,0.4908 T0.5007,0.4996 T0.4812,0.5141 T0.4627,0.5353 L0.4549,0.5451 L0.4661,0.543 Q0.4759,0.541,0.4909,0.55 T0.5315,0.5796 T0.5684,0.6083 Q0.5799,0.6161,0.5915,0.6212 T0.6181,0.6308 L0.6331,0.6353 Q0.6514,0.6418,0.7004,0.633 T0.8069,0.6075 Z",type:0}},"\uE0C1":{type:5,data:{d:"M0.011,0.0469 Q0.0152,0.0412,0.02,0.0361 T0.0292,0.0281 T0.0365,0.0253 Q0.0426,0.0257,0.0461,0.0263 T0.0534,0.03 T0.0582,0.0345 T0.0645,0.0434 T0.0723,0.0546 Q0.0807,0.0653,0.0897,0.0712 T0.1081,0.0775 T0.1255,0.0765 T0.1438,0.0695 T0.1605,0.0593 T0.1772,0.0473 Q0.2388,0,0.2927,0.0326 L0.2614,0.0767 L0.2581,0.0767 Q0.2494,0.0767,0.2444,0.0773 T0.2359,0.0789 T0.2291,0.0842 T0.2233,0.0922 T0.2146,0.1054 T0.2026,0.1236 Q0.1943,0.1354,0.1843,0.1444 T0.1675,0.1574 T0.1526,0.1654 T0.1429,0.1701 Q0.1436,0.1717,0.1446,0.1733 Q0.1504,0.1823,0.1581,0.1866 T0.1733,0.1915 T0.1909,0.1884 T0.2085,0.1809 T0.2265,0.169 T0.2427,0.1564 T0.2575,0.1436 T0.2688,0.1338 Q0.2969,0.1101,0.3195,0.0995 T0.3643,0.0901 T0.4114,0.1048 Q0.4279,0.1138,0.4426,0.1168 T0.4705,0.117 T0.4945,0.1089 T0.5186,0.0942 L0.5211,0.0926 L0.5289,0.0922 Q0.5411,0.095,0.5189,0.1203 Q0.505,0.1358,0.4913,0.1474 T0.4634,0.167 T0.4389,0.1796 T0.4121,0.1909 T0.3866,0.2011 Q0.3782,0.2047,0.3727,0.2092 Q0.3717,0.2117,0.3714,0.2141 Q0.3695,0.2263,0.3751,0.2388 T0.394,0.2616 T0.4253,0.2761 Q0.4398,0.2785,0.4513,0.2779 T0.4756,0.2724 T0.4987,0.2616 T0.5216,0.2476 T0.5453,0.2321 T0.5703,0.217 T0.5976,0.2043 Q0.7015,0.166,0.8099,0.2345 Q0.8361,0.2512,0.8572,0.2616 T0.9006,0.2779 T0.9432,0.281 T0.9819,0.2663 L0.9887,0.2671 Q1,0.272,0.9768,0.2969 Q0.9558,0.3193,0.9319,0.333 T0.8858,0.3513 T0.8391,0.3569 T0.793,0.3546 T0.7485,0.3497 T0.7065,0.3473 T0.6681,0.3522 T0.6344,0.3699 Q0.5963,0.4005,0.5847,0.4082 Q0.595,0.4139,0.6079,0.4174 T0.6367,0.4237 T0.6586,0.4278 Q0.6793,0.4331,0.7044,0.4278 Q0.7057,0.4278,0.707,0.4274 Q0.7067,0.429,0.7062,0.4313 T0.7036,0.4398 T0.6993,0.4513 T0.6931,0.4619 T0.6851,0.4698 Q0.6573,0.4857,0.6336,0.4947 T0.5831,0.5053 Q0.577,0.5057,0.5615,0.5065 T0.5394,0.508 T0.5221,0.51 T0.5042,0.5139 T0.4905,0.5204 Q0.5008,0.5232,0.515,0.5328 T0.549,0.5585 T0.5799,0.5824 Q0.5905,0.5897,0.6013,0.5946 T0.6262,0.6036 T0.6409,0.6081 Q0.6567,0.6134,0.7035,0.605 T0.8028,0.5816 L0.7974,0.5922 T0.7867,0.6134 L0.7812,0.624 Q0.7799,0.6248,0.7786,0.6252 Q0.7709,0.6297,0.7573,0.6372 T0.7346,0.6499 T0.7123,0.6621 T0.6897,0.6741 T0.6686,0.6847 T0.648,0.6941 T0.6297,0.7013 T0.6129,0.7062 T0.5995,0.7076 Q0.5992,0.7076,0.5831,0.7092 T0.5529,0.7092 T0.525,0.7019 Q0.5105,0.6958,0.4824,0.6811 T0.4356,0.6586 T0.4079,0.6533 Q0.3872,0.6741,0.3827,0.6782 Q0.3872,0.6835,0.3934,0.6929 T0.4037,0.7078 T0.4142,0.7198 T0.4284,0.7304 T0.4466,0.7365 Q0.4824,0.7443,0.5292,0.728 L0.5292,0.7294 T0.529,0.7333 T0.5284,0.7392 T0.5266,0.7463 T0.5236,0.7539 T0.5186,0.7612 T0.5111,0.7679 Q0.5086,0.77,0.5056,0.7716 Q0.4466,0.8104,0.3591,0.8091 Q0.344,0.8091,0.3246,0.802 T0.2893,0.7863 T0.2543,0.77 T0.2206,0.76 T0.1894,0.7643 Q0.1804,0.7684,0.1733,0.7753 Q0.1591,0.7892,0.1601,0.8169 Q0.1784,0.8067,0.1938,0.8053 T0.2212,0.8087 T0.2507,0.825 Q0.2614,0.8324,0.2744,0.8365 T0.3053,0.844 T0.332,0.8499 L0.303,0.894 L0.2998,0.8948 Q0.2904,0.8968,0.278,0.9009 T0.2554,0.9086 T0.233,0.9158 T0.2099,0.9213 T0.1868,0.9229 T0.1633,0.9192 Q0.1517,0.916,0.1367,0.9241 T0.1065,0.9462 T0.0753,0.9737 T0.041,0.9955 T0.0061,1 Q0,0.9984,0.0068,0.9845 Q0.009,0.9804,0.0123,0.9755 Q0.0187,0.9666,0.0261,0.9606 T0.0374,0.9555 Q0.0481,0.958,0.0629,0.949 T0.0931,0.9262 T0.1247,0.8987 T0.1597,0.8777 T0.1949,0.8752 Q0.213,0.8805,0.2343,0.8772 Q0.2249,0.8736,0.2172,0.8683 Q0.1914,0.8511,0.1738,0.8497 T0.1349,0.8617 Q0.1304,0.865,0.1273,0.8646 T0.1242,0.8605 L0.1239,0.8536 Q0.1226,0.8295,0.1354,0.8016 T0.1723,0.7496 Q0.1914,0.7308,0.2104,0.7219 Q0.2259,0.7145,0.2425,0.7141 T0.273,0.7182 T0.3022,0.7296 T0.3301,0.7439 T0.3569,0.7571 T0.3821,0.7643 Q0.373,0.7561,0.3598,0.7367 T0.3382,0.7088 L0.3327,0.7035 Q0.3282,0.699,0.3353,0.6884 T0.3517,0.6692 L0.3611,0.6607 Q0.3711,0.657,0.3751,0.6554 T0.3837,0.6507 T0.3898,0.6458 T0.3979,0.637 T0.4111,0.6236 Q0.4172,0.6175,0.4234,0.6138 Q0.4327,0.6081,0.4439,0.6077 T0.4655,0.6105 T0.4884,0.6201 T0.5113,0.633 T0.5347,0.6468 T0.5573,0.6578 Q0.5705,0.6635,0.5833,0.665 T0.6112,0.665 T0.6286,0.6635 Q0.6421,0.6631,0.6718,0.6501 Q0.626,0.6578,0.6089,0.6517 Q0.6063,0.6509,0.6002,0.6491 T0.5884,0.6454 T0.5757,0.6409 T0.561,0.6344 T0.5466,0.6256 Q0.536,0.6183,0.5108,0.5989 T0.4722,0.5712 T0.4527,0.564 L0.4418,0.5661 Q0.4308,0.5681,0.4366,0.5563 Q0.4401,0.5485,0.4476,0.5392 L0.455,0.5298 Q0.4631,0.5188,0.4714,0.51 T0.4866,0.4947 T0.5021,0.4831 T0.516,0.4749 T0.53,0.4694 T0.5428,0.4657 T0.5558,0.4637 T0.5674,0.4629 T0.5795,0.4625 Q0.5489,0.4543,0.534,0.4384 L0.5286,0.4327 Q0.5244,0.4282,0.5318,0.4174 T0.5486,0.398 L0.5579,0.3899 Q0.567,0.387,0.5749,0.3834 T0.5878,0.3772 T0.6012,0.3687 T0.6126,0.3603 T0.6268,0.3487 T0.6421,0.3361 Q0.6567,0.3242,0.6712,0.3159 T0.7009,0.3034 T0.7277,0.2973 T0.7556,0.2961 T0.7809,0.2977 T0.8074,0.301 Q0.8096,0.3014,0.8109,0.3016 T0.8143,0.302 T0.818,0.3026 Q0.8012,0.2936,0.7767,0.2781 Q0.6718,0.2117,0.5744,0.2476 Q0.5637,0.2516,0.5492,0.2604 T0.5218,0.2779 T0.4923,0.2965 T0.461,0.3124 T0.4282,0.3216 T0.394,0.3206 Q0.3637,0.3136,0.3474,0.2936 T0.3353,0.2459 Q0.3385,0.2247,0.355,0.2023 Q0.3646,0.1892,0.3763,0.179 Q0.3898,0.1672,0.4063,0.1595 Q0.3927,0.1562,0.3785,0.1485 Q0.3604,0.1387,0.3453,0.1354 T0.3161,0.1354 T0.2899,0.1458 T0.2627,0.166 Q0.262,0.1664,0.254,0.1733 T0.2414,0.1841 T0.2267,0.196 T0.2094,0.2086 T0.1915,0.2196 T0.1725,0.2288 T0.1546,0.2337 T0.1373,0.2343 T0.1223,0.228 T0.1097,0.2145 Q0.0962,0.1929,0.1258,0.1582 Q0.131,0.1517,0.1371,0.1464 Q0.1462,0.1387,0.156,0.1321 T0.1752,0.1205 T0.1878,0.1134 Q0.1891,0.1117,0.1947,0.103 T0.2035,0.0899 T0.2125,0.0781 T0.2243,0.064 Q0.1949,0.0616,0.1672,0.0828 Q0.153,0.0934,0.1412,0.1009 T0.1146,0.1146 T0.0873,0.1213 T0.0616,0.116 T0.0374,0.0962 Q0.0339,0.0909,0.0295,0.0846 T0.024,0.0767 T0.0202,0.0728 T0.0148,0.0704 T0.0068,0.0697 Q0.0003,0.0693,0.0058,0.0567 Q0.0077,0.0522,0.011,0.0469 Z",type:0}},"\uE0C2":{type:5,data:{d:"M0.1934,0.6075 Q0.2012,0.612,0.2188,0.6218 T0.2459,0.6369 T0.2718,0.6508 T0.2983,0.6644 T0.322,0.6758 T0.3447,0.6854 T0.363,0.6911 T0.3783,0.6936 Q0.3796,0.6936,0.396,0.6952 T0.4265,0.6952 T0.4548,0.6879 Q0.4656,0.6834,0.4805,0.6756 T0.5073,0.6614 T0.5327,0.6485 T0.5564,0.6389 T0.5759,0.6363 T0.5909,0.6422 Q0.5953,0.6467,0.604,0.6554 T0.6155,0.6669 T0.6251,0.6746 T0.6387,0.683 T0.6556,0.6903 L0.6498,0.6956 Q0.6414,0.7042,0.6319,0.7177 T0.6162,0.7403 T0.5979,0.7583 T0.5703,0.7703 Q0.5313,0.7785,0.4799,0.7605 Q0.5097,0.7817,0.5508,0.7911 T0.6302,0.8001 Q0.6431,0.7997,0.659,0.7942 T0.6888,0.7813 T0.7194,0.766 T0.7506,0.753 T0.7819,0.7472 T0.8131,0.754 Q0.8239,0.7589,0.8346,0.7681 T0.8544,0.7895 T0.8689,0.818 T0.8737,0.8507 L0.8733,0.858 Q0.8591,0.849,0.8474,0.845 T0.8242,0.8421 T0.8021,0.8482 T0.7772,0.8625 Q0.7653,0.8707,0.7513,0.8752 T0.7184,0.8831 T0.6908,0.889 Q0.7017,0.8915,0.7152,0.8956 T0.7396,0.9037 T0.7633,0.9115 T0.7872,0.9174 T0.8107,0.9194 T0.8347,0.9155 Q0.8493,0.9115,0.8671,0.9196 T0.9015,0.9421 T0.9345,0.9708 T0.9682,0.9943 T0.9997,1 Q0.999,0.94,0.9983,0.6508 T0.9976,0.2322 Q0.9976,0.2109,0.9992,0.1379 T1,0.0249 Q0.9949,0.0249,0.9917,0.0257 T0.9863,0.0273 T0.9817,0.0308 T0.9776,0.0357 T0.9721,0.0437 T0.9651,0.0539 Q0.956,0.0657,0.9456,0.072 T0.9247,0.0789 T0.904,0.0775 T0.8823,0.0694 T0.862,0.0573 T0.8422,0.0432 Q0.7856,0,0.7328,0.0318 Q0.7392,0.0318,0.7452,0.0324 T0.756,0.0345 T0.7657,0.0386 T0.7741,0.0437 T0.7821,0.0506 T0.7894,0.0588 T0.7966,0.0683 T0.8039,0.0787 T0.8119,0.0904 T0.8202,0.1028 Q0.8249,0.1093,0.849,0.1234 T0.8811,0.1448 Q0.8998,0.1612,0.8893,0.1783 Q0.8835,0.1877,0.8759,0.1928 T0.8593,0.1979 T0.8417,0.1967 T0.8222,0.1895 T0.8034,0.1791 T0.7848,0.1663 T0.7687,0.1534 T0.7543,0.141 T0.744,0.1322 Q0.7186,0.1106,0.6984,0.101 T0.6559,0.0926 T0.6082,0.1077 Q0.5899,0.1175,0.5733,0.1206 T0.5411,0.1202 T0.5122,0.1098 T0.4822,0.0914 Q0.4927,0.1032,0.5036,0.1122 T0.5276,0.1285 T0.5503,0.1401 T0.5777,0.1518 T0.6055,0.1636 Q0.6258,0.1726,0.6375,0.1863 T0.6519,0.2162 Q0.6556,0.2403,0.6392,0.2605 T0.5926,0.2876 Q0.5804,0.2909,0.5672,0.2899 T0.5428,0.2856 T0.5193,0.2764 T0.4966,0.264 T0.4744,0.2497 T0.4523,0.2352 T0.4301,0.2222 T0.4077,0.2118 Q0.3021,0.173,0.1903,0.2436 Q0.1669,0.2583,0.1504,0.2674 T0.1129,0.2846 T0.075,0.294 T0.0384,0.2911 T0,0.2746 Q0.021,0.297,0.0459,0.3082 T0.0953,0.3207 T0.146,0.3193 T0.1988,0.3131 T0.2508,0.3095 T0.3029,0.3176 T0.3529,0.3448 Q0.3566,0.348,0.3652,0.355 T0.3771,0.3645 T0.3877,0.3731 T0.3993,0.3815 T0.4103,0.3884 T0.4226,0.3953 T0.4352,0.4009 T0.4497,0.4064 L0.4443,0.4121 Q0.4372,0.4198,0.4277,0.4251 T0.4072,0.4337 T0.3867,0.439 T0.3656,0.4431 T0.3481,0.4468 Q0.3241,0.4529,0.2943,0.4463 Q0.3227,0.4623,0.3458,0.471 T0.3959,0.4814 Q0.404,0.4818,0.4187,0.4825 T0.4409,0.4835 T0.4605,0.4857 T0.4809,0.4908 T0.4992,0.4996 T0.5186,0.5141 T0.5371,0.5353 L0.5449,0.5451 L0.5337,0.543 Q0.5242,0.541,0.5091,0.55 T0.4683,0.5796 T0.4314,0.6083 Q0.4203,0.6161,0.4086,0.6212 T0.382,0.6308 T0.3667,0.6353 Q0.3485,0.6418,0.2995,0.633 T0.1934,0.6075 Z",type:0}},"\uE0C3":{type:5,data:{d:"M0.989,0.0469 Q0.9829,0.0379,0.9753,0.0316 T0.9635,0.0253 Q0.9574,0.0257,0.9539,0.0263 T0.9466,0.03 T0.9418,0.0345 T0.9355,0.0434 T0.9277,0.0546 Q0.9193,0.0653,0.9103,0.0712 T0.8919,0.0775 T0.8745,0.0765 T0.8564,0.0695 T0.8396,0.0593 T0.8228,0.0473 Q0.7612,0,0.7073,0.0326 L0.7386,0.0767 L0.7419,0.0767 Q0.7506,0.0767,0.7556,0.0773 T0.7641,0.0789 T0.7709,0.0842 T0.7767,0.0922 T0.7854,0.1054 T0.7974,0.1236 Q0.8057,0.1354,0.8157,0.1444 T0.8325,0.1574 T0.8474,0.1654 T0.8571,0.1701 Q0.8564,0.1717,0.8554,0.1733 Q0.8503,0.1815,0.844,0.186 T0.8301,0.1909 T0.8153,0.1903 T0.7993,0.1845 T0.7836,0.1758 T0.768,0.1648 T0.754,0.1535 T0.741,0.1425 T0.7312,0.1338 Q0.7031,0.1101,0.6805,0.0995 T0.6357,0.0901 T0.5886,0.1048 Q0.5721,0.1138,0.5574,0.1168 T0.5295,0.117 T0.5055,0.1089 T0.4814,0.0942 L0.4789,0.0926 L0.4711,0.0922 Q0.4589,0.095,0.4811,0.1203 Q0.495,0.1358,0.5087,0.1474 T0.5366,0.167 T0.5611,0.1796 T0.5879,0.1909 T0.6134,0.2011 Q0.6218,0.2047,0.6273,0.2092 Q0.6283,0.2117,0.6286,0.2141 Q0.6305,0.2263,0.6249,0.2388 T0.606,0.2616 T0.5747,0.2761 Q0.5602,0.2785,0.5487,0.2779 T0.5245,0.2724 T0.5015,0.2616 T0.4784,0.2476 T0.4547,0.2321 T0.4297,0.217 T0.4024,0.2043 Q0.2985,0.166,0.1901,0.2345 Q0.1694,0.248,0.1523,0.2569 T0.1165,0.2726 T0.0818,0.2812 T0.0494,0.2796 T0.0181,0.2663 L0.0116,0.2671 Q0,0.272,0.0232,0.2969 Q0.0468,0.3222,0.0742,0.3365 T0.1278,0.3542 T0.1807,0.3564 T0.233,0.352 T0.2815,0.3473 T0.3269,0.3509 T0.3656,0.3699 Q0.4037,0.4005,0.4153,0.4082 Q0.405,0.4139,0.3921,0.4174 T0.3633,0.4237 T0.3414,0.4278 Q0.3207,0.4331,0.2956,0.4278 Q0.2943,0.4278,0.293,0.4274 Q0.2933,0.429,0.2938,0.4313 T0.2964,0.4398 T0.3007,0.4513 T0.3069,0.4619 T0.3149,0.4698 Q0.3427,0.4857,0.3664,0.4947 T0.4169,0.5053 Q0.423,0.5057,0.4385,0.5065 T0.4606,0.508 T0.4779,0.51 T0.4958,0.5139 T0.5095,0.5204 Q0.4992,0.5232,0.485,0.5328 T0.451,0.5585 T0.4201,0.5824 Q0.4095,0.5897,0.3987,0.5946 T0.3738,0.6036 T0.3591,0.6081 Q0.3433,0.6134,0.2965,0.605 T0.1972,0.5816 L0.2026,0.5922 T0.2133,0.6134 L0.2188,0.624 Q0.2201,0.6248,0.2214,0.6252 Q0.2291,0.6297,0.2486,0.6405 T0.2786,0.657 T0.3066,0.6721 T0.3353,0.6866 T0.3598,0.6972 T0.383,0.7051 T0.4005,0.7076 Q0.4008,0.7076,0.4169,0.7092 T0.4471,0.7092 T0.475,0.7019 Q0.4895,0.6958,0.5176,0.6811 T0.5644,0.6586 T0.5921,0.6533 Q0.6128,0.6741,0.6173,0.6782 Q0.6131,0.6835,0.6068,0.6929 T0.5963,0.7078 T0.5858,0.7198 T0.5716,0.7304 T0.5534,0.7365 Q0.5179,0.7443,0.4708,0.728 L0.4708,0.7294 T0.471,0.7333 T0.4716,0.7392 T0.4734,0.7463 T0.4764,0.7539 T0.4814,0.7612 T0.4889,0.7679 Q0.4914,0.77,0.4944,0.7716 Q0.5534,0.8104,0.6409,0.8091 Q0.6521,0.8091,0.6654,0.8053 T0.6907,0.7959 T0.7157,0.7838 T0.741,0.772 T0.7652,0.7631 T0.7888,0.7594 T0.8106,0.7643 Q0.8196,0.7684,0.8267,0.7753 Q0.8409,0.7892,0.8399,0.8169 Q0.8216,0.8067,0.8062,0.8053 T0.7788,0.8087 T0.7493,0.825 Q0.7386,0.8324,0.7256,0.8365 T0.6947,0.844 T0.668,0.8499 L0.697,0.894 L0.7002,0.8948 Q0.7099,0.8968,0.7222,0.9009 T0.7446,0.9086 T0.767,0.9158 T0.7901,0.9213 T0.8132,0.9229 T0.8367,0.9192 Q0.8483,0.916,0.8633,0.9241 T0.8937,0.9462 T0.9248,0.9737 T0.959,0.9955 T0.9939,1 Q1,0.9984,0.9932,0.9845 Q0.991,0.9804,0.9877,0.9755 Q0.9813,0.9666,0.9739,0.9606 T0.9626,0.9555 Q0.9519,0.958,0.9372,0.949 T0.9071,0.9262 T0.8753,0.8987 T0.8403,0.8777 T0.8051,0.8752 Q0.787,0.8805,0.7657,0.8772 Q0.7751,0.8736,0.7828,0.8683 Q0.8086,0.8511,0.8262,0.8497 T0.8651,0.8617 Q0.8696,0.865,0.8727,0.8646 T0.8758,0.8605 L0.8761,0.8536 Q0.8774,0.8295,0.8646,0.8016 T0.8277,0.7496 Q0.8086,0.7308,0.7896,0.7219 Q0.7741,0.7145,0.7575,0.7141 T0.727,0.7182 T0.6978,0.7296 T0.6699,0.7439 T0.6433,0.7571 T0.6179,0.7643 Q0.627,0.7561,0.6402,0.7367 T0.6618,0.7088 L0.6673,0.7035 Q0.6718,0.699,0.6647,0.6884 T0.6483,0.6692 L0.6389,0.6607 Q0.6289,0.657,0.6249,0.6554 T0.6163,0.6507 T0.6102,0.6458 T0.6021,0.637 T0.5889,0.6236 Q0.5828,0.6175,0.5766,0.6138 Q0.5673,0.6081,0.5561,0.6077 T0.5345,0.6105 T0.5116,0.6201 T0.4887,0.633 T0.4653,0.6468 T0.4427,0.6578 Q0.4295,0.6635,0.4167,0.665 T0.3888,0.665 T0.3714,0.6635 Q0.3579,0.6631,0.3282,0.6501 Q0.374,0.6578,0.3911,0.6517 Q0.3937,0.6509,0.4024,0.6482 T0.4179,0.6434 T0.4348,0.6364 T0.4534,0.6256 Q0.464,0.6183,0.4892,0.5989 T0.5278,0.5712 T0.5476,0.564 L0.5582,0.5661 Q0.5692,0.5681,0.5634,0.5563 Q0.5599,0.5485,0.5524,0.5392 L0.545,0.5298 Q0.5369,0.5188,0.5286,0.51 T0.5134,0.4947 T0.4979,0.4831 T0.484,0.4749 T0.47,0.4694 T0.4572,0.4657 T0.4442,0.4637 T0.4326,0.4629 T0.4205,0.4625 Q0.4511,0.4543,0.466,0.4384 L0.4714,0.4327 Q0.4795,0.4237,0.4469,0.394 L0.4421,0.3899 Q0.433,0.387,0.4251,0.3834 T0.4122,0.3772 T0.3988,0.3687 T0.3874,0.3603 T0.3732,0.3487 T0.3579,0.3361 Q0.3433,0.3242,0.3288,0.3159 T0.2993,0.3034 T0.2725,0.2973 T0.2446,0.2961 T0.2193,0.2977 T0.1926,0.301 Q0.1855,0.3022,0.182,0.3026 Q0.1991,0.2936,0.2233,0.2781 Q0.3282,0.2117,0.4256,0.2476 Q0.4363,0.2516,0.451,0.2604 T0.4784,0.2779 T0.5077,0.2965 T0.539,0.3124 T0.5718,0.3216 T0.606,0.3206 Q0.6363,0.3136,0.6526,0.2936 T0.6647,0.2459 Q0.6615,0.2247,0.645,0.2023 Q0.6354,0.1892,0.6237,0.179 Q0.6102,0.1672,0.5937,0.1595 Q0.6073,0.1562,0.6215,0.1485 Q0.6396,0.1387,0.6547,0.1354 T0.6839,0.1354 T0.7101,0.1458 T0.7373,0.166 Q0.738,0.1664,0.746,0.1733 T0.7586,0.1841 T0.7733,0.196 T0.7907,0.2086 T0.8086,0.2196 T0.8275,0.2288 T0.8454,0.2337 T0.8627,0.2343 T0.8777,0.228 T0.8903,0.2145 Q0.9038,0.1929,0.8742,0.1582 Q0.869,0.1517,0.8629,0.1464 Q0.8538,0.1387,0.844,0.1321 T0.8248,0.1205 T0.8122,0.1134 Q0.8109,0.1117,0.8053,0.103 T0.7965,0.0899 T0.7875,0.0781 T0.7757,0.064 Q0.8051,0.0616,0.8328,0.0828 Q0.847,0.0934,0.8588,0.1009 T0.8854,0.1146 T0.9127,0.1213 T0.9384,0.116 T0.9626,0.0962 Q0.9664,0.0909,0.9706,0.0846 T0.976,0.0767 T0.9798,0.0728 T0.9852,0.0704 T0.9932,0.0697 Q0.9997,0.0693,0.9942,0.0567 Q0.9923,0.0522,0.989,0.0469 Z",type:0}},"\uE0C4":{type:5,data:{d:"M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0.9099,0.1158 L0.9099,0.1888 L0.9951,0.1888 L0.9951,0.1158 L0.9099,0.1158 Z M0.9075,0.1116 L0.9976,0.1116 Q1,0.1116,1,0.1137 L1,0.1909 Q1,0.193,0.9976,0.193 L0.9075,0.193 Q0.905,0.193,0.905,0.1909 L0.905,0.1137 Q0.905,0.1116,0.9075,0.1116 Z M0.7852,0.6086 L0.7852,0.682 L0.8704,0.682 L0.8704,0.6086 L0.7852,0.6086 Z M0.7828,0.6044 L0.8729,0.6044 Q0.8753,0.6044,0.8753,0.6065 L0.8753,0.6841 Q0.8753,0.6862,0.8729,0.6862 L0.7828,0.6862 Q0.7803,0.6862,0.7803,0.6841 L0.7803,0.6065 Q0.7803,0.6044,0.7828,0.6044 Z M0.66,0.1137 L0.7501,0.1137 L0.7501,0.1909 L0.66,0.1909 L0.66,0.1137 Z M0.6624,0.4102 L0.6624,0.4836 L0.7477,0.4836 L0.7477,0.4102 L0.6624,0.4102 Z M0.66,0.406 L0.7501,0.406 Q0.7526,0.406,0.7526,0.4081 L0.7526,0.4857 Q0.7526,0.4878,0.7501,0.4878 L0.66,0.4878 Q0.6576,0.4878,0.6576,0.4857 L0.6576,0.4081 Q0.6576,0.406,0.66,0.406 Z M0.6624,0.7131 L0.6624,0.7865 L0.7477,0.7865 L0.7477,0.7131 L0.6624,0.7131 Z M0.66,0.7089 L0.7501,0.7089 Q0.7526,0.7089,0.7526,0.711 L0.7526,0.7886 Q0.7526,0.7907,0.7501,0.7907 L0.66,0.7907 Q0.6576,0.7907,0.6576,0.7886 L0.6576,0.711 Q0.6576,0.7089,0.66,0.7089 Z M0.5407,0.2227 L0.5407,0.2836 L0.6113,0.2836 L0.6113,0.2227 L0.5407,0.2227 Z M0.5309,0.206 L0.621,0.206 Q0.623,0.206,0.6249,0.2068 T0.6281,0.2089 T0.6301,0.2116 T0.6308,0.2143 L0.6308,0.2919 Q0.6308,0.2957,0.6276,0.298 T0.621,0.3003 L0.5309,0.3003 Q0.5265,0.3003,0.5239,0.2976 T0.5212,0.2919 L0.5212,0.2143 Q0.5212,0.211,0.5244,0.2085 T0.5309,0.206 Z M0.5407,0.3184 L0.5407,0.3792 L0.6113,0.3792 L0.6113,0.3184 L0.5407,0.3184 Z M0.5309,0.3016 L0.621,0.3016 Q0.6254,0.3016,0.6281,0.3043 T0.6308,0.31 L0.6308,0.3876 Q0.6308,0.3914,0.6276,0.3937 T0.621,0.396 L0.5309,0.396 Q0.5265,0.396,0.5239,0.3932 T0.5212,0.3876 L0.5212,0.31 Q0.5212,0.3062,0.5244,0.3039 T0.5309,0.3016 Z M0.5309,0.4081 L0.621,0.4081 L0.621,0.4857 L0.5309,0.4857 L0.5309,0.4081 Z M0.5309,0.5059 L0.621,0.5059 L0.621,0.5835 L0.5309,0.5835 L0.5309,0.5059 Z M0.5407,0.6149 L0.5407,0.6758 L0.6113,0.6758 L0.6113,0.6149 L0.5407,0.6149 Z M0.5309,0.5982 L0.621,0.5982 Q0.6254,0.5982,0.6281,0.6009 T0.6308,0.6065 L0.6308,0.6841 Q0.6308,0.6879,0.6276,0.6902 T0.621,0.6925 L0.5309,0.6925 Q0.5265,0.6925,0.5239,0.6898 T0.5212,0.6841 L0.5212,0.6065 Q0.5212,0.6028,0.5244,0.6005 T0.5309,0.5982 Z M0.4062,0.1137 L0.4963,0.1137 L0.4963,0.1909 L0.4062,0.1909 L0.4062,0.1137 Z M0.4062,0.2143 L0.4963,0.2143 L0.4963,0.2919 L0.4062,0.2919 L0.4062,0.2143 Z M0.4184,0.4186 L0.4184,0.4753 L0.4842,0.4753 L0.4842,0.4186 L0.4184,0.4186 Z M0.4062,0.3977 L0.4963,0.3977 Q0.5017,0.3977,0.5051,0.401 T0.5085,0.4081 L0.5085,0.4857 Q0.5085,0.4904,0.5046,0.4933 T0.4963,0.4962 L0.4062,0.4962 Q0.4009,0.4962,0.3975,0.4929 T0.3941,0.4857 L0.3941,0.4081 Q0.3941,0.4035,0.398,0.4006 T0.4062,0.3977 Z M0.4062,0.5059 L0.4963,0.5059 L0.4963,0.5835 L0.4062,0.5835 L0.4062,0.5059 Z M0.4087,0.7131 L0.4087,0.7865 L0.4939,0.7865 L0.4939,0.7131 L0.4087,0.7131 Z M0.4062,0.7089 L0.4963,0.7089 Q0.4988,0.7089,0.4988,0.711 L0.4988,0.7886 Q0.4988,0.7907,0.4963,0.7907 L0.4062,0.7907 Q0.4038,0.7907,0.4038,0.7886 L0.4038,0.711 Q0.4038,0.7089,0.4062,0.7089 Z M0.2689,0.1137 L0.359,0.1137 L0.359,0.1909 L0.2689,0.1909 L0.2689,0.1137 Z M0.2689,0.2143 L0.359,0.2143 L0.359,0.2919 L0.2689,0.2919 L0.2689,0.2143 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.31 L0.359,0.31 L0.359,0.3876 L0.2689,0.3876 L0.2689,0.31 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.4081 L0.359,0.4081 L0.359,0.4857 L0.2689,0.4857 L0.2689,0.4081 Z M0.2689,0.5059 L0.359,0.5059 L0.359,0.5835 L0.2689,0.5835 L0.2689,0.5059 Z M0.2689,0.711 L0.359,0.711 L0.359,0.7886 L0.2689,0.7886 L0.2689,0.711 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.1374,0.1137 L0.2275,0.1137 L0.2275,0.1909 L0.1374,0.1909 L0.1374,0.1137 Z M0.1374,0.2143 L0.2275,0.2143 L0.2275,0.2919 L0.1374,0.2919 L0.1374,0.2143 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.31 L0.2275,0.31 L0.2275,0.3876 L0.1374,0.3876 L0.1374,0.31 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.4081 L0.2275,0.4081 L0.2275,0.4857 L0.1374,0.4857 L0.1374,0.4081 Z M0.1374,0.5059 L0.2275,0.5059 L0.2275,0.5835 L0.1374,0.5835 L0.1374,0.5059 Z M0.1374,0.6065 L0.2275,0.6065 L0.2275,0.6841 L0.1374,0.6841 L0.1374,0.6065 Z M0.1374,0.711 L0.2275,0.711 L0.2275,0.7886 L0.1374,0.7886 L0.1374,0.711 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0,0.1137 L0.0901,0.1137 L0.0901,0.1909 L0,0.1909 L0,0.1137 Z M0,0.2143 L0.0901,0.2143 L0.0901,0.2919 L0,0.2919 L0,0.2143 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.31 L0.0901,0.31 L0.0901,0.3876 L0,0.3876 L0,0.31 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.4081 L0.0901,0.4081 L0.0901,0.4857 L0,0.4857 L0,0.4081 Z M0,0.5059 L0.0901,0.5059 L0.0901,0.5835 L0,0.5835 L0,0.5059 Z M0,0.6065 L0.0901,0.6065 L0.0901,0.6841 L0,0.6841 L0,0.6065 Z M0,0.711 L0.0901,0.711 L0.0901,0.7886 L0,0.7886 L0,0.711 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z",type:0}},"\uE0C5":{type:5,data:{d:"M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M0.0901,0.1158 L0.0901,0.1888 L0.0049,0.1888 L0.0049,0.1158 L0.0901,0.1158 Z M0.0925,0.1116 L0.0024,0.1116 Q0,0.1116,0,0.1137 L0,0.1909 Q0,0.193,0.0024,0.193 L0.0925,0.193 Q0.095,0.193,0.095,0.1909 L0.095,0.1137 Q0.095,0.1116,0.0925,0.1116 Z M0.2148,0.6086 L0.2148,0.682 L0.1296,0.682 L0.1296,0.6086 L0.2148,0.6086 Z M0.2172,0.6044 L0.1271,0.6044 Q0.1247,0.6044,0.1247,0.6065 L0.1247,0.6841 Q0.1247,0.6862,0.1271,0.6862 L0.2172,0.6862 Q0.2197,0.6862,0.2197,0.6841 L0.2197,0.6065 Q0.2197,0.6044,0.2172,0.6044 Z M0.34,0.1137 L0.2499,0.1137 L0.2499,0.1909 L0.34,0.1909 L0.34,0.1137 Z M0.3376,0.4102 L0.3376,0.4836 L0.2523,0.4836 L0.2523,0.4102 L0.3376,0.4102 Z M0.34,0.406 L0.2499,0.406 Q0.2474,0.406,0.2474,0.4081 L0.2474,0.4857 Q0.2474,0.4878,0.2499,0.4878 L0.34,0.4878 Q0.3424,0.4878,0.3424,0.4857 L0.3424,0.4081 Q0.3424,0.406,0.34,0.406 Z M0.3376,0.7131 L0.3376,0.7865 L0.2523,0.7865 L0.2523,0.7131 L0.3376,0.7131 Z M0.34,0.7089 L0.2499,0.7089 Q0.2474,0.7089,0.2474,0.711 L0.2474,0.7886 Q0.2474,0.7907,0.2499,0.7907 L0.34,0.7907 Q0.3424,0.7907,0.3424,0.7886 L0.3424,0.711 Q0.3424,0.7089,0.34,0.7089 Z M0.4593,0.2227 L0.4593,0.2836 L0.3887,0.2836 L0.3887,0.2227 L0.4593,0.2227 Z M0.4691,0.206 L0.379,0.206 Q0.3746,0.206,0.3719,0.2087 T0.3692,0.2143 L0.3692,0.2919 Q0.3692,0.2957,0.3724,0.298 T0.379,0.3003 L0.4691,0.3003 Q0.4735,0.3003,0.4761,0.2976 T0.4788,0.2919 L0.4788,0.2143 Q0.4788,0.211,0.4756,0.2085 T0.4691,0.206 Z M0.4593,0.3184 L0.4593,0.3792 L0.3887,0.3792 L0.3887,0.3184 L0.4593,0.3184 Z M0.4691,0.3016 L0.379,0.3016 Q0.3746,0.3016,0.3719,0.3043 T0.3692,0.31 L0.3692,0.3876 Q0.3692,0.3914,0.3724,0.3937 T0.379,0.396 L0.4691,0.396 Q0.4735,0.396,0.4761,0.3932 T0.4788,0.3876 L0.4788,0.31 Q0.4788,0.3062,0.4756,0.3039 T0.4691,0.3016 Z M0.4691,0.4081 L0.379,0.4081 L0.379,0.4857 L0.4691,0.4857 L0.4691,0.4081 Z M0.4691,0.5059 L0.379,0.5059 L0.379,0.5835 L0.4691,0.5835 L0.4691,0.5059 Z M0.4593,0.6149 L0.4593,0.6758 L0.3887,0.6758 L0.3887,0.6149 L0.4593,0.6149 Z M0.4691,0.5982 L0.379,0.5982 Q0.3746,0.5982,0.3719,0.6009 T0.3692,0.6065 L0.3692,0.6841 Q0.3692,0.6879,0.3724,0.6902 T0.379,0.6925 L0.4691,0.6925 Q0.4735,0.6925,0.4761,0.6898 T0.4788,0.6841 L0.4788,0.6065 Q0.4788,0.6028,0.4756,0.6005 T0.4691,0.5982 Z M0.5938,0.1137 L0.5037,0.1137 L0.5037,0.1909 L0.5938,0.1909 L0.5938,0.1137 Z M0.5938,0.2143 L0.5037,0.2143 L0.5037,0.2919 L0.5938,0.2919 L0.5938,0.2143 Z M0.5816,0.4186 L0.5816,0.4753 L0.5158,0.4753 L0.5158,0.4186 L0.5816,0.4186 Z M0.5938,0.3977 L0.5037,0.3977 Q0.4983,0.3977,0.4949,0.401 T0.4915,0.4081 L0.4915,0.4857 Q0.4915,0.4904,0.4954,0.4933 T0.5037,0.4962 L0.5938,0.4962 Q0.5991,0.4962,0.6025,0.4929 T0.6059,0.4857 L0.6059,0.4081 Q0.6059,0.4035,0.602,0.4006 T0.5938,0.3977 Z M0.5938,0.5059 L0.5037,0.5059 L0.5037,0.5835 L0.5938,0.5835 L0.5938,0.5059 Z M0.5913,0.7131 L0.5913,0.7865 L0.5061,0.7865 L0.5061,0.7131 L0.5913,0.7131 Z M0.5938,0.7089 L0.5037,0.7089 Q0.5012,0.7089,0.5012,0.711 L0.5012,0.7886 Q0.5012,0.7907,0.5037,0.7907 L0.5938,0.7907 Q0.5962,0.7907,0.5962,0.7886 L0.5962,0.711 Q0.5962,0.7089,0.5938,0.7089 Z M0.7311,0.1137 L0.641,0.1137 L0.641,0.1909 L0.7311,0.1909 L0.7311,0.1137 Z M0.7311,0.2143 L0.641,0.2143 L0.641,0.2919 L0.7311,0.2919 L0.7311,0.2143 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.31 L0.641,0.31 L0.641,0.3876 L0.7311,0.3876 L0.7311,0.31 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.4081 L0.641,0.4081 L0.641,0.4857 L0.7311,0.4857 L0.7311,0.4081 Z M0.7311,0.5059 L0.641,0.5059 L0.641,0.5835 L0.7311,0.5835 L0.7311,0.5059 Z M0.7311,0.711 L0.641,0.711 L0.641,0.7886 L0.7311,0.7886 L0.7311,0.711 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.8626,0.1137 L0.7725,0.1137 L0.7725,0.1909 L0.8626,0.1909 L0.8626,0.1137 Z M0.8626,0.2143 L0.7725,0.2143 L0.7725,0.2919 L0.8626,0.2919 L0.8626,0.2143 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.31 L0.7725,0.31 L0.7725,0.3876 L0.8626,0.3876 L0.8626,0.31 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.4081 L0.7725,0.4081 L0.7725,0.4857 L0.8626,0.4857 L0.8626,0.4081 Z M0.8626,0.5059 L0.7725,0.5059 L0.7725,0.5835 L0.8626,0.5835 L0.8626,0.5059 Z M0.8626,0.6065 L0.7725,0.6065 L0.7725,0.6841 L0.8626,0.6841 L0.8626,0.6065 Z M0.8626,0.711 L0.7725,0.711 L0.7725,0.7886 L0.8626,0.7886 L0.8626,0.711 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M1,0.1137 L0.9099,0.1137 L0.9099,0.1909 L1,0.1909 L1,0.1137 Z M1,0.2143 L0.9099,0.2143 L0.9099,0.2919 L1,0.2919 L1,0.2143 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.31 L0.9099,0.31 L0.9099,0.3876 L1,0.3876 L1,0.31 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.4081 L0.9099,0.4081 L0.9099,0.4857 L1,0.4857 L1,0.4081 Z M1,0.5059 L0.9099,0.5059 L0.9099,0.5835 L1,0.5835 L1,0.5059 Z M1,0.6065 L0.9099,0.6065 L0.9099,0.6841 L1,0.6841 L1,0.6065 Z M1,0.711 L0.9099,0.711 L0.9099,0.7886 L1,0.7886 L1,0.711 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z",type:0}},"\uE0C6":{type:5,data:{d:"M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0.8574,0.3217 L0.8574,0.4098 L0.9691,0.4098 L0.9691,0.3217 L0.8574,0.3217 Z M0.8419,0.2978 L0.9846,0.2978 Q0.9892,0.2978,0.9928,0.2997 T0.9982,0.3043 T1,0.31 L1,0.4216 Q1,0.427,0.9951,0.4304 T0.9846,0.4337 L0.8419,0.4337 Q0.8352,0.4337,0.8308,0.4299 T0.8265,0.4216 L0.8265,0.31 Q0.8265,0.3062,0.829,0.3035 T0.835,0.2993 T0.8419,0.2978 Z M0.8574,0.888 L0.8574,0.9757 L0.9691,0.9757 L0.9691,0.888 L0.8574,0.888 Z M0.8419,0.8637 L0.9846,0.8637 Q0.9892,0.8637,0.9928,0.8658 T0.9982,0.8706 T1,0.8758 L1,0.9878 Q1,0.9929,0.9951,0.9964 T0.9846,1 L0.8419,1 Q0.8352,1,0.8308,0.996 T0.8265,0.9878 L0.8265,0.8758 Q0.8265,0.8708,0.8314,0.8672 T0.8419,0.8637 Z M0.6509,0.169 L0.6509,0.2706 L0.7806,0.2706 L0.7806,0.169 L0.6509,0.169 Z M0.6442,0.159 L0.7868,0.159 Q0.7899,0.159,0.7917,0.1604 T0.7935,0.164 L0.7935,0.2756 Q0.7935,0.2781,0.7915,0.2796 T0.7868,0.281 L0.6442,0.281 Q0.6416,0.281,0.6395,0.2794 T0.6375,0.2756 L0.6375,0.164 Q0.6375,0.1619,0.6398,0.1604 T0.6442,0.159 Z M0.6632,0.6044 L0.6632,0.6858 L0.7678,0.6858 L0.7678,0.6044 L0.6632,0.6044 Z M0.6442,0.5742 L0.7868,0.5742 Q0.7951,0.5742,0.8007,0.5791 T0.8064,0.5893 L0.8064,0.7013 Q0.8064,0.7076,0.8002,0.712 T0.7868,0.7164 L0.6442,0.7164 Q0.6359,0.7164,0.6305,0.7114 T0.6251,0.7013 L0.6251,0.5893 Q0.6251,0.5847,0.6282,0.5812 T0.6357,0.5759 T0.6442,0.5742 Z M0.4264,0.164 L0.5695,0.164 L0.5695,0.2756 L0.4264,0.2756 L0.4264,0.164 Z M0.4325,0.315 L0.4325,0.4165 L0.5628,0.4165 L0.5628,0.315 L0.4325,0.315 Z M0.4264,0.3049 L0.5695,0.3049 Q0.5721,0.3049,0.5739,0.3064 T0.5757,0.31 L0.5757,0.4216 Q0.5757,0.4237,0.5736,0.4251 T0.5695,0.4266 L0.4264,0.4266 Q0.4238,0.4266,0.422,0.4251 T0.4202,0.4216 L0.4202,0.31 Q0.4202,0.3087,0.4207,0.3077 T0.4222,0.306 T0.4243,0.3052 T0.4264,0.3049 Z M0.4264,0.4476 L0.5695,0.4476 L0.5695,0.5596 L0.4264,0.5596 L0.4264,0.4476 Z M0.4392,0.7408 L0.4392,0.8326 L0.5561,0.8326 L0.5561,0.7408 L0.4392,0.7408 Z M0.4264,0.7206 L0.5695,0.7206 Q0.5747,0.7206,0.5783,0.724 T0.5819,0.7307 L0.5819,0.8427 Q0.5819,0.8469,0.5778,0.8498 T0.5695,0.8528 L0.4264,0.8528 Q0.4207,0.8528,0.4171,0.8496 T0.4135,0.8427 L0.4135,0.7307 Q0.4135,0.7265,0.4176,0.7236 T0.4264,0.7206 Z M0.2178,0.164 L0.3605,0.164 L0.3605,0.2756 L0.2178,0.2756 L0.2178,0.164 Z M0.2178,0.31 L0.3605,0.31 L0.3605,0.4216 L0.2178,0.4216 L0.2178,0.31 Z M0.2178,0.5893 L0.3605,0.5893 L0.3605,0.7013 L0.2178,0.7013 L0.2178,0.5893 Z M0.2178,0.7307 L0.3605,0.7307 L0.3605,0.8427 L0.2178,0.8427 L0.2178,0.7307 Z M0.2178,0.8758 L0.3605,0.8758 L0.3605,0.9878 L0.2178,0.9878 L0.2178,0.8758 Z M0,0.164 L0.1432,0.164 L0.1432,0.2756 L0,0.2756 L0,0.164 Z M0,0.31 L0.1432,0.31 L0.1432,0.4216 L0,0.4216 L0,0.31 Z M0,0.4476 L0.1432,0.4476 L0.1432,0.5596 L0,0.5596 L0,0.4476 Z M0,0.5893 L0.1432,0.5893 L0.1432,0.7013 L0,0.7013 L0,0.5893 Z M0,0.7307 L0.1432,0.7307 L0.1432,0.8427 L0,0.8427 L0,0.7307 Z M0,0.8758 L0.1432,0.8758 L0.1432,0.9878 L0,0.9878 L0,0.8758 Z",type:0}},"\uE0C7":{type:5,data:{d:"M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M0.1432,0.3217 L0.1432,0.4098 L0.0309,0.4098 L0.0309,0.3217 L0.1432,0.3217 Z M0.1586,0.2978 L0.0154,0.2978 Q0.0088,0.2978,0.0044,0.3016 T0,0.31 L0,0.4216 Q0,0.427,0.0049,0.4304 T0.0154,0.4337 L0.1586,0.4337 Q0.1648,0.4337,0.1694,0.4299 T0.174,0.4216 L0.174,0.31 Q0.174,0.3062,0.1715,0.3035 T0.1653,0.2993 T0.1586,0.2978 Z M0.1432,0.888 L0.1432,0.9757 L0.0309,0.9757 L0.0309,0.888 L0.1432,0.888 Z M0.1586,0.8637 L0.0154,0.8637 Q0.0108,0.8637,0.0072,0.8658 T0.0018,0.8706 T0,0.8758 L0,0.9878 Q0,0.9929,0.0049,0.9964 T0.0154,1 L0.1586,1 Q0.1648,1,0.1694,0.996 T0.174,0.9878 L0.174,0.8758 Q0.174,0.8725,0.1715,0.8695 T0.1653,0.8651 T0.1586,0.8637 Z M0.3496,0.169 L0.3496,0.2706 L0.2194,0.2706 L0.2194,0.169 L0.3496,0.169 Z M0.3563,0.159 L0.2132,0.159 Q0.2106,0.159,0.2088,0.1604 T0.207,0.164 L0.207,0.2756 Q0.207,0.2781,0.2091,0.2796 T0.2132,0.281 L0.3563,0.281 Q0.3589,0.281,0.3607,0.2794 T0.3625,0.2756 L0.3625,0.164 Q0.3625,0.1619,0.3605,0.1604 T0.3563,0.159 Z M0.3368,0.6044 L0.3368,0.6858 L0.2327,0.6858 L0.2327,0.6044 L0.3368,0.6044 Z M0.3563,0.5742 L0.2132,0.5742 Q0.2049,0.5742,0.1993,0.5791 T0.1936,0.5893 L0.1936,0.7013 Q0.1936,0.7076,0.2001,0.712 T0.2132,0.7164 L0.3563,0.7164 Q0.3641,0.7164,0.3697,0.7114 T0.3754,0.7013 L0.3754,0.5893 Q0.3754,0.5847,0.3723,0.5812 T0.3648,0.5759 T0.3563,0.5742 Z M0.5736,0.164 L0.431,0.164 L0.431,0.2756 L0.5736,0.2756 L0.5736,0.164 Z M0.5675,0.315 L0.5675,0.4165 L0.4377,0.4165 L0.4377,0.315 L0.5675,0.315 Z M0.5736,0.3049 L0.431,0.3049 Q0.4284,0.3049,0.4264,0.3064 T0.4243,0.31 L0.4243,0.4216 Q0.4243,0.4237,0.4266,0.4251 T0.431,0.4266 L0.5736,0.4266 Q0.5767,0.4266,0.5785,0.4251 T0.5803,0.4216 L0.5803,0.31 Q0.5803,0.3087,0.5798,0.3077 T0.5783,0.306 T0.5762,0.3052 T0.5736,0.3049 Z M0.5736,0.4476 L0.431,0.4476 L0.431,0.5596 L0.5736,0.5596 L0.5736,0.4476 Z M0.5613,0.7408 L0.5613,0.8326 L0.4439,0.8326 L0.4439,0.7408 L0.5613,0.7408 Z M0.5736,0.7206 L0.431,0.7206 Q0.4253,0.7206,0.4217,0.724 T0.4181,0.7307 L0.4181,0.8427 Q0.4181,0.8469,0.4222,0.8498 T0.431,0.8528 L0.5736,0.8528 Q0.5778,0.8528,0.5808,0.8511 T0.5855,0.8471 T0.587,0.8427 L0.587,0.7307 Q0.587,0.7265,0.5826,0.7236 T0.5736,0.7206 Z M0.7827,0.164 L0.6395,0.164 L0.6395,0.2756 L0.7827,0.2756 L0.7827,0.164 Z M0.7827,0.31 L0.6395,0.31 L0.6395,0.4216 L0.7827,0.4216 L0.7827,0.31 Z M0.7827,0.5893 L0.6395,0.5893 L0.6395,0.7013 L0.7827,0.7013 L0.7827,0.5893 Z M0.7827,0.7307 L0.6395,0.7307 L0.6395,0.8427 L0.7827,0.8427 L0.7827,0.7307 Z M0.7827,0.8758 L0.6395,0.8758 L0.6395,0.9878 L0.7827,0.9878 L0.7827,0.8758 Z M1,0.164 L0.8574,0.164 L0.8574,0.2756 L1,0.2756 L1,0.164 Z M1,0.31 L0.8574,0.31 L0.8574,0.4216 L1,0.4216 L1,0.31 Z M1,0.4476 L0.8574,0.4476 L0.8574,0.5596 L1,0.5596 L1,0.4476 Z M1,0.5893 L0.8574,0.5893 L0.8574,0.7013 L1,0.7013 L1,0.5893 Z M1,0.7307 L0.8574,0.7307 L0.8574,0.8427 L1,0.8427 L1,0.7307 Z M1,0.8758 L0.8574,0.8758 L0.8574,0.9878 L1,0.9878 L1,0.8758 Z",type:0}},"\uE0C8":{type:5,data:{d:"M0.0008,0.9979 Q0.0008,1,0.0032,1 Q0.0482,1,0.0602,0.9975 Q0.0667,0.9963,0.0847,0.9917 T0.1084,0.9859 Q0.1124,0.9851,0.1221,0.9851 L0.1253,0.9851 L0.1285,0.9851 Q0.1518,0.9851,0.1655,0.9809 Q0.1695,0.9801,0.1731,0.9788 T0.1791,0.9766 T0.1839,0.9747 T0.1888,0.9732 L0.1936,0.972 T0.1988,0.9712 T0.2048,0.971 L0.2096,0.971 Q0.2201,0.9714,0.2233,0.9714 T0.2345,0.9693 Q0.2386,0.9685,0.2502,0.9674 T0.2715,0.966 L0.2811,0.9656 Q0.2916,0.9647,0.2916,0.9573 Q0.2916,0.954,0.2892,0.9515 T0.2819,0.9486 Q0.2755,0.9482,0.2683,0.9482 Q0.2643,0.9482,0.2582,0.9484 T0.2498,0.9486 Q0.2426,0.9486,0.2402,0.9473 Q0.2386,0.9469,0.2317,0.943 T0.2161,0.937 Q0.2104,0.9357,0.1992,0.933 T0.1831,0.9291 Q0.1807,0.9287,0.1783,0.9285 T0.1739,0.928 T0.1703,0.9276 T0.1675,0.9272 T0.1655,0.9266 T0.1639,0.9253 Q0.1598,0.9216,0.1462,0.9216 L0.143,0.9216 L0.1398,0.9216 L0.1365,0.9216 Q0.1197,0.9195,0.1197,0.9154 L0.1197,0.9154 Q0.1205,0.9121,0.1373,0.9088 T0.1703,0.9054 Q0.1791,0.9054,0.1863,0.904 T0.1984,0.9011 T0.2064,0.8992 Q0.2402,0.8967,0.2506,0.8934 Q0.2538,0.8926,0.2602,0.8899 T0.2707,0.8872 Q0.2715,0.8872,0.2731,0.8876 Q0.2795,0.8884,0.2884,0.8884 Q0.3068,0.8884,0.3092,0.8826 L0.3092,0.8797 Q0.3092,0.8772,0.3076,0.8747 T0.3044,0.8706 T0.3028,0.8681 Q0.3076,0.8656,0.3237,0.8656 Q0.3317,0.8656,0.3622,0.8623 Q0.3655,0.8623,0.3867,0.8617 T0.4217,0.8602 Q0.4241,0.8598,0.4289,0.8598 L0.4345,0.8598 L0.441,0.8598 Q0.4578,0.8598,0.4667,0.8586 Q0.4739,0.8573,0.4851,0.8567 T0.5004,0.8552 Q0.5012,0.8552,0.5108,0.855 T0.5269,0.853 T0.5333,0.8461 Q0.5333,0.8445,0.5325,0.8432 T0.5309,0.8409 T0.5289,0.8391 T0.5261,0.8378 L0.5229,0.837 T0.5185,0.8362 T0.5133,0.8355 T0.5076,0.8349 T0.502,0.8341 Q0.4932,0.8324,0.4859,0.8324 Q0.4827,0.8324,0.4787,0.8328 T0.4731,0.8333 Q0.4707,0.8333,0.4683,0.8324 Q0.4482,0.825,0.4394,0.825 Q0.4369,0.825,0.4345,0.8254 Q0.4225,0.827,0.4153,0.827 Q0.4088,0.827,0.4064,0.8258 Q0.3976,0.8204,0.3751,0.8204 Q0.3703,0.8204,0.3667,0.8214 T0.3622,0.8225 Q0.3598,0.8225,0.3558,0.82 Q0.351,0.8175,0.3402,0.8167 T0.3177,0.8156 T0.3028,0.8146 L0.2956,0.8121 T0.2871,0.8094 T0.2771,0.8073 T0.2635,0.8063 Q0.261,0.8063,0.2586,0.8067 Q0.2378,0.8071,0.2273,0.8071 Q0.2072,0.8071,0.2024,0.8051 Q0.1976,0.8034,0.1932,0.8026 T0.1867,0.8013 T0.1847,0.8001 L0.1847,0.7993 Q0.1855,0.7984,0.1855,0.7978 T0.1859,0.7966 T0.1867,0.7959 T0.188,0.7955 T0.1896,0.7951 T0.1916,0.7949 T0.1944,0.7945 T0.1984,0.7941 T0.2032,0.7939 Q0.212,0.7926,0.2325,0.7918 T0.2562,0.7905 Q0.2691,0.7893,0.2787,0.7847 Q0.2835,0.7827,0.2968,0.7791 T0.3149,0.7756 Q0.3189,0.7756,0.3305,0.7733 T0.3446,0.771 L0.3454,0.771 Q0.3462,0.771,0.3498,0.7721 T0.3574,0.774 T0.3655,0.7748 Q0.3703,0.7748,0.3743,0.7731 T0.3823,0.7708 T0.3916,0.77 T0.3992,0.7694 Q0.4008,0.7694,0.4076,0.7688 T0.4205,0.7675 L0.4305,0.7665 Q0.4345,0.7657,0.4402,0.7617 T0.4458,0.7545 Q0.4458,0.7503,0.4353,0.7487 Q0.4297,0.7478,0.4209,0.7462 T0.4044,0.7433 T0.3904,0.7416 L0.3847,0.7416 Q0.3807,0.7416,0.3747,0.7422 T0.3663,0.7428 T0.3606,0.7424 Q0.3582,0.742,0.3562,0.7418 T0.3522,0.7412 T0.3482,0.7406 T0.3446,0.7401 T0.341,0.7397 T0.3373,0.7393 T0.3341,0.7391 L0.3293,0.7391 Q0.3277,0.7395,0.3253,0.7395 Q0.3205,0.7395,0.3052,0.7387 T0.2835,0.7379 Q0.2755,0.7379,0.253,0.7325 Q0.2506,0.7316,0.2462,0.731 T0.2382,0.7296 T0.2305,0.7271 T0.2181,0.7234 T0.2048,0.7196 T0.2,0.7155 Q0.2,0.7142,0.2016,0.713 Q0.208,0.7059,0.2285,0.6978 T0.2618,0.6898 Q0.2739,0.6898,0.2831,0.6848 T0.2956,0.6794 Q0.3028,0.6786,0.3289,0.6765 T0.3566,0.674 Q0.3582,0.674,0.3855,0.6723 T0.4406,0.6688 T0.4755,0.6653 Q0.5004,0.6607,0.5124,0.6599 L0.5165,0.6599 Q0.5213,0.6599,0.5277,0.6605 T0.5373,0.6611 Q0.543,0.6611,0.5494,0.6599 Q0.592,0.6524,0.6,0.6524 Q0.6064,0.6524,0.61,0.6499 T0.6161,0.6474 Q0.6177,0.6474,0.6261,0.6495 T0.6418,0.6516 L0.6482,0.6516 Q0.653,0.6512,0.6651,0.6506 T0.6876,0.6493 T0.7084,0.6477 T0.7229,0.645 Q0.7269,0.6437,0.7333,0.6437 Q0.7373,0.6437,0.7458,0.6443 T0.7574,0.645 Q0.7614,0.645,0.7631,0.6445 T0.7655,0.6441 Q0.7671,0.6441,0.7723,0.6448 T0.7823,0.6454 Q0.788,0.6454,0.7952,0.6445 Q0.8233,0.6408,0.8723,0.6367 Q0.9012,0.6342,0.9092,0.6342 L0.9165,0.6342 L0.9205,0.6342 Q0.9277,0.6342,0.9382,0.6323 T0.9526,0.6304 Q0.9534,0.6304,0.9594,0.6307 T0.9695,0.6309 Q0.9976,0.6309,0.9992,0.6234 Q1,0.6201,1,0.618 Q1,0.6089,0.9775,0.608 Q0.9703,0.6076,0.9582,0.6076 Q0.9518,0.6076,0.9394,0.6078 T0.9213,0.608 Q0.9036,0.608,0.8972,0.6072 Q0.894,0.6068,0.8871,0.6058 T0.8751,0.6039 T0.8627,0.6024 T0.8506,0.6018 L0.8474,0.6018 Q0.841,0.6022,0.8281,0.6041 T0.8112,0.606 Q0.8096,0.606,0.808,0.6056 Q0.7783,0.6002,0.7647,0.6002 Q0.7598,0.6002,0.7454,0.6006 T0.7261,0.601 Q0.7221,0.601,0.7145,0.6022 T0.7036,0.6035 Q0.6996,0.6035,0.6964,0.6022 Q0.6723,0.5902,0.6442,0.5902 Q0.6426,0.5902,0.6386,0.5904 T0.6337,0.5906 Q0.6273,0.5906,0.6249,0.5894 L0.6177,0.5856 T0.6116,0.5838 T0.6016,0.5825 T0.5855,0.5823 Q0.5719,0.5823,0.5574,0.5798 T0.5373,0.5774 L0.5341,0.5774 Q0.5293,0.5778,0.5213,0.5786 T0.51,0.5794 Q0.5036,0.5794,0.4988,0.5769 Q0.4932,0.574,0.4771,0.574 Q0.4723,0.574,0.4631,0.5745 T0.4498,0.5749 Q0.4418,0.5749,0.4394,0.5736 Q0.4353,0.5724,0.4305,0.5715 T0.4225,0.5707 T0.4145,0.5705 T0.4056,0.5699 Q0.4008,0.5691,0.398,0.5689 T0.394,0.5689 T0.3908,0.5686 T0.3855,0.5674 Q0.3831,0.567,0.3811,0.5662 T0.3779,0.5645 T0.3743,0.563 T0.3695,0.5624 Q0.3655,0.5624,0.3594,0.5635 T0.3494,0.5645 T0.3406,0.5635 T0.3325,0.5624 Q0.3309,0.5624,0.3301,0.5628 L0.3141,0.5628 Q0.2635,0.5628,0.2506,0.5612 Q0.2474,0.5608,0.2434,0.5608 Q0.2402,0.5608,0.2349,0.561 T0.2273,0.5612 Q0.2209,0.5612,0.2177,0.5595 Q0.2145,0.557,0.2145,0.5558 Q0.2169,0.5537,0.2345,0.5521 Q0.2876,0.5475,0.3028,0.5425 Q0.3068,0.5413,0.3149,0.5396 T0.3309,0.5367 T0.3478,0.5338 T0.3606,0.5309 Q0.3631,0.5305,0.3755,0.5259 T0.3968,0.5214 Q0.3992,0.5214,0.4024,0.5218 T0.4096,0.5222 Q0.4161,0.5222,0.4213,0.5209 T0.4313,0.5185 T0.4402,0.5172 Q0.4418,0.5168,0.447,0.5168 L0.4558,0.5168 T0.4639,0.5162 T0.4707,0.5141 T0.4731,0.5102 Q0.4731,0.5089,0.4723,0.5081 Q0.4699,0.5019,0.4627,0.4994 T0.4369,0.4965 Q0.4273,0.4961,0.4193,0.4946 T0.4048,0.4915 T0.3952,0.4894 Q0.392,0.4886,0.388,0.4886 Q0.3847,0.4886,0.3775,0.4892 T0.3647,0.4898 Q0.3598,0.4898,0.355,0.4894 Q0.3165,0.4861,0.3141,0.4853 Q0.2779,0.4774,0.2715,0.4774 Q0.2699,0.4774,0.2683,0.4776 T0.2627,0.4778 Q0.2498,0.4778,0.2281,0.4762 Q0.2241,0.4749,0.2233,0.4737 Q0.2249,0.472,0.2321,0.4708 Q0.2402,0.4695,0.2554,0.4695 L0.2675,0.4695 L0.2795,0.4695 Q0.294,0.4695,0.2996,0.4679 Q0.3141,0.4629,0.3373,0.4616 Q0.3414,0.4612,0.3466,0.4606 T0.3582,0.4589 T0.3687,0.4562 T0.3727,0.4525 Q0.3727,0.4509,0.3703,0.4492 Q0.3639,0.4455,0.3474,0.4419 T0.3213,0.4384 L0.3157,0.4384 L0.31,0.4384 Q0.294,0.4384,0.2859,0.4363 Q0.2795,0.4347,0.2691,0.4347 L0.2618,0.4347 L0.2546,0.4347 Q0.2458,0.4347,0.2394,0.4338 Q0.2257,0.4318,0.2193,0.4303 T0.2092,0.4266 T0.2052,0.4224 T0.2024,0.4154 T0.196,0.4052 Q0.1912,0.3994,0.1867,0.3949 T0.1807,0.3884 T0.1791,0.3849 Q0.1791,0.3824,0.1839,0.3745 Q0.1871,0.3687,0.1944,0.3637 T0.2072,0.3561 T0.2221,0.3501 T0.2321,0.3463 Q0.2353,0.3455,0.241,0.3445 T0.2522,0.3428 T0.2639,0.3411 T0.2731,0.3384 T0.2956,0.3351 T0.3197,0.3326 Q0.3213,0.3322,0.3237,0.3322 T0.3325,0.3331 T0.3414,0.3339 Q0.3478,0.3339,0.3518,0.3314 Q0.3606,0.3264,0.3703,0.3264 Q0.3775,0.3264,0.3847,0.3293 Q0.3888,0.331,0.396,0.331 Q0.4024,0.331,0.4116,0.3295 T0.4265,0.3266 T0.4414,0.3227 T0.4514,0.3202 Q0.4578,0.3181,0.4839,0.3152 T0.5112,0.3123 T0.5177,0.3119 T0.5285,0.3111 T0.5398,0.3096 T0.549,0.3065 T0.5526,0.3015 Q0.5526,0.2999,0.5518,0.2982 Q0.547,0.2903,0.5386,0.2883 T0.5165,0.2862 Q0.5116,0.2862,0.5056,0.2866 T0.4972,0.287 Q0.4884,0.287,0.4787,0.2833 Q0.4594,0.2758,0.4265,0.2758 Q0.4169,0.2758,0.4129,0.2766 Q0.4112,0.2771,0.4104,0.2771 Q0.4088,0.2771,0.4056,0.2748 T0.3968,0.2721 Q0.3695,0.2696,0.359,0.2659 Q0.355,0.2646,0.351,0.2646 T0.3426,0.2661 T0.3365,0.2675 T0.3333,0.2659 Q0.3277,0.2605,0.3116,0.2553 T0.2859,0.2501 L0.2843,0.2501 L0.2819,0.2501 Q0.2779,0.2501,0.2723,0.2493 T0.2586,0.2472 T0.2474,0.2455 Q0.1799,0.236,0.1719,0.236 Q0.1663,0.236,0.1606,0.235 T0.1502,0.2316 T0.1454,0.2265 Q0.1454,0.2223,0.1558,0.2173 Q0.1679,0.2115,0.1859,0.2092 T0.2185,0.207 T0.2353,0.2057 Q0.2369,0.2049,0.2502,0.2041 T0.2799,0.2022 T0.302,0.2007 Q0.3084,0.1999,0.3245,0.1954 T0.347,0.1908 Q0.3502,0.1908,0.3534,0.1916 Q0.3663,0.197,0.388,0.197 Q0.404,0.197,0.4088,0.1929 Q0.4096,0.192,0.4108,0.192 T0.4177,0.1945 T0.4277,0.197 T0.4369,0.1949 Q0.4442,0.1908,0.4843,0.1875 Q0.4892,0.1871,0.4968,0.1839 T0.5084,0.1808 L0.5108,0.1808 Q0.5213,0.1825,0.5454,0.1825 Q0.5823,0.1825,0.5992,0.1779 Q0.6096,0.1746,0.6217,0.1746 Q0.6249,0.1746,0.6309,0.1748 T0.6394,0.175 Q0.6434,0.175,0.6466,0.1746 Q0.6474,0.1746,0.6594,0.1738 T0.6787,0.1713 T0.6892,0.1659 Q0.6924,0.1634,0.6924,0.1593 Q0.6924,0.1547,0.6888,0.151 T0.6795,0.1472 Q0.6779,0.1472,0.6763,0.1477 Q0.6699,0.1493,0.6667,0.1493 Q0.661,0.1493,0.6506,0.1456 Q0.6329,0.1394,0.6129,0.1394 Q0.6048,0.1394,0.5992,0.1377 T0.5888,0.136 T0.5803,0.1377 T0.5751,0.1394 Q0.5711,0.1394,0.5639,0.1369 Q0.5365,0.1273,0.5116,0.1273 Q0.4876,0.1273,0.4667,0.1253 Q0.4627,0.1248,0.4542,0.1211 T0.4337,0.1174 L0.4305,0.1174 Q0.4217,0.1178,0.4153,0.119 T0.408,0.1203 Q0.4056,0.1203,0.4,0.117 Q0.3976,0.1153,0.396,0.1136 T0.3932,0.1112 T0.39,0.1099 T0.3847,0.1095 Q0.3799,0.1095,0.3687,0.1103 Q0.355,0.1112,0.3434,0.1141 T0.3301,0.117 Q0.3261,0.117,0.3229,0.1145 Q0.3181,0.1116,0.3052,0.1087 T0.2787,0.1043 T0.2546,0.1029 Q0.2506,0.1029,0.2474,0.1033 Q0.2418,0.1037,0.2349,0.1054 T0.2249,0.107 Q0.2225,0.107,0.2193,0.1066 Q0.2024,0.1024,0.1863,0.1024 Q0.1823,0.1024,0.1562,0.0971 T0.1269,0.0917 T0.1161,0.0896 T0.106,0.0854 Q0.1076,0.0846,0.11,0.0842 Q0.1173,0.0821,0.1261,0.0821 Q0.1301,0.0821,0.1369,0.0825 T0.1462,0.083 Q0.1494,0.083,0.151,0.0825 T0.1831,0.0813 T0.2201,0.0784 T0.243,0.074 T0.2667,0.0718 Q0.2707,0.0718,0.2755,0.0724 T0.2843,0.073 T0.2936,0.0724 T0.3024,0.0705 T0.3072,0.068 T0.3092,0.0657 T0.3108,0.0647 Q0.3116,0.0643,0.3133,0.0643 L0.3157,0.0643 L0.3181,0.0643 Q0.3446,0.0643,0.3446,0.0556 T0.3205,0.0469 L0.3189,0.0469 Q0.3157,0.0469,0.3157,0.0465 Q0.3084,0.0427,0.2835,0.0386 Q0.2739,0.0369,0.2675,0.0342 T0.2538,0.0299 Q0.249,0.029,0.245,0.029 T0.2378,0.0303 T0.2321,0.0315 Q0.2305,0.0315,0.2273,0.0307 Q0.2201,0.0286,0.2004,0.0272 T0.1775,0.0245 Q0.1735,0.0228,0.1663,0.0226 T0.1522,0.0197 T0.1345,0.0145 T0.1124,0.0079 Q0.0996,0.0033,0.0671,0.0044 T0.0297,0.0041 Q0.0169,0,0.0024,0.0017 Q0,0.0021,0,0.0037 Z M0.0064,0.9954 L0.0056,0.0058 Q0.0177,0.005,0.0281,0.0083 Q0.0345,0.01,0.0675,0.0089 T0.1108,0.012 Q0.1221,0.0162,0.1329,0.0187 T0.1498,0.0236 T0.1606,0.0268 T0.1691,0.0274 T0.1759,0.0286 Q0.1807,0.0303,0.2,0.0315 T0.2257,0.0348 Q0.2305,0.0361,0.2337,0.0361 Q0.2361,0.0361,0.2398,0.0348 T0.2466,0.0336 Q0.249,0.0336,0.2522,0.034 Q0.2586,0.0353,0.2655,0.0382 T0.2827,0.0427 Q0.3068,0.0469,0.3124,0.0502 Q0.3157,0.0523,0.3237,0.0523 L0.3277,0.0523 L0.3309,0.0523 Q0.3398,0.0523,0.3398,0.0556 Q0.3398,0.0581,0.3341,0.0587 T0.3205,0.0593 T0.3092,0.0603 T0.3036,0.0643 T0.2932,0.068 Q0.2884,0.0684,0.2843,0.0684 Q0.2811,0.0684,0.2763,0.0678 T0.2667,0.0672 Q0.2586,0.0672,0.2422,0.0697 T0.2177,0.0747 Q0.2137,0.0759,0.1831,0.0767 T0.1494,0.0784 L0.1474,0.0784 T0.1398,0.078 T0.1285,0.0776 Q0.1173,0.0776,0.1084,0.08 Q0.1004,0.0817,0.1004,0.0846 Q0.1004,0.0867,0.104,0.0888 T0.1124,0.0923 T0.1213,0.0948 T0.1269,0.0958 T0.155,0.1014 T0.1863,0.107 Q0.2016,0.107,0.2185,0.1107 Q0.2217,0.1116,0.2257,0.1116 Q0.2289,0.1116,0.2365,0.1099 T0.2482,0.1074 L0.2538,0.1074 Q0.2683,0.1074,0.2912,0.1107 T0.3197,0.1182 Q0.3245,0.1215,0.3301,0.1215 Q0.3333,0.1215,0.3454,0.1184 T0.3687,0.1145 Q0.3735,0.1141,0.3779,0.1139 T0.3843,0.1134 T0.3871,0.1132 Q0.3896,0.1132,0.39,0.1136 T0.392,0.1161 T0.3968,0.1203 Q0.404,0.1248,0.408,0.1248 Q0.4096,0.1248,0.4165,0.1234 T0.4305,0.1215 L0.4337,0.1215 Q0.4442,0.1215,0.4518,0.1251 T0.4659,0.1294 Q0.4876,0.1319,0.5116,0.1319 T0.5622,0.141 Q0.5703,0.1439,0.5751,0.1439 Q0.5791,0.1439,0.5831,0.1423 T0.5888,0.1406 Q0.592,0.1406,0.5944,0.1412 T0.6012,0.1427 T0.6129,0.1435 Q0.6313,0.1435,0.649,0.1497 Q0.6602,0.1535,0.6667,0.1535 Q0.6715,0.1535,0.6779,0.1522 Q0.6787,0.1518,0.6795,0.1518 Q0.6827,0.1518,0.6847,0.1541 T0.6867,0.1593 Q0.6867,0.1618,0.6851,0.1634 Q0.6835,0.1655,0.6775,0.1667 T0.6598,0.169 T0.6458,0.1705 T0.6402,0.1709 Q0.6378,0.1709,0.6321,0.1705 T0.6225,0.1701 Q0.6096,0.1701,0.5968,0.1738 Q0.5823,0.1779,0.5446,0.1779 Q0.5205,0.1779,0.5116,0.1767 Q0.51,0.1763,0.5084,0.1763 Q0.5028,0.1763,0.4944,0.1796 T0.4835,0.1829 L0.4771,0.1833 T0.4659,0.1844 L0.4546,0.1858 T0.443,0.1881 T0.4337,0.1912 Q0.4321,0.1916,0.4305,0.1916 Q0.4273,0.1916,0.4209,0.1895 T0.412,0.1875 Q0.408,0.1875,0.4056,0.19 Q0.4024,0.1925,0.388,0.1925 Q0.3663,0.1925,0.3558,0.1879 Q0.3518,0.1862,0.3462,0.1862 Q0.3373,0.1862,0.3213,0.191 T0.3012,0.1962 Q0.2964,0.1966,0.2799,0.1976 T0.2494,0.1997 T0.2313,0.2024 L0.2313,0.2024 L0.2293,0.2024 L0.2261,0.2024 L0.2217,0.2024 Q0.204,0.2024,0.1851,0.2047 T0.1526,0.2136 Q0.1406,0.2198,0.1406,0.226 Q0.1406,0.2319,0.1518,0.2362 T0.1719,0.2406 Q0.1791,0.2406,0.2466,0.2497 Q0.2498,0.2501,0.2574,0.2516 T0.2711,0.2538 T0.2819,0.2547 L0.2851,0.2547 Q0.2851,0.2543,0.2859,0.2543 Q0.294,0.2543,0.3096,0.2592 T0.3285,0.2684 Q0.3317,0.2721,0.3373,0.2721 Q0.3406,0.2721,0.3462,0.2708 T0.3542,0.2696 Q0.3558,0.2696,0.3574,0.27 Q0.3679,0.2737,0.3968,0.2762 Q0.3984,0.2766,0.4004,0.2779 L0.4044,0.2804 T0.4096,0.2816 Q0.412,0.2816,0.4145,0.2808 Q0.4177,0.2804,0.4249,0.2804 Q0.4586,0.2804,0.4763,0.287 Q0.4867,0.2912,0.4972,0.2912 Q0.5004,0.2912,0.5064,0.2908 T0.5165,0.2903 Q0.5293,0.2903,0.5361,0.2922 T0.547,0.3003 L0.547,0.3011 Q0.547,0.3036,0.5414,0.3049 T0.5249,0.3067 T0.51,0.3082 Q0.5092,0.3082,0.4831,0.3111 T0.4498,0.3161 Q0.408,0.3264,0.3952,0.3264 Q0.3896,0.3264,0.3863,0.3252 Q0.3783,0.3223,0.3703,0.3223 Q0.359,0.3223,0.3494,0.3277 Q0.347,0.3293,0.343,0.3293 Q0.3414,0.3293,0.3353,0.3285 T0.3253,0.3277 T0.3181,0.3285 Q0.3149,0.3289,0.2952,0.3306 T0.2699,0.3347 Q0.2675,0.3364,0.2598,0.3372 T0.2434,0.3395 T0.2297,0.3426 L0.2205,0.3461 T0.2048,0.3523 T0.1908,0.3606 T0.1791,0.3725 Q0.1743,0.3803,0.1743,0.3845 Q0.1743,0.3874,0.1763,0.3901 T0.1827,0.3976 T0.1912,0.4073 T0.198,0.4189 T0.2032,0.4276 T0.2137,0.4326 T0.2386,0.438 Q0.2458,0.4392,0.2562,0.4392 L0.2639,0.4392 L0.2699,0.4392 Q0.2795,0.4392,0.2843,0.4405 Q0.2932,0.443,0.3092,0.443 L0.3157,0.443 L0.3213,0.443 Q0.3293,0.443,0.3458,0.4463 T0.3671,0.4525 Q0.3598,0.4558,0.3373,0.4571 Q0.3124,0.4587,0.298,0.4637 Q0.294,0.465,0.2811,0.465 L0.2703,0.465 L0.2586,0.465 Q0.241,0.465,0.2305,0.4666 Q0.2185,0.4687,0.2185,0.4728 Q0.2185,0.4745,0.2205,0.4764 T0.2241,0.4791 L0.2265,0.4803 L0.2273,0.4803 Q0.2506,0.4824,0.261,0.4824 Q0.2675,0.4824,0.2699,0.482 Q0.2699,0.4815,0.2707,0.4815 Q0.2771,0.4815,0.3124,0.4898 Q0.3173,0.4907,0.3229,0.4913 T0.3386,0.4925 T0.3542,0.4936 T0.3663,0.494 Q0.3711,0.494,0.3787,0.4936 T0.3888,0.4932 Q0.392,0.4932,0.3944,0.4936 T0.4032,0.4959 T0.4181,0.499 T0.4369,0.5006 Q0.449,0.501,0.455,0.5021 T0.4639,0.5048 T0.4675,0.5093 L0.4675,0.5097 Q0.4675,0.5106,0.4663,0.511 T0.4622,0.5116 L0.4566,0.512 T0.4486,0.5124 T0.4402,0.5127 Q0.4345,0.5131,0.4257,0.5153 T0.4096,0.5176 Q0.4064,0.5176,0.4024,0.5172 L0.3968,0.5172 Q0.3863,0.5172,0.3731,0.522 T0.359,0.5268 Q0.355,0.528,0.3317,0.5321 T0.3004,0.5388 Q0.2867,0.5433,0.2337,0.5479 Q0.2096,0.55,0.2096,0.5562 Q0.2096,0.5591,0.2145,0.5628 Q0.2193,0.5662,0.2289,0.5662 Q0.2313,0.5662,0.2373,0.5657 T0.2458,0.5653 L0.2498,0.5653 Q0.2635,0.5674,0.3124,0.5674 Q0.3261,0.5674,0.3301,0.567 L0.3317,0.567 Q0.3341,0.567,0.3394,0.568 T0.3486,0.5691 Q0.3542,0.5691,0.3606,0.5678 T0.3699,0.5666 T0.3759,0.5686 T0.3839,0.5718 T0.3908,0.573 T0.3964,0.5734 T0.4048,0.5742 T0.4165,0.5751 T0.4277,0.5757 T0.4369,0.5778 Q0.4426,0.5794,0.4538,0.5794 Q0.4586,0.5794,0.4679,0.5792 T0.4811,0.579 Q0.4924,0.579,0.4956,0.5807 Q0.502,0.584,0.5108,0.584 L0.5137,0.584 T0.5173,0.5838 L0.5205,0.5834 L0.5237,0.583 L0.5269,0.5825 T0.5305,0.5821 T0.5341,0.5819 L0.5365,0.5819 Q0.5414,0.5819,0.5562,0.5842 T0.5855,0.5865 Q0.5912,0.5865,0.5948,0.5867 T0.6012,0.5869 T0.6064,0.5873 T0.61,0.5877 T0.6133,0.5886 T0.6157,0.5896 L0.6185,0.591 T0.6217,0.5931 Q0.6265,0.5952,0.6369,0.5952 L0.6418,0.5952 L0.6466,0.5952 Q0.6723,0.5952,0.6932,0.606 Q0.698,0.608,0.7044,0.608 Q0.7084,0.608,0.7161,0.6068 T0.7261,0.6056 Q0.7309,0.6056,0.7454,0.6051 T0.7647,0.6047 Q0.7775,0.6047,0.8072,0.6101 Q0.8096,0.6105,0.812,0.6105 Q0.8177,0.6105,0.8297,0.6085 T0.8474,0.606 L0.8506,0.606 Q0.8562,0.606,0.8647,0.607 T0.8831,0.6095 T0.8964,0.6118 Q0.9036,0.6122,0.9221,0.6122 L0.9406,0.6122 L0.959,0.6122 L0.9767,0.6122 Q0.9952,0.613,0.9952,0.6172 Q0.9952,0.6176,0.9944,0.6221 Q0.9936,0.6259,0.9574,0.6259 L0.955,0.6259 L0.9526,0.6259 Q0.9478,0.6259,0.9373,0.628 T0.9205,0.63 L0.9173,0.63 Q0.9133,0.6296,0.9084,0.6296 Q0.9004,0.6296,0.8715,0.6321 Q0.8233,0.6367,0.7944,0.64 Q0.7888,0.6408,0.7839,0.6408 T0.7739,0.6402 T0.7671,0.6396 Q0.7639,0.6396,0.7622,0.64 Q0.7614,0.6404,0.759,0.6404 Q0.7574,0.6404,0.749,0.6398 T0.7357,0.6392 Q0.7261,0.6392,0.7213,0.6412 Q0.7173,0.6425,0.7032,0.6437 T0.6711,0.6458 T0.6474,0.647 Q0.645,0.6474,0.6418,0.6474 Q0.6353,0.6474,0.6277,0.6454 T0.6161,0.6433 Q0.6104,0.6433,0.6072,0.6456 T0.6,0.6479 Q0.5912,0.6479,0.5486,0.6557 Q0.5422,0.657,0.5373,0.657 Q0.5349,0.657,0.5289,0.6564 T0.5173,0.6557 L0.5124,0.6557 Q0.4996,0.6562,0.4739,0.6611 Q0.4675,0.6624,0.4398,0.6645 T0.3851,0.668 T0.3558,0.6698 Q0.355,0.6698,0.3285,0.6721 T0.2948,0.6752 Q0.2892,0.6761,0.2843,0.6786 T0.2747,0.6831 T0.2618,0.6852 Q0.253,0.6852,0.2394,0.6893 T0.2141,0.6993 T0.1968,0.7105 Q0.1944,0.713,0.1944,0.7155 Q0.1944,0.7196,0.2012,0.7229 T0.2177,0.7285 T0.2281,0.7308 Q0.2321,0.7329,0.2365,0.7339 T0.245,0.7356 T0.2514,0.7366 Q0.2522,0.737,0.2554,0.7379 T0.2614,0.7393 T0.2679,0.7406 T0.2755,0.7416 T0.2835,0.742 Q0.29,0.742,0.3048,0.7428 T0.3253,0.7437 L0.3301,0.7437 L0.3333,0.7437 Q0.3341,0.7437,0.3598,0.7466 Q0.3639,0.7474,0.3671,0.7474 T0.3759,0.7466 T0.3855,0.7457 Q0.388,0.7457,0.3904,0.7462 Q0.3984,0.7466,0.4137,0.7495 T0.4349,0.7532 T0.441,0.7557 Q0.441,0.7599,0.4289,0.7619 Q0.4257,0.7628,0.4197,0.7634 T0.4072,0.7644 T0.3984,0.7652 Q0.3968,0.7652,0.3912,0.7655 T0.3811,0.7665 T0.3719,0.7694 Q0.3695,0.7702,0.3655,0.7702 T0.3546,0.7686 T0.3462,0.7665 L0.3446,0.7665 Q0.3406,0.7665,0.3293,0.769 T0.3149,0.7715 Q0.3084,0.7715,0.2952,0.7748 T0.2763,0.781 Q0.2675,0.7847,0.2554,0.7864 Q0.253,0.7864,0.2325,0.7874 T0.2024,0.7893 Q0.2008,0.7893,0.1984,0.7897 T0.1948,0.7901 T0.1916,0.7903 T0.1888,0.7908 T0.1867,0.7914 T0.1847,0.7922 L0.1831,0.793 T0.1819,0.7943 L0.1811,0.7959 T0.1799,0.7976 Q0.1791,0.7993,0.1791,0.8001 Q0.1791,0.8034,0.1831,0.8051 T0.1932,0.8078 T0.2,0.8092 Q0.2072,0.8117,0.2257,0.8117 Q0.2337,0.8117,0.2586,0.8109 L0.2635,0.8109 Q0.2707,0.8109,0.2767,0.8117 T0.2859,0.8136 T0.2936,0.8163 T0.3012,0.8187 Q0.3068,0.82,0.3277,0.8206 T0.3526,0.8237 Q0.3582,0.8266,0.3622,0.8266 Q0.3647,0.8266,0.3687,0.8258 T0.3751,0.825 Q0.396,0.825,0.404,0.8291 Q0.408,0.8316,0.4161,0.8316 Q0.4217,0.8316,0.4353,0.8295 L0.4394,0.8295 Q0.4466,0.8295,0.4659,0.8366 Q0.4699,0.8378,0.4747,0.8378 Q0.4763,0.8378,0.4807,0.8374 T0.4884,0.837 Q0.494,0.837,0.5004,0.8382 Q0.5052,0.8391,0.5124,0.8399 Q0.5221,0.8411,0.5249,0.8422 T0.5277,0.8461 Q0.5277,0.8482,0.5233,0.849 T0.5112,0.8501 T0.4996,0.8511 Q0.4964,0.8519,0.4851,0.8523 T0.4659,0.8544 Q0.4578,0.8557,0.4402,0.8557 L0.4349,0.8557 L0.4297,0.8557 L0.4209,0.8557 Q0.408,0.8565,0.3867,0.8571 T0.3614,0.8582 Q0.3317,0.8611,0.3237,0.8611 L0.3225,0.8611 L0.3213,0.8611 Q0.2972,0.8611,0.2972,0.8681 Q0.2972,0.8714,0.3008,0.8747 T0.3044,0.8805 Q0.3044,0.881,0.3036,0.8814 Q0.3012,0.8839,0.2851,0.8839 Q0.2779,0.8839,0.2739,0.883 Q0.2723,0.8826,0.2699,0.8826 T0.2643,0.8835 T0.259,0.8853 T0.2542,0.8876 T0.249,0.8893 Q0.2386,0.8922,0.2056,0.8946 Q0.2016,0.8951,0.1916,0.898 T0.1703,0.9009 Q0.1518,0.9009,0.1337,0.9044 T0.1149,0.9146 L0.1149,0.9158 Q0.1149,0.9175,0.1157,0.9187 T0.1173,0.921 T0.1201,0.9226 T0.1233,0.9237 T0.1269,0.9245 L0.1305,0.9251 T0.1337,0.9255 L0.1357,0.9258 Q0.139,0.9262,0.1446,0.9262 T0.1542,0.9266 T0.1598,0.9282 Q0.1614,0.9303,0.1647,0.9314 T0.1739,0.9328 T0.1815,0.9336 Q0.1863,0.9345,0.1976,0.9372 T0.2145,0.9415 Q0.2225,0.9432,0.2285,0.9467 T0.2378,0.9515 Q0.2426,0.9531,0.2514,0.9531 Q0.2538,0.9531,0.2602,0.9529 T0.2707,0.9527 Q0.2763,0.9527,0.2815,0.9531 T0.2867,0.9569 T0.2811,0.9614 Q0.2434,0.9627,0.2329,0.9652 Q0.2249,0.9672,0.2241,0.9672 L0.2096,0.9668 L0.2056,0.9668 Q0.2,0.9668,0.196,0.9672 T0.1884,0.9687 T0.1815,0.9708 T0.1731,0.9737 T0.1639,0.9772 Q0.1518,0.9805,0.1285,0.9805 L0.1253,0.9805 Q0.1124,0.9805,0.1076,0.9818 Q0.1012,0.983,0.0831,0.9876 T0.0594,0.9934 Q0.049,0.9954,0.0064,0.9954 Z M0.0032,0.9979 L0.0024,0.0037 Q0.0161,0.0021,0.0289,0.0062 Q0.0337,0.0075,0.0671,0.0066 T0.1116,0.01 T0.1337,0.0166 T0.151,0.022 Q0.1582,0.0249,0.1659,0.0251 T0.1767,0.0265 Q0.1807,0.0278,0.2,0.0292 T0.2265,0.0328 Q0.2305,0.034,0.2329,0.034 T0.239,0.0326 T0.2458,0.0311 T0.253,0.0319 Q0.2578,0.0332,0.2614,0.0342 T0.2667,0.0363 T0.2723,0.0384 T0.2827,0.0406 Q0.3084,0.0448,0.3141,0.0485 Q0.3149,0.0494,0.3225,0.0494 T0.3361,0.0504 T0.3422,0.0554 T0.3357,0.0606 T0.3205,0.0618 T0.31,0.0626 Q0.3092,0.0626,0.3056,0.0659 T0.2932,0.0701 Q0.2884,0.0709,0.2843,0.0709 Q0.2811,0.0709,0.2759,0.0701 T0.2667,0.0693 Q0.2586,0.0693,0.2422,0.0718 T0.2193,0.0763 Q0.2137,0.0784,0.1831,0.079 T0.1502,0.0805 Q0.1494,0.0809,0.147,0.0809 Q0.1454,0.0809,0.1386,0.0803 T0.1277,0.0796 Q0.1173,0.0796,0.1092,0.0821 Q0.1036,0.0834,0.1036,0.0854 Q0.1036,0.0879,0.1129,0.0908 T0.1269,0.0937 Q0.1293,0.0937,0.1558,0.0991 T0.1863,0.1045 Q0.2016,0.1045,0.2193,0.1087 Q0.2225,0.1091,0.2249,0.1091 Q0.2289,0.1091,0.2357,0.1076 T0.2482,0.1054 Q0.2506,0.1049,0.2546,0.1049 Q0.2699,0.1049,0.2924,0.1085 T0.3213,0.1165 Q0.3253,0.119,0.3301,0.119 Q0.3325,0.119,0.3446,0.1161 T0.3687,0.1124 Q0.3831,0.1112,0.3863,0.1112 T0.3908,0.1118 T0.3936,0.1145 T0.3984,0.1186 Q0.4048,0.1228,0.408,0.1228 Q0.4096,0.1228,0.4161,0.1213 T0.4305,0.1195 L0.4337,0.1195 Q0.4426,0.1195,0.4474,0.1211 T0.4566,0.1246 T0.4667,0.1273 Q0.4876,0.1298,0.5116,0.1298 Q0.5365,0.1298,0.5631,0.1389 Q0.5703,0.1414,0.5751,0.1414 Q0.5783,0.1414,0.5819,0.1398 T0.5888,0.1381 Q0.5928,0.1381,0.5984,0.1398 T0.6129,0.1414 Q0.6321,0.1414,0.6498,0.1477 Q0.661,0.1514,0.6667,0.1514 Q0.6707,0.1514,0.6771,0.1501 Q0.6787,0.1497,0.6795,0.1497 Q0.6835,0.1497,0.6863,0.1526 T0.6892,0.1593 Q0.6892,0.1626,0.6876,0.1647 Q0.6851,0.1672,0.6799,0.1686 T0.6695,0.1705 T0.6566,0.1715 T0.6458,0.1725 Q0.6434,0.173,0.6402,0.173 Q0.6378,0.173,0.6317,0.1727 T0.6225,0.1725 Q0.6096,0.1725,0.5984,0.1759 Q0.5823,0.1804,0.5454,0.1804 Q0.5205,0.1804,0.5116,0.1788 L0.5084,0.1788 Q0.5036,0.1788,0.4956,0.1819 T0.4843,0.185 Q0.4434,0.1887,0.4353,0.1929 Q0.4321,0.1945,0.4289,0.1945 T0.4197,0.1922 T0.4112,0.19 T0.4072,0.1912 Q0.4032,0.1949,0.388,0.1949 Q0.3663,0.1949,0.3542,0.19 Q0.351,0.1883,0.3462,0.1883 Q0.3406,0.1883,0.3321,0.1906 T0.3153,0.1954 T0.302,0.1983 Q0.2972,0.1991,0.2859,0.1997 T0.2643,0.201 T0.245,0.2024 T0.2337,0.2041 Q0.2321,0.2045,0.2181,0.2045 T0.1851,0.207 T0.1542,0.2157 Q0.143,0.2211,0.143,0.226 Q0.143,0.2314,0.153,0.2348 T0.1719,0.2381 Q0.1791,0.2381,0.2474,0.2476 Q0.2506,0.248,0.2558,0.2489 T0.2651,0.2505 T0.2739,0.252 T0.2819,0.2526 Q0.2835,0.2526,0.2851,0.2522 L0.2859,0.2522 Q0.2948,0.2522,0.3104,0.2574 T0.3309,0.2671 Q0.3333,0.2696,0.3365,0.2696 Q0.339,0.2696,0.3442,0.2684 T0.3526,0.2671 T0.3582,0.2679 Q0.3687,0.2717,0.3968,0.2742 Q0.4,0.2746,0.4036,0.2771 T0.4096,0.2796 Q0.4112,0.2796,0.4137,0.2787 Q0.4169,0.2779,0.4257,0.2779 Q0.4594,0.2779,0.4779,0.2849 Q0.4876,0.2891,0.4972,0.2891 Q0.4996,0.2891,0.506,0.2887 T0.5165,0.2883 Q0.5253,0.2883,0.5305,0.2889 T0.541,0.292 T0.5494,0.299 L0.5494,0.3015 Q0.5494,0.3036,0.5466,0.3051 T0.5386,0.3073 T0.5285,0.3086 T0.5181,0.3094 T0.5108,0.3102 Q0.5068,0.3107,0.494,0.3121 L0.4683,0.315 T0.4506,0.3181 Q0.449,0.3181,0.4426,0.32 T0.4313,0.3229 T0.4197,0.3256 T0.4064,0.3281 T0.3952,0.3289 Q0.3888,0.3289,0.3855,0.3273 Q0.3783,0.3243,0.3703,0.3243 Q0.3598,0.3243,0.351,0.3297 Q0.3478,0.3314,0.3422,0.3314 Q0.3398,0.3314,0.3337,0.3306 T0.3245,0.3297 T0.3189,0.3306 Q0.3149,0.3314,0.2956,0.3328 T0.2715,0.3368 Q0.2691,0.338,0.2635,0.3391 T0.2522,0.3407 T0.2406,0.3424 T0.2313,0.3447 Q0.2305,0.3447,0.2213,0.348 T0.206,0.354 T0.1924,0.3621 T0.1815,0.3733 Q0.1767,0.3816,0.1767,0.3849 Q0.1767,0.387,0.1783,0.3893 T0.1847,0.3961 T0.1936,0.4063 T0.2004,0.4181 T0.2052,0.4264 T0.2145,0.4309 T0.2386,0.4359 Q0.2458,0.4372,0.2554,0.4372 Q0.2578,0.4372,0.2627,0.437 T0.2699,0.4367 Q0.2795,0.4367,0.2851,0.4384 Q0.294,0.4409,0.3092,0.4409 Q0.3116,0.4409,0.3157,0.4407 T0.3213,0.4405 Q0.3301,0.4405,0.3466,0.444 T0.3687,0.4509 Q0.3703,0.4517,0.3703,0.4525 Q0.3703,0.4575,0.3373,0.4596 Q0.3133,0.4608,0.2988,0.4658 Q0.294,0.4674,0.2803,0.4674 Q0.2763,0.4674,0.2687,0.4672 T0.257,0.467 Q0.2402,0.467,0.2313,0.4687 Q0.2209,0.4708,0.2209,0.4737 Q0.2209,0.4741,0.2213,0.4749 T0.2229,0.4764 L0.2249,0.4774 T0.2265,0.4778 L0.2273,0.4782 Q0.2498,0.4803,0.2618,0.4803 Q0.2667,0.4803,0.2691,0.4799 Q0.2699,0.4795,0.2715,0.4795 Q0.2779,0.4795,0.3133,0.4873 Q0.3181,0.4886,0.3233,0.4892 T0.3386,0.4905 T0.3542,0.4915 T0.3655,0.4919 Q0.3703,0.4919,0.3779,0.4915 T0.3888,0.4911 T0.3952,0.4915 Q0.3976,0.4919,0.4008,0.4927 L0.4072,0.4944 T0.4149,0.4961 T0.4249,0.4975 T0.4369,0.4985 Q0.449,0.499,0.4554,0.5 T0.4651,0.5031 T0.4699,0.5085 Q0.4707,0.5093,0.4707,0.5097 Q0.4707,0.5114,0.4687,0.5124 T0.4631,0.5139 T0.4562,0.5145 T0.4478,0.5147 L0.4402,0.5147 Q0.4353,0.5151,0.4265,0.5174 T0.4096,0.5197 L0.4024,0.5197 Q0.4,0.5193,0.3968,0.5193 Q0.3896,0.5193,0.3831,0.5209 T0.3703,0.5253 T0.3598,0.5288 Q0.3558,0.5301,0.3321,0.5342 T0.302,0.5409 Q0.2876,0.5454,0.2337,0.55 Q0.2112,0.5521,0.2112,0.5558 Q0.2112,0.5574,0.2161,0.5612 Q0.2201,0.5637,0.2281,0.5637 Q0.2305,0.5637,0.2361,0.5633 T0.2442,0.5628 Q0.2474,0.5628,0.2498,0.5633 Q0.2635,0.5653,0.3133,0.5653 Q0.3261,0.5653,0.3301,0.5649 L0.3325,0.5649 Q0.3349,0.5649,0.3402,0.5657 T0.3494,0.5666 Q0.3542,0.5666,0.3598,0.5657 Q0.3663,0.5645,0.3695,0.5645 Q0.3735,0.5645,0.3775,0.5666 T0.3847,0.5695 Q0.3888,0.5707,0.3912,0.5709 T0.3968,0.5711 T0.4048,0.572 Q0.4096,0.5724,0.4165,0.5726 T0.4281,0.5734 T0.4378,0.5757 Q0.4418,0.5774,0.4522,0.5774 Q0.4562,0.5774,0.4655,0.5769 T0.4795,0.5765 Q0.4932,0.5765,0.4972,0.579 Q0.5028,0.5819,0.51,0.5819 Q0.5141,0.5819,0.5221,0.5809 T0.5341,0.5798 L0.5373,0.5798 Q0.5422,0.5798,0.557,0.5821 T0.5855,0.5844 L0.5936,0.5844 L0.5992,0.5844 T0.604,0.5848 T0.6076,0.5852 T0.6108,0.5859 T0.6137,0.5865 T0.6161,0.5873 T0.6181,0.5881 T0.6209,0.5896 L0.6233,0.591 Q0.6265,0.5927,0.6353,0.5927 L0.6402,0.5927 L0.645,0.5927 Q0.6723,0.5927,0.6948,0.6039 Q0.6988,0.606,0.7036,0.606 Q0.7076,0.606,0.7153,0.6045 T0.7261,0.6031 Q0.7309,0.6031,0.7454,0.6027 T0.7647,0.6022 Q0.7783,0.6022,0.808,0.608 L0.812,0.608 Q0.8169,0.608,0.8293,0.6062 T0.8474,0.6039 L0.8506,0.6039 Q0.857,0.6039,0.8655,0.6049 T0.8835,0.6074 T0.8972,0.6093 Q0.9036,0.6101,0.9213,0.6101 L0.9398,0.6101 L0.959,0.6101 L0.9767,0.6101 Q0.9976,0.6109,0.9976,0.6176 Q0.9976,0.6188,0.9968,0.6226 Q0.9952,0.6284,0.9671,0.6284 L0.959,0.6284 L0.9526,0.6284 Q0.9486,0.6284,0.9382,0.6302 T0.9205,0.6321 L0.9173,0.6321 Q0.9133,0.6317,0.9092,0.6317 Q0.9012,0.6317,0.8715,0.6346 Q0.8233,0.6387,0.7944,0.6421 Q0.7888,0.6429,0.7831,0.6429 Q0.7783,0.6429,0.7731,0.6425 T0.7663,0.6421 L0.7622,0.6421 Q0.7614,0.6425,0.7586,0.6425 T0.7474,0.6421 T0.7349,0.6416 Q0.7261,0.6416,0.7221,0.6429 Q0.7173,0.645,0.7032,0.646 T0.6711,0.6479 T0.6482,0.6491 Q0.645,0.6495,0.6418,0.6495 Q0.6353,0.6495,0.6273,0.6474 T0.6161,0.6454 Q0.612,0.6454,0.6088,0.6477 T0.6,0.6499 Q0.5912,0.6499,0.5494,0.6578 Q0.5422,0.6591,0.5373,0.6591 Q0.5341,0.6591,0.5281,0.6584 T0.5173,0.6578 L0.5124,0.6578 Q0.5004,0.6582,0.4747,0.6632 Q0.4675,0.6649,0.4398,0.6667 T0.3851,0.6703 T0.3566,0.6719 Q0.3534,0.6723,0.3406,0.6734 T0.3141,0.6757 T0.2948,0.6773 Q0.2908,0.6781,0.2819,0.6829 T0.2618,0.6877 Q0.2482,0.6877,0.2273,0.6958 T0.1992,0.7117 Q0.1976,0.7138,0.1976,0.7155 Q0.1976,0.7188,0.2032,0.7213 T0.2181,0.7258 T0.2289,0.7292 Q0.2337,0.7308,0.2378,0.7316 L0.2458,0.7333 T0.2522,0.7345 Q0.2747,0.7399,0.2835,0.7399 Q0.29,0.7399,0.3048,0.7408 T0.3253,0.7416 Q0.3277,0.7416,0.3297,0.7414 T0.3341,0.7412 L0.3598,0.7445 Q0.3639,0.7449,0.3663,0.7449 Q0.3695,0.7449,0.3751,0.7443 T0.3855,0.7437 L0.3904,0.7437 Q0.3984,0.7441,0.4141,0.7472 T0.4353,0.7507 Q0.4434,0.752,0.4434,0.7553 Q0.4434,0.7578,0.439,0.7605 T0.4297,0.764 Q0.4257,0.7648,0.4201,0.7655 T0.4076,0.7665 T0.3984,0.7673 Q0.3968,0.7673,0.3912,0.7677 T0.3815,0.7688 T0.3735,0.771 T0.3655,0.7727 Q0.3606,0.7727,0.3534,0.7708 T0.3458,0.769 L0.3446,0.769 Q0.3414,0.769,0.3297,0.7713 T0.3149,0.7735 Q0.3092,0.7735,0.296,0.7771 T0.2771,0.7831 Q0.2683,0.7872,0.2554,0.7885 Q0.253,0.7889,0.2325,0.7897 T0.2032,0.7914 Q0.2008,0.7918,0.198,0.792 T0.194,0.7924 T0.1912,0.7928 T0.1888,0.793 T0.1867,0.7934 T0.1851,0.7943 T0.1843,0.7953 T0.1835,0.7968 T0.1823,0.7984 T0.1815,0.8001 Q0.1815,0.8022,0.1847,0.8032 T0.1932,0.8053 T0.2008,0.8071 Q0.2072,0.8092,0.2265,0.8092 Q0.2361,0.8092,0.2586,0.8088 L0.2635,0.8088 Q0.2731,0.8088,0.2795,0.8098 T0.2928,0.8134 T0.302,0.8167 Q0.306,0.8179,0.3277,0.8183 T0.3542,0.8221 Q0.359,0.8246,0.3622,0.8246 Q0.3639,0.8246,0.3675,0.8237 T0.3751,0.8229 Q0.3968,0.8229,0.4048,0.8275 Q0.4088,0.8291,0.4161,0.8291 Q0.4225,0.8291,0.4353,0.8275 Q0.4369,0.827,0.4394,0.827 Q0.4474,0.827,0.4675,0.8345 Q0.4707,0.8358,0.4739,0.8358 Q0.4755,0.8358,0.4799,0.8353 T0.4876,0.8349 Q0.494,0.8349,0.5012,0.8362 L0.5076,0.837 T0.5133,0.8376 T0.5181,0.8382 T0.5221,0.8391 L0.5253,0.8399 T0.5277,0.8409 L0.5293,0.8422 T0.5301,0.8438 L0.5301,0.8461 Q0.5301,0.8499,0.5249,0.8511 T0.5112,0.8526 T0.5004,0.8532 Q0.4964,0.854,0.4851,0.8546 T0.4667,0.8565 Q0.4578,0.8577,0.441,0.8577 L0.4345,0.8577 L0.4289,0.8577 L0.4209,0.8577 Q0.4153,0.8582,0.4088,0.8586 T0.3968,0.8592 T0.3855,0.8594 T0.3755,0.8596 T0.3675,0.86 T0.3622,0.8602 Q0.3317,0.8635,0.3237,0.8635 Q0.2996,0.8635,0.2996,0.8681 Q0.2996,0.8698,0.3032,0.8733 T0.3068,0.8797 L0.3068,0.8822 Q0.3052,0.8859,0.2859,0.8859 Q0.2787,0.8859,0.2739,0.8851 L0.2699,0.8851 Q0.2659,0.8851,0.259,0.888 T0.2498,0.8913 Q0.2394,0.8946,0.2056,0.8971 Q0.2032,0.8971,0.1928,0.9002 T0.1703,0.9034 Q0.1526,0.9034,0.1353,0.9067 T0.1173,0.915 L0.1173,0.9158 Q0.1173,0.9166,0.1177,0.9175 T0.1189,0.9191 T0.1205,0.9204 T0.1229,0.9212 T0.1257,0.922 T0.1285,0.9226 T0.1309,0.9231 T0.1337,0.9235 T0.1365,0.9237 L0.1446,0.9237 T0.1554,0.9243 T0.1614,0.9268 T0.1659,0.9293 T0.1743,0.9303 T0.1823,0.9316 Q0.1863,0.932,0.1912,0.9332 T0.2028,0.9361 T0.2153,0.939 Q0.2233,0.9411,0.2297,0.9448 T0.2386,0.9494 Q0.2426,0.9506,0.2506,0.9506 Q0.253,0.9506,0.2594,0.9504 T0.2699,0.9502 Q0.2763,0.9502,0.2819,0.9511 Q0.2851,0.9511,0.2871,0.9529 T0.2892,0.9573 Q0.2892,0.9627,0.2811,0.9635 Q0.2434,0.9652,0.2337,0.9672 Q0.2257,0.9693,0.2241,0.9693 Q0.2217,0.9693,0.2096,0.9689 L0.2056,0.9689 Q0.1976,0.9689,0.1924,0.9699 T0.1787,0.9741 L0.1647,0.9793 Q0.1518,0.983,0.1285,0.983 L0.1261,0.983 L0.1237,0.983 Q0.1124,0.983,0.1076,0.9838 Q0.102,0.9851,0.0839,0.9896 T0.0602,0.9954 T0.0406,0.9971 T0.0153,0.9979 L0.0032,0.9979 Z",type:0}},"\uE0CA":{type:5,data:{d:"M0.9992,0.998 Q0.9992,1,0.9968,1 Q0.9521,1,0.9392,0.998 Q0.9335,0.997,0.9152,0.9921 T0.8913,0.9862 Q0.8873,0.9852,0.8783,0.9852 L0.8747,0.9852 L0.871,0.9852 Q0.8483,0.9852,0.8345,0.9812 Q0.8289,0.9792,0.8228,0.9773 T0.8135,0.9743 T0.8054,0.9723 T0.7948,0.9713 L0.7908,0.9713 L0.777,0.9713 T0.7656,0.9693 Q0.7616,0.9683,0.7498,0.9674 T0.7283,0.9664 L0.7186,0.9654 Q0.708,0.9644,0.708,0.9575 Q0.708,0.9545,0.7109,0.952 T0.7178,0.9486 L0.7315,0.9486 L0.7417,0.9486 L0.7502,0.9486 Q0.7575,0.9486,0.7599,0.9476 Q0.7616,0.9466,0.7685,0.9426 T0.7843,0.9377 Q0.7899,0.9357,0.8009,0.9332 T0.8175,0.9298 Q0.82,0.9288,0.8228,0.9283 T0.8277,0.9278 T0.8313,0.9273 T0.8341,0.9263 T0.8362,0.9258 Q0.8402,0.9219,0.854,0.9219 L0.8569,0.9219 L0.8597,0.9219 L0.8637,0.9219 Q0.88,0.9199,0.88,0.9159 L0.88,0.9149 Q0.88,0.913,0.8719,0.911 T0.852,0.9075 T0.8297,0.906 Q0.8224,0.906,0.8167,0.905 T0.8074,0.9031 T0.8001,0.9006 T0.794,0.8991 Q0.7599,0.8971,0.7494,0.8942 Q0.7461,0.8932,0.7397,0.8902 T0.7299,0.8872 L0.7267,0.8872 Q0.721,0.8882,0.7121,0.8882 Q0.6926,0.8882,0.691,0.8833 L0.691,0.8803 Q0.691,0.8773,0.6922,0.8749 T0.6955,0.8704 L0.6975,0.8684 Q0.6926,0.8655,0.6764,0.8655 Q0.6683,0.8655,0.6375,0.8625 Q0.6358,0.8625,0.629,0.862 T0.6148,0.8615 T0.5973,0.861 T0.5791,0.8605 L0.5718,0.8605 L0.5661,0.8605 L0.5596,0.8605 Q0.5426,0.8605,0.5337,0.8586 Q0.5264,0.8576,0.515,0.8571 T0.4996,0.8556 Q0.498,0.8556,0.4947,0.8551 T0.4882,0.8546 T0.4809,0.8541 T0.474,0.8531 T0.4692,0.8506 T0.4672,0.8467 Q0.4672,0.8437,0.468,0.8422 T0.4704,0.8398 T0.4736,0.8383 T0.4781,0.8368 T0.4838,0.8358 T0.4911,0.8353 T0.4988,0.8338 Q0.5069,0.8328,0.5142,0.8328 Q0.5174,0.8328,0.5215,0.8333 T0.5264,0.8338 Q0.5296,0.8338,0.532,0.8328 Q0.5523,0.8249,0.5604,0.8249 L0.5653,0.8249 Q0.5775,0.8269,0.5848,0.8269 Q0.5912,0.8269,0.5937,0.8259 Q0.6026,0.821,0.6253,0.821 Q0.6294,0.821,0.6334,0.822 T0.6383,0.8229 Q0.6399,0.8229,0.644,0.82 Q0.6504,0.817,0.6723,0.816 T0.6975,0.814 Q0.6999,0.814,0.7048,0.8121 T0.7129,0.8091 T0.7226,0.8071 T0.7372,0.8061 L0.7421,0.8061 Q0.7624,0.8071,0.7721,0.8071 Q0.7924,0.8071,0.7981,0.8051 Q0.8021,0.8032,0.807,0.8027 T0.8135,0.8017 T0.8151,0.8002 L0.8151,0.7992 Q0.8143,0.7962,0.8127,0.7957 T0.8045,0.7943 Q0.7997,0.7943,0.7964,0.7933 Q0.7883,0.7933,0.7676,0.7923 T0.7437,0.7903 Q0.7307,0.7893,0.7218,0.7844 Q0.7194,0.7834,0.7141,0.7819 L0.7036,0.7789 T0.693,0.7765 T0.6853,0.7755 Q0.6805,0.7755,0.6691,0.773 T0.6553,0.7705 Q0.6545,0.7705,0.6545,0.7715 Q0.6537,0.7715,0.65,0.7725 T0.6423,0.774 T0.635,0.7745 Q0.6294,0.7745,0.6257,0.773 T0.618,0.771 T0.6083,0.77 T0.601,0.7695 Q0.5985,0.7695,0.5921,0.769 T0.5795,0.7676 T0.5702,0.7666 Q0.5653,0.7656,0.5596,0.7616 T0.5539,0.7547 Q0.5539,0.7498,0.5645,0.7488 Q0.5693,0.7478,0.5852,0.7448 T0.6099,0.7418 L0.6148,0.7418 Q0.6196,0.7418,0.6253,0.7423 T0.6338,0.7428 L0.6399,0.7428 Q0.6431,0.7418,0.6488,0.7409 T0.6586,0.7394 T0.6659,0.7389 L0.6707,0.7389 Q0.6723,0.7399,0.6748,0.7399 Q0.6796,0.7399,0.6946,0.7389 T0.7161,0.7379 Q0.7251,0.7379,0.7478,0.7319 Q0.7494,0.7319,0.7539,0.7315 T0.762,0.73 T0.7697,0.727 T0.7818,0.723 T0.7948,0.7196 T0.7997,0.7151 Q0.7997,0.7141,0.7989,0.7132 Q0.7924,0.7062,0.7717,0.6978 T0.738,0.6894 Q0.7267,0.6894,0.717,0.6845 T0.7048,0.6795 Q0.6975,0.6785,0.6711,0.6766 T0.6431,0.6746 Q0.6415,0.6736,0.6144,0.6721 T0.5596,0.6686 T0.5247,0.6657 Q0.4996,0.6607,0.4874,0.6597 L0.4834,0.6597 Q0.4785,0.6597,0.4724,0.6607 T0.4631,0.6617 Q0.4574,0.6617,0.4501,0.6597 Q0.4088,0.6518,0.4006,0.6518 Q0.3933,0.6518,0.3901,0.6499 T0.3844,0.6479 T0.3739,0.6499 T0.3585,0.6518 L0.352,0.6518 Q0.3471,0.6508,0.335,0.6503 T0.3122,0.6494 T0.2912,0.6479 T0.2766,0.6449 Q0.2733,0.6439,0.2668,0.6439 Q0.2628,0.6439,0.2543,0.6444 T0.2425,0.6449 T0.2368,0.6439 L0.2344,0.6439 Q0.2336,0.6439,0.2283,0.6444 T0.2182,0.6449 Q0.2117,0.6449,0.2052,0.6439 Q0.1768,0.6409,0.1281,0.637 Q0.0989,0.634,0.0916,0.634 L0.0835,0.634 L0.0795,0.634 Q0.0722,0.634,0.0616,0.632 T0.0479,0.6301 Q0.0462,0.6301,0.0406,0.6306 T0.0308,0.6311 Q0.0032,0.6311,0.0008,0.6231 Q0,0.6202,0,0.6182 Q0,0.6083,0.0227,0.6083 Q0.03,0.6073,0.0422,0.6073 Q0.0487,0.6073,0.0608,0.6078 T0.0787,0.6083 Q0.0973,0.6083,0.103,0.6073 Q0.1062,0.6063,0.1131,0.6053 T0.1253,0.6039 T0.1375,0.6024 T0.1492,0.6014 L0.1533,0.6014 Q0.1598,0.6024,0.1723,0.6044 T0.189,0.6063 Q0.1906,0.6063,0.1922,0.6053 Q0.2214,0.6004,0.236,0.6004 Q0.2409,0.6004,0.2551,0.6009 T0.2741,0.6014 Q0.2782,0.6014,0.2859,0.6024 T0.2968,0.6034 Q0.3009,0.6034,0.3041,0.6024 Q0.3277,0.5905,0.356,0.5905 L0.3613,0.5905 L0.3666,0.5905 Q0.3731,0.5905,0.3755,0.5895 Q0.382,0.5856,0.3844,0.5846 T0.3938,0.5831 T0.4144,0.5826 T0.4428,0.5801 T0.4631,0.5776 L0.4663,0.5776 Q0.4704,0.5776,0.4785,0.5786 T0.4899,0.5796 Q0.4964,0.5796,0.5012,0.5767 Q0.5069,0.5737,0.5223,0.5737 Q0.5272,0.5737,0.5369,0.5742 T0.5499,0.5747 Q0.558,0.5747,0.5612,0.5737 Q0.5661,0.5717,0.5714,0.5712 T0.5835,0.5702 T0.5945,0.5692 T0.6018,0.5687 L0.6062,0.5687 T0.6095,0.5682 T0.6148,0.5678 Q0.6172,0.5668,0.6196,0.5653 T0.6245,0.5633 T0.631,0.5628 T0.6407,0.5638 T0.6504,0.5648 T0.6594,0.5638 T0.6675,0.5628 L0.6707,0.5628 L0.6861,0.5628 Q0.7364,0.5628,0.7494,0.5608 L0.7567,0.5608 L0.7652,0.5608 L0.7729,0.5608 Q0.7794,0.5608,0.7818,0.5589 Q0.7851,0.5569,0.7859,0.5559 Q0.7835,0.5539,0.7656,0.5519 Q0.7121,0.547,0.6975,0.543 Q0.6934,0.541,0.6853,0.5396 L0.6691,0.5366 T0.6525,0.5336 T0.6391,0.5312 Q0.6367,0.5302,0.6245,0.5257 T0.6034,0.5213 L0.5977,0.5213 Q0.5945,0.5223,0.5904,0.5223 Q0.5839,0.5223,0.5787,0.5208 T0.5685,0.5183 T0.5596,0.5173 Q0.558,0.5173,0.5552,0.5168 T0.5495,0.5163 L0.5434,0.5163 T0.5373,0.5158 T0.532,0.5148 T0.5284,0.5129 T0.5272,0.5104 L0.5272,0.5074 Q0.5304,0.5015,0.5377,0.499 T0.5629,0.4965 Q0.5734,0.4955,0.5811,0.4946 T0.5953,0.4916 T0.605,0.4896 Q0.6083,0.4886,0.6123,0.4886 Q0.6148,0.4886,0.6225,0.4891 T0.635,0.4896 L0.6456,0.4896 Q0.6837,0.4857,0.6861,0.4857 Q0.7226,0.4768,0.7283,0.4768 Q0.7307,0.4768,0.732,0.4773 T0.7372,0.4777 Q0.7502,0.4777,0.7721,0.4758 Q0.7753,0.4748,0.777,0.4738 Q0.7745,0.4718,0.768,0.4708 Q0.7599,0.4688,0.7445,0.4688 Q0.7405,0.4688,0.7324,0.4693 T0.721,0.4698 Q0.7056,0.4698,0.6999,0.4679 Q0.6861,0.4629,0.6626,0.4619 Q0.6586,0.4609,0.6533,0.4604 T0.6419,0.459 T0.6314,0.456 T0.6269,0.452 Q0.6269,0.451,0.6302,0.4491 Q0.6358,0.4451,0.6525,0.4416 T0.6788,0.4382 L0.6845,0.4382 L0.6902,0.4382 Q0.7064,0.4382,0.7145,0.4362 Q0.7202,0.4342,0.7307,0.4342 L0.7356,0.4342 L0.7409,0.4342 L0.7453,0.4342 Q0.7543,0.4342,0.7607,0.4332 Q0.7778,0.4313,0.7847,0.4288 T0.7932,0.4248 T0.7972,0.4169 T0.8045,0.4045 L0.8094,0.3986 T0.8135,0.3942 L0.8167,0.3912 T0.8191,0.3887 T0.8204,0.3867 T0.8208,0.3848 Q0.8208,0.3818,0.8167,0.3739 Q0.8127,0.3689,0.8054,0.3635 T0.7924,0.3556 T0.7778,0.3497 T0.768,0.3462 Q0.764,0.3442,0.7478,0.3427 T0.7267,0.3383 Q0.7234,0.3363,0.7044,0.3348 T0.6805,0.3323 L0.6764,0.3323 T0.6679,0.3328 T0.6586,0.3333 Q0.6521,0.3333,0.648,0.3314 Q0.6391,0.3264,0.6294,0.3264 Q0.6229,0.3264,0.6156,0.3294 Q0.6115,0.3304,0.6042,0.3304 Q0.5977,0.3304,0.5888,0.3289 T0.5738,0.3264 T0.5584,0.3225 L0.5491,0.3195 Q0.5418,0.3185,0.5162,0.3155 T0.4895,0.3121 T0.4826,0.3116 T0.4716,0.3111 T0.4607,0.3096 T0.4513,0.3066 T0.4477,0.3017 Q0.4477,0.2997,0.4485,0.2977 Q0.4526,0.2898,0.4611,0.2878 T0.4842,0.2859 Q0.4882,0.2859,0.4943,0.2864 T0.5028,0.2868 Q0.5118,0.2868,0.5215,0.2829 Q0.5401,0.276,0.5734,0.276 Q0.5831,0.276,0.5872,0.277 L0.5904,0.277 T0.5949,0.2745 T0.6034,0.272 Q0.631,0.269,0.6407,0.2661 Q0.6448,0.2641,0.6488,0.2641 T0.6573,0.2656 T0.6634,0.2671 Q0.6659,0.2671,0.6675,0.2651 Q0.6723,0.2601,0.6886,0.2552 T0.7137,0.2502 L0.7153,0.2502 L0.7178,0.2502 Q0.7218,0.2502,0.7279,0.2493 T0.7417,0.2468 T0.7526,0.2453 Q0.8208,0.2354,0.8281,0.2354 Q0.8337,0.2354,0.8394,0.2344 T0.8496,0.2315 T0.854,0.2265 Q0.854,0.2216,0.8443,0.2176 Q0.8354,0.2127,0.8216,0.2102 T0.7972,0.2072 T0.777,0.2067 T0.7648,0.2057 Q0.7632,0.2047,0.7498,0.2038 T0.7202,0.2018 T0.6983,0.1998 Q0.6918,0.1998,0.676,0.1949 T0.6537,0.1899 Q0.6496,0.1899,0.6472,0.1919 Q0.6342,0.1968,0.6123,0.1968 Q0.5961,0.1968,0.5912,0.1929 Q0.5904,0.1919,0.5892,0.1919 T0.5823,0.1944 T0.5722,0.1968 T0.5637,0.1949 Q0.5556,0.1909,0.5158,0.1869 Q0.5109,0.1869,0.5032,0.1835 T0.4915,0.18 Q0.4907,0.18,0.4891,0.181 Q0.4793,0.182,0.455,0.182 Q0.4177,0.182,0.4015,0.1771 Q0.3901,0.1741,0.3779,0.1741 Q0.3747,0.1741,0.369,0.1746 T0.3609,0.1751 Q0.3569,0.1751,0.3536,0.1741 Q0.3512,0.1741,0.3459,0.1736 T0.3358,0.1726 T0.3256,0.1716 T0.3167,0.1696 T0.3106,0.1662 Q0.3082,0.1632,0.3082,0.1592 Q0.3082,0.1543,0.3114,0.1508 T0.3204,0.1474 L0.3236,0.1474 Q0.3301,0.1494,0.3333,0.1494 Q0.339,0.1494,0.3496,0.1454 Q0.3674,0.1385,0.3869,0.1385 Q0.395,0.1385,0.4011,0.137 T0.4116,0.1355 T0.4197,0.1375 T0.425,0.1395 Q0.429,0.1395,0.4355,0.1365 Q0.4631,0.1266,0.4882,0.1266 Q0.5126,0.1266,0.5337,0.1246 Q0.5369,0.1246,0.5458,0.1207 T0.5661,0.1167 L0.5693,0.1167 Q0.5791,0.1177,0.5852,0.1187 T0.5921,0.1197 Q0.5945,0.1197,0.6002,0.1167 Q0.6026,0.1147,0.605,0.1123 T0.6091,0.1093 T0.6156,0.1088 T0.6318,0.1098 Q0.6456,0.1108,0.6569,0.1137 T0.6707,0.1167 Q0.674,0.1167,0.6772,0.1147 Q0.6821,0.1108,0.6946,0.1083 T0.721,0.1044 T0.7453,0.1029 L0.7526,0.1029 Q0.7583,0.1039,0.7652,0.1053 T0.7753,0.1068 Q0.7778,0.1068,0.7802,0.1058 Q0.7981,0.1019,0.8143,0.1019 Q0.8175,0.1019,0.8439,0.0964 T0.8735,0.091 Q0.8759,0.091,0.8836,0.089 T0.8938,0.0851 Q0.8929,0.0841,0.8905,0.0841 Q0.8832,0.0821,0.8735,0.0821 Q0.8694,0.0821,0.8629,0.0826 T0.854,0.0831 Q0.8508,0.0831,0.8491,0.0821 Q0.8475,0.0821,0.8171,0.0811 T0.7802,0.0781 T0.7571,0.0737 T0.7332,0.0712 Q0.7299,0.0712,0.7247,0.0717 T0.7161,0.0722 L0.7064,0.0722 Q0.7032,0.0712,0.7003,0.0707 T0.6959,0.0692 T0.693,0.0678 T0.6914,0.0663 T0.6902,0.0648 T0.689,0.0643 L0.6869,0.0643 L0.6845,0.0643 L0.6821,0.0643 Q0.6553,0.0643,0.6553,0.0554 T0.6796,0.0465 L0.6813,0.0465 L0.6845,0.0465 Q0.6918,0.0425,0.717,0.0376 Q0.7226,0.0366,0.7267,0.0356 T0.7324,0.0336 T0.7376,0.0317 T0.747,0.0297 Q0.7518,0.0287,0.7551,0.0287 T0.762,0.0297 T0.768,0.0307 L0.7729,0.0307 Q0.7802,0.0277,0.7997,0.0267 T0.8224,0.0237 Q0.8273,0.0227,0.8341,0.0223 T0.8475,0.0193 T0.8654,0.0143 T0.8873,0.0079 Q0.9002,0.003,0.9331,0.004 T0.97,0.004 Q0.9838,0,0.9976,0.001 Q1,0.001,1,0.003 Z M0.9935,0.996 L0.9951,0.0049 Q0.9822,0.0049,0.9716,0.0079 Q0.9659,0.0099,0.9327,0.0089 T0.8897,0.0119 Q0.8775,0.0158,0.8666,0.0183 T0.85,0.0237 Q0.8418,0.0267,0.8341,0.0272 T0.8248,0.0277 Q0.82,0.0297,0.8001,0.0312 T0.7745,0.0346 Q0.7697,0.0356,0.7664,0.0356 Q0.764,0.0356,0.7603,0.0341 T0.7534,0.0326 Q0.751,0.0326,0.7478,0.0336 Q0.7429,0.0346,0.7397,0.0356 T0.7344,0.0376 T0.7283,0.0401 T0.7178,0.0425 Q0.6926,0.0465,0.6878,0.0495 Q0.6845,0.0514,0.6764,0.0514 L0.6723,0.0514 L0.6691,0.0514 Q0.6602,0.0514,0.6602,0.0554 Q0.6602,0.0574,0.6659,0.0579 T0.6796,0.0589 T0.691,0.0603 T0.6967,0.0643 T0.7072,0.0673 Q0.7121,0.0682,0.7153,0.0682 T0.7238,0.0678 T0.7332,0.0673 Q0.7413,0.0673,0.7579,0.0692 T0.7818,0.0742 Q0.7867,0.0752,0.8171,0.0762 T0.8508,0.0781 L0.852,0.0781 T0.8601,0.0776 T0.8719,0.0772 Q0.8832,0.0772,0.8921,0.0791 Q0.8994,0.0811,0.8994,0.0841 T0.8933,0.0895 T0.8812,0.094 T0.8735,0.0959 T0.8451,0.1014 T0.8143,0.1068 Q0.7989,0.1068,0.7818,0.1098 Q0.7778,0.1108,0.7745,0.1108 T0.768,0.1103 T0.7603,0.1088 T0.7518,0.1068 L0.7461,0.1068 Q0.7315,0.1068,0.7088,0.1103 T0.6805,0.1177 Q0.6756,0.1207,0.6699,0.1207 Q0.6667,0.1207,0.6545,0.1177 T0.631,0.1147 Q0.6269,0.1137,0.6225,0.1133 T0.616,0.1128 L0.6123,0.1128 T0.6103,0.1133 L0.6083,0.1157 T0.6034,0.1197 Q0.5961,0.1246,0.5921,0.1246 Q0.5904,0.1246,0.5835,0.1231 T0.5693,0.1217 L0.5661,0.1217 Q0.558,0.1217,0.5535,0.1231 T0.5446,0.1266 T0.5345,0.1296 Q0.5126,0.1316,0.4882,0.1316 T0.438,0.1405 Q0.4298,0.1434,0.425,0.1434 Q0.4209,0.1434,0.4173,0.1419 T0.4112,0.1405 T0.4027,0.1419 T0.3869,0.1434 Q0.3682,0.1434,0.3512,0.1494 Q0.3398,0.1533,0.3333,0.1533 Q0.3285,0.1533,0.3228,0.1513 L0.3212,0.1513 Q0.3179,0.1513,0.3155,0.1538 T0.3131,0.1592 Q0.3131,0.1612,0.3147,0.1632 Q0.3171,0.1652,0.3232,0.1667 T0.3406,0.1691 T0.3544,0.1701 L0.3601,0.1701 L0.3678,0.1701 L0.3771,0.1701 Q0.3909,0.1701,0.4031,0.1731 Q0.4177,0.178,0.4558,0.178 Q0.4793,0.178,0.4882,0.1761 L0.4915,0.1761 Q0.498,0.1761,0.5061,0.179 T0.5158,0.183 Q0.5166,0.183,0.5231,0.1835 T0.5341,0.1845 T0.545,0.1855 T0.5572,0.1874 T0.5661,0.1909 Q0.5677,0.1919,0.5702,0.1919 T0.5791,0.1894 T0.588,0.1869 Q0.5921,0.1869,0.5945,0.1899 Q0.5977,0.1919,0.6123,0.1919 Q0.6334,0.1919,0.6448,0.1879 Q0.6488,0.186,0.6537,0.186 Q0.6626,0.186,0.6784,0.1909 T0.6983,0.1958 Q0.704,0.1968,0.7206,0.1973 T0.7506,0.1993 T0.7689,0.2018 L0.7705,0.2018 L0.7741,0.2018 L0.7786,0.2018 Q0.8248,0.2018,0.8475,0.2136 Q0.8597,0.2196,0.8597,0.2255 T0.8483,0.2359 T0.8281,0.2404 Q0.8208,0.2404,0.7534,0.2493 Q0.7502,0.2502,0.7425,0.2512 T0.7287,0.2532 T0.7178,0.2542 L0.7153,0.2542 L0.7137,0.2542 Q0.7064,0.2542,0.6906,0.2591 T0.6715,0.2681 T0.6626,0.272 Q0.6594,0.272,0.6541,0.2705 T0.6464,0.269 T0.6431,0.27 Q0.6318,0.273,0.6034,0.276 Q0.6018,0.276,0.5998,0.2774 T0.5957,0.2799 T0.5904,0.2809 L0.5856,0.2809 Q0.5823,0.2799,0.575,0.2799 Q0.541,0.2799,0.5239,0.2868 Q0.5134,0.2908,0.5028,0.2908 Q0.4996,0.2908,0.4935,0.2903 T0.4842,0.2898 Q0.4704,0.2898,0.4635,0.2918 T0.4534,0.2997 L0.4534,0.3007 Q0.4534,0.3037,0.459,0.3046 T0.4753,0.3066 T0.4899,0.3076 Q0.4907,0.3076,0.5166,0.3106 T0.5507,0.3155 Q0.5921,0.3264,0.605,0.3264 Q0.6107,0.3264,0.6131,0.3254 Q0.6212,0.3225,0.6294,0.3225 Q0.6407,0.3225,0.6513,0.3274 Q0.6529,0.3294,0.6569,0.3294 Q0.6594,0.3294,0.665,0.3284 T0.6748,0.3274 T0.6821,0.3284 Q0.6861,0.3294,0.6967,0.3299 T0.7165,0.3314 T0.7299,0.3343 Q0.7324,0.3363,0.7486,0.3383 T0.7697,0.3422 Q0.7713,0.3432,0.7774,0.3452 T0.7887,0.3492 T0.8005,0.3546 T0.8127,0.3625 T0.8208,0.3719 Q0.8256,0.3798,0.8256,0.3848 Q0.8256,0.3877,0.8236,0.3902 T0.8171,0.3976 T0.809,0.4075 T0.8021,0.4189 T0.7968,0.4273 T0.7867,0.4322 T0.7616,0.4382 Q0.7543,0.4392,0.7437,0.4392 L0.7364,0.4392 L0.7299,0.4392 Q0.7202,0.4392,0.7161,0.4402 Q0.7072,0.4431,0.691,0.4431 L0.6845,0.4431 L0.6788,0.4431 Q0.6707,0.4431,0.6545,0.4461 T0.6334,0.452 Q0.6399,0.456,0.6626,0.457 Q0.6878,0.458,0.7024,0.4639 Q0.7064,0.4649,0.7186,0.4649 L0.7295,0.4649 L0.7413,0.4649 Q0.7591,0.4649,0.7697,0.4669 Q0.7818,0.4688,0.7818,0.4728 Q0.7818,0.4748,0.7798,0.4763 T0.7753,0.4787 L0.7737,0.4797 L0.7729,0.4797 Q0.7494,0.4827,0.7388,0.4827 Q0.7332,0.4827,0.7307,0.4817 L0.7291,0.4817 Q0.7226,0.4817,0.6878,0.4896 Q0.6829,0.4906,0.6772,0.4911 T0.6618,0.4921 T0.646,0.4931 T0.6342,0.4936 T0.6212,0.4931 T0.6115,0.4926 Q0.6083,0.4926,0.6058,0.4931 T0.5969,0.4955 T0.5819,0.499 T0.5629,0.5005 Q0.5572,0.5005,0.5527,0.501 T0.545,0.502 T0.5397,0.503 T0.5361,0.5045 T0.5337,0.5064 T0.5328,0.5094 L0.532,0.5094 Q0.532,0.5104,0.5337,0.5109 T0.5381,0.5114 T0.5438,0.5119 T0.5515,0.5124 L0.5604,0.5124 Q0.5661,0.5134,0.5746,0.5153 T0.5912,0.5173 L0.5977,0.5173 L0.6026,0.5173 Q0.6139,0.5173,0.6273,0.5218 T0.6407,0.5272 Q0.6448,0.5282,0.6683,0.5321 T0.6991,0.5381 Q0.7137,0.543,0.7664,0.548 Q0.7908,0.55,0.7908,0.5559 Q0.7908,0.5589,0.7859,0.5628 Q0.781,0.5658,0.7713,0.5658 Q0.7689,0.5658,0.7628,0.5653 T0.7543,0.5648 T0.7502,0.5658 Q0.7364,0.5678,0.6878,0.5678 Q0.674,0.5678,0.6699,0.5668 L0.6683,0.5668 Q0.6659,0.5668,0.6606,0.5678 T0.6513,0.5687 Q0.6464,0.5687,0.6391,0.5678 Q0.6334,0.5668,0.6302,0.5668 T0.6241,0.5687 T0.6164,0.5717 Q0.6123,0.5727,0.6099,0.5727 L0.6062,0.5727 T0.6022,0.5732 T0.5953,0.5737 Q0.5904,0.5747,0.5839,0.5747 T0.5726,0.5752 T0.5629,0.5776 T0.5458,0.5796 Q0.5418,0.5796,0.5324,0.5791 T0.5191,0.5786 Q0.5077,0.5786,0.5045,0.5806 Q0.498,0.5836,0.4899,0.5836 Q0.4858,0.5836,0.4777,0.5826 T0.4655,0.5816 L0.4631,0.5816 Q0.4582,0.5816,0.4436,0.5841 T0.4144,0.5865 L0.4051,0.5865 L0.3986,0.5865 T0.3933,0.587 T0.3897,0.5875 T0.3869,0.5885 T0.3844,0.5895 T0.3812,0.591 T0.3779,0.5925 Q0.3739,0.5955,0.3633,0.5955 L0.3585,0.5955 L0.3536,0.5955 Q0.3277,0.5955,0.3066,0.6053 Q0.3017,0.6083,0.296,0.6083 Q0.2912,0.6083,0.2839,0.6068 T0.2741,0.6053 Q0.2693,0.6053,0.2547,0.6048 T0.236,0.6044 Q0.2222,0.6044,0.193,0.6103 L0.1882,0.6103 Q0.1825,0.6103,0.1703,0.6083 T0.1525,0.6063 L0.15,0.6063 Q0.1436,0.6063,0.135,0.6073 T0.1168,0.6098 T0.1038,0.6113 Q0.0973,0.6123,0.0787,0.6123 L0.06,0.6123 L0.0414,0.6123 L0.0235,0.6123 Q0.0049,0.6133,0.0049,0.6172 L0.0057,0.6222 Q0.0073,0.6261,0.043,0.6261 L0.0454,0.6261 L0.0479,0.6261 Q0.0527,0.6261,0.0633,0.6281 T0.0803,0.6301 L0.0835,0.6301 L0.0916,0.6301 Q0.0998,0.6301,0.129,0.632 Q0.1768,0.636,0.206,0.64 Q0.2117,0.6409,0.2165,0.6409 Q0.2206,0.6409,0.2263,0.6405 T0.2336,0.64 L0.2384,0.64 L0.2409,0.64 T0.251,0.6395 T0.2644,0.639 Q0.2741,0.639,0.279,0.6409 Q0.283,0.6429,0.2972,0.6439 T0.3293,0.6459 T0.3528,0.6469 L0.3577,0.6469 Q0.3642,0.6469,0.3723,0.6449 T0.3844,0.6429 Q0.3901,0.6429,0.3933,0.6454 T0.4006,0.6479 Q0.4088,0.6479,0.4517,0.6558 Q0.4582,0.6568,0.4623,0.6568 Q0.4655,0.6568,0.4716,0.6563 T0.4826,0.6558 L0.4882,0.6558 Q0.5004,0.6558,0.5255,0.6607 Q0.5328,0.6627,0.5604,0.6647 T0.6152,0.6682 T0.644,0.6696 T0.6719,0.6721 T0.7056,0.6756 Q0.7113,0.6756,0.7198,0.6805 T0.738,0.6855 Q0.7453,0.6855,0.7551,0.6879 T0.7741,0.6939 T0.7912,0.7018 T0.8029,0.7102 Q0.8054,0.7132,0.8054,0.7151 Q0.8054,0.7191,0.8009,0.7216 T0.7908,0.7255 T0.779,0.7285 T0.7721,0.731 Q0.7672,0.7329,0.7632,0.7339 T0.7551,0.7354 T0.7486,0.7369 Q0.7478,0.7369,0.7445,0.7379 T0.7388,0.7394 T0.7324,0.7404 L0.7242,0.7413 T0.7161,0.7418 Q0.7105,0.7418,0.6955,0.7428 T0.6748,0.7438 L0.6699,0.7438 L0.6667,0.7438 Q0.6659,0.7438,0.6407,0.7468 Q0.6367,0.7478,0.6334,0.7478 T0.6245,0.7468 T0.6148,0.7458 L0.6099,0.7458 Q0.6018,0.7468,0.5864,0.7498 T0.5653,0.7532 T0.5596,0.7557 Q0.5596,0.7596,0.571,0.7616 Q0.5742,0.7626,0.5803,0.7631 T0.5929,0.7641 T0.6018,0.7656 L0.6087,0.7656 T0.6192,0.7666 T0.6277,0.7695 Q0.631,0.7705,0.6342,0.7705 Q0.6383,0.7705,0.6452,0.7685 T0.6537,0.7666 L0.6553,0.7666 Q0.6602,0.7666,0.6711,0.769 T0.6853,0.7715 Q0.691,0.7715,0.7048,0.775 T0.7242,0.7814 Q0.7324,0.7854,0.7445,0.7864 Q0.7478,0.7864,0.768,0.7873 T0.7972,0.7893 Q0.7989,0.7893,0.8017,0.7898 T0.8054,0.7903 L0.8082,0.7903 T0.811,0.7908 T0.8131,0.7913 T0.8151,0.7918 L0.8167,0.7928 T0.8179,0.7943 T0.8191,0.7957 T0.8204,0.7977 T0.8208,0.8002 Q0.8208,0.8032,0.8171,0.8051 T0.8074,0.8081 T0.7997,0.8091 Q0.7932,0.8121,0.7737,0.8121 Q0.7664,0.8121,0.7413,0.8111 L0.7364,0.8111 Q0.7275,0.8111,0.7214,0.8121 T0.7084,0.8155 T0.6991,0.819 Q0.6934,0.82,0.6723,0.8205 T0.6472,0.8239 Q0.6423,0.8269,0.6375,0.8269 Q0.6358,0.8269,0.6338,0.8264 T0.6294,0.8254 T0.6253,0.8249 Q0.6042,0.8249,0.5961,0.8289 Q0.5921,0.8318,0.5839,0.8318 Q0.5783,0.8318,0.5645,0.8299 L0.5604,0.8299 Q0.5531,0.8299,0.5337,0.8368 Q0.5296,0.8378,0.5255,0.8378 Q0.5239,0.8378,0.5195,0.8373 T0.5118,0.8368 Q0.5061,0.8368,0.4996,0.8388 Q0.4947,0.8388,0.4882,0.8398 Q0.4777,0.8417,0.4753,0.8427 T0.4728,0.8467 Q0.4728,0.8487,0.4769,0.8492 T0.4886,0.8501 T0.5004,0.8516 Q0.5036,0.8516,0.515,0.8521 T0.5345,0.8546 Q0.5426,0.8556,0.5596,0.8556 L0.5649,0.8556 L0.5702,0.8556 L0.5791,0.8556 Q0.5921,0.8566,0.6135,0.8571 T0.6383,0.8586 Q0.6683,0.8615,0.6764,0.8615 L0.6776,0.8615 L0.6788,0.8615 Q0.7032,0.8615,0.7032,0.8684 Q0.7032,0.8714,0.6995,0.8749 T0.6959,0.8803 L0.6959,0.8813 Q0.6991,0.8843,0.7153,0.8843 Q0.7218,0.8843,0.7259,0.8833 L0.7299,0.8833 Q0.7324,0.8833,0.7348,0.8838 T0.7393,0.8848 T0.7433,0.8863 L0.7474,0.8882 T0.751,0.8892 Q0.7616,0.8922,0.7948,0.8952 Q0.7981,0.8952,0.8082,0.8981 T0.8297,0.9011 T0.8528,0.9026 T0.8747,0.907 T0.8856,0.9149 L0.8856,0.9159 Q0.8856,0.9179,0.8848,0.9189 T0.8828,0.9209 T0.88,0.9228 T0.8767,0.9243 T0.8731,0.9248 T0.8698,0.9253 T0.8666,0.9258 L0.8646,0.9258 Q0.8621,0.9258,0.8577,0.9263 T0.85,0.9268 T0.8439,0.9273 T0.8402,0.9278 Q0.8386,0.9298,0.8358,0.9308 T0.8305,0.9322 T0.8244,0.9332 T0.8183,0.9337 Q0.8135,0.9347,0.8025,0.9372 T0.7859,0.9416 Q0.7778,0.9436,0.7717,0.9471 T0.7624,0.9515 Q0.7575,0.9535,0.7486,0.9535 Q0.7461,0.9535,0.7397,0.953 T0.7291,0.9525 Q0.7234,0.9525,0.7186,0.953 T0.7137,0.957 T0.7194,0.9614 Q0.7567,0.9634,0.7664,0.9654 Q0.7745,0.9674,0.7762,0.9674 L0.7908,0.9674 Q0.7924,0.9664,0.7948,0.9664 Q0.8029,0.9664,0.8082,0.9679 T0.8224,0.9723 T0.8362,0.9773 Q0.8483,0.9812,0.871,0.9812 L0.8751,0.9812 Q0.8873,0.9812,0.8929,0.9822 T0.9165,0.9876 T0.9408,0.9931 Q0.9513,0.996,0.9935,0.996 Z M0.9968,0.998 L0.9976,0.003 Q0.9838,0.002,0.9708,0.0059 Q0.9659,0.0069,0.9327,0.0059 T0.8881,0.0099 Q0.8767,0.0138,0.8658,0.0163 T0.8491,0.0218 Q0.8418,0.0247,0.8341,0.0247 T0.8232,0.0257 Q0.8191,0.0277,0.7997,0.0292 T0.7737,0.0326 Q0.7697,0.0336,0.7672,0.0336 T0.7612,0.0321 T0.7543,0.0307 T0.747,0.0317 Q0.7397,0.0326,0.7336,0.0356 T0.717,0.0406 Q0.6918,0.0445,0.6861,0.0475 Q0.6845,0.0485,0.6772,0.0485 T0.6638,0.05 T0.6577,0.0549 T0.6642,0.0598 T0.6796,0.0613 T0.6902,0.0623 Q0.691,0.0623,0.6946,0.0658 T0.7064,0.0702 L0.7161,0.0702 Q0.7194,0.0702,0.7242,0.0697 T0.7332,0.0692 Q0.7413,0.0692,0.7579,0.0717 T0.781,0.0762 Q0.7867,0.0781,0.8171,0.0786 T0.85,0.0801 L0.8532,0.0801 Q0.8548,0.0801,0.8613,0.0796 T0.8727,0.0791 Q0.8832,0.0791,0.8913,0.0811 Q0.897,0.0831,0.897,0.0851 Q0.897,0.088,0.8873,0.0905 T0.8735,0.093 Q0.871,0.093,0.8447,0.0984 T0.8143,0.1039 Q0.7981,0.1039,0.781,0.1078 Q0.7778,0.1088,0.7745,0.1088 T0.7644,0.1073 T0.7518,0.1048 L0.7453,0.1048 Q0.7299,0.1048,0.7076,0.1083 T0.6788,0.1157 Q0.6748,0.1187,0.6699,0.1187 Q0.6675,0.1187,0.6557,0.1157 T0.6318,0.1118 Q0.6164,0.1108,0.6135,0.1108 T0.6095,0.1113 T0.6067,0.1142 T0.6018,0.1187 Q0.5953,0.1227,0.5921,0.1227 Q0.5904,0.1227,0.5839,0.1212 T0.5693,0.1187 L0.5661,0.1187 Q0.558,0.1187,0.5527,0.1207 T0.5434,0.1246 T0.5337,0.1266 Q0.5126,0.1296,0.4882,0.1296 T0.4371,0.1385 Q0.4298,0.1414,0.425,0.1414 Q0.4225,0.1414,0.4189,0.1395 T0.4112,0.1375 Q0.4079,0.1375,0.4019,0.1395 T0.3869,0.1414 Q0.3682,0.1414,0.3504,0.1474 Q0.3398,0.1513,0.3333,0.1513 Q0.3293,0.1513,0.3228,0.1494 L0.3204,0.1494 Q0.3179,0.1494,0.3155,0.1508 T0.3118,0.1543 T0.3106,0.1592 T0.3127,0.1642 T0.32,0.1677 T0.3305,0.1701 T0.3435,0.1716 T0.3544,0.1721 Q0.3569,0.1731,0.3601,0.1731 Q0.3625,0.1731,0.3686,0.1726 T0.3779,0.1721 Q0.3901,0.1721,0.4023,0.1751 Q0.4177,0.18,0.455,0.18 Q0.4793,0.18,0.4882,0.178 L0.4915,0.178 Q0.4964,0.178,0.5045,0.1815 T0.5158,0.185 Q0.5564,0.1889,0.5645,0.1929 Q0.5677,0.1939,0.571,0.1939 Q0.575,0.1939,0.5807,0.1919 T0.5888,0.1899 T0.5929,0.1909 Q0.5969,0.1949,0.6123,0.1949 Q0.6334,0.1949,0.6456,0.1899 Q0.6488,0.1879,0.6537,0.1879 Q0.661,0.1879,0.6772,0.1929 T0.6983,0.1978 Q0.704,0.1988,0.7206,0.1998 T0.7502,0.2018 T0.7664,0.2038 Q0.7672,0.2038,0.7818,0.2043 T0.8151,0.2067 T0.8459,0.2156 Q0.8573,0.2206,0.8573,0.2255 Q0.8573,0.2295,0.8516,0.2324 T0.8394,0.2369 T0.8281,0.2384 Q0.8208,0.2384,0.7526,0.2473 Q0.7494,0.2483,0.7421,0.2493 T0.7287,0.2512 T0.7178,0.2522 L0.7153,0.2522 L0.7137,0.2522 Q0.7056,0.2522,0.6898,0.2572 T0.6691,0.2671 Q0.6667,0.269,0.6634,0.269 Q0.661,0.269,0.6557,0.2681 T0.6472,0.2671 Q0.6448,0.2671,0.6415,0.2681 Q0.631,0.271,0.6034,0.274 Q0.5994,0.274,0.5961,0.2765 T0.5904,0.2789 L0.5864,0.2789 Q0.5831,0.2779,0.5742,0.2779 Q0.541,0.2779,0.5223,0.2849 Q0.5126,0.2888,0.5028,0.2888 Q0.5004,0.2888,0.4943,0.2883 T0.4842,0.2878 Q0.4704,0.2878,0.4627,0.2898 T0.4509,0.2987 Q0.4501,0.2997,0.4501,0.3007 Q0.4501,0.3046,0.4574,0.3066 T0.4765,0.3091 T0.4899,0.3096 Q0.4931,0.3106,0.5061,0.3121 T0.5316,0.315 T0.5499,0.3175 Q0.5507,0.3175,0.5572,0.3195 T0.5685,0.3225 T0.5803,0.3254 T0.5933,0.3279 T0.605,0.3284 T0.6148,0.3274 Q0.6221,0.3244,0.6294,0.3244 Q0.6399,0.3244,0.6496,0.3294 Q0.6529,0.3314,0.6577,0.3314 Q0.6602,0.3314,0.6663,0.3304 T0.6756,0.3294 T0.6813,0.3304 Q0.6837,0.3304,0.6873,0.3309 L0.6946,0.3318 T0.7024,0.3323 T0.7105,0.3328 T0.7178,0.3338 T0.7238,0.3348 T0.7283,0.3363 Q0.7324,0.3383,0.7482,0.3403 T0.7689,0.3442 Q0.7697,0.3442,0.779,0.3477 T0.794,0.3536 T0.8074,0.3615 T0.8183,0.3729 Q0.8232,0.3808,0.8232,0.3848 Q0.8232,0.3867,0.8216,0.3892 T0.8151,0.3961 T0.8062,0.406 T0.7997,0.4179 T0.7952,0.4263 T0.7855,0.4308 T0.7616,0.4362 Q0.7543,0.4372,0.7445,0.4372 L0.7372,0.4372 L0.7307,0.4372 Q0.7202,0.4372,0.7153,0.4382 Q0.7064,0.4402,0.691,0.4402 L0.6845,0.4402 L0.6788,0.4402 Q0.6699,0.4402,0.6533,0.4436 T0.6318,0.451 Q0.6302,0.451,0.6302,0.452 Q0.6302,0.457,0.6626,0.459 Q0.6869,0.4609,0.7015,0.4659 Q0.7064,0.4669,0.7202,0.4669 L0.7311,0.4669 L0.7429,0.4669 Q0.7599,0.4669,0.7689,0.4688 Q0.7794,0.4708,0.7794,0.4738 L0.7786,0.4748 T0.777,0.4763 T0.7749,0.4773 L0.7737,0.4777 L0.7729,0.4777 Q0.7502,0.4797,0.738,0.4797 L0.7315,0.4797 L0.7291,0.4797 Q0.7226,0.4797,0.6869,0.4876 Q0.6821,0.4886,0.6768,0.4891 T0.6614,0.4901 T0.6456,0.4916 L0.6342,0.4916 Q0.6294,0.4916,0.6217,0.4911 T0.6115,0.4906 Q0.6083,0.4906,0.6054,0.4911 T0.5961,0.4936 T0.5815,0.4965 T0.5629,0.4985 Q0.5507,0.4985,0.5442,0.4995 T0.5345,0.5025 T0.5304,0.5084 L0.5296,0.5094 Q0.5296,0.5114,0.5316,0.5124 T0.5373,0.5138 T0.5442,0.5143 L0.5523,0.5143 L0.5604,0.5143 Q0.5637,0.5153,0.5677,0.5158 T0.5742,0.5173 T0.5811,0.5188 T0.5904,0.5193 L0.5977,0.5193 L0.6026,0.5193 Q0.6107,0.5193,0.6168,0.5208 T0.6294,0.5252 T0.6399,0.5292 Q0.6448,0.5302,0.6679,0.5341 T0.6983,0.541 Q0.7129,0.545,0.7664,0.55 Q0.7883,0.5519,0.7883,0.5559 Q0.7883,0.5579,0.7843,0.5608 T0.7721,0.5638 Q0.7697,0.5638,0.764,0.5633 T0.7559,0.5628 L0.7502,0.5628 Q0.7364,0.5648,0.6869,0.5648 L0.6699,0.5648 L0.6675,0.5648 Q0.665,0.5648,0.6602,0.5658 T0.6513,0.5668 Q0.6464,0.5668,0.6399,0.5658 Q0.6342,0.5648,0.6302,0.5648 T0.6225,0.5668 T0.6156,0.5697 Q0.6107,0.5707,0.6087,0.5707 T0.6034,0.5712 T0.5953,0.5717 Q0.5904,0.5727,0.5835,0.5727 T0.5718,0.5732 T0.562,0.5757 Q0.558,0.5767,0.5483,0.5767 L0.5349,0.5767 L0.5207,0.5767 Q0.5069,0.5767,0.5028,0.5786 Q0.4972,0.5816,0.4899,0.5816 Q0.4858,0.5816,0.4781,0.5806 T0.4663,0.5796 L0.4631,0.5796 Q0.4582,0.5796,0.4432,0.5821 T0.4144,0.5846 L0.4071,0.5846 L0.4011,0.5846 T0.3958,0.5851 T0.3921,0.5856 T0.3889,0.5861 T0.3865,0.5865 T0.384,0.5875 L0.382,0.5885 L0.3796,0.5895 L0.3771,0.5905 Q0.3731,0.5925,0.365,0.5925 L0.3601,0.5925 L0.3552,0.5925 Q0.3277,0.5925,0.3049,0.6044 Q0.3017,0.6053,0.296,0.6053 Q0.292,0.6053,0.2847,0.6044 T0.2741,0.6034 Q0.2693,0.6034,0.2547,0.6029 T0.236,0.6024 Q0.2222,0.6024,0.1922,0.6073 Q0.1906,0.6083,0.1882,0.6083 Q0.1833,0.6083,0.1711,0.6063 T0.1533,0.6044 Q0.1517,0.6034,0.1492,0.6034 Q0.1436,0.6034,0.135,0.6048 T0.1168,0.6078 T0.103,0.6093 Q0.0973,0.6103,0.0787,0.6103 L0.0604,0.6103 L0.0414,0.6103 L0.0235,0.6103 Q0.0024,0.6113,0.0024,0.6172 Q0.0024,0.6192,0.0032,0.6231 Q0.0049,0.6281,0.0333,0.6281 L0.0414,0.6281 L0.0479,0.6281 Q0.0519,0.6281,0.0624,0.6301 T0.0803,0.632 L0.0835,0.632 L0.0916,0.632 Q0.0989,0.632,0.1281,0.634 L0.206,0.6419 Q0.2117,0.6429,0.2174,0.6429 Q0.2214,0.6429,0.2271,0.6424 T0.2344,0.6419 L0.2376,0.6419 Q0.2393,0.6429,0.2417,0.6429 T0.2526,0.6419 T0.2652,0.6409 Q0.2741,0.6409,0.2782,0.6429 T0.2968,0.6459 T0.3293,0.6479 T0.352,0.6489 Q0.3552,0.6499,0.3585,0.6499 Q0.365,0.6499,0.3731,0.6474 T0.3844,0.6449 Q0.3885,0.6449,0.3917,0.6474 T0.4006,0.6499 Q0.4088,0.6499,0.4509,0.6578 Q0.4574,0.6588,0.4631,0.6588 Q0.4655,0.6588,0.4716,0.6583 T0.4834,0.6578 L0.4874,0.6578 Q0.5004,0.6588,0.5255,0.6637 Q0.532,0.6647,0.56,0.6667 T0.6148,0.6701 T0.644,0.6716 Q0.6464,0.6726,0.6594,0.6736 T0.6861,0.6756 T0.7048,0.6775 Q0.7097,0.6775,0.7186,0.6825 T0.738,0.6874 Q0.7518,0.6874,0.7729,0.6958 T0.8005,0.7122 Q0.8029,0.7141,0.8029,0.7151 Q0.8029,0.7191,0.7968,0.7216 T0.7818,0.726 T0.7705,0.729 L0.764,0.731 T0.7583,0.7324 T0.753,0.7334 T0.7486,0.7349 Q0.7251,0.7399,0.7161,0.7399 Q0.7097,0.7399,0.6951,0.7409 T0.6748,0.7418 L0.6703,0.7418 L0.6667,0.7418 Q0.6659,0.7418,0.6399,0.7448 L0.6334,0.7448 Q0.631,0.7448,0.6249,0.7443 T0.6148,0.7438 L0.6099,0.7438 Q0.601,0.7448,0.5856,0.7473 T0.5645,0.7507 Q0.5572,0.7517,0.5572,0.7547 T0.5616,0.7606 T0.5702,0.7646 Q0.5734,0.7646,0.5779,0.7651 T0.5864,0.7661 T0.5949,0.7671 T0.6018,0.7676 L0.6071,0.7676 T0.6135,0.7681 T0.62,0.769 T0.6269,0.771 T0.6342,0.7725 Q0.6391,0.7725,0.6464,0.7705 T0.6541,0.7685 L0.6553,0.7685 Q0.6586,0.7685,0.6699,0.771 T0.6853,0.7735 Q0.6902,0.7735,0.704,0.777 T0.7226,0.7834 Q0.7315,0.7873,0.7445,0.7883 Q0.747,0.7883,0.7676,0.7893 T0.7972,0.7913 Q0.7989,0.7913,0.8017,0.7918 T0.8058,0.7923 T0.809,0.7928 T0.8114,0.7933 T0.8131,0.7938 T0.8147,0.7943 T0.8159,0.7953 L0.8171,0.7967 T0.8175,0.7982 Q0.8183,0.7992,0.8183,0.8002 Q0.8183,0.8022,0.8155,0.8032 T0.807,0.8051 T0.7989,0.8071 Q0.7932,0.8091,0.7737,0.8091 L0.7413,0.8091 L0.7372,0.8091 Q0.7275,0.8091,0.721,0.8101 T0.7072,0.8136 T0.6983,0.817 Q0.6942,0.818,0.6825,0.818 T0.6602,0.819 T0.6456,0.822 Q0.6415,0.8249,0.6375,0.8249 Q0.6367,0.8249,0.6326,0.8239 T0.6253,0.8229 Q0.6034,0.8229,0.5953,0.8279 Q0.5912,0.8289,0.5848,0.8289 Q0.5775,0.8289,0.5645,0.8279 Q0.5629,0.8269,0.5604,0.8269 Q0.5523,0.8269,0.5328,0.8348 Q0.5296,0.8358,0.5264,0.8358 Q0.5247,0.8358,0.5203,0.8353 T0.5126,0.8348 Q0.5061,0.8348,0.4988,0.8358 Q0.4947,0.8368,0.4882,0.8378 Q0.4826,0.8388,0.4797,0.8388 T0.474,0.8398 T0.4704,0.8422 T0.4696,0.8462 T0.472,0.8501 T0.4781,0.8521 T0.4854,0.8526 L0.4935,0.8526 T0.4996,0.8536 Q0.5036,0.8536,0.515,0.8546 T0.5337,0.8566 Q0.5426,0.8576,0.5596,0.8576 L0.5657,0.8576 L0.571,0.8576 L0.5791,0.8576 Q0.5921,0.8586,0.6135,0.8591 T0.6383,0.8605 Q0.6683,0.8635,0.6764,0.8635 Q0.6999,0.8635,0.6999,0.8684 Q0.6999,0.8694,0.6967,0.8734 T0.6934,0.8803 L0.6934,0.8823 Q0.6951,0.8863,0.7137,0.8863 Q0.7218,0.8863,0.7267,0.8853 L0.7299,0.8853 Q0.734,0.8853,0.7409,0.8882 T0.7502,0.8912 Q0.7607,0.8942,0.794,0.8971 Q0.7972,0.8971,0.8078,0.9001 T0.8297,0.9031 Q0.8475,0.9031,0.8646,0.9065 T0.8824,0.9149 L0.8824,0.9159 Q0.8824,0.9169,0.882,0.9179 T0.8808,0.9194 L0.8792,0.9204 T0.8767,0.9214 T0.8743,0.9224 T0.8715,0.9228 L0.8686,0.9228 T0.8662,0.9233 T0.8637,0.9238 L0.8556,0.9238 T0.8447,0.9243 T0.8386,0.9268 Q0.837,0.9278,0.835,0.9288 T0.8305,0.9303 T0.8244,0.9308 T0.8175,0.9318 Q0.8127,0.9327,0.8017,0.9352 T0.7851,0.9397 Q0.777,0.9416,0.7705,0.9451 T0.7616,0.9496 Q0.7575,0.9505,0.7494,0.9505 L0.7405,0.9505 L0.7307,0.9505 Q0.7234,0.9505,0.7178,0.9515 Q0.7145,0.9515,0.7125,0.9535 T0.7105,0.9575 Q0.7105,0.9624,0.7194,0.9634 Q0.7567,0.9654,0.7664,0.9674 Q0.7745,0.9693,0.7762,0.9693 L0.7908,0.9693 L0.7948,0.9693 Q0.8005,0.9693,0.8058,0.9698 T0.8143,0.9718 T0.824,0.9753 T0.8354,0.9792 Q0.8483,0.9832,0.871,0.9832 L0.8739,0.9832 L0.8767,0.9832 Q0.8873,0.9832,0.8921,0.9842 Q0.8978,0.9852,0.9157,0.9896 T0.94,0.996 Q0.9424,0.996,0.9469,0.9965 T0.9566,0.997 T0.9676,0.9975 T0.9781,0.998 L0.9874,0.998 L0.9943,0.998 L0.9968,0.998 Z",type:0}},"\uE0CC":{type:5,data:{d:"M1,0.5071 L0.8623,0.7102 L0.5865,0.7102 L0.4489,0.5071 L0.5865,0.3045 L0.8623,0.3045 Z M0.4135,0.7974 L0.2758,1 L0,1 L0,0.5944 L0.2758,0.5944 Z M0.4135,0.2026 L0.2758,0.4056 L0,0.4056 L0,0 L0.2758,0 Z",type:0}},"\uE0CD":{type:5,data:{d:"M0.9726,0.5071 L0.8451,0.3184 L0.59,0.3184 L0.4624,0.5071 L0.59,0.6955 L0.8451,0.6955 Z M1,0.5071 L0.8588,0.716 L0.5768,0.716 L0.4355,0.5071 L0.5768,0.2982 L0.8588,0.2982 Z M0.401,0.7911 L0.2735,0.6028 L0.0236,0.6028 L0.0236,0.9799 L0.2735,0.9799 Z M0.4284,0.7911 L0.2872,1 L0,1 L0,0.5826 L0.2872,0.5826 Z M0.401,0.2089 L0.2735,0.0201 L0.0236,0.0201 L0.0236,0.3972 L0.2735,0.3972 Z M0.4284,0.2089 L0.2872,0.4174 L0,0.4174 L0,0 L0.2872,0 Z",type:0}},"\uE0CE":{type:5,data:{d:"M1,0.4996 Q1,0.5365,0.9924,0.568 T0.9718,0.6177 T0.9433,0.6363 L0.9425,0.6367 Q0.9355,0.6367,0.9285,0.6326 L0.9308,0.6326 Q0.9129,0.6195,0.9013,0.587 T0.8871,0.5164 T0.8943,0.4373 T0.9254,0.3687 L0.9254,0.3691 Q0.9339,0.3628,0.9425,0.3628 Q0.9584,0.3628,0.9716,0.3811 T0.9924,0.431 T1,0.4996 Z M0.9234,0.6321 L0.8776,0.7169 Q0.8791,0.732,0.8791,0.7441 Q0.8791,0.781,0.8716,0.8123 T0.8508,0.862 T0.8224,0.8809 L0.8216,0.8809 Q0.8154,0.8809,0.8092,0.8775 L0.8088,0.8775 Q0.7905,0.8633,0.7792,0.8297 T0.766,0.7571 T0.775,0.6768 T0.8088,0.6086 L0.7431,0.6065 Q0.719,0.6313,0.7072,0.6705 T0.6965,0.7487 T0.7097,0.823 T0.7423,0.8763 L0.7913,0.8775 L0.7314,0.9895 L0.4648,0.5038 L0.7299,0.0176 L0.7882,0.1229 L0.7404,0.1212 Q0.7244,0.138,0.7136,0.1617 T0.6982,0.2108 T0.6943,0.2632 T0.7003,0.3144 T0.7159,0.3586 T0.7396,0.3909 L0.8072,0.3926 Q0.7894,0.3796,0.7777,0.3479 T0.7635,0.2789 T0.7697,0.2011 T0.7987,0.1326 Q0.8092,0.1225,0.8204,0.1225 Q0.8321,0.1225,0.8426,0.1332 T0.8609,0.1623 T0.8731,0.2062 T0.8776,0.2592 Q0.8776,0.268,0.8768,0.2815 L0.9219,0.3628 L0.8644,0.3612 Q0.8403,0.3855,0.8284,0.4247 T0.8177,0.5029 T0.8309,0.5772 T0.8632,0.6305 Z M0.6759,0.6384 Q0.6576,0.625,0.6457,0.5914 T0.6319,0.5185 T0.6407,0.4375 T0.6751,0.3691 L0.609,0.367 Q0.5849,0.3918,0.5731,0.431 T0.5626,0.509 T0.5758,0.5833 T0.6082,0.6367 Z M0.6459,0.0025 L0.391,0.4551 L0.0035,0.4551 L0.246,0 Z M0.6553,1 L0.2421,1 L0,0.5449 L0.3875,0.5449 Z",type:0}},"\uE0CF":{type:5,data:{d:"M0.4996,0 Q0.5562,0,0.5961,0.0166 T0.6364,0.0566 L0.6368,0.0575 Q0.6368,0.0646,0.6324,0.0713 L0.6324,0.0692 Q0.6196,0.0868,0.5872,0.0986 T0.5166,0.1128 T0.4373,0.1057 T0.3689,0.0747 L0.3694,0.0747 Q0.3627,0.0663,0.3627,0.0575 Q0.3627,0.0336,0.4028,0.0168 T0.4996,0 Z M0.6324,0.0768 L0.717,0.1225 Q0.7321,0.1212,0.744,0.1212 Q0.8007,0.1212,0.8406,0.1378 T0.8809,0.1779 L0.8813,0.1783 Q0.8813,0.1846,0.8778,0.1909 Q0.8636,0.2093,0.8299,0.2208 T0.7573,0.2343 T0.6769,0.2253 T0.6089,0.1913 L0.6067,0.2567 Q0.6315,0.281,0.6707,0.2928 T0.7489,0.3035 T0.8231,0.2903 T0.8764,0.258 L0.8778,0.2085 L0.9898,0.2685 L0.504,0.5352 L0.0177,0.2701 L0.1227,0.2118 L0.1213,0.2596 Q0.1457,0.2836,0.1851,0.2955 T0.2635,0.3062 T0.3377,0.293 T0.3911,0.2605 L0.3928,0.193 Q0.38,0.2106,0.3483,0.2221 T0.2792,0.2364 T0.2013,0.2303 T0.1324,0.2013 Q0.1222,0.1909,0.1222,0.1795 Q0.1222,0.156,0.1623,0.1393 T0.2591,0.1225 Q0.2679,0.1225,0.2817,0.1233 L0.3627,0.078 L0.3609,0.1359 Q0.3857,0.1598,0.4249,0.1718 T0.5031,0.1825 T0.5773,0.1693 T0.6306,0.1367 Z M0.6386,0.3242 Q0.6253,0.3427,0.5917,0.3544 T0.5186,0.3683 T0.4376,0.3595 T0.3689,0.3251 L0.3671,0.3909 Q0.3915,0.4148,0.4309,0.4268 T0.5093,0.4375 T0.5835,0.4243 T0.6368,0.3918 Z M0.0027,0.354 L0.4553,0.6091 L0.4553,0.9962 L0,0.7542 Z M1,0.3448 L1,0.7576 L0.5447,1 L0.5447,0.6128 Z",type:0}},"\uE0D0":{type:5,data:{d:"M0,0.7699 Q0,0.7172,0.0277,0.6731 T0.103,0.6032 T0.2066,0.5774 Q0.2487,0.5774,0.2865,0.5922 T0.3526,0.6339 L0.3935,0.6715 Q0.4218,0.6984,0.438,0.7336 T0.4541,0.8075 Q0.4541,0.8602,0.4264,0.9046 T0.3511,0.9745 T0.2475,1 Q0.2054,1,0.1676,0.9852 T0.1016,0.9435 L0.0606,0.9059 Q0.0317,0.879,0.0159,0.8438 T0,0.7699 Z M0.5459,0.7699 Q0.5459,0.7172,0.5736,0.6731 T0.6489,0.6032 T0.7525,0.5774 Q0.7946,0.5774,0.8324,0.5922 T0.8984,0.6339 L0.9394,0.6715 Q0.9579,0.6892,0.9714,0.7105 T0.9925,0.7567 T1,0.8075 Q1,0.8602,0.9723,0.9046 T0.897,0.9745 T0.7934,1 Q0.7513,1,0.7135,0.9852 T0.6474,0.9435 L0.6065,0.9059 Q0.5776,0.879,0.5617,0.8438 T0.5459,0.7699 Z M0,0.1925 Q0,0.1398,0.0277,0.0957 T0.103,0.0258 T0.2066,0 Q0.2487,0,0.2865,0.0148 T0.3526,0.0565 L0.3935,0.0941 Q0.4218,0.121,0.438,0.1562 T0.4541,0.2301 Q0.4541,0.2828,0.4264,0.3269 T0.3511,0.3968 T0.2475,0.4226 Q0.2054,0.4226,0.1676,0.4078 T0.1016,0.3661 L0.0606,0.3285 Q0.0317,0.3016,0.0159,0.2664 T0,0.1925 Z M0.5459,0.1925 Q0.5459,0.1398,0.5736,0.0957 T0.6489,0.0258 T0.7525,0 Q0.7946,0,0.8324,0.0148 T0.8984,0.0565 L0.9394,0.0941 Q0.9677,0.121,0.9838,0.1562 T1,0.2301 Q1,0.2828,0.9723,0.3269 T0.897,0.3968 T0.7934,0.4226 Q0.7513,0.4226,0.7135,0.4078 T0.6474,0.3661 L0.6065,0.3285 Q0.5874,0.3108,0.5741,0.2895 T0.5534,0.2433 T0.5459,0.1925 Z M0.3301,0.6403 Q0.3306,0.6403,0.3312,0.6409 L0.3272,0.6371 L0.3272,0.6371 Z M0.8759,0.6403 Q0.8765,0.6403,0.8771,0.6409 Q0.8748,0.6387,0.8725,0.6371 L0.8731,0.6371 Z M0.3301,0.0629 Q0.3306,0.0629,0.3312,0.0634 L0.3272,0.0597 L0.3272,0.0602 Z M0.8759,0.0629 Q0.8765,0.0629,0.8771,0.0634 Q0.8748,0.0613,0.8725,0.0597 Q0.8731,0.0597,0.8731,0.0602 Z M0.779,0.4113 L0.7501,0.3849 Q0.7046,0.3844,0.6636,0.3661 Q0.7132,0.407,0.779,0.4113 Z M0.7957,0.4118 Q0.8477,0.4113,0.8918,0.3874 T0.9619,0.3223 T0.9885,0.2323 L0.9585,0.2048 Q0.9538,0.2769,0.8987,0.3282 T0.7657,0.3844 Z M0.9879,0.2167 Q0.9827,0.1554,0.9388,0.1097 Q0.9585,0.1478,0.959,0.1898 Z M0.2331,0.4113 L0.2043,0.3849 Q0.1587,0.3844,0.1177,0.3661 Q0.1673,0.407,0.2331,0.4113 Z M0.2499,0.4118 Q0.2885,0.4118,0.324,0.3973 T0.3852,0.3589 T0.4264,0.3019 T0.4426,0.2323 L0.4126,0.2048 Q0.408,0.2769,0.3529,0.3282 T0.2198,0.3844 Z M0.442,0.2167 Q0.4368,0.1554,0.393,0.1097 Q0.4126,0.1478,0.4132,0.1903 Z M0.779,0.9887 L0.7501,0.9624 Q0.7046,0.9618,0.6636,0.9435 Q0.7132,0.9844,0.779,0.9887 Z M0.7957,0.9892 Q0.8477,0.9887,0.8918,0.9648 T0.9619,0.8997 T0.9885,0.8097 L0.9585,0.7823 Q0.9538,0.8543,0.8987,0.9056 T0.7657,0.9618 Z M0.9879,0.7941 Q0.9827,0.7328,0.9388,0.6871 Q0.9585,0.7253,0.959,0.7672 Z M0.2331,0.9887 L0.2043,0.9624 Q0.1587,0.9618,0.1177,0.9435 Q0.1673,0.9844,0.2331,0.9887 Z M0.2499,0.9892 Q0.3018,0.9887,0.3459,0.9648 T0.416,0.8997 T0.4426,0.8102 L0.4126,0.7823 Q0.408,0.8543,0.3529,0.9056 T0.2198,0.9618 Z M0.442,0.7941 Q0.4368,0.7328,0.393,0.6871 Q0.4126,0.7253,0.4132,0.7672 Z M0.3191,0.6452 Q0.2943,0.622,0.2643,0.6091 T0.2066,0.5962 Q0.1795,0.5962,0.1497,0.6091 T0.0949,0.6441 T0.0537,0.6995 T0.0375,0.7699 Q0.0375,0.8419,0.0941,0.8941 Q0.1189,0.9172,0.1489,0.9304 T0.2066,0.9435 Q0.2337,0.9435,0.2634,0.9304 T0.3182,0.8952 T0.3595,0.8398 T0.3756,0.7699 Q0.3756,0.6978,0.3191,0.6452 Z M0.026,0.7731 L0.026,0.7699 Q0.026,0.6935,0.0854,0.6376 Q0.0658,0.6538,0.0514,0.6739 T0.0289,0.718 T0.0202,0.7677 Z M0.0271,0.7892 L0.0208,0.7833 Q0.0254,0.8419,0.0681,0.886 Q0.0664,0.8828,0.0646,0.879 Q0.0329,0.8382,0.0271,0.7892 Z M0.3866,0.7823 Q0.382,0.8511,0.3278,0.9016 Q0.3843,0.8565,0.3918,0.7871 Z M0.3872,0.7677 L0.393,0.7726 L0.393,0.7699 Q0.393,0.7349,0.3791,0.7038 Q0.3739,0.6973,0.3681,0.6914 Q0.3866,0.7274,0.3872,0.7677 Z M0.865,0.6452 Q0.8402,0.622,0.8102,0.6091 T0.7525,0.5962 Q0.7305,0.5962,0.7072,0.6043 T0.6619,0.628 T0.6223,0.6642 T0.5941,0.7124 T0.5834,0.7699 Q0.5834,0.8419,0.6399,0.8941 Q0.6561,0.9097,0.6757,0.9207 T0.7149,0.9376 T0.7525,0.9435 Q0.7796,0.9435,0.8093,0.9304 T0.8641,0.8952 T0.9054,0.8398 T0.9215,0.7699 Q0.9215,0.6978,0.865,0.6452 Z M0.5718,0.7731 L0.5718,0.7699 Q0.5718,0.6935,0.6313,0.6376 Q0.6013,0.6618,0.584,0.6954 T0.5661,0.7677 Z M0.573,0.7892 L0.5666,0.7833 Q0.5713,0.8419,0.614,0.886 Q0.6122,0.8828,0.6105,0.879 Q0.5788,0.8382,0.573,0.7892 Z M0.9325,0.7823 Q0.9279,0.8511,0.8736,0.9016 Q0.9308,0.8565,0.9377,0.7871 Z M0.9331,0.7672 L0.9388,0.7726 L0.9388,0.7699 Q0.9388,0.7349,0.925,0.7038 Q0.9198,0.6973,0.914,0.6914 Q0.9325,0.7274,0.9331,0.7672 Z M0.3191,0.0677 Q0.2943,0.0446,0.2643,0.0317 T0.2066,0.0188 Q0.1795,0.0188,0.1497,0.0317 T0.0949,0.0667 T0.0537,0.122 T0.0375,0.1925 Q0.0375,0.2645,0.0941,0.3167 Q0.1189,0.3398,0.1489,0.353 T0.2066,0.3661 Q0.2337,0.3661,0.2634,0.353 T0.3182,0.3177 T0.3595,0.2624 T0.3756,0.1925 Q0.3756,0.1204,0.3191,0.0677 Z M0.026,0.1957 L0.026,0.1925 Q0.026,0.1161,0.0854,0.0602 Q0.0554,0.0844,0.0381,0.118 T0.0202,0.1903 Z M0.0271,0.2118 L0.0208,0.2059 Q0.0254,0.2645,0.0681,0.3086 Q0.0664,0.3054,0.0646,0.3016 Q0.0329,0.2608,0.0271,0.2118 Z M0.3866,0.2048 Q0.382,0.2737,0.3278,0.3242 Q0.3849,0.279,0.3918,0.2097 Z M0.3872,0.1903 L0.393,0.1952 L0.393,0.1925 Q0.393,0.1581,0.3791,0.1263 Q0.3739,0.1199,0.3681,0.114 Q0.3866,0.15,0.3872,0.1903 Z M0.865,0.0677 Q0.8402,0.0446,0.8102,0.0317 T0.7525,0.0188 Q0.7253,0.0188,0.6956,0.0317 T0.6408,0.0667 T0.5995,0.122 T0.5834,0.1925 Q0.5834,0.2645,0.6399,0.3167 Q0.6647,0.3398,0.6947,0.353 T0.7525,0.3661 Q0.7796,0.3661,0.8093,0.353 T0.8641,0.3177 T0.9054,0.2624 T0.9215,0.1925 Q0.9215,0.1204,0.865,0.0677 Z M0.5718,0.1957 L0.5718,0.1925 Q0.5718,0.1161,0.6313,0.0602 Q0.6013,0.0844,0.584,0.118 T0.5661,0.1903 Z M0.573,0.2118 L0.5666,0.2059 Q0.5713,0.2645,0.614,0.3086 Q0.6122,0.3054,0.6105,0.3016 Q0.5788,0.2608,0.573,0.2118 Z M0.9325,0.2048 Q0.9279,0.2737,0.8736,0.3242 Q0.9308,0.279,0.9377,0.2097 Z M0.9331,0.1898 L0.9388,0.1952 L0.9388,0.1925 Q0.9388,0.1575,0.925,0.1263 Q0.9198,0.1199,0.914,0.114 Q0.9325,0.15,0.9331,0.1898 Z",type:0}},"\uE0D1":{type:5,data:{d:"M0.7639,0.1974 L0.9593,0.1974 L0.9593,0.1443 L0.7639,0.1443 L0.7639,0.1974 Z M0.7586,0.109 L0.9727,0.109 Q0.9839,0.109,0.992,0.1153 T1,0.1291 L1,0.382 Q1,0.3906,0.9912,0.3964 T0.9727,0.4021 L0.7586,0.4021 Q0.7468,0.4021,0.739,0.3956 T0.7313,0.382 L0.7313,0.1291 Q0.7313,0.1246,0.7339,0.1207 T0.7406,0.1143 T0.7495,0.1104 T0.7586,0.109 Z M0.7639,0.6575 L0.9593,0.6575 L0.9593,0.6044 L0.7639,0.6044 L0.7639,0.6575 Z M0.7313,0.8347 L0.7313,0.5843 Q0.7313,0.5757,0.7401,0.5699 T0.7586,0.5641 L0.9706,0.5641 Q0.9818,0.5641,0.9898,0.5705 T0.9979,0.5843 L0.9979,0.8347 Q0.9979,0.8433,0.989,0.8491 T0.9706,0.8549 L0.7586,0.8549 Q0.7468,0.8549,0.739,0.8483 T0.7313,0.8347 Z M0,1 L0,0 L0.7607,0 L0.7607,1 L0,1 Z",type:0}},"\uE0D2":{type:5,data:{d:"M0.9764,0.0029 L0.3179,0.4552 L0,0.4552 L0,0 Z M1,1 L0,1 L0,0.5448 L0.3091,0.5448 Z",type:0}},"\uE0D4":{type:5,data:{d:"M0.0236,0.0029 L0.6824,0.4552 L1,0.4552 L1,0 Z M0,1 L1,1 L1,0.5448 L0.6912,0.5448 Z",type:0}},"\uEE00":{type:3,data:"M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z"},"\uEE01":{type:3,data:"M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z"},"\uEE02":{type:3,data:"M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z"},"\uEE03":{type:3,data:"M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z M.25,.15 L1,.15 L1,.85 L.25,.85 Z"},"\uEE04":{type:3,data:"M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z M0,.15 L1,.15 L1,.85 L0,.85 Z"},"\uEE05":{type:3,data:"M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z M0,.15 L.75,.15 L.75,.85 L0,.85 Z"},"\uEE06":{type:5,data:{d:"M0.6574,0.303 Q0.623,0.291,0.5852,0.285 T0.5082,0.279 T0.4311,0.285 T0.359,0.303 L0.3082,0.248 Q0.3525,0.232,0.4041,0.2235 T0.5082,0.215 T0.6123,0.2235 T0.7082,0.248 Z",type:0},scaleType:1},"\uEE07":{type:5,data:{d:"M0.8557,0.582 L0.7656,0.551 Q0.7852,0.53,0.7951,0.507 T0.8049,0.46 Q0.8049,0.424,0.782,0.3905 T0.718,0.332 T0.6221,0.293 T0.5082,0.279 L0.5082,0.215 Q0.5607,0.215,0.6123,0.2235 T0.709,0.248 T0.7918,0.287 T0.8557,0.3375 T0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.582 Z",type:0},scaleType:1},"\uEE08":{type:5,data:{d:"M0.5082,0.705 Q0.482,0.705,0.4557,0.703 T0.4049,0.697 L0.4311,0.635 Q0.4508,0.638,0.4697,0.6395 T0.5082,0.641 Q0.5672,0.641,0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 Q0.8049,0.436,0.7951,0.413 T0.7656,0.369 L0.8557,0.338 Q0.882,0.365,0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z",type:0},scaleType:1},"\uEE09":{type:5,data:{d:"M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 L0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 T0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 L0.9098,0.46 Q0.9098,0.492,0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z",type:0},scaleType:1},"\uEE0A":{type:5,data:{d:"M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.338 L0.2508,0.369 Q0.2311,0.39,0.2213,0.413 T0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 Q0.5279,0.641,0.5467,0.6395 T0.5852,0.635 L0.6115,0.697 Q0.5869,0.701,0.5607,0.703 T0.5082,0.705 Z",type:0},scaleType:1},"\uEE0B":{type:5,data:{d:"M0.1607,0.582 Q0.1344,0.555,0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.3375 T0.2246,0.287 T0.3074,0.248 T0.4041,0.2235 T0.5082,0.215 L0.5082,0.279 Q0.4492,0.279,0.3943,0.293 T0.2984,0.332 T0.2344,0.3905 T0.2115,0.46 Q0.2115,0.484,0.2213,0.507 T0.2508,0.551 Z",type:0},scaleType:1},"\uF5D0":l.LINE_H,"\uF5D1":l.LINE_V,"\uF5D2":l.FADE_RIGHT,"\uF5D3":l.FADE_LEFT,"\uF5D4":l.FADE_DOWN,"\uF5D5":l.FADE_UP,"\uF5D6":l.CURVE_DOWN_RIGHT,"\uF5D7":l.CURVE_DOWN_LEFT,"\uF5D8":l.CURVE_UP_RIGHT,"\uF5D9":l.CURVE_UP_LEFT,"\uF5DA":[l.LINE_V,l.CURVE_UP_RIGHT],"\uF5DB":[l.LINE_V,l.CURVE_DOWN_RIGHT],"\uF5DC":[l.CURVE_DOWN_RIGHT,l.CURVE_UP_RIGHT],"\uF5DD":[l.LINE_V,l.CURVE_UP_LEFT],"\uF5DE":[l.LINE_V,l.CURVE_DOWN_LEFT],"\uF5DF":[l.CURVE_DOWN_LEFT,l.CURVE_UP_LEFT],"\uF5E0":[l.LINE_H,l.CURVE_DOWN_LEFT],"\uF5E1":[l.LINE_H,l.CURVE_DOWN_RIGHT],"\uF5E2":[l.CURVE_DOWN_LEFT,l.CURVE_DOWN_RIGHT],"\uF5E3":[l.LINE_H,l.CURVE_UP_LEFT],"\uF5E4":[l.LINE_H,l.CURVE_UP_RIGHT],"\uF5E5":[l.CURVE_UP_LEFT,l.CURVE_UP_RIGHT],"\uF5E6":[l.LINE_V,l.CURVE_UP_LEFT,l.CURVE_UP_RIGHT],"\uF5E7":[l.LINE_V,l.CURVE_DOWN_LEFT,l.CURVE_DOWN_RIGHT],"\uF5E8":[l.LINE_H,l.CURVE_DOWN_LEFT,l.CURVE_UP_LEFT],"\uF5E9":[l.LINE_H,l.CURVE_DOWN_RIGHT,l.CURVE_UP_RIGHT],"\uF5EA":[l.LINE_V,l.CURVE_DOWN_RIGHT,l.CURVE_UP_LEFT],"\uF5EB":[l.LINE_V,l.CURVE_DOWN_LEFT,l.CURVE_UP_RIGHT],"\uF5EC":[l.LINE_H,l.CURVE_DOWN_RIGHT,l.CURVE_UP_LEFT],"\uF5ED":[l.LINE_H,l.CURVE_DOWN_LEFT,l.CURVE_UP_RIGHT],"\uF5EE":[l.NODE_FILL,l.NODE_STROKE],"\uF5EF":l.NODE_STROKE,"\uF5F0":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_RIGHT],"\uF5F1":[l.NODE_STROKE,l.NODE_LINE_RIGHT],"\uF5F2":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_LEFT],"\uF5F3":[l.NODE_STROKE,l.NODE_LINE_LEFT],"\uF5F4":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF5F5":[l.NODE_STROKE,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF5F6":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN],"\uF5F7":[l.NODE_STROKE,l.NODE_LINE_DOWN],"\uF5F8":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP],"\uF5F9":[l.NODE_STROKE,l.NODE_LINE_UP],"\uF5FA":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_UP],"\uF5FB":[l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_UP],"\uF5FC":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_RIGHT],"\uF5FD":[l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_RIGHT],"\uF5FE":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT],"\uF5FF":[l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT],"\uF600":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_RIGHT],"\uF601":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_RIGHT],"\uF602":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_LEFT],"\uF603":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_LEFT],"\uF604":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_RIGHT],"\uF605":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_RIGHT],"\uF606":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT],"\uF607":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT],"\uF608":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF609":[l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF60A":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF60B":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF60C":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF60D":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\u{1FB00}":f(1),"\u{1FB01}":f(2),"\u{1FB02}":f(3),"\u{1FB03}":f(4),"\u{1FB04}":f(5),"\u{1FB05}":f(6),"\u{1FB06}":f(7),"\u{1FB07}":f(8),"\u{1FB08}":f(9),"\u{1FB09}":f(10),"\u{1FB0A}":f(11),"\u{1FB0B}":f(12),"\u{1FB0C}":f(13),"\u{1FB0D}":f(14),"\u{1FB0E}":f(15),"\u{1FB0F}":f(16),"\u{1FB10}":f(17),"\u{1FB11}":f(18),"\u{1FB12}":f(19),"\u{1FB13}":f(20),"\u{1FB14}":f(22),"\u{1FB15}":f(23),"\u{1FB16}":f(24),"\u{1FB17}":f(25),"\u{1FB18}":f(26),"\u{1FB19}":f(27),"\u{1FB1A}":f(28),"\u{1FB1B}":f(29),"\u{1FB1C}":f(30),"\u{1FB1D}":f(31),"\u{1FB1E}":f(32),"\u{1FB1F}":f(33),"\u{1FB20}":f(34),"\u{1FB21}":f(35),"\u{1FB22}":f(36),"\u{1FB23}":f(37),"\u{1FB24}":f(38),"\u{1FB25}":f(39),"\u{1FB26}":f(40),"\u{1FB27}":f(41),"\u{1FB28}":f(43),"\u{1FB29}":f(44),"\u{1FB2A}":f(45),"\u{1FB2B}":f(46),"\u{1FB2C}":f(47),"\u{1FB2D}":f(48),"\u{1FB2E}":f(49),"\u{1FB2F}":f(50),"\u{1FB30}":f(51),"\u{1FB31}":f(52),"\u{1FB32}":f(53),"\u{1FB33}":f(54),"\u{1FB34}":f(55),"\u{1FB35}":f(56),"\u{1FB36}":f(57),"\u{1FB37}":f(58),"\u{1FB38}":f(59),"\u{1FB39}":f(60),"\u{1FB3A}":f(61),"\u{1FB3B}":f(62),"\u{1FB3C}":{type:3,data:"M0,0.6667 L0,1 L0.5,1 Z"},"\u{1FB3D}":{type:3,data:"M0,0.6667 L0,1 L1,1 Z"},"\u{1FB3E}":{type:3,data:"M0,0.3333 L0,1 L0.5,1 Z"},"\u{1FB3F}":{type:3,data:"M0,0.3333 L0,1 L1,1 Z"},"\u{1FB40}":{type:3,data:"M0,0 L0,1 L0.5,1 Z"},"\u{1FB41}":{type:3,data:"M0,0.3333 L0.5,0 L1,0 L1,1 L0,1 Z"},"\u{1FB42}":{type:3,data:"M0,0.3333 L1,0 L1,1 L0,1 Z"},"\u{1FB43}":{type:3,data:"M0,0.6667 L0.5,0 L1,0 L1,1 L0,1 Z"},"\u{1FB44}":{type:3,data:"M0,0.6667 L1,0 L1,1 L0,1 Z"},"\u{1FB45}":{type:3,data:"M0,1 L0.5,0 L1,0 L1,1 Z"},"\u{1FB46}":{type:3,data:"M0,0.6667 L1,0.3333 L1,1 L0,1 Z"},"\u{1FB47}":{type:3,data:"M0.5,1 L1,0.6667 L1,1 Z"},"\u{1FB48}":{type:3,data:"M0,1 L1,0.6667 L1,1 Z"},"\u{1FB49}":{type:3,data:"M0.5,1 L1,0.3333 L1,1 Z"},"\u{1FB4A}":{type:3,data:"M0,1 L1,0.3333 L1,1 Z"},"\u{1FB4B}":{type:3,data:"M0.5,1 L1,0 L1,1 Z"},"\u{1FB4C}":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 L1,0.3333 Z"},"\u{1FB4D}":{type:3,data:"M0,0 L0,1 L1,1 L1,0.3333 Z"},"\u{1FB4E}":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 L1,0.6667 Z"},"\u{1FB4F}":{type:3,data:"M0,0 L0,1 L1,1 L1,0.6667 Z"},"\u{1FB50}":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 Z"},"\u{1FB51}":{type:3,data:"M0,0.3333 L0,1 L1,1 L1,0.6667 Z"},"\u{1FB52}":{type:3,data:"M0,0.6667 L0.5,1 L1,1 L1,0 L0,0 Z"},"\u{1FB53}":{type:3,data:"M0,0.6667 L1,1 L1,0 L0,0 Z"},"\u{1FB54}":{type:3,data:"M0,0.3333 L0.5,1 L1,1 L1,0 L0,0 Z"},"\u{1FB55}":{type:3,data:"M0,0.3333 L1,1 L1,0 L0,0 Z"},"\u{1FB56}":{type:3,data:"M0,0 L0.5,1 L1,1 L1,0 Z"},"\u{1FB57}":{type:3,data:"M0,0.3333 L0,0 L0.5,0 Z"},"\u{1FB58}":{type:3,data:"M0,0.3333 L0,0 L1,0 Z"},"\u{1FB59}":{type:3,data:"M0,0.6667 L0,0 L0.5,0 Z"},"\u{1FB5A}":{type:3,data:"M0,0.6667 L0,0 L1,0 Z"},"\u{1FB5B}":{type:3,data:"M0,1 L0,0 L0.5,0 Z"},"\u{1FB5C}":{type:3,data:"M0,0.6667 L0,0 L1,0 L1,0.3333 Z"},"\u{1FB5D}":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 L1,0.6667 Z"},"\u{1FB5E}":{type:3,data:"M0,1 L0,0 L1,0 L1,0.6667 Z"},"\u{1FB5F}":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 L1,0.3333 Z"},"\u{1FB60}":{type:3,data:"M0,1 L0,0 L1,0 L1,0.3333 Z"},"\u{1FB61}":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 Z"},"\u{1FB62}":{type:3,data:"M0.5,0 L1,0 L1,0.3333 Z"},"\u{1FB63}":{type:3,data:"M0,0 L1,0 L1,0.3333 Z"},"\u{1FB64}":{type:3,data:"M0.5,0 L1,0 L1,0.6667 Z"},"\u{1FB65}":{type:3,data:"M0,0 L1,0 L1,0.6667 Z"},"\u{1FB66}":{type:3,data:"M0.5,0 L1,0 L1,1 Z"},"\u{1FB67}":{type:3,data:"M0,0.3333 L1,0.6667 L1,0 L0,0 Z"},"\u{1FB68}":{type:3,data:"M0,0 L1,0 L1,1 L0,1 L0.5,0.5 Z"},"\u{1FB69}":{type:3,data:"M0,0 L0.5,0.5 L1,0 L1,1 L0,1 Z"},"\u{1FB6A}":{type:3,data:"M0,0 L1,0 L0.5,0.5 L1,1 L0,1 Z"},"\u{1FB6B}":{type:3,data:"M0,0 L1,0 L1,1 L0.5,0.5 L0,1 Z"},"\u{1FB6C}":{type:3,data:"M0,0 L0.5,0.5 L0,1 Z"},"\u{1FB6D}":{type:3,data:"M0,0 L1,0 L0.5,0.5 Z"},"\u{1FB6E}":{type:3,data:"M1,0 L1,1 L0.5,0.5 Z"},"\u{1FB6F}":{type:3,data:"M0,1 L1,1 L0.5,0.5 Z"},"\u{1FB70}":{type:0,data:[{x:1,y:0,w:1,h:8}]},"\u{1FB71}":{type:0,data:[{x:2,y:0,w:1,h:8}]},"\u{1FB72}":{type:0,data:[{x:3,y:0,w:1,h:8}]},"\u{1FB73}":{type:0,data:[{x:4,y:0,w:1,h:8}]},"\u{1FB74}":{type:0,data:[{x:5,y:0,w:1,h:8}]},"\u{1FB75}":{type:0,data:[{x:6,y:0,w:1,h:8}]},"\u{1FB76}":{type:0,data:[{x:0,y:1,w:8,h:1}]},"\u{1FB77}":{type:0,data:[{x:0,y:2,w:8,h:1}]},"\u{1FB78}":{type:0,data:[{x:0,y:3,w:8,h:1}]},"\u{1FB79}":{type:0,data:[{x:0,y:4,w:8,h:1}]},"\u{1FB7A}":{type:0,data:[{x:0,y:5,w:8,h:1}]},"\u{1FB7B}":{type:0,data:[{x:0,y:6,w:8,h:1}]},"\u{1FB7C}":{type:0,data:[{x:0,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}]},"\u{1FB7D}":{type:0,data:[{x:0,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}]},"\u{1FB7E}":{type:0,data:[{x:7,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}]},"\u{1FB7F}":{type:0,data:[{x:7,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}]},"\u{1FB80}":{type:0,data:[{x:0,y:0,w:8,h:1},{x:0,y:7,w:8,h:1}]},"\u{1FB81}":{type:0,data:[{x:0,y:0,w:8,h:1},{x:0,y:2,w:8,h:1},{x:0,y:4,w:8,h:1},{x:0,y:7,w:8,h:1}]},"\u{1FB82}":{type:0,data:[{x:0,y:0,w:8,h:2}]},"\u{1FB83}":{type:0,data:[{x:0,y:0,w:8,h:3}]},"\u{1FB84}":{type:0,data:[{x:0,y:0,w:8,h:5}]},"\u{1FB85}":{type:0,data:[{x:0,y:0,w:8,h:6}]},"\u{1FB86}":{type:0,data:[{x:0,y:0,w:8,h:7}]},"\u{1FB87}":{type:0,data:[{x:6,y:0,w:2,h:8}]},"\u{1FB88}":{type:0,data:[{x:5,y:0,w:3,h:8}]},"\u{1FB89}":{type:0,data:[{x:3,y:0,w:5,h:8}]},"\u{1FB8A}":{type:0,data:[{x:2,y:0,w:6,h:8}]},"\u{1FB8B}":{type:0,data:[{x:1,y:0,w:7,h:8}]},"\u{1FB8C}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L0.5,0 L0.5,1 L0,1 Z"},"\u{1FB8D}":{type:1,data:[[1,0],[0,1]],clipPath:"M0.5,0 L1,0 L1,1 L0.5,1 Z"},"\u{1FB8E}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L1,0.5 L0,0.5 Z"},"\u{1FB8F}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0.5 L1,0.5 L1,1 L0,1 Z"},"\u{1FB90}":{type:1,data:[[0,1],[1,0]]},"\u{1FB91}":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0.5 L1,0.5 L1,1 L0,1 Z"},{type:0,data:[{x:0,y:0,w:8,h:4}]}],"\u{1FB92}":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0 L1,0 L1,0.5 L0,0.5 Z"},{type:0,data:[{x:0,y:4,w:8,h:4}]}],"\u{1FB94}":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0 L0.5,0 L0.5,1 L0,1 Z"},{type:0,data:[{x:4,y:0,w:4,h:8}]}],"\u{1FB95}":{type:0,data:[{x:0,y:0,w:2,h:2},{x:4,y:0,w:2,h:2},{x:2,y:2,w:2,h:2},{x:6,y:2,w:2,h:2},{x:0,y:4,w:2,h:2},{x:4,y:4,w:2,h:2},{x:2,y:6,w:2,h:2},{x:6,y:6,w:2,h:2}]},"\u{1FB96}":{type:0,data:[{x:2,y:0,w:2,h:2},{x:6,y:0,w:2,h:2},{x:0,y:2,w:2,h:2},{x:4,y:2,w:2,h:2},{x:2,y:4,w:2,h:2},{x:6,y:4,w:2,h:2},{x:0,y:6,w:2,h:2},{x:4,y:6,w:2,h:2}]},"\u{1FB97}":{type:0,data:[{x:0,y:2,w:8,h:2},{x:0,y:6,w:8,h:2}]},"\u{1FB98}":{type:2,data:"M-0.25,-0.25 L1.25,1.25 M-0.25,0 L1,1.25 M-0.25,0.25 L0.75,1.25 M-0.25,0.5 L0.5,1.25 M0,-0.25 L1.25,1 M0.25,-0.25 L1.25,0.75 M0.5,-0.25 L1.25,0.5 M-0.25,0.75 L0.25,1.25 M0.75,-0.25 L1.25,0.25",strokeWidth:1},"\u{1FB99}":{type:2,data:"M-0.25,0.5 L0.5,-0.25 M-0.25,0.75 L0.75,-0.25 M-0.25,1 L1,-0.25 M-0.25,1.25 L1.25,-0.25 M0,1.25 L1.25,0 M0.25,1.25 L1.25,0.25 M0.5,1.25 L1.25,0.5 M-0.25,0.25 L0.25,-0.25 M0.75,1.25 L1.25,0.75",strokeWidth:1},"\u{1FB9A}":{type:5,data:{d:"M0,0 L.5,.5 L0,1 L1,1 L.5,.5 L1,0",type:0}},"\u{1FB9B}":{type:5,data:{d:"M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1",type:0}},"\u{1FB9C}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L0,1 Z"},"\u{1FB9D}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L1,1 Z"},"\u{1FB9E}":{type:1,data:[[1,0],[0,1]],clipPath:"M1,0 L1,1 L0,1 Z"},"\u{1FB9F}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,1 L0,1 Z"},"\u{1FBA0}":{type:2,data:"M.5,0 L0,.5",strokeWidth:1},"\u{1FBA1}":{type:2,data:"M.5,0 L1,.5",strokeWidth:1},"\u{1FBA2}":{type:2,data:"M0,.5 L.5,1",strokeWidth:1},"\u{1FBA3}":{type:2,data:"M1,.5 L.5,1",strokeWidth:1},"\u{1FBA4}":{type:2,data:"M.5,0 L0,.5 L.5,1",strokeWidth:1},"\u{1FBA5}":{type:2,data:"M.5,0 L1,.5 L.5,1",strokeWidth:1},"\u{1FBA6}":{type:2,data:"M0,.5 L.5,1 L1,.5",strokeWidth:1},"\u{1FBA7}":{type:2,data:"M0,.5 L.5,0 L1,.5",strokeWidth:1},"\u{1FBA8}":{type:2,data:"M.5,0 L0,.5 M1,.5 L.5,1",strokeWidth:1},"\u{1FBA9}":{type:2,data:"M.5,0 L1,.5 M0,.5 L.5,1",strokeWidth:1},"\u{1FBAA}":{type:2,data:"M.5,0 L1,.5 L.5,1 L0,.5",strokeWidth:1},"\u{1FBAB}":{type:2,data:"M.5,0 L0,.5 L.5,1 L1,.5",strokeWidth:1},"\u{1FBAC}":{type:2,data:"M0,.5 L.5,0 L1,.5 L.5,1",strokeWidth:1},"\u{1FBAD}":{type:2,data:"M1,.5 L.5,0 L0,.5 L.5,1",strokeWidth:1},"\u{1FBAE}":{type:2,data:"M.5,0 L1,.5 L.5,1 L0,.5 Z",strokeWidth:1},"\u{1FBAF}":{type:2,data:"M0,.5 L1,.5 M.5,.35 L.5,.65",strokeWidth:1},"\u{1FBB0}":{type:3,data:"M0.1,0.2 L0.1,.8 L.4,.6 L.9,0.6 Z",scaleType:1},"\u{1FBB1}":{type:4,data:{d:"M.1,.525 L.35,.675 L.9,.35",type:1},scaleType:1},"\u{1FBB2}":{type:3,data:"M.29,.27 L0.13,.56 L.22,.59 L.35,0.35 L.67,.35 L.57,.57 L.71,.76 L.22,.76 L.42,1 L.53,.98 L.43,.86 L.9,.86 L.71,.6 L1,.6 L1,.52 L.83,.52 L.92,.36 L1,.36 L1,.27Z M.99,.13 A.12,.12,0,1,1,.75,.13 A.12,.12,0,1,1,.99,.13"},"\u{1FBB3}":{type:3,data:"M0,.27 L.3,.27 L.55,.12 L.63,.18 L.33,.36 L0,.36 M0,.52 L.33,.52 L.59,.89 L.73,.89 L.73,.98 L.53,.98 L.28,.6 L0,.6"},"\u{1FBB4}":{type:4,data:{d:"M.15,.55 L.5,.4 L.5,.45 L.65,.45 L.65,.35 L.85,.35 L.85,.625 L.5,.625 L.5,.7 Z",type:0},scaleType:1},"\u{1FBB5}":[{type:5,data:{d:"M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z",type:0}},{type:5,data:{d:"M.15,.5 L.5,.35 L.5,.425 L.85,.425 L.85,.575 L.5,.575 L.5,.65 Z",type:0},scaleType:1}],"\u{1FBB6}":[{type:5,data:{d:"M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z",type:0}},{type:5,data:{d:"M.85,.5 L.5,.35 L.5,.425 L.15,.425 L.15,.575 L.5,.575 L.5,.65 Z",type:0},scaleType:1}],"\u{1FBB7}":[{type:5,data:{d:"M.875,0 L1,0 L1,1 L.875,1 Z",type:0}},{type:5,data:{d:"M.5,.675 L.2,.5 L.35,.5 L.35,.325 L.65,.325 L.65,.5 L.8,.5 Z",type:0},scaleType:1}],"\u{1FBB8}":[{type:5,data:{d:"M.875,0 L1,0 L1,1 L.875,1 Z",type:0}},{type:5,data:{d:"M.5,.325 L.2,.5 L.35,.5 L.35,.675 L.65,.675 L.65,.5 L.8,.5 Z",type:0},scaleType:1}],"\u{1FBB9}":{type:5,data:{d:"M1,.89 L.11,.89 L.11,.37 L.36,.12 L.74,.12 L.96,.34 L1,.34 L1,.405 L.92,.405 L.69,.185 L.41,.185 L.21,.42 L.21,.825 L1,.825 Z",type:0}},"\u{1FBBA}":{type:5,data:{d:"M0,.89 L0,.825 L.78,.825 L.78,.53 L.7,.415 L0,.415 L0,.35 L.75,.35 L.88,.48 L.88,.89 Z",type:0}},"\u{1FBBB}":{type:5,data:{d:"M.31,.275 L.44,.275 L.44,.47 L.05,.47 L.05,.405 L.31,.405 Z M.56,.275 L.69,.275 L.69,.405 L.95,.405 L.95,.47 L.56,.47 Z M.05,.53 L.44,.53 L.44,.725 L.31,.725 L.31,.595 L.05,.595 Z M.56,.53 L.95,.53 L.95,.595 L.69,.595 L.69,.725 L.56,.725 Z",type:0},scaleType:1},"\u{1FBBC}":[{type:3,data:"M0,0 L1,0 L1,1 L0,1 L0,.935 L.885,.935 L.885,.065 L0,.065 Z"},{type:3,data:"M.67,.5 A.17,.085,0,1,1,.33,.5 A.17,.085,0,1,1,.67,.5",scaleType:1}],"\u{1FBBD}":{type:4,data:{d:"M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1 Z",type:1}},"\u{1FBBE}":{type:4,data:{d:"M1,.5 L.5,1",type:1}},"\u{1FBBF}":{type:4,data:{d:"M.5,0 L1,.5 L.5,1 L0,.5 Z",type:1}},"\u{1FBC0}":{type:3,data:"M.16,.445 A.02,.01,0,0,1,.39,.33 L.5,.375 L.61,.33 A.02,.01,0,0,1,.84,.445 L.75,.5 L.84,.555 A.02,.01,0,0,1,.61,.67 L.5,.625 L.39,.67 A.02,.01,0,0,1,.16,.555 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z",scaleType:1},"\u{1FBC1}":{type:3,data:"M0.142308,0.924 Q0.130770,0.924,0.120193,0.922 T0.101924,0.916 T0.089424,0.9065 T0.084616,0.895 V0.025 Q0.084616,0.019,0.089424,0.0135 T0.101924,0.004 T0.120193,-0.002 T0.142308,-0.004 Q0.150000,-0.004,0.157693,-0.003 T0.173078,0.000 L0.430770,0.085 Q0.451924,0.059,0.494232,0.041 T0.587501,0.013 T0.692309,-0.0005 T0.800001,-0.004 H1 V0.055 H0.800001 Q0.771155,0.055,0.742309,0.056 T0.685578,0.060 T0.630770,0.0685 T0.580770,0.0825 T0.543270,0.1045 T0.528847,0.133 V0.647 H0.530770 Q0.596155,0.645,0.659616,0.638 T0.782693,0.6165 T0.893270,0.5805 T1,0.531 V0.623 Q0.934616,0.644,0.880770,0.6595 T0.769232,0.685 T0.650963,0.700 T0.528847,0.707 V0.787 Q0.528847,0.802,0.543270,0.8155 T0.580770,0.8375 T0.630770,0.8515 T0.685578,0.860 T0.742309,0.864 T0.800001,0.865 H1 V0.924 H0.800001 Q0.746155,0.924,0.692309,0.9205 T0.587501,0.907 T0.494232,0.879 T0.430770,0.835 L0.173078,0.920 Q0.165386,0.922,0.157693,0.923 T0.142308,0.924 Z M0.2,0.841 L0.415385,0.770 V0.150 L0.2,0.079 V0.841 Z M1,0.698 Q0.973077,0.694,0.969231,0.6885 T0.965385,0.677 Q0.965385,0.672,0.969231,0.6665 T1,0.657 V0.698 Z"},"\u{1FBC2}":{type:3,data:"M0,0.623 V0.531 Q0.044231,0.512,0.0625,0.4905 T0.092308,0.4465 T0.108654,0.4005 T0.113462,0.354 V0.351 Q0.113462,0.345,0.117308,0.3395 T0.129808,0.3305 T0.148846,0.3245 T0.171154,0.323 H0.501923 Q0.532692,0.323,0.563462,0.3185 T0.617308,0.303 T0.651923,0.2765 T0.663462,0.2435 V0.241 Q0.663462,0.219,0.657692,0.197 T0.639423,0.1545 T0.604808,0.115 T0.549615,0.082 T0.475577,0.0615 T0.392308,0.055 H0 V-0.004 H1 V0.055 H0.667308 Q0.694231,0.071,0.713462,0.09 T0.746154,0.129 T0.766346,0.1705 T0.775,0.213 H1 V0.316 Q0.971154,0.305,0.955769,0.2965 T0.920192,0.2825 T0.876923,0.2745 T0.830769,0.272 H0.773077 Q0.767308,0.297,0.743269,0.319 T0.680769,0.355 T0.595192,0.375 T0.501923,0.381 H0.227308 Q0.223462,0.415,0.211,0.4485 T0.172692,0.5135 T0.108269,0.573 T0,0.623 Z M0.611538,0.924 H0 V0.865 H0.611538 Q0.642308,0.865,0.673077,0.8605 T0.727885,0.845 T0.762308,0.8185 T0.773077,0.787 V0.785 Q0.773077,0.769,0.762308,0.7535 T0.727885,0.727 T0.673077,0.7115 T0.611538,0.707 H0.059615 Q0.048077,0.707,0.037115,0.7045 T0,0.694 V0.653 Q0.026923,0.648,0.037115,0.646 T0.059615,0.644 H0.722692 Q0.753462,0.644,0.784231,0.6395 T0.839038,0.624 T0.873846,0.5975 T0.884615,0.566 V0.564 Q0.884615,0.548,0.873846,0.532 T0.839038,0.5055 T0.784231,0.49 T0.722692,0.4855 H0.392308 Q0.380769,0.4855,0.370192,0.483 T0.351923,0.4765 T0.339423,0.467 T0.334615,0.4555 T0.339423,0.444 T0.351923,0.4345 T0.370192,0.428 T0.392308,0.4255 H0.832692 Q0.855769,0.4255,0.878846,0.4235 T0.922115,0.416 T0.957692,0.4015 T1,0.3815 V0.4685 Q0.975,0.4705,0.966346,0.4725 T0.95,0.4765 Q0.961538,0.4835,0.969231,0.4915 T1,0.5075 V0.6205 Q0.965385,0.6455,0.926923,0.665 T0.84,0.6935 Q0.866923,0.7115,0.877596,0.7345 T0.894231,0.7855 V0.787 Q0.894231,0.815,0.876923,0.8425 T0.820192,0.8895 T0.726923,0.916 T0.611538,0.924 Z"},"\u{1FBC3}":{type:3,data:"M0,0.473 V0.386 Q0.025490,0.378,0.028431,0.3695 T0.031373,0.352 V0.35 Q0.031373,0.342,0.028431,0.333 T0,0.316 V0.213 H0.652941 Q0.684314,0.213,0.715686,0.2085 T0.770588,0.193 T0.804902,0.1665 T0.815686,0.135 V0.133 Q0.815686,0.117,0.804902,0.101 T0.770588,0.0745 T0.715686,0.0595 T0.652941,0.055 H0 V-0.004 H0.652941 Q0.707843,-0.004,0.761765,0.004 T0.856863,0.031 T0.915686,0.0775 T0.933333,0.133 V0.135 Q0.933333,0.163,0.915686,0.1905 T0.856863,0.237 T0.761765,0.264 T0.652941,0.272 H0.109804 Q0.131373,0.29,0.140196,0.31 T0.149020,0.35 V0.352 Q0.149020,0.37,0.142157,0.388 T0.118627,0.4225 T0.076471,0.452 T0,0.473 Z M0,0.625 V0.513 Q0.029412,0.526,0.032353,0.54 T0.035294,0.568 V0.57 Q0.035294,0.584,0.032353,0.598 T0,0.625 Z"},"\u{1FBC4}":{type:3,data:"M0.019231,1.085 V-0.165 H0.980769 V1.085 H0.019231 Z M0.446154,0.527 H0.553846 Q0.553846,0.511,0.5625,0.495 T0.591346,0.466 T0.631731,0.4405 T0.666346,0.4135 T0.688462,0.3825 T0.696154,0.35 Q0.696154,0.33,0.682692,0.311 T0.641346,0.2785 T0.575,0.259 T0.498077,0.253 T0.421154,0.259 T0.356731,0.279 T0.315385,0.3125 T0.301923,0.352 V0.357 H0.409615 V0.354 Q0.409615,0.345,0.414423,0.335 T0.431731,0.318 T0.4625,0.3075 T0.498077,0.304 Q0.517308,0.304,0.534615,0.307 T0.564423,0.3165 T0.582692,0.332 T0.588462,0.35 Q0.588462,0.366,0.572115,0.3805 T0.535577,0.4075 T0.496154,0.4335 T0.465385,0.4625 T0.45,0.4945 T0.446154,0.527 Z M0.5,0.667 Q0.519231,0.667,0.5375,0.664 T0.569231,0.654 T0.588462,0.637 T0.594231,0.617 T0.588462,0.5975 T0.569231,0.581 T0.5375,0.571 T0.5,0.568 T0.4625,0.571 T0.430769,0.581 T0.411538,0.5975 T0.405769,0.617 T0.411538,0.637 T0.430769,0.654 T0.4625,0.664 T0.5,0.667 Z",scaleType:1},"\u{1FBC5}":{type:5,data:{d:"M.44,.425 L.44,.51 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.51 L.56,.425 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC6}":{type:5,data:{d:"M.44,.425 L.44,.46 L.23,.385 L.19,.43 L.44,.51 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.61,.735 L.7,.71 L.56,.56 L.56,.51 L.81,.43 L.77,.385 L.56,.46 L.56,.425 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC7}":{type:5,data:{d:"M.44,.425 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.425 Z M.18,.53 L.23,.575 L.81,.43 L.77,.385 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC8}":{type:5,data:{d:"M.44,.425 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.62,.735 L.71,.71 L.56,.56 L.56,.425 Z M.23,.385 L.18,.43 L.77,.575 L.81,.53 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC9}":{type:5,data:{d:"M.44,.425 L.45,.495 L.15,.645 L.34,.645 L.34,.7 L.44,.7 L.44,.645 L.56,.645 L.56,.7 L.66,.7 L.66,.645 L.84,.645 L.54,.495 L.56,.425 Z M.39,.6 L.5,.55 L.60,.6 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBCA}":{type:5,data:{d:"M.26,.375 L.5,.255 L.74,.375 L.74,.665 L.5,.55 L.26,.665 Z M.37,.395 L.37,.54 L.5,.475 L.63,.54 L.63,.395 L.5,.33 Z",type:0},scaleType:1},"\u{1FBCB}":{type:5,data:{d:"M.09,.41 L.32,.295 L.5,.375 L.68,.295 L.91,.41 L.75,.5 L.91,.59 L.68,.705 L.5,.625 L.32,.705 L.09,.59 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z",type:0},scaleType:1},"\u{1FBCC}":{type:5,data:{d:"M.55,.305 L.88,.305 L.88,.355 L.65,.355 L.65,.47 L.88,.47 L.88,.52 L.55,.52 Z",type:0},scaleType:1},"\u{1FBCD}":{type:5,data:{d:"M.38,.39 L.5,.33 L.62,.39 L.62,.53 L.5,.47 L.38,.53 Z",type:0},scaleType:1},"\u{1FBCE}":{type:3,data:"M0,0 L0.6667,0 L0.6667,1 L0,1 Z"},"\u{1FBCF}":{type:3,data:"M0,0 L0.3333,0 L0.3333,1 L0,1 Z"},"\u{1FBD0}":{type:2,data:"M1,.5 L0,1",strokeWidth:1},"\u{1FBD1}":{type:2,data:"M1,0 L0,.5",strokeWidth:1},"\u{1FBD2}":{type:2,data:"M0,0 L1,.5",strokeWidth:1},"\u{1FBD3}":{type:2,data:"M0,.5 L1,1",strokeWidth:1},"\u{1FBD4}":{type:2,data:"M0,0 L.5,1",strokeWidth:1},"\u{1FBD5}":{type:2,data:"M.5,0 L1,1",strokeWidth:1},"\u{1FBD6}":{type:2,data:"M1,0 L.5,1",strokeWidth:1},"\u{1FBD7}":{type:2,data:"M.5,0 L0,1",strokeWidth:1},"\u{1FBD8}":{type:2,data:"M0,0 L.5,.5 L1,0",strokeWidth:1},"\u{1FBD9}":{type:2,data:"M1,0 L.5,.5 L1,1",strokeWidth:1},"\u{1FBDA}":{type:2,data:"M0,1 L.5,.5 L1,1",strokeWidth:1},"\u{1FBDB}":{type:2,data:"M0,0 L.5,.5 L0,1",strokeWidth:1},"\u{1FBDC}":{type:2,data:"M0,0 L.5,1 L1,0",strokeWidth:1},"\u{1FBDD}":{type:2,data:"M1,0 L0,.5 L1,1",strokeWidth:1},"\u{1FBDE}":{type:2,data:"M0,1 L.5,0 L1,1",strokeWidth:1},"\u{1FBDF}":{type:2,data:"M0,0 L1,.5 L0,1",strokeWidth:1},"\u{1FBE0}":{type:5,data:{d:"M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0",type:1}},"\u{1FBE1}":{type:5,data:{d:"M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1",type:1}},"\u{1FBE2}":{type:5,data:{d:"M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1",type:1}},"\u{1FBE3}":{type:5,data:{d:"M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1",type:1}},"\u{1FBE4}":{type:0,data:[{x:2,y:0,w:4,h:4}]},"\u{1FBE5}":{type:0,data:[{x:2,y:4,w:4,h:4}]},"\u{1FBE6}":{type:0,data:[{x:0,y:2,w:4,h:4}]},"\u{1FBE7}":{type:0,data:[{x:4,y:2,w:4,h:4}]},"\u{1FBE8}":{type:5,data:{d:"M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0 Z",type:0}},"\u{1FBE9}":{type:5,data:{d:"M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1 Z",type:0}},"\u{1FBEA}":{type:5,data:{d:"M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1 Z",type:0}},"\u{1FBEB}":{type:5,data:{d:"M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1 Z",type:0}},"\u{1FBEC}":{type:5,data:{d:"M1,0 L.5,0 C.5,.276,.724,.5,1,.5 Z",type:0}},"\u{1FBED}":{type:5,data:{d:"M0,1 L.5,1 C.5,.724,.276,.5,0,.5 Z",type:0}},"\u{1FBEE}":{type:5,data:{d:"M1,1 L1,.5 C.724,.5,.5,.724,.5,1 Z",type:0}},"\u{1FBEF}":{type:5,data:{d:"M0,0 L0,.5 C.276,.5,.5,.276,.5,0 Z",type:0}},"\u{1FBF0}":{type:5,data:{d:o0(126),type:0},scaleType:1},"\u{1FBF1}":{type:5,data:{d:o0(48),type:0},scaleType:1},"\u{1FBF2}":{type:5,data:{d:o0(109),type:0},scaleType:1},"\u{1FBF3}":{type:5,data:{d:o0(121),type:0},scaleType:1},"\u{1FBF4}":{type:5,data:{d:o0(51),type:0},scaleType:1},"\u{1FBF5}":{type:5,data:{d:o0(91),type:0},scaleType:1},"\u{1FBF6}":{type:5,data:{d:o0(95),type:0},scaleType:1},"\u{1FBF7}":{type:5,data:{d:o0(114),type:0},scaleType:1},"\u{1FBF8}":{type:5,data:{d:o0(127),type:0},scaleType:1},"\u{1FBF9}":{type:5,data:{d:o0(123),type:0},scaleType:1},"\u{1FBFA}":{type:5,data:{d:"M.5,.175 C.2,.175,.15,.305,.15,.435 L.05,.63 L.35,.63 C.35,.682,.42,.76,.5,.76 C.58,.76,.65,.682,.65,.63 L.95,.63 L.85,.435 C.85,.305,.8,.175,.5,.175 Z",type:0},scaleType:1},...Object.fromEntries(Array.from({length:256},(i,e)=>[String.fromCodePoint(10240+e),{type:6,data:e}]))},$e=new Set([9617,9618,9619,129932,129933,129934,129935,129936,129937,129938,129940,129948,129949,129950,129951]);function f(i){return{type:2,data:()=>{let e=[],r=[.375,.25,.375],s=[0,3/8,5/8];for(let a=0;a<3;a++){let o=i>>a*2&1,n=i>>a*2+1&1;o&&n?e.push(`M0,${s[a]} L1,${s[a]} L1,${s[a]+r[a]} L0,${s[a]+r[a]} Z`):o?e.push(`M0,${s[a]} L${.5},${s[a]} L${.5},${s[a]+r[a]} L0,${s[a]+r[a]} Z`):n&&e.push(`M${.5},${s[a]} L1,${s[a]} L1,${s[a]+r[a]} L${.5},${s[a]+r[a]} Z`)}return e.join(" ")}}}function o0(i){let e=[];if(i&64){let Q=.21500000000000002,m=.95-.15-.015;e.push(`M${Q},${.2125} L${Q+.075},${.175} L${m-.075},${.175} L${m},${.2125} L${m-.075},${.25} L${Q+.075},${.25} Z`)}if(i&32){let c=.7999999999999999,u=.95-.15/2,C=.95,Q=.175+.075+.015,m=.5-.015;e.push(`M${u},${Q} L${C},${Q+.0375} L${C},${m-.0375} L${u},${m} L${c},${m-.0375} L${c},${Q+.0375} Z`)}if(i&16){let c=.7999999999999999,u=.95-.15/2,C=.95,Q=.5+.015,m=.825-.075-.015;e.push(`M${u},${Q} L${C},${Q+.0375} L${C},${m-.0375} L${u},${m} L${c},${m-.0375} L${c},${Q+.0375} Z`)}if(i&8){let Q=.21500000000000002,m=.95-.15-.015;e.push(`M${Q},${.7875} L${Q+.075},${.75} L${m-.075},${.75} L${m},${.7875} L${m-.075},${.825} L${Q+.075},${.825} Z`)}if(i&4&&e.push(`M${.125},${.515} L${.2},${.515+.0375} L${.2},${.735-.0375} L${.125},${.735} L${.05},${.735-.0375} L${.05},${.515+.0375} Z`),i&2&&e.push(`M${.125},${.265} L${.2},${.265+.0375} L${.2},${.485-.0375} L${.125},${.485} L${.05},${.485-.0375} L${.05},${.265+.0375} Z`),i&1){let Q=.21500000000000002,m=.95-.15-.015;e.push(`M${Q},${.5} L${Q+.075},${.4625} L${m-.075},${.4625} L${m},${.5} L${m-.075},${.5375} L${Q+.075},${.5375} Z`)}return e.join(" ")}var k=0,H=0,T0=!1,_0=!1,$0=!1,z,x0=0,G0=class{constructor(e,t,r,s,a,o){this._terminal=e;this._optionService=t;this._selectionRenderModel=r;this._decorationService=s;this._coreBrowserService=a;this._themeService=o;this.result={fg:0,bg:0,ext:0}}resolve(e,t,r,s,a){this.result.bg=e.bg,this.result.fg=e.fg,this.result.ext=e.bg&268435456?e.extended.ext:0,H=0,k=0,_0=!1,T0=!1,$0=!1,z=this._themeService.colors,x0=0;let o=e.getCode();if(o!==0&&e.extended.underlineStyle===4){let n=Math.max(1,Math.floor(this._optionService.rawOptions.fontSize*this._coreBrowserService.dpr/15));x0=t*s%(Math.round(n)*2)}if(x0===0&&$e.has(o)&&(x0=t*s%2*2+r*a%2),this._decorationService.forEachDecorationAtCell(t,r,"bottom",n=>{n.backgroundColorRGB&&(H=n.backgroundColorRGB.rgba>>8&16777215,_0=!0),n.foregroundColorRGB&&(k=n.foregroundColorRGB.rgba>>8&16777215,T0=!0)}),$0=this._selectionRenderModel.isCellSelected(this._terminal,t,r),$0){if(this.result.fg&67108864||(this.result.bg&50331648)!==0){if(this.result.fg&67108864)switch(this.result.fg&50331648){case 16777216:case 33554432:H=this._themeService.colors.ansi[this.result.fg&255].rgba;break;case 50331648:H=(this.result.fg&16777215)<<8|255;break;case 0:default:H=this._themeService.colors.foreground.rgba}else switch(this.result.bg&50331648){case 16777216:case 33554432:H=this._themeService.colors.ansi[this.result.bg&255].rgba;break;case 50331648:H=(this.result.bg&16777215)<<8|255;break}H=d0.blend(H,(this._coreBrowserService.isFocused?z.selectionBackgroundOpaque:z.selectionInactiveBackgroundOpaque).rgba&4294967040|128)>>8&16777215}else H=(this._coreBrowserService.isFocused?z.selectionBackgroundOpaque:z.selectionInactiveBackgroundOpaque).rgba>>8&16777215;if(_0=!0,z.selectionForeground&&(k=z.selectionForeground.rgba>>8&16777215,T0=!0),V0(e.getCode())){if(this.result.fg&67108864&&(this.result.bg&50331648)===0)k=(this._coreBrowserService.isFocused?z.selectionBackgroundOpaque:z.selectionInactiveBackgroundOpaque).rgba>>8&16777215;else{if(this.result.fg&67108864)switch(this.result.bg&50331648){case 16777216:case 33554432:k=this._themeService.colors.ansi[this.result.bg&255].rgba;break;case 50331648:k=(this.result.bg&16777215)<<8|255;break}else switch(this.result.fg&50331648){case 16777216:case 33554432:k=this._themeService.colors.ansi[this.result.fg&255].rgba;break;case 50331648:k=(this.result.fg&16777215)<<8|255;break;case 0:default:k=this._themeService.colors.foreground.rgba}k=d0.blend(k,(this._coreBrowserService.isFocused?z.selectionBackgroundOpaque:z.selectionInactiveBackgroundOpaque).rgba&4294967040|128)>>8&16777215}T0=!0}}this._decorationService.forEachDecorationAtCell(t,r,"top",n=>{n.backgroundColorRGB&&(H=n.backgroundColorRGB.rgba>>8&16777215,_0=!0),n.foregroundColorRGB&&(k=n.foregroundColorRGB.rgba>>8&16777215,T0=!0)}),_0&&($0?H=e.bg&-16777216&-134217729|H|50331648:H=e.bg&-16777216|H|50331648),T0&&(k=e.fg&-16777216&-67108865|k|50331648),this.result.fg&67108864&&(_0&&!T0&&((this.result.bg&50331648)===0?k=this.result.fg&-134217728|z.background.rgba>>8&16777215&16777215|50331648:k=this.result.fg&-134217728|this.result.bg&67108863,T0=!0),!_0&&T0&&((this.result.fg&50331648)===0?H=this.result.bg&-67108864|z.foreground.rgba>>8&16777215&16777215|50331648:H=this.result.bg&-67108864|this.result.fg&67108863,_0=!0)),z=void 0,this.result.bg=_0?H:this.result.bg,this.result.fg=T0?k:this.result.fg,this.result.ext&=536870911,this.result.ext|=x0<<29&3758096384}};var Ke=.5,K0=Be||we?"bottom":"ideographic";function qe(i,e,t,r,s,a,o,n,L,T,h,d,_=0){let p=Ve[e];if(p){let c=Array.isArray(p)?p:[p];for(let u of c)Pt(i,u,t,r,s,a,o,n,L,T,h,d,_);return!0}return!1}function Pt(i,e,t,r,s,a,o,n,L,T,h,d,_=0){let p=s,c=a,u=t,C=r;switch(e.scaleType===1&&(p=o,c=n,u=t+(s-o)/2,C=r+(a-n)/2),e.clipPath&&(i.save(),Vt(i,e.clipPath,u,C,p,c)),e.type){case 0:Bt(i,e.data,u,C,p,c);break;case 1:Ut(i,e.data,u,C,p,c,_);break;case 2:Wt(i,e.data,u,C,p,c,T,h,e.strokeWidth);break;case 3:Ht(i,e.data,u,C,p,c,T,e.strokeWidth);break;case 4:Zt(i,e.data,u,C,p,c,T,d);break;case 5:$t(i,e.data,u,C,p,c,L,T,h);break;case 6:wt(i,e.data,u,C,p,c);break}e.clipPath&&i.restore()}function Bt(i,e,t,r,s,a){for(let o=0;o{let F=parseFloat(m);return R%2===0?t+F*s:r+F*a});if(u==="M")i.moveTo(Q[0],Q[1]),T=Q[0],h=Q[1],d=T,_=h;else if(u==="L")i.lineTo(Q[0],Q[1]),T=Q[0],h=Q[1],d=T,_=h;else if(u==="Q")i.quadraticCurveTo(Q[0],Q[1],Q[2],Q[3]),d=Q[0],_=Q[1],T=Q[2],h=Q[3];else if(u==="T"){let m,R;p==="Q"||p==="T"?(m=2*T-d,R=2*h-_):(m=T,R=h),i.quadraticCurveTo(m,R,Q[0],Q[1]),d=m,_=R,T=Q[0],h=Q[1]}else u==="C"&&(i.bezierCurveTo(Q[0],Q[1],Q[2],Q[3],Q[4],Q[5]),d=Q[2],_=Q[3],T=Q[4],h=Q[5]);p=u}n!==void 0?(i.strokeStyle=i.fillStyle,i.lineWidth=o*n,i.stroke()):i.fill()}function kt(i,e,t,r,s,a,o,n,L,T){if(r===0||s===0){i.lineTo(L,T);return}r=Math.abs(r),s=Math.abs(s);let h=Math.cos(a),d=Math.sin(a),_=(e-L)/2,p=(t-T)/2,c=h*_+d*p,u=-d*_+h*p,C=r*r,Q=s*s,m=c*c,R=u*u,F=m/C+R/Q;if(F>1){let I=Math.sqrt(F);r*=I,s*=I,C=r*r,Q=s*s}let E=(C*Q-C*R-Q*m)/(C*R+Q*m);E<0&&(E=0);let y=(o===n?-1:1)*Math.sqrt(E),Y=y*(r*u/s),X=y*-(s*c/r),A0=h*Y-d*X+(e+L)/2,t0=d*Y+h*X+(t+T)/2,L0=(c-Y)/r,A=(u-X)/s,n0=(-c-Y)/r,a0=(-u-X)/s,$=Math.atan2(A,L0),q=Math.atan2(a0,n0)-$;n===0&&q>0?q-=2*Math.PI:n===1&&q<0&&(q+=2*Math.PI);let g0=$+q;i.ellipse(A0,t0,r,s,a,$,g0,n===0)}function Zt(i,e,t,r,s,a,o,n){i.save(),i.fillRect(t,r,s,a),n&&(i.fillStyle=n,i.strokeStyle=n),i.lineWidth=o,i.lineCap="square",i.beginPath();for(let L of e.d.split(" ")){let T=L[0],h=L.substring(1).split(",");if(!h[0]||!h[1]){T==="Z"&&i.closePath();continue}let d=h.map((_,p)=>{let c=parseFloat(_);return p%2===0?t+c*s:r+c*a});T==="M"?i.moveTo(d[0],d[1]):T==="L"&&i.lineTo(d[0],d[1])}e.type===1?i.stroke():i.fill(),i.restore()}var Xe=new Map;function Ut(i,e,t,r,s,a,o=0){let n=Xe.get(e);n||(n=new Map,Xe.set(e,n));let L=i.fillStyle;if(typeof L!="string")throw new Error(`Unexpected fillStyle type "${L}"`);let T=n.get(L);if(!T){let _=e[0].length,p=e.length,c=i.canvas.ownerDocument.createElement("canvas");c.width=_,c.height=p;let u=g(c.getContext("2d")),C=new ImageData(_,p),Q,m,R,F;if(L.startsWith("#"))Q=parseInt(L.slice(1,3),16),m=parseInt(L.slice(3,5),16),R=parseInt(L.slice(5,7),16),F=L.length>7&&parseInt(L.slice(7,9),16)||1;else if(L.startsWith("rgba"))[Q,m,R,F]=L.substring(5,L.length-1).split(",").map(E=>parseFloat(E));else throw new Error(`Unexpected fillStyle color format "${L}" when drawing pattern glyph`);for(let E=0;E>1&1,d=o&1;h!==0||d!==0?T.setTransform(new DOMMatrix().translateSelf(-h,-d)):T.setTransform(new DOMMatrix),i.fillStyle=T,i.fillRect(t,r,s,a)}function Wt(i,e,t,r,s,a,o,n,L){i.save(),i.beginPath(),i.rect(t,r,s,a),i.clip(),i.beginPath();let T;if(typeof e=="function"){let _=.15/a*s;T=e(.15,_)}else T=e;let h={currentX:0,currentY:0,lastControlX:0,lastControlY:0,lastCommand:""};for(let d of T.split(" ")){let _=d[0];if(_==="Z"){i.closePath(),h.lastCommand=_;continue}let p=je[_];if(!p){n.error(`Could not find drawing instructions for "${_}"`);continue}let c=d.substring(1).split(",");!c[0]||!c[1]||(p(i,Je(c,s,a,t,r,!0,o,0,0,!1),h),h.lastCommand=_)}L!==void 0?(i.strokeStyle=i.fillStyle,i.lineWidth=o*L,i.stroke()):i.fill(),i.closePath(),i.restore()}function Vt(i,e,t,r,s,a){i.beginPath();for(let o of e.split(" ")){let n=o[0];if(n==="Z"){i.closePath();continue}let L=o.substring(1).split(",");if(!L[0]||!L[1])continue;let T=t+parseFloat(L[0])*s,h=r+parseFloat(L[1])*a;n==="M"?i.moveTo(T,h):n==="L"&&i.lineTo(T,h)}i.clip()}function $t(i,e,t,r,s,a,o,n,L){let T=new Path2D;T.rect(t,r,s,a),i.clip(T),i.beginPath();let h=o/12;i.lineWidth=n*h;let d={currentX:0,currentY:0,lastControlX:0,lastControlY:0,lastCommand:""};for(let _ of e.d.split(" ")){let p=_[0];if(p==="Z"){i.closePath(),d.lastCommand=p;continue}let c=je[p];if(!c){L.error(`Could not find drawing instructions for "${p}"`);continue}let u=_.substring(1).split(",");!u[0]||!u[1]||(c(i,Je(u,s,a,t,r,!1,n,(e.leftPadding??0)*(h/2),(e.rightPadding??0)*(h/2)),d),d.lastCommand=p)}e.type===1?(i.strokeStyle=i.fillStyle,i.stroke()):i.fill(),i.closePath()}function Ye(i,e,t=0){return Math.max(Math.min(i,e),t)}var je={C:(i,e,t)=>{i.bezierCurveTo(e[0],e[1],e[2],e[3],e[4],e[5]),t.lastControlX=e[2],t.lastControlY=e[3],t.currentX=e[4],t.currentY=e[5]},L:(i,e,t)=>{i.lineTo(e[0],e[1]),t.lastControlX=t.currentX=e[0],t.lastControlY=t.currentY=e[1]},M:(i,e,t)=>{i.moveTo(e[0],e[1]),t.lastControlX=t.currentX=e[0],t.lastControlY=t.currentY=e[1]},Q:(i,e,t)=>{i.quadraticCurveTo(e[0],e[1],e[2],e[3]),t.lastControlX=e[0],t.lastControlY=e[1],t.currentX=e[2],t.currentY=e[3]},T:(i,e,t)=>{let r,s;t.lastCommand==="Q"||t.lastCommand==="T"?(r=2*t.currentX-t.lastControlX,s=2*t.currentY-t.lastControlY):(r=t.currentX,s=t.currentY),i.quadraticCurveTo(r,s,e[0],e[1]),t.lastControlX=r,t.lastControlY=s,t.currentX=e[0],t.currentY=e[1]}};function Je(i,e,t,r,s,a,o,n=0,L=0,T=!0){let h=i.map(d=>parseFloat(d)||parseInt(d));if(h.length<2)throw new Error("Too few arguments for instruction");for(let d=0;da){s-t<-20&&this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(s-t))}ms`),this._start();return}s=a}this.clear()}},Ce=class extends X0{_requestCallback(e){return setTimeout(()=>e(this._createDeadline(16)))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){let t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}},Qe=class extends X0{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}},et="requestIdleCallback"in globalThis?Qe:Ce;var s0=class i{constructor(){this.fg=0;this.bg=0;this.extended=new P0}static toColorRGB(e){return[e>>>16&255,e>>>8&255,e&255]}static fromColorRGB(e){return(e[0]&255)<<16|(e[1]&255)<<8|e[2]&255}clone(){let e=new i;return e.fg=this.fg,e.bg=this.bg,e.extended=this.extended.clone(),e}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)===50331648}isBgRGB(){return(this.bg&50331648)===50331648}isFgPalette(){return(this.fg&50331648)===16777216||(this.fg&50331648)===33554432}isBgPalette(){return(this.bg&50331648)===16777216||(this.bg&50331648)===33554432}isFgDefault(){return(this.fg&50331648)===0}isBgDefault(){return(this.bg&50331648)===0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===16777216||(this.extended.underlineColor&50331648)===33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===0:this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},P0=class i{constructor(e=0,t=0){this._ext=0;this._urlId=0;this._ext=e,this._urlId=t}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(e){this._ext=e}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(e){this._ext&=-469762049,this._ext|=e<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(e){this._ext&=-67108864,this._ext|=e&67108863}get urlId(){return this._urlId}set urlId(e){this._urlId=e}get underlineVariantOffset(){let e=(this._ext&3758096384)>>29;return e<0?e^4294967288:e}set underlineVariantOffset(e){this._ext&=536870911,this._ext|=e<<29&3758096384}clone(){return new i(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}};var U=class{constructor(){this._listeners=[];this._disposed=!1}get event(){return this._event?this._event:(this._event=(e,t,r)=>{if(this._disposed)return M(()=>{});let s={fn:e,thisArgs:t};this._listeners.push(s);let a=M(()=>{let o=this._listeners.indexOf(s);o!==-1&&this._listeners.splice(o,1)});return r&&(Array.isArray(r)?r.push(a):r.add(a)),a},this._event)}fire(e){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{let{fn:t,thisArgs:r}=this._listeners[0];t.call(r,e);return}default:{let t=this._listeners.slice();for(let{fn:r,thisArgs:s}of t)r.call(s,e)}}}dispose(){this._disposed||(this._disposed=!0,this._listeners.length=0)}},c0;(s=>{function i(a,o){return a(n=>o.fire(n))}s.forward=i;function e(a,o){return(n,L,T)=>a(h=>n.call(L,o(h)),void 0,T)}s.map=e;function t(...a){return(o,n,L)=>{let T=new R0;for(let h of a)T.add(h(d=>o.call(n,d)));return L&&(Array.isArray(L)?L.push(T):L.add(T)),T}}s.any=t;function r(a,o,n){return o(n),a(L=>o(L))}s.runAndSubscribe=r})(c0||={});var tt={texturePage:0,texturePosition:{x:0,y:0},texturePositionClipSpace:{x:0,y:0},offset:{x:0,y:0},size:{x:0,y:0},sizeClipSpace:{x:0,y:0}},B0=2;var w0,J=class i{constructor(e,t,r,s){this._document=e;this._config=t;this._unicodeService=r;this._logService=s;this._didWarmUp=!1;this._cacheMap=new D0;this._cacheMapCombined=new D0;this._pages=[];this._activePages=[];this._workBoundingBox={top:0,left:0,bottom:0,right:0};this._workAttributeData=new s0;this._textureSize=512;this._onAddTextureAtlasCanvas=new U;this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event;this._onRemoveTextureAtlasCanvas=new U;this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event;this._requestClearModel=!1;this._clearModelGeneration=0;this._createNewPage(),this._tmpCanvas=it(e,this._config.deviceCellWidth*4+B0*2,this._config.deviceCellHeight+B0*2),this._tmpCtx=g(this._tmpCanvas.getContext("2d",{alpha:this._config.allowTransparency,willReadFrequently:!0}))}get pages(){return this._pages}dispose(){this._tmpCanvas.remove();for(let e of this.pages)e.canvas.remove();this._onAddTextureAtlasCanvas.dispose(),this._onRemoveTextureAtlasCanvas.dispose()}warmUp(){this._didWarmUp||(this._doWarmUp(),this._didWarmUp=!0)}_doWarmUp(){let e=new et(this._logService);for(let t=33;t<126;t++)e.enqueue(()=>{if(!this._cacheMap.get(t,0,0,0)){let r=this._drawToCache(t,0,0,0,!1,void 0);this._cacheMap.set(t,0,0,0,r)}})}beginFrame(){let e=this._requestClearModel;return this._requestClearModel=!1,e}clearTexture(){if(this._pages.some(e=>e._glyphs.length>0||e.currentRow.x!==0||e.currentRow.y!==0)){this._evictAllPages();this._createNewPage()}}_evictAllPages(){for(let e of this._pages)this._onRemoveTextureAtlasCanvas.fire(e.canvas),e.canvas.remove();this._pages.length=0,this._activePages.length=0,this._overflowSizePage=void 0,this._cacheMap.clear(),this._cacheMapCombined.clear(),this._didWarmUp=!1,this._requestClearModel=!0,this._clearModelGeneration++}_createNewPage(){if(i.maxAtlasPages&&this._pages.length>=i.maxAtlasPages){let t=this._pages.filter(T=>T.canvas.width*2<=(i.maxTextureSize||4096)).sort((T,h)=>h.canvas.width!==T.canvas.width?h.canvas.width-T.canvas.width:h.percentageUsed-T.percentageUsed),r=-1,s=0;for(let T=0;TT.canvas.width!==a[0].canvas.width))this._evictAllPages();else{let o=a.map(T=>T.glyphs[0].texturePage).sort((T,h)=>T-h),n=this.pages.length-a.length,L=this._mergePages(a,n);L.version=++u0.nextVersion;for(let T=o.length-1;T>=0;T--)this._deletePage(o[T]);this.pages.push(L),this._requestClearModel=!0,this._clearModelGeneration++,this._onAddTextureAtlasCanvas.fire(L.canvas)}}let e=new u0(this._document,this._textureSize);return this._pages.push(e),this._activePages.push(e),this._onAddTextureAtlasCanvas.fire(e.canvas),e}_mergePages(e,t){let r=e[0].canvas.width*2,s=new u0(this._document,r,e);for(let[a,o]of e.entries()){let n=a*o.canvas.width%r,L=Math.floor(a/2)*o.canvas.height;s.ctx.drawImage(o.canvas,n,L);for(let h of o.glyphs)h.texturePage=t,h.sizeClipSpace.x=h.size.x/r,h.sizeClipSpace.y=h.size.y/r,h.texturePosition.x+=n,h.texturePosition.y+=L,h.texturePositionClipSpace.x=h.texturePosition.x/r,h.texturePositionClipSpace.y=h.texturePosition.y/r;this._onRemoveTextureAtlasCanvas.fire(o.canvas);let T=this._activePages.indexOf(o);T!==-1&&this._activePages.splice(T,1)}return s}_deletePage(e){this._pages.splice(e,1);for(let t=e;t=this._config.colors.ansi.length)throw new Error("No color found for idx "+e);return this._config.colors.ansi[e]}_getBackgroundColor(e,t,r,s){if(this._config.allowTransparency)return K;let a;switch(e){case 16777216:case 33554432:a=this._getColorFromAnsiIndex(t);break;case 50331648:let o=s0.toColorRGB(t);a=Z.toColor(o[0],o[1],o[2]);break;case 0:default:r?a=I0.opaque(this._config.colors.foreground):a=this._config.colors.background;break}return this._config.allowTransparency||(a=I0.opaque(a)),a}_getForegroundColor(e,t,r,s,a,o,n,L,T,h){let d=this._getMinimumContrastColor(e,t,r,s,a,o,n,T,L,h);if(d)return d;let _;switch(a){case 16777216:case 33554432:this._config.drawBoldTextInBrightColors&&T&&o<8&&(o+=8),_=this._getColorFromAnsiIndex(o);break;case 50331648:let p=s0.toColorRGB(o);_=Z.toColor(p[0],p[1],p[2]);break;case 0:default:n?_=this._config.colors.background:_=this._config.colors.foreground}return this._config.allowTransparency&&(_=I0.opaque(_)),L&&(_=I0.multiplyOpacity(_,Ke)),_}_resolveBackgroundRgba(e,t,r){switch(e){case 16777216:case 33554432:return this._getColorFromAnsiIndex(t).rgba;case 50331648:return t<<8;case 0:default:return r?this._config.colors.foreground.rgba:this._config.colors.background.rgba}}_resolveForegroundRgba(e,t,r,s){switch(e){case 16777216:case 33554432:return this._config.drawBoldTextInBrightColors&&s&&t<8&&(t+=8),this._getColorFromAnsiIndex(t).rgba;case 50331648:return t<<8;case 0:default:return r?this._config.colors.background.rgba:this._config.colors.foreground.rgba}}_getMinimumContrastColor(e,t,r,s,a,o,n,L,T,h){if(this._config.minimumContrastRatio===1||h)return;let d=this._getContrastCache(T),_=d.getColor(e,s);if(_!==void 0)return _??void 0;let p=this._resolveBackgroundRgba(t,r,n),c=this._resolveForegroundRgba(a,o,n,L),u=d0.ensureContrastRatio(p,c,this._config.minimumContrastRatio/(T?2:1));if(!u){d.setColor(e,s,null);return}let C=Z.toColor(u>>24&255,u>>16&255,u>>8&255);return d.setColor(e,s,C),C}_getContrastCache(e){return e?this._config.colors.halfContrastCache:this._config.colors.contrastCache}_drawToCache(e,t,r,s,a,o){let n=typeof e=="number"?String.fromCharCode(e):e;o&&this._tmpCanvas.parentElement!==o&&(this._tmpCanvas.style.display="none",o.append(this._tmpCanvas));let L=Math.min(this._config.deviceCellWidth*Math.max(n.length,2)+B0*2,this._config.deviceMaxTextureSize);this._tmpCanvas.width=b?b*2-f0:b-f0;!(f0>=b)===!1||Z0===0?(this._tmpCtx.setLineDash([Math.round(b),Math.round(b)]),this._tmpCtx.moveTo(j+Z0,G),this._tmpCtx.lineTo(Q0,G)):(this._tmpCtx.setLineDash([Math.round(b),Math.round(b)]),this._tmpCtx.moveTo(j,G),this._tmpCtx.lineTo(j+Z0,G),this._tmpCtx.moveTo(j+Z0+b,G),this._tmpCtx.lineTo(Q0,G)),f0=We(Q0-j,b,f0);break;case 5:let vt=.6,Ot=.3,de=Q0-j,Se=Math.floor(vt*de),Re=Math.floor(Ot*de),It=de-Se-Re;this._tmpCtx.setLineDash([Se,Re,It]),this._tmpCtx.moveTo(j,G),this._tmpCtx.lineTo(Q0,G);break;case 1:default:this._tmpCtx.moveTo(j,G),this._tmpCtx.lineTo(Q0,G);break}y0||this._tmpCtx.stroke(),this._tmpCtx.restore()}if(this._tmpCtx.restore(),!n0&&this._config.fontSize>=12&&!this._config.allowTransparency&&n!==" "){this._tmpCtx.save(),this._tmpCtx.textBaseline="alphabetic";let r0=this._tmpCtx.measureText(n);if(this._tmpCtx.restore(),"actualBoundingBoxDescent"in r0&&r0.actualBoundingBoxDescent>0){this._tmpCtx.save();let y0=new Path2D;y0.rect(i0,C0-Math.ceil(b/2),this._config.deviceCellWidth*$,v0-C0+Math.ceil(b/2)),this._tmpCtx.clip(y0),this._tmpCtx.lineWidth=this._config.devicePixelRatio*3,this._tmpCtx.strokeStyle=y.css,this._tmpCtx.strokeText(n,A,A+this._config.deviceCharHeight),this._tmpCtx.restore()}}}if(Q){let b=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/15)),D=b%2===1?.5:0;this._tmpCtx.lineWidth=b,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(A,A+D),this._tmpCtx.lineTo(A+this._config.deviceCharWidth*$,A+D),this._tmpCtx.stroke()}if(n0||this._tmpCtx.fillText(n,A,A+this._config.deviceCharHeight),n==="_"&&!this._config.allowTransparency){let b=fe(this._tmpCtx.getImageData(A,A,this._config.deviceCellWidth,this._config.deviceCellHeight),y,L0,a0);if(b)for(let D=1;D<=5&&(this._tmpCtx.save(),this._tmpCtx.fillStyle=y.css,this._tmpCtx.fillRect(0,0,this._tmpCanvas.width,this._tmpCanvas.height),this._tmpCtx.restore(),this._tmpCtx.fillText(n,A,A+this._config.deviceCharHeight-D),b=fe(this._tmpCtx.getImageData(A,A,this._config.deviceCellWidth,this._config.deviceCellHeight),y,L0,a0),!!b);D++);}if(C){let b=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/10)),D=this._tmpCtx.lineWidth%2===1?.5:0;this._tmpCtx.lineWidth=b,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(A,A+Math.floor(this._config.deviceCharHeight/2)-D),this._tmpCtx.lineTo(A+this._config.deviceCharWidth*$,A+Math.floor(this._config.deviceCharHeight/2)-D),this._tmpCtx.stroke()}this._tmpCtx.restore();let q=this._tmpCtx.getImageData(0,0,this._tmpCanvas.width,this._tmpCanvas.height),g0;if(this._config.allowTransparency?g0=Gt(q):g0=fe(q,y,L0,a0),g0)return tt;let I=this._findGlyphBoundingBox(q,this._workBoundingBox,L,t0,n0,A),O,v;for(;;){if(this._activePages.length===0){let b=this._createNewPage();O=b,v=b.currentRow,v.height=I.size.y;break}O=this._activePages[this._activePages.length-1],v=O.currentRow;for(let b of this._activePages)I.size.y<=b.currentRow.height&&(O=b,v=b.currentRow);for(let b=this._activePages.length-1;b>=0;b--)for(let D of this._activePages[b].fixedRows)D.height<=v.height&&I.size.y<=D.height&&(O=this._activePages[b],v=D);if(I.size.x>this._textureSize){this._overflowSizePage||(i.maxAtlasPages&&this._pages.length>=i.maxAtlasPages&&this._evictAllPages(),this._overflowSizePage=new u0(this._document,this._config.deviceMaxTextureSize),this.pages.push(this._overflowSizePage),this._requestClearModel=!0,this._clearModelGeneration++,this._onAddTextureAtlasCanvas.fire(this._overflowSizePage.canvas)),O=this._overflowSizePage,v=this._overflowSizePage.currentRow,v.x+I.size.x>=O.canvas.width&&(v.x=0,v.y+=v.height,v.height=0);break}if(v.y+I.size.y>=O.canvas.height||v.height>I.size.y+2){let b=!1;if(O.currentRow.y+O.currentRow.height+I.size.y>=O.canvas.height){let D;for(let i0 of this._activePages)if(i0.currentRow.y+i0.currentRow.height+I.size.y=i.maxAtlasPages&&v.y+I.size.y<=O.canvas.height&&v.height>=I.size.y&&v.x+I.size.x<=O.canvas.width)b=!0;else{let i0=this._createNewPage();O=i0,v=i0.currentRow,v.height=I.size.y,b=!0}}b||(O.currentRow.height>0&&O.fixedRows.push(O.currentRow),v={x:0,y:O.currentRow.y+O.currentRow.height,height:I.size.y},O.fixedRows.push(v),O.currentRow={x:0,y:v.y+v.height,height:0})}if(v.x+I.size.x<=O.canvas.width)break;v===O.currentRow?(v.x=0,v.y+=v.height,v.height=0):O.fixedRows.splice(O.fixedRows.indexOf(v),1)}return I.texturePage=this._pages.indexOf(O),I.texturePosition.x=v.x,I.texturePosition.y=v.y,I.texturePositionClipSpace.x=v.x/O.canvas.width,I.texturePositionClipSpace.y=v.y/O.canvas.height,I.sizeClipSpace.x/=O.canvas.width,I.sizeClipSpace.y/=O.canvas.height,v.height=Math.max(v.height,I.size.y),v.x+=I.size.x,O.ctx.putImageData(q,I.texturePosition.x-this._workBoundingBox.left,I.texturePosition.y-this._workBoundingBox.top,this._workBoundingBox.left,this._workBoundingBox.top,I.size.x,I.size.y),O.addGlyph(I),O.version=++u0.nextVersion,I}_findGlyphBoundingBox(e,t,r,s,a,o){t.top=0;let n=s?this._config.deviceCellHeight:this._tmpCanvas.height,L=s?this._config.deviceCellWidth:r,T=!1;for(let h=0;h=o;h--){for(let d=0;d=0;h--){for(let d=0;d>>24,a=e.rgba>>>16&255,o=e.rgba>>>8&255,n=t.rgba>>>24,L=t.rgba>>>16&255,T=t.rgba>>>8&255,h=Math.floor((Math.abs(s-n)+Math.abs(a-L)+Math.abs(o-T))/12),d=!0;for(let _=0;_0)return!1;return!0}function it(i,e,t){let r=i.createElement("canvas");return r.width=e,r.height=t,r}function rt(i,e,t,r,s,a,o,n,L=!0){let T={foreground:a.foreground,background:a.background,cursor:K,cursorAccent:K,selectionForeground:K,selectionBackgroundTransparent:K,selectionBackgroundOpaque:K,selectionInactiveBackgroundTransparent:K,selectionInactiveBackgroundOpaque:K,overviewRulerBorder:K,scrollbarSliderBackground:K,scrollbarSliderHoverBackground:K,scrollbarSliderActiveBackground:K,ansi:a.ansi.slice(),contrastCache:a.contrastCache,halfContrastCache:a.halfContrastCache};return{customGlyphs:L,devicePixelRatio:o,deviceMaxTextureSize:n,letterSpacing:s.letterSpacing,lineHeight:s.lineHeight,deviceCellWidth:i,deviceCellHeight:e,deviceCharWidth:t,deviceCharHeight:r,fontFamily:s.fontFamily,fontSize:s.fontSize,fontWeight:s.fontWeight,fontWeightBold:s.fontWeightBold,allowTransparency:s.allowTransparency,drawBoldTextInBrightColors:s.drawBoldTextInBrightColors,minimumContrastRatio:s.minimumContrastRatio,colors:T}}function Ee(i,e){for(let t=0;t=0){if(Ee(u.config,h))return u.atlas;u.ownedBy.length===1?(u.atlas.dispose(),e0.splice(c,1)):u.ownedBy.splice(C,1);break}}for(let c=0;c{this._renderCallback(),this._animationFrame=void 0})))}_restartInterval(e=600){this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout=this._coreBrowserService.window.setTimeout(()=>{if(this._animationTimeRestarted){let t=600-(Date.now()-this._animationTimeRestarted);if(this._animationTimeRestarted=void 0,t>0){this._restartInterval(t);return}}this.isCursorVisible=!1,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._renderCallback(),this._animationFrame=void 0}),this._blinkInterval=this._coreBrowserService.window.setInterval(()=>{if(this._animationTimeRestarted){let t=600-(Date.now()-this._animationTimeRestarted);this._animationTimeRestarted=void 0,this._restartInterval(t);return}this.isCursorVisible=!this.isCursorVisible,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._renderCallback(),this._animationFrame=void 0})},600)},e)}pause(){this.isCursorVisible=!0,this._isIdlePaused=!1,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0),this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}resume(){this.pause(),this._animationTimeRestarted=void 0,this._restartInterval(),this._resetIdleTimer(),this.restartBlinkAnimation()}_resetIdleTimer(){this._isIdlePaused=!1,this._idleTimeout&&this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_stopBlinkingDueToIdle(){this.isCursorVisible=!0,this._isIdlePaused=!0,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0),this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0,this._renderCallback()}};function ve(i,e,t){let r=new e.ResizeObserver(s=>{let a=s.find(L=>L.target===i);if(!a)return;if(!("devicePixelContentBoxSize"in a)){r?.disconnect(),r=void 0;return}let o=a.devicePixelContentBoxSize[0].inlineSize,n=a.devicePixelContentBoxSize[0].blockSize;o>0&&n>0&&t(o,n)});try{r.observe(i,{box:["device-pixel-content-box"]})}catch{r.disconnect(),r=void 0}return M(()=>r?.disconnect())}function nt(i){return i>65535?(i-=65536,String.fromCharCode((i>>10)+55296)+String.fromCharCode(i%1024+56320)):String.fromCharCode(i)}var j0=class i extends s0{constructor(){super(...arguments);this.content=0;this.fg=0;this.bg=0;this.extended=new P0;this.combinedData=""}static fromCharData(t){let r=new i;return r.setFromCharData(t),r}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?nt(this.content&2097151):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(t){this.fg=t[0],this.bg=0;let r=!1;if(t[1].length>2)r=!0;else if(t[1].length===2){let s=t[1].charCodeAt(0);if(55296<=s&&s<=56319){let a=t[1].charCodeAt(1);56320<=a&&a<=57343?this.content=(s-55296)*1024+a-56320+65536|t[2]<<22:r=!0}else r=!0}else this.content=t[1].charCodeAt(0)|t[2]<<22;r&&(this.combinedData=t[1],this.content=2097152|t[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}attributesEquals(t){if(this.getFgColorMode()!==t.getFgColorMode()||this.getFgColor()!==t.getFgColor()||this.getBgColorMode()!==t.getBgColorMode()||this.getBgColor()!==t.getBgColor()||this.isInverse()!==t.isInverse()||this.isBold()!==t.isBold()||this.isUnderline()!==t.isUnderline())return!1;if(this.isUnderline()){if(this.getUnderlineStyle()!==t.getUnderlineStyle())return!1;let r=this.isUnderlineColorDefault(),s=t.isUnderlineColorDefault();if(!(r&&s)&&(r!==s||this.getUnderlineColor()!==t.getUnderlineColor()||this.getUnderlineColorMode()!==t.getUnderlineColorMode()))return!1}return!(this.isOverline()!==t.isOverline()||this.isBlink()!==t.isBlink()||this.isInvisible()!==t.isInvisible()||this.isItalic()!==t.isItalic()||this.isDim()!==t.isDim()||this.isStrikethrough()!==t.isStrikethrough())}};var J0=class extends W{constructor(t,r,s){super();this._renderCallback=t;this._coreBrowserService=r;this._optionsService=s;this._intervalDuration=0;this._blinkOn=!0;this._needsBlinkInViewport=!1;this._isViewportVisible=!0;this._register(this._optionsService.onSpecificOptionChange("blinkIntervalDuration",a=>{this.setIntervalDuration(a)})),this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration),this._register(M(()=>this._clearInterval()))}get isBlinkOn(){return this._blinkOn}get isEnabled(){return this._intervalDuration>0}setNeedsBlinkInViewport(t){this._needsBlinkInViewport!==t&&(this._needsBlinkInViewport=t,this._updateIntervalState())}setViewportVisible(t){this._isViewportVisible!==t&&(this._isViewportVisible=t,this._updateIntervalState())}setIntervalDuration(t){t!==this._intervalDuration&&(this._intervalDuration=t,this._clearInterval(),this._updateIntervalState())}_updateIntervalState(){if(this._intervalDuration>0&&this._needsBlinkInViewport&&this._isViewportVisible){if(this._interval!==void 0)return;let r=this._blinkOn;this._blinkOn=!0,this._interval=this._coreBrowserService.window.setInterval(()=>{this._blinkOn=!this._blinkOn,this._renderCallback()},this._intervalDuration),r||this._renderCallback();return}this._clearInterval(),this._blinkOn||(this._blinkOn=!0,this._renderCallback())}_clearInterval(){this._interval!==void 0&&(this._coreBrowserService.window.clearInterval(this._interval),this._interval=void 0)}};var te=new Float32Array([2,0,0,0,0,-2,0,0,0,0,1,0,-1,1,0,1]);function ie(i,e,t,r){let s=g(i.createProgram());if(i.attachShader(s,g(at(i,i.VERTEX_SHADER,e,r))),i.attachShader(s,g(at(i,i.FRAGMENT_SHADER,t,r))),i.linkProgram(s),i.getProgramParameter(s,i.LINK_STATUS))return s;r.error(i.getProgramInfoLog(s)),i.deleteProgram(s)}function at(i,e,t,r){let s=g(i.createShader(e));if(i.shaderSource(s,t),i.compileShader(s),i.getShaderParameter(s,i.COMPILE_STATUS))return s;r.error(i.getShaderInfoLog(s)),i.deleteShader(s)}function ot(i,e){let t=Math.min(i.length*2,e),r=new Float32Array(t);for(let s=0;sFt(i5))}var R0=class{constructor(){this._disposables=new Set;this._isDisposed=false}get isDisposed(){return this._isDisposed}add(e){return this._isDisposed?e.dispose():this._disposables.add(e),e}dispose(){if(!this._isDisposed){this._isDisposed=true;for(let e of this._disposables)e.dispose();this._disposables.clear()}}clear(){for(let e of this._disposables)e.dispose();this._disposables.clear()}},W=class{constructor(){this._store=new R0}dispose(){this._store.dispose()}_register(e){return this._store.add(e)}};W.None=Object.freeze({dispose(){}});var m0=class{constructor(){this._isDisposed=false}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),this._value=e)}clear(){this.value=void 0}dispose(){;this._isDisposed=true,this._value?.dispose(),this._value=void 0}};var Pe=!!(typeof process<"u"&&"title"in process&&(typeof navigator>"u"||navigator.userAgent.startsWith("Node.js/"))),O0=Pe?"node":navigator.userAgent,ce=Pe?"node":navigator.platform,Be=O0.includes("Firefox"),n1=O0.includes("Chrome"),we=O0.includes("Edge"),ue=/^((?!chrome|android).)*safari/i.test(O0);function He(){if(!ue)return 0;let i5=O0.match(/Version\/(\d+)/);return i5===null||i5.length<2?0:parseInt(i5[1],10)}var a1=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(ce),o1=["Windows","Win16","Win32","WinCE"].includes(ce),T1=ce.indexOf("Linux")>=0,l1=/\bCrOS\b/.test(O0);var P=0,B=0,w=0,x=0,K={css:"#00000000",rgba:0},Z;(r=>{function i5(s,a,o,n){return n!==void 0?`#${E0(s)}${E0(a)}${E0(o)}${E0(n)}`:`#${E0(s)}${E0(a)}${E0(o)}`}r.toCss=i5;function e(s,a,o,n=255){return(s<<24|a<<16|o<<8|n)>>>0}r.toRgba=e;function t(s,a,o,n){return{css:r.toCss(s,a,o,n),rgba:r.toRgba(s,a,o,n)}}r.toColor=t})(Z||={});var I0;(n=>{function i5(L,T){if(x=(T.rgba&255)/255,x===1)return{css:T.css,rgba:T.rgba};let h=T.rgba>>24&255,d=T.rgba>>16&255,_=T.rgba>>8&255,p=L.rgba>>24&255,c=L.rgba>>16&255,u=L.rgba>>8&255;P=p+Math.round((h-p)*x),B=c+Math.round((d-c)*x),w=u+Math.round((_-u)*x);let C=Z.toCss(P,B,w),Q=Z.toRgba(P,B,w);return{css:C,rgba:Q}}n.blend=i5;function e(L){return(L.rgba&255)===255}n.isOpaque=e;function t(L,T,h){let d=d0.ensureContrastRatio(L.rgba,T.rgba,h);if(d)return Z.toColor(d>>24&255,d>>16&255,d>>8&255)}n.ensureContrastRatio=t;function r(L){let T=(L.rgba|255)>>>0;return[P,B,w]=d0.toChannels(T),{css:Z.toCss(P,B,w),rgba:T}}n.opaque=r;function s(L,T){return x=Math.round(T*255),[P,B,w]=d0.toChannels(L.rgba),{css:Z.toCss(P,B,w,x),rgba:Z.toRgba(P,B,w,x)}}n.opacity=s;function a(L,T){return x=L.rgba&255,s(L,x*T/255)}n.multiplyOpacity=a;function o(L){return[L.rgba>>24&255,L.rgba>>16&255,L.rgba>>8&255]}n.toColorRGB=o})(I0||={});var Mt;(r=>{let i5,e;try{let s=document.createElement("canvas");s.width=1,s.height=1;let a=s.getContext("2d",{willReadFrequently:true});a&&(i5=a,i5.globalCompositeOperation="copy",e=i5.createLinearGradient(0,0,1,1))}catch{}function t(s){if(s.match(/#[\da-f]{3,8}/i))switch(s.length){case 4:return P=parseInt(s.slice(1,2).repeat(2),16),B=parseInt(s.slice(2,3).repeat(2),16),w=parseInt(s.slice(3,4).repeat(2),16),Z.toColor(P,B,w);case 5:return P=parseInt(s.slice(1,2).repeat(2),16),B=parseInt(s.slice(2,3).repeat(2),16),w=parseInt(s.slice(3,4).repeat(2),16),x=parseInt(s.slice(4,5).repeat(2),16),Z.toColor(P,B,w,x);case 7:return{css:s,rgba:(parseInt(s.slice(1),16)<<8|255)>>>0};case 9:return{css:s,rgba:parseInt(s.slice(1),16)>>>0}}let a=s.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(a)return P=parseInt(a[1],10),B=parseInt(a[2],10),w=parseInt(a[3],10),x=Math.round((a[5]===void 0?1:parseFloat(a[5]))*255),Z.toColor(P,B,w,x);if(s==="transparent")return{css:"transparent",rgba:0};if(!i5||!e)throw new Error("css.toColor: Unsupported css format");if(i5.fillStyle=e,i5.fillStyle=s,typeof i5.fillStyle!="string")throw new Error("css.toColor: Unsupported css format");if(i5.fillRect(0,0,1,1),[P,B,w,x]=i5.getImageData(0,0,1,1).data,x!==255)throw new Error("css.toColor: Unsupported css format");return{rgba:Z.toRgba(P,B,w,x),css:s}}r.toColor=t})(Mt||={});var V;(t=>{function i5(r){return e(r>>16&255,r>>8&255,r&255)}t.relativeLuminance=i5;function e(r,s,a){let o=r/255,n=s/255,L=a/255,T=o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4),h=n<=.03928?n/12.92:Math.pow((n+.055)/1.055,2.4),d=L<=.03928?L/12.92:Math.pow((L+.055)/1.055,2.4);return T*.2126+h*.7152+d*.0722}t.relativeLuminance2=e})(V||={});var d0;(a=>{function i5(o,n){if(x=(n&255)/255,x===1)return n;let L=n>>24&255,T=n>>16&255,h=n>>8&255,d=o>>24&255,_=o>>16&255,p=o>>8&255;return P=d+Math.round((L-d)*x),B=_+Math.round((T-_)*x),w=p+Math.round((h-p)*x),Z.toRgba(P,B,w)}a.blend=i5;function e(o,n,L){let T=V.relativeLuminance(o>>8),h=V.relativeLuminance(n>>8);if(h0(T,h)>8));if(u>8));return u>Q?c:C}return c}let _=r(o,n,L),p=h0(T,V.relativeLuminance(_>>8));if(p>8));return p>u?_:c}return _}}a.ensureContrastRatio=e;function t(o,n,L){let T=o>>24&255,h=o>>16&255,d=o>>8&255,_=n>>24&255,p=n>>16&255,c=n>>8&255,u=h0(V.relativeLuminance2(_,p,c),V.relativeLuminance2(T,h,d));for(;u0||p>0||c>0);)_-=Math.max(0,Math.ceil(_*.1)),p-=Math.max(0,Math.ceil(p*.1)),c-=Math.max(0,Math.ceil(c*.1)),u=h0(V.relativeLuminance2(_,p,c),V.relativeLuminance2(T,h,d));return(_<<24|p<<16|c<<8|255)>>>0}a.reduceLuminance=t;function r(o,n,L){let T=o>>24&255,h=o>>16&255,d=o>>8&255,_=n>>24&255,p=n>>16&255,c=n>>8&255,u=h0(V.relativeLuminance2(_,p,c),V.relativeLuminance2(T,h,d));for(;u>>0}a.increaseLuminance=r;function s(o){return[o>>24&255,o>>16&255,o>>8&255,o&255]}a.toChannels=s})(d0||={});function E0(i5){let e=i5.toString(16);return e.length<2?"0"+e:e}function h0(i5,e){return i5=128512&&i5<=128591||i5>=127744&&i5<=128511||i5>=128640&&i5<=128767||i5>=9728&&i5<=9983||i5>=9984&&i5<=10175||i5>=65024&&i5<=65039||i5>=129280&&i5<=129535||i5>=127462&&i5<=127487}function Ze(i5,e,t,r){return e===1&&t>Math.ceil(r*1.5)&&i5!==void 0&&i5>255&&!yt(i5)&&!W0(i5)&&!At(i5)}function V0(i5){return W0(i5)||gt(i5)}function Ue(){return{css:{canvas:U0(),cell:U0()},device:{canvas:U0(),cell:U0(),char:{width:0,height:0,left:0,top:0}}}}function U0(){return{width:0,height:0}}function We(i5,e,t=0){return(i5-(Math.round(e)*2-t))%(Math.round(e)*2)}var l;(C=>(C.LINE_H=Object.freeze({type:2,data:"M0,.5 L1,.5",strokeWidth:1}),C.LINE_V=Object.freeze({type:2,data:"M.5,0 L.5,1",strokeWidth:1}),C.FADE_RIGHT=Object.freeze({type:2,data:"M0,.5 L.28,.5 M.32,.5 L.52,.5 M.60,.5 L.72,.5 M.84,.5 L.90,.5",strokeWidth:1}),C.FADE_LEFT=Object.freeze({type:2,data:"M.10,.5 L.16,.5 M.28,.5 L.40,.5 M.48,.5 L.68,.5 M.72,.5 L1,.5",strokeWidth:1}),C.FADE_DOWN=Object.freeze({type:2,data:"M.5,0 L.5,.28 M.5,.32 L.5,.52 M.5,.60 L.5,.72 M.5,.84 L.5,.90",strokeWidth:1}),C.FADE_UP=Object.freeze({type:2,data:"M.5,.10 L.5,.16 M.5,.28 L.5,.40 M.5,.48 L.5,.68 M.5,.72 L.5,1",strokeWidth:1}),C.CURVE_DOWN_RIGHT=Object.freeze({type:2,data:(Q,m)=>`M.5,1 L.5,${.5+m/.15*.5} C.5,${.5+m/.15*.5},.5,.5,1,.5`,strokeWidth:1}),C.CURVE_DOWN_LEFT=Object.freeze({type:2,data:(Q,m)=>`M.5,1 L.5,${.5+m/.15*.5} C.5,${.5+m/.15*.5},.5,.5,0,.5`,strokeWidth:1}),C.CURVE_UP_RIGHT=Object.freeze({type:2,data:(Q,m)=>`M.5,0 L.5,${.5-m/.15*.5} C.5,${.5-m/.15*.5},.5,.5,1,.5`,strokeWidth:1}),C.CURVE_UP_LEFT=Object.freeze({type:2,data:(Q,m)=>`M.5,0 L.5,${.5-m/.15*.5} C.5,${.5-m/.15*.5},.5,.5,0,.5`,strokeWidth:1}),C.NODE_FILL=Object.freeze({type:3,data:"M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5"}),C.NODE_STROKE=Object.freeze({type:3,data:"M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5",strokeWidth:1}),C.NODE_LINE_RIGHT=Object.freeze({type:3,data:"M1,.5 L.85,.5",strokeWidth:1}),C.NODE_LINE_LEFT=Object.freeze({type:3,data:"M0,.5 L.15,.5",strokeWidth:1}),C.NODE_LINE_DOWN=Object.freeze({type:3,data:"M.5,1 L.5,.7",strokeWidth:1}),C.NODE_LINE_UP=Object.freeze({type:3,data:"M.5,0 L.5,.3",strokeWidth:1})))(l||={});var Ve={"\u2500":{type:2,data:"M0,.5 L1,.5",strokeWidth:1},"\u2501":{type:2,data:"M0,.5 L1,.5",strokeWidth:3},"\u2502":{type:2,data:"M.5,0 L.5,1",strokeWidth:1},"\u2503":{type:2,data:"M.5,0 L.5,1",strokeWidth:3},"\u2504":{type:2,data:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",strokeWidth:1},"\u2505":{type:2,data:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",strokeWidth:3},"\u2506":{type:2,data:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",strokeWidth:1},"\u2507":{type:2,data:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",strokeWidth:3},"\u2508":{type:2,data:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",strokeWidth:1},"\u2509":{type:2,data:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",strokeWidth:3},"\u250A":{type:2,data:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",strokeWidth:1},"\u250B":{type:2,data:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",strokeWidth:3},"\u250C":{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},"\u250D":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u250E":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u250F":{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3},"\u2510":{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},"\u2511":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u2512":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2513":{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3},"\u2514":{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},"\u2515":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u2516":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2517":{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3},"\u2518":{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},"\u2519":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u251A":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u251B":{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3},"\u251C":{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:1},"\u251D":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u251E":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u251F":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2520":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"\u2521":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"\u2522":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"\u2523":{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:3},"\u2524":{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:1},"\u2525":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u2526":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2527":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2528":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"\u2529":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"\u252A":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"\u252B":{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:3},"\u252C":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:1},"\u252D":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u252E":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u252F":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"\u2530":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2531":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"\u2532":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"\u2533":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:3},"\u2534":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,0",strokeWidth:1},"\u2535":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u2536":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u2537":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"\u2538":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2539":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"\u253A":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"\u253B":{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,0",strokeWidth:3},"\u253C":{type:2,data:"M0,.5 L1,.5 M.5,0 L.5,1",strokeWidth:1},"\u253D":[{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u253E":[{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u253F":[{type:2,data:"M.5,0 L.5,1",strokeWidth:1},{type:2,data:"M0,.5 L1,.5",strokeWidth:3}],"\u2540":[{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2541":[{type:2,data:"M.5,.5 L.5,0 M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u2542":[{type:2,data:"M0,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1",strokeWidth:3}],"\u2543":[{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:3}],"\u2544":[{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:3}],"\u2545":[{type:2,data:"M.5,0 L.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M0,.5 L.5,.5 L.5,1",strokeWidth:3}],"\u2546":[{type:2,data:"M.5,0 L.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M0.5,1 L.5,.5 L1,.5",strokeWidth:3}],"\u2547":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0 M0,.5 L1,.5",strokeWidth:3}],"\u2548":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M0,.5 L1,.5 M.5,.5 L.5,1",strokeWidth:3}],"\u2549":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1 M.5,.5 L0,.5",strokeWidth:3}],"\u254A":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,0 L.5,1 M.5,.5 L1,.5",strokeWidth:3}],"\u254B":{type:2,data:"M0,.5 L1,.5 M.5,0 L.5,1",strokeWidth:3},"\u254C":{type:2,data:"M.1,.5 L.4,.5 M.6,.5 L.9,.5",strokeWidth:1},"\u254D":{type:2,data:"M.1,.5 L.4,.5 M.6,.5 L.9,.5",strokeWidth:3},"\u254E":{type:2,data:"M.5,.1 L.5,.4 M.5,.6 L.5,.9",strokeWidth:1},"\u254F":{type:2,data:"M.5,.1 L.5,.4 M.5,.6 L.5,.9",strokeWidth:3},"\u2550":{type:2,data:(i5,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u2551":{type:2,data:(i5,e)=>`M${.5-i5},0 L${.5-i5},1 M${.5+i5},0 L${.5+i5},1`,strokeWidth:1},"\u2552":{type:2,data:(i5,e)=>`M.5,1 L.5,${.5-e} L1,${.5-e} M.5,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u2553":{type:2,data:(i5,e)=>`M${.5-i5},1 L${.5-i5},.5 L1,.5 M${.5+i5},.5 L${.5+i5},1`,strokeWidth:1},"\u2554":{type:2,data:(i5,e)=>`M1,${.5-e} L${.5-i5},${.5-e} L${.5-i5},1 M1,${.5+e} L${.5+i5},${.5+e} L${.5+i5},1`,strokeWidth:1},"\u2555":{type:2,data:(i5,e)=>`M0,${.5-e} L.5,${.5-e} L.5,1 M0,${.5+e} L.5,${.5+e}`,strokeWidth:1},"\u2556":{type:2,data:(i5,e)=>`M${.5+i5},1 L${.5+i5},.5 L0,.5 M${.5-i5},.5 L${.5-i5},1`,strokeWidth:1},"\u2557":{type:2,data:(i5,e)=>`M0,${.5+e} L${.5-i5},${.5+e} L${.5-i5},1 M0,${.5-e} L${.5+i5},${.5-e} L${.5+i5},1`,strokeWidth:1},"\u2558":{type:2,data:(i5,e)=>`M.5,0 L.5,${.5+e} L1,${.5+e} M.5,${.5-e} L1,${.5-e}`,strokeWidth:1},"\u2559":{type:2,data:(i5,e)=>`M1,.5 L${.5-i5},.5 L${.5-i5},0 M${.5+i5},.5 L${.5+i5},0`,strokeWidth:1},"\u255A":{type:2,data:(i5,e)=>`M1,${.5-e} L${.5+i5},${.5-e} L${.5+i5},0 M1,${.5+e} L${.5-i5},${.5+e} L${.5-i5},0`,strokeWidth:1},"\u255B":{type:2,data:(i5,e)=>`M0,${.5+e} L.5,${.5+e} L.5,0 M0,${.5-e} L.5,${.5-e}`,strokeWidth:1},"\u255C":{type:2,data:(i5,e)=>`M0,.5 L${.5+i5},.5 L${.5+i5},0 M${.5-i5},.5 L${.5-i5},0`,strokeWidth:1},"\u255D":{type:2,data:(i5,e)=>`M0,${.5-e} L${.5-i5},${.5-e} L${.5-i5},0 M0,${.5+e} L${.5+i5},${.5+e} L${.5+i5},0`,strokeWidth:1},"\u255E":{type:2,data:(i5,e)=>`M.5,0 L.5,1 M.5,${.5-e} L1,${.5-e} M.5,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u255F":{type:2,data:(i5,e)=>`M${.5-i5},0 L${.5-i5},1 M${.5+i5},0 L${.5+i5},1 M${.5+i5},.5 L1,.5`,strokeWidth:1},"\u2560":{type:2,data:(i5,e)=>`M${.5-i5},0 L${.5-i5},1 M1,${.5+e} L${.5+i5},${.5+e} L${.5+i5},1 M1,${.5-e} L${.5+i5},${.5-e} L${.5+i5},0`,strokeWidth:1},"\u2561":{type:2,data:(i5,e)=>`M.5,0 L.5,1 M0,${.5-e} L.5,${.5-e} M0,${.5+e} L.5,${.5+e}`,strokeWidth:1},"\u2562":{type:2,data:(i5,e)=>`M0,.5 L${.5-i5},.5 M${.5-i5},0 L${.5-i5},1 M${.5+i5},0 L${.5+i5},1`,strokeWidth:1},"\u2563":{type:2,data:(i5,e)=>`M${.5+i5},0 L${.5+i5},1 M0,${.5+e} L${.5-i5},${.5+e} L${.5-i5},1 M0,${.5-e} L${.5-i5},${.5-e} L${.5-i5},0`,strokeWidth:1},"\u2564":{type:2,data:(i5,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e} M.5,${.5+e} L.5,1`,strokeWidth:1},"\u2565":{type:2,data:(i5,e)=>`M0,.5 L1,.5 M${.5-i5},.5 L${.5-i5},1 M${.5+i5},.5 L${.5+i5},1`,strokeWidth:1},"\u2566":{type:2,data:(i5,e)=>`M0,${.5-e} L1,${.5-e} M0,${.5+e} L${.5-i5},${.5+e} L${.5-i5},1 M1,${.5+e} L${.5+i5},${.5+e} L${.5+i5},1`,strokeWidth:1},"\u2567":{type:2,data:(i5,e)=>`M.5,0 L.5,${.5-e} M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u2568":{type:2,data:(i5,e)=>`M0,.5 L1,.5 M${.5-i5},.5 L${.5-i5},0 M${.5+i5},.5 L${.5+i5},0`,strokeWidth:1},"\u2569":{type:2,data:(i5,e)=>`M0,${.5+e} L1,${.5+e} M0,${.5-e} L${.5-i5},${.5-e} L${.5-i5},0 M1,${.5-e} L${.5+i5},${.5-e} L${.5+i5},0`,strokeWidth:1},"\u256A":{type:2,data:(i5,e)=>`M.5,0 L.5,1 M0,${.5-e} L1,${.5-e} M0,${.5+e} L1,${.5+e}`,strokeWidth:1},"\u256B":{type:2,data:(i5,e)=>`M0,.5 L1,.5 M${.5-i5},0 L${.5-i5},1 M${.5+i5},0 L${.5+i5},1`,strokeWidth:1},"\u256C":{type:2,data:(i5,e)=>`M0,${.5+e} L${.5-i5},${.5+e} L${.5-i5},1 M1,${.5+e} L${.5+i5},${.5+e} L${.5+i5},1 M0,${.5-e} L${.5-i5},${.5-e} L${.5-i5},0 M1,${.5-e} L${.5+i5},${.5-e} L${.5+i5},0`,strokeWidth:1},"\u256D":{type:2,data:(i5,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,1,.5`,strokeWidth:1},"\u256E":{type:2,data:(i5,e)=>`M.5,1 L.5,${.5+e/.15*.5} C.5,${.5+e/.15*.5},.5,.5,0,.5`,strokeWidth:1},"\u256F":{type:2,data:(i5,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,0,.5`,strokeWidth:1},"\u2570":{type:2,data:(i5,e)=>`M.5,0 L.5,${.5-e/.15*.5} C.5,${.5-e/.15*.5},.5,.5,1,.5`,strokeWidth:1},"\u2571":{type:2,data:"M1,0 L0,1",strokeWidth:1},"\u2572":{type:2,data:"M0,0 L1,1",strokeWidth:1},"\u2573":{type:2,data:"M1,0 L0,1 M0,0 L1,1",strokeWidth:1},"\u2574":{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},"\u2575":{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},"\u2576":{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},"\u2577":{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},"\u2578":{type:2,data:"M.5,.5 L0,.5",strokeWidth:3},"\u2579":{type:2,data:"M.5,.5 L.5,0",strokeWidth:3},"\u257A":{type:2,data:"M.5,.5 L1,.5",strokeWidth:3},"\u257B":{type:2,data:"M.5,.5 L.5,1",strokeWidth:3},"\u257C":[{type:2,data:"M.5,.5 L0,.5",strokeWidth:1},{type:2,data:"M.5,.5 L1,.5",strokeWidth:3}],"\u257D":[{type:2,data:"M.5,.5 L.5,0",strokeWidth:1},{type:2,data:"M.5,.5 L.5,1",strokeWidth:3}],"\u257E":[{type:2,data:"M.5,.5 L1,.5",strokeWidth:1},{type:2,data:"M.5,.5 L0,.5",strokeWidth:3}],"\u257F":[{type:2,data:"M.5,.5 L.5,1",strokeWidth:1},{type:2,data:"M.5,.5 L.5,0",strokeWidth:3}],"\u2580":{type:0,data:[{x:0,y:0,w:8,h:4}]},"\u2581":{type:0,data:[{x:0,y:7,w:8,h:1}]},"\u2582":{type:0,data:[{x:0,y:6,w:8,h:2}]},"\u2583":{type:0,data:[{x:0,y:5,w:8,h:3}]},"\u2584":{type:0,data:[{x:0,y:4,w:8,h:4}]},"\u2585":{type:0,data:[{x:0,y:3,w:8,h:5}]},"\u2586":{type:0,data:[{x:0,y:2,w:8,h:6}]},"\u2587":{type:0,data:[{x:0,y:1,w:8,h:7}]},"\u2588":{type:0,data:[{x:0,y:0,w:8,h:8}]},"\u2589":{type:0,data:[{x:0,y:0,w:7,h:8}]},"\u258A":{type:0,data:[{x:0,y:0,w:6,h:8}]},"\u258B":{type:0,data:[{x:0,y:0,w:5,h:8}]},"\u258C":{type:0,data:[{x:0,y:0,w:4,h:8}]},"\u258D":{type:0,data:[{x:0,y:0,w:3,h:8}]},"\u258E":{type:0,data:[{x:0,y:0,w:2,h:8}]},"\u258F":{type:0,data:[{x:0,y:0,w:1,h:8}]},"\u2590":{type:0,data:[{x:4,y:0,w:4,h:8}]},"\u2591":{type:1,data:[[1,0],[0,0]]},"\u2592":{type:1,data:[[1,0],[0,1]]},"\u2593":{type:1,data:[[1,1],[1,0]]},"\u2594":{type:0,data:[{x:0,y:0,w:8,h:1}]},"\u2595":{type:0,data:[{x:7,y:0,w:1,h:8}]},"\u2596":{type:0,data:[{x:0,y:4,w:4,h:4}]},"\u2597":{type:0,data:[{x:4,y:4,w:4,h:4}]},"\u2598":{type:0,data:[{x:0,y:0,w:4,h:4}]},"\u2599":{type:0,data:[{x:0,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}]},"\u259A":{type:0,data:[{x:0,y:0,w:4,h:4},{x:4,y:4,w:4,h:4}]},"\u259B":{type:0,data:[{x:0,y:0,w:4,h:8},{x:4,y:0,w:4,h:4}]},"\u259C":{type:0,data:[{x:0,y:0,w:8,h:4},{x:4,y:0,w:4,h:8}]},"\u259D":{type:0,data:[{x:4,y:0,w:4,h:4}]},"\u259E":{type:0,data:[{x:4,y:0,w:4,h:4},{x:0,y:4,w:4,h:4}]},"\u259F":{type:0,data:[{x:4,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}]},"\uE0A0":{type:5,data:{d:"M.3,1 L.03,1 L.03,.88 C.03,.82,.06,.78,.11,.73 C.15,.7,.2,.68,.28,.65 L.43,.6 C.49,.58,.53,.56,.56,.53 C.59,.5,.6,.47,.6,.43 L.6,.27 L.4,.27 L.69,.1 L.98,.27 L.78,.27 L.78,.46 C.78,.52,.76,.56,.72,.61 C.68,.66,.63,.67,.56,.7 L.48,.72 C.42,.74,.38,.76,.35,.78 C.32,.8,.31,.84,.31,.88 L.31,1 M.3,.5 L.03,.59 L.03,.09 L.3,.09 L.3,.655",type:0}},"\uE0A1":{type:5,data:{d:"M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.355,.03 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5",type:0}},"\uE0A2":{type:5,data:{d:"M.25,.94 C.16,.94,.11,.92,.11,.87 L.11,.53 C.11,.48,.15,.455,.23,.45 L.23,.3 C.23,.25,.26,.22,.31,.19 C.36,.16,.43,.15,.51,.15 C.59,.15,.66,.16,.71,.19 C.77,.22,.79,.26,.79,.3 L.79,.45 C.87,.45,.91,.48,.91,.53 L.91,.87 C.91,.92,.86,.94,.77,.94 L.24,.94 M.53,.2 C.49,.2,.45,.21,.42,.23 C.39,.25,.38,.27,.38,.3 L.38,.45 L.68,.45 L.68,.3 C.68,.27,.67,.25,.64,.23 C.61,.21,.58,.2,.53,.2 M.58,.82 L.58,.66 C.63,.65,.65,.63,.65,.6 C.65,.58,.64,.57,.61,.56 C.58,.55,.56,.54,.52,.54 C.48,.54,.46,.55,.43,.56 C.4,.57,.39,.59,.39,.6 C.39,.63,.41,.64,.46,.66 L.46,.82 L.57,.82",type:0}},"\uE0A3":{type:5,data:{d:"M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.7,.03 L.7,.1 L.355,.1 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5",type:0}},"\uE0B0":{type:5,data:{d:"M0,0 L1,.5 L0,1",type:0,rightPadding:2}},"\uE0B1":{type:5,data:{d:"M-1,-.5 L1,.5 L-1,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0B2":{type:5,data:{d:"M1,0 L0,.5 L1,1",type:0,leftPadding:2}},"\uE0B3":{type:5,data:{d:"M2,-.5 L0,.5 L2,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0B4":{type:5,data:{d:"M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0",type:0,rightPadding:1}},"\uE0B5":{type:5,data:{d:"M.2,1 C.422,1,.8,.826,.78,.5 C.8,.174,0.422,0,.2,0",type:1,rightPadding:1}},"\uE0B6":{type:5,data:{d:"M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0",type:0,leftPadding:1}},"\uE0B7":{type:5,data:{d:"M.8,1 C0.578,1,0.2,.826,.22,.5 C0.2,0.174,0.578,0,0.8,0",type:1,leftPadding:1}},"\uE0B8":{type:5,data:{d:"M-.5,-.5 L1.5,1.5 L-.5,1.5",type:0}},"\uE0B9":{type:5,data:{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0BA":{type:5,data:{d:"M1.5,-.5 L-.5,1.5 L1.5,1.5",type:0}},"\uE0BB":{type:5,data:{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0BC":{type:5,data:{d:"M1.5,-.5 L-.5,1.5 L-.5,-.5",type:0}},"\uE0BD":{type:5,data:{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0BE":{type:5,data:{d:"M-.5,-.5 L1.5,1.5 L1.5,-.5",type:0}},"\uE0BF":{type:5,data:{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1}},"\uE0C0":{type:5,data:{d:"M0.8069,0.6075 Q0.7991,0.612,0.7815,0.6218 T0.7542,0.6369 T0.7282,0.6508 T0.7016,0.6644 T0.6778,0.6758 T0.6551,0.6854 T0.637,0.6911 T0.622,0.6936 Q0.6186,0.6936,0.6128,0.6942 T0.6018,0.6952 T0.5894,0.6956 T0.5757,0.6952 T0.561,0.693 T0.5451,0.6879 Q0.5342,0.6834,0.5193,0.6756 T0.4925,0.6614 T0.4673,0.6485 T0.4436,0.6389 T0.4239,0.6363 T0.4089,0.6422 Q0.4045,0.6467,0.3958,0.6554 T0.3843,0.6669 T0.3748,0.6746 T0.3613,0.683 T0.3442,0.6903 L0.3499,0.6956 Q0.3584,0.7042,0.3679,0.7177 T0.3836,0.7403 T0.4019,0.7583 T0.4295,0.7703 Q0.4688,0.7785,0.5203,0.7605 Q0.4902,0.7817,0.449,0.7911 T0.3696,0.8001 Q0.3567,0.7997,0.341,0.7942 T0.3111,0.7813 T0.2805,0.766 T0.2493,0.753 T0.218,0.7472 T0.187,0.754 Q0.1762,0.7589,0.1653,0.7681 T0.1453,0.7895 T0.1308,0.818 T0.1264,0.8507 L0.1264,0.858 Q0.1453,0.8462,0.1607,0.8433 T0.1899,0.8452 T0.2226,0.8625 Q0.2344,0.8707,0.2485,0.8752 T0.2813,0.8831 T0.3089,0.889 Q0.2981,0.8915,0.2846,0.8956 T0.2603,0.9037 T0.2368,0.9115 T0.2129,0.9174 T0.1894,0.9194 T0.1653,0.9155 Q0.1504,0.9115,0.1328,0.9196 T0.0986,0.9421 T0.0655,0.9708 T0.0317,0.9943 T0,1 Q0.001,0.94,0.0017,0.6508 T0.0024,0.2322 Q0.002,0.2109,0.0007,0.1379 T0,0.0249 Q0.0058,0.0253,0.009,0.0257 T0.0156,0.0288 T0.0203,0.0333 T0.0264,0.0422 T0.0349,0.0539 Q0.044,0.0657,0.0542,0.072 T0.0752,0.0789 T0.096,0.0775 T0.1175,0.0694 T0.1379,0.0573 T0.1579,0.0432 Q0.2141,0,0.2673,0.0318 Q0.2588,0.0318,0.2517,0.033 T0.239,0.0361 T0.2276,0.0424 T0.218,0.0504 T0.2085,0.0612 T0.1997,0.0732 T0.1899,0.0875 T0.1795,0.1028 Q0.1748,0.1093,0.1507,0.1234 T0.1186,0.1448 Q0.0999,0.1612,0.1108,0.1783 Q0.1162,0.1877,0.1238,0.1928 T0.1404,0.1979 T0.158,0.1967 T0.1775,0.1895 T0.1963,0.1791 T0.2151,0.1663 T0.2314,0.1534 T0.2458,0.141 T0.2561,0.1322 Q0.2815,0.1106,0.3017,0.101 T0.344,0.0926 T0.3919,0.1077 Q0.4099,0.1175,0.4265,0.1206 T0.4588,0.1202 T0.488,0.1098 T0.5176,0.0914 Q0.5071,0.1032,0.4963,0.1122 T0.4722,0.1285 T0.4497,0.1401 T0.4223,0.1518 T0.3943,0.1636 Q0.374,0.1726,0.3623,0.1863 T0.3482,0.2162 Q0.3445,0.2403,0.3608,0.2605 T0.4072,0.2876 Q0.4194,0.2909,0.4326,0.2899 T0.457,0.2856 T0.4805,0.2764 T0.5032,0.264 T0.5256,0.2497 T0.5478,0.2352 T0.5698,0.2222 T0.5921,0.2118 Q0.6982,0.173,0.81,0.2436 Q0.833,0.2583,0.8496,0.2674 T0.887,0.2846 T0.925,0.294 T0.9617,0.2911 T1,0.2746 Q0.9817,0.2946,0.96,0.3056 T0.9175,0.3191 T0.8731,0.3209 T0.8277,0.3164 T0.7818,0.3109 T0.7359,0.3101 T0.6907,0.3197 T0.647,0.3448 Q0.6443,0.3472,0.6333,0.3562 T0.6194,0.3674 T0.6076,0.3766 T0.594,0.386 T0.5815,0.3931 T0.5661,0.4007 T0.5501,0.4064 L0.5556,0.4121 Q0.5627,0.4198,0.5722,0.4251 T0.5926,0.4337 T0.6131,0.439 T0.6343,0.4431 T0.6521,0.4468 Q0.6762,0.4529,0.7056,0.4463 Q0.6772,0.4623,0.6543,0.471 T0.604,0.4814 Q0.5962,0.4818,0.5815,0.4825 T0.5591,0.4835 T0.5393,0.4857 T0.519,0.4908 T0.5007,0.4996 T0.4812,0.5141 T0.4627,0.5353 L0.4549,0.5451 L0.4661,0.543 Q0.4759,0.541,0.4909,0.55 T0.5315,0.5796 T0.5684,0.6083 Q0.5799,0.6161,0.5915,0.6212 T0.6181,0.6308 L0.6331,0.6353 Q0.6514,0.6418,0.7004,0.633 T0.8069,0.6075 Z",type:0}},"\uE0C1":{type:5,data:{d:"M0.011,0.0469 Q0.0152,0.0412,0.02,0.0361 T0.0292,0.0281 T0.0365,0.0253 Q0.0426,0.0257,0.0461,0.0263 T0.0534,0.03 T0.0582,0.0345 T0.0645,0.0434 T0.0723,0.0546 Q0.0807,0.0653,0.0897,0.0712 T0.1081,0.0775 T0.1255,0.0765 T0.1438,0.0695 T0.1605,0.0593 T0.1772,0.0473 Q0.2388,0,0.2927,0.0326 L0.2614,0.0767 L0.2581,0.0767 Q0.2494,0.0767,0.2444,0.0773 T0.2359,0.0789 T0.2291,0.0842 T0.2233,0.0922 T0.2146,0.1054 T0.2026,0.1236 Q0.1943,0.1354,0.1843,0.1444 T0.1675,0.1574 T0.1526,0.1654 T0.1429,0.1701 Q0.1436,0.1717,0.1446,0.1733 Q0.1504,0.1823,0.1581,0.1866 T0.1733,0.1915 T0.1909,0.1884 T0.2085,0.1809 T0.2265,0.169 T0.2427,0.1564 T0.2575,0.1436 T0.2688,0.1338 Q0.2969,0.1101,0.3195,0.0995 T0.3643,0.0901 T0.4114,0.1048 Q0.4279,0.1138,0.4426,0.1168 T0.4705,0.117 T0.4945,0.1089 T0.5186,0.0942 L0.5211,0.0926 L0.5289,0.0922 Q0.5411,0.095,0.5189,0.1203 Q0.505,0.1358,0.4913,0.1474 T0.4634,0.167 T0.4389,0.1796 T0.4121,0.1909 T0.3866,0.2011 Q0.3782,0.2047,0.3727,0.2092 Q0.3717,0.2117,0.3714,0.2141 Q0.3695,0.2263,0.3751,0.2388 T0.394,0.2616 T0.4253,0.2761 Q0.4398,0.2785,0.4513,0.2779 T0.4756,0.2724 T0.4987,0.2616 T0.5216,0.2476 T0.5453,0.2321 T0.5703,0.217 T0.5976,0.2043 Q0.7015,0.166,0.8099,0.2345 Q0.8361,0.2512,0.8572,0.2616 T0.9006,0.2779 T0.9432,0.281 T0.9819,0.2663 L0.9887,0.2671 Q1,0.272,0.9768,0.2969 Q0.9558,0.3193,0.9319,0.333 T0.8858,0.3513 T0.8391,0.3569 T0.793,0.3546 T0.7485,0.3497 T0.7065,0.3473 T0.6681,0.3522 T0.6344,0.3699 Q0.5963,0.4005,0.5847,0.4082 Q0.595,0.4139,0.6079,0.4174 T0.6367,0.4237 T0.6586,0.4278 Q0.6793,0.4331,0.7044,0.4278 Q0.7057,0.4278,0.707,0.4274 Q0.7067,0.429,0.7062,0.4313 T0.7036,0.4398 T0.6993,0.4513 T0.6931,0.4619 T0.6851,0.4698 Q0.6573,0.4857,0.6336,0.4947 T0.5831,0.5053 Q0.577,0.5057,0.5615,0.5065 T0.5394,0.508 T0.5221,0.51 T0.5042,0.5139 T0.4905,0.5204 Q0.5008,0.5232,0.515,0.5328 T0.549,0.5585 T0.5799,0.5824 Q0.5905,0.5897,0.6013,0.5946 T0.6262,0.6036 T0.6409,0.6081 Q0.6567,0.6134,0.7035,0.605 T0.8028,0.5816 L0.7974,0.5922 T0.7867,0.6134 L0.7812,0.624 Q0.7799,0.6248,0.7786,0.6252 Q0.7709,0.6297,0.7573,0.6372 T0.7346,0.6499 T0.7123,0.6621 T0.6897,0.6741 T0.6686,0.6847 T0.648,0.6941 T0.6297,0.7013 T0.6129,0.7062 T0.5995,0.7076 Q0.5992,0.7076,0.5831,0.7092 T0.5529,0.7092 T0.525,0.7019 Q0.5105,0.6958,0.4824,0.6811 T0.4356,0.6586 T0.4079,0.6533 Q0.3872,0.6741,0.3827,0.6782 Q0.3872,0.6835,0.3934,0.6929 T0.4037,0.7078 T0.4142,0.7198 T0.4284,0.7304 T0.4466,0.7365 Q0.4824,0.7443,0.5292,0.728 L0.5292,0.7294 T0.529,0.7333 T0.5284,0.7392 T0.5266,0.7463 T0.5236,0.7539 T0.5186,0.7612 T0.5111,0.7679 Q0.5086,0.77,0.5056,0.7716 Q0.4466,0.8104,0.3591,0.8091 Q0.344,0.8091,0.3246,0.802 T0.2893,0.7863 T0.2543,0.77 T0.2206,0.76 T0.1894,0.7643 Q0.1804,0.7684,0.1733,0.7753 Q0.1591,0.7892,0.1601,0.8169 Q0.1784,0.8067,0.1938,0.8053 T0.2212,0.8087 T0.2507,0.825 Q0.2614,0.8324,0.2744,0.8365 T0.3053,0.844 T0.332,0.8499 L0.303,0.894 L0.2998,0.8948 Q0.2904,0.8968,0.278,0.9009 T0.2554,0.9086 T0.233,0.9158 T0.2099,0.9213 T0.1868,0.9229 T0.1633,0.9192 Q0.1517,0.916,0.1367,0.9241 T0.1065,0.9462 T0.0753,0.9737 T0.041,0.9955 T0.0061,1 Q0,0.9984,0.0068,0.9845 Q0.009,0.9804,0.0123,0.9755 Q0.0187,0.9666,0.0261,0.9606 T0.0374,0.9555 Q0.0481,0.958,0.0629,0.949 T0.0931,0.9262 T0.1247,0.8987 T0.1597,0.8777 T0.1949,0.8752 Q0.213,0.8805,0.2343,0.8772 Q0.2249,0.8736,0.2172,0.8683 Q0.1914,0.8511,0.1738,0.8497 T0.1349,0.8617 Q0.1304,0.865,0.1273,0.8646 T0.1242,0.8605 L0.1239,0.8536 Q0.1226,0.8295,0.1354,0.8016 T0.1723,0.7496 Q0.1914,0.7308,0.2104,0.7219 Q0.2259,0.7145,0.2425,0.7141 T0.273,0.7182 T0.3022,0.7296 T0.3301,0.7439 T0.3569,0.7571 T0.3821,0.7643 Q0.373,0.7561,0.3598,0.7367 T0.3382,0.7088 L0.3327,0.7035 Q0.3282,0.699,0.3353,0.6884 T0.3517,0.6692 L0.3611,0.6607 Q0.3711,0.657,0.3751,0.6554 T0.3837,0.6507 T0.3898,0.6458 T0.3979,0.637 T0.4111,0.6236 Q0.4172,0.6175,0.4234,0.6138 Q0.4327,0.6081,0.4439,0.6077 T0.4655,0.6105 T0.4884,0.6201 T0.5113,0.633 T0.5347,0.6468 T0.5573,0.6578 Q0.5705,0.6635,0.5833,0.665 T0.6112,0.665 T0.6286,0.6635 Q0.6421,0.6631,0.6718,0.6501 Q0.626,0.6578,0.6089,0.6517 Q0.6063,0.6509,0.6002,0.6491 T0.5884,0.6454 T0.5757,0.6409 T0.561,0.6344 T0.5466,0.6256 Q0.536,0.6183,0.5108,0.5989 T0.4722,0.5712 T0.4527,0.564 L0.4418,0.5661 Q0.4308,0.5681,0.4366,0.5563 Q0.4401,0.5485,0.4476,0.5392 L0.455,0.5298 Q0.4631,0.5188,0.4714,0.51 T0.4866,0.4947 T0.5021,0.4831 T0.516,0.4749 T0.53,0.4694 T0.5428,0.4657 T0.5558,0.4637 T0.5674,0.4629 T0.5795,0.4625 Q0.5489,0.4543,0.534,0.4384 L0.5286,0.4327 Q0.5244,0.4282,0.5318,0.4174 T0.5486,0.398 L0.5579,0.3899 Q0.567,0.387,0.5749,0.3834 T0.5878,0.3772 T0.6012,0.3687 T0.6126,0.3603 T0.6268,0.3487 T0.6421,0.3361 Q0.6567,0.3242,0.6712,0.3159 T0.7009,0.3034 T0.7277,0.2973 T0.7556,0.2961 T0.7809,0.2977 T0.8074,0.301 Q0.8096,0.3014,0.8109,0.3016 T0.8143,0.302 T0.818,0.3026 Q0.8012,0.2936,0.7767,0.2781 Q0.6718,0.2117,0.5744,0.2476 Q0.5637,0.2516,0.5492,0.2604 T0.5218,0.2779 T0.4923,0.2965 T0.461,0.3124 T0.4282,0.3216 T0.394,0.3206 Q0.3637,0.3136,0.3474,0.2936 T0.3353,0.2459 Q0.3385,0.2247,0.355,0.2023 Q0.3646,0.1892,0.3763,0.179 Q0.3898,0.1672,0.4063,0.1595 Q0.3927,0.1562,0.3785,0.1485 Q0.3604,0.1387,0.3453,0.1354 T0.3161,0.1354 T0.2899,0.1458 T0.2627,0.166 Q0.262,0.1664,0.254,0.1733 T0.2414,0.1841 T0.2267,0.196 T0.2094,0.2086 T0.1915,0.2196 T0.1725,0.2288 T0.1546,0.2337 T0.1373,0.2343 T0.1223,0.228 T0.1097,0.2145 Q0.0962,0.1929,0.1258,0.1582 Q0.131,0.1517,0.1371,0.1464 Q0.1462,0.1387,0.156,0.1321 T0.1752,0.1205 T0.1878,0.1134 Q0.1891,0.1117,0.1947,0.103 T0.2035,0.0899 T0.2125,0.0781 T0.2243,0.064 Q0.1949,0.0616,0.1672,0.0828 Q0.153,0.0934,0.1412,0.1009 T0.1146,0.1146 T0.0873,0.1213 T0.0616,0.116 T0.0374,0.0962 Q0.0339,0.0909,0.0295,0.0846 T0.024,0.0767 T0.0202,0.0728 T0.0148,0.0704 T0.0068,0.0697 Q0.0003,0.0693,0.0058,0.0567 Q0.0077,0.0522,0.011,0.0469 Z",type:0}},"\uE0C2":{type:5,data:{d:"M0.1934,0.6075 Q0.2012,0.612,0.2188,0.6218 T0.2459,0.6369 T0.2718,0.6508 T0.2983,0.6644 T0.322,0.6758 T0.3447,0.6854 T0.363,0.6911 T0.3783,0.6936 Q0.3796,0.6936,0.396,0.6952 T0.4265,0.6952 T0.4548,0.6879 Q0.4656,0.6834,0.4805,0.6756 T0.5073,0.6614 T0.5327,0.6485 T0.5564,0.6389 T0.5759,0.6363 T0.5909,0.6422 Q0.5953,0.6467,0.604,0.6554 T0.6155,0.6669 T0.6251,0.6746 T0.6387,0.683 T0.6556,0.6903 L0.6498,0.6956 Q0.6414,0.7042,0.6319,0.7177 T0.6162,0.7403 T0.5979,0.7583 T0.5703,0.7703 Q0.5313,0.7785,0.4799,0.7605 Q0.5097,0.7817,0.5508,0.7911 T0.6302,0.8001 Q0.6431,0.7997,0.659,0.7942 T0.6888,0.7813 T0.7194,0.766 T0.7506,0.753 T0.7819,0.7472 T0.8131,0.754 Q0.8239,0.7589,0.8346,0.7681 T0.8544,0.7895 T0.8689,0.818 T0.8737,0.8507 L0.8733,0.858 Q0.8591,0.849,0.8474,0.845 T0.8242,0.8421 T0.8021,0.8482 T0.7772,0.8625 Q0.7653,0.8707,0.7513,0.8752 T0.7184,0.8831 T0.6908,0.889 Q0.7017,0.8915,0.7152,0.8956 T0.7396,0.9037 T0.7633,0.9115 T0.7872,0.9174 T0.8107,0.9194 T0.8347,0.9155 Q0.8493,0.9115,0.8671,0.9196 T0.9015,0.9421 T0.9345,0.9708 T0.9682,0.9943 T0.9997,1 Q0.999,0.94,0.9983,0.6508 T0.9976,0.2322 Q0.9976,0.2109,0.9992,0.1379 T1,0.0249 Q0.9949,0.0249,0.9917,0.0257 T0.9863,0.0273 T0.9817,0.0308 T0.9776,0.0357 T0.9721,0.0437 T0.9651,0.0539 Q0.956,0.0657,0.9456,0.072 T0.9247,0.0789 T0.904,0.0775 T0.8823,0.0694 T0.862,0.0573 T0.8422,0.0432 Q0.7856,0,0.7328,0.0318 Q0.7392,0.0318,0.7452,0.0324 T0.756,0.0345 T0.7657,0.0386 T0.7741,0.0437 T0.7821,0.0506 T0.7894,0.0588 T0.7966,0.0683 T0.8039,0.0787 T0.8119,0.0904 T0.8202,0.1028 Q0.8249,0.1093,0.849,0.1234 T0.8811,0.1448 Q0.8998,0.1612,0.8893,0.1783 Q0.8835,0.1877,0.8759,0.1928 T0.8593,0.1979 T0.8417,0.1967 T0.8222,0.1895 T0.8034,0.1791 T0.7848,0.1663 T0.7687,0.1534 T0.7543,0.141 T0.744,0.1322 Q0.7186,0.1106,0.6984,0.101 T0.6559,0.0926 T0.6082,0.1077 Q0.5899,0.1175,0.5733,0.1206 T0.5411,0.1202 T0.5122,0.1098 T0.4822,0.0914 Q0.4927,0.1032,0.5036,0.1122 T0.5276,0.1285 T0.5503,0.1401 T0.5777,0.1518 T0.6055,0.1636 Q0.6258,0.1726,0.6375,0.1863 T0.6519,0.2162 Q0.6556,0.2403,0.6392,0.2605 T0.5926,0.2876 Q0.5804,0.2909,0.5672,0.2899 T0.5428,0.2856 T0.5193,0.2764 T0.4966,0.264 T0.4744,0.2497 T0.4523,0.2352 T0.4301,0.2222 T0.4077,0.2118 Q0.3021,0.173,0.1903,0.2436 Q0.1669,0.2583,0.1504,0.2674 T0.1129,0.2846 T0.075,0.294 T0.0384,0.2911 T0,0.2746 Q0.021,0.297,0.0459,0.3082 T0.0953,0.3207 T0.146,0.3193 T0.1988,0.3131 T0.2508,0.3095 T0.3029,0.3176 T0.3529,0.3448 Q0.3566,0.348,0.3652,0.355 T0.3771,0.3645 T0.3877,0.3731 T0.3993,0.3815 T0.4103,0.3884 T0.4226,0.3953 T0.4352,0.4009 T0.4497,0.4064 L0.4443,0.4121 Q0.4372,0.4198,0.4277,0.4251 T0.4072,0.4337 T0.3867,0.439 T0.3656,0.4431 T0.3481,0.4468 Q0.3241,0.4529,0.2943,0.4463 Q0.3227,0.4623,0.3458,0.471 T0.3959,0.4814 Q0.404,0.4818,0.4187,0.4825 T0.4409,0.4835 T0.4605,0.4857 T0.4809,0.4908 T0.4992,0.4996 T0.5186,0.5141 T0.5371,0.5353 L0.5449,0.5451 L0.5337,0.543 Q0.5242,0.541,0.5091,0.55 T0.4683,0.5796 T0.4314,0.6083 Q0.4203,0.6161,0.4086,0.6212 T0.382,0.6308 T0.3667,0.6353 Q0.3485,0.6418,0.2995,0.633 T0.1934,0.6075 Z",type:0}},"\uE0C3":{type:5,data:{d:"M0.989,0.0469 Q0.9829,0.0379,0.9753,0.0316 T0.9635,0.0253 Q0.9574,0.0257,0.9539,0.0263 T0.9466,0.03 T0.9418,0.0345 T0.9355,0.0434 T0.9277,0.0546 Q0.9193,0.0653,0.9103,0.0712 T0.8919,0.0775 T0.8745,0.0765 T0.8564,0.0695 T0.8396,0.0593 T0.8228,0.0473 Q0.7612,0,0.7073,0.0326 L0.7386,0.0767 L0.7419,0.0767 Q0.7506,0.0767,0.7556,0.0773 T0.7641,0.0789 T0.7709,0.0842 T0.7767,0.0922 T0.7854,0.1054 T0.7974,0.1236 Q0.8057,0.1354,0.8157,0.1444 T0.8325,0.1574 T0.8474,0.1654 T0.8571,0.1701 Q0.8564,0.1717,0.8554,0.1733 Q0.8503,0.1815,0.844,0.186 T0.8301,0.1909 T0.8153,0.1903 T0.7993,0.1845 T0.7836,0.1758 T0.768,0.1648 T0.754,0.1535 T0.741,0.1425 T0.7312,0.1338 Q0.7031,0.1101,0.6805,0.0995 T0.6357,0.0901 T0.5886,0.1048 Q0.5721,0.1138,0.5574,0.1168 T0.5295,0.117 T0.5055,0.1089 T0.4814,0.0942 L0.4789,0.0926 L0.4711,0.0922 Q0.4589,0.095,0.4811,0.1203 Q0.495,0.1358,0.5087,0.1474 T0.5366,0.167 T0.5611,0.1796 T0.5879,0.1909 T0.6134,0.2011 Q0.6218,0.2047,0.6273,0.2092 Q0.6283,0.2117,0.6286,0.2141 Q0.6305,0.2263,0.6249,0.2388 T0.606,0.2616 T0.5747,0.2761 Q0.5602,0.2785,0.5487,0.2779 T0.5245,0.2724 T0.5015,0.2616 T0.4784,0.2476 T0.4547,0.2321 T0.4297,0.217 T0.4024,0.2043 Q0.2985,0.166,0.1901,0.2345 Q0.1694,0.248,0.1523,0.2569 T0.1165,0.2726 T0.0818,0.2812 T0.0494,0.2796 T0.0181,0.2663 L0.0116,0.2671 Q0,0.272,0.0232,0.2969 Q0.0468,0.3222,0.0742,0.3365 T0.1278,0.3542 T0.1807,0.3564 T0.233,0.352 T0.2815,0.3473 T0.3269,0.3509 T0.3656,0.3699 Q0.4037,0.4005,0.4153,0.4082 Q0.405,0.4139,0.3921,0.4174 T0.3633,0.4237 T0.3414,0.4278 Q0.3207,0.4331,0.2956,0.4278 Q0.2943,0.4278,0.293,0.4274 Q0.2933,0.429,0.2938,0.4313 T0.2964,0.4398 T0.3007,0.4513 T0.3069,0.4619 T0.3149,0.4698 Q0.3427,0.4857,0.3664,0.4947 T0.4169,0.5053 Q0.423,0.5057,0.4385,0.5065 T0.4606,0.508 T0.4779,0.51 T0.4958,0.5139 T0.5095,0.5204 Q0.4992,0.5232,0.485,0.5328 T0.451,0.5585 T0.4201,0.5824 Q0.4095,0.5897,0.3987,0.5946 T0.3738,0.6036 T0.3591,0.6081 Q0.3433,0.6134,0.2965,0.605 T0.1972,0.5816 L0.2026,0.5922 T0.2133,0.6134 L0.2188,0.624 Q0.2201,0.6248,0.2214,0.6252 Q0.2291,0.6297,0.2486,0.6405 T0.2786,0.657 T0.3066,0.6721 T0.3353,0.6866 T0.3598,0.6972 T0.383,0.7051 T0.4005,0.7076 Q0.4008,0.7076,0.4169,0.7092 T0.4471,0.7092 T0.475,0.7019 Q0.4895,0.6958,0.5176,0.6811 T0.5644,0.6586 T0.5921,0.6533 Q0.6128,0.6741,0.6173,0.6782 Q0.6131,0.6835,0.6068,0.6929 T0.5963,0.7078 T0.5858,0.7198 T0.5716,0.7304 T0.5534,0.7365 Q0.5179,0.7443,0.4708,0.728 L0.4708,0.7294 T0.471,0.7333 T0.4716,0.7392 T0.4734,0.7463 T0.4764,0.7539 T0.4814,0.7612 T0.4889,0.7679 Q0.4914,0.77,0.4944,0.7716 Q0.5534,0.8104,0.6409,0.8091 Q0.6521,0.8091,0.6654,0.8053 T0.6907,0.7959 T0.7157,0.7838 T0.741,0.772 T0.7652,0.7631 T0.7888,0.7594 T0.8106,0.7643 Q0.8196,0.7684,0.8267,0.7753 Q0.8409,0.7892,0.8399,0.8169 Q0.8216,0.8067,0.8062,0.8053 T0.7788,0.8087 T0.7493,0.825 Q0.7386,0.8324,0.7256,0.8365 T0.6947,0.844 T0.668,0.8499 L0.697,0.894 L0.7002,0.8948 Q0.7099,0.8968,0.7222,0.9009 T0.7446,0.9086 T0.767,0.9158 T0.7901,0.9213 T0.8132,0.9229 T0.8367,0.9192 Q0.8483,0.916,0.8633,0.9241 T0.8937,0.9462 T0.9248,0.9737 T0.959,0.9955 T0.9939,1 Q1,0.9984,0.9932,0.9845 Q0.991,0.9804,0.9877,0.9755 Q0.9813,0.9666,0.9739,0.9606 T0.9626,0.9555 Q0.9519,0.958,0.9372,0.949 T0.9071,0.9262 T0.8753,0.8987 T0.8403,0.8777 T0.8051,0.8752 Q0.787,0.8805,0.7657,0.8772 Q0.7751,0.8736,0.7828,0.8683 Q0.8086,0.8511,0.8262,0.8497 T0.8651,0.8617 Q0.8696,0.865,0.8727,0.8646 T0.8758,0.8605 L0.8761,0.8536 Q0.8774,0.8295,0.8646,0.8016 T0.8277,0.7496 Q0.8086,0.7308,0.7896,0.7219 Q0.7741,0.7145,0.7575,0.7141 T0.727,0.7182 T0.6978,0.7296 T0.6699,0.7439 T0.6433,0.7571 T0.6179,0.7643 Q0.627,0.7561,0.6402,0.7367 T0.6618,0.7088 L0.6673,0.7035 Q0.6718,0.699,0.6647,0.6884 T0.6483,0.6692 L0.6389,0.6607 Q0.6289,0.657,0.6249,0.6554 T0.6163,0.6507 T0.6102,0.6458 T0.6021,0.637 T0.5889,0.6236 Q0.5828,0.6175,0.5766,0.6138 Q0.5673,0.6081,0.5561,0.6077 T0.5345,0.6105 T0.5116,0.6201 T0.4887,0.633 T0.4653,0.6468 T0.4427,0.6578 Q0.4295,0.6635,0.4167,0.665 T0.3888,0.665 T0.3714,0.6635 Q0.3579,0.6631,0.3282,0.6501 Q0.374,0.6578,0.3911,0.6517 Q0.3937,0.6509,0.4024,0.6482 T0.4179,0.6434 T0.4348,0.6364 T0.4534,0.6256 Q0.464,0.6183,0.4892,0.5989 T0.5278,0.5712 T0.5476,0.564 L0.5582,0.5661 Q0.5692,0.5681,0.5634,0.5563 Q0.5599,0.5485,0.5524,0.5392 L0.545,0.5298 Q0.5369,0.5188,0.5286,0.51 T0.5134,0.4947 T0.4979,0.4831 T0.484,0.4749 T0.47,0.4694 T0.4572,0.4657 T0.4442,0.4637 T0.4326,0.4629 T0.4205,0.4625 Q0.4511,0.4543,0.466,0.4384 L0.4714,0.4327 Q0.4795,0.4237,0.4469,0.394 L0.4421,0.3899 Q0.433,0.387,0.4251,0.3834 T0.4122,0.3772 T0.3988,0.3687 T0.3874,0.3603 T0.3732,0.3487 T0.3579,0.3361 Q0.3433,0.3242,0.3288,0.3159 T0.2993,0.3034 T0.2725,0.2973 T0.2446,0.2961 T0.2193,0.2977 T0.1926,0.301 Q0.1855,0.3022,0.182,0.3026 Q0.1991,0.2936,0.2233,0.2781 Q0.3282,0.2117,0.4256,0.2476 Q0.4363,0.2516,0.451,0.2604 T0.4784,0.2779 T0.5077,0.2965 T0.539,0.3124 T0.5718,0.3216 T0.606,0.3206 Q0.6363,0.3136,0.6526,0.2936 T0.6647,0.2459 Q0.6615,0.2247,0.645,0.2023 Q0.6354,0.1892,0.6237,0.179 Q0.6102,0.1672,0.5937,0.1595 Q0.6073,0.1562,0.6215,0.1485 Q0.6396,0.1387,0.6547,0.1354 T0.6839,0.1354 T0.7101,0.1458 T0.7373,0.166 Q0.738,0.1664,0.746,0.1733 T0.7586,0.1841 T0.7733,0.196 T0.7907,0.2086 T0.8086,0.2196 T0.8275,0.2288 T0.8454,0.2337 T0.8627,0.2343 T0.8777,0.228 T0.8903,0.2145 Q0.9038,0.1929,0.8742,0.1582 Q0.869,0.1517,0.8629,0.1464 Q0.8538,0.1387,0.844,0.1321 T0.8248,0.1205 T0.8122,0.1134 Q0.8109,0.1117,0.8053,0.103 T0.7965,0.0899 T0.7875,0.0781 T0.7757,0.064 Q0.8051,0.0616,0.8328,0.0828 Q0.847,0.0934,0.8588,0.1009 T0.8854,0.1146 T0.9127,0.1213 T0.9384,0.116 T0.9626,0.0962 Q0.9664,0.0909,0.9706,0.0846 T0.976,0.0767 T0.9798,0.0728 T0.9852,0.0704 T0.9932,0.0697 Q0.9997,0.0693,0.9942,0.0567 Q0.9923,0.0522,0.989,0.0469 Z",type:0}},"\uE0C4":{type:5,data:{d:"M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0.9099,0.1158 L0.9099,0.1888 L0.9951,0.1888 L0.9951,0.1158 L0.9099,0.1158 Z M0.9075,0.1116 L0.9976,0.1116 Q1,0.1116,1,0.1137 L1,0.1909 Q1,0.193,0.9976,0.193 L0.9075,0.193 Q0.905,0.193,0.905,0.1909 L0.905,0.1137 Q0.905,0.1116,0.9075,0.1116 Z M0.7852,0.6086 L0.7852,0.682 L0.8704,0.682 L0.8704,0.6086 L0.7852,0.6086 Z M0.7828,0.6044 L0.8729,0.6044 Q0.8753,0.6044,0.8753,0.6065 L0.8753,0.6841 Q0.8753,0.6862,0.8729,0.6862 L0.7828,0.6862 Q0.7803,0.6862,0.7803,0.6841 L0.7803,0.6065 Q0.7803,0.6044,0.7828,0.6044 Z M0.66,0.1137 L0.7501,0.1137 L0.7501,0.1909 L0.66,0.1909 L0.66,0.1137 Z M0.6624,0.4102 L0.6624,0.4836 L0.7477,0.4836 L0.7477,0.4102 L0.6624,0.4102 Z M0.66,0.406 L0.7501,0.406 Q0.7526,0.406,0.7526,0.4081 L0.7526,0.4857 Q0.7526,0.4878,0.7501,0.4878 L0.66,0.4878 Q0.6576,0.4878,0.6576,0.4857 L0.6576,0.4081 Q0.6576,0.406,0.66,0.406 Z M0.6624,0.7131 L0.6624,0.7865 L0.7477,0.7865 L0.7477,0.7131 L0.6624,0.7131 Z M0.66,0.7089 L0.7501,0.7089 Q0.7526,0.7089,0.7526,0.711 L0.7526,0.7886 Q0.7526,0.7907,0.7501,0.7907 L0.66,0.7907 Q0.6576,0.7907,0.6576,0.7886 L0.6576,0.711 Q0.6576,0.7089,0.66,0.7089 Z M0.5407,0.2227 L0.5407,0.2836 L0.6113,0.2836 L0.6113,0.2227 L0.5407,0.2227 Z M0.5309,0.206 L0.621,0.206 Q0.623,0.206,0.6249,0.2068 T0.6281,0.2089 T0.6301,0.2116 T0.6308,0.2143 L0.6308,0.2919 Q0.6308,0.2957,0.6276,0.298 T0.621,0.3003 L0.5309,0.3003 Q0.5265,0.3003,0.5239,0.2976 T0.5212,0.2919 L0.5212,0.2143 Q0.5212,0.211,0.5244,0.2085 T0.5309,0.206 Z M0.5407,0.3184 L0.5407,0.3792 L0.6113,0.3792 L0.6113,0.3184 L0.5407,0.3184 Z M0.5309,0.3016 L0.621,0.3016 Q0.6254,0.3016,0.6281,0.3043 T0.6308,0.31 L0.6308,0.3876 Q0.6308,0.3914,0.6276,0.3937 T0.621,0.396 L0.5309,0.396 Q0.5265,0.396,0.5239,0.3932 T0.5212,0.3876 L0.5212,0.31 Q0.5212,0.3062,0.5244,0.3039 T0.5309,0.3016 Z M0.5309,0.4081 L0.621,0.4081 L0.621,0.4857 L0.5309,0.4857 L0.5309,0.4081 Z M0.5309,0.5059 L0.621,0.5059 L0.621,0.5835 L0.5309,0.5835 L0.5309,0.5059 Z M0.5407,0.6149 L0.5407,0.6758 L0.6113,0.6758 L0.6113,0.6149 L0.5407,0.6149 Z M0.5309,0.5982 L0.621,0.5982 Q0.6254,0.5982,0.6281,0.6009 T0.6308,0.6065 L0.6308,0.6841 Q0.6308,0.6879,0.6276,0.6902 T0.621,0.6925 L0.5309,0.6925 Q0.5265,0.6925,0.5239,0.6898 T0.5212,0.6841 L0.5212,0.6065 Q0.5212,0.6028,0.5244,0.6005 T0.5309,0.5982 Z M0.4062,0.1137 L0.4963,0.1137 L0.4963,0.1909 L0.4062,0.1909 L0.4062,0.1137 Z M0.4062,0.2143 L0.4963,0.2143 L0.4963,0.2919 L0.4062,0.2919 L0.4062,0.2143 Z M0.4184,0.4186 L0.4184,0.4753 L0.4842,0.4753 L0.4842,0.4186 L0.4184,0.4186 Z M0.4062,0.3977 L0.4963,0.3977 Q0.5017,0.3977,0.5051,0.401 T0.5085,0.4081 L0.5085,0.4857 Q0.5085,0.4904,0.5046,0.4933 T0.4963,0.4962 L0.4062,0.4962 Q0.4009,0.4962,0.3975,0.4929 T0.3941,0.4857 L0.3941,0.4081 Q0.3941,0.4035,0.398,0.4006 T0.4062,0.3977 Z M0.4062,0.5059 L0.4963,0.5059 L0.4963,0.5835 L0.4062,0.5835 L0.4062,0.5059 Z M0.4087,0.7131 L0.4087,0.7865 L0.4939,0.7865 L0.4939,0.7131 L0.4087,0.7131 Z M0.4062,0.7089 L0.4963,0.7089 Q0.4988,0.7089,0.4988,0.711 L0.4988,0.7886 Q0.4988,0.7907,0.4963,0.7907 L0.4062,0.7907 Q0.4038,0.7907,0.4038,0.7886 L0.4038,0.711 Q0.4038,0.7089,0.4062,0.7089 Z M0.2689,0.1137 L0.359,0.1137 L0.359,0.1909 L0.2689,0.1909 L0.2689,0.1137 Z M0.2689,0.2143 L0.359,0.2143 L0.359,0.2919 L0.2689,0.2919 L0.2689,0.2143 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.31 L0.359,0.31 L0.359,0.3876 L0.2689,0.3876 L0.2689,0.31 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.4081 L0.359,0.4081 L0.359,0.4857 L0.2689,0.4857 L0.2689,0.4081 Z M0.2689,0.5059 L0.359,0.5059 L0.359,0.5835 L0.2689,0.5835 L0.2689,0.5059 Z M0.2689,0.711 L0.359,0.711 L0.359,0.7886 L0.2689,0.7886 L0.2689,0.711 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.1374,0.1137 L0.2275,0.1137 L0.2275,0.1909 L0.1374,0.1909 L0.1374,0.1137 Z M0.1374,0.2143 L0.2275,0.2143 L0.2275,0.2919 L0.1374,0.2919 L0.1374,0.2143 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.31 L0.2275,0.31 L0.2275,0.3876 L0.1374,0.3876 L0.1374,0.31 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.4081 L0.2275,0.4081 L0.2275,0.4857 L0.1374,0.4857 L0.1374,0.4081 Z M0.1374,0.5059 L0.2275,0.5059 L0.2275,0.5835 L0.1374,0.5835 L0.1374,0.5059 Z M0.1374,0.6065 L0.2275,0.6065 L0.2275,0.6841 L0.1374,0.6841 L0.1374,0.6065 Z M0.1374,0.711 L0.2275,0.711 L0.2275,0.7886 L0.1374,0.7886 L0.1374,0.711 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0,0.1137 L0.0901,0.1137 L0.0901,0.1909 L0,0.1909 L0,0.1137 Z M0,0.2143 L0.0901,0.2143 L0.0901,0.2919 L0,0.2919 L0,0.2143 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.31 L0.0901,0.31 L0.0901,0.3876 L0,0.3876 L0,0.31 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.4081 L0.0901,0.4081 L0.0901,0.4857 L0,0.4857 L0,0.4081 Z M0,0.5059 L0.0901,0.5059 L0.0901,0.5835 L0,0.5835 L0,0.5059 Z M0,0.6065 L0.0901,0.6065 L0.0901,0.6841 L0,0.6841 L0,0.6065 Z M0,0.711 L0.0901,0.711 L0.0901,0.7886 L0,0.7886 L0,0.711 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z",type:0}},"\uE0C5":{type:5,data:{d:"M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M0.0901,0.1158 L0.0901,0.1888 L0.0049,0.1888 L0.0049,0.1158 L0.0901,0.1158 Z M0.0925,0.1116 L0.0024,0.1116 Q0,0.1116,0,0.1137 L0,0.1909 Q0,0.193,0.0024,0.193 L0.0925,0.193 Q0.095,0.193,0.095,0.1909 L0.095,0.1137 Q0.095,0.1116,0.0925,0.1116 Z M0.2148,0.6086 L0.2148,0.682 L0.1296,0.682 L0.1296,0.6086 L0.2148,0.6086 Z M0.2172,0.6044 L0.1271,0.6044 Q0.1247,0.6044,0.1247,0.6065 L0.1247,0.6841 Q0.1247,0.6862,0.1271,0.6862 L0.2172,0.6862 Q0.2197,0.6862,0.2197,0.6841 L0.2197,0.6065 Q0.2197,0.6044,0.2172,0.6044 Z M0.34,0.1137 L0.2499,0.1137 L0.2499,0.1909 L0.34,0.1909 L0.34,0.1137 Z M0.3376,0.4102 L0.3376,0.4836 L0.2523,0.4836 L0.2523,0.4102 L0.3376,0.4102 Z M0.34,0.406 L0.2499,0.406 Q0.2474,0.406,0.2474,0.4081 L0.2474,0.4857 Q0.2474,0.4878,0.2499,0.4878 L0.34,0.4878 Q0.3424,0.4878,0.3424,0.4857 L0.3424,0.4081 Q0.3424,0.406,0.34,0.406 Z M0.3376,0.7131 L0.3376,0.7865 L0.2523,0.7865 L0.2523,0.7131 L0.3376,0.7131 Z M0.34,0.7089 L0.2499,0.7089 Q0.2474,0.7089,0.2474,0.711 L0.2474,0.7886 Q0.2474,0.7907,0.2499,0.7907 L0.34,0.7907 Q0.3424,0.7907,0.3424,0.7886 L0.3424,0.711 Q0.3424,0.7089,0.34,0.7089 Z M0.4593,0.2227 L0.4593,0.2836 L0.3887,0.2836 L0.3887,0.2227 L0.4593,0.2227 Z M0.4691,0.206 L0.379,0.206 Q0.3746,0.206,0.3719,0.2087 T0.3692,0.2143 L0.3692,0.2919 Q0.3692,0.2957,0.3724,0.298 T0.379,0.3003 L0.4691,0.3003 Q0.4735,0.3003,0.4761,0.2976 T0.4788,0.2919 L0.4788,0.2143 Q0.4788,0.211,0.4756,0.2085 T0.4691,0.206 Z M0.4593,0.3184 L0.4593,0.3792 L0.3887,0.3792 L0.3887,0.3184 L0.4593,0.3184 Z M0.4691,0.3016 L0.379,0.3016 Q0.3746,0.3016,0.3719,0.3043 T0.3692,0.31 L0.3692,0.3876 Q0.3692,0.3914,0.3724,0.3937 T0.379,0.396 L0.4691,0.396 Q0.4735,0.396,0.4761,0.3932 T0.4788,0.3876 L0.4788,0.31 Q0.4788,0.3062,0.4756,0.3039 T0.4691,0.3016 Z M0.4691,0.4081 L0.379,0.4081 L0.379,0.4857 L0.4691,0.4857 L0.4691,0.4081 Z M0.4691,0.5059 L0.379,0.5059 L0.379,0.5835 L0.4691,0.5835 L0.4691,0.5059 Z M0.4593,0.6149 L0.4593,0.6758 L0.3887,0.6758 L0.3887,0.6149 L0.4593,0.6149 Z M0.4691,0.5982 L0.379,0.5982 Q0.3746,0.5982,0.3719,0.6009 T0.3692,0.6065 L0.3692,0.6841 Q0.3692,0.6879,0.3724,0.6902 T0.379,0.6925 L0.4691,0.6925 Q0.4735,0.6925,0.4761,0.6898 T0.4788,0.6841 L0.4788,0.6065 Q0.4788,0.6028,0.4756,0.6005 T0.4691,0.5982 Z M0.5938,0.1137 L0.5037,0.1137 L0.5037,0.1909 L0.5938,0.1909 L0.5938,0.1137 Z M0.5938,0.2143 L0.5037,0.2143 L0.5037,0.2919 L0.5938,0.2919 L0.5938,0.2143 Z M0.5816,0.4186 L0.5816,0.4753 L0.5158,0.4753 L0.5158,0.4186 L0.5816,0.4186 Z M0.5938,0.3977 L0.5037,0.3977 Q0.4983,0.3977,0.4949,0.401 T0.4915,0.4081 L0.4915,0.4857 Q0.4915,0.4904,0.4954,0.4933 T0.5037,0.4962 L0.5938,0.4962 Q0.5991,0.4962,0.6025,0.4929 T0.6059,0.4857 L0.6059,0.4081 Q0.6059,0.4035,0.602,0.4006 T0.5938,0.3977 Z M0.5938,0.5059 L0.5037,0.5059 L0.5037,0.5835 L0.5938,0.5835 L0.5938,0.5059 Z M0.5913,0.7131 L0.5913,0.7865 L0.5061,0.7865 L0.5061,0.7131 L0.5913,0.7131 Z M0.5938,0.7089 L0.5037,0.7089 Q0.5012,0.7089,0.5012,0.711 L0.5012,0.7886 Q0.5012,0.7907,0.5037,0.7907 L0.5938,0.7907 Q0.5962,0.7907,0.5962,0.7886 L0.5962,0.711 Q0.5962,0.7089,0.5938,0.7089 Z M0.7311,0.1137 L0.641,0.1137 L0.641,0.1909 L0.7311,0.1909 L0.7311,0.1137 Z M0.7311,0.2143 L0.641,0.2143 L0.641,0.2919 L0.7311,0.2919 L0.7311,0.2143 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.31 L0.641,0.31 L0.641,0.3876 L0.7311,0.3876 L0.7311,0.31 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.4081 L0.641,0.4081 L0.641,0.4857 L0.7311,0.4857 L0.7311,0.4081 Z M0.7311,0.5059 L0.641,0.5059 L0.641,0.5835 L0.7311,0.5835 L0.7311,0.5059 Z M0.7311,0.711 L0.641,0.711 L0.641,0.7886 L0.7311,0.7886 L0.7311,0.711 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.8626,0.1137 L0.7725,0.1137 L0.7725,0.1909 L0.8626,0.1909 L0.8626,0.1137 Z M0.8626,0.2143 L0.7725,0.2143 L0.7725,0.2919 L0.8626,0.2919 L0.8626,0.2143 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.31 L0.7725,0.31 L0.7725,0.3876 L0.8626,0.3876 L0.8626,0.31 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.4081 L0.7725,0.4081 L0.7725,0.4857 L0.8626,0.4857 L0.8626,0.4081 Z M0.8626,0.5059 L0.7725,0.5059 L0.7725,0.5835 L0.8626,0.5835 L0.8626,0.5059 Z M0.8626,0.6065 L0.7725,0.6065 L0.7725,0.6841 L0.8626,0.6841 L0.8626,0.6065 Z M0.8626,0.711 L0.7725,0.711 L0.7725,0.7886 L0.8626,0.7886 L0.8626,0.711 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M1,0.1137 L0.9099,0.1137 L0.9099,0.1909 L1,0.1909 L1,0.1137 Z M1,0.2143 L0.9099,0.2143 L0.9099,0.2919 L1,0.2919 L1,0.2143 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.31 L0.9099,0.31 L0.9099,0.3876 L1,0.3876 L1,0.31 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.4081 L0.9099,0.4081 L0.9099,0.4857 L1,0.4857 L1,0.4081 Z M1,0.5059 L0.9099,0.5059 L0.9099,0.5835 L1,0.5835 L1,0.5059 Z M1,0.6065 L0.9099,0.6065 L0.9099,0.6841 L1,0.6841 L1,0.6065 Z M1,0.711 L0.9099,0.711 L0.9099,0.7886 L1,0.7886 L1,0.711 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z",type:0}},"\uE0C6":{type:5,data:{d:"M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0.8574,0.3217 L0.8574,0.4098 L0.9691,0.4098 L0.9691,0.3217 L0.8574,0.3217 Z M0.8419,0.2978 L0.9846,0.2978 Q0.9892,0.2978,0.9928,0.2997 T0.9982,0.3043 T1,0.31 L1,0.4216 Q1,0.427,0.9951,0.4304 T0.9846,0.4337 L0.8419,0.4337 Q0.8352,0.4337,0.8308,0.4299 T0.8265,0.4216 L0.8265,0.31 Q0.8265,0.3062,0.829,0.3035 T0.835,0.2993 T0.8419,0.2978 Z M0.8574,0.888 L0.8574,0.9757 L0.9691,0.9757 L0.9691,0.888 L0.8574,0.888 Z M0.8419,0.8637 L0.9846,0.8637 Q0.9892,0.8637,0.9928,0.8658 T0.9982,0.8706 T1,0.8758 L1,0.9878 Q1,0.9929,0.9951,0.9964 T0.9846,1 L0.8419,1 Q0.8352,1,0.8308,0.996 T0.8265,0.9878 L0.8265,0.8758 Q0.8265,0.8708,0.8314,0.8672 T0.8419,0.8637 Z M0.6509,0.169 L0.6509,0.2706 L0.7806,0.2706 L0.7806,0.169 L0.6509,0.169 Z M0.6442,0.159 L0.7868,0.159 Q0.7899,0.159,0.7917,0.1604 T0.7935,0.164 L0.7935,0.2756 Q0.7935,0.2781,0.7915,0.2796 T0.7868,0.281 L0.6442,0.281 Q0.6416,0.281,0.6395,0.2794 T0.6375,0.2756 L0.6375,0.164 Q0.6375,0.1619,0.6398,0.1604 T0.6442,0.159 Z M0.6632,0.6044 L0.6632,0.6858 L0.7678,0.6858 L0.7678,0.6044 L0.6632,0.6044 Z M0.6442,0.5742 L0.7868,0.5742 Q0.7951,0.5742,0.8007,0.5791 T0.8064,0.5893 L0.8064,0.7013 Q0.8064,0.7076,0.8002,0.712 T0.7868,0.7164 L0.6442,0.7164 Q0.6359,0.7164,0.6305,0.7114 T0.6251,0.7013 L0.6251,0.5893 Q0.6251,0.5847,0.6282,0.5812 T0.6357,0.5759 T0.6442,0.5742 Z M0.4264,0.164 L0.5695,0.164 L0.5695,0.2756 L0.4264,0.2756 L0.4264,0.164 Z M0.4325,0.315 L0.4325,0.4165 L0.5628,0.4165 L0.5628,0.315 L0.4325,0.315 Z M0.4264,0.3049 L0.5695,0.3049 Q0.5721,0.3049,0.5739,0.3064 T0.5757,0.31 L0.5757,0.4216 Q0.5757,0.4237,0.5736,0.4251 T0.5695,0.4266 L0.4264,0.4266 Q0.4238,0.4266,0.422,0.4251 T0.4202,0.4216 L0.4202,0.31 Q0.4202,0.3087,0.4207,0.3077 T0.4222,0.306 T0.4243,0.3052 T0.4264,0.3049 Z M0.4264,0.4476 L0.5695,0.4476 L0.5695,0.5596 L0.4264,0.5596 L0.4264,0.4476 Z M0.4392,0.7408 L0.4392,0.8326 L0.5561,0.8326 L0.5561,0.7408 L0.4392,0.7408 Z M0.4264,0.7206 L0.5695,0.7206 Q0.5747,0.7206,0.5783,0.724 T0.5819,0.7307 L0.5819,0.8427 Q0.5819,0.8469,0.5778,0.8498 T0.5695,0.8528 L0.4264,0.8528 Q0.4207,0.8528,0.4171,0.8496 T0.4135,0.8427 L0.4135,0.7307 Q0.4135,0.7265,0.4176,0.7236 T0.4264,0.7206 Z M0.2178,0.164 L0.3605,0.164 L0.3605,0.2756 L0.2178,0.2756 L0.2178,0.164 Z M0.2178,0.31 L0.3605,0.31 L0.3605,0.4216 L0.2178,0.4216 L0.2178,0.31 Z M0.2178,0.5893 L0.3605,0.5893 L0.3605,0.7013 L0.2178,0.7013 L0.2178,0.5893 Z M0.2178,0.7307 L0.3605,0.7307 L0.3605,0.8427 L0.2178,0.8427 L0.2178,0.7307 Z M0.2178,0.8758 L0.3605,0.8758 L0.3605,0.9878 L0.2178,0.9878 L0.2178,0.8758 Z M0,0.164 L0.1432,0.164 L0.1432,0.2756 L0,0.2756 L0,0.164 Z M0,0.31 L0.1432,0.31 L0.1432,0.4216 L0,0.4216 L0,0.31 Z M0,0.4476 L0.1432,0.4476 L0.1432,0.5596 L0,0.5596 L0,0.4476 Z M0,0.5893 L0.1432,0.5893 L0.1432,0.7013 L0,0.7013 L0,0.5893 Z M0,0.7307 L0.1432,0.7307 L0.1432,0.8427 L0,0.8427 L0,0.7307 Z M0,0.8758 L0.1432,0.8758 L0.1432,0.9878 L0,0.9878 L0,0.8758 Z",type:0}},"\uE0C7":{type:5,data:{d:"M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M0.1432,0.3217 L0.1432,0.4098 L0.0309,0.4098 L0.0309,0.3217 L0.1432,0.3217 Z M0.1586,0.2978 L0.0154,0.2978 Q0.0088,0.2978,0.0044,0.3016 T0,0.31 L0,0.4216 Q0,0.427,0.0049,0.4304 T0.0154,0.4337 L0.1586,0.4337 Q0.1648,0.4337,0.1694,0.4299 T0.174,0.4216 L0.174,0.31 Q0.174,0.3062,0.1715,0.3035 T0.1653,0.2993 T0.1586,0.2978 Z M0.1432,0.888 L0.1432,0.9757 L0.0309,0.9757 L0.0309,0.888 L0.1432,0.888 Z M0.1586,0.8637 L0.0154,0.8637 Q0.0108,0.8637,0.0072,0.8658 T0.0018,0.8706 T0,0.8758 L0,0.9878 Q0,0.9929,0.0049,0.9964 T0.0154,1 L0.1586,1 Q0.1648,1,0.1694,0.996 T0.174,0.9878 L0.174,0.8758 Q0.174,0.8725,0.1715,0.8695 T0.1653,0.8651 T0.1586,0.8637 Z M0.3496,0.169 L0.3496,0.2706 L0.2194,0.2706 L0.2194,0.169 L0.3496,0.169 Z M0.3563,0.159 L0.2132,0.159 Q0.2106,0.159,0.2088,0.1604 T0.207,0.164 L0.207,0.2756 Q0.207,0.2781,0.2091,0.2796 T0.2132,0.281 L0.3563,0.281 Q0.3589,0.281,0.3607,0.2794 T0.3625,0.2756 L0.3625,0.164 Q0.3625,0.1619,0.3605,0.1604 T0.3563,0.159 Z M0.3368,0.6044 L0.3368,0.6858 L0.2327,0.6858 L0.2327,0.6044 L0.3368,0.6044 Z M0.3563,0.5742 L0.2132,0.5742 Q0.2049,0.5742,0.1993,0.5791 T0.1936,0.5893 L0.1936,0.7013 Q0.1936,0.7076,0.2001,0.712 T0.2132,0.7164 L0.3563,0.7164 Q0.3641,0.7164,0.3697,0.7114 T0.3754,0.7013 L0.3754,0.5893 Q0.3754,0.5847,0.3723,0.5812 T0.3648,0.5759 T0.3563,0.5742 Z M0.5736,0.164 L0.431,0.164 L0.431,0.2756 L0.5736,0.2756 L0.5736,0.164 Z M0.5675,0.315 L0.5675,0.4165 L0.4377,0.4165 L0.4377,0.315 L0.5675,0.315 Z M0.5736,0.3049 L0.431,0.3049 Q0.4284,0.3049,0.4264,0.3064 T0.4243,0.31 L0.4243,0.4216 Q0.4243,0.4237,0.4266,0.4251 T0.431,0.4266 L0.5736,0.4266 Q0.5767,0.4266,0.5785,0.4251 T0.5803,0.4216 L0.5803,0.31 Q0.5803,0.3087,0.5798,0.3077 T0.5783,0.306 T0.5762,0.3052 T0.5736,0.3049 Z M0.5736,0.4476 L0.431,0.4476 L0.431,0.5596 L0.5736,0.5596 L0.5736,0.4476 Z M0.5613,0.7408 L0.5613,0.8326 L0.4439,0.8326 L0.4439,0.7408 L0.5613,0.7408 Z M0.5736,0.7206 L0.431,0.7206 Q0.4253,0.7206,0.4217,0.724 T0.4181,0.7307 L0.4181,0.8427 Q0.4181,0.8469,0.4222,0.8498 T0.431,0.8528 L0.5736,0.8528 Q0.5778,0.8528,0.5808,0.8511 T0.5855,0.8471 T0.587,0.8427 L0.587,0.7307 Q0.587,0.7265,0.5826,0.7236 T0.5736,0.7206 Z M0.7827,0.164 L0.6395,0.164 L0.6395,0.2756 L0.7827,0.2756 L0.7827,0.164 Z M0.7827,0.31 L0.6395,0.31 L0.6395,0.4216 L0.7827,0.4216 L0.7827,0.31 Z M0.7827,0.5893 L0.6395,0.5893 L0.6395,0.7013 L0.7827,0.7013 L0.7827,0.5893 Z M0.7827,0.7307 L0.6395,0.7307 L0.6395,0.8427 L0.7827,0.8427 L0.7827,0.7307 Z M0.7827,0.8758 L0.6395,0.8758 L0.6395,0.9878 L0.7827,0.9878 L0.7827,0.8758 Z M1,0.164 L0.8574,0.164 L0.8574,0.2756 L1,0.2756 L1,0.164 Z M1,0.31 L0.8574,0.31 L0.8574,0.4216 L1,0.4216 L1,0.31 Z M1,0.4476 L0.8574,0.4476 L0.8574,0.5596 L1,0.5596 L1,0.4476 Z M1,0.5893 L0.8574,0.5893 L0.8574,0.7013 L1,0.7013 L1,0.5893 Z M1,0.7307 L0.8574,0.7307 L0.8574,0.8427 L1,0.8427 L1,0.7307 Z M1,0.8758 L0.8574,0.8758 L0.8574,0.9878 L1,0.9878 L1,0.8758 Z",type:0}},"\uE0C8":{type:5,data:{d:"M0.0008,0.9979 Q0.0008,1,0.0032,1 Q0.0482,1,0.0602,0.9975 Q0.0667,0.9963,0.0847,0.9917 T0.1084,0.9859 Q0.1124,0.9851,0.1221,0.9851 L0.1253,0.9851 L0.1285,0.9851 Q0.1518,0.9851,0.1655,0.9809 Q0.1695,0.9801,0.1731,0.9788 T0.1791,0.9766 T0.1839,0.9747 T0.1888,0.9732 L0.1936,0.972 T0.1988,0.9712 T0.2048,0.971 L0.2096,0.971 Q0.2201,0.9714,0.2233,0.9714 T0.2345,0.9693 Q0.2386,0.9685,0.2502,0.9674 T0.2715,0.966 L0.2811,0.9656 Q0.2916,0.9647,0.2916,0.9573 Q0.2916,0.954,0.2892,0.9515 T0.2819,0.9486 Q0.2755,0.9482,0.2683,0.9482 Q0.2643,0.9482,0.2582,0.9484 T0.2498,0.9486 Q0.2426,0.9486,0.2402,0.9473 Q0.2386,0.9469,0.2317,0.943 T0.2161,0.937 Q0.2104,0.9357,0.1992,0.933 T0.1831,0.9291 Q0.1807,0.9287,0.1783,0.9285 T0.1739,0.928 T0.1703,0.9276 T0.1675,0.9272 T0.1655,0.9266 T0.1639,0.9253 Q0.1598,0.9216,0.1462,0.9216 L0.143,0.9216 L0.1398,0.9216 L0.1365,0.9216 Q0.1197,0.9195,0.1197,0.9154 L0.1197,0.9154 Q0.1205,0.9121,0.1373,0.9088 T0.1703,0.9054 Q0.1791,0.9054,0.1863,0.904 T0.1984,0.9011 T0.2064,0.8992 Q0.2402,0.8967,0.2506,0.8934 Q0.2538,0.8926,0.2602,0.8899 T0.2707,0.8872 Q0.2715,0.8872,0.2731,0.8876 Q0.2795,0.8884,0.2884,0.8884 Q0.3068,0.8884,0.3092,0.8826 L0.3092,0.8797 Q0.3092,0.8772,0.3076,0.8747 T0.3044,0.8706 T0.3028,0.8681 Q0.3076,0.8656,0.3237,0.8656 Q0.3317,0.8656,0.3622,0.8623 Q0.3655,0.8623,0.3867,0.8617 T0.4217,0.8602 Q0.4241,0.8598,0.4289,0.8598 L0.4345,0.8598 L0.441,0.8598 Q0.4578,0.8598,0.4667,0.8586 Q0.4739,0.8573,0.4851,0.8567 T0.5004,0.8552 Q0.5012,0.8552,0.5108,0.855 T0.5269,0.853 T0.5333,0.8461 Q0.5333,0.8445,0.5325,0.8432 T0.5309,0.8409 T0.5289,0.8391 T0.5261,0.8378 L0.5229,0.837 T0.5185,0.8362 T0.5133,0.8355 T0.5076,0.8349 T0.502,0.8341 Q0.4932,0.8324,0.4859,0.8324 Q0.4827,0.8324,0.4787,0.8328 T0.4731,0.8333 Q0.4707,0.8333,0.4683,0.8324 Q0.4482,0.825,0.4394,0.825 Q0.4369,0.825,0.4345,0.8254 Q0.4225,0.827,0.4153,0.827 Q0.4088,0.827,0.4064,0.8258 Q0.3976,0.8204,0.3751,0.8204 Q0.3703,0.8204,0.3667,0.8214 T0.3622,0.8225 Q0.3598,0.8225,0.3558,0.82 Q0.351,0.8175,0.3402,0.8167 T0.3177,0.8156 T0.3028,0.8146 L0.2956,0.8121 T0.2871,0.8094 T0.2771,0.8073 T0.2635,0.8063 Q0.261,0.8063,0.2586,0.8067 Q0.2378,0.8071,0.2273,0.8071 Q0.2072,0.8071,0.2024,0.8051 Q0.1976,0.8034,0.1932,0.8026 T0.1867,0.8013 T0.1847,0.8001 L0.1847,0.7993 Q0.1855,0.7984,0.1855,0.7978 T0.1859,0.7966 T0.1867,0.7959 T0.188,0.7955 T0.1896,0.7951 T0.1916,0.7949 T0.1944,0.7945 T0.1984,0.7941 T0.2032,0.7939 Q0.212,0.7926,0.2325,0.7918 T0.2562,0.7905 Q0.2691,0.7893,0.2787,0.7847 Q0.2835,0.7827,0.2968,0.7791 T0.3149,0.7756 Q0.3189,0.7756,0.3305,0.7733 T0.3446,0.771 L0.3454,0.771 Q0.3462,0.771,0.3498,0.7721 T0.3574,0.774 T0.3655,0.7748 Q0.3703,0.7748,0.3743,0.7731 T0.3823,0.7708 T0.3916,0.77 T0.3992,0.7694 Q0.4008,0.7694,0.4076,0.7688 T0.4205,0.7675 L0.4305,0.7665 Q0.4345,0.7657,0.4402,0.7617 T0.4458,0.7545 Q0.4458,0.7503,0.4353,0.7487 Q0.4297,0.7478,0.4209,0.7462 T0.4044,0.7433 T0.3904,0.7416 L0.3847,0.7416 Q0.3807,0.7416,0.3747,0.7422 T0.3663,0.7428 T0.3606,0.7424 Q0.3582,0.742,0.3562,0.7418 T0.3522,0.7412 T0.3482,0.7406 T0.3446,0.7401 T0.341,0.7397 T0.3373,0.7393 T0.3341,0.7391 L0.3293,0.7391 Q0.3277,0.7395,0.3253,0.7395 Q0.3205,0.7395,0.3052,0.7387 T0.2835,0.7379 Q0.2755,0.7379,0.253,0.7325 Q0.2506,0.7316,0.2462,0.731 T0.2382,0.7296 T0.2305,0.7271 T0.2181,0.7234 T0.2048,0.7196 T0.2,0.7155 Q0.2,0.7142,0.2016,0.713 Q0.208,0.7059,0.2285,0.6978 T0.2618,0.6898 Q0.2739,0.6898,0.2831,0.6848 T0.2956,0.6794 Q0.3028,0.6786,0.3289,0.6765 T0.3566,0.674 Q0.3582,0.674,0.3855,0.6723 T0.4406,0.6688 T0.4755,0.6653 Q0.5004,0.6607,0.5124,0.6599 L0.5165,0.6599 Q0.5213,0.6599,0.5277,0.6605 T0.5373,0.6611 Q0.543,0.6611,0.5494,0.6599 Q0.592,0.6524,0.6,0.6524 Q0.6064,0.6524,0.61,0.6499 T0.6161,0.6474 Q0.6177,0.6474,0.6261,0.6495 T0.6418,0.6516 L0.6482,0.6516 Q0.653,0.6512,0.6651,0.6506 T0.6876,0.6493 T0.7084,0.6477 T0.7229,0.645 Q0.7269,0.6437,0.7333,0.6437 Q0.7373,0.6437,0.7458,0.6443 T0.7574,0.645 Q0.7614,0.645,0.7631,0.6445 T0.7655,0.6441 Q0.7671,0.6441,0.7723,0.6448 T0.7823,0.6454 Q0.788,0.6454,0.7952,0.6445 Q0.8233,0.6408,0.8723,0.6367 Q0.9012,0.6342,0.9092,0.6342 L0.9165,0.6342 L0.9205,0.6342 Q0.9277,0.6342,0.9382,0.6323 T0.9526,0.6304 Q0.9534,0.6304,0.9594,0.6307 T0.9695,0.6309 Q0.9976,0.6309,0.9992,0.6234 Q1,0.6201,1,0.618 Q1,0.6089,0.9775,0.608 Q0.9703,0.6076,0.9582,0.6076 Q0.9518,0.6076,0.9394,0.6078 T0.9213,0.608 Q0.9036,0.608,0.8972,0.6072 Q0.894,0.6068,0.8871,0.6058 T0.8751,0.6039 T0.8627,0.6024 T0.8506,0.6018 L0.8474,0.6018 Q0.841,0.6022,0.8281,0.6041 T0.8112,0.606 Q0.8096,0.606,0.808,0.6056 Q0.7783,0.6002,0.7647,0.6002 Q0.7598,0.6002,0.7454,0.6006 T0.7261,0.601 Q0.7221,0.601,0.7145,0.6022 T0.7036,0.6035 Q0.6996,0.6035,0.6964,0.6022 Q0.6723,0.5902,0.6442,0.5902 Q0.6426,0.5902,0.6386,0.5904 T0.6337,0.5906 Q0.6273,0.5906,0.6249,0.5894 L0.6177,0.5856 T0.6116,0.5838 T0.6016,0.5825 T0.5855,0.5823 Q0.5719,0.5823,0.5574,0.5798 T0.5373,0.5774 L0.5341,0.5774 Q0.5293,0.5778,0.5213,0.5786 T0.51,0.5794 Q0.5036,0.5794,0.4988,0.5769 Q0.4932,0.574,0.4771,0.574 Q0.4723,0.574,0.4631,0.5745 T0.4498,0.5749 Q0.4418,0.5749,0.4394,0.5736 Q0.4353,0.5724,0.4305,0.5715 T0.4225,0.5707 T0.4145,0.5705 T0.4056,0.5699 Q0.4008,0.5691,0.398,0.5689 T0.394,0.5689 T0.3908,0.5686 T0.3855,0.5674 Q0.3831,0.567,0.3811,0.5662 T0.3779,0.5645 T0.3743,0.563 T0.3695,0.5624 Q0.3655,0.5624,0.3594,0.5635 T0.3494,0.5645 T0.3406,0.5635 T0.3325,0.5624 Q0.3309,0.5624,0.3301,0.5628 L0.3141,0.5628 Q0.2635,0.5628,0.2506,0.5612 Q0.2474,0.5608,0.2434,0.5608 Q0.2402,0.5608,0.2349,0.561 T0.2273,0.5612 Q0.2209,0.5612,0.2177,0.5595 Q0.2145,0.557,0.2145,0.5558 Q0.2169,0.5537,0.2345,0.5521 Q0.2876,0.5475,0.3028,0.5425 Q0.3068,0.5413,0.3149,0.5396 T0.3309,0.5367 T0.3478,0.5338 T0.3606,0.5309 Q0.3631,0.5305,0.3755,0.5259 T0.3968,0.5214 Q0.3992,0.5214,0.4024,0.5218 T0.4096,0.5222 Q0.4161,0.5222,0.4213,0.5209 T0.4313,0.5185 T0.4402,0.5172 Q0.4418,0.5168,0.447,0.5168 L0.4558,0.5168 T0.4639,0.5162 T0.4707,0.5141 T0.4731,0.5102 Q0.4731,0.5089,0.4723,0.5081 Q0.4699,0.5019,0.4627,0.4994 T0.4369,0.4965 Q0.4273,0.4961,0.4193,0.4946 T0.4048,0.4915 T0.3952,0.4894 Q0.392,0.4886,0.388,0.4886 Q0.3847,0.4886,0.3775,0.4892 T0.3647,0.4898 Q0.3598,0.4898,0.355,0.4894 Q0.3165,0.4861,0.3141,0.4853 Q0.2779,0.4774,0.2715,0.4774 Q0.2699,0.4774,0.2683,0.4776 T0.2627,0.4778 Q0.2498,0.4778,0.2281,0.4762 Q0.2241,0.4749,0.2233,0.4737 Q0.2249,0.472,0.2321,0.4708 Q0.2402,0.4695,0.2554,0.4695 L0.2675,0.4695 L0.2795,0.4695 Q0.294,0.4695,0.2996,0.4679 Q0.3141,0.4629,0.3373,0.4616 Q0.3414,0.4612,0.3466,0.4606 T0.3582,0.4589 T0.3687,0.4562 T0.3727,0.4525 Q0.3727,0.4509,0.3703,0.4492 Q0.3639,0.4455,0.3474,0.4419 T0.3213,0.4384 L0.3157,0.4384 L0.31,0.4384 Q0.294,0.4384,0.2859,0.4363 Q0.2795,0.4347,0.2691,0.4347 L0.2618,0.4347 L0.2546,0.4347 Q0.2458,0.4347,0.2394,0.4338 Q0.2257,0.4318,0.2193,0.4303 T0.2092,0.4266 T0.2052,0.4224 T0.2024,0.4154 T0.196,0.4052 Q0.1912,0.3994,0.1867,0.3949 T0.1807,0.3884 T0.1791,0.3849 Q0.1791,0.3824,0.1839,0.3745 Q0.1871,0.3687,0.1944,0.3637 T0.2072,0.3561 T0.2221,0.3501 T0.2321,0.3463 Q0.2353,0.3455,0.241,0.3445 T0.2522,0.3428 T0.2639,0.3411 T0.2731,0.3384 T0.2956,0.3351 T0.3197,0.3326 Q0.3213,0.3322,0.3237,0.3322 T0.3325,0.3331 T0.3414,0.3339 Q0.3478,0.3339,0.3518,0.3314 Q0.3606,0.3264,0.3703,0.3264 Q0.3775,0.3264,0.3847,0.3293 Q0.3888,0.331,0.396,0.331 Q0.4024,0.331,0.4116,0.3295 T0.4265,0.3266 T0.4414,0.3227 T0.4514,0.3202 Q0.4578,0.3181,0.4839,0.3152 T0.5112,0.3123 T0.5177,0.3119 T0.5285,0.3111 T0.5398,0.3096 T0.549,0.3065 T0.5526,0.3015 Q0.5526,0.2999,0.5518,0.2982 Q0.547,0.2903,0.5386,0.2883 T0.5165,0.2862 Q0.5116,0.2862,0.5056,0.2866 T0.4972,0.287 Q0.4884,0.287,0.4787,0.2833 Q0.4594,0.2758,0.4265,0.2758 Q0.4169,0.2758,0.4129,0.2766 Q0.4112,0.2771,0.4104,0.2771 Q0.4088,0.2771,0.4056,0.2748 T0.3968,0.2721 Q0.3695,0.2696,0.359,0.2659 Q0.355,0.2646,0.351,0.2646 T0.3426,0.2661 T0.3365,0.2675 T0.3333,0.2659 Q0.3277,0.2605,0.3116,0.2553 T0.2859,0.2501 L0.2843,0.2501 L0.2819,0.2501 Q0.2779,0.2501,0.2723,0.2493 T0.2586,0.2472 T0.2474,0.2455 Q0.1799,0.236,0.1719,0.236 Q0.1663,0.236,0.1606,0.235 T0.1502,0.2316 T0.1454,0.2265 Q0.1454,0.2223,0.1558,0.2173 Q0.1679,0.2115,0.1859,0.2092 T0.2185,0.207 T0.2353,0.2057 Q0.2369,0.2049,0.2502,0.2041 T0.2799,0.2022 T0.302,0.2007 Q0.3084,0.1999,0.3245,0.1954 T0.347,0.1908 Q0.3502,0.1908,0.3534,0.1916 Q0.3663,0.197,0.388,0.197 Q0.404,0.197,0.4088,0.1929 Q0.4096,0.192,0.4108,0.192 T0.4177,0.1945 T0.4277,0.197 T0.4369,0.1949 Q0.4442,0.1908,0.4843,0.1875 Q0.4892,0.1871,0.4968,0.1839 T0.5084,0.1808 L0.5108,0.1808 Q0.5213,0.1825,0.5454,0.1825 Q0.5823,0.1825,0.5992,0.1779 Q0.6096,0.1746,0.6217,0.1746 Q0.6249,0.1746,0.6309,0.1748 T0.6394,0.175 Q0.6434,0.175,0.6466,0.1746 Q0.6474,0.1746,0.6594,0.1738 T0.6787,0.1713 T0.6892,0.1659 Q0.6924,0.1634,0.6924,0.1593 Q0.6924,0.1547,0.6888,0.151 T0.6795,0.1472 Q0.6779,0.1472,0.6763,0.1477 Q0.6699,0.1493,0.6667,0.1493 Q0.661,0.1493,0.6506,0.1456 Q0.6329,0.1394,0.6129,0.1394 Q0.6048,0.1394,0.5992,0.1377 T0.5888,0.136 T0.5803,0.1377 T0.5751,0.1394 Q0.5711,0.1394,0.5639,0.1369 Q0.5365,0.1273,0.5116,0.1273 Q0.4876,0.1273,0.4667,0.1253 Q0.4627,0.1248,0.4542,0.1211 T0.4337,0.1174 L0.4305,0.1174 Q0.4217,0.1178,0.4153,0.119 T0.408,0.1203 Q0.4056,0.1203,0.4,0.117 Q0.3976,0.1153,0.396,0.1136 T0.3932,0.1112 T0.39,0.1099 T0.3847,0.1095 Q0.3799,0.1095,0.3687,0.1103 Q0.355,0.1112,0.3434,0.1141 T0.3301,0.117 Q0.3261,0.117,0.3229,0.1145 Q0.3181,0.1116,0.3052,0.1087 T0.2787,0.1043 T0.2546,0.1029 Q0.2506,0.1029,0.2474,0.1033 Q0.2418,0.1037,0.2349,0.1054 T0.2249,0.107 Q0.2225,0.107,0.2193,0.1066 Q0.2024,0.1024,0.1863,0.1024 Q0.1823,0.1024,0.1562,0.0971 T0.1269,0.0917 T0.1161,0.0896 T0.106,0.0854 Q0.1076,0.0846,0.11,0.0842 Q0.1173,0.0821,0.1261,0.0821 Q0.1301,0.0821,0.1369,0.0825 T0.1462,0.083 Q0.1494,0.083,0.151,0.0825 T0.1831,0.0813 T0.2201,0.0784 T0.243,0.074 T0.2667,0.0718 Q0.2707,0.0718,0.2755,0.0724 T0.2843,0.073 T0.2936,0.0724 T0.3024,0.0705 T0.3072,0.068 T0.3092,0.0657 T0.3108,0.0647 Q0.3116,0.0643,0.3133,0.0643 L0.3157,0.0643 L0.3181,0.0643 Q0.3446,0.0643,0.3446,0.0556 T0.3205,0.0469 L0.3189,0.0469 Q0.3157,0.0469,0.3157,0.0465 Q0.3084,0.0427,0.2835,0.0386 Q0.2739,0.0369,0.2675,0.0342 T0.2538,0.0299 Q0.249,0.029,0.245,0.029 T0.2378,0.0303 T0.2321,0.0315 Q0.2305,0.0315,0.2273,0.0307 Q0.2201,0.0286,0.2004,0.0272 T0.1775,0.0245 Q0.1735,0.0228,0.1663,0.0226 T0.1522,0.0197 T0.1345,0.0145 T0.1124,0.0079 Q0.0996,0.0033,0.0671,0.0044 T0.0297,0.0041 Q0.0169,0,0.0024,0.0017 Q0,0.0021,0,0.0037 Z M0.0064,0.9954 L0.0056,0.0058 Q0.0177,0.005,0.0281,0.0083 Q0.0345,0.01,0.0675,0.0089 T0.1108,0.012 Q0.1221,0.0162,0.1329,0.0187 T0.1498,0.0236 T0.1606,0.0268 T0.1691,0.0274 T0.1759,0.0286 Q0.1807,0.0303,0.2,0.0315 T0.2257,0.0348 Q0.2305,0.0361,0.2337,0.0361 Q0.2361,0.0361,0.2398,0.0348 T0.2466,0.0336 Q0.249,0.0336,0.2522,0.034 Q0.2586,0.0353,0.2655,0.0382 T0.2827,0.0427 Q0.3068,0.0469,0.3124,0.0502 Q0.3157,0.0523,0.3237,0.0523 L0.3277,0.0523 L0.3309,0.0523 Q0.3398,0.0523,0.3398,0.0556 Q0.3398,0.0581,0.3341,0.0587 T0.3205,0.0593 T0.3092,0.0603 T0.3036,0.0643 T0.2932,0.068 Q0.2884,0.0684,0.2843,0.0684 Q0.2811,0.0684,0.2763,0.0678 T0.2667,0.0672 Q0.2586,0.0672,0.2422,0.0697 T0.2177,0.0747 Q0.2137,0.0759,0.1831,0.0767 T0.1494,0.0784 L0.1474,0.0784 T0.1398,0.078 T0.1285,0.0776 Q0.1173,0.0776,0.1084,0.08 Q0.1004,0.0817,0.1004,0.0846 Q0.1004,0.0867,0.104,0.0888 T0.1124,0.0923 T0.1213,0.0948 T0.1269,0.0958 T0.155,0.1014 T0.1863,0.107 Q0.2016,0.107,0.2185,0.1107 Q0.2217,0.1116,0.2257,0.1116 Q0.2289,0.1116,0.2365,0.1099 T0.2482,0.1074 L0.2538,0.1074 Q0.2683,0.1074,0.2912,0.1107 T0.3197,0.1182 Q0.3245,0.1215,0.3301,0.1215 Q0.3333,0.1215,0.3454,0.1184 T0.3687,0.1145 Q0.3735,0.1141,0.3779,0.1139 T0.3843,0.1134 T0.3871,0.1132 Q0.3896,0.1132,0.39,0.1136 T0.392,0.1161 T0.3968,0.1203 Q0.404,0.1248,0.408,0.1248 Q0.4096,0.1248,0.4165,0.1234 T0.4305,0.1215 L0.4337,0.1215 Q0.4442,0.1215,0.4518,0.1251 T0.4659,0.1294 Q0.4876,0.1319,0.5116,0.1319 T0.5622,0.141 Q0.5703,0.1439,0.5751,0.1439 Q0.5791,0.1439,0.5831,0.1423 T0.5888,0.1406 Q0.592,0.1406,0.5944,0.1412 T0.6012,0.1427 T0.6129,0.1435 Q0.6313,0.1435,0.649,0.1497 Q0.6602,0.1535,0.6667,0.1535 Q0.6715,0.1535,0.6779,0.1522 Q0.6787,0.1518,0.6795,0.1518 Q0.6827,0.1518,0.6847,0.1541 T0.6867,0.1593 Q0.6867,0.1618,0.6851,0.1634 Q0.6835,0.1655,0.6775,0.1667 T0.6598,0.169 T0.6458,0.1705 T0.6402,0.1709 Q0.6378,0.1709,0.6321,0.1705 T0.6225,0.1701 Q0.6096,0.1701,0.5968,0.1738 Q0.5823,0.1779,0.5446,0.1779 Q0.5205,0.1779,0.5116,0.1767 Q0.51,0.1763,0.5084,0.1763 Q0.5028,0.1763,0.4944,0.1796 T0.4835,0.1829 L0.4771,0.1833 T0.4659,0.1844 L0.4546,0.1858 T0.443,0.1881 T0.4337,0.1912 Q0.4321,0.1916,0.4305,0.1916 Q0.4273,0.1916,0.4209,0.1895 T0.412,0.1875 Q0.408,0.1875,0.4056,0.19 Q0.4024,0.1925,0.388,0.1925 Q0.3663,0.1925,0.3558,0.1879 Q0.3518,0.1862,0.3462,0.1862 Q0.3373,0.1862,0.3213,0.191 T0.3012,0.1962 Q0.2964,0.1966,0.2799,0.1976 T0.2494,0.1997 T0.2313,0.2024 L0.2313,0.2024 L0.2293,0.2024 L0.2261,0.2024 L0.2217,0.2024 Q0.204,0.2024,0.1851,0.2047 T0.1526,0.2136 Q0.1406,0.2198,0.1406,0.226 Q0.1406,0.2319,0.1518,0.2362 T0.1719,0.2406 Q0.1791,0.2406,0.2466,0.2497 Q0.2498,0.2501,0.2574,0.2516 T0.2711,0.2538 T0.2819,0.2547 L0.2851,0.2547 Q0.2851,0.2543,0.2859,0.2543 Q0.294,0.2543,0.3096,0.2592 T0.3285,0.2684 Q0.3317,0.2721,0.3373,0.2721 Q0.3406,0.2721,0.3462,0.2708 T0.3542,0.2696 Q0.3558,0.2696,0.3574,0.27 Q0.3679,0.2737,0.3968,0.2762 Q0.3984,0.2766,0.4004,0.2779 L0.4044,0.2804 T0.4096,0.2816 Q0.412,0.2816,0.4145,0.2808 Q0.4177,0.2804,0.4249,0.2804 Q0.4586,0.2804,0.4763,0.287 Q0.4867,0.2912,0.4972,0.2912 Q0.5004,0.2912,0.5064,0.2908 T0.5165,0.2903 Q0.5293,0.2903,0.5361,0.2922 T0.547,0.3003 L0.547,0.3011 Q0.547,0.3036,0.5414,0.3049 T0.5249,0.3067 T0.51,0.3082 Q0.5092,0.3082,0.4831,0.3111 T0.4498,0.3161 Q0.408,0.3264,0.3952,0.3264 Q0.3896,0.3264,0.3863,0.3252 Q0.3783,0.3223,0.3703,0.3223 Q0.359,0.3223,0.3494,0.3277 Q0.347,0.3293,0.343,0.3293 Q0.3414,0.3293,0.3353,0.3285 T0.3253,0.3277 T0.3181,0.3285 Q0.3149,0.3289,0.2952,0.3306 T0.2699,0.3347 Q0.2675,0.3364,0.2598,0.3372 T0.2434,0.3395 T0.2297,0.3426 L0.2205,0.3461 T0.2048,0.3523 T0.1908,0.3606 T0.1791,0.3725 Q0.1743,0.3803,0.1743,0.3845 Q0.1743,0.3874,0.1763,0.3901 T0.1827,0.3976 T0.1912,0.4073 T0.198,0.4189 T0.2032,0.4276 T0.2137,0.4326 T0.2386,0.438 Q0.2458,0.4392,0.2562,0.4392 L0.2639,0.4392 L0.2699,0.4392 Q0.2795,0.4392,0.2843,0.4405 Q0.2932,0.443,0.3092,0.443 L0.3157,0.443 L0.3213,0.443 Q0.3293,0.443,0.3458,0.4463 T0.3671,0.4525 Q0.3598,0.4558,0.3373,0.4571 Q0.3124,0.4587,0.298,0.4637 Q0.294,0.465,0.2811,0.465 L0.2703,0.465 L0.2586,0.465 Q0.241,0.465,0.2305,0.4666 Q0.2185,0.4687,0.2185,0.4728 Q0.2185,0.4745,0.2205,0.4764 T0.2241,0.4791 L0.2265,0.4803 L0.2273,0.4803 Q0.2506,0.4824,0.261,0.4824 Q0.2675,0.4824,0.2699,0.482 Q0.2699,0.4815,0.2707,0.4815 Q0.2771,0.4815,0.3124,0.4898 Q0.3173,0.4907,0.3229,0.4913 T0.3386,0.4925 T0.3542,0.4936 T0.3663,0.494 Q0.3711,0.494,0.3787,0.4936 T0.3888,0.4932 Q0.392,0.4932,0.3944,0.4936 T0.4032,0.4959 T0.4181,0.499 T0.4369,0.5006 Q0.449,0.501,0.455,0.5021 T0.4639,0.5048 T0.4675,0.5093 L0.4675,0.5097 Q0.4675,0.5106,0.4663,0.511 T0.4622,0.5116 L0.4566,0.512 T0.4486,0.5124 T0.4402,0.5127 Q0.4345,0.5131,0.4257,0.5153 T0.4096,0.5176 Q0.4064,0.5176,0.4024,0.5172 L0.3968,0.5172 Q0.3863,0.5172,0.3731,0.522 T0.359,0.5268 Q0.355,0.528,0.3317,0.5321 T0.3004,0.5388 Q0.2867,0.5433,0.2337,0.5479 Q0.2096,0.55,0.2096,0.5562 Q0.2096,0.5591,0.2145,0.5628 Q0.2193,0.5662,0.2289,0.5662 Q0.2313,0.5662,0.2373,0.5657 T0.2458,0.5653 L0.2498,0.5653 Q0.2635,0.5674,0.3124,0.5674 Q0.3261,0.5674,0.3301,0.567 L0.3317,0.567 Q0.3341,0.567,0.3394,0.568 T0.3486,0.5691 Q0.3542,0.5691,0.3606,0.5678 T0.3699,0.5666 T0.3759,0.5686 T0.3839,0.5718 T0.3908,0.573 T0.3964,0.5734 T0.4048,0.5742 T0.4165,0.5751 T0.4277,0.5757 T0.4369,0.5778 Q0.4426,0.5794,0.4538,0.5794 Q0.4586,0.5794,0.4679,0.5792 T0.4811,0.579 Q0.4924,0.579,0.4956,0.5807 Q0.502,0.584,0.5108,0.584 L0.5137,0.584 T0.5173,0.5838 L0.5205,0.5834 L0.5237,0.583 L0.5269,0.5825 T0.5305,0.5821 T0.5341,0.5819 L0.5365,0.5819 Q0.5414,0.5819,0.5562,0.5842 T0.5855,0.5865 Q0.5912,0.5865,0.5948,0.5867 T0.6012,0.5869 T0.6064,0.5873 T0.61,0.5877 T0.6133,0.5886 T0.6157,0.5896 L0.6185,0.591 T0.6217,0.5931 Q0.6265,0.5952,0.6369,0.5952 L0.6418,0.5952 L0.6466,0.5952 Q0.6723,0.5952,0.6932,0.606 Q0.698,0.608,0.7044,0.608 Q0.7084,0.608,0.7161,0.6068 T0.7261,0.6056 Q0.7309,0.6056,0.7454,0.6051 T0.7647,0.6047 Q0.7775,0.6047,0.8072,0.6101 Q0.8096,0.6105,0.812,0.6105 Q0.8177,0.6105,0.8297,0.6085 T0.8474,0.606 L0.8506,0.606 Q0.8562,0.606,0.8647,0.607 T0.8831,0.6095 T0.8964,0.6118 Q0.9036,0.6122,0.9221,0.6122 L0.9406,0.6122 L0.959,0.6122 L0.9767,0.6122 Q0.9952,0.613,0.9952,0.6172 Q0.9952,0.6176,0.9944,0.6221 Q0.9936,0.6259,0.9574,0.6259 L0.955,0.6259 L0.9526,0.6259 Q0.9478,0.6259,0.9373,0.628 T0.9205,0.63 L0.9173,0.63 Q0.9133,0.6296,0.9084,0.6296 Q0.9004,0.6296,0.8715,0.6321 Q0.8233,0.6367,0.7944,0.64 Q0.7888,0.6408,0.7839,0.6408 T0.7739,0.6402 T0.7671,0.6396 Q0.7639,0.6396,0.7622,0.64 Q0.7614,0.6404,0.759,0.6404 Q0.7574,0.6404,0.749,0.6398 T0.7357,0.6392 Q0.7261,0.6392,0.7213,0.6412 Q0.7173,0.6425,0.7032,0.6437 T0.6711,0.6458 T0.6474,0.647 Q0.645,0.6474,0.6418,0.6474 Q0.6353,0.6474,0.6277,0.6454 T0.6161,0.6433 Q0.6104,0.6433,0.6072,0.6456 T0.6,0.6479 Q0.5912,0.6479,0.5486,0.6557 Q0.5422,0.657,0.5373,0.657 Q0.5349,0.657,0.5289,0.6564 T0.5173,0.6557 L0.5124,0.6557 Q0.4996,0.6562,0.4739,0.6611 Q0.4675,0.6624,0.4398,0.6645 T0.3851,0.668 T0.3558,0.6698 Q0.355,0.6698,0.3285,0.6721 T0.2948,0.6752 Q0.2892,0.6761,0.2843,0.6786 T0.2747,0.6831 T0.2618,0.6852 Q0.253,0.6852,0.2394,0.6893 T0.2141,0.6993 T0.1968,0.7105 Q0.1944,0.713,0.1944,0.7155 Q0.1944,0.7196,0.2012,0.7229 T0.2177,0.7285 T0.2281,0.7308 Q0.2321,0.7329,0.2365,0.7339 T0.245,0.7356 T0.2514,0.7366 Q0.2522,0.737,0.2554,0.7379 T0.2614,0.7393 T0.2679,0.7406 T0.2755,0.7416 T0.2835,0.742 Q0.29,0.742,0.3048,0.7428 T0.3253,0.7437 L0.3301,0.7437 L0.3333,0.7437 Q0.3341,0.7437,0.3598,0.7466 Q0.3639,0.7474,0.3671,0.7474 T0.3759,0.7466 T0.3855,0.7457 Q0.388,0.7457,0.3904,0.7462 Q0.3984,0.7466,0.4137,0.7495 T0.4349,0.7532 T0.441,0.7557 Q0.441,0.7599,0.4289,0.7619 Q0.4257,0.7628,0.4197,0.7634 T0.4072,0.7644 T0.3984,0.7652 Q0.3968,0.7652,0.3912,0.7655 T0.3811,0.7665 T0.3719,0.7694 Q0.3695,0.7702,0.3655,0.7702 T0.3546,0.7686 T0.3462,0.7665 L0.3446,0.7665 Q0.3406,0.7665,0.3293,0.769 T0.3149,0.7715 Q0.3084,0.7715,0.2952,0.7748 T0.2763,0.781 Q0.2675,0.7847,0.2554,0.7864 Q0.253,0.7864,0.2325,0.7874 T0.2024,0.7893 Q0.2008,0.7893,0.1984,0.7897 T0.1948,0.7901 T0.1916,0.7903 T0.1888,0.7908 T0.1867,0.7914 T0.1847,0.7922 L0.1831,0.793 T0.1819,0.7943 L0.1811,0.7959 T0.1799,0.7976 Q0.1791,0.7993,0.1791,0.8001 Q0.1791,0.8034,0.1831,0.8051 T0.1932,0.8078 T0.2,0.8092 Q0.2072,0.8117,0.2257,0.8117 Q0.2337,0.8117,0.2586,0.8109 L0.2635,0.8109 Q0.2707,0.8109,0.2767,0.8117 T0.2859,0.8136 T0.2936,0.8163 T0.3012,0.8187 Q0.3068,0.82,0.3277,0.8206 T0.3526,0.8237 Q0.3582,0.8266,0.3622,0.8266 Q0.3647,0.8266,0.3687,0.8258 T0.3751,0.825 Q0.396,0.825,0.404,0.8291 Q0.408,0.8316,0.4161,0.8316 Q0.4217,0.8316,0.4353,0.8295 L0.4394,0.8295 Q0.4466,0.8295,0.4659,0.8366 Q0.4699,0.8378,0.4747,0.8378 Q0.4763,0.8378,0.4807,0.8374 T0.4884,0.837 Q0.494,0.837,0.5004,0.8382 Q0.5052,0.8391,0.5124,0.8399 Q0.5221,0.8411,0.5249,0.8422 T0.5277,0.8461 Q0.5277,0.8482,0.5233,0.849 T0.5112,0.8501 T0.4996,0.8511 Q0.4964,0.8519,0.4851,0.8523 T0.4659,0.8544 Q0.4578,0.8557,0.4402,0.8557 L0.4349,0.8557 L0.4297,0.8557 L0.4209,0.8557 Q0.408,0.8565,0.3867,0.8571 T0.3614,0.8582 Q0.3317,0.8611,0.3237,0.8611 L0.3225,0.8611 L0.3213,0.8611 Q0.2972,0.8611,0.2972,0.8681 Q0.2972,0.8714,0.3008,0.8747 T0.3044,0.8805 Q0.3044,0.881,0.3036,0.8814 Q0.3012,0.8839,0.2851,0.8839 Q0.2779,0.8839,0.2739,0.883 Q0.2723,0.8826,0.2699,0.8826 T0.2643,0.8835 T0.259,0.8853 T0.2542,0.8876 T0.249,0.8893 Q0.2386,0.8922,0.2056,0.8946 Q0.2016,0.8951,0.1916,0.898 T0.1703,0.9009 Q0.1518,0.9009,0.1337,0.9044 T0.1149,0.9146 L0.1149,0.9158 Q0.1149,0.9175,0.1157,0.9187 T0.1173,0.921 T0.1201,0.9226 T0.1233,0.9237 T0.1269,0.9245 L0.1305,0.9251 T0.1337,0.9255 L0.1357,0.9258 Q0.139,0.9262,0.1446,0.9262 T0.1542,0.9266 T0.1598,0.9282 Q0.1614,0.9303,0.1647,0.9314 T0.1739,0.9328 T0.1815,0.9336 Q0.1863,0.9345,0.1976,0.9372 T0.2145,0.9415 Q0.2225,0.9432,0.2285,0.9467 T0.2378,0.9515 Q0.2426,0.9531,0.2514,0.9531 Q0.2538,0.9531,0.2602,0.9529 T0.2707,0.9527 Q0.2763,0.9527,0.2815,0.9531 T0.2867,0.9569 T0.2811,0.9614 Q0.2434,0.9627,0.2329,0.9652 Q0.2249,0.9672,0.2241,0.9672 L0.2096,0.9668 L0.2056,0.9668 Q0.2,0.9668,0.196,0.9672 T0.1884,0.9687 T0.1815,0.9708 T0.1731,0.9737 T0.1639,0.9772 Q0.1518,0.9805,0.1285,0.9805 L0.1253,0.9805 Q0.1124,0.9805,0.1076,0.9818 Q0.1012,0.983,0.0831,0.9876 T0.0594,0.9934 Q0.049,0.9954,0.0064,0.9954 Z M0.0032,0.9979 L0.0024,0.0037 Q0.0161,0.0021,0.0289,0.0062 Q0.0337,0.0075,0.0671,0.0066 T0.1116,0.01 T0.1337,0.0166 T0.151,0.022 Q0.1582,0.0249,0.1659,0.0251 T0.1767,0.0265 Q0.1807,0.0278,0.2,0.0292 T0.2265,0.0328 Q0.2305,0.034,0.2329,0.034 T0.239,0.0326 T0.2458,0.0311 T0.253,0.0319 Q0.2578,0.0332,0.2614,0.0342 T0.2667,0.0363 T0.2723,0.0384 T0.2827,0.0406 Q0.3084,0.0448,0.3141,0.0485 Q0.3149,0.0494,0.3225,0.0494 T0.3361,0.0504 T0.3422,0.0554 T0.3357,0.0606 T0.3205,0.0618 T0.31,0.0626 Q0.3092,0.0626,0.3056,0.0659 T0.2932,0.0701 Q0.2884,0.0709,0.2843,0.0709 Q0.2811,0.0709,0.2759,0.0701 T0.2667,0.0693 Q0.2586,0.0693,0.2422,0.0718 T0.2193,0.0763 Q0.2137,0.0784,0.1831,0.079 T0.1502,0.0805 Q0.1494,0.0809,0.147,0.0809 Q0.1454,0.0809,0.1386,0.0803 T0.1277,0.0796 Q0.1173,0.0796,0.1092,0.0821 Q0.1036,0.0834,0.1036,0.0854 Q0.1036,0.0879,0.1129,0.0908 T0.1269,0.0937 Q0.1293,0.0937,0.1558,0.0991 T0.1863,0.1045 Q0.2016,0.1045,0.2193,0.1087 Q0.2225,0.1091,0.2249,0.1091 Q0.2289,0.1091,0.2357,0.1076 T0.2482,0.1054 Q0.2506,0.1049,0.2546,0.1049 Q0.2699,0.1049,0.2924,0.1085 T0.3213,0.1165 Q0.3253,0.119,0.3301,0.119 Q0.3325,0.119,0.3446,0.1161 T0.3687,0.1124 Q0.3831,0.1112,0.3863,0.1112 T0.3908,0.1118 T0.3936,0.1145 T0.3984,0.1186 Q0.4048,0.1228,0.408,0.1228 Q0.4096,0.1228,0.4161,0.1213 T0.4305,0.1195 L0.4337,0.1195 Q0.4426,0.1195,0.4474,0.1211 T0.4566,0.1246 T0.4667,0.1273 Q0.4876,0.1298,0.5116,0.1298 Q0.5365,0.1298,0.5631,0.1389 Q0.5703,0.1414,0.5751,0.1414 Q0.5783,0.1414,0.5819,0.1398 T0.5888,0.1381 Q0.5928,0.1381,0.5984,0.1398 T0.6129,0.1414 Q0.6321,0.1414,0.6498,0.1477 Q0.661,0.1514,0.6667,0.1514 Q0.6707,0.1514,0.6771,0.1501 Q0.6787,0.1497,0.6795,0.1497 Q0.6835,0.1497,0.6863,0.1526 T0.6892,0.1593 Q0.6892,0.1626,0.6876,0.1647 Q0.6851,0.1672,0.6799,0.1686 T0.6695,0.1705 T0.6566,0.1715 T0.6458,0.1725 Q0.6434,0.173,0.6402,0.173 Q0.6378,0.173,0.6317,0.1727 T0.6225,0.1725 Q0.6096,0.1725,0.5984,0.1759 Q0.5823,0.1804,0.5454,0.1804 Q0.5205,0.1804,0.5116,0.1788 L0.5084,0.1788 Q0.5036,0.1788,0.4956,0.1819 T0.4843,0.185 Q0.4434,0.1887,0.4353,0.1929 Q0.4321,0.1945,0.4289,0.1945 T0.4197,0.1922 T0.4112,0.19 T0.4072,0.1912 Q0.4032,0.1949,0.388,0.1949 Q0.3663,0.1949,0.3542,0.19 Q0.351,0.1883,0.3462,0.1883 Q0.3406,0.1883,0.3321,0.1906 T0.3153,0.1954 T0.302,0.1983 Q0.2972,0.1991,0.2859,0.1997 T0.2643,0.201 T0.245,0.2024 T0.2337,0.2041 Q0.2321,0.2045,0.2181,0.2045 T0.1851,0.207 T0.1542,0.2157 Q0.143,0.2211,0.143,0.226 Q0.143,0.2314,0.153,0.2348 T0.1719,0.2381 Q0.1791,0.2381,0.2474,0.2476 Q0.2506,0.248,0.2558,0.2489 T0.2651,0.2505 T0.2739,0.252 T0.2819,0.2526 Q0.2835,0.2526,0.2851,0.2522 L0.2859,0.2522 Q0.2948,0.2522,0.3104,0.2574 T0.3309,0.2671 Q0.3333,0.2696,0.3365,0.2696 Q0.339,0.2696,0.3442,0.2684 T0.3526,0.2671 T0.3582,0.2679 Q0.3687,0.2717,0.3968,0.2742 Q0.4,0.2746,0.4036,0.2771 T0.4096,0.2796 Q0.4112,0.2796,0.4137,0.2787 Q0.4169,0.2779,0.4257,0.2779 Q0.4594,0.2779,0.4779,0.2849 Q0.4876,0.2891,0.4972,0.2891 Q0.4996,0.2891,0.506,0.2887 T0.5165,0.2883 Q0.5253,0.2883,0.5305,0.2889 T0.541,0.292 T0.5494,0.299 L0.5494,0.3015 Q0.5494,0.3036,0.5466,0.3051 T0.5386,0.3073 T0.5285,0.3086 T0.5181,0.3094 T0.5108,0.3102 Q0.5068,0.3107,0.494,0.3121 L0.4683,0.315 T0.4506,0.3181 Q0.449,0.3181,0.4426,0.32 T0.4313,0.3229 T0.4197,0.3256 T0.4064,0.3281 T0.3952,0.3289 Q0.3888,0.3289,0.3855,0.3273 Q0.3783,0.3243,0.3703,0.3243 Q0.3598,0.3243,0.351,0.3297 Q0.3478,0.3314,0.3422,0.3314 Q0.3398,0.3314,0.3337,0.3306 T0.3245,0.3297 T0.3189,0.3306 Q0.3149,0.3314,0.2956,0.3328 T0.2715,0.3368 Q0.2691,0.338,0.2635,0.3391 T0.2522,0.3407 T0.2406,0.3424 T0.2313,0.3447 Q0.2305,0.3447,0.2213,0.348 T0.206,0.354 T0.1924,0.3621 T0.1815,0.3733 Q0.1767,0.3816,0.1767,0.3849 Q0.1767,0.387,0.1783,0.3893 T0.1847,0.3961 T0.1936,0.4063 T0.2004,0.4181 T0.2052,0.4264 T0.2145,0.4309 T0.2386,0.4359 Q0.2458,0.4372,0.2554,0.4372 Q0.2578,0.4372,0.2627,0.437 T0.2699,0.4367 Q0.2795,0.4367,0.2851,0.4384 Q0.294,0.4409,0.3092,0.4409 Q0.3116,0.4409,0.3157,0.4407 T0.3213,0.4405 Q0.3301,0.4405,0.3466,0.444 T0.3687,0.4509 Q0.3703,0.4517,0.3703,0.4525 Q0.3703,0.4575,0.3373,0.4596 Q0.3133,0.4608,0.2988,0.4658 Q0.294,0.4674,0.2803,0.4674 Q0.2763,0.4674,0.2687,0.4672 T0.257,0.467 Q0.2402,0.467,0.2313,0.4687 Q0.2209,0.4708,0.2209,0.4737 Q0.2209,0.4741,0.2213,0.4749 T0.2229,0.4764 L0.2249,0.4774 T0.2265,0.4778 L0.2273,0.4782 Q0.2498,0.4803,0.2618,0.4803 Q0.2667,0.4803,0.2691,0.4799 Q0.2699,0.4795,0.2715,0.4795 Q0.2779,0.4795,0.3133,0.4873 Q0.3181,0.4886,0.3233,0.4892 T0.3386,0.4905 T0.3542,0.4915 T0.3655,0.4919 Q0.3703,0.4919,0.3779,0.4915 T0.3888,0.4911 T0.3952,0.4915 Q0.3976,0.4919,0.4008,0.4927 L0.4072,0.4944 T0.4149,0.4961 T0.4249,0.4975 T0.4369,0.4985 Q0.449,0.499,0.4554,0.5 T0.4651,0.5031 T0.4699,0.5085 Q0.4707,0.5093,0.4707,0.5097 Q0.4707,0.5114,0.4687,0.5124 T0.4631,0.5139 T0.4562,0.5145 T0.4478,0.5147 L0.4402,0.5147 Q0.4353,0.5151,0.4265,0.5174 T0.4096,0.5197 L0.4024,0.5197 Q0.4,0.5193,0.3968,0.5193 Q0.3896,0.5193,0.3831,0.5209 T0.3703,0.5253 T0.3598,0.5288 Q0.3558,0.5301,0.3321,0.5342 T0.302,0.5409 Q0.2876,0.5454,0.2337,0.55 Q0.2112,0.5521,0.2112,0.5558 Q0.2112,0.5574,0.2161,0.5612 Q0.2201,0.5637,0.2281,0.5637 Q0.2305,0.5637,0.2361,0.5633 T0.2442,0.5628 Q0.2474,0.5628,0.2498,0.5633 Q0.2635,0.5653,0.3133,0.5653 Q0.3261,0.5653,0.3301,0.5649 L0.3325,0.5649 Q0.3349,0.5649,0.3402,0.5657 T0.3494,0.5666 Q0.3542,0.5666,0.3598,0.5657 Q0.3663,0.5645,0.3695,0.5645 Q0.3735,0.5645,0.3775,0.5666 T0.3847,0.5695 Q0.3888,0.5707,0.3912,0.5709 T0.3968,0.5711 T0.4048,0.572 Q0.4096,0.5724,0.4165,0.5726 T0.4281,0.5734 T0.4378,0.5757 Q0.4418,0.5774,0.4522,0.5774 Q0.4562,0.5774,0.4655,0.5769 T0.4795,0.5765 Q0.4932,0.5765,0.4972,0.579 Q0.5028,0.5819,0.51,0.5819 Q0.5141,0.5819,0.5221,0.5809 T0.5341,0.5798 L0.5373,0.5798 Q0.5422,0.5798,0.557,0.5821 T0.5855,0.5844 L0.5936,0.5844 L0.5992,0.5844 T0.604,0.5848 T0.6076,0.5852 T0.6108,0.5859 T0.6137,0.5865 T0.6161,0.5873 T0.6181,0.5881 T0.6209,0.5896 L0.6233,0.591 Q0.6265,0.5927,0.6353,0.5927 L0.6402,0.5927 L0.645,0.5927 Q0.6723,0.5927,0.6948,0.6039 Q0.6988,0.606,0.7036,0.606 Q0.7076,0.606,0.7153,0.6045 T0.7261,0.6031 Q0.7309,0.6031,0.7454,0.6027 T0.7647,0.6022 Q0.7783,0.6022,0.808,0.608 L0.812,0.608 Q0.8169,0.608,0.8293,0.6062 T0.8474,0.6039 L0.8506,0.6039 Q0.857,0.6039,0.8655,0.6049 T0.8835,0.6074 T0.8972,0.6093 Q0.9036,0.6101,0.9213,0.6101 L0.9398,0.6101 L0.959,0.6101 L0.9767,0.6101 Q0.9976,0.6109,0.9976,0.6176 Q0.9976,0.6188,0.9968,0.6226 Q0.9952,0.6284,0.9671,0.6284 L0.959,0.6284 L0.9526,0.6284 Q0.9486,0.6284,0.9382,0.6302 T0.9205,0.6321 L0.9173,0.6321 Q0.9133,0.6317,0.9092,0.6317 Q0.9012,0.6317,0.8715,0.6346 Q0.8233,0.6387,0.7944,0.6421 Q0.7888,0.6429,0.7831,0.6429 Q0.7783,0.6429,0.7731,0.6425 T0.7663,0.6421 L0.7622,0.6421 Q0.7614,0.6425,0.7586,0.6425 T0.7474,0.6421 T0.7349,0.6416 Q0.7261,0.6416,0.7221,0.6429 Q0.7173,0.645,0.7032,0.646 T0.6711,0.6479 T0.6482,0.6491 Q0.645,0.6495,0.6418,0.6495 Q0.6353,0.6495,0.6273,0.6474 T0.6161,0.6454 Q0.612,0.6454,0.6088,0.6477 T0.6,0.6499 Q0.5912,0.6499,0.5494,0.6578 Q0.5422,0.6591,0.5373,0.6591 Q0.5341,0.6591,0.5281,0.6584 T0.5173,0.6578 L0.5124,0.6578 Q0.5004,0.6582,0.4747,0.6632 Q0.4675,0.6649,0.4398,0.6667 T0.3851,0.6703 T0.3566,0.6719 Q0.3534,0.6723,0.3406,0.6734 T0.3141,0.6757 T0.2948,0.6773 Q0.2908,0.6781,0.2819,0.6829 T0.2618,0.6877 Q0.2482,0.6877,0.2273,0.6958 T0.1992,0.7117 Q0.1976,0.7138,0.1976,0.7155 Q0.1976,0.7188,0.2032,0.7213 T0.2181,0.7258 T0.2289,0.7292 Q0.2337,0.7308,0.2378,0.7316 L0.2458,0.7333 T0.2522,0.7345 Q0.2747,0.7399,0.2835,0.7399 Q0.29,0.7399,0.3048,0.7408 T0.3253,0.7416 Q0.3277,0.7416,0.3297,0.7414 T0.3341,0.7412 L0.3598,0.7445 Q0.3639,0.7449,0.3663,0.7449 Q0.3695,0.7449,0.3751,0.7443 T0.3855,0.7437 L0.3904,0.7437 Q0.3984,0.7441,0.4141,0.7472 T0.4353,0.7507 Q0.4434,0.752,0.4434,0.7553 Q0.4434,0.7578,0.439,0.7605 T0.4297,0.764 Q0.4257,0.7648,0.4201,0.7655 T0.4076,0.7665 T0.3984,0.7673 Q0.3968,0.7673,0.3912,0.7677 T0.3815,0.7688 T0.3735,0.771 T0.3655,0.7727 Q0.3606,0.7727,0.3534,0.7708 T0.3458,0.769 L0.3446,0.769 Q0.3414,0.769,0.3297,0.7713 T0.3149,0.7735 Q0.3092,0.7735,0.296,0.7771 T0.2771,0.7831 Q0.2683,0.7872,0.2554,0.7885 Q0.253,0.7889,0.2325,0.7897 T0.2032,0.7914 Q0.2008,0.7918,0.198,0.792 T0.194,0.7924 T0.1912,0.7928 T0.1888,0.793 T0.1867,0.7934 T0.1851,0.7943 T0.1843,0.7953 T0.1835,0.7968 T0.1823,0.7984 T0.1815,0.8001 Q0.1815,0.8022,0.1847,0.8032 T0.1932,0.8053 T0.2008,0.8071 Q0.2072,0.8092,0.2265,0.8092 Q0.2361,0.8092,0.2586,0.8088 L0.2635,0.8088 Q0.2731,0.8088,0.2795,0.8098 T0.2928,0.8134 T0.302,0.8167 Q0.306,0.8179,0.3277,0.8183 T0.3542,0.8221 Q0.359,0.8246,0.3622,0.8246 Q0.3639,0.8246,0.3675,0.8237 T0.3751,0.8229 Q0.3968,0.8229,0.4048,0.8275 Q0.4088,0.8291,0.4161,0.8291 Q0.4225,0.8291,0.4353,0.8275 Q0.4369,0.827,0.4394,0.827 Q0.4474,0.827,0.4675,0.8345 Q0.4707,0.8358,0.4739,0.8358 Q0.4755,0.8358,0.4799,0.8353 T0.4876,0.8349 Q0.494,0.8349,0.5012,0.8362 L0.5076,0.837 T0.5133,0.8376 T0.5181,0.8382 T0.5221,0.8391 L0.5253,0.8399 T0.5277,0.8409 L0.5293,0.8422 T0.5301,0.8438 L0.5301,0.8461 Q0.5301,0.8499,0.5249,0.8511 T0.5112,0.8526 T0.5004,0.8532 Q0.4964,0.854,0.4851,0.8546 T0.4667,0.8565 Q0.4578,0.8577,0.441,0.8577 L0.4345,0.8577 L0.4289,0.8577 L0.4209,0.8577 Q0.4153,0.8582,0.4088,0.8586 T0.3968,0.8592 T0.3855,0.8594 T0.3755,0.8596 T0.3675,0.86 T0.3622,0.8602 Q0.3317,0.8635,0.3237,0.8635 Q0.2996,0.8635,0.2996,0.8681 Q0.2996,0.8698,0.3032,0.8733 T0.3068,0.8797 L0.3068,0.8822 Q0.3052,0.8859,0.2859,0.8859 Q0.2787,0.8859,0.2739,0.8851 L0.2699,0.8851 Q0.2659,0.8851,0.259,0.888 T0.2498,0.8913 Q0.2394,0.8946,0.2056,0.8971 Q0.2032,0.8971,0.1928,0.9002 T0.1703,0.9034 Q0.1526,0.9034,0.1353,0.9067 T0.1173,0.915 L0.1173,0.9158 Q0.1173,0.9166,0.1177,0.9175 T0.1189,0.9191 T0.1205,0.9204 T0.1229,0.9212 T0.1257,0.922 T0.1285,0.9226 T0.1309,0.9231 T0.1337,0.9235 T0.1365,0.9237 L0.1446,0.9237 T0.1554,0.9243 T0.1614,0.9268 T0.1659,0.9293 T0.1743,0.9303 T0.1823,0.9316 Q0.1863,0.932,0.1912,0.9332 T0.2028,0.9361 T0.2153,0.939 Q0.2233,0.9411,0.2297,0.9448 T0.2386,0.9494 Q0.2426,0.9506,0.2506,0.9506 Q0.253,0.9506,0.2594,0.9504 T0.2699,0.9502 Q0.2763,0.9502,0.2819,0.9511 Q0.2851,0.9511,0.2871,0.9529 T0.2892,0.9573 Q0.2892,0.9627,0.2811,0.9635 Q0.2434,0.9652,0.2337,0.9672 Q0.2257,0.9693,0.2241,0.9693 Q0.2217,0.9693,0.2096,0.9689 L0.2056,0.9689 Q0.1976,0.9689,0.1924,0.9699 T0.1787,0.9741 L0.1647,0.9793 Q0.1518,0.983,0.1285,0.983 L0.1261,0.983 L0.1237,0.983 Q0.1124,0.983,0.1076,0.9838 Q0.102,0.9851,0.0839,0.9896 T0.0602,0.9954 T0.0406,0.9971 T0.0153,0.9979 L0.0032,0.9979 Z",type:0}},"\uE0CA":{type:5,data:{d:"M0.9992,0.998 Q0.9992,1,0.9968,1 Q0.9521,1,0.9392,0.998 Q0.9335,0.997,0.9152,0.9921 T0.8913,0.9862 Q0.8873,0.9852,0.8783,0.9852 L0.8747,0.9852 L0.871,0.9852 Q0.8483,0.9852,0.8345,0.9812 Q0.8289,0.9792,0.8228,0.9773 T0.8135,0.9743 T0.8054,0.9723 T0.7948,0.9713 L0.7908,0.9713 L0.777,0.9713 T0.7656,0.9693 Q0.7616,0.9683,0.7498,0.9674 T0.7283,0.9664 L0.7186,0.9654 Q0.708,0.9644,0.708,0.9575 Q0.708,0.9545,0.7109,0.952 T0.7178,0.9486 L0.7315,0.9486 L0.7417,0.9486 L0.7502,0.9486 Q0.7575,0.9486,0.7599,0.9476 Q0.7616,0.9466,0.7685,0.9426 T0.7843,0.9377 Q0.7899,0.9357,0.8009,0.9332 T0.8175,0.9298 Q0.82,0.9288,0.8228,0.9283 T0.8277,0.9278 T0.8313,0.9273 T0.8341,0.9263 T0.8362,0.9258 Q0.8402,0.9219,0.854,0.9219 L0.8569,0.9219 L0.8597,0.9219 L0.8637,0.9219 Q0.88,0.9199,0.88,0.9159 L0.88,0.9149 Q0.88,0.913,0.8719,0.911 T0.852,0.9075 T0.8297,0.906 Q0.8224,0.906,0.8167,0.905 T0.8074,0.9031 T0.8001,0.9006 T0.794,0.8991 Q0.7599,0.8971,0.7494,0.8942 Q0.7461,0.8932,0.7397,0.8902 T0.7299,0.8872 L0.7267,0.8872 Q0.721,0.8882,0.7121,0.8882 Q0.6926,0.8882,0.691,0.8833 L0.691,0.8803 Q0.691,0.8773,0.6922,0.8749 T0.6955,0.8704 L0.6975,0.8684 Q0.6926,0.8655,0.6764,0.8655 Q0.6683,0.8655,0.6375,0.8625 Q0.6358,0.8625,0.629,0.862 T0.6148,0.8615 T0.5973,0.861 T0.5791,0.8605 L0.5718,0.8605 L0.5661,0.8605 L0.5596,0.8605 Q0.5426,0.8605,0.5337,0.8586 Q0.5264,0.8576,0.515,0.8571 T0.4996,0.8556 Q0.498,0.8556,0.4947,0.8551 T0.4882,0.8546 T0.4809,0.8541 T0.474,0.8531 T0.4692,0.8506 T0.4672,0.8467 Q0.4672,0.8437,0.468,0.8422 T0.4704,0.8398 T0.4736,0.8383 T0.4781,0.8368 T0.4838,0.8358 T0.4911,0.8353 T0.4988,0.8338 Q0.5069,0.8328,0.5142,0.8328 Q0.5174,0.8328,0.5215,0.8333 T0.5264,0.8338 Q0.5296,0.8338,0.532,0.8328 Q0.5523,0.8249,0.5604,0.8249 L0.5653,0.8249 Q0.5775,0.8269,0.5848,0.8269 Q0.5912,0.8269,0.5937,0.8259 Q0.6026,0.821,0.6253,0.821 Q0.6294,0.821,0.6334,0.822 T0.6383,0.8229 Q0.6399,0.8229,0.644,0.82 Q0.6504,0.817,0.6723,0.816 T0.6975,0.814 Q0.6999,0.814,0.7048,0.8121 T0.7129,0.8091 T0.7226,0.8071 T0.7372,0.8061 L0.7421,0.8061 Q0.7624,0.8071,0.7721,0.8071 Q0.7924,0.8071,0.7981,0.8051 Q0.8021,0.8032,0.807,0.8027 T0.8135,0.8017 T0.8151,0.8002 L0.8151,0.7992 Q0.8143,0.7962,0.8127,0.7957 T0.8045,0.7943 Q0.7997,0.7943,0.7964,0.7933 Q0.7883,0.7933,0.7676,0.7923 T0.7437,0.7903 Q0.7307,0.7893,0.7218,0.7844 Q0.7194,0.7834,0.7141,0.7819 L0.7036,0.7789 T0.693,0.7765 T0.6853,0.7755 Q0.6805,0.7755,0.6691,0.773 T0.6553,0.7705 Q0.6545,0.7705,0.6545,0.7715 Q0.6537,0.7715,0.65,0.7725 T0.6423,0.774 T0.635,0.7745 Q0.6294,0.7745,0.6257,0.773 T0.618,0.771 T0.6083,0.77 T0.601,0.7695 Q0.5985,0.7695,0.5921,0.769 T0.5795,0.7676 T0.5702,0.7666 Q0.5653,0.7656,0.5596,0.7616 T0.5539,0.7547 Q0.5539,0.7498,0.5645,0.7488 Q0.5693,0.7478,0.5852,0.7448 T0.6099,0.7418 L0.6148,0.7418 Q0.6196,0.7418,0.6253,0.7423 T0.6338,0.7428 L0.6399,0.7428 Q0.6431,0.7418,0.6488,0.7409 T0.6586,0.7394 T0.6659,0.7389 L0.6707,0.7389 Q0.6723,0.7399,0.6748,0.7399 Q0.6796,0.7399,0.6946,0.7389 T0.7161,0.7379 Q0.7251,0.7379,0.7478,0.7319 Q0.7494,0.7319,0.7539,0.7315 T0.762,0.73 T0.7697,0.727 T0.7818,0.723 T0.7948,0.7196 T0.7997,0.7151 Q0.7997,0.7141,0.7989,0.7132 Q0.7924,0.7062,0.7717,0.6978 T0.738,0.6894 Q0.7267,0.6894,0.717,0.6845 T0.7048,0.6795 Q0.6975,0.6785,0.6711,0.6766 T0.6431,0.6746 Q0.6415,0.6736,0.6144,0.6721 T0.5596,0.6686 T0.5247,0.6657 Q0.4996,0.6607,0.4874,0.6597 L0.4834,0.6597 Q0.4785,0.6597,0.4724,0.6607 T0.4631,0.6617 Q0.4574,0.6617,0.4501,0.6597 Q0.4088,0.6518,0.4006,0.6518 Q0.3933,0.6518,0.3901,0.6499 T0.3844,0.6479 T0.3739,0.6499 T0.3585,0.6518 L0.352,0.6518 Q0.3471,0.6508,0.335,0.6503 T0.3122,0.6494 T0.2912,0.6479 T0.2766,0.6449 Q0.2733,0.6439,0.2668,0.6439 Q0.2628,0.6439,0.2543,0.6444 T0.2425,0.6449 T0.2368,0.6439 L0.2344,0.6439 Q0.2336,0.6439,0.2283,0.6444 T0.2182,0.6449 Q0.2117,0.6449,0.2052,0.6439 Q0.1768,0.6409,0.1281,0.637 Q0.0989,0.634,0.0916,0.634 L0.0835,0.634 L0.0795,0.634 Q0.0722,0.634,0.0616,0.632 T0.0479,0.6301 Q0.0462,0.6301,0.0406,0.6306 T0.0308,0.6311 Q0.0032,0.6311,0.0008,0.6231 Q0,0.6202,0,0.6182 Q0,0.6083,0.0227,0.6083 Q0.03,0.6073,0.0422,0.6073 Q0.0487,0.6073,0.0608,0.6078 T0.0787,0.6083 Q0.0973,0.6083,0.103,0.6073 Q0.1062,0.6063,0.1131,0.6053 T0.1253,0.6039 T0.1375,0.6024 T0.1492,0.6014 L0.1533,0.6014 Q0.1598,0.6024,0.1723,0.6044 T0.189,0.6063 Q0.1906,0.6063,0.1922,0.6053 Q0.2214,0.6004,0.236,0.6004 Q0.2409,0.6004,0.2551,0.6009 T0.2741,0.6014 Q0.2782,0.6014,0.2859,0.6024 T0.2968,0.6034 Q0.3009,0.6034,0.3041,0.6024 Q0.3277,0.5905,0.356,0.5905 L0.3613,0.5905 L0.3666,0.5905 Q0.3731,0.5905,0.3755,0.5895 Q0.382,0.5856,0.3844,0.5846 T0.3938,0.5831 T0.4144,0.5826 T0.4428,0.5801 T0.4631,0.5776 L0.4663,0.5776 Q0.4704,0.5776,0.4785,0.5786 T0.4899,0.5796 Q0.4964,0.5796,0.5012,0.5767 Q0.5069,0.5737,0.5223,0.5737 Q0.5272,0.5737,0.5369,0.5742 T0.5499,0.5747 Q0.558,0.5747,0.5612,0.5737 Q0.5661,0.5717,0.5714,0.5712 T0.5835,0.5702 T0.5945,0.5692 T0.6018,0.5687 L0.6062,0.5687 T0.6095,0.5682 T0.6148,0.5678 Q0.6172,0.5668,0.6196,0.5653 T0.6245,0.5633 T0.631,0.5628 T0.6407,0.5638 T0.6504,0.5648 T0.6594,0.5638 T0.6675,0.5628 L0.6707,0.5628 L0.6861,0.5628 Q0.7364,0.5628,0.7494,0.5608 L0.7567,0.5608 L0.7652,0.5608 L0.7729,0.5608 Q0.7794,0.5608,0.7818,0.5589 Q0.7851,0.5569,0.7859,0.5559 Q0.7835,0.5539,0.7656,0.5519 Q0.7121,0.547,0.6975,0.543 Q0.6934,0.541,0.6853,0.5396 L0.6691,0.5366 T0.6525,0.5336 T0.6391,0.5312 Q0.6367,0.5302,0.6245,0.5257 T0.6034,0.5213 L0.5977,0.5213 Q0.5945,0.5223,0.5904,0.5223 Q0.5839,0.5223,0.5787,0.5208 T0.5685,0.5183 T0.5596,0.5173 Q0.558,0.5173,0.5552,0.5168 T0.5495,0.5163 L0.5434,0.5163 T0.5373,0.5158 T0.532,0.5148 T0.5284,0.5129 T0.5272,0.5104 L0.5272,0.5074 Q0.5304,0.5015,0.5377,0.499 T0.5629,0.4965 Q0.5734,0.4955,0.5811,0.4946 T0.5953,0.4916 T0.605,0.4896 Q0.6083,0.4886,0.6123,0.4886 Q0.6148,0.4886,0.6225,0.4891 T0.635,0.4896 L0.6456,0.4896 Q0.6837,0.4857,0.6861,0.4857 Q0.7226,0.4768,0.7283,0.4768 Q0.7307,0.4768,0.732,0.4773 T0.7372,0.4777 Q0.7502,0.4777,0.7721,0.4758 Q0.7753,0.4748,0.777,0.4738 Q0.7745,0.4718,0.768,0.4708 Q0.7599,0.4688,0.7445,0.4688 Q0.7405,0.4688,0.7324,0.4693 T0.721,0.4698 Q0.7056,0.4698,0.6999,0.4679 Q0.6861,0.4629,0.6626,0.4619 Q0.6586,0.4609,0.6533,0.4604 T0.6419,0.459 T0.6314,0.456 T0.6269,0.452 Q0.6269,0.451,0.6302,0.4491 Q0.6358,0.4451,0.6525,0.4416 T0.6788,0.4382 L0.6845,0.4382 L0.6902,0.4382 Q0.7064,0.4382,0.7145,0.4362 Q0.7202,0.4342,0.7307,0.4342 L0.7356,0.4342 L0.7409,0.4342 L0.7453,0.4342 Q0.7543,0.4342,0.7607,0.4332 Q0.7778,0.4313,0.7847,0.4288 T0.7932,0.4248 T0.7972,0.4169 T0.8045,0.4045 L0.8094,0.3986 T0.8135,0.3942 L0.8167,0.3912 T0.8191,0.3887 T0.8204,0.3867 T0.8208,0.3848 Q0.8208,0.3818,0.8167,0.3739 Q0.8127,0.3689,0.8054,0.3635 T0.7924,0.3556 T0.7778,0.3497 T0.768,0.3462 Q0.764,0.3442,0.7478,0.3427 T0.7267,0.3383 Q0.7234,0.3363,0.7044,0.3348 T0.6805,0.3323 L0.6764,0.3323 T0.6679,0.3328 T0.6586,0.3333 Q0.6521,0.3333,0.648,0.3314 Q0.6391,0.3264,0.6294,0.3264 Q0.6229,0.3264,0.6156,0.3294 Q0.6115,0.3304,0.6042,0.3304 Q0.5977,0.3304,0.5888,0.3289 T0.5738,0.3264 T0.5584,0.3225 L0.5491,0.3195 Q0.5418,0.3185,0.5162,0.3155 T0.4895,0.3121 T0.4826,0.3116 T0.4716,0.3111 T0.4607,0.3096 T0.4513,0.3066 T0.4477,0.3017 Q0.4477,0.2997,0.4485,0.2977 Q0.4526,0.2898,0.4611,0.2878 T0.4842,0.2859 Q0.4882,0.2859,0.4943,0.2864 T0.5028,0.2868 Q0.5118,0.2868,0.5215,0.2829 Q0.5401,0.276,0.5734,0.276 Q0.5831,0.276,0.5872,0.277 L0.5904,0.277 T0.5949,0.2745 T0.6034,0.272 Q0.631,0.269,0.6407,0.2661 Q0.6448,0.2641,0.6488,0.2641 T0.6573,0.2656 T0.6634,0.2671 Q0.6659,0.2671,0.6675,0.2651 Q0.6723,0.2601,0.6886,0.2552 T0.7137,0.2502 L0.7153,0.2502 L0.7178,0.2502 Q0.7218,0.2502,0.7279,0.2493 T0.7417,0.2468 T0.7526,0.2453 Q0.8208,0.2354,0.8281,0.2354 Q0.8337,0.2354,0.8394,0.2344 T0.8496,0.2315 T0.854,0.2265 Q0.854,0.2216,0.8443,0.2176 Q0.8354,0.2127,0.8216,0.2102 T0.7972,0.2072 T0.777,0.2067 T0.7648,0.2057 Q0.7632,0.2047,0.7498,0.2038 T0.7202,0.2018 T0.6983,0.1998 Q0.6918,0.1998,0.676,0.1949 T0.6537,0.1899 Q0.6496,0.1899,0.6472,0.1919 Q0.6342,0.1968,0.6123,0.1968 Q0.5961,0.1968,0.5912,0.1929 Q0.5904,0.1919,0.5892,0.1919 T0.5823,0.1944 T0.5722,0.1968 T0.5637,0.1949 Q0.5556,0.1909,0.5158,0.1869 Q0.5109,0.1869,0.5032,0.1835 T0.4915,0.18 Q0.4907,0.18,0.4891,0.181 Q0.4793,0.182,0.455,0.182 Q0.4177,0.182,0.4015,0.1771 Q0.3901,0.1741,0.3779,0.1741 Q0.3747,0.1741,0.369,0.1746 T0.3609,0.1751 Q0.3569,0.1751,0.3536,0.1741 Q0.3512,0.1741,0.3459,0.1736 T0.3358,0.1726 T0.3256,0.1716 T0.3167,0.1696 T0.3106,0.1662 Q0.3082,0.1632,0.3082,0.1592 Q0.3082,0.1543,0.3114,0.1508 T0.3204,0.1474 L0.3236,0.1474 Q0.3301,0.1494,0.3333,0.1494 Q0.339,0.1494,0.3496,0.1454 Q0.3674,0.1385,0.3869,0.1385 Q0.395,0.1385,0.4011,0.137 T0.4116,0.1355 T0.4197,0.1375 T0.425,0.1395 Q0.429,0.1395,0.4355,0.1365 Q0.4631,0.1266,0.4882,0.1266 Q0.5126,0.1266,0.5337,0.1246 Q0.5369,0.1246,0.5458,0.1207 T0.5661,0.1167 L0.5693,0.1167 Q0.5791,0.1177,0.5852,0.1187 T0.5921,0.1197 Q0.5945,0.1197,0.6002,0.1167 Q0.6026,0.1147,0.605,0.1123 T0.6091,0.1093 T0.6156,0.1088 T0.6318,0.1098 Q0.6456,0.1108,0.6569,0.1137 T0.6707,0.1167 Q0.674,0.1167,0.6772,0.1147 Q0.6821,0.1108,0.6946,0.1083 T0.721,0.1044 T0.7453,0.1029 L0.7526,0.1029 Q0.7583,0.1039,0.7652,0.1053 T0.7753,0.1068 Q0.7778,0.1068,0.7802,0.1058 Q0.7981,0.1019,0.8143,0.1019 Q0.8175,0.1019,0.8439,0.0964 T0.8735,0.091 Q0.8759,0.091,0.8836,0.089 T0.8938,0.0851 Q0.8929,0.0841,0.8905,0.0841 Q0.8832,0.0821,0.8735,0.0821 Q0.8694,0.0821,0.8629,0.0826 T0.854,0.0831 Q0.8508,0.0831,0.8491,0.0821 Q0.8475,0.0821,0.8171,0.0811 T0.7802,0.0781 T0.7571,0.0737 T0.7332,0.0712 Q0.7299,0.0712,0.7247,0.0717 T0.7161,0.0722 L0.7064,0.0722 Q0.7032,0.0712,0.7003,0.0707 T0.6959,0.0692 T0.693,0.0678 T0.6914,0.0663 T0.6902,0.0648 T0.689,0.0643 L0.6869,0.0643 L0.6845,0.0643 L0.6821,0.0643 Q0.6553,0.0643,0.6553,0.0554 T0.6796,0.0465 L0.6813,0.0465 L0.6845,0.0465 Q0.6918,0.0425,0.717,0.0376 Q0.7226,0.0366,0.7267,0.0356 T0.7324,0.0336 T0.7376,0.0317 T0.747,0.0297 Q0.7518,0.0287,0.7551,0.0287 T0.762,0.0297 T0.768,0.0307 L0.7729,0.0307 Q0.7802,0.0277,0.7997,0.0267 T0.8224,0.0237 Q0.8273,0.0227,0.8341,0.0223 T0.8475,0.0193 T0.8654,0.0143 T0.8873,0.0079 Q0.9002,0.003,0.9331,0.004 T0.97,0.004 Q0.9838,0,0.9976,0.001 Q1,0.001,1,0.003 Z M0.9935,0.996 L0.9951,0.0049 Q0.9822,0.0049,0.9716,0.0079 Q0.9659,0.0099,0.9327,0.0089 T0.8897,0.0119 Q0.8775,0.0158,0.8666,0.0183 T0.85,0.0237 Q0.8418,0.0267,0.8341,0.0272 T0.8248,0.0277 Q0.82,0.0297,0.8001,0.0312 T0.7745,0.0346 Q0.7697,0.0356,0.7664,0.0356 Q0.764,0.0356,0.7603,0.0341 T0.7534,0.0326 Q0.751,0.0326,0.7478,0.0336 Q0.7429,0.0346,0.7397,0.0356 T0.7344,0.0376 T0.7283,0.0401 T0.7178,0.0425 Q0.6926,0.0465,0.6878,0.0495 Q0.6845,0.0514,0.6764,0.0514 L0.6723,0.0514 L0.6691,0.0514 Q0.6602,0.0514,0.6602,0.0554 Q0.6602,0.0574,0.6659,0.0579 T0.6796,0.0589 T0.691,0.0603 T0.6967,0.0643 T0.7072,0.0673 Q0.7121,0.0682,0.7153,0.0682 T0.7238,0.0678 T0.7332,0.0673 Q0.7413,0.0673,0.7579,0.0692 T0.7818,0.0742 Q0.7867,0.0752,0.8171,0.0762 T0.8508,0.0781 L0.852,0.0781 T0.8601,0.0776 T0.8719,0.0772 Q0.8832,0.0772,0.8921,0.0791 Q0.8994,0.0811,0.8994,0.0841 T0.8933,0.0895 T0.8812,0.094 T0.8735,0.0959 T0.8451,0.1014 T0.8143,0.1068 Q0.7989,0.1068,0.7818,0.1098 Q0.7778,0.1108,0.7745,0.1108 T0.768,0.1103 T0.7603,0.1088 T0.7518,0.1068 L0.7461,0.1068 Q0.7315,0.1068,0.7088,0.1103 T0.6805,0.1177 Q0.6756,0.1207,0.6699,0.1207 Q0.6667,0.1207,0.6545,0.1177 T0.631,0.1147 Q0.6269,0.1137,0.6225,0.1133 T0.616,0.1128 L0.6123,0.1128 T0.6103,0.1133 L0.6083,0.1157 T0.6034,0.1197 Q0.5961,0.1246,0.5921,0.1246 Q0.5904,0.1246,0.5835,0.1231 T0.5693,0.1217 L0.5661,0.1217 Q0.558,0.1217,0.5535,0.1231 T0.5446,0.1266 T0.5345,0.1296 Q0.5126,0.1316,0.4882,0.1316 T0.438,0.1405 Q0.4298,0.1434,0.425,0.1434 Q0.4209,0.1434,0.4173,0.1419 T0.4112,0.1405 T0.4027,0.1419 T0.3869,0.1434 Q0.3682,0.1434,0.3512,0.1494 Q0.3398,0.1533,0.3333,0.1533 Q0.3285,0.1533,0.3228,0.1513 L0.3212,0.1513 Q0.3179,0.1513,0.3155,0.1538 T0.3131,0.1592 Q0.3131,0.1612,0.3147,0.1632 Q0.3171,0.1652,0.3232,0.1667 T0.3406,0.1691 T0.3544,0.1701 L0.3601,0.1701 L0.3678,0.1701 L0.3771,0.1701 Q0.3909,0.1701,0.4031,0.1731 Q0.4177,0.178,0.4558,0.178 Q0.4793,0.178,0.4882,0.1761 L0.4915,0.1761 Q0.498,0.1761,0.5061,0.179 T0.5158,0.183 Q0.5166,0.183,0.5231,0.1835 T0.5341,0.1845 T0.545,0.1855 T0.5572,0.1874 T0.5661,0.1909 Q0.5677,0.1919,0.5702,0.1919 T0.5791,0.1894 T0.588,0.1869 Q0.5921,0.1869,0.5945,0.1899 Q0.5977,0.1919,0.6123,0.1919 Q0.6334,0.1919,0.6448,0.1879 Q0.6488,0.186,0.6537,0.186 Q0.6626,0.186,0.6784,0.1909 T0.6983,0.1958 Q0.704,0.1968,0.7206,0.1973 T0.7506,0.1993 T0.7689,0.2018 L0.7705,0.2018 L0.7741,0.2018 L0.7786,0.2018 Q0.8248,0.2018,0.8475,0.2136 Q0.8597,0.2196,0.8597,0.2255 T0.8483,0.2359 T0.8281,0.2404 Q0.8208,0.2404,0.7534,0.2493 Q0.7502,0.2502,0.7425,0.2512 T0.7287,0.2532 T0.7178,0.2542 L0.7153,0.2542 L0.7137,0.2542 Q0.7064,0.2542,0.6906,0.2591 T0.6715,0.2681 T0.6626,0.272 Q0.6594,0.272,0.6541,0.2705 T0.6464,0.269 T0.6431,0.27 Q0.6318,0.273,0.6034,0.276 Q0.6018,0.276,0.5998,0.2774 T0.5957,0.2799 T0.5904,0.2809 L0.5856,0.2809 Q0.5823,0.2799,0.575,0.2799 Q0.541,0.2799,0.5239,0.2868 Q0.5134,0.2908,0.5028,0.2908 Q0.4996,0.2908,0.4935,0.2903 T0.4842,0.2898 Q0.4704,0.2898,0.4635,0.2918 T0.4534,0.2997 L0.4534,0.3007 Q0.4534,0.3037,0.459,0.3046 T0.4753,0.3066 T0.4899,0.3076 Q0.4907,0.3076,0.5166,0.3106 T0.5507,0.3155 Q0.5921,0.3264,0.605,0.3264 Q0.6107,0.3264,0.6131,0.3254 Q0.6212,0.3225,0.6294,0.3225 Q0.6407,0.3225,0.6513,0.3274 Q0.6529,0.3294,0.6569,0.3294 Q0.6594,0.3294,0.665,0.3284 T0.6748,0.3274 T0.6821,0.3284 Q0.6861,0.3294,0.6967,0.3299 T0.7165,0.3314 T0.7299,0.3343 Q0.7324,0.3363,0.7486,0.3383 T0.7697,0.3422 Q0.7713,0.3432,0.7774,0.3452 T0.7887,0.3492 T0.8005,0.3546 T0.8127,0.3625 T0.8208,0.3719 Q0.8256,0.3798,0.8256,0.3848 Q0.8256,0.3877,0.8236,0.3902 T0.8171,0.3976 T0.809,0.4075 T0.8021,0.4189 T0.7968,0.4273 T0.7867,0.4322 T0.7616,0.4382 Q0.7543,0.4392,0.7437,0.4392 L0.7364,0.4392 L0.7299,0.4392 Q0.7202,0.4392,0.7161,0.4402 Q0.7072,0.4431,0.691,0.4431 L0.6845,0.4431 L0.6788,0.4431 Q0.6707,0.4431,0.6545,0.4461 T0.6334,0.452 Q0.6399,0.456,0.6626,0.457 Q0.6878,0.458,0.7024,0.4639 Q0.7064,0.4649,0.7186,0.4649 L0.7295,0.4649 L0.7413,0.4649 Q0.7591,0.4649,0.7697,0.4669 Q0.7818,0.4688,0.7818,0.4728 Q0.7818,0.4748,0.7798,0.4763 T0.7753,0.4787 L0.7737,0.4797 L0.7729,0.4797 Q0.7494,0.4827,0.7388,0.4827 Q0.7332,0.4827,0.7307,0.4817 L0.7291,0.4817 Q0.7226,0.4817,0.6878,0.4896 Q0.6829,0.4906,0.6772,0.4911 T0.6618,0.4921 T0.646,0.4931 T0.6342,0.4936 T0.6212,0.4931 T0.6115,0.4926 Q0.6083,0.4926,0.6058,0.4931 T0.5969,0.4955 T0.5819,0.499 T0.5629,0.5005 Q0.5572,0.5005,0.5527,0.501 T0.545,0.502 T0.5397,0.503 T0.5361,0.5045 T0.5337,0.5064 T0.5328,0.5094 L0.532,0.5094 Q0.532,0.5104,0.5337,0.5109 T0.5381,0.5114 T0.5438,0.5119 T0.5515,0.5124 L0.5604,0.5124 Q0.5661,0.5134,0.5746,0.5153 T0.5912,0.5173 L0.5977,0.5173 L0.6026,0.5173 Q0.6139,0.5173,0.6273,0.5218 T0.6407,0.5272 Q0.6448,0.5282,0.6683,0.5321 T0.6991,0.5381 Q0.7137,0.543,0.7664,0.548 Q0.7908,0.55,0.7908,0.5559 Q0.7908,0.5589,0.7859,0.5628 Q0.781,0.5658,0.7713,0.5658 Q0.7689,0.5658,0.7628,0.5653 T0.7543,0.5648 T0.7502,0.5658 Q0.7364,0.5678,0.6878,0.5678 Q0.674,0.5678,0.6699,0.5668 L0.6683,0.5668 Q0.6659,0.5668,0.6606,0.5678 T0.6513,0.5687 Q0.6464,0.5687,0.6391,0.5678 Q0.6334,0.5668,0.6302,0.5668 T0.6241,0.5687 T0.6164,0.5717 Q0.6123,0.5727,0.6099,0.5727 L0.6062,0.5727 T0.6022,0.5732 T0.5953,0.5737 Q0.5904,0.5747,0.5839,0.5747 T0.5726,0.5752 T0.5629,0.5776 T0.5458,0.5796 Q0.5418,0.5796,0.5324,0.5791 T0.5191,0.5786 Q0.5077,0.5786,0.5045,0.5806 Q0.498,0.5836,0.4899,0.5836 Q0.4858,0.5836,0.4777,0.5826 T0.4655,0.5816 L0.4631,0.5816 Q0.4582,0.5816,0.4436,0.5841 T0.4144,0.5865 L0.4051,0.5865 L0.3986,0.5865 T0.3933,0.587 T0.3897,0.5875 T0.3869,0.5885 T0.3844,0.5895 T0.3812,0.591 T0.3779,0.5925 Q0.3739,0.5955,0.3633,0.5955 L0.3585,0.5955 L0.3536,0.5955 Q0.3277,0.5955,0.3066,0.6053 Q0.3017,0.6083,0.296,0.6083 Q0.2912,0.6083,0.2839,0.6068 T0.2741,0.6053 Q0.2693,0.6053,0.2547,0.6048 T0.236,0.6044 Q0.2222,0.6044,0.193,0.6103 L0.1882,0.6103 Q0.1825,0.6103,0.1703,0.6083 T0.1525,0.6063 L0.15,0.6063 Q0.1436,0.6063,0.135,0.6073 T0.1168,0.6098 T0.1038,0.6113 Q0.0973,0.6123,0.0787,0.6123 L0.06,0.6123 L0.0414,0.6123 L0.0235,0.6123 Q0.0049,0.6133,0.0049,0.6172 L0.0057,0.6222 Q0.0073,0.6261,0.043,0.6261 L0.0454,0.6261 L0.0479,0.6261 Q0.0527,0.6261,0.0633,0.6281 T0.0803,0.6301 L0.0835,0.6301 L0.0916,0.6301 Q0.0998,0.6301,0.129,0.632 Q0.1768,0.636,0.206,0.64 Q0.2117,0.6409,0.2165,0.6409 Q0.2206,0.6409,0.2263,0.6405 T0.2336,0.64 L0.2384,0.64 L0.2409,0.64 T0.251,0.6395 T0.2644,0.639 Q0.2741,0.639,0.279,0.6409 Q0.283,0.6429,0.2972,0.6439 T0.3293,0.6459 T0.3528,0.6469 L0.3577,0.6469 Q0.3642,0.6469,0.3723,0.6449 T0.3844,0.6429 Q0.3901,0.6429,0.3933,0.6454 T0.4006,0.6479 Q0.4088,0.6479,0.4517,0.6558 Q0.4582,0.6568,0.4623,0.6568 Q0.4655,0.6568,0.4716,0.6563 T0.4826,0.6558 L0.4882,0.6558 Q0.5004,0.6558,0.5255,0.6607 Q0.5328,0.6627,0.5604,0.6647 T0.6152,0.6682 T0.644,0.6696 T0.6719,0.6721 T0.7056,0.6756 Q0.7113,0.6756,0.7198,0.6805 T0.738,0.6855 Q0.7453,0.6855,0.7551,0.6879 T0.7741,0.6939 T0.7912,0.7018 T0.8029,0.7102 Q0.8054,0.7132,0.8054,0.7151 Q0.8054,0.7191,0.8009,0.7216 T0.7908,0.7255 T0.779,0.7285 T0.7721,0.731 Q0.7672,0.7329,0.7632,0.7339 T0.7551,0.7354 T0.7486,0.7369 Q0.7478,0.7369,0.7445,0.7379 T0.7388,0.7394 T0.7324,0.7404 L0.7242,0.7413 T0.7161,0.7418 Q0.7105,0.7418,0.6955,0.7428 T0.6748,0.7438 L0.6699,0.7438 L0.6667,0.7438 Q0.6659,0.7438,0.6407,0.7468 Q0.6367,0.7478,0.6334,0.7478 T0.6245,0.7468 T0.6148,0.7458 L0.6099,0.7458 Q0.6018,0.7468,0.5864,0.7498 T0.5653,0.7532 T0.5596,0.7557 Q0.5596,0.7596,0.571,0.7616 Q0.5742,0.7626,0.5803,0.7631 T0.5929,0.7641 T0.6018,0.7656 L0.6087,0.7656 T0.6192,0.7666 T0.6277,0.7695 Q0.631,0.7705,0.6342,0.7705 Q0.6383,0.7705,0.6452,0.7685 T0.6537,0.7666 L0.6553,0.7666 Q0.6602,0.7666,0.6711,0.769 T0.6853,0.7715 Q0.691,0.7715,0.7048,0.775 T0.7242,0.7814 Q0.7324,0.7854,0.7445,0.7864 Q0.7478,0.7864,0.768,0.7873 T0.7972,0.7893 Q0.7989,0.7893,0.8017,0.7898 T0.8054,0.7903 L0.8082,0.7903 T0.811,0.7908 T0.8131,0.7913 T0.8151,0.7918 L0.8167,0.7928 T0.8179,0.7943 T0.8191,0.7957 T0.8204,0.7977 T0.8208,0.8002 Q0.8208,0.8032,0.8171,0.8051 T0.8074,0.8081 T0.7997,0.8091 Q0.7932,0.8121,0.7737,0.8121 Q0.7664,0.8121,0.7413,0.8111 L0.7364,0.8111 Q0.7275,0.8111,0.7214,0.8121 T0.7084,0.8155 T0.6991,0.819 Q0.6934,0.82,0.6723,0.8205 T0.6472,0.8239 Q0.6423,0.8269,0.6375,0.8269 Q0.6358,0.8269,0.6338,0.8264 T0.6294,0.8254 T0.6253,0.8249 Q0.6042,0.8249,0.5961,0.8289 Q0.5921,0.8318,0.5839,0.8318 Q0.5783,0.8318,0.5645,0.8299 L0.5604,0.8299 Q0.5531,0.8299,0.5337,0.8368 Q0.5296,0.8378,0.5255,0.8378 Q0.5239,0.8378,0.5195,0.8373 T0.5118,0.8368 Q0.5061,0.8368,0.4996,0.8388 Q0.4947,0.8388,0.4882,0.8398 Q0.4777,0.8417,0.4753,0.8427 T0.4728,0.8467 Q0.4728,0.8487,0.4769,0.8492 T0.4886,0.8501 T0.5004,0.8516 Q0.5036,0.8516,0.515,0.8521 T0.5345,0.8546 Q0.5426,0.8556,0.5596,0.8556 L0.5649,0.8556 L0.5702,0.8556 L0.5791,0.8556 Q0.5921,0.8566,0.6135,0.8571 T0.6383,0.8586 Q0.6683,0.8615,0.6764,0.8615 L0.6776,0.8615 L0.6788,0.8615 Q0.7032,0.8615,0.7032,0.8684 Q0.7032,0.8714,0.6995,0.8749 T0.6959,0.8803 L0.6959,0.8813 Q0.6991,0.8843,0.7153,0.8843 Q0.7218,0.8843,0.7259,0.8833 L0.7299,0.8833 Q0.7324,0.8833,0.7348,0.8838 T0.7393,0.8848 T0.7433,0.8863 L0.7474,0.8882 T0.751,0.8892 Q0.7616,0.8922,0.7948,0.8952 Q0.7981,0.8952,0.8082,0.8981 T0.8297,0.9011 T0.8528,0.9026 T0.8747,0.907 T0.8856,0.9149 L0.8856,0.9159 Q0.8856,0.9179,0.8848,0.9189 T0.8828,0.9209 T0.88,0.9228 T0.8767,0.9243 T0.8731,0.9248 T0.8698,0.9253 T0.8666,0.9258 L0.8646,0.9258 Q0.8621,0.9258,0.8577,0.9263 T0.85,0.9268 T0.8439,0.9273 T0.8402,0.9278 Q0.8386,0.9298,0.8358,0.9308 T0.8305,0.9322 T0.8244,0.9332 T0.8183,0.9337 Q0.8135,0.9347,0.8025,0.9372 T0.7859,0.9416 Q0.7778,0.9436,0.7717,0.9471 T0.7624,0.9515 Q0.7575,0.9535,0.7486,0.9535 Q0.7461,0.9535,0.7397,0.953 T0.7291,0.9525 Q0.7234,0.9525,0.7186,0.953 T0.7137,0.957 T0.7194,0.9614 Q0.7567,0.9634,0.7664,0.9654 Q0.7745,0.9674,0.7762,0.9674 L0.7908,0.9674 Q0.7924,0.9664,0.7948,0.9664 Q0.8029,0.9664,0.8082,0.9679 T0.8224,0.9723 T0.8362,0.9773 Q0.8483,0.9812,0.871,0.9812 L0.8751,0.9812 Q0.8873,0.9812,0.8929,0.9822 T0.9165,0.9876 T0.9408,0.9931 Q0.9513,0.996,0.9935,0.996 Z M0.9968,0.998 L0.9976,0.003 Q0.9838,0.002,0.9708,0.0059 Q0.9659,0.0069,0.9327,0.0059 T0.8881,0.0099 Q0.8767,0.0138,0.8658,0.0163 T0.8491,0.0218 Q0.8418,0.0247,0.8341,0.0247 T0.8232,0.0257 Q0.8191,0.0277,0.7997,0.0292 T0.7737,0.0326 Q0.7697,0.0336,0.7672,0.0336 T0.7612,0.0321 T0.7543,0.0307 T0.747,0.0317 Q0.7397,0.0326,0.7336,0.0356 T0.717,0.0406 Q0.6918,0.0445,0.6861,0.0475 Q0.6845,0.0485,0.6772,0.0485 T0.6638,0.05 T0.6577,0.0549 T0.6642,0.0598 T0.6796,0.0613 T0.6902,0.0623 Q0.691,0.0623,0.6946,0.0658 T0.7064,0.0702 L0.7161,0.0702 Q0.7194,0.0702,0.7242,0.0697 T0.7332,0.0692 Q0.7413,0.0692,0.7579,0.0717 T0.781,0.0762 Q0.7867,0.0781,0.8171,0.0786 T0.85,0.0801 L0.8532,0.0801 Q0.8548,0.0801,0.8613,0.0796 T0.8727,0.0791 Q0.8832,0.0791,0.8913,0.0811 Q0.897,0.0831,0.897,0.0851 Q0.897,0.088,0.8873,0.0905 T0.8735,0.093 Q0.871,0.093,0.8447,0.0984 T0.8143,0.1039 Q0.7981,0.1039,0.781,0.1078 Q0.7778,0.1088,0.7745,0.1088 T0.7644,0.1073 T0.7518,0.1048 L0.7453,0.1048 Q0.7299,0.1048,0.7076,0.1083 T0.6788,0.1157 Q0.6748,0.1187,0.6699,0.1187 Q0.6675,0.1187,0.6557,0.1157 T0.6318,0.1118 Q0.6164,0.1108,0.6135,0.1108 T0.6095,0.1113 T0.6067,0.1142 T0.6018,0.1187 Q0.5953,0.1227,0.5921,0.1227 Q0.5904,0.1227,0.5839,0.1212 T0.5693,0.1187 L0.5661,0.1187 Q0.558,0.1187,0.5527,0.1207 T0.5434,0.1246 T0.5337,0.1266 Q0.5126,0.1296,0.4882,0.1296 T0.4371,0.1385 Q0.4298,0.1414,0.425,0.1414 Q0.4225,0.1414,0.4189,0.1395 T0.4112,0.1375 Q0.4079,0.1375,0.4019,0.1395 T0.3869,0.1414 Q0.3682,0.1414,0.3504,0.1474 Q0.3398,0.1513,0.3333,0.1513 Q0.3293,0.1513,0.3228,0.1494 L0.3204,0.1494 Q0.3179,0.1494,0.3155,0.1508 T0.3118,0.1543 T0.3106,0.1592 T0.3127,0.1642 T0.32,0.1677 T0.3305,0.1701 T0.3435,0.1716 T0.3544,0.1721 Q0.3569,0.1731,0.3601,0.1731 Q0.3625,0.1731,0.3686,0.1726 T0.3779,0.1721 Q0.3901,0.1721,0.4023,0.1751 Q0.4177,0.18,0.455,0.18 Q0.4793,0.18,0.4882,0.178 L0.4915,0.178 Q0.4964,0.178,0.5045,0.1815 T0.5158,0.185 Q0.5564,0.1889,0.5645,0.1929 Q0.5677,0.1939,0.571,0.1939 Q0.575,0.1939,0.5807,0.1919 T0.5888,0.1899 T0.5929,0.1909 Q0.5969,0.1949,0.6123,0.1949 Q0.6334,0.1949,0.6456,0.1899 Q0.6488,0.1879,0.6537,0.1879 Q0.661,0.1879,0.6772,0.1929 T0.6983,0.1978 Q0.704,0.1988,0.7206,0.1998 T0.7502,0.2018 T0.7664,0.2038 Q0.7672,0.2038,0.7818,0.2043 T0.8151,0.2067 T0.8459,0.2156 Q0.8573,0.2206,0.8573,0.2255 Q0.8573,0.2295,0.8516,0.2324 T0.8394,0.2369 T0.8281,0.2384 Q0.8208,0.2384,0.7526,0.2473 Q0.7494,0.2483,0.7421,0.2493 T0.7287,0.2512 T0.7178,0.2522 L0.7153,0.2522 L0.7137,0.2522 Q0.7056,0.2522,0.6898,0.2572 T0.6691,0.2671 Q0.6667,0.269,0.6634,0.269 Q0.661,0.269,0.6557,0.2681 T0.6472,0.2671 Q0.6448,0.2671,0.6415,0.2681 Q0.631,0.271,0.6034,0.274 Q0.5994,0.274,0.5961,0.2765 T0.5904,0.2789 L0.5864,0.2789 Q0.5831,0.2779,0.5742,0.2779 Q0.541,0.2779,0.5223,0.2849 Q0.5126,0.2888,0.5028,0.2888 Q0.5004,0.2888,0.4943,0.2883 T0.4842,0.2878 Q0.4704,0.2878,0.4627,0.2898 T0.4509,0.2987 Q0.4501,0.2997,0.4501,0.3007 Q0.4501,0.3046,0.4574,0.3066 T0.4765,0.3091 T0.4899,0.3096 Q0.4931,0.3106,0.5061,0.3121 T0.5316,0.315 T0.5499,0.3175 Q0.5507,0.3175,0.5572,0.3195 T0.5685,0.3225 T0.5803,0.3254 T0.5933,0.3279 T0.605,0.3284 T0.6148,0.3274 Q0.6221,0.3244,0.6294,0.3244 Q0.6399,0.3244,0.6496,0.3294 Q0.6529,0.3314,0.6577,0.3314 Q0.6602,0.3314,0.6663,0.3304 T0.6756,0.3294 T0.6813,0.3304 Q0.6837,0.3304,0.6873,0.3309 L0.6946,0.3318 T0.7024,0.3323 T0.7105,0.3328 T0.7178,0.3338 T0.7238,0.3348 T0.7283,0.3363 Q0.7324,0.3383,0.7482,0.3403 T0.7689,0.3442 Q0.7697,0.3442,0.779,0.3477 T0.794,0.3536 T0.8074,0.3615 T0.8183,0.3729 Q0.8232,0.3808,0.8232,0.3848 Q0.8232,0.3867,0.8216,0.3892 T0.8151,0.3961 T0.8062,0.406 T0.7997,0.4179 T0.7952,0.4263 T0.7855,0.4308 T0.7616,0.4362 Q0.7543,0.4372,0.7445,0.4372 L0.7372,0.4372 L0.7307,0.4372 Q0.7202,0.4372,0.7153,0.4382 Q0.7064,0.4402,0.691,0.4402 L0.6845,0.4402 L0.6788,0.4402 Q0.6699,0.4402,0.6533,0.4436 T0.6318,0.451 Q0.6302,0.451,0.6302,0.452 Q0.6302,0.457,0.6626,0.459 Q0.6869,0.4609,0.7015,0.4659 Q0.7064,0.4669,0.7202,0.4669 L0.7311,0.4669 L0.7429,0.4669 Q0.7599,0.4669,0.7689,0.4688 Q0.7794,0.4708,0.7794,0.4738 L0.7786,0.4748 T0.777,0.4763 T0.7749,0.4773 L0.7737,0.4777 L0.7729,0.4777 Q0.7502,0.4797,0.738,0.4797 L0.7315,0.4797 L0.7291,0.4797 Q0.7226,0.4797,0.6869,0.4876 Q0.6821,0.4886,0.6768,0.4891 T0.6614,0.4901 T0.6456,0.4916 L0.6342,0.4916 Q0.6294,0.4916,0.6217,0.4911 T0.6115,0.4906 Q0.6083,0.4906,0.6054,0.4911 T0.5961,0.4936 T0.5815,0.4965 T0.5629,0.4985 Q0.5507,0.4985,0.5442,0.4995 T0.5345,0.5025 T0.5304,0.5084 L0.5296,0.5094 Q0.5296,0.5114,0.5316,0.5124 T0.5373,0.5138 T0.5442,0.5143 L0.5523,0.5143 L0.5604,0.5143 Q0.5637,0.5153,0.5677,0.5158 T0.5742,0.5173 T0.5811,0.5188 T0.5904,0.5193 L0.5977,0.5193 L0.6026,0.5193 Q0.6107,0.5193,0.6168,0.5208 T0.6294,0.5252 T0.6399,0.5292 Q0.6448,0.5302,0.6679,0.5341 T0.6983,0.541 Q0.7129,0.545,0.7664,0.55 Q0.7883,0.5519,0.7883,0.5559 Q0.7883,0.5579,0.7843,0.5608 T0.7721,0.5638 Q0.7697,0.5638,0.764,0.5633 T0.7559,0.5628 L0.7502,0.5628 Q0.7364,0.5648,0.6869,0.5648 L0.6699,0.5648 L0.6675,0.5648 Q0.665,0.5648,0.6602,0.5658 T0.6513,0.5668 Q0.6464,0.5668,0.6399,0.5658 Q0.6342,0.5648,0.6302,0.5648 T0.6225,0.5668 T0.6156,0.5697 Q0.6107,0.5707,0.6087,0.5707 T0.6034,0.5712 T0.5953,0.5717 Q0.5904,0.5727,0.5835,0.5727 T0.5718,0.5732 T0.562,0.5757 Q0.558,0.5767,0.5483,0.5767 L0.5349,0.5767 L0.5207,0.5767 Q0.5069,0.5767,0.5028,0.5786 Q0.4972,0.5816,0.4899,0.5816 Q0.4858,0.5816,0.4781,0.5806 T0.4663,0.5796 L0.4631,0.5796 Q0.4582,0.5796,0.4432,0.5821 T0.4144,0.5846 L0.4071,0.5846 L0.4011,0.5846 T0.3958,0.5851 T0.3921,0.5856 T0.3889,0.5861 T0.3865,0.5865 T0.384,0.5875 L0.382,0.5885 L0.3796,0.5895 L0.3771,0.5905 Q0.3731,0.5925,0.365,0.5925 L0.3601,0.5925 L0.3552,0.5925 Q0.3277,0.5925,0.3049,0.6044 Q0.3017,0.6053,0.296,0.6053 Q0.292,0.6053,0.2847,0.6044 T0.2741,0.6034 Q0.2693,0.6034,0.2547,0.6029 T0.236,0.6024 Q0.2222,0.6024,0.1922,0.6073 Q0.1906,0.6083,0.1882,0.6083 Q0.1833,0.6083,0.1711,0.6063 T0.1533,0.6044 Q0.1517,0.6034,0.1492,0.6034 Q0.1436,0.6034,0.135,0.6048 T0.1168,0.6078 T0.103,0.6093 Q0.0973,0.6103,0.0787,0.6103 L0.0604,0.6103 L0.0414,0.6103 L0.0235,0.6103 Q0.0024,0.6113,0.0024,0.6172 Q0.0024,0.6192,0.0032,0.6231 Q0.0049,0.6281,0.0333,0.6281 L0.0414,0.6281 L0.0479,0.6281 Q0.0519,0.6281,0.0624,0.6301 T0.0803,0.632 L0.0835,0.632 L0.0916,0.632 Q0.0989,0.632,0.1281,0.634 L0.206,0.6419 Q0.2117,0.6429,0.2174,0.6429 Q0.2214,0.6429,0.2271,0.6424 T0.2344,0.6419 L0.2376,0.6419 Q0.2393,0.6429,0.2417,0.6429 T0.2526,0.6419 T0.2652,0.6409 Q0.2741,0.6409,0.2782,0.6429 T0.2968,0.6459 T0.3293,0.6479 T0.352,0.6489 Q0.3552,0.6499,0.3585,0.6499 Q0.365,0.6499,0.3731,0.6474 T0.3844,0.6449 Q0.3885,0.6449,0.3917,0.6474 T0.4006,0.6499 Q0.4088,0.6499,0.4509,0.6578 Q0.4574,0.6588,0.4631,0.6588 Q0.4655,0.6588,0.4716,0.6583 T0.4834,0.6578 L0.4874,0.6578 Q0.5004,0.6588,0.5255,0.6637 Q0.532,0.6647,0.56,0.6667 T0.6148,0.6701 T0.644,0.6716 Q0.6464,0.6726,0.6594,0.6736 T0.6861,0.6756 T0.7048,0.6775 Q0.7097,0.6775,0.7186,0.6825 T0.738,0.6874 Q0.7518,0.6874,0.7729,0.6958 T0.8005,0.7122 Q0.8029,0.7141,0.8029,0.7151 Q0.8029,0.7191,0.7968,0.7216 T0.7818,0.726 T0.7705,0.729 L0.764,0.731 T0.7583,0.7324 T0.753,0.7334 T0.7486,0.7349 Q0.7251,0.7399,0.7161,0.7399 Q0.7097,0.7399,0.6951,0.7409 T0.6748,0.7418 L0.6703,0.7418 L0.6667,0.7418 Q0.6659,0.7418,0.6399,0.7448 L0.6334,0.7448 Q0.631,0.7448,0.6249,0.7443 T0.6148,0.7438 L0.6099,0.7438 Q0.601,0.7448,0.5856,0.7473 T0.5645,0.7507 Q0.5572,0.7517,0.5572,0.7547 T0.5616,0.7606 T0.5702,0.7646 Q0.5734,0.7646,0.5779,0.7651 T0.5864,0.7661 T0.5949,0.7671 T0.6018,0.7676 L0.6071,0.7676 T0.6135,0.7681 T0.62,0.769 T0.6269,0.771 T0.6342,0.7725 Q0.6391,0.7725,0.6464,0.7705 T0.6541,0.7685 L0.6553,0.7685 Q0.6586,0.7685,0.6699,0.771 T0.6853,0.7735 Q0.6902,0.7735,0.704,0.777 T0.7226,0.7834 Q0.7315,0.7873,0.7445,0.7883 Q0.747,0.7883,0.7676,0.7893 T0.7972,0.7913 Q0.7989,0.7913,0.8017,0.7918 T0.8058,0.7923 T0.809,0.7928 T0.8114,0.7933 T0.8131,0.7938 T0.8147,0.7943 T0.8159,0.7953 L0.8171,0.7967 T0.8175,0.7982 Q0.8183,0.7992,0.8183,0.8002 Q0.8183,0.8022,0.8155,0.8032 T0.807,0.8051 T0.7989,0.8071 Q0.7932,0.8091,0.7737,0.8091 L0.7413,0.8091 L0.7372,0.8091 Q0.7275,0.8091,0.721,0.8101 T0.7072,0.8136 T0.6983,0.817 Q0.6942,0.818,0.6825,0.818 T0.6602,0.819 T0.6456,0.822 Q0.6415,0.8249,0.6375,0.8249 Q0.6367,0.8249,0.6326,0.8239 T0.6253,0.8229 Q0.6034,0.8229,0.5953,0.8279 Q0.5912,0.8289,0.5848,0.8289 Q0.5775,0.8289,0.5645,0.8279 Q0.5629,0.8269,0.5604,0.8269 Q0.5523,0.8269,0.5328,0.8348 Q0.5296,0.8358,0.5264,0.8358 Q0.5247,0.8358,0.5203,0.8353 T0.5126,0.8348 Q0.5061,0.8348,0.4988,0.8358 Q0.4947,0.8368,0.4882,0.8378 Q0.4826,0.8388,0.4797,0.8388 T0.474,0.8398 T0.4704,0.8422 T0.4696,0.8462 T0.472,0.8501 T0.4781,0.8521 T0.4854,0.8526 L0.4935,0.8526 T0.4996,0.8536 Q0.5036,0.8536,0.515,0.8546 T0.5337,0.8566 Q0.5426,0.8576,0.5596,0.8576 L0.5657,0.8576 L0.571,0.8576 L0.5791,0.8576 Q0.5921,0.8586,0.6135,0.8591 T0.6383,0.8605 Q0.6683,0.8635,0.6764,0.8635 Q0.6999,0.8635,0.6999,0.8684 Q0.6999,0.8694,0.6967,0.8734 T0.6934,0.8803 L0.6934,0.8823 Q0.6951,0.8863,0.7137,0.8863 Q0.7218,0.8863,0.7267,0.8853 L0.7299,0.8853 Q0.734,0.8853,0.7409,0.8882 T0.7502,0.8912 Q0.7607,0.8942,0.794,0.8971 Q0.7972,0.8971,0.8078,0.9001 T0.8297,0.9031 Q0.8475,0.9031,0.8646,0.9065 T0.8824,0.9149 L0.8824,0.9159 Q0.8824,0.9169,0.882,0.9179 T0.8808,0.9194 L0.8792,0.9204 T0.8767,0.9214 T0.8743,0.9224 T0.8715,0.9228 L0.8686,0.9228 T0.8662,0.9233 T0.8637,0.9238 L0.8556,0.9238 T0.8447,0.9243 T0.8386,0.9268 Q0.837,0.9278,0.835,0.9288 T0.8305,0.9303 T0.8244,0.9308 T0.8175,0.9318 Q0.8127,0.9327,0.8017,0.9352 T0.7851,0.9397 Q0.777,0.9416,0.7705,0.9451 T0.7616,0.9496 Q0.7575,0.9505,0.7494,0.9505 L0.7405,0.9505 L0.7307,0.9505 Q0.7234,0.9505,0.7178,0.9515 Q0.7145,0.9515,0.7125,0.9535 T0.7105,0.9575 Q0.7105,0.9624,0.7194,0.9634 Q0.7567,0.9654,0.7664,0.9674 Q0.7745,0.9693,0.7762,0.9693 L0.7908,0.9693 L0.7948,0.9693 Q0.8005,0.9693,0.8058,0.9698 T0.8143,0.9718 T0.824,0.9753 T0.8354,0.9792 Q0.8483,0.9832,0.871,0.9832 L0.8739,0.9832 L0.8767,0.9832 Q0.8873,0.9832,0.8921,0.9842 Q0.8978,0.9852,0.9157,0.9896 T0.94,0.996 Q0.9424,0.996,0.9469,0.9965 T0.9566,0.997 T0.9676,0.9975 T0.9781,0.998 L0.9874,0.998 L0.9943,0.998 L0.9968,0.998 Z",type:0}},"\uE0CC":{type:5,data:{d:"M1,0.5071 L0.8623,0.7102 L0.5865,0.7102 L0.4489,0.5071 L0.5865,0.3045 L0.8623,0.3045 Z M0.4135,0.7974 L0.2758,1 L0,1 L0,0.5944 L0.2758,0.5944 Z M0.4135,0.2026 L0.2758,0.4056 L0,0.4056 L0,0 L0.2758,0 Z",type:0}},"\uE0CD":{type:5,data:{d:"M0.9726,0.5071 L0.8451,0.3184 L0.59,0.3184 L0.4624,0.5071 L0.59,0.6955 L0.8451,0.6955 Z M1,0.5071 L0.8588,0.716 L0.5768,0.716 L0.4355,0.5071 L0.5768,0.2982 L0.8588,0.2982 Z M0.401,0.7911 L0.2735,0.6028 L0.0236,0.6028 L0.0236,0.9799 L0.2735,0.9799 Z M0.4284,0.7911 L0.2872,1 L0,1 L0,0.5826 L0.2872,0.5826 Z M0.401,0.2089 L0.2735,0.0201 L0.0236,0.0201 L0.0236,0.3972 L0.2735,0.3972 Z M0.4284,0.2089 L0.2872,0.4174 L0,0.4174 L0,0 L0.2872,0 Z",type:0}},"\uE0CE":{type:5,data:{d:"M1,0.4996 Q1,0.5365,0.9924,0.568 T0.9718,0.6177 T0.9433,0.6363 L0.9425,0.6367 Q0.9355,0.6367,0.9285,0.6326 L0.9308,0.6326 Q0.9129,0.6195,0.9013,0.587 T0.8871,0.5164 T0.8943,0.4373 T0.9254,0.3687 L0.9254,0.3691 Q0.9339,0.3628,0.9425,0.3628 Q0.9584,0.3628,0.9716,0.3811 T0.9924,0.431 T1,0.4996 Z M0.9234,0.6321 L0.8776,0.7169 Q0.8791,0.732,0.8791,0.7441 Q0.8791,0.781,0.8716,0.8123 T0.8508,0.862 T0.8224,0.8809 L0.8216,0.8809 Q0.8154,0.8809,0.8092,0.8775 L0.8088,0.8775 Q0.7905,0.8633,0.7792,0.8297 T0.766,0.7571 T0.775,0.6768 T0.8088,0.6086 L0.7431,0.6065 Q0.719,0.6313,0.7072,0.6705 T0.6965,0.7487 T0.7097,0.823 T0.7423,0.8763 L0.7913,0.8775 L0.7314,0.9895 L0.4648,0.5038 L0.7299,0.0176 L0.7882,0.1229 L0.7404,0.1212 Q0.7244,0.138,0.7136,0.1617 T0.6982,0.2108 T0.6943,0.2632 T0.7003,0.3144 T0.7159,0.3586 T0.7396,0.3909 L0.8072,0.3926 Q0.7894,0.3796,0.7777,0.3479 T0.7635,0.2789 T0.7697,0.2011 T0.7987,0.1326 Q0.8092,0.1225,0.8204,0.1225 Q0.8321,0.1225,0.8426,0.1332 T0.8609,0.1623 T0.8731,0.2062 T0.8776,0.2592 Q0.8776,0.268,0.8768,0.2815 L0.9219,0.3628 L0.8644,0.3612 Q0.8403,0.3855,0.8284,0.4247 T0.8177,0.5029 T0.8309,0.5772 T0.8632,0.6305 Z M0.6759,0.6384 Q0.6576,0.625,0.6457,0.5914 T0.6319,0.5185 T0.6407,0.4375 T0.6751,0.3691 L0.609,0.367 Q0.5849,0.3918,0.5731,0.431 T0.5626,0.509 T0.5758,0.5833 T0.6082,0.6367 Z M0.6459,0.0025 L0.391,0.4551 L0.0035,0.4551 L0.246,0 Z M0.6553,1 L0.2421,1 L0,0.5449 L0.3875,0.5449 Z",type:0}},"\uE0CF":{type:5,data:{d:"M0.4996,0 Q0.5562,0,0.5961,0.0166 T0.6364,0.0566 L0.6368,0.0575 Q0.6368,0.0646,0.6324,0.0713 L0.6324,0.0692 Q0.6196,0.0868,0.5872,0.0986 T0.5166,0.1128 T0.4373,0.1057 T0.3689,0.0747 L0.3694,0.0747 Q0.3627,0.0663,0.3627,0.0575 Q0.3627,0.0336,0.4028,0.0168 T0.4996,0 Z M0.6324,0.0768 L0.717,0.1225 Q0.7321,0.1212,0.744,0.1212 Q0.8007,0.1212,0.8406,0.1378 T0.8809,0.1779 L0.8813,0.1783 Q0.8813,0.1846,0.8778,0.1909 Q0.8636,0.2093,0.8299,0.2208 T0.7573,0.2343 T0.6769,0.2253 T0.6089,0.1913 L0.6067,0.2567 Q0.6315,0.281,0.6707,0.2928 T0.7489,0.3035 T0.8231,0.2903 T0.8764,0.258 L0.8778,0.2085 L0.9898,0.2685 L0.504,0.5352 L0.0177,0.2701 L0.1227,0.2118 L0.1213,0.2596 Q0.1457,0.2836,0.1851,0.2955 T0.2635,0.3062 T0.3377,0.293 T0.3911,0.2605 L0.3928,0.193 Q0.38,0.2106,0.3483,0.2221 T0.2792,0.2364 T0.2013,0.2303 T0.1324,0.2013 Q0.1222,0.1909,0.1222,0.1795 Q0.1222,0.156,0.1623,0.1393 T0.2591,0.1225 Q0.2679,0.1225,0.2817,0.1233 L0.3627,0.078 L0.3609,0.1359 Q0.3857,0.1598,0.4249,0.1718 T0.5031,0.1825 T0.5773,0.1693 T0.6306,0.1367 Z M0.6386,0.3242 Q0.6253,0.3427,0.5917,0.3544 T0.5186,0.3683 T0.4376,0.3595 T0.3689,0.3251 L0.3671,0.3909 Q0.3915,0.4148,0.4309,0.4268 T0.5093,0.4375 T0.5835,0.4243 T0.6368,0.3918 Z M0.0027,0.354 L0.4553,0.6091 L0.4553,0.9962 L0,0.7542 Z M1,0.3448 L1,0.7576 L0.5447,1 L0.5447,0.6128 Z",type:0}},"\uE0D0":{type:5,data:{d:"M0,0.7699 Q0,0.7172,0.0277,0.6731 T0.103,0.6032 T0.2066,0.5774 Q0.2487,0.5774,0.2865,0.5922 T0.3526,0.6339 L0.3935,0.6715 Q0.4218,0.6984,0.438,0.7336 T0.4541,0.8075 Q0.4541,0.8602,0.4264,0.9046 T0.3511,0.9745 T0.2475,1 Q0.2054,1,0.1676,0.9852 T0.1016,0.9435 L0.0606,0.9059 Q0.0317,0.879,0.0159,0.8438 T0,0.7699 Z M0.5459,0.7699 Q0.5459,0.7172,0.5736,0.6731 T0.6489,0.6032 T0.7525,0.5774 Q0.7946,0.5774,0.8324,0.5922 T0.8984,0.6339 L0.9394,0.6715 Q0.9579,0.6892,0.9714,0.7105 T0.9925,0.7567 T1,0.8075 Q1,0.8602,0.9723,0.9046 T0.897,0.9745 T0.7934,1 Q0.7513,1,0.7135,0.9852 T0.6474,0.9435 L0.6065,0.9059 Q0.5776,0.879,0.5617,0.8438 T0.5459,0.7699 Z M0,0.1925 Q0,0.1398,0.0277,0.0957 T0.103,0.0258 T0.2066,0 Q0.2487,0,0.2865,0.0148 T0.3526,0.0565 L0.3935,0.0941 Q0.4218,0.121,0.438,0.1562 T0.4541,0.2301 Q0.4541,0.2828,0.4264,0.3269 T0.3511,0.3968 T0.2475,0.4226 Q0.2054,0.4226,0.1676,0.4078 T0.1016,0.3661 L0.0606,0.3285 Q0.0317,0.3016,0.0159,0.2664 T0,0.1925 Z M0.5459,0.1925 Q0.5459,0.1398,0.5736,0.0957 T0.6489,0.0258 T0.7525,0 Q0.7946,0,0.8324,0.0148 T0.8984,0.0565 L0.9394,0.0941 Q0.9677,0.121,0.9838,0.1562 T1,0.2301 Q1,0.2828,0.9723,0.3269 T0.897,0.3968 T0.7934,0.4226 Q0.7513,0.4226,0.7135,0.4078 T0.6474,0.3661 L0.6065,0.3285 Q0.5874,0.3108,0.5741,0.2895 T0.5534,0.2433 T0.5459,0.1925 Z M0.3301,0.6403 Q0.3306,0.6403,0.3312,0.6409 L0.3272,0.6371 L0.3272,0.6371 Z M0.8759,0.6403 Q0.8765,0.6403,0.8771,0.6409 Q0.8748,0.6387,0.8725,0.6371 L0.8731,0.6371 Z M0.3301,0.0629 Q0.3306,0.0629,0.3312,0.0634 L0.3272,0.0597 L0.3272,0.0602 Z M0.8759,0.0629 Q0.8765,0.0629,0.8771,0.0634 Q0.8748,0.0613,0.8725,0.0597 Q0.8731,0.0597,0.8731,0.0602 Z M0.779,0.4113 L0.7501,0.3849 Q0.7046,0.3844,0.6636,0.3661 Q0.7132,0.407,0.779,0.4113 Z M0.7957,0.4118 Q0.8477,0.4113,0.8918,0.3874 T0.9619,0.3223 T0.9885,0.2323 L0.9585,0.2048 Q0.9538,0.2769,0.8987,0.3282 T0.7657,0.3844 Z M0.9879,0.2167 Q0.9827,0.1554,0.9388,0.1097 Q0.9585,0.1478,0.959,0.1898 Z M0.2331,0.4113 L0.2043,0.3849 Q0.1587,0.3844,0.1177,0.3661 Q0.1673,0.407,0.2331,0.4113 Z M0.2499,0.4118 Q0.2885,0.4118,0.324,0.3973 T0.3852,0.3589 T0.4264,0.3019 T0.4426,0.2323 L0.4126,0.2048 Q0.408,0.2769,0.3529,0.3282 T0.2198,0.3844 Z M0.442,0.2167 Q0.4368,0.1554,0.393,0.1097 Q0.4126,0.1478,0.4132,0.1903 Z M0.779,0.9887 L0.7501,0.9624 Q0.7046,0.9618,0.6636,0.9435 Q0.7132,0.9844,0.779,0.9887 Z M0.7957,0.9892 Q0.8477,0.9887,0.8918,0.9648 T0.9619,0.8997 T0.9885,0.8097 L0.9585,0.7823 Q0.9538,0.8543,0.8987,0.9056 T0.7657,0.9618 Z M0.9879,0.7941 Q0.9827,0.7328,0.9388,0.6871 Q0.9585,0.7253,0.959,0.7672 Z M0.2331,0.9887 L0.2043,0.9624 Q0.1587,0.9618,0.1177,0.9435 Q0.1673,0.9844,0.2331,0.9887 Z M0.2499,0.9892 Q0.3018,0.9887,0.3459,0.9648 T0.416,0.8997 T0.4426,0.8102 L0.4126,0.7823 Q0.408,0.8543,0.3529,0.9056 T0.2198,0.9618 Z M0.442,0.7941 Q0.4368,0.7328,0.393,0.6871 Q0.4126,0.7253,0.4132,0.7672 Z M0.3191,0.6452 Q0.2943,0.622,0.2643,0.6091 T0.2066,0.5962 Q0.1795,0.5962,0.1497,0.6091 T0.0949,0.6441 T0.0537,0.6995 T0.0375,0.7699 Q0.0375,0.8419,0.0941,0.8941 Q0.1189,0.9172,0.1489,0.9304 T0.2066,0.9435 Q0.2337,0.9435,0.2634,0.9304 T0.3182,0.8952 T0.3595,0.8398 T0.3756,0.7699 Q0.3756,0.6978,0.3191,0.6452 Z M0.026,0.7731 L0.026,0.7699 Q0.026,0.6935,0.0854,0.6376 Q0.0658,0.6538,0.0514,0.6739 T0.0289,0.718 T0.0202,0.7677 Z M0.0271,0.7892 L0.0208,0.7833 Q0.0254,0.8419,0.0681,0.886 Q0.0664,0.8828,0.0646,0.879 Q0.0329,0.8382,0.0271,0.7892 Z M0.3866,0.7823 Q0.382,0.8511,0.3278,0.9016 Q0.3843,0.8565,0.3918,0.7871 Z M0.3872,0.7677 L0.393,0.7726 L0.393,0.7699 Q0.393,0.7349,0.3791,0.7038 Q0.3739,0.6973,0.3681,0.6914 Q0.3866,0.7274,0.3872,0.7677 Z M0.865,0.6452 Q0.8402,0.622,0.8102,0.6091 T0.7525,0.5962 Q0.7305,0.5962,0.7072,0.6043 T0.6619,0.628 T0.6223,0.6642 T0.5941,0.7124 T0.5834,0.7699 Q0.5834,0.8419,0.6399,0.8941 Q0.6561,0.9097,0.6757,0.9207 T0.7149,0.9376 T0.7525,0.9435 Q0.7796,0.9435,0.8093,0.9304 T0.8641,0.8952 T0.9054,0.8398 T0.9215,0.7699 Q0.9215,0.6978,0.865,0.6452 Z M0.5718,0.7731 L0.5718,0.7699 Q0.5718,0.6935,0.6313,0.6376 Q0.6013,0.6618,0.584,0.6954 T0.5661,0.7677 Z M0.573,0.7892 L0.5666,0.7833 Q0.5713,0.8419,0.614,0.886 Q0.6122,0.8828,0.6105,0.879 Q0.5788,0.8382,0.573,0.7892 Z M0.9325,0.7823 Q0.9279,0.8511,0.8736,0.9016 Q0.9308,0.8565,0.9377,0.7871 Z M0.9331,0.7672 L0.9388,0.7726 L0.9388,0.7699 Q0.9388,0.7349,0.925,0.7038 Q0.9198,0.6973,0.914,0.6914 Q0.9325,0.7274,0.9331,0.7672 Z M0.3191,0.0677 Q0.2943,0.0446,0.2643,0.0317 T0.2066,0.0188 Q0.1795,0.0188,0.1497,0.0317 T0.0949,0.0667 T0.0537,0.122 T0.0375,0.1925 Q0.0375,0.2645,0.0941,0.3167 Q0.1189,0.3398,0.1489,0.353 T0.2066,0.3661 Q0.2337,0.3661,0.2634,0.353 T0.3182,0.3177 T0.3595,0.2624 T0.3756,0.1925 Q0.3756,0.1204,0.3191,0.0677 Z M0.026,0.1957 L0.026,0.1925 Q0.026,0.1161,0.0854,0.0602 Q0.0554,0.0844,0.0381,0.118 T0.0202,0.1903 Z M0.0271,0.2118 L0.0208,0.2059 Q0.0254,0.2645,0.0681,0.3086 Q0.0664,0.3054,0.0646,0.3016 Q0.0329,0.2608,0.0271,0.2118 Z M0.3866,0.2048 Q0.382,0.2737,0.3278,0.3242 Q0.3849,0.279,0.3918,0.2097 Z M0.3872,0.1903 L0.393,0.1952 L0.393,0.1925 Q0.393,0.1581,0.3791,0.1263 Q0.3739,0.1199,0.3681,0.114 Q0.3866,0.15,0.3872,0.1903 Z M0.865,0.0677 Q0.8402,0.0446,0.8102,0.0317 T0.7525,0.0188 Q0.7253,0.0188,0.6956,0.0317 T0.6408,0.0667 T0.5995,0.122 T0.5834,0.1925 Q0.5834,0.2645,0.6399,0.3167 Q0.6647,0.3398,0.6947,0.353 T0.7525,0.3661 Q0.7796,0.3661,0.8093,0.353 T0.8641,0.3177 T0.9054,0.2624 T0.9215,0.1925 Q0.9215,0.1204,0.865,0.0677 Z M0.5718,0.1957 L0.5718,0.1925 Q0.5718,0.1161,0.6313,0.0602 Q0.6013,0.0844,0.584,0.118 T0.5661,0.1903 Z M0.573,0.2118 L0.5666,0.2059 Q0.5713,0.2645,0.614,0.3086 Q0.6122,0.3054,0.6105,0.3016 Q0.5788,0.2608,0.573,0.2118 Z M0.9325,0.2048 Q0.9279,0.2737,0.8736,0.3242 Q0.9308,0.279,0.9377,0.2097 Z M0.9331,0.1898 L0.9388,0.1952 L0.9388,0.1925 Q0.9388,0.1575,0.925,0.1263 Q0.9198,0.1199,0.914,0.114 Q0.9325,0.15,0.9331,0.1898 Z",type:0}},"\uE0D1":{type:5,data:{d:"M0.7639,0.1974 L0.9593,0.1974 L0.9593,0.1443 L0.7639,0.1443 L0.7639,0.1974 Z M0.7586,0.109 L0.9727,0.109 Q0.9839,0.109,0.992,0.1153 T1,0.1291 L1,0.382 Q1,0.3906,0.9912,0.3964 T0.9727,0.4021 L0.7586,0.4021 Q0.7468,0.4021,0.739,0.3956 T0.7313,0.382 L0.7313,0.1291 Q0.7313,0.1246,0.7339,0.1207 T0.7406,0.1143 T0.7495,0.1104 T0.7586,0.109 Z M0.7639,0.6575 L0.9593,0.6575 L0.9593,0.6044 L0.7639,0.6044 L0.7639,0.6575 Z M0.7313,0.8347 L0.7313,0.5843 Q0.7313,0.5757,0.7401,0.5699 T0.7586,0.5641 L0.9706,0.5641 Q0.9818,0.5641,0.9898,0.5705 T0.9979,0.5843 L0.9979,0.8347 Q0.9979,0.8433,0.989,0.8491 T0.9706,0.8549 L0.7586,0.8549 Q0.7468,0.8549,0.739,0.8483 T0.7313,0.8347 Z M0,1 L0,0 L0.7607,0 L0.7607,1 L0,1 Z",type:0}},"\uE0D2":{type:5,data:{d:"M0.9764,0.0029 L0.3179,0.4552 L0,0.4552 L0,0 Z M1,1 L0,1 L0,0.5448 L0.3091,0.5448 Z",type:0}},"\uE0D4":{type:5,data:{d:"M0.0236,0.0029 L0.6824,0.4552 L1,0.4552 L1,0 Z M0,1 L1,1 L1,0.5448 L0.6912,0.5448 Z",type:0}},"\uEE00":{type:3,data:"M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z"},"\uEE01":{type:3,data:"M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z"},"\uEE02":{type:3,data:"M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z"},"\uEE03":{type:3,data:"M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z M.25,.15 L1,.15 L1,.85 L.25,.85 Z"},"\uEE04":{type:3,data:"M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z M0,.15 L1,.15 L1,.85 L0,.85 Z"},"\uEE05":{type:3,data:"M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z M0,.15 L.75,.15 L.75,.85 L0,.85 Z"},"\uEE06":{type:5,data:{d:"M0.6574,0.303 Q0.623,0.291,0.5852,0.285 T0.5082,0.279 T0.4311,0.285 T0.359,0.303 L0.3082,0.248 Q0.3525,0.232,0.4041,0.2235 T0.5082,0.215 T0.6123,0.2235 T0.7082,0.248 Z",type:0},scaleType:1},"\uEE07":{type:5,data:{d:"M0.8557,0.582 L0.7656,0.551 Q0.7852,0.53,0.7951,0.507 T0.8049,0.46 Q0.8049,0.424,0.782,0.3905 T0.718,0.332 T0.6221,0.293 T0.5082,0.279 L0.5082,0.215 Q0.5607,0.215,0.6123,0.2235 T0.709,0.248 T0.7918,0.287 T0.8557,0.3375 T0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.582 Z",type:0},scaleType:1},"\uEE08":{type:5,data:{d:"M0.5082,0.705 Q0.482,0.705,0.4557,0.703 T0.4049,0.697 L0.4311,0.635 Q0.4508,0.638,0.4697,0.6395 T0.5082,0.641 Q0.5672,0.641,0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 Q0.8049,0.436,0.7951,0.413 T0.7656,0.369 L0.8557,0.338 Q0.882,0.365,0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z",type:0},scaleType:1},"\uEE09":{type:5,data:{d:"M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 L0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 T0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 L0.9098,0.46 Q0.9098,0.492,0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z",type:0},scaleType:1},"\uEE0A":{type:5,data:{d:"M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.338 L0.2508,0.369 Q0.2311,0.39,0.2213,0.413 T0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 Q0.5279,0.641,0.5467,0.6395 T0.5852,0.635 L0.6115,0.697 Q0.5869,0.701,0.5607,0.703 T0.5082,0.705 Z",type:0},scaleType:1},"\uEE0B":{type:5,data:{d:"M0.1607,0.582 Q0.1344,0.555,0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.3375 T0.2246,0.287 T0.3074,0.248 T0.4041,0.2235 T0.5082,0.215 L0.5082,0.279 Q0.4492,0.279,0.3943,0.293 T0.2984,0.332 T0.2344,0.3905 T0.2115,0.46 Q0.2115,0.484,0.2213,0.507 T0.2508,0.551 Z",type:0},scaleType:1},"\uF5D0":l.LINE_H,"\uF5D1":l.LINE_V,"\uF5D2":l.FADE_RIGHT,"\uF5D3":l.FADE_LEFT,"\uF5D4":l.FADE_DOWN,"\uF5D5":l.FADE_UP,"\uF5D6":l.CURVE_DOWN_RIGHT,"\uF5D7":l.CURVE_DOWN_LEFT,"\uF5D8":l.CURVE_UP_RIGHT,"\uF5D9":l.CURVE_UP_LEFT,"\uF5DA":[l.LINE_V,l.CURVE_UP_RIGHT],"\uF5DB":[l.LINE_V,l.CURVE_DOWN_RIGHT],"\uF5DC":[l.CURVE_DOWN_RIGHT,l.CURVE_UP_RIGHT],"\uF5DD":[l.LINE_V,l.CURVE_UP_LEFT],"\uF5DE":[l.LINE_V,l.CURVE_DOWN_LEFT],"\uF5DF":[l.CURVE_DOWN_LEFT,l.CURVE_UP_LEFT],"\uF5E0":[l.LINE_H,l.CURVE_DOWN_LEFT],"\uF5E1":[l.LINE_H,l.CURVE_DOWN_RIGHT],"\uF5E2":[l.CURVE_DOWN_LEFT,l.CURVE_DOWN_RIGHT],"\uF5E3":[l.LINE_H,l.CURVE_UP_LEFT],"\uF5E4":[l.LINE_H,l.CURVE_UP_RIGHT],"\uF5E5":[l.CURVE_UP_LEFT,l.CURVE_UP_RIGHT],"\uF5E6":[l.LINE_V,l.CURVE_UP_LEFT,l.CURVE_UP_RIGHT],"\uF5E7":[l.LINE_V,l.CURVE_DOWN_LEFT,l.CURVE_DOWN_RIGHT],"\uF5E8":[l.LINE_H,l.CURVE_DOWN_LEFT,l.CURVE_UP_LEFT],"\uF5E9":[l.LINE_H,l.CURVE_DOWN_RIGHT,l.CURVE_UP_RIGHT],"\uF5EA":[l.LINE_V,l.CURVE_DOWN_RIGHT,l.CURVE_UP_LEFT],"\uF5EB":[l.LINE_V,l.CURVE_DOWN_LEFT,l.CURVE_UP_RIGHT],"\uF5EC":[l.LINE_H,l.CURVE_DOWN_RIGHT,l.CURVE_UP_LEFT],"\uF5ED":[l.LINE_H,l.CURVE_DOWN_LEFT,l.CURVE_UP_RIGHT],"\uF5EE":[l.NODE_FILL,l.NODE_STROKE],"\uF5EF":l.NODE_STROKE,"\uF5F0":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_RIGHT],"\uF5F1":[l.NODE_STROKE,l.NODE_LINE_RIGHT],"\uF5F2":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_LEFT],"\uF5F3":[l.NODE_STROKE,l.NODE_LINE_LEFT],"\uF5F4":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF5F5":[l.NODE_STROKE,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF5F6":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN],"\uF5F7":[l.NODE_STROKE,l.NODE_LINE_DOWN],"\uF5F8":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP],"\uF5F9":[l.NODE_STROKE,l.NODE_LINE_UP],"\uF5FA":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_UP],"\uF5FB":[l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_UP],"\uF5FC":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_RIGHT],"\uF5FD":[l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_RIGHT],"\uF5FE":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT],"\uF5FF":[l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT],"\uF600":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_RIGHT],"\uF601":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_RIGHT],"\uF602":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_LEFT],"\uF603":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_LEFT],"\uF604":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_RIGHT],"\uF605":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_RIGHT],"\uF606":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT],"\uF607":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT],"\uF608":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF609":[l.NODE_STROKE,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF60A":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF60B":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF60C":[l.NODE_FILL,l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\uF60D":[l.NODE_STROKE,l.NODE_LINE_UP,l.NODE_LINE_DOWN,l.NODE_LINE_LEFT,l.NODE_LINE_RIGHT],"\u{1FB00}":f(1),"\u{1FB01}":f(2),"\u{1FB02}":f(3),"\u{1FB03}":f(4),"\u{1FB04}":f(5),"\u{1FB05}":f(6),"\u{1FB06}":f(7),"\u{1FB07}":f(8),"\u{1FB08}":f(9),"\u{1FB09}":f(10),"\u{1FB0A}":f(11),"\u{1FB0B}":f(12),"\u{1FB0C}":f(13),"\u{1FB0D}":f(14),"\u{1FB0E}":f(15),"\u{1FB0F}":f(16),"\u{1FB10}":f(17),"\u{1FB11}":f(18),"\u{1FB12}":f(19),"\u{1FB13}":f(20),"\u{1FB14}":f(22),"\u{1FB15}":f(23),"\u{1FB16}":f(24),"\u{1FB17}":f(25),"\u{1FB18}":f(26),"\u{1FB19}":f(27),"\u{1FB1A}":f(28),"\u{1FB1B}":f(29),"\u{1FB1C}":f(30),"\u{1FB1D}":f(31),"\u{1FB1E}":f(32),"\u{1FB1F}":f(33),"\u{1FB20}":f(34),"\u{1FB21}":f(35),"\u{1FB22}":f(36),"\u{1FB23}":f(37),"\u{1FB24}":f(38),"\u{1FB25}":f(39),"\u{1FB26}":f(40),"\u{1FB27}":f(41),"\u{1FB28}":f(43),"\u{1FB29}":f(44),"\u{1FB2A}":f(45),"\u{1FB2B}":f(46),"\u{1FB2C}":f(47),"\u{1FB2D}":f(48),"\u{1FB2E}":f(49),"\u{1FB2F}":f(50),"\u{1FB30}":f(51),"\u{1FB31}":f(52),"\u{1FB32}":f(53),"\u{1FB33}":f(54),"\u{1FB34}":f(55),"\u{1FB35}":f(56),"\u{1FB36}":f(57),"\u{1FB37}":f(58),"\u{1FB38}":f(59),"\u{1FB39}":f(60),"\u{1FB3A}":f(61),"\u{1FB3B}":f(62),"\u{1FB3C}":{type:3,data:"M0,0.6667 L0,1 L0.5,1 Z"},"\u{1FB3D}":{type:3,data:"M0,0.6667 L0,1 L1,1 Z"},"\u{1FB3E}":{type:3,data:"M0,0.3333 L0,1 L0.5,1 Z"},"\u{1FB3F}":{type:3,data:"M0,0.3333 L0,1 L1,1 Z"},"\u{1FB40}":{type:3,data:"M0,0 L0,1 L0.5,1 Z"},"\u{1FB41}":{type:3,data:"M0,0.3333 L0.5,0 L1,0 L1,1 L0,1 Z"},"\u{1FB42}":{type:3,data:"M0,0.3333 L1,0 L1,1 L0,1 Z"},"\u{1FB43}":{type:3,data:"M0,0.6667 L0.5,0 L1,0 L1,1 L0,1 Z"},"\u{1FB44}":{type:3,data:"M0,0.6667 L1,0 L1,1 L0,1 Z"},"\u{1FB45}":{type:3,data:"M0,1 L0.5,0 L1,0 L1,1 Z"},"\u{1FB46}":{type:3,data:"M0,0.6667 L1,0.3333 L1,1 L0,1 Z"},"\u{1FB47}":{type:3,data:"M0.5,1 L1,0.6667 L1,1 Z"},"\u{1FB48}":{type:3,data:"M0,1 L1,0.6667 L1,1 Z"},"\u{1FB49}":{type:3,data:"M0.5,1 L1,0.3333 L1,1 Z"},"\u{1FB4A}":{type:3,data:"M0,1 L1,0.3333 L1,1 Z"},"\u{1FB4B}":{type:3,data:"M0.5,1 L1,0 L1,1 Z"},"\u{1FB4C}":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 L1,0.3333 Z"},"\u{1FB4D}":{type:3,data:"M0,0 L0,1 L1,1 L1,0.3333 Z"},"\u{1FB4E}":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 L1,0.6667 Z"},"\u{1FB4F}":{type:3,data:"M0,0 L0,1 L1,1 L1,0.6667 Z"},"\u{1FB50}":{type:3,data:"M0.5,0 L0,0 L0,1 L1,1 Z"},"\u{1FB51}":{type:3,data:"M0,0.3333 L0,1 L1,1 L1,0.6667 Z"},"\u{1FB52}":{type:3,data:"M0,0.6667 L0.5,1 L1,1 L1,0 L0,0 Z"},"\u{1FB53}":{type:3,data:"M0,0.6667 L1,1 L1,0 L0,0 Z"},"\u{1FB54}":{type:3,data:"M0,0.3333 L0.5,1 L1,1 L1,0 L0,0 Z"},"\u{1FB55}":{type:3,data:"M0,0.3333 L1,1 L1,0 L0,0 Z"},"\u{1FB56}":{type:3,data:"M0,0 L0.5,1 L1,1 L1,0 Z"},"\u{1FB57}":{type:3,data:"M0,0.3333 L0,0 L0.5,0 Z"},"\u{1FB58}":{type:3,data:"M0,0.3333 L0,0 L1,0 Z"},"\u{1FB59}":{type:3,data:"M0,0.6667 L0,0 L0.5,0 Z"},"\u{1FB5A}":{type:3,data:"M0,0.6667 L0,0 L1,0 Z"},"\u{1FB5B}":{type:3,data:"M0,1 L0,0 L0.5,0 Z"},"\u{1FB5C}":{type:3,data:"M0,0.6667 L0,0 L1,0 L1,0.3333 Z"},"\u{1FB5D}":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 L1,0.6667 Z"},"\u{1FB5E}":{type:3,data:"M0,1 L0,0 L1,0 L1,0.6667 Z"},"\u{1FB5F}":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 L1,0.3333 Z"},"\u{1FB60}":{type:3,data:"M0,1 L0,0 L1,0 L1,0.3333 Z"},"\u{1FB61}":{type:3,data:"M0.5,1 L0,1 L0,0 L1,0 Z"},"\u{1FB62}":{type:3,data:"M0.5,0 L1,0 L1,0.3333 Z"},"\u{1FB63}":{type:3,data:"M0,0 L1,0 L1,0.3333 Z"},"\u{1FB64}":{type:3,data:"M0.5,0 L1,0 L1,0.6667 Z"},"\u{1FB65}":{type:3,data:"M0,0 L1,0 L1,0.6667 Z"},"\u{1FB66}":{type:3,data:"M0.5,0 L1,0 L1,1 Z"},"\u{1FB67}":{type:3,data:"M0,0.3333 L1,0.6667 L1,0 L0,0 Z"},"\u{1FB68}":{type:3,data:"M0,0 L1,0 L1,1 L0,1 L0.5,0.5 Z"},"\u{1FB69}":{type:3,data:"M0,0 L0.5,0.5 L1,0 L1,1 L0,1 Z"},"\u{1FB6A}":{type:3,data:"M0,0 L1,0 L0.5,0.5 L1,1 L0,1 Z"},"\u{1FB6B}":{type:3,data:"M0,0 L1,0 L1,1 L0.5,0.5 L0,1 Z"},"\u{1FB6C}":{type:3,data:"M0,0 L0.5,0.5 L0,1 Z"},"\u{1FB6D}":{type:3,data:"M0,0 L1,0 L0.5,0.5 Z"},"\u{1FB6E}":{type:3,data:"M1,0 L1,1 L0.5,0.5 Z"},"\u{1FB6F}":{type:3,data:"M0,1 L1,1 L0.5,0.5 Z"},"\u{1FB70}":{type:0,data:[{x:1,y:0,w:1,h:8}]},"\u{1FB71}":{type:0,data:[{x:2,y:0,w:1,h:8}]},"\u{1FB72}":{type:0,data:[{x:3,y:0,w:1,h:8}]},"\u{1FB73}":{type:0,data:[{x:4,y:0,w:1,h:8}]},"\u{1FB74}":{type:0,data:[{x:5,y:0,w:1,h:8}]},"\u{1FB75}":{type:0,data:[{x:6,y:0,w:1,h:8}]},"\u{1FB76}":{type:0,data:[{x:0,y:1,w:8,h:1}]},"\u{1FB77}":{type:0,data:[{x:0,y:2,w:8,h:1}]},"\u{1FB78}":{type:0,data:[{x:0,y:3,w:8,h:1}]},"\u{1FB79}":{type:0,data:[{x:0,y:4,w:8,h:1}]},"\u{1FB7A}":{type:0,data:[{x:0,y:5,w:8,h:1}]},"\u{1FB7B}":{type:0,data:[{x:0,y:6,w:8,h:1}]},"\u{1FB7C}":{type:0,data:[{x:0,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}]},"\u{1FB7D}":{type:0,data:[{x:0,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}]},"\u{1FB7E}":{type:0,data:[{x:7,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}]},"\u{1FB7F}":{type:0,data:[{x:7,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}]},"\u{1FB80}":{type:0,data:[{x:0,y:0,w:8,h:1},{x:0,y:7,w:8,h:1}]},"\u{1FB81}":{type:0,data:[{x:0,y:0,w:8,h:1},{x:0,y:2,w:8,h:1},{x:0,y:4,w:8,h:1},{x:0,y:7,w:8,h:1}]},"\u{1FB82}":{type:0,data:[{x:0,y:0,w:8,h:2}]},"\u{1FB83}":{type:0,data:[{x:0,y:0,w:8,h:3}]},"\u{1FB84}":{type:0,data:[{x:0,y:0,w:8,h:5}]},"\u{1FB85}":{type:0,data:[{x:0,y:0,w:8,h:6}]},"\u{1FB86}":{type:0,data:[{x:0,y:0,w:8,h:7}]},"\u{1FB87}":{type:0,data:[{x:6,y:0,w:2,h:8}]},"\u{1FB88}":{type:0,data:[{x:5,y:0,w:3,h:8}]},"\u{1FB89}":{type:0,data:[{x:3,y:0,w:5,h:8}]},"\u{1FB8A}":{type:0,data:[{x:2,y:0,w:6,h:8}]},"\u{1FB8B}":{type:0,data:[{x:1,y:0,w:7,h:8}]},"\u{1FB8C}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L0.5,0 L0.5,1 L0,1 Z"},"\u{1FB8D}":{type:1,data:[[1,0],[0,1]],clipPath:"M0.5,0 L1,0 L1,1 L0.5,1 Z"},"\u{1FB8E}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L1,0.5 L0,0.5 Z"},"\u{1FB8F}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0.5 L1,0.5 L1,1 L0,1 Z"},"\u{1FB90}":{type:1,data:[[0,1],[1,0]]},"\u{1FB91}":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0.5 L1,0.5 L1,1 L0,1 Z"},{type:0,data:[{x:0,y:0,w:8,h:4}]}],"\u{1FB92}":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0 L1,0 L1,0.5 L0,0.5 Z"},{type:0,data:[{x:0,y:4,w:8,h:4}]}],"\u{1FB94}":[{type:1,data:[[0,1],[1,0]],clipPath:"M0,0 L0.5,0 L0.5,1 L0,1 Z"},{type:0,data:[{x:4,y:0,w:4,h:8}]}],"\u{1FB95}":{type:0,data:[{x:0,y:0,w:2,h:2},{x:4,y:0,w:2,h:2},{x:2,y:2,w:2,h:2},{x:6,y:2,w:2,h:2},{x:0,y:4,w:2,h:2},{x:4,y:4,w:2,h:2},{x:2,y:6,w:2,h:2},{x:6,y:6,w:2,h:2}]},"\u{1FB96}":{type:0,data:[{x:2,y:0,w:2,h:2},{x:6,y:0,w:2,h:2},{x:0,y:2,w:2,h:2},{x:4,y:2,w:2,h:2},{x:2,y:4,w:2,h:2},{x:6,y:4,w:2,h:2},{x:0,y:6,w:2,h:2},{x:4,y:6,w:2,h:2}]},"\u{1FB97}":{type:0,data:[{x:0,y:2,w:8,h:2},{x:0,y:6,w:8,h:2}]},"\u{1FB98}":{type:2,data:"M-0.25,-0.25 L1.25,1.25 M-0.25,0 L1,1.25 M-0.25,0.25 L0.75,1.25 M-0.25,0.5 L0.5,1.25 M0,-0.25 L1.25,1 M0.25,-0.25 L1.25,0.75 M0.5,-0.25 L1.25,0.5 M-0.25,0.75 L0.25,1.25 M0.75,-0.25 L1.25,0.25",strokeWidth:1},"\u{1FB99}":{type:2,data:"M-0.25,0.5 L0.5,-0.25 M-0.25,0.75 L0.75,-0.25 M-0.25,1 L1,-0.25 M-0.25,1.25 L1.25,-0.25 M0,1.25 L1.25,0 M0.25,1.25 L1.25,0.25 M0.5,1.25 L1.25,0.5 M-0.25,0.25 L0.25,-0.25 M0.75,1.25 L1.25,0.75",strokeWidth:1},"\u{1FB9A}":{type:5,data:{d:"M0,0 L.5,.5 L0,1 L1,1 L.5,.5 L1,0",type:0}},"\u{1FB9B}":{type:5,data:{d:"M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1",type:0}},"\u{1FB9C}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L0,1 Z"},"\u{1FB9D}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,0 L1,1 Z"},"\u{1FB9E}":{type:1,data:[[1,0],[0,1]],clipPath:"M1,0 L1,1 L0,1 Z"},"\u{1FB9F}":{type:1,data:[[1,0],[0,1]],clipPath:"M0,0 L1,1 L0,1 Z"},"\u{1FBA0}":{type:2,data:"M.5,0 L0,.5",strokeWidth:1},"\u{1FBA1}":{type:2,data:"M.5,0 L1,.5",strokeWidth:1},"\u{1FBA2}":{type:2,data:"M0,.5 L.5,1",strokeWidth:1},"\u{1FBA3}":{type:2,data:"M1,.5 L.5,1",strokeWidth:1},"\u{1FBA4}":{type:2,data:"M.5,0 L0,.5 L.5,1",strokeWidth:1},"\u{1FBA5}":{type:2,data:"M.5,0 L1,.5 L.5,1",strokeWidth:1},"\u{1FBA6}":{type:2,data:"M0,.5 L.5,1 L1,.5",strokeWidth:1},"\u{1FBA7}":{type:2,data:"M0,.5 L.5,0 L1,.5",strokeWidth:1},"\u{1FBA8}":{type:2,data:"M.5,0 L0,.5 M1,.5 L.5,1",strokeWidth:1},"\u{1FBA9}":{type:2,data:"M.5,0 L1,.5 M0,.5 L.5,1",strokeWidth:1},"\u{1FBAA}":{type:2,data:"M.5,0 L1,.5 L.5,1 L0,.5",strokeWidth:1},"\u{1FBAB}":{type:2,data:"M.5,0 L0,.5 L.5,1 L1,.5",strokeWidth:1},"\u{1FBAC}":{type:2,data:"M0,.5 L.5,0 L1,.5 L.5,1",strokeWidth:1},"\u{1FBAD}":{type:2,data:"M1,.5 L.5,0 L0,.5 L.5,1",strokeWidth:1},"\u{1FBAE}":{type:2,data:"M.5,0 L1,.5 L.5,1 L0,.5 Z",strokeWidth:1},"\u{1FBAF}":{type:2,data:"M0,.5 L1,.5 M.5,.35 L.5,.65",strokeWidth:1},"\u{1FBB0}":{type:3,data:"M0.1,0.2 L0.1,.8 L.4,.6 L.9,0.6 Z",scaleType:1},"\u{1FBB1}":{type:4,data:{d:"M.1,.525 L.35,.675 L.9,.35",type:1},scaleType:1},"\u{1FBB2}":{type:3,data:"M.29,.27 L0.13,.56 L.22,.59 L.35,0.35 L.67,.35 L.57,.57 L.71,.76 L.22,.76 L.42,1 L.53,.98 L.43,.86 L.9,.86 L.71,.6 L1,.6 L1,.52 L.83,.52 L.92,.36 L1,.36 L1,.27Z M.99,.13 A.12,.12,0,1,1,.75,.13 A.12,.12,0,1,1,.99,.13"},"\u{1FBB3}":{type:3,data:"M0,.27 L.3,.27 L.55,.12 L.63,.18 L.33,.36 L0,.36 M0,.52 L.33,.52 L.59,.89 L.73,.89 L.73,.98 L.53,.98 L.28,.6 L0,.6"},"\u{1FBB4}":{type:4,data:{d:"M.15,.55 L.5,.4 L.5,.45 L.65,.45 L.65,.35 L.85,.35 L.85,.625 L.5,.625 L.5,.7 Z",type:0},scaleType:1},"\u{1FBB5}":[{type:5,data:{d:"M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z",type:0}},{type:5,data:{d:"M.15,.5 L.5,.35 L.5,.425 L.85,.425 L.85,.575 L.5,.575 L.5,.65 Z",type:0},scaleType:1}],"\u{1FBB6}":[{type:5,data:{d:"M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z",type:0}},{type:5,data:{d:"M.85,.5 L.5,.35 L.5,.425 L.15,.425 L.15,.575 L.5,.575 L.5,.65 Z",type:0},scaleType:1}],"\u{1FBB7}":[{type:5,data:{d:"M.875,0 L1,0 L1,1 L.875,1 Z",type:0}},{type:5,data:{d:"M.5,.675 L.2,.5 L.35,.5 L.35,.325 L.65,.325 L.65,.5 L.8,.5 Z",type:0},scaleType:1}],"\u{1FBB8}":[{type:5,data:{d:"M.875,0 L1,0 L1,1 L.875,1 Z",type:0}},{type:5,data:{d:"M.5,.325 L.2,.5 L.35,.5 L.35,.675 L.65,.675 L.65,.5 L.8,.5 Z",type:0},scaleType:1}],"\u{1FBB9}":{type:5,data:{d:"M1,.89 L.11,.89 L.11,.37 L.36,.12 L.74,.12 L.96,.34 L1,.34 L1,.405 L.92,.405 L.69,.185 L.41,.185 L.21,.42 L.21,.825 L1,.825 Z",type:0}},"\u{1FBBA}":{type:5,data:{d:"M0,.89 L0,.825 L.78,.825 L.78,.53 L.7,.415 L0,.415 L0,.35 L.75,.35 L.88,.48 L.88,.89 Z",type:0}},"\u{1FBBB}":{type:5,data:{d:"M.31,.275 L.44,.275 L.44,.47 L.05,.47 L.05,.405 L.31,.405 Z M.56,.275 L.69,.275 L.69,.405 L.95,.405 L.95,.47 L.56,.47 Z M.05,.53 L.44,.53 L.44,.725 L.31,.725 L.31,.595 L.05,.595 Z M.56,.53 L.95,.53 L.95,.595 L.69,.595 L.69,.725 L.56,.725 Z",type:0},scaleType:1},"\u{1FBBC}":[{type:3,data:"M0,0 L1,0 L1,1 L0,1 L0,.935 L.885,.935 L.885,.065 L0,.065 Z"},{type:3,data:"M.67,.5 A.17,.085,0,1,1,.33,.5 A.17,.085,0,1,1,.67,.5",scaleType:1}],"\u{1FBBD}":{type:4,data:{d:"M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1 Z",type:1}},"\u{1FBBE}":{type:4,data:{d:"M1,.5 L.5,1",type:1}},"\u{1FBBF}":{type:4,data:{d:"M.5,0 L1,.5 L.5,1 L0,.5 Z",type:1}},"\u{1FBC0}":{type:3,data:"M.16,.445 A.02,.01,0,0,1,.39,.33 L.5,.375 L.61,.33 A.02,.01,0,0,1,.84,.445 L.75,.5 L.84,.555 A.02,.01,0,0,1,.61,.67 L.5,.625 L.39,.67 A.02,.01,0,0,1,.16,.555 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z",scaleType:1},"\u{1FBC1}":{type:3,data:"M0.142308,0.924 Q0.130770,0.924,0.120193,0.922 T0.101924,0.916 T0.089424,0.9065 T0.084616,0.895 V0.025 Q0.084616,0.019,0.089424,0.0135 T0.101924,0.004 T0.120193,-0.002 T0.142308,-0.004 Q0.150000,-0.004,0.157693,-0.003 T0.173078,0.000 L0.430770,0.085 Q0.451924,0.059,0.494232,0.041 T0.587501,0.013 T0.692309,-0.0005 T0.800001,-0.004 H1 V0.055 H0.800001 Q0.771155,0.055,0.742309,0.056 T0.685578,0.060 T0.630770,0.0685 T0.580770,0.0825 T0.543270,0.1045 T0.528847,0.133 V0.647 H0.530770 Q0.596155,0.645,0.659616,0.638 T0.782693,0.6165 T0.893270,0.5805 T1,0.531 V0.623 Q0.934616,0.644,0.880770,0.6595 T0.769232,0.685 T0.650963,0.700 T0.528847,0.707 V0.787 Q0.528847,0.802,0.543270,0.8155 T0.580770,0.8375 T0.630770,0.8515 T0.685578,0.860 T0.742309,0.864 T0.800001,0.865 H1 V0.924 H0.800001 Q0.746155,0.924,0.692309,0.9205 T0.587501,0.907 T0.494232,0.879 T0.430770,0.835 L0.173078,0.920 Q0.165386,0.922,0.157693,0.923 T0.142308,0.924 Z M0.2,0.841 L0.415385,0.770 V0.150 L0.2,0.079 V0.841 Z M1,0.698 Q0.973077,0.694,0.969231,0.6885 T0.965385,0.677 Q0.965385,0.672,0.969231,0.6665 T1,0.657 V0.698 Z"},"\u{1FBC2}":{type:3,data:"M0,0.623 V0.531 Q0.044231,0.512,0.0625,0.4905 T0.092308,0.4465 T0.108654,0.4005 T0.113462,0.354 V0.351 Q0.113462,0.345,0.117308,0.3395 T0.129808,0.3305 T0.148846,0.3245 T0.171154,0.323 H0.501923 Q0.532692,0.323,0.563462,0.3185 T0.617308,0.303 T0.651923,0.2765 T0.663462,0.2435 V0.241 Q0.663462,0.219,0.657692,0.197 T0.639423,0.1545 T0.604808,0.115 T0.549615,0.082 T0.475577,0.0615 T0.392308,0.055 H0 V-0.004 H1 V0.055 H0.667308 Q0.694231,0.071,0.713462,0.09 T0.746154,0.129 T0.766346,0.1705 T0.775,0.213 H1 V0.316 Q0.971154,0.305,0.955769,0.2965 T0.920192,0.2825 T0.876923,0.2745 T0.830769,0.272 H0.773077 Q0.767308,0.297,0.743269,0.319 T0.680769,0.355 T0.595192,0.375 T0.501923,0.381 H0.227308 Q0.223462,0.415,0.211,0.4485 T0.172692,0.5135 T0.108269,0.573 T0,0.623 Z M0.611538,0.924 H0 V0.865 H0.611538 Q0.642308,0.865,0.673077,0.8605 T0.727885,0.845 T0.762308,0.8185 T0.773077,0.787 V0.785 Q0.773077,0.769,0.762308,0.7535 T0.727885,0.727 T0.673077,0.7115 T0.611538,0.707 H0.059615 Q0.048077,0.707,0.037115,0.7045 T0,0.694 V0.653 Q0.026923,0.648,0.037115,0.646 T0.059615,0.644 H0.722692 Q0.753462,0.644,0.784231,0.6395 T0.839038,0.624 T0.873846,0.5975 T0.884615,0.566 V0.564 Q0.884615,0.548,0.873846,0.532 T0.839038,0.5055 T0.784231,0.49 T0.722692,0.4855 H0.392308 Q0.380769,0.4855,0.370192,0.483 T0.351923,0.4765 T0.339423,0.467 T0.334615,0.4555 T0.339423,0.444 T0.351923,0.4345 T0.370192,0.428 T0.392308,0.4255 H0.832692 Q0.855769,0.4255,0.878846,0.4235 T0.922115,0.416 T0.957692,0.4015 T1,0.3815 V0.4685 Q0.975,0.4705,0.966346,0.4725 T0.95,0.4765 Q0.961538,0.4835,0.969231,0.4915 T1,0.5075 V0.6205 Q0.965385,0.6455,0.926923,0.665 T0.84,0.6935 Q0.866923,0.7115,0.877596,0.7345 T0.894231,0.7855 V0.787 Q0.894231,0.815,0.876923,0.8425 T0.820192,0.8895 T0.726923,0.916 T0.611538,0.924 Z"},"\u{1FBC3}":{type:3,data:"M0,0.473 V0.386 Q0.025490,0.378,0.028431,0.3695 T0.031373,0.352 V0.35 Q0.031373,0.342,0.028431,0.333 T0,0.316 V0.213 H0.652941 Q0.684314,0.213,0.715686,0.2085 T0.770588,0.193 T0.804902,0.1665 T0.815686,0.135 V0.133 Q0.815686,0.117,0.804902,0.101 T0.770588,0.0745 T0.715686,0.0595 T0.652941,0.055 H0 V-0.004 H0.652941 Q0.707843,-0.004,0.761765,0.004 T0.856863,0.031 T0.915686,0.0775 T0.933333,0.133 V0.135 Q0.933333,0.163,0.915686,0.1905 T0.856863,0.237 T0.761765,0.264 T0.652941,0.272 H0.109804 Q0.131373,0.29,0.140196,0.31 T0.149020,0.35 V0.352 Q0.149020,0.37,0.142157,0.388 T0.118627,0.4225 T0.076471,0.452 T0,0.473 Z M0,0.625 V0.513 Q0.029412,0.526,0.032353,0.54 T0.035294,0.568 V0.57 Q0.035294,0.584,0.032353,0.598 T0,0.625 Z"},"\u{1FBC4}":{type:3,data:"M0.019231,1.085 V-0.165 H0.980769 V1.085 H0.019231 Z M0.446154,0.527 H0.553846 Q0.553846,0.511,0.5625,0.495 T0.591346,0.466 T0.631731,0.4405 T0.666346,0.4135 T0.688462,0.3825 T0.696154,0.35 Q0.696154,0.33,0.682692,0.311 T0.641346,0.2785 T0.575,0.259 T0.498077,0.253 T0.421154,0.259 T0.356731,0.279 T0.315385,0.3125 T0.301923,0.352 V0.357 H0.409615 V0.354 Q0.409615,0.345,0.414423,0.335 T0.431731,0.318 T0.4625,0.3075 T0.498077,0.304 Q0.517308,0.304,0.534615,0.307 T0.564423,0.3165 T0.582692,0.332 T0.588462,0.35 Q0.588462,0.366,0.572115,0.3805 T0.535577,0.4075 T0.496154,0.4335 T0.465385,0.4625 T0.45,0.4945 T0.446154,0.527 Z M0.5,0.667 Q0.519231,0.667,0.5375,0.664 T0.569231,0.654 T0.588462,0.637 T0.594231,0.617 T0.588462,0.5975 T0.569231,0.581 T0.5375,0.571 T0.5,0.568 T0.4625,0.571 T0.430769,0.581 T0.411538,0.5975 T0.405769,0.617 T0.411538,0.637 T0.430769,0.654 T0.4625,0.664 T0.5,0.667 Z",scaleType:1},"\u{1FBC5}":{type:5,data:{d:"M.44,.425 L.44,.51 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.51 L.56,.425 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC6}":{type:5,data:{d:"M.44,.425 L.44,.46 L.23,.385 L.19,.43 L.44,.51 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.61,.735 L.7,.71 L.56,.56 L.56,.51 L.81,.43 L.77,.385 L.56,.46 L.56,.425 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC7}":{type:5,data:{d:"M.44,.425 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.425 Z M.18,.53 L.23,.575 L.81,.43 L.77,.385 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC8}":{type:5,data:{d:"M.44,.425 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.62,.735 L.71,.71 L.56,.56 L.56,.425 Z M.23,.385 L.18,.43 L.77,.575 L.81,.53 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBC9}":{type:5,data:{d:"M.44,.425 L.45,.495 L.15,.645 L.34,.645 L.34,.7 L.44,.7 L.44,.645 L.56,.645 L.56,.7 L.66,.7 L.66,.645 L.84,.645 L.54,.495 L.56,.425 Z M.39,.6 L.5,.55 L.60,.6 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z",type:0},scaleType:1},"\u{1FBCA}":{type:5,data:{d:"M.26,.375 L.5,.255 L.74,.375 L.74,.665 L.5,.55 L.26,.665 Z M.37,.395 L.37,.54 L.5,.475 L.63,.54 L.63,.395 L.5,.33 Z",type:0},scaleType:1},"\u{1FBCB}":{type:5,data:{d:"M.09,.41 L.32,.295 L.5,.375 L.68,.295 L.91,.41 L.75,.5 L.91,.59 L.68,.705 L.5,.625 L.32,.705 L.09,.59 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z",type:0},scaleType:1},"\u{1FBCC}":{type:5,data:{d:"M.55,.305 L.88,.305 L.88,.355 L.65,.355 L.65,.47 L.88,.47 L.88,.52 L.55,.52 Z",type:0},scaleType:1},"\u{1FBCD}":{type:5,data:{d:"M.38,.39 L.5,.33 L.62,.39 L.62,.53 L.5,.47 L.38,.53 Z",type:0},scaleType:1},"\u{1FBCE}":{type:3,data:"M0,0 L0.6667,0 L0.6667,1 L0,1 Z"},"\u{1FBCF}":{type:3,data:"M0,0 L0.3333,0 L0.3333,1 L0,1 Z"},"\u{1FBD0}":{type:2,data:"M1,.5 L0,1",strokeWidth:1},"\u{1FBD1}":{type:2,data:"M1,0 L0,.5",strokeWidth:1},"\u{1FBD2}":{type:2,data:"M0,0 L1,.5",strokeWidth:1},"\u{1FBD3}":{type:2,data:"M0,.5 L1,1",strokeWidth:1},"\u{1FBD4}":{type:2,data:"M0,0 L.5,1",strokeWidth:1},"\u{1FBD5}":{type:2,data:"M.5,0 L1,1",strokeWidth:1},"\u{1FBD6}":{type:2,data:"M1,0 L.5,1",strokeWidth:1},"\u{1FBD7}":{type:2,data:"M.5,0 L0,1",strokeWidth:1},"\u{1FBD8}":{type:2,data:"M0,0 L.5,.5 L1,0",strokeWidth:1},"\u{1FBD9}":{type:2,data:"M1,0 L.5,.5 L1,1",strokeWidth:1},"\u{1FBDA}":{type:2,data:"M0,1 L.5,.5 L1,1",strokeWidth:1},"\u{1FBDB}":{type:2,data:"M0,0 L.5,.5 L0,1",strokeWidth:1},"\u{1FBDC}":{type:2,data:"M0,0 L.5,1 L1,0",strokeWidth:1},"\u{1FBDD}":{type:2,data:"M1,0 L0,.5 L1,1",strokeWidth:1},"\u{1FBDE}":{type:2,data:"M0,1 L.5,0 L1,1",strokeWidth:1},"\u{1FBDF}":{type:2,data:"M0,0 L1,.5 L0,1",strokeWidth:1},"\u{1FBE0}":{type:5,data:{d:"M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0",type:1}},"\u{1FBE1}":{type:5,data:{d:"M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1",type:1}},"\u{1FBE2}":{type:5,data:{d:"M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1",type:1}},"\u{1FBE3}":{type:5,data:{d:"M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1",type:1}},"\u{1FBE4}":{type:0,data:[{x:2,y:0,w:4,h:4}]},"\u{1FBE5}":{type:0,data:[{x:2,y:4,w:4,h:4}]},"\u{1FBE6}":{type:0,data:[{x:0,y:2,w:4,h:4}]},"\u{1FBE7}":{type:0,data:[{x:4,y:2,w:4,h:4}]},"\u{1FBE8}":{type:5,data:{d:"M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0 Z",type:0}},"\u{1FBE9}":{type:5,data:{d:"M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1 Z",type:0}},"\u{1FBEA}":{type:5,data:{d:"M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1 Z",type:0}},"\u{1FBEB}":{type:5,data:{d:"M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1 Z",type:0}},"\u{1FBEC}":{type:5,data:{d:"M1,0 L.5,0 C.5,.276,.724,.5,1,.5 Z",type:0}},"\u{1FBED}":{type:5,data:{d:"M0,1 L.5,1 C.5,.724,.276,.5,0,.5 Z",type:0}},"\u{1FBEE}":{type:5,data:{d:"M1,1 L1,.5 C.724,.5,.5,.724,.5,1 Z",type:0}},"\u{1FBEF}":{type:5,data:{d:"M0,0 L0,.5 C.276,.5,.5,.276,.5,0 Z",type:0}},"\u{1FBF0}":{type:5,data:{d:o0(126),type:0},scaleType:1},"\u{1FBF1}":{type:5,data:{d:o0(48),type:0},scaleType:1},"\u{1FBF2}":{type:5,data:{d:o0(109),type:0},scaleType:1},"\u{1FBF3}":{type:5,data:{d:o0(121),type:0},scaleType:1},"\u{1FBF4}":{type:5,data:{d:o0(51),type:0},scaleType:1},"\u{1FBF5}":{type:5,data:{d:o0(91),type:0},scaleType:1},"\u{1FBF6}":{type:5,data:{d:o0(95),type:0},scaleType:1},"\u{1FBF7}":{type:5,data:{d:o0(114),type:0},scaleType:1},"\u{1FBF8}":{type:5,data:{d:o0(127),type:0},scaleType:1},"\u{1FBF9}":{type:5,data:{d:o0(123),type:0},scaleType:1},"\u{1FBFA}":{type:5,data:{d:"M.5,.175 C.2,.175,.15,.305,.15,.435 L.05,.63 L.35,.63 C.35,.682,.42,.76,.5,.76 C.58,.76,.65,.682,.65,.63 L.95,.63 L.85,.435 C.85,.305,.8,.175,.5,.175 Z",type:0},scaleType:1},...Object.fromEntries(Array.from({length:256},(i5,e)=>[String.fromCodePoint(10240+e),{type:6,data:e}]))},$e=new Set([9617,9618,9619,129932,129933,129934,129935,129936,129937,129938,129940,129948,129949,129950,129951]);function f(i5){return{type:2,data:()=>{let e=[],r=[.375,.25,.375],s=[0,3/8,5/8];for(let a=0;a<3;a++){let o=i5>>a*2&1,n=i5>>a*2+1&1;o&&n?e.push(`M0,${s[a]} L1,${s[a]} L1,${s[a]+r[a]} L0,${s[a]+r[a]} Z`):o?e.push(`M0,${s[a]} L${.5},${s[a]} L${.5},${s[a]+r[a]} L0,${s[a]+r[a]} Z`):n&&e.push(`M${.5},${s[a]} L1,${s[a]} L1,${s[a]+r[a]} L${.5},${s[a]+r[a]} Z`)}return e.join(" ")}}}function o0(i5){let e=[];if(i5&64){let Q=.21500000000000002,m=.95-.15-.015;e.push(`M${Q},${.2125} L${Q+.075},${.175} L${m-.075},${.175} L${m},${.2125} L${m-.075},${.25} L${Q+.075},${.25} Z`)}if(i5&32){let c=.7999999999999999,u=.95-.15/2,C=.95,Q=.175+.075+.015,m=.5-.015;e.push(`M${u},${Q} L${C},${Q+.0375} L${C},${m-.0375} L${u},${m} L${c},${m-.0375} L${c},${Q+.0375} Z`)}if(i5&16){let c=.7999999999999999,u=.95-.15/2,C=.95,Q=.5+.015,m=.825-.075-.015;e.push(`M${u},${Q} L${C},${Q+.0375} L${C},${m-.0375} L${u},${m} L${c},${m-.0375} L${c},${Q+.0375} Z`)}if(i5&8){let Q=.21500000000000002,m=.95-.15-.015;e.push(`M${Q},${.7875} L${Q+.075},${.75} L${m-.075},${.75} L${m},${.7875} L${m-.075},${.825} L${Q+.075},${.825} Z`)}if(i5&4&&e.push(`M${.125},${.515} L${.2},${.515+.0375} L${.2},${.735-.0375} L${.125},${.735} L${.05},${.735-.0375} L${.05},${.515+.0375} Z`),i5&2&&e.push(`M${.125},${.265} L${.2},${.265+.0375} L${.2},${.485-.0375} L${.125},${.485} L${.05},${.485-.0375} L${.05},${.265+.0375} Z`),i5&1){let Q=.21500000000000002,m=.95-.15-.015;e.push(`M${Q},${.5} L${Q+.075},${.4625} L${m-.075},${.4625} L${m},${.5} L${m-.075},${.5375} L${Q+.075},${.5375} Z`)}return e.join(" ")}var k=0,H=0,T0=false,_0=false,$0=false,z,x0=0,G0=class{constructor(e,t,r,s,a,o){this._terminal=e;this._optionService=t;this._selectionRenderModel=r;this._decorationService=s;this._coreBrowserService=a;this._themeService=o;this.result={fg:0,bg:0,ext:0}}resolve(e,t,r,s,a){;this.result.bg=e.bg,this.result.fg=e.fg,this.result.ext=e.bg&268435456?e.extended.ext:0,H=0,k=0,_0=false,T0=false,$0=false,z=this._themeService.colors,x0=0;let o=e.getCode();if(o!==0&&e.extended.underlineStyle===4){let n=Math.max(1,Math.floor(this._optionService.rawOptions.fontSize*this._coreBrowserService.dpr/15));x0=t*s%(Math.round(n)*2)}if(x0===0&&$e.has(o)&&(x0=t*s%2*2+r*a%2),this._decorationService.forEachDecorationAtCell(t,r,"bottom",n=>{;n.backgroundColorRGB&&(H=n.backgroundColorRGB.rgba>>8&16777215,_0=true),n.foregroundColorRGB&&(k=n.foregroundColorRGB.rgba>>8&16777215,T0=true)}),$0=this._selectionRenderModel.isCellSelected(this._terminal,t,r),$0){if(this.result.fg&67108864||(this.result.bg&50331648)!==0){if(this.result.fg&67108864)switch(this.result.fg&50331648){case 16777216:case 33554432:H=this._themeService.colors.ansi[this.result.fg&255].rgba;break;case 50331648:H=(this.result.fg&16777215)<<8|255;break;case 0:default:H=this._themeService.colors.foreground.rgba}else switch(this.result.bg&50331648){case 16777216:case 33554432:H=this._themeService.colors.ansi[this.result.bg&255].rgba;break;case 50331648:H=(this.result.bg&16777215)<<8|255;break}H=d0.blend(H,(this._coreBrowserService.isFocused?z.selectionBackgroundOpaque:z.selectionInactiveBackgroundOpaque).rgba&4294967040|128)>>8&16777215}else H=(this._coreBrowserService.isFocused?z.selectionBackgroundOpaque:z.selectionInactiveBackgroundOpaque).rgba>>8&16777215;if(_0=true,z.selectionForeground&&(k=z.selectionForeground.rgba>>8&16777215,T0=true),V0(e.getCode())){if(this.result.fg&67108864&&(this.result.bg&50331648)===0)k=(this._coreBrowserService.isFocused?z.selectionBackgroundOpaque:z.selectionInactiveBackgroundOpaque).rgba>>8&16777215;else{if(this.result.fg&67108864)switch(this.result.bg&50331648){case 16777216:case 33554432:k=this._themeService.colors.ansi[this.result.bg&255].rgba;break;case 50331648:k=(this.result.bg&16777215)<<8|255;break}else switch(this.result.fg&50331648){case 16777216:case 33554432:k=this._themeService.colors.ansi[this.result.fg&255].rgba;break;case 50331648:k=(this.result.fg&16777215)<<8|255;break;case 0:default:k=this._themeService.colors.foreground.rgba}k=d0.blend(k,(this._coreBrowserService.isFocused?z.selectionBackgroundOpaque:z.selectionInactiveBackgroundOpaque).rgba&4294967040|128)>>8&16777215}T0=true}};this._decorationService.forEachDecorationAtCell(t,r,"top",n=>{;n.backgroundColorRGB&&(H=n.backgroundColorRGB.rgba>>8&16777215,_0=true),n.foregroundColorRGB&&(k=n.foregroundColorRGB.rgba>>8&16777215,T0=true)}),_0&&($0?H=e.bg&-16777216&-134217729|H|50331648:H=e.bg&-16777216|H|50331648),T0&&(k=e.fg&-16777216&-67108865|k|50331648),this.result.fg&67108864&&(_0&&!T0&&((this.result.bg&50331648)===0?k=this.result.fg&-134217728|z.background.rgba>>8&16777215&16777215|50331648:k=this.result.fg&-134217728|this.result.bg&67108863,T0=true),!_0&&T0&&((this.result.fg&50331648)===0?H=this.result.bg&-67108864|z.foreground.rgba>>8&16777215&16777215|50331648:H=this.result.bg&-67108864|this.result.fg&67108863,_0=true)),z=void 0,this.result.bg=_0?H:this.result.bg,this.result.fg=T0?k:this.result.fg,this.result.ext&=536870911,this.result.ext|=x0<<29&3758096384}};var Ke=.5,K0=Be||we?"bottom":"ideographic";function qe(i5,e,t,r,s,a,o,n,L,T,h,d,_=0){let p=Ve[e];if(p){let c=Array.isArray(p)?p:[p];for(let u of c)Pt(i5,u,t,r,s,a,o,n,L,T,h,d,_);return true}return false}function Pt(i5,e,t,r,s,a,o,n,L,T,h,d,_=0){let p=s,c=a,u=t,C=r;switch(e.scaleType===1&&(p=o,c=n,u=t+(s-o)/2,C=r+(a-n)/2),e.clipPath&&(i5.save(),Vt(i5,e.clipPath,u,C,p,c)),e.type){case 0:Bt(i5,e.data,u,C,p,c);break;case 1:Ut(i5,e.data,u,C,p,c,_);break;case 2:Wt(i5,e.data,u,C,p,c,T,h,e.strokeWidth);break;case 3:Ht(i5,e.data,u,C,p,c,T,e.strokeWidth);break;case 4:Zt(i5,e.data,u,C,p,c,T,d);break;case 5:$t(i5,e.data,u,C,p,c,L,T,h);break;case 6:wt(i5,e.data,u,C,p,c);break}e.clipPath&&i5.restore()}function Bt(i5,e,t,r,s,a){for(let o=0;o{let F=parseFloat(m);return R%2===0?t+F*s:r+F*a});if(u==="M")i5.moveTo(Q[0],Q[1]),T=Q[0],h=Q[1],d=T,_=h;else if(u==="L")i5.lineTo(Q[0],Q[1]),T=Q[0],h=Q[1],d=T,_=h;else if(u==="Q")i5.quadraticCurveTo(Q[0],Q[1],Q[2],Q[3]),d=Q[0],_=Q[1],T=Q[2],h=Q[3];else if(u==="T"){let m,R;p==="Q"||p==="T"?(m=2*T-d,R=2*h-_):(m=T,R=h),i5.quadraticCurveTo(m,R,Q[0],Q[1]),d=m,_=R,T=Q[0],h=Q[1]}else u==="C"&&(i5.bezierCurveTo(Q[0],Q[1],Q[2],Q[3],Q[4],Q[5]),d=Q[2],_=Q[3],T=Q[4],h=Q[5]);p=u}n!==void 0?(i5.strokeStyle=i5.fillStyle,i5.lineWidth=o*n,i5.stroke()):i5.fill()}function kt(i5,e,t,r,s,a,o,n,L,T){if(r===0||s===0){i5.lineTo(L,T);return};r=Math.abs(r),s=Math.abs(s);let h=Math.cos(a),d=Math.sin(a),_=(e-L)/2,p=(t-T)/2,c=h*_+d*p,u=-d*_+h*p,C=r*r,Q=s*s,m=c*c,R=u*u,F=m/C+R/Q;if(F>1){let I=Math.sqrt(F);r*=I,s*=I,C=r*r,Q=s*s}let E=(C*Q-C*R-Q*m)/(C*R+Q*m);E<0&&(E=0);let y=(o===n?-1:1)*Math.sqrt(E),Y=y*(r*u/s),X=y*-(s*c/r),A0=h*Y-d*X+(e+L)/2,t0=d*Y+h*X+(t+T)/2,L0=(c-Y)/r,A=(u-X)/s,n0=(-c-Y)/r,a0=(-u-X)/s,$=Math.atan2(A,L0),q=Math.atan2(a0,n0)-$;n===0&&q>0?q-=2*Math.PI:n===1&&q<0&&(q+=2*Math.PI);let g0=$+q;i5.ellipse(A0,t0,r,s,a,$,g0,n===0)}function Zt(i5,e,t,r,s,a,o,n){;i5.save(),i5.fillRect(t,r,s,a),n&&(i5.fillStyle=n,i5.strokeStyle=n),i5.lineWidth=o,i5.lineCap="square",i5.beginPath();for(let L of e.d.split(" ")){let T=L[0],h=L.substring(1).split(",");if(!h[0]||!h[1]){T==="Z"&&i5.closePath();continue}let d=h.map((_,p)=>{let c=parseFloat(_);return p%2===0?t+c*s:r+c*a});T==="M"?i5.moveTo(d[0],d[1]):T==="L"&&i5.lineTo(d[0],d[1])};e.type===1?i5.stroke():i5.fill(),i5.restore()}var Xe=new Map;function Ut(i5,e,t,r,s,a,o=0){let n=Xe.get(e);n||(n=new Map,Xe.set(e,n));let L=i5.fillStyle;if(typeof L!="string")throw new Error(`Unexpected fillStyle type "${L}"`);let T=n.get(L);if(!T){let _=e[0].length,p=e.length,c=i5.canvas.ownerDocument.createElement("canvas");c.width=_,c.height=p;let u=g(c.getContext("2d")),C=new ImageData(_,p),Q,m,R,F;if(L.startsWith("#"))Q=parseInt(L.slice(1,3),16),m=parseInt(L.slice(3,5),16),R=parseInt(L.slice(5,7),16),F=L.length>7&&parseInt(L.slice(7,9),16)||1;else if(L.startsWith("rgba"))[Q,m,R,F]=L.substring(5,L.length-1).split(",").map(E=>parseFloat(E));else throw new Error(`Unexpected fillStyle color format "${L}" when drawing pattern glyph`);for(let E=0;E>1&1,d=o&1;h!==0||d!==0?T.setTransform(new DOMMatrix().translateSelf(-h,-d)):T.setTransform(new DOMMatrix),i5.fillStyle=T,i5.fillRect(t,r,s,a)}function Wt(i5,e,t,r,s,a,o,n,L){;i5.save(),i5.beginPath(),i5.rect(t,r,s,a),i5.clip(),i5.beginPath();let T;if(typeof e=="function"){let _=.15/a*s;T=e(.15,_)}else T=e;let h={currentX:0,currentY:0,lastControlX:0,lastControlY:0,lastCommand:""};for(let d of T.split(" ")){let _=d[0];if(_==="Z"){;i5.closePath(),h.lastCommand=_;continue}let p=je[_];if(!p){n.error(`Could not find drawing instructions for "${_}"`);continue}let c=d.substring(1).split(",");!c[0]||!c[1]||(p(i5,Je(c,s,a,t,r,true,o,0,0,false),h),h.lastCommand=_)};L!==void 0?(i5.strokeStyle=i5.fillStyle,i5.lineWidth=o*L,i5.stroke()):i5.fill(),i5.closePath(),i5.restore()}function Vt(i5,e,t,r,s,a){i5.beginPath();for(let o of e.split(" ")){let n=o[0];if(n==="Z"){i5.closePath();continue}let L=o.substring(1).split(",");if(!L[0]||!L[1])continue;let T=t+parseFloat(L[0])*s,h=r+parseFloat(L[1])*a;n==="M"?i5.moveTo(T,h):n==="L"&&i5.lineTo(T,h)}i5.clip()}function $t(i5,e,t,r,s,a,o,n,L){let T=new Path2D;T.rect(t,r,s,a),i5.clip(T),i5.beginPath();let h=o/12;i5.lineWidth=n*h;let d={currentX:0,currentY:0,lastControlX:0,lastControlY:0,lastCommand:""};for(let _ of e.d.split(" ")){let p=_[0];if(p==="Z"){;i5.closePath(),d.lastCommand=p;continue}let c=je[p];if(!c){L.error(`Could not find drawing instructions for "${p}"`);continue}let u=_.substring(1).split(",");!u[0]||!u[1]||(c(i5,Je(u,s,a,t,r,false,n,(e.leftPadding??0)*(h/2),(e.rightPadding??0)*(h/2)),d),d.lastCommand=p)};e.type===1?(i5.strokeStyle=i5.fillStyle,i5.stroke()):i5.fill(),i5.closePath()}function Ye(i5,e,t=0){return Math.max(Math.min(i5,e),t)}var je={C:(i5,e,t)=>{;i5.bezierCurveTo(e[0],e[1],e[2],e[3],e[4],e[5]),t.lastControlX=e[2],t.lastControlY=e[3],t.currentX=e[4],t.currentY=e[5]},L:(i5,e,t)=>{;i5.lineTo(e[0],e[1]),t.lastControlX=t.currentX=e[0],t.lastControlY=t.currentY=e[1]},M:(i5,e,t)=>{;i5.moveTo(e[0],e[1]),t.lastControlX=t.currentX=e[0],t.lastControlY=t.currentY=e[1]},Q:(i5,e,t)=>{;i5.quadraticCurveTo(e[0],e[1],e[2],e[3]),t.lastControlX=e[0],t.lastControlY=e[1],t.currentX=e[2],t.currentY=e[3]},T:(i5,e,t)=>{let r,s;t.lastCommand==="Q"||t.lastCommand==="T"?(r=2*t.currentX-t.lastControlX,s=2*t.currentY-t.lastControlY):(r=t.currentX,s=t.currentY),i5.quadraticCurveTo(r,s,e[0],e[1]),t.lastControlX=r,t.lastControlY=s,t.currentX=e[0],t.currentY=e[1]}};function Je(i5,e,t,r,s,a,o,n=0,L=0,T=true){let h=i5.map(d=>parseFloat(d)||parseInt(d));if(h.length<2)throw new Error("Too few arguments for instruction");for(let d=0;da){;s-t<-20&&this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(s-t))}ms`),this._start();return}s=a}this.clear()}},Ce=class extends X0{_requestCallback(e){return setTimeout(()=>e(this._createDeadline(16)))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){let t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}},Qe=class extends X0{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}},et="requestIdleCallback"in globalThis?Qe:Ce;var s0=class i{constructor(){this.fg=0;this.bg=0;this.extended=new P0}static toColorRGB(e){return[e>>>16&255,e>>>8&255,e&255]}static fromColorRGB(e){return(e[0]&255)<<16|(e[1]&255)<<8|e[2]&255}clone(){let e=new i;return e.fg=this.fg,e.bg=this.bg,e.extended=this.extended.clone(),e}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)===50331648}isBgRGB(){return(this.bg&50331648)===50331648}isFgPalette(){return(this.fg&50331648)===16777216||(this.fg&50331648)===33554432}isBgPalette(){return(this.bg&50331648)===16777216||(this.bg&50331648)===33554432}isFgDefault(){return(this.fg&50331648)===0}isBgDefault(){return(this.bg&50331648)===0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===16777216||(this.extended.underlineColor&50331648)===33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===0:this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},P0=class i2{constructor(e=0,t=0){this._ext=0;this._urlId=0;this._ext=e,this._urlId=t}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(e){this._ext=e}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(e){;this._ext&=-469762049,this._ext|=e<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(e){;this._ext&=-67108864,this._ext|=e&67108863}get urlId(){return this._urlId}set urlId(e){this._urlId=e}get underlineVariantOffset(){let e=(this._ext&3758096384)>>29;return e<0?e^4294967288:e}set underlineVariantOffset(e){;this._ext&=536870911,this._ext|=e<<29&3758096384}clone(){return new i2(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}};var U=class{constructor(){this._listeners=[];this._disposed=false}get event(){return this._event?this._event:(this._event=(e,t,r)=>{if(this._disposed)return M(()=>{});let s={fn:e,thisArgs:t};this._listeners.push(s);let a=M(()=>{let o=this._listeners.indexOf(s);o!==-1&&this._listeners.splice(o,1)});return r&&(Array.isArray(r)?r.push(a):r.add(a)),a},this._event)}fire(e){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{let{fn:t,thisArgs:r}=this._listeners[0];t.call(r,e);return}default:{let t=this._listeners.slice();for(let{fn:r,thisArgs:s}of t)r.call(s,e)}}}dispose(){this._disposed||(this._disposed=true,this._listeners.length=0)}},c0;(s=>{function i5(a,o){return a(n=>o.fire(n))}s.forward=i5;function e(a,o){return(n,L,T)=>a(h=>n.call(L,o(h)),void 0,T)}s.map=e;function t(...a){return(o,n,L)=>{let T=new R0;for(let h of a)T.add(h(d=>o.call(n,d)));return L&&(Array.isArray(L)?L.push(T):L.add(T)),T}}s.any=t;function r(a,o,n){return o(n),a(L=>o(L))}s.runAndSubscribe=r})(c0||={});var tt={texturePage:0,texturePosition:{x:0,y:0},texturePositionClipSpace:{x:0,y:0},offset:{x:0,y:0},size:{x:0,y:0},sizeClipSpace:{x:0,y:0}},B0=2;var w0,J=class i3{constructor(e,t,r,s){this._document=e;this._config=t;this._unicodeService=r;this._logService=s;this._didWarmUp=false;this._cacheMap=new D0;this._cacheMapCombined=new D0;this._pages=[];this._activePages=[];this._workBoundingBox={top:0,left:0,bottom:0,right:0};this._workAttributeData=new s0;this._textureSize=512;this._onAddTextureAtlasCanvas=new U;this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event;this._onRemoveTextureAtlasCanvas=new U;this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event;this._requestClearModel=false;this._clearModelGeneration=0;this._createNewPage(),this._tmpCanvas=it(e,this._config.deviceCellWidth*4+B0*2,this._config.deviceCellHeight+B0*2),this._tmpCtx=g(this._tmpCanvas.getContext("2d",{alpha:this._config.allowTransparency,willReadFrequently:true}))}get pages(){return this._pages}dispose(){this._tmpCanvas.remove();for(let e of this.pages)e.canvas.remove();this._onAddTextureAtlasCanvas.dispose(),this._onRemoveTextureAtlasCanvas.dispose()}warmUp(){this._didWarmUp||(this._doWarmUp(),this._didWarmUp=true)}_doWarmUp(){let e=new et(this._logService);for(let t=33;t<126;t++)e.enqueue(()=>{if(!this._cacheMap.get(t,0,0,0)){let r=this._drawToCache(t,0,0,0,false,void 0);this._cacheMap.set(t,0,0,0,r)}})}beginFrame(){let e=this._requestClearModel;return this._requestClearModel=false,e}clearTexture(){if(this._pages.some(e=>e._glyphs.length>0||e.currentRow.x!==0||e.currentRow.y!==0)){this._evictAllPages();this._createNewPage()}}_evictAllPages(){for(let e of this._pages)this._onRemoveTextureAtlasCanvas.fire(e.canvas),e.canvas.remove();this._pages.length=0,this._activePages.length=0,this._overflowSizePage=void 0,this._cacheMap.clear(),this._cacheMapCombined.clear(),this._didWarmUp=false,this._requestClearModel=true,this._clearModelGeneration++}_createNewPage(){if(i3.maxAtlasPages&&this._pages.length>=i3.maxAtlasPages){let t=this._pages.filter(T=>T.canvas.width*2<=(i3.maxTextureSize||4096)).sort((T,h)=>h.canvas.width!==T.canvas.width?h.canvas.width-T.canvas.width:h.percentageUsed-T.percentageUsed),r=-1,s=0;for(let T=0;TT.canvas.width!==a[0].canvas.width))this._evictAllPages();else{let o=a.map(T=>T.glyphs[0].texturePage).sort((T,h)=>T-h),n=this.pages.length-a.length,L=this._mergePages(a,n);L.version=++u0.nextVersion;for(let T=o.length-1;T>=0;T--)this._deletePage(o[T]);this.pages.push(L),this._requestClearModel=true,this._clearModelGeneration++,this._onAddTextureAtlasCanvas.fire(L.canvas)}}let e=new u0(this._document,this._textureSize);return this._pages.push(e),this._activePages.push(e),this._onAddTextureAtlasCanvas.fire(e.canvas),e}_mergePages(e,t){let r=e[0].canvas.width*2,s=new u0(this._document,r,e);for(let[a,o]of e.entries()){let n=a*o.canvas.width%r,L=Math.floor(a/2)*o.canvas.height;s.ctx.drawImage(o.canvas,n,L);for(let h of o.glyphs)h.texturePage=t,h.sizeClipSpace.x=h.size.x/r,h.sizeClipSpace.y=h.size.y/r,h.texturePosition.x+=n,h.texturePosition.y+=L,h.texturePositionClipSpace.x=h.texturePosition.x/r,h.texturePositionClipSpace.y=h.texturePosition.y/r;this._onRemoveTextureAtlasCanvas.fire(o.canvas);let T=this._activePages.indexOf(o);T!==-1&&this._activePages.splice(T,1)}return s}_deletePage(e){this._pages.splice(e,1);for(let t=e;t=this._config.colors.ansi.length)throw new Error("No color found for idx "+e);return this._config.colors.ansi[e]}_getBackgroundColor(e,t,r,s){if(this._config.allowTransparency)return K;let a;switch(e){case 16777216:case 33554432:a=this._getColorFromAnsiIndex(t);break;case 50331648:let o=s0.toColorRGB(t);a=Z.toColor(o[0],o[1],o[2]);break;case 0:default:r?a=I0.opaque(this._config.colors.foreground):a=this._config.colors.background;break}return this._config.allowTransparency||(a=I0.opaque(a)),a}_getForegroundColor(e,t,r,s,a,o,n,L,T,h){let d=this._getMinimumContrastColor(e,t,r,s,a,o,n,T,L,h);if(d)return d;let _;switch(a){case 16777216:case 33554432:;this._config.drawBoldTextInBrightColors&&T&&o<8&&(o+=8),_=this._getColorFromAnsiIndex(o);break;case 50331648:let p=s0.toColorRGB(o);_=Z.toColor(p[0],p[1],p[2]);break;case 0:default:n?_=this._config.colors.background:_=this._config.colors.foreground}return this._config.allowTransparency&&(_=I0.opaque(_)),L&&(_=I0.multiplyOpacity(_,Ke)),_}_resolveBackgroundRgba(e,t,r){switch(e){case 16777216:case 33554432:return this._getColorFromAnsiIndex(t).rgba;case 50331648:return t<<8;case 0:default:return r?this._config.colors.foreground.rgba:this._config.colors.background.rgba}}_resolveForegroundRgba(e,t,r,s){switch(e){case 16777216:case 33554432:return this._config.drawBoldTextInBrightColors&&s&&t<8&&(t+=8),this._getColorFromAnsiIndex(t).rgba;case 50331648:return t<<8;case 0:default:return r?this._config.colors.background.rgba:this._config.colors.foreground.rgba}}_getMinimumContrastColor(e,t,r,s,a,o,n,L,T,h){if(this._config.minimumContrastRatio===1||h)return;let d=this._getContrastCache(T),_=d.getColor(e,s);if(_!==void 0)return _??void 0;let p=this._resolveBackgroundRgba(t,r,n),c=this._resolveForegroundRgba(a,o,n,L),u=d0.ensureContrastRatio(p,c,this._config.minimumContrastRatio/(T?2:1));if(!u){d.setColor(e,s,null);return}let C=Z.toColor(u>>24&255,u>>16&255,u>>8&255);return d.setColor(e,s,C),C}_getContrastCache(e){return e?this._config.colors.halfContrastCache:this._config.colors.contrastCache}_drawToCache(e,t,r,s,a,o){let n=typeof e=="number"?String.fromCharCode(e):e;o&&this._tmpCanvas.parentElement!==o&&(this._tmpCanvas.style.display="none",o.append(this._tmpCanvas));let L=Math.min(this._config.deviceCellWidth*Math.max(n.length,2)+B0*2,this._config.deviceMaxTextureSize);this._tmpCanvas.width=b?b*2-f0:b-f0;!(f0>=b)===false||Z0===0?(this._tmpCtx.setLineDash([Math.round(b),Math.round(b)]),this._tmpCtx.moveTo(j+Z0,G),this._tmpCtx.lineTo(Q0,G)):(this._tmpCtx.setLineDash([Math.round(b),Math.round(b)]),this._tmpCtx.moveTo(j,G),this._tmpCtx.lineTo(j+Z0,G),this._tmpCtx.moveTo(j+Z0+b,G),this._tmpCtx.lineTo(Q0,G)),f0=We(Q0-j,b,f0);break;case 5:let vt=.6,Ot=.3,de=Q0-j,Se=Math.floor(vt*de),Re=Math.floor(Ot*de),It=de-Se-Re;this._tmpCtx.setLineDash([Se,Re,It]),this._tmpCtx.moveTo(j,G),this._tmpCtx.lineTo(Q0,G);break;case 1:default:;this._tmpCtx.moveTo(j,G),this._tmpCtx.lineTo(Q0,G);break};y0||this._tmpCtx.stroke(),this._tmpCtx.restore()}if(this._tmpCtx.restore(),!n0&&this._config.fontSize>=12&&!this._config.allowTransparency&&n!==" "){;this._tmpCtx.save(),this._tmpCtx.textBaseline="alphabetic";let r0=this._tmpCtx.measureText(n);if(this._tmpCtx.restore(),"actualBoundingBoxDescent"in r0&&r0.actualBoundingBoxDescent>0){this._tmpCtx.save();let y0=new Path2D;y0.rect(i0,C0-Math.ceil(b/2),this._config.deviceCellWidth*$,v0-C0+Math.ceil(b/2)),this._tmpCtx.clip(y0),this._tmpCtx.lineWidth=this._config.devicePixelRatio*3,this._tmpCtx.strokeStyle=y.css,this._tmpCtx.strokeText(n,A,A+this._config.deviceCharHeight),this._tmpCtx.restore()}}}if(Q){let b=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/15)),D=b%2===1?.5:0;this._tmpCtx.lineWidth=b,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(A,A+D),this._tmpCtx.lineTo(A+this._config.deviceCharWidth*$,A+D),this._tmpCtx.stroke()}if(n0||this._tmpCtx.fillText(n,A,A+this._config.deviceCharHeight),n==="_"&&!this._config.allowTransparency){let b=fe(this._tmpCtx.getImageData(A,A,this._config.deviceCellWidth,this._config.deviceCellHeight),y,L0,a0);if(b)for(let D=1;D<=5&&(this._tmpCtx.save(),this._tmpCtx.fillStyle=y.css,this._tmpCtx.fillRect(0,0,this._tmpCanvas.width,this._tmpCanvas.height),this._tmpCtx.restore(),this._tmpCtx.fillText(n,A,A+this._config.deviceCharHeight-D),b=fe(this._tmpCtx.getImageData(A,A,this._config.deviceCellWidth,this._config.deviceCellHeight),y,L0,a0),!!b);D++);}if(C){let b=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/10)),D=this._tmpCtx.lineWidth%2===1?.5:0;this._tmpCtx.lineWidth=b,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(A,A+Math.floor(this._config.deviceCharHeight/2)-D),this._tmpCtx.lineTo(A+this._config.deviceCharWidth*$,A+Math.floor(this._config.deviceCharHeight/2)-D),this._tmpCtx.stroke()}this._tmpCtx.restore();let q=this._tmpCtx.getImageData(0,0,this._tmpCanvas.width,this._tmpCanvas.height),g0;if(this._config.allowTransparency?g0=Gt(q):g0=fe(q,y,L0,a0),g0)return tt;let I=this._findGlyphBoundingBox(q,this._workBoundingBox,L,t0,n0,A),O,v;for(;;){if(this._activePages.length===0){let b=this._createNewPage();O=b,v=b.currentRow,v.height=I.size.y;break};O=this._activePages[this._activePages.length-1],v=O.currentRow;for(let b of this._activePages)I.size.y<=b.currentRow.height&&(O=b,v=b.currentRow);for(let b=this._activePages.length-1;b>=0;b--)for(let D of this._activePages[b].fixedRows)D.height<=v.height&&I.size.y<=D.height&&(O=this._activePages[b],v=D);if(I.size.x>this._textureSize){;this._overflowSizePage||(i3.maxAtlasPages&&this._pages.length>=i3.maxAtlasPages&&this._evictAllPages(),this._overflowSizePage=new u0(this._document,this._config.deviceMaxTextureSize),this.pages.push(this._overflowSizePage),this._requestClearModel=true,this._clearModelGeneration++,this._onAddTextureAtlasCanvas.fire(this._overflowSizePage.canvas)),O=this._overflowSizePage,v=this._overflowSizePage.currentRow,v.x+I.size.x>=O.canvas.width&&(v.x=0,v.y+=v.height,v.height=0);break}if(v.y+I.size.y>=O.canvas.height||v.height>I.size.y+2){let b=false;if(O.currentRow.y+O.currentRow.height+I.size.y>=O.canvas.height){let D;for(let i0 of this._activePages)if(i0.currentRow.y+i0.currentRow.height+I.size.y=i3.maxAtlasPages&&v.y+I.size.y<=O.canvas.height&&v.height>=I.size.y&&v.x+I.size.x<=O.canvas.width)b=true;else{let i0=this._createNewPage();O=i0,v=i0.currentRow,v.height=I.size.y,b=true}}b||(O.currentRow.height>0&&O.fixedRows.push(O.currentRow),v={x:0,y:O.currentRow.y+O.currentRow.height,height:I.size.y},O.fixedRows.push(v),O.currentRow={x:0,y:v.y+v.height,height:0})}if(v.x+I.size.x<=O.canvas.width)break;v===O.currentRow?(v.x=0,v.y+=v.height,v.height=0):O.fixedRows.splice(O.fixedRows.indexOf(v),1)}return I.texturePage=this._pages.indexOf(O),I.texturePosition.x=v.x,I.texturePosition.y=v.y,I.texturePositionClipSpace.x=v.x/O.canvas.width,I.texturePositionClipSpace.y=v.y/O.canvas.height,I.sizeClipSpace.x/=O.canvas.width,I.sizeClipSpace.y/=O.canvas.height,v.height=Math.max(v.height,I.size.y),v.x+=I.size.x,O.ctx.putImageData(q,I.texturePosition.x-this._workBoundingBox.left,I.texturePosition.y-this._workBoundingBox.top,this._workBoundingBox.left,this._workBoundingBox.top,I.size.x,I.size.y),O.addGlyph(I),O.version=++u0.nextVersion,I}_findGlyphBoundingBox(e,t,r,s,a,o){t.top=0;let n=s?this._config.deviceCellHeight:this._tmpCanvas.height,L=s?this._config.deviceCellWidth:r,T=false;for(let h=0;h=o;h--){for(let d=0;d=0;h--){for(let d=0;d>>24,a=e.rgba>>>16&255,o=e.rgba>>>8&255,n=t.rgba>>>24,L=t.rgba>>>16&255,T=t.rgba>>>8&255,h=Math.floor((Math.abs(s-n)+Math.abs(a-L)+Math.abs(o-T))/12),d=true;for(let _=0;_0)return false;return true}function it(i5,e,t){let r=i5.createElement("canvas");return r.width=e,r.height=t,r}function rt(i5,e,t,r,s,a,o,n,L=true){let T={foreground:a.foreground,background:a.background,cursor:K,cursorAccent:K,selectionForeground:K,selectionBackgroundTransparent:K,selectionBackgroundOpaque:K,selectionInactiveBackgroundTransparent:K,selectionInactiveBackgroundOpaque:K,overviewRulerBorder:K,scrollbarSliderBackground:K,scrollbarSliderHoverBackground:K,scrollbarSliderActiveBackground:K,ansi:a.ansi.slice(),contrastCache:a.contrastCache,halfContrastCache:a.halfContrastCache};return{customGlyphs:L,devicePixelRatio:o,deviceMaxTextureSize:n,letterSpacing:s.letterSpacing,lineHeight:s.lineHeight,deviceCellWidth:i5,deviceCellHeight:e,deviceCharWidth:t,deviceCharHeight:r,fontFamily:s.fontFamily,fontSize:s.fontSize,fontWeight:s.fontWeight,fontWeightBold:s.fontWeightBold,allowTransparency:s.allowTransparency,drawBoldTextInBrightColors:s.drawBoldTextInBrightColors,minimumContrastRatio:s.minimumContrastRatio,colors:T}}function Ee(i5,e){for(let t=0;t=0){if(Ee(u.config,h))return u.atlas;u.ownedBy.length===1?(u.atlas.dispose(),e0.splice(c,1)):u.ownedBy.splice(C,1);break}}for(let c=0;c{;this._renderCallback(),this._animationFrame=void 0})))}_restartInterval(e=600){;this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout=this._coreBrowserService.window.setTimeout(()=>{if(this._animationTimeRestarted){let t=600-(Date.now()-this._animationTimeRestarted);if(this._animationTimeRestarted=void 0,t>0){this._restartInterval(t);return}};this.isCursorVisible=false,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{;this._renderCallback(),this._animationFrame=void 0}),this._blinkInterval=this._coreBrowserService.window.setInterval(()=>{if(this._animationTimeRestarted){let t=600-(Date.now()-this._animationTimeRestarted);this._animationTimeRestarted=void 0,this._restartInterval(t);return};this.isCursorVisible=!this.isCursorVisible,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{;this._renderCallback(),this._animationFrame=void 0})},600)},e)}pause(){;this.isCursorVisible=true,this._isIdlePaused=false,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0),this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}resume(){;this.pause(),this._animationTimeRestarted=void 0,this._restartInterval(),this._resetIdleTimer(),this.restartBlinkAnimation()}_resetIdleTimer(){;this._isIdlePaused=false,this._idleTimeout&&this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_stopBlinkingDueToIdle(){;this.isCursorVisible=true,this._isIdlePaused=true,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0),this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0,this._renderCallback()}};function ve(i5,e,t){let r=new e.ResizeObserver(s=>{let a=s.find(L=>L.target===i5);if(!a)return;if(!("devicePixelContentBoxSize"in a)){;r?.disconnect(),r=void 0;return}let o=a.devicePixelContentBoxSize[0].inlineSize,n=a.devicePixelContentBoxSize[0].blockSize;o>0&&n>0&&t(o,n)});try{r.observe(i5,{box:["device-pixel-content-box"]})}catch{;r.disconnect(),r=void 0}return M(()=>r?.disconnect())}function nt(i5){return i5>65535?(i5-=65536,String.fromCharCode((i5>>10)+55296)+String.fromCharCode(i5%1024+56320)):String.fromCharCode(i5)}var j0=class i4 extends s0{constructor(){super(...arguments);this.content=0;this.fg=0;this.bg=0;this.extended=new P0;this.combinedData=""}static fromCharData(t){let r=new i4;return r.setFromCharData(t),r}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?nt(this.content&2097151):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(t){;this.fg=t[0],this.bg=0;let r=false;if(t[1].length>2)r=true;else if(t[1].length===2){let s=t[1].charCodeAt(0);if(55296<=s&&s<=56319){let a=t[1].charCodeAt(1);56320<=a&&a<=57343?this.content=(s-55296)*1024+a-56320+65536|t[2]<<22:r=true}else r=true}else this.content=t[1].charCodeAt(0)|t[2]<<22;r&&(this.combinedData=t[1],this.content=2097152|t[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}attributesEquals(t){if(this.getFgColorMode()!==t.getFgColorMode()||this.getFgColor()!==t.getFgColor()||this.getBgColorMode()!==t.getBgColorMode()||this.getBgColor()!==t.getBgColor()||this.isInverse()!==t.isInverse()||this.isBold()!==t.isBold()||this.isUnderline()!==t.isUnderline())return false;if(this.isUnderline()){if(this.getUnderlineStyle()!==t.getUnderlineStyle())return false;let r=this.isUnderlineColorDefault(),s=t.isUnderlineColorDefault();if(!(r&&s)&&(r!==s||this.getUnderlineColor()!==t.getUnderlineColor()||this.getUnderlineColorMode()!==t.getUnderlineColorMode()))return false}return!(this.isOverline()!==t.isOverline()||this.isBlink()!==t.isBlink()||this.isInvisible()!==t.isInvisible()||this.isItalic()!==t.isItalic()||this.isDim()!==t.isDim()||this.isStrikethrough()!==t.isStrikethrough())}};var J0=class extends W{constructor(t,r,s){super();this._renderCallback=t;this._coreBrowserService=r;this._optionsService=s;this._intervalDuration=0;this._blinkOn=true;this._needsBlinkInViewport=false;this._isViewportVisible=true;this._register(this._optionsService.onSpecificOptionChange("blinkIntervalDuration",a=>{this.setIntervalDuration(a)})),this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration),this._register(M(()=>this._clearInterval()))}get isBlinkOn(){return this._blinkOn}get isEnabled(){return this._intervalDuration>0}setNeedsBlinkInViewport(t){this._needsBlinkInViewport!==t&&(this._needsBlinkInViewport=t,this._updateIntervalState())}setViewportVisible(t){this._isViewportVisible!==t&&(this._isViewportVisible=t,this._updateIntervalState())}setIntervalDuration(t){t!==this._intervalDuration&&(this._intervalDuration=t,this._clearInterval(),this._updateIntervalState())}_updateIntervalState(){if(this._intervalDuration>0&&this._needsBlinkInViewport&&this._isViewportVisible){if(this._interval!==void 0)return;let r=this._blinkOn;this._blinkOn=true,this._interval=this._coreBrowserService.window.setInterval(()=>{;this._blinkOn=!this._blinkOn,this._renderCallback()},this._intervalDuration),r||this._renderCallback();return};this._clearInterval(),this._blinkOn||(this._blinkOn=true,this._renderCallback())}_clearInterval(){this._interval!==void 0&&(this._coreBrowserService.window.clearInterval(this._interval),this._interval=void 0)}};var te=new Float32Array([2,0,0,0,0,-2,0,0,0,0,1,0,-1,1,0,1]);function ie(i5,e,t,r){let s=g(i5.createProgram());if(i5.attachShader(s,g(at(i5,i5.VERTEX_SHADER,e,r))),i5.attachShader(s,g(at(i5,i5.FRAGMENT_SHADER,t,r))),i5.linkProgram(s),i5.getProgramParameter(s,i5.LINK_STATUS))return s;r.error(i5.getProgramInfoLog(s)),i5.deleteProgram(s)}function at(i5,e,t,r){let s=g(i5.createShader(e));if(i5.shaderSource(s,t),i5.compileShader(s),i5.getShaderParameter(s,i5.COMPILE_STATUS))return s;r.error(i5.getShaderInfoLog(s)),i5.deleteShader(s)}function ot(i5,e){let t=Math.min(i5.length*2,e),r=new Float32Array(t);for(let s=0;sn.deleteProgram(this._program))),this._projectionLocation=g(n.getUniformLocation(this._program,"u_projection")),this._resolutionLocation=g(n.getUniformLocation(this._program,"u_resolution")),this._textureLocation=g(n.getUniformLocation(this._program,"u_texture"));let L=this._vertexArrayObject=n.createVertexArray();this._register(M(()=>n.deleteVertexArray(L))),n.bindVertexArray(L);let T=new Float32Array([0,0,1,0,0,1,1,1]),h=n.createBuffer();this._register(M(()=>n.deleteBuffer(h))),n.bindBuffer(n.ARRAY_BUFFER,h),n.bufferData(n.ARRAY_BUFFER,T,n.STATIC_DRAW),n.enableVertexAttribArray(0),n.vertexAttribPointer(0,2,this._gl.FLOAT,!1,0,0);let d=new Uint8Array([0,1,2,3]),_=n.createBuffer();this._register(M(()=>n.deleteBuffer(_))),n.bindBuffer(n.ELEMENT_ARRAY_BUFFER,_),n.bufferData(n.ELEMENT_ARRAY_BUFFER,d,n.STATIC_DRAW),this._attributesBuffer=g(n.createBuffer()),this._register(M(()=>n.deleteBuffer(this._attributesBuffer))),n.bindBuffer(n.ARRAY_BUFFER,this._attributesBuffer),n.enableVertexAttribArray(2),n.vertexAttribPointer(2,2,n.FLOAT,!1,44,0),n.vertexAttribDivisor(2,1),n.enableVertexAttribArray(3),n.vertexAttribPointer(3,2,n.FLOAT,!1,44,2*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(3,1),n.enableVertexAttribArray(4),n.vertexAttribPointer(4,1,n.FLOAT,!1,44,4*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(4,1),n.enableVertexAttribArray(5),n.vertexAttribPointer(5,2,n.FLOAT,!1,44,5*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(5,1),n.enableVertexAttribArray(6),n.vertexAttribPointer(6,2,n.FLOAT,!1,44,7*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(6,1),n.enableVertexAttribArray(1),n.vertexAttribPointer(1,2,n.FLOAT,!1,44,9*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(1,1),n.useProgram(this._program);let p=new Int32Array(J.maxAtlasPages);for(let c=0;cn.deleteTexture(u.texture))),n.activeTexture(n.TEXTURE0+c),n.bindTexture(n.TEXTURE_2D,u.texture),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE),n.texImage2D(n.TEXTURE_2D,0,n.RGBA,1,1,0,n.RGBA,n.UNSIGNED_BYTE,new Uint8Array([255,0,0,255])),this._atlasTextures[c]=u}n.enable(n.BLEND),n.blendFunc(n.SRC_ALPHA,n.ONE_MINUS_SRC_ALPHA),this.handleResize()}beginFrame(){return this._atlas?this._atlas.beginFrame():!0}updateCell(t,r,s,a,o,n,L,T,h){this._updateCell(this._vertices.attributes,t,r,s,a,o,n,L,T,h)}_updateCell(t,r,s,a,o,n,L,T,h,d){if(N=(s*this._terminal.cols+r)*11,a===0||a===void 0){t.fill(0,N,N+11-1-2);return}this._atlas&&(T&&T.length>1?S=this._atlas.getRasterizedGlyphCombinedChar(T,o,n,L,!1,this._terminal.element):S=this._atlas.getRasterizedGlyph(a,o,n,L,!1,this._terminal.element),Ie=Math.floor((this._dimensions.device.cell.width-this._dimensions.device.char.width)/2),o!==d&&S.offset.x>Ie?(k0=S.offset.x-Ie,t[N]=-(S.offset.x-k0)+this._dimensions.device.char.left,t[N+1]=-S.offset.y+this._dimensions.device.char.top,t[N+2]=(S.size.x-k0)/this._dimensions.device.canvas.width,t[N+3]=S.size.y/this._dimensions.device.canvas.height,t[N+4]=S.texturePage,t[N+5]=S.texturePositionClipSpace.x+k0/this._atlas.pages[S.texturePage].canvas.width,t[N+6]=S.texturePositionClipSpace.y,t[N+7]=S.sizeClipSpace.x-k0/this._atlas.pages[S.texturePage].canvas.width,t[N+8]=S.sizeClipSpace.y):(t[N]=-S.offset.x+this._dimensions.device.char.left,t[N+1]=-S.offset.y+this._dimensions.device.char.top,t[N+2]=S.size.x/this._dimensions.device.canvas.width,t[N+3]=S.size.y/this._dimensions.device.canvas.height,t[N+4]=S.texturePage,t[N+5]=S.texturePositionClipSpace.x,t[N+6]=S.texturePositionClipSpace.y,t[N+7]=S.sizeClipSpace.x,t[N+8]=S.sizeClipSpace.y),this._optionsService.rawOptions.rescaleOverlappingGlyphs&&Ze(a,h,S.size.x,this._dimensions.device.cell.width)&&(t[N+2]=(this._dimensions.device.cell.width-1)/this._dimensions.device.canvas.width))}clear(){let t=this._terminal,r=t.cols*t.rows*11;this._vertices.count!==r?this._vertices.attributes=new Float32Array(r):this._vertices.attributes.fill(0);let s=0;for(;s=e.rows||T<0){this.clear();return}this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=o,this.viewportEndRow=n,this.viewportCappedStartRow=L,this.viewportCappedEndRow=T,this.startCol=t[0],this.endCol=r[0]}isCellSelected(e,t,r){return this.hasSelection?(r-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&r>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&r<=this.viewportCappedEndRow:r>this.viewportStartRow&&r=this.startCol&&t=this.startCol):!1}};function Tt(){return new Fe}var lt=2147483648,se=class{constructor(){this.cells=new Uint32Array(0),this.lineLengths=new Uint32Array(0),this.selection=Tt()}resize(e,t){let r=e*t*4;r!==this.cells.length&&(this.cells=new Uint32Array(r),this.lineLengths=new Uint32Array(t))}clear(){this.cells.fill(0,0),this.lineLengths.fill(0,0)}};var jt=`#version 300 es + } ${e} else { outColor = vec4(0.0, 0.0, 0.0, 0.0); } -+}`}var N=0,S,Ie=0,k0=0,re=class extends W{constructor(t,r,s,a,o){super();this._terminal=t;this._gl=r;this._dimensions=s;this._optionsService=a;this._logService=o;this._activeBuffer=0;this._vertices={count:0,attributes:new Float32Array(0),attributesBuffers:[new Float32Array(0),new Float32Array(0)]};let n=this._gl;J.maxAtlasPages===void 0&&(J.maxAtlasPages=Math.min(32,g(n.getParameter(n.MAX_TEXTURE_IMAGE_UNITS))),J.maxTextureSize=g(n.getParameter(n.MAX_TEXTURE_SIZE))),this._program=g(ie(n,Xt,Yt(J.maxAtlasPages),this._logService)),this._register(M(()=>n.deleteProgram(this._program))),this._projectionLocation=g(n.getUniformLocation(this._program,"u_projection")),this._resolutionLocation=g(n.getUniformLocation(this._program,"u_resolution")),this._textureLocation=g(n.getUniformLocation(this._program,"u_texture"));let L=this._vertexArrayObject=n.createVertexArray();this._register(M(()=>n.deleteVertexArray(L))),n.bindVertexArray(L);let T=new Float32Array([0,0,1,0,0,1,1,1]),h=n.createBuffer();this._register(M(()=>n.deleteBuffer(h))),n.bindBuffer(n.ARRAY_BUFFER,h),n.bufferData(n.ARRAY_BUFFER,T,n.STATIC_DRAW),n.enableVertexAttribArray(0),n.vertexAttribPointer(0,2,this._gl.FLOAT,!1,0,0);let d=new Uint8Array([0,1,2,3]),_=n.createBuffer();this._register(M(()=>n.deleteBuffer(_))),n.bindBuffer(n.ELEMENT_ARRAY_BUFFER,_),n.bufferData(n.ELEMENT_ARRAY_BUFFER,d,n.STATIC_DRAW),this._attributesBuffer=g(n.createBuffer()),this._register(M(()=>n.deleteBuffer(this._attributesBuffer))),n.bindBuffer(n.ARRAY_BUFFER,this._attributesBuffer),n.enableVertexAttribArray(2),n.vertexAttribPointer(2,2,n.FLOAT,!1,44,0),n.vertexAttribDivisor(2,1),n.enableVertexAttribArray(3),n.vertexAttribPointer(3,2,n.FLOAT,!1,44,2*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(3,1),n.enableVertexAttribArray(4),n.vertexAttribPointer(4,1,n.FLOAT,!1,44,4*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(4,1),n.enableVertexAttribArray(5),n.vertexAttribPointer(5,2,n.FLOAT,!1,44,5*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(5,1),n.enableVertexAttribArray(6),n.vertexAttribPointer(6,2,n.FLOAT,!1,44,7*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(6,1),n.enableVertexAttribArray(1),n.vertexAttribPointer(1,2,n.FLOAT,!1,44,9*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(1,1),n.useProgram(this._program);let p=new Int32Array(J.maxAtlasPages);for(let c=0;cn.deleteTexture(u.texture))),n.activeTexture(n.TEXTURE0+c),n.bindTexture(n.TEXTURE_2D,u.texture),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE),n.texImage2D(n.TEXTURE_2D,0,n.RGBA,1,1,0,n.RGBA,n.UNSIGNED_BYTE,new Uint8Array([255,0,0,255])),this._atlasTextures[c]=u}n.enable(n.BLEND),n.blendFunc(n.SRC_ALPHA,n.ONE_MINUS_SRC_ALPHA),this.handleResize()}beginFrame(){if(!this._atlas)return!0;let _g=this._atlas._clearModelGeneration||0;return this._lastSeenClearModelGeneration!==_g?(this._lastSeenClearModelGeneration=_g,!0):!1}updateCell(t,r,s,a,o,n,L,T,h){this._updateCell(this._vertices.attributes,t,r,s,a,o,n,L,T,h)}_updateCell(t,r,s,a,o,n,L,T,h,d){if(N=(s*this._terminal.cols+r)*11,a===0||a===void 0){t.fill(0,N,N+11-1-2);return}this._atlas&&(T&&T.length>1?S=this._atlas.getRasterizedGlyphCombinedChar(T,o,n,L,!1,this._terminal.element):S=this._atlas.getRasterizedGlyph(a,o,n,L,!1,this._terminal.element),Ie=Math.floor((this._dimensions.device.cell.width-this._dimensions.device.char.width)/2),o!==d&&S.offset.x>Ie?(k0=S.offset.x-Ie,t[N]=-(S.offset.x-k0)+this._dimensions.device.char.left,t[N+1]=-S.offset.y+this._dimensions.device.char.top,t[N+2]=(S.size.x-k0)/this._dimensions.device.canvas.width,t[N+3]=S.size.y/this._dimensions.device.canvas.height,t[N+4]=S.texturePage,t[N+5]=S.texturePositionClipSpace.x+k0/this._atlas.pages[S.texturePage].canvas.width,t[N+6]=S.texturePositionClipSpace.y,t[N+7]=S.sizeClipSpace.x-k0/this._atlas.pages[S.texturePage].canvas.width,t[N+8]=S.sizeClipSpace.y):(t[N]=-S.offset.x+this._dimensions.device.char.left,t[N+1]=-S.offset.y+this._dimensions.device.char.top,t[N+2]=S.size.x/this._dimensions.device.canvas.width,t[N+3]=S.size.y/this._dimensions.device.canvas.height,t[N+4]=S.texturePage,t[N+5]=S.texturePositionClipSpace.x,t[N+6]=S.texturePositionClipSpace.y,t[N+7]=S.sizeClipSpace.x,t[N+8]=S.sizeClipSpace.y),this._optionsService.rawOptions.rescaleOverlappingGlyphs&&Ze(a,h,S.size.x,this._dimensions.device.cell.width)&&(t[N+2]=(this._dimensions.device.cell.width-1)/this._dimensions.device.canvas.width))}clear(){let t=this._terminal,r=t.cols*t.rows*11;this._vertices.count!==r?this._vertices.attributes=new Float32Array(r):this._vertices.attributes.fill(0);let s=0;for(;sthis._atlasTextures.length&&!this._pageOverflowWarned&&(this._pageOverflowWarned=!0,console.warn("xterm webgl: atlas page count ("+this._atlas.pages.length+") exceeds the renderer texture capacity ("+this._atlasTextures.length+"), some glyphs will not render correctly"));for(let o=0;o=e.rows||T<0){this.clear();return}this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=o,this.viewportEndRow=n,this.viewportCappedStartRow=L,this.viewportCappedEndRow=T,this.startCol=t[0],this.endCol=r[0]}isCellSelected(e,t,r){return this.hasSelection?(r-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&r>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&r<=this.viewportCappedEndRow:r>this.viewportStartRow&&r=this.startCol&&t=this.startCol):!1}};function Tt(){return new Fe}var lt=2147483648,se=class{constructor(){this.cells=new Uint32Array(0),this.lineLengths=new Uint32Array(0),this.selection=Tt()}resize(e,t){let r=e*t*4;r!==this.cells.length&&(this.cells=new Uint32Array(r),this.lineLengths=new Uint32Array(t))}clear(){this.cells.fill(0,0),this.lineLengths.fill(0,0)}};var jt=`#version 300 es ++}`}var N=0,S,Ie=0,k0=0,re=class extends W{constructor(t,r,s,a,o){super();this._terminal=t;this._gl=r;this._dimensions=s;this._optionsService=a;this._logService=o;this._activeBuffer=0;this._vertices={count:0,attributes:new Float32Array(0),attributesBuffers:[new Float32Array(0),new Float32Array(0)]};let n=this._gl;J.maxAtlasPages===void 0&&(J.maxAtlasPages=Math.min(32,g(n.getParameter(n.MAX_TEXTURE_IMAGE_UNITS))),J.maxTextureSize=g(n.getParameter(n.MAX_TEXTURE_SIZE))),this._program=g(ie(n,Xt,Yt(J.maxAtlasPages),this._logService)),this._register(M(()=>n.deleteProgram(this._program))),this._projectionLocation=g(n.getUniformLocation(this._program,"u_projection")),this._resolutionLocation=g(n.getUniformLocation(this._program,"u_resolution")),this._textureLocation=g(n.getUniformLocation(this._program,"u_texture"));let L=this._vertexArrayObject=n.createVertexArray();this._register(M(()=>n.deleteVertexArray(L))),n.bindVertexArray(L);let T=new Float32Array([0,0,1,0,0,1,1,1]),h=n.createBuffer();this._register(M(()=>n.deleteBuffer(h))),n.bindBuffer(n.ARRAY_BUFFER,h),n.bufferData(n.ARRAY_BUFFER,T,n.STATIC_DRAW),n.enableVertexAttribArray(0),n.vertexAttribPointer(0,2,this._gl.FLOAT,false,0,0);let d=new Uint8Array([0,1,2,3]),_=n.createBuffer();this._register(M(()=>n.deleteBuffer(_))),n.bindBuffer(n.ELEMENT_ARRAY_BUFFER,_),n.bufferData(n.ELEMENT_ARRAY_BUFFER,d,n.STATIC_DRAW),this._attributesBuffer=g(n.createBuffer()),this._register(M(()=>n.deleteBuffer(this._attributesBuffer))),n.bindBuffer(n.ARRAY_BUFFER,this._attributesBuffer),n.enableVertexAttribArray(2),n.vertexAttribPointer(2,2,n.FLOAT,false,44,0),n.vertexAttribDivisor(2,1),n.enableVertexAttribArray(3),n.vertexAttribPointer(3,2,n.FLOAT,false,44,2*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(3,1),n.enableVertexAttribArray(4),n.vertexAttribPointer(4,1,n.FLOAT,false,44,4*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(4,1),n.enableVertexAttribArray(5),n.vertexAttribPointer(5,2,n.FLOAT,false,44,5*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(5,1),n.enableVertexAttribArray(6),n.vertexAttribPointer(6,2,n.FLOAT,false,44,7*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(6,1),n.enableVertexAttribArray(1),n.vertexAttribPointer(1,2,n.FLOAT,false,44,9*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(1,1),n.useProgram(this._program);let p=new Int32Array(J.maxAtlasPages);for(let c=0;cn.deleteTexture(u.texture))),n.activeTexture(n.TEXTURE0+c),n.bindTexture(n.TEXTURE_2D,u.texture),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE),n.texImage2D(n.TEXTURE_2D,0,n.RGBA,1,1,0,n.RGBA,n.UNSIGNED_BYTE,new Uint8Array([255,0,0,255])),this._atlasTextures[c]=u};n.enable(n.BLEND),n.blendFunc(n.SRC_ALPHA,n.ONE_MINUS_SRC_ALPHA),this.handleResize()}beginFrame(){if(!this._atlas)return true;let _g=this._atlas._clearModelGeneration||0;return this._lastSeenClearModelGeneration!==_g?(this._lastSeenClearModelGeneration=_g,true):false}updateCell(t,r,s,a,o,n,L,T,h){this._updateCell(this._vertices.attributes,t,r,s,a,o,n,L,T,h)}_updateCell(t,r,s,a,o,n,L,T,h,d){if(N=(s*this._terminal.cols+r)*11,a===0||a===void 0){t.fill(0,N,N+11-1-2);return}this._atlas&&(T&&T.length>1?S=this._atlas.getRasterizedGlyphCombinedChar(T,o,n,L,false,this._terminal.element):S=this._atlas.getRasterizedGlyph(a,o,n,L,false,this._terminal.element),Ie=Math.floor((this._dimensions.device.cell.width-this._dimensions.device.char.width)/2),o!==d&&S.offset.x>Ie?(k0=S.offset.x-Ie,t[N]=-(S.offset.x-k0)+this._dimensions.device.char.left,t[N+1]=-S.offset.y+this._dimensions.device.char.top,t[N+2]=(S.size.x-k0)/this._dimensions.device.canvas.width,t[N+3]=S.size.y/this._dimensions.device.canvas.height,t[N+4]=S.texturePage,t[N+5]=S.texturePositionClipSpace.x+k0/this._atlas.pages[S.texturePage].canvas.width,t[N+6]=S.texturePositionClipSpace.y,t[N+7]=S.sizeClipSpace.x-k0/this._atlas.pages[S.texturePage].canvas.width,t[N+8]=S.sizeClipSpace.y):(t[N]=-S.offset.x+this._dimensions.device.char.left,t[N+1]=-S.offset.y+this._dimensions.device.char.top,t[N+2]=S.size.x/this._dimensions.device.canvas.width,t[N+3]=S.size.y/this._dimensions.device.canvas.height,t[N+4]=S.texturePage,t[N+5]=S.texturePositionClipSpace.x,t[N+6]=S.texturePositionClipSpace.y,t[N+7]=S.sizeClipSpace.x,t[N+8]=S.sizeClipSpace.y),this._optionsService.rawOptions.rescaleOverlappingGlyphs&&Ze(a,h,S.size.x,this._dimensions.device.cell.width)&&(t[N+2]=(this._dimensions.device.cell.width-1)/this._dimensions.device.canvas.width))}clear(){let t=this._terminal,r=t.cols*t.rows*11;this._vertices.count!==r?this._vertices.attributes=new Float32Array(r):this._vertices.attributes.fill(0);let s=0;for(;sthis._atlasTextures.length&&!this._pageOverflowWarned&&(this._pageOverflowWarned=true,console.warn("xterm webgl: atlas page count ("+this._atlas.pages.length+") exceeds the renderer texture capacity ("+this._atlasTextures.length+"), some glyphs will not render correctly"));for(let o=0;o=e.rows||T<0){this.clear();return};this.hasSelection=true,this.columnSelectMode=s,this.viewportStartRow=o,this.viewportEndRow=n,this.viewportCappedStartRow=L,this.viewportCappedEndRow=T,this.startCol=t[0],this.endCol=r[0]}isCellSelected(e,t,r){return this.hasSelection?(r-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&r>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&r<=this.viewportCappedEndRow:r>this.viewportStartRow&&r=this.startCol&&t=this.startCol):false}};function Tt(){return new Fe}var lt=2147483648,se=class{constructor(){;this.cells=new Uint32Array(0),this.lineLengths=new Uint32Array(0),this.selection=Tt()}resize(e,t){let r=e*t*4;r!==this.cells.length&&(this.cells=new Uint32Array(r),this.lineLengths=new Uint32Array(t))}clear(){;this.cells.fill(0,0),this.lineLengths.fill(0,0)}};var jt=`#version 300 es layout (location = 0) in vec2 a_position; layout (location = 1) in vec2 a_size; layout (location = 2) in vec4 a_color; -@@ -71,5 +71,5 @@ out vec4 outColor; +@@ -71,5 +66,4 @@ out vec4 outColor; void main() { outColor = v_color; -}`,l0=8,Me=l0*Float32Array.BYTES_PER_ELEMENT,e1=20*l0,ne=class{constructor(){this.attributes=new Float32Array(e1),this.count=0}},p0=0,Lt=0,ht=0,dt=0,_t=0,ct=0,ut=0,ae=class extends W{constructor(t,r,s,a,o){super();this._terminal=t;this._gl=r;this._dimensions=s;this._themeService=a;this._logService=o;this._vertices=new ne;this._verticesCursor=new ne;let n=this._gl;this._program=g(ie(n,jt,Jt,this._logService)),this._register(M(()=>n.deleteProgram(this._program))),this._projectionLocation=g(n.getUniformLocation(this._program,"u_projection"));let L=this._vertexArrayObject=n.createVertexArray();this._register(M(()=>n.deleteVertexArray(L))),n.bindVertexArray(L);let T=new Float32Array([0,0,1,0,0,1,1,1]),h=n.createBuffer();this._register(M(()=>n.deleteBuffer(h))),n.bindBuffer(n.ARRAY_BUFFER,h),n.bufferData(n.ARRAY_BUFFER,T,n.STATIC_DRAW),n.enableVertexAttribArray(3),n.vertexAttribPointer(3,2,this._gl.FLOAT,!1,0,0);let d=new Uint8Array([0,1,2,3]),_=n.createBuffer();this._register(M(()=>n.deleteBuffer(_))),n.bindBuffer(n.ELEMENT_ARRAY_BUFFER,_),n.bufferData(n.ELEMENT_ARRAY_BUFFER,d,n.STATIC_DRAW),this._attributesBuffer=g(n.createBuffer()),this._register(M(()=>n.deleteBuffer(this._attributesBuffer))),n.bindBuffer(n.ARRAY_BUFFER,this._attributesBuffer),n.enableVertexAttribArray(0),n.vertexAttribPointer(0,2,n.FLOAT,!1,Me,0),n.vertexAttribDivisor(0,1),n.enableVertexAttribArray(1),n.vertexAttribPointer(1,2,n.FLOAT,!1,Me,2*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(1,1),n.enableVertexAttribArray(2),n.vertexAttribPointer(2,4,n.FLOAT,!1,Me,4*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(2,1),this._updateCachedColors(a.colors),this._register(this._themeService.onChangeColors(p=>{this._updateCachedColors(p),this._updateViewportRectangle()}))}renderBackgrounds(){this._renderVertices(this._vertices)}renderCursor(){this._renderVertices(this._verticesCursor)}_renderVertices(t){let r=this._gl;r.useProgram(this._program),r.bindVertexArray(this._vertexArrayObject),r.uniformMatrix4fv(this._projectionLocation,!1,te),r.bindBuffer(r.ARRAY_BUFFER,this._attributesBuffer),r.bufferData(r.ARRAY_BUFFER,t.attributes,r.DYNAMIC_DRAW),r.drawElementsInstanced(this._gl.TRIANGLE_STRIP,4,r.UNSIGNED_BYTE,0,t.count)}handleResize(){this._updateViewportRectangle()}setDimensions(t){this._dimensions=t}_updateCachedColors(t){this._bgFloat=this._colorToFloat32Array(t.background),this._cursorFloat=this._colorToFloat32Array(t.cursor)}_updateViewportRectangle(){this._addRectangleFloat(this._vertices.attributes,0,0,0,this._terminal.cols*this._dimensions.device.cell.width,this._terminal.rows*this._dimensions.device.cell.height,this._bgFloat)}updateBackgrounds(t){let r=this._terminal,s=this._vertices,a=1,o,n,L,T,h,d,_,p,c,u,C;for(o=0;o>24&255)/255,_t=(p0>>16&255)/255,ct=(p0>>8&255)/255,ut=1,this._addRectangle(t.attributes,r,Lt,ht,(n-o)*this._dimensions.device.cell.width,this._dimensions.device.cell.height,dt,_t,ct,ut)}_addRectangle(t,r,s,a,o,n,L,T,h,d){t[r]=s/this._dimensions.device.canvas.width,t[r+1]=a/this._dimensions.device.canvas.height,t[r+2]=o/this._dimensions.device.canvas.width,t[r+3]=n/this._dimensions.device.canvas.height,t[r+4]=L,t[r+5]=T,t[r+6]=h,t[r+7]=d}_addRectangleFloat(t,r,s,a,o,n,L){t[r]=s/this._dimensions.device.canvas.width,t[r+1]=a/this._dimensions.device.canvas.height,t[r+2]=o/this._dimensions.device.canvas.width,t[r+3]=n/this._dimensions.device.canvas.height,t[r+4]=L[0],t[r+5]=L[1],t[r+6]=L[2],t[r+7]=L[3]}_colorToFloat32Array(t){return new Float32Array([(t.rgba>>24&255)/255,(t.rgba>>16&255)/255,(t.rgba>>8&255)/255,(t.rgba&255)/255])}};var oe=class extends W{constructor(t,r,s,a,o,n,L,T){super();this._container=r;this._alpha=o;this._coreBrowserService=n;this._optionsService=L;this._themeService=T;this._deviceCharWidth=0;this._deviceCharHeight=0;this._deviceCellWidth=0;this._deviceCellHeight=0;this._deviceCharLeft=0;this._deviceCharTop=0;this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add(`xterm-${s}-layer`),this._canvas.style.zIndex=a.toString(),this._initCanvas(),this._container.appendChild(this._canvas),this._register(this._themeService.onChangeColors(h=>{this._refreshCharAtlas(t,h),this.reset(t)})),this._register(M(()=>{this._canvas.remove()}))}_initCanvas(){this._ctx=g(this._canvas.getContext("2d",{alpha:this._alpha})),this._alpha||this._clearAll()}handleBlur(t){}handleFocus(t){}handleCursorMove(t){}handleGridChanged(t,r,s){}handleSelectionChanged(t,r,s,a=!1){}_setTransparency(t,r){if(r===this._alpha)return;let s=this._canvas;this._alpha=r,this._canvas=this._canvas.cloneNode(),this._initCanvas(),this._container.replaceChild(this._canvas,s),this._refreshCharAtlas(t,this._themeService.colors),this.handleGridChanged(t,0,t.rows-1)}_refreshCharAtlas(t,r){this._deviceCharWidth<=0&&this._deviceCharHeight<=0||(this._charAtlas=Y0(t,this._optionsService.rawOptions,r,this._deviceCellWidth,this._deviceCellHeight,this._deviceCharWidth,this._deviceCharHeight,this._coreBrowserService.dpr,2048),this._charAtlas.warmUp())}resize(t,r){this._deviceCellWidth=r.device.cell.width,this._deviceCellHeight=r.device.cell.height,this._deviceCharWidth=r.device.char.width,this._deviceCharHeight=r.device.char.height,this._deviceCharLeft=r.device.char.left,this._deviceCharTop=r.device.char.top,this._canvas.width=r.device.canvas.width,this._canvas.height=r.device.canvas.height,this._canvas.style.width=`${r.css.canvas.width}px`,this._canvas.style.height=`${r.css.canvas.height}px`,this._alpha||this._clearAll(),this._refreshCharAtlas(t,this._themeService.colors)}_fillBottomLineAtCells(t,r,s=1){this._ctx.fillRect(t*this._deviceCellWidth,(r+1)*this._deviceCellHeight-this._coreBrowserService.dpr-1,s*this._deviceCellWidth,this._coreBrowserService.dpr)}_clearAll(){this._alpha?this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(0,0,this._canvas.width,this._canvas.height))}_clearCells(t,r,s,a){this._alpha?this._ctx.clearRect(t*this._deviceCellWidth,r*this._deviceCellHeight,s*this._deviceCellWidth,a*this._deviceCellHeight):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(t*this._deviceCellWidth,r*this._deviceCellHeight,s*this._deviceCellWidth,a*this._deviceCellHeight))}_fillCharTrueColor(t,r,s,a){this._ctx.font=this._getFont(t,!1,!1),this._ctx.textBaseline=K0,this._clipCell(s,a,r.getWidth()),this._ctx.fillText(r.getChars(),s*this._deviceCellWidth+this._deviceCharLeft,a*this._deviceCellHeight+this._deviceCharTop+this._deviceCharHeight)}_clipCell(t,r,s){this._ctx.beginPath(),this._ctx.rect(t*this._deviceCellWidth,r*this._deviceCellHeight,s*this._deviceCellWidth,this._deviceCellHeight),this._ctx.clip()}_getFont(t,r,s){let a=r?t.options.fontWeightBold:t.options.fontWeight;return`${s?"italic":""} ${a} ${t.options.fontSize*this._coreBrowserService.dpr}px ${t.options.fontFamily}`}};var Te=class extends oe{constructor(e,t,r,s,a,o,n){super(r,e,"link",t,!0,a,o,n),this._register(s.onShowLinkUnderline(L=>this._handleShowLinkUnderline(L))),this._register(s.onHideLinkUnderline(L=>this._handleHideLinkUnderline(L)))}resize(e,t){super.resize(e,t),this._state=void 0}reset(e){this._clearCurrentLink()}_clearCurrentLink(){if(this._state){this._clearCells(this._state.x1,this._state.y1,this._state.cols-this._state.x1,1);let e=this._state.y2-this._state.y1-1;e>0&&this._clearCells(0,this._state.y1+1,this._state.cols,e),this._clearCells(0,this._state.y2,this._state.x2,1),this._state=void 0}}_handleShowLinkUnderline(e){if(e.fg===257?this._ctx.fillStyle=this._themeService.colors.background.css:e.fg!==void 0&&st(e.fg)?this._ctx.fillStyle=this._themeService.colors.ansi[e.fg].css:this._ctx.fillStyle=this._themeService.colors.foreground.css,e.y1===e.y2)this._fillBottomLineAtCells(e.x1,e.y1,e.x2-e.x1);else{this._fillBottomLineAtCells(e.x1,e.y1,e.cols-e.x1);for(let t=e.y1+1;tthis._handleColorChange())),this._cellColorResolver=new G0(this._terminal,this._optionsService,this._model.selection,this._decorationService,this._coreBrowserService,this._themeService),this._core=this._terminal._core,this._renderLayers=[new Te(this._core.screenElement,2,this._terminal,this._core.linkifier,this._coreBrowserService,T,this._themeService)],this.dimensions=Ue(),this._devicePixelRatio=this._coreBrowserService.dpr,this._updateDimensions(),this._updateCursorBlink(),this._register(T.onOptionChange(()=>this._handleOptionsChanged())),this._textBlinkStateManager=this._register(new J0(()=>this._requestRedrawViewport(),this._coreBrowserService,this._optionsService)),this._resetBlinkingRowState(),this._deviceMaxTextureSize=this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE),this._register(le(this._canvas,"webglcontextlost",c=>{this._logService.debug("webglcontextlost event received"),c.preventDefault(),this._contextRestorationTimeout=setTimeout(()=>{this._contextRestorationTimeout=void 0,this._logService.warn("webgl context not restored; firing onContextLoss"),this._onContextLoss.fire(c)},3e3)})),this._register(le(this._canvas,"webglcontextrestored",c=>{this._logService.warn("webglcontextrestored event received"),clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0,be(this._terminal),this._initializeWebGLState(),this._requestRedrawViewport()})),this._observerDisposable.value=ve(this._canvas,this._coreBrowserService.window,(c,u)=>this._setCanvasDevicePixelDimensions(c,u)),this._register(this._coreBrowserService.onWindowChange(c=>{this._observerDisposable.value=ve(this._canvas,c,(u,C)=>this._setCanvasDevicePixelDimensions(u,C))})),this._register(le(this._coreBrowserService.mainDocument,"mousedown",()=>this._cursorBlinkStateManager.value?.restartBlinkAnimation())),this._core.screenElement.appendChild(this._canvas),[this._rectangleRenderer.value,this._glyphRenderer.value]=this._initializeWebGLState(),this._isAttached=this._core.screenElement.isConnected,this._register(M(()=>{clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0;for(let c of this._renderLayers)c.dispose();this._canvas.parentElement?.removeChild(this._canvas),be(this._terminal)}))}get textureAtlas(){return this._charAtlas?.pages[0].canvas}_handleColorChange(){this._refreshCharAtlas(),this._clearModel(!0)}handleDevicePixelRatioChange(){this._devicePixelRatio!==this._coreBrowserService.dpr&&(this._devicePixelRatio=this._coreBrowserService.dpr,this.handleResize(this._terminal.cols,this._terminal.rows))}handleResize(t,r){this._updateDimensions(),this._model.resize(this._terminal.cols,this._terminal.rows),this._resetBlinkingRowState();for(let s of this._renderLayers)s.resize(this._terminal,this.dimensions);this._canvas.width=this.dimensions.device.canvas.width,this._canvas.height=this.dimensions.device.canvas.height,this._canvas.style.width=`${this.dimensions.css.canvas.width}px`,this._canvas.style.height=`${this.dimensions.css.canvas.height}px`,this._core.screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._core.screenElement.style.height=`${this.dimensions.css.canvas.height}px`,this._rectangleRenderer.value?.setDimensions(this.dimensions),this._rectangleRenderer.value?.handleResize(),this._glyphRenderer.value?.setDimensions(this.dimensions),this._glyphRenderer.value?.handleResize(),this._refreshCharAtlas(),this._clearModel(!1),this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1,sync:!0})}handleCharSizeChanged(){this.handleResize(this._terminal.cols,this._terminal.rows)}handleBlur(){for(let t of this._renderLayers)t.handleBlur(this._terminal);this._cursorBlinkStateManager.value?.pause(),this._requestRedrawViewport()}handleFocus(){for(let t of this._renderLayers)t.handleFocus(this._terminal);this._cursorBlinkStateManager.value?.resume(),this._requestRedrawViewport()}handleViewportVisibilityChange(t){this._textBlinkStateManager.setViewportVisible(t)}handleSelectionChanged(t,r,s){for(let a of this._renderLayers)a.handleSelectionChanged(this._terminal,t,r,s);this._model.selection.update(this._core,t,r,s),this._requestRedrawViewport()}handleCursorMove(){for(let t of this._renderLayers)t.handleCursorMove(this._terminal);this._cursorBlinkStateManager.value?.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._refreshCharAtlas(),this._updateCursorBlink()}_initializeWebGLState(){return this._rectangleRenderer.value=new ae(this._terminal,this._gl,this.dimensions,this._themeService,this._logService),this._glyphRenderer.value=new re(this._terminal,this._gl,this.dimensions,this._optionsService,this._logService),this.handleCharSizeChanged(),[this._rectangleRenderer.value,this._glyphRenderer.value]}_refreshCharAtlas(){if(this.dimensions.device.char.width<=0&&this.dimensions.device.char.height<=0){this._isAttached=!1;return}let t=Y0(this._terminal,this._optionsService.rawOptions,this._themeService.colors,this.dimensions.device.cell.width,this.dimensions.device.cell.height,this.dimensions.device.char.width,this.dimensions.device.char.height,this._coreBrowserService.dpr,this._deviceMaxTextureSize,this._customGlyphs);this._charAtlas!==t&&(this._onChangeTextureAtlas.fire(t.pages[0].canvas),this._charAtlasDisposable.value=De(c0.forward(t.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas),c0.forward(t.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas))),this._charAtlas=t,this._charAtlas.warmUp(),this._glyphRenderer.value?.setAtlas(this._charAtlas)}_clearModel(t){this._model.clear(),t&&this._glyphRenderer.value?.clear()}clearTextureAtlas(){this._charAtlas?.clearTexture(),this._clearModel(!0),this._requestRedrawViewport()}clear(){this._clearModel(!0);for(let t of this._renderLayers)t.reset(this._terminal);this._resetBlinkingRowState(),this._textBlinkStateManager.setNeedsBlinkInViewport(!1),this._cursorBlinkStateManager.value?.restartBlinkAnimation(),this._updateCursorBlink()}renderRows(t,r){if(!this._isAttached)if(this._core.screenElement?.isConnected&&this._charSizeService.width&&this._charSizeService.height)this._updateDimensions(),this._refreshCharAtlas(),this._isAttached=!0;else return;for(let o of this._renderLayers)o.handleGridChanged(this._terminal,t,r);if(!this._glyphRenderer.value||!this._rectangleRenderer.value)return;this._glyphRenderer.value.beginFrame()?(this._clearModel(!0),this._updateModel(0,this._terminal.rows-1)):this._updateModel(t,r);let s=!1,a=0;for(;this._charAtlas&&this._glyphRenderer.value.beginFrame()&&a++<32;)s=!0,this._clearModel(!0),this._updateModel(0,this._terminal.rows-1);s&&this._glyphRenderer.value.invalidateAtlasTextures(),this._rectangleRenderer.value.renderBackgrounds(),this._glyphRenderer.value.render(this._model),(!this._cursorBlinkStateManager.value||this._cursorBlinkStateManager.value.isCursorVisible)&&this._rectangleRenderer.value.renderCursor()}_updateCursorBlink(){this._coreService.decPrivateModes.cursorBlink??this._terminal.options.cursorBlink?this._cursorBlinkStateManager.value=new q0(()=>{this._requestRedrawCursor()},this._coreBrowserService):this._cursorBlinkStateManager.clear(),this._requestRedrawCursor()}_updateModel(t,r){let s=this._core,a=this._workCell,o,n,L,T,h,d,_,p,c,u,C,Q,m,R,F,E,y;t=pt(t,s.rows-1,0),r=pt(r,s.rows-1,0);let Y=this._coreService.decPrivateModes.cursorStyle??s.options.cursorStyle??"block",X=this._terminal.buffer.active.baseY+this._terminal.buffer.active.cursorY,A0=X-s.buffer.ydisp,t0=Math.min(this._terminal.buffer.active.cursorX,s.cols-1),L0=-1,A=this._coreService.isCursorInitialized&&!this._coreService.isCursorHidden&&(!this._cursorBlinkStateManager.value||this._cursorBlinkStateManager.value.isCursorVisible);this._model.cursor=void 0;let n0=!1;for(n=t;n<=r;n++){L=n+s.buffer.ydisp;let a0=s.buffer.lines.get(L);if(!a0){for(this._model.lineLengths[n]=0,E=0;E=_,c=E,h.length>0&&E===h[0][0]&&p){u=h.shift();let q=this._model.selection.isCellSelected(this._terminal,u[0],L);for(F=u[0]+1;F=u[1],p?(d=!0,a=new ge(a,T.translateToString(!0,u[0],u[1]),u[1]-u[0]),c=u[1]-1):_=u[1]}if(Q=a.getChars(),m=a.getCode(),F=(n*s.cols+E)*4,!$&&a.isBlink()&&($=!0),this._cellColorResolver.resolve(a,E,L,this.dimensions.device.cell.width,this.dimensions.device.cell.height),A&&L===X&&(E===t0&&(this._model.cursor={x:t0,y:A0,width:a.getWidth(),style:this._coreBrowserService.isFocused?Y:s.options.cursorInactiveStyle,cursorWidth:s.options.cursorWidth,dpr:this._devicePixelRatio},L0=t0+a.getWidth()-1),E>=t0&&E<=L0&&(this._coreBrowserService.isFocused&&Y==="block"||this._coreBrowserService.isFocused===!1&&s.options.cursorInactiveStyle==="block")&&(this._cellColorResolver.result.fg=50331648|this._themeService.colors.cursorAccent.rgba>>8&16777215,this._cellColorResolver.result.bg=50331648|this._themeService.colors.cursor.rgba>>8&16777215)),this._textBlinkStateManager.isEnabled&&!this._textBlinkStateManager.isBlinkOn&&a.isBlink()&&(this._cellColorResolver.result.fg|=1073741824),m!==0&&(this._model.lineLengths[n]=E+1),!(this._model.cells[F]===m&&this._model.cells[F+1]===this._cellColorResolver.result.bg&&this._model.cells[F+2]===this._cellColorResolver.result.fg&&this._model.cells[F+3]===this._cellColorResolver.result.ext)&&(n0=!0,Q.length>1&&(m|=lt),this._model.cells[F]=m,this._model.cells[F+1]=this._cellColorResolver.result.bg,this._model.cells[F+2]=this._cellColorResolver.result.fg,this._model.cells[F+3]=this._cellColorResolver.result.ext,R=a.getWidth(),this._glyphRenderer.value.updateCell(E,n,m,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext,Q,R,o),d)){for(a=this._workCell,E++;E<=c;E++)y=(n*s.cols+E)*4,this._nullModelCell(E,n,y,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext);E--}}this._setRowBlinkState(n,$)}n0&&this._rectangleRenderer.value.updateBackgrounds(this._model),this._rectangleRenderer.value.updateCursor(this._model),this._updateTextBlinkState()}_nullModelCell(t,r,s,a,o,n){this._glyphRenderer.value.updateCell(t,r,0,a,o,n,"",0,0),this._model.cells[s]=0,this._model.cells[s+1]=a,this._model.cells[s+2]=o,this._model.cells[s+3]=n}_resetBlinkingRowState(){this._rowHasBlinkingCells=new Array(this._terminal.rows).fill(!1),this._rowHasBlinkingCellsCount=0}_setRowBlinkState(t,r){this._rowHasBlinkingCells[t]!==r&&(this._rowHasBlinkingCells[t]=r,this._rowHasBlinkingCellsCount+=r?1:-1)}_updateTextBlinkState(){this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount>0)}_updateDimensions(){!this._charSizeService.width||!this._charSizeService.height||(this.dimensions.device.char.width=Math.floor(this._charSizeService.width*this._devicePixelRatio),this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*this._devicePixelRatio),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.top=this._optionsService.rawOptions.lineHeight===1?0:Math.round((this.dimensions.device.cell.height-this.dimensions.device.char.height)/2),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.char.left=Math.floor(this._optionsService.rawOptions.letterSpacing/2),this.dimensions.device.canvas.height=this._terminal.rows*this.dimensions.device.cell.height,this.dimensions.device.canvas.width=this._terminal.cols*this.dimensions.device.cell.width,this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/this._devicePixelRatio),this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/this._devicePixelRatio),this.dimensions.css.cell.height=this.dimensions.device.cell.height/this._devicePixelRatio,this.dimensions.css.cell.width=this.dimensions.device.cell.width/this._devicePixelRatio)}_setCanvasDevicePixelDimensions(t,r){this._canvas.width===t&&this._canvas.height===r||(this._canvas.width=t,this._canvas.height=r,this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1,sync:!0}))}_requestRedrawViewport(){this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1})}_requestRedrawCursor(){let t=this._terminal.buffer.active.cursorY;this._onRequestRedraw.fire({start:t,end:t})}},ge=class extends s0{constructor(t,r,s){super();this.content=0;this.combinedData="";this.fg=t.fg,this.bg=t.bg,this.combinedData=r,this._width=s}isCombined(){return 2097152}getWidth(){return this._width}getChars(){return this.combinedData}getCode(){return 2097151}setFromCharData(t){throw new Error("not implemented")}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}};function pt(i,e,t=0){return Math.max(Math.min(i,e),t)}var Ct=class extends W{constructor(t){if(ue&&He()<16){let r={antialias:!1,depth:!1,preserveDrawingBuffer:!0};if(!document.createElement("canvas").getContext("webgl2",r))throw new Error("WebGL2 is only supported on Safari 16 and above")}super();this._onChangeTextureAtlas=this._register(new U);this.onChangeTextureAtlas=this._onChangeTextureAtlas.event;this._onAddTextureAtlasCanvas=this._register(new U);this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event;this._onRemoveTextureAtlasCanvas=this._register(new U);this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event;this._onContextLoss=this._register(new U);this.onContextLoss=this._onContextLoss.event;this._customGlyphs=t?.customGlyphs??!0,this._preserveDrawingBuffer=t?.preserveDrawingBuffer}activate(t){let r=t._core;if(!t.element){this._register(r.onWillOpen(()=>this.activate(t)));return}this._terminal=t;let s=r.coreService,a=r.optionsService,o=r,n=o._renderService,L=o._characterJoinerService,T=o._charSizeService,h=o._coreBrowserService,d=o._decorationService,_=o._logService,p=o._themeService;this._renderer=this._register(new Le(t,L,T,h,s,d,_,a,p,this._customGlyphs,this._preserveDrawingBuffer)),this._register(c0.forward(this._renderer.onContextLoss,this._onContextLoss)),this._register(c0.forward(this._renderer.onChangeTextureAtlas,this._onChangeTextureAtlas)),this._register(c0.forward(this._renderer.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas)),this._register(c0.forward(this._renderer.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas)),n.setRenderer(this._renderer),this._register(M(()=>{if(this._terminal._core._store._isDisposed)return;let c=this._terminal._core._renderService;c.setRenderer(this._terminal._core._createRenderer()),c.handleResize(t.cols,t.rows)}))}get textureAtlas(){return this._renderer?.textureAtlas}clearTextureAtlas(){this._renderer?.clearTextureAtlas()}};export{Ct as WebglAddon}; -+}`,l0=8,Me=l0*Float32Array.BYTES_PER_ELEMENT,e1=20*l0,ne=class{constructor(){this.attributes=new Float32Array(e1),this.count=0}},p0=0,Lt=0,ht=0,dt=0,_t=0,ct=0,ut=0,ae=class extends W{constructor(t,r,s,a,o){super();this._terminal=t;this._gl=r;this._dimensions=s;this._themeService=a;this._logService=o;this._vertices=new ne;this._verticesCursor=new ne;let n=this._gl;this._program=g(ie(n,jt,Jt,this._logService)),this._register(M(()=>n.deleteProgram(this._program))),this._projectionLocation=g(n.getUniformLocation(this._program,"u_projection"));let L=this._vertexArrayObject=n.createVertexArray();this._register(M(()=>n.deleteVertexArray(L))),n.bindVertexArray(L);let T=new Float32Array([0,0,1,0,0,1,1,1]),h=n.createBuffer();this._register(M(()=>n.deleteBuffer(h))),n.bindBuffer(n.ARRAY_BUFFER,h),n.bufferData(n.ARRAY_BUFFER,T,n.STATIC_DRAW),n.enableVertexAttribArray(3),n.vertexAttribPointer(3,2,this._gl.FLOAT,!1,0,0);let d=new Uint8Array([0,1,2,3]),_=n.createBuffer();this._register(M(()=>n.deleteBuffer(_))),n.bindBuffer(n.ELEMENT_ARRAY_BUFFER,_),n.bufferData(n.ELEMENT_ARRAY_BUFFER,d,n.STATIC_DRAW),this._attributesBuffer=g(n.createBuffer()),this._register(M(()=>n.deleteBuffer(this._attributesBuffer))),n.bindBuffer(n.ARRAY_BUFFER,this._attributesBuffer),n.enableVertexAttribArray(0),n.vertexAttribPointer(0,2,n.FLOAT,!1,Me,0),n.vertexAttribDivisor(0,1),n.enableVertexAttribArray(1),n.vertexAttribPointer(1,2,n.FLOAT,!1,Me,2*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(1,1),n.enableVertexAttribArray(2),n.vertexAttribPointer(2,4,n.FLOAT,!1,Me,4*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(2,1),this._updateCachedColors(a.colors),this._register(this._themeService.onChangeColors(p=>{this._updateCachedColors(p),this._updateViewportRectangle()}))}renderBackgrounds(){this._renderVertices(this._vertices)}renderCursor(){this._renderVertices(this._verticesCursor)}_renderVertices(t){let r=this._gl;r.useProgram(this._program),r.bindVertexArray(this._vertexArrayObject),r.uniformMatrix4fv(this._projectionLocation,!1,te),r.bindBuffer(r.ARRAY_BUFFER,this._attributesBuffer),r.bufferData(r.ARRAY_BUFFER,t.attributes,r.DYNAMIC_DRAW),r.drawElementsInstanced(this._gl.TRIANGLE_STRIP,4,r.UNSIGNED_BYTE,0,t.count)}handleResize(){this._updateViewportRectangle()}setDimensions(t){this._dimensions=t}_updateCachedColors(t){this._bgFloat=this._colorToFloat32Array(t.background),this._cursorFloat=this._colorToFloat32Array(t.cursor)}_updateViewportRectangle(){this._addRectangleFloat(this._vertices.attributes,0,0,0,this._terminal.cols*this._dimensions.device.cell.width,this._terminal.rows*this._dimensions.device.cell.height,this._bgFloat)}updateBackgrounds(t){let r=this._terminal,s=this._vertices,a=1,o,n,L,T,h,d,_,p,c,u,C;for(o=0;o>24&255)/255,_t=(p0>>16&255)/255,ct=(p0>>8&255)/255,ut=1,this._addRectangle(t.attributes,r,Lt,ht,(n-o)*this._dimensions.device.cell.width,this._dimensions.device.cell.height,dt,_t,ct,ut)}_addRectangle(t,r,s,a,o,n,L,T,h,d){t[r]=s/this._dimensions.device.canvas.width,t[r+1]=a/this._dimensions.device.canvas.height,t[r+2]=o/this._dimensions.device.canvas.width,t[r+3]=n/this._dimensions.device.canvas.height,t[r+4]=L,t[r+5]=T,t[r+6]=h,t[r+7]=d}_addRectangleFloat(t,r,s,a,o,n,L){t[r]=s/this._dimensions.device.canvas.width,t[r+1]=a/this._dimensions.device.canvas.height,t[r+2]=o/this._dimensions.device.canvas.width,t[r+3]=n/this._dimensions.device.canvas.height,t[r+4]=L[0],t[r+5]=L[1],t[r+6]=L[2],t[r+7]=L[3]}_colorToFloat32Array(t){return new Float32Array([(t.rgba>>24&255)/255,(t.rgba>>16&255)/255,(t.rgba>>8&255)/255,(t.rgba&255)/255])}};var oe=class extends W{constructor(t,r,s,a,o,n,L,T){super();this._container=r;this._alpha=o;this._coreBrowserService=n;this._optionsService=L;this._themeService=T;this._deviceCharWidth=0;this._deviceCharHeight=0;this._deviceCellWidth=0;this._deviceCellHeight=0;this._deviceCharLeft=0;this._deviceCharTop=0;this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add(`xterm-${s}-layer`),this._canvas.style.zIndex=a.toString(),this._initCanvas(),this._container.appendChild(this._canvas),this._register(this._themeService.onChangeColors(h=>{this._refreshCharAtlas(t,h),this.reset(t)})),this._register(M(()=>{this._canvas.remove()}))}_initCanvas(){this._ctx=g(this._canvas.getContext("2d",{alpha:this._alpha})),this._alpha||this._clearAll()}handleBlur(t){}handleFocus(t){}handleCursorMove(t){}handleGridChanged(t,r,s){}handleSelectionChanged(t,r,s,a=!1){}_setTransparency(t,r){if(r===this._alpha)return;let s=this._canvas;this._alpha=r,this._canvas=this._canvas.cloneNode(),this._initCanvas(),this._container.replaceChild(this._canvas,s),this._refreshCharAtlas(t,this._themeService.colors),this.handleGridChanged(t,0,t.rows-1)}_refreshCharAtlas(t,r){this._deviceCharWidth<=0&&this._deviceCharHeight<=0||(this._charAtlas=Y0(t,this._optionsService.rawOptions,r,this._deviceCellWidth,this._deviceCellHeight,this._deviceCharWidth,this._deviceCharHeight,this._coreBrowserService.dpr,2048),this._charAtlas.warmUp())}resize(t,r){this._deviceCellWidth=r.device.cell.width,this._deviceCellHeight=r.device.cell.height,this._deviceCharWidth=r.device.char.width,this._deviceCharHeight=r.device.char.height,this._deviceCharLeft=r.device.char.left,this._deviceCharTop=r.device.char.top,this._canvas.width=r.device.canvas.width,this._canvas.height=r.device.canvas.height,this._canvas.style.width=`${r.css.canvas.width}px`,this._canvas.style.height=`${r.css.canvas.height}px`,this._alpha||this._clearAll(),this._refreshCharAtlas(t,this._themeService.colors)}_fillBottomLineAtCells(t,r,s=1){this._ctx.fillRect(t*this._deviceCellWidth,(r+1)*this._deviceCellHeight-this._coreBrowserService.dpr-1,s*this._deviceCellWidth,this._coreBrowserService.dpr)}_clearAll(){this._alpha?this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(0,0,this._canvas.width,this._canvas.height))}_clearCells(t,r,s,a){this._alpha?this._ctx.clearRect(t*this._deviceCellWidth,r*this._deviceCellHeight,s*this._deviceCellWidth,a*this._deviceCellHeight):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(t*this._deviceCellWidth,r*this._deviceCellHeight,s*this._deviceCellWidth,a*this._deviceCellHeight))}_fillCharTrueColor(t,r,s,a){this._ctx.font=this._getFont(t,!1,!1),this._ctx.textBaseline=K0,this._clipCell(s,a,r.getWidth()),this._ctx.fillText(r.getChars(),s*this._deviceCellWidth+this._deviceCharLeft,a*this._deviceCellHeight+this._deviceCharTop+this._deviceCharHeight)}_clipCell(t,r,s){this._ctx.beginPath(),this._ctx.rect(t*this._deviceCellWidth,r*this._deviceCellHeight,s*this._deviceCellWidth,this._deviceCellHeight),this._ctx.clip()}_getFont(t,r,s){let a=r?t.options.fontWeightBold:t.options.fontWeight;return`${s?"italic":""} ${a} ${t.options.fontSize*this._coreBrowserService.dpr}px ${t.options.fontFamily}`}};var Te=class extends oe{constructor(e,t,r,s,a,o,n){super(r,e,"link",t,!0,a,o,n),this._register(s.onShowLinkUnderline(L=>this._handleShowLinkUnderline(L))),this._register(s.onHideLinkUnderline(L=>this._handleHideLinkUnderline(L)))}resize(e,t){super.resize(e,t),this._state=void 0}reset(e){this._clearCurrentLink()}_clearCurrentLink(){if(this._state){this._clearCells(this._state.x1,this._state.y1,this._state.cols-this._state.x1,1);let e=this._state.y2-this._state.y1-1;e>0&&this._clearCells(0,this._state.y1+1,this._state.cols,e),this._clearCells(0,this._state.y2,this._state.x2,1),this._state=void 0}}_handleShowLinkUnderline(e){if(e.fg===257?this._ctx.fillStyle=this._themeService.colors.background.css:e.fg!==void 0&&st(e.fg)?this._ctx.fillStyle=this._themeService.colors.ansi[e.fg].css:this._ctx.fillStyle=this._themeService.colors.foreground.css,e.y1===e.y2)this._fillBottomLineAtCells(e.x1,e.y1,e.x2-e.x1);else{this._fillBottomLineAtCells(e.x1,e.y1,e.cols-e.x1);for(let t=e.y1+1;tthis._handleColorChange())),this._cellColorResolver=new G0(this._terminal,this._optionsService,this._model.selection,this._decorationService,this._coreBrowserService,this._themeService),this._core=this._terminal._core,this._renderLayers=[new Te(this._core.screenElement,2,this._terminal,this._core.linkifier,this._coreBrowserService,T,this._themeService)],this.dimensions=Ue(),this._devicePixelRatio=this._coreBrowserService.dpr,this._updateDimensions(),this._updateCursorBlink(),this._register(T.onOptionChange(()=>this._handleOptionsChanged())),this._textBlinkStateManager=this._register(new J0(()=>this._requestRedrawViewport(),this._coreBrowserService,this._optionsService)),this._resetBlinkingRowState(),this._deviceMaxTextureSize=this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE),this._register(le(this._canvas,"webglcontextlost",c=>{this._logService.debug("webglcontextlost event received"),c.preventDefault(),this._contextRestorationTimeout=setTimeout(()=>{this._contextRestorationTimeout=void 0,this._logService.warn("webgl context not restored; firing onContextLoss"),this._onContextLoss.fire(c)},3e3)})),this._register(le(this._canvas,"webglcontextrestored",c=>{this._logService.warn("webglcontextrestored event received"),clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0,be(this._terminal),this._initializeWebGLState(),this._requestRedrawViewport()})),this._observerDisposable.value=ve(this._canvas,this._coreBrowserService.window,(c,u)=>this._setCanvasDevicePixelDimensions(c,u)),this._register(this._coreBrowserService.onWindowChange(c=>{this._observerDisposable.value=ve(this._canvas,c,(u,C)=>this._setCanvasDevicePixelDimensions(u,C))})),this._register(le(this._coreBrowserService.mainDocument,"mousedown",()=>this._cursorBlinkStateManager.value?.restartBlinkAnimation())),this._core.screenElement.appendChild(this._canvas),[this._rectangleRenderer.value,this._glyphRenderer.value]=this._initializeWebGLState(),this._isAttached=this._core.screenElement.isConnected,this._register(M(()=>{clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0;for(let c of this._renderLayers)c.dispose();this._canvas.parentElement?.removeChild(this._canvas),be(this._terminal)}))}get textureAtlas(){return this._charAtlas?.pages[0].canvas}_handleColorChange(){this._refreshCharAtlas(),this._clearModel(!0)}handleDevicePixelRatioChange(){this._devicePixelRatio!==this._coreBrowserService.dpr&&(this._devicePixelRatio=this._coreBrowserService.dpr,this.handleResize(this._terminal.cols,this._terminal.rows))}handleResize(t,r){this._updateDimensions(),this._model.resize(this._terminal.cols,this._terminal.rows),this._resetBlinkingRowState();for(let s of this._renderLayers)s.resize(this._terminal,this.dimensions);this._canvas.width=this.dimensions.device.canvas.width,this._canvas.height=this.dimensions.device.canvas.height,this._canvas.style.width=`${this.dimensions.css.canvas.width}px`,this._canvas.style.height=`${this.dimensions.css.canvas.height}px`,this._core.screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._core.screenElement.style.height=`${this.dimensions.css.canvas.height}px`,this._rectangleRenderer.value?.setDimensions(this.dimensions),this._rectangleRenderer.value?.handleResize(),this._glyphRenderer.value?.setDimensions(this.dimensions),this._glyphRenderer.value?.handleResize(),this._refreshCharAtlas(),this._clearModel(!1),this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1,sync:!0})}handleCharSizeChanged(){this.handleResize(this._terminal.cols,this._terminal.rows)}handleBlur(){for(let t of this._renderLayers)t.handleBlur(this._terminal);this._cursorBlinkStateManager.value?.pause(),this._requestRedrawViewport()}handleFocus(){for(let t of this._renderLayers)t.handleFocus(this._terminal);this._cursorBlinkStateManager.value?.resume(),this._requestRedrawViewport()}handleViewportVisibilityChange(t){this._textBlinkStateManager.setViewportVisible(t)}handleSelectionChanged(t,r,s){for(let a of this._renderLayers)a.handleSelectionChanged(this._terminal,t,r,s);this._model.selection.update(this._core,t,r,s),this._requestRedrawViewport()}handleCursorMove(){for(let t of this._renderLayers)t.handleCursorMove(this._terminal);this._cursorBlinkStateManager.value?.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._refreshCharAtlas(),this._updateCursorBlink()}_initializeWebGLState(){return this._rectangleRenderer.value=new ae(this._terminal,this._gl,this.dimensions,this._themeService,this._logService),this._glyphRenderer.value=new re(this._terminal,this._gl,this.dimensions,this._optionsService,this._logService),this.handleCharSizeChanged(),[this._rectangleRenderer.value,this._glyphRenderer.value]}_refreshCharAtlas(){if(this.dimensions.device.char.width<=0&&this.dimensions.device.char.height<=0){this._isAttached=!1;return}let t=Y0(this._terminal,this._optionsService.rawOptions,this._themeService.colors,this.dimensions.device.cell.width,this.dimensions.device.cell.height,this.dimensions.device.char.width,this.dimensions.device.char.height,this._coreBrowserService.dpr,this._deviceMaxTextureSize,this._customGlyphs);this._charAtlas!==t&&(this._onChangeTextureAtlas.fire(t.pages[0].canvas),this._charAtlasDisposable.value=De(c0.forward(t.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas),c0.forward(t.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas))),this._charAtlas=t,this._charAtlas.warmUp(),this._glyphRenderer.value?.setAtlas(this._charAtlas)}_clearModel(t){this._model.clear(),t&&this._glyphRenderer.value?.clear()}clearTextureAtlas(){this._charAtlas?.clearTexture(),this._clearModel(!0),this._requestRedrawViewport()}clear(){this._clearModel(!0);for(let t of this._renderLayers)t.reset(this._terminal);this._resetBlinkingRowState(),this._textBlinkStateManager.setNeedsBlinkInViewport(!1),this._cursorBlinkStateManager.value?.restartBlinkAnimation(),this._updateCursorBlink()}renderRows(t,r){if(!this._isAttached)if(this._core.screenElement?.isConnected&&this._charSizeService.width&&this._charSizeService.height)this._updateDimensions(),this._refreshCharAtlas(),this._isAttached=!0;else return;for(let o of this._renderLayers)o.handleGridChanged(this._terminal,t,r);if(!this._glyphRenderer.value||!this._rectangleRenderer.value)return;this._glyphRenderer.value.beginFrame()?(this._clearModel(!0),this._updateModel(0,this._terminal.rows-1)):this._updateModel(t,r);let s=!1,a=0;for(;this._charAtlas&&a++<32&&this._glyphRenderer.value.beginFrame();)s=!0,this._clearModel(!0),this._updateModel(0,this._terminal.rows-1);s&&this._glyphRenderer.value.invalidateAtlasTextures(),this._rectangleRenderer.value.renderBackgrounds(),this._glyphRenderer.value.render(this._model),(!this._cursorBlinkStateManager.value||this._cursorBlinkStateManager.value.isCursorVisible)&&this._rectangleRenderer.value.renderCursor()}_updateCursorBlink(){this._coreService.decPrivateModes.cursorBlink??this._terminal.options.cursorBlink?this._cursorBlinkStateManager.value=new q0(()=>{this._requestRedrawCursor()},this._coreBrowserService):this._cursorBlinkStateManager.clear(),this._requestRedrawCursor()}_updateModel(t,r){let s=this._core,a=this._workCell,o,n,L,T,h,d,_,p,c,u,C,Q,m,R,F,E,y;t=pt(t,s.rows-1,0),r=pt(r,s.rows-1,0);let Y=this._coreService.decPrivateModes.cursorStyle??s.options.cursorStyle??"block",X=this._terminal.buffer.active.baseY+this._terminal.buffer.active.cursorY,A0=X-s.buffer.ydisp,t0=Math.min(this._terminal.buffer.active.cursorX,s.cols-1),L0=-1,A=this._coreService.isCursorInitialized&&!this._coreService.isCursorHidden&&(!this._cursorBlinkStateManager.value||this._cursorBlinkStateManager.value.isCursorVisible);this._model.cursor=void 0;let n0=!1;for(n=t;n<=r;n++){L=n+s.buffer.ydisp;let a0=s.buffer.lines.get(L);if(!a0){for(this._model.lineLengths[n]=0,E=0;E=_,c=E,h.length>0&&E===h[0][0]&&p){u=h.shift();let q=this._model.selection.isCellSelected(this._terminal,u[0],L);for(F=u[0]+1;F=u[1],p?(d=!0,a=new ge(a,T.translateToString(!0,u[0],u[1]),u[1]-u[0]),c=u[1]-1):_=u[1]}if(Q=a.getChars(),m=a.getCode(),F=(n*s.cols+E)*4,!$&&a.isBlink()&&($=!0),this._cellColorResolver.resolve(a,E,L,this.dimensions.device.cell.width,this.dimensions.device.cell.height),A&&L===X&&(E===t0&&(this._model.cursor={x:t0,y:A0,width:a.getWidth(),style:this._coreBrowserService.isFocused?Y:s.options.cursorInactiveStyle,cursorWidth:s.options.cursorWidth,dpr:this._devicePixelRatio},L0=t0+a.getWidth()-1),E>=t0&&E<=L0&&(this._coreBrowserService.isFocused&&Y==="block"||this._coreBrowserService.isFocused===!1&&s.options.cursorInactiveStyle==="block")&&(this._cellColorResolver.result.fg=50331648|this._themeService.colors.cursorAccent.rgba>>8&16777215,this._cellColorResolver.result.bg=50331648|this._themeService.colors.cursor.rgba>>8&16777215)),this._textBlinkStateManager.isEnabled&&!this._textBlinkStateManager.isBlinkOn&&a.isBlink()&&(this._cellColorResolver.result.fg|=1073741824),m!==0&&(this._model.lineLengths[n]=E+1),!(this._model.cells[F]===m&&this._model.cells[F+1]===this._cellColorResolver.result.bg&&this._model.cells[F+2]===this._cellColorResolver.result.fg&&this._model.cells[F+3]===this._cellColorResolver.result.ext)&&(n0=!0,Q.length>1&&(m|=lt),this._model.cells[F]=m,this._model.cells[F+1]=this._cellColorResolver.result.bg,this._model.cells[F+2]=this._cellColorResolver.result.fg,this._model.cells[F+3]=this._cellColorResolver.result.ext,R=a.getWidth(),this._glyphRenderer.value.updateCell(E,n,m,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext,Q,R,o),d)){for(a=this._workCell,E++;E<=c;E++)y=(n*s.cols+E)*4,this._nullModelCell(E,n,y,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext);E--}}this._setRowBlinkState(n,$)}n0&&this._rectangleRenderer.value.updateBackgrounds(this._model),this._rectangleRenderer.value.updateCursor(this._model),this._updateTextBlinkState()}_nullModelCell(t,r,s,a,o,n){this._glyphRenderer.value.updateCell(t,r,0,a,o,n,"",0,0),this._model.cells[s]=0,this._model.cells[s+1]=a,this._model.cells[s+2]=o,this._model.cells[s+3]=n}_resetBlinkingRowState(){this._rowHasBlinkingCells=new Array(this._terminal.rows).fill(!1),this._rowHasBlinkingCellsCount=0}_setRowBlinkState(t,r){this._rowHasBlinkingCells[t]!==r&&(this._rowHasBlinkingCells[t]=r,this._rowHasBlinkingCellsCount+=r?1:-1)}_updateTextBlinkState(){this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount>0)}_updateDimensions(){!this._charSizeService.width||!this._charSizeService.height||(this.dimensions.device.char.width=Math.floor(this._charSizeService.width*this._devicePixelRatio),this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*this._devicePixelRatio),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.top=this._optionsService.rawOptions.lineHeight===1?0:Math.round((this.dimensions.device.cell.height-this.dimensions.device.char.height)/2),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.char.left=Math.floor(this._optionsService.rawOptions.letterSpacing/2),this.dimensions.device.canvas.height=this._terminal.rows*this.dimensions.device.cell.height,this.dimensions.device.canvas.width=this._terminal.cols*this.dimensions.device.cell.width,this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/this._devicePixelRatio),this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/this._devicePixelRatio),this.dimensions.css.cell.height=this.dimensions.device.cell.height/this._devicePixelRatio,this.dimensions.css.cell.width=this.dimensions.device.cell.width/this._devicePixelRatio)}_setCanvasDevicePixelDimensions(t,r){this._canvas.width===t&&this._canvas.height===r||(this._canvas.width=t,this._canvas.height=r,this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1,sync:!0}))}_requestRedrawViewport(){this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1})}_requestRedrawCursor(){let t=this._terminal.buffer.active.cursorY;this._onRequestRedraw.fire({start:t,end:t})}},ge=class extends s0{constructor(t,r,s){super();this.content=0;this.combinedData="";this.fg=t.fg,this.bg=t.bg,this.combinedData=r,this._width=s}isCombined(){return 2097152}getWidth(){return this._width}getChars(){return this.combinedData}getCode(){return 2097151}setFromCharData(t){throw new Error("not implemented")}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}};function pt(i,e,t=0){return Math.max(Math.min(i,e),t)}var Ct=class extends W{constructor(t){if(ue&&He()<16){let r={antialias:!1,depth:!1,preserveDrawingBuffer:!0};if(!document.createElement("canvas").getContext("webgl2",r))throw new Error("WebGL2 is only supported on Safari 16 and above")}super();this._onChangeTextureAtlas=this._register(new U);this.onChangeTextureAtlas=this._onChangeTextureAtlas.event;this._onAddTextureAtlasCanvas=this._register(new U);this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event;this._onRemoveTextureAtlasCanvas=this._register(new U);this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event;this._onContextLoss=this._register(new U);this.onContextLoss=this._onContextLoss.event;this._customGlyphs=t?.customGlyphs??!0,this._preserveDrawingBuffer=t?.preserveDrawingBuffer}activate(t){let r=t._core;if(!t.element){this._register(r.onWillOpen(()=>this.activate(t)));return}this._terminal=t;let s=r.coreService,a=r.optionsService,o=r,n=o._renderService,L=o._characterJoinerService,T=o._charSizeService,h=o._coreBrowserService,d=o._decorationService,_=o._logService,p=o._themeService;this._renderer=this._register(new Le(t,L,T,h,s,d,_,a,p,this._customGlyphs,this._preserveDrawingBuffer)),this._register(c0.forward(this._renderer.onContextLoss,this._onContextLoss)),this._register(c0.forward(this._renderer.onChangeTextureAtlas,this._onChangeTextureAtlas)),this._register(c0.forward(this._renderer.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas)),this._register(c0.forward(this._renderer.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas)),n.setRenderer(this._renderer),this._register(M(()=>{if(this._terminal._core._store._isDisposed)return;let c=this._terminal._core._renderService;c.setRenderer(this._terminal._core._createRenderer()),c.handleResize(t.cols,t.rows)}))}get textureAtlas(){return this._renderer?.textureAtlas}clearTextureAtlas(){this._renderer?.clearTextureAtlas()}};export{Ct as WebglAddon}; - //# sourceMappingURL=addon-webgl.mjs.map +-//# sourceMappingURL=addon-webgl.mjs.map ++}`,l0=8,Me=l0*Float32Array.BYTES_PER_ELEMENT,e1=20*l0,ne=class{constructor(){;this.attributes=new Float32Array(e1),this.count=0}},p0=0,Lt=0,ht=0,dt=0,_t=0,ct=0,ut=0,ae=class extends W{constructor(t,r,s,a,o){super();this._terminal=t;this._gl=r;this._dimensions=s;this._themeService=a;this._logService=o;this._vertices=new ne;this._verticesCursor=new ne;let n=this._gl;this._program=g(ie(n,jt,Jt,this._logService)),this._register(M(()=>n.deleteProgram(this._program))),this._projectionLocation=g(n.getUniformLocation(this._program,"u_projection"));let L=this._vertexArrayObject=n.createVertexArray();this._register(M(()=>n.deleteVertexArray(L))),n.bindVertexArray(L);let T=new Float32Array([0,0,1,0,0,1,1,1]),h=n.createBuffer();this._register(M(()=>n.deleteBuffer(h))),n.bindBuffer(n.ARRAY_BUFFER,h),n.bufferData(n.ARRAY_BUFFER,T,n.STATIC_DRAW),n.enableVertexAttribArray(3),n.vertexAttribPointer(3,2,this._gl.FLOAT,false,0,0);let d=new Uint8Array([0,1,2,3]),_=n.createBuffer();this._register(M(()=>n.deleteBuffer(_))),n.bindBuffer(n.ELEMENT_ARRAY_BUFFER,_),n.bufferData(n.ELEMENT_ARRAY_BUFFER,d,n.STATIC_DRAW),this._attributesBuffer=g(n.createBuffer()),this._register(M(()=>n.deleteBuffer(this._attributesBuffer))),n.bindBuffer(n.ARRAY_BUFFER,this._attributesBuffer),n.enableVertexAttribArray(0),n.vertexAttribPointer(0,2,n.FLOAT,false,Me,0),n.vertexAttribDivisor(0,1),n.enableVertexAttribArray(1),n.vertexAttribPointer(1,2,n.FLOAT,false,Me,2*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(1,1),n.enableVertexAttribArray(2),n.vertexAttribPointer(2,4,n.FLOAT,false,Me,4*Float32Array.BYTES_PER_ELEMENT),n.vertexAttribDivisor(2,1),this._updateCachedColors(a.colors),this._register(this._themeService.onChangeColors(p=>{;this._updateCachedColors(p),this._updateViewportRectangle()}))}renderBackgrounds(){this._renderVertices(this._vertices)}renderCursor(){this._renderVertices(this._verticesCursor)}_renderVertices(t){let r=this._gl;r.useProgram(this._program),r.bindVertexArray(this._vertexArrayObject),r.uniformMatrix4fv(this._projectionLocation,false,te),r.bindBuffer(r.ARRAY_BUFFER,this._attributesBuffer),r.bufferData(r.ARRAY_BUFFER,t.attributes,r.DYNAMIC_DRAW),r.drawElementsInstanced(this._gl.TRIANGLE_STRIP,4,r.UNSIGNED_BYTE,0,t.count)}handleResize(){this._updateViewportRectangle()}setDimensions(t){this._dimensions=t}_updateCachedColors(t){;this._bgFloat=this._colorToFloat32Array(t.background),this._cursorFloat=this._colorToFloat32Array(t.cursor)}_updateViewportRectangle(){this._addRectangleFloat(this._vertices.attributes,0,0,0,this._terminal.cols*this._dimensions.device.cell.width,this._terminal.rows*this._dimensions.device.cell.height,this._bgFloat)}updateBackgrounds(t){let r=this._terminal,s=this._vertices,a=1,o,n,L,T,h,d,_,p,c,u,C;for(o=0;o>24&255)/255,_t=(p0>>16&255)/255,ct=(p0>>8&255)/255,ut=1,this._addRectangle(t.attributes,r,Lt,ht,(n-o)*this._dimensions.device.cell.width,this._dimensions.device.cell.height,dt,_t,ct,ut)}_addRectangle(t,r,s,a,o,n,L,T,h,d){;t[r]=s/this._dimensions.device.canvas.width,t[r+1]=a/this._dimensions.device.canvas.height,t[r+2]=o/this._dimensions.device.canvas.width,t[r+3]=n/this._dimensions.device.canvas.height,t[r+4]=L,t[r+5]=T,t[r+6]=h,t[r+7]=d}_addRectangleFloat(t,r,s,a,o,n,L){;t[r]=s/this._dimensions.device.canvas.width,t[r+1]=a/this._dimensions.device.canvas.height,t[r+2]=o/this._dimensions.device.canvas.width,t[r+3]=n/this._dimensions.device.canvas.height,t[r+4]=L[0],t[r+5]=L[1],t[r+6]=L[2],t[r+7]=L[3]}_colorToFloat32Array(t){return new Float32Array([(t.rgba>>24&255)/255,(t.rgba>>16&255)/255,(t.rgba>>8&255)/255,(t.rgba&255)/255])}};var oe=class extends W{constructor(t,r,s,a,o,n,L,T){super();this._container=r;this._alpha=o;this._coreBrowserService=n;this._optionsService=L;this._themeService=T;this._deviceCharWidth=0;this._deviceCharHeight=0;this._deviceCellWidth=0;this._deviceCellHeight=0;this._deviceCharLeft=0;this._deviceCharTop=0;this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add(`xterm-${s}-layer`),this._canvas.style.zIndex=a.toString(),this._initCanvas(),this._container.appendChild(this._canvas),this._register(this._themeService.onChangeColors(h=>{;this._refreshCharAtlas(t,h),this.reset(t)})),this._register(M(()=>{this._canvas.remove()}))}_initCanvas(){;this._ctx=g(this._canvas.getContext("2d",{alpha:this._alpha})),this._alpha||this._clearAll()}handleBlur(t){}handleFocus(t){}handleCursorMove(t){}handleGridChanged(t,r,s){}handleSelectionChanged(t,r,s,a=false){}_setTransparency(t,r){if(r===this._alpha)return;let s=this._canvas;this._alpha=r,this._canvas=this._canvas.cloneNode(),this._initCanvas(),this._container.replaceChild(this._canvas,s),this._refreshCharAtlas(t,this._themeService.colors),this.handleGridChanged(t,0,t.rows-1)}_refreshCharAtlas(t,r){;this._deviceCharWidth<=0&&this._deviceCharHeight<=0||(this._charAtlas=Y0(t,this._optionsService.rawOptions,r,this._deviceCellWidth,this._deviceCellHeight,this._deviceCharWidth,this._deviceCharHeight,this._coreBrowserService.dpr,2048),this._charAtlas.warmUp())}resize(t,r){;this._deviceCellWidth=r.device.cell.width,this._deviceCellHeight=r.device.cell.height,this._deviceCharWidth=r.device.char.width,this._deviceCharHeight=r.device.char.height,this._deviceCharLeft=r.device.char.left,this._deviceCharTop=r.device.char.top,this._canvas.width=r.device.canvas.width,this._canvas.height=r.device.canvas.height,this._canvas.style.width=`${r.css.canvas.width}px`,this._canvas.style.height=`${r.css.canvas.height}px`,this._alpha||this._clearAll(),this._refreshCharAtlas(t,this._themeService.colors)}_fillBottomLineAtCells(t,r,s=1){this._ctx.fillRect(t*this._deviceCellWidth,(r+1)*this._deviceCellHeight-this._coreBrowserService.dpr-1,s*this._deviceCellWidth,this._coreBrowserService.dpr)}_clearAll(){this._alpha?this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(0,0,this._canvas.width,this._canvas.height))}_clearCells(t,r,s,a){this._alpha?this._ctx.clearRect(t*this._deviceCellWidth,r*this._deviceCellHeight,s*this._deviceCellWidth,a*this._deviceCellHeight):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(t*this._deviceCellWidth,r*this._deviceCellHeight,s*this._deviceCellWidth,a*this._deviceCellHeight))}_fillCharTrueColor(t,r,s,a){;this._ctx.font=this._getFont(t,false,false),this._ctx.textBaseline=K0,this._clipCell(s,a,r.getWidth()),this._ctx.fillText(r.getChars(),s*this._deviceCellWidth+this._deviceCharLeft,a*this._deviceCellHeight+this._deviceCharTop+this._deviceCharHeight)}_clipCell(t,r,s){;this._ctx.beginPath(),this._ctx.rect(t*this._deviceCellWidth,r*this._deviceCellHeight,s*this._deviceCellWidth,this._deviceCellHeight),this._ctx.clip()}_getFont(t,r,s){let a=r?t.options.fontWeightBold:t.options.fontWeight;return`${s?"italic":""} ${a} ${t.options.fontSize*this._coreBrowserService.dpr}px ${t.options.fontFamily}`}};var Te=class extends oe{constructor(e,t,r,s,a,o,n){;super(r,e,"link",t,true,a,o,n),this._register(s.onShowLinkUnderline(L=>this._handleShowLinkUnderline(L))),this._register(s.onHideLinkUnderline(L=>this._handleHideLinkUnderline(L)))}resize(e,t){;super.resize(e,t),this._state=void 0}reset(e){this._clearCurrentLink()}_clearCurrentLink(){if(this._state){this._clearCells(this._state.x1,this._state.y1,this._state.cols-this._state.x1,1);let e=this._state.y2-this._state.y1-1;e>0&&this._clearCells(0,this._state.y1+1,this._state.cols,e),this._clearCells(0,this._state.y2,this._state.x2,1),this._state=void 0}}_handleShowLinkUnderline(e){if(e.fg===257?this._ctx.fillStyle=this._themeService.colors.background.css:e.fg!==void 0&&st(e.fg)?this._ctx.fillStyle=this._themeService.colors.ansi[e.fg].css:this._ctx.fillStyle=this._themeService.colors.foreground.css,e.y1===e.y2)this._fillBottomLineAtCells(e.x1,e.y1,e.x2-e.x1);else{this._fillBottomLineAtCells(e.x1,e.y1,e.cols-e.x1);for(let t=e.y1+1;tthis._handleColorChange())),this._cellColorResolver=new G0(this._terminal,this._optionsService,this._model.selection,this._decorationService,this._coreBrowserService,this._themeService),this._core=this._terminal._core,this._renderLayers=[new Te(this._core.screenElement,2,this._terminal,this._core.linkifier,this._coreBrowserService,T,this._themeService)],this.dimensions=Ue(),this._devicePixelRatio=this._coreBrowserService.dpr,this._updateDimensions(),this._updateCursorBlink(),this._register(T.onOptionChange(()=>this._handleOptionsChanged())),this._textBlinkStateManager=this._register(new J0(()=>this._requestRedrawViewport(),this._coreBrowserService,this._optionsService)),this._resetBlinkingRowState(),this._deviceMaxTextureSize=this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE),this._register(le(this._canvas,"webglcontextlost",c=>{;this._logService.debug("webglcontextlost event received"),c.preventDefault(),this._contextRestorationTimeout=setTimeout(()=>{;this._contextRestorationTimeout=void 0,this._logService.warn("webgl context not restored; firing onContextLoss"),this._onContextLoss.fire(c)},3e3)})),this._register(le(this._canvas,"webglcontextrestored",c=>{;this._logService.warn("webglcontextrestored event received"),clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0,be(this._terminal),this._initializeWebGLState(),this._requestRedrawViewport()})),this._observerDisposable.value=ve(this._canvas,this._coreBrowserService.window,(c,u)=>this._setCanvasDevicePixelDimensions(c,u)),this._register(this._coreBrowserService.onWindowChange(c=>{this._observerDisposable.value=ve(this._canvas,c,(u,C)=>this._setCanvasDevicePixelDimensions(u,C))})),this._register(le(this._coreBrowserService.mainDocument,"mousedown",()=>this._cursorBlinkStateManager.value?.restartBlinkAnimation())),this._core.screenElement.appendChild(this._canvas),[this._rectangleRenderer.value,this._glyphRenderer.value]=this._initializeWebGLState(),this._isAttached=this._core.screenElement.isConnected,this._register(M(()=>{;clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0;for(let c of this._renderLayers)c.dispose();this._canvas.parentElement?.removeChild(this._canvas),be(this._terminal)}))}get textureAtlas(){return this._charAtlas?.pages[0].canvas}_handleColorChange(){;this._refreshCharAtlas(),this._clearModel(true)}handleDevicePixelRatioChange(){this._devicePixelRatio!==this._coreBrowserService.dpr&&(this._devicePixelRatio=this._coreBrowserService.dpr,this.handleResize(this._terminal.cols,this._terminal.rows))}handleResize(t,r){;this._updateDimensions(),this._model.resize(this._terminal.cols,this._terminal.rows),this._resetBlinkingRowState();for(let s of this._renderLayers)s.resize(this._terminal,this.dimensions);this._canvas.width=this.dimensions.device.canvas.width,this._canvas.height=this.dimensions.device.canvas.height,this._canvas.style.width=`${this.dimensions.css.canvas.width}px`,this._canvas.style.height=`${this.dimensions.css.canvas.height}px`,this._core.screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._core.screenElement.style.height=`${this.dimensions.css.canvas.height}px`,this._rectangleRenderer.value?.setDimensions(this.dimensions),this._rectangleRenderer.value?.handleResize(),this._glyphRenderer.value?.setDimensions(this.dimensions),this._glyphRenderer.value?.handleResize(),this._refreshCharAtlas(),this._clearModel(false),this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1,sync:true})}handleCharSizeChanged(){this.handleResize(this._terminal.cols,this._terminal.rows)}handleBlur(){for(let t of this._renderLayers)t.handleBlur(this._terminal);this._cursorBlinkStateManager.value?.pause(),this._requestRedrawViewport()}handleFocus(){for(let t of this._renderLayers)t.handleFocus(this._terminal);this._cursorBlinkStateManager.value?.resume(),this._requestRedrawViewport()}handleViewportVisibilityChange(t){this._textBlinkStateManager.setViewportVisible(t)}handleSelectionChanged(t,r,s){for(let a of this._renderLayers)a.handleSelectionChanged(this._terminal,t,r,s);this._model.selection.update(this._core,t,r,s),this._requestRedrawViewport()}handleCursorMove(){for(let t of this._renderLayers)t.handleCursorMove(this._terminal);this._cursorBlinkStateManager.value?.restartBlinkAnimation()}_handleOptionsChanged(){;this._updateDimensions(),this._refreshCharAtlas(),this._updateCursorBlink()}_initializeWebGLState(){return this._rectangleRenderer.value=new ae(this._terminal,this._gl,this.dimensions,this._themeService,this._logService),this._glyphRenderer.value=new re(this._terminal,this._gl,this.dimensions,this._optionsService,this._logService),this.handleCharSizeChanged(),[this._rectangleRenderer.value,this._glyphRenderer.value]}_refreshCharAtlas(){if(this.dimensions.device.char.width<=0&&this.dimensions.device.char.height<=0){this._isAttached=false;return}let t=Y0(this._terminal,this._optionsService.rawOptions,this._themeService.colors,this.dimensions.device.cell.width,this.dimensions.device.cell.height,this.dimensions.device.char.width,this.dimensions.device.char.height,this._coreBrowserService.dpr,this._deviceMaxTextureSize,this._customGlyphs);this._charAtlas!==t&&(this._onChangeTextureAtlas.fire(t.pages[0].canvas),this._charAtlasDisposable.value=De(c0.forward(t.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas),c0.forward(t.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas))),this._charAtlas=t,this._charAtlas.warmUp(),this._glyphRenderer.value?.setAtlas(this._charAtlas)}_clearModel(t){;this._model.clear(),t&&this._glyphRenderer.value?.clear()}clearTextureAtlas(){;this._charAtlas?.clearTexture(),this._clearModel(true),this._requestRedrawViewport()}clear(){this._clearModel(true);for(let t of this._renderLayers)t.reset(this._terminal);this._resetBlinkingRowState(),this._textBlinkStateManager.setNeedsBlinkInViewport(false),this._cursorBlinkStateManager.value?.restartBlinkAnimation(),this._updateCursorBlink()}renderRows(t,r){if(!this._isAttached)if(this._core.screenElement?.isConnected&&this._charSizeService.width&&this._charSizeService.height)this._updateDimensions(),this._refreshCharAtlas(),this._isAttached=true;else return;for(let o of this._renderLayers)o.handleGridChanged(this._terminal,t,r);if(!this._glyphRenderer.value||!this._rectangleRenderer.value)return;this._glyphRenderer.value.beginFrame()?(this._clearModel(true),this._updateModel(0,this._terminal.rows-1)):this._updateModel(t,r);let s=false,a=0;for(;this._charAtlas&&a++<32&&this._glyphRenderer.value.beginFrame();)s=true,this._clearModel(true),this._updateModel(0,this._terminal.rows-1);s&&this._glyphRenderer.value.invalidateAtlasTextures(),this._rectangleRenderer.value.renderBackgrounds(),this._glyphRenderer.value.render(this._model),(!this._cursorBlinkStateManager.value||this._cursorBlinkStateManager.value.isCursorVisible)&&this._rectangleRenderer.value.renderCursor()}_updateCursorBlink(){;this._coreService.decPrivateModes.cursorBlink??this._terminal.options.cursorBlink?this._cursorBlinkStateManager.value=new q0(()=>{this._requestRedrawCursor()},this._coreBrowserService):this._cursorBlinkStateManager.clear(),this._requestRedrawCursor()}_updateModel(t,r){let s=this._core,a=this._workCell,o,n,L,T,h,d,_,p,c,u,C,Q,m,R,F,E,y;t=pt(t,s.rows-1,0),r=pt(r,s.rows-1,0);let Y=this._coreService.decPrivateModes.cursorStyle??s.options.cursorStyle??"block",X=this._terminal.buffer.active.baseY+this._terminal.buffer.active.cursorY,A0=X-s.buffer.ydisp,t0=Math.min(this._terminal.buffer.active.cursorX,s.cols-1),L0=-1,A=this._coreService.isCursorInitialized&&!this._coreService.isCursorHidden&&(!this._cursorBlinkStateManager.value||this._cursorBlinkStateManager.value.isCursorVisible);this._model.cursor=void 0;let n0=false;for(n=t;n<=r;n++){L=n+s.buffer.ydisp;let a0=s.buffer.lines.get(L);if(!a0){for(this._model.lineLengths[n]=0,E=0;E=_,c=E,h.length>0&&E===h[0][0]&&p){u=h.shift();let q=this._model.selection.isCellSelected(this._terminal,u[0],L);for(F=u[0]+1;F=u[1],p?(d=true,a=new ge(a,T.translateToString(true,u[0],u[1]),u[1]-u[0]),c=u[1]-1):_=u[1]}if(Q=a.getChars(),m=a.getCode(),F=(n*s.cols+E)*4,!$&&a.isBlink()&&($=true),this._cellColorResolver.resolve(a,E,L,this.dimensions.device.cell.width,this.dimensions.device.cell.height),A&&L===X&&(E===t0&&(this._model.cursor={x:t0,y:A0,width:a.getWidth(),style:this._coreBrowserService.isFocused?Y:s.options.cursorInactiveStyle,cursorWidth:s.options.cursorWidth,dpr:this._devicePixelRatio},L0=t0+a.getWidth()-1),E>=t0&&E<=L0&&(this._coreBrowserService.isFocused&&Y==="block"||this._coreBrowserService.isFocused===false&&s.options.cursorInactiveStyle==="block")&&(this._cellColorResolver.result.fg=50331648|this._themeService.colors.cursorAccent.rgba>>8&16777215,this._cellColorResolver.result.bg=50331648|this._themeService.colors.cursor.rgba>>8&16777215)),this._textBlinkStateManager.isEnabled&&!this._textBlinkStateManager.isBlinkOn&&a.isBlink()&&(this._cellColorResolver.result.fg|=1073741824),m!==0&&(this._model.lineLengths[n]=E+1),!(this._model.cells[F]===m&&this._model.cells[F+1]===this._cellColorResolver.result.bg&&this._model.cells[F+2]===this._cellColorResolver.result.fg&&this._model.cells[F+3]===this._cellColorResolver.result.ext)&&(n0=true,Q.length>1&&(m|=lt),this._model.cells[F]=m,this._model.cells[F+1]=this._cellColorResolver.result.bg,this._model.cells[F+2]=this._cellColorResolver.result.fg,this._model.cells[F+3]=this._cellColorResolver.result.ext,R=a.getWidth(),this._glyphRenderer.value.updateCell(E,n,m,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext,Q,R,o),d)){for(a=this._workCell,E++;E<=c;E++)y=(n*s.cols+E)*4,this._nullModelCell(E,n,y,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext);E--}}this._setRowBlinkState(n,$)};n0&&this._rectangleRenderer.value.updateBackgrounds(this._model),this._rectangleRenderer.value.updateCursor(this._model),this._updateTextBlinkState()}_nullModelCell(t,r,s,a,o,n){;this._glyphRenderer.value.updateCell(t,r,0,a,o,n,"",0,0),this._model.cells[s]=0,this._model.cells[s+1]=a,this._model.cells[s+2]=o,this._model.cells[s+3]=n}_resetBlinkingRowState(){;this._rowHasBlinkingCells=new Array(this._terminal.rows).fill(false),this._rowHasBlinkingCellsCount=0}_setRowBlinkState(t,r){this._rowHasBlinkingCells[t]!==r&&(this._rowHasBlinkingCells[t]=r,this._rowHasBlinkingCellsCount+=r?1:-1)}_updateTextBlinkState(){this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount>0)}_updateDimensions(){!this._charSizeService.width||!this._charSizeService.height||(this.dimensions.device.char.width=Math.floor(this._charSizeService.width*this._devicePixelRatio),this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*this._devicePixelRatio),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.top=this._optionsService.rawOptions.lineHeight===1?0:Math.round((this.dimensions.device.cell.height-this.dimensions.device.char.height)/2),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.char.left=Math.floor(this._optionsService.rawOptions.letterSpacing/2),this.dimensions.device.canvas.height=this._terminal.rows*this.dimensions.device.cell.height,this.dimensions.device.canvas.width=this._terminal.cols*this.dimensions.device.cell.width,this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/this._devicePixelRatio),this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/this._devicePixelRatio),this.dimensions.css.cell.height=this.dimensions.device.cell.height/this._devicePixelRatio,this.dimensions.css.cell.width=this.dimensions.device.cell.width/this._devicePixelRatio)}_setCanvasDevicePixelDimensions(t,r){;this._canvas.width===t&&this._canvas.height===r||(this._canvas.width=t,this._canvas.height=r,this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1,sync:true}))}_requestRedrawViewport(){this._onRequestRedraw.fire({start:0,end:this._terminal.rows-1})}_requestRedrawCursor(){let t=this._terminal.buffer.active.cursorY;this._onRequestRedraw.fire({start:t,end:t})}},ge=class extends s0{constructor(t,r,s){super();this.content=0;this.combinedData="";this.fg=t.fg,this.bg=t.bg,this.combinedData=r,this._width=s}isCombined(){return 2097152}getWidth(){return this._width}getChars(){return this.combinedData}getCode(){return 2097151}setFromCharData(t){throw new Error("not implemented")}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}};function pt(i5,e,t=0){return Math.max(Math.min(i5,e),t)}var Ct=class extends W{constructor(t){if(ue&&He()<16){let r={antialias:false,depth:false,preserveDrawingBuffer:true};if(!document.createElement("canvas").getContext("webgl2",r))throw new Error("WebGL2 is only supported on Safari 16 and above")}super();this._onChangeTextureAtlas=this._register(new U);this.onChangeTextureAtlas=this._onChangeTextureAtlas.event;this._onAddTextureAtlasCanvas=this._register(new U);this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event;this._onRemoveTextureAtlasCanvas=this._register(new U);this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event;this._onContextLoss=this._register(new U);this.onContextLoss=this._onContextLoss.event;this._customGlyphs=t?.customGlyphs??true,this._preserveDrawingBuffer=t?.preserveDrawingBuffer}activate(t){let r=t._core;if(!t.element){this._register(r.onWillOpen(()=>this.activate(t)));return}this._terminal=t;let s=r.coreService,a=r.optionsService,o=r,n=o._renderService,L=o._characterJoinerService,T=o._charSizeService,h=o._coreBrowserService,d=o._decorationService,_=o._logService,p=o._themeService;this._renderer=this._register(new Le(t,L,T,h,s,d,_,a,p,this._customGlyphs,this._preserveDrawingBuffer)),this._register(c0.forward(this._renderer.onContextLoss,this._onContextLoss)),this._register(c0.forward(this._renderer.onChangeTextureAtlas,this._onChangeTextureAtlas)),this._register(c0.forward(this._renderer.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas)),this._register(c0.forward(this._renderer.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas)),n.setRenderer(this._renderer),this._register(M(()=>{if(this._terminal._core._store._isDisposed)return;let c=this._terminal._core._renderService;c.setRenderer(this._terminal._core._createRenderer()),c.handleResize(t.cols,t.rows)}))}get textureAtlas(){return this._renderer?.textureAtlas}clearTextureAtlas(){this._renderer?.clearTextureAtlas()}};export{Ct as WebglAddon}; diff --git a/src/GlyphRenderer.ts b/src/GlyphRenderer.ts -index be1990c2a61a0ff5a37622ef7b6d8772e7a1056c..f47d52d295198701d5b65811a712bffe44943387 100644 +index be1990c2a61a0ff5a37622ef7b6d8772e7a1056c..c19e24f7963cfcfbca1cea4bf7f808bf534a19e7 100644 --- a/src/GlyphRenderer.ts +++ b/src/GlyphRenderer.ts @@ -74,7 +74,11 @@ out vec4 outColor; @@ -97,14 +131,16 @@ index be1990c2a61a0ff5a37622ef7b6d8772e7a1056c..f47d52d295198701d5b65811a712bffe if (this._atlas.pages[i].version !== this._atlasTextures[i].version) { this._bindAtlasPageTexture(gl, this._atlas, i); } -@@ -374,7 +395,10 @@ export class GlyphRenderer extends Disposable { +@@ -374,7 +395,12 @@ export class GlyphRenderer extends Disposable { } public setAtlas(atlas: ITextureAtlas): void { - this._atlas = atlas; + if (this._atlas !== atlas) { + this._atlas = atlas; -+ this._lastSeenClearModelGeneration = atlas.clearModelGeneration; ++ // Cached vertices still reference the previous atlas, even when the new ++ // shared atlas happens to be at the same clear generation. ++ this._lastSeenClearModelGeneration = -1; + } this.invalidateAtlasTextures(); } @@ -241,7 +277,7 @@ index 1c3e08a1cb14208e3d8b6dd726c79285200b9df4..04703e830ecc907f91c67c1524b32672 } activePage = this._overflowSizePage; diff --git a/src/Types.ts b/src/Types.ts -index 294582e3721d4614f6b14198229e67dfa820baec..ce3b43fc3826f55a161cfb398f6fd91f5907d056 100644 +index 294582e3721d4614f6b14198229e67dfa820baec..bc708d9b6d2cbeb206bd0b13325162c0765bf8fb 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -59,6 +59,9 @@ export interface ICharAtlasConfig { diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 448b4beac63..7caf6d2ee24 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -4163,6 +4163,74 @@ ], "demotionRule": "Disable the probe if it exceeds its per-reveal budget or produces false-positive anomaly noise." }, + { + "id": "terminal-render.atlas-identity-invalidation", + "title": "A WebGL atlas identity change rebuilds cached glyph vertices", + "maturity": "experimental", + "protection": "partial", + "owner": "terminal-rendering", + "layer": "electron-visual-golden", + "surfaces": ["WebGL rendering", "shared glyph atlas", "render model"], + "platforms": ["macos", "linux", "windows"], + "providers": ["local", "daemon", "ssh", "remote-runtime"], + "coveredPlatforms": ["macos"], + "coveredProviders": [], + "coverageNotes": "A deterministic macOS Electron test uses two real WebGL terminals with distinct atlas populations, attaches one renderer to the other's atlas, and compares its pixels before and after rendering. Provider paths are unaffected because atlas ownership is renderer-local; Linux and Windows GPU evidence remains pending.", + "motivatingLinks": ["https://github.com/stablyai/orca/pull/8899"], + "invariant": "Whenever GlyphRenderer attaches a different shared atlas, every cached glyph vertex is rebuilt before the next draw even if the old and new atlases have the same clear-model generation.", + "oracle": "Render terminal A and capture its non-empty canvas, populate terminal B with a distinct atlas, attach A's glyph renderer to B's atlas without changing A's buffer, render A again, and require byte-equivalent visible pixels. Also require the atlas identities to differ so the test cannot pass without exercising the transition.", + "commands": [ + "pnpm run ensure:electron-runtime && pnpm exec playwright test tests/e2e/terminal-webgl-atlas-budget.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1" + ], + "testFiles": ["tests/e2e/terminal-webgl-atlas-budget.spec.ts"], + "assertionRefs": [ + { + "file": "tests/e2e/terminal-webgl-atlas-budget.spec.ts", + "assertions": [ + "the replacement scenario uses distinct atlas identities and renders a non-empty baseline", + "attaching the replacement atlas leaves zero pixels different from the same terminal buffer's baseline" + ] + } + ], + "evidenceRuns": [ + { + "date": "2026-07-17", + "runner": "local", + "platform": "macos", + "command": "pnpm run ensure:electron-runtime && pnpm exec playwright test tests/e2e/terminal-webgl-atlas-budget.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1", + "result": "passed", + "durationSeconds": 44, + "summary": "Both real-WebGL scenarios passed. The atlas-replacement case changed 327,165 pixels with the old generation adoption and zero pixels after identity invalidation." + } + ], + "runtimeBudget": { + "p95Seconds": 180, + "scope": "fresh E2E build plus two real-WebGL atlas tests" + }, + "flakeHistory": { + "status": "unknown", + "evidence": "The focused local macOS run and the PR's macOS golden experiment job passed. The Linux golden job skipped both WebGL atlas tests because WebGL was unavailable; soak history is not yet available." + }, + "redGreenEvidence": { + "status": "complete", + "evidence": "The old setAtlas behavior deterministically changed 327,165 pixels after replacement; setting the last-seen generation to an invalid value on identity change produced zero changed pixels." + }, + "performanceBudget": { + "required": true, + "evidence": "The added work is one existing full-model rebuild only when atlas object identity changes; ordinary frames and repeated setAtlas calls with the same object take the existing path. The field sentinel remains off by default, samples only a modifier-clicked pane in a bounded ten-second burst, reuses and releases one readback canvas, and caps renderer and disk evidence." + }, + "promotionCriteria": [ + "Accumulate stable macOS and Linux GPU runs across the release golden suite.", + "Add Windows GPU evidence or retain the platform gap explicitly.", + "Remove the flag-gated field sentinel after the confirmation window while keeping this pixel regression." + ], + "knownGaps": [ + "The historical field capture predates renderer-internal atlas identity diagnostics, so it cannot prove this exact transition retrospectively.", + "The deterministic test drives the renderer transition directly rather than reproducing the original Cmd/Ctrl-click lifecycle trigger.", + "Windows GPU evidence is not yet recorded." + ], + "demotionRule": "Keep experimental or demote to protection none if GPU skips hide the transition, the pixel oracle flakes across stable runners, or the test no longer fails with generation adoption restored." + }, { "id": "terminal-render.pixel-refresh-repair", "title": "Forced repaint does not change revealed pane pixels", diff --git a/package.json b/package.json index 147a7c89226..799422d20c5 100644 --- a/package.json +++ b/package.json @@ -193,6 +193,7 @@ "oxlint-plugin-react-doctor": "0.2.10", "oxlint-tsgolint": "0.23.0", "pdfjs-dist": "^5.7.284", + "pngjs": "^7.0.0", "radix-ui": "^1.6.2", "react": "^19.2.7", "react-colorful": "^5.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8d00d79dc2..7ee48a5b7d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,7 +15,7 @@ patchedDependencies: hash: 81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9 path: config/patches/@xterm__addon-serialize@0.15.0-beta.287.patch '@xterm/addon-webgl@0.20.0-beta.286': - hash: cf737f93f193124ffc7f302bbbf81c2290b29b562335f24263209274caaf8e64 + hash: 6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258 path: config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch node-pty@1.1.0: hash: 407ae07e1e0e2ff2e8b58696449c54c31e51d87535bc6aa4a7a7b0b561407282 @@ -217,7 +217,7 @@ importers: version: 0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287) '@xterm/addon-webgl': specifier: 0.20.0-beta.286 - version: 0.20.0-beta.286(patch_hash=cf737f93f193124ffc7f302bbbf81c2290b29b562335f24263209274caaf8e64)(@xterm/xterm@6.1.0-beta.287) + version: 0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287) '@xterm/xterm': specifier: 6.1.0-beta.287 version: 6.1.0-beta.287 @@ -290,6 +290,9 @@ importers: pdfjs-dist: specifier: ^5.7.284 version: 5.7.284 + pngjs: + specifier: ^7.0.0 + version: 7.0.0 radix-ui: specifier: ^1.6.2 version: 1.6.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -9603,7 +9606,7 @@ snapshots: dependencies: '@xterm/xterm': 6.1.0-beta.287 - '@xterm/addon-webgl@0.20.0-beta.286(patch_hash=cf737f93f193124ffc7f302bbbf81c2290b29b562335f24263209274caaf8e64)(@xterm/xterm@6.1.0-beta.287)': + '@xterm/addon-webgl@0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287)': dependencies: '@xterm/xterm': 6.1.0-beta.287 diff --git a/src/main/ipc/register-core-handlers.test.ts b/src/main/ipc/register-core-handlers.test.ts index e3456d3dad4..c7ff8080a28 100644 --- a/src/main/ipc/register-core-handlers.test.ts +++ b/src/main/ipc/register-core-handlers.test.ts @@ -21,6 +21,7 @@ const { registerKeybindingHandlersMock, registerTelemetryHandlersMock, registerDiagnosticsHandlersMock, + registerTerminalRenderDesyncEvidenceHandlerMock, registerShellHandlersMock, registerPetHandlersMock, registerSessionHandlersMock, @@ -82,6 +83,7 @@ const { registerKeybindingHandlersMock: vi.fn(), registerTelemetryHandlersMock: vi.fn(), registerDiagnosticsHandlersMock: vi.fn(), + registerTerminalRenderDesyncEvidenceHandlerMock: vi.fn(), registerShellHandlersMock: vi.fn(), registerPetHandlersMock: vi.fn(), registerSessionHandlersMock: vi.fn(), @@ -334,6 +336,10 @@ vi.mock('./app', () => ({ registerAppHandlers: registerAppHandlersMock })) +vi.mock('./terminal-render-desync-evidence', () => ({ + registerTerminalRenderDesyncEvidenceHandler: registerTerminalRenderDesyncEvidenceHandlerMock +})) + vi.mock('./linear', () => ({ registerLinearHandlers: registerLinearHandlersMock })) @@ -380,6 +386,7 @@ describe('registerCoreHandlers', () => { registerKeybindingHandlersMock.mockReset() registerTelemetryHandlersMock.mockReset() registerDiagnosticsHandlersMock.mockReset() + registerTerminalRenderDesyncEvidenceHandlerMock.mockReset() registerShellHandlersMock.mockReset() registerPetHandlersMock.mockReset() registerSessionHandlersMock.mockReset() diff --git a/src/main/ipc/register-core-handlers.ts b/src/main/ipc/register-core-handlers.ts index cd2dab472b7..1542c49c440 100644 --- a/src/main/ipc/register-core-handlers.ts +++ b/src/main/ipc/register-core-handlers.ts @@ -50,6 +50,7 @@ import { registerUIHandlers, setTrustedUIRendererWebContentsId } from './ui' import { registerEmulatorFrameStreamHandlers } from './emulator-frame-stream' import { registerEmulatorVideoStreamHandlers } from './emulator-video-stream' import { registerSpeechHandlers } from './speech' +import { registerTerminalRenderDesyncEvidenceHandler } from './terminal-render-desync-evidence' import { registerOrcaProfileHandlers } from './orca-profiles' import { registerCodexAccountHandlers } from './codex-accounts' import { registerAgentHookHandlers } from './agent-hooks' @@ -152,6 +153,7 @@ export function registerCoreHandlers( // `src/main/observability/`, never from `src/main/telemetry/`. Order is // not load-bearing; both register independent ipcMain channels. registerDiagnosticsHandlers() + registerTerminalRenderDesyncEvidenceHandler() registerComputerUsePermissionHandlers() registerSettingsHandlers(store, agentAwakeService) registerSkillsHandlers(store) diff --git a/src/main/ipc/terminal-render-desync-evidence.test.ts b/src/main/ipc/terminal-render-desync-evidence.test.ts new file mode 100644 index 00000000000..9bc53984d40 --- /dev/null +++ b/src/main/ipc/terminal-render-desync-evidence.test.ts @@ -0,0 +1,121 @@ +import { mkdtemp, readFile, readdir, rm, stat } from 'node:fs/promises' +import os from 'node:os' +import path from 'node:path' +import { afterEach, describe, expect, it, vi } from 'vitest' + +const { ipcHandle, isTrustedUIRenderer } = vi.hoisted(() => ({ + ipcHandle: vi.fn(), + isTrustedUIRenderer: vi.fn(() => true) +})) + +vi.mock('electron', () => ({ + app: { getPath: vi.fn(() => '/test/user-data') }, + ipcMain: { handle: ipcHandle } +})) +vi.mock('./ui', () => ({ isTrustedUIRenderer })) + +import { + registerTerminalRenderDesyncEvidenceHandler, + writeTerminalRenderDesyncEvidence +} from './terminal-render-desync-evidence' + +const tempDirectories: string[] = [] + +afterEach(async () => { + await Promise.all(tempDirectories.splice(0).map((dir) => rm(dir, { recursive: true }))) +}) + +describe('writeTerminalRenderDesyncEvidence', () => { + it('rejects writes from renderers outside the trusted UI boundary', async () => { + isTrustedUIRenderer.mockReturnValue(false) + registerTerminalRenderDesyncEvidenceHandler() + const handler = ipcHandle.mock.calls.at(-1)?.[1] + + expect(() => + handler( + { sender: { id: 99 } }, + { captureId: 'capture-1', phase: 'corrupt', pngDataUrl: 'data:image/png;base64,eA==' } + ) + ).toThrow('Unauthorized render-desync evidence sender') + }) + + it('writes private PNG and metadata files under app-owned user data', async () => { + const userData = await mkdtemp(path.join(os.tmpdir(), 'orca-render-desync-')) + tempDirectories.push(userData) + const result = await writeTerminalRenderDesyncEvidence(userData, { + captureId: 'capture-1', + phase: 'corrupt', + pngDataUrl: `data:image/png;base64,${Buffer.from('png-bytes').toString('base64')}`, + metadata: { bufferText: 'private terminal text' } + }) + + expect(await readFile(result.pngPath, 'utf8')).toBe('png-bytes') + expect(JSON.parse(await readFile(result.metadataPath!, 'utf8'))).toEqual({ + bufferText: 'private terminal text' + }) + if (process.platform !== 'win32') { + expect((await stat(result.directory)).mode & 0o777).toBe(0o700) + expect((await stat(result.pngPath)).mode & 0o777).toBe(0o600) + } + }) + + it('rejects path traversal and non-PNG payloads', async () => { + const userData = await mkdtemp(path.join(os.tmpdir(), 'orca-render-desync-')) + tempDirectories.push(userData) + await expect( + writeTerminalRenderDesyncEvidence(userData, { + captureId: '../outside', + phase: 'corrupt', + pngDataUrl: 'data:image/png;base64,eA==' + }) + ).rejects.toThrow('Invalid render-desync capture id') + await expect( + writeTerminalRenderDesyncEvidence(userData, { + captureId: 'capture-2', + phase: 'corrupt', + pngDataUrl: 'data:text/plain;base64,eA==' + }) + ).rejects.toThrow('Invalid render-desync PNG payload') + }) + + it('rejects a phase outside the IPC filename allowlist', async () => { + const userData = await mkdtemp(path.join(os.tmpdir(), 'orca-render-desync-')) + tempDirectories.push(userData) + + await expect( + writeTerminalRenderDesyncEvidence(userData, { + captureId: 'capture-1', + phase: '../escaped' as 'corrupt', + pngDataUrl: 'data:image/png;base64,eA==' + }) + ).rejects.toThrow('Invalid render-desync evidence phase') + }) + + it('retains only the newest four capture directories', async () => { + const userData = await mkdtemp(path.join(os.tmpdir(), 'orca-render-desync-')) + tempDirectories.push(userData) + for (let capture = 1; capture <= 6; capture++) { + await writeTerminalRenderDesyncEvidence(userData, { + captureId: `capture-${capture}`, + phase: 'corrupt', + pngDataUrl: 'data:image/png;base64,eA==' + }) + } + + const captures = await readdir(path.join(userData, 'terminal-render-desync-evidence')) + expect(captures.sort()).toEqual(['capture-3', 'capture-4', 'capture-5', 'capture-6']) + }) + + it('rejects metadata that exceeds its storage budget', async () => { + const userData = await mkdtemp(path.join(os.tmpdir(), 'orca-render-desync-')) + tempDirectories.push(userData) + await expect( + writeTerminalRenderDesyncEvidence(userData, { + captureId: 'capture-large-metadata', + phase: 'corrupt', + pngDataUrl: 'data:image/png;base64,eA==', + metadata: { bufferText: 'x'.repeat(1024 * 1024) } + }) + ).rejects.toThrow('metadata exceeds the storage budget') + }) +}) diff --git a/src/main/ipc/terminal-render-desync-evidence.ts b/src/main/ipc/terminal-render-desync-evidence.ts new file mode 100644 index 00000000000..9ea82246539 --- /dev/null +++ b/src/main/ipc/terminal-render-desync-evidence.ts @@ -0,0 +1,114 @@ +import { mkdir, readdir, rm, stat, writeFile } from 'node:fs/promises' +import path from 'node:path' +import { app, ipcMain } from 'electron' +import type { + WriteTerminalRenderDesyncEvidenceArgs, + WriteTerminalRenderDesyncEvidenceResult +} from '../../shared/terminal-render-desync-evidence' +import { isTrustedUIRenderer } from './ui' + +const EVIDENCE_DIRECTORY = 'terminal-render-desync-evidence' +const MAX_PNG_DATA_URL_BYTES = 40 * 1024 * 1024 +const MAX_METADATA_BYTES = 1024 * 1024 +const MAX_CAPTURE_DIRECTORIES = 4 +const MAX_EVIDENCE_BYTES = 96 * 1024 * 1024 +const CAPTURE_ID_PATTERN = /^[a-zA-Z0-9_-]{1,120}$/ +const PNG_DATA_URL_PREFIX = 'data:image/png;base64,' +let evidenceWriteQueue = Promise.resolve() + +export function registerTerminalRenderDesyncEvidenceHandler(): void { + ipcMain.handle( + 'terminal:writeRenderDesyncEvidence', + (event, args: WriteTerminalRenderDesyncEvidenceArgs) => { + if (!isTrustedUIRenderer(event.sender)) { + throw new Error('Unauthorized render-desync evidence sender') + } + const write = evidenceWriteQueue.then(() => + writeTerminalRenderDesyncEvidence(app.getPath('userData'), args) + ) + // Why: serialize retention with writes, but do not let one failed capture + // poison later diagnostic attempts for the lifetime of the main process. + evidenceWriteQueue = write.then( + () => undefined, + () => undefined + ) + return write + } + ) +} + +export async function writeTerminalRenderDesyncEvidence( + userDataPath: string, + args: WriteTerminalRenderDesyncEvidenceArgs +): Promise { + if (!CAPTURE_ID_PATTERN.test(args.captureId)) { + throw new Error('Invalid render-desync capture id') + } + if (args.phase !== 'corrupt' && args.phase !== 'healed') { + throw new Error('Invalid render-desync evidence phase') + } + if ( + !args.pngDataUrl.startsWith(PNG_DATA_URL_PREFIX) || + args.pngDataUrl.length > MAX_PNG_DATA_URL_BYTES + ) { + throw new Error('Invalid render-desync PNG payload') + } + + const png = Buffer.from(args.pngDataUrl.slice(PNG_DATA_URL_PREFIX.length), 'base64') + const serializedMetadata = args.metadata ? `${JSON.stringify(args.metadata, null, 2)}\n` : null + if (serializedMetadata && Buffer.byteLength(serializedMetadata, 'utf8') > MAX_METADATA_BYTES) { + throw new Error('Render-desync metadata exceeds the storage budget') + } + const evidenceRoot = path.join(userDataPath, EVIDENCE_DIRECTORY) + const directory = path.join(evidenceRoot, args.captureId) + const pngPath = path.join(directory, `${args.phase}.png`) + const metadataPath = args.metadata ? path.join(directory, `${args.phase}.json`) : null + + // Why: captures contain terminal pixels and buffer text, so keep the + // opt-in field evidence private to the local OS account by default. + await mkdir(directory, { recursive: true, mode: 0o700 }) + await writeFile(pngPath, png, { mode: 0o600 }) + if (metadataPath && serializedMetadata) { + await writeFile(metadataPath, serializedMetadata, { + encoding: 'utf8', + mode: 0o600 + }) + } + await pruneRenderDesyncEvidence(evidenceRoot, directory) + return { directory, pngPath, metadataPath } +} + +async function pruneRenderDesyncEvidence(root: string, currentDirectory: string): Promise { + const entries = await readdir(root, { withFileTypes: true }) + const captures = await Promise.all( + entries + .filter((entry) => entry.isDirectory()) + .map(async (entry) => { + const directory = path.join(root, entry.name) + const [directoryStat, files] = await Promise.all([ + stat(directory), + readdir(directory, { withFileTypes: true }) + ]) + const sizes = await Promise.all( + files.filter((file) => file.isFile()).map((file) => stat(path.join(directory, file.name))) + ) + return { + directory, + bytes: sizes.reduce((total, file) => total + file.size, 0), + modifiedAt: directoryStat.mtimeMs + } + }) + ) + captures.sort((a, b) => a.modifiedAt - b.modifiedAt) + let totalBytes = captures.reduce((total, capture) => total + capture.bytes, 0) + while (captures.length > MAX_CAPTURE_DIRECTORIES || totalBytes > MAX_EVIDENCE_BYTES) { + const removableIndex = captures.findIndex((capture) => capture.directory !== currentDirectory) + const index = Math.max(removableIndex, 0) + const [capture] = captures.splice(index, 1) + await rm(capture.directory, { recursive: true, force: true }) + totalBytes -= capture.bytes + if (capture.directory === currentDirectory) { + throw new Error('Render-desync evidence exceeds the aggregate storage budget') + } + } +} diff --git a/src/main/ipc/ui.ts b/src/main/ipc/ui.ts index 48c26308e3f..de6f26136d5 100644 --- a/src/main/ipc/ui.ts +++ b/src/main/ipc/ui.ts @@ -76,7 +76,7 @@ export function registerUIHandlers(store: Store): void { }) } -function isTrustedUIRenderer(sender: WebContents): boolean { +export function isTrustedUIRenderer(sender: WebContents): boolean { if (sender.isDestroyed() || sender.getType() !== 'window') { return false } diff --git a/src/preload/api-types.ts b/src/preload/api-types.ts index c25c2acbc88..a209e436750 100644 --- a/src/preload/api-types.ts +++ b/src/preload/api-types.ts @@ -21,6 +21,10 @@ import type { } from '../shared/local-log-tail-types' import type { ReadClipboardTextOptions } from '../shared/clipboard-text' import type { AppIdentity } from '../shared/app-identity' +import type { + WriteTerminalRenderDesyncEvidenceArgs, + WriteTerminalRenderDesyncEvidenceResult +} from '../shared/terminal-render-desync-evidence' import type { MobileRelayStatus } from '../shared/mobile-relay-status' import type { MobilePairingConnectionMode } from '../shared/mobile-pairing-connection-mode' import type { @@ -948,6 +952,10 @@ export type AppApi = { /** Opens a native directory picker and authorizes the selected directory * for Floating Workspace markdown file creation. */ pickFloatingWorkspaceDirectory: () => Promise + /** Persists flag-gated terminal render evidence under app-owned userData. */ + writeTerminalRenderDesyncEvidence: ( + args: WriteTerminalRenderDesyncEvidenceArgs + ) => Promise } export type PreloadApi = { diff --git a/src/preload/index.ts b/src/preload/index.ts index 8084b0b1fc3..7c0f42369e9 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -64,6 +64,7 @@ import type { PtyRendererDeliveryStateReport } from '../shared/pty-renderer-delivery-health' import type { TerminalViewAttributes } from '../shared/terminal-view-attributes' +import type { WriteTerminalRenderDesyncEvidenceArgs } from '../shared/terminal-render-desync-evidence' import type { PtyMainDeliveryDiagnostics } from '../shared/pty-delivery-diagnostics' import type { WarpThemeImportPreview, @@ -505,7 +506,9 @@ const api = { pickFloatingMarkdownDocument: (): Promise => ipcRenderer.invoke('app:pickFloatingMarkdownDocument'), pickFloatingWorkspaceDirectory: (): Promise => - ipcRenderer.invoke('app:pickFloatingWorkspaceDirectory') + ipcRenderer.invoke('app:pickFloatingWorkspaceDirectory'), + writeTerminalRenderDesyncEvidence: (args: WriteTerminalRenderDesyncEvidenceArgs) => + ipcRenderer.invoke('terminal:writeRenderDesyncEvidence', args) }, orcaProfiles: { diff --git a/src/renderer/src/components/terminal-pane/terminal-freeze-breadcrumbs.ts b/src/renderer/src/components/terminal-pane/terminal-freeze-breadcrumbs.ts index 042d261450f..09225bda3a4 100644 --- a/src/renderer/src/components/terminal-pane/terminal-freeze-breadcrumbs.ts +++ b/src/renderer/src/components/terminal-pane/terminal-freeze-breadcrumbs.ts @@ -7,6 +7,7 @@ import { createPtyDeliveryBreadcrumbRing } from '../../../../shared/pty-delivery-diagnostics' import { setTerminalWebglDiagnosticRecorder } from '../../../../shared/terminal-webgl-diagnostics' +import { maybeStartTerminalRenderDesyncSentinel } from './terminal-render-desync-sentinel' const rendererDeliveryBreadcrumbs = createPtyDeliveryBreadcrumbRing() @@ -25,6 +26,11 @@ setTerminalWebglDiagnosticRecorder((kind, detail) => rendererDeliveryBreadcrumbs.record(kind, detail) ) +// Why: the sentinel is a field-diagnostic that must be armable on production +// builds; starting it from this diagnostics bootstrap keeps arming independent +// of any specific pane mounting first. No-op unless its localStorage flag is set. +maybeStartTerminalRenderDesyncSentinel() + export function getTerminalFreezeBreadcrumbs(): PtyDeliveryBreadcrumb[] { return rendererDeliveryBreadcrumbs.snapshot() } diff --git a/src/renderer/src/components/terminal-pane/terminal-render-desync-frame.test.ts b/src/renderer/src/components/terminal-pane/terminal-render-desync-frame.test.ts new file mode 100644 index 00000000000..6d7af35562b --- /dev/null +++ b/src/renderer/src/components/terminal-pane/terminal-render-desync-frame.test.ts @@ -0,0 +1,75 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { + measureDivergence, + releaseRenderDesyncReadback, + type BufferLike, + type SentinelRenderInternals +} from './terminal-render-desync-frame' + +const rendererState = { + atlasPages: 1, + atlasClearModelGeneration: 0, + atlasPageVersions: [0], + glyphLastSeenClearModelGeneration: 0, + glyphTextureVersions: [0], + modelLineLengths: [1], + vertexCount: 1, + activeVertexBuffer: 0 +} + +const buffer: BufferLike = { + cursorY: 1, + viewportY: 0, + getLine: () => ({ + getCell: () => ({ getChars: () => 'x', getWidth: () => 1 }), + translateToString: () => 'x' + }) +} + +afterEach(() => { + releaseRenderDesyncReadback() + vi.unstubAllGlobals() +}) + +describe('measureDivergence', () => { + it.each([ + { name: 'dark', background: [0, 0, 0] as const, ink: [255, 255, 255] as const }, + { name: 'light', background: [255, 255, 255] as const, ink: [0, 0, 0] as const } + ])('detects missing and rendered cells against a $name theme', ({ background, ink }) => { + const pixels = new Uint8ClampedArray(4 * 4 * 4) + for (let index = 0; index < pixels.length; index += 4) { + pixels[index] = background[0] + pixels[index + 1] = background[1] + pixels[index + 2] = background[2] + pixels[index + 3] = 255 + } + const context = { + drawImage: vi.fn(), + getImageData: vi.fn(() => ({ data: pixels })) + } + const createElement = vi.fn(() => ({ + width: 0, + height: 0, + getContext: () => context + })) + vi.stubGlobal('document', { createElement }) + const internals: SentinelRenderInternals = { + rows: 1, + cols: 1, + isPaused: false, + canvas: { width: 4, height: 4 } as HTMLCanvasElement, + cellWidth: 4, + cellHeight: 4, + backgroundRgb: background, + rendererState + } + + expect(measureDivergence(internals, buffer)?.missing).toBe(1) + const center = (1 * 4 + 1) * 4 + pixels[center] = ink[0] + pixels[center + 1] = ink[1] + pixels[center + 2] = ink[2] + expect(measureDivergence(internals, buffer)?.missing).toBe(0) + expect(createElement).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/renderer/src/components/terminal-pane/terminal-render-desync-frame.ts b/src/renderer/src/components/terminal-pane/terminal-render-desync-frame.ts new file mode 100644 index 00000000000..103adca6266 --- /dev/null +++ b/src/renderer/src/components/terminal-pane/terminal-render-desync-frame.ts @@ -0,0 +1,250 @@ +export type SentinelDivergence = { + textCells: number + missing: number + missPct: number + missingCells: Set +} + +export type SentinelRendererState = { + atlasPages: number + atlasClearModelGeneration: number | null + atlasPageVersions: number[] + glyphLastSeenClearModelGeneration: number | null + glyphTextureVersions: number[] + modelLineLengths: number[] + vertexCount: number | null + activeVertexBuffer: number | null +} + +export type SentinelRenderInternals = { + rows: number + cols: number + isPaused: boolean + canvas: HTMLCanvasElement + cellWidth: number + cellHeight: number + backgroundRgb: readonly [red: number, green: number, blue: number] + rendererState: SentinelRendererState +} + +type BufferLineLike = { + getCell: (x: number) => { getChars: () => string; getWidth: () => number } | undefined + translateToString: (trim: boolean) => string +} + +export type BufferLike = { + cursorY: number + viewportY: number + getLine: (y: number) => BufferLineLike | undefined +} + +const INK_BACKGROUND_DISTANCE = 36 +const MISSING_SET_MIN_OVERLAP = 0.5 +let readbackCanvas: HTMLCanvasElement | null = null +let readbackContext: CanvasRenderingContext2D | null = null + +export function reachRenderInternals(terminal: unknown): SentinelRenderInternals | null { + try { + const term = terminal as { + rows?: number + cols?: number + buffer?: unknown + _core?: { + _renderService?: { + _isPaused?: boolean + _renderer?: { + value?: { + _canvas?: HTMLCanvasElement + _charAtlas?: { + clearModelGeneration?: number + pages?: { version?: number }[] + } + _model?: { lineLengths?: number[] | Uint32Array } + _glyphRenderer?: { + value?: { + _activeBuffer?: number + _lastSeenClearModelGeneration?: number + _atlasTextures?: { version?: number }[] + _vertices?: { count?: number } + } + } + _themeService?: { colors?: { background?: { rgba?: number } } } + dimensions?: { device?: { cell?: { width?: number; height?: number } } } + } + } + } + } + } + const service = term._core?._renderService + const renderer = service?._renderer?.value + const cell = renderer?.dimensions?.device?.cell + const backgroundRgba = renderer?._themeService?.colors?.background?.rgba + if ( + typeof term.rows !== 'number' || + typeof term.cols !== 'number' || + !renderer?._canvas || + !renderer._charAtlas || + typeof backgroundRgba !== 'number' || + typeof cell?.width !== 'number' || + typeof cell?.height !== 'number' + ) { + return null + } + const glyphRenderer = renderer._glyphRenderer?.value + return { + rows: term.rows, + cols: term.cols, + isPaused: service?._isPaused === true, + canvas: renderer._canvas, + cellWidth: cell.width, + cellHeight: cell.height, + // xterm packs colors as RRGGBBAA; compare against the active theme so + // light terminals do not make every background pixel look like glyph ink. + backgroundRgb: [ + backgroundRgba >>> 24, + (backgroundRgba >>> 16) & 255, + (backgroundRgba >>> 8) & 255 + ], + rendererState: { + atlasPages: renderer._charAtlas.pages?.length ?? -1, + atlasClearModelGeneration: renderer._charAtlas.clearModelGeneration ?? null, + atlasPageVersions: renderer._charAtlas.pages?.map((page) => page.version ?? -1) ?? [], + glyphLastSeenClearModelGeneration: glyphRenderer?._lastSeenClearModelGeneration ?? null, + glyphTextureVersions: + glyphRenderer?._atlasTextures?.map((texture) => texture.version ?? -1) ?? [], + modelLineLengths: Array.from(renderer._model?.lineLengths ?? []), + vertexCount: glyphRenderer?._vertices?.count ?? null, + activeVertexBuffer: glyphRenderer?._activeBuffer ?? null + } + } + } catch { + return null + } +} + +export function activeBuffer(terminal: unknown): BufferLike | null { + const buffer = (terminal as { buffer?: { active?: BufferLike } }).buffer?.active + return buffer && typeof buffer.getLine === 'function' ? buffer : null +} + +export function measureDivergence( + internals: SentinelRenderInternals, + buffer: BufferLike +): SentinelDivergence | null { + const { canvas, cellWidth, cellHeight, rows, cols, backgroundRgb } = internals + if (!canvas.width || !canvas.height) { + return null + } + const ctx = getReadbackContext(canvas.width, canvas.height) + if (!ctx) { + return null + } + ctx.drawImage(canvas, 0, 0) + const image = ctx.getImageData(0, 0, canvas.width, canvas.height).data + + const missingCells = new Set() + let textCells = 0 + let missing = 0 + for (let row = 0; row < rows; row++) { + if (row === buffer.cursorY) { + continue + } + const line = buffer.getLine(buffer.viewportY + row) + if (!line) { + continue + } + for (let column = 0; column < cols; column++) { + const cell = line.getCell(column) + if (!cell) { + continue + } + const chars = cell.getChars() + if (chars === '' || chars === ' ' || cell.getWidth() === 0) { + continue + } + let ink = 0 + let sampled = 0 + const x0 = Math.round(column * cellWidth + cellWidth * 0.25) + const x1 = Math.round(column * cellWidth + cellWidth * 0.75) + const y0 = Math.round(row * cellHeight + cellHeight * 0.25) + const y1 = Math.round(row * cellHeight + cellHeight * 0.75) + for (let py = y0; py < y1; py += 2) { + for (let px = x0; px < x1; px += 2) { + if (px >= canvas.width || py >= canvas.height) { + continue + } + const index = (py * canvas.width + px) * 4 + const distance = + Math.abs(image[index] - backgroundRgb[0]) + + Math.abs(image[index + 1] - backgroundRgb[1]) + + Math.abs(image[index + 2] - backgroundRgb[2]) + if (distance > INK_BACKGROUND_DISTANCE) { + ink++ + } + sampled++ + } + } + if (!sampled) { + continue + } + textCells++ + if (ink === 0) { + missing++ + missingCells.add(row * cols + column) + } + } + } + return { + textCells, + missing, + missingCells, + missPct: textCells ? (100 * missing) / textCells : 0 + } +} + +function getReadbackContext(width: number, height: number): CanvasRenderingContext2D | null { + if (!readbackCanvas) { + readbackCanvas = document.createElement('canvas') + readbackContext = readbackCanvas.getContext('2d', { willReadFrequently: true }) + } + if (!readbackContext) { + return null + } + if (readbackCanvas.width !== width) { + readbackCanvas.width = width + } + if (readbackCanvas.height !== height) { + readbackCanvas.height = height + } + return readbackContext +} + +export function releaseRenderDesyncReadback(): void { + // Why: the diagnostic reuses one backing store during its burst, then drops + // the potentially multi-megabyte canvas instead of retaining it for the app lifetime. + if (readbackCanvas) { + readbackCanvas.width = 0 + readbackCanvas.height = 0 + } + readbackCanvas = null + readbackContext = null +} + +export function missingSetsOverlap(a: Set, b: Set): boolean { + let intersection = 0 + for (const cell of b) { + if (a.has(cell)) { + intersection++ + } + } + const union = a.size + b.size - intersection + return union > 0 && intersection / union >= MISSING_SET_MIN_OVERLAP +} + +export function bufferSnapshot(buffer: BufferLike, rows: number): string { + const lines: string[] = [] + for (let row = 0; row < rows; row++) { + lines.push(buffer.getLine(buffer.viewportY + row)?.translateToString(true) ?? '') + } + return lines.join('\n') +} diff --git a/src/renderer/src/components/terminal-pane/terminal-render-desync-sentinel.test.ts b/src/renderer/src/components/terminal-pane/terminal-render-desync-sentinel.test.ts new file mode 100644 index 00000000000..c5924a5dab8 --- /dev/null +++ b/src/renderer/src/components/terminal-pane/terminal-render-desync-sentinel.test.ts @@ -0,0 +1,218 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const forEachLivePaneForDesyncSentinel = vi.fn() +const resetAndRefreshAllTerminalWebglAtlases = vi.fn() +vi.mock('@/lib/pane-manager/pane-manager-registry', () => ({ + forEachLivePaneForDesyncSentinel: ( + ...args: Parameters + ) => forEachLivePaneForDesyncSentinel(...args), + resetAndRefreshAllTerminalWebglAtlases: () => resetAndRefreshAllTerminalWebglAtlases() +})) + +const recordTerminalWebglDiagnostic = vi.fn() +const documentAddEventListener = vi.fn() +const documentRemoveEventListener = vi.fn() +class FakeNode {} +const writeTerminalRenderDesyncEvidence = vi.fn().mockResolvedValue({ + directory: '/evidence/capture', + pngPath: '/evidence/capture/corrupt.png', + metadataPath: '/evidence/capture/corrupt.json' +}) +vi.mock('../../../../shared/terminal-webgl-diagnostics', () => ({ + recordTerminalWebglDiagnostic: (...args: Parameters) => + recordTerminalWebglDiagnostic(...args) +})) + +import { + getRenderDesyncEvidence, + maybeStartTerminalRenderDesyncSentinel, + RENDER_DESYNC_SENTINEL_FLAG, + sampleRenderDesyncOnce, + stopTerminalRenderDesyncSentinelForTesting +} from './terminal-render-desync-sentinel' + +function fakePane(overrides: { paused?: boolean } = {}) { + const refreshRows = vi.fn() + const terminal = { + element: { contains: vi.fn(() => false) }, + rows: 24, + cols: 80, + buffer: { + active: { + cursorY: 23, + viewportY: 0, + getLine: () => ({ + getCell: () => ({ getChars: () => 'x', getWidth: () => 1 }), + translateToString: () => 'x'.repeat(80) + }) + } + }, + _core: { + _renderService: { + _isPaused: overrides.paused === true, + refreshRows, + _renderer: { + value: { + _canvas: { width: 800, height: 480, toDataURL: () => 'data:image/png;base64,' }, + _charAtlas: {}, + _themeService: { colors: { background: { rgba: 0x000000ff } } }, + dimensions: { device: { cell: { width: 10, height: 20 } } } + } + } + } + } + } + return { pane: { id: 1, terminal }, refreshRows } +} + +function divergenceOf(cells: number[], textCells = 1000) { + return { + textCells, + missing: cells.length, + missingCells: new Set(cells), + missPct: (100 * cells.length) / textCells + } +} + +const manyCells = (offset: number) => Array.from({ length: 120 }, (_, i) => offset + i) + +describe('terminal-render-desync-sentinel', () => { + beforeEach(() => { + vi.clearAllMocks() + writeTerminalRenderDesyncEvidence.mockResolvedValue({ + directory: '/evidence/capture', + pngPath: '/evidence/capture/corrupt.png', + metadataPath: '/evidence/capture/corrupt.json' + }) + vi.stubGlobal('window', { api: { app: { writeTerminalRenderDesyncEvidence } } }) + vi.stubGlobal('document', { + addEventListener: documentAddEventListener, + removeEventListener: documentRemoveEventListener + }) + vi.stubGlobal('navigator', { userAgent: 'Mac' }) + vi.stubGlobal('Node', FakeNode) + }) + afterEach(() => { + stopTerminalRenderDesyncSentinelForTesting() + vi.unstubAllGlobals() + }) + + function sampleWith( + divergence: ReturnType | null, + paused = false, + paneKey = 'm1:p1' + ) { + const { pane, refreshRows } = fakePane({ paused }) + forEachLivePaneForDesyncSentinel.mockImplementation( + (visit: (key: string, pane: unknown) => void) => visit(paneKey, pane) + ) + sampleRenderDesyncOnce(() => divergence) + return { refreshRows } + } + + it('persists and recovers after the same cells stay missing twice', async () => { + const cells = manyCells(0) + sampleWith(divergenceOf(cells)) + expect(recordTerminalWebglDiagnostic).not.toHaveBeenCalled() + sampleWith(divergenceOf(cells)) + expect(recordTerminalWebglDiagnostic).toHaveBeenCalledWith( + 'webgl-render-desync', + expect.objectContaining({ paneKey: 'm1:p1', missing: 120 }) + ) + await vi.waitFor(() => expect(resetAndRefreshAllTerminalWebglAtlases).toHaveBeenCalledTimes(1)) + expect(writeTerminalRenderDesyncEvidence).toHaveBeenCalledWith( + expect.objectContaining({ + phase: 'corrupt', + metadata: expect.objectContaining({ bufferText: expect.stringContaining('x') }) + }) + ) + expect(getRenderDesyncEvidence()).toHaveLength(1) + expect(getRenderDesyncEvidence()[0].bufferText).toBeUndefined() + expect(getRenderDesyncEvidence()[0].livePngDataUrl).toBeUndefined() + }) + + it('does not redraw before measuring the compositor-presented canvas', () => { + const { refreshRows } = sampleWith(divergenceOf(manyCells(0))) + expect(refreshRows).not.toHaveBeenCalled() + }) + + it('does not trip when the missing cells move between samples (scroll lag)', () => { + sampleWith(divergenceOf(manyCells(0))) + sampleWith(divergenceOf(manyCells(500))) + sampleWith(divergenceOf(manyCells(1000))) + sampleWith(divergenceOf(manyCells(1500))) + expect(recordTerminalWebglDiagnostic).not.toHaveBeenCalled() + expect(resetAndRefreshAllTerminalWebglAtlases).not.toHaveBeenCalled() + }) + + it('does not trip below the missing-percentage threshold', () => { + const few = Array.from({ length: 10 }, (_, i) => i) + sampleWith(divergenceOf(few)) + sampleWith(divergenceOf(few)) + expect(recordTerminalWebglDiagnostic).not.toHaveBeenCalled() + }) + + it('requires consecutive threshold breaches', () => { + const cells = manyCells(0) + sampleWith(divergenceOf(cells)) + sampleWith(divergenceOf(Array.from({ length: 10 }, (_, i) => i))) + sampleWith(divergenceOf(cells)) + + expect(recordTerminalWebglDiagnostic).not.toHaveBeenCalled() + expect(resetAndRefreshAllTerminalWebglAtlases).not.toHaveBeenCalled() + }) + + it('caps capture writes while preserving recovery after the budget is spent', async () => { + for (let pane = 1; pane <= 5; pane++) { + sampleWith(divergenceOf(manyCells(0)), false, `m1:p${pane}`) + sampleWith(divergenceOf(manyCells(0)), false, `m1:p${pane}`) + } + + await vi.waitFor(() => expect(writeTerminalRenderDesyncEvidence).toHaveBeenCalledTimes(4)) + expect(getRenderDesyncEvidence()).toHaveLength(4) + expect(resetAndRefreshAllTerminalWebglAtlases).toHaveBeenCalledTimes(5) + }) + + it('resets tracking for paused panes instead of sampling them', () => { + const cells = manyCells(0) + sampleWith(divergenceOf(cells)) + sampleWith(divergenceOf(cells), true) + sampleWith(divergenceOf(cells)) + expect(recordTerminalWebglDiagnostic).not.toHaveBeenCalled() + }) + + it('stays disarmed without the flag and starts a burst on modifier-click', () => { + vi.useFakeTimers() + try { + const storage = new Map() + vi.stubGlobal('localStorage', { + getItem: (k: string) => storage.get(k) ?? null, + setItem: (k: string, v: string) => storage.set(k, v) + }) + maybeStartTerminalRenderDesyncSentinel() + const { pane } = fakePane() + const target = new FakeNode() + expect(forEachLivePaneForDesyncSentinel).not.toHaveBeenCalled() + + storage.set(RENDER_DESYNC_SENTINEL_FLAG, '1') + maybeStartTerminalRenderDesyncSentinel() + ;( + pane.terminal as never as { + _core: { _renderService: { _renderer: { value: { _canvas: { width: number } } } } } + } + )._core._renderService._renderer.value._canvas.width = 0 + ;( + pane.terminal as { element: { contains: ReturnType } } + ).element.contains.mockReturnValue(true) + forEachLivePaneForDesyncSentinel.mockImplementation( + (visit: (key: string, pane: unknown) => void) => visit('m1:p1', pane) + ) + const listener = documentAddEventListener.mock.calls.at(-1)?.[1] + listener({ button: 0, metaKey: true, ctrlKey: true, target }) + vi.advanceTimersByTime(300) + expect(forEachLivePaneForDesyncSentinel).toHaveBeenCalled() + } finally { + vi.useRealTimers() + } + }) +}) diff --git a/src/renderer/src/components/terminal-pane/terminal-render-desync-sentinel.ts b/src/renderer/src/components/terminal-pane/terminal-render-desync-sentinel.ts new file mode 100644 index 00000000000..b6a9b0847eb --- /dev/null +++ b/src/renderer/src/components/terminal-pane/terminal-render-desync-sentinel.ts @@ -0,0 +1,292 @@ +import { recordTerminalWebglDiagnostic } from '../../../../shared/terminal-webgl-diagnostics' +import { + forEachLivePaneForDesyncSentinel, + resetAndRefreshAllTerminalWebglAtlases +} from '@/lib/pane-manager/pane-manager-registry' +import { + activeBuffer, + bufferSnapshot, + measureDivergence, + missingSetsOverlap, + reachRenderInternals, + releaseRenderDesyncReadback, + type SentinelRendererState, + type SentinelRenderInternals +} from './terminal-render-desync-frame' + +/** + * Flag-gated render-desync sentinel for WebGL terminal panes. + * + * Detects the "buffer is correct but the canvas renders stale/garbled glyphs" + * class of bug (shared glyph-atlas desync) by comparing, per visible pane, the + * cells the xterm buffer says hold glyphs against the ink actually present on + * the WebGL canvas. Modifier-clicks start a short burst that reads the + * compositor-presented canvas before any forced redraw can heal or destroy the + * failure. A confirmed trip writes the pixels, buffer, and atlas/model versions + * to local app data before running the shared-atlas recovery. + * + * Off by default; enabled via localStorage so a production build can arm it + * from DevTools without a settings-schema change: + * localStorage.setItem('orca:render-desync-sentinel', '1') // then reload + */ + +export const RENDER_DESYNC_SENTINEL_FLAG = 'orca:render-desync-sentinel' +const SAMPLE_INTERVAL_MS = 250 +const SAMPLE_BURST_MS = 10_000 +// A real desync is pinned to fixed screen cells; scroll/frame lag moves around. +// Require the same cells missing across this many consecutive samples. +const PERSISTENT_SAMPLES = 2 +const MIN_TEXT_CELLS = 200 +const MISSING_PCT_THRESHOLD = 8 +const MAX_EVIDENCE_ENTRIES = 4 + +export type SentinelEvidence = { + captureId: string + paneKey: string + when: number + divergence: { textCells: number; missing: number; missPct: number } + paused: boolean + rendererState: SentinelRendererState + livePngDataUrl?: string + bufferText?: string + persistedDirectory?: string +} + +type SentinelPane = { + id: number + terminal: unknown +} + +const missingHistoryByPane = new Map[]>() +const pendingPaneKeys = new Set() +const healedCaptureTimeoutIds = new Set>() +const evidence: SentinelEvidence[] = [] +let burstIntervalId: ReturnType | null = null +let burstTimeoutId: ReturnType | null = null +let clickListener: ((event: MouseEvent) => void) | null = null +let burstTerminal: unknown = null + +export function getRenderDesyncEvidence(): SentinelEvidence[] { + return evidence +} + +export function sampleRenderDesyncOnce( + // Test seam: happy-dom has no 2D canvas, so tests inject crafted divergences. + measure: typeof measureDivergence = measureDivergence +): void { + forEachLivePaneForDesyncSentinel((paneKey, pane) => { + const terminal = (pane as SentinelPane).terminal + if ((burstTerminal && terminal !== burstTerminal) || pendingPaneKeys.has(paneKey)) { + return + } + const internals = reachRenderInternals(terminal) + if (!internals || internals.isPaused) { + missingHistoryByPane.delete(paneKey) + return + } + const buffer = activeBuffer(terminal) + if (!buffer) { + return + } + // Why: the field failure can heal on any refresh. Read the canvas exactly + // as Chromium presented it; recovery happens only after durable evidence. + const divergence = measure(internals, buffer) + if (!divergence || divergence.textCells < MIN_TEXT_CELLS) { + missingHistoryByPane.delete(paneKey) + return + } + if (divergence.missPct < MISSING_PCT_THRESHOLD) { + // Why: only consecutive threshold breaches prove persistence; retaining a + // subthreshold frame lets one later spike create a false field capture. + missingHistoryByPane.delete(paneKey) + return + } + const history = missingHistoryByPane.get(paneKey) ?? [] + history.push(divergence.missingCells) + while (history.length > PERSISTENT_SAMPLES) { + history.shift() + } + missingHistoryByPane.set(paneKey, history) + + if (history.length < PERSISTENT_SAMPLES) { + return + } + for (let i = 1; i < history.length; i++) { + if (!missingSetsOverlap(history[i - 1], history[i])) { + return + } + } + + missingHistoryByPane.delete(paneKey) + recordTerminalWebglDiagnostic('webgl-render-desync', { + paneKey, + textCells: divergence.textCells, + missing: divergence.missing, + missPct: Math.round(divergence.missPct * 10) / 10 + }) + if (evidence.length >= MAX_EVIDENCE_ENTRIES) { + // Why: captures can contain full terminal canvases and buffer contents. + // Keep recovery available after the per-session evidence budget is spent. + console.warn(`[terminal] render desync detected on pane ${paneKey}; capture budget exhausted`) + resetAndRefreshAllTerminalWebglAtlases() + stopSampleBurst() + return + } + pendingPaneKeys.add(paneKey) + const entry: SentinelEvidence = { + captureId: createCaptureId(paneKey), + paneKey, + when: Date.now(), + divergence: { + textCells: divergence.textCells, + missing: divergence.missing, + missPct: divergence.missPct + }, + paused: internals.isPaused, + rendererState: internals.rendererState, + livePngDataUrl: internals.canvas.toDataURL(), + bufferText: bufferSnapshot(buffer, internals.rows) + } + evidence.push(entry) + console.warn( + `[terminal] render desync detected on pane ${paneKey} ` + + `(${divergence.missing}/${divergence.textCells} cells, ${divergence.missPct.toFixed(1)}%) — persisting evidence` + ) + void persistEvidenceThenRecover(entry, internals) + }) +} + +export function maybeStartTerminalRenderDesyncSentinel(): void { + if (clickListener != null) { + return + } + let enabled = false + try { + enabled = globalThis.localStorage?.getItem(RENDER_DESYNC_SENTINEL_FLAG) === '1' + } catch { + enabled = false + } + if (!enabled) { + return + } + clickListener = (event) => { + const isMac = navigator.userAgent.includes('Mac') + if (event.button !== 0 || (isMac ? !event.metaKey : !event.ctrlKey)) { + return + } + const target = event.target + if (!(target instanceof Node)) { + return + } + let clickedTerminal: unknown = null + forEachLivePaneForDesyncSentinel((_paneKey, pane) => { + const terminal = (pane as SentinelPane).terminal as { element?: HTMLElement } + if (terminal.element?.contains(target)) { + clickedTerminal = terminal + } + }) + if (clickedTerminal) { + startSampleBurst(clickedTerminal) + } + } + document.addEventListener('mouseup', clickListener, true) + console.warn('[terminal] render-desync sentinel armed (10s post-link bursts)') +} + +export function stopTerminalRenderDesyncSentinelForTesting(): void { + stopSampleBurst() + if (clickListener != null) { + document.removeEventListener('mouseup', clickListener, true) + clickListener = null + } + missingHistoryByPane.clear() + pendingPaneKeys.clear() + for (const timeoutId of healedCaptureTimeoutIds) { + clearTimeout(timeoutId) + } + healedCaptureTimeoutIds.clear() + evidence.length = 0 +} + +function createCaptureId(paneKey: string): string { + const panePart = paneKey.replace(/[^a-zA-Z0-9_-]/g, '-') + const nonce = globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2) + return `${Date.now()}-${panePart}-${nonce}` +} + +function startSampleBurst(terminal: unknown): void { + stopSampleBurst() + burstTerminal = terminal + sampleRenderDesyncOnce() + burstIntervalId = setInterval(sampleRenderDesyncOnce, SAMPLE_INTERVAL_MS) + burstTimeoutId = setTimeout(stopSampleBurst, SAMPLE_BURST_MS) +} + +function stopSampleBurst(): void { + if (burstIntervalId != null) { + clearInterval(burstIntervalId) + burstIntervalId = null + } + if (burstTimeoutId != null) { + clearTimeout(burstTimeoutId) + burstTimeoutId = null + } + burstTerminal = null + missingHistoryByPane.clear() + releaseRenderDesyncReadback() +} + +async function persistEvidenceThenRecover( + entry: SentinelEvidence, + internals: SentinelRenderInternals +): Promise { + try { + const pngDataUrl = entry.livePngDataUrl + const bufferText = entry.bufferText + if (!pngDataUrl || bufferText == null) { + throw new Error('Render-desync evidence payload was released before persistence') + } + const persisted = await window.api.app.writeTerminalRenderDesyncEvidence({ + captureId: entry.captureId, + phase: 'corrupt', + pngDataUrl, + metadata: { + paneKey: entry.paneKey, + when: entry.when, + divergence: entry.divergence, + paused: entry.paused, + rendererState: entry.rendererState, + bufferText + } + }) + entry.persistedDirectory = persisted.directory + } catch (error) { + // Why: a failed write must leave the bad pixels intact; recovering here + // would destroy the only evidence without producing a durable capture. + console.error('[terminal] could not persist render-desync evidence; leaving pane intact', error) + pendingPaneKeys.delete(entry.paneKey) + return + } finally { + // Why: persistence owns a successful payload, while a failed write leaves + // the live pane intact; neither path should retain duplicate full-canvas data. + entry.livePngDataUrl = undefined + entry.bufferText = undefined + } + + resetAndRefreshAllTerminalWebglAtlases() + const timeoutId = setTimeout(() => { + healedCaptureTimeoutIds.delete(timeoutId) + void window.api.app + .writeTerminalRenderDesyncEvidence({ + captureId: entry.captureId, + phase: 'healed', + pngDataUrl: internals.canvas.toDataURL(), + metadata: { when: Date.now() } + }) + .catch((error) => + console.error('[terminal] could not persist healed render reference', error) + ) + .finally(() => pendingPaneKeys.delete(entry.paneKey)) + }, SAMPLE_INTERVAL_MS) + healedCaptureTimeoutIds.add(timeoutId) +} diff --git a/src/renderer/src/lib/pane-manager/pane-manager-registry.test.ts b/src/renderer/src/lib/pane-manager/pane-manager-registry.test.ts index 206ab74b215..d8841966ffd 100644 --- a/src/renderer/src/lib/pane-manager/pane-manager-registry.test.ts +++ b/src/renderer/src/lib/pane-manager/pane-manager-registry.test.ts @@ -1,5 +1,6 @@ import { afterEach, describe, expect, it, vi, type Mock } from 'vitest' import { + forEachLivePaneForDesyncSentinel, refitAndRefreshAllTerminalPanes, registerLivePaneManager, resetAndRefreshAllTerminalWebglAtlases, @@ -153,4 +154,28 @@ describe('pane manager registry', () => { expect(healthy.fitAllPanes).toHaveBeenCalledTimes(1) expect(healthy.refreshAllPanes).toHaveBeenCalledTimes(1) }) + + it('keeps pane keys stable when an earlier manager unregisters', () => { + const first = { + resetWebglTextureAtlases: vi.fn<() => void>(), + getPanes: () => [{ id: 1, terminal: {} }] + } + const second = { + resetWebglTextureAtlases: vi.fn<() => void>(), + getPanes: () => [{ id: 1, terminal: {} }] + } + registerLivePaneManager(first) + registeredManagers.push(first) + registerLivePaneManager(second) + registeredManagers.push(second) + const before: string[] = [] + forEachLivePaneForDesyncSentinel((paneKey) => before.push(paneKey)) + + unregisterLivePaneManager(first) + const after: string[] = [] + forEachLivePaneForDesyncSentinel((paneKey) => after.push(paneKey)) + + expect(before).toHaveLength(2) + expect(after).toEqual([before[1]]) + }) }) diff --git a/src/renderer/src/lib/pane-manager/pane-manager-registry.ts b/src/renderer/src/lib/pane-manager/pane-manager-registry.ts index 2a52b31ba78..8442c8af111 100644 --- a/src/renderer/src/lib/pane-manager/pane-manager-registry.ts +++ b/src/renderer/src/lib/pane-manager/pane-manager-registry.ts @@ -6,11 +6,17 @@ type RegisteredPaneManager = { fitAllPanes?: () => void refreshAllPanes?: () => void getRenderingDiagnostics?: () => PaneRenderingDiagnostics[] + getPanes?: () => { id: number; terminal: unknown }[] } const liveManagers = new Set() +const managerIds = new WeakMap() +let nextManagerId = 1 export function registerLivePaneManager(manager: RegisteredPaneManager): void { + if (!managerIds.has(manager)) { + managerIds.set(manager, nextManagerId++) + } liveManagers.add(manager) } @@ -83,6 +89,34 @@ export function getAllPaneRenderingDiagnostics(): PaneRenderingDiagnostics[] { return all } +/** + * Iterates every live pane for the render-desync sentinel. Weakly-held manager + * ids stay stable when an earlier manager unregisters without retaining it. + */ +export function forEachLivePaneForDesyncSentinel( + visit: (paneKey: string, pane: { id: number; terminal: unknown }) => void +): void { + for (const manager of liveManagers) { + const managerId = managerIds.get(manager) + if (managerId == null) { + continue + } + let panes: { id: number; terminal: unknown }[] = [] + try { + panes = manager.getPanes?.() ?? [] + } catch { + continue + } + for (const pane of panes) { + try { + visit(`m${managerId}:p${pane.id}`, pane) + } catch { + // Why: one pane's failure must not stop sentinel coverage of the rest. + } + } + } +} + export function refitAndRefreshAllTerminalPanes(): void { for (const manager of liveManagers) { try { diff --git a/src/renderer/src/web/web-preload-api.ts b/src/renderer/src/web/web-preload-api.ts index 6ae5cfa1c6d..f63316a5c34 100644 --- a/src/renderer/src/web/web-preload-api.ts +++ b/src/renderer/src/web/web-preload-api.ts @@ -502,7 +502,13 @@ function createWebPreloadApi(): Partial { getFloatingTerminalCwd: () => Promise.resolve(''), getFloatingMarkdownDirectory: () => Promise.resolve(''), pickFloatingMarkdownDocument: () => Promise.resolve(null), - pickFloatingWorkspaceDirectory: () => Promise.resolve(null) + pickFloatingWorkspaceDirectory: () => Promise.resolve(null), + // Browser fallback has no app-owned userData directory; rejecting keeps + // the sentinel from claiming that sensitive evidence was persisted. + writeTerminalRenderDesyncEvidence: () => + Promise.reject( + new Error('Terminal render evidence is unavailable in the browser fallback.') + ) }, starNag: { onShow: () => noopUnsubscribe, diff --git a/src/shared/terminal-render-desync-evidence.ts b/src/shared/terminal-render-desync-evidence.ts new file mode 100644 index 00000000000..1621626e187 --- /dev/null +++ b/src/shared/terminal-render-desync-evidence.ts @@ -0,0 +1,14 @@ +export type TerminalRenderDesyncEvidencePhase = 'corrupt' | 'healed' + +export type WriteTerminalRenderDesyncEvidenceArgs = { + captureId: string + phase: TerminalRenderDesyncEvidencePhase + pngDataUrl: string + metadata?: Record +} + +export type WriteTerminalRenderDesyncEvidenceResult = { + directory: string + pngPath: string + metadataPath: string | null +} diff --git a/tests/e2e/terminal-webgl-atlas-budget.spec.ts b/tests/e2e/terminal-webgl-atlas-budget.spec.ts index 22c9377d09d..70b9a87c9d6 100644 --- a/tests/e2e/terminal-webgl-atlas-budget.spec.ts +++ b/tests/e2e/terminal-webgl-atlas-budget.spec.ts @@ -32,6 +32,11 @@ type TestWebglAddon = { _canvas: HTMLCanvasElement _charAtlas: TextureAtlas & { _evictAllPages?: () => void } dimensions: { device: { cell: { height: number } } } + _glyphRenderer: { + value: { + setAtlas: (atlas: TextureAtlas) => void + } + } } clearTextureAtlas: () => void } @@ -57,6 +62,12 @@ type AtlasBudgetResult = { unbindableAfterWipe: number } +type AtlasReplacementResult = { + baselineInkPixels: number + distinctAtlases: boolean + pixelDiffAfterReplacement: number +} + async function forceActivePaneWebgl(page: Page): Promise { const tabId = await page.evaluate(() => { const state = window.__store?.getState() @@ -268,6 +279,155 @@ async function runAtlasBudgetScenario(page: Page): Promise { }) } +async function runAtlasReplacementScenario(page: Page): Promise { + return page.evaluate(async () => { + const state = window.__store?.getState() + const worktreeId = state?.activeWorktreeId + const tabId = + state?.activeTabType === 'terminal' + ? state.activeTabId + : worktreeId + ? (state?.activeTabIdByWorktree?.[worktreeId] ?? null) + : null + const manager = tabId ? window.__paneManagers?.get(tabId) : null + const pane = manager + ? ([ + ...(( + manager as unknown as { panes?: Map } + ).panes?.values() ?? []) + ][0] ?? null) + : null + if (!pane?.webglAddon) { + throw new Error('Active pane WebGL internals unavailable') + } + + const TerminalCtor = pane.terminal.constructor + const WebglCtor = pane.webglAddon.constructor as new (options?: { + customGlyphs?: boolean + }) => TestWebglAddon + const host = document.createElement('div') + host.style.cssText = + 'position:fixed;inset:0;width:2200px;height:700px;opacity:0.001;pointer-events:none;z-index:-1' + const containers = [document.createElement('div'), document.createElement('div')] + for (const container of containers) { + container.style.cssText = 'display:inline-block;width:1000px;height:650px' + host.appendChild(container) + } + document.body.appendChild(host) + + const makeTerminal = ( + container: HTMLElement, + customGlyphs: boolean + ): { terminal: TestTerminal; addon: TestWebglAddon } => { + const terminal = new TerminalCtor({ + cols: 72, + rows: 18, + fontSize: 24, + fontFamily: 'Menlo, monospace', + cursorBlink: false, + scrollback: 100 + }) + terminal.open(container) + const addon = new WebglCtor({ customGlyphs }) + terminal.loadAddon(addon) + return { terminal, addon } + } + const write = (terminal: TestTerminal, data: string): Promise => + new Promise((resolve) => terminal.write(data, resolve)) + const render = (terminal: TestTerminal): void => { + terminal._core._renderService._isPaused = false + terminal._core._renderService._needsFullRefresh = false + terminal._core._renderService.refreshRows(0, terminal.rows - 1, true) + } + const capture = (addon: TestWebglAddon): Uint8ClampedArray => { + const source = addon._renderer._canvas + const canvas = document.createElement('canvas') + canvas.width = source.width + canvas.height = source.height + const context = canvas.getContext('2d') + if (!context) { + throw new Error('Screenshot canvas context unavailable') + } + context.drawImage(source, 0, 0) + return context.getImageData(0, 0, canvas.width, canvas.height).data + } + const pixelDiff = (a: Uint8ClampedArray, b: Uint8ClampedArray): number => { + let count = 0 + for (let i = 0; i < Math.min(a.length, b.length); i += 4) { + if ( + Math.abs(a[i] - b[i]) > 8 || + Math.abs(a[i + 1] - b[i + 1]) > 8 || + Math.abs(a[i + 2] - b[i + 2]) > 8 + ) { + count += 1 + } + } + return count + } + const pixelsDifferentFromFirst = (pixels: Uint8ClampedArray): number => { + const [red, green, blue] = pixels + let count = 0 + for (let i = 0; i < pixels.length; i += 4) { + if ( + Math.abs(pixels[i] - red) > 8 || + Math.abs(pixels[i + 1] - green) > 8 || + Math.abs(pixels[i + 2] - blue) > 8 + ) { + count += 1 + } + } + return count + } + const content = (start: number): string => { + let output = '\x1b[2J\x1b[H\x1b[?25l' + let key = start + for (let row = 0; row < 16; row += 1) { + for (let column = 0; column < 48; column += 1) { + const red = (key * 29) & 255 + const green = (key * 71) & 255 + const blue = (key * 131) & 255 + output += `\x1b[38;2;${red};${green};${blue}m${String.fromCharCode(33 + (key % 94))}` + key += 1 + } + output += '\r\n' + } + return `${output}\x1b[0m` + } + + let terminalA: TestTerminal | undefined + let terminalB: TestTerminal | undefined + try { + const A = makeTerminal(containers[0], true) + terminalA = A.terminal + const B = makeTerminal(containers[1], false) + terminalB = B.terminal + await write(A.terminal, content(1_000)) + await write(B.terminal, content(100_000)) + render(A.terminal) + render(B.terminal) + + const atlasA = A.addon._renderer._charAtlas + const atlasB = B.addon._renderer._charAtlas + const baseline = capture(A.addon) + // Why: replacing a renderer's shared atlas leaves every cached vertex tied + // to the old atlas, so the next draw must rebuild even at the same generation. + A.addon._renderer._glyphRenderer.value.setAtlas(atlasB) + render(A.terminal) + const afterReplacement = capture(A.addon) + + return { + baselineInkPixels: pixelsDifferentFromFirst(baseline), + distinctAtlases: atlasA !== atlasB, + pixelDiffAfterReplacement: pixelDiff(afterReplacement, baseline) + } + } finally { + terminalA?.dispose() + terminalB?.dispose() + host.remove() + } + }) +} + test.describe('terminal WebGL atlas budget', () => { test.describe.configure({ timeout: 120_000 }) @@ -290,4 +450,16 @@ test.describe('terminal WebGL atlas budget', () => { expect(result.baselineInkPixels).toBeGreaterThan(1000) expect(result.pixelDiffAfterWipe).toBe(0) }) + + test('rebuilds cached vertices after attaching a different shared atlas @terminal-rendering-golden', async ({ + orcaPage + }) => { + await waitForActiveTerminalManager(orcaPage) + test.skip(!(await forceActivePaneWebgl(orcaPage)), 'WebGL unavailable in this environment') + const result = await runAtlasReplacementScenario(orcaPage) + + expect(result.distinctAtlases).toBe(true) + expect(result.baselineInkPixels).toBeGreaterThan(1000) + expect(result.pixelDiffAfterReplacement).toBe(0) + }) }) diff --git a/tools/terminal-garble-frame-analysis.mjs b/tools/terminal-garble-frame-analysis.mjs new file mode 100644 index 00000000000..93a0630c45a --- /dev/null +++ b/tools/terminal-garble-frame-analysis.mjs @@ -0,0 +1,155 @@ +import { PNG } from 'pngjs' + +function colorKey(red, green, blue) { + return `${red >> 3},${green >> 3},${blue >> 3}` +} + +export function analyzeTerminalFrame(buffer, geometry, viewport, terminalSnapshot) { + const png = PNG.sync.read(buffer) + const scaleX = png.width / viewport.width + const scaleY = png.height / viewport.height + const metrics = [] + + for (const pane of geometry) { + const bounds = pane.bounds + const x0 = Math.max(0, Math.floor(bounds.x * scaleX)) + const y0 = Math.max(0, Math.floor(bounds.y * scaleY)) + const x1 = Math.min(png.width, Math.ceil((bounds.x + bounds.width) * scaleX)) + const y1 = Math.min(png.height, Math.ceil((bounds.y + bounds.height) * scaleY)) + if (x0 >= x1 || y0 >= y1) { + throw new Error(`Terminal pane ${pane.index} falls outside the captured screenshot`) + } + const histogram = new Map() + for (let y = y0; y < y1; y += 4) { + for (let x = x0; x < x1; x += 4) { + const offset = (y * png.width + x) * 4 + const key = colorKey(png.data[offset], png.data[offset + 1], png.data[offset + 2]) + histogram.set(key, (histogram.get(key) ?? 0) + 1) + } + } + const backgroundEntry = [...histogram.entries()].sort((a, b) => b[1] - a[1])[0] + if (!backgroundEntry) { + throw new Error(`Terminal pane ${pane.index} has no pixels available for analysis`) + } + const background = backgroundEntry[0] + const [br, bg, bb] = background.split(',').map((part) => Number(part) * 8 + 4) + let different = 0 + let sampled = 0 + for (let y = y0; y < y1; y += 2) { + for (let x = x0; x < x1; x += 2) { + const offset = (y * png.width + x) * 4 + const distance = + Math.abs(png.data[offset] - br) + + Math.abs(png.data[offset + 1] - bg) + + Math.abs(png.data[offset + 2] - bb) + if (distance > 54) { + different++ + } + sampled++ + } + } + const terminalPane = terminalSnapshot.panes[pane.index] + const missingCells = [] + let textCells = 0 + if (terminalPane?.textCells) { + for (let row = 0; row < terminalPane.textCells.length; row++) { + for (const [column, chars] of terminalPane.textCells[row]) { + const cellX0 = Math.max(x0, Math.floor((bounds.x + column * pane.cell.width) * scaleX)) + const cellY0 = Math.max(y0, Math.floor((bounds.y + row * pane.cell.height) * scaleY)) + const cellX1 = Math.min( + x1, + Math.ceil((bounds.x + (column + 1) * pane.cell.width) * scaleX) + ) + const cellY1 = Math.min(y1, Math.ceil((bounds.y + (row + 1) * pane.cell.height) * scaleY)) + let maxDistance = 0 + const insetX = Math.max(1, Math.floor((cellX1 - cellX0) * 0.1)) + const insetY = Math.max(1, Math.floor((cellY1 - cellY0) * 0.1)) + for (let y = cellY0 + insetY; y < cellY1 - insetY; y++) { + for (let x = cellX0 + insetX; x < cellX1 - insetX; x++) { + const offset = (y * png.width + x) * 4 + const distance = + Math.abs(png.data[offset] - br) + + Math.abs(png.data[offset + 1] - bg) + + Math.abs(png.data[offset + 2] - bb) + maxDistance = Math.max(maxDistance, distance) + } + } + textCells++ + if (maxDistance < 36) { + missingCells.push(`${row}:${column}:${chars}`) + } + } + } + } + metrics.push({ + pane: pane.index, + background, + nonBackgroundRatio: sampled ? different / sampled : 0, + textCells, + missing: missingCells.length, + missPct: textCells ? (100 * missingCells.length) / textCells : 0, + missingCells + }) + } + return metrics +} + +export function findPersistentCellDivergences(attempts) { + const suspects = [] + for (const attempt of attempts) { + const baseline = new Map(attempt.baselinePanes.map((pane) => [pane.pane, pane.missPct])) + const histories = new Map() + const reported = new Set() + for (const frame of attempt.frames) { + for (const pane of frame.panes) { + const baselineMissPct = baseline.get(pane.pane) ?? pane.missPct + if (pane.textCells < 40 || pane.missPct < Math.max(8, baselineMissPct + 6)) { + histories.delete(pane.pane) + continue + } + const coordinates = new Set( + pane.missingCells.map((cell) => cell.split(':').slice(0, 2).join(':')) + ) + const history = histories.get(pane.pane) ?? [] + history.push({ file: frame.file, coordinates, pane }) + while (history.length > 3) { + history.shift() + } + histories.set(pane.pane, history) + if (history.length < 3 || reported.has(pane.pane)) { + continue + } + let persistent = true + for (let index = 1; index < history.length; index++) { + const before = history[index - 1].coordinates + const after = history[index].coordinates + let intersection = 0 + for (const coordinate of after) { + if (before.has(coordinate)) { + intersection++ + } + } + const union = before.size + after.size - intersection + if (!union || intersection / union < 0.5) { + persistent = false + break + } + } + if (persistent) { + reported.add(pane.pane) + suspects.push({ + attempt: attempt.attempt, + file: frame.file, + pane: pane.pane, + baselineMissPct, + observedMissPct: pane.missPct, + textCells: pane.textCells, + missing: pane.missing, + persistentFrames: history.map((entry) => entry.file) + }) + } + } + } + } + return suspects +} diff --git a/tools/terminal-garble-production-repro.mjs b/tools/terminal-garble-production-repro.mjs new file mode 100644 index 00000000000..b28056c3e0c --- /dev/null +++ b/tools/terminal-garble-production-repro.mjs @@ -0,0 +1,575 @@ +#!/usr/bin/env node + +import { _electron as electron } from '@stablyai/playwright-test' +import { + cpSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync +} from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { + createTerminalTab as createPackagedTerminalTab, + dismissOverlays, + ensureTerminal, + focusActiveTerminal +} from './win-update-e2e/app-driver.mjs' +import { createSeededRepo } from './win-update-e2e/onboarding-profile.mjs' +import { recoverProductionTerminalRefs } from './terminal-garble-react-terminal-recovery.mjs' +import { + analyzeTerminalFrame, + findPersistentCellDivergences +} from './terminal-garble-frame-analysis.mjs' + +const DEFAULT_EXECUTABLE = '/Applications/Orca.app/Contents/MacOS/Orca' +const DEFAULT_PROFILE = path.join(os.homedir(), 'Library', 'Application Support', 'orca') +const URL = 'https://example.com/orca-terminal-garble-repro' +const MODIFIER = process.platform === 'darwin' ? 'Meta' : 'Control' +const replayRoot = path.join(os.tmpdir(), 'garble-rig') +const REPLAY_A = path.join(replayRoot, 'frames-A.jsonl') +const REPLAY_B = path.join(replayRoot, 'frames-B.jsonl') +const REPLAY_SCRIPT = path.resolve('tools/terminal-garble-session-replay.mjs') +const PANE_COUNT = Number(argValue('--panes', '2')) +const TAB_COUNT = Number(argValue('--tabs', '1')) +const CLICK_COUNT = Number(argValue('--clicks', '3')) +const CAPTURE_INTERVAL_MS = 250 +const CAPTURE_WINDOW_MS = Number(argValue('--capture-ms', '12000')) +const WARMUP_MS = Number(argValue('--warmup-ms', '8000')) +const APPEARANCE = argValue('--appearance', 'dark') +const glyphChurn = process.argv.includes('--glyph-churn') + +function argValue(name, fallback) { + const prefix = `${name}=` + return process.argv.find((arg) => arg.startsWith(prefix))?.slice(prefix.length) ?? fallback +} + +function quoteShellArgument(value) { + if (process.platform === 'win32') { + return `"${value.replaceAll('"', '""')}"` + } + return `'${value.replaceAll("'", `'\\''`)}'` +} + +const executablePath = argValue('--executable', DEFAULT_EXECUTABLE) +const mainPath = argValue('--main', '') +const sourceProfile = argValue('--profile', DEFAULT_PROFILE) +const keepProfile = process.argv.includes('--keep-profile') +const allowOpenUrl = process.argv.includes('--allow-open-url') +const stubOpenUrl = !allowOpenUrl || process.argv.includes('--stub-open-url') +const stubOpenUrlAfterFirst = process.argv.includes('--stub-open-url-after-first') +const focusChurn = process.argv.includes('--focus-churn') +const runRoot = mkdtempSync(path.join(os.tmpdir(), 'orca-terminal-garble-')) +const userDataDir = path.join(runRoot, 'user-data') +const evidenceDir = path.join(runRoot, 'evidence') +mkdirSync(userDataDir, { recursive: true }) +mkdirSync(evidenceDir, { recursive: true }) +function sanitizeProfile() { + const sourceDataPath = path.join(sourceProfile, 'orca-data.json') + const data = JSON.parse(readFileSync(sourceDataPath, 'utf8')) + const repo = createSeededRepo(path.join(runRoot, 'fixture-repo')) + + // Why: use the real terminal settings and repo while omitting every persisted + // session/automation field that could duplicate work from the live profile. + const profile = { + settings: { ...data.settings, theme: APPEARANCE }, + onboarding: { + flowVersion: 4, + closedAt: Date.now(), + outcome: 'completed', + lastCompletedStep: 5 + }, + repos: [repo] + } + const serialized = `${JSON.stringify(profile)}\n` + writeFileSync(path.join(userDataDir, 'orca-data.json'), serialized) + + for (const file of ['Preferences', 'Local State']) { + const source = path.join(sourceProfile, file) + if (existsSync(source)) { + cpSync(source, path.join(userDataDir, file)) + } + } +} +async function exposeProductionTerminals(page) { + return page.evaluate(recoverProductionTerminalRefs) +} +async function runInActivePane(page, command) { + const activePane = page.locator('.pane:has([data-active-pane]):visible').last() + if (await activePane.isVisible().catch(() => false)) { + await activePane.click({ position: { x: 20, y: 40 } }) + await activePane.locator('.xterm-helper-textarea').last().focus() + } else { + await focusActiveTerminal(page) + } + await page.keyboard.type(command, { delay: 1 }) + await page.keyboard.press('Enter') +} +async function configurePanes(page) { + await page.waitForTimeout(1_000) + await dismissOverlays(page) + await ensureTerminal(page, { allowCreate: true, timeoutMs: 90_000 }) + await dismissOverlays(page) + for (let tab = 0; tab < TAB_COUNT; tab++) { + if (tab > 0) { + await createPackagedTerminalTab(page) + } + for (let pane = 0; pane < PANE_COUNT; pane++) { + const frames = (tab + pane) % 2 === 0 ? REPLAY_A : REPLAY_B + const args = [ + quoteShellArgument(REPLAY_SCRIPT), + quoteShellArgument(frames), + '--loop', + `--tick=${250 + ((tab + pane) % 5) * 20}`, + `--url=${quoteShellArgument(URL)}` + ] + if (glyphChurn) { + args.push(`--glyph-churn=${tab * PANE_COUNT + pane}`) + } + await runInActivePane(page, `node ${args.join(' ')}`) + if (pane + 1 < PANE_COUNT) { + await page.keyboard.press(pane % 2 === 0 ? `${MODIFIER}+d` : `${MODIFIER}+Shift+d`) + await page.waitForTimeout(500) + } + } + } + await page.waitForTimeout(WARMUP_MS) +} +async function paneGeometry(page) { + return page.evaluate(() => { + const exposedManagers = window.__paneManagers + if (exposedManagers instanceof Map) { + const managed = [] + for (const manager of exposedManagers.values()) { + for (const pane of manager.getPanes?.() ?? []) { + const screen = pane.container?.querySelector('.xterm-screen') + const bounds = screen?.getBoundingClientRect() + if (!bounds || bounds.width <= 0 || bounds.height <= 0) { + continue + } + managed.push({ + index: managed.length, + paneId: pane.id, + bounds: { + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height + }, + cell: { + width: bounds.width / pane.terminal.cols, + height: bounds.height / pane.terminal.rows + }, + canvases: screen.querySelectorAll('canvas').length, + cols: pane.terminal.cols, + rows: pane.terminal.rows + }) + } + } + if (managed.length > 0) { + return managed + } + } + const recovered = window.__terminalGarbleTerminals + if (Array.isArray(recovered)) { + const managed = recovered + .filter((terminal) => terminal.element?.offsetWidth && terminal.element?.offsetHeight) + .map((terminal, index) => { + const screen = terminal.element?.querySelector('.xterm-screen') + const bounds = screen?.getBoundingClientRect() + if (!bounds || bounds.width <= 0 || bounds.height <= 0) { + return null + } + return { + index, + bounds: { + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height + }, + cell: { + width: bounds.width / terminal.cols, + height: bounds.height / terminal.rows + }, + canvases: screen.querySelectorAll('canvas').length, + cols: terminal.cols, + rows: terminal.rows + } + }) + .filter(Boolean) + if (managed.length > 0) { + return managed + } + } + const visible = (element) => { + const rect = element.getBoundingClientRect() + return rect.width > 0 && rect.height > 0 + } + return Array.from(document.querySelectorAll('.xterm-screen')) + .filter(visible) + .map((screen, index) => { + const xterm = screen.closest('.xterm') + const screenRect = screen.getBoundingClientRect() + const fontSize = Number.parseFloat(getComputedStyle(xterm ?? screen).fontSize) || 13 + return { + index, + bounds: { + x: screenRect.x, + y: screenRect.y, + width: screenRect.width, + height: screenRect.height + }, + cell: { width: fontSize * 0.6, height: fontSize * 1.2 }, + canvases: screen.querySelectorAll('canvas').length + } + }) + .filter((pane) => pane.cell.width > 0 && pane.cell.height > 0) + }) +} +async function terminalState(page) { + return page.evaluate(() => { + const managers = window.__paneManagers + const recovered = window.__terminalGarbleTerminals + if (!(managers instanceof Map) && !Array.isArray(recovered)) { + return { exposed: false, focused: document.hasFocus(), panes: [] } + } + const terminalPanes = [] + if (managers instanceof Map) { + for (const manager of managers.values()) { + terminalPanes.push(...(manager.getPanes?.() ?? [])) + } + } else { + terminalPanes.push(...recovered.map((terminal, index) => ({ id: index, terminal }))) + } + const panes = [] + for (const pane of terminalPanes) { + const paneRect = pane.container?.getBoundingClientRect() + const terminalRect = pane.terminal?.element?.getBoundingClientRect() + if ( + (!paneRect || paneRect.width <= 0 || paneRect.height <= 0) && + (!terminalRect || terminalRect.width <= 0 || terminalRect.height <= 0) + ) { + continue + } + const terminal = pane.terminal + const buffer = terminal.buffer.active + const lines = [] + const textCells = [] + for (let row = 0; row < terminal.rows; row++) { + const line = buffer.getLine(buffer.viewportY + row) + lines.push(line?.translateToString(true) ?? '') + const rowCells = [] + for (let column = 0; column < terminal.cols; column++) { + const cell = line?.getCell(column) + const chars = cell?.getChars() ?? '' + if (chars !== '' && chars !== ' ' && cell?.getWidth() !== 0) { + rowCells.push([column, chars]) + } + } + textCells.push(rowCells) + } + const renderService = terminal._core?._renderService + const renderer = renderService?._renderer?.value + panes.push({ + paneId: pane.id, + cols: terminal.cols, + rows: terminal.rows, + viewportY: buffer.viewportY, + paused: renderService?._isPaused === true, + needsFullRefresh: renderService?._needsFullRefresh === true, + atlasPages: renderer?._charAtlas?.pages?.length ?? -1, + bufferText: lines.join('\n'), + textCells + }) + } + return { exposed: true, focused: document.hasFocus(), panes } + }) +} + +async function installOpenUrlStub(electronApp) { + return electronApp.evaluate(({ BrowserWindow, shell }, shouldChurnFocus) => { + const original = shell.openExternal + globalThis.__terminalGarbleStubbedUrls ??= [] + shell.openExternal = async (url) => { + globalThis.__terminalGarbleStubbedUrls.push({ url, at: Date.now() }) + if (shouldChurnFocus) { + globalThis.__terminalGarbleFocusSink?.destroy() + const sink = new BrowserWindow({ + width: 160, + height: 100, + x: -10_000, + y: -10_000, + show: false + }) + globalThis.__terminalGarbleFocusSink = sink + await sink.loadURL('data:text/html,focus-sink') + sink.show() + sink.focus() + } + } + return { installed: shell.openExternal !== original } + }, focusChurn) +} + +async function releaseFocusSink(electronApp) { + return electronApp.evaluate(() => { + const sink = globalThis.__terminalGarbleFocusSink + const existed = Boolean(sink && !sink.isDestroyed()) + sink?.destroy() + globalThis.__terminalGarbleFocusSink = null + return existed + }) +} + +async function installOpenUrlRecorder(electronApp) { + return electronApp.evaluate(({ shell }) => { + const original = shell.openExternal.bind(shell) + globalThis.__terminalGarbleOpenedUrls = [] + shell.openExternal = async (url, options) => { + globalThis.__terminalGarbleOpenedUrls.push({ url, at: Date.now() }) + return original(url, options) + } + }) +} + +async function readOpenUrlCalls(electronApp) { + return electronApp.evaluate(() => ({ + opened: globalThis.__terminalGarbleOpenedUrls ?? [], + stubbed: globalThis.__terminalGarbleStubbedUrls ?? [] + })) +} + +async function clickUrlAndCapture(electronApp, page, geometry, viewport, attempt) { + const targetPane = geometry[attempt % geometry.length] + const { bounds, cell } = targetPane + const target = { + x: bounds.x + cell.width * 12, + y: bounds.y + cell.height * 0.5 + } + const attemptDir = path.join(evidenceDir, `attempt-${attempt + 1}`) + mkdirSync(attemptDir, { recursive: true }) + + await page.waitForFunction( + ({ paneIndex, url }) => { + const visible = (window.__terminalGarbleTerminals ?? []).filter( + (terminal) => terminal.element?.offsetWidth && terminal.element?.offsetHeight + ) + const terminal = visible[paneIndex] + const line = terminal?.buffer?.active?.getLine(terminal.buffer.active.viewportY) + return line?.translateToString(true).startsWith(url) === true + }, + { paneIndex: targetPane.index, url: URL }, + { timeout: 10_000 } + ) + + const beforeState = await terminalState(page) + const before = await page.screenshot() + const baselinePanes = analyzeTerminalFrame(before, geometry, viewport, beforeState) + writeFileSync(path.join(attemptDir, 'before.png'), before) + writeFileSync( + path.join(attemptDir, 'state-before.json'), + `${JSON.stringify(beforeState, null, 2)}\n` + ) + const initialCalls = await readOpenUrlCalls(electronApp) + const initialCallCount = initialCalls.opened.length + initialCalls.stubbed.length + let hoverState = null + let activationAttempts = 0 + let activated = false + while (!activated && activationAttempts < 3) { + activationAttempts++ + await page.waitForFunction( + ({ paneIndex, url }) => { + const visible = (window.__terminalGarbleTerminals ?? []).filter( + (terminal) => terminal.element?.offsetWidth && terminal.element?.offsetHeight + ) + const terminal = visible[paneIndex] + const line = terminal?.buffer?.active?.getLine(terminal.buffer.active.viewportY) + return line?.translateToString(true).startsWith(url) === true + }, + { paneIndex: targetPane.index, url: URL }, + { timeout: 10_000 } + ) + await page.mouse.move(target.x, target.y) + await page + .waitForFunction( + ({ x, y }) => + document + .elementsFromPoint(x, y) + .some((element) => element.classList?.contains('xterm-cursor-pointer')), + target, + { timeout: 800 } + ) + .catch(() => false) + hoverState = await page.evaluate(({ x, y }) => { + const elements = document.elementsFromPoint(x, y).map((element) => ({ + tag: element.tagName, + className: String(element.className), + title: element.getAttribute('title') + })) + return { + elements, + decorations: document.querySelectorAll('.xterm-decoration').length + } + }, target) + await page.keyboard.down(MODIFIER) + await page.mouse.click(target.x, target.y) + await page.keyboard.up(MODIFIER) + await page.waitForTimeout(300) + const calls = await readOpenUrlCalls(electronApp) + activated = calls.opened.length + calls.stubbed.length > initialCallCount + } + if (!activated) { + throw new Error(`OSC-8 URL did not activate after ${activationAttempts} verified gestures`) + } + await page.waitForTimeout(250) + const focusTransition = { + rendererFocused: await page.evaluate(() => document.hasFocus()), + ...(await electronApp.evaluate(({ BrowserWindow }) => { + const sink = globalThis.__terminalGarbleFocusSink + const focusedWindow = BrowserWindow.getFocusedWindow() + return { + sinkFocused: Boolean(sink && !sink.isDestroyed() && sink.isFocused()), + focusedWindowIsSink: Boolean(sink && focusedWindow === sink) + } + })) + } + const focusSinkReleased = await releaseFocusSink(electronApp) + await page.bringToFront() + await page.waitForTimeout(500) + + const frames = [] + const deadline = Date.now() + CAPTURE_WINDOW_MS + let frame = 0 + while (Date.now() < deadline) { + const png = await page.screenshot() + const state = await terminalState(page) + const file = `frame-${String(frame).padStart(3, '0')}.png` + writeFileSync(path.join(attemptDir, file), png) + frames.push({ + file, + at: Date.now(), + panes: analyzeTerminalFrame(png, geometry, viewport, state) + }) + frame++ + await page.waitForTimeout(CAPTURE_INTERVAL_MS) + } + writeFileSync( + path.join(attemptDir, 'state-after.json'), + `${JSON.stringify(await terminalState(page), null, 2)}\n` + ) + writeFileSync(path.join(attemptDir, 'metrics.json'), `${JSON.stringify(frames, null, 2)}\n`) + return { + attempt: attempt + 1, + targetPane: targetPane.index, + activationAttempts, + focusSinkReleased, + hoverState, + focusTransition, + baselinePanes, + frames + } +} + +sanitizeProfile() +let app +let page +let summary +try { + const { ELECTRON_RUN_AS_NODE: _drop, ...cleanEnv } = process.env + const launchTarget = mainPath ? { args: [path.resolve(mainPath)] } : { executablePath } + app = await electron.launch({ + ...launchTarget, + env: { + ...cleanEnv, + ORCA_E2E_USER_DATA_DIR: userDataDir, + ...(mainPath ? { NODE_ENV: 'development', ORCA_E2E_HEADFUL: '1' } : {}) + } + }) + page = await app.firstWindow({ timeout: 120_000 }) + await page.waitForLoadState('domcontentloaded') + await page.keyboard.press('Escape') + await configurePanes(page) + const recoveredTerminals = await exposeProductionTerminals(page) + const geometry = await paneGeometry(page) + const oracleState = await terminalState(page) + const viewport = await page.evaluate(() => ({ + width: document.documentElement.clientWidth, + height: document.documentElement.clientHeight + })) + if (geometry.length < PANE_COUNT) { + throw new Error(`Expected ${PANE_COUNT} visible panes, found ${geometry.length}`) + } + if (oracleState.panes.length < geometry.length) { + throw new Error( + `Terminal buffer recovery found ${oracleState.panes.length} panes for ${geometry.length} captured panes` + ) + } + writeFileSync(path.join(evidenceDir, 'geometry.json'), `${JSON.stringify(geometry, null, 2)}\n`) + + await installOpenUrlRecorder(app) + const attempts = [] + let openUrlStub = stubOpenUrl ? await installOpenUrlStub(app) : null + for (let attempt = 0; attempt < CLICK_COUNT; attempt++) { + attempts.push(await clickUrlAndCapture(app, page, geometry, viewport, attempt)) + if (attempt === 0 && stubOpenUrlAfterFirst) { + openUrlStub = await installOpenUrlStub(app) + if (!openUrlStub.installed && CLICK_COUNT > 1) { + throw new Error(`Could not stub openUrl after the verified click: ${openUrlStub.reason}`) + } + } + } + const suspects = findPersistentCellDivergences(attempts) + const openUrlCalls = await readOpenUrlCalls(app) + summary = { + executablePath: mainPath ? path.resolve(mainPath) : executablePath, + runRoot, + viewport, + geometry, + recoveredTerminals, + focusChurn, + glyphChurn, + openUrlStub, + openUrlCalls, + attempts: attempts.map((attempt) => ({ + attempt: attempt.attempt, + targetPane: attempt.targetPane, + activationAttempts: attempt.activationAttempts, + focusSinkReleased: attempt.focusSinkReleased, + hoverState: attempt.hoverState, + focusTransition: attempt.focusTransition, + frameCount: attempt.frames.length + })), + suspects + } + writeFileSync(path.join(evidenceDir, 'summary.json'), `${JSON.stringify(summary, null, 2)}\n`) + console.log(JSON.stringify(summary, null, 2)) +} catch (error) { + if (page) { + await page.screenshot({ path: path.join(evidenceDir, 'failure.png') }).catch(() => {}) + const diagnostic = await page + .evaluate(() => ({ + title: document.title, + url: location.href, + text: document.body?.innerText.slice(0, 10_000) ?? '', + buttons: Array.from(document.querySelectorAll('button,[role="button"]')) + .map((element) => element.getAttribute('aria-label') || element.textContent?.trim()) + .filter(Boolean) + })) + .catch(() => null) + writeFileSync( + path.join(evidenceDir, 'failure.json'), + `${JSON.stringify({ error: String(error), diagnostic }, null, 2)}\n` + ) + } + throw error +} finally { + await app?.close().catch(() => {}) + if (!keepProfile) { + rmSync(userDataDir, { recursive: true, force: true }) + } + console.error(`[terminal-garble] evidence: ${evidenceDir}`) +} diff --git a/tools/terminal-garble-react-terminal-recovery.mjs b/tools/terminal-garble-react-terminal-recovery.mjs new file mode 100644 index 00000000000..4e40540728e --- /dev/null +++ b/tools/terminal-garble-react-terminal-recovery.mjs @@ -0,0 +1,98 @@ +export function recoverProductionTerminalRefs() { + const terminals = [] + const seenTerminals = new Set() + const seenObjects = new Set() + + const remember = (candidate) => { + try { + if ( + candidate && + typeof candidate === 'object' && + typeof candidate.rows === 'number' && + typeof candidate.cols === 'number' && + typeof candidate.buffer?.active?.getLine === 'function' && + candidate.element instanceof HTMLElement && + !seenTerminals.has(candidate) + ) { + seenTerminals.add(candidate) + terminals.push(candidate) + return true + } + if ( + candidate && + typeof candidate.getPanes === 'function' && + typeof candidate.getActivePane === 'function' + ) { + for (const pane of candidate.getPanes() ?? []) { + remember(pane?.terminal) + } + return true + } + } catch { + // Production objects can expose disposed getters while React cleans up. + } + return false + } + + const inspect = (candidate, depth) => { + if (!candidate || typeof candidate !== 'object' || seenObjects.has(candidate)) { + return + } + seenObjects.add(candidate) + if (remember(candidate) || depth <= 0 || candidate instanceof Node) { + return + } + if (candidate instanceof Map || candidate instanceof Set) { + for (const value of candidate.values()) { + inspect(value, depth - 1) + } + return + } + for (const key of Object.keys(candidate).slice(0, 80)) { + if (key === 'return' || key === 'child' || key === 'sibling' || key.startsWith('__react')) { + continue + } + try { + inspect(candidate[key], depth - 1) + } catch { + // Treat opaque host objects as leaves. + } + } + } + + for (const xterm of document.querySelectorAll('.xterm')) { + let root = xterm + let fiberKey = null + while (root && !fiberKey) { + fiberKey = Object.keys(root).find( + (key) => key.startsWith('__reactFiber$') || key.startsWith('__reactInternalInstance$') + ) + if (!fiberKey) { + root = root.parentElement + } + } + let fiber = fiberKey && root ? root[fiberKey] : null + let ancestorCount = 0 + while (fiber && ancestorCount < 80) { + let hook = fiber.memoizedState + let hookCount = 0 + while (hook && hookCount < 600) { + inspect(hook.memoizedState, 5) + hook = hook.next + hookCount++ + } + inspect(fiber.memoizedProps, 3) + fiber = fiber.return + ancestorCount++ + } + } + + // Why: packaged builds omit the E2E manager exposure, but a buffer oracle + // is required to distinguish real render corruption from valid TUI rewrites. + window.__terminalGarbleTerminals = terminals + return terminals.map((terminal) => ({ + cols: terminal.cols, + rows: terminal.rows, + visible: Boolean(terminal.element?.offsetWidth && terminal.element?.offsetHeight) + })) +} diff --git a/tools/terminal-garble-session-replay.mjs b/tools/terminal-garble-session-replay.mjs new file mode 100644 index 00000000000..9705f9e863c --- /dev/null +++ b/tools/terminal-garble-session-replay.mjs @@ -0,0 +1,79 @@ +#!/usr/bin/env node + +import { readFileSync } from 'node:fs' + +const file = process.argv[2] +const loop = process.argv.includes('--loop') +const tickMs = Number( + (process.argv.find((arg) => arg.startsWith('--tick=')) ?? '--tick=12').split('=')[1] +) +if (!Number.isFinite(tickMs) || tickMs <= 0) { + throw new Error('--tick must be a positive number of milliseconds') +} +const url = ( + process.argv.find((arg) => arg.startsWith('--url=')) ?? + '--url=https://example.com/orca-terminal-garble-repro' +).slice('--url='.length) +const glyphChurnSeed = Number( + (process.argv.find((arg) => arg.startsWith('--glyph-churn=')) ?? '--glyph-churn=-1').split('=')[1] +) +const frames = readFileSync(file, 'utf8') + .split('\n') + .filter(Boolean) + .map((line) => JSON.parse(line)) +const bytes = Buffer.concat( + frames.filter((frame) => frame.o).map((frame) => Buffer.from(frame.o, 'base64')) +) +const marker = Buffer.from('\x1b[?2026h') +const chunks = [] +let position = 0 + +while (position < bytes.length) { + let next = bytes.indexOf(marker, position + 1) + if (next === -1) { + next = bytes.length + } + chunks.push(bytes.subarray(position, next)) + position = next +} + +const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)) +const overlay = Buffer.from( + `\x1b7\x1b[1;1H\x1b]8;;${url}\x07\x1b[0;36;4m${url}\x1b[0m\x1b]8;;\x07\x1b[K\x1b8` +) +let glyphEpoch = 0 + +function glyphChurnOverlay() { + if (glyphChurnSeed < 0) { + return null + } + let output = '\x1b7' + for (let row = 2; row < 14; row++) { + output += `\x1b[${row};1H` + for (let column = 0; column < 66; column++) { + const key = glyphChurnSeed * 4099 + glyphEpoch * 997 + row * 67 + column + const red = (key * 29) & 255 + const green = (key * 71) & 255 + const blue = (key * 131) & 255 + const glyph = String.fromCharCode(33 + (key % 94)) + output += `\x1b[38;2;${red};${green};${blue}m${glyph}` + } + } + glyphEpoch++ + return Buffer.from(`${output}\x1b[0m\x1b8`) +} + +do { + for (const chunk of chunks) { + process.stdout.write(chunk) + const churn = glyphChurnOverlay() + if (churn) { + process.stdout.write(churn) + } + // Why: keep the real click trigger visible while replaying the captured + // TUI frames; save/restore preserves the session's cursor state. + process.stdout.write(overlay) + await sleep(tickMs) + } + await sleep(300) +} while (loop) From 613dcd8c9b5fb28a95d34df8e97367a9471f5492 Mon Sep 17 00:00:00 2001 From: Trevin Chow Date: Fri, 17 Jul 2026 16:39:02 -0700 Subject: [PATCH 014/137] fix(pr-comments): make the review sidebar legible for structured bot markdown (#9209) Restore readable block flow, typography, nested spacing, and heading semantics for structured review Markdown in the PR sidebar. Preserve the compact renderer's existing remote-image trust boundary. --- .../pr-comment-presentation.test.ts | 72 +++++++++++++++++-- .../right-sidebar/pr-comment-presentation.ts | 37 ++++++++-- .../sidebar/CommentMarkdown.test.tsx | 27 +++++++ .../comment-markdown-element-renderers.tsx | 40 ++++++++--- 4 files changed, 158 insertions(+), 18 deletions(-) diff --git a/src/renderer/src/components/right-sidebar/pr-comment-presentation.test.ts b/src/renderer/src/components/right-sidebar/pr-comment-presentation.test.ts index b85c7085dd9..388ad914dd0 100644 --- a/src/renderer/src/components/right-sidebar/pr-comment-presentation.test.ts +++ b/src/renderer/src/components/right-sidebar/pr-comment-presentation.test.ts @@ -15,8 +15,8 @@ describe('pr-comment-presentation', () => { it('returns card layout tokens for cards and focus variants', () => { const cards = getPRCommentPresentationClasses('cards') expect(cards.useCardLayout).toBe(true) - expect(cards.commentBody).toContain('text-xs') - expect(cards.commentBody).toContain('leading-5') + expect(cards.commentBody).toContain('text-[13px]') + expect(cards.commentBody).toContain('leading-relaxed') expect(cards.commentBody).toContain('text-foreground') expect(cards.group).toContain('bg-secondary') expect(cards.group).toContain('shadow-xs') @@ -25,10 +25,10 @@ describe('pr-comment-presentation', () => { const focus = getPRCommentPresentationClasses('focus') expect(focus.useCardLayout).toBe(true) - expect(focus.commentBody).toContain('text-xs') - expect(focus.commentBody).toContain('leading-5') - expect(focus.commentBodyReply).toContain('text-xs') - expect(focus.commentBodyReply).toContain('leading-5') + expect(focus.commentBody).toContain('text-[13px]') + expect(focus.commentBody).toContain('leading-relaxed') + expect(focus.commentBodyReply).toContain('text-[13px]') + expect(focus.commentBodyReply).toContain('leading-relaxed') expect(focus.author).toContain('text-[13px]') expect(focus.list).toContain('gap-2') expect(focus.commentBody).toContain('px-4 py-2.5') @@ -39,6 +39,66 @@ describe('pr-comment-presentation', () => { expect(focus.commentHeaderMetaWithSelection).toContain('pl-[3.25rem]') }) + it('restores block flow for span-rendered markdown paragraphs and headings', () => { + for (const variant of ['cards', 'focus', 'flat'] as const) { + const markdown = getPRCommentPresentationClasses(variant).commentBodyMarkdown + // The compact renderer emits no

, so a [&_p] rule would be dead code. + expect(markdown).not.toContain('[&_p]:') + expect(markdown).toContain('[&_.comment-md-p]:block') + expect(markdown).toContain('[&_.comment-md-p+.comment-md-p]:mt-2') + // Rhythm must key off every top-level block: a

/
between two + // paragraphs breaks an adjacent-sibling chain and zeroes the tail spacing. + expect(markdown).toContain('[&>*+*]:mt-2') + expect(markdown).toContain('[&_details]:my-2') + expect(markdown).toContain('[&_.comment-md-h]:block') + expect(markdown).toContain('[&_.comment-md-h]:mt-4') + } + }) + + it('lifts code, pre, and table off the 10px compact defaults', () => { + const markdown = getPRCommentPresentationClasses('cards').commentBodyMarkdown + expect(markdown).toContain('[&_code]:text-[0.92em]') + expect(markdown).toContain('[&_pre]:text-xs') + // Code inside a fence must track the fence, not shrink again. + expect(markdown).toContain('[&_pre_code]:text-[1em]') + expect(markdown).toContain('[&_table]:text-[12px]') + }) + + it('stops sub/sup small-print from compounding below the caption tier', () => { + const markdown = getPRCommentPresentationClasses('cards').commentBodyMarkdown + // The browser's 75% nests: 13px -> 9.75px -> 7.3px. Absolute sizes don't compound. + expect(markdown).toContain('[&_sub]:text-[11px]') + expect(markdown).toContain('[&_sup]:text-[11px]') + }) + + it('neutralizes the baseline sag only for sub/sup wrapping a badge image', () => { + const markdown = getPRCommentPresentationClasses('cards').commentBodyMarkdown + // stacks two -0.25em shifts; text subscripts keep theirs. + expect(markdown).toContain('[&_sub:has(img)]:bottom-0') + expect(markdown).toContain('[&_sub:has(img)]:align-middle') + expect(markdown).not.toContain('[&_sub]:bottom-0') + }) + + it('gives headings a perceptible step over the 13px body', () => { + const markdown = getPRCommentPresentationClasses('cards').commentBodyMarkdown + // 13px headings over a 12px body was a 1.08x ratio — invisible. Match the + // document variant's scale instead. + expect(markdown).toContain('[&_.comment-md-h1]:text-[18px]') + expect(markdown).toContain('[&_.comment-md-h2]:text-[16px]') + expect(markdown).toContain('[&_.comment-md-h3]:text-[15px]') + expect(markdown).not.toContain('[&_.comment-md-h1]:text-[13px]') + }) + + it('promotes bold-then-linebreak labels but not mid-sentence emphasis', () => { + const markdown = getPRCommentPresentationClasses('cards').commentBodyMarkdown + const label = '[&_.comment-md-p>strong:first-child:has(+br)' + expect(markdown).toContain(`${label}]:text-[15px]`) + expect(markdown).toContain(`${label}]:block`) + expect(markdown).toContain('[&_.comment-md-p:first-child>strong:first-child:has(+br)]:mt-0') + // Blocking the label makes its own
a duplicate break. + expect(markdown).toContain(`${label}+br]:hidden`) + }) + it('preserves the legacy flat layout tokens', () => { const flat = getPRCommentPresentationClasses('flat') expect(flat.useCardLayout).toBe(false) diff --git a/src/renderer/src/components/right-sidebar/pr-comment-presentation.ts b/src/renderer/src/components/right-sidebar/pr-comment-presentation.ts index 6ce3560f6a1..aa754f61ecb 100644 --- a/src/renderer/src/components/right-sidebar/pr-comment-presentation.ts +++ b/src/renderer/src/components/right-sidebar/pr-comment-presentation.ts @@ -72,8 +72,36 @@ export function getPRCommentGroupSurfaceClasses( return classes.join(' ') } -const MARKDOWN_BASE = - 'break-words [&_p]:my-1.5 [&_pre]:max-h-none [&_pre]:max-w-full [&_pre]:whitespace-pre-wrap [&_table]:w-full [&_table]:max-w-full' +// Why: compact p/h spans weld onto one line; scope block flow here so the +// renderer's other consumers retain their dense layout. +const MARKDOWN_BASE = cn( + 'break-words', + // Why: rhythm keys off every top-level block, not adjacent paragraphs — a
or + // raw
between them otherwise breaks the chain and collapses the tail to zero gap. + '[&_.comment-md-p]:block [&_.comment-md-p+.comment-md-p]:mt-2 [&>*+*]:mt-2 [&_details]:my-2', + '[&_.comment-md-h]:mt-4 [&_.comment-md-h]:mb-1 [&_.comment-md-h]:block [&_.comment-md-h]:leading-tight [&_.comment-md-h:first-child]:mt-0', + // Why: the document variant's proven scale (18/16/15 over 13px body) — the sidebar was + // the only surface rendering this markdown with no perceptible heading step. + '[&_.comment-md-h1]:text-[18px] [&_.comment-md-h2]:text-[16px] [&_.comment-md-h3]:text-[15px]', + // Why: bots write section titles as bold-then-linebreak, not headings, so those carry + // the real hierarchy. :has(+br) promotes only those, never mid-sentence emphasis. + '[&_.comment-md-p>strong:first-child:has(+br)]:mt-3 [&_.comment-md-p>strong:first-child:has(+br)]:mb-0.5 [&_.comment-md-p>strong:first-child:has(+br)]:block [&_.comment-md-p>strong:first-child:has(+br)]:text-[15px] [&_.comment-md-p>strong:first-child:has(+br)]:leading-tight', + '[&_.comment-md-p:first-child>strong:first-child:has(+br)]:mt-0', + // The label's own
would double the break once it is block-level. + '[&_.comment-md-p>strong:first-child:has(+br)+br]:hidden', + // Why: compact sizes code/pre/tables at 10px inside the prose, shrinking the densest + // content. 0.92em mirrors the document variant's inline-code ratio. + '[&_code]:text-[0.92em] [&_pre]:text-xs [&_pre_code]:text-[1em] [&_table]:text-[12px]', + // Why: bots use sub/sup as small-print, and the browser's 75% compounds when nested + // (13px -> 7.3px). An absolute size stops the compounding at the caption tier. + '[&_sub]:text-[11px] [&_sup]:text-[11px]', + // Why: a badge wrapped in inherits both -0.25em shifts (5.5px of sag). + // Only neutralize wrappers holding an image; real text subscripts keep their offset. + '[&_sub:has(img)]:bottom-0 [&_sub:has(img)]:align-middle', + '[&_sup:has(img)]:top-0 [&_sup:has(img)]:align-middle', + '[&_pre]:max-h-none [&_pre]:max-w-full [&_pre]:whitespace-pre-wrap', + '[&_table]:w-full [&_table]:max-w-full' +) // Why: in light mode card and canvas are both #fff, so border-border alone disappears. // overflow-clip preserves rounded clipping without letting focused row actions scroll content. @@ -89,8 +117,9 @@ const COMMENT_AVATAR = const RESOLVED_SECTION_LABEL = 'text-[11px] font-semibold uppercase tracking-wider text-muted-foreground' -// Why: markdown bodies need the secondary sidebar scale; 13px relaxed copy reads oversized in narrow comment cards. -const CARD_COMMENT_BODY_SIZE = 'text-xs leading-5' +// Why: matches the document-variant body (PullRequestPage/GitHubItemDialog) so the same +// bot markdown reads the same everywhere; 12px left no room for a heading step above it. +const CARD_COMMENT_BODY_SIZE = 'text-[13px] leading-relaxed' const CARD_COMMENT_AUTHOR_SIZE = 'text-[13px]' const CARD_COMMENT_LIST_GAP = 'gap-2' const CARD_COMMENT_BODY_PADDING = 'px-4 py-2.5' diff --git a/src/renderer/src/components/sidebar/CommentMarkdown.test.tsx b/src/renderer/src/components/sidebar/CommentMarkdown.test.tsx index b64d10a0456..4ca6323a8a2 100644 --- a/src/renderer/src/components/sidebar/CommentMarkdown.test.tsx +++ b/src/renderer/src/components/sidebar/CommentMarkdown.test.tsx @@ -3,6 +3,33 @@ import { describe, expect, it } from 'vitest' import CommentMarkdown, { remarkGitHubReferences } from './CommentMarkdown' describe('CommentMarkdown', () => { + it('marks compact headings so a parent can opt into block flow', () => { + const markup = renderToStaticMarkup( + + ) + + expect(markup).toContain('comment-md-h comment-md-h2') + expect(markup).toContain('comment-md-p') + expect(markup).toContain('role="heading"') + expect(markup).toContain('aria-level="2"') + // Weight-only styling stays the compact default; block flow is opt-in. + expect(markup).toContain('font-bold') + }) + + it('marks adjacent compact paragraphs inside disclosure content', () => { + const markup = renderToStaticMarkup( + More\n\nFirst paragraph.\n\nSecond paragraph.\n\n
' + } + /> + ) + + expect(markup).toContain( + 'First paragraph.\nSecond paragraph.' + ) + }) + it('autolinks same-repo GitHub issue references when repo context is provided', () => { const markup = renderToStaticMarkup( (
  • {children}
  • ), - // Headings render as bold text at the same size — no visual hierarchy needed - // in a tiny sidebar card. - h1: ({ children }) => {children}, - h2: ({ children }) => {children}, - h3: ({ children }) => {children}, - h4: ({ children }) => {children}, - h5: ({ children }) => {children}, - h6: ({ children }) => {children}, + // Spans preserve compact flow on shared surfaces; roles keep the source + // heading hierarchy navigable when the PR sidebar promotes them visually. + h1: ({ children }) => ( + + {children} + + ), + h2: ({ children }) => ( + + {children} + + ), + h3: ({ children }) => ( + + {children} + + ), + h4: ({ children }) => ( + + {children} + + ), + h5: ({ children }) => ( + + {children} + + ), + h6: ({ children }) => ( + + {children} + + ), // Horizontal rules as a subtle divider hr: () =>
    , // Compact blockquotes From 5907816457b9ad88743274db09ac4e67235e9967 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:58:04 -0700 Subject: [PATCH 015/137] feat(keybindings): swap tab-switch chords to the common convention for new users (#9240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(keybindings): swap tab-switch chords to the common convention for new users New installs now get the widespread mapping — Mod+Shift+[ / ] cycles across all tabs, Mod+Alt+[ / ] cycles within the active tab type. Pre-existing installs keep today's mapping: a one-time cohort seed (frozen on first launch via the fileExistedOnLoad signal, mirroring the telemetry migration) pins the legacy chords into keybindings.json, skipping any action the user already customized. - shared registry: swap the four tab.*SameType / tab.*AllTypes defaults; export LEGACY_TAB_SWITCH_BINDINGS for the seed - persistence: migrateTabSwitchKeybindings freezes the existing-vs-fresh cohort (tabSwitchKeybindingSeed = pending | done) - keybinding-file: seedLegacyTabSwitchBindings writes the legacy pins into the active-platform section so Settings reset still works - refresh stale default-chord comments - tests for the swapped defaults, the seed (fresh/existing/customized/idempotent), and the cohort migration * test(keybindings): prove existing-user parity + make the seed strictly per-action seedLegacyTabSwitchBindings now pins each un-customized action individually instead of skipping all four when any one is customized. A partially-customized existing user keeps their rebound action AND the pre-swap default on the rest; no existing user's behavior changes. The skip check keys on this platform's effective overrides so a foreign-platform-only override can't leave the active platform on a new default. Adds keybinding-service.test.ts: constructs a real KeybindingService and asserts effective bindings + real keystroke matching for both cohorts across darwin/ linux/win32, plus partial-customization, idempotency, and seed-failure retry. * fix(keybindings): preserve legacy files during tab shortcut seed * fix(keybindings): preserve valid pre-swap overrides --- src/main/browser/browser-guest-ui.ts | 6 +- src/main/browser/browser-manager.test.ts | 22 +- src/main/index.ts | 8 +- src/main/keybindings/keybinding-file.test.ts | 153 ++++++++++++ src/main/keybindings/keybinding-file.ts | 74 ++++++ .../keybindings/keybinding-service.test.ts | 227 ++++++++++++++++++ src/main/keybindings/keybinding-service.ts | 28 ++- src/main/persistence.test.ts | 57 +++++ src/main/persistence.ts | 33 ++- src/renderer/src/components/Terminal.tsx | 7 +- .../src/components/tab-bar/group-tab-order.ts | 4 +- .../src/components/terminal/tab-type-cycle.ts | 4 +- src/renderer/src/hooks/ipc-tab-switch.ts | 23 +- src/shared/keybindings.test.ts | 64 ++++- src/shared/keybindings.ts | 23 +- src/shared/types.ts | 6 + 16 files changed, 689 insertions(+), 50 deletions(-) create mode 100644 src/main/keybindings/keybinding-service.test.ts diff --git a/src/main/browser/browser-guest-ui.ts b/src/main/browser/browser-guest-ui.ts index ecfdae2e62b..1185e48d48e 100644 --- a/src/main/browser/browser-guest-ui.ts +++ b/src/main/browser/browser-guest-ui.ts @@ -358,9 +358,9 @@ export function setupGuestShortcutForwarding(args: { return true } - // Why: Cmd/Ctrl+Alt+[ / ] cycles across every tab type. Handled before - // the generic modifier-chord gate below because that gate rejects Alt. - // Mirrors the Alt-exempt branch pattern used for worktreeHistoryNavigate. + // Why: match this action outside the main-process shortcut allowlist so + // both the fresh Shift binding and upgrading users' seeded Alt binding + // reach the renderer instead of the focused guest page. const switchAllTypesDirection = keybindingMatchesAction( 'tab.nextAllTypes', input, diff --git a/src/main/browser/browser-manager.test.ts b/src/main/browser/browser-manager.test.ts index 6a4d6400e44..c49088569f4 100644 --- a/src/main/browser/browser-manager.test.ts +++ b/src/main/browser/browser-manager.test.ts @@ -2355,6 +2355,15 @@ describe('browserManager', () => { alt: false, shift: true }, + { + type: 'keyDown', + code: 'BracketRight', + key: ']', + meta: isDarwin, + control: !isDarwin, + alt: true, + shift: false + }, { type: 'keyDown', code: 'PageDown', @@ -2411,12 +2420,13 @@ describe('browserManager', () => { expect(rendererSendMock).toHaveBeenNthCalledWith(1, 'ui:newBrowserTab') expect(rendererSendMock).toHaveBeenNthCalledWith(2, 'ui:newTerminalTab') expect(rendererSendMock).toHaveBeenNthCalledWith(3, 'ui:closeActiveTab') - expect(rendererSendMock).toHaveBeenNthCalledWith(4, 'ui:switchTab', 1) - expect(rendererSendMock).toHaveBeenNthCalledWith(5, 'ui:switchTerminalTab', 1) - expect(rendererSendMock).toHaveBeenNthCalledWith(6, 'ui:openQuickOpen') - expect(rendererSendMock).toHaveBeenNthCalledWith(7, 'ui:focusBrowserAddressBar') - expect(rendererSendMock).toHaveBeenNthCalledWith(8, 'ui:reloadBrowserPage') - expect(rendererSendMock).toHaveBeenNthCalledWith(9, 'ui:hardReloadBrowserPage') + expect(rendererSendMock).toHaveBeenNthCalledWith(4, 'ui:switchTabAcrossAllTypes', 1) + expect(rendererSendMock).toHaveBeenNthCalledWith(5, 'ui:switchTab', 1) + expect(rendererSendMock).toHaveBeenNthCalledWith(6, 'ui:switchTerminalTab', 1) + expect(rendererSendMock).toHaveBeenNthCalledWith(7, 'ui:openQuickOpen') + expect(rendererSendMock).toHaveBeenNthCalledWith(8, 'ui:focusBrowserAddressBar') + expect(rendererSendMock).toHaveBeenNthCalledWith(9, 'ui:reloadBrowserPage') + expect(rendererSendMock).toHaveBeenNthCalledWith(10, 'ui:hardReloadBrowserPage') }) it('uses customized keybindings when forwarding browser guest shortcuts', () => { diff --git a/src/main/index.ts b/src/main/index.ts index d69013e5689..f872f034fd0 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1880,7 +1880,13 @@ app.whenReady().then(async () => { rateLimits.setNetworkProxySettingsResolver(() => store!.getSettings()) keybindings = new KeybindingService({ homePath: app.getPath('home'), - getLegacyOverrides: () => store!.getSettings().keybindings + getLegacyOverrides: () => store!.getSettings().keybindings, + legacyTabSwitchSeed: { + isPending: () => store!.getSettings().tabSwitchKeybindingSeed === 'pending', + markSeeded: () => { + store!.updateSettings({ tabSwitchKeybindingSeed: 'done' }) + } + } }) browserManager.setSettingsResolver(() => ({ keybindings: keybindings?.getOverrides() })) rateLimits.setInactiveClaudeAccountsResolver(() => { diff --git a/src/main/keybindings/keybinding-file.test.ts b/src/main/keybindings/keybinding-file.test.ts index cc7cb43c5c3..81af9ab31b2 100644 --- a/src/main/keybindings/keybinding-file.test.ts +++ b/src/main/keybindings/keybinding-file.test.ts @@ -2,10 +2,12 @@ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it } from 'vitest' +import { LEGACY_TAB_SWITCH_BINDINGS } from '../../shared/keybindings' import { getUserKeybindingsPath, migrateLegacyKeybindings, readKeybindingFile, + seedLegacyTabSwitchBindings, writeKeybindingOverride } from './keybinding-file' @@ -224,4 +226,155 @@ describe('keybinding-file', () => { 'view.tasks': ['Ctrl+Alt+T'] }) }) + + it('seeds the legacy tab-switch chords into the active platform section', () => { + const result = seedLegacyTabSwitchBindings(filePath, 'darwin', LEGACY_TAB_SWITCH_BINDINGS) + + expect(result.seeded).toBe(true) + const snapshot = readKeybindingFile(filePath, 'darwin') + // Written to the platform section so Settings reset still works normally. + expect(snapshot.platformOverrides.darwin).toEqual({ + 'tab.nextSameType': ['Mod+Shift+BracketRight'], + 'tab.previousSameType': ['Mod+Shift+BracketLeft'], + 'tab.nextAllTypes': ['Mod+Alt+BracketRight'], + 'tab.previousAllTypes': ['Mod+Alt+BracketLeft'] + }) + // Effective bindings reproduce the pre-swap behavior with no conflicts dropped. + expect(snapshot.overrides).toMatchObject({ + 'tab.nextSameType': ['Mod+Shift+BracketRight'], + 'tab.nextAllTypes': ['Mod+Alt+BracketRight'] + }) + expect(snapshot.diagnostics).toEqual([]) + }) + + it('preserves a customized action while still pinning the un-customized ones', () => { + // A partially-customized existing user: they rebound one action. The other + // three must still land on their pre-swap defaults, not the new ones. + writeKeybindingOverride(filePath, 'darwin', 'tab.nextSameType', ['Mod+Alt+K']) + + const result = seedLegacyTabSwitchBindings(filePath, 'darwin', LEGACY_TAB_SWITCH_BINDINGS) + + expect(result.seeded).toBe(true) + expect(readKeybindingFile(filePath, 'darwin').overrides).toEqual({ + 'tab.nextSameType': ['Mod+Alt+K'], + 'tab.previousSameType': ['Mod+Shift+BracketLeft'], + 'tab.nextAllTypes': ['Mod+Alt+BracketRight'], + 'tab.previousAllTypes': ['Mod+Alt+BracketLeft'] + }) + }) + + it('preserves pre-swap customizations that conflict only with the new defaults', () => { + // This was valid before the swap: moving nextSameType to Mod+K freed its + // old Shift+] chord for previousSameType. The new registry temporarily + // assigns Shift+] to nextAllTypes, but the seed must not mistake the + // resulting conflict for an absent user override and replace it. + writeFileSync( + filePath, + JSON.stringify({ + version: 1, + platforms: { + darwin: { + 'tab.nextSameType': ['Mod+K'], + 'tab.previousSameType': ['Mod+Shift+BracketRight'] + } + } + }), + 'utf8' + ) + + seedLegacyTabSwitchBindings(filePath, 'darwin', LEGACY_TAB_SWITCH_BINDINGS) + + expect(readKeybindingFile(filePath, 'darwin')).toMatchObject({ + overrides: { + 'tab.nextSameType': ['Mod+K'], + 'tab.previousSameType': ['Mod+Shift+BracketRight'], + 'tab.nextAllTypes': ['Mod+Alt+BracketRight'], + 'tab.previousAllTypes': ['Mod+Alt+BracketLeft'] + }, + diagnostics: [] + }) + }) + + it('is a no-op once every swapped action already resolves on this platform', () => { + // Seeding writes the whole document at once, so it clears the per-write + // conflict guard that a naive one-action-at-a-time write would trip. + const first = seedLegacyTabSwitchBindings(filePath, 'darwin', LEGACY_TAB_SWITCH_BINDINGS) + expect(first.seeded).toBe(true) + expect(first.snapshot.diagnostics).toEqual([]) + + const second = seedLegacyTabSwitchBindings(filePath, 'darwin', LEGACY_TAB_SWITCH_BINDINGS) + expect(second.seeded).toBe(false) + }) + + it('does not let a foreign-platform override block the active-platform pin', () => { + // Only linux is customized; a darwin launch must still pin darwin so the + // active platform keeps the pre-swap behavior. + writeKeybindingOverride(filePath, 'linux', 'tab.nextAllTypes', ['Mod+Alt+K']) + + const result = seedLegacyTabSwitchBindings(filePath, 'darwin', LEGACY_TAB_SWITCH_BINDINGS) + + expect(result.seeded).toBe(true) + expect(readKeybindingFile(filePath, 'darwin').platformOverrides.darwin).toMatchObject({ + 'tab.nextAllTypes': ['Mod+Alt+BracketRight'] + }) + expect(readKeybindingFile(filePath, 'linux').platformOverrides.linux).toEqual({ + 'tab.nextAllTypes': ['Mod+Alt+K'] + }) + }) + + it('leaves unrelated overrides intact and stays idempotent when seeding', () => { + writeKeybindingOverride(filePath, 'darwin', 'terminal.search', ['Mod+Shift+F']) + + const first = seedLegacyTabSwitchBindings(filePath, 'darwin', LEGACY_TAB_SWITCH_BINDINGS) + const second = seedLegacyTabSwitchBindings(filePath, 'darwin', LEGACY_TAB_SWITCH_BINDINGS) + + expect(first.seeded).toBe(true) + // A second pass is a no-op: the pins now read as existing customization. + expect(second.seeded).toBe(false) + const snapshot = readKeybindingFile(filePath, 'darwin') + expect(snapshot.overrides['terminal.search']).toEqual(['Mod+Shift+F']) + expect(snapshot.overrides['tab.nextAllTypes']).toEqual(['Mod+Alt+BracketRight']) + }) + + it('migrates root-level legacy overrides without losing custom shortcuts', () => { + writeFileSync( + filePath, + JSON.stringify({ + version: 1, + 'worktree.quickOpen': 'Mod+Shift+P', + 'tab.nextSameType': 'Mod+K' + }), + 'utf8' + ) + + seedLegacyTabSwitchBindings(filePath, 'darwin', LEGACY_TAB_SWITCH_BINDINGS) + + const written = JSON.parse(readFileSync(filePath, 'utf8')) as { + keybindings: Record + platforms: Record> + 'worktree.quickOpen'?: unknown + 'tab.nextSameType'?: unknown + } + expect(written['worktree.quickOpen']).toBeUndefined() + expect(written['tab.nextSameType']).toBeUndefined() + expect(written.keybindings).toMatchObject({ + 'worktree.quickOpen': ['Mod+Shift+P'], + 'tab.nextSameType': ['Mod+K'] + }) + expect(readKeybindingFile(filePath, 'darwin').overrides).toMatchObject({ + 'worktree.quickOpen': ['Mod+Shift+P'], + 'tab.nextSameType': ['Mod+K'], + 'tab.nextAllTypes': ['Mod+Alt+BracketRight'] + }) + }) + + it('does not replace an unreadable keybindings file while seeding', () => { + const unreadableContents = '{{{not json' + writeFileSync(filePath, unreadableContents, 'utf8') + + expect(() => + seedLegacyTabSwitchBindings(filePath, 'darwin', LEGACY_TAB_SWITCH_BINDINGS) + ).toThrow() + expect(readFileSync(filePath, 'utf8')).toBe(unreadableContents) + }) }) diff --git a/src/main/keybindings/keybinding-file.ts b/src/main/keybindings/keybinding-file.ts index 2e0f6137f9a..1da2f071c33 100644 --- a/src/main/keybindings/keybinding-file.ts +++ b/src/main/keybindings/keybinding-file.ts @@ -318,6 +318,80 @@ export function migrateLegacyKeybindings( writeJsonDocument(path, document) } +/** + * Pin the pre-swap tab-switch chords for a pre-existing install so upgrading + * users keep the shortcuts they learned. Writes into the active-platform + * section (mirroring `writeKeybindingOverride`) so the seeded values stay + * resettable from Settings. + * + * Pins per action, not all-or-nothing: an action is seeded only when this + * platform has no effective override for it yet. That way a user who rebound + * just one of the swapped actions keeps that choice AND keeps the pre-swap + * default on the other three — an existing user's behavior is never altered, + * whether they customized none, some, or all of them. Because every pin equals + * the action's old default, the seeded set reproduces exactly today's effective + * config and introduces no new conflicts. + */ +export function seedLegacyTabSwitchBindings( + path: string, + platform: NodeJS.Platform, + legacyBindings: Readonly>> +): { seeded: boolean; snapshot: KeybindingFileSnapshot } { + const keybindingPlatform = getKeybindingPlatform(platform) + const actionIds = Object.keys(legacyBindings) as KeybindingActionId[] + const current = readKeybindingFile(path, platform) + const activePlatformOverrides = current.platformOverrides[keybindingPlatform] ?? {} + // Why: the new defaults can temporarily make a valid pre-swap customization + // look conflicting and remove it from `current.overrides`. Inspect the parsed + // common + active-platform sections directly so the seed never replaces it. + const toSeed = actionIds.filter( + (actionId) => + !Object.prototype.hasOwnProperty.call(current.commonOverrides, actionId) && + !Object.prototype.hasOwnProperty.call(activePlatformOverrides, actionId) + ) + if (toSeed.length === 0) { + return { seeded: false, snapshot: current } + } + + const readResult = readJsonDocument(path) + if (!readResult.document) { + // Why: migration must never replace a user-owned file that could not be + // parsed; leaving the cohort pending allows a safe retry after repair. + throw new Error(readResult.error ?? 'Could not read keybindings file.') + } + const document = { ...readResult.document } + const common = isJsonObject(document.keybindings) + ? { ...document.keybindings } + : { ...current.commonOverrides } + for (const rootKey of Object.keys(document)) { + if (isKeybindingActionId(rootKey)) { + delete document[rootKey] + } + } + const platforms = isJsonObject(document.platforms) ? { ...document.platforms } : {} + const activePlatform = isJsonObject(platforms[keybindingPlatform]) + ? { ...(platforms[keybindingPlatform] as JsonObject) } + : {} + for (const actionId of toSeed) { + const normalized = normalizeKeybindingArrayForAction(actionId, legacyBindings[actionId] ?? []) + if (Array.isArray(normalized)) { + activePlatform[actionId] = normalized + } + } + + document.version = FILE_VERSION + document.keybindings = common + document.platforms = { + ...platforms, + darwin: isJsonObject(platforms.darwin) ? platforms.darwin : {}, + linux: isJsonObject(platforms.linux) ? platforms.linux : {}, + win32: isJsonObject(platforms.win32) ? platforms.win32 : {}, + [keybindingPlatform]: activePlatform + } + writeJsonDocument(path, document) + return { seeded: true, snapshot: readKeybindingFile(path, platform) } +} + export function writeKeybindingOverride( path: string, platform: NodeJS.Platform, diff --git a/src/main/keybindings/keybinding-service.test.ts b/src/main/keybindings/keybinding-service.test.ts new file mode 100644 index 00000000000..2d18190fba8 --- /dev/null +++ b/src/main/keybindings/keybinding-service.test.ts @@ -0,0 +1,227 @@ +import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { afterEach, beforeEach, describe, expect, it } from 'vitest' +import { + getEffectiveKeybindingsForAction, + keybindingMatchesAction, + LEGACY_TAB_SWITCH_BINDINGS, + type KeybindingActionId, + type KeybindingInput +} from '../../shared/keybindings' +import { getUserKeybindingsPath, writeKeybindingOverride } from './keybinding-file' +import { KeybindingService } from './keybinding-service' + +// End-to-end coverage of the tab-switch convention swap: constructs a real +// KeybindingService (which runs the seed) and asserts the effective bindings AND +// real keystroke matching for both cohorts. The load-bearing claim under test is +// that a pre-existing install's behavior is byte-identical to what it was before +// the swap, on every platform and every customization state. + +const PLATFORMS: NodeJS.Platform[] = ['darwin', 'linux', 'win32'] +const SWAPPED_ACTIONS = Object.keys(LEGACY_TAB_SWITCH_BINDINGS) as KeybindingActionId[] + +function makeCohort(pending: boolean): { + controller: { isPending: () => boolean; markSeeded: () => void } + seeded: () => boolean +} { + let stillPending = pending + let didSeed = false + return { + controller: { + isPending: () => stillPending, + markSeeded: () => { + didSeed = true + stillPending = false + } + }, + seeded: () => didSeed + } +} + +// A physical bracket press. Mod is Cmd on macOS and Ctrl elsewhere; the matcher +// resolves brackets by `code`, so this stays layout-independent. +function bracketPress(opts: { + code: 'BracketLeft' | 'BracketRight' + alt: boolean + shift: boolean + platform: NodeJS.Platform +}): KeybindingInput { + const isMac = opts.platform === 'darwin' + return { + code: opts.code, + key: opts.code === 'BracketRight' ? ']' : '[', + meta: isMac, + control: !isMac, + alt: opts.alt, + shift: opts.shift + } +} + +describe('KeybindingService tab-switch cohort seeding', () => { + let home: string + + beforeEach(() => { + home = mkdtempSync(join(tmpdir(), 'orca-kb-service-')) + }) + + afterEach(() => { + rmSync(home, { recursive: true, force: true }) + }) + + it.each(PLATFORMS)( + 'fresh install (%s) adopts the swapped defaults and writes no file', + (platform) => { + const cohort = makeCohort(false) + const service = new KeybindingService({ + homePath: home, + platform, + legacyTabSwitchSeed: cohort.controller + }) + + expect(cohort.seeded()).toBe(false) + // Nothing written: a fresh install rides on the registry defaults. + expect(existsSync(getUserKeybindingsPath(home))).toBe(false) + + const overrides = service.getOverrides() + expect(getEffectiveKeybindingsForAction('tab.nextAllTypes', platform, overrides)).toEqual([ + 'Mod+Shift+BracketRight' + ]) + expect(getEffectiveKeybindingsForAction('tab.nextSameType', platform, overrides)).toEqual([ + 'Mod+Alt+BracketRight' + ]) + + // Shift+bracket now drives the broad all-types cycle. + expect( + keybindingMatchesAction( + 'tab.nextAllTypes', + bracketPress({ code: 'BracketRight', alt: false, shift: true, platform }), + platform, + overrides + ) + ).toBe(true) + expect( + keybindingMatchesAction( + 'tab.nextSameType', + bracketPress({ code: 'BracketRight', alt: false, shift: true, platform }), + platform, + overrides + ) + ).toBe(false) + } + ) + + it.each(PLATFORMS)( + 'existing install (%s) keeps the pre-swap chords exactly, on a clean profile', + (platform) => { + const cohort = makeCohort(true) + const service = new KeybindingService({ + homePath: home, + platform, + legacyTabSwitchSeed: cohort.controller + }) + + expect(cohort.seeded()).toBe(true) + const overrides = service.getOverrides() + + // Effective bindings for all four actions equal the pre-swap defaults. + for (const actionId of SWAPPED_ACTIONS) { + expect(getEffectiveKeybindingsForAction(actionId, platform, overrides)).toEqual( + LEGACY_TAB_SWITCH_BINDINGS[actionId] + ) + } + + // Behavior is unchanged: Mod+Shift+bracket still cycles same-type, and + // Mod+Alt+bracket still cycles all-types — the inverse of a fresh install. + const shiftRight = bracketPress({ code: 'BracketRight', alt: false, shift: true, platform }) + expect(keybindingMatchesAction('tab.nextSameType', shiftRight, platform, overrides)).toBe( + true + ) + expect(keybindingMatchesAction('tab.nextAllTypes', shiftRight, platform, overrides)).toBe( + false + ) + + const altLeft = bracketPress({ code: 'BracketLeft', alt: true, shift: false, platform }) + expect(keybindingMatchesAction('tab.previousAllTypes', altLeft, platform, overrides)).toBe( + true + ) + expect(keybindingMatchesAction('tab.previousSameType', altLeft, platform, overrides)).toBe( + false + ) + } + ) + + it('existing install keeps a rebound action AND pins the pre-swap default on the rest', () => { + const platform: NodeJS.Platform = 'darwin' + // The user had rebound just one action before upgrading. + writeKeybindingOverride(getUserKeybindingsPath(home), platform, 'tab.nextSameType', ['Mod+K']) + + const cohort = makeCohort(true) + const service = new KeybindingService({ + homePath: home, + platform, + legacyTabSwitchSeed: cohort.controller + }) + const overrides = service.getOverrides() + + // Their custom binding is untouched... + expect(getEffectiveKeybindingsForAction('tab.nextSameType', platform, overrides)).toEqual([ + 'Mod+K' + ]) + // ...and the other three still resolve to their pre-swap defaults, not the + // new ones — no silent behavior change for a partially-customized user. + expect(getEffectiveKeybindingsForAction('tab.previousSameType', platform, overrides)).toEqual([ + 'Mod+Shift+BracketLeft' + ]) + expect(getEffectiveKeybindingsForAction('tab.nextAllTypes', platform, overrides)).toEqual([ + 'Mod+Alt+BracketRight' + ]) + expect(getEffectiveKeybindingsForAction('tab.previousAllTypes', platform, overrides)).toEqual([ + 'Mod+Alt+BracketLeft' + ]) + }) + + it('is idempotent: a second launch after the seed changes nothing', () => { + const platform: NodeJS.Platform = 'linux' + const cohort = makeCohort(true) + const first = new KeybindingService({ + homePath: home, + platform, + legacyTabSwitchSeed: cohort.controller + }) + const afterFirst = first.getOverrides() + + // Same cohort controller: markSeeded flipped it to not-pending, mirroring the + // persisted flag flipping to 'done'. + const second = new KeybindingService({ + homePath: home, + platform, + legacyTabSwitchSeed: cohort.controller + }) + expect(second.getOverrides()).toEqual(afterFirst) + for (const actionId of SWAPPED_ACTIONS) { + expect(getEffectiveKeybindingsForAction(actionId, platform, second.getOverrides())).toEqual( + LEGACY_TAB_SWITCH_BINDINGS[actionId] + ) + } + }) + + it('leaves the cohort pending when the seed write fails (retries next launch)', () => { + // homePath is a regular file, so creating `/.orca/` throws. The service + // must swallow the error and NOT mark the one-shot done. + const fileHome = join(home, 'not-a-dir') + writeFileSync(fileHome, 'x', 'utf8') + const cohort = makeCohort(true) + + expect( + () => + new KeybindingService({ + homePath: fileHome, + platform: 'darwin', + legacyTabSwitchSeed: cohort.controller + }) + ).not.toThrow() + expect(cohort.seeded()).toBe(false) + expect(cohort.controller.isPending()).toBe(true) + }) +}) diff --git a/src/main/keybindings/keybinding-service.ts b/src/main/keybindings/keybinding-service.ts index d3cae4495d0..dc6c9329adc 100644 --- a/src/main/keybindings/keybinding-service.ts +++ b/src/main/keybindings/keybinding-service.ts @@ -1,13 +1,15 @@ -import type { - KeybindingActionId, - KeybindingFileSnapshot, - KeybindingOverrides +import { + LEGACY_TAB_SWITCH_BINDINGS, + type KeybindingActionId, + type KeybindingFileSnapshot, + type KeybindingOverrides } from '../../shared/keybindings' import { ensureKeybindingFile, getUserKeybindingsPath, migrateLegacyKeybindings, readKeybindingFile, + seedLegacyTabSwitchBindings, writeKeybindingOverride } from './keybinding-file' @@ -15,6 +17,13 @@ export type KeybindingServiceOptions = { homePath: string platform?: NodeJS.Platform getLegacyOverrides?: () => KeybindingOverrides | undefined + /** Cohort seed for the tab-switch convention swap. `isPending` is true only + * for pre-existing installs on the first launch after the swap; `markSeeded` + * freezes the one-shot so it never runs again. */ + legacyTabSwitchSeed?: { + isPending: () => boolean + markSeeded: () => void + } } export class KeybindingService { @@ -28,6 +37,17 @@ export class KeybindingService { // Why: older builds persisted custom shortcuts inside global settings. // Once a keybindings file exists, it is the sole source of truth. migrateLegacyKeybindings(this.configPath, this.platform, options.getLegacyOverrides?.()) + // Why: pre-existing installs keep the old tab-switch chords. Only mark the + // one-shot done on success so a transient IO failure retries next launch + // instead of silently dropping the pin. + if (options.legacyTabSwitchSeed?.isPending()) { + try { + seedLegacyTabSwitchBindings(this.configPath, this.platform, LEGACY_TAB_SWITCH_BINDINGS) + options.legacyTabSwitchSeed.markSeeded() + } catch (error) { + console.error('Failed to seed legacy tab-switch keybindings:', error) + } + } } getPath(): string { diff --git a/src/main/persistence.test.ts b/src/main/persistence.test.ts index 39771208b1d..5ec90c93223 100644 --- a/src/main/persistence.test.ts +++ b/src/main/persistence.test.ts @@ -9797,6 +9797,63 @@ describe('Store', () => { }) }) +describe('Store.migrateTabSwitchKeybindings', () => { + // Freezes the install cohort for the tab-switch convention swap on first load. + // Keys on `fileExistedOnLoad` (not field presence) so the verdict is stable + // even after a fresh install writes its own data file on later launches. + + beforeEach(() => { + testState.dir = mkdtempSync(join(tmpdir(), 'orca-test-')) + }) + + afterEach(() => { + rmSync(testState.dir, { recursive: true, force: true }) + }) + + it('marks a truly fresh install done so it adopts the new defaults', async () => { + const store = await createStore() + expect(store.getSettings().tabSwitchKeybindingSeed).toBe('done') + }) + + it('marks a pre-existing install pending so the legacy chords get seeded', async () => { + writeDataFile({ + schemaVersion: 1, + repos: [makeRepo()], + worktreeMeta: {}, + settings: { theme: 'dark' }, + ui: {}, + githubCache: { pr: {}, issue: {} }, + workspaceSession: {} + }) + const store = await createStore() + expect(store.getSettings().tabSwitchKeybindingSeed).toBe('pending') + expect(store.getSettings().theme).toBe('dark') + }) + + it('treats a corrupt data file as a pre-existing install', async () => { + mkdirSync(testState.dir, { recursive: true }) + writeFileSync(dataFile(), '{{{corrupt json', 'utf-8') + const store = await createStore() + expect(store.getSettings().tabSwitchKeybindingSeed).toBe('pending') + }) + + it('preserves an already-frozen cohort on subsequent launches', async () => { + writeDataFile({ + schemaVersion: 1, + repos: [], + worktreeMeta: {}, + settings: { tabSwitchKeybindingSeed: 'done' }, + ui: {}, + githubCache: { pr: {}, issue: {} }, + workspaceSession: {} + }) + const store = await createStore() + // Existing file but cohort already resolved to 'done' — must not flip to + // 'pending' just because the data file happens to exist. + expect(store.getSettings().tabSwitchKeybindingSeed).toBe('done') + }) +}) + describe('Store.migrateWorktreeIdentity', () => { const OLD = 'repo1::/ws/cunner' const NEW = 'repo1::/ws/worktree-creation-spinner' diff --git a/src/main/persistence.ts b/src/main/persistence.ts index a237e941150..a76802b9227 100644 --- a/src/main/persistence.ts +++ b/src/main/persistence.ts @@ -3544,7 +3544,10 @@ export class Store { this.loadNeedsSave = true } - const migrated = this.migrateTelemetry(result, fileExistedOnLoad) + const migrated = this.migrateTabSwitchKeybindings( + this.migrateTelemetry(result, fileExistedOnLoad), + fileExistedOnLoad + ) // githubCache lives in a sidecar file now (see getGithubCacheFile). A // legacy in-file cache (pre-sidecar build, or a downgrade round-trip) is @@ -3580,6 +3583,34 @@ export class Store { // the banner resolves (the consent resolver returns `pending_banner` // until then, so nothing transmits). // - `installId` — anonymous UUID v4. Stable across launches; not surfaced in the UI. + // One-shot cohort freeze for the tab-switch keybinding convention swap. Runs + // on every `load()` but is a no-op once `tabSwitchKeybindingSeed` is set. The + // decision must be frozen on the first post-swap launch: `fileExistedOnLoad` + // only distinguishes existing vs fresh on that first run (a fresh install's + // data file exists on every subsequent launch), so persist the verdict now. + private migrateTabSwitchKeybindings( + state: PersistedState, + fileExistedOnLoad: boolean + ): PersistedState { + const existing = state.settings?.tabSwitchKeybindingSeed + if (existing === 'pending' || existing === 'done') { + return state + } + // Why: mark dirty so the frozen cohort survives the next restart even if no + // other setting changes this session; without this a fresh install could be + // re-read as "existing" once its data file lands on disk. + this.loadNeedsSave = true + return { + ...state, + settings: { + ...state.settings, + // Existing installs pin the old chords via a keybindings.json seed; + // fresh installs need nothing beyond the new registry defaults. + tabSwitchKeybindingSeed: fileExistedOnLoad ? 'pending' : 'done' + } + } + } + private migrateTelemetry(state: PersistedState, fileExistedOnLoad: boolean): PersistedState { const existing = state.settings?.telemetry // Why: the one-shot is complete only when all three invariants hold. diff --git a/src/renderer/src/components/Terminal.tsx b/src/renderer/src/components/Terminal.tsx index f75bbaf951e..14df288fd16 100644 --- a/src/renderer/src/components/Terminal.tsx +++ b/src/renderer/src/components/Terminal.tsx @@ -1950,10 +1950,9 @@ function Terminal(): React.JSX.Element | null { return } - // Cmd/Ctrl+Shift+] and Cmd/Ctrl+Shift+[ - switch tabs (scoped to the - // active tab type). Cmd/Ctrl+Alt+] and Cmd/Ctrl+Alt+[ cycles across - // every tab type as an escape hatch from the type-scoped default, and - // matches the platform tab-switch chord on macOS. + // Fresh installs use Cmd/Ctrl+Shift+[ / ] across all tab types and + // Cmd/Ctrl+Alt+[ / ] within the active type; upgrading users keep the + // inverse mapping, and both actions remain rebindable. // Why: use e.code instead of e.key because on macOS, Shift+[ reports '{' // as the key value (the shifted character), not '['. Option+[ also // composes to dead-key / punctuation on many layouts, so matching on diff --git a/src/renderer/src/components/tab-bar/group-tab-order.ts b/src/renderer/src/components/tab-bar/group-tab-order.ts index c6c7dea67fa..8a16e51786b 100644 --- a/src/renderer/src/components/tab-bar/group-tab-order.ts +++ b/src/renderer/src/components/tab-bar/group-tab-order.ts @@ -18,8 +18,8 @@ export type ActiveTabNavOrderIds = { /** * Compute the visible tab-strip order for a single group. * - * Why: keyboard navigation (Cmd/Ctrl+Shift+[ / ]) and the IPC switch-tab - * shortcut must walk tabs in the same order the TabBar renders them. The + * Why: keyboard tab-cycle actions and the IPC switch-tab shortcut must walk + * tabs in the same order the TabBar renders them. The * TabBar derives its order from `group.tabOrder` (the canonical split-group * state, updated by drag/drop via `reorderUnifiedTabs`). Reading from the * legacy `tabBarOrderByWorktree` drifts out of sync because that store is diff --git a/src/renderer/src/components/terminal/tab-type-cycle.ts b/src/renderer/src/components/terminal/tab-type-cycle.ts index 411cc81871d..990d00cd87a 100644 --- a/src/renderer/src/components/terminal/tab-type-cycle.ts +++ b/src/renderer/src/components/terminal/tab-type-cycle.ts @@ -44,8 +44,8 @@ type GetNextTabAcrossAllTypesParams = { direction: number } -// Why: companion to getNextTabWithinActiveType for the Cmd/Ctrl+Alt+Shift+]/[ -// "cycle across every tab" chord. Keeps the same dual-id matching semantics +// Why: companion to getNextTabWithinActiveType for the "cycle across every tab" +// chord (Cmd/Ctrl+Shift+]/[ on fresh installs). Keeps the same dual-id matching semantics // (prefer the active group's unified tabId to disambiguate split layouts, fall // back to the backing entity id) so behavior matches what the TabBar renders. export function getNextTabAcrossAllTypes({ diff --git a/src/renderer/src/hooks/ipc-tab-switch.ts b/src/renderer/src/hooks/ipc-tab-switch.ts index 18f4c4d4ff8..12dc43e5c97 100644 --- a/src/renderer/src/hooks/ipc-tab-switch.ts +++ b/src/renderer/src/hooks/ipc-tab-switch.ts @@ -18,9 +18,9 @@ type CycleContext = { } /** - * Shared setup for the Cmd/Ctrl+Shift+[ / ] chords (both the type-scoped and - * across-all-types variants). Returns null when there is no active worktree - * or the visible nav has at most one tab (nothing to cycle). + * Shared setup for the type-scoped and across-all-types tab-cycle actions. + * Returns null when there is no active worktree or the visible nav has at most + * one tab (nothing to cycle). */ function resolveCycleContext(): CycleContext | null { const store = useAppStore.getState() @@ -87,7 +87,9 @@ export function activateCyclableTab(store: AppStoreState, next: TypeCyclableTab) } /** - * Handle Cmd/Ctrl+Shift+[ / ] direction switching within the active tab type. + * Handle direction switching within the active tab type (same-type cycle). + * The default chord is Cmd/Ctrl+Alt+[ / ] for fresh installs; pre-swap installs + * keep Cmd/Ctrl+Shift+[ / ] via the seed migration, and either is rebindable. * Extracted from useIpcEvents to keep file size under the max-lines lint threshold. * Returns true if a tab switch occurred, false otherwise. */ @@ -114,14 +116,13 @@ export function handleSwitchTab(direction: number): boolean { } /** - * Handle Cmd/Ctrl+Alt+Shift+[ / ] cycling across every visible tab, - * regardless of tab type. + * Handle cycling across every visible tab, regardless of tab type. * - * Why: companion chord to the type-scoped Cmd/Ctrl+Shift+[ / ] that ships as - * the default. The type-scoped chord is the VS Code-style per-pane cycle; - * this one gives users an escape hatch back to the pre-scope "cycle through - * everything" behavior without needing a settings toggle (see PR #1281 - * discussion). Returns true if a tab switch occurred, false otherwise. + * Why: companion chord to the type-scoped same-type cycle. Fresh installs bind + * this broad cycle to Cmd/Ctrl+Shift+[ / ] — the widespread "switch tab" chord — + * and the same-type cycle to Cmd/Ctrl+Alt+[ / ]; pre-swap installs keep the + * inverse via the seed migration (see PR #1281 for the original type-scope + * rationale). Returns true if a tab switch occurred, false otherwise. */ export function handleSwitchTabAcrossAllTypes(direction: number): boolean { const ctx = resolveCycleContext() diff --git a/src/shared/keybindings.test.ts b/src/shared/keybindings.test.ts index 5b38914a75b..6c860897582 100644 --- a/src/shared/keybindings.test.ts +++ b/src/shared/keybindings.test.ts @@ -12,6 +12,7 @@ import { isDigitIndexActionId, isDoubleTapBinding, keybindingFromInput, + LEGACY_TAB_SWITCH_BINDINGS, keybindingFromInputForAction, keybindingMatchesAction, keybindingMatchesInput, @@ -443,6 +444,42 @@ describe('keybindings', () => { ]) }) + it('defaults tab-switch chords to the swapped convention for fresh installs', () => { + // New users get the widespread mapping: Shift+bracket cycles all tabs, + // Alt+bracket cycles within the active type. + expect(getEffectiveKeybindingsForAction('tab.nextAllTypes', 'darwin')).toEqual([ + 'Mod+Shift+BracketRight' + ]) + expect(getEffectiveKeybindingsForAction('tab.previousAllTypes', 'darwin')).toEqual([ + 'Mod+Shift+BracketLeft' + ]) + expect(getEffectiveKeybindingsForAction('tab.nextSameType', 'darwin')).toEqual([ + 'Mod+Alt+BracketRight' + ]) + expect(getEffectiveKeybindingsForAction('tab.previousSameType', 'darwin')).toEqual([ + 'Mod+Alt+BracketLeft' + ]) + }) + + it('pins the pre-swap chords via LEGACY_TAB_SWITCH_BINDINGS for upgrading installs', () => { + // These are what the seed migration writes so pre-existing users keep the + // shortcuts they learned; overriding an action with its legacy value must + // reproduce the old effective binding. + expect(LEGACY_TAB_SWITCH_BINDINGS).toEqual({ + 'tab.nextSameType': ['Mod+Shift+BracketRight'], + 'tab.previousSameType': ['Mod+Shift+BracketLeft'], + 'tab.nextAllTypes': ['Mod+Alt+BracketRight'], + 'tab.previousAllTypes': ['Mod+Alt+BracketLeft'] + }) + for (const [actionId, bindings] of Object.entries(LEGACY_TAB_SWITCH_BINDINGS)) { + expect( + getEffectiveKeybindingsForAction(actionId as KeybindingActionId, 'darwin', { + [actionId]: bindings + }) + ).toEqual(bindings) + } + }) + it('defines browser history shortcuts for Logitech side-button remaps', () => { expect(getEffectiveKeybindingsForAction('browser.back', 'darwin')).toEqual(['Mod+BracketLeft']) expect(getEffectiveKeybindingsForAction('browser.forward', 'darwin')).toEqual([ @@ -1216,29 +1253,30 @@ describe('keybindings', () => { expect(keybindingMatchesAction('terminal.focusNextPane', jisLeftBracket, 'darwin')).toBe(false) expect(keybindingMatchesAction('terminal.focusNextPane', jisRightBracket, 'darwin')).toBe(true) + // Alt+bracket is the fresh-install same-type default after the convention swap. expect( - keybindingMatchesAction('tab.previousAllTypes', { ...jisLeftBracket, alt: true }, 'darwin') + keybindingMatchesAction('tab.previousSameType', { ...jisLeftBracket, alt: true }, 'darwin') ).toBe(true) expect( - keybindingMatchesAction('tab.nextAllTypes', { ...jisRightBracket, alt: true }, 'darwin') + keybindingMatchesAction('tab.nextSameType', { ...jisRightBracket, alt: true }, 'darwin') ).toBe(true) expect( keybindingMatchesAction( - 'tab.previousAllTypes', + 'tab.previousSameType', { ...jisLeftBracket, control: true, meta: false, alt: true }, 'linux' ) ).toBe(true) expect( keybindingMatchesAction( - 'tab.nextAllTypes', + 'tab.nextSameType', { ...jisLeftBracket, control: true, meta: false, alt: true }, 'linux' ) ).toBe(false) expect( keybindingMatchesAction( - 'tab.nextAllTypes', + 'tab.nextSameType', { ...jisRightBracket, control: true, meta: false, alt: true }, 'linux' ) @@ -1268,7 +1306,7 @@ describe('keybindings', () => { expect( keybindingMatchesAction( - 'tab.previousAllTypes', + 'tab.previousSameType', { key: '[', code: 'Digit8', @@ -1282,7 +1320,7 @@ describe('keybindings', () => { ).toBe(false) expect( keybindingMatchesAction( - 'tab.previousAllTypes', + 'tab.previousSameType', { key: 'Dead', code: 'BracketLeft', @@ -1385,7 +1423,9 @@ describe('keybindings', () => { expect(formatKeybindingList(['DoubleTap+Shift'], 'linux')).toBe('Shift Shift') }) - it('matches macOS Option-composed bracket shortcuts for all-type tab switching', () => { + it('matches macOS Option-composed bracket shortcuts for same-type tab switching', () => { + // Cmd+Alt+bracket is the fresh-install same-type default after the swap, so + // Option-composed dead keys (\u2325[ -> "\u201c") must still resolve to that action. const macOptionLeftBracket = { key: '\u201c', code: 'BracketLeft', @@ -1403,12 +1443,12 @@ describe('keybindings', () => { shift: false } - expect(keybindingMatchesAction('tab.previousAllTypes', macOptionLeftBracket, 'darwin')).toBe( + expect(keybindingMatchesAction('tab.previousSameType', macOptionLeftBracket, 'darwin')).toBe( true ) - expect(keybindingMatchesAction('tab.nextAllTypes', macOptionLeftBracket, 'darwin')).toBe(false) - expect(keybindingMatchesAction('tab.nextAllTypes', macOptionRightBracket, 'darwin')).toBe(true) - expect(keybindingMatchesAction('tab.previousAllTypes', macOptionRightBracket, 'darwin')).toBe( + expect(keybindingMatchesAction('tab.nextSameType', macOptionLeftBracket, 'darwin')).toBe(false) + expect(keybindingMatchesAction('tab.nextSameType', macOptionRightBracket, 'darwin')).toBe(true) + expect(keybindingMatchesAction('tab.previousSameType', macOptionRightBracket, 'darwin')).toBe( false ) }) diff --git a/src/shared/keybindings.ts b/src/shared/keybindings.ts index f250c39848d..857446a8051 100644 --- a/src/shared/keybindings.ts +++ b/src/shared/keybindings.ts @@ -643,7 +643,10 @@ export const KEYBINDING_DEFINITIONS: readonly KeybindingDefinition[] = [ group: 'Tab Navigation', scope: 'tabs', searchKeywords: ['shortcut', 'tab', 'next', 'switch', 'cycle'], - defaultBindings: platformBindings(['Mod+Shift+BracketRight']) + // Why: Mod+Shift+Bracket is the widespread "switch tab" chord, so it now + // drives the broad all-types cycle for new users; same-type moves to + // Mod+Alt. Pre-release installs keep the old mapping via the seed migration. + defaultBindings: platformBindings(['Mod+Alt+BracketRight']) }, { id: 'tab.previousSameType', @@ -651,7 +654,7 @@ export const KEYBINDING_DEFINITIONS: readonly KeybindingDefinition[] = [ group: 'Tab Navigation', scope: 'tabs', searchKeywords: ['shortcut', 'tab', 'previous', 'switch', 'cycle'], - defaultBindings: platformBindings(['Mod+Shift+BracketLeft']) + defaultBindings: platformBindings(['Mod+Alt+BracketLeft']) }, { id: 'tab.nextAllTypes', @@ -659,7 +662,7 @@ export const KEYBINDING_DEFINITIONS: readonly KeybindingDefinition[] = [ group: 'Tab Navigation', scope: 'tabs', searchKeywords: ['shortcut', 'tab', 'next', 'switch', 'cycle', 'all', 'any'], - defaultBindings: platformBindings(['Mod+Alt+BracketRight']) + defaultBindings: platformBindings(['Mod+Shift+BracketRight']) }, { id: 'tab.previousAllTypes', @@ -667,7 +670,7 @@ export const KEYBINDING_DEFINITIONS: readonly KeybindingDefinition[] = [ group: 'Tab Navigation', scope: 'tabs', searchKeywords: ['shortcut', 'tab', 'previous', 'switch', 'cycle', 'all', 'any'], - defaultBindings: platformBindings(['Mod+Alt+BracketLeft']) + defaultBindings: platformBindings(['Mod+Shift+BracketLeft']) }, { id: 'tab.previousRecent', @@ -1070,6 +1073,18 @@ export const KEYBINDING_DEFINITIONS: readonly KeybindingDefinition[] = [ ...buildAgentTabKeybindingDefinitions() ] +/** + * The tab-switch bindings as they shipped before the convention swap. A one-time + * migration pins these for pre-release installs so upgrading users keep the + * shortcuts they learned; fresh installs get the new registry defaults above. + */ +export const LEGACY_TAB_SWITCH_BINDINGS: Readonly>> = { + 'tab.nextSameType': ['Mod+Shift+BracketRight'], + 'tab.previousSameType': ['Mod+Shift+BracketLeft'], + 'tab.nextAllTypes': ['Mod+Alt+BracketRight'], + 'tab.previousAllTypes': ['Mod+Alt+BracketLeft'] +} + export function agentTabActionId(agent: TuiAgent): AgentTabActionId { return `tab.newAgent.${agent}` } diff --git a/src/shared/types.ts b/src/shared/types.ts index bbe49dfac81..9f3d62e6968 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -3042,6 +3042,12 @@ export type GlobalSettings = { * false for fresh installs (no first-launch surface). */ existedBeforeTelemetryRelease: boolean } + /** One-shot cohort marker for the tab-switch keybinding convention swap. + * Absent before the migration runs. Set once on first boot after the swap: + * `'pending'` for pre-existing installs (a seed then pins the old chords in + * keybindings.json before flipping this to `'done'`), or `'done'` for fresh + * installs, which adopt the new registry defaults with no seed. */ + tabSwitchKeybindingSeed?: 'pending' | 'done' /** Local voice/dictation configuration (Phase 1 voice feature). Optional * because profiles created before voice landed won't have the key; * `getDefaultSettings()` hydrates `getDefaultVoiceSettings()` via the From 72a5c6f199b7af88f5d46af19d3aa99a4e53665d Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Fri, 17 Jul 2026 20:08:53 -0400 Subject: [PATCH 016/137] fix(terminal): paint paired remote terminals on first subscribe (#8954) * fix(terminal): paint paired remote terminals on first subscribe * test(terminal): cover desktop legacy subscriber PTY wait The widened terminal.subscribe gate (isMobile -> params.client) had no regression test: reverting it to the mobile-only gate left the suite green. Add a desktop legacy-subscriber case asserting the mount request and late-PTY wait fire before the scrollback-only fallback. * fix(terminal): cancel abandoned multiplex PTY waits Desktop subscribers can close while the new late-PTY wait is pending. Register provisional slot cancellation and abort all pending waits on unsubscribe, connection abort, or multiplex teardown so a late PTY cannot resurrect a ghost output stream. --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> --- src/main/runtime/rpc/methods/terminal.ts | 146 ++++--- .../runtime/rpc/terminal-multiplex.test.ts | 376 +++++++++++++++++- 2 files changed, 471 insertions(+), 51 deletions(-) diff --git a/src/main/runtime/rpc/methods/terminal.ts b/src/main/runtime/rpc/methods/terminal.ts index 1be167d6e1a..b0983d5e06f 100644 --- a/src/main/runtime/rpc/methods/terminal.ts +++ b/src/main/runtime/rpc/methods/terminal.ts @@ -1580,6 +1580,7 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [ let closed = false let cursor = 0 const streams = new Map() + const pendingPtyWaitControllers = new Map>() let ackTotalInFlightBytes = 0 let resolveMultiplex = (): void => {} const multiplexClosed = new Promise((resolve) => { @@ -1799,11 +1800,28 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [ emit({ type: 'end', streamId }) } } + const cancelPendingPtyWaits = (streamId: number): void => { + const controllers = pendingPtyWaitControllers.get(streamId) + if (!controllers) { + return + } + pendingPtyWaitControllers.delete(streamId) + for (const controller of controllers) { + controller.abort() + } + } + const cancelAllPendingPtyWaits = (): void => { + for (const streamId of Array.from(pendingPtyWaitControllers.keys())) { + cancelPendingPtyWaits(streamId) + } + } const closeMultiplex = (): void => { if (closed) { return } closed = true + signal?.removeEventListener('abort', cancelAllPendingPtyWaits) + cancelAllPendingPtyWaits() const remoteDesktopKeysByPty = new Map() for (const streamId of Array.from(streams.keys())) { const stream = streams.get(streamId) @@ -1830,6 +1848,7 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [ return } if (frame.opcode === TerminalStreamOpcode.Unsubscribe) { + cancelPendingPtyWaits(stream.streamId) detachStream(stream.streamId, false) return } @@ -2081,15 +2100,47 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [ emit({ type: 'end', streamId: request.streamId }) return } - if (!leaf?.ptyId && isMobile) { + if (!leaf?.ptyId && request.client) { + // Why: a never-mounted tab has no graph leaf to await; mounting the + // exact tab lets its PTY attach without activating the worktree. + runtime.requestRendererTerminalTabMount(request.terminal) + const waitController = new AbortController() + const pendingControllers = pendingPtyWaitControllers.get(request.streamId) ?? new Set() + pendingControllers.add(waitController) + pendingPtyWaitControllers.set(request.streamId, pendingControllers) + if (signal?.aborted) { + waitController.abort() + } + // Why: the live slot handler does not exist until the PTY attaches; + // retain cancellation ownership while the pane is still pending. + const unregisterPendingHandler = registerBinaryStreamHandler( + request.streamId, + (frame) => { + if (frame.opcode === TerminalStreamOpcode.Unsubscribe) { + cancelPendingPtyWaits(request.streamId) + detachStream(request.streamId, false) + } + } + ) try { - const ptyId = await runtime.waitForLeafPtyId(request.terminal, 10_000, signal) + const ptyId = await runtime.waitForLeafPtyId( + request.terminal, + 10_000, + waitController.signal + ) leaf = { ptyId } } catch { - if (closed || signal?.aborted) { + if (closed || signal?.aborted || waitController.signal.aborted) { return } // Fall through to the explicit no_connected_pty error below. + } finally { + const currentControllers = pendingPtyWaitControllers.get(request.streamId) + currentControllers?.delete(waitController) + if (currentControllers?.size === 0) { + pendingPtyWaitControllers.delete(request.streamId) + } + unregisterPendingHandler() } } if (!leaf?.ptyId) { @@ -2213,30 +2264,6 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [ return } - if (!isMobile) { - stream.unsubscribeFit = runtime.subscribeToFitOverrideChanges(ptyId, (event) => { - const mode = - event.mode === 'mobile-fit' - ? event.mode - : (runtime.getRemoteDesktopFitHold?.(ptyId, stream.remoteDesktopSubscriptionKey) - .mode ?? 'desktop-fit') - emit({ - type: 'fit-override-changed', - streamId: request.streamId, - mode, - cols: event.cols, - rows: event.rows - }) - }) - stream.unsubscribeDriver = runtime.subscribeToDriverChanges(ptyId, (driver) => { - emit({ - type: 'driver-changed', - streamId: request.streamId, - driver - }) - }) - } - let read = await runtime.readTerminal(request.terminal) let serialized = await serializeBudgetedMobileSnapshot(runtime, ptyId, isMobile) if (closed || streams.get(request.streamId) !== stream) { @@ -2264,25 +2291,6 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [ const layoutSeq = runtime.getLayout(ptyId)?.seq const snapshotFrameSeq = serialized?.seq ?? layoutSeq const snapshotOutputSeq = serialized?.seq - if (!isMobile) { - const fitOverride = runtime.getTerminalFitOverride(ptyId) - const desktopHold = runtime.getRemoteDesktopFitHold?.( - ptyId, - stream.remoteDesktopSubscriptionKey - ) ?? { mode: 'desktop-fit' as const, cols: size?.cols ?? 0, rows: size?.rows ?? 0 } - emit({ - type: 'fit-override-changed', - streamId: request.streamId, - mode: fitOverride?.mode ?? desktopHold.mode, - cols: fitOverride?.cols ?? desktopHold.cols, - rows: fitOverride?.rows ?? desktopHold.rows - }) - emit({ - type: 'driver-changed', - streamId: request.streamId, - driver: runtime.getDriver(ptyId) - }) - } emit({ type: 'subscribed', streamId: request.streamId, @@ -2327,6 +2335,46 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [ stream.pendingOutputBytes = 0 stream.pendingOutputOverflowed = false stream.outputBatcher.flush() + if (!isMobile) { + stream.unsubscribeFit = runtime.subscribeToFitOverrideChanges(ptyId, (event) => { + const mode = + event.mode === 'mobile-fit' + ? event.mode + : (runtime.getRemoteDesktopFitHold?.(ptyId, stream.remoteDesktopSubscriptionKey) + .mode ?? 'desktop-fit') + emit({ + type: 'fit-override-changed', + streamId: request.streamId, + mode, + cols: event.cols, + rows: event.rows + }) + }) + stream.unsubscribeDriver = runtime.subscribeToDriverChanges(ptyId, (driver) => { + emit({ + type: 'driver-changed', + streamId: request.streamId, + driver + }) + }) + const fitOverride = runtime.getTerminalFitOverride(ptyId) + const desktopHold = runtime.getRemoteDesktopFitHold?.( + ptyId, + stream.remoteDesktopSubscriptionKey + ) ?? { mode: 'desktop-fit' as const, cols: size?.cols ?? 0, rows: size?.rows ?? 0 } + emit({ + type: 'fit-override-changed', + streamId: request.streamId, + mode: fitOverride?.mode ?? desktopHold.mode, + cols: fitOverride?.cols ?? desktopHold.cols, + rows: fitOverride?.rows ?? desktopHold.rows + }) + emit({ + type: 'driver-changed', + streamId: request.streamId, + driver: runtime.getDriver(ptyId) + }) + } stream.unsubscribeResize = runtime.subscribeToTerminalResize(ptyId, (event) => { stream.outputBatcher.flush() const resizeGeneration = stream.resizeGeneration + 1 @@ -2430,6 +2478,8 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [ } }) + signal?.addEventListener('abort', cancelAllPendingPtyWaits, { once: true }) + runtime.registerSubscriptionCleanup( `terminal-multiplex:${connectionId}`, closeMultiplex, @@ -2464,10 +2514,10 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [ } // Why: the left pane's PTY spawns asynchronously after the tab is created. - // Mobile clients that subscribe before the PTY is ready would get a bare + // Clients that subscribe before the PTY is ready would get a bare // scrollback+end with no live stream or phone-fit. Wait for the PTY so // the subscribe can proceed normally. - if (!leaf?.ptyId && isMobile) { + if (!leaf?.ptyId && params.client) { // Why: a never-mounted tab has no graph leaf to await; mounting the // exact tab lets its PTY attach without activating the worktree. rendererMountRequestedBeforePty = runtime.requestRendererTerminalTabMount(params.terminal) diff --git a/src/main/runtime/rpc/terminal-multiplex.test.ts b/src/main/runtime/rpc/terminal-multiplex.test.ts index 99a77fb9afd..f281d72e48e 100644 --- a/src/main/runtime/rpc/terminal-multiplex.test.ts +++ b/src/main/runtime/rpc/terminal-multiplex.test.ts @@ -26,6 +26,7 @@ function stubRuntime(overrides: Partial = {}): OrcaRuntimeSe // only stub the legacy resolveLeafForHandle still bind; tests that need a // null/stale leaf override this explicitly. resolveLiveLeafForHandle: vi.fn().mockReturnValue({ ptyId: 'pty-1' }), + requestRendererTerminalTabMount: vi.fn().mockReturnValue(true), updateRemoteDesktopViewer: vi.fn().mockResolvedValue(true), unregisterRemoteDesktopViewer: vi.fn().mockResolvedValue(true), unregisterRemoteDesktopViewers: vi.fn().mockResolvedValue(true), @@ -40,6 +41,92 @@ function makeRequest(method: string, params?: unknown): RpcRequest { return { id: 'req-1', authToken: 'tok', method, params } } +function startDesktopMultiplexSubscribe( + overrides: Partial = {}, + trace?: string[] +) { + const messages: string[] = [] + const binaryFrames: Uint8Array[] = [] + const handlers = new Map< + number, + (frame: NonNullable>) => void + >() + const cleanups = new Map void>() + const runtime = stubRuntime({ + readTerminal: vi.fn().mockResolvedValue({ tail: [], truncated: false }), + serializeTerminalBuffer: vi.fn().mockResolvedValue({ data: 'snapshot', cols: 120, rows: 40 }), + getTerminalSize: vi.fn().mockReturnValue({ cols: 120, rows: 40 }), + getMobileDisplayMode: vi.fn().mockReturnValue('auto'), + getLayout: vi.fn().mockReturnValue({ seq: 1 }), + subscribeToTerminalData: vi.fn().mockReturnValue(vi.fn()), + subscribeToTerminalResize: vi.fn().mockReturnValue(vi.fn()), + subscribeToFitOverrideChanges: vi.fn().mockReturnValue(vi.fn()), + subscribeToDriverChanges: vi.fn().mockReturnValue(vi.fn()), + getTerminalFitOverride: vi.fn().mockReturnValue(null), + getDriver: vi.fn().mockReturnValue({ kind: 'idle' }), + registerSubscriptionCleanup: vi.fn((id: string, cleanup: () => void) => { + cleanups.set(id, cleanup) + }), + cleanupSubscription: vi.fn((id: string) => { + cleanups.get(id)?.() + }), + ...overrides, + waitForTerminal: + overrides.waitForTerminal ?? vi.fn(() => new Promise(() => {})) + }) + const dispatcher = new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }) + const dispatchPromise = dispatcher.dispatchStreaming( + makeRequest('terminal.multiplex', {}), + (msg) => { + messages.push(msg) + const type = JSON.parse(msg).result?.type + if (type) { + trace?.push(type) + } + }, + { + connectionId: 'conn-desktop-first-paint', + sendBinary: (bytes) => { + binaryFrames.push(bytes) + const opcode = decodeTerminalStreamFrame(bytes)?.opcode + if ( + opcode === TerminalStreamOpcode.SnapshotStart || + opcode === TerminalStreamOpcode.SnapshotChunk || + opcode === TerminalStreamOpcode.SnapshotEnd + ) { + trace?.push('snapshot') + } + }, + registerBinaryStreamHandler: (streamId, handler) => { + handlers.set(streamId, handler) + return () => handlers.delete(streamId) + } + } + ) + return { messages, binaryFrames, handlers, cleanups, runtime, dispatchPromise } +} + +function sendDesktopMultiplexSubscribe( + handlers: Map>) => void> +) { + handlers.get(0)?.( + decodeTerminalStreamFrame( + encodeTerminalStreamFrame({ + opcode: TerminalStreamOpcode.Subscribe, + streamId: 0, + seq: 1, + payload: encodeTerminalStreamJson({ + streamId: 7, + terminal: 'terminal-1', + client: { id: 'desktop-1', type: 'desktop' }, + capabilities: { ackOutput: 1, desktopViewportClaims: 1 }, + viewport: { cols: 120, rows: 40 } + }) + }) + )! + ) +} + describe('terminal multiplex RPC', () => { it('multiplexes terminal streams and routes desktop resize to the source PTY', async () => { vi.useFakeTimers() @@ -2676,11 +2763,15 @@ describe('terminal multiplex RPC', () => { ) await vi.waitFor(() => expect(runtime.waitForLeafPtyId).toHaveBeenCalled()) - expect(runtime.waitForLeafPtyId).toHaveBeenCalledWith('terminal-1', 10_000, controller.signal) + const pendingWaitSignal = vi.mocked(runtime.waitForLeafPtyId).mock.calls[0]?.[2] + expect(runtime.waitForLeafPtyId).toHaveBeenCalledWith( + 'terminal-1', + 10_000, + expect.any(AbortSignal) + ) controller.abort() - await Promise.resolve() - await Promise.resolve() + await vi.waitFor(() => expect(pendingWaitSignal?.aborted).toBe(true)) expect(runtime.readTerminal).not.toHaveBeenCalled() expect( @@ -2694,6 +2785,285 @@ describe('terminal multiplex RPC', () => { await dispatchPromise }) + it("waits for a desktop multiplex subscriber's PTY before retiring the terminal", async () => { + let resolvePty: (ptyId: string) => void = () => {} + const runtime = stubRuntime({ + resolveLiveLeafForHandle: vi.fn().mockReturnValue({ ptyId: null }), + requestRendererTerminalTabMount: vi.fn(), + waitForLeafPtyId: vi.fn( + () => + new Promise((resolve) => { + resolvePty = resolve + }) + ) + }) + const harness = startDesktopMultiplexSubscribe(runtime) + await vi.waitFor(() => + expect(harness.messages.some((msg) => JSON.parse(msg).result?.type === 'ready')).toBe(true) + ) + sendDesktopMultiplexSubscribe(harness.handlers) + await vi.waitFor(() => expect(runtime.waitForLeafPtyId).toHaveBeenCalled()) + expect(runtime.requestRendererTerminalTabMount).toHaveBeenCalledWith('terminal-1') + expect( + harness.binaryFrames + .map((frame) => decodeTerminalStreamFrame(frame)) + .filter((frame) => frame?.opcode === TerminalStreamOpcode.Error) + .map((frame) => (frame ? decodeTerminalStreamText(frame.payload) : '')) + ).toEqual([]) + resolvePty('pty-1') + await vi.waitFor(() => + expect( + harness.binaryFrames.map((frame) => decodeTerminalStreamFrame(frame)?.opcode) + ).toContain(TerminalStreamOpcode.SnapshotChunk) + ) + expect( + harness.binaryFrames + .map((frame) => decodeTerminalStreamFrame(frame)) + .filter((frame) => frame?.opcode === TerminalStreamOpcode.Error) + .map((frame) => (frame ? decodeTerminalStreamText(frame.payload) : '')) + ).toEqual([]) + harness.cleanups.get('terminal-multiplex:conn-desktop-first-paint')?.() + await harness.dispatchPromise + }) + + it('cancels a pending desktop PTY wait when its multiplex slot unsubscribes', async () => { + let resolvePty: (ptyId: string) => void = () => {} + let waitSignal: AbortSignal | undefined + const readTerminal = vi.fn().mockResolvedValue({ tail: [], truncated: false }) + const subscribeToTerminalData = vi.fn().mockReturnValue(vi.fn()) + const registerRemoteTerminalViewSubscriber = vi.fn().mockReturnValue(vi.fn()) + const waitForLeafPtyId = vi.fn( + (_handle: string, _timeoutMs?: number, signal?: AbortSignal) => + new Promise((resolve, reject) => { + resolvePty = resolve + waitSignal = signal + signal?.addEventListener('abort', () => reject(new Error('request_aborted')), { + once: true + }) + }) + ) + const harness = startDesktopMultiplexSubscribe({ + resolveLiveLeafForHandle: vi.fn().mockReturnValue({ ptyId: null }), + waitForLeafPtyId, + readTerminal, + subscribeToTerminalData, + registerRemoteTerminalViewSubscriber + }) + await vi.waitFor(() => + expect(harness.messages.some((msg) => JSON.parse(msg).result?.type === 'ready')).toBe(true) + ) + sendDesktopMultiplexSubscribe(harness.handlers) + await vi.waitFor(() => expect(waitForLeafPtyId).toHaveBeenCalled()) + + harness.handlers.get(7)?.( + decodeTerminalStreamFrame( + encodeTerminalStreamFrame({ + opcode: TerminalStreamOpcode.Unsubscribe, + streamId: 7, + seq: 2, + payload: new Uint8Array() + }) + )! + ) + resolvePty('pty-1') + + await vi.waitFor(() => + expect(waitSignal?.aborted || readTerminal.mock.calls.length > 0).toBe(true) + ) + // Why: a closed pane must not become a hidden live-output consumer when its late PTY appears. + expect(waitSignal?.aborted).toBe(true) + expect(readTerminal).not.toHaveBeenCalled() + expect(subscribeToTerminalData).not.toHaveBeenCalled() + expect(registerRemoteTerminalViewSubscriber).not.toHaveBeenCalled() + expect(harness.handlers.has(7)).toBe(false) + + harness.cleanups.get('terminal-multiplex:conn-desktop-first-paint')?.() + await harness.dispatchPromise + }) + + it("still reports no_connected_pty when a desktop multiplex subscriber's PTY never appears", async () => { + const runtime = stubRuntime({ + resolveLiveLeafForHandle: vi.fn().mockReturnValue({ ptyId: null }), + waitForLeafPtyId: vi.fn().mockRejectedValue(new Error('timeout')) + }) + const harness = startDesktopMultiplexSubscribe(runtime) + await vi.waitFor(() => + expect(harness.messages.some((msg) => JSON.parse(msg).result?.type === 'ready')).toBe(true) + ) + sendDesktopMultiplexSubscribe(harness.handlers) + await vi.waitFor(() => + expect( + harness.binaryFrames.map((frame) => decodeTerminalStreamFrame(frame)?.opcode) + ).toContain(TerminalStreamOpcode.Error) + ) + const errorFrame = harness.binaryFrames + .map((frame) => decodeTerminalStreamFrame(frame)) + .find((frame) => frame?.opcode === TerminalStreamOpcode.Error) + expect(errorFrame && decodeTerminalStreamText(errorFrame.payload)).toBe('no_connected_pty') + harness.cleanups.get('terminal-multiplex:conn-desktop-first-paint')?.() + await harness.dispatchPromise + }) + + it('emits initial desktop fit events after the first multiplex snapshot', async () => { + const trace: string[] = [] + let fitListener: ((event: { mode: string; cols: number; rows: number }) => void) | undefined + let driverListener: ((driver: unknown) => void) | undefined + const harness = startDesktopMultiplexSubscribe( + { + readTerminal: vi.fn(async () => { + fitListener?.({ mode: 'desktop-fit', cols: 100, rows: 30 }) + driverListener?.({ kind: 'transition-during-snapshot' }) + return { tail: [], truncated: false } as unknown as Awaited< + ReturnType + > + }), + subscribeToFitOverrideChanges: vi.fn((_ptyId, listener) => { + fitListener = listener + return vi.fn() + }), + subscribeToDriverChanges: vi.fn((_ptyId, listener) => { + driverListener = listener + return vi.fn() + }) + }, + trace + ) + await vi.waitFor(() => + expect(harness.messages.some((msg) => JSON.parse(msg).result?.type === 'ready')).toBe(true) + ) + sendDesktopMultiplexSubscribe(harness.handlers) + await vi.waitFor(() => expect(trace).toContain('driver-changed')) + expect(trace.lastIndexOf('snapshot')).toBeLessThan(trace.indexOf('fit-override-changed')) + expect(trace.lastIndexOf('snapshot')).toBeLessThan(trace.indexOf('driver-changed')) + harness.cleanups.get('terminal-multiplex:conn-desktop-first-paint')?.() + await harness.dispatchPromise + }) + + it('does not wait when a desktop multiplex subscriber already has a PTY', async () => { + const runtime = stubRuntime({ + requestRendererTerminalTabMount: vi.fn(), + waitForLeafPtyId: vi.fn() + }) + const harness = startDesktopMultiplexSubscribe(runtime) + await vi.waitFor(() => + expect(harness.messages.some((msg) => JSON.parse(msg).result?.type === 'ready')).toBe(true) + ) + sendDesktopMultiplexSubscribe(harness.handlers) + await vi.waitFor(() => + expect(harness.messages.some((msg) => JSON.parse(msg).result?.type === 'subscribed')).toBe( + true + ) + ) + expect(runtime.waitForLeafPtyId).not.toHaveBeenCalled() + expect(runtime.requestRendererTerminalTabMount).not.toHaveBeenCalled() + harness.cleanups.get('terminal-multiplex:conn-desktop-first-paint')?.() + await harness.dispatchPromise + }) + + it('preserves clientless multiplex subscriptions without a PTY wait', async () => { + const messages: string[] = [] + const binaryFrames: Uint8Array[] = [] + const handlers = new Map< + number, + (frame: NonNullable>) => void + >() + let cleanup: () => void = () => {} + const runtime = stubRuntime({ + resolveLiveLeafForHandle: vi.fn().mockReturnValue({ ptyId: null }), + requestRendererTerminalTabMount: vi.fn(), + waitForLeafPtyId: vi.fn(), + registerSubscriptionCleanup: vi.fn((_id: string, callback: () => void) => { + cleanup = callback + }) + }) + const dispatcher = new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }) + const dispatchPromise = dispatcher.dispatchStreaming( + makeRequest('terminal.multiplex', {}), + (msg) => messages.push(msg), + { + connectionId: 'conn-clientless-multiplex', + sendBinary: (bytes) => { + binaryFrames.push(bytes) + }, + registerBinaryStreamHandler: (streamId, handler) => { + handlers.set(streamId, handler) + return () => handlers.delete(streamId) + } + } + ) + await vi.waitFor(() => + expect(messages.some((msg) => JSON.parse(msg).result?.type === 'ready')).toBe(true) + ) + handlers.get(0)?.( + decodeTerminalStreamFrame( + encodeTerminalStreamFrame({ + opcode: TerminalStreamOpcode.Subscribe, + streamId: 0, + seq: 1, + payload: encodeTerminalStreamJson({ streamId: 7, terminal: 'terminal-1' }) + }) + )! + ) + await vi.waitFor(() => + expect(binaryFrames.map((frame) => decodeTerminalStreamFrame(frame)?.opcode)).toContain( + TerminalStreamOpcode.Error + ) + ) + expect(runtime.waitForLeafPtyId).not.toHaveBeenCalled() + expect(runtime.requestRendererTerminalTabMount).not.toHaveBeenCalled() + const errorFrame = binaryFrames + .map((frame) => decodeTerminalStreamFrame(frame)) + .find((frame) => frame?.opcode === TerminalStreamOpcode.Error) + expect(errorFrame && decodeTerminalStreamText(errorFrame.payload)).toBe('no_connected_pty') + cleanup() + await dispatchPromise + }) + + it('preserves clientless legacy subscriptions without a PTY wait or mount', async () => { + const messages: string[] = [] + const runtime = stubRuntime({ + resolveLeafForHandle: vi.fn().mockReturnValue({ ptyId: null }), + waitForLeafPtyId: vi.fn(), + requestRendererTerminalTabMount: vi.fn(), + readTerminal: vi.fn().mockResolvedValue({ tail: ['scrollback'], truncated: false }) + }) + const dispatcher = new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }) + const dispatchPromise = dispatcher.dispatchStreaming( + makeRequest('terminal.subscribe', { terminal: 'terminal-1' }), + (msg) => messages.push(msg), + { connectionId: 'conn-clientless-legacy' } + ) + await dispatchPromise + expect(runtime.waitForLeafPtyId).not.toHaveBeenCalled() + expect(runtime.requestRendererTerminalTabMount).not.toHaveBeenCalled() + expect(messages.map((msg) => JSON.parse(msg).result?.type)).toEqual(['subscribed', 'end']) + }) + + it('waits for a desktop legacy subscriber PTY before the scrollback-only fallback', async () => { + const messages: string[] = [] + const runtime = stubRuntime({ + resolveLeafForHandle: vi.fn().mockReturnValue({ ptyId: null }), + waitForLeafPtyId: vi.fn().mockRejectedValue(new Error('timeout')), + requestRendererTerminalTabMount: vi.fn().mockReturnValue(true), + readTerminal: vi.fn().mockResolvedValue({ tail: ['scrollback'], truncated: false }) + }) + const dispatcher = new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }) + const dispatchPromise = dispatcher.dispatchStreaming( + makeRequest('terminal.subscribe', { + terminal: 'terminal-1', + client: { id: 'desktop-1', type: 'desktop' } + }), + (msg) => messages.push(msg), + { connectionId: 'conn-desktop-legacy' } + ) + await dispatchPromise + // Widened gate: a desktop client must mount + await its late PTY, not skip + // straight to the bare scrollback path the way it did under the mobile-only gate. + expect(runtime.requestRendererTerminalTabMount).toHaveBeenCalledWith('terminal-1') + expect(runtime.waitForLeafPtyId).toHaveBeenCalledWith('terminal-1', 10_000, undefined) + expect(messages.map((msg) => JSON.parse(msg).result?.type)).toEqual(['subscribed', 'end']) + }) + it('keeps view-subscriber releases balanced when a same-streamId subscribe overwrites a blocked one', async () => { const messages: string[] = [] const binaryFrames: Uint8Array[] = [] From 49e625d41ab7940845f9f2ed47e63b51429260a6 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:12:27 -0700 Subject: [PATCH 017/137] fix(mobile): harden WebSocket accept path against socket overload (#9247) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(mobile): make the :6768 WebSocket accept loop leak-proof and self-healing The desktop runtime's mobile WebSocket server on :6768 could wedge with the socket in LISTEN but the TCP accept loop stalled — new connections piling up in SYN_RCVD with zero ESTABLISHED, even from localhost, with no self-heal. Since Orca mobile has no APNs (the persistent WS doubles as the push channel), a wedged accept loop silently drops every notification while the app still shows cached UI. Root cause: connections were only capped at the WebSocket-upgrade layer (MAX_WS_CONNECTIONS), after the socket is already accepted. TCP-level sockets were unbounded, so leaked/half-open sockets (backgrounded phones, flaky relay, reconnect storms) could grow until the process ran out of file descriptors and accept() started failing with EMFILE. - Bound TCP sockets via httpServer.maxConnections (2x the WS cap). At the cap Node accepts-then-closes, so the accept loop always keeps draining and the EMFILE/SYN_RCVD wedge is structurally impossible on this listener. - Handle accept-level errors: ws forwards httpServer 'error' onto the WebSocketServer; with no listener Node rethrows it as an uncaught exception. Swallow + log and keep listening. - Force-terminate over-capacity sockets: a bare ws.close(1013) left half-open phones lingering at >cap forever, rejecting everyone after them. Add an error handler + a 1s terminate fallback so the descriptor is always freed. - Log heartbeat reaping / near-cap live counts so the leak is observable. * fix(mobile): avoid masking fatal accept failures --- src/main/runtime/rpc/ws-transport.test.ts | 40 +++++++++++++++++++++++ src/main/runtime/rpc/ws-transport.ts | 36 +++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/main/runtime/rpc/ws-transport.test.ts b/src/main/runtime/rpc/ws-transport.test.ts index ca34340ccb1..5c02194a835 100644 --- a/src/main/runtime/rpc/ws-transport.test.ts +++ b/src/main/runtime/rpc/ws-transport.test.ts @@ -327,6 +327,46 @@ describe('WebSocketTransport', () => { liveClient.close() }) + it('bounds raw TCP sockets above the WebSocket connection budget', async () => { + // Why: the WebSocket cap applies only after upgrade, so raw sockets need a + // finite independent bound without reducing the 128 legitimate WS slots. + const { transport } = await createTransport() + await transport.start() + + const httpServer = (transport as unknown as { httpServer: { maxConnections: number } }) + .httpServer + expect(httpServer.maxConnections).toBe(256) + }) + + it('force-terminates an over-capacity socket that ignores the close frame', async () => { + // Why: a backgrounded/half-open phone may never ack the 1013 close, so a + // bare ws.close() retains its descriptor until the heartbeat. A reconnect + // flood can fill the TCP headroom during that window, so rejection must + // hard-close on a short fixed deadline. + const { transport } = await createTransport() + await transport.start() + + const ws = await connectWs(transport) + const wss = (transport as unknown as { wss: { clients: Set } }).wss + const serverSocket = Array.from(wss.clients)[0] + expect(serverSocket).toBeDefined() + const terminateSpy = vi.spyOn(serverSocket!, 'terminate') + + vi.useFakeTimers() + try { + ;(transport as unknown as { rejectOverCapacity(ws: WebSocket): void }).rejectOverCapacity( + serverSocket! + ) + vi.advanceTimersByTime(1_000) + } finally { + vi.useRealTimers() + } + + expect(terminateSpy).toHaveBeenCalled() + terminateSpy.mockRestore() + ws.close() + }) + it('is idempotent on double start', async () => { const { transport } = await createTransport() diff --git a/src/main/runtime/rpc/ws-transport.ts b/src/main/runtime/rpc/ws-transport.ts index 0d5403c5991..ec106a63327 100644 --- a/src/main/runtime/rpc/ws-transport.ts +++ b/src/main/runtime/rpc/ws-transport.ts @@ -14,6 +14,11 @@ const MAX_WS_MESSAGE_BYTES = 1024 * 1024 // streams (session tabs, terminals, file watches, browser streams). Keep the // cap high enough that leaked/stale streams do not starve short control RPCs. const MAX_WS_CONNECTIONS = 128 +// Why: hard-bound this listener's descriptor use above the WS-upgrade cap. +// Node accepts then drops sockets beyond maxConnections, so raw/pre-upgrade +// clients cannot grow without bound while the existing WS budget remains +// available to legitimate long-lived streams. +const MAX_TCP_CONNECTIONS = MAX_WS_CONNECTIONS * 2 const PRE_AUTH_TIMEOUT_MS = 10_000 type WebSocketMessagePayload = string | Uint8Array type WebSocketMessageHandler = { @@ -224,6 +229,10 @@ export class WebSocketTransport implements RpcTransport { }) }) + // Why: the WS cap applies only after upgrade. A separate TCP cap prevents + // raw and pre-upgrade sockets from consuming an unbounded descriptor budget. + httpServer.maxConnections = MAX_TCP_CONNECTIONS + const wss = new WebSocketServer({ server: httpServer, maxPayload: MAX_WS_MESSAGE_BYTES @@ -231,7 +240,7 @@ export class WebSocketTransport implements RpcTransport { wss.on('connection', (ws) => { if (wss.clients.size > MAX_WS_CONNECTIONS) { - ws.close(1013, 'Maximum connections reached') + this.rejectOverCapacity(ws) return } this.handleConnection(ws) @@ -242,6 +251,22 @@ export class WebSocketTransport implements RpcTransport { this.startHeartbeat() } + // Why: over the WS-upgrade cap, request a graceful 1013 close but force the + // socket down shortly after. A backgrounded/half-open phone may never ack the + // close frame, so a bare ws.close() can retain the descriptor until the next + // heartbeat. Under a reconnect flood, shortening that window bounds the + // number of rejected sockets that can accumulate behind the WS cap. The + // 'error' listener prevents a reset while closing from becoming unhandled. + private rejectOverCapacity(ws: WebSocket): void { + ws.on('error', () => {}) + ws.close(1013, 'Maximum connections reached') + // Why: give the 1013 close a brief window, then hard-terminate so the + // descriptor is freed even if the client never acks the close frame. + const terminateTimer = setTimeout(() => ws.terminate(), 1_000) + terminateTimer.unref?.() + ws.once('close', () => clearTimeout(terminateTimer)) + } + // Why: ping every live socket on a fixed cadence and terminate any that // didn't pong since the previous tick. This is the only thing that // reliably reaps half-open mobile sockets stranded by background @@ -255,12 +280,14 @@ export class WebSocketTransport implements RpcTransport { if (!wss) { return } + let reaped = 0 for (const ws of wss.clients) { if (!this.wsAlive.has(ws)) { // Why: terminate() (vs close()) skips the close handshake and // immediately fires the 'close' event, freeing the slot. close() // on an already-dead socket can hang for the OS-level TCP timeout. ws.terminate() + reaped++ continue } this.wsAlive.delete(ws) @@ -271,6 +298,13 @@ export class WebSocketTransport implements RpcTransport { // close handler will run regardless, so swallow the throw. } } + // Why: steady reaping or a client count riding the cap are early overload + // signals; surface them without logging on healthy heartbeat ticks. + if (reaped > 0 || wss.clients.size >= MAX_WS_CONNECTIONS) { + console.warn( + `[ws-transport] heartbeat reaped ${reaped}; ${wss.clients.size} tracked sockets` + ) + } }, this.heartbeatIntervalMs) if (typeof this.heartbeatTimer.unref === 'function') { this.heartbeatTimer.unref() From 546cc8237fbf5c4ff686949eeb0ed9d159522d66 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:18:27 -0700 Subject: [PATCH 018/137] Improve native chat ui (#9246) * refactor(native-chat): extract option appliers and improve toggle UI - Extract setOption apply logic to native-chat-session-option-apply.ts - Add invokeAction method for toggle-only options without tracked baseline - Replace checkbox UI with On/Off radio groups for boolean options - Display option values only in pills (remove redundant label prefixes) - Serialize concurrent applies so later dispatches win in order * Remove focus-visible border from composer container The container uses a steady hairline border (no focus/click flash), with focus-visible styling delegated to the inner textarea. The container is a layout wrapper, not a focus target. * Prevent option commits on model switch during dispatch When a user changes an option and the model switches before dispatch completes, committing the stale option would overwrite state under the new model. Guard by capturing the baseline option state before dispatch and validating it hasn't changed post-dispatch. * Use invokeAction for model option in NativeChatComposer test Codex model is an agent picker mid-session, so setOption rejects. Update test to use invokeAction to match the actual UI behavior. --- .../native-chat/NativeChatComposer.test.tsx | 3 +- .../native-chat/NativeChatComposerField.tsx | 7 +- .../native-chat/NativeChatMessageList.tsx | 2 + .../NativeChatSessionOptionPickers.test.tsx | 291 +++++++++++++--- .../NativeChatSessionOptionPickers.tsx | 126 ++++--- .../native-chat-pty-session-options.test.ts | 303 ++++++++++++++++- .../native-chat-pty-session-options.ts | 141 +------- .../native-chat-session-option-apply.ts | 314 ++++++++++++++++++ ...ive-chat-session-option-command-builder.ts | 4 +- ...e-chat-session-option-command-recording.ts | 7 +- .../native-chat-session-option-flip.ts | 45 +++ .../native-chat-session-option-labels.ts | 21 +- .../native-chat-session-option-snapshot.ts | 5 +- src/renderer/src/i18n/locales/en.json | 8 +- src/renderer/src/i18n/locales/es.json | 8 +- src/renderer/src/i18n/locales/ja.json | 8 +- src/renderer/src/i18n/locales/ko.json | 8 +- src/renderer/src/i18n/locales/zh.json | 8 +- .../src/i18n/native-chat-locales.test.ts | 9 +- src/shared/native-chat-session-options.ts | 8 +- 20 files changed, 1060 insertions(+), 266 deletions(-) create mode 100644 src/renderer/src/components/native-chat/native-chat-session-option-apply.ts create mode 100644 src/renderer/src/components/native-chat/native-chat-session-option-flip.ts diff --git a/src/renderer/src/components/native-chat/NativeChatComposer.test.tsx b/src/renderer/src/components/native-chat/NativeChatComposer.test.tsx index f1f1794cc3b..6a48118b51a 100644 --- a/src/renderer/src/components/native-chat/NativeChatComposer.test.tsx +++ b/src/renderer/src/components/native-chat/NativeChatComposer.test.tsx @@ -371,8 +371,9 @@ describe('NativeChatComposer', () => { /> ) + // Why: Codex model is agent-picker mid-session — setOption rejects; UI uses invokeAction. await act(async () => { - await mocks.fieldProps?.sessionOptionsSurface?.setOption('model', '') + await mocks.fieldProps?.sessionOptionsSurface?.invokeAction('model') }) expect(mocks.sendNativeChatMessageVerified).toHaveBeenCalledWith( diff --git a/src/renderer/src/components/native-chat/NativeChatComposerField.tsx b/src/renderer/src/components/native-chat/NativeChatComposerField.tsx index 15dc1366de7..fd76146650b 100644 --- a/src/renderer/src/components/native-chat/NativeChatComposerField.tsx +++ b/src/renderer/src/components/native-chat/NativeChatComposerField.tsx @@ -122,8 +122,11 @@ export function NativeChatComposerField({
    {imageAttachments.length > 0 ? ( diff --git a/src/renderer/src/components/native-chat/NativeChatMessageList.tsx b/src/renderer/src/components/native-chat/NativeChatMessageList.tsx index 71aee362083..354732ba0d8 100644 --- a/src/renderer/src/components/native-chat/NativeChatMessageList.tsx +++ b/src/renderer/src/components/native-chat/NativeChatMessageList.tsx @@ -377,6 +377,8 @@ export function NativeChatMessageList({ >
    ({ - translate: (_key: string, fallback: string, values?: Record) => - fallback.replace('{{value0}}', values?.value0 ?? '') + translate: (_key: string, fallback: string, values?: Record) => { + if (!values) { + return fallback + } + return Object.entries(values).reduce( + (text, [name, value]) => text.replaceAll(`{{${name}}}`, String(value)), + fallback + ) + } })) vi.mock('@/components/ui/button', () => ({ @@ -21,44 +29,99 @@ vi.mock('@/components/ui/tooltip', () => ({ TooltipContent: ({ children }: { children: React.ReactNode }) =>
    {children}
    })) -vi.mock('@/components/ui/dropdown-menu', () => ({ - DropdownMenu: ({ children }: { children: React.ReactNode }) =>
    {children}
    , - DropdownMenuTrigger: ({ - children, - disabled - }: { - children: React.ReactNode - disabled?: boolean - }) =>
    {children}
    , - DropdownMenuContent: ({ children }: { children: React.ReactNode }) =>
    {children}
    , - DropdownMenuLabel: ({ children }: { children: React.ReactNode }) =>
    {children}
    , - DropdownMenuSeparator: () =>
    , - DropdownMenuItem: ({ children, disabled }: React.ButtonHTMLAttributes) => ( - - ), - DropdownMenuCheckboxItem: ({ - children, - disabled - }: React.ButtonHTMLAttributes) => ( - - ), - DropdownMenuRadioGroup: ({ children }: { children: React.ReactNode }) =>
    {children}
    , - DropdownMenuRadioItem: ({ - children, - disabled, - value - }: React.ButtonHTMLAttributes & { value: string }) => ( - - ) -})) +vi.mock('@/components/ui/dropdown-menu', () => { + const React = require('react') as typeof ReactModule + return { + DropdownMenu: ({ children }: { children: React.ReactNode }) =>
    {children}
    , + DropdownMenuTrigger: ({ + children, + disabled + }: { + children: React.ReactNode + disabled?: boolean + }) =>
    {children}
    , + DropdownMenuContent: ({ children }: { children: React.ReactNode }) =>
    {children}
    , + DropdownMenuLabel: ({ children }: { children: React.ReactNode }) =>
    {children}
    , + DropdownMenuSeparator: () =>
    , + DropdownMenuItem: ({ + children, + disabled, + onSelect + }: React.ButtonHTMLAttributes & { onSelect?: () => void }) => ( + + ), + // Why: exercises value binding + onValueChange contract the real Radix + // group provides; selected value is exposed via data-radio-value. + DropdownMenuRadioGroup: ({ + children, + value, + onValueChange + }: { + children: React.ReactNode + value?: string + onValueChange?: (value: string) => void + }) => ( +
    + {React.Children.map(children, (child) => { + if (!React.isValidElement(child)) { + return child + } + const props = child.props as { + value?: string + disabled?: boolean + children?: React.ReactNode + } + const selected = props.value !== undefined && props.value === value + return ( + + ) + })} +
    + ), + DropdownMenuRadioItem: ({ + children, + disabled, + value + }: React.ButtonHTMLAttributes & { value: string }) => ( + // Why: parent RadioGroup mock reads `value` via Children.map — keep it on + // props even though native span has no value attribute. + )} + > + {children} + + ) + } +}) import { NativeChatSessionOptionPickers } from './NativeChatSessionOptionPickers' const surface = { getSnapshot: vi.fn(() => []), setOption: vi.fn(), + invokeAction: vi.fn(), subscribe: vi.fn(() => vi.fn()) } @@ -117,21 +180,24 @@ describe('NativeChatSessionOptionPickers', () => { isWorking={false} /> ) - expect(screen.getByRole('button', { name: 'Model' }).textContent).toContain('Model: Opus 4.8') - expect(screen.getByRole('button', { name: 'Effort' }).textContent).toContain( - 'Effort: High · Fast' + expect(screen.getByRole('button', { name: 'Model Opus 4.8' }).textContent).toContain('Opus 4.8') + expect(screen.getByRole('button', { name: 'Model Opus 4.8' }).textContent).not.toContain( + 'Model:' + ) + expect(screen.getByRole('button', { name: 'Effort High · Fast' }).textContent).toContain( + 'High · Fast' ) expect( screen - .getByRole('button', { name: 'Effort' }) - .compareDocumentPosition(screen.getByRole('button', { name: 'Model' })) & + .getByRole('button', { name: 'Effort High · Fast' }) + .compareDocumentPosition(screen.getByRole('button', { name: 'Model Opus 4.8' })) & Node.DOCUMENT_POSITION_FOLLOWING ).not.toBe(0) rerender( ) - expect(screen.queryByRole('button', { name: 'Effort' })).toBeNull() + expect(screen.queryByRole('button', { name: /Effort/ })).toBeNull() }) it('names a lone unknown effort control explicitly', () => { @@ -154,10 +220,14 @@ describe('NativeChatSessionOptionPickers', () => { ) expect( - screen.getByRole('button', { name: 'Model' }).parentElement?.getAttribute('data-disabled') + screen + .getByRole('button', { name: 'Model Opus 4.8' }) + .parentElement?.getAttribute('data-disabled') ).toBe('true') expect( - screen.getByRole('button', { name: 'Effort' }).parentElement?.getAttribute('data-disabled') + screen + .getByRole('button', { name: 'Effort High' }) + .parentElement?.getAttribute('data-disabled') ).toBe('true') }) @@ -188,8 +258,10 @@ describe('NativeChatSessionOptionPickers', () => { isWorking={false} /> ) - expect(screen.getByRole('button', { name: 'Session options' }).textContent).toContain('Fast') - expect(screen.queryByRole('button', { name: 'Effort' })).toBeNull() + expect(screen.getByRole('button', { name: 'Session options Fast' }).textContent).toContain( + 'Fast' + ) + expect(screen.queryByRole('button', { name: /^Effort/ })).toBeNull() }) it('shows the unconfirmed hint for dispatched values', () => { @@ -203,10 +275,12 @@ describe('NativeChatSessionOptionPickers', () => { expect(screen.getByText('Sent to the agent — not confirmed')).not.toBeNull() }) - it('renders agent-picker routes as one action instead of radio choices', () => { + it('renders agent-picker routes as one action instead of radio choices', async () => { + const invokeAction = vi.fn().mockResolvedValue({ snapshot: [] }) + const liveSurface = { ...surface, invokeAction } render( { expect(screen.getByRole('button', { name: 'Choose in agent picker…' })).not.toBeNull() expect(screen.queryByText('GPT-5.5')).toBeNull() expect(screen.queryByText('GPT-5.2 Codex')).toBeNull() + screen.getByRole('button', { name: 'Choose in agent picker…' }).click() + await waitFor(() => expect(invokeAction).toHaveBeenCalledWith('model')) + }) + + it('uses a Toggle action for unknown flip-only options via invokeAction', async () => { + const invokeAction = vi.fn().mockResolvedValue({ snapshot: [] }) + const setOption = vi.fn().mockResolvedValue({ snapshot: [] }) + const liveSurface = { ...surface, setOption, invokeAction } + render( + + ) + expect(screen.getByText('Toggle fast mode')).not.toBeNull() + expect(screen.queryByText('On')).toBeNull() + expect(screen.queryByText('Off')).toBeNull() + screen.getByText('Toggle fast mode').click() + await waitFor(() => expect(invokeAction).toHaveBeenCalledWith('fastMode')) + expect(setOption).not.toHaveBeenCalled() + }) + + it('uses On/Off radios for known boolean options without inventing a selection', async () => { + const setOption = vi.fn().mockResolvedValue({ snapshot: [] }) + const liveSurface = { ...surface, setOption } + const { rerender } = render( + + ) + expect(screen.queryByText('Toggle fast mode')).toBeNull() + const onRadio = screen.getByRole('radio', { name: 'On' }) + expect(onRadio.getAttribute('data-state')).toBe('checked') + expect(onRadio.getAttribute('aria-checked')).toBe('true') + const fastGroup = onRadio.parentElement + expect(fastGroup?.getAttribute('data-radio-value')).toBe('on') + expect(fastGroup?.getAttribute('data-on-value-change')).toBe('1') + screen.getByRole('radio', { name: 'Off' }).click() + await waitFor(() => expect(setOption).toHaveBeenCalledWith('fastMode', false)) + + setOption.mockClear() + rerender( + + ) + // Unknown composed boolean: hint + radios present, nothing pre-selected. + expect(screen.getByText('Current value unknown — pick On or Off')).not.toBeNull() + const thinkingGroup = screen.getByRole('radio', { name: 'On' }).parentElement + expect(thinkingGroup?.getAttribute('data-radio-value')).toBe('') + screen.getByRole('radio', { name: 'Off' }).click() + await waitFor(() => expect(setOption).toHaveBeenCalledWith('thinking', false)) + }) + + it('does not show unconfirmed for applied flip-only booleans', () => { + render( + + ) + expect(screen.queryByText('Sent to the agent — not confirmed')).toBeNull() + }) + + it('shows unconfirmed for confirmable dispatched booleans', () => { + render( + + ) + expect(screen.getByText('Sent to the agent — not confirmed')).not.toBeNull() }) }) diff --git a/src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.tsx b/src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.tsx index 15852d27db5..75ac157f93b 100644 --- a/src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.tsx +++ b/src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.tsx @@ -4,7 +4,6 @@ import { toast } from 'sonner' import { Button } from '@/components/ui/button' import { DropdownMenu, - DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, @@ -78,6 +77,15 @@ function PickerTrigger(props: { disabledReason?: string | null dispatched: boolean }): React.JSX.Element { + // Why: value-only visible text must still include the category in the + // accessible name (WCAG 2.5.3 Label in Name / voice control). + const accessibleName = + props.label === props.tooltipLabel + ? props.tooltipLabel + : translate('components.native-chat.composer.pillAccessibleName', '{{value0}} {{value1}}', { + value0: props.tooltipLabel, + value1: props.label + }) return ( @@ -86,7 +94,7 @@ function PickerTrigger(props: { type="button" variant="ghost" size="xs" - aria-label={props.tooltipLabel} + aria-label={accessibleName} className="max-w-48 text-muted-foreground" > {props.label} @@ -116,49 +124,62 @@ function ChoiceBody(props: { label: string; description?: string }): React.JSX.E ) } -function actionLabel(descriptor: SessionOptionDescriptor): string { - if (descriptor.action?.type === 'agent-picker') { - return translate( - 'components.native-chat.composer.chooseInAgentPicker', - 'Choose in agent picker…' - ) - } - return translate('components.native-chat.composer.toggleOption', 'Toggle {{value0}}', { - value0: nativeChatSessionOptionLabel(descriptor).toLowerCase() - }) -} - function DescriptorMenuRows(props: { descriptor: SessionOptionDescriptor pending: boolean setValue: (value: SessionOptionValue) => void + invokeAction: () => void }): React.JSX.Element { - const { descriptor, pending, setValue } = props - if (descriptor.action) { + const { descriptor, pending, setValue, invokeAction } = props + // Why: flip-only without a baseline is an action — never claim On/Off. + if (descriptor.action?.type === 'toggle-command') { return ( - - setValue( - descriptor.kind.type === 'boolean' - ? !(descriptor.kind.currentValue ?? false) - : (descriptor.kind.currentValue ?? '') - ) - } - > - {actionLabel(descriptor)} + invokeAction()}> + {translate('components.native-chat.composer.toggleOption', 'Toggle {{value0}}', { + value0: nativeChatSessionOptionLabel(descriptor).toLowerCase() + })} ) } - if (descriptor.kind.type === 'boolean') { + // Why: agent-picker opens the TUI; it is not a set of radio choices. + if (descriptor.action?.type === 'agent-picker') { return ( - setValue(checked === true)} - > - {nativeChatSessionOptionLabel(descriptor)} - + invokeAction()}> + {translate( + 'components.native-chat.composer.chooseInAgentPicker', + 'Choose in agent picker…' + )} + + ) + } + // Why: absolute On/Off only when we have tracked truth. Unknown composed + // booleans leave the group unselected so empty radios are not a selection. + if (descriptor.kind.type === 'boolean') { + const selected = + descriptor.kind.currentValue === true + ? 'on' + : descriptor.kind.currentValue === false + ? 'off' + : undefined + return ( + <> + {selected === undefined ? ( + + {translate( + 'components.native-chat.composer.valueUnknown', + 'Current value unknown — pick On or Off' + )} + + ) : null} + setValue(next === 'on')}> + + {translate('components.native-chat.composer.optionValue.on', 'On')} + + + {translate('components.native-chat.composer.optionValue.off', 'Off')} + + + ) } return ( @@ -182,6 +203,22 @@ function DescriptorMenuRows(props: { ) } +function runSurfaceCall( + pendingKey: string, + setPendingId: (id: string | null) => void, + call: () => Promise +): void { + setPendingId(pendingKey) + void call() + .catch((error) => { + toast.error( + translate('components.native-chat.composer.optionUpdateFailed', 'Could not update option'), + { description: error instanceof Error ? error.message : String(error) } + ) + }) + .finally(() => setPendingId(null)) +} + function NativeChatSessionOptionPickersInner({ surface, snapshot, @@ -195,19 +232,10 @@ function NativeChatSessionOptionPickersInner({ } const setOption = (descriptor: SessionOptionDescriptor, value: SessionOptionValue): void => { - setPendingId(descriptor.id) - void surface - .setOption(descriptor.id, value) - .catch((error) => { - toast.error( - translate( - 'components.native-chat.composer.optionUpdateFailed', - 'Could not update option' - ), - { description: error instanceof Error ? error.message : String(error) } - ) - }) - .finally(() => setPendingId(null)) + runSurfaceCall(descriptor.id, setPendingId, () => surface.setOption(descriptor.id, value)) + } + const invokeAction = (descriptor: SessionOptionDescriptor): void => { + runSurfaceCall(descriptor.id, setPendingId, () => surface.invokeAction(descriptor.id)) } const modelReason = nativeChatSessionOptionDisabledReason(model.disabledReason) @@ -243,6 +271,7 @@ function NativeChatSessionOptionPickersInner({ descriptor={descriptor} pending={pendingId !== null} setValue={(value) => setOption(descriptor, value)} + invokeAction={() => invokeAction(descriptor)} />
    ) @@ -266,6 +295,7 @@ function NativeChatSessionOptionPickersInner({ descriptor={model} pending={pendingId !== null} setValue={(value) => setOption(model, value)} + invokeAction={() => invokeAction(model)} /> diff --git a/src/renderer/src/components/native-chat/native-chat-pty-session-options.test.ts b/src/renderer/src/components/native-chat/native-chat-pty-session-options.test.ts index 771daacd23e..8475167186f 100644 --- a/src/renderer/src/components/native-chat/native-chat-pty-session-options.test.ts +++ b/src/renderer/src/components/native-chat/native-chat-pty-session-options.test.ts @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { clearNativeChatSessionOptionCacheForTests, + readNativeChatSessionOptionCache, seedNativeChatAppliedSessionOptions } from './native-chat-session-option-cache' import { createNativeChatPtySessionOptions } from './native-chat-pty-session-options' @@ -206,7 +207,179 @@ describe('native chat PTY session options', () => { expect(onAgentPicker).not.toHaveBeenCalled() }) - it('keeps an unknown toggle unknown after the one-shot action', async () => { + it('leaves flip-only unknown after a one-shot so the UI never invents on/off', async () => { + seedNativeChatAppliedSessionOptions('pty-1', 'claude', { + model: 'opus', + effort: 'high' + }) + const dispatch = vi.fn() + const persist = vi.fn() + const surface = createNativeChatPtySessionOptions({ + agent: 'claude', + scopeKey: 'pty-1', + mode: 'live', + dispatchCommand: dispatch, + persistSelection: persist + })! + const fastBefore = surface.getSnapshot().find(({ id }) => id === 'fastMode') + expect(fastBefore?.action?.type).toBe('toggle-command') + expect(fastBefore?.kind).toMatchObject({ type: 'boolean' }) + expect(fastBefore?.kind).not.toHaveProperty('defaultValue') + + await expect(surface.setOption('fastMode', true)).rejects.toThrow( + 'Current value is unknown; use the Toggle action instead.' + ) + expect(dispatch).not.toHaveBeenCalled() + const result = await surface.invokeAction('fastMode') + expect(dispatch).toHaveBeenCalledWith('/fast') + // Why: a flip-only command never reports an absolute value. + expect(result.snapshot.find(({ id }) => id === 'fastMode')).toMatchObject({ + valueSource: 'unknown', + action: { type: 'toggle-command' } + }) + expect(persist).not.toHaveBeenCalled() + }) + + it('no-ops a seeded toggle when already at the requested value', async () => { + seedNativeChatAppliedSessionOptions('pty-1', 'claude', { + model: 'opus', + effort: 'high', + fastMode: true + }) + const dispatch = vi.fn() + const surface = createNativeChatPtySessionOptions({ + agent: 'claude', + scopeKey: 'pty-1', + mode: 'live', + dispatchCommand: dispatch + })! + + const result = await surface.setOption('fastMode', true) + expect(dispatch).not.toHaveBeenCalled() + expect(result.snapshot.find(({ id }) => id === 'fastMode')).toMatchObject({ + valueSource: 'applied', + kind: { type: 'boolean', currentValue: true } + }) + }) + + it('no-ops a known toggle at the same absolute target (flip is not set-to-value)', async () => { + seedNativeChatAppliedSessionOptions('pty-1', 'claude', { + model: 'opus', + effort: 'high', + fastMode: true + }) + const dispatch = vi.fn() + const surface = createNativeChatPtySessionOptions({ + agent: 'claude', + scopeKey: 'pty-1', + mode: 'live', + dispatchCommand: dispatch + })! + + await surface.setOption('fastMode', false) + dispatch.mockClear() + // Why: a second same-target set would re-send `/fast` and invert the agent + // if the first flip landed — unlike set-to-value commands, flips cannot retry. + const result = await surface.setOption('fastMode', false) + expect(dispatch).not.toHaveBeenCalled() + expect(result.snapshot.find(({ id }) => id === 'fastMode')).toMatchObject({ + valueSource: 'applied', + kind: { type: 'boolean', currentValue: false } + }) + }) + + it('dispatches the opposite absolute target for a known toggle', async () => { + seedNativeChatAppliedSessionOptions('pty-1', 'claude', { + model: 'opus', + effort: 'high', + fastMode: true + }) + const dispatch = vi.fn() + const surface = createNativeChatPtySessionOptions({ + agent: 'claude', + scopeKey: 'pty-1', + mode: 'live', + dispatchCommand: dispatch + })! + + await surface.setOption('fastMode', false) + dispatch.mockClear() + const result = await surface.setOption('fastMode', true) + expect(dispatch).toHaveBeenCalledWith('/fast') + expect(result.snapshot.find(({ id }) => id === 'fastMode')).toMatchObject({ + valueSource: 'applied', + kind: { type: 'boolean', currentValue: true } + }) + }) + + it('tracks a known toggle flip as applied without persisting', async () => { + seedNativeChatAppliedSessionOptions('pty-1', 'claude', { + model: 'opus', + effort: 'high', + fastMode: true + }) + const dispatch = vi.fn() + const persist = vi.fn() + const surface = createNativeChatPtySessionOptions({ + agent: 'claude', + scopeKey: 'pty-1', + mode: 'live', + dispatchCommand: dispatch, + persistSelection: persist + })! + + const result = await surface.setOption('fastMode', false) + expect(dispatch).toHaveBeenCalledWith('/fast') + // Why: flip-only never heals; applied is best-known absolute, not dispatched. + expect(result.snapshot.find(({ id }) => id === 'fastMode')).toMatchObject({ + valueSource: 'applied', + kind: { type: 'boolean', currentValue: false } + }) + expect(result.snapshot.find(({ id }) => id === 'fastMode')?.action).toBeUndefined() + expect(persist).not.toHaveBeenCalled() + }) + + it('serializes concurrent setOption calls so later writes win in order', async () => { + seedNativeChatAppliedSessionOptions('pty-1', 'claude', { + model: 'opus', + effort: 'high', + fastMode: true + }) + let releaseFirst: (() => void) | undefined + const dispatch = vi.fn((command: string) => { + if (command === '/fast') { + return new Promise((resolve) => { + releaseFirst = resolve + }) + } + return Promise.resolve() + }) + const surface = createNativeChatPtySessionOptions({ + agent: 'claude', + scopeKey: 'pty-1', + mode: 'live', + dispatchCommand: dispatch + })! + + const first = surface.setOption('fastMode', false) + const second = surface.setOption('effort', 'low') + // Why: appliers queue on microtasks — wait for the first dispatch to start. + await vi.waitFor(() => expect(dispatch).toHaveBeenCalledTimes(1)) + // Second stays queued until first finishes. + expect(dispatch).not.toHaveBeenCalledWith('/effort low') + releaseFirst?.() + await first + await second + expect(dispatch).toHaveBeenCalledWith('/effort low') + expect(surface.getSnapshot().find(({ id }) => id === 'effort')).toMatchObject({ + kind: { currentValue: 'low' } + }) + expect(surface.getSnapshot().find(({ id }) => id === 'fastMode')).toMatchObject({ + kind: { currentValue: false } + }) + }) + + it('stays unknown after a typed flip then a picker toggle (no invented absolute)', async () => { seedNativeChatAppliedSessionOptions('pty-1', 'claude', { model: 'opus', effort: 'high' @@ -218,15 +391,131 @@ describe('native chat PTY session options', () => { mode: 'live', dispatchCommand: dispatch })! - const fastBefore = surface.getSnapshot().find(({ id }) => id === 'fastMode') - expect(fastBefore?.action?.type).toBe('toggle-command') - const result = await surface.setOption('fastMode', true) - expect(dispatch).toHaveBeenCalledWith('/fast') - expect(result.snapshot.find(({ id }) => id === 'fastMode')).toMatchObject({ + // Typed `/fast` clears any prior tracking; option stays unknown. + surface.recordOutgoingCommand('/fast') + expect(surface.getSnapshot().find(({ id }) => id === 'fastMode')).toMatchObject({ valueSource: 'unknown', action: { type: 'toggle-command' } }) + + await surface.invokeAction('fastMode') + expect(dispatch).toHaveBeenCalledWith('/fast') + expect(surface.getSnapshot().find(({ id }) => id === 'fastMode')).toMatchObject({ + valueSource: 'unknown', + action: { type: 'toggle-command' } + }) + }) + + it('does not re-assert absolute state when a typed flip clears tracking mid-dispatch', async () => { + seedNativeChatAppliedSessionOptions('pty-1', 'claude', { + model: 'opus', + effort: 'high', + fastMode: true + }) + let resolveDispatch: (() => void) | undefined + const dispatch = vi.fn( + () => + new Promise((resolve) => { + resolveDispatch = resolve + }) + ) + const surface = createNativeChatPtySessionOptions({ + agent: 'claude', + scopeKey: 'pty-1', + mode: 'live', + dispatchCommand: dispatch + })! + + const pending = surface.setOption('fastMode', false) + await vi.waitFor(() => expect(dispatch).toHaveBeenCalled()) + // Why: typed `/fast` during await must win — do not write the picker value after. + surface.recordOutgoingCommand('/fast') + resolveDispatch?.() + await pending + expect(surface.getSnapshot().find(({ id }) => id === 'fastMode')).toMatchObject({ + valueSource: 'unknown', + action: { type: 'toggle-command' } + }) + }) + + it('does not write flip state onto a model that changed mid-dispatch', async () => { + seedNativeChatAppliedSessionOptions('pty-1', 'claude', { + model: 'opus', + effort: 'high', + fastMode: true + }) + let resolveDispatch: (() => void) | undefined + const dispatch = vi.fn( + () => + new Promise((resolve) => { + resolveDispatch = resolve + }) + ) + const surface = createNativeChatPtySessionOptions({ + agent: 'claude', + scopeKey: 'pty-1', + mode: 'live', + dispatchCommand: dispatch + })! + + const pending = surface.setOption('fastMode', false) + await vi.waitFor(() => expect(dispatch).toHaveBeenCalled()) + surface.recordOutgoingCommand('/model sonnet') + resolveDispatch?.() + await pending + expect(surface.getSnapshot().find(({ id }) => id === 'model')).toMatchObject({ + kind: { currentValue: 'sonnet' } + }) + // Why: only opus carries fastMode in the catalog. The aborted flip must not + // pollute the destination model bucket, and must not rewrite the source. + const cached = readNativeChatSessionOptionCache('pty-1') + expect(cached?.valuesByModel.sonnet?.fastMode).toBeUndefined() + expect(cached?.valuesByModel.opus?.fastMode).toMatchObject({ + value: true, + source: 'applied' + }) + }) + + it('does not commit a non-flip option onto a model that changed mid-dispatch', async () => { + seedNativeChatAppliedSessionOptions('pty-1', 'claude', { + model: 'opus', + effort: 'high' + }) + let resolveDispatch: (() => void) | undefined + const dispatch = vi.fn( + () => + new Promise((resolve) => { + resolveDispatch = resolve + }) + ) + const persist = vi.fn() + const surface = createNativeChatPtySessionOptions({ + agent: 'claude', + scopeKey: 'pty-1', + mode: 'live', + dispatchCommand: dispatch, + persistSelection: persist + })! + + const pending = surface.setOption('effort', 'xhigh') + await vi.waitFor(() => expect(dispatch).toHaveBeenCalled()) + // Why: a model switch during await must win — do not write effort after. + surface.recordOutgoingCommand('/model sonnet') + resolveDispatch?.() + await pending + + // Why: the aborted effort commit must not land under the destination model + // nor persist there, and must leave the source model untouched. + const cached = readNativeChatSessionOptionCache('pty-1') + expect(cached?.valuesByModel.sonnet?.effort).toBeUndefined() + expect(cached?.valuesByModel.opus?.effort).toMatchObject({ + value: 'high', + source: 'applied' + }) + expect(persist).not.toHaveBeenCalledWith( + expect.objectContaining({ optionId: 'effort', modelId: 'sonnet' }) + ) }) it('hands Codex model changes to the TUI picker and drops stale truth', async () => { @@ -247,7 +536,7 @@ describe('native chat PTY session options', () => { 'agent-picker' ) - const result = await surface.setOption('effort', 'xhigh') + const result = await surface.invokeAction('effort') expect(dispatch).toHaveBeenCalledWith('/model') expect(onAgentPicker).toHaveBeenCalledOnce() expect(result.snapshot).toHaveLength(1) diff --git a/src/renderer/src/components/native-chat/native-chat-pty-session-options.ts b/src/renderer/src/components/native-chat/native-chat-pty-session-options.ts index 41ce304011c..a97b471edee 100644 --- a/src/renderer/src/components/native-chat/native-chat-pty-session-options.ts +++ b/src/renderer/src/components/native-chat/native-chat-pty-session-options.ts @@ -1,10 +1,6 @@ import { - findCatalogModel, - findCatalogOption, getAgentSessionOptionCatalog, - type CatalogMidSessionApply, - type CatalogModel, - type CatalogOptionApply + type CatalogModel } from '../../../../shared/agent-session-option-catalog' import type { AgentType } from '../../../../shared/agent-status-types' import type { @@ -17,16 +13,12 @@ import { readNativeChatSessionOptionCache, writeNativeChatSessionOptionCache } from './native-chat-session-option-cache' +import { createSessionOptionAppliers } from './native-chat-session-option-apply' import { buildNativeChatSessionOptionSnapshot, - flattenNativeChatSessionOptionRecord, type NativeChatSessionOptionMode } from './native-chat-session-option-snapshot' -import { buildNativeChatSessionOptionCommand } from './native-chat-session-option-command-builder' -import type { - NativeChatSessionOptionDispatchCommand, - NativeChatSessionOptionDispatchResult -} from './native-chat-session-option-command-dispatch' +import type { NativeChatSessionOptionDispatchCommand } from './native-chat-session-option-command-dispatch' import { applyNativeChatReportedSessionOptions } from './native-chat-session-option-reporting' import { recordNativeChatSessionOptionCommand } from './native-chat-session-option-command-recording' @@ -129,121 +121,24 @@ export function createNativeChatPtySessionOptions( } } - const currentApply = ( - optionId: string - ): { apply: CatalogOptionApply; modelId: string | null } | null => { - const modelId = typeof record.model?.value === 'string' ? record.model.value : null - if (optionId === 'model') { - return { apply: catalog.modelApply, modelId } - } - const model = modelId ? findCatalogModel({ ...catalog, models }, modelId) : undefined - const option = findCatalogOption(model, optionId) - return option ? { apply: option.apply, modelId } : null - } - - const handleAgentPicker = async (midSession: CatalogMidSessionApply): Promise => { - if (midSession.kind !== 'agent-picker') { - return - } - await args.dispatchCommand(midSession.command) - clearModelTruth() - publish() - args.onAgentPicker?.() - } - - const setOption = async (id: string, value: SessionOptionValue) => { - const resolved = currentApply(id) - if (!resolved) { - throw new Error(`Unknown session option: ${id}`) - } - const { apply, modelId: previousModelId } = resolved - if (args.mode === 'live' && apply.midSession?.kind === 'agent-picker') { - await handleAgentPicker(apply.midSession) - return { snapshot } - } - const source = args.mode === 'live' ? 'dispatched' : 'applied' - let dispatchResult: NativeChatSessionOptionDispatchResult | void = undefined - const toggleWasKnown = - apply.midSession?.kind === 'toggle-command' && previousModelId - ? record.valuesByModel[previousModelId]?.[id] !== undefined - : false - if (args.mode === 'live') { - const command = buildNativeChatSessionOptionCommand({ - optionId: id, - value, - apply, - modelId: previousModelId, - catalog, - models, - record - }) - if (!command) { - throw new Error('This option can only be set when the session starts.') - } - const detectAgentInteraction = - apply.midSession?.kind === 'command' - ? apply.midSession.detectAgentInteraction - : apply.composedIntoModel && catalog.modelApply.midSession?.kind === 'command' - ? catalog.modelApply.midSession.detectAgentInteraction - : undefined - const expectedChoiceLabel = - id === 'model' && typeof value === 'string' - ? (findCatalogModel({ ...catalog, models }, value)?.label ?? value) - : undefined - dispatchResult = detectAgentInteraction - ? await args.dispatchCommand(command, { - detectAgentInteraction, - expectedChoiceLabel - }) - : await args.dispatchCommand(command) - } else if (!apply.launchArgs && !apply.composedIntoModel) { - throw new Error('This option is only available after the session starts.') - } - - if (dispatchResult?.outcome === 'rejected') { - throw new Error('Claude kept the current model.') - } - if (dispatchResult?.outcome === 'unknown') { - clearModelTruth() - publish() - throw new Error('Could not verify the model change; open the terminal to check.') - } - if (dispatchResult?.outcome === 'interaction-required') { - clearModelTruth() - publish() - args.onAgentPicker?.() - return { snapshot } - } - - if (apply.midSession?.kind === 'toggle-command' && !toggleWasKnown) { - return { snapshot: publish() } - } - if (id === 'model' && previousModelId !== value) { - record.model = undefined - if (args.mode === 'live' && typeof value === 'string') { - // Why: switching models can reset its effort/toggles. A value cached - // from an earlier visit to that model is no longer live evidence. - delete record.valuesByModel[value] - } - } - const modelId = setTrackedValue(id, value, source) - if (apply.midSession?.kind === 'toggle-command' && previousModelId && source === 'dispatched') { - record.valuesByModel[previousModelId] = { - ...record.valuesByModel[previousModelId], - [id]: { value, source } - } - } - persist(modelId ?? previousModelId, id, value) - const next = publish() - if (args.mode === 'draft' && typeof record.model?.value === 'string') { - args.onDraftValuesChanged?.(flattenNativeChatSessionOptionRecord(record, record.model.value)) - } - return { snapshot: next } - } + const appliers = createSessionOptionAppliers({ + mode: args.mode, + catalog, + getModels: () => models, + getRecord: () => record, + dispatchCommand: args.dispatchCommand, + onAgentPicker: args.onAgentPicker, + persistSelection: args.persistSelection, + onDraftValuesChanged: args.onDraftValuesChanged, + publish, + clearModelTruth, + setTrackedValue + }) return { getSnapshot: () => snapshot, - setOption, + setOption: appliers.setOption, + invokeAction: appliers.invokeAction, subscribe: (listener) => { listeners.add(listener) return () => listeners.delete(listener) diff --git a/src/renderer/src/components/native-chat/native-chat-session-option-apply.ts b/src/renderer/src/components/native-chat/native-chat-session-option-apply.ts new file mode 100644 index 00000000000..d8e86bf941c --- /dev/null +++ b/src/renderer/src/components/native-chat/native-chat-session-option-apply.ts @@ -0,0 +1,314 @@ +import { + findCatalogModel, + findCatalogOption, + type AgentSessionOptionCatalog, + type CatalogMidSessionApply, + type CatalogModel, + type CatalogOptionApply +} from '../../../../shared/agent-session-option-catalog' +import type { + SessionOptionDescriptor, + SessionOptionSetResult, + SessionOptionValue +} from '../../../../shared/native-chat-session-options' +import type { NativeChatSessionOptionRecord } from './native-chat-session-option-cache' +import { buildNativeChatSessionOptionCommand } from './native-chat-session-option-command-builder' +import type { + NativeChatSessionOptionDispatchCommand, + NativeChatSessionOptionDispatchResult +} from './native-chat-session-option-command-dispatch' +import { getTrackedOption, isFlipOnlyMidSession } from './native-chat-session-option-flip' +import { + flattenNativeChatSessionOptionRecord, + type NativeChatSessionOptionMode +} from './native-chat-session-option-snapshot' + +type SessionOptionApplyContext = { + mode: NativeChatSessionOptionMode + catalog: AgentSessionOptionCatalog + getModels: () => CatalogModel[] + getRecord: () => NativeChatSessionOptionRecord + dispatchCommand: NativeChatSessionOptionDispatchCommand + onAgentPicker?: () => void + persistSelection?: (args: { + modelId: string + optionId: string + value: SessionOptionValue + }) => Promise | void + onDraftValuesChanged?: (values: Record) => void + publish: () => SessionOptionDescriptor[] + clearModelTruth: () => void + setTrackedValue: ( + optionId: string, + value: SessionOptionValue, + source: 'applied' | 'dispatched' + ) => string | null +} + +/** Why: ordered applies make a later absolute target observe the result of an + * earlier flip instead of dispatching against a stale baseline. */ +function createSerializedApplyQueue(): (fn: () => Promise) => Promise { + let tail: Promise = Promise.resolve() + return (fn: () => Promise): Promise => { + const run = tail.then(fn, fn) + tail = run.then( + () => undefined, + () => undefined + ) + return run + } +} + +function currentApply( + ctx: SessionOptionApplyContext, + optionId: string +): { apply: CatalogOptionApply; modelId: string | null } | null { + const record = ctx.getRecord() + const modelId = typeof record.model?.value === 'string' ? record.model.value : null + if (optionId === 'model') { + return { apply: ctx.catalog.modelApply, modelId } + } + const model = modelId + ? findCatalogModel({ ...ctx.catalog, models: ctx.getModels() }, modelId) + : undefined + const option = findCatalogOption(model, optionId) + return option ? { apply: option.apply, modelId } : null +} + +function persist( + ctx: SessionOptionApplyContext, + modelId: string | null, + optionId: string, + value: SessionOptionValue +): void { + if (modelId) { + void ctx.persistSelection?.({ modelId, optionId, value }) + } +} + +function finish( + ctx: SessionOptionApplyContext, + args?: { + modelId: string | null + optionId: string + value: SessionOptionValue + skipPersist?: boolean + } +): SessionOptionSetResult { + if (args && !args.skipPersist) { + persist(ctx, args.modelId, args.optionId, args.value) + } + const snapshot = ctx.publish() + const record = ctx.getRecord() + if (ctx.mode === 'draft' && typeof record.model?.value === 'string') { + ctx.onDraftValuesChanged?.(flattenNativeChatSessionOptionRecord(record, record.model.value)) + } + return { snapshot } +} + +async function handleAgentPicker( + ctx: SessionOptionApplyContext, + midSession: Extract +): Promise { + await ctx.dispatchCommand(midSession.command) + ctx.clearModelTruth() + const snapshot = ctx.publish() + ctx.onAgentPicker?.() + return { snapshot } +} + +async function dispatchLiveCommand( + ctx: SessionOptionApplyContext, + args: { + optionId: string + value: SessionOptionValue + apply: CatalogOptionApply + modelId: string | null + } +): Promise { + const models = ctx.getModels() + const record = ctx.getRecord() + const command = buildNativeChatSessionOptionCommand({ + optionId: args.optionId, + value: args.value, + apply: args.apply, + modelId: args.modelId, + catalog: ctx.catalog, + models, + record + }) + if (!command) { + throw new Error('This option can only be set when the session starts.') + } + const detectAgentInteraction = + args.apply.midSession?.kind === 'command' + ? args.apply.midSession.detectAgentInteraction + : args.apply.composedIntoModel && ctx.catalog.modelApply.midSession?.kind === 'command' + ? ctx.catalog.modelApply.midSession.detectAgentInteraction + : undefined + const expectedChoiceLabel = + args.optionId === 'model' && typeof args.value === 'string' + ? (findCatalogModel({ ...ctx.catalog, models }, args.value)?.label ?? args.value) + : undefined + return detectAgentInteraction + ? await ctx.dispatchCommand(command, { + detectAgentInteraction, + expectedChoiceLabel + }) + : await ctx.dispatchCommand(command) +} + +function applyDispatchOutcome( + ctx: SessionOptionApplyContext, + dispatchResult: NativeChatSessionOptionDispatchResult | void +): SessionOptionSetResult | null { + if (dispatchResult?.outcome === 'rejected') { + throw new Error('Claude kept the current model.') + } + if (dispatchResult?.outcome === 'unknown') { + ctx.clearModelTruth() + ctx.publish() + throw new Error('Could not verify the model change; open the terminal to check.') + } + if (dispatchResult?.outcome === 'interaction-required') { + ctx.clearModelTruth() + const snapshot = ctx.publish() + ctx.onAgentPicker?.() + return { snapshot } + } + return null +} + +async function applySetOption( + ctx: SessionOptionApplyContext, + id: string, + value: SessionOptionValue +): Promise { + const resolved = currentApply(ctx, id) + if (!resolved) { + throw new Error(`Unknown session option: ${id}`) + } + const { apply, modelId: previousModelId } = resolved + if (ctx.mode === 'live' && apply.midSession?.kind === 'agent-picker') { + throw new Error('This option must be changed in the agent picker.') + } + + const liveFlipOnly = ctx.mode === 'live' && isFlipOnlyMidSession(apply.midSession) + const trackedToggle = liveFlipOnly + ? getTrackedOption(ctx.getRecord(), previousModelId, id) + : undefined + if (liveFlipOnly && !trackedToggle) { + // Why: a flip from an unknown baseline cannot honor an absolute target. + throw new Error('Current value is unknown; use the Toggle action instead.') + } + // Why: same absolute target must never re-dispatch a flip (would invert the agent). + if (liveFlipOnly && trackedToggle?.value === value) { + return { snapshot: ctx.publish() } + } + // Why: flip-only never heals via agent report — track as applied best-known. + const source = liveFlipOnly || ctx.mode !== 'live' ? 'applied' : 'dispatched' + + // Why: baseline for detecting a model switch, typed command, or agent report + // that lands mid-dispatch, so the commit below never overwrites newer state. + const trackedBeforeDispatch = + ctx.mode === 'live' && id !== 'model' + ? getTrackedOption(ctx.getRecord(), previousModelId, id) + : undefined + + let dispatchResult: NativeChatSessionOptionDispatchResult | void = undefined + if (ctx.mode === 'live') { + dispatchResult = await dispatchLiveCommand(ctx, { + optionId: id, + value, + apply, + modelId: previousModelId + }) + } else if (!apply.launchArgs && !apply.composedIntoModel) { + throw new Error('This option is only available after the session starts.') + } + + const early = applyDispatchOutcome(ctx, dispatchResult) + if (early) { + return early + } + + const record = ctx.getRecord() + if (id === 'model' && previousModelId !== value) { + record.model = undefined + if (ctx.mode === 'live' && typeof value === 'string') { + // Why: switching models can reset effort/toggles for the destination model. + delete record.valuesByModel[value] + } + } + + if (liveFlipOnly) { + // Why: typed flips, reports, or model changes during dispatch supersede the + // baseline this absolute target was computed from. + const modelStill = typeof record.model?.value === 'string' ? record.model.value : null + if (modelStill !== previousModelId) { + return finish(ctx, { modelId: previousModelId, optionId: id, value, skipPersist: true }) + } + if (getTrackedOption(record, previousModelId, id) !== trackedToggle) { + return finish(ctx, { modelId: previousModelId, optionId: id, value, skipPersist: true }) + } + // Why: never persist unconfirmed flip-only state into durable defaults. + ctx.setTrackedValue(id, value, source) + return finish(ctx, { modelId: previousModelId, optionId: id, value, skipPersist: true }) + } + + if (ctx.mode === 'live' && id !== 'model') { + // Why: a model switch, typed command, or agent report during dispatch supersedes + // the baseline this commit was computed from — committing now would overwrite + // newer state and could write/persist a model-scoped value under the new model. + const modelStill = typeof record.model?.value === 'string' ? record.model.value : null + if ( + modelStill !== previousModelId || + getTrackedOption(record, previousModelId, id) !== trackedBeforeDispatch + ) { + return finish(ctx, { modelId: previousModelId, optionId: id, value, skipPersist: true }) + } + } + + const modelId = ctx.setTrackedValue(id, value, source) + return finish(ctx, { modelId: modelId ?? previousModelId, optionId: id, value }) +} + +async function applyInvokeAction( + ctx: SessionOptionApplyContext, + id: string +): Promise { + const resolved = currentApply(ctx, id) + if (!resolved) { + throw new Error(`Unknown session option: ${id}`) + } + const { apply, modelId } = resolved + if (apply.midSession?.kind === 'agent-picker') { + if (ctx.mode !== 'live') { + throw new Error('This option is only available after the session starts.') + } + return handleAgentPicker(ctx, apply.midSession) + } + if (!isFlipOnlyMidSession(apply.midSession)) { + throw new Error('This option requires a value.') + } + if (ctx.mode !== 'live') { + throw new Error('This option is only available after the session starts.') + } + if (getTrackedOption(ctx.getRecord(), modelId, id)) { + throw new Error('This option has a known value; choose On or Off instead.') + } + // Why: an unknown baseline remains unknown after one inversion. + await ctx.dispatchCommand(apply.midSession.command) + return finish(ctx) +} + +export function createSessionOptionAppliers(ctx: SessionOptionApplyContext): { + setOption: (id: string, value: SessionOptionValue) => Promise + invokeAction: (id: string) => Promise +} { + const serialize = createSerializedApplyQueue() + return { + setOption: (id, value) => serialize(() => applySetOption(ctx, id, value)), + invokeAction: (id) => serialize(() => applyInvokeAction(ctx, id)) + } +} diff --git a/src/renderer/src/components/native-chat/native-chat-session-option-command-builder.ts b/src/renderer/src/components/native-chat/native-chat-session-option-command-builder.ts index 94de397b0ef..c1b7cff882d 100644 --- a/src/renderer/src/components/native-chat/native-chat-session-option-command-builder.ts +++ b/src/renderer/src/components/native-chat/native-chat-session-option-command-builder.ts @@ -21,7 +21,9 @@ export function buildNativeChatSessionOptionCommand(args: { if (midSession?.kind === 'command') { return midSession.build(args.value) } - if (midSession?.kind === 'toggle-command' || midSession?.kind === 'agent-picker') { + // Why: a known flip has an absolute target only for local tracking; the + // command itself always performs one inversion. + if (midSession?.kind === 'toggle-command') { return midSession.command } if (!args.apply.composedIntoModel || !args.modelId || !args.catalog.composeModelValue) { diff --git a/src/renderer/src/components/native-chat/native-chat-session-option-command-recording.ts b/src/renderer/src/components/native-chat/native-chat-session-option-command-recording.ts index 051e14b1c1d..6c3487ebff2 100644 --- a/src/renderer/src/components/native-chat/native-chat-session-option-command-recording.ts +++ b/src/renderer/src/components/native-chat/native-chat-session-option-command-recording.ts @@ -10,6 +10,7 @@ import { isSessionOptionAgentPickerCommand, parseBuiltSessionOptionCommand } from './native-chat-session-option-command-matching' +import { clearTrackedOption, isFlipOnlyMidSession } from './native-chat-session-option-flip' function clearModel(record: NativeChatSessionOptionRecord): void { const modelId = typeof record.model?.value === 'string' ? record.model.value : null @@ -30,11 +31,9 @@ function recordCommandApply(args: { if (!midSession || midSession.kind === 'unsupported') { return false } - if (midSession.kind === 'toggle-command' && command === midSession.command) { + if (isFlipOnlyMidSession(midSession) && command === midSession.command) { const modelId = typeof record.model?.value === 'string' ? record.model.value : null - if (modelId) { - delete record.valuesByModel[modelId]?.[optionId] - } + clearTrackedOption(record, modelId, optionId) return true } if (isSessionOptionAgentPickerCommand(midSession, command)) { diff --git a/src/renderer/src/components/native-chat/native-chat-session-option-flip.ts b/src/renderer/src/components/native-chat/native-chat-session-option-flip.ts new file mode 100644 index 00000000000..553eaa5df9f --- /dev/null +++ b/src/renderer/src/components/native-chat/native-chat-session-option-flip.ts @@ -0,0 +1,45 @@ +import type { CatalogMidSessionApply } from '../../../../shared/agent-session-option-catalog' +import type { + NativeChatSessionOptionRecord, + TrackedNativeChatSessionOption +} from './native-chat-session-option-cache' + +/** Why: one predicate for flip-only mid-session commands so snapshot, + * absolute apply, value-less actions, and typed-command recording stay aligned. */ +export function isFlipOnlyMidSession( + midSession: CatalogMidSessionApply | undefined +): midSession is Extract { + return midSession?.kind === 'toggle-command' +} + +export function getTrackedOption( + record: NativeChatSessionOptionRecord, + modelId: string | null, + optionId: string +): TrackedNativeChatSessionOption | undefined { + if (!modelId) { + return undefined + } + return record.valuesByModel[modelId]?.[optionId] +} + +export function clearTrackedOption( + record: NativeChatSessionOptionRecord, + modelId: string | null, + optionId: string +): void { + if (!modelId) { + return + } + const current = record.valuesByModel[modelId] + if (!current || !(optionId in current)) { + return + } + const next = { ...current } + delete next[optionId] + if (Object.keys(next).length === 0) { + delete record.valuesByModel[modelId] + } else { + record.valuesByModel[modelId] = next + } +} diff --git a/src/renderer/src/components/native-chat/native-chat-session-option-labels.ts b/src/renderer/src/components/native-chat/native-chat-session-option-labels.ts index 33b940d88f3..dd55e1d4656 100644 --- a/src/renderer/src/components/native-chat/native-chat-session-option-labels.ts +++ b/src/renderer/src/components/native-chat/native-chat-session-option-labels.ts @@ -55,29 +55,27 @@ export function nativeChatSessionOptionDisabledReason( 'components.native-chat.composer.availableAfterSessionStarts', 'Available after the session starts.' ) - default: + case undefined: return null } } export function nativeChatModelPillLabel(descriptor: SessionOptionDescriptor): string { - const modelLabel = translate('components.native-chat.composer.model', 'Model') + // Why: show the value only (Codex/Conductor style). "Model:" is redundant — + // the control's aria-label/tooltip already names the category. if ( descriptor.valueSource === 'unknown' || descriptor.kind.type !== 'select' || !descriptor.kind.currentValue ) { - return modelLabel + return translate('components.native-chat.composer.model', 'Model') } - const valueLabel = nativeChatSessionChoiceLabel( + return nativeChatSessionChoiceLabel( descriptor.kind.choices.find((choice) => choice.value === descriptor.kind.currentValue) ?? { value: descriptor.kind.currentValue, label: descriptor.kind.currentValue } ) - return translate('components.native-chat.composer.modelWithValue', 'Model: {{value0}}', { - value0: valueLabel - }) } export function nativeChatOptionsPillTitle( @@ -119,14 +117,9 @@ export function nativeChatOptionsPillLabel( ) } } + // Why: value-only pill (no "Effort:" prefix) — category lives on the tooltip. if (labels.length > 0) { - const joinedLabels = labels.join(' · ') - if (effort) { - return translate('components.native-chat.composer.effortWithValue', 'Effort: {{value0}}', { - value0: joinedLabels - }) - } - return joinedLabels + return labels.join(' · ') } if (effort) { return nativeChatSessionOptionLabel(effort) diff --git a/src/renderer/src/components/native-chat/native-chat-session-option-snapshot.ts b/src/renderer/src/components/native-chat/native-chat-session-option-snapshot.ts index 987e66edf4b..125bc1dbb9b 100644 --- a/src/renderer/src/components/native-chat/native-chat-session-option-snapshot.ts +++ b/src/renderer/src/components/native-chat/native-chat-session-option-snapshot.ts @@ -14,6 +14,7 @@ import type { NativeChatSessionOptionRecord, TrackedNativeChatSessionOption } from './native-chat-session-option-cache' +import { isFlipOnlyMidSession } from './native-chat-session-option-flip' import { translate } from '@/i18n/i18n' export type NativeChatSessionOptionMode = 'draft' | 'live' @@ -60,7 +61,9 @@ function actionForApply( if (apply.midSession?.kind === 'agent-picker') { return { type: 'agent-picker' } } - if (apply.midSession?.kind === 'toggle-command' && !tracked) { + // Why: only unknown flip-only options are actions; once we have a tracked + // baseline the UI can show absolute On/Off without inventing a start state. + if (isFlipOnlyMidSession(apply.midSession) && !tracked) { return { type: 'toggle-command' } } return undefined diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 161e0e4a626..87e36d2e564 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -13275,15 +13275,15 @@ "worktreeNotReady": "Worktree not ready — try again in a moment.", "uploadingAttachments": "Uploading {{value0}} file(s) to remote…", "model": "Model", - "modelWithValue": "Model: {{value0}}", "effort": "Effort", - "effortWithValue": "Effort: {{value0}}", "fastMode": "Fast mode", "thinking": "Thinking", "options": "Options", "sessionOptions": "Session options", "chooseInAgentPicker": "Choose in agent picker…", "toggleOption": "Toggle {{value0}}", + "pillAccessibleName": "{{value0}} {{value1}}", + "valueUnknown": "Current value unknown — pick On or Off", "sentNotConfirmed": "Sent to the agent — not confirmed", "setWhenSessionStarts": "Set when the session starts.", "availableAfterSessionStarts": "Available after the session starts.", @@ -13295,7 +13295,9 @@ "medium": "Medium", "high": "High", "xhigh": "Extra high", - "max": "Max" + "max": "Max", + "on": "On", + "off": "Off" } }, "tool": { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index a8f5f8e8003..ea9d29b7b2f 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -13252,15 +13252,15 @@ "worktreeNotReady": "Worktree no está listo — intenta de nuevo en un momento.", "uploadingAttachments": "Subiendo {{value0}} archivo(s) al remoto…", "model": "Modelo", - "modelWithValue": "Modelo: {{value0}}", "effort": "Esfuerzo", - "effortWithValue": "Esfuerzo: {{value0}}", "fastMode": "Modo rápido", "thinking": "Razonamiento", "options": "Opciones", "sessionOptions": "Opciones de sesión", "chooseInAgentPicker": "Elegir en el selector del agente…", "toggleOption": "Alternar {{value0}}", + "pillAccessibleName": "{{value0}} {{value1}}", + "valueUnknown": "Valor actual desconocido — elige Activado o Desactivado", "sentNotConfirmed": "Enviado al agente — sin confirmar", "setWhenSessionStarts": "Configúralo al iniciar la sesión.", "availableAfterSessionStarts": "Disponible después de iniciar la sesión.", @@ -13272,7 +13272,9 @@ "medium": "Medio", "high": "Alto", "xhigh": "Muy alto", - "max": "Máximo" + "max": "Máximo", + "on": "Activado", + "off": "Desactivado" } }, "tool": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index c2f1c217840..96cea8a5d9e 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -13252,15 +13252,15 @@ "worktreeNotReady": "Worktreeが準備できていません — しばらくしてから再試行してください。", "uploadingAttachments": "{{value0}}個のファイルをリモートにアップロード中…", "model": "モデル", - "modelWithValue": "モデル: {{value0}}", "effort": "推論レベル", - "effortWithValue": "推論レベル: {{value0}}", "fastMode": "高速モード", "thinking": "思考", "options": "オプション", "sessionOptions": "セッションオプション", "chooseInAgentPicker": "エージェントの選択画面で選ぶ…", "toggleOption": "{{value0}}を切り替える", + "pillAccessibleName": "{{value0}} {{value1}}", + "valueUnknown": "現在の値が不明です — オンまたはオフを選んでください", "sentNotConfirmed": "エージェントに送信済み — 未確認", "setWhenSessionStarts": "セッション開始時に設定してください。", "availableAfterSessionStarts": "セッション開始後に利用できます。", @@ -13272,7 +13272,9 @@ "medium": "中", "high": "高", "xhigh": "最高", - "max": "最大" + "max": "最大", + "on": "オン", + "off": "オフ" } }, "tool": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index deeb333b0d7..2ddb96e9468 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -13252,15 +13252,15 @@ "worktreeNotReady": "워크트리가 준비되지 않았습니다 — 잠시 후 다시 시도하세요.", "uploadingAttachments": "{{value0}}개 파일을 원격으로 업로드 중…", "model": "모델", - "modelWithValue": "모델: {{value0}}", "effort": "추론 수준", - "effortWithValue": "추론 수준: {{value0}}", "fastMode": "빠른 모드", "thinking": "사고", "options": "옵션", "sessionOptions": "세션 옵션", "chooseInAgentPicker": "에이전트 선택기에서 선택…", "toggleOption": "{{value0}} 전환", + "pillAccessibleName": "{{value0}} {{value1}}", + "valueUnknown": "현재 값을 알 수 없음 — 켜짐 또는 꺼짐을 선택하세요", "sentNotConfirmed": "에이전트에 전송됨 — 확인되지 않음", "setWhenSessionStarts": "세션을 시작할 때 설정하세요.", "availableAfterSessionStarts": "세션이 시작된 후 사용할 수 있습니다.", @@ -13272,7 +13272,9 @@ "medium": "중간", "high": "높음", "xhigh": "매우 높음", - "max": "최대" + "max": "최대", + "on": "켜짐", + "off": "꺼짐" } }, "tool": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index ba31e9596ca..bbc6a337e7f 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -13252,15 +13252,15 @@ "worktreeNotReady": "工作树未就绪 — 请稍后重试。", "uploadingAttachments": "正在上传 {{value0}} 个文件到远程…", "model": "模型", - "modelWithValue": "模型:{{value0}}", "effort": "推理强度", - "effortWithValue": "推理强度:{{value0}}", "fastMode": "快速模式", "thinking": "思考", "options": "选项", "sessionOptions": "会话选项", "chooseInAgentPicker": "在代理选择器中选择…", "toggleOption": "切换{{value0}}", + "pillAccessibleName": "{{value0}} {{value1}}", + "valueUnknown": "当前值未知 — 请选择开或关", "sentNotConfirmed": "已发送给代理 — 尚未确认", "setWhenSessionStarts": "请在会话开始时设置。", "availableAfterSessionStarts": "会话开始后可用。", @@ -13272,7 +13272,9 @@ "medium": "中", "high": "高", "xhigh": "极高", - "max": "最大" + "max": "最大", + "on": "开", + "off": "关" } }, "tool": { diff --git a/src/renderer/src/i18n/native-chat-locales.test.ts b/src/renderer/src/i18n/native-chat-locales.test.ts index 9e27c12b207..f9be40ef0f7 100644 --- a/src/renderer/src/i18n/native-chat-locales.test.ts +++ b/src/renderer/src/i18n/native-chat-locales.test.ts @@ -29,22 +29,27 @@ describe('native chat locale copy', () => { const composer = catalog.components['native-chat'].composer for (const key of [ 'model', - 'modelWithValue', 'effort', - 'effortWithValue', 'fastMode', 'thinking', 'options', 'sessionOptions', 'chooseInAgentPicker', + 'toggleOption', + 'valueUnknown', 'sentNotConfirmed' ] as const) { expect(composer[key].trim()).not.toBe('') expect(composer[key]).not.toBe(englishComposer[key]) } for (const key of ['fast', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const) { + expect(composer.optionValue[key].trim()).not.toBe('') expect(composer.optionValue[key]).not.toBe(englishComposer.optionValue[key]) } + // Why: On/Off loanwords are valid translations; only require non-empty. + for (const key of ['on', 'off'] as const) { + expect(composer.optionValue[key].trim()).not.toBe('') + } } ) }) diff --git a/src/shared/native-chat-session-options.ts b/src/shared/native-chat-session-options.ts index d159919e6a7..b735408d271 100644 --- a/src/shared/native-chat-session-options.ts +++ b/src/shared/native-chat-session-options.ts @@ -27,7 +27,10 @@ export type SessionOptionDescriptor = { currentValue?: string choices: SessionOptionSelectChoice[] } - | { type: 'boolean'; currentValue?: boolean } + | { + type: 'boolean' + currentValue?: boolean + } valueSource: SessionOptionValueSource settable: boolean disabledReason?: SessionOptionDisabledReason @@ -52,6 +55,9 @@ export type PersistedNativeChatSessionOptions = Partial< export type SessionOptionsSurface = { getSnapshot(): SessionOptionDescriptor[] + /** Apply an absolute target; known flip-only options use their tracked baseline. */ setOption(id: string, value: SessionOptionValue): Promise + /** Invoke the value-less action exposed by the current descriptor. */ + invokeAction(id: string): Promise subscribe(listener: (snapshot: SessionOptionDescriptor[]) => void): () => void } From 803a4428686338ce79b7f96cd78fbfc25044b33c Mon Sep 17 00:00:00 2001 From: slashdevcorpse <73707049+slashdevcorpse@users.noreply.github.com> Date: Fri, 17 Jul 2026 20:18:36 -0400 Subject: [PATCH 019/137] test(e2e): harden terminal restart regressions on Windows (#9064) * test(e2e): harden Windows terminal restart regressions * test(e2e): cover renderer replacement rejection * test(e2e): tolerate ESRCH when force-killing the daemon on POSIX * fix(ci): preserve Windows restart test selection --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> --- .../windows-terminal-restart-e2e.yml | 103 +++++++++ .../restored-terminal-input-readiness.ts | 80 +++++++ tests/e2e/helpers/terminal-input-probes.ts | 25 ++- tests/e2e/helpers/terminal.ts | 13 +- .../restart-restore-terminal-input.spec.ts | 71 +++++- ...ored-terminal-input-readiness.unit.test.ts | 210 ++++++++++++++++++ tests/e2e/terminal-probe-input-sequence.ts | 9 + ...terminal-probe-input-sequence.unit.test.ts | 20 +- .../e2e/terminal-restart-persistence.spec.ts | 57 ++--- 9 files changed, 543 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/windows-terminal-restart-e2e.yml create mode 100644 tests/e2e/helpers/restored-terminal-input-readiness.ts create mode 100644 tests/e2e/restored-terminal-input-readiness.unit.test.ts diff --git a/.github/workflows/windows-terminal-restart-e2e.yml b/.github/workflows/windows-terminal-restart-e2e.yml new file mode 100644 index 00000000000..73543b5e07e --- /dev/null +++ b/.github/workflows/windows-terminal-restart-e2e.yml @@ -0,0 +1,103 @@ +name: Windows terminal restart E2E + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + paths: + - '.github/workflows/windows-terminal-restart-e2e.yml' + - 'package.json' + - 'pnpm-lock.yaml' + - 'electron.vite.config.ts' + - 'config/patches/**' + - 'config/scripts/ensure-native-runtime.mjs' + - 'config/scripts/rebuild-native-deps.mjs' + - 'tests/playwright.config.ts' + - 'tests/e2e/global-setup.ts' + - 'tests/e2e/global-teardown.ts' + - 'tests/e2e/helpers/**' + - 'tests/e2e/restart-restore-terminal-input.spec.ts' + - 'tests/e2e/restored-terminal-input-readiness.unit.test.ts' + - 'tests/e2e/terminal-probe-input-sequence.ts' + - 'tests/e2e/terminal-probe-input-sequence.unit.test.ts' + - 'tests/e2e/terminal-restart-persistence.spec.ts' + - 'src/main/daemon/**' + - 'src/main/ipc/pty*.ts' + - 'src/main/providers/**' + - 'src/main/pty/**' + - 'src/preload/**' + - 'src/renderer/src/components/terminal-pane/**' + - 'src/renderer/src/lib/pane-manager/**' + - 'src/shared/pty-session-id-format.ts' + workflow_dispatch: + inputs: + ref: + description: Ref to check out + required: false + type: string + +permissions: + contents: read + +concurrency: + group: windows-terminal-restart-e2e-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + windows-terminal-restart: + name: Windows terminal restart regressions + runs-on: windows-2022 + timeout-minutes: 30 + env: + NODE_OPTIONS: --max-old-space-size=4096 + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.ref || github.ref }} + persist-credentials: false + + # Why: pnpm must exist before setup-node resolves its dependency cache. + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: package.json + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build Electron app for Windows E2E + run: pnpm exec electron-vite build --mode e2e + + - name: Run Windows terminal restart regressions + env: + SKIP_BUILD: '1' + ORCA_E2E_FORWARD_APP_LOGS: '1' + ORCA_REQUIRE_WINDOWS_TERMINAL_RESTART_E2E: '1' + # Why: pnpm forwards a literal `--` to Playwright, which makes the + # grep and worker flags positional filters instead of CLI options. + run: >- + pnpm run test:e2e + tests/e2e/restart-restore-terminal-input.spec.ts + tests/e2e/terminal-restart-persistence.spec.ts + --grep "clean restart with a live daemon session|cold-restored pane accepts typing|daemon snapshot relaunch preserves" + --workers=1 + + - name: Upload Playwright traces + if: failure() + uses: actions/upload-artifact@v7 + with: + name: windows-terminal-restart-playwright-traces + path: test-results/ + retention-days: 7 + if-no-files-found: ignore diff --git a/tests/e2e/helpers/restored-terminal-input-readiness.ts b/tests/e2e/helpers/restored-terminal-input-readiness.ts new file mode 100644 index 00000000000..c54bb8e10bc --- /dev/null +++ b/tests/e2e/helpers/restored-terminal-input-readiness.ts @@ -0,0 +1,80 @@ +import { randomUUID } from 'node:crypto' +import type { Page } from '@stablyai/playwright-test' +import { buildFreshShellProbeInputSequence } from '../terminal-probe-input-sequence' + +type ReadinessAttempt = { + marker: string + paneInstanceId: string +} + +export async function waitForRestoredTerminalInputReady( + page: Page, + expectedPtyId: string, + timeoutMs = 15_000 +): Promise { + const deadline = Date.now() + timeoutMs + // Why: ConPTY echo can lag behind the poll interval, so retain every marker + // sent to the current concrete pane instead of chasing only the newest one. + let pendingAttempts: readonly ReadinessAttempt[] = [] + + while (Date.now() < deadline) { + const marker = `ORCA_RESTORED_INPUT_READY_${randomUUID().replaceAll('-', '')}` + const [input] = buildFreshShellProbeInputSequence(`echo ${marker}\r`) + if (!input) { + return false + } + try { + const result = await page.evaluate( + ({ expectedPtyId, input, pendingAttempts }) => { + for (const manager of window.__paneManagers?.values() ?? []) { + const pane = manager.getActivePane?.() ?? manager.getPanes?.()[0] + if (pane?.container?.dataset?.ptyId !== expectedPtyId) { + continue + } + const container = pane.container as HTMLElement & { + __orcaE2eTerminalInputReadinessInstanceId?: string + } + container.__orcaE2eTerminalInputReadinessInstanceId ??= crypto.randomUUID() + const paneInstanceId = container.__orcaE2eTerminalInputReadinessInstanceId + const output = pane.serializeAddon?.serialize?.() ?? '' + if ( + pendingAttempts.some( + (attempt) => + attempt.paneInstanceId === paneInstanceId && output.includes(attempt.marker) + ) + ) { + return { ready: true, paneInstanceId } + } + // Why: one input() payload is either wholly replay-suppressed or + // wholly forwarded, unlike character-by-character keyboard typing. + pane.terminal.input(input, true) + return { ready: false, paneInstanceId } + } + return null + }, + { expectedPtyId, input, pendingAttempts } + ) + if (result?.ready) { + return true + } + pendingAttempts = result + ? [ + ...pendingAttempts.filter( + (attempt) => attempt.paneInstanceId === result.paneInstanceId + ), + { marker, paneInstanceId: result.paneInstanceId } + ] + : [] + } catch { + // Relaunch can replace the document between polls; the next attempt + // resolves the current pane and binding instead of retaining stale state. + pendingAttempts = [] + } + + const remainingMs = deadline - Date.now() + if (remainingMs > 0) { + await page.waitForTimeout(Math.min(100, remainingMs)) + } + } + return false +} diff --git a/tests/e2e/helpers/terminal-input-probes.ts b/tests/e2e/helpers/terminal-input-probes.ts index 82fdd2678c4..9d6b45457a0 100644 --- a/tests/e2e/helpers/terminal-input-probes.ts +++ b/tests/e2e/helpers/terminal-input-probes.ts @@ -11,7 +11,7 @@ * * Direct `window.api.pty.write` bypasses the renderer transport, so: * direct dead → MAIN-side drop (ownership/provider routing) - * direct alive, transport dead → RENDERER transport unbound + * direct alive, renderer dead → RENDERER input path (replay, focus, binding) * The ownership-rebuild probe invokes pty:listSessions, which repopulates * `ptyOwnership` as a side effect — input reviving after it is a smoking gun * for the missing-ownership drop path. @@ -25,6 +25,7 @@ import { expect, type ElectronApplication, type Page } from '@stablyai/playwright-test' import { sendToTerminal, waitForTerminalOutput } from './terminal' +import { buildSettledShellProbeInputSequence } from '../terminal-probe-input-sequence' // ─── Page-based probes (healthy CDP session) ──────────────────────── @@ -34,9 +35,9 @@ export async function probeDirectWrite( marker: string, timeoutMs = 10_000 ): Promise { - // \x03\x15 = ETX+NAK (interrupt + kill-line) so a TUI or half-typed line on - // the shell doesn't swallow the probe — same trick discoverActivePtyId uses. - await sendToTerminal(page, ptyId, `\x03\x15echo ${marker}\r`) + for (const input of buildSettledShellProbeInputSequence(`echo ${marker}\r`)) { + await sendToTerminal(page, ptyId, input) + } try { await waitForTerminalOutput(page, marker, timeoutMs) return true @@ -209,6 +210,7 @@ export async function mainProbeTransportPaste( timeoutMs = 10_000 ): Promise { try { + const inputs = buildSettledShellProbeInputSequence(`echo ${marker}\r`) const fed = await mainRendererEval( electronApp, `(() => { @@ -217,7 +219,9 @@ export async function mainProbeTransportPaste( for (const manager of managers.values()) { const pane = manager.getActivePane?.() ?? (manager.getPanes?.() ?? [])[0] if (pane?.terminal?.input) { - pane.terminal.input('\\x03\\x15echo ${marker}\\r', true) + for (const input of ${JSON.stringify(inputs)}) { + pane.terminal.input(input, true) + } return true } } @@ -240,9 +244,10 @@ export async function mainProbeDirectWrite( timeoutMs = 10_000 ): Promise { try { + const inputs = buildSettledShellProbeInputSequence(`echo ${marker}\r`) await mainRendererEval( electronApp, - `window.api.pty.write(${JSON.stringify(ptyId)}, ${JSON.stringify(`\x03\x15echo ${marker}\r`)})` + `for (const input of ${JSON.stringify(inputs)}) { window.api.pty.write(${JSON.stringify(ptyId)}, input) }` ) } catch { return false @@ -275,14 +280,20 @@ export function buildFrozenPaneReport( directAlive: boolean transportAlive: boolean revivedByOwnershipRebuild: boolean + ownershipRebuildAttempted?: boolean + readinessAlive?: boolean ptyIds: string[] terminalTail: string } ): string { return [ `REPRODUCED frozen terminal (${context}):`, + ...(probes.readinessAlive === undefined + ? [] + : [` replay + transport readiness probe alive: ${probes.readinessAlive}`]), ` direct pty:write probe alive: ${probes.directAlive} (false ⇒ MAIN-side drop: ptyOwnership/provider)`, - ` transport (onData→sendInput) probe alive: ${probes.transportAlive} (false with direct alive ⇒ RENDERER transport unbound)`, + ` renderer input-path probe alive: ${probes.transportAlive} (false with direct alive ⇒ replay, focus, or renderer binding failure)`, + ` ownership rebuild attempted: ${probes.ownershipRebuildAttempted ?? true}`, ` revived by pty:listSessions ownership rebuild: ${probes.revivedByOwnershipRebuild}`, ` pane ptyIds: ${JSON.stringify(probes.ptyIds)}`, ` terminal tail:\n${probes.terminalTail.slice(-600)}` diff --git a/tests/e2e/helpers/terminal.ts b/tests/e2e/helpers/terminal.ts index fa4f703a142..52d32e2b02d 100644 --- a/tests/e2e/helpers/terminal.ts +++ b/tests/e2e/helpers/terminal.ts @@ -1,6 +1,7 @@ /* eslint-disable max-lines -- Terminal E2E helpers share one PaneManager-backed path for PTY IO, split actions, and stable pane identity snapshots. */ import type { Page } from '@stablyai/playwright-test' import { expect } from '@stablyai/playwright-test' +import { buildFreshShellProbeInputSequence } from '../terminal-probe-input-sequence' export const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ @@ -264,15 +265,21 @@ export async function discoverActivePtyId(page: Page): Promise { throw new Error('discoverActivePtyId: active tab has no PTY candidates in store') } + const candidateInputs = candidateIds.map((_id, index) => + buildFreshShellProbeInputSequence(`echo ${marker}_${index}\r`) + ) + await page.evaluate( - ({ marker, candidateIds }) => { + ({ candidateIds, candidateInputs }) => { // Why: daemon PTY IDs can contain path separators and shell metacharacters. // Echo a numeric probe index, then map it back to the opaque ID in Node. for (const [index, id] of candidateIds.entries()) { - window.api.pty.write(String(id), `\x03\x15echo ${marker}_${index}\r`) + for (const input of candidateInputs[index] ?? []) { + window.api.pty.write(String(id), input) + } } }, - { marker, candidateIds } + { candidateIds, candidateInputs } ) let foundPtyId: string | null = null diff --git a/tests/e2e/restart-restore-terminal-input.spec.ts b/tests/e2e/restart-restore-terminal-input.spec.ts index 2ded33f0c0a..1ceed4254c5 100644 --- a/tests/e2e/restart-restore-terminal-input.spec.ts +++ b/tests/e2e/restart-restore-terminal-input.spec.ts @@ -20,6 +20,7 @@ * a typeable pane */ +import { execFileSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import path from 'node:path' import type { ElectronApplication, Page } from '@stablyai/playwright-test' @@ -42,9 +43,13 @@ import { probeKeyboardType, probeOwnershipRebuildRevival } from './helpers/terminal-input-probes' +import { waitForRestoredTerminalInputReady } from './helpers/restored-terminal-input-readiness' import { PROTOCOL_VERSION } from '../../src/main/daemon/types' import { PTY_SESSION_ID_SEPARATOR } from '../../src/shared/pty-session-id-format' +const REQUIRE_WINDOWS_TERMINAL_RESTART_E2E = + process.env.ORCA_REQUIRE_WINDOWS_TERMINAL_RESTART_E2E === '1' + function readDaemonPid(userDataDir: string): number { const raw = readFileSync( path.join(userDataDir, 'daemon', `daemon-v${PROTOCOL_VERSION}.pid`), @@ -57,11 +62,61 @@ function readDaemonPid(userDataDir: string): number { return parsed.pid } +function isProcessAlive(pid: number): boolean { + try { + process.kill(pid, 0) + return true + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ESRCH') { + return false + } + throw error + } +} + +async function terminateDaemonForColdRestart(pid: number): Promise { + if (!isProcessAlive(pid)) { + return + } + if (process.platform === 'win32') { + try { + execFileSync('taskkill.exe', ['/PID', String(pid), '/T', '/F'], { + stdio: 'pipe', + timeout: 10_000 + }) + } catch (error) { + if (isProcessAlive(pid)) { + throw error + } + } + } else { + try { + process.kill(pid, 'SIGKILL') + } catch (error) { + // Why: the daemon can self-exit between the liveness guard and this + // signal; tolerate ESRCH like the Windows branch re-checks liveness. + if ((error as NodeJS.ErrnoException).code !== 'ESRCH') { + throw error + } + } + } + await expect + .poll(() => isProcessAlive(pid), { + timeout: 10_000, + message: `daemon ${pid} remained alive after forced termination` + }) + .toBe(false) +} + function seededRepoPathOrSkip(): string { const repoPath = existsSync(TEST_REPO_PATH_FILE) ? readFileSync(TEST_REPO_PATH_FILE, 'utf-8').trim() : '' - test.skip(!repoPath || !existsSync(repoPath), 'Global setup did not produce a seeded test repo') + const unavailable = !repoPath || !existsSync(repoPath) + if (unavailable && REQUIRE_WINDOWS_TERMINAL_RESTART_E2E) { + throw new Error('Required Windows restart E2E seeded repo is unavailable') + } + test.skip(unavailable, 'Global setup did not produce a seeded test repo') return repoPath } @@ -99,18 +154,23 @@ async function settleRestoredLaunch(page: Page): Promise { */ async function expectRestoredPaneAcceptsInput(page: Page, context: string): Promise { const ptyIds = await getStorePtyIds(page) - const kbAlive = await probeKeyboardType(page, 'KB_RESTORED_OK', 15_000) + const readinessAlive = + ptyIds.length > 0 && (await waitForRestoredTerminalInputReady(page, ptyIds[0], 15_000)) + const kbAlive = readinessAlive && (await probeKeyboardType(page, 'KB_RESTORED_OK', 15_000)) const directAlive = ptyIds.length > 0 && (await probeDirectWrite(page, ptyIds[0], 'DIRECT_RESTORED_OK', 15_000)) - if (!kbAlive || !directAlive) { + if (!readinessAlive || !kbAlive || !directAlive) { + const ownershipRebuildAttempted = !directAlive && ptyIds.length > 0 const revived = - ptyIds.length > 0 && + ownershipRebuildAttempted && (await probeOwnershipRebuildRevival(page, ptyIds[0], 'REVIVED_RESTORED_OK')) throw new Error( buildFrozenPaneReport(context, { directAlive, transportAlive: kbAlive, revivedByOwnershipRebuild: revived, + ownershipRebuildAttempted, + readinessAlive, ptyIds, terminalTail: await getTerminalContent(page) }) @@ -223,7 +283,6 @@ test('restored pane recovers input after the daemon un-wedges', async (// oxlint test('cold-restored pane accepts typing after the daemon died between launches', async (// oxlint-disable-next-line no-empty-pattern -- Playwright's second fixture arg is testInfo; the first must be an object destructure to opt out of the default fixture set. {}, testInfo) => { - test.skip(process.platform === 'win32', 'POSIX signal semantics keep this deterministic') test.setTimeout(300_000) const repoPath = seededRepoPathOrSkip() const session = createRestartSession(testInfo) @@ -240,7 +299,7 @@ test('cold-restored pane accepts typing after the daemon died between launches', // The daemon dies uncleanly between runs (crash, reboot, force-kill). The // persisted session now references sessions no living daemon holds. - process.kill(daemonPid, 'SIGKILL') + await terminateDaemonForColdRestart(daemonPid) const second = await session.launch() secondApp = second.app diff --git a/tests/e2e/restored-terminal-input-readiness.unit.test.ts b/tests/e2e/restored-terminal-input-readiness.unit.test.ts new file mode 100644 index 00000000000..8eef3321f40 --- /dev/null +++ b/tests/e2e/restored-terminal-input-readiness.unit.test.ts @@ -0,0 +1,210 @@ +import type { Page } from '@stablyai/playwright-test' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { waitForRestoredTerminalInputReady } from './helpers/restored-terminal-input-readiness' + +type TestPane = { + container: { + dataset: { ptyId: string } + __orcaE2eTerminalInputReadinessInstanceId?: string + } + serializeAddon: { serialize: () => string } + terminal: { input: (data: string, wasUserInput: boolean) => void } +} + +const originalWindowDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'window') + +function installPaneWindow(pane: TestPane): void { + Object.defineProperty(globalThis, 'window', { + configurable: true, + value: { + __paneManagers: new Map([ + [ + 'tab-1', + { + getActivePane: () => pane, + getPanes: () => [pane] + } + ] + ]) + } + }) +} + +function createPage(): Page { + return { + evaluate: async (callback: (arg: unknown) => unknown, arg: unknown) => callback(arg), + waitForTimeout: async (timeoutMs: number) => { + await vi.advanceTimersByTimeAsync(timeoutMs) + } + } as unknown as Page +} + +describe('restored terminal input readiness', () => { + beforeEach(() => { + vi.useFakeTimers() + vi.setSystemTime(0) + }) + + afterEach(() => { + vi.useRealTimers() + if (originalWindowDescriptor) { + Object.defineProperty(globalThis, 'window', originalWindowDescriptor) + } else { + Reflect.deleteProperty(globalThis, 'window') + } + }) + + it('retries after replay drops the first full input payload', async () => { + let content = '' + let attempts = 0 + const input = vi.fn((data: string) => { + attempts += 1 + if (attempts >= 2) { + content = data + } + }) + installPaneWindow({ + container: { dataset: { ptyId: 'pty-1' } }, + serializeAddon: { serialize: () => content }, + terminal: { input } + }) + + await expect(waitForRestoredTerminalInputReady(createPage(), 'pty-1', 500)).resolves.toBe(true) + expect(input).toHaveBeenCalledTimes(2) + expect(input.mock.calls.every(([, wasUserInput]) => wasUserInput === true)).toBe(true) + }) + + it('accepts a healthy PTY echo that arrives after multiple poll intervals', async () => { + let content = '' + const input = vi.fn((data: string) => { + setTimeout(() => { + content = data + }, 250) + }) + installPaneWindow({ + container: { dataset: { ptyId: 'pty-1' } }, + serializeAddon: { serialize: () => content }, + terminal: { input } + }) + + await expect(waitForRestoredTerminalInputReady(createPage(), 'pty-1', 800)).resolves.toBe(true) + expect(input.mock.calls.length).toBeGreaterThan(1) + }) + + it('never treats a different pane PTY as ready', async () => { + const input = vi.fn() + installPaneWindow({ + container: { dataset: { ptyId: 'pty-other' } }, + serializeAddon: { serialize: () => 'unrelated terminal output' }, + terminal: { input } + }) + + await expect(waitForRestoredTerminalInputReady(createPage(), 'pty-1', 250)).resolves.toBe(false) + expect(input).not.toHaveBeenCalled() + }) + + it('does not accept a marker replayed into a replacement pane', async () => { + let activePane: TestPane + let replacementContent = '' + const replacementInput = vi.fn((data: string) => { + replacementContent = data + }) + const replacementPane: TestPane = { + container: { dataset: { ptyId: 'pty-1' } }, + serializeAddon: { serialize: () => replacementContent }, + terminal: { input: replacementInput } + } + const firstInput = vi.fn((data: string) => { + replacementContent = data + activePane = replacementPane + }) + activePane = { + container: { dataset: { ptyId: 'pty-1' } }, + serializeAddon: { serialize: () => '' }, + terminal: { input: firstInput } + } + Object.defineProperty(globalThis, 'window', { + configurable: true, + value: { + __paneManagers: new Map([ + [ + 'tab-1', + { + getActivePane: () => activePane, + getPanes: () => [activePane] + } + ] + ]) + } + }) + + await expect(waitForRestoredTerminalInputReady(createPage(), 'pty-1', 500)).resolves.toBe(true) + expect(firstInput).toHaveBeenCalledTimes(1) + expect(replacementInput).toHaveBeenCalledTimes(1) + }) + + it('discards stale attempts when document replacement rejects evaluation', async () => { + let activePane: TestPane + let replacementContent = '' + const replacementInput = vi.fn((data: string) => { + replacementContent = data + }) + const replacementPane: TestPane = { + container: { dataset: { ptyId: 'pty-1' } }, + serializeAddon: { serialize: () => replacementContent }, + terminal: { input: replacementInput } + } + let originalInputCalls = 0 + const firstInput = vi.fn((data: string) => { + originalInputCalls += 1 + if (originalInputCalls === 2) { + replacementContent = data + activePane = replacementPane + } + }) + activePane = { + container: { dataset: { ptyId: 'pty-1' } }, + serializeAddon: { serialize: () => '' }, + terminal: { input: firstInput } + } + Object.defineProperty(globalThis, 'window', { + configurable: true, + value: { + __paneManagers: new Map([ + [ + 'tab-1', + { + getActivePane: () => activePane, + getPanes: () => [activePane] + } + ] + ]) + } + }) + + const pendingAttemptCounts: number[] = [] + let evaluateCalls = 0 + const page = { + evaluate: async (callback: (arg: unknown) => unknown, arg: unknown) => { + evaluateCalls += 1 + pendingAttemptCounts.push( + (arg as { pendingAttempts: readonly unknown[] }).pendingAttempts.length + ) + const result = callback(arg) + if (evaluateCalls === 2) { + throw new Error('Execution context was destroyed') + } + return result + }, + waitForTimeout: async (timeoutMs: number) => { + await vi.advanceTimersByTimeAsync(timeoutMs) + } + } as unknown as Page + + await expect(waitForRestoredTerminalInputReady(page, 'pty-1', 500)).resolves.toBe(true) + expect(pendingAttemptCounts).toEqual([0, 1, 0, 1]) + expect(firstInput).toHaveBeenCalledTimes(2) + expect(replacementInput).toHaveBeenCalledTimes(1) + expect(replacementInput.mock.calls[0]?.[0]).not.toBe(firstInput.mock.calls[1]?.[0]) + }) +}) diff --git a/tests/e2e/terminal-probe-input-sequence.ts b/tests/e2e/terminal-probe-input-sequence.ts index 85402078072..f6dd532e19e 100644 --- a/tests/e2e/terminal-probe-input-sequence.ts +++ b/tests/e2e/terminal-probe-input-sequence.ts @@ -3,3 +3,12 @@ export function buildFreshShellProbeInputSequence(command: string): readonly str // corrupts the following PowerShell command before the shell is ready. return [command] } + +export function buildSettledShellProbeInputSequence( + command: string, + platform: NodeJS.Platform = process.platform +): readonly string[] { + // Why: Ctrl+U is a POSIX line-editor binding. A settled native Windows shell + // needs a separate Ctrl+C so ConPTY cannot join it to the command as input. + return platform === 'win32' ? ['\x03', command] : ['\x03\x15', command] +} diff --git a/tests/e2e/terminal-probe-input-sequence.unit.test.ts b/tests/e2e/terminal-probe-input-sequence.unit.test.ts index 41e2cd82504..e50f69fe612 100644 --- a/tests/e2e/terminal-probe-input-sequence.unit.test.ts +++ b/tests/e2e/terminal-probe-input-sequence.unit.test.ts @@ -1,12 +1,26 @@ import { describe, expect, it } from 'vitest' -import { buildFreshShellProbeInputSequence } from './terminal-probe-input-sequence' +import { + buildFreshShellProbeInputSequence, + buildSettledShellProbeInputSequence +} from './terminal-probe-input-sequence' + +const command = "& 'C:\\node\\node.exe' '-e' 'console.log(1)'\r" describe('buildFreshShellProbeInputSequence', () => { it('does not prefix fresh shell probes with interrupt or line-kill bytes', () => { - const command = "& 'C:\\node\\node.exe' '-e' 'console.log(1)'\r" - expect(buildFreshShellProbeInputSequence(command)).toEqual([command]) expect(buildFreshShellProbeInputSequence(command).join('')).not.toContain('\x03') expect(buildFreshShellProbeInputSequence(command).join('')).not.toContain('\x15') }) }) + +describe('buildSettledShellProbeInputSequence', () => { + it('resets PowerShell without sending the POSIX Ctrl+U binding', () => { + expect(buildSettledShellProbeInputSequence(command, 'win32')).toEqual(['\x03', command]) + expect(buildSettledShellProbeInputSequence(command, 'win32').join('')).not.toContain('\x15') + }) + + it.each(['linux', 'darwin'] as const)('preserves the POSIX reset on %s', (platform) => { + expect(buildSettledShellProbeInputSequence(command, platform)).toEqual(['\x03\x15', command]) + }) +}) diff --git a/tests/e2e/terminal-restart-persistence.spec.ts b/tests/e2e/terminal-restart-persistence.spec.ts index 19cccc43c36..b9cbd6a00b6 100644 --- a/tests/e2e/terminal-restart-persistence.spec.ts +++ b/tests/e2e/terminal-restart-persistence.spec.ts @@ -48,12 +48,28 @@ import { import { attachRepoAndOpenTerminal, createRestartSession } from './helpers/orca-restart' import { PTY_SESSION_ID_SEPARATOR } from '../../src/shared/pty-session-id-format' +const REQUIRE_WINDOWS_TERMINAL_RESTART_E2E = + process.env.ORCA_REQUIRE_WINDOWS_TERMINAL_RESTART_E2E === '1' +const MISSING_SEEDED_REPO_MESSAGE = 'Global setup did not produce a seeded test repo' + // Why: each test in this file does a full quit→relaunch cycle, which spawns // two Electron instances back-to-back. Running in serial keeps the isolated // userDataDirs from competing for the same Electron cache lock on cold start // and keeps the failure mode interpretable when something goes wrong. test.describe.configure({ mode: 'serial' }) +function seededRepoPathOrSkip(): string { + const repoPath = existsSync(TEST_REPO_PATH_FILE) + ? readFileSync(TEST_REPO_PATH_FILE, 'utf-8').trim() + : '' + const unavailable = !repoPath || !existsSync(repoPath) + if (unavailable && REQUIRE_WINDOWS_TERMINAL_RESTART_E2E) { + throw new Error('Required Windows restart E2E seeded repo is unavailable') + } + test.skip(unavailable, MISSING_SEEDED_REPO_MESSAGE) + return repoPath +} + /** * Shared bootstrap for a *first* launch: attach the seeded test repo, * activate its worktree, ensure a terminal is mounted, and return the @@ -75,6 +91,9 @@ async function bootstrapFirstLaunch( const hasPaneManager = await waitForActiveTerminalManager(page, 30_000) .then(() => true) .catch(() => false) + if (!hasPaneManager && REQUIRE_WINDOWS_TERMINAL_RESTART_E2E) { + throw new Error('Required Windows restart E2E TerminalPane manager did not mount') + } test.skip( !hasPaneManager, 'Electron automation in this environment never mounts the TerminalPane manager, so restart-persistence assertions would only fail on harness setup.' @@ -189,11 +208,7 @@ async function expectSavedLayoutToContainTitle( test.describe('Terminal restart persistence', () => { test('scrollback survives clean quit and relaunch', async (// oxlint-disable-next-line no-empty-pattern -- Playwright's second fixture arg is testInfo; the first must be an object destructure to opt out of the default fixture set. {}, testInfo) => { - const repoPath = readFileSync(TEST_REPO_PATH_FILE, 'utf-8').trim() - if (!repoPath || !existsSync(repoPath)) { - test.skip(true, 'Global setup did not produce a seeded test repo') - return - } + const repoPath = seededRepoPathOrSkip() const session = createRestartSession(testInfo) let firstApp: ElectronApplication | null = null @@ -249,11 +264,7 @@ test.describe('Terminal restart persistence', () => { test('daemon snapshot relaunch preserves the cursor on the shell prompt', async (// oxlint-disable-next-line no-empty-pattern -- Playwright's second fixture arg is testInfo; the first must be an object destructure to opt out of the default fixture set. {}, testInfo) => { - const repoPath = readFileSync(TEST_REPO_PATH_FILE, 'utf-8').trim() - if (!repoPath || !existsSync(repoPath)) { - test.skip(true, 'Global setup did not produce a seeded test repo') - return - } + const repoPath = seededRepoPathOrSkip() const session = createRestartSession(testInfo) let firstApp: ElectronApplication | null = null @@ -267,7 +278,13 @@ test.describe('Terminal restart persistence', () => { const prompt = `ORCA_RESTART_PROMPT_${Date.now()}_GT ` const marker = `ORCA_CURSOR_RESTART_${Date.now()}` - await execInTerminal(firstLaunch.page, ptyId, `export PS1='${prompt}'; PROMPT='${prompt}'`) + const promptCommand = + process.platform === 'win32' + ? `function global:prompt { '${prompt}' }` + : `export PS1='${prompt}'; PROMPT='${prompt}'` + // Why: the Windows default shell is PowerShell, whose prompt is a + // function; PS1/PROMPT assignments remain the Bash/Zsh path. + await execInTerminal(firstLaunch.page, ptyId, promptCommand) await waitForTerminalActiveLine(firstLaunch.page, prompt.trim()) await execInTerminal(firstLaunch.page, ptyId, `echo ${marker}`) await waitForTerminalOutput(firstLaunch.page, marker) @@ -313,11 +330,7 @@ test.describe('Terminal restart persistence', () => { test('active worktree and terminal tab count survive restart', async (// oxlint-disable-next-line no-empty-pattern -- Playwright's second fixture arg is testInfo; the first must be an object destructure to opt out of the default fixture set. {}, testInfo) => { - const repoPath = readFileSync(TEST_REPO_PATH_FILE, 'utf-8').trim() - if (!repoPath || !existsSync(repoPath)) { - test.skip(true, 'Global setup did not produce a seeded test repo') - return - } + const repoPath = seededRepoPathOrSkip() const session = createRestartSession(testInfo) let firstApp: ElectronApplication | null = null @@ -378,11 +391,7 @@ test.describe('Terminal restart persistence', () => { test('restored Set Title pane label survives agent title churn', async (// oxlint-disable-next-line no-empty-pattern -- Playwright's second fixture arg is testInfo; the first must be an object destructure to opt out of the default fixture set. {}, testInfo) => { - const repoPath = readFileSync(TEST_REPO_PATH_FILE, 'utf-8').trim() - if (!repoPath || !existsSync(repoPath)) { - test.skip(true, 'Global setup did not produce a seeded test repo') - return - } + const repoPath = seededRepoPathOrSkip() const session = createRestartSession(testInfo) let firstApp: ElectronApplication | null = null @@ -451,11 +460,7 @@ test.describe('Terminal restart persistence', () => { test('idle session does not spam session.set writes', async (// oxlint-disable-next-line no-empty-pattern -- Playwright's second fixture arg is testInfo; the first must be an object destructure to opt out of the default fixture set. {}, testInfo) => { - const repoPath = readFileSync(TEST_REPO_PATH_FILE, 'utf-8').trim() - if (!repoPath || !existsSync(repoPath)) { - test.skip(true, 'Global setup did not produce a seeded test repo') - return - } + const repoPath = seededRepoPathOrSkip() const session = createRestartSession(testInfo) let app: ElectronApplication | null = null From 2a22dd0f2a5b3a2cd3661c86e0da3d8579231c85 Mon Sep 17 00:00:00 2001 From: hanjoonchoe Date: Sat, 18 Jul 2026 09:23:44 +0900 Subject: [PATCH 020/137] fix(i18n): translate untranslated Korean catalog strings (#9019) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translate 105 ko.json entries that were still identical to their English source after the machine bootstrap — dialog bodies, settings descriptions, shortcut recorder labels, Windows firewall notices, Jira connection copy, crash-report notices, and source-control panel text. Intentionally left in English: brand/product names, command and config examples, CSS/selector strings used by animated visuals, and lowercase concatenation fragments whose word order cannot be reordered safely. --- src/renderer/src/i18n/locales/ko.json | 210 +++++++++++++------------- 1 file changed, 105 insertions(+), 105 deletions(-) diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 2ddb96e9468..ca88ba53b5c 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -1425,7 +1425,7 @@ "checkActionRequiredHint": "머지가 해제되려면 이 검사에 대해 GitHub에서 수동 작업(예: 워크플로우 실행 승인)이 필요합니다.", "dd5d9a4f17": "{{value0}}에 업데이트됨", "71a3c0f9d2": "워크스페이스 시작", - "filesUnavailable": "Couldn't load changed files.", + "filesUnavailable": "변경된 파일을 불러오지 못했습니다.", "filesRetry": "Retry" }, "QuickOpen": { @@ -2409,8 +2409,8 @@ "error": "워크스페이스 정리 실패", "skippedAncestor": "중첩된 워크스페이스를 제거할 수 없어 건너뛰었습니다.", "timedOut": "{{value0}} 제거가 예상보다 오래 걸리고 있습니다. 백그라운드에서 계속 실행됩니다.", - "stillRemoving": "Still removing workspaces: {{value0}}", - "skippedPendingAncestor": "Skipped because a nested workspace has not finished removing." + "stillRemoving": "아직 워크스페이스를 제거하는 중입니다: {{value0}}", + "skippedPendingAncestor": "중첩된 워크스페이스의 제거가 끝나지 않아 건너뛰었습니다." }, "candidateRow": { "gitLabel": "Git", @@ -3262,8 +3262,8 @@ "remaining": "{{value0}}% 남음" }, "UsagePercentageDisplayChangeNotice": { - "title": "Usage now shows % used", - "body": "Prefer remaining? Change it in Settings.", + "title": "사용량이 이제 사용된 %로 표시됩니다", + "body": "남은 양 표시를 원하시나요? 설정에서 변경할 수 있습니다.", "dismiss": "Dismiss", "openSettings": "Open Settings", "gotIt": "Got it" @@ -4770,7 +4770,7 @@ "forgetBody": "이 작업 공간을 Orca에서만 제거합니다. 파일, Git 워크트리, {{host}}의 브랜치는 그대로 유지됩니다.", "title": "Delete “{{name}}”?", "disconnectedBody": "이 워크트리의 SSH 호스트가 연결되어 있지 않습니다. 재연결하여 원격에서도 삭제하거나 Orca에서만 제거하세요.", - "ghostBody": "{{host}} is no longer a saved SSH host, so this workspace is no longer connected to a live host. It can only be removed from Orca — files and branches on the remote are left untouched.", + "ghostBody": "{{host}}은(는) 더 이상 저장된 SSH 호스트가 아니므로 이 워크스페이스는 라이브 호스트에 연결되어 있지 않습니다. Orca에서 제거만 할 수 있으며, 원격의 파일과 브랜치는 그대로 유지됩니다.", "cancel": "취소", "forget": "Orca에서 제거", "reconnectAndDelete": "재연결 후 삭제" @@ -5089,7 +5089,7 @@ "windowSidebarSummary": "사이드바, 상태 표시줄 및 파일 탐색기", "statusBarCount": "{{value0}}개 표시기가 보입니다.", "gitIgnoredGlossary": ".gitignore와 일치하는 파일.", - "statusBarDescription": "Choose which indicators appear in the status bar.", + "statusBarDescription": "상태 표시줄에 표시할 항목을 선택합니다.", "showPinnedWorktreesInGroups": { "title": "고정된 워크트리를 원래 목록에도 표시", "description": "고정된 워크트리는 Pinned에 유지하면서 전체, 프로젝트, 상태 및 PR에도 표시합니다." @@ -5485,8 +5485,8 @@ "4aa4d9fb73": "가로 스크롤을 요구하는 대신 diff 편집기에서 긴 줄을 줄 바꿈합니다.", "bf16ef0af2": "사용 안 함", "3f6892f307": "사용", - "b82f86d7d2": "Rich Markdown Spellcheck", - "5195f0b9ef": "Show browser spelling underlines and suggestions while editing rich Markdown.", + "b82f86d7d2": "리치 Markdown 맞춤법 검사", + "5195f0b9ef": "리치 Markdown을 편집하는 동안 브라우저 맞춤법 밑줄과 제안을 표시합니다.", "7ddd66fede": "편집기 자동 줄 바꿈", "9b18de6eea": "가로 스크롤 대신 파일 편집기에서 긴 줄을 자동으로 줄 바꿈합니다." }, @@ -6464,7 +6464,7 @@ "2309068a6f": "파괴적", "65358016ea": "버리기", "dev": "Dev Tools", - "devDescription": "Dev-only tools for exercising UI states.", + "devDescription": "UI 상태를 시험해 보기 위한 개발 전용 도구입니다.", "linearTitle": "Linear", "linearDescription": "Give agents the skill to read and update your linked Linear tickets." }, @@ -7764,8 +7764,8 @@ "8e593f04fc": "고정된 탭을 닫기 전에 확인 대화상자를 표시합니다.", "defaultProjectRuntime": "기본 프로젝트 런타임", "defaultProjectRuntimeDescription": "로컬 Windows 프로젝트가 상속할 런타임을 선택합니다.", - "d2d2d929c0": "Rich Markdown Spellcheck", - "4497e2e2bb": "Show browser spelling underlines and suggestions while editing rich Markdown.", + "d2d2d929c0": "리치 Markdown 맞춤법 검사", + "4497e2e2bb": "리치 Markdown을 편집하는 동안 브라우저 맞춤법 밑줄과 제안을 표시합니다.", "e61157e926": "편집기 자동 줄 바꿈", "005be5c699": "가로 스크롤 대신 파일 편집기에서 긴 줄을 자동으로 줄 바꿈합니다." } @@ -8208,7 +8208,7 @@ "cfad7ce5f3": "ai", "a47f51127e": "소스 제어", "6cc5c65e64": "프로젝트별 Git 생성이 재정의됩니다.", - "eec3995dc6": "Git AI Author", + "eec3995dc6": "Git AI 작성자", "cc876ca5f2": "repos", "6469de5368": "프로젝트", "3067595d82": "삭제", @@ -8540,7 +8540,7 @@ "keyword_warp": "warp", "keyword_themes": "themes", "keyword_yaml": "yaml", - "keyword_legacy_title": "Import themes from Warp" + "keyword_legacy_title": "Warp에서 테마 가져오기" }, "yaml_import": { "title": "YAML에서 가져오기", @@ -8765,8 +8765,8 @@ "e1f5e6424c": "{{value0}}개 워크스페이스{{value1}} 연결됨", "disconnect_all": "연결 해제", "account_scope_prefix": "계정 범위", - "2d60ec7921": "Connect a Jira Cloud site with an API token, or a self-hosted Jira with a personal access token or username and password. Credentials are sent to the selected remote runtime and stored there with runtime-supported encryption.", - "977e360b71": "Connect a Jira Cloud site with an API token, or a self-hosted Jira with a personal access token or username and password. Credentials are stored locally and encrypted when local runtime storage supports it." + "2d60ec7921": "API 토큰으로 Jira Cloud 사이트를 연결하거나, 개인 액세스 토큰 또는 사용자 이름과 비밀번호로 자체 호스팅 Jira를 연결하세요. 자격 증명은 선택한 원격 런타임으로 전송되어 런타임이 지원하는 암호화로 저장됩니다.", + "977e360b71": "API 토큰으로 Jira Cloud 사이트를 연결하거나, 개인 액세스 토큰 또는 사용자 이름과 비밀번호로 자체 호스팅 Jira를 연결하세요. 자격 증명은 로컬에 저장되며 로컬 런타임 저장소가 지원하는 경우 암호화됩니다." } } } @@ -8928,26 +8928,26 @@ "e27c8d45bf": "환경 변수로 인해 진단이 비활성화되었습니다." }, "ShortcutCommandBlock": { - "eb72c52c28": "Press a shortcut, or double-tap a modifier (e.g. {{value0}}). Esc cancels.", + "eb72c52c28": "단축키를 누르거나 수정자 키를 두 번 탭하세요(예: {{value0}}). Esc 키로 취소합니다.", "70b5d25583": "{{value0}} shortcut", "287e07ddde": "Modified", "3c83cd7d1c": "Disabled", - "07939d084e": "Reset {{value0}} to default", - "9b02917027": "Reset to default", + "07939d084e": "{{value0}}을(를) 기본값으로 재설정", + "9b02917027": "기본값으로 재설정", "a799f90f82": "Disable {{value0}}", "25e6e76618": "단축키 비활성화", "035a822ef0": "단축키 추가", - "a0e2ef0e61": "Add another shortcut for {{value0}}", + "a0e2ef0e61": "{{value0}}에 다른 단축키 추가", "245c83af24": "다른 단축키 추가", "482a60225d": "Enable {{value0}}", "6287677c37": "활성화", - "01481b964c": "Add shortcut for {{value0}}" + "01481b964c": "{{value0}}에 단축키 추가" }, "ShortcutRecorderButton": { - "1a13bb054d": "Press shortcut keys for {{value0}}. Escape cancels.", - "3732775d74": "Add shortcut for {{value0}}", - "88764af2c1": "Change shortcut for {{value0}}", - "30feb099d6": "Change shortcut {{value0}} of {{value1}} for {{value2}}", + "1a13bb054d": "{{value0}}에 사용할 단축키를 누르세요. Escape 키로 취소합니다.", + "3732775d74": "{{value0}}에 단축키 추가", + "88764af2c1": "{{value0}}의 단축키 변경", + "30feb099d6": "{{value2}}의 단축키 {{value1}}개 중 {{value0}}번째 변경", "5d982a2a1f": "단축키 대기 중", "152e0bcd64": "단축키 추가", "5bd56445da": "단축키 변경", @@ -8955,7 +8955,7 @@ }, "ShortcutRemoveButton": { "9e29aff18b": "{{value0}} 바로가기 {{value1}} 제거", - "2a9588b1c2": "Remove this binding" + "2a9588b1c2": "이 바인딩 제거" }, "BrowserLocalhostWorktreeLabelsSetting": { "8ac8c3ad19": "로컬호스트 작업 트리 레이블", @@ -8981,7 +8981,7 @@ "b94ff260e6": "안드로이드 스튜디오 다운로드", "18925b082d": "SDK 폴더 찾기", "8c52684db8": "지우기", - "76eb88b88e": "iOS Simulator (Xcode)", + "76eb88b88e": "iOS 시뮬레이터(Xcode)", "c6f3ea4f12": "준비됨", "e4f14b50d7": "Xcode를 설치하고 iOS 시뮬레이터 런타임을 추가하세요.", "63fe73a1ea": "Android SDK 폴더를 업데이트할 수 없습니다." @@ -8990,33 +8990,33 @@ "advanced": "고급" }, "DevToolsPane": { - "nativeActionLayoutCheck": "Native action layout check", + "nativeActionLayoutCheck": "네이티브 액션 레이아웃 확인", "secondary": "Secondary", - "secondaryActionClicked": "Secondary action clicked", + "secondaryActionClicked": "보조 액션 클릭됨", "primaryAction": "Primary action", - "primaryActionClicked": "Primary action clicked", + "primaryActionClicked": "기본 액션 클릭됨", "branchHasChanges": "branch has changes", - "viewChangesClicked": "View Changes clicked", - "forceDeleteClicked": "Force Delete clicked", + "viewChangesClicked": "변경 사항 보기 클릭됨", + "forceDeleteClicked": "강제 삭제 클릭됨", "devOnlyCallback": "Dev-only callback.", "infoToast": "Info toast", "infoToastDescription": "명시적 작업이 없는 긴 정보 메시지입니다.", - "localCanaryBehind": "Local canary is behind origin/canary", + "localCanaryBehind": "로컬 canary가 origin/canary보다 뒤처져 있습니다", "successToast": "Success toast", "successToastDescription": "짧은 확인 상태입니다.", "settingsSaved": "Settings saved", "shortSuccessCopy": "짧은 성공 메시지입니다.", "errorToast": "Error toast", "errorToastDescription": "복구 작업이 없는 긴 오류 메시지입니다.", - "failedToSyncWorkspaceMetadata": "Failed to sync workspace metadata", - "nativeActionToast": "Native action toast", + "failedToSyncWorkspaceMetadata": "워크스페이스 메타데이터 동기화에 실패했습니다", + "nativeActionToast": "네이티브 액션 토스트", "nativeActionToastDescription": "Sonner 작업 및 취소 슬롯을 사용합니다.", - "deleteFailureToast": "Delete failure toast", + "deleteFailureToast": "삭제 실패 토스트", "deleteFailureToastDescription": "보기 및 강제 삭제가 포함된 사용자 정의 바닥글입니다.", - "behindBaseRefToast": "Behind base ref toast", + "behindBaseRefToast": "기준 ref 뒤처짐 토스트", "behindBaseRefToastDescription": "인라인 설정 링크와 바닥글 작업이 있는 영구 프롬프트입니다.", "notificationPlayground": "Notification playground", - "notificationPlaygroundDescription": "Dev-only triggers for checking toast layout, recovery actions, and long-copy wrapping.", + "notificationPlaygroundDescription": "토스트 레이아웃, 복구 액션, 긴 텍스트 줄바꿈을 확인하기 위한 개발 전용 트리거입니다.", "devOnly": "Dev only", "orcaCloud": "Orca Cloud", "orcaCloudDescription": "1方클라우드 로그인의 개발자 전용 미리보기. 프로덕션에서는 숨겨집니다. 개발 환경에서는 ORCA_CLOUD_API_URL과 ORCA_CLOUD_CLIENT_ID가 설정되면 사이드바 계정 전환기에도 표시됩니다.", @@ -9130,7 +9130,7 @@ "b7e2d9f0a3": "터미널의 grok /usage 화면과 같은 주간 크레딧 비율입니다.", "c6d1a8f4e2": "{{when}}에 재설정", "e6dadc1e2b": "Monthly usage", - "75e396bf42": "Included monthly usage for Grok unified-billing accounts.", + "75e396bf42": "Grok 통합 결제 계정에 포함된 월간 사용량입니다.", "b36fa2c908": "Signed in. Orca reads the Grok CLI session stored on disk.", "f08c41de73": "Session expired — run grok on the computer running Orca and wait for it to start. If prompted, complete sign-in, then click Refresh usage. No chat message is needed." }, @@ -9139,8 +9139,8 @@ "usagePercentageDisplayRemaining": "남음" }, "TerminalInteractionSection": { - "567633ff50": "Right-click pastes the clipboard into the terminal. Control-click to open the context menu.", - "c64497148a": "Right-click pastes the clipboard. Control-click opens the context menu." + "567633ff50": "우클릭하면 클립보드 내용을 터미널에 붙여넣습니다. 컨텍스트 메뉴를 열려면 Control 클릭하세요.", + "c64497148a": "우클릭하면 클립보드를 붙여넣습니다. Control 클릭으로 컨텍스트 메뉴를 엽니다." }, "MobileRelayStatusSection": { "registered": "Registered", @@ -9148,13 +9148,13 @@ "reconnecting": "Reconnecting", "offline": "Offline", "title": "Orca Relay", - "automatic": "Connect from anywhere when a phone is paired", - "signInPrompt": "Sign in on this desktop to connect from anywhere", - "directStillAvailable": "LAN and Tailscale connections remain available.", - "directNeedsNoAccount": "LAN and Tailscale pairing still work without an account.", + "automatic": "휴대폰이 페어링되면 어디서나 연결", + "signInPrompt": "어디서나 연결하려면 이 데스크톱에서 로그인하세요", + "directStillAvailable": "LAN 및 Tailscale 연결은 계속 사용할 수 있습니다.", + "directNeedsNoAccount": "LAN 및 Tailscale 페어링은 계정 없이도 계속 작동합니다.", "signIn": "Sign in", "unavailable": "Unavailable", - "standby": "Standby — no relay devices" + "standby": "대기 중 — 릴레이 기기 없음" }, "MobilePairingConnectionOptions": { "ready": "준비됨", @@ -9724,7 +9724,7 @@ "b0da3a4d3e": "실행 레시피가 이미 저장되었습니다", "bff4795a6d": "저장된 레시피를 업데이트하려면 agent, 인수 또는 프롬프트 템플릿을 변경하세요.", "5c75b24735": "Orca가 시작하기 전에 agent가 받을 내용을 사용자 지정합니다.", - "repoAgentOverrideNote": "This repository overrides your global default ({{global}}) and currently runs {{effective}}. Save to this repository to change what runs here." + "repoAgentOverrideNote": "이 저장소는 전역 기본값({{global}})을 재정의하며 현재 {{effective}}을(를) 실행합니다. 이 저장소에서 실행할 항목을 바꾸려면 이 저장소에 저장하세요." }, "SourceControlTextGenerationDialog": { "c5b7fa7cb6": "전역 기본값으로 저장", @@ -9820,9 +9820,9 @@ "066fedd446": "실패한 작업", "ae8a04ef17": "충돌 파일 세부정보를 사용할 수 없습니다.", "73d0675356": "충돌 세부정보 새로고침 중…", - "f5bc5c4cf1": "The hosting provider reports conflicts, but local Git did not reproduce them. Refresh the review or push the branch to recalculate mergeability.", - "5bc9bda2af": "Run from this worktree", - "e87fb3d929": "Copy mergeability refresh commands", + "f5bc5c4cf1": "호스팅 제공자는 충돌을 보고했지만 로컬 Git에서는 재현되지 않았습니다. 리뷰를 새로 고치거나 브랜치를 푸시하여 병합 가능성을 다시 계산하세요.", + "5bc9bda2af": "이 워크트리에서 실행", + "e87fb3d929": "병합 가능성 새로 고침 명령 복사", "1e53e45072": "복사됨", "084c516efb": "Copy commands", "5dc3af25c0": "댓글 선택", @@ -9832,23 +9832,23 @@ "49ea0937e4": "댓글을 해결 목록에 추가", "9fecebb29d": "추가", "b8c4e2a1f7": "전체 로그 보기", - "f8a2c91d04": "Queue for agent", + "f8a2c91d04": "에이전트 대기열에 추가", "b4e8a1c902": "Queued", "7c1f0a2b11": "열기", "e8b4c1a903": "Resolved · {{value0}}", - "c3a8e5d710": "Needs review · {{value0}}", + "c3a8e5d710": "리뷰 필요 · {{value0}}", "a7f0c7e8d1": "Queue", "8a621a2c4f": "그룹화", "b13f85d75c": "타임라인", - "f5cf324efa": "Comment display options", + "f5cf324efa": "댓글 표시 옵션", "5e6e5a13fa": "보기", - "actionRequiredHint": "This check needs a manual action on GitHub (for example, approving the workflow run) before merging is unblocked.", + "actionRequiredHint": "이 검사는 병합 차단이 해제되기 전에 GitHub에서 수동 작업(예: 워크플로 실행 승인)이 필요합니다.", "b3195cba33": "작성자의 봇 지정 해제", "f588b46a6c": "작성자를 봇으로 지정" }, "empty": { "state": { - "3322603418": "Pull request status unavailable", + "3322603418": "풀 리퀘스트 상태를 사용할 수 없음", "5b0cfae9a5": "체크와 리뷰를 시작하려면 {{value0}}을(를) 만드세요.", "13e1c7d5ed": "{{value0}}을(를) 찾을 수 없습니다.", "d372072df1": "현재 속도 제한 예산으로 인해 GitHub 새로 고침이 일시 중지되었습니다.", @@ -9864,7 +9864,7 @@ "41252bc53f": "게시되지 않은 브랜치", "05e4aec17b": "{{value0}} 검사는 작업이 완료된 후에 사용할 수 있습니다.", "d77c513c1e": "{{value0}} 진행 중", - "b597440265": "Refresh GitHub status for this branch to load checks and review." + "b597440265": "검사와 리뷰를 불러오려면 이 브랜치의 GitHub 상태를 새로 고치세요." } } } @@ -10004,7 +10004,7 @@ "226b85a3a7": "가져오기", "323bb614aa": "Commit 및 동기화", "2b8e6595fd": "Commit", - "04d709801d": "Fetch from remote without merging" + "04d709801d": "병합하지 않고 원격에서 가져오기(fetch)" } }, "primary": { @@ -10029,8 +10029,8 @@ "2d8f185fbc": "부분적으로 준비된 파일을 commit 하기 전에 모든 변경 사항을 준비합니다.", "a6457b46a7": "commit 하기 전에 충돌을 해결하세요.", "484f45c439": "{{value0}} 진행 중…", - "6f7a8b9c0d": "Remote operation in progress…", - "7f8a9b0c1d": "Remote operation in progress — try again once it finishes", + "6f7a8b9c0d": "원격 작업 진행 중…", + "7f8a9b0c1d": "원격 작업 진행 중 — 완료된 후 다시 시도하세요", "74fc171e99": "강제 푸시 진행 중…", "16aee3a5c1": "Commit 진행 중…", "e61b0d7a3c": "commits을 게시하기 전에 브랜치를 체크아웃하세요.", @@ -10080,8 +10080,8 @@ "8b8ee9d22f": "이 파일을 소유한 호스트를 확인할 수 없습니다. 워크스페이스 연결을 확인한 후 다시 시도하세요.", "af1270b90d": "{{count}}개 항목을 영구적으로 삭제하시겠습니까?", "dd029aa5cd": "원격 호스트의 선택한 항목과 디렉터리 내용을 영구적으로 삭제합니다. 이 작업은 되돌릴 수 없습니다.", - "77fdc36183": "Delete {{count}} items?", - "fca915a67a": "Remote items are permanently deleted and cannot be undone. Local items move to the {{value0}}." + "77fdc36183": "{{count}}개 항목을 삭제하시겠습니까?", + "fca915a67a": "원격 항목은 영구적으로 삭제되며 되돌릴 수 없습니다. 로컬 항목은 {{value0}}(으)로 이동합니다." }, "useFileExplorerHandlers": { "32cd9fd991": "심볼릭 링크 대상을 열 수 없습니다" @@ -10130,7 +10130,7 @@ "sessionsShownCompact": "{{value0}} 표시됨", "originalPaneUnavailable": "원래 창은 더 이상 사용할 수 없습니다.", "worktreeUnavailable": "워크트리는 더 이상 사용할 수 없습니다.", - "openSupportedWorkspace": "Open a workspace before resuming a session.", + "openSupportedWorkspace": "세션을 재개하기 전에 워크스페이스를 여세요.", "localSessionSshWorkspaceUnsupported": "이 세션의 기록은 이 컴퓨터에 저장되어 있어 SSH 워크스페이스에서는 재개할 수 없습니다. 대신 로컬 워크스페이스를 여세요.", "sessionHostMismatchUnsupported": "이 세션은 다른 호스트에 속합니다. 재개하려면 같은 호스트의 워크스페이스를 여세요." }, @@ -10162,7 +10162,7 @@ "projectScope": "프로젝트", "currentProjectLower": "현재 프로젝트", "project": "프로젝트", - "hostScopeAriaLabel": "Session History host: {{value0}}", + "hostScopeAriaLabel": "세션 기록 호스트: {{value0}}", "host": "호스트" }, "AiVaultSessionDetails": { @@ -10312,9 +10312,9 @@ "viewLog": "로그 보기" }, "aiVaultSessionLogOpen": { - "workspaceGone": "Couldn't open log — workspace is no longer available.", - "notAuthorized": "Couldn't open log — path not authorized.", - "alreadyEditable": "Log is already open for editing." + "workspaceGone": "로그를 열 수 없습니다 — 워크스페이스를 더 이상 사용할 수 없습니다.", + "notAuthorized": "로그를 열 수 없습니다 — 경로가 승인되지 않았습니다.", + "alreadyEditable": "로그가 이미 편집용으로 열려 있습니다." }, "github": { "refresh": { @@ -10729,7 +10729,7 @@ "9883b58693": "Orca 모바일 닫기", "fb5f28330e": "사이드바에 표시", "c669abcf8f": "사이드바에서 숨기기", - "e1c7b4a92d": "Configure in Settings > Mobile.", + "e1c7b4a92d": "설정 > 모바일에서 구성하세요.", "a4f8c2d91e": "왼쪽 사이드바에서 Orca Mobile 제거", "b7e3d1c84a": "왼쪽 사이드바에 Orca Mobile 표시", "f3d8e5b71a": "단축키를 왼쪽 사이드바에 다시 추가합니다." @@ -10839,19 +10839,19 @@ "add-custom": "사용자 지정 주소 추가…" }, "WindowsFirewallNotice": { - "repair-success": "Windows Firewall now allows Orca Mobile on private networks", - "repair-failed": "Could not update the Windows Firewall rules", - "public-title": "Windows marks this network as public", - "missing-title": "Allow phone connections through Windows Firewall", - "public-description": "Change this trusted Wi-Fi network to Private before allowing Orca Mobile connections.", - "missing-description": "Windows may block the pairing server. Add a rule for this Orca app and TCP port {{port}} on Private networks.", - "open-settings": "Open network settings", - "waiting": "Waiting for Windows…", - "allow": "Allow phone connections", - "repair-unverified": "Windows Firewall access could not be verified", - "blocked-title": "Windows may be blocking Orca Mobile", - "blocked-description": "An existing inbound Block rule can override the pairing exception. Repair removes conflicting TCP rules for this Orca app, then allows port {{port}} on Private networks.", - "repair": "Repair firewall access" + "repair-success": "이제 Windows 방화벽이 개인 네트워크에서 Orca Mobile을 허용합니다", + "repair-failed": "Windows 방화벽 규칙을 업데이트할 수 없습니다", + "public-title": "Windows가 이 네트워크를 공용으로 표시합니다", + "missing-title": "Windows 방화벽에서 휴대폰 연결 허용", + "public-description": "Orca Mobile 연결을 허용하기 전에 이 신뢰할 수 있는 Wi-Fi 네트워크를 개인으로 변경하세요.", + "missing-description": "Windows가 페어링 서버를 차단할 수 있습니다. 개인 네트워크에서 이 Orca 앱과 TCP 포트 {{port}}에 대한 규칙을 추가하세요.", + "open-settings": "네트워크 설정 열기", + "waiting": "Windows 대기 중…", + "allow": "휴대폰 연결 허용", + "repair-unverified": "Windows 방화벽 액세스를 확인할 수 없습니다", + "blocked-title": "Windows가 Orca Mobile을 차단하고 있을 수 있습니다", + "blocked-description": "기존 인바운드 차단 규칙이 페어링 예외보다 우선할 수 있습니다. 복구를 실행하면 이 Orca 앱의 충돌하는 TCP 규칙을 제거한 다음 개인 네트워크에서 포트 {{port}}을(를) 허용합니다.", + "repair": "방화벽 액세스 복구" } }, "gitlab": { @@ -11506,9 +11506,9 @@ } }, "useEmulatorScreenKeyboard": { - "pasteTooLarge": "Paste is too large for emulator keyboard input.", - "unsupportedPasteText": "Emulator keyboard paste supports US keyboard text only.", - "pasteTargetUnavailable": "Emulator keyboard paste failed because the device is not ready." + "pasteTooLarge": "붙여넣을 내용이 에뮬레이터 키보드 입력에 비해 너무 큽니다.", + "unsupportedPasteText": "에뮬레이터 키보드 붙여넣기는 US 키보드 텍스트만 지원합니다.", + "pasteTargetUnavailable": "기기가 준비되지 않아 에뮬레이터 키보드 붙여넣기에 실패했습니다." } } }, @@ -11533,7 +11533,7 @@ "21783df79f": "파일 트리 축소", "481e63ca52": "파일", "d5ac717d65": "commit 안 됨", - "39b6b9e4e4": "Committed on Branch" + "39b6b9e4e4": "브랜치에 커밋됨" }, "CombinedDiffViewer": { "35cc27aeb2": "소스 제어에서", @@ -12193,14 +12193,14 @@ }, "submit": { "notice": { - "unknownError": "The crash report request failed before it returned a reason.", - "ticketUploaded": "Diagnostic ticket {{value0}} was uploaded but not linked.", - "uncheckDiagnostics": "Uncheck \"Attach recent diagnostic logs\" and try again, or copy the details.", - "checkConnection": "Check your connection and try again, or copy the details.", - "notSent": "Crash report wasn't sent", + "unknownError": "크래시 보고서 요청이 이유를 반환하기 전에 실패했습니다.", + "ticketUploaded": "진단 티켓 {{value0}}이(가) 업로드되었지만 연결되지 않았습니다.", + "uncheckDiagnostics": "\"최근 진단 로그 첨부\"를 해제하고 다시 시도하거나, 세부 정보를 복사하세요.", + "checkConnection": "연결을 확인하고 다시 시도하거나, 세부 정보를 복사하세요.", + "notSent": "크래시 보고서가 전송되지 않았습니다", "copyDetails": "Copy Details", - "sentWithoutDiagnostics": "Crash report sent without diagnostic logs", - "diagnosticsReason": "Diagnostic logs were not attached: {{value0}}" + "sentWithoutDiagnostics": "진단 로그 없이 크래시 보고서를 전송했습니다", + "diagnosticsReason": "진단 로그가 첨부되지 않았습니다: {{value0}}" } }, "copy": { @@ -12391,9 +12391,9 @@ "c13693fe27": "{{value0}}개 주석", "074f0ed10b": "{{value0}}개 주석이 준비되었습니다. 다른 요소를 선택하거나 모든 피드백을 복사하세요.", "a2164a6e5a": "{{value0}}개 주석이 준비되었습니다. 다른 요소를 선택하거나 모든 피드백을 복사하세요.", - "9f6f2e8c19": "The downloaded file path is unavailable.", - "0c79b7634d": "Could not open the downloaded file. It may have been moved or deleted.", - "397d9dc923": "Could not show the downloaded file. It may have been moved or deleted.", + "9f6f2e8c19": "다운로드한 파일 경로를 사용할 수 없습니다.", + "0c79b7634d": "다운로드한 파일을 열 수 없습니다. 파일이 이동되었거나 삭제되었을 수 있습니다.", + "397d9dc923": "다운로드한 파일을 표시할 수 없습니다. 파일이 이동되었거나 삭제되었을 수 있습니다.", "39c04fed61": "Downloading paused", "5c3d530a68": "Downloaded", "4bb7424d6b": "Canceled", @@ -12757,7 +12757,7 @@ "AutomationSetupDecisionField": { "5a7863909c": "새 워크스페이스마다 설정 실행", "18f000ad4e": "고급", - "874b72195b": "When this automation creates a workspace, prepare it the same way creating a worktree by hand does — run the project's setup and open its terminal tabs." + "874b72195b": "이 자동화가 워크스페이스를 만들 때, 워크트리를 직접 만들 때와 같은 방식으로 준비합니다 — 프로젝트 설정을 실행하고 터미널 탭을 엽니다." } }, "agent": { @@ -12830,23 +12830,23 @@ "e176f9d0c5": "Jira Cloud 사이트 URL", "d785c42b8b": "Jira Cloud 사이트 URL, Atlassian 이메일, API 토큰을 사용해 이슈를 탐색합니다.", "8388bdea2b": "Jira 사이트 연결", - "2e2b69e48e": "Use a self-hosted Jira base URL and a personal access token to browse issues.", - "b67e919bd5": "Jira instance type", + "2e2b69e48e": "자체 호스팅 Jira 기본 URL과 개인 액세스 토큰으로 이슈를 탐색하세요.", + "b67e919bd5": "Jira 인스턴스 유형", "17787d6e4b": "Atlassian Cloud", "bc7a831773": "Self-hosted", - "3489e186d6": "Jira site URL", + "3489e186d6": "Jira 사이트 URL", "cbc27fa599": "https://jira.example.com", - "730d973bae": "Personal access token", - "8b9c7b9e7b": "Jira personal access token", - "ccfb086d3e": "Create a personal access token in your Jira profile under Personal Access Tokens.", - "1d947a07ab": "Use a self-hosted Jira base URL, username, and password to browse issues.", - "f49708c369": "Jira authentication method", + "730d973bae": "개인 액세스 토큰", + "8b9c7b9e7b": "Jira 개인 액세스 토큰", + "ccfb086d3e": "Jira 프로필의 개인 액세스 토큰 메뉴에서 개인 액세스 토큰을 생성하세요.", + "1d947a07ab": "자체 호스팅 Jira 기본 URL, 사용자 이름, 비밀번호로 이슈를 탐색하세요.", + "f49708c369": "Jira 인증 방법", "84a810dd0e": "Username & password", "8d1223fa5c": "Username", "be9eba0a1b": "username", "70035652d7": "Password", - "c50abbf340": "Jira account password", - "d8737db691": "Use your Jira Server or Data Center account username and password." + "c50abbf340": "Jira 계정 비밀번호", + "d8737db691": "Jira Server 또는 Data Center 계정의 사용자 이름과 비밀번호를 사용하세요." } } }, From 1bd757ad2fde37c0ac78a83beacbcfbdc6bbae48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9F=A6=E7=BC=96=E4=B8=89=E7=BB=9D?= Date: Sat, 18 Jul 2026 08:25:55 +0800 Subject: [PATCH 021/137] =?UTF-8?q?fix(i18n):=20correct=20zh=20translation?= =?UTF-8?q?=20of=20"Pin=20Tab"=20to=20=E5=9B=BA=E5=AE=9A=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=20(#9157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: weixin Co-authored-by: Claude Opus 4.8 --- config/scripts/locale-zh-value-overrides.mjs | 2 ++ src/renderer/src/i18n/locales/zh.json | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/config/scripts/locale-zh-value-overrides.mjs b/config/scripts/locale-zh-value-overrides.mjs index 24afcbd3701..d11c747c1df 100644 --- a/config/scripts/locale-zh-value-overrides.mjs +++ b/config/scripts/locale-zh-value-overrides.mjs @@ -42,6 +42,8 @@ export const ZH_VALUE_OVERRIDES = { 'Open the PR details to view current reviewers.': '打开 PR 详情以查看当前评审人。', 'Review comment added.': '已添加评审评论。', 'Loading labels': '加载标签', + // Why: MT rendered the "Pin Tab" action as "引脚标签" (noun reading of "pin"); pair it with 取消固定标签. + 'Pin Tab': '固定标签', Approved: '已批准', Strike: '删除线', Bold: '粗体', diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index bbc6a337e7f..901c57a1004 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -2747,7 +2747,7 @@ "7e8106899f": "向左拆分", "2186a8407c": "向下拆分", "96354ed249": "向上拆分", - "911542656f": "引脚标签", + "911542656f": "固定标签", "c5aaee8c39": "取消固定标签" }, "EditorFileTab": { @@ -2765,7 +2765,7 @@ "e3ff145b98": "向左拆分", "1d04b1630b": "向下拆分", "6b3efb106e": "向上拆分", - "fdd29eb669": "引脚标签", + "fdd29eb669": "固定标签", "8e9d603a09": "取消固定标签" }, "QuickLaunchButton": { @@ -2805,7 +2805,7 @@ "c2d8b0991f": "紫色的", "cb3eadefd2": "蓝色", "20baa43c05": "没有任何", - "60f958ec75": "引脚标签", + "60f958ec75": "固定标签", "417722e9c2": "取消固定标签", "splitTerminalRight": "向右拆分终端", "splitTerminalDown": "向下拆分终端" From 7c7bf259a2d00a9d6aa186b16b4289b6c7a4b73a Mon Sep 17 00:00:00 2001 From: Asurada <43401755+ousugo@users.noreply.github.com> Date: Sat, 18 Jul 2026 08:27:18 +0800 Subject: [PATCH 022/137] fix(i18n): correct Chinese UI terminology (#9154) --- src/renderer/src/i18n/locales/zh.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 901c57a1004..4c88fb17470 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -4458,7 +4458,7 @@ } }, "useAddRepoCloneFlow": { - "4d0013cc93": "克隆存储库", + "4d0013cc93": "仓库已克隆", "0dc4d1b657": "输入克隆目标的服务器路径。" }, "useAddRepoLocalFolderFlow": { @@ -6141,7 +6141,7 @@ "39da8a10bf": "使用 GitHub 头像", "c490787d24": "表情符号", "b2d7fd2116": "图标", - "2d8bd302fa": "阿凡达", + "2d8bd302fa": "头像", "913c55833d": "自定义仓库颜色 {{value0}}", "0e5f0693c1": "选择自定义仓库颜色", "642dc29c6d": "颜色", @@ -6601,8 +6601,8 @@ "f8050f6307": "已同步 {{value0}} 服务器{{value1}}", "68c13b4589": "测试失败", "81d08bcddf": "连接成功", - "2c4ee7332b": "远程继电器复位失败", - "db2e48975e": "远程继电器复位", + "2c4ee7332b": "无法重置远程中继", + "db2e48975e": "远程中继已重置", "025e107643": "无法结束远程终端", "90e308c98b": "远程终端结束", "a43de1d3ee": "断开连接失败", @@ -6620,17 +6620,17 @@ }, "SshPassphraseDialog": { "d5a234456f": "取消", - "c3ce71aad6": "输入密码", + "c3ce71aad6": "输入密钥口令", "abaa0dc653": "输入密码", - "ce4fdf7914": "输入密码", + "ce4fdf7914": "请输入以下密钥的口令:", "dbf9b6f2d0": "输入密码", "c55f105262": "无法取消 SSH 凭据请求", "b8e88fd0de": "无法提交 SSH 凭据", - "405066423c": "开锁", + "405066423c": "解锁", "bec2c1318f": "连接", - "8a349e3fac": "{{value0}} 的密码", + "8a349e3fac": "{{value0}} 的密钥口令", "cab3d5f5a5": "{{value0}} 的密码", - "1f3dde805d": "SSH 密钥密码", + "1f3dde805d": "SSH 密钥口令", "106bd57f4a": "SSH 密码" }, "SshTargetCard": { @@ -6641,8 +6641,8 @@ "7f7b3d7ab4": "删除目标", "3d21a22d0e": "移除目标", "3d8af2949f": "编辑目标", - "762a48c662": "重置远程继电器", - "97dea4e8cf": "重置远程继电器", + "762a48c662": "重置远程中继", + "97dea4e8cf": "正在重置远程中继", "da16e108e6": "结束远程终端", "c77f1abfe3": "结束远程终端", "18968ede9e": "错误", @@ -6655,7 +6655,7 @@ "7e66942808": "这将停止此 SSH 目标上的活动终端会话。重新连接不会恢复它们。", "accf177a03": "结束远程终端?", "26be00392d": "这将强制停止此 SSH 目标的远程中继。该目标的活动远程终端和端口转发将结束。", - "570a7a0574": "重置远程继电器?", + "570a7a0574": "重置远程中继?", "3bb0cf0ee4": "这将删除目标并结束任何活动的远程终端。", "4808966c41": "删除 SSH 目标" }, @@ -7983,7 +7983,7 @@ "d0c89bc4a9": "覆盖", "87711f4b8f": "VPN", "16bff559a0": "尾网", - "c690e3ee38": "尾鳞", + "c690e3ee38": "tailscale", "a023683767": "界面", "7b37c2e557": "网络", "3190ef67a4": "选择用于手机配对的网络地址。", From 3f335efdb90594a11c6e958a56237542319ebbc5 Mon Sep 17 00:00:00 2001 From: eisen0419 Date: Fri, 17 Jul 2026 17:29:04 -0700 Subject: [PATCH 023/137] feat(agents): pi session resume support (#8876) * feat(agents): pi session resume support * fix(pi): require persisted session files for resume * test(sleeping-agent): use non-resumable sentinel in malformed-record fixture The 'drops malformed sleeping agent resume records' test used agent:'pi' as its example of an unknown/non-resumable agent, expecting the record to be dropped. This PR added 'pi' to RESUMABLE_TUI_AGENTS, making that fixture valid and retained, so the toBeUndefined assertion broke. Switch the malformed-case fixture to a genuinely non-resumable sentinel ('definitely-not-an-agent') so the drop-malformed path is still exercised; no other assertions changed. * Add durable resume identity for Pi sessions without fabricating turn sta Pi's `session_start` hook now carries the session file needed to resume a sleeping pane, but until now Orca either discarded it or treated it as a fake status transition. Thread a `providerSessionOnly` envelope through the hook listener, relay, main-process server, and renderer store so resume identity (and its session-file-scoped equality/claim key) can be persisted and replayed without emitting prompt telemetry or a visible working/done row. * Add durable resume identity for completed Pi sessions Pi's agent_end hook marks a turn done, but the underlying TUI session stays alive and resumable. Previously a `done` status wiped sleeping records and launch config as if the session ended, so hibernation, manual worktree sleep, and quit-capture all lost Pi's resume identity. - Track a "live recovery" record for done-but-still-resumable Pi sessions, exempting it from the usual done-state cleanup paths in agent-status.ts and agent-hibernation-planner.ts - Gate providerSessionOnly rows and sleeping-agent schema records on actual resumability (getAgentResumeArgv) instead of trusting the presence of a provider session - Wait for Pi to persist its session file before advertising resume metadata, and treat `/reload` as a non-terminal event so it doesn't clobber visible status - Extend SSH relay envelopes to carry providerSessionOnly so remote hosts get the same behavior * Add explicit periodic/quit mode to sleeping-agent session capture Split captureAllSleepingAgentSessions into 'periodic' and 'quit' modes so a background checkpoint can no longer downgrade a confirmed-quit record or promote a completed Pi session without an authoritative transcript path. Updates all call sites and tests accordingly. * Use normalizeAgentStatusPayload for default pi status Remove unnecessary JSON.stringify wrapper and call the appropriate normalization function directly. --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> --- src/main/agent-hooks/server.test.ts | 207 ++++++++++++ src/main/agent-hooks/server.ts | 63 +++- src/main/index.ts | 21 +- .../pi/agent-status-extension-source.test.ts | 150 ++++++++- src/main/pi/agent-status-extension-source.ts | 40 ++- src/main/pi/agent-status-handler-source.ts | 19 +- ...ay-session-agent-hooks.integration.test.ts | 28 +- src/main/ssh/ssh-relay-session.ts | 2 + src/relay/agent-hook-server.test.ts | 56 ++++ src/relay/agent-hook-server.ts | 1 + src/renderer/src/App.tsx | 4 +- .../terminal-pane/pty-connection.ts | 20 +- src/renderer/src/hooks/useIpcEvents.ts | 32 +- .../lib/agent-hibernation-coordinator.test.ts | 49 +++ .../src/lib/agent-hibernation-planner.test.ts | 45 +++ .../src/lib/agent-hibernation-planner.ts | 14 +- .../src/lib/pi-session-resume-wake.test.ts | 61 ++++ .../src/lib/resume-sleeping-agent-session.ts | 23 +- .../src/lib/sleeping-agent-pane-ownership.ts | 3 +- .../agent-status-manual-sleep-capture.test.ts | 19 ++ .../agent-status-provider-session.test.ts | 285 ++++++++++++++++ .../slices/agent-status-quit-capture.test.ts | 66 +++- src/renderer/src/store/slices/agent-status.ts | 311 ++++++++++++++++-- src/shared/agent-hook-listener.test.ts | 72 ++++ src/shared/agent-hook-listener.ts | 25 +- src/shared/agent-hook-relay.ts | 2 + src/shared/agent-session-resume.test.ts | 37 ++- src/shared/agent-session-resume.ts | 68 +++- src/shared/agent-status-types.ts | 2 + ...pace-session-schema.sleeping-agent.test.ts | 72 +++- .../workspace-session-sleeping-agents.ts | 52 +-- ...ent-session-live-force-exit-resume.spec.ts | 2 +- 32 files changed, 1703 insertions(+), 148 deletions(-) create mode 100644 src/renderer/src/lib/pi-session-resume-wake.test.ts create mode 100644 src/renderer/src/store/slices/agent-status-provider-session.test.ts diff --git a/src/main/agent-hooks/server.test.ts b/src/main/agent-hooks/server.test.ts index 1239e6dd9eb..b666c9329bb 100644 --- a/src/main/agent-hooks/server.test.ts +++ b/src/main/agent-hooks/server.test.ts @@ -5956,6 +5956,71 @@ describe('Last-status persistence', () => { } }) + it('persists and hydrates Pi session identity without creating status telemetry', async () => { + const firstServer = new AgentHookServer() + const firstRendererListener = vi.fn() + const statusChangeListener = vi.fn() + firstServer.setListener(firstRendererListener) + firstServer.subscribeStatusChanges(statusChangeListener) + await firstServer.start({ env: 'production', userDataPath }) + try { + const response = await postHookEvent( + firstServer, + buildBody({ + hook_event_name: 'session_start', + session_id: 'pi-session-1', + session_file: '/tmp/pi-session-1.jsonl' + }), + '/hook/pi' + ) + expect(response.status).toBe(204) + expect(firstRendererListener).toHaveBeenCalledWith( + expect.objectContaining({ + paneKey: PANE, + providerSessionOnly: true, + providerSession: { + key: 'session_id', + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-1.jsonl' + } + }) + ) + expect(statusChangeListener).toHaveBeenCalledWith([]) + expect(trackMock).not.toHaveBeenCalledWith('agent_prompt_sent', expect.anything()) + + firstServer.flushStatusPersistSync() + const file = JSON.parse(readFileSync(lastStatusPath(), 'utf8')) + expect(file.entries[PANE]).toMatchObject({ + providerSessionOnly: true, + providerSession: { + key: 'session_id', + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-1.jsonl' + } + }) + } finally { + firstServer.stop() + } + + const hydratedServer = new AgentHookServer() + await hydratedServer.start({ env: 'production', userDataPath }) + try { + const hydratedListener = vi.fn() + hydratedServer.setListener(hydratedListener) + expect(hydratedListener).toHaveBeenCalledWith( + expect.objectContaining({ + paneKey: PANE, + providerSessionOnly: true, + providerSession: expect.objectContaining({ transcriptPath: '/tmp/pi-session-1.jsonl' }), + isReplay: true + }) + ) + expect(hydratedServer.getStatusChangeSnapshot()).toEqual([]) + } finally { + hydratedServer.stop() + } + }) + it('does not write prompt interaction keys to last-status.json', async () => { const server = new AgentHookServer() await server.start({ @@ -6122,6 +6187,43 @@ describe('Last-status persistence', () => { } }) + it('drops hydrated metadata-only entries without a resumable Pi session', async () => { + mkdirSync(join(userDataPath, 'agent-hooks'), { recursive: true }) + const receivedAt = recentTs() + writeFileSync( + lastStatusPath(), + JSON.stringify({ + version: 2, + entries: { + [PANE]: { + paneKey: PANE, + tabId: 'tab-1', + worktreeId: 'wt-1', + receivedAt, + stateStartedAt: receivedAt, + providerSessionOnly: true, + providerSession: { key: 'session_id', id: 'pi-session-without-file' }, + payload: { state: 'done', prompt: '', agentType: 'pi' } + } + } + }), + 'utf8' + ) + + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const server = new AgentHookServer() + await server.start({ env: 'production', userDataPath }) + try { + expect(server.getStatusSnapshot()).toEqual([]) + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('last-status hydrate dropped 1 entries') + ) + } finally { + server.stop() + warnSpy.mockRestore() + } + }) + it('rejects a stale version mismatch on hydrate', async () => { mkdirSync(join(userDataPath, 'agent-hooks'), { recursive: true }) writeFileSync( @@ -6466,6 +6568,111 @@ describe('Last-status persistence', () => { }) describe('AgentHookServer ingestRemote', () => { + it('caches and replays Pi session identity without exposing a turn-status change', () => { + vi.useFakeTimers() + vi.setSystemTime(1_000) + try { + const server = new AgentHookServer() + const rendererListener = vi.fn() + const statusChangeListener = vi.fn() + server.setListener(rendererListener) + server.subscribeStatusChanges(statusChangeListener) + + server.ingestRemote( + { + paneKey: PANE, + tabId: 'tab-1', + worktreeId: 'wt-1', + providerSession: { + key: 'session_id', + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-1.jsonl' + }, + providerSessionOnly: true, + payload: { state: 'done', prompt: '', agentType: 'pi' } + }, + 'conn-1' + ) + + expect(rendererListener).toHaveBeenCalledWith( + expect.objectContaining({ + paneKey: PANE, + connectionId: 'conn-1', + providerSessionOnly: true, + providerSession: { + key: 'session_id', + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-1.jsonl' + } + }) + ) + expect(statusChangeListener).toHaveBeenCalledWith([]) + expect(server.getStatusChangeSnapshot()).toEqual([]) + expect(server.getStatusSnapshot()).toEqual([ + expect.objectContaining({ + paneKey: PANE, + providerSessionOnly: true, + providerSession: expect.objectContaining({ transcriptPath: '/tmp/pi-session-1.jsonl' }) + }) + ]) + expect(trackMock).not.toHaveBeenCalledWith('agent_prompt_sent', expect.anything()) + + const replayListener = vi.fn() + server.setListener(replayListener) + expect(replayListener).toHaveBeenCalledWith( + expect.objectContaining({ paneKey: PANE, providerSessionOnly: true, isReplay: true }) + ) + } finally { + vi.useRealTimers() + } + }) + + it('rejects invalid remote metadata-only session envelopes', () => { + const server = new AgentHookServer() + const listener = vi.fn() + server.setListener(listener) + + server.ingestRemote( + { + paneKey: PANE, + tabId: 'tab-1', + worktreeId: 'wt-1', + providerSessionOnly: true, + payload: { state: 'done', prompt: '', agentType: 'pi' } + }, + 'conn-1' + ) + server.ingestRemote( + { + paneKey: PANE, + tabId: 'tab-1', + worktreeId: 'wt-1', + providerSessionOnly: true, + providerSession: { key: 'session_id', id: 'pi-session-1' }, + payload: { state: 'done', prompt: '', agentType: 'pi' } + }, + 'conn-1' + ) + server.ingestRemote( + { + paneKey: PANE, + tabId: 'tab-1', + worktreeId: 'wt-1', + providerSessionOnly: true, + providerSession: { + key: 'session_id', + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-1.jsonl' + }, + payload: { state: 'done', prompt: '', agentType: 'claude' } + }, + 'conn-1' + ) + + expect(listener).not.toHaveBeenCalled() + expect(server.getStatusSnapshot()).toEqual([]) + }) + it('stamps connectionId and forwards a valid relay envelope to the listener', () => { const server = new AgentHookServer() const payload = parseAgentStatusPayload( diff --git a/src/main/agent-hooks/server.ts b/src/main/agent-hooks/server.ts index 3480378d32c..e810a45f3d4 100644 --- a/src/main/agent-hooks/server.ts +++ b/src/main/agent-hooks/server.ts @@ -64,7 +64,11 @@ import { } from '../../shared/agent-question-answered-intent' import { parseLegacyNumericPaneKey, parsePaneKey } from '../../shared/stable-pane-id' import type { LegacyPaneKeyAliasEntry } from '../../shared/types' -import { normalizeAgentProviderSession } from '../../shared/agent-session-resume' +import { + getAgentResumeArgv, + normalizeAgentProviderSession, + type AgentProviderSessionMetadata +} from '../../shared/agent-session-resume' import { isCommandCodeNewTurnWhileWorking } from '../../shared/command-code-turn-boundary' export type { AgentHookSource } @@ -197,6 +201,15 @@ function dropHydratedIdleClaudeSubagents( } } +// Why: the sole gate deciding whether a providerSessionOnly row is trustworthy +// enough to keep. Shared so the hydrate and relay-ingest paths can't drift. +function isValidPiProviderSessionOnly( + providerSession: AgentProviderSessionMetadata | undefined, + agentType: AgentType | undefined +): boolean { + return Boolean(providerSession && agentType === 'pi' && getAgentResumeArgv('pi', providerSession)) +} + function sanitizeHydratedEntry( paneKey: string, rawEntry: unknown @@ -253,6 +266,11 @@ function sanitizeHydratedEntry( if (!payload) { return null } + const providerSession = normalizeAgentProviderSession(record.providerSession) ?? undefined + const providerSessionOnly = record.providerSessionOnly === true + if (providerSessionOnly && !isValidPiProviderSessionOnly(providerSession, payload.agentType)) { + return null + } return { paneKey, launchToken: typeof record.launchToken === 'string' ? record.launchToken : undefined, @@ -264,7 +282,8 @@ function sanitizeHydratedEntry( toolUseId: typeof record.toolUseId === 'string' ? record.toolUseId : undefined, toolAgentId: typeof record.toolAgentId === 'string' ? record.toolAgentId : undefined, toolAgentType: typeof record.toolAgentType === 'string' ? record.toolAgentType : undefined, - providerSession: normalizeAgentProviderSession(record.providerSession) ?? undefined, + providerSession, + providerSessionOnly: providerSessionOnly ? true : undefined, payload, receivedAt, stateStartedAt @@ -281,6 +300,7 @@ function toAgentStatusIpcPayload(entry: EnrichedAgentHookEventPayload): AgentSta receivedAt: entry.receivedAt, stateStartedAt: entry.stateStartedAt, ...(entry.providerSession ? { providerSession: entry.providerSession } : {}), + ...(entry.providerSessionOnly ? { providerSessionOnly: true } : {}), ...(entry.promptInteractionKey ? { promptInteractionKey: entry.promptInteractionKey } : {}), ...entry.payload } @@ -568,6 +588,9 @@ export class AgentHookServer { if (!existing) { return false } + if (existing.providerSessionOnly) { + return false + } const payload = existing.payload const agentType: AgentType | undefined = payload.agentType // Why: Droid's Ctrl+C does not interrupt the current turn; repeated Ctrl+C @@ -700,13 +723,17 @@ export class AgentHookServer { } getStatusChangeSnapshot(): AgentHookStatusChangeEntry[] { - return Array.from(this.state.lastStatusByPaneKey.entries(), ([paneKey, entry]) => { + return Array.from(this.state.lastStatusByPaneKey.entries()).flatMap(([paneKey, entry]) => { const enriched = entry as EnrichedAgentHookEventPayload - return { - state: enriched.payload.state, - receivedAt: enriched.receivedAt, - observedInCurrentRuntime: this.runtimeObservedStatusPaneKeys.has(paneKey) - } + return enriched.providerSessionOnly + ? [] + : [ + { + state: enriched.payload.state, + receivedAt: enriched.receivedAt, + observedInCurrentRuntime: this.runtimeObservedStatusPaneKeys.has(paneKey) + } + ] }) } @@ -870,6 +897,18 @@ export class AgentHookServer { | EnrichedAgentHookEventPayload | undefined const now = Date.now() + if (payload.providerSessionOnly) { + // Why: Pi session_start must replace stale turn state and survive snapshot + // replay, but it must not emit prompt telemetry or a fabricated status. + const enriched = this.attachStatusTiming(payload, now) + this.clearAssistantMessageRetry(enriched.paneKey) + this.runtimeObservedStatusPaneKeys.delete(enriched.paneKey) + this.state.lastStatusByPaneKey.set(enriched.paneKey, enriched) + this.scheduleStatusPersist() + this.notifyStatusChangeListeners() + this.onAgentStatus?.(enriched) + return enriched + } const identity = resolveAgentStatusIdentity({ existing: previous ? { @@ -1374,6 +1413,7 @@ export class AgentHookServer { toolAgentId?: string toolAgentType?: string providerSession?: unknown + providerSessionOnly?: unknown isReplay?: boolean payload: unknown }, @@ -1468,6 +1508,12 @@ export class AgentHookServer { if (!normalizedPayload) { return } + if ( + envelope.providerSessionOnly === true && + !isValidPiProviderSessionOnly(providerSession, normalizedPayload.agentType) + ) { + return + } // Why: run the same warn-once diagnostics the HTTP path runs (cross-build // version mismatch, dev-vs-prod env mismatch). Use `this.env` as the // expected env so the messages match what the local server produces. @@ -1489,6 +1535,7 @@ export class AgentHookServer { toolAgentId, toolAgentType, providerSession, + providerSessionOnly: envelope.providerSessionOnly === true ? true : undefined, isReplay: envelope.isReplay === true ? true : undefined, payload: normalizedPayload } diff --git a/src/main/index.ts b/src/main/index.ts index f872f034fd0..7c11a2e8e14 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1134,12 +1134,30 @@ function openMainWindow(): BrowserWindow { stateStartedAt, launchToken, providerSession, + providerSessionOnly, promptInteractionKey, isReplay }) => { if (mainWindow?.isDestroyed()) { return } + if (providerSessionOnly) { + // Why: session_start refreshes durable resume identity while Pi is + // idle; forward it without driving titles, telemetry, or status UI. + mainWindow?.webContents.send('agentStatus:set', { + ...payload, + paneKey, + ...(launchToken ? { launchToken } : {}), + tabId, + worktreeId, + connectionId, + receivedAt, + stateStartedAt, + ...(providerSession ? { providerSession } : {}), + providerSessionOnly: true + }) + return + } maybeAutoRenameBranchOnFirstWorkFromHook({ paneKey, tabId, worktreeId, payload, isReplay }) const orchestration = runtime?.getAgentStatusOrchestrationContextForPaneKey(paneKey) const terminalHandle = runtime?.getAgentStatusTerminalHandleForPaneKey(paneKey) @@ -1943,7 +1961,8 @@ app.whenReady().then(async () => { getDesktopWindowStatus: getDesktopWindowStatus, // Why: hook-reported agent status is the same source the desktop sidebar // reads. worktree.ps pulls it at query time so mobile shows the same agents. - getAgentStatusSnapshot: () => agentHookServer.getStatusSnapshot(), + getAgentStatusSnapshot: () => + agentHookServer.getStatusSnapshot().filter((entry) => entry.providerSessionOnly !== true), // Why: source codex-home here (runs in BOTH window and serve modes) so the // aiVault.listSessions RPC includes managed-Codex sessions on remote/SSH // hosts; the window-only registerCoreHandlers path never runs under serve. diff --git a/src/main/pi/agent-status-extension-source.test.ts b/src/main/pi/agent-status-extension-source.test.ts index c77b094b97a..fd65d81a6cb 100644 --- a/src/main/pi/agent-status-extension-source.test.ts +++ b/src/main/pi/agent-status-extension-source.test.ts @@ -6,7 +6,11 @@ import { describe, expect, it, vi } from 'vitest' import { getPiAgentStatusExtensionSource } from './agent-status-extension-source' type HookContext = { - isIdle: () => boolean + isIdle?: () => boolean + sessionManager?: { + getSessionId?: () => unknown + getSessionFile?: () => unknown + } } type HookHandler = (event?: unknown, context?: HookContext) => Promise | void @@ -177,6 +181,150 @@ function createHarness(args: { } describe('getPiAgentStatusExtensionSource', () => { + it('includes the session id and file path in Pi status posts after session_start', async () => { + const harness = createHarness({ + kind: 'pi', + existsSync: (path) => path === '/tmp/pi-session-1.jsonl' + }) + + await harness.callHook( + 'session_start', + {}, + { + sessionManager: { + getSessionId: () => 'pi-session-1', + getSessionFile: () => '/tmp/pi-session-1.jsonl' + } + } + ) + + expect(harness.fetchMock).toHaveBeenCalledTimes(1) + expect(JSON.parse(String(harness.fetchMock.mock.calls[0]?.[1]?.body)).payload).toEqual({ + hook_event_name: 'session_start', + session_id: 'pi-session-1', + session_file: '/tmp/pi-session-1.jsonl' + }) + + await harness.callHook('before_agent_start', { prompt: 'resume this task' }) + + await vi.waitFor(() => expect(harness.fetchMock).toHaveBeenCalledTimes(2)) + const body = JSON.parse(String(harness.fetchMock.mock.calls[1]?.[1]?.body)) + expect(body.payload).toEqual({ + hook_event_name: 'before_agent_start', + prompt: 'resume this task', + session_id: 'pi-session-1', + session_file: '/tmp/pi-session-1.jsonl' + }) + }) + + it('waits until Pi creates its planned session file before advertising resume identity', async () => { + let sessionFileExists = false + const harness = createHarness({ + kind: 'pi', + existsSync: (path) => path === '/tmp/pi-session-1.jsonl' && sessionFileExists + }) + + await harness.callHook( + 'session_start', + { reason: 'startup' }, + { + sessionManager: { + getSessionId: () => 'pi-session-1', + getSessionFile: () => '/tmp/pi-session-1.jsonl' + } + } + ) + + expect(JSON.parse(String(harness.fetchMock.mock.calls[0]?.[1]?.body)).payload).toEqual({ + hook_event_name: 'session_start' + }) + + sessionFileExists = true + await harness.callHook('agent_end') + + await vi.waitFor(() => expect(harness.fetchMock).toHaveBeenCalledTimes(2)) + expect(JSON.parse(String(harness.fetchMock.mock.calls[1]?.[1]?.body)).payload).toEqual({ + hook_event_name: 'agent_end', + session_id: 'pi-session-1', + session_file: '/tmp/pi-session-1.jsonl' + }) + }) + + it('refreshes Pi session metadata on reload without posting a replacement status', async () => { + const harness = createHarness({ + kind: 'pi', + existsSync: (path) => path === '/tmp/pi-reloaded.jsonl' + }) + + await harness.callHook( + 'session_start', + { reason: 'reload' }, + { + sessionManager: { + getSessionId: () => 'pi-reloaded', + getSessionFile: () => '/tmp/pi-reloaded.jsonl' + } + } + ) + expect(harness.fetchMock).not.toHaveBeenCalled() + + await harness.callHook('agent_start') + expect(JSON.parse(String(harness.fetchMock.mock.calls[0]?.[1]?.body)).payload).toEqual({ + hook_event_name: 'agent_start', + session_id: 'pi-reloaded', + session_file: '/tmp/pi-reloaded.jsonl' + }) + }) + + it('omits absent or empty Pi session metadata from status posts', async () => { + for (const sessionManager of [ + { getSessionId: () => '', getSessionFile: () => undefined }, + { getSessionFile: () => '/tmp/pi-session.jsonl' } + ]) { + const harness = createHarness({ kind: 'pi' }) + await harness.callHook('session_start', {}, { sessionManager }) + await harness.callHook('agent_start') + + await vi.waitFor(() => expect(harness.fetchMock).toHaveBeenCalledTimes(2)) + const payloads = harness.fetchMock.mock.calls.map( + ([_, init]) => JSON.parse(String(init?.body)).payload + ) + expect(payloads).toEqual([ + { hook_event_name: 'session_start' }, + { hook_event_name: 'agent_start' } + ]) + } + }) + + it('keeps OMP runtime status payloads unchanged by Pi session metadata', async () => { + const harness = createHarness({ kind: 'omp' }) + + await harness.callHook( + 'session_start', + {}, + { + sessionManager: { + getSessionId: () => 'omp-session-1', + getSessionFile: () => '/tmp/omp-session-1.jsonl' + } + } + ) + await harness.callHook('agent_start') + + expect(harness.fetchMock).toHaveBeenCalledTimes(1) + expect(harness.fetchMock.mock.calls[0]?.[1]?.body).toBe( + JSON.stringify({ + paneKey: 'pane-1', + launchToken: 'launch-1', + tabId: 'tab-1', + worktreeId: 'tree-1', + env: 'env-1', + version: '1.2.3', + payload: { hook_event_name: 'agent_start' } + }) + ) + }) + it('routes an OMP executable through /hook/omp', async () => { const harness = createHarness({ kind: 'pi', diff --git a/src/main/pi/agent-status-extension-source.ts b/src/main/pi/agent-status-extension-source.ts index a7abe4df9a5..881640183de 100644 --- a/src/main/pi/agent-status-extension-source.ts +++ b/src/main/pi/agent-status-extension-source.ts @@ -16,6 +16,41 @@ import { getPiAgentStatusHandlerSourceLines } from './agent-status-handler-sourc export const ORCA_PI_AGENT_STATUS_EXTENSION_FILE = 'orca-agent-status.ts' export function getPiAgentStatusExtensionSource(kind: PiAgentKind = 'pi'): string { + const sessionMetadataSourceLines = + kind === 'pi' + ? [ + 'let sessionMetadata: Record = {}', + '', + 'function updateSessionMetadata(ctx: unknown): void {', + ' const sessionManager = (ctx as { sessionManager?: { getSessionId?: () => unknown; getSessionFile?: () => unknown } } | null)?.sessionManager', + ' const sessionId = sessionManager?.getSessionId?.()', + ' const sessionFile = sessionManager?.getSessionFile?.()', + " sessionMetadata = typeof sessionId === 'string' && sessionId ? {", + ' session_id: sessionId,', + " ...(typeof sessionFile === 'string' && sessionFile ? { session_file: sessionFile } : {}),", + ' } : {}', + '}', + '', + 'function getPersistedSessionMetadata(): Record {', + ' const sessionFile = sessionMetadata.session_file', + " if (typeof sessionFile !== 'string' || !sessionFile) return {}", + ' try {', + " const fs = require('fs')", + ' // Why: Pi publishes its planned path before creating the transcript;', + ' // recheck on every post so the first completed turn becomes resumable.', + ' return fs.existsSync(sessionFile) ? sessionMetadata : {}', + ' } catch {', + ' return {}', + ' }', + '}', + '' + ] + : [] + const payloadLine = + kind === 'pi' + ? ' payload: { hook_event_name: hookEventName, ...getPersistedSessionMetadata(), ...extra },' + : ' payload: { hook_event_name: hookEventName, ...extra },' + // Why: keep this string self-contained — it runs inside the pi process, // so it cannot import from Orca's main bundle. fs/http coords come from // the same endpoint file the OpenCode plugin reads (process.env is frozen @@ -32,6 +67,7 @@ export function getPiAgentStatusExtensionSource(kind: PiAgentKind = 'pi'): strin 'const HOOK_POST_TIMEOUT_MS = 1000', 'let activePost = false', 'let pendingPost: { hookEventName: string; extra: Record } | null = null', + ...sessionMetadataSourceLines, '', '// Why: re-reading the endpoint file on every event is cheap (small file,', '// rare changes) but stat+mtime caching avoids re-parsing on every event', @@ -144,7 +180,7 @@ export function getPiAgentStatusExtensionSource(kind: PiAgentKind = 'pi'): strin " worktreeId: process.env.ORCA_WORKTREE_ID || '',", ' env: coords.env,', ' version: coords.version,', - ' payload: { hook_event_name: hookEventName, ...extra },', + payloadLine, ' })', " const controller = typeof AbortController !== 'undefined' ? new AbortController() : null", ' let timeout: ReturnType | undefined', @@ -258,6 +294,6 @@ export function getPiAgentStatusExtensionSource(kind: PiAgentKind = 'pi'): strin ' }', '}', '', - ...getPiAgentStatusHandlerSourceLines() + ...getPiAgentStatusHandlerSourceLines(kind) ].join('\n') } diff --git a/src/main/pi/agent-status-handler-source.ts b/src/main/pi/agent-status-handler-source.ts index 38c4354ee13..a1abdf09ef4 100644 --- a/src/main/pi/agent-status-handler-source.ts +++ b/src/main/pi/agent-status-handler-source.ts @@ -1,6 +1,22 @@ +import type { PiAgentKind } from '../../shared/pi-agent-kind' + // Why: keep the generated handler registrations separate from hook transport; // both are independently sizeable and the installed extension concatenates them. -export function getPiAgentStatusHandlerSourceLines(): string[] { +export function getPiAgentStatusHandlerSourceLines(kind: PiAgentKind): string[] { + const sessionStartHandler = + kind === 'pi' + ? [ + " pi.on('session_start', (event, ctx) => {", + ' updateSessionMetadata(ctx)', + ' // Why: /reload re-registers the active session, but it is not a', + ' // turn boundary and must not clear the visible status or unread state.', + " if (event.reason === 'reload') return", + " post('session_start')", + ' })', + '' + ] + : [] + return [ '// Why: pi assistant messages carry content as an array of parts', "// ({ type: 'text', text } / tool_use / tool_result / reasoning). We only", @@ -36,6 +52,7 @@ export function getPiAgentStatusHandlerSourceLines(): string[] { ' const selfPid = String(process.pid)', ' if (ownerPid && ownerPid !== selfPid) return', ' process.env.ORCA_PI_STATUS_OWNED = selfPid', + ...sessionStartHandler, " pi.on('before_agent_start', (event) => {", " post('before_agent_start', { prompt: event.prompt ?? '' })", ' })', diff --git a/src/main/ssh/ssh-relay-session-agent-hooks.integration.test.ts b/src/main/ssh/ssh-relay-session-agent-hooks.integration.test.ts index 3de35a65fb2..91ee554dff4 100644 --- a/src/main/ssh/ssh-relay-session-agent-hooks.integration.test.ts +++ b/src/main/ssh/ssh-relay-session-agent-hooks.integration.test.ts @@ -414,19 +414,22 @@ describe('SshRelaySession agent hooks over a fake relay transport', () => { relay.notifyAgentHook( makeEnvelope({ - source: 'claude', - hookEventName: 'PreToolUse', + source: 'pi', + hookEventName: 'session_start', promptInteractionKey: 'command-code-transcript-user-3', toolUseId: 'toolu-1', toolAgentId: 'agent-subagent-a', toolAgentType: 'Review', - providerSession: { key: 'session_id', id: 'ssh-relay-session-1' }, + providerSessionOnly: true, + providerSession: { + key: 'session_id', + id: 'ssh-relay-session-1', + transcriptPath: '/tmp/ssh-relay-session-1.jsonl' + }, payload: { - state: 'working', - prompt: 'remote prompt', - agentType: 'claude', - toolName: 'Bash', - toolInput: 'pnpm test' + state: 'done', + prompt: '', + agentType: 'pi' } }) ) @@ -434,12 +437,17 @@ describe('SshRelaySession agent hooks over a fake relay transport', () => { await vi.waitFor(() => expect(ingestSpy).toHaveBeenCalledWith( expect.objectContaining({ - hookEventName: 'PreToolUse', + hookEventName: 'session_start', promptInteractionKey: 'command-code-transcript-user-3', toolUseId: 'toolu-1', toolAgentId: 'agent-subagent-a', toolAgentType: 'Review', - providerSession: { key: 'session_id', id: 'ssh-relay-session-1' } + providerSessionOnly: true, + providerSession: { + key: 'session_id', + id: 'ssh-relay-session-1', + transcriptPath: '/tmp/ssh-relay-session-1.jsonl' + } }), 'conn-hook-metadata' ) diff --git a/src/main/ssh/ssh-relay-session.ts b/src/main/ssh/ssh-relay-session.ts index b449daabb70..df274457f44 100644 --- a/src/main/ssh/ssh-relay-session.ts +++ b/src/main/ssh/ssh-relay-session.ts @@ -932,6 +932,7 @@ export class SshRelaySession { toolAgentType?: unknown isReplay?: unknown providerSession?: unknown + providerSessionOnly?: unknown payload?: unknown } if (typeof envelope.paneKey !== 'string') { @@ -963,6 +964,7 @@ export class SshRelaySession { typeof envelope.toolAgentType === 'string' ? envelope.toolAgentType : undefined, isReplay: envelope.isReplay === true ? true : undefined, providerSession: envelope.providerSession, + providerSessionOnly: envelope.providerSessionOnly === true ? true : undefined, payload: envelope.payload }, this.targetId diff --git a/src/relay/agent-hook-server.test.ts b/src/relay/agent-hook-server.test.ts index 4edb3dde409..3d73506c7bf 100644 --- a/src/relay/agent-hook-server.test.ts +++ b/src/relay/agent-hook-server.test.ts @@ -131,6 +131,62 @@ describe('RelayAgentHookServer', () => { } }) + it('forwards and replays Pi session identity as metadata-only', async () => { + const forward = vi.fn<(envelope: AgentHookRelayEnvelope) => void>() + const server = new RelayAgentHookServer({ endpointDir: dir, forward }) + await server.start() + try { + const { port, token } = server.getCoordinates() + const res = await fetch(`http://127.0.0.1:${port}/hook/pi`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Orca-Agent-Hook-Token': token + }, + body: JSON.stringify({ + paneKey: PANE_KEY, + tabId: 'tab-1', + worktreeId: 'wt-1', + env: 'remote', + version: '1', + payload: { + hook_event_name: 'session_start', + session_id: 'pi-session-1', + session_file: '/tmp/pi-session-1.jsonl' + } + }) + }) + + expect(res.status).toBe(204) + expect(forward).toHaveBeenCalledTimes(1) + expect(forward.mock.calls[0][0]).toMatchObject({ + source: 'pi', + paneKey: PANE_KEY, + providerSessionOnly: true, + providerSession: { + key: 'session_id', + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-1.jsonl' + } + }) + + forward.mockClear() + expect(server.replayCachedPayloadsForPanes()).toBe(1) + expect(forward).toHaveBeenCalledWith( + expect.objectContaining({ + source: 'pi', + providerSessionOnly: true, + providerSession: expect.objectContaining({ + transcriptPath: '/tmp/pi-session-1.jsonl' + }), + isReplay: true + }) + ) + } finally { + server.stop() + } + }) + it('does not replay paneKeys after clearPaneState', async () => { const forward = vi.fn<(envelope: AgentHookRelayEnvelope) => void>() const server = new RelayAgentHookServer({ endpointDir: dir, forward }) diff --git a/src/relay/agent-hook-server.ts b/src/relay/agent-hook-server.ts index f5200a2b178..8768b57dc1e 100644 --- a/src/relay/agent-hook-server.ts +++ b/src/relay/agent-hook-server.ts @@ -359,6 +359,7 @@ export class RelayAgentHookServer { toolAgentId: event.toolAgentId, toolAgentType: event.toolAgentType, ...(event.providerSession ? { providerSession: event.providerSession } : {}), + ...(event.providerSessionOnly ? { providerSessionOnly: true } : {}), isReplay: options.isReplay === true ? true : undefined, env, version, diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index ab19d2e244a..6a150add541 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1371,7 +1371,7 @@ function App(): React.JSX.Element { // which is in-memory. Capture them into the persisted sleeping-session // map so a daemon/session death while the app is closed can still // cold-restore via the agent's resume command (#5232). - useAppStore.getState().captureAllSleepingAgentSessions() + useAppStore.getState().captureAllSleepingAgentSessions('quit') // Why: re-read state after capture() calls populated scrollback buffers // into the store via Zustand setters. The earlier read is only for the // gating flags and would miss those updates. @@ -1398,7 +1398,7 @@ function App(): React.JSX.Element { if (!shouldPersistWorkspaceSession(useAppStore.getState())) { return } - useAppStore.getState().captureAllSleepingAgentSessions() + useAppStore.getState().captureAllSleepingAgentSessions('periodic') }, SLEEPING_AGENT_RESUME_CAPTURE_INTERVAL_MS) return () => window.clearInterval(timer) }, []) diff --git a/src/renderer/src/components/terminal-pane/pty-connection.ts b/src/renderer/src/components/terminal-pane/pty-connection.ts index 5716e535dfa..e9c0b60aacc 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.ts @@ -230,6 +230,7 @@ import { resolveTuiAgentLaunchEnv } from '../../../../shared/tui-agent-launch-defaults' import { + agentProviderSessionsEqual, isResumableTuiAgent, normalizeAgentProviderSession, type ResumableTuiAgent, @@ -1099,14 +1100,7 @@ export function connectPanePty( return legacy?.numericPaneId === String(pane.id) }) const providerSessionKeys = new Set( - legacyMatches.map(([, record]) => - [ - record.worktreeId, - record.agent, - record.providerSession.key, - record.providerSession.id - ].join('\0') - ) + legacyMatches.map(([, record]) => getProviderSessionClaimKey(record)) ) const oldestLegacyMatch = legacyMatches .slice() @@ -1136,8 +1130,11 @@ export function connectPanePty( paneKey !== consumed.paneKey && record.worktreeId === consumed.record.worktreeId && record.agent === consumed.record.agent && - record.providerSession.key === consumed.record.providerSession.key && - record.providerSession.id === consumed.record.providerSession.id + agentProviderSessionsEqual( + record.agent, + record.providerSession, + consumed.record.providerSession + ) ) { // Why: legacy pane aliases can leave multiple sleeping rows for one // provider session; once this pane resumes it, every alias is stale. @@ -4391,8 +4388,7 @@ export function connectPanePty( sleepingRecord?.launchConfig && (!useLiveEntry || (sleepingRecord.agent === agent && - sleepingRecord.providerSession.key === providerSession.key && - sleepingRecord.providerSession.id === providerSession.id)) + agentProviderSessionsEqual(agent, sleepingRecord.providerSession, providerSession))) ? sleepingRecord.launchConfig : undefined const launchConfig = diff --git a/src/renderer/src/hooks/useIpcEvents.ts b/src/renderer/src/hooks/useIpcEvents.ts index 2a0fe660165..cadc9d48a77 100644 --- a/src/renderer/src/hooks/useIpcEvents.ts +++ b/src/renderer/src/hooks/useIpcEvents.ts @@ -3160,6 +3160,32 @@ export function useIpcEvents(): void { ) { return 'dropped' } + const existingStatus = store.agentStatusByPaneKey[paneKey] + if (existingStatus && data.receivedAt < existingStatus.updatedAt) { + // Why: the store rejects out-of-order status rows; keep metadata-only + // session identity on the same accepted event boundary. + return 'dropped' + } + if (data.providerSessionOnly) { + if (!data.providerSession || data.agentType !== 'pi') { + return 'dropped' + } + store.recordAgentProviderSession( + paneKey, + 'pi', + data.providerSession, + { updatedAt: data.receivedAt }, + { + tabId: ownerTabId, + worktreeId: data.worktreeId ?? owningWorktreeId, + // Why: persist the WSL-normalized ownership id, not the raw relay + // provenance; a `wsl:*` connectionId would misroute later resumes. + ...(ownershipConnectionId !== undefined ? { connectionId: ownershipConnectionId } : {}) + }, + data.launchToken ? { launchToken: data.launchToken } : undefined + ) + return 'applied' + } const resolvedPayload = resolveHookPayloadAgentType(payload, identityTitle ?? title) const statusPayload = data.orchestration ? { ...resolvedPayload, orchestration: data.orchestration } @@ -3167,12 +3193,6 @@ export function useIpcEvents(): void { const statusPayloadWithTurnBoundary = data.promptInteractionKey ? { ...statusPayload, promptInteractionKey: data.promptInteractionKey } : statusPayload - const existingStatus = store.agentStatusByPaneKey[paneKey] - if (existingStatus && data.receivedAt < existingStatus.updatedAt) { - // Why: the store rejects out-of-order status rows; keep notification and - // terminal lifecycle effects on the same accepted event boundary. - return 'dropped' - } const identity = resolveAgentStatusIdentity({ existing: existingStatus ? { diff --git a/src/renderer/src/lib/agent-hibernation-coordinator.test.ts b/src/renderer/src/lib/agent-hibernation-coordinator.test.ts index 2a3bc5d7dc6..73df9ed51fa 100644 --- a/src/renderer/src/lib/agent-hibernation-coordinator.test.ts +++ b/src/renderer/src/lib/agent-hibernation-coordinator.test.ts @@ -1,3 +1,5 @@ +import { tmpdir } from 'node:os' +import { join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' import type { AgentStatusEntry } from '../../../shared/agent-status-types' import type { TerminalLayoutSnapshot, TerminalTab } from '../../../shared/types' @@ -24,6 +26,7 @@ import type { AppState } from '@/store/types' const NOW = 10_000_000 const LEAF = '11111111-1111-4111-8111-111111111111' +const PI_TRANSCRIPT_PATH = join(tmpdir(), 'pi-session-1.jsonl') const mockRuntimeEnvironmentCall = vi.fn() @@ -190,6 +193,52 @@ describe('agent sleep coordinator', () => { expect(useAppStore.getState().shutdownWorktreeTerminals).not.toHaveBeenCalled() }) + it('hibernates completed Pi after the periodic recovery capture', async () => { + vi.useFakeTimers() + const piEntry = { + ...entry(), + agentType: 'pi' as const, + providerSession: { + key: 'session_id' as const, + id: 'pi-session-1', + transcriptPath: PI_TRANSCRIPT_PATH + } + } + const shutdown = installEligibleState(vi.fn().mockResolvedValue(undefined), { + agentStatusByPaneKey: { [piEntry.paneKey]: piEntry }, + sleepingAgentSessionsByPaneKey: { + [piEntry.paneKey]: { + paneKey: piEntry.paneKey, + tabId: piEntry.tabId, + worktreeId: piEntry.worktreeId!, + agent: 'pi', + providerSession: piEntry.providerSession, + prompt: '', + state: 'working', + capturedAt: piEntry.updatedAt, + updatedAt: piEntry.updatedAt, + origin: 'live' + } + } + }) + + const liveRecord = useAppStore.getState().sleepingAgentSessionsByPaneKey[piEntry.paneKey] + useAppStore.getState().captureAllSleepingAgentSessions('periodic') + expect(useAppStore.getState().sleepingAgentSessionsByPaneKey[piEntry.paneKey]).toBe(liveRecord) + + startAgentHibernationCoordinator({ intervalMs: 1000, now: () => NOW }) + await vi.advanceTimersByTimeAsync(1000) + expect(shutdown).not.toHaveBeenCalled() + await vi.advanceTimersByTimeAsync(1000) + + expect(shutdown).toHaveBeenCalledWith('wt-bg', { + paneKey: piEntry.paneKey, + tabId: 'tab-1', + leafId: LEAF, + ptyId: 'pty-1' + }) + }) + it('hibernates an eligible pane when a sibling shell PTY is live', async () => { vi.useFakeTimers() const shutdown = installEligibleState(vi.fn().mockResolvedValue(undefined), { diff --git a/src/renderer/src/lib/agent-hibernation-planner.test.ts b/src/renderer/src/lib/agent-hibernation-planner.test.ts index 69f9bfbc42c..30ceab0f586 100644 --- a/src/renderer/src/lib/agent-hibernation-planner.test.ts +++ b/src/renderer/src/lib/agent-hibernation-planner.test.ts @@ -115,6 +115,19 @@ describe('agent sleep planner', () => { expect( plannedWorktrees(snapshot({ agentStatusByPaneKey: { [noSession.paneKey]: noSession } })) ).toEqual([]) + const ephemeralPi = entry({ agentType: 'pi', providerSession: undefined }) + expect( + plannedWorktrees(snapshot({ agentStatusByPaneKey: { [ephemeralPi.paneKey]: ephemeralPi } })) + ).toEqual([]) + const piWithoutTranscript = entry({ + agentType: 'pi', + providerSession: { key: 'session_id', id: 'pi-session-1' } + }) + expect( + plannedWorktrees( + snapshot({ agentStatusByPaneKey: { [piWithoutTranscript.paneKey]: piWithoutTranscript } }) + ) + ).toEqual([]) const unsupported = entry({ agentType: 'amp' }) expect( plannedWorktrees(snapshot({ agentStatusByPaneKey: { [unsupported.paneKey]: unsupported } })) @@ -355,6 +368,38 @@ describe('agent sleep planner', () => { ).toEqual([]) }) + it('still hibernates completed Pi panes that only retain live resume identity', () => { + const piEntry = entry({ + agentType: 'pi', + providerSession: { + key: 'session_id', + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-1.jsonl' + } + }) + expect( + plannedPaneKeys( + snapshot({ + agentStatusByPaneKey: { [piEntry.paneKey]: piEntry }, + sleepingAgentSessionsByPaneKey: { + [piEntry.paneKey]: { + paneKey: piEntry.paneKey, + tabId: 'tab-1', + worktreeId: 'wt-bg', + agent: 'pi', + providerSession: piEntry.providerSession!, + prompt: '', + state: 'working', + capturedAt: OLD, + updatedAt: OLD, + origin: 'live' + } + } + }) + ) + ).toEqual([piEntry.paneKey]) + }) + it('rejects mobile-driven panes because paired clients can send input outside desktop xterm', () => { expect(plannedWorktrees(snapshot({ mobileLockedPtyIds: ['pty-1'] }))).toEqual([]) }) diff --git a/src/renderer/src/lib/agent-hibernation-planner.ts b/src/renderer/src/lib/agent-hibernation-planner.ts index 6f3a70d446c..9298f1020c1 100644 --- a/src/renderer/src/lib/agent-hibernation-planner.ts +++ b/src/renderer/src/lib/agent-hibernation-planner.ts @@ -1,5 +1,6 @@ import type { AgentStatusEntry } from '../../../shared/agent-status-types' import { + agentProviderSessionsEqual, getAgentResumeArgv, isResumableTuiAgent, type SleepingAgentSessionRecord @@ -131,10 +132,21 @@ function getEligiblePane(args: { foregroundTerminalLastSeenAtByTabId, mobileLockedPtyIds } = args + const sleepingRecord = sleepingAgentSessionsByPaneKey[entry.paneKey] + // Why: Pi's done hook ends a turn, not its TUI. Its live recovery checkpoint + // must not make the still-running pane look already hibernated. + const hasOnlyLivePiRecoveryIdentity = Boolean( + entry.agentType === 'pi' && + entry.providerSession && + sleepingRecord?.agent === 'pi' && + sleepingRecord.origin === 'live' && + sleepingRecord.worktreeId === tab.worktreeId && + agentProviderSessionsEqual('pi', entry.providerSession, sleepingRecord.providerSession) + ) if ( entry.state !== 'done' || entry.interrupted === true || - sleepingAgentSessionsByPaneKey[entry.paneKey] + (sleepingRecord && !hasOnlyLivePiRecoveryIdentity) ) { return null } diff --git a/src/renderer/src/lib/pi-session-resume-wake.test.ts b/src/renderer/src/lib/pi-session-resume-wake.test.ts new file mode 100644 index 00000000000..a0f69f68c2a --- /dev/null +++ b/src/renderer/src/lib/pi-session-resume-wake.test.ts @@ -0,0 +1,61 @@ +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { afterEach, describe, expect, it } from 'vitest' +import type { SleepingAgentSessionRecord } from '../../../shared/agent-session-resume' +import type { TerminalTab } from '../../../shared/types' +import { useAppStore } from '@/store' +import { resumeSleepingAgentSessionsForWorktree } from './resume-sleeping-agent-session' + +const initialAppStoreState = useAppStore.getState() +const PI_TRANSCRIPT_PATH = join(tmpdir(), 'pi-session-1.jsonl') + +afterEach(() => { + useAppStore.setState(initialAppStoreState, true) +}) + +describe('Pi session wake', () => { + it('wakes a manually slept Pi session with its transcript identity', () => { + const providerSession = { + key: 'session_id' as const, + id: 'pi-session-1', + transcriptPath: PI_TRANSCRIPT_PATH + } + const record: SleepingAgentSessionRecord = { + paneKey: 'tab-1:leaf-1', + tabId: 'tab-1', + worktreeId: 'wt-1', + agent: 'pi', + providerSession, + prompt: '', + state: 'working', + capturedAt: 1, + updatedAt: 1, + origin: 'worktree-sleep' + } + const tab: TerminalTab = { + id: 'tab-1', + ptyId: null, + worktreeId: 'wt-1', + title: 'shell', + customTitle: null, + color: null, + sortOrder: 0, + createdAt: 1 + } + useAppStore.setState({ + tabsByWorktree: { 'wt-1': [tab] }, + sleepingAgentSessionsByPaneKey: { [record.paneKey]: record } + }) + + const launched = resumeSleepingAgentSessionsForWorktree('wt-1') + + expect(launched).toBe(1) + const state = useAppStore.getState() + const resumedTab = state.tabsByWorktree['wt-1']?.find((candidate) => candidate.id !== tab.id) + expect(resumedTab?.launchAgent).toBe('pi') + expect(state.pendingStartupByTabId[resumedTab!.id]?.resumeProviderSession).toEqual( + providerSession + ) + expect(state.sleepingAgentSessionsByPaneKey[record.paneKey]).toBeUndefined() + }) +}) diff --git a/src/renderer/src/lib/resume-sleeping-agent-session.ts b/src/renderer/src/lib/resume-sleeping-agent-session.ts index b73d4b02fe6..8716e156b79 100644 --- a/src/renderer/src/lib/resume-sleeping-agent-session.ts +++ b/src/renderer/src/lib/resume-sleeping-agent-session.ts @@ -1,7 +1,7 @@ import { useAppStore } from '@/store' -import type { - AgentProviderSessionMetadata, - SleepingAgentSessionRecord +import { + agentProviderSessionsEqual, + type SleepingAgentSessionRecord } from '../../../shared/agent-session-resume' import { AGENT_STATUS_STALE_AFTER_MS } from '../../../shared/agent-status-types' import { @@ -68,13 +68,6 @@ function getNewestActiveRecordsByClaimKey( return newestRecords } -function providerSessionsMatch( - left: AgentProviderSessionMetadata | undefined, - right: AgentProviderSessionMetadata -): boolean { - return Boolean(left && left.key === right.key && left.id === right.id) -} - function getAgentStatusTabId(entry: { paneKey: string tabId?: string | undefined @@ -99,7 +92,7 @@ function activeOrQueuedResumeClaimsProviderSession( entry.worktreeId === record.worktreeId && entry.agentType === record.agent && entry.state !== 'done' && - providerSessionsMatch(entry.providerSession, record.providerSession) + agentProviderSessionsEqual(record.agent, entry.providerSession, record.providerSession) ) { return true } @@ -109,7 +102,11 @@ function activeOrQueuedResumeClaimsProviderSession( if ( worktreeTabIds.has(tabId) && startup.launchAgent === record.agent && - providerSessionsMatch(startup.resumeProviderSession, record.providerSession) + agentProviderSessionsEqual( + record.agent, + startup.resumeProviderSession, + record.providerSession + ) ) { return true } @@ -120,7 +117,7 @@ function activeOrQueuedResumeClaimsProviderSession( worktreeTabIds.has(tabId) && claim.worktreeId === record.worktreeId && claim.launchAgent === record.agent && - providerSessionsMatch(claim.providerSession, record.providerSession) + agentProviderSessionsEqual(record.agent, claim.providerSession, record.providerSession) ) { return true } diff --git a/src/renderer/src/lib/sleeping-agent-pane-ownership.ts b/src/renderer/src/lib/sleeping-agent-pane-ownership.ts index c1f30108c2d..90b154cb7f7 100644 --- a/src/renderer/src/lib/sleeping-agent-pane-ownership.ts +++ b/src/renderer/src/lib/sleeping-agent-pane-ownership.ts @@ -10,7 +10,8 @@ import { parseLegacyNumericPaneKey, parsePaneKey } from '../../../shared/stable- type AppStoreState = ReturnType export function getProviderSessionClaimKey(record: SleepingAgentSessionRecord): string { - return `${record.worktreeId}\0${record.agent}\0${record.providerSession.key}\0${record.providerSession.id}` + const base = `${record.worktreeId}\0${record.agent}\0${record.providerSession.key}\0${record.providerSession.id}` + return record.agent === 'pi' ? `${base}\0${record.providerSession.transcriptPath ?? ''}` : base } export function isPassiveCompletedHibernationEvidence(record: SleepingAgentSessionRecord): boolean { diff --git a/src/renderer/src/store/slices/agent-status-manual-sleep-capture.test.ts b/src/renderer/src/store/slices/agent-status-manual-sleep-capture.test.ts index 36f9cc56a3a..75b37f039fc 100644 --- a/src/renderer/src/store/slices/agent-status-manual-sleep-capture.test.ts +++ b/src/renderer/src/store/slices/agent-status-manual-sleep-capture.test.ts @@ -145,6 +145,25 @@ describe('manual sleep agent session capture', () => { expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:stale']).toBeUndefined() }) + it('does not promote Pi identity without an authoritative transcript', () => { + vi.useFakeTimers() + vi.setSystemTime(NOW) + const store = createTestStore() + seedTabs(store) + store.setState({ + sleepingAgentSessionsByPaneKey: { + 'tab-1:leaf-1': makeSleepingRecord({ + agent: 'pi', + providerSession: { key: 'session_id', id: 'pi-session-1' } + }) + } + } as Partial) + + store.getState().captureSleepingAgentSessionsByWorktree('wt-1') + + expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toBeUndefined() + }) + it('uses manual sleep filtering when terminal shutdown captures sleeping records', async () => { vi.useFakeTimers() vi.setSystemTime(NOW) diff --git a/src/renderer/src/store/slices/agent-status-provider-session.test.ts b/src/renderer/src/store/slices/agent-status-provider-session.test.ts new file mode 100644 index 00000000000..41f42e60bd6 --- /dev/null +++ b/src/renderer/src/store/slices/agent-status-provider-session.test.ts @@ -0,0 +1,285 @@ +import { describe, expect, it } from 'vitest' +import type { SleepingAgentSessionRecord } from '../../../../shared/agent-session-resume' +import type { AppState } from '../types' +import { getProviderSessionClaimKey } from '../../lib/sleeping-agent-pane-ownership' +import { createTestStore, makeTab } from './store-test-helpers' + +describe('recordAgentProviderSession', () => { + it('uses the session file as part of Pi resume ownership only', () => { + const base = { + paneKey: 'tab-1:leaf-1', + tabId: 'tab-1', + worktreeId: 'wt-1', + prompt: '', + state: 'working' as const, + capturedAt: 10, + updatedAt: 10, + origin: 'live' as const + } + const makeRecord = ( + agent: 'pi' | 'claude', + transcriptPath: string + ): SleepingAgentSessionRecord => ({ + ...base, + agent, + providerSession: { key: 'session_id', id: 'session-1', transcriptPath } + }) + + expect(getProviderSessionClaimKey(makeRecord('pi', '/tmp/first.jsonl'))).not.toBe( + getProviderSessionClaimKey(makeRecord('pi', '/tmp/second.jsonl')) + ) + expect(getProviderSessionClaimKey(makeRecord('claude', '/tmp/first.jsonl'))).toBe( + getProviderSessionClaimKey(makeRecord('claude', '/tmp/second.jsonl')) + ) + }) + + it('keeps Pi session identity durable without fabricating a visible turn', () => { + const store = createTestStore() + store.setState({ + tabsByWorktree: { + 'wt-1': [makeTab({ id: 'tab-1', worktreeId: 'wt-1' })] + } + } as Partial) + const launchConfig = { + agentCommand: "pi '--model' 'anthropic/claude-sonnet-4'", + agentArgs: '--model anthropic/claude-sonnet-4', + agentEnv: { PI_CODING_AGENT_DIR: '/tmp/pi-agent' } + } + const providerSession = { + key: 'session_id' as const, + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-1.jsonl' + } + + store.getState().registerAgentLaunchConfig('tab-1:leaf-1', launchConfig, { + agentType: 'pi', + launchToken: 'pi-launch-1', + tabId: 'tab-1', + leafId: 'leaf-1' + }) + store + .getState() + .setAgentStatus( + 'tab-1:leaf-1', + { state: 'working', prompt: 'stale turn', agentType: 'pi' }, + 'Pi', + { updatedAt: 10, stateStartedAt: 10 }, + { tabId: 'tab-1', worktreeId: 'wt-1' } + ) + + store + .getState() + .recordAgentProviderSession( + 'tab-1:leaf-1', + 'pi', + providerSession, + { updatedAt: 20 }, + { tabId: 'tab-1', worktreeId: 'wt-1', connectionId: null }, + { launchToken: 'pi-launch-1' } + ) + + expect(store.getState().agentStatusByPaneKey['tab-1:leaf-1']).toBeUndefined() + expect(store.getState().retainedAgentsByPaneKey['tab-1:leaf-1']).toBeUndefined() + expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toMatchObject({ + paneKey: 'tab-1:leaf-1', + tabId: 'tab-1', + worktreeId: 'wt-1', + agent: 'pi', + providerSession, + launchConfig, + origin: 'live' + }) + + const liveRecord = store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1'] + store.getState().captureAllSleepingAgentSessions('periodic') + expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toBe(liveRecord) + + store.getState().captureSleepingAgentSessionsByWorktree('wt-1', ['tab-1:leaf-1']) + expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toMatchObject({ + providerSession, + launchConfig, + origin: 'worktree-sleep' + }) + }) + + it('does not reuse Pi launch config when the session file identity changes', () => { + const store = createTestStore() + store.setState({ + tabsByWorktree: { + 'wt-1': [makeTab({ id: 'tab-1', worktreeId: 'wt-1' })] + }, + sleepingAgentSessionsByPaneKey: { + 'tab-1:leaf-1': { + paneKey: 'tab-1:leaf-1', + tabId: 'tab-1', + worktreeId: 'wt-1', + agent: 'pi', + providerSession: { + key: 'session_id', + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-old.jsonl' + }, + prompt: '', + state: 'working', + capturedAt: 10, + updatedAt: 10, + launchConfig: { agentArgs: '--model old', agentEnv: { PI_PROFILE: 'old' } }, + origin: 'live' + } + } + } as Partial) + + store.getState().recordAgentProviderSession( + 'tab-1:leaf-1', + 'pi', + { + key: 'session_id', + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-new.jsonl' + }, + { updatedAt: 20 }, + { tabId: 'tab-1', worktreeId: 'wt-1' } + ) + + expect( + store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']?.launchConfig + ).toBeUndefined() + }) + + it('keeps a completed Pi session resumable through manual worktree sleep', async () => { + const store = createTestStore() + store.setState({ + tabsByWorktree: { + 'wt-1': [makeTab({ id: 'tab-1', worktreeId: 'wt-1' })] + } + } as Partial) + const providerSession = { + key: 'session_id' as const, + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-1.jsonl' + } + + store + .getState() + .recordAgentProviderSession( + 'tab-1:leaf-1', + 'pi', + providerSession, + { updatedAt: 10 }, + { tabId: 'tab-1', worktreeId: 'wt-1', connectionId: 'ssh-connection-1' } + ) + store + .getState() + .setAgentStatus( + 'tab-1:leaf-1', + { state: 'working', prompt: 'finish the task', agentType: 'pi' }, + 'Pi', + { updatedAt: 20, stateStartedAt: 20 }, + { tabId: 'tab-1', worktreeId: 'wt-1' }, + { providerSession } + ) + store + .getState() + .setAgentStatus( + 'tab-1:leaf-1', + { state: 'done', prompt: 'finish the task', agentType: 'pi' }, + 'Pi', + { updatedAt: 30, stateStartedAt: 30 }, + { tabId: 'tab-1', worktreeId: 'wt-1' }, + { providerSession } + ) + + expect(store.getState().agentStatusByPaneKey['tab-1:leaf-1']?.state).toBe('done') + const liveRecord = store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1'] + expect(liveRecord).toMatchObject({ + agent: 'pi', + providerSession, + connectionId: 'ssh-connection-1', + state: 'working', + origin: 'live' + }) + + store.getState().captureAllSleepingAgentSessions('periodic') + expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toBe(liveRecord) + + await store.getState().shutdownWorktreeTerminals('wt-1', { + keepIdentifiers: true, + shutdownReason: 'manual-sleep', + sleepingPaneKeys: ['tab-1:leaf-1'] + }) + + expect(store.getState().agentStatusByPaneKey['tab-1:leaf-1']).toBeUndefined() + expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toMatchObject({ + agent: 'pi', + providerSession, + connectionId: 'ssh-connection-1', + state: 'working', + origin: 'worktree-sleep' + }) + }) + + it('keeps a completed Pi session resumable through quit capture', () => { + const store = createTestStore() + store.setState({ + tabsByWorktree: { + 'wt-1': [makeTab({ id: 'tab-1', worktreeId: 'wt-1' })] + } + } as Partial) + const providerSession = { + key: 'session_id' as const, + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-1.jsonl' + } + + store + .getState() + .recordAgentProviderSession( + 'tab-1:leaf-1', + 'pi', + providerSession, + { updatedAt: 10 }, + { tabId: 'tab-1', worktreeId: 'wt-1', connectionId: 'ssh-connection-1' } + ) + store + .getState() + .setAgentStatus( + 'tab-1:leaf-1', + { state: 'working', prompt: 'finish the task', agentType: 'pi' }, + 'Pi', + { updatedAt: 20, stateStartedAt: 20 }, + { tabId: 'tab-1', worktreeId: 'wt-1' }, + { providerSession } + ) + store + .getState() + .setAgentStatus( + 'tab-1:leaf-1', + { state: 'done', prompt: 'finish the task', agentType: 'pi' }, + 'Pi', + { updatedAt: 30, stateStartedAt: 30 }, + { tabId: 'tab-1', worktreeId: 'wt-1' }, + { providerSession } + ) + + store.getState().captureAllSleepingAgentSessions('periodic') + expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toMatchObject({ + providerSession, + connectionId: 'ssh-connection-1', + origin: 'live' + }) + + store.getState().captureAllSleepingAgentSessions('quit') + + const quitRecord = store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1'] + expect(quitRecord).toMatchObject({ + agent: 'pi', + providerSession, + connectionId: 'ssh-connection-1', + state: 'working', + origin: 'quit' + }) + + store.getState().captureAllSleepingAgentSessions('periodic') + expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toBe(quitRecord) + }) +}) diff --git a/src/renderer/src/store/slices/agent-status-quit-capture.test.ts b/src/renderer/src/store/slices/agent-status-quit-capture.test.ts index bf005d398e7..c125bb369e7 100644 --- a/src/renderer/src/store/slices/agent-status-quit-capture.test.ts +++ b/src/renderer/src/store/slices/agent-status-quit-capture.test.ts @@ -207,7 +207,7 @@ describe('captureAllSleepingAgentSessions', () => { const records = collectSleepingAgentSessionRecordsForWorktree(store.getState(), 'wt-1') expect(records['tab-1:leaf-1']?.launchConfig).toEqual(launchConfig) - store.getState().captureAllSleepingAgentSessions() + store.getState().captureAllSleepingAgentSessions('quit') expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toMatchObject({ origin: 'quit', launchConfig @@ -233,14 +233,13 @@ describe('captureAllSleepingAgentSessions', () => { { providerSession } ) - store.getState().captureAllSleepingAgentSessions() + store.getState().captureAllSleepingAgentSessions('quit') const first = store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1'] expect(first).toMatchObject({ origin: 'quit' }) - // Why: the periodic resume-record capture re-runs this action every - // minute; an unchanged agent must not dirty the store (and the debounced - // session write) with a record differing only by capturedAt. - store.getState().captureAllSleepingAgentSessions() + // Why: beforeunload can fire twice during a confirmed close; the second + // capture must not dirty the store with a capturedAt-only rewrite. + store.getState().captureAllSleepingAgentSessions('quit') expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toBe(first) // A real status change must still refresh the record. @@ -254,12 +253,55 @@ describe('captureAllSleepingAgentSessions', () => { { tabId: 'tab-1', worktreeId: 'wt-1' }, { providerSession } ) - store.getState().captureAllSleepingAgentSessions() + store.getState().captureAllSleepingAgentSessions('quit') const refreshed = store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1'] expect(refreshed).not.toBe(first) expect(refreshed).toMatchObject({ state: 'waiting', origin: 'quit' }) }) + it('does not let a periodic checkpoint supersede a confirmed quit record', () => { + const store = createTestStore() + store.setState({ + tabsByWorktree: { + 'wt-1': [makeTab({ id: 'tab-1', worktreeId: 'wt-1' })] + } + } as Partial) + const providerSession = { key: 'session_id' as const, id: 'codex-session-1' } + store + .getState() + .setAgentStatus( + 'tab-1:leaf-1', + { state: 'working', prompt: 'first task', agentType: 'codex' }, + 'Codex', + { updatedAt: 10, stateStartedAt: 10 }, + { tabId: 'tab-1', worktreeId: 'wt-1' }, + { providerSession } + ) + + store.getState().captureAllSleepingAgentSessions('quit') + const quitRecord = store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1'] + store.getState().captureAllSleepingAgentSessions('periodic') + + expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toBe(quitRecord) + expect(quitRecord).toMatchObject({ origin: 'quit', providerSession }) + + store + .getState() + .setAgentStatus( + 'tab-1:leaf-1', + { state: 'working', prompt: 'new task', agentType: 'codex' }, + 'Codex', + { updatedAt: 20, stateStartedAt: 20 }, + { tabId: 'tab-1', worktreeId: 'wt-1' }, + { providerSession: { key: 'session_id', id: 'codex-session-2' } } + ) + + expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toMatchObject({ + origin: 'live', + providerSession: { key: 'session_id', id: 'codex-session-2' } + }) + }) + it('preserves hydrated launch config during live recapture without a registry entry', () => { const store = createTestStore() store.setState({ @@ -311,7 +353,7 @@ describe('captureAllSleepingAgentSessions', () => { ?.launchConfig ).toEqual(launchConfig) - store.getState().captureAllSleepingAgentSessions() + store.getState().captureAllSleepingAgentSessions('quit') expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toMatchObject({ origin: 'quit', launchConfig @@ -756,7 +798,7 @@ describe('captureAllSleepingAgentSessions', () => { } } as Partial) - store.getState().captureAllSleepingAgentSessions() + store.getState().captureAllSleepingAgentSessions('quit') const records = store.getState().sleepingAgentSessionsByPaneKey expect(records['tab-1:leaf-1']).toMatchObject({ @@ -790,7 +832,7 @@ describe('captureAllSleepingAgentSessions', () => { agentStatusByPaneKey: { 'tab-1:leaf-1': entry } } as Partial) - store.getState().captureAllSleepingAgentSessions() + store.getState().captureAllSleepingAgentSessions('quit') expect(store.getState().sleepingAgentSessionsByPaneKey).toEqual({}) }) @@ -806,7 +848,7 @@ describe('captureAllSleepingAgentSessions', () => { } } as Partial) - store.getState().captureAllSleepingAgentSessions() + store.getState().captureAllSleepingAgentSessions('quit') expect(store.getState().sleepingAgentSessionsByPaneKey).toEqual({}) }) @@ -826,7 +868,7 @@ describe('captureAllSleepingAgentSessions', () => { agentStatusByPaneKey: { 'tab-1:leaf-1': entry } } as Partial) - store.getState().captureAllSleepingAgentSessions() + store.getState().captureAllSleepingAgentSessions('quit') expect(store.getState().sleepingAgentSessionsByPaneKey['tab-1:leaf-1']).toMatchObject({ worktreeId: 'wt-1', diff --git a/src/renderer/src/store/slices/agent-status.ts b/src/renderer/src/store/slices/agent-status.ts index f6a902d2e49..14fa98fefe7 100644 --- a/src/renderer/src/store/slices/agent-status.ts +++ b/src/renderer/src/store/slices/agent-status.ts @@ -13,9 +13,11 @@ import { type ParsedAgentStatusPayload } from '../../../../shared/agent-status-types' import { + agentProviderSessionsEqual, getAgentResumeArgv, isResumableTuiAgent, type AgentProviderSessionMetadata, + type ResumableTuiAgent, type SleepingAgentLaunchConfig, type SleepingAgentSessionRecord } from '../../../../shared/agent-session-resume' @@ -61,6 +63,8 @@ export type AgentStatusWorktreeShutdownReason = | 'remove-worktree' | 'auto-hibernate-completed-agent' +type AllAgentSessionCaptureMode = 'periodic' | 'quit' + type DropAgentStatusByWorktreeOptions = { shutdownReason?: AgentStatusWorktreeShutdownReason sleepingPaneKeys?: readonly string[] | ReadonlySet @@ -166,6 +170,16 @@ export type AgentStatusSlice = { } ) => void + /** Record resume identity without creating a visible turn-status row. */ + recordAgentProviderSession: ( + paneKey: string, + agent: ResumableTuiAgent, + providerSession: AgentProviderSessionMetadata, + timing?: { updatedAt?: number }, + routing?: { tabId?: string; worktreeId?: string; connectionId?: string | null }, + metadata?: { launchToken?: string } + ) => void + registerAgentLaunchConfig: ( paneKey: string, launchConfig: SleepingAgentLaunchConfig, @@ -224,9 +238,9 @@ export type AgentStatusSlice = { dropAgentStatusByWorktree: (worktreeId: string, opts?: DropAgentStatusByWorktreeOptions) => void captureSleepingAgentSessionsByWorktree: (worktreeId: string, paneKeys?: string[]) => void - /** Capture resumable agent sessions across every worktree. Called from the - * quit flush so provider session ids survive an app restart. */ - captureAllSleepingAgentSessions: () => void + /** Capture resumable agent sessions across every worktree for crash recovery + * or a confirmed quit. The mode keeps live and quit precedence explicit. */ + captureAllSleepingAgentSessions: (mode: AllAgentSessionCaptureMode) => void clearSleepingAgentSession: (paneKey: string) => void clearSleepingAgentSessionsByPaneKey: (paneKeys: readonly string[]) => void clearSleepingAgentSessionsByWorktree: (worktreeId: string) => void @@ -530,6 +544,22 @@ function isValidCompletedAgentHibernationEntry(entry: AgentStatusEntry): boolean return entry.state === 'done' && entry.interrupted !== true } +function isCompletedPiWithLiveRecoveryRecord( + entry: AgentStatusEntry | undefined, + record: SleepingAgentSessionRecord | undefined +): record is SleepingAgentSessionRecord { + return Boolean( + entry?.state === 'done' && + entry.agentType === 'pi' && + entry.providerSession && + record?.agent === 'pi' && + record.origin === 'live' && + (!entry.worktreeId || entry.worktreeId === record.worktreeId) && + agentProviderSessionsEqual('pi', entry.providerSession, record.providerSession) && + getAgentResumeArgv('pi', record.providerSession) + ) +} + export function removeSleepingRecordsReplacedByManualWorktreeSleep( records: Record, worktreeId: string, @@ -570,6 +600,30 @@ export function collectSleepingAgentSessionRecordsForWorktree( const tabPrefixes = (state.tabsByWorktree[worktreeId] ?? []).map((tab) => `${tab.id}:`) const records: Record = {} + if (isManualWorktreeSleep) { + for (const existing of Object.values(state.sleepingAgentSessionsByPaneKey)) { + const liveEntry = state.agentStatusByPaneKey[existing.paneKey] + if ( + existing.worktreeId !== worktreeId || + existing.origin !== 'live' || + (liveEntry !== undefined && !isCompletedPiWithLiveRecoveryRecord(liveEntry, existing)) || + (allowedPaneKeys && !allowedPaneKeys.has(existing.paneKey)) || + !getAgentResumeArgv(existing.agent, existing.providerSession) + ) { + continue + } + // Why: Pi identity is resumable with no turn row and while idle after + // done; manual sleep must promote both instead of deleting the checkpoint. + records[existing.paneKey] = { + ...existing, + state: 'working', + capturedAt, + updatedAt: capturedAt, + origin: 'worktree-sleep' + } + } + } + for (const retained of Object.values(state.retainedAgentsByPaneKey)) { if (isCompletedAgentHibernation) { continue @@ -671,8 +725,7 @@ function sleepingRecordsEquivalentIgnoringCaptureTime( existing.tabId === next.tabId && existing.worktreeId === next.worktreeId && existing.agent === next.agent && - existing.providerSession.key === next.providerSession.key && - existing.providerSession.id === next.providerSession.id && + agentProviderSessionsEqual(existing.agent, existing.providerSession, next.providerSession) && existing.prompt === next.prompt && existing.state === next.state && existing.updatedAt === next.updatedAt && @@ -696,12 +749,26 @@ function recoveryRecordMatches( existing.agent === next.agent && existing.worktreeId === next.worktreeId && existing.tabId === next.tabId && - existing.providerSession.key === next.providerSession.key && - existing.providerSession.id === next.providerSession.id && + agentProviderSessionsEqual(existing.agent, existing.providerSession, next.providerSession) && launchConfigsEqual(existing.launchConfig, next.launchConfig) ) } +function recoveryRecordTargetsSameSession( + existing: SleepingAgentSessionRecord | undefined, + next: SleepingAgentSessionRecord +): boolean { + if (!existing) { + return false + } + return ( + existing.agent === next.agent && + existing.worktreeId === next.worktreeId && + existing.tabId === next.tabId && + agentProviderSessionsEqual(existing.agent, existing.providerSession, next.providerSession) + ) +} + function copyLaunchConfig(config: SleepingAgentLaunchConfig): SleepingAgentLaunchConfig { return { ...(config.agentCommand ? { agentCommand: config.agentCommand } : {}), @@ -725,16 +792,6 @@ function launchConfigsEqual( return aKeys.length === bKeys.length && aKeys.every((key) => a.agentEnv[key] === b.agentEnv[key]) } -function providerSessionsEqual( - a: AgentProviderSessionMetadata | undefined, - b: AgentProviderSessionMetadata | undefined -): boolean { - if (a === undefined || b === undefined) { - return a === b - } - return a.key === b.key && a.id === b.id -} - function normalizeLaunchConfigRegistrationMetadata( paneKey: string, metadata: AgentLaunchConfigRegistrationMetadata | undefined @@ -761,7 +818,11 @@ function launchConfigRegistryEntriesEqual( a.identity.tabId === b.identity.tabId && a.identity.leafId === b.identity.leafId && a.identity.terminalHandle === b.identity.terminalHandle && - providerSessionsEqual(a.identity.providerSession, b.identity.providerSession) + agentProviderSessionsEqual( + a.identity.agentType ?? b.identity.agentType, + a.identity.providerSession, + b.identity.providerSession + ) ) } @@ -805,7 +866,11 @@ function registryEntryMatchesStatus(args: { return false } if (identity.providerSession !== undefined) { - return providerSessionsEqual(identity.providerSession, args.providerSession) + return agentProviderSessionsEqual( + args.agentType, + identity.providerSession, + args.providerSession + ) } if (identity.launchToken !== undefined) { return true @@ -814,7 +879,11 @@ function registryEntryMatchesStatus(args: { return true } if (args.existingProviderSession && args.providerSession) { - return providerSessionsEqual(args.existingProviderSession, args.providerSession) + return agentProviderSessionsEqual( + args.agentType, + args.existingProviderSession, + args.providerSession + ) } return false } @@ -844,7 +913,11 @@ function getLaunchConfigForEntry( return sleepingRecord?.launchConfig && sleepingRecord.agent === entry.agentType && entry.providerSession && - providerSessionsEqual(sleepingRecord.providerSession, entry.providerSession) + agentProviderSessionsEqual( + entry.agentType, + sleepingRecord.providerSession, + entry.providerSession + ) ? sleepingRecord.launchConfig : undefined } @@ -1382,6 +1455,134 @@ export const createAgentStatusSlice: StateCreator { + paneKey = resolveAgentPaneAuthorityKey(paneKey) + const updatedAt = timing?.updatedAt ?? Date.now() + if ( + paneKey in get().recentlyRetiredAgentStatusPaneKeys || + isRecentlyClosedAgentStatusTab( + get().recentlyClosedAgentStatusTabIds, + getTabIdFromPaneKey(paneKey) + ) || + !getAgentResumeArgv(agent, providerSession) + ) { + return + } + let removedLiveStatus = false + set((s) => { + const existingStatus = s.agentStatusByPaneKey[paneKey] + const existingRecord = s.sleepingAgentSessionsByPaneKey[paneKey] + if ( + (existingStatus && updatedAt < existingStatus.updatedAt) || + (existingRecord && updatedAt < existingRecord.updatedAt) + ) { + return s + } + const tabId = routing?.tabId ?? getTabIdFromPaneKey(paneKey) ?? existingRecord?.tabId + const worktreeId = + routing?.worktreeId ?? + existingStatus?.worktreeId ?? + existingRecord?.worktreeId ?? + findAgentPaneWorktreeId(s, paneKey) + if (!worktreeId) { + return s + } + const registryEntry = s.agentLaunchConfigByPaneKey[paneKey] + const registryMatches = registryEntryMatchesStatus({ + entry: registryEntry, + paneKey, + agentType: agent, + tabId, + terminalHandle: undefined, + launchToken: metadata?.launchToken, + providerSession, + existingProviderSession: existingRecord?.providerSession, + providerSessionChanged: false + }) + const launchConfig = + (registryMatches ? registryEntry?.launchConfig : undefined) ?? + (existingRecord?.agent === agent && + agentProviderSessionsEqual(agent, existingRecord.providerSession, providerSession) + ? existingRecord.launchConfig + : undefined) + const record: SleepingAgentSessionRecord = { + paneKey, + ...(tabId ? { tabId } : {}), + worktreeId, + agent, + providerSession, + prompt: '', + // Why: this is durable process/session identity, not visible turn + // state; a non-done value keeps cold restore eligible. + state: 'working', + capturedAt: updatedAt, + updatedAt, + ...(existingStatus?.terminalTitle + ? { terminalTitle: existingStatus.terminalTitle } + : existingRecord?.terminalTitle + ? { terminalTitle: existingRecord.terminalTitle } + : {}), + ...(routing?.connectionId !== undefined + ? { connectionId: routing.connectionId } + : existingRecord?.connectionId !== undefined + ? { connectionId: existingRecord.connectionId } + : {}), + ...(launchConfig ? { launchConfig: copyLaunchConfig(launchConfig) } : {}), + origin: 'live' + } + removedLiveStatus = existingStatus !== undefined + const nextLive = removedLiveStatus ? { ...s.agentStatusByPaneKey } : s.agentStatusByPaneKey + if (removedLiveStatus) { + delete nextLive[paneKey] + } + const nextRetained = + paneKey in s.retainedAgentsByPaneKey + ? { ...s.retainedAgentsByPaneKey } + : s.retainedAgentsByPaneKey + if (nextRetained !== s.retainedAgentsByPaneKey) { + delete nextRetained[paneKey] + } + // Why: on identity mismatch the sleeping record drops its launch config, + // so a stale registry entry must be cleared too or a later return to the + // old provider identity could reuse stale args/env. Mirror setAgentStatus. + let nextLaunchConfigs = s.agentLaunchConfigByPaneKey + if (registryMatches && registryEntry) { + nextLaunchConfigs = { + ...nextLaunchConfigs, + [paneKey]: { + ...registryEntry, + identity: { ...registryEntry.identity, providerSession } + } + } + } else if (registryEntry) { + nextLaunchConfigs = { ...nextLaunchConfigs } + delete nextLaunchConfigs[paneKey] + } + return { + agentStatusByPaneKey: nextLive, + retainedAgentsByPaneKey: nextRetained, + sleepingAgentSessionsByPaneKey: { + ...s.sleepingAgentSessionsByPaneKey, + [paneKey]: record + }, + agentLaunchConfigByPaneKey: nextLaunchConfigs, + acknowledgedAgentsByPaneKey: removePaneKeys( + s.acknowledgedAgentsByPaneKey, + new Set([paneKey]) + ), + unreadAgentCompletionPanes: removePaneKeys( + s.unreadAgentCompletionPanes, + new Set([paneKey]) + ), + agentStatusEpoch: removedLiveStatus ? s.agentStatusEpoch + 1 : s.agentStatusEpoch, + sortEpoch: removedLiveStatus ? s.sortEpoch + 1 : s.sortEpoch + } + }) + if (removedLiveStatus) { + queueMicrotask(() => freshness.schedule()) + } + }, + setAgentStatus: (paneKey, payload, terminalTitle, timing, routing, metadata) => { paneKey = resolveAgentPaneAuthorityKey(paneKey) const updatedAt = timing?.updatedAt ?? Date.now() @@ -1522,8 +1723,11 @@ export const createAgentStatusSlice: StateCreator entry.paneKey === paneKey ) const liveRecoveryWorktreeId = - entry.state === 'done' + entry.state === 'done' && !retainsPiRecoveryIdentity ? null : (entry.worktreeId ?? findAgentPaneWorktreeId(s, entry.paneKey)) const liveRecoveryRecord = liveRecoveryWorktreeId ? sleepingRecordFromEntry({ state: s, - entry, + // Why: a completed Pi turn leaves the TUI session alive. Keep + // resume identity active without representing done as pending work. + entry: retainsPiRecoveryIdentity + ? { ...entry, state: 'working', prompt: '', lastAssistantMessage: undefined } + : entry, worktreeId: liveRecoveryWorktreeId, capturedAt: updatedAt, launchConfig: launchConfigSource, @@ -1714,7 +1931,11 @@ export const createAgentStatusSlice: StateCreator { - // Why: the quit flush must persist provider session ids for every live - // agent pane — otherwise agents whose daemon PTYs die while the app is - // closed have nothing to `--resume` from (#5232). Only live entries are - // captured: retained rows belong to panes the user already closed, and - // `done` sessions have nothing to resume. + captureAllSleepingAgentSessions: (mode) => { + // Why: both periodic crash checkpoints and quit flushes persist live + // provider ids, but only a confirmed quit may claim quit precedence. set((s) => { const capturedAt = Date.now() + const origin = mode === 'quit' ? ('quit' as const) : ('live' as const) const next: Record = { ...s.sleepingAgentSessionsByPaneKey } let changed = false for (const entry of Object.values(s.agentStatusByPaneKey)) { if (entry.state === 'done') { + const existing = next[entry.paneKey] + if (!isCompletedPiWithLiveRecoveryRecord(entry, existing)) { + continue + } + if (mode === 'periodic') { + continue + } + const record = { ...existing, capturedAt, origin } + if (!sleepingRecordsEquivalentIgnoringCaptureTime(existing, record)) { + next[entry.paneKey] = record + changed = true + } continue } const worktreeId = entry.worktreeId ?? findAgentPaneWorktreeId(s, entry.paneKey) @@ -2550,12 +2781,20 @@ export const createAgentStatusSlice: StateCreator { }) }) + it('captures Pi session ids on Pi-compatible status events', () => { + const event = normalizeHookPayload( + state, + 'pi', + { + paneKey: PANE_KEY, + payload: { + hook_event_name: 'before_agent_start', + prompt: 'resume this task', + session_id: 'pi-session-1', + session_file: '/tmp/pi-session-1.jsonl' + } + }, + 'production' + ) + + expect(event?.payload).toMatchObject({ + state: 'working', + prompt: 'resume this task', + agentType: 'pi' + }) + expect(event?.providerSession).toEqual({ + key: 'session_id', + id: 'pi-session-1', + transcriptPath: '/tmp/pi-session-1.jsonl' + }) + }) + + it('clears Pi turn cache and emits only resume identity on session_start', () => { + const start = normalizeHookPayload( + state, + 'pi', + { + paneKey: PANE_KEY, + payload: { hook_event_name: 'before_agent_start', prompt: 'stale turn' } + }, + 'production' + ) + expect(start?.payload.prompt).toBe('stale turn') + + const sessionStart = normalizeHookPayload( + state, + 'pi', + { + paneKey: PANE_KEY, + payload: { + hook_event_name: 'session_start', + session_id: 'pi-session-2', + session_file: '/tmp/pi-session-2.jsonl' + } + }, + 'production' + ) + expect(sessionStart).toMatchObject({ + providerSessionOnly: true, + providerSession: { + key: 'session_id', + id: 'pi-session-2', + transcriptPath: '/tmp/pi-session-2.jsonl' + }, + payload: { state: 'done', prompt: '', agentType: 'pi' } + }) + + const next = normalizeHookPayload( + state, + 'pi', + { paneKey: PANE_KEY, payload: { hook_event_name: 'tool_call', tool_name: 'bash' } }, + 'production' + ) + expect(next?.payload.prompt).toBe('') + }) + it('normalizes Command Code hooks and reads turn text from the transcript', () => { const tmpDir = mkdtempSync(join(tmpdir(), 'orca-command-code-transcript-')) const transcriptPath = join(tmpDir, 'transcript.jsonl') diff --git a/src/shared/agent-hook-listener.ts b/src/shared/agent-hook-listener.ts index f080ce1cecf..c4df60fb28f 100644 --- a/src/shared/agent-hook-listener.ts +++ b/src/shared/agent-hook-listener.ts @@ -297,6 +297,9 @@ export type AgentHookEventPayload = { toolAgentType?: string /** Provider-owned conversation/session id needed to resume a sleeping agent. */ providerSession?: AgentProviderSessionMetadata + /** Session identity update with no turn-state transition. The receiver uses + * this to refresh durable resume metadata without showing a fake status row. */ + providerSessionOnly?: boolean /** True when this event is a relay cache replay rather than a live hook. */ isReplay?: boolean payload: ParsedAgentStatusPayload @@ -3412,6 +3415,13 @@ function normalizePiCompatibleEvent( paneKey: string, hookPayload: Record ): ParsedAgentStatusPayload | null { + if (agentType === 'pi' && eventName === 'session_start') { + // Why: Pi emits session_start when the TUI opens or resumes; discard stale + // turn details without creating a visible working row before user activity. + clearPaneTurnCacheState(state, paneKey) + return null + } + const stateName = eventName === 'before_agent_start' || eventName === 'agent_start' || @@ -3889,7 +3899,17 @@ export function normalizeHookPayload( // stamps the real value from `mux` identity on receive. See // docs/design/agent-status-over-ssh.md §5. const providerSession = extractAgentProviderSession(source, hookPayloadRecord) - return payload + const providerSessionOnly = + source === 'pi' && eventName === 'session_start' && providerSession !== null + // Why: session_start establishes resume identity while Pi is idle. Carry a + // valid placeholder through the status-shaped transport; receivers discard + // it when providerSessionOnly is set, so no working/done row is fabricated. + const transportPayload = + payload ?? + (providerSessionOnly + ? normalizeAgentStatusPayload({ state: 'done', prompt: '', agentType: 'pi' }) + : null) + return transportPayload ? { paneKey, launchToken, @@ -3914,7 +3934,8 @@ export function normalizeHookPayload( toolAgentId: readFirstString(hookPayloadRecord, ['agent_id', 'agentId']), toolAgentType: readString(hookPayloadRecord, 'agent_type'), ...(providerSession ? { providerSession } : {}), - payload + ...(providerSessionOnly ? { providerSessionOnly: true } : {}), + payload: transportPayload } : null } diff --git a/src/shared/agent-hook-relay.ts b/src/shared/agent-hook-relay.ts index 4500dc3f26e..6469a002270 100644 --- a/src/shared/agent-hook-relay.ts +++ b/src/shared/agent-hook-relay.ts @@ -80,6 +80,8 @@ export type AgentHookRelayEnvelope = { toolAgentType?: string /** Provider-owned conversation/session id needed to resume a sleeping agent. */ providerSession?: AgentProviderSessionMetadata + /** True when this envelope updates resume identity without changing turn status. */ + providerSessionOnly?: boolean /** True when the relay is replaying its cache after Orca reconnects. */ isReplay?: boolean /** Forwarded from the agent CLI POST body. The relay default is `remote`, diff --git a/src/shared/agent-session-resume.test.ts b/src/shared/agent-session-resume.test.ts index 47fe1180659..dd106314189 100644 --- a/src/shared/agent-session-resume.test.ts +++ b/src/shared/agent-session-resume.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest' import { + agentProviderSessionsEqual, extractAgentProviderSession, getAgentResumeArgv, isResumableTuiAgent, @@ -21,6 +22,11 @@ describe('agent session resume metadata', () => { { key: 'conversation_id', id: 'agy-conversation' } ], ['opencode', { sessionID: 'opencode-session' }, { key: 'session_id', id: 'opencode-session' }], + [ + 'pi', + { session_id: 'pi-session', session_file: '/tmp/pi-session.jsonl' }, + { key: 'session_id', id: 'pi-session', transcriptPath: '/tmp/pi-session.jsonl' } + ], ['mimo-code', { sessionID: 'mimo-session' }, { key: 'session_id', id: 'mimo-session' }], ['droid', { session_id: 'droid-session' }, { key: 'session_id', id: 'droid-session' }], ['grok', { sessionId: 'grok-session' }, { key: 'session_id', id: 'grok-session' }], @@ -35,6 +41,11 @@ describe('agent session resume metadata', () => { ['gemini', { key: 'session_id', id: 's1' }, ['gemini', '--resume', 's1']], ['antigravity', { key: 'conversation_id', id: 's1' }, ['agy', '--conversation', 's1']], ['opencode', { key: 'session_id', id: 's1' }, ['opencode', '--session', 's1']], + [ + 'pi', + { key: 'session_id', id: 's1', transcriptPath: '/tmp/pi-session.jsonl' }, + ['pi', '--session', '/tmp/pi-session.jsonl'] + ], ['mimo-code', { key: 'session_id', id: 's1' }, ['mimo', '--session', 's1']], ['droid', { key: 'session_id', id: 's1' }, ['droid', '--resume', 's1']], ['grok', { key: 'session_id', id: 's1' }, ['grok', '--resume', 's1']], @@ -44,7 +55,7 @@ describe('agent session resume metadata', () => { }) it('rejects unsupported sources and unsafe ids', () => { - expect(extractAgentProviderSession('pi', { session_id: 'pi-session' })).toBeNull() + expect(extractAgentProviderSession('omp', { session_id: 'omp-session' })).toBeNull() expect(normalizeAgentProviderSession({ key: 'session_id', id: 'bad\nid' })).toBeNull() expect(normalizeAgentProviderSession({ key: 'session_id', id: '--last' })).toBeNull() expect(extractAgentProviderSession('codex', { session_id: '--last' })).toBeNull() @@ -54,6 +65,23 @@ describe('agent session resume metadata', () => { }) }) + it('does not capture ephemeral Pi sessions without a session file', () => { + expect(extractAgentProviderSession('pi', { session_id: 'pi-session' })).toBeNull() + expect( + extractAgentProviderSession('pi', { session_id: 'pi-session', session_file: '' }) + ).toBeNull() + expect(extractAgentProviderSession('pi', { session_file: '/tmp/pi-session.jsonl' })).toBeNull() + expect(getAgentResumeArgv('pi', { key: 'session_id', id: 'pi-session' })).toBeNull() + }) + + it('compares the actual provider resume locator for each agent', () => { + const first = { key: 'session_id' as const, id: 'session-1', transcriptPath: '/tmp/first' } + const second = { key: 'session_id' as const, id: 'session-1', transcriptPath: '/tmp/second' } + + expect(agentProviderSessionsEqual('pi', first, second)).toBe(false) + expect(agentProviderSessionsEqual('claude', first, second)).toBe(true) + }) + it('rejects devin resume when provider session key is not session_id', () => { expect(getAgentResumeArgv('devin', { key: 'conversation_id', id: 'x' })).toBeNull() }) @@ -84,5 +112,12 @@ describe('agent session resume metadata', () => { expect( normalizeAgentProviderSession({ key: 'session_id', id: 'ok', transcriptPath: '/x/r.jsonl' }) ).toEqual({ key: 'session_id', id: 'ok', transcriptPath: '/x/r.jsonl' }) + expect( + normalizeAgentProviderSession({ + key: 'session_id', + id: 'ok', + transcriptPath: '/tmp/bad\npath.jsonl' + }) + ).toEqual({ key: 'session_id', id: 'ok' }) }) }) diff --git a/src/shared/agent-session-resume.ts b/src/shared/agent-session-resume.ts index 5a48ae33838..027550cb6a7 100644 --- a/src/shared/agent-session-resume.ts +++ b/src/shared/agent-session-resume.ts @@ -8,6 +8,7 @@ export const RESUMABLE_TUI_AGENTS = [ 'gemini', 'antigravity', 'opencode', + 'pi', 'mimo-code', 'droid', 'grok', @@ -25,7 +26,8 @@ export type AgentProviderSessionMetadata = { * (Claude/Codex `transcript_path`), when available. Native chat reads this * directly because recent Claude Code versions name the transcript file with a * UUID that differs from the hook `session_id`, so reconstructing the path from - * `id` alone fails. `id` is still used for CLI resume (`--resume `). */ + * `id` alone fails. Claude/Codex still resume by id; Pi uses its reported + * `session_file` as the authoritative `--session` resume locator. */ transcriptPath?: string } @@ -100,23 +102,29 @@ function readSessionId(record: Record, keys: readonly string[]) /** The agent hook's authoritative transcript/rollout path, when present. Used by * native chat to read the exact file rather than reconstructing it from the * session id (which recent Claude Code no longer matches to the file name). */ -function readTranscriptPath(record: Record): string | undefined { - const raw = record.transcript_path ?? record.transcriptPath - if (typeof raw !== 'string') { - return undefined +function readTranscriptPathFromKeys( + record: Record, + keys: readonly string[] +): string | undefined { + for (const key of keys) { + const raw = record[key] + if (typeof raw !== 'string') { + continue + } + const trimmed = raw.trim() + if (trimmed && !hasUnsafeProviderSessionIdChars(trimmed)) { + return trimmed + } } - const trimmed = raw.trim() - if (!trimmed || hasUnsafeProviderSessionIdChars(trimmed)) { - return undefined - } - return trimmed + return undefined } function withTranscriptPath( metadata: AgentProviderSessionMetadata, - payload: Record + payload: Record, + keys: readonly string[] = ['transcript_path', 'transcriptPath'] ): AgentProviderSessionMetadata { - const transcriptPath = readTranscriptPath(payload) + const transcriptPath = readTranscriptPathFromKeys(payload, keys) return transcriptPath ? { ...metadata, transcriptPath } : metadata } @@ -137,13 +145,29 @@ export function normalizeAgentProviderSession(raw: unknown): AgentProviderSessio if (!id) { return null } - const transcriptPath = - typeof record.transcriptPath === 'string' && record.transcriptPath.trim().length > 0 - ? record.transcriptPath - : undefined + // Why: persisted/relay metadata crosses a trust boundary too; apply the same + // control-character rejection used for hook-reported transcript paths. + const transcriptPath = readTranscriptPathFromKeys(record, ['transcriptPath']) return transcriptPath ? { key, id, transcriptPath } : { key, id } } +/** Compare the provider-owned values that identify the CLI resume target. + * Pi's file path is identity; other agents resume by their provider id. */ +export function agentProviderSessionsEqual( + agent: string | undefined, + left: AgentProviderSessionMetadata | undefined, + right: AgentProviderSessionMetadata | undefined +): boolean { + if (left === undefined || right === undefined) { + return left === right + } + return ( + left.key === right.key && + left.id === right.id && + (agent !== 'pi' || left.transcriptPath === right.transcriptPath) + ) +} + export function extractAgentProviderSession( source: AgentHookSource, payload: Record @@ -173,6 +197,13 @@ export function extractAgentProviderSession( const id = readSessionId(payload, ['sessionID']) return id ? { key: 'session_id', id } : null } + case 'pi': { + const id = readSessionId(payload, ['session_id']) + const providerSession = id + ? withTranscriptPath({ key: 'session_id', id }, payload, ['session_file']) + : null + return providerSession?.transcriptPath ? providerSession : null + } case 'grok': { const id = readSessionId(payload, ['sessionId', 'session_id']) return id ? { key: 'session_id', id } : null @@ -183,7 +214,6 @@ export function extractAgentProviderSession( } case 'amp': case 'cursor': - case 'pi': case 'omp': case 'command-code': case 'copilot': @@ -208,6 +238,10 @@ export function getAgentResumeArgv( return providerSession.key === 'conversation_id' ? ['agy', '--conversation', id] : null case 'opencode': return providerSession.key === 'session_id' ? ['opencode', '--session', id] : null + case 'pi': + return providerSession.key === 'session_id' && providerSession.transcriptPath + ? ['pi', '--session', providerSession.transcriptPath] + : null case 'mimo-code': return providerSession.key === 'session_id' ? ['mimo', '--session', id] : null case 'droid': diff --git a/src/shared/agent-status-types.ts b/src/shared/agent-status-types.ts index d7004fdb554..728d8e1b9bb 100644 --- a/src/shared/agent-status-types.ts +++ b/src/shared/agent-status-types.ts @@ -224,6 +224,8 @@ export type AgentStatusIpcPayload = ParsedAgentStatusPayload & { stateStartedAt: number orchestration?: AgentStatusOrchestrationContext providerSession?: AgentProviderSessionMetadata + /** Resume identity update only; the status-shaped fields are transport placeholders. */ + providerSessionOnly?: boolean /** Live-only Command Code turn boundary key; not persisted to last-status.json. */ promptInteractionKey?: string } diff --git a/src/shared/workspace-session-schema.sleeping-agent.test.ts b/src/shared/workspace-session-schema.sleeping-agent.test.ts index 9c65a4ddcd6..2bfc0d79754 100644 --- a/src/shared/workspace-session-schema.sleeping-agent.test.ts +++ b/src/shared/workspace-session-schema.sleeping-agent.test.ts @@ -41,6 +41,74 @@ describe('parseWorkspaceSession sleeping agents', () => { } }) + it('preserves the authoritative Pi session file through hydration', () => { + const result = parseWorkspaceSession({ + activeRepoId: null, + activeWorktreeId: null, + activeTabId: null, + tabsByWorktree: {}, + terminalLayoutsByTabId: {}, + sleepingAgentSessionsByPaneKey: { + 'tab1:pane-1': { + paneKey: 'tab1:pane-1', + tabId: 'tab1', + worktreeId: 'wt', + agent: 'pi', + providerSession: { + key: 'session_id', + id: 'pi-session', + transcriptPath: '/tmp/pi-session.jsonl' + }, + prompt: '', + state: 'working', + capturedAt: 10, + updatedAt: 10, + origin: 'live' + } + } + }) + + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.providerSession).toEqual( + { + key: 'session_id', + id: 'pi-session', + transcriptPath: '/tmp/pi-session.jsonl' + } + ) + } + }) + + it('drops Pi sleeping-agent records without an authoritative session file', () => { + const result = parseWorkspaceSession({ + activeRepoId: null, + activeWorktreeId: null, + activeTabId: null, + tabsByWorktree: {}, + terminalLayoutsByTabId: {}, + sleepingAgentSessionsByPaneKey: { + 'tab1:pane-1': { + paneKey: 'tab1:pane-1', + tabId: 'tab1', + worktreeId: 'wt', + agent: 'pi', + providerSession: { key: 'session_id', id: 'pi-session' }, + prompt: '', + state: 'done', + capturedAt: 10, + updatedAt: 10, + origin: 'live' + } + } + }) + + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.value.sleepingAgentSessionsByPaneKey).toBeUndefined() + } + }) + it('drops invalid sleeping agent launch config without dropping the record', () => { const result = parseWorkspaceSession({ activeRepoId: null, @@ -294,8 +362,8 @@ describe('parseWorkspaceSession sleeping agents', () => { 'tab1:pane-1': { paneKey: 'tab1:pane-1', worktreeId: 'wt', - agent: 'pi', - providerSession: { key: 'session_id', id: 'pi-session' }, + agent: 'definitely-not-an-agent', + providerSession: { key: 'session_id', id: 'bogus-session' }, prompt: 'continue', state: 'working', capturedAt: 10, diff --git a/src/shared/workspace-session-sleeping-agents.ts b/src/shared/workspace-session-sleeping-agents.ts index ca110178481..6c979a03d1b 100644 --- a/src/shared/workspace-session-sleeping-agents.ts +++ b/src/shared/workspace-session-sleeping-agents.ts @@ -1,5 +1,9 @@ import { z } from 'zod' -import { normalizeAgentProviderSession, RESUMABLE_TUI_AGENTS } from './agent-session-resume' +import { + getAgentResumeArgv, + normalizeAgentProviderSession, + RESUMABLE_TUI_AGENTS +} from './agent-session-resume' import { isValidTerminalTabId } from './terminal-tab-id' const terminalTabIdSchema = z @@ -11,7 +15,10 @@ const agentProviderSessionSchema = z.preprocess( (raw) => normalizeAgentProviderSession(raw) ?? undefined, z.object({ key: z.enum(['session_id', 'conversation_id']), - id: z.string().min(1).max(512) + id: z.string().min(1).max(512), + // Why: Pi resumes by its authoritative session file, so dropping this + // field during hydration makes an otherwise valid record unusable. + transcriptPath: z.string().min(1).optional() }) ) @@ -65,23 +72,30 @@ export const sleepingAgentLaunchConfigSchema = z.preprocess((raw) => { return parsed.success ? parsed.data : undefined }, sleepingAgentLaunchConfigBaseSchema.optional()) -const sleepingAgentSessionRecordSchema = z.object({ - paneKey: z.string().refine((value) => value.length > 0), - tabId: terminalTabIdSchema.optional(), - worktreeId: z.string().min(1), - agent: z.enum(RESUMABLE_TUI_AGENTS), - providerSession: agentProviderSessionSchema, - prompt: z.string(), - state: z.enum(['working', 'blocked', 'waiting', 'done']), - capturedAt: z.number().finite().positive(), - updatedAt: z.number().finite().positive(), - terminalTitle: z.string().optional(), - lastAssistantMessage: z.string().optional(), - interrupted: z.boolean().optional(), - connectionId: z.string().nullable().optional(), - launchConfig: sleepingAgentLaunchConfigSchema.optional(), - origin: z.enum(['worktree-sleep', 'quit', 'live']).optional() -}) +const sleepingAgentSessionRecordSchema = z + .object({ + paneKey: z.string().refine((value) => value.length > 0), + tabId: terminalTabIdSchema.optional(), + worktreeId: z.string().min(1), + agent: z.enum(RESUMABLE_TUI_AGENTS), + providerSession: agentProviderSessionSchema, + prompt: z.string(), + state: z.enum(['working', 'blocked', 'waiting', 'done']), + capturedAt: z.number().finite().positive(), + updatedAt: z.number().finite().positive(), + terminalTitle: z.string().optional(), + lastAssistantMessage: z.string().optional(), + interrupted: z.boolean().optional(), + connectionId: z.string().nullable().optional(), + launchConfig: sleepingAgentLaunchConfigSchema.optional(), + origin: z.enum(['worktree-sleep', 'quit', 'live']).optional() + }) + .refine( + (record) => getAgentResumeArgv(record.agent, record.providerSession) !== null, + // Why: a hydrated record must be directly resumable; Pi additionally needs + // its persisted transcript path and agents must use their supported key. + { message: 'provider session is not resumable for this agent', path: ['providerSession'] } + ) export const sleepingAgentSessionsByPaneKeySchema = z.preprocess((raw) => { if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) { diff --git a/tests/e2e/agent-session-live-force-exit-resume.spec.ts b/tests/e2e/agent-session-live-force-exit-resume.spec.ts index ac4d5a47260..8b7cdf4cb87 100644 --- a/tests/e2e/agent-session-live-force-exit-resume.spec.ts +++ b/tests/e2e/agent-session-live-force-exit-resume.spec.ts @@ -209,7 +209,7 @@ test('resumes a live agent record after force-exit restart when pane PTY ownersh // Exercise quit capture: origin:'quit' changes the live record, triggering the // hydration-gated writer before polling persisted state. - await page.evaluate(() => window.__store?.getState().captureAllSleepingAgentSessions()) + await page.evaluate(() => window.__store?.getState().captureAllSleepingAgentSessions('quit')) // Why: the record reaches disk via the debounced session writer (150ms) plus // the main-process scheduleSave (up to 5s). Under CI event-loop starvation — From 212b79a03a673baa0df265bc6971530801d71d1e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 00:30:55 +0000 Subject: [PATCH 024/137] release: v1.4.145-rc.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 799422d20c5..408cbe31806 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "orca", - "version": "1.4.144-rc.4", + "version": "1.4.145-rc.3", "description": "Next-gen IDE for parallel agentic development", "homepage": "https://github.com/stablyai/orca", "author": "stablyai", From 2668af035cdec709d1cb4c59ccca059eb7f7d82e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 00:50:48 +0000 Subject: [PATCH 025/137] Update README downloads badge --- docs/assets/readme-downloads.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/assets/readme-downloads.svg b/docs/assets/readme-downloads.svg index 31ede357f47..08f027e2e15 100644 --- a/docs/assets/readme-downloads.svg +++ b/docs/assets/readme-downloads.svg @@ -1,5 +1,5 @@ - - downloads: 6.0m + + downloads: 6.1m @@ -15,7 +15,7 @@ downloads downloads - 6.0m - 6.0m + 6.1m + 6.1m From 24ac5a556e006765b0b3420f57de7ea387855c6b Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:00:06 -0700 Subject: [PATCH 026/137] fix(updater): humanize update-error card and hide raw error behind Show details (#9248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(updater): humanize update-error card and hide raw error behind Show details Windows auto-update failures surfaced the raw electron-updater message as the card headline — most visibly the PowerShell "Command failed: … Get-Authenticode Signature …" dump when antivirus/EDR blocks the post-download signature check. That reads as a crash, not an actionable state. Classify each failure and lead with one plain-language sentence + the right action, keeping the raw error one click away: - New shared classifier (updater-windows-signature-check): distinguishes an AV/EDR-blocked signature check (environment) from a genuine wrong-publisher mismatch (security). The two are mutually exclusive so a real integrity failure is never softened into "try again". - UpdateCard: raw error moves behind a collapsed "Show details" toggle; adds a security-stop variant (wrong publisher → no retry, "Open official releases") and the AV-blocked variant ("Update Verification Blocked"). HTTP/2 and generic paths keep their existing actions, now with the same details disclosure. - Main process records a windows_signature_check_blocked lifecycle event so we can size the affected Windows cohort in the field. Verified each error scenario in a running Electron build (signature-check blocked, wrong-publisher security stop, HTTP/2, generic, and the expanded details view). * fix(updater): make Show details a caret disclosure above the action row Move the raw-error toggle directly above the Retry/Download buttons and give it a rotating chevron; the Last error block now expands in place beneath the caret instead of appearing above the summary, with the action buttons pinned below. * fix(updater): prevent signature-check bypass * fix(updater): surface retry start immediately --- src/main/updater.test.ts | 48 ++++ src/main/updater.ts | 21 +- .../components/UpdateCard.error-card.test.tsx | 96 ++++++++ src/renderer/src/components/UpdateCard.tsx | 205 +++++++++++++----- src/renderer/src/i18n/locales/en.json | 9 +- src/renderer/src/i18n/locales/es.json | 9 +- src/renderer/src/i18n/locales/ja.json | 9 +- src/renderer/src/i18n/locales/ko.json | 9 +- src/renderer/src/i18n/locales/zh.json | 9 +- .../updater-windows-signature-check.test.ts | 68 ++++++ src/shared/updater-windows-signature-check.ts | 23 ++ 11 files changed, 448 insertions(+), 58 deletions(-) create mode 100644 src/renderer/src/components/UpdateCard.error-card.test.tsx create mode 100644 src/shared/updater-windows-signature-check.test.ts create mode 100644 src/shared/updater-windows-signature-check.ts diff --git a/src/main/updater.test.ts b/src/main/updater.test.ts index 0bed8d0a3ce..e516951d51e 100644 --- a/src/main/updater.test.ts +++ b/src/main/updater.test.ts @@ -1170,6 +1170,54 @@ describe('updater', () => { ) }) + it('surfaces an accepted retry before electron-updater emits download progress', async () => { + fetchNewerReleaseTagsMock.mockResolvedValue({ tags: ['v1.0.61'], state: 'ready' }) + autoUpdaterMock.checkForUpdates.mockImplementation(() => { + autoUpdaterMock.emit('checking-for-update') + queueMicrotask(() => { + autoUpdaterMock.emit('update-available', { version: '1.0.61' }) + }) + return Promise.resolve(undefined) + }) + autoUpdaterMock.downloadUpdate + .mockRejectedValueOnce(new Error('signature check blocked')) + .mockImplementationOnce(() => new Promise(() => {})) + const sendMock = vi.fn() + const mainWindow = { webContents: { send: sendMock } } + + const { setupAutoUpdater, checkForUpdatesFromMenu, downloadUpdate } = await import('./updater') + + setupAutoUpdater(mainWindow as never, { getLastUpdateCheckAt: () => Date.now() }) + checkForUpdatesFromMenu() + await vi.waitFor(() => { + expect(sendMock).toHaveBeenCalledWith('updater:status', { + state: 'available', + version: '1.0.61', + changelog: null + }) + }) + + downloadUpdate() + await vi.waitFor(() => { + expect(sendMock).toHaveBeenCalledWith('updater:status', { + state: 'error', + message: 'signature check blocked' + }) + }) + + sendMock.mockClear() + downloadUpdate() + + expect(sendMock).toHaveBeenCalledWith('updater:status', { + state: 'downloading', + percent: 0, + version: '1.0.61' + }) + + downloadUpdate() + expect(autoUpdaterMock.downloadUpdate).toHaveBeenCalledTimes(2) + }) + it('defers quitAndInstall through the shared main-process entrypoint', async () => { vi.useFakeTimers() diff --git a/src/main/updater.ts b/src/main/updater.ts index 290100f0f63..2ec8da1ec22 100644 --- a/src/main/updater.ts +++ b/src/main/updater.ts @@ -2,6 +2,7 @@ import { app, BrowserWindow, powerMonitor } from 'electron' import { is } from '@electron-toolkit/utils' import type { UpdateCheckOptions, UpdateStatus } from '../shared/types' +import { isWindowsSignatureCheckUnavailableFailure } from '../shared/updater-windows-signature-check' import { killAllPty } from './ipc/pty' import { withUpdaterSpan } from './observability/instrumentation' import { loadElectronAutoUpdater, type ElectronAutoUpdater } from './electron-updater-loader' @@ -117,9 +118,8 @@ let _getPendingUpdateNudgeId: (() => string | null) | null = null let _getDismissedUpdateNudgeId: (() => string | null) | null = null let _setPendingUpdateNudgeId: ((id: string | null) => void) | null = null let _setDismissedUpdateNudgeId: ((id: string | null) => void) | null = null -// Why: guards against duplicate download() calls when both the card and -// Settings trigger a download before the first download-progress event -// flips the status to 'downloading'. +// Why: guards against duplicate download() calls while an accepted request +// transitions the authoritative status to 'downloading'. let downloadInFlight = false /** Guards against the macOS `activate` handler re-opening the old version * while Squirrel's ShipIt is replacing the .app bundle. */ @@ -523,6 +523,14 @@ function sendErrorStatus(message: string, userInitiated?: boolean): void { ) { return } + // Why: counts AV/EDR-blocked Windows signature checks in the field so we can + // size the affected cohort before investing in bigger updater changes. + if (isWindowsSignatureCheckUnavailableFailure(message)) { + recordUpdaterLifecycle('windows_signature_check_blocked', undefined, { + level: 'warn', + message: 'Windows update signature check could not run' + }) + } sendStatus({ state: 'error', message, userInitiated }) } @@ -1510,8 +1518,15 @@ export function downloadUpdate(): void { if (!canStart) { return } + const version = currentStatus.state === 'available' ? currentStatus.version : availableVersion + if (!version) { + return + } downloadInFlight = true beginMacUpdateDownload() + // Why: retries may spend seconds in setup before electron-updater emits + // progress; surface acceptance immediately so the action never looks inert. + sendStatus({ state: 'downloading', percent: 0, version }) getAutoUpdater() .downloadUpdate() .catch((err) => { diff --git a/src/renderer/src/components/UpdateCard.error-card.test.tsx b/src/renderer/src/components/UpdateCard.error-card.test.tsx new file mode 100644 index 00000000000..cbe84536bbf --- /dev/null +++ b/src/renderer/src/components/UpdateCard.error-card.test.tsx @@ -0,0 +1,96 @@ +// @vitest-environment happy-dom +import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { useAppStore } from '../store' +import { UpdateCard } from './UpdateCard' + +const openUrl = vi.fn() +const download = vi.fn() + +function renderAfterAvailableStatus(): void { + useAppStore.setState({ + updateStatus: { + state: 'available', + version: '1.4.200', + changelog: null + }, + updateChangelog: null, + dismissedUpdateVersion: null, + updateCardCollapsed: false, + updateReassuranceSeen: true + }) + render() +} + +beforeEach(() => { + useAppStore.setState(useAppStore.getInitialState(), true) + openUrl.mockReset() + download.mockReset() + Object.defineProperty(window, 'api', { + configurable: true, + value: { + app: { relaunch: vi.fn() }, + settings: { set: vi.fn().mockResolvedValue(undefined) }, + shell: { openUrl }, + ui: { set: vi.fn().mockResolvedValue(undefined) }, + updater: { + check: vi.fn(), + dismissNudge: vi.fn(), + download, + quitAndInstall: vi.fn().mockResolvedValue(undefined) + } + } + }) + Object.defineProperty(window, 'matchMedia', { + configurable: true, + value: vi.fn().mockReturnValue({ + matches: false, + addEventListener: vi.fn(), + removeEventListener: vi.fn() + }) + }) +}) + +afterEach(() => { + cleanup() + useAppStore.setState(useAppStore.getInitialState(), true) +}) + +describe('UpdateCard Windows signature failures', () => { + it('does not offer the rejected version as a manual publisher-check bypass', () => { + const message = + 'New version 1.4.200 is not signed by the application owner: publisherNames: Orca' + renderAfterAvailableStatus() + + act(() => useAppStore.getState().setUpdateStatus({ state: 'error', message })) + + expect(screen.getByText("Update Wasn't Installed")).toBeTruthy() + expect(screen.getByText(/Don't install this download/)).toBeTruthy() + expect(screen.queryByText(message)).toBeNull() + + fireEvent.click(screen.getByRole('button', { name: 'Check official releases' })) + expect(openUrl).toHaveBeenCalledWith('https://github.com/stablyai/orca/releases') + expect(openUrl).not.toHaveBeenCalledWith(expect.stringContaining('/tag/')) + }) + + it('keeps the blocked-check error collapsed while preserving retry and details', () => { + const message = + 'Command failed: powershell.exe Get-AuthenticodeSignature -LiteralPath update.exe' + renderAfterAvailableStatus() + + act(() => useAppStore.getState().setUpdateStatus({ state: 'error', message })) + + expect(screen.getByText('Update Verification Blocked')).toBeTruthy() + expect(screen.queryByText(message)).toBeNull() + expect(screen.queryByText(/Windows verifies the installer/)).toBeNull() + + fireEvent.click(screen.getByRole('button', { name: 'Retry Download' })) + expect(download).toHaveBeenCalledTimes(1) + + fireEvent.click(screen.getByRole('button', { name: 'Show details' })) + expect(screen.getByText(message)).toBeTruthy() + expect(screen.getByRole('button', { name: 'Hide details' }).getAttribute('aria-expanded')).toBe( + 'true' + ) + }) +}) diff --git a/src/renderer/src/components/UpdateCard.tsx b/src/renderer/src/components/UpdateCard.tsx index 806540832a3..9010a740e10 100644 --- a/src/renderer/src/components/UpdateCard.tsx +++ b/src/renderer/src/components/UpdateCard.tsx @@ -1,14 +1,28 @@ /* eslint-disable max-lines -- Why: the update card owns the full updater lifecycle in one renderer surface. Keeping the state machine and its presentation variants together avoids scattering tightly coupled update behavior across multiple files. */ -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useId, useRef, useState } from 'react' import { usePrefersReducedMotion } from '@/hooks/usePrefersReducedMotion' import { useAppStore } from '../store' import { Card } from './ui/card' import { Button } from './ui/button' import { Progress } from './ui/progress' -import { AlertCircle, Check, Loader2, Minus, Network, RotateCw, X } from 'lucide-react' +import { + AlertCircle, + Check, + ChevronRight, + Loader2, + Minus, + Network, + RotateCw, + ShieldAlert, + X +} from 'lucide-react' import type { ChangelogData } from '../../../shared/types' +import { + isWindowsSignatureCheckUnavailableFailure, + isWindowsSignatureMismatchFailure +} from '../../../shared/updater-windows-signature-check' import { translate } from '@/i18n/i18n' // ── Helpers ────────────────────────────────────────────────────────── @@ -37,11 +51,16 @@ export function isHttp2ProtocolError(message: string): boolean { } type ErrorCardModel = { - variant?: 'default' | 'http1Compatibility' + variant?: 'default' | 'http1Compatibility' | 'security' title: string summary: string - message: string + /** Optional guidance box between the summary and the raw error output. */ + explainer?: string + /** Raw error text, shown only when the user expands "Show details". */ + detail?: string releaseUrl: string + /** Overrides the secondary button label (defaults to "Download Manually"). */ + manualLabel?: string primaryAction?: { label: string pendingLabel?: string @@ -337,7 +356,16 @@ export function UpdateCard() { }) } + // Why: classify each failure so the card leads with one plain sentence and + // the right action, and keeps the raw error behind "Show details" instead of + // dumping a PowerShell/stack trace as the headline. Order matters: the + // security-stop (wrong publisher) must win over the AV/EDR "check couldn't + // run" case so a real integrity failure is never softened into "try again". const isHttp2UpdateError = status.state === 'error' && isHttp2ProtocolError(status.message) + const isSignatureMismatchError = + status.state === 'error' && isWindowsSignatureMismatchFailure(status.message) + const isSignatureCheckBlockedError = + status.state === 'error' && isWindowsSignatureCheckUnavailableFailure(status.message) const errorCard: ErrorCardModel | null = status.state === 'error' ? isHttp2UpdateError @@ -345,7 +373,11 @@ export function UpdateCard() { variant: 'http1Compatibility', title: translate('auto.components.UpdateCard.1339b82cee', 'HTTP/2 Download Blocked'), summary: 'Orca can retry through HTTP/1.1 compatibility mode.', - message: compatibilitySetupError ?? status.message, + explainer: translate( + 'auto.components.UpdateCard.90559b14e3', + 'This turns on a process-wide Electron networking switch after restart. Use it for corporate VPNs or proxies that reject HTTP/2 update downloads.' + ), + detail: compatibilitySetupError ?? status.message, releaseUrl: releaseUrlForVersion(cachedVersion), primaryAction: { label: translate('auto.components.UpdateCard.933c6fdf5b', 'Enable & Restart'), @@ -354,35 +386,71 @@ export function UpdateCard() { onClick: handleEnableHttp1Compatibility } } - : { - // Why: title is scoped to the operation that failed so check-time - // failures (commonly GitHub-side) don't read as a bug in Orca. - title: cachedVersion ? 'Update Error' : 'Update Check Failed', - summary: cachedVersion - ? 'Could not complete the update.' - : 'Could not check for updates.', - message: status.message, - releaseUrl: releaseUrlForVersion(cachedVersion), - // Why: check-time failures are often transient (offline, GitHub - // hiccup), so offer a Re-check next to "Download Manually" instead - // of forcing the user into the manual fallback. - primaryAction: cachedVersion - ? { + : isSignatureMismatchError + ? { + // Security stop: the installer is readable but signed by the wrong + // publisher. No retry — only a path to the verified download. + variant: 'security', + title: translate('auto.components.UpdateCard.5b309b19f3', "Update Wasn't Installed"), + summary: translate( + 'auto.components.UpdateCard.092f09fc14', + "The installer's publisher doesn't match Orca, so we stopped the update. Don't install this download; check official releases for a corrected version." + ), + detail: status.message, + // Why: linking the rejected version directly would invite users + // to bypass the publisher check by running the same installer. + releaseUrl: releaseUrlForVersion(null), + manualLabel: translate( + 'auto.components.UpdateCard.c9ff9b9ec2', + 'Check official releases' + ) + } + : isSignatureCheckBlockedError + ? { + title: translate( + 'auto.components.UpdateCard.e944c2de43', + 'Update Verification Blocked' + ), + summary: translate( + 'auto.components.UpdateCard.a05992a26b', + "The signature check couldn't run — usually because antivirus software blocked it. Retry the download, or get the installer from our official releases." + ), + detail: status.message, + releaseUrl: releaseUrlForVersion(cachedVersion), + primaryAction: { label: translate('auto.components.UpdateCard.48565a32bc', 'Retry Download'), onClick: handleUpdate } - : { - label: translate('auto.components.UpdateCard.6b0085010d', 'Re-check'), - onClick: () => { - void window.api.updater.check({ includePrerelease: false }) - } - } - } + } + : { + // Why: title is scoped to the operation that failed so check-time + // failures (commonly GitHub-side) don't read as a bug in Orca. + title: cachedVersion ? 'Update Error' : 'Update Check Failed', + summary: cachedVersion + ? 'Could not complete the update.' + : 'Could not check for updates.', + detail: status.message, + releaseUrl: releaseUrlForVersion(cachedVersion), + // Why: check-time failures are often transient (offline, GitHub + // hiccup), so offer a Re-check next to "Download Manually" instead + // of forcing the user into the manual fallback. + primaryAction: cachedVersion + ? { + label: translate('auto.components.UpdateCard.48565a32bc', 'Retry Download'), + onClick: handleUpdate + } + : { + label: translate('auto.components.UpdateCard.6b0085010d', 'Re-check'), + onClick: () => { + void window.api.updater.check({ includePrerelease: false }) + } + } + } : installError ? { title: translate('auto.components.UpdateCard.4cf109845a', 'Update Error'), summary: 'Could not restart to install the update.', - message: installError, + detail: installError, releaseUrl: releaseUrlForVersion(cachedVersion), primaryAction: { label: translate('auto.components.UpdateCard.2c2d3e03ca', 'Try Again'), @@ -494,8 +562,10 @@ export function UpdateCard() { void }) { + // Why: the raw error is kept for support but starts collapsed so the card + // leads with the plain-language summary, not a PowerShell/stack dump. + const [showDetails, setShowDetails] = useState(false) + const detailId = useId() const isCompatibility = variant === 'http1Compatibility' - const Icon = isCompatibility ? Network : AlertCircle + const isSecurity = variant === 'security' + const Icon = isCompatibility ? Network : isSecurity ? ShieldAlert : AlertCircle return (
    -
    +
    @@ -933,25 +1018,45 @@ function ErrorCardContent({
    - {isCompatibility ? ( + {explainer ? (
    -

    - {translate( - 'auto.components.UpdateCard.90559b14e3', - 'This turns on a process-wide Electron networking switch after restart. Use it for corporate VPNs or proxies that reject HTTP/2 update downloads.' - )} -

    +

    {explainer}

    ) : null} -
    -

    - {translate('auto.components.UpdateCard.3553a8672f', 'Last error')} -

    -

    - {message} -

    -
    + {/* Why: a caret disclosure sits directly above the raw error it reveals, + so the card leads with the plain summary and expands the last-error + block in place — the action buttons stay pinned below it. */} + {detail ? ( +
    + + {showDetails ? ( +
    +

    + {translate('auto.components.UpdateCard.3553a8672f', 'Last error')} +

    +

    + {detail} +

    +
    + ) : null} +
    + ) : null}
    {primaryAction && ( @@ -976,9 +1081,9 @@ function ErrorCardContent({ variant="outline" size="sm" onClick={() => void window.api.shell.openUrl(releaseUrl)} - className={primaryAction ? 'flex-1' : 'w-full'} + className="flex-1" > - {translate('auto.components.UpdateCard.47126bcf57', 'Download Manually')} + {manualLabel ?? translate('auto.components.UpdateCard.47126bcf57', 'Download Manually')}
    diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 87e36d2e564..3eb944c8330 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -1867,7 +1867,14 @@ "a726967bd3": "Dismiss", "d5253b54af": "error", "7ffc08506e": "check", - "522df222b9": "spinner" + "522df222b9": "spinner", + "e944c2de43": "Update Verification Blocked", + "5b309b19f3": "Update Wasn't Installed", + "092f09fc14": "The installer's publisher doesn't match Orca, so we stopped the update. Don't install this download; check official releases for a corrected version.", + "c9ff9b9ec2": "Check official releases", + "a05992a26b": "The signature check couldn't run — usually because antivirus software blocked it. Retry the download, or get the installer from our official releases.", + "5194358929": "Hide details", + "8bc9e17d8f": "Show details" }, "WorktreeJumpPalette": { "ac037cfac2": "Move", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index ea9d29b7b2f..df5980671b5 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -1844,7 +1844,14 @@ "a726967bd3": "Descartar", "d5253b54af": "error", "7ffc08506e": "check", - "522df222b9": "spinner" + "522df222b9": "spinner", + "e944c2de43": "Update Verification Blocked", + "5b309b19f3": "Update Wasn't Installed", + "092f09fc14": "The installer's publisher doesn't match Orca, so we stopped the update. Don't install this download; check official releases for a corrected version.", + "c9ff9b9ec2": "Check official releases", + "a05992a26b": "The signature check couldn't run — usually because antivirus software blocked it. Retry the download, or get the installer from our official releases.", + "5194358929": "Hide details", + "8bc9e17d8f": "Show details" }, "WorktreeJumpPalette": { "ac037cfac2": "Mover", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 96cea8a5d9e..30a587a73b1 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -1844,7 +1844,14 @@ "a726967bd3": "閉じる", "d5253b54af": "エラー", "7ffc08506e": "チェック", - "522df222b9": "スピナー" + "522df222b9": "スピナー", + "e944c2de43": "Update Verification Blocked", + "5b309b19f3": "Update Wasn't Installed", + "092f09fc14": "The installer's publisher doesn't match Orca, so we stopped the update. Don't install this download; check official releases for a corrected version.", + "c9ff9b9ec2": "Check official releases", + "a05992a26b": "The signature check couldn't run — usually because antivirus software blocked it. Retry the download, or get the installer from our official releases.", + "5194358929": "Hide details", + "8bc9e17d8f": "Show details" }, "WorktreeJumpPalette": { "ac037cfac2": "移動", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index ca88ba53b5c..e15a6de0759 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -1844,7 +1844,14 @@ "a726967bd3": "닫기", "d5253b54af": "오류", "7ffc08506e": "확인", - "522df222b9": "스피너" + "522df222b9": "스피너", + "e944c2de43": "Update Verification Blocked", + "5b309b19f3": "Update Wasn't Installed", + "092f09fc14": "The installer's publisher doesn't match Orca, so we stopped the update. Don't install this download; check official releases for a corrected version.", + "c9ff9b9ec2": "Check official releases", + "a05992a26b": "The signature check couldn't run — usually because antivirus software blocked it. Retry the download, or get the installer from our official releases.", + "5194358929": "Hide details", + "8bc9e17d8f": "Show details" }, "WorktreeJumpPalette": { "ac037cfac2": "이동", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 4c88fb17470..2e9e529d695 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -1844,7 +1844,14 @@ "a726967bd3": "关闭", "d5253b54af": "错误", "7ffc08506e": "查看", - "522df222b9": "旋转器" + "522df222b9": "旋转器", + "e944c2de43": "Update Verification Blocked", + "5b309b19f3": "Update Wasn't Installed", + "092f09fc14": "The installer's publisher doesn't match Orca, so we stopped the update. Don't install this download; check official releases for a corrected version.", + "c9ff9b9ec2": "Check official releases", + "a05992a26b": "The signature check couldn't run — usually because antivirus software blocked it. Retry the download, or get the installer from our official releases.", + "5194358929": "Hide details", + "8bc9e17d8f": "Show details" }, "WorktreeJumpPalette": { "ac037cfac2": "手机", diff --git a/src/shared/updater-windows-signature-check.test.ts b/src/shared/updater-windows-signature-check.test.ts new file mode 100644 index 00000000000..648630f58a2 --- /dev/null +++ b/src/shared/updater-windows-signature-check.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from 'vitest' +import { + isWindowsSignatureCheckUnavailableFailure, + isWindowsSignatureMismatchFailure +} from './updater-windows-signature-check' + +describe('isWindowsSignatureCheckUnavailableFailure', () => { + it('matches the PowerShell command failure shape (timeout / non-zero exit)', () => { + expect( + isWindowsSignatureCheckUnavailableFailure( + 'Command failed: set "PSModulePath=" & chcp 65001 >NUL & powershell.exe -NoProfile ' + + '-NonInteractive -InputFormat None -Command "Get-AuthenticodeSignature -LiteralPath ' + + "'C:\\Users\\u\\AppData\\Local\\orca-updater\\pending\\orca-windows-setup.exe' | " + + 'ConvertTo-Json -Compress"' + ) + ).toBe(true) + }) + + it('matches the stderr failure shape', () => { + expect( + isWindowsSignatureCheckUnavailableFailure( + 'Cannot execute Get-AuthenticodeSignature, stderr: Access is denied. ' + + 'Failing signature validation due to unknown stderr.' + ) + ).toBe(true) + }) + + it('does not match a genuine signature mismatch', () => { + expect( + isWindowsSignatureCheckUnavailableFailure( + 'New version 1.4.144 is not signed by the application owner: ' + + 'publisherNames: SignPath Foundation, raw info: {"Status": 0}' + ) + ).toBe(false) + }) + + it('does not match unrelated update errors', () => { + expect(isWindowsSignatureCheckUnavailableFailure('net::ERR_HTTP2_PROTOCOL_ERROR')).toBe(false) + expect( + isWindowsSignatureCheckUnavailableFailure('Cannot find channel "latest.yml" (404)') + ).toBe(false) + }) +}) + +describe('isWindowsSignatureMismatchFailure', () => { + it('matches the wrong-publisher integrity failure', () => { + expect( + isWindowsSignatureMismatchFailure( + 'New version 1.4.144 is not signed by the application owner: ' + + 'publisherNames: SignPath Foundation, raw info: {"Status": 0}' + ) + ).toBe(true) + }) + + it('is mutually exclusive with the check-unavailable classifier', () => { + const mismatch = 'New version 1.4.144 is not signed by the application owner: publisherNames: X' + expect(isWindowsSignatureMismatchFailure(mismatch)).toBe(true) + expect(isWindowsSignatureCheckUnavailableFailure(mismatch)).toBe(false) + + const blocked = 'Command failed: … Get-AuthenticodeSignature -LiteralPath …' + expect(isWindowsSignatureCheckUnavailableFailure(blocked)).toBe(true) + expect(isWindowsSignatureMismatchFailure(blocked)).toBe(false) + }) + + it('does not match unrelated errors', () => { + expect(isWindowsSignatureMismatchFailure('net::ERR_HTTP2_PROTOCOL_ERROR')).toBe(false) + }) +}) diff --git a/src/shared/updater-windows-signature-check.ts b/src/shared/updater-windows-signature-check.ts new file mode 100644 index 00000000000..58be137f236 --- /dev/null +++ b/src/shared/updater-windows-signature-check.ts @@ -0,0 +1,23 @@ +// Why: electron-updater verifies Windows installers by spawning PowerShell's +// Get-AuthenticodeSignature. Antivirus/EDR interception, its hardcoded 20s +// timeout, or stalled revocation lookups kill that spawn, and the raw +// child-process failure becomes the update error. Detect that shape so the UI +// can explain it instead of dumping the command line. A true signature +// mismatch ("not signed by the application owner") must NOT match — that is a +// real integrity failure, not environment interference. +export function isWindowsSignatureCheckUnavailableFailure(message: string): boolean { + const normalized = message.toLowerCase() + if (normalized.includes('not signed by the application owner')) { + return false + } + return normalized.includes('get-authenticodesignature') +} + +// Why: electron-updater throws ERR_UPDATER_INVALID_SIGNATURE with this exact +// phrase when the downloaded installer is validly readable but signed by the +// wrong publisher — a genuine integrity failure, NOT environment interference. +// This must drive a security-stop message (no silent "retry" framing) and stay +// distinct from isWindowsSignatureCheckUnavailableFailure above. +export function isWindowsSignatureMismatchFailure(message: string): boolean { + return message.toLowerCase().includes('not signed by the application owner') +} From cbdd750f731464a7faaccc375bcd85c1acf24524 Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:07:24 -0700 Subject: [PATCH 027/137] fix(terminal): keep redraw transcript cursors stable (#9081) Co-authored-by: Orca --- src/main/runtime/orca-runtime.test.ts | 81 +++++++++- src/main/runtime/orca-runtime.ts | 147 ++++++++++++++++-- ...ned-tail-redraw-window.equivalence.test.ts | 3 + 3 files changed, 212 insertions(+), 19 deletions(-) diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index e5021aebb90..40521e2ba5d 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -13435,7 +13435,7 @@ describe('OrcaRuntimeService', () => { cursor: Number(beforeRedraw.nextCursor) }) expect(cursorRead.tail).toEqual(['• Working.', 'Tool call finished']) - expect(cursorRead.oldestCursor).toBe('2') + expect(cursorRead.oldestCursor).toBe('0') expect(cursorRead.nextCursor).toBe('4') }) @@ -13459,10 +13459,85 @@ describe('OrcaRuntimeService', () => { cursor: Number(beforeRedraw.nextCursor) }) expect(cursorRead.tail).toEqual(['B2']) - expect(cursorRead.oldestCursor).toBe('2') + expect(cursorRead.oldestCursor).toBe('0') expect(cursorRead.nextCursor).toBe('4') }) + it('keeps cursor pagination stable across a CUU redraw of the live screen', async () => { + const runtime = new OrcaRuntimeService(store) + const liveScreen = new HeadlessEmulator({ cols: 80, rows: 24 }) + syncSinglePty(runtime) + + const [terminal] = (await runtime.listTerminals()).terminals + const initialOutput = 'A\r\nB\r\nC\r\n' + const redraw = '\x1b[2A\x1b[2K\x1b[1GB2\r\n' + runtime.onPtyData('pty-1', initialOutput, 100) + await liveScreen.write(initialOutput) + + const firstPage = await runtime.readTerminal(terminal.handle, { cursor: 0, limit: 2 }) + expect(firstPage).toMatchObject({ + tail: ['A', 'B'], + truncated: false, + limited: true, + oldestCursor: '0', + nextCursor: '2', + latestCursor: '3', + returnedLineCount: 2 + }) + + runtime.onPtyData('pty-1', redraw, 101) + await liveScreen.write(redraw) + + // Why: CUU changes the mutable screen, but completed-line pagination is a distinct contract. + expect(liveScreen.getVisibleLines().filter((line) => line.length > 0)).toEqual(['A', 'B2', 'C']) + await expect(runtime.readTerminal(terminal.handle, { cursor: 2 })).resolves.toMatchObject({ + tail: ['C', 'B2'], + truncated: false, + limited: false, + oldestCursor: '0', + nextCursor: '4', + latestCursor: '4', + returnedLineCount: 2 + }) + + liveScreen.dispose() + }) + + it('records completed transcript lines before later CUU redraws in the same PTY chunk', async () => { + const runtime = new OrcaRuntimeService(store) + syncSinglePty(runtime) + + const [terminal] = (await runtime.listTerminals()).terminals + runtime.onPtyData('pty-1', 'A\r\nB\r\nC\r\n\x1b[2A\x1b[2K\x1b[1GB2\r\n', 100) + + await expect(runtime.readTerminal(terminal.handle, { cursor: 0 })).resolves.toMatchObject({ + tail: ['A', 'B', 'C', 'B2'], + truncated: false, + oldestCursor: '0', + nextCursor: '4', + latestCursor: '4' + }) + }) + + it('bounds completed transcript entries from a single multi-line redraw chunk', async () => { + const runtime = new OrcaRuntimeService(store) + syncSinglePty(runtime) + + const [terminal] = (await runtime.listTerminals()).terminals + const lines = Array.from({ length: 2100 }, (_, index) => `redraw-${index}`) + runtime.onPtyData('pty-1', `\x1b[1A${lines.join('\n')}\n`, 100) + + await expect( + runtime.readTerminal(terminal.handle, { cursor: 0, limit: 3 }) + ).resolves.toMatchObject({ + tail: ['redraw-100', 'redraw-101', 'redraw-102'], + truncated: true, + oldestCursor: '100', + nextCursor: '103', + latestCursor: '2100' + }) + }) + it('retains multi-line ANSI redraws when the last footer row stays partial', async () => { const runtime = new OrcaRuntimeService(store) syncSinglePty(runtime) @@ -13486,7 +13561,7 @@ describe('OrcaRuntimeService', () => { cursor: Number(beforeRedraw.nextCursor) }) expect(cursorRead.tail).toEqual(['• Working.']) - expect(cursorRead.oldestCursor).toBe('2') + expect(cursorRead.oldestCursor).toBe('0') expect(cursorRead.nextCursor).toBe('3') runtime.onPtyData('pty-1', '\n', 102) diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index c9fcca82eb3..205f27fd691 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -975,6 +975,8 @@ type RuntimeLeafRecord = RuntimeSyncedLeaf & { lastOutputAt: number | null lastExitCode: number | null tailBuffer: string[] + tailTranscriptBuffer: string[] + tailTranscriptChars: number tailPartialLine: string tailPendingAnsi: string tailRedrawCursor: RetainedTailRedrawCursor | null @@ -1031,6 +1033,8 @@ type RuntimePtyWorktreeRecord = { titleUpdatedAt: number | null lastOutputAt: number | null tailBuffer: string[] + tailTranscriptBuffer: string[] + tailTranscriptChars: number tailPartialLine: string tailPendingAnsi: string tailRedrawCursor: RetainedTailRedrawCursor | null @@ -3332,6 +3336,8 @@ export class OrcaRuntimeService { lastOutputAt: tailSource?.lastOutputAt ?? null, lastExitCode: tailSource?.lastExitCode ?? null, tailBuffer: tailSource?.tailBuffer ?? [], + tailTranscriptBuffer: tailSource?.tailTranscriptBuffer ?? [], + tailTranscriptChars: tailSource?.tailTranscriptChars ?? 0, tailPartialLine: tailSource?.tailPartialLine ?? '', tailPendingAnsi: tailSource?.tailPendingAnsi ?? '', tailRedrawCursor: tailSource?.tailRedrawCursor ?? null, @@ -6040,6 +6046,7 @@ export class OrcaRuntimeService { const ptyTailBefore = pty ? { lines: pty.tailBuffer, + transcriptLines: pty.tailTranscriptBuffer, partialLine: pty.tailPartialLine, pendingAnsi: pty.tailPendingAnsi, redrawCursor: pty.tailRedrawCursor, @@ -6061,10 +6068,18 @@ export class OrcaRuntimeService { pty.tailRedrawCursor ) ptyTailAfter = nextTail + const nextTranscript = appendCompletedTerminalTranscript( + pty.tailTranscriptBuffer, + pty.tailTranscriptChars, + nextTail.newlyCompletedLines, + nextTail.newCompleteLines + ) pty.tailBuffer = nextTail.lines + pty.tailTranscriptBuffer = nextTranscript.lines + pty.tailTranscriptChars = nextTranscript.characters pty.tailPartialLine = nextTail.partialLine pty.tailRedrawCursor = nextTail.redrawCursor - pty.tailTruncated = pty.tailTruncated || nextTail.truncated + pty.tailTruncated = pty.tailTruncated || nextTail.truncated || nextTranscript.truncated pty.tailLinesTotal += nextTail.newCompleteLines pty.preview = buildPreview(pty.tailBuffer, pty.tailPartialLine) this.scheduleWaitBlockedCheck(ptyId, normalized.text, at) @@ -6087,6 +6102,7 @@ export class OrcaRuntimeService { ptyTailAfter && tailStateMatches( leaf.tailBuffer, + leaf.tailTranscriptBuffer, leaf.tailPartialLine, leaf.tailPendingAnsi, leaf.tailRedrawCursor, @@ -6098,6 +6114,8 @@ export class OrcaRuntimeService { // Why: the leaf and PTY record usually mirror the same terminal. Reuse // the PTY tail update instead of splitting large output twice. leaf.tailBuffer = pty.tailBuffer + leaf.tailTranscriptBuffer = pty.tailTranscriptBuffer + leaf.tailTranscriptChars = pty.tailTranscriptChars leaf.tailPartialLine = pty.tailPartialLine leaf.tailPendingAnsi = pty.tailPendingAnsi leaf.tailRedrawCursor = pty.tailRedrawCursor @@ -6123,6 +6141,12 @@ export class OrcaRuntimeService { normalized.text, leaf.tailRedrawCursor ) + const nextTranscript = appendCompletedTerminalTranscript( + leaf.tailTranscriptBuffer, + leaf.tailTranscriptChars, + nextTail.newlyCompletedLines, + nextTail.newCompleteLines + ) const nextWaitState = computeTerminalTailWaitState( nextTail.lines, nextTail.partialLine, @@ -6133,9 +6157,11 @@ export class OrcaRuntimeService { } leaf.tailWaitState = nextWaitState leaf.tailBuffer = nextTail.lines + leaf.tailTranscriptBuffer = nextTranscript.lines + leaf.tailTranscriptChars = nextTranscript.characters leaf.tailPartialLine = nextTail.partialLine leaf.tailRedrawCursor = nextTail.redrawCursor - leaf.tailTruncated = leaf.tailTruncated || nextTail.truncated + leaf.tailTruncated = leaf.tailTruncated || nextTail.truncated || nextTranscript.truncated leaf.tailLinesTotal += nextTail.newCompleteLines leaf.preview = buildPreview(leaf.tailBuffer, leaf.tailPartialLine) } @@ -11037,7 +11063,8 @@ export class OrcaRuntimeService { const read = readTerminalTail({ handle, status: getTerminalState(leaf), - completedLines: leaf.tailBuffer, + previewLines: leaf.tailBuffer, + completedLines: leaf.tailTranscriptBuffer, partialLine: leaf.tailPartialLine, completedLineCount: leaf.tailLinesTotal, bufferTruncated: leaf.tailTruncated, @@ -21333,6 +21360,8 @@ export class OrcaRuntimeService { titleUpdatedAt: titleObservedAt, lastOutputAt: state.lastOutputAt ?? null, tailBuffer: [], + tailTranscriptBuffer: [], + tailTranscriptChars: 0, tailPartialLine: '', tailPendingAnsi: '', tailRedrawCursor: null, @@ -21470,6 +21499,8 @@ export class OrcaRuntimeService { // Why: disconnected PTY records can stay addressable for status/exit reads, // but their retained transcripts must not accumulate after the process dies. pty.tailBuffer = [] + pty.tailTranscriptBuffer = [] + pty.tailTranscriptChars = 0 pty.tailPartialLine = '' pty.tailPendingAnsi = '' pty.tailRedrawCursor = null @@ -22903,7 +22934,8 @@ export class OrcaRuntimeService { return readTerminalTail({ handle, status: pty.connected ? 'running' : pty.lastExitCode !== null ? 'exited' : 'unknown', - completedLines: pty.tailBuffer, + previewLines: pty.tailBuffer, + completedLines: pty.tailTranscriptBuffer, partialLine: pty.tailPartialLine, completedLineCount: pty.tailLinesTotal, bufferTruncated: pty.tailTruncated, @@ -26532,6 +26564,7 @@ export function appendNormalizedToTailBuffer( redrawCursor: RetainedTailRedrawCursor | null truncated: boolean newCompleteLines: number + newlyCompletedLines: string[] } { if (normalizedChunk.length === 0) { return { @@ -26539,7 +26572,8 @@ export function appendNormalizedToTailBuffer( partialLine: previousPartialLine, redrawCursor: previousRedrawCursor, truncated: false, - newCompleteLines: 0 + newCompleteLines: 0, + newlyCompletedLines: [] } } @@ -26563,6 +26597,7 @@ export function appendNormalizedToTailBuffer( // visible redraw segment instead of appending every spinner frame. const segments = splitRetainedTerminalTailSegments(combinedChunk) const pieces = processTerminalTailCompleteSegments(segments.completeSegments) + const newlyCompletedLines = pieces.map((line) => trimTerminalLineRight(line)) const partialResult = applyTerminalLineControls(segments.partialSegment) const nextPartialLine = trimTerminalLineRight(partialResult.text) const retainedPartialLine = nextPartialLine.slice(-MAX_TAIL_PARTIAL_CHARS) @@ -26570,10 +26605,7 @@ export function appendNormalizedToTailBuffer( const omittedNewCompleteLines = newCompleteLines - pieces.length let nextLines = newCompleteLines > 0 - ? [ - ...(omittedNewCompleteLines > 0 ? [] : previousLines), - ...pieces.map((line) => line.replace(/[ \t]+$/g, '')) - ] + ? [...(omittedNewCompleteLines > 0 ? [] : previousLines), ...newlyCompletedLines] : previousLines let truncated = previousPartialWasCapped || @@ -26615,7 +26647,8 @@ export function appendNormalizedToTailBuffer( partialLine: retainedPartialLine, redrawCursor, truncated, - newCompleteLines + newCompleteLines, + newlyCompletedLines } } @@ -26667,6 +26700,7 @@ function appendNormalizedToMultilineTailBuffer( redrawCursor: RetainedTailRedrawCursor | null truncated: boolean newCompleteLines: number + newlyCompletedLines: string[] } { const windowRows = maxUpwardCursorReach(normalizedChunk, previousRedrawCursor) + REDRAW_WINDOW_SAFETY_ROWS @@ -26724,7 +26758,8 @@ function appendNormalizedToMultilineTailBuffer( partialLine: windowed.partialLine, redrawCursor: windowed.redrawCursor, truncated, - newCompleteLines: windowed.newCompleteLines + newCompleteLines: windowed.newCompleteLines, + newlyCompletedLines: windowed.newlyCompletedLines } } @@ -26740,6 +26775,7 @@ export function appendNormalizedToMultilineTailBufferUnwindowed( redrawCursor: RetainedTailRedrawCursor | null truncated: boolean newCompleteLines: number + newlyCompletedLines: string[] } { const rows: RetainedTerminalRow[] = [ ...previousLines.map((line) => ({ text: line, completed: true })), @@ -26750,8 +26786,29 @@ export function appendNormalizedToMultilineTailBufferUnwindowed( : rows.length - 1 let cursorColumn = previousRedrawCursor?.column ?? boundedPreviousPartialLine.length let newCompleteLines = 0 + const newlyCompletedLines: string[] = [] + let newlyCompletedLineCharacters = 0 + let newlyCompletedLineStart = 0 let truncated = previousPartialWasCapped + const retainNewlyCompletedLine = (line: string): void => { + newlyCompletedLines.push(line) + newlyCompletedLineCharacters += line.length + while ( + newlyCompletedLines.length - newlyCompletedLineStart > MAX_TAIL_LINES || + newlyCompletedLineCharacters > MAX_TAIL_CHARS + ) { + newlyCompletedLineCharacters -= newlyCompletedLines[newlyCompletedLineStart]!.length + newlyCompletedLineStart += 1 + } + // Why: a single PTY chunk can contain unbounded newlines; compact in batches + // while retaining the suffix needed for stable transcript pagination. + if (newlyCompletedLineStart >= MAX_TAIL_LINES) { + newlyCompletedLines.splice(0, newlyCompletedLineStart) + newlyCompletedLineStart = 0 + } + } + const ensureCursorRow = (): void => { while (cursorRow >= rows.length) { rows.push({ text: '', completed: false }) @@ -26807,6 +26864,7 @@ export function appendNormalizedToMultilineTailBufferUnwindowed( ensureCursorRow() rows[cursorRow]!.completed = true newCompleteLines += 1 + retainNewlyCompletedLine(trimTerminalLineRight(rows[cursorRow]!.text)) cursorRow += 1 cursorColumn = 0 ensureCursorRow() @@ -26848,7 +26906,16 @@ export function appendNormalizedToMultilineTailBufferUnwindowed( writeChar(char) } - return finalizeRetainedTerminalRows(rows, cursorRow, cursorColumn, truncated, newCompleteLines) + return finalizeRetainedTerminalRows( + rows, + cursorRow, + cursorColumn, + truncated, + newCompleteLines, + newlyCompletedLineStart > 0 + ? newlyCompletedLines.slice(newlyCompletedLineStart) + : newlyCompletedLines + ) } type RetainedTailRedrawCursor = { @@ -26866,13 +26933,15 @@ function finalizeRetainedTerminalRows( cursorRow: number, cursorColumn: number, initialTruncated: boolean, - newCompleteLines: number + newCompleteLines: number, + newlyCompletedLines: string[] ): { lines: string[] partialLine: string redrawCursor: RetainedTailRedrawCursor | null truncated: boolean newCompleteLines: number + newlyCompletedLines: string[] } { let truncated = initialTruncated let retainedRows = rows.map((row) => ({ ...row, text: row.text.replace(/[ \t]+$/g, '') })) @@ -26934,7 +27003,8 @@ function finalizeRetainedTerminalRows( partialLine, redrawCursor, truncated, - newCompleteLines + newCompleteLines, + newlyCompletedLines } } @@ -27161,8 +27231,43 @@ function containsTerminalVerticalLineControl(value: string): boolean { return false } +function appendCompletedTerminalTranscript( + previousLines: string[], + previousCharacters: number, + newlyCompletedLines: string[], + newCompleteLineCount: number +): { lines: string[]; characters: number; truncated: boolean } { + if (newCompleteLineCount === 0) { + return { lines: previousLines, characters: previousCharacters, truncated: false } + } + + const omittedNewLineCount = Math.max(0, newCompleteLineCount - newlyCompletedLines.length) + const lines = omittedNewLineCount > 0 ? [] : [...previousLines] + let characters = omittedNewLineCount > 0 ? 0 : previousCharacters + for (const line of newlyCompletedLines) { + lines.push(line) + characters += line.length + } + + let dropCount = Math.max(0, lines.length - MAX_TAIL_LINES) + for (let index = 0; index < dropCount; index += 1) { + characters -= lines[index]!.length + } + while (dropCount < lines.length && characters > MAX_TAIL_CHARS) { + characters -= lines[dropCount]!.length + dropCount += 1 + } + + return { + lines: dropCount > 0 ? lines.slice(dropCount) : lines, + characters, + truncated: omittedNewLineCount > 0 || dropCount > 0 + } +} + function tailStateMatches( lines: string[], + transcriptLines: string[], partialLine: string, pendingAnsi: string, redrawCursor: RetainedTailRedrawCursor | null, @@ -27170,6 +27275,7 @@ function tailStateMatches( linesTotal: number, snapshot: { lines: string[] + transcriptLines: string[] partialLine: string pendingAnsi: string redrawCursor: RetainedTailRedrawCursor | null @@ -27183,7 +27289,8 @@ function tailStateMatches( !tailRedrawCursorsMatch(redrawCursor, snapshot.redrawCursor) || truncated !== snapshot.truncated || linesTotal !== snapshot.linesTotal || - lines.length !== snapshot.lines.length + lines.length !== snapshot.lines.length || + transcriptLines.length !== snapshot.transcriptLines.length ) { return false } @@ -27195,6 +27302,13 @@ function tailStateMatches( return false } } + if (transcriptLines !== snapshot.transcriptLines) { + for (let index = 0; index < transcriptLines.length; index++) { + if (transcriptLines[index] !== snapshot.transcriptLines[index]) { + return false + } + } + } return true } @@ -27253,6 +27367,7 @@ function trimTerminalPreviewToCharacterBudget( function readTerminalTail(args: { handle: string status: RuntimeTerminalState + previewLines: string[] completedLines: string[] partialLine: string completedLineCount: number @@ -27303,7 +27418,7 @@ function readTerminalTail(args: { // latest bounded view, while the larger retained buffer remains available // through cursor reads plus --limit. const limit = terminalReadLimit(args.limit, DEFAULT_TERMINAL_READ_LIMIT) - const allLines = buildTailLines(args.completedLines, args.partialLine) + const allLines = buildTailLines(args.previewLines, args.partialLine) const lineBoundedTail = allLines.slice(-limit) const charBoundedTail = trimTerminalPreviewToCharacterBudget( lineBoundedTail, diff --git a/src/main/runtime/retained-tail-redraw-window.equivalence.test.ts b/src/main/runtime/retained-tail-redraw-window.equivalence.test.ts index 32685f93b53..30b36483675 100644 --- a/src/main/runtime/retained-tail-redraw-window.equivalence.test.ts +++ b/src/main/runtime/retained-tail-redraw-window.equivalence.test.ts @@ -85,6 +85,9 @@ describe('windowed redraw tail equivalence', () => { expect(actual.redrawCursor, `round ${round} cursor`).toEqual(expected.redrawCursor) expect(actual.truncated, `round ${round} truncated`).toBe(expected.truncated) expect(actual.newCompleteLines, `round ${round} newLines`).toBe(expected.newCompleteLines) + expect(actual.newlyCompletedLines, `round ${round} completedLines`).toEqual( + expected.newlyCompletedLines + ) } }) }) From 2f5fc65ba40b4139cea6770eb45c8d842348940b Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:09:39 -0700 Subject: [PATCH 028/137] fix(terminal): don't report agent exit on a degraded foreground scan (#9244) * fix(terminal): don't report agent exit on a degraded foreground scan On Windows/ConPTY the foreground scan (a Get-CimInstance Win32_Process PowerShell fork) can exceed its 3s budget under load, and there is no wmic fallback on Win11 24H2+. A timed-out scan returned the shell name, which the completion coordinator reads as "agent exited" and fires a false "agent done" notification while the agent is still working. Prefer the last positively-recognized agent across a degraded (available:false) scan so a transient failure no longer looks like an exit. An authoritative (available:true) scan still refreshes/clears the memory, so a genuine exit or crash is still detected. No-op on macOS, where the scan is always available. * fix(terminal): drop stale foreground scan results --- src/main/providers/local-pty-provider.test.ts | 44 +++++++++++++---- src/main/providers/local-pty-provider.ts | 33 ++++++++++++- .../stable-foreground-process.test.ts | 47 +++++++++++++++++++ .../providers/stable-foreground-process.ts | 40 ++++++++++++++++ 4 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 src/main/providers/stable-foreground-process.test.ts create mode 100644 src/main/providers/stable-foreground-process.ts diff --git a/src/main/providers/local-pty-provider.test.ts b/src/main/providers/local-pty-provider.test.ts index 54f8c2a110c..40f43cd6f65 100644 --- a/src/main/providers/local-pty-provider.test.ts +++ b/src/main/providers/local-pty-provider.test.ts @@ -67,10 +67,8 @@ vi.mock('./windows-powershell-executable', () => ({ })) vi.mock('./agent-foreground-process', () => ({ - resolveAgentForegroundProcessWithAvailability: async (...args: unknown[]) => ({ - available: true, - processName: await resolveAgentForegroundProcessMock(...args) - }) + resolveAgentForegroundProcessWithAvailability: (...args: unknown[]) => + resolveAgentForegroundProcessMock(...args) })) vi.mock('../wsl', () => ({ @@ -143,7 +141,10 @@ describe('LocalPtyProvider', () => { terminateDescendantSnapshotMock.mockReset() resolveAgentForegroundProcessMock.mockReset() resolveAgentForegroundProcessMock.mockImplementation( - async (_pid: number, fallbackProcess: string | null) => fallbackProcess + async (_pid: number, fallbackProcess: string | null) => ({ + available: true, + processName: fallbackProcess + }) ) exitCb = undefined @@ -1358,13 +1359,40 @@ describe('LocalPtyProvider', () => { it('returns null for unknown PTY ids', async () => { expect(await provider.getForegroundProcess('nonexistent')).toBeNull() }) + + it('keeps a recognized agent across an unavailable scan without adding probes', async () => { + resolveAgentForegroundProcessMock + .mockResolvedValueOnce({ available: true, processName: 'claude' }) + .mockResolvedValueOnce({ available: false, processName: 'powershell.exe' }) + const { id } = await provider.spawn({ cols: 80, rows: 24 }) + + await expect(provider.getForegroundProcess(id)).resolves.toBe('claude') + await expect(provider.getForegroundProcess(id)).resolves.toBe('claude') + expect(resolveAgentForegroundProcessMock).toHaveBeenCalledTimes(2) + }) + + it('drops a delayed scan result after the PTY exits', async () => { + let resolveScan!: (resolution: { available: boolean; processName: string }) => void + resolveAgentForegroundProcessMock.mockReturnValue( + new Promise((resolve) => { + resolveScan = resolve + }) + ) + const { id } = await provider.spawn({ cols: 80, rows: 24 }) + + const foreground = provider.getForegroundProcess(id) + exitCb?.({ exitCode: 0 }) + resolveScan({ available: true, processName: 'droid' }) + + await expect(foreground).resolves.toBeNull() + }) }) describe('confirmForegroundProcess', () => { it('drops a delayed result after the PTY exits', async () => { - let resolveScan!: (processName: string) => void + let resolveScan!: (resolution: { available: boolean; processName: string }) => void resolveAgentForegroundProcessMock.mockReturnValue( - new Promise((resolve) => { + new Promise((resolve) => { resolveScan = resolve }) ) @@ -1372,7 +1400,7 @@ describe('LocalPtyProvider', () => { const confirmation = provider.confirmForegroundProcess(id) exitCb?.({ exitCode: 0 }) - resolveScan('droid') + resolveScan({ available: true, processName: 'droid' }) await expect(confirmation).resolves.toBeNull() }) diff --git a/src/main/providers/local-pty-provider.ts b/src/main/providers/local-pty-provider.ts index 70e05d8a7e4..0faea3109e1 100644 --- a/src/main/providers/local-pty-provider.ts +++ b/src/main/providers/local-pty-provider.ts @@ -52,6 +52,7 @@ import { } from '../git-bash' import { WINDOWS_GIT_BASH_SHELL } from '../../shared/windows-terminal-shell' import { resolveAgentForegroundProcessWithAvailability } from './agent-foreground-process' +import { resolveStableForegroundProcess } from './stable-foreground-process' import { getAgentForegroundContextPaths } from './agent-foreground-context-paths' import { recognizeAgentProcessFromCommandLine } from '../../shared/agent-process-recognition' import { @@ -91,6 +92,9 @@ type PtyShutdownOperation = { const ptyShutdownOperations = new Map() const ptyShellName = new Map() const ptyAgentForegroundContextPaths = new Map() +// Why: remembers the last positively-recognized agent foreground per PTY so a +// degraded/timed-out scan does not report the shell and look like an exit. +const ptyLastRecognizedForeground = new Map() const ptyTerminalHandle = new Map() const ptyInitialCwd = new Map() // Why: node-pty callbacks must be disposed before environment teardown, but @@ -226,6 +230,7 @@ function clearPtyState(id: string): void { ptyAgentSessionIds.delete(id) ptyShellName.delete(id) ptyAgentForegroundContextPaths.delete(id) + ptyLastRecognizedForeground.delete(id) ptyTerminalHandle.delete(id) ptyInitialCwd.delete(id) ptyLoadGeneration.delete(id) @@ -1183,6 +1188,7 @@ export class LocalPtyProvider implements IPtyProvider { async getForegroundProcess(id: string): Promise { const proc = ptyProcesses.get(id) if (!proc) { + ptyLastRecognizedForeground.delete(id) return null } try { @@ -1193,9 +1199,32 @@ export class LocalPtyProvider implements IPtyProvider { contextPaths: ptyAgentForegroundContextPaths.get(id) } ) - return resolution.processName + // Why: the scan can outlive PTY teardown or id reuse; stale results must + // not resurrect cache state for a process that no longer owns this id. + if (ptyProcesses.get(id) !== proc) { + return null + } + // Why: a degraded/timed-out scan must not report the shell as the + // foreground — the completion coordinator reads that as an exit and fires + // a false "agent done" while the agent is still working. Prefer the last + // recognized agent across a transient failure (e.g. a Windows CIM timeout). + const stable = resolveStableForegroundProcess( + resolution, + ptyLastRecognizedForeground.get(id) ?? null + ) + if (stable.lastRecognizedAgent) { + ptyLastRecognizedForeground.set(id, stable.lastRecognizedAgent) + } else { + ptyLastRecognizedForeground.delete(id) + } + return stable.processName } catch { - return null + if (ptyProcesses.get(id) !== proc) { + return null + } + // Why: an inspection error is itself a degraded read; fall back to the + // last recognized agent rather than null (which also reads as an exit). + return ptyLastRecognizedForeground.get(id) ?? null } } diff --git a/src/main/providers/stable-foreground-process.test.ts b/src/main/providers/stable-foreground-process.test.ts new file mode 100644 index 00000000000..3e718324fe3 --- /dev/null +++ b/src/main/providers/stable-foreground-process.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest' +import { resolveStableForegroundProcess } from './stable-foreground-process' + +describe('resolveStableForegroundProcess', () => { + it('reports the last recognized agent when a scan is unavailable (Windows CIM timeout)', () => { + // A degraded scan falls back to the shell name. Without this, the shell + // reads as "agent exited" and fires a false completion while it still works. + const result = resolveStableForegroundProcess( + { available: false, processName: 'powershell.exe' }, + 'claude' + ) + expect(result.processName).toBe('claude') + expect(result.lastRecognizedAgent).toBe('claude') + }) + + it('remembers the agent from a completed scan that found it', () => { + const result = resolveStableForegroundProcess({ available: true, processName: 'claude' }, null) + expect(result.processName).toBe('claude') + expect(result.lastRecognizedAgent).toBe('claude') + }) + + it('reports a real exit and clears memory when a completed scan finds no agent', () => { + // Regression guard: a genuine exit/crash must still be detectable — an + // authoritative (available) scan with no agent overrides the memory. + const result = resolveStableForegroundProcess( + { available: true, processName: 'powershell.exe' }, + 'claude' + ) + expect(result.processName).toBe('powershell.exe') + expect(result.lastRecognizedAgent).toBeNull() + }) + + it('passes through when a scan is unavailable and nothing is remembered', () => { + const result = resolveStableForegroundProcess( + { available: false, processName: 'powershell.exe' }, + null + ) + expect(result.processName).toBe('powershell.exe') + expect(result.lastRecognizedAgent).toBeNull() + }) + + it('prefers the remembered agent even when a degraded scan returns null', () => { + const result = resolveStableForegroundProcess({ available: false, processName: null }, 'codex') + expect(result.processName).toBe('codex') + expect(result.lastRecognizedAgent).toBe('codex') + }) +}) diff --git a/src/main/providers/stable-foreground-process.ts b/src/main/providers/stable-foreground-process.ts new file mode 100644 index 00000000000..2bb11433cbc --- /dev/null +++ b/src/main/providers/stable-foreground-process.ts @@ -0,0 +1,40 @@ +import { recognizeAgentProcess } from '../../shared/agent-process-recognition' +import type { AgentForegroundProcessResolution } from './agent-foreground-process' + +export type StableForegroundProcess = { + /** Foreground process name to report to callers. */ + processName: string | null + /** Agent name to remember for the next degraded read; null clears the memory. */ + lastRecognizedAgent: string | null +} + +/** + * Keep the reported foreground process stable across a degraded inspection. + * + * Why: on Windows/ConPTY the foreground scan (a `Get-CimInstance Win32_Process` + * PowerShell fork) can exceed its 3s budget under load, and there is no `wmic` + * fallback on Win11 24H2+. A degraded scan returns `available: false` and falls + * back to the shell name — which the completion coordinator reads as "the agent + * exited" and fires a false "agent done" notification while the agent is still + * working. On a degraded read, prefer the last agent we positively recognized so + * a transient scan failure never looks like an exit. A completed (`available`) + * scan is authoritative and refreshes — or clears — that memory, so a genuine + * exit is still detected. + */ +export function resolveStableForegroundProcess( + resolution: AgentForegroundProcessResolution, + lastRecognizedAgent: string | null +): StableForegroundProcess { + if (resolution.available) { + const isAgent = + resolution.processName !== null && recognizeAgentProcess(resolution.processName) !== null + return { + processName: resolution.processName, + lastRecognizedAgent: isAgent ? resolution.processName : null + } + } + return { + processName: lastRecognizedAgent ?? resolution.processName, + lastRecognizedAgent + } +} From 360b2423ea237bb4f259f34502d57960a5197c2b Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:29:44 -0700 Subject: [PATCH 029/137] Fix duplicate and lost IME terminal commits (#9235) Co-authored-by: Orca --- .../@xterm__xterm@6.1.0-beta.287.patch | 157 +++++++++++++++++ package.json | 3 +- pnpm-lock.yaml | 49 +++--- ...me-xterm-composition-deduplication.test.ts | 162 ++++++++++++++++++ 4 files changed, 347 insertions(+), 24 deletions(-) create mode 100644 config/patches/@xterm__xterm@6.1.0-beta.287.patch create mode 100644 src/renderer/src/components/terminal-pane/terminal-ime-xterm-composition-deduplication.test.ts diff --git a/config/patches/@xterm__xterm@6.1.0-beta.287.patch b/config/patches/@xterm__xterm@6.1.0-beta.287.patch new file mode 100644 index 00000000000..3847fbfbb60 --- /dev/null +++ b/config/patches/@xterm__xterm@6.1.0-beta.287.patch @@ -0,0 +1,157 @@ +diff --git a/lib/xterm.js b/lib/xterm.js +index 9602d2a2abbdd6eb1ed884dd2cdcace32d1bb1a3..eecf435886d866a838430b9f81f713deed3eb8c1 100644 +--- a/lib/xterm.js ++++ b/lib/xterm.js +@@ -1,2 +1,2 @@ +-!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i=t();for(var s in i)("object"==typeof exports?exports:e)[s]=i[s]}}(globalThis,()=>(()=>{"use strict";var e={2840(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},a=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._handleBoundaryFocus(e,0),this._bottomBoundaryFocusListener=e=>this._handleBoundaryFocus(e,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=r.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new c.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this._register(this._terminal.onResize(e=>this._handleResize(e.rows))),this._register(this._terminal.onRender(e=>this._refreshRows(e.start,e.end))),this._register(this._terminal.onScroll(()=>this._refreshRows())),this._register(this._terminal.onA11yChar(e=>this._handleChar(e))),this._register(this._terminal.onLineFeed(()=>this._handleChar("\n"))),this._register(this._terminal.onA11yTab(e=>this._handleTab(e))),this._register(this._terminal.onKey(e=>this._handleKey(e.key))),this._register(this._terminal.onBlur(()=>this._clearLiveRegion())),this._register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this._register((0,f.addDisposableListener)(r,"selectionchange",()=>this._handleSelectionChange())),this._register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRowsDimensions(),this._refreshRows(),this._register((0,d.toDisposable)(()=>{this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,"\n"===e&&(this._liveRegionLineCount++,21===this._liveRegionLineCount&&(this._liveRegion.textContent=l.tooMuchOutput.get())))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){const i=this._terminal.buffer,s=i.lines.length.toString();for(let r=e;r<=t;r++){const e=i.lines.get(i.ydisp+r),t=[],o=e?.translateToString(!0,void 0,void 0,t)||"",n=(i.ydisp+r+1).toString(),a=this._rowElements[r];a&&(0===o.length?(a.textContent=" ",this._rowColumns.set(a,[0,1])):(a.textContent=o,this._rowColumns.set(a,t)),a.setAttribute("aria-posinset",n),a.setAttribute("aria-setsize",s),this._alignRowWidth(a))}this._announceCharacters()}_announceCharacters(){0!==this._charsToAnnounce.length&&(this._liveRegion.textContent===l.tooMuchOutput.get()&&this._clearLiveRegion(),this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){const i=e.target,s=this._rowElements[0===t?1:this._rowElements.length-2];if(i.getAttribute("aria-posinset")===(0===t?"1":`${this._terminal.buffer.lines.length}`))return;if(e.relatedTarget!==s)return;let r,o;if(0===t?(r=i,o=this._rowElements.pop(),this._rowContainer.removeChild(o)):(r=this._rowElements.shift(),o=i,this._rowContainer.removeChild(r)),r.removeEventListener("focus",this._topBoundaryFocusListener),o.removeEventListener("focus",this._bottomBoundaryFocusListener),0===t){const e=this._createAccessibilityTreeNode();this._rowElements.unshift(e),this._rowContainer.insertAdjacentElement("afterbegin",e)}else{const e=this._createAccessibilityTreeNode();this._rowElements.push(e),this._rowContainer.appendChild(e)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(0===t?-1:1),this._rowElements[0===t?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(0===this._rowElements.length)return;const e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed)return void(this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection());if(!e.anchorNode||!e.focusNode)return void console.error("anchorNode and/or focusNode are null");let t={node:e.anchorNode,offset:e.anchorOffset},i={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(i.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===i.node&&t.offset>i.offset)&&([t,i]=[i,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;const s=this._rowElements.slice(-1)[0];if(i.node.compareDocumentPosition(s)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(i={node:s,offset:s.textContent?.length??0}),!this._rowContainer.contains(i.node))return;const r=({node:e,offset:t})=>{const i=e instanceof Text?e.parentNode:e;let s=parseInt(i?.getAttribute("aria-posinset"),10)-1;if(isNaN(s))return console.warn("row is invalid. Race condition?"),null;const r=this._rowColumns.get(i);if(!r)return console.warn("columns is null. Race condition?"),null;let o=t=this._terminal.cols&&(++s,o=0),{row:s,column:o}},o=r(t),n=r(i);if(o&&n){if(o.row>n.row||o.row===n.row&&o.column>=n.column)throw new Error("invalid range");this._terminal.select(o.column,o.row,(n.row-o.row)*this._terminal.cols-o.column+n.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let e=this._rowContainer.children.length;ee;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){const e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;ethis._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(e=>this.refresh(e?.start??0,e?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(e=>this._reportWindowsOptions(e))),this._register(this._inputHandler.onColor(e=>this._handleColorEvent(e))),this._register(I.EventUtils.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(I.EventUtils.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(I.EventUtils.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(I.EventUtils.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(e=>this._afterResize(e.cols,e.rows))),this._register((0,N.toDisposable)(()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)}))}_handleColorEvent(e){if(this._themeService)for(const t of e){let e,i;switch(t.index){case 256:e="foreground",i="10";break;case 257:e="background",i="11";break;case 258:e="cursor",i="12";break;default:e="ansi",i="4;"+t.index}switch(t.type){case 0:const s=E.color.toColorRGB("ansi"===e?this._themeService.colors.ansi[t.index]:this._themeService.colors[e]);this.coreService.triggerDataEvent(`]${i};${(0,M.toRgbString)(s)}\\`);break;case 1:if("ansi"===e)this._themeService.modifyColors(e=>e.ansi[t.index]=E.channels.toColor(...t.color));else{const i=e;this._themeService.modifyColors(e=>e[i]=E.channels.toColor(...t.color))}break;case 2:this._themeService.restoreColor(t.index)}}}_reportColorScheme(){if(!this._themeService)return;const e=E.rgb.relativeLuminance(this._themeService.colors.background.rgba>>8)>8)?1:2;this.coreService.triggerDataEvent(`[?997;${e}n`)}_setup(){super._setup(),this._customKeyEventHandler=void 0}get buffer(){return this.buffers.active}focus(){this.textarea&&this.textarea.focus({preventScroll:!0})}_handleScreenReaderModeOptionChange(e){e?!this._accessibilityManager.value&&this._renderService&&(this._accessibilityManager.value=this._instantiationService.createInstance(P.AccessibilityManager,this)):this._accessibilityManager.clear()}_handleTextAreaFocus(e){this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(""),this.element.classList.add("focus"),this._showCursor(),this._onFocus.fire()}blur(){return this.textarea?.blur()}_handleTextAreaBlur(){this.textarea.value="",this.refresh(this.buffer.y,this.buffer.y),this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(""),this.element.classList.remove("focus"),this._onBlur.fire()}_syncTextArea(){if(!this.textarea||!this.buffer.isCursorInViewport||this._compositionHelper.isComposing||!this._renderService)return;const e=this.buffer.ybase+this.buffer.y,t=this.buffer.lines.get(e);if(!t)return;const i=Math.min(this.buffer.x,this.cols-1),s=this._renderService.dimensions.css.cell.height,r=t.getWidth(i),o=this._renderService.dimensions.css.cell.width*r,n=this.buffer.y*this._renderService.dimensions.css.cell.height,a=i*this._renderService.dimensions.css.cell.width;this.textarea.style.left=a+"px",this.textarea.style.top=n+"px",this.textarea.style.width=o+"px",this.textarea.style.height=s+"px",this.textarea.style.lineHeight=s+"px",this.textarea.style.zIndex="-5"}_initGlobal(){this._bindKeys(),this._register((0,H.addDisposableListener)(this.element,"copy",e=>{this.hasSelection()&&(0,a.copyHandler)(e,this._selectionService)}));const e=e=>(0,a.handlePasteEvent)(e,this.textarea,this.coreService,this.optionsService);this._register((0,H.addDisposableListener)(this.textarea,"paste",e)),this._register((0,H.addDisposableListener)(this.element,"paste",e)),x.isFirefox?this._register((0,H.addDisposableListener)(this.element,"mousedown",e=>{2===e.button&&(0,a.rightClickHandler)(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register((0,H.addDisposableListener)(this.element,"contextmenu",e=>{(0,a.rightClickHandler)(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),x.isLinux&&this._register((0,H.addDisposableListener)(this.element,"auxclick",e=>{1===e.button&&(0,a.moveTextAreaUnderMouseCursor)(e,this.textarea,this.screenElement)}))}_bindKeys(){this._register((0,H.addDisposableListener)(this.textarea,"keyup",e=>this._keyUp(e),!0)),this._register((0,H.addDisposableListener)(this.textarea,"keydown",e=>this._keyDown(e),!0)),this._register((0,H.addDisposableListener)(this.textarea,"keypress",e=>this._keyPress(e),!0)),this._register((0,H.addDisposableListener)(this.textarea,"compositionstart",()=>{this._syncTextArea(),this._compositionHelper.compositionstart(),this._compositionHelper.updateCompositionElements()})),this._register((0,H.addDisposableListener)(this.textarea,"compositionupdate",e=>this._compositionHelper.compositionupdate(e))),this._register((0,H.addDisposableListener)(this.textarea,"compositionend",()=>this._compositionHelper.compositionend())),this._register((0,H.addDisposableListener)(this.textarea,"input",e=>this._inputEvent(e),!0)),this._register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(e){if(!e)throw new Error("Terminal requires a parent element.");if(e.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this.element?.ownerDocument.defaultView&&this._coreBrowserService)return void(this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView));this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),this.element.classList.toggle("allow-transparency",this.options.allowTransparency),this._register(this.optionsService.onSpecificOptionChange("allowTransparency",e=>this.element.classList.toggle("allow-transparency",e))),e.appendChild(this.element);const t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._register((0,H.addDisposableListener)(this.screenElement,"mousemove",e=>this.updateCursorStyle(e))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);const i=this.textarea=this._document.createElement("textarea");this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",h.promptLabel.get()),x.isChromeOS||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange("disableStdin",()=>i.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(g.CoreBrowserService,this.textarea,e.ownerDocument.defaultView??window,this._document??("undefined"!=typeof window?window.document:null))),this._instantiationService.setService(C.ICoreBrowserService,this._coreBrowserService),this._register((0,H.addDisposableListener)(this.textarea,"focus",e=>this._handleTextAreaFocus(e))),this._register((0,H.addDisposableListener)(this.textarea,"blur",()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(p.CharSizeService,this._document,this._helperContainer),this._instantiationService.setService(C.ICharSizeService,this._charSizeService),this._themeService=this._instantiationService.createInstance(k.ThemeService),this._instantiationService.setService(C.IThemeService,this._themeService),this._register(this._inputHandler.onRequestColorSchemeQuery(()=>this._reportColorScheme())),this._register(this._themeService.onChangeColors(()=>{this.coreService.decPrivateModes.colorSchemeUpdates&&this._reportColorScheme()})),this._characterJoinerService=this._instantiationService.createInstance(v.CharacterJoinerService),this._instantiationService.setService(C.ICharacterJoinerService,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(w.RenderService,this.rows,this.screenElement)),this._instantiationService.setService(C.IRenderService,this._renderService),this._register(this._renderService.onRenderedViewportChange(e=>this._onRender.fire(e))),this._register(this._renderService.onDimensionsChange(e=>this._onDimensionsChange.fire({css:{canvas:{...e.css.canvas},cell:{...e.css.cell}},device:{canvas:{...e.device.canvas},cell:{...e.device.cell},char:{...e.device.char}}}))),this.onResize(e=>this._renderService.resize(e.cols,e.rows)),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(u.CompositionHelper,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseCoordsService=this._instantiationService.createInstance(S.MouseCoordsService),this._instantiationService.setService(C.IMouseCoordsService,this._mouseCoordsService);const s=this._linkifier.value=this._register(this._instantiationService.createInstance(O.Linkifier,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch(e){this._logService.error("onWillOpen handler threw an exception",e)}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this._register(this.onResize(()=>{this._renderService.handleResize(this.cols,this.rows),this._syncTextArea()})),this._register(this.onBlur(()=>this._renderService.handleBlur())),this._register(this.onFocus(()=>this._renderService.handleFocus())),this._viewport=this._register(this._instantiationService.createInstance(c.Viewport,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(e=>{super.scrollLines(e,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(y.SelectionService,this.element,this.screenElement,s)),this._instantiationService.setService(C.ISelectionService,this._selectionService),this._mouseService=this._instantiationService.createInstance(b.MouseService),this._instantiationService.setService(C.IMouseService,this._mouseService),this._register(this._selectionService.onRequestScrollLines(e=>this.scrollLines(e.amount,e.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(e=>this._renderService.handleSelectionChanged(e.start,e.end,e.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(e=>{this.textarea.value=e,this.textarea.focus(),this.textarea.select()})),this._register(I.EventUtils.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(d.BufferDecorationRenderer,this.screenElement)),this._register((0,H.addDisposableListener)(this.element,"mousedown",e=>this._selectionService.handleMouseDown(e))),this.mouseStateService.areMouseEventsActive&&!this.options.mouseEventsRequireAlt?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):(this._selectionService.enable(),this.element.classList.remove("enable-mouse-events")),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(P.AccessibilityManager,this)),this._register(this.optionsService.onSpecificOptionChange("screenReaderMode",e=>this._handleScreenReaderModeOptionChange(e)));const r=this.options.scrollbar?.showScrollbar??!0,o=this.options.scrollbar?.width;r&&o&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(_.OverviewRulerRenderer,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("scrollbar",e=>{const t=(e?.showScrollbar??!0)&&!!e?.width;!this._overviewRulerRenderer&&t&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(_.OverviewRulerRenderer,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this._mouseService.bindMouse({element:this.element,screenElement:this.screenElement,document:this._document,handleTouchScroll:e=>this._viewport?.handleTouchScroll(e)},e=>this._register(e),()=>this.focus())}_createRenderer(){return this._instantiationService.createInstance(f.DomRenderer,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}refresh(e,t,i=!1){this._renderService?.refreshRows(e,t,i)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){const t=e-this._bufferService.buffer.ydisp;0!==t&&this.scrollLines(t)}paste(e){(0,a.paste)(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this.mouseStateService.setCustomWheelEventHandler(e)}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");const t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return!!this._selectionService&&this._selectionService.hasSelection}select(e,t,i){this._selectionService.setSelection(e,t,i)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(this._selectionService&&this._selectionService.hasSelection)return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;const t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;t||"Dead"!==e.key&&"AltGraph"!==e.key||(this._unprocessedDeadKey=!0);const i=this._keyboardService.evaluateKeyDown(e);if(this.updateCursorStyle(e),3===i.type||2===i.type){const t=this.rows-1;return this.scrollLines(2===i.type?-t:t),e.preventDefault(),e.stopPropagation(),!1}if(1===i.type&&this.selectAll(),this._isThirdLevelShift(this.browser,e))return!0;if(i.cancel&&(e.preventDefault(),e.stopPropagation()),!i.key)return!0;if(!this._keyboardService.useKitty&&!this._keyboardService.useWin32InputMode&&e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&1===e.key.length&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)return!0;if(this._unprocessedDeadKey)return this._unprocessedDeadKey=!1,!0;""!==i.key&&"\r"!==i.key||(this.textarea.value="");const s=this._keyboardService.useWin32InputMode&&W(e);if(this._onKey.fire({key:i.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(i.key,!s),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return e.preventDefault(),e.stopPropagation(),!1;this._keyDownHandled=!0}_isThirdLevelShift(e,t){const i=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState("AltGraph");return"keypress"===t.type?i:i&&(!t.keyCode||t.keyCode>47)}_keyUp(e){if(this._keyDownSeen=!1,this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return;W(e)||this.focus();const t=this._keyboardService.evaluateKeyUp(e);if(t?.key){const i=this._keyboardService.useWin32InputMode&&W(e);this.coreService.triggerDataEvent(t.key,!i)}this.updateCursorStyle(e),this._keyPressHandled=!1}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled)return!1;if(this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;if(e.charCode)t=e.charCode;else if(null===e.which||void 0===e.which)t=e.keyCode;else{if(0===e.which||0===e.charCode)return!1;t=e.which}return!(!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)||(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,0))}_inputEvent(e){if(e.data&&"insertText"===e.inputType&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;const t=e.data;return this.coreService.triggerDataEvent(t,!0),!0}return!1}resize(e,t){e!==this.cols||t!==this.rows?super.resize(e,t):this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure()}_afterResize(e,t){this._charSizeService?.measure()}clear(){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;efunction(e){const t=l(e);for(t.animFrameRequested=!1,t.current=t.next,t.next=[],t.inAnimationFrameRunner=!0;t.current.length>0;)t.current.sort(a.sort),t.current.shift().execute();t.inAnimationFrameRunner=!1}(e))),r};const s=i(3132);function r(e){const t=e;if(t?.ownerDocument?.defaultView)return t.ownerDocument.defaultView;const i=e;return i?.view?i.view:window}class o{constructor(e,t,i,s){this._node=e,this._type=t,this._handler=i,this._options=s,e.addEventListener(t,i,s)}dispose(){this._node&&this._handler&&(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,this._handler=null)}}function n(e,t,i,s){return new o(e,t,i,s)}t.eventType={CLICK:"click",MOUSE_DOWN:"mousedown",MOUSE_OVER:"mouseover",MOUSE_LEAVE:"mouseleave",KEY_DOWN:"keydown",KEY_UP:"keyup",INPUT:"input",BLUR:"blur",FOCUS:"focus",CHANGE:"change",POINTER_DOWN:"pointerdown",POINTER_MOVE:"pointermove",POINTER_UP:"pointerup",MOUSE_WHEEL:"wheel",WHEEL:"wheel"};class a{constructor(e,t){this._runner=e,this.priority=t,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(e){console.error(e)}}static sort(e,t){return t.priority-e.priority}}const h=new Map;function l(e){let t=h.get(e);return t||(t={next:[],current:[],animFrameRequested:!1,inAnimationFrameRunner:!1},h.set(e,t)),t}class c extends s.IntervalTimer{constructor(e){super(),this._defaultTarget=e?r(e):void 0}cancelAndSet(e,t,i){super.cancelAndSet(e,t,i??this._defaultTarget??window)}}t.WindowIntervalTimer=c},8906(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Linkifier=void 0;const o=i(4812),n=i(6501),a=i(7098),h=i(8636),l=i(4159);let c=class extends o.Disposable{get currentLink(){return this._currentLink}constructor(e,t,i,s,r){super(),this._element=e,this._mouseCoordsService=t,this._renderService=i,this._bufferService=s,this._linkProviderService=r,this._linkCacheDisposables=[],this._isMouseOut=!0,this._wasResized=!1,this._activeLine=-1,this._onShowLinkUnderline=this._register(new h.Emitter),this.onShowLinkUnderline=this._onShowLinkUnderline.event,this._onHideLinkUnderline=this._register(new h.Emitter),this.onHideLinkUnderline=this._onHideLinkUnderline.event,this._register((0,o.toDisposable)(()=>{(0,o.dispose)(this._linkCacheDisposables),this._linkCacheDisposables.length=0,this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()})),this._register(this._bufferService.onResize(()=>{this._clearCurrentLink(),this._wasResized=!0})),this._register((0,l.addDisposableListener)(this._element,"mouseleave",()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this._register((0,l.addDisposableListener)(this._element,"mousemove",this._handleMouseMove.bind(this))),this._register((0,l.addDisposableListener)(this._element,"mousedown",this._handleMouseDown.bind(this))),this._register((0,l.addDisposableListener)(this._element,"mouseup",this._handleMouseUp.bind(this)))}_handleMouseMove(e){this._lastMouseEvent=e;const t=this._positionFromMouseEvent(e,this._element);if(!t)return;this._isMouseOut=!1;const i=e.composedPath();for(let e=0;e{e?.forEach(e=>{e.link.dispose&&e.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=e.y);let i=!1;for(const[s,r]of this._linkProviderService.linkProviders.entries())if(t){const t=this._activeProviderReplies?.get(s);t&&(i=this._checkLinkProviderResult(s,e,i))}else r.provideLinks(e.y,t=>{if(this._isMouseOut)return;const r=t?.map(e=>({link:e}));this._activeProviderReplies?.set(s,r),i=this._checkLinkProviderResult(s,e,i),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)})}_removeIntersectingLinks(e,t){const i=new Set;for(let s=0;se?this._bufferService.cols:s.link.range.end.x;for(let e=o;e<=n;e++){if(i.has(e)){r.splice(t--,1);break}i.add(e)}}}}_checkLinkProviderResult(e,t,i){if(!this._activeProviderReplies)return i;const s=this._activeProviderReplies.get(e);let r=!1;for(let t=0;tthis._linkAtPosition(e.link,t));e&&(i=!0,this._handleNewLink(e))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!i)for(let e=0;ethis._linkAtPosition(e.link,t));if(s){i=!0,this._handleNewLink(s);break}}return i}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;const t=this._positionFromMouseEvent(e,this._element);var i,s;t&&this._mouseDownLink&&(i=this._mouseDownLink.link,s=this._currentLink.link,i.text===s.text&&i.range.start.x===s.range.start.x&&i.range.start.y===s.range.start.y&&i.range.end.x===s.range.end.x&&i.range.end.y===s.range.end.y)&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){this._currentLink&&this._lastMouseEvent&&(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,(0,o.dispose)(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;const t=this._positionFromMouseEvent(this._lastMouseEvent,this._element);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:void 0===e.link.decorations||e.link.decorations.underline,pointerCursor:void 0===e.link.decorations||e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:e=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==e&&(this._currentLink.state.decorations.pointerCursor=e,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",e))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:t=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==t&&(this._currentLink.state.decorations.underline=t,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,t))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e=>{if(!this._currentLink)return;const t=0===e.start?0:e.start+1+this._bufferService.buffer.ydisp,i=this._bufferService.buffer.ydisp+1+e.end;if(this._currentLink.link.range.start.y>=t&&this._currentLink.link.range.end.y<=i&&(this._clearCurrentLink(t,i),this._lastMouseEvent)){const e=this._positionFromMouseEvent(this._lastMouseEvent,this._element);e&&this._askForLink(e,!1)}})))}_linkHover(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(i,t.text)}_fireUnderlineEvent(e,t){const i=e.range,s=this._bufferService.buffer.ydisp,r=this._createLinkUnderlineEvent(i.start.x-1,i.start.y-s-1,i.end.x,i.end.y-s-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(r)}_linkLeave(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(i,t.text)}_linkAtPosition(e,t){const i=e.range.start.y*this._bufferService.cols+e.range.start.x,s=e.range.end.y*this._bufferService.cols+e.range.end.x,r=t.y*this._bufferService.cols+t.x;return i<=r&&r<=s}_positionFromMouseEvent(e,t){const i=this._mouseCoordsService.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(i)return{x:i[0],y:i[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,i,s,r){return{x1:e,y1:t,x2:i,y2:s,cols:this._bufferService.cols,fg:r}}};t.Linkifier=c,t.Linkifier=c=s([r(1,a.IMouseCoordsService),r(2,a.IRenderService),r(3,n.IBufferService),r(4,a.ILinkProviderService)],c)},7721(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.tooMuchOutput=t.promptLabel=void 0;let i="Terminal input";const s={get:()=>i,set:e=>i=e};t.promptLabel=s;let r="Too much output to announce, navigate to rows manually to read";const o={get:()=>r,set:e=>r=e};t.tooMuchOutput=o},3285(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OscLinkProvider=void 0;const o=i(3055),n=i(6501);let a=class{constructor(e,t,i){this._bufferService=e,this._optionsService=t,this._oscLinkService=i,this._workCell=new o.CellData}provideLinks(e,t){const i=this._bufferService.buffer.lines.get(e-1);if(!i)return void t(void 0);const s=[],r=this._optionsService.rawOptions.linkHandler,o=this._workCell,n=i.getTrimmedLength();let a=-1,l=-1,c=!1;for(let t=0;tr?r.activate(e,t,d):h(0,t),hover:(e,t)=>r?.hover?.(e,t,d),leave:(e,t)=>r?.leave?.(e,t,d)})}c=!1,o.hasExtendedAttrs()&&o.extended.urlId?(l=t,a=o.extended.urlId):(l=-1,a=-1)}}t(s)}_getRangeWithLineWrap(e,t,i,s){let r=e,o=t,n=e,a=i;for(;0===o;){const e=this._bufferService.buffer.lines.get(r-1);if(!e?.isWrapped)break;const t=this._bufferService.buffer.lines.get(r-2);if(!t)break;const i=t.getTrimmedLength();if(0===i||!this._hasUrlId(t,i-1,s))break;let n=i-1;for(;n>0&&this._hasUrlId(t,n-1,s);)n--;r--,o=n}for(;;){const e=this._bufferService.buffer.lines.get(n-1);if(!e)break;if(a!==e.getTrimmedLength())break;const t=this._bufferService.buffer.lines.get(n);if(!t?.isWrapped)break;const i=t.getTrimmedLength();if(0===i||!this._hasUrlId(t,0,s))break;let r=1;for(;rthis._innerRefresh()),this._animationFrame}refresh(e,t,i){this._rowCount=i,e=e??0,t=t??this._rowCount-1,this._rowStart=void 0!==this._rowStart?Math.min(this._rowStart,e):e,this._rowEnd=void 0!==this._rowEnd?Math.max(this._rowEnd,t):t,void 0===this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._animationFrame=void 0,void 0===this._rowStart||void 0===this._rowEnd||void 0===this._rowCount)return void this._runRefreshCallbacks();const e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(const e of this._refreshCallbacks)e(0);this._refreshCallbacks=[]}}},4292(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.TimeBasedDebouncer=void 0,t.TimeBasedDebouncer=class{constructor(e,t=1e3){this._renderCallback=e,this._debounceThresholdMS=t,this._lastRefreshMs=0,this._additionalRefreshRequested=!1}dispose(){this._refreshTimeoutID&&(clearTimeout(this._refreshTimeoutID),this._refreshTimeoutID=void 0),this._additionalRefreshRequested=!1}refresh(e,t,i){this._rowCount=i,e=e??0,t=t??this._rowCount-1,this._rowStart=void 0!==this._rowStart?Math.min(this._rowStart,e):e,this._rowEnd=void 0!==this._rowEnd?Math.max(this._rowEnd,t):t;const s=performance.now();if(s-this._lastRefreshMs>=this._debounceThresholdMS)void 0!==this._refreshTimeoutID&&(clearTimeout(this._refreshTimeoutID),this._refreshTimeoutID=void 0,this._additionalRefreshRequested=!1),this._lastRefreshMs=s,this._innerRefresh();else if(!this._additionalRefreshRequested){const e=s-this._lastRefreshMs,t=this._debounceThresholdMS-e;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout(()=>{this._lastRefreshMs=performance.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0},t)}}_innerRefresh(){if(void 0===this._rowStart||void 0===this._rowEnd||void 0===this._rowCount)return;const e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t)}}},9302(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DEFAULT_ANSI_COLORS=void 0;const s=i(4103);t.DEFAULT_ANSI_COLORS=Object.freeze((()=>{const e=[s.css.toColor("#2e3436"),s.css.toColor("#cc0000"),s.css.toColor("#4e9a06"),s.css.toColor("#c4a000"),s.css.toColor("#3465a4"),s.css.toColor("#75507b"),s.css.toColor("#06989a"),s.css.toColor("#d3d7cf"),s.css.toColor("#555753"),s.css.toColor("#ef2929"),s.css.toColor("#8ae234"),s.css.toColor("#fce94f"),s.css.toColor("#729fcf"),s.css.toColor("#ad7fa8"),s.css.toColor("#34e2e2"),s.css.toColor("#eeeeec")],t=[0,95,135,175,215,255];for(let i=0;i<216;i++){const r=t[i/36%6|0],o=t[i/6%6|0],n=t[i%6];e.push({css:s.channels.toCss(r,o,n),rgba:s.channels.toRgba(r,o,n)})}for(let t=0;t<24;t++){const i=8+10*t;e.push({css:s.channels.toCss(i,i,i),rgba:s.channels.toRgba(i,i,i)})}return e})())},4017(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Viewport=void 0;const o=i(7098),n=i(4812),a=i(6501),h=i(4159),l=i(8566),c=i(8636),d=i(7880);let _=class extends n.Disposable{constructor(e,t,i,s,r,o,a,_,u){super(),this._bufferService=i,this._coreService=r,this._optionsService=_,this._renderService=u,this._onRequestScrollLines=this._register(new c.Emitter),this.onRequestScrollLines=this._onRequestScrollLines.event,this._isSyncing=!1,this._isHandlingScroll=!1,this._suppressOnScrollHandler=!1,this._needsSyncOnRender=!1;const f=this._register(new d.Scrollable({forceIntegerValues:!1,smoothScrollDuration:this._optionsService.rawOptions.smoothScrollDuration,scheduleAtNextAnimationFrame:e=>(0,h.scheduleAtNextAnimationFrame)(s.window,e)}));this._register(this._optionsService.onSpecificOptionChange("smoothScrollDuration",()=>{f.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration)})),this._scrollableElement=this._register(new l.SmoothScrollableElement(t,{vertical:1,horizontal:2,useShadows:!1,mouseWheelSmoothScroll:!0,verticalHasArrows:this._optionsService.rawOptions.scrollbar?.showArrows??!1,...this._getChangeOptions()},f)),this._register(this._optionsService.onMultipleOptionChange(["scrollSensitivity","fastScrollSensitivity","scrollbar"],()=>this._scrollableElement.updateOptions(this._getChangeOptions()))),this._register(o.onProtocolChange(e=>{this._scrollableElement.updateOptions({handleMouseWheel:!(16&e)})})),this._scrollableElement.setScrollDimensions({height:0,scrollHeight:0}),this._register(c.EventUtils.runAndSubscribe(a.onChangeColors,()=>{e.style.backgroundColor=a.colors.background.css,this._scrollableElement.getDomNode().style.backgroundColor=a.colors.background.css})),e.appendChild(this._scrollableElement.getDomNode()),this._register((0,n.toDisposable)(()=>this._scrollableElement.getDomNode().remove())),this._styleElement=s.mainDocument.createElement("style"),t.appendChild(this._styleElement),this._register((0,n.toDisposable)(()=>this._styleElement.remove())),this._register(c.EventUtils.runAndSubscribe(a.onChangeColors,()=>{this._styleElement.textContent=[".xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider {",` background: ${a.colors.scrollbarSliderBackground.css};`,"}",".xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider:hover {",` background: ${a.colors.scrollbarSliderHoverBackground.css};`,"}",".xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider.xterm-active {",` background: ${a.colors.scrollbarSliderActiveBackground.css};`,"}"].join("\n")})),this._register(this._bufferService.onResize(()=>this.queueSync())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._latestYDisp=void 0,this.queueSync()})),this._register(this._bufferService.onScroll(()=>this._sync())),this._register(this._renderService.onRender(()=>{this._needsSyncOnRender&&(this._needsSyncOnRender=!1,this._sync())})),this._register(this._scrollableElement.onScroll(e=>this._handleScroll(e)))}scrollLines(e){const t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:t.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,t){t&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!t,scrollTop:e*this._renderService.dimensions.css.cell.height})}_getChangeOptions(){const e=this._optionsService.rawOptions.scrollbar?.showScrollbar??!0,t=this._optionsService.rawOptions.scrollbar?.showArrows??!1,i=e?this._optionsService.rawOptions.scrollbar?.width??14:0;return{mouseWheelScrollSensitivity:this._optionsService.rawOptions.scrollSensitivity,fastScrollSensitivity:this._optionsService.rawOptions.fastScrollSensitivity,vertical:e?1:2,verticalScrollbarSize:i,verticalHasArrows:t}}queueSync(e){void 0!==e&&(this._latestYDisp=e),void 0===this._queuedAnimationFrame&&(this._queuedAnimationFrame=this._renderService.addRefreshCallback(()=>{this._queuedAnimationFrame=void 0,this._sync(this._latestYDisp)}))}_sync(e=this._bufferService.buffer.ydisp){this._renderService&&!this._isSyncing&&(this._coreService.decPrivateModes.synchronizedOutput?this._needsSyncOnRender=!0:(this._isSyncing=!0,this._suppressOnScrollHandler=!0,this._scrollableElement.setScrollDimensions({height:this._renderService.dimensions.css.canvas.height,scrollHeight:this._renderService.dimensions.css.cell.height*this._bufferService.buffer.lines.length}),this._suppressOnScrollHandler=!1,e!==this._latestYDisp&&this._scrollableElement.setScrollPosition({scrollTop:e*this._renderService.dimensions.css.cell.height}),this._isSyncing=!1))}_handleScroll(e){if(!this._renderService)return;if(this._isHandlingScroll||this._suppressOnScrollHandler)return;this._isHandlingScroll=!0;const t=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),i=t-this._bufferService.buffer.ydisp;0!==i&&(this._latestYDisp=t,this._onRequestScrollLines.fire(i)),this._isHandlingScroll=!1}handleTouchScroll(e){const t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({scrollTop:t.scrollTop-e})}};t.Viewport=_,t.Viewport=_=s([r(2,a.IBufferService),r(3,o.ICoreBrowserService),r(4,a.ICoreService),r(5,a.IMouseStateService),r(6,o.IThemeService),r(7,a.IOptionsService),r(8,o.IRenderService)],_)},4196(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.BufferDecorationRenderer=void 0;const o=i(7098),n=i(4812),a=i(6501);let h=class extends n.Disposable{constructor(e,t,i,s,r){super(),this._screenElement=e,this._bufferService=t,this._coreBrowserService=i,this._decorationService=s,this._renderService=r,this._decorationElements=new Map,this._altBufferIsActive=!1,this._dimensionsChanged=!1,this._container=document.createElement("div"),this._container.classList.add("xterm-decoration-container"),this._screenElement.appendChild(this._container),this._register(this._renderService.onRenderedViewportChange(()=>this._doRefreshDecorations())),this._register(this._renderService.onDimensionsChange(()=>{this._dimensionsChanged=!0,this._queueRefresh()})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt})),this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh())),this._register(this._decorationService.onDecorationRemoved(e=>this._removeDecoration(e))),this._register((0,n.toDisposable)(()=>{this._container.remove(),this._decorationElements.clear()}))}_queueRefresh(){void 0===this._animationFrame&&(this._animationFrame=this._renderService.addRefreshCallback(()=>{this._doRefreshDecorations(),this._animationFrame=void 0}))}_doRefreshDecorations(){for(const e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){const t=this._coreBrowserService.mainDocument.createElement("div");t.classList.add("xterm-decoration"),t.classList.toggle("xterm-decoration-top-layer","top"===e?.options?.layer),t.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,t.style.height=(e.options.height||1)*this._renderService.dimensions.css.cell.height+"px",t.style.top=(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height+"px",t.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;const i=e.options.x??0;return i&&i>this._bufferService.cols&&(t.style.display="none"),this._refreshXPosition(e,t),t}_refreshStyle(e){const t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display="none",e.onRenderEmitter.fire(e.element));else{let i=this._decorationElements.get(e);i||(i=this._createElement(e),e.element=i,this._decorationElements.set(e,i),this._container.appendChild(i),e.onDispose(()=>{this._decorationElements.delete(e),i.remove()})),i.style.display=this._altBufferIsActive?"none":"block",this._altBufferIsActive||(i.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,i.style.height=(e.options.height||1)*this._renderService.dimensions.css.cell.height+"px",i.style.top=t*this._renderService.dimensions.css.cell.height+"px",i.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(i)}}_refreshXPosition(e,t=e.element){if(!t)return;const i=e.options.x??0;"right"===(e.options.anchor||"left")?t.style.right=i?i*this._renderService.dimensions.css.cell.width+"px":"":t.style.left=i?i*this._renderService.dimensions.css.cell.width+"px":""}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};t.BufferDecorationRenderer=h,t.BufferDecorationRenderer=h=s([r(1,a.IBufferService),r(2,o.ICoreBrowserService),r(3,a.IDecorationService),r(4,o.IRenderService)],h)},957(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ColorZoneStore=void 0,t.ColorZoneStore=class{constructor(){this._zones=[],this._zonePool=[],this._zonePoolIndex=0,this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(e){if(e.options.overviewRulerOptions){for(const t of this._zones)if(t.color===e.options.overviewRulerOptions.color&&t.position===e.options.overviewRulerOptions.position){if(this._lineIntersectsZone(t,e.marker.line))return;if(this._lineAdjacentToZone(t,e.marker.line,e.options.overviewRulerOptions.position))return void this._addLineToZone(t,e.marker.line)}if(this._zonePoolIndex=e.startBufferLine&&t<=e.endBufferLine}_lineAdjacentToZone(e,t,i){return t>=e.startBufferLine-this._linePadding[i||"full"]&&t<=e.endBufferLine+this._linePadding[i||"full"]}_addLineToZone(e,t){e.startBufferLine=Math.min(e.startBufferLine,t),e.endBufferLine=Math.max(e.endBufferLine,t)}}},9925(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OverviewRulerRenderer=void 0;const o=i(957),n=i(7098),a=i(4812),h=i(6501),l={full:0,left:0,center:0,right:0},c={full:0,left:0,center:0,right:0},d={full:0,left:0,center:0,right:0};let _=class extends a.Disposable{get _width(){const e=this._optionsService.rawOptions.scrollbar;return e?.showScrollbar??1?e?.width??0:0}constructor(e,t,i,s,r,n,h,l){super(),this._viewportElement=e,this._screenElement=t,this._bufferService=i,this._decorationService=s,this._renderService=r,this._optionsService=n,this._themeService=h,this._coreBrowserService=l,this._colorZoneStore=new o.ColorZoneStore,this._shouldUpdateDimensions=!0,this._shouldUpdateAnchor=!0,this._lastKnownBufferLength=0,this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add("xterm-decoration-overview-ruler"),this._refreshCanvasDimensions(),this._viewportElement.parentElement?.insertBefore(this._canvas,this._viewportElement),this._register((0,a.toDisposable)(()=>this._canvas?.remove()));const c=this._canvas.getContext("2d");if(!c)throw new Error("Ctx cannot be null");this._ctx=c,this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh(void 0,!0))),this._register(this._decorationService.onDecorationRemoved(()=>this._queueRefresh(void 0,!0))),this._register(this._renderService.onRenderedViewportChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?"none":"block"})),this._register(this._bufferService.onScroll(()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())})),this._register(this._renderService.onDimensionsChange(()=>this._queueRefresh(!0))),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh(!0))),this._register(this._optionsService.onSpecificOptionChange("scrollbar",()=>this._queueRefresh(!0))),this._register(this._themeService.onChangeColors(()=>this._queueRefresh())),this._register((0,a.toDisposable)(()=>{void 0!==this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)})),this._queueRefresh(!0)}_refreshDrawConstants(){const e=Math.floor((this._canvas.width-1)/3),t=Math.ceil((this._canvas.width-1)/3);c.full=this._canvas.width,c.left=e,c.center=t,c.right=e,this._refreshDrawHeightConstants(),d.full=1,d.left=1,d.center=1+c.left,d.right=1+c.left+c.center}_refreshDrawHeightConstants(){l.full=Math.round(2*this._coreBrowserService.dpr);const e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);l.left=t,l.center=t,l.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){if(this._store.isDisposed||!this._renderService.hasRenderer())return;const e=this._renderService.dimensions.css.canvas.height,t=this._renderService.dimensions.device.canvas.height;this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${e}px`,this._canvas.height=t,this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){if(this._store.isDisposed||!this._renderService.hasRenderer())return;this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(const e of this._decorationService.decorations)this._colorZoneStore.addDecoration(e);this._ctx.lineWidth=1,this._renderRulerOutline();const e=this._colorZoneStore.zones;for(const t of e)"full"!==t.position&&this._renderColorZone(t);for(const t of e)"full"===t.position&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderRulerOutline(){this._ctx.fillStyle=this._themeService.colors.overviewRulerBorder.css,this._ctx.fillRect(0,0,1,this._canvas.height),this._optionsService.rawOptions.scrollbar?.overviewRuler?.showTopBorder&&this._ctx.fillRect(1,0,this._canvas.width-1,1),this._optionsService.rawOptions.scrollbar?.overviewRuler?.showBottomBorder&&this._ctx.fillRect(1,this._canvas.height-1,this._canvas.width-1,this._canvas.height)}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect(d[e.position||"full"],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-l[e.position||"full"]/2),c[e.position||"full"],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+l[e.position||"full"]))}_queueRefresh(e,t){this._store.isDisposed||(this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,void 0===this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._store.isDisposed||this._refreshDecorations(),this._animationFrame=void 0})))}};t.OverviewRulerRenderer=_,t.OverviewRulerRenderer=_=s([r(2,h.IBufferService),r(3,h.IDecorationService),r(4,n.IRenderService),r(5,h.IOptionsService),r(6,n.IThemeService),r(7,n.ICoreBrowserService)],_)},3618(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CompositionHelper=void 0;const o=i(7098),n=i(6501);let a=class{get isComposing(){return this._isComposing}constructor(e,t,i,s,r,o){this._textarea=e,this._compositionView=t,this._bufferService=i,this._optionsService=s,this._coreService=r,this._renderService=o,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._compositionSuffix="",this._dataAlreadySent=""}compositionstart(){this._isComposing=!0;const e=this._textarea.selectionStart??this._textarea.value.length,t=this._textarea.selectionEnd??e;this._compositionPosition.start=Math.min(e,t),this._compositionPosition.end=Math.max(e,t),this._compositionSuffix=this._textarea.value.substring(this._compositionPosition.end),this._compositionView.textContent="",this._dataAlreadySent="",this._compositionView.classList.add("active")}compositionupdate(e){this._compositionView.textContent=`‎${e.data}‎`,this.updateCompositionElements(),setTimeout(()=>{const e=this._textarea.selectionEnd??this._textarea.value.length;this._compositionPosition.end=Math.max(this._compositionPosition.start,e)},0)}compositionend(){this._finalizeComposition(!0)}keydown(e){if(this._isComposing||this._isSendingComposition){if(20===e.keyCode||229===e.keyCode)return!1;if(16===e.keyCode||17===e.keyCode||18===e.keyCode)return!1;this._finalizeComposition(!1)}return 229!==e.keyCode||(this._handleAnyTextareaChanges(),!1)}_finalizeComposition(e){if(this._compositionView.classList.remove("active"),this._isComposing=!1,e){const e={start:this._compositionPosition.start,end:this._compositionPosition.end},t=this._compositionSuffix;this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){let i;if(this._isSendingComposition=!1,e.start+=this._dataAlreadySent.length,this._isComposing)i=this._textarea.value.substring(e.start,this._compositionPosition.start);else{const s=this._textarea.value,r=t.length>0&&s.endsWith(t)?s.length-t.length:s.length;i=s.substring(e.start,Math.max(e.start,r))}i.length>0&&this._coreService.triggerDataEvent(i,!0)}},0)}else{this._isSendingComposition=!1;const e=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(e,!0)}}_handleAnyTextareaChanges(){if(this._textareaChangeTimer)return;const e=this._textarea.value;this._textareaChangeTimer=window.setTimeout(()=>{if(this._textareaChangeTimer=void 0,!this._isComposing){const t=this._textarea.value,i=t.replace(e,"");this._dataAlreadySent=i,t.length>e.length?this._coreService.triggerDataEvent(i,!0):t.lengththis.updateCompositionElements(!0),0)}}};t.CompositionHelper=a,t.CompositionHelper=a=s([r(2,n.IBufferService),r(3,n.IOptionsService),r(4,n.ICoreService),r(5,o.IRenderService)],a)},5251(e,t){function i(e,t,i){const s=i.getBoundingClientRect(),r=e.getComputedStyle(i),o=parseInt(r.getPropertyValue("padding-left"),10),n=parseInt(r.getPropertyValue("padding-top"),10);return[t.clientX-s.left-o,t.clientY-s.top-n]}Object.defineProperty(t,"__esModule",{value:!0}),t.getCoordsRelativeToElement=i,t.getCoords=function(e,t,s,r,o,n,a,h,l){if(!n)return;const c=i(e,t,s);return c[0]=Math.ceil((c[0]+(l?a/2:0))/a),c[1]=Math.ceil(c[1]/h),c[0]=Math.min(Math.max(c[0],1),r+(l?1:0)),c[1]=Math.min(Math.max(c[1],1),o),c}},9686(e,t){function i(e,t,i,o){const h=e-s(e,i),l=t-s(t,i),c=Math.abs(h-l)-function(e,t,i){let o=0;const n=e-s(e,i),a=t-s(t,i);for(let s=0;s=0&&et?"A":"B"}function o(e,t,i,s,r,o){let n=e,a=t,h="";for(;(n!==i||a!==s)&&a>=0&&ao.cols-1?(h+=o.buffer.translateBufferLineToString(a,!1,e,n),n=0,e=0,a++):!r&&n<0&&(h+=o.buffer.translateBufferLineToString(a,!1,0,e+1),n=o.cols-1,e=n,a--);return h+o.buffer.translateBufferLineToString(a,!1,e,n)}function n(e,t){return""+(t?"O":"[")+e}function a(e,t){e=Math.floor(e);let i="";for(let s=0;s0?h-s(h,l):t;const _=h,u=function(e,t,r,o,n,a){let h;return h=i(t,o,n,a).length>0?o-s(o,n):t,e=r&&he?"D":"C",a(Math.abs(l-e),n(d,h));d=c>t?"D":"C";const _=Math.abs(c-t);return a(function(e,t){return t.cols-e}(c>t?e:l,r)+(_-1)*r.cols+1+((c>t?l:e)-1),n(d,h))}},6081(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._core.options[e],i=(e,t)=>{this._checkReadonlyOptions(e),this._core.options[e]=t};for(const e in this._core.options){const s={get:t.bind(this,e),set:i.bind(this,e)};Object.defineProperty(this._publicOptions,e,s)}}_checkReadonlyOptions(e){if(f.includes(e))throw new Error(`Option "${e}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get onDimensionsChange(){return this._core.onDimensionsChange}get element(){return this._core.element}get screenElement(){return this._core.screenElement}get parser(){return this._parser??=new _.ParserApi(this._core)}get unicode(){return this._checkProposedApi(),new u.UnicodeApi(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer??=this._register(new d.BufferNamespaceApi(this._core))}get markers(){return this._core.markers}get modes(){const e=this._core.coreService.decPrivateModes;let t="none";switch(this._core.mouseStateService.activeProtocol){case"X10":t="x10";break;case"VT200":t="vt200";break;case"DRAG":t="drag";break;case"ANY":t="any"}return{applicationCursorKeysMode:e.applicationCursorKeys,applicationKeypadMode:e.applicationKeypad,bracketedPasteMode:e.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:t,originMode:e.origin,reverseWraparoundMode:e.reverseWraparound,sendFocusMode:e.sendFocus,showCursor:!this._core.coreService.isCursorHidden,synchronizedOutputMode:e.synchronizedOutput,win32InputMode:e.win32InputMode,wraparoundMode:e.wraparound}}get dimensions(){return this._core.dimensions}get options(){return this._publicOptions}set options(e){for(const t in e)this._publicOptions[t]=e[t]}blur(){this._core.blur()}focus(){this._core.focus()}input(e,t=!0){this._core.input(e,t)}resize(e,t){this._verifyIntegers(e,t),this._core.resize(e,t)}open(e){this._core.open(e)}attachCustomKeyEventHandler(e){this._core.attachCustomKeyEventHandler(e)}attachCustomWheelEventHandler(e){this._core.attachCustomWheelEventHandler(e)}registerLinkProvider(e){return this._core.registerLinkProvider(e)}registerCharacterJoiner(e){return this._core.registerCharacterJoiner(e)}deregisterCharacterJoiner(e){this._core.deregisterCharacterJoiner(e)}registerMarker(e=0){return this._verifyIntegers(e),this._core.registerMarker(e)}registerDecoration(e){return this._verifyPositiveIntegers(e.x??0,e.width??0,e.height??0),this._core.registerDecoration(e)}hasSelection(){return this._core.hasSelection()}select(e,t,i){this._verifyIntegers(e,t,i),this._core.select(e,t,i)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(e,t){this._verifyIntegers(e,t),this._core.selectLines(e,t)}dispose(){super.dispose()}scrollLines(e){this._verifyIntegers(e),this._core.scrollLines(e)}scrollPages(e){this._verifyIntegers(e),this._core.scrollPages(e)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(e){this._verifyIntegers(e),this._core.scrollToLine(e)}clear(){this._core.clear()}write(e,t){this._core.write(e,t)}writeln(e,t){this._core.write(e),this._core.write("\r\n",t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){this._addonManager.loadAddon(this,e)}static get strings(){return{get promptLabel(){return a.promptLabel.get()},set promptLabel(e){a.promptLabel.set(e)},get tooMuchOutput(){return a.tooMuchOutput.get()},set tooMuchOutput(e){a.tooMuchOutput.set(e)}}}_verifyIntegers(...e){for(p of e)if(p===1/0||isNaN(p)||p%1!=0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...e){for(p of e)if(p&&(p===1/0||isNaN(p)||p%1!=0||p<0))throw new Error("This API only accepts positive integers")}}t.Terminal=v},3955(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DomRenderer=void 0;const o=i(1433),n=i(2744),a=i(9176),h=i(6181),l=i(2274),c=i(654),d=i(7098),_=i(4103),u=i(4812),f=i(6501),p=i(8636),v=i(4159);let g=1,m=class extends u.Disposable{constructor(e,t,i,s,r,a,d,_,f,m,b,w,y,C){super(),this._terminal=e,this._document=t,this._element=i,this._screenElement=s,this._viewportElement=r,this._helperContainer=a,this._linkifier2=d,this._charSizeService=f,this._optionsService=m,this._bufferService=b,this._coreService=w,this._coreBrowserService=y,this._themeService=C,this._terminalClass=g++,this._rowElements=[],this._selectionRenderModel=(0,l.createSelectionRenderModel)(),this._lastSelectionColumnMode=!1,this._rowHasBlinkingCells=[],this._rowHasBlinkingCellsCount=0,this._onRequestRedraw=this._register(new p.Emitter),this.onRequestRedraw=this._onRequestRedraw.event,this._rowContainer=this._document.createElement("div"),this._rowContainer.classList.add("xterm-rows"),this._rowContainer.style.lineHeight="normal",this._rowContainer.setAttribute("aria-hidden","true"),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement("div"),this._selectionContainer.classList.add("xterm-selection"),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=(0,h.createRenderDimensions)(),this._updateDimensions(),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._themeService.onChangeColors(e=>this._injectCss(e))),this._injectCss(this._themeService.colors),this._rowFactory=_.createInstance(o.DomRendererRowFactory,document),this._element.classList.add("xterm-dom-renderer-owner-"+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline(e=>this._handleLinkHover(e))),this._register(this._linkifier2.onHideLinkUnderline(e=>this._handleLinkLeave(e))),this._cursorBlinkStateManager=new S(this._rowContainer,this._coreBrowserService),this._register((0,v.addDisposableListener)(this._document,"mousedown",()=>this._cursorBlinkStateManager.restartBlinkAnimation())),this._register((0,u.toDisposable)(()=>this._cursorBlinkStateManager.dispose())),this._textBlinkStateManager=this._register(new c.TextBlinkStateManager(()=>this._onRequestRedraw.fire({start:0,end:this._bufferService.rows-1}),this._coreBrowserService,this._optionsService)),this._register((0,u.toDisposable)(()=>{this._element.classList.remove("xterm-dom-renderer-owner-"+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new n.WidthCache,this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){const e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(const e of this._rowElements)e.style.width=`${this.dimensions.css.canvas.width}px`,e.style.height=`${this.dimensions.css.cell.height}px`,e.style.lineHeight=`${this.dimensions.css.cell.height}px`,e.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));const t=`${this._terminalSelector} .xterm-rows span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .xterm-rows { pointer-events: none; color: ${e.foreground.css};}`;t+=`${this._terminalSelector} .xterm-rows, ${this._terminalSelector} .xterm-rows span { font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`,t+=`${this._terminalSelector} .xterm-rows .xterm-dim { color: ${_.color.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}${this._terminalSelector} span.xterm-blink-hidden { visibility: hidden;}`;const i=`blink_underline_${this._terminalClass}`,s=`blink_bar_${this._terminalClass}`,r=`blink_block_${this._terminalClass}`;t+=`@keyframes ${i} { 50% { border-bottom-style: hidden; }}`,t+=`@keyframes ${s} { 50% { box-shadow: none; }}`,t+=`@keyframes ${r} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${i} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${s} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-cursor-blink-idle .xterm-cursor.xterm-cursor-blink { animation: none !important;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .xterm-selection { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .xterm-selection div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .xterm-selection div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(const[i,s]of e.ansi.entries())t+=`${this._terminalSelector} .xterm-fg-${i} { color: ${s.css}; }${this._terminalSelector} .xterm-fg-${i}.xterm-dim { color: ${_.color.multiplyOpacity(s,.5).css}; }${this._terminalSelector} .xterm-bg-${i} { background-color: ${s.css}; }`;t+=`${this._terminalSelector} .xterm-fg-${a.INVERTED_DEFAULT_COLOR} { color: ${_.color.opaque(e.background).css}; }${this._terminalSelector} .xterm-fg-${a.INVERTED_DEFAULT_COLOR}.xterm-dim { color: ${_.color.multiplyOpacity(_.color.opaque(e.background),.5).css}; }${this._terminalSelector} .xterm-bg-${a.INVERTED_DEFAULT_COLOR} { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){const e=this.dimensions.css.cell.width-this._widthCache.get("W",!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let e=this._rowElements.length;e<=t;e++){const e=this._document.createElement("div");this._rowContainer.appendChild(e),this._rowElements.push(e),this._rowHasBlinkingCells.push(!1)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop()),this._rowHasBlinkingCells.pop()&&this._rowHasBlinkingCellsCount--}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove("xterm-focus"),this._cursorBlinkStateManager.pause(),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add("xterm-focus"),this._cursorBlinkStateManager.resume(),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleViewportVisibilityChange(e){this._textBlinkStateManager.setViewportVisible(e)}handleSelectionChanged(e,t,i){const s=this._bufferService.rows;this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,i);let r=0,o=-1;this._lastSelectionStart&&this._lastSelectionEnd&&(this._selectionRenderModel.update(this._terminal,this._lastSelectionStart,this._lastSelectionEnd,this._lastSelectionColumnMode),this._selectionRenderModel.hasSelection&&(r=this._selectionRenderModel.viewportCappedStartRow,o=this._selectionRenderModel.viewportCappedEndRow));let n=0,a=-1;if(!e||!t)return;if(this._selectionRenderModel.update(this._terminal,e,t,i),this._selectionRenderModel.hasSelection){const s=this._selectionRenderModel.viewportStartRow,r=this._selectionRenderModel.viewportEndRow,o=this._selectionRenderModel.viewportCappedStartRow,h=this._selectionRenderModel.viewportCappedEndRow;n=o,a=h;const l=this._document.createDocumentFragment();if(i){const i=e[0]>t[0];l.appendChild(this._createSelectionElement(o,i?t[0]:e[0],i?e[0]:t[0],h-o+1))}else{const i=s===o?e[0]:0,n=o===r?t[0]:this._bufferService.cols;l.appendChild(this._createSelectionElement(o,i,n));const a=h-o-1;if(l.appendChild(this._createSelectionElement(o+1,0,this._bufferService.cols,a)),o!==h){const e=r===h?t[0]:this._bufferService.cols;l.appendChild(this._createSelectionElement(h,0,e))}}this._selectionContainer.appendChild(l)}let h=Math.min(r,n),l=Math.max(o,a);if(l>=0){h=Math.max(h,0),l=Math.min(l,s-1);const e=this._bufferService.buffer.y;this._selectionRenderModel.hasSelection&&e>=0&&ethis.dimensions.css.canvas.width&&(n=this.dimensions.css.canvas.width-o),r.style.height=s*this.dimensions.css.cell.height+"px",r.style.top=e*this.dimensions.css.cell.height+"px",r.style.left=`${o}px`,r.style.width=`${n}px`,r}handleCursorMove(){this._cursorBlinkStateManager.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(const e of this._rowElements)e.replaceChildren();this._rowHasBlinkingCellsCount>0&&(this._rowHasBlinkingCells.fill(!1),this._rowHasBlinkingCellsCount=0,this._textBlinkStateManager.setNeedsBlinkInViewport(!1))}renderRows(e,t){const i=this._bufferService.buffer,s=i.ybase+i.y,r=Math.min(i.x,this._bufferService.cols-1),o=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,n=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,a=this._optionsService.rawOptions.cursorInactiveStyle,h={hasBlinkingCells:!1};for(let l=e;l<=t;l++){const e=l+i.ydisp,t=this._rowElements[l];if(!t)continue;const c=i.lines.get(e);c?(t.replaceChildren(...this._rowFactory.createRow(c,e,e===s,n,a,r,o,this._textBlinkStateManager.isBlinkOn,this.dimensions.css.cell.width,this._widthCache,-1,-1,h)),this._setRowBlinkState(l,h.hasBlinkingCells)):(t.replaceChildren(),this._setRowBlinkState(l,!1))}this._updateTextBlinkState()}get _terminalSelector(){return`.xterm-dom-renderer-owner-${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,i,s,r,o){i<0&&(e=0),s<0&&(t=0);const n=this._bufferService.rows-1;i=Math.max(Math.min(i,n),0),s=Math.max(Math.min(s,n),0),r=Math.min(r,this._bufferService.cols);const a=this._bufferService.buffer,h=a.ybase+a.y,l=Math.min(a.x,r-1),c=this._optionsService.rawOptions.cursorBlink,d=this._optionsService.rawOptions.cursorStyle,_=this._optionsService.rawOptions.cursorInactiveStyle,u={hasBlinkingCells:!1};for(let n=i;n<=s;++n){const f=n+a.ydisp,p=this._rowElements[n];if(!p)continue;const v=a.lines.get(f);v?(p.replaceChildren(...this._rowFactory.createRow(v,f,f===h,d,_,l,c,this._textBlinkStateManager.isBlinkOn,this.dimensions.css.cell.width,this._widthCache,o?n===i?e:0:-1,o?(n===s?t:r)-1:-1,u)),this._setRowBlinkState(n,u.hasBlinkingCells)):(p.replaceChildren(),this._setRowBlinkState(n,!1))}this._updateTextBlinkState()}_setRowBlinkState(e,t){this._rowHasBlinkingCells[e]!==t&&(this._rowHasBlinkingCells[e]=t,this._rowHasBlinkingCellsCount+=t?1:-1)}_updateTextBlinkState(){this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount>0)}};t.DomRenderer=m,t.DomRenderer=m=s([r(7,f.IInstantiationService),r(8,d.ICharSizeService),r(9,f.IOptionsService),r(10,f.IBufferService),r(11,f.ICoreService),r(12,d.ICoreBrowserService),r(13,d.IThemeService)],m);class S{constructor(e,t){this._rowContainer=e,this._coreBrowserService=t,this._isIdlePaused=!1,this._coreBrowserService.isFocused&&this._resetIdleTimer()}dispose(){this._clearIdleTimer()}restartBlinkAnimation(){this._isIdlePaused&&this._rowContainer.classList.remove("xterm-cursor-blink-idle"),this._resetIdleTimer()}pause(){this._isIdlePaused=!1,this._clearIdleTimer()}resume(){this._isIdlePaused=!1,this._rowContainer.classList.remove("xterm-cursor-blink-idle"),this._resetIdleTimer()}_resetIdleTimer(){this._isIdlePaused=!1,this._clearIdleTimer(),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_clearIdleTimer(){void 0!==this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}_stopBlinkingDueToIdle(){this._rowContainer.classList.add("xterm-cursor-blink-idle"),this._isIdlePaused=!0,this._idleTimeout=void 0}}},1433(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DomRendererRowFactory=void 0;const o=i(9176),n=i(8938),a=i(3055),h=i(6501),l=i(4103),c=i(7098),d=i(945),_=i(6181),u=i(5451);let f=class{constructor(e,t,i,s,r,o,n){this._document=e,this._characterJoinerService=t,this._optionsService=i,this._coreBrowserService=s,this._coreService=r,this._decorationService=o,this._themeService=n,this._workCell=new a.CellData,this._columnSelectMode=!1,this.defaultSpacing=0}handleSelectionChanged(e,t,i){this._selectionStart=e,this._selectionEnd=t,this._columnSelectMode=i}createRow(e,t,i,s,r,a,h,c,_,f,p,v,g){const m=[];g&&(g.hasBlinkingCells=!1);const S=this._characterJoinerService.getJoinedCharacters(t),b=this._themeService.colors;let w,y=e.getNoBgTrimmedLength();i&&y=A,F=I,W=this._workCell;if(S.length>0&&I===S[0][0]&&N){const s=S.shift(),r=this._isCellInSelection(s[0],t);for(C=s[0]+1;C=s[1],N?(H=!0,W=new d.JoinedCellData(this._workCell,e.translateToString(!0,s[0],s[1]),s[1]-s[0]),F=s[1]-1,y=W.getWidth()):A=s[1]}const z=this._isCellInSelection(I,t),K=i&&I===a,U=O&&I>=p&&I<=v;g&&W.isBlink()&&(g.hasBlinkingCells=!0),!c&&W.isBlink()&&P.push("xterm-blink-hidden");let j=!1;this._decorationService.forEachDecorationAtCell(I,t,void 0,e=>{j=!0});let $=W.getChars()||n.WHITESPACE_CELL_CHAR;if(" "===$&&(W.isUnderline()||W.isOverline())&&($=" "),k=y*_-f.get($,W.isBold(),W.isItalic()),w){if(D&&(z&&T||!z&&!T&&W.bg===L)&&(z&&T&&b.selectionForeground||W.fg===x)&&W.extended.ext===R&&U===M&&k===B&&!K&&!H&&!j&&N){W.isInvisible()?E+=n.WHITESPACE_CELL_CHAR:E+=$,D++;continue}D&&(w.textContent=E),w=this._document.createElement("span"),D=0,E=""}else w=this._document.createElement("span");if(L=W.bg,x=W.fg,R=W.extended.ext,M=U,B=k,T=z,H&&a>=I&&a<=F&&(a=I),!this._coreService.isCursorHidden&&K&&this._coreService.isCursorInitialized)if(P.push("xterm-cursor"),this._coreBrowserService.isFocused)h&&P.push("xterm-cursor-blink"),P.push("bar"===s?"xterm-cursor-bar":"underline"===s?"xterm-cursor-underline":"xterm-cursor-block");else if(r)switch(r){case"outline":P.push("xterm-cursor-outline");break;case"block":P.push("xterm-cursor-block");break;case"bar":P.push("xterm-cursor-bar");break;case"underline":P.push("xterm-cursor-underline")}if(W.isBold()&&P.push("xterm-bold"),W.isItalic()&&P.push("xterm-italic"),W.isDim()&&P.push("xterm-dim"),E=W.isInvisible()?n.WHITESPACE_CELL_CHAR:W.getChars()||n.WHITESPACE_CELL_CHAR,W.isUnderline()&&(P.push(`xterm-underline-${W.extended.underlineStyle}`)," "===E&&(E=" "),!W.isUnderlineColorDefault()))if(W.isUnderlineColorRGB())w.style.textDecorationColor=`rgb(${u.AttributeData.toColorRGB(W.getUnderlineColor()).join(",")})`;else{let e=W.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&W.isBold()&&e<8&&(e+=8),w.style.textDecorationColor=b.ansi[e].css}W.isOverline()&&(P.push("xterm-overline")," "===E&&(E=" ")),W.isStrikethrough()&&P.push("xterm-strikethrough"),U&&(w.style.textDecoration="underline");let q=W.getFgColor(),V=W.getFgColorMode(),X=W.getBgColor(),Y=W.getBgColorMode();const G=!!W.isInverse();if(G){const e=q;q=X,X=e;const t=V;V=Y,Y=t}let J,Z,Q,ee=!1;switch(this._decorationService.forEachDecorationAtCell(I,t,void 0,e=>{"top"!==e.options.layer&&ee||(e.backgroundColorRGB&&(Y=50331648,X=e.backgroundColorRGB.rgba>>8&16777215,J=e.backgroundColorRGB),e.foregroundColorRGB&&(V=50331648,q=e.foregroundColorRGB.rgba>>8&16777215,Z=e.foregroundColorRGB),ee="top"===e.options.layer)}),!ee&&z&&(J=this._coreBrowserService.isFocused?b.selectionBackgroundOpaque:b.selectionInactiveBackgroundOpaque,X=J.rgba>>8&16777215,Y=50331648,ee=!0,b.selectionForeground&&(V=50331648,q=b.selectionForeground.rgba>>8&16777215,Z=b.selectionForeground)),ee&&P.push("xterm-decoration-top"),Y){case 16777216:case 33554432:Q=b.ansi[X],P.push(`xterm-bg-${X}`);break;case 50331648:Q=l.channels.toColor(X>>16,X>>8&255,255&X),this._addStyle(w,`background-color:#${(X>>>0).toString(16).padStart(6,"0")}`);break;default:G?(Q=b.foreground,P.push(`xterm-bg-${o.INVERTED_DEFAULT_COLOR}`)):Q=b.background}switch(J||W.isDim()&&(J=l.color.multiplyOpacity(Q,.5)),V){case 16777216:case 33554432:W.isBold()&&q<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(q+=8),this._applyMinimumContrast(w,Q,b.ansi[q],W,J,void 0)||P.push(`xterm-fg-${q}`);break;case 50331648:const e=l.channels.toColor(q>>16&255,q>>8&255,255&q);this._applyMinimumContrast(w,Q,e,W,J,Z)||this._addStyle(w,`color:#${q.toString(16).padStart(6,"0")}`);break;default:this._applyMinimumContrast(w,Q,b.foreground,W,J,Z)||G&&P.push(`xterm-fg-${o.INVERTED_DEFAULT_COLOR}`)}P.length&&(w.className=P.join(" "),P.length=0),K||H||j||!N?w.textContent=E:D++,k!==this.defaultSpacing&&(w.style.letterSpacing=`${k}px`),m.push(w),I=F}return w&&D&&(w.textContent=E),m}_applyMinimumContrast(e,t,i,s,r,o){if(1===this._optionsService.rawOptions.minimumContrastRatio||(0,_.treatGlyphAsBackgroundColor)(s.getCode()))return!1;const n=this._getContrastCache(s);let a;if(r||o||(a=n.getColor(t.rgba,i.rgba)),void 0===a){const e=this._optionsService.rawOptions.minimumContrastRatio/(s.isDim()?2:1);a=l.color.ensureContrastRatio(r??t,o??i,e),n.setColor((r??t).rgba,(o??i).rgba,a??null)}return!!a&&(this._addStyle(e,`color:${a.css}`),!0)}_getContrastCache(e){return e.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(e,t){e.setAttribute("style",`${e.getAttribute("style")||""}${t};`)}_isCellInSelection(e,t){const i=this._selectionStart,s=this._selectionEnd;return!(!i||!s)&&(this._columnSelectMode?i[0]<=s[0]?e>=i[0]&&t>=i[1]&&e=i[1]&&e>=s[0]&&t<=s[1]:t>i[1]&&t=i[0]&&e=i[0])}};t.DomRendererRowFactory=f,t.DomRendererRowFactory=f=s([r(1,c.ICharacterJoinerService),r(2,h.IOptionsService),r(3,c.ICoreBrowserService),r(4,h.ICoreService),r(5,h.IDecorationService),r(6,c.IThemeService)],f)},2744(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.WidthCache=void 0;const s=i(6181);t.WidthCache=class{constructor(e=()=>new r){this._flat=new Float32Array(256),this._font="",this._fontSize=0,this._weight="normal",this._weightBold="bold",this._canvasElements=[],this._canvasElements=[e(),e(),e(),e()],this.clear()}dispose(){this._canvasElements.length=0,this._holey=void 0}clear(){this._flat.fill(-9999),this._holey=new Map}setFont(e,t,i,s){e===this._font&&t===this._fontSize&&i===this._weight&&s===this._weightBold||(this._font=e,this._fontSize=t,this._weight=i,this._weightBold=s,this._canvasElements[0].setFont(e,t,i,!1),this._canvasElements[1].setFont(e,t,s,!1),this._canvasElements[2].setFont(e,t,i,!0),this._canvasElements[3].setFont(e,t,s,!0),this.clear())}get(e,t,i){let s;if(!t&&!i&&1===e.length&&(s=e.charCodeAt(0))<256){if(-9999!==this._flat[s])return this._flat[s];const t=this._measure(e,0);return t>0&&(this._flat[s]=t),t}let r=e;t&&(r+="B"),i&&(r+="I");let o=this._holey.get(r);if(void 0===o){let s=0;t&&(s|=1),i&&(s|=2),o=this._measure(e,s),o>0&&this._holey.set(r,o)}return o}_measure(e,t){return this._canvasElements[t].measure(e)}};class r{constructor(){"undefined"!=typeof OffscreenCanvas?(this._canvas=new OffscreenCanvas(1,1),this._ctx=(0,s.throwIfFalsy)(this._canvas.getContext("2d"))):(this._canvas=document.createElement("canvas"),this._canvas.width=1,this._canvas.height=1,this._ctx=(0,s.throwIfFalsy)(this._canvas.getContext("2d")))}setFont(e,t,i,s){const r=s?"italic":"";this._ctx.font=`${r} ${i} ${t}px ${e}`.trim()}measure(e){return this._ctx.measureText(e).width}}},9176(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.INVERTED_DEFAULT_COLOR=void 0,t.INVERTED_DEFAULT_COLOR=257},6181(e,t){function i(e){return 57508<=e&&e<=57558}function s(e){return e>=128512&&e<=128591||e>=127744&&e<=128511||e>=128640&&e<=128767||e>=9728&&e<=9983||e>=9984&&e<=10175||e>=65024&&e<=65039||e>=129280&&e<=129535||e>=127462&&e<=127487}Object.defineProperty(t,"__esModule",{value:!0}),t.throwIfFalsy=function(e){if(!e)throw new Error("value must not be falsy");return e},t.isPowerlineGlyph=i,t.isRestrictedPowerlineGlyph=function(e){return 57520<=e&&e<=57527},t.isEmoji=s,t.allowRescaling=function(e,t,r,o){return 1===t&&r>Math.ceil(1.5*o)&&void 0!==e&&e>255&&!s(e)&&!i(e)&&!function(e){return 57344<=e&&e<=63743}(e)},t.treatGlyphAsBackgroundColor=function(e){return i(e)||function(e){return 9472<=e&&e<=9631}(e)},t.createRenderDimensions=function(){return{css:{canvas:{width:0,height:0},cell:{width:0,height:0}},device:{canvas:{width:0,height:0},cell:{width:0,height:0},char:{width:0,height:0,left:0,top:0}}}},t.computeNextVariantOffset=function(e,t,i=0){return(e-(2*Math.round(t)-i))%(2*Math.round(t))}},2274(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.createSelectionRenderModel=function(){return new i};class i{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(e,t,i,s=!1){if(this.selectionStart=t,this.selectionEnd=i,!t||!i||t[0]===i[0]&&t[1]===i[1])return void this.clear();const r=e.buffers.active.ydisp,o=t[1]-r,n=i[1]-r,a=Math.max(o,0),h=Math.min(n,e.rows-1);a>=e.rows||h<0?this.clear():(this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=o,this.viewportEndRow=n,this.viewportCappedStartRow=a,this.viewportCappedEndRow=h,this.startCol=t[0],this.endCol=i[0])}isCellSelected(e,t,i){return!!this.hasSelection&&(i-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&i>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&i<=this.viewportCappedEndRow:i>this.viewportStartRow&&i=this.startCol&&t=this.startCol)}}},654(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.TextBlinkStateManager=void 0;const s=i(4812);class r extends s.Disposable{constructor(e,t,i){super(),this._renderCallback=e,this._coreBrowserService=t,this._optionsService=i,this._intervalDuration=0,this._blinkOn=!0,this._needsBlinkInViewport=!1,this._isViewportVisible=!0,this._register(this._optionsService.onSpecificOptionChange("blinkIntervalDuration",e=>{this.setIntervalDuration(e)})),this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration),this._register((0,s.toDisposable)(()=>this._clearInterval()))}get isBlinkOn(){return this._blinkOn}get isEnabled(){return this._intervalDuration>0}setNeedsBlinkInViewport(e){this._needsBlinkInViewport!==e&&(this._needsBlinkInViewport=e,this._updateIntervalState())}setViewportVisible(e){this._isViewportVisible!==e&&(this._isViewportVisible=e,this._updateIntervalState())}setIntervalDuration(e){e!==this._intervalDuration&&(this._intervalDuration=e,this._clearInterval(),this._updateIntervalState())}_updateIntervalState(){if(this._intervalDuration>0&&this._needsBlinkInViewport&&this._isViewportVisible){if(void 0!==this._interval)return;const e=this._blinkOn;return this._blinkOn=!0,this._interval=this._coreBrowserService.window.setInterval(()=>{this._blinkOn=!this._blinkOn,this._renderCallback()},this._intervalDuration),void(e||this._renderCallback())}this._clearInterval(),this._blinkOn||(this._blinkOn=!0,this._renderCallback())}_clearInterval(){void 0!==this._interval&&(this._coreBrowserService.window.clearInterval(this._interval),this._interval=void 0)}}t.TextBlinkStateManager=r},8501(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._domNodePointerDown(e)))}_createArrow(e){const t=this._register(new c.ScrollbarArrow(e));return this.domNode.domNode.appendChild(t.bgDomNode),this.domNode.domNode.appendChild(t.domNode),t}_createSlider(e,t,i,s){this.slider=new h.FastDomNode(document.createElement("div")),this.slider.setClassName("xterm-slider"),this.slider.setPosition("absolute"),this.slider.setTop(e),this.slider.setLeft(t),"number"==typeof i&&this.slider.setWidth(i),"number"==typeof s&&this.slider.setHeight(s),this.slider.setLayerHinting(!0),this.slider.setContain("strict"),this.domNode.domNode.appendChild(this.slider.domNode),this._register(a.addDisposableListener(this.slider.domNode,a.eventType.POINTER_DOWN,e=>{0===e.button&&(e.preventDefault(),this._sliderPointerDown(e))})),this._onclick(this.slider.domNode,e=>{e.leftButton&&e.stopPropagation()})}_handleElementSize(e){return this._scrollbarState.setVisibleSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_handleElementScrollSize(e){return this._scrollbarState.setScrollSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_handleElementScrollPosition(e){return this._scrollbarState.setScrollPosition(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}beginReveal(){this._visibilityController.setShouldBeVisible(!0)}beginHide(){this._visibilityController.setShouldBeVisible(!1)}render(){this._shouldRender&&(this._shouldRender=!1,this._renderDomNode(this._scrollbarState.getRectangleLargeSize(),this._scrollbarState.getRectangleSmallSize()),this._updateSlider(this._scrollbarState.getSliderSize(),this._scrollbarState.getArrowSize()+this._scrollbarState.getSliderPosition()))}_domNodePointerDown(e){e.target===this.domNode.domNode&&this._handlePointerDown(e)}delegatePointerDown(e){const t=this.domNode.domNode.getClientRects()[0].top,i=t+this._scrollbarState.getSliderPosition(),s=t+this._scrollbarState.getSliderPosition()+this._scrollbarState.getSliderSize(),r=this._sliderPointerPosition(e);i<=r&&r<=s?0===e.button&&(e.preventDefault(),this._sliderPointerDown(e)):this._handlePointerDown(e)}_handlePointerDown(e){let t,i;if(e.target===this.domNode.domNode&&"number"==typeof e.offsetX&&"number"==typeof e.offsetY)t=e.offsetX,i=e.offsetY;else{const s=a.getDomNodePagePosition(this.domNode.domNode);t=e.pageX-s.left,i=e.pageY-s.top}const s=this._pointerDownRelativePosition(t,i);this._setDesiredScrollPositionNow(this._scrollByPage?this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(s):this._scrollbarState.getDesiredScrollPositionFromOffset(s)),0===e.button&&(e.preventDefault(),this._sliderPointerDown(e))}_sliderPointerDown(e){if(!(e.target&&e.target instanceof Element))return;const t=this._sliderPointerPosition(e),i=this._sliderOrthogonalPointerPosition(e),s=this._scrollbarState.clone();this.slider.toggleClassName("xterm-active",!0),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,e=>{const r=this._sliderOrthogonalPointerPosition(e),o=Math.abs(r-i);if(u.isWindows&&o>140)return void this._setDesiredScrollPositionNow(s.getScrollPosition());const n=this._sliderPointerPosition(e)-t;this._setDesiredScrollPositionNow(s.getDesiredScrollPositionFromDelta(n))},()=>{this.slider.toggleClassName("xterm-active",!1),this._host.handleDragEnd()}),this._host.handleDragStart()}_setDesiredScrollPositionNow(e){const t={};this.writeScrollPosition(t,e),this._scrollable.setScrollPositionNow(t)}updateScrollbarSize(e){this._updateScrollbarSize(e),this._scrollbarState.setScrollbarSize(e),this._shouldRender=!0,this._lazyRender||this.render()}isNeeded(){return this._scrollbarState.isNeeded()}}t.AbstractScrollbar=f},1203(e,t){function i(e){return"number"==typeof e?`${e}px`:e}Object.defineProperty(t,"__esModule",{value:!0}),t.FastDomNode=void 0,t.FastDomNode=class{constructor(e){this.domNode=e,this._width="",this._height="",this._top="",this._left="",this._bottom="",this._right="",this._className="",this._position="",this._layerHint=!1,this._contain="none"}setWidth(e){const t=i(e);this._width!==t&&(this._width=t,this.domNode.style.width=this._width)}setHeight(e){const t=i(e);this._height!==t&&(this._height=t,this.domNode.style.height=this._height)}setTop(e){const t=i(e);this._top!==t&&(this._top=t,this.domNode.style.top=this._top)}setLeft(e){const t=i(e);this._left!==t&&(this._left=t,this.domNode.style.left=this._left)}setBottom(e){const t=i(e);this._bottom!==t&&(this._bottom=t,this.domNode.style.bottom=this._bottom)}setRight(e){const t=i(e);this._right!==t&&(this._right=t,this.domNode.style.right=this._right)}setClassName(e){this._className!==e&&(this._className=e,this.domNode.className=this._className)}toggleClassName(e,t){this.domNode.classList.toggle(e,t),this._className=this.domNode.className}setPosition(e){this._position!==e&&(this._position=e,this.domNode.style.position=this._position)}setLayerHinting(e){this._layerHint!==e&&(this._layerHint=e,this.domNode.style.transform=e?"translate3d(0px, 0px, 0px)":"")}setContain(e){this._contain!==e&&(this._contain=e,this.domNode.style.contain=this._contain)}setAttribute(e,t){this.domNode.setAttribute(e,t)}}},928(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;n{try{e.releasePointerCapture(t)}catch{}}))}catch{o=a.getWindow(e)}this._hooks.add(a.addDisposableListener(o,a.eventType.POINTER_MOVE,e=>{e.buttons===i?(e.preventDefault(),this._pointerMoveCallback(e)):this.stopMonitoring(!0)})),this._hooks.add(a.addDisposableListener(o,a.eventType.POINTER_UP,e=>this.stopMonitoring(!0)))}}},9699(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.HorizontalScrollbar=void 0;const s=i(8501),r=i(1270);class o extends s.AbstractScrollbar{constructor(e,t,i){const s=e.getScrollDimensions(),o=e.getCurrentScrollPosition();if(super({lazyRender:t.lazyRender,host:i,scrollbarState:new r.ScrollbarState(t.horizontalHasArrows?t.horizontalScrollbarSize:0,2===t.horizontal?0:t.horizontalScrollbarSize,2===t.vertical?0:t.verticalScrollbarSize,s.width,s.scrollWidth,o.scrollLeft),visibility:t.horizontal,extraScrollbarClassName:"xterm-horizontal",scrollable:e,scrollByPage:t.scrollByPage}),t.horizontalHasArrows)throw new Error("horizontalHasArrows is not supported in xterm.js");this._createSlider(Math.floor((t.horizontalScrollbarSize-t.horizontalSliderSize)/2),0,void 0,t.horizontalSliderSize)}_updateSlider(e,t){this.slider.setWidth(e),this.slider.setLeft(t)}_renderDomNode(e,t){this.domNode.setWidth(e),this.domNode.setHeight(t),this.domNode.setLeft(0),this.domNode.setBottom(0)}handleScroll(e){return this._shouldRender=this._handleElementScrollSize(e.scrollWidth)||this._shouldRender,this._shouldRender=this._handleElementScrollPosition(e.scrollLeft)||this._shouldRender,this._shouldRender=this._handleElementSize(e.width)||this._shouldRender,this._shouldRender}_pointerDownRelativePosition(e,t){return e}_sliderPointerPosition(e){return e.pageX}_sliderOrthogonalPointerPosition(e){return e.pageY}_updateScrollbarSize(e){this.slider.setHeight(e)}writeScrollPosition(e,t){e.scrollLeft=t}updateOptions(e){this.updateScrollbarSize(2===e.horizontal?0:e.horizontalScrollbarSize),this._scrollbarState.setOppositeScrollbarSize(2===e.vertical?0:e.verticalScrollbarSize),this._visibilityController.setVisibility(e.horizontal),this._scrollByPage=e.scrollByPage}}t.HorizontalScrollbar=o},3988(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;ni&&(s=i-t),s<0&&(s=0),r<0&&(r=0),n+r>o&&(n=o-r),n<0&&(n=0),this.width=t,this.scrollWidth=i,this.scrollLeft=s,this.height=r,this.scrollHeight=o,this.scrollTop=n}equals(e){return this.rawScrollLeft===e.rawScrollLeft&&this.rawScrollTop===e.rawScrollTop&&this.width===e.width&&this.scrollWidth===e.scrollWidth&&this.scrollLeft===e.scrollLeft&&this.height===e.height&&this.scrollHeight===e.scrollHeight&&this.scrollTop===e.scrollTop}withScrollDimensions(e,t){return new o(this._forceIntegerValues,void 0!==e.width?e.width:this.width,void 0!==e.scrollWidth?e.scrollWidth:this.scrollWidth,t?this.rawScrollLeft:this.scrollLeft,void 0!==e.height?e.height:this.height,void 0!==e.scrollHeight?e.scrollHeight:this.scrollHeight,t?this.rawScrollTop:this.scrollTop)}withScrollPosition(e){return new o(this._forceIntegerValues,this.width,this.scrollWidth,void 0!==e.scrollLeft?e.scrollLeft:this.rawScrollLeft,this.height,this.scrollHeight,void 0!==e.scrollTop?e.scrollTop:this.rawScrollTop)}createScrollEvent(e,t){const i=this.width!==e.width,s=this.scrollWidth!==e.scrollWidth,r=this.scrollLeft!==e.scrollLeft,o=this.height!==e.height,n=this.scrollHeight!==e.scrollHeight,a=this.scrollTop!==e.scrollTop;return{inSmoothScrolling:t,oldWidth:e.width,oldScrollWidth:e.scrollWidth,oldScrollLeft:e.scrollLeft,width:this.width,scrollWidth:this.scrollWidth,scrollLeft:this.scrollLeft,oldHeight:e.height,oldScrollHeight:e.scrollHeight,oldScrollTop:e.scrollTop,height:this.height,scrollHeight:this.scrollHeight,scrollTop:this.scrollTop,widthChanged:i,scrollWidthChanged:s,scrollLeftChanged:r,heightChanged:o,scrollHeightChanged:n,scrollTopChanged:a}}}t.ScrollState=o;class n extends r.Disposable{constructor(e){super(),this._scrollableBrand=void 0,this._onScroll=this._register(new s.Emitter),this.onScroll=this._onScroll.event,this._smoothScrollDuration=e.smoothScrollDuration,this._scheduleAtNextAnimationFrame=e.scheduleAtNextAnimationFrame,this._state=new o(e.forceIntegerValues,0,0,0,0,0,0),this._smoothScrolling=null}dispose(){this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),super.dispose()}setSmoothScrollDuration(e){this._smoothScrollDuration=e}validateScrollPosition(e){return this._state.withScrollPosition(e)}getScrollDimensions(){return this._state}setScrollDimensions(e,t){const i=this._state.withScrollDimensions(e,t);this._setState(i,Boolean(this._smoothScrolling)),this._smoothScrolling?.acceptScrollDimensions(this._state)}getFutureScrollPosition(){return this._smoothScrolling?this._smoothScrolling.to:this._state}getCurrentScrollPosition(){return this._state}setScrollPositionNow(e){const t=this._state.withScrollPosition(e);this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),this._setState(t,!1)}setScrollPositionSmooth(e,t){if(0!==this._smoothScrollDuration){if(this._smoothScrolling){e={scrollLeft:void 0===e.scrollLeft?this._smoothScrolling.to.scrollLeft:e.scrollLeft,scrollTop:void 0===e.scrollTop?this._smoothScrolling.to.scrollTop:e.scrollTop};const i=this._state.withScrollPosition(e);if(this._smoothScrolling.to.scrollLeft===i.scrollLeft&&this._smoothScrolling.to.scrollTop===i.scrollTop)return;let s;s=t?new l(this._smoothScrolling.from,i,this._smoothScrolling.startTime,this._smoothScrolling.duration):l.start(this._state,i,this._smoothScrollDuration),this._smoothScrolling.dispose(),this._smoothScrolling=s}else{const t=this._state.withScrollPosition(e);this._smoothScrolling=l.start(this._state,t,this._smoothScrollDuration)}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}else this.setScrollPositionNow(e)}hasPendingScrollAnimation(){return Boolean(this._smoothScrolling)}_performSmoothScrolling(){if(!this._smoothScrolling)return;const e=this._smoothScrolling.tick(),t=this._state.withScrollPosition(e);return this._setState(t,!0),this._smoothScrolling?e.isDone?(this._smoothScrolling.dispose(),void(this._smoothScrolling=null)):void(this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})):void 0}_setState(e,t){const i=this._state;i.equals(e)||(this._state=e,this._onScroll.fire(this._state.createScrollEvent(i,t)))}}t.Scrollable=n;class a{constructor(e,t,i){this.scrollLeft=e,this.scrollTop=t,this.isDone=i}}function h(e,t){const i=t-e;return function(t){return e+i*(1-(s=1-t,Math.pow(s,3)));var s}}class l{constructor(e,t,i,s){this.from=e,this.to=t,this.duration=s,this.startTime=i,this.animationFrameDisposable=null,this._initAnimations()}_initAnimations(){this._scrollLeft=this._initAnimation(this.from.scrollLeft,this.to.scrollLeft,this.to.width),this._scrollTop=this._initAnimation(this.from.scrollTop,this.to.scrollTop,this.to.height)}_initAnimation(e,t,i){if(Math.abs(e-t)>2.5*i){let n,a;return e0&&Math.abs(e.deltaY)>0)return 1;let i=.5;if(this._isAlmostInt(e.deltaX)&&this._isAlmostInt(e.deltaY)||(i+=.25),t){const s=Math.abs(e.deltaX),r=Math.abs(e.deltaY),o=Math.abs(t.deltaX),n=Math.abs(t.deltaY),a=Math.max(Math.min(s,o),1),h=Math.max(Math.min(r,n),1),l=Math.max(s,o),c=Math.max(r,n);l%a===0&&c%h===0&&(i-=.5)}return Math.min(Math.max(i,0),1)}_isAlmostInt(e){return Math.abs(Math.round(e)-e)<.01}}S.INSTANCE=new S;class b extends _.Widget{get options(){return this._options}constructor(e,t,i){let s;super(),this._onScroll=this._register(new f.Emitter),this.onScroll=this._onScroll.event,t=t??{};const r=!i;i?s=i:(t.mouseWheelSmoothScroll=!1,s=new g.Scrollable({forceIntegerValues:!0,smoothScrollDuration:0,scheduleAtNextAnimationFrame:t=>a.scheduleAtNextAnimationFrame(a.getWindow(e),t)})),this._options=function(e){const t={lazyRender:void 0!==e.lazyRender&&e.lazyRender,className:void 0!==e.className?e.className:"",useShadows:void 0===e.useShadows||e.useShadows,handleMouseWheel:void 0===e.handleMouseWheel||e.handleMouseWheel,flipAxes:void 0!==e.flipAxes&&e.flipAxes,consumeMouseWheelIfScrollbarIsNeeded:void 0!==e.consumeMouseWheelIfScrollbarIsNeeded&&e.consumeMouseWheelIfScrollbarIsNeeded,alwaysConsumeMouseWheel:void 0!==e.alwaysConsumeMouseWheel&&e.alwaysConsumeMouseWheel,scrollYToX:void 0!==e.scrollYToX&&e.scrollYToX,mouseWheelScrollSensitivity:void 0!==e.mouseWheelScrollSensitivity?e.mouseWheelScrollSensitivity:1,fastScrollSensitivity:void 0!==e.fastScrollSensitivity?e.fastScrollSensitivity:5,scrollPredominantAxis:void 0===e.scrollPredominantAxis||e.scrollPredominantAxis,mouseWheelSmoothScroll:void 0===e.mouseWheelSmoothScroll||e.mouseWheelSmoothScroll,listenOnDomNode:void 0!==e.listenOnDomNode?e.listenOnDomNode:null,horizontal:void 0!==e.horizontal?e.horizontal:1,horizontalScrollbarSize:void 0!==e.horizontalScrollbarSize?e.horizontalScrollbarSize:10,horizontalSliderSize:void 0!==e.horizontalSliderSize?e.horizontalSliderSize:0,horizontalHasArrows:void 0!==e.horizontalHasArrows&&e.horizontalHasArrows,vertical:void 0!==e.vertical?e.vertical:1,verticalScrollbarSize:void 0!==e.verticalScrollbarSize?e.verticalScrollbarSize:10,verticalHasArrows:void 0!==e.verticalHasArrows&&e.verticalHasArrows,verticalSliderSize:void 0!==e.verticalSliderSize?e.verticalSliderSize:0,scrollByPage:void 0!==e.scrollByPage&&e.scrollByPage};return t.horizontalSliderSize=void 0!==e.horizontalSliderSize?e.horizontalSliderSize:t.horizontalScrollbarSize,t.verticalSliderSize=void 0!==e.verticalSliderSize?e.verticalSliderSize:t.verticalScrollbarSize,v.isMac&&(t.className+=" xterm-mac"),t}(t),this._scrollable=s,this._register(this._scrollable.onScroll(e=>{this._handleScroll(e),this._onScroll.fire(e)})),r&&this._register(this._scrollable);const o={handleMouseWheel:e=>this._handleMouseWheel(e),handleDragStart:()=>this._handleDragStart(),handleDragEnd:()=>this._handleDragEnd()};this._verticalScrollbar=this._register(new d.VerticalScrollbar(this._scrollable,this._options,o)),this._horizontalScrollbar=this._register(new c.HorizontalScrollbar(this._scrollable,this._options,o)),this._domNode=document.createElement("div"),this._domNode.className="xterm-scrollable-element "+this._options.className,this._domNode.setAttribute("role","presentation"),this._domNode.style.position="relative",this._domNode.appendChild(e),this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode),this._domNode.appendChild(this._verticalScrollbar.domNode.domNode),this._options.useShadows?(this._leftShadowDomNode=new h.FastDomNode(document.createElement("div")),this._leftShadowDomNode.setClassName("xterm-shadow"),this._domNode.appendChild(this._leftShadowDomNode.domNode),this._topShadowDomNode=new h.FastDomNode(document.createElement("div")),this._topShadowDomNode.setClassName("xterm-shadow"),this._domNode.appendChild(this._topShadowDomNode.domNode),this._topLeftShadowDomNode=new h.FastDomNode(document.createElement("div")),this._topLeftShadowDomNode.setClassName("xterm-shadow"),this._domNode.appendChild(this._topLeftShadowDomNode.domNode)):(this._leftShadowDomNode=null,this._topShadowDomNode=null,this._topLeftShadowDomNode=null),this._listenOnDomNode=this._options.listenOnDomNode??this._domNode,this._mouseWheelToDispose=[],this._setListeningToMouseWheel(this._options.handleMouseWheel),this._onmouseover(this._listenOnDomNode,e=>this._handleMouseOver(e)),this._onmouseleave(this._listenOnDomNode,e=>this._handleMouseLeave(e)),this._hideTimeout=this._register(new u.TimeoutTimer),this._isDragging=!1,this._mouseIsOver=!1,this._shouldRender=!0,this._revealOnScroll=!0}dispose(){this._mouseWheelToDispose=(0,p.dispose)(this._mouseWheelToDispose),super.dispose()}getDomNode(){return this._domNode}getScrollDimensions(){return this._scrollable.getScrollDimensions()}setScrollDimensions(e){this._scrollable.setScrollDimensions(e,!1)}setScrollPosition(e){e.reuseAnimation?this._scrollable.setScrollPositionSmooth(e,e.reuseAnimation):this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}updateClassName(e){this._options.className=e,v.isMac&&(this._options.className+=" xterm-mac"),this._domNode.className="xterm-scrollable-element "+this._options.className}updateOptions(e){void 0!==e.handleMouseWheel&&(this._options.handleMouseWheel=e.handleMouseWheel,this._setListeningToMouseWheel(this._options.handleMouseWheel)),void 0!==e.mouseWheelScrollSensitivity&&(this._options.mouseWheelScrollSensitivity=e.mouseWheelScrollSensitivity),void 0!==e.fastScrollSensitivity&&(this._options.fastScrollSensitivity=e.fastScrollSensitivity),void 0!==e.scrollPredominantAxis&&(this._options.scrollPredominantAxis=e.scrollPredominantAxis),void 0!==e.horizontal&&(this._options.horizontal=e.horizontal),void 0!==e.vertical&&(this._options.vertical=e.vertical),void 0!==e.horizontalHasArrows&&(this._options.horizontalHasArrows=e.horizontalHasArrows),void 0!==e.verticalHasArrows&&(this._options.verticalHasArrows=e.verticalHasArrows),void 0!==e.horizontalScrollbarSize&&(this._options.horizontalScrollbarSize=e.horizontalScrollbarSize),void 0!==e.verticalScrollbarSize&&(this._options.verticalScrollbarSize=e.verticalScrollbarSize),void 0!==e.scrollByPage&&(this._options.scrollByPage=e.scrollByPage),this._horizontalScrollbar.updateOptions(this._options),this._verticalScrollbar.updateOptions(this._options),this._options.lazyRender||this._render()}delegateScrollFromMouseWheelEvent(e){this._handleMouseWheel(new l.StandardWheelEvent(e))}_setListeningToMouseWheel(e){if(this._mouseWheelToDispose.length>0!==e&&(this._mouseWheelToDispose=(0,p.dispose)(this._mouseWheelToDispose),e)){const e=e=>{this._handleMouseWheel(new l.StandardWheelEvent(e))};this._mouseWheelToDispose.push(a.addDisposableListener(this._listenOnDomNode,a.eventType.MOUSE_WHEEL,e,{passive:!1}))}}_handleMouseWheel(e){if(e.browserEvent?.defaultPrevented)return;const t=S.INSTANCE;t.acceptStandardWheelEvent(e);let i=!1;if(e.deltaY||e.deltaX){let s=e.deltaY*this._options.mouseWheelScrollSensitivity,r=e.deltaX*this._options.mouseWheelScrollSensitivity;this._options.scrollPredominantAxis&&(this._options.scrollYToX&&r+s===0?r=s=0:Math.abs(s)>=Math.abs(r)?r=0:s=0),this._options.flipAxes&&([s,r]=[r,s]);const o=!v.isMac&&e.browserEvent&&e.browserEvent.shiftKey;!this._options.scrollYToX&&!o||r||(r=s,s=0),e.browserEvent&&e.browserEvent.altKey&&(r*=this._options.fastScrollSensitivity,s*=this._options.fastScrollSensitivity);const n=this._scrollable.getFutureScrollPosition();let a={};if(s){const e=50*s,t=n.scrollTop-(e<0?Math.floor(e):Math.ceil(e));this._verticalScrollbar.writeScrollPosition(a,t)}if(r){const e=50*r,t=n.scrollLeft-(e<0?Math.floor(e):Math.ceil(e));this._horizontalScrollbar.writeScrollPosition(a,t)}a=this._scrollable.validateScrollPosition(a),(n.scrollLeft!==a.scrollLeft||n.scrollTop!==a.scrollTop)&&(this._options.mouseWheelSmoothScroll&&t.isPhysicalMouseWheel()?this._scrollable.setScrollPositionSmooth(a):this._scrollable.setScrollPositionNow(a),i=!0)}let s=i;!s&&this._options.alwaysConsumeMouseWheel&&(s=!0),!s&&this._options.consumeMouseWheelIfScrollbarIsNeeded&&(this._verticalScrollbar.isNeeded()||this._horizontalScrollbar.isNeeded())&&(s=!0),s&&(e.preventDefault(),e.stopPropagation())}_handleScroll(e){this._shouldRender=this._horizontalScrollbar.handleScroll(e)||this._shouldRender,this._shouldRender=this._verticalScrollbar.handleScroll(e)||this._shouldRender,this._options.useShadows&&(this._shouldRender=!0),this._revealOnScroll&&this._reveal(),this._options.lazyRender||this._render()}renderNow(){if(!this._options.lazyRender)throw new Error("Please use `lazyRender` together with `renderNow`!");this._render()}_render(){if(this._shouldRender&&(this._shouldRender=!1,this._horizontalScrollbar.render(),this._verticalScrollbar.render(),this._options.useShadows)){const e=this._scrollable.getCurrentScrollPosition(),t=e.scrollTop>0,i=e.scrollLeft>0,s=i?" xterm-shadow-left":"",r=t?" xterm-shadow-top":"",o=i||t?" xterm-shadow-top-left-corner":"";this._leftShadowDomNode.setClassName(`xterm-shadow${s}`),this._topShadowDomNode.setClassName(`xterm-shadow${r}`),this._topLeftShadowDomNode.setClassName(`xterm-shadow${o}${r}${s}`)}}_handleDragStart(){this._isDragging=!0,this._reveal()}_handleDragEnd(){this._isDragging=!1,this._hide()}_handleMouseLeave(e){this._mouseIsOver=!1,this._hide()}_handleMouseOver(e){this._mouseIsOver=!0,this._reveal()}_reveal(){this._verticalScrollbar.beginReveal(),this._horizontalScrollbar.beginReveal(),this._scheduleHide()}_hide(){this._mouseIsOver||this._isDragging||(this._verticalScrollbar.beginHide(),this._horizontalScrollbar.beginHide())}_scheduleHide(){this._mouseIsOver||this._isDragging||this._hideTimeout.cancelAndSet(()=>this._hide(),500)}}t.SmoothScrollableElement=b},9594(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._arrowPointerDown(e))),this._register(c.addStandardDisposableListener(this.domNode,c.eventType.POINTER_DOWN,e=>this._arrowPointerDown(e))),this._pointerdownRepeatTimer=this._register(new c.WindowIntervalTimer),this._pointerdownScheduleRepeatTimer=this._register(new l.TimeoutTimer)}_arrowPointerDown(e){e.target&&e.target instanceof Element&&(this._handleActivate(),this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancelAndSet(()=>{this._pointerdownRepeatTimer.cancelAndSet(()=>this._handleActivate(),1e3/24,c.getWindow(e))},200),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,e=>{},()=>{this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancel()}),e.preventDefault())}}t.ScrollbarArrow=d},1270(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ScrollbarState=void 0;class i{constructor(e,t,i,s,r,o){this._scrollbarSize=Math.round(t),this._oppositeScrollbarSize=Math.round(i),this._arrowSize=Math.round(e),this._visibleSize=s,this._scrollSize=r,this._scrollPosition=o,this._computedAvailableSize=0,this._computedIsNeeded=!1,this._computedSliderSize=0,this._computedSliderRatio=0,this._computedSliderPosition=0,this._refreshComputedValues()}clone(){return new i(this._arrowSize,this._scrollbarSize,this._oppositeScrollbarSize,this._visibleSize,this._scrollSize,this._scrollPosition)}setVisibleSize(e){const t=Math.round(e);return this._visibleSize!==t&&(this._visibleSize=t,this._refreshComputedValues(),!0)}setScrollSize(e){const t=Math.round(e);return this._scrollSize!==t&&(this._scrollSize=t,this._refreshComputedValues(),!0)}setScrollPosition(e){const t=Math.round(e);return this._scrollPosition!==t&&(this._scrollPosition=t,this._refreshComputedValues(),!0)}setScrollbarSize(e){this._scrollbarSize=Math.round(e)}setArrowSize(e){const t=Math.round(e);this._arrowSize!==t&&(this._arrowSize=t,this._refreshComputedValues())}setOppositeScrollbarSize(e){this._oppositeScrollbarSize=Math.round(e)}static _computeValues(e,t,i,s,r){const o=Math.max(0,i-e),n=Math.max(0,o-2*t),a=s>0&&s>i;if(!a)return{computedAvailableSize:Math.round(o),computedIsNeeded:a,computedSliderSize:Math.round(n),computedSliderRatio:0,computedSliderPosition:0};const h=Math.round(Math.max(20,Math.floor(i*n/s))),l=(n-h)/(s-i),c=r*l;return{computedAvailableSize:Math.round(o),computedIsNeeded:a,computedSliderSize:Math.round(h),computedSliderRatio:l,computedSliderPosition:Math.round(c)}}_refreshComputedValues(){const e=i._computeValues(this._oppositeScrollbarSize,this._arrowSize,this._visibleSize,this._scrollSize,this._scrollPosition);this._computedAvailableSize=e.computedAvailableSize,this._computedIsNeeded=e.computedIsNeeded,this._computedSliderSize=e.computedSliderSize,this._computedSliderRatio=e.computedSliderRatio,this._computedSliderPosition=e.computedSliderPosition}getArrowSize(){return this._arrowSize}getScrollPosition(){return this._scrollPosition}getRectangleLargeSize(){return this._computedAvailableSize}getRectangleSmallSize(){return this._scrollbarSize}isNeeded(){return this._computedIsNeeded}getSliderSize(){return this._computedSliderSize}getSliderPosition(){return this._computedSliderPosition}getDesiredScrollPositionFromOffset(e){if(!this._computedIsNeeded)return 0;const t=e-this._arrowSize-this._computedSliderSize/2;return Math.round(t/this._computedSliderRatio)}getDesiredScrollPositionFromOffsetPaged(e){if(!this._computedIsNeeded)return 0;const t=e-this._arrowSize;let i=this._scrollPosition;return t{this._domNode?.setClassName(this._visibleClassName)},0))}_hide(e){this._revealTimer.cancel(),this._isVisible&&(this._isVisible=!1,this._domNode?.setClassName(this._invisibleClassName+(e?" xterm-fade":"")))}}t.ScrollbarVisibilityController=o},2650(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},a=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;n{s||(s=!0,this._remove(i))}}_remove(e){if(e.prev!==_.Undefined&&e.next!==_.Undefined){const t=e.prev;t.next=e.next,e.next.prev=t}else e.prev===_.Undefined&&e.next===_.Undefined?(this._first=_.Undefined,this._last=_.Undefined):e.next===_.Undefined?(this._last=this._last.prev,this._last.next=_.Undefined):e.prev===_.Undefined&&(this._first=this._first.next,this._first.prev=_.Undefined)}*[Symbol.iterator](){let e=this._first;for(;e!==_.Undefined;)yield e.element,e=e.next}}var f;!function(e){e.TAP="-xterm-gesturetap",e.CHANGE="-xterm-gesturechange",e.START="-xterm-gesturestart",e.END="-xterm-gesturesend",e.CONTEXT_MENU="-xterm-gesturecontextmenu"}(f||(t.EventType=f={}));class p extends l.Disposable{constructor(){super(),this._dispatched=!1,this._targets=new u,this._ignoreTargets=new u,this._activeTouches={},this._handle=null,this._lastSetTapCountTime=0;const e=c;this._register(h.addDisposableListener(e.document,"touchstart",e=>this._handleTouchStart(e),{passive:!1})),this._register(h.addDisposableListener(e.document,"touchend",t=>this._handleTouchEnd(e,t))),this._register(h.addDisposableListener(e.document,"touchmove",e=>this._handleTouchMove(e),{passive:!1}))}static addTarget(e){if(!p.isTouchDevice())return l.Disposable.None;p._instance||(p._instance=new p);const t=p._instance._targets.push(e);return(0,l.toDisposable)(t)}static ignoreTarget(e){if(!p.isTouchDevice())return l.Disposable.None;p._instance||(p._instance=new p);const t=p._instance._ignoreTargets.push(e);return(0,l.toDisposable)(t)}static isTouchDevice(){return"ontouchstart"in c||navigator.maxTouchPoints>0}dispose(){this._handle&&(this._handle.dispose(),this._handle=null),super.dispose()}_handleTouchStart(e){const t=Date.now();this._handle&&(this._handle.dispose(),this._handle=null);for(let i=0,s=e.targetTouches.length;i=p._holdDelay&&Math.abs(n.initialPageX-d(n.rollingPageX))<30&&Math.abs(n.initialPageY-d(n.rollingPageY))<30){const e=this._newGestureEvent(f.CONTEXT_MENU,n.initialTarget);e.pageX=d(n.rollingPageX),e.pageY=d(n.rollingPageY),this._dispatchEvent(e)}else if(1===s){const t=d(n.rollingPageX),s=d(n.rollingPageY),r=d(n.rollingTimestamps)-n.rollingTimestamps[0],o=t-n.rollingPageX[0],a=s-n.rollingPageY[0],h=[...this._targets].filter(e=>n.initialTarget instanceof Node&&e.contains(n.initialTarget));this._inertia(e,h,i,Math.abs(o)/r,o>0?1:-1,t,Math.abs(a)/r,a>0?1:-1,s)}this._dispatchEvent(this._newGestureEvent(f.END,n.initialTarget)),delete this._activeTouches[o.identifier]}this._dispatched&&(t.preventDefault(),t.stopPropagation(),this._dispatched=!1)}_newGestureEvent(e,t){const i=document.createEvent("CustomEvent");return i.initEvent(e,!1,!0),i.initialTarget=t,i.tapCount=0,i}_dispatchEvent(e){if(e.type===f.TAP){const t=(new Date).getTime();let i;i=t-this._lastSetTapCountTime>p._clearTapCountTime?1:2,this._lastSetTapCountTime=t,e.tapCount=i}else e.type!==f.CHANGE&&e.type!==f.CONTEXT_MENU||(this._lastSetTapCountTime=0);if(e.initialTarget instanceof Node){for(const t of this._ignoreTargets)if(t.contains(e.initialTarget))return;const t=[];for(const i of this._targets)if(i.contains(e.initialTarget)){let s=0,r=e.initialTarget;for(;r&&r!==i;)s++,r=r.parentElement;t.push([s,i])}t.sort((e,t)=>e[0]-t[0]);for(const[,i]of t)i.dispatchEvent(e),this._dispatched=!0}}_inertia(e,t,i,s,r,o,n,a,l){this._handle=h.scheduleAtNextAnimationFrame(e,()=>{const h=Date.now(),c=h-i;let d=0,_=0,u=!0;s+=p._scrollFriction*c,n+=p._scrollFriction*c,s>0&&(u=!1,d=r*s*c),n>0&&(u=!1,_=a*n*c);const v=this._newGestureEvent(f.CHANGE);v.translationX=d,v.translationY=_,t.forEach(e=>e.dispatchEvent(v)),u||this._inertia(e,t,h,s,r,o+d,n,a,l+_)})}_handleTouchMove(e){const t=Date.now();for(let i=0,s=e.changedTouches.length;i3&&(r.rollingPageX.shift(),r.rollingPageY.shift(),r.rollingTimestamps.shift()),r.rollingPageX.push(s.pageX),r.rollingPageY.push(s.pageY),r.rollingTimestamps.push(t)}this._dispatched&&(e.preventDefault(),e.stopPropagation(),this._dispatched=!1)}}t.Gesture=p,p._scrollFriction=-.005,p._holdDelay=700,p._clearTapCountTime=400,n([function(e,t,i){let s=null,r=null;if("function"==typeof i.value?(s="value",r=i.value,0!==r.length&&console.warn("Memoize should only be used in functions with zero parameters")):"function"==typeof i.get&&(s="get",r=i.get),!r||!s)throw new Error("not supported");const o=`$memoize$${t}`;i[s]=function(...e){return this.hasOwnProperty(o)||Object.defineProperty(this,o,{configurable:!1,enumerable:!1,writable:!1,value:r.apply(this,e)}),this[o]}}],p,"isTouchDevice",null)},8997(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.VerticalScrollbar=void 0;const s=i(8501),r=i(1270);class o extends s.AbstractScrollbar{constructor(e,t,i){const s=e.getScrollDimensions(),o=e.getCurrentScrollPosition(),n=t.verticalHasArrows;super({lazyRender:t.lazyRender,host:i,scrollbarState:new r.ScrollbarState(n?t.verticalScrollbarSize:0,2===t.vertical?0:t.verticalScrollbarSize,0,s.height,s.scrollHeight,o.scrollTop),visibility:t.vertical,extraScrollbarClassName:"xterm-vertical",scrollable:e,scrollByPage:t.scrollByPage}),this._arrowScrollDelta=0,this._setArrows(n,t.verticalScrollbarSize),this._createSlider(0,Math.floor((t.verticalScrollbarSize-t.verticalSliderSize)/2),t.verticalSliderSize,void 0)}_updateSlider(e,t){this.slider.setHeight(e),this.slider.setTop(t)}_renderDomNode(e,t){this.domNode.setWidth(t),this.domNode.setHeight(e),this.domNode.setRight(0),this.domNode.setTop(0)}handleScroll(e){return this._shouldRender=this._handleElementScrollSize(e.scrollHeight)||this._shouldRender,this._shouldRender=this._handleElementScrollPosition(e.scrollTop)||this._shouldRender,this._shouldRender=this._handleElementSize(e.height)||this._shouldRender,this._shouldRender}_pointerDownRelativePosition(e,t){return t}_sliderPointerPosition(e){return e.pageY}_sliderOrthogonalPointerPosition(e){return e.pageX}_updateScrollbarSize(e){this.slider.setWidth(e)}writeScrollPosition(e,t){e.scrollTop=t}_arrowScroll(e){const t=this._scrollable.getCurrentScrollPosition();this._scrollable.setScrollPositionNow({scrollTop:t.scrollTop+e})}_setArrows(e,t){if(this._arrowScrollDelta=t,!this._arrowUp||!this._arrowDown){const e=0;this._arrowUp=this._createArrow({className:"xterm-scra xterm-arrow-up",top:e,left:e,bgWidth:t,bgHeight:t,handleActivate:()=>this._arrowScroll(-this._arrowScrollDelta)}),this._arrowDown=this._createArrow({className:"xterm-scra xterm-arrow-down",bottom:e,left:e,bgWidth:t,bgHeight:t,handleActivate:()=>this._arrowScroll(this._arrowScrollDelta)})}if(this._updateArrowSize(this._arrowUp,t),this._updateArrowSize(this._arrowDown,t),!this._arrowUp||!this._arrowDown)return;const i=e?"":"none";this._arrowUp.bgDomNode.style.display=i,this._arrowUp.domNode.style.display=i,this._arrowDown.bgDomNode.style.display=i,this._arrowDown.domNode.style.display=i}_updateArrowSize(e,t){e&&(e.bgDomNode.style.width=`${t}px`,e.bgDomNode.style.height=`${t}px`,e.domNode.style.width=`${t}px`,e.domNode.style.height=`${t}px`)}updateOptions(e){const t=e.verticalHasArrows?e.verticalScrollbarSize:0;this._scrollbarState.setArrowSize(t),this._setArrows(e.verticalHasArrows,e.verticalScrollbarSize),this.updateScrollbarSize(2===e.vertical?0:e.verticalScrollbarSize),this._scrollbarState.setOppositeScrollbarSize(0),this._visibilityController.setVisibility(e.vertical),this._scrollByPage=e.scrollByPage}}t.VerticalScrollbar=o},7741(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nt(new h.StandardMouseEvent(a.getWindow(e),i))))}_onmouseover(e,t){this._register(a.addDisposableListener(e,a.eventType.MOUSE_OVER,i=>t(new h.StandardMouseEvent(a.getWindow(e),i))))}_onmouseleave(e,t){this._register(a.addDisposableListener(e,a.eventType.MOUSE_LEAVE,i=>t(new h.StandardMouseEvent(a.getWindow(e),i))))}}t.Widget=c},5959(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionModel=void 0,t.SelectionModel=class{constructor(e){this._bufferService=e,this.isSelectAllActive=!1,this.selectionStartLength=0}clearSelection(){this.selectionStart=void 0,this.selectionEnd=void 0,this.isSelectAllActive=!1,this.selectionStartLength=0}get finalSelectionStart(){return this.isSelectAllActive?[0,0]:this.selectionEnd&&this.selectionStart&&this.areSelectionValuesReversed()?this.selectionEnd:this.selectionStart}get finalSelectionEnd(){if(this.isSelectAllActive)return[this._bufferService.cols,this._bufferService.buffer.ybase+this._bufferService.rows-1];if(this.selectionStart){if(!this.selectionEnd||this.areSelectionValuesReversed()){const e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?e%this._bufferService.cols===0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)-1]:[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){const e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[Math.max(e,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){const e=this.selectionStart,t=this.selectionEnd;return!(!e||!t)&&(e[1]>t[1]||e[1]===t[1]&&e[0]>t[0])}handleTrim(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):!!(this.selectionStart&&this.selectionStart[1]<0)&&(this.selectionStart=[0,0],!0)}}},4792(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CharSizeService=void 0;const o=i(6501),n=i(4812),a=i(8636);let h=class extends n.Disposable{get hasValidSize(){return this.width>0&&this.height>0}constructor(e,t,i){super(),this._optionsService=i,this.width=0,this.height=0,this._onCharSizeChange=this._register(new a.Emitter),this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new d(this._optionsService))}catch{this._measureStrategy=this._register(new c(e,t,this._optionsService))}this._register(this._optionsService.onMultipleOptionChange(["fontFamily","fontSize"],()=>this.measure()))}measure(){const e=this._measureStrategy.measure();e.width===this.width&&e.height===this.height||(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};t.CharSizeService=h,t.CharSizeService=h=s([r(2,o.IOptionsService)],h);class l extends n.Disposable{constructor(){super(...arguments),this._result={width:0,height:0}}_validateAndSet(e,t){void 0!==e&&e>0&&void 0!==t&&t>0&&(this._result.width=e,this._result.height=t)}}class c extends l{constructor(e,t,i){super(),this._document=e,this._parentElement=t,this._optionsService=i,this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W".repeat(32),this._measureElement.setAttribute("aria-hidden","true"),this._measureElement.style.whiteSpace="pre",this._measureElement.style.fontKerning="none",this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}}class d extends l{constructor(e){super(),this._optionsService=e,this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext("2d");const t=this._ctx.measureText("W");if(!("width"in t&&"fontBoundingBoxAscent"in t&&"fontBoundingBoxDescent"in t))throw new Error("Required font metrics not supported")}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;const e=this._ctx.measureText("W");return this._validateAndSet(e.width,e.fontBoundingBoxAscent+e.fontBoundingBoxDescent),this._result}}},945(e,t,i){var s,r=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},o=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CharacterJoinerService=t.JoinedCellData=void 0;const n=i(5451),a=i(8938),h=i(3055),l=i(6501);class c extends n.AttributeData{constructor(e,t,i){super(),this.content=0,this.combinedData="",this.fg=e.fg,this.bg=e.bg,this.combinedData=t,this._width=i}isCombined(){return 2097152}getWidth(){return this._width}getChars(){return this.combinedData}getCode(){return 2097151}setFromCharData(e){throw new Error("not implemented")}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}}t.JoinedCellData=c;let d=s=class{constructor(e){this._bufferService=e,this._characterJoiners=[],this._nextCharacterJoinerId=0,this._workCell=new h.CellData}register(e){const t={id:this._nextCharacterJoinerId++,handler:e};return this._characterJoiners.push(t),t.id}deregister(e){for(let t=0;t1){const e=this._getJoinedRanges(s,h,n,t,o);for(let t=0;t1){const e=this._getJoinedRanges(s,h,n,t,o);for(let t=0;tthis._screenDprMonitor.setWindow(e))),this._register(s.EventUtils.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register((0,r.addDisposableListener)(this._textarea,"focus",()=>this._isFocused=!0)),this._register((0,r.addDisposableListener)(this._textarea,"blur",()=>this._isFocused=!1))}get window(){return this._window}set window(e){this._window!==e&&(this._window=e,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return void 0===this._cachedIsFocused&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask(()=>this._cachedIsFocused=void 0)),this._cachedIsFocused}}t.CoreBrowserService=n;class a extends o.Disposable{constructor(e){super(),this._parentWindow=e,this._windowResizeListener=this._register(new o.MutableDisposable),this._onDprChange=this._register(new s.Emitter),this.onDprChange=this._onDprChange.event,this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register((0,o.toDisposable)(()=>this.clearListener()))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=(0,r.addDisposableListener)(this._parentWindow,"resize",()=>this._setDprAndFireIfDiffers())}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){this._outerListener&&(this._resolutionMediaMatchList?.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){this._resolutionMediaMatchList&&this._outerListener&&(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}}},2136(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.KeyboardService=void 0;const o=i(706),n=i(7241),a=i(9249),h=i(701),l=i(6501);let c=class{constructor(e,t){this._coreService=e,this._optionsService=t}_getWin32InputMode(){return this._win32InputMode??=new a.Win32InputMode,this._win32InputMode}_getKittyKeyboard(){return this._kittyKeyboard??=new n.KittyKeyboard,this._kittyKeyboard}evaluateKeyDown(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!0);const t=this._coreService.kittyKeyboard.flags;return this.useKitty?this._getKittyKeyboard().evaluate(e,t,e.repeat?2:1,h.isMac&&this._optionsService.rawOptions.macOptionIsMeta):(0,o.evaluateKeyboardEvent)(e,this._coreService.decPrivateModes.applicationCursorKeys,h.isMac,this._optionsService.rawOptions.macOptionIsMeta)}evaluateKeyUp(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!1);const t=this._coreService.kittyKeyboard.flags;return this.useKitty&&2&t?this._getKittyKeyboard().evaluate(e,t,3,h.isMac&&this._optionsService.rawOptions.macOptionIsMeta):void 0}get useKitty(){const e=this._coreService.kittyKeyboard.flags;return!(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard||!n.KittyKeyboard.shouldUseProtocol(e))}get useWin32InputMode(){return!(!this._optionsService.rawOptions.vtExtensions?.win32InputMode||!this._coreService.decPrivateModes.win32InputMode)}};t.KeyboardService=c,t.KeyboardService=c=s([r(0,l.ICoreService),r(1,l.IOptionsService)],c)},9820(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.LinkProviderService=void 0;const s=i(4812);class r extends s.Disposable{constructor(){super(),this.linkProviders=[],this._register((0,s.toDisposable)(()=>this.linkProviders.length=0))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{const t=this.linkProviders.indexOf(e);-1!==t&&this.linkProviders.splice(t,1)}}}}t.LinkProviderService=r},8294(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.MouseCoordsService=void 0;const o=i(4159),n=i(5251),a=i(7098);let h=class{constructor(e,t){this._charSizeService=e,this._renderService=t}getCoords(e,t,i,s,r){return(0,n.getCoords)((0,o.getWindow)(t),e,t,i,s,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,r)}getMouseReportCoords(e,t){const i=(0,n.getCoordsRelativeToElement)((0,o.getWindow)(t),e,t);if(this._charSizeService.hasValidSize)return i[0]=Math.min(Math.max(i[0],0),this._renderService.dimensions.css.canvas.width-1),i[1]=Math.min(Math.max(i[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(i[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(i[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(i[0]),y:Math.floor(i[1])}}};t.MouseCoordsService=h,t.MouseCoordsService=h=s([r(0,a.ICharSizeService),r(1,a.IRenderService)],h)},9784(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.AltMouseCursorController=t.MouseService=void 0;const o=i(4159),n=i(6501),a=i(4812),h=i(7098),l=i(2650);let c=class{constructor(e,t,i,s,r,o,n,a,h){this._renderService=e,this._mouseCoordsService=t,this._mouseStateService=i,this._coreService=s,this._bufferService=r,this._optionsService=o,this._selectionService=n,this._logService=a,this._coreBrowserService=h,this._lastEvent=null,this._wheelPartialScroll=0,this._touchScrollAccumulator=0}bindMouse(e,t,i){const{element:s,document:r}=e,n={mouseup:null,wheel:null,mousedrag:null,mousemove:null},h={target:e,focus:i,requestedEvents:n},c={mouseup:e=>this._handleMouseUp(h,e),wheel:e=>this._handleWheel(h,e),mousedrag:e=>this._handleMouseDrag(h,e),mousemove:e=>this._handleMouseMove(h,e)};this._altMouseCursor=new d(s,r,()=>this._mouseStateService.areMouseEventsActive&&!!this._optionsService.rawOptions.mouseEventsRequireAlt),t(this._altMouseCursor),t(this._mouseStateService.onProtocolChange(e=>{this._handleProtocolChange(h,c,e)})),t(this._optionsService.onSpecificOptionChange("mouseEventsRequireAlt",()=>{this._syncMouseModeState(s),this._altMouseCursor?.sync()})),this._mouseStateService.activeProtocol=this._mouseStateService.activeProtocol,t((0,a.toDisposable)(()=>{n.mouseup&&r.removeEventListener("mouseup",n.mouseup),n.mousedrag&&r.removeEventListener("mousemove",n.mousedrag)})),t((0,o.addDisposableListener)(s,"mousedown",e=>this._handleMouseDown(h,e))),t((0,o.addDisposableListener)(s,"wheel",e=>this._handlePassiveWheel(h,e),{passive:!1})),t(l.Gesture.addTarget(e.screenElement)),t((0,o.addDisposableListener)(e.screenElement,l.EventType.START,()=>this._handleTouchStart())),t((0,o.addDisposableListener)(e.screenElement,l.EventType.CHANGE,e=>this._handleTouchChange(h,e)))}_sendEvent(e,t){const i=this._mouseCoordsService.getMouseReportCoords(t,e.target.screenElement);if(!i)return!1;let s,r;switch(t.overrideType||t.type){case"mousemove":r=32,void 0===t.buttons?(s=3,void 0!==t.button&&(s=t.button<3?t.button:3)):s=1&t.buttons?0:4&t.buttons?1:2&t.buttons?2:3;break;case"mouseup":r=0,s=t.button<3?t.button:3;break;case"mousedown":r=1,s=t.button<3?t.button:3;break;case"wheel":if(!this._mouseStateService.allowCustomWheelEvent(t))return!1;const e=t.deltaY;if(0===e)return!1;if(0===this._consumeWheelEvent(t,this._renderService?.dimensions?.device?.cell?.height,this._coreBrowserService?.dpr))return!1;r=e<0?0:1,s=4;break;default:return!1}if(void 0===r||void 0===s||s>4)return!1;if(4!==s&&this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive&&!t.altKey)return!1;const o=4!==s&&this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive;return this._triggerMouseEvent({col:i.col,row:i.row,x:i.x,y:i.y,button:s,action:r,ctrl:t.ctrlKey,alt:!o&&t.altKey,shift:t.shiftKey})}_handleMouseUp(e,t){this._sendEvent(e,t),t.buttons||(e.requestedEvents.mouseup&&e.target.document.removeEventListener("mouseup",e.requestedEvents.mouseup),e.requestedEvents.mousedrag&&e.target.document.removeEventListener("mousemove",e.requestedEvents.mousedrag))}_handleWheel(e,t){return this._sendEvent(e,t),t.preventDefault(),t.stopPropagation(),!1}_handleMouseDrag(e,t){t.buttons&&this._sendEvent(e,t)}_handleMouseMove(e,t){t.buttons||this._sendEvent(e,t)}_handleMouseDown(e,t){t.preventDefault(),e.focus(),this._mouseStateService.areMouseEventsActive&&!this._selectionService.shouldForceSelection(t)&&(this._sendEvent(e,t),e.requestedEvents.mouseup&&e.target.document.addEventListener("mouseup",e.requestedEvents.mouseup),e.requestedEvents.mousedrag&&e.target.document.addEventListener("mousemove",e.requestedEvents.mousedrag))}_handlePassiveWheel(e,t){if(!e.requestedEvents.wheel){if(!this._mouseStateService.allowCustomWheelEvent(t))return!1;if(!this._bufferService.buffer.hasScrollback){if(0===t.deltaY)return!1;if(0===this._consumeWheelEvent(t,this._renderService?.dimensions?.device?.cell?.height,this._coreBrowserService?.dpr))return t.preventDefault(),t.stopPropagation(),!1;const e=""+(this._coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(t.deltaY<0?"A":"B");return this._coreService.triggerDataEvent(e,!0),t.preventDefault(),t.stopPropagation(),!1}}}_handleTouchStart(){this._touchScrollAccumulator=0}_handleTouchChange(e,t){t.preventDefault(),t.stopPropagation(),e.requestedEvents.wheel?this._handleTouchScrollAsWheel(e,t):this._bufferService.buffer.hasScrollback?e.target.handleTouchScroll?.(t.translationY):this._handleTouchScrollAsKeys(t)}_handleTouchScrollAsKeys(e){const t=this._renderService?.dimensions.css.cell.height;if(!t)return;this._touchScrollAccumulator-=e.translationY;const i=Math.trunc(this._touchScrollAccumulator/t);if(0===i)return;this._touchScrollAccumulator-=i*t;const s=""+(this._coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(i<0?"A":"B");for(let e=0;e0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(r*=this._bufferService.rows),r}_triggerMouseEvent(e){if(e.col<0||e.col>=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows)return!1;if(4===e.button&&32===e.action)return!1;if(3===e.button&&32!==e.action)return!1;if(4!==e.button&&(2===e.action||3===e.action))return!1;if(e.col++,e.row++,32===e.action&&this._lastEvent&&this._equalEvents(this._lastEvent,e,this._mouseStateService.isPixelEncoding))return!1;if(!this._mouseStateService.restrictMouseEvent(e))return!1;const t=this._mouseStateService.encodeMouseEvent(e);return t&&(this._mouseStateService.isDefaultEncoding?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0}_explainEvents(e){return{down:!!(1&e),up:!!(2&e),drag:!!(4&e),move:!!(8&e),wheel:!!(16&e)}}_equalEvents(e,t,i){if(i){if(e.x!==t.x)return!1;if(e.y!==t.y)return!1}else{if(e.col!==t.col)return!1;if(e.row!==t.row)return!1}return e.button===t.button&&e.action===t.action&&e.ctrl===t.ctrl&&e.alt===t.alt&&e.shift===t.shift}};t.MouseService=c,t.MouseService=c=s([r(0,h.IRenderService),r(1,h.IMouseCoordsService),r(2,n.IMouseStateService),r(3,n.ICoreService),r(4,n.IBufferService),r(5,n.IOptionsService),r(6,h.ISelectionService),r(7,n.ILogService),r(8,h.ICoreBrowserService)],c);class d{constructor(e,t,i){this._element=e,this._document=t,this._isActive=i,this._listeners=new a.MutableDisposable}dispose(){this._listeners.dispose()}sync(){if(this._listeners.clear(),!this._isActive())return;const e=new a.DisposableStore,t=e=>this.syncFromModifier(e);e.add((0,o.addDisposableListener)(this._document,"keydown",t)),e.add((0,o.addDisposableListener)(this._document,"keyup",t)),e.add((0,o.addDisposableListener)(this._element,"mousemove",t));const i=this._element.ownerDocument?.defaultView;i&&e.add((0,o.addDisposableListener)(i,"blur",()=>{this._isActive()&&this.resetClass()})),this._listeners.value=e}resetClass(){this._updateClass(!1)}syncFromModifier(e){this._isActive()&&this._updateClass(e.getModifierState("Alt"))}_updateClass(e){e?this._element.classList.add("enable-mouse-events"):this._element.classList.remove("enable-mouse-events")}}t.AltMouseCursorController=d},5783(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.RenderService=void 0;const o=i(4852),n=i(7098),a=i(4812),h=i(6168),l=i(6501),c=i(8636);let d=class extends a.Disposable{get dimensions(){return this._renderer.value.dimensions}constructor(e,t,i,s,r,n,l,d,u,f){super(),this._rowCount=e,this._optionsService=i,this._logService=s,this._charSizeService=r,this._coreService=n,this._coreBrowserService=u,this._renderer=this._register(new a.MutableDisposable),this._observerDisposable=this._register(new a.MutableDisposable),this._isPaused=!1,this._needsFullRefresh=!1,this._isNextRenderRedrawOnly=!0,this._needsSelectionRefresh=!1,this._canvasWidth=0,this._canvasHeight=0,this._selectionState={start:void 0,end:void 0,columnSelectMode:!1},this._onDimensionsChange=this._register(new c.Emitter),this.onDimensionsChange=this._onDimensionsChange.event,this._onRenderedViewportChange=this._register(new c.Emitter),this.onRenderedViewportChange=this._onRenderedViewportChange.event,this._onRender=this._register(new c.Emitter),this.onRender=this._onRender.event,this._onRefreshRequest=this._register(new c.Emitter),this.onRefreshRequest=this._onRefreshRequest.event,this._pausedResizeTask=this._register(new h.DebouncedIdleTask(this._logService)),this._renderDebouncer=new o.RenderDebouncer((e,t)=>this._renderRows(e,t),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new _(this._coreBrowserService,this._coreService,()=>this._fullRefresh()),this._register((0,a.toDisposable)(()=>this._syncOutputHandler.dispose())),this._register(this._coreBrowserService.onDprChange(()=>this.handleDevicePixelRatioChange())),this._register(d.onResize(()=>this._fullRefresh())),this._register(d.buffers.onBufferActivate(()=>this._renderer.value?.clear())),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this._register(l.onDecorationRegistered(()=>this._fullRefresh())),this._register(l.onDecorationRemoved(()=>this._fullRefresh())),this._register(this._optionsService.onMultipleOptionChange(["drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio","rescaleOverlappingGlyphs"],()=>{this.clear(),this.handleResize(d.cols,d.rows),this._fullRefresh()})),this._register(this._optionsService.onMultipleOptionChange(["cursorBlink","cursorStyle"],()=>this.refreshRows(d.buffer.y,d.buffer.y,void 0,!0))),this._register(f.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(this._coreBrowserService.window,t),this._register(this._coreBrowserService.onWindowChange(e=>this._registerIntersectionObserver(e,t)))}_registerIntersectionObserver(e,t){if("IntersectionObserver"in e){const i=new e.IntersectionObserver(e=>this._handleIntersectionChange(e[e.length-1]),{threshold:0});this._observerDisposable.value=(0,a.toDisposable)(()=>{this._intersectionObserver?.disconnect(),this._intersectionObserver=void 0}),this._intersectionObserver=i,i.observe(t)}}_handleIntersectionChange(e){this._isPaused=void 0===e.isIntersecting?0===e.intersectionRatio:!e.isIntersecting,this._renderer.value?.handleViewportVisibilityChange?.(!this._isPaused),this._isPaused||this._charSizeService.hasValidSize||this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,i=!1,s=!1){if(this._isPaused)return void(this._needsFullRefresh=!0);if(this._coreService.decPrivateModes.synchronizedOutput)return void this._syncOutputHandler.bufferRows(e,t);const r=this._syncOutputHandler.flush();r&&(e=Math.min(e,r.start),t=Math.max(t,r.end)),s||(this._isNextRenderRedrawOnly=!1),i?this._renderRows(e,t):this._renderDebouncer.refresh(e,t,this._rowCount)}_renderRows(e,t){this._renderer.value&&(this._coreService.decPrivateModes.synchronizedOutput?this._syncOutputHandler.bufferRows(e,t):(e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0))}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value&&(this._renderer.value.onRequestRedraw(e=>this.refreshRows(e.start,e.end,e.sync,!0)),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){this._renderer.value&&(this._renderer.value.clearTextureAtlas?.(),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>this._renderer.value?.handleResize(e,t)):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,t,i){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=i,this._renderer.value?.handleSelectionChanged(e,t,i)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};t.RenderService=d,t.RenderService=d=s([r(2,l.IOptionsService),r(3,l.ILogService),r(4,n.ICharSizeService),r(5,l.ICoreService),r(6,l.IDecorationService),r(7,l.IBufferService),r(8,n.ICoreBrowserService),r(9,n.IThemeService)],d);class _{constructor(e,t,i){this._coreBrowserService=e,this._coreService=t,this._onTimeout=i,this._start=0,this._end=0,this._isBuffering=!1}bufferRows(e,t){this._isBuffering?(this._start=Math.min(this._start,e),this._end=Math.max(this._end,t)):(this._start=e,this._end=t,this._isBuffering=!0),this._timeout??=this._coreBrowserService.window.setTimeout(()=>{this._timeout=void 0,this._coreService.decPrivateModes.synchronizedOutput=!1,this._onTimeout()},1e3)}flush(){if(void 0!==this._timeout&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0),!this._isBuffering)return;const e={start:this._start,end:this._end};return this._isBuffering=!1,e}dispose(){void 0!==this._timeout&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}}},2079(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},a=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._handleMouseMove(e),this._mouseUpListener=e=>this._handleMouseUp(e),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener.value=this._bufferService.buffer.lines.onTrim(e=>this._handleTrim(e)),this._register(this._bufferService.buffers.onBufferActivate(e=>this._handleBufferActivate(e))),this.enable(),this._model=new d.SelectionModel(this._bufferService),this._activeSelectionMode=0,this._register((0,u.toDisposable)(()=>{this._removeMouseDownListeners()})),this._register(this._bufferService.onResize(e=>{e.rowsChanged&&this.clearSelection()}))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!(!e||!t||e[0]===t[0]&&e[1]===t[1])}get selectionText(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";const i=this._bufferService.buffer,s=[];if(3===this._activeSelectionMode){if(e[0]===t[0])return"";const r=e[0]e.replace(b," ")).join(f.isWindows?"\r\n":"\n")}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh())),f.isLinux&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:3===this._activeSelectionMode})}_isClickInSelection(e){const t=this._getMouseBufferCoords(e),i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!!(i&&s&&t)&&this._areCoordsInSelection(t,i,s)}isCellInSelection(e,t){const i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!(!i||!s)&&this._areCoordsInSelection([e,t],i,s)}_areCoordsInSelection(e,t,i){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){const i=this._linkifier.currentLink?.link?.range;if(i)return this._model.selectionStart=[i.start.x-1,i.start.y-1],this._model.selectionStartLength=(0,p.getRangeLength)(i,this._bufferService.cols),this._model.selectionEnd=void 0,!0;const s=this._getMouseBufferCoords(e);return!!s&&(this._selectWordAt(s,t),this._model.selectionEnd=void 0,!0)}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){const t=this._mouseCoordsService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t}_getMouseEventScrollAmount(e){let t=(0,l.getCoordsRelativeToElement)(this._coreBrowserService.window,e,this._screenElement)[1];const i=this._renderService.dimensions.css.canvas.height;return t>=0&&t<=i?0:(t>i&&(t-=i),t=Math.min(Math.max(t,-50),50),t/=50,t/Math.abs(t)+Math.round(14*t))}shouldForceSelection(e){return this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive?!e.altKey:f.isMac?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}handleMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,!(2===e.button&&this.hasSelection||0!==e.button||this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive&&e.altKey)){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._handleIncrementalClick(e):1===e.detail?this._handleSingleClick(e):2===e.detail?this._handleDoubleClick(e):3===e.detail&&this._handleTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval(()=>this._dragScroll(),50)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_handleSingleClick(e){const t=this.hasSelection;if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),!this._model.selectionStart)return;this._model.selectionEnd=void 0,t&&this._fireOnSelectionChange(this._model.finalSelectionStart,this._model.finalSelectionEnd,!1);const i=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);i&&i.length!==this._model.selectionStart[0]&&0===i.hasWidth(this._model.selectionStart[0])&&this._model.selectionStart[0]++}_handleDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(e){const t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))}shouldColumnSelect(e){return(!this._optionsService.rawOptions.mouseEventsRequireAlt||!this._mouseStateService.areMouseEventsActive)&&e.altKey&&!(f.isMac&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;const t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),!this._model.selectionEnd)return void this.refresh(!0);2===this._activeSelectionMode?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));const i=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows-1,e.lines.length-1)):(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}}_handleMouseUp(e){const t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&t<500&&e.altKey&&this._optionsService.rawOptions.altClickMovesCursor){if(this._bufferService.buffer.ybase===this._bufferService.buffer.ydisp){const t=this._mouseCoordsService.getCoords(e,this._element,this._bufferService.cols,this._bufferService.rows,!1);if(t&&void 0!==t[0]&&void 0!==t[1]){const e=(0,c.moveToCellSequence)(t[0]-1,t[1]-1,this._bufferService,this._coreService.decPrivateModes.applicationCursorKeys);this._coreService.triggerDataEvent(e,!0)}}}else this._fireEventIfSelectionChanged()}_fireEventIfSelectionChanged(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd,i=!(!e||!t||e[0]===t[0]&&e[1]===t[1]);i?e&&t&&(this._oldSelectionStart&&this._oldSelectionEnd&&e[0]===this._oldSelectionStart[0]&&e[1]===this._oldSelectionStart[1]&&t[0]===this._oldSelectionEnd[0]&&t[1]===this._oldSelectionEnd[1]||this._fireOnSelectionChange(e,t,i)):this._oldHasSelection&&this._fireOnSelectionChange(e,t,i)}_fireOnSelectionChange(e,t,i){this._oldSelectionStart=e,this._oldSelectionEnd=t,this._oldHasSelection=i,this._onSelectionChange.fire()}_handleBufferActivate(e){this.clearSelection(),this._trimListener.value=e.activeBuffer.lines.onTrim(e=>this._handleTrim(e))}_convertViewportColToCharacterIndex(e,t){let i=t;for(let s=0;t>=s;s++){const r=e.loadCell(s,this._workCell).getChars().length;0===this._workCell.getWidth()?i--:r>1&&t!==s&&(i+=r-1)}return i}setSelection(e,t,i){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=i,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,t,i=!0,s=!0){if(e[0]>=this._bufferService.cols)return;const r=this._bufferService.buffer,o=r.lines.get(e[1]);if(!o)return;const n=r.translateBufferLineToString(e[1],!1);let a=this._convertViewportColToCharacterIndex(o,e[0]),h=a;const l=e[0]-a;let c=0,d=0,_=0,u=0;if(" "===n.charAt(a)){for(;a>0&&" "===n.charAt(a-1);)a--;for(;h1&&(u+=s-1,h+=s-1);t>0&&a>0&&!this._isCharWordSeparator(o.loadCell(t-1,this._workCell));){o.loadCell(t-1,this._workCell);const e=this._workCell.getChars().length;0===this._workCell.getWidth()?(c++,t--):e>1&&(_+=e-1,a-=e-1),a--,t--}for(;i1&&(u+=e-1,h+=e-1),h++,i++}}h++;let f=a+l-c+_,p=Math.min(this._bufferService.cols,h-a+c+d-_-u);if(t||""!==n.slice(a,h).trim()){if(i&&0===f&&32!==o.getCodePoint(0)){const t=r.lines.get(e[1]-1);if(t&&o.isWrapped&&32!==t.getCodePoint(this._bufferService.cols-1)){const t=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(t){const e=this._bufferService.cols-t.start;f-=e,p+=e}}}if(s&&f+p===this._bufferService.cols&&32!==o.getCodePoint(this._bufferService.cols-1)){const t=r.lines.get(e[1]+1);if(t?.isWrapped&&32!==t.getCodePoint(0)){const t=this._getWordAt([0,e[1]+1],!1,!1,!0);t&&(p+=t.length)}}return{start:f,length:p}}}_selectWordAt(e,t){const i=this._getWordAt(e,t);if(i){for(;i.start<0;)i.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[i.start,e[1]],this._model.selectionStartLength=i.length}}_selectToWordAt(e){const t=this._getWordAt(e,!0);if(t){let i=e[1];for(;t.start<0;)t.start+=this._bufferService.cols,i--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,i++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,i]}}_isCharWordSeparator(e){return 0!==e.getWidth()&&this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){const t=this._bufferService.buffer.getWrappedRangeForLine(e),i={start:{x:0,y:t.first},end:{x:this._bufferService.cols-1,y:t.last}};this._model.selectionStart=[0,t.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=(0,p.getRangeLength)(i,this._bufferService.cols)}};t.SelectionService=w,t.SelectionService=w=n([h(3,g.IBufferService),h(4,g.ICoreService),h(5,_.IMouseCoordsService),h(6,g.IOptionsService),h(7,g.IMouseStateService),h(8,_.IRenderService),h(9,_.ICoreBrowserService)],w)},7098(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.IKeyboardService=t.ILinkProviderService=t.IThemeService=t.ICharacterJoinerService=t.ISelectionService=t.IRenderService=t.IMouseService=t.IMouseCoordsService=t.ICoreBrowserService=t.ICharSizeService=void 0;const s=i(6201);t.ICharSizeService=(0,s.createDecorator)("CharSizeService"),t.ICoreBrowserService=(0,s.createDecorator)("CoreBrowserService"),t.IMouseCoordsService=(0,s.createDecorator)("MouseCoordsService"),t.IMouseService=(0,s.createDecorator)("MouseService"),t.IRenderService=(0,s.createDecorator)("RenderService"),t.ISelectionService=(0,s.createDecorator)("SelectionService"),t.ICharacterJoinerService=(0,s.createDecorator)("CharacterJoinerService"),t.IThemeService=(0,s.createDecorator)("ThemeService"),t.ILinkProviderService=(0,s.createDecorator)("LinkProviderService"),t.IKeyboardService=(0,s.createDecorator)("KeyboardService")},9078(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.ThemeService=void 0;const o=i(7174),n=i(9302),a=i(4103),h=i(4812),l=i(6501),c=i(8636),d=a.css.toColor("#ffffff"),_=a.css.toColor("#000000"),u=a.css.toColor("#ffffff"),f=_,p={css:"rgba(255, 255, 255, 0.3)",rgba:4294967117},v=d;let g=class extends h.Disposable{get colors(){return this._colors}constructor(e){super(),this._optionsService=e,this._contrastCache=new o.ColorContrastCache,this._halfContrastCache=new o.ColorContrastCache,this._onChangeColors=this._register(new c.Emitter),this.onChangeColors=this._onChangeColors.event,this._colors={foreground:d,background:_,cursor:u,cursorAccent:f,selectionForeground:void 0,selectionBackgroundTransparent:p,selectionBackgroundOpaque:a.color.blend(_,p),selectionInactiveBackgroundTransparent:p,selectionInactiveBackgroundOpaque:a.color.blend(_,p),scrollbarSliderBackground:a.color.opacity(d,.2),scrollbarSliderHoverBackground:a.color.opacity(d,.4),scrollbarSliderActiveBackground:a.color.opacity(d,.5),overviewRulerBorder:d,ansi:n.DEFAULT_ANSI_COLORS.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this._register(this._optionsService.onSpecificOptionChange("minimumContrastRatio",()=>this._contrastCache.clear())),this._register(this._optionsService.onSpecificOptionChange("theme",()=>this._setTheme(this._optionsService.rawOptions.theme)))}_setTheme(e={}){const t=this._colors;if(t.foreground=m(e.foreground,d),t.background=m(e.background,_),t.cursor=a.color.blend(t.background,m(e.cursor,u)),t.cursorAccent=a.color.blend(t.background,m(e.cursorAccent,f)),t.selectionBackgroundTransparent=m(e.selectionBackground,p),t.selectionBackgroundOpaque=a.color.blend(t.background,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundTransparent=m(e.selectionInactiveBackground,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundOpaque=a.color.blend(t.background,t.selectionInactiveBackgroundTransparent),t.selectionForeground=e.selectionForeground?m(e.selectionForeground,a.NULL_COLOR):void 0,t.selectionForeground===a.NULL_COLOR&&(t.selectionForeground=void 0),a.color.isOpaque(t.selectionBackgroundTransparent)){const e=.3;t.selectionBackgroundTransparent=a.color.opacity(t.selectionBackgroundTransparent,e)}if(a.color.isOpaque(t.selectionInactiveBackgroundTransparent)){const e=.3;t.selectionInactiveBackgroundTransparent=a.color.opacity(t.selectionInactiveBackgroundTransparent,e)}if(t.scrollbarSliderBackground=m(e.scrollbarSliderBackground,a.color.opacity(t.foreground,.2)),t.scrollbarSliderHoverBackground=m(e.scrollbarSliderHoverBackground,a.color.opacity(t.foreground,.4)),t.scrollbarSliderActiveBackground=m(e.scrollbarSliderActiveBackground,a.color.opacity(t.foreground,.5)),t.overviewRulerBorder=m(e.overviewRulerBorder,v),t.ansi=n.DEFAULT_ANSI_COLORS.slice(),t.ansi[0]=m(e.black,n.DEFAULT_ANSI_COLORS[0]),t.ansi[1]=m(e.red,n.DEFAULT_ANSI_COLORS[1]),t.ansi[2]=m(e.green,n.DEFAULT_ANSI_COLORS[2]),t.ansi[3]=m(e.yellow,n.DEFAULT_ANSI_COLORS[3]),t.ansi[4]=m(e.blue,n.DEFAULT_ANSI_COLORS[4]),t.ansi[5]=m(e.magenta,n.DEFAULT_ANSI_COLORS[5]),t.ansi[6]=m(e.cyan,n.DEFAULT_ANSI_COLORS[6]),t.ansi[7]=m(e.white,n.DEFAULT_ANSI_COLORS[7]),t.ansi[8]=m(e.brightBlack,n.DEFAULT_ANSI_COLORS[8]),t.ansi[9]=m(e.brightRed,n.DEFAULT_ANSI_COLORS[9]),t.ansi[10]=m(e.brightGreen,n.DEFAULT_ANSI_COLORS[10]),t.ansi[11]=m(e.brightYellow,n.DEFAULT_ANSI_COLORS[11]),t.ansi[12]=m(e.brightBlue,n.DEFAULT_ANSI_COLORS[12]),t.ansi[13]=m(e.brightMagenta,n.DEFAULT_ANSI_COLORS[13]),t.ansi[14]=m(e.brightCyan,n.DEFAULT_ANSI_COLORS[14]),t.ansi[15]=m(e.brightWhite,n.DEFAULT_ANSI_COLORS[15]),e.extendedAnsi){const i=Math.min(t.ansi.length-16,e.extendedAnsi.length);for(let s=0;ssetTimeout(t,e))},t.disposableTimeout=function(e,t=0,i){const r=setTimeout(()=>{e(),i&&o.dispose()},t),o=(0,s.toDisposable)(()=>{clearTimeout(r)});return i?.add(o),o};const s=i(4812);t.TimeoutTimer=class{constructor(){this._token=-1,this._isDisposed=!1}dispose(){this.cancel(),this._isDisposed=!0}cancel(){-1!==this._token&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(e,t){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout(()=>{this._token=-1,e()},t)}setIfNotSet(e,t){if(this._isDisposed)throw new Error("Calling setIfNotSet on a disposed TimeoutTimer");-1===this._token&&(this._token=setTimeout(()=>{this._token=-1,e()},t))}},t.MicrotaskTimer=class{constructor(){this._isScheduled=!1,this._isDisposed=!1}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._isScheduled=!1}set(e){if(this._isDisposed)throw new Error("Calling set on a disposed MicrotaskTimer");this._isScheduled||(this._isScheduled=!0,queueMicrotask(()=>{this._isScheduled&&(this._isScheduled=!1,e())}))}},t.IntervalTimer=class{constructor(){this._isDisposed=!1}cancel(){this._disposable?.dispose(),this._disposable=void 0}cancelAndSet(e,t,i=globalThis){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed IntervalTimer");this.cancel();const s=i.setInterval(()=>{e()},t);this._disposable={dispose:()=>{i.clearInterval(s),this._disposable=void 0}}}dispose(){this.cancel(),this._isDisposed=!0}}},5639(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.CircularList=void 0;const s=i(4812),r=i(8636);class o extends s.Disposable{constructor(e){super(),this._maxLength=e,this.onDeleteEmitter=this._register(new r.Emitter),this.onDelete=this.onDeleteEmitter.event,this.onInsertEmitter=this._register(new r.Emitter),this.onInsert=this.onInsertEmitter.event,this.onTrimEmitter=this._register(new r.Emitter),this.onTrim=this.onTrimEmitter.event,this._array=new Array(this._maxLength),this._startIndex=0,this._length=0}get maxLength(){return this._maxLength}set maxLength(e){if(this._maxLength===e)return;const t=new Array(e);for(let i=0;ithis._length)for(let t=this._length;t=e;t--)this._array[this._getCyclicIndex(t+i.length)]=this._array[this._getCyclicIndex(t)];for(let t=0;tthis._maxLength){const e=this._length+i.length-this._maxLength;this._startIndex+=e,this._length=this._maxLength,this.onTrimEmitter.fire(e)}else this._length+=i.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,t,i){if(!(t<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+i<0)throw new Error("Cannot shift elements in list beyond index 0");if(i>0){for(let s=t-1;s>=0;s--)this.set(e+s+i,this.get(e+s));const s=e+t+i-this._length;if(s>0)for(this._length+=s;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let s=0;s>>0},e.toColor=function(t,i,s,r){return{css:e.toCss(t,i,s,r),rgba:e.toRgba(t,i,s,r)}}}(n||(t.channels=n={})),function(e){function t(e,t){return o=Math.round(255*t),[i,s,r]=c.toChannels(e.rgba),{css:n.toCss(i,s,r,o),rgba:n.toRgba(i,s,r,o)}}e.blend=function(e,t){if(o=(255&t.rgba)/255,1===o)return{css:t.css,rgba:t.rgba};const a=t.rgba>>24&255,h=t.rgba>>16&255,l=t.rgba>>8&255,c=e.rgba>>24&255,d=e.rgba>>16&255,_=e.rgba>>8&255;return i=c+Math.round((a-c)*o),s=d+Math.round((h-d)*o),r=_+Math.round((l-_)*o),{css:n.toCss(i,s,r),rgba:n.toRgba(i,s,r)}},e.isOpaque=function(e){return!(255&~e.rgba)},e.ensureContrastRatio=function(e,t,i){const s=c.ensureContrastRatio(e.rgba,t.rgba,i);if(s)return n.toColor(s>>24&255,s>>16&255,s>>8&255)},e.opaque=function(e){const t=(255|e.rgba)>>>0;return[i,s,r]=c.toChannels(t),{css:n.toCss(i,s,r),rgba:t}},e.opacity=t,e.multiplyOpacity=function(e,i){return o=255&e.rgba,t(e,o*i/255)},e.toColorRGB=function(e){return[e.rgba>>24&255,e.rgba>>16&255,e.rgba>>8&255]}}(a||(t.color=a={})),function(e){let t,a;try{const e=document.createElement("canvas");e.width=1,e.height=1;const i=e.getContext("2d",{willReadFrequently:!0});i&&(t=i,t.globalCompositeOperation="copy",a=t.createLinearGradient(0,0,1,1))}catch{}e.toColor=function(e){if(e.match(/#[\da-f]{3,8}/i))switch(e.length){case 4:return i=parseInt(e.slice(1,2).repeat(2),16),s=parseInt(e.slice(2,3).repeat(2),16),r=parseInt(e.slice(3,4).repeat(2),16),n.toColor(i,s,r);case 5:return i=parseInt(e.slice(1,2).repeat(2),16),s=parseInt(e.slice(2,3).repeat(2),16),r=parseInt(e.slice(3,4).repeat(2),16),o=parseInt(e.slice(4,5).repeat(2),16),n.toColor(i,s,r,o);case 7:return{css:e,rgba:(parseInt(e.slice(1),16)<<8|255)>>>0};case 9:return{css:e,rgba:parseInt(e.slice(1),16)>>>0}}const h=e.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(h)return i=parseInt(h[1],10),s=parseInt(h[2],10),r=parseInt(h[3],10),o=Math.round(255*(void 0===h[5]?1:parseFloat(h[5]))),n.toColor(i,s,r,o);if("transparent"===e)return{css:"transparent",rgba:0};if(!t||!a)throw new Error("css.toColor: Unsupported css format");if(t.fillStyle=a,t.fillStyle=e,"string"!=typeof t.fillStyle)throw new Error("css.toColor: Unsupported css format");if(t.fillRect(0,0,1,1),[i,s,r,o]=t.getImageData(0,0,1,1).data,255!==o)throw new Error("css.toColor: Unsupported css format");return{rgba:n.toRgba(i,s,r,o),css:e}}}(h||(t.css=h={})),function(e){function t(e,t,i){const s=e/255,r=t/255,o=i/255;return.2126*(s<=.03928?s/12.92:Math.pow((s+.055)/1.055,2.4))+.7152*(r<=.03928?r/12.92:Math.pow((r+.055)/1.055,2.4))+.0722*(o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4))}e.relativeLuminance=function(e){return t(e>>16&255,e>>8&255,255&e)},e.relativeLuminance2=t}(l||(t.rgb=l={})),function(e){function t(e,t,i){const s=e>>24&255,r=e>>16&255,o=e>>8&255;let n=t>>24&255,a=t>>16&255,h=t>>8&255,c=_(l.relativeLuminance2(n,a,h),l.relativeLuminance2(s,r,o));for(;c0||a>0||h>0);)n-=Math.max(0,Math.ceil(.1*n)),a-=Math.max(0,Math.ceil(.1*a)),h-=Math.max(0,Math.ceil(.1*h)),c=_(l.relativeLuminance2(n,a,h),l.relativeLuminance2(s,r,o));return(n<<24|a<<16|h<<8|255)>>>0}function a(e,t,i){const s=e>>24&255,r=e>>16&255,o=e>>8&255;let n=t>>24&255,a=t>>16&255,h=t>>8&255,c=_(l.relativeLuminance2(n,a,h),l.relativeLuminance2(s,r,o));for(;c>>0}e.blend=function(e,t){if(o=(255&t)/255,1===o)return t;const a=t>>24&255,h=t>>16&255,l=t>>8&255,c=e>>24&255,d=e>>16&255,_=e>>8&255;return i=c+Math.round((a-c)*o),s=d+Math.round((h-d)*o),r=_+Math.round((l-_)*o),n.toRgba(i,s,r)},e.ensureContrastRatio=function(e,i,s){const r=l.relativeLuminance(e>>8),o=l.relativeLuminance(i>>8);if(_(r,o)>8));if(n_(r,l.relativeLuminance(t>>8))?o:t}return o}const n=a(e,i,s),h=_(r,l.relativeLuminance(n>>8));if(h_(r,l.relativeLuminance(o>>8))?n:o}return n}},e.reduceLuminance=t,e.increaseLuminance=a,e.toChannels=function(e){return[e>>24&255,e>>16&255,e>>8&255,255&e]}}(c||(t.rgba=c={}))},5777(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.CoreTerminal=void 0;const s=i(6501),r=i(6025),o=i(7276),n=i(9640),a=i(56),h=i(4071),l=i(6478),c=i(7428),d=i(6415),_=i(5746),u=i(5882),f=i(2486),p=i(3562),v=i(8811),g=i(8636),m=i(4812);let S=!1;class b extends m.Disposable{get onScroll(){return this._onScrollApi||(this._onScrollApi=this._register(new g.Emitter),this._onScroll.event(e=>{this._onScrollApi?.fire(e.position)})),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(e){for(const t in e)this.optionsService.options[t]=e[t]}constructor(e){super(),this._windowsWrappingHeuristics=this._register(new m.MutableDisposable),this._onBinary=this._register(new g.Emitter),this.onBinary=this._onBinary.event,this._onData=this._register(new g.Emitter),this.onData=this._onData.event,this._onLineFeed=this._register(new g.Emitter),this.onLineFeed=this._onLineFeed.event,this._onRender=this._register(new g.Emitter),this.onRender=this._onRender.event,this._onResize=this._register(new g.Emitter),this.onResize=this._onResize.event,this._onWriteParsed=this._register(new g.Emitter),this.onWriteParsed=this._onWriteParsed.event,this._onScroll=this._register(new g.Emitter),this._instantiationService=new r.InstantiationService,this.optionsService=this._register(new a.OptionsService(e)),this._instantiationService.setService(s.IOptionsService,this.optionsService),this._logService=this._register(this._instantiationService.createInstance(o.LogService)),this._instantiationService.setService(s.ILogService,this._logService),this._bufferService=this._register(this._instantiationService.createInstance(n.BufferService)),this._instantiationService.setService(s.IBufferService,this._bufferService),this.coreService=this._register(this._instantiationService.createInstance(h.CoreService)),this._instantiationService.setService(s.ICoreService,this.coreService),this.mouseStateService=this._register(this._instantiationService.createInstance(l.MouseStateService)),this._instantiationService.setService(s.IMouseStateService,this.mouseStateService),this.unicodeService=this._register(this._instantiationService.createInstance(d.UnicodeService)),this.unicodeService.register(new c.UnicodeV6),this._instantiationService.setService(s.IUnicodeService,this.unicodeService),this._charsetService=this._instantiationService.createInstance(_.CharsetService),this._instantiationService.setService(s.ICharsetService,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(v.OscLinkService),this._instantiationService.setService(s.IOscLinkService,this._oscLinkService),this._inputHandler=this._register(new f.InputHandler(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.mouseStateService,this.unicodeService)),this._register(g.EventUtils.forward(this._inputHandler.onLineFeed,this._onLineFeed)),this._register(g.EventUtils.forward(this._bufferService.onResize,this._onResize)),this._register(g.EventUtils.forward(this.coreService.onData,this._onData)),this._register(g.EventUtils.forward(this.coreService.onBinary,this._onBinary)),this._register(this.coreService.onRequestScrollToBottom(()=>this.scrollToBottom(!0))),this._register(this.coreService.onUserInput(()=>this._writeBuffer.handleUserInput())),this._register(this.optionsService.onMultipleOptionChange(["windowsPty"],()=>this._handleWindowsPtyOptionChange())),this._register(this._bufferService.onScroll(()=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)})),this._writeBuffer=this._register(new p.WriteBuffer((e,t)=>this._inputHandler.parse(e,t))),this._register(g.EventUtils.forward(this._writeBuffer.onWriteParsed,this._onWriteParsed))}write(e,t){this._writeBuffer.write(e,t)}writeSync(e,t){this._logService.logLevel<=s.LogLevelEnum.WARN&&!S&&(this._logService.warn("writeSync is unreliable and will be removed soon."),S=!0),this._writeBuffer.writeSync(e,t)}input(e,t=!0){this.coreService.triggerDataEvent(e,t)}resize(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,2),t=Math.max(t,1),this._writeBuffer.flushSync(),this._bufferService.resize(e,t))}scroll(e,t=!1){this._bufferService.scroll(e,t)}scrollLines(e,t){this._bufferService.scrollLines(e,t)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){const t=e-this._bufferService.buffer.ydisp;0!==t&&this.scrollLines(t)}registerEscHandler(e,t){return this._inputHandler.registerEscHandler(e,t)}registerDcsHandler(e,t){return this._inputHandler.registerDcsHandler(e,t)}registerCsiHandler(e,t){return this._inputHandler.registerCsiHandler(e,t)}registerOscHandler(e,t){return this._inputHandler.registerOscHandler(e,t)}registerApcHandler(e,t){return this._inputHandler.registerApcHandler(e,t)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.mouseStateService.reset()}_handleWindowsPtyOptionChange(){let e=!1;const t=this.optionsService.rawOptions.windowsPty;t&&void 0!==t.backend&&void 0!==t.buildNumber&&(e=!!("conpty"===t.backend&&t.buildNumber<21376)),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){const e=[];e.push(this.onLineFeed(u.updateWindowsModeWrappedState.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:"H"},()=>((0,u.updateWindowsModeWrappedState)(this._bufferService),!1))),this._windowsWrappingHeuristics.value=(0,m.toDisposable)(()=>{for(const t of e)t.dispose()})}}}t.CoreTerminal=b},8636(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.EventUtils=t.Emitter=void 0;const s=i(4812);var r;t.Emitter=class{constructor(){this._listeners=[],this._disposed=!1}get event(){return this._event||(this._event=(e,t,i)=>{if(this._disposed)return(0,s.toDisposable)(()=>{});const r={fn:e,thisArgs:t};this._listeners.push(r);const o=(0,s.toDisposable)(()=>{const e=this._listeners.indexOf(r);-1!==e&&this._listeners.splice(e,1)});return i&&(Array.isArray(i)?i.push(o):i.add(o)),o}),this._event}fire(e){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{const{fn:t,thisArgs:i}=this._listeners[0];return void t.call(i,e)}default:{const t=this._listeners.slice();for(const{fn:i,thisArgs:s}of t)i.call(s,e)}}}dispose(){this._disposed||(this._disposed=!0,this._listeners.length=0)}},function(e){e.forward=function(e,t){return e(e=>t.fire(e))},e.map=function(e,t){return(i,s,r)=>e(e=>i.call(s,t(e)),void 0,r)},e.any=function(...e){return(t,i,r)=>{const o=new s.DisposableStore;for(const s of e)o.add(s(e=>t.call(i,e)));return r&&(Array.isArray(r)?r.push(o):r.add(o)),o}},e.runAndSubscribe=function(e,t,i){return t(i),e(e=>t(e))}}(r||(t.EventUtils=r={}))},2486(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.InputHandler=t.WindowsOptionsReportType=void 0,t.isValidColorIndex=L;const o=i(6760),n=i(6717),a=i(4812),h=i(726),l=i(6107),c=i(8938),d=i(3055),_=i(5451),u=i(6501),f=i(6415),p=i(1346),v=i(9823),g=i(2607),m=i(8693),S=i(8636),b=i(7804),w={"(":0,")":1,"*":2,"+":3,"-":1,".":2};function y(e,t){if(e>24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}var C;!function(e){e[e.GET_WIN_SIZE_PIXELS=0]="GET_WIN_SIZE_PIXELS",e[e.GET_CELL_SIZE_PIXELS=1]="GET_CELL_SIZE_PIXELS"}(C||(t.WindowsOptionsReportType=C={}));let k=0;class D extends a.Disposable{getAttrData(){return this._curAttrData}constructor(e,t,i,s,r,a,c,d,_=new n.EscapeSequenceParser){super(),this._bufferService=e,this._charsetService=t,this._coreService=i,this._logService=s,this._optionsService=r,this._oscLinkService=a,this._mouseStateService=c,this._unicodeService=d,this._parser=_,this._parseBuffer=new Uint32Array(4096),this._stringDecoder=new h.StringToUtf32,this._utf8Decoder=new h.Utf8ToUtf32,this._windowTitle="",this._iconName="",this._windowTitleStack=[],this._iconNameStack=[],this._curAttrData=l.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=l.DEFAULT_ATTR_DATA.clone(),this._onRequestBell=this._register(new S.Emitter),this.onRequestBell=this._onRequestBell.event,this._onRequestRefreshRows=this._register(new S.Emitter),this.onRequestRefreshRows=this._onRequestRefreshRows.event,this._onRequestReset=this._register(new S.Emitter),this.onRequestReset=this._onRequestReset.event,this._onRequestSendFocus=this._register(new S.Emitter),this.onRequestSendFocus=this._onRequestSendFocus.event,this._onRequestSyncScrollBar=this._register(new S.Emitter),this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event,this._onRequestWindowsOptionsReport=this._register(new S.Emitter),this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event,this._onA11yChar=this._register(new S.Emitter),this.onA11yChar=this._onA11yChar.event,this._onA11yTab=this._register(new S.Emitter),this.onA11yTab=this._onA11yTab.event,this._onCursorMove=this._register(new S.Emitter),this.onCursorMove=this._onCursorMove.event,this._onLineFeed=this._register(new S.Emitter),this.onLineFeed=this._onLineFeed.event,this._onScroll=this._register(new S.Emitter),this.onScroll=this._onScroll.event,this._onTitleChange=this._register(new S.Emitter),this.onTitleChange=this._onTitleChange.event,this._onColor=this._register(new S.Emitter),this.onColor=this._onColor.event,this._onRequestColorSchemeQuery=this._register(new S.Emitter),this.onRequestColorSchemeQuery=this._onRequestColorSchemeQuery.event,this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0},this._specialColors=[256,257,258],this._register(this._parser),this._dirtyRowTracker=new E(this._bufferService),this._activeBuffer=this._bufferService.buffer,this._register(this._bufferService.buffers.onBufferActivate(e=>this._activeBuffer=e.activeBuffer)),this._parser.setCsiHandlerFallback((e,t)=>{this._logService.debug("Unknown CSI code: ",{identifier:this._parser.identToString(e),params:t.toArray()})}),this._parser.setEscHandlerFallback(e=>{this._logService.debug("Unknown ESC code: ",{identifier:this._parser.identToString(e)})}),this._parser.setExecuteHandlerFallback(e=>{this._logService.debug("Unknown EXECUTE code: ",{code:e})}),this._parser.setOscHandlerFallback((e,t,i)=>{this._logService.debug("Unknown OSC code: ",{identifier:e,action:t,data:i})}),this._parser.setDcsHandlerFallback((e,t,i)=>{"HOOK"===t&&(i=i.toArray()),this._logService.debug("Unknown DCS code: ",{identifier:this._parser.identToString(e),action:t,payload:i})}),this._parser.setApcHandlerFallback((e,t,i)=>{this._logService.debug("Unknown APC code: ",{identifier:this._parser.identToString(e),action:t,payload:i})}),this._parser.setPrintHandler((e,t,i)=>this.print(e,t,i)),this._parser.registerCsiHandler({final:"@"},e=>this.insertChars(e)),this._parser.registerCsiHandler({intermediates:" ",final:"@"},e=>this.scrollLeft(e)),this._parser.registerCsiHandler({final:"A"},e=>this.cursorUp(e)),this._parser.registerCsiHandler({intermediates:" ",final:"A"},e=>this.scrollRight(e)),this._parser.registerCsiHandler({final:"B"},e=>this.cursorDown(e)),this._parser.registerCsiHandler({final:"C"},e=>this.cursorForward(e)),this._parser.registerCsiHandler({final:"D"},e=>this.cursorBackward(e)),this._parser.registerCsiHandler({final:"E"},e=>this.cursorNextLine(e)),this._parser.registerCsiHandler({final:"F"},e=>this.cursorPrecedingLine(e)),this._parser.registerCsiHandler({final:"G"},e=>this.cursorCharAbsolute(e)),this._parser.registerCsiHandler({final:"H"},e=>this.cursorPosition(e)),this._parser.registerCsiHandler({final:"I"},e=>this.cursorForwardTab(e)),this._parser.registerCsiHandler({final:"J"},e=>this.eraseInDisplay(e,!1)),this._parser.registerCsiHandler({prefix:"?",final:"J"},e=>this.eraseInDisplay(e,!0)),this._parser.registerCsiHandler({final:"K"},e=>this.eraseInLine(e,!1)),this._parser.registerCsiHandler({prefix:"?",final:"K"},e=>this.eraseInLine(e,!0)),this._parser.registerCsiHandler({final:"L"},e=>this.insertLines(e)),this._parser.registerCsiHandler({final:"M"},e=>this.deleteLines(e)),this._parser.registerCsiHandler({final:"P"},e=>this.deleteChars(e)),this._parser.registerCsiHandler({final:"S"},e=>this.scrollUp(e)),this._parser.registerCsiHandler({final:"T"},e=>this.scrollDown(e)),this._parser.registerCsiHandler({final:"X"},e=>this.eraseChars(e)),this._parser.registerCsiHandler({final:"Z"},e=>this.cursorBackwardTab(e)),this._parser.registerCsiHandler({final:"^"},e=>this.scrollDown(e)),this._parser.registerCsiHandler({final:"`"},e=>this.charPosAbsolute(e)),this._parser.registerCsiHandler({final:"a"},e=>this.hPositionRelative(e)),this._parser.registerCsiHandler({final:"b"},e=>this.repeatPrecedingCharacter(e)),this._parser.registerCsiHandler({final:"c"},e=>this.sendDeviceAttributesPrimary(e)),this._parser.registerCsiHandler({prefix:">",final:"c"},e=>this.sendDeviceAttributesSecondary(e)),this._parser.registerCsiHandler({final:"d"},e=>this.linePosAbsolute(e)),this._parser.registerCsiHandler({final:"e"},e=>this.vPositionRelative(e)),this._parser.registerCsiHandler({final:"f"},e=>this.hVPosition(e)),this._parser.registerCsiHandler({final:"g"},e=>this.tabClear(e)),this._parser.registerCsiHandler({final:"h"},e=>this.setMode(e)),this._parser.registerCsiHandler({prefix:"?",final:"h"},e=>this.setModePrivate(e)),this._parser.registerCsiHandler({final:"l"},e=>this.resetMode(e)),this._parser.registerCsiHandler({prefix:"?",final:"l"},e=>this.resetModePrivate(e)),this._parser.registerCsiHandler({final:"m"},e=>this.charAttributes(e)),this._parser.registerCsiHandler({final:"n"},e=>this.deviceStatus(e)),this._parser.registerCsiHandler({prefix:"?",final:"n"},e=>this.deviceStatusPrivate(e)),this._parser.registerCsiHandler({intermediates:"!",final:"p"},e=>this.softReset(e)),this._parser.registerCsiHandler({prefix:">",final:"q"},e=>this.sendXtVersion(e)),this._parser.registerCsiHandler({intermediates:" ",final:"q"},e=>this.setCursorStyle(e)),this._parser.registerCsiHandler({final:"r"},e=>this.setScrollRegion(e)),this._parser.registerCsiHandler({final:"s"},e=>this.saveCursor(e)),this._parser.registerCsiHandler({final:"t"},e=>this.windowOptions(e)),this._parser.registerCsiHandler({final:"u"},e=>this.restoreCursor(e)),this._parser.registerCsiHandler({intermediates:"'",final:"}"},e=>this.insertColumns(e)),this._parser.registerCsiHandler({intermediates:"'",final:"~"},e=>this.deleteColumns(e)),this._parser.registerCsiHandler({intermediates:'"',final:"q"},e=>this.selectProtected(e)),this._parser.registerCsiHandler({intermediates:"$",final:"p"},e=>this.requestMode(e,!0)),this._parser.registerCsiHandler({prefix:"?",intermediates:"$",final:"p"},e=>this.requestMode(e,!1)),this._parser.registerCsiHandler({prefix:"=",final:"u"},e=>this.kittyKeyboardSet(e)),this._parser.registerCsiHandler({prefix:"?",final:"u"},e=>this.kittyKeyboardQuery(e)),this._parser.registerCsiHandler({prefix:">",final:"u"},e=>this.kittyKeyboardPush(e)),this._parser.registerCsiHandler({prefix:"<",final:"u"},e=>this.kittyKeyboardPop(e)),this._parser.setExecuteHandler("",()=>this.bell()),this._parser.setExecuteHandler("\n",()=>this.lineFeed()),this._parser.setExecuteHandler("\v",()=>this.lineFeed()),this._parser.setExecuteHandler("\f",()=>this.lineFeed()),this._parser.setExecuteHandler("\r",()=>this.carriageReturn()),this._parser.setExecuteHandler("\b",()=>this.backspace()),this._parser.setExecuteHandler("\t",()=>this.tab()),this._parser.setExecuteHandler("",()=>this.shiftOut()),this._parser.setExecuteHandler("",()=>this.shiftIn()),this._parser.setExecuteHandler("„",()=>this.index()),this._parser.setExecuteHandler("…",()=>this.nextLine()),this._parser.setExecuteHandler("ˆ",()=>this.tabSet()),this._parser.registerOscHandler(0,new p.OscHandler(e=>(this.setTitle(e),this.setIconName(e),!0))),this._parser.registerOscHandler(1,new p.OscHandler(e=>this.setIconName(e))),this._parser.registerOscHandler(2,new p.OscHandler(e=>this.setTitle(e))),this._parser.registerOscHandler(4,new p.OscHandler(e=>this.setOrReportIndexedColor(e))),this._parser.registerOscHandler(8,new p.OscHandler(e=>this.setHyperlink(e))),this._parser.registerOscHandler(10,new p.OscHandler(e=>this.setOrReportFgColor(e))),this._parser.registerOscHandler(11,new p.OscHandler(e=>this.setOrReportBgColor(e))),this._parser.registerOscHandler(12,new p.OscHandler(e=>this.setOrReportCursorColor(e))),this._parser.registerOscHandler(104,new p.OscHandler(e=>this.restoreIndexedColor(e))),this._parser.registerOscHandler(110,new p.OscHandler(e=>this.restoreFgColor(e))),this._parser.registerOscHandler(111,new p.OscHandler(e=>this.restoreBgColor(e))),this._parser.registerOscHandler(112,new p.OscHandler(e=>this.restoreCursorColor(e))),this._parser.registerEscHandler({final:"7"},()=>this.saveCursor()),this._parser.registerEscHandler({final:"8"},()=>this.restoreCursor()),this._parser.registerEscHandler({final:"D"},()=>this.index()),this._parser.registerEscHandler({final:"E"},()=>this.nextLine()),this._parser.registerEscHandler({final:"H"},()=>this.tabSet()),this._parser.registerEscHandler({final:"M"},()=>this.reverseIndex()),this._parser.registerEscHandler({final:"="},()=>this.keypadApplicationMode()),this._parser.registerEscHandler({final:">"},()=>this.keypadNumericMode()),this._parser.registerEscHandler({final:"c"},()=>this.fullReset()),this._parser.registerEscHandler({final:"n"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"o"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"|"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"}"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"~"},()=>this.setgLevel(1)),this._parser.registerEscHandler({intermediates:"%",final:"@"},()=>this.selectDefaultCharset()),this._parser.registerEscHandler({intermediates:"%",final:"G"},()=>this.selectDefaultCharset());for(const e in o.CHARSETS)this._parser.registerEscHandler({intermediates:"(",final:e},()=>this.selectCharset("("+e)),this._parser.registerEscHandler({intermediates:")",final:e},()=>this.selectCharset(")"+e)),this._parser.registerEscHandler({intermediates:"*",final:e},()=>this.selectCharset("*"+e)),this._parser.registerEscHandler({intermediates:"+",final:e},()=>this.selectCharset("+"+e)),this._parser.registerEscHandler({intermediates:"-",final:e},()=>this.selectCharset("-"+e)),this._parser.registerEscHandler({intermediates:".",final:e},()=>this.selectCharset("."+e)),this._parser.registerEscHandler({intermediates:"/",final:e},()=>this.selectCharset("/"+e));this._parser.registerEscHandler({intermediates:"#",final:"8"},()=>this.screenAlignmentPattern()),this._parser.setErrorHandler(e=>(this._logService.error("Parsing error: ",e),e)),this._parser.registerDcsHandler({intermediates:"$",final:"q"},new v.DcsHandler((e,t)=>this.requestStatusString(e,t)))}_preserveStack(e,t,i,s){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=t,this._parseStack.decodedLength=i,this._parseStack.position=s}_logSlowResolvingAsync(e){if(this._logService.logLevel<=u.LogLevelEnum.WARN){let t;const i=new Promise((e,i)=>{t=setTimeout(()=>i("#SLOW_TIMEOUT"),5e3)});Promise.race([e,i]).then(()=>{void 0!==t&&clearTimeout(t)},e=>{if(void 0!==t&&clearTimeout(t),"#SLOW_TIMEOUT"!==e)throw e;console.warn("async parser handler taking longer than 5000 ms")})}}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,t){let i,s=this._activeBuffer.x,r=this._activeBuffer.y,o=0;const n=this._parseStack.paused;if(n){if(i=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,t))return this._logSlowResolvingAsync(i),i;s=this._parseStack.cursorStartX,r=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>131072&&(o=this._parseStack.position+131072)}if(this._logService.logLevel<=u.LogLevelEnum.DEBUG&&this._logService.debug("parsing data "+("string"==typeof e?` "${e}"`:` "${Array.prototype.map.call(e,e=>String.fromCharCode(e)).join("")}"`)),this._logService.logLevel===u.LogLevelEnum.TRACE&&this._logService.trace("parsing data (codes)","string"==typeof e?e.split("").map(e=>e.charCodeAt(0)):e),this._parseBuffer.length131072)for(let t=o;t0&&2===p.getWidth(this._activeBuffer.x-1)&&p.setCellFromCodepoint(this._activeBuffer.x-1,0,1,u);let v=this._parser.precedingJoinState;for(let g=t;ga)if(d){const e=p;let t=this._activeBuffer.x-m;if(this._activeBuffer.x=m,this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData(),!0)):(this._activeBuffer.y>=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),p=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),!p)return;for(m>0&&p instanceof l.BufferLine&&p.copyCellsFrom(e,t,0,m,!1);t=0;)p.setCellFromCodepoint(this._activeBuffer.x++,0,0,u);continue}if(_&&(p.insertCells(this._activeBuffer.x,r-m,this._activeBuffer.getNullCell(u)),2===p.getWidth(a-1)&&p.setCellFromCodepoint(a-1,c.NULL_CELL_CODE,c.NULL_CELL_WIDTH,u)),p.setCellFromCodepoint(this._activeBuffer.x++,s,r,u),r>0)for(;--r;)p.setCellFromCodepoint(this._activeBuffer.x++,0,0,u)}this._parser.precedingJoinState=v,this._activeBuffer.x0&&0===p.getWidth(this._activeBuffer.x)&&!p.hasContent(this._activeBuffer.x)&&p.setCellFromCodepoint(this._activeBuffer.x,0,1,u),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,t){return"t"!==e.final||e.prefix||e.intermediates?this._parser.registerCsiHandler(e,t):this._parser.registerCsiHandler(e,e=>!y(e.params[0],this._optionsService.rawOptions.windowOptions)||t(e))}registerDcsHandler(e,t){return this._parser.registerDcsHandler(e,new v.DcsHandler(t))}registerEscHandler(e,t){return this._parser.registerEscHandler(e,t)}registerOscHandler(e,t){return this._parser.registerOscHandler(e,new p.OscHandler(t))}registerApcHandler(e,t){return this._parser.registerApcHandler(e,new g.ApcHandler(t))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(0===this._activeBuffer.x&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y)?.isWrapped){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;const e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);e.hasWidth(this._activeBuffer.x)&&!e.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;const e=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-e),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(e=this._bufferService.cols-1){this._activeBuffer.x=Math.min(e,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(e,t){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+t):(this._activeBuffer.x=e,this._activeBuffer.y=t),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,t){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+t)}cursorUp(e){const t=this._activeBuffer.y-this._activeBuffer.scrollTop;return t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){const t=this._activeBuffer.scrollBottom-this._activeBuffer.y;return t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1),!0}cursorForward(e){return this._moveCursor(e.params[0]||1,0),!0}cursorBackward(e){return this._moveCursor(-(e.params[0]||1),0),!0}cursorNextLine(e){return this.cursorDown(e),this._activeBuffer.x=0,!0}cursorPrecedingLine(e){return this.cursorUp(e),this._activeBuffer.x=0,!0}cursorCharAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(e){return this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1),!0}charPosAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(e){return this._moveCursor(e.params[0]||1,0),!0}linePosAbsolute(e){return this._setCursor(this._activeBuffer.x,(e.params[0]||1)-1),!0}vPositionRelative(e){return this._moveCursor(0,e.params[0]||1),!0}hVPosition(e){return this.cursorPosition(e),!0}tabClear(e){const t=e.params[0];return 0===t?delete this._activeBuffer.tabs[this._activeBuffer.x]:3===t&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){const t=e.params[0];return 1===t&&(this._curAttrData.bg|=536870912),2!==t&&0!==t||(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,t,i,s=!1,r=!1){const o=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);o&&(o.replaceCells(t,i,this._activeBuffer.getNullCell(this._eraseAttrData()),r),s&&(o.isWrapped=!1))}_resetBufferLine(e,t=!1){const i=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);i&&(i.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),t),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),i.isWrapped=!1)}eraseInDisplay(e,t=!1){let i;switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:for(i=this._activeBuffer.y,this._dirtyRowTracker.markDirty(i),this._eraseInBufferLine(i++,this._activeBuffer.x,this._bufferService.cols,0===this._activeBuffer.x,t);i=this._bufferService.cols){const e=this._activeBuffer.lines.get(i+1);e&&(e.isWrapped=!1)}for(;i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0);break;case 2:if(this._optionsService.rawOptions.scrollOnEraseInDisplay){for(i=this._bufferService.rows,this._dirtyRowTracker.markRangeDirty(0,i-1);i--;){const e=this._activeBuffer.lines.get(this._activeBuffer.ybase+i);if(e?.getTrimmedLength())break}for(;i>=0;i--)this._bufferService.scroll(this._eraseAttrData())}else{for(i=this._bufferService.rows,this._dirtyRowTracker.markDirty(i-1);i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0)}break;case 3:const e=this._activeBuffer.lines.length-this._bufferService.rows;e>0&&(this._activeBuffer.lines.trimStart(e),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-e,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-e,0),this._onScroll.fire(0))}return!0}eraseInLine(e,t=!1){switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,0===this._activeBuffer.x,t);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,t);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,t)}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let t=e.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y65535?2:1}let h=a;for(let e=1;e0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent("[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent("[?6c")),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent("[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent("[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent("[>83;40003;0c")),!0}sendXtVersion(e){return e.params[0]>0||this._coreService.triggerDataEvent(`P>|xterm.js(${b.XTERM_VERSION})\\`),!0}_is(e){return(this._optionsService.rawOptions.termName+"").startsWith(e)}setMode(e){for(let t=0;t(o.triggerDataEvent(`[${t?"":"?"}${e};${i}$y`),!0),_=e=>e?1:2,u=e.params[0];return t?d(u,2===u?4:4===u?_(o.modes.insertMode):12===u?3:20===u?_(c.convertEol):0):1===u?d(u,_(i.applicationCursorKeys)):3===u?d(u,c.windowOptions.setWinLines?80===a?2:132===a?1:0:0):6===u?d(u,_(i.origin)):7===u?d(u,_(i.wraparound)):8===u?d(u,3):9===u?d(u,_("X10"===s)):12===u?d(u,_(c.cursorBlink)):25===u?d(u,_(!o.isCursorHidden)):45===u?d(u,_(i.reverseWraparound)):66===u?d(u,_(i.applicationKeypad)):67===u?d(u,4):1e3===u?d(u,_("VT200"===s)):1002===u?d(u,_("DRAG"===s)):1003===u?d(u,_("ANY"===s)):1004===u?d(u,_(i.sendFocus)):1005===u?d(u,4):1006===u?d(u,_("SGR"===r)):1015===u?d(u,4):1016===u?d(u,_("SGR_PIXELS"===r)):1048===u?d(u,1):47===u||1047===u||1049===u?d(u,_(h===l)):2004===u?d(u,_(i.bracketedPasteMode)):2026===u?d(u,_(i.synchronizedOutput)):9001===u&&this._optionsService.rawOptions.vtExtensions?.win32InputMode?d(u,_(i.win32InputMode)):d(u,0)}_updateAttrColor(e,t,i,s,r){return 2===t?(e|=50331648,e&=-16777216,e|=_.AttributeData.fromColorRGB([i,s,r])):5===t&&(e&=-67108864,e|=33554432|255&i),e}_extractColor(e,t,i){const s=[0,0,-1,0,0,0];let r=0,o=0;do{if(s[o+r]=e.params[t+o],e.hasSubParams(t+o)){const i=e.getSubParams(t+o);let n=0;do{5===s[1]&&(r=1),s[o+n+1+r]=i[n]}while(++n=2||2===s[1]&&o+r>=5)break;s[1]&&(r=1)}while(++o+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,0===e&&(t.fg&=-268435457),t.updateExtended()}_processSGR0(e){e.fg=l.DEFAULT_ATTR_DATA.fg,e.bg=l.DEFAULT_ATTR_DATA.bg,e.extended=e.extended.clone(),e.extended.underlineStyle=0,e.extended.underlineColor&=-67108864,e.updateExtended()}charAttributes(e){if(1===e.length&&0===e.params[0])return this._processSGR0(this._curAttrData),!0;const t=e.length;let i;const s=this._curAttrData;for(let r=0;r=30&&i<=37?(s.fg&=-67108864,s.fg|=16777216|i-30):i>=40&&i<=47?(s.bg&=-67108864,s.bg|=16777216|i-40):i>=90&&i<=97?(s.fg&=-67108864,s.fg|=16777224|i-90):i>=100&&i<=107?(s.bg&=-67108864,s.bg|=16777224|i-100):0===i?this._processSGR0(s):1===i?s.fg|=134217728:3===i?s.bg|=67108864:4===i?(s.fg|=268435456,this._processUnderline(e.hasSubParams(r)?e.getSubParams(r)[0]:1,s)):5===i?s.fg|=536870912:7===i?s.fg|=67108864:8===i?s.fg|=1073741824:9===i?s.fg|=2147483648:2===i?s.bg|=134217728:21===i?this._processUnderline(2,s):22===i?(s.fg&=-134217729,s.bg&=-134217729):23===i?s.bg&=-67108865:24===i?(s.fg&=-268435457,this._processUnderline(0,s)):25===i?s.fg&=-536870913:27===i?s.fg&=-67108865:28===i?s.fg&=-1073741825:29===i?s.fg&=2147483647:39===i?(s.fg&=-67108864,s.fg|=16777215&l.DEFAULT_ATTR_DATA.fg):49===i?(s.bg&=-67108864,s.bg|=16777215&l.DEFAULT_ATTR_DATA.bg):38===i||48===i||58===i?r+=this._extractColor(e,r,s):53===i?s.bg|=1073741824:55===i?s.bg&=-1073741825:221===i&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??1)?s.fg&=-134217729:222===i&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??1)?s.bg&=-134217729:59===i?(s.extended=s.extended.clone(),s.extended.underlineColor=-1,s.updateExtended()):this._logService.debug("Unknown SGR attribute: %d.",i);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent("");break;case 6:const e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`[${e};${t}R`)}return!0}deviceStatusPrivate(e){switch(e.params[0]){case 6:const e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`[?${e};${t}R`);break;case 15:case 25:case 26:case 53:break;case 996:(this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery??1)&&this._onRequestColorSchemeQuery.fire()}return!0}softReset(e){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=l.DEFAULT_ATTR_DATA.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(e){const t=0===e.length?1:e.params[0];if(0===t)this._coreService.decPrivateModes.cursorStyle=void 0,this._coreService.decPrivateModes.cursorBlink=void 0;else{switch(t){case 1:case 2:this._coreService.decPrivateModes.cursorStyle="block";break;case 3:case 4:this._coreService.decPrivateModes.cursorStyle="underline";break;case 5:case 6:this._coreService.decPrivateModes.cursorStyle="bar"}const e=t%2==1;this._coreService.decPrivateModes.cursorBlink=e}return!0}setScrollRegion(e){const t=e.params[0]||1;let i;return(e.length<2||(i=e.params[1])>this._bufferService.rows||0===i)&&(i=this._bufferService.rows),i>t&&(this._activeBuffer.scrollTop=t-1,this._activeBuffer.scrollBottom=i-1,this._setCursor(0,0)),!0}windowOptions(e){if(!y(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;const t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:2!==t&&this._onRequestWindowsOptionsReport.fire(C.GET_WIN_SIZE_PIXELS);break;case 16:this._onRequestWindowsOptionsReport.fire(C.GET_CELL_SIZE_PIXELS);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:0!==t&&2!==t||(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>10&&this._windowTitleStack.shift()),0!==t&&1!==t||(this._iconNameStack.push(this._iconName),this._iconNameStack.length>10&&this._iconNameStack.shift());break;case 23:0!==t&&2!==t||this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),0!==t&&1!==t||this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop())}return!0}saveCursor(e){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._activeBuffer.savedCharsets=this._charsetService.charsets.slice(),this._activeBuffer.savedGlevel=this._charsetService.glevel,this._activeBuffer.savedOriginMode=this._coreService.decPrivateModes.origin,this._activeBuffer.savedWraparoundMode=this._coreService.decPrivateModes.wraparound,!0}restoreCursor(e){this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg;for(let e=0;e1;){const e=i.shift(),s=i.shift();if(/^\d+$/.exec(e)){const i=parseInt(e,10);if(L(i))if("?"===s)t.push({type:0,index:i});else{const e=(0,m.parseColor)(s);e&&t.push({type:1,index:i,color:e})}}}return t.length&&this._onColor.fire(t),!0}setHyperlink(e){const t=e.indexOf(";");if(-1===t)return!0;const i=e.slice(0,t).trim(),s=e.slice(t+1);return s?this._createHyperlink(i,s):!i.trim()&&this._finishHyperlink()}_createHyperlink(e,t){this._getCurrentLinkId()&&this._finishHyperlink();const i=e.split(":");let s;const r=i.findIndex(e=>e.startsWith("id="));return-1!==r&&(s=i[r].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:s,uri:t}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,t){const i=e.split(";");for(let e=0;e=this._specialColors.length);++e,++t)if("?"===i[e])this._onColor.fire([{type:0,index:this._specialColors[t]}]);else{const s=(0,m.parseColor)(i[e]);s&&this._onColor.fire([{type:1,index:this._specialColors[t],color:s}])}return!0}setOrReportFgColor(e){return this._setOrReportSpecialColor(e,0)}setOrReportBgColor(e){return this._setOrReportSpecialColor(e,1)}setOrReportCursorColor(e){return this._setOrReportSpecialColor(e,2)}restoreIndexedColor(e){if(!e)return this._onColor.fire([{type:2}]),!0;const t=[],i=e.split(";");for(let e=0;e=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){const e=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,e,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=l.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=l.DEFAULT_ATTR_DATA.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=67108863&this._curAttrData.bg,this._eraseAttrDataInternal}setgLevel(e){return this._charsetService.setgLevel(e),!0}screenAlignmentPattern(){const e=new d.CellData;e.content=1<<22|"E".charCodeAt(0),e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let t=0;t(this._coreService.triggerDataEvent(`${e}\\`),!0))('"q'===e?`P1$r${this._curAttrData.isProtected()?1:0}"q`:'"p'===e?'P1$r61;1"p':"r"===e?`P1$r${i.scrollTop+1};${i.scrollBottom+1}r`:"m"===e?"P1$r0m":" q"===e?`P1$r${{block:2,underline:4,bar:6}[s.cursorStyle]-(s.cursorBlink?1:0)} q`:"P0$r")}markRangeDirty(e,t){this._dirtyRowTracker.markRangeDirty(e,t)}kittyKeyboardSet(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;const t=e.params[0]||0,i=e.length>1&&e.params[1]||1,s=this._coreService.kittyKeyboard;switch(i){case 1:s.flags=t;break;case 2:s.flags|=t;break;case 3:s.flags&=~t}return!0}kittyKeyboardQuery(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;const t=this._coreService.kittyKeyboard.flags;return this._coreService.triggerDataEvent(`[?${t}u`),!0}kittyKeyboardPush(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;const t=e.params[0]||0,i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;return s.length>=16&&s.shift(),s.push(i.flags),i.flags=t,!0}kittyKeyboardPop(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;const t=Math.max(1,e.params[0]||1),i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;for(let e=0;e0;e++)i.flags=s.pop();return 0===s.length&&t>0&&(i.flags=0),!0}}t.InputHandler=D;let E=class{constructor(e){this._bufferService=e,this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(e){ethis.end&&(this.end=e)}markRangeDirty(e,t){e>t&&(k=e,e=t,t=k),ethis.end&&(this.end=t)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};function L(e){return 0<=e&&e<256}E=s([r(0,u.IBufferService)],E)},4812(e,t){function i(e){return{dispose:e}}function s(e){if(!e)return e;if(Array.isArray(e)){for(const t of e)t.dispose();return[]}return e.dispose(),e}Object.defineProperty(t,"__esModule",{value:!0}),t.MutableDisposable=t.Disposable=t.DisposableStore=void 0,t.toDisposable=i,t.dispose=s,t.combinedDisposable=function(...e){return i(()=>s(e))};class r{constructor(){this._disposables=new Set,this._isDisposed=!1}get isDisposed(){return this._isDisposed}add(e){return this._isDisposed?e.dispose():this._disposables.add(e),e}dispose(){if(!this._isDisposed){this._isDisposed=!0;for(const e of this._disposables)e.dispose();this._disposables.clear()}}clear(){for(const e of this._disposables)e.dispose();this._disposables.clear()}}t.DisposableStore=r;class o{constructor(){this._store=new r}dispose(){this._store.dispose()}_register(e){return this._store.add(e)}}t.Disposable=o,o.None=Object.freeze({dispose(){}}),t.MutableDisposable=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,this._value?.dispose(),this._value=void 0}}},7710(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.FourKeyMap=t.TwoKeyMap=void 0;class i{constructor(){this._data={}}set(e,t,i){this._data[e]||(this._data[e]={}),this._data[e][t]=i}get(e,t){return this._data[e]?this._data[e][t]:void 0}clear(){this._data={}}}t.TwoKeyMap=i,t.FourKeyMap=class{constructor(){this._data=new i}set(e,t,s,r,o){this._data.get(e,t)||this._data.set(e,t,new i),this._data.get(e,t).set(s,r,o)}get(e,t,i,s){return this._data.get(e,t)?.get(i,s)}clear(){this._data.clear()}}},701(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.isChromeOS=t.isLinux=t.isWindows=t.isMac=t.isSafari=t.isLegacyEdge=t.isChrome=t.isFirefox=t.isNode=void 0,t.getZoomFactor=function(e){return 1},t.getSafariVersion=function(){if(!t.isSafari)return 0;const e=i.match(/Version\/(\d+)/);return null===e||e.length<2?0:parseInt(e[1],10)},t.isNode=!("undefined"==typeof process||!("title"in process)||"undefined"!=typeof navigator&&!navigator.userAgent.startsWith("Node.js/"));const i=t.isNode?"node":navigator.userAgent,s=t.isNode?"node":navigator.platform;t.isFirefox=i.includes("Firefox"),t.isChrome=i.includes("Chrome"),t.isLegacyEdge=i.includes("Edge"),t.isSafari=/^((?!chrome|android).)*safari/i.test(i),t.isMac=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(s),t.isWindows=["Windows","Win16","Win32","WinCE"].includes(s),t.isLinux=s.indexOf("Linux")>=0,t.isChromeOS=/\bCrOS\b/.test(i)},3087(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.SortedList=void 0;const s=i(6168);let r=0;t.SortedList=class{constructor(e,t){this._getKey=e,this._array=[],this._insertedValues=[],this._isFlushingInserted=!1,this._deletedIndices=[],this._isFlushingDeleted=!1,this._flushInsertedTask=new s.IdleTaskQueue(t),this._flushDeletedTask=new s.IdleTaskQueue(t)}clear(){this._array.length=0,this._insertedValues.length=0,this._flushInsertedTask.clear(),this._isFlushingInserted=!1,this._deletedIndices.length=0,this._flushDeletedTask.clear(),this._isFlushingDeleted=!1}insert(e){this._flushCleanupDeleted(),0===this._insertedValues.length&&this._flushInsertedTask.enqueue(()=>this._flushInserted()),this._insertedValues.push(e)}_flushInserted(){const e=this._insertedValues.sort((e,t)=>this._getKey(e)-this._getKey(t));let t=0,i=0;const s=new Array(this._array.length+this._insertedValues.length);for(let r=0;r=this._array.length||this._getKey(e[t])<=this._getKey(this._array[i])?(s[r]=e[t],t++):s[r]=this._array[i++];this._array=s,this._insertedValues.length=0}_flushCleanupInserted(){!this._isFlushingInserted&&this._insertedValues.length>0&&this._flushInsertedTask.flush()}delete(e){if(this._flushCleanupInserted(),0===this._array.length)return!1;const t=this._getKey(e);if(void 0===t)return!1;if(r=this._search(t),-1===r)return!1;if(this._getKey(this._array[r])!==t)return!1;do{if(this._array[r]===e)return 0===this._deletedIndices.length&&this._flushDeletedTask.enqueue(()=>this._flushDeleted()),this._deletedIndices.push(r),!0}while(++re-t);let t=0;const i=new Array(this._array.length-e.length);let s=0;for(let r=0;r0&&this._flushDeletedTask.flush()}*getKeyIterator(e){if(this._flushCleanupInserted(),this._flushCleanupDeleted(),0!==this._array.length&&(r=this._search(e),!(r<0||r>=this._array.length)&&this._getKey(this._array[r])===e))do{yield this._array[r]}while(++r=this._array.length)&&this._getKey(this._array[r])===e))do{t(this._array[r])}while(++r=t;){let s=t+i>>1;const r=this._getKey(this._array[s]);if(r>e)i=s-1;else{if(!(r0&&this._getKey(this._array[s-1])===e;)s--;return s}t=s+1}}return t}}},4220(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.LimitedStringBuilder=t.StringBuilder=void 0;class i{constructor(){this._chunks=[],this._length=0}get length(){return this._length}reset(){this._chunks.length=0,this._length=0}append(e){this._chunks.push(e),this._length+=e.length}toString(){return this._chunks.join("")}}t.StringBuilder=i,t.LimitedStringBuilder=class{constructor(e){this._limit=e,this._builder=new i}get length(){return this._builder.length}get limit(){return this._limit}reset(){this._builder.reset()}append(e){return this._builder.append(e),this._builder.length>this._limit&&(this._builder.reset(),!0)}toString(){return this._builder.toString()}}},6168(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.DebouncedIdleTask=t.IdleTaskQueue=t.PriorityTaskQueue=void 0;class i{constructor(e){this._tasks=[],this._i=0,this._logService=e}enqueue(e){this._tasks.push(e),this._start()}flush(){for(;this._ii)return r-t<-20&&this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(r-t))}ms`),void this._start();r=i}this.clear()}}class s extends i{_requestCallback(e){return setTimeout(()=>e(this._createDeadline(16)))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){const t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}}t.PriorityTaskQueue=s,t.IdleTaskQueue="requestIdleCallback"in globalThis?class extends i{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}}:s,t.DebouncedIdleTask=class{constructor(e){this._queue=new t.IdleTaskQueue(e)}set(e){this._queue.clear(),this._queue.enqueue(e)}flush(){this._queue.flush()}dispose(){this._queue.clear()}}},7804(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.XTERM_VERSION=void 0,t.XTERM_VERSION="6.1.0-beta.287"},5882(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.updateWindowsModeWrappedState=function(e){const t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1),i=t?.get(e.cols-1),r=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);r&&i&&(r.isWrapped=i[s.CHAR_DATA_CODE_INDEX]!==s.NULL_CELL_CODE&&i[s.CHAR_DATA_CODE_INDEX]!==s.WHITESPACE_CELL_CODE)};const s=i(8938)},5451(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ExtendedAttrs=t.AttributeData=void 0;class i{constructor(){this.fg=0,this.bg=0,this.extended=new s}static toColorRGB(e){return[e>>>16&255,e>>>8&255,255&e]}static fromColorRGB(e){return(255&e[0])<<16|(255&e[1])<<8|255&e[2]}clone(){const e=new i;return e.fg=this.fg,e.bg=this.bg,e.extended=this.extended.clone(),e}isInverse(){return 67108864&this.fg}isBold(){return 134217728&this.fg}isUnderline(){return this.hasExtendedAttrs()&&0!==this.extended.underlineStyle?1:268435456&this.fg}isBlink(){return 536870912&this.fg}isInvisible(){return 1073741824&this.fg}isItalic(){return 67108864&this.bg}isDim(){return 134217728&this.bg}isStrikethrough(){return 2147483648&this.fg}isProtected(){return 536870912&this.bg}isOverline(){return 1073741824&this.bg}getFgColorMode(){return 50331648&this.fg}getBgColorMode(){return 50331648&this.bg}isFgRGB(){return!(50331648&~this.fg)}isBgRGB(){return!(50331648&~this.bg)}isFgPalette(){return 16777216==(50331648&this.fg)||33554432==(50331648&this.fg)}isBgPalette(){return 16777216==(50331648&this.bg)||33554432==(50331648&this.bg)}isFgDefault(){return!(50331648&this.fg)}isBgDefault(){return!(50331648&this.bg)}isAttributeDefault(){return 0===this.fg&&0===this.bg}getFgColor(){switch(50331648&this.fg){case 16777216:case 33554432:return 255&this.fg;case 50331648:return 16777215&this.fg;default:return-1}}getBgColor(){switch(50331648&this.bg){case 16777216:case 33554432:return 255&this.bg;case 50331648:return 16777215&this.bg;default:return-1}}hasExtendedAttrs(){return 268435456&this.bg}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(268435456&this.bg&&~this.extended.underlineColor)switch(50331648&this.extended.underlineColor){case 16777216:case 33554432:return 255&this.extended.underlineColor;case 50331648:return 16777215&this.extended.underlineColor;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return 268435456&this.bg&&~this.extended.underlineColor?50331648&this.extended.underlineColor:this.getFgColorMode()}isUnderlineColorRGB(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&~this.extended.underlineColor):this.isFgRGB()}isUnderlineColorPalette(){return 268435456&this.bg&&~this.extended.underlineColor?16777216==(50331648&this.extended.underlineColor)||33554432==(50331648&this.extended.underlineColor):this.isFgPalette()}isUnderlineColorDefault(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&this.extended.underlineColor):this.isFgDefault()}getUnderlineStyle(){return 268435456&this.fg?268435456&this.bg?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}}t.AttributeData=i;class s{get ext(){return this._urlId?-469762049&this._ext|this.underlineStyle<<26:this._ext}set ext(e){this._ext=e}get underlineStyle(){return this._urlId?5:(469762048&this._ext)>>26}set underlineStyle(e){this._ext&=-469762049,this._ext|=e<<26&469762048}get underlineColor(){return 67108863&this._ext}set underlineColor(e){this._ext&=-67108864,this._ext|=67108863&e}get urlId(){return this._urlId}set urlId(e){this._urlId=e}get underlineVariantOffset(){const e=(3758096384&this._ext)>>29;return e<0?4294967288^e:e}set underlineVariantOffset(e){this._ext&=536870911,this._ext|=e<<29&3758096384}constructor(e=0,t=0){this._ext=0,this._urlId=0,this._ext=e,this._urlId=t}clone(){return new s(this._ext,this._urlId)}isEmpty(){return 0===this.underlineStyle&&0===this._urlId}}t.ExtendedAttrs=s},1073(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.Buffer=t.MAX_BUFFER_SIZE=void 0;const s=i(5639),r=i(4812),o=i(6168),n=i(5451),a=i(6107),h=i(3326),l=i(732),c=i(3055),d=i(8938),_=i(8158),u=i(6760);t.MAX_BUFFER_SIZE=4294967295;class f extends r.Disposable{constructor(e,t,i,n){super(),this._hasScrollback=e,this._optionsService=t,this._bufferService=i,this._logService=n,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.tabs={},this.savedY=0,this.savedX=0,this.savedCurAttrData=a.DEFAULT_ATTR_DATA.clone(),this.savedCharset=u.DEFAULT_CHARSET,this.savedCharsets=[],this.savedGlevel=0,this.savedOriginMode=!1,this.savedWraparoundMode=!0,this.markers=[],this._nullCell=c.CellData.fromCharData([0,d.NULL_CELL_CHAR,d.NULL_CELL_WIDTH,d.NULL_CELL_CODE]),this._whitespaceCell=c.CellData.fromCharData([0,d.WHITESPACE_CELL_CHAR,d.WHITESPACE_CELL_WIDTH,d.WHITESPACE_CELL_CODE]),this._isClearing=!1,this._memoryCleanupPosition=0,this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new s.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops(),this._memoryCleanupQueue=new o.IdleTaskQueue(this._logService),this._register((0,r.toDisposable)(()=>this._memoryCleanupQueue.clear())),this._register((0,r.toDisposable)(()=>this.clearAllMarkers())),this._stringCache=this._register(new h.BufferLineStringCache)}getNullCell(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new n.ExtendedAttrs),this._nullCell}getWhitespaceCell(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new n.ExtendedAttrs),this._whitespaceCell}getBlankLine(e,t){return new a.BufferLine(this._stringCache,this._bufferService.cols,this.getNullCell(e),t)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){const e=this.ybase+this.y-this.ydisp;return e>=0&&et.MAX_BUFFER_SIZE?t.MAX_BUFFER_SIZE:i}fillViewportRows(e){if(0===this.lines.length){e??=a.DEFAULT_ATTR_DATA;let t=this._rows;for(;t--;)this.lines.push(this.getBlankLine(e))}}clear(){this._stringCache.clear(),this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new s.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(e,t){const i=this.getNullCell(a.DEFAULT_ATTR_DATA);this._stringCache.clear();let s=0;const r=this._getCorrectBufferLength(t);if(r>this.lines.maxLength&&(this.lines.maxLength=r),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+o+1?(this.ybase--,o++,this.ydisp>0&&this.ydisp--):this.lines.push(new a.BufferLine(this._stringCache,e,i,!1)));else for(let e=this._rows;e>t;e--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(r0&&(this.lines.trimStart(e),this.ybase=Math.max(this.ybase-e,0),this.ydisp=Math.max(this.ydisp-e,0),this.savedY=Math.max(this.savedY-e,0)),this.lines.maxLength=r}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),o&&(this.y+=o),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(let t=0;t0){const e=Math.max(0,this.lines.length-this.ybase-1);this.y=Math.min(this.y,e)}this._memoryCleanupQueue.clear(),s>.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue(()=>this._batchedMemoryCleanup()))}_batchedMemoryCleanup(){let e=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,e=!1);let t=0;for(;this._memoryCleanupPosition100)return!0;return e}get _isReflowEnabled(){const e=this._optionsService.rawOptions.windowsPty;return e&&e.buildNumber?this._hasScrollback&&"conpty"===e.backend&&e.buildNumber>=21376:this._hasScrollback}_reflow(e,t){this._cols!==e&&(e>this._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))}_reflowLarger(e,t){const i=this._optionsService.rawOptions.reflowCursorLine,s=(0,l.reflowLargerGetLinesToRemove)(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(a.DEFAULT_ATTR_DATA),i);if(s.length>0){const i=(0,l.reflowLargerCreateNewLayout)(this.lines,s);(0,l.reflowLargerApplyNewLayout)(this.lines,i.layout),this._reflowLargerAdjustViewport(e,t,i.countRemoved)}}_reflowLargerAdjustViewport(e,t,i){const s=this.getNullCell(a.DEFAULT_ATTR_DATA);let r=i;for(;r-- >0;)0===this.ybase?(this.y>0&&this.y--,this.lines.length=0;n--){let h=this.lines.get(n);if(!h||!h.isWrapped&&h.getTrimmedLength()<=e)continue;const c=[h];for(;h.isWrapped&&n>0;)h=this.lines.get(--n),c.unshift(h);if(!i){const e=this.ybase+this.y;if(e>=n&&e0&&(r.push({start:n+c.length+o,newLines:p}),o+=p.length),c.push(...p);let v=_.length-1,g=_[v];0===g&&(v--,g=_[v]);let m=c.length-u-1,S=d;for(;m>=0;){const e=Math.min(S,g);if(void 0===c[v])break;if(c[v].copyCellsFrom(c[m],S-e,g-e,e,!0),g-=e,0===g&&(v--,g=_[v]),S-=e,0===S){m--;const e=Math.max(m,0);S=(0,l.getWrappedLineTrimmedLength)(c,e,this._cols)}}for(let t=0;t0;)0===this.ybase?this.y0){const e=[],t=[];for(let e=0;e=0;l--)if(a&&a.start>s+h){for(let e=a.newLines.length-1;e>=0;e--)this.lines.set(l--,a.newLines[e]);l++,e.push({index:s+1,amount:a.newLines.length}),h+=a.newLines.length,a=r[++n]}else this.lines.set(l,t[s--]);let l=0;for(let t=e.length-1;t>=0;t--)e[t].index+=l,this.lines.onInsertEmitter.fire(e[t]),l+=e[t].amount;const c=Math.max(0,i+o-this.lines.maxLength);c>0&&this.lines.onTrimEmitter.fire(c)}}translateBufferLineToString(e,t,i=0,s){const r=this.lines.get(e);return r?r.translateToString(t,i,s):""}getWrappedRangeForLine(e){let t=e,i=e;for(;t>0&&this.lines.get(t).isWrapped;)t--;for(;i+10;);return e>=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(e??=this.x;!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e}clearMarkers(e){this._isClearing=!0;for(let t=0;t{t.line-=e,t.line<0&&t.dispose()})),t.register(this.lines.onInsert(e=>{t.line>=e.index&&(t.line+=e.amount)})),t.register(this.lines.onDelete(e=>{t.line>=e.index&&t.linee.index&&(t.line-=e.amount)})),t.register(t.onDispose(()=>this._removeMarker(t))),t}_removeMarker(e){this._isClearing||this.markers.splice(this.markers.indexOf(e),1)}}t.Buffer=f},6107(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.BufferLine=t.DEFAULT_ATTR_DATA=void 0;const s=i(5451),r=i(3055),o=i(8938),n=i(726),a=i(4220);t.DEFAULT_ATTR_DATA=Object.freeze(new s.AttributeData);let h=0;const l=new r.CellData,c=new a.StringBuilder;class d{constructor(e,t,i,s=!1){this._stringCache=e,this.isWrapped=s,this._combined={},this._extendedAttrs={},this._data=new Uint32Array(3*t);const n=i??r.CellData.fromCharData([0,o.NULL_CELL_CHAR,o.NULL_CELL_WIDTH,o.NULL_CELL_CODE]);for(let e=0;e>22,2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):i]}set(e,t){this._invalidateStringCache(),this._data[3*e+1]=t[o.CHAR_DATA_ATTR_INDEX],t[o.CHAR_DATA_CHAR_INDEX].length>1?(this._combined[e]=t[1],this._data[3*e+0]=2097152|e|t[o.CHAR_DATA_WIDTH_INDEX]<<22):this._data[3*e+0]=t[o.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|t[o.CHAR_DATA_WIDTH_INDEX]<<22}getWidth(e){return this._data[3*e+0]>>22}hasWidth(e){return 12582912&this._data[3*e+0]}getFg(e){return this._data[3*e+1]}getBg(e){return this._data[3*e+2]}hasContent(e){return 4194303&this._data[3*e+0]}getCodePoint(e){const t=this._data[3*e+0];return 2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):2097151&t}isCombined(e){return 2097152&this._data[3*e+0]}getString(e){const t=this._data[3*e+0];return 2097152&t?this._combined[e]:2097151&t?(0,n.stringFromCodePoint)(2097151&t):""}isProtected(e){return 536870912&this._data[3*e+2]}loadCell(e,i){return h=3*e,i.content=this._data[h+0],i.fg=this._data[h+1],i.bg=this._data[h+2],2097152&i.content?i.combinedData=this._combined[e]:i.combinedData="",268435456&i.bg?i.extended=this._extendedAttrs[e]:i.extended=t.DEFAULT_ATTR_DATA.extended.clone(),i}setCell(e,t){this._invalidateStringCache(),2097152&t.content&&(this._combined[e]=t.combinedData),268435456&t.bg&&(this._extendedAttrs[e]=t.extended),this._data[3*e+0]=t.content,this._data[3*e+1]=t.fg,this._data[3*e+2]=t.bg}setCellFromCodepoint(e,t,i,s){this._invalidateStringCache(),268435456&s.bg&&(this._extendedAttrs[e]=s.extended),this._data[3*e+0]=t|i<<22,this._data[3*e+1]=s.fg,this._data[3*e+2]=s.bg}addCodepointToCell(e,t,i){this._invalidateStringCache();let s=this._data[3*e+0];2097152&s?this._combined[e]+=(0,n.stringFromCodePoint)(t):2097151&s?(this._combined[e]=(0,n.stringFromCodePoint)(2097151&s)+(0,n.stringFromCodePoint)(t),s&=-2097152,s|=2097152):s=t|1<<22,i&&(s&=-12582913,s|=i<<22),this._data[3*e+0]=s}insertCells(e,t,i){if(this._invalidateStringCache(),(e%=this.length)&&2===this.getWidth(e-1)&&this.setCellFromCodepoint(e-1,0,1,i),t=0;--i)this.setCell(e+t+i,this.loadCell(e+i,l));for(let s=0;sthis.length){if(this._data.buffer.byteLength>=4*i)this._data=new Uint32Array(this._data.buffer,0,i);else{const e=new Uint32Array(i);e.set(this._data),this._data=e}for(let i=this.length;i=e&&delete this._combined[s]}const s=Object.keys(this._extendedAttrs);for(let t=0;t=e&&delete this._extendedAttrs[i]}}return this.length=e,4*i*2=0;--e)if(4194303&this._data[3*e+0])return e+(this._data[3*e+0]>>22);return 0}getNoBgTrimmedLength(){for(let e=this.length-1;e>=0;--e)if(4194303&this._data[3*e+0]||50331648&this._data[3*e+2])return e+(this._data[3*e+0]>>22);return 0}copyCellsFrom(e,t,i,s,r){this._invalidateStringCache();const o=e._data;if(r)for(let r=s-1;r>=0;r--){for(let e=0;e<3;e++)this._data[3*(i+r)+e]=o[3*(t+r)+e];this._copyCellMapsFrom(e,t+r,i+r)}else for(let r=0;r>22||1}s&&s.push(t);const h=c.toString();if(c.reset(),r){const t=this._getStringCacheEntry(!0);t.value=h,t.isTrimmed=!!e}return h}_getStringCacheEntry(e){const t=this._stringCacheEntryRef?.deref();if(t&&t.generation===this._stringCache.generation)return t;if(!e)return;const i=this._stringCache.allocateEntry();return this._stringCacheEntryRef=new WeakRef(i),i}_invalidateStringCache(){const e=this._getStringCacheEntry(!1);e&&(e.value=void 0,e.isTrimmed=!1)}_copyCellMapsFrom(e,t,i){const s=3*t;2097152&e._data[s+0]&&(this._combined[i]=e._combined[t]),268435456&e._data[s+2]&&(this._extendedAttrs[i]=e._extendedAttrs[t])}_copySparseMapsFrom(e){this._combined={},this._extendedAttrs={};for(let t=0;tthis.entries.clear()))}touch(){this._scheduleClear()}allocateEntry(){const e={value:void 0,isTrimmed:!1,generation:this.generation};return this.entries.add(e),this._scheduleClear(),e}clear(){this._clearTimeout.clear(),this._lastAccessTimestamp=0,this.generation++;for(const e of this.entries)e.value=void 0,e.isTrimmed=!1;this.entries.clear()}_scheduleClear(){this._lastAccessTimestamp=Date.now(),this._clearTimeout.value||this._scheduleClearTimeout(15e3)}_scheduleClearTimeout(e){this._clearTimeout.value=(0,s.disposableTimeout)(()=>{const e=Date.now()-this._lastAccessTimestamp;e>=15e3?this.clear():this._scheduleClearTimeout(15e3-e)},e)}}t.BufferLineStringCache=o},9384(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.getRangeLength=function(e,t){if(e.start.y>e.end.y)throw new Error(`Buffer range end (${e.end.x}, ${e.end.y}) cannot be before start (${e.start.x}, ${e.start.y})`);return t*(e.end.y-e.start.y)+(e.end.x-e.start.x+1)}},732(e,t){function i(e,t,i){if(t===e.length-1)return e[t].getTrimmedLength();const s=!e[t].hasContent(i-1)&&1===e[t].getWidth(i-1),r=2===e[t+1].getWidth(0);return s&&r?i-1:i}Object.defineProperty(t,"__esModule",{value:!0}),t.reflowLargerGetLinesToRemove=function(e,t,s,r,o,n){const a=[];for(let h=0;h=h&&r0&&(e>_||0===d[e].getTrimmedLength());e--)v++;v>0&&(a.push(h+d.length-v),a.push(v)),h+=d.length-1}return a},t.reflowLargerCreateNewLayout=function(e,t){const i=[];let s=0,r=t[s],o=0;for(let n=0;nl&&(n-=l,a++);const c=2===e[a].getWidth(n-1);c&&n--;const d=c?s-1:s;r.push(d),h+=d}return r},t.getWrappedLineTrimmedLength=i},4097(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.BufferSet=void 0;const s=i(4812),r=i(1073),o=i(8636);class n extends s.Disposable{constructor(e,t,i){super(),this._optionsService=e,this._bufferService=t,this._logService=i,this._normalBuffer=this._register(new s.MutableDisposable),this._altBuffer=this._register(new s.MutableDisposable),this._onBufferActivate=this._register(new o.Emitter),this.onBufferActivate=this._onBufferActivate.event,this.reset(),this._register(this._optionsService.onSpecificOptionChange("scrollback",()=>this.resize(this._bufferService.cols,this._bufferService.rows))),this._register(this._optionsService.onSpecificOptionChange("tabStopWidth",()=>this.setupTabStops()))}reset(){this._normal=new r.Buffer(!0,this._optionsService,this._bufferService,this._logService),this._normalBuffer.value=this._normal,this._normal.fillViewportRows(),this._alt=new r.Buffer(!1,this._optionsService,this._bufferService,this._logService),this._altBuffer.value=this._alt,this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}}t.BufferSet=n},3055(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.CellData=void 0;const s=i(726),r=i(8938),o=i(5451);class n extends o.AttributeData{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new o.ExtendedAttrs,this.combinedData=""}static fromCharData(e){const t=new n;return t.setFromCharData(e),t}isCombined(){return 2097152&this.content}getWidth(){return this.content>>22}getChars(){return 2097152&this.content?this.combinedData:2097151&this.content?(0,s.stringFromCodePoint)(2097151&this.content):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):2097151&this.content}setFromCharData(e){this.fg=e[r.CHAR_DATA_ATTR_INDEX],this.bg=0;let t=!1;if(e[r.CHAR_DATA_CHAR_INDEX].length>2)t=!0;else if(2===e[r.CHAR_DATA_CHAR_INDEX].length){const i=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0);if(55296<=i&&i<=56319){const s=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(1);56320<=s&&s<=57343?this.content=1024*(i-55296)+s-56320+65536|e[r.CHAR_DATA_WIDTH_INDEX]<<22:t=!0}else t=!0}else this.content=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|e[r.CHAR_DATA_WIDTH_INDEX]<<22;t&&(this.combinedData=e[r.CHAR_DATA_CHAR_INDEX],this.content=2097152|e[r.CHAR_DATA_WIDTH_INDEX]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}attributesEquals(e){if(this.getFgColorMode()!==e.getFgColorMode()||this.getFgColor()!==e.getFgColor())return!1;if(this.getBgColorMode()!==e.getBgColorMode()||this.getBgColor()!==e.getBgColor())return!1;if(this.isInverse()!==e.isInverse())return!1;if(this.isBold()!==e.isBold())return!1;if(this.isUnderline()!==e.isUnderline())return!1;if(this.isUnderline()){if(this.getUnderlineStyle()!==e.getUnderlineStyle())return!1;const t=this.isUnderlineColorDefault(),i=e.isUnderlineColorDefault();if(!t||!i){if(t!==i)return!1;if(this.getUnderlineColor()!==e.getUnderlineColor())return!1;if(this.getUnderlineColorMode()!==e.getUnderlineColorMode())return!1}}return this.isOverline()===e.isOverline()&&this.isBlink()===e.isBlink()&&this.isInvisible()===e.isInvisible()&&this.isItalic()===e.isItalic()&&this.isDim()===e.isDim()&&this.isStrikethrough()===e.isStrikethrough()}}t.CellData=n},8938(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.WHITESPACE_CELL_CODE=t.WHITESPACE_CELL_WIDTH=t.WHITESPACE_CELL_CHAR=t.NULL_CELL_CODE=t.NULL_CELL_WIDTH=t.NULL_CELL_CHAR=t.CHAR_DATA_CODE_INDEX=t.CHAR_DATA_WIDTH_INDEX=t.CHAR_DATA_CHAR_INDEX=t.CHAR_DATA_ATTR_INDEX=t.DEFAULT_EXT=t.DEFAULT_ATTR=t.DEFAULT_COLOR=void 0,t.DEFAULT_COLOR=0,t.DEFAULT_ATTR=t.DEFAULT_COLOR<<9|256,t.DEFAULT_EXT=0,t.CHAR_DATA_ATTR_INDEX=0,t.CHAR_DATA_CHAR_INDEX=1,t.CHAR_DATA_WIDTH_INDEX=2,t.CHAR_DATA_CODE_INDEX=3,t.NULL_CELL_CHAR="",t.NULL_CELL_WIDTH=1,t.NULL_CELL_CODE=0,t.WHITESPACE_CELL_CHAR=" ",t.WHITESPACE_CELL_WIDTH=1,t.WHITESPACE_CELL_CODE=32},8158(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.Marker=void 0;const s=i(4812),r=i(8636);class o{get id(){return this._id}constructor(e){this.line=e,this.isDisposed=!1,this._disposables=[],this._id=o._nextId++,this._onDispose=this.register(new r.Emitter),this.onDispose=this._onDispose.event}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),(0,s.dispose)(this._disposables),this._disposables.length=0)}register(e){return this._disposables.push(e),e}}t.Marker=o,o._nextId=1},6760(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.DEFAULT_CHARSET=t.CHARSETS=void 0,t.CHARSETS={},t.DEFAULT_CHARSET=t.CHARSETS.B,t.CHARSETS[0]={"`":"◆",a:"▒",b:"␉",c:"␌",d:"␍",e:"␊",f:"°",g:"±",h:"␤",i:"␋",j:"┘",k:"┐",l:"┌",m:"└",n:"┼",o:"⎺",p:"⎻",q:"─",r:"⎼",s:"⎽",t:"├",u:"┤",v:"┴",w:"┬",x:"│",y:"≤",z:"≥","{":"π","|":"≠","}":"£","~":"·"},t.CHARSETS.A={"#":"£"},t.CHARSETS.B=void 0,t.CHARSETS[4]={"#":"£","@":"¾","[":"ij","\\":"½","]":"|","{":"¨","|":"f","}":"¼","~":"´"},t.CHARSETS.C=t.CHARSETS[5]={"[":"Ä","\\":"Ö","]":"Å","^":"Ü","`":"é","{":"ä","|":"ö","}":"å","~":"ü"},t.CHARSETS.R={"#":"£","@":"à","[":"°","\\":"ç","]":"§","{":"é","|":"ù","}":"è","~":"¨"},t.CHARSETS.Q={"@":"à","[":"â","\\":"ç","]":"ê","^":"î","`":"ô","{":"é","|":"ù","}":"è","~":"û"},t.CHARSETS.K={"@":"§","[":"Ä","\\":"Ö","]":"Ü","{":"ä","|":"ö","}":"ü","~":"ß"},t.CHARSETS.Y={"#":"£","@":"§","[":"°","\\":"ç","]":"é","`":"ù","{":"à","|":"ò","}":"è","~":"ì"},t.CHARSETS.E=t.CHARSETS[6]={"@":"Ä","[":"Æ","\\":"Ø","]":"Å","^":"Ü","`":"ä","{":"æ","|":"ø","}":"å","~":"ü"},t.CHARSETS.Z={"#":"£","@":"§","[":"¡","\\":"Ñ","]":"¿","{":"°","|":"ñ","}":"ç"},t.CHARSETS.H=t.CHARSETS[7]={"@":"É","[":"Ä","\\":"Ö","]":"Å","^":"Ü","`":"é","{":"ä","|":"ö","}":"å","~":"ü"},t.CHARSETS["="]={"#":"ù","@":"à","[":"é","\\":"ç","]":"ê","^":"î",_:"è","`":"ô","{":"ä","|":"ö","}":"ü","~":"û"}},706(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.evaluateKeyboardEvent=function(e,t,s,r){const o={type:0,cancel:!1,key:void 0},n=(e.shiftKey?1:0)|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:"UIKeyInputUpArrow"===e.key?o.key=t?"OA":"":"UIKeyInputLeftArrow"===e.key?o.key=t?"OD":"":"UIKeyInputRightArrow"===e.key?o.key=t?"OC":"":"UIKeyInputDownArrow"===e.key&&(o.key=t?"OB":"");break;case 8:o.key=e.ctrlKey?"\b":"",e.altKey&&(o.key=""+o.key);break;case 9:if(e.shiftKey){o.key="";break}o.key="\t",o.cancel=!0;break;case 13:"c"===e.key&&e.ctrlKey?o.key="":o.key=e.altKey?"\r":"\r",o.cancel=!0;break;case 27:o.key="",e.altKey&&(o.key=""),o.cancel=!0;break;case 37:if(e.metaKey)break;o.key=n?"[1;"+(n+1)+"D":t?"OD":"";break;case 39:if(e.metaKey)break;o.key=n?"[1;"+(n+1)+"C":t?"OC":"";break;case 38:if(e.metaKey)break;o.key=n?"[1;"+(n+1)+"A":t?"OA":"";break;case 40:if(e.metaKey)break;o.key=n?"[1;"+(n+1)+"B":t?"OB":"";break;case 45:e.shiftKey||e.ctrlKey||(o.key="[2~");break;case 46:o.key=n?"[3;"+(n+1)+"~":"[3~";break;case 36:o.key=n?"[1;"+(n+1)+"H":t?"OH":"";break;case 35:o.key=n?"[1;"+(n+1)+"F":t?"OF":"";break;case 33:e.shiftKey?o.type=2:e.ctrlKey?o.key="[5;"+(n+1)+"~":o.key="[5~";break;case 34:e.shiftKey?o.type=3:e.ctrlKey?o.key="[6;"+(n+1)+"~":o.key="[6~";break;case 112:o.key=n?"[1;"+(n+1)+"P":"OP";break;case 113:o.key=n?"[1;"+(n+1)+"Q":"OQ";break;case 114:o.key=n?"[1;"+(n+1)+"R":"OR";break;case 115:o.key=n?"[1;"+(n+1)+"S":"OS";break;case 116:o.key=n?"[15;"+(n+1)+"~":"[15~";break;case 117:o.key=n?"[17;"+(n+1)+"~":"[17~";break;case 118:o.key=n?"[18;"+(n+1)+"~":"[18~";break;case 119:o.key=n?"[19;"+(n+1)+"~":"[19~";break;case 120:o.key=n?"[20;"+(n+1)+"~":"[20~";break;case 121:o.key=n?"[21;"+(n+1)+"~":"[21~";break;case 122:o.key=n?"[23;"+(n+1)+"~":"[23~";break;case 123:o.key=n?"[24;"+(n+1)+"~":"[24~";break;default:if(!e.ctrlKey||e.shiftKey||e.altKey||e.metaKey)if(s&&!r||!e.altKey||e.metaKey)if(!s||e.altKey||e.ctrlKey||e.shiftKey||!e.metaKey){if(e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&1===e.key.length)o.key=e.key;else if(e.key&&e.ctrlKey&&e.shiftKey)switch(e.code){case"Minus":o.key="";break;case"Digit2":o.key="\0";break;case"Digit6":o.key=""}}else 65===e.keyCode&&(o.type=1);else{const t=i[e.keyCode],s=t?.[e.shiftKey?1:0];if(s)o.key=""+s;else if(e.keyCode>=65&&e.keyCode<=90){const t=e.ctrlKey?e.keyCode-64:e.keyCode+32;let i=String.fromCharCode(t);e.shiftKey&&(i=i.toUpperCase()),o.key=""+i}else if(32===e.keyCode)o.key=""+(e.ctrlKey?"\0":" ");else if("Dead"===e.key&&e.code.startsWith("Key")){let t=e.code.slice(3,4);e.shiftKey||(t=t.toLowerCase()),o.key=""+t,o.cancel=!0}}else e.keyCode>=65&&e.keyCode<=90?o.key=String.fromCharCode(e.keyCode-64):32===e.keyCode?o.key="\0":e.keyCode>=51&&e.keyCode<=55?o.key=String.fromCharCode(e.keyCode-51+27):56===e.keyCode?o.key="":"/"===e.key?o.key="":219===e.keyCode?o.key="":220===e.keyCode?o.key="":221===e.keyCode&&(o.key="")}return o};const i={48:["0",")"],49:["1","!"],50:["2","@"],51:["3","#"],52:["4","$"],53:["5","%"],54:["6","^"],55:["7","&"],56:["8","*"],57:["9","("],186:[";",":"],187:["=","+"],188:[",","<"],189:["-","_"],190:[".",">"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']}},7241(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.KittyKeyboard=void 0,t.KittyKeyboard=class{constructor(){this._functionalKeyCodes={Escape:27,Enter:13,Tab:9,Backspace:127,CapsLock:57358,ScrollLock:57359,NumLock:57360,PrintScreen:57361,Pause:57362,ContextMenu:57363,F13:57376,F14:57377,F15:57378,F16:57379,F17:57380,F18:57381,F19:57382,F20:57383,F21:57384,F22:57385,F23:57386,F24:57387,F25:57388,KP_0:57399,KP_1:57400,KP_2:57401,KP_3:57402,KP_4:57403,KP_5:57404,KP_6:57405,KP_7:57406,KP_8:57407,KP_9:57408,KP_Decimal:57409,KP_Divide:57410,KP_Multiply:57411,KP_Subtract:57412,KP_Add:57413,KP_Enter:57414,KP_Equal:57415,ShiftLeft:57441,ShiftRight:57447,ControlLeft:57442,ControlRight:57448,AltLeft:57443,AltRight:57449,MetaLeft:57444,MetaRight:57450,MediaPlayPause:57430,MediaStop:57432,MediaTrackNext:57435,MediaTrackPrevious:57436,AudioVolumeDown:57438,AudioVolumeUp:57439,AudioVolumeMute:57440},this._csiTildeKeys={Insert:2,Delete:3,PageUp:5,PageDown:6,F5:15,F6:17,F7:18,F8:19,F9:20,F10:21,F11:23,F12:24},this._csiLetterKeys={ArrowUp:"A",ArrowDown:"B",ArrowRight:"C",ArrowLeft:"D",Home:"H",End:"F"},this._ss3FunctionKeys={F1:"P",F2:"Q",F3:"R",F4:"S"}}_getNumpadKeyCode(e){if(e.code.startsWith("Numpad")){const t=e.code.slice(6);if(t>="0"&&t<="9")return 57399+parseInt(t,10);switch(t){case"Decimal":return 57409;case"Divide":return 57410;case"Multiply":return 57411;case"Subtract":return 57412;case"Add":return 57413;case"Enter":return 57414;case"Equal":return 57415}}}_getModifierKeyCode(e){switch(e.code){case"ShiftLeft":return 57441;case"ShiftRight":return 57447;case"ControlLeft":return 57442;case"ControlRight":return 57448;case"AltLeft":return 57443;case"AltRight":return 57449;case"MetaLeft":return 57444;case"MetaRight":return 57450}}_encodeModifiers(e){let t=0;return e.shiftKey&&(t|=1),e.altKey&&(t|=2),e.ctrlKey&&(t|=4),e.metaKey&&(t|=8),t>0?t+1:0}_getKeyCode(e,t){const i=this._getNumpadKeyCode(e);if(void 0!==i)return i;const s=this._getModifierKeyCode(e);if(void 0!==s)return s;const r=this._functionalKeyCodes[e.key];if(void 0!==r)return r;if((e.shiftKey||t&&e.altKey)&&e.code){if(e.code.startsWith("Digit")&&6===e.code.length){const t=e.code.charAt(5);if(t>="0"&&t<="9")return t.charCodeAt(0)}if(e.code.startsWith("Key")&&4===e.code.length)return e.code.charAt(3).toLowerCase().charCodeAt(0)}if(1===e.key.length){const t=e.key.codePointAt(0);return t>=65&&t<=90?t+32:t}}_isModifierKey(e){return"Shift"===e.key||"Control"===e.key||"Alt"===e.key||"Meta"===e.key}_isLockKey(e){return"CapsLock"===e.key||"NumLock"===e.key||"ScrollLock"===e.key}_buildCsiLetterSequence(e,t,i,s){const r=s&&1!==i;if(t>0||r){let s="[1;"+(t>0?t:"1");return r&&(s+=":"+i),s+=e,s}return"["+e}_buildSs3Sequence(e,t,i,s){const r=s&&1!==i;if(t>0||r){let s="[1;"+(t>0?t:"1");return r&&(s+=":"+i),s+=e,s}return"O"+e}_buildCsiTildeSequence(e,t,i,s){const r=s&&1!==i;let o="["+e;return(t>0||r)&&(o+=";"+(t>0?t:"1"),r&&(o+=":"+i)),o+="~",o}_buildCsiUSequence(e,t,i,s,r,o,n){const a=!!(2&r);let h,l="["+t;4&r&&e.shiftKey&&1===e.key.length&&!o&&!n&&(h=e.key.codePointAt(0),l+=":"+h);const c=16&r&&3!==s&&1===e.key.length&&!o&&!n&&!e.ctrlKey?e.key.codePointAt(0):void 0,d=a&&1!==s&&(3===s||void 0===c);return(i>0||d||void 0!==c)&&(l+=";",i>0?l+=i:d&&(l+="1"),d&&(l+=":"+s)),void 0!==c&&(l+=";"+c),l+="u",l}evaluate(e,t,i=1,s=!1){const r={type:0,cancel:!1,key:void 0},o=this._encodeModifiers(e),n=this._isModifierKey(e),a=!!(2&t);if(!a&&3===i)return r;if(n&&!(8&t))return r;if(this._isLockKey(e)&&!(8&t))return r;const h=this._csiLetterKeys[e.key];if(h)return r.key=this._buildCsiLetterSequence(h,o,i,a),r.cancel=!0,r;const l=this._ss3FunctionKeys[e.key];if(l)return r.key=this._buildSs3Sequence(l,o,i,a),r.cancel=!0,r;const c=this._csiTildeKeys[e.key];if(void 0!==c)return r.key=this._buildCsiTildeSequence(c,o,i,a),r.cancel=!0,r;const d=this._getKeyCode(e,s);if(void 0===d)return r;const _=13===d||9===d||127===d;if(_&&3===i&&!(8&t))return r;const u=void 0!==this._functionalKeyCodes[e.key]||void 0!==this._getNumpadKeyCode(e);if(8&t||a&&3===i||(1&t||a)&&(u&&!_||o>0&&1!==e.key.length||o-1>1))r.key=this._buildCsiUSequence(e,d,o,i,t,u,n),r.cancel=!0;else{const t=13===d?"\r":9===d?"\t":127===d?"":void 0;t?r.key=t:1!==e.key.length||e.ctrlKey||e.altKey||e.metaKey||(r.key=e.key)}return r}static shouldUseProtocol(e){return e>0}}},726(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Utf8ToUtf32=t.StringToUtf32=void 0,t.stringFromCodePoint=function(e){return e>65535?(e-=65536,String.fromCharCode(55296+(e>>10))+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)},t.utf32ToString=function(e,t=0,i=e.length){let s="";for(let r=t;r65535?(t-=65536,s+=String.fromCharCode(55296+(t>>10))+String.fromCharCode(t%1024+56320)):s+=String.fromCharCode(t)}return s},t.StringToUtf32=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){const i=e.length;if(!i)return 0;let s=0,r=0;if(this._interim){const i=e.charCodeAt(r++);56320<=i&&i<=57343?t[s++]=1024*(this._interim-55296)+i-56320+65536:(t[s++]=this._interim,t[s++]=i),this._interim=0}for(let o=r;o=i)return this._interim=r,s;const n=e.charCodeAt(o);56320<=n&&n<=57343?t[s++]=1024*(r-55296)+n-56320+65536:(t[s++]=r,t[s++]=n);continue}65279!==r&&(t[s++]=r)}return s}},t.Utf8ToUtf32=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){const i=e.length;if(!i)return 0;let s,r,o,n,a,h=0,l=0;if(this.interim[0]){let s=!1,r=this.interim[0];r&=192==(224&r)?31:224==(240&r)?15:7;let o,n=0;for(;(o=this.interim[++n])&&n<4;)r<<=6,r|=63&o;const a=192==(224&this.interim[0])?2:224==(240&this.interim[0])?3:4,c=a-n;for(;l=i)return 0;if(o=e[l++],128!=(192&o)){l--,s=!0;break}this.interim[n++]=o,r<<=6,r|=63&o}s||(2===a?r<128?l--:t[h++]=r:3===a?r<2048||r>=55296&&r<=57343||65279===r||(t[h++]=r):r<65536||r>1114111||(t[h++]=r)),this.interim.fill(0)}const c=i-4;let d=l;for(;d=i)return this.interim[0]=s,h;if(r=e[d++],128!=(192&r)){d--;continue}if(a=(31&s)<<6|63&r,a<128){d--;continue}t[h++]=a}else if(224==(240&s)){if(d>=i)return this.interim[0]=s,h;if(r=e[d++],128!=(192&r)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,h;if(o=e[d++],128!=(192&o)){d--;continue}if(a=(15&s)<<12|(63&r)<<6|63&o,a<2048||a>=55296&&a<=57343||65279===a)continue;t[h++]=a}else if(240==(248&s)){if(d>=i)return this.interim[0]=s,h;if(r=e[d++],128!=(192&r)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,h;if(o=e[d++],128!=(192&o)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,this.interim[2]=o,h;if(n=e[d++],128!=(192&n)){d--;continue}if(a=(7&s)<<18|(63&r)<<12|(63&o)<<6|63&n,a<65536||a>1114111)continue;t[h++]=a}}return h}}},7428(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV6=void 0;const s=i(6415),r=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],o=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]];let n;t.UnicodeV6=class{constructor(){if(this.version="6",!n){n=new Uint8Array(65536),n.fill(1),n[0]=0,n.fill(0,1,32),n.fill(0,127,160),n.fill(2,4352,4448),n[9001]=2,n[9002]=2,n.fill(2,11904,42192),n[12351]=1,n.fill(2,44032,55204),n.fill(2,63744,64256),n.fill(2,65040,65050),n.fill(2,65072,65136),n.fill(2,65280,65377),n.fill(2,65504,65511);for(let e=0;et[r][1])return!1;for(;r>=s;)if(i=s+r>>1,e>t[i][1])s=i+1;else{if(!(e=131072&&e<=196605||e>=196608&&e<=262141?2:1}charProperties(e,t){let i=this.wcwidth(e),r=0===i&&0!==t;if(r){const e=s.UnicodeService.extractWidth(t);0===e?r=!1:e>i&&(i=e)}return s.UnicodeService.createPropertyValue(0,i,r)}}},9249(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Win32InputMode=void 0,t.Win32InputMode=class{constructor(){this._codeToVk={KeyA:65,KeyB:66,KeyC:67,KeyD:68,KeyE:69,KeyF:70,KeyG:71,KeyH:72,KeyI:73,KeyJ:74,KeyK:75,KeyL:76,KeyM:77,KeyN:78,KeyO:79,KeyP:80,KeyQ:81,KeyR:82,KeyS:83,KeyT:84,KeyU:85,KeyV:86,KeyW:87,KeyX:88,KeyY:89,KeyZ:90,Digit0:48,Digit1:49,Digit2:50,Digit3:51,Digit4:52,Digit5:53,Digit6:54,Digit7:55,Digit8:56,Digit9:57,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,F13:124,F14:125,F15:126,F16:127,F17:128,F18:129,F19:130,F20:131,F21:132,F22:133,F23:134,F24:135,Numpad0:96,Numpad1:97,Numpad2:98,Numpad3:99,Numpad4:100,Numpad5:101,Numpad6:102,Numpad7:103,Numpad8:104,Numpad9:105,NumpadMultiply:106,NumpadAdd:107,NumpadSeparator:108,NumpadSubtract:109,NumpadDecimal:110,NumpadDivide:111,NumpadEnter:13,NumLock:144,ArrowUp:38,ArrowDown:40,ArrowLeft:37,ArrowRight:39,Home:36,End:35,PageUp:33,PageDown:34,Insert:45,Delete:46,ShiftLeft:16,ShiftRight:16,ControlLeft:17,ControlRight:17,AltLeft:18,AltRight:18,MetaLeft:91,MetaRight:92,CapsLock:20,ScrollLock:145,Escape:27,Enter:13,Tab:9,Space:32,Backspace:8,Pause:19,ContextMenu:93,PrintScreen:44,Semicolon:186,Equal:187,Comma:188,Minus:189,Period:190,Slash:191,Backquote:192,BracketLeft:219,Backslash:220,BracketRight:221,Quote:222,IntlBackslash:226},this._codeToScancode={KeyQ:16,KeyW:17,KeyE:18,KeyR:19,KeyT:20,KeyY:21,KeyU:22,KeyI:23,KeyO:24,KeyP:25,KeyA:30,KeyS:31,KeyD:32,KeyF:33,KeyG:34,KeyH:35,KeyJ:36,KeyK:37,KeyL:38,KeyZ:44,KeyX:45,KeyC:46,KeyV:47,KeyB:48,KeyN:49,KeyM:50,Digit1:2,Digit2:3,Digit3:4,Digit4:5,Digit5:6,Digit6:7,Digit7:8,Digit8:9,Digit9:10,Digit0:11,F1:59,F2:60,F3:61,F4:62,F5:63,F6:64,F7:65,F8:66,F9:67,F10:68,F11:87,F12:88,Numpad0:82,Numpad1:79,Numpad2:80,Numpad3:81,Numpad4:75,Numpad5:76,Numpad6:77,Numpad7:71,Numpad8:72,Numpad9:73,NumpadMultiply:55,NumpadAdd:78,NumpadSubtract:74,NumpadDecimal:83,NumpadDivide:53,NumpadEnter:28,NumLock:69,ArrowUp:72,ArrowDown:80,ArrowLeft:75,ArrowRight:77,Home:71,End:79,PageUp:73,PageDown:81,Insert:82,Delete:83,ShiftLeft:42,ShiftRight:54,ControlLeft:29,ControlRight:29,AltLeft:56,AltRight:56,CapsLock:58,ScrollLock:70,Escape:1,Enter:28,Tab:15,Space:57,Backspace:14,Pause:69,Semicolon:39,Equal:13,Comma:51,Minus:12,Period:52,Slash:53,Backquote:41,BracketLeft:26,Backslash:43,BracketRight:27,Quote:40},this._enhancedKeyCodes=new Set(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","PageUp","PageDown","Insert","Delete","NumpadEnter","NumpadDivide","ControlRight","AltRight","PrintScreen","Pause","ContextMenu","MetaLeft","MetaRight"]),this._keyToControlChar={Enter:13,Backspace:8,Tab:9,Escape:27}}_getVirtualKeyCode(e){const t=this._codeToVk[e.code];return void 0!==t?t:e.keyCode||0}_getScanCode(e){return this._codeToScancode[e.code]||0}_getUnicodeChar(e){if(e.ctrlKey&&!e.altKey&&!e.metaKey){if("Enter"===e.key)return 10;if("Backspace"===e.key)return 127}const t=this._keyToControlChar[e.key];if(void 0!==t)return t;if(1===e.key.length){const t=e.key.codePointAt(0)||0;if(e.ctrlKey&&!e.altKey&&!e.metaKey){if(t>=65&&t<=90)return t-64;if(t>=97&&t<=122)return t-96}return t}return 0}_getControlKeyState(e){let t=0;return e.shiftKey&&(t|=16),e.ctrlKey&&("ControlRight"===e.code?t|=4:t|=8),e.altKey&&("AltRight"===e.code?t|=1:t|=2),this._enhancedKeyCodes.has(e.code)&&(t|=256),t}evaluateKeyboardEvent(e,t){return{type:0,cancel:!0,key:`[${this._getVirtualKeyCode(e)};${this._getScanCode(e)};${this._getUnicodeChar(e)};${t?1:0};${this._getControlKeyState(e)};1_`}}}},3562(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.WriteBuffer=void 0;const s=i(3132),r=i(4812),o=i(8636);class n extends r.Disposable{constructor(e){super(),this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0,this._isSyncWriting=!1,this._syncCalls=0,this._didUserInput=!1,this._innerWriteTimer=this._register(new s.TimeoutTimer),this._onWriteParsed=this._register(new o.Emitter),this.onWriteParsed=this._onWriteParsed.event,this._register((0,r.toDisposable)(()=>{this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0}))}handleUserInput(){this._didUserInput=!0}flushSync(){if(this._store.isDisposed)return;if(this._isSyncWriting)return;let e;this._isSyncWriting=!0;let t=!1;for(;e=this._writeBuffer.shift();){t=!0,this._action(e);const i=this._callbacks.shift();i&&i()}this._pendingData=0,this._bufferOffset=2147483647,this._writeBuffer.length=0,this._callbacks.length=0,this._isSyncWriting=!1,t&&this._onWriteParsed.fire()}writeSync(e,t){if(this._store.isDisposed)return;if(void 0!==t&&this._syncCalls>t)return void(this._syncCalls=0);if(this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;let i;for(this._isSyncWriting=!0;i=this._writeBuffer.shift();){this._action(i);const e=this._callbacks.shift();e&&e()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,t){if(!this._store.isDisposed){if(this._pendingData>5e7)throw new Error("write data discarded, use flow control to avoid losing data");if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput)return this._didUserInput=!1,this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t),void this._innerWrite();this._scheduleInnerWrite()}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)}}_scheduleInnerWrite(e=0,t=!0){this._store.isDisposed||this._innerWriteTimer.cancelAndSet(()=>this._innerWrite(e,t),0)}_innerWrite(e=0,t=!0){if(this._store.isDisposed)return;const i=e||performance.now();for(;this._writeBuffer.length>this._bufferOffset;){const e=this._writeBuffer[this._bufferOffset],s=this._action(e,t);if(s){const e=e=>{this._store.isDisposed||(performance.now()-i>=12?this._scheduleInnerWrite(0,e):this._innerWrite(i,e))};return void s.catch(e=>(queueMicrotask(()=>{throw e}),Promise.resolve(!1))).then(e)}const r=this._callbacks[this._bufferOffset];if(r&&r(),this._bufferOffset++,this._pendingData-=e.length,performance.now()-i>=12)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>50&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),this._scheduleInnerWrite()):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}}t.WriteBuffer=n},8693(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.parseColor=function(e){if(!e)return;let t=e.toLowerCase();if(t.startsWith("rgb:")){t=t.slice(4);const e=i.exec(t);if(e){const t=e[1]?15:e[4]?255:e[7]?4095:65535;return[Math.round(parseInt(e[1]||e[4]||e[7]||e[10],16)/t*255),Math.round(parseInt(e[2]||e[5]||e[8]||e[11],16)/t*255),Math.round(parseInt(e[3]||e[6]||e[9]||e[12],16)/t*255)]}}else if(t.startsWith("#")&&(t=t.slice(1),s.exec(t)&&[3,6,9,12].includes(t.length))){const e=t.length/3,i=[0,0,0];for(let s=0;s<3;++s){const r=parseInt(t.slice(e*s,e*s+e),16);i[s]=1===e?r<<4:2===e?r:3===e?r>>4:r>>8}return i}},t.toRgbString=function(e,t=16){const[i,s,o]=e;return`rgb:${r(i,t)}/${r(s,t)}/${r(o,t)}`};const i=/^([\da-f])\/([\da-f])\/([\da-f])$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/,s=/^[\da-f]+$/;function r(e,t){const i=e.toString(16),s=i.length<2?"0"+i:i;switch(t){case 4:return i[0];case 8:return s;case 12:return(s+s).slice(0,3);default:return s+s}}},2607(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.ApcHandler=t.ApcParser=void 0;const s=i(726),r=i(4220),o=[];t.ApcParser=class{constructor(){this._handlers=Object.create(null),this._active=o,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){this._handlers[e]??=[];const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=o}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=o,this._ident=0}start(e){if(this.reset(),this._ident=e,this._active=this._handlers[e]||o,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].start();else this._handlerFb(this._ident,"START")}put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._ident,"PUT",(0,s.utf32ToString)(e,t,i))}end(e,t=!0){if(this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].end(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._ident,"END",e);this._active=o,this._ident=0}};class n{constructor(e){this._handler=e,this._data=new r.LimitedStringBuilder(n._payloadLimit),this._hitLimit=!1}start(){this._data.reset(),this._hitLimit=!1}put(e,t,i){this._hitLimit||this._data.append((0,s.utf32ToString)(e,t,i))&&(this._hitLimit=!0)}end(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data.toString()),t instanceof Promise))return t.then(e=>(this._data.reset(),this._hitLimit=!1,e));return this._data.reset(),this._hitLimit=!1,t}}t.ApcHandler=n,n._payloadLimit=1e7},9823(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DcsHandler=t.DcsParser=void 0;const s=i(726),r=i(7262),o=i(4220),n=[];t.DcsParser=class{constructor(){this._handlers=Object.create(null),this._active=n,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=n}registerHandler(e,t){this._handlers[e]??=[];const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].unhook(!1);this._stack.paused=!1,this._active=n,this._ident=0}hook(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||n,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].hook(t);else this._handlerFb(this._ident,"HOOK",t)}put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._ident,"PUT",(0,s.utf32ToString)(e,t,i))}unhook(e,t=!0){if(this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].unhook(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].unhook(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._ident,"UNHOOK",e);this._active=n,this._ident=0}};const a=new r.Params;a.addParam(0);class h{constructor(e){this._handler=e,this._data=new o.LimitedStringBuilder(h._payloadLimit),this._params=a,this._hitLimit=!1}hook(e){this._params=e.length>1||e.params[0]?e.clone():a,this._data.reset(),this._hitLimit=!1}put(e,t,i){this._hitLimit||this._data.append((0,s.utf32ToString)(e,t,i))&&(this._hitLimit=!0)}unhook(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data.toString(),this._params),t instanceof Promise))return t.then(e=>(this._params=a,this._data.reset(),this._hitLimit=!1,e));return this._params=a,this._data.reset(),this._hitLimit=!1,t}}t.DcsHandler=h,h._payloadLimit=1e7},6717(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.EscapeSequenceParser=t.VT500_TRANSITION_TABLE=t.TransitionTable=void 0;const s=i(4812),r=i(7262),o=i(1346),n=i(9823),a=i(2607);class h{constructor(e){this.table=new Uint16Array(e)}setDefault(e,t){this.table.fill(e<<8|t)}add(e,t,i,s){this.table[t<<8|e]=i<<8|s}addMany(e,t,i,s){for(let r=0;rt),i=(e,i)=>t.slice(e,i),s=i(32,127),r=i(0,24);r.push(25),r.push.apply(r,i(28,32));const o=i(0,17);e.setDefault(1,0),e.addMany(s,0,2,0);for(const t of o)e.addMany([24,26,153,154],t,3,0),e.addMany(i(128,144),t,3,0),e.addMany(i(144,152),t,3,0),e.add(156,t,0,0),e.add(27,t,11,1),e.add(157,t,4,8),e.addMany([152,158],t,0,7),e.add(159,t,11,14),e.add(155,t,11,3),e.add(144,t,11,9);return e.addMany(r,0,3,0),e.addMany(r,1,3,1),e.add(127,1,0,1),e.addMany(r,8,0,8),e.addMany(r,3,3,3),e.add(127,3,0,3),e.addMany(r,4,3,4),e.add(127,4,0,4),e.addMany(r,6,3,6),e.addMany(r,5,3,5),e.add(127,5,0,5),e.addMany(r,2,3,2),e.add(127,2,0,2),e.add(93,1,4,8),e.addMany(s,8,5,8),e.add(127,8,5,8),e.addMany([156,27,24,26,7],8,6,0),e.addMany(i(28,32),8,0,8),e.addMany([88,94],1,0,7),e.addMany(s,7,0,7),e.addMany(r,7,0,7),e.add(156,7,0,0),e.add(127,7,0,7),e.add(95,1,11,14),e.addMany(r,14,0,14),e.add(127,14,0,14),e.addMany(i(32,48),14,9,15),e.addMany(i(48,127),14,15,16),e.addMany(i(48,127),15,15,16),e.addMany(r,15,0,15),e.addMany(i(32,48),15,9,15),e.add(127,15,0,15),e.addMany(s,16,16,16),e.addMany(r,16,0,16),e.addMany(i(8,14),16,16,16),e.add(127,16,0,16),e.addMany([27,156,24,26],16,17,0),e.add(91,1,11,3),e.addMany(i(64,127),3,7,0),e.addMany(i(48,60),3,8,4),e.addMany([60,61,62,63],3,9,4),e.addMany(i(48,60),4,8,4),e.addMany(i(64,127),4,7,0),e.addMany([60,61,62,63],4,0,6),e.addMany(i(32,64),6,0,6),e.add(127,6,0,6),e.addMany(i(64,127),6,0,0),e.addMany(i(32,48),3,9,5),e.addMany(i(32,48),5,9,5),e.addMany(i(48,64),5,0,6),e.addMany(i(64,127),5,7,0),e.addMany(i(32,48),4,9,5),e.addMany(i(32,48),1,9,2),e.addMany(i(32,48),2,9,2),e.addMany(i(48,127),2,10,0),e.addMany(i(48,80),1,10,0),e.addMany(i(81,88),1,10,0),e.addMany([89,90,92],1,10,0),e.addMany(i(96,127),1,10,0),e.add(80,1,11,9),e.addMany(r,9,0,9),e.add(127,9,0,9),e.addMany(i(32,48),9,9,12),e.addMany(i(48,60),9,8,10),e.addMany([60,61,62,63],9,9,10),e.addMany(r,11,0,11),e.addMany(i(32,128),11,0,11),e.addMany(r,10,0,10),e.add(127,10,0,10),e.addMany(i(48,60),10,8,10),e.addMany([60,61,62,63],10,0,11),e.addMany(i(32,48),10,9,12),e.addMany(r,12,0,12),e.add(127,12,0,12),e.addMany(i(32,48),12,9,12),e.addMany(i(48,64),12,0,11),e.addMany(i(64,127),12,12,13),e.addMany(i(64,127),10,12,13),e.addMany(i(64,127),9,12,13),e.addMany(r,13,13,13),e.addMany(s,13,13,13),e.add(127,13,0,13),e.addMany([27,156,24,26],13,14,0),e.add(l,0,2,0),e.add(l,8,5,8),e.add(l,6,0,6),e.add(l,11,0,11),e.add(l,13,13,13),e.add(l,16,16,16),e}();class c extends s.Disposable{constructor(e=t.VT500_TRANSITION_TABLE){super(),this._transitions=e,this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0},this.initialState=0,this.currentState=this.initialState,this._params=new r.Params,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(e,t,i)=>{},this._executeHandlerFb=e=>{},this._csiHandlerFb=(e,t)=>{},this._escHandlerFb=e=>{},this._errorHandlerFb=e=>e,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._executeHandlersArr=new Array(24).fill(void 0),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this._register((0,s.toDisposable)(()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._executeHandlersArr=new Array(24).fill(void 0),this._escHandlers=Object.create(null)})),this._oscParser=this._register(new o.OscParser),this._dcsParser=this._register(new n.DcsParser),this._apcParser=this._register(new a.ApcParser),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:"\\"},()=>!0)}_identifier(e,t=[64,126]){let i=0;if(e.prefix){if(e.prefix.length>1)throw new Error("only one byte as prefix supported");if(i=e.prefix.charCodeAt(0),i<60||i>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(e.intermediates){if(e.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(let t=0;ts||s>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");i<<=8,i|=s}}if(1!==e.final.length)throw new Error("final must be a single byte");const s=e.final.charCodeAt(0);if(t[0]>s||s>t[1])throw new Error(`final must be in range ${t[0]} .. ${t[1]}`);return i<<=8,i|=s,i}identToString(e){const t=[];for(;e;)t.push(String.fromCharCode(255&e)),e>>=8;return t.reverse().join("")}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,t){const i=this._identifier(e,[48,126]);this._escHandlers[i]??=[];const s=this._escHandlers[i];return s.push(t),{dispose:()=>{const e=s.indexOf(t);-1!==e&&s.splice(e,1)}}}clearEscHandler(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]}setEscHandlerFallback(e){this._escHandlerFb=e}setExecuteHandler(e,t){const i=e.charCodeAt(0);this._executeHandlers[i]=t,i<24&&(this._executeHandlersArr[i]=t)}clearExecuteHandler(e){const t=e.charCodeAt(0);this._executeHandlers[t]&&delete this._executeHandlers[t],t<24&&(this._executeHandlersArr[t]=void 0)}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,t){const i=this._identifier(e);this._csiHandlers[i]??=[];const s=this._csiHandlers[i];return s.push(t),{dispose:()=>{const e=s.indexOf(t);-1!==e&&s.splice(e,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,t){return this._dcsParser.registerHandler(this._identifier(e),t)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,t){return this._oscParser.registerHandler(e,t)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}registerApcHandler(e,t){return e.prefix=void 0,this._apcParser.registerHandler(this._identifier(e,[48,126]),t)}clearApcHandler(e){e.prefix=void 0,this._apcParser.clearHandler(this._identifier(e,[48,126]))}setApcHandlerFallback(e){this._apcParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._apcParser.reset(),this._params.resetZdm(),this._collect=0,this.precedingJoinState=0,0!==this._parseStack.state&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(e,t,i,s,r){this._parseStack.state=e,this._parseStack.handlers=t,this._parseStack.handlerPos=i,this._parseStack.transition=s,this._parseStack.chunkPos=r}parse(e,t,i){let s,r,o,n=0;if(this._parseStack.state)if(2===this._parseStack.state)this._parseStack.state=0,n=this._parseStack.chunkPos+1;else{if(void 0===i||1===this._parseStack.state)throw this._parseStack.state=1,new Error("improper continuation due to previous async handler, giving up parsing");const t=this._parseStack.handlers;let r=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(!1===i&&r>-1)for(;r>=0&&(o=t[r](this._params),!0!==o);r--)if(o instanceof Promise)return this._parseStack.handlerPos=r,o;this._parseStack.handlers=[];break;case 4:if(!1===i&&r>-1)for(;r>=0&&(o=t[r](),!0!==o);r--)if(o instanceof Promise)return this._parseStack.handlerPos=r,o;this._parseStack.handlers=[];break;case 6:if(s=e[this._parseStack.chunkPos],o=this._dcsParser.unhook(24!==s&&26!==s,i),o)return o;27===s&&(this._parseStack.transition|=1),this._params.resetZdm(),this._collect=0;break;case 5:if(s=e[this._parseStack.chunkPos],o=this._oscParser.end(24!==s&&26!==s,i),o)return o;27===s&&(this._parseStack.transition|=1),this._params.resetZdm(),this._collect=0;break;case 7:if(s=e[this._parseStack.chunkPos],o=this._apcParser.end(24!==s&&26!==s,i),o)return o;27===s&&(this._parseStack.transition|=1),this._params.resetZdm(),this._collect=0}this._parseStack.state=0,n=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=255&this._parseStack.transition}for(let i=n;i=60&&n<=63&&(this._collect=n,s++);let a=!1;for(;s=48&&n<=57)this._params.addDigit(n-48);else if(59===n)this._params.addParam(0);else{if(58!==n){if(n>=64&&n<=126){const e=this._csiHandlers[this._collect<<8|n];let t=e?e.length-1:-1;for(;t>=0&&(o=e[t](this._params),!0!==o);t--)if(o instanceof Promise)return r=1792,this._preserveStack(3,e,t,r,s),o;t<0&&this._csiHandlerFb(this._collect<<8|n,this._params),this.precedingJoinState=0,i=s,this.currentState=0,a=!0;break}break}this._params.addSubParam(-1)}a||(i=s-1,this.currentState=4);continue}switch(r=this._transitions.table[this.currentState<<8|(s>8){case 2:let n=i;const a=t-4;for(;n=32&&(e[n]<=126||e[n]>=l)&&e[++n]>=32&&(e[n]<=126||e[n]>=l)&&e[++n]>=32&&(e[n]<=126||e[n]>=l)&&e[++n]>=32&&(e[n]<=126||e[n]>=l););if(n>=a)for(;n=32&&(e[n]<=126||e[n]>=l);)n++;this._printHandler(e,i,n),i=n-1;break;case 3:this._executeHandlers[s]?this._executeHandlers[s]():this._executeHandlerFb(s),this.precedingJoinState=0;break;case 0:break;case 1:if(this._errorHandler({position:i,code:s,currentState:this.currentState,collect:this._collect,params:this._params,abort:!1}).abort)return;break;case 7:const h=this._csiHandlers[this._collect<<8|s];let c=h?h.length-1:-1;for(;c>=0&&(o=h[c](this._params),!0!==o);c--)if(o instanceof Promise)return this._preserveStack(3,h,c,r,i),o;c<0&&this._csiHandlerFb(this._collect<<8|s,this._params),this.precedingJoinState=0;break;case 8:do{switch(s){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(s-48)}}while(++i47&&s<60);i--;break;case 9:this._collect<<=8,this._collect|=s;break;case 10:const d=this._escHandlers[this._collect<<8|s];let _=d?d.length-1:-1;for(;_>=0&&(o=d[_](),!0!==o);_--)if(o instanceof Promise)return this._preserveStack(4,d,_,r,i),o;_<0&&this._escHandlerFb(this._collect<<8|s),this.precedingJoinState=0;break;case 11:this._params.resetZdm(),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|s,this._params);break;case 13:for(let r=i+1;;++r)if(r>=t||24===(s=e[r])||26===s||27===s||s>127&&s=t||(s=e[r])<32||s>127&&s=32&&e[s]<127||e[s]>=8&&e[s]<14||e[s]>=l))){this._apcParser.put(e,i,s),i=s-1;break}break;case 17:if(o=this._apcParser.end(24!==s&&26!==s),o)return this._preserveStack(7,[],0,r,i),o;27===s&&(r|=1),this._params.resetZdm(),this._collect=0,this.precedingJoinState=0}this.currentState=255&r}}}t.EscapeSequenceParser=c},1346(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.OscHandler=t.OscParser=void 0;const s=i(726),r=i(4220),o=[];t.OscParser=class{constructor(){this._state=0,this._active=o,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){this._handlers[e]??=[];const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=o}reset(){if(2===this._state)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=o,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||o,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].start();else this._handlerFb(this._id,"START")}_put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._id,"PUT",(0,s.utf32ToString)(e,t,i))}start(){this.reset(),this._state=1}put(e,t,i){if(3!==this._state){if(1===this._state)for(;t0&&this._put(e,t,i)}}end(e,t=!0){if(0!==this._state){if(3!==this._state)if(1===this._state&&this._start(),this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].end(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._id,"END",e);this._active=o,this._id=-1,this._state=0}}};class n{constructor(e){this._handler=e,this._data=new r.LimitedStringBuilder(n._payloadLimit),this._hitLimit=!1}start(){this._data.reset(),this._hitLimit=!1}put(e,t,i){this._hitLimit||this._data.append((0,s.utf32ToString)(e,t,i))&&(this._hitLimit=!0)}end(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data.toString()),t instanceof Promise))return t.then(e=>(this._data.reset(),this._hitLimit=!1,e));return this._data.reset(),this._hitLimit=!1,t}}t.OscHandler=n,n._payloadLimit=1e7},7262(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Params=void 0;class i{static fromArray(e){const t=new i;if(!e.length)return t;for(let i=Array.isArray(e[0])?1:0;i256)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(e),this.length=0,this._subParams=new Int32Array(t),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(e),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}clone(){const e=new i(this.maxLength,this.maxSubParamsLength);return e.params.set(this.params),e.length=this.length,e._subParams.set(this._subParams),e._subParamsLength=this._subParamsLength,e._subParamsIdx.set(this._subParamsIdx),e._rejectDigits=this._rejectDigits,e._rejectSubDigits=this._rejectSubDigits,e._digitIsSub=this._digitIsSub,e}toArray(){const e=[];for(let t=0;t>8,s=255&this._subParamsIdx[t];s-i>0&&e.push(Array.prototype.slice.call(this._subParams,i,s))}return e}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}resetZdm(){this.length=1,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1,this._subParamsIdx[0]=0,this.params[0]=0}addParam(e){if(this._digitIsSub=!1,this.length>=this.maxLength)this._rejectDigits=!0;else{if(e<-1)throw new Error("values less than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=e>2147483647?2147483647:e}}addSubParam(e){if(this._digitIsSub=!0,this.length)if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength)this._rejectSubDigits=!0;else{if(e<-1)throw new Error("values less than -1 are not allowed");this._subParams[this._subParamsLength++]=e>2147483647?2147483647:e,this._subParamsIdx[this.length-1]++}}hasSubParams(e){return(255&this._subParamsIdx[e])-(this._subParamsIdx[e]>>8)>0}getSubParams(e){const t=this._subParamsIdx[e]>>8,i=255&this._subParamsIdx[e];return i-t>0?this._subParams.subarray(t,i):null}getSubParamsAll(){const e={};for(let t=0;t>8,s=255&this._subParamsIdx[t];s-i>0&&(e[t]=this._subParams.slice(i,s))}return e}addDigit(e){let t;if(this._rejectDigits||!(t=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;const i=this._digitIsSub?this._subParams:this.params,s=i[t-1];i[t-1]=~s?Math.min(10*s+e,2147483647):e}}t.Params=i},3027(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.AddonManager=void 0,t.AddonManager=class{constructor(){this._addons=[]}dispose(){for(let e=this._addons.length-1;e>=0;e--)this._addons[e].instance.dispose()}loadAddon(e,t){const i={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(i),t.dispose=()=>this._wrappedAddonDispose(i),t.activate(e)}_wrappedAddonDispose(e){if(e.isDisposed)return;let t=-1;for(let i=0;i=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new s.CellData)}translateToString(e,t,i){return this._line.translateToString(e,t,i)}}},5101(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.BufferNamespaceApi=void 0;const s=i(3235),r=i(4812),o=i(8636);class n extends r.Disposable{constructor(e){super(),this._core=e,this._onBufferChange=this._register(new o.Emitter),this.onBufferChange=this._onBufferChange.event,this._normal=new s.BufferApiView(this._core.buffers.normal,"normal"),this._alternate=new s.BufferApiView(this._core.buffers.alt,"alternate"),this._register(this._core.buffers.onBufferActivate(()=>this._onBufferChange.fire(this.active)))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw new Error("Active buffer is neither normal nor alternate")}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}}t.BufferNamespaceApi=n},6097(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ParserApi=void 0,t.ParserApi=class{constructor(e){this._core=e}registerCsiHandler(e,t){return this._core.registerCsiHandler(e,e=>t(e.toArray()))}addCsiHandler(e,t){return this.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._core.registerDcsHandler(e,(e,i)=>t(e,i.toArray()))}addDcsHandler(e,t){return this.registerDcsHandler(e,t)}registerEscHandler(e,t){return this._core.registerEscHandler(e,t)}addEscHandler(e,t){return this.registerEscHandler(e,t)}registerOscHandler(e,t){return this._core.registerOscHandler(e,t)}addOscHandler(e,t){return this.registerOscHandler(e,t)}registerApcHandler(e,t){return this._core.registerApcHandler(e,t)}}},4335(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeApi=void 0,t.UnicodeApi=class{constructor(e){this._core=e}register(e){this._core.unicodeService.register(e)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(e){this._core.unicodeService.activeVersion=e}}},9640(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.BufferService=void 0;const o=i(4812),n=i(4097),a=i(6501),h=i(8636);let l=class extends o.Disposable{get buffer(){return this.buffers.active}constructor(e,t){super(),this.isUserScrolling=!1,this._onResize=this._register(new h.Emitter),this.onResize=this._onResize.event,this._onScroll=this._register(new h.Emitter),this.onScroll=this._onScroll.event,this.cols=Math.max(e.rawOptions.cols||0,2),this.rows=Math.max(e.rawOptions.rows||0,1),this.buffers=this._register(new n.BufferSet(e,this,t)),this._register(this.buffers.onBufferActivate(e=>{this._onScroll.fire(e.activeBuffer.ydisp)}))}resize(e,t){const i=this.cols!==e,s=this.rows!==t;this.cols=e,this.rows=t,this.buffers.resize(e,t),this._onResize.fire({cols:e,rows:t,colsChanged:i,rowsChanged:s})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,t=!1){const i=this.buffer;let s;s=this._cachedBlankLine,s&&s.length===this.cols&&s.getFg(0)===e.fg&&s.getBg(0)===e.bg||(s=i.getBlankLine(e,t),this._cachedBlankLine=s),s.isWrapped=t;const r=i.ybase+i.scrollTop,o=i.ybase+i.scrollBottom;if(0===i.scrollTop){const e=i.lines.isFull;o===i.lines.length-1?e?i.lines.recycle().copyFrom(s):i.lines.push(s.clone()):i.lines.splice(o+1,0,s.clone()),e?this.isUserScrolling&&(i.ydisp=Math.max(i.ydisp-1,0)):(i.ybase++,this.isUserScrolling||i.ydisp++)}else{const e=o-r+1;i.lines.shiftElements(r+1,e-1,-1),i.lines.set(o,s.clone())}this.isUserScrolling||(i.ydisp=i.ybase),this._onScroll.fire(i.ydisp)}scrollLines(e,t){const i=this.buffer;if(e<0){if(0===i.ydisp)return;this.isUserScrolling=!0}else e+i.ydisp>=i.ybase&&(this.isUserScrolling=!1);const s=i.ydisp;i.ydisp=Math.max(Math.min(i.ydisp+e,i.ybase),0),s!==i.ydisp&&(t||this._onScroll.fire(i.ydisp))}};t.BufferService=l,t.BufferService=l=s([r(0,a.IOptionsService),r(1,a.ILogService)],l)},5746(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.CharsetService=void 0,t.CharsetService=class{constructor(){this.glevel=0,this._charsets=[]}get charsets(){return this._charsets}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(e){this.glevel=e,this.charset=this._charsets[e]}setgCharset(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)}}},4071(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CoreService=void 0;const o=i(4812),n=i(6501),a=i(8636),h=Object.freeze({insertMode:!1}),l=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,colorSchemeUpdates:!1,cursorBlink:void 0,cursorStyle:void 0,origin:!1,reverseWraparound:!1,sendFocus:!1,synchronizedOutput:!1,win32InputMode:!1,wraparound:!0});let c=class extends o.Disposable{constructor(e,t,i){super(),this._bufferService=e,this._logService=t,this._optionsService=i,this.isCursorHidden=!1,this._onData=this._register(new a.Emitter),this.onData=this._onData.event,this._onUserInput=this._register(new a.Emitter),this.onUserInput=this._onUserInput.event,this._onBinary=this._register(new a.Emitter),this.onBinary=this._onBinary.event,this._onRequestScrollToBottom=this._register(new a.Emitter),this.onRequestScrollToBottom=this._onRequestScrollToBottom.event,this.isCursorInitialized=i.rawOptions.showCursorImmediately??!1,this.modes=structuredClone(h),this.decPrivateModes=structuredClone(l),this.kittyKeyboard={flags:0,mainFlags:0,altFlags:0,mainStack:[],altStack:[]}}reset(){this.modes=structuredClone(h),this.decPrivateModes=structuredClone(l),this.kittyKeyboard={flags:0,mainFlags:0,altFlags:0,mainStack:[],altStack:[]}}triggerDataEvent(e,t=!1){if(this._optionsService.rawOptions.disableStdin)return;const i=this._bufferService.buffer;t&&this._optionsService.rawOptions.scrollOnUserInput&&i.ybase!==i.ydisp&&this._onRequestScrollToBottom.fire(),t&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`),this._logService.trace("sending data (codes)",()=>e.split("").map(e=>e.charCodeAt(0))),this._onData.fire(e)}triggerBinaryEvent(e){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${e}"`),this._logService.trace("sending binary (codes)",()=>e.split("").map(e=>e.charCodeAt(0))),this._onBinary.fire(e))}};t.CoreService=c,t.CoreService=c=s([r(0,n.IBufferService),r(1,n.ILogService),r(2,n.IOptionsService)],c)},4720(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DecorationLineCache=t.DecorationService=void 0;const o=i(3132),n=i(4103),a=i(4812),h=i(6501),l=i(3087),c=i(8636);let d=0,_=0,u=class extends a.Disposable{get decorations(){return this._decorations.values()}constructor(e,t){super(),this._logService=e,this._bufferService=t,this._lineCache=this._register(new f),this._onDecorationRegistered=this._register(new c.Emitter),this.onDecorationRegistered=this._onDecorationRegistered.event,this._onDecorationRemoved=this._register(new c.Emitter),this.onDecorationRemoved=this._onDecorationRemoved.event,this._decorations=new l.SortedList(e=>e?.marker.line,this._logService),this._register((0,a.toDisposable)(()=>this.reset())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._lineCache.attachToBufferLines(this._bufferService.buffer.lines)})),this._lineCache.attachToBufferLines(this._bufferService.buffer.lines)}registerDecoration(e){if(e.marker.isDisposed)return;const t=new p(e);if(t){const e=t.marker.onDispose(()=>t.dispose()),i=t.onDispose(()=>{i.dispose(),t&&(this._decorations.delete(t)&&(this._lineCache.remove(t),this._onDecorationRemoved.fire(t)),e.dispose())});this._decorations.insert(t),this._lineCache.add(t),this._onDecorationRegistered.fire(t)}return t}reset(){for(const e of this._decorations.values())e.dispose();this._decorations.clear(),this._lineCache.clear()}*getDecorationsAtCell(e,t,i){const s=this._lineCache.getDecorationsOnLine(t);if(s)for(const t of s)d=t.options.x??0,_=d+(t.options.width??1),e>=d&&e<_&&(!i||(t.options.layer??"bottom")===i)&&(yield t)}forEachDecorationAtCell(e,t,i,s){const r=this._lineCache.getDecorationsOnLine(t);if(r)for(const t of r)d=t.options.x??0,_=d+(t.options.width??1),e>=d&&e<_&&(!i||(t.options.layer??"bottom")===i)&&s(t)}};t.DecorationService=u,t.DecorationService=u=s([r(0,h.ILogService),r(1,h.IBufferService)],u);class f extends a.Disposable{constructor(){super(...arguments),this._decorationsByLine=new Map,this._decorations=new Set,this._bufferLineListeners=this._register(new a.MutableDisposable),this._lineIndexSyncTimer=this._register(new o.MicrotaskTimer),this._lineIndexSyncCallbacks=[]}clear(){this._lineIndexSyncCallbacks.length=0,this._lineIndexSyncTimer.cancel(),this._decorationsByLine.clear(),this._decorations.clear()}add(e){this._decorations.add(e),this._addToLineBuckets(e)}remove(e){this._decorations.delete(e),this._removeFromLineBuckets(e)}getDecorationsOnLine(e){return this._decorationsByLine.get(e)}attachToBufferLines(e){const t=new a.DisposableStore;this._bufferLineListeners.value=t,t.add(e.onTrim(e=>this._handleBufferLinesTrim(e))),t.add(e.onInsert(e=>this._handleBufferLinesInsert(e))),t.add(e.onDelete(e=>this._handleBufferLinesDelete(e)))}_getDecorationHeight(e){return e.options.height??1}_addToLineBuckets(e){const t=e.marker.line;if(t<0)return;e._indexedStartLine=t;const i=this._getDecorationHeight(e);for(let s=t;s=0&&this._addToLineBuckets(e)}_scheduleLineIndexSync(e){this._lineIndexSyncCallbacks.push(e),this._lineIndexSyncTimer.set(()=>{const e=this._lineIndexSyncCallbacks;this._lineIndexSyncCallbacks=[];for(const t of e)t()})}_handleBufferLinesTrim(e){if(e<=0)return;const t=new Map;for(const[i,s]of this._decorationsByLine){const r=i-e;r<0||this._mergeLineBucket(t,r,s)}this._decorationsByLine.clear();for(const[e,i]of t)this._decorationsByLine.set(e,i);for(const t of this._decorations)t.marker.isDisposed||(t._indexedStartLine-=e)}_handleBufferLinesInsert(e){this._scheduleLineIndexSync(()=>this._applyBufferLinesInsert(e))}_handleBufferLinesDelete(e){this._scheduleLineIndexSync(()=>this._applyBufferLinesDelete(e))}_mergeLineBucket(e,t,i){const s=e.get(t);if(s)for(let e=0,t=i.length;et&&(s.push(e),this._removeFromLineBuckets(e))}const r=new Map;for(const[e,s]of this._decorationsByLine){const o=e>=t?e+i:e;this._mergeLineBucket(r,o,s)}this._decorationsByLine.clear();for(const[e,t]of r)this._decorationsByLine.set(e,t);for(const e of this._decorations)e.marker.isDisposed||e._indexedStartLine>=t&&(e._indexedStartLine=e.marker.line);for(const e of s)this._addToLineBuckets(e)}_applyBufferLinesDelete(e){const t=e.index+e.amount,i=new Map;for(const[s,r]of this._decorationsByLine){if(s>=e.index&&s=t?s-e.amount:s;this._mergeLineBucket(i,o,r)}this._decorationsByLine.clear();for(const[e,t]of i)this._decorationsByLine.set(e,t);const s=[];for(const i of this._decorations){if(i.marker.isDisposed)continue;const r=i._indexedStartLine,o=this._getDecorationHeight(i);r>=t?i._indexedStartLine=i.marker.line:rt&&s.push(i)}for(const e of s)this._reindexDecoration(e)}}t.DecorationLineCache=f;class p extends a.DisposableStore{get backgroundColorRGB(){return null===this._cachedBg&&(this.options.backgroundColor?this._cachedBg=n.css.toColor(this.options.backgroundColor):this._cachedBg=void 0),this._cachedBg}get foregroundColorRGB(){return null===this._cachedFg&&(this.options.foregroundColor?this._cachedFg=n.css.toColor(this.options.foregroundColor):this._cachedFg=void 0),this._cachedFg}constructor(e){super(),this.options=e,this.onRenderEmitter=this.add(new c.Emitter),this.onRender=this.onRenderEmitter.event,this._onDispose=this.add(new c.Emitter),this.onDispose=this._onDispose.event,this._cachedBg=null,this._cachedFg=null,this.marker=e.marker,this._indexedStartLine=e.marker.line,this.options.overviewRulerOptions&&!this.options.overviewRulerOptions.position&&(this.options.overviewRulerOptions.position="full")}dispose(){this._onDispose.fire(),super.dispose()}}},6025(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.InstantiationService=t.ServiceCollection=void 0;const s=i(6501),r=i(6201);class o{constructor(...e){this._entries=new Map;for(const[t,i]of e)this.set(t,i)}set(e,t){const i=this._entries.get(e);return this._entries.set(e,t),i}forEach(e){for(const[t,i]of this._entries.entries())e(t,i)}has(e){return this._entries.has(e)}get(e){return this._entries.get(e)}}t.ServiceCollection=o,t.InstantiationService=class{constructor(){this._services=new o,this._services.set(s.IInstantiationService,this)}setService(e,t){this._services.set(e,t)}getService(e){return this._services.get(e)}createInstance(e,...t){const i=(0,r.getServiceDependencies)(e).sort((e,t)=>e.index-t.index),s=[];for(const t of i){const i=this._services.get(t.id);if(!i)throw new Error(`[createInstance] ${e.name} depends on UNKNOWN service ${t.id._id}.`);s.push(i)}const o=i.length>0?i[0].index:t.length;if(t.length!==o)throw new Error(`[createInstance] First service dependency of ${e.name} at position ${o+1} conflicts with ${t.length} static arguments`);return new e(...[...t,...s])}}},7276(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.LogService=void 0;const o=i(4812),n=i(6501),a={trace:n.LogLevelEnum.TRACE,debug:n.LogLevelEnum.DEBUG,info:n.LogLevelEnum.INFO,warn:n.LogLevelEnum.WARN,error:n.LogLevelEnum.ERROR,off:n.LogLevelEnum.OFF};let h=class extends o.Disposable{get logLevel(){return this._logLevel}constructor(e){super(),this._optionsService=e,this._logLevel=n.LogLevelEnum.OFF,this._updateLogLevel(),this._register(this._optionsService.onSpecificOptionChange("logLevel",()=>this._updateLogLevel()))}_updateLogLevel(){this._logLevel=a[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let t=0;t!1},X10:{events:1,restrict:e=>4!==e.button&&1===e.action&&(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)},VT200:{events:19,restrict:e=>32!==e.action},DRAG:{events:23,restrict:e=>32!==e.action||3!==e.button},ANY:{events:31,restrict:e=>!0}};function n(e,t){let i=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return 4===e.button?(i|=64,i|=e.action):(i|=3&e.button,4&e.button&&(i|=64),8&e.button&&(i|=128),32===e.action?i|=32:0!==e.action||t||(i|=3)),i}const a=String.fromCharCode,h={DEFAULT:e=>{const t=[n(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?"":`${a(t[0])}${a(t[1])}${a(t[2])}`},SGR:e=>{const t=0===e.action&&4!==e.button?"m":"M";return`[<${n(e,!0)};${e.col};${e.row}${t}`},SGR_PIXELS:e=>{const t=0===e.action&&4!==e.button?"m":"M";return`[<${n(e,!0)};${e.x};${e.y}${t}`}};class l extends s.Disposable{constructor(){super(),this._protocols={},this._encodings={},this._activeProtocol="",this._activeEncoding="",this._onProtocolChange=this._register(new r.Emitter),this.onProtocolChange=this._onProtocolChange.event;for(const e of Object.keys(o))this.addProtocol(e,o[e]);for(const e of Object.keys(h))this.addEncoding(e,h[e]);this.reset()}addProtocol(e,t){this._protocols[e]=t}addEncoding(e,t){this._encodings[e]=t}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return 0!==this._protocols[this._activeProtocol].events}set activeProtocol(e){if(!this._protocols[e])throw new Error(`unknown protocol "${e}"`);this._activeProtocol=e,this._onProtocolChange.fire(this._protocols[e].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(e){if(!this._encodings[e])throw new Error(`unknown encoding "${e}"`);this._activeEncoding=e}reset(){this.activeProtocol="NONE",this.activeEncoding="DEFAULT"}setCustomWheelEventHandler(e){this._customWheelEventHandler=e}allowCustomWheelEvent(e){return!this._customWheelEventHandler||!1!==this._customWheelEventHandler(e)}restrictMouseEvent(e){return this._protocols[this._activeProtocol].restrict(e)}encodeMouseEvent(e){return this._encodings[this._activeEncoding](e)}get isDefaultEncoding(){return"DEFAULT"===this._activeEncoding}get isPixelEncoding(){return"SGR_PIXELS"===this._activeEncoding}}t.MouseStateService=l},56(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.OptionsService=t.DEFAULT_OPTIONS=void 0;const s=i(4812),r=i(701),o=i(8636);t.DEFAULT_OPTIONS={cols:80,rows:24,showCursorImmediately:!1,cursorBlink:!1,blinkIntervalDuration:0,cursorStyle:"block",cursorWidth:1,cursorInactiveStyle:"outline",drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollSensitivity:5,fontFamily:"monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:"info",logger:null,scrollback:1e3,scrollbar:{showScrollbar:!0},scrollOnEraseInDisplay:!1,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,mouseEventsRequireAlt:!1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},reflowCursorLine:!1,rescaleOverlappingGlyphs:!1,rightClickSelectsWord:r.isMac,windowOptions:{},windowsPty:{},wordSeparator:" ()[]{}',\"`",altClickMovesCursor:!0,convertEol:!1,termName:"xterm",quirks:{},vtExtensions:{}};const n=["normal","bold","100","200","300","400","500","600","700","800","900"];class a extends s.Disposable{constructor(e){super(),this._onOptionChange=this._register(new o.Emitter),this.onOptionChange=this._onOptionChange.event;const i={...t.DEFAULT_OPTIONS};for(const t in e)if(t in i)try{const s=e[t];i[t]=this._sanitizeAndValidateOption(t,s)}catch(e){console.error(e)}this.rawOptions=i,this.options={...i},this._setupOptions(),this._register((0,s.toDisposable)(()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null}))}onSpecificOptionChange(e,t){return this.onOptionChange(i=>{i===e&&t(this.rawOptions[e])})}onMultipleOptionChange(e,t){return this.onOptionChange(i=>{-1!==e.indexOf(i)&&t()})}_setupOptions(){const e=e=>{if(!(e in t.DEFAULT_OPTIONS))throw new Error(`No option with key "${e}"`);return this.rawOptions[e]},i=(e,i)=>{if(!(e in t.DEFAULT_OPTIONS))throw new Error(`No option with key "${e}"`);i=this._sanitizeAndValidateOption(e,i),this.rawOptions[e]!==i&&(this.rawOptions[e]=i,this._onOptionChange.fire(e))};for(const t in this.rawOptions){const s={get:e.bind(this,t),set:i.bind(this,t)};Object.defineProperty(this.options,t,s)}}_sanitizeAndValidateOption(e,i){switch(e){case"cursorStyle":if(i||(i=t.DEFAULT_OPTIONS[e]),!function(e){return"block"===e||"underline"===e||"bar"===e}(i))throw new Error(`"${i}" is not a valid value for ${e}`);break;case"wordSeparator":i||(i=t.DEFAULT_OPTIONS[e]);break;case"fontWeight":case"fontWeightBold":if("number"==typeof i&&1<=i&&i<=1e3)break;i=n.includes(i)?i:t.DEFAULT_OPTIONS[e];break;case"blinkIntervalDuration":if((i=Math.floor(i))<0)throw new Error(`${e} cannot be less than 0, value: ${i}`);break;case"cursorWidth":i=Math.floor(i);case"lineHeight":case"tabStopWidth":if(i<1)throw new Error(`${e} cannot be less than 1, value: ${i}`);break;case"minimumContrastRatio":i=Math.max(1,Math.min(21,Math.round(10*i)/10));break;case"scrollback":if((i=Math.min(i,4294967295))<0)throw new Error(`${e} cannot be less than 0, value: ${i}`);break;case"fastScrollSensitivity":case"scrollSensitivity":if(i<=0)throw new Error(`${e} cannot be less than or equal to 0, value: ${i}`);break;case"rows":case"cols":if(!i&&0!==i)throw new Error(`${e} must be numeric, value: ${i}`);break;case"windowsPty":i=i??{}}return i}}t.OptionsService=a},8811(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OscLinkService=void 0;const o=i(6501);let n=class{constructor(e){this._bufferService=e,this._nextId=1,this._entriesWithId=new Map,this._dataByLinkId=new Map}registerLink(e){const t=this._bufferService.buffer;if(void 0===e.id){const i=t.addMarker(t.ybase+t.y),s={data:e,id:this._nextId++,lines:[i]};return i.onDispose(()=>this._removeMarkerFromLink(s,i)),this._dataByLinkId.set(s.id,s),s.id}const i=e,s=this._getEntryIdKey(i),r=this._entriesWithId.get(s);if(r)return this.addLineToLink(r.id,t.ybase+t.y),r.id;const o=t.addMarker(t.ybase+t.y),n={id:this._nextId++,key:this._getEntryIdKey(i),data:i,lines:[o]};return o.onDispose(()=>this._removeMarkerFromLink(n,o)),this._entriesWithId.set(n.key,n),this._dataByLinkId.set(n.id,n),n.id}addLineToLink(e,t){const i=this._dataByLinkId.get(e);if(i&&i.lines.every(e=>e.line!==t)){const e=this._bufferService.buffer.addMarker(t);i.lines.push(e),e.onDispose(()=>this._removeMarkerFromLink(i,e))}}getLinkData(e){return this._dataByLinkId.get(e)?.data}_getEntryIdKey(e){return`${e.id};;${e.uri}`}_removeMarkerFromLink(e,t){const i=e.lines.indexOf(t);-1!==i&&(e.lines.splice(i,1),0===e.lines.length&&(void 0!==e.data.id&&this._entriesWithId.delete(e.key),this._dataByLinkId.delete(e.id)))}};t.OscLinkService=n,t.OscLinkService=n=s([r(0,o.IBufferService)],n)},6201(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.serviceRegistry=void 0,t.getServiceDependencies=function(e){return e.di$dependencies||[]},t.createDecorator=function(e){if(t.serviceRegistry.has(e))return t.serviceRegistry.get(e);const i=function(e,t,s){if(3!==arguments.length)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");!function(e,t,i){t.di$target===t?t.di$dependencies.push({id:e,index:i}):(t.di$dependencies=[{id:e,index:i}],t.di$target=t)}(i,e,s)};return i._id=e,t.serviceRegistry.set(e,i),i},t.serviceRegistry=new Map},6501(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.IDecorationService=t.IUnicodeService=t.IOscLinkService=t.IOptionsService=t.ILogService=t.LogLevelEnum=t.IInstantiationService=t.ICharsetService=t.ICoreService=t.IMouseStateService=t.IBufferService=void 0;const s=i(6201);var r;t.IBufferService=(0,s.createDecorator)("BufferService"),t.IMouseStateService=(0,s.createDecorator)("MouseStateService"),t.ICoreService=(0,s.createDecorator)("CoreService"),t.ICharsetService=(0,s.createDecorator)("CharsetService"),t.IInstantiationService=(0,s.createDecorator)("InstantiationService"),function(e){e[e.TRACE=0]="TRACE",e[e.DEBUG=1]="DEBUG",e[e.INFO=2]="INFO",e[e.WARN=3]="WARN",e[e.ERROR=4]="ERROR",e[e.OFF=5]="OFF"}(r||(t.LogLevelEnum=r={})),t.ILogService=(0,s.createDecorator)("LogService"),t.IOptionsService=(0,s.createDecorator)("OptionsService"),t.IOscLinkService=(0,s.createDecorator)("OscLinkService"),t.IUnicodeService=(0,s.createDecorator)("UnicodeService"),t.IDecorationService=(0,s.createDecorator)("DecorationService")},6415(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeService=void 0;const s=i(8636);class r{constructor(){this._providers=Object.create(null),this._active="",this._onChange=new s.Emitter,this.onChange=this._onChange.event}static extractShouldJoin(e){return!!(1&e)}static extractWidth(e){return e>>1&3}static extractCharKind(e){return e>>3}static createPropertyValue(e,t,i=!1){return(16777215&e)<<3|(3&t)<<1|(i?1:0)}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(e){if(!this._providers[e])throw new Error(`unknown Unicode version "${e}"`);this._active=e,this._activeProvider=this._providers[e],this._onChange.fire(e)}register(e){this._providers[e.version]=e,this._active||(this.activeVersion=e.version)}wcwidth(e){return this._activeProvider.wcwidth(e)}getStringCellWidth(e){let t=0,i=0;const s=e.length;for(let o=0;o=s)return t+this.wcwidth(n);const i=e.charCodeAt(o);56320<=i&&i<=57343?n=1024*(n-55296)+i-56320+65536:t+=this.wcwidth(i)}const a=this.charProperties(n,i);let h=r.extractWidth(a);r.extractShouldJoin(a)&&(h-=r.extractWidth(i)),t+=h,i=a}return t}charProperties(e,t){return this._activeProvider.charProperties(e,t)}}t.UnicodeService=r}},t={};return function i(s){var r=t[s];if(void 0!==r)return r.exports;var o=t[s]={exports:{}};return e[s].call(o.exports,o,o.exports,i),o.exports}(6081)})()); ++!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i=t();for(var s in i)("object"==typeof exports?exports:e)[s]=i[s]}}(globalThis,()=>(()=>{"use strict";var e={2840(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},a=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._handleBoundaryFocus(e,0),this._bottomBoundaryFocusListener=e=>this._handleBoundaryFocus(e,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=r.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new c.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this._register(this._terminal.onResize(e=>this._handleResize(e.rows))),this._register(this._terminal.onRender(e=>this._refreshRows(e.start,e.end))),this._register(this._terminal.onScroll(()=>this._refreshRows())),this._register(this._terminal.onA11yChar(e=>this._handleChar(e))),this._register(this._terminal.onLineFeed(()=>this._handleChar("\n"))),this._register(this._terminal.onA11yTab(e=>this._handleTab(e))),this._register(this._terminal.onKey(e=>this._handleKey(e.key))),this._register(this._terminal.onBlur(()=>this._clearLiveRegion())),this._register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this._register((0,f.addDisposableListener)(r,"selectionchange",()=>this._handleSelectionChange())),this._register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRowsDimensions(),this._refreshRows(),this._register((0,d.toDisposable)(()=>{this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,"\n"===e&&(this._liveRegionLineCount++,21===this._liveRegionLineCount&&(this._liveRegion.textContent=l.tooMuchOutput.get())))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){const i=this._terminal.buffer,s=i.lines.length.toString();for(let r=e;r<=t;r++){const e=i.lines.get(i.ydisp+r),t=[],o=e?.translateToString(!0,void 0,void 0,t)||"",n=(i.ydisp+r+1).toString(),a=this._rowElements[r];a&&(0===o.length?(a.textContent=" ",this._rowColumns.set(a,[0,1])):(a.textContent=o,this._rowColumns.set(a,t)),a.setAttribute("aria-posinset",n),a.setAttribute("aria-setsize",s),this._alignRowWidth(a))}this._announceCharacters()}_announceCharacters(){0!==this._charsToAnnounce.length&&(this._liveRegion.textContent===l.tooMuchOutput.get()&&this._clearLiveRegion(),this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){const i=e.target,s=this._rowElements[0===t?1:this._rowElements.length-2];if(i.getAttribute("aria-posinset")===(0===t?"1":`${this._terminal.buffer.lines.length}`))return;if(e.relatedTarget!==s)return;let r,o;if(0===t?(r=i,o=this._rowElements.pop(),this._rowContainer.removeChild(o)):(r=this._rowElements.shift(),o=i,this._rowContainer.removeChild(r)),r.removeEventListener("focus",this._topBoundaryFocusListener),o.removeEventListener("focus",this._bottomBoundaryFocusListener),0===t){const e=this._createAccessibilityTreeNode();this._rowElements.unshift(e),this._rowContainer.insertAdjacentElement("afterbegin",e)}else{const e=this._createAccessibilityTreeNode();this._rowElements.push(e),this._rowContainer.appendChild(e)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(0===t?-1:1),this._rowElements[0===t?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(0===this._rowElements.length)return;const e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed)return void(this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection());if(!e.anchorNode||!e.focusNode)return void console.error("anchorNode and/or focusNode are null");let t={node:e.anchorNode,offset:e.anchorOffset},i={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(i.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===i.node&&t.offset>i.offset)&&([t,i]=[i,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;const s=this._rowElements.slice(-1)[0];if(i.node.compareDocumentPosition(s)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(i={node:s,offset:s.textContent?.length??0}),!this._rowContainer.contains(i.node))return;const r=({node:e,offset:t})=>{const i=e instanceof Text?e.parentNode:e;let s=parseInt(i?.getAttribute("aria-posinset"),10)-1;if(isNaN(s))return console.warn("row is invalid. Race condition?"),null;const r=this._rowColumns.get(i);if(!r)return console.warn("columns is null. Race condition?"),null;let o=t=this._terminal.cols&&(++s,o=0),{row:s,column:o}},o=r(t),n=r(i);if(o&&n){if(o.row>n.row||o.row===n.row&&o.column>=n.column)throw new Error("invalid range");this._terminal.select(o.column,o.row,(n.row-o.row)*this._terminal.cols-o.column+n.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let e=this._rowContainer.children.length;ee;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){const e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;ethis._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(e=>this.refresh(e?.start??0,e?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(e=>this._reportWindowsOptions(e))),this._register(this._inputHandler.onColor(e=>this._handleColorEvent(e))),this._register(I.EventUtils.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(I.EventUtils.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(I.EventUtils.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(I.EventUtils.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(e=>this._afterResize(e.cols,e.rows))),this._register((0,N.toDisposable)(()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)}))}_handleColorEvent(e){if(this._themeService)for(const t of e){let e,i;switch(t.index){case 256:e="foreground",i="10";break;case 257:e="background",i="11";break;case 258:e="cursor",i="12";break;default:e="ansi",i="4;"+t.index}switch(t.type){case 0:const s=E.color.toColorRGB("ansi"===e?this._themeService.colors.ansi[t.index]:this._themeService.colors[e]);this.coreService.triggerDataEvent(`]${i};${(0,M.toRgbString)(s)}\\`);break;case 1:if("ansi"===e)this._themeService.modifyColors(e=>e.ansi[t.index]=E.channels.toColor(...t.color));else{const i=e;this._themeService.modifyColors(e=>e[i]=E.channels.toColor(...t.color))}break;case 2:this._themeService.restoreColor(t.index)}}}_reportColorScheme(){if(!this._themeService)return;const e=E.rgb.relativeLuminance(this._themeService.colors.background.rgba>>8)>8)?1:2;this.coreService.triggerDataEvent(`[?997;${e}n`)}_setup(){super._setup(),this._customKeyEventHandler=void 0}get buffer(){return this.buffers.active}focus(){this.textarea&&this.textarea.focus({preventScroll:!0})}_handleScreenReaderModeOptionChange(e){e?!this._accessibilityManager.value&&this._renderService&&(this._accessibilityManager.value=this._instantiationService.createInstance(P.AccessibilityManager,this)):this._accessibilityManager.clear()}_handleTextAreaFocus(e){this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(""),this.element.classList.add("focus"),this._showCursor(),this._onFocus.fire()}blur(){return this.textarea?.blur()}_handleTextAreaBlur(){this.textarea.value="",this.refresh(this.buffer.y,this.buffer.y),this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(""),this.element.classList.remove("focus"),this._onBlur.fire()}_syncTextArea(){if(!this.textarea||!this.buffer.isCursorInViewport||this._compositionHelper.isComposing||!this._renderService)return;const e=this.buffer.ybase+this.buffer.y,t=this.buffer.lines.get(e);if(!t)return;const i=Math.min(this.buffer.x,this.cols-1),s=this._renderService.dimensions.css.cell.height,r=t.getWidth(i),o=this._renderService.dimensions.css.cell.width*r,n=this.buffer.y*this._renderService.dimensions.css.cell.height,a=i*this._renderService.dimensions.css.cell.width;this.textarea.style.left=a+"px",this.textarea.style.top=n+"px",this.textarea.style.width=o+"px",this.textarea.style.height=s+"px",this.textarea.style.lineHeight=s+"px",this.textarea.style.zIndex="-5"}_initGlobal(){this._bindKeys(),this._register((0,H.addDisposableListener)(this.element,"copy",e=>{this.hasSelection()&&(0,a.copyHandler)(e,this._selectionService)}));const e=e=>(0,a.handlePasteEvent)(e,this.textarea,this.coreService,this.optionsService);this._register((0,H.addDisposableListener)(this.textarea,"paste",e)),this._register((0,H.addDisposableListener)(this.element,"paste",e)),x.isFirefox?this._register((0,H.addDisposableListener)(this.element,"mousedown",e=>{2===e.button&&(0,a.rightClickHandler)(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register((0,H.addDisposableListener)(this.element,"contextmenu",e=>{(0,a.rightClickHandler)(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),x.isLinux&&this._register((0,H.addDisposableListener)(this.element,"auxclick",e=>{1===e.button&&(0,a.moveTextAreaUnderMouseCursor)(e,this.textarea,this.screenElement)}))}_bindKeys(){this._register((0,H.addDisposableListener)(this.textarea,"keyup",e=>this._keyUp(e),!0)),this._register((0,H.addDisposableListener)(this.textarea,"keydown",e=>this._keyDown(e),!0)),this._register((0,H.addDisposableListener)(this.textarea,"keypress",e=>this._keyPress(e),!0)),this._register((0,H.addDisposableListener)(this.textarea,"compositionstart",()=>{this._syncTextArea(),this._compositionHelper.compositionstart(),this._compositionHelper.updateCompositionElements()})),this._register((0,H.addDisposableListener)(this.textarea,"compositionupdate",e=>this._compositionHelper.compositionupdate(e))),this._register((0,H.addDisposableListener)(this.textarea,"compositionend",()=>this._compositionHelper.compositionend())),this._register((0,H.addDisposableListener)(this.textarea,"input",e=>this._inputEvent(e),!0)),this._register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(e){if(!e)throw new Error("Terminal requires a parent element.");if(e.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this.element?.ownerDocument.defaultView&&this._coreBrowserService)return void(this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView));this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),this.element.classList.toggle("allow-transparency",this.options.allowTransparency),this._register(this.optionsService.onSpecificOptionChange("allowTransparency",e=>this.element.classList.toggle("allow-transparency",e))),e.appendChild(this.element);const t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._register((0,H.addDisposableListener)(this.screenElement,"mousemove",e=>this.updateCursorStyle(e))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);const i=this.textarea=this._document.createElement("textarea");this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",h.promptLabel.get()),x.isChromeOS||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange("disableStdin",()=>i.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(g.CoreBrowserService,this.textarea,e.ownerDocument.defaultView??window,this._document??("undefined"!=typeof window?window.document:null))),this._instantiationService.setService(C.ICoreBrowserService,this._coreBrowserService),this._register((0,H.addDisposableListener)(this.textarea,"focus",e=>this._handleTextAreaFocus(e))),this._register((0,H.addDisposableListener)(this.textarea,"blur",()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(p.CharSizeService,this._document,this._helperContainer),this._instantiationService.setService(C.ICharSizeService,this._charSizeService),this._themeService=this._instantiationService.createInstance(k.ThemeService),this._instantiationService.setService(C.IThemeService,this._themeService),this._register(this._inputHandler.onRequestColorSchemeQuery(()=>this._reportColorScheme())),this._register(this._themeService.onChangeColors(()=>{this.coreService.decPrivateModes.colorSchemeUpdates&&this._reportColorScheme()})),this._characterJoinerService=this._instantiationService.createInstance(v.CharacterJoinerService),this._instantiationService.setService(C.ICharacterJoinerService,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(w.RenderService,this.rows,this.screenElement)),this._instantiationService.setService(C.IRenderService,this._renderService),this._register(this._renderService.onRenderedViewportChange(e=>this._onRender.fire(e))),this._register(this._renderService.onDimensionsChange(e=>this._onDimensionsChange.fire({css:{canvas:{...e.css.canvas},cell:{...e.css.cell}},device:{canvas:{...e.device.canvas},cell:{...e.device.cell},char:{...e.device.char}}}))),this.onResize(e=>this._renderService.resize(e.cols,e.rows)),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(u.CompositionHelper,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseCoordsService=this._instantiationService.createInstance(S.MouseCoordsService),this._instantiationService.setService(C.IMouseCoordsService,this._mouseCoordsService);const s=this._linkifier.value=this._register(this._instantiationService.createInstance(O.Linkifier,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch(e){this._logService.error("onWillOpen handler threw an exception",e)}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this._register(this.onResize(()=>{this._renderService.handleResize(this.cols,this.rows),this._syncTextArea()})),this._register(this.onBlur(()=>this._renderService.handleBlur())),this._register(this.onFocus(()=>this._renderService.handleFocus())),this._viewport=this._register(this._instantiationService.createInstance(c.Viewport,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(e=>{super.scrollLines(e,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(y.SelectionService,this.element,this.screenElement,s)),this._instantiationService.setService(C.ISelectionService,this._selectionService),this._mouseService=this._instantiationService.createInstance(b.MouseService),this._instantiationService.setService(C.IMouseService,this._mouseService),this._register(this._selectionService.onRequestScrollLines(e=>this.scrollLines(e.amount,e.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(e=>this._renderService.handleSelectionChanged(e.start,e.end,e.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(e=>{this.textarea.value=e,this.textarea.focus(),this.textarea.select()})),this._register(I.EventUtils.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(d.BufferDecorationRenderer,this.screenElement)),this._register((0,H.addDisposableListener)(this.element,"mousedown",e=>this._selectionService.handleMouseDown(e))),this.mouseStateService.areMouseEventsActive&&!this.options.mouseEventsRequireAlt?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):(this._selectionService.enable(),this.element.classList.remove("enable-mouse-events")),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(P.AccessibilityManager,this)),this._register(this.optionsService.onSpecificOptionChange("screenReaderMode",e=>this._handleScreenReaderModeOptionChange(e)));const r=this.options.scrollbar?.showScrollbar??!0,o=this.options.scrollbar?.width;r&&o&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(_.OverviewRulerRenderer,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("scrollbar",e=>{const t=(e?.showScrollbar??!0)&&!!e?.width;!this._overviewRulerRenderer&&t&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(_.OverviewRulerRenderer,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this._mouseService.bindMouse({element:this.element,screenElement:this.screenElement,document:this._document,handleTouchScroll:e=>this._viewport?.handleTouchScroll(e)},e=>this._register(e),()=>this.focus())}_createRenderer(){return this._instantiationService.createInstance(f.DomRenderer,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}refresh(e,t,i=!1){this._renderService?.refreshRows(e,t,i)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){const t=e-this._bufferService.buffer.ydisp;0!==t&&this.scrollLines(t)}paste(e){(0,a.paste)(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this.mouseStateService.setCustomWheelEventHandler(e)}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");const t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return!!this._selectionService&&this._selectionService.hasSelection}select(e,t,i){this._selectionService.setSelection(e,t,i)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(this._selectionService&&this._selectionService.hasSelection)return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;const t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;t||"Dead"!==e.key&&"AltGraph"!==e.key||(this._unprocessedDeadKey=!0);const i=this._keyboardService.evaluateKeyDown(e);if(this.updateCursorStyle(e),3===i.type||2===i.type){const t=this.rows-1;return this.scrollLines(2===i.type?-t:t),e.preventDefault(),e.stopPropagation(),!1}if(1===i.type&&this.selectAll(),this._isThirdLevelShift(this.browser,e))return!0;if(i.cancel&&(e.preventDefault(),e.stopPropagation()),!i.key)return!0;if(!this._keyboardService.useKitty&&!this._keyboardService.useWin32InputMode&&e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&1===e.key.length&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)return!0;if(this._unprocessedDeadKey)return this._unprocessedDeadKey=!1,!0;""!==i.key&&"\r"!==i.key||(this.textarea.value="");const s=this._keyboardService.useWin32InputMode&&W(e);if(this._onKey.fire({key:i.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(i.key,!s),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return e.preventDefault(),e.stopPropagation(),!1;this._keyDownHandled=!0}_isThirdLevelShift(e,t){const i=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState("AltGraph");return"keypress"===t.type?i:i&&(!t.keyCode||t.keyCode>47)}_keyUp(e){if(this._keyDownSeen=!1,this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return;W(e)||this.focus();const t=this._keyboardService.evaluateKeyUp(e);if(t?.key){const i=this._keyboardService.useWin32InputMode&&W(e);this.coreService.triggerDataEvent(t.key,!i)}this.updateCursorStyle(e),this._keyPressHandled=!1}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled)return!1;if(this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;if(e.charCode)t=e.charCode;else if(null===e.which||void 0===e.which)t=e.keyCode;else{if(0===e.which||0===e.charCode)return!1;t=e.which}return!(!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)||(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this._compositionHelper.keypress(t)||this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,0))}_inputEvent(e){if(e.data&&"insertText"===e.inputType&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;const t=e.data;return this.coreService.triggerDataEvent(t,!0),!0}return!1}resize(e,t){e!==this.cols||t!==this.rows?super.resize(e,t):this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure()}_afterResize(e,t){this._charSizeService?.measure()}clear(){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;efunction(e){const t=l(e);for(t.animFrameRequested=!1,t.current=t.next,t.next=[],t.inAnimationFrameRunner=!0;t.current.length>0;)t.current.sort(a.sort),t.current.shift().execute();t.inAnimationFrameRunner=!1}(e))),r};const s=i(3132);function r(e){const t=e;if(t?.ownerDocument?.defaultView)return t.ownerDocument.defaultView;const i=e;return i?.view?i.view:window}class o{constructor(e,t,i,s){this._node=e,this._type=t,this._handler=i,this._options=s,e.addEventListener(t,i,s)}dispose(){this._node&&this._handler&&(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,this._handler=null)}}function n(e,t,i,s){return new o(e,t,i,s)}t.eventType={CLICK:"click",MOUSE_DOWN:"mousedown",MOUSE_OVER:"mouseover",MOUSE_LEAVE:"mouseleave",KEY_DOWN:"keydown",KEY_UP:"keyup",INPUT:"input",BLUR:"blur",FOCUS:"focus",CHANGE:"change",POINTER_DOWN:"pointerdown",POINTER_MOVE:"pointermove",POINTER_UP:"pointerup",MOUSE_WHEEL:"wheel",WHEEL:"wheel"};class a{constructor(e,t){this._runner=e,this.priority=t,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(e){console.error(e)}}static sort(e,t){return t.priority-e.priority}}const h=new Map;function l(e){let t=h.get(e);return t||(t={next:[],current:[],animFrameRequested:!1,inAnimationFrameRunner:!1},h.set(e,t)),t}class c extends s.IntervalTimer{constructor(e){super(),this._defaultTarget=e?r(e):void 0}cancelAndSet(e,t,i){super.cancelAndSet(e,t,i??this._defaultTarget??window)}}t.WindowIntervalTimer=c},8906(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Linkifier=void 0;const o=i(4812),n=i(6501),a=i(7098),h=i(8636),l=i(4159);let c=class extends o.Disposable{get currentLink(){return this._currentLink}constructor(e,t,i,s,r){super(),this._element=e,this._mouseCoordsService=t,this._renderService=i,this._bufferService=s,this._linkProviderService=r,this._linkCacheDisposables=[],this._isMouseOut=!0,this._wasResized=!1,this._activeLine=-1,this._onShowLinkUnderline=this._register(new h.Emitter),this.onShowLinkUnderline=this._onShowLinkUnderline.event,this._onHideLinkUnderline=this._register(new h.Emitter),this.onHideLinkUnderline=this._onHideLinkUnderline.event,this._register((0,o.toDisposable)(()=>{(0,o.dispose)(this._linkCacheDisposables),this._linkCacheDisposables.length=0,this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()})),this._register(this._bufferService.onResize(()=>{this._clearCurrentLink(),this._wasResized=!0})),this._register((0,l.addDisposableListener)(this._element,"mouseleave",()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this._register((0,l.addDisposableListener)(this._element,"mousemove",this._handleMouseMove.bind(this))),this._register((0,l.addDisposableListener)(this._element,"mousedown",this._handleMouseDown.bind(this))),this._register((0,l.addDisposableListener)(this._element,"mouseup",this._handleMouseUp.bind(this)))}_handleMouseMove(e){this._lastMouseEvent=e;const t=this._positionFromMouseEvent(e,this._element);if(!t)return;this._isMouseOut=!1;const i=e.composedPath();for(let e=0;e{e?.forEach(e=>{e.link.dispose&&e.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=e.y);let i=!1;for(const[s,r]of this._linkProviderService.linkProviders.entries())if(t){const t=this._activeProviderReplies?.get(s);t&&(i=this._checkLinkProviderResult(s,e,i))}else r.provideLinks(e.y,t=>{if(this._isMouseOut)return;const r=t?.map(e=>({link:e}));this._activeProviderReplies?.set(s,r),i=this._checkLinkProviderResult(s,e,i),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)})}_removeIntersectingLinks(e,t){const i=new Set;for(let s=0;se?this._bufferService.cols:s.link.range.end.x;for(let e=o;e<=n;e++){if(i.has(e)){r.splice(t--,1);break}i.add(e)}}}}_checkLinkProviderResult(e,t,i){if(!this._activeProviderReplies)return i;const s=this._activeProviderReplies.get(e);let r=!1;for(let t=0;tthis._linkAtPosition(e.link,t));e&&(i=!0,this._handleNewLink(e))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!i)for(let e=0;ethis._linkAtPosition(e.link,t));if(s){i=!0,this._handleNewLink(s);break}}return i}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;const t=this._positionFromMouseEvent(e,this._element);var i,s;t&&this._mouseDownLink&&(i=this._mouseDownLink.link,s=this._currentLink.link,i.text===s.text&&i.range.start.x===s.range.start.x&&i.range.start.y===s.range.start.y&&i.range.end.x===s.range.end.x&&i.range.end.y===s.range.end.y)&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){this._currentLink&&this._lastMouseEvent&&(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,(0,o.dispose)(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;const t=this._positionFromMouseEvent(this._lastMouseEvent,this._element);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:void 0===e.link.decorations||e.link.decorations.underline,pointerCursor:void 0===e.link.decorations||e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:e=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==e&&(this._currentLink.state.decorations.pointerCursor=e,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",e))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:t=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==t&&(this._currentLink.state.decorations.underline=t,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,t))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e=>{if(!this._currentLink)return;const t=0===e.start?0:e.start+1+this._bufferService.buffer.ydisp,i=this._bufferService.buffer.ydisp+1+e.end;if(this._currentLink.link.range.start.y>=t&&this._currentLink.link.range.end.y<=i&&(this._clearCurrentLink(t,i),this._lastMouseEvent)){const e=this._positionFromMouseEvent(this._lastMouseEvent,this._element);e&&this._askForLink(e,!1)}})))}_linkHover(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(i,t.text)}_fireUnderlineEvent(e,t){const i=e.range,s=this._bufferService.buffer.ydisp,r=this._createLinkUnderlineEvent(i.start.x-1,i.start.y-s-1,i.end.x,i.end.y-s-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(r)}_linkLeave(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(i,t.text)}_linkAtPosition(e,t){const i=e.range.start.y*this._bufferService.cols+e.range.start.x,s=e.range.end.y*this._bufferService.cols+e.range.end.x,r=t.y*this._bufferService.cols+t.x;return i<=r&&r<=s}_positionFromMouseEvent(e,t){const i=this._mouseCoordsService.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(i)return{x:i[0],y:i[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,i,s,r){return{x1:e,y1:t,x2:i,y2:s,cols:this._bufferService.cols,fg:r}}};t.Linkifier=c,t.Linkifier=c=s([r(1,a.IMouseCoordsService),r(2,a.IRenderService),r(3,n.IBufferService),r(4,a.ILinkProviderService)],c)},7721(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.tooMuchOutput=t.promptLabel=void 0;let i="Terminal input";const s={get:()=>i,set:e=>i=e};t.promptLabel=s;let r="Too much output to announce, navigate to rows manually to read";const o={get:()=>r,set:e=>r=e};t.tooMuchOutput=o},3285(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OscLinkProvider=void 0;const o=i(3055),n=i(6501);let a=class{constructor(e,t,i){this._bufferService=e,this._optionsService=t,this._oscLinkService=i,this._workCell=new o.CellData}provideLinks(e,t){const i=this._bufferService.buffer.lines.get(e-1);if(!i)return void t(void 0);const s=[],r=this._optionsService.rawOptions.linkHandler,o=this._workCell,n=i.getTrimmedLength();let a=-1,l=-1,c=!1;for(let t=0;tr?r.activate(e,t,d):h(0,t),hover:(e,t)=>r?.hover?.(e,t,d),leave:(e,t)=>r?.leave?.(e,t,d)})}c=!1,o.hasExtendedAttrs()&&o.extended.urlId?(l=t,a=o.extended.urlId):(l=-1,a=-1)}}t(s)}_getRangeWithLineWrap(e,t,i,s){let r=e,o=t,n=e,a=i;for(;0===o;){const e=this._bufferService.buffer.lines.get(r-1);if(!e?.isWrapped)break;const t=this._bufferService.buffer.lines.get(r-2);if(!t)break;const i=t.getTrimmedLength();if(0===i||!this._hasUrlId(t,i-1,s))break;let n=i-1;for(;n>0&&this._hasUrlId(t,n-1,s);)n--;r--,o=n}for(;;){const e=this._bufferService.buffer.lines.get(n-1);if(!e)break;if(a!==e.getTrimmedLength())break;const t=this._bufferService.buffer.lines.get(n);if(!t?.isWrapped)break;const i=t.getTrimmedLength();if(0===i||!this._hasUrlId(t,0,s))break;let r=1;for(;rthis._innerRefresh()),this._animationFrame}refresh(e,t,i){this._rowCount=i,e=e??0,t=t??this._rowCount-1,this._rowStart=void 0!==this._rowStart?Math.min(this._rowStart,e):e,this._rowEnd=void 0!==this._rowEnd?Math.max(this._rowEnd,t):t,void 0===this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._animationFrame=void 0,void 0===this._rowStart||void 0===this._rowEnd||void 0===this._rowCount)return void this._runRefreshCallbacks();const e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(const e of this._refreshCallbacks)e(0);this._refreshCallbacks=[]}}},4292(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.TimeBasedDebouncer=void 0,t.TimeBasedDebouncer=class{constructor(e,t=1e3){this._renderCallback=e,this._debounceThresholdMS=t,this._lastRefreshMs=0,this._additionalRefreshRequested=!1}dispose(){this._refreshTimeoutID&&(clearTimeout(this._refreshTimeoutID),this._refreshTimeoutID=void 0),this._additionalRefreshRequested=!1}refresh(e,t,i){this._rowCount=i,e=e??0,t=t??this._rowCount-1,this._rowStart=void 0!==this._rowStart?Math.min(this._rowStart,e):e,this._rowEnd=void 0!==this._rowEnd?Math.max(this._rowEnd,t):t;const s=performance.now();if(s-this._lastRefreshMs>=this._debounceThresholdMS)void 0!==this._refreshTimeoutID&&(clearTimeout(this._refreshTimeoutID),this._refreshTimeoutID=void 0,this._additionalRefreshRequested=!1),this._lastRefreshMs=s,this._innerRefresh();else if(!this._additionalRefreshRequested){const e=s-this._lastRefreshMs,t=this._debounceThresholdMS-e;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout(()=>{this._lastRefreshMs=performance.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0},t)}}_innerRefresh(){if(void 0===this._rowStart||void 0===this._rowEnd||void 0===this._rowCount)return;const e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t)}}},9302(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DEFAULT_ANSI_COLORS=void 0;const s=i(4103);t.DEFAULT_ANSI_COLORS=Object.freeze((()=>{const e=[s.css.toColor("#2e3436"),s.css.toColor("#cc0000"),s.css.toColor("#4e9a06"),s.css.toColor("#c4a000"),s.css.toColor("#3465a4"),s.css.toColor("#75507b"),s.css.toColor("#06989a"),s.css.toColor("#d3d7cf"),s.css.toColor("#555753"),s.css.toColor("#ef2929"),s.css.toColor("#8ae234"),s.css.toColor("#fce94f"),s.css.toColor("#729fcf"),s.css.toColor("#ad7fa8"),s.css.toColor("#34e2e2"),s.css.toColor("#eeeeec")],t=[0,95,135,175,215,255];for(let i=0;i<216;i++){const r=t[i/36%6|0],o=t[i/6%6|0],n=t[i%6];e.push({css:s.channels.toCss(r,o,n),rgba:s.channels.toRgba(r,o,n)})}for(let t=0;t<24;t++){const i=8+10*t;e.push({css:s.channels.toCss(i,i,i),rgba:s.channels.toRgba(i,i,i)})}return e})())},4017(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Viewport=void 0;const o=i(7098),n=i(4812),a=i(6501),h=i(4159),l=i(8566),c=i(8636),d=i(7880);let _=class extends n.Disposable{constructor(e,t,i,s,r,o,a,_,u){super(),this._bufferService=i,this._coreService=r,this._optionsService=_,this._renderService=u,this._onRequestScrollLines=this._register(new c.Emitter),this.onRequestScrollLines=this._onRequestScrollLines.event,this._isSyncing=!1,this._isHandlingScroll=!1,this._suppressOnScrollHandler=!1,this._needsSyncOnRender=!1;const f=this._register(new d.Scrollable({forceIntegerValues:!1,smoothScrollDuration:this._optionsService.rawOptions.smoothScrollDuration,scheduleAtNextAnimationFrame:e=>(0,h.scheduleAtNextAnimationFrame)(s.window,e)}));this._register(this._optionsService.onSpecificOptionChange("smoothScrollDuration",()=>{f.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration)})),this._scrollableElement=this._register(new l.SmoothScrollableElement(t,{vertical:1,horizontal:2,useShadows:!1,mouseWheelSmoothScroll:!0,verticalHasArrows:this._optionsService.rawOptions.scrollbar?.showArrows??!1,...this._getChangeOptions()},f)),this._register(this._optionsService.onMultipleOptionChange(["scrollSensitivity","fastScrollSensitivity","scrollbar"],()=>this._scrollableElement.updateOptions(this._getChangeOptions()))),this._register(o.onProtocolChange(e=>{this._scrollableElement.updateOptions({handleMouseWheel:!(16&e)})})),this._scrollableElement.setScrollDimensions({height:0,scrollHeight:0}),this._register(c.EventUtils.runAndSubscribe(a.onChangeColors,()=>{e.style.backgroundColor=a.colors.background.css,this._scrollableElement.getDomNode().style.backgroundColor=a.colors.background.css})),e.appendChild(this._scrollableElement.getDomNode()),this._register((0,n.toDisposable)(()=>this._scrollableElement.getDomNode().remove())),this._styleElement=s.mainDocument.createElement("style"),t.appendChild(this._styleElement),this._register((0,n.toDisposable)(()=>this._styleElement.remove())),this._register(c.EventUtils.runAndSubscribe(a.onChangeColors,()=>{this._styleElement.textContent=[".xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider {",` background: ${a.colors.scrollbarSliderBackground.css};`,"}",".xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider:hover {",` background: ${a.colors.scrollbarSliderHoverBackground.css};`,"}",".xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider.xterm-active {",` background: ${a.colors.scrollbarSliderActiveBackground.css};`,"}"].join("\n")})),this._register(this._bufferService.onResize(()=>this.queueSync())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._latestYDisp=void 0,this.queueSync()})),this._register(this._bufferService.onScroll(()=>this._sync())),this._register(this._renderService.onRender(()=>{this._needsSyncOnRender&&(this._needsSyncOnRender=!1,this._sync())})),this._register(this._scrollableElement.onScroll(e=>this._handleScroll(e)))}scrollLines(e){const t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:t.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,t){t&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!t,scrollTop:e*this._renderService.dimensions.css.cell.height})}_getChangeOptions(){const e=this._optionsService.rawOptions.scrollbar?.showScrollbar??!0,t=this._optionsService.rawOptions.scrollbar?.showArrows??!1,i=e?this._optionsService.rawOptions.scrollbar?.width??14:0;return{mouseWheelScrollSensitivity:this._optionsService.rawOptions.scrollSensitivity,fastScrollSensitivity:this._optionsService.rawOptions.fastScrollSensitivity,vertical:e?1:2,verticalScrollbarSize:i,verticalHasArrows:t}}queueSync(e){void 0!==e&&(this._latestYDisp=e),void 0===this._queuedAnimationFrame&&(this._queuedAnimationFrame=this._renderService.addRefreshCallback(()=>{this._queuedAnimationFrame=void 0,this._sync(this._latestYDisp)}))}_sync(e=this._bufferService.buffer.ydisp){this._renderService&&!this._isSyncing&&(this._coreService.decPrivateModes.synchronizedOutput?this._needsSyncOnRender=!0:(this._isSyncing=!0,this._suppressOnScrollHandler=!0,this._scrollableElement.setScrollDimensions({height:this._renderService.dimensions.css.canvas.height,scrollHeight:this._renderService.dimensions.css.cell.height*this._bufferService.buffer.lines.length}),this._suppressOnScrollHandler=!1,e!==this._latestYDisp&&this._scrollableElement.setScrollPosition({scrollTop:e*this._renderService.dimensions.css.cell.height}),this._isSyncing=!1))}_handleScroll(e){if(!this._renderService)return;if(this._isHandlingScroll||this._suppressOnScrollHandler)return;this._isHandlingScroll=!0;const t=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),i=t-this._bufferService.buffer.ydisp;0!==i&&(this._latestYDisp=t,this._onRequestScrollLines.fire(i)),this._isHandlingScroll=!1}handleTouchScroll(e){const t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({scrollTop:t.scrollTop-e})}};t.Viewport=_,t.Viewport=_=s([r(2,a.IBufferService),r(3,o.ICoreBrowserService),r(4,a.ICoreService),r(5,a.IMouseStateService),r(6,o.IThemeService),r(7,a.IOptionsService),r(8,o.IRenderService)],_)},4196(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.BufferDecorationRenderer=void 0;const o=i(7098),n=i(4812),a=i(6501);let h=class extends n.Disposable{constructor(e,t,i,s,r){super(),this._screenElement=e,this._bufferService=t,this._coreBrowserService=i,this._decorationService=s,this._renderService=r,this._decorationElements=new Map,this._altBufferIsActive=!1,this._dimensionsChanged=!1,this._container=document.createElement("div"),this._container.classList.add("xterm-decoration-container"),this._screenElement.appendChild(this._container),this._register(this._renderService.onRenderedViewportChange(()=>this._doRefreshDecorations())),this._register(this._renderService.onDimensionsChange(()=>{this._dimensionsChanged=!0,this._queueRefresh()})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt})),this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh())),this._register(this._decorationService.onDecorationRemoved(e=>this._removeDecoration(e))),this._register((0,n.toDisposable)(()=>{this._container.remove(),this._decorationElements.clear()}))}_queueRefresh(){void 0===this._animationFrame&&(this._animationFrame=this._renderService.addRefreshCallback(()=>{this._doRefreshDecorations(),this._animationFrame=void 0}))}_doRefreshDecorations(){for(const e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){const t=this._coreBrowserService.mainDocument.createElement("div");t.classList.add("xterm-decoration"),t.classList.toggle("xterm-decoration-top-layer","top"===e?.options?.layer),t.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,t.style.height=(e.options.height||1)*this._renderService.dimensions.css.cell.height+"px",t.style.top=(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height+"px",t.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;const i=e.options.x??0;return i&&i>this._bufferService.cols&&(t.style.display="none"),this._refreshXPosition(e,t),t}_refreshStyle(e){const t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display="none",e.onRenderEmitter.fire(e.element));else{let i=this._decorationElements.get(e);i||(i=this._createElement(e),e.element=i,this._decorationElements.set(e,i),this._container.appendChild(i),e.onDispose(()=>{this._decorationElements.delete(e),i.remove()})),i.style.display=this._altBufferIsActive?"none":"block",this._altBufferIsActive||(i.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,i.style.height=(e.options.height||1)*this._renderService.dimensions.css.cell.height+"px",i.style.top=t*this._renderService.dimensions.css.cell.height+"px",i.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(i)}}_refreshXPosition(e,t=e.element){if(!t)return;const i=e.options.x??0;"right"===(e.options.anchor||"left")?t.style.right=i?i*this._renderService.dimensions.css.cell.width+"px":"":t.style.left=i?i*this._renderService.dimensions.css.cell.width+"px":""}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};t.BufferDecorationRenderer=h,t.BufferDecorationRenderer=h=s([r(1,a.IBufferService),r(2,o.ICoreBrowserService),r(3,a.IDecorationService),r(4,o.IRenderService)],h)},957(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ColorZoneStore=void 0,t.ColorZoneStore=class{constructor(){this._zones=[],this._zonePool=[],this._zonePoolIndex=0,this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(e){if(e.options.overviewRulerOptions){for(const t of this._zones)if(t.color===e.options.overviewRulerOptions.color&&t.position===e.options.overviewRulerOptions.position){if(this._lineIntersectsZone(t,e.marker.line))return;if(this._lineAdjacentToZone(t,e.marker.line,e.options.overviewRulerOptions.position))return void this._addLineToZone(t,e.marker.line)}if(this._zonePoolIndex=e.startBufferLine&&t<=e.endBufferLine}_lineAdjacentToZone(e,t,i){return t>=e.startBufferLine-this._linePadding[i||"full"]&&t<=e.endBufferLine+this._linePadding[i||"full"]}_addLineToZone(e,t){e.startBufferLine=Math.min(e.startBufferLine,t),e.endBufferLine=Math.max(e.endBufferLine,t)}}},9925(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OverviewRulerRenderer=void 0;const o=i(957),n=i(7098),a=i(4812),h=i(6501),l={full:0,left:0,center:0,right:0},c={full:0,left:0,center:0,right:0},d={full:0,left:0,center:0,right:0};let _=class extends a.Disposable{get _width(){const e=this._optionsService.rawOptions.scrollbar;return e?.showScrollbar??1?e?.width??0:0}constructor(e,t,i,s,r,n,h,l){super(),this._viewportElement=e,this._screenElement=t,this._bufferService=i,this._decorationService=s,this._renderService=r,this._optionsService=n,this._themeService=h,this._coreBrowserService=l,this._colorZoneStore=new o.ColorZoneStore,this._shouldUpdateDimensions=!0,this._shouldUpdateAnchor=!0,this._lastKnownBufferLength=0,this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add("xterm-decoration-overview-ruler"),this._refreshCanvasDimensions(),this._viewportElement.parentElement?.insertBefore(this._canvas,this._viewportElement),this._register((0,a.toDisposable)(()=>this._canvas?.remove()));const c=this._canvas.getContext("2d");if(!c)throw new Error("Ctx cannot be null");this._ctx=c,this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh(void 0,!0))),this._register(this._decorationService.onDecorationRemoved(()=>this._queueRefresh(void 0,!0))),this._register(this._renderService.onRenderedViewportChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?"none":"block"})),this._register(this._bufferService.onScroll(()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())})),this._register(this._renderService.onDimensionsChange(()=>this._queueRefresh(!0))),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh(!0))),this._register(this._optionsService.onSpecificOptionChange("scrollbar",()=>this._queueRefresh(!0))),this._register(this._themeService.onChangeColors(()=>this._queueRefresh())),this._register((0,a.toDisposable)(()=>{void 0!==this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)})),this._queueRefresh(!0)}_refreshDrawConstants(){const e=Math.floor((this._canvas.width-1)/3),t=Math.ceil((this._canvas.width-1)/3);c.full=this._canvas.width,c.left=e,c.center=t,c.right=e,this._refreshDrawHeightConstants(),d.full=1,d.left=1,d.center=1+c.left,d.right=1+c.left+c.center}_refreshDrawHeightConstants(){l.full=Math.round(2*this._coreBrowserService.dpr);const e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);l.left=t,l.center=t,l.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){if(this._store.isDisposed||!this._renderService.hasRenderer())return;const e=this._renderService.dimensions.css.canvas.height,t=this._renderService.dimensions.device.canvas.height;this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${e}px`,this._canvas.height=t,this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){if(this._store.isDisposed||!this._renderService.hasRenderer())return;this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(const e of this._decorationService.decorations)this._colorZoneStore.addDecoration(e);this._ctx.lineWidth=1,this._renderRulerOutline();const e=this._colorZoneStore.zones;for(const t of e)"full"!==t.position&&this._renderColorZone(t);for(const t of e)"full"===t.position&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderRulerOutline(){this._ctx.fillStyle=this._themeService.colors.overviewRulerBorder.css,this._ctx.fillRect(0,0,1,this._canvas.height),this._optionsService.rawOptions.scrollbar?.overviewRuler?.showTopBorder&&this._ctx.fillRect(1,0,this._canvas.width-1,1),this._optionsService.rawOptions.scrollbar?.overviewRuler?.showBottomBorder&&this._ctx.fillRect(1,this._canvas.height-1,this._canvas.width-1,this._canvas.height)}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect(d[e.position||"full"],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-l[e.position||"full"]/2),c[e.position||"full"],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+l[e.position||"full"]))}_queueRefresh(e,t){this._store.isDisposed||(this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,void 0===this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._store.isDisposed||this._refreshDecorations(),this._animationFrame=void 0})))}};t.OverviewRulerRenderer=_,t.OverviewRulerRenderer=_=s([r(2,h.IBufferService),r(3,h.IDecorationService),r(4,n.IRenderService),r(5,h.IOptionsService),r(6,n.IThemeService),r(7,n.ICoreBrowserService)],_)},3618(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CompositionHelper=void 0;const o=i(7098),n=i(6501);let a=class{get isComposing(){return this._isComposing}constructor(e,t,i,s,r,o){this._textarea=e,this._compositionView=t,this._bufferService=i,this._optionsService=s,this._coreService=r,this._renderService=o,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._compositionSuffix="",this._dataAlreadySent="",this._pendingKeypressData=""}compositionstart(){this._isComposing=!0;const e=this._textarea.selectionStart??this._textarea.value.length,t=this._textarea.selectionEnd??e;this._compositionPosition.start=Math.min(e,t),this._compositionPosition.end=Math.max(e,t),this._compositionSuffix=this._textarea.value.substring(this._compositionPosition.end),this._compositionView.textContent="",this._dataAlreadySent="",this._compositionView.classList.add("active")}compositionupdate(e){this._compositionView.textContent=`‎${e.data}‎`,this.updateCompositionElements(),setTimeout(()=>{const e=this._textarea.selectionEnd??this._textarea.value.length;this._compositionPosition.end=Math.max(this._compositionPosition.start,e)},0)}compositionend(){this._finalizeComposition(!0)}keydown(e){if(this._isComposing||this._isSendingComposition){if(20===e.keyCode||229===e.keyCode)return!1;if(16===e.keyCode||17===e.keyCode||18===e.keyCode)return!1;this._finalizeComposition(!1)}return 229!==e.keyCode||(this._handleAnyTextareaChanges(),!1)}keypress(e){return!!this._isSendingComposition&&(this._pendingKeypressData+=e,!0)}_finalizeComposition(e){if(this._compositionView.classList.remove("active"),this._isComposing=!1,e){const e={start:this._compositionPosition.start,end:this._compositionPosition.end},t=this._compositionSuffix;this._pendingKeypressData="",this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){let i;if(this._isSendingComposition=!1,e.start+=this._dataAlreadySent.length,this._isComposing)i=this._textarea.value.substring(e.start,this._compositionPosition.start);else{const s=this._textarea.value,r=t.length>0&&s.endsWith(t)?s.length-t.length:s.length;i=s.substring(e.start,Math.max(e.start,r))}this._sendCompositionInput(i)}},0)}else{this._isSendingComposition=!1;const e=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._sendCompositionInput(e)}}_sendCompositionInput(e){const t=this._pendingKeypressData;if(!e.includes(t))if(t.includes(e))e=t;else{let i=Math.min(e.length,t.length);for(;i>0&&!e.endsWith(t.substring(0,i));)i--;let s=Math.min(e.length,t.length);for(;s>0&&!t.endsWith(e.substring(0,s));)s--;e=i>s?e+t.substring(i):t+e.substring(s)}this._pendingKeypressData="",e.length>0&&this._coreService.triggerDataEvent(e,!0)}_handleAnyTextareaChanges(){if(this._textareaChangeTimer)return;const e=this._textarea.value;this._textareaChangeTimer=window.setTimeout(()=>{if(this._textareaChangeTimer=void 0,!this._isComposing){const t=this._textarea.value,i=t.replace(e,"");this._dataAlreadySent=i,t.length>e.length?this._coreService.triggerDataEvent(i,!0):t.lengththis.updateCompositionElements(!0),0)}}};t.CompositionHelper=a,t.CompositionHelper=a=s([r(2,n.IBufferService),r(3,n.IOptionsService),r(4,n.ICoreService),r(5,o.IRenderService)],a)},5251(e,t){function i(e,t,i){const s=i.getBoundingClientRect(),r=e.getComputedStyle(i),o=parseInt(r.getPropertyValue("padding-left"),10),n=parseInt(r.getPropertyValue("padding-top"),10);return[t.clientX-s.left-o,t.clientY-s.top-n]}Object.defineProperty(t,"__esModule",{value:!0}),t.getCoordsRelativeToElement=i,t.getCoords=function(e,t,s,r,o,n,a,h,l){if(!n)return;const c=i(e,t,s);return c[0]=Math.ceil((c[0]+(l?a/2:0))/a),c[1]=Math.ceil(c[1]/h),c[0]=Math.min(Math.max(c[0],1),r+(l?1:0)),c[1]=Math.min(Math.max(c[1],1),o),c}},9686(e,t){function i(e,t,i,o){const h=e-s(e,i),l=t-s(t,i),c=Math.abs(h-l)-function(e,t,i){let o=0;const n=e-s(e,i),a=t-s(t,i);for(let s=0;s=0&&et?"A":"B"}function o(e,t,i,s,r,o){let n=e,a=t,h="";for(;(n!==i||a!==s)&&a>=0&&ao.cols-1?(h+=o.buffer.translateBufferLineToString(a,!1,e,n),n=0,e=0,a++):!r&&n<0&&(h+=o.buffer.translateBufferLineToString(a,!1,0,e+1),n=o.cols-1,e=n,a--);return h+o.buffer.translateBufferLineToString(a,!1,e,n)}function n(e,t){return""+(t?"O":"[")+e}function a(e,t){e=Math.floor(e);let i="";for(let s=0;s0?h-s(h,l):t;const _=h,u=function(e,t,r,o,n,a){let h;return h=i(t,o,n,a).length>0?o-s(o,n):t,e=r&&he?"D":"C",a(Math.abs(l-e),n(d,h));d=c>t?"D":"C";const _=Math.abs(c-t);return a(function(e,t){return t.cols-e}(c>t?e:l,r)+(_-1)*r.cols+1+((c>t?l:e)-1),n(d,h))}},6081(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._core.options[e],i=(e,t)=>{this._checkReadonlyOptions(e),this._core.options[e]=t};for(const e in this._core.options){const s={get:t.bind(this,e),set:i.bind(this,e)};Object.defineProperty(this._publicOptions,e,s)}}_checkReadonlyOptions(e){if(f.includes(e))throw new Error(`Option "${e}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get onDimensionsChange(){return this._core.onDimensionsChange}get element(){return this._core.element}get screenElement(){return this._core.screenElement}get parser(){return this._parser??=new _.ParserApi(this._core)}get unicode(){return this._checkProposedApi(),new u.UnicodeApi(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer??=this._register(new d.BufferNamespaceApi(this._core))}get markers(){return this._core.markers}get modes(){const e=this._core.coreService.decPrivateModes;let t="none";switch(this._core.mouseStateService.activeProtocol){case"X10":t="x10";break;case"VT200":t="vt200";break;case"DRAG":t="drag";break;case"ANY":t="any"}return{applicationCursorKeysMode:e.applicationCursorKeys,applicationKeypadMode:e.applicationKeypad,bracketedPasteMode:e.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:t,originMode:e.origin,reverseWraparoundMode:e.reverseWraparound,sendFocusMode:e.sendFocus,showCursor:!this._core.coreService.isCursorHidden,synchronizedOutputMode:e.synchronizedOutput,win32InputMode:e.win32InputMode,wraparoundMode:e.wraparound}}get dimensions(){return this._core.dimensions}get options(){return this._publicOptions}set options(e){for(const t in e)this._publicOptions[t]=e[t]}blur(){this._core.blur()}focus(){this._core.focus()}input(e,t=!0){this._core.input(e,t)}resize(e,t){this._verifyIntegers(e,t),this._core.resize(e,t)}open(e){this._core.open(e)}attachCustomKeyEventHandler(e){this._core.attachCustomKeyEventHandler(e)}attachCustomWheelEventHandler(e){this._core.attachCustomWheelEventHandler(e)}registerLinkProvider(e){return this._core.registerLinkProvider(e)}registerCharacterJoiner(e){return this._core.registerCharacterJoiner(e)}deregisterCharacterJoiner(e){this._core.deregisterCharacterJoiner(e)}registerMarker(e=0){return this._verifyIntegers(e),this._core.registerMarker(e)}registerDecoration(e){return this._verifyPositiveIntegers(e.x??0,e.width??0,e.height??0),this._core.registerDecoration(e)}hasSelection(){return this._core.hasSelection()}select(e,t,i){this._verifyIntegers(e,t,i),this._core.select(e,t,i)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(e,t){this._verifyIntegers(e,t),this._core.selectLines(e,t)}dispose(){super.dispose()}scrollLines(e){this._verifyIntegers(e),this._core.scrollLines(e)}scrollPages(e){this._verifyIntegers(e),this._core.scrollPages(e)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(e){this._verifyIntegers(e),this._core.scrollToLine(e)}clear(){this._core.clear()}write(e,t){this._core.write(e,t)}writeln(e,t){this._core.write(e),this._core.write("\r\n",t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){this._addonManager.loadAddon(this,e)}static get strings(){return{get promptLabel(){return a.promptLabel.get()},set promptLabel(e){a.promptLabel.set(e)},get tooMuchOutput(){return a.tooMuchOutput.get()},set tooMuchOutput(e){a.tooMuchOutput.set(e)}}}_verifyIntegers(...e){for(p of e)if(p===1/0||isNaN(p)||p%1!=0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...e){for(p of e)if(p&&(p===1/0||isNaN(p)||p%1!=0||p<0))throw new Error("This API only accepts positive integers")}}t.Terminal=v},3955(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DomRenderer=void 0;const o=i(1433),n=i(2744),a=i(9176),h=i(6181),l=i(2274),c=i(654),d=i(7098),_=i(4103),u=i(4812),f=i(6501),p=i(8636),v=i(4159);let g=1,m=class extends u.Disposable{constructor(e,t,i,s,r,a,d,_,f,m,b,w,y,C){super(),this._terminal=e,this._document=t,this._element=i,this._screenElement=s,this._viewportElement=r,this._helperContainer=a,this._linkifier2=d,this._charSizeService=f,this._optionsService=m,this._bufferService=b,this._coreService=w,this._coreBrowserService=y,this._themeService=C,this._terminalClass=g++,this._rowElements=[],this._selectionRenderModel=(0,l.createSelectionRenderModel)(),this._lastSelectionColumnMode=!1,this._rowHasBlinkingCells=[],this._rowHasBlinkingCellsCount=0,this._onRequestRedraw=this._register(new p.Emitter),this.onRequestRedraw=this._onRequestRedraw.event,this._rowContainer=this._document.createElement("div"),this._rowContainer.classList.add("xterm-rows"),this._rowContainer.style.lineHeight="normal",this._rowContainer.setAttribute("aria-hidden","true"),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement("div"),this._selectionContainer.classList.add("xterm-selection"),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=(0,h.createRenderDimensions)(),this._updateDimensions(),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._themeService.onChangeColors(e=>this._injectCss(e))),this._injectCss(this._themeService.colors),this._rowFactory=_.createInstance(o.DomRendererRowFactory,document),this._element.classList.add("xterm-dom-renderer-owner-"+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline(e=>this._handleLinkHover(e))),this._register(this._linkifier2.onHideLinkUnderline(e=>this._handleLinkLeave(e))),this._cursorBlinkStateManager=new S(this._rowContainer,this._coreBrowserService),this._register((0,v.addDisposableListener)(this._document,"mousedown",()=>this._cursorBlinkStateManager.restartBlinkAnimation())),this._register((0,u.toDisposable)(()=>this._cursorBlinkStateManager.dispose())),this._textBlinkStateManager=this._register(new c.TextBlinkStateManager(()=>this._onRequestRedraw.fire({start:0,end:this._bufferService.rows-1}),this._coreBrowserService,this._optionsService)),this._register((0,u.toDisposable)(()=>{this._element.classList.remove("xterm-dom-renderer-owner-"+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new n.WidthCache,this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){const e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(const e of this._rowElements)e.style.width=`${this.dimensions.css.canvas.width}px`,e.style.height=`${this.dimensions.css.cell.height}px`,e.style.lineHeight=`${this.dimensions.css.cell.height}px`,e.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));const t=`${this._terminalSelector} .xterm-rows span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .xterm-rows { pointer-events: none; color: ${e.foreground.css};}`;t+=`${this._terminalSelector} .xterm-rows, ${this._terminalSelector} .xterm-rows span { font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`,t+=`${this._terminalSelector} .xterm-rows .xterm-dim { color: ${_.color.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}${this._terminalSelector} span.xterm-blink-hidden { visibility: hidden;}`;const i=`blink_underline_${this._terminalClass}`,s=`blink_bar_${this._terminalClass}`,r=`blink_block_${this._terminalClass}`;t+=`@keyframes ${i} { 50% { border-bottom-style: hidden; }}`,t+=`@keyframes ${s} { 50% { box-shadow: none; }}`,t+=`@keyframes ${r} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${i} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${s} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-cursor-blink-idle .xterm-cursor.xterm-cursor-blink { animation: none !important;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .xterm-selection { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .xterm-selection div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .xterm-selection div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(const[i,s]of e.ansi.entries())t+=`${this._terminalSelector} .xterm-fg-${i} { color: ${s.css}; }${this._terminalSelector} .xterm-fg-${i}.xterm-dim { color: ${_.color.multiplyOpacity(s,.5).css}; }${this._terminalSelector} .xterm-bg-${i} { background-color: ${s.css}; }`;t+=`${this._terminalSelector} .xterm-fg-${a.INVERTED_DEFAULT_COLOR} { color: ${_.color.opaque(e.background).css}; }${this._terminalSelector} .xterm-fg-${a.INVERTED_DEFAULT_COLOR}.xterm-dim { color: ${_.color.multiplyOpacity(_.color.opaque(e.background),.5).css}; }${this._terminalSelector} .xterm-bg-${a.INVERTED_DEFAULT_COLOR} { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){const e=this.dimensions.css.cell.width-this._widthCache.get("W",!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let e=this._rowElements.length;e<=t;e++){const e=this._document.createElement("div");this._rowContainer.appendChild(e),this._rowElements.push(e),this._rowHasBlinkingCells.push(!1)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop()),this._rowHasBlinkingCells.pop()&&this._rowHasBlinkingCellsCount--}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove("xterm-focus"),this._cursorBlinkStateManager.pause(),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add("xterm-focus"),this._cursorBlinkStateManager.resume(),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleViewportVisibilityChange(e){this._textBlinkStateManager.setViewportVisible(e)}handleSelectionChanged(e,t,i){const s=this._bufferService.rows;this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,i);let r=0,o=-1;this._lastSelectionStart&&this._lastSelectionEnd&&(this._selectionRenderModel.update(this._terminal,this._lastSelectionStart,this._lastSelectionEnd,this._lastSelectionColumnMode),this._selectionRenderModel.hasSelection&&(r=this._selectionRenderModel.viewportCappedStartRow,o=this._selectionRenderModel.viewportCappedEndRow));let n=0,a=-1;if(!e||!t)return;if(this._selectionRenderModel.update(this._terminal,e,t,i),this._selectionRenderModel.hasSelection){const s=this._selectionRenderModel.viewportStartRow,r=this._selectionRenderModel.viewportEndRow,o=this._selectionRenderModel.viewportCappedStartRow,h=this._selectionRenderModel.viewportCappedEndRow;n=o,a=h;const l=this._document.createDocumentFragment();if(i){const i=e[0]>t[0];l.appendChild(this._createSelectionElement(o,i?t[0]:e[0],i?e[0]:t[0],h-o+1))}else{const i=s===o?e[0]:0,n=o===r?t[0]:this._bufferService.cols;l.appendChild(this._createSelectionElement(o,i,n));const a=h-o-1;if(l.appendChild(this._createSelectionElement(o+1,0,this._bufferService.cols,a)),o!==h){const e=r===h?t[0]:this._bufferService.cols;l.appendChild(this._createSelectionElement(h,0,e))}}this._selectionContainer.appendChild(l)}let h=Math.min(r,n),l=Math.max(o,a);if(l>=0){h=Math.max(h,0),l=Math.min(l,s-1);const e=this._bufferService.buffer.y;this._selectionRenderModel.hasSelection&&e>=0&&ethis.dimensions.css.canvas.width&&(n=this.dimensions.css.canvas.width-o),r.style.height=s*this.dimensions.css.cell.height+"px",r.style.top=e*this.dimensions.css.cell.height+"px",r.style.left=`${o}px`,r.style.width=`${n}px`,r}handleCursorMove(){this._cursorBlinkStateManager.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(const e of this._rowElements)e.replaceChildren();this._rowHasBlinkingCellsCount>0&&(this._rowHasBlinkingCells.fill(!1),this._rowHasBlinkingCellsCount=0,this._textBlinkStateManager.setNeedsBlinkInViewport(!1))}renderRows(e,t){const i=this._bufferService.buffer,s=i.ybase+i.y,r=Math.min(i.x,this._bufferService.cols-1),o=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,n=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,a=this._optionsService.rawOptions.cursorInactiveStyle,h={hasBlinkingCells:!1};for(let l=e;l<=t;l++){const e=l+i.ydisp,t=this._rowElements[l];if(!t)continue;const c=i.lines.get(e);c?(t.replaceChildren(...this._rowFactory.createRow(c,e,e===s,n,a,r,o,this._textBlinkStateManager.isBlinkOn,this.dimensions.css.cell.width,this._widthCache,-1,-1,h)),this._setRowBlinkState(l,h.hasBlinkingCells)):(t.replaceChildren(),this._setRowBlinkState(l,!1))}this._updateTextBlinkState()}get _terminalSelector(){return`.xterm-dom-renderer-owner-${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,i,s,r,o){i<0&&(e=0),s<0&&(t=0);const n=this._bufferService.rows-1;i=Math.max(Math.min(i,n),0),s=Math.max(Math.min(s,n),0),r=Math.min(r,this._bufferService.cols);const a=this._bufferService.buffer,h=a.ybase+a.y,l=Math.min(a.x,r-1),c=this._optionsService.rawOptions.cursorBlink,d=this._optionsService.rawOptions.cursorStyle,_=this._optionsService.rawOptions.cursorInactiveStyle,u={hasBlinkingCells:!1};for(let n=i;n<=s;++n){const f=n+a.ydisp,p=this._rowElements[n];if(!p)continue;const v=a.lines.get(f);v?(p.replaceChildren(...this._rowFactory.createRow(v,f,f===h,d,_,l,c,this._textBlinkStateManager.isBlinkOn,this.dimensions.css.cell.width,this._widthCache,o?n===i?e:0:-1,o?(n===s?t:r)-1:-1,u)),this._setRowBlinkState(n,u.hasBlinkingCells)):(p.replaceChildren(),this._setRowBlinkState(n,!1))}this._updateTextBlinkState()}_setRowBlinkState(e,t){this._rowHasBlinkingCells[e]!==t&&(this._rowHasBlinkingCells[e]=t,this._rowHasBlinkingCellsCount+=t?1:-1)}_updateTextBlinkState(){this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount>0)}};t.DomRenderer=m,t.DomRenderer=m=s([r(7,f.IInstantiationService),r(8,d.ICharSizeService),r(9,f.IOptionsService),r(10,f.IBufferService),r(11,f.ICoreService),r(12,d.ICoreBrowserService),r(13,d.IThemeService)],m);class S{constructor(e,t){this._rowContainer=e,this._coreBrowserService=t,this._isIdlePaused=!1,this._coreBrowserService.isFocused&&this._resetIdleTimer()}dispose(){this._clearIdleTimer()}restartBlinkAnimation(){this._isIdlePaused&&this._rowContainer.classList.remove("xterm-cursor-blink-idle"),this._resetIdleTimer()}pause(){this._isIdlePaused=!1,this._clearIdleTimer()}resume(){this._isIdlePaused=!1,this._rowContainer.classList.remove("xterm-cursor-blink-idle"),this._resetIdleTimer()}_resetIdleTimer(){this._isIdlePaused=!1,this._clearIdleTimer(),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_clearIdleTimer(){void 0!==this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}_stopBlinkingDueToIdle(){this._rowContainer.classList.add("xterm-cursor-blink-idle"),this._isIdlePaused=!0,this._idleTimeout=void 0}}},1433(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DomRendererRowFactory=void 0;const o=i(9176),n=i(8938),a=i(3055),h=i(6501),l=i(4103),c=i(7098),d=i(945),_=i(6181),u=i(5451);let f=class{constructor(e,t,i,s,r,o,n){this._document=e,this._characterJoinerService=t,this._optionsService=i,this._coreBrowserService=s,this._coreService=r,this._decorationService=o,this._themeService=n,this._workCell=new a.CellData,this._columnSelectMode=!1,this.defaultSpacing=0}handleSelectionChanged(e,t,i){this._selectionStart=e,this._selectionEnd=t,this._columnSelectMode=i}createRow(e,t,i,s,r,a,h,c,_,f,p,v,g){const m=[];g&&(g.hasBlinkingCells=!1);const S=this._characterJoinerService.getJoinedCharacters(t),b=this._themeService.colors;let w,y=e.getNoBgTrimmedLength();i&&y=A,F=I,W=this._workCell;if(S.length>0&&I===S[0][0]&&N){const s=S.shift(),r=this._isCellInSelection(s[0],t);for(C=s[0]+1;C=s[1],N?(H=!0,W=new d.JoinedCellData(this._workCell,e.translateToString(!0,s[0],s[1]),s[1]-s[0]),F=s[1]-1,y=W.getWidth()):A=s[1]}const z=this._isCellInSelection(I,t),K=i&&I===a,U=O&&I>=p&&I<=v;g&&W.isBlink()&&(g.hasBlinkingCells=!0),!c&&W.isBlink()&&P.push("xterm-blink-hidden");let j=!1;this._decorationService.forEachDecorationAtCell(I,t,void 0,e=>{j=!0});let $=W.getChars()||n.WHITESPACE_CELL_CHAR;if(" "===$&&(W.isUnderline()||W.isOverline())&&($=" "),k=y*_-f.get($,W.isBold(),W.isItalic()),w){if(D&&(z&&T||!z&&!T&&W.bg===L)&&(z&&T&&b.selectionForeground||W.fg===x)&&W.extended.ext===R&&U===M&&k===B&&!K&&!H&&!j&&N){W.isInvisible()?E+=n.WHITESPACE_CELL_CHAR:E+=$,D++;continue}D&&(w.textContent=E),w=this._document.createElement("span"),D=0,E=""}else w=this._document.createElement("span");if(L=W.bg,x=W.fg,R=W.extended.ext,M=U,B=k,T=z,H&&a>=I&&a<=F&&(a=I),!this._coreService.isCursorHidden&&K&&this._coreService.isCursorInitialized)if(P.push("xterm-cursor"),this._coreBrowserService.isFocused)h&&P.push("xterm-cursor-blink"),P.push("bar"===s?"xterm-cursor-bar":"underline"===s?"xterm-cursor-underline":"xterm-cursor-block");else if(r)switch(r){case"outline":P.push("xterm-cursor-outline");break;case"block":P.push("xterm-cursor-block");break;case"bar":P.push("xterm-cursor-bar");break;case"underline":P.push("xterm-cursor-underline")}if(W.isBold()&&P.push("xterm-bold"),W.isItalic()&&P.push("xterm-italic"),W.isDim()&&P.push("xterm-dim"),E=W.isInvisible()?n.WHITESPACE_CELL_CHAR:W.getChars()||n.WHITESPACE_CELL_CHAR,W.isUnderline()&&(P.push(`xterm-underline-${W.extended.underlineStyle}`)," "===E&&(E=" "),!W.isUnderlineColorDefault()))if(W.isUnderlineColorRGB())w.style.textDecorationColor=`rgb(${u.AttributeData.toColorRGB(W.getUnderlineColor()).join(",")})`;else{let e=W.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&W.isBold()&&e<8&&(e+=8),w.style.textDecorationColor=b.ansi[e].css}W.isOverline()&&(P.push("xterm-overline")," "===E&&(E=" ")),W.isStrikethrough()&&P.push("xterm-strikethrough"),U&&(w.style.textDecoration="underline");let q=W.getFgColor(),V=W.getFgColorMode(),X=W.getBgColor(),Y=W.getBgColorMode();const G=!!W.isInverse();if(G){const e=q;q=X,X=e;const t=V;V=Y,Y=t}let J,Z,Q,ee=!1;switch(this._decorationService.forEachDecorationAtCell(I,t,void 0,e=>{"top"!==e.options.layer&&ee||(e.backgroundColorRGB&&(Y=50331648,X=e.backgroundColorRGB.rgba>>8&16777215,J=e.backgroundColorRGB),e.foregroundColorRGB&&(V=50331648,q=e.foregroundColorRGB.rgba>>8&16777215,Z=e.foregroundColorRGB),ee="top"===e.options.layer)}),!ee&&z&&(J=this._coreBrowserService.isFocused?b.selectionBackgroundOpaque:b.selectionInactiveBackgroundOpaque,X=J.rgba>>8&16777215,Y=50331648,ee=!0,b.selectionForeground&&(V=50331648,q=b.selectionForeground.rgba>>8&16777215,Z=b.selectionForeground)),ee&&P.push("xterm-decoration-top"),Y){case 16777216:case 33554432:Q=b.ansi[X],P.push(`xterm-bg-${X}`);break;case 50331648:Q=l.channels.toColor(X>>16,X>>8&255,255&X),this._addStyle(w,`background-color:#${(X>>>0).toString(16).padStart(6,"0")}`);break;default:G?(Q=b.foreground,P.push(`xterm-bg-${o.INVERTED_DEFAULT_COLOR}`)):Q=b.background}switch(J||W.isDim()&&(J=l.color.multiplyOpacity(Q,.5)),V){case 16777216:case 33554432:W.isBold()&&q<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(q+=8),this._applyMinimumContrast(w,Q,b.ansi[q],W,J,void 0)||P.push(`xterm-fg-${q}`);break;case 50331648:const e=l.channels.toColor(q>>16&255,q>>8&255,255&q);this._applyMinimumContrast(w,Q,e,W,J,Z)||this._addStyle(w,`color:#${q.toString(16).padStart(6,"0")}`);break;default:this._applyMinimumContrast(w,Q,b.foreground,W,J,Z)||G&&P.push(`xterm-fg-${o.INVERTED_DEFAULT_COLOR}`)}P.length&&(w.className=P.join(" "),P.length=0),K||H||j||!N?w.textContent=E:D++,k!==this.defaultSpacing&&(w.style.letterSpacing=`${k}px`),m.push(w),I=F}return w&&D&&(w.textContent=E),m}_applyMinimumContrast(e,t,i,s,r,o){if(1===this._optionsService.rawOptions.minimumContrastRatio||(0,_.treatGlyphAsBackgroundColor)(s.getCode()))return!1;const n=this._getContrastCache(s);let a;if(r||o||(a=n.getColor(t.rgba,i.rgba)),void 0===a){const e=this._optionsService.rawOptions.minimumContrastRatio/(s.isDim()?2:1);a=l.color.ensureContrastRatio(r??t,o??i,e),n.setColor((r??t).rgba,(o??i).rgba,a??null)}return!!a&&(this._addStyle(e,`color:${a.css}`),!0)}_getContrastCache(e){return e.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(e,t){e.setAttribute("style",`${e.getAttribute("style")||""}${t};`)}_isCellInSelection(e,t){const i=this._selectionStart,s=this._selectionEnd;return!(!i||!s)&&(this._columnSelectMode?i[0]<=s[0]?e>=i[0]&&t>=i[1]&&e=i[1]&&e>=s[0]&&t<=s[1]:t>i[1]&&t=i[0]&&e=i[0])}};t.DomRendererRowFactory=f,t.DomRendererRowFactory=f=s([r(1,c.ICharacterJoinerService),r(2,h.IOptionsService),r(3,c.ICoreBrowserService),r(4,h.ICoreService),r(5,h.IDecorationService),r(6,c.IThemeService)],f)},2744(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.WidthCache=void 0;const s=i(6181);t.WidthCache=class{constructor(e=()=>new r){this._flat=new Float32Array(256),this._font="",this._fontSize=0,this._weight="normal",this._weightBold="bold",this._canvasElements=[],this._canvasElements=[e(),e(),e(),e()],this.clear()}dispose(){this._canvasElements.length=0,this._holey=void 0}clear(){this._flat.fill(-9999),this._holey=new Map}setFont(e,t,i,s){e===this._font&&t===this._fontSize&&i===this._weight&&s===this._weightBold||(this._font=e,this._fontSize=t,this._weight=i,this._weightBold=s,this._canvasElements[0].setFont(e,t,i,!1),this._canvasElements[1].setFont(e,t,s,!1),this._canvasElements[2].setFont(e,t,i,!0),this._canvasElements[3].setFont(e,t,s,!0),this.clear())}get(e,t,i){let s;if(!t&&!i&&1===e.length&&(s=e.charCodeAt(0))<256){if(-9999!==this._flat[s])return this._flat[s];const t=this._measure(e,0);return t>0&&(this._flat[s]=t),t}let r=e;t&&(r+="B"),i&&(r+="I");let o=this._holey.get(r);if(void 0===o){let s=0;t&&(s|=1),i&&(s|=2),o=this._measure(e,s),o>0&&this._holey.set(r,o)}return o}_measure(e,t){return this._canvasElements[t].measure(e)}};class r{constructor(){"undefined"!=typeof OffscreenCanvas?(this._canvas=new OffscreenCanvas(1,1),this._ctx=(0,s.throwIfFalsy)(this._canvas.getContext("2d"))):(this._canvas=document.createElement("canvas"),this._canvas.width=1,this._canvas.height=1,this._ctx=(0,s.throwIfFalsy)(this._canvas.getContext("2d")))}setFont(e,t,i,s){const r=s?"italic":"";this._ctx.font=`${r} ${i} ${t}px ${e}`.trim()}measure(e){return this._ctx.measureText(e).width}}},9176(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.INVERTED_DEFAULT_COLOR=void 0,t.INVERTED_DEFAULT_COLOR=257},6181(e,t){function i(e){return 57508<=e&&e<=57558}function s(e){return e>=128512&&e<=128591||e>=127744&&e<=128511||e>=128640&&e<=128767||e>=9728&&e<=9983||e>=9984&&e<=10175||e>=65024&&e<=65039||e>=129280&&e<=129535||e>=127462&&e<=127487}Object.defineProperty(t,"__esModule",{value:!0}),t.throwIfFalsy=function(e){if(!e)throw new Error("value must not be falsy");return e},t.isPowerlineGlyph=i,t.isRestrictedPowerlineGlyph=function(e){return 57520<=e&&e<=57527},t.isEmoji=s,t.allowRescaling=function(e,t,r,o){return 1===t&&r>Math.ceil(1.5*o)&&void 0!==e&&e>255&&!s(e)&&!i(e)&&!function(e){return 57344<=e&&e<=63743}(e)},t.treatGlyphAsBackgroundColor=function(e){return i(e)||function(e){return 9472<=e&&e<=9631}(e)},t.createRenderDimensions=function(){return{css:{canvas:{width:0,height:0},cell:{width:0,height:0}},device:{canvas:{width:0,height:0},cell:{width:0,height:0},char:{width:0,height:0,left:0,top:0}}}},t.computeNextVariantOffset=function(e,t,i=0){return(e-(2*Math.round(t)-i))%(2*Math.round(t))}},2274(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.createSelectionRenderModel=function(){return new i};class i{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(e,t,i,s=!1){if(this.selectionStart=t,this.selectionEnd=i,!t||!i||t[0]===i[0]&&t[1]===i[1])return void this.clear();const r=e.buffers.active.ydisp,o=t[1]-r,n=i[1]-r,a=Math.max(o,0),h=Math.min(n,e.rows-1);a>=e.rows||h<0?this.clear():(this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=o,this.viewportEndRow=n,this.viewportCappedStartRow=a,this.viewportCappedEndRow=h,this.startCol=t[0],this.endCol=i[0])}isCellSelected(e,t,i){return!!this.hasSelection&&(i-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&i>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&i<=this.viewportCappedEndRow:i>this.viewportStartRow&&i=this.startCol&&t=this.startCol)}}},654(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.TextBlinkStateManager=void 0;const s=i(4812);class r extends s.Disposable{constructor(e,t,i){super(),this._renderCallback=e,this._coreBrowserService=t,this._optionsService=i,this._intervalDuration=0,this._blinkOn=!0,this._needsBlinkInViewport=!1,this._isViewportVisible=!0,this._register(this._optionsService.onSpecificOptionChange("blinkIntervalDuration",e=>{this.setIntervalDuration(e)})),this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration),this._register((0,s.toDisposable)(()=>this._clearInterval()))}get isBlinkOn(){return this._blinkOn}get isEnabled(){return this._intervalDuration>0}setNeedsBlinkInViewport(e){this._needsBlinkInViewport!==e&&(this._needsBlinkInViewport=e,this._updateIntervalState())}setViewportVisible(e){this._isViewportVisible!==e&&(this._isViewportVisible=e,this._updateIntervalState())}setIntervalDuration(e){e!==this._intervalDuration&&(this._intervalDuration=e,this._clearInterval(),this._updateIntervalState())}_updateIntervalState(){if(this._intervalDuration>0&&this._needsBlinkInViewport&&this._isViewportVisible){if(void 0!==this._interval)return;const e=this._blinkOn;return this._blinkOn=!0,this._interval=this._coreBrowserService.window.setInterval(()=>{this._blinkOn=!this._blinkOn,this._renderCallback()},this._intervalDuration),void(e||this._renderCallback())}this._clearInterval(),this._blinkOn||(this._blinkOn=!0,this._renderCallback())}_clearInterval(){void 0!==this._interval&&(this._coreBrowserService.window.clearInterval(this._interval),this._interval=void 0)}}t.TextBlinkStateManager=r},8501(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._domNodePointerDown(e)))}_createArrow(e){const t=this._register(new c.ScrollbarArrow(e));return this.domNode.domNode.appendChild(t.bgDomNode),this.domNode.domNode.appendChild(t.domNode),t}_createSlider(e,t,i,s){this.slider=new h.FastDomNode(document.createElement("div")),this.slider.setClassName("xterm-slider"),this.slider.setPosition("absolute"),this.slider.setTop(e),this.slider.setLeft(t),"number"==typeof i&&this.slider.setWidth(i),"number"==typeof s&&this.slider.setHeight(s),this.slider.setLayerHinting(!0),this.slider.setContain("strict"),this.domNode.domNode.appendChild(this.slider.domNode),this._register(a.addDisposableListener(this.slider.domNode,a.eventType.POINTER_DOWN,e=>{0===e.button&&(e.preventDefault(),this._sliderPointerDown(e))})),this._onclick(this.slider.domNode,e=>{e.leftButton&&e.stopPropagation()})}_handleElementSize(e){return this._scrollbarState.setVisibleSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_handleElementScrollSize(e){return this._scrollbarState.setScrollSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_handleElementScrollPosition(e){return this._scrollbarState.setScrollPosition(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}beginReveal(){this._visibilityController.setShouldBeVisible(!0)}beginHide(){this._visibilityController.setShouldBeVisible(!1)}render(){this._shouldRender&&(this._shouldRender=!1,this._renderDomNode(this._scrollbarState.getRectangleLargeSize(),this._scrollbarState.getRectangleSmallSize()),this._updateSlider(this._scrollbarState.getSliderSize(),this._scrollbarState.getArrowSize()+this._scrollbarState.getSliderPosition()))}_domNodePointerDown(e){e.target===this.domNode.domNode&&this._handlePointerDown(e)}delegatePointerDown(e){const t=this.domNode.domNode.getClientRects()[0].top,i=t+this._scrollbarState.getSliderPosition(),s=t+this._scrollbarState.getSliderPosition()+this._scrollbarState.getSliderSize(),r=this._sliderPointerPosition(e);i<=r&&r<=s?0===e.button&&(e.preventDefault(),this._sliderPointerDown(e)):this._handlePointerDown(e)}_handlePointerDown(e){let t,i;if(e.target===this.domNode.domNode&&"number"==typeof e.offsetX&&"number"==typeof e.offsetY)t=e.offsetX,i=e.offsetY;else{const s=a.getDomNodePagePosition(this.domNode.domNode);t=e.pageX-s.left,i=e.pageY-s.top}const s=this._pointerDownRelativePosition(t,i);this._setDesiredScrollPositionNow(this._scrollByPage?this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(s):this._scrollbarState.getDesiredScrollPositionFromOffset(s)),0===e.button&&(e.preventDefault(),this._sliderPointerDown(e))}_sliderPointerDown(e){if(!(e.target&&e.target instanceof Element))return;const t=this._sliderPointerPosition(e),i=this._sliderOrthogonalPointerPosition(e),s=this._scrollbarState.clone();this.slider.toggleClassName("xterm-active",!0),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,e=>{const r=this._sliderOrthogonalPointerPosition(e),o=Math.abs(r-i);if(u.isWindows&&o>140)return void this._setDesiredScrollPositionNow(s.getScrollPosition());const n=this._sliderPointerPosition(e)-t;this._setDesiredScrollPositionNow(s.getDesiredScrollPositionFromDelta(n))},()=>{this.slider.toggleClassName("xterm-active",!1),this._host.handleDragEnd()}),this._host.handleDragStart()}_setDesiredScrollPositionNow(e){const t={};this.writeScrollPosition(t,e),this._scrollable.setScrollPositionNow(t)}updateScrollbarSize(e){this._updateScrollbarSize(e),this._scrollbarState.setScrollbarSize(e),this._shouldRender=!0,this._lazyRender||this.render()}isNeeded(){return this._scrollbarState.isNeeded()}}t.AbstractScrollbar=f},1203(e,t){function i(e){return"number"==typeof e?`${e}px`:e}Object.defineProperty(t,"__esModule",{value:!0}),t.FastDomNode=void 0,t.FastDomNode=class{constructor(e){this.domNode=e,this._width="",this._height="",this._top="",this._left="",this._bottom="",this._right="",this._className="",this._position="",this._layerHint=!1,this._contain="none"}setWidth(e){const t=i(e);this._width!==t&&(this._width=t,this.domNode.style.width=this._width)}setHeight(e){const t=i(e);this._height!==t&&(this._height=t,this.domNode.style.height=this._height)}setTop(e){const t=i(e);this._top!==t&&(this._top=t,this.domNode.style.top=this._top)}setLeft(e){const t=i(e);this._left!==t&&(this._left=t,this.domNode.style.left=this._left)}setBottom(e){const t=i(e);this._bottom!==t&&(this._bottom=t,this.domNode.style.bottom=this._bottom)}setRight(e){const t=i(e);this._right!==t&&(this._right=t,this.domNode.style.right=this._right)}setClassName(e){this._className!==e&&(this._className=e,this.domNode.className=this._className)}toggleClassName(e,t){this.domNode.classList.toggle(e,t),this._className=this.domNode.className}setPosition(e){this._position!==e&&(this._position=e,this.domNode.style.position=this._position)}setLayerHinting(e){this._layerHint!==e&&(this._layerHint=e,this.domNode.style.transform=e?"translate3d(0px, 0px, 0px)":"")}setContain(e){this._contain!==e&&(this._contain=e,this.domNode.style.contain=this._contain)}setAttribute(e,t){this.domNode.setAttribute(e,t)}}},928(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;n{try{e.releasePointerCapture(t)}catch{}}))}catch{o=a.getWindow(e)}this._hooks.add(a.addDisposableListener(o,a.eventType.POINTER_MOVE,e=>{e.buttons===i?(e.preventDefault(),this._pointerMoveCallback(e)):this.stopMonitoring(!0)})),this._hooks.add(a.addDisposableListener(o,a.eventType.POINTER_UP,e=>this.stopMonitoring(!0)))}}},9699(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.HorizontalScrollbar=void 0;const s=i(8501),r=i(1270);class o extends s.AbstractScrollbar{constructor(e,t,i){const s=e.getScrollDimensions(),o=e.getCurrentScrollPosition();if(super({lazyRender:t.lazyRender,host:i,scrollbarState:new r.ScrollbarState(t.horizontalHasArrows?t.horizontalScrollbarSize:0,2===t.horizontal?0:t.horizontalScrollbarSize,2===t.vertical?0:t.verticalScrollbarSize,s.width,s.scrollWidth,o.scrollLeft),visibility:t.horizontal,extraScrollbarClassName:"xterm-horizontal",scrollable:e,scrollByPage:t.scrollByPage}),t.horizontalHasArrows)throw new Error("horizontalHasArrows is not supported in xterm.js");this._createSlider(Math.floor((t.horizontalScrollbarSize-t.horizontalSliderSize)/2),0,void 0,t.horizontalSliderSize)}_updateSlider(e,t){this.slider.setWidth(e),this.slider.setLeft(t)}_renderDomNode(e,t){this.domNode.setWidth(e),this.domNode.setHeight(t),this.domNode.setLeft(0),this.domNode.setBottom(0)}handleScroll(e){return this._shouldRender=this._handleElementScrollSize(e.scrollWidth)||this._shouldRender,this._shouldRender=this._handleElementScrollPosition(e.scrollLeft)||this._shouldRender,this._shouldRender=this._handleElementSize(e.width)||this._shouldRender,this._shouldRender}_pointerDownRelativePosition(e,t){return e}_sliderPointerPosition(e){return e.pageX}_sliderOrthogonalPointerPosition(e){return e.pageY}_updateScrollbarSize(e){this.slider.setHeight(e)}writeScrollPosition(e,t){e.scrollLeft=t}updateOptions(e){this.updateScrollbarSize(2===e.horizontal?0:e.horizontalScrollbarSize),this._scrollbarState.setOppositeScrollbarSize(2===e.vertical?0:e.verticalScrollbarSize),this._visibilityController.setVisibility(e.horizontal),this._scrollByPage=e.scrollByPage}}t.HorizontalScrollbar=o},3988(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;ni&&(s=i-t),s<0&&(s=0),r<0&&(r=0),n+r>o&&(n=o-r),n<0&&(n=0),this.width=t,this.scrollWidth=i,this.scrollLeft=s,this.height=r,this.scrollHeight=o,this.scrollTop=n}equals(e){return this.rawScrollLeft===e.rawScrollLeft&&this.rawScrollTop===e.rawScrollTop&&this.width===e.width&&this.scrollWidth===e.scrollWidth&&this.scrollLeft===e.scrollLeft&&this.height===e.height&&this.scrollHeight===e.scrollHeight&&this.scrollTop===e.scrollTop}withScrollDimensions(e,t){return new o(this._forceIntegerValues,void 0!==e.width?e.width:this.width,void 0!==e.scrollWidth?e.scrollWidth:this.scrollWidth,t?this.rawScrollLeft:this.scrollLeft,void 0!==e.height?e.height:this.height,void 0!==e.scrollHeight?e.scrollHeight:this.scrollHeight,t?this.rawScrollTop:this.scrollTop)}withScrollPosition(e){return new o(this._forceIntegerValues,this.width,this.scrollWidth,void 0!==e.scrollLeft?e.scrollLeft:this.rawScrollLeft,this.height,this.scrollHeight,void 0!==e.scrollTop?e.scrollTop:this.rawScrollTop)}createScrollEvent(e,t){const i=this.width!==e.width,s=this.scrollWidth!==e.scrollWidth,r=this.scrollLeft!==e.scrollLeft,o=this.height!==e.height,n=this.scrollHeight!==e.scrollHeight,a=this.scrollTop!==e.scrollTop;return{inSmoothScrolling:t,oldWidth:e.width,oldScrollWidth:e.scrollWidth,oldScrollLeft:e.scrollLeft,width:this.width,scrollWidth:this.scrollWidth,scrollLeft:this.scrollLeft,oldHeight:e.height,oldScrollHeight:e.scrollHeight,oldScrollTop:e.scrollTop,height:this.height,scrollHeight:this.scrollHeight,scrollTop:this.scrollTop,widthChanged:i,scrollWidthChanged:s,scrollLeftChanged:r,heightChanged:o,scrollHeightChanged:n,scrollTopChanged:a}}}t.ScrollState=o;class n extends r.Disposable{constructor(e){super(),this._scrollableBrand=void 0,this._onScroll=this._register(new s.Emitter),this.onScroll=this._onScroll.event,this._smoothScrollDuration=e.smoothScrollDuration,this._scheduleAtNextAnimationFrame=e.scheduleAtNextAnimationFrame,this._state=new o(e.forceIntegerValues,0,0,0,0,0,0),this._smoothScrolling=null}dispose(){this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),super.dispose()}setSmoothScrollDuration(e){this._smoothScrollDuration=e}validateScrollPosition(e){return this._state.withScrollPosition(e)}getScrollDimensions(){return this._state}setScrollDimensions(e,t){const i=this._state.withScrollDimensions(e,t);this._setState(i,Boolean(this._smoothScrolling)),this._smoothScrolling?.acceptScrollDimensions(this._state)}getFutureScrollPosition(){return this._smoothScrolling?this._smoothScrolling.to:this._state}getCurrentScrollPosition(){return this._state}setScrollPositionNow(e){const t=this._state.withScrollPosition(e);this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),this._setState(t,!1)}setScrollPositionSmooth(e,t){if(0!==this._smoothScrollDuration){if(this._smoothScrolling){e={scrollLeft:void 0===e.scrollLeft?this._smoothScrolling.to.scrollLeft:e.scrollLeft,scrollTop:void 0===e.scrollTop?this._smoothScrolling.to.scrollTop:e.scrollTop};const i=this._state.withScrollPosition(e);if(this._smoothScrolling.to.scrollLeft===i.scrollLeft&&this._smoothScrolling.to.scrollTop===i.scrollTop)return;let s;s=t?new l(this._smoothScrolling.from,i,this._smoothScrolling.startTime,this._smoothScrolling.duration):l.start(this._state,i,this._smoothScrollDuration),this._smoothScrolling.dispose(),this._smoothScrolling=s}else{const t=this._state.withScrollPosition(e);this._smoothScrolling=l.start(this._state,t,this._smoothScrollDuration)}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}else this.setScrollPositionNow(e)}hasPendingScrollAnimation(){return Boolean(this._smoothScrolling)}_performSmoothScrolling(){if(!this._smoothScrolling)return;const e=this._smoothScrolling.tick(),t=this._state.withScrollPosition(e);return this._setState(t,!0),this._smoothScrolling?e.isDone?(this._smoothScrolling.dispose(),void(this._smoothScrolling=null)):void(this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})):void 0}_setState(e,t){const i=this._state;i.equals(e)||(this._state=e,this._onScroll.fire(this._state.createScrollEvent(i,t)))}}t.Scrollable=n;class a{constructor(e,t,i){this.scrollLeft=e,this.scrollTop=t,this.isDone=i}}function h(e,t){const i=t-e;return function(t){return e+i*(1-(s=1-t,Math.pow(s,3)));var s}}class l{constructor(e,t,i,s){this.from=e,this.to=t,this.duration=s,this.startTime=i,this.animationFrameDisposable=null,this._initAnimations()}_initAnimations(){this._scrollLeft=this._initAnimation(this.from.scrollLeft,this.to.scrollLeft,this.to.width),this._scrollTop=this._initAnimation(this.from.scrollTop,this.to.scrollTop,this.to.height)}_initAnimation(e,t,i){if(Math.abs(e-t)>2.5*i){let n,a;return e0&&Math.abs(e.deltaY)>0)return 1;let i=.5;if(this._isAlmostInt(e.deltaX)&&this._isAlmostInt(e.deltaY)||(i+=.25),t){const s=Math.abs(e.deltaX),r=Math.abs(e.deltaY),o=Math.abs(t.deltaX),n=Math.abs(t.deltaY),a=Math.max(Math.min(s,o),1),h=Math.max(Math.min(r,n),1),l=Math.max(s,o),c=Math.max(r,n);l%a===0&&c%h===0&&(i-=.5)}return Math.min(Math.max(i,0),1)}_isAlmostInt(e){return Math.abs(Math.round(e)-e)<.01}}S.INSTANCE=new S;class b extends _.Widget{get options(){return this._options}constructor(e,t,i){let s;super(),this._onScroll=this._register(new f.Emitter),this.onScroll=this._onScroll.event,t=t??{};const r=!i;i?s=i:(t.mouseWheelSmoothScroll=!1,s=new g.Scrollable({forceIntegerValues:!0,smoothScrollDuration:0,scheduleAtNextAnimationFrame:t=>a.scheduleAtNextAnimationFrame(a.getWindow(e),t)})),this._options=function(e){const t={lazyRender:void 0!==e.lazyRender&&e.lazyRender,className:void 0!==e.className?e.className:"",useShadows:void 0===e.useShadows||e.useShadows,handleMouseWheel:void 0===e.handleMouseWheel||e.handleMouseWheel,flipAxes:void 0!==e.flipAxes&&e.flipAxes,consumeMouseWheelIfScrollbarIsNeeded:void 0!==e.consumeMouseWheelIfScrollbarIsNeeded&&e.consumeMouseWheelIfScrollbarIsNeeded,alwaysConsumeMouseWheel:void 0!==e.alwaysConsumeMouseWheel&&e.alwaysConsumeMouseWheel,scrollYToX:void 0!==e.scrollYToX&&e.scrollYToX,mouseWheelScrollSensitivity:void 0!==e.mouseWheelScrollSensitivity?e.mouseWheelScrollSensitivity:1,fastScrollSensitivity:void 0!==e.fastScrollSensitivity?e.fastScrollSensitivity:5,scrollPredominantAxis:void 0===e.scrollPredominantAxis||e.scrollPredominantAxis,mouseWheelSmoothScroll:void 0===e.mouseWheelSmoothScroll||e.mouseWheelSmoothScroll,listenOnDomNode:void 0!==e.listenOnDomNode?e.listenOnDomNode:null,horizontal:void 0!==e.horizontal?e.horizontal:1,horizontalScrollbarSize:void 0!==e.horizontalScrollbarSize?e.horizontalScrollbarSize:10,horizontalSliderSize:void 0!==e.horizontalSliderSize?e.horizontalSliderSize:0,horizontalHasArrows:void 0!==e.horizontalHasArrows&&e.horizontalHasArrows,vertical:void 0!==e.vertical?e.vertical:1,verticalScrollbarSize:void 0!==e.verticalScrollbarSize?e.verticalScrollbarSize:10,verticalHasArrows:void 0!==e.verticalHasArrows&&e.verticalHasArrows,verticalSliderSize:void 0!==e.verticalSliderSize?e.verticalSliderSize:0,scrollByPage:void 0!==e.scrollByPage&&e.scrollByPage};return t.horizontalSliderSize=void 0!==e.horizontalSliderSize?e.horizontalSliderSize:t.horizontalScrollbarSize,t.verticalSliderSize=void 0!==e.verticalSliderSize?e.verticalSliderSize:t.verticalScrollbarSize,v.isMac&&(t.className+=" xterm-mac"),t}(t),this._scrollable=s,this._register(this._scrollable.onScroll(e=>{this._handleScroll(e),this._onScroll.fire(e)})),r&&this._register(this._scrollable);const o={handleMouseWheel:e=>this._handleMouseWheel(e),handleDragStart:()=>this._handleDragStart(),handleDragEnd:()=>this._handleDragEnd()};this._verticalScrollbar=this._register(new d.VerticalScrollbar(this._scrollable,this._options,o)),this._horizontalScrollbar=this._register(new c.HorizontalScrollbar(this._scrollable,this._options,o)),this._domNode=document.createElement("div"),this._domNode.className="xterm-scrollable-element "+this._options.className,this._domNode.setAttribute("role","presentation"),this._domNode.style.position="relative",this._domNode.appendChild(e),this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode),this._domNode.appendChild(this._verticalScrollbar.domNode.domNode),this._options.useShadows?(this._leftShadowDomNode=new h.FastDomNode(document.createElement("div")),this._leftShadowDomNode.setClassName("xterm-shadow"),this._domNode.appendChild(this._leftShadowDomNode.domNode),this._topShadowDomNode=new h.FastDomNode(document.createElement("div")),this._topShadowDomNode.setClassName("xterm-shadow"),this._domNode.appendChild(this._topShadowDomNode.domNode),this._topLeftShadowDomNode=new h.FastDomNode(document.createElement("div")),this._topLeftShadowDomNode.setClassName("xterm-shadow"),this._domNode.appendChild(this._topLeftShadowDomNode.domNode)):(this._leftShadowDomNode=null,this._topShadowDomNode=null,this._topLeftShadowDomNode=null),this._listenOnDomNode=this._options.listenOnDomNode??this._domNode,this._mouseWheelToDispose=[],this._setListeningToMouseWheel(this._options.handleMouseWheel),this._onmouseover(this._listenOnDomNode,e=>this._handleMouseOver(e)),this._onmouseleave(this._listenOnDomNode,e=>this._handleMouseLeave(e)),this._hideTimeout=this._register(new u.TimeoutTimer),this._isDragging=!1,this._mouseIsOver=!1,this._shouldRender=!0,this._revealOnScroll=!0}dispose(){this._mouseWheelToDispose=(0,p.dispose)(this._mouseWheelToDispose),super.dispose()}getDomNode(){return this._domNode}getScrollDimensions(){return this._scrollable.getScrollDimensions()}setScrollDimensions(e){this._scrollable.setScrollDimensions(e,!1)}setScrollPosition(e){e.reuseAnimation?this._scrollable.setScrollPositionSmooth(e,e.reuseAnimation):this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}updateClassName(e){this._options.className=e,v.isMac&&(this._options.className+=" xterm-mac"),this._domNode.className="xterm-scrollable-element "+this._options.className}updateOptions(e){void 0!==e.handleMouseWheel&&(this._options.handleMouseWheel=e.handleMouseWheel,this._setListeningToMouseWheel(this._options.handleMouseWheel)),void 0!==e.mouseWheelScrollSensitivity&&(this._options.mouseWheelScrollSensitivity=e.mouseWheelScrollSensitivity),void 0!==e.fastScrollSensitivity&&(this._options.fastScrollSensitivity=e.fastScrollSensitivity),void 0!==e.scrollPredominantAxis&&(this._options.scrollPredominantAxis=e.scrollPredominantAxis),void 0!==e.horizontal&&(this._options.horizontal=e.horizontal),void 0!==e.vertical&&(this._options.vertical=e.vertical),void 0!==e.horizontalHasArrows&&(this._options.horizontalHasArrows=e.horizontalHasArrows),void 0!==e.verticalHasArrows&&(this._options.verticalHasArrows=e.verticalHasArrows),void 0!==e.horizontalScrollbarSize&&(this._options.horizontalScrollbarSize=e.horizontalScrollbarSize),void 0!==e.verticalScrollbarSize&&(this._options.verticalScrollbarSize=e.verticalScrollbarSize),void 0!==e.scrollByPage&&(this._options.scrollByPage=e.scrollByPage),this._horizontalScrollbar.updateOptions(this._options),this._verticalScrollbar.updateOptions(this._options),this._options.lazyRender||this._render()}delegateScrollFromMouseWheelEvent(e){this._handleMouseWheel(new l.StandardWheelEvent(e))}_setListeningToMouseWheel(e){if(this._mouseWheelToDispose.length>0!==e&&(this._mouseWheelToDispose=(0,p.dispose)(this._mouseWheelToDispose),e)){const e=e=>{this._handleMouseWheel(new l.StandardWheelEvent(e))};this._mouseWheelToDispose.push(a.addDisposableListener(this._listenOnDomNode,a.eventType.MOUSE_WHEEL,e,{passive:!1}))}}_handleMouseWheel(e){if(e.browserEvent?.defaultPrevented)return;const t=S.INSTANCE;t.acceptStandardWheelEvent(e);let i=!1;if(e.deltaY||e.deltaX){let s=e.deltaY*this._options.mouseWheelScrollSensitivity,r=e.deltaX*this._options.mouseWheelScrollSensitivity;this._options.scrollPredominantAxis&&(this._options.scrollYToX&&r+s===0?r=s=0:Math.abs(s)>=Math.abs(r)?r=0:s=0),this._options.flipAxes&&([s,r]=[r,s]);const o=!v.isMac&&e.browserEvent&&e.browserEvent.shiftKey;!this._options.scrollYToX&&!o||r||(r=s,s=0),e.browserEvent&&e.browserEvent.altKey&&(r*=this._options.fastScrollSensitivity,s*=this._options.fastScrollSensitivity);const n=this._scrollable.getFutureScrollPosition();let a={};if(s){const e=50*s,t=n.scrollTop-(e<0?Math.floor(e):Math.ceil(e));this._verticalScrollbar.writeScrollPosition(a,t)}if(r){const e=50*r,t=n.scrollLeft-(e<0?Math.floor(e):Math.ceil(e));this._horizontalScrollbar.writeScrollPosition(a,t)}a=this._scrollable.validateScrollPosition(a),(n.scrollLeft!==a.scrollLeft||n.scrollTop!==a.scrollTop)&&(this._options.mouseWheelSmoothScroll&&t.isPhysicalMouseWheel()?this._scrollable.setScrollPositionSmooth(a):this._scrollable.setScrollPositionNow(a),i=!0)}let s=i;!s&&this._options.alwaysConsumeMouseWheel&&(s=!0),!s&&this._options.consumeMouseWheelIfScrollbarIsNeeded&&(this._verticalScrollbar.isNeeded()||this._horizontalScrollbar.isNeeded())&&(s=!0),s&&(e.preventDefault(),e.stopPropagation())}_handleScroll(e){this._shouldRender=this._horizontalScrollbar.handleScroll(e)||this._shouldRender,this._shouldRender=this._verticalScrollbar.handleScroll(e)||this._shouldRender,this._options.useShadows&&(this._shouldRender=!0),this._revealOnScroll&&this._reveal(),this._options.lazyRender||this._render()}renderNow(){if(!this._options.lazyRender)throw new Error("Please use `lazyRender` together with `renderNow`!");this._render()}_render(){if(this._shouldRender&&(this._shouldRender=!1,this._horizontalScrollbar.render(),this._verticalScrollbar.render(),this._options.useShadows)){const e=this._scrollable.getCurrentScrollPosition(),t=e.scrollTop>0,i=e.scrollLeft>0,s=i?" xterm-shadow-left":"",r=t?" xterm-shadow-top":"",o=i||t?" xterm-shadow-top-left-corner":"";this._leftShadowDomNode.setClassName(`xterm-shadow${s}`),this._topShadowDomNode.setClassName(`xterm-shadow${r}`),this._topLeftShadowDomNode.setClassName(`xterm-shadow${o}${r}${s}`)}}_handleDragStart(){this._isDragging=!0,this._reveal()}_handleDragEnd(){this._isDragging=!1,this._hide()}_handleMouseLeave(e){this._mouseIsOver=!1,this._hide()}_handleMouseOver(e){this._mouseIsOver=!0,this._reveal()}_reveal(){this._verticalScrollbar.beginReveal(),this._horizontalScrollbar.beginReveal(),this._scheduleHide()}_hide(){this._mouseIsOver||this._isDragging||(this._verticalScrollbar.beginHide(),this._horizontalScrollbar.beginHide())}_scheduleHide(){this._mouseIsOver||this._isDragging||this._hideTimeout.cancelAndSet(()=>this._hide(),500)}}t.SmoothScrollableElement=b},9594(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._arrowPointerDown(e))),this._register(c.addStandardDisposableListener(this.domNode,c.eventType.POINTER_DOWN,e=>this._arrowPointerDown(e))),this._pointerdownRepeatTimer=this._register(new c.WindowIntervalTimer),this._pointerdownScheduleRepeatTimer=this._register(new l.TimeoutTimer)}_arrowPointerDown(e){e.target&&e.target instanceof Element&&(this._handleActivate(),this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancelAndSet(()=>{this._pointerdownRepeatTimer.cancelAndSet(()=>this._handleActivate(),1e3/24,c.getWindow(e))},200),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,e=>{},()=>{this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancel()}),e.preventDefault())}}t.ScrollbarArrow=d},1270(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ScrollbarState=void 0;class i{constructor(e,t,i,s,r,o){this._scrollbarSize=Math.round(t),this._oppositeScrollbarSize=Math.round(i),this._arrowSize=Math.round(e),this._visibleSize=s,this._scrollSize=r,this._scrollPosition=o,this._computedAvailableSize=0,this._computedIsNeeded=!1,this._computedSliderSize=0,this._computedSliderRatio=0,this._computedSliderPosition=0,this._refreshComputedValues()}clone(){return new i(this._arrowSize,this._scrollbarSize,this._oppositeScrollbarSize,this._visibleSize,this._scrollSize,this._scrollPosition)}setVisibleSize(e){const t=Math.round(e);return this._visibleSize!==t&&(this._visibleSize=t,this._refreshComputedValues(),!0)}setScrollSize(e){const t=Math.round(e);return this._scrollSize!==t&&(this._scrollSize=t,this._refreshComputedValues(),!0)}setScrollPosition(e){const t=Math.round(e);return this._scrollPosition!==t&&(this._scrollPosition=t,this._refreshComputedValues(),!0)}setScrollbarSize(e){this._scrollbarSize=Math.round(e)}setArrowSize(e){const t=Math.round(e);this._arrowSize!==t&&(this._arrowSize=t,this._refreshComputedValues())}setOppositeScrollbarSize(e){this._oppositeScrollbarSize=Math.round(e)}static _computeValues(e,t,i,s,r){const o=Math.max(0,i-e),n=Math.max(0,o-2*t),a=s>0&&s>i;if(!a)return{computedAvailableSize:Math.round(o),computedIsNeeded:a,computedSliderSize:Math.round(n),computedSliderRatio:0,computedSliderPosition:0};const h=Math.round(Math.max(20,Math.floor(i*n/s))),l=(n-h)/(s-i),c=r*l;return{computedAvailableSize:Math.round(o),computedIsNeeded:a,computedSliderSize:Math.round(h),computedSliderRatio:l,computedSliderPosition:Math.round(c)}}_refreshComputedValues(){const e=i._computeValues(this._oppositeScrollbarSize,this._arrowSize,this._visibleSize,this._scrollSize,this._scrollPosition);this._computedAvailableSize=e.computedAvailableSize,this._computedIsNeeded=e.computedIsNeeded,this._computedSliderSize=e.computedSliderSize,this._computedSliderRatio=e.computedSliderRatio,this._computedSliderPosition=e.computedSliderPosition}getArrowSize(){return this._arrowSize}getScrollPosition(){return this._scrollPosition}getRectangleLargeSize(){return this._computedAvailableSize}getRectangleSmallSize(){return this._scrollbarSize}isNeeded(){return this._computedIsNeeded}getSliderSize(){return this._computedSliderSize}getSliderPosition(){return this._computedSliderPosition}getDesiredScrollPositionFromOffset(e){if(!this._computedIsNeeded)return 0;const t=e-this._arrowSize-this._computedSliderSize/2;return Math.round(t/this._computedSliderRatio)}getDesiredScrollPositionFromOffsetPaged(e){if(!this._computedIsNeeded)return 0;const t=e-this._arrowSize;let i=this._scrollPosition;return t{this._domNode?.setClassName(this._visibleClassName)},0))}_hide(e){this._revealTimer.cancel(),this._isVisible&&(this._isVisible=!1,this._domNode?.setClassName(this._invisibleClassName+(e?" xterm-fade":"")))}}t.ScrollbarVisibilityController=o},2650(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},a=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;n{s||(s=!0,this._remove(i))}}_remove(e){if(e.prev!==_.Undefined&&e.next!==_.Undefined){const t=e.prev;t.next=e.next,e.next.prev=t}else e.prev===_.Undefined&&e.next===_.Undefined?(this._first=_.Undefined,this._last=_.Undefined):e.next===_.Undefined?(this._last=this._last.prev,this._last.next=_.Undefined):e.prev===_.Undefined&&(this._first=this._first.next,this._first.prev=_.Undefined)}*[Symbol.iterator](){let e=this._first;for(;e!==_.Undefined;)yield e.element,e=e.next}}var f;!function(e){e.TAP="-xterm-gesturetap",e.CHANGE="-xterm-gesturechange",e.START="-xterm-gesturestart",e.END="-xterm-gesturesend",e.CONTEXT_MENU="-xterm-gesturecontextmenu"}(f||(t.EventType=f={}));class p extends l.Disposable{constructor(){super(),this._dispatched=!1,this._targets=new u,this._ignoreTargets=new u,this._activeTouches={},this._handle=null,this._lastSetTapCountTime=0;const e=c;this._register(h.addDisposableListener(e.document,"touchstart",e=>this._handleTouchStart(e),{passive:!1})),this._register(h.addDisposableListener(e.document,"touchend",t=>this._handleTouchEnd(e,t))),this._register(h.addDisposableListener(e.document,"touchmove",e=>this._handleTouchMove(e),{passive:!1}))}static addTarget(e){if(!p.isTouchDevice())return l.Disposable.None;p._instance||(p._instance=new p);const t=p._instance._targets.push(e);return(0,l.toDisposable)(t)}static ignoreTarget(e){if(!p.isTouchDevice())return l.Disposable.None;p._instance||(p._instance=new p);const t=p._instance._ignoreTargets.push(e);return(0,l.toDisposable)(t)}static isTouchDevice(){return"ontouchstart"in c||navigator.maxTouchPoints>0}dispose(){this._handle&&(this._handle.dispose(),this._handle=null),super.dispose()}_handleTouchStart(e){const t=Date.now();this._handle&&(this._handle.dispose(),this._handle=null);for(let i=0,s=e.targetTouches.length;i=p._holdDelay&&Math.abs(n.initialPageX-d(n.rollingPageX))<30&&Math.abs(n.initialPageY-d(n.rollingPageY))<30){const e=this._newGestureEvent(f.CONTEXT_MENU,n.initialTarget);e.pageX=d(n.rollingPageX),e.pageY=d(n.rollingPageY),this._dispatchEvent(e)}else if(1===s){const t=d(n.rollingPageX),s=d(n.rollingPageY),r=d(n.rollingTimestamps)-n.rollingTimestamps[0],o=t-n.rollingPageX[0],a=s-n.rollingPageY[0],h=[...this._targets].filter(e=>n.initialTarget instanceof Node&&e.contains(n.initialTarget));this._inertia(e,h,i,Math.abs(o)/r,o>0?1:-1,t,Math.abs(a)/r,a>0?1:-1,s)}this._dispatchEvent(this._newGestureEvent(f.END,n.initialTarget)),delete this._activeTouches[o.identifier]}this._dispatched&&(t.preventDefault(),t.stopPropagation(),this._dispatched=!1)}_newGestureEvent(e,t){const i=document.createEvent("CustomEvent");return i.initEvent(e,!1,!0),i.initialTarget=t,i.tapCount=0,i}_dispatchEvent(e){if(e.type===f.TAP){const t=(new Date).getTime();let i;i=t-this._lastSetTapCountTime>p._clearTapCountTime?1:2,this._lastSetTapCountTime=t,e.tapCount=i}else e.type!==f.CHANGE&&e.type!==f.CONTEXT_MENU||(this._lastSetTapCountTime=0);if(e.initialTarget instanceof Node){for(const t of this._ignoreTargets)if(t.contains(e.initialTarget))return;const t=[];for(const i of this._targets)if(i.contains(e.initialTarget)){let s=0,r=e.initialTarget;for(;r&&r!==i;)s++,r=r.parentElement;t.push([s,i])}t.sort((e,t)=>e[0]-t[0]);for(const[,i]of t)i.dispatchEvent(e),this._dispatched=!0}}_inertia(e,t,i,s,r,o,n,a,l){this._handle=h.scheduleAtNextAnimationFrame(e,()=>{const h=Date.now(),c=h-i;let d=0,_=0,u=!0;s+=p._scrollFriction*c,n+=p._scrollFriction*c,s>0&&(u=!1,d=r*s*c),n>0&&(u=!1,_=a*n*c);const v=this._newGestureEvent(f.CHANGE);v.translationX=d,v.translationY=_,t.forEach(e=>e.dispatchEvent(v)),u||this._inertia(e,t,h,s,r,o+d,n,a,l+_)})}_handleTouchMove(e){const t=Date.now();for(let i=0,s=e.changedTouches.length;i3&&(r.rollingPageX.shift(),r.rollingPageY.shift(),r.rollingTimestamps.shift()),r.rollingPageX.push(s.pageX),r.rollingPageY.push(s.pageY),r.rollingTimestamps.push(t)}this._dispatched&&(e.preventDefault(),e.stopPropagation(),this._dispatched=!1)}}t.Gesture=p,p._scrollFriction=-.005,p._holdDelay=700,p._clearTapCountTime=400,n([function(e,t,i){let s=null,r=null;if("function"==typeof i.value?(s="value",r=i.value,0!==r.length&&console.warn("Memoize should only be used in functions with zero parameters")):"function"==typeof i.get&&(s="get",r=i.get),!r||!s)throw new Error("not supported");const o=`$memoize$${t}`;i[s]=function(...e){return this.hasOwnProperty(o)||Object.defineProperty(this,o,{configurable:!1,enumerable:!1,writable:!1,value:r.apply(this,e)}),this[o]}}],p,"isTouchDevice",null)},8997(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.VerticalScrollbar=void 0;const s=i(8501),r=i(1270);class o extends s.AbstractScrollbar{constructor(e,t,i){const s=e.getScrollDimensions(),o=e.getCurrentScrollPosition(),n=t.verticalHasArrows;super({lazyRender:t.lazyRender,host:i,scrollbarState:new r.ScrollbarState(n?t.verticalScrollbarSize:0,2===t.vertical?0:t.verticalScrollbarSize,0,s.height,s.scrollHeight,o.scrollTop),visibility:t.vertical,extraScrollbarClassName:"xterm-vertical",scrollable:e,scrollByPage:t.scrollByPage}),this._arrowScrollDelta=0,this._setArrows(n,t.verticalScrollbarSize),this._createSlider(0,Math.floor((t.verticalScrollbarSize-t.verticalSliderSize)/2),t.verticalSliderSize,void 0)}_updateSlider(e,t){this.slider.setHeight(e),this.slider.setTop(t)}_renderDomNode(e,t){this.domNode.setWidth(t),this.domNode.setHeight(e),this.domNode.setRight(0),this.domNode.setTop(0)}handleScroll(e){return this._shouldRender=this._handleElementScrollSize(e.scrollHeight)||this._shouldRender,this._shouldRender=this._handleElementScrollPosition(e.scrollTop)||this._shouldRender,this._shouldRender=this._handleElementSize(e.height)||this._shouldRender,this._shouldRender}_pointerDownRelativePosition(e,t){return t}_sliderPointerPosition(e){return e.pageY}_sliderOrthogonalPointerPosition(e){return e.pageX}_updateScrollbarSize(e){this.slider.setWidth(e)}writeScrollPosition(e,t){e.scrollTop=t}_arrowScroll(e){const t=this._scrollable.getCurrentScrollPosition();this._scrollable.setScrollPositionNow({scrollTop:t.scrollTop+e})}_setArrows(e,t){if(this._arrowScrollDelta=t,!this._arrowUp||!this._arrowDown){const e=0;this._arrowUp=this._createArrow({className:"xterm-scra xterm-arrow-up",top:e,left:e,bgWidth:t,bgHeight:t,handleActivate:()=>this._arrowScroll(-this._arrowScrollDelta)}),this._arrowDown=this._createArrow({className:"xterm-scra xterm-arrow-down",bottom:e,left:e,bgWidth:t,bgHeight:t,handleActivate:()=>this._arrowScroll(this._arrowScrollDelta)})}if(this._updateArrowSize(this._arrowUp,t),this._updateArrowSize(this._arrowDown,t),!this._arrowUp||!this._arrowDown)return;const i=e?"":"none";this._arrowUp.bgDomNode.style.display=i,this._arrowUp.domNode.style.display=i,this._arrowDown.bgDomNode.style.display=i,this._arrowDown.domNode.style.display=i}_updateArrowSize(e,t){e&&(e.bgDomNode.style.width=`${t}px`,e.bgDomNode.style.height=`${t}px`,e.domNode.style.width=`${t}px`,e.domNode.style.height=`${t}px`)}updateOptions(e){const t=e.verticalHasArrows?e.verticalScrollbarSize:0;this._scrollbarState.setArrowSize(t),this._setArrows(e.verticalHasArrows,e.verticalScrollbarSize),this.updateScrollbarSize(2===e.vertical?0:e.verticalScrollbarSize),this._scrollbarState.setOppositeScrollbarSize(0),this._visibilityController.setVisibility(e.vertical),this._scrollByPage=e.scrollByPage}}t.VerticalScrollbar=o},7741(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nt(new h.StandardMouseEvent(a.getWindow(e),i))))}_onmouseover(e,t){this._register(a.addDisposableListener(e,a.eventType.MOUSE_OVER,i=>t(new h.StandardMouseEvent(a.getWindow(e),i))))}_onmouseleave(e,t){this._register(a.addDisposableListener(e,a.eventType.MOUSE_LEAVE,i=>t(new h.StandardMouseEvent(a.getWindow(e),i))))}}t.Widget=c},5959(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionModel=void 0,t.SelectionModel=class{constructor(e){this._bufferService=e,this.isSelectAllActive=!1,this.selectionStartLength=0}clearSelection(){this.selectionStart=void 0,this.selectionEnd=void 0,this.isSelectAllActive=!1,this.selectionStartLength=0}get finalSelectionStart(){return this.isSelectAllActive?[0,0]:this.selectionEnd&&this.selectionStart&&this.areSelectionValuesReversed()?this.selectionEnd:this.selectionStart}get finalSelectionEnd(){if(this.isSelectAllActive)return[this._bufferService.cols,this._bufferService.buffer.ybase+this._bufferService.rows-1];if(this.selectionStart){if(!this.selectionEnd||this.areSelectionValuesReversed()){const e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?e%this._bufferService.cols===0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)-1]:[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){const e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[Math.max(e,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){const e=this.selectionStart,t=this.selectionEnd;return!(!e||!t)&&(e[1]>t[1]||e[1]===t[1]&&e[0]>t[0])}handleTrim(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):!!(this.selectionStart&&this.selectionStart[1]<0)&&(this.selectionStart=[0,0],!0)}}},4792(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CharSizeService=void 0;const o=i(6501),n=i(4812),a=i(8636);let h=class extends n.Disposable{get hasValidSize(){return this.width>0&&this.height>0}constructor(e,t,i){super(),this._optionsService=i,this.width=0,this.height=0,this._onCharSizeChange=this._register(new a.Emitter),this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new d(this._optionsService))}catch{this._measureStrategy=this._register(new c(e,t,this._optionsService))}this._register(this._optionsService.onMultipleOptionChange(["fontFamily","fontSize"],()=>this.measure()))}measure(){const e=this._measureStrategy.measure();e.width===this.width&&e.height===this.height||(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};t.CharSizeService=h,t.CharSizeService=h=s([r(2,o.IOptionsService)],h);class l extends n.Disposable{constructor(){super(...arguments),this._result={width:0,height:0}}_validateAndSet(e,t){void 0!==e&&e>0&&void 0!==t&&t>0&&(this._result.width=e,this._result.height=t)}}class c extends l{constructor(e,t,i){super(),this._document=e,this._parentElement=t,this._optionsService=i,this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W".repeat(32),this._measureElement.setAttribute("aria-hidden","true"),this._measureElement.style.whiteSpace="pre",this._measureElement.style.fontKerning="none",this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}}class d extends l{constructor(e){super(),this._optionsService=e,this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext("2d");const t=this._ctx.measureText("W");if(!("width"in t&&"fontBoundingBoxAscent"in t&&"fontBoundingBoxDescent"in t))throw new Error("Required font metrics not supported")}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;const e=this._ctx.measureText("W");return this._validateAndSet(e.width,e.fontBoundingBoxAscent+e.fontBoundingBoxDescent),this._result}}},945(e,t,i){var s,r=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},o=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CharacterJoinerService=t.JoinedCellData=void 0;const n=i(5451),a=i(8938),h=i(3055),l=i(6501);class c extends n.AttributeData{constructor(e,t,i){super(),this.content=0,this.combinedData="",this.fg=e.fg,this.bg=e.bg,this.combinedData=t,this._width=i}isCombined(){return 2097152}getWidth(){return this._width}getChars(){return this.combinedData}getCode(){return 2097151}setFromCharData(e){throw new Error("not implemented")}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}}t.JoinedCellData=c;let d=s=class{constructor(e){this._bufferService=e,this._characterJoiners=[],this._nextCharacterJoinerId=0,this._workCell=new h.CellData}register(e){const t={id:this._nextCharacterJoinerId++,handler:e};return this._characterJoiners.push(t),t.id}deregister(e){for(let t=0;t1){const e=this._getJoinedRanges(s,h,n,t,o);for(let t=0;t1){const e=this._getJoinedRanges(s,h,n,t,o);for(let t=0;tthis._screenDprMonitor.setWindow(e))),this._register(s.EventUtils.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register((0,r.addDisposableListener)(this._textarea,"focus",()=>this._isFocused=!0)),this._register((0,r.addDisposableListener)(this._textarea,"blur",()=>this._isFocused=!1))}get window(){return this._window}set window(e){this._window!==e&&(this._window=e,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return void 0===this._cachedIsFocused&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask(()=>this._cachedIsFocused=void 0)),this._cachedIsFocused}}t.CoreBrowserService=n;class a extends o.Disposable{constructor(e){super(),this._parentWindow=e,this._windowResizeListener=this._register(new o.MutableDisposable),this._onDprChange=this._register(new s.Emitter),this.onDprChange=this._onDprChange.event,this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register((0,o.toDisposable)(()=>this.clearListener()))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=(0,r.addDisposableListener)(this._parentWindow,"resize",()=>this._setDprAndFireIfDiffers())}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){this._outerListener&&(this._resolutionMediaMatchList?.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){this._resolutionMediaMatchList&&this._outerListener&&(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}}},2136(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.KeyboardService=void 0;const o=i(706),n=i(7241),a=i(9249),h=i(701),l=i(6501);let c=class{constructor(e,t){this._coreService=e,this._optionsService=t}_getWin32InputMode(){return this._win32InputMode??=new a.Win32InputMode,this._win32InputMode}_getKittyKeyboard(){return this._kittyKeyboard??=new n.KittyKeyboard,this._kittyKeyboard}evaluateKeyDown(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!0);const t=this._coreService.kittyKeyboard.flags;return this.useKitty?this._getKittyKeyboard().evaluate(e,t,e.repeat?2:1,h.isMac&&this._optionsService.rawOptions.macOptionIsMeta):(0,o.evaluateKeyboardEvent)(e,this._coreService.decPrivateModes.applicationCursorKeys,h.isMac,this._optionsService.rawOptions.macOptionIsMeta)}evaluateKeyUp(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!1);const t=this._coreService.kittyKeyboard.flags;return this.useKitty&&2&t?this._getKittyKeyboard().evaluate(e,t,3,h.isMac&&this._optionsService.rawOptions.macOptionIsMeta):void 0}get useKitty(){const e=this._coreService.kittyKeyboard.flags;return!(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard||!n.KittyKeyboard.shouldUseProtocol(e))}get useWin32InputMode(){return!(!this._optionsService.rawOptions.vtExtensions?.win32InputMode||!this._coreService.decPrivateModes.win32InputMode)}};t.KeyboardService=c,t.KeyboardService=c=s([r(0,l.ICoreService),r(1,l.IOptionsService)],c)},9820(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.LinkProviderService=void 0;const s=i(4812);class r extends s.Disposable{constructor(){super(),this.linkProviders=[],this._register((0,s.toDisposable)(()=>this.linkProviders.length=0))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{const t=this.linkProviders.indexOf(e);-1!==t&&this.linkProviders.splice(t,1)}}}}t.LinkProviderService=r},8294(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.MouseCoordsService=void 0;const o=i(4159),n=i(5251),a=i(7098);let h=class{constructor(e,t){this._charSizeService=e,this._renderService=t}getCoords(e,t,i,s,r){return(0,n.getCoords)((0,o.getWindow)(t),e,t,i,s,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,r)}getMouseReportCoords(e,t){const i=(0,n.getCoordsRelativeToElement)((0,o.getWindow)(t),e,t);if(this._charSizeService.hasValidSize)return i[0]=Math.min(Math.max(i[0],0),this._renderService.dimensions.css.canvas.width-1),i[1]=Math.min(Math.max(i[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(i[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(i[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(i[0]),y:Math.floor(i[1])}}};t.MouseCoordsService=h,t.MouseCoordsService=h=s([r(0,a.ICharSizeService),r(1,a.IRenderService)],h)},9784(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.AltMouseCursorController=t.MouseService=void 0;const o=i(4159),n=i(6501),a=i(4812),h=i(7098),l=i(2650);let c=class{constructor(e,t,i,s,r,o,n,a,h){this._renderService=e,this._mouseCoordsService=t,this._mouseStateService=i,this._coreService=s,this._bufferService=r,this._optionsService=o,this._selectionService=n,this._logService=a,this._coreBrowserService=h,this._lastEvent=null,this._wheelPartialScroll=0,this._touchScrollAccumulator=0}bindMouse(e,t,i){const{element:s,document:r}=e,n={mouseup:null,wheel:null,mousedrag:null,mousemove:null},h={target:e,focus:i,requestedEvents:n},c={mouseup:e=>this._handleMouseUp(h,e),wheel:e=>this._handleWheel(h,e),mousedrag:e=>this._handleMouseDrag(h,e),mousemove:e=>this._handleMouseMove(h,e)};this._altMouseCursor=new d(s,r,()=>this._mouseStateService.areMouseEventsActive&&!!this._optionsService.rawOptions.mouseEventsRequireAlt),t(this._altMouseCursor),t(this._mouseStateService.onProtocolChange(e=>{this._handleProtocolChange(h,c,e)})),t(this._optionsService.onSpecificOptionChange("mouseEventsRequireAlt",()=>{this._syncMouseModeState(s),this._altMouseCursor?.sync()})),this._mouseStateService.activeProtocol=this._mouseStateService.activeProtocol,t((0,a.toDisposable)(()=>{n.mouseup&&r.removeEventListener("mouseup",n.mouseup),n.mousedrag&&r.removeEventListener("mousemove",n.mousedrag)})),t((0,o.addDisposableListener)(s,"mousedown",e=>this._handleMouseDown(h,e))),t((0,o.addDisposableListener)(s,"wheel",e=>this._handlePassiveWheel(h,e),{passive:!1})),t(l.Gesture.addTarget(e.screenElement)),t((0,o.addDisposableListener)(e.screenElement,l.EventType.START,()=>this._handleTouchStart())),t((0,o.addDisposableListener)(e.screenElement,l.EventType.CHANGE,e=>this._handleTouchChange(h,e)))}_sendEvent(e,t){const i=this._mouseCoordsService.getMouseReportCoords(t,e.target.screenElement);if(!i)return!1;let s,r;switch(t.overrideType||t.type){case"mousemove":r=32,void 0===t.buttons?(s=3,void 0!==t.button&&(s=t.button<3?t.button:3)):s=1&t.buttons?0:4&t.buttons?1:2&t.buttons?2:3;break;case"mouseup":r=0,s=t.button<3?t.button:3;break;case"mousedown":r=1,s=t.button<3?t.button:3;break;case"wheel":if(!this._mouseStateService.allowCustomWheelEvent(t))return!1;const e=t.deltaY;if(0===e)return!1;if(0===this._consumeWheelEvent(t,this._renderService?.dimensions?.device?.cell?.height,this._coreBrowserService?.dpr))return!1;r=e<0?0:1,s=4;break;default:return!1}if(void 0===r||void 0===s||s>4)return!1;if(4!==s&&this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive&&!t.altKey)return!1;const o=4!==s&&this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive;return this._triggerMouseEvent({col:i.col,row:i.row,x:i.x,y:i.y,button:s,action:r,ctrl:t.ctrlKey,alt:!o&&t.altKey,shift:t.shiftKey})}_handleMouseUp(e,t){this._sendEvent(e,t),t.buttons||(e.requestedEvents.mouseup&&e.target.document.removeEventListener("mouseup",e.requestedEvents.mouseup),e.requestedEvents.mousedrag&&e.target.document.removeEventListener("mousemove",e.requestedEvents.mousedrag))}_handleWheel(e,t){return this._sendEvent(e,t),t.preventDefault(),t.stopPropagation(),!1}_handleMouseDrag(e,t){t.buttons&&this._sendEvent(e,t)}_handleMouseMove(e,t){t.buttons||this._sendEvent(e,t)}_handleMouseDown(e,t){t.preventDefault(),e.focus(),this._mouseStateService.areMouseEventsActive&&!this._selectionService.shouldForceSelection(t)&&(this._sendEvent(e,t),e.requestedEvents.mouseup&&e.target.document.addEventListener("mouseup",e.requestedEvents.mouseup),e.requestedEvents.mousedrag&&e.target.document.addEventListener("mousemove",e.requestedEvents.mousedrag))}_handlePassiveWheel(e,t){if(!e.requestedEvents.wheel){if(!this._mouseStateService.allowCustomWheelEvent(t))return!1;if(!this._bufferService.buffer.hasScrollback){if(0===t.deltaY)return!1;if(0===this._consumeWheelEvent(t,this._renderService?.dimensions?.device?.cell?.height,this._coreBrowserService?.dpr))return t.preventDefault(),t.stopPropagation(),!1;const e=""+(this._coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(t.deltaY<0?"A":"B");return this._coreService.triggerDataEvent(e,!0),t.preventDefault(),t.stopPropagation(),!1}}}_handleTouchStart(){this._touchScrollAccumulator=0}_handleTouchChange(e,t){t.preventDefault(),t.stopPropagation(),e.requestedEvents.wheel?this._handleTouchScrollAsWheel(e,t):this._bufferService.buffer.hasScrollback?e.target.handleTouchScroll?.(t.translationY):this._handleTouchScrollAsKeys(t)}_handleTouchScrollAsKeys(e){const t=this._renderService?.dimensions.css.cell.height;if(!t)return;this._touchScrollAccumulator-=e.translationY;const i=Math.trunc(this._touchScrollAccumulator/t);if(0===i)return;this._touchScrollAccumulator-=i*t;const s=""+(this._coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(i<0?"A":"B");for(let e=0;e0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(r*=this._bufferService.rows),r}_triggerMouseEvent(e){if(e.col<0||e.col>=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows)return!1;if(4===e.button&&32===e.action)return!1;if(3===e.button&&32!==e.action)return!1;if(4!==e.button&&(2===e.action||3===e.action))return!1;if(e.col++,e.row++,32===e.action&&this._lastEvent&&this._equalEvents(this._lastEvent,e,this._mouseStateService.isPixelEncoding))return!1;if(!this._mouseStateService.restrictMouseEvent(e))return!1;const t=this._mouseStateService.encodeMouseEvent(e);return t&&(this._mouseStateService.isDefaultEncoding?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0}_explainEvents(e){return{down:!!(1&e),up:!!(2&e),drag:!!(4&e),move:!!(8&e),wheel:!!(16&e)}}_equalEvents(e,t,i){if(i){if(e.x!==t.x)return!1;if(e.y!==t.y)return!1}else{if(e.col!==t.col)return!1;if(e.row!==t.row)return!1}return e.button===t.button&&e.action===t.action&&e.ctrl===t.ctrl&&e.alt===t.alt&&e.shift===t.shift}};t.MouseService=c,t.MouseService=c=s([r(0,h.IRenderService),r(1,h.IMouseCoordsService),r(2,n.IMouseStateService),r(3,n.ICoreService),r(4,n.IBufferService),r(5,n.IOptionsService),r(6,h.ISelectionService),r(7,n.ILogService),r(8,h.ICoreBrowserService)],c);class d{constructor(e,t,i){this._element=e,this._document=t,this._isActive=i,this._listeners=new a.MutableDisposable}dispose(){this._listeners.dispose()}sync(){if(this._listeners.clear(),!this._isActive())return;const e=new a.DisposableStore,t=e=>this.syncFromModifier(e);e.add((0,o.addDisposableListener)(this._document,"keydown",t)),e.add((0,o.addDisposableListener)(this._document,"keyup",t)),e.add((0,o.addDisposableListener)(this._element,"mousemove",t));const i=this._element.ownerDocument?.defaultView;i&&e.add((0,o.addDisposableListener)(i,"blur",()=>{this._isActive()&&this.resetClass()})),this._listeners.value=e}resetClass(){this._updateClass(!1)}syncFromModifier(e){this._isActive()&&this._updateClass(e.getModifierState("Alt"))}_updateClass(e){e?this._element.classList.add("enable-mouse-events"):this._element.classList.remove("enable-mouse-events")}}t.AltMouseCursorController=d},5783(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.RenderService=void 0;const o=i(4852),n=i(7098),a=i(4812),h=i(6168),l=i(6501),c=i(8636);let d=class extends a.Disposable{get dimensions(){return this._renderer.value.dimensions}constructor(e,t,i,s,r,n,l,d,u,f){super(),this._rowCount=e,this._optionsService=i,this._logService=s,this._charSizeService=r,this._coreService=n,this._coreBrowserService=u,this._renderer=this._register(new a.MutableDisposable),this._observerDisposable=this._register(new a.MutableDisposable),this._isPaused=!1,this._needsFullRefresh=!1,this._isNextRenderRedrawOnly=!0,this._needsSelectionRefresh=!1,this._canvasWidth=0,this._canvasHeight=0,this._selectionState={start:void 0,end:void 0,columnSelectMode:!1},this._onDimensionsChange=this._register(new c.Emitter),this.onDimensionsChange=this._onDimensionsChange.event,this._onRenderedViewportChange=this._register(new c.Emitter),this.onRenderedViewportChange=this._onRenderedViewportChange.event,this._onRender=this._register(new c.Emitter),this.onRender=this._onRender.event,this._onRefreshRequest=this._register(new c.Emitter),this.onRefreshRequest=this._onRefreshRequest.event,this._pausedResizeTask=this._register(new h.DebouncedIdleTask(this._logService)),this._renderDebouncer=new o.RenderDebouncer((e,t)=>this._renderRows(e,t),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new _(this._coreBrowserService,this._coreService,()=>this._fullRefresh()),this._register((0,a.toDisposable)(()=>this._syncOutputHandler.dispose())),this._register(this._coreBrowserService.onDprChange(()=>this.handleDevicePixelRatioChange())),this._register(d.onResize(()=>this._fullRefresh())),this._register(d.buffers.onBufferActivate(()=>this._renderer.value?.clear())),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this._register(l.onDecorationRegistered(()=>this._fullRefresh())),this._register(l.onDecorationRemoved(()=>this._fullRefresh())),this._register(this._optionsService.onMultipleOptionChange(["drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio","rescaleOverlappingGlyphs"],()=>{this.clear(),this.handleResize(d.cols,d.rows),this._fullRefresh()})),this._register(this._optionsService.onMultipleOptionChange(["cursorBlink","cursorStyle"],()=>this.refreshRows(d.buffer.y,d.buffer.y,void 0,!0))),this._register(f.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(this._coreBrowserService.window,t),this._register(this._coreBrowserService.onWindowChange(e=>this._registerIntersectionObserver(e,t)))}_registerIntersectionObserver(e,t){if("IntersectionObserver"in e){const i=new e.IntersectionObserver(e=>this._handleIntersectionChange(e[e.length-1]),{threshold:0});this._observerDisposable.value=(0,a.toDisposable)(()=>{this._intersectionObserver?.disconnect(),this._intersectionObserver=void 0}),this._intersectionObserver=i,i.observe(t)}}_handleIntersectionChange(e){this._isPaused=void 0===e.isIntersecting?0===e.intersectionRatio:!e.isIntersecting,this._renderer.value?.handleViewportVisibilityChange?.(!this._isPaused),this._isPaused||this._charSizeService.hasValidSize||this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,i=!1,s=!1){if(this._isPaused)return void(this._needsFullRefresh=!0);if(this._coreService.decPrivateModes.synchronizedOutput)return void this._syncOutputHandler.bufferRows(e,t);const r=this._syncOutputHandler.flush();r&&(e=Math.min(e,r.start),t=Math.max(t,r.end)),s||(this._isNextRenderRedrawOnly=!1),i?this._renderRows(e,t):this._renderDebouncer.refresh(e,t,this._rowCount)}_renderRows(e,t){this._renderer.value&&(this._coreService.decPrivateModes.synchronizedOutput?this._syncOutputHandler.bufferRows(e,t):(e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0))}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value&&(this._renderer.value.onRequestRedraw(e=>this.refreshRows(e.start,e.end,e.sync,!0)),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){this._renderer.value&&(this._renderer.value.clearTextureAtlas?.(),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>this._renderer.value?.handleResize(e,t)):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,t,i){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=i,this._renderer.value?.handleSelectionChanged(e,t,i)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};t.RenderService=d,t.RenderService=d=s([r(2,l.IOptionsService),r(3,l.ILogService),r(4,n.ICharSizeService),r(5,l.ICoreService),r(6,l.IDecorationService),r(7,l.IBufferService),r(8,n.ICoreBrowserService),r(9,n.IThemeService)],d);class _{constructor(e,t,i){this._coreBrowserService=e,this._coreService=t,this._onTimeout=i,this._start=0,this._end=0,this._isBuffering=!1}bufferRows(e,t){this._isBuffering?(this._start=Math.min(this._start,e),this._end=Math.max(this._end,t)):(this._start=e,this._end=t,this._isBuffering=!0),this._timeout??=this._coreBrowserService.window.setTimeout(()=>{this._timeout=void 0,this._coreService.decPrivateModes.synchronizedOutput=!1,this._onTimeout()},1e3)}flush(){if(void 0!==this._timeout&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0),!this._isBuffering)return;const e={start:this._start,end:this._end};return this._isBuffering=!1,e}dispose(){void 0!==this._timeout&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}}},2079(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},a=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._handleMouseMove(e),this._mouseUpListener=e=>this._handleMouseUp(e),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener.value=this._bufferService.buffer.lines.onTrim(e=>this._handleTrim(e)),this._register(this._bufferService.buffers.onBufferActivate(e=>this._handleBufferActivate(e))),this.enable(),this._model=new d.SelectionModel(this._bufferService),this._activeSelectionMode=0,this._register((0,u.toDisposable)(()=>{this._removeMouseDownListeners()})),this._register(this._bufferService.onResize(e=>{e.rowsChanged&&this.clearSelection()}))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!(!e||!t||e[0]===t[0]&&e[1]===t[1])}get selectionText(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";const i=this._bufferService.buffer,s=[];if(3===this._activeSelectionMode){if(e[0]===t[0])return"";const r=e[0]e.replace(b," ")).join(f.isWindows?"\r\n":"\n")}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh())),f.isLinux&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:3===this._activeSelectionMode})}_isClickInSelection(e){const t=this._getMouseBufferCoords(e),i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!!(i&&s&&t)&&this._areCoordsInSelection(t,i,s)}isCellInSelection(e,t){const i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!(!i||!s)&&this._areCoordsInSelection([e,t],i,s)}_areCoordsInSelection(e,t,i){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){const i=this._linkifier.currentLink?.link?.range;if(i)return this._model.selectionStart=[i.start.x-1,i.start.y-1],this._model.selectionStartLength=(0,p.getRangeLength)(i,this._bufferService.cols),this._model.selectionEnd=void 0,!0;const s=this._getMouseBufferCoords(e);return!!s&&(this._selectWordAt(s,t),this._model.selectionEnd=void 0,!0)}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){const t=this._mouseCoordsService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t}_getMouseEventScrollAmount(e){let t=(0,l.getCoordsRelativeToElement)(this._coreBrowserService.window,e,this._screenElement)[1];const i=this._renderService.dimensions.css.canvas.height;return t>=0&&t<=i?0:(t>i&&(t-=i),t=Math.min(Math.max(t,-50),50),t/=50,t/Math.abs(t)+Math.round(14*t))}shouldForceSelection(e){return this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive?!e.altKey:f.isMac?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}handleMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,!(2===e.button&&this.hasSelection||0!==e.button||this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive&&e.altKey)){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._handleIncrementalClick(e):1===e.detail?this._handleSingleClick(e):2===e.detail?this._handleDoubleClick(e):3===e.detail&&this._handleTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval(()=>this._dragScroll(),50)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_handleSingleClick(e){const t=this.hasSelection;if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),!this._model.selectionStart)return;this._model.selectionEnd=void 0,t&&this._fireOnSelectionChange(this._model.finalSelectionStart,this._model.finalSelectionEnd,!1);const i=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);i&&i.length!==this._model.selectionStart[0]&&0===i.hasWidth(this._model.selectionStart[0])&&this._model.selectionStart[0]++}_handleDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(e){const t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))}shouldColumnSelect(e){return(!this._optionsService.rawOptions.mouseEventsRequireAlt||!this._mouseStateService.areMouseEventsActive)&&e.altKey&&!(f.isMac&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;const t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),!this._model.selectionEnd)return void this.refresh(!0);2===this._activeSelectionMode?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));const i=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows-1,e.lines.length-1)):(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}}_handleMouseUp(e){const t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&t<500&&e.altKey&&this._optionsService.rawOptions.altClickMovesCursor){if(this._bufferService.buffer.ybase===this._bufferService.buffer.ydisp){const t=this._mouseCoordsService.getCoords(e,this._element,this._bufferService.cols,this._bufferService.rows,!1);if(t&&void 0!==t[0]&&void 0!==t[1]){const e=(0,c.moveToCellSequence)(t[0]-1,t[1]-1,this._bufferService,this._coreService.decPrivateModes.applicationCursorKeys);this._coreService.triggerDataEvent(e,!0)}}}else this._fireEventIfSelectionChanged()}_fireEventIfSelectionChanged(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd,i=!(!e||!t||e[0]===t[0]&&e[1]===t[1]);i?e&&t&&(this._oldSelectionStart&&this._oldSelectionEnd&&e[0]===this._oldSelectionStart[0]&&e[1]===this._oldSelectionStart[1]&&t[0]===this._oldSelectionEnd[0]&&t[1]===this._oldSelectionEnd[1]||this._fireOnSelectionChange(e,t,i)):this._oldHasSelection&&this._fireOnSelectionChange(e,t,i)}_fireOnSelectionChange(e,t,i){this._oldSelectionStart=e,this._oldSelectionEnd=t,this._oldHasSelection=i,this._onSelectionChange.fire()}_handleBufferActivate(e){this.clearSelection(),this._trimListener.value=e.activeBuffer.lines.onTrim(e=>this._handleTrim(e))}_convertViewportColToCharacterIndex(e,t){let i=t;for(let s=0;t>=s;s++){const r=e.loadCell(s,this._workCell).getChars().length;0===this._workCell.getWidth()?i--:r>1&&t!==s&&(i+=r-1)}return i}setSelection(e,t,i){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=i,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,t,i=!0,s=!0){if(e[0]>=this._bufferService.cols)return;const r=this._bufferService.buffer,o=r.lines.get(e[1]);if(!o)return;const n=r.translateBufferLineToString(e[1],!1);let a=this._convertViewportColToCharacterIndex(o,e[0]),h=a;const l=e[0]-a;let c=0,d=0,_=0,u=0;if(" "===n.charAt(a)){for(;a>0&&" "===n.charAt(a-1);)a--;for(;h1&&(u+=s-1,h+=s-1);t>0&&a>0&&!this._isCharWordSeparator(o.loadCell(t-1,this._workCell));){o.loadCell(t-1,this._workCell);const e=this._workCell.getChars().length;0===this._workCell.getWidth()?(c++,t--):e>1&&(_+=e-1,a-=e-1),a--,t--}for(;i1&&(u+=e-1,h+=e-1),h++,i++}}h++;let f=a+l-c+_,p=Math.min(this._bufferService.cols,h-a+c+d-_-u);if(t||""!==n.slice(a,h).trim()){if(i&&0===f&&32!==o.getCodePoint(0)){const t=r.lines.get(e[1]-1);if(t&&o.isWrapped&&32!==t.getCodePoint(this._bufferService.cols-1)){const t=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(t){const e=this._bufferService.cols-t.start;f-=e,p+=e}}}if(s&&f+p===this._bufferService.cols&&32!==o.getCodePoint(this._bufferService.cols-1)){const t=r.lines.get(e[1]+1);if(t?.isWrapped&&32!==t.getCodePoint(0)){const t=this._getWordAt([0,e[1]+1],!1,!1,!0);t&&(p+=t.length)}}return{start:f,length:p}}}_selectWordAt(e,t){const i=this._getWordAt(e,t);if(i){for(;i.start<0;)i.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[i.start,e[1]],this._model.selectionStartLength=i.length}}_selectToWordAt(e){const t=this._getWordAt(e,!0);if(t){let i=e[1];for(;t.start<0;)t.start+=this._bufferService.cols,i--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,i++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,i]}}_isCharWordSeparator(e){return 0!==e.getWidth()&&this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){const t=this._bufferService.buffer.getWrappedRangeForLine(e),i={start:{x:0,y:t.first},end:{x:this._bufferService.cols-1,y:t.last}};this._model.selectionStart=[0,t.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=(0,p.getRangeLength)(i,this._bufferService.cols)}};t.SelectionService=w,t.SelectionService=w=n([h(3,g.IBufferService),h(4,g.ICoreService),h(5,_.IMouseCoordsService),h(6,g.IOptionsService),h(7,g.IMouseStateService),h(8,_.IRenderService),h(9,_.ICoreBrowserService)],w)},7098(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.IKeyboardService=t.ILinkProviderService=t.IThemeService=t.ICharacterJoinerService=t.ISelectionService=t.IRenderService=t.IMouseService=t.IMouseCoordsService=t.ICoreBrowserService=t.ICharSizeService=void 0;const s=i(6201);t.ICharSizeService=(0,s.createDecorator)("CharSizeService"),t.ICoreBrowserService=(0,s.createDecorator)("CoreBrowserService"),t.IMouseCoordsService=(0,s.createDecorator)("MouseCoordsService"),t.IMouseService=(0,s.createDecorator)("MouseService"),t.IRenderService=(0,s.createDecorator)("RenderService"),t.ISelectionService=(0,s.createDecorator)("SelectionService"),t.ICharacterJoinerService=(0,s.createDecorator)("CharacterJoinerService"),t.IThemeService=(0,s.createDecorator)("ThemeService"),t.ILinkProviderService=(0,s.createDecorator)("LinkProviderService"),t.IKeyboardService=(0,s.createDecorator)("KeyboardService")},9078(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.ThemeService=void 0;const o=i(7174),n=i(9302),a=i(4103),h=i(4812),l=i(6501),c=i(8636),d=a.css.toColor("#ffffff"),_=a.css.toColor("#000000"),u=a.css.toColor("#ffffff"),f=_,p={css:"rgba(255, 255, 255, 0.3)",rgba:4294967117},v=d;let g=class extends h.Disposable{get colors(){return this._colors}constructor(e){super(),this._optionsService=e,this._contrastCache=new o.ColorContrastCache,this._halfContrastCache=new o.ColorContrastCache,this._onChangeColors=this._register(new c.Emitter),this.onChangeColors=this._onChangeColors.event,this._colors={foreground:d,background:_,cursor:u,cursorAccent:f,selectionForeground:void 0,selectionBackgroundTransparent:p,selectionBackgroundOpaque:a.color.blend(_,p),selectionInactiveBackgroundTransparent:p,selectionInactiveBackgroundOpaque:a.color.blend(_,p),scrollbarSliderBackground:a.color.opacity(d,.2),scrollbarSliderHoverBackground:a.color.opacity(d,.4),scrollbarSliderActiveBackground:a.color.opacity(d,.5),overviewRulerBorder:d,ansi:n.DEFAULT_ANSI_COLORS.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this._register(this._optionsService.onSpecificOptionChange("minimumContrastRatio",()=>this._contrastCache.clear())),this._register(this._optionsService.onSpecificOptionChange("theme",()=>this._setTheme(this._optionsService.rawOptions.theme)))}_setTheme(e={}){const t=this._colors;if(t.foreground=m(e.foreground,d),t.background=m(e.background,_),t.cursor=a.color.blend(t.background,m(e.cursor,u)),t.cursorAccent=a.color.blend(t.background,m(e.cursorAccent,f)),t.selectionBackgroundTransparent=m(e.selectionBackground,p),t.selectionBackgroundOpaque=a.color.blend(t.background,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundTransparent=m(e.selectionInactiveBackground,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundOpaque=a.color.blend(t.background,t.selectionInactiveBackgroundTransparent),t.selectionForeground=e.selectionForeground?m(e.selectionForeground,a.NULL_COLOR):void 0,t.selectionForeground===a.NULL_COLOR&&(t.selectionForeground=void 0),a.color.isOpaque(t.selectionBackgroundTransparent)){const e=.3;t.selectionBackgroundTransparent=a.color.opacity(t.selectionBackgroundTransparent,e)}if(a.color.isOpaque(t.selectionInactiveBackgroundTransparent)){const e=.3;t.selectionInactiveBackgroundTransparent=a.color.opacity(t.selectionInactiveBackgroundTransparent,e)}if(t.scrollbarSliderBackground=m(e.scrollbarSliderBackground,a.color.opacity(t.foreground,.2)),t.scrollbarSliderHoverBackground=m(e.scrollbarSliderHoverBackground,a.color.opacity(t.foreground,.4)),t.scrollbarSliderActiveBackground=m(e.scrollbarSliderActiveBackground,a.color.opacity(t.foreground,.5)),t.overviewRulerBorder=m(e.overviewRulerBorder,v),t.ansi=n.DEFAULT_ANSI_COLORS.slice(),t.ansi[0]=m(e.black,n.DEFAULT_ANSI_COLORS[0]),t.ansi[1]=m(e.red,n.DEFAULT_ANSI_COLORS[1]),t.ansi[2]=m(e.green,n.DEFAULT_ANSI_COLORS[2]),t.ansi[3]=m(e.yellow,n.DEFAULT_ANSI_COLORS[3]),t.ansi[4]=m(e.blue,n.DEFAULT_ANSI_COLORS[4]),t.ansi[5]=m(e.magenta,n.DEFAULT_ANSI_COLORS[5]),t.ansi[6]=m(e.cyan,n.DEFAULT_ANSI_COLORS[6]),t.ansi[7]=m(e.white,n.DEFAULT_ANSI_COLORS[7]),t.ansi[8]=m(e.brightBlack,n.DEFAULT_ANSI_COLORS[8]),t.ansi[9]=m(e.brightRed,n.DEFAULT_ANSI_COLORS[9]),t.ansi[10]=m(e.brightGreen,n.DEFAULT_ANSI_COLORS[10]),t.ansi[11]=m(e.brightYellow,n.DEFAULT_ANSI_COLORS[11]),t.ansi[12]=m(e.brightBlue,n.DEFAULT_ANSI_COLORS[12]),t.ansi[13]=m(e.brightMagenta,n.DEFAULT_ANSI_COLORS[13]),t.ansi[14]=m(e.brightCyan,n.DEFAULT_ANSI_COLORS[14]),t.ansi[15]=m(e.brightWhite,n.DEFAULT_ANSI_COLORS[15]),e.extendedAnsi){const i=Math.min(t.ansi.length-16,e.extendedAnsi.length);for(let s=0;ssetTimeout(t,e))},t.disposableTimeout=function(e,t=0,i){const r=setTimeout(()=>{e(),i&&o.dispose()},t),o=(0,s.toDisposable)(()=>{clearTimeout(r)});return i?.add(o),o};const s=i(4812);t.TimeoutTimer=class{constructor(){this._token=-1,this._isDisposed=!1}dispose(){this.cancel(),this._isDisposed=!0}cancel(){-1!==this._token&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(e,t){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout(()=>{this._token=-1,e()},t)}setIfNotSet(e,t){if(this._isDisposed)throw new Error("Calling setIfNotSet on a disposed TimeoutTimer");-1===this._token&&(this._token=setTimeout(()=>{this._token=-1,e()},t))}},t.MicrotaskTimer=class{constructor(){this._isScheduled=!1,this._isDisposed=!1}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._isScheduled=!1}set(e){if(this._isDisposed)throw new Error("Calling set on a disposed MicrotaskTimer");this._isScheduled||(this._isScheduled=!0,queueMicrotask(()=>{this._isScheduled&&(this._isScheduled=!1,e())}))}},t.IntervalTimer=class{constructor(){this._isDisposed=!1}cancel(){this._disposable?.dispose(),this._disposable=void 0}cancelAndSet(e,t,i=globalThis){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed IntervalTimer");this.cancel();const s=i.setInterval(()=>{e()},t);this._disposable={dispose:()=>{i.clearInterval(s),this._disposable=void 0}}}dispose(){this.cancel(),this._isDisposed=!0}}},5639(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.CircularList=void 0;const s=i(4812),r=i(8636);class o extends s.Disposable{constructor(e){super(),this._maxLength=e,this.onDeleteEmitter=this._register(new r.Emitter),this.onDelete=this.onDeleteEmitter.event,this.onInsertEmitter=this._register(new r.Emitter),this.onInsert=this.onInsertEmitter.event,this.onTrimEmitter=this._register(new r.Emitter),this.onTrim=this.onTrimEmitter.event,this._array=new Array(this._maxLength),this._startIndex=0,this._length=0}get maxLength(){return this._maxLength}set maxLength(e){if(this._maxLength===e)return;const t=new Array(e);for(let i=0;ithis._length)for(let t=this._length;t=e;t--)this._array[this._getCyclicIndex(t+i.length)]=this._array[this._getCyclicIndex(t)];for(let t=0;tthis._maxLength){const e=this._length+i.length-this._maxLength;this._startIndex+=e,this._length=this._maxLength,this.onTrimEmitter.fire(e)}else this._length+=i.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,t,i){if(!(t<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+i<0)throw new Error("Cannot shift elements in list beyond index 0");if(i>0){for(let s=t-1;s>=0;s--)this.set(e+s+i,this.get(e+s));const s=e+t+i-this._length;if(s>0)for(this._length+=s;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let s=0;s>>0},e.toColor=function(t,i,s,r){return{css:e.toCss(t,i,s,r),rgba:e.toRgba(t,i,s,r)}}}(n||(t.channels=n={})),function(e){function t(e,t){return o=Math.round(255*t),[i,s,r]=c.toChannels(e.rgba),{css:n.toCss(i,s,r,o),rgba:n.toRgba(i,s,r,o)}}e.blend=function(e,t){if(o=(255&t.rgba)/255,1===o)return{css:t.css,rgba:t.rgba};const a=t.rgba>>24&255,h=t.rgba>>16&255,l=t.rgba>>8&255,c=e.rgba>>24&255,d=e.rgba>>16&255,_=e.rgba>>8&255;return i=c+Math.round((a-c)*o),s=d+Math.round((h-d)*o),r=_+Math.round((l-_)*o),{css:n.toCss(i,s,r),rgba:n.toRgba(i,s,r)}},e.isOpaque=function(e){return!(255&~e.rgba)},e.ensureContrastRatio=function(e,t,i){const s=c.ensureContrastRatio(e.rgba,t.rgba,i);if(s)return n.toColor(s>>24&255,s>>16&255,s>>8&255)},e.opaque=function(e){const t=(255|e.rgba)>>>0;return[i,s,r]=c.toChannels(t),{css:n.toCss(i,s,r),rgba:t}},e.opacity=t,e.multiplyOpacity=function(e,i){return o=255&e.rgba,t(e,o*i/255)},e.toColorRGB=function(e){return[e.rgba>>24&255,e.rgba>>16&255,e.rgba>>8&255]}}(a||(t.color=a={})),function(e){let t,a;try{const e=document.createElement("canvas");e.width=1,e.height=1;const i=e.getContext("2d",{willReadFrequently:!0});i&&(t=i,t.globalCompositeOperation="copy",a=t.createLinearGradient(0,0,1,1))}catch{}e.toColor=function(e){if(e.match(/#[\da-f]{3,8}/i))switch(e.length){case 4:return i=parseInt(e.slice(1,2).repeat(2),16),s=parseInt(e.slice(2,3).repeat(2),16),r=parseInt(e.slice(3,4).repeat(2),16),n.toColor(i,s,r);case 5:return i=parseInt(e.slice(1,2).repeat(2),16),s=parseInt(e.slice(2,3).repeat(2),16),r=parseInt(e.slice(3,4).repeat(2),16),o=parseInt(e.slice(4,5).repeat(2),16),n.toColor(i,s,r,o);case 7:return{css:e,rgba:(parseInt(e.slice(1),16)<<8|255)>>>0};case 9:return{css:e,rgba:parseInt(e.slice(1),16)>>>0}}const h=e.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(h)return i=parseInt(h[1],10),s=parseInt(h[2],10),r=parseInt(h[3],10),o=Math.round(255*(void 0===h[5]?1:parseFloat(h[5]))),n.toColor(i,s,r,o);if("transparent"===e)return{css:"transparent",rgba:0};if(!t||!a)throw new Error("css.toColor: Unsupported css format");if(t.fillStyle=a,t.fillStyle=e,"string"!=typeof t.fillStyle)throw new Error("css.toColor: Unsupported css format");if(t.fillRect(0,0,1,1),[i,s,r,o]=t.getImageData(0,0,1,1).data,255!==o)throw new Error("css.toColor: Unsupported css format");return{rgba:n.toRgba(i,s,r,o),css:e}}}(h||(t.css=h={})),function(e){function t(e,t,i){const s=e/255,r=t/255,o=i/255;return.2126*(s<=.03928?s/12.92:Math.pow((s+.055)/1.055,2.4))+.7152*(r<=.03928?r/12.92:Math.pow((r+.055)/1.055,2.4))+.0722*(o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4))}e.relativeLuminance=function(e){return t(e>>16&255,e>>8&255,255&e)},e.relativeLuminance2=t}(l||(t.rgb=l={})),function(e){function t(e,t,i){const s=e>>24&255,r=e>>16&255,o=e>>8&255;let n=t>>24&255,a=t>>16&255,h=t>>8&255,c=_(l.relativeLuminance2(n,a,h),l.relativeLuminance2(s,r,o));for(;c0||a>0||h>0);)n-=Math.max(0,Math.ceil(.1*n)),a-=Math.max(0,Math.ceil(.1*a)),h-=Math.max(0,Math.ceil(.1*h)),c=_(l.relativeLuminance2(n,a,h),l.relativeLuminance2(s,r,o));return(n<<24|a<<16|h<<8|255)>>>0}function a(e,t,i){const s=e>>24&255,r=e>>16&255,o=e>>8&255;let n=t>>24&255,a=t>>16&255,h=t>>8&255,c=_(l.relativeLuminance2(n,a,h),l.relativeLuminance2(s,r,o));for(;c>>0}e.blend=function(e,t){if(o=(255&t)/255,1===o)return t;const a=t>>24&255,h=t>>16&255,l=t>>8&255,c=e>>24&255,d=e>>16&255,_=e>>8&255;return i=c+Math.round((a-c)*o),s=d+Math.round((h-d)*o),r=_+Math.round((l-_)*o),n.toRgba(i,s,r)},e.ensureContrastRatio=function(e,i,s){const r=l.relativeLuminance(e>>8),o=l.relativeLuminance(i>>8);if(_(r,o)>8));if(n_(r,l.relativeLuminance(t>>8))?o:t}return o}const n=a(e,i,s),h=_(r,l.relativeLuminance(n>>8));if(h_(r,l.relativeLuminance(o>>8))?n:o}return n}},e.reduceLuminance=t,e.increaseLuminance=a,e.toChannels=function(e){return[e>>24&255,e>>16&255,e>>8&255,255&e]}}(c||(t.rgba=c={}))},5777(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.CoreTerminal=void 0;const s=i(6501),r=i(6025),o=i(7276),n=i(9640),a=i(56),h=i(4071),l=i(6478),c=i(7428),d=i(6415),_=i(5746),u=i(5882),f=i(2486),p=i(3562),v=i(8811),g=i(8636),m=i(4812);let S=!1;class b extends m.Disposable{get onScroll(){return this._onScrollApi||(this._onScrollApi=this._register(new g.Emitter),this._onScroll.event(e=>{this._onScrollApi?.fire(e.position)})),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(e){for(const t in e)this.optionsService.options[t]=e[t]}constructor(e){super(),this._windowsWrappingHeuristics=this._register(new m.MutableDisposable),this._onBinary=this._register(new g.Emitter),this.onBinary=this._onBinary.event,this._onData=this._register(new g.Emitter),this.onData=this._onData.event,this._onLineFeed=this._register(new g.Emitter),this.onLineFeed=this._onLineFeed.event,this._onRender=this._register(new g.Emitter),this.onRender=this._onRender.event,this._onResize=this._register(new g.Emitter),this.onResize=this._onResize.event,this._onWriteParsed=this._register(new g.Emitter),this.onWriteParsed=this._onWriteParsed.event,this._onScroll=this._register(new g.Emitter),this._instantiationService=new r.InstantiationService,this.optionsService=this._register(new a.OptionsService(e)),this._instantiationService.setService(s.IOptionsService,this.optionsService),this._logService=this._register(this._instantiationService.createInstance(o.LogService)),this._instantiationService.setService(s.ILogService,this._logService),this._bufferService=this._register(this._instantiationService.createInstance(n.BufferService)),this._instantiationService.setService(s.IBufferService,this._bufferService),this.coreService=this._register(this._instantiationService.createInstance(h.CoreService)),this._instantiationService.setService(s.ICoreService,this.coreService),this.mouseStateService=this._register(this._instantiationService.createInstance(l.MouseStateService)),this._instantiationService.setService(s.IMouseStateService,this.mouseStateService),this.unicodeService=this._register(this._instantiationService.createInstance(d.UnicodeService)),this.unicodeService.register(new c.UnicodeV6),this._instantiationService.setService(s.IUnicodeService,this.unicodeService),this._charsetService=this._instantiationService.createInstance(_.CharsetService),this._instantiationService.setService(s.ICharsetService,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(v.OscLinkService),this._instantiationService.setService(s.IOscLinkService,this._oscLinkService),this._inputHandler=this._register(new f.InputHandler(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.mouseStateService,this.unicodeService)),this._register(g.EventUtils.forward(this._inputHandler.onLineFeed,this._onLineFeed)),this._register(g.EventUtils.forward(this._bufferService.onResize,this._onResize)),this._register(g.EventUtils.forward(this.coreService.onData,this._onData)),this._register(g.EventUtils.forward(this.coreService.onBinary,this._onBinary)),this._register(this.coreService.onRequestScrollToBottom(()=>this.scrollToBottom(!0))),this._register(this.coreService.onUserInput(()=>this._writeBuffer.handleUserInput())),this._register(this.optionsService.onMultipleOptionChange(["windowsPty"],()=>this._handleWindowsPtyOptionChange())),this._register(this._bufferService.onScroll(()=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)})),this._writeBuffer=this._register(new p.WriteBuffer((e,t)=>this._inputHandler.parse(e,t))),this._register(g.EventUtils.forward(this._writeBuffer.onWriteParsed,this._onWriteParsed))}write(e,t){this._writeBuffer.write(e,t)}writeSync(e,t){this._logService.logLevel<=s.LogLevelEnum.WARN&&!S&&(this._logService.warn("writeSync is unreliable and will be removed soon."),S=!0),this._writeBuffer.writeSync(e,t)}input(e,t=!0){this.coreService.triggerDataEvent(e,t)}resize(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,2),t=Math.max(t,1),this._writeBuffer.flushSync(),this._bufferService.resize(e,t))}scroll(e,t=!1){this._bufferService.scroll(e,t)}scrollLines(e,t){this._bufferService.scrollLines(e,t)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){const t=e-this._bufferService.buffer.ydisp;0!==t&&this.scrollLines(t)}registerEscHandler(e,t){return this._inputHandler.registerEscHandler(e,t)}registerDcsHandler(e,t){return this._inputHandler.registerDcsHandler(e,t)}registerCsiHandler(e,t){return this._inputHandler.registerCsiHandler(e,t)}registerOscHandler(e,t){return this._inputHandler.registerOscHandler(e,t)}registerApcHandler(e,t){return this._inputHandler.registerApcHandler(e,t)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.mouseStateService.reset()}_handleWindowsPtyOptionChange(){let e=!1;const t=this.optionsService.rawOptions.windowsPty;t&&void 0!==t.backend&&void 0!==t.buildNumber&&(e=!!("conpty"===t.backend&&t.buildNumber<21376)),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){const e=[];e.push(this.onLineFeed(u.updateWindowsModeWrappedState.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:"H"},()=>((0,u.updateWindowsModeWrappedState)(this._bufferService),!1))),this._windowsWrappingHeuristics.value=(0,m.toDisposable)(()=>{for(const t of e)t.dispose()})}}}t.CoreTerminal=b},8636(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.EventUtils=t.Emitter=void 0;const s=i(4812);var r;t.Emitter=class{constructor(){this._listeners=[],this._disposed=!1}get event(){return this._event||(this._event=(e,t,i)=>{if(this._disposed)return(0,s.toDisposable)(()=>{});const r={fn:e,thisArgs:t};this._listeners.push(r);const o=(0,s.toDisposable)(()=>{const e=this._listeners.indexOf(r);-1!==e&&this._listeners.splice(e,1)});return i&&(Array.isArray(i)?i.push(o):i.add(o)),o}),this._event}fire(e){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{const{fn:t,thisArgs:i}=this._listeners[0];return void t.call(i,e)}default:{const t=this._listeners.slice();for(const{fn:i,thisArgs:s}of t)i.call(s,e)}}}dispose(){this._disposed||(this._disposed=!0,this._listeners.length=0)}},function(e){e.forward=function(e,t){return e(e=>t.fire(e))},e.map=function(e,t){return(i,s,r)=>e(e=>i.call(s,t(e)),void 0,r)},e.any=function(...e){return(t,i,r)=>{const o=new s.DisposableStore;for(const s of e)o.add(s(e=>t.call(i,e)));return r&&(Array.isArray(r)?r.push(o):r.add(o)),o}},e.runAndSubscribe=function(e,t,i){return t(i),e(e=>t(e))}}(r||(t.EventUtils=r={}))},2486(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.InputHandler=t.WindowsOptionsReportType=void 0,t.isValidColorIndex=L;const o=i(6760),n=i(6717),a=i(4812),h=i(726),l=i(6107),c=i(8938),d=i(3055),_=i(5451),u=i(6501),f=i(6415),p=i(1346),v=i(9823),g=i(2607),m=i(8693),S=i(8636),b=i(7804),w={"(":0,")":1,"*":2,"+":3,"-":1,".":2};function y(e,t){if(e>24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}var C;!function(e){e[e.GET_WIN_SIZE_PIXELS=0]="GET_WIN_SIZE_PIXELS",e[e.GET_CELL_SIZE_PIXELS=1]="GET_CELL_SIZE_PIXELS"}(C||(t.WindowsOptionsReportType=C={}));let k=0;class D extends a.Disposable{getAttrData(){return this._curAttrData}constructor(e,t,i,s,r,a,c,d,_=new n.EscapeSequenceParser){super(),this._bufferService=e,this._charsetService=t,this._coreService=i,this._logService=s,this._optionsService=r,this._oscLinkService=a,this._mouseStateService=c,this._unicodeService=d,this._parser=_,this._parseBuffer=new Uint32Array(4096),this._stringDecoder=new h.StringToUtf32,this._utf8Decoder=new h.Utf8ToUtf32,this._windowTitle="",this._iconName="",this._windowTitleStack=[],this._iconNameStack=[],this._curAttrData=l.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=l.DEFAULT_ATTR_DATA.clone(),this._onRequestBell=this._register(new S.Emitter),this.onRequestBell=this._onRequestBell.event,this._onRequestRefreshRows=this._register(new S.Emitter),this.onRequestRefreshRows=this._onRequestRefreshRows.event,this._onRequestReset=this._register(new S.Emitter),this.onRequestReset=this._onRequestReset.event,this._onRequestSendFocus=this._register(new S.Emitter),this.onRequestSendFocus=this._onRequestSendFocus.event,this._onRequestSyncScrollBar=this._register(new S.Emitter),this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event,this._onRequestWindowsOptionsReport=this._register(new S.Emitter),this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event,this._onA11yChar=this._register(new S.Emitter),this.onA11yChar=this._onA11yChar.event,this._onA11yTab=this._register(new S.Emitter),this.onA11yTab=this._onA11yTab.event,this._onCursorMove=this._register(new S.Emitter),this.onCursorMove=this._onCursorMove.event,this._onLineFeed=this._register(new S.Emitter),this.onLineFeed=this._onLineFeed.event,this._onScroll=this._register(new S.Emitter),this.onScroll=this._onScroll.event,this._onTitleChange=this._register(new S.Emitter),this.onTitleChange=this._onTitleChange.event,this._onColor=this._register(new S.Emitter),this.onColor=this._onColor.event,this._onRequestColorSchemeQuery=this._register(new S.Emitter),this.onRequestColorSchemeQuery=this._onRequestColorSchemeQuery.event,this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0},this._specialColors=[256,257,258],this._register(this._parser),this._dirtyRowTracker=new E(this._bufferService),this._activeBuffer=this._bufferService.buffer,this._register(this._bufferService.buffers.onBufferActivate(e=>this._activeBuffer=e.activeBuffer)),this._parser.setCsiHandlerFallback((e,t)=>{this._logService.debug("Unknown CSI code: ",{identifier:this._parser.identToString(e),params:t.toArray()})}),this._parser.setEscHandlerFallback(e=>{this._logService.debug("Unknown ESC code: ",{identifier:this._parser.identToString(e)})}),this._parser.setExecuteHandlerFallback(e=>{this._logService.debug("Unknown EXECUTE code: ",{code:e})}),this._parser.setOscHandlerFallback((e,t,i)=>{this._logService.debug("Unknown OSC code: ",{identifier:e,action:t,data:i})}),this._parser.setDcsHandlerFallback((e,t,i)=>{"HOOK"===t&&(i=i.toArray()),this._logService.debug("Unknown DCS code: ",{identifier:this._parser.identToString(e),action:t,payload:i})}),this._parser.setApcHandlerFallback((e,t,i)=>{this._logService.debug("Unknown APC code: ",{identifier:this._parser.identToString(e),action:t,payload:i})}),this._parser.setPrintHandler((e,t,i)=>this.print(e,t,i)),this._parser.registerCsiHandler({final:"@"},e=>this.insertChars(e)),this._parser.registerCsiHandler({intermediates:" ",final:"@"},e=>this.scrollLeft(e)),this._parser.registerCsiHandler({final:"A"},e=>this.cursorUp(e)),this._parser.registerCsiHandler({intermediates:" ",final:"A"},e=>this.scrollRight(e)),this._parser.registerCsiHandler({final:"B"},e=>this.cursorDown(e)),this._parser.registerCsiHandler({final:"C"},e=>this.cursorForward(e)),this._parser.registerCsiHandler({final:"D"},e=>this.cursorBackward(e)),this._parser.registerCsiHandler({final:"E"},e=>this.cursorNextLine(e)),this._parser.registerCsiHandler({final:"F"},e=>this.cursorPrecedingLine(e)),this._parser.registerCsiHandler({final:"G"},e=>this.cursorCharAbsolute(e)),this._parser.registerCsiHandler({final:"H"},e=>this.cursorPosition(e)),this._parser.registerCsiHandler({final:"I"},e=>this.cursorForwardTab(e)),this._parser.registerCsiHandler({final:"J"},e=>this.eraseInDisplay(e,!1)),this._parser.registerCsiHandler({prefix:"?",final:"J"},e=>this.eraseInDisplay(e,!0)),this._parser.registerCsiHandler({final:"K"},e=>this.eraseInLine(e,!1)),this._parser.registerCsiHandler({prefix:"?",final:"K"},e=>this.eraseInLine(e,!0)),this._parser.registerCsiHandler({final:"L"},e=>this.insertLines(e)),this._parser.registerCsiHandler({final:"M"},e=>this.deleteLines(e)),this._parser.registerCsiHandler({final:"P"},e=>this.deleteChars(e)),this._parser.registerCsiHandler({final:"S"},e=>this.scrollUp(e)),this._parser.registerCsiHandler({final:"T"},e=>this.scrollDown(e)),this._parser.registerCsiHandler({final:"X"},e=>this.eraseChars(e)),this._parser.registerCsiHandler({final:"Z"},e=>this.cursorBackwardTab(e)),this._parser.registerCsiHandler({final:"^"},e=>this.scrollDown(e)),this._parser.registerCsiHandler({final:"`"},e=>this.charPosAbsolute(e)),this._parser.registerCsiHandler({final:"a"},e=>this.hPositionRelative(e)),this._parser.registerCsiHandler({final:"b"},e=>this.repeatPrecedingCharacter(e)),this._parser.registerCsiHandler({final:"c"},e=>this.sendDeviceAttributesPrimary(e)),this._parser.registerCsiHandler({prefix:">",final:"c"},e=>this.sendDeviceAttributesSecondary(e)),this._parser.registerCsiHandler({final:"d"},e=>this.linePosAbsolute(e)),this._parser.registerCsiHandler({final:"e"},e=>this.vPositionRelative(e)),this._parser.registerCsiHandler({final:"f"},e=>this.hVPosition(e)),this._parser.registerCsiHandler({final:"g"},e=>this.tabClear(e)),this._parser.registerCsiHandler({final:"h"},e=>this.setMode(e)),this._parser.registerCsiHandler({prefix:"?",final:"h"},e=>this.setModePrivate(e)),this._parser.registerCsiHandler({final:"l"},e=>this.resetMode(e)),this._parser.registerCsiHandler({prefix:"?",final:"l"},e=>this.resetModePrivate(e)),this._parser.registerCsiHandler({final:"m"},e=>this.charAttributes(e)),this._parser.registerCsiHandler({final:"n"},e=>this.deviceStatus(e)),this._parser.registerCsiHandler({prefix:"?",final:"n"},e=>this.deviceStatusPrivate(e)),this._parser.registerCsiHandler({intermediates:"!",final:"p"},e=>this.softReset(e)),this._parser.registerCsiHandler({prefix:">",final:"q"},e=>this.sendXtVersion(e)),this._parser.registerCsiHandler({intermediates:" ",final:"q"},e=>this.setCursorStyle(e)),this._parser.registerCsiHandler({final:"r"},e=>this.setScrollRegion(e)),this._parser.registerCsiHandler({final:"s"},e=>this.saveCursor(e)),this._parser.registerCsiHandler({final:"t"},e=>this.windowOptions(e)),this._parser.registerCsiHandler({final:"u"},e=>this.restoreCursor(e)),this._parser.registerCsiHandler({intermediates:"'",final:"}"},e=>this.insertColumns(e)),this._parser.registerCsiHandler({intermediates:"'",final:"~"},e=>this.deleteColumns(e)),this._parser.registerCsiHandler({intermediates:'"',final:"q"},e=>this.selectProtected(e)),this._parser.registerCsiHandler({intermediates:"$",final:"p"},e=>this.requestMode(e,!0)),this._parser.registerCsiHandler({prefix:"?",intermediates:"$",final:"p"},e=>this.requestMode(e,!1)),this._parser.registerCsiHandler({prefix:"=",final:"u"},e=>this.kittyKeyboardSet(e)),this._parser.registerCsiHandler({prefix:"?",final:"u"},e=>this.kittyKeyboardQuery(e)),this._parser.registerCsiHandler({prefix:">",final:"u"},e=>this.kittyKeyboardPush(e)),this._parser.registerCsiHandler({prefix:"<",final:"u"},e=>this.kittyKeyboardPop(e)),this._parser.setExecuteHandler("",()=>this.bell()),this._parser.setExecuteHandler("\n",()=>this.lineFeed()),this._parser.setExecuteHandler("\v",()=>this.lineFeed()),this._parser.setExecuteHandler("\f",()=>this.lineFeed()),this._parser.setExecuteHandler("\r",()=>this.carriageReturn()),this._parser.setExecuteHandler("\b",()=>this.backspace()),this._parser.setExecuteHandler("\t",()=>this.tab()),this._parser.setExecuteHandler("",()=>this.shiftOut()),this._parser.setExecuteHandler("",()=>this.shiftIn()),this._parser.setExecuteHandler("„",()=>this.index()),this._parser.setExecuteHandler("…",()=>this.nextLine()),this._parser.setExecuteHandler("ˆ",()=>this.tabSet()),this._parser.registerOscHandler(0,new p.OscHandler(e=>(this.setTitle(e),this.setIconName(e),!0))),this._parser.registerOscHandler(1,new p.OscHandler(e=>this.setIconName(e))),this._parser.registerOscHandler(2,new p.OscHandler(e=>this.setTitle(e))),this._parser.registerOscHandler(4,new p.OscHandler(e=>this.setOrReportIndexedColor(e))),this._parser.registerOscHandler(8,new p.OscHandler(e=>this.setHyperlink(e))),this._parser.registerOscHandler(10,new p.OscHandler(e=>this.setOrReportFgColor(e))),this._parser.registerOscHandler(11,new p.OscHandler(e=>this.setOrReportBgColor(e))),this._parser.registerOscHandler(12,new p.OscHandler(e=>this.setOrReportCursorColor(e))),this._parser.registerOscHandler(104,new p.OscHandler(e=>this.restoreIndexedColor(e))),this._parser.registerOscHandler(110,new p.OscHandler(e=>this.restoreFgColor(e))),this._parser.registerOscHandler(111,new p.OscHandler(e=>this.restoreBgColor(e))),this._parser.registerOscHandler(112,new p.OscHandler(e=>this.restoreCursorColor(e))),this._parser.registerEscHandler({final:"7"},()=>this.saveCursor()),this._parser.registerEscHandler({final:"8"},()=>this.restoreCursor()),this._parser.registerEscHandler({final:"D"},()=>this.index()),this._parser.registerEscHandler({final:"E"},()=>this.nextLine()),this._parser.registerEscHandler({final:"H"},()=>this.tabSet()),this._parser.registerEscHandler({final:"M"},()=>this.reverseIndex()),this._parser.registerEscHandler({final:"="},()=>this.keypadApplicationMode()),this._parser.registerEscHandler({final:">"},()=>this.keypadNumericMode()),this._parser.registerEscHandler({final:"c"},()=>this.fullReset()),this._parser.registerEscHandler({final:"n"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"o"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"|"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"}"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"~"},()=>this.setgLevel(1)),this._parser.registerEscHandler({intermediates:"%",final:"@"},()=>this.selectDefaultCharset()),this._parser.registerEscHandler({intermediates:"%",final:"G"},()=>this.selectDefaultCharset());for(const e in o.CHARSETS)this._parser.registerEscHandler({intermediates:"(",final:e},()=>this.selectCharset("("+e)),this._parser.registerEscHandler({intermediates:")",final:e},()=>this.selectCharset(")"+e)),this._parser.registerEscHandler({intermediates:"*",final:e},()=>this.selectCharset("*"+e)),this._parser.registerEscHandler({intermediates:"+",final:e},()=>this.selectCharset("+"+e)),this._parser.registerEscHandler({intermediates:"-",final:e},()=>this.selectCharset("-"+e)),this._parser.registerEscHandler({intermediates:".",final:e},()=>this.selectCharset("."+e)),this._parser.registerEscHandler({intermediates:"/",final:e},()=>this.selectCharset("/"+e));this._parser.registerEscHandler({intermediates:"#",final:"8"},()=>this.screenAlignmentPattern()),this._parser.setErrorHandler(e=>(this._logService.error("Parsing error: ",e),e)),this._parser.registerDcsHandler({intermediates:"$",final:"q"},new v.DcsHandler((e,t)=>this.requestStatusString(e,t)))}_preserveStack(e,t,i,s){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=t,this._parseStack.decodedLength=i,this._parseStack.position=s}_logSlowResolvingAsync(e){if(this._logService.logLevel<=u.LogLevelEnum.WARN){let t;const i=new Promise((e,i)=>{t=setTimeout(()=>i("#SLOW_TIMEOUT"),5e3)});Promise.race([e,i]).then(()=>{void 0!==t&&clearTimeout(t)},e=>{if(void 0!==t&&clearTimeout(t),"#SLOW_TIMEOUT"!==e)throw e;console.warn("async parser handler taking longer than 5000 ms")})}}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,t){let i,s=this._activeBuffer.x,r=this._activeBuffer.y,o=0;const n=this._parseStack.paused;if(n){if(i=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,t))return this._logSlowResolvingAsync(i),i;s=this._parseStack.cursorStartX,r=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>131072&&(o=this._parseStack.position+131072)}if(this._logService.logLevel<=u.LogLevelEnum.DEBUG&&this._logService.debug("parsing data "+("string"==typeof e?` "${e}"`:` "${Array.prototype.map.call(e,e=>String.fromCharCode(e)).join("")}"`)),this._logService.logLevel===u.LogLevelEnum.TRACE&&this._logService.trace("parsing data (codes)","string"==typeof e?e.split("").map(e=>e.charCodeAt(0)):e),this._parseBuffer.length131072)for(let t=o;t0&&2===p.getWidth(this._activeBuffer.x-1)&&p.setCellFromCodepoint(this._activeBuffer.x-1,0,1,u);let v=this._parser.precedingJoinState;for(let g=t;ga)if(d){const e=p;let t=this._activeBuffer.x-m;if(this._activeBuffer.x=m,this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData(),!0)):(this._activeBuffer.y>=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),p=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),!p)return;for(m>0&&p instanceof l.BufferLine&&p.copyCellsFrom(e,t,0,m,!1);t=0;)p.setCellFromCodepoint(this._activeBuffer.x++,0,0,u);continue}if(_&&(p.insertCells(this._activeBuffer.x,r-m,this._activeBuffer.getNullCell(u)),2===p.getWidth(a-1)&&p.setCellFromCodepoint(a-1,c.NULL_CELL_CODE,c.NULL_CELL_WIDTH,u)),p.setCellFromCodepoint(this._activeBuffer.x++,s,r,u),r>0)for(;--r;)p.setCellFromCodepoint(this._activeBuffer.x++,0,0,u)}this._parser.precedingJoinState=v,this._activeBuffer.x0&&0===p.getWidth(this._activeBuffer.x)&&!p.hasContent(this._activeBuffer.x)&&p.setCellFromCodepoint(this._activeBuffer.x,0,1,u),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,t){return"t"!==e.final||e.prefix||e.intermediates?this._parser.registerCsiHandler(e,t):this._parser.registerCsiHandler(e,e=>!y(e.params[0],this._optionsService.rawOptions.windowOptions)||t(e))}registerDcsHandler(e,t){return this._parser.registerDcsHandler(e,new v.DcsHandler(t))}registerEscHandler(e,t){return this._parser.registerEscHandler(e,t)}registerOscHandler(e,t){return this._parser.registerOscHandler(e,new p.OscHandler(t))}registerApcHandler(e,t){return this._parser.registerApcHandler(e,new g.ApcHandler(t))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(0===this._activeBuffer.x&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y)?.isWrapped){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;const e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);e.hasWidth(this._activeBuffer.x)&&!e.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;const e=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-e),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(e=this._bufferService.cols-1){this._activeBuffer.x=Math.min(e,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(e,t){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+t):(this._activeBuffer.x=e,this._activeBuffer.y=t),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,t){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+t)}cursorUp(e){const t=this._activeBuffer.y-this._activeBuffer.scrollTop;return t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){const t=this._activeBuffer.scrollBottom-this._activeBuffer.y;return t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1),!0}cursorForward(e){return this._moveCursor(e.params[0]||1,0),!0}cursorBackward(e){return this._moveCursor(-(e.params[0]||1),0),!0}cursorNextLine(e){return this.cursorDown(e),this._activeBuffer.x=0,!0}cursorPrecedingLine(e){return this.cursorUp(e),this._activeBuffer.x=0,!0}cursorCharAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(e){return this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1),!0}charPosAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(e){return this._moveCursor(e.params[0]||1,0),!0}linePosAbsolute(e){return this._setCursor(this._activeBuffer.x,(e.params[0]||1)-1),!0}vPositionRelative(e){return this._moveCursor(0,e.params[0]||1),!0}hVPosition(e){return this.cursorPosition(e),!0}tabClear(e){const t=e.params[0];return 0===t?delete this._activeBuffer.tabs[this._activeBuffer.x]:3===t&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){const t=e.params[0];return 1===t&&(this._curAttrData.bg|=536870912),2!==t&&0!==t||(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,t,i,s=!1,r=!1){const o=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);o&&(o.replaceCells(t,i,this._activeBuffer.getNullCell(this._eraseAttrData()),r),s&&(o.isWrapped=!1))}_resetBufferLine(e,t=!1){const i=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);i&&(i.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),t),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),i.isWrapped=!1)}eraseInDisplay(e,t=!1){let i;switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:for(i=this._activeBuffer.y,this._dirtyRowTracker.markDirty(i),this._eraseInBufferLine(i++,this._activeBuffer.x,this._bufferService.cols,0===this._activeBuffer.x,t);i=this._bufferService.cols){const e=this._activeBuffer.lines.get(i+1);e&&(e.isWrapped=!1)}for(;i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0);break;case 2:if(this._optionsService.rawOptions.scrollOnEraseInDisplay){for(i=this._bufferService.rows,this._dirtyRowTracker.markRangeDirty(0,i-1);i--;){const e=this._activeBuffer.lines.get(this._activeBuffer.ybase+i);if(e?.getTrimmedLength())break}for(;i>=0;i--)this._bufferService.scroll(this._eraseAttrData())}else{for(i=this._bufferService.rows,this._dirtyRowTracker.markDirty(i-1);i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0)}break;case 3:const e=this._activeBuffer.lines.length-this._bufferService.rows;e>0&&(this._activeBuffer.lines.trimStart(e),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-e,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-e,0),this._onScroll.fire(0))}return!0}eraseInLine(e,t=!1){switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,0===this._activeBuffer.x,t);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,t);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,t)}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let t=e.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y65535?2:1}let h=a;for(let e=1;e0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent("[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent("[?6c")),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent("[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent("[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent("[>83;40003;0c")),!0}sendXtVersion(e){return e.params[0]>0||this._coreService.triggerDataEvent(`P>|xterm.js(${b.XTERM_VERSION})\\`),!0}_is(e){return(this._optionsService.rawOptions.termName+"").startsWith(e)}setMode(e){for(let t=0;t(o.triggerDataEvent(`[${t?"":"?"}${e};${i}$y`),!0),_=e=>e?1:2,u=e.params[0];return t?d(u,2===u?4:4===u?_(o.modes.insertMode):12===u?3:20===u?_(c.convertEol):0):1===u?d(u,_(i.applicationCursorKeys)):3===u?d(u,c.windowOptions.setWinLines?80===a?2:132===a?1:0:0):6===u?d(u,_(i.origin)):7===u?d(u,_(i.wraparound)):8===u?d(u,3):9===u?d(u,_("X10"===s)):12===u?d(u,_(c.cursorBlink)):25===u?d(u,_(!o.isCursorHidden)):45===u?d(u,_(i.reverseWraparound)):66===u?d(u,_(i.applicationKeypad)):67===u?d(u,4):1e3===u?d(u,_("VT200"===s)):1002===u?d(u,_("DRAG"===s)):1003===u?d(u,_("ANY"===s)):1004===u?d(u,_(i.sendFocus)):1005===u?d(u,4):1006===u?d(u,_("SGR"===r)):1015===u?d(u,4):1016===u?d(u,_("SGR_PIXELS"===r)):1048===u?d(u,1):47===u||1047===u||1049===u?d(u,_(h===l)):2004===u?d(u,_(i.bracketedPasteMode)):2026===u?d(u,_(i.synchronizedOutput)):9001===u&&this._optionsService.rawOptions.vtExtensions?.win32InputMode?d(u,_(i.win32InputMode)):d(u,0)}_updateAttrColor(e,t,i,s,r){return 2===t?(e|=50331648,e&=-16777216,e|=_.AttributeData.fromColorRGB([i,s,r])):5===t&&(e&=-67108864,e|=33554432|255&i),e}_extractColor(e,t,i){const s=[0,0,-1,0,0,0];let r=0,o=0;do{if(s[o+r]=e.params[t+o],e.hasSubParams(t+o)){const i=e.getSubParams(t+o);let n=0;do{5===s[1]&&(r=1),s[o+n+1+r]=i[n]}while(++n=2||2===s[1]&&o+r>=5)break;s[1]&&(r=1)}while(++o+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,0===e&&(t.fg&=-268435457),t.updateExtended()}_processSGR0(e){e.fg=l.DEFAULT_ATTR_DATA.fg,e.bg=l.DEFAULT_ATTR_DATA.bg,e.extended=e.extended.clone(),e.extended.underlineStyle=0,e.extended.underlineColor&=-67108864,e.updateExtended()}charAttributes(e){if(1===e.length&&0===e.params[0])return this._processSGR0(this._curAttrData),!0;const t=e.length;let i;const s=this._curAttrData;for(let r=0;r=30&&i<=37?(s.fg&=-67108864,s.fg|=16777216|i-30):i>=40&&i<=47?(s.bg&=-67108864,s.bg|=16777216|i-40):i>=90&&i<=97?(s.fg&=-67108864,s.fg|=16777224|i-90):i>=100&&i<=107?(s.bg&=-67108864,s.bg|=16777224|i-100):0===i?this._processSGR0(s):1===i?s.fg|=134217728:3===i?s.bg|=67108864:4===i?(s.fg|=268435456,this._processUnderline(e.hasSubParams(r)?e.getSubParams(r)[0]:1,s)):5===i?s.fg|=536870912:7===i?s.fg|=67108864:8===i?s.fg|=1073741824:9===i?s.fg|=2147483648:2===i?s.bg|=134217728:21===i?this._processUnderline(2,s):22===i?(s.fg&=-134217729,s.bg&=-134217729):23===i?s.bg&=-67108865:24===i?(s.fg&=-268435457,this._processUnderline(0,s)):25===i?s.fg&=-536870913:27===i?s.fg&=-67108865:28===i?s.fg&=-1073741825:29===i?s.fg&=2147483647:39===i?(s.fg&=-67108864,s.fg|=16777215&l.DEFAULT_ATTR_DATA.fg):49===i?(s.bg&=-67108864,s.bg|=16777215&l.DEFAULT_ATTR_DATA.bg):38===i||48===i||58===i?r+=this._extractColor(e,r,s):53===i?s.bg|=1073741824:55===i?s.bg&=-1073741825:221===i&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??1)?s.fg&=-134217729:222===i&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??1)?s.bg&=-134217729:59===i?(s.extended=s.extended.clone(),s.extended.underlineColor=-1,s.updateExtended()):this._logService.debug("Unknown SGR attribute: %d.",i);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent("");break;case 6:const e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`[${e};${t}R`)}return!0}deviceStatusPrivate(e){switch(e.params[0]){case 6:const e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`[?${e};${t}R`);break;case 15:case 25:case 26:case 53:break;case 996:(this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery??1)&&this._onRequestColorSchemeQuery.fire()}return!0}softReset(e){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=l.DEFAULT_ATTR_DATA.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(e){const t=0===e.length?1:e.params[0];if(0===t)this._coreService.decPrivateModes.cursorStyle=void 0,this._coreService.decPrivateModes.cursorBlink=void 0;else{switch(t){case 1:case 2:this._coreService.decPrivateModes.cursorStyle="block";break;case 3:case 4:this._coreService.decPrivateModes.cursorStyle="underline";break;case 5:case 6:this._coreService.decPrivateModes.cursorStyle="bar"}const e=t%2==1;this._coreService.decPrivateModes.cursorBlink=e}return!0}setScrollRegion(e){const t=e.params[0]||1;let i;return(e.length<2||(i=e.params[1])>this._bufferService.rows||0===i)&&(i=this._bufferService.rows),i>t&&(this._activeBuffer.scrollTop=t-1,this._activeBuffer.scrollBottom=i-1,this._setCursor(0,0)),!0}windowOptions(e){if(!y(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;const t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:2!==t&&this._onRequestWindowsOptionsReport.fire(C.GET_WIN_SIZE_PIXELS);break;case 16:this._onRequestWindowsOptionsReport.fire(C.GET_CELL_SIZE_PIXELS);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:0!==t&&2!==t||(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>10&&this._windowTitleStack.shift()),0!==t&&1!==t||(this._iconNameStack.push(this._iconName),this._iconNameStack.length>10&&this._iconNameStack.shift());break;case 23:0!==t&&2!==t||this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),0!==t&&1!==t||this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop())}return!0}saveCursor(e){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._activeBuffer.savedCharsets=this._charsetService.charsets.slice(),this._activeBuffer.savedGlevel=this._charsetService.glevel,this._activeBuffer.savedOriginMode=this._coreService.decPrivateModes.origin,this._activeBuffer.savedWraparoundMode=this._coreService.decPrivateModes.wraparound,!0}restoreCursor(e){this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg;for(let e=0;e1;){const e=i.shift(),s=i.shift();if(/^\d+$/.exec(e)){const i=parseInt(e,10);if(L(i))if("?"===s)t.push({type:0,index:i});else{const e=(0,m.parseColor)(s);e&&t.push({type:1,index:i,color:e})}}}return t.length&&this._onColor.fire(t),!0}setHyperlink(e){const t=e.indexOf(";");if(-1===t)return!0;const i=e.slice(0,t).trim(),s=e.slice(t+1);return s?this._createHyperlink(i,s):!i.trim()&&this._finishHyperlink()}_createHyperlink(e,t){this._getCurrentLinkId()&&this._finishHyperlink();const i=e.split(":");let s;const r=i.findIndex(e=>e.startsWith("id="));return-1!==r&&(s=i[r].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:s,uri:t}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,t){const i=e.split(";");for(let e=0;e=this._specialColors.length);++e,++t)if("?"===i[e])this._onColor.fire([{type:0,index:this._specialColors[t]}]);else{const s=(0,m.parseColor)(i[e]);s&&this._onColor.fire([{type:1,index:this._specialColors[t],color:s}])}return!0}setOrReportFgColor(e){return this._setOrReportSpecialColor(e,0)}setOrReportBgColor(e){return this._setOrReportSpecialColor(e,1)}setOrReportCursorColor(e){return this._setOrReportSpecialColor(e,2)}restoreIndexedColor(e){if(!e)return this._onColor.fire([{type:2}]),!0;const t=[],i=e.split(";");for(let e=0;e=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){const e=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,e,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=l.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=l.DEFAULT_ATTR_DATA.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=67108863&this._curAttrData.bg,this._eraseAttrDataInternal}setgLevel(e){return this._charsetService.setgLevel(e),!0}screenAlignmentPattern(){const e=new d.CellData;e.content=1<<22|"E".charCodeAt(0),e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let t=0;t(this._coreService.triggerDataEvent(`${e}\\`),!0))('"q'===e?`P1$r${this._curAttrData.isProtected()?1:0}"q`:'"p'===e?'P1$r61;1"p':"r"===e?`P1$r${i.scrollTop+1};${i.scrollBottom+1}r`:"m"===e?"P1$r0m":" q"===e?`P1$r${{block:2,underline:4,bar:6}[s.cursorStyle]-(s.cursorBlink?1:0)} q`:"P0$r")}markRangeDirty(e,t){this._dirtyRowTracker.markRangeDirty(e,t)}kittyKeyboardSet(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;const t=e.params[0]||0,i=e.length>1&&e.params[1]||1,s=this._coreService.kittyKeyboard;switch(i){case 1:s.flags=t;break;case 2:s.flags|=t;break;case 3:s.flags&=~t}return!0}kittyKeyboardQuery(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;const t=this._coreService.kittyKeyboard.flags;return this._coreService.triggerDataEvent(`[?${t}u`),!0}kittyKeyboardPush(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;const t=e.params[0]||0,i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;return s.length>=16&&s.shift(),s.push(i.flags),i.flags=t,!0}kittyKeyboardPop(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;const t=Math.max(1,e.params[0]||1),i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;for(let e=0;e0;e++)i.flags=s.pop();return 0===s.length&&t>0&&(i.flags=0),!0}}t.InputHandler=D;let E=class{constructor(e){this._bufferService=e,this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(e){ethis.end&&(this.end=e)}markRangeDirty(e,t){e>t&&(k=e,e=t,t=k),ethis.end&&(this.end=t)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};function L(e){return 0<=e&&e<256}E=s([r(0,u.IBufferService)],E)},4812(e,t){function i(e){return{dispose:e}}function s(e){if(!e)return e;if(Array.isArray(e)){for(const t of e)t.dispose();return[]}return e.dispose(),e}Object.defineProperty(t,"__esModule",{value:!0}),t.MutableDisposable=t.Disposable=t.DisposableStore=void 0,t.toDisposable=i,t.dispose=s,t.combinedDisposable=function(...e){return i(()=>s(e))};class r{constructor(){this._disposables=new Set,this._isDisposed=!1}get isDisposed(){return this._isDisposed}add(e){return this._isDisposed?e.dispose():this._disposables.add(e),e}dispose(){if(!this._isDisposed){this._isDisposed=!0;for(const e of this._disposables)e.dispose();this._disposables.clear()}}clear(){for(const e of this._disposables)e.dispose();this._disposables.clear()}}t.DisposableStore=r;class o{constructor(){this._store=new r}dispose(){this._store.dispose()}_register(e){return this._store.add(e)}}t.Disposable=o,o.None=Object.freeze({dispose(){}}),t.MutableDisposable=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,this._value?.dispose(),this._value=void 0}}},7710(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.FourKeyMap=t.TwoKeyMap=void 0;class i{constructor(){this._data={}}set(e,t,i){this._data[e]||(this._data[e]={}),this._data[e][t]=i}get(e,t){return this._data[e]?this._data[e][t]:void 0}clear(){this._data={}}}t.TwoKeyMap=i,t.FourKeyMap=class{constructor(){this._data=new i}set(e,t,s,r,o){this._data.get(e,t)||this._data.set(e,t,new i),this._data.get(e,t).set(s,r,o)}get(e,t,i,s){return this._data.get(e,t)?.get(i,s)}clear(){this._data.clear()}}},701(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.isChromeOS=t.isLinux=t.isWindows=t.isMac=t.isSafari=t.isLegacyEdge=t.isChrome=t.isFirefox=t.isNode=void 0,t.getZoomFactor=function(e){return 1},t.getSafariVersion=function(){if(!t.isSafari)return 0;const e=i.match(/Version\/(\d+)/);return null===e||e.length<2?0:parseInt(e[1],10)},t.isNode=!("undefined"==typeof process||!("title"in process)||"undefined"!=typeof navigator&&!navigator.userAgent.startsWith("Node.js/"));const i=t.isNode?"node":navigator.userAgent,s=t.isNode?"node":navigator.platform;t.isFirefox=i.includes("Firefox"),t.isChrome=i.includes("Chrome"),t.isLegacyEdge=i.includes("Edge"),t.isSafari=/^((?!chrome|android).)*safari/i.test(i),t.isMac=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(s),t.isWindows=["Windows","Win16","Win32","WinCE"].includes(s),t.isLinux=s.indexOf("Linux")>=0,t.isChromeOS=/\bCrOS\b/.test(i)},3087(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.SortedList=void 0;const s=i(6168);let r=0;t.SortedList=class{constructor(e,t){this._getKey=e,this._array=[],this._insertedValues=[],this._isFlushingInserted=!1,this._deletedIndices=[],this._isFlushingDeleted=!1,this._flushInsertedTask=new s.IdleTaskQueue(t),this._flushDeletedTask=new s.IdleTaskQueue(t)}clear(){this._array.length=0,this._insertedValues.length=0,this._flushInsertedTask.clear(),this._isFlushingInserted=!1,this._deletedIndices.length=0,this._flushDeletedTask.clear(),this._isFlushingDeleted=!1}insert(e){this._flushCleanupDeleted(),0===this._insertedValues.length&&this._flushInsertedTask.enqueue(()=>this._flushInserted()),this._insertedValues.push(e)}_flushInserted(){const e=this._insertedValues.sort((e,t)=>this._getKey(e)-this._getKey(t));let t=0,i=0;const s=new Array(this._array.length+this._insertedValues.length);for(let r=0;r=this._array.length||this._getKey(e[t])<=this._getKey(this._array[i])?(s[r]=e[t],t++):s[r]=this._array[i++];this._array=s,this._insertedValues.length=0}_flushCleanupInserted(){!this._isFlushingInserted&&this._insertedValues.length>0&&this._flushInsertedTask.flush()}delete(e){if(this._flushCleanupInserted(),0===this._array.length)return!1;const t=this._getKey(e);if(void 0===t)return!1;if(r=this._search(t),-1===r)return!1;if(this._getKey(this._array[r])!==t)return!1;do{if(this._array[r]===e)return 0===this._deletedIndices.length&&this._flushDeletedTask.enqueue(()=>this._flushDeleted()),this._deletedIndices.push(r),!0}while(++re-t);let t=0;const i=new Array(this._array.length-e.length);let s=0;for(let r=0;r0&&this._flushDeletedTask.flush()}*getKeyIterator(e){if(this._flushCleanupInserted(),this._flushCleanupDeleted(),0!==this._array.length&&(r=this._search(e),!(r<0||r>=this._array.length)&&this._getKey(this._array[r])===e))do{yield this._array[r]}while(++r=this._array.length)&&this._getKey(this._array[r])===e))do{t(this._array[r])}while(++r=t;){let s=t+i>>1;const r=this._getKey(this._array[s]);if(r>e)i=s-1;else{if(!(r0&&this._getKey(this._array[s-1])===e;)s--;return s}t=s+1}}return t}}},4220(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.LimitedStringBuilder=t.StringBuilder=void 0;class i{constructor(){this._chunks=[],this._length=0}get length(){return this._length}reset(){this._chunks.length=0,this._length=0}append(e){this._chunks.push(e),this._length+=e.length}toString(){return this._chunks.join("")}}t.StringBuilder=i,t.LimitedStringBuilder=class{constructor(e){this._limit=e,this._builder=new i}get length(){return this._builder.length}get limit(){return this._limit}reset(){this._builder.reset()}append(e){return this._builder.append(e),this._builder.length>this._limit&&(this._builder.reset(),!0)}toString(){return this._builder.toString()}}},6168(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.DebouncedIdleTask=t.IdleTaskQueue=t.PriorityTaskQueue=void 0;class i{constructor(e){this._tasks=[],this._i=0,this._logService=e}enqueue(e){this._tasks.push(e),this._start()}flush(){for(;this._ii)return r-t<-20&&this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(r-t))}ms`),void this._start();r=i}this.clear()}}class s extends i{_requestCallback(e){return setTimeout(()=>e(this._createDeadline(16)))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){const t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}}t.PriorityTaskQueue=s,t.IdleTaskQueue="requestIdleCallback"in globalThis?class extends i{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}}:s,t.DebouncedIdleTask=class{constructor(e){this._queue=new t.IdleTaskQueue(e)}set(e){this._queue.clear(),this._queue.enqueue(e)}flush(){this._queue.flush()}dispose(){this._queue.clear()}}},7804(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.XTERM_VERSION=void 0,t.XTERM_VERSION="6.1.0-beta.287"},5882(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.updateWindowsModeWrappedState=function(e){const t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1),i=t?.get(e.cols-1),r=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);r&&i&&(r.isWrapped=i[s.CHAR_DATA_CODE_INDEX]!==s.NULL_CELL_CODE&&i[s.CHAR_DATA_CODE_INDEX]!==s.WHITESPACE_CELL_CODE)};const s=i(8938)},5451(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ExtendedAttrs=t.AttributeData=void 0;class i{constructor(){this.fg=0,this.bg=0,this.extended=new s}static toColorRGB(e){return[e>>>16&255,e>>>8&255,255&e]}static fromColorRGB(e){return(255&e[0])<<16|(255&e[1])<<8|255&e[2]}clone(){const e=new i;return e.fg=this.fg,e.bg=this.bg,e.extended=this.extended.clone(),e}isInverse(){return 67108864&this.fg}isBold(){return 134217728&this.fg}isUnderline(){return this.hasExtendedAttrs()&&0!==this.extended.underlineStyle?1:268435456&this.fg}isBlink(){return 536870912&this.fg}isInvisible(){return 1073741824&this.fg}isItalic(){return 67108864&this.bg}isDim(){return 134217728&this.bg}isStrikethrough(){return 2147483648&this.fg}isProtected(){return 536870912&this.bg}isOverline(){return 1073741824&this.bg}getFgColorMode(){return 50331648&this.fg}getBgColorMode(){return 50331648&this.bg}isFgRGB(){return!(50331648&~this.fg)}isBgRGB(){return!(50331648&~this.bg)}isFgPalette(){return 16777216==(50331648&this.fg)||33554432==(50331648&this.fg)}isBgPalette(){return 16777216==(50331648&this.bg)||33554432==(50331648&this.bg)}isFgDefault(){return!(50331648&this.fg)}isBgDefault(){return!(50331648&this.bg)}isAttributeDefault(){return 0===this.fg&&0===this.bg}getFgColor(){switch(50331648&this.fg){case 16777216:case 33554432:return 255&this.fg;case 50331648:return 16777215&this.fg;default:return-1}}getBgColor(){switch(50331648&this.bg){case 16777216:case 33554432:return 255&this.bg;case 50331648:return 16777215&this.bg;default:return-1}}hasExtendedAttrs(){return 268435456&this.bg}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(268435456&this.bg&&~this.extended.underlineColor)switch(50331648&this.extended.underlineColor){case 16777216:case 33554432:return 255&this.extended.underlineColor;case 50331648:return 16777215&this.extended.underlineColor;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return 268435456&this.bg&&~this.extended.underlineColor?50331648&this.extended.underlineColor:this.getFgColorMode()}isUnderlineColorRGB(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&~this.extended.underlineColor):this.isFgRGB()}isUnderlineColorPalette(){return 268435456&this.bg&&~this.extended.underlineColor?16777216==(50331648&this.extended.underlineColor)||33554432==(50331648&this.extended.underlineColor):this.isFgPalette()}isUnderlineColorDefault(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&this.extended.underlineColor):this.isFgDefault()}getUnderlineStyle(){return 268435456&this.fg?268435456&this.bg?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}}t.AttributeData=i;class s{get ext(){return this._urlId?-469762049&this._ext|this.underlineStyle<<26:this._ext}set ext(e){this._ext=e}get underlineStyle(){return this._urlId?5:(469762048&this._ext)>>26}set underlineStyle(e){this._ext&=-469762049,this._ext|=e<<26&469762048}get underlineColor(){return 67108863&this._ext}set underlineColor(e){this._ext&=-67108864,this._ext|=67108863&e}get urlId(){return this._urlId}set urlId(e){this._urlId=e}get underlineVariantOffset(){const e=(3758096384&this._ext)>>29;return e<0?4294967288^e:e}set underlineVariantOffset(e){this._ext&=536870911,this._ext|=e<<29&3758096384}constructor(e=0,t=0){this._ext=0,this._urlId=0,this._ext=e,this._urlId=t}clone(){return new s(this._ext,this._urlId)}isEmpty(){return 0===this.underlineStyle&&0===this._urlId}}t.ExtendedAttrs=s},1073(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.Buffer=t.MAX_BUFFER_SIZE=void 0;const s=i(5639),r=i(4812),o=i(6168),n=i(5451),a=i(6107),h=i(3326),l=i(732),c=i(3055),d=i(8938),_=i(8158),u=i(6760);t.MAX_BUFFER_SIZE=4294967295;class f extends r.Disposable{constructor(e,t,i,n){super(),this._hasScrollback=e,this._optionsService=t,this._bufferService=i,this._logService=n,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.tabs={},this.savedY=0,this.savedX=0,this.savedCurAttrData=a.DEFAULT_ATTR_DATA.clone(),this.savedCharset=u.DEFAULT_CHARSET,this.savedCharsets=[],this.savedGlevel=0,this.savedOriginMode=!1,this.savedWraparoundMode=!0,this.markers=[],this._nullCell=c.CellData.fromCharData([0,d.NULL_CELL_CHAR,d.NULL_CELL_WIDTH,d.NULL_CELL_CODE]),this._whitespaceCell=c.CellData.fromCharData([0,d.WHITESPACE_CELL_CHAR,d.WHITESPACE_CELL_WIDTH,d.WHITESPACE_CELL_CODE]),this._isClearing=!1,this._memoryCleanupPosition=0,this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new s.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops(),this._memoryCleanupQueue=new o.IdleTaskQueue(this._logService),this._register((0,r.toDisposable)(()=>this._memoryCleanupQueue.clear())),this._register((0,r.toDisposable)(()=>this.clearAllMarkers())),this._stringCache=this._register(new h.BufferLineStringCache)}getNullCell(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new n.ExtendedAttrs),this._nullCell}getWhitespaceCell(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new n.ExtendedAttrs),this._whitespaceCell}getBlankLine(e,t){return new a.BufferLine(this._stringCache,this._bufferService.cols,this.getNullCell(e),t)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){const e=this.ybase+this.y-this.ydisp;return e>=0&&et.MAX_BUFFER_SIZE?t.MAX_BUFFER_SIZE:i}fillViewportRows(e){if(0===this.lines.length){e??=a.DEFAULT_ATTR_DATA;let t=this._rows;for(;t--;)this.lines.push(this.getBlankLine(e))}}clear(){this._stringCache.clear(),this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new s.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(e,t){const i=this.getNullCell(a.DEFAULT_ATTR_DATA);this._stringCache.clear();let s=0;const r=this._getCorrectBufferLength(t);if(r>this.lines.maxLength&&(this.lines.maxLength=r),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+o+1?(this.ybase--,o++,this.ydisp>0&&this.ydisp--):this.lines.push(new a.BufferLine(this._stringCache,e,i,!1)));else for(let e=this._rows;e>t;e--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(r0&&(this.lines.trimStart(e),this.ybase=Math.max(this.ybase-e,0),this.ydisp=Math.max(this.ydisp-e,0),this.savedY=Math.max(this.savedY-e,0)),this.lines.maxLength=r}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),o&&(this.y+=o),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(let t=0;t0){const e=Math.max(0,this.lines.length-this.ybase-1);this.y=Math.min(this.y,e)}this._memoryCleanupQueue.clear(),s>.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue(()=>this._batchedMemoryCleanup()))}_batchedMemoryCleanup(){let e=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,e=!1);let t=0;for(;this._memoryCleanupPosition100)return!0;return e}get _isReflowEnabled(){const e=this._optionsService.rawOptions.windowsPty;return e&&e.buildNumber?this._hasScrollback&&"conpty"===e.backend&&e.buildNumber>=21376:this._hasScrollback}_reflow(e,t){this._cols!==e&&(e>this._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))}_reflowLarger(e,t){const i=this._optionsService.rawOptions.reflowCursorLine,s=(0,l.reflowLargerGetLinesToRemove)(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(a.DEFAULT_ATTR_DATA),i);if(s.length>0){const i=(0,l.reflowLargerCreateNewLayout)(this.lines,s);(0,l.reflowLargerApplyNewLayout)(this.lines,i.layout),this._reflowLargerAdjustViewport(e,t,i.countRemoved)}}_reflowLargerAdjustViewport(e,t,i){const s=this.getNullCell(a.DEFAULT_ATTR_DATA);let r=i;for(;r-- >0;)0===this.ybase?(this.y>0&&this.y--,this.lines.length=0;n--){let h=this.lines.get(n);if(!h||!h.isWrapped&&h.getTrimmedLength()<=e)continue;const c=[h];for(;h.isWrapped&&n>0;)h=this.lines.get(--n),c.unshift(h);if(!i){const e=this.ybase+this.y;if(e>=n&&e0&&(r.push({start:n+c.length+o,newLines:p}),o+=p.length),c.push(...p);let v=_.length-1,g=_[v];0===g&&(v--,g=_[v]);let m=c.length-u-1,S=d;for(;m>=0;){const e=Math.min(S,g);if(void 0===c[v])break;if(c[v].copyCellsFrom(c[m],S-e,g-e,e,!0),g-=e,0===g&&(v--,g=_[v]),S-=e,0===S){m--;const e=Math.max(m,0);S=(0,l.getWrappedLineTrimmedLength)(c,e,this._cols)}}for(let t=0;t0;)0===this.ybase?this.y0){const e=[],t=[];for(let e=0;e=0;l--)if(a&&a.start>s+h){for(let e=a.newLines.length-1;e>=0;e--)this.lines.set(l--,a.newLines[e]);l++,e.push({index:s+1,amount:a.newLines.length}),h+=a.newLines.length,a=r[++n]}else this.lines.set(l,t[s--]);let l=0;for(let t=e.length-1;t>=0;t--)e[t].index+=l,this.lines.onInsertEmitter.fire(e[t]),l+=e[t].amount;const c=Math.max(0,i+o-this.lines.maxLength);c>0&&this.lines.onTrimEmitter.fire(c)}}translateBufferLineToString(e,t,i=0,s){const r=this.lines.get(e);return r?r.translateToString(t,i,s):""}getWrappedRangeForLine(e){let t=e,i=e;for(;t>0&&this.lines.get(t).isWrapped;)t--;for(;i+10;);return e>=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(e??=this.x;!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e}clearMarkers(e){this._isClearing=!0;for(let t=0;t{t.line-=e,t.line<0&&t.dispose()})),t.register(this.lines.onInsert(e=>{t.line>=e.index&&(t.line+=e.amount)})),t.register(this.lines.onDelete(e=>{t.line>=e.index&&t.linee.index&&(t.line-=e.amount)})),t.register(t.onDispose(()=>this._removeMarker(t))),t}_removeMarker(e){this._isClearing||this.markers.splice(this.markers.indexOf(e),1)}}t.Buffer=f},6107(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.BufferLine=t.DEFAULT_ATTR_DATA=void 0;const s=i(5451),r=i(3055),o=i(8938),n=i(726),a=i(4220);t.DEFAULT_ATTR_DATA=Object.freeze(new s.AttributeData);let h=0;const l=new r.CellData,c=new a.StringBuilder;class d{constructor(e,t,i,s=!1){this._stringCache=e,this.isWrapped=s,this._combined={},this._extendedAttrs={},this._data=new Uint32Array(3*t);const n=i??r.CellData.fromCharData([0,o.NULL_CELL_CHAR,o.NULL_CELL_WIDTH,o.NULL_CELL_CODE]);for(let e=0;e>22,2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):i]}set(e,t){this._invalidateStringCache(),this._data[3*e+1]=t[o.CHAR_DATA_ATTR_INDEX],t[o.CHAR_DATA_CHAR_INDEX].length>1?(this._combined[e]=t[1],this._data[3*e+0]=2097152|e|t[o.CHAR_DATA_WIDTH_INDEX]<<22):this._data[3*e+0]=t[o.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|t[o.CHAR_DATA_WIDTH_INDEX]<<22}getWidth(e){return this._data[3*e+0]>>22}hasWidth(e){return 12582912&this._data[3*e+0]}getFg(e){return this._data[3*e+1]}getBg(e){return this._data[3*e+2]}hasContent(e){return 4194303&this._data[3*e+0]}getCodePoint(e){const t=this._data[3*e+0];return 2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):2097151&t}isCombined(e){return 2097152&this._data[3*e+0]}getString(e){const t=this._data[3*e+0];return 2097152&t?this._combined[e]:2097151&t?(0,n.stringFromCodePoint)(2097151&t):""}isProtected(e){return 536870912&this._data[3*e+2]}loadCell(e,i){return h=3*e,i.content=this._data[h+0],i.fg=this._data[h+1],i.bg=this._data[h+2],2097152&i.content?i.combinedData=this._combined[e]:i.combinedData="",268435456&i.bg?i.extended=this._extendedAttrs[e]:i.extended=t.DEFAULT_ATTR_DATA.extended.clone(),i}setCell(e,t){this._invalidateStringCache(),2097152&t.content&&(this._combined[e]=t.combinedData),268435456&t.bg&&(this._extendedAttrs[e]=t.extended),this._data[3*e+0]=t.content,this._data[3*e+1]=t.fg,this._data[3*e+2]=t.bg}setCellFromCodepoint(e,t,i,s){this._invalidateStringCache(),268435456&s.bg&&(this._extendedAttrs[e]=s.extended),this._data[3*e+0]=t|i<<22,this._data[3*e+1]=s.fg,this._data[3*e+2]=s.bg}addCodepointToCell(e,t,i){this._invalidateStringCache();let s=this._data[3*e+0];2097152&s?this._combined[e]+=(0,n.stringFromCodePoint)(t):2097151&s?(this._combined[e]=(0,n.stringFromCodePoint)(2097151&s)+(0,n.stringFromCodePoint)(t),s&=-2097152,s|=2097152):s=t|1<<22,i&&(s&=-12582913,s|=i<<22),this._data[3*e+0]=s}insertCells(e,t,i){if(this._invalidateStringCache(),(e%=this.length)&&2===this.getWidth(e-1)&&this.setCellFromCodepoint(e-1,0,1,i),t=0;--i)this.setCell(e+t+i,this.loadCell(e+i,l));for(let s=0;sthis.length){if(this._data.buffer.byteLength>=4*i)this._data=new Uint32Array(this._data.buffer,0,i);else{const e=new Uint32Array(i);e.set(this._data),this._data=e}for(let i=this.length;i=e&&delete this._combined[s]}const s=Object.keys(this._extendedAttrs);for(let t=0;t=e&&delete this._extendedAttrs[i]}}return this.length=e,4*i*2=0;--e)if(4194303&this._data[3*e+0])return e+(this._data[3*e+0]>>22);return 0}getNoBgTrimmedLength(){for(let e=this.length-1;e>=0;--e)if(4194303&this._data[3*e+0]||50331648&this._data[3*e+2])return e+(this._data[3*e+0]>>22);return 0}copyCellsFrom(e,t,i,s,r){this._invalidateStringCache();const o=e._data;if(r)for(let r=s-1;r>=0;r--){for(let e=0;e<3;e++)this._data[3*(i+r)+e]=o[3*(t+r)+e];this._copyCellMapsFrom(e,t+r,i+r)}else for(let r=0;r>22||1}s&&s.push(t);const h=c.toString();if(c.reset(),r){const t=this._getStringCacheEntry(!0);t.value=h,t.isTrimmed=!!e}return h}_getStringCacheEntry(e){const t=this._stringCacheEntryRef?.deref();if(t&&t.generation===this._stringCache.generation)return t;if(!e)return;const i=this._stringCache.allocateEntry();return this._stringCacheEntryRef=new WeakRef(i),i}_invalidateStringCache(){const e=this._getStringCacheEntry(!1);e&&(e.value=void 0,e.isTrimmed=!1)}_copyCellMapsFrom(e,t,i){const s=3*t;2097152&e._data[s+0]&&(this._combined[i]=e._combined[t]),268435456&e._data[s+2]&&(this._extendedAttrs[i]=e._extendedAttrs[t])}_copySparseMapsFrom(e){this._combined={},this._extendedAttrs={};for(let t=0;tthis.entries.clear()))}touch(){this._scheduleClear()}allocateEntry(){const e={value:void 0,isTrimmed:!1,generation:this.generation};return this.entries.add(e),this._scheduleClear(),e}clear(){this._clearTimeout.clear(),this._lastAccessTimestamp=0,this.generation++;for(const e of this.entries)e.value=void 0,e.isTrimmed=!1;this.entries.clear()}_scheduleClear(){this._lastAccessTimestamp=Date.now(),this._clearTimeout.value||this._scheduleClearTimeout(15e3)}_scheduleClearTimeout(e){this._clearTimeout.value=(0,s.disposableTimeout)(()=>{const e=Date.now()-this._lastAccessTimestamp;e>=15e3?this.clear():this._scheduleClearTimeout(15e3-e)},e)}}t.BufferLineStringCache=o},9384(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.getRangeLength=function(e,t){if(e.start.y>e.end.y)throw new Error(`Buffer range end (${e.end.x}, ${e.end.y}) cannot be before start (${e.start.x}, ${e.start.y})`);return t*(e.end.y-e.start.y)+(e.end.x-e.start.x+1)}},732(e,t){function i(e,t,i){if(t===e.length-1)return e[t].getTrimmedLength();const s=!e[t].hasContent(i-1)&&1===e[t].getWidth(i-1),r=2===e[t+1].getWidth(0);return s&&r?i-1:i}Object.defineProperty(t,"__esModule",{value:!0}),t.reflowLargerGetLinesToRemove=function(e,t,s,r,o,n){const a=[];for(let h=0;h=h&&r0&&(e>_||0===d[e].getTrimmedLength());e--)v++;v>0&&(a.push(h+d.length-v),a.push(v)),h+=d.length-1}return a},t.reflowLargerCreateNewLayout=function(e,t){const i=[];let s=0,r=t[s],o=0;for(let n=0;nl&&(n-=l,a++);const c=2===e[a].getWidth(n-1);c&&n--;const d=c?s-1:s;r.push(d),h+=d}return r},t.getWrappedLineTrimmedLength=i},4097(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.BufferSet=void 0;const s=i(4812),r=i(1073),o=i(8636);class n extends s.Disposable{constructor(e,t,i){super(),this._optionsService=e,this._bufferService=t,this._logService=i,this._normalBuffer=this._register(new s.MutableDisposable),this._altBuffer=this._register(new s.MutableDisposable),this._onBufferActivate=this._register(new o.Emitter),this.onBufferActivate=this._onBufferActivate.event,this.reset(),this._register(this._optionsService.onSpecificOptionChange("scrollback",()=>this.resize(this._bufferService.cols,this._bufferService.rows))),this._register(this._optionsService.onSpecificOptionChange("tabStopWidth",()=>this.setupTabStops()))}reset(){this._normal=new r.Buffer(!0,this._optionsService,this._bufferService,this._logService),this._normalBuffer.value=this._normal,this._normal.fillViewportRows(),this._alt=new r.Buffer(!1,this._optionsService,this._bufferService,this._logService),this._altBuffer.value=this._alt,this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}}t.BufferSet=n},3055(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.CellData=void 0;const s=i(726),r=i(8938),o=i(5451);class n extends o.AttributeData{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new o.ExtendedAttrs,this.combinedData=""}static fromCharData(e){const t=new n;return t.setFromCharData(e),t}isCombined(){return 2097152&this.content}getWidth(){return this.content>>22}getChars(){return 2097152&this.content?this.combinedData:2097151&this.content?(0,s.stringFromCodePoint)(2097151&this.content):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):2097151&this.content}setFromCharData(e){this.fg=e[r.CHAR_DATA_ATTR_INDEX],this.bg=0;let t=!1;if(e[r.CHAR_DATA_CHAR_INDEX].length>2)t=!0;else if(2===e[r.CHAR_DATA_CHAR_INDEX].length){const i=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0);if(55296<=i&&i<=56319){const s=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(1);56320<=s&&s<=57343?this.content=1024*(i-55296)+s-56320+65536|e[r.CHAR_DATA_WIDTH_INDEX]<<22:t=!0}else t=!0}else this.content=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|e[r.CHAR_DATA_WIDTH_INDEX]<<22;t&&(this.combinedData=e[r.CHAR_DATA_CHAR_INDEX],this.content=2097152|e[r.CHAR_DATA_WIDTH_INDEX]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}attributesEquals(e){if(this.getFgColorMode()!==e.getFgColorMode()||this.getFgColor()!==e.getFgColor())return!1;if(this.getBgColorMode()!==e.getBgColorMode()||this.getBgColor()!==e.getBgColor())return!1;if(this.isInverse()!==e.isInverse())return!1;if(this.isBold()!==e.isBold())return!1;if(this.isUnderline()!==e.isUnderline())return!1;if(this.isUnderline()){if(this.getUnderlineStyle()!==e.getUnderlineStyle())return!1;const t=this.isUnderlineColorDefault(),i=e.isUnderlineColorDefault();if(!t||!i){if(t!==i)return!1;if(this.getUnderlineColor()!==e.getUnderlineColor())return!1;if(this.getUnderlineColorMode()!==e.getUnderlineColorMode())return!1}}return this.isOverline()===e.isOverline()&&this.isBlink()===e.isBlink()&&this.isInvisible()===e.isInvisible()&&this.isItalic()===e.isItalic()&&this.isDim()===e.isDim()&&this.isStrikethrough()===e.isStrikethrough()}}t.CellData=n},8938(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.WHITESPACE_CELL_CODE=t.WHITESPACE_CELL_WIDTH=t.WHITESPACE_CELL_CHAR=t.NULL_CELL_CODE=t.NULL_CELL_WIDTH=t.NULL_CELL_CHAR=t.CHAR_DATA_CODE_INDEX=t.CHAR_DATA_WIDTH_INDEX=t.CHAR_DATA_CHAR_INDEX=t.CHAR_DATA_ATTR_INDEX=t.DEFAULT_EXT=t.DEFAULT_ATTR=t.DEFAULT_COLOR=void 0,t.DEFAULT_COLOR=0,t.DEFAULT_ATTR=t.DEFAULT_COLOR<<9|256,t.DEFAULT_EXT=0,t.CHAR_DATA_ATTR_INDEX=0,t.CHAR_DATA_CHAR_INDEX=1,t.CHAR_DATA_WIDTH_INDEX=2,t.CHAR_DATA_CODE_INDEX=3,t.NULL_CELL_CHAR="",t.NULL_CELL_WIDTH=1,t.NULL_CELL_CODE=0,t.WHITESPACE_CELL_CHAR=" ",t.WHITESPACE_CELL_WIDTH=1,t.WHITESPACE_CELL_CODE=32},8158(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.Marker=void 0;const s=i(4812),r=i(8636);class o{get id(){return this._id}constructor(e){this.line=e,this.isDisposed=!1,this._disposables=[],this._id=o._nextId++,this._onDispose=this.register(new r.Emitter),this.onDispose=this._onDispose.event}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),(0,s.dispose)(this._disposables),this._disposables.length=0)}register(e){return this._disposables.push(e),e}}t.Marker=o,o._nextId=1},6760(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.DEFAULT_CHARSET=t.CHARSETS=void 0,t.CHARSETS={},t.DEFAULT_CHARSET=t.CHARSETS.B,t.CHARSETS[0]={"`":"◆",a:"▒",b:"␉",c:"␌",d:"␍",e:"␊",f:"°",g:"±",h:"␤",i:"␋",j:"┘",k:"┐",l:"┌",m:"└",n:"┼",o:"⎺",p:"⎻",q:"─",r:"⎼",s:"⎽",t:"├",u:"┤",v:"┴",w:"┬",x:"│",y:"≤",z:"≥","{":"π","|":"≠","}":"£","~":"·"},t.CHARSETS.A={"#":"£"},t.CHARSETS.B=void 0,t.CHARSETS[4]={"#":"£","@":"¾","[":"ij","\\":"½","]":"|","{":"¨","|":"f","}":"¼","~":"´"},t.CHARSETS.C=t.CHARSETS[5]={"[":"Ä","\\":"Ö","]":"Å","^":"Ü","`":"é","{":"ä","|":"ö","}":"å","~":"ü"},t.CHARSETS.R={"#":"£","@":"à","[":"°","\\":"ç","]":"§","{":"é","|":"ù","}":"è","~":"¨"},t.CHARSETS.Q={"@":"à","[":"â","\\":"ç","]":"ê","^":"î","`":"ô","{":"é","|":"ù","}":"è","~":"û"},t.CHARSETS.K={"@":"§","[":"Ä","\\":"Ö","]":"Ü","{":"ä","|":"ö","}":"ü","~":"ß"},t.CHARSETS.Y={"#":"£","@":"§","[":"°","\\":"ç","]":"é","`":"ù","{":"à","|":"ò","}":"è","~":"ì"},t.CHARSETS.E=t.CHARSETS[6]={"@":"Ä","[":"Æ","\\":"Ø","]":"Å","^":"Ü","`":"ä","{":"æ","|":"ø","}":"å","~":"ü"},t.CHARSETS.Z={"#":"£","@":"§","[":"¡","\\":"Ñ","]":"¿","{":"°","|":"ñ","}":"ç"},t.CHARSETS.H=t.CHARSETS[7]={"@":"É","[":"Ä","\\":"Ö","]":"Å","^":"Ü","`":"é","{":"ä","|":"ö","}":"å","~":"ü"},t.CHARSETS["="]={"#":"ù","@":"à","[":"é","\\":"ç","]":"ê","^":"î",_:"è","`":"ô","{":"ä","|":"ö","}":"ü","~":"û"}},706(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.evaluateKeyboardEvent=function(e,t,s,r){const o={type:0,cancel:!1,key:void 0},n=(e.shiftKey?1:0)|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:"UIKeyInputUpArrow"===e.key?o.key=t?"OA":"":"UIKeyInputLeftArrow"===e.key?o.key=t?"OD":"":"UIKeyInputRightArrow"===e.key?o.key=t?"OC":"":"UIKeyInputDownArrow"===e.key&&(o.key=t?"OB":"");break;case 8:o.key=e.ctrlKey?"\b":"",e.altKey&&(o.key=""+o.key);break;case 9:if(e.shiftKey){o.key="";break}o.key="\t",o.cancel=!0;break;case 13:"c"===e.key&&e.ctrlKey?o.key="":o.key=e.altKey?"\r":"\r",o.cancel=!0;break;case 27:o.key="",e.altKey&&(o.key=""),o.cancel=!0;break;case 37:if(e.metaKey)break;o.key=n?"[1;"+(n+1)+"D":t?"OD":"";break;case 39:if(e.metaKey)break;o.key=n?"[1;"+(n+1)+"C":t?"OC":"";break;case 38:if(e.metaKey)break;o.key=n?"[1;"+(n+1)+"A":t?"OA":"";break;case 40:if(e.metaKey)break;o.key=n?"[1;"+(n+1)+"B":t?"OB":"";break;case 45:e.shiftKey||e.ctrlKey||(o.key="[2~");break;case 46:o.key=n?"[3;"+(n+1)+"~":"[3~";break;case 36:o.key=n?"[1;"+(n+1)+"H":t?"OH":"";break;case 35:o.key=n?"[1;"+(n+1)+"F":t?"OF":"";break;case 33:e.shiftKey?o.type=2:e.ctrlKey?o.key="[5;"+(n+1)+"~":o.key="[5~";break;case 34:e.shiftKey?o.type=3:e.ctrlKey?o.key="[6;"+(n+1)+"~":o.key="[6~";break;case 112:o.key=n?"[1;"+(n+1)+"P":"OP";break;case 113:o.key=n?"[1;"+(n+1)+"Q":"OQ";break;case 114:o.key=n?"[1;"+(n+1)+"R":"OR";break;case 115:o.key=n?"[1;"+(n+1)+"S":"OS";break;case 116:o.key=n?"[15;"+(n+1)+"~":"[15~";break;case 117:o.key=n?"[17;"+(n+1)+"~":"[17~";break;case 118:o.key=n?"[18;"+(n+1)+"~":"[18~";break;case 119:o.key=n?"[19;"+(n+1)+"~":"[19~";break;case 120:o.key=n?"[20;"+(n+1)+"~":"[20~";break;case 121:o.key=n?"[21;"+(n+1)+"~":"[21~";break;case 122:o.key=n?"[23;"+(n+1)+"~":"[23~";break;case 123:o.key=n?"[24;"+(n+1)+"~":"[24~";break;default:if(!e.ctrlKey||e.shiftKey||e.altKey||e.metaKey)if(s&&!r||!e.altKey||e.metaKey)if(!s||e.altKey||e.ctrlKey||e.shiftKey||!e.metaKey){if(e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&1===e.key.length)o.key=e.key;else if(e.key&&e.ctrlKey&&e.shiftKey)switch(e.code){case"Minus":o.key="";break;case"Digit2":o.key="\0";break;case"Digit6":o.key=""}}else 65===e.keyCode&&(o.type=1);else{const t=i[e.keyCode],s=t?.[e.shiftKey?1:0];if(s)o.key=""+s;else if(e.keyCode>=65&&e.keyCode<=90){const t=e.ctrlKey?e.keyCode-64:e.keyCode+32;let i=String.fromCharCode(t);e.shiftKey&&(i=i.toUpperCase()),o.key=""+i}else if(32===e.keyCode)o.key=""+(e.ctrlKey?"\0":" ");else if("Dead"===e.key&&e.code.startsWith("Key")){let t=e.code.slice(3,4);e.shiftKey||(t=t.toLowerCase()),o.key=""+t,o.cancel=!0}}else e.keyCode>=65&&e.keyCode<=90?o.key=String.fromCharCode(e.keyCode-64):32===e.keyCode?o.key="\0":e.keyCode>=51&&e.keyCode<=55?o.key=String.fromCharCode(e.keyCode-51+27):56===e.keyCode?o.key="":"/"===e.key?o.key="":219===e.keyCode?o.key="":220===e.keyCode?o.key="":221===e.keyCode&&(o.key="")}return o};const i={48:["0",")"],49:["1","!"],50:["2","@"],51:["3","#"],52:["4","$"],53:["5","%"],54:["6","^"],55:["7","&"],56:["8","*"],57:["9","("],186:[";",":"],187:["=","+"],188:[",","<"],189:["-","_"],190:[".",">"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']}},7241(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.KittyKeyboard=void 0,t.KittyKeyboard=class{constructor(){this._functionalKeyCodes={Escape:27,Enter:13,Tab:9,Backspace:127,CapsLock:57358,ScrollLock:57359,NumLock:57360,PrintScreen:57361,Pause:57362,ContextMenu:57363,F13:57376,F14:57377,F15:57378,F16:57379,F17:57380,F18:57381,F19:57382,F20:57383,F21:57384,F22:57385,F23:57386,F24:57387,F25:57388,KP_0:57399,KP_1:57400,KP_2:57401,KP_3:57402,KP_4:57403,KP_5:57404,KP_6:57405,KP_7:57406,KP_8:57407,KP_9:57408,KP_Decimal:57409,KP_Divide:57410,KP_Multiply:57411,KP_Subtract:57412,KP_Add:57413,KP_Enter:57414,KP_Equal:57415,ShiftLeft:57441,ShiftRight:57447,ControlLeft:57442,ControlRight:57448,AltLeft:57443,AltRight:57449,MetaLeft:57444,MetaRight:57450,MediaPlayPause:57430,MediaStop:57432,MediaTrackNext:57435,MediaTrackPrevious:57436,AudioVolumeDown:57438,AudioVolumeUp:57439,AudioVolumeMute:57440},this._csiTildeKeys={Insert:2,Delete:3,PageUp:5,PageDown:6,F5:15,F6:17,F7:18,F8:19,F9:20,F10:21,F11:23,F12:24},this._csiLetterKeys={ArrowUp:"A",ArrowDown:"B",ArrowRight:"C",ArrowLeft:"D",Home:"H",End:"F"},this._ss3FunctionKeys={F1:"P",F2:"Q",F3:"R",F4:"S"}}_getNumpadKeyCode(e){if(e.code.startsWith("Numpad")){const t=e.code.slice(6);if(t>="0"&&t<="9")return 57399+parseInt(t,10);switch(t){case"Decimal":return 57409;case"Divide":return 57410;case"Multiply":return 57411;case"Subtract":return 57412;case"Add":return 57413;case"Enter":return 57414;case"Equal":return 57415}}}_getModifierKeyCode(e){switch(e.code){case"ShiftLeft":return 57441;case"ShiftRight":return 57447;case"ControlLeft":return 57442;case"ControlRight":return 57448;case"AltLeft":return 57443;case"AltRight":return 57449;case"MetaLeft":return 57444;case"MetaRight":return 57450}}_encodeModifiers(e){let t=0;return e.shiftKey&&(t|=1),e.altKey&&(t|=2),e.ctrlKey&&(t|=4),e.metaKey&&(t|=8),t>0?t+1:0}_getKeyCode(e,t){const i=this._getNumpadKeyCode(e);if(void 0!==i)return i;const s=this._getModifierKeyCode(e);if(void 0!==s)return s;const r=this._functionalKeyCodes[e.key];if(void 0!==r)return r;if((e.shiftKey||t&&e.altKey)&&e.code){if(e.code.startsWith("Digit")&&6===e.code.length){const t=e.code.charAt(5);if(t>="0"&&t<="9")return t.charCodeAt(0)}if(e.code.startsWith("Key")&&4===e.code.length)return e.code.charAt(3).toLowerCase().charCodeAt(0)}if(1===e.key.length){const t=e.key.codePointAt(0);return t>=65&&t<=90?t+32:t}}_isModifierKey(e){return"Shift"===e.key||"Control"===e.key||"Alt"===e.key||"Meta"===e.key}_isLockKey(e){return"CapsLock"===e.key||"NumLock"===e.key||"ScrollLock"===e.key}_buildCsiLetterSequence(e,t,i,s){const r=s&&1!==i;if(t>0||r){let s="[1;"+(t>0?t:"1");return r&&(s+=":"+i),s+=e,s}return"["+e}_buildSs3Sequence(e,t,i,s){const r=s&&1!==i;if(t>0||r){let s="[1;"+(t>0?t:"1");return r&&(s+=":"+i),s+=e,s}return"O"+e}_buildCsiTildeSequence(e,t,i,s){const r=s&&1!==i;let o="["+e;return(t>0||r)&&(o+=";"+(t>0?t:"1"),r&&(o+=":"+i)),o+="~",o}_buildCsiUSequence(e,t,i,s,r,o,n){const a=!!(2&r);let h,l="["+t;4&r&&e.shiftKey&&1===e.key.length&&!o&&!n&&(h=e.key.codePointAt(0),l+=":"+h);const c=16&r&&3!==s&&1===e.key.length&&!o&&!n&&!e.ctrlKey?e.key.codePointAt(0):void 0,d=a&&1!==s&&(3===s||void 0===c);return(i>0||d||void 0!==c)&&(l+=";",i>0?l+=i:d&&(l+="1"),d&&(l+=":"+s)),void 0!==c&&(l+=";"+c),l+="u",l}evaluate(e,t,i=1,s=!1){const r={type:0,cancel:!1,key:void 0},o=this._encodeModifiers(e),n=this._isModifierKey(e),a=!!(2&t);if(!a&&3===i)return r;if(n&&!(8&t))return r;if(this._isLockKey(e)&&!(8&t))return r;const h=this._csiLetterKeys[e.key];if(h)return r.key=this._buildCsiLetterSequence(h,o,i,a),r.cancel=!0,r;const l=this._ss3FunctionKeys[e.key];if(l)return r.key=this._buildSs3Sequence(l,o,i,a),r.cancel=!0,r;const c=this._csiTildeKeys[e.key];if(void 0!==c)return r.key=this._buildCsiTildeSequence(c,o,i,a),r.cancel=!0,r;const d=this._getKeyCode(e,s);if(void 0===d)return r;const _=13===d||9===d||127===d;if(_&&3===i&&!(8&t))return r;const u=void 0!==this._functionalKeyCodes[e.key]||void 0!==this._getNumpadKeyCode(e);if(8&t||a&&3===i||(1&t||a)&&(u&&!_||o>0&&1!==e.key.length||o-1>1))r.key=this._buildCsiUSequence(e,d,o,i,t,u,n),r.cancel=!0;else{const t=13===d?"\r":9===d?"\t":127===d?"":void 0;t?r.key=t:1!==e.key.length||e.ctrlKey||e.altKey||e.metaKey||(r.key=e.key)}return r}static shouldUseProtocol(e){return e>0}}},726(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Utf8ToUtf32=t.StringToUtf32=void 0,t.stringFromCodePoint=function(e){return e>65535?(e-=65536,String.fromCharCode(55296+(e>>10))+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)},t.utf32ToString=function(e,t=0,i=e.length){let s="";for(let r=t;r65535?(t-=65536,s+=String.fromCharCode(55296+(t>>10))+String.fromCharCode(t%1024+56320)):s+=String.fromCharCode(t)}return s},t.StringToUtf32=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){const i=e.length;if(!i)return 0;let s=0,r=0;if(this._interim){const i=e.charCodeAt(r++);56320<=i&&i<=57343?t[s++]=1024*(this._interim-55296)+i-56320+65536:(t[s++]=this._interim,t[s++]=i),this._interim=0}for(let o=r;o=i)return this._interim=r,s;const n=e.charCodeAt(o);56320<=n&&n<=57343?t[s++]=1024*(r-55296)+n-56320+65536:(t[s++]=r,t[s++]=n);continue}65279!==r&&(t[s++]=r)}return s}},t.Utf8ToUtf32=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){const i=e.length;if(!i)return 0;let s,r,o,n,a,h=0,l=0;if(this.interim[0]){let s=!1,r=this.interim[0];r&=192==(224&r)?31:224==(240&r)?15:7;let o,n=0;for(;(o=this.interim[++n])&&n<4;)r<<=6,r|=63&o;const a=192==(224&this.interim[0])?2:224==(240&this.interim[0])?3:4,c=a-n;for(;l=i)return 0;if(o=e[l++],128!=(192&o)){l--,s=!0;break}this.interim[n++]=o,r<<=6,r|=63&o}s||(2===a?r<128?l--:t[h++]=r:3===a?r<2048||r>=55296&&r<=57343||65279===r||(t[h++]=r):r<65536||r>1114111||(t[h++]=r)),this.interim.fill(0)}const c=i-4;let d=l;for(;d=i)return this.interim[0]=s,h;if(r=e[d++],128!=(192&r)){d--;continue}if(a=(31&s)<<6|63&r,a<128){d--;continue}t[h++]=a}else if(224==(240&s)){if(d>=i)return this.interim[0]=s,h;if(r=e[d++],128!=(192&r)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,h;if(o=e[d++],128!=(192&o)){d--;continue}if(a=(15&s)<<12|(63&r)<<6|63&o,a<2048||a>=55296&&a<=57343||65279===a)continue;t[h++]=a}else if(240==(248&s)){if(d>=i)return this.interim[0]=s,h;if(r=e[d++],128!=(192&r)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,h;if(o=e[d++],128!=(192&o)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,this.interim[2]=o,h;if(n=e[d++],128!=(192&n)){d--;continue}if(a=(7&s)<<18|(63&r)<<12|(63&o)<<6|63&n,a<65536||a>1114111)continue;t[h++]=a}}return h}}},7428(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV6=void 0;const s=i(6415),r=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],o=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]];let n;t.UnicodeV6=class{constructor(){if(this.version="6",!n){n=new Uint8Array(65536),n.fill(1),n[0]=0,n.fill(0,1,32),n.fill(0,127,160),n.fill(2,4352,4448),n[9001]=2,n[9002]=2,n.fill(2,11904,42192),n[12351]=1,n.fill(2,44032,55204),n.fill(2,63744,64256),n.fill(2,65040,65050),n.fill(2,65072,65136),n.fill(2,65280,65377),n.fill(2,65504,65511);for(let e=0;et[r][1])return!1;for(;r>=s;)if(i=s+r>>1,e>t[i][1])s=i+1;else{if(!(e=131072&&e<=196605||e>=196608&&e<=262141?2:1}charProperties(e,t){let i=this.wcwidth(e),r=0===i&&0!==t;if(r){const e=s.UnicodeService.extractWidth(t);0===e?r=!1:e>i&&(i=e)}return s.UnicodeService.createPropertyValue(0,i,r)}}},9249(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Win32InputMode=void 0,t.Win32InputMode=class{constructor(){this._codeToVk={KeyA:65,KeyB:66,KeyC:67,KeyD:68,KeyE:69,KeyF:70,KeyG:71,KeyH:72,KeyI:73,KeyJ:74,KeyK:75,KeyL:76,KeyM:77,KeyN:78,KeyO:79,KeyP:80,KeyQ:81,KeyR:82,KeyS:83,KeyT:84,KeyU:85,KeyV:86,KeyW:87,KeyX:88,KeyY:89,KeyZ:90,Digit0:48,Digit1:49,Digit2:50,Digit3:51,Digit4:52,Digit5:53,Digit6:54,Digit7:55,Digit8:56,Digit9:57,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,F13:124,F14:125,F15:126,F16:127,F17:128,F18:129,F19:130,F20:131,F21:132,F22:133,F23:134,F24:135,Numpad0:96,Numpad1:97,Numpad2:98,Numpad3:99,Numpad4:100,Numpad5:101,Numpad6:102,Numpad7:103,Numpad8:104,Numpad9:105,NumpadMultiply:106,NumpadAdd:107,NumpadSeparator:108,NumpadSubtract:109,NumpadDecimal:110,NumpadDivide:111,NumpadEnter:13,NumLock:144,ArrowUp:38,ArrowDown:40,ArrowLeft:37,ArrowRight:39,Home:36,End:35,PageUp:33,PageDown:34,Insert:45,Delete:46,ShiftLeft:16,ShiftRight:16,ControlLeft:17,ControlRight:17,AltLeft:18,AltRight:18,MetaLeft:91,MetaRight:92,CapsLock:20,ScrollLock:145,Escape:27,Enter:13,Tab:9,Space:32,Backspace:8,Pause:19,ContextMenu:93,PrintScreen:44,Semicolon:186,Equal:187,Comma:188,Minus:189,Period:190,Slash:191,Backquote:192,BracketLeft:219,Backslash:220,BracketRight:221,Quote:222,IntlBackslash:226},this._codeToScancode={KeyQ:16,KeyW:17,KeyE:18,KeyR:19,KeyT:20,KeyY:21,KeyU:22,KeyI:23,KeyO:24,KeyP:25,KeyA:30,KeyS:31,KeyD:32,KeyF:33,KeyG:34,KeyH:35,KeyJ:36,KeyK:37,KeyL:38,KeyZ:44,KeyX:45,KeyC:46,KeyV:47,KeyB:48,KeyN:49,KeyM:50,Digit1:2,Digit2:3,Digit3:4,Digit4:5,Digit5:6,Digit6:7,Digit7:8,Digit8:9,Digit9:10,Digit0:11,F1:59,F2:60,F3:61,F4:62,F5:63,F6:64,F7:65,F8:66,F9:67,F10:68,F11:87,F12:88,Numpad0:82,Numpad1:79,Numpad2:80,Numpad3:81,Numpad4:75,Numpad5:76,Numpad6:77,Numpad7:71,Numpad8:72,Numpad9:73,NumpadMultiply:55,NumpadAdd:78,NumpadSubtract:74,NumpadDecimal:83,NumpadDivide:53,NumpadEnter:28,NumLock:69,ArrowUp:72,ArrowDown:80,ArrowLeft:75,ArrowRight:77,Home:71,End:79,PageUp:73,PageDown:81,Insert:82,Delete:83,ShiftLeft:42,ShiftRight:54,ControlLeft:29,ControlRight:29,AltLeft:56,AltRight:56,CapsLock:58,ScrollLock:70,Escape:1,Enter:28,Tab:15,Space:57,Backspace:14,Pause:69,Semicolon:39,Equal:13,Comma:51,Minus:12,Period:52,Slash:53,Backquote:41,BracketLeft:26,Backslash:43,BracketRight:27,Quote:40},this._enhancedKeyCodes=new Set(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","PageUp","PageDown","Insert","Delete","NumpadEnter","NumpadDivide","ControlRight","AltRight","PrintScreen","Pause","ContextMenu","MetaLeft","MetaRight"]),this._keyToControlChar={Enter:13,Backspace:8,Tab:9,Escape:27}}_getVirtualKeyCode(e){const t=this._codeToVk[e.code];return void 0!==t?t:e.keyCode||0}_getScanCode(e){return this._codeToScancode[e.code]||0}_getUnicodeChar(e){if(e.ctrlKey&&!e.altKey&&!e.metaKey){if("Enter"===e.key)return 10;if("Backspace"===e.key)return 127}const t=this._keyToControlChar[e.key];if(void 0!==t)return t;if(1===e.key.length){const t=e.key.codePointAt(0)||0;if(e.ctrlKey&&!e.altKey&&!e.metaKey){if(t>=65&&t<=90)return t-64;if(t>=97&&t<=122)return t-96}return t}return 0}_getControlKeyState(e){let t=0;return e.shiftKey&&(t|=16),e.ctrlKey&&("ControlRight"===e.code?t|=4:t|=8),e.altKey&&("AltRight"===e.code?t|=1:t|=2),this._enhancedKeyCodes.has(e.code)&&(t|=256),t}evaluateKeyboardEvent(e,t){return{type:0,cancel:!0,key:`[${this._getVirtualKeyCode(e)};${this._getScanCode(e)};${this._getUnicodeChar(e)};${t?1:0};${this._getControlKeyState(e)};1_`}}}},3562(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.WriteBuffer=void 0;const s=i(3132),r=i(4812),o=i(8636);class n extends r.Disposable{constructor(e){super(),this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0,this._isSyncWriting=!1,this._syncCalls=0,this._didUserInput=!1,this._innerWriteTimer=this._register(new s.TimeoutTimer),this._onWriteParsed=this._register(new o.Emitter),this.onWriteParsed=this._onWriteParsed.event,this._register((0,r.toDisposable)(()=>{this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0}))}handleUserInput(){this._didUserInput=!0}flushSync(){if(this._store.isDisposed)return;if(this._isSyncWriting)return;let e;this._isSyncWriting=!0;let t=!1;for(;e=this._writeBuffer.shift();){t=!0,this._action(e);const i=this._callbacks.shift();i&&i()}this._pendingData=0,this._bufferOffset=2147483647,this._writeBuffer.length=0,this._callbacks.length=0,this._isSyncWriting=!1,t&&this._onWriteParsed.fire()}writeSync(e,t){if(this._store.isDisposed)return;if(void 0!==t&&this._syncCalls>t)return void(this._syncCalls=0);if(this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;let i;for(this._isSyncWriting=!0;i=this._writeBuffer.shift();){this._action(i);const e=this._callbacks.shift();e&&e()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,t){if(!this._store.isDisposed){if(this._pendingData>5e7)throw new Error("write data discarded, use flow control to avoid losing data");if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput)return this._didUserInput=!1,this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t),void this._innerWrite();this._scheduleInnerWrite()}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)}}_scheduleInnerWrite(e=0,t=!0){this._store.isDisposed||this._innerWriteTimer.cancelAndSet(()=>this._innerWrite(e,t),0)}_innerWrite(e=0,t=!0){if(this._store.isDisposed)return;const i=e||performance.now();for(;this._writeBuffer.length>this._bufferOffset;){const e=this._writeBuffer[this._bufferOffset],s=this._action(e,t);if(s){const e=e=>{this._store.isDisposed||(performance.now()-i>=12?this._scheduleInnerWrite(0,e):this._innerWrite(i,e))};return void s.catch(e=>(queueMicrotask(()=>{throw e}),Promise.resolve(!1))).then(e)}const r=this._callbacks[this._bufferOffset];if(r&&r(),this._bufferOffset++,this._pendingData-=e.length,performance.now()-i>=12)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>50&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),this._scheduleInnerWrite()):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}}t.WriteBuffer=n},8693(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.parseColor=function(e){if(!e)return;let t=e.toLowerCase();if(t.startsWith("rgb:")){t=t.slice(4);const e=i.exec(t);if(e){const t=e[1]?15:e[4]?255:e[7]?4095:65535;return[Math.round(parseInt(e[1]||e[4]||e[7]||e[10],16)/t*255),Math.round(parseInt(e[2]||e[5]||e[8]||e[11],16)/t*255),Math.round(parseInt(e[3]||e[6]||e[9]||e[12],16)/t*255)]}}else if(t.startsWith("#")&&(t=t.slice(1),s.exec(t)&&[3,6,9,12].includes(t.length))){const e=t.length/3,i=[0,0,0];for(let s=0;s<3;++s){const r=parseInt(t.slice(e*s,e*s+e),16);i[s]=1===e?r<<4:2===e?r:3===e?r>>4:r>>8}return i}},t.toRgbString=function(e,t=16){const[i,s,o]=e;return`rgb:${r(i,t)}/${r(s,t)}/${r(o,t)}`};const i=/^([\da-f])\/([\da-f])\/([\da-f])$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/,s=/^[\da-f]+$/;function r(e,t){const i=e.toString(16),s=i.length<2?"0"+i:i;switch(t){case 4:return i[0];case 8:return s;case 12:return(s+s).slice(0,3);default:return s+s}}},2607(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.ApcHandler=t.ApcParser=void 0;const s=i(726),r=i(4220),o=[];t.ApcParser=class{constructor(){this._handlers=Object.create(null),this._active=o,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){this._handlers[e]??=[];const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=o}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=o,this._ident=0}start(e){if(this.reset(),this._ident=e,this._active=this._handlers[e]||o,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].start();else this._handlerFb(this._ident,"START")}put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._ident,"PUT",(0,s.utf32ToString)(e,t,i))}end(e,t=!0){if(this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].end(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._ident,"END",e);this._active=o,this._ident=0}};class n{constructor(e){this._handler=e,this._data=new r.LimitedStringBuilder(n._payloadLimit),this._hitLimit=!1}start(){this._data.reset(),this._hitLimit=!1}put(e,t,i){this._hitLimit||this._data.append((0,s.utf32ToString)(e,t,i))&&(this._hitLimit=!0)}end(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data.toString()),t instanceof Promise))return t.then(e=>(this._data.reset(),this._hitLimit=!1,e));return this._data.reset(),this._hitLimit=!1,t}}t.ApcHandler=n,n._payloadLimit=1e7},9823(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DcsHandler=t.DcsParser=void 0;const s=i(726),r=i(7262),o=i(4220),n=[];t.DcsParser=class{constructor(){this._handlers=Object.create(null),this._active=n,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=n}registerHandler(e,t){this._handlers[e]??=[];const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].unhook(!1);this._stack.paused=!1,this._active=n,this._ident=0}hook(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||n,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].hook(t);else this._handlerFb(this._ident,"HOOK",t)}put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._ident,"PUT",(0,s.utf32ToString)(e,t,i))}unhook(e,t=!0){if(this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].unhook(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].unhook(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._ident,"UNHOOK",e);this._active=n,this._ident=0}};const a=new r.Params;a.addParam(0);class h{constructor(e){this._handler=e,this._data=new o.LimitedStringBuilder(h._payloadLimit),this._params=a,this._hitLimit=!1}hook(e){this._params=e.length>1||e.params[0]?e.clone():a,this._data.reset(),this._hitLimit=!1}put(e,t,i){this._hitLimit||this._data.append((0,s.utf32ToString)(e,t,i))&&(this._hitLimit=!0)}unhook(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data.toString(),this._params),t instanceof Promise))return t.then(e=>(this._params=a,this._data.reset(),this._hitLimit=!1,e));return this._params=a,this._data.reset(),this._hitLimit=!1,t}}t.DcsHandler=h,h._payloadLimit=1e7},6717(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.EscapeSequenceParser=t.VT500_TRANSITION_TABLE=t.TransitionTable=void 0;const s=i(4812),r=i(7262),o=i(1346),n=i(9823),a=i(2607);class h{constructor(e){this.table=new Uint16Array(e)}setDefault(e,t){this.table.fill(e<<8|t)}add(e,t,i,s){this.table[t<<8|e]=i<<8|s}addMany(e,t,i,s){for(let r=0;rt),i=(e,i)=>t.slice(e,i),s=i(32,127),r=i(0,24);r.push(25),r.push.apply(r,i(28,32));const o=i(0,17);e.setDefault(1,0),e.addMany(s,0,2,0);for(const t of o)e.addMany([24,26,153,154],t,3,0),e.addMany(i(128,144),t,3,0),e.addMany(i(144,152),t,3,0),e.add(156,t,0,0),e.add(27,t,11,1),e.add(157,t,4,8),e.addMany([152,158],t,0,7),e.add(159,t,11,14),e.add(155,t,11,3),e.add(144,t,11,9);return e.addMany(r,0,3,0),e.addMany(r,1,3,1),e.add(127,1,0,1),e.addMany(r,8,0,8),e.addMany(r,3,3,3),e.add(127,3,0,3),e.addMany(r,4,3,4),e.add(127,4,0,4),e.addMany(r,6,3,6),e.addMany(r,5,3,5),e.add(127,5,0,5),e.addMany(r,2,3,2),e.add(127,2,0,2),e.add(93,1,4,8),e.addMany(s,8,5,8),e.add(127,8,5,8),e.addMany([156,27,24,26,7],8,6,0),e.addMany(i(28,32),8,0,8),e.addMany([88,94],1,0,7),e.addMany(s,7,0,7),e.addMany(r,7,0,7),e.add(156,7,0,0),e.add(127,7,0,7),e.add(95,1,11,14),e.addMany(r,14,0,14),e.add(127,14,0,14),e.addMany(i(32,48),14,9,15),e.addMany(i(48,127),14,15,16),e.addMany(i(48,127),15,15,16),e.addMany(r,15,0,15),e.addMany(i(32,48),15,9,15),e.add(127,15,0,15),e.addMany(s,16,16,16),e.addMany(r,16,0,16),e.addMany(i(8,14),16,16,16),e.add(127,16,0,16),e.addMany([27,156,24,26],16,17,0),e.add(91,1,11,3),e.addMany(i(64,127),3,7,0),e.addMany(i(48,60),3,8,4),e.addMany([60,61,62,63],3,9,4),e.addMany(i(48,60),4,8,4),e.addMany(i(64,127),4,7,0),e.addMany([60,61,62,63],4,0,6),e.addMany(i(32,64),6,0,6),e.add(127,6,0,6),e.addMany(i(64,127),6,0,0),e.addMany(i(32,48),3,9,5),e.addMany(i(32,48),5,9,5),e.addMany(i(48,64),5,0,6),e.addMany(i(64,127),5,7,0),e.addMany(i(32,48),4,9,5),e.addMany(i(32,48),1,9,2),e.addMany(i(32,48),2,9,2),e.addMany(i(48,127),2,10,0),e.addMany(i(48,80),1,10,0),e.addMany(i(81,88),1,10,0),e.addMany([89,90,92],1,10,0),e.addMany(i(96,127),1,10,0),e.add(80,1,11,9),e.addMany(r,9,0,9),e.add(127,9,0,9),e.addMany(i(32,48),9,9,12),e.addMany(i(48,60),9,8,10),e.addMany([60,61,62,63],9,9,10),e.addMany(r,11,0,11),e.addMany(i(32,128),11,0,11),e.addMany(r,10,0,10),e.add(127,10,0,10),e.addMany(i(48,60),10,8,10),e.addMany([60,61,62,63],10,0,11),e.addMany(i(32,48),10,9,12),e.addMany(r,12,0,12),e.add(127,12,0,12),e.addMany(i(32,48),12,9,12),e.addMany(i(48,64),12,0,11),e.addMany(i(64,127),12,12,13),e.addMany(i(64,127),10,12,13),e.addMany(i(64,127),9,12,13),e.addMany(r,13,13,13),e.addMany(s,13,13,13),e.add(127,13,0,13),e.addMany([27,156,24,26],13,14,0),e.add(l,0,2,0),e.add(l,8,5,8),e.add(l,6,0,6),e.add(l,11,0,11),e.add(l,13,13,13),e.add(l,16,16,16),e}();class c extends s.Disposable{constructor(e=t.VT500_TRANSITION_TABLE){super(),this._transitions=e,this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0},this.initialState=0,this.currentState=this.initialState,this._params=new r.Params,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(e,t,i)=>{},this._executeHandlerFb=e=>{},this._csiHandlerFb=(e,t)=>{},this._escHandlerFb=e=>{},this._errorHandlerFb=e=>e,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._executeHandlersArr=new Array(24).fill(void 0),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this._register((0,s.toDisposable)(()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._executeHandlersArr=new Array(24).fill(void 0),this._escHandlers=Object.create(null)})),this._oscParser=this._register(new o.OscParser),this._dcsParser=this._register(new n.DcsParser),this._apcParser=this._register(new a.ApcParser),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:"\\"},()=>!0)}_identifier(e,t=[64,126]){let i=0;if(e.prefix){if(e.prefix.length>1)throw new Error("only one byte as prefix supported");if(i=e.prefix.charCodeAt(0),i<60||i>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(e.intermediates){if(e.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(let t=0;ts||s>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");i<<=8,i|=s}}if(1!==e.final.length)throw new Error("final must be a single byte");const s=e.final.charCodeAt(0);if(t[0]>s||s>t[1])throw new Error(`final must be in range ${t[0]} .. ${t[1]}`);return i<<=8,i|=s,i}identToString(e){const t=[];for(;e;)t.push(String.fromCharCode(255&e)),e>>=8;return t.reverse().join("")}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,t){const i=this._identifier(e,[48,126]);this._escHandlers[i]??=[];const s=this._escHandlers[i];return s.push(t),{dispose:()=>{const e=s.indexOf(t);-1!==e&&s.splice(e,1)}}}clearEscHandler(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]}setEscHandlerFallback(e){this._escHandlerFb=e}setExecuteHandler(e,t){const i=e.charCodeAt(0);this._executeHandlers[i]=t,i<24&&(this._executeHandlersArr[i]=t)}clearExecuteHandler(e){const t=e.charCodeAt(0);this._executeHandlers[t]&&delete this._executeHandlers[t],t<24&&(this._executeHandlersArr[t]=void 0)}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,t){const i=this._identifier(e);this._csiHandlers[i]??=[];const s=this._csiHandlers[i];return s.push(t),{dispose:()=>{const e=s.indexOf(t);-1!==e&&s.splice(e,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,t){return this._dcsParser.registerHandler(this._identifier(e),t)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,t){return this._oscParser.registerHandler(e,t)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}registerApcHandler(e,t){return e.prefix=void 0,this._apcParser.registerHandler(this._identifier(e,[48,126]),t)}clearApcHandler(e){e.prefix=void 0,this._apcParser.clearHandler(this._identifier(e,[48,126]))}setApcHandlerFallback(e){this._apcParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._apcParser.reset(),this._params.resetZdm(),this._collect=0,this.precedingJoinState=0,0!==this._parseStack.state&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(e,t,i,s,r){this._parseStack.state=e,this._parseStack.handlers=t,this._parseStack.handlerPos=i,this._parseStack.transition=s,this._parseStack.chunkPos=r}parse(e,t,i){let s,r,o,n=0;if(this._parseStack.state)if(2===this._parseStack.state)this._parseStack.state=0,n=this._parseStack.chunkPos+1;else{if(void 0===i||1===this._parseStack.state)throw this._parseStack.state=1,new Error("improper continuation due to previous async handler, giving up parsing");const t=this._parseStack.handlers;let r=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(!1===i&&r>-1)for(;r>=0&&(o=t[r](this._params),!0!==o);r--)if(o instanceof Promise)return this._parseStack.handlerPos=r,o;this._parseStack.handlers=[];break;case 4:if(!1===i&&r>-1)for(;r>=0&&(o=t[r](),!0!==o);r--)if(o instanceof Promise)return this._parseStack.handlerPos=r,o;this._parseStack.handlers=[];break;case 6:if(s=e[this._parseStack.chunkPos],o=this._dcsParser.unhook(24!==s&&26!==s,i),o)return o;27===s&&(this._parseStack.transition|=1),this._params.resetZdm(),this._collect=0;break;case 5:if(s=e[this._parseStack.chunkPos],o=this._oscParser.end(24!==s&&26!==s,i),o)return o;27===s&&(this._parseStack.transition|=1),this._params.resetZdm(),this._collect=0;break;case 7:if(s=e[this._parseStack.chunkPos],o=this._apcParser.end(24!==s&&26!==s,i),o)return o;27===s&&(this._parseStack.transition|=1),this._params.resetZdm(),this._collect=0}this._parseStack.state=0,n=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=255&this._parseStack.transition}for(let i=n;i=60&&n<=63&&(this._collect=n,s++);let a=!1;for(;s=48&&n<=57)this._params.addDigit(n-48);else if(59===n)this._params.addParam(0);else{if(58!==n){if(n>=64&&n<=126){const e=this._csiHandlers[this._collect<<8|n];let t=e?e.length-1:-1;for(;t>=0&&(o=e[t](this._params),!0!==o);t--)if(o instanceof Promise)return r=1792,this._preserveStack(3,e,t,r,s),o;t<0&&this._csiHandlerFb(this._collect<<8|n,this._params),this.precedingJoinState=0,i=s,this.currentState=0,a=!0;break}break}this._params.addSubParam(-1)}a||(i=s-1,this.currentState=4);continue}switch(r=this._transitions.table[this.currentState<<8|(s>8){case 2:let n=i;const a=t-4;for(;n=32&&(e[n]<=126||e[n]>=l)&&e[++n]>=32&&(e[n]<=126||e[n]>=l)&&e[++n]>=32&&(e[n]<=126||e[n]>=l)&&e[++n]>=32&&(e[n]<=126||e[n]>=l););if(n>=a)for(;n=32&&(e[n]<=126||e[n]>=l);)n++;this._printHandler(e,i,n),i=n-1;break;case 3:this._executeHandlers[s]?this._executeHandlers[s]():this._executeHandlerFb(s),this.precedingJoinState=0;break;case 0:break;case 1:if(this._errorHandler({position:i,code:s,currentState:this.currentState,collect:this._collect,params:this._params,abort:!1}).abort)return;break;case 7:const h=this._csiHandlers[this._collect<<8|s];let c=h?h.length-1:-1;for(;c>=0&&(o=h[c](this._params),!0!==o);c--)if(o instanceof Promise)return this._preserveStack(3,h,c,r,i),o;c<0&&this._csiHandlerFb(this._collect<<8|s,this._params),this.precedingJoinState=0;break;case 8:do{switch(s){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(s-48)}}while(++i47&&s<60);i--;break;case 9:this._collect<<=8,this._collect|=s;break;case 10:const d=this._escHandlers[this._collect<<8|s];let _=d?d.length-1:-1;for(;_>=0&&(o=d[_](),!0!==o);_--)if(o instanceof Promise)return this._preserveStack(4,d,_,r,i),o;_<0&&this._escHandlerFb(this._collect<<8|s),this.precedingJoinState=0;break;case 11:this._params.resetZdm(),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|s,this._params);break;case 13:for(let r=i+1;;++r)if(r>=t||24===(s=e[r])||26===s||27===s||s>127&&s=t||(s=e[r])<32||s>127&&s=32&&e[s]<127||e[s]>=8&&e[s]<14||e[s]>=l))){this._apcParser.put(e,i,s),i=s-1;break}break;case 17:if(o=this._apcParser.end(24!==s&&26!==s),o)return this._preserveStack(7,[],0,r,i),o;27===s&&(r|=1),this._params.resetZdm(),this._collect=0,this.precedingJoinState=0}this.currentState=255&r}}}t.EscapeSequenceParser=c},1346(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.OscHandler=t.OscParser=void 0;const s=i(726),r=i(4220),o=[];t.OscParser=class{constructor(){this._state=0,this._active=o,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){this._handlers[e]??=[];const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=o}reset(){if(2===this._state)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=o,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||o,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].start();else this._handlerFb(this._id,"START")}_put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._id,"PUT",(0,s.utf32ToString)(e,t,i))}start(){this.reset(),this._state=1}put(e,t,i){if(3!==this._state){if(1===this._state)for(;t0&&this._put(e,t,i)}}end(e,t=!0){if(0!==this._state){if(3!==this._state)if(1===this._state&&this._start(),this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].end(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._id,"END",e);this._active=o,this._id=-1,this._state=0}}};class n{constructor(e){this._handler=e,this._data=new r.LimitedStringBuilder(n._payloadLimit),this._hitLimit=!1}start(){this._data.reset(),this._hitLimit=!1}put(e,t,i){this._hitLimit||this._data.append((0,s.utf32ToString)(e,t,i))&&(this._hitLimit=!0)}end(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data.toString()),t instanceof Promise))return t.then(e=>(this._data.reset(),this._hitLimit=!1,e));return this._data.reset(),this._hitLimit=!1,t}}t.OscHandler=n,n._payloadLimit=1e7},7262(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Params=void 0;class i{static fromArray(e){const t=new i;if(!e.length)return t;for(let i=Array.isArray(e[0])?1:0;i256)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(e),this.length=0,this._subParams=new Int32Array(t),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(e),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}clone(){const e=new i(this.maxLength,this.maxSubParamsLength);return e.params.set(this.params),e.length=this.length,e._subParams.set(this._subParams),e._subParamsLength=this._subParamsLength,e._subParamsIdx.set(this._subParamsIdx),e._rejectDigits=this._rejectDigits,e._rejectSubDigits=this._rejectSubDigits,e._digitIsSub=this._digitIsSub,e}toArray(){const e=[];for(let t=0;t>8,s=255&this._subParamsIdx[t];s-i>0&&e.push(Array.prototype.slice.call(this._subParams,i,s))}return e}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}resetZdm(){this.length=1,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1,this._subParamsIdx[0]=0,this.params[0]=0}addParam(e){if(this._digitIsSub=!1,this.length>=this.maxLength)this._rejectDigits=!0;else{if(e<-1)throw new Error("values less than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=e>2147483647?2147483647:e}}addSubParam(e){if(this._digitIsSub=!0,this.length)if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength)this._rejectSubDigits=!0;else{if(e<-1)throw new Error("values less than -1 are not allowed");this._subParams[this._subParamsLength++]=e>2147483647?2147483647:e,this._subParamsIdx[this.length-1]++}}hasSubParams(e){return(255&this._subParamsIdx[e])-(this._subParamsIdx[e]>>8)>0}getSubParams(e){const t=this._subParamsIdx[e]>>8,i=255&this._subParamsIdx[e];return i-t>0?this._subParams.subarray(t,i):null}getSubParamsAll(){const e={};for(let t=0;t>8,s=255&this._subParamsIdx[t];s-i>0&&(e[t]=this._subParams.slice(i,s))}return e}addDigit(e){let t;if(this._rejectDigits||!(t=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;const i=this._digitIsSub?this._subParams:this.params,s=i[t-1];i[t-1]=~s?Math.min(10*s+e,2147483647):e}}t.Params=i},3027(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.AddonManager=void 0,t.AddonManager=class{constructor(){this._addons=[]}dispose(){for(let e=this._addons.length-1;e>=0;e--)this._addons[e].instance.dispose()}loadAddon(e,t){const i={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(i),t.dispose=()=>this._wrappedAddonDispose(i),t.activate(e)}_wrappedAddonDispose(e){if(e.isDisposed)return;let t=-1;for(let i=0;i=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new s.CellData)}translateToString(e,t,i){return this._line.translateToString(e,t,i)}}},5101(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.BufferNamespaceApi=void 0;const s=i(3235),r=i(4812),o=i(8636);class n extends r.Disposable{constructor(e){super(),this._core=e,this._onBufferChange=this._register(new o.Emitter),this.onBufferChange=this._onBufferChange.event,this._normal=new s.BufferApiView(this._core.buffers.normal,"normal"),this._alternate=new s.BufferApiView(this._core.buffers.alt,"alternate"),this._register(this._core.buffers.onBufferActivate(()=>this._onBufferChange.fire(this.active)))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw new Error("Active buffer is neither normal nor alternate")}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}}t.BufferNamespaceApi=n},6097(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ParserApi=void 0,t.ParserApi=class{constructor(e){this._core=e}registerCsiHandler(e,t){return this._core.registerCsiHandler(e,e=>t(e.toArray()))}addCsiHandler(e,t){return this.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._core.registerDcsHandler(e,(e,i)=>t(e,i.toArray()))}addDcsHandler(e,t){return this.registerDcsHandler(e,t)}registerEscHandler(e,t){return this._core.registerEscHandler(e,t)}addEscHandler(e,t){return this.registerEscHandler(e,t)}registerOscHandler(e,t){return this._core.registerOscHandler(e,t)}addOscHandler(e,t){return this.registerOscHandler(e,t)}registerApcHandler(e,t){return this._core.registerApcHandler(e,t)}}},4335(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeApi=void 0,t.UnicodeApi=class{constructor(e){this._core=e}register(e){this._core.unicodeService.register(e)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(e){this._core.unicodeService.activeVersion=e}}},9640(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.BufferService=void 0;const o=i(4812),n=i(4097),a=i(6501),h=i(8636);let l=class extends o.Disposable{get buffer(){return this.buffers.active}constructor(e,t){super(),this.isUserScrolling=!1,this._onResize=this._register(new h.Emitter),this.onResize=this._onResize.event,this._onScroll=this._register(new h.Emitter),this.onScroll=this._onScroll.event,this.cols=Math.max(e.rawOptions.cols||0,2),this.rows=Math.max(e.rawOptions.rows||0,1),this.buffers=this._register(new n.BufferSet(e,this,t)),this._register(this.buffers.onBufferActivate(e=>{this._onScroll.fire(e.activeBuffer.ydisp)}))}resize(e,t){const i=this.cols!==e,s=this.rows!==t;this.cols=e,this.rows=t,this.buffers.resize(e,t),this._onResize.fire({cols:e,rows:t,colsChanged:i,rowsChanged:s})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,t=!1){const i=this.buffer;let s;s=this._cachedBlankLine,s&&s.length===this.cols&&s.getFg(0)===e.fg&&s.getBg(0)===e.bg||(s=i.getBlankLine(e,t),this._cachedBlankLine=s),s.isWrapped=t;const r=i.ybase+i.scrollTop,o=i.ybase+i.scrollBottom;if(0===i.scrollTop){const e=i.lines.isFull;o===i.lines.length-1?e?i.lines.recycle().copyFrom(s):i.lines.push(s.clone()):i.lines.splice(o+1,0,s.clone()),e?this.isUserScrolling&&(i.ydisp=Math.max(i.ydisp-1,0)):(i.ybase++,this.isUserScrolling||i.ydisp++)}else{const e=o-r+1;i.lines.shiftElements(r+1,e-1,-1),i.lines.set(o,s.clone())}this.isUserScrolling||(i.ydisp=i.ybase),this._onScroll.fire(i.ydisp)}scrollLines(e,t){const i=this.buffer;if(e<0){if(0===i.ydisp)return;this.isUserScrolling=!0}else e+i.ydisp>=i.ybase&&(this.isUserScrolling=!1);const s=i.ydisp;i.ydisp=Math.max(Math.min(i.ydisp+e,i.ybase),0),s!==i.ydisp&&(t||this._onScroll.fire(i.ydisp))}};t.BufferService=l,t.BufferService=l=s([r(0,a.IOptionsService),r(1,a.ILogService)],l)},5746(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.CharsetService=void 0,t.CharsetService=class{constructor(){this.glevel=0,this._charsets=[]}get charsets(){return this._charsets}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(e){this.glevel=e,this.charset=this._charsets[e]}setgCharset(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)}}},4071(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CoreService=void 0;const o=i(4812),n=i(6501),a=i(8636),h=Object.freeze({insertMode:!1}),l=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,colorSchemeUpdates:!1,cursorBlink:void 0,cursorStyle:void 0,origin:!1,reverseWraparound:!1,sendFocus:!1,synchronizedOutput:!1,win32InputMode:!1,wraparound:!0});let c=class extends o.Disposable{constructor(e,t,i){super(),this._bufferService=e,this._logService=t,this._optionsService=i,this.isCursorHidden=!1,this._onData=this._register(new a.Emitter),this.onData=this._onData.event,this._onUserInput=this._register(new a.Emitter),this.onUserInput=this._onUserInput.event,this._onBinary=this._register(new a.Emitter),this.onBinary=this._onBinary.event,this._onRequestScrollToBottom=this._register(new a.Emitter),this.onRequestScrollToBottom=this._onRequestScrollToBottom.event,this.isCursorInitialized=i.rawOptions.showCursorImmediately??!1,this.modes=structuredClone(h),this.decPrivateModes=structuredClone(l),this.kittyKeyboard={flags:0,mainFlags:0,altFlags:0,mainStack:[],altStack:[]}}reset(){this.modes=structuredClone(h),this.decPrivateModes=structuredClone(l),this.kittyKeyboard={flags:0,mainFlags:0,altFlags:0,mainStack:[],altStack:[]}}triggerDataEvent(e,t=!1){if(this._optionsService.rawOptions.disableStdin)return;const i=this._bufferService.buffer;t&&this._optionsService.rawOptions.scrollOnUserInput&&i.ybase!==i.ydisp&&this._onRequestScrollToBottom.fire(),t&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`),this._logService.trace("sending data (codes)",()=>e.split("").map(e=>e.charCodeAt(0))),this._onData.fire(e)}triggerBinaryEvent(e){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${e}"`),this._logService.trace("sending binary (codes)",()=>e.split("").map(e=>e.charCodeAt(0))),this._onBinary.fire(e))}};t.CoreService=c,t.CoreService=c=s([r(0,n.IBufferService),r(1,n.ILogService),r(2,n.IOptionsService)],c)},4720(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DecorationLineCache=t.DecorationService=void 0;const o=i(3132),n=i(4103),a=i(4812),h=i(6501),l=i(3087),c=i(8636);let d=0,_=0,u=class extends a.Disposable{get decorations(){return this._decorations.values()}constructor(e,t){super(),this._logService=e,this._bufferService=t,this._lineCache=this._register(new f),this._onDecorationRegistered=this._register(new c.Emitter),this.onDecorationRegistered=this._onDecorationRegistered.event,this._onDecorationRemoved=this._register(new c.Emitter),this.onDecorationRemoved=this._onDecorationRemoved.event,this._decorations=new l.SortedList(e=>e?.marker.line,this._logService),this._register((0,a.toDisposable)(()=>this.reset())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._lineCache.attachToBufferLines(this._bufferService.buffer.lines)})),this._lineCache.attachToBufferLines(this._bufferService.buffer.lines)}registerDecoration(e){if(e.marker.isDisposed)return;const t=new p(e);if(t){const e=t.marker.onDispose(()=>t.dispose()),i=t.onDispose(()=>{i.dispose(),t&&(this._decorations.delete(t)&&(this._lineCache.remove(t),this._onDecorationRemoved.fire(t)),e.dispose())});this._decorations.insert(t),this._lineCache.add(t),this._onDecorationRegistered.fire(t)}return t}reset(){for(const e of this._decorations.values())e.dispose();this._decorations.clear(),this._lineCache.clear()}*getDecorationsAtCell(e,t,i){const s=this._lineCache.getDecorationsOnLine(t);if(s)for(const t of s)d=t.options.x??0,_=d+(t.options.width??1),e>=d&&e<_&&(!i||(t.options.layer??"bottom")===i)&&(yield t)}forEachDecorationAtCell(e,t,i,s){const r=this._lineCache.getDecorationsOnLine(t);if(r)for(const t of r)d=t.options.x??0,_=d+(t.options.width??1),e>=d&&e<_&&(!i||(t.options.layer??"bottom")===i)&&s(t)}};t.DecorationService=u,t.DecorationService=u=s([r(0,h.ILogService),r(1,h.IBufferService)],u);class f extends a.Disposable{constructor(){super(...arguments),this._decorationsByLine=new Map,this._decorations=new Set,this._bufferLineListeners=this._register(new a.MutableDisposable),this._lineIndexSyncTimer=this._register(new o.MicrotaskTimer),this._lineIndexSyncCallbacks=[]}clear(){this._lineIndexSyncCallbacks.length=0,this._lineIndexSyncTimer.cancel(),this._decorationsByLine.clear(),this._decorations.clear()}add(e){this._decorations.add(e),this._addToLineBuckets(e)}remove(e){this._decorations.delete(e),this._removeFromLineBuckets(e)}getDecorationsOnLine(e){return this._decorationsByLine.get(e)}attachToBufferLines(e){const t=new a.DisposableStore;this._bufferLineListeners.value=t,t.add(e.onTrim(e=>this._handleBufferLinesTrim(e))),t.add(e.onInsert(e=>this._handleBufferLinesInsert(e))),t.add(e.onDelete(e=>this._handleBufferLinesDelete(e)))}_getDecorationHeight(e){return e.options.height??1}_addToLineBuckets(e){const t=e.marker.line;if(t<0)return;e._indexedStartLine=t;const i=this._getDecorationHeight(e);for(let s=t;s=0&&this._addToLineBuckets(e)}_scheduleLineIndexSync(e){this._lineIndexSyncCallbacks.push(e),this._lineIndexSyncTimer.set(()=>{const e=this._lineIndexSyncCallbacks;this._lineIndexSyncCallbacks=[];for(const t of e)t()})}_handleBufferLinesTrim(e){if(e<=0)return;const t=new Map;for(const[i,s]of this._decorationsByLine){const r=i-e;r<0||this._mergeLineBucket(t,r,s)}this._decorationsByLine.clear();for(const[e,i]of t)this._decorationsByLine.set(e,i);for(const t of this._decorations)t.marker.isDisposed||(t._indexedStartLine-=e)}_handleBufferLinesInsert(e){this._scheduleLineIndexSync(()=>this._applyBufferLinesInsert(e))}_handleBufferLinesDelete(e){this._scheduleLineIndexSync(()=>this._applyBufferLinesDelete(e))}_mergeLineBucket(e,t,i){const s=e.get(t);if(s)for(let e=0,t=i.length;et&&(s.push(e),this._removeFromLineBuckets(e))}const r=new Map;for(const[e,s]of this._decorationsByLine){const o=e>=t?e+i:e;this._mergeLineBucket(r,o,s)}this._decorationsByLine.clear();for(const[e,t]of r)this._decorationsByLine.set(e,t);for(const e of this._decorations)e.marker.isDisposed||e._indexedStartLine>=t&&(e._indexedStartLine=e.marker.line);for(const e of s)this._addToLineBuckets(e)}_applyBufferLinesDelete(e){const t=e.index+e.amount,i=new Map;for(const[s,r]of this._decorationsByLine){if(s>=e.index&&s=t?s-e.amount:s;this._mergeLineBucket(i,o,r)}this._decorationsByLine.clear();for(const[e,t]of i)this._decorationsByLine.set(e,t);const s=[];for(const i of this._decorations){if(i.marker.isDisposed)continue;const r=i._indexedStartLine,o=this._getDecorationHeight(i);r>=t?i._indexedStartLine=i.marker.line:rt&&s.push(i)}for(const e of s)this._reindexDecoration(e)}}t.DecorationLineCache=f;class p extends a.DisposableStore{get backgroundColorRGB(){return null===this._cachedBg&&(this.options.backgroundColor?this._cachedBg=n.css.toColor(this.options.backgroundColor):this._cachedBg=void 0),this._cachedBg}get foregroundColorRGB(){return null===this._cachedFg&&(this.options.foregroundColor?this._cachedFg=n.css.toColor(this.options.foregroundColor):this._cachedFg=void 0),this._cachedFg}constructor(e){super(),this.options=e,this.onRenderEmitter=this.add(new c.Emitter),this.onRender=this.onRenderEmitter.event,this._onDispose=this.add(new c.Emitter),this.onDispose=this._onDispose.event,this._cachedBg=null,this._cachedFg=null,this.marker=e.marker,this._indexedStartLine=e.marker.line,this.options.overviewRulerOptions&&!this.options.overviewRulerOptions.position&&(this.options.overviewRulerOptions.position="full")}dispose(){this._onDispose.fire(),super.dispose()}}},6025(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.InstantiationService=t.ServiceCollection=void 0;const s=i(6501),r=i(6201);class o{constructor(...e){this._entries=new Map;for(const[t,i]of e)this.set(t,i)}set(e,t){const i=this._entries.get(e);return this._entries.set(e,t),i}forEach(e){for(const[t,i]of this._entries.entries())e(t,i)}has(e){return this._entries.has(e)}get(e){return this._entries.get(e)}}t.ServiceCollection=o,t.InstantiationService=class{constructor(){this._services=new o,this._services.set(s.IInstantiationService,this)}setService(e,t){this._services.set(e,t)}getService(e){return this._services.get(e)}createInstance(e,...t){const i=(0,r.getServiceDependencies)(e).sort((e,t)=>e.index-t.index),s=[];for(const t of i){const i=this._services.get(t.id);if(!i)throw new Error(`[createInstance] ${e.name} depends on UNKNOWN service ${t.id._id}.`);s.push(i)}const o=i.length>0?i[0].index:t.length;if(t.length!==o)throw new Error(`[createInstance] First service dependency of ${e.name} at position ${o+1} conflicts with ${t.length} static arguments`);return new e(...[...t,...s])}}},7276(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.LogService=void 0;const o=i(4812),n=i(6501),a={trace:n.LogLevelEnum.TRACE,debug:n.LogLevelEnum.DEBUG,info:n.LogLevelEnum.INFO,warn:n.LogLevelEnum.WARN,error:n.LogLevelEnum.ERROR,off:n.LogLevelEnum.OFF};let h=class extends o.Disposable{get logLevel(){return this._logLevel}constructor(e){super(),this._optionsService=e,this._logLevel=n.LogLevelEnum.OFF,this._updateLogLevel(),this._register(this._optionsService.onSpecificOptionChange("logLevel",()=>this._updateLogLevel()))}_updateLogLevel(){this._logLevel=a[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let t=0;t!1},X10:{events:1,restrict:e=>4!==e.button&&1===e.action&&(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)},VT200:{events:19,restrict:e=>32!==e.action},DRAG:{events:23,restrict:e=>32!==e.action||3!==e.button},ANY:{events:31,restrict:e=>!0}};function n(e,t){let i=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return 4===e.button?(i|=64,i|=e.action):(i|=3&e.button,4&e.button&&(i|=64),8&e.button&&(i|=128),32===e.action?i|=32:0!==e.action||t||(i|=3)),i}const a=String.fromCharCode,h={DEFAULT:e=>{const t=[n(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?"":`${a(t[0])}${a(t[1])}${a(t[2])}`},SGR:e=>{const t=0===e.action&&4!==e.button?"m":"M";return`[<${n(e,!0)};${e.col};${e.row}${t}`},SGR_PIXELS:e=>{const t=0===e.action&&4!==e.button?"m":"M";return`[<${n(e,!0)};${e.x};${e.y}${t}`}};class l extends s.Disposable{constructor(){super(),this._protocols={},this._encodings={},this._activeProtocol="",this._activeEncoding="",this._onProtocolChange=this._register(new r.Emitter),this.onProtocolChange=this._onProtocolChange.event;for(const e of Object.keys(o))this.addProtocol(e,o[e]);for(const e of Object.keys(h))this.addEncoding(e,h[e]);this.reset()}addProtocol(e,t){this._protocols[e]=t}addEncoding(e,t){this._encodings[e]=t}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return 0!==this._protocols[this._activeProtocol].events}set activeProtocol(e){if(!this._protocols[e])throw new Error(`unknown protocol "${e}"`);this._activeProtocol=e,this._onProtocolChange.fire(this._protocols[e].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(e){if(!this._encodings[e])throw new Error(`unknown encoding "${e}"`);this._activeEncoding=e}reset(){this.activeProtocol="NONE",this.activeEncoding="DEFAULT"}setCustomWheelEventHandler(e){this._customWheelEventHandler=e}allowCustomWheelEvent(e){return!this._customWheelEventHandler||!1!==this._customWheelEventHandler(e)}restrictMouseEvent(e){return this._protocols[this._activeProtocol].restrict(e)}encodeMouseEvent(e){return this._encodings[this._activeEncoding](e)}get isDefaultEncoding(){return"DEFAULT"===this._activeEncoding}get isPixelEncoding(){return"SGR_PIXELS"===this._activeEncoding}}t.MouseStateService=l},56(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.OptionsService=t.DEFAULT_OPTIONS=void 0;const s=i(4812),r=i(701),o=i(8636);t.DEFAULT_OPTIONS={cols:80,rows:24,showCursorImmediately:!1,cursorBlink:!1,blinkIntervalDuration:0,cursorStyle:"block",cursorWidth:1,cursorInactiveStyle:"outline",drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollSensitivity:5,fontFamily:"monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:"info",logger:null,scrollback:1e3,scrollbar:{showScrollbar:!0},scrollOnEraseInDisplay:!1,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,mouseEventsRequireAlt:!1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},reflowCursorLine:!1,rescaleOverlappingGlyphs:!1,rightClickSelectsWord:r.isMac,windowOptions:{},windowsPty:{},wordSeparator:" ()[]{}',\"`",altClickMovesCursor:!0,convertEol:!1,termName:"xterm",quirks:{},vtExtensions:{}};const n=["normal","bold","100","200","300","400","500","600","700","800","900"];class a extends s.Disposable{constructor(e){super(),this._onOptionChange=this._register(new o.Emitter),this.onOptionChange=this._onOptionChange.event;const i={...t.DEFAULT_OPTIONS};for(const t in e)if(t in i)try{const s=e[t];i[t]=this._sanitizeAndValidateOption(t,s)}catch(e){console.error(e)}this.rawOptions=i,this.options={...i},this._setupOptions(),this._register((0,s.toDisposable)(()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null}))}onSpecificOptionChange(e,t){return this.onOptionChange(i=>{i===e&&t(this.rawOptions[e])})}onMultipleOptionChange(e,t){return this.onOptionChange(i=>{-1!==e.indexOf(i)&&t()})}_setupOptions(){const e=e=>{if(!(e in t.DEFAULT_OPTIONS))throw new Error(`No option with key "${e}"`);return this.rawOptions[e]},i=(e,i)=>{if(!(e in t.DEFAULT_OPTIONS))throw new Error(`No option with key "${e}"`);i=this._sanitizeAndValidateOption(e,i),this.rawOptions[e]!==i&&(this.rawOptions[e]=i,this._onOptionChange.fire(e))};for(const t in this.rawOptions){const s={get:e.bind(this,t),set:i.bind(this,t)};Object.defineProperty(this.options,t,s)}}_sanitizeAndValidateOption(e,i){switch(e){case"cursorStyle":if(i||(i=t.DEFAULT_OPTIONS[e]),!function(e){return"block"===e||"underline"===e||"bar"===e}(i))throw new Error(`"${i}" is not a valid value for ${e}`);break;case"wordSeparator":i||(i=t.DEFAULT_OPTIONS[e]);break;case"fontWeight":case"fontWeightBold":if("number"==typeof i&&1<=i&&i<=1e3)break;i=n.includes(i)?i:t.DEFAULT_OPTIONS[e];break;case"blinkIntervalDuration":if((i=Math.floor(i))<0)throw new Error(`${e} cannot be less than 0, value: ${i}`);break;case"cursorWidth":i=Math.floor(i);case"lineHeight":case"tabStopWidth":if(i<1)throw new Error(`${e} cannot be less than 1, value: ${i}`);break;case"minimumContrastRatio":i=Math.max(1,Math.min(21,Math.round(10*i)/10));break;case"scrollback":if((i=Math.min(i,4294967295))<0)throw new Error(`${e} cannot be less than 0, value: ${i}`);break;case"fastScrollSensitivity":case"scrollSensitivity":if(i<=0)throw new Error(`${e} cannot be less than or equal to 0, value: ${i}`);break;case"rows":case"cols":if(!i&&0!==i)throw new Error(`${e} must be numeric, value: ${i}`);break;case"windowsPty":i=i??{}}return i}}t.OptionsService=a},8811(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OscLinkService=void 0;const o=i(6501);let n=class{constructor(e){this._bufferService=e,this._nextId=1,this._entriesWithId=new Map,this._dataByLinkId=new Map}registerLink(e){const t=this._bufferService.buffer;if(void 0===e.id){const i=t.addMarker(t.ybase+t.y),s={data:e,id:this._nextId++,lines:[i]};return i.onDispose(()=>this._removeMarkerFromLink(s,i)),this._dataByLinkId.set(s.id,s),s.id}const i=e,s=this._getEntryIdKey(i),r=this._entriesWithId.get(s);if(r)return this.addLineToLink(r.id,t.ybase+t.y),r.id;const o=t.addMarker(t.ybase+t.y),n={id:this._nextId++,key:this._getEntryIdKey(i),data:i,lines:[o]};return o.onDispose(()=>this._removeMarkerFromLink(n,o)),this._entriesWithId.set(n.key,n),this._dataByLinkId.set(n.id,n),n.id}addLineToLink(e,t){const i=this._dataByLinkId.get(e);if(i&&i.lines.every(e=>e.line!==t)){const e=this._bufferService.buffer.addMarker(t);i.lines.push(e),e.onDispose(()=>this._removeMarkerFromLink(i,e))}}getLinkData(e){return this._dataByLinkId.get(e)?.data}_getEntryIdKey(e){return`${e.id};;${e.uri}`}_removeMarkerFromLink(e,t){const i=e.lines.indexOf(t);-1!==i&&(e.lines.splice(i,1),0===e.lines.length&&(void 0!==e.data.id&&this._entriesWithId.delete(e.key),this._dataByLinkId.delete(e.id)))}};t.OscLinkService=n,t.OscLinkService=n=s([r(0,o.IBufferService)],n)},6201(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.serviceRegistry=void 0,t.getServiceDependencies=function(e){return e.di$dependencies||[]},t.createDecorator=function(e){if(t.serviceRegistry.has(e))return t.serviceRegistry.get(e);const i=function(e,t,s){if(3!==arguments.length)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");!function(e,t,i){t.di$target===t?t.di$dependencies.push({id:e,index:i}):(t.di$dependencies=[{id:e,index:i}],t.di$target=t)}(i,e,s)};return i._id=e,t.serviceRegistry.set(e,i),i},t.serviceRegistry=new Map},6501(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.IDecorationService=t.IUnicodeService=t.IOscLinkService=t.IOptionsService=t.ILogService=t.LogLevelEnum=t.IInstantiationService=t.ICharsetService=t.ICoreService=t.IMouseStateService=t.IBufferService=void 0;const s=i(6201);var r;t.IBufferService=(0,s.createDecorator)("BufferService"),t.IMouseStateService=(0,s.createDecorator)("MouseStateService"),t.ICoreService=(0,s.createDecorator)("CoreService"),t.ICharsetService=(0,s.createDecorator)("CharsetService"),t.IInstantiationService=(0,s.createDecorator)("InstantiationService"),function(e){e[e.TRACE=0]="TRACE",e[e.DEBUG=1]="DEBUG",e[e.INFO=2]="INFO",e[e.WARN=3]="WARN",e[e.ERROR=4]="ERROR",e[e.OFF=5]="OFF"}(r||(t.LogLevelEnum=r={})),t.ILogService=(0,s.createDecorator)("LogService"),t.IOptionsService=(0,s.createDecorator)("OptionsService"),t.IOscLinkService=(0,s.createDecorator)("OscLinkService"),t.IUnicodeService=(0,s.createDecorator)("UnicodeService"),t.IDecorationService=(0,s.createDecorator)("DecorationService")},6415(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeService=void 0;const s=i(8636);class r{constructor(){this._providers=Object.create(null),this._active="",this._onChange=new s.Emitter,this.onChange=this._onChange.event}static extractShouldJoin(e){return!!(1&e)}static extractWidth(e){return e>>1&3}static extractCharKind(e){return e>>3}static createPropertyValue(e,t,i=!1){return(16777215&e)<<3|(3&t)<<1|(i?1:0)}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(e){if(!this._providers[e])throw new Error(`unknown Unicode version "${e}"`);this._active=e,this._activeProvider=this._providers[e],this._onChange.fire(e)}register(e){this._providers[e.version]=e,this._active||(this.activeVersion=e.version)}wcwidth(e){return this._activeProvider.wcwidth(e)}getStringCellWidth(e){let t=0,i=0;const s=e.length;for(let o=0;o=s)return t+this.wcwidth(n);const i=e.charCodeAt(o);56320<=i&&i<=57343?n=1024*(n-55296)+i-56320+65536:t+=this.wcwidth(i)}const a=this.charProperties(n,i);let h=r.extractWidth(a);r.extractShouldJoin(a)&&(h-=r.extractWidth(i)),t+=h,i=a}return t}charProperties(e,t){return this._activeProvider.charProperties(e,t)}}t.UnicodeService=r}},t={};return function i(s){var r=t[s];if(void 0!==r)return r.exports;var o=t[s]={exports:{}};return e[s].call(o.exports,o,o.exports,i),o.exports}(6081)})()); + //# sourceMappingURL=xterm.js.map +\ No newline at end of file +diff --git a/lib/xterm.mjs b/lib/xterm.mjs +index 9bc5087a0729e74e20b59eddf5d8259e056de338..9fa15d1e81c591d9e8cffa621d37a4ed54b80a4f 100644 +--- a/lib/xterm.mjs ++++ b/lib/xterm.mjs +@@ -17,11 +17,11 @@ + var Ms=Object.defineProperty;var kn=Object.getOwnPropertyDescriptor;var Mn=(n,i)=>{for(var e in i)Ms(n,e,{get:i[e],enumerable:!0})};var y=(n,i,e,t)=>{for(var r=t>1?void 0:t?kn(i,e):i,s=n.length-1,o;s>=0;s--)(o=n[s])&&(r=(t?o(i,e,r):o(r))||r);return t&&r&&Ms(i,e,r),r},m=(n,i)=>(e,t)=>i(e,t,n);var Bs="Terminal input",Ut={get:()=>Bs,set:n=>Bs=n},Ps="Too much output to announce, navigate to rows manually to read",Ze={get:()=>Ps,set:n=>Ps=n};function Bn(n){return n.replace(/\r?\n/g,"\r")}function Pn(n,i){return i?`\x1B[200~${n.replace(/\x1b/g,"\u241B")}\x1B[201~`:n}function Os(n,i){n.clipboardData&&n.clipboardData.setData("text/plain",i.selectionText),n.preventDefault()}function Ns(n,i,e,t){if(n.stopPropagation(),n.clipboardData){let r=n.clipboardData.getData("text/plain");Nr(r,i,e,t)}}function Nr(n,i,e,t){n=Bn(n),n=Pn(n,e.decPrivateModes.bracketedPasteMode&&t.rawOptions.ignoreBracketedPasteMode!==!0),e.triggerDataEvent(n,!0),i.value=""}function Fr(n,i,e){let t=e.getBoundingClientRect(),r=n.clientX-t.left-10,s=n.clientY-t.top-10;i.style.width="20px",i.style.height="20px",i.style.left=`${r}px`,i.style.top=`${s}px`,i.style.zIndex="1000",i.focus()}function Hr(n,i,e,t,r){Fr(n,i,e),r&&t.rightClickSelect(n),i.value=t.selectionText,i.select()}function be(n){return n>65535?(n-=65536,String.fromCharCode((n>>10)+55296)+String.fromCharCode(n%1024+56320)):String.fromCharCode(n)}function ye(n,i=0,e=n.length){let t="";for(let r=i;r65535?(s-=65536,t+=String.fromCharCode((s>>10)+55296)+String.fromCharCode(s%1024+56320)):t+=String.fromCharCode(s)}return t}var mi=class{constructor(){this._interim=0}clear(){this._interim=0}decode(i,e){let t=i.length;if(!t)return 0;let r=0,s=0;if(this._interim){let o=i.charCodeAt(s++);56320<=o&&o<=57343?e[r++]=(this._interim-55296)*1024+o-56320+65536:(e[r++]=this._interim,e[r++]=o),this._interim=0}for(let o=s;o=t)return this._interim=a,r;let l=i.charCodeAt(o);56320<=l&&l<=57343?e[r++]=(a-55296)*1024+l-56320+65536:(e[r++]=a,e[r++]=l);continue}a!==65279&&(e[r++]=a)}return r}},bi=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(i,e){let t=i.length;if(!t)return 0;let r=0,s,o,a,l,h,d=0;if(this.interim[0]){let _=!1,p=this.interim[0];p&=(p&224)===192?31:(p&240)===224?15:7;let v=0,f;for(;(f=this.interim[++v])&&v<4;)p<<=6,p|=f&63;let S=(this.interim[0]&224)===192?2:(this.interim[0]&240)===224?3:4,I=S-v;for(;d=t)return 0;if(f=i[d++],(f&192)!==128){d--,_=!0;break}else this.interim[v++]=f,p<<=6,p|=f&63}_||(S===2?p<128?d--:e[r++]=p:S===3?p<2048||p>=55296&&p<=57343||p===65279||(e[r++]=p):p<65536||p>1114111||(e[r++]=p)),this.interim.fill(0)}let c=t-4,u=d;for(;u=t)return this.interim[0]=s,r;if(o=i[u++],(o&192)!==128){u--;continue}if(h=(s&31)<<6|o&63,h<128){u--;continue}e[r++]=h}else if((s&240)===224){if(u>=t)return this.interim[0]=s,r;if(o=i[u++],(o&192)!==128){u--;continue}if(u>=t)return this.interim[0]=s,this.interim[1]=o,r;if(a=i[u++],(a&192)!==128){u--;continue}if(h=(s&15)<<12|(o&63)<<6|a&63,h<2048||h>=55296&&h<=57343||h===65279)continue;e[r++]=h}else if((s&248)===240){if(u>=t)return this.interim[0]=s,r;if(o=i[u++],(o&192)!==128){u--;continue}if(u>=t)return this.interim[0]=s,this.interim[1]=o,r;if(a=i[u++],(a&192)!==128){u--;continue}if(u>=t)return this.interim[0]=s,this.interim[1]=o,this.interim[2]=a,r;if(l=i[u++],(l&192)!==128){u--;continue}if(h=(s&7)<<18|(o&63)<<12|(a&63)<<6|l&63,h<65536||h>1114111)continue;e[r++]=h}}return r}};var ue=class n{constructor(){this.fg=0;this.bg=0;this.extended=new ke}static toColorRGB(i){return[i>>>16&255,i>>>8&255,i&255]}static fromColorRGB(i){return(i[0]&255)<<16|(i[1]&255)<<8|i[2]&255}clone(){let i=new n;return i.fg=this.fg,i.bg=this.bg,i.extended=this.extended.clone(),i}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)===50331648}isBgRGB(){return(this.bg&50331648)===50331648}isFgPalette(){return(this.fg&50331648)===16777216||(this.fg&50331648)===33554432}isBgPalette(){return(this.bg&50331648)===16777216||(this.bg&50331648)===33554432}isFgDefault(){return(this.fg&50331648)===0}isBgDefault(){return(this.bg&50331648)===0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===16777216||(this.extended.underlineColor&50331648)===33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===0:this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},ke=class n{constructor(i=0,e=0){this._ext=0;this._urlId=0;this._ext=i,this._urlId=e}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(i){this._ext=i}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(i){this._ext&=-469762049,this._ext|=i<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(i){this._ext&=-67108864,this._ext|=i&67108863}get urlId(){return this._urlId}set urlId(i){this._urlId=i}get underlineVariantOffset(){let i=(this._ext&3758096384)>>29;return i<0?i^4294967288:i}set underlineVariantOffset(i){this._ext&=536870911,this._ext|=i<<29&3758096384}clone(){return new n(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}};var F=class n extends ue{constructor(){super(...arguments);this.content=0;this.fg=0;this.bg=0;this.extended=new ke;this.combinedData=""}static fromCharData(e){let t=new n;return t.setFromCharData(e),t}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?be(this.content&2097151):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(e){this.fg=e[0],this.bg=0;let t=!1;if(e[1].length>2)t=!0;else if(e[1].length===2){let r=e[1].charCodeAt(0);if(55296<=r&&r<=56319){let s=e[1].charCodeAt(1);56320<=s&&s<=57343?this.content=(r-55296)*1024+s-56320+65536|e[2]<<22:t=!0}else t=!0}else this.content=e[1].charCodeAt(0)|e[2]<<22;t&&(this.combinedData=e[1],this.content=2097152|e[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}attributesEquals(e){if(this.getFgColorMode()!==e.getFgColorMode()||this.getFgColor()!==e.getFgColor()||this.getBgColorMode()!==e.getBgColorMode()||this.getBgColor()!==e.getBgColor()||this.isInverse()!==e.isInverse()||this.isBold()!==e.isBold()||this.isUnderline()!==e.isUnderline())return!1;if(this.isUnderline()){if(this.getUnderlineStyle()!==e.getUnderlineStyle())return!1;let t=this.isUnderlineColorDefault(),r=e.isUnderlineColorDefault();if(!(t&&r)&&(t!==r||this.getUnderlineColor()!==e.getUnderlineColor()||this.getUnderlineColorMode()!==e.getUnderlineColorMode()))return!1}return!(this.isOverline()!==e.isOverline()||this.isBlink()!==e.isBlink()||this.isInvisible()!==e.isInvisible()||this.isItalic()!==e.isItalic()||this.isDim()!==e.isDim()||this.isStrikethrough()!==e.isStrikethrough())}};var zr=new Map;function Hs(n){return n.di$dependencies||[]}function H(n){if(zr.has(n))return zr.get(n);let i=function(e,t,r){if(arguments.length!==3)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");Fn(i,e,r)};return i._id=n,zr.set(n,i),i}function Fn(n,i,e){i.di$target===i?i.di$dependencies.push({id:n,index:e}):(i.di$dependencies=[{id:n,index:e}],i.di$target=i)}var D=H("BufferService"),Me=H("MouseStateService"),Y=H("CoreService"),Ws=H("CharsetService"),Qe=H("InstantiationService");var fe=H("LogService"),R=H("OptionsService"),vi=H("OscLinkService"),Us=H("UnicodeService"),ge=H("DecorationService");var et=class{constructor(i,e,t){this._bufferService=i;this._optionsService=e;this._oscLinkService=t;this._workCell=new F}provideLinks(i,e){let t=this._bufferService.buffer.lines.get(i-1);if(!t){e(void 0);return}let r=[],s=this._optionsService.rawOptions.linkHandler,o=this._workCell,a=t.getTrimmedLength(),l=-1,h=-1,d=!1;for(let c=0;cs?s.activate(f,S,p):Hn(f,S),hover:(f,S)=>s?.hover?.(f,S,p),leave:(f,S)=>s?.leave?.(f,S,p)})}d=!1,o.hasExtendedAttrs()&&o.extended.urlId?(h=c,l=o.extended.urlId):(h=-1,l=-1)}}e(r)}_getRangeWithLineWrap(i,e,t,r){let s=i,o=e,a=i,l=t;for(;o===0&&this._bufferService.buffer.lines.get(s-1)?.isWrapped;){let d=this._bufferService.buffer.lines.get(s-2);if(!d)break;let c=d.getTrimmedLength();if(c===0||!this._hasUrlId(d,c-1,r))break;let u=c-1;for(;u>0&&this._hasUrlId(d,u-1,r);)u--;s--,o=u}for(;;){let h=this._bufferService.buffer.lines.get(a-1);if(!h)break;let d=h.getTrimmedLength();if(l!==d)break;let c=this._bufferService.buffer.lines.get(a);if(!c?.isWrapped)break;let u=c.getTrimmedLength();if(u===0||!this._hasUrlId(c,0,r))break;let _=1;for(;_{n(),e&&r.dispose()},i),r=E(()=>{clearTimeout(t)});return e?.add(r),r}var Ie=class{constructor(){this._token=-1;this._isDisposed=!1}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._token!==-1&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(i,e){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout(()=>{this._token=-1,i()},e)}setIfNotSet(i,e){if(this._isDisposed)throw new Error("Calling setIfNotSet on a disposed TimeoutTimer");this._token===-1&&(this._token=setTimeout(()=>{this._token=-1,i()},e))}},Ci=class{constructor(){this._isScheduled=!1;this._isDisposed=!1}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._isScheduled=!1}set(i){if(this._isDisposed)throw new Error("Calling set on a disposed MicrotaskTimer");this._isScheduled||(this._isScheduled=!0,queueMicrotask(()=>{this._isScheduled&&(this._isScheduled=!1,i())}))}},Ei=class{constructor(){this._isDisposed=!1}cancel(){this._disposable?.dispose(),this._disposable=void 0}cancelAndSet(i,e,t=globalThis){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed IntervalTimer");this.cancel();let r=t.setInterval(()=>{i()},e);this._disposable={dispose:()=>{t.clearInterval(r),this._disposable=void 0}}}dispose(){this.cancel(),this._isDisposed=!0}};function se(n){let i=n;if(i?.ownerDocument?.defaultView)return i.ownerDocument.defaultView;let e=n;return e?.view?e.view:window}var Gr=class{constructor(i,e,t,r){this._node=i,this._type=e,this._handler=t,this._options=r,i.addEventListener(e,t,r)}dispose(){!this._node||!this._handler||(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,this._handler=null)}};function C(n,i,e,t){return new Gr(n,i,e,t)}function Vr(n,i,e,t){return C(n,i,e,t)}var le={CLICK:"click",MOUSE_DOWN:"mousedown",MOUSE_OVER:"mouseover",MOUSE_LEAVE:"mouseleave",KEY_DOWN:"keydown",KEY_UP:"keyup",INPUT:"input",BLUR:"blur",FOCUS:"focus",CHANGE:"change",POINTER_DOWN:"pointerdown",POINTER_MOVE:"pointermove",POINTER_UP:"pointerup",MOUSE_WHEEL:"wheel",WHEEL:"wheel"};function $s(n){let i=n.getBoundingClientRect(),e=se(n);return{left:i.left+e.scrollX,top:i.top+e.scrollY,width:i.width,height:i.height}}var yi=class{constructor(i,e){this._runner=i;this.priority=e;this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(i){console.error(i)}}static sort(i,e){return e.priority-i.priority}},Vs=new Map;function qs(n){let i=Vs.get(n);return i||(i={next:[],current:[],animFrameRequested:!1,inAnimationFrameRunner:!1},Vs.set(n,i)),i}function Wn(n){let i=qs(n);for(i.animFrameRequested=!1,i.current=i.next,i.next=[],i.inAnimationFrameRunner=!0;i.current.length>0;)i.current.sort(yi.sort),i.current.shift().execute();i.inAnimationFrameRunner=!1}function tt(n,i,e=0){let t=qs(n),r=new yi(i,e);return t.next.push(r),t.animFrameRequested||(t.animFrameRequested=!0,n.requestAnimationFrame(()=>Wn(n))),r}var xi=class extends Ei{constructor(i){super(),this._defaultTarget=i?se(i):void 0}cancelAndSet(i,e,t){super.cancelAndSet(i,e,t??this._defaultTarget??window)}};var we=class{constructor(i){this.domNode=i;this._width="";this._height="";this._top="";this._left="";this._bottom="";this._right="";this._className="";this._position="";this._layerHint=!1;this._contain="none"}setWidth(i){let e=rt(i);this._width!==e&&(this._width=e,this.domNode.style.width=this._width)}setHeight(i){let e=rt(i);this._height!==e&&(this._height=e,this.domNode.style.height=this._height)}setTop(i){let e=rt(i);this._top!==e&&(this._top=e,this.domNode.style.top=this._top)}setLeft(i){let e=rt(i);this._left!==e&&(this._left=e,this.domNode.style.left=this._left)}setBottom(i){let e=rt(i);this._bottom!==e&&(this._bottom=e,this.domNode.style.bottom=this._bottom)}setRight(i){let e=rt(i);this._right!==e&&(this._right=e,this.domNode.style.right=this._right)}setClassName(i){this._className!==i&&(this._className=i,this.domNode.className=this._className)}toggleClassName(i,e){this.domNode.classList.toggle(i,e),this._className=this.domNode.className}setPosition(i){this._position!==i&&(this._position=i,this.domNode.style.position=this._position)}setLayerHinting(i){this._layerHint!==i&&(this._layerHint=i,i?this.domNode.style.transform="translate3d(0px, 0px, 0px)":this.domNode.style.transform="")}setContain(i){this._contain!==i&&(this._contain=i,this.domNode.style.contain=this._contain)}setAttribute(i,e){this.domNode.setAttribute(i,e)}};function rt(n){return typeof n=="number"?`${n}px`:n}var Ke={};Mn(Ke,{getSafariVersion:()=>Kn,getZoomFactor:()=>Xr,isChrome:()=>Kt,isChromeOS:()=>Yr,isFirefox:()=>nt,isLegacyEdge:()=>Un,isLinux:()=>zt,isMac:()=>ie,isNode:()=>$r,isSafari:()=>wi,isWindows:()=>Ue});var $r=!!(typeof process<"u"&&"title"in process&&(typeof navigator>"u"||navigator.userAgent.startsWith("Node.js/"))),st=$r?"node":navigator.userAgent,qr=$r?"node":navigator.platform,nt=st.includes("Firefox"),Kt=st.includes("Chrome"),Un=st.includes("Edge"),wi=/^((?!chrome|android).)*safari/i.test(st);function Xr(n){return 1}function Kn(){if(!wi)return 0;let n=st.match(/Version\/(\d+)/);return n===null||n.length<2?0:parseInt(n[1],10)}var ie=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(qr),Ue=["Windows","Win16","Win32","WinCE"].includes(qr),zt=qr.indexOf("Linux")>=0,Yr=/\bCrOS\b/.test(st);var Xs=new WeakMap;function zn(n){if(!n.parent||n.parent===n)return null;try{let i=n.location,e=n.parent.location;if(i.origin!=="null"&&e.origin!=="null"&&i.origin!==e.origin)return null}catch{return null}return n.parent}var jr=class{static _getSameOriginWindowChain(i){let e=Xs.get(i);if(!e){e=[],Xs.set(i,e);let t=i,r;do r=zn(t),r?e.push({window:new WeakRef(t),iframeElement:t.frameElement??null}):e.push({window:new WeakRef(t),iframeElement:null}),t=r;while(t)}return e.slice(0)}static getPositionOfChildWindowRelativeToAncestorWindow(i,e){if(!e||i===e)return{top:0,left:0};let t=0,r=0,s=this._getSameOriginWindowChain(i);for(let o of s){let a=o.window.deref();if(t+=a?.scrollY??0,r+=a?.scrollX??0,a===e||!o.iframeElement)break;let l=o.iframeElement.getBoundingClientRect();t+=l.top,r+=l.left}return{top:t,left:r}}},ot=class{constructor(i,e){this.timestamp=Date.now(),this.browserEvent=e,this.leftButton=e.button===0,this.middleButton=e.button===1,this.rightButton=e.button===2,this.buttons=e.buttons,this.target=e.target,this.detail=e.detail??1,e.type==="dblclick"&&(this.detail=2),this.ctrlKey=e.ctrlKey,this.shiftKey=e.shiftKey,this.altKey=e.altKey,this.metaKey=e.metaKey,typeof e.pageX=="number"?(this.posx=e.pageX,this.posy=e.pageY):(this.posx=e.clientX+this.target.ownerDocument.body.scrollLeft+this.target.ownerDocument.documentElement.scrollLeft,this.posy=e.clientY+this.target.ownerDocument.body.scrollTop+this.target.ownerDocument.documentElement.scrollTop);let t=jr.getPositionOfChildWindowRelativeToAncestorWindow(i,e.view);this.posx-=t.left,this.posy-=t.top}preventDefault(){this.browserEvent.preventDefault()}stopPropagation(){this.browserEvent.stopPropagation()}},Gt=class{constructor(i,e=0,t=0){this.browserEvent=i??null,this.target=i?i.target??i.targetNode??i.srcElement??null:null,this.deltaY=t,this.deltaX=e;let r=!1;if(Kt){let s=navigator.userAgent.match(/Chrome\/(\d+)/);r=(s?parseInt(s[1],10):123)<=122}if(i){let s=i,o=i,a=i.view?.devicePixelRatio??1;if(typeof s.wheelDeltaY<"u")r?this.deltaY=s.wheelDeltaY/(120*a):this.deltaY=s.wheelDeltaY/120;else if(typeof o.VERTICAL_AXIS<"u"&&o.axis===o.VERTICAL_AXIS)this.deltaY=-o.detail/3;else if(i.type==="wheel"){let l=i;l.deltaMode===l.DOM_DELTA_LINE?nt&&!ie?this.deltaY=-i.deltaY/3:this.deltaY=-i.deltaY:this.deltaY=-i.deltaY/40}if(typeof s.wheelDeltaX<"u")wi&&Ue?this.deltaX=-(s.wheelDeltaX/120):r?this.deltaX=s.wheelDeltaX/(120*a):this.deltaX=s.wheelDeltaX/120;else if(typeof o.HORIZONTAL_AXIS<"u"&&o.axis===o.HORIZONTAL_AXIS)this.deltaX=-i.detail/3;else if(i.type==="wheel"){let l=i;l.deltaMode===l.DOM_DELTA_LINE?nt&&!ie?this.deltaX=-i.deltaX/3:this.deltaX=-i.deltaX:this.deltaX=-i.deltaX/40}this.deltaY===0&&this.deltaX===0&&i.wheelDelta&&(r?this.deltaY=i.wheelDelta/(120*a):this.deltaY=i.wheelDelta/120)}}preventDefault(){this.browserEvent?.preventDefault()}stopPropagation(){this.browserEvent?.stopPropagation()}};var at=class{constructor(){this._hooks=new pe;this._pointerMoveCallback=null;this._onStopCallback=null}dispose(){this.stopMonitoring(!1),this._hooks.dispose()}stopMonitoring(i){if(!this.isMonitoring())return;this._hooks.clear(),this._pointerMoveCallback=null;let e=this._onStopCallback;this._onStopCallback=null,i&&e&&e()}isMonitoring(){return!!this._pointerMoveCallback}startMonitoring(i,e,t,r,s){this.isMonitoring()&&this.stopMonitoring(!1),this._pointerMoveCallback=r,this._onStopCallback=s;let o=i;try{i.setPointerCapture(e),this._hooks.add(E(()=>{try{i.releasePointerCapture(e)}catch{}}))}catch{o=se(i)}this._hooks.add(C(o,le.POINTER_MOVE,a=>{if(a.buttons!==t){this.stopMonitoring(!0);return}a.preventDefault(),this._pointerMoveCallback(a)})),this._hooks.add(C(o,le.POINTER_UP,a=>this.stopMonitoring(!0)))}};var Ne=class extends g{_onclick(i,e){this._register(C(i,le.CLICK,t=>e(new ot(se(i),t))))}_onmouseover(i,e){this._register(C(i,le.MOUSE_OVER,t=>e(new ot(se(i),t))))}_onmouseleave(i,e){this._register(C(i,le.MOUSE_LEAVE,t=>e(new ot(se(i),t))))}};var Ti=class extends Ne{constructor(i){super(),this._handleActivate=i.handleActivate,this.bgDomNode=document.createElement("div"),this.bgDomNode.className="xterm-arrow-background",this.bgDomNode.style.position="absolute",this.bgDomNode.style.width=i.bgWidth+"px",this.bgDomNode.style.height=i.bgHeight+"px",typeof i.top<"u"&&(this.bgDomNode.style.top="0px"),typeof i.left<"u"&&(this.bgDomNode.style.left="0px"),typeof i.bottom<"u"&&(this.bgDomNode.style.bottom="0px"),typeof i.right<"u"&&(this.bgDomNode.style.right="0px"),this.domNode=document.createElement("div"),this.domNode.className=i.className,this.domNode.style.position="absolute";let e=Math.min(i.bgWidth,i.bgHeight);this.domNode.style.width=e+"px",this.domNode.style.height=e+"px",typeof i.top<"u"&&(this.domNode.style.top=i.top+"px"),typeof i.left<"u"&&(this.domNode.style.left=i.left+"px"),typeof i.bottom<"u"&&(this.domNode.style.bottom=i.bottom+"px"),typeof i.right<"u"&&(this.domNode.style.right=i.right+"px"),this._pointerMoveMonitor=this._register(new at),this._register(Vr(this.bgDomNode,le.POINTER_DOWN,t=>this._arrowPointerDown(t))),this._register(Vr(this.domNode,le.POINTER_DOWN,t=>this._arrowPointerDown(t))),this._pointerdownRepeatTimer=this._register(new xi),this._pointerdownScheduleRepeatTimer=this._register(new Ie)}_arrowPointerDown(i){if(!i.target||!(i.target instanceof Element))return;let e=()=>{this._pointerdownRepeatTimer.cancelAndSet(()=>this._handleActivate(),1e3/24,se(i))};this._handleActivate(),this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancelAndSet(e,200),this._pointerMoveMonitor.startMonitoring(i.target,i.pointerId,i.buttons,t=>{},()=>{this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancel()}),i.preventDefault()}};var b=class{constructor(){this._listeners=[];this._disposed=!1}get event(){return this._event?this._event:(this._event=(i,e,t)=>{if(this._disposed)return E(()=>{});let r={fn:i,thisArgs:e};this._listeners.push(r);let s=E(()=>{let o=this._listeners.indexOf(r);o!==-1&&this._listeners.splice(o,1)});return t&&(Array.isArray(t)?t.push(s):t.add(s)),s},this._event)}fire(i){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{let{fn:e,thisArgs:t}=this._listeners[0];e.call(t,i);return}default:{let e=this._listeners.slice();for(let{fn:t,thisArgs:r}of e)t.call(r,i)}}}dispose(){this._disposed||(this._disposed=!0,this._listeners.length=0)}},j;(r=>{function n(s,o){return s(a=>o.fire(a))}r.forward=n;function i(s,o){return(a,l,h)=>s(d=>a.call(l,o(d)),void 0,h)}r.map=i;function e(...s){return(o,a,l)=>{let h=new pe;for(let d of s)h.add(d(c=>o.call(a,c)));return l&&(Array.isArray(l)?l.push(h):l.add(h)),h}}r.any=e;function t(s,o,a){return o(a),s(l=>o(l))}r.runAndSubscribe=t})(j||={});var Jr=class n{constructor(i,e,t,r,s,o,a){this._forceIntegerValues=i;this._scrollStateBrand=void 0;this._forceIntegerValues&&(e=e|0,t=t|0,r=r|0,s=s|0,o=o|0,a=a|0),this.rawScrollLeft=r,this.rawScrollTop=a,e<0&&(e=0),r+e>t&&(r=t-e),r<0&&(r=0),s<0&&(s=0),a+s>o&&(a=o-s),a<0&&(a=0),this.width=e,this.scrollWidth=t,this.scrollLeft=r,this.height=s,this.scrollHeight=o,this.scrollTop=a}equals(i){return this.rawScrollLeft===i.rawScrollLeft&&this.rawScrollTop===i.rawScrollTop&&this.width===i.width&&this.scrollWidth===i.scrollWidth&&this.scrollLeft===i.scrollLeft&&this.height===i.height&&this.scrollHeight===i.scrollHeight&&this.scrollTop===i.scrollTop}withScrollDimensions(i,e){return new n(this._forceIntegerValues,typeof i.width<"u"?i.width:this.width,typeof i.scrollWidth<"u"?i.scrollWidth:this.scrollWidth,e?this.rawScrollLeft:this.scrollLeft,typeof i.height<"u"?i.height:this.height,typeof i.scrollHeight<"u"?i.scrollHeight:this.scrollHeight,e?this.rawScrollTop:this.scrollTop)}withScrollPosition(i){return new n(this._forceIntegerValues,this.width,this.scrollWidth,typeof i.scrollLeft<"u"?i.scrollLeft:this.rawScrollLeft,this.height,this.scrollHeight,typeof i.scrollTop<"u"?i.scrollTop:this.rawScrollTop)}createScrollEvent(i,e){let t=this.width!==i.width,r=this.scrollWidth!==i.scrollWidth,s=this.scrollLeft!==i.scrollLeft,o=this.height!==i.height,a=this.scrollHeight!==i.scrollHeight,l=this.scrollTop!==i.scrollTop;return{inSmoothScrolling:e,oldWidth:i.width,oldScrollWidth:i.scrollWidth,oldScrollLeft:i.scrollLeft,width:this.width,scrollWidth:this.scrollWidth,scrollLeft:this.scrollLeft,oldHeight:i.height,oldScrollHeight:i.scrollHeight,oldScrollTop:i.scrollTop,height:this.height,scrollHeight:this.scrollHeight,scrollTop:this.scrollTop,widthChanged:t,scrollWidthChanged:r,scrollLeftChanged:s,heightChanged:o,scrollHeightChanged:a,scrollTopChanged:l}}},lt=class extends g{constructor(e){super();this._scrollableBrand=void 0;this._onScroll=this._register(new b);this.onScroll=this._onScroll.event;this._smoothScrollDuration=e.smoothScrollDuration,this._scheduleAtNextAnimationFrame=e.scheduleAtNextAnimationFrame,this._state=new Jr(e.forceIntegerValues,0,0,0,0,0,0),this._smoothScrolling=null}dispose(){this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),super.dispose()}setSmoothScrollDuration(e){this._smoothScrollDuration=e}validateScrollPosition(e){return this._state.withScrollPosition(e)}getScrollDimensions(){return this._state}setScrollDimensions(e,t){let r=this._state.withScrollDimensions(e,t);this._setState(r,!!this._smoothScrolling),this._smoothScrolling?.acceptScrollDimensions(this._state)}getFutureScrollPosition(){return this._smoothScrolling?this._smoothScrolling.to:this._state}getCurrentScrollPosition(){return this._state}setScrollPositionNow(e){let t=this._state.withScrollPosition(e);this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),this._setState(t,!1)}setScrollPositionSmooth(e,t){if(this._smoothScrollDuration===0){this.setScrollPositionNow(e);return}if(this._smoothScrolling){e={scrollLeft:typeof e.scrollLeft>"u"?this._smoothScrolling.to.scrollLeft:e.scrollLeft,scrollTop:typeof e.scrollTop>"u"?this._smoothScrolling.to.scrollTop:e.scrollTop};let r=this._state.withScrollPosition(e);if(this._smoothScrolling.to.scrollLeft===r.scrollLeft&&this._smoothScrolling.to.scrollTop===r.scrollTop)return;let s;t?s=new Vt(this._smoothScrolling.from,r,this._smoothScrolling.startTime,this._smoothScrolling.duration):s=Vt.start(this._state,r,this._smoothScrollDuration),this._smoothScrolling.dispose(),this._smoothScrolling=s}else{let r=this._state.withScrollPosition(e);this._smoothScrolling=Vt.start(this._state,r,this._smoothScrollDuration)}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}hasPendingScrollAnimation(){return!!this._smoothScrolling}_performSmoothScrolling(){if(!this._smoothScrolling)return;let e=this._smoothScrolling.tick(),t=this._state.withScrollPosition(e);if(this._setState(t,!0),!!this._smoothScrolling){if(e.isDone){this._smoothScrolling.dispose(),this._smoothScrolling=null;return}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}}_setState(e,t){let r=this._state;r.equals(e)||(this._state=e,this._onScroll.fire(this._state.createScrollEvent(r,t)))}},Di=class{constructor(i,e,t){this.scrollLeft=i,this.scrollTop=e,this.isDone=t}};function Zr(n,i){let e=i-n;return function(t){return n+e*$n(t)}}function Gn(n,i,e){return function(t){return t2.5*t){let s,o;return i{this._domNode?.setClassName(this._visibleClassName)},0))}_hide(i){this._revealTimer.cancel(),this._isVisible&&(this._isVisible=!1,this._domNode?.setClassName(this._invisibleClassName+(i?" xterm-fade":"")))}};var qn=140,ct=class extends Ne{constructor(i){super(),this._lazyRender=i.lazyRender,this._host=i.host,this._scrollable=i.scrollable,this._scrollByPage=i.scrollByPage,this._scrollbarState=i.scrollbarState,this._visibilityController=this._register(new Ri(i.visibility,"xterm-visible xterm-scrollbar "+i.extraScrollbarClassName,"xterm-invisible xterm-scrollbar "+i.extraScrollbarClassName)),this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._pointerMoveMonitor=this._register(new at),this._shouldRender=!0,this.domNode=new we(document.createElement("div")),this.domNode.setAttribute("role","presentation"),this.domNode.setAttribute("aria-hidden","true"),this._visibilityController.setDomNode(this.domNode),this.domNode.setPosition("absolute"),this._register(C(this.domNode.domNode,le.POINTER_DOWN,e=>this._domNodePointerDown(e)))}_createArrow(i){let e=this._register(new Ti(i));return this.domNode.domNode.appendChild(e.bgDomNode),this.domNode.domNode.appendChild(e.domNode),e}_createSlider(i,e,t,r){this.slider=new we(document.createElement("div")),this.slider.setClassName("xterm-slider"),this.slider.setPosition("absolute"),this.slider.setTop(i),this.slider.setLeft(e),typeof t=="number"&&this.slider.setWidth(t),typeof r=="number"&&this.slider.setHeight(r),this.slider.setLayerHinting(!0),this.slider.setContain("strict"),this.domNode.domNode.appendChild(this.slider.domNode),this._register(C(this.slider.domNode,le.POINTER_DOWN,s=>{s.button===0&&(s.preventDefault(),this._sliderPointerDown(s))})),this._onclick(this.slider.domNode,s=>{s.leftButton&&s.stopPropagation()})}_handleElementSize(i){return this._scrollbarState.setVisibleSize(i)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_handleElementScrollSize(i){return this._scrollbarState.setScrollSize(i)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_handleElementScrollPosition(i){return this._scrollbarState.setScrollPosition(i)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}beginReveal(){this._visibilityController.setShouldBeVisible(!0)}beginHide(){this._visibilityController.setShouldBeVisible(!1)}render(){this._shouldRender&&(this._shouldRender=!1,this._renderDomNode(this._scrollbarState.getRectangleLargeSize(),this._scrollbarState.getRectangleSmallSize()),this._updateSlider(this._scrollbarState.getSliderSize(),this._scrollbarState.getArrowSize()+this._scrollbarState.getSliderPosition()))}_domNodePointerDown(i){i.target===this.domNode.domNode&&this._handlePointerDown(i)}delegatePointerDown(i){let e=this.domNode.domNode.getClientRects()[0].top,t=e+this._scrollbarState.getSliderPosition(),r=e+this._scrollbarState.getSliderPosition()+this._scrollbarState.getSliderSize(),s=this._sliderPointerPosition(i);t<=s&&s<=r?i.button===0&&(i.preventDefault(),this._sliderPointerDown(i)):this._handlePointerDown(i)}_handlePointerDown(i){let e,t;if(i.target===this.domNode.domNode&&typeof i.offsetX=="number"&&typeof i.offsetY=="number")e=i.offsetX,t=i.offsetY;else{let s=$s(this.domNode.domNode);e=i.pageX-s.left,t=i.pageY-s.top}let r=this._pointerDownRelativePosition(e,t);this._setDesiredScrollPositionNow(this._scrollByPage?this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(r):this._scrollbarState.getDesiredScrollPositionFromOffset(r)),i.button===0&&(i.preventDefault(),this._sliderPointerDown(i))}_sliderPointerDown(i){if(!i.target||!(i.target instanceof Element))return;let e=this._sliderPointerPosition(i),t=this._sliderOrthogonalPointerPosition(i),r=this._scrollbarState.clone();this.slider.toggleClassName("xterm-active",!0),this._pointerMoveMonitor.startMonitoring(i.target,i.pointerId,i.buttons,s=>{let o=this._sliderOrthogonalPointerPosition(s),a=Math.abs(o-t);if(Ue&&a>qn){this._setDesiredScrollPositionNow(r.getScrollPosition());return}let h=this._sliderPointerPosition(s)-e;this._setDesiredScrollPositionNow(r.getDesiredScrollPositionFromDelta(h))},()=>{this.slider.toggleClassName("xterm-active",!1),this._host.handleDragEnd()}),this._host.handleDragStart()}_setDesiredScrollPositionNow(i){let e={};this.writeScrollPosition(e,i),this._scrollable.setScrollPositionNow(e)}updateScrollbarSize(i){this._updateScrollbarSize(i),this._scrollbarState.setScrollbarSize(i),this._shouldRender=!0,this._lazyRender||this.render()}isNeeded(){return this._scrollbarState.isNeeded()}};var ht=class n{constructor(i,e,t,r,s,o){this._scrollbarSize=Math.round(e),this._oppositeScrollbarSize=Math.round(t),this._arrowSize=Math.round(i),this._visibleSize=r,this._scrollSize=s,this._scrollPosition=o,this._computedAvailableSize=0,this._computedIsNeeded=!1,this._computedSliderSize=0,this._computedSliderRatio=0,this._computedSliderPosition=0,this._refreshComputedValues()}clone(){return new n(this._arrowSize,this._scrollbarSize,this._oppositeScrollbarSize,this._visibleSize,this._scrollSize,this._scrollPosition)}setVisibleSize(i){let e=Math.round(i);return this._visibleSize!==e?(this._visibleSize=e,this._refreshComputedValues(),!0):!1}setScrollSize(i){let e=Math.round(i);return this._scrollSize!==e?(this._scrollSize=e,this._refreshComputedValues(),!0):!1}setScrollPosition(i){let e=Math.round(i);return this._scrollPosition!==e?(this._scrollPosition=e,this._refreshComputedValues(),!0):!1}setScrollbarSize(i){this._scrollbarSize=Math.round(i)}setArrowSize(i){let e=Math.round(i);this._arrowSize!==e&&(this._arrowSize=e,this._refreshComputedValues())}setOppositeScrollbarSize(i){this._oppositeScrollbarSize=Math.round(i)}static _computeValues(i,e,t,r,s){let o=Math.max(0,t-i),a=Math.max(0,o-2*e),l=r>0&&r>t;if(!l)return{computedAvailableSize:Math.round(o),computedIsNeeded:l,computedSliderSize:Math.round(a),computedSliderRatio:0,computedSliderPosition:0};let h=Math.round(Math.max(20,Math.floor(t*a/r))),d=(a-h)/(r-t),c=s*d;return{computedAvailableSize:Math.round(o),computedIsNeeded:l,computedSliderSize:Math.round(h),computedSliderRatio:d,computedSliderPosition:Math.round(c)}}_refreshComputedValues(){let i=n._computeValues(this._oppositeScrollbarSize,this._arrowSize,this._visibleSize,this._scrollSize,this._scrollPosition);this._computedAvailableSize=i.computedAvailableSize,this._computedIsNeeded=i.computedIsNeeded,this._computedSliderSize=i.computedSliderSize,this._computedSliderRatio=i.computedSliderRatio,this._computedSliderPosition=i.computedSliderPosition}getArrowSize(){return this._arrowSize}getScrollPosition(){return this._scrollPosition}getRectangleLargeSize(){return this._computedAvailableSize}getRectangleSmallSize(){return this._scrollbarSize}isNeeded(){return this._computedIsNeeded}getSliderSize(){return this._computedSliderSize}getSliderPosition(){return this._computedSliderPosition}getDesiredScrollPositionFromOffset(i){if(!this._computedIsNeeded)return 0;let e=i-this._arrowSize-this._computedSliderSize/2;return Math.round(e/this._computedSliderRatio)}getDesiredScrollPositionFromOffsetPaged(i){if(!this._computedIsNeeded)return 0;let e=i-this._arrowSize,t=this._scrollPosition;return ethis._arrowScroll(-this._arrowScrollDelta)}),this._arrowDown=this._createArrow({className:"xterm-scra xterm-arrow-down",bottom:0,left:0,bgWidth:t,bgHeight:t,handleActivate:()=>this._arrowScroll(this._arrowScrollDelta)})),this._updateArrowSize(this._arrowUp,t),this._updateArrowSize(this._arrowDown,t),!this._arrowUp||!this._arrowDown)return;let r=e?"":"none";this._arrowUp.bgDomNode.style.display=r,this._arrowUp.domNode.style.display=r,this._arrowDown.bgDomNode.style.display=r,this._arrowDown.domNode.style.display=r}_updateArrowSize(e,t){e&&(e.bgDomNode.style.width=`${t}px`,e.bgDomNode.style.height=`${t}px`,e.domNode.style.width=`${t}px`,e.domNode.style.height=`${t}px`)}updateOptions(e){let t=e.verticalHasArrows?e.verticalScrollbarSize:0;this._scrollbarState.setArrowSize(t),this._setArrows(e.verticalHasArrows,e.verticalScrollbarSize),this.updateScrollbarSize(e.vertical===2?0:e.verticalScrollbarSize),this._scrollbarState.setOppositeScrollbarSize(0),this._visibilityController.setVisibility(e.vertical),this._scrollByPage=e.scrollByPage}};var Qr=class{constructor(i,e,t){this.timestamp=i,this.deltaX=e,this.deltaY=t,this.score=0}},Bi=class Bi{constructor(){this._capacity=5,this._memory=[],this._front=-1,this._rear=-1}isPhysicalMouseWheel(){if(this._front===-1&&this._rear===-1)return!1;let i=1,e=0,t=1,r=this._rear;for(;r!==-1;){let s=r===this._front?i:Math.pow(2,-t);if(i-=s,e+=this._memory[r].score*s,r===this._front)break;r=(this._capacity+r-1)%this._capacity,t++}return e<=.5}acceptStandardWheelEvent(i){if(Kt){let e=se(i.browserEvent),t=Xr(e);this.accept(Date.now(),i.deltaX*t,i.deltaY*t)}else this.accept(Date.now(),i.deltaX,i.deltaY)}accept(i,e,t){let r=null,s=new Qr(i,e,t);this._front===-1&&this._rear===-1?(this._memory[0]=s,this._front=0,this._rear=0):(r=this._memory[this._rear],this._rear=(this._rear+1)%this._capacity,this._rear===this._front&&(this._front=(this._front+1)%this._capacity),this._memory[this._rear]=s),s.score=this._computeScore(s,r)}_computeScore(i,e){if(Math.abs(i.deltaX)>0&&Math.abs(i.deltaY)>0)return 1;let t=.5;if((!this._isAlmostInt(i.deltaX)||!this._isAlmostInt(i.deltaY))&&(t+=.25),e){let r=Math.abs(i.deltaX),s=Math.abs(i.deltaY),o=Math.abs(e.deltaX),a=Math.abs(e.deltaY),l=Math.max(Math.min(r,o),1),h=Math.max(Math.min(s,a),1),d=Math.max(r,o),c=Math.max(s,a);d%l===0&&c%h===0&&(t-=.5)}return Math.min(Math.max(t,0),1)}_isAlmostInt(i){return Math.abs(Math.round(i)-i)<.01}};Bi.INSTANCE=new Bi;var es=Bi,Mi=class extends Ne{constructor(e,t,r){super();this._onScroll=this._register(new b);this.onScroll=this._onScroll.event;t=t??{};let s,o=!r;r?s=r:(t.mouseWheelSmoothScroll=!1,s=new lt({forceIntegerValues:!0,smoothScrollDuration:0,scheduleAtNextAnimationFrame:l=>tt(se(e),l)})),this._options=Xn(t),this._scrollable=s,this._register(this._scrollable.onScroll(l=>{this._handleScroll(l),this._onScroll.fire(l)})),o&&this._register(this._scrollable);let a={handleMouseWheel:l=>this._handleMouseWheel(l),handleDragStart:()=>this._handleDragStart(),handleDragEnd:()=>this._handleDragEnd()};this._verticalScrollbar=this._register(new ki(this._scrollable,this._options,a)),this._horizontalScrollbar=this._register(new Ai(this._scrollable,this._options,a)),this._domNode=document.createElement("div"),this._domNode.className="xterm-scrollable-element "+this._options.className,this._domNode.setAttribute("role","presentation"),this._domNode.style.position="relative",this._domNode.appendChild(e),this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode),this._domNode.appendChild(this._verticalScrollbar.domNode.domNode),this._options.useShadows?(this._leftShadowDomNode=new we(document.createElement("div")),this._leftShadowDomNode.setClassName("xterm-shadow"),this._domNode.appendChild(this._leftShadowDomNode.domNode),this._topShadowDomNode=new we(document.createElement("div")),this._topShadowDomNode.setClassName("xterm-shadow"),this._domNode.appendChild(this._topShadowDomNode.domNode),this._topLeftShadowDomNode=new we(document.createElement("div")),this._topLeftShadowDomNode.setClassName("xterm-shadow"),this._domNode.appendChild(this._topLeftShadowDomNode.domNode)):(this._leftShadowDomNode=null,this._topShadowDomNode=null,this._topLeftShadowDomNode=null),this._listenOnDomNode=this._options.listenOnDomNode??this._domNode,this._mouseWheelToDispose=[],this._setListeningToMouseWheel(this._options.handleMouseWheel),this._onmouseover(this._listenOnDomNode,l=>this._handleMouseOver(l)),this._onmouseleave(this._listenOnDomNode,l=>this._handleMouseLeave(l)),this._hideTimeout=this._register(new Ie),this._isDragging=!1,this._mouseIsOver=!1,this._shouldRender=!0,this._revealOnScroll=!0}get options(){return this._options}dispose(){this._mouseWheelToDispose=Oe(this._mouseWheelToDispose),super.dispose()}getDomNode(){return this._domNode}getScrollDimensions(){return this._scrollable.getScrollDimensions()}setScrollDimensions(e){this._scrollable.setScrollDimensions(e,!1)}setScrollPosition(e){e.reuseAnimation?this._scrollable.setScrollPositionSmooth(e,e.reuseAnimation):this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}updateClassName(e){this._options.className=e,ie&&(this._options.className+=" xterm-mac"),this._domNode.className="xterm-scrollable-element "+this._options.className}updateOptions(e){typeof e.handleMouseWheel<"u"&&(this._options.handleMouseWheel=e.handleMouseWheel,this._setListeningToMouseWheel(this._options.handleMouseWheel)),typeof e.mouseWheelScrollSensitivity<"u"&&(this._options.mouseWheelScrollSensitivity=e.mouseWheelScrollSensitivity),typeof e.fastScrollSensitivity<"u"&&(this._options.fastScrollSensitivity=e.fastScrollSensitivity),typeof e.scrollPredominantAxis<"u"&&(this._options.scrollPredominantAxis=e.scrollPredominantAxis),typeof e.horizontal<"u"&&(this._options.horizontal=e.horizontal),typeof e.vertical<"u"&&(this._options.vertical=e.vertical),typeof e.horizontalHasArrows<"u"&&(this._options.horizontalHasArrows=e.horizontalHasArrows),typeof e.verticalHasArrows<"u"&&(this._options.verticalHasArrows=e.verticalHasArrows),typeof e.horizontalScrollbarSize<"u"&&(this._options.horizontalScrollbarSize=e.horizontalScrollbarSize),typeof e.verticalScrollbarSize<"u"&&(this._options.verticalScrollbarSize=e.verticalScrollbarSize),typeof e.scrollByPage<"u"&&(this._options.scrollByPage=e.scrollByPage),this._horizontalScrollbar.updateOptions(this._options),this._verticalScrollbar.updateOptions(this._options),this._options.lazyRender||this._render()}delegateScrollFromMouseWheelEvent(e){this._handleMouseWheel(new Gt(e))}_setListeningToMouseWheel(e){if(this._mouseWheelToDispose.length>0!==e&&(this._mouseWheelToDispose=Oe(this._mouseWheelToDispose),e)){let r=s=>{this._handleMouseWheel(new Gt(s))};this._mouseWheelToDispose.push(C(this._listenOnDomNode,le.MOUSE_WHEEL,r,{passive:!1}))}}_handleMouseWheel(e){if(e.browserEvent?.defaultPrevented)return;let t=es.INSTANCE;t.acceptStandardWheelEvent(e);let r=!1;if(e.deltaY||e.deltaX){let o=e.deltaY*this._options.mouseWheelScrollSensitivity,a=e.deltaX*this._options.mouseWheelScrollSensitivity;this._options.scrollPredominantAxis&&(this._options.scrollYToX&&a+o===0?a=o=0:Math.abs(o)>=Math.abs(a)?a=0:o=0),this._options.flipAxes&&([o,a]=[a,o]);let l=!ie&&e.browserEvent&&e.browserEvent.shiftKey;(this._options.scrollYToX||l)&&!a&&(a=o,o=0),e.browserEvent&&e.browserEvent.altKey&&(a=a*this._options.fastScrollSensitivity,o=o*this._options.fastScrollSensitivity);let h=this._scrollable.getFutureScrollPosition(),d={};if(o){let c=50*o,u=h.scrollTop-(c<0?Math.floor(c):Math.ceil(c));this._verticalScrollbar.writeScrollPosition(d,u)}if(a){let c=50*a,u=h.scrollLeft-(c<0?Math.floor(c):Math.ceil(c));this._horizontalScrollbar.writeScrollPosition(d,u)}d=this._scrollable.validateScrollPosition(d),(h.scrollLeft!==d.scrollLeft||h.scrollTop!==d.scrollTop)&&(this._options.mouseWheelSmoothScroll&&t.isPhysicalMouseWheel()?this._scrollable.setScrollPositionSmooth(d):this._scrollable.setScrollPositionNow(d),r=!0)}let s=r;!s&&this._options.alwaysConsumeMouseWheel&&(s=!0),!s&&this._options.consumeMouseWheelIfScrollbarIsNeeded&&(this._verticalScrollbar.isNeeded()||this._horizontalScrollbar.isNeeded())&&(s=!0),s&&(e.preventDefault(),e.stopPropagation())}_handleScroll(e){this._shouldRender=this._horizontalScrollbar.handleScroll(e)||this._shouldRender,this._shouldRender=this._verticalScrollbar.handleScroll(e)||this._shouldRender,this._options.useShadows&&(this._shouldRender=!0),this._revealOnScroll&&this._reveal(),this._options.lazyRender||this._render()}renderNow(){if(!this._options.lazyRender)throw new Error("Please use `lazyRender` together with `renderNow`!");this._render()}_render(){if(this._shouldRender&&(this._shouldRender=!1,this._horizontalScrollbar.render(),this._verticalScrollbar.render(),this._options.useShadows)){let e=this._scrollable.getCurrentScrollPosition(),t=e.scrollTop>0,r=e.scrollLeft>0,s=r?" xterm-shadow-left":"",o=t?" xterm-shadow-top":"",a=r||t?" xterm-shadow-top-left-corner":"";this._leftShadowDomNode.setClassName(`xterm-shadow${s}`),this._topShadowDomNode.setClassName(`xterm-shadow${o}`),this._topLeftShadowDomNode.setClassName(`xterm-shadow${a}${o}${s}`)}}_handleDragStart(){this._isDragging=!0,this._reveal()}_handleDragEnd(){this._isDragging=!1,this._hide()}_handleMouseLeave(e){this._mouseIsOver=!1,this._hide()}_handleMouseOver(e){this._mouseIsOver=!0,this._reveal()}_reveal(){this._verticalScrollbar.beginReveal(),this._horizontalScrollbar.beginReveal(),this._scheduleHide()}_hide(){!this._mouseIsOver&&!this._isDragging&&(this._verticalScrollbar.beginHide(),this._horizontalScrollbar.beginHide())}_scheduleHide(){!this._mouseIsOver&&!this._isDragging&&this._hideTimeout.cancelAndSet(()=>this._hide(),500)}};function Xn(n){let i={lazyRender:typeof n.lazyRender<"u"?n.lazyRender:!1,className:typeof n.className<"u"?n.className:"",useShadows:typeof n.useShadows<"u"?n.useShadows:!0,handleMouseWheel:typeof n.handleMouseWheel<"u"?n.handleMouseWheel:!0,flipAxes:typeof n.flipAxes<"u"?n.flipAxes:!1,consumeMouseWheelIfScrollbarIsNeeded:typeof n.consumeMouseWheelIfScrollbarIsNeeded<"u"?n.consumeMouseWheelIfScrollbarIsNeeded:!1,alwaysConsumeMouseWheel:typeof n.alwaysConsumeMouseWheel<"u"?n.alwaysConsumeMouseWheel:!1,scrollYToX:typeof n.scrollYToX<"u"?n.scrollYToX:!1,mouseWheelScrollSensitivity:typeof n.mouseWheelScrollSensitivity<"u"?n.mouseWheelScrollSensitivity:1,fastScrollSensitivity:typeof n.fastScrollSensitivity<"u"?n.fastScrollSensitivity:5,scrollPredominantAxis:typeof n.scrollPredominantAxis<"u"?n.scrollPredominantAxis:!0,mouseWheelSmoothScroll:typeof n.mouseWheelSmoothScroll<"u"?n.mouseWheelSmoothScroll:!0,listenOnDomNode:typeof n.listenOnDomNode<"u"?n.listenOnDomNode:null,horizontal:typeof n.horizontal<"u"?n.horizontal:1,horizontalScrollbarSize:typeof n.horizontalScrollbarSize<"u"?n.horizontalScrollbarSize:10,horizontalSliderSize:typeof n.horizontalSliderSize<"u"?n.horizontalSliderSize:0,horizontalHasArrows:typeof n.horizontalHasArrows<"u"?n.horizontalHasArrows:!1,vertical:typeof n.vertical<"u"?n.vertical:1,verticalScrollbarSize:typeof n.verticalScrollbarSize<"u"?n.verticalScrollbarSize:10,verticalHasArrows:typeof n.verticalHasArrows<"u"?n.verticalHasArrows:!1,verticalSliderSize:typeof n.verticalSliderSize<"u"?n.verticalSliderSize:0,scrollByPage:typeof n.scrollByPage<"u"?n.scrollByPage:!1};return i.horizontalSliderSize=typeof n.horizontalSliderSize<"u"?n.horizontalSliderSize:i.horizontalScrollbarSize,i.verticalSliderSize=typeof n.verticalSliderSize<"u"?n.verticalSliderSize:i.verticalScrollbarSize,ie&&(i.className+=" xterm-mac"),i}var dt=class extends g{constructor(e,t,r,s,o,a,l,h,d){super();this._bufferService=r;this._coreService=o;this._optionsService=h;this._renderService=d;this._onRequestScrollLines=this._register(new b);this.onRequestScrollLines=this._onRequestScrollLines.event;this._isSyncing=!1;this._isHandlingScroll=!1;this._suppressOnScrollHandler=!1;this._needsSyncOnRender=!1;let c=this._register(new lt({forceIntegerValues:!1,smoothScrollDuration:this._optionsService.rawOptions.smoothScrollDuration,scheduleAtNextAnimationFrame:u=>tt(s.window,u)}));this._register(this._optionsService.onSpecificOptionChange("smoothScrollDuration",()=>{c.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration)})),this._scrollableElement=this._register(new Mi(t,{vertical:1,horizontal:2,useShadows:!1,mouseWheelSmoothScroll:!0,verticalHasArrows:this._optionsService.rawOptions.scrollbar?.showArrows??!1,...this._getChangeOptions()},c)),this._register(this._optionsService.onMultipleOptionChange(["scrollSensitivity","fastScrollSensitivity","scrollbar"],()=>this._scrollableElement.updateOptions(this._getChangeOptions()))),this._register(a.onProtocolChange(u=>{this._scrollableElement.updateOptions({handleMouseWheel:!(u&16)})})),this._scrollableElement.setScrollDimensions({height:0,scrollHeight:0}),this._register(j.runAndSubscribe(l.onChangeColors,()=>{e.style.backgroundColor=l.colors.background.css,this._scrollableElement.getDomNode().style.backgroundColor=l.colors.background.css})),e.appendChild(this._scrollableElement.getDomNode()),this._register(E(()=>this._scrollableElement.getDomNode().remove())),this._styleElement=s.mainDocument.createElement("style"),t.appendChild(this._styleElement),this._register(E(()=>this._styleElement.remove())),this._register(j.runAndSubscribe(l.onChangeColors,()=>{this._styleElement.textContent=[".xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider {",` background: ${l.colors.scrollbarSliderBackground.css};`,"}",".xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider:hover {",` background: ${l.colors.scrollbarSliderHoverBackground.css};`,"}",".xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider.xterm-active {",` background: ${l.colors.scrollbarSliderActiveBackground.css};`,"}"].join(` +-`)})),this._register(this._bufferService.onResize(()=>this.queueSync())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._latestYDisp=void 0,this.queueSync()})),this._register(this._bufferService.onScroll(()=>this._sync())),this._register(this._renderService.onRender(()=>{this._needsSyncOnRender&&(this._needsSyncOnRender=!1,this._sync())})),this._register(this._scrollableElement.onScroll(u=>this._handleScroll(u)))}scrollLines(e){let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:t.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,t){t&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!t,scrollTop:e*this._renderService.dimensions.css.cell.height})}_getChangeOptions(){let e=this._optionsService.rawOptions.scrollbar?.showScrollbar??!0,t=this._optionsService.rawOptions.scrollbar?.showArrows??!1,r=e?this._optionsService.rawOptions.scrollbar?.width??14:0;return{mouseWheelScrollSensitivity:this._optionsService.rawOptions.scrollSensitivity,fastScrollSensitivity:this._optionsService.rawOptions.fastScrollSensitivity,vertical:e?1:2,verticalScrollbarSize:r,verticalHasArrows:t}}queueSync(e){e!==void 0&&(this._latestYDisp=e),this._queuedAnimationFrame===void 0&&(this._queuedAnimationFrame=this._renderService.addRefreshCallback(()=>{this._queuedAnimationFrame=void 0,this._sync(this._latestYDisp)}))}_sync(e=this._bufferService.buffer.ydisp){if(!(!this._renderService||this._isSyncing)){if(this._coreService.decPrivateModes.synchronizedOutput){this._needsSyncOnRender=!0;return}this._isSyncing=!0,this._suppressOnScrollHandler=!0,this._scrollableElement.setScrollDimensions({height:this._renderService.dimensions.css.canvas.height,scrollHeight:this._renderService.dimensions.css.cell.height*this._bufferService.buffer.lines.length}),this._suppressOnScrollHandler=!1,e!==this._latestYDisp&&this._scrollableElement.setScrollPosition({scrollTop:e*this._renderService.dimensions.css.cell.height}),this._isSyncing=!1}}_handleScroll(e){if(!this._renderService||this._isHandlingScroll||this._suppressOnScrollHandler)return;this._isHandlingScroll=!0;let t=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),r=t-this._bufferService.buffer.ydisp;r!==0&&(this._latestYDisp=t,this._onRequestScrollLines.fire(r)),this._isHandlingScroll=!1}handleTouchScroll(e){let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({scrollTop:t.scrollTop-e})}};dt=y([m(2,D),m(3,G),m(4,Y),m(5,Me),m(6,_e),m(7,R),m(8,V)],dt);var ut=class extends g{constructor(e,t,r,s,o){super();this._screenElement=e;this._bufferService=t;this._coreBrowserService=r;this._decorationService=s;this._renderService=o;this._decorationElements=new Map;this._altBufferIsActive=!1;this._dimensionsChanged=!1;this._container=document.createElement("div"),this._container.classList.add("xterm-decoration-container"),this._screenElement.appendChild(this._container),this._register(this._renderService.onRenderedViewportChange(()=>this._doRefreshDecorations())),this._register(this._renderService.onDimensionsChange(()=>{this._dimensionsChanged=!0,this._queueRefresh()})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt})),this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh())),this._register(this._decorationService.onDecorationRemoved(a=>this._removeDecoration(a))),this._register(E(()=>{this._container.remove(),this._decorationElements.clear()}))}_queueRefresh(){this._animationFrame===void 0&&(this._animationFrame=this._renderService.addRefreshCallback(()=>{this._doRefreshDecorations(),this._animationFrame=void 0}))}_doRefreshDecorations(){for(let e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){let t=this._coreBrowserService.mainDocument.createElement("div");t.classList.add("xterm-decoration"),t.classList.toggle("xterm-decoration-top-layer",e?.options?.layer==="top"),t.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,t.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,t.style.top=`${(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height}px`,t.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;let r=e.options.x??0;return r&&r>this._bufferService.cols&&(t.style.display="none"),this._refreshXPosition(e,t),t}_refreshStyle(e){let t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display="none",e.onRenderEmitter.fire(e.element));else{let r=this._decorationElements.get(e);r||(r=this._createElement(e),e.element=r,this._decorationElements.set(e,r),this._container.appendChild(r),e.onDispose(()=>{this._decorationElements.delete(e),r.remove()})),r.style.display=this._altBufferIsActive?"none":"block",this._altBufferIsActive||(r.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,r.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,r.style.top=`${t*this._renderService.dimensions.css.cell.height}px`,r.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(r)}}_refreshXPosition(e,t=e.element){if(!t)return;let r=e.options.x??0;(e.options.anchor||"left")==="right"?t.style.right=r?`${r*this._renderService.dimensions.css.cell.width}px`:"":t.style.left=r?`${r*this._renderService.dimensions.css.cell.width}px`:""}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};ut=y([m(1,D),m(2,G),m(3,ge),m(4,V)],ut);var Pi=class{constructor(){this._zones=[];this._zonePool=[];this._zonePoolIndex=0;this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(i){if(i.options.overviewRulerOptions){for(let e of this._zones)if(e.color===i.options.overviewRulerOptions.color&&e.position===i.options.overviewRulerOptions.position){if(this._lineIntersectsZone(e,i.marker.line))return;if(this._lineAdjacentToZone(e,i.marker.line,i.options.overviewRulerOptions.position)){this._addLineToZone(e,i.marker.line);return}}if(this._zonePoolIndex=i.startBufferLine&&e<=i.endBufferLine}_lineAdjacentToZone(i,e,t){return e>=i.startBufferLine-this._linePadding[t||"full"]&&e<=i.endBufferLine+this._linePadding[t||"full"]}_addLineToZone(i,e){i.startBufferLine=Math.min(i.startBufferLine,e),i.endBufferLine=Math.max(i.endBufferLine,e)}};var Ce={full:0,left:0,center:0,right:0},Fe={full:0,left:0,center:0,right:0},$t={full:0,left:0,center:0,right:0},ze=class extends g{constructor(e,t,r,s,o,a,l,h){super();this._viewportElement=e;this._screenElement=t;this._bufferService=r;this._decorationService=s;this._renderService=o;this._optionsService=a;this._themeService=l;this._coreBrowserService=h;this._colorZoneStore=new Pi;this._shouldUpdateDimensions=!0;this._shouldUpdateAnchor=!0;this._lastKnownBufferLength=0;this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add("xterm-decoration-overview-ruler"),this._refreshCanvasDimensions(),this._viewportElement.parentElement?.insertBefore(this._canvas,this._viewportElement),this._register(E(()=>this._canvas?.remove()));let d=this._canvas.getContext("2d");if(d)this._ctx=d;else throw new Error("Ctx cannot be null");this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh(void 0,!0))),this._register(this._decorationService.onDecorationRemoved(()=>this._queueRefresh(void 0,!0))),this._register(this._renderService.onRenderedViewportChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?"none":"block"})),this._register(this._bufferService.onScroll(()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())})),this._register(this._renderService.onDimensionsChange(()=>this._queueRefresh(!0))),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh(!0))),this._register(this._optionsService.onSpecificOptionChange("scrollbar",()=>this._queueRefresh(!0))),this._register(this._themeService.onChangeColors(()=>this._queueRefresh())),this._register(E(()=>{this._animationFrame!==void 0&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)})),this._queueRefresh(!0)}get _width(){let e=this._optionsService.rawOptions.scrollbar;return e?.showScrollbar??!0?e?.width??0:0}_refreshDrawConstants(){let e=Math.floor((this._canvas.width-1)/3),t=Math.ceil((this._canvas.width-1)/3);Fe.full=this._canvas.width,Fe.left=e,Fe.center=t,Fe.right=e,this._refreshDrawHeightConstants(),$t.full=1,$t.left=1,$t.center=1+Fe.left,$t.right=1+Fe.left+Fe.center}_refreshDrawHeightConstants(){Ce.full=Math.round(2*this._coreBrowserService.dpr);let e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);Ce.left=t,Ce.center=t,Ce.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Ce.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Ce.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Ce.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Ce.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){if(this._store.isDisposed||!this._renderService.hasRenderer())return;let e=this._renderService.dimensions.css.canvas.height,t=this._renderService.dimensions.device.canvas.height;this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${e}px`,this._canvas.height=t,this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){if(this._store.isDisposed||!this._renderService.hasRenderer())return;this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(let t of this._decorationService.decorations)this._colorZoneStore.addDecoration(t);this._ctx.lineWidth=1,this._renderRulerOutline();let e=this._colorZoneStore.zones;for(let t of e)t.position!=="full"&&this._renderColorZone(t);for(let t of e)t.position==="full"&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderRulerOutline(){this._ctx.fillStyle=this._themeService.colors.overviewRulerBorder.css,this._ctx.fillRect(0,0,1,this._canvas.height),this._optionsService.rawOptions.scrollbar?.overviewRuler?.showTopBorder&&this._ctx.fillRect(1,0,this._canvas.width-1,1),this._optionsService.rawOptions.scrollbar?.overviewRuler?.showBottomBorder&&this._ctx.fillRect(1,this._canvas.height-1,this._canvas.width-1,this._canvas.height)}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect($t[e.position||"full"],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-Ce[e.position||"full"]/2),Fe[e.position||"full"],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+Ce[e.position||"full"]))}_queueRefresh(e,t){this._store.isDisposed||(this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,this._animationFrame===void 0&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._store.isDisposed||this._refreshDecorations(),this._animationFrame=void 0})))}};ze=y([m(2,D),m(3,ge),m(4,V),m(5,R),m(6,_e),m(7,G)],ze);var ft=class{constructor(i,e,t,r,s,o){this._textarea=i;this._compositionView=e;this._bufferService=t;this._optionsService=r;this._coreService=s;this._renderService=o;this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._compositionSuffix="",this._dataAlreadySent=""}get isComposing(){return this._isComposing}compositionstart(){this._isComposing=!0;let i=this._textarea.selectionStart??this._textarea.value.length,e=this._textarea.selectionEnd??i;this._compositionPosition.start=Math.min(i,e),this._compositionPosition.end=Math.max(i,e),this._compositionSuffix=this._textarea.value.substring(this._compositionPosition.end),this._compositionView.textContent="",this._dataAlreadySent="",this._compositionView.classList.add("active")}compositionupdate(i){this._compositionView.textContent=`\u200E${i.data}\u200E`,this.updateCompositionElements(),setTimeout(()=>{let e=this._textarea.selectionEnd??this._textarea.value.length;this._compositionPosition.end=Math.max(this._compositionPosition.start,e)},0)}compositionend(){this._finalizeComposition(!0)}keydown(i){if(this._isComposing||this._isSendingComposition){if(i.keyCode===20||i.keyCode===229||i.keyCode===16||i.keyCode===17||i.keyCode===18)return!1;this._finalizeComposition(!1)}return i.keyCode===229?(this._handleAnyTextareaChanges(),!1):!0}_finalizeComposition(i){if(this._compositionView.classList.remove("active"),this._isComposing=!1,i){let e={start:this._compositionPosition.start,end:this._compositionPosition.end},t=this._compositionSuffix;this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){this._isSendingComposition=!1;let r;if(e.start+=this._dataAlreadySent.length,this._isComposing)r=this._textarea.value.substring(e.start,this._compositionPosition.start);else{let s=this._textarea.value,o=t.length>0&&s.endsWith(t)?s.length-t.length:s.length;r=s.substring(e.start,Math.max(e.start,o))}r.length>0&&this._coreService.triggerDataEvent(r,!0)}},0)}else{this._isSendingComposition=!1;let e=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(e,!0)}}_handleAnyTextareaChanges(){if(this._textareaChangeTimer)return;let i=this._textarea.value;this._textareaChangeTimer=window.setTimeout(()=>{if(this._textareaChangeTimer=void 0,!this._isComposing){let e=this._textarea.value,t=e.replace(i,"");this._dataAlreadySent=t,e.length>i.length?this._coreService.triggerDataEvent(t,!0):e.lengththis.updateCompositionElements(!0),0)}}};ft=y([m(2,D),m(3,R),m(4,Y),m(5,V)],ft);var J=0,Q=0,ee=0,W=0,ts={css:"#00000000",rgba:0},O;(t=>{function n(r,s,o,a){return a!==void 0?`#${Ve(r)}${Ve(s)}${Ve(o)}${Ve(a)}`:`#${Ve(r)}${Ve(s)}${Ve(o)}`}t.toCss=n;function i(r,s,o,a=255){return(r<<24|s<<16|o<<8|a)>>>0}t.toRgba=i;function e(r,s,o,a){return{css:t.toCss(r,s,o,a),rgba:t.toRgba(r,s,o,a)}}t.toColor=e})(O||={});var k;(a=>{function n(l,h){if(W=(h.rgba&255)/255,W===1)return{css:h.css,rgba:h.rgba};let d=h.rgba>>24&255,c=h.rgba>>16&255,u=h.rgba>>8&255,_=l.rgba>>24&255,p=l.rgba>>16&255,v=l.rgba>>8&255;J=_+Math.round((d-_)*W),Q=p+Math.round((c-p)*W),ee=v+Math.round((u-v)*W);let f=O.toCss(J,Q,ee),S=O.toRgba(J,Q,ee);return{css:f,rgba:S}}a.blend=n;function i(l){return(l.rgba&255)===255}a.isOpaque=i;function e(l,h,d){let c=Oi.ensureContrastRatio(l.rgba,h.rgba,d);if(c)return O.toColor(c>>24&255,c>>16&255,c>>8&255)}a.ensureContrastRatio=e;function t(l){let h=(l.rgba|255)>>>0;return[J,Q,ee]=Oi.toChannels(h),{css:O.toCss(J,Q,ee),rgba:h}}a.opaque=t;function r(l,h){return W=Math.round(h*255),[J,Q,ee]=Oi.toChannels(l.rgba),{css:O.toCss(J,Q,ee,W),rgba:O.toRgba(J,Q,ee,W)}}a.opacity=r;function s(l,h){return W=l.rgba&255,r(l,W*h/255)}a.multiplyOpacity=s;function o(l){return[l.rgba>>24&255,l.rgba>>16&255,l.rgba>>8&255]}a.toColorRGB=o})(k||={});var B;(t=>{let n,i;try{let r=document.createElement("canvas");r.width=1,r.height=1;let s=r.getContext("2d",{willReadFrequently:!0});s&&(n=s,n.globalCompositeOperation="copy",i=n.createLinearGradient(0,0,1,1))}catch{}function e(r){if(r.match(/#[\da-f]{3,8}/i))switch(r.length){case 4:return J=parseInt(r.slice(1,2).repeat(2),16),Q=parseInt(r.slice(2,3).repeat(2),16),ee=parseInt(r.slice(3,4).repeat(2),16),O.toColor(J,Q,ee);case 5:return J=parseInt(r.slice(1,2).repeat(2),16),Q=parseInt(r.slice(2,3).repeat(2),16),ee=parseInt(r.slice(3,4).repeat(2),16),W=parseInt(r.slice(4,5).repeat(2),16),O.toColor(J,Q,ee,W);case 7:return{css:r,rgba:(parseInt(r.slice(1),16)<<8|255)>>>0};case 9:return{css:r,rgba:parseInt(r.slice(1),16)>>>0}}let s=r.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(s)return J=parseInt(s[1],10),Q=parseInt(s[2],10),ee=parseInt(s[3],10),W=Math.round((s[5]===void 0?1:parseFloat(s[5]))*255),O.toColor(J,Q,ee,W);if(r==="transparent")return{css:"transparent",rgba:0};if(!n||!i)throw new Error("css.toColor: Unsupported css format");if(n.fillStyle=i,n.fillStyle=r,typeof n.fillStyle!="string")throw new Error("css.toColor: Unsupported css format");if(n.fillRect(0,0,1,1),[J,Q,ee,W]=n.getImageData(0,0,1,1).data,W!==255)throw new Error("css.toColor: Unsupported css format");return{rgba:O.toRgba(J,Q,ee,W),css:r}}t.toColor=e})(B||={});var Z;(e=>{function n(t){return i(t>>16&255,t>>8&255,t&255)}e.relativeLuminance=n;function i(t,r,s){let o=t/255,a=r/255,l=s/255,h=o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4),d=a<=.03928?a/12.92:Math.pow((a+.055)/1.055,2.4),c=l<=.03928?l/12.92:Math.pow((l+.055)/1.055,2.4);return h*.2126+d*.7152+c*.0722}e.relativeLuminance2=i})(Z||={});var Oi;(s=>{function n(o,a){if(W=(a&255)/255,W===1)return a;let l=a>>24&255,h=a>>16&255,d=a>>8&255,c=o>>24&255,u=o>>16&255,_=o>>8&255;return J=c+Math.round((l-c)*W),Q=u+Math.round((h-u)*W),ee=_+Math.round((d-_)*W),O.toRgba(J,Q,ee)}s.blend=n;function i(o,a,l){let h=Z.relativeLuminance(o>>8),d=Z.relativeLuminance(a>>8);if(Te(h,d)>8));if(v>8));return v>S?p:f}return p}let u=t(o,a,l),_=Te(h,Z.relativeLuminance(u>>8));if(_>8));return _>v?u:p}return u}}s.ensureContrastRatio=i;function e(o,a,l){let h=o>>24&255,d=o>>16&255,c=o>>8&255,u=a>>24&255,_=a>>16&255,p=a>>8&255,v=Te(Z.relativeLuminance2(u,_,p),Z.relativeLuminance2(h,d,c));for(;v0||_>0||p>0);)u-=Math.max(0,Math.ceil(u*.1)),_-=Math.max(0,Math.ceil(_*.1)),p-=Math.max(0,Math.ceil(p*.1)),v=Te(Z.relativeLuminance2(u,_,p),Z.relativeLuminance2(h,d,c));return(u<<24|_<<16|p<<8|255)>>>0}s.reduceLuminance=e;function t(o,a,l){let h=o>>24&255,d=o>>16&255,c=o>>8&255,u=a>>24&255,_=a>>16&255,p=a>>8&255,v=Te(Z.relativeLuminance2(u,_,p),Z.relativeLuminance2(h,d,c));for(;v>>0}s.increaseLuminance=t;function r(o){return[o>>24&255,o>>16&255,o>>8&255,o&255]}s.toChannels=r})(Oi||={});function Ve(n){let i=n.toString(16);return i.length<2?"0"+i:i}function Te(n,i){return n1){let u=this._getJoinedRanges(r,l,a,e,o);for(let _=0;_1){let c=this._getJoinedRanges(r,l,a,e,o);for(let u=0;u=ks,Lr=ae,x=this._workCell;if(v.length>0&&ae===v[0][0]&&je){let A=v.shift(),Pr=this._isCellInSelection(A[0],e);for(T=A[0]+1;T=A[1],je?(fi=!0,x=new Ni(this._workCell,i.translateToString(!0,A[0],A[1]),A[1]-A[0]),Lr=A[1]-1,Rr=x.getWidth()):ks=A[1]}let Nt=this._isCellInSelection(ae,e),Ar=t&&ae===o,kr=An&&ae>=c&&ae<=u;_&&x.isBlink()&&(_.hasBlinkingCells=!0),!l&&x.isBlink()&&N.push("xterm-blink-hidden");let Mr=!1;this._decorationService.forEachDecorationAtCell(ae,e,void 0,A=>{Mr=!0});let _i=x.getChars()||" ";if(_i===" "&&(x.isUnderline()||x.isOverline())&&(_i="\xA0"),Ot=Rr*h-d.get(_i,x.isBold(),x.isItalic()),!I)I=this._document.createElement("span");else if(w&&(Nt&&ui||!Nt&&!ui&&x.bg===te)&&(Nt&&ui&&f.selectionForeground||x.fg===Ds)&&x.extended.ext===Rs&&kr===Ls&&Ot===As&&!Ar&&!fi&&!Mr&&je){x.isInvisible()?L+=" ":L+=_i,w++;continue}else w&&(I.textContent=L),I=this._document.createElement("span"),w=0,L="";if(te=x.bg,Ds=x.fg,Rs=x.extended.ext,Ls=kr,As=Ot,ui=Nt,fi&&o>=ae&&o<=Lr&&(o=ae),!this._coreService.isCursorHidden&&Ar&&this._coreService.isCursorInitialized){if(N.push("xterm-cursor"),this._coreBrowserService.isFocused)a&&N.push("xterm-cursor-blink"),N.push(r==="bar"?"xterm-cursor-bar":r==="underline"?"xterm-cursor-underline":"xterm-cursor-block");else if(s)switch(s){case"outline":N.push("xterm-cursor-outline");break;case"block":N.push("xterm-cursor-block");break;case"bar":N.push("xterm-cursor-bar");break;case"underline":N.push("xterm-cursor-underline");break;default:break}}if(x.isBold()&&N.push("xterm-bold"),x.isItalic()&&N.push("xterm-italic"),x.isDim()&&N.push("xterm-dim"),x.isInvisible()?L=" ":L=x.getChars()||" ",x.isUnderline()&&(N.push(`xterm-underline-${x.extended.underlineStyle}`),L===" "&&(L="\xA0"),!x.isUnderlineColorDefault()))if(x.isUnderlineColorRGB())I.style.textDecorationColor=`rgb(${ue.toColorRGB(x.getUnderlineColor()).join(",")})`;else{let A=x.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&x.isBold()&&A<8&&(A+=8),I.style.textDecorationColor=f.ansi[A].css}x.isOverline()&&(N.push("xterm-overline"),L===" "&&(L="\xA0")),x.isStrikethrough()&&N.push("xterm-strikethrough"),kr&&(I.style.textDecoration="underline");let de=x.getFgColor(),Ft=x.getFgColorMode(),Se=x.getBgColor(),Ht=x.getBgColorMode(),Br=!!x.isInverse();if(Br){let A=de;de=Se,Se=A;let Pr=Ft;Ft=Ht,Ht=Pr}let Le,pi,Wt=!1;this._decorationService.forEachDecorationAtCell(ae,e,void 0,A=>{A.options.layer!=="top"&&Wt||(A.backgroundColorRGB&&(Ht=50331648,Se=A.backgroundColorRGB.rgba>>8&16777215,Le=A.backgroundColorRGB),A.foregroundColorRGB&&(Ft=50331648,de=A.foregroundColorRGB.rgba>>8&16777215,pi=A.foregroundColorRGB),Wt=A.options.layer==="top")}),!Wt&&Nt&&(Le=this._coreBrowserService.isFocused?f.selectionBackgroundOpaque:f.selectionInactiveBackgroundOpaque,Se=Le.rgba>>8&16777215,Ht=50331648,Wt=!0,f.selectionForeground&&(Ft=50331648,de=f.selectionForeground.rgba>>8&16777215,pi=f.selectionForeground)),Wt&&N.push("xterm-decoration-top");let Ae;switch(Ht){case 16777216:case 33554432:Ae=f.ansi[Se],N.push(`xterm-bg-${Se}`);break;case 50331648:Ae=O.toColor(Se>>16,Se>>8&255,Se&255),this._addStyle(I,`background-color:#${(Se>>>0).toString(16).padStart(6,"0")}`);break;case 0:default:Br?(Ae=f.foreground,N.push(`xterm-bg-${257}`)):Ae=f.background}switch(Le||x.isDim()&&(Le=k.multiplyOpacity(Ae,.5)),Ft){case 16777216:case 33554432:x.isBold()&&de<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(de+=8),this._applyMinimumContrast(I,Ae,f.ansi[de],x,Le,void 0)||N.push(`xterm-fg-${de}`);break;case 50331648:let A=O.toColor(de>>16&255,de>>8&255,de&255);this._applyMinimumContrast(I,Ae,A,x,Le,pi)||this._addStyle(I,`color:#${de.toString(16).padStart(6,"0")}`);break;case 0:default:this._applyMinimumContrast(I,Ae,f.foreground,x,Le,pi)||Br&&N.push(`xterm-fg-${257}`)}N.length&&(I.className=N.join(" "),N.length=0),!Ar&&!fi&&!Mr&&je?w++:I.textContent=L,Ot!==this.defaultSpacing&&(I.style.letterSpacing=`${Ot}px`),p.push(I),ae=Lr}return I&&w&&(I.textContent=L),p}_applyMinimumContrast(i,e,t,r,s,o){if(this._optionsService.rawOptions.minimumContrastRatio===1||js(r.getCode()))return!1;let a=this._getContrastCache(r),l;if(!s&&!o&&(l=a.getColor(e.rgba,t.rgba)),l===void 0){let h=this._optionsService.rawOptions.minimumContrastRatio/(r.isDim()?2:1);l=k.ensureContrastRatio(s??e,o??t,h),a.setColor((s??e).rgba,(o??t).rgba,l??null)}return l?(this._addStyle(i,`color:${l.css}`),!0):!1}_getContrastCache(i){return i.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(i,e){i.setAttribute("style",`${i.getAttribute("style")||""}${e};`)}_isCellInSelection(i,e){let t=this._selectionStart,r=this._selectionEnd;return!t||!r?!1:this._columnSelectMode?t[0]<=r[0]?i>=t[0]&&e>=t[1]&&i=t[1]&&i>=r[0]&&e<=r[1]:e>t[1]&&e=t[0]&&i=t[0]}};_t=y([m(1,gi),m(2,R),m(3,G),m(4,Y),m(5,ge),m(6,_e)],_t);var Hi=class{constructor(i=()=>new rs){this._flat=new Float32Array(256);this._font="";this._fontSize=0;this._weight="normal";this._weightBold="bold";this._canvasElements=[];this._canvasElements=[i(),i(),i(),i()],this.clear()}dispose(){this._canvasElements.length=0,this._holey=void 0}clear(){this._flat.fill(-9999),this._holey=new Map}setFont(i,e,t,r){i===this._font&&e===this._fontSize&&t===this._weight&&r===this._weightBold||(this._font=i,this._fontSize=e,this._weight=t,this._weightBold=r,this._canvasElements[0].setFont(i,e,t,!1),this._canvasElements[1].setFont(i,e,r,!1),this._canvasElements[2].setFont(i,e,t,!0),this._canvasElements[3].setFont(i,e,r,!0),this.clear())}get(i,e,t){let r;if(!e&&!t&&i.length===1&&(r=i.charCodeAt(0))<256){if(this._flat[r]!==-9999)return this._flat[r];let a=this._measure(i,0);return a>0&&(this._flat[r]=a),a}let s=i;e&&(s+="B"),t&&(s+="I");let o=this._holey.get(s);if(o===void 0){let a=0;e&&(a|=1),t&&(a|=2),o=this._measure(i,a),o>0&&this._holey.set(s,o)}return o}_measure(i,e){return this._canvasElements[e].measure(i)}},rs=class{constructor(){typeof OffscreenCanvas<"u"?(this._canvas=new OffscreenCanvas(1,1),this._ctx=is(this._canvas.getContext("2d"))):(this._canvas=document.createElement("canvas"),this._canvas.width=1,this._canvas.height=1,this._ctx=is(this._canvas.getContext("2d")))}setFont(i,e,t,r){let s=r?"italic":"";this._ctx.font=`${s} ${t} ${e}px ${i}`.trim()}measure(i){return this._ctx.measureText(i).width}};var ss=class{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(i,e,t,r=!1){if(this.selectionStart=e,this.selectionEnd=t,!e||!t||e[0]===t[0]&&e[1]===t[1]){this.clear();return}let s=i.buffers.active.ydisp,o=e[1]-s,a=t[1]-s,l=Math.max(o,0),h=Math.min(a,i.rows-1);if(l>=i.rows||h<0){this.clear();return}this.hasSelection=!0,this.columnSelectMode=r,this.viewportStartRow=o,this.viewportEndRow=a,this.viewportCappedStartRow=l,this.viewportCappedEndRow=h,this.startCol=e[0],this.endCol=t[0]}isCellSelected(i,e,t){return this.hasSelection?(t-=i.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?e>=this.startCol&&t>=this.viewportCappedStartRow&&e=this.viewportCappedStartRow&&e>=this.endCol&&t<=this.viewportCappedEndRow:t>this.viewportStartRow&&t=this.startCol&&e=this.startCol):!1}};function Js(){return new ss}var Wi=class extends g{constructor(e,t,r){super();this._renderCallback=e;this._coreBrowserService=t;this._optionsService=r;this._intervalDuration=0;this._blinkOn=!0;this._needsBlinkInViewport=!1;this._isViewportVisible=!0;this._register(this._optionsService.onSpecificOptionChange("blinkIntervalDuration",s=>{this.setIntervalDuration(s)})),this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration),this._register(E(()=>this._clearInterval()))}get isBlinkOn(){return this._blinkOn}get isEnabled(){return this._intervalDuration>0}setNeedsBlinkInViewport(e){this._needsBlinkInViewport!==e&&(this._needsBlinkInViewport=e,this._updateIntervalState())}setViewportVisible(e){this._isViewportVisible!==e&&(this._isViewportVisible=e,this._updateIntervalState())}setIntervalDuration(e){e!==this._intervalDuration&&(this._intervalDuration=e,this._clearInterval(),this._updateIntervalState())}_updateIntervalState(){if(this._intervalDuration>0&&this._needsBlinkInViewport&&this._isViewportVisible){if(this._interval!==void 0)return;let t=this._blinkOn;this._blinkOn=!0,this._interval=this._coreBrowserService.window.setInterval(()=>{this._blinkOn=!this._blinkOn,this._renderCallback()},this._intervalDuration),t||this._renderCallback();return}this._clearInterval(),this._blinkOn||(this._blinkOn=!0,this._renderCallback())}_clearInterval(){this._interval!==void 0&&(this._coreBrowserService.window.clearInterval(this._interval),this._interval=void 0)}};var Zn=1,mt=class extends g{constructor(e,t,r,s,o,a,l,h,d,c,u,_,p,v){super();this._terminal=e;this._document=t;this._element=r;this._screenElement=s;this._viewportElement=o;this._helperContainer=a;this._linkifier2=l;this._charSizeService=d;this._optionsService=c;this._bufferService=u;this._coreService=_;this._coreBrowserService=p;this._themeService=v;this._terminalClass=Zn++;this._rowElements=[];this._selectionRenderModel=Js();this._lastSelectionColumnMode=!1;this._rowHasBlinkingCells=[];this._rowHasBlinkingCellsCount=0;this._onRequestRedraw=this._register(new b);this.onRequestRedraw=this._onRequestRedraw.event;this._rowContainer=this._document.createElement("div"),this._rowContainer.classList.add("xterm-rows"),this._rowContainer.style.lineHeight="normal",this._rowContainer.setAttribute("aria-hidden","true"),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement("div"),this._selectionContainer.classList.add("xterm-selection"),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=Zs(),this._updateDimensions(),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._themeService.onChangeColors(f=>this._injectCss(f))),this._injectCss(this._themeService.colors),this._rowFactory=h.createInstance(_t,document),this._element.classList.add("xterm-dom-renderer-owner-"+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline(f=>this._handleLinkHover(f))),this._register(this._linkifier2.onHideLinkUnderline(f=>this._handleLinkLeave(f))),this._cursorBlinkStateManager=new ns(this._rowContainer,this._coreBrowserService),this._register(C(this._document,"mousedown",()=>this._cursorBlinkStateManager.restartBlinkAnimation())),this._register(E(()=>this._cursorBlinkStateManager.dispose())),this._textBlinkStateManager=this._register(new Wi(()=>this._onRequestRedraw.fire({start:0,end:this._bufferService.rows-1}),this._coreBrowserService,this._optionsService)),this._register(E(()=>{this._element.classList.remove("xterm-dom-renderer-owner-"+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new Hi,this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){let e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(let r of this._rowElements)r.style.width=`${this.dimensions.css.canvas.width}px`,r.style.height=`${this.dimensions.css.cell.height}px`,r.style.lineHeight=`${this.dimensions.css.cell.height}px`,r.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));let t=`${this._terminalSelector} .xterm-rows span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .xterm-rows { pointer-events: none; color: ${e.foreground.css};}`;t+=`${this._terminalSelector} .xterm-rows, ${this._terminalSelector} .xterm-rows span { font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`,t+=`${this._terminalSelector} .xterm-rows .xterm-dim { color: ${k.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}${this._terminalSelector} span.xterm-blink-hidden { visibility: hidden;}`;let r=`blink_underline_${this._terminalClass}`,s=`blink_bar_${this._terminalClass}`,o=`blink_block_${this._terminalClass}`;t+=`@keyframes ${r} { 50% { border-bottom-style: hidden; }}`,t+=`@keyframes ${s} { 50% { box-shadow: none; }}`,t+=`@keyframes ${o} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${s} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${o} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-cursor-blink-idle .xterm-cursor.xterm-cursor-blink { animation: none !important;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .xterm-selection { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .xterm-selection div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .xterm-selection div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(let[a,l]of e.ansi.entries())t+=`${this._terminalSelector} .xterm-fg-${a} { color: ${l.css}; }${this._terminalSelector} .xterm-fg-${a}.xterm-dim { color: ${k.multiplyOpacity(l,.5).css}; }${this._terminalSelector} .xterm-bg-${a} { background-color: ${l.css}; }`;t+=`${this._terminalSelector} .xterm-fg-${257} { color: ${k.opaque(e.background).css}; }${this._terminalSelector} .xterm-fg-${257}.xterm-dim { color: ${k.multiplyOpacity(k.opaque(e.background),.5).css}; }${this._terminalSelector} .xterm-bg-${257} { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){let e=this.dimensions.css.cell.width-this._widthCache.get("W",!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let r=this._rowElements.length;r<=t;r++){let s=this._document.createElement("div");this._rowContainer.appendChild(s),this._rowElements.push(s),this._rowHasBlinkingCells.push(!1)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop()),this._rowHasBlinkingCells.pop()&&this._rowHasBlinkingCellsCount--}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove("xterm-focus"),this._cursorBlinkStateManager.pause(),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add("xterm-focus"),this._cursorBlinkStateManager.resume(),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleViewportVisibilityChange(e){this._textBlinkStateManager.setViewportVisible(e)}handleSelectionChanged(e,t,r){let s=this._bufferService.rows;this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,r);let o=0,a=-1;this._lastSelectionStart&&this._lastSelectionEnd&&(this._selectionRenderModel.update(this._terminal,this._lastSelectionStart,this._lastSelectionEnd,this._lastSelectionColumnMode),this._selectionRenderModel.hasSelection&&(o=this._selectionRenderModel.viewportCappedStartRow,a=this._selectionRenderModel.viewportCappedEndRow));let l=0,h=-1;if(!e||!t)return;if(this._selectionRenderModel.update(this._terminal,e,t,r),this._selectionRenderModel.hasSelection){let u=this._selectionRenderModel.viewportStartRow,_=this._selectionRenderModel.viewportEndRow,p=this._selectionRenderModel.viewportCappedStartRow,v=this._selectionRenderModel.viewportCappedEndRow;l=p,h=v;let f=this._document.createDocumentFragment();if(r){let S=e[0]>t[0];f.appendChild(this._createSelectionElement(p,S?t[0]:e[0],S?e[0]:t[0],v-p+1))}else{let S=u===p?e[0]:0,I=p===_?t[0]:this._bufferService.cols;f.appendChild(this._createSelectionElement(p,S,I));let w=v-p-1;if(f.appendChild(this._createSelectionElement(p+1,0,this._bufferService.cols,w)),p!==v){let L=_===v?t[0]:this._bufferService.cols;f.appendChild(this._createSelectionElement(v,0,L))}}this._selectionContainer.appendChild(f)}let d=Math.min(o,l),c=Math.max(a,h);if(c>=0){d=Math.max(d,0),c=Math.min(c,s-1);let _=this._bufferService.buffer.y;this._selectionRenderModel.hasSelection&&_>=0&&_this.dimensions.css.canvas.width&&(l=this.dimensions.css.canvas.width-a),o.style.height=`${s*this.dimensions.css.cell.height}px`,o.style.top=`${e*this.dimensions.css.cell.height}px`,o.style.left=`${a}px`,o.style.width=`${l}px`,o}handleCursorMove(){this._cursorBlinkStateManager.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(let e of this._rowElements)e.replaceChildren();this._rowHasBlinkingCellsCount>0&&(this._rowHasBlinkingCells.fill(!1),this._rowHasBlinkingCellsCount=0,this._textBlinkStateManager.setNeedsBlinkInViewport(!1))}renderRows(e,t){let r=this._bufferService.buffer,s=r.ybase+r.y,o=Math.min(r.x,this._bufferService.cols-1),a=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,l=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,h=this._optionsService.rawOptions.cursorInactiveStyle,d={hasBlinkingCells:!1};for(let c=e;c<=t;c++){let u=c+r.ydisp,_=this._rowElements[c];if(!_)continue;let p=r.lines.get(u);if(!p){_.replaceChildren(),this._setRowBlinkState(c,!1);continue}_.replaceChildren(...this._rowFactory.createRow(p,u,u===s,l,h,o,a,this._textBlinkStateManager.isBlinkOn,this.dimensions.css.cell.width,this._widthCache,-1,-1,d)),this._setRowBlinkState(c,d.hasBlinkingCells)}this._updateTextBlinkState()}get _terminalSelector(){return`.xterm-dom-renderer-owner-${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,r,s,o,a){r<0&&(e=0),s<0&&(t=0);let l=this._bufferService.rows-1;r=Math.max(Math.min(r,l),0),s=Math.max(Math.min(s,l),0),o=Math.min(o,this._bufferService.cols);let h=this._bufferService.buffer,d=h.ybase+h.y,c=Math.min(h.x,o-1),u=this._optionsService.rawOptions.cursorBlink,_=this._optionsService.rawOptions.cursorStyle,p=this._optionsService.rawOptions.cursorInactiveStyle,v={hasBlinkingCells:!1};for(let f=r;f<=s;++f){let S=f+h.ydisp,I=this._rowElements[f];if(!I)continue;let w=h.lines.get(S);if(!w){I.replaceChildren(),this._setRowBlinkState(f,!1);continue}I.replaceChildren(...this._rowFactory.createRow(w,S,S===d,_,p,c,u,this._textBlinkStateManager.isBlinkOn,this.dimensions.css.cell.width,this._widthCache,a?f===r?e:0:-1,a?(f===s?t:o)-1:-1,v)),this._setRowBlinkState(f,v.hasBlinkingCells)}this._updateTextBlinkState()}_setRowBlinkState(e,t){this._rowHasBlinkingCells[e]!==t&&(this._rowHasBlinkingCells[e]=t,this._rowHasBlinkingCellsCount+=t?1:-1)}_updateTextBlinkState(){this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount>0)}};mt=y([m(7,Qe),m(8,Be),m(9,R),m(10,D),m(11,Y),m(12,G),m(13,_e)],mt);var ns=class{constructor(i,e){this._rowContainer=i;this._coreBrowserService=e;this._isIdlePaused=!1;this._coreBrowserService.isFocused&&this._resetIdleTimer()}dispose(){this._clearIdleTimer()}restartBlinkAnimation(){this._isIdlePaused&&this._rowContainer.classList.remove("xterm-cursor-blink-idle"),this._resetIdleTimer()}pause(){this._isIdlePaused=!1,this._clearIdleTimer()}resume(){this._isIdlePaused=!1,this._rowContainer.classList.remove("xterm-cursor-blink-idle"),this._resetIdleTimer()}_resetIdleTimer(){this._isIdlePaused=!1,this._clearIdleTimer(),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_clearIdleTimer(){this._idleTimeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}_stopBlinkingDueToIdle(){this._rowContainer.classList.add("xterm-cursor-blink-idle"),this._isIdlePaused=!0,this._idleTimeout=void 0}};var bt=class extends g{constructor(e,t,r){super();this._optionsService=r;this.width=0;this.height=0;this._onCharSizeChange=this._register(new b);this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new as(this._optionsService))}catch{this._measureStrategy=this._register(new os(e,t,this._optionsService))}this._register(this._optionsService.onMultipleOptionChange(["fontFamily","fontSize"],()=>this.measure()))}get hasValidSize(){return this.width>0&&this.height>0}measure(){let e=this._measureStrategy.measure();(e.width!==this.width||e.height!==this.height)&&(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};bt=y([m(2,R)],bt);var Ui=class extends g{constructor(){super(...arguments);this._result={width:0,height:0}}_validateAndSet(e,t){e!==void 0&&e>0&&t!==void 0&&t>0&&(this._result.width=e,this._result.height=t)}},os=class extends Ui{constructor(e,t,r){super();this._document=e;this._parentElement=t;this._optionsService=r;this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W".repeat(32),this._measureElement.setAttribute("aria-hidden","true"),this._measureElement.style.whiteSpace="pre",this._measureElement.style.fontKerning="none",this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}},as=class extends Ui{constructor(e){super();this._optionsService=e;this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext("2d");let t=this._ctx.measureText("W");if(!("width"in t&&"fontBoundingBoxAscent"in t&&"fontBoundingBoxDescent"in t))throw new Error("Required font metrics not supported")}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;let e=this._ctx.measureText("W");return this._validateAndSet(e.width,e.fontBoundingBoxAscent+e.fontBoundingBoxDescent),this._result}};var Ki=class extends g{constructor(e,t,r){super();this._textarea=e;this._window=t;this.mainDocument=r;this._isFocused=!1;this._cachedIsFocused=void 0;this._onDprChange=this._register(new b);this.onDprChange=this._onDprChange.event;this._onWindowChange=this._register(new b);this.onWindowChange=this._onWindowChange.event;this._screenDprMonitor=this._register(new ls(this._window)),this._register(this.onWindowChange(s=>this._screenDprMonitor.setWindow(s))),this._register(j.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register(C(this._textarea,"focus",()=>this._isFocused=!0)),this._register(C(this._textarea,"blur",()=>this._isFocused=!1))}get window(){return this._window}set window(e){this._window!==e&&(this._window=e,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return this._cachedIsFocused===void 0&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask(()=>this._cachedIsFocused=void 0)),this._cachedIsFocused}},ls=class extends g{constructor(e){super();this._parentWindow=e;this._windowResizeListener=this._register(new P);this._onDprChange=this._register(new b);this.onDprChange=this._onDprChange.event;this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register(E(()=>this.clearListener()))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=C(this._parentWindow,"resize",()=>this._setDprAndFireIfDiffers())}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){this._outerListener&&(this._resolutionMediaMatchList?.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){!this._resolutionMediaMatchList||!this._outerListener||(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}};var zi=class extends g{constructor(){super();this.linkProviders=[];this._register(E(()=>this.linkProviders.length=0))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{let t=this.linkProviders.indexOf(e);t!==-1&&this.linkProviders.splice(t,1)}}}};function qt(n,i,e){let t=e.getBoundingClientRect(),r=n.getComputedStyle(e),s=parseInt(r.getPropertyValue("padding-left"),10),o=parseInt(r.getPropertyValue("padding-top"),10);return[i.clientX-t.left-s,i.clientY-t.top-o]}function Qs(n,i,e,t,r,s,o,a,l){if(!s)return;let h=qt(n,i,e);return h[0]=Math.ceil((h[0]+(l?o/2:0))/o),h[1]=Math.ceil(h[1]/a),h[0]=Math.min(Math.max(h[0],1),t+(l?1:0)),h[1]=Math.min(Math.max(h[1],1),r),h}var vt=class{constructor(i,e){this._charSizeService=i;this._renderService=e}getCoords(i,e,t,r,s){return Qs(se(e),i,e,t,r,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,s)}getMouseReportCoords(i,e){let t=qt(se(e),i,e);if(this._charSizeService.hasValidSize)return t[0]=Math.min(Math.max(t[0],0),this._renderService.dimensions.css.canvas.width-1),t[1]=Math.min(Math.max(t[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(t[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(t[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(t[0]),y:Math.floor(t[1])}}};vt=y([m(0,Be),m(1,V)],vt);var en=typeof window=="object"?window:globalThis;function ce(n,i=0){return n[n.length-(1+i)]}function Jn(n,i,e){let t=null,r=null;if(typeof e.value=="function"?(t="value",r=e.value,r.length!==0&&console.warn("Memoize should only be used in functions with zero parameters")):typeof e.get=="function"&&(t="get",r=e.get),!r||!t)throw new Error("not supported");let s=`$memoize$${i}`,o=e;o[t]=function(...a){return this.hasOwnProperty(s)||Object.defineProperty(this,s,{configurable:!1,enumerable:!1,writable:!1,value:r.apply(this,a)}),this[s]}}var St=class St{constructor(i){this.element=i,this.next=St.Undefined,this.prev=St.Undefined}};St.Undefined=new St(void 0);var re=St,Gi=class{constructor(){this._first=re.Undefined;this._last=re.Undefined}push(i){return this._insert(i,!0)}_insert(i,e){let t=new re(i);if(this._first===re.Undefined)this._first=t,this._last=t;else if(e){let s=this._last;this._last=t,t.prev=s,s.next=t}else{let s=this._first;this._first=t,t.next=s,s.prev=t}let r=!1;return()=>{r||(r=!0,this._remove(t))}}_remove(i){if(i.prev!==re.Undefined&&i.next!==re.Undefined){let e=i.prev;e.next=i.next,i.next.prev=e}else i.prev===re.Undefined&&i.next===re.Undefined?(this._first=re.Undefined,this._last=re.Undefined):i.next===re.Undefined?(this._last=this._last.prev,this._last.next=re.Undefined):i.prev===re.Undefined&&(this._first=this._first.next,this._first.prev=re.Undefined)}*[Symbol.iterator](){let i=this._first;for(;i!==re.Undefined;)yield i.element,i=i.next}},he;(s=>(s.TAP="-xterm-gesturetap",s.CHANGE="-xterm-gesturechange",s.START="-xterm-gesturestart",s.END="-xterm-gesturesend",s.CONTEXT_MENU="-xterm-gesturecontextmenu"))(he||={});var K=class K extends g{constructor(){super();this._dispatched=!1;this._targets=new Gi;this._ignoreTargets=new Gi;this._activeTouches={},this._handle=null,this._lastSetTapCountTime=0;let e=en;this._register(C(e.document,"touchstart",t=>this._handleTouchStart(t),{passive:!1})),this._register(C(e.document,"touchend",t=>this._handleTouchEnd(e,t))),this._register(C(e.document,"touchmove",t=>this._handleTouchMove(t),{passive:!1}))}static addTarget(e){if(!K.isTouchDevice())return g.None;K._instance||(K._instance=new K);let t=K._instance._targets.push(e);return E(t)}static ignoreTarget(e){if(!K.isTouchDevice())return g.None;K._instance||(K._instance=new K);let t=K._instance._ignoreTargets.push(e);return E(t)}static isTouchDevice(){return"ontouchstart"in en||navigator.maxTouchPoints>0}dispose(){this._handle&&(this._handle.dispose(),this._handle=null),super.dispose()}_handleTouchStart(e){let t=Date.now();this._handle&&(this._handle.dispose(),this._handle=null);for(let r=0,s=e.targetTouches.length;r=K._holdDelay&&Math.abs(h.initialPageX-ce(h.rollingPageX))<30&&Math.abs(h.initialPageY-ce(h.rollingPageY))<30){let c=this._newGestureEvent(he.CONTEXT_MENU,h.initialTarget);c.pageX=ce(h.rollingPageX),c.pageY=ce(h.rollingPageY),this._dispatchEvent(c)}else if(s===1){let c=ce(h.rollingPageX),u=ce(h.rollingPageY),_=ce(h.rollingTimestamps)-h.rollingTimestamps[0],p=c-h.rollingPageX[0],v=u-h.rollingPageY[0],f=[...this._targets].filter(S=>h.initialTarget instanceof Node&&S.contains(h.initialTarget));this._inertia(e,f,r,Math.abs(p)/_,p>0?1:-1,c,Math.abs(v)/_,v>0?1:-1,u)}this._dispatchEvent(this._newGestureEvent(he.END,h.initialTarget)),delete this._activeTouches[l.identifier]}this._dispatched&&(t.preventDefault(),t.stopPropagation(),this._dispatched=!1)}_newGestureEvent(e,t){let r=document.createEvent("CustomEvent");return r.initEvent(e,!1,!0),r.initialTarget=t,r.tapCount=0,r}_dispatchEvent(e){if(e.type===he.TAP){let t=new Date().getTime(),r;t-this._lastSetTapCountTime>K._clearTapCountTime?r=1:r=2,this._lastSetTapCountTime=t,e.tapCount=r}else(e.type===he.CHANGE||e.type===he.CONTEXT_MENU)&&(this._lastSetTapCountTime=0);if(e.initialTarget instanceof Node){for(let r of this._ignoreTargets)if(r.contains(e.initialTarget))return;let t=[];for(let r of this._targets)if(r.contains(e.initialTarget)){let s=0,o=e.initialTarget;for(;o&&o!==r;)s++,o=o.parentElement;t.push([s,r])}t.sort((r,s)=>r[0]-s[0]);for(let[,r]of t)r.dispatchEvent(e),this._dispatched=!0}}_inertia(e,t,r,s,o,a,l,h,d){this._handle=tt(e,()=>{let c=Date.now(),u=c-r,_=0,p=0,v=!0;s+=K._scrollFriction*u,l+=K._scrollFriction*u,s>0&&(v=!1,_=o*s*u),l>0&&(v=!1,p=h*l*u);let f=this._newGestureEvent(he.CHANGE);f.translationX=_,f.translationY=p,t.forEach(S=>S.dispatchEvent(f)),v||this._inertia(e,t,c,s,o,a+_,l,h,d+p)})}_handleTouchMove(e){let t=Date.now();for(let r=0,s=e.changedTouches.length;r3&&(a.rollingPageX.shift(),a.rollingPageY.shift(),a.rollingTimestamps.shift()),a.rollingPageX.push(o.pageX),a.rollingPageY.push(o.pageY),a.rollingTimestamps.push(t)}this._dispatched&&(e.preventDefault(),e.stopPropagation(),this._dispatched=!1)}};K._scrollFriction=-.005,K._holdDelay=700,K._clearTapCountTime=400,y([Jn],K,"isTouchDevice",1);var Vi=K;var gt=class{constructor(i,e,t,r,s,o,a,l,h){this._renderService=i;this._mouseCoordsService=e;this._mouseStateService=t;this._coreService=r;this._bufferService=s;this._optionsService=o;this._selectionService=a;this._logService=l;this._coreBrowserService=h;this._lastEvent=null;this._wheelPartialScroll=0;this._touchScrollAccumulator=0}bindMouse(i,e,t){let{element:r,document:s}=i,o={mouseup:null,wheel:null,mousedrag:null,mousemove:null},a={target:i,focus:t,requestedEvents:o},l={mouseup:h=>this._handleMouseUp(a,h),wheel:h=>this._handleWheel(a,h),mousedrag:h=>this._handleMouseDrag(a,h),mousemove:h=>this._handleMouseMove(a,h)};this._altMouseCursor=new cs(r,s,()=>this._mouseStateService.areMouseEventsActive&&!!this._optionsService.rawOptions.mouseEventsRequireAlt),e(this._altMouseCursor),e(this._mouseStateService.onProtocolChange(h=>{this._handleProtocolChange(a,l,h)})),e(this._optionsService.onSpecificOptionChange("mouseEventsRequireAlt",()=>{this._syncMouseModeState(r),this._altMouseCursor?.sync()})),this._mouseStateService.activeProtocol=this._mouseStateService.activeProtocol,e(E(()=>{o.mouseup&&s.removeEventListener("mouseup",o.mouseup),o.mousedrag&&s.removeEventListener("mousemove",o.mousedrag)})),e(C(r,"mousedown",h=>this._handleMouseDown(a,h))),e(C(r,"wheel",h=>this._handlePassiveWheel(a,h),{passive:!1})),e(Vi.addTarget(i.screenElement)),e(C(i.screenElement,he.START,()=>this._handleTouchStart())),e(C(i.screenElement,he.CHANGE,h=>this._handleTouchChange(a,h)))}_sendEvent(i,e){let t=this._mouseCoordsService.getMouseReportCoords(e,i.target.screenElement);if(!t)return!1;let r,s;switch(e.overrideType||e.type){case"mousemove":s=32,e.buttons===void 0?(r=3,e.button!==void 0&&(r=e.button<3?e.button:3)):r=e.buttons&1?0:e.buttons&4?1:e.buttons&2?2:3;break;case"mouseup":s=0,r=e.button<3?e.button:3;break;case"mousedown":s=1,r=e.button<3?e.button:3;break;case"wheel":if(!this._mouseStateService.allowCustomWheelEvent(e))return!1;let a=e.deltaY;if(a===0||this._consumeWheelEvent(e,this._renderService?.dimensions?.device?.cell?.height,this._coreBrowserService?.dpr)===0)return!1;s=a<0?0:1,r=4;break;default:return!1}if(s===void 0||r===void 0||r>4||r!==4&&this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive&&!e.altKey)return!1;let o=r!==4&&this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive;return this._triggerMouseEvent({col:t.col,row:t.row,x:t.x,y:t.y,button:r,action:s,ctrl:e.ctrlKey,alt:o?!1:e.altKey,shift:e.shiftKey})}_handleMouseUp(i,e){this._sendEvent(i,e),e.buttons||(i.requestedEvents.mouseup&&i.target.document.removeEventListener("mouseup",i.requestedEvents.mouseup),i.requestedEvents.mousedrag&&i.target.document.removeEventListener("mousemove",i.requestedEvents.mousedrag))}_handleWheel(i,e){return this._sendEvent(i,e),e.preventDefault(),e.stopPropagation(),!1}_handleMouseDrag(i,e){e.buttons&&this._sendEvent(i,e)}_handleMouseMove(i,e){e.buttons||this._sendEvent(i,e)}_handleMouseDown(i,e){e.preventDefault(),i.focus(),!(!this._mouseStateService.areMouseEventsActive||this._selectionService.shouldForceSelection(e))&&(this._sendEvent(i,e),i.requestedEvents.mouseup&&i.target.document.addEventListener("mouseup",i.requestedEvents.mouseup),i.requestedEvents.mousedrag&&i.target.document.addEventListener("mousemove",i.requestedEvents.mousedrag))}_handlePassiveWheel(i,e){if(!i.requestedEvents.wheel){if(!this._mouseStateService.allowCustomWheelEvent(e))return!1;if(!this._bufferService.buffer.hasScrollback){if(e.deltaY===0)return!1;if(this._consumeWheelEvent(e,this._renderService?.dimensions?.device?.cell?.height,this._coreBrowserService?.dpr)===0)return e.preventDefault(),e.stopPropagation(),!1;let s="\x1B"+(this._coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(e.deltaY<0?"A":"B");return this._coreService.triggerDataEvent(s,!0),e.preventDefault(),e.stopPropagation(),!1}}}_handleTouchStart(){this._touchScrollAccumulator=0}_handleTouchChange(i,e){if(e.preventDefault(),e.stopPropagation(),i.requestedEvents.wheel){this._handleTouchScrollAsWheel(i,e);return}if(!this._bufferService.buffer.hasScrollback){this._handleTouchScrollAsKeys(e);return}i.target.handleTouchScroll?.(e.translationY)}_handleTouchScrollAsKeys(i){let e=this._renderService?.dimensions.css.cell.height;if(!e)return;this._touchScrollAccumulator-=i.translationY;let t=Math.trunc(this._touchScrollAccumulator/e);if(t===0)return;this._touchScrollAccumulator-=t*e;let r="\x1B"+(this._coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(t<0?"A":"B");for(let s=0;s0?1:-1),this._wheelPartialScroll%=1):i.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(s*=this._bufferService.rows),s}_triggerMouseEvent(i){if(i.col<0||i.col>=this._bufferService.cols||i.row<0||i.row>=this._bufferService.rows||i.button===4&&i.action===32||i.button===3&&i.action!==32||i.button!==4&&(i.action===2||i.action===3)||(i.col++,i.row++,i.action===32&&this._lastEvent&&this._equalEvents(this._lastEvent,i,this._mouseStateService.isPixelEncoding))||!this._mouseStateService.restrictMouseEvent(i))return!1;let e=this._mouseStateService.encodeMouseEvent(i);return e&&(this._mouseStateService.isDefaultEncoding?this._coreService.triggerBinaryEvent(e):this._coreService.triggerDataEvent(e,!0)),this._lastEvent=i,!0}_explainEvents(i){return{down:!!(i&1),up:!!(i&2),drag:!!(i&4),move:!!(i&8),wheel:!!(i&16)}}_equalEvents(i,e,t){if(t){if(i.x!==e.x||i.y!==e.y)return!1}else if(i.col!==e.col||i.row!==e.row)return!1;return!(i.button!==e.button||i.action!==e.action||i.ctrl!==e.ctrl||i.alt!==e.alt||i.shift!==e.shift)}};gt=y([m(0,V),m(1,Pe),m(2,Me),m(3,Y),m(4,D),m(5,R),m(6,Si),m(7,fe),m(8,G)],gt);var cs=class{constructor(i,e,t){this._element=i;this._document=e;this._isActive=t;this._listeners=new P}dispose(){this._listeners.dispose()}sync(){if(this._listeners.clear(),!this._isActive())return;let i=new pe,e=r=>this.syncFromModifier(r);i.add(C(this._document,"keydown",e)),i.add(C(this._document,"keyup",e)),i.add(C(this._element,"mousemove",e));let t=this._element.ownerDocument?.defaultView;t&&i.add(C(t,"blur",()=>{this._isActive()&&this.resetClass()})),this._listeners.value=i}resetClass(){this._updateClass(!1)}syncFromModifier(i){this._isActive()&&this._updateClass(i.getModifierState("Alt"))}_updateClass(i){i?this._element.classList.add("enable-mouse-events"):this._element.classList.remove("enable-mouse-events")}};var $i=class{constructor(i,e){this._renderCallback=i;this._coreBrowserService=e;this._refreshCallbacks=[]}dispose(){this._animationFrame!==void 0&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}addRefreshCallback(i){return this._refreshCallbacks.push(i),this._animationFrame??=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()),this._animationFrame}refresh(i,e,t){this._rowCount=t,i=i??0,e=e??this._rowCount-1,this._rowStart=this._rowStart!==void 0?Math.min(this._rowStart,i):i,this._rowEnd=this._rowEnd!==void 0?Math.max(this._rowEnd,e):e,this._animationFrame===void 0&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._animationFrame=void 0,this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0){this._runRefreshCallbacks();return}let i=Math.max(this._rowStart,0),e=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(i,e),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(let i of this._refreshCallbacks)i(0);this._refreshCallbacks=[]}};var qi=class{constructor(i){this._tasks=[];this._i=0;this._logService=i}enqueue(i){this._tasks.push(i),this._start()}flush(){for(;this._is){r-e<-20&&this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(r-e))}ms`),this._start();return}r=s}this.clear()}},hs=class extends qi{_requestCallback(i){return setTimeout(()=>i(this._createDeadline(16)))}_cancelCallback(i){clearTimeout(i)}_createDeadline(i){let e=performance.now()+i;return{timeRemaining:()=>Math.max(0,e-performance.now())}}},ds=class extends qi{_requestCallback(i){return requestIdleCallback(i)}_cancelCallback(i){cancelIdleCallback(i)}},It="requestIdleCallback"in globalThis?ds:hs,Xi=class{constructor(i){this._queue=new It(i)}set(i){this._queue.clear(),this._queue.enqueue(i)}flush(){this._queue.flush()}dispose(){this._queue.clear()}};var Ct=class extends g{constructor(e,t,r,s,o,a,l,h,d,c){super();this._rowCount=e;this._optionsService=r;this._logService=s;this._charSizeService=o;this._coreService=a;this._coreBrowserService=d;this._renderer=this._register(new P);this._observerDisposable=this._register(new P);this._isPaused=!1;this._needsFullRefresh=!1;this._isNextRenderRedrawOnly=!0;this._needsSelectionRefresh=!1;this._canvasWidth=0;this._canvasHeight=0;this._selectionState={start:void 0,end:void 0,columnSelectMode:!1};this._onDimensionsChange=this._register(new b);this.onDimensionsChange=this._onDimensionsChange.event;this._onRenderedViewportChange=this._register(new b);this.onRenderedViewportChange=this._onRenderedViewportChange.event;this._onRender=this._register(new b);this.onRender=this._onRender.event;this._onRefreshRequest=this._register(new b);this.onRefreshRequest=this._onRefreshRequest.event;this._pausedResizeTask=this._register(new Xi(this._logService)),this._renderDebouncer=new $i((u,_)=>this._renderRows(u,_),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new us(this._coreBrowserService,this._coreService,()=>this._fullRefresh()),this._register(E(()=>this._syncOutputHandler.dispose())),this._register(this._coreBrowserService.onDprChange(()=>this.handleDevicePixelRatioChange())),this._register(h.onResize(()=>this._fullRefresh())),this._register(h.buffers.onBufferActivate(()=>this._renderer.value?.clear())),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this._register(l.onDecorationRegistered(()=>this._fullRefresh())),this._register(l.onDecorationRemoved(()=>this._fullRefresh())),this._register(this._optionsService.onMultipleOptionChange(["drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio","rescaleOverlappingGlyphs"],()=>{this.clear(),this.handleResize(h.cols,h.rows),this._fullRefresh()})),this._register(this._optionsService.onMultipleOptionChange(["cursorBlink","cursorStyle"],()=>this.refreshRows(h.buffer.y,h.buffer.y,void 0,!0))),this._register(c.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(this._coreBrowserService.window,t),this._register(this._coreBrowserService.onWindowChange(u=>this._registerIntersectionObserver(u,t)))}get dimensions(){return this._renderer.value.dimensions}_registerIntersectionObserver(e,t){if("IntersectionObserver"in e){let r=new e.IntersectionObserver(s=>this._handleIntersectionChange(s[s.length-1]),{threshold:0});this._observerDisposable.value=E(()=>{this._intersectionObserver?.disconnect(),this._intersectionObserver=void 0}),this._intersectionObserver=r,r.observe(t)}}_handleIntersectionChange(e){this._isPaused=e.isIntersecting===void 0?e.intersectionRatio===0:!e.isIntersecting,this._renderer.value?.handleViewportVisibilityChange?.(!this._isPaused),!this._isPaused&&!this._charSizeService.hasValidSize&&this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,r=!1,s=!1){if(this._isPaused){this._needsFullRefresh=!0;return}if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}let o=this._syncOutputHandler.flush();o&&(e=Math.min(e,o.start),t=Math.max(t,o.end)),s||(this._isNextRenderRedrawOnly=!1),r?this._renderRows(e,t):this._renderDebouncer.refresh(e,t,this._rowCount)}_renderRows(e,t){if(this._renderer.value){if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0}}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value&&(this._renderer.value.onRequestRedraw(t=>this.refreshRows(t.start,t.end,t.sync,!0)),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){this._renderer.value&&(this._renderer.value.clearTextureAtlas?.(),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>this._renderer.value?.handleResize(e,t)):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,t,r){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=r,this._renderer.value?.handleSelectionChanged(e,t,r)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};Ct=y([m(2,R),m(3,fe),m(4,Be),m(5,Y),m(6,ge),m(7,D),m(8,G),m(9,_e)],Ct);var us=class{constructor(i,e,t){this._coreBrowserService=i;this._coreService=e;this._onTimeout=t;this._start=0;this._end=0;this._isBuffering=!1}bufferRows(i,e){this._isBuffering?(this._start=Math.min(this._start,i),this._end=Math.max(this._end,e)):(this._start=i,this._end=e,this._isBuffering=!0),this._timeout??=this._coreBrowserService.window.setTimeout(()=>{this._timeout=void 0,this._coreService.decPrivateModes.synchronizedOutput=!1,this._onTimeout()},1e3)}flush(){if(this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0),!this._isBuffering)return;let i={start:this._start,end:this._end};return this._isBuffering=!1,i}dispose(){this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}};function tn(n,i,e,t){let r=e.buffer.x,s=e.buffer.y;if(!e.buffer.hasScrollback)return ro(r,s,n,i,e,t)+Yi(s,i,e,t)+so(r,s,n,i,e,t);let o;if(s===i)return o=r>n?"D":"C",Yt(Math.abs(r-n),Xt(o,t));o=s>i?"D":"C";let a=Math.abs(s-i),l=io(s>i?n:r,e)+(a-1)*e.cols+1+to(s>i?r:n,e);return Yt(l,Xt(o,t))}function to(n,i){return n-1}function io(n,i){return i.cols-n}function ro(n,i,e,t,r,s){return Yi(i,t,r,s).length===0?"":Yt(sn(n,i,n,i-$e(i,r),!1,r).length,Xt("D",s))}function Yi(n,i,e,t){let r=n-$e(n,e),s=i-$e(i,e),o=Math.abs(r-s)-no(n,i,e);return Yt(o,Xt(rn(n,i),t))}function so(n,i,e,t,r,s){let o;Yi(i,t,r,s).length>0?o=t-$e(t,r):o=i;let a=t,l=oo(n,i,e,t,r,s);return Yt(sn(n,o,e,a,l==="C",r).length,Xt(l,s))}function no(n,i,e){let t=0,r=n-$e(n,e),s=i-$e(i,e);for(let o=0;o=0&&n0?o=t-$e(t,r):o=i,n=e&&oi?"A":"B"}function sn(n,i,e,t,r,s){let o=n,a=i,l="";for(;(o!==e||a!==t)&&a>=0&&as.cols-1?(l+=s.buffer.translateBufferLineToString(a,!1,n,o),o=0,n=0,a++):!r&&o<0&&(l+=s.buffer.translateBufferLineToString(a,!1,0,n+1),o=s.cols-1,n=o,a--);return l+s.buffer.translateBufferLineToString(a,!1,n,o)}function Xt(n,i){let e=i?"O":"[";return"\x1B"+e+n}function Yt(n,i){n=Math.floor(n);let e="";for(let t=0;tthis._bufferService.cols?i%this._bufferService.cols===0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(i/this._bufferService.cols)-1]:[i%this._bufferService.cols,this.selectionStart[1]+Math.floor(i/this._bufferService.cols)]:[i,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){let i=this.selectionStart[0]+this.selectionStartLength;return i>this._bufferService.cols?[i%this._bufferService.cols,this.selectionStart[1]+Math.floor(i/this._bufferService.cols)]:[Math.max(i,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){let i=this.selectionStart,e=this.selectionEnd;return!i||!e?!1:i[1]>e[1]||i[1]===e[1]&&i[0]>e[0]}handleTrim(i){return this.selectionStart&&(this.selectionStart[1]-=i),this.selectionEnd&&(this.selectionEnd[1]-=i),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):this.selectionStart&&this.selectionStart[1]<0?(this.selectionStart=[0,0],!0):!1}};function fs(n,i){if(n.start.y>n.end.y)throw new Error(`Buffer range end (${n.end.x}, ${n.end.y}) cannot be before start (${n.start.x}, ${n.start.y})`);return i*(n.end.y-n.start.y)+(n.end.x-n.start.x+1)}var ao="\xA0",lo=new RegExp(ao,"g");var Et=class extends g{constructor(e,t,r,s,o,a,l,h,d,c){super();this._element=e;this._screenElement=t;this._linkifier=r;this._bufferService=s;this._coreService=o;this._mouseCoordsService=a;this._optionsService=l;this._mouseStateService=h;this._renderService=d;this._coreBrowserService=c;this._dragScrollAmount=0;this._enabled=!0;this._trimListener=this._register(new P);this._workCell=new F;this._mouseDownTimeStamp=0;this._oldHasSelection=!1;this._oldSelectionStart=void 0;this._oldSelectionEnd=void 0;this._onLinuxMouseSelection=this._register(new b);this.onLinuxMouseSelection=this._onLinuxMouseSelection.event;this._onRedrawRequest=this._register(new b);this.onRequestRedraw=this._onRedrawRequest.event;this._onSelectionChange=this._register(new b);this.onSelectionChange=this._onSelectionChange.event;this._onRequestScrollLines=this._register(new b);this.onRequestScrollLines=this._onRequestScrollLines.event;this._mouseMoveListener=u=>this._handleMouseMove(u),this._mouseUpListener=u=>this._handleMouseUp(u),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener.value=this._bufferService.buffer.lines.onTrim(u=>this._handleTrim(u)),this._register(this._bufferService.buffers.onBufferActivate(u=>this._handleBufferActivate(u))),this.enable(),this._model=new ji(this._bufferService),this._activeSelectionMode=0,this._register(E(()=>{this._removeMouseDownListeners()})),this._register(this._bufferService.onResize(u=>{u.rowsChanged&&this.clearSelection()}))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!e||!t?!1:e[0]!==t[0]||e[1]!==t[1]}get selectionText(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";let r=this._bufferService.buffer,s=[];if(this._activeSelectionMode===3){if(e[0]===t[0])return"";let a=e[0]a.replace(lo," ")).join(Ue?`\r ++`)})),this._register(this._bufferService.onResize(()=>this.queueSync())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._latestYDisp=void 0,this.queueSync()})),this._register(this._bufferService.onScroll(()=>this._sync())),this._register(this._renderService.onRender(()=>{this._needsSyncOnRender&&(this._needsSyncOnRender=!1,this._sync())})),this._register(this._scrollableElement.onScroll(u=>this._handleScroll(u)))}scrollLines(e){let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:t.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,t){t&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!t,scrollTop:e*this._renderService.dimensions.css.cell.height})}_getChangeOptions(){let e=this._optionsService.rawOptions.scrollbar?.showScrollbar??!0,t=this._optionsService.rawOptions.scrollbar?.showArrows??!1,r=e?this._optionsService.rawOptions.scrollbar?.width??14:0;return{mouseWheelScrollSensitivity:this._optionsService.rawOptions.scrollSensitivity,fastScrollSensitivity:this._optionsService.rawOptions.fastScrollSensitivity,vertical:e?1:2,verticalScrollbarSize:r,verticalHasArrows:t}}queueSync(e){e!==void 0&&(this._latestYDisp=e),this._queuedAnimationFrame===void 0&&(this._queuedAnimationFrame=this._renderService.addRefreshCallback(()=>{this._queuedAnimationFrame=void 0,this._sync(this._latestYDisp)}))}_sync(e=this._bufferService.buffer.ydisp){if(!(!this._renderService||this._isSyncing)){if(this._coreService.decPrivateModes.synchronizedOutput){this._needsSyncOnRender=!0;return}this._isSyncing=!0,this._suppressOnScrollHandler=!0,this._scrollableElement.setScrollDimensions({height:this._renderService.dimensions.css.canvas.height,scrollHeight:this._renderService.dimensions.css.cell.height*this._bufferService.buffer.lines.length}),this._suppressOnScrollHandler=!1,e!==this._latestYDisp&&this._scrollableElement.setScrollPosition({scrollTop:e*this._renderService.dimensions.css.cell.height}),this._isSyncing=!1}}_handleScroll(e){if(!this._renderService||this._isHandlingScroll||this._suppressOnScrollHandler)return;this._isHandlingScroll=!0;let t=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),r=t-this._bufferService.buffer.ydisp;r!==0&&(this._latestYDisp=t,this._onRequestScrollLines.fire(r)),this._isHandlingScroll=!1}handleTouchScroll(e){let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({scrollTop:t.scrollTop-e})}};dt=y([m(2,D),m(3,G),m(4,Y),m(5,Me),m(6,_e),m(7,R),m(8,V)],dt);var ut=class extends g{constructor(e,t,r,s,o){super();this._screenElement=e;this._bufferService=t;this._coreBrowserService=r;this._decorationService=s;this._renderService=o;this._decorationElements=new Map;this._altBufferIsActive=!1;this._dimensionsChanged=!1;this._container=document.createElement("div"),this._container.classList.add("xterm-decoration-container"),this._screenElement.appendChild(this._container),this._register(this._renderService.onRenderedViewportChange(()=>this._doRefreshDecorations())),this._register(this._renderService.onDimensionsChange(()=>{this._dimensionsChanged=!0,this._queueRefresh()})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt})),this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh())),this._register(this._decorationService.onDecorationRemoved(a=>this._removeDecoration(a))),this._register(E(()=>{this._container.remove(),this._decorationElements.clear()}))}_queueRefresh(){this._animationFrame===void 0&&(this._animationFrame=this._renderService.addRefreshCallback(()=>{this._doRefreshDecorations(),this._animationFrame=void 0}))}_doRefreshDecorations(){for(let e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){let t=this._coreBrowserService.mainDocument.createElement("div");t.classList.add("xterm-decoration"),t.classList.toggle("xterm-decoration-top-layer",e?.options?.layer==="top"),t.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,t.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,t.style.top=`${(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height}px`,t.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;let r=e.options.x??0;return r&&r>this._bufferService.cols&&(t.style.display="none"),this._refreshXPosition(e,t),t}_refreshStyle(e){let t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display="none",e.onRenderEmitter.fire(e.element));else{let r=this._decorationElements.get(e);r||(r=this._createElement(e),e.element=r,this._decorationElements.set(e,r),this._container.appendChild(r),e.onDispose(()=>{this._decorationElements.delete(e),r.remove()})),r.style.display=this._altBufferIsActive?"none":"block",this._altBufferIsActive||(r.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,r.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,r.style.top=`${t*this._renderService.dimensions.css.cell.height}px`,r.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(r)}}_refreshXPosition(e,t=e.element){if(!t)return;let r=e.options.x??0;(e.options.anchor||"left")==="right"?t.style.right=r?`${r*this._renderService.dimensions.css.cell.width}px`:"":t.style.left=r?`${r*this._renderService.dimensions.css.cell.width}px`:""}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};ut=y([m(1,D),m(2,G),m(3,ge),m(4,V)],ut);var Pi=class{constructor(){this._zones=[];this._zonePool=[];this._zonePoolIndex=0;this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(i){if(i.options.overviewRulerOptions){for(let e of this._zones)if(e.color===i.options.overviewRulerOptions.color&&e.position===i.options.overviewRulerOptions.position){if(this._lineIntersectsZone(e,i.marker.line))return;if(this._lineAdjacentToZone(e,i.marker.line,i.options.overviewRulerOptions.position)){this._addLineToZone(e,i.marker.line);return}}if(this._zonePoolIndex=i.startBufferLine&&e<=i.endBufferLine}_lineAdjacentToZone(i,e,t){return e>=i.startBufferLine-this._linePadding[t||"full"]&&e<=i.endBufferLine+this._linePadding[t||"full"]}_addLineToZone(i,e){i.startBufferLine=Math.min(i.startBufferLine,e),i.endBufferLine=Math.max(i.endBufferLine,e)}};var Ce={full:0,left:0,center:0,right:0},Fe={full:0,left:0,center:0,right:0},$t={full:0,left:0,center:0,right:0},ze=class extends g{constructor(e,t,r,s,o,a,l,h){super();this._viewportElement=e;this._screenElement=t;this._bufferService=r;this._decorationService=s;this._renderService=o;this._optionsService=a;this._themeService=l;this._coreBrowserService=h;this._colorZoneStore=new Pi;this._shouldUpdateDimensions=!0;this._shouldUpdateAnchor=!0;this._lastKnownBufferLength=0;this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add("xterm-decoration-overview-ruler"),this._refreshCanvasDimensions(),this._viewportElement.parentElement?.insertBefore(this._canvas,this._viewportElement),this._register(E(()=>this._canvas?.remove()));let d=this._canvas.getContext("2d");if(d)this._ctx=d;else throw new Error("Ctx cannot be null");this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh(void 0,!0))),this._register(this._decorationService.onDecorationRemoved(()=>this._queueRefresh(void 0,!0))),this._register(this._renderService.onRenderedViewportChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?"none":"block"})),this._register(this._bufferService.onScroll(()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())})),this._register(this._renderService.onDimensionsChange(()=>this._queueRefresh(!0))),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh(!0))),this._register(this._optionsService.onSpecificOptionChange("scrollbar",()=>this._queueRefresh(!0))),this._register(this._themeService.onChangeColors(()=>this._queueRefresh())),this._register(E(()=>{this._animationFrame!==void 0&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)})),this._queueRefresh(!0)}get _width(){let e=this._optionsService.rawOptions.scrollbar;return e?.showScrollbar??!0?e?.width??0:0}_refreshDrawConstants(){let e=Math.floor((this._canvas.width-1)/3),t=Math.ceil((this._canvas.width-1)/3);Fe.full=this._canvas.width,Fe.left=e,Fe.center=t,Fe.right=e,this._refreshDrawHeightConstants(),$t.full=1,$t.left=1,$t.center=1+Fe.left,$t.right=1+Fe.left+Fe.center}_refreshDrawHeightConstants(){Ce.full=Math.round(2*this._coreBrowserService.dpr);let e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);Ce.left=t,Ce.center=t,Ce.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Ce.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Ce.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Ce.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Ce.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){if(this._store.isDisposed||!this._renderService.hasRenderer())return;let e=this._renderService.dimensions.css.canvas.height,t=this._renderService.dimensions.device.canvas.height;this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${e}px`,this._canvas.height=t,this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){if(this._store.isDisposed||!this._renderService.hasRenderer())return;this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(let t of this._decorationService.decorations)this._colorZoneStore.addDecoration(t);this._ctx.lineWidth=1,this._renderRulerOutline();let e=this._colorZoneStore.zones;for(let t of e)t.position!=="full"&&this._renderColorZone(t);for(let t of e)t.position==="full"&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderRulerOutline(){this._ctx.fillStyle=this._themeService.colors.overviewRulerBorder.css,this._ctx.fillRect(0,0,1,this._canvas.height),this._optionsService.rawOptions.scrollbar?.overviewRuler?.showTopBorder&&this._ctx.fillRect(1,0,this._canvas.width-1,1),this._optionsService.rawOptions.scrollbar?.overviewRuler?.showBottomBorder&&this._ctx.fillRect(1,this._canvas.height-1,this._canvas.width-1,this._canvas.height)}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect($t[e.position||"full"],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-Ce[e.position||"full"]/2),Fe[e.position||"full"],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+Ce[e.position||"full"]))}_queueRefresh(e,t){this._store.isDisposed||(this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,this._animationFrame===void 0&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._store.isDisposed||this._refreshDecorations(),this._animationFrame=void 0})))}};ze=y([m(2,D),m(3,ge),m(4,V),m(5,R),m(6,_e),m(7,G)],ze);var ft=class{constructor(i,e,t,r,s,o){this._textarea=i;this._compositionView=e;this._bufferService=t;this._optionsService=r;this._coreService=s;this._renderService=o;this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._compositionSuffix="",this._dataAlreadySent="",this._pendingKeypressData=""}get isComposing(){return this._isComposing}compositionstart(){this._isComposing=!0;let i=this._textarea.selectionStart??this._textarea.value.length,e=this._textarea.selectionEnd??i;this._compositionPosition.start=Math.min(i,e),this._compositionPosition.end=Math.max(i,e),this._compositionSuffix=this._textarea.value.substring(this._compositionPosition.end),this._compositionView.textContent="",this._dataAlreadySent="",this._compositionView.classList.add("active")}compositionupdate(i){this._compositionView.textContent=`\u200E${i.data}\u200E`,this.updateCompositionElements(),setTimeout(()=>{let e=this._textarea.selectionEnd??this._textarea.value.length;this._compositionPosition.end=Math.max(this._compositionPosition.start,e)},0)}compositionend(){this._finalizeComposition(!0)}keydown(i){if(this._isComposing||this._isSendingComposition){if(i.keyCode===20||i.keyCode===229||i.keyCode===16||i.keyCode===17||i.keyCode===18)return!1;this._finalizeComposition(!1)}return i.keyCode===229?(this._handleAnyTextareaChanges(),!1):!0}keypress(i){return this._isSendingComposition?(this._pendingKeypressData+=i,!0):!1}_finalizeComposition(i){if(this._compositionView.classList.remove("active"),this._isComposing=!1,i){let e={start:this._compositionPosition.start,end:this._compositionPosition.end},t=this._compositionSuffix;this._pendingKeypressData="",this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){this._isSendingComposition=!1;let r;if(e.start+=this._dataAlreadySent.length,this._isComposing)r=this._textarea.value.substring(e.start,this._compositionPosition.start);else{let s=this._textarea.value,o=t.length>0&&s.endsWith(t)?s.length-t.length:s.length;r=s.substring(e.start,Math.max(e.start,o))}this._sendCompositionInput(r)}},0)}else{this._isSendingComposition=!1;let e=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._sendCompositionInput(e)}}_sendCompositionInput(i){let e=this._pendingKeypressData;if(!i.includes(e))if(e.includes(i))i=e;else{let t=Math.min(i.length,e.length);for(;t>0&&!i.endsWith(e.substring(0,t));)t--;let r=Math.min(i.length,e.length);for(;r>0&&!e.endsWith(i.substring(0,r));)r--;i=t>r?i+e.substring(t):e+i.substring(r)}this._pendingKeypressData="",i.length>0&&this._coreService.triggerDataEvent(i,!0)}_handleAnyTextareaChanges(){if(this._textareaChangeTimer)return;let i=this._textarea.value;this._textareaChangeTimer=window.setTimeout(()=>{if(this._textareaChangeTimer=void 0,!this._isComposing){let e=this._textarea.value,t=e.replace(i,"");this._dataAlreadySent=t,e.length>i.length?this._coreService.triggerDataEvent(t,!0):e.lengththis.updateCompositionElements(!0),0)}}};ft=y([m(2,D),m(3,R),m(4,Y),m(5,V)],ft);var J=0,Q=0,ee=0,W=0,ts={css:"#00000000",rgba:0},O;(t=>{function n(r,s,o,a){return a!==void 0?`#${Ve(r)}${Ve(s)}${Ve(o)}${Ve(a)}`:`#${Ve(r)}${Ve(s)}${Ve(o)}`}t.toCss=n;function i(r,s,o,a=255){return(r<<24|s<<16|o<<8|a)>>>0}t.toRgba=i;function e(r,s,o,a){return{css:t.toCss(r,s,o,a),rgba:t.toRgba(r,s,o,a)}}t.toColor=e})(O||={});var k;(a=>{function n(l,h){if(W=(h.rgba&255)/255,W===1)return{css:h.css,rgba:h.rgba};let d=h.rgba>>24&255,c=h.rgba>>16&255,u=h.rgba>>8&255,_=l.rgba>>24&255,p=l.rgba>>16&255,v=l.rgba>>8&255;J=_+Math.round((d-_)*W),Q=p+Math.round((c-p)*W),ee=v+Math.round((u-v)*W);let f=O.toCss(J,Q,ee),S=O.toRgba(J,Q,ee);return{css:f,rgba:S}}a.blend=n;function i(l){return(l.rgba&255)===255}a.isOpaque=i;function e(l,h,d){let c=Oi.ensureContrastRatio(l.rgba,h.rgba,d);if(c)return O.toColor(c>>24&255,c>>16&255,c>>8&255)}a.ensureContrastRatio=e;function t(l){let h=(l.rgba|255)>>>0;return[J,Q,ee]=Oi.toChannels(h),{css:O.toCss(J,Q,ee),rgba:h}}a.opaque=t;function r(l,h){return W=Math.round(h*255),[J,Q,ee]=Oi.toChannels(l.rgba),{css:O.toCss(J,Q,ee,W),rgba:O.toRgba(J,Q,ee,W)}}a.opacity=r;function s(l,h){return W=l.rgba&255,r(l,W*h/255)}a.multiplyOpacity=s;function o(l){return[l.rgba>>24&255,l.rgba>>16&255,l.rgba>>8&255]}a.toColorRGB=o})(k||={});var B;(t=>{let n,i;try{let r=document.createElement("canvas");r.width=1,r.height=1;let s=r.getContext("2d",{willReadFrequently:!0});s&&(n=s,n.globalCompositeOperation="copy",i=n.createLinearGradient(0,0,1,1))}catch{}function e(r){if(r.match(/#[\da-f]{3,8}/i))switch(r.length){case 4:return J=parseInt(r.slice(1,2).repeat(2),16),Q=parseInt(r.slice(2,3).repeat(2),16),ee=parseInt(r.slice(3,4).repeat(2),16),O.toColor(J,Q,ee);case 5:return J=parseInt(r.slice(1,2).repeat(2),16),Q=parseInt(r.slice(2,3).repeat(2),16),ee=parseInt(r.slice(3,4).repeat(2),16),W=parseInt(r.slice(4,5).repeat(2),16),O.toColor(J,Q,ee,W);case 7:return{css:r,rgba:(parseInt(r.slice(1),16)<<8|255)>>>0};case 9:return{css:r,rgba:parseInt(r.slice(1),16)>>>0}}let s=r.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(s)return J=parseInt(s[1],10),Q=parseInt(s[2],10),ee=parseInt(s[3],10),W=Math.round((s[5]===void 0?1:parseFloat(s[5]))*255),O.toColor(J,Q,ee,W);if(r==="transparent")return{css:"transparent",rgba:0};if(!n||!i)throw new Error("css.toColor: Unsupported css format");if(n.fillStyle=i,n.fillStyle=r,typeof n.fillStyle!="string")throw new Error("css.toColor: Unsupported css format");if(n.fillRect(0,0,1,1),[J,Q,ee,W]=n.getImageData(0,0,1,1).data,W!==255)throw new Error("css.toColor: Unsupported css format");return{rgba:O.toRgba(J,Q,ee,W),css:r}}t.toColor=e})(B||={});var Z;(e=>{function n(t){return i(t>>16&255,t>>8&255,t&255)}e.relativeLuminance=n;function i(t,r,s){let o=t/255,a=r/255,l=s/255,h=o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4),d=a<=.03928?a/12.92:Math.pow((a+.055)/1.055,2.4),c=l<=.03928?l/12.92:Math.pow((l+.055)/1.055,2.4);return h*.2126+d*.7152+c*.0722}e.relativeLuminance2=i})(Z||={});var Oi;(s=>{function n(o,a){if(W=(a&255)/255,W===1)return a;let l=a>>24&255,h=a>>16&255,d=a>>8&255,c=o>>24&255,u=o>>16&255,_=o>>8&255;return J=c+Math.round((l-c)*W),Q=u+Math.round((h-u)*W),ee=_+Math.round((d-_)*W),O.toRgba(J,Q,ee)}s.blend=n;function i(o,a,l){let h=Z.relativeLuminance(o>>8),d=Z.relativeLuminance(a>>8);if(Te(h,d)>8));if(v>8));return v>S?p:f}return p}let u=t(o,a,l),_=Te(h,Z.relativeLuminance(u>>8));if(_>8));return _>v?u:p}return u}}s.ensureContrastRatio=i;function e(o,a,l){let h=o>>24&255,d=o>>16&255,c=o>>8&255,u=a>>24&255,_=a>>16&255,p=a>>8&255,v=Te(Z.relativeLuminance2(u,_,p),Z.relativeLuminance2(h,d,c));for(;v0||_>0||p>0);)u-=Math.max(0,Math.ceil(u*.1)),_-=Math.max(0,Math.ceil(_*.1)),p-=Math.max(0,Math.ceil(p*.1)),v=Te(Z.relativeLuminance2(u,_,p),Z.relativeLuminance2(h,d,c));return(u<<24|_<<16|p<<8|255)>>>0}s.reduceLuminance=e;function t(o,a,l){let h=o>>24&255,d=o>>16&255,c=o>>8&255,u=a>>24&255,_=a>>16&255,p=a>>8&255,v=Te(Z.relativeLuminance2(u,_,p),Z.relativeLuminance2(h,d,c));for(;v>>0}s.increaseLuminance=t;function r(o){return[o>>24&255,o>>16&255,o>>8&255,o&255]}s.toChannels=r})(Oi||={});function Ve(n){let i=n.toString(16);return i.length<2?"0"+i:i}function Te(n,i){return n1){let u=this._getJoinedRanges(r,l,a,e,o);for(let _=0;_1){let c=this._getJoinedRanges(r,l,a,e,o);for(let u=0;u=ks,Lr=ae,x=this._workCell;if(v.length>0&&ae===v[0][0]&&je){let A=v.shift(),Pr=this._isCellInSelection(A[0],e);for(T=A[0]+1;T=A[1],je?(fi=!0,x=new Ni(this._workCell,i.translateToString(!0,A[0],A[1]),A[1]-A[0]),Lr=A[1]-1,Rr=x.getWidth()):ks=A[1]}let Nt=this._isCellInSelection(ae,e),Ar=t&&ae===o,kr=An&&ae>=c&&ae<=u;_&&x.isBlink()&&(_.hasBlinkingCells=!0),!l&&x.isBlink()&&N.push("xterm-blink-hidden");let Mr=!1;this._decorationService.forEachDecorationAtCell(ae,e,void 0,A=>{Mr=!0});let _i=x.getChars()||" ";if(_i===" "&&(x.isUnderline()||x.isOverline())&&(_i="\xA0"),Ot=Rr*h-d.get(_i,x.isBold(),x.isItalic()),!I)I=this._document.createElement("span");else if(w&&(Nt&&ui||!Nt&&!ui&&x.bg===te)&&(Nt&&ui&&f.selectionForeground||x.fg===Ds)&&x.extended.ext===Rs&&kr===Ls&&Ot===As&&!Ar&&!fi&&!Mr&&je){x.isInvisible()?L+=" ":L+=_i,w++;continue}else w&&(I.textContent=L),I=this._document.createElement("span"),w=0,L="";if(te=x.bg,Ds=x.fg,Rs=x.extended.ext,Ls=kr,As=Ot,ui=Nt,fi&&o>=ae&&o<=Lr&&(o=ae),!this._coreService.isCursorHidden&&Ar&&this._coreService.isCursorInitialized){if(N.push("xterm-cursor"),this._coreBrowserService.isFocused)a&&N.push("xterm-cursor-blink"),N.push(r==="bar"?"xterm-cursor-bar":r==="underline"?"xterm-cursor-underline":"xterm-cursor-block");else if(s)switch(s){case"outline":N.push("xterm-cursor-outline");break;case"block":N.push("xterm-cursor-block");break;case"bar":N.push("xterm-cursor-bar");break;case"underline":N.push("xterm-cursor-underline");break;default:break}}if(x.isBold()&&N.push("xterm-bold"),x.isItalic()&&N.push("xterm-italic"),x.isDim()&&N.push("xterm-dim"),x.isInvisible()?L=" ":L=x.getChars()||" ",x.isUnderline()&&(N.push(`xterm-underline-${x.extended.underlineStyle}`),L===" "&&(L="\xA0"),!x.isUnderlineColorDefault()))if(x.isUnderlineColorRGB())I.style.textDecorationColor=`rgb(${ue.toColorRGB(x.getUnderlineColor()).join(",")})`;else{let A=x.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&x.isBold()&&A<8&&(A+=8),I.style.textDecorationColor=f.ansi[A].css}x.isOverline()&&(N.push("xterm-overline"),L===" "&&(L="\xA0")),x.isStrikethrough()&&N.push("xterm-strikethrough"),kr&&(I.style.textDecoration="underline");let de=x.getFgColor(),Ft=x.getFgColorMode(),Se=x.getBgColor(),Ht=x.getBgColorMode(),Br=!!x.isInverse();if(Br){let A=de;de=Se,Se=A;let Pr=Ft;Ft=Ht,Ht=Pr}let Le,pi,Wt=!1;this._decorationService.forEachDecorationAtCell(ae,e,void 0,A=>{A.options.layer!=="top"&&Wt||(A.backgroundColorRGB&&(Ht=50331648,Se=A.backgroundColorRGB.rgba>>8&16777215,Le=A.backgroundColorRGB),A.foregroundColorRGB&&(Ft=50331648,de=A.foregroundColorRGB.rgba>>8&16777215,pi=A.foregroundColorRGB),Wt=A.options.layer==="top")}),!Wt&&Nt&&(Le=this._coreBrowserService.isFocused?f.selectionBackgroundOpaque:f.selectionInactiveBackgroundOpaque,Se=Le.rgba>>8&16777215,Ht=50331648,Wt=!0,f.selectionForeground&&(Ft=50331648,de=f.selectionForeground.rgba>>8&16777215,pi=f.selectionForeground)),Wt&&N.push("xterm-decoration-top");let Ae;switch(Ht){case 16777216:case 33554432:Ae=f.ansi[Se],N.push(`xterm-bg-${Se}`);break;case 50331648:Ae=O.toColor(Se>>16,Se>>8&255,Se&255),this._addStyle(I,`background-color:#${(Se>>>0).toString(16).padStart(6,"0")}`);break;case 0:default:Br?(Ae=f.foreground,N.push(`xterm-bg-${257}`)):Ae=f.background}switch(Le||x.isDim()&&(Le=k.multiplyOpacity(Ae,.5)),Ft){case 16777216:case 33554432:x.isBold()&&de<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(de+=8),this._applyMinimumContrast(I,Ae,f.ansi[de],x,Le,void 0)||N.push(`xterm-fg-${de}`);break;case 50331648:let A=O.toColor(de>>16&255,de>>8&255,de&255);this._applyMinimumContrast(I,Ae,A,x,Le,pi)||this._addStyle(I,`color:#${de.toString(16).padStart(6,"0")}`);break;case 0:default:this._applyMinimumContrast(I,Ae,f.foreground,x,Le,pi)||Br&&N.push(`xterm-fg-${257}`)}N.length&&(I.className=N.join(" "),N.length=0),!Ar&&!fi&&!Mr&&je?w++:I.textContent=L,Ot!==this.defaultSpacing&&(I.style.letterSpacing=`${Ot}px`),p.push(I),ae=Lr}return I&&w&&(I.textContent=L),p}_applyMinimumContrast(i,e,t,r,s,o){if(this._optionsService.rawOptions.minimumContrastRatio===1||js(r.getCode()))return!1;let a=this._getContrastCache(r),l;if(!s&&!o&&(l=a.getColor(e.rgba,t.rgba)),l===void 0){let h=this._optionsService.rawOptions.minimumContrastRatio/(r.isDim()?2:1);l=k.ensureContrastRatio(s??e,o??t,h),a.setColor((s??e).rgba,(o??t).rgba,l??null)}return l?(this._addStyle(i,`color:${l.css}`),!0):!1}_getContrastCache(i){return i.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(i,e){i.setAttribute("style",`${i.getAttribute("style")||""}${e};`)}_isCellInSelection(i,e){let t=this._selectionStart,r=this._selectionEnd;return!t||!r?!1:this._columnSelectMode?t[0]<=r[0]?i>=t[0]&&e>=t[1]&&i=t[1]&&i>=r[0]&&e<=r[1]:e>t[1]&&e=t[0]&&i=t[0]}};_t=y([m(1,gi),m(2,R),m(3,G),m(4,Y),m(5,ge),m(6,_e)],_t);var Hi=class{constructor(i=()=>new rs){this._flat=new Float32Array(256);this._font="";this._fontSize=0;this._weight="normal";this._weightBold="bold";this._canvasElements=[];this._canvasElements=[i(),i(),i(),i()],this.clear()}dispose(){this._canvasElements.length=0,this._holey=void 0}clear(){this._flat.fill(-9999),this._holey=new Map}setFont(i,e,t,r){i===this._font&&e===this._fontSize&&t===this._weight&&r===this._weightBold||(this._font=i,this._fontSize=e,this._weight=t,this._weightBold=r,this._canvasElements[0].setFont(i,e,t,!1),this._canvasElements[1].setFont(i,e,r,!1),this._canvasElements[2].setFont(i,e,t,!0),this._canvasElements[3].setFont(i,e,r,!0),this.clear())}get(i,e,t){let r;if(!e&&!t&&i.length===1&&(r=i.charCodeAt(0))<256){if(this._flat[r]!==-9999)return this._flat[r];let a=this._measure(i,0);return a>0&&(this._flat[r]=a),a}let s=i;e&&(s+="B"),t&&(s+="I");let o=this._holey.get(s);if(o===void 0){let a=0;e&&(a|=1),t&&(a|=2),o=this._measure(i,a),o>0&&this._holey.set(s,o)}return o}_measure(i,e){return this._canvasElements[e].measure(i)}},rs=class{constructor(){typeof OffscreenCanvas<"u"?(this._canvas=new OffscreenCanvas(1,1),this._ctx=is(this._canvas.getContext("2d"))):(this._canvas=document.createElement("canvas"),this._canvas.width=1,this._canvas.height=1,this._ctx=is(this._canvas.getContext("2d")))}setFont(i,e,t,r){let s=r?"italic":"";this._ctx.font=`${s} ${t} ${e}px ${i}`.trim()}measure(i){return this._ctx.measureText(i).width}};var ss=class{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(i,e,t,r=!1){if(this.selectionStart=e,this.selectionEnd=t,!e||!t||e[0]===t[0]&&e[1]===t[1]){this.clear();return}let s=i.buffers.active.ydisp,o=e[1]-s,a=t[1]-s,l=Math.max(o,0),h=Math.min(a,i.rows-1);if(l>=i.rows||h<0){this.clear();return}this.hasSelection=!0,this.columnSelectMode=r,this.viewportStartRow=o,this.viewportEndRow=a,this.viewportCappedStartRow=l,this.viewportCappedEndRow=h,this.startCol=e[0],this.endCol=t[0]}isCellSelected(i,e,t){return this.hasSelection?(t-=i.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?e>=this.startCol&&t>=this.viewportCappedStartRow&&e=this.viewportCappedStartRow&&e>=this.endCol&&t<=this.viewportCappedEndRow:t>this.viewportStartRow&&t=this.startCol&&e=this.startCol):!1}};function Js(){return new ss}var Wi=class extends g{constructor(e,t,r){super();this._renderCallback=e;this._coreBrowserService=t;this._optionsService=r;this._intervalDuration=0;this._blinkOn=!0;this._needsBlinkInViewport=!1;this._isViewportVisible=!0;this._register(this._optionsService.onSpecificOptionChange("blinkIntervalDuration",s=>{this.setIntervalDuration(s)})),this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration),this._register(E(()=>this._clearInterval()))}get isBlinkOn(){return this._blinkOn}get isEnabled(){return this._intervalDuration>0}setNeedsBlinkInViewport(e){this._needsBlinkInViewport!==e&&(this._needsBlinkInViewport=e,this._updateIntervalState())}setViewportVisible(e){this._isViewportVisible!==e&&(this._isViewportVisible=e,this._updateIntervalState())}setIntervalDuration(e){e!==this._intervalDuration&&(this._intervalDuration=e,this._clearInterval(),this._updateIntervalState())}_updateIntervalState(){if(this._intervalDuration>0&&this._needsBlinkInViewport&&this._isViewportVisible){if(this._interval!==void 0)return;let t=this._blinkOn;this._blinkOn=!0,this._interval=this._coreBrowserService.window.setInterval(()=>{this._blinkOn=!this._blinkOn,this._renderCallback()},this._intervalDuration),t||this._renderCallback();return}this._clearInterval(),this._blinkOn||(this._blinkOn=!0,this._renderCallback())}_clearInterval(){this._interval!==void 0&&(this._coreBrowserService.window.clearInterval(this._interval),this._interval=void 0)}};var Zn=1,mt=class extends g{constructor(e,t,r,s,o,a,l,h,d,c,u,_,p,v){super();this._terminal=e;this._document=t;this._element=r;this._screenElement=s;this._viewportElement=o;this._helperContainer=a;this._linkifier2=l;this._charSizeService=d;this._optionsService=c;this._bufferService=u;this._coreService=_;this._coreBrowserService=p;this._themeService=v;this._terminalClass=Zn++;this._rowElements=[];this._selectionRenderModel=Js();this._lastSelectionColumnMode=!1;this._rowHasBlinkingCells=[];this._rowHasBlinkingCellsCount=0;this._onRequestRedraw=this._register(new b);this.onRequestRedraw=this._onRequestRedraw.event;this._rowContainer=this._document.createElement("div"),this._rowContainer.classList.add("xterm-rows"),this._rowContainer.style.lineHeight="normal",this._rowContainer.setAttribute("aria-hidden","true"),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement("div"),this._selectionContainer.classList.add("xterm-selection"),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=Zs(),this._updateDimensions(),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._themeService.onChangeColors(f=>this._injectCss(f))),this._injectCss(this._themeService.colors),this._rowFactory=h.createInstance(_t,document),this._element.classList.add("xterm-dom-renderer-owner-"+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline(f=>this._handleLinkHover(f))),this._register(this._linkifier2.onHideLinkUnderline(f=>this._handleLinkLeave(f))),this._cursorBlinkStateManager=new ns(this._rowContainer,this._coreBrowserService),this._register(C(this._document,"mousedown",()=>this._cursorBlinkStateManager.restartBlinkAnimation())),this._register(E(()=>this._cursorBlinkStateManager.dispose())),this._textBlinkStateManager=this._register(new Wi(()=>this._onRequestRedraw.fire({start:0,end:this._bufferService.rows-1}),this._coreBrowserService,this._optionsService)),this._register(E(()=>{this._element.classList.remove("xterm-dom-renderer-owner-"+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new Hi,this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){let e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(let r of this._rowElements)r.style.width=`${this.dimensions.css.canvas.width}px`,r.style.height=`${this.dimensions.css.cell.height}px`,r.style.lineHeight=`${this.dimensions.css.cell.height}px`,r.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));let t=`${this._terminalSelector} .xterm-rows span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .xterm-rows { pointer-events: none; color: ${e.foreground.css};}`;t+=`${this._terminalSelector} .xterm-rows, ${this._terminalSelector} .xterm-rows span { font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`,t+=`${this._terminalSelector} .xterm-rows .xterm-dim { color: ${k.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}${this._terminalSelector} span.xterm-blink-hidden { visibility: hidden;}`;let r=`blink_underline_${this._terminalClass}`,s=`blink_bar_${this._terminalClass}`,o=`blink_block_${this._terminalClass}`;t+=`@keyframes ${r} { 50% { border-bottom-style: hidden; }}`,t+=`@keyframes ${s} { 50% { box-shadow: none; }}`,t+=`@keyframes ${o} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${s} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${o} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-cursor-blink-idle .xterm-cursor.xterm-cursor-blink { animation: none !important;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .xterm-selection { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .xterm-selection div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .xterm-selection div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(let[a,l]of e.ansi.entries())t+=`${this._terminalSelector} .xterm-fg-${a} { color: ${l.css}; }${this._terminalSelector} .xterm-fg-${a}.xterm-dim { color: ${k.multiplyOpacity(l,.5).css}; }${this._terminalSelector} .xterm-bg-${a} { background-color: ${l.css}; }`;t+=`${this._terminalSelector} .xterm-fg-${257} { color: ${k.opaque(e.background).css}; }${this._terminalSelector} .xterm-fg-${257}.xterm-dim { color: ${k.multiplyOpacity(k.opaque(e.background),.5).css}; }${this._terminalSelector} .xterm-bg-${257} { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){let e=this.dimensions.css.cell.width-this._widthCache.get("W",!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let r=this._rowElements.length;r<=t;r++){let s=this._document.createElement("div");this._rowContainer.appendChild(s),this._rowElements.push(s),this._rowHasBlinkingCells.push(!1)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop()),this._rowHasBlinkingCells.pop()&&this._rowHasBlinkingCellsCount--}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove("xterm-focus"),this._cursorBlinkStateManager.pause(),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add("xterm-focus"),this._cursorBlinkStateManager.resume(),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleViewportVisibilityChange(e){this._textBlinkStateManager.setViewportVisible(e)}handleSelectionChanged(e,t,r){let s=this._bufferService.rows;this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,r);let o=0,a=-1;this._lastSelectionStart&&this._lastSelectionEnd&&(this._selectionRenderModel.update(this._terminal,this._lastSelectionStart,this._lastSelectionEnd,this._lastSelectionColumnMode),this._selectionRenderModel.hasSelection&&(o=this._selectionRenderModel.viewportCappedStartRow,a=this._selectionRenderModel.viewportCappedEndRow));let l=0,h=-1;if(!e||!t)return;if(this._selectionRenderModel.update(this._terminal,e,t,r),this._selectionRenderModel.hasSelection){let u=this._selectionRenderModel.viewportStartRow,_=this._selectionRenderModel.viewportEndRow,p=this._selectionRenderModel.viewportCappedStartRow,v=this._selectionRenderModel.viewportCappedEndRow;l=p,h=v;let f=this._document.createDocumentFragment();if(r){let S=e[0]>t[0];f.appendChild(this._createSelectionElement(p,S?t[0]:e[0],S?e[0]:t[0],v-p+1))}else{let S=u===p?e[0]:0,I=p===_?t[0]:this._bufferService.cols;f.appendChild(this._createSelectionElement(p,S,I));let w=v-p-1;if(f.appendChild(this._createSelectionElement(p+1,0,this._bufferService.cols,w)),p!==v){let L=_===v?t[0]:this._bufferService.cols;f.appendChild(this._createSelectionElement(v,0,L))}}this._selectionContainer.appendChild(f)}let d=Math.min(o,l),c=Math.max(a,h);if(c>=0){d=Math.max(d,0),c=Math.min(c,s-1);let _=this._bufferService.buffer.y;this._selectionRenderModel.hasSelection&&_>=0&&_this.dimensions.css.canvas.width&&(l=this.dimensions.css.canvas.width-a),o.style.height=`${s*this.dimensions.css.cell.height}px`,o.style.top=`${e*this.dimensions.css.cell.height}px`,o.style.left=`${a}px`,o.style.width=`${l}px`,o}handleCursorMove(){this._cursorBlinkStateManager.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(let e of this._rowElements)e.replaceChildren();this._rowHasBlinkingCellsCount>0&&(this._rowHasBlinkingCells.fill(!1),this._rowHasBlinkingCellsCount=0,this._textBlinkStateManager.setNeedsBlinkInViewport(!1))}renderRows(e,t){let r=this._bufferService.buffer,s=r.ybase+r.y,o=Math.min(r.x,this._bufferService.cols-1),a=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,l=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,h=this._optionsService.rawOptions.cursorInactiveStyle,d={hasBlinkingCells:!1};for(let c=e;c<=t;c++){let u=c+r.ydisp,_=this._rowElements[c];if(!_)continue;let p=r.lines.get(u);if(!p){_.replaceChildren(),this._setRowBlinkState(c,!1);continue}_.replaceChildren(...this._rowFactory.createRow(p,u,u===s,l,h,o,a,this._textBlinkStateManager.isBlinkOn,this.dimensions.css.cell.width,this._widthCache,-1,-1,d)),this._setRowBlinkState(c,d.hasBlinkingCells)}this._updateTextBlinkState()}get _terminalSelector(){return`.xterm-dom-renderer-owner-${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,r,s,o,a){r<0&&(e=0),s<0&&(t=0);let l=this._bufferService.rows-1;r=Math.max(Math.min(r,l),0),s=Math.max(Math.min(s,l),0),o=Math.min(o,this._bufferService.cols);let h=this._bufferService.buffer,d=h.ybase+h.y,c=Math.min(h.x,o-1),u=this._optionsService.rawOptions.cursorBlink,_=this._optionsService.rawOptions.cursorStyle,p=this._optionsService.rawOptions.cursorInactiveStyle,v={hasBlinkingCells:!1};for(let f=r;f<=s;++f){let S=f+h.ydisp,I=this._rowElements[f];if(!I)continue;let w=h.lines.get(S);if(!w){I.replaceChildren(),this._setRowBlinkState(f,!1);continue}I.replaceChildren(...this._rowFactory.createRow(w,S,S===d,_,p,c,u,this._textBlinkStateManager.isBlinkOn,this.dimensions.css.cell.width,this._widthCache,a?f===r?e:0:-1,a?(f===s?t:o)-1:-1,v)),this._setRowBlinkState(f,v.hasBlinkingCells)}this._updateTextBlinkState()}_setRowBlinkState(e,t){this._rowHasBlinkingCells[e]!==t&&(this._rowHasBlinkingCells[e]=t,this._rowHasBlinkingCellsCount+=t?1:-1)}_updateTextBlinkState(){this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount>0)}};mt=y([m(7,Qe),m(8,Be),m(9,R),m(10,D),m(11,Y),m(12,G),m(13,_e)],mt);var ns=class{constructor(i,e){this._rowContainer=i;this._coreBrowserService=e;this._isIdlePaused=!1;this._coreBrowserService.isFocused&&this._resetIdleTimer()}dispose(){this._clearIdleTimer()}restartBlinkAnimation(){this._isIdlePaused&&this._rowContainer.classList.remove("xterm-cursor-blink-idle"),this._resetIdleTimer()}pause(){this._isIdlePaused=!1,this._clearIdleTimer()}resume(){this._isIdlePaused=!1,this._rowContainer.classList.remove("xterm-cursor-blink-idle"),this._resetIdleTimer()}_resetIdleTimer(){this._isIdlePaused=!1,this._clearIdleTimer(),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_clearIdleTimer(){this._idleTimeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}_stopBlinkingDueToIdle(){this._rowContainer.classList.add("xterm-cursor-blink-idle"),this._isIdlePaused=!0,this._idleTimeout=void 0}};var bt=class extends g{constructor(e,t,r){super();this._optionsService=r;this.width=0;this.height=0;this._onCharSizeChange=this._register(new b);this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new as(this._optionsService))}catch{this._measureStrategy=this._register(new os(e,t,this._optionsService))}this._register(this._optionsService.onMultipleOptionChange(["fontFamily","fontSize"],()=>this.measure()))}get hasValidSize(){return this.width>0&&this.height>0}measure(){let e=this._measureStrategy.measure();(e.width!==this.width||e.height!==this.height)&&(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};bt=y([m(2,R)],bt);var Ui=class extends g{constructor(){super(...arguments);this._result={width:0,height:0}}_validateAndSet(e,t){e!==void 0&&e>0&&t!==void 0&&t>0&&(this._result.width=e,this._result.height=t)}},os=class extends Ui{constructor(e,t,r){super();this._document=e;this._parentElement=t;this._optionsService=r;this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W".repeat(32),this._measureElement.setAttribute("aria-hidden","true"),this._measureElement.style.whiteSpace="pre",this._measureElement.style.fontKerning="none",this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}},as=class extends Ui{constructor(e){super();this._optionsService=e;this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext("2d");let t=this._ctx.measureText("W");if(!("width"in t&&"fontBoundingBoxAscent"in t&&"fontBoundingBoxDescent"in t))throw new Error("Required font metrics not supported")}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;let e=this._ctx.measureText("W");return this._validateAndSet(e.width,e.fontBoundingBoxAscent+e.fontBoundingBoxDescent),this._result}};var Ki=class extends g{constructor(e,t,r){super();this._textarea=e;this._window=t;this.mainDocument=r;this._isFocused=!1;this._cachedIsFocused=void 0;this._onDprChange=this._register(new b);this.onDprChange=this._onDprChange.event;this._onWindowChange=this._register(new b);this.onWindowChange=this._onWindowChange.event;this._screenDprMonitor=this._register(new ls(this._window)),this._register(this.onWindowChange(s=>this._screenDprMonitor.setWindow(s))),this._register(j.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register(C(this._textarea,"focus",()=>this._isFocused=!0)),this._register(C(this._textarea,"blur",()=>this._isFocused=!1))}get window(){return this._window}set window(e){this._window!==e&&(this._window=e,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return this._cachedIsFocused===void 0&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask(()=>this._cachedIsFocused=void 0)),this._cachedIsFocused}},ls=class extends g{constructor(e){super();this._parentWindow=e;this._windowResizeListener=this._register(new P);this._onDprChange=this._register(new b);this.onDprChange=this._onDprChange.event;this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register(E(()=>this.clearListener()))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=C(this._parentWindow,"resize",()=>this._setDprAndFireIfDiffers())}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){this._outerListener&&(this._resolutionMediaMatchList?.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){!this._resolutionMediaMatchList||!this._outerListener||(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}};var zi=class extends g{constructor(){super();this.linkProviders=[];this._register(E(()=>this.linkProviders.length=0))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{let t=this.linkProviders.indexOf(e);t!==-1&&this.linkProviders.splice(t,1)}}}};function qt(n,i,e){let t=e.getBoundingClientRect(),r=n.getComputedStyle(e),s=parseInt(r.getPropertyValue("padding-left"),10),o=parseInt(r.getPropertyValue("padding-top"),10);return[i.clientX-t.left-s,i.clientY-t.top-o]}function Qs(n,i,e,t,r,s,o,a,l){if(!s)return;let h=qt(n,i,e);return h[0]=Math.ceil((h[0]+(l?o/2:0))/o),h[1]=Math.ceil(h[1]/a),h[0]=Math.min(Math.max(h[0],1),t+(l?1:0)),h[1]=Math.min(Math.max(h[1],1),r),h}var vt=class{constructor(i,e){this._charSizeService=i;this._renderService=e}getCoords(i,e,t,r,s){return Qs(se(e),i,e,t,r,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,s)}getMouseReportCoords(i,e){let t=qt(se(e),i,e);if(this._charSizeService.hasValidSize)return t[0]=Math.min(Math.max(t[0],0),this._renderService.dimensions.css.canvas.width-1),t[1]=Math.min(Math.max(t[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(t[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(t[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(t[0]),y:Math.floor(t[1])}}};vt=y([m(0,Be),m(1,V)],vt);var en=typeof window=="object"?window:globalThis;function ce(n,i=0){return n[n.length-(1+i)]}function Jn(n,i,e){let t=null,r=null;if(typeof e.value=="function"?(t="value",r=e.value,r.length!==0&&console.warn("Memoize should only be used in functions with zero parameters")):typeof e.get=="function"&&(t="get",r=e.get),!r||!t)throw new Error("not supported");let s=`$memoize$${i}`,o=e;o[t]=function(...a){return this.hasOwnProperty(s)||Object.defineProperty(this,s,{configurable:!1,enumerable:!1,writable:!1,value:r.apply(this,a)}),this[s]}}var St=class St{constructor(i){this.element=i,this.next=St.Undefined,this.prev=St.Undefined}};St.Undefined=new St(void 0);var re=St,Gi=class{constructor(){this._first=re.Undefined;this._last=re.Undefined}push(i){return this._insert(i,!0)}_insert(i,e){let t=new re(i);if(this._first===re.Undefined)this._first=t,this._last=t;else if(e){let s=this._last;this._last=t,t.prev=s,s.next=t}else{let s=this._first;this._first=t,t.next=s,s.prev=t}let r=!1;return()=>{r||(r=!0,this._remove(t))}}_remove(i){if(i.prev!==re.Undefined&&i.next!==re.Undefined){let e=i.prev;e.next=i.next,i.next.prev=e}else i.prev===re.Undefined&&i.next===re.Undefined?(this._first=re.Undefined,this._last=re.Undefined):i.next===re.Undefined?(this._last=this._last.prev,this._last.next=re.Undefined):i.prev===re.Undefined&&(this._first=this._first.next,this._first.prev=re.Undefined)}*[Symbol.iterator](){let i=this._first;for(;i!==re.Undefined;)yield i.element,i=i.next}},he;(s=>(s.TAP="-xterm-gesturetap",s.CHANGE="-xterm-gesturechange",s.START="-xterm-gesturestart",s.END="-xterm-gesturesend",s.CONTEXT_MENU="-xterm-gesturecontextmenu"))(he||={});var K=class K extends g{constructor(){super();this._dispatched=!1;this._targets=new Gi;this._ignoreTargets=new Gi;this._activeTouches={},this._handle=null,this._lastSetTapCountTime=0;let e=en;this._register(C(e.document,"touchstart",t=>this._handleTouchStart(t),{passive:!1})),this._register(C(e.document,"touchend",t=>this._handleTouchEnd(e,t))),this._register(C(e.document,"touchmove",t=>this._handleTouchMove(t),{passive:!1}))}static addTarget(e){if(!K.isTouchDevice())return g.None;K._instance||(K._instance=new K);let t=K._instance._targets.push(e);return E(t)}static ignoreTarget(e){if(!K.isTouchDevice())return g.None;K._instance||(K._instance=new K);let t=K._instance._ignoreTargets.push(e);return E(t)}static isTouchDevice(){return"ontouchstart"in en||navigator.maxTouchPoints>0}dispose(){this._handle&&(this._handle.dispose(),this._handle=null),super.dispose()}_handleTouchStart(e){let t=Date.now();this._handle&&(this._handle.dispose(),this._handle=null);for(let r=0,s=e.targetTouches.length;r=K._holdDelay&&Math.abs(h.initialPageX-ce(h.rollingPageX))<30&&Math.abs(h.initialPageY-ce(h.rollingPageY))<30){let c=this._newGestureEvent(he.CONTEXT_MENU,h.initialTarget);c.pageX=ce(h.rollingPageX),c.pageY=ce(h.rollingPageY),this._dispatchEvent(c)}else if(s===1){let c=ce(h.rollingPageX),u=ce(h.rollingPageY),_=ce(h.rollingTimestamps)-h.rollingTimestamps[0],p=c-h.rollingPageX[0],v=u-h.rollingPageY[0],f=[...this._targets].filter(S=>h.initialTarget instanceof Node&&S.contains(h.initialTarget));this._inertia(e,f,r,Math.abs(p)/_,p>0?1:-1,c,Math.abs(v)/_,v>0?1:-1,u)}this._dispatchEvent(this._newGestureEvent(he.END,h.initialTarget)),delete this._activeTouches[l.identifier]}this._dispatched&&(t.preventDefault(),t.stopPropagation(),this._dispatched=!1)}_newGestureEvent(e,t){let r=document.createEvent("CustomEvent");return r.initEvent(e,!1,!0),r.initialTarget=t,r.tapCount=0,r}_dispatchEvent(e){if(e.type===he.TAP){let t=new Date().getTime(),r;t-this._lastSetTapCountTime>K._clearTapCountTime?r=1:r=2,this._lastSetTapCountTime=t,e.tapCount=r}else(e.type===he.CHANGE||e.type===he.CONTEXT_MENU)&&(this._lastSetTapCountTime=0);if(e.initialTarget instanceof Node){for(let r of this._ignoreTargets)if(r.contains(e.initialTarget))return;let t=[];for(let r of this._targets)if(r.contains(e.initialTarget)){let s=0,o=e.initialTarget;for(;o&&o!==r;)s++,o=o.parentElement;t.push([s,r])}t.sort((r,s)=>r[0]-s[0]);for(let[,r]of t)r.dispatchEvent(e),this._dispatched=!0}}_inertia(e,t,r,s,o,a,l,h,d){this._handle=tt(e,()=>{let c=Date.now(),u=c-r,_=0,p=0,v=!0;s+=K._scrollFriction*u,l+=K._scrollFriction*u,s>0&&(v=!1,_=o*s*u),l>0&&(v=!1,p=h*l*u);let f=this._newGestureEvent(he.CHANGE);f.translationX=_,f.translationY=p,t.forEach(S=>S.dispatchEvent(f)),v||this._inertia(e,t,c,s,o,a+_,l,h,d+p)})}_handleTouchMove(e){let t=Date.now();for(let r=0,s=e.changedTouches.length;r3&&(a.rollingPageX.shift(),a.rollingPageY.shift(),a.rollingTimestamps.shift()),a.rollingPageX.push(o.pageX),a.rollingPageY.push(o.pageY),a.rollingTimestamps.push(t)}this._dispatched&&(e.preventDefault(),e.stopPropagation(),this._dispatched=!1)}};K._scrollFriction=-.005,K._holdDelay=700,K._clearTapCountTime=400,y([Jn],K,"isTouchDevice",1);var Vi=K;var gt=class{constructor(i,e,t,r,s,o,a,l,h){this._renderService=i;this._mouseCoordsService=e;this._mouseStateService=t;this._coreService=r;this._bufferService=s;this._optionsService=o;this._selectionService=a;this._logService=l;this._coreBrowserService=h;this._lastEvent=null;this._wheelPartialScroll=0;this._touchScrollAccumulator=0}bindMouse(i,e,t){let{element:r,document:s}=i,o={mouseup:null,wheel:null,mousedrag:null,mousemove:null},a={target:i,focus:t,requestedEvents:o},l={mouseup:h=>this._handleMouseUp(a,h),wheel:h=>this._handleWheel(a,h),mousedrag:h=>this._handleMouseDrag(a,h),mousemove:h=>this._handleMouseMove(a,h)};this._altMouseCursor=new cs(r,s,()=>this._mouseStateService.areMouseEventsActive&&!!this._optionsService.rawOptions.mouseEventsRequireAlt),e(this._altMouseCursor),e(this._mouseStateService.onProtocolChange(h=>{this._handleProtocolChange(a,l,h)})),e(this._optionsService.onSpecificOptionChange("mouseEventsRequireAlt",()=>{this._syncMouseModeState(r),this._altMouseCursor?.sync()})),this._mouseStateService.activeProtocol=this._mouseStateService.activeProtocol,e(E(()=>{o.mouseup&&s.removeEventListener("mouseup",o.mouseup),o.mousedrag&&s.removeEventListener("mousemove",o.mousedrag)})),e(C(r,"mousedown",h=>this._handleMouseDown(a,h))),e(C(r,"wheel",h=>this._handlePassiveWheel(a,h),{passive:!1})),e(Vi.addTarget(i.screenElement)),e(C(i.screenElement,he.START,()=>this._handleTouchStart())),e(C(i.screenElement,he.CHANGE,h=>this._handleTouchChange(a,h)))}_sendEvent(i,e){let t=this._mouseCoordsService.getMouseReportCoords(e,i.target.screenElement);if(!t)return!1;let r,s;switch(e.overrideType||e.type){case"mousemove":s=32,e.buttons===void 0?(r=3,e.button!==void 0&&(r=e.button<3?e.button:3)):r=e.buttons&1?0:e.buttons&4?1:e.buttons&2?2:3;break;case"mouseup":s=0,r=e.button<3?e.button:3;break;case"mousedown":s=1,r=e.button<3?e.button:3;break;case"wheel":if(!this._mouseStateService.allowCustomWheelEvent(e))return!1;let a=e.deltaY;if(a===0||this._consumeWheelEvent(e,this._renderService?.dimensions?.device?.cell?.height,this._coreBrowserService?.dpr)===0)return!1;s=a<0?0:1,r=4;break;default:return!1}if(s===void 0||r===void 0||r>4||r!==4&&this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive&&!e.altKey)return!1;let o=r!==4&&this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive;return this._triggerMouseEvent({col:t.col,row:t.row,x:t.x,y:t.y,button:r,action:s,ctrl:e.ctrlKey,alt:o?!1:e.altKey,shift:e.shiftKey})}_handleMouseUp(i,e){this._sendEvent(i,e),e.buttons||(i.requestedEvents.mouseup&&i.target.document.removeEventListener("mouseup",i.requestedEvents.mouseup),i.requestedEvents.mousedrag&&i.target.document.removeEventListener("mousemove",i.requestedEvents.mousedrag))}_handleWheel(i,e){return this._sendEvent(i,e),e.preventDefault(),e.stopPropagation(),!1}_handleMouseDrag(i,e){e.buttons&&this._sendEvent(i,e)}_handleMouseMove(i,e){e.buttons||this._sendEvent(i,e)}_handleMouseDown(i,e){e.preventDefault(),i.focus(),!(!this._mouseStateService.areMouseEventsActive||this._selectionService.shouldForceSelection(e))&&(this._sendEvent(i,e),i.requestedEvents.mouseup&&i.target.document.addEventListener("mouseup",i.requestedEvents.mouseup),i.requestedEvents.mousedrag&&i.target.document.addEventListener("mousemove",i.requestedEvents.mousedrag))}_handlePassiveWheel(i,e){if(!i.requestedEvents.wheel){if(!this._mouseStateService.allowCustomWheelEvent(e))return!1;if(!this._bufferService.buffer.hasScrollback){if(e.deltaY===0)return!1;if(this._consumeWheelEvent(e,this._renderService?.dimensions?.device?.cell?.height,this._coreBrowserService?.dpr)===0)return e.preventDefault(),e.stopPropagation(),!1;let s="\x1B"+(this._coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(e.deltaY<0?"A":"B");return this._coreService.triggerDataEvent(s,!0),e.preventDefault(),e.stopPropagation(),!1}}}_handleTouchStart(){this._touchScrollAccumulator=0}_handleTouchChange(i,e){if(e.preventDefault(),e.stopPropagation(),i.requestedEvents.wheel){this._handleTouchScrollAsWheel(i,e);return}if(!this._bufferService.buffer.hasScrollback){this._handleTouchScrollAsKeys(e);return}i.target.handleTouchScroll?.(e.translationY)}_handleTouchScrollAsKeys(i){let e=this._renderService?.dimensions.css.cell.height;if(!e)return;this._touchScrollAccumulator-=i.translationY;let t=Math.trunc(this._touchScrollAccumulator/e);if(t===0)return;this._touchScrollAccumulator-=t*e;let r="\x1B"+(this._coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(t<0?"A":"B");for(let s=0;s0?1:-1),this._wheelPartialScroll%=1):i.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(s*=this._bufferService.rows),s}_triggerMouseEvent(i){if(i.col<0||i.col>=this._bufferService.cols||i.row<0||i.row>=this._bufferService.rows||i.button===4&&i.action===32||i.button===3&&i.action!==32||i.button!==4&&(i.action===2||i.action===3)||(i.col++,i.row++,i.action===32&&this._lastEvent&&this._equalEvents(this._lastEvent,i,this._mouseStateService.isPixelEncoding))||!this._mouseStateService.restrictMouseEvent(i))return!1;let e=this._mouseStateService.encodeMouseEvent(i);return e&&(this._mouseStateService.isDefaultEncoding?this._coreService.triggerBinaryEvent(e):this._coreService.triggerDataEvent(e,!0)),this._lastEvent=i,!0}_explainEvents(i){return{down:!!(i&1),up:!!(i&2),drag:!!(i&4),move:!!(i&8),wheel:!!(i&16)}}_equalEvents(i,e,t){if(t){if(i.x!==e.x||i.y!==e.y)return!1}else if(i.col!==e.col||i.row!==e.row)return!1;return!(i.button!==e.button||i.action!==e.action||i.ctrl!==e.ctrl||i.alt!==e.alt||i.shift!==e.shift)}};gt=y([m(0,V),m(1,Pe),m(2,Me),m(3,Y),m(4,D),m(5,R),m(6,Si),m(7,fe),m(8,G)],gt);var cs=class{constructor(i,e,t){this._element=i;this._document=e;this._isActive=t;this._listeners=new P}dispose(){this._listeners.dispose()}sync(){if(this._listeners.clear(),!this._isActive())return;let i=new pe,e=r=>this.syncFromModifier(r);i.add(C(this._document,"keydown",e)),i.add(C(this._document,"keyup",e)),i.add(C(this._element,"mousemove",e));let t=this._element.ownerDocument?.defaultView;t&&i.add(C(t,"blur",()=>{this._isActive()&&this.resetClass()})),this._listeners.value=i}resetClass(){this._updateClass(!1)}syncFromModifier(i){this._isActive()&&this._updateClass(i.getModifierState("Alt"))}_updateClass(i){i?this._element.classList.add("enable-mouse-events"):this._element.classList.remove("enable-mouse-events")}};var $i=class{constructor(i,e){this._renderCallback=i;this._coreBrowserService=e;this._refreshCallbacks=[]}dispose(){this._animationFrame!==void 0&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}addRefreshCallback(i){return this._refreshCallbacks.push(i),this._animationFrame??=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()),this._animationFrame}refresh(i,e,t){this._rowCount=t,i=i??0,e=e??this._rowCount-1,this._rowStart=this._rowStart!==void 0?Math.min(this._rowStart,i):i,this._rowEnd=this._rowEnd!==void 0?Math.max(this._rowEnd,e):e,this._animationFrame===void 0&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._animationFrame=void 0,this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0){this._runRefreshCallbacks();return}let i=Math.max(this._rowStart,0),e=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(i,e),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(let i of this._refreshCallbacks)i(0);this._refreshCallbacks=[]}};var qi=class{constructor(i){this._tasks=[];this._i=0;this._logService=i}enqueue(i){this._tasks.push(i),this._start()}flush(){for(;this._is){r-e<-20&&this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(r-e))}ms`),this._start();return}r=s}this.clear()}},hs=class extends qi{_requestCallback(i){return setTimeout(()=>i(this._createDeadline(16)))}_cancelCallback(i){clearTimeout(i)}_createDeadline(i){let e=performance.now()+i;return{timeRemaining:()=>Math.max(0,e-performance.now())}}},ds=class extends qi{_requestCallback(i){return requestIdleCallback(i)}_cancelCallback(i){cancelIdleCallback(i)}},It="requestIdleCallback"in globalThis?ds:hs,Xi=class{constructor(i){this._queue=new It(i)}set(i){this._queue.clear(),this._queue.enqueue(i)}flush(){this._queue.flush()}dispose(){this._queue.clear()}};var Ct=class extends g{constructor(e,t,r,s,o,a,l,h,d,c){super();this._rowCount=e;this._optionsService=r;this._logService=s;this._charSizeService=o;this._coreService=a;this._coreBrowserService=d;this._renderer=this._register(new P);this._observerDisposable=this._register(new P);this._isPaused=!1;this._needsFullRefresh=!1;this._isNextRenderRedrawOnly=!0;this._needsSelectionRefresh=!1;this._canvasWidth=0;this._canvasHeight=0;this._selectionState={start:void 0,end:void 0,columnSelectMode:!1};this._onDimensionsChange=this._register(new b);this.onDimensionsChange=this._onDimensionsChange.event;this._onRenderedViewportChange=this._register(new b);this.onRenderedViewportChange=this._onRenderedViewportChange.event;this._onRender=this._register(new b);this.onRender=this._onRender.event;this._onRefreshRequest=this._register(new b);this.onRefreshRequest=this._onRefreshRequest.event;this._pausedResizeTask=this._register(new Xi(this._logService)),this._renderDebouncer=new $i((u,_)=>this._renderRows(u,_),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new us(this._coreBrowserService,this._coreService,()=>this._fullRefresh()),this._register(E(()=>this._syncOutputHandler.dispose())),this._register(this._coreBrowserService.onDprChange(()=>this.handleDevicePixelRatioChange())),this._register(h.onResize(()=>this._fullRefresh())),this._register(h.buffers.onBufferActivate(()=>this._renderer.value?.clear())),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this._register(l.onDecorationRegistered(()=>this._fullRefresh())),this._register(l.onDecorationRemoved(()=>this._fullRefresh())),this._register(this._optionsService.onMultipleOptionChange(["drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio","rescaleOverlappingGlyphs"],()=>{this.clear(),this.handleResize(h.cols,h.rows),this._fullRefresh()})),this._register(this._optionsService.onMultipleOptionChange(["cursorBlink","cursorStyle"],()=>this.refreshRows(h.buffer.y,h.buffer.y,void 0,!0))),this._register(c.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(this._coreBrowserService.window,t),this._register(this._coreBrowserService.onWindowChange(u=>this._registerIntersectionObserver(u,t)))}get dimensions(){return this._renderer.value.dimensions}_registerIntersectionObserver(e,t){if("IntersectionObserver"in e){let r=new e.IntersectionObserver(s=>this._handleIntersectionChange(s[s.length-1]),{threshold:0});this._observerDisposable.value=E(()=>{this._intersectionObserver?.disconnect(),this._intersectionObserver=void 0}),this._intersectionObserver=r,r.observe(t)}}_handleIntersectionChange(e){this._isPaused=e.isIntersecting===void 0?e.intersectionRatio===0:!e.isIntersecting,this._renderer.value?.handleViewportVisibilityChange?.(!this._isPaused),!this._isPaused&&!this._charSizeService.hasValidSize&&this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,r=!1,s=!1){if(this._isPaused){this._needsFullRefresh=!0;return}if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}let o=this._syncOutputHandler.flush();o&&(e=Math.min(e,o.start),t=Math.max(t,o.end)),s||(this._isNextRenderRedrawOnly=!1),r?this._renderRows(e,t):this._renderDebouncer.refresh(e,t,this._rowCount)}_renderRows(e,t){if(this._renderer.value){if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0}}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value&&(this._renderer.value.onRequestRedraw(t=>this.refreshRows(t.start,t.end,t.sync,!0)),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){this._renderer.value&&(this._renderer.value.clearTextureAtlas?.(),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>this._renderer.value?.handleResize(e,t)):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,t,r){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=r,this._renderer.value?.handleSelectionChanged(e,t,r)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};Ct=y([m(2,R),m(3,fe),m(4,Be),m(5,Y),m(6,ge),m(7,D),m(8,G),m(9,_e)],Ct);var us=class{constructor(i,e,t){this._coreBrowserService=i;this._coreService=e;this._onTimeout=t;this._start=0;this._end=0;this._isBuffering=!1}bufferRows(i,e){this._isBuffering?(this._start=Math.min(this._start,i),this._end=Math.max(this._end,e)):(this._start=i,this._end=e,this._isBuffering=!0),this._timeout??=this._coreBrowserService.window.setTimeout(()=>{this._timeout=void 0,this._coreService.decPrivateModes.synchronizedOutput=!1,this._onTimeout()},1e3)}flush(){if(this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0),!this._isBuffering)return;let i={start:this._start,end:this._end};return this._isBuffering=!1,i}dispose(){this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}};function tn(n,i,e,t){let r=e.buffer.x,s=e.buffer.y;if(!e.buffer.hasScrollback)return ro(r,s,n,i,e,t)+Yi(s,i,e,t)+so(r,s,n,i,e,t);let o;if(s===i)return o=r>n?"D":"C",Yt(Math.abs(r-n),Xt(o,t));o=s>i?"D":"C";let a=Math.abs(s-i),l=io(s>i?n:r,e)+(a-1)*e.cols+1+to(s>i?r:n,e);return Yt(l,Xt(o,t))}function to(n,i){return n-1}function io(n,i){return i.cols-n}function ro(n,i,e,t,r,s){return Yi(i,t,r,s).length===0?"":Yt(sn(n,i,n,i-$e(i,r),!1,r).length,Xt("D",s))}function Yi(n,i,e,t){let r=n-$e(n,e),s=i-$e(i,e),o=Math.abs(r-s)-no(n,i,e);return Yt(o,Xt(rn(n,i),t))}function so(n,i,e,t,r,s){let o;Yi(i,t,r,s).length>0?o=t-$e(t,r):o=i;let a=t,l=oo(n,i,e,t,r,s);return Yt(sn(n,o,e,a,l==="C",r).length,Xt(l,s))}function no(n,i,e){let t=0,r=n-$e(n,e),s=i-$e(i,e);for(let o=0;o=0&&n0?o=t-$e(t,r):o=i,n=e&&oi?"A":"B"}function sn(n,i,e,t,r,s){let o=n,a=i,l="";for(;(o!==e||a!==t)&&a>=0&&as.cols-1?(l+=s.buffer.translateBufferLineToString(a,!1,n,o),o=0,n=0,a++):!r&&o<0&&(l+=s.buffer.translateBufferLineToString(a,!1,0,n+1),o=s.cols-1,n=o,a--);return l+s.buffer.translateBufferLineToString(a,!1,n,o)}function Xt(n,i){let e=i?"O":"[";return"\x1B"+e+n}function Yt(n,i){n=Math.floor(n);let e="";for(let t=0;tthis._bufferService.cols?i%this._bufferService.cols===0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(i/this._bufferService.cols)-1]:[i%this._bufferService.cols,this.selectionStart[1]+Math.floor(i/this._bufferService.cols)]:[i,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){let i=this.selectionStart[0]+this.selectionStartLength;return i>this._bufferService.cols?[i%this._bufferService.cols,this.selectionStart[1]+Math.floor(i/this._bufferService.cols)]:[Math.max(i,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){let i=this.selectionStart,e=this.selectionEnd;return!i||!e?!1:i[1]>e[1]||i[1]===e[1]&&i[0]>e[0]}handleTrim(i){return this.selectionStart&&(this.selectionStart[1]-=i),this.selectionEnd&&(this.selectionEnd[1]-=i),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):this.selectionStart&&this.selectionStart[1]<0?(this.selectionStart=[0,0],!0):!1}};function fs(n,i){if(n.start.y>n.end.y)throw new Error(`Buffer range end (${n.end.x}, ${n.end.y}) cannot be before start (${n.start.x}, ${n.start.y})`);return i*(n.end.y-n.start.y)+(n.end.x-n.start.x+1)}var ao="\xA0",lo=new RegExp(ao,"g");var Et=class extends g{constructor(e,t,r,s,o,a,l,h,d,c){super();this._element=e;this._screenElement=t;this._linkifier=r;this._bufferService=s;this._coreService=o;this._mouseCoordsService=a;this._optionsService=l;this._mouseStateService=h;this._renderService=d;this._coreBrowserService=c;this._dragScrollAmount=0;this._enabled=!0;this._trimListener=this._register(new P);this._workCell=new F;this._mouseDownTimeStamp=0;this._oldHasSelection=!1;this._oldSelectionStart=void 0;this._oldSelectionEnd=void 0;this._onLinuxMouseSelection=this._register(new b);this.onLinuxMouseSelection=this._onLinuxMouseSelection.event;this._onRedrawRequest=this._register(new b);this.onRequestRedraw=this._onRedrawRequest.event;this._onSelectionChange=this._register(new b);this.onSelectionChange=this._onSelectionChange.event;this._onRequestScrollLines=this._register(new b);this.onRequestScrollLines=this._onRequestScrollLines.event;this._mouseMoveListener=u=>this._handleMouseMove(u),this._mouseUpListener=u=>this._handleMouseUp(u),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener.value=this._bufferService.buffer.lines.onTrim(u=>this._handleTrim(u)),this._register(this._bufferService.buffers.onBufferActivate(u=>this._handleBufferActivate(u))),this.enable(),this._model=new ji(this._bufferService),this._activeSelectionMode=0,this._register(E(()=>{this._removeMouseDownListeners()})),this._register(this._bufferService.onResize(u=>{u.rowsChanged&&this.clearSelection()}))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!e||!t?!1:e[0]!==t[0]||e[1]!==t[1]}get selectionText(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";let r=this._bufferService.buffer,s=[];if(this._activeSelectionMode===3){if(e[0]===t[0])return"";let a=e[0]a.replace(lo," ")).join(Ue?`\r + `:` + `)}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh())),zt&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:this._activeSelectionMode===3})}_isClickInSelection(e){let t=this._getMouseBufferCoords(e),r=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!r||!s||!t?!1:this._areCoordsInSelection(t,r,s)}isCellInSelection(e,t){let r=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!r||!s?!1:this._areCoordsInSelection([e,t],r,s)}_areCoordsInSelection(e,t,r){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){let r=this._linkifier.currentLink?.link?.range;if(r)return this._model.selectionStart=[r.start.x-1,r.start.y-1],this._model.selectionStartLength=fs(r,this._bufferService.cols),this._model.selectionEnd=void 0,!0;let s=this._getMouseBufferCoords(e);return s?(this._selectWordAt(s,t),this._model.selectionEnd=void 0,!0):!1}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){let t=this._mouseCoordsService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t}_getMouseEventScrollAmount(e){let t=qt(this._coreBrowserService.window,e,this._screenElement)[1],r=this._renderService.dimensions.css.canvas.height;return t>=0&&t<=r?0:(t>r&&(t-=r),t=Math.min(Math.max(t,-50),50),t/=50,t/Math.abs(t)+Math.round(t*14))}shouldForceSelection(e){return this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive?!e.altKey:ie?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}handleMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,!(e.button===2&&this.hasSelection)&&e.button===0&&!(this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive&&e.altKey)){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._handleIncrementalClick(e):e.detail===1?this._handleSingleClick(e):e.detail===2?this._handleDoubleClick(e):e.detail===3&&this._handleTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval(()=>this._dragScroll(),50)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_handleSingleClick(e){let t=this.hasSelection;if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),!this._model.selectionStart)return;this._model.selectionEnd=void 0,t&&this._fireOnSelectionChange(this._model.finalSelectionStart,this._model.finalSelectionEnd,!1);let r=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);r&&r.length!==this._model.selectionStart[0]&&r.hasWidth(this._model.selectionStart[0])===0&&this._model.selectionStart[0]++}_handleDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(e){let t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))}shouldColumnSelect(e){return this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive?!1:e.altKey&&!(ie&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;let t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),!this._model.selectionEnd){this.refresh(!0);return}this._activeSelectionMode===2?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));let r=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows-1,e.lines.length-1)):(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}}_handleMouseUp(e){let t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&t<500&&e.altKey&&this._optionsService.rawOptions.altClickMovesCursor){if(this._bufferService.buffer.ybase===this._bufferService.buffer.ydisp){let r=this._mouseCoordsService.getCoords(e,this._element,this._bufferService.cols,this._bufferService.rows,!1);if(r&&r[0]!==void 0&&r[1]!==void 0){let s=tn(r[0]-1,r[1]-1,this._bufferService,this._coreService.decPrivateModes.applicationCursorKeys);this._coreService.triggerDataEvent(s,!0)}}}else this._fireEventIfSelectionChanged()}_fireEventIfSelectionChanged(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd,r=!!e&&!!t&&(e[0]!==t[0]||e[1]!==t[1]);if(!r){this._oldHasSelection&&this._fireOnSelectionChange(e,t,r);return}!e||!t||(!this._oldSelectionStart||!this._oldSelectionEnd||e[0]!==this._oldSelectionStart[0]||e[1]!==this._oldSelectionStart[1]||t[0]!==this._oldSelectionEnd[0]||t[1]!==this._oldSelectionEnd[1])&&this._fireOnSelectionChange(e,t,r)}_fireOnSelectionChange(e,t,r){this._oldSelectionStart=e,this._oldSelectionEnd=t,this._oldHasSelection=r,this._onSelectionChange.fire()}_handleBufferActivate(e){this.clearSelection(),this._trimListener.value=e.activeBuffer.lines.onTrim(t=>this._handleTrim(t))}_convertViewportColToCharacterIndex(e,t){let r=t;for(let s=0;t>=s;s++){let o=e.loadCell(s,this._workCell).getChars().length;this._workCell.getWidth()===0?r--:o>1&&t!==s&&(r+=o-1)}return r}setSelection(e,t,r){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=r,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,t,r=!0,s=!0){if(e[0]>=this._bufferService.cols)return;let o=this._bufferService.buffer,a=o.lines.get(e[1]);if(!a)return;let l=o.translateBufferLineToString(e[1],!1),h=this._convertViewportColToCharacterIndex(a,e[0]),d=h,c=e[0]-h,u=0,_=0,p=0,v=0;if(l.charAt(h)===" "){for(;h>0&&l.charAt(h-1)===" ";)h--;for(;d1&&(v+=L-1,d+=L-1);I>0&&h>0&&!this._isCharWordSeparator(a.loadCell(I-1,this._workCell));){a.loadCell(I-1,this._workCell);let T=this._workCell.getChars().length;this._workCell.getWidth()===0?(u++,I--):T>1&&(p+=T-1,h-=T-1),h--,I--}for(;w1&&(v+=T-1,d+=T-1),d++,w++}}d++;let f=h+c-u+p,S=Math.min(this._bufferService.cols,d-h+u+_-p-v);if(!(!t&&l.slice(h,d).trim()==="")){if(r&&f===0&&a.getCodePoint(0)!==32){let I=o.lines.get(e[1]-1);if(I&&a.isWrapped&&I.getCodePoint(this._bufferService.cols-1)!==32){let w=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(w){let L=this._bufferService.cols-w.start;f-=L,S+=L}}}if(s&&f+S===this._bufferService.cols&&a.getCodePoint(this._bufferService.cols-1)!==32){let I=o.lines.get(e[1]+1);if(I?.isWrapped&&I.getCodePoint(0)!==32){let w=this._getWordAt([0,e[1]+1],!1,!1,!0);w&&(S+=w.length)}}return{start:f,length:S}}}_selectWordAt(e,t){let r=this._getWordAt(e,t);if(r){for(;r.start<0;)r.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[r.start,e[1]],this._model.selectionStartLength=r.length}}_selectToWordAt(e){let t=this._getWordAt(e,!0);if(t){let r=e[1];for(;t.start<0;)t.start+=this._bufferService.cols,r--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,r++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,r]}}_isCharWordSeparator(e){return e.getWidth()===0?!1:this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){let t=this._bufferService.buffer.getWrappedRangeForLine(e),r={start:{x:0,y:t.first},end:{x:this._bufferService.cols-1,y:t.last}};this._model.selectionStart=[0,t.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=fs(r,this._bufferService.cols)}};Et=y([m(3,D),m(4,Y),m(5,Pe),m(6,R),m(7,Me),m(8,V),m(9,G)],Et);var jt=class{constructor(){this._data={}}set(i,e,t){this._data[i]||(this._data[i]={}),this._data[i][e]=t}get(i,e){return this._data[i]?this._data[i][e]:void 0}clear(){this._data={}}};var Zt=class{constructor(){this._color=new jt;this._css=new jt}setCss(i,e,t){this._css.set(i,e,t)}getCss(i,e){return this._css.get(i,e)}setColor(i,e,t){this._color.set(i,e,t)}getColor(i,e){return this._color.get(i,e)}clear(){this._color.clear(),this._css.clear()}};var $=Object.freeze((()=>{let n=[B.toColor("#2e3436"),B.toColor("#cc0000"),B.toColor("#4e9a06"),B.toColor("#c4a000"),B.toColor("#3465a4"),B.toColor("#75507b"),B.toColor("#06989a"),B.toColor("#d3d7cf"),B.toColor("#555753"),B.toColor("#ef2929"),B.toColor("#8ae234"),B.toColor("#fce94f"),B.toColor("#729fcf"),B.toColor("#ad7fa8"),B.toColor("#34e2e2"),B.toColor("#eeeeec")],i=[0,95,135,175,215,255];for(let e=0;e<216;e++){let t=i[e/36%6|0],r=i[e/6%6|0],s=i[e%6];n.push({css:O.toCss(t,r,s),rgba:O.toRgba(t,r,s)})}for(let e=0;e<24;e++){let t=8+e*10;n.push({css:O.toCss(t,t,t),rgba:O.toRgba(t,t,t)})}return n})());var qe=B.toColor("#ffffff"),Qt=B.toColor("#000000"),nn=B.toColor("#ffffff"),on=Qt,Jt={css:"rgba(255, 255, 255, 0.3)",rgba:4294967117},co=qe,yt=class extends g{constructor(e){super();this._optionsService=e;this._contrastCache=new Zt;this._halfContrastCache=new Zt;this._onChangeColors=this._register(new b);this.onChangeColors=this._onChangeColors.event;this._colors={foreground:qe,background:Qt,cursor:nn,cursorAccent:on,selectionForeground:void 0,selectionBackgroundTransparent:Jt,selectionBackgroundOpaque:k.blend(Qt,Jt),selectionInactiveBackgroundTransparent:Jt,selectionInactiveBackgroundOpaque:k.blend(Qt,Jt),scrollbarSliderBackground:k.opacity(qe,.2),scrollbarSliderHoverBackground:k.opacity(qe,.4),scrollbarSliderActiveBackground:k.opacity(qe,.5),overviewRulerBorder:qe,ansi:$.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this._register(this._optionsService.onSpecificOptionChange("minimumContrastRatio",()=>this._contrastCache.clear())),this._register(this._optionsService.onSpecificOptionChange("theme",()=>this._setTheme(this._optionsService.rawOptions.theme)))}get colors(){return this._colors}_setTheme(e={}){let t=this._colors;if(t.foreground=M(e.foreground,qe),t.background=M(e.background,Qt),t.cursor=k.blend(t.background,M(e.cursor,nn)),t.cursorAccent=k.blend(t.background,M(e.cursorAccent,on)),t.selectionBackgroundTransparent=M(e.selectionBackground,Jt),t.selectionBackgroundOpaque=k.blend(t.background,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundTransparent=M(e.selectionInactiveBackground,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundOpaque=k.blend(t.background,t.selectionInactiveBackgroundTransparent),t.selectionForeground=e.selectionForeground?M(e.selectionForeground,ts):void 0,t.selectionForeground===ts&&(t.selectionForeground=void 0),k.isOpaque(t.selectionBackgroundTransparent)&&(t.selectionBackgroundTransparent=k.opacity(t.selectionBackgroundTransparent,.3)),k.isOpaque(t.selectionInactiveBackgroundTransparent)&&(t.selectionInactiveBackgroundTransparent=k.opacity(t.selectionInactiveBackgroundTransparent,.3)),t.scrollbarSliderBackground=M(e.scrollbarSliderBackground,k.opacity(t.foreground,.2)),t.scrollbarSliderHoverBackground=M(e.scrollbarSliderHoverBackground,k.opacity(t.foreground,.4)),t.scrollbarSliderActiveBackground=M(e.scrollbarSliderActiveBackground,k.opacity(t.foreground,.5)),t.overviewRulerBorder=M(e.overviewRulerBorder,co),t.ansi=$.slice(),t.ansi[0]=M(e.black,$[0]),t.ansi[1]=M(e.red,$[1]),t.ansi[2]=M(e.green,$[2]),t.ansi[3]=M(e.yellow,$[3]),t.ansi[4]=M(e.blue,$[4]),t.ansi[5]=M(e.magenta,$[5]),t.ansi[6]=M(e.cyan,$[6]),t.ansi[7]=M(e.white,$[7]),t.ansi[8]=M(e.brightBlack,$[8]),t.ansi[9]=M(e.brightRed,$[9]),t.ansi[10]=M(e.brightGreen,$[10]),t.ansi[11]=M(e.brightYellow,$[11]),t.ansi[12]=M(e.brightBlue,$[12]),t.ansi[13]=M(e.brightMagenta,$[13]),t.ansi[14]=M(e.brightCyan,$[14]),t.ansi[15]=M(e.brightWhite,$[15]),e.extendedAnsi){let r=Math.min(t.ansi.length-16,e.extendedAnsi.length);for(let s=0;s"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']};function ln(n,i,e,t){let r={type:0,cancel:!1,key:void 0},s=(n.shiftKey?1:0)|(n.altKey?2:0)|(n.ctrlKey?4:0)|(n.metaKey?8:0);switch(n.keyCode){case 0:n.key==="UIKeyInputUpArrow"?i?r.key="\x1BOA":r.key="\x1B[A":n.key==="UIKeyInputLeftArrow"?i?r.key="\x1BOD":r.key="\x1B[D":n.key==="UIKeyInputRightArrow"?i?r.key="\x1BOC":r.key="\x1B[C":n.key==="UIKeyInputDownArrow"&&(i?r.key="\x1BOB":r.key="\x1B[B");break;case 8:r.key=n.ctrlKey?"\b":"\x7F",n.altKey&&(r.key="\x1B"+r.key);break;case 9:if(n.shiftKey){r.key="\x1B[Z";break}r.key=" ",r.cancel=!0;break;case 13:n.key==="c"&&n.ctrlKey?r.key="":r.key=n.altKey?"\x1B\r":"\r",r.cancel=!0;break;case 27:r.key="\x1B",n.altKey&&(r.key="\x1B\x1B"),r.cancel=!0;break;case 37:if(n.metaKey)break;s?r.key="\x1B[1;"+(s+1)+"D":i?r.key="\x1BOD":r.key="\x1B[D";break;case 39:if(n.metaKey)break;s?r.key="\x1B[1;"+(s+1)+"C":i?r.key="\x1BOC":r.key="\x1B[C";break;case 38:if(n.metaKey)break;s?r.key="\x1B[1;"+(s+1)+"A":i?r.key="\x1BOA":r.key="\x1B[A";break;case 40:if(n.metaKey)break;s?r.key="\x1B[1;"+(s+1)+"B":i?r.key="\x1BOB":r.key="\x1B[B";break;case 45:!n.shiftKey&&!n.ctrlKey&&(r.key="\x1B[2~");break;case 46:s?r.key="\x1B[3;"+(s+1)+"~":r.key="\x1B[3~";break;case 36:s?r.key="\x1B[1;"+(s+1)+"H":i?r.key="\x1BOH":r.key="\x1B[H";break;case 35:s?r.key="\x1B[1;"+(s+1)+"F":i?r.key="\x1BOF":r.key="\x1B[F";break;case 33:n.shiftKey?r.type=2:n.ctrlKey?r.key="\x1B[5;"+(s+1)+"~":r.key="\x1B[5~";break;case 34:n.shiftKey?r.type=3:n.ctrlKey?r.key="\x1B[6;"+(s+1)+"~":r.key="\x1B[6~";break;case 112:s?r.key="\x1B[1;"+(s+1)+"P":r.key="\x1BOP";break;case 113:s?r.key="\x1B[1;"+(s+1)+"Q":r.key="\x1BOQ";break;case 114:s?r.key="\x1B[1;"+(s+1)+"R":r.key="\x1BOR";break;case 115:s?r.key="\x1B[1;"+(s+1)+"S":r.key="\x1BOS";break;case 116:s?r.key="\x1B[15;"+(s+1)+"~":r.key="\x1B[15~";break;case 117:s?r.key="\x1B[17;"+(s+1)+"~":r.key="\x1B[17~";break;case 118:s?r.key="\x1B[18;"+(s+1)+"~":r.key="\x1B[18~";break;case 119:s?r.key="\x1B[19;"+(s+1)+"~":r.key="\x1B[19~";break;case 120:s?r.key="\x1B[20;"+(s+1)+"~":r.key="\x1B[20~";break;case 121:s?r.key="\x1B[21;"+(s+1)+"~":r.key="\x1B[21~";break;case 122:s?r.key="\x1B[23;"+(s+1)+"~":r.key="\x1B[23~";break;case 123:s?r.key="\x1B[24;"+(s+1)+"~":r.key="\x1B[24~";break;default:if(n.ctrlKey&&!n.shiftKey&&!n.altKey&&!n.metaKey)n.keyCode>=65&&n.keyCode<=90?r.key=String.fromCharCode(n.keyCode-64):n.keyCode===32?r.key="\0":n.keyCode>=51&&n.keyCode<=55?r.key=String.fromCharCode(n.keyCode-51+27):n.keyCode===56?r.key="\x7F":n.key==="/"?r.key="":n.keyCode===219?r.key="\x1B":n.keyCode===220?r.key="":n.keyCode===221&&(r.key="");else if((!e||t)&&n.altKey&&!n.metaKey){let a=ho[n.keyCode]?.[n.shiftKey?1:0];if(a)r.key="\x1B"+a;else if(n.keyCode>=65&&n.keyCode<=90){let l=n.ctrlKey?n.keyCode-64:n.keyCode+32,h=String.fromCharCode(l);n.shiftKey&&(h=h.toUpperCase()),r.key="\x1B"+h}else if(n.keyCode===32)r.key="\x1B"+(n.ctrlKey?"\0":" ");else if(n.key==="Dead"&&n.code.startsWith("Key")){let l=n.code.slice(3,4);n.shiftKey||(l=l.toLowerCase()),r.key="\x1B"+l,r.cancel=!0}}else if(e&&!n.altKey&&!n.ctrlKey&&!n.shiftKey&&n.metaKey)n.keyCode===65&&(r.type=1);else if(n.key&&!n.ctrlKey&&!n.altKey&&!n.metaKey&&n.keyCode>=48&&n.key.length===1)r.key=n.key;else if(n.key&&n.ctrlKey&&n.shiftKey)switch(n.code){case"Minus":r.key="";break;case"Digit2":r.key="\0";break;case"Digit6":r.key="";break}break}return r}var ei=class{constructor(){this._functionalKeyCodes={Escape:27,Enter:13,Tab:9,Backspace:127,CapsLock:57358,ScrollLock:57359,NumLock:57360,PrintScreen:57361,Pause:57362,ContextMenu:57363,F13:57376,F14:57377,F15:57378,F16:57379,F17:57380,F18:57381,F19:57382,F20:57383,F21:57384,F22:57385,F23:57386,F24:57387,F25:57388,KP_0:57399,KP_1:57400,KP_2:57401,KP_3:57402,KP_4:57403,KP_5:57404,KP_6:57405,KP_7:57406,KP_8:57407,KP_9:57408,KP_Decimal:57409,KP_Divide:57410,KP_Multiply:57411,KP_Subtract:57412,KP_Add:57413,KP_Enter:57414,KP_Equal:57415,ShiftLeft:57441,ShiftRight:57447,ControlLeft:57442,ControlRight:57448,AltLeft:57443,AltRight:57449,MetaLeft:57444,MetaRight:57450,MediaPlayPause:57430,MediaStop:57432,MediaTrackNext:57435,MediaTrackPrevious:57436,AudioVolumeDown:57438,AudioVolumeUp:57439,AudioVolumeMute:57440};this._csiTildeKeys={Insert:2,Delete:3,PageUp:5,PageDown:6,F5:15,F6:17,F7:18,F8:19,F9:20,F10:21,F11:23,F12:24};this._csiLetterKeys={ArrowUp:"A",ArrowDown:"B",ArrowRight:"C",ArrowLeft:"D",Home:"H",End:"F"};this._ss3FunctionKeys={F1:"P",F2:"Q",F3:"R",F4:"S"}}_getNumpadKeyCode(i){if(i.code.startsWith("Numpad")){let e=i.code.slice(6);if(e>="0"&&e<="9")return 57399+parseInt(e,10);switch(e){case"Decimal":return 57409;case"Divide":return 57410;case"Multiply":return 57411;case"Subtract":return 57412;case"Add":return 57413;case"Enter":return 57414;case"Equal":return 57415}}}_getModifierKeyCode(i){switch(i.code){case"ShiftLeft":return 57441;case"ShiftRight":return 57447;case"ControlLeft":return 57442;case"ControlRight":return 57448;case"AltLeft":return 57443;case"AltRight":return 57449;case"MetaLeft":return 57444;case"MetaRight":return 57450}}_encodeModifiers(i){let e=0;return i.shiftKey&&(e|=1),i.altKey&&(e|=2),i.ctrlKey&&(e|=4),i.metaKey&&(e|=8),e>0?e+1:0}_getKeyCode(i,e){let t=this._getNumpadKeyCode(i);if(t!==void 0)return t;let r=this._getModifierKeyCode(i);if(r!==void 0)return r;let s=this._functionalKeyCodes[i.key];if(s!==void 0)return s;if((i.shiftKey||e&&i.altKey)&&i.code){if(i.code.startsWith("Digit")&&i.code.length===6){let o=i.code.charAt(5);if(o>="0"&&o<="9")return o.charCodeAt(0)}if(i.code.startsWith("Key")&&i.code.length===4)return i.code.charAt(3).toLowerCase().charCodeAt(0)}if(i.key.length===1){let o=i.key.codePointAt(0);return o>=65&&o<=90?o+32:o}}_isModifierKey(i){return i.key==="Shift"||i.key==="Control"||i.key==="Alt"||i.key==="Meta"}_isLockKey(i){return i.key==="CapsLock"||i.key==="NumLock"||i.key==="ScrollLock"}_buildCsiLetterSequence(i,e,t,r){let s=r&&t!==1;if(e>0||s){let o="\x1B[1;"+(e>0?e:"1");return s&&(o+=":"+t),o+=i,o}return"\x1B["+i}_buildSs3Sequence(i,e,t,r){let s=r&&t!==1;if(e>0||s){let o="\x1B[1;"+(e>0?e:"1");return s&&(o+=":"+t),o+=i,o}return"\x1BO"+i}_buildCsiTildeSequence(i,e,t,r){let s=r&&t!==1,o="\x1B["+i;return(e>0||s)&&(o+=";"+(e>0?e:"1"),s&&(o+=":"+t)),o+="~",o}_buildCsiUSequence(i,e,t,r,s,o,a){let l=!!(s&2),h=!!(s&4),d="\x1B["+e,c;h&&i.shiftKey&&i.key.length===1&&!o&&!a&&(c=i.key.codePointAt(0),d+=":"+c);let _=!!(s&16)&&r!==3&&i.key.length===1&&!o&&!a&&!i.ctrlKey?i.key.codePointAt(0):void 0,p=l&&r!==1&&(r===3||_===void 0);return(t>0||p||_!==void 0)&&(d+=";",t>0?d+=t:p&&(d+="1"),p&&(d+=":"+r)),_!==void 0&&(d+=";"+_),d+="u",d}evaluate(i,e,t=1,r=!1){let s={type:0,cancel:!1,key:void 0},o=this._encodeModifiers(i),a=this._isModifierKey(i),l=!!(e&2);if(!l&&t===3||a&&!(e&8)||this._isLockKey(i)&&!(e&8))return s;let h=this._csiLetterKeys[i.key];if(h)return s.key=this._buildCsiLetterSequence(h,o,t,l),s.cancel=!0,s;let d=this._ss3FunctionKeys[i.key];if(d)return s.key=this._buildSs3Sequence(d,o,t,l),s.cancel=!0,s;let c=this._csiTildeKeys[i.key];if(c!==void 0)return s.key=this._buildCsiTildeSequence(c,o,t,l),s.cancel=!0,s;let u=this._getKeyCode(i,r);if(u===void 0)return s;let _=u===13||u===9||u===127;if(_&&t===3&&!(e&8))return s;let p=this._functionalKeyCodes[i.key]!==void 0||this._getNumpadKeyCode(i)!==void 0;if(!!(e&8||l&&t===3||(e&1||l)&&(p&&!_||o>0&&i.key.length!==1||o-1>1)))s.key=this._buildCsiUSequence(i,u,o,t,e,p,a),s.cancel=!0;else{let f=u===13?"\r":u===9?" ":u===127?"\x7F":void 0;f?s.key=f:i.key.length===1&&!i.ctrlKey&&!i.altKey&&!i.metaKey&&(s.key=i.key)}return s}static shouldUseProtocol(i){return i>0}};var Zi=class{constructor(){this._codeToVk={KeyA:65,KeyB:66,KeyC:67,KeyD:68,KeyE:69,KeyF:70,KeyG:71,KeyH:72,KeyI:73,KeyJ:74,KeyK:75,KeyL:76,KeyM:77,KeyN:78,KeyO:79,KeyP:80,KeyQ:81,KeyR:82,KeyS:83,KeyT:84,KeyU:85,KeyV:86,KeyW:87,KeyX:88,KeyY:89,KeyZ:90,Digit0:48,Digit1:49,Digit2:50,Digit3:51,Digit4:52,Digit5:53,Digit6:54,Digit7:55,Digit8:56,Digit9:57,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,F13:124,F14:125,F15:126,F16:127,F17:128,F18:129,F19:130,F20:131,F21:132,F22:133,F23:134,F24:135,Numpad0:96,Numpad1:97,Numpad2:98,Numpad3:99,Numpad4:100,Numpad5:101,Numpad6:102,Numpad7:103,Numpad8:104,Numpad9:105,NumpadMultiply:106,NumpadAdd:107,NumpadSeparator:108,NumpadSubtract:109,NumpadDecimal:110,NumpadDivide:111,NumpadEnter:13,NumLock:144,ArrowUp:38,ArrowDown:40,ArrowLeft:37,ArrowRight:39,Home:36,End:35,PageUp:33,PageDown:34,Insert:45,Delete:46,ShiftLeft:16,ShiftRight:16,ControlLeft:17,ControlRight:17,AltLeft:18,AltRight:18,MetaLeft:91,MetaRight:92,CapsLock:20,ScrollLock:145,Escape:27,Enter:13,Tab:9,Space:32,Backspace:8,Pause:19,ContextMenu:93,PrintScreen:44,Semicolon:186,Equal:187,Comma:188,Minus:189,Period:190,Slash:191,Backquote:192,BracketLeft:219,Backslash:220,BracketRight:221,Quote:222,IntlBackslash:226};this._codeToScancode={KeyQ:16,KeyW:17,KeyE:18,KeyR:19,KeyT:20,KeyY:21,KeyU:22,KeyI:23,KeyO:24,KeyP:25,KeyA:30,KeyS:31,KeyD:32,KeyF:33,KeyG:34,KeyH:35,KeyJ:36,KeyK:37,KeyL:38,KeyZ:44,KeyX:45,KeyC:46,KeyV:47,KeyB:48,KeyN:49,KeyM:50,Digit1:2,Digit2:3,Digit3:4,Digit4:5,Digit5:6,Digit6:7,Digit7:8,Digit8:9,Digit9:10,Digit0:11,F1:59,F2:60,F3:61,F4:62,F5:63,F6:64,F7:65,F8:66,F9:67,F10:68,F11:87,F12:88,Numpad0:82,Numpad1:79,Numpad2:80,Numpad3:81,Numpad4:75,Numpad5:76,Numpad6:77,Numpad7:71,Numpad8:72,Numpad9:73,NumpadMultiply:55,NumpadAdd:78,NumpadSubtract:74,NumpadDecimal:83,NumpadDivide:53,NumpadEnter:28,NumLock:69,ArrowUp:72,ArrowDown:80,ArrowLeft:75,ArrowRight:77,Home:71,End:79,PageUp:73,PageDown:81,Insert:82,Delete:83,ShiftLeft:42,ShiftRight:54,ControlLeft:29,ControlRight:29,AltLeft:56,AltRight:56,CapsLock:58,ScrollLock:70,Escape:1,Enter:28,Tab:15,Space:57,Backspace:14,Pause:69,Semicolon:39,Equal:13,Comma:51,Minus:12,Period:52,Slash:53,Backquote:41,BracketLeft:26,Backslash:43,BracketRight:27,Quote:40};this._enhancedKeyCodes=new Set(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","PageUp","PageDown","Insert","Delete","NumpadEnter","NumpadDivide","ControlRight","AltRight","PrintScreen","Pause","ContextMenu","MetaLeft","MetaRight"]);this._keyToControlChar={Enter:13,Backspace:8,Tab:9,Escape:27}}_getVirtualKeyCode(i){let e=this._codeToVk[i.code];return e!==void 0?e:i.keyCode||0}_getScanCode(i){return this._codeToScancode[i.code]||0}_getUnicodeChar(i){if(i.ctrlKey&&!i.altKey&&!i.metaKey){if(i.key==="Enter")return 10;if(i.key==="Backspace")return 127}let e=this._keyToControlChar[i.key];if(e!==void 0)return e;if(i.key.length===1){let t=i.key.codePointAt(0)||0;if(i.ctrlKey&&!i.altKey&&!i.metaKey){if(t>=65&&t<=90)return t-64;if(t>=97&&t<=122)return t-96}return t}return 0}_getControlKeyState(i){let e=0;return i.shiftKey&&(e|=16),i.ctrlKey&&(i.code==="ControlRight"?e|=4:e|=8),i.altKey&&(i.code==="AltRight"?e|=1:e|=2),this._enhancedKeyCodes.has(i.code)&&(e|=256),e}evaluateKeyboardEvent(i,e){let t=this._getVirtualKeyCode(i),r=this._getScanCode(i),s=this._getUnicodeChar(i),o=e?1:0,a=this._getControlKeyState(i);return{type:0,cancel:!0,key:`\x1B[${t};${r};${s};${o};${a};1_`}}};var xt=class{constructor(i,e){this._coreService=i;this._optionsService=e}_getWin32InputMode(){return this._win32InputMode??=new Zi,this._win32InputMode}_getKittyKeyboard(){return this._kittyKeyboard??=new ei,this._kittyKeyboard}evaluateKeyDown(i){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(i,!0);let e=this._coreService.kittyKeyboard.flags;return this.useKitty?this._getKittyKeyboard().evaluate(i,e,i.repeat?2:1,ie&&this._optionsService.rawOptions.macOptionIsMeta):ln(i,this._coreService.decPrivateModes.applicationCursorKeys,ie,this._optionsService.rawOptions.macOptionIsMeta)}evaluateKeyUp(i){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(i,!1);let e=this._coreService.kittyKeyboard.flags;if(this.useKitty&&e&2)return this._getKittyKeyboard().evaluate(i,e,3,ie&&this._optionsService.rawOptions.macOptionIsMeta)}get useKitty(){let i=this._coreService.kittyKeyboard.flags;return!!(this._optionsService.rawOptions.vtExtensions?.kittyKeyboard&&ei.shouldUseProtocol(i))}get useWin32InputMode(){return!!(this._optionsService.rawOptions.vtExtensions?.win32InputMode&&this._coreService.decPrivateModes.win32InputMode)}};xt=y([m(0,Y),m(1,R)],xt);var ps=class{constructor(...i){this._entries=new Map;for(let[e,t]of i)this.set(e,t)}set(i,e){let t=this._entries.get(i);return this._entries.set(i,e),t}forEach(i){for(let[e,t]of this._entries.entries())i(e,t)}has(i){return this._entries.has(i)}get(i){return this._entries.get(i)}},Ji=class{constructor(){this._services=new ps;this._services.set(Qe,this)}setService(i,e){this._services.set(i,e)}getService(i){return this._services.get(i)}createInstance(i,...e){let t=Hs(i).sort((o,a)=>o.index-a.index),r=[];for(let o of t){let a=this._services.get(o.id);if(!a)throw new Error(`[createInstance] ${i.name} depends on UNKNOWN service ${o.id._id}.`);r.push(a)}let s=t.length>0?t[0].index:e.length;if(e.length!==s)throw new Error(`[createInstance] First service dependency of ${i.name} at position ${s+1} conflicts with ${e.length} static arguments`);return new i(...e,...r)}};var uo={trace:0,debug:1,info:2,warn:3,error:4,off:5},fo="xterm.js: ",wt=class extends g{constructor(e){super();this._optionsService=e;this._logLevel=5;this._updateLogLevel(),this._register(this._optionsService.onSpecificOptionChange("logLevel",()=>this._updateLogLevel()))}get logLevel(){return this._logLevel}_updateLogLevel(){this._logLevel=uo[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let t=0;tthis._length)for(let t=this._length;t=e;s--)this._array[this._getCyclicIndex(s+r.length)]=this._array[this._getCyclicIndex(s)];for(let s=0;sthis._maxLength){let s=this._length+r.length-this._maxLength;this._startIndex+=s,this._length=this._maxLength,this.onTrimEmitter.fire(s)}else this._length+=r.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,t,r){if(!(t<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+r<0)throw new Error("Cannot shift elements in list beyond index 0");if(r>0){for(let o=t-1;o>=0;o--)this.set(e+o+r,this.get(e+o));let s=e+t+r-this._length;if(s>0)for(this._length+=s;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let s=0;sthis._limit?(this._builder.reset(),!0):!1}toString(){return this._builder.toString()}};var U=Object.freeze(new ue),Qi=0,hn=new F,er=new ii,De=class n{constructor(i,e,t,r=!1){this._stringCache=i;this.isWrapped=r;this._combined={};this._extendedAttrs={};this._data=new Uint32Array(e*3);let s=t??F.fromCharData([0,"",1,0]);for(let o=0;o>22,e&2097152?this._combined[i].charCodeAt(this._combined[i].length-1):t]}set(i,e){this._invalidateStringCache(),this._data[i*3+1]=e[0],e[1].length>1?(this._combined[i]=e[1],this._data[i*3+0]=i|2097152|e[2]<<22):this._data[i*3+0]=e[1].charCodeAt(0)|e[2]<<22}getWidth(i){return this._data[i*3+0]>>22}hasWidth(i){return this._data[i*3+0]&12582912}getFg(i){return this._data[i*3+1]}getBg(i){return this._data[i*3+2]}hasContent(i){return this._data[i*3+0]&4194303}getCodePoint(i){let e=this._data[i*3+0];return e&2097152?this._combined[i].charCodeAt(this._combined[i].length-1):e&2097151}isCombined(i){return this._data[i*3+0]&2097152}getString(i){let e=this._data[i*3+0];return e&2097152?this._combined[i]:e&2097151?be(e&2097151):""}isProtected(i){return this._data[i*3+2]&536870912}loadCell(i,e){return Qi=i*3,e.content=this._data[Qi+0],e.fg=this._data[Qi+1],e.bg=this._data[Qi+2],e.content&2097152?e.combinedData=this._combined[i]:e.combinedData="",e.bg&268435456?e.extended=this._extendedAttrs[i]:e.extended=U.extended.clone(),e}setCell(i,e){this._invalidateStringCache(),e.content&2097152&&(this._combined[i]=e.combinedData),e.bg&268435456&&(this._extendedAttrs[i]=e.extended),this._data[i*3+0]=e.content,this._data[i*3+1]=e.fg,this._data[i*3+2]=e.bg}setCellFromCodepoint(i,e,t,r){this._invalidateStringCache(),r.bg&268435456&&(this._extendedAttrs[i]=r.extended),this._data[i*3+0]=e|t<<22,this._data[i*3+1]=r.fg,this._data[i*3+2]=r.bg}addCodepointToCell(i,e,t){this._invalidateStringCache();let r=this._data[i*3+0];r&2097152?this._combined[i]+=be(e):r&2097151?(this._combined[i]=be(r&2097151)+be(e),r&=-2097152,r|=2097152):r=e|1<<22,t&&(r&=-12582913,r|=t<<22),this._data[i*3+0]=r}insertCells(i,e,t){if(this._invalidateStringCache(),i%=this.length,i&&this.getWidth(i-1)===2&&this.setCellFromCodepoint(i-1,0,1,t),e=0;--r)this.setCell(i+e+r,this.loadCell(i+r,hn));for(let r=0;rthis.length){if(this._data.buffer.byteLength>=t*4)this._data=new Uint32Array(this._data.buffer,0,t);else{let r=new Uint32Array(t);r.set(this._data),this._data=r}for(let r=this.length;r=i&&delete this._combined[a]}let s=Object.keys(this._extendedAttrs);for(let o=0;o=i&&delete this._extendedAttrs[a]}}return this.length=i,t*4*2=0;--i)if(this._data[i*3+0]&4194303)return i+(this._data[i*3+0]>>22);return 0}getNoBgTrimmedLength(){for(let i=this.length-1;i>=0;--i)if(this._data[i*3+0]&4194303||this._data[i*3+2]&50331648)return i+(this._data[i*3+0]>>22);return 0}copyCellsFrom(i,e,t,r,s){this._invalidateStringCache();let o=i._data;if(s)for(let a=r-1;a>=0;a--){for(let l=0;l<3;l++)this._data[(t+a)*3+l]=o[(e+a)*3+l];this._copyCellMapsFrom(i,e+a,t+a)}else for(let a=0;a>22||1}r&&r.push(e);let a=er.toString();if(er.reset(),s){let l=this._getStringCacheEntry(!0);l.value=a,l.isTrimmed=!!i}return a}_getStringCacheEntry(i){let e=this._stringCacheEntryRef?.deref();if(e&&e.generation===this._stringCache.generation)return e;if(!i)return;let t=this._stringCache.allocateEntry();return this._stringCacheEntryRef=new WeakRef(t),t}_invalidateStringCache(){let i=this._getStringCacheEntry(!1);i&&(i.value=void 0,i.isTrimmed=!1)}_copyCellMapsFrom(i,e,t){let r=e*3;i._data[r+0]&2097152&&(this._combined[t]=i._combined[e]),i._data[r+2]&268435456&&(this._extendedAttrs[t]=i._extendedAttrs[e])}_copySparseMapsFrom(i){this._combined={},this._extendedAttrs={};for(let e=0;ethis.entries.clear()))}touch(){this._scheduleClear()}allocateEntry(){let e={value:void 0,isTrimmed:!1,generation:this.generation};return this.entries.add(e),this._scheduleClear(),e}clear(){this._clearTimeout.clear(),this._lastAccessTimestamp=0,this.generation++;for(let e of this.entries)e.value=void 0,e.isTrimmed=!1;this.entries.clear()}_scheduleClear(){this._lastAccessTimestamp=Date.now(),!this._clearTimeout.value&&this._scheduleClearTimeout(15e3)}_scheduleClearTimeout(e){this._clearTimeout.value=Gs(()=>{let t=Date.now()-this._lastAccessTimestamp;if(t>=15e3){this.clear();return}this._scheduleClearTimeout(15e3-t)},e)}};function dn(n,i,e,t,r,s){let o=[];for(let a=0;a=a&&t0&&(f>c||d[f].getTrimmedLength()===0);f--)v++;v>0&&(o.push(a+d.length-v),o.push(v)),a+=d.length-1}return o}function un(n,i){let e=[],t=0,r=i[t],s=0;for(let o=0;ol&&(s-=l,o++);let h=n[o].getWidth(s-1)===2;h&&s--;let d=h?e-1:e;t.push(d),a+=d}return t}function Tt(n,i,e){if(i===n.length-1)return n[i].getTrimmedLength();let t=!n[i].hasContent(e-1)&&n[i].getWidth(e-1)===1,r=n[i+1].getWidth(0)===2;return t&&r?e-1:e}var rr=class rr{constructor(i){this.line=i;this.isDisposed=!1;this._disposables=[];this._id=rr._nextId++;this._onDispose=this.register(new b);this.onDispose=this._onDispose.event}get id(){return this._id}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),Oe(this._disposables),this._disposables.length=0)}register(i){return this._disposables.push(i),i}};rr._nextId=1;var ir=rr;var q={},Re=q.B;q[0]={"`":"\u25C6",a:"\u2592",b:"\u2409",c:"\u240C",d:"\u240D",e:"\u240A",f:"\xB0",g:"\xB1",h:"\u2424",i:"\u240B",j:"\u2518",k:"\u2510",l:"\u250C",m:"\u2514",n:"\u253C",o:"\u23BA",p:"\u23BB",q:"\u2500",r:"\u23BC",s:"\u23BD",t:"\u251C",u:"\u2524",v:"\u2534",w:"\u252C",x:"\u2502",y:"\u2264",z:"\u2265","{":"\u03C0","|":"\u2260","}":"\xA3","~":"\xB7"};q.A={"#":"\xA3"};q.B=void 0;q[4]={"#":"\xA3","@":"\xBE","[":"ij","\\":"\xBD","]":"|","{":"\xA8","|":"f","}":"\xBC","~":"\xB4"};q.C=q[5]={"[":"\xC4","\\":"\xD6","]":"\xC5","^":"\xDC","`":"\xE9","{":"\xE4","|":"\xF6","}":"\xE5","~":"\xFC"};q.R={"#":"\xA3","@":"\xE0","[":"\xB0","\\":"\xE7","]":"\xA7","{":"\xE9","|":"\xF9","}":"\xE8","~":"\xA8"};q.Q={"@":"\xE0","[":"\xE2","\\":"\xE7","]":"\xEA","^":"\xEE","`":"\xF4","{":"\xE9","|":"\xF9","}":"\xE8","~":"\xFB"};q.K={"@":"\xA7","[":"\xC4","\\":"\xD6","]":"\xDC","{":"\xE4","|":"\xF6","}":"\xFC","~":"\xDF"};q.Y={"#":"\xA3","@":"\xA7","[":"\xB0","\\":"\xE7","]":"\xE9","`":"\xF9","{":"\xE0","|":"\xF2","}":"\xE8","~":"\xEC"};q.E=q[6]={"@":"\xC4","[":"\xC6","\\":"\xD8","]":"\xC5","^":"\xDC","`":"\xE4","{":"\xE6","|":"\xF8","}":"\xE5","~":"\xFC"};q.Z={"#":"\xA3","@":"\xA7","[":"\xA1","\\":"\xD1","]":"\xBF","{":"\xB0","|":"\xF1","}":"\xE7"};q.H=q[7]={"@":"\xC9","[":"\xC4","\\":"\xD6","]":"\xC5","^":"\xDC","`":"\xE9","{":"\xE4","|":"\xF6","}":"\xE5","~":"\xFC"};q["="]={"#":"\xF9","@":"\xE0","[":"\xE9","\\":"\xE7","]":"\xEA","^":"\xEE",_:"\xE8","`":"\xF4","{":"\xE4","|":"\xF6","}":"\xFC","~":"\xFB"};var pn=4294967295,si=class extends g{constructor(e,t,r,s){super();this._hasScrollback=e;this._optionsService=t;this._bufferService=r;this._logService=s;this.ydisp=0;this.ybase=0;this.y=0;this.x=0;this.tabs={};this.savedY=0;this.savedX=0;this.savedCurAttrData=U.clone();this.savedCharset=Re;this.savedCharsets=[];this.savedGlevel=0;this.savedOriginMode=!1;this.savedWraparoundMode=!0;this.markers=[];this._nullCell=F.fromCharData([0,"",1,0]);this._whitespaceCell=F.fromCharData([0," ",1,32]);this._isClearing=!1;this._memoryCleanupPosition=0;this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new ti(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops(),this._memoryCleanupQueue=new It(this._logService),this._register(E(()=>this._memoryCleanupQueue.clear())),this._register(E(()=>this.clearAllMarkers())),this._stringCache=this._register(new tr)}getNullCell(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new ke),this._nullCell}getWhitespaceCell(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new ke),this._whitespaceCell}getBlankLine(e,t){return new De(this._stringCache,this._bufferService.cols,this.getNullCell(e),t)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){let t=this.ybase+this.y-this.ydisp;return t>=0&&tpn?pn:t}fillViewportRows(e){if(this.lines.length===0){e??=U;let t=this._rows;for(;t--;)this.lines.push(this.getBlankLine(e))}}clear(){this._stringCache.clear(),this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new ti(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(e,t){let r=this.getNullCell(U);this._stringCache.clear();let s=0,o=this._getCorrectBufferLength(t);if(o>this.lines.maxLength&&(this.lines.maxLength=o),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+a+1?(this.ybase--,a++,this.ydisp>0&&this.ydisp--):this.lines.push(new De(this._stringCache,e,r,!1)));else for(let l=this._rows;l>t;l--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(o0&&(this.lines.trimStart(l),this.ybase=Math.max(this.ybase-l,0),this.ydisp=Math.max(this.ydisp-l,0),this.savedY=Math.max(this.savedY-l,0)),this.lines.maxLength=o}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),a&&(this.y+=a),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(let a=0;a0){let a=Math.max(0,this.lines.length-this.ybase-1);this.y=Math.min(this.y,a)}this._memoryCleanupQueue.clear(),s>.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue(()=>this._batchedMemoryCleanup()))}_batchedMemoryCleanup(){let e=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,e=!1);let t=0;for(;this._memoryCleanupPosition100)return!0;return e}get _isReflowEnabled(){let e=this._optionsService.rawOptions.windowsPty;return e&&e.buildNumber?this._hasScrollback&&e.backend==="conpty"&&e.buildNumber>=21376:this._hasScrollback}_reflow(e,t){this._cols!==e&&(e>this._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))}_reflowLarger(e,t){let r=this._optionsService.rawOptions.reflowCursorLine,s=dn(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(U),r);if(s.length>0){let o=un(this.lines,s);fn(this.lines,o.layout),this._reflowLargerAdjustViewport(e,t,o.countRemoved)}}_reflowLargerAdjustViewport(e,t,r){let s=this.getNullCell(U),o=r;for(;o-- >0;)this.ybase===0?(this.y>0&&this.y--,this.lines.length=0;l--){let h=this.lines.get(l);if(!h||!h.isWrapped&&h.getTrimmedLength()<=e)continue;let d=[h];for(;h.isWrapped&&l>0;)h=this.lines.get(--l),d.unshift(h);if(!r){let T=this.ybase+this.y;if(T>=l&&T0&&(o.push({start:l+d.length+a,newLines:v}),a+=v.length),d.push(...v);let f=u.length-1,S=u[f];S===0&&(f--,S=u[f]);let I=d.length-_-1,w=c;for(;I>=0;){let T=Math.min(w,S);if(d[f]===void 0)break;if(d[f].copyCellsFrom(d[I],w-T,S-T,T,!0),S-=T,S===0&&(f--,S=u[f]),w-=T,w===0){I--;let te=Math.max(I,0);w=Tt(d,te,this._cols)}}for(let T=0;T0;)this.ybase===0?this.y0){let l=[],h=[];for(let S=0;S=0;S--)if(_&&_.start>c+p){for(let I=_.newLines.length-1;I>=0;I--)this.lines.set(S--,_.newLines[I]);S++,l.push({index:c+1,amount:_.newLines.length}),p+=_.newLines.length,_=o[++u]}else this.lines.set(S,h[c--]);let v=0;for(let S=l.length-1;S>=0;S--)l[S].index+=v,this.lines.onInsertEmitter.fire(l[S]),v+=l[S].amount;let f=Math.max(0,d+a-this.lines.maxLength);f>0&&this.lines.onTrimEmitter.fire(f)}}translateBufferLineToString(e,t,r=0,s){let o=this.lines.get(e);return o?o.translateToString(t,r,s):""}getWrappedRangeForLine(e){let t=e,r=e;for(;t>0&&this.lines.get(t).isWrapped;)t--;for(;r+10;);return e>=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(e??=this.x;!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e}clearMarkers(e){this._isClearing=!0;for(let t=0;t{t.line-=r,t.line<0&&t.dispose()})),t.register(this.lines.onInsert(r=>{t.line>=r.index&&(t.line+=r.amount)})),t.register(this.lines.onDelete(r=>{t.line>=r.index&&t.liner.index&&(t.line-=r.amount)})),t.register(t.onDispose(()=>this._removeMarker(t))),t}_removeMarker(e){this._isClearing||this.markers.splice(this.markers.indexOf(e),1)}};var sr=class extends g{constructor(e,t,r){super();this._optionsService=e;this._bufferService=t;this._logService=r;this._normalBuffer=this._register(new P);this._altBuffer=this._register(new P);this._onBufferActivate=this._register(new b);this.onBufferActivate=this._onBufferActivate.event;this.reset(),this._register(this._optionsService.onSpecificOptionChange("scrollback",()=>this.resize(this._bufferService.cols,this._bufferService.rows))),this._register(this._optionsService.onSpecificOptionChange("tabStopWidth",()=>this.setupTabStops()))}reset(){this._normal=new si(!0,this._optionsService,this._bufferService,this._logService),this._normalBuffer.value=this._normal,this._normal.fillViewportRows(),this._alt=new si(!1,this._optionsService,this._bufferService,this._logService),this._altBuffer.value=this._alt,this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}};var Dt=class extends g{constructor(e,t){super();this.isUserScrolling=!1;this._onResize=this._register(new b);this.onResize=this._onResize.event;this._onScroll=this._register(new b);this.onScroll=this._onScroll.event;this.cols=Math.max(e.rawOptions.cols||0,2),this.rows=Math.max(e.rawOptions.rows||0,1),this.buffers=this._register(new sr(e,this,t)),this._register(this.buffers.onBufferActivate(r=>{this._onScroll.fire(r.activeBuffer.ydisp)}))}get buffer(){return this.buffers.active}resize(e,t){let r=this.cols!==e,s=this.rows!==t;this.cols=e,this.rows=t,this.buffers.resize(e,t),this._onResize.fire({cols:e,rows:t,colsChanged:r,rowsChanged:s})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,t=!1){let r=this.buffer,s;s=this._cachedBlankLine,(!s||s.length!==this.cols||s.getFg(0)!==e.fg||s.getBg(0)!==e.bg)&&(s=r.getBlankLine(e,t),this._cachedBlankLine=s),s.isWrapped=t;let o=r.ybase+r.scrollTop,a=r.ybase+r.scrollBottom;if(r.scrollTop===0){let l=r.lines.isFull;a===r.lines.length-1?l?r.lines.recycle().copyFrom(s):r.lines.push(s.clone()):r.lines.splice(a+1,0,s.clone()),l?this.isUserScrolling&&(r.ydisp=Math.max(r.ydisp-1,0)):(r.ybase++,this.isUserScrolling||r.ydisp++)}else{let l=a-o+1;r.lines.shiftElements(o+1,l-1,-1),r.lines.set(a,s.clone())}this.isUserScrolling||(r.ydisp=r.ybase),this._onScroll.fire(r.ydisp)}scrollLines(e,t){let r=this.buffer;if(e<0){if(r.ydisp===0)return;this.isUserScrolling=!0}else e+r.ydisp>=r.ybase&&(this.isUserScrolling=!1);let s=r.ydisp;r.ydisp=Math.max(Math.min(r.ydisp+e,r.ybase),0),s!==r.ydisp&&(t||this._onScroll.fire(r.ydisp))}};Dt=y([m(0,R),m(1,fe)],Dt);var Rt={cols:80,rows:24,showCursorImmediately:!1,cursorBlink:!1,blinkIntervalDuration:0,cursorStyle:"block",cursorWidth:1,cursorInactiveStyle:"outline",drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollSensitivity:5,fontFamily:"monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:"info",logger:null,scrollback:1e3,scrollbar:{showScrollbar:!0},scrollOnEraseInDisplay:!1,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,mouseEventsRequireAlt:!1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},reflowCursorLine:!1,rescaleOverlappingGlyphs:!1,rightClickSelectsWord:ie,windowOptions:{},windowsPty:{},wordSeparator:" ()[]{}',\"`",altClickMovesCursor:!0,convertEol:!1,termName:"xterm",quirks:{},vtExtensions:{}},po=["normal","bold","100","200","300","400","500","600","700","800","900"],nr=class extends g{constructor(e){super();this._onOptionChange=this._register(new b);this.onOptionChange=this._onOptionChange.event;let t={...Rt};for(let r in e)if(r in t)try{let s=e[r];t[r]=this._sanitizeAndValidateOption(r,s)}catch(s){console.error(s)}this.rawOptions=t,this.options={...t},this._setupOptions(),this._register(E(()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null}))}onSpecificOptionChange(e,t){return this.onOptionChange(r=>{r===e&&t(this.rawOptions[e])})}onMultipleOptionChange(e,t){return this.onOptionChange(r=>{e.indexOf(r)!==-1&&t()})}_setupOptions(){let e=r=>{if(!(r in Rt))throw new Error(`No option with key "${r}"`);return this.rawOptions[r]},t=(r,s)=>{if(!(r in Rt))throw new Error(`No option with key "${r}"`);s=this._sanitizeAndValidateOption(r,s),this.rawOptions[r]!==s&&(this.rawOptions[r]=s,this._onOptionChange.fire(r))};for(let r in this.rawOptions){let s={get:e.bind(this,r),set:t.bind(this,r)};Object.defineProperty(this.options,r,s)}}_sanitizeAndValidateOption(e,t){switch(e){case"cursorStyle":if(t||(t=Rt[e]),!mo(t))throw new Error(`"${t}" is not a valid value for ${e}`);break;case"wordSeparator":t||(t=Rt[e]);break;case"fontWeight":case"fontWeightBold":if(typeof t=="number"&&1<=t&&t<=1e3)break;t=po.includes(t)?t:Rt[e];break;case"blinkIntervalDuration":if(t=Math.floor(t),t<0)throw new Error(`${e} cannot be less than 0, value: ${t}`);break;case"cursorWidth":t=Math.floor(t);case"lineHeight":case"tabStopWidth":if(t<1)throw new Error(`${e} cannot be less than 1, value: ${t}`);break;case"minimumContrastRatio":t=Math.max(1,Math.min(21,Math.round(t*10)/10));break;case"scrollback":if(t=Math.min(t,4294967295),t<0)throw new Error(`${e} cannot be less than 0, value: ${t}`);break;case"fastScrollSensitivity":case"scrollSensitivity":if(t<=0)throw new Error(`${e} cannot be less than or equal to 0, value: ${t}`);break;case"rows":case"cols":if(!t&&t!==0)throw new Error(`${e} must be numeric, value: ${t}`);break;case"windowsPty":t=t??{};break}return t}};function mo(n){return n==="block"||n==="underline"||n==="bar"}var mn=Object.freeze({insertMode:!1}),bn=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,colorSchemeUpdates:!1,cursorBlink:void 0,cursorStyle:void 0,origin:!1,reverseWraparound:!1,sendFocus:!1,synchronizedOutput:!1,win32InputMode:!1,wraparound:!0}),vn=()=>({flags:0,mainFlags:0,altFlags:0,mainStack:[],altStack:[]}),Lt=class extends g{constructor(e,t,r){super();this._bufferService=e;this._logService=t;this._optionsService=r;this.isCursorHidden=!1;this._onData=this._register(new b);this.onData=this._onData.event;this._onUserInput=this._register(new b);this.onUserInput=this._onUserInput.event;this._onBinary=this._register(new b);this.onBinary=this._onBinary.event;this._onRequestScrollToBottom=this._register(new b);this.onRequestScrollToBottom=this._onRequestScrollToBottom.event;this.isCursorInitialized=r.rawOptions.showCursorImmediately??!1,this.modes=structuredClone(mn),this.decPrivateModes=structuredClone(bn),this.kittyKeyboard=vn()}reset(){this.modes=structuredClone(mn),this.decPrivateModes=structuredClone(bn),this.kittyKeyboard=vn()}triggerDataEvent(e,t=!1){if(this._optionsService.rawOptions.disableStdin)return;let r=this._bufferService.buffer;t&&this._optionsService.rawOptions.scrollOnUserInput&&r.ybase!==r.ydisp&&this._onRequestScrollToBottom.fire(),t&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`),this._logService.trace("sending data (codes)",()=>e.split("").map(s=>s.charCodeAt(0))),this._onData.fire(e)}triggerBinaryEvent(e){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${e}"`),this._logService.trace("sending binary (codes)",()=>e.split("").map(t=>t.charCodeAt(0))),this._onBinary.fire(e))}};Lt=y([m(0,D),m(1,fe),m(2,R)],Lt);var Sn={NONE:{events:0,restrict:()=>!1},X10:{events:1,restrict:n=>n.button===4||n.action!==1?!1:(n.ctrl=!1,n.alt=!1,n.shift=!1,!0)},VT200:{events:19,restrict:n=>n.action!==32},DRAG:{events:23,restrict:n=>!(n.action===32&&n.button===3)},ANY:{events:31,restrict:n=>!0}};function vs(n,i){let e=(n.ctrl?16:0)|(n.shift?4:0)|(n.alt?8:0);return n.button===4?(e|=64,e|=n.action):(e|=n.button&3,n.button&4&&(e|=64),n.button&8&&(e|=128),n.action===32?e|=32:n.action===0&&!i&&(e|=3)),e}var Ss=String.fromCharCode,gn={DEFAULT:n=>{let i=[vs(n,!1)+32,n.col+32,n.row+32];return i[0]>255||i[1]>255||i[2]>255?"":`\x1B[M${Ss(i[0])}${Ss(i[1])}${Ss(i[2])}`},SGR:n=>{let i=n.action===0&&n.button!==4?"m":"M";return`\x1B[<${vs(n,!0)};${n.col};${n.row}${i}`},SGR_PIXELS:n=>{let i=n.action===0&&n.button!==4?"m":"M";return`\x1B[<${vs(n,!0)};${n.x};${n.y}${i}`}},or=class extends g{constructor(){super();this._protocols={};this._encodings={};this._activeProtocol="";this._activeEncoding="";this._onProtocolChange=this._register(new b);this.onProtocolChange=this._onProtocolChange.event;for(let e of Object.keys(Sn))this.addProtocol(e,Sn[e]);for(let e of Object.keys(gn))this.addEncoding(e,gn[e]);this.reset()}addProtocol(e,t){this._protocols[e]=t}addEncoding(e,t){this._encodings[e]=t}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return this._protocols[this._activeProtocol].events!==0}set activeProtocol(e){if(!this._protocols[e])throw new Error(`unknown protocol "${e}"`);this._activeProtocol=e,this._onProtocolChange.fire(this._protocols[e].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(e){if(!this._encodings[e])throw new Error(`unknown encoding "${e}"`);this._activeEncoding=e}reset(){this.activeProtocol="NONE",this.activeEncoding="DEFAULT"}setCustomWheelEventHandler(e){this._customWheelEventHandler=e}allowCustomWheelEvent(e){return this._customWheelEventHandler?this._customWheelEventHandler(e)!==!1:!0}restrictMouseEvent(e){return this._protocols[this._activeProtocol].restrict(e)}encodeMouseEvent(e){return this._encodings[this._activeEncoding](e)}get isDefaultEncoding(){return this._activeEncoding==="DEFAULT"}get isPixelEncoding(){return this._activeEncoding==="SGR_PIXELS"}};var me=class n{constructor(){this._providers=Object.create(null);this._active="";this._onChange=new b;this.onChange=this._onChange.event}static extractShouldJoin(i){return(i&1)!==0}static extractWidth(i){return i>>1&3}static extractCharKind(i){return i>>3}static createPropertyValue(i,e,t=!1){return(i&16777215)<<3|(e&3)<<1|(t?1:0)}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(i){if(!this._providers[i])throw new Error(`unknown Unicode version "${i}"`);this._active=i,this._activeProvider=this._providers[i],this._onChange.fire(i)}register(i){this._providers[i.version]=i,this._active||(this.activeVersion=i.version)}wcwidth(i){return this._activeProvider.wcwidth(i)}getStringCellWidth(i){let e=0,t=0,r=i.length;for(let s=0;s=r)return e+this.wcwidth(o);let h=i.charCodeAt(s);56320<=h&&h<=57343?o=(o-55296)*1024+h-56320+65536:e+=this.wcwidth(h)}let a=this.charProperties(o,t),l=n.extractWidth(a);n.extractShouldJoin(a)&&(l-=n.extractWidth(t)),e+=l,t=a}return e}charProperties(i,e){return this._activeProvider.charProperties(i,e)}};var gs=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],bo=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]],X;function vo(n,i){let e=0,t=i.length-1,r;if(ni[t][1])return!1;for(;t>=e;)if(r=e+t>>1,n>i[r][1])e=r+1;else if(n=131072&&i<=196605||i>=196608&&i<=262141?2:1}charProperties(i,e){let t=this.wcwidth(i),r=t===0&&e!==0;if(r){let s=me.extractWidth(e);s===0?r=!1:s>t&&(t=s)}return me.createPropertyValue(0,t,r)}};var lr=class{constructor(){this.glevel=0;this._charsets=[]}get charsets(){return this._charsets}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(i){this.glevel=i,this.charset=this._charsets[i]}setgCharset(i,e){this._charsets[i]=e,this.glevel===i&&(this.charset=e)}};function Is(n){let e=n.buffer.lines.get(n.buffer.ybase+n.buffer.y-1)?.get(n.cols-1),t=n.buffer.lines.get(n.buffer.ybase+n.buffer.y);t&&e&&(t.isWrapped=e[3]!==0&&e[3]!==32)}var At=class n{constructor(i=32,e=32){this.maxLength=i;this.maxSubParamsLength=e;if(e>256)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(i),this.length=0,this._subParams=new Int32Array(e),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(i),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}static fromArray(i){let e=new n;if(!i.length)return e;for(let t=Array.isArray(i[0])?1:0;t>8,r=this._subParamsIdx[e]&255;r-t>0&&i.push(Array.prototype.slice.call(this._subParams,t,r))}return i}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}resetZdm(){this.length=1,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1,this._subParamsIdx[0]=0,this.params[0]=0}addParam(i){if(this._digitIsSub=!1,this.length>=this.maxLength){this._rejectDigits=!0;return}if(i<-1)throw new Error("values less than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=i>2147483647?2147483647:i}addSubParam(i){if(this._digitIsSub=!0,!!this.length){if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength){this._rejectSubDigits=!0;return}if(i<-1)throw new Error("values less than -1 are not allowed");this._subParams[this._subParamsLength++]=i>2147483647?2147483647:i,this._subParamsIdx[this.length-1]++}}hasSubParams(i){return(this._subParamsIdx[i]&255)-(this._subParamsIdx[i]>>8)>0}getSubParams(i){let e=this._subParamsIdx[i]>>8,t=this._subParamsIdx[i]&255;return t-e>0?this._subParams.subarray(e,t):null}getSubParamsAll(){let i={};for(let e=0;e>8,r=this._subParamsIdx[e]&255;r-t>0&&(i[e]=this._subParams.slice(t,r))}return i}addDigit(i){let e;if(this._rejectDigits||!(e=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;let t=this._digitIsSub?this._subParams:this.params,r=t[e-1];t[e-1]=~r?Math.min(r*10+i,2147483647):i}};var ni=[],cr=class{constructor(){this._state=0;this._active=ni;this._id=-1;this._handlers=Object.create(null);this._handlerFb=()=>{};this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(i,e){this._handlers[i]??=[];let t=this._handlers[i];return t.push(e),{dispose:()=>{let r=t.indexOf(e);r!==-1&&t.splice(r,1)}}}clearHandler(i){this._handlers[i]&&delete this._handlers[i]}setHandlerFallback(i){this._handlerFb=i}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=ni}reset(){if(this._state===2)for(let i=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;i>=0;--i)this._active[i].end(!1);this._stack.paused=!1,this._active=ni,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||ni,!this._active.length)this._handlerFb(this._id,"START");else for(let i=this._active.length-1;i>=0;i--)this._active[i].start()}_put(i,e,t){if(!this._active.length)this._handlerFb(this._id,"PUT",ye(i,e,t));else for(let r=this._active.length-1;r>=0;r--)this._active[r].put(i,e,t)}start(){this.reset(),this._state=1}put(i,e,t){if(this._state!==3){if(this._state===1)for(;e0&&this._put(i,e,t)}}end(i,e=!0){if(this._state!==0){if(this._state!==3)if(this._state===1&&this._start(),!this._active.length)this._handlerFb(this._id,"END",i);else{let t=!1,r=this._active.length-1,s=!1;if(this._stack.paused&&(r=this._stack.loopPosition-1,t=e,s=this._stack.fallThrough,this._stack.paused=!1),!s&&t===!1){for(;r>=0&&(t=this._active[r].end(i),t!==!0);r--)if(t instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!1,t;r--}for(;r>=0;r--)if(t=this._active[r].end(!1),t instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!0,t}this._active=ni,this._id=-1,this._state=0}}},hr=class hr{constructor(i){this._handler=i;this._data=new We(hr._payloadLimit);this._hitLimit=!1}start(){this._data.reset(),this._hitLimit=!1}put(i,e,t){this._hitLimit||this._data.append(ye(i,e,t))&&(this._hitLimit=!0)}end(i){let e=!1;if(this._hitLimit)e=!1;else if(i&&(e=this._handler(this._data.toString()),e instanceof Promise))return e.then(t=>(this._data.reset(),this._hitLimit=!1,t));return this._data.reset(),this._hitLimit=!1,e}};hr._payloadLimit=1e7;var ne=hr;var oi=[],dr=class{constructor(){this._handlers=Object.create(null);this._active=oi;this._ident=0;this._handlerFb=()=>{};this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=oi}registerHandler(i,e){this._handlers[i]??=[];let t=this._handlers[i];return t.push(e),{dispose:()=>{let r=t.indexOf(e);r!==-1&&t.splice(r,1)}}}clearHandler(i){this._handlers[i]&&delete this._handlers[i]}setHandlerFallback(i){this._handlerFb=i}reset(){if(this._active.length)for(let i=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;i>=0;--i)this._active[i].unhook(!1);this._stack.paused=!1,this._active=oi,this._ident=0}hook(i,e){if(this.reset(),this._ident=i,this._active=this._handlers[i]||oi,!this._active.length)this._handlerFb(this._ident,"HOOK",e);else for(let t=this._active.length-1;t>=0;t--)this._active[t].hook(e)}put(i,e,t){if(!this._active.length)this._handlerFb(this._ident,"PUT",ye(i,e,t));else for(let r=this._active.length-1;r>=0;r--)this._active[r].put(i,e,t)}unhook(i,e=!0){if(!this._active.length)this._handlerFb(this._ident,"UNHOOK",i);else{let t=!1,r=this._active.length-1,s=!1;if(this._stack.paused&&(r=this._stack.loopPosition-1,t=e,s=this._stack.fallThrough,this._stack.paused=!1),!s&&t===!1){for(;r>=0&&(t=this._active[r].unhook(i),t!==!0);r--)if(t instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!1,t;r--}for(;r>=0;r--)if(t=this._active[r].unhook(!1),t instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!0,t}this._active=oi,this._ident=0}},ai=new At;ai.addParam(0);var ur=class ur{constructor(i){this._handler=i;this._data=new We(ur._payloadLimit);this._params=ai;this._hitLimit=!1}hook(i){this._params=i.length>1||i.params[0]?i.clone():ai,this._data.reset(),this._hitLimit=!1}put(i,e,t){this._hitLimit||this._data.append(ye(i,e,t))&&(this._hitLimit=!0)}unhook(i){let e=!1;if(this._hitLimit)e=!1;else if(i&&(e=this._handler(this._data.toString(),this._params),e instanceof Promise))return e.then(t=>(this._params=ai,this._data.reset(),this._hitLimit=!1,t));return this._params=ai,this._data.reset(),this._hitLimit=!1,e}};ur._payloadLimit=1e7;var li=ur;var ci=[],fr=class{constructor(){this._handlers=Object.create(null);this._active=ci;this._ident=0;this._handlerFb=()=>{};this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(i,e){this._handlers[i]??=[];let t=this._handlers[i];return t.push(e),{dispose:()=>{let r=t.indexOf(e);r!==-1&&t.splice(r,1)}}}clearHandler(i){this._handlers[i]&&delete this._handlers[i]}setHandlerFallback(i){this._handlerFb=i}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=ci}reset(){if(this._active.length)for(let i=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;i>=0;--i)this._active[i].end(!1);this._stack.paused=!1,this._active=ci,this._ident=0}start(i){if(this.reset(),this._ident=i,this._active=this._handlers[i]||ci,!this._active.length)this._handlerFb(this._ident,"START");else for(let e=this._active.length-1;e>=0;e--)this._active[e].start()}put(i,e,t){if(!this._active.length)this._handlerFb(this._ident,"PUT",ye(i,e,t));else for(let r=this._active.length-1;r>=0;r--)this._active[r].put(i,e,t)}end(i,e=!0){if(!this._active.length)this._handlerFb(this._ident,"END",i);else{let t=!1,r=this._active.length-1,s=!1;if(this._stack.paused&&(r=this._stack.loopPosition-1,t=e,s=this._stack.fallThrough,this._stack.paused=!1),!s&&t===!1){for(;r>=0&&(t=this._active[r].end(i),t!==!0);r--)if(t instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!1,t;r--}for(;r>=0;r--)if(t=this._active[r].end(!1),t instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!0,t}this._active=ci,this._ident=0}},pr=class pr{constructor(i){this._handler=i;this._data=new We(pr._payloadLimit);this._hitLimit=!1}start(){this._data.reset(),this._hitLimit=!1}put(i,e,t){this._hitLimit||this._data.append(ye(i,e,t))&&(this._hitLimit=!0)}end(i){let e=!1;if(this._hitLimit)e=!1;else if(i&&(e=this._handler(this._data.toString()),e instanceof Promise))return e.then(t=>(this._data.reset(),this._hitLimit=!1,t));return this._data.reset(),this._hitLimit=!1,e}};pr._payloadLimit=1e7;var _r=pr;var Cs=class{constructor(i){this.table=new Uint16Array(i)}setDefault(i,e){this.table.fill(i<<8|e)}add(i,e,t,r){this.table[e<<8|i]=t<<8|r}addMany(i,e,t,r){for(let s=0;sl),t=(a,l)=>e.slice(a,l),r=t(32,127),s=t(0,24);s.push(25),s.push.apply(s,t(28,32));let o=t(0,17);n.setDefault(1,0),n.addMany(r,0,2,0);for(let a of o)n.addMany([24,26,153,154],a,3,0),n.addMany(t(128,144),a,3,0),n.addMany(t(144,152),a,3,0),n.add(156,a,0,0),n.add(27,a,11,1),n.add(157,a,4,8),n.addMany([152,158],a,0,7),n.add(159,a,11,14),n.add(155,a,11,3),n.add(144,a,11,9);return n.addMany(s,0,3,0),n.addMany(s,1,3,1),n.add(127,1,0,1),n.addMany(s,8,0,8),n.addMany(s,3,3,3),n.add(127,3,0,3),n.addMany(s,4,3,4),n.add(127,4,0,4),n.addMany(s,6,3,6),n.addMany(s,5,3,5),n.add(127,5,0,5),n.addMany(s,2,3,2),n.add(127,2,0,2),n.add(93,1,4,8),n.addMany(r,8,5,8),n.add(127,8,5,8),n.addMany([156,27,24,26,7],8,6,0),n.addMany(t(28,32),8,0,8),n.addMany([88,94],1,0,7),n.addMany(r,7,0,7),n.addMany(s,7,0,7),n.add(156,7,0,0),n.add(127,7,0,7),n.add(95,1,11,14),n.addMany(s,14,0,14),n.add(127,14,0,14),n.addMany(t(32,48),14,9,15),n.addMany(t(48,127),14,15,16),n.addMany(t(48,127),15,15,16),n.addMany(s,15,0,15),n.addMany(t(32,48),15,9,15),n.add(127,15,0,15),n.addMany(r,16,16,16),n.addMany(s,16,0,16),n.addMany(t(8,14),16,16,16),n.add(127,16,0,16),n.addMany([27,156,24,26],16,17,0),n.add(91,1,11,3),n.addMany(t(64,127),3,7,0),n.addMany(t(48,60),3,8,4),n.addMany([60,61,62,63],3,9,4),n.addMany(t(48,60),4,8,4),n.addMany(t(64,127),4,7,0),n.addMany([60,61,62,63],4,0,6),n.addMany(t(32,64),6,0,6),n.add(127,6,0,6),n.addMany(t(64,127),6,0,0),n.addMany(t(32,48),3,9,5),n.addMany(t(32,48),5,9,5),n.addMany(t(48,64),5,0,6),n.addMany(t(64,127),5,7,0),n.addMany(t(32,48),4,9,5),n.addMany(t(32,48),1,9,2),n.addMany(t(32,48),2,9,2),n.addMany(t(48,127),2,10,0),n.addMany(t(48,80),1,10,0),n.addMany(t(81,88),1,10,0),n.addMany([89,90,92],1,10,0),n.addMany(t(96,127),1,10,0),n.add(80,1,11,9),n.addMany(s,9,0,9),n.add(127,9,0,9),n.addMany(t(32,48),9,9,12),n.addMany(t(48,60),9,8,10),n.addMany([60,61,62,63],9,9,10),n.addMany(s,11,0,11),n.addMany(t(32,128),11,0,11),n.addMany(s,10,0,10),n.add(127,10,0,10),n.addMany(t(48,60),10,8,10),n.addMany([60,61,62,63],10,0,11),n.addMany(t(32,48),10,9,12),n.addMany(s,12,0,12),n.add(127,12,0,12),n.addMany(t(32,48),12,9,12),n.addMany(t(48,64),12,0,11),n.addMany(t(64,127),12,12,13),n.addMany(t(64,127),10,12,13),n.addMany(t(64,127),9,12,13),n.addMany(s,13,13,13),n.addMany(r,13,13,13),n.add(127,13,0,13),n.addMany([27,156,24,26],13,14,0),n.add(oe,0,2,0),n.add(oe,8,5,8),n.add(oe,6,0,6),n.add(oe,11,0,11),n.add(oe,13,13,13),n.add(oe,16,16,16),n})(),mr=class extends g{constructor(e=So){super();this._transitions=e;this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0};this.initialState=0,this.currentState=this.initialState,this._params=new At,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(t,r,s)=>{},this._executeHandlerFb=t=>{},this._csiHandlerFb=(t,r)=>{},this._escHandlerFb=t=>{},this._errorHandlerFb=t=>t,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._executeHandlersArr=new Array(24).fill(void 0),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this._register(E(()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._executeHandlersArr=new Array(24).fill(void 0),this._escHandlers=Object.create(null)})),this._oscParser=this._register(new cr),this._dcsParser=this._register(new dr),this._apcParser=this._register(new fr),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:"\\"},()=>!0)}_identifier(e,t=[64,126]){let r=0;if(e.prefix){if(e.prefix.length>1)throw new Error("only one byte as prefix supported");if(r=e.prefix.charCodeAt(0),r<60||r>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(e.intermediates){if(e.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(let o=0;oa||a>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");r<<=8,r|=a}}if(e.final.length!==1)throw new Error("final must be a single byte");let s=e.final.charCodeAt(0);if(t[0]>s||s>t[1])throw new Error(`final must be in range ${t[0]} .. ${t[1]}`);return r<<=8,r|=s,r}identToString(e){let t=[];for(;e;)t.push(String.fromCharCode(e&255)),e>>=8;return t.reverse().join("")}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,t){let r=this._identifier(e,[48,126]);this._escHandlers[r]??=[];let s=this._escHandlers[r];return s.push(t),{dispose:()=>{let o=s.indexOf(t);o!==-1&&s.splice(o,1)}}}clearEscHandler(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]}setEscHandlerFallback(e){this._escHandlerFb=e}setExecuteHandler(e,t){let r=e.charCodeAt(0);this._executeHandlers[r]=t,r<24&&(this._executeHandlersArr[r]=t)}clearExecuteHandler(e){let t=e.charCodeAt(0);this._executeHandlers[t]&&delete this._executeHandlers[t],t<24&&(this._executeHandlersArr[t]=void 0)}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,t){let r=this._identifier(e);this._csiHandlers[r]??=[];let s=this._csiHandlers[r];return s.push(t),{dispose:()=>{let o=s.indexOf(t);o!==-1&&s.splice(o,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,t){return this._dcsParser.registerHandler(this._identifier(e),t)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,t){return this._oscParser.registerHandler(e,t)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}registerApcHandler(e,t){return e.prefix=void 0,this._apcParser.registerHandler(this._identifier(e,[48,126]),t)}clearApcHandler(e){e.prefix=void 0,this._apcParser.clearHandler(this._identifier(e,[48,126]))}setApcHandlerFallback(e){this._apcParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._apcParser.reset(),this._params.resetZdm(),this._collect=0,this.precedingJoinState=0,this._parseStack.state!==0&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(e,t,r,s,o){this._parseStack.state=e,this._parseStack.handlers=t,this._parseStack.handlerPos=r,this._parseStack.transition=s,this._parseStack.chunkPos=o}parse(e,t,r){let s,o,a=0,l;if(this._parseStack.state)if(this._parseStack.state===2)this._parseStack.state=0,a=this._parseStack.chunkPos+1;else{if(r===void 0||this._parseStack.state===1)throw this._parseStack.state=1,new Error("improper continuation due to previous async handler, giving up parsing");let h=this._parseStack.handlers,d=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(r===!1&&d>-1){for(;d>=0&&(l=h[d](this._params),l!==!0);d--)if(l instanceof Promise)return this._parseStack.handlerPos=d,l}this._parseStack.handlers=[];break;case 4:if(r===!1&&d>-1){for(;d>=0&&(l=h[d](),l!==!0);d--)if(l instanceof Promise)return this._parseStack.handlerPos=d,l}this._parseStack.handlers=[];break;case 6:if(s=e[this._parseStack.chunkPos],l=this._dcsParser.unhook(s!==24&&s!==26,r),l)return l;s===27&&(this._parseStack.transition|=1),this._params.resetZdm(),this._collect=0;break;case 5:if(s=e[this._parseStack.chunkPos],l=this._oscParser.end(s!==24&&s!==26,r),l)return l;s===27&&(this._parseStack.transition|=1),this._params.resetZdm(),this._collect=0;break;case 7:if(s=e[this._parseStack.chunkPos],l=this._apcParser.end(s!==24&&s!==26,r),l)return l;s===27&&(this._parseStack.transition|=1),this._params.resetZdm(),this._collect=0;break}this._parseStack.state=0,a=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=this._parseStack.transition&255}for(let h=a;h=60&&c<=63&&(this._collect=c,d++);let u=!1;for(;d=48&&c<=57)this._params.addDigit(c-48);else if(c===59)this._params.addParam(0);else if(c===58)this._params.addSubParam(-1);else if(c>=64&&c<=126){let _=this._csiHandlers[this._collect<<8|c],p=_?_.length-1:-1;for(;p>=0&&(l=_[p](this._params),l!==!0);p--)if(l instanceof Promise)return o=1792,this._preserveStack(3,_,p,o,d),l;p<0&&this._csiHandlerFb(this._collect<<8|c,this._params),this.precedingJoinState=0,h=d,this.currentState=0,u=!0;break}else break;u||(h=d-1,this.currentState=4);continue}switch(o=this._transitions.table[this.currentState<<8|(s>8){case 2:let d=h,c=t-4;for(;d=32&&(e[d]<=126||e[d]>=oe)&&e[++d]>=32&&(e[d]<=126||e[d]>=oe)&&e[++d]>=32&&(e[d]<=126||e[d]>=oe)&&e[++d]>=32&&(e[d]<=126||e[d]>=oe););if(d>=c)for(;d=32&&(e[d]<=126||e[d]>=oe);)d++;this._printHandler(e,h,d),h=d-1;break;case 3:this._executeHandlers[s]?this._executeHandlers[s]():this._executeHandlerFb(s),this.precedingJoinState=0;break;case 0:break;case 1:if(this._errorHandler({position:h,code:s,currentState:this.currentState,collect:this._collect,params:this._params,abort:!1}).abort)return;break;case 7:let _=this._csiHandlers[this._collect<<8|s],p=_?_.length-1:-1;for(;p>=0&&(l=_[p](this._params),l!==!0);p--)if(l instanceof Promise)return this._preserveStack(3,_,p,o,h),l;p<0&&this._csiHandlerFb(this._collect<<8|s,this._params),this.precedingJoinState=0;break;case 8:do switch(s){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(s-48)}while(++h47&&s<60);h--;break;case 9:this._collect<<=8,this._collect|=s;break;case 10:let v=this._escHandlers[this._collect<<8|s],f=v?v.length-1:-1;for(;f>=0&&(l=v[f](),l!==!0);f--)if(l instanceof Promise)return this._preserveStack(4,v,f,o,h),l;f<0&&this._escHandlerFb(this._collect<<8|s),this.precedingJoinState=0;break;case 11:this._params.resetZdm(),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|s,this._params);break;case 13:for(let S=h+1;;++S)if(S>=t||(s=e[S])===24||s===26||s===27||s>127&&s=t||(s=e[S])<32||s>127&&s=32&&e[S]<127||e[S]>=8&&e[S]<14||e[S]>=oe))){this._apcParser.put(e,h,S),h=S-1;break}break;case 17:if(l=this._apcParser.end(s!==24&&s!==26),l)return this._preserveStack(7,[],0,o,h),l;s===27&&(o|=1),this._params.resetZdm(),this._collect=0,this.precedingJoinState=0;break}this.currentState=o&255}}};var go=/^([\da-f])\/([\da-f])\/([\da-f])$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/,Io=/^[\da-f]+$/;function ys(n){if(!n)return;let i=n.toLowerCase();if(i.startsWith("rgb:")){i=i.slice(4);let e=go.exec(i);if(e){let t=e[1]?15:e[4]?255:e[7]?4095:65535;return[Math.round(parseInt(e[1]||e[4]||e[7]||e[10],16)/t*255),Math.round(parseInt(e[2]||e[5]||e[8]||e[11],16)/t*255),Math.round(parseInt(e[3]||e[6]||e[9]||e[12],16)/t*255)]}}else if(i.startsWith("#")&&(i=i.slice(1),Io.exec(i)&&[3,6,9,12].includes(i.length))){let e=i.length/3,t=[0,0,0];for(let r=0;r<3;++r){let s=parseInt(i.slice(e*r,e*r+e),16);t[r]=e===1?s<<4:e===2?s:e===3?s>>4:s>>8}return t}}function Es(n,i){let e=n.toString(16),t=e.length<2?"0"+e:e;switch(i){case 4:return e[0];case 8:return t;case 12:return(t+t).slice(0,3);default:return t+t}}function En(n,i=16){let[e,t,r]=n;return`rgb:${Es(e,i)}/${Es(t,i)}/${Es(r,i)}`}var yn="6.1.0-beta.287";var Eo={"(":0,")":1,"*":2,"+":3,"-":1,".":2};function xn(n,i){if(n>24)return i.setWinLines||!1;switch(n){case 1:return!!i.restoreWin;case 2:return!!i.minimizeWin;case 3:return!!i.setWinPosition;case 4:return!!i.setWinSizePixels;case 5:return!!i.raiseWin;case 6:return!!i.lowerWin;case 7:return!!i.refreshWin;case 8:return!!i.setWinSizeChars;case 9:return!!i.maximizeWin;case 10:return!!i.fullscreenWin;case 11:return!!i.getWinState;case 13:return!!i.getWinPosition;case 14:return!!i.getWinSizePixels;case 15:return!!i.getScreenSizePixels;case 16:return!!i.getCellSizePixels;case 18:return!!i.getWinSizeChars;case 19:return!!i.getScreenSizeChars;case 20:return!!i.getIconTitle;case 21:return!!i.getWinTitle;case 22:return!!i.pushTitle;case 23:return!!i.popTitle;case 24:return!!i.setWinLines}return!1}var wn=0,br=class extends g{constructor(e,t,r,s,o,a,l,h,d=new mr){super();this._bufferService=e;this._charsetService=t;this._coreService=r;this._logService=s;this._optionsService=o;this._oscLinkService=a;this._mouseStateService=l;this._unicodeService=h;this._parser=d;this._parseBuffer=new Uint32Array(4096);this._stringDecoder=new mi;this._utf8Decoder=new bi;this._windowTitle="";this._iconName="";this._windowTitleStack=[];this._iconNameStack=[];this._curAttrData=U.clone();this._eraseAttrDataInternal=U.clone();this._onRequestBell=this._register(new b);this.onRequestBell=this._onRequestBell.event;this._onRequestRefreshRows=this._register(new b);this.onRequestRefreshRows=this._onRequestRefreshRows.event;this._onRequestReset=this._register(new b);this.onRequestReset=this._onRequestReset.event;this._onRequestSendFocus=this._register(new b);this.onRequestSendFocus=this._onRequestSendFocus.event;this._onRequestSyncScrollBar=this._register(new b);this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event;this._onRequestWindowsOptionsReport=this._register(new b);this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event;this._onA11yChar=this._register(new b);this.onA11yChar=this._onA11yChar.event;this._onA11yTab=this._register(new b);this.onA11yTab=this._onA11yTab.event;this._onCursorMove=this._register(new b);this.onCursorMove=this._onCursorMove.event;this._onLineFeed=this._register(new b);this.onLineFeed=this._onLineFeed.event;this._onScroll=this._register(new b);this.onScroll=this._onScroll.event;this._onTitleChange=this._register(new b);this.onTitleChange=this._onTitleChange.event;this._onColor=this._register(new b);this.onColor=this._onColor.event;this._onRequestColorSchemeQuery=this._register(new b);this.onRequestColorSchemeQuery=this._onRequestColorSchemeQuery.event;this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0};this._specialColors=[256,257,258];this._register(this._parser),this._dirtyRowTracker=new hi(this._bufferService),this._activeBuffer=this._bufferService.buffer,this._register(this._bufferService.buffers.onBufferActivate(c=>this._activeBuffer=c.activeBuffer)),this._parser.setCsiHandlerFallback((c,u)=>{this._logService.debug("Unknown CSI code: ",{identifier:this._parser.identToString(c),params:u.toArray()})}),this._parser.setEscHandlerFallback(c=>{this._logService.debug("Unknown ESC code: ",{identifier:this._parser.identToString(c)})}),this._parser.setExecuteHandlerFallback(c=>{this._logService.debug("Unknown EXECUTE code: ",{code:c})}),this._parser.setOscHandlerFallback((c,u,_)=>{this._logService.debug("Unknown OSC code: ",{identifier:c,action:u,data:_})}),this._parser.setDcsHandlerFallback((c,u,_)=>{u==="HOOK"&&(_=_.toArray()),this._logService.debug("Unknown DCS code: ",{identifier:this._parser.identToString(c),action:u,payload:_})}),this._parser.setApcHandlerFallback((c,u,_)=>{this._logService.debug("Unknown APC code: ",{identifier:this._parser.identToString(c),action:u,payload:_})}),this._parser.setPrintHandler((c,u,_)=>this.print(c,u,_)),this._parser.registerCsiHandler({final:"@"},c=>this.insertChars(c)),this._parser.registerCsiHandler({intermediates:" ",final:"@"},c=>this.scrollLeft(c)),this._parser.registerCsiHandler({final:"A"},c=>this.cursorUp(c)),this._parser.registerCsiHandler({intermediates:" ",final:"A"},c=>this.scrollRight(c)),this._parser.registerCsiHandler({final:"B"},c=>this.cursorDown(c)),this._parser.registerCsiHandler({final:"C"},c=>this.cursorForward(c)),this._parser.registerCsiHandler({final:"D"},c=>this.cursorBackward(c)),this._parser.registerCsiHandler({final:"E"},c=>this.cursorNextLine(c)),this._parser.registerCsiHandler({final:"F"},c=>this.cursorPrecedingLine(c)),this._parser.registerCsiHandler({final:"G"},c=>this.cursorCharAbsolute(c)),this._parser.registerCsiHandler({final:"H"},c=>this.cursorPosition(c)),this._parser.registerCsiHandler({final:"I"},c=>this.cursorForwardTab(c)),this._parser.registerCsiHandler({final:"J"},c=>this.eraseInDisplay(c,!1)),this._parser.registerCsiHandler({prefix:"?",final:"J"},c=>this.eraseInDisplay(c,!0)),this._parser.registerCsiHandler({final:"K"},c=>this.eraseInLine(c,!1)),this._parser.registerCsiHandler({prefix:"?",final:"K"},c=>this.eraseInLine(c,!0)),this._parser.registerCsiHandler({final:"L"},c=>this.insertLines(c)),this._parser.registerCsiHandler({final:"M"},c=>this.deleteLines(c)),this._parser.registerCsiHandler({final:"P"},c=>this.deleteChars(c)),this._parser.registerCsiHandler({final:"S"},c=>this.scrollUp(c)),this._parser.registerCsiHandler({final:"T"},c=>this.scrollDown(c)),this._parser.registerCsiHandler({final:"X"},c=>this.eraseChars(c)),this._parser.registerCsiHandler({final:"Z"},c=>this.cursorBackwardTab(c)),this._parser.registerCsiHandler({final:"^"},c=>this.scrollDown(c)),this._parser.registerCsiHandler({final:"`"},c=>this.charPosAbsolute(c)),this._parser.registerCsiHandler({final:"a"},c=>this.hPositionRelative(c)),this._parser.registerCsiHandler({final:"b"},c=>this.repeatPrecedingCharacter(c)),this._parser.registerCsiHandler({final:"c"},c=>this.sendDeviceAttributesPrimary(c)),this._parser.registerCsiHandler({prefix:">",final:"c"},c=>this.sendDeviceAttributesSecondary(c)),this._parser.registerCsiHandler({final:"d"},c=>this.linePosAbsolute(c)),this._parser.registerCsiHandler({final:"e"},c=>this.vPositionRelative(c)),this._parser.registerCsiHandler({final:"f"},c=>this.hVPosition(c)),this._parser.registerCsiHandler({final:"g"},c=>this.tabClear(c)),this._parser.registerCsiHandler({final:"h"},c=>this.setMode(c)),this._parser.registerCsiHandler({prefix:"?",final:"h"},c=>this.setModePrivate(c)),this._parser.registerCsiHandler({final:"l"},c=>this.resetMode(c)),this._parser.registerCsiHandler({prefix:"?",final:"l"},c=>this.resetModePrivate(c)),this._parser.registerCsiHandler({final:"m"},c=>this.charAttributes(c)),this._parser.registerCsiHandler({final:"n"},c=>this.deviceStatus(c)),this._parser.registerCsiHandler({prefix:"?",final:"n"},c=>this.deviceStatusPrivate(c)),this._parser.registerCsiHandler({intermediates:"!",final:"p"},c=>this.softReset(c)),this._parser.registerCsiHandler({prefix:">",final:"q"},c=>this.sendXtVersion(c)),this._parser.registerCsiHandler({intermediates:" ",final:"q"},c=>this.setCursorStyle(c)),this._parser.registerCsiHandler({final:"r"},c=>this.setScrollRegion(c)),this._parser.registerCsiHandler({final:"s"},c=>this.saveCursor(c)),this._parser.registerCsiHandler({final:"t"},c=>this.windowOptions(c)),this._parser.registerCsiHandler({final:"u"},c=>this.restoreCursor(c)),this._parser.registerCsiHandler({intermediates:"'",final:"}"},c=>this.insertColumns(c)),this._parser.registerCsiHandler({intermediates:"'",final:"~"},c=>this.deleteColumns(c)),this._parser.registerCsiHandler({intermediates:'"',final:"q"},c=>this.selectProtected(c)),this._parser.registerCsiHandler({intermediates:"$",final:"p"},c=>this.requestMode(c,!0)),this._parser.registerCsiHandler({prefix:"?",intermediates:"$",final:"p"},c=>this.requestMode(c,!1)),this._parser.registerCsiHandler({prefix:"=",final:"u"},c=>this.kittyKeyboardSet(c)),this._parser.registerCsiHandler({prefix:"?",final:"u"},c=>this.kittyKeyboardQuery(c)),this._parser.registerCsiHandler({prefix:">",final:"u"},c=>this.kittyKeyboardPush(c)),this._parser.registerCsiHandler({prefix:"<",final:"u"},c=>this.kittyKeyboardPop(c)),this._parser.setExecuteHandler("\x07",()=>this.bell()),this._parser.setExecuteHandler(` + `,()=>this.lineFeed()),this._parser.setExecuteHandler("\v",()=>this.lineFeed()),this._parser.setExecuteHandler("\f",()=>this.lineFeed()),this._parser.setExecuteHandler("\r",()=>this.carriageReturn()),this._parser.setExecuteHandler("\b",()=>this.backspace()),this._parser.setExecuteHandler(" ",()=>this.tab()),this._parser.setExecuteHandler("",()=>this.shiftOut()),this._parser.setExecuteHandler("",()=>this.shiftIn()),this._parser.setExecuteHandler("\x84",()=>this.index()),this._parser.setExecuteHandler("\x85",()=>this.nextLine()),this._parser.setExecuteHandler("\x88",()=>this.tabSet()),this._parser.registerOscHandler(0,new ne(c=>(this.setTitle(c),this.setIconName(c),!0))),this._parser.registerOscHandler(1,new ne(c=>this.setIconName(c))),this._parser.registerOscHandler(2,new ne(c=>this.setTitle(c))),this._parser.registerOscHandler(4,new ne(c=>this.setOrReportIndexedColor(c))),this._parser.registerOscHandler(8,new ne(c=>this.setHyperlink(c))),this._parser.registerOscHandler(10,new ne(c=>this.setOrReportFgColor(c))),this._parser.registerOscHandler(11,new ne(c=>this.setOrReportBgColor(c))),this._parser.registerOscHandler(12,new ne(c=>this.setOrReportCursorColor(c))),this._parser.registerOscHandler(104,new ne(c=>this.restoreIndexedColor(c))),this._parser.registerOscHandler(110,new ne(c=>this.restoreFgColor(c))),this._parser.registerOscHandler(111,new ne(c=>this.restoreBgColor(c))),this._parser.registerOscHandler(112,new ne(c=>this.restoreCursorColor(c))),this._parser.registerEscHandler({final:"7"},()=>this.saveCursor()),this._parser.registerEscHandler({final:"8"},()=>this.restoreCursor()),this._parser.registerEscHandler({final:"D"},()=>this.index()),this._parser.registerEscHandler({final:"E"},()=>this.nextLine()),this._parser.registerEscHandler({final:"H"},()=>this.tabSet()),this._parser.registerEscHandler({final:"M"},()=>this.reverseIndex()),this._parser.registerEscHandler({final:"="},()=>this.keypadApplicationMode()),this._parser.registerEscHandler({final:">"},()=>this.keypadNumericMode()),this._parser.registerEscHandler({final:"c"},()=>this.fullReset()),this._parser.registerEscHandler({final:"n"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"o"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"|"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"}"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"~"},()=>this.setgLevel(1)),this._parser.registerEscHandler({intermediates:"%",final:"@"},()=>this.selectDefaultCharset()),this._parser.registerEscHandler({intermediates:"%",final:"G"},()=>this.selectDefaultCharset());for(let c in q)this._parser.registerEscHandler({intermediates:"(",final:c},()=>this.selectCharset("("+c)),this._parser.registerEscHandler({intermediates:")",final:c},()=>this.selectCharset(")"+c)),this._parser.registerEscHandler({intermediates:"*",final:c},()=>this.selectCharset("*"+c)),this._parser.registerEscHandler({intermediates:"+",final:c},()=>this.selectCharset("+"+c)),this._parser.registerEscHandler({intermediates:"-",final:c},()=>this.selectCharset("-"+c)),this._parser.registerEscHandler({intermediates:".",final:c},()=>this.selectCharset("."+c)),this._parser.registerEscHandler({intermediates:"/",final:c},()=>this.selectCharset("/"+c));this._parser.registerEscHandler({intermediates:"#",final:"8"},()=>this.screenAlignmentPattern()),this._parser.setErrorHandler(c=>(this._logService.error("Parsing error: ",c),c)),this._parser.registerDcsHandler({intermediates:"$",final:"q"},new li((c,u)=>this.requestStatusString(c,u)))}getAttrData(){return this._curAttrData}_preserveStack(e,t,r,s){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=t,this._parseStack.decodedLength=r,this._parseStack.position=s}_logSlowResolvingAsync(e){if(this._logService.logLevel<=3){let t,r=new Promise((s,o)=>{t=setTimeout(()=>o("#SLOW_TIMEOUT"),5e3)});Promise.race([e,r]).then(()=>{t!==void 0&&clearTimeout(t)},s=>{if(t!==void 0&&clearTimeout(t),s!=="#SLOW_TIMEOUT")throw s;console.warn("async parser handler taking longer than 5000 ms")})}}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,t){let r,s=this._activeBuffer.x,o=this._activeBuffer.y,a=0,l=this._parseStack.paused;if(l){if(r=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,t))return this._logSlowResolvingAsync(r),r;s=this._parseStack.cursorStartX,o=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>131072&&(a=this._parseStack.position+131072)}if(this._logService.logLevel<=1&&this._logService.debug(`parsing data ${typeof e=="string"?` "${e}"`:` "${Array.prototype.map.call(e,c=>String.fromCharCode(c)).join("")}"`}`),this._logService.logLevel===0&&this._logService.trace("parsing data (codes)",typeof e=="string"?e.split("").map(c=>c.charCodeAt(0)):e),this._parseBuffer.length131072)for(let c=a;c0&&_.getWidth(this._activeBuffer.x-1)===2&&_.setCellFromCodepoint(this._activeBuffer.x-1,0,1,u);let p=this._parser.precedingJoinState;for(let v=t;vh){if(d){let L=_,T=this._activeBuffer.x-I;if(this._activeBuffer.x=I,this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData(),!0)):(this._activeBuffer.y>=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),_=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),!_)return;for(I>0&&_ instanceof De&&_.copyCellsFrom(L,T,0,I,!1);T=0;)_.setCellFromCodepoint(this._activeBuffer.x++,0,0,u);continue}if(c&&(_.insertCells(this._activeBuffer.x,o-I,this._activeBuffer.getNullCell(u)),_.getWidth(h-1)===2&&_.setCellFromCodepoint(h-1,0,1,u)),_.setCellFromCodepoint(this._activeBuffer.x++,s,o,u),o>0)for(;--o;)_.setCellFromCodepoint(this._activeBuffer.x++,0,0,u)}this._parser.precedingJoinState=p,this._activeBuffer.x0&&_.getWidth(this._activeBuffer.x)===0&&!_.hasContent(this._activeBuffer.x)&&_.setCellFromCodepoint(this._activeBuffer.x,0,1,u),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,t){return e.final==="t"&&!e.prefix&&!e.intermediates?this._parser.registerCsiHandler(e,r=>xn(r.params[0],this._optionsService.rawOptions.windowOptions)?t(r):!0):this._parser.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._parser.registerDcsHandler(e,new li(t))}registerEscHandler(e,t){return this._parser.registerEscHandler(e,t)}registerOscHandler(e,t){return this._parser.registerOscHandler(e,new ne(t))}registerApcHandler(e,t){return this._parser.registerApcHandler(e,new _r(t))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(this._activeBuffer.x===0&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y)?.isWrapped){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;let e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);e.hasWidth(this._activeBuffer.x)&&!e.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let e=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-e),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(e=this._bufferService.cols-1){this._activeBuffer.x=Math.min(e,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(e,t){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+t):(this._activeBuffer.x=e,this._activeBuffer.y=t),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,t){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+t)}cursorUp(e){let t=this._activeBuffer.y-this._activeBuffer.scrollTop;return t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){let t=this._activeBuffer.scrollBottom-this._activeBuffer.y;return t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1),!0}cursorForward(e){return this._moveCursor(e.params[0]||1,0),!0}cursorBackward(e){return this._moveCursor(-(e.params[0]||1),0),!0}cursorNextLine(e){return this.cursorDown(e),this._activeBuffer.x=0,!0}cursorPrecedingLine(e){return this.cursorUp(e),this._activeBuffer.x=0,!0}cursorCharAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(e){return this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1),!0}charPosAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(e){return this._moveCursor(e.params[0]||1,0),!0}linePosAbsolute(e){return this._setCursor(this._activeBuffer.x,(e.params[0]||1)-1),!0}vPositionRelative(e){return this._moveCursor(0,e.params[0]||1),!0}hVPosition(e){return this.cursorPosition(e),!0}tabClear(e){let t=e.params[0];return t===0?delete this._activeBuffer.tabs[this._activeBuffer.x]:t===3&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){let t=e.params[0];return t===1&&(this._curAttrData.bg|=536870912),(t===2||t===0)&&(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,t,r,s=!1,o=!1){let a=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);a&&(a.replaceCells(t,r,this._activeBuffer.getNullCell(this._eraseAttrData()),o),s&&(a.isWrapped=!1))}_resetBufferLine(e,t=!1){let r=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);r&&(r.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),t),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),r.isWrapped=!1)}eraseInDisplay(e,t=!1){this._restrictCursor(this._bufferService.cols);let r;switch(e.params[0]){case 0:for(r=this._activeBuffer.y,this._dirtyRowTracker.markDirty(r),this._eraseInBufferLine(r++,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);r=this._bufferService.cols){let o=this._activeBuffer.lines.get(r+1);o&&(o.isWrapped=!1)}for(;r--;)this._resetBufferLine(r,t);this._dirtyRowTracker.markDirty(0);break;case 2:if(this._optionsService.rawOptions.scrollOnEraseInDisplay){for(r=this._bufferService.rows,this._dirtyRowTracker.markRangeDirty(0,r-1);r--&&!this._activeBuffer.lines.get(this._activeBuffer.ybase+r)?.getTrimmedLength(););for(;r>=0;r--)this._bufferService.scroll(this._eraseAttrData())}else{for(r=this._bufferService.rows,this._dirtyRowTracker.markDirty(r-1);r--;)this._resetBufferLine(r,t);this._dirtyRowTracker.markDirty(0)}break;case 3:let s=this._activeBuffer.lines.length-this._bufferService.rows;s>0&&(this._activeBuffer.lines.trimStart(s),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-s,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-s,0),this._onScroll.fire(0));break}return!0}eraseInLine(e,t=!1){switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,t);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,t);break}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let t=e.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y65535?2:1}let c=d;for(let u=1;u0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent("\x1B[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent("\x1B[?6c")),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent("\x1B[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent("\x1B[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent("\x1B[>83;40003;0c")),!0}sendXtVersion(e){return e.params[0]>0||this._coreService.triggerDataEvent(`\x1BP>|xterm.js(${yn})\x1B\\`),!0}_is(e){return(this._optionsService.rawOptions.termName+"").startsWith(e)}setMode(e){for(let t=0;t(te[te.NOT_RECOGNIZED=0]="NOT_RECOGNIZED",te[te.SET=1]="SET",te[te.RESET=2]="RESET",te[te.PERMANENTLY_SET=3]="PERMANENTLY_SET",te[te.PERMANENTLY_RESET=4]="PERMANENTLY_RESET"))(r||={});let s=this._coreService.decPrivateModes,{activeProtocol:o,activeEncoding:a}=this._mouseStateService,l=this._coreService,{buffers:h,cols:d}=this._bufferService,{active:c,alt:u}=h,_=this._optionsService.rawOptions,p=(S,I)=>(l.triggerDataEvent(`\x1B[${t?"":"?"}${S};${I}$y`),!0),v=S=>S?1:2,f=e.params[0];return t?f===2?p(f,4):f===4?p(f,v(l.modes.insertMode)):f===12?p(f,3):f===20?p(f,v(_.convertEol)):p(f,0):f===1?p(f,v(s.applicationCursorKeys)):f===3?p(f,_.windowOptions.setWinLines?d===80?2:d===132?1:0:0):f===6?p(f,v(s.origin)):f===7?p(f,v(s.wraparound)):f===8?p(f,3):f===9?p(f,v(o==="X10")):f===12?p(f,v(_.cursorBlink)):f===25?p(f,v(!l.isCursorHidden)):f===45?p(f,v(s.reverseWraparound)):f===66?p(f,v(s.applicationKeypad)):f===67?p(f,4):f===1e3?p(f,v(o==="VT200")):f===1002?p(f,v(o==="DRAG")):f===1003?p(f,v(o==="ANY")):f===1004?p(f,v(s.sendFocus)):f===1005?p(f,4):f===1006?p(f,v(a==="SGR")):f===1015?p(f,4):f===1016?p(f,v(a==="SGR_PIXELS")):f===1048?p(f,1):f===47||f===1047||f===1049?p(f,v(c===u)):f===2004?p(f,v(s.bracketedPasteMode)):f===2026?p(f,v(s.synchronizedOutput)):f===9001&&this._optionsService.rawOptions.vtExtensions?.win32InputMode?p(f,v(s.win32InputMode)):p(f,0)}_updateAttrColor(e,t,r,s,o){return t===2?(e|=50331648,e&=-16777216,e|=ue.fromColorRGB([r,s,o])):t===5&&(e&=-67108864,e|=33554432|r&255),e}_extractColor(e,t,r){let s=[0,0,-1,0,0,0],o=0,a=0;do{if(s[a+o]=e.params[t+a],e.hasSubParams(t+a)){let l=e.getSubParams(t+a),h=0;do s[1]===5&&(o=1),s[a+h+1+o]=l[h];while(++h=2||s[1]===2&&a+o>=5)break;s[1]&&(o=1)}while(++a+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,e===0&&(t.fg&=-268435457),t.updateExtended()}_processSGR0(e){e.fg=U.fg,e.bg=U.bg,e.extended=e.extended.clone(),e.extended.underlineStyle=0,e.extended.underlineColor&=-67108864,e.updateExtended()}charAttributes(e){if(e.length===1&&e.params[0]===0)return this._processSGR0(this._curAttrData),!0;let t=e.length,r,s=this._curAttrData;for(let o=0;o=30&&r<=37?(s.fg&=-67108864,s.fg|=16777216|r-30):r>=40&&r<=47?(s.bg&=-67108864,s.bg|=16777216|r-40):r>=90&&r<=97?(s.fg&=-67108864,s.fg|=16777216|r-90|8):r>=100&&r<=107?(s.bg&=-67108864,s.bg|=16777216|r-100|8):r===0?this._processSGR0(s):r===1?s.fg|=134217728:r===3?s.bg|=67108864:r===4?(s.fg|=268435456,this._processUnderline(e.hasSubParams(o)?e.getSubParams(o)[0]:1,s)):r===5?s.fg|=536870912:r===7?s.fg|=67108864:r===8?s.fg|=1073741824:r===9?s.fg|=2147483648:r===2?s.bg|=134217728:r===21?this._processUnderline(2,s):r===22?(s.fg&=-134217729,s.bg&=-134217729):r===23?s.bg&=-67108865:r===24?(s.fg&=-268435457,this._processUnderline(0,s)):r===25?s.fg&=-536870913:r===27?s.fg&=-67108865:r===28?s.fg&=-1073741825:r===29?s.fg&=2147483647:r===39?(s.fg&=-67108864,s.fg|=U.fg&16777215):r===49?(s.bg&=-67108864,s.bg|=U.bg&16777215):r===38||r===48||r===58?o+=this._extractColor(e,o,s):r===53?s.bg|=1073741824:r===55?s.bg&=-1073741825:r===221&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??!0)?s.fg&=-134217729:r===222&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??!0)?s.bg&=-134217729:r===59?(s.extended=s.extended.clone(),s.extended.underlineColor=-1,s.updateExtended()):this._logService.debug("Unknown SGR attribute: %d.",r);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent("\x1B[0n");break;case 6:let t=this._activeBuffer.y+1,r=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`\x1B[${t};${r}R`);break}return!0}deviceStatusPrivate(e){switch(e.params[0]){case 6:let t=this._activeBuffer.y+1,r=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`\x1B[?${t};${r}R`);break;case 15:break;case 25:break;case 26:break;case 53:break;case 996:(this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery??!0)&&this._onRequestColorSchemeQuery.fire();break}return!0}softReset(e){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=U.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(e){let t=e.length===0?1:e.params[0];if(t===0)this._coreService.decPrivateModes.cursorStyle=void 0,this._coreService.decPrivateModes.cursorBlink=void 0;else{switch(t){case 1:case 2:this._coreService.decPrivateModes.cursorStyle="block";break;case 3:case 4:this._coreService.decPrivateModes.cursorStyle="underline";break;case 5:case 6:this._coreService.decPrivateModes.cursorStyle="bar";break}let r=t%2===1;this._coreService.decPrivateModes.cursorBlink=r}return!0}setScrollRegion(e){let t=e.params[0]||1,r;return(e.length<2||(r=e.params[1])>this._bufferService.rows||r===0)&&(r=this._bufferService.rows),r>t&&(this._activeBuffer.scrollTop=t-1,this._activeBuffer.scrollBottom=r-1,this._setCursor(0,0)),!0}windowOptions(e){if(!xn(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;let t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:t!==2&&this._onRequestWindowsOptionsReport.fire(0);break;case 16:this._onRequestWindowsOptionsReport.fire(1);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`\x1B[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:(t===0||t===2)&&(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>10&&this._windowTitleStack.shift()),(t===0||t===1)&&(this._iconNameStack.push(this._iconName),this._iconNameStack.length>10&&this._iconNameStack.shift());break;case 23:(t===0||t===2)&&this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),(t===0||t===1)&&this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop());break}return!0}saveCursor(e){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._activeBuffer.savedCharsets=this._charsetService.charsets.slice(),this._activeBuffer.savedGlevel=this._charsetService.glevel,this._activeBuffer.savedOriginMode=this._coreService.decPrivateModes.origin,this._activeBuffer.savedWraparoundMode=this._coreService.decPrivateModes.wraparound,!0}restoreCursor(e){this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg;for(let t=0;t1;){let s=r.shift(),o=r.shift();if(/^\d+$/.exec(s)){let a=parseInt(s,10);if(Tn(a))if(o==="?")t.push({type:0,index:a});else{let l=ys(o);l&&t.push({type:1,index:a,color:l})}}}return t.length&&this._onColor.fire(t),!0}setHyperlink(e){let t=e.indexOf(";");if(t===-1)return!0;let r=e.slice(0,t).trim(),s=e.slice(t+1);return s?this._createHyperlink(r,s):r.trim()?!1:this._finishHyperlink()}_createHyperlink(e,t){this._getCurrentLinkId()&&this._finishHyperlink();let r=e.split(":"),s,o=r.findIndex(a=>a.startsWith("id="));return o!==-1&&(s=r[o].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:s,uri:t}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,t){let r=e.split(";");for(let s=0;s=this._specialColors.length);++s,++t)if(r[s]==="?")this._onColor.fire([{type:0,index:this._specialColors[t]}]);else{let o=ys(r[s]);o&&this._onColor.fire([{type:1,index:this._specialColors[t],color:o}])}return!0}setOrReportFgColor(e){return this._setOrReportSpecialColor(e,0)}setOrReportBgColor(e){return this._setOrReportSpecialColor(e,1)}setOrReportCursorColor(e){return this._setOrReportSpecialColor(e,2)}restoreIndexedColor(e){if(!e)return this._onColor.fire([{type:2}]),!0;let t=[],r=e.split(";");for(let s=0;s=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){let e=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,e,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=U.clone(),this._eraseAttrDataInternal=U.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=this._curAttrData.bg&67108863,this._eraseAttrDataInternal}setgLevel(e){return this._charsetService.setgLevel(e),!0}screenAlignmentPattern(){let e=new F;e.content=1<<22|69,e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let t=0;t(this._coreService.triggerDataEvent(`\x1B${l}\x1B\\`),!0),s=this._bufferService.buffer,o=this._optionsService.rawOptions,a={block:2,underline:4,bar:6};return r(e==='"q'?`P1$r${this._curAttrData.isProtected()?1:0}"q`:e==='"p'?'P1$r61;1"p':e==="r"?`P1$r${s.scrollTop+1};${s.scrollBottom+1}r`:e==="m"?"P1$r0m":e===" q"?`P1$r${a[o.cursorStyle]-(o.cursorBlink?1:0)} q`:"P0$r")}markRangeDirty(e,t){this._dirtyRowTracker.markRangeDirty(e,t)}kittyKeyboardSet(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=e.params[0]||0,r=e.length>1&&e.params[1]||1,s=this._coreService.kittyKeyboard;switch(r){case 1:s.flags=t;break;case 2:s.flags|=t;break;case 3:s.flags&=~t;break}return!0}kittyKeyboardQuery(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=this._coreService.kittyKeyboard.flags;return this._coreService.triggerDataEvent(`\x1B[?${t}u`),!0}kittyKeyboardPush(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=e.params[0]||0,r=this._coreService.kittyKeyboard,o=this._bufferService.buffer===this._bufferService.buffers.alt?r.altStack:r.mainStack;return o.length>=16&&o.shift(),o.push(r.flags),r.flags=t,!0}kittyKeyboardPop(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=Math.max(1,e.params[0]||1),r=this._coreService.kittyKeyboard,o=this._bufferService.buffer===this._bufferService.buffers.alt?r.altStack:r.mainStack;for(let a=0;a0;a++)r.flags=o.pop();return o.length===0&&t>0&&(r.flags=0),!0}},hi=class{constructor(i){this._bufferService=i;this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(i){ithis.end&&(this.end=i)}markRangeDirty(i,e){i>e&&(wn=i,i=e,e=wn),ithis.end&&(this.end=e)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};hi=y([m(0,D)],hi);function Tn(n){return 0<=n&&n<256}var vr=class extends g{constructor(e){super();this._action=e;this._writeBuffer=[];this._callbacks=[];this._pendingData=0;this._bufferOffset=0;this._isSyncWriting=!1;this._syncCalls=0;this._didUserInput=!1;this._innerWriteTimer=this._register(new Ie);this._onWriteParsed=this._register(new b);this.onWriteParsed=this._onWriteParsed.event;this._register(E(()=>{this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0}))}handleUserInput(){this._didUserInput=!0}flushSync(){if(this._store.isDisposed||this._isSyncWriting)return;this._isSyncWriting=!0;let e,t=!1;for(;e=this._writeBuffer.shift();){t=!0,this._action(e);let r=this._callbacks.shift();r&&r()}this._pendingData=0,this._bufferOffset=2147483647,this._writeBuffer.length=0,this._callbacks.length=0,this._isSyncWriting=!1,t&&this._onWriteParsed.fire()}writeSync(e,t){if(this._store.isDisposed)return;if(t!==void 0&&this._syncCalls>t){this._syncCalls=0;return}if(this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;this._isSyncWriting=!0;let r;for(;r=this._writeBuffer.shift();){this._action(r);let s=this._callbacks.shift();s&&s()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,t){if(!this._store.isDisposed){if(this._pendingData>5e7)throw new Error("write data discarded, use flow control to avoid losing data");if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput){this._didUserInput=!1,this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t),this._innerWrite();return}this._scheduleInnerWrite()}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)}}_scheduleInnerWrite(e=0,t=!0){this._store.isDisposed||this._innerWriteTimer.cancelAndSet(()=>this._innerWrite(e,t),0)}_innerWrite(e=0,t=!0){if(this._store.isDisposed)return;let r=e||performance.now();for(;this._writeBuffer.length>this._bufferOffset;){let s=this._writeBuffer[this._bufferOffset],o=this._action(s,t);if(o){let l=h=>{this._store.isDisposed||(performance.now()-r>=12?this._scheduleInnerWrite(0,h):this._innerWrite(r,h))};o.catch(h=>(queueMicrotask(()=>{throw h}),Promise.resolve(!1))).then(l);return}let a=this._callbacks[this._bufferOffset];if(a&&a(),this._bufferOffset++,this._pendingData-=s.length,performance.now()-r>=12)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>50&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),this._scheduleInnerWrite()):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}};var kt=class{constructor(i){this._bufferService=i;this._nextId=1;this._entriesWithId=new Map;this._dataByLinkId=new Map}registerLink(i){let e=this._bufferService.buffer;if(i.id===void 0){let l=e.addMarker(e.ybase+e.y),h={data:i,id:this._nextId++,lines:[l]};return l.onDispose(()=>this._removeMarkerFromLink(h,l)),this._dataByLinkId.set(h.id,h),h.id}let t=i,r=this._getEntryIdKey(t),s=this._entriesWithId.get(r);if(s)return this.addLineToLink(s.id,e.ybase+e.y),s.id;let o=e.addMarker(e.ybase+e.y),a={id:this._nextId++,key:this._getEntryIdKey(t),data:t,lines:[o]};return o.onDispose(()=>this._removeMarkerFromLink(a,o)),this._entriesWithId.set(a.key,a),this._dataByLinkId.set(a.id,a),a.id}addLineToLink(i,e){let t=this._dataByLinkId.get(i);if(t&&t.lines.every(r=>r.line!==e)){let r=this._bufferService.buffer.addMarker(e);t.lines.push(r),r.onDispose(()=>this._removeMarkerFromLink(t,r))}}getLinkData(i){return this._dataByLinkId.get(i)?.data}_getEntryIdKey(i){return`${i.id};;${i.uri}`}_removeMarkerFromLink(i,e){let t=i.lines.indexOf(e);t!==-1&&(i.lines.splice(t,1),i.lines.length===0&&(i.data.id!==void 0&&this._entriesWithId.delete(i.key),this._dataByLinkId.delete(i.id)))}};kt=y([m(0,D)],kt);var Dn=!1,Sr=class extends g{constructor(e){super();this._windowsWrappingHeuristics=this._register(new P);this._onBinary=this._register(new b);this.onBinary=this._onBinary.event;this._onData=this._register(new b);this.onData=this._onData.event;this._onLineFeed=this._register(new b);this.onLineFeed=this._onLineFeed.event;this._onRender=this._register(new b);this.onRender=this._onRender.event;this._onResize=this._register(new b);this.onResize=this._onResize.event;this._onWriteParsed=this._register(new b);this.onWriteParsed=this._onWriteParsed.event;this._onScroll=this._register(new b);this._instantiationService=new Ji,this.optionsService=this._register(new nr(e)),this._instantiationService.setService(R,this.optionsService),this._logService=this._register(this._instantiationService.createInstance(wt)),this._instantiationService.setService(fe,this._logService),this._bufferService=this._register(this._instantiationService.createInstance(Dt)),this._instantiationService.setService(D,this._bufferService),this.coreService=this._register(this._instantiationService.createInstance(Lt)),this._instantiationService.setService(Y,this.coreService),this.mouseStateService=this._register(this._instantiationService.createInstance(or)),this._instantiationService.setService(Me,this.mouseStateService),this.unicodeService=this._register(this._instantiationService.createInstance(me)),this.unicodeService.register(new ar),this._instantiationService.setService(Us,this.unicodeService),this._charsetService=this._instantiationService.createInstance(lr),this._instantiationService.setService(Ws,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(kt),this._instantiationService.setService(vi,this._oscLinkService),this._inputHandler=this._register(new br(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.mouseStateService,this.unicodeService)),this._register(j.forward(this._inputHandler.onLineFeed,this._onLineFeed)),this._register(j.forward(this._bufferService.onResize,this._onResize)),this._register(j.forward(this.coreService.onData,this._onData)),this._register(j.forward(this.coreService.onBinary,this._onBinary)),this._register(this.coreService.onRequestScrollToBottom(()=>this.scrollToBottom(!0))),this._register(this.coreService.onUserInput(()=>this._writeBuffer.handleUserInput())),this._register(this.optionsService.onMultipleOptionChange(["windowsPty"],()=>this._handleWindowsPtyOptionChange())),this._register(this._bufferService.onScroll(()=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)})),this._writeBuffer=this._register(new vr((t,r)=>this._inputHandler.parse(t,r))),this._register(j.forward(this._writeBuffer.onWriteParsed,this._onWriteParsed))}get onScroll(){return this._onScrollApi||(this._onScrollApi=this._register(new b),this._onScroll.event(e=>{this._onScrollApi?.fire(e.position)})),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(e){for(let t in e)this.optionsService.options[t]=e[t]}write(e,t){this._writeBuffer.write(e,t)}writeSync(e,t){this._logService.logLevel<=3&&!Dn&&(this._logService.warn("writeSync is unreliable and will be removed soon."),Dn=!0),this._writeBuffer.writeSync(e,t)}input(e,t=!0){this.coreService.triggerDataEvent(e,t)}resize(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,2),t=Math.max(t,1),this._writeBuffer.flushSync(),this._bufferService.resize(e,t))}scroll(e,t=!1){this._bufferService.scroll(e,t)}scrollLines(e,t){this._bufferService.scrollLines(e,t)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}registerEscHandler(e,t){return this._inputHandler.registerEscHandler(e,t)}registerDcsHandler(e,t){return this._inputHandler.registerDcsHandler(e,t)}registerCsiHandler(e,t){return this._inputHandler.registerCsiHandler(e,t)}registerOscHandler(e,t){return this._inputHandler.registerOscHandler(e,t)}registerApcHandler(e,t){return this._inputHandler.registerApcHandler(e,t)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.mouseStateService.reset()}_handleWindowsPtyOptionChange(){let e=!1,t=this.optionsService.rawOptions.windowsPty;t&&t.backend!==void 0&&t.buildNumber!==void 0&&(e=t.backend==="conpty"&&t.buildNumber<21376),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){let e=[];e.push(this.onLineFeed(Is.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:"H"},()=>(Is(this._bufferService),!1))),this._windowsWrappingHeuristics.value=E(()=>{for(let t of e)t.dispose()})}}};var z=0,gr=class{constructor(i,e){this._getKey=i;this._array=[];this._insertedValues=[];this._isFlushingInserted=!1;this._deletedIndices=[];this._isFlushingDeleted=!1;this._flushInsertedTask=new It(e),this._flushDeletedTask=new It(e)}clear(){this._array.length=0,this._insertedValues.length=0,this._flushInsertedTask.clear(),this._isFlushingInserted=!1,this._deletedIndices.length=0,this._flushDeletedTask.clear(),this._isFlushingDeleted=!1}insert(i){this._flushCleanupDeleted(),this._insertedValues.length===0&&this._flushInsertedTask.enqueue(()=>this._flushInserted()),this._insertedValues.push(i)}_flushInserted(){let i=this._insertedValues.sort((s,o)=>this._getKey(s)-this._getKey(o)),e=0,t=0,r=new Array(this._array.length+this._insertedValues.length);for(let s=0;s=this._array.length||this._getKey(i[e])<=this._getKey(this._array[t])?(r[s]=i[e],e++):r[s]=this._array[t++];this._array=r,this._insertedValues.length=0}_flushCleanupInserted(){!this._isFlushingInserted&&this._insertedValues.length>0&&this._flushInsertedTask.flush()}delete(i){if(this._flushCleanupInserted(),this._array.length===0)return!1;let e=this._getKey(i);if(e===void 0||(z=this._search(e),z===-1)||this._getKey(this._array[z])!==e)return!1;do if(this._array[z]===i)return this._deletedIndices.length===0&&this._flushDeletedTask.enqueue(()=>this._flushDeleted()),this._deletedIndices.push(z),!0;while(++zs-o),e=0,t=new Array(this._array.length-i.length),r=0;for(let s=0;s0&&this._flushDeletedTask.flush()}*getKeyIterator(i){if(this._flushCleanupInserted(),this._flushCleanupDeleted(),this._array.length!==0&&(z=this._search(i),!(z<0||z>=this._array.length)&&this._getKey(this._array[z])===i))do yield this._array[z];while(++z=this._array.length)&&this._getKey(this._array[z])===i))do e(this._array[z]);while(++z=e;){let r=e+t>>1,s=this._getKey(this._array[r]);if(s>i)t=r-1;else if(s0&&this._getKey(this._array[r-1])===i;)r--;return r}}return e}};var Mt=0,Ir=0,Bt=class extends g{constructor(e,t){super();this._logService=e;this._bufferService=t;this._lineCache=this._register(new xs);this._onDecorationRegistered=this._register(new b);this.onDecorationRegistered=this._onDecorationRegistered.event;this._onDecorationRemoved=this._register(new b);this.onDecorationRemoved=this._onDecorationRemoved.event;this._decorations=new gr(r=>r?.marker.line,this._logService),this._register(E(()=>this.reset())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._lineCache.attachToBufferLines(this._bufferService.buffer.lines)})),this._lineCache.attachToBufferLines(this._bufferService.buffer.lines)}get decorations(){return this._decorations.values()}registerDecoration(e){if(e.marker.isDisposed)return;let t=new ws(e);if(t){let r=t.marker.onDispose(()=>t.dispose()),s=t.onDispose(()=>{s.dispose(),t&&(this._decorations.delete(t)&&(this._lineCache.remove(t),this._onDecorationRemoved.fire(t)),r.dispose())});this._decorations.insert(t),this._lineCache.add(t),this._onDecorationRegistered.fire(t)}return t}reset(){for(let e of this._decorations.values())e.dispose();this._decorations.clear(),this._lineCache.clear()}*getDecorationsAtCell(e,t,r){let s=this._lineCache.getDecorationsOnLine(t);if(s)for(let o of s)Mt=o.options.x??0,Ir=Mt+(o.options.width??1),e>=Mt&&e=Mt&&ethis._handleBufferLinesTrim(r))),t.add(e.onInsert(r=>this._handleBufferLinesInsert(r))),t.add(e.onDelete(r=>this._handleBufferLinesDelete(r)))}_getDecorationHeight(e){return e.options.height??1}_addToLineBuckets(e){let t=e.marker.line;if(t<0)return;e._indexedStartLine=t;let r=this._getDecorationHeight(e);for(let s=t;s=0&&this._addToLineBuckets(e)}_scheduleLineIndexSync(e){this._lineIndexSyncCallbacks.push(e),this._lineIndexSyncTimer.set(()=>{let t=this._lineIndexSyncCallbacks;this._lineIndexSyncCallbacks=[];for(let r of t)r()})}_handleBufferLinesTrim(e){if(e<=0)return;let t=new Map;for(let[r,s]of this._decorationsByLine){let o=r-e;o<0||this._mergeLineBucket(t,o,s)}this._decorationsByLine.clear();for(let[r,s]of t)this._decorationsByLine.set(r,s);for(let r of this._decorations)r.marker.isDisposed||(r._indexedStartLine-=e)}_handleBufferLinesInsert(e){this._scheduleLineIndexSync(()=>this._applyBufferLinesInsert(e))}_handleBufferLinesDelete(e){this._scheduleLineIndexSync(()=>this._applyBufferLinesDelete(e))}_mergeLineBucket(e,t,r){let s=e.get(t);if(s)for(let o=0,a=r.length;ot&&(s.push(a),this._removeFromLineBuckets(a))}let o=new Map;for(let[a,l]of this._decorationsByLine){let h=a>=t?a+r:a;this._mergeLineBucket(o,h,l)}this._decorationsByLine.clear();for(let[a,l]of o)this._decorationsByLine.set(a,l);for(let a of this._decorations)a.marker.isDisposed||a._indexedStartLine>=t&&(a._indexedStartLine=a.marker.line);for(let a of s)this._addToLineBuckets(a)}_applyBufferLinesDelete(e){let t=e.index+e.amount,r=new Map;for(let[o,a]of this._decorationsByLine){if(o>=e.index&&o=t?o-e.amount:o;this._mergeLineBucket(r,l,a)}this._decorationsByLine.clear();for(let[o,a]of r)this._decorationsByLine.set(o,a);let s=[];for(let o of this._decorations){if(o.marker.isDisposed)continue;let a=o._indexedStartLine,l=this._getDecorationHeight(o);a>=t?o._indexedStartLine=o.marker.line:at&&s.push(o)}for(let o of s)this._reindexDecoration(o)}},ws=class extends pe{constructor(e){super();this.options=e;this.onRenderEmitter=this.add(new b);this.onRender=this.onRenderEmitter.event;this._onDispose=this.add(new b);this.onDispose=this._onDispose.event;this._cachedBg=null;this._cachedFg=null;this.marker=e.marker,this._indexedStartLine=e.marker.line,this.options.overviewRulerOptions&&!this.options.overviewRulerOptions.position&&(this.options.overviewRulerOptions.position="full")}get backgroundColorRGB(){return this._cachedBg===null&&(this.options.backgroundColor?this._cachedBg=B.toColor(this.options.backgroundColor):this._cachedBg=void 0),this._cachedBg}get foregroundColorRGB(){return this._cachedFg===null&&(this.options.foregroundColor?this._cachedFg=B.toColor(this.options.foregroundColor):this._cachedFg=void 0),this._cachedFg}dispose(){this._onDispose.fire(),super.dispose()}};var yo=1e3,Cr=class{constructor(i,e=yo){this._renderCallback=i;this._debounceThresholdMS=e;this._lastRefreshMs=0;this._additionalRefreshRequested=!1}dispose(){this._refreshTimeoutID&&(clearTimeout(this._refreshTimeoutID),this._refreshTimeoutID=void 0),this._additionalRefreshRequested=!1}refresh(i,e,t){this._rowCount=t,i=i??0,e=e??this._rowCount-1,this._rowStart=this._rowStart!==void 0?Math.min(this._rowStart,i):i,this._rowEnd=this._rowEnd!==void 0?Math.max(this._rowEnd,e):e;let r=performance.now();if(r-this._lastRefreshMs>=this._debounceThresholdMS)this._refreshTimeoutID!==void 0&&(clearTimeout(this._refreshTimeoutID),this._refreshTimeoutID=void 0,this._additionalRefreshRequested=!1),this._lastRefreshMs=r,this._innerRefresh();else if(!this._additionalRefreshRequested){let s=r-this._lastRefreshMs,o=this._debounceThresholdMS-s;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout(()=>{this._lastRefreshMs=performance.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0},o)}}_innerRefresh(){if(this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0)return;let i=Math.max(this._rowStart,0),e=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(i,e)}};var Rn=!1,Ye=class extends g{constructor(e,t,r,s){super();this._terminal=e;this._coreBrowserService=r;this._renderService=s;this._rowColumns=new WeakMap;this._liveRegionLineCount=0;this._charsToConsume=[];this._charsToAnnounce="";let o=this._coreBrowserService.mainDocument;this._accessibilityContainer=o.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=o.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let a=0;athis._handleBoundaryFocus(a,0),this._bottomBoundaryFocusListener=a=>this._handleBoundaryFocus(a,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=o.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new Cr(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");Rn?(this._accessibilityContainer.classList.add("debug"),this._rowContainer.classList.add("debug"),this._debugRootContainer=o.createElement("div"),this._debugRootContainer.classList.add("xterm"),this._debugRootContainer.appendChild(o.createTextNode("------start a11y------")),this._debugRootContainer.appendChild(this._accessibilityContainer),this._debugRootContainer.appendChild(o.createTextNode("------end a11y------")),this._terminal.element.insertAdjacentElement("afterend",this._debugRootContainer)):this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this._register(this._terminal.onResize(a=>this._handleResize(a.rows))),this._register(this._terminal.onRender(a=>this._refreshRows(a.start,a.end))),this._register(this._terminal.onScroll(()=>this._refreshRows())),this._register(this._terminal.onA11yChar(a=>this._handleChar(a))),this._register(this._terminal.onLineFeed(()=>this._handleChar(` + `))),this._register(this._terminal.onA11yTab(a=>this._handleTab(a))),this._register(this._terminal.onKey(a=>this._handleKey(a.key))),this._register(this._terminal.onBlur(()=>this._clearLiveRegion())),this._register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this._register(C(o,"selectionchange",()=>this._handleSelectionChange())),this._register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRowsDimensions(),this._refreshRows(),this._register(E(()=>{Rn?this._debugRootContainer.remove():this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,e===` +-`&&(this._liveRegionLineCount++,this._liveRegionLineCount===21&&(this._liveRegion.textContent=Ze.get())))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){let r=this._terminal.buffer,s=r.lines.length.toString();for(let o=e;o<=t;o++){let a=r.lines.get(r.ydisp+o),l=[],h=a?.translateToString(!0,void 0,void 0,l)||"",d=(r.ydisp+o+1).toString(),c=this._rowElements[o];c&&(h.length===0?(c.textContent="\xA0",this._rowColumns.set(c,[0,1])):(c.textContent=h,this._rowColumns.set(c,l)),c.setAttribute("aria-posinset",d),c.setAttribute("aria-setsize",s),this._alignRowWidth(c))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent===Ze.get()&&this._clearLiveRegion(),this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){let r=e.target,s=this._rowElements[t===0?1:this._rowElements.length-2],o=r.getAttribute("aria-posinset"),a=t===0?"1":`${this._terminal.buffer.lines.length}`;if(o===a||e.relatedTarget!==s)return;let l,h;if(t===0?(l=r,h=this._rowElements.pop(),this._rowContainer.removeChild(h)):(l=this._rowElements.shift(),h=r,this._rowContainer.removeChild(l)),l.removeEventListener("focus",this._topBoundaryFocusListener),h.removeEventListener("focus",this._bottomBoundaryFocusListener),t===0){let d=this._createAccessibilityTreeNode();this._rowElements.unshift(d),this._rowContainer.insertAdjacentElement("afterbegin",d)}else{let d=this._createAccessibilityTreeNode();this._rowElements.push(d),this._rowContainer.appendChild(d)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(t===0?-1:1),this._rowElements[t===0?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(this._rowElements.length===0)return;let e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed){this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection();return}if(!e.anchorNode||!e.focusNode){console.error("anchorNode and/or focusNode are null");return}let t={node:e.anchorNode,offset:e.anchorOffset},r={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(r.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===r.node&&t.offset>r.offset)&&([t,r]=[r,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;let s=this._rowElements.slice(-1)[0];if(r.node.compareDocumentPosition(s)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(r={node:s,offset:s.textContent?.length??0}),!this._rowContainer.contains(r.node))return;let o=({node:h,offset:d})=>{let c=h instanceof Text?h.parentNode:h,u=parseInt(c?.getAttribute("aria-posinset"),10)-1;if(isNaN(u))return console.warn("row is invalid. Race condition?"),null;let _=this._rowColumns.get(c);if(!_)return console.warn("columns is null. Race condition?"),null;let p=d<_.length?_[d]:_.slice(-1)[0]+1;return p>=this._terminal.cols&&(++u,p=0),{row:u,column:p}},a=o(t),l=o(r);if(!(!a||!l)){if(a.row>l.row||a.row===l.row&&a.column>=l.column)throw new Error("invalid range");this._terminal.select(a.column,a.row,(l.row-a.row)*this._terminal.cols-a.column+l.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let t=this._rowContainer.children.length;te;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){let e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e{Oe(this._linkCacheDisposables),this._linkCacheDisposables.length=0,this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()})),this._register(this._bufferService.onResize(()=>{this._clearCurrentLink(),this._wasResized=!0})),this._register(C(this._element,"mouseleave",()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this._register(C(this._element,"mousemove",this._handleMouseMove.bind(this))),this._register(C(this._element,"mousedown",this._handleMouseDown.bind(this))),this._register(C(this._element,"mouseup",this._handleMouseUp.bind(this)))}get currentLink(){return this._currentLink}_handleMouseMove(e){this._lastMouseEvent=e;let t=this._positionFromMouseEvent(e,this._element);if(!t)return;this._isMouseOut=!1;let r=e.composedPath();for(let s=0;s{s?.forEach(o=>{o.link.dispose&&o.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=e.y);let r=!1;for(let[s,o]of this._linkProviderService.linkProviders.entries())t?this._activeProviderReplies?.get(s)&&(r=this._checkLinkProviderResult(s,e,r)):o.provideLinks(e.y,a=>{if(this._isMouseOut)return;let l=a?.map(h=>({link:h}));this._activeProviderReplies?.set(s,l),r=this._checkLinkProviderResult(s,e,r),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)})}_removeIntersectingLinks(e,t){let r=new Set;for(let s=0;se?this._bufferService.cols:l.link.range.end.x;for(let c=h;c<=d;c++){if(r.has(c)){o.splice(a--,1);break}r.add(c)}}}}_checkLinkProviderResult(e,t,r){if(!this._activeProviderReplies)return r;let s=this._activeProviderReplies.get(e),o=!1;for(let a=0;athis._linkAtPosition(l.link,t));a&&(r=!0,this._handleNewLink(a))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!r)for(let a=0;athis._linkAtPosition(h.link,t));if(l){r=!0,this._handleNewLink(l);break}}return r}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;let t=this._positionFromMouseEvent(e,this._element);t&&this._mouseDownLink&&xo(this._mouseDownLink.link,this._currentLink.link)&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){!this._currentLink||!this._lastMouseEvent||(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,Oe(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;let t=this._positionFromMouseEvent(this._lastMouseEvent,this._element);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:e.link.decorations===void 0?!0:e.link.decorations.underline,pointerCursor:e.link.decorations===void 0?!0:e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:r=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==r&&(this._currentLink.state.decorations.pointerCursor=r,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",r))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:r=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==r&&(this._currentLink.state.decorations.underline=r,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,r))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(r=>{if(!this._currentLink)return;let s=r.start===0?0:r.start+1+this._bufferService.buffer.ydisp,o=this._bufferService.buffer.ydisp+1+r.end;if(this._currentLink.link.range.start.y>=s&&this._currentLink.link.range.end.y<=o&&(this._clearCurrentLink(s,o),this._lastMouseEvent)){let a=this._positionFromMouseEvent(this._lastMouseEvent,this._element);a&&this._askForLink(a,!1)}})))}_linkHover(e,t,r){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(r,t.text)}_fireUnderlineEvent(e,t){let r=e.range,s=this._bufferService.buffer.ydisp,o=this._createLinkUnderlineEvent(r.start.x-1,r.start.y-s-1,r.end.x,r.end.y-s-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(o)}_linkLeave(e,t,r){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(r,t.text)}_linkAtPosition(e,t){let r=e.range.start.y*this._bufferService.cols+e.range.start.x,s=e.range.end.y*this._bufferService.cols+e.range.end.x,o=t.y*this._bufferService.cols+t.x;return r<=o&&o<=s}_positionFromMouseEvent(e,t){let r=this._mouseCoordsService.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(r)return{x:r[0],y:r[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,r,s,o){return{x1:e,y1:t,x2:r,y2:s,cols:this._bufferService.cols,fg:o}}};Pt=y([m(1,Pe),m(2,V),m(3,D),m(4,Ii)],Pt);function xo(n,i){return n.text===i.text&&n.range.start.x===i.range.start.x&&n.range.start.y===i.range.start.y&&n.range.end.x===i.range.end.x&&n.range.end.y===i.range.end.y}var Er=class extends Sr{constructor(e={}){super(e);this._linkifier=this._register(new P);this.browser=Ke;this._keyDownHandled=!1;this._keyDownSeen=!1;this._keyPressHandled=!1;this._unprocessedDeadKey=!1;this._accessibilityManager=this._register(new P);this._onCursorMove=this._register(new b);this.onCursorMove=this._onCursorMove.event;this._onKey=this._register(new b);this.onKey=this._onKey.event;this._onSelectionChange=this._register(new b);this.onSelectionChange=this._onSelectionChange.event;this._onTitleChange=this._register(new b);this.onTitleChange=this._onTitleChange.event;this._onBell=this._register(new b);this.onBell=this._onBell.event;this._onFocus=this._register(new b);this._onBlur=this._register(new b);this._onA11yCharEmitter=this._register(new b);this._onA11yTabEmitter=this._register(new b);this._onWillOpen=this._register(new b);this._onDimensionsChange=this._register(new b);this.onDimensionsChange=this._onDimensionsChange.event;this._setup(),this._decorationService=this._instantiationService.createInstance(Bt),this._instantiationService.setService(ge,this._decorationService),this._keyboardService=this._instantiationService.createInstance(xt),this._instantiationService.setService(zs,this._keyboardService),this._linkProviderService=this._instantiationService.createInstance(zi),this._instantiationService.setService(Ii,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(et)),this._register(this._inputHandler.onRequestBell(()=>this._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(t=>this.refresh(t?.start??0,t?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(t=>this._reportWindowsOptions(t))),this._register(this._inputHandler.onColor(t=>this._handleColorEvent(t))),this._register(j.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(j.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(j.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(j.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(t=>this._afterResize(t.cols,t.rows))),this._register(E(()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)}))}get linkifier(){return this._linkifier.value}get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}get dimensions(){if(!this._renderService)return;let e=this._renderService.dimensions;return{css:{canvas:{...e.css.canvas},cell:{...e.css.cell}},device:{canvas:{...e.device.canvas},cell:{...e.device.cell},char:{...e.device.char}}}}_handleColorEvent(e){if(this._themeService)for(let t of e){let r,s;switch(t.index){case 256:r="foreground",s="10";break;case 257:r="background",s="11";break;case 258:r="cursor",s="12";break;default:r="ansi",s="4;"+t.index}switch(t.type){case 0:let o=k.toColorRGB(r==="ansi"?this._themeService.colors.ansi[t.index]:this._themeService.colors[r]);this.coreService.triggerDataEvent(`\x1B]${s};${En(o)}\x1B\\`);break;case 1:if(r==="ansi")this._themeService.modifyColors(a=>a.ansi[t.index]=O.toColor(...t.color));else{let a=r;this._themeService.modifyColors(l=>l[a]=O.toColor(...t.color))}break;case 2:this._themeService.restoreColor(t.index);break}}}_reportColorScheme(){if(!this._themeService)return;let e=Z.relativeLuminance(this._themeService.colors.background.rgba>>8),t=Z.relativeLuminance(this._themeService.colors.foreground.rgba>>8),r=e{this.hasSelection()&&Os(t,this._selectionService)}));let e=t=>Ns(t,this.textarea,this.coreService,this.optionsService);this._register(C(this.textarea,"paste",e)),this._register(C(this.element,"paste",e)),nt?this._register(C(this.element,"mousedown",t=>{t.button===2&&Hr(t,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register(C(this.element,"contextmenu",t=>{Hr(t,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),zt&&this._register(C(this.element,"auxclick",t=>{t.button===1&&Fr(t,this.textarea,this.screenElement)}))}_bindKeys(){this._register(C(this.textarea,"keyup",e=>this._keyUp(e),!0)),this._register(C(this.textarea,"keydown",e=>this._keyDown(e),!0)),this._register(C(this.textarea,"keypress",e=>this._keyPress(e),!0)),this._register(C(this.textarea,"compositionstart",()=>{this._syncTextArea(),this._compositionHelper.compositionstart(),this._compositionHelper.updateCompositionElements()})),this._register(C(this.textarea,"compositionupdate",e=>this._compositionHelper.compositionupdate(e))),this._register(C(this.textarea,"compositionend",()=>this._compositionHelper.compositionend())),this._register(C(this.textarea,"input",e=>this._inputEvent(e),!0)),this._register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(e){if(!e)throw new Error("Terminal requires a parent element.");if(e.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this.element?.ownerDocument.defaultView&&this._coreBrowserService){this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView);return}this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),this.element.classList.toggle("allow-transparency",this.options.allowTransparency),this._register(this.optionsService.onSpecificOptionChange("allowTransparency",l=>this.element.classList.toggle("allow-transparency",l))),e.appendChild(this.element);let t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._register(C(this.screenElement,"mousemove",l=>this.updateCursorStyle(l))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);let r=this.textarea=this._document.createElement("textarea");this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",Ut.get()),Yr||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange("disableStdin",()=>r.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(Ki,this.textarea,e.ownerDocument.defaultView??window,this._document??(typeof window<"u"?window.document:null))),this._instantiationService.setService(G,this._coreBrowserService),this._register(C(this.textarea,"focus",l=>this._handleTextAreaFocus(l))),this._register(C(this.textarea,"blur",()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(bt,this._document,this._helperContainer),this._instantiationService.setService(Be,this._charSizeService),this._themeService=this._instantiationService.createInstance(yt),this._instantiationService.setService(_e,this._themeService),this._register(this._inputHandler.onRequestColorSchemeQuery(()=>this._reportColorScheme())),this._register(this._themeService.onChangeColors(()=>{this.coreService.decPrivateModes.colorSchemeUpdates&&this._reportColorScheme()})),this._characterJoinerService=this._instantiationService.createInstance(He),this._instantiationService.setService(gi,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(Ct,this.rows,this.screenElement)),this._instantiationService.setService(V,this._renderService),this._register(this._renderService.onRenderedViewportChange(l=>this._onRender.fire(l))),this._register(this._renderService.onDimensionsChange(l=>this._onDimensionsChange.fire({css:{canvas:{...l.css.canvas},cell:{...l.css.cell}},device:{canvas:{...l.device.canvas},cell:{...l.device.cell},char:{...l.device.char}}}))),this.onResize(l=>this._renderService.resize(l.cols,l.rows)),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(ft,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseCoordsService=this._instantiationService.createInstance(vt),this._instantiationService.setService(Pe,this._mouseCoordsService);let s=this._linkifier.value=this._register(this._instantiationService.createInstance(Pt,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch(l){this._logService.error("onWillOpen handler threw an exception",l)}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this._register(this.onResize(()=>{this._renderService.handleResize(this.cols,this.rows),this._syncTextArea()})),this._register(this.onBlur(()=>this._renderService.handleBlur())),this._register(this.onFocus(()=>this._renderService.handleFocus())),this._viewport=this._register(this._instantiationService.createInstance(dt,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(l=>{super.scrollLines(l,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(Et,this.element,this.screenElement,s)),this._instantiationService.setService(Si,this._selectionService),this._mouseService=this._instantiationService.createInstance(gt),this._instantiationService.setService(Ks,this._mouseService),this._register(this._selectionService.onRequestScrollLines(l=>this.scrollLines(l.amount,l.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(l=>this._renderService.handleSelectionChanged(l.start,l.end,l.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(l=>{this.textarea.value=l,this.textarea.focus(),this.textarea.select()})),this._register(j.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(ut,this.screenElement)),this._register(C(this.element,"mousedown",l=>this._selectionService.handleMouseDown(l))),this.mouseStateService.areMouseEventsActive&&!this.options.mouseEventsRequireAlt?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):(this._selectionService.enable(),this.element.classList.remove("enable-mouse-events")),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(Ye,this)),this._register(this.optionsService.onSpecificOptionChange("screenReaderMode",l=>this._handleScreenReaderModeOptionChange(l)));let o=this.options.scrollbar?.showScrollbar??!0,a=this.options.scrollbar?.width;o&&a&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(ze,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("scrollbar",l=>{let h=(l?.showScrollbar??!0)&&!!l?.width;!this._overviewRulerRenderer&&h&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(ze,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this._mouseService.bindMouse({element:this.element,screenElement:this.screenElement,document:this._document,handleTouchScroll:l=>this._viewport?.handleTouchScroll(l)},l=>this._register(l),()=>this.focus())}_createRenderer(){return this._instantiationService.createInstance(mt,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}refresh(e,t,r=!1){this._renderService?.refreshRows(e,t,r)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}paste(e){Nr(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this.mouseStateService.setCustomWheelEventHandler(e)}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");let t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return this._selectionService?this._selectionService.hasSelection:!1}select(e,t,r){this._selectionService.setSelection(e,t,r)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(!(!this._selectionService||!this._selectionService.hasSelection))return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;let t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;!t&&(e.key==="Dead"||e.key==="AltGraph")&&(this._unprocessedDeadKey=!0);let r=this._keyboardService.evaluateKeyDown(e);if(this.updateCursorStyle(e),r.type===3||r.type===2){let o=this.rows-1;return this.scrollLines(r.type===2?-o:o),e.preventDefault(),e.stopPropagation(),!1}if(r.type===1&&this.selectAll(),this._isThirdLevelShift(this.browser,e)||(r.cancel&&(e.preventDefault(),e.stopPropagation()),!r.key)||!this._keyboardService.useKitty&&!this._keyboardService.useWin32InputMode&&e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.key.length===1&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)return!0;if(this._unprocessedDeadKey)return this._unprocessedDeadKey=!1,!0;(r.key===""||r.key==="\r")&&(this.textarea.value="");let s=this._keyboardService.useWin32InputMode&&Ts(e);if(this._onKey.fire({key:r.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(r.key,!s),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return e.preventDefault(),e.stopPropagation(),!1;this._keyDownHandled=!0}_isThirdLevelShift(e,t){let r=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState("AltGraph");return t.type==="keypress"?r:r&&(!t.keyCode||t.keyCode>47)}_keyUp(e){if(this._keyDownSeen=!1,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return;Ts(e)||this.focus();let t=this._keyboardService.evaluateKeyUp(e);if(t?.key){let r=this._keyboardService.useWin32InputMode&&Ts(e);this.coreService.triggerDataEvent(t.key,!r)}this.updateCursorStyle(e),this._keyPressHandled=!1}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled||this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;if(e.charCode)t=e.charCode;else if(e.which===null||e.which===void 0)t=e.keyCode;else if(e.which!==0&&e.charCode!==0)t=e.which;else return!1;return!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)?!1:(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,!0)}_inputEvent(e){if(e.data&&e.inputType==="insertText"&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;let t=e.data;return this.coreService.triggerDataEvent(t,!0),!0}return!1}resize(e,t){if(e===this.cols&&t===this.rows){this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure();return}super.resize(e,t)}_afterResize(e,t){this._charSizeService?.measure()}clear(){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;e=0;i--)this._addons[i].instance.dispose()}loadAddon(i,e){let t={instance:e,dispose:e.dispose,isDisposed:!1};this._addons.push(t),e.dispose=()=>this._wrappedAddonDispose(t),e.activate(i)}_wrappedAddonDispose(i){if(i.isDisposed)return;let e=-1;for(let t=0;t=this._line.length))return e?(this._line.loadCell(i,e),e):this._line.loadCell(i,new F)}translateToString(i,e,t){return this._line.translateToString(i,e,t)}};var di=class{constructor(i,e){this._buffer=i;this.type=e}init(i){return this._buffer=i,this}get cursorY(){return this._buffer.y}get cursorX(){return this._buffer.x}get viewportY(){return this._buffer.ydisp}get baseY(){return this._buffer.ybase}get length(){return this._buffer.lines.length}getLine(i){let e=this._buffer.lines.get(i);if(e)return new xr(e)}getNullCell(){return new F}};var wr=class extends g{constructor(e){super();this._core=e;this._onBufferChange=this._register(new b);this.onBufferChange=this._onBufferChange.event;this._normal=new di(this._core.buffers.normal,"normal"),this._alternate=new di(this._core.buffers.alt,"alternate"),this._register(this._core.buffers.onBufferActivate(()=>this._onBufferChange.fire(this.active)))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw new Error("Active buffer is neither normal nor alternate")}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}};var Tr=class{constructor(i){this._core=i}registerCsiHandler(i,e){return this._core.registerCsiHandler(i,t=>e(t.toArray()))}addCsiHandler(i,e){return this.registerCsiHandler(i,e)}registerDcsHandler(i,e){return this._core.registerDcsHandler(i,(t,r)=>e(t,r.toArray()))}addDcsHandler(i,e){return this.registerDcsHandler(i,e)}registerEscHandler(i,e){return this._core.registerEscHandler(i,e)}addEscHandler(i,e){return this.registerEscHandler(i,e)}registerOscHandler(i,e){return this._core.registerOscHandler(i,e)}addOscHandler(i,e){return this.registerOscHandler(i,e)}registerApcHandler(i,e){return this._core.registerApcHandler(i,e)}};var Dr=class{constructor(i){this._core=i}register(i){this._core.unicodeService.register(i)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(i){this._core.unicodeService.activeVersion=i}};var wo=["cols","rows"],Ee=0,Ln=class extends g{constructor(i){super(),this._core=this._register(new Er(i)),this._addonManager=this._register(new yr),this._publicOptions={...this._core.options};let e=r=>this._core.options[r],t=(r,s)=>{this._checkReadonlyOptions(r),this._core.options[r]=s};for(let r in this._core.options){let s={get:e.bind(this,r),set:t.bind(this,r)};Object.defineProperty(this._publicOptions,r,s)}}_checkReadonlyOptions(i){if(wo.includes(i))throw new Error(`Option "${i}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get onDimensionsChange(){return this._core.onDimensionsChange}get element(){return this._core.element}get screenElement(){return this._core.screenElement}get parser(){return this._parser??=new Tr(this._core)}get unicode(){return this._checkProposedApi(),new Dr(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer??=this._register(new wr(this._core))}get markers(){return this._core.markers}get modes(){let i=this._core.coreService.decPrivateModes,e="none";switch(this._core.mouseStateService.activeProtocol){case"X10":e="x10";break;case"VT200":e="vt200";break;case"DRAG":e="drag";break;case"ANY":e="any";break}return{applicationCursorKeysMode:i.applicationCursorKeys,applicationKeypadMode:i.applicationKeypad,bracketedPasteMode:i.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:e,originMode:i.origin,reverseWraparoundMode:i.reverseWraparound,sendFocusMode:i.sendFocus,showCursor:!this._core.coreService.isCursorHidden,synchronizedOutputMode:i.synchronizedOutput,win32InputMode:i.win32InputMode,wraparoundMode:i.wraparound}}get dimensions(){return this._core.dimensions}get options(){return this._publicOptions}set options(i){for(let e in i)this._publicOptions[e]=i[e]}blur(){this._core.blur()}focus(){this._core.focus()}input(i,e=!0){this._core.input(i,e)}resize(i,e){this._verifyIntegers(i,e),this._core.resize(i,e)}open(i){this._core.open(i)}attachCustomKeyEventHandler(i){this._core.attachCustomKeyEventHandler(i)}attachCustomWheelEventHandler(i){this._core.attachCustomWheelEventHandler(i)}registerLinkProvider(i){return this._core.registerLinkProvider(i)}registerCharacterJoiner(i){return this._core.registerCharacterJoiner(i)}deregisterCharacterJoiner(i){this._core.deregisterCharacterJoiner(i)}registerMarker(i=0){return this._verifyIntegers(i),this._core.registerMarker(i)}registerDecoration(i){return this._verifyPositiveIntegers(i.x??0,i.width??0,i.height??0),this._core.registerDecoration(i)}hasSelection(){return this._core.hasSelection()}select(i,e,t){this._verifyIntegers(i,e,t),this._core.select(i,e,t)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(i,e){this._verifyIntegers(i,e),this._core.selectLines(i,e)}dispose(){super.dispose()}scrollLines(i){this._verifyIntegers(i),this._core.scrollLines(i)}scrollPages(i){this._verifyIntegers(i),this._core.scrollPages(i)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(i){this._verifyIntegers(i),this._core.scrollToLine(i)}clear(){this._core.clear()}write(i,e){this._core.write(i,e)}writeln(i,e){this._core.write(i),this._core.write(`\r ++`&&(this._liveRegionLineCount++,this._liveRegionLineCount===21&&(this._liveRegion.textContent=Ze.get())))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){let r=this._terminal.buffer,s=r.lines.length.toString();for(let o=e;o<=t;o++){let a=r.lines.get(r.ydisp+o),l=[],h=a?.translateToString(!0,void 0,void 0,l)||"",d=(r.ydisp+o+1).toString(),c=this._rowElements[o];c&&(h.length===0?(c.textContent="\xA0",this._rowColumns.set(c,[0,1])):(c.textContent=h,this._rowColumns.set(c,l)),c.setAttribute("aria-posinset",d),c.setAttribute("aria-setsize",s),this._alignRowWidth(c))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent===Ze.get()&&this._clearLiveRegion(),this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){let r=e.target,s=this._rowElements[t===0?1:this._rowElements.length-2],o=r.getAttribute("aria-posinset"),a=t===0?"1":`${this._terminal.buffer.lines.length}`;if(o===a||e.relatedTarget!==s)return;let l,h;if(t===0?(l=r,h=this._rowElements.pop(),this._rowContainer.removeChild(h)):(l=this._rowElements.shift(),h=r,this._rowContainer.removeChild(l)),l.removeEventListener("focus",this._topBoundaryFocusListener),h.removeEventListener("focus",this._bottomBoundaryFocusListener),t===0){let d=this._createAccessibilityTreeNode();this._rowElements.unshift(d),this._rowContainer.insertAdjacentElement("afterbegin",d)}else{let d=this._createAccessibilityTreeNode();this._rowElements.push(d),this._rowContainer.appendChild(d)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(t===0?-1:1),this._rowElements[t===0?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(this._rowElements.length===0)return;let e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed){this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection();return}if(!e.anchorNode||!e.focusNode){console.error("anchorNode and/or focusNode are null");return}let t={node:e.anchorNode,offset:e.anchorOffset},r={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(r.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===r.node&&t.offset>r.offset)&&([t,r]=[r,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;let s=this._rowElements.slice(-1)[0];if(r.node.compareDocumentPosition(s)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(r={node:s,offset:s.textContent?.length??0}),!this._rowContainer.contains(r.node))return;let o=({node:h,offset:d})=>{let c=h instanceof Text?h.parentNode:h,u=parseInt(c?.getAttribute("aria-posinset"),10)-1;if(isNaN(u))return console.warn("row is invalid. Race condition?"),null;let _=this._rowColumns.get(c);if(!_)return console.warn("columns is null. Race condition?"),null;let p=d<_.length?_[d]:_.slice(-1)[0]+1;return p>=this._terminal.cols&&(++u,p=0),{row:u,column:p}},a=o(t),l=o(r);if(!(!a||!l)){if(a.row>l.row||a.row===l.row&&a.column>=l.column)throw new Error("invalid range");this._terminal.select(a.column,a.row,(l.row-a.row)*this._terminal.cols-a.column+l.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let t=this._rowContainer.children.length;te;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){let e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e{Oe(this._linkCacheDisposables),this._linkCacheDisposables.length=0,this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()})),this._register(this._bufferService.onResize(()=>{this._clearCurrentLink(),this._wasResized=!0})),this._register(C(this._element,"mouseleave",()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this._register(C(this._element,"mousemove",this._handleMouseMove.bind(this))),this._register(C(this._element,"mousedown",this._handleMouseDown.bind(this))),this._register(C(this._element,"mouseup",this._handleMouseUp.bind(this)))}get currentLink(){return this._currentLink}_handleMouseMove(e){this._lastMouseEvent=e;let t=this._positionFromMouseEvent(e,this._element);if(!t)return;this._isMouseOut=!1;let r=e.composedPath();for(let s=0;s{s?.forEach(o=>{o.link.dispose&&o.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=e.y);let r=!1;for(let[s,o]of this._linkProviderService.linkProviders.entries())t?this._activeProviderReplies?.get(s)&&(r=this._checkLinkProviderResult(s,e,r)):o.provideLinks(e.y,a=>{if(this._isMouseOut)return;let l=a?.map(h=>({link:h}));this._activeProviderReplies?.set(s,l),r=this._checkLinkProviderResult(s,e,r),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)})}_removeIntersectingLinks(e,t){let r=new Set;for(let s=0;se?this._bufferService.cols:l.link.range.end.x;for(let c=h;c<=d;c++){if(r.has(c)){o.splice(a--,1);break}r.add(c)}}}}_checkLinkProviderResult(e,t,r){if(!this._activeProviderReplies)return r;let s=this._activeProviderReplies.get(e),o=!1;for(let a=0;athis._linkAtPosition(l.link,t));a&&(r=!0,this._handleNewLink(a))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!r)for(let a=0;athis._linkAtPosition(h.link,t));if(l){r=!0,this._handleNewLink(l);break}}return r}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;let t=this._positionFromMouseEvent(e,this._element);t&&this._mouseDownLink&&xo(this._mouseDownLink.link,this._currentLink.link)&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){!this._currentLink||!this._lastMouseEvent||(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,Oe(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;let t=this._positionFromMouseEvent(this._lastMouseEvent,this._element);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:e.link.decorations===void 0?!0:e.link.decorations.underline,pointerCursor:e.link.decorations===void 0?!0:e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:r=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==r&&(this._currentLink.state.decorations.pointerCursor=r,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",r))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:r=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==r&&(this._currentLink.state.decorations.underline=r,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,r))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(r=>{if(!this._currentLink)return;let s=r.start===0?0:r.start+1+this._bufferService.buffer.ydisp,o=this._bufferService.buffer.ydisp+1+r.end;if(this._currentLink.link.range.start.y>=s&&this._currentLink.link.range.end.y<=o&&(this._clearCurrentLink(s,o),this._lastMouseEvent)){let a=this._positionFromMouseEvent(this._lastMouseEvent,this._element);a&&this._askForLink(a,!1)}})))}_linkHover(e,t,r){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(r,t.text)}_fireUnderlineEvent(e,t){let r=e.range,s=this._bufferService.buffer.ydisp,o=this._createLinkUnderlineEvent(r.start.x-1,r.start.y-s-1,r.end.x,r.end.y-s-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(o)}_linkLeave(e,t,r){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(r,t.text)}_linkAtPosition(e,t){let r=e.range.start.y*this._bufferService.cols+e.range.start.x,s=e.range.end.y*this._bufferService.cols+e.range.end.x,o=t.y*this._bufferService.cols+t.x;return r<=o&&o<=s}_positionFromMouseEvent(e,t){let r=this._mouseCoordsService.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(r)return{x:r[0],y:r[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,r,s,o){return{x1:e,y1:t,x2:r,y2:s,cols:this._bufferService.cols,fg:o}}};Pt=y([m(1,Pe),m(2,V),m(3,D),m(4,Ii)],Pt);function xo(n,i){return n.text===i.text&&n.range.start.x===i.range.start.x&&n.range.start.y===i.range.start.y&&n.range.end.x===i.range.end.x&&n.range.end.y===i.range.end.y}var Er=class extends Sr{constructor(e={}){super(e);this._linkifier=this._register(new P);this.browser=Ke;this._keyDownHandled=!1;this._keyDownSeen=!1;this._keyPressHandled=!1;this._unprocessedDeadKey=!1;this._accessibilityManager=this._register(new P);this._onCursorMove=this._register(new b);this.onCursorMove=this._onCursorMove.event;this._onKey=this._register(new b);this.onKey=this._onKey.event;this._onSelectionChange=this._register(new b);this.onSelectionChange=this._onSelectionChange.event;this._onTitleChange=this._register(new b);this.onTitleChange=this._onTitleChange.event;this._onBell=this._register(new b);this.onBell=this._onBell.event;this._onFocus=this._register(new b);this._onBlur=this._register(new b);this._onA11yCharEmitter=this._register(new b);this._onA11yTabEmitter=this._register(new b);this._onWillOpen=this._register(new b);this._onDimensionsChange=this._register(new b);this.onDimensionsChange=this._onDimensionsChange.event;this._setup(),this._decorationService=this._instantiationService.createInstance(Bt),this._instantiationService.setService(ge,this._decorationService),this._keyboardService=this._instantiationService.createInstance(xt),this._instantiationService.setService(zs,this._keyboardService),this._linkProviderService=this._instantiationService.createInstance(zi),this._instantiationService.setService(Ii,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(et)),this._register(this._inputHandler.onRequestBell(()=>this._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(t=>this.refresh(t?.start??0,t?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(t=>this._reportWindowsOptions(t))),this._register(this._inputHandler.onColor(t=>this._handleColorEvent(t))),this._register(j.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(j.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(j.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(j.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(t=>this._afterResize(t.cols,t.rows))),this._register(E(()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)}))}get linkifier(){return this._linkifier.value}get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}get dimensions(){if(!this._renderService)return;let e=this._renderService.dimensions;return{css:{canvas:{...e.css.canvas},cell:{...e.css.cell}},device:{canvas:{...e.device.canvas},cell:{...e.device.cell},char:{...e.device.char}}}}_handleColorEvent(e){if(this._themeService)for(let t of e){let r,s;switch(t.index){case 256:r="foreground",s="10";break;case 257:r="background",s="11";break;case 258:r="cursor",s="12";break;default:r="ansi",s="4;"+t.index}switch(t.type){case 0:let o=k.toColorRGB(r==="ansi"?this._themeService.colors.ansi[t.index]:this._themeService.colors[r]);this.coreService.triggerDataEvent(`\x1B]${s};${En(o)}\x1B\\`);break;case 1:if(r==="ansi")this._themeService.modifyColors(a=>a.ansi[t.index]=O.toColor(...t.color));else{let a=r;this._themeService.modifyColors(l=>l[a]=O.toColor(...t.color))}break;case 2:this._themeService.restoreColor(t.index);break}}}_reportColorScheme(){if(!this._themeService)return;let e=Z.relativeLuminance(this._themeService.colors.background.rgba>>8),t=Z.relativeLuminance(this._themeService.colors.foreground.rgba>>8),r=e{this.hasSelection()&&Os(t,this._selectionService)}));let e=t=>Ns(t,this.textarea,this.coreService,this.optionsService);this._register(C(this.textarea,"paste",e)),this._register(C(this.element,"paste",e)),nt?this._register(C(this.element,"mousedown",t=>{t.button===2&&Hr(t,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register(C(this.element,"contextmenu",t=>{Hr(t,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),zt&&this._register(C(this.element,"auxclick",t=>{t.button===1&&Fr(t,this.textarea,this.screenElement)}))}_bindKeys(){this._register(C(this.textarea,"keyup",e=>this._keyUp(e),!0)),this._register(C(this.textarea,"keydown",e=>this._keyDown(e),!0)),this._register(C(this.textarea,"keypress",e=>this._keyPress(e),!0)),this._register(C(this.textarea,"compositionstart",()=>{this._syncTextArea(),this._compositionHelper.compositionstart(),this._compositionHelper.updateCompositionElements()})),this._register(C(this.textarea,"compositionupdate",e=>this._compositionHelper.compositionupdate(e))),this._register(C(this.textarea,"compositionend",()=>this._compositionHelper.compositionend())),this._register(C(this.textarea,"input",e=>this._inputEvent(e),!0)),this._register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(e){if(!e)throw new Error("Terminal requires a parent element.");if(e.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this.element?.ownerDocument.defaultView&&this._coreBrowserService){this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView);return}this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),this.element.classList.toggle("allow-transparency",this.options.allowTransparency),this._register(this.optionsService.onSpecificOptionChange("allowTransparency",l=>this.element.classList.toggle("allow-transparency",l))),e.appendChild(this.element);let t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._register(C(this.screenElement,"mousemove",l=>this.updateCursorStyle(l))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);let r=this.textarea=this._document.createElement("textarea");this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",Ut.get()),Yr||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange("disableStdin",()=>r.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(Ki,this.textarea,e.ownerDocument.defaultView??window,this._document??(typeof window<"u"?window.document:null))),this._instantiationService.setService(G,this._coreBrowserService),this._register(C(this.textarea,"focus",l=>this._handleTextAreaFocus(l))),this._register(C(this.textarea,"blur",()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(bt,this._document,this._helperContainer),this._instantiationService.setService(Be,this._charSizeService),this._themeService=this._instantiationService.createInstance(yt),this._instantiationService.setService(_e,this._themeService),this._register(this._inputHandler.onRequestColorSchemeQuery(()=>this._reportColorScheme())),this._register(this._themeService.onChangeColors(()=>{this.coreService.decPrivateModes.colorSchemeUpdates&&this._reportColorScheme()})),this._characterJoinerService=this._instantiationService.createInstance(He),this._instantiationService.setService(gi,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(Ct,this.rows,this.screenElement)),this._instantiationService.setService(V,this._renderService),this._register(this._renderService.onRenderedViewportChange(l=>this._onRender.fire(l))),this._register(this._renderService.onDimensionsChange(l=>this._onDimensionsChange.fire({css:{canvas:{...l.css.canvas},cell:{...l.css.cell}},device:{canvas:{...l.device.canvas},cell:{...l.device.cell},char:{...l.device.char}}}))),this.onResize(l=>this._renderService.resize(l.cols,l.rows)),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(ft,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseCoordsService=this._instantiationService.createInstance(vt),this._instantiationService.setService(Pe,this._mouseCoordsService);let s=this._linkifier.value=this._register(this._instantiationService.createInstance(Pt,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch(l){this._logService.error("onWillOpen handler threw an exception",l)}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this._register(this.onResize(()=>{this._renderService.handleResize(this.cols,this.rows),this._syncTextArea()})),this._register(this.onBlur(()=>this._renderService.handleBlur())),this._register(this.onFocus(()=>this._renderService.handleFocus())),this._viewport=this._register(this._instantiationService.createInstance(dt,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(l=>{super.scrollLines(l,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(Et,this.element,this.screenElement,s)),this._instantiationService.setService(Si,this._selectionService),this._mouseService=this._instantiationService.createInstance(gt),this._instantiationService.setService(Ks,this._mouseService),this._register(this._selectionService.onRequestScrollLines(l=>this.scrollLines(l.amount,l.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(l=>this._renderService.handleSelectionChanged(l.start,l.end,l.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(l=>{this.textarea.value=l,this.textarea.focus(),this.textarea.select()})),this._register(j.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(ut,this.screenElement)),this._register(C(this.element,"mousedown",l=>this._selectionService.handleMouseDown(l))),this.mouseStateService.areMouseEventsActive&&!this.options.mouseEventsRequireAlt?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):(this._selectionService.enable(),this.element.classList.remove("enable-mouse-events")),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(Ye,this)),this._register(this.optionsService.onSpecificOptionChange("screenReaderMode",l=>this._handleScreenReaderModeOptionChange(l)));let o=this.options.scrollbar?.showScrollbar??!0,a=this.options.scrollbar?.width;o&&a&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(ze,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("scrollbar",l=>{let h=(l?.showScrollbar??!0)&&!!l?.width;!this._overviewRulerRenderer&&h&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(ze,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this._mouseService.bindMouse({element:this.element,screenElement:this.screenElement,document:this._document,handleTouchScroll:l=>this._viewport?.handleTouchScroll(l)},l=>this._register(l),()=>this.focus())}_createRenderer(){return this._instantiationService.createInstance(mt,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}refresh(e,t,r=!1){this._renderService?.refreshRows(e,t,r)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}paste(e){Nr(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this.mouseStateService.setCustomWheelEventHandler(e)}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");let t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return this._selectionService?this._selectionService.hasSelection:!1}select(e,t,r){this._selectionService.setSelection(e,t,r)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(!(!this._selectionService||!this._selectionService.hasSelection))return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;let t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;!t&&(e.key==="Dead"||e.key==="AltGraph")&&(this._unprocessedDeadKey=!0);let r=this._keyboardService.evaluateKeyDown(e);if(this.updateCursorStyle(e),r.type===3||r.type===2){let o=this.rows-1;return this.scrollLines(r.type===2?-o:o),e.preventDefault(),e.stopPropagation(),!1}if(r.type===1&&this.selectAll(),this._isThirdLevelShift(this.browser,e)||(r.cancel&&(e.preventDefault(),e.stopPropagation()),!r.key)||!this._keyboardService.useKitty&&!this._keyboardService.useWin32InputMode&&e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.key.length===1&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)return!0;if(this._unprocessedDeadKey)return this._unprocessedDeadKey=!1,!0;(r.key===""||r.key==="\r")&&(this.textarea.value="");let s=this._keyboardService.useWin32InputMode&&Ts(e);if(this._onKey.fire({key:r.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(r.key,!s),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return e.preventDefault(),e.stopPropagation(),!1;this._keyDownHandled=!0}_isThirdLevelShift(e,t){let r=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState("AltGraph");return t.type==="keypress"?r:r&&(!t.keyCode||t.keyCode>47)}_keyUp(e){if(this._keyDownSeen=!1,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return;Ts(e)||this.focus();let t=this._keyboardService.evaluateKeyUp(e);if(t?.key){let r=this._keyboardService.useWin32InputMode&&Ts(e);this.coreService.triggerDataEvent(t.key,!r)}this.updateCursorStyle(e),this._keyPressHandled=!1}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled||this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;if(e.charCode)t=e.charCode;else if(e.which===null||e.which===void 0)t=e.keyCode;else if(e.which!==0&&e.charCode!==0)t=e.which;else return!1;return!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)?!1:(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this._compositionHelper.keypress(t)||this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,!0)}_inputEvent(e){if(e.data&&e.inputType==="insertText"&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;let t=e.data;return this.coreService.triggerDataEvent(t,!0),!0}return!1}resize(e,t){if(e===this.cols&&t===this.rows){this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure();return}super.resize(e,t)}_afterResize(e,t){this._charSizeService?.measure()}clear(){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;e=0;i--)this._addons[i].instance.dispose()}loadAddon(i,e){let t={instance:e,dispose:e.dispose,isDisposed:!1};this._addons.push(t),e.dispose=()=>this._wrappedAddonDispose(t),e.activate(i)}_wrappedAddonDispose(i){if(i.isDisposed)return;let e=-1;for(let t=0;t=this._line.length))return e?(this._line.loadCell(i,e),e):this._line.loadCell(i,new F)}translateToString(i,e,t){return this._line.translateToString(i,e,t)}};var di=class{constructor(i,e){this._buffer=i;this.type=e}init(i){return this._buffer=i,this}get cursorY(){return this._buffer.y}get cursorX(){return this._buffer.x}get viewportY(){return this._buffer.ydisp}get baseY(){return this._buffer.ybase}get length(){return this._buffer.lines.length}getLine(i){let e=this._buffer.lines.get(i);if(e)return new xr(e)}getNullCell(){return new F}};var wr=class extends g{constructor(e){super();this._core=e;this._onBufferChange=this._register(new b);this.onBufferChange=this._onBufferChange.event;this._normal=new di(this._core.buffers.normal,"normal"),this._alternate=new di(this._core.buffers.alt,"alternate"),this._register(this._core.buffers.onBufferActivate(()=>this._onBufferChange.fire(this.active)))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw new Error("Active buffer is neither normal nor alternate")}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}};var Tr=class{constructor(i){this._core=i}registerCsiHandler(i,e){return this._core.registerCsiHandler(i,t=>e(t.toArray()))}addCsiHandler(i,e){return this.registerCsiHandler(i,e)}registerDcsHandler(i,e){return this._core.registerDcsHandler(i,(t,r)=>e(t,r.toArray()))}addDcsHandler(i,e){return this.registerDcsHandler(i,e)}registerEscHandler(i,e){return this._core.registerEscHandler(i,e)}addEscHandler(i,e){return this.registerEscHandler(i,e)}registerOscHandler(i,e){return this._core.registerOscHandler(i,e)}addOscHandler(i,e){return this.registerOscHandler(i,e)}registerApcHandler(i,e){return this._core.registerApcHandler(i,e)}};var Dr=class{constructor(i){this._core=i}register(i){this._core.unicodeService.register(i)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(i){this._core.unicodeService.activeVersion=i}};var wo=["cols","rows"],Ee=0,Ln=class extends g{constructor(i){super(),this._core=this._register(new Er(i)),this._addonManager=this._register(new yr),this._publicOptions={...this._core.options};let e=r=>this._core.options[r],t=(r,s)=>{this._checkReadonlyOptions(r),this._core.options[r]=s};for(let r in this._core.options){let s={get:e.bind(this,r),set:t.bind(this,r)};Object.defineProperty(this._publicOptions,r,s)}}_checkReadonlyOptions(i){if(wo.includes(i))throw new Error(`Option "${i}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get onDimensionsChange(){return this._core.onDimensionsChange}get element(){return this._core.element}get screenElement(){return this._core.screenElement}get parser(){return this._parser??=new Tr(this._core)}get unicode(){return this._checkProposedApi(),new Dr(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer??=this._register(new wr(this._core))}get markers(){return this._core.markers}get modes(){let i=this._core.coreService.decPrivateModes,e="none";switch(this._core.mouseStateService.activeProtocol){case"X10":e="x10";break;case"VT200":e="vt200";break;case"DRAG":e="drag";break;case"ANY":e="any";break}return{applicationCursorKeysMode:i.applicationCursorKeys,applicationKeypadMode:i.applicationKeypad,bracketedPasteMode:i.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:e,originMode:i.origin,reverseWraparoundMode:i.reverseWraparound,sendFocusMode:i.sendFocus,showCursor:!this._core.coreService.isCursorHidden,synchronizedOutputMode:i.synchronizedOutput,win32InputMode:i.win32InputMode,wraparoundMode:i.wraparound}}get dimensions(){return this._core.dimensions}get options(){return this._publicOptions}set options(i){for(let e in i)this._publicOptions[e]=i[e]}blur(){this._core.blur()}focus(){this._core.focus()}input(i,e=!0){this._core.input(i,e)}resize(i,e){this._verifyIntegers(i,e),this._core.resize(i,e)}open(i){this._core.open(i)}attachCustomKeyEventHandler(i){this._core.attachCustomKeyEventHandler(i)}attachCustomWheelEventHandler(i){this._core.attachCustomWheelEventHandler(i)}registerLinkProvider(i){return this._core.registerLinkProvider(i)}registerCharacterJoiner(i){return this._core.registerCharacterJoiner(i)}deregisterCharacterJoiner(i){this._core.deregisterCharacterJoiner(i)}registerMarker(i=0){return this._verifyIntegers(i),this._core.registerMarker(i)}registerDecoration(i){return this._verifyPositiveIntegers(i.x??0,i.width??0,i.height??0),this._core.registerDecoration(i)}hasSelection(){return this._core.hasSelection()}select(i,e,t){this._verifyIntegers(i,e,t),this._core.select(i,e,t)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(i,e){this._verifyIntegers(i,e),this._core.selectLines(i,e)}dispose(){super.dispose()}scrollLines(i){this._verifyIntegers(i),this._core.scrollLines(i)}scrollPages(i){this._verifyIntegers(i),this._core.scrollPages(i)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(i){this._verifyIntegers(i),this._core.scrollToLine(i)}clear(){this._core.clear()}write(i,e){this._core.write(i,e)}writeln(i,e){this._core.write(i),this._core.write(`\r + `,e)}paste(i){this._core.paste(i)}refresh(i,e){this._verifyIntegers(i,e),this._core.refresh(i,e)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(i){this._addonManager.loadAddon(this,i)}static get strings(){return{get promptLabel(){return Ut.get()},set promptLabel(i){Ut.set(i)},get tooMuchOutput(){return Ze.get()},set tooMuchOutput(i){Ze.set(i)}}}_verifyIntegers(...i){for(Ee of i)if(Ee===1/0||isNaN(Ee)||Ee%1!==0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...i){for(Ee of i)if(Ee&&(Ee===1/0||isNaN(Ee)||Ee%1!==0||Ee<0))throw new Error("This API only accepts positive integers")}};export{Ln as Terminal}; + //# sourceMappingURL=xterm.mjs.map +diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts +index 4557e1652c34737fdf853436bd9328d9918eee2b..5c60ecbf14aaa8ecec5e5beb2a3e479084f86453 100644 +--- a/src/browser/CoreBrowserTerminal.ts ++++ b/src/browser/CoreBrowserTerminal.ts +@@ -1008,7 +1008,9 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { + + this._onKey.fire({ key, domEvent: ev }); + this._showCursor(); +- this.coreService.triggerDataEvent(key, true); ++ if (!this._compositionHelper!.keypress(key)) { ++ this.coreService.triggerDataEvent(key, true); ++ } + + this._keyPressHandled = true; + +diff --git a/src/browser/Types.ts b/src/browser/Types.ts +index 497afcf535f3eaca00889525a77e15eb633ccd96..c9fc2cf1c06d86cf5459eae1fadc8dce6b4c753b 100644 +--- a/src/browser/Types.ts ++++ b/src/browser/Types.ts +@@ -44,6 +44,7 @@ export interface ICompositionHelper { + compositionend(): void; + updateCompositionElements(dontRecurse?: boolean): void; + keydown(ev: KeyboardEvent): boolean; ++ keypress(text: string): boolean; + } + + export interface IBrowser { +diff --git a/src/browser/input/CompositionHelper.ts b/src/browser/input/CompositionHelper.ts +index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..c93d6d8b35dfd7318444147cec12f07ea430c3f2 100644 +--- a/src/browser/input/CompositionHelper.ts ++++ b/src/browser/input/CompositionHelper.ts +@@ -47,6 +47,11 @@ export class CompositionHelper { + */ + private _dataAlreadySent: string; + ++ /** ++ * Keypress text waiting to be reconciled with the textarea composition candidate. ++ */ ++ private _pendingKeypressData: string; ++ + /** + * The pending textarea change timer, if any. + */ +@@ -65,6 +70,7 @@ export class CompositionHelper { + this._compositionPosition = { start: 0, end: 0 }; + this._compositionSuffix = ''; + this._dataAlreadySent = ''; ++ this._pendingKeypressData = ''; + } + + /** +@@ -138,6 +144,18 @@ export class CompositionHelper { + return true; + } + ++ /** ++ * Defers keypress text while a composition finalizer is pending so all input is emitted once ++ * after reconciliation with the final textarea candidate. ++ */ ++ public keypress(text: string): boolean { ++ if (this._isSendingComposition) { ++ this._pendingKeypressData += text; ++ return true; ++ } ++ return false; ++ } ++ + /** + * Finalizes the composition, resuming regular input actions. This is called when a composition + * is ending. +@@ -154,7 +172,7 @@ export class CompositionHelper { + // Cancel any delayed composition send requests and send the input immediately. + this._isSendingComposition = false; + const input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end); +- this._coreService.triggerDataEvent(input, true); ++ this._sendCompositionInput(input); + } else { + // Make a deep copy of the composition position here as a new compositionstart event may + // fire before the setTimeout executes. +@@ -163,6 +181,7 @@ export class CompositionHelper { + end: this._compositionPosition.end + }; + const currentCompositionSuffix = this._compositionSuffix; ++ this._pendingKeypressData = ''; + + // Since composition* events happen before the changes take place in the textarea on most + // browsers, use a setTimeout with 0ms time to allow the native compositionend event to +@@ -195,14 +214,39 @@ export class CompositionHelper { + : value.length; + input = value.substring(currentCompositionPosition.start, Math.max(currentCompositionPosition.start, valueEnd)); + } +- if (input.length > 0) { +- this._coreService.triggerDataEvent(input, true); +- } ++ this._sendCompositionInput(input); + } + }, 0); + } + } + ++ private _sendCompositionInput(input: string): void { ++ const keypress = this._pendingKeypressData; ++ // Why: Chromium may copy keypress text anywhere into the candidate, or expose only an ++ // overlapping edge. Use the shortest ordered merge so neither observation is repeated. ++ if (!input.includes(keypress)) { ++ if (keypress.includes(input)) { ++ input = keypress; ++ } else { ++ let inputFirstOverlap = Math.min(input.length, keypress.length); ++ while (inputFirstOverlap > 0 && !input.endsWith(keypress.substring(0, inputFirstOverlap))) { ++ inputFirstOverlap--; ++ } ++ let keypressFirstOverlap = Math.min(input.length, keypress.length); ++ while (keypressFirstOverlap > 0 && !keypress.endsWith(input.substring(0, keypressFirstOverlap))) { ++ keypressFirstOverlap--; ++ } ++ input = inputFirstOverlap > keypressFirstOverlap ++ ? input + keypress.substring(inputFirstOverlap) ++ : keypress + input.substring(keypressFirstOverlap); ++ } ++ } ++ this._pendingKeypressData = ''; ++ if (input.length > 0) { ++ this._coreService.triggerDataEvent(input, true); ++ } ++ } ++ + /** + * Apply any changes made to the textarea after the current event chain is allowed to complete. + * This should be called when not currently composing but a keydown event with the "composition diff --git a/package.json b/package.json index 408cbe31806..f6ee0ae7da0 100644 --- a/package.json +++ b/package.json @@ -273,7 +273,8 @@ "node-pty@1.1.0": "config/patches/node-pty@1.1.0.patch", "@xterm/addon-ligatures@0.11.0-beta.287": "config/patches/@xterm__addon-ligatures@0.11.0-beta.287.patch", "@xterm/addon-webgl@0.20.0-beta.286": "config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch", - "@xterm/addon-serialize@0.15.0-beta.287": "config/patches/@xterm__addon-serialize@0.15.0-beta.287.patch" + "@xterm/addon-serialize@0.15.0-beta.287": "config/patches/@xterm__addon-serialize@0.15.0-beta.287.patch", + "@xterm/xterm@6.1.0-beta.287": "config/patches/@xterm__xterm@6.1.0-beta.287.patch" } }, "reactDoctor": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ee48a5b7d4..b6ee54760e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ patchedDependencies: '@xterm/addon-webgl@0.20.0-beta.286': hash: 6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258 path: config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch + '@xterm/xterm@6.1.0-beta.287': + hash: 9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c + path: config/patches/@xterm__xterm@6.1.0-beta.287.patch node-pty@1.1.0: hash: 407ae07e1e0e2ff2e8b58696449c54c31e51d87535bc6aa4a7a7b0b561407282 path: config/patches/node-pty@1.1.0.patch @@ -42,7 +45,7 @@ importers: version: 2.5.6 '@xterm/addon-serialize': specifier: 0.15.0-beta.287 - version: 0.15.0-beta.287(patch_hash=81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9)(@xterm/xterm@6.1.0-beta.287) + version: 0.15.0-beta.287(patch_hash=81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9)(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)) '@xterm/headless': specifier: 6.1.0-beta.287 version: 6.1.0-beta.287 @@ -202,25 +205,25 @@ importers: version: 5.2.0(vite@7.3.6(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4)) '@xterm/addon-fit': specifier: 0.12.0-beta.287 - version: 0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287) + version: 0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)) '@xterm/addon-ligatures': specifier: 0.11.0-beta.287 - version: 0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287) + version: 0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)) '@xterm/addon-search': specifier: 0.17.0-beta.287 - version: 0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287) + version: 0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)) '@xterm/addon-unicode11': specifier: 0.10.0-beta.287 - version: 0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287) + version: 0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)) '@xterm/addon-web-links': specifier: 0.13.0-beta.287 - version: 0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287) + version: 0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)) '@xterm/addon-webgl': specifier: 0.20.0-beta.286 - version: 0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287) + version: 0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)) '@xterm/xterm': specifier: 6.1.0-beta.287 - version: 6.1.0-beta.287 + version: 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -9580,39 +9583,39 @@ snapshots: '@xmldom/xmldom@0.9.10': {} - '@xterm/addon-fit@0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287)': + '@xterm/addon-fit@0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))': dependencies: - '@xterm/xterm': 6.1.0-beta.287 + '@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c) - '@xterm/addon-ligatures@0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287)': + '@xterm/addon-ligatures@0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))': dependencies: - '@xterm/xterm': 6.1.0-beta.287 + '@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c) lru-cache: 11.5.1 opentype.js: 2.0.0 - '@xterm/addon-search@0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287)': + '@xterm/addon-search@0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))': dependencies: - '@xterm/xterm': 6.1.0-beta.287 + '@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c) - '@xterm/addon-serialize@0.15.0-beta.287(patch_hash=81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9)(@xterm/xterm@6.1.0-beta.287)': + '@xterm/addon-serialize@0.15.0-beta.287(patch_hash=81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9)(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))': dependencies: - '@xterm/xterm': 6.1.0-beta.287 + '@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c) - '@xterm/addon-unicode11@0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287)': + '@xterm/addon-unicode11@0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))': dependencies: - '@xterm/xterm': 6.1.0-beta.287 + '@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c) - '@xterm/addon-web-links@0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287)': + '@xterm/addon-web-links@0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))': dependencies: - '@xterm/xterm': 6.1.0-beta.287 + '@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c) - '@xterm/addon-webgl@0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287)': + '@xterm/addon-webgl@0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))': dependencies: - '@xterm/xterm': 6.1.0-beta.287 + '@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c) '@xterm/headless@6.1.0-beta.287': {} - '@xterm/xterm@6.1.0-beta.287': {} + '@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)': {} abbrev@4.0.0: {} diff --git a/src/renderer/src/components/terminal-pane/terminal-ime-xterm-composition-deduplication.test.ts b/src/renderer/src/components/terminal-pane/terminal-ime-xterm-composition-deduplication.test.ts new file mode 100644 index 00000000000..1fa9fd9cbf2 --- /dev/null +++ b/src/renderer/src/components/terminal-pane/terminal-ime-xterm-composition-deduplication.test.ts @@ -0,0 +1,162 @@ +// @vitest-environment happy-dom +import { Terminal } from '@xterm/xterm' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +function nextEventLoop(): Promise { + return new Promise((resolve) => window.setTimeout(resolve, 0)) +} + +function openTerminal(): { + emitted: string[] + terminal: Terminal + textarea: HTMLTextAreaElement +} { + const container = document.createElement('div') + document.body.appendChild(container) + const terminal = new Terminal() + terminal.open(container) + const textarea = terminal.textarea + if (!textarea) { + throw new Error('xterm helper textarea was not created') + } + const emitted: string[] = [] + terminal.onData((data) => emitted.push(data)) + return { emitted, terminal, textarea } +} + +function startComposition(textarea: HTMLTextAreaElement, text: string): void { + textarea.dispatchEvent(new CompositionEvent('compositionstart', { bubbles: true })) + textarea.dispatchEvent(new CompositionEvent('compositionupdate', { data: text, bubbles: true })) + textarea.value = text +} + +function dispatchKeypress(textarea: HTMLTextAreaElement, text: string): void { + const keypress = new KeyboardEvent('keypress', { key: text, bubbles: true }) + // happy-dom omits Chromium's legacy charCode field that xterm still reads. + Object.defineProperty(keypress, 'charCode', { value: text.charCodeAt(0) }) + textarea.dispatchEvent(keypress) +} + +describe('xterm IME composition de-duplication', () => { + beforeEach(() => { + vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue({ + measureText: () => ({ width: 10 }) + } as unknown as CanvasRenderingContext2D) + }) + + afterEach(() => { + vi.restoreAllMocks() + document.body.replaceChildren() + }) + + it('emits a post-composition IBus Hangul keypress only once', async () => { + const { emitted, terminal, textarea } = openTerminal() + startComposition(textarea, '한') + await nextEventLoop() + + // Why: IBus clears at compositionend, then restores the same commit after + // xterm's keypress path has already emitted it. + textarea.value = '' + textarea.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true })) + const compositionHelper = ( + terminal as unknown as { + _core: { + _compositionHelper: { + _isSendingComposition: boolean + _pendingKeypressData: string + } + } + } + )._core._compositionHelper + expect(compositionHelper._isSendingComposition).toBe(true) + dispatchKeypress(textarea, '한') + expect(compositionHelper._pendingKeypressData).toBe('한') + expect(emitted).toEqual([]) + textarea.value = '한' + textarea.dispatchEvent( + new InputEvent('input', { data: '한', inputType: 'insertText', bubbles: true }) + ) + await nextEventLoop() + + expect(emitted.join('')).toBe('한') + terminal.dispose() + }) + + it('preserves composition-first order when keypress overlaps its suffix', async () => { + const { emitted, terminal, textarea } = openTerminal() + startComposition(textarea, '가한') + await nextEventLoop() + + textarea.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true })) + dispatchKeypress(textarea, '한') + await nextEventLoop() + + expect(emitted.join('')).toBe('가한') + terminal.dispose() + }) + + it('emits unmatched keypress before propagated composition text', async () => { + const { emitted, terminal, textarea } = openTerminal() + startComposition(textarea, '한') + await nextEventLoop() + + textarea.dispatchEvent(new CompositionEvent('compositionupdate', { data: 'a', bubbles: true })) + textarea.value = 'a' + textarea.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true })) + dispatchKeypress(textarea, '한') + await nextEventLoop() + + expect(emitted.join('')).toBe('한a') + terminal.dispose() + }) + + it('does not repeat keypress contained before propagated composition text', async () => { + const { emitted, terminal, textarea } = openTerminal() + startComposition(textarea, '한') + await nextEventLoop() + + textarea.dispatchEvent( + new CompositionEvent('compositionupdate', { data: '한a', bubbles: true }) + ) + textarea.value = '한a' + textarea.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true })) + dispatchKeypress(textarea, '한') + await nextEventLoop() + + expect(emitted.join('')).toBe('한a') + terminal.dispose() + }) + + it('merges multiple deferred keypresses with partial textarea overlap', async () => { + const { emitted, terminal, textarea } = openTerminal() + startComposition(textarea, '한') + await nextEventLoop() + + textarea.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true })) + dispatchKeypress(textarea, 'a') + dispatchKeypress(textarea, 'b') + textarea.value = '한a' + await nextEventLoop() + + expect(emitted.join('')).toBe('한ab') + terminal.dispose() + }) + + it('reconciles buffered keypress before immediate keydown finalization', async () => { + const { emitted, terminal, textarea } = openTerminal() + startComposition(textarea, '한') + await nextEventLoop() + + textarea.value = '' + textarea.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true })) + dispatchKeypress(textarea, '한') + textarea.value = '한' + const keydown = new KeyboardEvent('keydown', { key: 'a', code: 'KeyA', bubbles: true }) + Object.defineProperty(keydown, 'keyCode', { value: 65 }) + textarea.dispatchEvent(keydown) + await nextEventLoop() + + expect(emitted.join('')).toBe('한a') + terminal.dispose() + }) +}) From ed28dec59298cb1f44db09ceed61e639c9d3347b Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:37:20 -0700 Subject: [PATCH 030/137] feat(new-workspace): always offer "Add a new project" in Create worktree picker (#9251) Co-authored-by: Orca --- .../components/NewWorkspaceComposerCard.tsx | 1 + .../new-workspace/ProjectCombobox.test.tsx | 34 +++++++++++++++++++ .../new-workspace/ProjectCombobox.tsx | 33 +++++++++++++++++- src/renderer/src/i18n/locales/en.json | 3 +- src/renderer/src/i18n/locales/es.json | 3 +- src/renderer/src/i18n/locales/ja.json | 3 +- src/renderer/src/i18n/locales/ko.json | 3 +- src/renderer/src/i18n/locales/zh.json | 3 +- 8 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/components/NewWorkspaceComposerCard.tsx b/src/renderer/src/components/NewWorkspaceComposerCard.tsx index c7d96c14fef..ab593fb3e5a 100644 --- a/src/renderer/src/components/NewWorkspaceComposerCard.tsx +++ b/src/renderer/src/components/NewWorkspaceComposerCard.tsx @@ -836,6 +836,7 @@ export default function NewWorkspaceComposerCard({ value={selectedProjectId} onValueChange={onProjectChange} onValueSelected={focusNameInput} + onAddProject={handleAddRepo} placeholder={ projectPlaceholder ?? translate('auto.components.NewWorkspaceComposerCard.dccd26d4e4', 'Choose project') diff --git a/src/renderer/src/components/new-workspace/ProjectCombobox.test.tsx b/src/renderer/src/components/new-workspace/ProjectCombobox.test.tsx index cb0309d3e6e..c9353239b10 100644 --- a/src/renderer/src/components/new-workspace/ProjectCombobox.test.tsx +++ b/src/renderer/src/components/new-workspace/ProjectCombobox.test.tsx @@ -139,6 +139,40 @@ describe('ProjectCombobox', () => { expect(onValueChange).toHaveBeenCalledWith('project-group:folder-group') }) + it('always offers an "Add a new project" action, including when the list is empty', () => { + const onAddProject = vi.fn() + + act(() => { + root.render( + + ) + }) + + const addButton = Array.from(container.querySelectorAll('button')).find( + (button) => button.textContent?.includes('Add a new project') + ) + expect(addButton).toBeTruthy() + + act(() => { + addButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) + }) + + expect(onAddProject).toHaveBeenCalledTimes(1) + }) + + it('omits the "Add a new project" action when no handler is provided', () => { + act(() => { + root.render() + }) + + expect(container.textContent).not.toContain('Add a new project') + }) + it('renders directory details for duplicate project names', () => { const duplicateProjects: NewWorkspaceProjectOption[] = [ { diff --git a/src/renderer/src/components/new-workspace/ProjectCombobox.tsx b/src/renderer/src/components/new-workspace/ProjectCombobox.tsx index 91fdda0dbe3..232d9919044 100644 --- a/src/renderer/src/components/new-workspace/ProjectCombobox.tsx +++ b/src/renderer/src/components/new-workspace/ProjectCombobox.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useMemo, useState } from 'react' -import { Check, ChevronsUpDown, FolderOpen } from 'lucide-react' +import { Check, ChevronsUpDown, FolderOpen, FolderPlus } from 'lucide-react' import { Button } from '@/components/ui/button' import { Command, @@ -22,6 +22,7 @@ type ProjectComboboxProps = { value: string | null onValueChange: (projectId: string) => void onValueSelected?: (projectId: string) => void + onAddProject?: () => void placeholder?: string triggerClassName?: string invalid?: boolean @@ -33,6 +34,7 @@ export default function ProjectCombobox({ value, onValueChange, onValueSelected, + onAddProject, placeholder = 'Choose project', triggerClassName, invalid = false, @@ -100,6 +102,12 @@ export default function ProjectCombobox({ [onValueChange, onValueSelected] ) + const handleAddProject = useCallback((): void => { + setOpen(false) + setQuery('') + onAddProject?.() + }, [onAddProject]) + const handleTriggerKeyDown = useCallback( (event: React.KeyboardEvent): void => { if (open) { @@ -219,6 +227,29 @@ export default function ProjectCombobox({ ))} + {onAddProject ? ( + // Why: pinned below the scrollable list so "Add a new project" stays + // reachable in every state, including when the list is empty and + // CommandEmpty shows "No projects match your search." +
    + +
    + ) : null} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 3eb944c8330..c4ad44cfc0a 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -10689,7 +10689,8 @@ }, "ProjectCombobox": { "search": "Search projects...", - "empty": "No projects match your search." + "empty": "No projects match your search.", + "addProject": "Add a new project" } } }, diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index df5980671b5..35b01b8f1f3 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -10666,7 +10666,8 @@ }, "ProjectCombobox": { "search": "Buscar proyectos...", - "empty": "Ningún proyecto coincide con tu búsqueda." + "empty": "Ningún proyecto coincide con tu búsqueda.", + "addProject": "Add a new project" } } }, diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 30a587a73b1..82c04510161 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -10666,7 +10666,8 @@ }, "ProjectCombobox": { "search": "Search projects...", - "empty": "No projects match your search." + "empty": "No projects match your search.", + "addProject": "Add a new project" } } }, diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index e15a6de0759..1be570ee1d4 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -10666,7 +10666,8 @@ }, "ProjectCombobox": { "search": "프로젝트 검색...", - "empty": "검색과 일치하는 프로젝트가 없습니다." + "empty": "검색과 일치하는 프로젝트가 없습니다.", + "addProject": "Add a new project" } } }, diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 2e9e529d695..d4d7ea6af65 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -10666,7 +10666,8 @@ }, "ProjectCombobox": { "search": "搜索项目...", - "empty": "没有匹配搜索的项目。" + "empty": "没有匹配搜索的项目。", + "addProject": "Add a new project" } } }, From 3a27fb59cd8670d5d22f9d1f0b96203ba2d59aba Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:37:55 -0700 Subject: [PATCH 031/137] fix(settings): stop shortcut filter rail overlapping the list below xl (#9252) Co-authored-by: Orca --- src/renderer/src/components/settings/ShortcutsPane.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/settings/ShortcutsPane.tsx b/src/renderer/src/components/settings/ShortcutsPane.tsx index 29f9661d196..a35294e1b3f 100644 --- a/src/renderer/src/components/settings/ShortcutsPane.tsx +++ b/src/renderer/src/components/settings/ShortcutsPane.tsx @@ -343,7 +343,10 @@ export function ShortcutsPane(): React.JSX.Element {
    ) : null} -
    + {/* Below xl the rail stacks above the list in one column; pin the rail + row to its content (auto) and let the list row take the rest, so the + rail can't spill over the list the way two equal auto rows would. */} +
    Date: Fri, 17 Jul 2026 21:42:31 -0400 Subject: [PATCH 032/137] fix(terminal): replay cold restores at recovered grid (#9043) --- src/main/daemon/daemon-pty-adapter.test.ts | 1 + src/main/daemon/daemon-pty-adapter.ts | 10 +- src/main/ipc/pty.test.ts | 28 +++- src/main/ipc/pty.ts | 24 ++- src/main/providers/types.ts | 3 + src/preload/api-types.ts | 2 +- src/preload/index.ts | 2 +- .../terminal-pane/pty-connection.test.ts | 154 ++++++++++++++++-- .../terminal-pane/pty-connection.ts | 62 ++++++- .../terminal-pane/pty-transport-types.ts | 2 +- 10 files changed, 248 insertions(+), 40 deletions(-) diff --git a/src/main/daemon/daemon-pty-adapter.test.ts b/src/main/daemon/daemon-pty-adapter.test.ts index 0dac05d41c3..49db8d1bd4b 100644 --- a/src/main/daemon/daemon-pty-adapter.test.ts +++ b/src/main/daemon/daemon-pty-adapter.test.ts @@ -1296,6 +1296,7 @@ describe('DaemonPtyAdapter (IPtyProvider)', () => { expect(result.coldRestore).toBeDefined() expect(result.coldRestore!.scrollback).toContain('Server running') expect(result.coldRestore!.cwd).toBe('/projects/myapp') + expect(result.coldRestore).toMatchObject({ cols: 120, rows: 40 }) expect(lastSpawnOpts).toMatchObject({ sessionId, cwd: '/projects/myapp', diff --git a/src/main/daemon/daemon-pty-adapter.ts b/src/main/daemon/daemon-pty-adapter.ts index 3bfbd659dd2..55efaaf0d15 100644 --- a/src/main/daemon/daemon-pty-adapter.ts +++ b/src/main/daemon/daemon-pty-adapter.ts @@ -36,6 +36,8 @@ import type { TerminalOscLinkRange } from '../../shared/terminal-osc-link-ranges type ColdRestorePayload = { scrollback: string cwd: string + cols: number + rows: number oscLinks?: TerminalOscLinkRange[] } @@ -595,7 +597,13 @@ export class DaemonPtyAdapter implements IPtyProvider { if (!scrollback) { return null } - return { scrollback, cwd: restoreInfo.cwd, oscLinks: restoreInfo.oscLinks } + return { + scrollback, + cwd: restoreInfo.cwd, + cols: restoreInfo.cols, + rows: restoreInfo.rows, + oscLinks: restoreInfo.oscLinks + } } async sendSignal(id: string, signal: string): Promise { diff --git a/src/main/ipc/pty.test.ts b/src/main/ipc/pty.test.ts index 64047cb1c46..5c8c9f71728 100644 --- a/src/main/ipc/pty.test.ts +++ b/src/main/ipc/pty.test.ts @@ -10765,15 +10765,24 @@ describe('registerPtyHandlers', () => { ) }) - it('seeds headless terminal state with cold-restore cwd metadata', async () => { + it('seeds cold restore at recovered dimensions with a legacy dimensionless fallback', async () => { const oscLinks = [{ row: 0, startCol: 0, endCol: 8, uri: 'https://example.com/restored' }] const coldRestore = { scrollback: 'restored history\r\n', cwd: '/projects/restored', + cols: 132, + rows: 43, oscLinks } + const spawn = vi + .fn() + .mockResolvedValueOnce({ id: 'pty-cold-restore', coldRestore }) + .mockResolvedValueOnce({ + id: 'pty-legacy-cold-restore', + coldRestore: { scrollback: 'legacy history\r\n', cwd: '/projects/legacy' } + }) setLocalPtyProvider({ - spawn: vi.fn(async () => ({ id: 'pty-cold-restore', coldRestore })), + spawn, write: vi.fn(), resize: vi.fn(), kill: vi.fn(), @@ -10798,12 +10807,23 @@ describe('registerPtyHandlers', () => { await handlers.get('pty:spawn')!(null, { cols: 80, rows: 24 }) - expect(runtime.seedHeadlessTerminal).toHaveBeenCalledWith( + expect(runtime.seedHeadlessTerminal).toHaveBeenNthCalledWith( + 1, 'pty-cold-restore', 'restored history\r\n', - undefined, + { cols: 132, rows: 43 }, { cwd: '/projects/restored', oscLinks, preferProviderIfExisting: true } ) + + await handlers.get('pty:spawn')!(null, { cols: 80, rows: 24 }) + + expect(runtime.seedHeadlessTerminal).toHaveBeenNthCalledWith( + 2, + 'pty-legacy-cold-restore', + 'legacy history\r\n', + undefined, + { cwd: '/projects/legacy', oscLinks: undefined, preferProviderIfExisting: true } + ) }) it('upgrades legacy numeric pane keys when the spawn metadata proves the stable leaf', async () => { diff --git a/src/main/ipc/pty.ts b/src/main/ipc/pty.ts index d04a84a506e..0e7a4112fe9 100644 --- a/src/main/ipc/pty.ts +++ b/src/main/ipc/pty.ts @@ -4374,7 +4374,7 @@ export function registerPtyHandlers( // its hydration path will seed the emulator from xterm's live buffer, // which is richer than the daemon snapshot. if (runtime && !rendererPreSignaled && !rendererAlreadyRegistered) { - const seedSize = + const snapshotSeedSize = typeof result.snapshotCols === 'number' && typeof result.snapshotRows === 'number' ? { cols: result.snapshotCols, rows: result.snapshotRows } : undefined @@ -4386,7 +4386,7 @@ export function registerPtyHandlers( runtime.seedHeadlessTerminal( result.id, result.snapshot, - seedSize, + snapshotSeedSize, typeof result.snapshotKittyKeyboardFlags === 'number' ? { kittyKeyboardFlags: result.snapshotKittyKeyboardFlags } : {} @@ -4396,11 +4396,21 @@ export function registerPtyHandlers( typeof result.coldRestore.scrollback === 'string' && result.coldRestore.scrollback.length > 0 ) { - runtime.seedHeadlessTerminal(result.id, result.coldRestore.scrollback, seedSize, { - cwd: result.coldRestore.cwd, - oscLinks: result.coldRestore.oscLinks, - preferProviderIfExisting: true - }) + const coldRestoreSeedSize = + typeof result.coldRestore.cols === 'number' && + typeof result.coldRestore.rows === 'number' + ? { cols: result.coldRestore.cols, rows: result.coldRestore.rows } + : undefined + runtime.seedHeadlessTerminal( + result.id, + result.coldRestore.scrollback, + coldRestoreSeedSize, + { + cwd: result.coldRestore.cwd, + oscLinks: result.coldRestore.oscLinks, + preferProviderIfExisting: true + } + ) } } if ( diff --git a/src/main/providers/types.ts b/src/main/providers/types.ts index 6dc7b6ec1a3..d08577f98de 100644 --- a/src/main/providers/types.ts +++ b/src/main/providers/types.ts @@ -147,6 +147,9 @@ export type PtySpawnResult = { coldRestore?: { scrollback: string cwd: string + /** Optional for compatibility with restore payloads from older app code. */ + cols?: number + rows?: number oscLinks?: TerminalOscLinkRange[] } } diff --git a/src/preload/api-types.ts b/src/preload/api-types.ts index a209e436750..f77783f9c5b 100644 --- a/src/preload/api-types.ts +++ b/src/preload/api-types.ts @@ -1333,7 +1333,7 @@ export type PreloadApi = { isAlternateScreen?: boolean replay?: string sessionExpired?: boolean - coldRestore?: { scrollback: string; cwd: string } + coldRestore?: { scrollback: string; cwd: string; cols?: number; rows?: number } startupCwdFallback?: { kind: 'worktree'; cwd: string } }> write: (id: string, data: string) => void diff --git a/src/preload/index.ts b/src/preload/index.ts index 7c0f42369e9..d1c1b4cb795 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -874,7 +874,7 @@ const api = { isAlternateScreen?: boolean replay?: string sessionExpired?: boolean - coldRestore?: { scrollback: string; cwd: string } + coldRestore?: { scrollback: string; cwd: string; cols?: number; rows?: number } startupCwdFallback?: { kind: 'worktree'; cwd: string } }> => ipcRenderer.invoke('pty:spawn', opts), diff --git a/src/renderer/src/components/terminal-pane/pty-connection.test.ts b/src/renderer/src/components/terminal-pane/pty-connection.test.ts index 18e99dac5a7..e8aeb34ef8a 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.test.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.test.ts @@ -7160,15 +7160,30 @@ describe('connectPanePty', () => { expect(rendered.visibleLines).toEqual(['PS >', '', '', '']) }) - it('cold-restores scrollback then blanks the viewport without erasing scrollback', async () => { + it('cold-restores at the recovered grid and clears only the dirty viewport before blanking', async () => { const { connectPanePty } = await import('./pty-connection') const transport = createMockTransport('fresh-pty') const written: string[] = [] + const operations: ( + | { kind: 'write'; data: string } + | { kind: 'resize'; cols: number; rows: number } + )[] = [] + const destinationCols = 10 + const destinationRows = 5 + const recoveredCols = 20 + const recoveredRows = 3 + const coldScrollback = '\x1b[1;1HCOLD\x1b[1;15HEND\r\nCOLD_SOURCE_ROW_02' + const viewportClear = '\x1b[2J\x1b[H' transport.connect.mockImplementation(async ({ sessionId }: { sessionId?: string }) => { if (sessionId) { return { id: 'fresh-pty', - coldRestore: { scrollback: 'cold TUI row one\r\ncold TUI row two', cwd: '/tmp/wt-1' } + coldRestore: { + scrollback: coldScrollback, + cwd: '/tmp/wt-1', + cols: recoveredCols, + rows: recoveredRows + } } } return 'fresh-pty' @@ -7182,13 +7197,34 @@ describe('connectPanePty', () => { } as StoreState const pane = createPane(1) - pane.terminal.rows = 4 - pane.terminal.cols = 20 + pane.terminal.rows = destinationRows + pane.terminal.cols = destinationCols + let sawViewportClear = false + const preResizeBarrier = { release: null as (() => void) | null } pane.terminal.write = vi.fn((data: string, callback?: () => void) => { written.push(data) + operations.push({ kind: 'write', data }) + if (data === viewportClear) { + sawViewportClear = true + } else if (sawViewportClear && data === '' && preResizeBarrier.release === null) { + preResizeBarrier.release = callback ?? (() => {}) + return + } callback?.() }) + pane.terminal.resize = vi.fn((cols: number, rows: number) => { + operations.push({ kind: 'resize', cols, rows }) + pane.terminal.cols = cols + pane.terminal.rows = rows + }) const manager = createManager(1) + pane.fitAddon.proposeDimensions = vi.fn(() => ({ + cols: destinationCols, + rows: destinationRows + })) + pane.fitAddon.fit = vi.fn(() => { + pane.terminal.resize(destinationCols, destinationRows) + }) const deps = createDeps({ restoredLeafId: LEAF_1, restoredPtyIdByLeafId: { [LEAF_1]: 'lost-pty' } @@ -7196,24 +7232,110 @@ describe('connectPanePty', () => { connectPanePty(pane as never, manager as never, deps as never) await flushAsyncTicks(20) + expect(preResizeBarrier.release).not.toBeNull() + expect(pane.terminal.resize).not.toHaveBeenCalledWith(recoveredCols, recoveredRows) + expect(written).not.toContain(coldScrollback) - const blankViewport = buildFreshShellViewportBlankingSequence(4) + preResizeBarrier.release?.() + await flushAsyncTicks(20) + + const blankViewport = buildFreshShellViewportBlankingSequence(destinationRows) + expect(pane.terminal.resize).toHaveBeenCalledWith(recoveredCols, recoveredRows) + expect(transport.resize).not.toHaveBeenCalledWith(recoveredCols, recoveredRows) + expect(transport.resize).toHaveBeenLastCalledWith(destinationCols, destinationRows) + expect(written).toContain(viewportClear) expect(written).not.toContain('\x1b[2J\x1b[3J\x1b[H') expect(written).toEqual( - expect.arrayContaining([ - 'cold TUI row one\r\ncold TUI row two', - POST_REPLAY_MODE_RESET, - blankViewport - ]) + expect.arrayContaining([coldScrollback, POST_REPLAY_MODE_RESET, blankViewport]) ) - expect(written.indexOf('cold TUI row one\r\ncold TUI row two')).toBeLessThan( - written.indexOf(blankViewport) + expect(written.indexOf(viewportClear)).toBeLessThan(written.indexOf(coldScrollback)) + expect(written.indexOf(coldScrollback)).toBeLessThan(written.indexOf(blankViewport)) + const viewportClearOperation = operations.findIndex( + (operation) => operation.kind === 'write' && operation.data === viewportClear ) + const recoveredResizeOperation = operations.findIndex( + (operation) => + operation.kind === 'resize' && + operation.cols === recoveredCols && + operation.rows === recoveredRows + ) + expect(viewportClearOperation).toBeLessThan(recoveredResizeOperation) - const rendered = await renderHeadlessTerminalState([...written, 'PS >'], 20, 4) - expect(rendered.baseY).toBeGreaterThan(0) - expect(rendered.allLines.some((line) => line.includes('cold TUI row'))).toBe(true) - expect(rendered.visibleLines).toEqual(['PS >', '', '', '']) + // Model the real operation order. The previous behavior appended the + // recovered 20-column snapshot to a dirty 10-column xterm, leaving stale + // cells and wrapping each authoritative row at the wrong grid. + const rendered = new Terminal({ + cols: destinationCols, + rows: destinationRows, + allowProposedApi: true + }) + try { + await writeHeadlessTerminal( + rendered, + 'KEEP_1\r\nKEEP_2\r\nOLD_ROW_1\r\nOLD_ROW_2\r\nOLD_ROW_3\r\nOLD_ROW_4\r\nOLD_ROW_5' + ) + let replayedAtRecoveredGrid = false + let sourceGridLines: string[] = [] + for (const operation of operations) { + if (operation.kind === 'resize') { + if ( + replayedAtRecoveredGrid && + operation.cols === destinationCols && + operation.rows === destinationRows && + sourceGridLines.length === 0 + ) { + sourceGridLines = Array.from( + { length: rendered.buffer.active.length }, + (_, lineIndex) => + rendered.buffer.active.getLine(lineIndex)?.translateToString(true) ?? '' + ) + } + rendered.resize(operation.cols, operation.rows) + if (operation.cols === recoveredCols && operation.rows === recoveredRows) { + replayedAtRecoveredGrid = true + } + } else { + await writeHeadlessTerminal(rendered, operation.data) + } + } + if (sourceGridLines.length === 0) { + sourceGridLines = Array.from( + { length: rendered.buffer.active.length }, + (_, lineIndex) => rendered.buffer.active.getLine(lineIndex)?.translateToString(true) ?? '' + ) + } + expect(sourceGridLines).toContain('COLD END') + if (rendered.cols !== destinationCols || rendered.rows !== destinationRows) { + rendered.resize(destinationCols, destinationRows) + } + await writeHeadlessTerminal(rendered, 'PS >') + const buffer = rendered.buffer.active + const lines = Array.from({ length: buffer.length }, (_, lineIndex) => + buffer.getLine(lineIndex)?.translateToString(true) + ) + const logicalLines: string[] = [] + for (let lineIndex = 0; lineIndex < buffer.length; lineIndex += 1) { + const line = buffer.getLine(lineIndex) + const text = line?.translateToString(true) ?? '' + if (line?.isWrapped && logicalLines.length > 0) { + logicalLines[logicalLines.length - 1] += text + } else { + logicalLines.push(text) + } + } + const visibleLines = Array.from({ length: rendered.rows }, (_, row) => + buffer.getLine(buffer.viewportY + row)?.translateToString(true) + ) + expect(buffer.baseY).toBeGreaterThan(0) + expect(lines.some((line) => line?.includes('OLD_ROW_'))).toBe(false) + expect(lines.some((line) => line?.includes('KEEP_1'))).toBe(true) + expect(lines.some((line) => line?.includes('KEEP_2'))).toBe(true) + expect(logicalLines.some((line) => /COLD\s+END/.test(line))).toBe(true) + expect(logicalLines.some((line) => line.includes('COLD_SOURCE_ROW_02'))).toBe(true) + expect(visibleLines).toEqual(['PS >', '', '', '', '']) + } finally { + rendered.dispose() + } }) it('resumes the provider agent session when daemon reattach cold-restores a fresh shell', async () => { diff --git a/src/renderer/src/components/terminal-pane/pty-connection.ts b/src/renderer/src/components/terminal-pane/pty-connection.ts index e9c0b60aacc..352fd3b8ec6 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.ts @@ -1019,7 +1019,7 @@ export function connectPanePty( // foreground frame opened, so a split end marker that lands after the redraw // window still drains on the fast path instead of the 1s coalesce fallback. let synchronizedForegroundFrameInteractive = false - let suppressSnapshotReplayPtyResize = false + let suppressStructuralReplayPtyResize = false // Why: hidden-delivery gate sync is wired up alongside the deferred PTY // output plumbing inside the connect frame; lifecycle hooks (visibility // flips, exit, dispose) run before/after it exists, so start with no-ops. @@ -3677,7 +3677,7 @@ export function connectPanePty( pane.container.addEventListener(PANE_PTY_RESIZE_HOLD_FLUSH_EVENT, onHeldPtyResizeFlush) const onResizeDisposable = pane.terminal.onResize(({ cols, rows }) => { - if (suppressSnapshotReplayPtyResize || suppressViewportClaimTerminalResize) { + if (suppressStructuralReplayPtyResize || suppressViewportClaimTerminalResize) { return } forwardPtyResize(cols, rows) @@ -4906,8 +4906,8 @@ export function connectPanePty( return deps.restoredViewportBlankingPanesRef?.current.delete(pane.id) ?? false } - const writeFreshShellViewportBlanking = (): void => { - writeReplayData(buildFreshShellViewportBlankingSequence(pane.terminal.rows)) + const writeFreshShellViewportBlanking = (rows = pane.terminal.rows): void => { + writeReplayData(buildFreshShellViewportBlankingSequence(rows)) } const prepareFreshShellViewportForSpawn = (options: FreshSpawnOptions): void => { @@ -6500,11 +6500,11 @@ export function connectPanePty( ) { // Why: xterm parses writes later. Keep snapshot dimensions until // the FIFO sentinel completes so serialized wraps stay exact. - suppressSnapshotReplayPtyResize = true + suppressStructuralReplayPtyResize = true try { pane.terminal.resize(snapshot.cols, snapshot.rows) } finally { - suppressSnapshotReplayPtyResize = false + suppressStructuralReplayPtyResize = false } } if (!snapshot.alternateScreen) { @@ -7286,11 +7286,11 @@ export function connectPanePty( hasSnapshotDimensions && (pane.terminal.cols !== snapshotCols || pane.terminal.rows !== snapshotRows) ) { - suppressSnapshotReplayPtyResize = true + suppressStructuralReplayPtyResize = true try { pane.terminal.resize(snapshotCols, snapshotRows) } finally { - suppressSnapshotReplayPtyResize = false + suppressStructuralReplayPtyResize = false } } writeReplayData('\x1b[2J\x1b[3J\x1b[H') @@ -7337,6 +7337,48 @@ export function connectPanePty( } } } else if (connectResult?.coldRestore) { + let destinationRows = pane.terminal.rows + try { + const proposedDestination = pane.fitAddon.proposeDimensions() + if ( + proposedDestination && + Number.isFinite(proposedDestination.rows) && + proposedDestination.rows > 0 + ) { + destinationRows = Math.max(destinationRows, proposedDestination.rows) + } + } catch { + // The current xterm grid remains a safe lower bound for blanking. + } + // Why: shrinking first would promote clipped stale viewport rows into + // scrollback, beyond the reach of a later viewport-only clear. + writeReplayData('\x1b[2J\x1b[H') + await waitForTerminalReplayWritesParsed(pane.terminal) + if (!isCurrentReattachPayload()) { + return + } + const coldRestoreCols = connectResult.coldRestore.cols + const coldRestoreRows = connectResult.coldRestore.rows + const hasColdRestoreDimensions = + typeof coldRestoreCols === 'number' && + typeof coldRestoreRows === 'number' && + Number.isFinite(coldRestoreCols) && + Number.isFinite(coldRestoreRows) && + coldRestoreCols > 0 && + coldRestoreRows > 0 + if ( + hasColdRestoreDimensions && + (pane.terminal.cols !== coldRestoreCols || pane.terminal.rows !== coldRestoreRows) + ) { + // Why: recovered ANSI cursor positions belong to the checkpoint's + // grid. Keep this layout-only resize from reaching the fresh PTY. + suppressStructuralReplayPtyResize = true + try { + pane.terminal.resize(coldRestoreCols, coldRestoreRows) + } finally { + suppressStructuralReplayPtyResize = false + } + } // replayIntoTerminal: the recorded scrollback is raw PTY output that // may contain query sequences the previous agent CLI emitted; // writing them through xterm.write would trigger auto-replies that @@ -7359,7 +7401,9 @@ export function connectPanePty( // flags it pushed died with it — the fresh shell starts at zero. kittyKeyboardModes.reset() consumeRestoredViewportBlankingMarker() - writeFreshShellViewportBlanking() + // Why: a taller destination fit must not pull recovered rows back + // into the fresh shell's viewport after source-grid replay. + writeFreshShellViewportBlanking(Math.max(destinationRows, pane.terminal.rows)) if (!isRemoteRuntimePtyId(ptyId)) { window.api.pty.ackColdRestore(ptyId) } diff --git a/src/renderer/src/components/terminal-pane/pty-transport-types.ts b/src/renderer/src/components/terminal-pane/pty-transport-types.ts index 1777091c376..ecbb6db57c5 100644 --- a/src/renderer/src/components/terminal-pane/pty-transport-types.ts +++ b/src/renderer/src/components/terminal-pane/pty-transport-types.ts @@ -49,7 +49,7 @@ export type PtyConnectResult = { snapshotRows?: number isAlternateScreen?: boolean sessionExpired?: boolean - coldRestore?: { scrollback: string; cwd: string } + coldRestore?: { scrollback: string; cwd: string; cols?: number; rows?: number } replay?: string startupCwdFallback?: { kind: 'worktree'; cwd: string } /** Trailing partial escape the daemon emulator held mid-parse; the reattach From 64c7f14c3b1612cda4db68a7ce76b749e8feee4e Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:42:59 -0700 Subject: [PATCH 033/137] fix(codex): clarify account-switch restart card actions (#9254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The account-switched restart card had two near-synonymous dismissive buttons (Dismiss / Collapse) whose distinct behaviors weren't legible, and body copy that attributed "keep working with the current account" to Collapse — which only minimizes the notice. Remap to intent-named actions: - Dismiss -> "Keep old account" (clears the notice; pane stays on the old account) - Collapse -> "Not now" (minimize to the pill; decision deferred) - Body no longer claims the minimize action commits you to an account. Restart, the title, and the heading are unchanged. New English strings get fresh auto keys so stale es/ja/ko/zh translations don't persist; catalog re-synced. --- src/renderer/src/components/CodexRestartChip.tsx | 8 ++++---- src/renderer/src/i18n/locales/en.json | 6 +++--- src/renderer/src/i18n/locales/es.json | 6 +++--- src/renderer/src/i18n/locales/ja.json | 6 +++--- src/renderer/src/i18n/locales/ko.json | 6 +++--- src/renderer/src/i18n/locales/zh.json | 6 +++--- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/renderer/src/components/CodexRestartChip.tsx b/src/renderer/src/components/CodexRestartChip.tsx index 960703e8759..a4f11bdc0bb 100644 --- a/src/renderer/src/components/CodexRestartChip.tsx +++ b/src/renderer/src/components/CodexRestartChip.tsx @@ -209,17 +209,17 @@ function LoudRestartOverlay({
    {translate( - 'auto.components.CodexRestartChip.e9b2c7d1a5', - 'Restart this session to use {{value0}}. Collapse to keep working with the current account.', + 'auto.components.CodexRestartChip.9375620cc3', + 'Restart this session to use {{value0}}. It stays on the previous account until you do.', { value0: restartNotice.nextAccountLabel } )}
    + {mobileOnboardingBadge.hasPairedDevice ? ( + + + + + + {translate( + 'auto.components.sidebar.SidebarNav.d599269755', + 'Hide from sidebar' + )} + + ) : null} - +
    diff --git a/src/renderer/src/components/sidebar/mobile-sidebar-onboarding-badge.test.ts b/src/renderer/src/components/sidebar/mobile-sidebar-onboarding-badge.test.ts index 622cae453a8..5e01ad3d36c 100644 --- a/src/renderer/src/components/sidebar/mobile-sidebar-onboarding-badge.test.ts +++ b/src/renderer/src/components/sidebar/mobile-sidebar-onboarding-badge.test.ts @@ -1,13 +1,113 @@ -import { describe, expect, it } from 'vitest' -import { shouldLoadMobileSidebarOnboardingBadge } from './mobile-sidebar-onboarding-badge' +// @vitest-environment happy-dom + +import { act, createElement, type ComponentProps } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + shouldShowMobileSidebarOnboardingBadge, + useMobileSidebarOnboardingBadge +} from './mobile-sidebar-onboarding-badge' +import { + _resetPairedMobileDevicesCacheForTests, + replacePairedMobileDevices +} from '../mobile/paired-mobile-devices' + +type HookState = ReturnType + +let latestHookState: HookState | null = null + +function HookProbe({ enabled = true }: { enabled?: boolean }): null { + latestHookState = useMobileSidebarOnboardingBadge(enabled) + return null +} + +const mountedRoots: Root[] = [] +const listDevices = vi.fn() + +async function renderHookProbe(props: ComponentProps = {}): Promise { + const container = document.createElement('div') + document.body.appendChild(container) + const root = createRoot(container) + mountedRoots.push(root) + await act(async () => { + root.render(createElement(HookProbe, props)) + }) +} describe('mobile sidebar onboarding badge', () => { - it('does not query mobile devices when the sidebar button is hidden', () => { - expect(shouldLoadMobileSidebarOnboardingBadge(false, false)).toBe(false) + beforeEach(() => { + vi.useRealTimers() + latestHookState = null + listDevices.mockReset() + _resetPairedMobileDevicesCacheForTests() + window.localStorage.clear() + Object.defineProperty(window, 'api', { + configurable: true, + value: { + mobile: { + listDevices + } + } + }) }) - it('queries mobile devices only while visible and undismissed', () => { - expect(shouldLoadMobileSidebarOnboardingBadge(true, false)).toBe(true) - expect(shouldLoadMobileSidebarOnboardingBadge(true, true)).toBe(false) + afterEach(async () => { + await act(async () => { + for (const root of mountedRoots.splice(0)) { + root.unmount() + } + }) + document.body.innerHTML = '' + vi.useRealTimers() + }) + + it('does not show when the sidebar button is hidden', () => { + expect(shouldShowMobileSidebarOnboardingBadge(false, false)).toBe(false) + }) + + it('shows only while enabled and undismissed', () => { + expect(shouldShowMobileSidebarOnboardingBadge(true, false)).toBe(true) + expect(shouldShowMobileSidebarOnboardingBadge(true, true)).toBe(false) + }) + + it('marks a paired device after shared mobile devices load', async () => { + listDevices.mockResolvedValue({ + devices: [{ deviceId: 'phone-1', name: 'Phone', pairedAt: 1, lastSeenAt: 2 }] + }) + + await renderHookProbe() + + await vi.waitFor(() => expect(latestHookState?.hasPairedDevice).toBe(true)) + expect(latestHookState?.visible).toBe(false) + }) + + it('shows the badge after shared mobile devices load empty', async () => { + replacePairedMobileDevices([]) + + await renderHookProbe() + + expect(latestHookState?.visible).toBe(true) + expect(latestHookState?.hasPairedDevice).toBe(false) + }) + + it('does not show the badge on a failed load and recovers on window focus', async () => { + listDevices.mockRejectedValueOnce(new Error('startup race')).mockResolvedValueOnce({ + devices: [{ deviceId: 'phone-1', name: 'Phone', pairedAt: 1, lastSeenAt: 2 }] + }) + + await renderHookProbe() + + // Failed initial load: loaded flips true but the error flag suppresses the + // onboarding badge so it can't masquerade as "no devices paired". + await vi.waitFor(() => expect(listDevices).toHaveBeenCalledTimes(1)) + await vi.waitFor(() => expect(latestHookState?.visible).toBe(false)) + expect(latestHookState?.hasPairedDevice).toBe(false) + + // Regaining focus re-fetches and un-wedges the persistent sidebar consumer. + await act(async () => { + window.dispatchEvent(new Event('focus')) + }) + + await vi.waitFor(() => expect(latestHookState?.hasPairedDevice).toBe(true)) }) }) diff --git a/src/renderer/src/components/sidebar/mobile-sidebar-onboarding-badge.ts b/src/renderer/src/components/sidebar/mobile-sidebar-onboarding-badge.ts index 0203823985b..f4442421944 100644 --- a/src/renderer/src/components/sidebar/mobile-sidebar-onboarding-badge.ts +++ b/src/renderer/src/components/sidebar/mobile-sidebar-onboarding-badge.ts @@ -1,4 +1,5 @@ -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useState } from 'react' +import { usePairedMobileDevices } from '../mobile/paired-mobile-devices' const DISMISS_KEY = 'orca.mobile.sidebar-onboarding-dismissed' @@ -10,7 +11,7 @@ function readDismissed(): boolean { } } -export function shouldLoadMobileSidebarOnboardingBadge( +export function shouldShowMobileSidebarOnboardingBadge( enabled: boolean, dismissed: boolean ): boolean { @@ -22,32 +23,11 @@ export function shouldLoadMobileSidebarOnboardingBadge( // permanently, mirroring the once-and-done feel of an inbox unread dot. export function useMobileSidebarOnboardingBadge(enabled = true): { visible: boolean + hasPairedDevice: boolean dismiss: () => void } { const [dismissed, setDismissed] = useState(() => readDismissed()) - const [hasPairedDevice, setHasPairedDevice] = useState(null) - - useEffect(() => { - if (!shouldLoadMobileSidebarOnboardingBadge(enabled, dismissed)) { - return - } - let cancelled = false - void (async () => { - try { - const result = await window.api.mobile.listDevices() - if (!cancelled) { - setHasPairedDevice(result.devices.length > 0) - } - } catch { - if (!cancelled) { - setHasPairedDevice(false) - } - } - })() - return () => { - cancelled = true - } - }, [dismissed, enabled]) + const mobileDevices = usePairedMobileDevices({ enabled }) const dismiss = useCallback(() => { if (dismissed) { @@ -62,7 +42,14 @@ export function useMobileSidebarOnboardingBadge(enabled = true): { }, [dismissed]) return { - visible: enabled && !dismissed && hasPairedDevice === false, + visible: + shouldShowMobileSidebarOnboardingBadge(enabled, dismissed) && + mobileDevices.loaded && + // Why: a failed load also lands loaded:true with no devices; don't show + // the onboarding badge until we actually know the device list is empty. + !mobileDevices.error && + !mobileDevices.hasPairedDevice, + hasPairedDevice: mobileDevices.hasPairedDevice, dismiss } } diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 534bcdd32fc..1575481e14b 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -10800,7 +10800,7 @@ "0bad5b07c8": "GitHub and Linear", "d047197480": "GitHub · Linear", "a4c3f7b7aa": "Tasks", - "d33d7a9c29": "orca  ·  feat/mobile-page", + "d33d7a9c29": "orca · feat/mobile-page", "25d6e8a491": "feat/mobile-page", "c791677f2f": "Resume", "cf3f98fa3f": "Disconnected", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 9cd63fa6b22..34481f1ac18 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -10777,7 +10777,7 @@ "0bad5b07c8": "GitHub と Linear", "d047197480": "GitHub · Linear", "a4c3f7b7aa": "タスク", - "d33d7a9c29": "orca  ·  feat/mobile-page", + "d33d7a9c29": "orca · feat/mobile-page", "25d6e8a491": "feat/mobile-page", "c791677f2f": "再開する", "cf3f98fa3f": "切断されました", From 8d5c6746448e61413da6c8cbfc094f8d98015460 Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Sun, 19 Jul 2026 01:19:46 -0700 Subject: [PATCH 065/137] fix(win): launch Store-installed PowerShell 7 in terminals (#9437) Resolve Microsoft Store PowerShell App Execution Aliases to their real package executable before ConPTY spawn, while preserving explicit PowerShell choices and the existing fail-safe fallback chain. --- .../windows-powershell-executable.test.ts | 44 +++++++++++++++++++ .../windows-powershell-executable.ts | 37 +++++++++++++--- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/main/providers/windows-powershell-executable.test.ts b/src/main/providers/windows-powershell-executable.test.ts index 12502b35a9f..6db78fe3359 100644 --- a/src/main/providers/windows-powershell-executable.test.ts +++ b/src/main/providers/windows-powershell-executable.test.ts @@ -19,6 +19,8 @@ const WINDOWS_POWERSHELL = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powe // The Microsoft Store App Execution Alias stub for pwsh — a zero-byte reparse // point under WindowsApps that ConPTY's CreateProcessW rejects with error 5. const PWSH_STORE_ALIAS = 'C:\\Users\\dev\\AppData\\Local\\Microsoft\\WindowsApps\\pwsh.exe' +const PWSH_STORE_EXE = + 'C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.6.3.0_x64__8wekyb3d8bbwe\\pwsh.exe' describe('resolveWindowsPowerShellExecutablePath', () => { it('returns null on non-Windows platforms', () => { @@ -80,6 +82,35 @@ describe('resolveWindowsPowerShellExecutablePath', () => { expect(resolved).not.toBe(PWSH_STORE_ALIAS) }) + it('repro: resolves a Store App Execution Alias to its real package executable', () => { + const resolved = resolveWindowsPowerShellExecutablePath('pwsh.exe', { + platform: 'win32', + env: { + ...WIN_ENV, + Path: 'C:\\Users\\dev\\AppData\\Local\\Microsoft\\WindowsApps' + }, + isRealExecutable: (p) => p === PWSH_STORE_EXE, + resolveAppExecutionAlias: (p) => (p === PWSH_STORE_ALIAS ? PWSH_STORE_EXE : null) + }) + expect(resolved).toBe(PWSH_STORE_EXE) + }) + + it.each(['pwsh.exe', PWSH_STORE_ALIAS, 'C:\\Tools\\powershell.exe'])( + 'rejects unsafe Store alias target %s', + (target) => { + const resolved = resolveWindowsPowerShellExecutablePath('pwsh.exe', { + platform: 'win32', + env: { + ...WIN_ENV, + Path: 'C:\\Users\\dev\\AppData\\Local\\Microsoft\\WindowsApps' + }, + isRealExecutable: (p) => p === target, + resolveAppExecutionAlias: (p) => (p === PWSH_STORE_ALIAS ? target : null) + }) + expect(resolved).toBeNull() + } + ) + it('returns null for pwsh when no real executable is found', () => { expect( resolveWindowsPowerShellExecutablePath('pwsh.exe', { @@ -112,6 +143,19 @@ describe('resolveWindowsPowerShellSpawnChain', () => { expect(chain).not.toContain(PWSH_STORE_ALIAS) }) + it('uses the real Store package executable ahead of Windows PowerShell', () => { + const chain = resolveWindowsPowerShellSpawnChain('pwsh.exe', { + platform: 'win32', + env: { + ...WIN_ENV, + Path: 'C:\\Users\\dev\\AppData\\Local\\Microsoft\\WindowsApps' + }, + isRealExecutable: (p) => p === PWSH_STORE_EXE || p === WINDOWS_POWERSHELL, + resolveAppExecutionAlias: (p) => (p === PWSH_STORE_ALIAS ? PWSH_STORE_EXE : null) + }) + expect(chain).toEqual([PWSH_STORE_EXE, WINDOWS_POWERSHELL, WIN_ENV.ComSpec]) + }) + it('orders PATH-resolved pwsh before Windows PowerShell when standard roots miss', () => { const chain = resolveWindowsPowerShellSpawnChain('pwsh.exe', { platform: 'win32', diff --git a/src/main/providers/windows-powershell-executable.ts b/src/main/providers/windows-powershell-executable.ts index d04eebf552e..d8654829afd 100644 --- a/src/main/providers/windows-powershell-executable.ts +++ b/src/main/providers/windows-powershell-executable.ts @@ -1,4 +1,4 @@ -import { existsSync, statSync } from 'node:fs' +import { existsSync, readlinkSync, statSync } from 'node:fs' import { win32 as pathWin32 } from 'node:path' /** Dependency seams so the resolver can be unit-tested without a real Windows @@ -7,6 +7,8 @@ export type WindowsPowerShellResolveOptions = { env?: NodeJS.ProcessEnv /** Returns true when the candidate path is a real, non-empty executable. */ isRealExecutable?: (path: string) => boolean + /** Resolves a Store App Execution Alias to its package executable. */ + resolveAppExecutionAlias?: (path: string) => string | null platform?: NodeJS.Platform } @@ -37,6 +39,15 @@ function defaultIsRealExecutable(candidate: string): boolean { } } +function defaultResolveAppExecutionAlias(candidate: string): string | null { + try { + const target = readlinkSync(candidate) + return pathWin32.resolve(pathWin32.dirname(candidate), target) + } catch { + return null + } +} + function readEnv(env: NodeJS.ProcessEnv, names: string[]): string | undefined { for (const name of names) { const value = env[name] @@ -140,9 +151,8 @@ export function getWindowsCmdPath(env: NodeJS.ProcessEnv = process.env): string /** * Resolve a PowerShell family name to a real absolute executable path. * - * Returns null when no real executable can be found for the family (e.g. pwsh.exe - * is only present as a Store App Execution Alias stub). Callers fall back to the - * next link in the Windows shell chain when this returns null. + * Returns null when no real executable or safely resolvable Store alias target + * can be found for the family. Callers then try the next Windows shell fallback. */ export function resolveWindowsPowerShellExecutablePath( family: 'pwsh.exe' | 'powershell.exe', @@ -154,6 +164,8 @@ export function resolveWindowsPowerShellExecutablePath( } const env = options.env ?? process.env const isRealExecutable = options.isRealExecutable ?? defaultIsRealExecutable + const resolveAppExecutionAlias = + options.resolveAppExecutionAlias ?? defaultResolveAppExecutionAlias if (family === 'powershell.exe') { const windowsPowerShell = getWindowsPowerShellPath(env) @@ -161,7 +173,22 @@ export function resolveWindowsPowerShellExecutablePath( } for (const candidate of getPwshCandidatePaths(env)) { - if (!isWindowsAppExecutionAliasPath(candidate) && isRealExecutable(candidate)) { + if (isWindowsAppExecutionAliasPath(candidate)) { + const target = resolveAppExecutionAlias(candidate) + // Why: ConPTY cannot launch the alias reparse point, but its real Store + // package target is a normal executable that preserves the user's pwsh choice. + if ( + target && + pathWin32.isAbsolute(target) && + pathWin32.basename(target).toLowerCase() === 'pwsh.exe' && + !isWindowsAppExecutionAliasPath(target) && + isRealExecutable(target) + ) { + return pathWin32.normalize(target) + } + continue + } + if (isRealExecutable(candidate)) { return candidate } } From 5848550b5ebd08098cad3e2b9086fd2472a52944 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 19 Jul 2026 02:34:39 -0700 Subject: [PATCH 066/137] fix(composer): show full worktree path on hover in Run on dropdown (#9398) * fix(composer): show full worktree path on hover in Run on dropdown Co-authored-by: Orca * refactor(composer): use native title tooltip for Run on paths Co-authored-by: Orca * fix(composer): reliable native-style tooltip for Run on paths The native title attribute won't re-trigger between adjacent rows in Chromium, so the tooltip flickered/dropped as you moved down the list. Use the shared (controlled) Tooltip instead, styled compact + arrowless to read like an OS tooltip; the app-level provider's skip-delay makes row-to-row hovers reliable. Co-authored-by: Orca * fix(composer): stop Run on path tooltip wedging shut Radix's uncontrolled hover state could flash the tooltip open then wedge it closed inside the cmdk list (shared pointer-transit state getting stuck), so re-hovering never reopened it. Drive open state with a controlled hover-intent handler (open after a delay, close after a short grace) so re-entry always reopens and transient enter/leave thrash debounces to a stable state. Co-authored-by: Orca * fix(composer): rebuild Run on path tooltip as a portal (no loop/wedge) Both the native title and Radix Tooltip (controlled or not) misbehaved inside the cmdk list: flashing, wedging shut, or opening/closing in a loop. Replace with a self-contained tooltip that renders a fixed, pointer-events-none portal on document.body and only changes state on pointer enter/leave/down (never pointermove). Opening it cannot reflow the list or become the hover target, so it structurally cannot create the enter/leave feedback loop. Co-authored-by: Orca * fix(composer): trigger Run on path tooltip on the path line, always below - Scope the hover trigger to the truncated path line itself (not the whole row), so only hovering the path reveals it. - Always position the tooltip below the hovered line for predictable placement (was flipping above near the viewport bottom). Co-authored-by: Orca * fix(composer): anchor Run on path tooltip under the path line The old placement anchored to the window's right half when the trigger sat past viewport-center, which shoved the tooltip to the far right edge of the window, disconnected from the dropdown. Always left-align it directly under the hovered path line and cap max-width to the remaining viewport width so long paths wrap instead of overflowing. Co-authored-by: Orca --------- Co-authored-by: Orca --- .../components/NewWorkspaceComposerCard.tsx | 86 ++++++++++++++++++- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/components/NewWorkspaceComposerCard.tsx b/src/renderer/src/components/NewWorkspaceComposerCard.tsx index 7bd2800e002..238360230f2 100644 --- a/src/renderer/src/components/NewWorkspaceComposerCard.tsx +++ b/src/renderer/src/components/NewWorkspaceComposerCard.tsx @@ -2,6 +2,7 @@ composer card markup together so the inline and modal variants share one UI surface without splitting the controlled form into hard-to-follow fragments. */ import React from 'react' +import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { @@ -233,6 +234,87 @@ type WorkspaceRunTargetComboboxProps = { onRecipeChange?: (recipeId: string | null) => void } +type HostPathTooltipPosition = { + left: number + top: number + maxWidth: number +} + +const HOST_PATH_TOOLTIP_DELAY_MS = 400 +const HOST_PATH_TOOLTIP_VIEWPORT_GAP_PX = 8 +const HOST_PATH_TOOLTIP_TRIGGER_GAP_PX = 4 + +function HostPathTooltip({ path }: { path: string }): React.JSX.Element { + const tooltipId = React.useId() + const timerRef = React.useRef | null>(null) + const pointerInsideRef = React.useRef(false) + const [position, setPosition] = React.useState(null) + + const hideTooltip = React.useCallback((): void => { + pointerInsideRef.current = false + if (timerRef.current !== null) { + clearTimeout(timerRef.current) + timerRef.current = null + } + setPosition(null) + }, []) + + React.useEffect(() => hideTooltip, [hideTooltip]) + + const handlePointerEnter = React.useCallback((event: React.PointerEvent): void => { + pointerInsideRef.current = true + const trigger = event.currentTarget + if (timerRef.current !== null) { + clearTimeout(timerRef.current) + } + timerRef.current = setTimeout(() => { + timerRef.current = null + if (!pointerInsideRef.current || !trigger.isConnected) { + return + } + const rect = trigger.getBoundingClientRect() + // Why: anchor directly under the hovered path line (left-aligned to it), and cap the width to + // the space remaining to the viewport edge so a long path wraps instead of flying off-screen. + const left = Math.max(HOST_PATH_TOOLTIP_VIEWPORT_GAP_PX, rect.left) + setPosition({ + left, + top: rect.bottom + HOST_PATH_TOOLTIP_TRIGGER_GAP_PX, + maxWidth: window.innerWidth - left - HOST_PATH_TOOLTIP_VIEWPORT_GAP_PX + }) + }, HOST_PATH_TOOLTIP_DELAY_MS) + }, []) + + return ( + <> + {/* Trigger is the truncated path line itself, so the tooltip only appears when hovering it. */} +
    + {path} +
    + {/* Why: a fixed, pointer-transparent portal cannot reflow cmdk or become the hover target. */} + {position + ? createPortal( + , + document.body + ) + : null} + + ) +} + function WorkspaceRunTargetCombobox({ hostOptions, hostValue, @@ -341,9 +423,7 @@ function WorkspaceRunTargetCombobox({
    {option.label}
    -
    - {option.path} -
    +
    ))} From 80e632282c87fce15ea80a7db4105ea68a509f6b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:35:17 +0000 Subject: [PATCH 067/137] Update README downloads badge --- docs/assets/readme-downloads.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/assets/readme-downloads.svg b/docs/assets/readme-downloads.svg index 3fd53ea36f9..525f31215a9 100644 --- a/docs/assets/readme-downloads.svg +++ b/docs/assets/readme-downloads.svg @@ -1,5 +1,5 @@ - - downloads: 6.3m + + downloads: 6.4m @@ -15,7 +15,7 @@ downloads downloads - 6.3m - 6.3m + 6.4m + 6.4m From 0b71f3bfbaf52c1ebb76510c1184cd801714cda8 Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Sun, 19 Jul 2026 11:27:29 -0700 Subject: [PATCH 068/137] test(e2e): prove the terminal daemon survives a main-process crash on Windows (#7742) (#9311) * test(e2e): prove the terminal daemon survives a main-process crash on Windows (#7742) Add a win-crash-survival e2e harness (sibling to win-update-e2e) that force-kills ONLY the packaged app's real Electron main (resolved via app.evaluate -> process.pid, /F no /T) and asserts the detached orca-terminal-daemon.exe plus its ConPTY shell survive with no pwsh 0xE9 FailFast, then that a relaunch re-adopts the SAME daemon and the reattached UI binds to the SAME survivor shell (proved via a per-shell env sentinel read back through the restored terminal). This guards the #7742 fix (standalone relocated daemon that outlives main death) against regression. A directional `--expect orphaned` profile fails on a fixed build, keeping the survival assertions honest. Windows-only; reuses win-update-e2e app-driver/daemon-process modules. * test(e2e): harden Windows crash-survival proof * test(ci): keep crash survival gate durable * test(e2e): tolerate restart hydration navigation * test(e2e): prove exact shell input after crash * perf(ci): avoid crash harness installer rebuilds * test(ci): harden crash survival evidence and cost * test(e2e): fail closed on authoritative crash target * test(e2e): fail closed on crash liveness evidence --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> --- .github/workflows/win-crash-survival-e2e.yml | 195 ++++++++ config/reliability-gates.jsonc | 94 +++- .../scripts/win-crash-survival-e2e.test.mjs | 227 +++++++++ package.json | 1 + tests/e2e/helpers/orca-restart.ts | 67 ++- .../e2e/orca-restart-navigation.unit.test.ts | 20 + tools/win-crash-survival-e2e/README.md | 114 +++++ tools/win-crash-survival-e2e/cli-args.mjs | 130 ++++++ .../crash-assertions.mjs | 101 ++++ tools/win-crash-survival-e2e/crash-step.mjs | 121 +++++ .../daemon-identity.mjs | 28 ++ .../win-crash-survival-e2e/reattach-proof.mjs | 19 + tools/win-crash-survival-e2e/run.mjs | 432 ++++++++++++++++++ tools/win-update-e2e/README.md | 6 + tools/win-update-e2e/app-driver.mjs | 137 +++++- tools/win-update-e2e/assertions.mjs | 7 +- tools/win-update-e2e/daemon-processes.mjs | 18 +- tools/win-update-e2e/powershell-runner.mjs | 5 + 18 files changed, 1665 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/win-crash-survival-e2e.yml create mode 100644 config/scripts/win-crash-survival-e2e.test.mjs create mode 100644 tests/e2e/orca-restart-navigation.unit.test.ts create mode 100644 tools/win-crash-survival-e2e/README.md create mode 100644 tools/win-crash-survival-e2e/cli-args.mjs create mode 100644 tools/win-crash-survival-e2e/crash-assertions.mjs create mode 100644 tools/win-crash-survival-e2e/crash-step.mjs create mode 100644 tools/win-crash-survival-e2e/daemon-identity.mjs create mode 100644 tools/win-crash-survival-e2e/reattach-proof.mjs create mode 100644 tools/win-crash-survival-e2e/run.mjs diff --git a/.github/workflows/win-crash-survival-e2e.yml b/.github/workflows/win-crash-survival-e2e.yml new file mode 100644 index 00000000000..e4c388ceb18 --- /dev/null +++ b/.github/workflows/win-crash-survival-e2e.yml @@ -0,0 +1,195 @@ +name: Windows Crash-Survival E2E + +# Why: proves the detached daemon-host relocation makes terminal sessions SURVIVE +# a CRASH of Orca's main process (GitHub #7742), the companion guarantee to the +# update-survival harness. Builds an (unsigned) installer FROM THIS BRANCH, +# silent-installs it, opens a terminal, force-kills ONLY the app main (no +# tree-kill), and asserts the daemon + shell stay alive, a relaunch adopts the +# same daemon and shell, and post-crash input causes no pwsh FailFast. Targeted +# pull requests keep it as a durable regression gate. A CI runner is the only +# safe place to install — see the relocation post-mortem. + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + paths: + - '.github/workflows/win-crash-survival-e2e.yml' + - 'package.json' + - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' + - 'electron.vite.config.ts' + - 'build-plugins/**' + - 'config/electron-builder.config.cjs' + - 'config/patches/**' + - 'config/scripts/ensure-native-runtime.mjs' + - 'config/scripts/rebuild-native-deps.mjs' + - 'native/**' + - 'resources/win32/**' + - 'src/main/daemon/**' + - 'src/main/index.ts' + - 'src/main/ipc/pty*.ts' + - 'src/main/persistence.ts' + - 'src/main/providers/**' + - 'src/main/pty/**' + - 'src/main/startup/first-window-startup-services.ts' + - 'src/main/window/attach-main-window-services.ts' + - 'src/preload/**' + - 'src/renderer/src/App.tsx' + - 'src/renderer/src/components/terminal-pane/**' + - 'src/renderer/src/hooks/useIpcEvents.ts' + - 'src/renderer/src/lib/pane-manager/**' + - 'src/renderer/src/lib/session-write-subscriber.ts' + - 'src/renderer/src/lib/workspace-session-host-persistence.ts' + - 'src/renderer/src/store/slices/terminals.ts' + - 'src/shared/pty-session-id-format.ts' + - 'tools/win-crash-survival-e2e/**' + - 'tools/win-update-e2e/**' + # Why: source tests and benchmarks do not change the packaged artifact; + # their own verify jobs cover them without spending a Windows build slot. + - '!src/**/*.test.*' + - '!src/**/*.bench.*' + workflow_dispatch: + inputs: + expect: + description: Expected outcome profile + required: true + type: choice + default: survival + options: + - survival + - orphaned + +permissions: + contents: read + +concurrency: + group: win-crash-survival-e2e-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + crash-survival: + name: crash-survival (packaged build) + runs-on: windows-2022 + timeout-minutes: 50 + env: + EXPECT: ${{ inputs.expect || 'survival' }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + # This job only builds and runs the harness locally; it never pushes. + persist-credentials: false + + # Why: setup-node can restore pnpm's content-addressed store only after + # the pnpm binary exists, avoiding repeat dependency downloads per run. + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: package.json + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + # Why: cache the built installer by production build inputs, excluding + # tests/reliability metadata so harness-only edits skip electron-builder. + # The daemon relocation lives under src/, so product changes still rebuild. + - name: Cache branch installer + id: cache-installer + uses: actions/cache@v4 + with: + path: dist/orca-windows-setup.exe + key: >- + crash-survival-installer-${{ hashFiles( + 'src/**', + '!src/**/*.test.*', + '!src/**/*.bench.*', + 'config/**', + '!config/**/*.test.*', + '!config/reliability-gates.jsonc', + '!config/max-lines-baseline.txt', + '!config/vitest.config.ts', + 'build-plugins/**', + 'native/**', + 'resources/**', + 'electron.vite.config.ts', + 'tsconfig.json', + 'vite.web.config.ts', + '.npmrc', + 'package.json', + 'pnpm-lock.yaml', + 'pnpm-workspace.yaml' + ) }} + + # Why: production edits miss the installer cache by design, but Electron + # and NSIS downloads are lockfile-owned and need not be fetched again. + - name: Cache electron-builder downloads + if: steps.cache-installer.outputs.cache-hit != 'true' + uses: actions/cache@v5 + with: + path: | + ~\AppData\Local\electron\Cache + ~\AppData\Local\electron-builder\Cache + key: crash-survival-electron-builder-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + crash-survival-electron-builder- + + - name: Build Windows installer (unsigned) + if: steps.cache-installer.outputs.cache-hit != 'true' + run: | + node config/scripts/ensure-native-runtime.mjs --runtime=electron + pnpm run build:desktop + pnpm exec electron-builder --config config/electron-builder.config.cjs --win --publish never + + # Why: silent-install the branch build so the crash harness has a packaged + # Orca.exe to drive. A clean CI runner installs to the default per-user + # location (%LOCALAPPDATA%\Programs\Orca), which the harness auto-locates. + - name: Silent-install branch build + shell: pwsh + run: | + $exe = "dist/orca-windows-setup.exe" + if (-not (Test-Path $exe)) { throw "Installer not found at $exe" } + Start-Process -FilePath $exe -ArgumentList '/S' -Wait + $installed = Join-Path $env:LOCALAPPDATA 'Programs/Orca/Orca.exe' + if (-not (Test-Path $installed)) { + $installed = Join-Path $env:LOCALAPPDATA 'Programs/orca/Orca.exe' + } + if (-not (Test-Path $installed)) { throw "Installed Orca.exe not found after silent install" } + "ORCA_EXE=$installed" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + + # Why: crash ONLY the app main and assert the relocated daemon + its shell + # survive, then that relaunch adopts them and input causes no pwsh FailFast. + - name: Run crash-survival harness + id: harness + shell: pwsh + env: + ORCA_E2E_DIAG_DIR: artifacts/diag + run: | + New-Item -ItemType Directory -Force artifacts | Out-Null + $log = "artifacts/crash-survival-output.log" + node tools/win-crash-survival-e2e/run.mjs ` + --expect "$env:EXPECT" ` + --exe-path "$env:ORCA_EXE" ` + --soak-seconds 8 2>&1 | Tee-Object -FilePath $log + exit $LASTEXITCODE + + - name: Upload crash-survival output + if: always() + uses: actions/upload-artifact@v7 + with: + name: win-crash-survival-output + path: | + artifacts/crash-survival-output.log + artifacts/diag/** + retention-days: 7 + if-no-files-found: warn diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 9846d201cfe..3c49afcb582 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "updatedAt": "2026-07-17", + "updatedAt": "2026-07-19", "policy": { "maturityLevels": [ "experimental", @@ -1693,6 +1693,98 @@ ], "demotionRule": "Cannot promote without deterministic user-visible oracle, failure artifacts, and stable runtime history." }, + { + "id": "terminal-session.windows-main-crash-survival", + "title": "A Windows main-process crash preserves and reattaches daemon terminals", + "maturity": "experimental", + "protection": "partial", + "owner": "terminal-platform", + "layer": "windows-packaged-electron-daemon", + "surfaces": [ + "Electron main-process crash", + "detached terminal daemon", + "ConPTY shell survival", + "packaged relaunch reattach" + ], + "platforms": ["windows"], + "providers": ["daemon"], + "coveredPlatforms": ["windows"], + "coveredProviders": ["daemon"], + "coverageNotes": "A packaged Windows CI run force-kills the launched instance's real Electron main without tree-killing, then proves the same scoped daemon and interactive PowerShell survive, no FailFast event is observed, and relaunch input reaches the same shell. Focused cross-platform unit tests fail closed on unavailable event-log or PID-liveness evidence, stale or ambiguous daemon identity, and incomplete reattach assertions.", + "motivatingLinks": [ + "https://github.com/stablyai/orca/issues/7742", + "https://github.com/stablyai/orca/pull/9311" + ], + "invariant": "On packaged Windows, abrupt death of Orca's Electron main process must not terminate or replace the userData-scoped terminal daemon or its live ConPTY shell, and a relaunch must adopt that exact daemon and route terminal input to that exact surviving shell without a PowerShell 0xE9 FailFast.", + "oracle": "Resolve the launched instance's real main PID from inside Electron, force-kill only that PID, require it to die, require one command-line-scoped daemon PID and the stamped interactive shell PID to remain live, relaunch with persisted state, require the daemon PID to remain identical, read the exact shell PID and a per-shell environment sentinel back through the exact restored tab, then require a successful Windows Application event-log query with zero matching pwsh FailFast events across the full crash-to-input window.", + "commands": [ + "pnpm exec vitest run --config config/vitest.config.ts config/scripts/win-crash-survival-e2e.test.mjs", + "node tools/win-crash-survival-e2e/run.mjs --expect survival --exe-path \"$env:ORCA_EXE\" --soak-seconds 8" + ], + "testFiles": [ + "config/scripts/win-crash-survival-e2e.test.mjs", + "tools/win-crash-survival-e2e/run.mjs" + ], + "assertionRefs": [ + { + "file": "config/scripts/win-crash-survival-e2e.test.mjs", + "assertions": [ + "survival requires the crash antecedent, daemon and shell liveness, unchanged daemon identity, zero FailFast events, and same-shell reattach", + "event-log query failure cannot be converted into zero FailFast events", + "malformed event-log and PID-liveness evidence fails closed", + "stale, missing, or ambiguous userData-scoped daemon identity fails closed", + "the packaged survival proof remains wired to targeted pull requests without a duplicate branch-push run" + ] + }, + { + "file": "tools/win-crash-survival-e2e/run.mjs", + "assertions": [ + "force-killing only the real Electron main leaves the exact scoped daemon and stamped interactive shell alive", + "packaged relaunch adopts the unchanged daemon and reads the survivor shell's environment sentinel through the restored terminal" + ] + } + ], + "evidenceRuns": [ + { + "date": "2026-07-18", + "runner": "ci", + "platform": "windows", + "command": "node tools/win-crash-survival-e2e/run.mjs --expect survival --exe-path \"$env:ORCA_EXE\" --soak-seconds 8", + "result": "passed", + "durationSeconds": 61, + "summary": "The packaged branch build's real main died; the same daemon and shell PIDs survived; the event-log scan found zero FailFast events; relaunch adopted the unchanged daemon; and terminal input read the survivor shell sentinel back." + } + ], + "runtimeBudget": { + "p95Seconds": 120, + "scope": "installed-app crash/relaunch harness, excluding installer build and dependency setup" + }, + "flakeHistory": { + "status": "unknown", + "evidence": "One green packaged Windows CI run is recorded; the durable pull-request trigger must accumulate repeated history before promotion." + }, + "redGreenEvidence": { + "status": "partial", + "evidence": "The inverse orphaned profile failed against the fixed packaged build because the daemon stayed live and no FailFast occurred. A genuinely pre-relocation packaged build has not yet been retained as a CI red fixture." + }, + "performanceBudget": { + "required": true, + "evidence": "Production code is unchanged. The harness has bounded 500ms liveness polls, one 8-second crash soak, two scoped daemon identity scans plus teardown rediscovery, a 60-second cap on every synchronous PowerShell probe, a 5-second cap on Electron main-PID resolution, cleared and unreferenced close deadlines, a 50-minute job timeout, concurrency cancellation, and targeted production-path filtering that excludes source tests and benchmarks. The installer cache is keyed by every production build input; the job also restores the pnpm store and restores Electron/NSIS downloads only when an installer rebuild is required. The measured packaged harness completed in 61 seconds." + }, + "promotionCriteria": [ + "Collect at least 100 consecutive targeted Windows PR or soak passes over 14 days with zero unexplained flakes.", + "Retain a pre-relocation packaged red fixture or equivalent fault injection that makes the survival profile fail for daemon death and shell loss.", + "Keep the live oracle fail-closed for crash delivery, event-log access, daemon identity, and same-shell reattach." + ], + "knownGaps": [ + "The packaged journey proves one terminal end to end; concurrent restoration across the user's multi-terminal, multi-worktree layout is not exercised live.", + "The gate covers a Windows local daemon-backed PowerShell terminal; WSL-backed shells are not exercised.", + "SSH, remote-runtime, relay, mobile, macOS, and Linux paths are unaffected by the Windows relocated ConPTY host contract and are not exercised here.", + "The Application event-log query is machine-wide, so an unrelated PowerShell crash on a non-isolated runner can false-fail the gate.", + "Standalone daemon self-crash and renderer-only crash containment remain separate invariants." + ], + "demotionRule": "Keep experimental or quarantine with a linked harness defect if the Windows job flakes; never weaken the crash, identity, event-log, or same-shell reattach oracle to obtain a pass." + }, { "id": "terminal-platform.windows-conpty-liveness", "title": "Windows ConPTY terminals stay input-live, render-live, and geometry-live", diff --git a/config/scripts/win-crash-survival-e2e.test.mjs b/config/scripts/win-crash-survival-e2e.test.mjs new file mode 100644 index 00000000000..c59f98ffb90 --- /dev/null +++ b/config/scripts/win-crash-survival-e2e.test.mjs @@ -0,0 +1,227 @@ +import { readFileSync } from 'node:fs' +import { describe, expect, it, vi } from 'vitest' +import { parseArgs } from '../../tools/win-crash-survival-e2e/cli-args.mjs' +import { buildCrashAssertions } from '../../tools/win-crash-survival-e2e/crash-assertions.mjs' +import { scanPwshFailFast } from '../../tools/win-crash-survival-e2e/crash-step.mjs' +import { selectScopedDaemon } from '../../tools/win-crash-survival-e2e/daemon-identity.mjs' +import { + reattachSentinelMatches, + selectCreatedTabId +} from '../../tools/win-crash-survival-e2e/reattach-proof.mjs' +import { quotePowerShellLiteral } from '../../tools/win-update-e2e/powershell-runner.mjs' +import { closeApp, resolveElectronMainPid } from '../../tools/win-update-e2e/app-driver.mjs' +import { isPidAlive } from '../../tools/win-update-e2e/daemon-processes.mjs' + +describe('win-crash-survival-e2e proof contracts', () => { + it('keeps the packaged proof wired as a targeted pull-request gate', () => { + const workflow = readFileSync('.github/workflows/win-crash-survival-e2e.yml', 'utf8') + expect(workflow).toMatch(/^ pull_request:/m) + expect(workflow).not.toMatch(/^ push:/m) + expect(workflow).toContain("- 'src/main/daemon/**'") + expect(workflow).toContain("- 'src/main/index.ts'") + expect(workflow).toContain("- 'src/main/ipc/pty*.ts'") + expect(workflow).toContain("- 'src/main/startup/first-window-startup-services.ts'") + expect(workflow).toContain("- 'src/main/window/attach-main-window-services.ts'") + expect(workflow).toContain("- 'src/preload/**'") + expect(workflow).toContain("- 'src/renderer/src/components/terminal-pane/**'") + expect(workflow).toContain("- 'src/renderer/src/store/slices/terminals.ts'") + expect(workflow).toContain("- '!src/**/*.test.*'") + expect(workflow).toContain("- '!src/**/*.bench.*'") + expect(workflow).toContain('--expect "$env:EXPECT"') + expect(workflow).toContain('exit $LASTEXITCODE') + expect(workflow).toContain("'!config/**/*.test.*'") + expect(workflow).toContain("'!src/**/*.test.*'") + expect(workflow).toContain("'!src/**/*.bench.*'") + expect(workflow).toContain("'!config/reliability-gates.jsonc'") + expect(workflow).toContain("'resources/**'") + expect(workflow).toContain('cache: pnpm') + expect(workflow.indexOf('- name: Setup Node.js')).toBeGreaterThan( + workflow.indexOf('- name: Setup pnpm') + ) + expect(workflow).toContain("if: steps.cache-installer.outputs.cache-hit != 'true'") + expect(workflow).toContain('crash-survival-electron-builder-') + }) + + it('requires the full survival oracle, including daemon identity and reattach', () => { + const base = { + profile: 'survival', + mainDied: true, + daemonAliveAfterCrash: true, + shellAliveAfterCrash: true, + failFastEvents: [], + preDaemonPid: 101, + postDaemonPid: 101, + postDaemonAlive: true, + reattachProven: true + } + expect(buildCrashAssertions(base).every((entry) => entry.pass)).toBe(true) + expect( + buildCrashAssertions({ ...base, postDaemonPid: 202 }).find((entry) => + entry.name.startsWith('relaunch adopts') + )?.pass + ).toBe(false) + expect( + buildCrashAssertions({ ...base, reattachProven: false }).find((entry) => + entry.name.startsWith('reattached UI') + )?.pass + ).toBe(false) + }) + + it('scans for FailFast only after the post-crash input probe', () => { + const harness = readFileSync('tools/win-crash-survival-e2e/run.mjs', 'utf8') + const scanIndex = harness.indexOf('const { events: failFastEvents }') + const probeIndex = harness.indexOf('reattachProven = await proveReattachedShell') + expect(scanIndex).not.toBe(-1) + expect(probeIndex).not.toBe(-1) + expect(scanIndex).toBeGreaterThan(probeIndex) + }) + + it('fails closed when the Windows event log query fails', () => { + let command = '' + expect(() => + scanPwshFailFast(1234, (received) => { + command = received + return { code: 1, stdout: '', stderr: 'access denied', error: null } + }) + ).toThrow('pwsh-failfast scan failed (exit 1): access denied') + expect(command).toContain('-ErrorAction Stop') + expect(command).toContain('NoMatchingEventsFound*') + }) + + it('accepts an empty event result only from the serialized evidence envelope', () => { + expect( + scanPwshFailFast(1234, () => ({ + code: 0, + stdout: '{"events":[]}', + stderr: '', + error: null + })) + ).toEqual({ events: [] }) + expect(() => + scanPwshFailFast(1234, () => ({ code: 0, stdout: '', stderr: '', error: null })) + ).toThrow('pwsh-failfast scan returned no JSON output') + expect(() => + scanPwshFailFast(1234, () => ({ code: 0, stdout: '{}', stderr: '', error: null })) + ).toThrow('without an events envelope') + }) + + it('fails closed when PID liveness evidence is unavailable', () => { + expect(isPidAlive(42, () => ({ code: 0, stdout: 'alive\n', stderr: '', error: null }))).toBe( + true + ) + expect(isPidAlive(42, () => ({ code: 0, stdout: 'dead\n', stderr: '', error: null }))).toBe( + false + ) + expect(() => + isPidAlive(42, () => ({ code: 1, stdout: '', stderr: 'access denied', error: null })) + ).toThrow('PID liveness probe failed (exit 1): access denied') + expect(() => isPidAlive(42, () => ({ code: 0, stdout: '', stderr: '', error: null }))).toThrow( + 'PID liveness probe returned an invalid state' + ) + }) + + it('uses the scoped live process as daemon authority', () => { + expect( + selectScopedDaemon( + [{ pid: 42, appVersion: '1.2.3' }], + [{ pid: 42, commandLine: 'daemon-entry.js --socket scoped' }] + ) + ).toEqual({ pid: 42, appVersion: '1.2.3' }) + expect(() => + selectScopedDaemon( + [{ pid: 41, appVersion: 'stale' }], + [{ pid: 42, commandLine: 'daemon-entry.js --socket scoped' }] + ) + ).toThrow('daemon PID file does not match scoped live daemon 42') + expect(() => selectScopedDaemon([], [])).toThrow('expected exactly one') + expect(() => selectScopedDaemon([], [{ pid: 1 }, { pid: 2 }])).toThrow('expected exactly one') + }) + + it('rejects CLI typos and duplicate value flags before launching', () => { + const baseArgs = ['--expect', 'survival', '--exe-path', process.execPath] + expect(parseArgs(baseArgs).errors).toEqual([]) + expect(parseArgs([...baseArgs, '--exe-pathh', process.execPath]).errors).toContain( + 'Unknown argument: --exe-pathh' + ) + expect(parseArgs([...baseArgs, '--expect', 'orphaned']).errors).toContain( + 'Duplicate argument: --expect' + ) + }) + + it('quotes apostrophes in generated PowerShell path literals', () => { + expect(quotePowerShellLiteral("C:\\Users\\O'Brien\\shell.pid")).toBe( + "'C:\\Users\\O''Brien\\shell.pid'" + ) + }) + + it('targets exactly the terminal tab created before the crash', () => { + expect(selectCreatedTabId(['agent-tab'], ['agent-tab', 'terminal-tab'])).toBe('terminal-tab') + expect(() => selectCreatedTabId(['agent-tab'], ['agent-tab'])).toThrow( + 'expected exactly one created terminal tab, found 0' + ) + expect(() => selectCreatedTabId([], ['first', 'second'])).toThrow( + 'expected exactly one created terminal tab, found 2' + ) + }) + + it('requires both the per-shell canary and exact survivor pid', () => { + expect(reattachSentinelMatches('1660|canary\r\n', 'canary', 1660)).toBe(true) + expect(reattachSentinelMatches('1770|canary', 'canary', 1660)).toBe(false) + expect(reattachSentinelMatches('1660|other', 'canary', 1660)).toBe(false) + expect(reattachSentinelMatches('1660|canary|extra', 'canary', 1660)).toBe(false) + }) + + it('requires the real packaged main for the crash proof but permits fallback cleanup', async () => { + const harness = readFileSync('tools/win-crash-survival-e2e/run.mjs', 'utf8') + expect(harness).toContain( + 'resolveElectronMainPid(session.app, { allowLauncherFallback: false })' + ) + expect( + await resolveElectronMainPid({ + evaluate: async () => 222, + process: () => ({ pid: 111 }) + }) + ).toBe(222) + const unavailableApp = { + evaluate: async () => { + throw new Error('main unavailable') + }, + process: () => ({ pid: 111 }) + } + expect( + await resolveElectronMainPid(unavailableApp, { allowLauncherFallback: false }) + ).toBeNull() + expect(await resolveElectronMainPid(unavailableApp)).toBe(111) + }) + + it('bounds main PID resolution when the Electron connection is wedged', async () => { + vi.useFakeTimers() + try { + const result = resolveElectronMainPid( + { + evaluate: () => new Promise(() => {}), + process: () => ({ pid: 333 }) + }, + { timeoutMs: 20 } + ) + await vi.advanceTimersByTimeAsync(20) + await expect(result).resolves.toBe(333) + } finally { + vi.useRealTimers() + } + }) + + it('releases the close deadline after a successful app close', async () => { + vi.useFakeTimers() + try { + await closeApp({ + evaluate: async () => 444, + process: () => ({ pid: 333 }), + close: async () => {} + }) + expect(vi.getTimerCount()).toBe(0) + } finally { + vi.useRealTimers() + } + }) +}) diff --git a/package.json b/package.json index 170bfc99565..5035c621383 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "test:e2e:ssh-docker-watcher-isolation": "node config/scripts/run-ssh-docker-watcher-isolation-e2e.mjs", "test:e2e:source-control-scale": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/source-control-large-file-count.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1", "win-update-e2e": "node tools/win-update-e2e/run.mjs", + "win-crash-survival-e2e": "node tools/win-crash-survival-e2e/run.mjs", "test:e2e:ssh-codex-artifacts-repro": "node config/scripts/run-ssh-codex-artifacts-repro-e2e.mjs", "test:e2e:headful": "pnpm run ensure:electron-runtime && npx playwright test --config tests/playwright.config.ts --project electron-headful", "test:e2e:computer": "vitest run --config tests/e2e/vitest.config.ts", diff --git a/tests/e2e/helpers/orca-restart.ts b/tests/e2e/helpers/orca-restart.ts index 742d34ffdda..06d500066f0 100644 --- a/tests/e2e/helpers/orca-restart.ts +++ b/tests/e2e/helpers/orca-restart.ts @@ -141,23 +141,25 @@ export async function attachRepoAndOpenTerminal(page: Page, repoPath: string): P await expect .poll( () => - page.evaluate(async (repoId) => { - const store = window.__store - if (!store) { - return false - } - // Why: repos.add emits a concurrent refresh whose generation can - // supersede this fetch; poll until either refresh publishes the repo. - await store.getState().fetchRepos() - const repo = store.getState().repos.find((candidate) => candidate.id === repoId) - if (!repo) { - return false - } - // Why: this restart fixture uses the global e2e repo, whose seeded Git - // worktree is external to Orca's workspace root after the visibility rollout. - await store.getState().updateRepo(repo.id, { externalWorktreeVisibility: 'show' }) - return true - }, repoId), + readRestartRendererState(() => + page.evaluate(async (repoId) => { + const store = window.__store + if (!store) { + return false + } + // Why: repos.add emits a concurrent refresh whose generation can + // supersede this fetch; poll until either refresh publishes the repo. + await store.getState().fetchRepos() + const repo = store.getState().repos.find((candidate) => candidate.id === repoId) + if (!repo) { + return false + } + // Why: this restart fixture uses the global e2e repo, whose seeded Git + // worktree is external to Orca's workspace root after the visibility rollout. + await store.getState().updateRepo(repo.id, { externalWorktreeVisibility: 'show' }) + return true + }, repoId) + ), { timeout: 30_000, message: `attachRepoAndOpenTerminal: expected e2e repo to be loaded: ${repoPath}` @@ -178,14 +180,16 @@ export async function attachRepoAndOpenTerminal(page: Page, repoPath: string): P await expect .poll( async () => - page.evaluate(async (repoId) => { - const store = window.__store - if (!store) { - return false - } - await store.getState().fetchWorktrees(repoId) - return (store.getState().worktreesByRepo[repoId]?.length ?? 0) > 0 - }, repoId), + readRestartRendererState(() => + page.evaluate(async (repoId) => { + const store = window.__store + if (!store) { + return false + } + await store.getState().fetchWorktrees(repoId) + return (store.getState().worktreesByRepo[repoId]?.length ?? 0) > 0 + }, repoId) + ), { timeout: 15_000, message: 'attachRepoAndOpenTerminal: seeded worktree never surfaced in the store' @@ -217,6 +221,19 @@ export async function attachRepoAndOpenTerminal(page: Page, repoPath: string): P return worktreeId } +export async function readRestartRendererState(read: () => Promise): Promise { + try { + return await read() + } catch (error) { + // Why: initial hydration can replace the renderer document; the enclosing + // state poll must retry that transition without hiding other failures. + if (error instanceof Error && error.message.includes('Execution context was destroyed')) { + return null + } + throw error + } +} + function isValidGitRepo(repoPath: string): boolean { if (!repoPath || !existsSync(repoPath)) { return false diff --git a/tests/e2e/orca-restart-navigation.unit.test.ts b/tests/e2e/orca-restart-navigation.unit.test.ts new file mode 100644 index 00000000000..5dcd02a4cae --- /dev/null +++ b/tests/e2e/orca-restart-navigation.unit.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest' +import { readRestartRendererState } from './helpers/orca-restart' + +describe('restart renderer state polling', () => { + it('treats document replacement as pending state', async () => { + await expect( + readRestartRendererState(async () => { + throw new Error('Execution context was destroyed, most likely because of a navigation.') + }) + ).resolves.toBeNull() + }) + + it('does not hide non-navigation renderer failures', async () => { + await expect( + readRestartRendererState(async () => { + throw new Error('fetchWorktrees failed') + }) + ).rejects.toThrow('fetchWorktrees failed') + }) +}) diff --git a/tools/win-crash-survival-e2e/README.md b/tools/win-crash-survival-e2e/README.md new file mode 100644 index 00000000000..edfb5704815 --- /dev/null +++ b/tools/win-crash-survival-e2e/README.md @@ -0,0 +1,114 @@ +# win-crash-survival-e2e — packaged crash-survival proof harness + +**Windows only.** Proves that a **crash of Orca's main process** does not orphan +open terminal PTYs — the regression behind +[GitHub #7742](https://github.com/stablyai/orca/issues/7742) — +with machine-checkable assertions against an **already-installed, packaged** +`Orca.exe`. + +## Why this exists + +On Windows, when Orca's main/renderer process crashed, open terminal PTYs were +orphaned and PowerShell hard-crashed with a `0xE9` "No process is on the other +end of the pipe" `FailFast`. Root cause: the terminal **daemon** (which hosts the +ConPTYs) died together with the main process, severing the console pipe. + +The fix re-architected the daemon into a standalone, relocated +`orca-terminal-daemon.exe` (see +[`src/main/daemon/daemon-host-relocation.ts`](../../src/main/daemon/daemon-host-relocation.ts)) +that is spawned **detached** and **survives main-process death**. + +There is already a harness proving the daemon survives a Windows **update** +([`tools/win-update-e2e`](../win-update-e2e/README.md)). This harness proves the +daemon survives a **crash** of the main process, so that guarantee can't silently +regress. It **reuses win-update-e2e's shared modules** (app driver, daemon +discovery, onboarding seed, PowerShell runner, platform guard, table renderer) +and adds only the crash step + its assertions. + +## What it does + +1. **Launch** the installed `Orca.exe` under an isolated `userData` dir + (`ORCA_E2E_USER_DATA_DIR`), seeded with a fresh profile (onboarding dismissed + plus one throwaway git repo), then open a plain terminal tab (the seeded + workspace opens an agent tab, not a bare shell). +2. **Stamp the interactive shell** — typing DIRECTLY into it (not a nested + `powershell`), set a per-shell env sentinel `ORCA_CRASH_SENTINEL=` and + record the shell's own `$PID`. The command finishes fast, leaving the shell + idle at a live PSReadLine prompt — the exact state that FailFasts with `0xE9` + on a broken build. +3. **Record** the daemon PID and the real Electron **main** PID (resolved via + `app.evaluate(() => process.pid)` — the launched instance's own main, not the + launcher stub `app.process()` returns, and not a machine-wide scan). +4. **Crash** — `taskkill /F /PID ` with **no `/T`** and **no + graceful close**. This kills ONLY the real main of the instance this harness + launched, never a scanned or image-named process, and never the process tree — + a real crash does not tree-kill the detached daemon. Then **prove the crash + landed** (poll the main PID until dead). +5. **Assert survival**: the daemon PID and the same interactive shell PID are + still alive after the crash soak. +6. **Relaunch** (same `userData`, no reseed) and assert the daemon PID is + **unchanged** (the new main **adopts** the surviving daemon instead of forking + a new one) and that the reattached UI is bound to the **same survivor shell** — + a bounded, readiness-aware command on the exact restored tab reads back both + `ORCA_CRASH_SENTINEL` and the shell's `$PID`, which a freshly re-spawned shell + would not carry. +7. **Scan the full crash-to-input window** and require the Windows **Application + event log** to contain **zero** pwsh `FailFast` / `0xE9` events (matched by + crash-reporter provider+id, not fragile Message text). Scanning after the + reattach keystroke catches shells that fail only on their next console read. +8. **Teardown** — close the relaunched app, then kill this run's scoped daemon + **tree** (re-discovered fresh via `findDaemonProcesses(userData)`, which the + surviving shell is a descendant of) and remove the temp profile. It never kills + a PID captured earlier in the run (a recycled PID could hit an innocent + process), never installs/uninstalls, and never touches any other Orca on the box. + +Exit code is `0` when every non-informational assertion passes, else `1` (`2` for +a CLI usage error). + +## Usage + +```powershell +pnpm win-crash-survival-e2e --expect survival +# or explicitly point at an installed exe: +node tools/win-crash-survival-e2e/run.mjs --expect survival --exe-path "C:\Users\\AppData\Local\Programs\orca\Orca.exe" +``` + +### Flags + +| Flag | Meaning | +| -------------------- | ---------------------------------------------------------------------------------------------- | +| `--expect ` | Assertion profile (required): `survival` or `orphaned` (see below) | +| `--exe-path ` | Installed `Orca.exe` to drive (default: per-user install under `%LOCALAPPDATA%\Programs\Orca`) | +| `--soak-seconds ` | Post-crash observation window before relaunch (default `8`) | +| `--keep-profile` | Skip temp-profile cleanup (debugging) | + +### Profiles + +- **`survival`** — the fixed behavior and the baseline that must keep passing: + the main crash actually lands, yet the daemon + the same interactive shell PID + survive, zero pwsh `FailFast` events fire, a relaunch **adopts** the same daemon + PID, and the reattached UI reads back the survivor shell's env sentinel. +- **`orphaned`** — the directional inverse describing the **old broken #7742** + behavior. Daemon death is the **primary** signal (deterministic); pwsh + `FailFast` / `0xE9` is **secondary** — faithful only because the shell is left + idle at a live PSReadLine prompt (which queries the severed console). On a fixed + build this profile is **expected to fail**, proving the survival assertions are + not vacuous. It is **not exercised in CI** (`workflow_dispatch` is unavailable on + a non-default branch) and the `0xE9` only reproduces on a genuinely broken build. + +## Safety + +- **Never installs, updates, or uninstalls anything** — it only launches an + existing exe against an isolated `userData` dir. +- The crash kills **only** the real Electron main of the instance this harness + launched — resolved via `app.evaluate(() => process.pid)` (not the launcher stub + `app.process()` returns) — `/F` with **no `/T`**. It never `taskkill`s by image + name or a scanned pid, so a developer's live Orca (a different `userData`, out of + scope) is untouched. +- **Teardown never kills a PID captured earlier in the run** (a recycled PID could + hit an innocent process): daemon cleanup re-discovers this run's daemon fresh via + a `userData`-scoped `findDaemonProcesses`, and the surviving shell is torn down + as a descendant of that daemon tree. +- Daemon discovery is **scoped** to this run's `userData` path, so it never + matches the many other daemons a dev box or CI runner can host. +- Windows-only (`assertWin32`); it no-ops with a clear error off win32. diff --git a/tools/win-crash-survival-e2e/cli-args.mjs b/tools/win-crash-survival-e2e/cli-args.mjs new file mode 100644 index 00000000000..535d8b65ca7 --- /dev/null +++ b/tools/win-crash-survival-e2e/cli-args.mjs @@ -0,0 +1,130 @@ +// Argument parsing for `node run.mjs` (crash-survival harness). +// +// Unlike win-update-e2e, this harness installs nothing: it drives an ALREADY +// installed, packaged Orca.exe. So its only source is `--exe-path` (defaulting +// to the per-user install location). Exactly one profile (--expect) is required. + +import { existsSync } from 'node:fs' +import path from 'node:path' +import { locateInstalledExe } from '../win-update-e2e/installer-steps.mjs' + +const VALID_PROFILES = new Set(['survival', 'orphaned']) +const VALUE_FLAGS = new Set(['--expect', '--exe-path', '--soak-seconds']) +const BOOLEAN_FLAGS = new Set(['--keep-profile']) + +const USAGE = ` +win-crash-survival-e2e — packaged crash-survival proof harness (Windows only) + +Proves that force-killing ONLY Orca's main process (a real crash, no tree-kill) +leaves the detached terminal daemon + its pwsh shell alive, with no pwsh FailFast +(0xE9 "No process is on the other end of the pipe"), and that a relaunch ADOPTS +the surviving daemon instead of forking a new one. See #7742. + +Usage: + node tools/win-crash-survival-e2e/run.mjs --expect [--exe-path ] [options] + +Required: + --expect Assertion profile: + survival = the fixed behavior (daemon + shell survive + the main crash, zero pwsh FailFast, relaunch adopts + the same daemon PID). This is what must keep passing. + orphaned = the OLD broken #7742 behavior (daemon dies + with main, pwsh FailFasts). Directional inverse used + to prove the harness actually catches a regression; + on a fixed build this profile is EXPECTED to fail. + +Options: + --exe-path Installed Orca.exe to drive (default: the per-user + install under %LOCALAPPDATA%\\Programs\\Orca). The + harness NEVER installs/uninstalls — it only launches + this exe against an isolated userData dir. + --soak-seconds Post-crash observation window before relaunch (default: 8) + --keep-profile Skip temp-profile cleanup at teardown (for debugging) + -h, --help Show this help +` + +export function parseArgs(argv) { + if (argv.includes('-h') || argv.includes('--help')) { + return { help: true, usage: USAGE } + } + + const exePathFlagPresent = argv.includes('--exe-path') + const opts = { + // Only auto-locate on win32: off-win32 this would needlessly spawn powershell, + // and run.mjs asserts win32 first so the platform message wins over any + // "no Orca.exe found" default-resolution error. + exePath: + takeValue(argv, '--exe-path') ?? + (process.platform === 'win32' ? locateInstalledExe() : undefined) ?? + undefined, + expect: takeValue(argv, '--expect'), + soakSeconds: Number(takeValue(argv, '--soak-seconds') ?? '8'), + keepProfile: argv.includes('--keep-profile'), + usage: USAGE + } + + const errors = validate(opts, exePathFlagPresent, argv) + return { ...opts, errors } +} + +function validate(opts, exePathFlagPresent, argv) { + const errors = [] + errors.push(...validateArgShape(argv)) + if (!opts.expect) { + errors.push('Missing --expect ') + } else if (!VALID_PROFILES.has(opts.expect)) { + errors.push(`Invalid --expect "${opts.expect}" (expected survival or orphaned)`) + } + // Distinguish "--exe-path omitted" (fall back to auto-locate) from + // "--exe-path with no value" (a mistake that must fail, not silently default). + if (exePathFlagPresent && takeValue(argv, '--exe-path') === undefined) { + errors.push('--exe-path requires a path value') + } else if (!opts.exePath) { + errors.push( + 'No installed Orca.exe found under %LOCALAPPDATA%\\Programs — pass --exe-path ' + ) + } else if (!existsSync(opts.exePath)) { + errors.push(`--exe-path does not exist: ${opts.exePath}`) + } else if (!path.isAbsolute(opts.exePath)) { + errors.push(`--exe-path must be an absolute path (got "${opts.exePath}")`) + } + if (!Number.isFinite(opts.soakSeconds) || opts.soakSeconds < 0) { + errors.push('--soak-seconds must be a non-negative number') + } + return errors +} + +function validateArgShape(argv) { + const errors = [] + const seen = new Set() + for (let index = 0; index < argv.length; index++) { + const arg = argv[index] + if (!VALUE_FLAGS.has(arg) && !BOOLEAN_FLAGS.has(arg)) { + errors.push(`Unknown argument: ${arg}`) + continue + } + if (seen.has(arg)) { + errors.push(`Duplicate argument: ${arg}`) + } + seen.add(arg) + if (VALUE_FLAGS.has(arg)) { + const value = argv[index + 1] + if (value !== undefined && !value.startsWith('--')) { + index++ + } + } + } + return errors +} + +function takeValue(argv, flag) { + const idx = argv.indexOf(flag) + if (idx < 0) { + return undefined + } + const value = argv[idx + 1] + if (value === undefined || value.startsWith('--')) { + return undefined + } + return value +} diff --git a/tools/win-crash-survival-e2e/crash-assertions.mjs b/tools/win-crash-survival-e2e/crash-assertions.mjs new file mode 100644 index 00000000000..1e01938cb4c --- /dev/null +++ b/tools/win-crash-survival-e2e/crash-assertions.mjs @@ -0,0 +1,101 @@ +// Profile assertions for the crash-survival proof. +// +// Two directional profiles over the SAME observed evidence, so CI can prove the +// harness catches a regression (an --expect that disagrees with reality fails): +// survival — the fixed #7742 behavior. The main crash actually landed, yet the +// detached daemon and the same interactive shell PID survive, ZERO pwsh +// FailFast events fire, a relaunch ADOPTS the same daemon PID, and the +// reattached UI reads back the survivor shell's env sentinel (proving +// keystrokes reach the same PTY, not a re-spawn). +// orphaned — the OLD broken behavior. The daemon dies with main (primary) and, +// because the shell was left idle at a live PSReadLine prompt, pwsh FailFasts +// with 0xE9 (secondary). On a fixed build this profile is EXPECTED to fail. +// NOTE: the orphaned direction is NOT exercised in CI (workflow_dispatch is +// unavailable on a non-default branch) and the 0xE9 only reproduces on a +// genuinely broken build — it is the directional inverse, documented and +// locally runnable, not a gate. + +function assertion(name, pass, expected, actual, detail = '') { + return { name, pass, expected, actual, detail } +} + +/** Build the ordered assertion list for the run's profile. */ +export function buildCrashAssertions(ctx) { + // The crash actually landing is the shared antecedent for BOTH profiles: without + // it, every survival/orphan signal below would be meaningless. + const mainDied = assertion( + 'main process actually died (crash landed)', + Boolean(ctx.mainDied), + 'main pid dead after taskkill', + String(ctx.mainDied) + ) + const profileAssertions = + ctx.profile === 'orphaned' ? orphanedAssertions(ctx) : survivalAssertions(ctx) + return [mainDied, ...profileAssertions] +} + +function survivalAssertions(ctx) { + const failFastDetail = (ctx.failFastEvents ?? []) + .slice(0, 3) + .map((e) => `${e.provider}#${e.id}@${e.timeCreated}`) + .join('; ') + return [ + assertion( + 'daemon survives main crash (PID still alive)', + Boolean(ctx.daemonAliveAfterCrash), + `daemon pid ${ctx.preDaemonPid} alive after crash`, + String(ctx.daemonAliveAfterCrash) + ), + assertion( + 'interactive shell survives main crash (PID still alive)', + Boolean(ctx.shellAliveAfterCrash), + `shell pid ${ctx.shellPid} alive after crash`, + String(ctx.shellAliveAfterCrash) + ), + assertion( + 'zero pwsh FailFast / 0xE9 during crash window', + (ctx.failFastEvents ?? []).length === 0, + '0 pwsh FailFast events', + `${(ctx.failFastEvents ?? []).length} events`, + failFastDetail + ), + assertion( + 'relaunch adopts the same daemon (PID unchanged)', + ctx.preDaemonPid != null && + ctx.preDaemonPid === ctx.postDaemonPid && + Boolean(ctx.postDaemonAlive), + `daemon pid ${ctx.preDaemonPid} adopted (not re-forked)`, + `post pid ${ctx.postDaemonPid} (alive: ${ctx.postDaemonAlive})` + ), + assertion( + 'reattached UI is bound to the SAME survivor shell (env sentinel reads back)', + Boolean(ctx.reattachProven), + 'survivor shell env sentinel readable via reattached terminal', + String(ctx.reattachProven) + ) + ] +} + +function orphanedAssertions(ctx) { + // The directional inverse: this profile passes ONLY on a build that reproduces + // the #7742 orphaning. Daemon death is the PRIMARY inverse (deterministic); + // pwsh FailFast is SECONDARY — faithful here only because the shell is left idle + // at a live PSReadLine prompt, which is what queries the severed console and + // triggers the 0xE9 on a broken build. Running this profile against a fixed + // build makes these FAIL, which is the proof the survival assertions above are + // not vacuous. + return [ + assertion( + 'daemon dies with main crash (old #7742 behavior)', + ctx.daemonAliveAfterCrash === false, + `daemon pid ${ctx.preDaemonPid} dead after crash`, + `alive: ${ctx.daemonAliveAfterCrash}` + ), + assertion( + 'pwsh FailFast / 0xE9 fired during crash window', + (ctx.failFastEvents ?? []).length > 0, + '>=1 pwsh FailFast event', + `${(ctx.failFastEvents ?? []).length} events` + ) + ] +} diff --git a/tools/win-crash-survival-e2e/crash-step.mjs b/tools/win-crash-survival-e2e/crash-step.mjs new file mode 100644 index 00000000000..43dc18f04cd --- /dev/null +++ b/tools/win-crash-survival-e2e/crash-step.mjs @@ -0,0 +1,121 @@ +// The abrupt main-process crash + its Windows event-log forensics. +// +// GitHub #7742: when Orca's main/renderer process died on Windows, the terminal +// daemon (which hosts the ConPTYs) died with it, severing the console pipe, and +// PowerShell hard-crashed with a 0xE9 "No process is on the other end of the +// pipe" FailFast. The fix relocates the daemon into a standalone, detached +// orca-terminal-daemon.exe that SURVIVES main death (src/main/daemon/ +// daemon-host-relocation.ts). This module reproduces the crash and scans for the +// pwsh FailFast that must no longer occur. + +import { execFileSync } from 'node:child_process' +import { runCommandSync } from '../win-update-e2e/powershell-runner.mjs' + +/** + * Abruptly crash ONLY the app main process. `/F` (force) with NO `/T` (tree) is + * the single load-bearing detail: a real main crash does not tree-kill the + * detached daemon, so tree-killing here — or closing gracefully — would leave the + * daemon alive for the wrong reason and make the survival assertion pass + * vacuously. Kills exactly `pid` (the real Electron main of the instance the + * harness launched, resolved via app.evaluate -> process.pid), never a scanned + * or image-named process, so a live user Orca on the same box is never touched. + */ +export function crashMainProcess(pid) { + if (!Number.isInteger(pid) || pid <= 0) { + throw new Error(`crashMainProcess: refusing to kill invalid pid ${pid}`) + } + // NO '/T': tree-killing would take the detached daemon down with the main and + // defeat the entire point of the test. + execFileSync('taskkill', ['/F', '/PID', String(pid)], { stdio: 'ignore' }) +} + +/** + * Scan the Windows Application event log for pwsh/powershell FailFast crashes in + * the crash window (from `sinceMs`). The #7742 signature is a 0xE9 exit / "No + * process is on the other end of the pipe" / FailFast (0xc0000409 stack-overrun), + * reported as an Application Error (#1000), Windows Error Reporting (#1001), or + * .NET Runtime (#1026) event for pwsh.exe. Matching by provider+id (not Message + * text alone) matters because Message can be null when the provider's resource + * DLL does not render — a null-Message crash from those reporters is still counted + * unless its text positively rules out pwsh, so we never miss a real FailFast. + * + * The scan is MACHINE-WIDE (Get-WinEvent has no per-process filter): on an + * isolated CI runner nothing else crashes in the ~10s window, so it can only ever + * false-FAIL (an unrelated crash), never false-PASS. Zero events is the decisive + * proof the ConPTY pipe was NOT severed. Returns + * { events: [{ id, provider, timeCreated, message }] }. + */ +export function scanPwshFailFast(sinceMs, runCommand = runCommandSync) { + // Build the start boundary from Unix ms directly (no locale-dependent string + // parse) and back it off 5s for clock skew between Node's Date.now() and the + // event-log timestamps. + const startMs = Math.max(0, Math.floor(sinceMs) - 5000) + const command = [ + `$start = [System.DateTimeOffset]::FromUnixTimeMilliseconds(${startMs}).LocalDateTime`, + `$crashProviders = @('Application Error','Windows Error Reporting','.NET Runtime')`, + `$crashIds = @(1000,1001,1026)`, + `$sig = '0xe9|other end of the pipe|FailFast|0xc0000409|Faulting application name: pwsh|Faulting application name: powershell'`, + // @() guards the PS 5.1 single-item unwrap: one match must still serialize as + // an array, or the JS side sees an object and .length explodes. + `try {`, + ` $sourceEvents = @(Get-WinEvent -FilterHashtable @{ LogName='Application'; StartTime=$start } -ErrorAction Stop)`, + `} catch {`, + // Get-WinEvent reports an empty window as an error; that is valid zero-event + // evidence, while permissions/service/query failures must still fail closed. + ` if ($_.FullyQualifiedErrorId -like 'NoMatchingEventsFound*') { $sourceEvents = @() } else { throw }`, + `}`, + `$events = @($sourceEvents |`, + ` Where-Object {`, + // A crash-reporter event referencing pwsh/powershell (or whose Message failed + // to render at all) counts; otherwise fall back to explicit signature text. + ` (($crashProviders -contains $_.ProviderName) -and ($crashIds -contains $_.Id) -and`, + ` ((-not $_.Message) -or ($_.Message -match 'pwsh|powershell'))) -or`, + ` ($_.Message -and ($_.Message -match 'pwsh|powershell') -and ($_.Message -match $sig)) })`, + `$out = @($events | ForEach-Object {`, + ` $msg = if ($_.Message) { $_.Message.Substring(0, [Math]::Min(400, $_.Message.Length)) } else { '' }`, + ` [pscustomobject]@{ id = $_.Id; provider = $_.ProviderName; timeCreated = $_.TimeCreated.ToString('o'); message = $msg } })`, + `ConvertTo-Json -InputObject @{ events = $out } -Depth 4 -Compress` + ].join('\n') + + const { stdout, stderr, code, error } = runCommand(command) + if (error) { + throw new Error(`pwsh-failfast scan spawn failed: ${error.message}`) + } + // Why: unavailable event-log evidence cannot count as proof that no FailFast + // occurred; fail the harness instead of converting an empty error into zero. + if (code !== 0) { + throw new Error(`pwsh-failfast scan failed (exit ${code}): ${stderr.trim()}`) + } + const trimmed = stdout.trim() + if (!trimmed) { + // Why: the script always serializes an events envelope, including for zero + // matches; empty stdout means the load-bearing evidence never arrived. + throw new Error('pwsh-failfast scan returned no JSON output') + } + let parsed + try { + parsed = JSON.parse(trimmed) + } catch (parseError) { + throw new Error( + `pwsh-failfast scan returned non-JSON (exit ${code}): ${parseError.message}\n` + + `stdout:\n${trimmed}\nstderr:\n${stderr}` + ) + } + if ( + !parsed || + typeof parsed !== 'object' || + Array.isArray(parsed) || + !Object.hasOwn(parsed, 'events') || + parsed.events == null + ) { + // Why: only the explicit envelope proves the query completed; malformed + // JSON must not be indistinguishable from an authoritative zero-event result. + throw new Error('pwsh-failfast scan returned JSON without an events envelope') + } + const raw = parsed.events + if (!Array.isArray(raw) && typeof raw !== 'object') { + throw new Error('pwsh-failfast scan returned an invalid events envelope') + } + const events = Array.isArray(raw) ? raw : raw ? [raw] : [] + return { events } +} diff --git a/tools/win-crash-survival-e2e/daemon-identity.mjs b/tools/win-crash-survival-e2e/daemon-identity.mjs new file mode 100644 index 00000000000..268b23be86d --- /dev/null +++ b/tools/win-crash-survival-e2e/daemon-identity.mjs @@ -0,0 +1,28 @@ +// Resolve one authoritative daemon identity from the userData-scoped process +// scan and its advisory PID file metadata. + +/** + * Require exactly one live, scoped daemon and cross-check any PID-file record. + * The process command line owns identity; a stale PID file must never turn an + * unrelated recycled PID into false crash-survival evidence. + */ +export function selectScopedDaemon(pidFiles, scannedProcesses) { + if (scannedProcesses.length !== 1) { + throw new Error(`expected exactly one userData-scoped daemon, found ${scannedProcesses.length}`) + } + + const processRecord = scannedProcesses[0] + const numericPidFiles = pidFiles.filter((record) => Number.isInteger(record.pid)) + const matchingPidFile = numericPidFiles.find((record) => record.pid === processRecord.pid) + if (numericPidFiles.length > 0 && !matchingPidFile) { + throw new Error( + `daemon PID file does not match scoped live daemon ${processRecord.pid} ` + + `(recorded: ${numericPidFiles.map((record) => record.pid).join(', ')})` + ) + } + + return { + pid: processRecord.pid, + appVersion: matchingPidFile?.appVersion ?? null + } +} diff --git a/tools/win-crash-survival-e2e/reattach-proof.mjs b/tools/win-crash-survival-e2e/reattach-proof.mjs new file mode 100644 index 00000000000..9ecec5ba2e1 --- /dev/null +++ b/tools/win-crash-survival-e2e/reattach-proof.mjs @@ -0,0 +1,19 @@ +/** Resolve the one tab introduced by the harness. Ambiguity would make the + * post-crash input oracle capable of targeting an unrelated restored tab. */ +export function selectCreatedTabId(beforeTabIds, afterTabIds) { + const before = new Set(beforeTabIds) + const created = afterTabIds.filter((tabId) => !before.has(tabId)) + if (created.length !== 1) { + throw new Error(`expected exactly one created terminal tab, found ${created.length}`) + } + return created[0] +} + +/** The canary rejects a respawned shell; the PID additionally proves that input + * reached the exact survivor whose liveness was checked during the crash. */ +export function reattachSentinelMatches(raw, expectedCanary, expectedShellPid) { + const [pidPart, canaryPart, extraPart] = raw.trim().split('|') + return ( + extraPart === undefined && canaryPart === expectedCanary && Number(pidPart) === expectedShellPid + ) +} diff --git a/tools/win-crash-survival-e2e/run.mjs b/tools/win-crash-survival-e2e/run.mjs new file mode 100644 index 00000000000..4f9d8b242b0 --- /dev/null +++ b/tools/win-crash-survival-e2e/run.mjs @@ -0,0 +1,432 @@ +// win-crash-survival-e2e — packaged crash-survival proof harness. +// +// GitHub #7742: on Windows, when Orca's main/renderer process crashed, open +// terminal PTYs were orphaned and PowerShell hard-crashed with a 0xE9 "No +// process is on the other end of the pipe" FailFast, because the terminal daemon +// (hosting the ConPTYs) died together with the main process and severed the +// console pipe. The fix relocates the daemon into a standalone, detached +// orca-terminal-daemon.exe that survives main death (src/main/daemon/ +// daemon-host-relocation.ts). win-update-e2e proves the daemon survives a +// Windows UPDATE; this harness proves it survives a CRASH of the main process. +// +// Flow: launch the installed app (isolated userData) → open a plain terminal and, +// typing DIRECTLY into the interactive shell, stamp a per-shell env sentinel plus +// that shell's own $PID (leaving it idle at a live PSReadLine prompt, the faithful +// #7742 crash condition) → force-kill ONLY the real app main (no tree-kill, no +// graceful close) → prove the main actually died, then that the daemon + that same +// shell PID stay alive with no pwsh FailFast → relaunch, adopt the surviving +// daemon, and prove the reattached UI is bound to the SAME survivor shell by +// reading back its env sentinel (a re-spawned shell would not have it). +// Windows-only. See README.md. + +import { mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import path from 'node:path' +import { execFileSync } from 'node:child_process' +import { assertWin32 } from '../win-update-e2e/platform-guard.mjs' +import { + launchInstalledApp, + ensureTerminal, + dismissOverlays, + createTerminalTab, + listTabIds, + typeLine, + sendCtrlC, + waitForTerminalReady, + closeApp, + captureFailureDiagnostics, + resolveElectronMainPid +} from '../win-update-e2e/app-driver.mjs' +import { + findDaemonProcesses, + isPidAlive, + readDaemonPidFiles +} from '../win-update-e2e/daemon-processes.mjs' +import { createSeededRepo, buildFreshProfile } from '../win-update-e2e/onboarding-profile.mjs' +import { renderTable, allPassed } from '../win-update-e2e/assertions.mjs' +import { quotePowerShellLiteral } from '../win-update-e2e/powershell-runner.mjs' +import { parseArgs } from './cli-args.mjs' +import { crashMainProcess, scanPwshFailFast } from './crash-step.mjs' +import { buildCrashAssertions } from './crash-assertions.mjs' +import { selectScopedDaemon } from './daemon-identity.mjs' +import { reattachSentinelMatches, selectCreatedTabId } from './reattach-proof.mjs' + +const SORTABLE_TAB = '[data-testid="sortable-tab"]' +// The per-shell env var stamped into the interactive shell; reading it back after +// relaunch proves keystrokes reach the SAME survivor shell (a fresh re-spawn lacks it). +const SENTINEL_ENV = 'ORCA_CRASH_SENTINEL' + +function log(step, msg) { + console.log(`[win-crash-survival-e2e] ${step}: ${msg}`) +} + +async function main() { + const opts = parseArgs(process.argv.slice(2)) + if (opts.help) { + console.log(opts.usage) + return 0 + } + // Assert win32 BEFORE surfacing arg errors so an off-win32 invocation gets the + // clear platform message, not a confusing "no Orca.exe found" default-resolution + // failure. + assertWin32('win-crash-survival-e2e') + if (opts.errors?.length) { + console.error(`Argument errors:\n - ${opts.errors.join('\n - ')}\n${opts.usage}`) + return 2 + } + + const runId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}` + const canary = `ORCA-CRASH-SENTINEL-${runId}` + const runDir = mkdtempSync(path.join(tmpdir(), `orca-win-crash-e2e-${runId}-`)) + const userDataDir = path.join(runDir, 'userData') + const shellPidFile = path.join(runDir, 'shell.pid') + const reattachFile = path.join(runDir, 'reattach.txt') + + log('setup', `runId=${runId} runDir=${runDir} profile=${opts.expect} exe=${opts.exePath}`) + + const ctx = { session: null } + const diagDir = process.env.ORCA_E2E_DIAG_DIR || path.join(runDir, 'diag') + let passed = false + try { + passed = await runProof(ctx, { opts, canary, runDir, userDataDir, shellPidFile, reattachFile }) + if (!passed && ctx.session?.page) { + const diag = await captureFailureDiagnostics(ctx.session.page, diagDir, 'assertion-failure') + log('diag', `captured -> ${diagDir} (store=${diag.info?.hasStore ?? 'n/a'})`) + } + } catch (err) { + console.error(`[win-crash-survival-e2e] FATAL: ${err.stack || err.message}`) + if (ctx.session?.page) { + const diag = await captureFailureDiagnostics(ctx.session.page, diagDir, 'driving-failure') + log('diag', `captured -> ${diagDir} (store=${diag.info?.hasStore ?? 'n/a'})`) + } + passed = false + } finally { + await teardown({ app: ctx.session?.app, userDataDir, keepProfile: opts.keepProfile, runDir }) + } + return passed ? 0 : 1 +} + +/** + * Run the launch → crash → survive → relaunch → assert proof. `ctx.session` is + * assigned as each app launches so a caller's finally can tear down a partial + * session. Returns whether every assertion passed. + */ +async function runProof(ctx, args) { + const { opts, canary, runDir, userDataDir, shellPidFile, reattachFile } = args + + // Seed a fresh profile (onboarding dismissed + one throwaway repo) ONLY before + // the first launch. The relaunch must use the app's own persisted state so the + // reattach/adoption assertions are meaningful. + const seededRepo = createSeededRepo(path.join(runDir, 'fixture-repo')) + const seedProfile = buildFreshProfile({ repo: seededRepo }) + + // --- First launch: open a plain terminal and stamp the interactive shell --- + let session = await launchInstalledApp({ exePath: opts.exePath, userDataDir, seedProfile }) + ctx.session = session + await ensureTerminal(session.page, { allowCreate: true }) + await dismissOverlays(session.page) + // Opening the seeded workspace lands on its default tab (an agent, not a bare + // shell). Add an explicit plain-terminal tab so the sentinel commands run in a + // real pwsh prompt — typing shell commands into an agent TUI would never run. + const initialTabIds = await listTabIds(session.page) + await createTerminalTab(session.page) + await dismissOverlays(session.page) + const tabIds = await listTabIds(session.page) + const terminalTabId = selectCreatedTabId(initialTabIds, tabIds) + await waitForTerminalReady(session.page, 60_000, terminalTabId) + log('sessions', `terminal ready; created=${terminalTabId}; tab ids: ${tabIds.join(', ')}`) + + // Type DIRECTLY into the interactive shell (not a nested powershell) so $env and + // $PID belong to THIS shell: stamp the env sentinel and record the shell's own + // PID. The command completes fast, leaving the shell idle at a live PSReadLine + // prompt — the exact state that FailFasts with 0xE9 on a broken build. The pid + // file appearing also proves keystrokes reached and ran in the shell. + await typeLine( + session.page, + `$env:${SENTINEL_ENV}='${canary}'; Set-Content -LiteralPath ${quotePowerShellLiteral(shellPidFile)} -Value $PID`, + terminalTabId + ) + const shellPid = await waitForIntFile(shellPidFile, 15_000) + log('shell', `interactive shell pid=${shellPid} (sentinel ${SENTINEL_ENV}=${canary})`) + + const preDaemon = resolveScopedDaemon(userDataDir) + log('daemon', `pre-crash daemon pid=${preDaemon.pid} appVersion=${preDaemon.appVersion}`) + + // Resolve the REAL Electron main pid from INSIDE the main process. On this + // packaged build app.process().pid is a launcher stub that immediately re-execs + // the actual browser process; killing the stub would leave the real main (and + // its single-instance lock) alive and make survival vacuously true. app.evaluate + // runs in the main process, so process.pid there is the exact main of the + // instance this harness launched — authoritative, not a machine-wide scan. + const mainPid = await resolveElectronMainPid(session.app, { allowLauncherFallback: false }) + if (!Number.isInteger(mainPid) || mainPid <= 0) { + throw new Error(`could not resolve app main pid (got ${mainPid})`) + } + + // --- CRASH: force-kill ONLY the real main (no /T tree-kill, no graceful close) --- + const crashStartMs = Date.now() + log('crash', `taskkill /F /PID ${mainPid} (real main, no /T) — abrupt main-process death`) + crashMainProcess(mainPid) + // The crashed app's driver is dead; drop it so teardown never re-closes it. + ctx.session = null + + // Prove the crash actually LANDED before trusting any survival signal — an + // assertion that never fires would make the whole proof vacuous. + const mainDied = await waitForPidDead(mainPid, 15_000) + log('crash', `main pid ${mainPid} dead: ${mainDied}`) + + // Observe the survival window: the daemon and the SAME shell PID must keep running. + await delay(opts.soakSeconds * 1000) + const daemonAliveAfterCrash = preDaemon.pid != null && isPidAlive(preDaemon.pid) + const shellAliveAfterCrash = shellPid != null && isPidAlive(shellPid) + log( + 'crash', + `after crash: daemonAlive=${daemonAliveAfterCrash} shellAlive=${shellAliveAfterCrash}` + ) + + // --- Relaunch: adopt the surviving daemon and prove the reattached UI is the + // same survivor shell (env sentinel reads back) --- + clearSingletonLocks(userDataDir) + session = await launchInstalledApp({ exePath: opts.exePath, userDataDir }) + ctx.session = session + let reattachProven = false + try { + // No create on relaunch: the terminal must be RESTORED, not freshly made. + await ensureTerminal(session.page, { allowCreate: false }) + await dismissOverlays(session.page) + reattachProven = await proveReattachedShell(session.page, { + file: reattachFile, + expectedCanary: canary, + expectedShellPid: shellPid, + terminalTabId + }) + } catch (err) { + log('relaunch', `reattach proof did not complete: ${err.message}`) + } + log('relaunch', `reattached UI bound to survivor shell: ${reattachProven}`) + + const postDaemon = resolveScopedDaemon(userDataDir) + const postDaemonAlive = postDaemon.pid != null && isPidAlive(postDaemon.pid) + log('daemon', `post-relaunch daemon pid=${postDaemon.pid} alive=${postDaemonAlive}`) + + // Why: PowerShell can stay alive on a severed ConPTY until the next console + // read. Scan after the reattach keystroke so the user-visible 0xE9 is covered. + const { events: failFastEvents } = scanPwshFailFast(crashStartMs) + log('event-log', `pwsh FailFast/0xE9 events since crash: ${failFastEvents.length}`) + for (const e of failFastEvents.slice(0, 3)) { + log('event-log', ` ${e.provider}#${e.id}@${e.timeCreated}`) + } + + const assertions = buildCrashAssertions({ + profile: opts.expect, + shellPid, + preDaemonPid: preDaemon.pid, + postDaemonPid: postDaemon.pid, + postDaemonAlive, + mainDied, + daemonAliveAfterCrash, + shellAliveAfterCrash, + reattachProven, + failFastEvents + }) + const passed = allPassed(assertions) + console.log(renderTable(assertions, 'win-crash-survival-e2e')) + log('result', passed ? 'PASS' : 'FAIL') + return passed +} + +/** + * Prove the reattached UI is bound to the SAME survivor shell: type a command that + * writes the shell's own $PID plus the persisted env sentinel to a file, then + * confirm the sentinel (and PID) match. A freshly re-spawned shell would not carry + * the env var. Targets the exact pre-crash tab id so the probe cannot type shell + * commands into an unrelated agent tab. Repeats the idempotent command while the + * restored pane transport converges; a filesystem match, not elapsed time, wins. + */ +async function proveReattachedShell( + page, + { file, expectedCanary, expectedShellPid, terminalTabId } +) { + const restoredTabIds = await listTabIds(page) + log('relaunch', `restored tab ids: ${restoredTabIds.join(', ')}; target=${terminalTabId}`) + const targetTab = page.locator(`${SORTABLE_TAB}[data-tab-id="${terminalTabId}"]`).first() + await targetTab.waitFor({ state: 'attached', timeout: 15_000 }) + + const deadline = Date.now() + 30_000 + let attempt = 0 + while (Date.now() < deadline) { + attempt++ + const readinessBudgetMs = Math.max(deadline - Date.now(), 1) + await targetTab.click({ force: true, timeout: readinessBudgetMs }) + await waitForTerminalReady(page, readinessBudgetMs, terminalTabId) + // Why: a partially forwarded earlier attempt can leave text at PSReadLine; + // clear it before replaying the complete idempotent proof command. + await sendCtrlC(page, terminalTabId) + await typeLine( + page, + `Set-Content -LiteralPath ${quotePowerShellLiteral(file)} -Value "$($PID)|$($env:${SENTINEL_ENV})"`, + terminalTabId + ) + const remainingMs = deadline - Date.now() + const hit = await waitForSentinel( + file, + expectedCanary, + expectedShellPid, + Math.min(3_000, Math.max(remainingMs, 0)) + ) + if (hit) { + log('relaunch', `same-shell sentinel read back on attempt ${attempt}`) + return true + } + log('relaunch', `same-shell probe attempt ${attempt} produced no matching sentinel`) + } + return false +} + +/** Poll for the reattach file and require both the per-shell canary and exact + * survivor PID. Either check alone is weaker than the asserted shell identity. */ +async function waitForSentinel(file, expectedCanary, expectedShellPid, timeoutMs) { + const deadline = Date.now() + timeoutMs + while (Date.now() < deadline) { + try { + if (reattachSentinelMatches(readFileSync(file, 'utf8'), expectedCanary, expectedShellPid)) { + return true + } + } catch { + /* not written yet */ + } + await delay(500) + } + return false +} + +/** + * Resolve THIS run's daemon, scoped to its isolated userData dir so unrelated + * daemons on the machine (including the developer's live Orca) are ignored. + * The scoped live process scan is authoritative; PID files only contribute + * metadata after their PID matches that process. + */ +function resolveScopedDaemon(userDataDir) { + const pidFiles = readDaemonPidFiles(userDataDir) + const scan = findDaemonProcesses(userDataDir) + return selectScopedDaemon(pidFiles, scan) +} + +/** + * Remove Electron/Chromium single-instance lock files a crashed main can leave + * behind in the isolated profile, so the relaunch is not refused/redirected by a + * stale lock. Best-effort — absent files are normal. + */ +function clearSingletonLocks(userDataDir) { + let entries = [] + try { + entries = readdirSync(userDataDir) + } catch { + return + } + for (const entry of entries) { + if (entry.startsWith('Singleton')) { + try { + rmSync(path.join(userDataDir, entry), { recursive: true, force: true }) + } catch { + /* leave it; relaunch may still succeed */ + } + } + } +} + +/** + * Tear down only what THIS harness created. Kills are re-scoped at teardown time + * via a FRESH findDaemonProcesses(userDataDir): the interactive shell is a + * descendant of this run's daemon, so a /T tree-kill of the freshly-discovered + * daemon removes the daemon + OpenConsole + shell together. We deliberately do NOT + * kill any pid captured earlier in the run — a captured pid can be recycled by the + * OS onto an innocent process, so only pids re-verified as this run's daemon (by + * scoped command-line match) are ever killed. Never installs/uninstalls and never + * touches any other Orca on the box (a live user instance uses a different + * userData and is out of scope by construction). + */ +async function teardown({ app, userDataDir, keepProfile, runDir }) { + try { + await closeApp(app) + } catch { + /* already closed / never launched */ + } + for (const proc of findDaemonProcesses(userDataDir)) { + killPidTree(proc.pid) + } + if (keepProfile) { + log('teardown', `--keep-profile set; leaving ${runDir}`) + return + } + // Best-effort: a just-killed daemon/child can briefly hold file handles under + // the profile, so a locked rmSync must not turn cleanup into a FATAL. + try { + rmSync(runDir, { recursive: true, force: true }) + } catch (err) { + log('teardown', `could not remove ${runDir} (${err.code || err.message}); leaving it`) + } +} + +function killPidTree(pid) { + if (!Number.isInteger(pid) || pid <= 0) { + return + } + try { + execFileSync('taskkill', ['/pid', String(pid), '/T', '/F'], { stdio: 'ignore' }) + } catch { + /* already dead */ + } +} + +/** Poll until a pid is no longer alive (the crash landed), or timeout. */ +async function waitForPidDead(pid, timeoutMs) { + const deadline = Date.now() + timeoutMs + while (Date.now() < deadline) { + if (!isPidAlive(pid)) { + return true + } + await delay(500) + } + return false +} + +function readIntFile(filePath) { + try { + const n = Number(readFileSync(filePath, 'utf8').trim()) + return Number.isInteger(n) ? n : null + } catch { + return null + } +} + +/** Poll for an int-valued file (the shell writes its PID asynchronously once the + * typed command runs), returning the int or null after timeoutMs. */ +async function waitForIntFile(filePath, timeoutMs) { + const deadline = Date.now() + timeoutMs + while (Date.now() < deadline) { + const n = readIntFile(filePath) + if (n != null) { + return n + } + await delay(500) + } + return null +} + +function delay(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +if (process.argv[1] && path.resolve(process.argv[1]) === import.meta.filename) { + main() + .then((code) => { + // Force-exit: a launched Electron app can keep libuv handles open, which + // would otherwise pin Node alive until the CI job timeout. + process.exit(code) + }) + .catch((err) => { + console.error('[win-crash-survival-e2e] FATAL:', err.stack || err.message) + process.exit(1) + }) +} diff --git a/tools/win-update-e2e/README.md b/tools/win-update-e2e/README.md index 973958a8b80..6c771bb1197 100644 --- a/tools/win-update-e2e/README.md +++ b/tools/win-update-e2e/README.md @@ -5,6 +5,12 @@ harness performs a real silent update and proves, with machine-checkable assertions, what happens to the terminal **daemon** and its **sessions** across the update — and whether any console/terminal window flashes. +> **Companion harness:** proving the daemon survives a **crash** of the main +> process (GitHub #7742), rather than an update, lives in +> [`tools/win-crash-survival-e2e`](../win-crash-survival-e2e/README.md). It reuses +> the shared modules in this directory (app driver, daemon discovery, PowerShell +> runner, platform guard, table renderer). + It is the Phase 0 "proof harness" deliverable from [`docs/windows-terminal-update-survival-plan.md`](../../docs/windows-terminal-update-survival-plan.md). It exists specifically because the July 2026 attempt shipped four broken RCs diff --git a/tools/win-update-e2e/app-driver.mjs b/tools/win-update-e2e/app-driver.mjs index 8f16a44645a..71baba46583 100644 --- a/tools/win-update-e2e/app-driver.mjs +++ b/tools/win-update-e2e/app-driver.mjs @@ -61,11 +61,62 @@ export async function launchInstalledApp({ ...extraEnv } }) - const page = await app.firstWindow({ timeout: 120_000 }) - await page.waitForLoadState('domcontentloaded') + // If firstWindow times out (the launched main never shows a window), the + // Electron process is still running — force-kill its tree before rethrowing so + // a driving failure never leaks an orphaned main to the CI job timeout. + let page + try { + page = await app.firstWindow({ timeout: 120_000 }) + await page.waitForLoadState('domcontentloaded') + } catch (err) { + const pid = await resolveElectronMainPid(app) + if (pid) { + try { + execFileSync('taskkill', ['/pid', String(pid), '/T', '/F'], { stdio: 'ignore' }) + } catch { + /* already gone */ + } + } + throw err + } return { app, page } } +/** Resolve the packaged Electron main, optionally falling back to Playwright's child PID. */ +export async function resolveElectronMainPid( + app, + { allowLauncherFallback = true, timeoutMs = 5_000 } = {} +) { + let timeout + try { + // Why: packaged launchers can re-exec, leaving app.process() pointing at a + // dead stub while evaluate runs in the authoritative Electron main. + const pid = await Promise.race([ + app.evaluate(() => process.pid), + new Promise((_, reject) => { + // Why: a wedged main connection is common on cleanup paths; resolving + // its authoritative PID must not consume the entire CI job timeout. + timeout = setTimeout(() => reject(new Error('main PID resolution timed out')), timeoutMs) + timeout.unref?.() + }) + ]) + if (Number.isInteger(pid) && pid > 0) { + return pid + } + } catch { + /* the main connection may already be unavailable */ + } finally { + clearTimeout(timeout) + } + // Why: crash proofs must fail closed rather than kill a packaged launcher stub + // and mistake its death for the authoritative Electron main crashing. + if (!allowLauncherFallback) { + return null + } + const fallbackPid = app.process()?.pid + return Number.isInteger(fallbackPid) && fallbackPid > 0 ? fallbackPid : null +} + /** * Best-effort diagnostics dump when driving fails: a screenshot, the visible * body text, and whether the e2e store is exposed (it is not in production @@ -102,7 +153,22 @@ export async function captureFailureDiagnostics(page, dir, label) { buttons: Array.from(document.querySelectorAll('button,[role="button"]')) .map((el) => (el.getAttribute('aria-label') || el.textContent || '').trim()) .filter((v, i, a) => v && a.indexOf(v) === i) - .slice(0, 60) + .slice(0, 60), + tabs: Array.from(document.querySelectorAll('[data-testid="sortable-tab"]')) + .map((el) => ({ + id: el.getAttribute('data-tab-id'), + title: el.getAttribute('data-tab-title'), + ariaLabel: el.getAttribute('aria-label'), + selected: el.getAttribute('aria-selected') + })) + .slice(0, 40), + activeElement: document.activeElement + ? { + tag: document.activeElement.tagName, + className: document.activeElement.getAttribute('class'), + ariaLabel: document.activeElement.getAttribute('aria-label') + } + : null })) writeFileSync(path.join(dir, `${label}.json`), JSON.stringify(info, null, 2)) out.info = info @@ -112,16 +178,18 @@ export async function captureFailureDiagnostics(page, dir, label) { return out } -/** Wait until the visible terminal surface and its xterm container are mounted. */ -export async function waitForTerminalReady(page, timeoutMs = 60_000) { - await page - .locator(TERMINAL_SURFACE_VISIBLE) - .first() - .waitFor({ state: 'visible', timeout: timeoutMs }) - await page - .locator(XTERM_CONTAINER_VISIBLE) - .first() - .waitFor({ state: 'visible', timeout: timeoutMs }) +/** Wait until the visible terminal surface and its xterm container are mounted. + * An expected tab id prevents post-restore probes from accepting another tab. */ +export async function waitForTerminalReady(page, timeoutMs = 60_000, terminalTabId = null) { + const selector = terminalTabId + ? `[data-terminal-tab-id="${terminalTabId}"]:visible` + : TERMINAL_SURFACE_VISIBLE + const surface = page.locator(selector).first() + await surface.waitFor({ state: 'visible', timeout: timeoutMs }) + await surface.locator(XTERM_CONTAINER_VISIBLE).first().waitFor({ + state: 'visible', + timeout: timeoutMs + }) } /** @@ -245,7 +313,7 @@ export async function listTabIds(page) { * off-screen helper textarea alone does not, which is why typed input was being * dropped. Click the pane, then focus the helper textarea as a belt-and-braces. */ -export async function focusActiveTerminal(page) { +export async function focusActiveTerminal(page, terminalTabId = null) { // A feature-tip modal can appear late and swallow keystrokes; clear any before // focusing so typed commands actually reach the shell. for (const name of OVERLAY_DISMISS_LABELS) { @@ -254,25 +322,32 @@ export async function focusActiveTerminal(page) { await btn.click({ timeout: 2_000 }).catch(() => {}) } } - const surface = page.locator(TERMINAL_SURFACE_VISIBLE).first() - await surface.click({ position: { x: 24, y: 24 }, timeout: 15_000 }).catch(() => {}) + const selector = terminalTabId + ? `[data-terminal-tab-id="${terminalTabId}"]:visible` + : TERMINAL_SURFACE_VISIBLE + const surface = page.locator(selector).first() + const click = surface.click({ position: { x: 24, y: 24 }, timeout: 15_000 }) + // Why: an exact-tab proof must fail closed if that restored surface vanishes; + // typing into whichever element retained focus could falsely target another tab. + await (terminalTabId ? click : click.catch(() => {})) // Scope the helper textarea to the visible surface so focus can't land on a // hidden duplicate pane's textarea (which would silently swallow keystrokes). const input = surface.locator(XTERM_INPUT).last() - await input.focus().catch(() => {}) + const focus = input.focus() + await (terminalTabId ? focus : focus.catch(() => {})) return input } /** Type a line and submit it (Enter → \r submits in the shell). */ -export async function typeLine(page, text) { - await focusActiveTerminal(page) +export async function typeLine(page, text, terminalTabId = null) { + await focusActiveTerminal(page, terminalTabId) await page.keyboard.type(text) await page.keyboard.press('Enter') } /** Send Ctrl+C to the active terminal. */ -export async function sendCtrlC(page) { - await focusActiveTerminal(page) +export async function sendCtrlC(page, terminalTabId = null) { + await focusActiveTerminal(page, terminalTabId) await page.keyboard.press('Control+C') } @@ -339,19 +414,31 @@ export async function readTerminalTextBestEffort(page) { * left alive exactly as a normal quit would. */ export async function closeApp(app, timeoutMs = 10_000) { - const proc = app.process() + // A partially-created session (launch failed before assignment) passes undefined. + if (!app) { + return + } + const mainPid = await resolveElectronMainPid(app) + let closeTimeout try { await Promise.race([ app.close(), - new Promise((_, reject) => setTimeout(() => reject(new Error('close timeout')), timeoutMs)) + new Promise((_, reject) => { + closeTimeout = setTimeout(() => reject(new Error('close timeout')), timeoutMs) + closeTimeout.unref?.() + }) ]) } catch { - if (proc?.pid) { + if (mainPid) { try { - execFileSync('taskkill', ['/pid', String(proc.pid), '/T', '/F'], { stdio: 'ignore' }) + execFileSync('taskkill', ['/pid', String(mainPid), '/T', '/F'], { stdio: 'ignore' }) } catch { /* already gone */ } } + } finally { + // Why: successful closes must not retain a timeout closure or keep a shared + // harness process alive until the failure deadline expires. + clearTimeout(closeTimeout) } } diff --git a/tools/win-update-e2e/assertions.mjs b/tools/win-update-e2e/assertions.mjs index 20db79e48c5..3d3efd77cec 100644 --- a/tools/win-update-e2e/assertions.mjs +++ b/tools/win-update-e2e/assertions.mjs @@ -172,8 +172,9 @@ export function allPassed(assertions) { return assertions.every((a) => a.pass === true || a.pass === null) } -/** Render the assertion list as an aligned PASS/FAIL/INFO table. */ -export function renderTable(assertions) { +/** Render the assertion list as an aligned PASS/FAIL/INFO table. `label` names + * the harness in the header (shared with the crash-survival harness). */ +export function renderTable(assertions, label = 'win-update-e2e') { const symbol = (pass) => (pass === true ? 'PASS' : pass === false ? 'FAIL' : 'INFO') const nameWidth = Math.max(...assertions.map((a) => a.name.length), 10) const lines = assertions.map((a) => { @@ -181,6 +182,6 @@ export function renderTable(assertions) { return ` [${symbol(a.pass)}] ${a.name.padEnd(nameWidth)} expected: ${a.expected}; actual: ${a.actual}${detail}` }) const failed = assertions.filter((a) => a.pass === false).length - const header = `\n===== win-update-e2e assertions (${failed === 0 ? 'ALL PASS' : `${failed} FAILED`}) =====` + const header = `\n===== ${label} assertions (${failed === 0 ? 'ALL PASS' : `${failed} FAILED`}) =====` return [header, ...lines, ''].join('\n') } diff --git a/tools/win-update-e2e/daemon-processes.mjs b/tools/win-update-e2e/daemon-processes.mjs index cfd9c5d41b6..f7d43cc9b44 100644 --- a/tools/win-update-e2e/daemon-processes.mjs +++ b/tools/win-update-e2e/daemon-processes.mjs @@ -97,14 +97,26 @@ export function readDaemonPidFiles(userDataDir = defaultUserDataDir()) { } /** True if a PID currently maps to a live process. */ -export function isPidAlive(pid) { +export function isPidAlive(pid, runCommand = runCommandSync) { if (!Number.isInteger(pid) || pid <= 0) { return false } - const { stdout } = runCommandSync( + const { stdout, stderr, code, error } = runCommand( `if (Get-Process -Id ${pid} -ErrorAction SilentlyContinue) { 'alive' } else { 'dead' }` ) - return stdout.trim() === 'alive' + if (error) { + throw new Error(`PID liveness probe failed to spawn: ${error.message}`) + } + if (code !== 0) { + throw new Error(`PID liveness probe failed (exit ${code}): ${stderr.trim()}`) + } + const state = stdout.trim() + if (state !== 'alive' && state !== 'dead') { + // Why: blank or unexpected output is unavailable evidence, not proof that + // a process died; crash-survival assertions must fail closed. + throw new Error(`PID liveness probe returned an invalid state: ${JSON.stringify(state)}`) + } + return state === 'alive' } function runJsonCommand(command) { diff --git a/tools/win-update-e2e/powershell-runner.mjs b/tools/win-update-e2e/powershell-runner.mjs index 68561b26120..9b401d1ff1c 100644 --- a/tools/win-update-e2e/powershell-runner.mjs +++ b/tools/win-update-e2e/powershell-runner.mjs @@ -13,6 +13,11 @@ const BASE_ARGS = ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass' // whole CI job times out. Callers can override via opts.timeout. const DEFAULT_SYNC_TIMEOUT_MS = 60_000 +/** Quote a value as a PowerShell single-quoted literal. */ +export function quotePowerShellLiteral(value) { + return `'${String(value).replaceAll("'", "''")}'` +} + /** * Run a .ps1 file synchronously and return { code, stdout, stderr }. * scriptArgs is an array of string arguments passed after -File. From b19dccd9b74328daa39bf20955a57fdfc7279c3e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:35:53 +0000 Subject: [PATCH 069/137] Update README downloads badge --- docs/assets/readme-downloads.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/assets/readme-downloads.svg b/docs/assets/readme-downloads.svg index 525f31215a9..1825ee96d32 100644 --- a/docs/assets/readme-downloads.svg +++ b/docs/assets/readme-downloads.svg @@ -1,5 +1,5 @@ - - downloads: 6.4m + + downloads: 6.5m @@ -15,7 +15,7 @@ downloads downloads - 6.4m - 6.4m + 6.5m + 6.5m From b4e85d95a6dbcfbc2289ec20346a634555051623 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:13:16 -0700 Subject: [PATCH 070/137] feat(editor): add Toggle H5 to markdown editor (#9448) --- src/renderer/src/assets/markdown-preview.css | 8 ++++- .../src/assets/rich-markdown-editor.css | 9 +++++- .../src/components/editor/MarkdownPreview.tsx | 2 +- .../editor/details-markdown-html.test.ts | 29 +++++++++++++++++++ .../editor/details-markdown-html.ts | 18 ++++++++---- .../editor/markdown-round-trip.test.ts | 11 +++++-- .../editor/rich-markdown-commands.test.ts | 2 ++ .../editor/rich-markdown-details-extension.ts | 3 +- .../rich-markdown-heading-slash-commands.tsx | 21 ++++++++++++++ .../rich-markdown-slash-command-primitives.ts | 1 + src/renderer/src/i18n/locales/en.json | 4 ++- src/renderer/src/i18n/locales/es.json | 4 ++- src/renderer/src/i18n/locales/ja.json | 4 ++- src/renderer/src/i18n/locales/ko.json | 4 ++- src/renderer/src/i18n/locales/zh.json | 4 ++- 15 files changed, 107 insertions(+), 17 deletions(-) diff --git a/src/renderer/src/assets/markdown-preview.css b/src/renderer/src/assets/markdown-preview.css index 9388731bb9c..7f5afaf76f1 100644 --- a/src/renderer/src/assets/markdown-preview.css +++ b/src/renderer/src/assets/markdown-preview.css @@ -556,7 +556,7 @@ line-height: 1.3; } -/* Heading toggle sizes mirror the plain h2–h4 scales (1.4 / 1.15 / 1em). */ +/* Heading toggle sizes mirror the plain h2–h5 scales (1.4 / 1.15 / 1 / 0.92em). */ .markdown-body details.orca-details[data-orca-toggle='heading-2'] > summary { font-size: 1.4em; line-height: 1.3; @@ -572,6 +572,12 @@ line-height: 1.3; } +.markdown-body details.orca-details[data-orca-toggle='heading-5'] > summary { + font-size: 0.92em; + color: var(--muted-foreground); + line-height: 1.3; +} + .markdown-body details.orca-details > :not(summary) { margin-left: 0.15em; } diff --git a/src/renderer/src/assets/rich-markdown-editor.css b/src/renderer/src/assets/rich-markdown-editor.css index 48c13e0057d..4b918455221 100644 --- a/src/renderer/src/assets/rich-markdown-editor.css +++ b/src/renderer/src/assets/rich-markdown-editor.css @@ -764,7 +764,7 @@ height: 2.405em; } -/* Heading toggle sizes mirror the plain h2–h4 scales (1.4 / 1.15 / 1em). */ +/* Heading toggle sizes mirror the plain h2–h5 scales (1.4 / 1.15 / 1 / 0.92em). */ .rich-markdown-editor details.orca-details[data-orca-toggle='heading-2'] > summary, .rich-markdown-editor .orca-details[data-type='details'][data-orca-toggle='heading-2'] summary { font-size: 1.4em; @@ -787,6 +787,13 @@ line-height: 1.3; } +.rich-markdown-editor details.orca-details[data-orca-toggle='heading-5'] > summary, +.rich-markdown-editor .orca-details[data-type='details'][data-orca-toggle='heading-5'] summary { + font-size: 0.92em; + color: var(--muted-foreground); + line-height: 1.3; +} + .rich-markdown-editor blockquote { padding: 0.5em 1em; border-left: 3px solid var(--border); diff --git a/src/renderer/src/components/editor/MarkdownPreview.tsx b/src/renderer/src/components/editor/MarkdownPreview.tsx index 4c09bd0a8b5..3fa53012683 100644 --- a/src/renderer/src/components/editor/MarkdownPreview.tsx +++ b/src/renderer/src/components/editor/MarkdownPreview.tsx @@ -315,7 +315,7 @@ const markdownPreviewSanitizeSchema = { ...(defaultSchema.attributes?.details ?? []), 'open', ['className', 'orca-details'], - ['dataOrcaToggle', 'heading-1', 'heading-2', 'heading-3', 'heading-4'] + ['dataOrcaToggle', 'heading-1', 'heading-2', 'heading-3', 'heading-4', 'heading-5'] ], h1: [...(defaultSchema.attributes?.h1 ?? []), 'id'], h2: [...(defaultSchema.attributes?.h2 ?? []), 'id'], diff --git a/src/renderer/src/components/editor/details-markdown-html.test.ts b/src/renderer/src/components/editor/details-markdown-html.test.ts index e381f453835..c4e1493a198 100644 --- a/src/renderer/src/components/editor/details-markdown-html.test.ts +++ b/src/renderer/src/components/editor/details-markdown-html.test.ts @@ -2,6 +2,8 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import { extractDetailsSummaryHtml, isEditableDetailsHtmlBlock, + parseDetailsAttributes, + parseToggleHeadingVariant, type DetailsHtmlBlock } from './details-markdown-html' @@ -45,4 +47,31 @@ describe('details markdown html', () => { ) expect(usedSummaryCapture).toBe(false) }) + + it('accepts heading-5 toggle variants and rejects unsupported levels', () => { + expect(parseToggleHeadingVariant('heading-5')).toBe('heading-5') + expect(parseToggleHeadingVariant('heading-6')).toBeNull() + expect(parseDetailsAttributes(' data-orca-toggle="heading-5"')).toMatchObject({ + variant: 'heading-5' + }) + expect(parseDetailsAttributes(' data-orca-toggle="heading-6"')).toMatchObject({ + variant: null + }) + + const editableHeading5: DetailsHtmlBlock = { + raw: '', + openingAttributes: ' data-orca-toggle="heading-5"', + inner: 'Toggle

    Body

    ', + hasNestedDetails: false + } + const unsupportedHeading6: DetailsHtmlBlock = { + raw: '', + openingAttributes: ' data-orca-toggle="heading-6"', + inner: 'Toggle

    Body

    ', + hasNestedDetails: false + } + + expect(isEditableDetailsHtmlBlock(editableHeading5)).toBe(true) + expect(isEditableDetailsHtmlBlock(unsupportedHeading6)).toBe(false) + }) }) diff --git a/src/renderer/src/components/editor/details-markdown-html.ts b/src/renderer/src/components/editor/details-markdown-html.ts index 6534793ce63..a2f4238b658 100644 --- a/src/renderer/src/components/editor/details-markdown-html.ts +++ b/src/renderer/src/components/editor/details-markdown-html.ts @@ -1,14 +1,20 @@ import type { MarkdownToken } from '@tiptap/core' -// Toggle summaries can render at heading scales 1–4, mirroring the plain -// heading levels the slash menu / toolbar dropdown offer (h1–h4). -export type ToggleHeadingVariant = 'heading-1' | 'heading-2' | 'heading-3' | 'heading-4' +// Toggle summaries can render at heading scales 1–5, mirroring the plain +// heading levels the slash menu / toolbar dropdown offer (h1–h5). +export type ToggleHeadingVariant = + | 'heading-1' + | 'heading-2' + | 'heading-3' + | 'heading-4' + | 'heading-5' export const TOGGLE_HEADING_VARIANTS: readonly ToggleHeadingVariant[] = [ 'heading-1', 'heading-2', 'heading-3', - 'heading-4' + 'heading-4', + 'heading-5' ] export function parseToggleHeadingVariant(value: unknown): ToggleHeadingVariant | null { @@ -49,7 +55,7 @@ export function parseDetailsAttributes(rawAttributes: string): Record { ) }) - it.each(['heading-2', 'heading-3', 'heading-4'])( + it.each(['heading-2', 'heading-3', 'heading-4', 'heading-5'])( 'preserves %s-styled details blocks', (variant) => { expect( @@ -166,6 +166,12 @@ describe('rich markdown round trip', () => { expect(roundTripMarkdown(input)).toBe(input.trimEnd()) }) + it('preserves details blocks with unsupported toggle variants as passthrough html', () => { + const input = + '
    Toggle

    Body

    \n' + expect(roundTripMarkdown(input)).toBe(input.trimEnd()) + }) + it('preserves details blocks with closing tags inside fenced code as passthrough html', () => { const input = [ '
    Toggle', @@ -208,7 +214,8 @@ describe('rich markdown round trip', () => { it.each([ ['toggle-h2', 'heading-2'], ['toggle-h3', 'heading-3'], - ['toggle-h4', 'heading-4'] + ['toggle-h4', 'heading-4'], + ['toggle-h5', 'heading-5'] ] as const)('inserts editable %s toggles from slash commands', (commandId, variant) => { expect(slashCommandMarkdown(commandId)).toBe( `
    \n\n\n\n\n
    ` diff --git a/src/renderer/src/components/editor/rich-markdown-commands.test.ts b/src/renderer/src/components/editor/rich-markdown-commands.test.ts index 5caffa90e7a..7a043e5e860 100644 --- a/src/renderer/src/components/editor/rich-markdown-commands.test.ts +++ b/src/renderer/src/components/editor/rich-markdown-commands.test.ts @@ -35,6 +35,7 @@ describe('rich markdown slash commands', () => { expect(getCommand('inline-math').aliases).toContain('latex') expect(getCommand('math-block').aliases).toContain('equation block') expect(getCommand('emoji').aliases).toContain('reaction') + expect(getCommand('toggle-h5').aliases).toContain('toggle-h5') }) it('orders commands under section headers', () => { @@ -48,6 +49,7 @@ describe('rich markdown slash commands', () => { 'Toggle headings:toggle-h2', 'Toggle headings:toggle-h3', 'Toggle headings:toggle-h4', + 'Toggle headings:toggle-h5', 'Basic blocks:blockquote', 'Basic blocks:ordered-list', 'Basic blocks:bullet-list', diff --git a/src/renderer/src/components/editor/rich-markdown-details-extension.ts b/src/renderer/src/components/editor/rich-markdown-details-extension.ts index c36fa5753e1..151fe512330 100644 --- a/src/renderer/src/components/editor/rich-markdown-details-extension.ts +++ b/src/renderer/src/components/editor/rich-markdown-details-extension.ts @@ -22,7 +22,8 @@ const TOGGLE_HEADING_PLACEHOLDERS: Record { insertToggle(editor, 'heading-4') } + }, + { + id: 'toggle-h5', + get label() { + return translate( + 'auto.components.editor.rich.markdown.slash.commands.21d8c463e5', + 'Toggle H5' + ) + }, + aliases: ['toggle-h5'], + icon: icon(ChevronRight), + group: 'Toggle headings', + get description() { + return translate( + 'auto.components.editor.rich.markdown.slash.commands.dc239b41ad', + 'Create a collapsible section with a deep heading summary.' + ) + }, + run: (editor) => { + insertToggle(editor, 'heading-5') + } } ] diff --git a/src/renderer/src/components/editor/rich-markdown-slash-command-primitives.ts b/src/renderer/src/components/editor/rich-markdown-slash-command-primitives.ts index 3f982d9bba9..6cd7bd429ae 100644 --- a/src/renderer/src/components/editor/rich-markdown-slash-command-primitives.ts +++ b/src/renderer/src/components/editor/rich-markdown-slash-command-primitives.ts @@ -23,6 +23,7 @@ export type SlashCommandId = | 'heading-4' | 'toggle-h4' | 'heading-5' + | 'toggle-h5' | 'task-list' | 'bullet-list' | 'ordered-list' diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 1575481e14b..5d48f72c149 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -12030,7 +12030,9 @@ "2f9d6b4e10": "Toggle H3", "8c1a3e7d52": "Create a collapsible section with a small heading summary.", "5e0b9c2a71": "Toggle H4", - "d4f16a8b39": "Create a collapsible section with a nested heading summary." + "d4f16a8b39": "Create a collapsible section with a nested heading summary.", + "21d8c463e5": "Toggle H5", + "dc239b41ad": "Create a collapsible section with a deep heading summary." } } } diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 3a5311cb76c..163138a3f18 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -12007,7 +12007,9 @@ "2f9d6b4e10": "Alternar título 3", "8c1a3e7d52": "Cree una sección plegable con un resumen de encabezado pequeño.", "5e0b9c2a71": "Alternar título 4", - "d4f16a8b39": "Cree una sección plegable con un resumen de encabezado anidado." + "d4f16a8b39": "Cree una sección plegable con un resumen de encabezado anidado.", + "21d8c463e5": "Alternar título 5", + "dc239b41ad": "Cree una sección plegable con un resumen de encabezado profundo." } } } diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 34481f1ac18..9bcc9051304 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -12007,7 +12007,9 @@ "2f9d6b4e10": "見出し 3 を切り替えます", "8c1a3e7d52": "小さな見出しの概要を含む折りたたみ可能なセクションを作成します。", "5e0b9c2a71": "見出し 4 を切り替えます", - "d4f16a8b39": "入れ子の見出しの概要を含む折りたたみ可能なセクションを作成します。" + "d4f16a8b39": "入れ子の見出しの概要を含む折りたたみ可能なセクションを作成します。", + "21d8c463e5": "見出し 5 を切り替えます", + "dc239b41ad": "深い見出しの概要を含む折りたたみ可能なセクションを作成します。" } } } diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index e8d8ba49319..73b1d12b378 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -12007,7 +12007,9 @@ "2f9d6b4e10": "토글 제목 3", "8c1a3e7d52": "작은 제목 요약이 포함된 접을 수 있는 섹션을 만듭니다.", "5e0b9c2a71": "토글 제목 4", - "d4f16a8b39": "중첩된 제목 요약이 포함된 접을 수 있는 섹션을 만듭니다." + "d4f16a8b39": "중첩된 제목 요약이 포함된 접을 수 있는 섹션을 만듭니다.", + "21d8c463e5": "토글 제목 5", + "dc239b41ad": "깊은 제목 요약이 포함된 접을 수 있는 섹션을 만듭니다." } } } diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 2ec328ba2e1..874022be095 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -12007,7 +12007,9 @@ "2f9d6b4e10": "切换标题 3", "8c1a3e7d52": "创建一个带有小标题摘要的可折叠部分。", "5e0b9c2a71": "切换标题 4", - "d4f16a8b39": "创建一个带有嵌套标题摘要的可折叠部分。" + "d4f16a8b39": "创建一个带有嵌套标题摘要的可折叠部分。", + "21d8c463e5": "切换标题 5", + "dc239b41ad": "创建一个带有深层标题摘要的可折叠部分。" } } } From 500f0c50deb86849aa2811b7c120a08c0bd31816 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:14:25 -0700 Subject: [PATCH 071/137] Prevent adding the same Claude account twice (#8912) * fix(claude-accounts): block adding a duplicate Claude account (#6616) Adding the same Claude subscription via Settings -> AI Provider Accounts appended a second managed account with an identical email/organization, producing duplicate rows that confused account selection and rate-limit tracking. doAddAccount now checks the captured identity against existing accounts before appending and rejects the add with a clear message when it matches. Identity is keyed on email + organizationUuid scoped to the runtime (host/WSL distro), so the same email can still be added under a different organization, and both sides are normalized so a legacy account persisted before the runtime fields existed still matches. * fix(claude-accounts): skip rollback I/O for duplicates --- .../claude-duplicate-account.test.ts | 113 +++++++++++++ .../claude-duplicate-account.ts | 55 +++++++ src/main/claude-accounts/service.test.ts | 155 +++++++++++++++++- src/main/claude-accounts/service.ts | 23 ++- 4 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 src/main/claude-accounts/claude-duplicate-account.test.ts create mode 100644 src/main/claude-accounts/claude-duplicate-account.ts diff --git a/src/main/claude-accounts/claude-duplicate-account.test.ts b/src/main/claude-accounts/claude-duplicate-account.test.ts new file mode 100644 index 00000000000..23b2fd2f994 --- /dev/null +++ b/src/main/claude-accounts/claude-duplicate-account.test.ts @@ -0,0 +1,113 @@ +import { describe, expect, it } from 'vitest' +import type { ClaudeManagedAccount } from '../../shared/types' +import { + findDuplicateClaudeAccount, + type ClaudeAccountIdentityCandidate +} from './claude-duplicate-account' + +function makeAccount(overrides: Partial = {}): ClaudeManagedAccount { + return { + id: 'existing-account', + email: 'host@x.com', + managedAuthPath: '/managed/existing-account/auth', + authMethod: 'subscription-oauth', + createdAt: 1, + updatedAt: 1, + lastAuthenticatedAt: 1, + ...overrides + } +} + +function makeCandidate( + overrides: Partial = {} +): ClaudeAccountIdentityCandidate { + return { + email: 'host@x.com', + organizationUuid: null, + managedAuthRuntime: 'host', + wslDistro: null, + ...overrides + } +} + +describe('findDuplicateClaudeAccount', () => { + it('returns the existing account on an exact host match', () => { + const account = makeAccount({ managedAuthRuntime: 'host', organizationUuid: null }) + expect(findDuplicateClaudeAccount([account], makeCandidate())?.id).toBe('existing-account') + }) + + it('returns null when the organization differs', () => { + const account = makeAccount({ managedAuthRuntime: 'host', organizationUuid: 'org-A' }) + expect( + findDuplicateClaudeAccount([account], makeCandidate({ organizationUuid: 'org-B' })) + ).toBe(null) + }) + + it('returns null when a wsl candidate is compared against a host account', () => { + const account = makeAccount({ managedAuthRuntime: 'host', organizationUuid: 'org-A' }) + expect( + findDuplicateClaudeAccount( + [account], + makeCandidate({ organizationUuid: 'org-A', managedAuthRuntime: 'wsl', wslDistro: 'Ubuntu' }) + ) + ).toBe(null) + }) + + it('returns null when both are wsl but on different distros', () => { + const account = makeAccount({ + managedAuthRuntime: 'wsl', + wslDistro: 'Ubuntu', + organizationUuid: 'org-A' + }) + expect( + findDuplicateClaudeAccount( + [account], + makeCandidate({ organizationUuid: 'org-A', managedAuthRuntime: 'wsl', wslDistro: 'Debian' }) + ) + ).toBe(null) + }) + + it('returns the account when both are wsl on the same distro', () => { + const account = makeAccount({ + managedAuthRuntime: 'wsl', + wslDistro: 'Ubuntu', + organizationUuid: 'org-A' + }) + expect( + findDuplicateClaudeAccount( + [account], + makeCandidate({ organizationUuid: 'org-A', managedAuthRuntime: 'wsl', wslDistro: 'Ubuntu' }) + )?.id + ).toBe('existing-account') + }) + + it('matches a legacy account with undefined runtime against a host candidate', () => { + // managedAuthRuntime omitted → undefined normalizes to 'host'. + const account = makeAccount({ organizationUuid: null }) + expect(findDuplicateClaudeAccount([account], makeCandidate())?.id).toBe('existing-account') + }) + + it('treats an existing undefined org as equal to a candidate null org', () => { + // organizationUuid omitted → undefined, candidate is explicit null. + const account = makeAccount({ managedAuthRuntime: 'host' }) + expect( + findDuplicateClaudeAccount([account], makeCandidate({ organizationUuid: null }))?.id + ).toBe('existing-account') + }) + + it('matches when the email differs only by case and whitespace', () => { + const account = makeAccount({ managedAuthRuntime: 'host', email: 'Host@X.com ' }) + expect(findDuplicateClaudeAccount([account], makeCandidate({ email: 'host@x.com' }))?.id).toBe( + 'existing-account' + ) + }) + + it('returns null when the candidate email is null', () => { + const account = makeAccount({ managedAuthRuntime: 'host' }) + expect(findDuplicateClaudeAccount([account], makeCandidate({ email: null }))).toBe(null) + }) + + it('returns null for an empty accounts array', () => { + expect(findDuplicateClaudeAccount([], makeCandidate())).toBe(null) + }) +}) diff --git a/src/main/claude-accounts/claude-duplicate-account.ts b/src/main/claude-accounts/claude-duplicate-account.ts new file mode 100644 index 00000000000..55e57165541 --- /dev/null +++ b/src/main/claude-accounts/claude-duplicate-account.ts @@ -0,0 +1,55 @@ +import type { ClaudeManagedAccount } from '../../shared/types' +import { getClaudeWslSelectionKey } from './runtime-selection' + +export type ClaudeAccountIdentityCandidate = { + email: string | null + organizationUuid: string | null + managedAuthRuntime: 'host' | 'wsl' + wslDistro: string | null +} + +function normalizeEmail(value: string | null | undefined): string | null { + const trimmed = value?.trim().toLowerCase() + return trimmed ? trimmed : null +} + +function normalizeOrganizationUuid(value: string | null | undefined): string | null { + const trimmed = value?.trim() + return trimmed ? trimmed : null +} + +function runtimeScopeKey( + runtime: 'host' | 'wsl' | undefined, + wslDistro: string | null | undefined +): string { + // Why: accounts persisted before the runtime fields existed have + // managedAuthRuntime === undefined; treat that as 'host' so a new host add + // still matches them (#6616). WSL folds through the same distro-bucket key + // runtime-selection.ts uses, so a distro compares equal to its own bucket. + const normalizedRuntime = runtime ?? 'host' + return normalizedRuntime === 'wsl' ? `wsl:${getClaudeWslSelectionKey(wslDistro)}` : 'host' +} + +// Why: the same Claude email can legitimately belong to two organizations, and +// host vs WSL (and each WSL distro) keep separate managed-auth stores — so a +// duplicate is only a match on email + organization + runtime scope, each side +// normalized so a legacy/undefined field cannot dodge the check (#6616). +export function findDuplicateClaudeAccount( + accounts: readonly ClaudeManagedAccount[], + candidate: ClaudeAccountIdentityCandidate +): ClaudeManagedAccount | null { + const email = normalizeEmail(candidate.email) + if (!email) { + return null + } + const organizationUuid = normalizeOrganizationUuid(candidate.organizationUuid) + const scope = runtimeScopeKey(candidate.managedAuthRuntime, candidate.wslDistro) + return ( + accounts.find( + (account) => + normalizeEmail(account.email) === email && + normalizeOrganizationUuid(account.organizationUuid) === organizationUuid && + runtimeScopeKey(account.managedAuthRuntime, account.wslDistro) === scope + ) ?? null + ) +} diff --git a/src/main/claude-accounts/service.test.ts b/src/main/claude-accounts/service.test.ts index 31a62ff7880..1c0f94e172d 100644 --- a/src/main/claude-accounts/service.test.ts +++ b/src/main/claude-accounts/service.test.ts @@ -1,7 +1,7 @@ /* eslint-disable max-lines -- test suite covers Claude capture and rollback edge cases */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { EventEmitter } from 'node:events' -import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { PassThrough } from 'node:stream' @@ -671,6 +671,159 @@ describe('ClaudeAccountService credential capture', () => { ) }) + it('rejects adding a Claude account whose identity already exists', async () => { + setPlatform('linux') + tempDir = '/tmp/orca-claude-service-test' + rmSync(tempDir, { recursive: true, force: true }) + const existingAuthPath = join(tempDir, 'claude-accounts', 'existing-account', 'auth') + mkdirSync(existingAuthPath, { recursive: true }) + const existingMarkerPath = join(existingAuthPath, '.orca-managed-claude-auth') + writeFileSync(existingMarkerPath, 'existing-account\n', 'utf-8') + let settings = { + claudeManagedAccounts: [ + { + id: 'existing-account', + email: 'new@example.com', + managedAuthPath: existingAuthPath, + managedAuthRuntime: 'host', + wslDistro: null, + wslLinuxAuthPath: null, + authMethod: 'subscription-oauth', + organizationUuid: null, + organizationName: null, + createdAt: 1, + updatedAt: 1, + lastAuthenticatedAt: 1 + } + ], + activeClaudeManagedAccountId: 'existing-account', + activeClaudeManagedAccountIdsByRuntime: { host: 'existing-account', wsl: {} } + } + const store = { + getSettings: vi.fn(() => settings), + updateSettings: vi.fn((updates: Partial) => { + settings = { ...settings, ...updates } + return settings + }) + } + const runtimeAuth = { + clearLastWrittenCredentialsJson: vi.fn(), + syncForCurrentSelection: vi.fn(async () => {}), + forceMaterializeCurrentSelectionForRollback: vi.fn(async () => {}) + } + const rateLimits = { + evictInactiveClaudeCache: vi.fn(), + refreshForClaudeAccountChange: vi.fn(async () => ({ accounts: [], activeAccountId: null })) + } + const { ClaudeAccountService } = await import('./service') + const service = new ClaudeAccountService( + store as never, + rateLimits as never, + runtimeAuth as never + ) + ;( + service as unknown as { + runClaudeLoginAndCapture(): Promise<{ + credentialsJson: string + oauthAccount: unknown + identity: { email: string; organizationUuid: string | null; organizationName: null } + }> + } + ).runClaudeLoginAndCapture = vi.fn(async () => ({ + credentialsJson: '{"new":true}\n', + oauthAccount: { newOauth: true }, + identity: { email: 'new@example.com', organizationUuid: null, organizationName: null } + })) + + await expect(service.addAccount({ runtime: 'host' })).rejects.toThrow( + 'This Claude account is already added.' + ) + + expect(settings.claudeManagedAccounts).toHaveLength(1) + expect(readFileSync(existingMarkerPath, 'utf-8')).toBe('existing-account\n') + // The guard fires before credentials/settings change, so rollback I/O + // would only add latency and could mask the duplicate error. + expect(store.updateSettings).not.toHaveBeenCalled() + expect(runtimeAuth.forceMaterializeCurrentSelectionForRollback).not.toHaveBeenCalled() + // The rejected add's throwaway managed-auth dir must be cleaned up, leaving + // only the pre-existing account's dir behind. + expect(readdirSync(join(tempDir, 'claude-accounts')).sort()).toEqual(['existing-account']) + }) + + it('adds a Claude account with the same email under a different organization', async () => { + setPlatform('linux') + tempDir = '/tmp/orca-claude-service-test' + rmSync(tempDir, { recursive: true, force: true }) + const existingAuthPath = join(tempDir, 'claude-accounts', 'existing-account', 'auth') + mkdirSync(existingAuthPath, { recursive: true }) + writeFileSync( + join(existingAuthPath, '.orca-managed-claude-auth'), + 'existing-account\n', + 'utf-8' + ) + let settings = { + claudeManagedAccounts: [ + { + id: 'existing-account', + email: 'new@example.com', + managedAuthPath: existingAuthPath, + managedAuthRuntime: 'host', + wslDistro: null, + wslLinuxAuthPath: null, + authMethod: 'subscription-oauth', + organizationUuid: 'org-A', + organizationName: null, + createdAt: 1, + updatedAt: 1, + lastAuthenticatedAt: 1 + } + ], + activeClaudeManagedAccountId: 'existing-account', + activeClaudeManagedAccountIdsByRuntime: { host: 'existing-account', wsl: {} } + } + const store = { + getSettings: vi.fn(() => settings), + updateSettings: vi.fn((updates: Partial) => { + settings = { ...settings, ...updates } + return settings + }) + } + const runtimeAuth = { + clearLastWrittenCredentialsJson: vi.fn(), + syncForCurrentSelection: vi.fn(async () => {}), + forceMaterializeCurrentSelectionForRollback: vi.fn(async () => {}) + } + const rateLimits = { + evictInactiveClaudeCache: vi.fn(), + refreshForClaudeAccountChange: vi.fn(async () => ({ accounts: [], activeAccountId: null })) + } + const { ClaudeAccountService } = await import('./service') + const service = new ClaudeAccountService( + store as never, + rateLimits as never, + runtimeAuth as never + ) + ;( + service as unknown as { + runClaudeLoginAndCapture(): Promise<{ + credentialsJson: string + oauthAccount: unknown + identity: { email: string; organizationUuid: string | null; organizationName: null } + }> + } + ).runClaudeLoginAndCapture = vi.fn(async () => ({ + credentialsJson: '{"new":true}\n', + oauthAccount: { newOauth: true }, + identity: { email: 'new@example.com', organizationUuid: 'org-B', organizationName: null } + })) + + await service.addAccount({ runtime: 'host' }) + + expect(settings.claudeManagedAccounts).toHaveLength(2) + expect(settings.claudeManagedAccounts[1].email).toBe('new@example.com') + expect(settings.claudeManagedAccounts[1].organizationUuid).toBe('org-B') + }) + it('switches the active Claude account while PTYs are live', async () => { setPlatform('linux') tempDir = '/tmp/orca-claude-service-test' diff --git a/src/main/claude-accounts/service.ts b/src/main/claude-accounts/service.ts index eac29e22c6d..4cf9d256d5a 100644 --- a/src/main/claude-accounts/service.ts +++ b/src/main/claude-accounts/service.ts @@ -30,6 +30,7 @@ import { writeManagedClaudeKeychainCredentials } from './keychain' import { beginClaudeAuthSwitch, endClaudeAuthSwitch } from './live-pty-gate' +import { findDuplicateClaudeAccount } from './claude-duplicate-account' import { parseWslUncPath } from '../../shared/wsl-paths' import { toWindowsWslPath } from '../wsl' import { buildEncodedWslBashCommand } from '../wsl-bash-command' @@ -139,12 +140,26 @@ export class ClaudeAccountService { const managedAuth = this.createManagedAuthDir(accountId, target) const { managedAuthPath } = managedAuth const previousSettings = this.store.getSettings() + let duplicateIdentityFound = false try { const captured = await this.runClaudeLoginAndCapture(managedAuth) if (!captured.identity.email) { throw new Error('Claude login completed, but Orca could not resolve the account email.') } + // Why: duplicate rows confuse account selection and rate-limit tracking; + // the per-row Re-authenticate action already refreshes credentials. + if ( + findDuplicateClaudeAccount(previousSettings.claudeManagedAccounts, { + email: captured.identity.email, + organizationUuid: captured.identity.organizationUuid, + managedAuthRuntime: managedAuth.managedAuthRuntime, + wslDistro: managedAuth.wslDistro + }) + ) { + duplicateIdentityFound = true + throw new Error('This Claude account is already added.') + } await this.writeManagedAuth(accountId, managedAuthPath, captured) const now = Date.now() @@ -173,8 +188,12 @@ export class ClaudeAccountService { this.rateLimits.evictInactiveClaudeCache(accountId) return this.getSnapshot() } catch (error) { - this.restoreClaudeSettings(previousSettings) - await this.runtimeAuth.forceMaterializeCurrentSelectionForRollback() + // Duplicate detection precedes every credential/settings write, so only + // its throwaway auth directory needs cleanup. + if (!duplicateIdentityFound) { + this.restoreClaudeSettings(previousSettings) + await this.runtimeAuth.forceMaterializeCurrentSelectionForRollback() + } await this.safeRemoveManagedAuth(accountId, managedAuthPath) throw error } From 37cd830192309267e224bbbaf413a22d73de8f37 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sun, 19 Jul 2026 13:09:36 -0700 Subject: [PATCH 072/137] Confirm before merging a PR/MR from the sidebar strategy dropdown (#8911) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The right-sidebar Checks panel merge control merged the PR/MR immediately when a strategy was chosen from its dropdown, with no confirmation — so opening the dropdown to switch strategies (e.g. Squash -> Merge commit) merged on the spot. Every other PR/MR merge surface (PullRequestPage, GitHubItemDialog, TaskPage) already confirms first, and the close/reopen path in this same hook confirms too; only the hosted-review merge path was missing the gate. Add the same confirmation dialog to the hosted-review handleMerge, provider-aware so it reads "Squash and merge PR #N?" / "Squash and merge MR !N?". Selecting a strategy now asks before merging; the merge still runs when explicitly confirmed. Fixes #7943 --- .../use-hosted-review-actions.test.tsx | 87 +++++++++++++++++-- .../use-hosted-review-actions.ts | 28 +++++- src/renderer/src/i18n/locales/en.json | 7 ++ src/renderer/src/i18n/locales/es.json | 7 ++ src/renderer/src/i18n/locales/ja.json | 7 ++ src/renderer/src/i18n/locales/ko.json | 7 ++ src/renderer/src/i18n/locales/zh.json | 7 ++ 7 files changed, 142 insertions(+), 8 deletions(-) diff --git a/src/renderer/src/components/right-sidebar/use-hosted-review-actions.test.tsx b/src/renderer/src/components/right-sidebar/use-hosted-review-actions.test.tsx index 82527dc8ed5..761abf269f4 100644 --- a/src/renderer/src/components/right-sidebar/use-hosted-review-actions.test.tsx +++ b/src/renderer/src/components/right-sidebar/use-hosted-review-actions.test.tsx @@ -46,14 +46,25 @@ function makeRepo(overrides: Partial = {}): Repo { } as Repo } -function HookProbe(props: { repo: Repo; onRefreshReview: () => Promise }): null { +type ProviderOverrides = { + review?: HostedReviewActionInfo + isGitLab?: boolean + shortLabel?: string + reviewLabel?: string +} + +function HookProbe(props: { + repo: Repo + onRefreshReview: () => Promise + overrides?: ProviderOverrides +}): null { latest = useHostedReviewActions({ - review, + review: props.overrides?.review ?? review, githubPR, repo: props.repo, - isGitLab: false, - shortLabel: 'PR', - reviewLabel: 'pull request', + isGitLab: props.overrides?.isGitLab ?? false, + shortLabel: props.overrides?.shortLabel ?? 'PR', + reviewLabel: props.overrides?.reviewLabel ?? 'pull request', defaultMergeMethod: 'squash', autoMergeAction: null, onRefreshReview: props.onRefreshReview @@ -61,12 +72,16 @@ function HookProbe(props: { repo: Repo; onRefreshReview: () => Promise }): return null } -async function renderHook(repo: Repo, onRefreshReview = vi.fn().mockResolvedValue(undefined)) { +async function renderHook( + repo: Repo, + onRefreshReview = vi.fn().mockResolvedValue(undefined), + overrides?: ProviderOverrides +) { const container = document.createElement('div') document.body.appendChild(container) root = createRoot(container) await act(async () => { - root?.render(createElement(HookProbe, { repo, onRefreshReview })) + root?.render(createElement(HookProbe, { repo, onRefreshReview, overrides })) }) return { onRefreshReview } } @@ -140,4 +155,62 @@ describe('useHostedReviewActions', () => { expect(runtimeRpcMocks.callRuntimeRpc).not.toHaveBeenCalled() expect(onRefreshReview).toHaveBeenCalledTimes(1) }) + + it('confirms before merging with the selected strategy label (#7943)', async () => { + await renderHook(makeRepo()) + + await act(async () => { + await latest?.handleMerge('merge') + }) + + expect(confirmationMocks.confirm).toHaveBeenCalledWith( + expect.objectContaining({ + title: 'Create merge commit PR #1015?', + confirmLabel: 'Create merge commit' + }) + ) + }) + + it('does not merge when the confirmation is cancelled (#7943)', async () => { + confirmationMocks.confirm.mockResolvedValue(false) + const { onRefreshReview } = await renderHook(makeRepo()) + + await act(async () => { + await latest?.handleMerge('squash') + }) + + expect(confirmationMocks.confirm).toHaveBeenCalledTimes(1) + expect(window.api.gh.mergePR).not.toHaveBeenCalled() + expect(runtimeRpcMocks.callRuntimeRpc).not.toHaveBeenCalled() + expect(onRefreshReview).not.toHaveBeenCalled() + }) + + it('confirms GitLab MR merges with provider-aware copy (#7943)', async () => { + await renderHook(makeRepo(), undefined, { + review: { + provider: 'gitlab', + number: 42, + state: 'open', + status: 'success', + mergeable: 'MERGEABLE' + }, + isGitLab: true, + shortLabel: 'MR', + reviewLabel: 'merge request' + }) + + await act(async () => { + await latest?.handleMerge('squash') + }) + + expect(confirmationMocks.confirm).toHaveBeenCalledWith( + expect.objectContaining({ title: 'Squash and merge MR !42?' }) + ) + expect(window.api.gl.mergeMR).toHaveBeenCalledWith({ + repoPath: '/repo', + repoId: 'repo-1', + iid: 42, + method: 'squash' + }) + }) }) diff --git a/src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts b/src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts index ed3a1b23bcc..7f2274bfdcc 100644 --- a/src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts +++ b/src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts @@ -5,6 +5,7 @@ import type { GitHubPRAutoMergeAction } from '@/components/github-pr-merge-state import type { HostedReviewInfo } from '../../../../shared/hosted-review' import type { PRInfo, Repo } from '../../../../shared/types' import type { GitHubPRMergeMethod } from '../../../../shared/types' +import { GITHUB_PR_MERGE_METHOD_LABELS } from '../../../../shared/github-pr-merge-methods' import { mergeGitHubHostedReview, setGitHubHostedReviewAutoMerge, @@ -63,6 +64,21 @@ export function useHostedReviewActions({ const handleMerge = useCallback( async (method: GitHubPRMergeMethod = defaultMergeMethod) => { + // Why: choosing a strategy from the merge dropdown must not merge on its + // own — confirm first, matching every other PR/MR merge surface (#7943). + const label = GITHUB_PR_MERGE_METHOD_LABELS[method] + const confirmed = await confirm({ + title: `${label} ${shortLabel} ${isGitLab ? '!' : '#'}${review.number}?`, + description: translate( + 'auto.components.right.sidebar.use.hosted.review.actions.e475c29b17', + 'This will merge the {{value0}}.', + { value0: reviewLabel } + ), + confirmLabel: label + }) + if (!confirmed) { + return + } setMerging(true) setActionError(null) try { @@ -90,7 +106,17 @@ export function useHostedReviewActions({ setMerging(false) } }, - [githubPR?.prRepo, isGitLab, defaultMergeMethod, onRefreshReview, repo, review.number] + [ + confirm, + githubPR?.prRepo, + isGitLab, + shortLabel, + reviewLabel, + defaultMergeMethod, + onRefreshReview, + repo, + review.number + ] ) const handleAutoMerge = useCallback(async () => { diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 5d48f72c149..9bad363c2b3 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -10098,6 +10098,13 @@ "cfafa92509": "No unresolved conflicts to send." } } + }, + "hosted": { + "review": { + "actions": { + "e475c29b17": "This will merge the {{value0}}." + } + } } }, "fileExplorerOperationOwner": { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 163138a3f18..05b76df3900 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -10075,6 +10075,13 @@ "cfafa92509": "No hay conflictos sin resolver para enviar." } } + }, + "hosted": { + "review": { + "actions": { + "e475c29b17": "This will merge the {{value0}}." + } + } } }, "fileExplorerOperationOwner": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 9bcc9051304..8150d25ca97 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -10075,6 +10075,13 @@ "cfafa92509": "送信する未解決の競合はありません。" } } + }, + "hosted": { + "review": { + "actions": { + "e475c29b17": "This will merge the {{value0}}." + } + } } }, "fileExplorerOperationOwner": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 73b1d12b378..cc28a9e9dd9 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -10075,6 +10075,13 @@ "cfafa92509": "보낼 해결되지 않은 충돌이 없습니다." } } + }, + "hosted": { + "review": { + "actions": { + "e475c29b17": "This will merge the {{value0}}." + } + } } }, "fileExplorerOperationOwner": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 874022be095..a649bc1611c 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -10075,6 +10075,13 @@ "cfafa92509": "没有未解决的冲突需要发送。" } } + }, + "hosted": { + "review": { + "actions": { + "e475c29b17": "This will merge the {{value0}}." + } + } } }, "fileExplorerOperationOwner": { From a833b5cba6c9fe8e26b893c58b613fdb980d0572 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sun, 19 Jul 2026 13:15:14 -0700 Subject: [PATCH 073/137] Clear a removed remote host's leftover repos and worktrees from the sidebar (#9463) When a runtime host identity (runtime:) is removed from the saved list, retire the repos, project host setups, and worktree rows it owned so the same physical checkout stops duplicating in the sidebar. setRuntimeEnvironments now diffs the previous in-memory saved list against the new one and purges state owned by any environment that just left, routed through the existing worktree purge cascade (tabs/PTY/browser/editor/agent maps). Scoped to the removal diff, not an absolute keep-set, so a serving instance's locally-persisted runtime-stamped repos (whose env id was never in this instance's saved list) are never torn down. A tombstone set (removedRuntimeEnvironmentIds) guards the three repo-catalog merges so an in-flight fetch for a just-removed env can't re-add purged repos, while a runtime env merely absent from a not-yet-hydrated saved list still merges normally. Refs #8881 --- .../purge-stale-runtime-host-state.test.ts | 250 ++++++++++++++++++ src/renderer/src/store/slices/repos.ts | 19 ++ ...me-catalog-merge-removed-env-guard.test.ts | 117 ++++++++ ...host-purge-session-partition-split.test.ts | 90 +++++++ .../src/store/slices/runtime-status.ts | 43 ++- ...-runtime-environments-purge-wiring.test.ts | 100 +++++++ .../slices/stale-runtime-host-rows.test.ts | 100 +++++++ .../store/slices/stale-runtime-host-rows.ts | 58 ++++ .../src/store/slices/worktree-helpers.ts | 9 + src/renderer/src/store/slices/worktrees.ts | 77 ++++++ 10 files changed, 861 insertions(+), 2 deletions(-) create mode 100644 src/renderer/src/store/slices/purge-stale-runtime-host-state.test.ts create mode 100644 src/renderer/src/store/slices/runtime-catalog-merge-removed-env-guard.test.ts create mode 100644 src/renderer/src/store/slices/runtime-host-purge-session-partition-split.test.ts create mode 100644 src/renderer/src/store/slices/set-runtime-environments-purge-wiring.test.ts create mode 100644 src/renderer/src/store/slices/stale-runtime-host-rows.test.ts create mode 100644 src/renderer/src/store/slices/stale-runtime-host-rows.ts diff --git a/src/renderer/src/store/slices/purge-stale-runtime-host-state.test.ts b/src/renderer/src/store/slices/purge-stale-runtime-host-state.test.ts new file mode 100644 index 00000000000..e6926e7669d --- /dev/null +++ b/src/renderer/src/store/slices/purge-stale-runtime-host-state.test.ts @@ -0,0 +1,250 @@ +/** + * Action coverage for `purgeStaleRuntimeHostState` (#8881). + * + * Removing a runtime identity must retire the repos/setups/rows/detected rows and + * cascaded tab state it owned, while leaving local, ssh, and — the P1 regression — + * a *never-saved* runtime-stamped LOCAL repo (a serving instance's own row) + * untouched. The action is scoped to the removal diff, so it must no-op (no + * sortEpoch bump) for an empty diff or an env that matches nothing. + */ +import { describe, it, expect, vi } from 'vitest' +import { toRuntimeExecutionHostId } from '../../../../shared/execution-host' +import type { ProjectHostSetup, DetectedWorktreeListResult } from '../../../../shared/types' + +vi.mock('sonner', () => ({ + toast: { info: vi.fn(), success: vi.fn(), error: vi.fn(), warning: vi.fn() } +})) + +vi.mock('@/components/terminal-pane/pty-dispatcher', () => ({ + restorePtyDataHandlersAfterFailedShutdown: vi.fn(), + unregisterPtyDataHandlers: vi.fn() +})) + +// @ts-expect-error -- minimal window.api stub for the store under test +globalThis.window = { api: {} } + +import { createTestStore, seedStore, makeWorktree, makeTab, TEST_REPO } from './store-test-helpers' + +const RUNTIME_A = toRuntimeExecutionHostId('env-a') +const RUNTIME_NEVER_SAVED = toRuntimeExecutionHostId('env-serving-client') + +function setup(overrides: Partial & { id: string }): ProjectHostSetup { + return { + projectId: 'p', + hostId: 'local', + repoId: '', + path: '/tmp', + displayName: 'setup', + setupState: 'ready', + setupMethod: 'existing', + createdAt: 0, + updatedAt: 0, + ...overrides + } as ProjectHostSetup +} + +function detectedResult( + repoId: string, + worktrees: DetectedWorktreeListResult['worktrees'] +): DetectedWorktreeListResult { + return { repoId, authoritative: true, source: 'git', worktrees } +} + +function detected( + id: string, + repoId: string, + hostId: ReturnType | 'local' +): DetectedWorktreeListResult['worktrees'][number] { + return { + ...makeWorktree({ id, repoId, hostId }), + ownership: 'orca-managed', + selectedCheckout: true, + visible: true + } as DetectedWorktreeListResult['worktrees'][number] +} + +describe('purgeStaleRuntimeHostState', () => { + it('purges the removed env and preserves local + never-saved runtime-stamped repos', () => { + const store = createTestStore() + const WT_A = 'repoA::/wt-a' + const TAB_A = 'tab-a' + seedStore(store, { + // A local repo, a runtime:env-a repo (the removed one), and the P1 regression + // case: a LOCAL repo carrying a runtime stamp whose env was never saved. + repos: [ + TEST_REPO, + { + id: 'repoA', + path: '/repoA', + displayName: 'A', + badgeColor: '#000', + addedAt: 0, + executionHostId: RUNTIME_A + }, + { + id: 'repoServing', + path: '/repoServing', + displayName: 'Serving', + badgeColor: '#000', + addedAt: 0, + executionHostId: RUNTIME_NEVER_SAVED + } + ], + projectHostSetups: [ + setup({ id: 's-local', repoId: 'repo1', hostId: 'local' }), + setup({ id: 's-a', repoId: 'repoA', hostId: RUNTIME_A }), + setup({ id: 's-a-repoless', repoId: '', hostId: RUNTIME_A }), + setup({ id: 's-serving', repoId: 'repoServing', hostId: RUNTIME_NEVER_SAVED }) + ], + worktreesByRepo: { + repo1: [makeWorktree({ id: 'repo1::/wt-local', repoId: 'repo1', hostId: 'local' })], + repoA: [makeWorktree({ id: WT_A, repoId: 'repoA', path: '/wt-a', hostId: RUNTIME_A })], + repoServing: [ + makeWorktree({ + id: 'repoServing::/wt-s', + repoId: 'repoServing', + hostId: RUNTIME_NEVER_SAVED + }) + ] + }, + detectedWorktreesByRepo: { + repoA: detectedResult('repoA', [detected(WT_A, 'repoA', RUNTIME_A)]) + }, + tabsByWorktree: { [WT_A]: [makeTab({ id: TAB_A, worktreeId: WT_A })] } + }) + const epochBefore = store.getState().sortEpoch + + store.getState().purgeStaleRuntimeHostState(['env-a']) + + const s = store.getState() + // Removed env's repo/setups/rows/detected/tab all gone. + expect(s.repos.map((r) => r.id)).toEqual(['repo1', 'repoServing']) + expect(s.projectHostSetups.map((x) => x.id).sort()).toEqual(['s-local', 's-serving']) + expect(s.worktreesByRepo.repoA).toEqual([]) + expect(s.detectedWorktreesByRepo.repoA.worktrees).toEqual([]) + expect(s.tabsByWorktree[WT_A]).toBeUndefined() + // Local repo AND the never-saved runtime-stamped local repo BOTH survive. + expect(s.worktreesByRepo.repo1).toHaveLength(1) + expect(s.worktreesByRepo.repoServing).toHaveLength(1) + // sortEpoch bumped because rows changed. + expect(s.sortEpoch).toBe(epochBefore + 1) + }) + + it('keeps a still-saved sibling runtime env when another runtime env is removed', () => { + const store = createTestStore() + const RUNTIME_B = toRuntimeExecutionHostId('env-b') + seedStore(store, { + repos: [ + { + id: 'repoA', + path: '/repoA', + displayName: 'A', + badgeColor: '#000', + addedAt: 0, + executionHostId: RUNTIME_A + }, + { + id: 'repoB', + path: '/repoB', + displayName: 'B', + badgeColor: '#000', + addedAt: 0, + executionHostId: RUNTIME_B + } + ], + worktreesByRepo: { + repoA: [makeWorktree({ id: 'repoA::/wt-a', repoId: 'repoA', hostId: RUNTIME_A })], + repoB: [makeWorktree({ id: 'repoB::/wt-b', repoId: 'repoB', hostId: RUNTIME_B })] + } + }) + + // Only env-a is removed; env-b is a still-saved sibling runtime host. + store.getState().purgeStaleRuntimeHostState(['env-a']) + + const s = store.getState() + expect(s.repos.map((r) => r.id)).toEqual(['repoB']) + expect(s.worktreesByRepo.repoB).toHaveLength(1) + expect(s.worktreesByRepo.repoA).toEqual([]) + }) + + it('is a no-op (no sortEpoch bump) for an empty removed set and for a non-matching env', () => { + const store = createTestStore() + seedStore(store, { + repos: [TEST_REPO], + worktreesByRepo: { + repo1: [makeWorktree({ id: 'repo1::/wt', repoId: 'repo1', hostId: 'local' })] + } + }) + const before = store.getState() + const epochBefore = before.sortEpoch + const worktreesRef = before.worktreesByRepo + const reposRef = before.repos + + store.getState().purgeStaleRuntimeHostState([]) + store.getState().purgeStaleRuntimeHostState(['env-does-not-exist']) + + const s = store.getState() + expect(s.sortEpoch).toBe(epochBefore) + // Nothing stale => the reducer returns state untouched (same references). + expect(s.worktreesByRepo).toBe(worktreesRef) + expect(s.repos).toBe(reposRef) + }) + + it('same-repoId-on-two-hosts: keeps the local row and the repo key after purge', () => { + const store = createTestStore() + // repoId 'shared' exists under both local and runtime:env-a in worktreesByRepo. + seedStore(store, { + repos: [ + { id: 'shared', path: '/shared', displayName: 'shared', badgeColor: '#000', addedAt: 0 }, + { + id: 'sharedRuntime', + path: '/shared', + displayName: 'shared', + badgeColor: '#000', + addedAt: 0, + executionHostId: RUNTIME_A + } + ], + worktreesByRepo: { + shared: [ + makeWorktree({ id: 'shared::/wt-local', repoId: 'shared', hostId: 'local' }), + makeWorktree({ id: 'shared::/wt-a', repoId: 'shared', hostId: RUNTIME_A }) + ] + } + }) + + store.getState().purgeStaleRuntimeHostState(['env-a']) + + const s = store.getState() + expect(s.worktreesByRepo).toHaveProperty('shared') + expect(s.worktreesByRepo.shared.map((w) => w.id)).toEqual(['shared::/wt-local']) + }) + + it('clears activeRepoId and drops the purged id from filterRepoIds', () => { + const store = createTestStore() + seedStore(store, { + repos: [ + TEST_REPO, + { + id: 'repoA', + path: '/repoA', + displayName: 'A', + badgeColor: '#000', + addedAt: 0, + executionHostId: RUNTIME_A + } + ], + worktreesByRepo: { + repoA: [makeWorktree({ id: 'repoA::/wt-a', repoId: 'repoA', hostId: RUNTIME_A })] + }, + activeRepoId: 'repoA', + filterRepoIds: ['repo1', 'repoA'] + }) + + store.getState().purgeStaleRuntimeHostState(['env-a']) + + const s = store.getState() + expect(s.activeRepoId).toBeNull() + expect(s.filterRepoIds).toEqual(['repo1']) + }) +}) diff --git a/src/renderer/src/store/slices/repos.ts b/src/renderer/src/store/slices/repos.ts index 476092c2c34..2bb99fa3484 100644 --- a/src/renderer/src/store/slices/repos.ts +++ b/src/renderer/src/store/slices/repos.ts @@ -85,6 +85,7 @@ import { toSshExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host' +import { isRemovedRuntimeHostId } from './stale-runtime-host-rows' import { cleanupEphemeralVmRuntimesForDeleted } from '@/lib/ephemeral-vm-runtime-cleanup' import { folderWorkspaceKey, parseWorkspaceKey } from '../../../../shared/workspace-scope' import { formatFolderWorkspaceCreateError } from '../../lib/folder-workspace-path-status' @@ -1562,6 +1563,13 @@ export const createRepoSlice: StateCreator = (set, } let finalizedHostRepos: Repo[] = [] set((s) => { + // Why: an in-flight fetch for a just-removed runtime env would otherwise + // re-add purged repos/rows and stick — nothing re-triggers the purge. Skip + // only when the catalog's env was actually removed (tombstoned), not merely + // absent from the not-yet-hydrated saved list (#8881). + if (isRemovedRuntimeHostId(catalog.hostId, s.removedRuntimeEnvironmentIds)) { + return s + } // Why: after re-adoption re-points a repo onto a re-added SSH target, the // per-host merge leaves the stale row on the old (removed) target id — a // ghost a terminal pane can bind to and fail with "SSH target not found". @@ -1616,6 +1624,11 @@ export const createRepoSlice: StateCreator = (set, const catalog = await fetchRepoCatalogForTarget(target) let finalizedHostRepos: Repo[] = [] set((s) => { + // Why: skip a merge for a runtime env removed while this Connect-flow fetch + // was in flight, so purged repos/rows are not re-added (#8881). + if (isRemovedRuntimeHostId(catalog.hostId, s.removedRuntimeEnvironmentIds)) { + return s + } const result = mergeFetchedRepoCatalog(catalog, s.repos) const reconciliation = reconcileSupersededSshRepos(result.repos, s) const finalizedRepos = applyManualRepoOrder(reconciliation.repos, s.manualRepoOrder) @@ -1682,6 +1695,12 @@ export const createRepoSlice: StateCreator = (set, } let hostRepos: Repo[] = [] set((s) => { + // Why: an all-host refresh can still be merging a host removed mid-load; + // skip a catalog whose env was tombstoned by a removal, not one merely + // absent from the not-yet-hydrated saved list (#8881). + if (isRemovedRuntimeHostId(catalog.hostId, s.removedRuntimeEnvironmentIds)) { + return s + } const result = mergeFetchedRepoCatalog(catalog, s.repos) const reconciliation = reconcileSupersededSshRepos(result.repos, s) const finalizedRepos = applyManualRepoOrder(reconciliation.repos, s.manualRepoOrder) diff --git a/src/renderer/src/store/slices/runtime-catalog-merge-removed-env-guard.test.ts b/src/renderer/src/store/slices/runtime-catalog-merge-removed-env-guard.test.ts new file mode 100644 index 00000000000..972a78b1ad2 --- /dev/null +++ b/src/renderer/src/store/slices/runtime-catalog-merge-removed-env-guard.test.ts @@ -0,0 +1,117 @@ +/** + * Catalog-merge guard coverage for #8881: an in-flight catalog fetch for a runtime + * env removed mid-load must NOT re-add the purged repos, because nothing + * re-triggers the purge afterwards. The guard keys on `catalog.hostId` and the + * *tombstone* set (`removedRuntimeEnvironmentIds`), so only a genuinely-removed env + * is skipped — a runtime env merely absent from a not-yet-hydrated saved list still + * merges (else boot would drop legitimate runtime repos), and a still-saved env or a + * `hostId: 'local'` catalog whose repos carry runtime stamps also merges. + */ +import { describe, it, expect, vi } from 'vitest' +import type * as RuntimeRpcClientModule from '@/runtime/runtime-rpc-client' +import type { Repo } from '../../../../shared/types' +import type { PublicKnownRuntimeEnvironment } from '../../../../shared/runtime-environments' + +vi.mock('sonner', () => ({ + toast: { info: vi.fn(), success: vi.fn(), error: vi.fn(), warning: vi.fn() } +})) + +vi.mock('@/components/terminal-pane/pty-dispatcher', () => ({ + restorePtyDataHandlersAfterFailedShutdown: vi.fn(), + unregisterPtyDataHandlers: vi.fn() +})) + +// Every runtime RPC (repo.list, project.list, projectHostSetup.list, capability +// probes) resolves this shape — repo.list reads `.repos`; the compatibility probes +// read `.projects` / `.setups`; anything else falls back safely. +const rpcResult = vi.hoisted(() => ({ current: { repos: [] as Repo[], projects: [], setups: [] } })) + +vi.mock('@/runtime/runtime-rpc-client', async (importOriginal) => { + const actual = await importOriginal() + return { ...actual, callRuntimeRpc: vi.fn(async () => rpcResult.current) } +}) + +const localReposList = vi.fn<() => Promise>() + +// @ts-expect-error -- minimal window.api stub for the store under test +globalThis.window = { api: { repos: { list: localReposList } } } + +import { createTestStore, seedStore, TEST_REPO } from './store-test-helpers' + +function env(id: string): PublicKnownRuntimeEnvironment { + return { id } as unknown as PublicKnownRuntimeEnvironment +} + +function repo(id: string, extra: Partial = {}): Repo { + return { + id, + path: `/${id}`, + displayName: id, + badgeColor: '#000', + addedAt: 0, + ...extra + } as Repo +} + +describe('environment-catalog merge guard drops catalogs for a removed env', () => { + it('fetchRuntimeEnvironmentRepos skips the merge when the catalog env was removed (tombstoned)', async () => { + const store = createTestStore() + seedStore(store, { + repos: [TEST_REPO], + runtimeEnvironments: [env('env-gone'), env('env-b')] + }) + // Remove env-gone from the saved list → tombstoned. A catalog fetch for it that + // was already in flight across the removal must not re-add its repos. + store.getState().setRuntimeEnvironments([env('env-b')]) + rpcResult.current = { repos: [repo('ghost')], projects: [], setups: [] } + + await store.getState().fetchRuntimeEnvironmentRepos('env-gone') + + expect(store.getState().repos.map((r) => r.id)).toEqual(['repo1']) + }) + + it('fetchRuntimeEnvironmentRepos merges a catalog whose env is absent but not tombstoned (not yet hydrated)', async () => { + // The P1 the tombstone fixes: on boot the saved list can still be empty while a + // runtime catalog is enumerated from disk. Absence must NOT skip the merge. + const store = createTestStore() + seedStore(store, { repos: [TEST_REPO], runtimeEnvironments: [] }) + rpcResult.current = { repos: [repo('fresh')], projects: [], setups: [] } + + await store.getState().fetchRuntimeEnvironmentRepos('env-hydrating') + + expect( + store + .getState() + .repos.map((r) => r.id) + .sort() + ).toEqual(['fresh', 'repo1']) + }) + + it('fetchRuntimeEnvironmentRepos merges the catalog for a still-saved env id', async () => { + const store = createTestStore() + seedStore(store, { repos: [TEST_REPO], runtimeEnvironments: [env('env-saved')] }) + rpcResult.current = { repos: [repo('fresh')], projects: [], setups: [] } + + await store.getState().fetchRuntimeEnvironmentRepos('env-saved') + + expect( + store + .getState() + .repos.map((r) => r.id) + .sort() + ).toEqual(['fresh', 'repo1']) + }) + + it("fetchRepos merges a 'local' catalog even when its repos carry runtime stamps", async () => { + const store = createTestStore() + seedStore(store, { repos: [TEST_REPO], runtimeEnvironments: [] }) + // A serving instance's own local catalog: hostId 'local', repos runtime-stamped. + localReposList.mockResolvedValue([ + repo('localStamped', { executionHostId: 'runtime:some-client' }) + ]) + + await store.getState().fetchRepos() + + expect(store.getState().repos.map((r) => r.id)).toContain('localStamped') + }) +}) diff --git a/src/renderer/src/store/slices/runtime-host-purge-session-partition-split.test.ts b/src/renderer/src/store/slices/runtime-host-purge-session-partition-split.test.ts new file mode 100644 index 00000000000..da09283ffcb --- /dev/null +++ b/src/renderer/src/store/slices/runtime-host-purge-session-partition-split.test.ts @@ -0,0 +1,90 @@ +/** + * Directly covers the "stops new stale-partition writes" claim of #8881: once the + * purge removes a removed-env runtime worktree row + repo, `buildHostIdByWorktreeId` + * must resolve that worktree to 'local' (its owner is gone) and + * `patchWorkspaceSessionByHost` must no longer route a write to the removed runtime + * partition. + */ +import { describe, it, expect, vi } from 'vitest' +import { toRuntimeExecutionHostId } from '../../../../shared/execution-host' +import type { WorkspaceSessionPatch } from '../../../../shared/types' +import { + buildHostIdByWorktreeId, + patchWorkspaceSessionByHost, + type HostPersistenceState +} from '@/lib/workspace-session-host-persistence' + +vi.mock('sonner', () => ({ + toast: { info: vi.fn(), success: vi.fn(), error: vi.fn(), warning: vi.fn() } +})) + +vi.mock('@/components/terminal-pane/pty-dispatcher', () => ({ + restorePtyDataHandlersAfterFailedShutdown: vi.fn(), + unregisterPtyDataHandlers: vi.fn() +})) + +// @ts-expect-error -- minimal window.api stub for the store under test +globalThis.window = { api: {} } + +import { createTestStore, seedStore, makeWorktree, TEST_REPO } from './store-test-helpers' + +const RUNTIME_A = toRuntimeExecutionHostId('env-a') +const WT_A = 'repoA::/wt-a' + +function seedRuntimeRow(store: ReturnType): void { + seedStore(store, { + repos: [ + TEST_REPO, + { + id: 'repoA', + path: '/repoA', + displayName: 'A', + badgeColor: '#000', + addedAt: 0, + executionHostId: RUNTIME_A + } + ], + worktreesByRepo: { + repoA: [makeWorktree({ id: WT_A, repoId: 'repoA', path: '/wt-a', hostId: RUNTIME_A })] + } + }) +} + +describe('purge stops routing session writes to the removed runtime partition', () => { + it('buildHostIdByWorktreeId resolves the removed worktree to local after purge', () => { + const store = createTestStore() + seedRuntimeRow(store) + + // Before: the row is owned by the runtime host. + expect(buildHostIdByWorktreeId(store.getState() as HostPersistenceState)(WT_A)).toBe(RUNTIME_A) + + store.getState().purgeStaleRuntimeHostState(['env-a']) + + // After: its owner is gone, so it falls back to the local partition. + expect(buildHostIdByWorktreeId(store.getState() as HostPersistenceState)(WT_A)).toBe('local') + }) + + it('patchWorkspaceSessionByHost no longer writes the runtime partition after purge', async () => { + const store = createTestStore() + seedRuntimeRow(store) + const api = { + get: vi.fn().mockResolvedValue({}), + patch: vi.fn().mockResolvedValue(undefined), + setSync: vi.fn() + } + const patch = { activeWorktreeIdsOnShutdown: [WT_A] } as unknown as WorkspaceSessionPatch + + await patchWorkspaceSessionByHost(api, patch, store.getState() as HostPersistenceState) + const runtimeWritesBefore = api.patch.mock.calls.filter(([, hostId]) => hostId === RUNTIME_A) + expect(runtimeWritesBefore).toHaveLength(1) + + store.getState().purgeStaleRuntimeHostState(['env-a']) + api.patch.mockClear() + + await patchWorkspaceSessionByHost(api, patch, store.getState() as HostPersistenceState) + const runtimeWritesAfter = api.patch.mock.calls.filter(([, hostId]) => hostId === RUNTIME_A) + expect(runtimeWritesAfter).toHaveLength(0) + // The local write still happens. + expect(api.patch).toHaveBeenCalled() + }) +}) diff --git a/src/renderer/src/store/slices/runtime-status.ts b/src/renderer/src/store/slices/runtime-status.ts index e9dae52e299..cd953432985 100644 --- a/src/renderer/src/store/slices/runtime-status.ts +++ b/src/renderer/src/store/slices/runtime-status.ts @@ -23,7 +23,14 @@ export type RuntimeStatusSlice = { /** Keyed by runtime environment id. Fed into buildExecutionHostRegistry so * compat verdicts/blocked health show live in the sidebar host pickers. */ runtimeStatusByEnvironmentId: Map - /** Replaces the saved-environment list and trims stale status entries. */ + /** Tombstones of runtime environment ids that were removed from the saved list + * this session and not yet re-added. Distinct from "absent from + * `runtimeEnvironments`", which also matches not-yet-hydrated envs — a + * catalog-merge guard keyed on mere absence would drop legitimate runtime repos + * during boot before the saved list hydrates (#8881). */ + removedRuntimeEnvironmentIds: ReadonlySet + /** Replaces the saved-environment list, trims stale status entries, and + * retires state owned by any environment that just left the saved list. */ setRuntimeEnvironments: (environments: PublicKnownRuntimeEnvironment[]) => void /** Merges one environment's status. Replaces the prior entry for that id. */ setRuntimeEnvironmentStatus: (environmentId: string, status: RuntimeEnvironmentStatus) => void @@ -44,8 +51,16 @@ export const createRuntimeStatusSlice: StateCreator ({ runtimeEnvironments: [], runtimeStatusByEnvironmentId: new Map(), + removedRuntimeEnvironmentIds: new Set(), setRuntimeEnvironments: (environments) => { + // Why: diff against the accumulated in-memory saved list (not a second disk + // read) so a main-initiated removal that never calls setRuntimeEnvironments + // still enters the diff on the next list read. #8881. + const nextIds = new Set(environments.map((environment) => environment.id)) + const removedIds = get() + .runtimeEnvironments.map((environment) => environment.id) + .filter((id) => !nextIds.has(id)) set((s) => { const keep = new Set(environments.map((environment) => environment.id)) const nextStatuses = new Map(s.runtimeStatusByEnvironmentId) @@ -56,9 +71,26 @@ export const createRuntimeStatusSlice: StateCreator environment.id)) // A detached environment's mirrored SSH state must not outlive it. get().retainEnvironmentSshState?.(environments.map((environment) => environment.id)) + // Retire repos/setups/worktree rows owned by a just-removed runtime identity so + // the same checkout stops duplicating in the sidebar. Scoped to the removal diff + // (not an absolute keep-set) to spare a serving instance's local runtime-stamped + // repos, whose env id was never in this instance's saved list. + if (removedIds.length > 0) { + get().purgeStaleRuntimeHostState?.(removedIds) + } }, setRuntimeEnvironmentStatus: (environmentId, status) => { diff --git a/src/renderer/src/store/slices/set-runtime-environments-purge-wiring.test.ts b/src/renderer/src/store/slices/set-runtime-environments-purge-wiring.test.ts new file mode 100644 index 00000000000..80d4c24daad --- /dev/null +++ b/src/renderer/src/store/slices/set-runtime-environments-purge-wiring.test.ts @@ -0,0 +1,100 @@ +/** + * Wiring coverage for #8881: `setRuntimeEnvironments` must fire the stale-runtime + * purge on the removal diff. Load-bearing because the call is optional-chained + * (`get().purgeStaleRuntimeHostState?.(removedIds)`) — a rename/omission would + * silently no-op while every pure-helper unit test still passed. + */ +import { describe, it, expect, vi } from 'vitest' +import { toRuntimeExecutionHostId } from '../../../../shared/execution-host' +import type { ProjectHostSetup } from '../../../../shared/types' +import type { PublicKnownRuntimeEnvironment } from '../../../../shared/runtime-environments' + +vi.mock('sonner', () => ({ + toast: { info: vi.fn(), success: vi.fn(), error: vi.fn(), warning: vi.fn() } +})) + +vi.mock('@/components/terminal-pane/pty-dispatcher', () => ({ + restorePtyDataHandlersAfterFailedShutdown: vi.fn(), + unregisterPtyDataHandlers: vi.fn() +})) + +// @ts-expect-error -- minimal window.api stub for the store under test +globalThis.window = { api: {} } + +import { createTestStore, seedStore, makeWorktree, makeTab, TEST_REPO } from './store-test-helpers' + +const RUNTIME_A = toRuntimeExecutionHostId('env-a') + +function env(id: string): PublicKnownRuntimeEnvironment { + return { id } as unknown as PublicKnownRuntimeEnvironment +} + +function seedStale(store: ReturnType): void { + const WT_A = 'repoA::/wt-a' + seedStore(store, { + // In-memory previous saved list — the diff is computed against this. + runtimeEnvironments: [env('env-a'), env('env-b')], + repos: [ + TEST_REPO, + { + id: 'repoA', + path: '/repoA', + displayName: 'A', + badgeColor: '#000', + addedAt: 0, + executionHostId: RUNTIME_A + } + ], + projectHostSetups: [ + { + id: 's-a', + projectId: 'p', + hostId: RUNTIME_A, + repoId: 'repoA', + path: '/repoA', + displayName: 'A', + setupState: 'ready', + setupMethod: 'imported-existing-folder', + createdAt: 0, + updatedAt: 0 + } as ProjectHostSetup + ], + worktreesByRepo: { + repoA: [makeWorktree({ id: WT_A, repoId: 'repoA', path: '/wt-a', hostId: RUNTIME_A })] + }, + tabsByWorktree: { [WT_A]: [makeTab({ id: 'tab-a', worktreeId: WT_A })] } + }) +} + +describe('setRuntimeEnvironments removal diff fires the stale-runtime purge', () => { + it('purges the removed env owned repo/rows/setup/tab', () => { + const store = createTestStore() + seedStale(store) + + // env-a removed; env-b remains. + store.getState().setRuntimeEnvironments([env('env-b')]) + + const s = store.getState() + expect(s.repos.map((r) => r.id)).toEqual(['repo1']) + expect(s.projectHostSetups).toEqual([]) + expect(s.worktreesByRepo.repoA).toEqual([]) + expect(s.tabsByWorktree['repoA::/wt-a']).toBeUndefined() + // The saved list itself was updated. + expect(s.runtimeEnvironments.map((e) => e.id)).toEqual(['env-b']) + }) + + it('is a no-op for repos/rows when no environment was removed', () => { + const store = createTestStore() + seedStale(store) + const reposRef = store.getState().repos + const worktreesRef = store.getState().worktreesByRepo + + // Same set (order irrelevant) => empty removal diff => purge never runs. + store.getState().setRuntimeEnvironments([env('env-a'), env('env-b')]) + + const s = store.getState() + expect(s.repos).toBe(reposRef) + expect(s.worktreesByRepo).toBe(worktreesRef) + expect(s.repos.map((r) => r.id)).toEqual(['repo1', 'repoA']) + }) +}) diff --git a/src/renderer/src/store/slices/stale-runtime-host-rows.test.ts b/src/renderer/src/store/slices/stale-runtime-host-rows.test.ts new file mode 100644 index 00000000000..6d0e2a5dfff --- /dev/null +++ b/src/renderer/src/store/slices/stale-runtime-host-rows.test.ts @@ -0,0 +1,100 @@ +/** + * Unit coverage for the pure predicates/reducer behind the #8881 store purge. + * + * The removal-diff scoping is the load-bearing safety property: `isRemovedRuntimeHostId` + * may only match runtime hosts whose env id is in the *removed* (tombstoned) set — + * never local/ssh/unhosted, and never a runtime host merely absent from a + * not-yet-hydrated saved list. The reducer must preserve reference identity when + * nothing changed so it never churns the render pipeline. + */ +import { describe, it, expect } from 'vitest' +import { toRuntimeExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host' +import { + isRemovedRuntimeHostId, + dropWorktreeRowsForRemovedRuntimeEnvironments +} from './stale-runtime-host-rows' + +const runtimeA = toRuntimeExecutionHostId('env-a') +const runtimeB = toRuntimeExecutionHostId('env-b') + +type Row = { id: string; hostId?: ExecutionHostId } + +function row(id: string, hostId?: ExecutionHostId): Row { + return { id, hostId } +} + +describe('isRemovedRuntimeHostId', () => { + it('is true for a runtime host whose env id is in the removed set', () => { + expect(isRemovedRuntimeHostId(runtimeA, new Set(['env-a']))).toBe(true) + }) + + it('is false for a runtime host NOT in the removed set', () => { + expect(isRemovedRuntimeHostId(runtimeB, new Set(['env-a']))).toBe(false) + }) + + it('is false for local, ssh, and undefined host ids', () => { + const removed = new Set(['env-a']) + expect(isRemovedRuntimeHostId('local', removed)).toBe(false) + expect(isRemovedRuntimeHostId('ssh:my-server', removed)).toBe(false) + expect(isRemovedRuntimeHostId(undefined, removed)).toBe(false) + expect(isRemovedRuntimeHostId(null, removed)).toBe(false) + }) + + it('is false when the removed set is empty', () => { + expect(isRemovedRuntimeHostId(runtimeA, new Set())).toBe(false) + }) + + it("is false for a 'local' catalog host even when a serving instance's repos carry runtime stamps", () => { + // The catalog-merge guard keys on catalog.hostId (the fetch *target*). A serving + // instance's own catalog is fetched as hostId 'local' regardless of the runtime: + // stamps its repos carry, so it is exempt by construction. + expect(isRemovedRuntimeHostId('local', new Set(['env-a']))).toBe(false) + }) +}) + +describe('dropWorktreeRowsForRemovedRuntimeEnvironments', () => { + it('drops matching runtime rows and reports their ids', () => { + const rowsByRepo = { + repo1: [row('w-local', 'local'), row('w-a', runtimeA)] + } + const result = dropWorktreeRowsForRemovedRuntimeEnvironments(rowsByRepo, new Set(['env-a'])) + expect(result.rowsByRepo.repo1).toEqual([row('w-local', 'local')]) + expect(result.removedWorktreeIds).toEqual(['w-a']) + }) + + it('returns the SAME reference with empty ids when nothing matches', () => { + const rowsByRepo = { repo1: [row('w-local', 'local'), row('w-b', runtimeB)] } + const result = dropWorktreeRowsForRemovedRuntimeEnvironments(rowsByRepo, new Set(['env-a'])) + expect(result.rowsByRepo).toBe(rowsByRepo) + expect(result.removedWorktreeIds).toEqual([]) + }) + + it('returns the SAME reference with empty ids when the removed set is empty', () => { + const rowsByRepo = { repo1: [row('w-a', runtimeA)] } + const result = dropWorktreeRowsForRemovedRuntimeEnvironments(rowsByRepo, new Set()) + expect(result.rowsByRepo).toBe(rowsByRepo) + expect(result.removedWorktreeIds).toEqual([]) + }) + + it('keeps the repo key even when all of its rows drop', () => { + const rowsByRepo = { repoA: [row('w-a1', runtimeA), row('w-a2', runtimeA)] } + const result = dropWorktreeRowsForRemovedRuntimeEnvironments(rowsByRepo, new Set(['env-a'])) + expect(Object.keys(result.rowsByRepo)).toEqual(['repoA']) + expect(result.rowsByRepo.repoA).toEqual([]) + expect(result.removedWorktreeIds).toEqual(['w-a1', 'w-a2']) + }) + + it('gives only the changed repo a new array; unchanged repos keep their reference', () => { + const unchanged = [row('w-local', 'local')] + const changing = [row('w-keep', 'local'), row('w-a', runtimeA)] + const rowsByRepo = { repoUnchanged: unchanged, repoChanging: changing } + const result = dropWorktreeRowsForRemovedRuntimeEnvironments(rowsByRepo, new Set(['env-a'])) + // A change anywhere returns a new top-level map... + expect(result.rowsByRepo).not.toBe(rowsByRepo) + // ...the untouched repo keeps its exact original array reference... + expect(result.rowsByRepo.repoUnchanged).toBe(unchanged) + // ...and only the changed repo gets a fresh filtered array. + expect(result.rowsByRepo.repoChanging).not.toBe(changing) + expect(result.rowsByRepo.repoChanging).toEqual([row('w-keep', 'local')]) + }) +}) diff --git a/src/renderer/src/store/slices/stale-runtime-host-rows.ts b/src/renderer/src/store/slices/stale-runtime-host-rows.ts new file mode 100644 index 00000000000..896873be8ed --- /dev/null +++ b/src/renderer/src/store/slices/stale-runtime-host-rows.ts @@ -0,0 +1,58 @@ +import { parseExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host' + +/** + * True iff `hostId` names a `runtime:` host whose environment id is in the + * removed set. Local, SSH, and unhosted (`hostId` undefined) rows never match. + * Removal-diff scoping (not "absent from the saved list") is load-bearing: a + * serving instance's locally-persisted runtime-stamped repos carry an env id that + * was never in this instance's saved list, so it can never enter the removed diff. + */ +export function isRemovedRuntimeHostId( + hostId: string | null | undefined, + removedEnvironmentIds: ReadonlySet +): boolean { + const parsed = parseExecutionHostId(hostId) + return parsed?.kind === 'runtime' && removedEnvironmentIds.has(parsed.environmentId) +} + +export type DropRuntimeRowsResult = { + rowsByRepo: Record + removedWorktreeIds: string[] +} + +/** + * Drops rows whose `hostId` is a removed-env runtime host from a + * `worktreesByRepo`-shaped map. Returns the SAME reference when nothing changed + * (render-churn guard). Repo keys are kept even when their row list empties — the + * repo itself is purged from `repos`, so grouping never references an empty entry, + * and keeping keys avoids threading a cross-slice "does this repo survive" callback + * into a pure helper. Generic so it serves both `Worktree[]` and the detected + * worktrees' `.worktrees` arrays. + */ +export function dropWorktreeRowsForRemovedRuntimeEnvironments< + T extends { id: string; hostId?: ExecutionHostId } +>( + rowsByRepo: Record, + removedEnvironmentIds: ReadonlySet +): DropRuntimeRowsResult { + if (removedEnvironmentIds.size === 0) { + return { rowsByRepo, removedWorktreeIds: [] } + } + let changed = false + const removedWorktreeIds: string[] = [] + const next: Record = {} + for (const [repoId, rows] of Object.entries(rowsByRepo)) { + const survivors = rows.filter((row) => { + if (isRemovedRuntimeHostId(row.hostId, removedEnvironmentIds)) { + removedWorktreeIds.push(row.id) + return false + } + return true + }) + next[repoId] = survivors.length === rows.length ? rows : survivors + if (survivors.length !== rows.length) { + changed = true + } + } + return changed ? { rowsByRepo: next, removedWorktreeIds } : { rowsByRepo, removedWorktreeIds: [] } +} diff --git a/src/renderer/src/store/slices/worktree-helpers.ts b/src/renderer/src/store/slices/worktree-helpers.ts index 3b4e3ac974b..55c763740e2 100644 --- a/src/renderer/src/store/slices/worktree-helpers.ts +++ b/src/renderer/src/store/slices/worktree-helpers.ts @@ -263,6 +263,15 @@ export type WorktreeSlice = { * one-shot at hydration time. See design §4.4. */ purgeWorktreeTerminalState: (worktreeIds: string[]) => void + /** + * Retires every client-store row (repos, project host setups, worktree + + * detected-worktree rows, and their tab/PTY/browser/editor cascade) owned by a + * runtime host whose environment id was just removed from the saved list. + * Scoped to the removal diff so a serving instance's locally-persisted + * runtime-stamped repos — whose env id was never saved here — are never torn + * down. No-op when the removed set is empty or nothing matched (#8881). + */ + purgeStaleRuntimeHostState: (removedEnvironmentIds: Iterable) => void /** * Re-key every worktree-scoped map + pointer from `oldWorktreeId` to * `newWorktreeId` after a folder rename changed the worktree's path-derived id. diff --git a/src/renderer/src/store/slices/worktrees.ts b/src/renderer/src/store/slices/worktrees.ts index 5541ad0ef92..1845663c7d7 100644 --- a/src/renderer/src/store/slices/worktrees.ts +++ b/src/renderer/src/store/slices/worktrees.ts @@ -31,6 +31,10 @@ import { type ClosedTerminalTabSnapshot } from './recently-closed-tabs' import { findRepoForHost } from './repo-host-identity' +import { + dropWorktreeRowsForRemovedRuntimeEnvironments, + isRemovedRuntimeHostId +} from './stale-runtime-host-rows' import { ensureHooksConfirmed } from '@/lib/ensure-hooks-confirmed' import { cleanupEphemeralVmRuntimesForDeleted } from '@/lib/ephemeral-vm-runtime-cleanup' import { tabHasLivePty } from '@/lib/tab-has-live-pty' @@ -4973,6 +4977,79 @@ export const createWorktreeSlice: StateCreator set((s) => buildWorktreePurgeState(s, purgeableWorktreeIds)) }, + purgeStaleRuntimeHostState: (removedEnvironmentIds) => { + const removed = new Set(removedEnvironmentIds) + if (removed.size === 0) { + return + } + set((s) => { + const survivingRepos = s.repos.filter( + (repo) => !isRemovedRuntimeHostId(getRepoExecutionHostId(repo), removed) + ) + const reposChanged = survivingRepos.length !== s.repos.length + + // Why: broader than filterSetupsForPrunedRepoRows — a repoId-less setup on + // the removed host can still flip projectIdsRequiringSetupGroups and split a + // surviving project group, so drop every setup owned by the removed host. + const survivingSetups = s.projectHostSetups.filter( + (setup) => !isRemovedRuntimeHostId(setup.hostId, removed) + ) + const setupsChanged = survivingSetups.length !== s.projectHostSetups.length + + const worktreeDrop = dropWorktreeRowsForRemovedRuntimeEnvironments(s.worktreesByRepo, removed) + const detectedRows: Record = + Object.fromEntries( + Object.entries(s.detectedWorktreesByRepo).map(([repoId, result]) => [ + repoId, + result.worktrees + ]) + ) + const detectedDrop = dropWorktreeRowsForRemovedRuntimeEnvironments(detectedRows, removed) + + const worktreesChanged = worktreeDrop.rowsByRepo !== s.worktreesByRepo + const detectedChanged = detectedDrop.rowsByRepo !== detectedRows + if (!reposChanged && !setupsChanged && !worktreesChanged && !detectedChanged) { + return s + } + + const removedWorktreeIds = new Set([ + ...worktreeDrop.removedWorktreeIds, + ...detectedDrop.removedWorktreeIds + ]) + const purgeState = + removedWorktreeIds.size > 0 ? buildWorktreePurgeState(s, [...removedWorktreeIds]) : {} + + const detectedWorktreesByRepo = detectedChanged + ? Object.fromEntries( + Object.entries(s.detectedWorktreesByRepo).map(([repoId, result]) => [ + repoId, + { ...result, worktrees: detectedDrop.rowsByRepo[repoId] } + ]) + ) + : s.detectedWorktreesByRepo + + const rowsChanged = worktreesChanged || detectedChanged + const survivingRepoIds = new Set(survivingRepos.map((repo) => repo.id)) + return { + ...purgeState, + ...(reposChanged ? { repos: survivingRepos } : {}), + ...(setupsChanged ? { projectHostSetups: survivingSetups } : {}), + ...(worktreesChanged ? { worktreesByRepo: worktreeDrop.rowsByRepo } : {}), + ...(detectedChanged ? { detectedWorktreesByRepo } : {}), + ...(rowsChanged ? { sortEpoch: s.sortEpoch + 1 } : {}), + // Why: mirror validateRepoScopedUi's repo-scoped UI subset so a filtered or + // active sidebar can't be left referencing a purged repo id. + ...(reposChanged + ? { + activeRepoId: + s.activeRepoId && survivingRepoIds.has(s.activeRepoId) ? s.activeRepoId : null, + filterRepoIds: s.filterRepoIds.filter((repoId) => survivingRepoIds.has(repoId)) + } + : {}) + } + }) + }, + migrateWorktreeIdentity: (oldWorktreeId: string, newWorktreeId: string) => { if (oldWorktreeId === newWorktreeId) { return From 37039cd09571b434773aa551618c11c5405a2aed Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Sun, 19 Jul 2026 13:18:34 -0700 Subject: [PATCH 074/137] fix(source-control): bound Create PR eligibility probe so it can't hang (#9292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(source-control): bound Create PR eligibility probe so it can't hang The local Electron eligibility path had no renderer-side timeout, so a hung main-process git/gh subprocess (plausible on Windows) left the Create PR header stuck in its "Checking whether this branch can create a pull request..." loading state with the button permanently disabled. The runtime-hosted path was already bounded by callRuntimeRpc's 30s timeout; the local path was not. - Wrap the local window.api.hostedReview.getCreationEligibility call in a 30s timeout so a never-settling probe rejects instead of hanging. - On probe failure/timeout, synthesize a local-status blocker snapshot (dirty -> no_upstream -> needs_sync -> needs_push, mirroring the main-process ordering) so a dirty branch still offers the commit/preparation intent and a clean unpublished branch the publish intent, instead of an inert disabled button. Fall back to the retryable failed state only when local status can't determine a blocker. Extracts the eligibility snapshot builders into their own module to stay under the max-lines cap. Provider-aware copy and host-scoped (local/SSH/runtime) behavior preserved; no git commands changed. * fix(source-control): guard synthesized eligibility fallback (default branch, provider parity) Mirror the main-process canReturnLocalBlocker guard in the renderer's probe-failure fallback so a timed-out/failed probe can't synthesize a 'dirty'/commit blocker on the default branch or a detached HEAD — which would have surfaced an enabled Create PR that commits onto the base branch. The ahead-only needs_push case is likewise dropped to match main (it won't offer a push it can't auth-check first). Infer the provider from the repo remote host so a GitLab (etc.) repo with no linked review shows its own review copy during loading and after a failure instead of the GitHub default. Also: assert the timeout error by type, and rename the snapshot test to match its module. * fix(source-control): keep eligibility timeout stable * fix(source-control): keep eligibility timeout bounded * test(source-control): assert eligibility effect dependencies --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> --- ...ourceControl.host-context-boundary.test.ts | 35 ++++ .../right-sidebar/SourceControl.tsx | 139 ++++++++++--- ...view-creation-eligibility-snapshot.test.ts | 184 ++++++++++++++++++ ...ed-review-creation-eligibility-snapshot.ts | 94 +++++++++ ...control-primary-create-pr-intent-action.ts | 19 +- .../src/store/slices/hosted-review.test.ts | 40 ++++ .../src/store/slices/hosted-review.ts | 46 ++++- 7 files changed, 512 insertions(+), 45 deletions(-) create mode 100644 src/renderer/src/components/right-sidebar/source-control-hosted-review-creation-eligibility-snapshot.test.ts create mode 100644 src/renderer/src/components/right-sidebar/source-control-hosted-review-creation-eligibility-snapshot.ts diff --git a/src/renderer/src/components/right-sidebar/SourceControl.host-context-boundary.test.ts b/src/renderer/src/components/right-sidebar/SourceControl.host-context-boundary.test.ts index 2b64ffacc1f..e3e6cb4c90e 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.host-context-boundary.test.ts +++ b/src/renderer/src/components/right-sidebar/SourceControl.host-context-boundary.test.ts @@ -51,4 +51,39 @@ describe('SourceControl host-context boundaries', () => { expect(requestContext).toContain('settings,') expect(requestContext).not.toContain('useAppStore.getState().settings') }) + + it('keeps eligibility base refreshes scoped to repo execution ownership', () => { + const ownerSettingsSection = sourceBetween( + SOURCE_CONTROL_SOURCE, + 'const activeRepoSettings = useMemo(', + 'const updateSettings = useAppStore' + ) + expect(ownerSettingsSection).not.toContain('activeRepo ?? null') + expect(ownerSettingsSection).toContain( + '[activeRepoConnectionId, activeRepoExecutionHostId, activeRepoId, settings]' + ) + + const baseRefSection = sourceBetween( + SOURCE_CONTROL_SOURCE, + '// Why: reset to null so that effectiveBaseRef becomes falsy until the IPC', + 'const normalizedWorktreeBaseRef =' + ) + expect(baseRefSection).toContain( + 'getRuntimeRepoBaseRefDefault(\n { activeRuntimeEnvironmentId: activeRepoRuntimeEnvironmentId },\n activeRepoId' + ) + const dependencyBlock = sourceBetween(baseRefSection, ' }, [', ' ])') + const dependencyEntries = dependencyBlock + .split('\n') + .slice(1) + .map((line) => line.trim().replace(/,$/, '')) + .filter((line) => line.length > 0 && !line.startsWith('//')) + expect(dependencyEntries).toEqual([ + 'activeRepoConnectionId', + 'activeRepoExecutionHostId', + 'activeRepoId', + 'activeRepoRuntimeEnvironmentId', + 'isBranchVisible', + 'isFolder' + ]) + }) }) diff --git a/src/renderer/src/components/right-sidebar/SourceControl.tsx b/src/renderer/src/components/right-sidebar/SourceControl.tsx index c1574bd8b8c..665fd7419c1 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.tsx @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import React, { useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } from 'react' import { ArrowDownUp, AlertTriangle, @@ -267,6 +267,10 @@ import { resolveVisibleCreatePrHeaderAction } from './source-control-create-pr-i import { resolveBlockedCreateReviewNoticeMessage } from './source-control-create-review-blocked-action' import { buildLoadingHostedReviewCreationEligibility, + buildLocalBlockerHostedReviewCreationEligibility, + resolveHostedReviewCreationProviderForTarget +} from './source-control-hosted-review-creation-eligibility-snapshot' +import { resolveCreatePrHeaderAction, resolveProvisionalHostedReviewProvider } from './source-control-primary-create-pr-intent-action' @@ -283,6 +287,7 @@ import { resolveHostedReviewStateForActions } from './source-control-hosted-review-push-target' import { buildSourceControlManualReviewUrlFromContext } from './source-control-manual-review-url' +import { parseRemoteRepo } from './source-control-remote-repo' export { HostedReviewHeaderLink } from './hosted-review-header-chrome' import { createRunningCommitMessageGenerationRecord, @@ -823,6 +828,10 @@ function SourceControlInner(): React.JSX.Element { const worktreeMap = useWorktreeMap() const rightSidebarTab = useAppStore((s) => s.rightSidebarTab) const activeRepo = useRepoById(activeWorktree?.repoId ?? null) + const activeRepoId = activeRepo?.id ?? null + const activeRepoPath = activeRepo?.path ?? null + const activeRepoConnectionId = activeRepo?.connectionId ?? null + const activeRepoExecutionHostId = activeRepo?.executionHostId ?? null const gitIdentityDisplay = activeWorktree ? getWorktreeGitIdentityDisplay(activeWorktree) : null const detachedHeadDisplay = gitIdentityDisplay?.kind === 'detached' ? gitIdentityDisplay : null const branchName = gitIdentityDisplay?.kind === 'branch' ? gitIdentityDisplay.branchName : '' @@ -891,9 +900,20 @@ function SourceControlInner(): React.JSX.Element { // Why: git/file mutations and repo metadata requests belong to the repo // OWNER host, not the currently focused host in the sidebar. const activeRepoSettings = useMemo( - () => getRepoOwnerRoutedSettings(settings, activeRepo ?? null), - [activeRepo, settings] + () => + getRepoOwnerRoutedSettings( + settings, + activeRepoId + ? { + id: activeRepoId, + connectionId: activeRepoConnectionId, + executionHostId: activeRepoExecutionHostId + } + : null + ), + [activeRepoConnectionId, activeRepoExecutionHostId, activeRepoId, settings] ) + const activeRepoRuntimeEnvironmentId = activeRepoSettings?.activeRuntimeEnvironmentId ?? null const updateSettings = useAppStore((s) => s.updateSettings) const openSettingsTarget = useAppStore((s) => s.openSettingsTarget) const openSettingsPage = useAppStore((s) => s.openSettingsPage) @@ -1240,7 +1260,7 @@ function SourceControlInner(): React.JSX.Element { const generateError = activeCommitMessageGenerationRecord?.error ?? generateErrors[activeWorktreeId ?? ''] ?? null const activeConnectionId = activeWorktreeId - ? (getConnectionId(activeWorktreeId) ?? activeRepo?.connectionId ?? null) + ? (getConnectionId(activeWorktreeId) ?? activeRepoConnectionId) : null const activeSourceControlLaunchPlatform = resolveSourceControlLaunchPlatform({ connectionId: activeConnectionId, @@ -1417,7 +1437,7 @@ function SourceControlInner(): React.JSX.Element { ) useEffect(() => { - if (!isBranchVisible || !activeRepo || isFolder) { + if (!isBranchVisible || !activeRepoId || isFolder) { return } @@ -1429,7 +1449,10 @@ function SourceControlInner(): React.JSX.Element { setDefaultBaseRef(null) let stale = false - void getRuntimeRepoBaseRefDefault(activeRepoSettings, activeRepo.id) + void getRuntimeRepoBaseRefDefault( + { activeRuntimeEnvironmentId: activeRepoRuntimeEnvironmentId }, + activeRepoId + ) .then((result) => { if (!stale) { // Why: IPC now returns a `{ defaultBaseRef, remoteCount }` envelope; @@ -1451,7 +1474,16 @@ function SourceControlInner(): React.JSX.Element { return () => { stale = true } - }, [activeRepo, activeRepoSettings, isBranchVisible, isFolder]) + }, [ + // Why: only repo/host ownership changes should reset the base; unrelated + // repo metadata churn must not indirectly restart eligibility's timeout. + activeRepoConnectionId, + activeRepoExecutionHostId, + activeRepoId, + activeRepoRuntimeEnvironmentId, + isBranchVisible, + isFolder + ]) const normalizedWorktreeBaseRef = activeWorktree?.baseRef?.trim() || null const normalizedRepoBaseRef = activeRepo?.worktreeBaseRef?.trim() || null @@ -1566,6 +1598,13 @@ function SourceControlInner(): React.JSX.Element { hostedReviewCreationRequestMatchesCurrent && hostedReviewCreationRequestState.status === 'loading' && hostedReview === null + // Why: without a linked review or probe result the provider is unknown; infer + // it from the remote host so a GitLab (etc.) repo shows its own review copy + // instead of the GitHub default during loading and after a probe failure. + const remoteInferredHostedReviewProvider = useMemo( + () => parseRemoteRepo(activeRepo?.gitRemoteIdentity?.remoteUrl ?? '')?.provider ?? null, + [activeRepo?.gitRemoteIdentity?.remoteUrl] + ) const provisionalHostedReviewProvider = useMemo( () => resolveProvisionalHostedReviewProvider({ @@ -1582,7 +1621,8 @@ function SourceControlInner(): React.JSX.Element { linkedGitLabMR, linkedBitbucketPR, linkedAzureDevOpsPR, - linkedGiteaPR + linkedGiteaPR, + remoteInferredProvider: remoteInferredHostedReviewProvider }), [ activeRepo?.id, @@ -1593,9 +1633,17 @@ function SourceControlInner(): React.JSX.Element { linkedBitbucketPR, linkedGitHubPR, linkedGitLabMR, - linkedGiteaPR + linkedGiteaPR, + remoteInferredHostedReviewProvider ] ) + const resolveCurrentHostedReviewCreationProvider = useEffectEvent(() => + resolveHostedReviewCreationProviderForTarget( + hostedReviewCreationProviderHintRef.current, + { repoId: activeRepoId, worktreeId: activeWorktreeId ?? null, branch: branchName }, + provisionalHostedReviewProvider + ) + ) useEffect(() => { const hasConcreteProviderHint = hostedReview !== null || @@ -1634,18 +1682,16 @@ function SourceControlInner(): React.JSX.Element { // upstream/dirty/base state is reconciling after commit or push, while // preserving provider copy from the previous safe snapshot. if (isHostedReviewCreationLoading) { - const providerHint = hostedReviewCreationProviderHintRef.current - const provider = - providerHint.repoId === (activeRepo?.id ?? null) && - providerHint.worktreeId === (activeWorktreeId ?? null) && - providerHint.branch === branchName - ? providerHint.provider - : provisionalHostedReviewProvider + const provider = resolveHostedReviewCreationProviderForTarget( + hostedReviewCreationProviderHintRef.current, + { repoId: activeRepoId, worktreeId: activeWorktreeId ?? null, branch: branchName }, + provisionalHostedReviewProvider + ) return buildLoadingHostedReviewCreationEligibility(provider) } return hostedReviewCreation }, [ - activeRepo?.id, + activeRepoId, activeWorktreeId, branchName, hostedReviewCreation, @@ -3224,7 +3270,14 @@ function SourceControlInner(): React.JSX.Element { ]) useEffect(() => { - if (!isBranchVisible || !activeRepo || isFolder || !branchName || !activeWorktreeId) { + if ( + !isBranchVisible || + !activeRepoId || + !activeRepoPath || + isFolder || + !branchName || + !activeWorktreeId + ) { setHostedReviewCreationState(null) setHostedReviewCreationRequestState(null) return @@ -3239,7 +3292,7 @@ function SourceControlInner(): React.JSX.Element { } let stale = false setHostedReviewCreationRequestState({ - repoId: activeRepo.id, + repoId: activeRepoId, worktreeId: activeWorktreeId, branch: branchName, status: 'loading' @@ -3248,8 +3301,8 @@ function SourceControlInner(): React.JSX.Element { // to click while the new preflight is still resolving. setHostedReviewCreationState(null) void getHostedReviewCreationEligibility({ - repoPath: activeRepo.path, - repoId: activeRepo.id, + repoPath: activeRepoPath, + repoId: activeRepoId, ...(worktreePath ? { worktreePath } : {}), branch: branchName, base: effectiveBaseRef ?? null, @@ -3267,7 +3320,7 @@ function SourceControlInner(): React.JSX.Element { .then((result) => { if (!stale) { setHostedReviewCreationState({ - repoId: activeRepo.id, + repoId: activeRepoId, worktreeId: activeWorktreeId, branch: branchName, data: result @@ -3277,21 +3330,51 @@ function SourceControlInner(): React.JSX.Element { }) .catch((error) => { console.warn('[SourceControl] hosted review creation eligibility failed', error) - if (!stale) { - setHostedReviewCreationState(null) - setHostedReviewCreationRequestState({ - repoId: activeRepo.id, + if (stale) { + return + } + // Why: a failed/timed-out remote probe must not leave an inert disabled + // button. When the local git status determines a blocker (dirty, no + // upstream, needs push/sync), surface the actionable preparation intent; + // otherwise fall back to the retryable failed state. + const localBlocker = buildLocalBlockerHostedReviewCreationEligibility( + resolveCurrentHostedReviewCreationProvider(), + { + branch: branchName, + baseRef: effectiveBaseRef, + hasUncommittedChanges: hasUncommittedEntries, + hasUpstream: remoteStatus?.hasUpstream, + ahead: remoteStatus?.ahead, + behind: remoteStatus?.behind + } + ) + if (localBlocker) { + setHostedReviewCreationState({ + repoId: activeRepoId, worktreeId: activeWorktreeId, branch: branchName, - status: 'failed' + data: localBlocker }) + setHostedReviewCreationRequestState(null) + return } + setHostedReviewCreationState(null) + setHostedReviewCreationRequestState({ + repoId: activeRepoId, + worktreeId: activeWorktreeId, + branch: branchName, + status: 'failed' + }) }) return () => { stale = true } }, [ - activeRepo, + // Why: unrelated repo metadata replacement must not restart a hung probe's timeout. + activeRepoConnectionId, + activeRepoExecutionHostId, + activeRepoId, + activeRepoPath, branchName, effectiveBaseRef, getHostedReviewCreationEligibility, diff --git a/src/renderer/src/components/right-sidebar/source-control-hosted-review-creation-eligibility-snapshot.test.ts b/src/renderer/src/components/right-sidebar/source-control-hosted-review-creation-eligibility-snapshot.test.ts new file mode 100644 index 00000000000..ae6dceadc7c --- /dev/null +++ b/src/renderer/src/components/right-sidebar/source-control-hosted-review-creation-eligibility-snapshot.test.ts @@ -0,0 +1,184 @@ +import { describe, expect, it } from 'vitest' +import { + buildLocalBlockerHostedReviewCreationEligibility, + resolveHostedReviewCreationProviderForTarget +} from './source-control-hosted-review-creation-eligibility-snapshot' +import { resolveCreatePrIntentEligibility } from './source-control-create-pr-intent-state' + +const featureBranch = { branch: 'feature/create-pr', baseRef: 'main' } + +describe('resolveHostedReviewCreationProviderForTarget', () => { + const target = { repoId: 'repo-1', worktreeId: 'worktree-1', branch: 'feature/create-pr' } + + it('preserves a known self-hosted provider for the same target', () => { + expect( + resolveHostedReviewCreationProviderForTarget( + { ...target, provider: 'gitlab' }, + target, + 'github' + ) + ).toBe('gitlab') + }) + + it('does not leak a provider hint across worktrees', () => { + expect( + resolveHostedReviewCreationProviderForTarget( + { ...target, provider: 'gitlab' }, + { ...target, worktreeId: 'worktree-2' }, + 'github' + ) + ).toBe('github') + }) +}) + +describe('buildLocalBlockerHostedReviewCreationEligibility', () => { + it('reports dirty when there are uncommitted changes', () => { + const eligibility = buildLocalBlockerHostedReviewCreationEligibility('github', { + ...featureBranch, + hasUncommittedChanges: true, + hasUpstream: false, + ahead: 0, + behind: 0 + }) + expect(eligibility).toMatchObject({ blockedReason: 'dirty', nextAction: 'commit' }) + // The synthesized blocker must drive the actionable Create PR intent so a + // failed remote probe still offers commit/publish preparation. + expect( + resolveCreatePrIntentEligibility({ + stagedCount: 1, + hasStageableChanges: true, + hasMessage: true, + hasUnresolvedConflicts: false, + upstreamStatus: { hasUpstream: false, ahead: 0, behind: 0 }, + hostedReviewCreation: eligibility, + branchCommitsAhead: 0 + }) + ).toEqual({ eligible: true, kind: 'dirty' }) + }) + + it('prefers dirty over no_upstream when both apply, matching main-process ordering', () => { + expect( + buildLocalBlockerHostedReviewCreationEligibility('gitlab', { + ...featureBranch, + hasUncommittedChanges: true, + hasUpstream: false, + ahead: 0, + behind: 0 + }) + ).toMatchObject({ provider: 'gitlab', blockedReason: 'dirty' }) + }) + + it('reports no_upstream for a clean unpublished branch', () => { + expect( + buildLocalBlockerHostedReviewCreationEligibility('github', { + ...featureBranch, + hasUncommittedChanges: false, + hasUpstream: false, + ahead: 0, + behind: 0 + }) + ).toMatchObject({ blockedReason: 'no_upstream', nextAction: 'publish' }) + }) + + it('reports needs_sync when the branch is behind its upstream', () => { + expect( + buildLocalBlockerHostedReviewCreationEligibility('github', { + ...featureBranch, + hasUncommittedChanges: false, + hasUpstream: true, + ahead: 1, + behind: 3 + }) + ).toMatchObject({ blockedReason: 'needs_sync', nextAction: 'sync' }) + }) + + it('returns null for an ahead-only branch, matching main which will not offer an un-auth-checked push', () => { + expect( + buildLocalBlockerHostedReviewCreationEligibility('github', { + ...featureBranch, + hasUncommittedChanges: false, + hasUpstream: true, + ahead: 2, + behind: 0 + }) + ).toBeNull() + }) + + it('returns null on the default branch even when dirty, so it cannot enable a commit onto the base branch', () => { + // Guards the regression where a failed probe on the default branch + // synthesized dirty/commit and surfaced an enabled Create PR. + expect( + buildLocalBlockerHostedReviewCreationEligibility('github', { + branch: 'main', + baseRef: 'origin/main', + hasUncommittedChanges: true, + hasUpstream: true, + ahead: 0, + behind: 0 + }) + ).toBeNull() + }) + + it('returns null for a detached HEAD', () => { + expect( + buildLocalBlockerHostedReviewCreationEligibility('github', { + branch: 'HEAD', + baseRef: 'main', + hasUncommittedChanges: true, + hasUpstream: false, + ahead: 0, + behind: 0 + }) + ).toBeNull() + }) + + it('returns null when the local status cannot determine a blocker', () => { + expect( + buildLocalBlockerHostedReviewCreationEligibility('github', { + ...featureBranch, + hasUncommittedChanges: false, + hasUpstream: true, + ahead: 0, + behind: 0 + }) + ).toBeNull() + }) + + it('returns null when upstream status is unknown, matching main which treats it as retryable', () => { + expect( + buildLocalBlockerHostedReviewCreationEligibility('github', { + ...featureBranch, + hasUncommittedChanges: false, + hasUpstream: undefined, + ahead: undefined, + behind: undefined + }) + ).toBeNull() + }) + + it('does not synthesize needs_sync when upstream is unknown but a stale behind count is set', () => { + // The needs_sync branch is gated on hasUpstream === true so an unknown + // upstream stays retryable, matching main's `hasUpstream !== true` → null. + expect( + buildLocalBlockerHostedReviewCreationEligibility('github', { + ...featureBranch, + hasUncommittedChanges: false, + hasUpstream: undefined, + ahead: 0, + behind: 2 + }) + ).toBeNull() + }) + + it('returns null for providers that do not support hosted review creation', () => { + expect( + buildLocalBlockerHostedReviewCreationEligibility('bitbucket', { + ...featureBranch, + hasUncommittedChanges: true, + hasUpstream: false, + ahead: 0, + behind: 0 + }) + ).toBeNull() + }) +}) diff --git a/src/renderer/src/components/right-sidebar/source-control-hosted-review-creation-eligibility-snapshot.ts b/src/renderer/src/components/right-sidebar/source-control-hosted-review-creation-eligibility-snapshot.ts new file mode 100644 index 00000000000..f951af458f0 --- /dev/null +++ b/src/renderer/src/components/right-sidebar/source-control-hosted-review-creation-eligibility-snapshot.ts @@ -0,0 +1,94 @@ +import { supportsHostedReviewCreation } from '../../../../shared/hosted-review-creation-providers' +import { normalizeHostedReviewBaseRef } from '../../../../shared/hosted-review-refs' +import type { + HostedReviewCreationEligibility, + HostedReviewProvider +} from '../../../../shared/hosted-review' + +export function buildLoadingHostedReviewCreationEligibility( + provider: HostedReviewProvider +): HostedReviewCreationEligibility { + return { + provider, + review: null, + canCreate: false, + blockedReason: null, + nextAction: null + } +} + +export function resolveHostedReviewCreationProviderForTarget( + hint: { + repoId: string | null + worktreeId: string | null + branch: string + provider: HostedReviewProvider + }, + target: { repoId: string | null; worktreeId: string | null; branch: string }, + fallback: HostedReviewProvider +): HostedReviewProvider { + return hint.repoId === target.repoId && + hint.worktreeId === target.worktreeId && + hint.branch === target.branch + ? hint.provider + : fallback +} + +/** + * Local-status-only eligibility used when the remote creation probe fails or + * times out, so a failed probe still offers the actionable commit/publish/sync + * preparation action instead of leaving an inert disabled button. + * + * Mirrors the main process's own lookup-failure fallback exactly — both its + * `canReturnLocalBlocker` guard and its blocker ordering + * (`src/main/source-control/hosted-review-creation.ts`). The guard is + * load-bearing: without it a failed probe on the default branch or a detached + * HEAD would synthesize `dirty`/`commit` and surface an *enabled* Create PR + * that commits onto the base branch, where the real probe returns + * `default_branch`/`detached_head` (disabled). Returns null when no local + * blocker is determinable (unknown upstream, ahead-only, fully synced) so the + * caller can surface the retryable state — matching main, which won't offer a + * push it can't first auth-check. + */ +export function buildLocalBlockerHostedReviewCreationEligibility( + provider: HostedReviewProvider, + status: { + branch: string | null | undefined + baseRef: string | null | undefined + hasUncommittedChanges: boolean + hasUpstream: boolean | undefined + ahead: number | undefined + behind: number | undefined + } +): HostedReviewCreationEligibility | null { + const branch = status.branch?.trim() ?? '' + const baseBranch = normalizeHostedReviewBaseRef(status.baseRef ?? '').trim() + const canReturnLocalBlocker = + branch !== '' && + branch !== 'HEAD' && + supportsHostedReviewCreation(provider) && + (baseBranch === '' || branch.toLowerCase() !== baseBranch.toLowerCase()) && + (status.hasUncommittedChanges || status.hasUpstream !== true || (status.behind ?? 0) > 0) + if (!canReturnLocalBlocker) { + return null + } + const base = { + provider, + review: null, + canCreate: false as const, + defaultBaseRef: null, + head: branch + } + if (status.hasUncommittedChanges) { + return { ...base, blockedReason: 'dirty', nextAction: 'commit' } + } + if (status.hasUpstream === false) { + return { ...base, blockedReason: 'no_upstream', nextAction: 'publish' } + } + // Unknown upstream (hasUpstream !== true) is retryable, not an actionable + // blocker — mirror main, which returns a null blocker there. + if (status.hasUpstream === true && (status.behind ?? 0) > 0) { + return { ...base, blockedReason: 'needs_sync', nextAction: 'sync' } + } + return null +} diff --git a/src/renderer/src/components/right-sidebar/source-control-primary-create-pr-intent-action.ts b/src/renderer/src/components/right-sidebar/source-control-primary-create-pr-intent-action.ts index 6ee7bce037f..dcb635ab0e6 100644 --- a/src/renderer/src/components/right-sidebar/source-control-primary-create-pr-intent-action.ts +++ b/src/renderer/src/components/right-sidebar/source-control-primary-create-pr-intent-action.ts @@ -26,6 +26,10 @@ export function resolveProvisionalHostedReviewProvider(input: { linkedBitbucketPR?: number | null linkedAzureDevOpsPR?: number | null linkedGiteaPR?: number | null + // Provider inferred from the repo's remote URL host; used before the GitHub + // default so a GitLab (etc.) repo with no linked review still gets its own + // review copy while a probe is loading or after it fails. + remoteInferredProvider?: HostedReviewProvider | null }): HostedReviewProvider { if (input.hostedReview?.provider && supportsHostedReviewCreation(input.hostedReview.provider)) { return input.hostedReview.provider @@ -49,19 +53,10 @@ export function resolveProvisionalHostedReviewProvider(input: { if (input.linkedGitHubPR != null || input.fallbackGitHubPR != null) { return 'github' } - return 'github' -} - -export function buildLoadingHostedReviewCreationEligibility( - provider: HostedReviewProvider -): HostedReviewCreationEligibility { - return { - provider, - review: null, - canCreate: false, - blockedReason: null, - nextAction: null + if (input.remoteInferredProvider && supportsHostedReviewCreation(input.remoteInferredProvider)) { + return input.remoteInferredProvider } + return 'github' } function shouldOfferCreatePrHeaderChrome( diff --git a/src/renderer/src/store/slices/hosted-review.test.ts b/src/renderer/src/store/slices/hosted-review.test.ts index b5a91fe6a9e..c6234a99504 100644 --- a/src/renderer/src/store/slices/hosted-review.test.ts +++ b/src/renderer/src/store/slices/hosted-review.test.ts @@ -4,6 +4,7 @@ import type { AppState } from '../types' import { createHostedReviewSlice, getHostedReviewCacheKey, + HostedReviewCreationEligibilityTimeoutError, refreshHostedReviewCard } from './hosted-review' import type { HostedReviewInfo } from '../../../../shared/hosted-review' @@ -376,6 +377,45 @@ describe('hosted review slice', () => { }) }) + it('rejects a never-settling local eligibility probe after the timeout', async () => { + vi.useFakeTimers() + // A hung git/gh subprocess never resolves; the store must not wait forever. + mockApi.hostedReview.getCreationEligibility.mockReturnValueOnce(new Promise(() => {})) + const store = makeStore() + + const pending = store.getState().getHostedReviewCreationEligibility({ + repoPath: '/repo', + branch: 'feature/create-pr', + base: 'main' + }) + const assertion = expect(pending).rejects.toBeInstanceOf( + HostedReviewCreationEligibilityTimeoutError + ) + await vi.advanceTimersByTimeAsync(30_000) + await assertion + expect(vi.getTimerCount()).toBe(0) + }) + + it('clears the timeout as soon as a local eligibility probe settles', async () => { + vi.useFakeTimers() + mockApi.hostedReview.getCreationEligibility.mockResolvedValueOnce({ + provider: 'github', + review: null, + canCreate: true, + blockedReason: null, + nextAction: null + }) + + const store = makeStore() + await store.getState().getHostedReviewCreationEligibility({ + repoPath: '/repo', + branch: 'feature/create-pr', + base: 'main' + }) + + expect(vi.getTimerCount()).toBe(0) + }) + it('uses the selected worktree selector for runtime pull request creation', async () => { runtimeRpc.callRuntimeRpc.mockResolvedValueOnce({ ok: true, diff --git a/src/renderer/src/store/slices/hosted-review.ts b/src/renderer/src/store/slices/hosted-review.ts index a638d964c57..c71c3beca0a 100644 --- a/src/renderer/src/store/slices/hosted-review.ts +++ b/src/renderer/src/store/slices/hosted-review.ts @@ -32,6 +32,40 @@ type CreateHostedReviewStoreInput = CreateHostedReviewInput & { repoId?: string const CACHE_TTL_MS = 60_000 const HOSTED_REVIEW_CACHE_MAX = 500 +// Why: the runtime path is bounded by callRuntimeRpc's own timeout; the local +// Electron path had none, so a hung git/gh subprocess (e.g. a stalled Windows +// credential probe) could leave the Create PR header stuck in its "Checking…" +// loading state forever. Mirror the runtime bound so a never-settling probe +// rejects and the UI can fall back to an actionable/retryable state. +const CREATION_ELIGIBILITY_TIMEOUT_MS = 30_000 + +export class HostedReviewCreationEligibilityTimeoutError extends Error { + constructor(timeoutMs: number) { + super(`Timed out checking pull request creation eligibility after ${timeoutMs}ms`) + this.name = 'HostedReviewCreationEligibilityTimeoutError' + } +} + +function withCreationEligibilityTimeout( + promise: Promise, + timeoutMs = CREATION_ELIGIBILITY_TIMEOUT_MS +): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + reject(new HostedReviewCreationEligibilityTimeoutError(timeoutMs)) + }, timeoutMs) + promise.then( + (value) => { + clearTimeout(timer) + resolve(value) + }, + (error) => { + clearTimeout(timer) + reject(error) + } + ) + }) +} const inflightHostedReviewRequests = new Map< string, @@ -259,11 +293,13 @@ export const createHostedReviewSlice: StateCreator { From fbe7780566f453cb047154825aba3a3d6ec84939 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sun, 19 Jul 2026 14:30:59 -0700 Subject: [PATCH 075/137] =?UTF-8?q?Fix=20diff=20editor=20flashing=20"Loadi?= =?UTF-8?q?ng=E2=80=A6"=20on=20every=20save=20(#9424)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix diff editor flashing "Loading…" on every save Saving a file open in a single-file diff tab blanked the editor to a centered "Loading…" for a frame on every content-changing save. The DiffViewer's React key embedded the modified-content signature, so each save changed the key and forced a full unmount/remount of the Monaco diff editor — which shows its built-in loading placeholder while re-initializing, and also discarded scroll position and undo history. The modifiedModelKey → modifiedModelPath rotation already refreshes diff content in place without a remount, so the signature in the outer key was redundant. Drop it; keep the view-state scope and explicit reload nonce for a stable per-tab identity. Adds a regression test asserting a save does not remount the diff editor. * Harden in-place diff model refresh * Preserve diff view state across model swaps * Fix retained diff model disposal race --- ...fCommentDecorator.model-lifecycle.test.tsx | 61 ++++++++ .../diff-comments/useDiffCommentDecorator.tsx | 17 ++- .../src/components/editor/DiffViewer.tsx | 10 +- .../EditorContent.monaco-lifecycle.test.tsx | 59 +++++++ .../src/components/editor/EditorContent.tsx | 4 +- .../editor/diff-model-swap-view-state.test.ts | 90 +++++++++++ .../editor/diff-model-swap-view-state.ts | 52 +++++++ .../useDiffViewerLargeDiffLifecycle.test.tsx | 144 ++++++++++++++++++ .../editor/useDiffViewerLargeDiffLifecycle.ts | 41 +++++ 9 files changed, 471 insertions(+), 7 deletions(-) create mode 100644 src/renderer/src/components/diff-comments/useDiffCommentDecorator.model-lifecycle.test.tsx create mode 100644 src/renderer/src/components/editor/diff-model-swap-view-state.test.ts create mode 100644 src/renderer/src/components/editor/diff-model-swap-view-state.ts create mode 100644 src/renderer/src/components/editor/useDiffViewerLargeDiffLifecycle.test.tsx diff --git a/src/renderer/src/components/diff-comments/useDiffCommentDecorator.model-lifecycle.test.tsx b/src/renderer/src/components/diff-comments/useDiffCommentDecorator.model-lifecycle.test.tsx new file mode 100644 index 00000000000..fb92076c081 --- /dev/null +++ b/src/renderer/src/components/diff-comments/useDiffCommentDecorator.model-lifecycle.test.tsx @@ -0,0 +1,61 @@ +// @vitest-environment happy-dom +import { renderHook } from '@testing-library/react' +import { afterEach, describe, expect, it, vi } from 'vitest' +import type { editor as MonacoEditor } from 'monaco-editor' + +const storeFixture = vi.hoisted(() => ({ + activeGroupIdByWorktree: {}, + clearDeliveredDiffComments: vi.fn() +})) + +vi.mock('@/store', () => ({ + useAppStore: (selector: (state: typeof storeFixture) => unknown) => selector(storeFixture) +})) + +import { useDiffCommentDecorator } from './useDiffCommentDecorator' + +afterEach(() => { + document.body.replaceChildren() + vi.clearAllMocks() +}) + +describe('useDiffCommentDecorator model lifecycle', () => { + it('rebuilds model-scoped resources when a retained editor swaps models', () => { + const editorDomNode = document.createElement('div') + document.body.appendChild(editorDomNode) + const disposeMouseMove = vi.fn() + const disposeMouseLeave = vi.fn() + const disposeScroll = vi.fn() + const editor = { + getDomNode: () => editorDomNode, + getOption: () => 19, + onMouseMove: () => ({ dispose: disposeMouseMove }), + onMouseLeave: () => ({ dispose: disposeMouseLeave }), + onDidScrollChange: () => ({ dispose: disposeScroll }), + changeViewZones: (callback: (accessor: object) => void) => callback({}) + } as unknown as MonacoEditor.ICodeEditor + const hook = renderHook( + ({ monacoModelIdentity }) => + useDiffCommentDecorator({ + editor, + monacoModelIdentity, + filePath: 'notes.ts', + worktreeId: 'worktree-1', + comments: [], + onAddCommentClick: vi.fn(), + onDeleteComment: vi.fn() + }), + { initialProps: { monacoModelIdentity: 'modified-v1' } } + ) + const firstButton = editorDomNode.querySelector('.orca-diff-comment-add-btn') + + hook.rerender({ monacoModelIdentity: 'modified-v2' }) + + const replacementButton = editorDomNode.querySelector('.orca-diff-comment-add-btn') + expect(replacementButton).not.toBeNull() + expect(replacementButton).not.toBe(firstButton) + expect(disposeMouseMove).toHaveBeenCalledOnce() + expect(disposeMouseLeave).toHaveBeenCalledOnce() + expect(disposeScroll).toHaveBeenCalledOnce() + }) +}) diff --git a/src/renderer/src/components/diff-comments/useDiffCommentDecorator.tsx b/src/renderer/src/components/diff-comments/useDiffCommentDecorator.tsx index 3bcb4366e89..50c590ba402 100644 --- a/src/renderer/src/components/diff-comments/useDiffCommentDecorator.tsx +++ b/src/renderer/src/components/diff-comments/useDiffCommentDecorator.tsx @@ -36,6 +36,9 @@ export type DecoratedDiffComment = DiffComment & { type DecoratorArgs = { editor: monacoEditor.ICodeEditor | null + // Why: Monaco destroys model-scoped view zones when a retained editor swaps + // models, so the decorator must rebuild even though the editor object is stable. + monacoModelIdentity?: string filePath: string worktreeId: string comments: readonly DecoratedDiffComment[] @@ -119,6 +122,7 @@ function getSingleCommentSendScopes( export function useDiffCommentDecorator({ editor, + monacoModelIdentity, filePath, worktreeId, comments, @@ -424,7 +428,7 @@ export function useDiffCommentDecorator({ pendingScrollRef.current = null scrollToZoneRef.current = null } - }, [addButtonLabel, cancelScrollToZoneFrame, commentableLineSet, editor]) + }, [addButtonLabel, cancelScrollToZoneFrame, commentableLineSet, editor, monacoModelIdentity]) useEffect(() => { if (!editor) { @@ -686,6 +690,7 @@ export function useDiffCommentDecorator({ editor, filePath, formatCommentPrompt, + monacoModelIdentity, worktreeId, comments ]) @@ -729,5 +734,13 @@ export function useDiffCommentDecorator({ } // If !laidOut we wait — onDomNodeTop on the zone will pick the request // up and call scrollToZone once Monaco's render pass places the zone. - }, [cancelScrollToZoneFrame, editor, comments, pendingScrollCommentId, filePath, worktreeId]) + }, [ + cancelScrollToZoneFrame, + editor, + comments, + pendingScrollCommentId, + filePath, + monacoModelIdentity, + worktreeId + ]) } diff --git a/src/renderer/src/components/editor/DiffViewer.tsx b/src/renderer/src/components/editor/DiffViewer.tsx index dbeb8fca2bf..079f03277a4 100644 --- a/src/renderer/src/components/editor/DiffViewer.tsx +++ b/src/renderer/src/components/editor/DiffViewer.tsx @@ -25,6 +25,7 @@ import { getDiffViewerLargeDiffSaveAction } from './diff-viewer-large-diff-save- import type { DiffViewerProps } from './diff-viewer-props' import { buildDiffEditorWordWrapOptions } from './diff-editor-word-wrap-options' import { useDiffEditorRegistration } from './diff-navigation-context' +import { preserveDiffViewStateAcrossModelSwaps } from './diff-model-swap-view-state' export default function DiffViewer({ modelKey, @@ -64,10 +65,8 @@ export default function DiffViewer({ () => (allDiffComments ?? []).filter((c) => c.filePath === relativePath && isDiffComment(c)), [allDiffComments, relativePath] ) - const diffEditorFontSize = computeDiffEditorFontSize( - settings?.terminalFontSize ?? 13, - editorFontZoomLevel - ) + const terminalFontSize = settings?.terminalFontSize ?? 13 + const diffEditorFontSize = computeDiffEditorFontSize(terminalFontSize, editorFontZoomLevel) const isDark = settings?.theme === 'dark' || (settings?.theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches) @@ -106,6 +105,7 @@ export default function DiffViewer({ // updateDiffComment is only wired for local diffs (worktreeId present). useDiffCommentDecorator({ editor: hasLineCommentAction ? modifiedEditor : null, + monacoModelIdentity: modifiedModelKey ?? modelKey, filePath: relativePath, worktreeId: worktreeId ?? '', comments: worktreeId ? diffComments : [], @@ -309,6 +309,7 @@ export default function DiffViewer({ modelKey, originalModelKey, modifiedModelKey, + diffEditorRef, onEnterFallback: handleEnterLargeDiffFallback }) @@ -321,6 +322,7 @@ export default function DiffViewer({ const originalEditor = diffEditor.getOriginalEditor() const modifiedEditor = diffEditor.getModifiedEditor() + diffEditor.onDidDispose(preserveDiffViewStateAcrossModelSwaps(diffEditor).dispose) setupCopy(originalEditor, monaco, filePath, propsRef) setupCopy(modifiedEditor, monaco, filePath, propsRef) diff --git a/src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx b/src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx index 3251b43ac1a..0e7d9f6262a 100644 --- a/src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx +++ b/src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx @@ -5,6 +5,7 @@ import type { OpenFile } from '@/store/slices/editor' const lifecycle = vi.hoisted(() => ({ events: [] as string[], + diffModelKeys: [] as string[], models: new Map(), mountedProps: [] as { filePath: string; readOnly?: boolean; liveTail?: boolean }[] })) @@ -14,6 +15,20 @@ vi.mock('@/lib/lazy-with-retry', async () => { const { syncContentOnMount } = await import('./monaco-content-sync') return { lazyWithRetry: (factory: () => Promise) => { + if (factory.toString().includes('/DiffViewer.tsx')) { + return function MockDiffViewer(props: { filePath: string; modifiedModelKey?: string }) { + lifecycle.diffModelKeys.push(props.modifiedModelKey ?? '') + /* oxlint-disable react-hooks/exhaustive-deps -- Mount-only by design: a prop-effect would hide a missing outer React remount. */ + React.useEffect(() => { + lifecycle.events.push(`mount-diff:${props.filePath}`) + return () => { + lifecycle.events.push(`unmount-diff:${props.filePath}`) + } + }, []) + /* oxlint-enable react-hooks/exhaustive-deps */ + return null + } + } if (!factory.toString().includes('/MonacoEditor.tsx')) { return () => null } @@ -144,9 +159,25 @@ function props(activeFile: OpenFile, content: string) { } } +function diffProps(activeFile: OpenFile, modifiedContent: string) { + return { + ...props(activeFile, ''), + diffContents: { + [activeFile.id]: { + kind: 'text' as const, + originalContent: '', + modifiedContent, + originalIsBinary: false as const, + modifiedIsBinary: false as const + } + } + } +} + afterEach(() => { cleanup() lifecycle.events.length = 0 + lifecycle.diffModelKeys.length = 0 lifecycle.models.clear() lifecycle.mountedProps.length = 0 }) @@ -176,6 +207,34 @@ describe('EditorContent Monaco lifecycle boundary', () => { }) }) + it('keeps the diff editor mounted when a save updates the modified content', () => { + // Why: saves must rotate the retained model for freshness without remounting + // the editor and flashing Monaco's loading placeholder. + const diff = file('/repo/notes.ts', { mode: 'diff', diffSource: 'unstaged' }) + + const view = render() + view.rerender() + + expect(lifecycle.events).toEqual(['mount-diff:/repo/notes.ts']) + expect(lifecycle.diffModelKeys).toHaveLength(2) + expect(lifecycle.diffModelKeys[1]).not.toBe(lifecycle.diffModelKeys[0]) + }) + + it('still remounts the diff editor for an explicit reload request', () => { + const diff = file('/repo/notes.ts', { mode: 'diff', diffSource: 'unstaged' }) + const view = render() + + view.rerender( + + ) + + expect(lifecycle.events).toEqual([ + 'mount-diff:/repo/notes.ts', + 'unmount-diff:/repo/notes.ts', + 'mount-diff:/repo/notes.ts' + ]) + }) + it('passes live-tail ownership only for a read-only live log', () => { const liveLog = file('/repo/session.jsonl', { readOnly: true, liveTail: true }) diff --git a/src/renderer/src/components/editor/EditorContent.tsx b/src/renderer/src/components/editor/EditorContent.tsx index 023c0b9fcae..2e5dcbea391 100644 --- a/src/renderer/src/components/editor/EditorContent.tsx +++ b/src/renderer/src/components/editor/EditorContent.tsx @@ -963,7 +963,9 @@ export function EditorContent({ const modifiedModelKey = `${diffViewStateKey}:modified:${getDiffContentSignature(dc.modifiedContent)}:${diffReloadNonce}` const diffViewer = ( void + +function modelEditorFixture() { + let willChangeListener: ModelListener = () => {} + let didChangeListener: ModelListener = () => {} + const disposeWillChange = vi.fn() + const disposeDidChange = vi.fn() + return { + editor: { + onWillChangeModel: (listener: ModelListener) => { + willChangeListener = listener + return { dispose: disposeWillChange } + }, + onDidChangeModel: (listener: ModelListener) => { + didChangeListener = listener + return { dispose: disposeDidChange } + } + } as unknown as editor.ICodeEditor, + fireWillChange: () => willChangeListener(), + fireDidChange: () => didChangeListener(), + disposeWillChange, + disposeDidChange + } +} + +describe('diff model swap view state', () => { + beforeEach(() => { + vi.restoreAllMocks() + }) + + it('coalesces a two-sided model rotation into one view-state restore', () => { + const original = modelEditorFixture() + const modified = modelEditorFixture() + const viewState = { original: {}, modified: {} } as editor.IDiffEditorViewState + const diffEditor = { + getOriginalEditor: () => original.editor, + getModifiedEditor: () => modified.editor, + getModel: () => ({ original: {}, modified: {} }), + saveViewState: vi.fn(() => viewState), + restoreViewState: vi.fn() + } as unknown as editor.IStandaloneDiffEditor + const scheduledFrames: FrameRequestCallback[] = [] + vi.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => { + scheduledFrames.push(callback) + return scheduledFrames.length + }) + vi.spyOn(window, 'cancelAnimationFrame').mockImplementation(() => {}) + + preserveDiffViewStateAcrossModelSwaps(diffEditor) + original.fireWillChange() + original.fireDidChange() + modified.fireWillChange() + modified.fireDidChange() + + expect(diffEditor.saveViewState).toHaveBeenCalledOnce() + expect(window.cancelAnimationFrame).toHaveBeenCalledWith(1) + scheduledFrames[1](0) + expect(diffEditor.restoreViewState).toHaveBeenCalledOnce() + expect(diffEditor.restoreViewState).toHaveBeenCalledWith(viewState) + }) + + it('cancels pending work and listeners when the editor is disposed', () => { + const original = modelEditorFixture() + const modified = modelEditorFixture() + const diffEditor = { + getOriginalEditor: () => original.editor, + getModifiedEditor: () => modified.editor, + saveViewState: () => ({ original: {}, modified: {} }), + restoreViewState: vi.fn() + } as unknown as editor.IStandaloneDiffEditor + vi.spyOn(window, 'requestAnimationFrame').mockReturnValue(7) + vi.spyOn(window, 'cancelAnimationFrame').mockImplementation(() => {}) + const subscription = preserveDiffViewStateAcrossModelSwaps(diffEditor) + + modified.fireWillChange() + modified.fireDidChange() + subscription.dispose() + + expect(window.cancelAnimationFrame).toHaveBeenCalledWith(7) + expect(original.disposeWillChange).toHaveBeenCalledOnce() + expect(original.disposeDidChange).toHaveBeenCalledOnce() + expect(modified.disposeWillChange).toHaveBeenCalledOnce() + expect(modified.disposeDidChange).toHaveBeenCalledOnce() + }) +}) diff --git a/src/renderer/src/components/editor/diff-model-swap-view-state.ts b/src/renderer/src/components/editor/diff-model-swap-view-state.ts new file mode 100644 index 00000000000..e46a1863c0d --- /dev/null +++ b/src/renderer/src/components/editor/diff-model-swap-view-state.ts @@ -0,0 +1,52 @@ +import type { IDisposable, editor } from 'monaco-editor' + +export function preserveDiffViewStateAcrossModelSwaps( + diffEditor: editor.IStandaloneDiffEditor +): IDisposable { + let pendingViewState: editor.IDiffEditorViewState | null = null + let restoreFrame: number | null = null + + const captureViewState = (): void => { + // Why: Monaco resets cursor and scroll state when a retained editor swaps + // models, so capture once before either diff side starts rotating. + pendingViewState ??= diffEditor.saveViewState() + } + const scheduleRestore = (): void => { + if (!pendingViewState) { + return + } + if (restoreFrame !== null) { + cancelAnimationFrame(restoreFrame) + } + restoreFrame = requestAnimationFrame(() => { + restoreFrame = null + const viewState = pendingViewState + pendingViewState = null + if (viewState && diffEditor.getModel()) { + diffEditor.restoreViewState(viewState) + } + }) + } + + const originalEditor = diffEditor.getOriginalEditor() + const modifiedEditor = diffEditor.getModifiedEditor() + const subscriptions = [ + originalEditor.onWillChangeModel(captureViewState), + originalEditor.onDidChangeModel(scheduleRestore), + modifiedEditor.onWillChangeModel(captureViewState), + modifiedEditor.onDidChangeModel(scheduleRestore) + ] + + return { + dispose: () => { + for (const subscription of subscriptions) { + subscription.dispose() + } + if (restoreFrame !== null) { + cancelAnimationFrame(restoreFrame) + } + restoreFrame = null + pendingViewState = null + } + } +} diff --git a/src/renderer/src/components/editor/useDiffViewerLargeDiffLifecycle.test.tsx b/src/renderer/src/components/editor/useDiffViewerLargeDiffLifecycle.test.tsx new file mode 100644 index 00000000000..a12ab4b3463 --- /dev/null +++ b/src/renderer/src/components/editor/useDiffViewerLargeDiffLifecycle.test.tsx @@ -0,0 +1,144 @@ +// @vitest-environment happy-dom +import { act, renderHook } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' +import type { editor } from 'monaco-editor' + +const monacoFixture = vi.hoisted(() => { + const models = new Map< + string, + { dispose: ReturnType; isAttachedToEditor: () => boolean } + >() + return { + models, + monaco: { + Uri: { parse: (value: string) => value }, + editor: { + getModel: (path: string) => models.get(path) ?? null + } + } + } +}) + +vi.mock('@/lib/monaco-setup', () => ({ monaco: monacoFixture.monaco })) + +import { getDiffViewerMonacoModelPaths } from './diff-monaco-model-disposal' +import { useDiffViewerLargeDiffLifecycle } from './useDiffViewerLargeDiffLifecycle' + +function detachedModel(): { + dispose: ReturnType + isAttachedToEditor: () => boolean +} { + return { dispose: vi.fn(), isAttachedToEditor: () => false } +} + +function diffEditorFixture( + original: unknown, + modified: unknown +): { + current: editor.IStandaloneDiffEditor + setModel: ReturnType +} { + let models = { original, modified } + const setModel = vi.fn((nextModels: typeof models) => { + models = nextModels + }) + return { + current: { + getModel: () => models, + setModel + } as unknown as editor.IStandaloneDiffEditor, + setModel + } +} + +describe('useDiffViewerLargeDiffLifecycle', () => { + it('keeps repeated content rotations bounded to the current Monaco models', async () => { + const modelKey = 'diff-tab' + const originalModelKey = 'original-v1' + const onEnterFallback = vi.fn() + const paths = ['modified-v1', 'modified-v2', 'modified-v3'].map((modifiedModelKey) => + getDiffViewerMonacoModelPaths({ + modelKey, + originalModelKey, + modifiedModelKey, + generationSuffix: '' + }) + ) + const firstModel = detachedModel() + const secondModel = detachedModel() + const currentModel = detachedModel() + const originalModel = detachedModel() + monacoFixture.models.set(paths[0].originalModelPath, originalModel) + monacoFixture.models.set(paths[0].modifiedModelPath, firstModel) + monacoFixture.models.set(paths[1].modifiedModelPath, secondModel) + monacoFixture.models.set(paths[2].modifiedModelPath, currentModel) + const retainedEditor = diffEditorFixture(originalModel, firstModel) + + const hook = renderHook( + ({ modifiedModelKey }) => + useDiffViewerLargeDiffLifecycle({ + limited: false, + modelKey, + originalModelKey, + modifiedModelKey, + diffEditorRef: retainedEditor, + onEnterFallback + }), + { initialProps: { modifiedModelKey: 'modified-v1' } } + ) + + hook.rerender({ modifiedModelKey: 'modified-v2' }) + await act(() => Promise.resolve()) + hook.rerender({ modifiedModelKey: 'modified-v3' }) + await act(() => Promise.resolve()) + + expect(firstModel.dispose).toHaveBeenCalledOnce() + expect(secondModel.dispose).toHaveBeenCalledOnce() + expect(currentModel.dispose).not.toHaveBeenCalled() + expect(retainedEditor.setModel).toHaveBeenCalledTimes(2) + expect(hook.result.current).toEqual(paths[2]) + expect(onEnterFallback).not.toHaveBeenCalled() + }) + + it('resets the owning diff widget before disposing a superseded model', async () => { + const modelKey = 'diff-tab' + const paths = ['modified-v1', 'modified-v2'].map((modifiedModelKey) => + getDiffViewerMonacoModelPaths({ + modelKey, + modifiedModelKey, + generationSuffix: '' + }) + ) + const supersededModel = detachedModel() + const originalModel = detachedModel() + const currentModel = detachedModel() + monacoFixture.models.set(paths[0].originalModelPath, originalModel) + monacoFixture.models.set(paths[0].modifiedModelPath, supersededModel) + monacoFixture.models.set(paths[1].modifiedModelPath, currentModel) + const retainedEditor = diffEditorFixture(originalModel, supersededModel) + + const hook = renderHook( + ({ modifiedModelKey }) => + useDiffViewerLargeDiffLifecycle({ + limited: false, + modelKey, + modifiedModelKey, + diffEditorRef: retainedEditor, + onEnterFallback: vi.fn() + }), + { initialProps: { modifiedModelKey: 'modified-v1' } } + ) + + hook.rerender({ modifiedModelKey: 'modified-v2' }) + await act(() => Promise.resolve()) + + expect(retainedEditor.setModel).toHaveBeenCalledWith({ + original: originalModel, + modified: currentModel + }) + expect(supersededModel.dispose).toHaveBeenCalledOnce() + expect(retainedEditor.setModel.mock.invocationCallOrder[0]).toBeLessThan( + supersededModel.dispose.mock.invocationCallOrder[0] + ) + }) +}) diff --git a/src/renderer/src/components/editor/useDiffViewerLargeDiffLifecycle.ts b/src/renderer/src/components/editor/useDiffViewerLargeDiffLifecycle.ts index 0224b36576e..3bfb1bf18da 100644 --- a/src/renderer/src/components/editor/useDiffViewerLargeDiffLifecycle.ts +++ b/src/renderer/src/components/editor/useDiffViewerLargeDiffLifecycle.ts @@ -1,7 +1,10 @@ import { useEffect, useMemo, useRef, useState } from 'react' +import type { RefObject } from 'react' +import type { editor } from 'monaco-editor' import { monaco } from '@/lib/monaco-setup' import { disposeUnattachedDiffViewerMonacoModels, + disposeUnattachedMonacoModelPaths, getDiffViewerMonacoModelPaths } from './diff-monaco-model-disposal' @@ -10,6 +13,7 @@ type DiffViewerLargeDiffLifecycleInput = { modelKey: string originalModelKey?: string modifiedModelKey?: string + diffEditorRef: RefObject onEnterFallback: () => void } @@ -18,6 +22,7 @@ export function useDiffViewerLargeDiffLifecycle({ modelKey, originalModelKey, modifiedModelKey, + diffEditorRef, onEnterFallback }: DiffViewerLargeDiffLifecycleInput): { originalModelPath: string @@ -38,6 +43,42 @@ export function useDiffViewerLargeDiffLifecycle({ ) const currentDiffModelPathsRef = useRef(currentDiffModelPaths) currentDiffModelPathsRef.current = currentDiffModelPaths + const previousDiffModelPathsRef = useRef(currentDiffModelPaths) + + useEffect(() => { + const previousModelPaths = previousDiffModelPathsRef.current + previousDiffModelPathsRef.current = currentDiffModelPaths + const supersededModelPaths = [ + previousModelPaths.originalModelPath !== currentDiffModelPaths.originalModelPath + ? previousModelPaths.originalModelPath + : null, + previousModelPaths.modifiedModelPath !== currentDiffModelPaths.modifiedModelPath + ? previousModelPaths.modifiedModelPath + : null + ].filter((modelPath): modelPath is string => modelPath !== null) + if (supersededModelPaths.length === 0) { + return + } + const diffEditor = diffEditorRef.current + if (diffEditor) { + const originalModel = monaco.editor.getModel( + monaco.Uri.parse(currentDiffModelPaths.originalModelPath) + ) + const modifiedModel = monaco.editor.getModel( + monaco.Uri.parse(currentDiffModelPaths.modifiedModelPath) + ) + if (!originalModel || !modifiedModel) { + return + } + const activeModels = diffEditor.getModel() + if (activeModels?.original !== originalModel || activeModels.modified !== modifiedModel) { + // Why: @monaco-editor/react swaps the two child models separately, but + // Monaco's diff widget must release its old pair before either is disposed. + diffEditor.setModel({ original: originalModel, modified: modifiedModel }) + } + } + disposeUnattachedMonacoModelPaths(monaco, supersededModelPaths) + }, [currentDiffModelPaths, diffEditorRef]) useEffect(() => { if (!limited) { From ca4003a9e365cca322c4d86aae08b532c8b34ae9 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sun, 19 Jul 2026 14:40:54 -0700 Subject: [PATCH 076/137] Keep Agent Session History from freezing the app on huge OpenCode databases (#8924) * fix(agent-history): keep the app responsive while scanning huge OpenCode databases (#8864) Opening or refreshing Agent Session History froze the entire app when OpenCode's opencode.db had grown to multiple GB (reporter: 29 GB with 20+ live opencode writers). All OpenCode SQLite reads ran synchronously (node:sqlite DatabaseSync) on the Electron main-process event loop, the discovery query evaluated a COUNT+json_extract subquery for every session before LIMIT, and the preview query JSON-parsed every part blob of each session. Live writers bump session.time_updated continuously, so the mtime parse cache missed every 15s/focus/manual refresh and the multi-tens-of-seconds freeze repeated forever. Fix: - Run OpenCode SQLite discovery and per-session parsing on a persistent worker thread (lazy spawn, unref'd, idle teardown, FIFO dispatch, per-call timeouts, crash-loop cap). Faults surface as per-source scan issues instead of stalls; other providers' results always arrive. - Fall back to the in-process reader when no worker bundle exists, surfacing a degraded-mode scan issue so a persistent fallback cannot silently reintroduce the hang. - Bound the queries: sort+LIMIT sessions before computing message counts, and source previews from the newest 100 messages via the (session_id, time_created, id) index instead of scanning every part. Measured on a 22 GB synthetic DB matching the reporter's shape: main- process IPC RTT during a fully cache-invalidated scan went from a 32.8 s continuous block (UI click timeout) to 1 ms max, with the panel populating normally (previews and message counts intact). * fix(agent-history): drop the cached worker handle after a clean exit and document worker-client exports A worker that exits cleanly on its own left this.worker pointing at the dead thread; the next request would post into it and stall to its timeout instead of respawning. Clean idle exits now drop the handle without counting as a death. Also adds JSDoc to the new exported scan-worker surfaces. * fix(agent-history): keep OpenCode scans off the main thread * fix(agent-history): align OpenCode recency ordering --- electron.vite.config.ts | 3 + .../ai-vault/session-scanner-agent-parser.ts | 4 +- ...ion-scanner-opencode-sqlite-bounds.test.ts | 285 ++++++++++++++ ...canner-opencode-sqlite-coexistence.test.ts | 15 +- ...ssion-scanner-opencode-sqlite-discovery.ts | 173 +-------- .../session-scanner-opencode-sqlite-list.ts | 121 ++++++ ...nner-opencode-sqlite-worker-client.test.ts | 360 ++++++++++++++++++ ...n-scanner-opencode-sqlite-worker-client.ts | 344 +++++++++++++++++ ...on-scanner-opencode-sqlite-worker-entry.ts | 58 +++ ...scanner-opencode-sqlite-worker-protocol.ts | 37 ++ ...on-scanner-opencode-sqlite-worker-spawn.ts | 76 ++++ .../session-scanner-opencode-sqlite.ts | 14 +- 12 files changed, 1323 insertions(+), 167 deletions(-) create mode 100644 src/main/ai-vault/session-scanner-opencode-sqlite-bounds.test.ts create mode 100644 src/main/ai-vault/session-scanner-opencode-sqlite-list.ts create mode 100644 src/main/ai-vault/session-scanner-opencode-sqlite-worker-client.test.ts create mode 100644 src/main/ai-vault/session-scanner-opencode-sqlite-worker-client.ts create mode 100644 src/main/ai-vault/session-scanner-opencode-sqlite-worker-entry.ts create mode 100644 src/main/ai-vault/session-scanner-opencode-sqlite-worker-protocol.ts create mode 100644 src/main/ai-vault/session-scanner-opencode-sqlite-worker-spawn.ts diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 7b8c9cb8318..634e86e50f5 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -182,6 +182,9 @@ export default defineConfig({ 'computer-sidecar': resolve('src/main/computer/sidecar-entry.ts'), 'stt-worker': resolve('src/main/speech/stt-worker.ts'), 'warp-theme-parser-worker': resolve('src/main/warp-themes/warp-theme-parser-worker.ts'), + 'session-scanner-opencode-sqlite-worker-entry': resolve( + 'src/main/ai-vault/session-scanner-opencode-sqlite-worker-entry.ts' + ), // Why: forked with ELECTRON_RUN_AS_NODE so @parcel/watcher faults // can't take down the main process (issue #7547). 'parcel-watcher-process-entry': resolve('src/main/ipc/parcel-watcher-process-entry.ts'), diff --git a/src/main/ai-vault/session-scanner-agent-parser.ts b/src/main/ai-vault/session-scanner-agent-parser.ts index 029227d8745..bcfb8babdfc 100644 --- a/src/main/ai-vault/session-scanner-agent-parser.ts +++ b/src/main/ai-vault/session-scanner-agent-parser.ts @@ -6,7 +6,7 @@ import { parseGrokSessionFile } from './session-scanner-grok-parser' import { parseMessageGraphSessionFile, parseRovoSessionFile } from './session-scanner-graph-parsers' import { parseKimiSessionFile } from './session-scanner-kimi-parser' import { splitOpenCodeSqliteCandidate } from './session-scanner-opencode-sqlite-paths' -import { parseOpenCodeSqliteSession } from './session-scanner-opencode-sqlite' +import { parseOpenCodeSqliteSessionViaWorker } from './session-scanner-opencode-sqlite-worker-spawn' import { parseClaudeSessionFile } from './session-scanner-primary-parsers' import { parseGeminiSessionFile } from './session-scanner-gemini-parsers' import { parseCodexSessionFile } from './session-scanner-codex-parser' @@ -50,7 +50,7 @@ export async function parseAgentSessionFile( // real filesystem paths and fall through to the JSON parser. const sqliteCandidate = splitOpenCodeSqliteCandidate(candidate.file.path) if (sqliteCandidate) { - return parseOpenCodeSqliteSession({ + return parseOpenCodeSqliteSessionViaWorker({ dbPath: sqliteCandidate.dbPath, sessionId: sqliteCandidate.sessionId, platform diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-bounds.test.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-bounds.test.ts new file mode 100644 index 00000000000..7f50a220247 --- /dev/null +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-bounds.test.ts @@ -0,0 +1,285 @@ +import { mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { afterEach, describe, expect, it } from 'vitest' +import type { AiVaultScanIssue } from '../../shared/ai-vault-types' +import Database from '../sqlite/sync-database' +import { buildOpenCodeSqliteCandidatePath } from './session-scanner-opencode-sqlite-paths' +import { listOpenCodeSqliteSessions } from './session-scanner-opencode-sqlite-list' +import { parseOpenCodeSqliteSession } from './session-scanner-opencode-sqlite' + +// Part B (#8864) bounds: discovery reads only the newest session identities and +// recency fields, while the preview join reads parts of only the newest 100 +// messages of a session. These tests pin the observable effects. + +let tempDirs: string[] = [] + +afterEach(() => { + for (const dir of tempDirs) { + rmSync(dir, { recursive: true, force: true }) + } + tempDirs = [] +}) + +function createTempDb(): { db: Database.Database; path: string } { + const dir = mkdtempSync(join(tmpdir(), 'orca-opencode-bounds-')) + tempDirs.push(dir) + const path = join(dir, 'opencode.db') + return { db: new Database(path), path } +} + +function applySchema(db: Database.Database): void { + db.exec(` + CREATE TABLE session ( + id TEXT PRIMARY KEY, + project_id TEXT, + parent_id TEXT, + directory TEXT, + title TEXT, + time_created INTEGER NOT NULL, + time_updated INTEGER NOT NULL, + time_archived INTEGER, + model TEXT, + agent TEXT, + cost REAL DEFAULT 0, + tokens_input INTEGER DEFAULT 0, + tokens_output INTEGER DEFAULT 0, + tokens_reasoning INTEGER DEFAULT 0, + tokens_cache_read INTEGER DEFAULT 0 + ); + CREATE TABLE message ( + id TEXT PRIMARY KEY, + session_id TEXT NOT NULL, + time_created INTEGER NOT NULL, + data TEXT NOT NULL + ); + CREATE TABLE part ( + id TEXT PRIMARY KEY, + message_id TEXT NOT NULL, + session_id TEXT NOT NULL, + time_created INTEGER NOT NULL, + data TEXT NOT NULL + ); + `) +} + +function insertSession(db: Database.Database, id: string, timeUpdated: number): void { + db.prepare( + `INSERT INTO session (id, project_id, directory, title, time_created, time_updated, agent) + VALUES (?, 'proj', '/tmp/w', ?, ?, ?, 'build')` + ).run(id, `Session ${id}`, timeUpdated - 1000, timeUpdated) +} + +function insertUserMessage( + db: Database.Database, + args: { id: string; sessionId: string; timeCreated: number; text: string } +): void { + db.prepare(`INSERT INTO message (id, session_id, time_created, data) VALUES (?, ?, ?, ?)`).run( + args.id, + args.sessionId, + args.timeCreated, + JSON.stringify({ role: 'user' }) + ) + db.prepare( + `INSERT INTO part (id, message_id, session_id, time_created, data) VALUES (?, ?, ?, ?, ?)` + ).run( + `prt_${args.id}`, + args.id, + args.sessionId, + args.timeCreated, + JSON.stringify({ type: 'text', text: args.text }) + ) +} + +function insertMessageWithPart( + db: Database.Database, + args: { + id: string + sessionId: string + timeCreated: number + role: string + partType: string + text: string + } +): void { + db.prepare(`INSERT INTO message (id, session_id, time_created, data) VALUES (?, ?, ?, ?)`).run( + args.id, + args.sessionId, + args.timeCreated, + JSON.stringify({ role: args.role }) + ) + db.prepare( + `INSERT INTO part (id, message_id, session_id, time_created, data) VALUES (?, ?, ?, ?, ?)` + ).run( + `prt_${args.id}`, + args.id, + args.sessionId, + args.timeCreated, + JSON.stringify({ type: args.partType, text: args.text }) + ) +} + +describe('listOpenCodeSqliteSessions — LIMIT-first discovery', () => { + it('returns only the newest `limit` sessions by time_updated', async () => { + const { db, path } = createTempDb() + applySchema(db) + for (let i = 0; i < 5; i++) { + insertSession(db, `ses_${i}`, 1_777_634_000_000 + i * 1000) + } + db.close() + + const candidates = await listOpenCodeSqliteSessions({ dbPaths: [path], limit: 2, issues: [] }) + expect(candidates.map((c) => c.file.path)).toEqual([ + buildOpenCodeSqliteCandidatePath(path, 'ses_4'), + buildOpenCodeSqliteCandidatePath(path, 'ses_3') + ]) + }) + + it('uses time_created recency when time_updated is not positive', async () => { + const { db, path } = createTempDb() + applySchema(db) + insertSession(db, 'ses_updated', 1_777_634_002_000) + db.prepare( + `INSERT INTO session (id, project_id, directory, title, time_created, time_updated, agent) + VALUES ('ses_created', 'proj', '/tmp/w', 'Created fallback', ?, 0, 'build')` + ).run(1_777_634_003_000) + db.close() + + const candidates = await listOpenCodeSqliteSessions({ dbPaths: [path], limit: 1, issues: [] }) + expect(candidates.map((candidate) => candidate.file.path)).toEqual([ + buildOpenCodeSqliteCandidatePath(path, 'ses_created') + ]) + expect(candidates[0].file.mtimeMs).toBe(1_777_634_003_000) + }) + + it('does not read message payloads that discovery does not use', async () => { + const { db, path } = createTempDb() + applySchema(db) + insertSession(db, 'ses_candidate', 1_777_634_001_000) + db.prepare(`INSERT INTO message (id, session_id, time_created, data) VALUES (?, ?, ?, ?)`).run( + 'msg_malformed', + 'ses_candidate', + 1_777_634_000_500, + 'malformed JSON' + ) + db.close() + + const issues: AiVaultScanIssue[] = [] + const candidates = await listOpenCodeSqliteSessions({ dbPaths: [path], limit: 10, issues }) + expect(candidates.map((candidate) => candidate.file.path)).toEqual([ + buildOpenCodeSqliteCandidatePath(path, 'ses_candidate') + ]) + expect(issues).toEqual([]) + }) +}) + +describe('parseOpenCodeSqliteSession — bounded preview window', () => { + it('surfaces the newest text previews and retains the full message count', async () => { + // Positive coverage of preview selection (newest OPENCODE_SQLITE_PREVIEW_LIMIT + // by recency) + full-count retention. The window bound itself is pinned by the + // discriminating test below (this all-text fixture would pass either way). + const { db, path } = createTempDb() + applySchema(db) + const base = 1_777_634_000_000 + insertSession(db, 'ses_big', base + 200_000) + + for (let i = 0; i < 105; i++) { + insertUserMessage(db, { + id: `msg_${String(i).padStart(3, '0')}`, + sessionId: 'ses_big', + timeCreated: base + i * 100, + text: i === 0 ? 'OLDEST_PROMPT' : `recent text ${i}` + }) + } + db.close() + + const session = await parseOpenCodeSqliteSession({ + dbPath: path, + sessionId: 'ses_big', + platform: 'darwin' + }) + expect(session).not.toBeNull() + // Full user/assistant count is retained for display accuracy. + expect(session!.messageCount).toBe(105) + // The preview surfaces the newest text parts only. + expect(session!.previewMessages).toHaveLength(5) + const previewText = session!.previewMessages.map((m) => m.text) + expect(previewText).not.toContain('OLDEST_PROMPT') + expect(previewText).toContain('recent text 104') + }) + + it('excludes user text whose message falls outside the newest-100 window', async () => { + // Discriminating fixture: the newest 100 messages carry only non-text parts, + // so the window yields zero previews; the user text lives in older messages + // beyond the window. The pre-fix whole-session scan would surface that old + // text, so this fails if the 100-message window is reverted. + const { db, path } = createTempDb() + applySchema(db) + const base = 1_777_634_000_000 + insertSession(db, 'ses_window', base + 500_000) + + // Older-than-window user messages with real text parts. + for (let i = 0; i < 3; i++) { + insertMessageWithPart(db, { + id: `old_${i}`, + sessionId: 'ses_window', + timeCreated: base + i * 100, + role: 'user', + partType: 'text', + text: `OLD_USER_TEXT_${i}` + }) + } + // 100 newer assistant messages whose only part is non-text (tool output), so + // they fill the newest-100 window but contribute no preview text. + for (let i = 0; i < 100; i++) { + insertMessageWithPart(db, { + id: `new_${String(i).padStart(3, '0')}`, + sessionId: 'ses_window', + timeCreated: base + 10_000 + i * 100, + role: 'assistant', + partType: 'tool', + text: `tool output ${i}` + }) + } + db.close() + + const session = await parseOpenCodeSqliteSession({ + dbPath: path, + sessionId: 'ses_window', + platform: 'darwin' + }) + expect(session).not.toBeNull() + // Full user/assistant count is retained (3 user + 100 assistant). + expect(session!.messageCount).toBe(103) + // The window excludes the old user text; the newest 100 have no text parts. + expect(session!.previewMessages).toHaveLength(0) + expect(session!.previewMessages.map((m) => m.text)).not.toContain('OLD_USER_TEXT_0') + }) + + it('counts only user/assistant messages, ignoring tool/system rows', async () => { + const { db, path } = createTempDb() + applySchema(db) + insertSession(db, 'ses_roles', 1_777_634_100_000) + insertUserMessage(db, { + id: 'msg_user', + sessionId: 'ses_roles', + timeCreated: 1_777_634_000_100, + text: 'hello' + }) + // A tool-role message must not inflate the count. + db.prepare(`INSERT INTO message (id, session_id, time_created, data) VALUES (?, ?, ?, ?)`).run( + 'msg_tool', + 'ses_roles', + 1_777_634_000_200, + JSON.stringify({ role: 'tool' }) + ) + db.close() + + const session = await parseOpenCodeSqliteSession({ + dbPath: path, + sessionId: 'ses_roles', + platform: 'darwin' + }) + expect(session!.messageCount).toBe(1) + }) +}) diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-coexistence.test.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-coexistence.test.ts index 1819afe81f2..84c9c7d7c11 100644 --- a/src/main/ai-vault/session-scanner-opencode-sqlite-coexistence.test.ts +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-coexistence.test.ts @@ -2,10 +2,23 @@ import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises' import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { afterEach, describe, expect, it } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { scanAiVaultSessions } from './session-scanner' import Database from '../sqlite/sync-database' +// Why: this source-level integration suite has no built worker entry. Keep its +// SQLite fixtures inline explicitly; production fails closed if the bundle is absent. +vi.mock('./session-scanner-opencode-sqlite-worker-spawn', async () => { + const [{ listOpenCodeSqliteSessions }, { parseOpenCodeSqliteSession }] = await Promise.all([ + import('./session-scanner-opencode-sqlite-list'), + import('./session-scanner-opencode-sqlite') + ]) + return { + listOpenCodeSqliteSessionsViaWorker: listOpenCodeSqliteSessions, + parseOpenCodeSqliteSessionViaWorker: parseOpenCodeSqliteSession + } +}) + let tempRoots: string[] = [] let tempDbDirs: string[] = [] diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-discovery.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-discovery.ts index 25456159038..a32ca1b2447 100644 --- a/src/main/ai-vault/session-scanner-opencode-sqlite-discovery.ts +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-discovery.ts @@ -1,170 +1,19 @@ import { basename, extname, join } from 'node:path' -import type { AiVaultAgent, AiVaultScanIssue } from '../../shared/ai-vault-types' +import type { AiVaultScanIssue } from '../../shared/ai-vault-types' import { discoverFiles } from './session-scanner-discovery' -import { buildOpenCodeSqliteCandidatePath } from './session-scanner-opencode-sqlite-paths' import { splitOpenCodeSqliteCandidate } from './session-scanner-opencode-sqlite-paths' -import type { - FileWithMtime, - SessionFileCandidate, - SessionFileDiscovery -} from './session-scanner-types' -import { errorMessage } from './session-scanner-values' -import SyncDatabase from '../sqlite/sync-database' -import { columnExists, tableExists } from '../opencode-usage/schema-helpers' +import { listOpenCodeSqliteSessionsViaWorker } from './session-scanner-opencode-sqlite-worker-spawn' +import type { FileWithMtime, SessionFileDiscovery } from './session-scanner-types' // Why: keep the SQLite discovery + dedup layer separate from the parser so // each file stays under the max-lines lint rule and the discovery layer can -// be tested in isolation. +// be tested in isolation. The SQLite list query + reader now lives in +// session-scanner-opencode-sqlite-list.ts (re-exported below so existing +// callers/tests keep importing it from here) and runs on a worker thread. -type SessionRow = { - id: string - title: string | null - directory: string | null - time_created: number - time_updated: number - model_json: string | null - agent: string | null - tokens_input: number - tokens_output: number - tokens_reasoning: number - tokens_cache_read: number - cost: number - message_count: number -} - -function openReadonlyDatabase(dbPath: string): SyncDatabase { - const db = new SyncDatabase(dbPath, { readonly: true, fileMustExist: true }) - db.pragma('query_only = ON') - return db -} - -function canReadOpenCodeSessions(db: SyncDatabase): boolean { - return ( - tableExists(db, 'session') && - columnExists(db, 'session', 'time_created') && - columnExists(db, 'session', 'time_updated') - ) -} - -function sessionColumnSelect(db: SyncDatabase, columnName: string): string { - return columnExists(db, 'session', columnName) ? `s.${columnName}` : 'NULL' -} - -function canCountOpenCodeMessages(db: SyncDatabase): boolean { - return ( - tableExists(db, 'message') && - columnExists(db, 'message', 'session_id') && - columnExists(db, 'message', 'data') - ) -} - -function buildSessionListQuery(db: SyncDatabase): string { - const modelSelect = sessionColumnSelect(db, 'model') - const agentSelect = sessionColumnSelect(db, 'agent') - const tokenColumns = ['tokens_input', 'tokens_output', 'tokens_reasoning', 'tokens_cache_read'] - const tokenSelects = tokenColumns - .map((col) => `${columnExists(db, 'session', col) ? `s.${col}` : '0'} AS ${col}`) - .join(', ') - const costSelect = columnExists(db, 'session', 'cost') ? 's.cost' : '0' - const parentIdPredicate = columnExists(db, 'session', 'parent_id') - ? 'AND s.parent_id IS NULL' - : '' - const archivedPredicate = columnExists(db, 'session', 'time_archived') - ? 'AND s.time_archived IS NULL' - : '' - const messageCountSubquery = canCountOpenCodeMessages(db) - ? `(SELECT COUNT(*) FROM message m - WHERE m.session_id = s.id - AND json_extract(m.data, '$.role') IN ('user','assistant'))` - : '0' - - return `SELECT s.id, - ${sessionColumnSelect(db, 'title')} AS title, - ${sessionColumnSelect(db, 'directory')} AS directory, - s.time_created, - s.time_updated, - ${modelSelect} AS model_json, ${agentSelect} AS agent, - ${tokenSelects}, ${costSelect} AS cost, - ${messageCountSubquery} AS message_count - FROM session s - WHERE 1=1 ${parentIdPredicate} ${archivedPredicate} - ORDER BY s.time_updated DESC - LIMIT ?` -} - -function rowToCandidate(row: SessionRow, dbPath: string): SessionFileCandidate { - const mtimeMs = - typeof row.time_updated === 'number' && row.time_updated > 0 - ? row.time_updated - : row.time_created - return { - agent: 'opencode' as AiVaultAgent, - file: { - path: buildOpenCodeSqliteCandidatePath(dbPath, row.id), - mtimeMs, - modifiedAt: new Date(mtimeMs).toISOString() - }, - codexHome: null - } -} - -function dedupeAndSortSqliteCandidates(candidates: SessionFileCandidate[]): SessionFileCandidate[] { - const candidatesBySessionId = new Map() - for (const candidate of candidates) { - const parsed = splitOpenCodeSqliteCandidate(candidate.file.path) - if (!parsed) { - continue - } - const previous = candidatesBySessionId.get(parsed.sessionId) - if (!previous || candidate.file.mtimeMs > previous.file.mtimeMs) { - candidatesBySessionId.set(parsed.sessionId, candidate) - } - } - return [...candidatesBySessionId.values()].sort((left, right) => { - return right.file.mtimeMs - left.file.mtimeMs - }) -} - -/** - * List OpenCode sessions from one or more SQLite databases as synthetic - * `SessionFileCandidate` entries. Each candidate's file path is a synthetic - * `#` string that the parser dispatcher routes to - * `parseOpenCodeSqliteSession`. Databases that lack the `session` table are - * silently skipped; errors are recorded as scan issues. - * @param args.dbPaths - Absolute paths to opencode.db files to scan. - * @param args.limit - Maximum number of sessions to return per database. - * @param args.issues - Collected scan issues to append errors to. - * @returns Array of synthetic candidates sorted by `time_updated` DESC. - */ -export async function listOpenCodeSqliteSessions(args: { - dbPaths: readonly string[] - limit: number - issues: AiVaultScanIssue[] -}): Promise { - const candidates: SessionFileCandidate[] = [] - for (const dbPath of args.dbPaths) { - let db: SyncDatabase | null = null - try { - db = openReadonlyDatabase(dbPath) - if (!canReadOpenCodeSessions(db)) { - continue - } - const rows = db.prepare(buildSessionListQuery(db)).all(args.limit) as SessionRow[] - for (const row of rows) { - candidates.push(rowToCandidate(row, dbPath)) - } - } catch (err) { - args.issues.push({ - agent: 'opencode', - path: dbPath, - message: errorMessage(err) - }) - } finally { - db?.close() - } - } - return dedupeAndSortSqliteCandidates(candidates) -} +// Re-exported: the pure list reader moved to its own electron-free module so the +// worker entry can import it without the worker client's Electron dependency. +export { listOpenCodeSqliteSessions } from './session-scanner-opencode-sqlite-list' // Why: extract the sessionId from a legacy file path like // storage/session//.json. Falls back to the filename @@ -200,7 +49,9 @@ export async function discoverOpenCodeSessions(args: { issues: args.issues, extensions: ['.json'] }), - listOpenCodeSqliteSessions({ + // Why (#8864): the SQLite list leg runs on a worker thread; only this leg + // moves off the main thread, the filesystem scan stays inline. + listOpenCodeSqliteSessionsViaWorker({ dbPaths: args.dbPaths, limit: args.limitPerAgent, issues: args.issues diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-list.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-list.ts new file mode 100644 index 00000000000..b89b97b229e --- /dev/null +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-list.ts @@ -0,0 +1,121 @@ +import type { AiVaultAgent, AiVaultScanIssue } from '../../shared/ai-vault-types' +import { buildOpenCodeSqliteCandidatePath } from './session-scanner-opencode-sqlite-paths' +import { splitOpenCodeSqliteCandidate } from './session-scanner-opencode-sqlite-paths' +import type { SessionFileCandidate } from './session-scanner-types' +import { errorMessage } from './session-scanner-values' +import SyncDatabase from '../sqlite/sync-database' +import { columnExists, tableExists } from '../opencode-usage/schema-helpers' + +// Why: the SQLite session-list query + reader lives in its own electron-free +// module so both the worker entry and the main-thread worker client can import +// it without pulling in the client's Electron dependency. + +type SessionRow = { + id: string + time_created: number + time_updated: number +} + +function openReadonlyDatabase(dbPath: string): SyncDatabase { + const db = new SyncDatabase(dbPath, { readonly: true, fileMustExist: true }) + db.pragma('query_only = ON') + return db +} + +function canReadOpenCodeSessions(db: SyncDatabase): boolean { + return ( + tableExists(db, 'session') && + columnExists(db, 'session', 'time_created') && + columnExists(db, 'session', 'time_updated') + ) +} + +function buildSessionListQuery(db: SyncDatabase): string { + const parentIdPredicate = columnExists(db, 'session', 'parent_id') ? 'AND parent_id IS NULL' : '' + const archivedPredicate = columnExists(db, 'session', 'time_archived') + ? 'AND time_archived IS NULL' + : '' + + // Why (#8864): discovery needs only identity and recency. Reading message + // blobs or unused session metadata here repeats expensive work on every + // refresh; the parse path loads metadata only for candidates it actually uses. + return `SELECT id, time_created, time_updated + FROM session + WHERE 1=1 ${parentIdPredicate} ${archivedPredicate} + ORDER BY CASE WHEN time_updated > 0 THEN time_updated ELSE time_created END DESC + LIMIT ?` +} + +function rowToCandidate(row: SessionRow, dbPath: string): SessionFileCandidate { + const mtimeMs = + typeof row.time_updated === 'number' && row.time_updated > 0 + ? row.time_updated + : row.time_created + return { + agent: 'opencode' as AiVaultAgent, + file: { + path: buildOpenCodeSqliteCandidatePath(dbPath, row.id), + mtimeMs, + modifiedAt: new Date(mtimeMs).toISOString() + }, + codexHome: null + } +} + +function dedupeAndSortSqliteCandidates(candidates: SessionFileCandidate[]): SessionFileCandidate[] { + const candidatesBySessionId = new Map() + for (const candidate of candidates) { + const parsed = splitOpenCodeSqliteCandidate(candidate.file.path) + if (!parsed) { + continue + } + const previous = candidatesBySessionId.get(parsed.sessionId) + if (!previous || candidate.file.mtimeMs > previous.file.mtimeMs) { + candidatesBySessionId.set(parsed.sessionId, candidate) + } + } + return [...candidatesBySessionId.values()].sort((left, right) => { + return right.file.mtimeMs - left.file.mtimeMs + }) +} + +/** + * List OpenCode sessions from one or more SQLite databases as synthetic + * `SessionFileCandidate` entries. Each candidate's file path is a synthetic + * `#` string that the parser dispatcher routes to + * `parseOpenCodeSqliteSession`. Databases that lack the `session` table are + * silently skipped; errors are recorded as scan issues. + * @param args.dbPaths - Absolute paths to opencode.db files to scan. + * @param args.limit - Maximum number of sessions to return per database. + * @param args.issues - Collected scan issues to append errors to. + * @returns Array of synthetic candidates sorted by effective recency. + */ +export async function listOpenCodeSqliteSessions(args: { + dbPaths: readonly string[] + limit: number + issues: AiVaultScanIssue[] +}): Promise { + const candidates: SessionFileCandidate[] = [] + for (const dbPath of args.dbPaths) { + let db: SyncDatabase | null = null + try { + db = openReadonlyDatabase(dbPath) + if (!canReadOpenCodeSessions(db)) { + continue + } + const rows = db.prepare(buildSessionListQuery(db)).all(args.limit) as SessionRow[] + for (const row of rows) { + candidates.push(rowToCandidate(row, dbPath)) + } + } catch (err) { + args.issues.push({ + agent: 'opencode', + path: dbPath, + message: errorMessage(err) + }) + } finally { + db?.close() + } + } + return dedupeAndSortSqliteCandidates(candidates) +} diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-worker-client.test.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-worker-client.test.ts new file mode 100644 index 00000000000..ab04a455cb5 --- /dev/null +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-worker-client.test.ts @@ -0,0 +1,360 @@ +import type { Worker } from 'node:worker_threads' +import { describe, expect, it, vi } from 'vitest' +import { + IDLE_TEARDOWN_MS, + LIST_TIMEOUT_MS, + MAX_CONSECUTIVE_DEATHS, + OpenCodeSqliteWorkerClient, + PARSE_TIMEOUT_MS +} from './session-scanner-opencode-sqlite-worker-client' +import type { + OpenCodeSqliteWorkerRequest, + OpenCodeSqliteWorkerResponse +} from './session-scanner-opencode-sqlite-worker-protocol' +import type { AiVaultScanIssue } from '../../shared/ai-vault-types' + +// A worker_threads stand-in the tests drive directly: it records posted requests +// and lets a test emit message/error/exit without a built worker bundle. +class FakeWorker { + postedRequests: OpenCodeSqliteWorkerRequest[] = [] + terminated = false + unrefed = false + private listeners = new Map void>>() + + on(event: string, listener: (arg?: unknown) => void): this { + const set = this.listeners.get(event) ?? new Set() + set.add(listener) + this.listeners.set(event, set) + return this + } + + off(event: string, listener: (arg?: unknown) => void): this { + this.listeners.get(event)?.delete(listener) + return this + } + + removeAllListeners(): void { + this.listeners.clear() + } + + unref(): void { + this.unrefed = true + } + + async terminate(): Promise { + this.terminated = true + return 1 + } + + postMessage(request: OpenCodeSqliteWorkerRequest): void { + this.postedRequests.push(request) + } + + emit(event: string, arg?: unknown): void { + // Copy first: the client removes its listeners synchronously during a fault. + for (const listener of Array.from(this.listeners.get(event) ?? [])) { + listener(arg) + } + } + + lastId(): number { + const last = this.postedRequests.at(-1) + if (!last) { + throw new Error('no request posted to fake worker') + } + return last.id + } +} + +function makeFactory(workers: FakeWorker[]): () => Worker { + return () => { + const worker = new FakeWorker() + workers.push(worker) + return worker as unknown as Worker + } +} + +describe('OpenCodeSqliteWorkerClient', () => { + it('correlates responses by id and ignores stale ids', async () => { + const workers: FakeWorker[] = [] + const client = new OpenCodeSqliteWorkerClient({ workerFactory: makeFactory(workers), log() {} }) + + const parsePromise = client.parse({ dbPath: '/db#a', sessionId: 'a', platform: 'darwin' }) + const worker = workers[0] + expect(worker).toBeDefined() + expect(worker!.unrefed).toBe(true) + + // A response for a different id must not settle the active call. + worker!.emit('message', { + id: 999, + ok: true, + value: null + } satisfies OpenCodeSqliteWorkerResponse) + worker!.emit('message', { + id: worker!.lastId(), + ok: true, + value: { sessionId: 'a' } + } satisfies OpenCodeSqliteWorkerResponse) + + await expect(parsePromise).resolves.toEqual({ sessionId: 'a' }) + }) + + it('dispatches one request at a time in FIFO order', async () => { + const workers: FakeWorker[] = [] + const client = new OpenCodeSqliteWorkerClient({ workerFactory: makeFactory(workers), log() {} }) + + const first = client.parse({ dbPath: '/db#a', sessionId: 'a', platform: 'darwin' }) + const second = client.parse({ dbPath: '/db#b', sessionId: 'b', platform: 'darwin' }) + const worker = workers[0]! + + // Only the first is dispatched; the second waits for the active slot. + expect(worker.postedRequests).toHaveLength(1) + expect(worker.postedRequests[0]).toMatchObject({ kind: 'parse', sessionId: 'a' }) + + worker.emit('message', { id: worker.postedRequests[0]!.id, ok: true, value: 'A' }) + await first + + expect(worker.postedRequests).toHaveLength(2) + expect(worker.postedRequests[1]).toMatchObject({ kind: 'parse', sessionId: 'b' }) + worker.emit('message', { id: worker.postedRequests[1]!.id, ok: true, value: 'B' }) + await expect(second).resolves.toBe('B') + // The worker is reused across serial calls (one persistent worker). + expect(workers).toHaveLength(1) + }) + + it('times out only the active call, then respawns and drains the queue', async () => { + vi.useFakeTimers() + try { + const workers: FakeWorker[] = [] + const client = new OpenCodeSqliteWorkerClient({ + workerFactory: makeFactory(workers), + log() {} + }) + + const active = client.parse({ dbPath: '/db#a', sessionId: 'a', platform: 'darwin' }) + const queued = client.parse({ dbPath: '/db#b', sessionId: 'b', platform: 'darwin' }) + const activeAssertion = expect(active).rejects.toThrow(/timed out/) + + // The queued call's timer must not have started while it waited, so only + // the active call fires at the parse timeout. + await vi.advanceTimersByTimeAsync(PARSE_TIMEOUT_MS) + await activeAssertion + + // Fault respawns a fresh worker and dispatches the still-queued call. + expect(workers).toHaveLength(2) + const respawned = workers[1]! + expect(respawned.postedRequests).toHaveLength(1) + expect(respawned.postedRequests[0]).toMatchObject({ sessionId: 'b' }) + respawned.emit('message', { id: respawned.lastId(), ok: true, value: 'B' }) + await expect(queued).resolves.toBe('B') + } finally { + vi.useRealTimers() + } + }) + + it('rejects only the active call on a worker crash and respawns for the queue', async () => { + const workers: FakeWorker[] = [] + const client = new OpenCodeSqliteWorkerClient({ workerFactory: makeFactory(workers), log() {} }) + + const active = client.parse({ dbPath: '/db#a', sessionId: 'a', platform: 'darwin' }) + const queued = client.parse({ dbPath: '/db#b', sessionId: 'b', platform: 'darwin' }) + const activeAssertion = expect(active).rejects.toThrow(/exited with code/) + + workers[0]!.emit('exit', 1) + await activeAssertion + expect(workers[0]!.terminated).toBe(true) + + // Exactly one respawn; the queued call drains on the new worker. + expect(workers).toHaveLength(2) + const respawned = workers[1]! + respawned.emit('message', { id: respawned.lastId(), ok: true, value: 'B' }) + await expect(queued).resolves.toBe('B') + }) + + it('fails closed instead of running SQLite on the main thread when spawn fails', async () => { + const client = new OpenCodeSqliteWorkerClient({ + workerFactory() { + throw new Error('no worker bundle') + }, + log() {} + }) + + const listIssues: AiVaultScanIssue[] = [] + await expect( + client.list({ dbPaths: ['/tmp/opencode.db'], limit: 10, issues: listIssues }) + ).resolves.toEqual([]) + expect( + listIssues.some((issue) => /background scanner could not start/.test(issue.message)) + ).toBe(true) + await expect( + client.parse({ dbPath: '/tmp/opencode.db', sessionId: 'ses_skipped', platform: 'darwin' }) + ).rejects.toThrow(/background scanner could not start/) + }) + + it('stops respawning after the consecutive-death cap and fails the rest to issues', async () => { + const workers: FakeWorker[] = [] + const client = new OpenCodeSqliteWorkerClient({ workerFactory: makeFactory(workers), log() {} }) + + const pending = Array.from({ length: MAX_CONSECUTIVE_DEATHS + 2 }, (_, i) => + client.parse({ dbPath: `/db#${i}`, sessionId: `s${i}`, platform: 'darwin' }) + ) + const settled = pending.map((promise) => expect(promise).rejects.toThrow()) + + // Crash every worker as it is spawned; the client respawns up to the cap. + for (let i = 0; i < MAX_CONSECUTIVE_DEATHS; i++) { + expect(workers[i]).toBeDefined() + workers[i]!.emit('error', new Error(`crash ${i}`)) + } + + await Promise.all(settled) + // No respawn past the cap: only MAX_CONSECUTIVE_DEATHS workers were created, + // and the queued remainder failed to scan issues rather than looping. + expect(workers).toHaveLength(MAX_CONSECUTIVE_DEATHS) + }) + + it('surfaces a list-leg timeout as a scan issue and returns no candidates', async () => { + vi.useFakeTimers() + try { + const workers: FakeWorker[] = [] + const client = new OpenCodeSqliteWorkerClient({ + workerFactory: makeFactory(workers), + log() {} + }) + const issues: AiVaultScanIssue[] = [] + const listPromise = client.list({ + dbPaths: ['/tmp/opencode.db'], + limit: 10, + issues + }) + // The list request is dispatched but never answered → it must time out into + // a scan issue (not an unbounded stall) and contribute no sessions. + await vi.advanceTimersByTimeAsync(LIST_TIMEOUT_MS) + await expect(listPromise).resolves.toEqual([]) + expect(issues).toHaveLength(1) + expect(issues[0]!.agent).toBe('opencode') + expect(issues[0]!.message).toMatch(/did not complete/) + } finally { + vi.useRealTimers() + } + }) + + it('self-heals after repeated spawn failures instead of latching unavailable', async () => { + const workers: FakeWorker[] = [] + let failSpawns = true + const client = new OpenCodeSqliteWorkerClient({ + workerFactory() { + if (failSpawns) { + throw new Error('spawn down') + } + const worker = new FakeWorker() + workers.push(worker) + return worker as unknown as Worker + }, + log() {} + }) + // Scan 1: both calls fail closed, keeping synchronous SQLite off the main + // thread. The failure is not latched, so a later scan can still recover. + const firstIssues: AiVaultScanIssue[] = [] + const first = await client.list({ dbPaths: ['/db'], limit: 10, issues: firstIssues }) + expect(first).toEqual([]) + expect( + firstIssues.some((issue) => /background scanner could not start/.test(issue.message)) + ).toBe(true) + await expect( + client.parse({ dbPath: '/db', sessionId: 'ses_heal', platform: 'darwin' }) + ).rejects.toThrow(/background scanner could not start/) + expect(workers).toHaveLength(0) + + // Spawns recover; the next scan must re-probe and use the worker. + failSpawns = false + const secondIssues: AiVaultScanIssue[] = [] + const secondPromise = client.list({ dbPaths: ['/db'], limit: 10, issues: secondIssues }) + const worker = workers[0] + expect(worker).toBeDefined() + worker!.emit('message', { + id: worker!.lastId(), + ok: true, + value: { candidates: [], issues: [] } + } satisfies OpenCodeSqliteWorkerResponse) + await expect(secondPromise).resolves.toEqual([]) + expect(secondIssues).toHaveLength(0) + }) + + it('drops a cleanly exited idle worker and respawns on the next request', async () => { + const workers: FakeWorker[] = [] + const client = new OpenCodeSqliteWorkerClient({ workerFactory: makeFactory(workers), log() {} }) + + const first = client.parse({ dbPath: '/db#a', sessionId: 'a', platform: 'darwin' }) + workers[0]!.emit('message', { id: workers[0]!.lastId(), ok: true, value: 'A' }) + await expect(first).resolves.toBe('A') + workers[0]!.emit('exit', 0) + + const second = client.parse({ dbPath: '/db#b', sessionId: 'b', platform: 'darwin' }) + expect(workers).toHaveLength(2) + workers[1]!.emit('message', { id: workers[1]!.lastId(), ok: true, value: 'B' }) + await expect(second).resolves.toBe('B') + }) + + it('terminates an idle worker and respawns on later work', async () => { + vi.useFakeTimers() + try { + const workers: FakeWorker[] = [] + const client = new OpenCodeSqliteWorkerClient({ + workerFactory: makeFactory(workers), + log() {} + }) + + const first = client.parse({ dbPath: '/db#a', sessionId: 'a', platform: 'darwin' }) + workers[0]!.emit('message', { id: workers[0]!.lastId(), ok: true, value: 'A' }) + await expect(first).resolves.toBe('A') + await vi.advanceTimersByTimeAsync(IDLE_TEARDOWN_MS) + expect(workers[0]!.terminated).toBe(true) + + const second = client.parse({ dbPath: '/db#b', sessionId: 'b', platform: 'darwin' }) + expect(workers).toHaveLength(2) + workers[1]!.emit('message', { id: workers[1]!.lastId(), ok: true, value: 'B' }) + await expect(second).resolves.toBe('B') + } finally { + vi.useRealTimers() + } + }) + + it('does not carry a worker death from a prior burst into the next scan cap', async () => { + const workers: FakeWorker[] = [] + const client = new OpenCodeSqliteWorkerClient({ workerFactory: makeFactory(workers), log() {} }) + + // Burst 1: one parse whose worker dies, then the burst ends (queue empties) + // with no success to reset the death counter. + const first = client.parse({ dbPath: '/db#a', sessionId: 'a', platform: 'darwin' }) + const firstAssertion = expect(first).rejects.toThrow(/exited with code/) + workers[0]!.emit('exit', 1) + await firstAssertion + + // Burst 2 from idle: the fresh scan resets the cap, so MAX_CONSECUTIVE_DEATHS + // brand-new workers must be spawned before the remainder drains — the carried + // death from burst 1 does not count against burst 2. + const pending = Array.from({ length: MAX_CONSECUTIVE_DEATHS }, (_, i) => + client.parse({ dbPath: `/db#b${i}`, sessionId: `b${i}`, platform: 'darwin' }) + ) + const settled = pending.map((promise) => expect(promise).rejects.toThrow()) + for (let i = 0; i < MAX_CONSECUTIVE_DEATHS; i++) { + workers.at(-1)!.emit('error', new Error(`b-crash ${i}`)) + } + await Promise.all(settled) + // One worker from burst 1 plus a fresh worker per allowed death in burst 2. + expect(workers).toHaveLength(1 + MAX_CONSECUTIVE_DEATHS) + }) + + it('reuses the warm worker across a burst without respawning', async () => { + const workers: FakeWorker[] = [] + const client = new OpenCodeSqliteWorkerClient({ workerFactory: makeFactory(workers), log() {} }) + + for (let i = 0; i < 3; i++) { + const promise = client.parse({ dbPath: `/db#${i}`, sessionId: `s${i}`, platform: 'darwin' }) + const worker = workers[0]! + worker.emit('message', { id: worker.lastId(), ok: true, value: `v${i}` }) + await expect(promise).resolves.toBe(`v${i}`) + } + expect(workers).toHaveLength(1) + }) +}) diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-worker-client.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-worker-client.ts new file mode 100644 index 00000000000..ef156b0b3f4 --- /dev/null +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-worker-client.ts @@ -0,0 +1,344 @@ +import type { Worker } from 'node:worker_threads' +import type { AiVaultScanIssue, AiVaultSession } from '../../shared/ai-vault-types' +import type { + OpenCodeSqliteListRequest, + OpenCodeSqliteListValue, + OpenCodeSqliteParseRequest, + OpenCodeSqliteWorkerRequest, + OpenCodeSqliteWorkerResponse +} from './session-scanner-opencode-sqlite-worker-protocol' +import type { SessionFileCandidate } from './session-scanner-types' +import { errorMessage } from './session-scanner-values' + +// Why (#8864): a lazily-spawned, unref'd worker runs OpenCode SQLite reads off +// the main-process event loop. Lifecycle (idle teardown, FIFO one-at-a-time +// dispatch, per-call timeouts, respawn-on-fault) mirrors src/main/speech/ +// stt-service.ts. The default spawn + shared singleton live in +// session-scanner-opencode-sqlite-worker-spawn.ts. + +export const LIST_TIMEOUT_MS = 30_000 +export const PARSE_TIMEOUT_MS = 15_000 +export const IDLE_TEARDOWN_MS = 30_000 +// After this many consecutive worker deaths, fail the remaining queued calls to +// scan issues instead of respawning so a DB that reliably kills the worker can't +// spin a crash loop. Reset on any successful response, after draining, and when a +// fresh scan burst starts from idle (so the cap is per-scan, not process-wide). +export const MAX_CONSECUTIVE_DEATHS = 3 + +export type WorkerFactory = () => Worker + +// Omit collapses to the shared keys, so omit each member and let +// the client stamp the correlation id. +type OpenCodeSqliteRequestBody = + | Omit + | Omit + +type PendingCall = { + request: OpenCodeSqliteWorkerRequest + timeoutMs: number + resolve: (value: unknown) => void + reject: (error: Error) => void + timer: NodeJS.Timeout | null +} + +// Distinguishes "no worker available at all" from a timeout or crash so callers +// can surface a precise issue while keeping synchronous SQLite off the main thread. +class OpenCodeSqliteWorkerUnavailableError extends Error {} + +/** + * Main-thread bridge that runs OpenCode SQLite reads on a persistent worker + * thread. Dispatches one request at a time (FIFO), times each request out from + * dispatch, respawns after faults (capped by `MAX_CONSECUTIVE_DEATHS`), tears + * the worker down after `IDLE_TEARDOWN_MS` of inactivity, and fails closed when + * no worker can be spawned rather than moving SQLite work onto the main thread. + */ +export class OpenCodeSqliteWorkerClient { + private worker: Worker | null = null + private active: PendingCall | null = null + private queue: PendingCall[] = [] + private idleTimer: NodeJS.Timeout | null = null + private consecutiveDeaths = 0 + private nextId = 1 + private loggedWorkerUnavailable = false + private cleanupWorkerListeners: (() => void) | null = null + private readonly workerFactory: WorkerFactory + private readonly log: (message: string) => void + + constructor(options: { workerFactory: WorkerFactory; log?: (message: string) => void }) { + this.workerFactory = options.workerFactory + this.log = options.log ?? ((message) => console.warn(message)) + } + + /** + * List session candidates from the given OpenCode databases on the worker. + * @param args.dbPaths - Absolute paths to opencode.db files to scan. + * @param args.limit - Maximum number of sessions to return per database. + * @param args.issues - Collected scan issues (worker issues are merged in). + * @returns Synthetic candidates sorted by effective recency; empty (with a + * scan issue) when the worker is unavailable, times out, or crashes. + */ + async list(args: { + dbPaths: readonly string[] + limit: number + issues: AiVaultScanIssue[] + }): Promise { + if (args.dbPaths.length === 0) { + return [] + } + try { + const value = (await this.dispatch( + { kind: 'list', dbPaths: args.dbPaths, limit: args.limit }, + LIST_TIMEOUT_MS + )) as OpenCodeSqliteListValue + args.issues.push(...value.issues) + return value.candidates + } catch (err) { + if (err instanceof OpenCodeSqliteWorkerUnavailableError) { + args.issues.push({ + agent: 'opencode', + path: args.dbPaths[0] ?? 'opencode.db', + message: + 'OpenCode history was skipped because its background scanner could not start; the app remains responsive.' + }) + return [] + } + // Timeout/crash: this storage dir's SQLite DBs contribute no sessions this + // scan, surfaced as one scan issue rather than an unbounded stall. + args.issues.push({ + agent: 'opencode', + path: args.dbPaths[0] ?? 'opencode.db', + message: `OpenCode history scan did not complete: ${errorMessage(err)}` + }) + return [] + } + } + + /** + * Parse a single OpenCode session on the worker. + * @param args.dbPath - Absolute path to the opencode.db file. + * @param args.sessionId - Primary key in the `session` table. + * @param args.platform - Platform used for resume-command generation. + * @returns The parsed session, or `null` when it does not exist; rejects on + * worker timeout/crash so the scanner records a per-session scan issue. + */ + async parse(args: { + dbPath: string + sessionId: string + platform: NodeJS.Platform + }): Promise { + try { + const value = await this.dispatch( + { kind: 'parse', dbPath: args.dbPath, sessionId: args.sessionId, platform: args.platform }, + PARSE_TIMEOUT_MS + ) + return value as AiVaultSession | null + } catch (err) { + if (err instanceof OpenCodeSqliteWorkerUnavailableError) { + throw new Error('OpenCode SQLite background scanner could not start.') + } + // Reject only this session; the scanner turns the throw into a scan issue. + throw err instanceof Error ? err : new Error(String(err)) + } + } + + private dispatch(request: OpenCodeSqliteRequestBody, timeoutMs: number): Promise { + return new Promise((resolve, reject) => { + const id = this.nextId++ + // A fresh burst from full idle starts a new scan: clear any death count + // carried from a prior scan so the respawn cap can't drain this scan early. + if (!this.active && this.queue.length === 0) { + this.consecutiveDeaths = 0 + } + this.queue.push({ + request: { ...request, id } as OpenCodeSqliteWorkerRequest, + timeoutMs, + resolve, + reject, + timer: null + }) + this.pump() + }) + } + + private pump(): void { + if (this.active || this.queue.length === 0) { + return + } + const worker = this.ensureWorker() + if (!worker) { + this.failQueuedAsUnavailable() + return + } + const call = this.queue.shift() + if (!call) { + return + } + this.active = call + this.clearIdleTimer() + // Timeout clock starts at dispatch (not enqueue): a batch may enqueue up to + // 8 parses at once, and a queue-inclusive timeout would fire falsely. + call.timer = setTimeout(() => this.onTimeout(call), call.timeoutMs) + call.timer.unref?.() + worker.postMessage(call.request) + } + + private ensureWorker(): Worker | null { + if (this.worker) { + return this.worker + } + try { + const worker = this.workerFactory() + const onMessage = (response: OpenCodeSqliteWorkerResponse): void => this.onMessage(response) + const onError = (error: Error): void => this.onWorkerFault(error) + const onExit = (code: number): void => this.onWorkerExit(code) + worker.on('message', onMessage) + worker.on('error', onError) + worker.on('exit', onExit) + this.cleanupWorkerListeners = () => { + worker.off('message', onMessage) + worker.off('error', onError) + worker.off('exit', onExit) + } + // Never keep the app alive for a scan worker. + worker.unref?.() + this.worker = worker + return worker + } catch (err) { + // Why (#8864): never fall back to synchronous SQLite reads here; a missing + // bundle or resource-exhausted spawn must omit OpenCode history rather than + // reintroduce the main-process hang this worker boundary prevents. + if (!this.loggedWorkerUnavailable) { + this.loggedWorkerUnavailable = true + this.log(`OpenCode SQLite worker unavailable; skipping its history. ${errorMessage(err)}`) + } + return null + } + } + + private onMessage(response: OpenCodeSqliteWorkerResponse): void { + const call = this.active + if (!call || call.request.id !== response.id) { + return + } + this.consecutiveDeaths = 0 + if (response.ok) { + this.settle(call, () => call.resolve(response.value)) + } else { + this.settle(call, () => call.reject(new Error(response.error))) + } + this.afterSettle() + } + + private onTimeout(call: PendingCall): void { + if (this.active !== call) { + return + } + this.onWorkerFault(new Error(`OpenCode SQLite worker timed out after ${call.timeoutMs}ms`)) + } + + private onWorkerExit(code: number): void { + // A clean self-exit is not a death, but the stale handle must be dropped + // or the next dispatch would post into the dead worker and stall to timeout. + if (code === 0 && !this.active && this.queue.length === 0) { + this.destroyWorker() + return + } + this.onWorkerFault(new Error(`OpenCode SQLite worker exited with code ${code}`)) + } + + private onWorkerFault(error: Error): void { + const failed = this.active + this.destroyWorker() + this.consecutiveDeaths++ + if (failed) { + this.settle(failed, () => failed.reject(error)) + } + if (this.consecutiveDeaths >= MAX_CONSECUTIVE_DEATHS) { + this.drainQueueAfterCrashLoop(error) + return + } + if (this.queue.length > 0) { + this.pump() + } + } + + private drainQueueAfterCrashLoop(error: Error): void { + const pending = this.queue + this.queue = [] + this.consecutiveDeaths = 0 + const drainError = new Error( + `OpenCode SQLite worker crashed repeatedly; skipping remaining sessions (${error.message})` + ) + for (const call of pending) { + this.settle(call, () => call.reject(drainError)) + } + } + + private failQueuedAsUnavailable(): void { + const pending = this.queue + this.queue = [] + for (const call of pending) { + this.settle(call, () => + call.reject(new OpenCodeSqliteWorkerUnavailableError('worker spawn failed')) + ) + } + } + + private settle(call: PendingCall, run: () => void): void { + if (call.timer) { + clearTimeout(call.timer) + call.timer = null + } + if (this.active === call) { + this.active = null + } + run() + } + + private afterSettle(): void { + if (this.queue.length > 0) { + this.pump() + } else { + this.scheduleIdleTeardown() + } + } + + private scheduleIdleTeardown(): void { + this.clearIdleTimer() + if (!this.worker) { + return + } + this.idleTimer = setTimeout(() => this.teardownIfIdle(), IDLE_TEARDOWN_MS) + this.idleTimer.unref?.() + } + + private teardownIfIdle(): void { + this.idleTimer = null + // Only tear down with nothing active AND nothing queued: a request arriving + // as the timer fires must never be lost to a self-exiting worker. + if (this.active || this.queue.length > 0) { + return + } + this.destroyWorker() + } + + private clearIdleTimer(): void { + if (this.idleTimer) { + clearTimeout(this.idleTimer) + this.idleTimer = null + } + } + + private destroyWorker(): void { + this.clearIdleTimer() + const worker = this.worker + this.worker = null + if (!worker) { + return + } + this.cleanupWorkerListeners?.() + this.cleanupWorkerListeners = null + worker.removeAllListeners() + void worker.terminate().catch(() => undefined) + } +} diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-worker-entry.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-worker-entry.ts new file mode 100644 index 00000000000..75dbfe7d7da --- /dev/null +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-worker-entry.ts @@ -0,0 +1,58 @@ +import { parentPort } from 'node:worker_threads' +import type { AiVaultScanIssue } from '../../shared/ai-vault-types' +import { listOpenCodeSqliteSessions } from './session-scanner-opencode-sqlite-list' +import { parseOpenCodeSqliteSession } from './session-scanner-opencode-sqlite' +import type { + OpenCodeSqliteWorkerRequest, + OpenCodeSqliteWorkerResponse +} from './session-scanner-opencode-sqlite-worker-protocol' + +// Why (#8864): OpenCode SQLite reads use synchronous node:sqlite. Running them +// on this worker thread keeps the multi-GB-DB scan off the Electron main-process +// event loop. The client dispatches one request at a time, so this loop stays +// serial; imports must remain electron-free (see the worker-protocol note). + +if (!parentPort) { + throw new Error('OpenCode SQLite worker must run with a parent port.') +} +const port = parentPort + +async function handleRequest( + request: OpenCodeSqliteWorkerRequest +): Promise { + try { + if (request.kind === 'list') { + const issues: AiVaultScanIssue[] = [] + const candidates = await listOpenCodeSqliteSessions({ + dbPaths: request.dbPaths, + limit: request.limit, + issues + }) + return { id: request.id, ok: true, value: { candidates, issues } } + } + const session = await parseOpenCodeSqliteSession({ + dbPath: request.dbPath, + sessionId: request.sessionId, + platform: request.platform + }) + return { id: request.id, ok: true, value: session } + } catch (err) { + return { id: request.id, ok: false, error: err instanceof Error ? err.message : String(err) } + } +} + +port.on('message', (request: OpenCodeSqliteWorkerRequest) => { + void handleRequest(request).then((response) => { + try { + port.postMessage(response) + } catch { + // A non-cloneable result would otherwise post nothing and leave the client + // waiting out its timeout; fail that request fast instead. + port.postMessage({ + id: request.id, + ok: false, + error: 'OpenCode SQLite worker result could not be serialized.' + }) + } + }) +}) diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-worker-protocol.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-worker-protocol.ts new file mode 100644 index 00000000000..f0a85edf891 --- /dev/null +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-worker-protocol.ts @@ -0,0 +1,37 @@ +import type { AiVaultScanIssue, AiVaultSession } from '../../shared/ai-vault-types' +import type { SessionFileCandidate } from './session-scanner-types' + +// Why: request/response shapes shared by the worker entry and the main-thread +// client. Kept type-only (and electron-free) so importing it into the worker +// bundle can never pull the client's Electron dependency across the boundary. + +export type OpenCodeSqliteListRequest = { + id: number + kind: 'list' + dbPaths: readonly string[] + limit: number +} + +export type OpenCodeSqliteParseRequest = { + id: number + kind: 'parse' + dbPath: string + sessionId: string + platform: NodeJS.Platform +} + +export type OpenCodeSqliteWorkerRequest = OpenCodeSqliteListRequest | OpenCodeSqliteParseRequest + +// The list leg returns candidates plus the issues it accumulated; the worker +// mutates a local array and hands it back so the caller can merge it into the +// scan's shared issue list. +export type OpenCodeSqliteListValue = { + candidates: SessionFileCandidate[] + issues: AiVaultScanIssue[] +} + +export type OpenCodeSqliteParseValue = AiVaultSession | null + +export type OpenCodeSqliteWorkerResponse = + | { id: number; ok: true; value: unknown } + | { id: number; ok: false; error: string } diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-worker-spawn.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-worker-spawn.ts new file mode 100644 index 00000000000..17d4cee9833 --- /dev/null +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-worker-spawn.ts @@ -0,0 +1,76 @@ +import { existsSync } from 'node:fs' +import { join } from 'node:path' +import { Worker } from 'node:worker_threads' +import type { AiVaultScanIssue, AiVaultSession } from '../../shared/ai-vault-types' +import type { SessionFileCandidate } from './session-scanner-types' +import { OpenCodeSqliteWorkerClient } from './session-scanner-opencode-sqlite-worker-client' + +// Why: resolve the built worker entry + own the process-wide shared client so +// the client class stays free of Electron (require'd lazily here) and the +// scanner call sites depend only on the two routing functions below. + +function resolveWorkerEntryPath(): string { + let app: { isPackaged: boolean } | null = null + try { + app = require('electron').app ?? null + } catch { + app = null + } + if (app?.isPackaged) { + return join( + process.resourcesPath, + 'app.asar', + 'out', + 'main', + 'session-scanner-opencode-sqlite-worker-entry.js' + ) + } + return join(__dirname, 'session-scanner-opencode-sqlite-worker-entry.js') +} + +function defaultWorkerFactory(): Worker { + const workerPath = resolveWorkerEntryPath() + // Why: a missing built entry must throw synchronously so the client can fail + // closed before it waits on a worker that can never post a result. + if (!existsSync(workerPath)) { + throw new Error(`OpenCode SQLite worker entry not found: ${workerPath}`) + } + return new Worker(workerPath) +} + +let sharedClient: OpenCodeSqliteWorkerClient | null = null + +function getSharedClient(): OpenCodeSqliteWorkerClient { + sharedClient ??= new OpenCodeSqliteWorkerClient({ workerFactory: defaultWorkerFactory }) + return sharedClient +} + +/** + * List OpenCode SQLite session candidates through the shared worker client. + * @param args.dbPaths - Absolute paths to opencode.db files to scan. + * @param args.limit - Maximum number of sessions to return per database. + * @param args.issues - Collected scan issues to append errors to. + * @returns Synthetic candidates sorted by effective recency. + */ +export function listOpenCodeSqliteSessionsViaWorker(args: { + dbPaths: readonly string[] + limit: number + issues: AiVaultScanIssue[] +}): Promise { + return getSharedClient().list(args) +} + +/** + * Parse one OpenCode SQLite session through the shared worker client. + * @param args.dbPath - Absolute path to the opencode.db file. + * @param args.sessionId - Primary key in the `session` table. + * @param args.platform - Platform used for resume-command generation. + * @returns The parsed session, or `null` when it does not exist. + */ +export function parseOpenCodeSqliteSessionViaWorker(args: { + dbPath: string + sessionId: string + platform: NodeJS.Platform +}): Promise { + return getSharedClient().parse(args) +} diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite.ts b/src/main/ai-vault/session-scanner-opencode-sqlite.ts index f3a6dbcd055..ad3e9b76f41 100644 --- a/src/main/ai-vault/session-scanner-opencode-sqlite.ts +++ b/src/main/ai-vault/session-scanner-opencode-sqlite.ts @@ -16,6 +16,12 @@ import { columnExists, tableExists } from '../opencode-usage/schema-helpers' // session-scanner-opencode-sqlite-discovery.ts. const OPENCODE_SQLITE_PREVIEW_LIMIT = 5 +// Why (#8864): a heavy session can hold ~10K parts (25-150 KB tool-output +// blobs). Join preview parts against only the newest N messages instead of +// scanning every part of the session, using the real (session_id, time_created, +// id) index. Bounds the read to those messages' parts; the 15 s parse timeout +// caps the residual for a single pathological giant part. +const OPENCODE_SQLITE_PREVIEW_MESSAGE_WINDOW = 100 type SessionRow = { id: string @@ -164,10 +170,12 @@ function buildPreviewQuery(db: SyncDatabase): string | null { p.time_created, json_extract(m.data, '$.summary.title') AS summary_title, json_extract(m.data, '$.summary.body') AS summary_body - FROM message m + FROM (SELECT id, data FROM message + WHERE session_id = ? + ORDER BY time_created DESC, id DESC + LIMIT ${OPENCODE_SQLITE_PREVIEW_MESSAGE_WINDOW}) m JOIN part p ON p.message_id = m.id - WHERE m.session_id = ? - AND json_extract(m.data, '$.role') IN ('user','assistant') + WHERE json_extract(m.data, '$.role') IN ('user','assistant') AND json_extract(p.data, '$.type') = 'text' ORDER BY p.time_created DESC LIMIT ?` From 9d262c98f6ab7efd4d00b9632f4c0b7c2512cd7d Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sun, 19 Jul 2026 14:43:07 -0700 Subject: [PATCH 077/137] Bump mobile app.json to 0.0.32 (#9466) --- mobile/app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/app.json b/mobile/app.json index 0208b82f4a7..bd307ea83c3 100644 --- a/mobile/app.json +++ b/mobile/app.json @@ -2,7 +2,7 @@ "expo": { "name": "Orca", "slug": "orca-mobile", - "version": "0.0.31", + "version": "0.0.32", "orientation": "default", "icon": "./assets/icon.png", "userInterfaceStyle": "automatic", From 764cadafc7daa82f68aca2aac71b70f349be4203 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sun, 19 Jul 2026 15:20:54 -0700 Subject: [PATCH 078/137] fix(terminal): confirm the agent from ConPTY console presence to avoid false exits (#9258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(terminal): confirm the agent from ConPTY console presence to avoid false exits On Windows the foreground scan is a whole-process-table PowerShell fork that, under load, exceeds its timeout or returns an incomplete snapshot — the completion coordinator then reads the shell as the foreground and fires a false "agent done" while the agent is still working. While a recognized agent is still active, confirm it with a cheap ConPTY console-membership read instead: a child process still attached to the console means the agent is working, so keep it without the whole-table scan. Fall through to the authoritative scan only when the console is shell-only (the agent likely exited). No-op off Windows, and never worse than the existing degraded-scan fallback. * fix(terminal): apply the ConPTY console-presence check on the daemon foreground path Windows PTYs are hosted by the terminal daemon, whose foreground-identity refresh (not the local provider) is what runs by default — it retired the cached agent on a timed-out or incomplete CIM scan, so foreground reads fell back to the shell and fired a false "agent done" while the agent was working. Mirror the local-provider fix on the daemon path: a degraded scan (available:false) no longer retires the identity; an authoritative scan that resolves no agent is confirmed against a ConPTY console-membership read before retiring (an incomplete snapshot with a child still attached keeps the agent); and the sync foreground read serves the cached agent across a shell fallback on Windows (an unreliable exit signal under load) until the background refresh authoritatively retires it. The membership read stays off the sync path. * fix(terminal): exclude the console-list helper's own process from ConPTY membership The console-membership helper attaches to the console to read it, so GetConsoleProcessList counts the helper's own forked process. A bare shell therefore read as [helper, shell] and looked like it still had a child, so a genuine shell-only console (an exited agent) was never detected — the foreground refresh held the exited agent's identity indefinitely. Drop the helper's own pid before judging membership; a remaining set of only the shell (or the AttachConsole-failure fallback) is not child proof. Fixes real-exit detection on both the daemon and local foreground paths. * fix(terminal): require conclusive ConPTY exit evidence * fix(terminal): absorb delayed ConPTY helper errors --- ...ubprocess-foreground-degraded-scan.test.ts | 167 ++++++++++++++++++ src/main/daemon/pty-subprocess.test.ts | 8 + src/main/daemon/pty-subprocess.ts | 73 +++++--- src/main/providers/local-pty-provider.test.ts | 70 ++++++++ src/main/providers/local-pty-provider.ts | 40 ++++- .../windows-conpty-process-membership.test.ts | 52 +++++- .../windows-conpty-process-membership.ts | 24 ++- .../windows-console-foreground.test.ts | 26 +++ .../providers/windows-console-foreground.ts | 15 ++ 9 files changed, 433 insertions(+), 42 deletions(-) create mode 100644 src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts create mode 100644 src/main/providers/windows-console-foreground.test.ts create mode 100644 src/main/providers/windows-console-foreground.ts diff --git a/src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts b/src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts new file mode 100644 index 00000000000..31b7f5562ca --- /dev/null +++ b/src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts @@ -0,0 +1,167 @@ +// Guards the daemon foreground identity against Windows scan degradation: a +// timed-out CIM scan (available:false) or an incomplete snapshot (available:true +// with the agent row missing) must not retire a still-working agent and make +// the coordinator read the shell as a false "agent done". Console presence is +// the arbiter of a real exit. +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +const { spawnMock, isPwshAvailableMock, resolveAgentForegroundProcessMock, readConptyMock } = + vi.hoisted(() => ({ + spawnMock: vi.fn(), + isPwshAvailableMock: vi.fn(), + resolveAgentForegroundProcessMock: vi.fn(), + readConptyMock: vi.fn() + })) + +vi.mock('node-pty', () => ({ spawn: spawnMock })) +vi.mock('../pwsh', () => ({ isPwshAvailable: isPwshAvailableMock })) + +const PWSH7_ABS = 'C:\\Program Files\\PowerShell\\7\\pwsh.exe' +const WINDOWS_POWERSHELL_ABS = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' +const CMD_ABS = 'C:\\Windows\\System32\\cmd.exe' +vi.mock('../providers/windows-powershell-executable', () => ({ + resolveWindowsPowerShellExecutablePath: (family: 'pwsh.exe' | 'powershell.exe') => + family === 'pwsh.exe' ? PWSH7_ABS : WINDOWS_POWERSHELL_ABS, + resolveWindowsPowerShellSpawnChain: (family: 'pwsh.exe' | 'powershell.exe') => + family === 'pwsh.exe' + ? [PWSH7_ABS, WINDOWS_POWERSHELL_ABS, CMD_ABS] + : [WINDOWS_POWERSHELL_ABS, CMD_ABS], + getWindowsCmdPath: () => CMD_ABS +})) + +vi.mock('../providers/agent-foreground-process', () => ({ + resolveAgentForegroundProcessWithAvailability: (...args: unknown[]) => + resolveAgentForegroundProcessMock(...args) +})) + +vi.mock('../providers/windows-conpty-process-membership', () => ({ + readWindowsConptyProcessIds: (...args: unknown[]) => readConptyMock(...args) +})) + +import { createPtySubprocess } from './pty-subprocess' + +const BASE_TIME_MS = 1_000_000 + +function mockPtyProcess(processName: string, pid = 12345) { + return { + pid, + write: vi.fn(), + resize: vi.fn(), + kill: vi.fn(), + process: processName, + onData: vi.fn(() => ({ dispose: vi.fn() })), + onExit: vi.fn(() => ({ dispose: vi.fn() })) + } +} + +async function flushAsyncTicks(count = 12): Promise { + for (let i = 0; i < count; i++) { + await Promise.resolve() + } +} + +async function readForegroundAt( + handle: { getForegroundProcess: () => string | null }, + atMs: number +): Promise { + vi.setSystemTime(BASE_TIME_MS + atMs) + const foreground = handle.getForegroundProcess() + await flushAsyncTicks() + return foreground +} + +describe('daemon pty foreground degraded-scan handling', () => { + let platform: PropertyDescriptor | undefined + let previousUserDataPath: string | undefined + let userDataPath: string + + beforeEach(() => { + spawnMock.mockReset() + isPwshAvailableMock.mockReset() + isPwshAvailableMock.mockReturnValue(false) + resolveAgentForegroundProcessMock.mockReset() + readConptyMock.mockReset() + readConptyMock.mockResolvedValue(null) + previousUserDataPath = process.env.ORCA_USER_DATA_PATH + userDataPath = mkdtempSync(join(tmpdir(), 'daemon-pty-degraded-scan-test-')) + process.env.ORCA_USER_DATA_PATH = userDataPath + platform = Object.getOwnPropertyDescriptor(process, 'platform') + vi.useFakeTimers({ toFake: ['Date'] }) + vi.setSystemTime(BASE_TIME_MS) + }) + + afterEach(() => { + vi.useRealTimers() + if (platform) { + Object.defineProperty(process, 'platform', platform) + } + if (previousUserDataPath === undefined) { + delete process.env.ORCA_USER_DATA_PATH + } else { + process.env.ORCA_USER_DATA_PATH = previousUserDataPath + } + rmSync(userDataPath, { recursive: true, force: true }) + }) + + function spawnWindowsShell() { + Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) + const proc = mockPtyProcess('powershell.exe') + spawnMock.mockReturnValue(proc) + const handle = createPtySubprocess({ sessionId: 'test', cols: 80, rows: 24 }) + return { proc, handle } + } + + it('keeps a cached agent across a degraded (timed-out) scan', async () => { + resolveAgentForegroundProcessMock + .mockResolvedValueOnce({ available: true, processName: 'claude' }) + .mockResolvedValue({ available: false, processName: null }) + const { handle } = spawnWindowsShell() + + await readForegroundAt(handle, 0) // establishes 'claude' + expect(await readForegroundAt(handle, 1_000)).toBe('claude') // refresh returns degraded → keep + // Past the 1s TTL with a shell fallback: pre-fix this returned the shell. + expect(await readForegroundAt(handle, 2_500)).toBe('claude') + expect(readConptyMock).not.toHaveBeenCalled() + }) + + it('keeps a cached agent when a scan finds no agent but the console still has a child', async () => { + resolveAgentForegroundProcessMock + .mockResolvedValueOnce({ available: true, processName: 'claude' }) + .mockResolvedValue({ available: true, processName: null }) + readConptyMock.mockResolvedValue(new Set([12345, 999])) // child still attached + const { handle } = spawnWindowsShell() + + await readForegroundAt(handle, 0) + expect(await readForegroundAt(handle, 1_000)).toBe('claude') + expect(await readForegroundAt(handle, 2_500)).toBe('claude') + }) + + it('retires a cached agent when a scan finds no agent and the console is shell-only', async () => { + resolveAgentForegroundProcessMock + .mockResolvedValueOnce({ available: true, processName: 'claude' }) + .mockResolvedValue({ available: true, processName: null }) + readConptyMock.mockResolvedValue(new Set([12345])) + const { handle } = spawnWindowsShell() + + await readForegroundAt(handle, 0) + await readForegroundAt(handle, 1_000) // refresh clears the cache + expect(await readForegroundAt(handle, 1_100)).toBe('powershell.exe') + expect(readConptyMock).toHaveBeenCalledTimes(1) + }) + + it('keeps a cached agent when the console-membership probe is unavailable', async () => { + resolveAgentForegroundProcessMock + .mockResolvedValueOnce({ available: true, processName: 'claude' }) + .mockResolvedValue({ available: true, processName: null }) + readConptyMock.mockResolvedValue(null) + const { handle } = spawnWindowsShell() + + await readForegroundAt(handle, 0) + expect(await readForegroundAt(handle, 1_000)).toBe('claude') + expect(await readForegroundAt(handle, 2_500)).toBe('claude') + expect(readConptyMock).toHaveBeenCalledTimes(2) + }) +}) diff --git a/src/main/daemon/pty-subprocess.test.ts b/src/main/daemon/pty-subprocess.test.ts index 62011ccd183..30f45915a82 100644 --- a/src/main/daemon/pty-subprocess.test.ts +++ b/src/main/daemon/pty-subprocess.test.ts @@ -67,6 +67,14 @@ vi.mock('../providers/agent-foreground-process', () => ({ } })) +// Console-membership reads run a real node-pty fork that never settles under +// fake timers; default to "shell-only" so the degraded-scan guard falls through +// to its existing retirement logic (the degraded-scan behavior itself is +// covered in pty-subprocess-foreground-degraded-scan.test.ts). +vi.mock('../providers/windows-conpty-process-membership', () => ({ + readWindowsConptyProcessIds: () => Promise.resolve(new Set([12345])) +})) + import { createPtySubprocess, checkPtySpawnHealth } from './pty-subprocess' import { PREVIOUS_DAEMON_PROTOCOL_VERSIONS, PROTOCOL_VERSION } from './types' import { TERMINAL_GIT_CREDENTIAL_GUARD_POLICY_ENV } from '../../shared/terminal-git-credential-guard' diff --git a/src/main/daemon/pty-subprocess.ts b/src/main/daemon/pty-subprocess.ts index 8e1f7024891..2ab88945689 100644 --- a/src/main/daemon/pty-subprocess.ts +++ b/src/main/daemon/pty-subprocess.ts @@ -983,38 +983,59 @@ export function createPtySubprocess(opts: PtySubprocessOptions): SubprocessHandl // Why: daemon foreground reads are sync and run on the IPC hot path. // Refresh derived identities (shell/wrapper/helper -> codex/claude/etc.) // in the background and serve them from a short cache on later reads. + // Why: Windows may need an async membership check before this shared + // shell/wrapper retirement policy is safe to apply. + const retireStaleForegroundIdentity = (): void => { + const currentFallbackProcess = getFallbackForegroundProcess() + if ( + fallbackIsShell && + !getActiveStartupAgentForeground() && + currentFallbackProcess !== null && + isShellProcess(currentFallbackProcess) + ) { + cachedAgentForeground = null + startupAgentForeground = null + } else if ( + cachedAgentForeground !== null && + Date.now() - cachedAgentForeground.refreshedAt > FOREGROUND_AGENT_CACHE_TTL_MS && + currentFallbackProcess !== null && + isAgentForegroundWrapperProcess(currentFallbackProcess) + ) { + // Why: the wrapper's tree no longer resolves to an agent — an expired + // identity must not transfer to an unrelated wrapper (e.g. npm right + // after an agent exit). Fresh identities survive one-off scan hiccups. + cachedAgentForeground = null + } + } void resolveAgentForegroundProcessWithAvailability(proc.pid, fallbackProcess, { contextPaths: agentForegroundContextPaths }) - .then(({ processName }) => { + .then(({ processName, available }) => { if (dead) { return } + // Why: a degraded scan is not exit evidence; retiring here fires false + // completion while an agent is still working under CIM load. + if (!available) { + return + } if (!processName || !recognizeAgentProcess(processName)) { - const currentFallbackProcess = getFallbackForegroundProcess() - if ( - fallbackIsShell && - !getActiveStartupAgentForeground() && - currentFallbackProcess !== null && - isShellProcess(currentFallbackProcess) - ) { - cachedAgentForeground = null - startupAgentForeground = null - } else if ( - cachedAgentForeground !== null && - Date.now() - cachedAgentForeground.refreshedAt > FOREGROUND_AGENT_CACHE_TTL_MS && - currentFallbackProcess !== null && - isAgentForegroundWrapperProcess(currentFallbackProcess) - ) { - // Why: the wrapper's tree no longer resolves to an agent — an expired - // identity must not transfer to an unrelated wrapper (e.g. npm right - // after an agent exit). Fresh identities survive one-off scan hiccups. - cachedAgentForeground = null + // Why: a Windows snapshot can omit a live agent; only verified + // shell-only membership may retire its cached identity. + if (process.platform === 'win32' && fallbackIsShell && cachedAgentForeground !== null) { + return readWindowsConptyProcessIds(proc.pid).then((consoleProcessIds) => { + if (dead || consoleProcessIds === null || consoleProcessIds.size > 1) { + return + } + retireStaleForegroundIdentity() + }) } + retireStaleForegroundIdentity() return } cachedAgentForeground = { processName, refreshedAt: Date.now() } startupAgentForeground = null + return processName }) .catch(() => { // Best-effort only: foreground enrichment must never affect PTY health. @@ -1069,14 +1090,18 @@ export function createPtySubprocess(opts: PtySubprocessOptions): SubprocessHandl return cachedAgentForeground.processName } // Why: a wrapper foreground (node/python) can never identify itself, and - // readers poll slower than the cache TTL — returning the raw wrapper here + // readers poll slower than the cache TTL — returning the raw fallback here // would hide the resolved identity forever. Serve the last resolved agent - // while the scheduled refresh revalidates; exit truth is safe because an - // exited agent's foreground falls back to the shell, not a wrapper. + // while the scheduled refresh revalidates. On Windows a shell fallback is + // also an unreliable exit signal (ConPTY lag under load surfaces the shell + // while the agent is alive), so trust the cache there too; the background + // refresh retires the identity only after a console-presence read confirms + // the agent left the console. if ( cachedAgentForeground && fallbackProcess !== null && - isAgentForegroundWrapperProcess(fallbackProcess) + (isAgentForegroundWrapperProcess(fallbackProcess) || + (process.platform === 'win32' && isShellProcess(fallbackProcess))) ) { return cachedAgentForeground.processName } diff --git a/src/main/providers/local-pty-provider.test.ts b/src/main/providers/local-pty-provider.test.ts index 0e4a828817e..4e2b189deaa 100644 --- a/src/main/providers/local-pty-provider.test.ts +++ b/src/main/providers/local-pty-provider.test.ts @@ -12,6 +12,7 @@ const { spawnMock, prepareMacosTccLoginShellMock, resolveAgentForegroundProcessMock, + readWindowsConptyProcessIdsMock, captureDescendantSnapshotMock, terminateDescendantSnapshotMock } = vi.hoisted(() => ({ @@ -23,6 +24,7 @@ const { spawnMock: vi.fn(), prepareMacosTccLoginShellMock: vi.fn(), resolveAgentForegroundProcessMock: vi.fn(), + readWindowsConptyProcessIdsMock: vi.fn(), captureDescendantSnapshotMock: vi.fn(), terminateDescendantSnapshotMock: vi.fn() })) @@ -79,6 +81,10 @@ vi.mock('./agent-foreground-process', () => ({ resolveAgentForegroundProcessMock(...args) })) +vi.mock('./windows-conpty-process-membership', () => ({ + readWindowsConptyProcessIds: (...args: unknown[]) => readWindowsConptyProcessIdsMock(...args) +})) + vi.mock('../wsl', () => ({ parseWslPath: (path: string) => { const match = path.match(/^\\\\wsl\.localhost\\([^\\]+)(.*)$/) @@ -156,6 +162,8 @@ describe('LocalPtyProvider', () => { processName: fallbackProcess }) ) + readWindowsConptyProcessIdsMock.mockReset() + readWindowsConptyProcessIdsMock.mockResolvedValue(null) exitCb = undefined mockProc = { @@ -1427,6 +1435,68 @@ describe('LocalPtyProvider', () => { await expect(foreground).resolves.toBeNull() }) + + it('confirms a still-active agent from ConPTY console presence without a whole-table scan', async () => { + Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) + mockProc.process = 'powershell.exe' + resolveAgentForegroundProcessMock.mockResolvedValue({ + available: true, + processName: 'claude' + }) + // A child beyond the shell is still attached to this console. + readWindowsConptyProcessIdsMock.mockResolvedValue(new Set([12345, 999])) + const { id } = await provider.spawn({ cols: 80, rows: 24 }) + + // First call establishes the agent identity via the scan. + await expect(provider.getForegroundProcess(id)).resolves.toBe('claude') + // node-pty still only names the shell, but console presence confirms the + // agent — no second whole-table scan. + await expect(provider.getForegroundProcess(id)).resolves.toBe('claude') + expect(resolveAgentForegroundProcessMock).toHaveBeenCalledTimes(1) + }) + + it('falls through to the scan when the ConPTY console shows only the shell', async () => { + Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) + mockProc.process = 'powershell.exe' + resolveAgentForegroundProcessMock.mockResolvedValue({ + available: true, + processName: 'claude' + }) + readWindowsConptyProcessIdsMock.mockResolvedValue(new Set([12345])) + const { id } = await provider.spawn({ cols: 80, rows: 24 }) + + await expect(provider.getForegroundProcess(id)).resolves.toBe('claude') + await expect(provider.getForegroundProcess(id)).resolves.toBe('claude') + expect(resolveAgentForegroundProcessMock).toHaveBeenCalledTimes(2) + }) + + it('keeps the cached agent when both the console probe and process snapshot are inconclusive', async () => { + Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) + mockProc.process = 'powershell.exe' + resolveAgentForegroundProcessMock + .mockResolvedValueOnce({ available: true, processName: 'claude' }) + .mockResolvedValue({ available: true, processName: null }) + readWindowsConptyProcessIdsMock.mockResolvedValue(null) + const { id } = await provider.spawn({ cols: 80, rows: 24 }) + + await expect(provider.getForegroundProcess(id)).resolves.toBe('claude') + await expect(provider.getForegroundProcess(id)).resolves.toBe('claude') + expect(resolveAgentForegroundProcessMock).toHaveBeenCalledTimes(2) + }) + + it('retires the cached agent after verified shell-only membership and a no-agent scan', async () => { + Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) + mockProc.process = 'powershell.exe' + resolveAgentForegroundProcessMock + .mockResolvedValueOnce({ available: true, processName: 'claude' }) + .mockResolvedValue({ available: true, processName: null }) + readWindowsConptyProcessIdsMock.mockResolvedValue(new Set([12345])) + const { id } = await provider.spawn({ cols: 80, rows: 24 }) + + await expect(provider.getForegroundProcess(id)).resolves.toBe('claude') + await expect(provider.getForegroundProcess(id)).resolves.toBeNull() + expect(resolveAgentForegroundProcessMock).toHaveBeenCalledTimes(2) + }) }) describe('confirmForegroundProcess', () => { diff --git a/src/main/providers/local-pty-provider.ts b/src/main/providers/local-pty-provider.ts index 292999c027e..fef99e7d4f8 100644 --- a/src/main/providers/local-pty-provider.ts +++ b/src/main/providers/local-pty-provider.ts @@ -61,6 +61,7 @@ import { terminateDescendantSnapshot } from '../pty-descendant-termination' import { readWindowsConptyProcessIds } from './windows-conpty-process-membership' +import { canConfirmAgentFromConsolePresence } from './windows-console-foreground' import { forceKillPosixPtyProcessGroups } from '../pty/posix-pty-process-groups' import { shouldUseShellReadyStartupDelivery } from '../../shared/codex-startup-delivery' import { assertSafeAgentStartupCwd, resolveSafePtyDefaultCwd } from './pty-default-cwd' @@ -1234,10 +1235,35 @@ export class LocalPtyProvider implements IPtyProvider { ptyLastRecognizedForeground.delete(id) return null } + const fallbackProcess = resolveForegroundFallbackProcess( + proc.process || null, + ptyShellName.get(id) + ) + const cachedAgent = ptyLastRecognizedForeground.get(id) ?? null + let consoleMembershipUnavailable = false + // Why: exact console membership can preserve a live cached agent without + // trusting the whole-table scan that becomes incomplete under Windows load. + if ( + process.platform === 'win32' && + canConfirmAgentFromConsolePresence(cachedAgent, fallbackProcess) + ) { + try { + const consoleProcessIds = await readWindowsConptyProcessIds(proc.pid) + if (ptyProcesses.get(id) !== proc) { + return null + } + if (consoleProcessIds !== null && consoleProcessIds.size > 1 && cachedAgent !== null) { + return cachedAgent + } + consoleMembershipUnavailable = consoleProcessIds === null + } catch { + consoleMembershipUnavailable = true + } + } try { const resolution = await resolveAgentForegroundProcessWithAvailability( proc.pid, - resolveForegroundFallbackProcess(proc.process || null, ptyShellName.get(id)), + fallbackProcess, { contextPaths: ptyAgentForegroundContextPaths.get(id) } @@ -1251,9 +1277,17 @@ export class LocalPtyProvider implements IPtyProvider { // foreground — the completion coordinator reads that as an exit and fires // a false "agent done" while the agent is still working. Prefer the last // recognized agent across a transient failure (e.g. a Windows CIM timeout). + const lastRecognizedAgent = ptyLastRecognizedForeground.get(id) ?? null + const resolvedAgent = resolution.processName + ? recognizeAgentProcessFromCommandLine(resolution.processName) + : null + // Why: an incomplete global snapshot plus an unavailable console probe is + // not exit proof; only verified shell-only membership may clear the cache. const stable = resolveStableForegroundProcess( - resolution, - ptyLastRecognizedForeground.get(id) ?? null + consoleMembershipUnavailable && resolvedAgent === null + ? { ...resolution, available: false } + : resolution, + lastRecognizedAgent ) if (stable.lastRecognizedAgent) { ptyLastRecognizedForeground.set(id, stable.lastRecognizedAgent) diff --git a/src/main/providers/windows-conpty-process-membership.test.ts b/src/main/providers/windows-conpty-process-membership.test.ts index e9e2ef09fdd..9ae23b6f379 100644 --- a/src/main/providers/windows-conpty-process-membership.test.ts +++ b/src/main/providers/windows-conpty-process-membership.test.ts @@ -2,9 +2,15 @@ import { EventEmitter } from 'node:events' import { describe, expect, it, vi } from 'vitest' import { readWindowsConptyProcessIds } from './windows-conpty-process-membership' -function forkWith(event: 'message' | 'error' | 'none', value?: unknown) { - const child = new EventEmitter() as EventEmitter & { kill: ReturnType } +function forkWith(event: 'message' | 'error' | 'none', value?: unknown, pid?: number) { + const child = new EventEmitter() as EventEmitter & { + kill: ReturnType + pid?: number + } child.kill = vi.fn() + if (pid !== undefined) { + child.pid = pid + } const forkProcess = vi.fn(() => { queueMicrotask(() => { if (event === 'message') { @@ -20,7 +26,7 @@ function forkWith(event: 'message' | 'error' | 'none', value?: unknown) { describe('readWindowsConptyProcessIds', () => { it('returns exact console membership from the fixed node-pty helper', async () => { - const { forkProcess } = forkWith('message', [101, 202, 303]) + const { forkProcess } = forkWith('message', [999, 101, 202, 303], 999) await expect( readWindowsConptyProcessIds(101, { @@ -36,14 +42,28 @@ describe('readWindowsConptyProcessIds', () => { }) it.each([ - ['root-only failure fallback', [101]], - ['malformed response', [101, '202']], - ['missing PTY root', [202, 303]] - ])('fails closed for %s', async (_label, processIds) => { - const { forkProcess } = forkWith('message', processIds) + ['root-only failure fallback', [101], 999], + ['malformed response', [999, 101, '202'], 999], + ['missing PTY root', [999, 202, 303], 999], + ['missing helper pid', [101, 202], 999], + ['unavailable helper pid', [101, 202], undefined] + ])('fails closed for %s', async (_label, processIds, helperPid) => { + const { forkProcess } = forkWith('message', processIds, helperPid) await expect(readWindowsConptyProcessIds(101, { forkProcess })).resolves.toBeNull() }) + it('returns root-only membership when only the helper and shell are attached', async () => { + const { forkProcess } = forkWith('message', [999, 101], 999) + await expect(readWindowsConptyProcessIds(101, { forkProcess })).resolves.toEqual(new Set([101])) + }) + + it('reports membership excluding the helper when a real child is attached', async () => { + const { forkProcess } = forkWith('message', [999, 101, 202], 999) + await expect(readWindowsConptyProcessIds(101, { forkProcess })).resolves.toEqual( + new Set([101, 202]) + ) + }) + it('handles helper spawn errors without an unhandled child error', async () => { const { forkProcess } = forkWith('error') await expect(readWindowsConptyProcessIds(101, { forkProcess })).resolves.toBeNull() @@ -61,4 +81,20 @@ describe('readWindowsConptyProcessIds', () => { vi.useRealTimers() } }) + + it('absorbs an asynchronous kill error after timeout settlement', async () => { + vi.useFakeTimers() + try { + const { child, forkProcess } = forkWith('none') + child.kill.mockImplementation(() => { + queueMicrotask(() => child.emit('error', new Error('kill failed'))) + }) + const result = readWindowsConptyProcessIds(101, { forkProcess, timeoutMs: 10 }) + await vi.advanceTimersByTimeAsync(10) + await expect(result).resolves.toBeNull() + expect(child.listenerCount('error')).toBe(0) + } finally { + vi.useRealTimers() + } + }) }) diff --git a/src/main/providers/windows-conpty-process-membership.ts b/src/main/providers/windows-conpty-process-membership.ts index 759a234c63f..28e29ebbd97 100644 --- a/src/main/providers/windows-conpty-process-membership.ts +++ b/src/main/providers/windows-conpty-process-membership.ts @@ -15,8 +15,8 @@ function resolveNodePtyConsoleListAgent(): string { } /** - * Runs node-pty's fixed native console-list helper with bounded error/exit - * handling. A root-only result is its failure fallback, not shell proof. + * Returns normalized console membership, or null when the probe is unavailable. + * A root-only set proves the shell is alone because successful raw results include the helper. */ export function readWindowsConptyProcessIds( rootPid: number, @@ -45,23 +45,33 @@ export function readWindowsConptyProcessIds( settled = true clearTimeout(timeout) child.removeListener('message', onMessage) - child.removeListener('error', onFailure) - child.removeListener('exit', onFailure) + // Why: kill failures can emit asynchronously after timeout settlement; + // teardown listeners stay until exit so they cannot crash the daemon. resolve(value) } const onFailure = (): void => finish(null) + const onExit = (): void => { + child.removeListener('error', onFailure) + finish(null) + } const onMessage = (message: ProcessListMessage): void => { const value = message?.consoleProcessList + const helperPid = child.pid if ( !Array.isArray(value) || - value.length <= 1 || + helperPid === undefined || !value.includes(rootPid) || + !value.includes(helperPid) || value.some((pid) => !Number.isSafeInteger(pid) || pid <= 0) ) { finish(null) return } - finish(new Set(value)) + // Why: GetConsoleProcessList includes this helper; removing it makes a + // root-only set authoritative shell-only evidence instead of a false child. + const consoleProcessIds = new Set(value) + consoleProcessIds.delete(helperPid) + finish(consoleProcessIds) } const timeout = setTimeout(() => { child.kill() @@ -69,6 +79,6 @@ export function readWindowsConptyProcessIds( }, deps.timeoutMs ?? CONPTY_PROCESS_LIST_TIMEOUT_MS) child.once('message', onMessage) child.once('error', onFailure) - child.once('exit', onFailure) + child.once('exit', onExit) }) } diff --git a/src/main/providers/windows-console-foreground.test.ts b/src/main/providers/windows-console-foreground.test.ts new file mode 100644 index 00000000000..f13ef2ae556 --- /dev/null +++ b/src/main/providers/windows-console-foreground.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest' +import { canConfirmAgentFromConsolePresence } from './windows-console-foreground' + +describe('canConfirmAgentFromConsolePresence', () => { + it('is true for a cached agent when node-pty only names the shell (a scan would run)', () => { + expect(canConfirmAgentFromConsolePresence('claude', 'powershell.exe')).toBe(true) + expect(canConfirmAgentFromConsolePresence('codex', 'cmd.exe')).toBe(true) + }) + + it('is false when node-pty already names a recognized agent (no scan needed)', () => { + // Nothing to save here — the fast no-scan path already returns the agent. + expect(canConfirmAgentFromConsolePresence('claude', 'claude')).toBe(false) + }) + + it('is false for a generic wrapper that may outlive the cached agent', () => { + expect(canConfirmAgentFromConsolePresence('claude', 'node.exe')).toBe(false) + }) + + it('is false when no agent has been recognized yet (identity must be established first)', () => { + expect(canConfirmAgentFromConsolePresence(null, 'powershell.exe')).toBe(false) + }) + + it('is false when there is no fallback process name', () => { + expect(canConfirmAgentFromConsolePresence('claude', null)).toBe(false) + }) +}) diff --git a/src/main/providers/windows-console-foreground.ts b/src/main/providers/windows-console-foreground.ts new file mode 100644 index 00000000000..77b5bfe4f6d --- /dev/null +++ b/src/main/providers/windows-console-foreground.ts @@ -0,0 +1,15 @@ +import { isShellProcess } from '../../shared/shell-process-detection' + +/** Whether ConPTY membership can revalidate a cached agent without a process scan. */ +export function canConfirmAgentFromConsolePresence( + cachedAgentName: string | null, + fallbackProcess: string | null +): boolean { + return ( + cachedAgentName !== null && + fallbackProcess !== null && + // Why: a generic wrapper may outlive the agent; only the shell fallback is + // the known unreliable Windows exit signal this cache is allowed to bridge. + isShellProcess(fallbackProcess) + ) +} From 624c8d4120cd355a336a02048ae5ce6fbe0b2d16 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sun, 19 Jul 2026 15:21:06 -0700 Subject: [PATCH 079/137] Make plain-text file:// links clickable in the terminal (#9467) * Make plain-text file:// links clickable in the terminal Printed file:// URIs (e.g. a report path echoed by a tool or agent) were neither http links nor bare filesystem paths, so the terminal's URL and local-path detectors both skipped them and the link was dead. Orca already resolves and opens file:// URIs for OSC 8 hyperlinks. Reuse that exact resolver for plain-text URIs so a printed file:// behaves the same whether or not the emitter wrapped it in an escape sequence: - Promote the (dependency-pure) file-url target resolver into src/shared so the OSC path and the new plain-text path share one implementation. - Add a file:// detector that decodes the URI to a filesystem path and routes it through the existing file-link pipeline (existence probe + openDetectedFilePath), so line/col anchors, %20, Windows drive paths, html-in-browser, editor reveal, and SSH/runtime resolution all just work. Lines without file:// are unchanged: the pass short-circuits to the prior result, so only file://-bearing lines gain a link. - Add unit + integration coverage for detection, decoding, and no-double-link. * Harden plain-text file URI detection * Split terminal file link detection modules --- config/max-lines-baseline.txt | 1 - .../terminal-osc-link-routing.ts | 2 +- .../lib/terminal-bare-file-link-detection.ts | 68 +++++ .../terminal-file-link-detection-ranges.ts | 126 ++++++++ .../src/lib/terminal-file-uri-link.test.ts | 67 +++++ .../src/lib/terminal-file-uri-link.ts | 95 ++++++ src/renderer/src/lib/terminal-links.test.ts | 28 ++ src/renderer/src/lib/terminal-links.ts | 277 ++++-------------- .../terminal-file-url-target.test.ts | 0 .../terminal-file-url-target.ts | 2 +- 10 files changed, 438 insertions(+), 228 deletions(-) create mode 100644 src/renderer/src/lib/terminal-bare-file-link-detection.ts create mode 100644 src/renderer/src/lib/terminal-file-link-detection-ranges.ts create mode 100644 src/renderer/src/lib/terminal-file-uri-link.test.ts create mode 100644 src/renderer/src/lib/terminal-file-uri-link.ts rename src/{renderer/src/components/terminal-pane => shared}/terminal-file-url-target.test.ts (100%) rename src/{renderer/src/components/terminal-pane => shared}/terminal-file-url-target.ts (95%) diff --git a/config/max-lines-baseline.txt b/config/max-lines-baseline.txt index 543ff2e6a60..be7d1cd9080 100644 --- a/config/max-lines-baseline.txt +++ b/config/max-lines-baseline.txt @@ -280,7 +280,6 @@ inline src/renderer/src/lib/pane-manager/pane-manager.ts inline src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.test.ts inline src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.ts inline src/renderer/src/lib/pane-manager/pane-tree-ops.ts -inline src/renderer/src/lib/terminal-links.ts inline src/renderer/src/lib/worktree-activation.test.ts inline src/renderer/src/lib/worktree-activation.ts inline src/renderer/src/runtime/remote-runtime-terminal-multiplexer.ts diff --git a/src/renderer/src/components/terminal-pane/terminal-osc-link-routing.ts b/src/renderer/src/components/terminal-pane/terminal-osc-link-routing.ts index 2b043d648cf..cdea515c945 100644 --- a/src/renderer/src/components/terminal-pane/terminal-osc-link-routing.ts +++ b/src/renderer/src/components/terminal-pane/terminal-osc-link-routing.ts @@ -1,7 +1,7 @@ import { resolveTerminalFileLinkText } from '@/lib/terminal-links' import { isWindowsAbsolutePathLike } from '../../../../shared/cross-platform-path' import type { LinkHandlerDeps } from './terminal-link-handlers' -import { resolveTerminalFileUrlTarget } from './terminal-file-url-target' +import { resolveTerminalFileUrlTarget } from '../../../../shared/terminal-file-url-target' import { openDetectedFilePath } from './terminal-file-open-routing' import { isTerminalLinkActivation } from './terminal-link-activation' import { diff --git a/src/renderer/src/lib/terminal-bare-file-link-detection.ts b/src/renderer/src/lib/terminal-bare-file-link-detection.ts new file mode 100644 index 00000000000..364ebaffd6c --- /dev/null +++ b/src/renderer/src/lib/terminal-bare-file-link-detection.ts @@ -0,0 +1,68 @@ +import { + detectTerminalFileLinkRanges, + terminalFileLinkRangesOverlap, + toParsedTerminalFileLink +} from './terminal-file-link-detection-ranges' +import type { ParsedTerminalFileLink } from './terminal-links' + +// Mirrors VSCode's terminal word separators, with `:` handled by the existing +// line/column suffix parser instead of acting as a raw separator. +const WORD_TOKEN_REGEX = /[^\s()[\]{}'",;<>|`]+/g + +const EXTENSIONLESS_FILENAMES = new Set([ + 'Makefile', + 'Dockerfile', + 'Rakefile', + 'Gemfile', + 'Procfile', + 'LICENSE', + 'README', + 'CHANGELOG', + 'AUTHORS', + 'NOTICE', + 'CONTRIBUTING' +]) + +const BARE_FILENAME_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9._+-]*$/ +const MAX_BARE_FILENAME_TOKEN_LENGTH = 120 + +function looksLikeFilename(token: string): boolean { + if (token.length < 2 || token.length > 100) { + return false + } + if (!BARE_FILENAME_PATTERN.test(token)) { + return false + } + if (/^\d+$/.test(token)) { + return false + } + if (token.includes('.')) { + return !/^\.+$/.test(token) + } + return EXTENSIONLESS_FILENAMES.has(token) +} + +// Bare words are filesystem-validated by the provider, so reject obvious prose +// before paying for a stat while retaining common extensionless project files. +export function detectBareFilenameLinks( + lineText: string, + claimedRanges: readonly [number, number][] +): ParsedTerminalFileLink[] { + const links: ParsedTerminalFileLink[] = [] + for (const range of detectTerminalFileLinkRanges(lineText, WORD_TOKEN_REGEX)) { + if (terminalFileLinkRangesOverlap(range, claimedRanges)) { + continue + } + // Why: huge terminal blobs can be one unbroken token; parse only bounded + // bare-filename candidates so hover link detection stays interactive. + if (range.text.length > MAX_BARE_FILENAME_TOKEN_LENGTH) { + continue + } + const link = toParsedTerminalFileLink(range) + if (!link || !looksLikeFilename(link.pathText)) { + continue + } + links.push(link) + } + return links +} diff --git a/src/renderer/src/lib/terminal-file-link-detection-ranges.ts b/src/renderer/src/lib/terminal-file-link-detection-ranges.ts new file mode 100644 index 00000000000..304a76482bc --- /dev/null +++ b/src/renderer/src/lib/terminal-file-link-detection-ranges.ts @@ -0,0 +1,126 @@ +import { parseExplicitFileLinkTarget } from './explicit-file-link-target' +import type { ParsedTerminalFileLink } from './terminal-links' + +const LEADING_TRIM_CHARS = new Set(['(', '[', '{', '"', "'"]) +const TRAILING_TRIM_CHARS = new Set([')', ']', '}', '"', "'", ',', ';', '.']) + +export type DetectedTerminalFileLinkRange = { + startIndex: number + endIndex: number + text: string +} + +function trimBoundaryPunctuation( + value: string, + startIndex: number +): DetectedTerminalFileLinkRange | null { + let start = 0 + let end = value.length + + while (start < end && LEADING_TRIM_CHARS.has(value[start])) { + start += 1 + } + while (end > start && TRAILING_TRIM_CHARS.has(value[end - 1])) { + end -= 1 + } + + if (start >= end) { + return null + } + + return { + text: value.slice(start, end), + startIndex: startIndex + start, + endIndex: startIndex + end + } +} + +export function* detectTerminalFileLinkRanges( + lineText: string, + regex: RegExp +): Generator { + for (const match of lineText.matchAll(regex)) { + const rawStart = match.index ?? 0 + const trimmed = trimBoundaryPunctuation(match[0], rawStart) + if (trimmed) { + yield trimmed + } + } +} + +export function mergeTerminalFileLinkRanges(ranges: [number, number][]): [number, number][] { + if (ranges.length <= 1) { + return ranges + } + const sorted = ranges.slice().sort((left, right) => left[0] - right[0] || left[1] - right[1]) + const merged: [number, number][] = [] + for (const range of sorted) { + const last = merged.at(-1) + if (!last || range[0] > last[1]) { + merged.push([range[0], range[1]]) + continue + } + last[1] = Math.max(last[1], range[1]) + } + return merged +} + +export function terminalFileLinkRangesOverlap( + range: DetectedTerminalFileLinkRange, + claimedRanges: readonly [number, number][] +): boolean { + // Why: generated terminal lines can contain thousands of file-looking tokens; + // overlap checks must stay logarithmic instead of scanning every prior range. + let low = 0 + let high = claimedRanges.length + while (low < high) { + const mid = Math.floor((low + high) / 2) + if (claimedRanges[mid][0] < range.endIndex) { + low = mid + 1 + } else { + high = mid + } + } + const previous = claimedRanges[low - 1] + return previous !== undefined && previous[1] > range.startIndex +} + +export function insertTerminalFileLinkClaimedRange( + claimedRanges: [number, number][], + range: [number, number] +): void { + const last = claimedRanges.at(-1) + if (!last || last[0] <= range[0]) { + claimedRanges.push(range) + return + } + + let low = 0 + let high = claimedRanges.length + while (low < high) { + const mid = Math.floor((low + high) / 2) + if (claimedRanges[mid][0] <= range[0]) { + low = mid + 1 + } else { + high = mid + } + } + claimedRanges.splice(low, 0, range) +} + +export function toParsedTerminalFileLink( + range: DetectedTerminalFileLinkRange +): ParsedTerminalFileLink | null { + const parsed = parseExplicitFileLinkTarget(range.text) + if (!parsed) { + return null + } + return { + pathText: parsed.pathText, + line: parsed.line, + column: parsed.column, + startIndex: range.startIndex, + endIndex: range.endIndex, + displayText: range.text + } +} diff --git a/src/renderer/src/lib/terminal-file-uri-link.test.ts b/src/renderer/src/lib/terminal-file-uri-link.test.ts new file mode 100644 index 00000000000..1e8504f69d3 --- /dev/null +++ b/src/renderer/src/lib/terminal-file-uri-link.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from 'vitest' +import { detectTerminalFileUriLinks } from './terminal-file-uri-link' + +describe('detectTerminalFileUriLinks', () => { + it('decodes a localhost-less file URI to an absolute path', () => { + const line = 'Report: file:///Users/dev/orca/report.html' + const [link] = detectTerminalFileUriLinks(line) + expect(link).toMatchObject({ + pathText: '/Users/dev/orca/report.html', + line: null, + column: null + }) + expect(line.slice(link.startIndex, link.endIndex)).toBe('file:///Users/dev/orca/report.html') + expect(link.displayText).toBe('file:///Users/dev/orca/report.html') + }) + + it('percent-decodes spaces in the path', () => { + const [link] = detectTerminalFileUriLinks('open file:///Users/dev/My%20Reports/out.html now') + expect(link.pathText).toBe('/Users/dev/My Reports/out.html') + }) + + it('keeps standard unescaped parentheses and apostrophes inside the path', () => { + const uri = "file:///tmp/Brennan's%20Report%20(final)" + const [link] = detectTerminalFileUriLinks(`open (${uri})`) + expect(link.pathText).toBe("/tmp/Brennan's Report (final)") + expect(link.displayText).toBe(uri) + }) + + it('carries a :line:col suffix', () => { + const [link] = detectTerminalFileUriLinks('file:///Users/dev/app.ts:12:3') + expect(link).toMatchObject({ pathText: '/Users/dev/app.ts', line: 12, column: 3 }) + }) + + it('carries an #Lline anchor', () => { + const [link] = detectTerminalFileUriLinks('file:///Users/dev/app.ts#L42') + expect(link).toMatchObject({ pathText: '/Users/dev/app.ts', line: 42, column: null }) + }) + + it('strips the WHATWG leading slash before a Windows drive path', () => { + const [link] = detectTerminalFileUriLinks('file:///C:/Users/dev/report.html') + expect(link.pathText).toBe('C:/Users/dev/report.html') + }) + + it('trims trailing sentence punctuation but keeps the extension', () => { + const [link] = detectTerminalFileUriLinks('see file:///tmp/out.html.') + expect(link.displayText).toBe('file:///tmp/out.html') + expect(link.pathText).toBe('/tmp/out.html') + }) + + it('rejects remote hosts (existence probing handles the path otherwise)', () => { + expect(detectTerminalFileUriLinks('file://build-server/var/log/out.txt')).toEqual([]) + }) + + it('ignores non-file schemes and malformed escapes', () => { + expect(detectTerminalFileUriLinks('https://example.com/x.html')).toEqual([]) + expect(detectTerminalFileUriLinks('file:///tmp/%E0%A4%A.txt')).toEqual([]) + }) + + it('finds multiple file URIs on one line', () => { + const links = detectTerminalFileUriLinks('a file:///tmp/a.txt b file:///tmp/b.txt') + expect(links.map((link) => link.pathText)).toEqual(['/tmp/a.txt', '/tmp/b.txt']) + }) + + it('does not expose oversized terminal tokens to filesystem probing', () => { + expect(detectTerminalFileUriLinks(`file:///tmp/${'a'.repeat(10_000)}`)).toEqual([]) + }) +}) diff --git a/src/renderer/src/lib/terminal-file-uri-link.ts b/src/renderer/src/lib/terminal-file-uri-link.ts new file mode 100644 index 00000000000..d9751ba24cf --- /dev/null +++ b/src/renderer/src/lib/terminal-file-uri-link.ts @@ -0,0 +1,95 @@ +import { resolveTerminalFileUrlTarget } from '../../../shared/terminal-file-url-target' +import type { ParsedTerminalFileLink } from './terminal-links' + +// Why: plain-text file URIs bypass both the HTTP and local-path detectors; use +// the OSC 8 resolver so both terminal representations open identically. + +const MAX_FILE_URI_LENGTH = 2048 +// Why: extraction runs on hover; cap before URL parsing and filesystem probes +// so a file:// prefix in a huge dumped token cannot block the renderer. +const FILE_URI_REGEX = /\bfile:\/\/[^\s"`<>|]{1,2049}/gi + +const TRAILING_PROSE_CHARS = new Set(['.', ',', ';', ':', '!', '?', '>', '"', "'", '`']) + +function trimTrailingProse(uriText: string): string { + let parentheses = 0 + let brackets = 0 + let braces = 0 + for (const char of uriText) { + parentheses += char === ')' ? 1 : char === '(' ? -1 : 0 + brackets += char === ']' ? 1 : char === '[' ? -1 : 0 + braces += char === '}' ? 1 : char === '{' ? -1 : 0 + } + + let end = uriText.length + while (end > 0) { + const char = uriText[end - 1] + if (TRAILING_PROSE_CHARS.has(char)) { + end -= 1 + continue + } + // Why: standard file URIs leave parentheses unescaped; trim only closing + // delimiters supplied by surrounding prose, not balanced filename text. + if (char === ')' && parentheses > 0) { + parentheses -= 1 + end -= 1 + continue + } + if (char === ']' && brackets > 0) { + brackets -= 1 + end -= 1 + continue + } + if (char === '}' && braces > 0) { + braces -= 1 + end -= 1 + continue + } + break + } + return uriText.slice(0, end) +} + +// Remote hosts are rejected: on a local pane a hostname'd URI would resolve to a +// path that does not exist, and the provider's existence probe would drop it +// anyway. Windows UNC support stays with the OSC path, which has the platform +// context this pure pass deliberately avoids. +function toFileUriLink(uriText: string, startIndex: number): ParsedTerminalFileLink | null { + let url: URL + try { + url = new URL(uriText) + } catch { + return null + } + const target = resolveTerminalFileUrlTarget(url) + if (!target) { + return null + } + return { + pathText: target.filePath, + line: target.line, + column: target.column, + startIndex, + endIndex: startIndex + uriText.length, + displayText: uriText + } +} + +export function detectTerminalFileUriLinks(lineText: string): ParsedTerminalFileLink[] { + const links: ParsedTerminalFileLink[] = [] + for (const match of lineText.matchAll(FILE_URI_REGEX)) { + const startIndex = match.index ?? 0 + if (match[0].length > MAX_FILE_URI_LENGTH) { + continue + } + const trimmed = trimTrailingProse(match[0]) + if (!trimmed) { + continue + } + const link = toFileUriLink(trimmed, startIndex) + if (link) { + links.push(link) + } + } + return links +} diff --git a/src/renderer/src/lib/terminal-links.test.ts b/src/renderer/src/lib/terminal-links.test.ts index f6aa3501a0a..66ff8d870a8 100644 --- a/src/renderer/src/lib/terminal-links.test.ts +++ b/src/renderer/src/lib/terminal-links.test.ts @@ -5,6 +5,7 @@ import { columnForTerminalFileLinkTap } from '../../../shared/terminal-file-link-conformance' import { + extractTerminalFileLinkCandidates, extractTerminalFileLinks, isPathInsideWorktree, resolveTerminalFileLink, @@ -316,4 +317,31 @@ describe('terminal path helpers', () => { it('does not resolve partial text as an OSC hyperlink target', () => { expect(resolveTerminalFileLinkText('open docs/README.md', '/repo')).toBeNull() }) + + describe('plain-text file:// URIs', () => { + it('extracts a printed file:// URI as a file link resolving to its path', () => { + const line = 'Report: file:///Users/dev/orca/report.html' + const link = extractTerminalFileLinks(line).find( + (candidate) => candidate.displayText === 'file:///Users/dev/orca/report.html' + ) + expect(link).toMatchObject({ pathText: '/Users/dev/orca/report.html' }) + expect(resolveTerminalFileLink(link!, '/Users/dev/orca')).toEqual({ + absolutePath: '/Users/dev/orca/report.html', + line: null, + column: null + }) + }) + + it('does not also emit a bare-path link for the URI body', () => { + const links = extractTerminalFileLinks('file:///Users/dev/orca/report.html') + expect(links.map((link) => link.displayText)).toEqual(['file:///Users/dev/orca/report.html']) + }) + + it('exposes file:// URIs to the hover candidate pass as well', () => { + const candidates = extractTerminalFileLinkCandidates('file:///tmp/out.txt:9') + expect(candidates.some((link) => link.pathText === '/tmp/out.txt' && link.line === 9)).toBe( + true + ) + }) + }) }) diff --git a/src/renderer/src/lib/terminal-links.ts b/src/renderer/src/lib/terminal-links.ts index 605360e4245..729f2d839e7 100644 --- a/src/renderer/src/lib/terminal-links.ts +++ b/src/renderer/src/lib/terminal-links.ts @@ -1,9 +1,15 @@ -/* eslint-disable max-lines -- Why: terminal link parsing depends on ordered passes sharing range state. */ import { normalizeAbsolutePath } from './terminal-path-normalization' +import { resolveExplicitFileLinkTarget } from './explicit-file-link-target' +import { detectBareFilenameLinks } from './terminal-bare-file-link-detection' import { - parseExplicitFileLinkTarget, - resolveExplicitFileLinkTarget -} from './explicit-file-link-target' + detectTerminalFileLinkRanges, + insertTerminalFileLinkClaimedRange, + mergeTerminalFileLinkRanges, + terminalFileLinkRangesOverlap, + toParsedTerminalFileLink, + type DetectedTerminalFileLinkRange +} from './terminal-file-link-detection-ranges' +import { detectTerminalFileUriLinks } from './terminal-file-uri-link' export type ParsedTerminalFileLink = { pathText: string @@ -52,63 +58,7 @@ const SPACED_LOCAL_PATH_REGEXES = [ LINE_ENDING_SPACED_PATH_REGEX ] -// Word separators used by the bare-filename pass. Mirrors the default set in -// VSCode's `terminal.integrated.wordSeparators` with the exception that we -// include `:` indirectly via the line:col suffix parser rather than as a -// raw separator. A word is any maximal run of non-separator characters. -// \s matches NBSP in modern JS; xterm powerline glyphs are in the PUA and -// never appear in filenames, so we don't list them explicitly. -const WORD_TOKEN_REGEX = /[^\s()[\]{}'",;<>|`]+/g - -const LEADING_TRIM_CHARS = new Set(['(', '[', '{', '"', "'"]) -const TRAILING_TRIM_CHARS = new Set([')', ']', '}', '"', "'", ',', ';', '.']) - -function trimBoundaryPunctuation( - value: string, - startIndex: number -): { text: string; startIndex: number; endIndex: number } | null { - let start = 0 - let end = value.length - - while (start < end && LEADING_TRIM_CHARS.has(value[start])) { - start += 1 - } - while (end > start && TRAILING_TRIM_CHARS.has(value[end - 1])) { - end -= 1 - } - - if (start >= end) { - return null - } - - return { - text: value.slice(start, end), - startIndex: startIndex + start, - endIndex: startIndex + end - } -} - -// Project files that look like filenames despite having no extension. The -// word detector otherwise requires a `.` in the token to keep noise down — -// without this list, `ls` output containing `Makefile` or `LICENSE` would -// not be clickable. -const EXTENSIONLESS_FILENAMES = new Set([ - 'Makefile', - 'Dockerfile', - 'Rakefile', - 'Gemfile', - 'Procfile', - 'LICENSE', - 'README', - 'CHANGELOG', - 'AUTHORS', - 'NOTICE', - 'CONTRIBUTING' -]) - -const BARE_FILENAME_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9._+-]*$/ const URI_PREFIX_CHAR_PATTERN = /^[A-Za-z0-9+./:-]$/ -const MAX_BARE_FILENAME_TOKEN_LENGTH = 120 function hasPathSeparator(text: string): boolean { return text.includes('/') || text.includes('\\') @@ -147,41 +97,6 @@ function hasSpacedPathExtension(text: string): boolean { return /\s/.test(trimmedText) && /\.[A-Za-z0-9_+-]+(?::\d+)?(?::\d+)?$/.test(trimmedText) } -// Bare words are validated against the filesystem by the provider, so this -// filter's job is to reject tokens that are obviously not filenames before -// we pay for a stat. Plain words like `src` or `my-cli` are usually -// directories or binaries and produce more noise than value — users who -// really want to open them can prefix with `./`. -function looksLikeFilename(token: string): boolean { - if (token.length < 2 || token.length > 100) { - return false - } - if (!BARE_FILENAME_PATTERN.test(token)) { - return false - } - if (/^\d+$/.test(token)) { - return false - } - if (token.includes('.')) { - return !/^\.+$/.test(token) - } - return EXTENSIONLESS_FILENAMES.has(token) -} - -type DetectedRange = { startIndex: number; endIndex: number; text: string } -// Shared tokenization: run a regex over the line, trim boundary punctuation, -// hand each surviving range to the caller. Collapses the three near-copies -// of this loop the module had grown. -function* detectRanges(lineText: string, regex: RegExp): Generator { - for (const match of lineText.matchAll(regex)) { - const rawStart = match.index ?? 0 - const trimmed = trimBoundaryPunctuation(match[0], rawStart) - if (trimmed) { - yield trimmed - } - } -} - function getImmediateUriPrefix(lineText: string, endIndex: number): string { let start = endIndex while (start > 0 && URI_PREFIX_CHAR_PATTERN.test(lineText[start - 1])) { @@ -190,7 +105,7 @@ function getImmediateUriPrefix(lineText: string, endIndex: number): string { return lineText.slice(start, endIndex) } -function isInsideUriScheme(lineText: string, range: DetectedRange): boolean { +function isInsideUriScheme(lineText: string, range: DetectedTerminalFileLinkRange): boolean { const prefix = getImmediateUriPrefix(lineText, range.startIndex) // Why: local-path matching can start at the `//host/path` portion of a URL. return ( @@ -200,61 +115,9 @@ function isInsideUriScheme(lineText: string, range: DetectedRange): boolean { ) } -function mergeRanges(ranges: [number, number][]): [number, number][] { - if (ranges.length <= 1) { - return ranges - } - const sorted = ranges.slice().sort((left, right) => left[0] - right[0] || left[1] - right[1]) - const merged: [number, number][] = [] - for (const range of sorted) { - const last = merged.at(-1) - if (!last || range[0] > last[1]) { - merged.push([range[0], range[1]]) - continue - } - last[1] = Math.max(last[1], range[1]) - } - return merged -} - -function rangesOverlap(range: DetectedRange, claimedRanges: readonly [number, number][]): boolean { - // Why: generated terminal lines can contain thousands of file-looking tokens; - // overlap checks must stay logarithmic instead of scanning every prior range. - let low = 0 - let high = claimedRanges.length - while (low < high) { - const mid = Math.floor((low + high) / 2) - if (claimedRanges[mid][0] < range.endIndex) { - low = mid + 1 - } else { - high = mid - } - } - const previous = claimedRanges[low - 1] - return previous !== undefined && previous[1] > range.startIndex -} - -function insertClaimedRange(claimedRanges: [number, number][], range: [number, number]): void { - const last = claimedRanges.at(-1) - if (!last || last[0] <= range[0]) { - claimedRanges.push(range) - return - } - - let low = 0 - let high = claimedRanges.length - while (low < high) { - const mid = Math.floor((low + high) / 2) - if (claimedRanges[mid][0] <= range[0]) { - low = mid + 1 - } else { - high = mid - } - } - claimedRanges.splice(low, 0, range) -} - -function trimSpacedPathTrailingProse(range: DetectedRange): DetectedRange { +function trimSpacedPathTrailingProse( + range: DetectedTerminalFileLinkRange +): DetectedTerminalFileLinkRange { // Why: keep one extension-terminated path, but drop trailing prose or a // second unrelated path that the broad spaced-path scan also captured. A // line-end extension token only extends the span when the added segment is @@ -296,7 +159,9 @@ function countPathStarts(text: string): number { return count } -function trimTrailingWhitespace(range: DetectedRange): DetectedRange { +function trimTrailingWhitespace( + range: DetectedTerminalFileLinkRange +): DetectedTerminalFileLinkRange { const text = range.text.trimEnd() return { text, @@ -305,8 +170,10 @@ function trimTrailingWhitespace(range: DetectedRange): DetectedRange { } } -function buildLineEndingSpacedPathPrefixRanges(range: DetectedRange): DetectedRange[] { - const ranges: DetectedRange[] = [] +function buildLineEndingSpacedPathPrefixRanges( + range: DetectedTerminalFileLinkRange +): DetectedTerminalFileLinkRange[] { + const ranges: DetectedTerminalFileLinkRange[] = [] for (const match of range.text.matchAll(/\s+/g)) { const endIndex = match.index ?? 0 const text = range.text.slice(0, endIndex).trimEnd() @@ -321,25 +188,6 @@ function buildLineEndingSpacedPathPrefixRanges(range: DetectedRange): DetectedRa return ranges.toReversed() } -function toParsedLink(range: DetectedRange): ParsedTerminalFileLink | null { - const parsed = parseExplicitFileLinkTarget(range.text) - if (!parsed) { - return null - } - return { - pathText: parsed.pathText, - line: parsed.line, - column: parsed.column, - startIndex: range.startIndex, - endIndex: range.endIndex, - displayText: range.text - } -} - -function sortLinksByPosition(links: ParsedTerminalFileLink[]): ParsedTerminalFileLink[] { - return links.sort((a, b) => a.startIndex - b.startIndex || b.endIndex - a.endIndex) -} - // Ported from VSCode's TerminalLocalLinkDetector. Extracts anything that // contains a path separator, optionally with a `:line:col` suffix — covers // `./src/foo.ts`, `/abs/bar`, `src/foo.ts:12:3`, etc. @@ -353,14 +201,14 @@ function detectLocalPathLinks( const links: ParsedTerminalFileLink[] = [] const spacedLinks = detectSpacedLocalPathLinks(lineText, includeLineEndingPrefixCandidates) - const spacedRanges = mergeRanges( + const spacedRanges = mergeTerminalFileLinkRanges( spacedLinks.map(({ startIndex, endIndex }): [number, number] => [startIndex, endIndex]) ) for (const link of spacedLinks) { links.push(link) } - for (const range of detectRanges(lineText, LOCAL_PATH_REGEX)) { - if (rangesOverlap(range, spacedRanges)) { + for (const range of detectTerminalFileLinkRanges(lineText, LOCAL_PATH_REGEX)) { + if (terminalFileLinkRangesOverlap(range, spacedRanges)) { continue } if (isInsideUriScheme(lineText, range)) { @@ -369,12 +217,12 @@ function detectLocalPathLinks( if (!/[\\/]/.test(range.text)) { continue } - const link = toParsedLink(range) + const link = toParsedTerminalFileLink(range) if (link) { links.push(link) } } - return sortLinksByPosition(links) + return links.sort((a, b) => a.startIndex - b.startIndex || b.endIndex - a.endIndex) } function detectSpacedLocalPathLinks( @@ -384,7 +232,7 @@ function detectSpacedLocalPathLinks( const links: ParsedTerminalFileLink[] = [] const claimedRanges: [number, number][] = [] for (const regex of SPACED_LOCAL_PATH_REGEXES) { - for (const range of detectRanges(lineText, regex)) { + for (const range of detectTerminalFileLinkRanges(lineText, regex)) { if (regex === SPACED_PATH_WITH_SEPARATOR_REGEX && !hasSeparatorAfterWhitespace(range.text)) { continue } @@ -398,7 +246,10 @@ function detectSpacedLocalPathLinks( ) { continue } - if (rangesOverlap(range, claimedRanges) || isInsideUriScheme(lineText, range)) { + if ( + terminalFileLinkRangesOverlap(range, claimedRanges) || + isInsideUriScheme(lineText, range) + ) { continue } const candidateRanges = @@ -407,7 +258,9 @@ function detectSpacedLocalPathLinks( : [range] const candidateLinks = candidateRanges .map((candidateRange) => - toParsedLink(trimSpacedPathTrailingProse(trimTrailingWhitespace(candidateRange))) + toParsedTerminalFileLink( + trimSpacedPathTrailingProse(trimTrailingWhitespace(candidateRange)) + ) ) .filter((link): link is ParsedTerminalFileLink => link !== null) const link = candidateLinks[0] @@ -415,65 +268,39 @@ function detectSpacedLocalPathLinks( for (const candidateLink of candidateLinks) { links.push(candidateLink) } - insertClaimedRange(claimedRanges, [link.startIndex, link.endIndex]) + insertTerminalFileLinkClaimedRange(claimedRanges, [link.startIndex, link.endIndex]) } } } return links } -// Ported from VSCode's TerminalWordLinkDetector. Tokenizes the line on -// separators and emits filename-ish words so `ls` output becomes clickable. -// Skips ranges already claimed by the local-path pass to avoid double links -// when a bare filename happens to be a substring of a longer path. -function detectBareFilenameLinks( +// Runs the file-uri, local-path, and bare-filename passes in that precedence. +// `file://` and separator paths claim their ranges first so the bare-filename +// pass never re-links a token already covered by a longer explicit link. +function assembleFileLinks( lineText: string, - claimedRanges: readonly [number, number][] + includeLineEndingPrefixCandidates: boolean ): ParsedTerminalFileLink[] { - const links: ParsedTerminalFileLink[] = [] - for (const range of detectRanges(lineText, WORD_TOKEN_REGEX)) { - if (rangesOverlap(range, claimedRanges)) { - continue - } - // Why: huge terminal blobs can be one unbroken token; parse only bounded - // bare-filename candidates so hover link detection stays interactive. - if (range.text.length > MAX_BARE_FILENAME_TOKEN_LENGTH) { - continue - } - const link = toParsedLink(range) - if (!link) { - continue - } - if (!looksLikeFilename(link.pathText)) { - continue - } - links.push(link) + const uriLinks = detectTerminalFileUriLinks(lineText) + const pathLinks = detectLocalPathLinks(lineText, includeLineEndingPrefixCandidates) + const explicitLinks = uriLinks.length > 0 ? [...uriLinks, ...pathLinks] : pathLinks + const claimed = mergeTerminalFileLinkRanges( + explicitLinks.map(({ startIndex, endIndex }): [number, number] => [startIndex, endIndex]) + ) + const wordLinks = detectBareFilenameLinks(lineText, claimed) + for (const link of wordLinks) { + explicitLinks.push(link) } - return links + return explicitLinks } export function extractTerminalFileLinks(lineText: string): ParsedTerminalFileLink[] { - const pathLinks = detectLocalPathLinks(lineText) - const claimed = mergeRanges( - pathLinks.map(({ startIndex, endIndex }): [number, number] => [startIndex, endIndex]) - ) - const wordLinks = detectBareFilenameLinks(lineText, claimed) - for (const link of wordLinks) { - pathLinks.push(link) - } - return pathLinks + return assembleFileLinks(lineText, false) } export function extractTerminalFileLinkCandidates(lineText: string): ParsedTerminalFileLink[] { - const pathLinks = detectLocalPathLinks(lineText, true) - const claimed = mergeRanges( - pathLinks.map(({ startIndex, endIndex }): [number, number] => [startIndex, endIndex]) - ) - const wordLinks = detectBareFilenameLinks(lineText, claimed) - for (const link of wordLinks) { - pathLinks.push(link) - } - return pathLinks + return assembleFileLinks(lineText, true) } export function resolveTerminalFileLink( diff --git a/src/renderer/src/components/terminal-pane/terminal-file-url-target.test.ts b/src/shared/terminal-file-url-target.test.ts similarity index 100% rename from src/renderer/src/components/terminal-pane/terminal-file-url-target.test.ts rename to src/shared/terminal-file-url-target.test.ts diff --git a/src/renderer/src/components/terminal-pane/terminal-file-url-target.ts b/src/shared/terminal-file-url-target.ts similarity index 95% rename from src/renderer/src/components/terminal-pane/terminal-file-url-target.ts rename to src/shared/terminal-file-url-target.ts index f21b31ed7c4..941023181ee 100644 --- a/src/renderer/src/components/terminal-pane/terminal-file-url-target.ts +++ b/src/shared/terminal-file-url-target.ts @@ -1,4 +1,4 @@ -import { fileUriToFilesystemPath } from '../../../../shared/file-uri-path' +import { fileUriToFilesystemPath } from './file-uri-path' export type TerminalFileUrlTarget = { filePath: string From 08f446190abadafa9ae9fc224ded171640822858 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sun, 19 Jul 2026 15:59:36 -0700 Subject: [PATCH 080/137] Standardize per-repo Source Control AI save UX (#9427) * Standardize per-repo Source Control AI save UX with global recipe patter Extract draft/save logic into `useRepositorySourceControlAiGlobalUx` hook and serial persist queue, matching how global action recipes save: selects persist immediately, CLI args and command template draft until per-action Save. * Support draft/commit for per-repo custom-command input - Drafts custom commands locally on keystroke without backend writes - Commits to persistence only on blur or mode selection change - Adds forceRepoMode local state to fix mode-select snapping with empty fields - Mirrors existing pattern used for action recipe text editing - Improves persist queue with repo-switch safety (returns boolean, pins repoId at schedule time) - Adds comprehensive test coverage for settings hook and persist queue * Fix forceRepoMode logic to preserve REPO intent mid-edit Always set forceRepoMode based on field content instead of only when a repo command is present. This preserves REPO intent when the field is empty during editing, only exiting when a value is explicitly entered. Add test for draft discard and revert behavior. --- .../RepositorySourceControlAiActionRows.tsx | 24 +- ...RepositorySourceControlAiCustomCommand.tsx | 56 ++- .../RepositorySourceControlAiSection.test.ts | 80 +--- .../RepositorySourceControlAiSection.tsx | 399 +++--------------- ...repository-source-control-ai-draft.test.ts | 56 --- .../repository-source-control-ai-draft.ts | 308 ++++++++++---- ...sitory-source-control-ai-global-ux.test.ts | 236 +++++++++++ .../repository-source-control-ai-global-ux.ts | 316 ++++++++++++++ ...ry-source-control-ai-persist-queue.test.ts | 149 +++++++ ...ository-source-control-ai-persist-queue.ts | 73 ++++ 10 files changed, 1116 insertions(+), 581 deletions(-) delete mode 100644 src/renderer/src/components/settings/repository-source-control-ai-draft.test.ts create mode 100644 src/renderer/src/components/settings/repository-source-control-ai-global-ux.test.ts create mode 100644 src/renderer/src/components/settings/repository-source-control-ai-global-ux.ts create mode 100644 src/renderer/src/components/settings/repository-source-control-ai-persist-queue.test.ts create mode 100644 src/renderer/src/components/settings/repository-source-control-ai-persist-queue.ts diff --git a/src/renderer/src/components/settings/RepositorySourceControlAiActionRows.tsx b/src/renderer/src/components/settings/RepositorySourceControlAiActionRows.tsx index 58a735e04dc..9fcfe68c077 100644 --- a/src/renderer/src/components/settings/RepositorySourceControlAiActionRows.tsx +++ b/src/renderer/src/components/settings/RepositorySourceControlAiActionRows.tsx @@ -51,7 +51,8 @@ type RepositorySourceControlAiActionRowsProps = { onActionTemplateChange: (actionId: SourceControlActionId, value: string) => void onActionAgentArgsChange: (actionId: SourceControlActionId, value: string) => void onAppendVariable: (actionId: SourceControlActionId, variable: string) => void - isSaving: boolean + /** Per-action saving state for CLI args + command template (matches global recipes). */ + savingActionIds: Partial> actionDirtyById: Record onActionDiscard: (actionId: SourceControlActionId) => void onActionSave: (actionId: SourceControlActionId) => void @@ -67,7 +68,7 @@ export function RepositorySourceControlAiActionRows({ onActionTemplateChange, onActionAgentArgsChange, onAppendVariable, - isSaving, + savingActionIds, actionDirtyById, onActionDiscard, onActionSave @@ -103,6 +104,7 @@ export function RepositorySourceControlAiActionRows({ const agentWarningText = getSourceControlActionAgentWarningText(actionId, effectiveAgent) const agentSupportText = getSourceControlActionAgentSupportText(actionId) const actionDirty = actionDirtyById[actionId] + const isSavingAction = savingActionIds[actionId] === true return (
    {hasOverride ? ( -
    +

    {actionDirty ? translate( - 'auto.components.settings.RepositorySourceControlAiSection.e57dde9d93', + 'auto.components.settings.SourceControlAiActionRecipeDefaults.817128d94e', 'Unsaved changes' ) : translate( - 'auto.components.settings.RepositorySourceControlAiSection.ccb07dd027', + 'auto.components.settings.SourceControlAiActionRecipeDefaults.9d3cc627f8', 'Saved' )}

    @@ -268,10 +270,10 @@ export function RepositorySourceControlAiActionRows({ variant="ghost" size="xs" onClick={() => onActionDiscard(actionId)} - disabled={isSaving} + disabled={isSavingAction} > {translate( - 'auto.components.settings.RepositorySourceControlAiSection.67b3ff5467', + 'auto.components.settings.SourceControlAiActionRecipeDefaults.b3914ecbbc', 'Discard' )} @@ -281,15 +283,15 @@ export function RepositorySourceControlAiActionRows({ variant="secondary" size="xs" onClick={() => onActionSave(actionId)} - disabled={!actionDirty || isSaving} + disabled={!actionDirty || isSavingAction} > - {isSaving + {isSavingAction ? translate( - 'auto.components.settings.RepositorySourceControlAiSection.57e6e9d4b1', + 'auto.components.settings.SourceControlAiActionRecipeDefaults.4f549a5fa8', 'Saving...' ) : translate( - 'auto.components.settings.RepositorySourceControlAiSection.152268c295', + 'auto.components.settings.SourceControlAiActionRecipeDefaults.d18d665e12', 'Save' )} diff --git a/src/renderer/src/components/settings/RepositorySourceControlAiCustomCommand.tsx b/src/renderer/src/components/settings/RepositorySourceControlAiCustomCommand.tsx index 6a22472b2ee..e3e6e32cf6b 100644 --- a/src/renderer/src/components/settings/RepositorySourceControlAiCustomCommand.tsx +++ b/src/renderer/src/components/settings/RepositorySourceControlAiCustomCommand.tsx @@ -1,4 +1,5 @@ import type React from 'react' +import { useState } from 'react' import type { SourceControlAiSettings } from '../../../../shared/source-control-ai-types' import { Input } from '../ui/input' import { Label } from '../ui/label' @@ -12,19 +13,27 @@ import { translate } from '@/i18n/i18n' type RepositorySourceControlAiCustomCommandProps = { value: string | undefined source: SourceControlAiSettings + // onChange drafts the value locally (per keystroke); onCommit persists it (on blur / mode change). onChange: (value: string | undefined) => void + onCommit: (value: string | undefined) => void +} + +function isRepoCommand(value: string | undefined): boolean { + return typeof value === 'string' && value.trim().length > 0 } export function RepositorySourceControlAiCustomCommand({ value, source, - onChange + onChange, + onCommit }: RepositorySourceControlAiCustomCommandProps): React.JSX.Element { - // Why: value only counts as a repo command when it is a non-empty trimmed string; - // empty/nullish values make hasRepoCommand select CUSTOM_COMMAND_MODE_INHERIT - // instead of CUSTOM_COMMAND_MODE_REPO, so clearing the input switches mode. - const hasRepoCommand = typeof value === 'string' && value.trim().length > 0 - const mode = hasRepoCommand ? CUSTOM_COMMAND_MODE_REPO : CUSTOM_COMMAND_MODE_INHERIT + // Why: selecting "Repository command" with an empty global/repo value would otherwise snap the + // Select back to inherit (empty is not a repo command). Keep a local intent so the user can type. + const [forceRepoMode, setForceRepoMode] = useState(false) + const hasRepoCommand = isRepoCommand(value) + const mode = + hasRepoCommand || forceRepoMode ? CUSTOM_COMMAND_MODE_REPO : CUSTOM_COMMAND_MODE_INHERIT return (
    @@ -45,13 +54,23 @@ export function RepositorySourceControlAiCustomCommand({ ) - // by falling back to click-based selection on child options. + // Why: match on label (textContent) and value so opaque option IDs still select; also handles combobox via click. await refSender('Runtime.callFunctionOn', { objectId: object.objectId, functionDeclaration: `function(val) { @@ -433,9 +415,7 @@ export class CdpBridge { const sender = this.makeCdpSender(guest) await this.ensureDebuggerAttached(guest) - // Why: JS scrollBy is deterministic and doesn't require page focus, - // unlike Input.dispatchMouseEvent mouseWheel which is unreliable in - // Electron webviews and can timeout. + // Why: JS scrollBy needs no focus and is deterministic, unlike mouseWheel which is unreliable in Electron webviews. const expr = amount ? `window.scrollBy(0, ${direction === 'down' ? amount : -amount})` : `window.scrollBy(0, ${direction === 'down' ? 'window.innerHeight' : '-window.innerHeight'})` @@ -501,10 +481,7 @@ export class CdpBridge { clickCount: 1 }) - // Why: custom checkbox patterns (label wrapping hidden input, overlays) - // may not toggle from coordinate-based clicking. Verify state changed - // and fall back to programmatic .click() if needed. Wrapped in try/catch - // because the click may have triggered a re-render that invalidated objectId. + // Why: custom checkboxes may not toggle from a coordinate click; verify state and fall back to programmatic .click(). try { const { result: afterState } = (await refSender('Runtime.callFunctionOn', { objectId: object.objectId, @@ -639,10 +616,7 @@ export class CdpBridge { cssContentSize?: { width: number; height: number } contentSize?: { width: number; height: number } } - // Why: Page.captureScreenshot clip coordinates are CSS pixels. On HiDPI - // displays, Electron can report device-pixel `contentSize`, which causes - // Chromium to tile the page into duplicated quadrants. Prefer - // `cssContentSize` so the clip matches the page's CSS layout size. + // Why: screenshot clip uses CSS pixels; on HiDPI, device-pixel contentSize tiles duplicates, so prefer cssContentSize. const contentSize = metrics.cssContentSize ?? metrics.contentSize if (!contentSize) { throw new BrowserError('browser_error', 'Unable to determine full-page screenshot bounds') @@ -699,10 +673,7 @@ export class CdpBridge { const sender = this.makeCdpSender(guest) await this.ensureDebuggerAttached(guest) - // Why: Network.setCookie requires either domain or url to scope the cookie. - // If the caller doesn't provide a domain, infer it from the current page URL - // so the cookie is usable on the active page without forcing callers to - // know the domain upfront. + // Why: Network.setCookie needs a domain or url to scope the cookie; infer the domain from the current page when omitted. let domain = cookie.domain if (!domain) { const { result: urlResult } = (await sender('Runtime.evaluate', { @@ -747,8 +718,7 @@ export class CdpBridge { if (url) { params.url = url } - // Why: Network.deleteCookies requires at least one of domain or url. - // Infer from current page if neither was provided. + // Why: Network.deleteCookies needs a domain or url; infer from the current page if neither was given. if (!domain && !url) { const { result: urlResult } = (await sender('Runtime.evaluate', { expression: 'location.href', @@ -781,8 +751,7 @@ export class CdpBridge { deviceScaleFactor, mobile }) - // Why: BrowserView's compositor surface can keep the previous host size - // after metrics-only resize, which crops remote screencast clients. + // Why: metrics-only resize can leave the compositor surface at the old size, cropping remote screencast clients. await Promise.resolve(sender('Emulation.setVisibleSize', { width, height })).catch(() => {}) return { width, height, deviceScaleFactor, mobile } @@ -852,9 +821,7 @@ export class CdpBridge { return { requests: [...state.pausedRequests.values()] } } - // TODO: Add interceptContinue/interceptBlock once agent-browser supports per-request - // interception decisions. The CDP Fetch domain supports it, but the agent-browser CLI - // only operates on URL pattern-level routing, creating a design mismatch. + // TODO: Add interceptContinue/interceptBlock once agent-browser supports per-request decisions (CLI is URL-pattern-only). // ── Console/network capture ── @@ -975,8 +942,7 @@ export class CdpBridge { const valueStr = result.value !== undefined ? String(result.value) : (result.description ?? '') - // Why: agent-browser returns { result, origin } — match that shape so both - // bridges satisfy the same BrowserEvalResult contract. + // Why: include origin to match agent-browser's BrowserEvalResult shape across both bridges. const { result: urlResult } = (await sender('Runtime.evaluate', { expression: 'location.origin', returnByValue: true @@ -1011,8 +977,7 @@ export class CdpBridge { } async tabSwitch(index: number): Promise { - // Why: filter to live tabs so indices match what tabList() presents. - // Destroyed-but-not-yet-cleaned entries must be skipped. + // Why: filter to live tabs so indices match tabList(), skipping destroyed-but-uncleaned entries. const liveEntries = [...this.getRegisteredTabs()].filter(([_, wcId]) => { const guest = webContents.fromId(wcId) return guest && !guest.isDestroyed() @@ -1061,10 +1026,7 @@ export class CdpBridge { if (guest && !guest.isDestroyed()) { return guest } - // Why: the stored webContentsId may be stale after a Chromium process swap - // (navigation to a different-origin page, crash recovery). Fall through to - // the auto-select logic rather than immediately failing, since the tab may - // still be alive under a new webContentsId. + // Why: webContentsId goes stale after a process swap; fall through to auto-select since the tab may have a new id. this.activeWebContentsId = null } @@ -1096,11 +1058,7 @@ export class CdpBridge { } private getRegisteredTabs(): Map { - // Why: BrowserManager's tab maps are private. We access the singleton's - // state via the public getGuestWebContentsId method by iterating known tabs. - // This method provides the tab enumeration the CDP bridge needs without - // modifying BrowserManager's encapsulation. In the future a public - // listTabs() method on BrowserManager would be cleaner. + // Why: reach into BrowserManager's private tab map since it exposes no public listTabs(). return (this.browserManager as unknown as { webContentsIdByTabId: Map }) .webContentsIdByTabId } @@ -1176,9 +1134,7 @@ export class CdpBridge { } try { - // Why: browser tabs are already debugger-attached by BrowserManager's - // anti-detection injection. Reusing that attachment lets CDP commands - // run instead of failing with "another debugger is already attached." + // Why: BrowserManager already attached the debugger; reuse it to avoid "another debugger is already attached." if (!guest.debugger.isAttached()) { guest.debugger.attach('1.3') } @@ -1194,24 +1150,19 @@ export class CdpBridge { await sender('DOM.enable') await sender('Network.enable') - // Why: cross-origin iframes run in separate renderer processes (OOPIFs) and - // are invisible to the parent's CDP session. setAutoAttach with flatten:true - // gives each OOPIF its own sessionId that we can target with sendCommand. + // Why: OOPIF iframes are invisible to the parent CDP session; flatten:true gives each a targetable sessionId. await sender('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: false, flatten: true }) - // Why: attaching the CDP debugger sets navigator.webdriver = true and - // exposes other automation signals that Cloudflare Turnstile checks. - // Override them on every new document load so challenges succeed. + // Why: CDP attach exposes automation signals (navigator.webdriver) that Cloudflare checks; override per new document. await sender('Page.addScriptToEvaluateOnNewDocument', { source: ANTI_DETECTION_SCRIPT }) - // Why: only remove CDP bridge listeners from the previous attach cycle. - // Screencast/proxy sessions share this debugger and own their teardown hooks. + // Why: only remove this bridge's listeners; screencast/proxy sessions share the debugger and own their teardown. this.removeDebuggerListeners(guest, state) const detachListener = (): void => { @@ -1226,8 +1177,7 @@ export class CdpBridge { state.snapshotResult = null state.navigationId = null } - // Why: an unhandled alert/confirm/prompt blocks ALL subsequent CDP commands. - // Auto-dismissing prevents the session from hanging indefinitely. + // Why: an unhandled JS dialog blocks all subsequent CDP commands; auto-dismiss to avoid hanging. if (method === 'Page.javascriptDialogOpening') { const dialog = params as { type: string; message: string } | undefined guest.debugger @@ -1236,8 +1186,7 @@ export class CdpBridge { }) .catch(() => {}) } - // Why: track cross-origin iframe sessions so we can route CDP commands - // and accessibility tree queries to the correct session. + // Why: track iframe sessions so CDP commands and AX queries route to the correct session. if (method === 'Target.attachedToTarget') { const p = params as | { @@ -1247,7 +1196,6 @@ export class CdpBridge { | undefined if (p?.sessionId && p.targetInfo?.type === 'iframe' && p.targetInfo.targetId) { state.iframeSessions.set(p.targetInfo.targetId, p.sessionId) - // Enable domains on iframe session guest.debugger.sendCommand('DOM.enable', {}, p.sessionId).catch(() => {}) guest.debugger.sendCommand('Accessibility.enable', {}, p.sessionId).catch(() => {}) guest.debugger.sendCommand('Runtime.enable', {}, p.sessionId).catch(() => {}) @@ -1264,8 +1212,7 @@ export class CdpBridge { } } } - // Why: console and network events are buffered per-tab so the agent can - // retrieve them on demand via the capture commands. + // Why: buffer console/network events per-tab so the agent can retrieve them on demand. if (state.capturing) { if (method === 'Runtime.consoleAPICalled') { const p = params as @@ -1314,8 +1261,7 @@ export class CdpBridge { timestamp: p.timestamp ?? Date.now() } state.networkLog.push(entry) - // Why: track requestId→entry so loadingFinished can attribute - // size to the correct response, not just the most recent one. + // Why: map requestId→entry so loadingFinished attributes size to the right response, not the latest one. if (p.requestId) { state.networkRequestMap.set(p.requestId, entry) } @@ -1343,8 +1289,7 @@ export class CdpBridge { } } } - // Why: request interception buffers paused requests so the agent can - // inspect and decide to continue or block them. + // Why: buffer paused requests so the agent can later inspect and continue or block them. if (state.intercepting && method === 'Fetch.requestPaused') { const p = params as | { @@ -1376,10 +1321,7 @@ export class CdpBridge { private makeCdpSender(guest: Electron.WebContents, sessionId?: string): CdpCommandSender { return (method: string, params?: Record) => { const command = guest.debugger.sendCommand(method, params, sessionId) as Promise - // Why: Electron's CDP sendCommand can hang indefinitely if the debugger - // session is stale (e.g. after a renderer process swap that wasn't detected). - // A 10s timeout prevents the RPC from blocking until the CLI's socket timeout. - // The timer is cancelled on success/failure to avoid dangling timer accumulation. + // Why: Electron's CDP sendCommand can hang on a stale debugger session, so a 10s timeout bounds the RPC. let timer: ReturnType return Promise.race([ command.finally(() => clearTimeout(timer)), @@ -1421,9 +1363,7 @@ export class CdpBridge { ) } - // Why: iframe refs use a child session whose navigation history is independent - // of the parent page. Checking the parent's navId against an iframe session - // would always mismatch and falsely reject valid refs. + // Why: iframe refs use a child session with independent nav history, so a parent-navId check would falsely reject them. if (!entry.sessionId) { const currentNavId = await this.getNavigationId(sender) if (state.navigationId && currentNavId !== state.navigationId) { @@ -1441,10 +1381,7 @@ export class CdpBridge { await refSender('DOM.describeNode', { backendNodeId: entry.backendDOMNodeId }) return entry } catch { - // Why: dynamic pages (React, Vue) re-render DOM nodes frequently. A ref - // from a snapshot taken seconds ago may point to a detached node even though - // an identical element exists. Re-query the AX tree to find the fresh node - // by matching role + name, avoiding a wasted retry for the agent. + // Why: dynamic pages re-render nodes, detaching snapshot refs; re-query the AX tree by role+name for the fresh node. const recovered = await this.tryRecoverRef(refSender, entry) if (recovered) { entry.backendDOMNodeId = recovered @@ -1480,10 +1417,7 @@ export class CdpBridge { return { cx: (x1 + x3) / 2, cy: (y1 + y3) / 2 } } - // Why: elements inside cross-origin iframes report coordinates relative to - // the iframe viewport, but Input.dispatchMouseEvent operates on the parent - // page's coordinate space. We find the iframe element on the parent page and - // add its top-left offset to translate iframe-local coords to page coords. + // Why: cross-origin iframes report iframe-local coords, but Input events use parent-page space; add the iframe offset. private async getIframeOffset( guest: Electron.WebContents, sessionId: string @@ -1495,9 +1429,7 @@ export class CdpBridge { for (const [targetId, sid] of state.iframeSessions) { if (sid === sessionId) { try { - // Why: use Target.getTargetInfo to get the iframe's URL, then match it - // against DOM iframe src attributes to find the correct element's position. - // This correctly handles pages with multiple iframes. + // Why: match the iframe's target URL against DOM iframe src to pick the right element on multi-iframe pages. const { targetInfo } = (await parentSender('Target.getTargetInfo', { targetId })) as { targetInfo: { url?: string } } @@ -1526,8 +1458,7 @@ export class CdpBridge { return { offsetX: rect.x, offsetY: rect.y } } } - // Why: iframe may have redirected after load, making src differ - // from the actual target URL. Match by origin as a fallback. + // Why: iframe may redirect after load so src differs from target URL; match by origin as a fallback. try { const targetOrigin = new URL(targetUrl).origin for (const rect of rects) { @@ -1554,8 +1485,7 @@ export class CdpBridge { return { offsetX: 0, offsetY: 0 } } - // Why: for elements inside iframes, translates iframe-local coordinates to - // page-level coordinates that Input.dispatchMouseEvent expects. + // Why: Input.dispatchMouseEvent uses parent-page coords, so translate iframe-local coords for iframe elements. private async getPageCoordinates( guest: Electron.WebContents, refEntry: RefEntry, @@ -1569,9 +1499,7 @@ export class CdpBridge { return { cx: localCx + offsetX, cy: localCy + offsetY } } - // Why: uses nth-index to pick the correct duplicate when multiple elements - // share the same role+name. Without this, recovery always returns the first - // match which may not be the element the ref originally pointed to. + // Why: nth-index disambiguates duplicate role+name matches so recovery hits the original element, not the first match. private async tryRecoverRef(sender: CdpCommandSender, entry: RefEntry): Promise { try { const { nodes } = (await sender('Accessibility.getFullAXTree')) as { @@ -1626,9 +1554,7 @@ export class CdpBridge { } private async waitForLoad(sender: CdpCommandSender, guest: Electron.WebContents): Promise { - // Why: wait for document.readyState=complete first, then wait for network - // idle (no pending requests for 500ms). This handles SPAs that fire 'load' - // before async content is rendered. + // Why: SPAs fire 'load' before async content renders, so also wait for 500ms of network idle. const TIMEOUT_MS = 25_000 const IDLE_MS = 500 const startedAt = Date.now() @@ -1792,9 +1718,7 @@ export class CdpBridge { } } -// Why: CDP's Input.dispatchKeyEvent requires `windowsVirtualKeyCode` (not `keyCode`) -// and `text` for keys that produce default browser actions (Enter submits forms, -// Tab moves focus). Without `text`, Chrome fires the event but doesn't perform the action. +// Why: Input.dispatchKeyEvent needs `text` for keys with default actions (Enter/Tab), or Chrome skips the action. type KeyDefinition = { key: string code: string @@ -1823,10 +1747,7 @@ function resolveKeyDefinition(key: string): KeyDefinition { if (KEY_DEFINITIONS[key]) { return KEY_DEFINITIONS[key] } - // Why: single characters need proper code values — digits use "DigitN", - // letters use "KeyX", and other characters use their char code for the - // windowsVirtualKeyCode. Invalid codes cause events to be dropped by sites - // that check event.code. + // Why: sites that check event.code drop events with invalid code values. if (key.length === 1) { const charCode = key.charCodeAt(0) if (charCode >= 48 && charCode <= 57) { diff --git a/src/main/browser/grab-guest-script.ts b/src/main/browser/grab-guest-script.ts index 0613d7319eb..8841fb6cf97 100644 --- a/src/main/browser/grab-guest-script.ts +++ b/src/main/browser/grab-guest-script.ts @@ -1,20 +1,6 @@ -/* eslint-disable max-lines -- Why: the guest overlay runtime is a single -self-contained JS string template that must be injected atomically into the -guest page. Splitting it across modules would require a string concatenation -build step that adds complexity without improving auditability. */ -// --------------------------------------------------------------------------- -// Browser Context Grab — guest overlay runtime builder -// -// This module produces self-contained JavaScript strings that main injects into -// browser guests via executeJavaScript(). The guest runtime is intentionally -// ephemeral: it installs on arm, resolves once on finalize, and fully removes -// itself on teardown. -// -// Why a string builder rather than a bundled file: Orca's browser guests have -// no preload and no Node access. The injected code must be a plain JS string -// that runs in the page's own world. Keeping it as a template here lets main -// version it alongside the rest of the grab lifecycle. -// --------------------------------------------------------------------------- +/* eslint-disable max-lines -- the guest overlay runtime is one self-contained JS string injected atomically; splitting it adds a concat build step for no auditability gain. */ +// Browser Context Grab — builds self-contained JS strings injected into guests via executeJavaScript(). +// Why a string builder not a bundle: guests have no preload/Node; injected code must be plain JS in the page's own world. type GuestScriptAction = 'arm' | 'awaitClick' | 'finalize' | 'extractHover' | 'teardown' @@ -42,10 +28,7 @@ export function buildGuestOverlayScript(action: GuestScriptAction): string { } } -// --------------------------------------------------------------------------- -// The arm script installs the overlay container and hover tracking. -// It stores state on window.__orcaGrab so finalize/teardown can access it. -// --------------------------------------------------------------------------- +// arm: install the overlay + hover tracking; state lives on window.__orcaGrab so finalize/teardown can reach it. const ARM_SCRIPT = `(function() { 'use strict'; @@ -839,11 +822,7 @@ const ARM_SCRIPT = `(function() { return true; })()` -// --------------------------------------------------------------------------- -// The awaitClick script returns a Promise that resolves when the user clicks -// on the full-viewport overlay. The click never reaches the page because the -// overlay host has pointer-events:all and the handler calls stopPropagation. -// --------------------------------------------------------------------------- +// awaitClick: resolve when the user clicks the overlay; stopPropagation + pointer-events:all keep the click off the page. const AWAIT_CLICK_SCRIPT = `new Promise(function(resolve, reject) { 'use strict'; var grab = window.__orcaGrab; @@ -919,9 +898,6 @@ const AWAIT_CLICK_SCRIPT = `new Promise(function(resolve, reject) { }; })` -// --------------------------------------------------------------------------- -// The finalize script extracts the payload for the currently hovered element. -// --------------------------------------------------------------------------- const FINALIZE_SCRIPT = `(function() { 'use strict'; var grab = window.__orcaGrab; @@ -939,12 +915,7 @@ const FINALIZE_SCRIPT = `(function() { return payload; })()` -// --------------------------------------------------------------------------- -// The extractHover script reads the payload for the currently hovered element -// WITHOUT cleaning up. The overlay and awaitClick listener stay active so the -// user can continue picking elements. Used by keyboard shortcuts (C/S) that -// copy the hovered element without requiring a click first. -// --------------------------------------------------------------------------- +// extractHover: read payload but keep overlay/listeners active so the user can keep picking (C/S shortcut copy, no click). const EXTRACT_HOVER_SCRIPT = `(function() { 'use strict'; var grab = window.__orcaGrab; @@ -958,9 +929,6 @@ const EXTRACT_HOVER_SCRIPT = `(function() { } })()` -// --------------------------------------------------------------------------- -// The teardown script removes the overlay and cleans up all state. -// --------------------------------------------------------------------------- const TEARDOWN_SCRIPT = `(function() { 'use strict'; var grab = window.__orcaGrab; diff --git a/src/main/claude-accounts/runtime-auth-service.ts b/src/main/claude-accounts/runtime-auth-service.ts index 72a29cfa6a9..3d901f872c0 100644 --- a/src/main/claude-accounts/runtime-auth-service.ts +++ b/src/main/claude-accounts/runtime-auth-service.ts @@ -1,6 +1,4 @@ -/* eslint-disable max-lines -- Why: Claude account switching has one safety -boundary: runtime auth materialization. Keeping file, Keychain, snapshot, and -env-patch semantics together prevents PTY launch and quota fetch paths drifting. */ +/* eslint-disable max-lines -- Why: keeps file/Keychain/snapshot/env-patch auth semantics together so PTY launch and quota-fetch paths can't drift. */ import { execFileSync } from 'node:child_process' import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs' import { dirname, join } from 'node:path' @@ -98,10 +96,7 @@ export class ClaudeRuntimeAuthService { private readonly pathResolver = new ClaudeRuntimePathResolver() private mutationQueue: Promise = Promise.resolve() private lastSyncedAccountId: string | null = null - // Why: tracks the credentials Orca last wrote to the shared credentials file. - // On managed→system-default transition, if the file differs from this value, - // an external login (e.g. `claude auth login`) overwrote it — so Orca adopts - // the file as the new system default instead of restoring a stale snapshot. + // Why: creds Orca last wrote to the shared file; a mismatch on managed→default transition means an external login overwrote it, so adopt it as the new default. private lastWrittenCredentialsJson: string | null = null private hasMaterializedRuntimeAuth = false private hasLastWrittenOauthAccount = false @@ -217,17 +212,13 @@ export class ClaudeRuntimeAuthService { previousManagedOauthAccount ) ) { - // Why: switching away while Claude is live must preserve verified - // token refreshes before replacing the shared runtime credentials. + // Why: switching away while Claude is live must preserve verified token refreshes before replacing shared runtime credentials. await this.writeManagedCredentials( previousAccount, outgoingReadBackResult.runtimeCredentialsJson ) } else { - // Why: Claude's runtime credential blob can lack enough identity - // proof to attribute a live-session refresh. Do not persist that - // unverified blob, but also do not block the user from moving new - // terminals to the selected managed account. + // Why: the runtime blob may lack identity proof for a live-session refresh; skip persisting it, but still let new terminals move to the account. console.warn( '[claude-runtime-auth] Skipping unverified live Claude auth read-back while switching accounts' ) @@ -297,9 +288,7 @@ export class ClaudeRuntimeAuthService { }) return } - // Why: WSL managed Claude accounts are already isolated by their Linux - // CLAUDE_CONFIG_DIR. Materializing them into Windows ~/.claude would mix - // two runtime auth stores and break the Terminal-default runtime contract. + // Why: WSL managed accounts are isolated by their Linux CLAUDE_CONFIG_DIR; materializing into Windows ~/.claude would mix two auth stores. this.clearLastWrittenRuntimeState() return } @@ -364,10 +353,7 @@ export class ClaudeRuntimeAuthService { ) } - // Why: Claude CLI refreshes expired OAuth tokens and writes them back to - // .credentials.json. If we detect the runtime file differs from what Orca - // last wrote, the CLI must have refreshed — so we preserve those tokens - // back to managed storage before overwriting runtime with managed state. + // Why: the CLI writes refreshed tokens to .credentials.json; if runtime differs from our last write, preserve them to managed storage before overwriting. if (this.lastSyncedAccountId === activeAccount.id) { if (this.skipNextReadBackForAccountId === activeAccount.id) { this.skipNextReadBackForAccountId = null @@ -383,9 +369,7 @@ export class ClaudeRuntimeAuthService { } else if ( readBackResult.status === 'rejected' && readBackResult.runtimeCredentialsChanged && - // Why: a live Claude that lost a refresh-token race wipes its runtime - // blob (empty tokens). Preserving that wreckage would leave every new - // session logged out — rematerialize managed credentials instead. + // Why: a live Claude that lost a refresh race can wipe its runtime blob (empty tokens); preserving that would log out every new session. readBackResult.hasValidChangedRuntimeCredentials && hasLiveClaudePtys() ) { @@ -398,13 +382,11 @@ export class ClaudeRuntimeAuthService { this.readManagedOauthAccount(activeAccount) ) ) { - // Why: this Claude process was launched under the active managed - // account, but persistence still needs positive account proof. + // Why: this Claude launched under the active managed account, but persistence still needs positive account proof. await this.writeManagedCredentials(activeAccount, readBackResult.runtimeCredentialsJson) credentialsJson = readBackResult.runtimeCredentialsJson } else { - // Why: while Claude is running, an unknown refresh can still belong - // to a live session. Rewriting stale managed auth logs that session out. + // Why: while Claude runs, an unknown refresh may belong to a live session; rewriting stale managed auth logs it out. console.warn( '[claude-runtime-auth] Preserving changed Claude runtime credentials while live Claude terminals are running' ) @@ -420,14 +402,7 @@ export class ClaudeRuntimeAuthService { this.skipNextReadBackForAccountId = null } - // Why: own the OAuth refresh whenever no live `claude` owns these - // credentials — both switching into an account and re-syncing the active - // account with an expired token. A single-use refresh token is rotated and - // persisted to managed storage atomically before we materialize it, so the - // runtime never gets a stale token that fails with invalid_grant. Skipped - // entirely while a Claude PTY is live: that process owns the credentials - // and refreshing here would race its own rotation (double-rotation - // invalidates one copy) — the read-back above preserves its refresh instead. + // Why: rotate+persist the single-use token to managed storage before materializing (else runtime gets a stale token that fails invalid_grant); skip while a live PTY owns the creds since refreshing would double-rotate it (invalidating one copy) — read-back preserves its refresh instead. const liveClaudePtys = hasLiveClaudePtys() if (liveClaudePtys && isOauthTokenExpiring(credentialsJson)) { this.managedRefreshDeferredByLivePtyAccountId = activeAccount.id @@ -445,8 +420,7 @@ export class ClaudeRuntimeAuthService { const paths = this.pathResolver.getRuntimePaths() this.writeRuntimeCredentials(credentialsJson) if (process.platform === 'darwin') { - // Why: Claude Code 2.1+ reads the scoped service, while older builds read - // the legacy unsuffixed service. Runtime switching must satisfy both. + // Why: Claude Code 2.1+ reads the scoped service, older builds the legacy unsuffixed one; runtime switching must satisfy both. try { await writeActiveClaudeKeychainCredentialsForRuntime(credentialsJson, paths.configDir) } catch (error) { @@ -469,10 +443,7 @@ export class ClaudeRuntimeAuthService { this.hasMaterializedRuntimeAuth = true } - // Why: called by ClaudeAccountService before syncForCurrentSelection() after - // re-auth or add-account. Those flows write fresh tokens to managed storage, - // so the read-back must be skipped to avoid overwriting them with stale - // runtime tokens. + // Why: re-auth/add-account write fresh managed tokens; skip the next read-back so stale runtime tokens can't overwrite them. clearLastWrittenCredentialsJson( accountId = this.store.getSettings().activeClaudeManagedAccountId ): void { @@ -526,14 +497,7 @@ export class ClaudeRuntimeAuthService { if (match.kind !== 'matched') { continue } - // Why: on cold app start we cannot tell whether matching runtime - // credentials are a fresh CLI refresh or stale state. Adopt when the - // token expiry proves runtime is newer, OR the refresh token rotated - // and runtime is not provably older. A rotated refresh token with - // equal/missing expiry is a genuine CLI refresh we'd otherwise drop - // (stranding a stale managed token); but if expiry proves runtime is - // older, managed already holds the newer token (e.g. a prior read-back - // or proactive refresh), so reject it. + // Why: on cold start we can't tell a fresh CLI refresh from stale runtime creds; adopt only when expiry or a rotated refresh token proves runtime is newer than managed. if (this.lastWrittenCredentialsJson === null) { const fresher = this.runtimeCredentialsAreFresher( runtimeContents.credentialsJson, @@ -587,16 +551,13 @@ export class ClaudeRuntimeAuthService { } return { status: 'persisted' } } catch (error) { - // Why: read-back is best-effort. A transient fs error must not block the - // forward sync path — the worst case is one more stale-token cycle, which - // is strictly better than failing the entire sync. + // Why: read-back is best-effort; a transient fs error must not block forward sync (worst case: one more stale-token cycle). console.warn('[claude-runtime-auth] Failed to read back refreshed tokens:', error) return { status: 'rejected', runtimeCredentialsChanged: this.runtimeCredentialsChangedSinceLastWrite(baselineCredentialsJson), - // Why: an fs error hides whether a live session's refresh is present, - // so err toward preserving runtime state like before. + // Why: an fs error hides whether a live session's refresh is present, so err toward preserving runtime state. hasValidChangedRuntimeCredentials: true } } @@ -723,8 +684,7 @@ export class ClaudeRuntimeAuthService { settings = this.store.getSettings() ): ClaudeAccountSelectionTarget { if (process.platform === 'win32' && settings.localAccountRuntime === 'wsl') { - // Why: account auth defaults follow account runtime settings, not hidden - // legacy terminal WSL settings that can outlive the Terminal UI control. + // Why: auth defaults follow account runtime settings, not legacy terminal WSL settings that can outlive the Terminal UI. return { runtime: 'wsl', wslDistro: settings.localAccountWslDistro ?? null } } return { runtime: 'host' } @@ -799,9 +759,7 @@ export class ClaudeRuntimeAuthService { return 'mismatch' } - // Why: this mirrors the Codex runtime-home guard. If another Claude login - // or missed live process rewrites shared runtime credentials, do not - // persist those credentials into the selected managed account. + // Why: mirrors the Codex runtime-home guard; don't persist shared runtime creds into the managed account if another login rewrote them. const selectedOrganizationUuid = this.normalizeField( account.organizationUuid ?? managedIdentity?.organizationUuid ?? @@ -1075,14 +1033,11 @@ export class ClaudeRuntimeAuthService { /** * Proactively refresh an account's OAuth token and persist the rotation to - * managed storage. Returns the refreshed credentials JSON when a rotation was - * stored, or null when no refresh happened (token still valid, no refresh - * token, or the network call failed — in which case the caller keeps the - * existing credentials, never worse than before). + * managed storage. Returns the refreshed credentials JSON, or null when no + * refresh happened (token valid, no refresh token, or network failure). * - * Caller guarantees this account is not the live/active one and runs inside - * the serialized mutation queue, so a single-use refresh token can't be - * rotated concurrently. + * Caller guarantees this account isn't the live/active one and runs inside the + * serialized mutation queue, so the single-use refresh token can't rotate concurrently. */ private async refreshManagedAccountTokenIfNeeded( account: ClaudeManagedAccount, @@ -1271,9 +1226,7 @@ export class ClaudeRuntimeAuthService { previouslyWrittenCredentialsJson ) let hasCredentialSurfaceOwnership = fileCredentialsOwned - // Why: runtime auth restore is two-phase: prove ownership before mutating - // any surface, then restore OAuth first. If OAuth fails, the credential - // proof remains intact for retry. + // Why: prove ownership before mutating anything, and restore OAuth first so a failure leaves the credential proof intact for retry. this.lastWrittenCredentialsJson = previouslyWrittenCredentialsJson let scopedSnapshot: ClaudeKeychainSnapshotValue | null = null let legacySnapshot: ClaudeKeychainSnapshotValue | null = null @@ -1326,9 +1279,7 @@ export class ClaudeRuntimeAuthService { if (this.hasLastWrittenOauthAccount) { return this.lastWrittenOauthAccount } - // Why: persisted managed metadata is an account identity hint, not proof - // that Orca wrote .claude.json. Use it only after another surface proves - // the current runtime auth still belongs to the managed account. + // Why: managed metadata hints identity but isn't proof Orca wrote .claude.json; use only after a credential surface proves ownership. if (hasCredentialSurfaceOwnership && ownedOauthAccount !== undefined) { return ownedOauthAccount } @@ -1779,10 +1730,7 @@ export class ClaudeRuntimeAuthService { private writeRuntimeCredentials(contents: string): void { const credentialsPath = this.pathResolver.getRuntimePaths().credentialsPath mkdirSync(dirname(credentialsPath), { recursive: true }) - // Why: repeated Claude spawns sync auth, but credentials rarely change. - // Skipping unchanged rewrites avoids Windows EPERM contention in #1507. - // Still verify the file because another Claude process may have rewritten - // runtime credentials since Orca last materialized them. + // Why: skip unchanged rewrites to dodge Windows EPERM contention (#1507); re-verify the file since another Claude may have rewritten it. if ( this.lastWrittenCredentialsJson === contents && this.fileContentsEqual(credentialsPath, contents) @@ -1838,8 +1786,7 @@ export class ClaudeRuntimeAuthService { return parsed as Record } } catch { - // Why: invalid config is an unknown external state. Replacing it with a - // fresh object could silently erase user or Claude-owned settings. + // Why: invalid config is unknown external state; return null so we don't erase user or Claude-owned settings. return null } return null diff --git a/src/main/claude/hook-service.ts b/src/main/claude/hook-service.ts index 980c9604bec..95304f19fc6 100644 --- a/src/main/claude/hook-service.ts +++ b/src/main/claude/hook-service.ts @@ -54,24 +54,14 @@ function getManagedScript( 'setlocal', ...(options.skipWhenDevinImportsClaude ? [ - // Why: Devin imports .claude hooks by default. Skip Orca's managed - // Claude hook there so status posts stay attributed to Devin. + // Why: Devin imports .claude hooks by default; skip Orca's managed hook there so status posts stay attributed to Devin. `if not "%DEVIN_PROJECT_DIR%"=="" goto :${WINDOWS_HOOK_STDIN_DRAIN_LABEL}` ] : []), - // Why: the endpoint file holds the *live* port/token for this Orca - // install. A PTY that survived an Orca restart has stale PORT/TOKEN - // baked into its env from the old instance — loading `endpoint.cmd` - // (`set KEY=VALUE` lines) via `call` refreshes them so the hook - // reaches the current server. Falls through to PTY env if the file - // is missing (first run / pre-endpoint-file / running outside Orca). + // Why: call the endpoint file to refresh port/token — a PTY that survived an Orca restart carries stale env; falls through to PTY env if missing. 'if defined ORCA_AGENT_HOOK_ENDPOINT if exist "%ORCA_AGENT_HOOK_ENDPOINT%" call "%ORCA_AGENT_HOOK_ENDPOINT%" 2>nul', ...buildWindowsHookEnvironmentGuardLines(), - // Why: post via curl.exe, not a second PowerShell. Claude's launcher is - // already an encoded PowerShell command (Git Bash needs it to survive - // spaces); a PowerShell post on top of that meant two interpreter - // startups per hook. The post runs inside the .cmd (cmd.exe context), so - // curl works the same here as for the POSIX/Codex hooks. + // Why: post via curl.exe, not PowerShell — Claude's launcher is already encoded PowerShell, so a PS post would double interpreter startups per hook. buildWindowsAgentHookCurlPostCommand('claude'), 'exit /b 0', ...buildWindowsHookStdinDrainEpilogue(), @@ -84,40 +74,22 @@ function getManagedScript( ...buildPosixHookPayloadCapture(), ...(options.skipWhenDevinImportsClaude ? [ - // Why: Devin imports .claude hooks by default. Skip Orca's managed - // Claude hook there so status posts stay attributed to Devin. + // Why: Devin imports .claude hooks by default; skip Orca's managed hook there so status posts stay attributed to Devin. 'if [ -n "$DEVIN_PROJECT_DIR" ]; then', ' exit 0', 'fi' ] : []), - // Why: the endpoint file holds the *live* port/token for this Orca - // install. PTYs that survive an Orca restart have stale PORT/TOKEN - // baked into their env from the old instance — sourcing the file here - // lets us reach the new server. Falls back to PTY env if the file is - // missing (first-run / pre-endpoint-file scripts / running outside Orca). - // Why: suppress stderr on the `.` builtin. A TOCTOU race (endpoint unlinked - // between the `[ -r ]` test and the source) or a malformed line (e.g. CRLF - // bled in from a cross-platform userData copy) would otherwise print a - // parse error that agent transcripts could surface. Stale coords → dead - // port → silent-fail is the documented fail-open path anyway — the env-var - // guards below handle the empty PORT/TOKEN case — so swallowing the noise - // here is strictly better than leaking shell errors into the hook output. - // `|| :` defends against an eventual `set -e` in an outer script context - // (not present today) aborting the hook on a parse error. + // Why: source the endpoint file to refresh port/token — a PTY that survived an Orca restart carries stale env; falls back to PTY env if missing. + // Why: suppress stderr / || : so a stray parse error (TOCTOU or CRLF) can't leak into hook output or trip an outer set -e. 'if [ -n "$ORCA_AGENT_HOOK_ENDPOINT" ] && [ -r "$ORCA_AGENT_HOOK_ENDPOINT" ]; then', ' . "$ORCA_AGENT_HOOK_ENDPOINT" 2>/dev/null || :', 'fi', 'if [ -z "$ORCA_AGENT_HOOK_PORT" ] || [ -z "$ORCA_AGENT_HOOK_TOKEN" ] || [ -z "$ORCA_PANE_KEY" ]; then', ' exit 0', 'fi', - // Why: worktreeId embeds a filesystem path, so hand-building JSON in POSIX - // shell is not safe once a path contains quotes or newlines. Post the raw - // hook payload plus metadata as form fields and let the receiver parse it. - // Timeout caps best-effort hook posts if the local listener stalls. - // Why: pipe payload to curl's stdin (`payload@-`) instead of an inline - // `payload=$VALUE` arg, so tens-of-KB tool output stays off the curl - // command line (EDR command-line false positives). Wire body is identical. + // Why: paths can hold quotes/newlines, so hand-building JSON in shell is unsafe; post the raw payload + metadata as form fields for the receiver to parse. + // Why: pipe payload to curl stdin (`payload@-`), not an inline arg, so large tool output stays off the command line (EDR false positives). 'printf \'%s\' "$payload" | curl -sS -X POST "http://127.0.0.1:${ORCA_AGENT_HOOK_PORT}/hook/claude" \\', ' --connect-timeout 0.5 --max-time 1.5 \\', ' -H "Content-Type: application/x-www-form-urlencoded" \\', @@ -155,10 +127,7 @@ export class ClaudeHookService { } } - // Why: Report `partial` when only some managed events are registered so the - // sidebar surfaces a degraded install rather than a false-positive - // `installed`. Each CLAUDE_EVENTS entry must contain the managed command for - // the integration to function end-to-end. + // Why: report `partial` when only some events are registered so the sidebar shows a degraded install, not a false-positive `installed`. const command = getManagedCommand(scriptPath) const missing: string[] = [] let presentCount = 0 @@ -219,24 +188,13 @@ export class ClaudeHookService { return this.getStatus() } - // Why: install Orca's Claude hook settings on the remote box rather than the - // local machine. Caller passes the user's SFTP handle plus the resolved - // remote `$HOME`; POSIX-only by design (Windows-remote deferred). + // Why: install the Claude hook on the remote box (via SFTP); POSIX-only by design (Windows-remote deferred). async installRemote(sftp: SFTPWrapper, remoteHome: string): Promise { - // Why: remote-Windows is out of scope for v1 — we ship POSIX-shaped paths - // and a `.sh` managed script body. The remote platform is gated by the - // relay's capability RPC at a higher layer; we cannot detect it from - // `process.platform` here (that's the local box). + // Why: remote-Windows is out of scope; ship POSIX paths. process.platform here is the local box, not the remote, so it can't gate this. const remoteConfigPath = getRemoteConfigPath(remoteHome, this.options.settings) const remoteScriptFileName = getPosixManagedScriptFileName(this.options.settings) const remoteScriptPath = `${remoteHome.replace(/\/$/, '')}/.orca/agent-hooks/${remoteScriptFileName}` - // Why: SFTP reads/writes fail far more often than local fs (network drops, - // EACCES on remote dirs, disk full, channel closed). Wrap the entire - // install flow in try/catch so a transient I/O failure surfaces as a - // structured `state: 'error'` result for the UI, not an unstructured - // rejection the caller has to remember to handle. A `null` config - // specifically means "file present but unparseable" — keep that branch - // distinct so the user sees an actionable message. + // Why: SFTP I/O fails often (network/EACCES/disk); wrap install so transient failures surface as structured state:'error' rather than an unhandled rejection. try { const config = await readHooksJsonRemote(sftp, remoteConfigPath) if (!config) { @@ -249,19 +207,12 @@ export class ClaudeHookService { } } - // Why: the POSIX wrapper is identical regardless of where the script - // lands; only the path differs. Reuse the same wrapper helper. + // Why: the POSIX wrapper is identical regardless of where the script lands; only the path differs. const command = getRemoteManagedCommand(remoteScriptPath) const nextConfig = applyManagedHooks(config, command, remoteScriptFileName) - // Why: write the script first, then the settings — settings.json - // referencing a missing script body would fire `command not found` on - // every tool call until the user re-runs install. Doing it in this - // order means a partial-failure mid-install at worst leaves the user - // with a working script no settings.json points at (a no-op), instead - // of broken settings.json. - // Why: SSH remotes use POSIX `.sh` hook paths even when Orca itself is - // running on Windows; never derive remote script syntax from local OS. + // Why: write script before settings — a mid-install failure then leaves a harmless orphan script, not settings.json pointing at a missing one. + // Why: SSH remotes use POSIX `.sh` paths even when Orca runs on Windows; never derive remote script syntax from the local OS. await writeManagedScriptRemote( sftp, remoteScriptPath, diff --git a/src/main/cli/cli-installer.ts b/src/main/cli/cli-installer.ts index 431c04e917b..18d2fcc8f18 100644 --- a/src/main/cli/cli-installer.ts +++ b/src/main/cli/cli-installer.ts @@ -105,13 +105,7 @@ export class CliInstaller { this.processPathEnv = options.processPathEnv ?? process.env.PATH ?? process.env.Path ?? null this.commandPathOverride = options.commandPathOverride ?? process.env.ORCA_CLI_INSTALL_PATH ?? null - // Why: resolved once at construction — existsSync must not run on every - // getStatus() call (hot path). /usr/local/bin is absent by default on Apple - // Silicon Macs (Homebrew moved to /opt/homebrew); fall back to ~/.local/bin - // which is user-writable, requires no elevated permissions, and is the - // XDG-standard user bin dir already on PATH via shell init on arm64. - // defaultMacCommandPath is a test seam: it feeds into the existence check - // so tests can simulate arm64 without relying on the real /usr/local/bin. + // Why: resolved once here (getStatus is hot); /usr/local/bin is absent on Apple Silicon, so fall back to user-writable ~/.local/bin. const candidateMacPath = options.defaultMacCommandPath ?? DEFAULT_MAC_COMMAND_PATH this.macCommandPath = existsSync(dirname(candidateMacPath)) ? candidateMacPath @@ -202,21 +196,15 @@ export class CliInstaller { await this.installAppImageWrapper(status.commandPath, status.launcherPath) await this.removeLegacyLinuxCommandIfManaged(status.launcherPath) } else if (this.isWindowsPackagedBundledCommand(status.commandPath, status.launcherPath)) { - // Why: packaged Windows already ships resources/bin/orca.exe. Registration - // only owns the user PATH entry; rewriting the asset makes it recurse. + // Why: packaged Windows already ships resources/bin/orca.exe; registration only owns the PATH entry. } else { - // Why: mkdir stays here for the Windows wrapper path — the target dir is - // user-writable (%LOCALAPPDATA%) so EACCES cannot occur. The symlink path - // handles its own mkdir inside installSymlink so EACCES triggers the - // privileged-runner instead of propagating as an unhandled rejection. + // Why: the Windows wrapper dir is user-writable (%LOCALAPPDATA%), so mkdir here can't hit EACCES. await mkdir(dirname(status.commandPath), { recursive: true }) await this.installWindowsWrapper(status.commandPath, status.launcherPath) } if (this.platform === 'win32') { - // Why: Windows shells discover commands via the user PATH, not by walking - // arbitrary app install directories. The CLI installer therefore owns the - // user-scoped PATH entry instead of assuming the desktop installer did it. + // Why: Windows shells find commands via user PATH, so the installer owns that entry, not the desktop installer. await this.ensureWindowsPathEntry(dirname(status.commandPath)) } @@ -319,12 +307,10 @@ export class CliInstaller { const status = await this.inspectSymlink(commandPath, launcherPath) if (status.state !== 'not_installed') { if (reachedDefaultCommandPath && !isDefaultCommandPath && status.state === 'conflict') { - // Why: a non-Orca command after an empty/default install slot can be - // shadowed safely by installing there; no user file needs replacing. + // Why: a non-Orca command after an empty default slot can be shadowed by installing there; no user file replaced. continue } - // Why: PATH lookup is first-match-wins; use the executable command the - // shell will actually run, while preserving conflicts that shadow Orca. + // Why: PATH lookup is first-match-wins; return the command the shell will actually run, preserving shadowing conflicts. return commandPath } } @@ -345,8 +331,7 @@ export class CliInstaller { } if (!this.isPackaged) { - // Why: default dev registration is a separate command, while tests and - // diagnostics can still exercise production paths via commandPathOverride. + // Why: dev uses a separate command; tests/diagnostics still reach production paths via commandPathOverride. if (this.platform === 'darwin') { return `/usr/local/bin/${DEV_COMMAND_NAME}` } @@ -363,18 +348,13 @@ export class CliInstaller { } if (this.platform === 'linux') { - // Why: Linux does not have a single privileged global shell-command flow - // equivalent to macOS's /usr/local/bin integration. ~/.local/bin is the - // least surprising user-scoped location that many distros already expose. - // Why `orca-ide`: GNOME Orca (the screen reader) ships /usr/bin/orca on - // most Linux distros. Using `orca-ide` avoids shadowing that system - // command, matching the executableName already used for the Electron binary. + // Why: Linux lacks a privileged global command flow; ~/.local/bin is the least-surprising user-scoped dir. + // Why `orca-ide`: GNOME Orca ships /usr/bin/orca, so avoid shadowing that screen reader. return join(this.homePath, '.local', 'bin', LINUX_COMMAND_NAME) } if (this.platform === 'win32') { - // Why: NSIS /D installs can live outside LOCALAPPDATA. The packaged - // resources directory is the authoritative native launcher location. + // Why: NSIS /D installs can live outside LOCALAPPDATA, so use the packaged resources dir as authoritative. return getBundledLauncherPath(this.platform, this.resourcesPath) } @@ -412,10 +392,7 @@ export class CliInstaller { if (status.state === 'stale') { await unlink(status.commandPath as string) } - // Why: mkdir is placed here (not in install()) so that an EACCES/EPERM - // failure — e.g. /usr/local/bin absent on Intel Mac — falls into the - // privileged-runner catch below instead of surfacing as an unhandled - // rejection that leaves Settings silently showing "not installed". + // Why: mkdir stays here (not install()) so an EACCES falls into the privileged-runner catch below. await mkdir(dirname(status.commandPath as string), { recursive: true }) await symlink(status.launcherPath as string, status.commandPath as string) } catch (error) { @@ -423,10 +400,7 @@ export class CliInstaller { throw error } - // Why: macOS shell-command registration should behave like VS Code and - // place a stable symlink in /usr/local/bin instead of rewriting shell rc - // files. Fallback to an elevated shell command keeps the public command - // stable even when the app lacks direct write access to that directory. + // Why: fall back to an elevated shell to place the /usr/local/bin symlink (VS Code-style) when direct write is denied. await this.privilegedRunner( `mkdir -p ${quoteShell(dirname(status.commandPath as string))} && ` + `ln -sfn ${quoteShell(status.launcherPath as string)} ${quoteShell(status.commandPath as string)}` @@ -465,8 +439,7 @@ export class CliInstaller { return } - // Why: after the Linux command rename, the old Orca-owned `orca` symlink - // would keep shadowing GNOME Orca even though the new command is installed. + // Why: after the Linux command rename, the old `orca` symlink would keep shadowing GNOME Orca. await unlink(legacyCommandPath) } catch (error) { if (isMissingError(error)) { @@ -492,8 +465,7 @@ export class CliInstaller { return true } - // Why: AppImage upgrades can leave a legacy symlink into a now-gone FUSE - // mount; the stable AppImage path is not a sibling of that old target. + // Why: AppImage upgrades can strand a legacy symlink into a now-gone FUSE mount that isn't a sibling of the stable path. return /(?:^|[/\\])resources[/\\]bin[/\\]orca$/.test(resolvedTarget) } @@ -502,8 +474,7 @@ export class CliInstaller { } private async installAppImageWrapper(commandPath: string, appImagePath: string): Promise { - // Why: unlike macOS symlink install, AppImage uses the user-writable Linux - // command dir and must create it before writing the wrapper file. + // Why: the AppImage command dir is user-writable, so create it before writing the wrapper. await mkdir(dirname(commandPath), { recursive: true }) await writeFile(commandPath, buildAppImageCliWrapper(appImagePath), { encoding: 'utf8', @@ -644,8 +615,7 @@ export class CliInstaller { } if (this.platform === 'darwin') { - // Why: prior packaged installs can leave a symlink to an older Orca.app - // resources launcher, but arbitrary user-owned symlinks must not be replaced. + // Why: reclaim symlinks to an older Orca.app launcher, but never replace arbitrary user-owned symlinks. return /(?:^|[/\\])[^/\\]+\.app[/\\]Contents[/\\]Resources[/\\]bin[/\\][^/\\]+$/.test( resolvedTarget ) @@ -670,8 +640,7 @@ export class CliInstaller { const siblingDevUserDataPath = `${packagedUserDataPath}-dev` const siblingDevLauncherDir = resolve(siblingDevUserDataPath, ...DEV_LAUNCHER_DIR) - // Why: development builds generate launchers under the sibling `*-dev` - // profile; packaged Orca must be able to reclaim that public command. + // Why: dev builds generate launchers under the sibling `*-dev` profile; packaged Orca must reclaim that command. return ( basename(siblingDevUserDataPath) === `${basename(packagedUserDataPath)}-dev` && isPathInsideOrEqual(siblingDevLauncherDir, resolvedTarget) @@ -901,13 +870,11 @@ export class CliInstaller { if (result.state === 'success') { return { value: result.value, expandable: result.expandable } } - // Why: PATH updates are read-modify-write; continuing after an unknown read - // could replace the user's existing PATH with an incomplete value. + // Why: PATH is read-modify-write; continuing after a failed read could clobber the user's PATH with a partial value. throw new Error(`${result.detail} No PATH changes were made.`) } - // Why: raw PowerShell errors reach the UI, so translate denied PATH writes - // while preserving the original diagnostic as the error cause. + // Why: raw PowerShell errors reach the UI, so translate denied PATH writes (keeping the original as cause). private async writeWindowsUserPathEntry( value: string, pathDirectory: string, @@ -954,10 +921,7 @@ async function ensureDevLauncher(args: { ) await mkdir(dirname(launcherPath), { recursive: true }) - // Why: packaged Orca ships real platform launchers under resources/bin, but - // development builds do not have that stable asset layout. Generating a - // launcher in userData lets us validate the shell-command flow without - // changing the packaged registration contract. + // Why: dev builds lack the packaged resources/bin launcher, so generate one in userData to validate the flow. const content = args.platform === 'win32' ? buildWindowsDevLauncher(args.execPath, args.cliEntryPath, args.userDataPath) @@ -967,9 +931,7 @@ async function ensureDevLauncher(args: { mode: args.platform === 'win32' ? undefined : 0o755 }) if (args.commandName === DEV_COMMAND_NAME && args.platform !== 'win32') { - // Why: dev PTYs prepend userData/cli/bin to PATH, and product-owned - // commands are documented as `orca ...`. Keep that local alias fresh - // without claiming the global production command. + // Why: dev PTYs prepend this dir to PATH, so keep a local `orca` alias without claiming the global command. await writeFile(join(dirname(launcherPath), 'orca'), content, { encoding: 'utf8', mode: 0o755 @@ -1045,9 +1007,7 @@ function extractManagedUnixLauncherTarget(content: string): string | null { return null } - // Why: older dev installs wrote a generated shell launcher directly to - // /usr/local/bin/orca. Treat only Orca's compiled CLI entrypoints as managed; - // arbitrary user scripts that happen to launch Electron must stay conflicts. + // Why: only Orca's compiled CLI entrypoints count as managed; arbitrary Electron-launching scripts stay conflicts. return /(?:^|[/\\])(?:out|app\.asar\.unpacked[/\\]out)[/\\]cli[/\\]index\.js$/.test(cliPath) ? cliPath : null @@ -1153,8 +1113,7 @@ function isMissingError(error: unknown): boolean { ) } -// Why: localized permission errors retain these .NET/ACL markers even when -// their human-readable PowerShell text is mojibake. +// Why: localized permission errors keep these .NET/ACL markers even when the PowerShell text is mojibake. function isWindowsUserPathPermissionError(error: unknown): boolean { if (!(error instanceof Error)) { return false @@ -1199,9 +1158,7 @@ async function writeWindowsUserPath(value: string): Promise { await runWindowsPathCommand([ '-NoProfile', '-Command', - // Why: PATH registration must stay user-scoped on Windows so the Orca - // desktop app can manage the public shell command without requiring - // elevation or mutating machine-wide environment state. + // Why: user-scoped PATH avoids requiring elevation or mutating machine-wide state. `[Environment]::SetEnvironmentVariable('Path', ${quotePowerShell(value)}, 'User')` ]) } @@ -1224,8 +1181,7 @@ function runWindowsPathCommand(args: string[]): Promise { resolve(stdout) } - // Why: Windows PATH reads/writes back CLI Settings; wedged PowerShell must - // not keep command registration status or install/remove pending forever. + // Why: bound wedged PowerShell so PATH reads/writes can't leave CLI registration pending forever. const timeout = setTimeout(() => { child?.kill() finish(new Error(`Windows PATH command timed out after ${WINDOWS_PATH_WRITE_TIMEOUT_MS}ms.`)) diff --git a/src/main/codex-accounts/runtime-home-service.ts b/src/main/codex-accounts/runtime-home-service.ts index 29e181bd775..5c6e42f63d4 100644 --- a/src/main/codex-accounts/runtime-home-service.ts +++ b/src/main/codex-accounts/runtime-home-service.ts @@ -1,7 +1,4 @@ -/* eslint-disable max-lines -- Why: this service owns the single runtime-home -contract for Codex inside Orca. Keeping path resolution, system-default -snapshots, auth materialization, and recovery together prevents account-switch -semantics from drifting across PTY launch, login, and quota fetch paths. */ +/* eslint-disable max-lines -- Why: keeps Codex's whole runtime-home contract in one place so account-switch semantics don't drift across launch/login/quota paths. */ import { appendFileSync, copyFileSync, @@ -92,20 +89,11 @@ type CodexReadBackMatch = | { kind: 'none' | 'ambiguous' } export class CodexRuntimeHomeService { - // Why: tracks whether the runtime auth.json currently mirrors a managed - // account. When null, runtime auth follows the user's system-default - // ~/.codex/auth.json instead of being written back to a managed account. + // Which managed account runtime auth.json mirrors; null means it follows system-default ~/.codex instead of a managed account. private lastSyncedAccountId: string | null = null - // Why: tracks the auth.json content Orca last wrote to the runtime CODEX_HOME. - // Between syncs, if the file differs, Codex CLI refreshed the token — so - // Orca writes back the refreshed token to managed storage before overwriting. - // On managed→system-default transition, if the file differs, an external - // login (e.g. `codex auth login`) overwrote it — so Orca adopts the file as - // the new system default instead of restoring a stale snapshot. + // Last auth.json Orca wrote to the runtime home; a later diff signals an out-of-band change (Codex token refresh, or external login to adopt). private lastWrittenAuthJson: string | null = null - // Why: WSL terminals have their own stable runtime homes per distro. They - // cannot share the host baseline or host sync can make stale WSL auth look - // newer than managed storage. + // Why: WSL terminals have per-distro runtime homes; sharing the host baseline can make stale WSL auth look newer than managed storage. private readonly lastWrittenWslAuthJsonByDistro = new Map() private readonly lastSyncedWslAccountIdByDistro = new Map() private readonly wslRuntimeHomePathByDistro = new Map() @@ -124,20 +112,13 @@ export class CodexRuntimeHomeService { settings.codexManagedAccounts, normalizeCodexRuntimeSelection(settings).host ) - // Why: WSL-managed homes are never materialized into host ~/.codex. - // Treating one as "last synced" makes cold start look like a host-account - // transition and can restore/delete host auth that Orca never touched. + // Why: WSL-managed homes never touch host ~/.codex; treating one as "last synced" makes cold start mangle host auth Orca never touched. this.lastSyncedAccountId = this.getWslManagedHomePath(activeAccount) ? null : normalizeCodexRuntimeSelection(settings).host } - /** - * Materializes the runtime home needed before launching the CLI. - * - * Historical session bridging is requested in the background so launch setup - * returns as soon as the active runtime home is ready. - */ + /** Materializes the runtime home needed before launching the CLI (session bridging runs in the background so setup returns once it's ready). */ prepareForCodexLaunch(target?: CodexAccountSelectionTarget): string | null { if (target?.runtime === 'wsl') { const wslTarget = this.resolveWslDefaultTarget(target) @@ -150,8 +131,7 @@ export class CodexRuntimeHomeService { this.syncForCurrentSelection() syncSystemCodexResourcesIntoManagedHome() syncSystemConfigIntoManagedCodexHome() - // Why: historical Codex sessions can be large; bridge them after launch - // setup so starting a fresh Codex TUI never waits on a full tree walk. + // Why: sessions can be large; bridge them after launch so starting a fresh TUI never waits on a full tree walk. void startSystemCodexSessionBridgeInBackground( {}, resolveHostCodexSessionSourceHome(this.store.getSettings()) @@ -171,16 +151,14 @@ export class CodexRuntimeHomeService { if (!distro) { return } - // Why: history-only override lets custom-CODEX_HOME users bridge from their - // real home; falls back to /.codex, which auth/config still use. + // Why: history-only override lets custom-CODEX_HOME users bridge from their real home; falls back to /.codex. const systemCodexHomePath = resolveWslCodexSessionSourceHome(this.store.getSettings(), distro) ?? this.getWslSystemCodexHomePath({ runtime: 'wsl', wslDistro: distro }) if (!systemCodexHomePath || systemCodexHomePath === runtimeHomePath) { return } - // Why: WSL history must be hardlinked inside the distro; host-side links - // cannot bridge Windows and WSL filesystems in a resume-visible way. + // Why: WSL history must be hardlinked inside the distro; host-side links can't bridge Windows and WSL filesystems in a resume-visible way. void startWslCodexSessionBridgeInBackground({ distro, systemCodexHomePath, @@ -240,8 +218,7 @@ export class CodexRuntimeHomeService { if (!systemHomePath || systemHomePath === runtimeHomePath) { return } - // Why: WSL uses a distro-local CODEX_HOME, so host resource mirroring - // cannot provide the distro user's global instructions. + // Why: WSL uses a distro-local CODEX_HOME, so host resource mirroring can't provide the distro user's global instructions. syncCodexGlobalInstructionsIntoManagedHome({ systemHomePath, managedHomePath: runtimeHomePath @@ -313,10 +290,7 @@ export class CodexRuntimeHomeService { } }) } - // Why: only restore the system-default mirror when transitioning FROM a - // managed account. When no managed account was ever active, later syncs - // should mirror the user's current ~/.codex/auth.json instead of - // replaying an old snapshot on every PTY launch / rate-limit fetch. + // Why: only restore the system-default mirror when leaving a managed account; otherwise later syncs mirror current ~/.codex instead of replaying an old snapshot. if (this.lastSyncedAccountId !== null) { this.restoreSystemDefaultSnapshot({ detectExternalLogin: outgoingReadBackResult !== 'rejected' @@ -332,16 +306,12 @@ export class CodexRuntimeHomeService { ) { this.restoreSystemDefaultSnapshot({ detectExternalLogin: false }) } else if (logoutMarkerStatus.kind === 'system-default-changed') { - // Why: a real ~/.codex logout after a local runtime logout should - // keep runtime auth absent instead of restoring the stale snapshot. + // Why: a real ~/.codex logout after a local runtime logout should keep runtime auth absent, not restore the stale snapshot. this.captureSystemDefaultSnapshot({ force: true }) this.persistRuntimeLogoutMarker(null) this.lastWrittenAuthJson = null } else if (this.lastWrittenAuthJson === null) { - // Why: Orca-launched Codex sessions now use an Orca-owned CODEX_HOME - // even when no managed account is selected. Seed that runtime home - // from the user's current system-default auth once so dev/prod Orca - // terminals stay logged in without mutating ~/.codex on startup. + // Why: unmanaged sessions use an Orca-owned CODEX_HOME; seed it once from system-default auth so terminals stay logged in without mutating ~/.codex. this.restoreSystemDefaultSnapshot({ detectExternalLogin: false }) } else { this.persistRuntimeLogoutMarker() @@ -376,10 +346,7 @@ export class CodexRuntimeHomeService { this.captureSystemDefaultSnapshot({ force: true }) } - // Why: Codex CLI refreshes expired OAuth tokens in CODEX_HOME/auth.json. - // If we detect the runtime file differs from what Orca last wrote, the CLI - // must have refreshed — so we preserve those tokens back to managed - // storage before overwriting runtime with managed state. + // Why: Codex refreshes OAuth tokens in the runtime auth.json; if it differs from Orca's last write, read those back to managed storage before overwriting. if (this.lastSyncedAccountId === activeAccount.id) { if (this.skipNextReadBackForAccountId === activeAccount.id) { this.skipNextReadBackForAccountId = null @@ -397,10 +364,7 @@ export class CodexRuntimeHomeService { this.writeRuntimeAuth(readFileSync(activeAuthPath, 'utf-8')) } - // Why: called by CodexAccountService before syncForCurrentSelection() after - // re-auth or add-account. Those flows write fresh tokens to managed storage, - // so the read-back must be skipped to avoid overwriting them with stale - // runtime tokens. + // Why: re-auth/add-account write fresh managed tokens, so skip the next read-back to avoid clobbering them with stale runtime tokens. clearLastWrittenAuthJson( accountId = normalizeCodexRuntimeSelection(this.store.getSettings()).host ): void { @@ -463,8 +427,7 @@ export class CodexRuntimeHomeService { } return 'rejected' } - // Why: after app restart, Orca has no last-written baseline. Identity - // alone cannot prove runtime auth is newer than managed storage. + // Why: after restart there's no last-written baseline, so identity alone can't prove runtime auth is newer than managed storage. if ( lastWrittenAuthJson === null && !this.runtimeAuthIsFresher(runtimeContents, match.managedAuthContents) @@ -482,9 +445,7 @@ export class CodexRuntimeHomeService { } return 'persisted' } catch (error) { - // Why: read-back is best-effort. A transient fs error must not block the - // forward sync path — the worst case is one more stale-token cycle, which - // is strictly better than failing the entire sync. + // Why: read-back is best-effort; a transient fs error must not block the forward sync — worst case is one more stale-token cycle. console.warn('[codex-runtime-home] Failed to read back refreshed tokens:', error) return 'rejected' } @@ -534,9 +495,7 @@ export class CodexRuntimeHomeService { const settings = this.store.getSettings() const selectedAccountId = getSelectedCodexAccountIdForTarget(settings, target) if (selectedAccountId === null) { - // Why: the system-default account changes outside Orca (login, logout, - // token refresh). Read its real home directly so a cached runtime copy - // cannot stay stale; filesystem probing in the fetcher is asynchronous. + // Why: the system-default account changes outside Orca, so read its real home directly to avoid a stale cached runtime copy. return this.getWslSystemCodexHomePath(target) } const cachedRuntimeHomePath = this.wslRuntimeHomePathByDistro.get(distro) @@ -545,9 +504,7 @@ export class CodexRuntimeHomeService { this.lastSyncedWslAccountIdByDistro.has(distro) && this.lastSyncedWslAccountIdByDistro.get(distro) === selectedAccountId ) { - // Why: RateLimitService resolves provenance twice per poll. Account - // changes sync explicitly, so repeated resolution must stay path-only - // instead of blocking main on UNC reads and a wsl.exe migration probe. + // Why: RateLimitService resolves provenance twice per poll; stay path-only so it doesn't block main on UNC reads and a wsl.exe probe. return cachedRuntimeHomePath } } @@ -640,9 +597,7 @@ export class CodexRuntimeHomeService { (mirroredSystemDefaultAuth === null && this.runtimeAuthIsFresher(runtimeAuth, systemAuth))) ) { - // Why: WSL runtime homes are per-distro and their in-memory baseline is - // lost on app restart. A same-identity fresher runtime auth is a Codex - // token refresh and should be copied back before we mirror ~/.codex. + // Why: WSL baselines are lost on restart, so a same-identity fresher runtime auth is a token refresh; copy it back before mirroring ~/.codex. this.writeRuntimeAuthAtPath(systemAuthPath, runtimeAuth) this.lastWrittenWslAuthJsonByDistro.set(distro, runtimeAuth) this.lastSyncedWslAccountIdByDistro.set(distro, null) @@ -722,8 +677,7 @@ export class CodexRuntimeHomeService { ) const nextLinuxPath = `${activeLinuxPath}.next-${process.pid}-${Date.now()}` const activeLinuxParentPath = this.dirnameLinuxPath(activeLinuxPath) - // Why: WSL drops bash argv here and login-shell cleanup can turn explicit - // `exit 0` into status 1, so keep this script literal and fall-through. + // Why: WSL drops bash argv, so keep the script literal; login-shell cleanup turns `exit 0` into status 1, so fall through. execFileSync( 'wsl.exe', [ @@ -844,10 +798,7 @@ export class CodexRuntimeHomeService { } const managedIdentity = this.readIdentityFromAuthContents(managedAuthContents) - // Why: old live Codex PTYs can still write refreshed tokens into the - // shared runtime home after the user switches accounts. Never persist - // that write into the newly active managed account unless the auth claims - // still match the account Orca believes is selected. + // Why: old PTYs may write refreshed tokens into the shared runtime after an account switch; only persist when the auth still matches the selected account. const selectedEmail = this.firstNonNull( this.normalizeField(activeAccount.email), managedIdentity?.email @@ -893,9 +844,7 @@ export class CodexRuntimeHomeService { return false } - // Why: stale managed Codex PTYs share the same runtime home. Only read a - // runtime refresh back into ~/.codex when the auth still claims the same - // system-default identity Orca mirrored earlier. + // Why: stale PTYs share the runtime home; only read a refresh back to ~/.codex when the auth still claims the mirrored system-default identity. if ( systemDefaultIdentity.email && runtimeIdentity.email && @@ -1238,9 +1187,7 @@ export class CodexRuntimeHomeService { this.migrateLegacySessions(managedHomePath, accountId) } - // Why: migration is intentionally one-shot. Re-importing every startup - // would keep replaying stale managed-home state back into the shared - // runtime and make it feel nondeterministic. + // Why: migration is one-shot; re-importing every startup would replay stale managed-home state into the shared runtime. writeFileAtomically( this.getMigrationMarkerPath(), `${JSON.stringify({ completedAt: Date.now(), migratedHomeCount: managedHomes.length })}\n` @@ -1348,8 +1295,7 @@ export class CodexRuntimeHomeService { } private appendListedFiles(target: string[], source: readonly string[]): void { - // Why: migrating legacy session trees must tolerate directories larger than - // V8's argument limit for spread calls. + // Why: tolerate directories larger than V8's argument limit for spread calls. for (const filePath of source) { target.push(filePath) } @@ -1366,8 +1312,7 @@ export class CodexRuntimeHomeService { try { appendFileSync(diagnosticsPath, `${JSON.stringify(record)}\n`, { encoding: 'utf-8' }) } catch (error) { - // Why: conflict diagnostics are useful, but must not make the one-shot - // migration fail after the session file has already been preserved. + // Why: diagnostics must not fail the one-shot migration after the session file is already preserved. console.warn('[codex-runtime-home] Failed to append migration diagnostic:', error) } } @@ -1418,17 +1363,13 @@ export class CodexRuntimeHomeService { systemDefaultAuth === mirroredSystemDefaultAuth && this.runtimeAuthMatchesSystemDefaultIdentity(runtimeAuth, mirroredSystemDefaultAuth) ) { - // Why: system-default Codex now refreshes tokens inside Orca's - // runtime CODEX_HOME. Read that refresh back to ~/.codex so the next - // sync does not overwrite fresh runtime credentials with stale ones. + // Why: Codex refreshes tokens in the runtime CODEX_HOME; read that back to ~/.codex so the next sync won't clobber fresh creds with stale ones. this.writeSystemDefaultAuth(runtimeAuth) this.captureSystemDefaultSnapshot({ force: true }) this.lastWrittenAuthJson = runtimeAuth return } - // Why: the unmanaged path used to read ~/.codex directly. Mirror later - // external logins/logouts into Orca's runtime home so ordinary Orca - // Codex sessions keep matching the user's current system-default state. + // Why: mirror external logins/logouts into Orca's runtime home so unmanaged Codex sessions keep matching the current system-default state. this.captureSystemDefaultSnapshot({ force: true }) this.writeRuntimeAuth(systemDefaultAuth) } @@ -1449,18 +1390,14 @@ export class CodexRuntimeHomeService { } if (options.detectExternalLogin && !existsSync(runtimeAuthPath)) { - // Why: once Orca owns the runtime CODEX_HOME, deleting auth.json there is - // a local logout signal for Orca-launched Codex sessions, not a reason to - // rewrite the user's real ~/.codex snapshot back into place. + // Why: with Orca owning CODEX_HOME, a deleted runtime auth.json is a local logout, not a cue to restore the user's real ~/.codex snapshot. this.persistRuntimeLogoutMarker() this.lastWrittenAuthJson = null return } if (options.detectExternalLogin) { - // Why: while a managed account is selected, the runtime auth file exists - // with managed credentials. If ~/.codex/auth.json vanished meanwhile, - // switching back must preserve that external system-default logout. + // Why: if ~/.codex/auth.json vanished while a managed account was selected, switching back must preserve that external system-default logout. rmSync(runtimeAuthPath, { force: true }) this.captureSystemDefaultSnapshot({ force: true }) this.persistRuntimeLogoutMarker() @@ -1507,9 +1444,7 @@ export class CodexRuntimeHomeService { } private clearRuntimeAuthAfterSystemDefaultLogout(runtimeAuthPath: string): void { - // Why: when the real ~/.codex auth disappears, Orca should treat that as an - // external logout for unmanaged sessions, even if runtime auth had already - // refreshed inside Orca's CODEX_HOME. + // Why: a vanished ~/.codex auth means external logout for unmanaged sessions, even if runtime auth already refreshed in Orca's CODEX_HOME. rmSync(runtimeAuthPath, { force: true }) this.captureSystemDefaultSnapshot({ force: true }) this.persistRuntimeLogoutMarker() @@ -1522,8 +1457,7 @@ export class CodexRuntimeHomeService { } private writeRuntimeAuth(contents: string): void { - // Why: auth.json contains sensitive credentials. Restrict to owner-only - // so other users on a shared Linux/macOS machine cannot read it. + // Why: auth.json holds credentials; restrict to owner-only so other users on a shared machine cannot read it. this.clearRuntimeLogoutMarker() if (this.fileContentsEqual(this.getRuntimeAuthPath(), contents)) { this.ensureOwnerOnlyMode(this.getRuntimeAuthPath()) @@ -1634,9 +1568,7 @@ export class CodexRuntimeHomeService { ) { return parsed as CodexSystemDefaultSnapshot } - // Why: pre-PR snapshots wrote raw auth.json contents verbatim. Treat any - // valid JSON object without an authJson wrapper as the legacy format so - // upgraders do not lose their system-default auth on first deselect. + // Why: pre-PR snapshots stored raw auth.json; treat objects lacking an authJson wrapper as legacy so upgraders don't lose their auth. if ( parsed && typeof parsed === 'object' && @@ -1656,9 +1588,7 @@ export class CodexRuntimeHomeService { } } -// Why: the seed config is read over UNC but consumed by Codex inside WSL, so -// relative path-valued settings must anchor to the Linux-side source home; a -// verbatim copy breaks Codex config load (os error 2). +// Why: Codex reads this config inside WSL, so relative path settings must anchor to the Linux-side home (verbatim copy breaks load, os error 2). export function prepareWslRuntimeSeedConfig( configContents: string, sourceHomePath: string diff --git a/src/main/codex-accounts/service.ts b/src/main/codex-accounts/service.ts index 2a0fb54bdc4..1eb3dedf561 100644 --- a/src/main/codex-accounts/service.ts +++ b/src/main/codex-accounts/service.ts @@ -1,6 +1,4 @@ -/* eslint-disable max-lines -- Why: this service intentionally keeps Codex -account lifecycle, path safety, login, and identity parsing in one audited -main-process module so the managed-account boundary stays explicit. */ +/* eslint-disable max-lines -- Why: keeps Codex account lifecycle, path safety, login, and identity parsing in one audited main-process module. */ import { randomUUID } from 'node:crypto' import { execFileSync, spawn } from 'node:child_process' import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs' @@ -55,8 +53,7 @@ type ResolvedCodexIdentity = { type CanonicalCodexConfig = { contents: string - /** Home the config was read from, in the path style Codex sees at runtime - * (Linux-side for WSL); relative path-valued settings resolve against it. */ + /** Home the config was read from, in the path style Codex sees (Linux-side for WSL); relative settings resolve against it. */ sourceHomePath: string } @@ -77,9 +74,7 @@ function shellQuote(value: string): string { } export class CodexAccountService { - // Why: account mutations read settings, do async work (login, rate-limit - // refresh), then write settings. Without serialization, overlapping calls - // (e.g. double-click "Add Account") can cause lost updates. + // Why: serialize the read-modify-write of settings; overlapping calls (e.g. double-click Add) would lose updates. private mutationQueue: Promise = Promise.resolve() constructor( @@ -170,8 +165,7 @@ export class CodexAccountService { this.runtimeHome.clearLastWrittenAuthJson(account.id) this.runtimeHome.syncForCurrentSelection() - // Why: the new account becomes active, so the previous active account is - // now inactive and its last-known usage should be cached for the switcher. + // Why: switching activates the new account, so cache the outgoing account's usage for the switcher. const outgoingAccountId = getSelectedCodexAccountIdForTarget(settings, targetSelection) await this.rateLimits.refreshForCodexAccountChange(outgoingAccountId, targetSelection) return this.getSnapshot() @@ -215,9 +209,7 @@ export class CodexAccountService { this.runtimeHome.clearLastWrittenAuthJson(accountId) this.runtimeHome.syncForCurrentSelection(getCodexSelectionTargetForAccount(account)) - // Why: re-auth can change which actual Codex identity the managed home - // points at. Force a fresh read immediately so the status bar cannot keep - // showing the previous account's quota under the updated label. + // Why: re-auth can change the underlying Codex identity, so force a fresh read to avoid showing stale quota. await this.rateLimits.refreshForCodexAccountChange( undefined, getCodexSelectionTargetForAccount(account) @@ -244,8 +236,7 @@ export class CodexAccountService { this.runtimeHome.syncForCurrentSelection() this.safeRemoveManagedHome(account.managedHomePath) - // Why: a removed account can no longer appear in the switcher dropdown, - // so purge its cached usage to avoid stale entries. + // Why: removed accounts can't appear in the switcher, so purge cached usage to avoid stale entries. this.rateLimits.evictInactiveCodexCache(accountId) await this.rateLimits.refreshForCodexAccountChange( getSelectedCodexAccountIdForTarget(settings, getCodexSelectionTargetForAccount(account)) === @@ -358,9 +349,7 @@ export class CodexAccountService { const managedHomePath = join(this.getManagedAccountsRoot(), accountId, 'home') mkdirSync(managedHomePath, { recursive: true }) - // Why: Codex expects CODEX_HOME to be a concrete directory it can own. We - // pre-create the directory and leave a marker so future cleanup code can - // prove the path belongs to Orca before deleting anything. + // Why: marker lets future cleanup prove the path belongs to Orca before deleting anything. writeFileSync(join(managedHomePath, '.orca-managed-home'), `${accountId}\n`, 'utf-8') return { managedHomePath: this.assertManagedHomePath(managedHomePath), @@ -462,12 +451,7 @@ export class CodexAccountService { } const trustedManagedHomePath = this.assertManagedHomePath(managedHomePath) - // Why: Orca account switching is meant to swap Codex credentials and quota - // identity, not silently fork the user's sandbox/config defaults. Syncing - // one canonical config into every managed home keeps auth isolated per - // account while preserving consistent Codex behavior. Managed homes are - // real CODEX_HOMEs for `codex login`, so relative path-valued settings - // must keep resolving against the home the config was read from. + // Why: sync one canonical config per home so switching swaps only auth/quota; rewrite relative paths to resolve against the source home. this.writeManagedConfig( trustedManagedHomePath, rewriteRelativePathConfigValues(canonicalConfig.contents, canonicalConfig.sourceHomePath) @@ -507,8 +491,7 @@ export class CodexAccountService { } try { - // Why: the config is read over UNC but consumed by Codex inside WSL, so - // path rewrites must anchor to the Linux-side ~/.codex, not the UNC path. + // Why: read over UNC but consumed inside WSL, so path rewrites must anchor to the Linux-side ~/.codex, not the UNC path. return { contents: readFileSync(configPath, 'utf-8'), sourceHomePath: `${wslHome}/.codex` } } catch (error) { console.warn('[codex-accounts] Failed to read WSL canonical config:', error) @@ -523,8 +506,7 @@ export class CodexAccountService { return } } catch { - // Why: read errors should not make a stale config look current; the - // atomic write path owns Windows ACL repair and persistent error surfacing. + // Why: a read error must not make a stale config look current; atomic write owns ACL repair and error surfacing. } writeFileAtomically(configPath, contents) } @@ -561,8 +543,7 @@ export class CodexAccountService { throw originalError } - // Why: explicit re-auth is allowed to recover from a lost empty container, - // but only at the exact Orca-owned account path persisted for this account. + // Why: re-auth may recreate a lost empty home, but only at the exact Orca-owned path persisted for this account. mkdirSync(expectedManagedHomePath, { recursive: true }) writeFileSync(join(expectedManagedHomePath, '.orca-managed-home'), `${account.id}\n`, 'utf-8') return this.assertManagedHomePath(expectedManagedHomePath) @@ -687,18 +668,11 @@ export class CodexAccountService { throw new Error('Managed Codex home directory does not exist on disk.') } - // realpath() requires the leaf to exist. For pre-login add flow we create - // the home directory first so the containment check still verifies the - // canonical on-disk target rather than trusting persisted text blindly. + // realpath() requires the leaf to exist; verify the canonical on-disk target rather than trusting persisted text. const canonicalCandidate = realpathSync(resolvedCandidate) const canonicalRoot = realpathSync(resolvedRoot) - // Why: the prefix check must compare canonical paths on both sides. On - // macOS, userData sits under /var/folders/... which realpath resolves to - // /private/var/folders/...; comparing a canonical candidate against a - // non-canonical root would spuriously reject every managed home. In dev - // mode (orca-dev/ vs orca/) this check also filters out production-rooted - // paths before downstream sync runs. + // Why: both sides must be canonical — macOS /var/folders realpaths to /private/var, else every managed home is rejected. if ( canonicalCandidate !== canonicalRoot && !canonicalCandidate.startsWith(canonicalRoot + sep) @@ -727,8 +701,7 @@ export class CodexAccountService { linuxHomePath: string, expectedAccountId: string ): void { - // Why: WSL home creation can fail after mkdir/marker write but before the - // path is trusted. Cleanup must prove the marker/account ID inside WSL. + // Why: creation can fail after mkdir/marker but before trust, so cleanup must verify the marker/account ID inside WSL. try { execFileSync( 'wsl.exe', @@ -784,13 +757,10 @@ export class CodexAccountService { return } - // Why: managed homes live at //home. Removing - // just the home/ leaf leaves an empty / directory behind. + // Why: homes live at //home; removing the home/ leaf leaves an empty / behind. try { const parentDir = resolve(managedHomePath, '..') - // Why: managedHomePath is already canonicalized by assertManagedHomePath, - // so the root must be canonicalized too for the prefix check to work on - // macOS where userData resolves through /private/var. + // Why: canonicalize the root too so the prefix check works on macOS where userData resolves through /private/var. const root = realpathSync(this.getManagedAccountsRoot()) if (parentDir.startsWith(root + sep) && parentDir !== root) { rmSync(parentDir, { recursive: true, force: true }) @@ -816,11 +786,7 @@ export class CodexAccountService { } : (() => { const codexCommand = resolveCodexCommand() - // Why: on Windows, resolveCodexCommand() may return a .cmd/.bat file - // (e.g. codex.cmd from npm). Node's child_process.spawn cannot execute - // batch scripts directly without shell:true, but shell:true with an args - // array causes DEP0190 because args are concatenated, not escaped. - // Fix: detect batch scripts and invoke cmd.exe /c explicitly. + // Why: Windows codex may be a .cmd/.bat; spawn+shell:true would trigger DEP0190, so invoke cmd.exe /c explicitly. const { spawnCmd, spawnArgs } = getSpawnArgsForWindows(codexCommand, ['login']) return { command: spawnCmd, @@ -834,8 +800,7 @@ export class CodexAccountService { })() const child = spawn(spawnConfig.command, spawnConfig.args, { stdio: ['ignore', 'pipe', 'pipe'], - // Why: route through cmd.exe for .cmd/.bat entrypoints would otherwise - // flash a console window in the packaged GUI app on Windows. + // Why: prevents a console window flash for .cmd/.bat entrypoints routed through cmd.exe on Windows. windowsHide: true, env: spawnConfig.env }) @@ -881,9 +846,7 @@ export class CodexAccountService { const onError = (error: Error): void => { settle(() => { const isEnoent = (error as NodeJS.ErrnoException).code === 'ENOENT' - // Why: ENOENT can mean either the codex binary doesn't exist OR the - // script's shebang interpreter (node) isn't in PATH. When we resolved - // codex to a full path, ENOENT almost certainly means node is missing. + // Why: ENOENT is ambiguous — missing codex binary or missing node in PATH; a resolved full path implies node is missing. const isBareCommand = spawnConfig.codexCommand === 'codex' const message = isEnoent ? isBareCommand @@ -963,10 +926,7 @@ export class CodexAccountService { const authFilePath = join(this.assertManagedHomePath(managedHomePath), 'auth.json') const raw = JSON.parse(readFileSync(authFilePath, 'utf-8')) as Record - // Why: API-key-based auth files have no OAuth tokens or JWT identity - // claims. Returning nulls causes the caller to fail with a clear - // "could not resolve the account email" error rather than crashing - // on missing nested token fields. + // Why: API-key auth files have no OAuth/JWT claims; return nulls so the caller fails cleanly instead of crashing. if (typeof raw.OPENAI_API_KEY === 'string' && raw.OPENAI_API_KEY.trim() !== '') { return { idToken: null, diff --git a/src/main/codex/config-settings-promotion.ts b/src/main/codex/config-settings-promotion.ts index 7e8552bb9b6..a7a1add6857 100644 --- a/src/main/codex/config-settings-promotion.ts +++ b/src/main/codex/config-settings-promotion.ts @@ -19,16 +19,9 @@ import { updateTomlLineScanState } from './config-toml-line-scan' -// Why: the config mirror rewrites the runtime config.toml from ~/.codex on -// every launch (and on background rate-limit fetches), so settings the user -// changes inside Orca-launched Codex silently revert. Promotion diffs the -// runtime file against a baseline of what Orca last wrote — anything that -// differs is a change Codex persisted for the user and belongs in ~/.codex. +// Why: the mirror reverts in-Codex config changes each launch; promotion salvages them by diffing the last baseline. -// Why: only the user-preference scalars the Codex TUI itself persists -// (/model writes model + model_reasoning_effort, /approvals writes -// approval_policy + sandbox_mode). Every key added here gets written into the -// user's real ~/.codex/config.toml, so grow this list deliberately. +// Why: only scalars the Codex TUI persists; each key here is written to the user's real ~/.codex, so grow deliberately. export const PROMOTED_CODEX_SETTING_KEYS = [ 'model', 'model_reasoning_effort', @@ -38,8 +31,7 @@ export const PROMOTED_CODEX_SETTING_KEYS = [ type TopLevelSettingValue = { raw: string - // Why: a value that opens a multiline string/array cannot be replaced or - // copied line-by-line safely, so it is excluded from promotion entirely. + // Why: a multiline string/array value can't be replaced line-by-line, so it's excluded from promotion. multiline: boolean } @@ -78,9 +70,7 @@ function readSettingsBaseline(runtimeHomePath: string): Map | nu } } -// Why: only keys in the top-level preamble are scanned — Codex writes profile -// overrides into [profiles.*] tables, and rewriting nested tables surgically -// is not worth the risk for stage-1 promotion. +// Why: only top-level preamble keys are scanned; rewriting nested [profiles.*] tables isn't worth the risk here. function readTopLevelSettingValues(configPath: string): Map { const result = new Map() if (!existsSync(configPath)) { @@ -108,19 +98,16 @@ function readTopLevelSettingValues(configPath: string): Map = {} for (const [key, value] of readTopLevelSettingValues(runtimeTomlPath)) { if (!value.multiline) { @@ -130,8 +117,7 @@ export function snapshotCodexRuntimeSettingsBaseline( const file: SettingsBaselineFile = { version: 1, settings } const baselinePath = getSettingsBaselinePath(runtimeHomePath) const serialized = `${JSON.stringify(file, null, 2)}\n` - // Why: launch preparation can run repeatedly; skip byte-identical rewrites - // so an unchanged pass does no disk writes. + // Why: launch prep runs repeatedly; skip byte-identical rewrites to avoid needless disk writes. if (existsSync(baselinePath) && readFileSync(baselinePath, 'utf-8') === serialized) { return } @@ -157,19 +143,16 @@ function getHostPromotionHomes(): CodexSettingsPromotionHomes { } /** - * Promotes setting changes the user made inside Orca-launched Codex (written - * by Codex into the runtime config.toml) into ~/.codex/config.toml. Runs - * before the config mirror so the promoted values survive the same mirror - * pass instead of reverting. WSL callers pass explicit per-distro homes; the - * default is the host runtime home and host ~/.codex. + * Promotes in-Codex setting changes from the runtime config.toml into ~/.codex/config.toml. + * Runs before the config mirror so promoted values survive it instead of reverting. + * WSL callers pass explicit per-distro homes; default is the host runtime home and ~/.codex. */ export function promoteCodexRuntimeSettingsToSystem(homes?: CodexSettingsPromotionHomes): boolean { try { promoteCodexRuntimeSettingsToSystemUnsafe(homes ?? getHostPromotionHomes()) return true } catch (error) { - // Why: promotion is best-effort launch prep; callers preserve the runtime - // for retry, while a malformed file must not block Codex launch itself. + // Why: promotion is best-effort launch prep; a malformed file must not block Codex launch. console.warn('[codex-settings-promotion] failed to promote runtime settings', error) return false } @@ -185,11 +168,7 @@ function promoteCodexRuntimeSettingsToSystemUnsafe(homes: CodexSettingsPromotion if (!existsSync(runtimeTomlPath)) { return } - // Why: without a baseline of what Orca last mirrored (first launch after - // upgrading to a build with promotion, or a corrupted snapshot), a stale - // runtime value is indistinguishable from a fresh in-Codex change. Skip - // this pass — the mirror writes the first baseline and promotion starts on - // the next one. + // Why: without a baseline, a stale runtime value looks like a fresh in-Codex change; skip until the mirror writes one. const baseline = readSettingsBaseline(runtimeHomePath) if (!baseline) { return @@ -210,8 +189,7 @@ function promoteCodexRuntimeSettingsToSystemUnsafe(homes: CodexSettingsPromotion if (system?.multiline) { continue } - // Why: ~/.codex stays source of truth — if the user also edited it there - // since the baseline, the outside edit wins over the in-Codex change. + // Why: ~/.codex is source of truth — an outside edit since the baseline wins over the in-Codex change. if (system?.raw !== baseline.get(key)) { continue } @@ -220,23 +198,16 @@ function promoteCodexRuntimeSettingsToSystemUnsafe(homes: CodexSettingsPromotion if (updates.size === 0) { return } - // Why: a genuinely fresh host has no ~/.codex yet; without the directory - // the atomic write ENOENTs and the following mirror wipes the setting. - // Owner-only: the directory holds auth.json and the full user config. + // Why: a fresh host has no ~/.codex; create it owner-only (holds auth.json) or the atomic write ENOENTs and the mirror wipes it. mkdirSync(systemHomePath, { recursive: true, mode: 0o700 }) const writeTarget = resolvePromotionWriteTarget(systemTomlPath) - // Why: a dangling dotfile-manager symlink can point into a directory tree - // that has not been materialized yet; preserve the link and create its real - // parent so the atomic temp file can be written beside the target. + // Why: a dangling symlink may target an unmade dir tree; create its real parent so the atomic temp write has a home. mkdirSync(dirname(writeTarget.path), { recursive: true, mode: 0o700 }) const targetExists = existsSync(writeTarget.path) const systemContent = targetExists ? readFileSync(writeTarget.path, 'utf-8') : '' const nextContent = upsertTopLevelSettingsInContent(systemContent, updates) if (targetExists && parseWslUncPath(writeTarget.path)) { - // Why: symlink metadata through the \\wsl$ 9P provider is not reliable - // enough for realpath/lstat detection, and an atomic rename would replace - // a WSL-side dotfile symlink with a plain file. Writing through the - // existing file preserves the Linux-side inode (and its mode). + // Why: \\wsl$ 9P symlink metadata is unreliable; write through the existing file to preserve the WSL-side inode. writeFileSync(writeTarget.path, nextContent, 'utf-8') return } @@ -245,11 +216,7 @@ function promoteCodexRuntimeSettingsToSystemUnsafe(homes: CodexSettingsPromotion }) } -// Why: promotion rewrites the user's real config.toml. Follow an existing -// symlink (dotfile managers) instead of replacing the link with a plain file, -// and carry the real file's mode forward — an atomic write without a mode -// would widen a user-restricted 0600 config to the process umask default. -// A new or unreadable target is created owner-only. +// Why: follow an existing dotfile-manager symlink and carry its mode forward so an atomic write can't widen a 0600 config. function resolvePromotionWriteTarget(systemTomlPath: string): { path: string; mode: number } { try { const realPath = realpathSync(systemTomlPath) @@ -282,8 +249,7 @@ function resolveDanglingSymlinkTarget(linkPath: string): string { return currentPath } } - // Why: replacing any link in a cycle would destroy dotfile-manager state; - // abort promotion and leave the runtime/baseline intact for manual repair. + // Why: replacing any link in a cycle would destroy dotfile-manager state; abort instead. throw new Error(`Codex config symlink cycle at ${linkPath}`) } @@ -310,8 +276,7 @@ export function upsertTopLevelSettingsInContent( state = updateTomlLineScanState(state, line) } - // Why: CRLF configs keep a trailing \r after the split; new lines must use - // the file's existing endings or a Windows-owned config becomes mixed-EOL. + // Why: match the file's existing EOL (CRLF split leaves a trailing \r) so a Windows config doesn't go mixed-EOL. const usesCrlf = content.includes('\r\n') const insertions: string[] = [] for (const [key, raw] of updates) { diff --git a/src/main/codex/config-toml-trust.test.ts b/src/main/codex/config-toml-trust.test.ts index 9f8efbea403..0b661a3d590 100644 --- a/src/main/codex/config-toml-trust.test.ts +++ b/src/main/codex/config-toml-trust.test.ts @@ -28,10 +28,7 @@ import { type CodexTrustEntry } from './config-toml-trust' -// Why: this hash was captured from a real Codex 0.129 `/hooks` approval. If -// Codex changes its serialization or normalization rules, this test fails -// loudly instead of silently shipping bad trust entries that put hooks back -// into the review pile. +// Why: captured from a real Codex 0.129 `/hooks` approval; fails loudly if Codex's serialization drifts. const REAL_APPROVED_COMMAND = '/bin/sh "/tmp/orca-case-b-mCmCe6/agent-hooks/codex-hook.sh"' const REAL_APPROVED_HASH = 'sha256:bc013489dba495431d3790fda62ee5a7d907a7c491e29ad26238c3a5d6d2b163' @@ -153,12 +150,7 @@ describe('computeTrustedHash', () => { }) it('drops the matcher on user_prompt_submit/stop like matcher_pattern_for_event', () => { - // Why: Codex ignores matchers on these two events and hashes the - // identity WITHOUT the matcher field. A definition carrying - // `"matcher": ""` (common in third-party installers) must therefore - // hash identically to one without a matcher, or the system-trust - // mirror misses and the stale-entry sweep deletes the trust Codex - // wrote — re-prompting the user on every launch. + // Why: Codex hashes these two events WITHOUT the matcher, so `"matcher": ""` must hash like no matcher. for (const eventLabel of ['user_prompt_submit', 'stop'] as const) { const base: CodexTrustEntry = { sourcePath: '/x/hooks.json', @@ -174,12 +166,7 @@ describe('computeTrustedHash', () => { }) it('pins the matcher-omitted hash for a Stop entry that carries an empty matcher', () => { - // Why: regression pin for the shape observed in the wild (a real - // Codex 0.140 config.toml, path anonymized): Codex stores the - // matcher-omitted hash for Stop even when hooks.json carries - // `"matcher": ""`, so we must never fold the matcher into this - // identity. If serialization or normalization drifts, this constant - // fails loudly. + // Why: regression pin (real Codex 0.140 config) — Stop uses the matcher-omitted hash even with `"matcher": ""`. expect( computeTrustedHash({ sourcePath: '/home/user/.codex/hooks.json', @@ -291,8 +278,7 @@ describe('computeTrustKey', () => { }) it('uses native Windows backslashes in the trust key Codex looks up', () => { - // Why: Codex 0.140 writes approved Windows hook trust keys as raw native - // paths under [hooks.state]. + // Why: Codex 0.140 writes approved Windows hook trust keys as raw native paths under [hooks.state]. const winPath = 'C:\\Users\\Rod\\AppData\\Roaming\\orca\\hooks.json' const key = computeTrustKey({ sourcePath: winPath, @@ -306,8 +292,7 @@ describe('computeTrustKey', () => { }) it('preserves literal backslashes in non-Windows-style fallback paths', () => { - // Why: SSH/POSIX paths can legally contain `\` as a filename character; - // only Windows-style separators should be normalized. + // Why: POSIX/SSH paths can contain `\` as a filename char; only Windows separators get normalized. expect(getCodexCanonicalTrustPath('/tmp/with\\literal/hooks.json')).toBe( '/tmp/with\\literal/hooks.json' ) @@ -387,8 +372,7 @@ describe('upsertHookTrustEntries', () => { expect(written).not.toContain('STALE') expect(written).toContain('[unrelated]') expect(written).toContain('value = 42') - // Why: we only own the [hooks.state.""] block — the [features] - // block must be unchanged. + // Why: we only own the [hooks.state.""] block — [features] must be untouched. expect(written).toContain('[features]\nhooks = true') }) @@ -507,8 +491,7 @@ describe('upsertHookTrustEntries', () => { } upsertHookTrustEntries(configPath, [entry]) const firstWrite = readFileSync(configPath, 'utf-8') - // Why: a no-op upsert must not roll the .bak forward — repeated calls - // (e.g. from app start) would otherwise destroy the last recoverable copy. + // Why: a no-op upsert must not roll .bak forward, or repeated calls destroy the last recoverable copy. rmSync(`${configPath}.bak`, { force: true }) upsertHookTrustEntries(configPath, [entry]) expect(existsSync(`${configPath}.bak`)).toBe(false) @@ -516,8 +499,7 @@ describe('upsertHookTrustEntries', () => { }) it('replaces a stale block written with CRLF line endings without duplicating', () => { - // Why: regression — Windows-style \r\n in the existing config previously - // caused the header pattern to miss and append a duplicate block. + // Why: regression — \r\n in the existing config made the header pattern miss and append a duplicate. const key = '/x/hooks.json:pre_tool_use:0:0' const original = [ '[features]', @@ -598,8 +580,7 @@ describe('upsertHookTrustEntries', () => { expect(written).toContain('foo = 1') }) - // Why: TOML supports both basic-string and literal-string quoted keys; - // header detection must respect `]` inside `'...'` too. + // Why: TOML allows literal-string quoted keys, so header detection must respect `]` inside `'...'`. it('preserves an unrelated table whose literal-string key contains a `]`', () => { const key = '/x/hooks.json:pre_tool_use:0:0' const original = [ @@ -749,8 +730,7 @@ describe('upsertHookTrustEntries', () => { }) it('treats `\\"""` inside a multi-line basic string as an escaped quote, not a close', () => { - // Why: a basic multi-line string with `\"` escapes must not be misread as - // closing early — content and any following real header must survive intact. + // Why: `\"` escapes in a multi-line basic string must not be misread as closing early. const original = [ 'prompt = """', 'use \\"\\"\\" carefully', @@ -779,8 +759,7 @@ describe('upsertHookTrustEntries', () => { }) it('escapes literal `"` and `\\` in non-Windows source paths inside the trust block header', () => { - // Why: a backslash in a POSIX path can be a literal filename character, so - // it must still be escaped instead of normalized away. + // Why: a backslash in a POSIX path is a literal filename char, so escape it instead of normalizing. const entry: CodexTrustEntry = { sourcePath: '/x/with"quote\\and\\back/hooks.json', eventLabel: 'pre_tool_use', @@ -797,9 +776,7 @@ describe('upsertHookTrustEntries', () => { }) it('overwrites an existing block whose header has leading whitespace (TOML allows indent)', () => { - // Why: regression — buildHeaderPattern used to require column-0 headers, - // but the reader accepts indented ones. That mismatch caused upsert to - // append a duplicate `[hooks.state.""]` block, producing invalid TOML. + // Why: regression — buildHeaderPattern required column-0 headers but the reader accepts indented ones. const key = '/x/hooks.json:pre_tool_use:0:0' const original = ` [hooks.state."${key}"]\nenabled = true\ntrusted_hash = "sha256:OLD"\n` writeFileSync(configPath, original, 'utf-8') @@ -821,9 +798,7 @@ describe('upsertHookTrustEntries', () => { }) it('preserves `enabled = false` when the user hand-edited it before reinstall', () => { - // Why: regression — auto-install on app start used to clobber a - // hand-disabled hook back to enabled = true, removing the only way to - // mute Orca's hook short of full uninstall. + // Why: regression — auto-install used to clobber a hand-disabled hook back to enabled = true. const key = '/x/hooks.json:pre_tool_use:0:0' const original = `[hooks.state."${key}"]\nenabled = false\ntrusted_hash = "sha256:OLD"\n` writeFileSync(configPath, original, 'utf-8') @@ -844,9 +819,7 @@ describe('upsertHookTrustEntries', () => { }) it('overwrites an existing block when the file ends without a trailing newline', () => { - // Why: regression — buildHeaderPattern used to require a trailing - // `\r?\n`, missing it caused the upsert path to take the no-match branch - // and append a duplicate block at EOF. + // Why: regression — buildHeaderPattern required a trailing newline, appending a duplicate at EOF. const key = '/x/hooks.json:pre_tool_use:0:0' const original = `[hooks.state."${key}"]\nenabled = true\ntrusted_hash = "sha256:OLD"` writeFileSync(configPath, original, 'utf-8') @@ -868,10 +841,7 @@ describe('upsertHookTrustEntries', () => { }) it('overwrites an existing block whose header has an inline comment', () => { - // Why: regression — buildHeaderPattern used to require the header line - // to end at \r?\n or EOF, missing TOML-valid trailing comments. The - // upsert path then took the no-match branch and appended a duplicate - // `[hooks.state.""]` block, producing invalid TOML. + // Why: regression — buildHeaderPattern missed TOML-valid trailing comments and appended a duplicate block. const key = '/x/hooks.json:pre_tool_use:0:0' const original = `[hooks.state."${key}"] # user note\nenabled = true\ntrusted_hash = "sha256:OLD"\n` writeFileSync(configPath, original, 'utf-8') @@ -893,8 +863,7 @@ describe('upsertHookTrustEntries', () => { }) it('finds and replaces a legacy forward-slash block when Orca upserts with native backslash key', () => { - // Why: Codex 0.140 can expose Windows trust keys with either separator - // shape depending on startup cwd, so Orca replaces stale blocks with both. + // Why: Codex 0.140 exposes Windows keys with either separator depending on cwd, so replace both. const backslashPath = 'C:\\Users\\Rod\\AppData\\Roaming\\orca\\hooks.json' const legacyKey = `${backslashPath.replace(/\\/g, '/')}:session_start:0:0` const original = [ @@ -923,8 +892,7 @@ describe('upsertHookTrustEntries', () => { }) it('produces exactly one Windows separator pair after two consecutive upserts', () => { - // Why: idempotency guard — repeated auto-install on app start must not - // accumulate duplicate trust blocks and produce invalid TOML. + // Why: idempotency guard — repeated auto-install must not accumulate duplicate blocks. const entry: CodexTrustEntry = { sourcePath: 'C:\\Users\\Rod\\AppData\\Roaming\\orca\\hooks.json', eventLabel: 'session_start', @@ -941,8 +909,7 @@ describe('upsertHookTrustEntries', () => { }) it('falls back to TOML basic-string headers when a Windows path contains an apostrophe', () => { - // Why: TOML literal-string table keys cannot contain apostrophes, but - // Windows user/profile paths can. + // Why: TOML literal-string keys can't hold apostrophes, but Windows profile paths can. const entry: CodexTrustEntry = { sourcePath: "C:\\Users\\O'Connor\\AppData\\Roaming\\orca\\hooks.json", eventLabel: 'session_start', @@ -964,10 +931,7 @@ describe('upsertHookTrustEntries', () => { }) it('finds a Codex-written block with lowercased username when Orca key has mixed-case username', () => { - // Why: realpathSync.native casing can differ between what Codex wrote - // (C:\Users\rod\...) and what Orca resolves (C:\Users\Rod\...). - // normalizeHookTrustKeyForLookup case-folds Windows-shaped paths so the - // existing block is replaced rather than a duplicate appended. + // Why: realpathSync.native casing can differ from what Codex wrote, so case-fold to replace not duplicate. const lowercasePath = 'C:\\Users\\rod\\AppData\\Roaming\\orca\\hooks.json' const mixedCasePath = 'C:\\Users\\Rod\\AppData\\Roaming\\orca\\hooks.json' const literalKey = `${lowercasePath}:session_start:0:0` @@ -1057,8 +1021,7 @@ describe('upsertProjectTrustLevel', () => { }) it('preserves CRLF endings and writes native Windows path separators in the header', () => { - // Why: local project trust still follows Codex's realpath display, while - // remote project trust preserves the SSH provider's canonical path string. + // Why: local trust follows Codex's realpath; remote trust preserves the SSH provider's canonical path. const original = ['[profiles.default]', 'model = "gpt-5"', ''].join('\r\n') const updated = upsertProjectTrustLevelInContent(original, 'C:\\Users\\nw\\repo', 'trusted') @@ -1070,8 +1033,7 @@ describe('upsertProjectTrustLevel', () => { }) it('updates an existing Windows backslash project block after separator normalization', () => { - // Why: hook trust now writes paired Windows variants, but project trust - // must still repair an existing single project table in place. + // Why: hook trust writes paired Windows variants, but project trust still repairs a single table in place. const original = [ '[projects."C:\\\\Users\\\\nw\\\\repo"]', 'notes = "keep"', @@ -1089,8 +1051,7 @@ describe('upsertProjectTrustLevel', () => { }) it('updates an existing legacy Windows forward-slash project block', () => { - // Why: older Orca builds normalized Windows project paths to forward - // slashes; native-backslash hook fixes must not duplicate those blocks. + // Why: older Orca builds normalized to forward slashes; backslash fixes must not duplicate them. const original = [ '[projects."C:/Users/nw/repo"]', 'notes = "keep"', @@ -1149,8 +1110,7 @@ describe('upsertProjectTrustLevel', () => { }) it('keeps case-distinct WSL Linux project paths as separate trust blocks', () => { - // Why: the \\wsl$\ share is case-insensitive on Windows, but the - // Linux path underneath is not — .../Repo and .../repo are two projects. + // Why: \\wsl$\ is case-insensitive but the Linux path under it is not — two distinct projects. const existingPath = '\\\\wsl$\\Ubuntu\\home\\u\\Repo' const incomingPath = '\\\\wsl$\\Ubuntu\\home\\u\\repo' const original = [`[projects.'${existingPath}']`, 'trust_level = "untrusted"', ''].join('\n') @@ -1161,16 +1121,14 @@ describe('upsertProjectTrustLevel', () => { expect(updated.match(/\[projects\./g)).toHaveLength(2) expect(updated).toContain(`[projects.'${existingPath}']`) - // Why: serializer writes basic-string headers via escapeTomlString; assert - // that form so the fixture can't drift from real header matching. + // Why: serializer writes basic-string headers via escapeTomlString; assert that exact form. expect(updated).toContain(`[projects."${escapeTomlString(incomingPath)}"]`) expect(updated).toContain('trust_level = "untrusted"') expect(updated).toContain('trust_level = "trusted"') }) it('updates the same WSL project block across wsl$ and wsl.localhost spellings', () => { - // Why: the two share spellings alias the same distro filesystem, so a - // revoked project must not be re-trusted under the other spelling. + // Why: the two share spellings alias the same distro, so a revoke must not survive under the other. const original = [ "[projects.'\\\\wsl$\\Ubuntu\\home\\u\\proj']", 'trust_level = "untrusted"', @@ -1204,8 +1162,7 @@ describe('upsertProjectTrustLevel', () => { }) it('preserves an already-canonical remote Windows project path', () => { - // Why: SSH project paths are resolved on the remote; local realpath would - // canonicalize the wrong machine if the same path happens to exist locally. + // Why: SSH paths resolve on the remote; local realpath would canonicalize the wrong machine. const updated = upsertProjectTrustLevelInContent('', 'C:/Users/nw/repo', 'trusted', { alreadyCanonical: true }) @@ -1241,8 +1198,7 @@ describe('normalizeCodexProjectPathForLookup', () => { }) it('merges separator and distro-casing variants of the same WSL path', () => { - // Why: separators and the case-insensitive \\wsl$\ share may drift, - // but the same Linux path must still resolve to one trust key. + // Why: separator and \\wsl$\ casing may drift, but the same Linux path is one trust key. expect(normalizeCodexProjectPathForLookup('\\\\wsl$\\Ubuntu\\home\\u\\proj')).toBe( normalizeCodexProjectPathForLookup('//WSL$/ubuntu/home/u/proj') ) @@ -1451,9 +1407,7 @@ describe('removeHookTrustEntries', () => { }) it('preserves the line separator when no blank line precedes the removed block', () => { - // Why: regression — removeTrustBlock used to cut from match.index (the - // captured leading newline) and fused the previous content into the next - // header, producing invalid TOML like `a = 1[other]`. + // Why: regression — removeTrustBlock cut the leading newline, fusing prior content into the next header. const key = '/x/hooks.json:pre_tool_use:0:0' const original = [ 'a = 1', @@ -1498,8 +1452,7 @@ describe('removeHookTrustEntries', () => { }) it('removes a block whose header has an inline comment', () => { - // Why: paired with the upsert regression; the same pattern mismatch - // would silently leave the dead block in place during uninstall. + // Why: same pattern mismatch as the upsert regression would leave the dead block during uninstall. const key = '/x/hooks.json:pre_tool_use:0:0' const original = `[hooks.state."${key}"] # user note\nenabled = true\ntrusted_hash = "sha256:K"\n` writeFileSync(configPath, original, 'utf-8') @@ -1574,10 +1527,7 @@ describe('readHookTrustEntries', () => { }) it('normalizes backslash block key to forward-slash at ingestion', () => { - // Why: a real Windows path on disk like C:\foo gets written escaped as - // `C:\\foo` inside the TOML key. The Map key is normalized (backslash -> - // forward-slash) so computeTrustKey lookups match regardless of how Codex - // encoded the path separator. + // Why: normalize the Map key (backslash -> forward-slash) so computeTrustKey lookups match either encoding. const original = [ '[hooks.state."C:\\\\foo\\\\hooks.json:pre_tool_use:0:0"]', 'enabled = true', @@ -1608,8 +1558,7 @@ describe('readHookTrustEntries', () => { }) it('supports case-insensitive lookups for Windows hook trust keys read from config', () => { - // Why: Codex and realpathSync.native can disagree on user-path casing; - // status checks still need Map.get(computeTrustKey(...)) to find the row. + // Why: Codex and realpathSync.native can disagree on path casing, but lookups must still match. const rawKey = 'C:\\Users\\rod\\AppData\\Roaming\\orca\\hooks.json:session_start:0:0' const lookupKey = 'C:/Users/Rod/AppData/Roaming/orca/hooks.json:session_start:0:0' const original = [ @@ -1651,8 +1600,7 @@ describe('readHookTrustEntries', () => { }) it('keeps case-distinct WSL UNC hook paths distinct', () => { - // Why: the \\wsl$\ share is case-insensitive, but the Linux tail - // is not — folding the whole path would merge two distinct hook sources. + // Why: \\wsl$\ is case-insensitive but the Linux tail is not — don't fold two distinct sources. const upperKey = '\\\\wsl$\\Ubuntu\\home\\u\\Repo\\hooks.json:session_start:0:0' const lowerKey = '\\\\wsl$\\Ubuntu\\home\\u\\repo\\hooks.json:session_start:0:0' writeFileSync( @@ -1720,8 +1668,7 @@ describe('readHookTrustEntries', () => { }) it('does not extract a fake [hooks.state.""] header from inside a """ block', () => { - // Why: a header-shaped line embedded in a multi-line basic string must not - // be parsed as a real trust entry. + // Why: a header-shaped line inside a multi-line basic string must not parse as a real entry. const original = [ 'description = """', '[hooks.state."fake-key"]', @@ -1753,9 +1700,7 @@ describe('readHookTrustEntries', () => { }) it('reads a block whose header has an inline comment', () => { - // Why: regression — headerLineRegex used to reject TOML-valid trailing - // comments, hiding existing trust entries from getStatus and causing - // it to misreport hooks as untrusted. + // Why: regression — headerLineRegex rejected TOML-valid trailing comments, hiding trust entries. const key = '/x/hooks.json:pre_tool_use:0:0' const original = `[hooks.state."${key}"] # user note\nenabled = true\ntrusted_hash = "sha256:CMT"\n` writeFileSync(configPath, original, 'utf-8') @@ -1777,8 +1722,7 @@ describe('parseTrustKey', () => { }) it('parses a Windows-style sourcePath whose drive letter contains a colon', () => { - // Why: validates the "anchor on the LAST three colons" approach so colons - // inside the sourcePath itself round-trip correctly. + // Why: anchor on the LAST three colons so colons inside sourcePath round-trip. expect(parseTrustKey('C:\\Users\\x\\.codex\\hooks.json:session_start:2:3')).toEqual({ sourcePath: 'C:\\Users\\x\\.codex\\hooks.json', eventLabel: 'session_start', @@ -1825,8 +1769,7 @@ describe('parseTrustKey', () => { }) }) - // Why: Number('') === 0 silently passes Number.isInteger; without strict - // canonical-form validation, malformed keys would coerce into valid ones. + // Why: Number('') === 0 passes Number.isInteger, so empty segments need explicit rejection. it('returns null for empty group/handler segments', () => { expect(parseTrustKey('/x/hooks.json:pre_tool_use::0')).toBeNull() expect(parseTrustKey('/x/hooks.json:pre_tool_use:0:')).toBeNull() @@ -1841,9 +1784,7 @@ describe('parseTrustKey', () => { }) describe('upsertHookTrustEntries with array-of-tables boundaries', () => { - // Why: findNextTableHeader must treat `[[array.of.tables]]` as a block - // boundary; otherwise an upsert/remove can consume past array entries - // into unrelated user content. + // Why: [[array.of.tables]] must count as a block boundary, else upsert/remove eats past array entries. it('stops the replacement at a following [[array.of.tables]] header', () => { const key = '/x/hooks.json:pre_tool_use:0:0' const original = [ diff --git a/src/main/codex/config-toml-trust.ts b/src/main/codex/config-toml-trust.ts index eede0efea32..1313c4d1195 100644 --- a/src/main/codex/config-toml-trust.ts +++ b/src/main/codex/config-toml-trust.ts @@ -17,13 +17,8 @@ import { updateTomlLineScanState } from './config-toml-line-scan' -// Why: Codex 0.129+ gates each hook on a `trusted_hash` entry in -// ~/.codex/config.toml under [hooks.state.""]. Without it the hook is in -// the "review required" pile and never fires, so the agent-status sidebar -// silently goes blank. We reproduce Codex's hash so install() can register -// trust the same way `/hooks` would. Algorithm reverse-engineered from -// codex-rs/hooks/src/engine/discovery.rs (command_hook_hash) + -// codex-rs/config/src/fingerprint.rs (version_for_toml). +// Why: Codex 0.129+ gates each hook on a `trusted_hash` in config.toml under [hooks.state.""]; without it the hook never fires (agent-status goes blank). +// Hash algorithm reverse-engineered from codex-rs/hooks/src/engine/discovery.rs (command_hook_hash) + config/src/fingerprint.rs (version_for_toml). export type CodexEventLabel = | 'pre_tool_use' @@ -46,8 +41,7 @@ export type CodexTrustEntry = { handlerIndex: number /** The exact `command` string written to hooks.json. */ command: string - /** Effective timeout in seconds. When undefined, defaults to 600. - * Explicit values are clamped to a minimum of 1. */ + /** Effective timeout in seconds; defaults to 600 when undefined, explicit values clamped to a minimum of 1. */ timeoutSec?: number /** Whether the handler is async. Defaults to false. */ async?: boolean @@ -55,12 +49,9 @@ export type CodexTrustEntry = { matcher?: string /** Optional statusMessage field. */ statusMessage?: string - /** Verbatim hash to write instead of computing one. Used when carrying a - * Codex-written approval across files, so trust survives even if Codex's - * hash algorithm drifts from computeTrustedHash. Never fed into hashing. */ + /** Verbatim hash to write instead of computing one (survives Codex hash-algorithm drift). Never fed into hashing. */ trustedHash?: string - /** Explicit enabled state to write. When absent, a pre-existing - * `enabled = false` on the target block is preserved. */ + /** Explicit enabled state to write; when absent, a pre-existing `enabled = false` is preserved. */ enabled?: boolean } @@ -71,8 +62,7 @@ export type CodexHookTrustState = { export type CodexProjectTrustLevel = 'trusted' | 'untrusted' -// Why: callers use computeTrustKey() for lookups, while existing TOML can carry -// Codex-written separator/casing variants. Normalize both sides at the Map edge. +// Why: normalize keys at the Map edge so Codex-written separator/casing variants match computeTrustKey() lookups. class HookTrustEntryMap extends Map { override get(key: string): CodexHookTrustState | undefined { return super.get(normalizeHookTrustKeyForLookup(key)) @@ -91,8 +81,7 @@ class HookTrustEntryMap extends Map { } } -// Why: matches Codex's canonical_json. Sorts object keys recursively before -// SHA-256ing; arrays preserve order. +// Why: matches Codex's canonical_json — recursively sorts object keys before hashing; arrays keep order. function canonicalize(value: unknown): unknown { if (Array.isArray(value)) { return value.map(canonicalize) @@ -107,14 +96,7 @@ function canonicalize(value: unknown): unknown { return value } -// Why: reproduces matcher_pattern_for_event (codex-rs -// hooks/src/events/common.rs). Codex ignores matchers on -// UserPromptSubmit/Stop and drops them from the hook identity BEFORE -// hashing, so a hooks.json entry carrying `"matcher": ""` on those -// events must hash the same as one without it. Including it computes a -// hash Codex never writes: system trust then looks stale, and -// removeStaleRuntimeHookTrustEntries deletes the entry Codex wrote on -// every launch — an endless re-trust prompt for those two events. +// Why: mirrors codex-rs matcher_pattern_for_event (hooks/src/events/common.rs) — Codex drops matchers on user_prompt_submit/stop before hashing, so including one yields a hash it never writes → endless re-trust. function matcherPatternForEvent( eventLabel: CodexEventLabel, matcher: string | undefined @@ -133,12 +115,7 @@ function matcherPatternForEvent( } } -// Why: reproduces command_hook_hash. NormalizedHookIdentity has `group: -// MatcherGroup` flattened in, so the wire shape is { event_name, matcher?, -// hooks: [] }. `matcher` is omitted (not null) when -// absent — Rust's Option=None drops through the TOML→JSON path — -// and normalized per event first (see matcherPatternForEvent). -// Handler is normalized to timeout=600 (or explicit, min 1) and async=false. +// Why: reproduces Codex's command_hook_hash; wire shape is { event_name, matcher?, hooks:[handler] } with matcher omitted (not null) when absent. export function computeTrustedHash(entry: CodexTrustEntry): string { const handler: Record = { type: 'command', @@ -167,10 +144,7 @@ export function computeTrustKey(entry: CodexTrustEntry): string { export function getCodexCanonicalTrustPath(sourcePath: string): string { try { - // Why: Codex canonicalizes trust paths before building config keys. On - // macOS, /var is a symlink to /private/var; trusting the raw path still - // leaves the TUI in review/trust prompts. On Windows, Codex 0.140 writes - // hook state keys with native raw backslashes under an explicit parent table. + // Why: Codex canonicalizes trust paths (e.g. macOS /var→/private/var symlink); a raw path leaves the TUI stuck in trust prompts. return realpathSync.native(sourcePath) } catch { return normalizeWindowsPathForCodexLookup(sourcePath) @@ -179,8 +153,7 @@ export function getCodexCanonicalTrustPath(sourcePath: string): string { function getCodexCanonicalProjectPath(projectPath: string): string { try { - // Why: local project trust still needs Codex's realpath shape, but remote - // SSH callers pass an already-canonical remote path string. + // Why: local trust needs Codex's realpath shape, but remote SSH callers already pass a canonical path. return realpathSync.native(projectPath) } catch { return projectPath @@ -209,20 +182,17 @@ function usesWindowsPathSeparators(sourcePath: string): boolean { ) } -// Why: Codex and Orca can disagree on quote style, separators, and casing for -// the same Windows project, including when the caller targets a remote host. +// Why: Codex and Orca can disagree on quote style, separators, and casing for the same Windows project. export function normalizeCodexProjectPathForLookup(projectPath: string): string { if (!usesWindowsPathSeparators(projectPath)) { return projectPath } - // Why: the Linux path under a WSL share is case-sensitive, so folding it would - // conflate distinct dirs (e.g. .../Repo vs .../repo) onto one trust key. + // Why: the Linux tail under a WSL share is case-sensitive, so folding it would conflate distinct dirs onto one key. const slashedPath = normalizeWindowsPathSeparators(projectPath) return foldWslUncPathCaseInsensitiveParts(slashedPath) ?? slashedPath.toLowerCase() } -// Why: trust revocations recorded before WSL tails compared case-sensitively -// can carry drifted casing; fold fully so matching errs toward revoked. +// Why: old revocations can carry drifted casing; fold fully so matching errs toward revoked. export function normalizeCodexProjectPathForRevocationLookup(projectPath: string): string { const normalized = normalizeCodexProjectPathForLookup(projectPath) return usesWindowsPathSeparators(projectPath) ? normalized.toLowerCase() : normalized @@ -234,9 +204,7 @@ export function parseTrustKey(key: string): { groupIndex: number handlerIndex: number } | null { - // Why: keys have shape `:::`. - // sourcePath itself may contain `:` (Windows drive letters), so anchor the - // parse at the LAST three colons rather than the first. + // Why: sourcePath may contain `:` (Windows drive letters), so anchor the parse at the last three colons. const lastColon = key.lastIndexOf(':') if (lastColon === -1) { return null @@ -273,8 +241,7 @@ export function parseTrustKey(key: string): { } } -// Why: Number('') === 0 and Number('1e2') === 100 both pass Number.isInteger, -// so reject any non-canonical decimal form before numeric conversion. +// Why: Number('') === 0 and Number('1e2') === 100 both pass Number.isInteger, so reject non-canonical decimal forms first. function isCanonicalNonNegativeInt(value: string): boolean { return /^(0|[1-9]\d*)$/.test(value) } @@ -292,25 +259,14 @@ function isCodexEventLabel(value: string): value is CodexEventLabel { ) } -// Why: TOML 1.0 forbids BOMs but real-world editors (especially on Windows) sometimes -// write them. A leading  would break header regexes anchored at `^[ \t]*\[`, so -// strip it once at the file boundary and let the rest of the parser stay simple. +// Why: strip a leading BOM (some Windows editors write one) so header regexes anchored at `^[ \t]*\[` still match. function readTomlFile(configPath: string): string { const raw = readFileSync(configPath, 'utf-8') return raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw } -// Why: regex-edit ~/.codex/config.toml rather than parse + reserialize. The -// file is hand-edited by users (and other tools) and a round-trip through -// any TOML library would lose comments, key ordering, and inline-table -// style. We only ever (a) replace an existing [hooks.state.""] block -// keyed by *our* known hook keys, or (b) append a new block at EOF. Other -// content is byte-preserved. -// Why: this is a read-modify-write with no inter-process lock. Codex CLI's -// /hooks flow also writes [hooks.state.*] blocks in this file, so two -// concurrent writers can lose each other's edits. writeConfigAtomically -// prevents partial writes but not lost updates. install() is idempotent -// (deterministic hashes), so the next install() repairs drift. +// Why: regex-edit config.toml (not parse+reserialize) to byte-preserve user comments, key ordering, and inline-table style. +// Why: this read-modify-write has no lock and races Codex's /hooks writer, but idempotent install() repairs any lost update. export function upsertHookTrustEntries( configPath: string, entries: readonly CodexTrustEntry[] @@ -402,10 +358,7 @@ export function upsertProjectTrustLevelInContent( return `${existing.slice(0, headerLineEnd)}${eol}${trustLine}${existing.slice(headerLineEnd)}` } -// Why: build the canonical block we own. The two field names mirror what -// Codex itself writes when the user approves via /hooks (HookStateToml -// fields). `enabled` is plumbed through so an existing user-set -// `enabled = false` survives reinstall. +// Why: field names mirror Codex's HookStateToml (/hooks approval); `enabled` is plumbed so a user-set `enabled = false` survives reinstall. function buildTrustBlock(key: string, hash: string, enabled: boolean): string { return [ `[hooks.state.${formatHookStateTableKey(key)}]`, @@ -417,8 +370,7 @@ function buildTrustBlock(key: string, hash: string, enabled: boolean): string { function formatHookStateTableKey(key: string): string { const parsed = parseTrustKey(key) if (parsed && usesWindowsPathSeparators(parsed.sourcePath) && !key.includes("'")) { - // Why: Codex 0.140 trusts Windows hooks only when the state table keys - // match the raw native path shape it writes after /hooks approval. + // Why: Codex 0.140 trusts Windows hooks only when state table keys match the raw native path shape it writes. return `'${key}'` } return `"${escapeTomlString(key)}"` @@ -436,8 +388,7 @@ function getTrustKeyWriteVariants(key: string): string[] { ].filter((variant, index, variants) => variants.indexOf(variant) === index) } -// Why: TOML basic strings forbid raw control chars; escape backslash first so -// later substitutions don't double-escape the inserted backslashes. +// Why: escape backslash first so later substitutions don't double-escape the inserted backslashes. export function escapeTomlString(value: string): string { return value .replaceAll('\\', '\\\\') @@ -469,19 +420,12 @@ function upsertTrustBlocks( if (content.length === 0) { return `${block}\n` } - // Why: leave one blank line before our appended block so the file stays - // readable, but don't compound separators when the file already ends in - // a blank line. + // Why: one blank line before the appended block, without compounding separators when the file already ends blank. const separator = content.endsWith('\n\n') ? '' : content.endsWith('\n') ? '\n' : '\n\n' return `${content}${separator}${block}\n` } - // Why: preserve a user-set `enabled = false` so a hand-disabled hook is not - // silently re-enabled by the next auto-install on app start. - // If duplicate blocks already exist, treat any disabled copy as authoritative - // while collapsing the malformed TOML back to one table. - // An explicit enabled state (write-back promotion of an in-Orca /hooks - // toggle) overrides that preservation: it IS the user's latest decision. + // Why: preserve a user-set `enabled = false` (any disabled duplicate wins) unless an explicit state overrides it. const enabled = explicitEnabled ?? !ranges.some((range) => { @@ -505,8 +449,7 @@ function upsertTrustBlocks( } function buildTrustBlocks(keys: readonly string[], hash: string, enabled: boolean): string { - // Why: Codex 0.140 can expose Windows hook-state keys with either native - // backslashes or forward slashes depending on the cwd string used at startup. + // Why: Codex 0.140 exposes Windows hook-state keys with either backslashes or forward slashes depending on startup cwd. return keys.map((key) => buildTrustBlock(key, hash, enabled)).join('\n\n') } @@ -533,13 +476,10 @@ type TrustBlockRange = { end: number } -// Why: separator and casing drift between Codex-written keys (raw backslash, -// potentially lowercased) and Orca-built keys (forward-slash, realpathSync.native -// casing) must not prevent findTrustBlockRanges from matching an existing block. +// Why: separator/casing drift between Codex-written and Orca-built keys must not stop findTrustBlockRanges from matching. export function normalizeHookTrustKeyForLookup(key: string): string { const parsed = parseTrustKey(key) - // Why: fold by path shape, not host platform — hook sources on WSL and SSH - // Windows remotes need the same folding when Orca runs on macOS or Linux. + // Why: fold by path shape, not host platform — WSL/SSH Windows remotes need folding even when Orca runs on macOS/Linux. const foldedPath = normalizeCodexProjectPathForLookup(parsed ? parsed.sourcePath : key) return parsed ? `${foldedPath}:${parsed.eventLabel}:${parsed.groupIndex}:${parsed.handlerIndex}` @@ -578,8 +518,7 @@ type ParsedTomlString = { endIndex: number } -// Why: Codex can write hook-state dotted keys as TOML basic strings while -// users/Codex TUI may leave equivalent literal-string keys behind. +// Why: hook-state keys appear as both TOML basic strings and equivalent literal-string keys. function parseHookStateHeaderKey(line: string): string | null { const trimmed = line.trimStart() const prefixMatch = /^\[[ \t]*hooks[ \t]*\.[ \t]*state[ \t]*\.[ \t]*/.exec(trimmed) @@ -599,8 +538,7 @@ function parseHookStateHeaderKey(line: string): string | null { } export function parseCodexProjectHeaderPath(line: string): string | null { - // Why: mirror section headers come from split CRLF files and retain the - // terminal carriage return, while direct upserts scan CR-stripped lines. + // Why: mirror section headers retain a terminal CR (split CRLF files) while direct upserts scan CR-stripped lines. const trimmed = line.replace(/\r$/, '').trimStart() const prefixMatch = /^\[[ \t]*projects[ \t]*\.[ \t]*/.exec(trimmed) if (!prefixMatch) { @@ -688,9 +626,7 @@ function skipTomlInlineWhitespace(line: string, startIndex: number): number { } return index } -// Why: quoted keys can contain `]` (e.g. `[hooks.state."a]b"]`) and `[` lines -// inside multi-line strings aren't headers, so we need a stateful scanner — -// a flat regex misclassifies both cases. +// Why: quoted keys can contain `]` and `[` lines inside multi-line strings aren't headers, so a flat regex misclassifies both — need a stateful scan. function findNextTableHeader(text: string): number { let cursor = 0 let scanState = createTomlLineScanState() @@ -701,9 +637,7 @@ function findNextTableHeader(text: string): number { const line = rawLine.replace(/\r$/, '') if (isTomlStructuralLine(scanState)) { const trimmed = line.trimStart() - // Why: stop at both `[table]` and `[[array.of.tables]]` — both end our - // block. Skipping `[[ ]]` here would let our slice consume past array - // entries into unrelated user content. + // Why: stop at both `[table]` and `[[array.of.tables]]`; skipping `[[ ]]` would let the slice consume unrelated content. if (trimmed.startsWith('[') && isCompleteTableHeader(trimmed)) { return cursor } @@ -717,10 +651,7 @@ function findNextTableHeader(text: string): number { return -1 } -// Why: walk the header byte-by-byte so `]` inside a quoted key segment -// doesn't terminate us early. Basic strings honor `\` escapes; literal -// strings (single quotes) don't allow escapes per TOML spec. -// Accepts both `[table]` and `[[array.of.tables]]` since either ends a block. +// Why: walk byte-by-byte so a `]` inside a quoted key segment doesn't terminate the header early. function isCompleteTableHeader(line: string): boolean { if (!line.startsWith('[')) { return false @@ -775,10 +706,7 @@ function isCompleteTableHeader(line: string): boolean { return false } -// Why: same atomic-rename + .bak rotation pattern as writeHooksJson — a -// half-written config.toml can brick a user's Codex install, so write to -// tmp and rename. Random-suffix tmp name avoids cross-process races on -// rapid reinstalls. +// Why: a half-written config.toml can brick Codex, so write to a random-suffix tmp then rename (.bak rotation), avoiding cross-process races. export function writeConfigAtomically(configPath: string, contents: string): void { const dir = dirname(configPath) mkdirSync(dir, { recursive: true }) @@ -838,8 +766,7 @@ export function readHookTrustEntries(configPath: string): Map): void { - // Why: Codex rejects unknown top-level hooks.json fields, so plugin manager - // bookkeeping such as `_managed` must not survive Orca's rewrite. + // Why: Codex rejects unknown top-level hooks.json fields, so plugin bookkeeping like `_managed` must not survive Orca's rewrite. writeHooksJson(configPath, { hooks }) } @@ -95,8 +90,7 @@ function getCodexConfigTomlPath(): string { return join(getOrcaManagedCodexHomePath(), 'config.toml') } -// Why: the managed-event subset of the shared PascalCase→label map; the -// full mapping lives in codex-hook-identity.ts so promotion can't drift. +// Why: managed-event subset of the shared label map; full mapping lives in codex-hook-identity.ts so promotion can't drift. const CODEX_EVENT_LABEL: Record<(typeof CODEX_EVENTS)[number], CodexEventLabel> = { SessionStart: CODEX_HOOK_EVENT_LABEL.SessionStart!, UserPromptSubmit: CODEX_HOOK_EVENT_LABEL.UserPromptSubmit!, @@ -141,8 +135,7 @@ export type { CodexWslRuntimeHookInstallPlan } function wrapReadablePosixHookCommand(scriptPath: string): string { const quoted = `'${scriptPath.replaceAll("'", "'\\''")}'` - // Why: WSL runtime hooks are written from Windows through UNC, where the - // executable bit is not reliable; a missing script must still own stdin. + // Why: WSL hooks are written from Windows over UNC where the exec bit is unreliable; a missing script must still own stdin. return `if [ -f ${quoted} ] && [ -r ${quoted} ]; then /bin/sh ${quoted}; else ${POSIX_HOOK_STDIN_DRAIN_COMMAND}; fi` } @@ -240,9 +233,7 @@ function commandUsesCodexPluginOnlyPlaceholder(command: string | undefined): boo } function removeCodexPluginEnvironmentCommands(definitions: HookDefinition[]): HookDefinition[] { - // Why: Orca mirrors system hooks into a plain runtime hooks.json. Plugin - // placeholders only work for Codex plugin hook sources, so copying those - // commands here strips the environment they require and turns them into 127s. + // Why: plugin placeholders only resolve for Codex plugin hook sources; mirroring them into a plain runtime hooks.json turns them into 127s. return removeManagedCommands(definitions, commandUsesCodexPluginOnlyPlaceholder) } @@ -282,9 +273,7 @@ function getRuntimeHooksWithSystemUserHooks( continue } - // Why: runtime hooks are derived from the user's system hooks plus Orca's - // managed hooks. Reusing old runtime user-hook copies would keep deleted or - // edited ~/.codex/hooks.json entries alive for new Orca-launched sessions. + // Why: rebuild from system hooks; reusing old runtime copies would keep deleted/edited ~/.codex/hooks.json entries alive for new sessions. nextHooks[eventName] = dedupeHookDefinitions(systemUserDefinitions) } @@ -314,8 +303,7 @@ function getTrustedSystemUserHookSignatures( try { trustEntries = readHookTrustEntries(getSystemCodexConfigTomlPath()) } catch (error) { - // Why: a hand-broken system config.toml should only disable user-hook - // trust mirroring; Orca's managed runtime hooks can still be installed. + // Why: a hand-broken system config.toml should only disable user-hook trust mirroring, not block Orca's managed runtime hooks. console.warn('[codex-hook-service] failed to read system hook trust entries', error) return signatures } @@ -346,8 +334,7 @@ function getTrustedSystemUserHookSignatures( return } const signature = getCodexHookTrustSignature(entry) - // Why: runtime deduping collapses identical system hook definitions; - // if any duplicate remains enabled, keep the mirrored hook enabled. + // Why: runtime deduping collapses identical definitions; if any duplicate stays enabled, keep the mirrored hook enabled. if (state.enabled || !signatures.has(signature)) { signatures.set(signature, state) } @@ -372,11 +359,7 @@ function resolveTrustedSystemHookState( return { enabled: reorderedEnabled, trustedHash: expectedHash } } if (state?.trustedHash) { - // Why: carry a key-matched system hash verbatim instead of dropping it as - // stale. Codex is the authority on its own hash algorithm; recomputing - // here is what turned #7110-style hash drift into an endless re-approval - // loop. If the hash is genuinely stale (edited hook), Codex prompts — - // exactly what a plain ~/.codex session would do. + // Why: carry a key-matched system hash verbatim — recomputing caused #7110 re-approval loops since Codex owns its hash algorithm. return { enabled: state.enabled !== false, trustedHash: state.trustedHash } } return null @@ -402,8 +385,7 @@ function getTrustedSystemHookHashesByEvent( trustedHashesByEvent.set(parsed.eventLabel, hashes) } const enabled = state.enabled !== false - // Why: Codex trust keys include hook indices. If a user reorders hooks, - // the hash still proves the same event+command identity was approved. + // Why: Codex trust keys include hook indices, but the hash still proves the same event+command identity was approved after a reorder. if (enabled || !hashes.has(state.trustedHash)) { hashes.set(state.trustedHash, enabled) } @@ -488,8 +470,7 @@ function buildHookTrustHeaderKeyPattern(key: string): string { const alternatives = [...new Set(keyVariants)].flatMap((variant) => { const quoted = [`"${escapeRegex(escapeTomlString(variant))}"`] if (!variant.includes("'")) { - // Why: tolerate raw-backslash literal keys left by Codex/manual approval - // while Orca repairs mirrored runtime trust across both Windows variants. + // Why: tolerate raw-backslash literal keys from Codex/manual approval while repairing mirrored runtime trust across both Windows variants. quoted.push(`'${escapeRegex(variant)}'`) } return quoted @@ -573,11 +554,9 @@ function cleanupLegacySystemManagedHooks(): void { } } - // Why: Codex hooks moved to Orca's managed CODEX_HOME; old entries in - // ~/.codex would keep external Codex sessions reporting into Orca. + // Why: Codex hooks moved to Orca's managed CODEX_HOME; stale ~/.codex entries would keep external Codex sessions reporting into Orca. if (removedManagedHook) { - // Why: this is the user's system hooks file, not Orca's runtime copy. - // Remove only stale Orca hook entries and preserve other managers' metadata. + // Why: this is the user's system hooks file, not Orca's runtime copy; remove only stale Orca entries and preserve other managers' metadata. writeHooksJson(legacyConfigPath, { ...config, hooks: nextHooks }) } removeMatchingTrustEntries(getSystemCodexConfigTomlPath(), trustEntries) @@ -612,8 +591,7 @@ function cleanupLegacyCodexProfileHooks(): void { if (next === existing) { return } - // Why: #2778 wrote Orca hooks into a Codex profile file. Runtime CODEX_HOME - // supersedes that representation, so remove only Orca's marked block. + // Why: #2778 wrote Orca hooks into a Codex profile file; runtime CODEX_HOME supersedes it, so remove only Orca's marked block. if (next.trim().length === 0) { unlinkSync(profilePath) } else { @@ -639,10 +617,7 @@ function removeRuntimeManagedHookTrustEntries(configPath: string): void { const managedEventLabels = new Set( CODEX_EVENTS.map((event) => CODEX_EVENT_LABEL[event]) ) - // Why: only drop entries WE wrote. The same config.toml can contain - // user-approved trust entries for non-Orca commands, so match by hash - // equivalence to our managed command — a sourcePath-only filter would - // wipe the user's manually-approved entries. + // Why: drop only entries we wrote — match by hash to our managed command, since a sourcePath-only filter would wipe user-approved non-Orca entries. const ourKeys: string[] = [] const canonicalConfigPath = getCodexCanonicalTrustPath(configPath) for (const [key, state] of existingEntries) { @@ -662,8 +637,7 @@ function removeRuntimeManagedHookTrustEntries(configPath: string): void { groupIndex: parts.groupIndex, handlerIndex: parts.handlerIndex, command, - // Why: match the timeout install() wrote, or remove() would fail to - // recognize (and clean up) its own managed trust entries. + // Why: match the timeout install() wrote, or remove() can't recognize and clean up its own managed trust entries. timeoutSec: MANAGED_HOOK_TIMEOUT_SECONDS } const recognizedHashes = new Set([ @@ -679,8 +653,7 @@ function removeRuntimeManagedHookTrustEntries(configPath: string): void { removeHookTrustEntries(tomlPath, ourKeys) } } catch (error) { - // Best effort — stale trust entries are harmless once hooks.json no - // longer references the hook. Log so a programmer error doesn't disappear silently. + // Best effort — stale trust is harmless once hooks.json no longer references the hook; log so a programmer error isn't silent. console.warn('[codex-hook-service] failed to clean trust entries', error) } } @@ -725,8 +698,7 @@ function removeWslRuntimeManagedHookTrustEntries(plan: CodexWslRuntimeHookInstal removeHookTrustEntries(plan.tomlPath, ourKeys) } } catch (error) { - // Why: removing disabled WSL status hooks should be best-effort like the - // host cleanup path; stale trust is inert once hooks.json no longer points at us. + // Why: best-effort like host cleanup; stale trust is inert once hooks.json no longer points at us. console.warn('[codex-hook-service] failed to clean WSL trust entries', error) } } @@ -749,8 +721,7 @@ function removeStaleWslRuntimeManagedHookTrustEntries( continue } const sourcePath = parts.sourcePath - // Why: this cleanup owns only guest-side WSL trust. A runtime config can - // still contain user Windows/remote hooks, which must remain untouched. + // Why: this cleanup owns only guest-side WSL trust; leave any user Windows/remote hooks in the runtime config untouched. if (!sourcePath.startsWith('/') || !sourcePath.endsWith('/hooks.json')) { continue } @@ -782,10 +753,7 @@ function getManagedScript(target: 'local' | 'posix' = 'local'): string { return [ '@echo off', 'setlocal', - // Why: see claude/hook-service.ts for rationale. The endpoint file holds - // the live port/token for this Orca install; sourcing it here lets a - // surviving PTY reach the current server even though its env points at - // the prior Orca's coordinates. + // Why: the endpoint file holds this install's live port/token; sourcing it lets a surviving PTY reach the current server (see claude/hook-service.ts). 'if defined ORCA_AGENT_HOOK_ENDPOINT if exist "%ORCA_AGENT_HOOK_ENDPOINT%" call "%ORCA_AGENT_HOOK_ENDPOINT%" 2>nul', ...buildWindowsHookEnvironmentGuardLines(), buildWindowsAgentHookCurlPostCommand('codex'), @@ -798,15 +766,12 @@ function getManagedScript(target: 'local' | 'posix' = 'local'): string { return [ '#!/bin/sh', ...buildPosixHookPayloadCapture(), - // Why: see claude/hook-service.ts for rationale. Sourcing refreshes - // PORT/TOKEN/ENV/VERSION from the current Orca so a surviving PTY keeps - // reporting after a restart. + // Why: sourcing refreshes PORT/TOKEN/ENV/VERSION from the current Orca so a surviving PTY keeps reporting after a restart (see claude/hook-service.ts). 'load_hook_endpoint() {', ' endpoint_path="$1"', ' case "$endpoint_path" in', ' *.cmd)', - // Why: Windows passes endpoint.cmd into WSL through WSLENV path translation. - // Parse only Orca's known assignments; cmd.exe `set` lines are not shell syntax. + // Why: Windows passes endpoint.cmd into WSL via WSLENV; parse only Orca's known assignments since cmd.exe `set` lines aren't shell syntax. ' endpoint_cr=$(printf "\\r")', ' while IFS= read -r endpoint_line || [ -n "$endpoint_line" ]; do', ' endpoint_line=${endpoint_line%"$endpoint_cr"}', @@ -833,13 +798,8 @@ function getManagedScript(target: 'local' | 'posix' = 'local'): string { ' curl_bin="$1"', ' connect_timeout="${2:-0.5}"', ' max_time="${3:-1.5}"', - // Why: worktreeId embeds a filesystem path, so hand-building JSON in POSIX - // shell is not safe once a path contains quotes or newlines. Post the raw - // hook payload plus metadata as form fields and let the receiver parse it. - // Timeout caps best-effort hook posts if the local listener stalls. - // Why: pipe payload to curl's stdin (`payload@-`) instead of an inline - // `payload=$VALUE` arg, so tens-of-KB tool output stays off the curl - // command line (EDR command-line false positives). Wire body is identical. + // Why: worktreeId embeds a path, so hand-building JSON in shell is unsafe with quotes/newlines; post raw payload plus metadata as form fields instead. + // Why: pipe payload to curl's stdin (`payload@-`) not an inline arg, so tens-of-KB tool output stays off the command line (EDR false positives). ' printf \'%s\' "$payload" | "$curl_bin" -sS -X POST "http://127.0.0.1:${ORCA_AGENT_HOOK_PORT}/hook/codex" \\', ' --connect-timeout "$connect_timeout" --max-time "$max_time" \\', ' --noproxy "127.0.0.1" \\', @@ -923,8 +883,7 @@ function installManagedHooksIntoWslRuntime( writeManagedScript(plan.scriptPath, getManagedScript('posix')) writeCodexHooksJson(plan.configPath, nextHooks) try { - // Why: WSL runtime homes may carry user hook approvals we did not rebuild - // here; only upsert Orca's entries instead of sweeping the whole source. + // Why: WSL runtime homes may carry user hook approvals we didn't rebuild; upsert only Orca's entries instead of sweeping the whole source. upsertHookTrustEntries(plan.tomlPath, trustEntries) removeStaleWslRuntimeManagedHookTrustEntries(plan.tomlPath, trustEntries) } catch (error) { @@ -974,8 +933,7 @@ function refreshWslRuntimeUserHooks(plan: CodexWslRuntimeHookInstallPlan): Agent writeCodexHooksJson(plan.configPath, nextHooks) removeWslRuntimeManagedHookTrustEntries(plan) try { - // Why: the disabled path may be reached after the WSL mount root changed, - // so cleanup cannot be scoped only to the plan's current source path. + // Why: the disabled path may run after the WSL mount root changed, so cleanup can't be scoped to the plan's current source path. removeStaleWslRuntimeManagedHookTrustEntries(plan.tomlPath, []) } catch (error) { console.warn('[codex-hook-service] failed to clean stale WSL trust entries', error) @@ -989,8 +947,7 @@ function refreshWslRuntimeUserHooks(plan: CodexWslRuntimeHookInstallPlan): Agent } } -// Why: transport failures preserve the last known-good identity, while a -// successful absence probe is strong enough to revoke trust immediately. +// Why: transport failures preserve last known-good identity; a successful absence probe is strong enough to revoke trust immediately. function getWslHookReconciliationAction(args: { settlement: WslCanonicalPathSettlement isCurrentGeneration: boolean @@ -1013,8 +970,7 @@ function getWslHookReconciliationAction(args: { return 'reinstall' } -// Why: fold only the Windows-case-insensitive portion — a full lowercase would -// let case-distinct WSL runtime homes share one reconciliation generation slot. +// Why: fold only the Windows-case-insensitive portion; a full lowercase would let case-distinct WSL homes share one reconciliation slot. function getWslReconciliationKey(runtimeHomePath: string): string { return normalizeCodexProjectPathForLookup(runtimeHomePath) } @@ -1114,15 +1070,10 @@ export class CodexHookService { } } - // Why: Report `partial` when managed events are missing OR when their - // trust entries are missing/stale. Codex 0.129+ silently drops untrusted - // hooks, so a green status without trust verification is misleading. + // Why: Codex 0.129+ silently drops untrusted hooks, so report `partial` when managed events OR their trust entries are missing/stale. const command = getManagedCommand(scriptPath) const tomlPath = getCodexConfigTomlPath() - // Why: an unreadable config.toml (EACCES/EIO) is distinct from "file - // absent" (which returns an empty Map without throwing). Hooks.json may - // still be fine, so report partial with a specific reason rather than - // collapsing to a generic error or masking it as universally-stale trust. + // Why: an unreadable config.toml (EACCES/EIO) differs from "absent" (empty Map); hooks.json may be fine, so report partial with a specific reason. let trustEntries: Map let trustReadError: string | null = null try { @@ -1138,16 +1089,12 @@ export class CodexHookService { let presentCount = 0 for (const eventName of CODEX_EVENTS) { const definitions = Array.isArray(config.hooks?.[eventName]) ? config.hooks![eventName]! : [] - // Why: older installs appended this command, while current installs - // prepend it. Picking the last match keeps status repair conservative - // if duplicate managed definitions survive from a stale hooks.json. + // Why: older installs appended, current ones prepend; last-match keeps status repair conservative when stale duplicate definitions survive. let foundGroupIndex = -1 let foundHandlerIndex = -1 definitions.forEach((definition, idx) => { const hooks = definition.hooks ?? [] - // Why: mirror the LAST-match-wins rule at the group level — if a user - // merged hook arrays and ended up with our command at multiple indices - // in one group, the surviving runtime entry is the last one. + // Why: last-match-wins at the group level — if merged hook arrays repeat our command, the surviving runtime entry is the last one. const handlerIdx = hooks.findLastIndex((hook) => hook.command === command) if (handlerIdx !== -1) { foundGroupIndex = idx @@ -1159,14 +1106,9 @@ export class CodexHookService { continue } presentCount += 1 - // Why: a stale hash blocks firing the same as a missing entry, so - // compare against the canonical hash we would write. - // Why: capture the actual handler index — Codex's hook_key uses the - // positional handlerIndex, and a user-merged hook array can put our - // command at a non-zero slot, so hardcoding 0 would misreport trust. - // Why: the managed hook is written with `timeout` (see install()), and - // Codex folds the handler timeout into its trust hash. Hash the same - // timeout here or status would report every managed hook as stale-trust. + // Why: a stale hash blocks firing like a missing entry, so compare against the canonical hash we would write. + // Why: Codex's hook_key is positional, so hardcoding handlerIndex 0 misreports trust for user-merged hook arrays. + // Why: hash the same `timeout` install() writes, since Codex folds it into the trust hash or every managed hook reports stale-trust. const trustInput: CodexTrustEntry = { sourcePath: configPath, eventLabel: CODEX_EVENT_LABEL[eventName], @@ -1188,8 +1130,7 @@ export class CodexHookService { let detail: string | null if (presentCount === 0) { state = 'not_installed' - // Why: surface the trust read error even when not_installed so the user - // has actionable info if config.toml is broken. + // Why: surface the trust read error even when not_installed, so a broken config.toml gives actionable info. detail = trustReadError !== null ? `Trust entries unverifiable: ${trustReadError}` : null } else if ( missing.length === 0 && @@ -1221,10 +1162,7 @@ export class CodexHookService { install(): AgentHookInstallStatus { const configPath = getConfigPath() const scriptPath = getManagedScriptPath() - // Why: must run before this install rewrites hooks.json/config.toml — - // approvals the user made inside Orca-launched Codex are keyed to the - // previous launch's runtime layout, and stale-trust cleanup below would - // delete them once the system config stops backing them. + // Why: run before install rewrites hooks.json/config.toml, since stale-trust cleanup would delete in-Orca approvals keyed to the prior layout. promoteCodexRuntimeHookApprovalsToSystem() const config = readHooksJson(configPath) if (!config) { @@ -1237,29 +1175,20 @@ export class CodexHookService { } } - // Why: match by script filename (not exact command string) so a fresh - // install sweeps stale entries left by older builds or a different - // Electron userData path (dev vs. prod). Without this, repeated installs - // accumulate duplicate hook entries pointing at defunct scripts. + // Why: match by script filename (not exact command) so a fresh install sweeps stale entries from older builds or a different userData path. const isManagedCommand = createManagedCommandMatcher(getCodexManagedScriptFileName()) const command = getManagedCommand(scriptPath) const hookPlan = getRuntimeHooksWithSystemUserHooks(config.hooks, isManagedCommand) const nextHooks = hookPlan.hooks const managedEvents = new Set(CODEX_EVENTS) - // Why: sweep managed entries out of events we no longer subscribe to - // (e.g., PreToolUse from a prior install). Without this, users who - // already had PreToolUse registered would keep firing stale hooks on - // every auto-approved tool call after the app upgrade. + // Why: sweep managed entries from events we no longer subscribe to (e.g. a prior install's PreToolUse), else they keep firing stale hooks after upgrade. for (const [eventName, definitions] of Object.entries(nextHooks)) { if (managedEvents.has(eventName)) { continue } if (!Array.isArray(definitions)) { - // Why: a malformed hooks.json entry (non-array value for an event name) - // would make removeManagedCommands throw. Skip instead — we aren't - // going to sweep something we can't parse, and the install() for - // managed events below still runs. + // Why: a non-array event value would make removeManagedCommands throw; skip the unparsable entry, managed events below still install. continue } const cleaned = removeManagedCommands(definitions, isManagedCommand) @@ -1270,10 +1199,7 @@ export class CodexHookService { } } - // Why: Codex 0.129+ requires a per-hook trust entry in config.toml or the - // hook sits in the "review required" pile. We compute the trust hash for - // each managed entry as we install it and persist it alongside hooks.json - // so the user does not have to /hooks-approve after every install. + // Why: Codex 0.129+ requires a per-hook config.toml trust entry or the hook needs manual /hooks-approve; precompute the hash to avoid that. const mirroredUserTrustEntries = moveMirroredRuntimeUserTrustAfterManagedStatusHook( hookPlan.trustEntries ) @@ -1285,11 +1211,8 @@ export class CodexHookService { hooks: [buildManagedCommandHook(command)] } nextHooks[eventName] = [definition, ...cleaned] - // Why: the status hook must run before user hooks so a slow - // PostToolUse/Stop hook cannot leave the sidebar stuck on the previous - // state while Codex visibly reports that hooks are still running. - // timeoutSec mirrors the hook's `timeout` so the trust hash matches the - // entry actually written to hooks.json. + // Why: status hook runs before user hooks so a slow PostToolUse/Stop hook can't leave the sidebar stuck on the previous state. + // Why: timeoutSec mirrors the hook's `timeout` so the trust hash matches the entry written to hooks.json. trustEntries.push({ sourcePath: configPath, eventLabel: CODEX_EVENT_LABEL[eventName], @@ -1303,17 +1226,12 @@ export class CodexHookService { config.hooks = nextHooks writeManagedScript(scriptPath, getManagedScript()) writeCodexHooksJson(configPath, nextHooks) - // Why: trust entries write last so a half-write can't leave a hash - // pointing at a hook that doesn't exist. Surface failures — without this, - // getStatus would report green for a hook Codex won't actually fire. + // Why: trust entries write last so a half-write can't leave a hash pointing at a nonexistent hook. + // Why: surface trust-write failures — otherwise getStatus reports green for a hook Codex won't fire. try { const tomlPath = getCodexConfigTomlPath() syncSystemConfigIntoManagedCodexHome() - // Why: system user hook approvals are mirrored into runtime CODEX_HOME. - // If the user later revokes approval in ~/.codex/config.toml, preserving - // all old runtime [hooks.state.*] blocks would keep Orca Codex trusted. - // Upsert first so duplicate repair can preserve a disabled managed copy - // before stale cleanup removes old managed hook keys. + // Why: don't let revoked ~/.codex approvals survive as stale runtime trust; upsert before cleanup to preserve a disabled managed copy. upsertHookTrustEntries(tomlPath, trustEntries) removeStaleRuntimeHookTrustEntries(tomlPath, configPath, trustEntries) applyMirroredRuntimeUserHookTrustStates(tomlPath, mirroredUserTrustEntries) @@ -1340,14 +1258,9 @@ export class CodexHookService { sftp: SFTPWrapper, remoteHome: string, options?: { - /** Explicit CODEX_HOME dir (flat layout: hooks.json/config.toml at its - * root). WSL sessions read Orca's managed runtime home, not ~/.codex — - * installing to the default location leaves those sessions hookless. */ + /** Explicit CODEX_HOME dir (flat layout). WSL sessions read Orca's managed runtime home, not ~/.codex, so the default location leaves them hookless. */ codexHomeDir?: string - /** Skip the trust write when config.toml doesn't exist yet. The WSL - * runtime home's config.toml is seeded only-if-absent by the launch - * path; creating it here first would silently cancel that seed. A - * later (idempotent) reinstall upserts trust once the seed lands. */ + /** Skip the trust write when config.toml is absent — the WSL launch path seeds it only-if-absent, so creating it here would cancel that seed. */ deferTrustUntilConfigToml?: boolean } ): Promise { @@ -1404,13 +1317,10 @@ export class CodexHookService { } config.hooks = nextHooks - // Why: script/settings first, trust TOML last. A partial trust write - // leaves Codex asking for approval rather than executing a missing script. - // Why: SSH remotes use POSIX `.sh` hook paths even when Orca itself is - // running on Windows; never derive remote script syntax from local OS. + // Why: write script/settings before trust TOML; a partial trust write leaves Codex asking approval instead of running a missing script. + // Why: SSH remotes use POSIX `.sh` paths even when Orca runs on Windows; never derive remote script syntax from local OS. await writeManagedScriptRemote(sftp, remoteScriptPath, getManagedScript('posix')) - // Why: SSH installs edit the user's remote ~/.codex/hooks.json directly. - // Preserve non-Orca top-level metadata while replacing the hooks tree. + // Why: SSH edits the user's remote ~/.codex/hooks.json directly, so preserve non-Orca top-level metadata while replacing the hooks tree. await writeHooksJsonRemote(sftp, remoteConfigPath, { ...config, hooks: nextHooks }) try { const existingTomlRaw = await readTextFileRemote(sftp, remoteTomlPath) @@ -1460,13 +1370,11 @@ export class CodexHookService { refreshRuntimeUserHooks(): AgentHookInstallStatus { const configPath = getConfigPath() - // Why: same as install() — capture in-Orca approvals before this refresh - // rewrites the runtime files they are keyed against. + // Why: capture in-Orca approvals before this refresh rewrites the runtime files they are keyed against. promoteCodexRuntimeHookApprovalsToSystem() const config = readHooksJson(configPath) if (!config) { - // Why: disabled launch prep used to call remove(); preserve its legacy - // cleanup behavior even when runtime hooks.json is malformed. + // Why: disabled launch prep once called remove(); preserve that legacy cleanup even when runtime hooks.json is malformed. cleanupLegacyManagedHookRepresentations() return { agent: 'codex', @@ -1486,10 +1394,7 @@ export class CodexHookService { const tomlPath = getCodexConfigTomlPath() const trustEntries = hookPlan.trustEntries.map(({ entry }) => entry) syncSystemConfigIntoManagedCodexHome() - // Why: this path is used when Orca status hooks are disabled. The - // runtime CODEX_HOME should keep user hooks, but not Orca-managed trust. - // Write current mirrored user trust first so stale cleanup compares - // against current hashes while deleting old managed hook keys. + // Why: disabled path keeps user hooks but not managed trust; write mirrored trust first so stale cleanup compares against current hashes. upsertHookTrustEntries(tomlPath, trustEntries) removeStaleRuntimeHookTrustEntries(tomlPath, configPath, trustEntries) applyMirroredRuntimeUserHookTrustStates(tomlPath, hookPlan.trustEntries) @@ -1513,8 +1418,7 @@ export class CodexHookService { const configExists = existsSync(configPath) const config = readHooksJson(configPath) if (!config) { - // Why: a malformed runtime hooks.json should not strand old hooks in - // ~/.codex or the legacy profile after the user disables Codex hooks. + // Why: a malformed hooks.json shouldn't strand old hooks in ~/.codex or the legacy profile after disabling. cleanupLegacyManagedHookRepresentations() return { agent: 'codex', @@ -1526,14 +1430,11 @@ export class CodexHookService { } const nextHooks = { ...config.hooks } - // Why: same broad matcher as install(), so remove() also cleans up stale - // entries from older builds even if the current scriptPath has moved. + // Why: same broad matcher as install() so stale entries from older builds get cleaned even if scriptPath moved. const isManagedCommand = createManagedCommandMatcher(getCodexManagedScriptFileName()) for (const [eventName, definitions] of Object.entries(nextHooks)) { if (!Array.isArray(definitions)) { - // Why: a malformed hooks.json entry (non-array value for an event name) - // would make removeManagedCommands throw. Skip instead — we have no - // managed commands to remove from something we can't parse. + // Why: a non-array event value would make removeManagedCommands throw; skip it. continue } const cleaned = removeManagedCommands(definitions, isManagedCommand) @@ -1544,14 +1445,11 @@ export class CodexHookService { } } if (configExists) { - // Why: remove() can be the only repair path for a parseable runtime file - // whose top-level plugin metadata makes Codex reject hooks.json. + // Why: remove() may be the only repair path for a file whose top-level plugin metadata makes Codex reject hooks.json. writeCodexHooksJson(configPath, nextHooks) } - // Why: also drop our trust entries so config.toml doesn't accumulate dead - // [hooks.state."..."] blocks across install/remove cycles. Best-effort — - // a stale entry is harmless once hooks.json no longer references it. + // Why: drop trust entries so config.toml doesn't accumulate dead [hooks.state] blocks across install/remove cycles. removeRuntimeManagedHookTrustEntries(configPath) cleanupLegacyManagedHookRepresentations() diff --git a/src/main/cursor/hook-service.ts b/src/main/cursor/hook-service.ts index b1ccdbf126d..9ce9172f8fb 100644 --- a/src/main/cursor/hook-service.ts +++ b/src/main/cursor/hook-service.ts @@ -26,22 +26,8 @@ import { buildWindowsHookStdinDrainEpilogue } from '../agent-hooks/hook-stdin-contract' -// Why: cursor-agent exposes a declarative hooks.json surface at -// ~/.cursor/hooks.json (https://cursor.com/docs/hooks) with camelCase event -// names. We subscribe to the minimum set that drives the sidebar spinner and -// turn-boundary detection: -// - beforeSubmitPrompt: new turn starts (carries the user's prompt) -// - stop: turn ends (→ done) -// - preToolUse/postToolUse/postToolUseFailure: in-flight tool preview -// between submit and stop — without these the pane appears idle for the -// entire duration of a long tool-heavy turn -// - beforeShellExecution / beforeMCPExecution: shell/MCP tool preview (→ working) -// - afterAgentResponse: carries the final composed reply text so the -// dashboard can surface it on done -// sessionStart / sessionEnd are intentionally NOT subscribed — cursor-agent -// fires them at process lifetime boundaries rather than at turn boundaries, -// and sessionStart's fire-time can race the first beforeSubmitPrompt in a -// way that would reset the prompt cache for the just-submitted turn. +// cursor-agent's declarative hooks surface (https://cursor.com/docs/hooks); subscribe to the minimum set for spinner + turn detection. +// sessionStart/sessionEnd are NOT subscribed: they fire at process (not turn) boundaries and can race/reset the just-submitted turn's prompt cache. const CURSOR_EVENTS = [ 'beforeSubmitPrompt', 'stop', @@ -76,10 +62,7 @@ function getManagedScript(target: 'local' | 'posix' = 'local'): string { return [ '@echo off', 'setlocal', - // Why: see claude/hook-service.ts for rationale. The endpoint file holds - // the live port/token for this Orca install; sourcing it here lets a - // surviving PTY reach the current server even though its env points at - // the prior Orca's coordinates. + // Why: source the endpoint file so a surviving PTY reaches the current server, not the prior Orca's coordinates (see claude/hook-service.ts). 'if defined ORCA_AGENT_HOOK_ENDPOINT if exist "%ORCA_AGENT_HOOK_ENDPOINT%" call "%ORCA_AGENT_HOOK_ENDPOINT%" 2>nul', ...buildWindowsHookEnvironmentGuardLines(), buildWindowsAgentHookPostCommand('cursor'), @@ -92,22 +75,15 @@ function getManagedScript(target: 'local' | 'posix' = 'local'): string { return [ '#!/bin/sh', ...buildPosixHookPayloadCapture(), - // Why: see claude/hook-service.ts for rationale. Sourcing refreshes - // PORT/TOKEN/ENV/VERSION from the current Orca so a surviving PTY keeps - // reporting after a restart. + // Why: sourcing refreshes PORT/TOKEN/ENV from the current Orca so a surviving PTY keeps reporting after a restart (see claude/hook-service.ts). 'if [ -n "$ORCA_AGENT_HOOK_ENDPOINT" ] && [ -r "$ORCA_AGENT_HOOK_ENDPOINT" ]; then', ' . "$ORCA_AGENT_HOOK_ENDPOINT" 2>/dev/null || :', 'fi', 'if [ -z "$ORCA_AGENT_HOOK_PORT" ] || [ -z "$ORCA_AGENT_HOOK_TOKEN" ] || [ -z "$ORCA_PANE_KEY" ]; then', ' exit 0', 'fi', - // Why: worktreeId embeds a filesystem path, so hand-building JSON in POSIX - // shell is not safe once a path contains quotes or newlines. Post the raw - // hook payload plus metadata as form fields and let the receiver parse it. - // Timeout caps best-effort hook posts if the local listener stalls. - // Why: pipe payload to curl's stdin (`payload@-`) instead of an inline - // `payload=$VALUE` arg, so tens-of-KB tool output stays off the curl - // command line (EDR command-line false positives). Wire body is identical. + // Why: worktreeId embeds a path, so hand-building JSON in shell is unsafe (quotes/newlines); post raw payload as form fields instead. + // Why: pipe payload via curl stdin (`payload@-`), not an inline arg, so large tool output stays off the command line (EDR false positives). 'printf \'%s\' "$payload" | curl -sS -X POST "http://127.0.0.1:${ORCA_AGENT_HOOK_PORT}/hook/cursor" \\', ' --connect-timeout 0.5 --max-time 1.5 \\', ' -H "Content-Type: application/x-www-form-urlencoded" \\', @@ -144,9 +120,7 @@ export class CursorHookService { let presentCount = 0 for (const eventName of CURSOR_EVENTS) { const definitions = Array.isArray(config.hooks?.[eventName]) ? config.hooks![eventName]! : [] - // Why: Cursor's schema places the command directly on the definition - // (not nested under a `hooks` array as Claude does), so match both - // shapes to stay robust against future schema changes. + // Why: Cursor puts command directly on the definition (Claude nests under `hooks`); match both shapes. const hasCommand = definitions.some( (definition) => definition.command === command || @@ -189,22 +163,14 @@ export class CursorHookService { } const command = getManagedCommand(scriptPath) - // Why: Cursor's hooks.json wraps the map under a top-level "hooks" key - // (same as Claude/Codex). A fresh file with no prior hook install will - // have config.hooks === undefined. + // Why: config.hooks is undefined on a fresh file with no prior hook install. const nextHooks = { ...config.hooks } const managedEvents = new Set(CURSOR_EVENTS) - // Why: match by script filename (not exact command string) so a fresh - // install sweeps stale entries left by older builds or a different - // Electron userData path (dev vs. prod). Without this, repeated installs - // accumulate duplicate hook entries pointing at defunct scripts. + // Why: match by script filename (not exact command) so installs sweep stale entries from older builds or a different userData path. const isManagedCommand = createManagedCommandMatcher(getManagedScriptFileName()) - // Why: sweep managed entries out of events we no longer subscribe to - // (e.g. a future rename where we drop an event name). Without this, - // users who already had that event registered would keep firing stale - // hooks after the app upgrade. + // Why: sweep managed entries from events we no longer subscribe to, else upgraded users keep firing stale hooks. for (const [eventName, definitions] of Object.entries(nextHooks)) { if (managedEvents.has(eventName)) { continue @@ -226,24 +192,16 @@ export class CursorHookService { for (const eventName of CURSOR_EVENTS) { const current = Array.isArray(nextHooks[eventName]) ? nextHooks[eventName] : [] - // Sweep both the Claude-shaped (hooks[].command) and Cursor-shaped - // (definition.command) variants so repeated installs converge on a - // single managed entry. + // Sweep both Claude-shaped (hooks[].command) and Cursor-shaped (definition.command) variants so installs converge on one entry. const cleaned = removeManagedCommands(current, isManagedCommand).filter( (definition) => !isManagedCommand(definition.command as string | undefined) ) - // Why: Cursor's documented schema puts `command` directly on the - // definition (not under `hooks`). Emit that shape so cursor-agent - // actually invokes the script. + // Why: Cursor's schema puts `command` directly on the definition (not under `hooks`); emit that shape. const definition: HookDefinition = buildManagedCommandDefinition(command) nextHooks[eventName] = [...cleaned, definition] } - // Why: cursor-agent's config schema requires a top-level `version: 1` - // (see https://cursor.com/docs/hooks). Preserve an existing value if the - // user has already pinned one, otherwise stamp the default so a fresh - // install produces a valid file. HooksConfig has an index signature on - // extra keys, so assign via a Record-typed object to satisfy the type. + // Why: cursor-agent's schema requires top-level `version: 1` (https://cursor.com/docs/hooks); keep any user-pinned value. const nextConfig: Record = { ...config, hooks: nextHooks } if (nextConfig.version === undefined) { nextConfig.version = 1 @@ -253,12 +211,7 @@ export class CursorHookService { return this.getStatus() } - // Why: install Orca's managed Cursor hooks on the remote box. Mirrors - // ClaudeHookService.installRemote — POSIX-only, uses the same SFTP-backed - // primitives, and emits Cursor's documented schema (top-level `command` - // on each definition + top-level `version: 1`) so cursor-agent on the - // remote actually invokes the script. See docs/design/agent-status-over-ssh.md - // §8. + // Installs managed Cursor hooks on an SSH remote (POSIX-only). See docs/design/agent-status-over-ssh.md §8. async installRemote(sftp: SFTPWrapper, remoteHome: string): Promise { const remoteConfigPath = `${remoteHome.replace(/\/$/, '')}/.cursor/hooks.json` const remoteScriptPath = `${remoteHome.replace(/\/$/, '')}/.orca/agent-hooks/cursor-hook.sh` @@ -280,8 +233,7 @@ export class CursorHookService { for (const eventName of CURSOR_EVENTS) { const current = Array.isArray(nextHooks[eventName]) ? nextHooks[eventName] : [] - // Why: same dual-shape sweep as the local install — repeated - // installs converge on a single managed entry. + // Why: dual-shape sweep so repeated installs converge on a single managed entry. const cleaned = removeManagedCommands(current, isManagedCommand).filter( (definition) => !isManagedCommand(definition.command as string | undefined) ) @@ -294,11 +246,8 @@ export class CursorHookService { nextConfig.version = 1 } - // Why: script-then-config order so a partial-failure mid-install at - // worst leaves a working script no settings.json points at — see - // ClaudeHookService.installRemote. - // Why: SSH remotes use POSIX `.sh` hook paths even when Orca itself is - // running on Windows; never derive remote script syntax from local OS. + // Why: script-then-config order so a partial mid-install leaves a working script nothing points at. + // Why: SSH remotes always use POSIX `.sh` hook paths even when Orca runs on Windows; never derive from local OS. await writeManagedScriptRemote(sftp, remoteScriptPath, getManagedScript('posix')) await writeHooksJsonRemote(sftp, remoteConfigPath, nextConfig) diff --git a/src/main/devin/hook-service.ts b/src/main/devin/hook-service.ts index 8b4cc439058..82b00a71c3c 100644 --- a/src/main/devin/hook-service.ts +++ b/src/main/devin/hook-service.ts @@ -39,12 +39,7 @@ function getManagedScript(target: 'local' | 'posix' = 'local'): string { return [ '@echo off', 'setlocal', - // Why: the endpoint file holds the *live* port/token for this Orca - // install. A PTY that survived an Orca restart has stale PORT/TOKEN - // baked into its env from the old instance — loading `endpoint.cmd` - // (`set KEY=VALUE` lines) via `call` refreshes them so the hook - // reaches the current server. Falls through to PTY env if the file - // is missing (first run / pre-endpoint-file / running outside Orca). + // Why: endpoint file holds the live port/token; a PTY that outlives an Orca restart carries stale env, so `call` it to refresh (else PTY env). 'if defined ORCA_AGENT_HOOK_ENDPOINT if exist "%ORCA_AGENT_HOOK_ENDPOINT%" call "%ORCA_AGENT_HOOK_ENDPOINT%" 2>nul', ...buildWindowsHookEnvironmentGuardLines(), buildWindowsAgentHookPostCommand('devin'), @@ -57,33 +52,16 @@ function getManagedScript(target: 'local' | 'posix' = 'local'): string { return [ '#!/bin/sh', ...buildPosixHookPayloadCapture(), - // Why: the endpoint file holds the *live* port/token for this Orca - // install. PTYs that survive an Orca restart have stale PORT/TOKEN - // baked into their env from the old instance — sourcing the file here - // lets us reach the new server. Falls back to PTY env if the file is - // missing (first-run / pre-endpoint-file scripts / running outside Orca). - // Why: suppress stderr on the `.` builtin. A TOCTOU race (endpoint unlinked - // between the `[ -r ]` test and the source) or a malformed line (e.g. CRLF - // bled in from a cross-platform userData copy) would otherwise print a - // parse error that agent transcripts could surface. Stale coords → dead - // port → silent-fail is the documented fail-open path anyway — the env-var - // guards below handle the empty PORT/TOKEN case — so swallowing the noise - // here is strictly better than leaking shell errors into the hook output. - // `|| :` defends against an eventual `set -e` in an outer script context - // (not present today) aborting the hook on a parse error. + // Why: endpoint file holds the live port/token; PTYs that outlive an Orca restart carry stale env, so source it to reach the new server (else PTY env). + // Why: silence the `.` builtin (2>/dev/null + `|| :`) so a TOCTOU race or CRLF-mangled line can't leak shell parse errors into agent transcripts (fail-open). 'if [ -n "$ORCA_AGENT_HOOK_ENDPOINT" ] && [ -r "$ORCA_AGENT_HOOK_ENDPOINT" ]; then', ' . "$ORCA_AGENT_HOOK_ENDPOINT" 2>/dev/null || :', 'fi', 'if [ -z "$ORCA_AGENT_HOOK_PORT" ] || [ -z "$ORCA_AGENT_HOOK_TOKEN" ] || [ -z "$ORCA_PANE_KEY" ]; then', ' exit 0', 'fi', - // Why: worktreeId embeds a filesystem path, so hand-building JSON in POSIX - // shell is not safe once a path contains quotes or newlines. Post the raw - // hook payload plus metadata as form fields and let the receiver parse it. - // Timeout caps best-effort hook posts if the local listener stalls. - // Why: pipe payload to curl's stdin (`payload@-`) instead of an inline - // `payload=$VALUE` arg, so tens-of-KB tool output stays off the curl - // command line (EDR command-line false positives). Wire body is identical. + // Why: worktreeId embeds a filesystem path, so hand-building JSON in shell is unsafe (quotes/newlines); post as form fields instead. + // Why: pipe payload to curl's stdin (payload@-) not an inline arg, so large tool output stays off the command line (EDR false positives). 'printf \'%s\' "$payload" | curl -sS -X POST "http://127.0.0.1:${ORCA_AGENT_HOOK_PORT}/hook/devin" \\', ' --connect-timeout 0.5 --max-time 1.5 \\', ' -H "Content-Type: application/x-www-form-urlencoded" \\', @@ -115,10 +93,7 @@ export class DevinHookService { } } - // Why: Report `partial` when only some managed events are registered so the - // sidebar surfaces a degraded install rather than a false-positive - // `installed`. Each DEVIN_EVENTS entry must contain the managed command for - // the integration to function end-to-end. + // Why: report `partial` when only some managed events are registered, so the sidebar shows a degraded install instead of a false `installed`. const command = getDevinManagedCommand(scriptPath) const missing: string[] = [] let presentCount = 0 @@ -178,28 +153,15 @@ export class DevinHookService { return this.getStatus() } - // Why: install Orca's Devin hook settings on the remote box rather than the - // local machine. Caller passes the user's SFTP handle plus the resolved - // remote `$HOME`; POSIX-only by design (Windows-remote deferred). + // Why: install the Devin hook on the remote box (SFTP handle + resolved remote $HOME); POSIX-only by design. async installRemote(sftp: SFTPWrapper, remoteHome: string): Promise { - // Why: remote-Windows is out of scope for v1 — we ship POSIX-shaped paths - // and a `.sh` managed script body. The remote platform is gated by the - // relay's capability RPC at a higher layer; we cannot detect it from - // `process.platform` here (that's the local box). + // Why: remote-Windows is out of scope for v1; process.platform here is the local box, not the remote, so assume POSIX. const remoteConfigPath = getDevinRemoteConfigPath(remoteHome) const remoteScriptFileName = getDevinPosixManagedScriptFileName() const remoteScriptPath = `${remoteHome.replace(/\/$/, '')}/.orca/agent-hooks/${remoteScriptFileName}` - // Why: SFTP reads/writes fail far more often than local fs (network drops, - // EACCES on remote dirs, disk full, channel closed). Wrap the entire - // install flow in try/catch so a transient I/O failure surfaces as a - // structured `state: 'error'` result for the UI, not an unstructured - // rejection the caller has to remember to handle. A `null` config - // specifically means "file present but unparseable" — keep that branch - // distinct so the user sees an actionable message. + // Why: SFTP I/O fails far more often than local fs; wrap the flow so failures surface as a structured error, not an unhandled rejection. try { - // Why: Devin config.json is JSONC (comments); stock - // JSON.parse rejects them. Read the raw text via SFTP and parse with - // jsonc-parser, mirroring the local readDevinHooksConfig path. + // Why: Devin config.json is JSONC (comments), so JSON.parse rejects it; parse via jsonc-parser. const body = await readTextFileRemote(sftp, remoteConfigPath) const config = body === null ? {} : parseDevinHooksConfigText(body, 'remote Devin config.json') @@ -213,19 +175,12 @@ export class DevinHookService { } } - // Why: the POSIX wrapper is identical regardless of where the script - // lands; only the path differs. Reuse the same wrapper helper. + // Why: the POSIX wrapper body is path-independent, so reuse the same helper. const command = getDevinRemoteManagedCommand(remoteScriptPath) const nextConfig = applyDevinManagedHooks(config, command, remoteScriptFileName) - // Why: write the script first, then the settings — settings.json - // referencing a missing script body would fire `command not found` on - // every tool call until the user re-runs install. Doing it in this - // order means a partial-failure mid-install at worst leaves the user - // with a working script no settings.json points at (a no-op), instead - // of broken settings.json. - // Why: SSH remotes use POSIX `.sh` hook paths even when Orca itself is - // running on Windows; never derive remote script syntax from local OS. + // Why: write script before settings so a mid-install failure never leaves settings.json referencing a missing script. + // Why: SSH remotes use POSIX `.sh` hooks even when Orca runs on Windows; never derive remote script syntax from local OS. await writeManagedScriptRemote(sftp, remoteScriptPath, getManagedScript('posix')) await writeHooksJsonRemote(sftp, remoteConfigPath, nextConfig) diff --git a/src/main/gemini/hook-service.ts b/src/main/gemini/hook-service.ts index e078476edce..1615ea336f9 100644 --- a/src/main/gemini/hook-service.ts +++ b/src/main/gemini/hook-service.ts @@ -27,15 +27,8 @@ import { buildWindowsHookStdinDrainEpilogue } from '../agent-hooks/hook-stdin-contract' -// Why: Gemini CLI fires `BeforeAgent` when a turn starts and `AfterAgent` when -// it completes. `AfterTool` marks the resumption of model work after a tool -// call, which maps back to `working`. Gemini has no permission-prompt hook -// (approvals flow through inline UI), so Orca cannot surface a waiting state -// for Gemini — that is an upstream limitation, not an Orca bug. -// -// Gemini's native pre-tool event is BeforeTool, not Claude/Codex's PreToolUse. -// Keep installing the pre-tool status hook, but sweep stale PreToolUse entries -// below so current Gemini CLI no longer warns about an invalid event bucket. +// Why: Gemini has no permission-prompt hook (approvals are inline UI), so Orca can't show a waiting state — upstream limitation. +// Why: Gemini's pre-tool event is BeforeTool, not Claude/Codex's PreToolUse; sweep stale PreToolUse entries below. const GEMINI_EVENTS = ['BeforeAgent', 'AfterAgent', 'AfterTool', 'BeforeTool'] as const function getConfigPath(): string { @@ -61,14 +54,9 @@ function getManagedScript(target: 'local' | 'posix' = 'local'): string { return [ '@echo off', 'setlocal', - // Why: Gemini expects valid JSON on stdout even when the hook has nothing - // to return. Emit `{}` first so the agent never stalls parsing our - // output, even if the env-var guards below cause an early exit. + // Why: emit `{}` first so Gemini never stalls parsing stdout, even if the guards below exit early. 'echo {}', - // Why: see claude/hook-service.ts for rationale. The endpoint file holds - // the live port/token for this Orca install; sourcing it here lets a - // surviving PTY reach the current server even though its env points at - // the prior Orca's coordinates. + // Why: source the endpoint file so a surviving PTY reaches the current server. See claude/hook-service.ts. 'if defined ORCA_AGENT_HOOK_ENDPOINT if exist "%ORCA_AGENT_HOOK_ENDPOINT%" call "%ORCA_AGENT_HOOK_ENDPOINT%" 2>nul', ...buildWindowsHookEnvironmentGuardLines(), buildWindowsAgentHookPostCommand('gemini'), @@ -80,27 +68,18 @@ function getManagedScript(target: 'local' | 'posix' = 'local'): string { return [ '#!/bin/sh', - // Why: Gemini expects valid JSON on stdout even when the hook has nothing - // to return. Emit `{}` first so the agent never stalls parsing our output, - // even if the env-var guards below cause an early exit. + // Why: emit `{}` first so Gemini never stalls parsing stdout, even if the guards below exit early. 'printf "{}\\n"', ...buildPosixHookPayloadCapture(), - // Why: see claude/hook-service.ts for rationale. Sourcing refreshes - // PORT/TOKEN/ENV/VERSION from the current Orca so a surviving PTY keeps - // reporting after a restart. + // Why: source refreshes endpoint coords so a PTY surviving an Orca restart keeps reporting. See claude/hook-service.ts. 'if [ -n "$ORCA_AGENT_HOOK_ENDPOINT" ] && [ -r "$ORCA_AGENT_HOOK_ENDPOINT" ]; then', ' . "$ORCA_AGENT_HOOK_ENDPOINT" 2>/dev/null || :', 'fi', 'if [ -z "$ORCA_AGENT_HOOK_PORT" ] || [ -z "$ORCA_AGENT_HOOK_TOKEN" ] || [ -z "$ORCA_PANE_KEY" ]; then', ' exit 0', 'fi', - // Why: worktreeId embeds a filesystem path, so hand-building JSON in POSIX - // shell is not safe once a path contains quotes or newlines. Post the raw - // hook payload plus metadata as form fields and let the receiver parse it. - // Timeout caps best-effort hook posts if the local listener stalls. - // Why: pipe payload to curl's stdin (`payload@-`) instead of an inline - // `payload=$VALUE` arg, so tens-of-KB tool output stays off the curl - // command line (EDR command-line false positives). Wire body is identical. + // Why: worktreeId embeds a path, so post form fields, not hand-built JSON that breaks on quotes/newlines. + // Why: pipe payload via curl stdin (`payload@-`) so large tool output stays off the command line (EDR false positives). 'printf \'%s\' "$payload" | curl -sS -X POST "http://127.0.0.1:${ORCA_AGENT_HOOK_PORT}/hook/gemini" \\', ' --connect-timeout 0.5 --max-time 1.5 \\', ' -H "Content-Type: application/x-www-form-urlencoded" \\', @@ -179,18 +158,12 @@ export class GeminiHookService { const command = getManagedCommand(scriptPath) const nextHooks = { ...config.hooks } - // Why: match by script filename (not exact command string) so a fresh - // install sweeps stale entries left by older builds or a different - // Electron userData path (dev vs. prod). Without this, repeated installs - // accumulate duplicate hook entries pointing at defunct scripts. + // Why: match by filename not exact command, so installs sweep stale entries instead of duplicating them. const isManagedCommand = createManagedCommandMatcher(getManagedScriptFileName()) const managedEvents = new Set(GEMINI_EVENTS) - // Why: when Orca stops subscribing to an event, install() must sweep the - // old managed entry out of any leftover event bucket. Otherwise a stale - // hook such as PreToolUse survives forever in ~/.gemini/settings.json and - // continues firing even though the current build no longer wants it. + // Why: sweep managed entries from dropped event buckets so stale hooks (e.g. PreToolUse) don't keep firing. for (const [eventName, definitions] of Object.entries(nextHooks)) { if (managedEvents.has(eventName)) { continue @@ -222,11 +195,7 @@ export class GeminiHookService { return this.getStatus() } - // Why: install Orca's managed Gemini hooks on the remote box. Mirrors - // ClaudeHookService.installRemote — POSIX-only, uses the same SFTP-backed - // primitives, and lays down the same script body the local install - // generates so a remote-side Gemini CLI behaves identically. See - // docs/design/agent-status-over-ssh.md §8. + // POSIX-only remote install mirroring ClaudeHookService.installRemote. See docs/design/agent-status-over-ssh.md §8. async installRemote(sftp: SFTPWrapper, remoteHome: string): Promise { const remoteConfigPath = `${remoteHome.replace(/\/$/, '')}/.gemini/settings.json` const remoteScriptPath = `${remoteHome.replace(/\/$/, '')}/.orca/agent-hooks/gemini-hook.sh` @@ -247,8 +216,7 @@ export class GeminiHookService { const isManagedCommand = createManagedCommandMatcher('gemini-hook.sh') const managedEvents = new Set(GEMINI_EVENTS) - // Why: remote installs must sweep legacy managed event buckets too. - // Otherwise stale PreToolUse entries keep warning in SSH Gemini sessions. + // Why: sweep legacy managed event buckets so stale PreToolUse stops warning in SSH Gemini sessions. for (const [eventName, definitions] of Object.entries(nextHooks)) { if (managedEvents.has(eventName)) { continue @@ -275,10 +243,8 @@ export class GeminiHookService { } config.hooks = nextHooks - // Why: write the script first so an interrupted install never leaves - // settings.json pointing at a missing script. See ClaudeHookService. - // Why: SSH remotes use POSIX `.sh` hook paths even when Orca itself is - // running on Windows; never derive remote script syntax from local OS. + // Why: write the script before settings.json so an interrupted install never points at a missing script. + // Why: SSH remotes always use POSIX `.sh` paths even when Orca runs on Windows. await writeManagedScriptRemote(sftp, remoteScriptPath, getManagedScript('posix')) await writeHooksJsonRemote(sftp, remoteConfigPath, config) @@ -314,14 +280,10 @@ export class GeminiHookService { } const nextHooks = { ...config.hooks } - // Why: same broad matcher as install(), so remove() also cleans up stale - // entries from older builds even if the current scriptPath has moved. + // Why: match by filename so remove() sweeps stale entries even after the script path moved. const isManagedCommand = createManagedCommandMatcher(getManagedScriptFileName()) for (const [eventName, definitions] of Object.entries(nextHooks)) { - // Why: a malformed settings.json entry (non-array value for an event - // name) would make removeManagedCommands throw via definitions.flatMap. - // Skip — remove() must fail open so a broken user config never blocks - // uninstall. + // Why: fail open on malformed (non-array) entries so a broken user config never blocks uninstall. if (!Array.isArray(definitions)) { continue } diff --git a/src/main/git/repo.test.ts b/src/main/git/repo.test.ts index d4abbd2501e..90199d1c69b 100644 --- a/src/main/git/repo.test.ts +++ b/src/main/git/repo.test.ts @@ -15,9 +15,7 @@ import { searchBaseRefs } from './repo' -// Why: these tests exercise real git state (not mocked gitExecFileAsync) -// because the change under test is in the `for-each-ref` glob argument -// shape — the exact kind of bug a mock-based test would miss. +// Why: use real git state (not mocked) because the bug is in the for-each-ref glob shape a mock would miss. function git(cwd: string, args: string[]): string { return execFileSync('git', args, { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }) @@ -25,21 +23,14 @@ function git(cwd: string, args: string[]): string { function initRepo(dir: string): void { git(dir, ['init', '--quiet']) - // Why: explicit symbolic-ref rather than `git init --initial-branch=main` - // (which requires git >= 2.28). Setting HEAD before the first commit - // makes the initial branch `main` regardless of host git version or - // `init.defaultBranch` config. + // Why: `--initial-branch=main` needs git >= 2.28; symbolic-ref before the first commit forces `main` on any git version. git(dir, ['symbolic-ref', 'HEAD', 'refs/heads/main']) git(dir, ['config', 'user.email', 'test@test.com']) git(dir, ['config', 'user.name', 'Test']) git(dir, ['commit', '--allow-empty', '-m', 'initial', '--quiet']) } -/** - * Create a remote-tracking ref in `mainDir` at the given short-name - * (e.g. `origin/main`) by creating a packed ref under refs/remotes. - * Uses `update-ref` so we don't need to actually configure a live remote. - */ +/** Create a remote-tracking ref via `update-ref`, avoiding a live remote. */ function createRemoteRef(mainDir: string, shortName: string, sha: string): void { git(mainDir, ['update-ref', `refs/remotes/${shortName}`, sha]) } @@ -137,7 +128,6 @@ describe('searchBaseRefs (widened glob)', () => { const sha = getHeadSha(tmpDir) createRemoteRef(tmpDir, 'origin/main', sha) createRemoteRef(tmpDir, 'upstream/main', sha) - // symbolic-ref creates the HEAD pseudo-ref git(tmpDir, ['symbolic-ref', 'refs/remotes/origin/HEAD', 'refs/remotes/origin/main']) git(tmpDir, ['symbolic-ref', 'refs/remotes/upstream/HEAD', 'refs/remotes/upstream/main']) @@ -164,9 +154,7 @@ describe('searchBaseRefs (widened glob)', () => { expect(results).toContain('local-only') }) - // Why: a single-word query must match a term in ANY segment of a slashed - // branch name (`user/feature`), not just the leaf. fnmatch `*` does not - // cross `/`, so the glob must use `**` to span ref segments. + // Why: fnmatch `*` doesn't cross `/`, so a single-word query needs `**` to match any segment of a slashed name. it('finds a local slashed branch when the query lands in a deep segment', async () => { git(tmpDir, ['branch', 'feature/login']) @@ -311,10 +299,7 @@ describe('searchBaseRefs (widened glob)', () => { await expect(searchBaseRefs(tmpDir, '', Number.NaN)).resolves.toEqual([]) }) - // Why: the picker displays results as `/` and labels - // `origin/main` as "Current", so users naturally retype the displayed - // format. Before segment-wise globbing this returned [] because the - // slash in the query made every fnmatch pattern unable to match. + // Why: users retype the displayed `/` format, so a slashed query must still match. it('finds the ref when the query is in display format `/`', async () => { const sha = getHeadSha(tmpDir) createRemoteRef(tmpDir, 'upstream/main', sha) @@ -334,8 +319,7 @@ describe('searchBaseRefs (widened glob)', () => { expect(results).toContain('upstream/feature-x') expect(results).toContain('upstream/feature-y') - // Why: `upstream/feat` pins the remote segment to *upstream*, so - // origin/feature-x must not leak in. + // Why: `upstream/feat` pins the remote segment to *upstream*, so origin/feature-x must not leak in. expect(results).not.toContain('origin/feature-x') }) @@ -343,10 +327,7 @@ describe('searchBaseRefs (widened glob)', () => { const sha = getHeadSha(tmpDir) createRemoteRef(tmpDir, 'upstream/main', sha) - // Why: `main/upstream` means "remote matching *main*, branch matching - // *upstream*". upstream/main has remote=upstream (no `main`) and - // branch=main (no `upstream`), so it must NOT match — confirms each - // token is pinned to its own segment, not treated as a free substring. + // Why: each token is pinned to its own segment (remote vs branch), so `main/upstream` must not match `upstream/main`. const results = await searchBaseRefs(tmpDir, 'main/upstream') expect(results).not.toContain('upstream/main') @@ -357,10 +338,7 @@ describe('searchBaseRefs (widened glob)', () => { createRemoteRef(tmpDir, 'upstream/main', sha) git(tmpDir, ['symbolic-ref', 'refs/remotes/upstream/HEAD', 'refs/remotes/upstream/main']) - // Why: typing `upstream/HEAD` must not surface the pseudo-ref even - // though it's a valid display-format query — the HEAD filter runs - // after the glob match, so coverage of the filter must survive - // changes to the glob shape. + // Why: the HEAD filter runs after the glob match, so display-format queries must still drop the pseudo-ref. const results = await searchBaseRefs(tmpDir, 'upstream/HEAD') expect(results).not.toContain('upstream/HEAD') @@ -370,10 +348,7 @@ describe('searchBaseRefs (widened glob)', () => { const sha = getHeadSha(tmpDir) createRemoteRef(tmpDir, 'upstream/main', sha) - // Why: empty tokens from trailing/leading/doubled slashes would - // degrade to `**` segments with fnmatch, matching nothing useful - // and silently breaking the feature. Filtering empty tokens keeps - // the query behavior identical to the intended non-empty tokens. + // Why: empty tokens from stray slashes would degrade to `**` and match nothing, so they're filtered out. expect(await searchBaseRefs(tmpDir, 'upstream/')).toContain('upstream/main') expect(await searchBaseRefs(tmpDir, '/upstream')).toContain('upstream/main') expect(await searchBaseRefs(tmpDir, 'upstream//main')).toContain('upstream/main') @@ -515,15 +490,13 @@ describe('getDefaultBaseRef (regression — unchanged behavior)', () => { }) it('does NOT fall through to upstream/main when origin/* is absent', () => { - // Why: this is the explicit design decision — the default probe order - // is origin-only. upstream-aware defaulting is deferred work. + // Why: default probe order is origin-only by design; upstream-aware defaulting is deferred. const sha = getHeadSha(tmpDir) createRemoteRef(tmpDir, 'upstream/main', sha) const result = getDefaultBaseRef(tmpDir) - // With no origin/* but a local main branch (initRepo creates one), - // we expect the local `main` — NOT `upstream/main`. + // initRepo creates a local `main`, so with no origin/* we expect it — not `upstream/main`. expect(result).toBe('main') expect(result).not.toBe('upstream/main') }) diff --git a/src/main/git/repo.ts b/src/main/git/repo.ts index fa1c23ebf0d..df5b2865132 100644 --- a/src/main/git/repo.ts +++ b/src/main/git/repo.ts @@ -33,14 +33,9 @@ function gitExecOptions( } /** - * Ordered probe list used to resolve a repo's default base ref when no - * explicit origin/HEAD symbolic-ref is set. `returnAs` is the short-name - * format the UI expects (matches how `git for-each-ref --format=%(refname:short)` - * would render the ref). - * - * Why: shared between the local path (getDefaultBaseRefAsync) and the SSH - * relay path in src/main/ipc/repos.ts so both resolve identical defaults - * for equivalent repo states. + * Ordered probe list for a repo's default base ref when no origin/HEAD symbolic-ref is set. + * `returnAs` is the short-name format the UI expects (as `for-each-ref --format=%(refname:short)` renders it). + * Shared local/SSH so both resolve identical defaults. */ export const DEFAULT_BASE_REF_PROBES: readonly { ref: string; returnAs: string }[] = [ { ref: 'refs/remotes/origin/main', returnAs: 'origin/main' }, @@ -50,12 +45,8 @@ export const DEFAULT_BASE_REF_PROBES: readonly { ref: string; returnAs: string } ] /** - * Walk DEFAULT_BASE_REF_PROBES in order, returning the first ref whose - * existence is confirmed by `hasRef`. Returns null if none exist. - * - * Why: abstracts the "how do we test a ref exists" detail so the local - * path (hasGitRefAsync) and the SSH path (provider.exec rev-parse) can - * share a single authoritative probe ordering. + * Walk DEFAULT_BASE_REF_PROBES in order, returning the first ref `hasRef` confirms, or null. + * Abstracts the existence test so local and SSH paths share one authoritative probe ordering. */ async function resolveDefaultBaseRefFromProbes( hasRef: (ref: string) => Promise @@ -68,9 +59,7 @@ async function resolveDefaultBaseRefFromProbes( return null } -/** - * Check if a path is a valid git repository (regular or bare). - */ +/** Check if a path is a valid git repository (regular or bare). */ export function isGitRepo(path: string): boolean { try { if (!existsSync(path) || !statSync(path).isDirectory()) { @@ -80,8 +69,7 @@ export function isGitRepo(path: string): boolean { return false } - // Authoritative positive signal: ask git directly. Covers regular work - // trees, linked worktrees (gitfile), submodules, and bare repos. + // Ask git directly first — authoritative for work trees, linked worktrees, submodules, and bare repos. const gitProbeResult = probeGitRepo(path) if (gitProbeResult === 'repo') { return true @@ -90,20 +78,11 @@ export function isGitRepo(path: string): boolean { return false } - // Why: `git rev-parse` can fail to produce a clean answer for reasons - // unrelated to repo-ness — a transient spawn failure or git-shim hiccup in - // the packaged app, resource pressure in the Electron main process, or a - // repo whose config errors out. Treating every such failure as "not a repo" - // silently downgrades a real repository to a plain folder (worktrees, SCM, - // PRs all disappear) and is the regression behind the spurious "Open as - // Folder" prompt. Fall back to a validated `.git` marker so a directory that - // genuinely carries Git metadata is still recognized; a directory with only - // a garbage `.git` file has no valid marker and is correctly rejected. + // Why: rev-parse can fail for reasons unrelated to repo-ness (spawn hiccup, config error); fall back to a + // validated `.git` marker instead of downgrading a real repo to a plain folder (the spurious "Open as Folder" bug). const markerScan = scanGitMarkerSync(path) if (markerScan.status === 'valid' && !warnedMarkerFallbackThisSession) { - // Why: warn only once per session. The folder scanner calls isGitRepo for - // many paths; if git is genuinely unavailable, warning per path would flood - // the main-process logs without adding signal beyond the first occurrence. + // Why: warn once per session; the folder scanner calls isGitRepo for many paths and would otherwise flood logs. warnedMarkerFallbackThisSession = true console.warn('[isGitRepo] git rev-parse could not confirm repo; accepted via .git marker', { path @@ -179,23 +158,16 @@ export function getGitRepoRoot(path: string): string { export function normalizeGitRepoRootForInputPath(inputPath: string, rootPath: string): string { const inputWsl = parseWslUncPath(inputPath) if (inputWsl && rootPath.startsWith('/')) { - // Why: WSL git reports Linux-native roots; Orca must persist the UNC path so - // later local git calls keep routing through the WSL-aware runner. + // Why: WSL git reports Linux-native roots; persist the UNC path so later git calls keep routing through the WSL runner. return toWindowsWslPath(rootPath, inputWsl.distro) } return normalizeRuntimePathSeparators(rootPath) } /** - * Filesystem-only check for genuine Git metadata, used as a fallback when git - * cannot give a clean answer. Strict enough to reject a directory whose `.git` - * is a garbage file (preserving the validation added in 18ed7b27d): - * - `.git` directory: accepted only if it has real common or linked-worktree - * gitdir shape, so empty/incomplete `.git/` folders are rejected. - * - `.git` file: accepted only if its `gitdir:` target resolves to valid Git - * metadata, covering linked worktrees and submodules. - * - bare repo root: accepted when HEAD + objects/ + refs/ are present and the - * config does not mark it as a regular worktree admin dir. + * Filesystem-only fallback check for genuine Git metadata when git can't answer cleanly. Strict enough to + * reject a garbage `.git` file (validation from 18ed7b27d): accepts a `.git` dir/file with real gitdir shape + * or a bare-repo root (HEAD + objects/ + refs/, not a worktree admin dir). */ function scanGitMarkerSync(path: string): GitMarkerScanResult { const realPath = resolveRealPathSync(path) @@ -207,8 +179,7 @@ function scanGitMarkerSync(path: string): GitMarkerScanResult { realPathScan.status === 'valid' && pathsReferToSameEntry(lexicalScan.rootPath, realPathScan.rootPath) ) { - // Why: preserve lexical spellings such as /var vs /private/var, but let a - // symlink from one repo into another bind to the real target repo like git. + // Why: preserve lexical spellings (/var vs /private/var), but let a cross-repo symlink bind to the real target like git. return lexicalScan } return realPathScan @@ -439,18 +410,14 @@ function isGitBooleanFalse(value: string): boolean { return ['', 'false', 'no', 'off', '0'].includes(value.toLowerCase()) } -/** - * Get a human-readable name for the repo from its path. - */ +/** Get a human-readable name for the repo from its path. */ export function getRepoName(path: string): string { const name = basename(path) // Strip .git suffix from bare repos return name.endsWith('.git') ? name.slice(0, -4) : name } -/** - * Get the remote origin URL, or null if not set. - */ +/** Get the remote origin URL, or null if not set. */ export function getRemoteUrl(path: string): string | null { try { return getRemoteUrlByName(path, 'origin') @@ -486,8 +453,7 @@ function getVerifiedOriginHeadBaseRef(path: string): string | null { cwd: path }).trim() - // Why: origin/HEAD may survive a default-branch rename while pointing at a - // deleted ref; verify before trusting it over the probe list. + // Why: origin/HEAD may survive a default-branch rename pointing at a deleted ref; verify before trusting it. return ref && hasGitRef(path, ref) ? gitRefToDefaultBaseRef(ref) : null } catch { return null @@ -495,14 +461,8 @@ function getVerifiedOriginHeadBaseRef(path: string): string | null { } /** - * Resolve the default base ref for new worktrees. - * Prefer the remote primary branch over a potentially stale local branch. - * - * Why: returns `null` when no candidate ref is resolvable. Previously this - * fell through to a hardcoded `'origin/main'` even when that ref did not - * exist, which silently handed `git worktree add` a bad ref and produced - * an opaque git error. Callers now fail loudly with a useful message, or - * degrade gracefully for non-creation uses (e.g. hosted URL building). + * Resolve the default base ref for new worktrees, preferring the remote primary over a stale local branch. + * Returns null when nothing resolves (rather than a hardcoded `origin/main`) so callers fail loudly or degrade. */ export function getDefaultBaseRef(path: string): string | null { const originHeadBaseRef = getVerifiedOriginHeadBaseRef(path) @@ -510,8 +470,7 @@ export function getDefaultBaseRef(path: string): string | null { return originHeadBaseRef } - // Why: walk the shared DEFAULT_BASE_REF_PROBES list so the sync path and the - // async/SSH paths cannot drift on which refs are tried or in what order. + // Why: walk the shared DEFAULT_BASE_REF_PROBES so sync and async/SSH paths can't drift on ref order. for (const { ref, returnAs } of DEFAULT_BASE_REF_PROBES) { if (hasGitRef(path, ref)) { return returnAs @@ -528,14 +487,8 @@ export async function getBaseRefDefault( } /** - * Return { ahead, behind } for localRef vs remoteRef, or null on git failure. - * - * Why: `rev-list --left-right --count A...B` emits `\t` — - * ahead = commits on A not reachable from B; behind = commits on B not - * reachable from A. This is the merge-base-symmetric delta used by the - * stale-base dispatch guard (§3.1). Returning null on any failure (bad - * ref, corrupt repo, non-numeric output) lets callers degrade gracefully - * instead of failing dispatch on a probe error. + * Return { ahead, behind } (merge-base-symmetric delta) for localRef vs remoteRef, or null on failure. + * ahead = commits on localRef not in remoteRef; behind = the reverse. Used by the stale-base dispatch guard (§3.1). */ export function getRemoteDrift( repoPath: string, @@ -559,12 +512,8 @@ export function getRemoteDrift( } /** - * Up to `limit` commit subjects present on remoteRef but not localRef, in - * recency order. Returns [] on git failure. - * - * Why: powers the preamble drift section (§3.2) so a worker dispatched - * against an acknowledged-stale base can see at a glance whether the - * drift touches their task area. + * Up to `limit` commit subjects on remoteRef but not localRef, recency order; [] on git failure. + * Powers the preamble drift section (§3.2) so a worker sees whether stale-base drift touches its area. */ export function getRecentDriftSubjects( repoPath: string, @@ -584,29 +533,18 @@ export function getRecentDriftSubjects( } } -/** - * Parse `git remote` stdout into a count of configured remotes. - * - * Why: shared between the local path and the SSH relay path so the - * count semantics cannot drift. - */ +/** Parse `git remote` stdout into a remote count. Shared local/SSH so count semantics can't drift. */ export function parseRemoteCount(stdout: string): number { return stdout.split('\n').filter((line) => line.trim().length > 0).length } -/** - * Count the repo's configured remotes by shelling out `git remote`. - * Returns 0 on error — callers use 0 as "unknown / do not render the - * multi-remote hint", preserving today's no-hint behavior on failure. - */ +/** Count configured remotes via `git remote`; returns 0 on error (callers read 0 as "unknown / no hint"). */ export async function getRemoteCount(path: string): Promise { try { const { stdout } = await gitExecFileAsync(['remote'], { cwd: path }) return parseRemoteCount(stdout) } catch (err) { - // Why: surface the failure for diagnostics; callers treat 0 as "unknown / - // do not render the multi-remote hint", but silently swallowing the error - // makes a missing hint impossible to debug. + // Why: log so a missing multi-remote hint is debuggable; callers still treat 0 as "unknown". console.warn('[getRemoteCount] git remote failed', { path, err }) return 0 } @@ -638,17 +576,10 @@ async function resolveVerifiedOriginHeadBaseRefViaExec(exec: GitExec): Promise { const originHeadBaseRef = await resolveVerifiedOriginHeadBaseRefViaExec(exec) @@ -678,30 +609,10 @@ async function getDefaultBaseRefAsync( } /** - * Build the argv for `git for-each-ref` used by ref search, given an - * already-normalized query string. + * Build the argv for `git for-each-ref` used by ref search, given an already-normalized query. * - * Why: glob `refs/remotes/*\/*` (not `refs/remotes/origin/*`) so fork - * workflows can discover branches from any configured remote (e.g. - * `upstream/main`). The picker would otherwise structurally deny the - * correct answer for fork contributors — see docs/upstream-base-ref-design.md. - * - * Why paired leaf/ancestor globs for a single-segment query: `git for-each-ref` - * uses fnmatch-style globs where `*` does NOT cross `/`. Slash-named branch - * refs need an ancestor-segment glob for `user` in `user/feature`, a leaf glob - * for `feature`, and the same remote-side shape so typing a remote name like - * `upstream` keeps working. - * - * Why the multi-segment branch: the picker displays results as - * `upstream/main`, so users naturally retype that format. With a single - * glob, `upstream/main` becomes `refs/remotes/*upstream/main*\/*` — five - * path segments, zero matches. Splitting on `/` and emitting one - * `**` per ref segment maps directly to git's ref structure - * (`refs/remotes//`, `refs/heads/`) and makes - * display-format queries actually find the ref on screen. - * - * Why shared: the local path and the SSH relay path must send the exact - * same argv so results cannot diverge between transports. + * Why: glob every remote (`refs/remotes/*\/*`), not just origin, so fork workflows can find branches like + * `upstream/main` — see docs/upstream-base-ref-design.md. Shared with the SSH relay path so argv can't diverge. */ const REF_SEARCH_CANDIDATE_MULTIPLIER = 4 const REF_SEARCH_LEGACY_HEADROOM = 100 @@ -737,30 +648,18 @@ export function buildSearchBaseRefsArgv( '--sort=-committerdate', ...(excludeRemoteHead ? [ - // Why: exclude remote HEAD pseudo-refs before --count so the bounded - // candidate window is spent on refs the picker can actually display. + // Why: exclude remote HEAD pseudo-refs before --count so the candidate window holds displayable refs. '--exclude=refs/remotes/**/HEAD' ] : []), - // Why: empty Branch-tab searches use broad globs; cap git output before - // execFile/SSH buffers capture every ref in very large repositories. + // Why: cap git output so broad globs don't overflow execFile/SSH buffers in very large repos. `--count=${candidateCount}` ] - // Why: split on `/` so display-format queries (`upstream/main`) route - // each token to one git ref segment. Filter empty tokens so trailing - // (`upstream/`), leading (`/main`), or doubled (`upstream//main`) - // slashes don't produce empty `**` segments that degrade to useless - // patterns. A single remaining token means the user hasn't committed - // to a remote-plus-branch query yet — route through the widened - // single-segment globs below instead of pinning to one segment. + // Why: split on `/` so display-format queries route each token to one ref segment; filter empties from stray slashes. const tokens = getRefSearchTokens(normalizedQuery) if (tokens.length <= 1) { const q = tokens[0] ?? '' - // Why `**`, not `*`: git for-each-ref globs are fnmatch-style where a - // single `*` does NOT cross `/`. Slash-named branches (`user/feature`) - // are the norm, so match both leaf and ancestor branch-name segments. - // The remote ancestor glob also preserves remote-name queries like - // `upstream` while `**/` keeps flat names like `main` working. + // Why `**` not `*`: fnmatch `*` can't cross `/`, so match slash-named branches at both leaf and ancestor segments. return [ ...base, `refs/heads/**/*${q}*`, @@ -769,11 +668,7 @@ export function buildSearchBaseRefsArgv( `refs/remotes/**/*${q}*/**` ] } - // Why: multi-token queries like `upstream/main` map one `*token*` per - // ref segment, so each token is matched within a single git ref - // segment (fnmatch `*` cannot cross `/`). The picker displays results - // as `/`, so users naturally retype that format; this - // branch is what makes re-typing a visible result actually find it. + // Why: one `*token*` per ref segment because fnmatch `*` can't cross `/`; lets a retyped `/` result match. const segmented = tokens.map((token) => `*${token}*`).join('/') const substringQuery = tokens.join('/') const remoteBranchRootPatterns = @@ -785,9 +680,7 @@ export function buildSearchBaseRefsArgv( : [`refs/remotes/*/${substringQuery}*`, `refs/remotes/*/${substringQuery}*/**`] const segmentedPatterns = [`refs/remotes/${segmented}`, `refs/heads/${segmented}`] const branchRootPatterns = [ - // Why: branch names often contain slashes (`plan/docs`). Segment-wise - // display-format globs only align with `/`; these root - // patterns also match the local branch name beneath any configured remote. + // Why: branch names often contain slashes (plan/docs); these root patterns also match a local branch beneath any remote. `refs/heads/${substringQuery}*`, `refs/heads/${substringQuery}*/**`, ...remoteBranchRootPatterns @@ -865,9 +758,7 @@ export async function getDefaultRemote( options: LocalGitExecOptions = {} ): Promise { const defaultRef = await getDefaultBaseRefAsync(path, options) - // Why: getDefaultBaseRefAsync returns null when no default branch can be - // detected (e.g. a brand-new repo with no commits on origin). Guard so we - // don't crash on .includes(); fall through to the remote-list heuristics. + // Why: getDefaultBaseRefAsync returns null when no default branch exists; guard so .includes() can't crash. const defaultBranch = defaultRef ? defaultRef.includes('/') ? defaultRef.split('/').slice(1).join('/') @@ -930,14 +821,11 @@ export async function searchBaseRefDetails( const normalizedQuery = normalizeRefSearchQuery(query) try { - // Why: argv (including the two-remote-glob rationale) lives in - // buildSearchBaseRefsArgv so the SSH sibling cannot drift. + // Why: argv lives in buildSearchBaseRefsArgv so the SSH sibling cannot drift. const remotes = await listRemoteNames(path) const tokens = getRefSearchTokens(normalizedQuery) if (tokens.length > 1) { - // Why: ambiguous slash queries need both display-format matches - // (`upstream/feat`) and local branch-name matches (`plan/docs`). - // Parse and merge buckets before the final limit so neither side starves. + // Why: slash queries need both display-format and local-branch matches; merge before the limit so neither starves. const results = await Promise.all([ runSearchBaseRefsGit(path, normalizedQuery, limit, { remoteNames: remotes, @@ -959,10 +847,7 @@ export async function searchBaseRefDetails( }) return parseAndFilterSearchRefDetails(result.stdout, limit, remotes) } catch (err) { - // Why: surface the failure for diagnostics; callers treat `[]` as "no - // matches", but silently swallowing the error makes a missing result - // set impossible to debug. Mirrors the SSH sibling in - // src/main/ipc/repos.ts. + // Why: log so a missing result set is debuggable; callers still treat [] as "no matches". console.warn('[searchBaseRefs] for-each-ref failed', { path, err }) return [] } @@ -981,15 +866,8 @@ async function listRemoteNames(path: string, options: LocalGitExecOptions = {}): } /** - * Parse `git for-each-ref --format=%(refname)%00%(refname:short)` stdout - * into a deduped list of short refs, filtering out `/HEAD` - * pseudo-refs, honoring a limit. - * - * Why: shared between the local `searchBaseRefs` and the SSH branch in - * `src/main/ipc/repos.ts` so both return identical, correctly-filtered - * results. The same bug class (wrong filter ordering, HEAD leaking into - * results, duplicate short refs) that motivated this helper originally - * lived in a single location; two copies double the regression surface. + * Parse `git for-each-ref --format=%(refname)%00%(refname:short)` stdout into a deduped list of short + * refs, dropping `/HEAD` pseudo-refs. Shared with the SSH branch in ipc/repos.ts so filtering can't drift. */ export function parseAndFilterSearchRefs(stdout: string, limit: number): string[] { return parseAndFilterSearchRefDetails(stdout, limit).map((entry) => entry.refName) @@ -1010,22 +888,13 @@ export function parseAndFilterSearchRefDetails( .map((line) => { const nul = line.indexOf('\0') if (nul < 0) { - // Why: defensive fallback for an unlikely %(refname) format change. - // Drop the entry — emitting a full refname as a "short" ref would - // hand callers a ref they can't use (and would bypass the HEAD - // filter below, since we could no longer tell a `/HEAD` - // pseudo-ref from a local branch named `foo/HEAD`). + // Why: no NUL means an unexpected %(refname) format; drop it rather than hand callers an unusable "short" ref. return null } return { full: line.slice(0, nul), short: line.slice(nul + 1) } }) .filter((entry): entry is { full: string; short: string } => entry !== null) - // Why: drop `refs/remotes//HEAD` pseudo-refs. Uses `.+` (not - // `[^/]+`) because git allows slashes in remote names, so nested - // remotes like `refs/remotes/foo/bar/HEAD` also match. A local branch - // named `foo/HEAD` (rare but valid per git check-ref-format) is - // preserved because its `full` is `refs/heads/foo/HEAD`, which does - // not match this pattern. + // Why: drop `/HEAD` pseudo-refs; `.+` (not `[^/]+`) since git allows slashes in remote names. .filter(({ full }) => !/^refs\/remotes\/.+\/HEAD$/.test(full)) .filter(({ short }) => { if (seen.has(short)) { @@ -1038,9 +907,7 @@ export function parseAndFilterSearchRefDetails( refName: short, localBranchName: resolveLocalBranchName(full, short, sortedRemotes) })) - // Why: `Math.max(0, limit)` — treat pathological `limit <= 0` as - // "zero results" rather than "at least 1". More honest than silently - // returning a single ref when the caller explicitly asked for none. + // Why: Math.max(0, limit) so pathological limit <= 0 yields zero results, not one. .slice(0, Math.max(0, limit)) ) } @@ -1099,9 +966,7 @@ export async function getBranchConflictKind( return false } const shortRef = trimmed.replace(/^refs\/remotes\//, '') - // Why: git allows slashes in remote names. Use the configured remote - // list so foo/bar/feature resolves as branch "feature" for remote - // "foo/bar", matching searchBaseRefDetails. + // Why: git allows slashes in remote names; use the configured list so foo/bar/feature resolves to branch "feature". return resolveLocalBranchName(trimmed, shortRef, remoteNames) === branchName }) @@ -1121,10 +986,7 @@ function isAllowedRemoteBaseRef(refName: string, allowedBaseRef: string | undefi return refName === normalizedAllowedRef } -/** - * Build a hosted URL (e.g. GitHub, GitLab, Bitbucket) for a specific file - * and line in the repo. Returns null when the remote isn't a recognized host. - */ +/** Build a hosted URL (GitHub/GitLab/Bitbucket) for a file+line; null when the remote isn't a recognized host. */ export function getRemoteFileUrl( repoPath: string, relativePath: string, @@ -1144,10 +1006,7 @@ export function getRemoteFileUrl( return buildHostedRemoteFileUrl(remoteUrl, relativePath, defaultBranch, line) } -/** - * Build a hosted URL (e.g. GitHub, GitLab, Bitbucket) for a commit. Returns - * null when the origin remote isn't a recognized host. - */ +/** Build a hosted URL (GitHub/GitLab/Bitbucket) for a commit; null when origin isn't a recognized host. */ export function getRemoteCommitUrl(repoPath: string, sha: string): string | null { const remoteUrl = getRemoteUrl(repoPath) if (!remoteUrl) { diff --git a/src/main/git/runner.ts b/src/main/git/runner.ts index 598ed2a9781..fac9f57ecac 100644 --- a/src/main/git/runner.ts +++ b/src/main/git/runner.ts @@ -1,13 +1,10 @@ -/* eslint-disable max-lines -- Why: command routing, WSL translation, and -git/gh/glab wrappers must stay co-located so platform behavior remains -consistent across every repo-scoped subprocess call. */ +/* eslint-disable max-lines -- command routing, WSL translation, and git/gh/glab wrappers stay co-located for consistent platform behavior. */ /** * Centralized git/gh/command runner with transparent WSL support. * - * Why: When a repo lives on a WSL filesystem (UNC path like \\wsl.localhost\Ubuntu\...), - * native Windows binaries (git.exe, gh.exe, rg.exe) are either absent or extremely slow. - * This module detects WSL paths and routes command execution through `wsl.exe -d ` - * with translated Linux paths, so every call site gets WSL support for free. + * Why: when a repo lives on a WSL filesystem, native Windows binaries (git.exe, + * gh.exe, rg.exe) are absent or slow, so this routes execution through + * `wsl.exe -d ` with translated Linux paths. */ import { execFile, @@ -42,16 +39,13 @@ import { } from '../../shared/wsl-login-shell-command' import { UNTRANSLATED_GIT_OUTPUT_ENV } from '../../shared/git-output-locale' import { endSubprocessStdin } from '../../shared/subprocess-stdin-write' -// Re-exported for existing importers; lightweight consumers should import these -// from './exec-error' directly so they do not depend on this heavy module. +// Re-exported for existing importers; lightweight consumers should import from './exec-error' to avoid this heavy module. import { extractExecError, parseRetryAfterMs } from './exec-error' export { extractExecError, parseRetryAfterMs } // ─── Core resolution ──────────────────────────────────────────────── -// `LANGUAGE=en LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8` — env-assignment prefix for -// WSL-routed git, where spawn env cannot cross the wsl.exe boundary. Values are -// shell-safe unquoted (alnum, dot, dash, underscore only). +// Env-assignment prefix for WSL-routed git, where spawn env can't cross the wsl.exe boundary; values are shell-safe unquoted. const GIT_OUTPUT_LOCALE_SHELL_PREFIX = Object.entries(UNTRANSLATED_GIT_OUTPUT_ENV) .map(([key, value]) => `${key}=${value}`) .join(' ') @@ -65,14 +59,10 @@ type ResolvedCommand = { } /** - * Translate any Windows-style paths in command arguments to Linux paths - * when the command will execute inside WSL. + * Translate Windows-style path arguments to Linux paths for commands run in WSL. * - * Why: callers like worktree-create pass Windows paths (e.g. the workspace - * directory) as git arguments. WSL git doesn't understand Windows paths, - * so we must translate them. WSL UNC paths (\\wsl.localhost\...) are - * converted to their native Linux form; regular Windows drive paths - * (C:\Users\...) are converted to /mnt/c/Users/... + * Why: callers pass Windows paths as git arguments, which WSL git can't read. + * UNC paths (\\wsl.localhost\…) become native Linux; drive paths (C:\…) → /mnt/c/… */ function translateArgsForWsl(args: string[]): string[] { return args.map(translateArgForWsl) @@ -155,8 +145,7 @@ function resolveHostGitHubCli(command: 'gh', args: string[]): ResolvedCommand { return { binary: command, args, - // Why: host gh cannot use a WSL UNC cwd reliably. We only fall back - // for commands with explicit repo/API context, so no repo cwd is required. + // Why: host gh can't use a WSL UNC cwd; we only fall back for commands with explicit repo/API context, so none is needed. cwd: undefined, wsl: null } @@ -183,13 +172,10 @@ function isHostCommandMissing(err: unknown, command: 'gh' | 'glab'): boolean { } /** - * Given a command, its arguments, and a working directory, resolve whether - * the invocation should be routed through wsl.exe. + * Resolve whether a command invocation should be routed through wsl.exe. * - * Why `bash -c "cd ... && ..."` instead of `--cd`: wsl.exe's --cd flag - * does not work reliably when invoked via Node's execFile/spawn (it fails - * with ERROR_PATH_NOT_FOUND in some configurations). Using bash -c with - * an explicit cd is universally supported. + * Why `bash -c "cd … && …"` instead of `--cd`: wsl.exe's --cd fails with + * ERROR_PATH_NOT_FOUND under Node's execFile/spawn in some configs. */ function resolveCommand( command: string, @@ -202,16 +188,8 @@ function resolveCommand( return { binary: command, args, cwd, wsl: null } } - // Why: global gh callers (rate_limit, listAccessibleProjects) have no - // meaningful cwd to derive a WSL distro from. On WSL-only Windows setups, - // gh.exe isn't on the host PATH and the spawn fails with ENOENT. Allow - // callers to pass a distro hint so we can route through wsl.exe regardless. - // TODO(wsl-default-distro): the codebase currently has no persistent - // "default WSL distro" setting — distros are derived from individual repo - // paths. Until such a setting exists, global gh callers without an explicit - // override silently fall back to host gh.exe, which on WSL-only Windows - // installs will ENOENT. The wslDistroOverride parameter is the hook for - // wiring a future setting in without re-plumbing the runner. + // Why: global gh callers (rate_limit, listAccessibleProjects) have no cwd to derive a distro from; a distro hint still routes through wsl.exe. + // TODO(wsl-default-distro): no default-distro setting yet, so override-less global gh callers fall back to host gh.exe (ENOENT on WSL-only installs). const cwdWsl = cwd ? parseWslPath(cwd) : null const wsl: WslPathInfo | null = cwdWsl ?? (wslDistroOverride ? { distro: wslDistroOverride, linuxPath: '' } : null) @@ -220,20 +198,12 @@ function resolveCommand( } const translatedArgs = translateArgsForWsl(args) - // Why: env set on wsl.exe stays on the Windows side (WSLENV forwards only - // named vars), so the untranslated-output locale must ride the command - // string for git stderr parsers to work inside the distro (issue #7808). + // Why: env on wsl.exe stays Windows-side (WSLENV forwards only named vars), so the locale must ride the command string (issue #7808). const localePrefix = command === 'git' ? `${GIT_OUTPUT_LOCALE_SHELL_PREFIX} ` : '' const escapedCommand = quotePosixShell(command) - // Why: shell-escape each argument to prevent word splitting / glob expansion - // inside the bash -c string. Single quotes are safe for all chars except - // single quotes themselves, which we escape as '\'' (end quote, escaped - // literal, reopen quote). + // Why: shell-escape each arg to prevent word splitting / glob expansion inside the bash -c string. const escapedArgs = translatedArgs.map(quotePosixShell) - // Why: when cwd is supplied as a WSL UNC path, prepend `cd &&` - // so the command runs in the expected directory. When the caller only - // supplied a distro override (no cwd), skip the cd entirely — the gh CLI - // doesn't need a particular cwd for global calls like `api rate_limit`. + // Why: prepend `cd &&` for a UNC cwd; skip it when only a distro override was given (global gh needs no cwd). const linuxCwd = cwdWsl?.linuxPath ?? (cwd && wslDistroOverride ? translateArgForWsl(cwd) : null) const shellCmd = linuxCwd ? `cd ${quotePosixShell(linuxCwd)} && ${localePrefix}${escapedCommand} ${escapedArgs.join(' ')}` @@ -258,9 +228,7 @@ function resolveCommand( return { binary: 'wsl.exe', args: ['-d', wsl.distro, '--', 'bash', '-c', shellCmd], - // Why: cwd is set to undefined because wsl.exe handles directory switching - // via the cd inside bash -c. Setting a UNC cwd on the Node process would - // be redundant and can cause issues with some Node internals. + // Why: the `cd` inside bash -c handles the directory; a UNC cwd on the Node process is redundant and can break Node internals. cwd: undefined, wsl } @@ -268,12 +236,7 @@ function resolveCommand( // ─── Git-specific runners ─────────────────────────────────────────── -// Why: Node's execFile only honors maxBuffer when it is a number — passing -// `undefined` (which happens whenever a caller omits the option) disables the -// cap entirely, so a command that prints more than V8's ~512MB max string -// length crashes the main process uncatchably inside execFile's exit handler -// (Array.join over the buffered chunks). Apply this floor so no git call can -// ever buffer without a bound. Matches the relay's MAX_GIT_BUFFER. +// Why: execFile disables its cap when maxBuffer is undefined; unbounded output over V8's string max crashes main uncatchably — keep in sync with relay MAX_GIT_BUFFER. export const DEFAULT_GIT_MAX_BUFFER = 10 * 1024 * 1024 type GitExecOptions = { @@ -334,8 +297,7 @@ function killSpawnedCommandTree(child: ChildProcess): Promise { return new Promise((resolve) => { let killer: ChildProcess try { - // Why: Windows shims and wsl.exe can own descendants; wait for /t tree - // cleanup before settling so a timed-out command cannot outlive its probe. + // Why: Windows shims/wsl.exe own descendants; wait for /t tree cleanup so a timed-out command can't outlive its probe. killer = spawn('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore', windowsHide: true @@ -457,8 +419,7 @@ function execFileCapture( try { const spawnStartedAt = performance.now() - // Why: the abort listener below owns tree-aware cleanup. Node's - // signal handler could kill wsl.exe before taskkill sees its children. + // Why: our abort listener owns tree cleanup; Node's signal handler could kill wsl.exe before taskkill sees its children. child = execFile( command, args, @@ -495,8 +456,7 @@ function execFileCapture( endSubprocessStdin(child.stdin, options.stdin) } - // Why: Node's native timeout can wait forever for signal-ignoring CLIs; - // enforce our own deadline and settle after bounded process-tree cleanup. + // Why: Node's timeout waits forever on signal-ignoring CLIs; enforce our own deadline with bounded tree cleanup. if (options.timeout && options.timeout > 0) { timer = setTimeout(() => { if (settled || terminating) { @@ -623,19 +583,14 @@ export function gitOptionalLocksDisabledEnv( } /** - * Append git config entries through the GIT_CONFIG_COUNT / GIT_CONFIG_KEY_n / - * GIT_CONFIG_VALUE_n env protocol (git >= 2.31), composing with any count - * already present in `env` so we never clobber config a caller injected the - * same way. + * Append git config via the GIT_CONFIG_COUNT/KEY_n/VALUE_n env protocol (git >= 2.31), + * composing with any count already in `env` so we never clobber a caller's config. */ export { appendGitConfigEnv } /** - * Pin Orca-spawned git to untranslated English output so stderr/progress - * parsers keep working under any user locale (issue #7808; see - * UNTRANSLATED_GIT_OUTPUT_ENV for the full rationale). Terminal git is - * untouched. Injected by every git runner in this module; WSL-routed spawns - * get the same values via GIT_OUTPUT_LOCALE_SHELL_PREFIX instead. + * Pin Orca-spawned git to untranslated English output so stderr/progress parsers + * work under any user locale (issue #7808). Terminal git is untouched. */ export function untranslatedGitOutputEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv { return { ...env, ...UNTRANSLATED_GIT_OUTPUT_ENV } @@ -649,11 +604,9 @@ export function promptGuardGitEnv( } /** - * Credential-prompt guard for a general-purpose shell environment (terminal - * PTYs, hook scripts): everything promptGuardGitEnv does EXCEPT the issue-7808 - * locale pins. Those exist so Orca can parse stderr of git it spawns itself; - * forcing LC_ALL/LANG/LANGUAGE onto a user's shell would change the locale of - * every child process, not just git's. + * Credential-prompt guard for a general-purpose shell (PTYs, hook scripts): + * like promptGuardGitEnv but without the issue-7808 locale pins, which would + * change the locale of every child process, not just git's. */ export function promptGuardShellEnv( env: NodeJS.ProcessEnv = process.env, @@ -663,21 +616,15 @@ export function promptGuardShellEnv( } /** - * Force git to be non-interactive so it fails fast instead of blocking forever - * on a prompt. Without this, a git read-path call (status, worktree list, …) - * that hits an auth/credential prompt or an SSH host-key confirmation hangs on - * stdin with no terminal to answer it; on the headless `serve` runtime those - * stuck calls pile up and the runtime stops answering all clients (issue #5308). + * Force git non-interactive so it fails fast instead of hanging on a prompt with + * no terminal to answer it; on headless `serve` those stuck calls wedge every + * client (issue #5308). * - * - GIT_TERMINAL_PROMPT=0: git refuses to prompt for credentials and errors out. - * - GIT_ASKPASS / SSH_ASKPASS: emptied when unset so no GUI/askpass helper can - * pop a prompt and block. A caller-provided askpass is preserved on purpose — - * custom askpass setups commonly *serve* credentials non-interactively, and - * blanking them would break those fetches. - * - GIT_SSH_COMMAND BatchMode=yes: SSH fails instead of waiting on an - * interactive password/host-key prompt. BatchMode does NOT change host trust - * (an unknown host still errors, it just won't hang). Only added when the - * caller hasn't set its own GIT_SSH_COMMAND. + * - GIT_TERMINAL_PROMPT=0: git errors instead of prompting for credentials. + * - GIT_ASKPASS / SSH_ASKPASS: emptied when unset so no GUI helper blocks; a + * caller-provided askpass is preserved (custom setups serve creds non-interactively). + * - GIT_SSH_COMMAND BatchMode=yes: SSH errors instead of prompting (doesn't change + * host trust); only added when the caller hasn't set its own. */ export function nonInteractiveGitEnv( env: NodeJS.ProcessEnv = process.env, @@ -687,9 +634,7 @@ export function nonInteractiveGitEnv( if (!next.GIT_SSH_COMMAND) { next.GIT_SSH_COMMAND = 'ssh -o BatchMode=yes' if (platform === 'win32') { - // Why: forward across the WSL boundary only when we set the value — - // plain `ssh` resolves inside the distro, whereas a caller's - // Windows-specific GIT_SSH_COMMAND must not leak into Linux git. + // Why: forward GIT_SSH_COMMAND to WSL only when we set it — a caller's Windows-specific value must not leak into Linux git. addWslEnvKeys(next, ['GIT_SSH_COMMAND']) } } @@ -852,8 +797,7 @@ async function buildNetworkSshPolicyEnv(options: GitExecOptions): Promise<{ const batchModeCommand = buildOpenSshBatchModeCommand(configuredCommand) if (!batchModeCommand) { - // Why: custom wrappers are executable user policy; rewriting their argv is - // riskier than relying on prompt guards plus the caller's target timeout. + // Why: custom SSH wrappers are user policy; rewriting their argv is riskier than relying on prompt guards + timeout. return { env: promptEnv, mode: 'configured-wrapper-passthrough' } } @@ -872,10 +816,7 @@ export async function gitExecFileAsync( args: string[], options: GitExecOptions ): Promise<{ stdout: string; stderr: string }> { - // Why wrap here: the resolved binary path / WSL detection is internal - // detail; the span attributes track the user-visible `git - // ` form so dashboards group cleanly by intent rather than by - // platform-conditional binary path. + // Why: span the user-visible `git ` form, not the resolved binary, so dashboards group by intent. return withGitSpan( { args, ...(options.cwd !== undefined ? { cwd: options.cwd } : {}) }, async () => { @@ -893,8 +834,7 @@ export async function gitExecFileAsync( maxBuffer: options.maxBuffer, timeout: options.timeout, stdin: options.stdin, - // Why: never let a git read-path call block on an interactive prompt - // (issue #5308) — fail fast instead of hanging the runtime. + // Why: never let a git read-path call block on an interactive prompt (issue #5308) — fail fast. env: policy.env, signal: options.signal }) @@ -973,8 +913,7 @@ export async function gitExecFileAsyncBuffer( return { stdout } } -/** Result of a streamed git command. `stoppedEarly` is true when the caller's - * onStdout hook asked to stop and the child was killed before exiting. */ +/** Result of a streamed git command; `stoppedEarly` is true when onStdout asked to stop before the child exited. */ export type GitStreamResult = { stoppedEarly: boolean } type GitStreamOptions = { @@ -985,10 +924,8 @@ type GitStreamOptions = { /** Byte backstop; defaults to DEFAULT_GIT_MAX_BUFFER. */ maxBuffer?: number /** - * Called for each decoded stdout chunk as it arrives. Return true to stop: - * the child is killed and the promise resolves with stoppedEarly=true. This - * lets a streaming parser bail out (e.g. once an entry limit is reached) - * without ever buffering the full output. + * Called for each decoded stdout chunk. Return true to stop: the child is + * killed and the promise resolves with stoppedEarly=true. */ onStdout: (chunk: string) => boolean | void } @@ -996,11 +933,9 @@ type GitStreamOptions = { /** * Stream a git command's stdout incrementally instead of buffering it whole. * - * Why: status on a repo with an enormous un-ignored folder can emit more output - * than fits in a single string, crashing the process when buffered. Streaming - * lets the parser count entries as they arrive and stop git the moment a limit - * is crossed, so memory stays bounded. Built on gitSpawn so WSL routing is - * preserved. stderr is bounded; a non-zero exit rejects (unless we stopped it). + * Why: output larger than V8's max string (e.g. status on a repo with a huge + * un-ignored folder) crashes the process when buffered; streaming keeps memory + * bounded and lets the parser stop git early. Built on gitSpawn for WSL routing. */ export async function gitStreamStdout( args: string[], @@ -1026,9 +961,7 @@ export async function gitStreamStdout( let stdoutBytes = 0 let stderr = '' let stderrBytes = 0 - // Why: decode statefully so a multibyte UTF-8 character split across two - // chunks (common with non-ASCII filenames) isn't corrupted into - // replacement characters and mis-parsed. + // Why: decode statefully so a multibyte UTF-8 char split across chunks isn't corrupted into replacement chars. const stdoutDecoder = new StringDecoder('utf8') const stderrDecoder = new StringDecoder('utf8') @@ -1066,9 +999,7 @@ export async function gitStreamStdout( if (decoded.length === 0) { return } - // Why: the parser callback is caller-supplied; a throw here would escape - // the stream event handler and crash the main process (the exact failure - // mode this streaming path exists to prevent). Convert it to a rejection. + // Why: a throw from the caller's parser would escape this event handler and crash main; convert to a rejection. let shouldStop: boolean | void try { shouldStop = options.onStdout(decoded) @@ -1078,8 +1009,7 @@ export async function gitStreamStdout( return } if (shouldStop === true) { - // Why: parser hit its limit. Kill git and resolve cleanly — the - // partial output we already parsed is the intended result. + // Parser hit its limit: kill git and resolve cleanly with the partial output. stoppedEarly = true void killSpawnedCommandTree(child) finish(null) @@ -1121,11 +1051,7 @@ export async function gitStreamStdout( }) } -// Why: sync git calls run on the Electron main thread. Local git is normally -// fast, but a repo on a dead network drive / cloud-placeholder path can hang -// git on filesystem timeouts for minutes with no timeout set — the leading -// explanation for issue #7225's 127s "Not Responding" freeze. Callers needing -// longer operations should use the async runners instead. +// Why: sync git blocks the main thread; a dead network drive can hang git for minutes without a timeout (issue #7225's 127s freeze). const GIT_EXEC_SYNC_TIMEOUT_MS = 15_000 /** @@ -1154,8 +1080,7 @@ export function gitExecFileSync( timeout: options.timeout ?? GIT_EXEC_SYNC_TIMEOUT_MS }) as string } finally { - // Sync exec holds the main thread for its whole duration, so the entire - // call is main-thread block time — the cost issue #7576 flags. + // Sync exec blocks the main thread for its whole duration — the cost issue #7576 flags. recordSubprocessSpawn(resolved.binary, resolved.args, performance.now() - spawnStartedAt) } } @@ -1184,23 +1109,9 @@ export function gitSpawn( // ─── gh CLI runners ───────────────────────────────────────────────── -// Why: non-repo-scoped gh calls (listAccessibleProjects, rate_limit, etc.) -// have no meaningful cwd. Allow it to be omitted so the one WSL-aware wrapper -// serves both repo-scoped and global callers and we stop having two spawn -// sites (the other one — a plain execFileAsync in project-view.ts — bypasses -// retry/backoff and any future quota tracker). -// Why: `wslDistro` is an explicit hint for global (cwd-less) gh callers on -// WSL-only Windows installs where gh.exe isn't on the host PATH. When set, -// resolveCommand routes the spawn through `wsl.exe -d -- gh ...` -// even without a UNC cwd to parse a distro from. Repo-scoped callers should -// keep using cwd — the distro derives from the path automatically there. -// Why: `idempotent` gates the transient-error retry. When undefined we -// auto-detect from argv (writes are detected by `-X POST/PATCH/PUT/DELETE` -// or a `query=mutation …` arg); callers can also pass an explicit override. -// A 5xx/socket reset after the request reaches GitHub but before the -// response returns is the canonical case where the server-side write -// succeeded; retrying would create a duplicate comment/issue/label addition. -// See bug-scan finding 1. +// `cwd?` omitted for non-repo-scoped gh calls (rate_limit, listAccessibleProjects) so one WSL-aware wrapper serves both. +// `wslDistro?` routes global cwd-less gh through `wsl.exe -d ` on WSL-only Windows where gh.exe isn't on host PATH. +// `idempotent?` gates transient-error retry (auto-detected from argv); retrying a write that already reached GitHub would duplicate it. type GhExecOptions = Omit & { cwd?: string wslDistro?: string @@ -1208,8 +1119,7 @@ type GhExecOptions = Omit & { } const NON_IDEMPOTENT_METHODS = new Set(['POST', 'PATCH', 'PUT', 'DELETE']) -// `gh ` write subcommands. Reads (view/list/status/checks) -// are absent on purpose so the default of "retry" stays for them. +// `gh ` write subcommands; reads are absent on purpose so they keep retrying. const NON_IDEMPOTENT_GH_VERBS = new Set([ 'create', 'edit', @@ -1251,9 +1161,7 @@ function argsLookIdempotent(args: string[]): boolean { return false } } - // `gh api` auto-switches GET→POST when -f/-F/--field/--raw-field body - // fields are supplied without an explicit -X. Track those to classify - // such calls as non-idempotent. + // `gh api` auto-POSTs when -f/-F/--field body fields are given without -X; track them. if (a === '-f' || a === '-F' || a === '--field' || a === '--raw-field') { hasApiBodyField = true } else if ( @@ -1264,8 +1172,7 @@ function argsLookIdempotent(args: string[]): boolean { ) { hasApiBodyField = true } - // `gh api graphql -f query=mutation(...){ ... }` — detect mutation queries - // so writes via the GraphQL endpoint also fail fast on transient errors. + // Detect GraphQL `query=mutation(…)` so endpoint writes also fail fast on transient errors. if (a.startsWith('query=')) { hasGraphQlQuery = true const trimmed = a.slice('query='.length).trimStart().toLowerCase() @@ -1274,10 +1181,7 @@ function argsLookIdempotent(args: string[]): boolean { } } } - // `gh api ... -f foo=bar` with no explicit method: gh switches to POST. - // Treat as non-idempotent so a transient 5xx after the server applied - // the write doesn't retry and duplicate it. GraphQL reads are the exception: - // gh sends them as POST body fields, but a query operation is idempotent. + // `gh api -f foo=bar` with no -X auto-POSTs → non-idempotent; GraphQL query bodies are the exception (still reads). if ( args[0] === 'api' && hasApiBodyField && @@ -1286,10 +1190,7 @@ function argsLookIdempotent(args: string[]): boolean { ) { return false } - // `gh issue close`, `gh pr edit`, `gh pr merge`, etc. The first arg is the - // noun (issue/pr/repo/label/...) and the second is the verb. Defaulting - // `gh api` calls without an explicit -X to GET-equivalent (idempotent) is - // intentional: callers that POST through `gh api` set `-X POST`. + // `gh ` writes (args[1]); `gh api` without -X defaults to idempotent GET, so it's excluded here. if (args.length >= 2 && args[0] !== 'api') { if (NON_IDEMPOTENT_GH_VERBS.has(args[1])) { return false @@ -1301,12 +1202,9 @@ function argsLookIdempotent(args: string[]): boolean { /** * Classify whether a gh execFile rejection is worth retrying. * - * Why: gh surfaces HTTP status in stderr as "HTTP 504", "HTTP 502", etc. - * Network resets and DNS hiccups also show up as stderr substrings. We retry - * those and 429 (rate-limited) — but only 429s without an explicit - * Retry-After (the caller is better off propagating so the UI can show the - * actual wait time). The primary-rate-limit 403 branch is NOT retried: those - * require the user to back off for minutes, which is not transient. + * Why: gh surfaces HTTP status as stderr substrings ("HTTP 504", econnreset, …). + * Retry 5xx/network resets and 429 only without Retry-After (propagate those so + * the UI can show the wait); primary-rate-limit 403 is never transient. */ export function isTransientGhError(stderr: string): boolean { const s = stderr.toLowerCase() @@ -1328,18 +1226,10 @@ export function isTransientGhError(stderr: string): boolean { return false } -// Why: total of 3 attempts (original + 2 retries) with 250ms → 1s backoff. -// These are standard "transient 5xx" values. Longer waits push past user -// patience for an interactive action; shorter waits would hammer the same -// unhealthy upstream that just failed. The array length defines retry count; -// total attempts = length + 1. +// Why: 3 attempts total (250ms → 1s backoff); array length defines retry count (total attempts = length + 1). const GH_RETRY_DELAYS_MS = [250, 1000] as const -// Why: the upstream Retry-After header is server-suggested but unbounded — -// GitHub has been observed to send tens-of-seconds values on rare incidents, -// and a malicious or misconfigured proxy could send anything. Cap the wait -// at 30s so a single transient gh call can never block the IPC main thread -// for longer than the user's patience budget for an interactive action. +// Why: Retry-After is unbounded and untrusted; cap at 30s so a gh call can't block the IPC thread indefinitely. const GH_RETRY_AFTER_MAX_MS = 30_000 const DEFAULT_GH_EXEC_TIMEOUT_MS = 30_000 @@ -1367,17 +1257,14 @@ function nonInteractiveGhEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.Proce * Async gh CLI execution. Drop-in replacement for * `execFileAsync('gh', args, { cwd, encoding, ... })`. * - * Retries transient 5xx / 429 (without Retry-After) / network-reset failures - * with exponential backoff. Non-transient errors (auth, 404, rate-limit 403, - * validation, 429-with-Retry-After) fail fast on the first attempt. + * Retries transient 5xx / 429-without-Retry-After / network-reset failures with + * exponential backoff; other errors fail fast. */ export async function ghExecFileAsync( args: string[], options: GhExecOptions = {} ): Promise<{ stdout: string; stderr: string }> { - // Why: while a bucket's primary rate limit is exhausted, every spawn would - // return the same 403 — fail fast without paying the subprocess cost. The - // rate_limit probe itself is exempt so the breaker can learn the reset time. + // Why: while a bucket is rate-limited every spawn returns 403 — fail fast; the probe is exempt so the breaker can learn the reset. const rateLimitBucket = classifyGhRateLimitBucket(args) if (!isGhRateLimitProbe(args)) { const blockedUntilMs = getGhRateLimitBlockedUntilMs(rateLimitBucket) @@ -1395,8 +1282,7 @@ export async function ghExecFileAsync( cwd: resolved.cwd, encoding: (options.encoding ?? 'utf-8') as BufferEncoding, maxBuffer: options.maxBuffer, - // Why: GitHub detail IPC powers PR cards, Tasks, and URL worktree - // creation; one stuck gh child must fail visibly, not wedge every lane. + // Why: bound gh so one stuck child fails visibly instead of wedging the IPC lane. timeout: options.timeout ?? defaultGhExecTimeoutMs(options.env), env: nonInteractiveGhEnv(options.env) }) @@ -1417,8 +1303,7 @@ export async function ghExecFileAsync( ) { const wslResolved = resolveDefaultWslCli('gh', args) if (wslResolved) { - // Why: WSL-only Windows installs have no gh.exe on the host PATH, but - // global calls like rate_limit/auth do not carry a repo cwd to route by. + // Why: WSL-only Windows installs have no host gh.exe, and global calls (rate_limit/auth) carry no cwd to route by. resolved = wslResolved attemptedDefaultWslFallback = true attempt = -1 @@ -1432,22 +1317,10 @@ export async function ghExecFileAsync( continue } const isLastAttempt = attempt >= GH_RETRY_DELAYS_MS.length - // Why: only retry idempotent calls. A 5xx/socket reset can arrive - // after the server already applied a POST/PATCH/PUT/DELETE; retrying - // would duplicate the write (e.g. double-post a comment, double-add - // a label). When the caller doesn't say, we auto-detect from argv — - // explicit `-X ` and GraphQL `query=mutation …` are treated - // as non-idempotent. See bug-scan finding 1. + // Why: only retry idempotent calls; a transient error can arrive after a write already applied, so retrying would duplicate it. const idempotent = options.idempotent ?? argsLookIdempotent(args) if (idempotent && !isLastAttempt && isTransientGhError(stderr)) { - // Why: when the upstream surfaced a Retry-After (e.g. on a transient - // 5xx that GitHub explicitly recommends backing off for), honor it - // instead of using our default backoff — sleeping less than the - // server suggests just earns another failure and burns our retry - // budget. Cap at GH_RETRY_AFTER_MAX_MS so a pathologically large - // hint can't block IPC for minutes; if the real wait is longer, the - // attempt will fail again and the error will propagate to the UI - // where the user can see it. + // Why: honor the server's Retry-After over our backoff (a shorter sleep just re-fails); cap so a huge hint can't stall IPC. const retryAfterMs = parseRetryAfterMs(stderr) const delayMs = retryAfterMs !== null @@ -1464,13 +1337,7 @@ export async function ghExecFileAsync( } // ─── glab CLI runner ──────────────────────────────────────────────── -// Why: parallel to gh CLI runner above. GitLab support is added by -// cloning gh's surface rather than abstracting both behind a generic -// runner — keeping them as parallel implementations matches the -// project's clone-and-adapt approach for new providers and avoids -// touching the working gh path. Reuses the shared retry/transient -// helpers since HTTP-status- and TCP-error-based classification is -// provider-agnostic. +// Why: cloned from the gh runner rather than abstracted behind a generic runner, to avoid touching the working gh path. type GlabExecOptions = Omit & { cwd?: string @@ -1479,21 +1346,9 @@ type GlabExecOptions = Omit & { allowDefaultWslFallback?: boolean } +/** Async glab CLI execution; drop-in for execFileAsync('glab', …). Retry policy mirrors ghExecFileAsync. */ /** - * Async glab CLI execution. Drop-in replacement for - * `execFileAsync('glab', args, { cwd, encoding, ... })`. - * - * Retry policy mirrors ghExecFileAsync. - */ -/** - * glab's `--hostname` flag rejects a host that carries a port - * ("error parsing --hostname: invalid hostname"). A self-hosted GitLab on a - * non-default port (e.g. `gitlab.example.com:8443`) must instead be selected - * via the `GITLAB_HOST` env var, which accepts `host:port`. Translate any - * `--hostname host:port` pair into `GITLAB_HOST` so every call site (`api`, - * `auth status`, …) works against ported self-hosted instances. Port-less - * `--hostname` values are left untouched. - * + * glab's `--hostname` rejects host:port, so a ported self-hosted GitLab must use the GITLAB_HOST env var instead — translate it. * @internal exported for tests. */ export function redirectPortedHostnameToEnv( @@ -1555,9 +1410,7 @@ export async function glabExecFileAsync( } } const isLastAttempt = attempt >= GH_RETRY_DELAYS_MS.length - // Why: mirror gh's write-safety gate. A transient error after GitLab - // applies a POST/PATCH/PUT/DELETE must not create duplicate comments, - // issues, or merge actions through an automatic retry. + // Why: mirror gh's write-safety gate — don't auto-retry a non-idempotent write that GitLab may already have applied. const idempotent = options.idempotent ?? argsLookIdempotent(args) if (idempotent && !isLastAttempt && isTransientGhError(stderr)) { const retryAfterMs = parseRetryAfterMs(stderr) @@ -1602,10 +1455,7 @@ export function wslAwareSpawn( /** * Translate absolute Linux paths in git output back to Windows UNC paths. - * - * Why: when git runs inside WSL, paths in output (e.g. `git worktree list`) - * are Linux-native (/home/user/repo). The rest of Orca needs Windows UNC - * paths (\\wsl.localhost\Ubuntu\home\user\repo) to read files via Node fs. + * Why: git-in-WSL emits Linux-native paths, but Orca reads files via Node fs, which needs Windows UNC. */ export function translateWslOutputPaths( output: string, @@ -1618,15 +1468,11 @@ export function translateWslOutputPaths( return output } - // Replace absolute Linux paths that start with / and look like filesystem - // paths in structured git output (e.g. "worktree /home/user/repo/feature") + // Rewrite absolute Linux paths in structured git output (e.g. "worktree /home/user/repo/feature") to Windows UNC. return output.replace(/(?<=worktree )(\/.+)$/gm, (_match, linuxPath: string) => toWindowsWslPath(linuxPath, distro) ) } -/** - * Get the WSL info for a path, if applicable. Convenience re-export so - * consumers don't need to import from wsl.ts directly. - */ +/** Convenience re-export of wsl.ts path helpers so consumers don't import it directly. */ export { parseWslPath, toLinuxPath, toWindowsWslPath, isWslPath } from '../wsl' diff --git a/src/main/git/status.ts b/src/main/git/status.ts index ae818b52567..d325003fd2b 100644 --- a/src/main/git/status.ts +++ b/src/main/git/status.ts @@ -75,12 +75,7 @@ type SubmodulePathsCacheEntry = { paths: string[]; expiresAt: number } const submodulePathsCache = new Map() let submodulePathsCacheGeneration = 0 -// Why: the effective-upstream resolution chain (symbolic-ref + rev-parse ×2-3 -// + config snapshot) costs 4-5 subprocess spawns and only changes when branch -// or git config changes. Ahead/behind is a pure function of the two rev-list -// endpoints, so a recently-resolved name can be revalidated with one rev-list -// spawn per poll tick; a failed rev-list (deleted ref) falls back to a full -// re-resolve. Issue #7576: this path dominated idle main-process spawn churn. +// Why: cache the upstream name to skip its 4-5-spawn resolution chain each poll; revalidate via one rev-list (issue #7576). const RESOLVED_UPSTREAM_NAME_CACHE_TTL_MS = 60_000 type ResolvedUpstreamNameCacheEntry = { @@ -97,9 +92,7 @@ const gitDiffReadDedupe = new InFlightPromiseDedupe() const effectiveUpstreamStatusWriteGeneration = new Map() const statusReadsInFlight = new Map>() -// Why: a mutation invalidates both in-flight diff reads and in-flight status -// coalescing; clearing only the diff dedupe would let a post-mutation -// getStatus() join a pre-mutation read and return stale entries. +// Why: clear both diff and status in-flight caches; clearing only diff would let getStatus() join a pre-mutation read. export function invalidateGitReadCaches(): void { gitDiffReadDedupe.clear() statusReadsInFlight.clear() @@ -113,8 +106,7 @@ export async function runWithGitReadCacheInvalidation(run: () => Promise): try { return await run() } finally { - // Why: a read that started during the mutation can be stale too, so the - // post-mutation boundary retires both pre-existing and overlapping reads. + // Why: a read that started mid-mutation can be stale too, so invalidate again after. invalidateGitReadCaches() } } @@ -125,8 +117,7 @@ export function clearSubmodulePathsCacheForTests(): void { function clearSubmodulePathsCache(): void { submodulePathsCache.clear() - // Why: a pre-mutation .gitmodules read must not repopulate the cache after - // the mutation invalidated it. + // Why: bump the generation so a pre-mutation read can't repopulate the invalidated cache. submodulePathsCacheGeneration += 1 } @@ -139,8 +130,7 @@ function gitRuntimeOptionsKey(options: GitRuntimeOptions): readonly unknown[] { } function getSubmodulePathsCacheKey(worktreePath: string, options: GitRuntimeOptions): string { - // Why: the same path string can refer to different filesystem views across - // WSL distros, so the `.gitmodules` cache must follow runtime routing. + // Why: the same path can map to different WSL-distro filesystems, so key the cache by runtime routing. return [worktreePath, ...gitRuntimeOptionsKey(options)].join('\0') } @@ -182,8 +172,7 @@ function rememberSubmodulePaths(cacheKey: string, paths: string[], now: number): trimSubmodulePathsCache() } -// Why: status tests reuse this reset hook, so every cross-call memoization layer -// must reset together even though the historical name mentions upstream only. +// Why: tests reuse this hook, so every memoization layer resets together despite the upstream-only name. export function clearEffectiveUpstreamStatusCacheForTests(): void { effectiveUpstreamStatusCache.clear() effectiveUpstreamStatusInFlight.clear() @@ -222,8 +211,7 @@ export async function getStatus( if (options.signal) { return runGetStatus(worktreePath, options) } - // Why: dedupe only concurrent identical reads; after settle, callers must - // execute a fresh status read rather than observing a cached result. + // Why: dedupe only concurrent identical reads; after settle, callers must run a fresh read. const cacheKey = getStatusReadKey(worktreePath, options) const inFlightStatus = statusReadsInFlight.get(cacheKey) if (inFlightStatus) { @@ -265,21 +253,15 @@ async function runGetStatus( const lineStatsWriteToken = beginGitStatusLineStatsCacheWrite(lineStatsCacheKey) let effectiveUpstreamStatus: GitUpstreamStatus | undefined let statusSucceeded = false - // Why: a negative/fractional/NaN limit would trigger spurious early-stop or - // inconsistent truncation; fall back to the default unless it's a valid - // non-negative integer (0 explicitly disables the cap). + // Why: a bad limit (negative/fractional/NaN) breaks early-stop; require a valid non-negative int (0 disables the cap). const limit = typeof options.limit === 'number' && Number.isInteger(options.limit) && options.limit >= 0 ? options.limit : DEFAULT_GIT_STATUS_LIMIT - // Why: detectConflictOperation (4 existsSync + readFile) and git status are - // independent. Running them concurrently saves one round-trip of I/O latency. + // Why: detectConflictOperation and git status are independent, so run them concurrently to save I/O latency. const conflictPromise = detectConflictOperation(worktreePath) - // Why: -c core.quotePath=false keeps non-ASCII filenames (Japanese, emoji, - // etc.) as raw UTF-8 instead of git's default C-style octal escapes wrapped - // in double quotes. Without it, the parsed entry.path is unreadable in the - // sidebar and downstream `git show :"docs/\346..."` lookups silently miss. + // Why: core.quotePath=false keeps non-ASCII paths as raw UTF-8, not octal escapes, so entry.path is readable and lookups match. const statusArgs = [ '-c', 'core.quotePath=false', @@ -292,9 +274,7 @@ async function runGetStatus( statusArgs.push('--ignored=matching') } - // Why: stream + parse incrementally and stop git the moment the entry count - // crosses `limit`, so a repo with an enormous un-ignored folder never buffers - // a status listing big enough to crash the process. See StatusPorcelainParser. + // Why: stream + parse and stop at `limit` so a huge un-ignored folder can't buffer enough to crash the process. const parser = new StatusPorcelainParser() let didHitLimit = false const conflictOperation = await conflictPromise @@ -303,8 +283,7 @@ async function runGetStatus( const { stoppedEarly } = await gitStreamStdout(statusArgs, { cwd: worktreePath, wslDistro: options.wslDistro, - // Why: status polling is read-like; avoid refreshing the index and racing - // terminal Git commands on `.git/worktrees/*/index.lock`. + // Why: status polling is read-like; disable optional locks to avoid racing terminal Git on index.lock. env: gitOptionalLocksDisabledEnv(), signal: options.signal, onStdout: (chunk) => parser.update(chunk, limit) @@ -315,22 +294,18 @@ async function runGetStatus( didHitLimit = stoppedEarly statusSucceeded = true } catch (error) { - // Why: an aborted scan must reject, not resolve — swallowing here would let - // a cancelled request be mistaken for a completed (empty) status result. + // Why: an aborted scan must reject, not resolve as an empty result. if (options.signal?.aborted) { throw error } // Not a git repo or git not available } - // Why: the parser stops one entry past the limit (it checks after pushing), so - // trim back to exactly `limit` for a stable "first N shown" contract. + // Why: the parser overshoots by one (checks after pushing), so trim to exactly `limit`. const entries = didHitLimit ? parser.entries.slice(0, limit) : parser.entries const { head, branch, upstreamName, upstreamAheadBehind } = parser.branch - // Why: unmerged (`u`) records need async per-file git lookups, so the parser - // collected their raw lines; resolve them now. Conflicts are rare and never - // the source of huge output, so this stays off the streamed hot path. + // Why: unmerged (`u`) records need async per-file lookups; resolve them off the hot path (conflicts are rare). if (!didHitLimit) { for (const line of parser.unmergedLines) { const unmergedEntry = await parseUnmergedEntry(worktreePath, line) @@ -350,11 +325,7 @@ async function runGetStatus( options ) try { - // Why: the probe promise and its name/negative caches are shared by - // concurrent status reads, so one caller's abort must not reject the - // shared probe or evict warm cache state for the others. The probe is - // small and its cached result stays useful, so run it unbound from - // this request's signal. + // Why: the shared probe/caches serve concurrent reads, so run it unbound from this signal — one abort mustn't reject it for others. const { signal: _requestSignal, ...sharedProbeOptions } = options effectiveUpstreamStatus = await readOrProbeEffectiveUpstreamStatus( cacheKey, @@ -364,18 +335,12 @@ async function runGetStatus( options.bypassEffectiveUpstreamNegativeCache === true ) } catch { - // Why: git status polling should not fail just because the richer - // upstream probe hit a transient ref/read error; the explicit - // upstream-status path will surface those failures when invoked. + // Why: don't fail status polling on a transient upstream-probe error; the explicit upstream path surfaces those. } } } - // Why: attach per-area line counts for the sidebar. Diffs run after status - // (we need the entry list first) and only for areas that have entries, so a - // clean tree costs zero extra git calls. Skipped when the limit was hit — - // running numstat over a huge change set would reintroduce the cost the limit - // exists to avoid, matching how a "huge" repo disables extra git features. + // Why: line counts run only for areas with entries (clean tree = 0 calls); skip past the limit to avoid numstat over a huge set. if (!didHitLimit) { await reuseOrRecomputeGitStatusLineStats({ cacheKey: lineStatsCacheKey, @@ -390,8 +355,7 @@ async function runGetStatus( clearGitStatusLineStatsCacheKey(lineStatsCacheKey, lineStatsWriteToken) } - // Why: abort after the stream (e.g. during unmerged/upstream/line-stats work) - // must still reject — never resolve a cancelled scan as a completed result. + // Why: an abort after the stream (unmerged/upstream/line-stats work) must still reject, not resolve. if (options.signal?.aborted) { const error = new Error('The operation was aborted.') error.name = 'AbortError' @@ -423,15 +387,13 @@ async function runGetStatus( } function getStatusLineStatsCacheKey(worktreePath: string, options: GitRuntimeOptions = {}): string { - // Why: identical path strings can address different Linux filesystems in - // different WSL distros, so derived stats must follow Git's execution host. + // Why: identical paths can map to different WSL-distro filesystems, so key stats by Git's execution host. return `${options.wslDistro ?? 'native'}\0${worktreePath}` } /** * Resolve a submodule's own worktree path from a parent worktree + relative - * submodule path, rejecting anything that escapes the parent. Shared by the - * on-demand submodule status query and the submodule-aware diff router. + * submodule path, rejecting anything that escapes the parent. */ export function resolveSubmoduleWorktreePath(worktreePath: string, submodulePath: string): string { if (!submodulePath || submodulePath.includes('\0') || path.isAbsolute(submodulePath)) { @@ -446,11 +408,8 @@ export function resolveSubmoduleWorktreePath(worktreePath: string, submodulePath } /** - * Run a plain status inside a submodule's own worktree. Used by the lazy - * "expand submodule" flow — the parent status only reports a single gitlink - * row, so the inner per-file changes are fetched on demand here. Entry paths - * are relative to the submodule root; the renderer prefixes them with the - * submodule path. + * Run a plain status inside a submodule's own worktree (lazy "expand submodule" + * flow). Entry paths are relative to the submodule root; the renderer prefixes them. */ export async function getSubmoduleStatus( worktreePath: string, @@ -459,9 +418,7 @@ export async function getSubmoduleStatus( ): Promise { const submoduleWorktreePath = resolveSubmoduleWorktreePath(worktreePath, submodulePath) const workingResult = await getStatus(submoduleWorktreePath, options) - // Why: a moved gitlink (clean worktree) has no uncommitted status rows; its - // real changes live between the parent-recorded commit and the checked-out - // commit. Surface those as inner rows so the expansion isn't empty. + // Why: a moved gitlink (clean worktree) has no status rows; surface the parent-commit→checkout range as inner rows. const fromOid = options.staged ? await readGitlinkOidFromTree(worktreePath, 'HEAD', submodulePath, options) : (await readGitlinkOidFromIndex(worktreePath, submodulePath, options)) || @@ -494,9 +451,8 @@ export async function getSubmoduleStatus( } /** - * List files changed between two submodule commits as status rows. Used when a - * gitlink pointer moved so the expanded submodule shows the committed file - * changes (each row diffs the file across the two commits). + * List files changed between two submodule commits as status rows — used when a + * gitlink pointer moved so the expanded submodule shows committed changes. */ async function computeSubmoduleRangeEntries( submoduleWorktreePath: string, @@ -567,15 +523,11 @@ async function runNumstat( ) return parseNumstat(stdout) } catch (error) { - // Why: an aborted pass must reject so a cancelled scan is never treated as - // a completed one; only a genuine (non-abort) numstat failure degrades to - // uncounted rows below. + // Why: an aborted pass must reject; only a genuine numstat failure degrades to uncounted rows. if (options.signal?.aborted) { throw error } - // Why: a numstat failure (e.g. transient lock) should leave rows without - // counts rather than break the whole status refresh. Null (vs an empty - // map) tells the caller the pass is incomplete and must not be cached. + // Why: a numstat failure leaves rows uncounted; null (not empty map) flags the pass incomplete and uncacheable. return null } } @@ -708,8 +660,7 @@ function rememberEffectiveUpstreamStatus( probedSameNameOriginRef: boolean, writeGeneration: number ): void { - // Why: hasConfiguredPushTarget gates a write action. Re-probe it each poll - // rather than keeping a stale positive target after branch config changes. + // Why: hasConfiguredPushTarget gates a write action; re-probe each poll rather than cache a stale positive. if (status.hasUpstream || status.hasConfiguredPushTarget) { effectiveUpstreamStatusCache.delete(cacheKey) effectiveUpstreamStatusWriteGeneration.set(cacheKey, writeGeneration + 1) @@ -722,8 +673,7 @@ function rememberEffectiveUpstreamStatus( if (!probedSameNameOriginRef) { return } - // Why: a stable no-upstream branch should not spawn failed git probes every - // source-control poll, but remote refs can appear after push/fetch. + // Why: cache the negative so a stable no-upstream branch doesn't re-probe every poll (TTL lets push/fetch refs appear). effectiveUpstreamStatusCache.set(cacheKey, { status, expiresAt: now + EFFECTIVE_UPSTREAM_NEGATIVE_CACHE_TTL_MS @@ -758,8 +708,7 @@ async function readOrProbeEffectiveUpstreamStatus( } } - // Why: source-control mount and root git refresh can overlap during startup. - // Coalesce the richer upstream probe so a stable missing ref fails once. + // Why: overlapping refreshes at startup — coalesce the upstream probe so a stable missing ref fails once. const writeGeneration = effectiveUpstreamStatusWriteGeneration.get(cacheKey) ?? 0 const probe = probeOrRevalidateEffectiveUpstreamStatus( cacheKey, @@ -809,8 +758,7 @@ async function probeOrRevalidateEffectiveUpstreamStatus( ) return { status, probedSameNameOriginRef: false } } catch (error) { - // Why: an aborted probe says nothing about the ref; evicting the warm - // name cache here would force a pointless full re-resolve next scan. + // Why: an aborted probe says nothing about the ref; don't evict the warm name cache. if (options.signal?.aborted) { throw error } @@ -889,12 +837,7 @@ async function parseUnmergedEntry( worktreePath: string, line: string ): Promise { - // Why: porcelain v2 unmerged entries are fully space-separated (like type-1 - // ordinary entries), NOT tab-separated. The format is: - // u

    - // The path starts at field index 10 and may contain spaces, so we join the - // remaining fields. The earlier tab-based parsing silently dropped all - // unmerged entries because the tab was never present. + // Why: porcelain v2 `u` records are space-separated (not tab); path is field 10+ and may contain spaces, so join the tail. const parts = line.split(' ') const xy = parts[1] const modeStage1 = parts[3] @@ -905,9 +848,7 @@ async function parseUnmergedEntry( return null } - // Why: submodule conflicts (mode 160000) are out of scope for v1. - // Presenting them with normal file-conflict UX would be misleading because - // submodule resolution requires different Git commands and user mental model. + // Why: submodule conflicts (mode 160000) are out of scope for v1 — they need different resolution UX. if ([modeStage1, modeStage2, modeStage3].some((mode) => mode === '160000')) { return null } @@ -917,9 +858,7 @@ async function parseUnmergedEntry( return null } - // Why: porcelain v2 `u` records do not provide rename-origin metadata (unlike - // `2` records), so oldPath is intentionally omitted. v1 should not promise - // rename ancestry in conflict rows without a separate Git query. + // Why: porcelain v2 `u` records lack rename-origin metadata, so oldPath is intentionally omitted. return { path: filePath, area: 'unstaged', @@ -950,16 +889,8 @@ function parseConflictKind(xy: string): GitConflictKind | null { } } -// Why: the `status` field on conflict entries is a *rendering compatibility* -// choice for existing icon/color plumbing, not a semantic claim about the file. -// The conflict badge and subtype carry the real meaning. We use 'modified' when -// a working-tree file exists and 'deleted' when it does not, so that downstream -// consumers (file explorer decorations, tab badges) get a reasonable fallback -// without needing conflict-aware upgrades in v1. -// -// For `deleted_by_us` / `deleted_by_them` and the `added_by_*` variants, Git's -// behavior depends on the merge strategy, so we check the filesystem rather -// than hardcoding an assumption. +// Why: `status` here is a rendering-compat choice for icon/color plumbing, not semantic; the conflict badge carries the real meaning. +// Why: for deleted_by_*/added_by_* variants Git's result depends on merge strategy, so check the filesystem. async function getConflictCompatibilityStatus( worktreePath: string, filePath: string, @@ -976,25 +907,13 @@ async function getConflictCompatibilityStatus( try { return existsSync(path.join(worktreePath, filePath)) ? 'modified' : 'deleted' } catch { - // Why: if the filesystem check throws (permissions error, unmounted path, - // etc.), 'modified' is the safer fallback. It avoids suppressing the row - // from the sidebar and avoids a misleading 'deleted' when we simply could - // not check. The conflict badge still carries the real semantics. + // Why: on an fs check failure, 'modified' is safer — it keeps the row visible rather than falsely showing 'deleted'. return 'modified' } } -// Why: there is an inherent race between the `git status` call and these -// fs.existsSync checks — the HEAD file may not yet exist or may already be -// cleaned up by the time we check. In that case we fall back to 'unknown' for -// one poll cycle, which is acceptable. The renderer uses this to label the -// merge summary ("Merge conflicts" vs "Rebase conflicts" vs generic "Conflicts"). -// -// Why rebase detection relies on rebase-merge/ or rebase-apply/ directories -// instead of REBASE_HEAD: those directories persist for the entire rebase, so -// they cover both conflicting and non-conflicting steps. REBASE_HEAD, by -// contrast, only exists on some steps and can also be left behind after a -// completed rebase, which would make the UI show a stale "Rebasing" badge. +// Why: the git-status → existsSync race can miss a transient HEAD; fall back to 'unknown' for one poll cycle. +// Why: detect rebase from rebase-merge/ or rebase-apply/ dirs (persist all steps), not REBASE_HEAD (partial, lingers → stale badge). export async function detectConflictOperation(worktreePath: string): Promise { const gitDir = await resolveGitDir(worktreePath) const mergeHead = path.join(gitDir, 'MERGE_HEAD') @@ -1061,9 +980,8 @@ export async function resolveGitDir(worktreePath: string): Promise { } /** - * List configured submodule paths (relative, forward-slash) for a worktree, - * cached briefly. Read from `.gitmodules` so a single diff click doesn't pay - * for an index-wide `ls-files` scan. Used to route gitlink/inner diffs. + * List configured submodule paths (relative, forward-slash) for a worktree, cached + * briefly. Read from `.gitmodules` to avoid an index-wide `ls-files` scan. */ export async function listSubmodulePaths( worktreePath: string, @@ -1075,8 +993,7 @@ export async function listSubmodulePaths( if (cached) { return cached } - // Why: prune on misses so removed worktrees do not accumulate while hot - // cache hits stay O(1). + // Why: prune on misses so removed worktrees don't accumulate; hot hits stay O(1). pruneExpiredSubmodulePathsCache(now) const cacheGeneration = submodulePathsCacheGeneration let paths: string[] = [] @@ -1174,9 +1091,8 @@ async function readWorkingSubmoduleHead( } /** - * Synthesize a gitlink pointer diff. Git represents submodule commit changes as - * a one-line `Subproject commit ` swap, so feeding the old/new oids through - * the normal text differ matches git's own rendering. + * Synthesize a gitlink pointer diff: Git represents submodule commit changes as a + * one-line `Subproject commit ` swap, so the old/new oids feed the text differ. */ async function buildSubmodulePointerDiff( worktreePath: string, @@ -1184,8 +1100,7 @@ async function buildSubmodulePointerDiff( staged: boolean, compareAgainstHead: boolean, options: GitRuntimeOptions, - // Why: default to the validated resolver so every caller (not just loadDiff) - // is protected from a .gitmodules path escaping the parent worktree. + // Why: default to the validated resolver so every caller is guarded against path escape. submoduleWorktreePath = resolveSubmoduleWorktreePath(worktreePath, submodulePath) ): Promise { let leftOid = '' @@ -1212,9 +1127,8 @@ async function buildSubmodulePointerDiff( } /** - * Diff a file inside a submodule across two of its commits. Used when the parent - * gitlink moved but the submodule worktree is clean — the change is committed, - * so compare the recorded commit's blob against the checked-out commit's blob. + * Diff a file inside a submodule across two of its commits — used when the parent + * gitlink moved but the submodule worktree is clean (change is committed). */ async function buildSubmoduleInnerCommitRangeDiff( submoduleWorktreePath: string, @@ -1256,8 +1170,7 @@ export async function getDiff( compareAgainstHead = false, options: GitRuntimeOptions = {} ): Promise { - // Why: register the in-flight dedupe synchronously (before any await) so - // concurrent identical reads coalesce; submodule routing happens inside. + // Why: register the dedupe synchronously (before any await) so concurrent identical reads coalesce. return gitDiffReadDedupe.run( stableInFlightKey([ 'diff', @@ -1278,17 +1191,12 @@ async function loadDiff( compareAgainstHead: boolean, options: GitRuntimeOptions ): Promise { - // Why: gitlink paths can't be read as blobs (`git show HEAD:` is a "bad - // object") and a submodule working dir reads as empty, so route submodule - // diffs explicitly: the gitlink root → pointer diff, inner files → recurse - // into the submodule's own worktree. + // Why: gitlink paths can't be read as blobs, so route submodule diffs explicitly (root → pointer, inner → recurse). const submodulePaths = await listSubmodulePaths(worktreePath, options) if (submodulePaths.length > 0) { const matchedSubmodule = findContainingSubmodule(submodulePaths, filePath) if (matchedSubmodule) { - // Why: matchedSubmodule originates from .gitmodules, so validate it against - // the worktree boundary before any inner read — a crafted submodule path - // must not let the diff escape the selected repo. + // Why: validate the .gitmodules-derived path against the worktree boundary so a crafted one can't escape the repo. const submoduleWorktreePath = resolveSubmoduleWorktreePath(worktreePath, matchedSubmodule) const normalizedFilePath = filePath.replace(/\\/g, '/').replace(/\/+$/, '') if (normalizedFilePath === matchedSubmodule) { @@ -1309,9 +1217,7 @@ async function loadDiff( const toOid = staged ? await readGitlinkOidFromIndex(worktreePath, matchedSubmodule, options) : await readWorkingSubmoduleHead(submoduleWorktreePath, options) - // Why: when the gitlink moved but the submodule worktree is clean, the - // file's change lives in committed history — diff the two commits. Only - // fall back to the working-tree blob read when the commit didn't move. + // Why: a moved gitlink with a clean submodule worktree means the change is committed — diff the two commits. if (fromOid && toOid && fromOid !== toOid) { return buildSubmoduleInnerCommitRangeDiff( submoduleWorktreePath, @@ -1362,8 +1268,7 @@ async function loadDiff( modifiedIsBinary, filePath ) - // Why: mark a proven deletion so previewers can fall back to the original bytes - // without mistaking a read failure's empty modified side for a deletion. + // Why: mark a proven deletion so previewers don't mistake a read failure's empty side for one. if (result.kind === 'binary' && modifiedDeleted) { return { ...result, modifiedDeleted: true } } @@ -1387,8 +1292,7 @@ export async function getBranchCompare( const compareRef = await resolveCompareRef(worktreePath, options) summary.compareRef = compareRef - // Why: short remote display refs like "origin/main" can collide with a local - // branch of the same name. Compare against the proven remote-tracking ref. + // Why: short refs like "origin/main" can collide with a local branch; use the proven remote-tracking ref. const resolvedBaseRef = await resolveWorktreeAddBaseRef(baseRef, (qualifiedRef) => hasWorktreeBaseCommitRef(worktreePath, qualifiedRef, options) ) @@ -1402,16 +1306,13 @@ export async function getBranchCompare( try { baseOid = await resolveRefOid(worktreePath, resolvedBaseRef, options) summary.baseOid = baseOid - // Why: new remote worktrees can be on an unborn branch until the first - // commit. There are no committed branch changes yet; surfacing this as a - // compare error makes the source-control panel look broken. + // Why: an unborn branch (new remote worktree) has no changes yet; a compare error would look broken. summary.changedFiles = 0 summary.commitsAhead = 0 summary.status = 'ready' return { summary, entries: [] } } catch { - // Preserve the existing unborn-head message when even the base is not - // resolvable; callers cannot compare or present a useful empty state. + // Preserve the unborn-head message when even the base is unresolvable. } summary.status = 'unborn-head' summary.errorMessage = @@ -1637,14 +1538,12 @@ async function loadBranchChanges( headOid: string, options: GitRuntimeOptions = {} ): Promise { - // Why: see core.quotePath=false rationale in getStatus — same reason here so - // branch-diff entries render with their real UTF-8 paths. + // Why: core.quotePath=false keeps real UTF-8 paths — see getStatus rationale. const gitOptions = { ...gitOptionsForWorktree(worktreePath, options), maxBuffer: MAX_GIT_SHOW_BYTES } - // Why: both diffs walk the same range and are independent, so start them - // together instead of serializing two potentially large git operations. + // Why: both diffs are independent, so run them concurrently instead of serializing. const [{ stdout }, { stdout: numstat }] = await Promise.all([ gitExecFileAsync( ['-c', 'core.quotePath=false', 'diff', '--name-status', '-M', '-C', mergeBase, headOid], @@ -1658,8 +1557,7 @@ async function loadBranchChanges( const statsByPath = parseNumstat(numstat) const entries: GitBranchChangeEntry[] = [] - // [Fix]: Split by /\r?\n/ instead of '\n' to handle Git CRLF output on Windows, - // preventing trailing \r characters in extracted file paths. + // Why: split on /\r?\n/ so Git's CRLF output on Windows leaves no trailing \r in paths. for (const line of stdout.split(/\r?\n/)) { if (!line) { continue @@ -1678,8 +1576,7 @@ async function loadCommitChanges( commitOid: string, options: GitRuntimeOptions = {} ): Promise { - // Why: root commits have no parent tree; diff-tree --root asks git to - // compare against the repository's empty tree without hardcoding hash format. + // Why: root commits have no parent tree; diff-tree --root uses git's empty tree, avoiding a hardcoded hash-format-specific oid. const args = parentOid ? ['-c', 'core.quotePath=false', 'diff', '--name-status', '-M', '-C', parentOid, commitOid] : [ @@ -1713,8 +1610,7 @@ async function loadCommitChanges( ...gitOptionsForWorktree(worktreePath, options), maxBuffer: MAX_GIT_SHOW_BYTES } - // Why: commit diff rows need metadata and line counts, but those git queries - // do not depend on each other. + // Why: the two git queries are independent, so run them in parallel. const [{ stdout }, { stdout: numstat }] = await Promise.all([ gitExecFileAsync(args, gitOptions), gitExecFileAsync(numstatArgs, gitOptions) @@ -1872,9 +1768,7 @@ async function readWorkingTreeFile(filePath: string): Promise try { fileStat = await stat(filePath) } catch (error) { - // Why: ENOENT means the working-tree file is genuinely gone (a deletion); - // any other stat error is a read failure, which must not be reported as an - // absence since callers fall back to the original bytes only for deletions. + // Why: only ENOENT is a real deletion; other stat errors are read failures, not absence. return { content: '', isBinary: false, @@ -1885,8 +1779,7 @@ async function readWorkingTreeFile(filePath: string): Promise return { content: '', isBinary: false, exists: false } } if (fileStat.size > MAX_GIT_SHOW_BYTES) { - // Why: git blob reads are capped through maxBuffer; mirror that bound for - // unstaged working-tree content before readFile can pull in huge assets. + // Why: mirror git's maxBuffer cap for working-tree reads so readFile can't pull in huge assets. return { content: '', isBinary: true, exists: true } } try { @@ -1932,15 +1825,12 @@ function buildDiffResult( modifiedContent, originalIsBinary, modifiedIsBinary, - // Why: binary diff previews were originally image-only, so the renderer - // still checks `isImage` before showing a preview component. Preserve - // that legacy flag for PDFs until the wider contract is renamed. + // Why: renderer still checks legacy `isImage` before previewing, so set it for PDFs too until the contract is renamed. ...(mimeType ? { isImage: true, mimeType } : {}) } as GitDiffResult } - // Why: if the diff exceeds safe render limits, avoid sending large text - // payloads and return metadata so the renderer can show fallback UI. + // Why: over the render limit, return metadata instead of huge text so the renderer can show fallback UI. const largeDiffRenderLimit = getLargeDiffRenderLimit({ originalContent, modifiedContent }) if (largeDiffRenderLimit.limited) { return { @@ -2049,9 +1939,7 @@ export async function getStagedCommitContext( if (!isMaxBufferOverflowError(error)) { throw error } - // Why: a very large staged diff overflows maxBuffer (ENOBUFS). The patch is - // optional context that gets truncated to STAGED_DIFF_BYTE_BUDGET anyway, so - // degrade to the file-name summary instead of failing commit-message generation. + // Why: staged patch is optional context (truncated later anyway); degrade to file-name summary rather than fail. console.warn( '[git] Staged patch too large to read; using file summary only:', describeMaxBufferOverflowError(error) @@ -2075,9 +1963,7 @@ export async function commitChanges( await gitExecFileAsync(['commit', '-m', message], gitOptionsForWorktree(worktreePath, options)) return { success: true } } catch (error) { - // Why: surface whichever channel carries the useful message. Pre-commit/GPG - // hook failures write to stderr; "nothing to commit, working tree clean" - // writes to stdout. Try stderr first, fall back to stdout, then error.message. + // Why: useful message may be on stderr (hook/GPG failures) or stdout ("nothing to commit"), so try both then message. const readStringField = (field: string): string | null => { if (typeof error === 'object' && error && field in error) { const v = (error as Record)[field] @@ -2149,8 +2035,7 @@ function normalizeGitPathForCompare(filePath: string): string { } function literalPathspec(filePath: string, options: GitRuntimeOptions): string { - // Why: Windows validation produces backslashes, but Git running inside WSL - // needs POSIX paths. Host paths stay untouched so POSIX filenames remain literal. + // Why: Git inside WSL needs POSIX paths, but host paths must stay literal, so convert backslashes only for WSL. const runtimePath = options.wslDistro ? filePath.replace(/\\/g, '/') : filePath return `:(literal)${runtimePath}` } @@ -2177,8 +2062,7 @@ async function listTrackedPathSpecs( ...gitOptionsForWorktree(worktreePath, options) } ) - // Why: a tracked directory can contain enough paths for push(...split) - // to exceed the JavaScript argument limit before discard decisions run. + // Why: a tracked directory can hold enough paths to exceed the JS argument limit. for (const trackedPath of stdout.split('\0')) { if (trackedPath) { trackedPaths.push(trackedPath) diff --git a/src/main/git/worktree.ts b/src/main/git/worktree.ts index 84ce3963192..92fb4437d77 100644 --- a/src/main/git/worktree.ts +++ b/src/main/git/worktree.ts @@ -84,10 +84,7 @@ const SPARSE_CHECKOUT_DETECTION_CONCURRENCY = 8 const PRUNABLE_EXISTENCE_PROBE_CONCURRENCY = 8 -// Why: bound `git worktree add` so a OneDrive cloud-placeholder base path can't -// stall its checkout writes for minutes (STA-1292) — a stuck create then fails -// fast instead of spinning forever. Generous enough not to kill a legit large -// checkout (mirrors the SYNC runner's cloud-placeholder floor, #7225). +// Why: bound `git worktree add` so a OneDrive cloud-placeholder stall fails fast (STA-1292); generous enough for a legit large checkout (#7225). export const WORKTREE_ADD_TIMEOUT_MS = 180_000 export const WORKTREE_REMOVAL_PREFLIGHT_TIMEOUT_MS = 30_000 @@ -155,9 +152,7 @@ function parseRemoteTrackingLocalBaseRef( return undefined } - // Why: Only refs proven to be remote-tracking refs get refresh status. - // Local branches can contain slashes (e.g. release/2026) and must not - // produce a fake "Local 2026 was not refreshed" warning. + // Why: only proven remote-tracking refs get refresh status; slash-containing local branches (release/2026) must not fake a "not refreshed" warning. const shortRemoteRef = remoteTrackingRef.slice(remoteRefPrefix.length) const slashIndex = shortRemoteRef.indexOf('/') if (slashIndex <= 0) { @@ -196,9 +191,7 @@ async function evaluateLocalBaseRefRefreshability( let localOid = '' let remoteOid = '' try { - // Why: advisory and mutating paths must agree on "safe to fast-forward". - // `rev-list A...B` proves the local branch exists, has no local-only - // commits, and tells the toast whether it is actually behind. + // Why: advisory and mutating paths must agree on "safe to fast-forward"; `rev-list A...B` proves no local-only commits and how far behind. const { stdout } = await gitExecFileAsync( ['rev-list', '--left-right', '--count', `${parsed.fullRef}...${remoteTrackingRef}`], gitExecOptions(repoPath, options) @@ -208,8 +201,7 @@ async function evaluateLocalBaseRefRefreshability( return { refreshable: false, result: { ...resultBase, status: 'skipped_not_fast_forward' } } } if (!shouldInspectOwner(parsedDrift.behind)) { - // Why: a current local ref cannot produce an update suggestion, so the - // advisory path need not resolve OIDs or inspect its owner worktree. + // Why: a current local ref yields no update suggestion, so the advisory path skips OID resolution and owner inspection. return undefined } const { stdout: localOidOutput } = await gitExecFileAsync( @@ -238,8 +230,7 @@ async function evaluateLocalBaseRefRefreshability( } try { - // Why: if the local base branch is checked out anywhere, turning on the - // setting would only update it when that owner worktree is clean. + // Why: if the local base branch is checked out, only update it when that owner worktree is clean. const { stdout: worktreeListOutput } = await gitExecFileAsync( ['worktree', 'list', '--porcelain'], gitExecOptions(repoPath, options) @@ -276,9 +267,7 @@ async function evaluateLocalBaseRefRefreshability( } } - // Why: localBranch isn't checked out anywhere, so there is no working tree - // to desync — a bare ref fast-forward (update-ref) is safe. Omitting - // ownerWorktreePath signals the mutating path to take that branch. + // Why: localBranch isn't checked out anywhere, so a bare-ref fast-forward is safe; omitting ownerWorktreePath signals the mutating path to take it. return { refreshable: true, ...resultBase, @@ -332,8 +321,7 @@ async function persistWorktreeCreationBase( } catch (error) { console.warn(`addWorktree: failed to set ${configKey} for ${worktreePath}`, error) try { - // Why: reused branch names may carry stale base metadata; if replacement - // fails, remove the old value so consumers do not trust outdated lineage. + // Why: reused branch names may carry stale base metadata; if replacement fails, unset it so consumers don't trust stale lineage. await gitExecFileAsync(['config', '--local', '--unset-all', configKey], { ...gitExecOptions(worktreePath, options) }) @@ -356,8 +344,7 @@ async function unsetWorktreeCreationBase( ...gitExecOptions(worktreePath, options) }) } catch { - // Best-effort cleanup; missing keys and locked config both leave the - // original sparse setup error as the actionable failure. + // Best-effort cleanup; leave the original sparse-setup error as the actionable failure. } } @@ -383,8 +370,7 @@ function resolveRevParsePath(repoPath: string, value: string): string { if (posix.isAbsolute(value) || win32.isAbsolute(value)) { return value } - // Old git ignores `--path-format=absolute`, so a relative toplevel/git-dir - // must be resolved against the scanned repo path. + // Old git ignores `--path-format=absolute`, so resolve a relative toplevel/git-dir against the scanned repo path. return looksLikeWindowsPath(repoPath) ? win32.resolve(repoPath, value) : posix.resolve(repoPath, value) @@ -393,11 +379,8 @@ function resolveRevParsePath(repoPath: string, value: string): string { type RepoLocation = { topLevel: string; commonDir: string } function parseRepoLocation(repoPath: string, output: string): RepoLocation | undefined { - // Old git (pre `--path-format`) echoes the unrecognized flag to stdout and - // exits 0 rather than erroring, so drop any echoed `-`-prefixed lines and - // read the two trailing path lines (toplevel, then git-common-dir). Strip only - // the trailing CR, not surrounding spaces — git paths may legitimately start - // or end with a space. + // Old git echoes the unrecognized `--path-format` flag and exits 0, so drop `-`-prefixed lines and + // read the last two path lines (toplevel, git-common-dir); strip only trailing CR — paths may have edge spaces. const lines = output .split('\n') .map((line) => (line.endsWith('\r') ? line.slice(0, -1) : line)) @@ -430,8 +413,7 @@ async function readRepoLocation( gitExecOptions(repoPath, options) ) if (hasUnsupportedRevParsePathFormatEcho(stdout)) { - // Why: some old Git versions echo the unknown option and exit zero; - // remember that compatibility signal even though parsing can recover. + // Why: some old Git echoes the unknown option and exits zero; remember that compat signal even though parsing recovers. capabilities.rememberUnsupported('rev-parse-path-format') } return parseRepoLocation(resolveBasePath, stdout) @@ -457,11 +439,8 @@ async function normalizeMainWorktreePath( ): Promise { const mainIndex = worktrees.findIndex((worktree) => worktree.isMainWorktree) const mainWorktree = worktrees[mainIndex] - // Compare in the Git-output space: under WSL the porcelain/rev-parse paths are - // Linux while repoPath is a UNC path, so without translating, a UNC repoPath - // never matches the early-return and fires a needless rev-parse on every poll. - // Use the platform-independent UNC parser so the comparison holds regardless - // of host OS; the runner still receives the original repoPath for WSL routing. + // Why: under WSL, porcelain/rev-parse paths are Linux but repoPath is UNC; compare in Git-output + // space so the early-return matches and we skip a needless rev-parse per poll (runner still gets repoPath). const wslRepo = parseWslUncPath(repoPath) const comparablePath = wslRepo ? wslRepo.linuxPath : repoPath if (!mainWorktree || areWorktreePathsEqual(mainWorktree.path, comparablePath)) { @@ -473,10 +452,8 @@ async function normalizeMainWorktreePath( return worktrees } - // Why: only a separate-git-dir/submodule main worktree reports the Git - // directory as the main entry — i.e. the main entry equals git-common-dir. - // A linked worktree's main entry is a real working root, so gating on this - // equality avoids overwriting it with the linked worktree's own toplevel. + // Why: only a separate-git-dir/submodule main worktree reports git-common-dir as its path; gate on + // that equality so we don't overwrite a linked worktree's real working root with its own toplevel. if (!areWorktreePathsEqual(mainWorktree.path, location.commonDir)) { return worktrees } @@ -527,8 +504,7 @@ export function parseWorktreeList( const rawReason = line.slice('locked'.length).trim() lockReason = options.nulDelimited ? rawReason : decodeGitCQuotedPath(rawReason) } else if (line === 'prunable' || line.startsWith('prunable ')) { - // Why: Git ≥ 2.36 flags registrations whose directory is gone; ignoring - // it surfaces the stale worktree as a live workspace (issue #8389). + // Why: Git ≥2.36 flags registrations whose directory is gone; ignoring it shows the stale worktree as live (#8389). prunable = true const rawReason = line.slice('prunable'.length).trim() prunableReason = options.nulDelimited ? rawReason : decodeGitCQuotedPath(rawReason) @@ -610,8 +586,7 @@ async function readWorktreeList( ) }, async () => { - // Why: `-z` is required to preserve worktree paths containing newlines, - // but Git <2.36 rejects it. Keep the line parser as the fallback. + // Why: `-z` preserves worktree paths with newlines but Git <2.36 rejects it; fall back to the line parser. const { stdout } = await gitExecFileAsync(['worktree', 'list', '--porcelain'], { cwd: repoPath, ...options @@ -621,11 +596,8 @@ async function readWorktreeList( parseWorktreeList(stdout), options ) - // Why: this `-z`-unsupported fallback (Git <2.36) also serves Git <2.31, - // which emits no `prunable` annotation; probe each linked worktree path - // for existence instead of treating stale registrations as live. On Git - // 2.31–2.35 `parseWorktreeList` already set `prunable`, so the probe is a - // harmless backstop that skips those entries (issue #8389). + // Why: Git <2.31 emits no `prunable`, so probe each linked path for existence instead of trusting + // stale registrations; a harmless backstop on 2.31–2.35 where parseWorktreeList already set it (#8389). return annotatePrunableByExistence(normalized, repoPath, options) }, isUnsupportedWorktreeListZError @@ -645,11 +617,8 @@ async function annotatePrunableByExistence( const index = nextIndex nextIndex += 1 const worktree = worktrees[index] - // Git only marks linked worktrees prunable, and never locked ones (a - // lock shields the registration even when the directory is missing). The - // `locked` annotation is only parsed on Git >=2.31, so on older Git a - // locked+missing worktree cannot be shielded here. A missing main - // worktree is handled by the repo-level ENOENT paths. + // Git only prunes linked worktrees, never locked ones (a lock shields a missing dir; `locked` + // parses only on Git >=2.31). A missing main worktree is handled by the repo-level ENOENT paths. if ( !worktree || worktree.isMainWorktree || @@ -709,17 +678,10 @@ export async function listWorktreeGraph( } } -// Why: a cold start lists every repo's worktrees from several independent -// paths (renderer worktrees fetch, lineage resolution, boot pty-registry -// hydration), and each `git worktree list` is a full process spawn — -// expensive on Windows (issue #7225). Sharing the in-flight promise collapses -// concurrent duplicate scans; nothing is served after a scan settles. +// Why: cold start triggers many concurrent `git worktree list` spawns per repo (expensive on Windows, #7225); share the in-flight promise to collapse duplicates. const inFlightWorktreeScans = new Map>() -// Why: a caller listing AFTER a mutation completed must observe it, so it may -// not join a scan that started before the mutation. Bumping the generation on -// mutation completion retires older in-flight scans from sharing (they still -// settle for their original callers). +// Why: a listing after a mutation must not join a scan that predates it; bumping the generation on mutation retires older in-flight scans from sharing. const worktreeScanGenerations = new Map() function hasInFlightWorktreeScanForRepo(repoPath: string): boolean { @@ -733,8 +695,7 @@ function hasInFlightWorktreeScanForRepo(repoPath: string): boolean { } function bumpWorktreeScanGeneration(repoPath: string): void { - // Why: generations only prevent joining a pre-mutation scan. Without an - // active scan, retaining the repo path just leaks completed mutation keys. + // Why: generations only prevent joining a pre-mutation scan; with no active scan, keeping the repo path just leaks completed mutation keys. if (!hasInFlightWorktreeScanForRepo(repoPath)) { return } @@ -742,8 +703,7 @@ function bumpWorktreeScanGeneration(repoPath: string): void { } function pruneWorktreeScanGeneration(repoPath: string): void { - // Why: ordinary scan settlement should stay O(1); only repos invalidated - // during an active scan need the cross-generation in-flight check. + // Why: keep ordinary scan settlement O(1); only repos invalidated during an active scan need the cross-generation check. if (!worktreeScanGenerations.has(repoPath)) { return } @@ -813,9 +773,7 @@ async function listWorktreesUnshared( if (isNotGitRepositoryError(err)) { return [] } - // Why: a silent catch turns git compatibility or repo-state failures into - // opaque downstream errors like "Worktree created but not found in listing". - // Surface the cause so future regressions show up immediately. + // Why: don't swallow git-compat/repo-state failures — else they resurface as opaque "created but not found in listing" errors. console.warn(`[git/worktree] listWorktrees failed for ${repoPath}:`, err) return [] } @@ -853,8 +811,7 @@ async function annotateSparseCheckoutStatus( } } - // Why: worktree refreshes run on git-status polling paths. Many worktrees can - // otherwise fan out `.git`/sparse-checkout filesystem probes all at once. + // Why: cap concurrency so status-poll refreshes don't fan out many sparse-checkout filesystem probes at once. const workerCount = Math.min(SPARSE_CHECKOUT_DETECTION_CONCURRENCY, worktrees.length) await Promise.all(Array.from({ length: workerCount }, () => detectNext())) return annotated @@ -913,17 +870,14 @@ async function refreshLocalBaseRefForWorktreeCreate( return { ...resultBase, status: 'updated', ownerWorktreePath: currentOwner.path } } - // Why: no owner worktree — fast-forward the bare ref. The expected-old-OID - // form makes this a no-op-safe compare-and-swap if the ref moved since the - // evaluation snapshot. + // Why: no owner worktree — fast-forward the bare ref; the expected-old-OID form is a no-op-safe CAS if the ref moved since evaluation. await gitExecFileAsync( ['update-ref', evaluation.fullRef, evaluation.remoteOid, evaluation.localOid], gitExecOptions(repoPath, options) ) return { ...resultBase, status: 'updated' } } catch { - // update-ref/reset can fail on locked refs, filesystem errors, or unusual - // worktree states. Worktree creation should still proceed. + // update-ref/reset can fail on locked refs or odd worktree states; worktree creation should still proceed. return { ...resultBase, status: 'skipped_error' } } } @@ -934,10 +888,9 @@ async function refreshLocalBaseRefForWorktreeCreate( * @param worktreePath - Absolute path where the worktree will be created * @param branch - Branch name for the new worktree * @param baseBranch - Optional base branch to create from (defaults to HEAD) - * @remarks Side effect: passes `--no-track`, writes `branch..base` - * for new-branch worktrees with a base ref, and may write - * `push.autoSetupRemote=true` to the repo's shared config. Config writes are - * best-effort and warn-only. See body comments below for the full rationale. + * @remarks Side effects (best-effort, warn-only): passes `--no-track`, writes + * `branch..base` for new-branch worktrees with a base ref, and may + * write `push.autoSetupRemote=true` to the repo's shared config. */ export async function addWorktree( repoPath: string, @@ -985,20 +938,13 @@ async function performAddWorktree( // Why: -b would create a new branch instead of checking out the selected one. args.push(worktreePath, branch) } else { - // Why: --no-track keeps the new branch from inheriting the base ref's - // upstream, so `git status` doesn't report "behind by N" against the base - // pre-publish and tools/agents don't misread an unpublished branch as - // out-of-sync. First push sets the upstream — see push.autoSetupRemote - // below for the terminal ergonomics. + // Why: --no-track avoids inheriting the base's upstream so `git status` won't misreport "behind by N" pre-publish; first push sets it (see push.autoSetupRemote below). args.push('--no-track', '-b', branch, worktreePath) if (baseBranch) { effectiveBase = await resolveWorktreeAddBaseRef(baseBranch, (qualifiedRef) => hasWorktreeBaseCommitRef(repoPath, qualifiedRef, options) ) - // Why: resolving the creation base first distinguishes real - // remote-tracking refs from slash-containing local branch names. - // The mutation stays behind the explicit setting so the default - // remains conservative. + // Why: resolve the creation base first to distinguish remote-tracking refs from slash-containing local branches (mutation gated behind the explicit setting). if (refreshLocalBaseRef) { localBaseRefRefresh = await refreshLocalBaseRefForWorktreeCreate( repoPath, @@ -1021,8 +967,7 @@ async function performAddWorktree( } await gitExecFileAsync(args, { ...gitExecOptions(repoPath, options), - // Why: bound the checkout so a OneDrive cloud-placeholder stall (STA-1292) - // fails fast rather than hanging worktree creation indefinitely. + // Why: bound the checkout so a OneDrive cloud-placeholder stall (STA-1292) fails fast instead of hanging. timeout: WORKTREE_ADD_TIMEOUT_MS }) @@ -1034,38 +979,13 @@ async function performAddWorktree( await persistWorktreeCreationBase(worktreePath, branch, effectiveBase, options) } - // SSH parity: src/relay/git-handler-worktree-ops.ts addWorktreeOp mirrors this exact - // probe-and-write state machine. If you change the logic here, update - // the relay handler in lockstep so local and SSH paths stay aligned. - // - // Why: with --no-track there is no upstream until first push. Setting - // push.autoSetupRemote=true makes a plain `git push` from the terminal - // create origin/ and set it as upstream automatically — matching - // user expectations from modern git without requiring `-u`. Note that - // `--local` on a linked worktree writes to the shared common-dir config, - // so this affects the whole repo, not just this worktree. That is - // intentional and acceptable: the value is benign and idempotent, and - // every Orca-created worktree wants the same default. True per-worktree - // scope would require enabling extensions.worktreeConfig=true repo-wide, - // which is a larger change we deliberately avoid. - // - // Notes on the design: - // - push.autoSetupRemote is honored by git >= 2.37; older clients ignore - // the value, so `git push` falls back to the pre-2.37 "no upstream" - // error and the user runs `git push -u` once. - // - Failures here are warn-only: config writes are best-effort and a - // missing write degrades to the same fallback as old git. - // - The write is skipped when any value is already set (local, global, - // or system) so a deliberate user `false` is preserved. - // - Not rolled back on creation failure: addSparseWorktree's catch path - // removes the worktree but does not unset this config. That is consistent - // with the "benign and idempotent" rationale above — every Orca-created - // worktree wants this default, and a future creation will silently re-set - // it via the existing-value check anyway. + // SSH parity: relay's addWorktreeOp (src/relay/git-handler-worktree-ops.ts) mirrors this — change both in lockstep. + // Why: --no-track leaves no upstream until first push; push.autoSetupRemote=true lets a plain + // `git push` create+set origin/ (git >=2.37; older clients ignore it). `--local` on a + // linked worktree writes the shared common-dir config (whole repo) — intentional and idempotent, + // so it's warn-only and not rolled back on failure. try { - // Why: `--get` (not `--local --get`) so a value set at any scope - // (local/global/system) counts as "user already chose" and we don't - // overwrite it. + // Why: `--get` (not `--local --get`) so a value at any scope counts as "user already chose" and isn't overwritten. let alreadySet = false try { await gitExecFileAsync(['config', '--get', 'push.autoSetupRemote'], { @@ -1073,10 +993,7 @@ async function performAddWorktree( }) alreadySet = true } catch (readError) { - // Why: `git config --get` exits 1 only when the key is unset at every - // scope. Any other exit code means a real read failure (corrupt config, - // locked file, parse error) — surface that via the outer catch instead - // of silently overwriting whatever value the user actually has. + // Why: `git config --get` exits 1 only when unset at every scope; any other code is a real read failure — rethrow rather than overwrite the user's value. const code = (readError as { code?: unknown })?.code if (code !== 1) { throw readError @@ -1138,15 +1055,13 @@ export async function addSparseWorktree( try { await removeWorktree(repoPath, worktreePath, true, { deleteBranch: !options.checkoutExistingBranch, - // Why: rolling back a failed creation — the just-created branch has no - // user commits, so force-delete it rather than preserving an orphan. + // Why: failed-creation rollback — the fresh branch has no user commits, so force-delete rather than orphan it. forceBranchDelete: !options.checkoutExistingBranch, ...(options.wslDistro ? { wslDistro: options.wslDistro } : {}) }) } catch { wrapped.cleanupFailed = true - // Why: the user needs to know that manual cleanup may be required — - // otherwise a half-created worktree silently lingers on disk. + // Why: surface that manual cleanup may be needed, else a half-created worktree lingers silently on disk. wrapped.message = `${wrapped.message} (cleanup also failed — the partially created worktree at "${worktreePath}" may need manual removal)` } } @@ -1155,13 +1070,10 @@ export async function addSparseWorktree( } /** - * Move a worktree's directory to a new path with `git worktree move`, which - * relocates the working tree and rewrites git's gitdir pointers so the linkage - * stays intact — a raw `fs.rename` would corrupt the `.git` file and the - * `.git/worktrees//gitdir` back-pointer. Local worktrees only: the - * first-work folder rename skips SSH/remote, so there is no relay parity handler - * for this op. The caller owns migrating Orca's path-derived worktree identity - * after a successful move, and pre-checks that the destination is free. + * Move a worktree with `git worktree move` (not `fs.rename`, which corrupts the + * `.git` file and the `.git/worktrees//gitdir` back-pointer). Local-only, + * so there is no relay parity handler. Caller owns migrating Orca's + * path-derived worktree identity and pre-checks that the destination is free. */ export async function moveWorktree( repoPath: string, @@ -1184,10 +1096,7 @@ export async function removeWorktree( repoPath: string, worktreePath: string, force = false, - // Why: forceBranchDelete is for cleaning up a worktree this code just created - // (e.g. rollback of a failed creation) where the fresh branch has no user work - // and must be removed outright. User-initiated deletes leave it false so unmerged - // commits are preserved. + // forceBranchDelete: for failed-creation rollback (fresh branch, no user work); user deletes leave it false so unmerged commits survive. options: RemoveWorktreeOptions = {} ): Promise { try { @@ -1213,8 +1122,7 @@ async function performRemoveWorktree( const branchName = normalizeLocalBranchRef(removedWorktree?.branch ?? '') const branchHead = removedWorktree?.head ?? '' - // Why: callers outside the IPC/runtime preflight must not bypass Git's lock - // contract or depend on localized stderr to discover it after side effects. + // Why: callers outside the IPC/runtime preflight must not bypass Git's lock contract or rely on localized stderr after side effects. assertWorktreeUnlockedForRemoval(removedWorktree) const args = ['worktree', 'remove'] @@ -1228,9 +1136,7 @@ async function performRemoveWorktree( if (force || !isSubmoduleWorktreeRemovalRefusal(error)) { throw error } - // Why: Git refuses non-force removal of any worktree with an initialised - // submodule even when everything is clean. Re-prove cleanliness (parent - // status reports dirty submodule content as ` M `), then --force. + // Why: Git refuses non-force removal of a worktree with an initialised submodule even when clean; re-prove cleanliness, then --force. await assertWorktreeCleanForRemoval(worktreePath, false, options) await gitExecFileAsync( ['worktree', 'remove', '--force', worktreePath], @@ -1246,13 +1152,8 @@ async function performRemoveWorktree( } try { - // Why: `git worktree remove` only detaches the filesystem entry. Orca also - // drops the now-unused local branch here so delete-worktree does not leave - // behind orphaned feature branches unless another worktree still points at it. - // Use `-d` (not `-D`): Git refuses to delete a branch with commits not merged - // into its upstream or HEAD, so unpublished work is preserved instead of - // force-deleted. forceBranchDelete opts into `-D` for failed-creation rollback, - // where the fresh branch has no user work to protect. + // Why: also drop the now-orphaned branch so delete-worktree leaves none; `-d` (not `-D`) preserves + // unmerged work, and forceBranchDelete opts into `-D` for failed-creation rollback. const branchDeleteResult = await deleteLocalBranchAfterWorktreeRemoval( repoPath, branchName, @@ -1277,16 +1178,14 @@ async function performRemoveWorktree( return {} } } catch (alreadyMergedDeleteError) { - // Why: the worktree is already gone; a raced branch cleanup should - // degrade to the preserved-branch recovery path instead of failing delete. + // Why: worktree is already gone; a raced branch cleanup should degrade to preserved-branch recovery, not fail delete. console.warn( `[git] Failed to delete already-merged local branch "${branchName}" after removing worktree`, alreadyMergedDeleteError ) } } - // Expected when the branch still has unmerged/unpublished commits: keep it. - // Deleting a worktree must never silently discard commits. + // Keep an unmerged/unpublished branch: deleting a worktree must never silently discard commits. console.warn( `[git] Preserved local branch "${branchName}" after removing worktree (not fully merged)`, error @@ -1315,8 +1214,7 @@ async function deleteLocalBranchAfterWorktreeRemoval( } try { - // Why: `branch -d` is the cheap live-checkout guard. Only pay for - // `worktree prune` when a stale admin record may be the thing blocking it. + // Why: only pay for `worktree prune` when a stale admin record may be blocking `branch -d`. await gitExecFileAsync(['worktree', 'prune'], gitExecOptions(repoPath, options)) } catch (error) { console.warn(`[git] Failed to prune worktrees before deleting branch "${branchName}"`, error) @@ -1350,9 +1248,7 @@ async function deleteAlreadyMergedBranchAfterSafeDeleteFailure( }) const targetRefs = await getBranchCleanupTargetRefs(runGit, branchName) await refreshBranchCleanupTargetRefs(runGit, targetRefs) - // Why: squash merges rewrite commit IDs, so `branch -d` can reject a branch - // whose changes are already on the base ref. Delete only when Git can prove - // the branch contributes no tree changes to that base. + // Why: squash merges rewrite commit IDs, so `branch -d` rejects already-merged branches; delete only when Git proves no unmerged tree changes. if ( !(await branchHasNoUnmergedChangesOnAnyTarget( runGit, @@ -1389,8 +1285,7 @@ export async function forceDeleteLocalBranch( if (await isLocalBranchCheckedOut(repoPath, branchName, runGit)) { throw new Error(`Local branch "${branchName}" is checked out in another worktree.`) } - // Why: stale toast actions must not delete a branch that moved after Git - // preserved it. `update-ref` deletes only if the ref still has expectedHead. + // Why: stale toast actions must not delete a branch that moved; `update-ref -d` deletes only if the ref still == expectedHead. try { await runGit(['update-ref', '-d', `refs/heads/${branchName}`, expectedHead], repoPath) } catch { @@ -1412,8 +1307,7 @@ export async function forceDeleteLocalBranch( try { await runGit(['config', '--remove-section', `branch.${branchName}`], repoPath) } catch { - // Best-effort parity with `git branch -D`; stale config is harmless and - // should not make the already-deleted ref look like a failed delete. + // Best-effort parity with `git branch -D`; stale config is harmless. } } @@ -1493,22 +1387,8 @@ function translateWorktreePath( } async function detectSparseCheckout(worktreePath: string): Promise { - // Why: `listWorktrees` runs on every 3-second git-status poll and on every - // worktree refresh, so this probe fires N times per poll for N worktrees. - // The previous `git sparse-checkout list` subprocess made that N*poll extra - // git processes, which regressed app responsiveness on machines with many - // worktrees (see PR #1131 revert in #1290). A single fs.stat on the - // per-worktree sparse-checkout config file is ~two orders of magnitude - // cheaper and has the same truthiness semantics: Git writes this file when - // sparse checkout is enabled for the worktree and does not write it - // otherwise. - // - // Why per-worktree gitdir and not `/.git/info/sparse-checkout`: - // linked worktrees have a `.git` file that points at - // `/.git/worktrees/`, and that is where Git stores the - // worktree-local sparse-checkout config. `core.sparseCheckout` itself is - // shared across all worktrees, so the presence of the config file is the - // correct per-worktree signal. + // Why: fs.stat the per-worktree gitdir's sparse-checkout config instead of a per-poll `git sparse-checkout list` subprocess that regressed responsiveness (PR #1290); + // the file's presence is the per-worktree signal because core.sparseCheckout is shared across all worktrees. try { const gitDir = await resolveGitDir(worktreePath) const stats = await stat(join(gitDir, 'info', 'sparse-checkout')) diff --git a/src/main/github/client.ts b/src/main/github/client.ts index d9d41f4f653..53bf26614ff 100644 --- a/src/main/github/client.ts +++ b/src/main/github/client.ts @@ -1,5 +1,4 @@ -/* eslint-disable max-lines -- Why: co-locating all GitHub client functions keeps the -concurrency acquire/release pattern and error handling consistent across operations. */ +/* eslint-disable max-lines -- Why: co-locating all GitHub client functions keeps acquire/release and error handling consistent. */ import type { ClassifiedError, GitPushTarget, @@ -66,8 +65,7 @@ import { type LocalGitExecOptions, type OwnerRepo } from './gh-utils' -// Import the pure error-parsing helpers from the lightweight module, not -// `./gh-utils`, so tests that mock gh-utils still resolve the real functions. +// Why: import from the lightweight module (not ./gh-utils) so tests mocking gh-utils still get the real functions. import { extractExecError, parseRetryAfterMs } from '../git/exec-error' import { isCommitPartOfMergedPR, @@ -120,8 +118,7 @@ type HostedReviewLocalGitOptions = ReturnType() @@ -197,9 +194,7 @@ function prRefreshUpstreamError( message: safePRRefreshErrorMessage(errorType), fetchedAt: Date.now() } - // Why: a rate limit that carries a Retry-After is a real cooldown — surface it - // as the retry schedule so the renderer disables manual Retry until the reset - // and shows a truthful auto-retry time, instead of retrying into another 429. + // Why: a Retry-After is a real cooldown — surface it as the retry schedule so the renderer doesn't retry into another 429. if (errorType === 'rate_limited') { const retryAfterMs = parseRetryAfterMs(extractExecError(err).stderr) if (retryAfterMs !== null && retryAfterMs > 0) { @@ -268,9 +263,8 @@ function sanitizeRemoteName(owner: string, repo: string): string { } /** - * A fork push target plus the PR's `maintainer_can_modify` flag. The flag rides - * alongside the target (rather than inside {@link GitPushTarget}) so it never - * leaks into the persisted, validated push-target shape. + * A fork push target plus the PR's `maintainer_can_modify` flag, kept outside + * {@link GitPushTarget} so it never leaks into the persisted push-target shape. */ export type PullRequestPushTarget = { pushTarget: GitPushTarget @@ -309,9 +303,7 @@ export async function getPullRequestPushTarget( prStdout = stdout break } catch (error) { - // Why: in fork workflows `origin` is often the contributor fork while - // the PR number belongs to `upstream`; probe all known PR repos before - // deciding this PR number is unavailable. + // Why: origin is often the contributor fork while the PR belongs to upstream; probe all PR repos before giving up. if (isNotFoundGhError(error)) { continue } @@ -421,22 +413,17 @@ export async function getAuthenticatedViewer(): Promise { } } -// Why: main-process maps omit repoId because the IPC handler never receives -// a repo identifier beyond path. Exported because runtime consumers receive -// listWorkItems results before the renderer stamps repoId. +// Why: omit repoId — the main process only has the path; the renderer stamps repoId after IPC. export type MainWorkItem = Omit -// Why: issue numbers follow creation order within a repository. Pinning this -// sort keeps gh's rich PR rows aligned with numbered Search API issue pages. +// Why: issue numbers follow creation order, so this sort aligns gh's PR rows with numbered Search API issue pages. const WORK_ITEM_NUMBER_SORT_QUALIFIER = 'sort:created-desc' const WORK_ITEM_PR_LIST_JSON_FIELDS = 'number,title,state,url,labels,updatedAt,author,isDraft,headRefName,baseRefName,headRefOid,headRepositoryOwner,reviewRequests' -// Why: these fields are intentionally excluded from `gh pr list` because -// statusCheckRollup/review decision/PR-specific merge metadata fan out into -// expensive GraphQL work across every row. Requested reviewers are kept in the -// list payload because the Tasks table renders that column on first paint. +// Why: kept out of `gh pr list` — statusCheckRollup/reviewDecision/merge metadata fan out into expensive per-row GraphQL. +// Requested reviewers stay in the list payload because Tasks renders that column on first paint. const WORK_ITEM_PR_DETAIL_JSON_FIELDS = 'number,title,state,url,labels,updatedAt,author,isDraft,headRefName,baseRefName,headRefOid,headRepositoryOwner,additions,deletions,changedFiles,reviewDecision,reviewRequests,latestReviews,assignees,statusCheckRollup,mergeable,mergeStateStatus,autoMergeRequest,maintainerCanModify' @@ -464,12 +451,8 @@ function mapIssueWorkItem(item: Record): MainWorkItem { } /** - * Derive both the author login and its API avatar_url in one place so GHE - * avatars render (the login-only `github.com/{login}.png` URL 404s on GHE). - * - * REST exposes `user.avatar_url`; gh/GraphQL expose `author.avatarUrl`. `gh pr - * view` omits the avatar entirely, so authorAvatarUrl is left undefined and the - * UI falls back to the login URL then a placeholder. See #8784. + * Derive author login + avatar_url together so GHE avatars render — the login-only + * `{login}.png` URL 404s on GHE. REST uses `user.avatar_url`, gh/GraphQL `author.avatarUrl` (#8784). */ function authorFieldsFromUnknown( item: Record @@ -505,8 +488,7 @@ function extractHeadOwnerLogin(item: Record): string | null { } } } - // Why: when a fork is deleted or made inaccessible, GitHub can return - // `head.repo = null` but still include `head.user`/`head.label`. + // Why: a deleted/inaccessible fork returns head.repo = null but still has head.user/head.label. const user = head.user if (typeof user === 'object' && user !== null) { const login = (user as { login?: unknown }).login @@ -682,14 +664,9 @@ function mapPullRequestWorkItem( item: Record, baseOwnerRepo: OwnerRepo | null = null ): MainWorkItem { - // Why: fork PRs are disabled in the Start-from picker. We compare the PR head's - // owner to the selected repo's owner; when the base repo is unknown we default - // to false so non-picker call sites see the same shape as before. + // Why: fork PRs are disabled in the Start-from picker; compare head owner to the selected repo's owner. const headOwnerLogin = extractHeadOwnerLogin(item) - // Why: only emit isCrossRepository when we actually know the head owner. If - // the gh response lacks `headRepositoryOwner` (older callers, tests without - // that fixture, or gh not returning it), leave the field undefined instead - // of falsely claiming "not a fork". + // Why: leave isCrossRepository undefined when the head owner is unknown, rather than falsely claiming "not a fork". const isCrossRepository = headOwnerLogin !== null && baseOwnerRepo !== null ? headOwnerLogin !== baseOwnerRepo.owner @@ -787,8 +764,7 @@ async function hydrateWorkItemRepositoryMergeMetadata( if (!ownerRepo || !hasPullRequest) { return items } - // Why: merge method settings are repository-level, so one cached metadata - // probe can keep Tasks rows accurate without per-PR GraphQL fan-out. + // Why: merge settings are repo-level, so one cached probe keeps Tasks rows accurate without per-PR GraphQL fan-out. const mergeMetadata = await detectRepositoryMergeMetadata(ownerRepo, undefined, ghOptions) if (!mergeMetadata.mergeMethodSettings && mergeMetadata.autoMergeAllowed === null) { return items @@ -835,9 +811,7 @@ async function fetchIssueWorkItem( return mapIssueWorkItem(JSON.parse(stdout) as Record) } -// REST /pulls/{n} has requested_reviewers but not latestReviews. When the JSON -// `gh pr view` path fails, still pull review fields from gh so mobile/desktop -// reviewer lists (CodeRabbit COMMENTED, etc.) are not silently empty. +// Why: REST /pulls/{n} lacks latestReviews, so pull review fields from gh so reviewer lists aren't silently empty. const WORK_ITEM_PR_REVIEW_JSON_FIELDS = 'reviewRequests,latestReviews' async function fetchPullRequestReviewFields( @@ -896,9 +870,7 @@ async function fetchPullRequestWorkItem( ) const item = JSON.parse(stdout) as Record const mapped = mapPullRequestWorkItem(item, ownerRepo) - // Why: merge-metadata GraphQL is best-effort. A failure here must not fall - // through to the REST path below — that path drops latestReviews and blanks - // the mobile/desktop reviewer list for bots that only left a review. + // Why: merge-metadata GraphQL is best-effort — don't fall through to REST, which drops latestReviews and blanks bot-only reviewer lists. const baseRefName = typeof item.baseRefName === 'string' ? item.baseRefName : undefined try { const mergeMetadata = await detectRepositoryMergeMetadata(ownerRepo, baseRefName, ghOptions) @@ -1010,8 +982,7 @@ function buildWorkItemListRequest(args: { } } - // Why: search/issues omits the PR fields used by the Tasks management - // columns. Fetch through gh's rich PR list and slice its stable created sort. + // Why: search/issues omits the PR fields the Tasks columns need; use gh's rich PR list on a stable created sort. searchParts.push(WORK_ITEM_NUMBER_SORT_QUALIFIER) const out = [ 'pr', @@ -1030,11 +1001,7 @@ function buildWorkItemListRequest(args: { return { args: out, offset: (page - 1) * limit } } -// Why: internal shape shared by listRecentWorkItems / listQueriedWorkItems so -// listWorkItems can lift per-side errors into the IPC envelope. The issue-side -// error is the specific new class of silent wrongness introduced by #1076 — -// PR-side errors existed before and are explicitly out of scope for this -// feature per the parent design doc §6. +// Why: shared shape so listWorkItems can lift the issue-side error (#1076 silent wrongness) into the IPC envelope; PR errors out of scope (§6). type PartialWorkItemsResult = { items: MainWorkItem[] issuesError?: ClassifiedError @@ -1048,8 +1015,7 @@ function assertSshRepoHasResolvedGitHubSource(args: { if (!args.connectionId || args.issueOwnerRepo || args.prOwnerRepo) { return } - // Why: SSH repo paths are remote-only, so gh cannot use cwd to infer repo - // context. Without a resolved owner/repo, running gh would query local state. + // Why: SSH repo paths are remote-only, so without a resolved owner/repo gh would query local state. throw new Error(GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE) } @@ -1066,8 +1032,7 @@ async function resolvePrWorkItemSource( localGitOptions: LocalGitExecOptions = {} ): Promise { const [originCandidate, upstreamCandidate] = await Promise.all([ - // Why: this caller combines both remotes itself, so it needs origin - // specifically (getOwnerRepo is upstream-first since #7331). + // Why: this caller combines both remotes itself, so it needs origin specifically (getOwnerRepo is upstream-first since #7331). getOwnerRepoForRemote(repoPath, 'origin', connectionId, localGitOptions), getOwnerRepoForRemote(repoPath, 'upstream', connectionId, localGitOptions) ]) @@ -1077,10 +1042,8 @@ async function resolvePrWorkItemSource( } /** - * gh exec for calls that rely on gh's own cwd→repo resolution (no explicit - * owner/repo). Serves a remembered deterministic resolution failure without - * spawning, so a remote-less repo costs one gh spawn per config change/TTL - * instead of two per Tasks refresh. + * gh exec relying on gh's own cwd→repo resolution. Serves a remembered resolution failure + * without spawning, so a remote-less repo costs one gh spawn per config change/TTL, not two per refresh. */ async function ghCwdResolvedExec( context: GitHubRepoContext, @@ -1135,9 +1098,7 @@ async function listRecentWorkItems( issueRequest.args.splice(1, 2) } if (issueOwnerRepo || prOwnerRepo || requiresExplicitRepo) { - // Why: allSettled so a 403 on upstream issues doesn't zero out the origin - // PR half — the UI renders partial results plus a banner for the failing - // side, matching the parent design doc's partial-failure rule (§2). + // Why: allSettled so a 403 on the issue side doesn't zero the PR half — partial results + banner (parent doc §2). const [issuesSettled, prsSettled] = await Promise.allSettled([ issueOwnerRepo ? ghExecFileAsync(issueRequest.args, ghOptions) @@ -1155,9 +1116,7 @@ async function listRecentWorkItems( let issuesError: ClassifiedError | undefined if (issuesSettled.status === 'fulfilled') { issues = (JSON.parse(issuesSettled.value.stdout) as Record[]) - // Why: the GitHub search/issues endpoint may still return PRs with a - // pull_request marker even when queried with is:issue. Filter them out - // here to keep the issue and PR buckets clean. + // Why: search/issues can still return PRs (pull_request marker) even with is:issue; filter them out. .filter((item) => !('pull_request' in item)) .map(mapIssueWorkItem) } else { @@ -1175,15 +1134,8 @@ async function listRecentWorkItems( .map((item) => mapPullRequestWorkItem(item, prOwnerRepo)) prs = await hydrateWorkItemRepositoryMergeMetadata(prs, prOwnerRepo, ghOptions) } else { - // Why: PR-side failures must preserve the pre-diff behavior of - // Promise.all by re-throwing so the rejection propagates up through - // listWorkItems to the renderer's cross-repo aggregator (which counts - // the repo as failed). This feature is scoped to the issue-side silent - // wrongness from #1076; PR errors must not be silently swallowed here. - // Why: if the issue side ALSO failed, the classified issuesError would - // otherwise be silently dropped when we throw the PR reason. Log it so - // debugging both-sides-failed scenarios (e.g. 403 on both endpoints) - // isn't blind to the issue-side classification. + // Why: re-throw PR errors so the cross-repo aggregator counts the repo failed; this feature only fixes issue-side swallowing (#1076). + // Why: log issuesError first so a both-sides-failed case isn't blind to the classification we're about to drop. if (issuesError) { console.warn( 'listRecentWorkItems: both issue and PR sides failed; issuesError was classified:', @@ -1200,13 +1152,7 @@ async function listRecentWorkItems( } } - // Why: the fallback path (non-GitHub remote — neither issueOwnerRepo nor - // prOwnerRepo resolved) intentionally stays on Promise.all rather than the - // Promise.allSettled + per-side classification used above. There are no - // `sources` to surface on this branch and nothing for the partial-failure - // banner to render, so a single-side failure here means the whole call is - // effectively unusable for the feature — reject-all matches reality. If - // non-GitHub remotes ever grow source metadata, revisit this symmetry. + // Why: non-GitHub remotes have no sources for a partial-failure banner, so keep Promise.all (reject-all) instead of allSettled. const [issuesResult, prsResult] = await Promise.all([ ghCwdResolvedExec(repoContext, issueRequest.args, ghOptions), ghCwdResolvedExec(repoContext, prRequest.args, ghOptions) @@ -1249,9 +1195,7 @@ async function listQueriedWorkItems( let nonAvailabilityFailureCount = 0 let availabilityError: unknown - // Why: run the issue and PR fetches in parallel but surface the - // issue-side error separately so the IPC envelope can carry it up. PR-side - // failures retain the prior swallow-and-log behavior per parent doc §6. + // Why: surface the issue-side error separately for the IPC envelope; PR-side keeps prior swallow-and-log (parent doc §6). const issueFetch = (async (): Promise => { if (!issueScope) { return { items: [] } @@ -1327,9 +1271,7 @@ async function listQueriedWorkItems( const [issueResult, prItems] = await Promise.all([issueFetch, prFetch]) if (availabilityError && successfulRequestCount === 0 && nonAvailabilityFailureCount === 0) { - // Why: combined queries normally preserve either successful half. When - // every attempted half hit the same availability class, propagate one of - // those existing failures so Tasks can distinguish an outage from no data. + // Why: when every half hit the same availability failure, propagate it so Tasks can distinguish an outage from no data. throw availabilityError } return { @@ -1369,10 +1311,7 @@ export async function listWorkItems( const prOwnerRepo = prResolved.source await acquire() try { - // Why: errors propagate to IPC so the renderer's cross-repo aggregator can - // count this repo as failed and surface the partial-failure banner. A - // catch-all here would make an auth/network failure indistinguishable from - // an empty result and silently under-report per-repo failures. + // Why: let errors propagate to IPC — a catch-all would make failure indistinguishable from empty and under-report per-repo failures. const partial = !trimmedQuery ? await listRecentWorkItems( repoPath, @@ -1425,8 +1364,7 @@ function buildSearchQueryString( if (query.state === 'open') { parts.push('is:open') } else if (query.state === 'closed') { - // Why: GitHub search treats merged PRs as closed. Exclude merged so the - // "Closed" filter actually means closed-without-merge. + // Why: GitHub search treats merged PRs as closed; exclude merged so "Closed" means closed-without-merge. parts.push('is:closed') if (query.scope !== 'issue') { parts.push('-is:merged') @@ -1482,16 +1420,13 @@ async function countWorkItemsForQuery( ], ghOptions ) - // Why: over-counts gh cache hits, which is the safe direction — the search - // bucket is only 30/min and the next probe corrects the estimate. + // Why: over-counting cache hits is the safe direction — the next probe corrects the estimate. noteRateLimitSpend('search') return Number.parseInt(stdout.trim(), 10) || 0 } function sameOwnerRepo(left: OwnerRepo | null, right: OwnerRepo | null): boolean { - // Why: GitHub treats owner and repo names as case-insensitive, so remotes - // with different casing (StablyAI/Orca vs stablyai/orca) point at the same - // repo and should not split into two search queries. + // Why: GitHub owner/repo names are case-insensitive, so differently-cased remotes are the same repo (don't split into two searches). return ( left?.owner.toLowerCase() === right?.owner.toLowerCase() && left?.repo.toLowerCase() === right?.repo.toLowerCase() @@ -1512,9 +1447,7 @@ function defaultOpenWorkItemQuery(): ParsedTaskQuery { } } -// Why: uses GitHub's search API to get total_count without fetching items. -// This powers the pagination bar so the user sees total pages upfront. -// Cached for 120s to avoid burning the search rate limit (30 req/min). +// Why: cached 120s to avoid burning the 30/min search rate limit that backs the pagination total. export async function countWorkItems( repoPath: string, query?: string, @@ -1540,10 +1473,7 @@ export async function countWorkItems( const parsedQuery = trimmedQuery ? parseTaskQuery(trimmedQuery) : null const effectiveQuery = parsedQuery ?? defaultOpenWorkItemQuery() - // Why: counts are decorative (pagination totals). The search bucket is only - // 30/min, so a multi-repo Tasks page must stop counting when the budget is - // gone instead of converting the remaining repos into 403 spawns. getRateLimit - // is 30s-cached and single-flight, so priming here is one spawn per window. + // Why: counts are decorative, so stop when the 30/min search budget is gone rather than spawn into 403s (getRateLimit is 30s-cached). await getRateLimit() if (rateLimitGuard('search').blocked) { return 0 @@ -1562,9 +1492,7 @@ export async function countWorkItems( } const counts: Promise[] = [] - // Why: `draft`, `reviewRequested`, and `reviewedBy` are PR-only predicates. - // When present, the issue half would always return 0 and wastes a search - // API call — skip the issue half entirely in that case. + // Why: draft/reviewRequested/reviewedBy are PR-only, so the issue half would always return 0 — skip it to save a search call. const hasPrOnlyFilter = effectiveQuery.draft || effectiveQuery.reviewRequested !== null || @@ -1596,9 +1524,7 @@ export async function countWorkItems( ) ) } - // Why: allSettled so a single failing search (e.g. transient network, rate - // limit on one side) doesn't silently zero out the total; sum only the - // fulfilled halves instead. + // Why: allSettled so one failing search side doesn't zero the total; sum only fulfilled halves. const results = await Promise.allSettled(counts) let total = 0 for (const r of results) { @@ -1622,9 +1548,7 @@ export async function getRepoSlug( connectionId?: string | null, options: HostedReviewExecutionOptions = {} ): Promise<{ owner: string; repo: string } | null> { - // Why: the slug is the checkout's own identity (renderer display, icon - // autodetect), so it must stay origin-based — getOwnerRepo became - // upstream-first for PR reads in #7331. + // Why: the slug is the checkout's own identity, so it must stay origin-based (getOwnerRepo is upstream-first since #7331). return getOwnerRepoForRemote( repoPath, 'origin', @@ -1634,11 +1558,8 @@ export async function getRepoSlug( } /** - * Resolve a fork's upstream/parent owner/repo, or null when the repo is not a - * fork. Why: a fork's `origin` points at the personal copy, so repo identity - * (notably the avatar) should prefer the upstream. Fast-paths the `upstream` - * remote (offline); otherwise asks the GitHub API for the fork parent. The API - * call targets the explicit origin slug, so it works for SSH repos too. + * Resolve a fork's upstream/parent owner/repo, or null when not a fork. + * Why: a fork's `origin` is the personal copy, so repo identity (avatar) should prefer upstream. * Best-effort: any failure (offline, unauthed, non-GitHub) resolves to null. */ export async function getRepoUpstream( @@ -1648,8 +1569,7 @@ export async function getRepoUpstream( ): Promise { const localGitArgs = hostedReviewLocalGitOptionArgs(options) const localGitOptions = localGitArgs[0] ?? {} - // Why: must be origin specifically — the fork-parent fast-path below compares - // it against the upstream remote (getOwnerRepo is upstream-first since #7331). + // Why: must be origin — the fast-path compares it against upstream (getOwnerRepo is upstream-first since #7331). const origin = await getOwnerRepoForRemote(repoPath, 'origin', connectionId, ...localGitArgs) if (!origin) { return null @@ -1667,8 +1587,7 @@ export async function getRepoUpstream( try { const { stdout } = await ghExecFileAsync( ['repo', 'view', `${origin.owner}/${origin.repo}`, '--json', 'isFork,parent'], - // Why: best-effort fork lookup runs at add-time; cap latency so a stalled - // gh process can't hold up repo creation. + // Why: cap latency so a stalled gh process can't hold up repo creation. { ...ghRepoExecOptions(githubRepoContext(repoPath, connectionId, localGitOptions)), timeout: 10_000 @@ -1753,8 +1672,7 @@ function parseCreatePRPayload(stdout: string): { number: number; url: string } | } catch { // Fall through to URL parsing for older gh versions without --json support. } - // Why: gh prints the PR URL (not JSON) here; match any host, not just - // github.com, so a GitHub Enterprise Server URL still parses directly (#8312). + // Why: match any host (not just github.com) so a GHES PR URL still parses (#8312). const urlMatch = trimmed.match(/https?:\/\/[^\s/]+\/[^\s/]+\/[^\s/]+\/pull\/(\d+)/) if (!urlMatch) { return null @@ -1762,11 +1680,7 @@ function parseCreatePRPayload(stdout: string): { number: number; url: string } | return { number: Number(urlMatch[1]), url: urlMatch[0] } } -// Why: `gh --repo OWNER/REPO` resolves the shorthand against gh's default host -// (usually github.com), not the repo's remote — so a GHES repo would target a -// same-named github.com repo, or fail. Qualify with the host for GHES so gh hits -// the Enterprise server; this is the only host signal for SSH repos, which run -// gh with no cwd context (#8312). github.com keeps the bare shorthand. +// Why: bare OWNER/REPO resolves against gh's default host, so qualify with host for GHES; the only host signal for cwd-less SSH repos (#8312). function ghRepoArg(slug: { owner: string; repo: string; host?: string }): string { return slug.host && slug.host.toLowerCase() !== 'github.com' ? `${slug.host}/${slug.owner}/${slug.repo}` @@ -1860,11 +1774,8 @@ export async function createGitHubPullRequest( } } - // Why: github.com-only slug parsing returns null for GHES, so fall back to the - // enterprise resolver (gh-authenticated custom host) before giving up (#8312). - // Creation targets origin: the head branch is unqualified, so `gh pr create` - // must run against the repo the branch was pushed to, not the fork parent - // (getOwnerRepo became upstream-first for PR reads in #7331). + // Why: github.com-only slug parsing returns null for GHES; fall back to the enterprise resolver (#8312). + // Why: use origin, not the fork parent — the unqualified head branch was pushed there (getOwnerRepo upstream-first since #7331). const ownerRepo = (await getOwnerRepoForRemote( repoPath, @@ -2029,11 +1940,7 @@ export async function getWorkItem( return issue } } catch (err) { - // Why: a transient failure (5xx, rate limit, network flake) on issue #N would - // silently fall through to PR #N — potentially a completely unrelated item. - // Only fall through when the issue genuinely doesn't exist (404); re-throw everything - // else so the outer catch returns null and the caller sees a real failure instead of - // a wrong item. classifyGhError centralizes the 404/"not found" pattern-matching. + // Why: only fall through to PR #N on a genuine 404; re-throw transient errors so a flake can't surface an unrelated PR. const stderr = err instanceof Error ? err.message : String(err) if (classifyGhError(stderr).type !== 'not_found') { throw err @@ -2130,8 +2037,7 @@ const GITHUB_AUTO_MERGE_METHODS: Record TRACKED_UPSTREAM_SNAPSHOT_CACHE_MAX_ENTRIES) { const oldestKey = trackedUpstreamSnapshotCache.keys().next().value if (oldestKey === undefined) { @@ -2549,8 +2448,7 @@ async function getTrackedUpstreamBranch( if (result.upstreamsByBranchName.has(branchName)) { return result.upstreamsByBranchName.get(branchName) ?? null } - // Why: a concurrent snapshot may finish before this branch exists in git. - // Re-probe instead of returning a one-shot synthetic null. + // Why: a concurrent snapshot may finish before this branch exists in git; re-probe instead of returning a synthetic null. const retryInFlight = trackedUpstreamSnapshotInFlight.get(cacheKey) if (retryInFlight) { const retryResult = await retryInFlight @@ -2558,9 +2456,7 @@ async function getTrackedUpstreamBranch( } } - // Why: PR polling can ask about hundreds of local worktree branches at once. - // Read the branch upstream snapshot in one git process per repo/runtime - // instead of spawning one failing `branch@{upstream}` probe per branch. + // Why: PR polling asks about hundreds of branches at once; read all upstreams in one git process per repo/runtime, not one probe per branch. const probeGeneration = beginTrackedUpstreamSnapshotProbe(cacheKey) const probe = probeTrackedUpstreamSnapshot(repoPath, connectionId, localGitOptions) trackedUpstreamSnapshotInFlight.set(cacheKey, probe) @@ -2615,8 +2511,7 @@ async function probeTrackedUpstreamSnapshot( const gitConfigSignature = startingGitConfigSignature === endingGitConfigSignature ? endingGitConfigSignature : undefined return { - // Why: transient git failures must not cache an empty snapshot that forces - // every branch lookup to delete and re-probe on the next refresh tick. + // Why: don't cache an empty snapshot after a transient git failure, or every branch lookup re-probes on the next refresh tick. cacheable: !configSignatureChanged && !probeFailed, probeFailed, ...(gitConfigSignature ? { gitConfigSignature } : {}), @@ -2627,8 +2522,7 @@ async function probeTrackedUpstreamSnapshot( function getCacheableTrackedUpstreamSnapshot( upstreamsByBranchName: Map ): Map { - // Why: SSH/WSL cannot cheaply inspect remote .git/config here. The short TTL - // still bounds stale positives, while repeated PR refreshes share one scan. + // Why: SSH/WSL can't cheaply inspect remote .git/config here; the short TTL bounds stale positives while refreshes share one scan. return upstreamsByBranchName } @@ -2744,8 +2638,7 @@ async function lookupPRByBranchName(args: { args.ghOptions ) : await getFallbackPRListForBranch(candidate, args.branchName, args.ghOptions) - // Why: REST/list branch lookup identifies the PR cheaply; exact - // `gh pr view` carries review, merge queue, and auto-merge state. + // Why: REST/list branch lookup identifies the PR cheaply; exact `gh pr view` carries review, merge-queue, and auto-merge state. const data = await hydrateBranchLookupWithExactPR(candidate, branchData, args.ghOptions) if (data) { return { data, dataRepo: candidate } @@ -2777,8 +2670,7 @@ async function lookupPRByBranchName(args: { } } } - // Why: branch-list failures are ambiguous for fork discovery, but exact - // fallback-number recovery should still get a chance before surfacing error. + // Why: branch-list failures are ambiguous for fork discovery; give exact fallback-number recovery a chance before surfacing the error. return hasPendingError ? { data: null, dataRepo: null, pendingError } : { data: null, dataRepo: null } @@ -2837,8 +2729,7 @@ async function getPRByNumber( ghOptions ) } catch (err) { - // Why: deleted or manually edited linked PR metadata should fall back to - // branch discovery; quota/auth/network failures get one cheaper REST exact lookup. + // Why: deleted/edited linked PR metadata falls back to branch discovery; quota/auth/network failures get one cheaper REST exact lookup. if (isNotFoundGhError(err)) { return null } @@ -2892,8 +2783,7 @@ async function lookupPRByNumber(args: { } } catch (err) { if (isNoPullRequestError(err)) { - // Why: stale cached fallback numbers should not turn every poll into an - // error when the PR was deleted or belonged to a different repo. + // Why: stale cached fallback numbers shouldn't error every poll when the PR was deleted or belongs to another repo. return { data: null, dataRepo: null } } throw err @@ -2941,10 +2831,7 @@ export async function getPRForBranch( return outcome.kind === 'found' ? outcome.pr : null } -// Why: the exact-linked fallback (`gh pr view` with no resolved repo candidates) -// returns dataRepo=null, which would leave the merged-PR membership probe unable -// to run. Derive the PR's own repo from its web URL so a diverged merged linked -// PR can still be confirmed and cleared. Host-agnostic to cover GitHub Enterprise. +// Why: exact-linked fallback returns dataRepo=null; derive the PR's repo from its web URL (host-agnostic for GHE) so the merged-PR membership probe can run. function ownerRepoFromPullRequestUrl(url: string): OwnerRepo | null { const match = url.match(/^https?:\/\/[^/\s]+\/([^/\s]+)\/([^/\s]+)\/pull\/\d+/) return match ? { owner: match[1], repo: match[2] } : null @@ -2958,10 +2845,8 @@ export async function getPRForBranchOutcome( fallbackPRNumber?: number | null, options: GitHubPRBranchLookupOptions = {} ): Promise { - // Strip refs/heads/ prefix if present const branchName = branch.replace(/^refs\/heads\//, '') - // Why: detached HEAD cannot use branch lookup, but an exact linked/fallback - // PR number remains safe to query and keeps review state visible. + // Why: detached HEAD can't use branch lookup, but an exact linked/fallback PR number is still safe to query and keeps review state visible. if (!branchName && typeof linkedPRNumber !== 'number' && typeof fallbackPRNumber !== 'number') { return { kind: 'no-pr', fetchedAt: Date.now() } } @@ -3028,8 +2913,7 @@ export async function getPRForBranchOutcome( explicitCurrentHeadOid ) if (membership === 'not-contained') { - // explicitCurrentHeadOid is non-null here (guarded above); record the - // exact head so consumers only clear the worktree that actually diverged. + // explicitCurrentHeadOid is non-null here (guarded above); record the exact diverged head so consumers clear only that worktree. headDivergedFromMergedPRAtOid = explicitCurrentHeadOid } } @@ -3040,9 +2924,7 @@ export async function getPRForBranchOutcome( if (!candidate || !isMergedImplicitPR(candidate, linkedPRNumber)) { return false } - // Why: prefer the caller-supplied worktree HEAD; only shell out (against - // the main repo path) when no explicit oid is available, matching legacy - // behavior. This keeps merged-at-head PRs visible for secondary worktrees. + // Why: prefer the caller's worktree HEAD; only shell out (main repo path) when no explicit oid, keeping merged-at-head PRs visible for secondary worktrees. currentHeadOidForMergedImplicit ??= explicitCurrentHeadOid !== null ? explicitCurrentHeadOid @@ -3050,10 +2932,7 @@ export async function getPRForBranchOutcome( if (!shouldHideMergedImplicitPR(candidate, linkedPRNumber, currentHeadOidForMergedImplicit)) { return false } - // Why: a worktree can sit behind its own PR's final head (update-branch - // merges, web-committed suggestions). A head that is one of the PR's own - // commits is the same line of work, not a reused branch name — keep the - // merged PR visible instead of offering "create a pull request". + // Why: a head that is one of the PR's own commits (update-branch/web commits) is the same work, not a reused branch name — keep the merged PR visible. return ( (await mergedPRContainsHead(candidate, candidateRepo, currentHeadOidForMergedImplicit)) !== 'contained' @@ -3069,8 +2948,7 @@ export async function getPRForBranchOutcome( data = exactLookup.data dataRepo = exactLookup.dataRepo } else if (branchName) { - // During a rebase the worktree is in detached HEAD and branch is empty. - // An empty --head filter causes gh to return an arbitrary PR. + // During a rebase (detached HEAD) branch is empty; an empty --head filter makes gh return an arbitrary PR. const branchLookup = await lookupPRByBranchName({ candidates, headRepo, @@ -3084,8 +2962,7 @@ export async function getPRForBranchOutcome( hasPendingBranchLookupError = true } if (!data) { - // Why: the tracked upstream can identify the real PR head by branch - // name or by fork owner even when branch names match locally. + // Why: the tracked upstream identifies the real PR head by branch name or fork owner even when local branch names match. const upstreamBranch = await getTrackedUpstreamBranch( repoPath, branchName, @@ -3154,16 +3031,11 @@ export async function getPRForBranchOutcome( explicitCurrentHeadOid !== null && shouldHideMergedImplicitPR(data, linkedPRNumber, explicitCurrentHeadOid) && (await mergedPRContainsHead(data, dataRepo, explicitCurrentHeadOid)) !== 'contained' - // Why no lazy-HEAD re-check on preservation: fallback numbers come from - // callers that already gated them on head equality or confirmed - // containment; re-hiding against the main-repo HEAD would blank - // deleted-head merged PRs that are deliberately kept visible. + // Why no lazy-HEAD re-check: fallback numbers were already gated on head equality/containment; re-hiding would blank kept deleted-head merged PRs. const shouldPreserveMergedFallback = !explicitHeadHidesMergedImplicitPR && (fallbackConfirmedMergedBranch || options.acceptMergedFallbackPR === true) - // Why: a currently visible PR can be merged outside Orca; when the caller - // marks the fallback as visible review state, keep its lifecycle fresh even - // if GitHub no longer reports it by branch (for example deleted heads). + // Why: a visible PR can be merged outside Orca; keep a caller-marked fallback fresh even when GitHub no longer reports it by branch (e.g. deleted heads). if ((await hideMergedImplicitPR(data, dataRepo)) && !shouldPreserveMergedFallback) { return { kind: 'no-pr', fetchedAt: Date.now() } } @@ -3440,8 +3312,7 @@ function mapGraphQLPendingApprovalCheckSuite( name: getPendingApprovalCheckSuiteName(suite, headSha, index), status: 'completed', conclusion: 'action_required', - // Why: suite-only approval blockers have no check run; use the suite page - // as the actionable destination when GraphQL exposes one. + // Why: suite-only approval blockers have no check run; link the suite page when GraphQL exposes one. url: nullableString(suite.url) ?? (headSha ? getPendingApprovalCheckSuiteUrl(ownerRepo, headSha, suite.databaseId) : null) @@ -3472,8 +3343,7 @@ function mapGraphQLPRChecksResponse( .map((context) => nullableNumber(context.checkSuite?.databaseId)) .filter((id): id is number => id !== null) ) - // Why: mixed-CI repos expose Jenkins/Prow/Tide as legacy status contexts - // inside the same rollup as check runs. Keep check-run metadata on collisions. + // Why: mixed-CI repos expose Jenkins/Prow/Tide as legacy status contexts in the same rollup; keep check-run metadata on name collisions. const legacyStatuses = contexts .filter(isGraphQLStatusContext) .map(mapGraphQLStatusContext) @@ -3544,8 +3414,7 @@ async function getPRChecksViaRestFallback( .map(mapRestCommitStatus) .filter((check): check is PRCheckDetail => check !== null && !checkRunNames.has(check.name)) } catch (err) { - // Why: the REST fallback is already degraded; keep richer check-run rows - // if the legacy-status enrichment fails. + // Why: REST fallback is already degraded; keep the richer check-run rows if legacy-status enrichment fails. console.warn('getPRChecks REST status fallback failed:', err) } @@ -3612,8 +3481,7 @@ export async function getPRChecks( } const { stdout } = await ghExecFileAsync(fallbackArgs, ghOptions).catch((err: unknown) => { const { stderr } = extractExecError(err) - // Why: `gh pr checks` exits non-zero when a PR genuinely has no check - // runs yet. Treat that as an empty optional section, not a load failure. + // Why: `gh pr checks` exits non-zero when a PR has no check runs yet; treat that as empty, not a load failure. if (stderr.toLowerCase().includes('no checks reported')) { return { stdout: '[]', stderr } } @@ -3644,8 +3512,7 @@ export async function getPRChecks( if (canUseGraphQLRollup) { await acquire() try { - // Why: --cache 60s saves rate-limit budget during polling, but when the - // user explicitly clicks refresh we must skip it so gh fetches fresh data. + // Why: --cache 60s saves rate-limit budget during polling; explicit refresh skips it for fresh data. const cacheArgs = options?.noCache ? [] : ['--cache', '60s'] const { stdout } = await ghExecFileAsync( [ @@ -3672,8 +3539,7 @@ export async function getPRChecks( return checks } } catch (err) { - // Why: if GitHub's richer rollup query is unavailable, the older gh - // command still returns the combined check/status list for display. + // Why: fall back to older `gh pr checks` when GitHub's richer rollup query is unavailable. console.warn('getPRChecks via GraphQL rollup failed, falling back to gh pr checks:', err) } finally { release() @@ -3821,8 +3687,7 @@ async function attachFailedJobLogTails( }) .slice(0, PR_CHECK_LOG_TAIL_JOB_LIMIT) - // Why: failed workflows can have many jobs; cap log fetches so details remain - // a lazy, bounded follow-up request instead of a burst of hosted log downloads. + // Why: cap log fetches so failed-job details stay a bounded follow-up, not a burst of hosted log downloads. for (const job of failedJobs) { const cacheKey = getCheckJobLogTailCacheKey(job) if (!cacheKey) { @@ -4042,9 +3907,7 @@ export async function rerunPRChecks( } } -// Why: review thread resolution status and thread IDs are only available via -// GraphQL. The REST pulls/{n}/comments endpoint does not expose them, so we -// use GraphQL for review threads and REST for issue-level comments. +// Why: review thread resolution status + thread IDs are GraphQL-only (REST pulls/{n}/comments omits them). const REVIEW_THREADS_QUERY = ` query($owner: String!, $repo: String!, $pr: Int!) { repository(owner: $owner, name: $repo) { @@ -4114,15 +3977,11 @@ export async function getPRComments( await acquire() try { if (ownerRepo) { - // Why: --cache 60s saves rate-limit budget during normal loads, but when the - // user explicitly clicks refresh we must skip it so gh fetches fresh data. + // Why: --cache 60s saves rate-limit budget on normal loads; explicit refresh skips it for fresh data. const cacheArgs = options?.noCache ? [] : ['--cache', '60s'] const base = `repos/${ownerRepo.owner}/${ownerRepo.repo}` - // Why: use allSettled so a single failing endpoint (e.g. GraphQL - // permissions, transient network error) doesn't blank out all comments. - // Each source is parsed independently; failed sources contribute zero - // comments instead of aborting the entire fetch. + // Why: allSettled so one failing endpoint doesn't blank out all comments; failed sources contribute zero. const reviewThreadsGuard = rateLimitGuard('graphql') let reviewThreadsFetch: Promise<{ stdout: string; stderr: string } | null> if (reviewThreadsGuard.blocked) { @@ -4151,10 +4010,7 @@ export async function getPRComments( ghOptions ), reviewThreadsFetch, - // Why: review summaries (approve, request changes, general comments) live - // under pulls/{n}/reviews, not under issue comments or review threads. - // Without this, a reviewer who submits "LGTM" without inline threads - // would have their comment silently dropped from the panel. + // Why: review summaries (approve/request-changes/general) live under pulls/{n}/reviews, not issue comments or threads. ghExecFileAsync( ['api', ...cacheArgs, `${base}/pulls/${prNumber}/reviews?per_page=100`], ghOptions @@ -4260,9 +4116,7 @@ export async function getPRComments( threadId: thread.id, isResolved: thread.isResolved, isOutdated: thread.line == null, - // Why: GitHub nulls out line/startLine when the commented code is - // outdated (e.g. after a force-push). Fall back to originalLine which - // always preserves the line numbers from when the comment was created. + // Why: GitHub nulls line/startLine when the commented code is outdated (e.g. force-push); originalLine preserves the original numbers. line: thread.line ?? thread.originalLine ?? undefined, startLine: thread.startLine ?? thread.originalStartLine ?? undefined }) @@ -4274,8 +4128,7 @@ export async function getPRComments( } } - // Parse review summaries (REST) — only include reviews with a body, - // since empty-body reviews (e.g. approvals with no comment) add noise. + // Review summaries (REST); skip empty-body reviews (e.g. approvals with no comment) as noise. type RESTReview = { id: number user: { login: string; avatar_url: string; type?: string } | null @@ -4581,9 +4434,7 @@ export async function mergePR( return { ok: false, error: mergeBlocker } } - // Don't use --delete-branch: it tries to delete the local branch which - // fails when the user's worktree is checked out on it. Branch cleanup - // is handled by worktree deletion (local) and GitHub's auto-delete setting (remote). + // Don't use --delete-branch: it deletes the local branch, which fails while the worktree is checked out on it. const args = ['pr', 'merge', String(prNumber), `--${method}`] if (ownerRepo) { args.push('--repo', `${ownerRepo.owner}/${ownerRepo.repo}`) @@ -4636,9 +4487,7 @@ export async function setPRAutoMerge( } } -// Why: GitHub rejects enabling auto-merge on a PR that can already merge with -// "Pull request is in clean status". Surface the actionable next step (merge -// directly) instead of the raw GraphQL error. +// Why: GitHub rejects auto-merge on an already-mergeable PR ("clean status"); surface an actionable message instead of the raw error. function classifySetAutoMergeError(message: string): string { if (/in clean status/i.test(message)) { return 'This pull request can already be merged. Use Merge instead of auto-merge.' @@ -4734,8 +4583,7 @@ async function enablePRAutoMerge( if (pr.headRefOid) { args.push('-f', `expectedHeadOid=${pr.headRefOid}`) } - // Why: `gh pr merge --auto` can perform an immediate merge; this mutation - // only creates GitHub's auto-merge request and lets branch requirements gate it. + // Why: `gh pr merge --auto` can merge immediately; this mutation only creates the auto-merge request, letting branch requirements gate it. await ghExecFileAsync(args, { ...ghOptions, env: { ...process.env, GH_PROMPT_DISABLED: '1' } @@ -4769,8 +4617,7 @@ async function getPRMergeBlocker( if (pr.mergeQueueRequired === true) { return 'This pull request must be merged through GitHub merge queue. Use Merge when ready instead.' } - // Why: conflict summaries shell out to local git; SSH repo paths are remote-only - // until that helper is routed through the SSH git provider. + // Why: conflict summaries shell out to local git; skip for SSH repos until that helper routes through the SSH provider. if ( connectionId || pr.mergeable !== 'CONFLICTING' || @@ -4790,8 +4637,7 @@ async function getPRMergeBlocker( ) return formatMergeConflictBlocker(pr.baseRefName, summary) } catch { - // Why: conflict preflight should improve stale UI diagnostics, not make - // merge impossible when the lookup endpoint has a transient failure. + // Why: conflict preflight should improve stale UI diagnostics, not block merge on a transient lookup failure. return null } } @@ -4827,8 +4673,7 @@ export async function updatePRState( await acquire() try { const cmd = updates.state === 'closed' ? 'close' : 'reopen' - // Why: GitHub can reject REST pull state PATCHes for reopen paths with a - // generic 422; gh's PR commands use GitHub's supported reopen flow. + // Why: gh's PR commands use GitHub's supported reopen flow; REST state PATCH can 422 on reopen. await ghExecFileAsync( ['pr', cmd, String(prNumber), '--repo', `${ownerRepo.owner}/${ownerRepo.repo}`], { diff --git a/src/main/github/pr-refresh-coordinator.ts b/src/main/github/pr-refresh-coordinator.ts index 700cc32ecd2..592bea4870b 100644 --- a/src/main/github/pr-refresh-coordinator.ts +++ b/src/main/github/pr-refresh-coordinator.ts @@ -1,6 +1,4 @@ -/* eslint-disable max-lines -- Why: the coordinator keeps queueing, pacing, and -renderer broadcast rules together so freshness and rate-limit invariants are -reviewable in one place. */ +/* eslint-disable max-lines -- Why: queueing, pacing, and broadcast rules stay together so freshness and rate-limit invariants are reviewable in one place. */ import { webContents } from 'electron' import type { GitHubPRRefreshAlias, @@ -83,16 +81,13 @@ const queue = new Map() const backgroundStarts: number[] = [] const activeStartsByScope = new Map() const errorBackoff = new Map() -// Per-key manual-retry cooldown from a real secondary rate limit (Retry-After). -// refreshPRNow refuses a manual retry until this time so a stale renderer cannot -// bypass the gate the client advertised via `retryDisabledUntil`. +// Per-key manual-retry cooldown (secondary rate-limit Retry-After); refreshPRNow refuses manual retry until this time so a stale renderer can't bypass it. const manualRetryGates = new Map() let lastBackgroundStartAt = 0 /** * Track (or clear) the manual-retry cooldown for a key from a broadcast outcome. - * Only a rate-limit outcome carrying `retryDisabledUntil` sets a gate; any other - * settled outcome clears it. + * Only a rate-limit outcome carrying `retryDisabledUntil` sets a gate; any other settled outcome clears it. */ function noteManualRetryGate(key: string, outcome: PRRefreshOutcome): void { if (outcome.kind === 'upstream-error' && outcome.retryDisabledUntil !== undefined) { @@ -169,8 +164,7 @@ export function clearVisiblePRRefreshWindow(windowId: number): void { const hadVisibleRefreshes = visibleByWindow.delete(windowId) clearActiveBurstWindow(windowId) if (hadVisibleRefreshes) { - // Why: visible follow-ups are owned by the renderer that reported them. - // If that WebContents is destroyed, no later visibility report may arrive. + // Why: visible follow-ups are owned by the reporting renderer; if its WebContents is destroyed, no later visibility report arrives. removeInvisibleVisibleRefreshes() } } @@ -178,12 +172,7 @@ export function clearVisiblePRRefreshWindow(windowId: number): void { /** * Drop a removed worktree's aliases from every queue entry. * - * Why: many local branches/worktrees that resolve to the same PR coalesce into - * one queue entry whose `aliases` map keeps one entry per worktree. Aliases are - * only pruned when a candidate is re-enqueued as invalid — never when a worktree - * is simply removed. Over a long session with churning worktrees these maps grow - * unbounded, contributing to the renderer/process memory creep behind the OOM - * reports. Called from worktree removal so stale aliases are released promptly. + * Why: aliases are otherwise only pruned when a candidate is re-enqueued as invalid, so churning worktrees grow these maps unbounded (OOM creep). */ export function pruneWorktreePRRefreshAliases(worktreeId: string): void { for (const [key, entry] of queue) { @@ -203,8 +192,7 @@ export function pruneWorktreePRRefreshAliases(worktreeId: string): void { resetKeyRetryState(key) continue } - // Keep the entry alive but ensure its representative candidate is not a - // dangling reference to the removed worktree. + // Keep the entry alive but replace its representative so it isn't a dangling reference to the removed worktree. if (entry.candidate.worktreeId === worktreeId) { const replacementAlias = entry.aliases.values().next().value if (replacementAlias) { @@ -213,9 +201,7 @@ export function pruneWorktreePRRefreshAliases(worktreeId: string): void { cacheKey: replacementAlias.cacheKey, branch: replacementAlias.branch, worktreeId: replacementAlias.worktreeId, - // Why: the probe now represents the replacement worktree, so it must - // use that worktree's head — otherwise divergence is stamped for the - // removed worktree's head and the survivor's link is never cleared. + // Why: the probe now represents the replacement worktree, so use its head — else the removed worktree's link never clears. currentHeadOid: replacementAlias.currentHeadOid ?? null } } @@ -382,8 +368,7 @@ function setVisibleFollowUp(entry: QueueEntry): void { existing.aliases.set(alias.cacheKey, alias) } - // Why: a user activation can arrive while a background refresh is awaiting gh. - // The background follow-up must not overwrite that pending active/manual work. + // Why: a user activation can arrive while a background refresh awaits gh; the follow-up must not overwrite that pending active/manual work. if ( bypassesFreshnessDelay(existing.reason) || existing.priority > entry.priority || @@ -418,9 +403,7 @@ function removeQueuedAliasForInvalidCandidate(key: string, alias: GitHubPRRefres cacheKey: replacementAlias.cacheKey, branch: replacementAlias.branch, worktreeId: replacementAlias.worktreeId, - // Why: the probe now represents the replacement worktree, so it must use - // that worktree's head — otherwise divergence is stamped for the pruned - // candidate's head and the survivor's link is never cleared. + // Why: the probe now represents the replacement worktree, so use its head — else the survivor's link never clears. currentHeadOid: replacementAlias.currentHeadOid ?? null, isArchived: false, isBare: false @@ -430,8 +413,7 @@ function removeQueuedAliasForInvalidCandidate(key: string, alias: GitHubPRRefres /** * Advances the visible-key error backoff and returns the earliest retry time. - * The renderer surfaces this as `nextAutoRetryAt`; only visible keys auto-retry, - * so callers must gate on `isVisibleKey` before computing a schedule. + * Why: only visible keys auto-retry, so callers must gate on `isVisibleKey` first. */ function nextVisibleErrorRetryAt(key: string): number { const failures = (errorBackoff.get(key)?.failures ?? 0) + 1 @@ -442,23 +424,14 @@ function nextVisibleErrorRetryAt(key: string): number { } /** - * Stamps the auto-retry time onto an error outcome before broadcast so every - * alias receives identical timing. - * - * Why: this only sets `nextAutoRetryAt` (the "Orca will retry at {time}" hint). - * It must NOT set `retryDisabledUntil` — that field disables *manual* Retry and - * is reserved for a real rate-limit gate (the paused/breaker path in - * `refreshPRNow`, which maps `pausedUntil` into it). Deriving it from ordinary - * exponential backoff would disable a manual Retry that `refreshPRNow` would in - * fact accept. + * Stamps the auto-retry time onto an error outcome before broadcast. + * Why: sets only `nextAutoRetryAt`, never `retryDisabledUntil` — that disables *manual* Retry and is reserved for a real rate-limit gate. */ function withErrorSchedule(outcome: PRRefreshOutcome, retryAt: number): PRRefreshOutcome { if (outcome.kind !== 'upstream-error') { return outcome } - // Why: preserve a real Retry-After cooldown the client already stamped on the - // outcome (`retryDisabledUntil`); auto-retry no earlier than both the backoff - // and that cooldown so we do not retry into an active secondary rate limit. + // Why: honor a real Retry-After cooldown the client stamped (`retryDisabledUntil`) so we don't auto-retry into an active secondary rate limit. const cooldownUntil = outcome.retryDisabledUntil return { ...outcome, @@ -476,14 +449,12 @@ function scheduleVisibleFollowUp( options?: { pendingMergeabilityDelayMs?: number; plannedRetryAt?: number } ): void { if (!isVisibleKey(key)) { - // Why: manual/active refreshes can remove the queued visible retry after - // its owner window is gone, leaving the retry backoff without an owner. + // Why: manual/active refreshes can remove the queued visible retry after its owner window is gone, orphaning the backoff. resetKeyRetryState(key) return } if (outcome.kind === 'upstream-error') { - // Why: reuse the retry time already computed for the broadcast schedule so - // the same failure is not counted twice against the backoff. + // Why: reuse the retry time already computed for the broadcast so the same failure isn't counted twice against the backoff. const retryAt = options?.plannedRetryAt ?? nextVisibleErrorRetryAt(key) setVisibleFollowUp({ key, @@ -495,8 +466,7 @@ function scheduleVisibleFollowUp( queuedAt: nextQueueOrder(), windowId }) - // Why: this is a delayed retry, not active work; showing it as a spinner - // makes visible worktrees look stuck until the backoff expires. + // Why: this is a delayed retry, not active work; a spinner would make visible worktrees look stuck until backoff expires. scheduleDrain(retryAt - Date.now()) return } @@ -511,9 +481,7 @@ function scheduleVisibleFollowUp( pendingMergeabilityDueAt === null ? regularDueAt : Math.min(regularDueAt, pendingMergeabilityDueAt) - // Why: coalesced linked-PR refreshes may represent several local branches. - // Preserve every alias for the next visible follow-up so all cache entries - // keep receiving periodic updates. + // Why: a coalesced linked-PR refresh may represent several branches; preserve every alias so all cache entries keep getting updates. setVisibleFollowUp({ key, candidate: followUpCandidate, @@ -522,8 +490,7 @@ function scheduleVisibleFollowUp( priority, dueAt, queuedAt: nextQueueOrder(), - // Why: this manual one-shot fixes GitHub's transient UNKNOWN state; visible - // spacing would otherwise delay it past the intended prompt retry window. + // Why: this manual one-shot fixes GitHub's transient UNKNOWN state; visible spacing would delay it past the prompt retry window. bypassBackgroundBudget: pendingMergeabilityDueAt !== null, windowId }) @@ -543,8 +510,7 @@ function refreshIntervalForCandidate(candidate: GitHubPRRefreshCandidate): numbe candidate.cachedMergeable === 'UNKNOWN' && !hasResolvedMergeStateStatus(candidate.cachedMergeStateStatus) ) { - // Why: GitHub can return transient UNKNOWN mergeability while it computes - // the PR test merge; visible merge buttons need a prompt follow-up. + // Why: GitHub returns transient UNKNOWN mergeability while computing the test merge; visible merge buttons need a prompt follow-up. return MERGEABILITY_PENDING_REFRESH_MS } if (candidate.cachedChecksStatus === 'success') { @@ -573,9 +539,7 @@ function isMergeabilityPendingOutcome(outcome: PRRefreshOutcome): boolean { } function backgroundRefreshBuckets(): ('core' | 'graphql')[] { - // Why: branch refreshes prefer REST but can still fall back to `gh pr list` - // when local head-owner metadata is unavailable. Guard both buckets until the - // client exposes an exact per-lookup cost plan. + // Why: branch refreshes prefer REST but can fall back to `gh pr list`, so guard both buckets until the client exposes a per-lookup cost plan. return ['core', 'graphql'] } @@ -648,8 +612,7 @@ function activeOrder(a: QueueEntry, b: QueueEntry): number { if (activeBurstScope(a) !== activeBurstScope(b)) { return 0 } - // Why: activation churn should refresh the worktree the user lands on before - // stale transient selections from the same window/runtime scope. + // Why: refresh the worktree the user lands on before stale transient selections in the same window/runtime scope. return b.queuedAt - a.queuedAt } @@ -765,10 +728,7 @@ async function drainQueue(): Promise { ) if (isBackground(next.reason)) { - // Why: the probe only warms rateLimitGuard's cached snapshot, so a - // failed probe must fail open — GHES with rate limiting disabled 404s - // every probe (#7553). A genuinely broken gh surfaces per-key as typed - // fetch outcomes instead (visible keys additionally back off). + // Why: the probe only warms rateLimitGuard's cache, so it must fail open — GHES with rate limiting disabled 404s every probe (#7553). await getRateLimit() const buckets = backgroundRefreshBuckets() const blockedGuard = buckets @@ -791,8 +751,7 @@ async function drainQueue(): Promise { noteBackgroundStart() } if (next.reason === 'active') { - // Why: activation is high priority, but tab/worktree churn can enqueue - // many distinct active refreshes that each probe local Git. + // Why: tab/worktree churn can enqueue many distinct active refreshes that each probe local Git. noteActiveStart(next) } for (const bucket of buckets) { @@ -808,8 +767,7 @@ async function drainQueue(): Promise { next.candidate.linkedPRNumber == null ? (next.candidate.fallbackPRNumber ?? null) : null, ...hostedReviewOptionArgs(next.candidate) ) - // Why: compute the retry schedule before broadcasting so the outcome and - // its follow-up retry share one timing; only visible keys auto-retry. + // Why: compute the retry schedule before broadcasting so the outcome and its follow-up share one timing; only visible keys auto-retry. let plannedRetryAt: number | undefined let broadcastOutcome = outcome if (outcome.kind === 'upstream-error' && isVisibleKey(next.key)) { @@ -882,11 +840,7 @@ export function enqueuePRRefresh( existing.candidate = candidate existing.windowId = windowId ?? existing.windowId } else if (existing.candidate.worktreeId === candidate.worktreeId) { - // Why: a non-promoting coalesce (e.g. visible→visible) keeps the existing - // representative, but the representative drives the probe head. If its own - // worktree moved head/branch, refresh those probe inputs so divergence is - // stamped for the current head — otherwise the head-scoped clear never - // matches and a merged linked PR lingers after a branch switch. + // Why: the representative drives the probe head; refresh probe inputs when its worktree moved head/branch, else a merged linked PR lingers after a switch. existing.candidate = { ...existing.candidate, cacheKey: candidate.cacheKey, @@ -908,8 +862,7 @@ export function enqueuePRRefresh( windowId }) } - // Why: visible/SWR refreshes are background maintenance and may sit behind - // the budget queue. Only user/action-driven queueing should surface in UI. + // Why: visible/SWR are background maintenance behind the budget queue; only user/action-driven queueing should surface in UI. if (shouldBroadcastQueued(reason, dueAt)) { broadcast({ aliases: [alias], reason, status: 'queued' }) } @@ -968,11 +921,7 @@ export async function refreshPRNow(candidate: GitHubPRRefreshCandidate): Promise return outcome } - // Why: enforce the same rate-limit gate main advertises via `retryDisabledUntil` - // so a stale renderer cannot bypass it. A blocked primary bucket means gh would - // 403 anyway; a recorded secondary Retry-After cooldown means gh would 429 - // again. Refuse without spending quota until the later of the two and report - // the pause honestly. + // Why: enforce the rate-limit gate so a stale renderer can't bypass it; refuse without spending quota until the later of the two cooldowns. const manualBlockedGuard = backgroundRefreshBuckets() .map((bucket) => rateLimitGuard(bucket)) .find((guard) => guard.blocked) @@ -983,11 +932,7 @@ export async function refreshPRNow(candidate: GitHubPRRefreshCandidate): Promise ) if (gateUntil > Date.now()) { const retryAt = gateUntil - // Why: the paused broadcast maps `pausedUntil` into the renderer's - // `nextAutoRetryAt` ("Orca will retry at {time}"), so that auto-retry must be - // real. Requeue this candidate at the reset time and wake the drain then — - // mirroring the background paused path — instead of advertising an automatic - // retry that was never scheduled (finding 12). + // Why: paused maps `pausedUntil` into the renderer's auto-retry, so requeue at reset (finding 12) — don't advertise an unscheduled retry. queue.set(key, { key, candidate, @@ -1041,8 +986,7 @@ export async function refreshPRNow(candidate: GitHubPRRefreshCandidate): Promise ) scheduleVisibleFollowUp(key, candidate, outcome, 40, aliases, undefined, { plannedRetryAt, - // Why: GitHub often reports UNKNOWN immediately after `gh pr reopen`; - // do one prompt visible retry so conflicts replace the transient label. + // Why: GitHub reports UNKNOWN right after `gh pr reopen`; one prompt visible retry replaces the transient label. pendingMergeabilityDelayMs: MANUAL_MERGEABILITY_PENDING_REFRESH_MS }) return broadcastOutcome diff --git a/src/main/github/project-view.ts b/src/main/github/project-view.ts index 3a040d37520..8e2be18044e 100644 --- a/src/main/github/project-view.ts +++ b/src/main/github/project-view.ts @@ -1,9 +1,4 @@ -/* eslint-disable max-lines -- Why: ProjectV2 GraphQL has its own normalization -layer, retry policy (parent-field dance), paste-to-add parser, and discovery -pagination. Co-locating the read path keeps the retry/classify/normalize -contract reviewable as one surface. Lower-level plumbing (slug validation, -error classifier, runGraphql/runRest) lives in ./project-view/internals; the -slug-addressed write path lives in ./project-view/mutations. */ +/* eslint-disable max-lines -- Why: co-locate the ProjectV2 read path (normalize/retry/paste-parser/discovery) as one reviewable surface; plumbing lives in ./project-view/internals and mutations. */ import { acquire, release, @@ -52,8 +47,7 @@ import { isGitHubProjectRefInputTooLarge } from '../../shared/github-project-ref-input' -// Re-export the public API so existing call sites (`./project-view`) keep -// working unchanged. The split is internal-only. +// Re-export the public API so existing `./project-view` call sites keep working; the split is internal-only. export { isValidOwnerSlug, isValidRepoSlug, @@ -77,14 +71,7 @@ export { // ─── Constants ───────────────────────────────────────────────────────── -// Why: these defaults were deliberately shrunk from 50/50/100 to cut quota -// spend in the most expensive gh call reachable from TaskPage. Discovery -// walks viewer projects, then up to `DISCOVERY_MAX_ORGS` orgs × a nested -// `projectsV2(first:N)` query. The org loop dominates the cost and is the -// path that produced the user-visible HTTP 504 when one org was slow. Users -// with projects outside this window can still paste a URL to reach them — -// no functional loss. Prior values: MAX_ORGS=50, ORG_PAGE_SIZE=30, -// PROJECTS_PER_OWNER=100, nested projectsV2 first=50. +// Why: defaults deliberately shrunk to cut quota spend in discovery — the org loop dominates and produced the HTTP 504; overflow owners can paste a URL. const ITEM_PAGE_SIZE = 100 const MAX_ITEMS = 500 const VIEWS_PAGE_SIZE = 20 @@ -98,9 +85,7 @@ export const PROJECT_VIEW_OWNER_CACHE_MAX_ENTRIES = 512 // ─── Module-scope caches (reset on HMR — intentional) ────────────────── -// Why: pasted and discovered GitHub owners are user-controlled across a long -// main-process session; keep capability probes bounded to avoid unbounded -// retention while preserving the hot-owner fast path. +// Why: owners are user-controlled over a long session; bound cache entries to avoid unbounded retention while keeping the hot-owner fast path. function rememberProjectViewCacheEntry( cache: Map, key: K, @@ -129,20 +114,12 @@ function getProjectViewCacheEntry(cache: Map, key: K): V | undefined return value } -// Why: HMR reloading should re-probe capability. Both caches live as plain -// module locals so a dev-time code swap naturally re-runs capability probes -// instead of carrying a stale "unsupported" flag into fresh code. +// Why: plain module locals so HMR code swaps re-run capability probes instead of carrying a stale "unsupported" flag. const ownerTypeCache = new Map() -// Why: keyed by `${owner}\0${ownerType}` rather than a process-global flag so -// a single owner's capability gap (e.g. token without scope on org A) doesn't -// poison every subsequent fetch for unrelated owners that DO support -// Issue.parent. See bug-scan finding 2. +// Why: keyed per owner (not a process-global flag) so one owner's capability gap doesn't poison others that DO support Issue.parent (bug-scan finding 2). const parentFieldRetriedByOwner = new Map() const parentFieldWarningLoggedByOwner = new Map() -// Why: concurrent fetchAllItems calls for the same owner all observe the -// same "not-yet-retried" state, each issuing a duplicate first-page probe -// and racing to set the flag. Use an in-flight promise per owner so only -// one caller drives the probe; siblings await the same result. +// Why: in-flight promise per owner so concurrent fetchAllItems callers share one probe instead of each racing a duplicate first-page probe. const parentFieldProbeInFlight = new Map>() function ownerScopeKey(owner: string, ownerType: GitHubProjectOwnerType): string { @@ -392,8 +369,7 @@ export function normalizeFieldValue( } case undefined: default: - // Unknown __typename → forward-compat: drop silently, do not throw, - // do not classify as drift (see design §Error Handling). + // Unknown __typename → forward-compat: drop silently, don't classify as drift (see design §Error Handling). return null } } @@ -713,13 +689,7 @@ async function fetchViewFieldsContinuation( ): Promise< { ok: true; fields: RawProjectV2Field[] } | { ok: false; error: GitHubProjectViewError } > { - // Why: address the view directly via `node(id:)` instead of re-fetching the - // whole project + walking views every page. Previous shape paid an - // unnecessary `${VIEWS_PAGE_SIZE}` views fan-out per field-continuation - // page; the new shape is one round-trip per field page, which is the - // minimum possible cost. Field-paged views are rare (>50 fields), so this - // only matters for the few projects that hit it — but when they do, the - // savings compound across pagination loops. + // Why: address the view directly via node(id:) instead of re-walking all views each page — one round-trip per field page. const query = ` query($after:String!, $viewId:ID!) { node(id:$viewId) { @@ -844,9 +814,7 @@ type RawItemsPage = { nodes?: (RawItem | null)[] } -// Why: runGraphql returns the classified error but not the raw GraphQL -// errors; for the parent-field retry decision we need those. This variant -// returns the raw envelope so callers can re-inspect. +// Why: unlike runGraphql, return the raw GraphQL error envelope so the parent-field retry decision can re-inspect it. async function fetchItemsPageWithRaw(args: { owner: string ownerType: GitHubProjectOwnerType @@ -925,9 +893,7 @@ async function fetchItemsPageWithRaw(args: { try { parsed = JSON.parse(stdout) } catch { - // Why: when gh exits non-zero with no parseable JSON on stdout (network, - // auth, rate-limit, missing scope), classify against stderr so callers - // see the real cause instead of a synthesized drift/not-found. + // Why: gh exited non-zero with unparseable stdout; classify against stderr so callers see the real cause, not a synthesized drift/not-found. if (execFailed) { return { ok: false, @@ -943,9 +909,7 @@ async function fetchItemsPageWithRaw(args: { stderr } } - // Why: gh exec rejected but stdout still had a parseable error envelope — - // fall through to the parsed.errors branch below. If parsed has neither - // data nor errors, surface the stderr classification rather than not_found. + // Why: gh rejected but stdout parsed; fall through to parsed.errors below, else surface the stderr classification rather than not_found. if (execFailed && (!parsed.errors || parsed.errors.length === 0) && !parsed.data) { return { ok: false, @@ -987,26 +951,16 @@ async function fetchAllItems(args: { | { ok: true; rows: GitHubProjectRow[]; totalCount: number; parentFieldDropped: boolean } | { ok: false; error: GitHubProjectViewError; totalCount?: number } > { - // Why: caches keyed by (owner, ownerType) so a single owner's lack of - // Issue.parent capability (e.g. token without scope on org A) doesn't - // poison fetches for unrelated owners. See bug-scan finding 2. + // Why: keyed by (owner, ownerType) so one owner's missing Issue.parent capability doesn't poison unrelated owners. const scopeKey = ownerScopeKey(args.owner, args.ownerType) - // Why: if another caller for the SAME owner is currently probing whether - // Issue.parent is supported, await its decision so we don't fire a - // duplicate with-parent probe. We must re-read the retried flag AFTER - // awaiting because the probe may have flipped it. + // Why: await any in-flight same-owner probe so we don't duplicate it, then re-read the retried flag (the probe may have flipped it). const inFlight = parentFieldProbeInFlight.get(scopeKey) if (inFlight) { await inFlight.catch(() => {}) } let includeParent = !hasParentFieldRetried(scopeKey) let parentFieldDropped = !includeParent - // First page — single-flight the with-parent attempt PER OWNER so - // concurrent callers for the same owner observe one probe result instead - // of each issuing their own. We must assign the in-flight promise - // synchronously (no await between the get() check and the set()) so - // concurrent callers race on the same promise rather than each creating - // a duplicate probe. + // Single-flight the with-parent probe per owner; assign the in-flight promise synchronously (no await between get() and set()) so callers share one probe. let first: Awaited> let probePromise: Promise>> | null = null if (includeParent && !parentFieldProbeInFlight.has(scopeKey)) { @@ -1026,12 +980,7 @@ async function fetchAllItems(args: { after: null, includeParent: true }) - // Why: flip parentFieldRetriedByOwner BEFORE resolveProbe()/clearing - // parentFieldProbeInFlight so siblings that awoke on `inFlight.catch()` - // observe the updated flag. Doing this in the outer block (after - // `await probePromise`) leaves a gap where siblings see - // parentFieldProbeInFlight=null and parentFieldRetriedByOwner without - // the scopeKey, then issue duplicate with-parent probes. + // Why: set the retried flag BEFORE resolving/clearing the probe so siblings awoken on inFlight.catch() see it and don't fire duplicate with-parent probes. if (!result.ok && errorsIndicateParentField(result.rawErrors, result.stderr)) { markParentFieldRetried(scopeKey) } @@ -1054,9 +1003,7 @@ async function fetchAllItems(args: { }) } if (!first.ok && includeParent && errorsIndicateParentField(first.rawErrors, first.stderr)) { - // Retry the whole table without parent. Mark this owner as retried so - // subsequent fetches against the same owner skip the probe — but other - // owners are unaffected. + // Retry the whole table without parent; mark this owner retried so later fetches skip the probe (other owners unaffected). markParentFieldRetried(scopeKey) includeParent = false parentFieldDropped = true @@ -1255,16 +1202,7 @@ export async function getProjectViewTable( matchStrength = m } } - // Why: stop as soon as we have ANY match — including 'default' (first - // table view). Continuing to walk views pages for a default selector - // costs one extra GraphQL call per page with no upside: the default - // contract is "first table view we see", and view layouts don't change - // ordering between pages such that a later view would outrank the - // first table layout. Previously we kept walking on default match - // because the precedence comment hinted at re-ranking, but no real - // selector promotes a 'default' to a stronger match within the same - // selector input — those ranks only matter when the caller supplied - // a selector. Bail early on any non-null selectedRaw. + // Why: stop on ANY match (incl. 'default' = first table view); walking further pages costs a GraphQL call per page with no re-ranking upside. if (selectedRaw) { break } @@ -1300,15 +1238,11 @@ export async function getProjectViewTable( } const selectedView = finalized.view - // Why: an explicit empty-string override means "no filter"; treat undefined - // as "use the view's filter as-is". The override is ephemeral — never - // persisted to GitHub — so users can clear the search without mutating the - // view's stored filter. + // Why: empty-string override means "no filter"; undefined means "use the view's stored filter". The override is ephemeral, never persisted. const effectiveQuery = typeof args.queryOverride === 'string' ? args.queryOverride : selectedView.filter - // Unsupported layout: return without paginating items; attempt a cheap - // count-only query best-effort. + // Unsupported layout: skip item pagination; best-effort count-only query. if (selectedView.layout !== 'TABLE_LAYOUT') { const count = await fetchItemsCountOnly({ owner: args.owner, @@ -1389,10 +1323,7 @@ type RawViewerDiscovery = { export async function listAccessibleProjects(): Promise { const viewerProjects: GitHubProjectSummary[] = [] const orgProjects: GitHubProjectSummary[] = [] - // Why: per-org failures are collected so the picker can render a "some orgs - // didn't load" banner with the affected logins, instead of aborting the - // whole discovery on the first 504. Users with flaky org fetches still get - // viewer + other orgs in the list. + // Why: collect per-org failures so the picker shows a "some orgs didn't load" banner instead of aborting discovery on the first 504. const partialFailures: { owner: string; message: string }[] = [] let viewerLogin: string | null = null @@ -1423,10 +1354,7 @@ export async function listAccessibleProjects(): Promise(query, vars) if (!res.ok) { - // Why: a viewer-level failure is structural — if we can't list the - // user's own projects, we have nothing to build on. Propagate as a - // hard error instead of returning partial. Org-level errors below - // are non-fatal because the viewer slice is still useful on its own. + // Why: viewer-level failure is structural (no projects to build on), so propagate hard; org-level errors below are non-fatal. return { ok: false, error: res.error } } if (!res.data.viewer) { @@ -1463,13 +1391,7 @@ export async function listAccessibleProjects(): Promise(query, vars) if (!res.ok) { - // Why: the org-listing query itself failed (not a nested projectsV2). - // Record it as a partial failure against a synthetic `*` owner so the - // UI banner explains why additional orgs aren't listed, but keep any - // viewer projects we already collected. This is the critical 504 path - // the user reported in the ProjectPicker. + // Why: org-listing failed; record a synthetic '*' partial failure so the banner explains it, but keep collected viewer projects (the reported 504 path). partialFailures.push({ owner: '*', message: res.error.message }) break } @@ -1516,9 +1434,7 @@ export async function listAccessibleProjects(): Promise 0 && b.remaining < floor) { return { blocked: true, remaining: b.remaining, limit: b.limit, resetAt: b.resetAt } } @@ -143,12 +107,8 @@ export function rateLimitGuard(bucket: RateLimitBucketKind): } /** - * Decrement the cached `remaining` counter for a bucket after a successful - * spawn. Why: the canonical numbers come from the next probe, but between - * probes the cached snapshot would over-report budget if we didn't account - * for the work we just did. The decrement keeps the circuit breaker honest - * during a burst (e.g. paginating items) instead of waiting 30s for the cache - * to expire. + * Decrement the cached `remaining` for a bucket after a successful spawn. + * Why: between probes the snapshot would over-report budget, so this keeps the breaker honest during a burst instead of waiting for the TTL. */ export function noteRateLimitSpend(bucket: RateLimitBucketKind, cost = 1): void { if (!cached) { @@ -160,11 +120,7 @@ export function noteRateLimitSpend(bucket: RateLimitBucketKind, cost = 1): void } } -// Why: when the runner's breaker trips it only knows "blocked", not for how -// long. `gh api rate_limit` is exempt from limits, so one forced probe turns -// the fallback block into the bucket's real reset time (or clears a block -// that a stale fallback would otherwise keep alive). Single-flight: a 90-repo -// 403 burst must refine once, not 90 times. +// Why: the breaker only knows "blocked", not the reset time; one exempt probe refines it to the real reset or clears a stale block (single-flight so a 403 burst probes once). let resetRefinementInFlight: Promise | null = null function refineBreakerFromSnapshot(): void { @@ -193,9 +149,7 @@ function refineBreakerFromSnapshot(): void { registerGhRateLimitResetProbe(() => refineBreakerFromSnapshot()) -// Why: a 90-repo fan-out that primes the guard concurrently must resolve to -// one `gh api rate_limit` spawn, not one per repo — the TTL cache alone can't -// dedupe calls that all start before the first probe lands. +// Why: single-flight so a concurrent fan-out resolves to one probe — the TTL cache can't dedupe calls that start before the first lands. let probeInFlight: Promise | null = null export async function getRateLimit(options?: { force?: boolean }): Promise { diff --git a/src/main/github/work-item-details.ts b/src/main/github/work-item-details.ts index 406856c6b3e..87cdbe52eaa 100644 --- a/src/main/github/work-item-details.ts +++ b/src/main/github/work-item-details.ts @@ -1,6 +1,4 @@ -/* eslint-disable max-lines -- Why: the PR/Issue details service groups the - body/comments/files/checks fetch paths alongside the file-contents resolver - so the drawer's rate-limit and caching strategy lives in one place. */ +/* eslint-disable max-lines -- Why: groups the PR/Issue fetch paths and file-contents resolver so caching/rate-limit strategy lives in one place. */ import type { GitHubAssignableUser, GitHubPRFile, @@ -28,17 +26,12 @@ import { noteRateLimitSpend, rateLimitGuard } from './rate-limit' import { getPRReviewCommentLineNumbersFromPatch } from './pr-review-comment-lines' import { isMaxBufferOverflowError } from '../git/max-buffer-overflow' -// Why: a PR "changed file" listing returned by the REST endpoint is paginated -// at 100 per page; we cap at a reasonable total so a massive PR cannot starve -// the gh semaphore while we fetch file listings. +// Why: cap total PR files so a massive PR can't starve the gh semaphore while paging (100/page). const MAX_PR_FILES = 300 -// Why: issue timelines can be extremely noisy from automation and cross-links. -// Bound drawer detail work so one huge issue cannot monopolize gh/API time. +// Why: bound noisy issue timelines so one huge issue can't monopolize gh/API time. const MAX_ISSUE_TIMELINE_ITEMS = 300 const GITHUB_REST_PAGE_SIZE = 100 -// Why: hosted PR files must exceed the renderer's large-diff threshold before -// we give up on the raw fetch; otherwise the UI sees an empty diff instead of -// the safety fallback. +// Why: raw-fetch buffer must exceed the renderer's large-diff threshold, else the UI shows an empty diff instead of the fallback. const GITHUB_RAW_CONTENT_MAX_BUFFER_BYTES = 8 * 1024 * 1024 function localGitOptionArgs(options: LocalGitExecOptions = {}): [] | [LocalGitExecOptions] { @@ -75,12 +68,7 @@ const WORK_ITEM_PARTICIPANTS_QUERY = `query($owner: String!, $repo: String!, $nu } }` -// Why: a single GraphQL round-trip replaces three serial gh subprocesses on -// the issue path (REST issue + REST comments + GraphQL participants). The -// previous fan-out could spawn ~3 `gh` processes per drawer-open; this drops -// it to one. We still fall back to the legacy REST+GraphQL path if the -// collapsed query throws or returns missing data — see the strict-fallback -// branch in getWorkItemDetails. +// Why: one GraphQL round-trip replaces the 3 serial gh subprocesses (REST issue + comments + participants); falls back to the legacy path on failure. const ISSUE_DETAILS_QUERY = `query($owner: String!, $repo: String!, $number: Int!) { repository(owner: $owner, name: $repo) { issue(number: $number) { @@ -300,8 +288,7 @@ async function getIssueTimelineItems( ], ghOptions ) - // Why: --jq emits compact NDJSON while explicit pages let us stop once - // supported activity reaches the drawer cap. + // Why: --jq emits NDJSON and explicit paging lets us stop once we hit the drawer cap. const pageEvents = parseRestTimelineEventLines(stdout) for (const event of pageEvents) { const item = mapRestTimelineEvent(event) @@ -324,10 +311,9 @@ async function getIssueTimelineItems( } /** - * Fetch an issue's body, comments, assignees, participants, and timeline in a - * single GraphQL round-trip. Returns null on any partial error so the caller - * falls back to the strict REST path. Assignee/participant avatars are resolved - * here so GitHub Enterprise users don't render blank. + * Fetch an issue's body, comments, assignees, participants, and timeline in one GraphQL round-trip. + * Returns null on any partial error so the caller falls back to the strict REST path. + * Avatars are resolved here so GHE users don't render blank. */ async function getIssueDetailsViaGraphQL( repoPath: string, @@ -338,9 +324,7 @@ async function getIssueDetailsViaGraphQL( body: string comments: PRComment[] assignees: string[] - // Avatar-bearing assignees, kept separate from the `assignees` login list so - // callers that only need logins are unaffected; used to enrich assignee - // avatars that `gh` leaves blank (notably GitHub Enterprise users). + // Avatar-bearing assignees, kept separate from the login-only `assignees`; enriches avatars `gh` leaves blank (GHE). assigneeUsers: GitHubAssignableUser[] participants: GitHubAssignableUser[] timelineItems: GitHubIssueTimelineItem[] @@ -376,9 +360,7 @@ async function getIssueDetailsViaGraphQL( ) const parsed = JSON.parse(stdout) as GraphQLIssueDetailsResponse if (parsed.errors && parsed.errors.length > 0) { - // Why: any partial GraphQL error (permissions, unknown field on a fork) - // forces the strict REST fallback so the drawer never paints a half-built - // shell. The fallback path's behavior is the historical contract. + // Why: any partial GraphQL error forces the strict REST fallback so the drawer never paints a half-built shell. return null } const issue = parsed.data?.repository?.issue @@ -436,8 +418,7 @@ function mergeGitHubUsers(users: GitHubAssignableUser[]): GitHubAssignableUser[] const key = user.login.toLowerCase() const existing = byLogin.get(key) if (existing) { - // Why: avoid mutating caller-provided objects — return a new merged record - // so upstream references to `user`/`existing` stay unchanged. + // Why: return a new merged record instead of mutating caller-provided objects. byLogin.set(key, { login: existing.login, name: existing.name ?? user.name ?? null, @@ -486,14 +467,10 @@ function mapFileStatus(raw: string): GitHubPRFile['status'] { } } -// Why: GitHub's REST file listing does not explicitly flag binary files, but it -// omits the `patch` field for them. When a file has changes but no patch, we -// treat it as binary so the drawer's diff tab can show a placeholder instead of -// attempting to fetch contents that would render as noise in a text diff viewer. +// Why: REST doesn't flag binaries but omits `patch` for them; treat "changes but no patch" as binary so the diff tab shows a placeholder. function isBinaryHint(file: RESTPRFile): boolean { if (file.status === 'removed' || file.status === 'added') { - // A newly added or removed file with zero patch text but non-zero changes - // is almost always binary (images, lockfiles over the size cap, etc.). + // Added/removed file with changes but no patch is almost always binary (images, oversized lockfiles). return file.patch === undefined && file.changes > 0 } return file.patch === undefined && file.changes > 0 @@ -543,9 +520,7 @@ async function getPRHeadBaseSha( } } -// Why: null signals a failed/blocked fetch (rate limit, auth, unresolved -// remote) so the Files tab can show a retryable error instead of the -// misleading "No files changed." empty state; [] means a genuinely empty PR. +// Why: null = failed/blocked fetch (Files tab shows retry, not "No files changed."); [] = genuinely empty PR. async function getPRFiles( repoPath: string, prNumber: number, @@ -822,9 +797,7 @@ async function getWorkItemParticipants( connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise { - // Why: work items in a fork live on the upstream remote, so participants must - // be resolved via the same upstream-first resolvers the body/comment fetches - // use (getIssueOwnerRepo for issues, getOwnerRepo for PRs; see #7331). + // Why: fork work items live upstream, so resolve owner/repo with the same upstream-first resolvers as body/comments (#7331). const ownerRepo = item.type === 'issue' ? await getIssueOwnerRepo(repoPath, connectionId, ...localGitOptionArgs(localGitOptions)) @@ -895,8 +868,7 @@ async function getGitHubUsersByLogin( return [] } if (rateLimitGuard('graphql').blocked) { - // Why: surface the skip. Callers degrade silently to login-based avatar URLs - // (blank on GHE), so without this log "why are avatars missing" is untraceable. + // Why: log the skip — callers degrade silently to blank GHE avatars, so it's otherwise untraceable. console.warn( `getGitHubUsersByLogin skipped: GraphQL rate-limit budget exhausted (${uniqueLogins.length} logins unresolved)` ) @@ -945,15 +917,9 @@ async function getGitHubUsersByLogin( } /** - * Stamp GraphQL-resolved avatars onto a work item's author, reviewers, review - * requests, latest reviews, and assignees. + * Stamp GraphQL-resolved avatars onto a work item's author, reviewers, review requests, latest reviews, and assignees. * - * The work item's users come from `gh pr view`, which omits avatar_url. On - * GitHub Enterprise the login-based `github.com/{login}.png` URL 404s, so those - * avatars render blank while comment avatars (fetched via GraphQL) work. - * `knownUsers` (participants + the mention-author batch, which already includes - * the item's reviewers/assignees) carries the resolved avatars, so we apply - * them without any extra round-trip. See #8784. + * Why: `gh pr view` omits avatar_url, so login-based avatars 404 on GHE; knownUsers carries the GraphQL-resolved ones. See #8784. */ function enrichItemDisplayAvatars( item: Omit, @@ -968,16 +934,11 @@ function enrichItemDisplayAvatars( if (avatarByLogin.size === 0) { return item } - // Why: prefer the GraphQL-resolved avatar over whatever `gh` returned. `gh pr - // view` often yields an empty avatar or the default `u/0` placeholder for - // enterprise users; the resolved avatar is the authoritative one for that - // login. Fall back to the original only when the lookup found nothing. + // Why: prefer the GraphQL-resolved avatar — `gh pr view` returns empty/`u/0` placeholders for enterprise users; fall back to the original only when the lookup is empty. const avatarFor = (login: string): string | undefined => avatarByLogin.get(login.toLowerCase()) const resolvedAvatar = (login: string, existing?: string | null): string | undefined => avatarFor(login) || existing || undefined - // Callers coalesce a missing result to the field's "no avatar" sentinel ('' - // for GitHubAssignableUser, null for GitHubPRReviewSummary); GitHubUserAvatar - // treats both as falsy and falls back to the login URL then initials. + // Callers coalesce a missing result to each field's "no avatar" sentinel ('' or null); GitHubUserAvatar falls back to login URL then initials. const authorAvatarUrl = (item.author ? avatarFor(item.author) : undefined) || item.authorAvatarUrl return { ...item, @@ -1020,17 +981,8 @@ async function getMentionParticipants( connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise { - // Why: resolve mention authors AND the item's reviewers/assignees in ONE - // aliased GraphQL round-trip. Folding the display users in here means the - // caller can reuse this result to stamp avatars (see enrichItemDisplayAvatars) - // without a second, separately rate-limited lookup — which is what made GHE - // reviewer/assignee avatars resolve intermittently. See #8784. - // Why: order the always-visible display users (author, reviewers, assignees) - // before comment authors so the 40-login cap in getGitHubUsersByLogin never - // drops a reviewer/assignee avatar on a PR with many commenters. On a PR with - // 40+ distinct participants the trailing comment authors are dropped from this - // batch and lose only their @mention-suggestion avatar; rendered comment - // avatars are unaffected (they carry their own avatarUrl from the comment). + // Why: resolve mention authors + display users (reviewers/assignees) in one aliased trip so avatars reuse it without a second rate-limited lookup (#8784). + // Why: order display users before comment authors so the 40-login cap in getGitHubUsersByLogin can't drop a reviewer/assignee avatar. const visibleLogins = [ item.author ?? '', ...(item.reviewRequests ?? []).map((user) => user.login), @@ -1065,8 +1017,7 @@ async function getPRChecksForDetails( ...localGitOptionArgs(localGitOptions) ) } catch (err) { - // Why: checks are auxiliary PR metadata; a gh CLI edge case must not block - // the user from opening the PR review drawer and reading the files/comments. + // Why: checks are auxiliary — a gh failure must not block opening the PR drawer. console.warn('getWorkItemDetails PR checks failed:', err) return [] } @@ -1079,9 +1030,7 @@ export async function getWorkItemDetails( connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise { - // Why: getWorkItem already handles acquire/release. We call it first (outside - // our semaphore) so the known-cheap lookup doesn't compete with the richer - // detail fetches that follow. + // Why: getWorkItem self-manages acquire/release; call it outside our semaphore so the cheap lookup doesn't block detail fetches. const item: Omit | null = await getWorkItem( repoPath, number, @@ -1096,13 +1045,7 @@ export async function getWorkItemDetails( await acquire() try { if (item.type === 'issue') { - // Why: try the collapsed single-GraphQL path first — body, assignees, - // participants, and comments all return in one round-trip. On any - // failure (permissions, partial errors, non-GitHub remote), strictly - // fall back to the legacy REST+GraphQL fan-out so historical behavior - // is preserved. The GraphQL `participants` connection includes every - // commenter, so we skip the extra `getMentionParticipants` aliased - // user-hydration trip when the collapsed path succeeds. + // Why: one GraphQL round-trip returns everything (its participants include commenters, so getMentionParticipants is skipped); fall back to REST+GraphQL on any failure. const collapsed = await getIssueDetailsViaGraphQL( repoPath, item.number, @@ -1111,9 +1054,7 @@ export async function getWorkItemDetails( ) if (collapsed) { return { - // Include assigneeUsers: a non-participating assignee is absent from - // the participants connection, so without them item.assignees keeps a - // blank/placeholder avatar (common for GitHub Enterprise users). + // Include assigneeUsers: non-participating assignees are absent from participants and keep a blank avatar (GHE). item: enrichItemDisplayAvatars(item, [ ...collapsed.participants, ...collapsed.assigneeUsers @@ -1125,8 +1066,7 @@ export async function getWorkItemDetails( timelineItems: collapsed.timelineItems } } - // Why: fall back to body/comments and GraphQL participants in parallel; - // the mention-participant merge is a cheap local operation afterward. + // Fallback: fetch body/comments and participants in parallel. const [{ body, comments, assignees, timelineItems }, participants] = await Promise.all([ getIssueBodyAndComments(repoPath, item.number, connectionId, localGitOptions), getWorkItemParticipants(repoPath, item, connectionId, localGitOptions) @@ -1165,9 +1105,7 @@ export async function getWorkItemDetails( getWorkItemParticipants(repoPath, item, connectionId, localGitOptions) ]) - // Why: run the mention-author GraphQL lookup in parallel with the final - // checks fetch instead of serially — both depend only on data from the - // Promise.all above, so there's no ordering requirement between them. + // Why: mention-author lookup and checks are independent, so run them in parallel. const [mentionParticipants, checks] = await Promise.all([ getMentionParticipants(repoPath, item, comments, participants, connectionId, localGitOptions), getPRChecksForDetails(repoPath, item.number, shas?.headSha, connectionId, localGitOptions) @@ -1181,8 +1119,7 @@ export async function getWorkItemDetails( baseSha: shas?.baseSha, pullRequestId: viewedStates?.pullRequestId, checks, - // Why: distinguish a failed file fetch (null) from an empty PR so the - // Files tab surfaces a retry instead of "No files changed." + // Why: null (failed fetch) vs empty PR — Files tab shows retry, not "No files changed." files: files === null ? undefined : mergePRFileViewedStates(files, viewedStates), filesUnavailable: files === null, participants: mentionParticipants @@ -1192,10 +1129,7 @@ export async function getWorkItemDetails( } } -// Why: base64-decoded contents at specific commits are needed to feed Orca's -// Monaco-based DiffViewer (which expects original/modified text, not unified -// diff patches). Fetching via gh api --cache keeps rate-limit usage bounded -// during rapid file-expand clicks in the drawer. +// Why: Monaco DiffViewer needs original/modified text (not patches); --cache bounds rate-limit spend on rapid file-expands. async function fetchContentAtRef(args: { repoPath: string connectionId?: string | null @@ -1222,9 +1156,7 @@ async function fetchContentAtRef(args: { maxBuffer: GITHUB_RAW_CONTENT_MAX_BUFFER_BYTES } ) - // Raw content response: Electron's execFile returns string in utf-8. If the - // file is binary, the string will contain replacement characters — we treat - // anything with a NUL byte in the first 2KB as binary and skip rendering. + // Heuristic: a NUL byte in the first 2KB means binary (execFile decodes as lossy utf-8). const sample = stdout.slice(0, 2048) if (sample.includes('\u0000')) { return { content: '', isBinary: true } @@ -1265,9 +1197,7 @@ export async function getPRFileContents(args: { await acquire() try { - // Why: for added files there's no original content at the base ref; for - // removed files there's no modified content at the head ref. Skipping the - // redundant fetches keeps latency down and avoids spurious 404 warnings. + // Why: added files have no base-ref original, removed files no head-ref modified; skip those to avoid spurious 404s. const needsOriginal = args.status !== 'added' const needsModified = args.status !== 'removed' const originalRef = args.baseSha diff --git a/src/main/gitlab/client.ts b/src/main/gitlab/client.ts index eef57b3aaba..a179ea4be86 100644 --- a/src/main/gitlab/client.ts +++ b/src/main/gitlab/client.ts @@ -1,6 +1,4 @@ -/* eslint-disable max-lines -- Why: parallel to src/main/github/client.ts — -co-locating GitLab MR/issue/work-item operations keeps the concurrency -acquire/release pattern obvious across operations. */ +/* eslint-disable max-lines -- co-locates GitLab MR/issue/work-item operations sharing one acquire/release pattern. */ import type { ClassifiedError, GitLabAssignableUser, @@ -48,8 +46,7 @@ import { type HostedReviewExecutionOptions } from '../source-control/hosted-review-git-options' -// Why: glab REST API addresses projects by URL-encoded path. Centralized -// so call sites don't forget the slash escapes for nested groups. +// Why: glab REST addresses projects by URL-encoded path; escapes slashes for nested groups. function encodedProject(projectPath: string): string { return encodeURIComponent(projectPath) } @@ -67,9 +64,8 @@ function hostedReviewLocalGitOptionArgs( } /** - * Get the authenticated GitLab viewer. Mirrors getAuthenticatedViewer - * from the GitHub client — returns null when glab is unavailable, the - * user is unauthenticated, or the lookup fails. + * Get the authenticated GitLab viewer. + * Returns null when glab is unavailable, unauthenticated, or the lookup fails. */ export async function getAuthenticatedViewer(): Promise { await acquire() @@ -202,8 +198,7 @@ function rememberGitLabRateLimitSnapshot( snapshot: GitLabRateLimitSnapshot ): void { pruneGitLabRateLimitCache() - // Why: self-managed GitLab hostnames come from repo config; keep this - // process cache bounded even across many transient hosts. + // Why: self-managed hostnames come from repo config; keep this cache bounded across many transient hosts. gitLabRateLimitCache.delete(cacheKey) gitLabRateLimitCache.set(cacheKey, snapshot) pruneGitLabRateLimitCache() @@ -223,9 +218,7 @@ export async function getRateLimit(options?: { await acquire() try { - // Why: GitLab.com and self-managed GitLab instances expose REST budget - // headers inconsistently. Query a cheap authenticated endpoint and report - // the headers when present; a null bucket means this host omitted them. + // Why: GitLab exposes REST budget headers inconsistently; a null bucket means this host omitted them. const args = host ? ['--hostname', host, 'user'] : ['user'] const { headers } = await glabApiWithHeaders(args) const snapshot = parseGitLabRateLimitSnapshot(headers, host) @@ -239,10 +232,7 @@ export async function getRateLimit(options?: { } } -/** - * Resolve a project's full GitLab project ref (host + path). Mirrors - * github/getRepoSlug. Returns null for non-GitLab remotes. - */ +/** Resolve a project's full GitLab project ref (host + path); null for non-GitLab remotes. */ export async function getProjectSlug( repoPath: string, connectionId?: string | null, @@ -254,9 +244,8 @@ export async function getProjectSlug( } /** - * Fetch a single merge request with the pipeline status rolled up. - * Returns null when the MR doesn't exist or glab fails — callers - * decide whether to surface "not found" UI. + * Fetch a single merge request with pipeline status rolled up. + * Returns null when the MR doesn't exist or glab fails. */ export async function getMergeRequest( repoPath: string, @@ -285,9 +274,7 @@ export async function getMergeRequest( head_pipeline?: { status?: string } | null pipeline?: { status?: string } | null } - // Why: GitLab's MR detail surfaces the head pipeline directly. - // Older instances expose `pipeline` instead of `head_pipeline` — try - // both. If neither is set the rollup falls back to neutral. + // Why: older GitLab instances expose `pipeline` instead of `head_pipeline`; try both. const pipelineStatus = derivePipelineStatus(data.head_pipeline ?? data.pipeline ?? null) return mapMRInfo(data, pipelineStatus) } catch { @@ -298,11 +285,8 @@ export async function getMergeRequest( } /** - * Find the merge request whose source branch matches the given branch - * name. Mirrors github/getPRForBranch — returns the most recently - * updated MR for the branch, or null when none exists. The branch is the - * local checkout's current ref (Orca strips refs/heads/ prefix upstream - * so we don't need to here). + * Find the merge request whose source branch matches the given name. + * Returns the most recently updated MR for the branch, or null when none exists. */ export async function getMergeRequestForBranch( repoPath: string, @@ -310,9 +294,7 @@ export async function getMergeRequestForBranch( linkedMRIid?: number | null, connectionId?: string | null, options: HostedReviewExecutionOptions = {}, - // Why: the existing-review lookup behind Create must distinguish a real glab - // transport/auth failure from an accepted "no MR". When true, a failed lookup - // throws instead of collapsing to null so callers never report false not_found. + // Why: when true, a failed lookup throws instead of returning null, so callers never report a false not_found. throwOnFailure = false ): Promise { const branchName = branch.replace(/^refs\/heads\//, '') @@ -343,8 +325,7 @@ export async function getMergeRequestForBranch( })[] if (Array.isArray(data) && data.length > 0) { const raw = data[0] - // Why: older GitLab list payloads expose `pipeline` instead of - // `head_pipeline`, matching the detail endpoint compatibility path. + // Why: older GitLab list payloads expose `pipeline` instead of `head_pipeline`. const pipelineStatus = derivePipelineStatus(raw.head_pipeline ?? raw.pipeline ?? null) return mapMRInfo(raw, pipelineStatus) } @@ -352,9 +333,7 @@ export async function getMergeRequestForBranch( if (typeof linkedMRIid !== 'number') { return null } - // Why: create-from-MR worktrees may use a fresh local branch name rather - // than the MR source branch. Fall back to the durable linked iid so the - // core review status still follows the workspace. + // Why: create-from-MR worktrees may rename the branch; fall back to the durable linked iid. const { stdout } = await glabExecFileAsync( [ 'api', @@ -380,9 +359,7 @@ export async function getMergeRequestForBranch( } /** - * Existing-review lookup that surfaces glab transport/auth failures instead of - * collapsing them to null, so a failed lookup becomes - * `reviewLookupOutcome: 'unavailable'` rather than a false "No merge request found". + * Like getMergeRequestForBranch but throws glab failures instead of returning null, so callers report 'unavailable' not a false "not found". */ export function getMergeRequestForBranchOrThrow( repoPath: string, @@ -407,10 +384,7 @@ function mrListStateFlags(state: MRListState): string[] { } } -/** - * List merge requests for a project. Uses glab CLI pagination because - * it handles self-hosted auth and project selection consistently. - */ +/** List merge requests for a project via glab CLI, which handles self-hosted auth and project selection. */ export async function listMergeRequests( repoPath: string, state: MRListState = 'opened', @@ -422,10 +396,7 @@ export async function listMergeRequests( localGitOptions: LocalGitExecOptions = {} ): Promise { const knownHosts = await getGlabKnownHosts(connectionId, localGitOptions) - // Why: MRs sit on `origin` in the fork model (the user's fork is where - // they push branches and submit MRs). Mirror github's `getOwnerRepo` - // call site by going through the upstream/origin preference resolver - // so cross-fork workflows reuse the same plumbing. + // Why: MRs live on the user's fork (origin); route through the preference resolver so fork workflows share plumbing. const { source: projectRef } = await resolveIssueSource( repoPath, preference, @@ -435,8 +406,7 @@ export async function listMergeRequests( ) if (!projectRef) { if (connectionId) { - // Why: SSH-backed repos have no local cwd for glab to infer from. - // Running cwd-less could resolve an unrelated local project instead. + // Why: SSH-backed repos have no local cwd; a cwd-less glab could resolve an unrelated project. return { items: [], page, @@ -449,14 +419,9 @@ export async function listMergeRequests( } } } - // Why: fallback — let glab infer project from cwd, same as listIssues. - // Used when the repo's remote host is not in getGlabKnownHosts(connectionId) - // (e.g. a fresh self-hosted instance), but glab itself can still - // resolve it from the local git config. + // Why: fallback — let glab infer the project from cwd when the host isn't in getGlabKnownHosts. const stateFlag = mrListStateFlags(state) - // Why: the cwd-inferred fallback must honor the same search the API path - // does, otherwise typing a query against a self-hosted / unresolved-projectRef - // repo silently returns the unfiltered list (the original #6263 symptom). + // Why: apply the same search as the API path, else queries are silently ignored on cwd-inferred repos (#6263). const searchFlag = query?.trim() ? ['--search', query.trim()] : [] await acquire() try { @@ -484,8 +449,7 @@ export async function listMergeRequests( items: data.map((d) => mapMRToWorkItem(d, 'unknown')), page, perPage, - // Why: the CLI doesn't return x-total headers, so totals are - // approximate. For the Tasks UI this is acceptable. + // Why: the CLI omits x-total headers, so totals are approximate. totalCount: data.length, totalPages: data.length < perPage ? page : page + 1 } @@ -503,8 +467,7 @@ export async function listMergeRequests( release() } } - // Why: 'all' is exposed as the picker filter but GitLab's API expects - // no state param to mean "any state". Drop the param when 'all'. + // Why: GitLab's API uses an absent state param to mean "any"; drop it for 'all'. const stateParam = state === 'all' ? '' : `&state=${state}` const searchParam = query?.trim() ? `&search=${encodeURIComponent(query.trim())}` : '' const path = @@ -524,8 +487,7 @@ export async function listMergeRequests( page, perPage, totalCount: parseHeaderInt(headers['x-total'], 0), - // Why: when 'all' state is requested or the per_page is large, - // GitLab may not include x-total-pages; fall back to ceil(total/perPage). + // Why: GitLab may omit x-total-pages for 'all' or large per_page; fall back to ceil(total/perPage). totalPages: parseHeaderInt(headers['x-total-pages'], 0) || Math.max(1, Math.ceil(parseHeaderInt(headers['x-total'], 0) / perPage)) @@ -554,10 +516,8 @@ function parseHeaderInt(value: string | undefined, fallback: number): number { } /** - * Fetch a work item (MR or issue) given an explicit project ref + - * iid + type. Mirrors github/getWorkItemByOwnerRepo — used by the - * paste-URL flow in the picker where the URL determines the project - * directly rather than going through the local repo's remotes. + * Fetch a work item (MR or issue) by explicit project ref + iid + type. + * Used by the paste-URL flow, where the URL determines the project directly. */ export async function getWorkItemByProjectRef( repoPath: string, @@ -573,8 +533,7 @@ export async function getWorkItemByProjectRef( const { stdout } = await glabExecFileAsync( [ 'api', - // Why: pasted GitLab URLs carry an explicit host; preserve it even for - // local/runtime-local repos so cwd remotes cannot redirect the lookup. + // Why: preserve the pasted URL's explicit host so cwd remotes can't redirect the lookup. ...(projectRef.host ? ['--hostname', projectRef.host] : []), `projects/${encodedProject(projectRef.path)}/${resource}/${iid}` ], @@ -593,9 +552,7 @@ export async function getWorkItemByProjectRef( } function mrStateToIssueState(state: MRListState): IssueListState | null { - // Why: GitLab issues don't have a 'merged' state. When the user is - // filtering MRs to merged, return null so listWorkItems can skip the - // issues fetch entirely instead of mis-mapping to opened/closed. + // Why: issues have no 'merged' state; return null so the issues fetch is skipped, not mis-mapped. switch (state) { case 'opened': return 'opened' @@ -640,16 +597,8 @@ export async function listWorkItems( } } } - // Why: fan out the two read calls so the response time is the slower - // of the two, not their sum. Errors classify per-side; an MR-side - // failure with a successful issues fetch still surfaces issues with - // an error envelope. - // - // Why we don't go through `listIssues` here: that function returns - // IssueInfo, which deliberately strips the raw glab fields (notably - // `updated_at`). The combined sort needs updatedAt, so we read the - // raw issues API directly and run mapIssueToWorkItem against the - // raw payload instead. + // Why: fan out both reads so latency is the slower of the two, not the sum. + // Why read the raw issues API (not listIssues): IssueInfo strips updated_at, which the combined sort needs. const [mrs, issues] = await Promise.all([ listMergeRequests( repoPath, @@ -680,20 +629,13 @@ export async function listWorkItems( const merged = [...mrs.items, ...issues.items].sort((a, b) => (b.updatedAt ?? '').localeCompare(a.updatedAt ?? '') ) - // Why: combine error envelopes — the renderer's banner cares about - // any failed fetch, not which one. MR-side error wins because it's - // strictly more informative than an issues-side error in most - // permission scenarios (issues can be disabled per project). + // Why: MR-side error wins — issues can be disabled per project, making its error less informative. const error: ClassifiedError | undefined = mrs.error ?? issues.error return { items: merged, page, perPage, - // Why: approximate totals — an exact combined-pagination total would - // require a server-side ordering primitive across two distinct - // resources, which the GitLab API doesn't offer. MR total is the - // right direction; the UI's "Page X of Y" reads as a hint, not a - // strict count. + // Why: approximate totals — GitLab can't paginate across MRs+issues as one set, so MR totals stand in. totalCount: mrs.totalCount, totalPages: mrs.totalPages, ...(error ? { error } : {}) @@ -739,14 +681,7 @@ export async function fetchIssuesAsWorkItems( /** * List the authenticated user's GitLab todos (gitlab.com/dashboard/todos). - * Cross-project — `glab api todos` is user-scoped so the cwd doesn't - * affect the result; callers may pass any registered repo path so the - * IPC handler's path-validation guard has something to check. - * - * Why: GitLab's todos surface is the closest GitLab-native analogue of - * GitHub's notifications/inbox. Surfacing it in Orca lets users start - * work directly from a mention/assignment without going to gitlab.com - * first. + * User-scoped, so cwd is irrelevant; repoPath only satisfies the IPC handler's path-validation guard. */ export async function listTodos( repoPath: string, @@ -764,9 +699,7 @@ export async function listTodos( } await acquire() try { - // Why: per_page=50 keeps this user-scoped cross-project view cheap. The UI - // shows the highest-priority todos first, so avoid walking every pending - // todo page from large GitLab accounts. + // Why: per_page=50 keeps this cross-project view cheap; the UI only shows top-priority todos. const { stdout } = await glabExecFileAsync( [ 'api', @@ -805,9 +738,7 @@ export async function listTodos( state: t.state === 'done' ? 'done' : 'pending' })) } catch { - // Why: silent empty-list on auth/network failures matches the rest - // of the read-side surface (`listLabels`, `listAssignableUsers`). - // The caller's banner / loading-state UI signals connectivity issues. + // Why: silent empty-list on auth/network failure matches the read-side surface; caller UI signals connectivity. return [] } finally { release() @@ -815,9 +746,6 @@ export async function listTodos( } // ── MR mutations ────────────────────────────────────────────────── -// Why: mirror the GitHub-side actions (mergePR, updatePRTitle, close -// via gh issue close, etc.) for the GitLab dialog footer. All take a -// repoPath + iid and resolve the project ref via the existing helper. async function withProjectRef( repoPath: string, @@ -875,9 +803,7 @@ export async function closeMR( return { ok: true } } catch (err) { const msg = err instanceof Error ? err.message : String(err) - // Why: glab returns a non-zero exit when the MR is already - // closed — treat that as success since the desired state is - // reached. + // Why: glab exits non-zero when the MR is already closed — treat as success. if (msg.toLowerCase().includes('already')) { return { ok: true } } @@ -951,9 +877,7 @@ export async function mergeMR( async (projectRef, repoFlag) => { await acquire() try { - // Why: glab mr merge accepts --squash and --rebase flags; - // omitting both does a regular merge commit. Map our union - // to the right glab flag. + // Why: omitting both --squash and --rebase yields a regular merge commit. const methodFlag = method === 'squash' ? ['--squash'] : method === 'rebase' ? ['--rebase'] : [] await glabExecFileAsync( @@ -1148,8 +1072,7 @@ export async function resolveMRDiscussion( } await acquire() try { - // Why: GitLab resolves/reopens the whole discussion thread, not a single - // note; this mirrors GitHub's thread-level resolve mutation. + // Why: GitLab resolves/reopens the whole discussion thread, not a single note. await glabExecFileAsync( [ 'api', @@ -1429,7 +1352,5 @@ export { updateIssue } from './issues' -// Why: surface the upstream-aware project-ref helper so non-issue call -// sites that need the resolved project (e.g. the paste-URL UI) don't -// have to import from gl-utils directly. +// Re-exported so paste-URL call sites don't import getProjectRefForRemote from gl-utils directly. export { getProjectRefForRemote } diff --git a/src/main/opencode/hook-service.test.ts b/src/main/opencode/hook-service.test.ts index 2ad00cf5baa..f3956519ee7 100644 --- a/src/main/opencode/hook-service.test.ts +++ b/src/main/opencode/hook-service.test.ts @@ -46,10 +46,7 @@ describe('OpenCode hook plugin source', () => { }) it('resolves hook coords from the endpoint file before falling back to process.env', () => { - // Why: a long-running OpenCode session was fork()ed with the prior Orca's - // PORT/TOKEN frozen into process.env. The plugin must prefer the on-disk - // endpoint file (rewritten on every Orca start()) over env, otherwise it - // keeps posting to a dead port after an Orca restart. + // Why: a forked session freezes the prior Orca's PORT/TOKEN in env; prefer the on-disk endpoint file or it posts to a dead port after restart. const source = _internals.getOpenCodePluginSource() expect(source).toContain('function readEndpointFile()') @@ -71,12 +68,7 @@ describe('OpenCode hook plugin source', () => { }) it('caches the parsed endpoint file on mtime+size+inode to skip re-reads per post', () => { - // Why: message.part.updated fires many times per second during a streaming - // assistant reply. Each post() calls resolveHookCoords() which reads the - // endpoint file — without the cache we'd readFileSync + parse on every - // streamed Part. The cache key combines mtime + size + inode so renameSync - // (writeEndpointFile's atomic swap) invalidates the cache via the ino - // change even when mtime resolution is coarse and size happens to match. + // Why: cache the parse to avoid a read per streamed Part; inode in the key lets renameSync invalidate it even when mtime/size collide. const source = _internals.getOpenCodePluginSource() expect(source).toContain('let cachedEndpointKey = "";') @@ -91,14 +83,7 @@ describe('OpenCode hook plugin source', () => { }) it('forwards question.asked as AskUserQuestion so the pane flips to waiting', () => { - // Why: OpenCode exposes two separate plugin events for human-in-the-loop - // moments — `permission.asked` (blocks on tool approval) and - // `question.asked` (the agent called an ask-the-user tool). The plugin - // must forward both so the server-side normalizer can map each to - // `waiting` and render the red indicator. Dropping `question.asked` - // leaves the pane stuck in `working` while the agent is actually idle, - // waiting on a human reply — exactly the bug other OpenCode integrations - // also handle. + // Why: forward question.asked too (not just permission.asked), else the pane stays "working" while the agent idles on a human reply. const source = _internals.getOpenCodePluginSource() expect(source).toContain('if (event.type === "question.asked")') @@ -118,10 +103,7 @@ describe('OpenCode hook plugin source', () => { }) it('guards endpoint-file parse warnings with a process-lifetime latch', () => { - // Why: ENOENT is the normal pre-install case and must stay silent, but a - // malformed/unreadable file (EACCES, EIO, parse error) would otherwise - // spam stderr once per hook post. The latch keeps the warning to once per - // OpenCode process — mirrors server.ts's warnedVersions/warnedEnvs intent. + // Why: ENOENT is normal pre-install (stay silent), but a bad file would spam stderr per post; latch warns once per process. const source = _internals.getOpenCodePluginSource() expect(source).toContain('let warnedBadEndpoint = false;') @@ -132,11 +114,7 @@ describe('OpenCode hook plugin source', () => { describe('OpenCode id safety guard', () => { it('accepts the daemon-path sessionId shape (worktreeId@@uuid with ::/...)', () => { - // Why: after the daemon-parity refactor (#1148) pty.ts mints sessionIds - // like `@@` where worktreeId contains "::" and a - // filesystem path. The previous strict regex rejected every real id and - // silently dropped OPENCODE_CONFIG_DIR. Lock in that such ids are now - // accepted so the plugin dir is actually written. + // Why: after #1148 pty.ts mints sessionIds like @@ with "::" and a path; the old strict regex rejected every real id. const daemonSessionId = '50c010a2-bc8e-4eb1-8847-5812133ad6df::/Users/thebr/ghostx/workspaces/noqa/autoheal@@a1b2c3d4' expect(isUsableId(daemonSessionId)).toBe(true) @@ -152,8 +130,7 @@ describe('OpenCode id safety guard', () => { }) it('rejects non-string runtime values even though the type says string', () => { - // Why: the typeof guard is defense-in-depth for any-typed callers; - // without a test, a future refactor could delete the guard silently. + // Why: the typeof guard is defense-in-depth for any-typed callers; a test keeps a refactor from silently deleting it. expect(isUsableId(undefined as unknown as string)).toBe(false) expect(isUsableId(null as unknown as string)).toBe(false) expect(isUsableId(42 as unknown as string)).toBe(false) @@ -176,11 +153,7 @@ describe('OpenCode id safety guard', () => { }) describe('OpenCodeHookService buildPtyEnv / clearPty round-trip', () => { - // Why: the primitives above only prove the helpers work in isolation. This - // suite exercises the public surface against a real filesystem so a future - // regression — e.g. re-tightening the id guard or desyncing the path used by - // writeLegacyPluginConfig vs clearPty — fails loudly. Before #1148 the service - // silently returned {} for daemon-shaped ids; these tests lock that in. + // Why: exercise the public surface against a real filesystem so regressions fail loudly (before #1148 daemon-shaped ids silently returned {}). const daemonSessionId = '50c010a2-bc8e-4eb1-8847-5812133ad6df::/Users/thebr/ghostx/workspaces/noqa/autoheal@@a1b2c3d4' const plainUuidId = 'c0ffee00-0000-4000-8000-000000000000' @@ -244,9 +217,7 @@ describe('OpenCodeHookService buildPtyEnv / clearPty round-trip', () => { }) it('buildPtyEnv preserves a user-set OPENCODE_CONFIG_DIR when the id is unusable', () => { - // Why: defense-in-depth — if the bounds guard rejects the id, we still - // must not blow away the user's own OPENCODE_CONFIG_DIR. The status - // plugin is forfeited, but the user's plugins/auth/keymap keep loading. + // Why: even when the id is rejected, don't blow away the user's own OPENCODE_CONFIG_DIR. const service = new OpenCodeHookService() const userDir = mkdtempSync(join(tmpdir(), 'orca-opencode-userdir-')) try { @@ -271,11 +242,7 @@ describe('OpenCodeHookService buildPtyEnv / clearPty round-trip', () => { }) describe('OpenCodeHookService overlay mode (user OPENCODE_CONFIG_DIR set)', () => { - // Why: locks in docs/opencode-config-dir-collision.md — when the user has - // their own OPENCODE_CONFIG_DIR (e.g. a company-wide opencode config repo), - // Orca must mirror it into a source-scoped overlay rather than `delete` its - // own injection or overwrite the user's value. The user's auth/models/keymap - // and Orca's status plugin both load via a single OPENCODE_CONFIG_DIR. + // Why: with a user-set OPENCODE_CONFIG_DIR, mirror it into an overlay rather than overwrite it (docs/opencode-config-dir-collision.md). const ptyId = 'overlay-pty-1' let userDataDir: string let userConfigDir: string @@ -350,18 +317,14 @@ describe('OpenCodeHookService overlay mode (user OPENCODE_CONFIG_DIR set)', () = it.skipIf(process.platform === 'win32')( 'mirrors top-level entries via symlinks so plugins/ is a real directory', () => { - // Why: only the plugins/ subtree needs entry-by-entry mirroring so Orca - // can drop a sibling file alongside the user's plugins. Other top-level - // entries (auth.json, opencode.json) are mirrored as a single symlink so - // user edits propagate live on POSIX. + // Why: only plugins/ needs per-entry mirroring (Orca drops a sibling); other entries are single symlinks so user edits propagate live. const service = new OpenCodeHookService() const env = service.buildPtyEnv(ptyId, userConfigDir) const overlay = env.OPENCODE_CONFIG_DIR! expect(lstatSync(join(overlay, 'opencode.json')).isSymbolicLink()).toBe(true) expect(lstatSync(join(overlay, 'auth.json')).isSymbolicLink()).toBe(true) - // plugins/ must be a real directory in the overlay so Orca can write - // its sibling status plugin into it. + // plugins/ must be a real dir in the overlay so Orca can drop its sibling plugin. expect(lstatSync(join(overlay, 'plugins')).isDirectory()).toBe(true) expect(lstatSync(join(overlay, 'plugins')).isSymbolicLink()).toBe(false) // user-plugin.js inside plugins/ is mirrored entry-by-entry. @@ -370,11 +333,7 @@ describe('OpenCodeHookService overlay mode (user OPENCODE_CONFIG_DIR set)', () = ) it("does not overwrite a user plugin file with the same filename as Orca's plugin", () => { - // Why: the failure mode this guards against — a user-owned plugin file - // happens to be named orca-opencode-status.js. Without the per-entry - // skip in mirrorUserConfig, the file would be linked into the overlay - // and Orca's writeFileSync would write through the symlink, destroying - // the user's content on their real filesystem. + // Why: a user plugin named orca-opencode-status.js must not be symlinked into the overlay, or writeFileSync would clobber it. const userOrcaSentinel = 'USER OWNED ORCA-NAMED PLUGIN — DO NOT CLOBBER' writeFileSync(join(userConfigDir, 'plugins', 'orca-opencode-status.js'), userOrcaSentinel) @@ -399,15 +358,12 @@ describe('OpenCodeHookService overlay mode (user OPENCODE_CONFIG_DIR set)', () = it.skipIf(process.platform === 'win32')( 'does not write through a symlinked plugins/ directory into the user filesystem', () => { - // Why: if plugins/ is a symlink (common dotfiles pattern), writing Orca's - // status plugin through it would land in the user's real filesystem — - // exactly the failure mode docs/opencode-config-dir-collision.md rejects. + // Why: writing Orca's plugin through a symlinked plugins/ would leak into the user's fs (docs/opencode-config-dir-collision.md). const realPluginsDir = mkdtempSync(join(tmpdir(), 'orca-real-plugins-')) try { writeFileSync(join(realPluginsDir, 'real-plugin.js'), 'REAL USER PLUGIN') - // Replace the userConfigDir/plugins dir created by beforeEach with a - // symlink pointing at the external "real" plugins dir. + // Swap plugins/ for a symlink to the external "real" dir (dotfiles pattern). rmSync(join(userConfigDir, 'plugins'), { recursive: true, force: true }) symlinkSync(realPluginsDir, join(userConfigDir, 'plugins'), 'dir') @@ -416,15 +372,13 @@ describe('OpenCodeHookService overlay mode (user OPENCODE_CONFIG_DIR set)', () = // The user's real filesystem must NOT receive Orca's status plugin. expect(existsSync(join(realPluginsDir, 'orca-opencode-status.js'))).toBe(false) - // Overlay's plugins/ must be a real directory, not a symlink that - // would write through to the user's filesystem. + // Overlay's plugins/ must be a real dir, else writes leak into the user's filesystem. expect(lstatSync(join(env.OPENCODE_CONFIG_DIR!, 'plugins')).isSymbolicLink()).toBe(false) // Orca's status plugin lands in the overlay only. expect( existsSync(join(env.OPENCODE_CONFIG_DIR!, 'plugins', 'orca-opencode-status.js')) ).toBe(true) - // The user's sentinel plugin is reachable through the overlay (mirrored - // entry-by-entry after resolving the symlink target). + // User's real plugin is reachable via the overlay (plugins mirrored entry-by-entry after resolving the symlink target). expect( readFileSync(join(env.OPENCODE_CONFIG_DIR!, 'plugins', 'real-plugin.js'), 'utf8') ).toBe('REAL USER PLUGIN') @@ -435,10 +389,7 @@ describe('OpenCodeHookService overlay mode (user OPENCODE_CONFIG_DIR set)', () = ) it("preserves the user's OPENCODE_CONFIG_DIR when the path does not exist", () => { - // Why: typoed user path — overriding it with an Orca-owned dir would let - // Orca's status plugin "succeed" while silently hiding the user's typo. - // The design rejects that: leave the user's value alone and let OpenCode - // surface the typo on its own. + // Why: leave a nonexistent user path alone so OpenCode surfaces the typo instead of Orca silently hiding it. const service = new OpenCodeHookService() const missingPath = join(tmpdir(), `orca-opencode-nope-${Date.now()}`) expect(existsSync(missingPath)).toBe(false) @@ -456,11 +407,7 @@ describe('OpenCodeHookService overlay mode (user OPENCODE_CONFIG_DIR set)', () = }) it("preserves the user's OPENCODE_CONFIG_DIR when the mirror step fails", async () => { - // Why: mock the shared mirrorEntry helper to throw on the first symlink - // (e.g. Windows without developer mode → EPERM). The hook service must - // catch and fall back to { OPENCODE_CONFIG_DIR: existingConfigDir } — - // the user's plugins/auth/models keep loading; only Orca's status plugin - // is forfeited. + // Why: if mirroring fails (e.g. Windows EPERM), fall back to the user's OPENCODE_CONFIG_DIR so their config still loads. const overlayMirror = await import('../pty/overlay-mirror') const mirrorSpy = vi.spyOn(overlayMirror, 'mirrorEntry').mockImplementation(() => { throw new Error('simulated EPERM on symlink') @@ -469,8 +416,7 @@ describe('OpenCodeHookService overlay mode (user OPENCODE_CONFIG_DIR set)', () = const service = new OpenCodeHookService() const env = service.buildPtyEnv(ptyId, userConfigDir) expect(env).toEqual({ OPENCODE_CONFIG_DIR: userConfigDir }) - // Overlay cleanup is deliberately not on the fallback path; it may hold - // OpenCode-created runtime files on Windows. + // Overlay cleanup is deliberately off the fallback path; it may hold OpenCode runtime files on Windows. const overlayDir = join( userDataDir, 'opencode-config-overlays', @@ -486,8 +432,7 @@ describe('OpenCodeHookService overlay mode (user OPENCODE_CONFIG_DIR set)', () = it.skipIf(process.platform === 'win32')( 'clearPty leaves the source overlay off the teardown hot path', () => { - // Why: OpenCode may populate OPENCODE_CONFIG_DIR with runtime files. - // PTY teardown must not recursively delete that tree on Electron main. + // Why: OpenCode may fill the overlay with runtime files; PTY teardown must not recursively delete it on Electron main. const service = new OpenCodeHookService() service.buildPtyEnv(ptyId, userConfigDir) @@ -509,10 +454,7 @@ describe('OpenCodeHookService overlay mode (user OPENCODE_CONFIG_DIR set)', () = ) it('rebuilding the overlay for the same ptyId does not corrupt the user dir', () => { - // Mirrors the daemon cold-restore code path that calls buildPtyEnv with - // the same sessionId across restarts. Each rebuild must refresh the prior - // overlay safely (no symlink-walk into user data) and keep both user files - // and Orca's plugin reachable. + // Mirrors daemon cold-restore: rebuilding for the same sessionId must refresh the overlay without symlink-walking into user data. const service = new OpenCodeHookService() service.buildPtyEnv(ptyId, userConfigDir) service.buildPtyEnv(ptyId, userConfigDir) diff --git a/src/main/opencode/hook-service.ts b/src/main/opencode/hook-service.ts index 4195e4a0732..c2baa488dc9 100644 --- a/src/main/opencode/hook-service.ts +++ b/src/main/opencode/hook-service.ts @@ -1,7 +1,4 @@ -/* eslint-disable max-lines -- Why: this file contains a multi-line inline - JS plugin source emitted into OpenCode's plugins directory as a single - file; splitting the plugin source across TS modules would obscure the - runtime artifact and scatter tightly coupled string-template logic. */ +/* eslint-disable max-lines -- Why: holds an inline JS plugin source emitted as one file; splitting across TS modules would scatter tightly coupled string-template logic. */ import { app } from 'electron' import { join } from 'node:path' import { @@ -28,33 +25,13 @@ type OpenCodeOverlayManifest = { pluginEntries: string[] } -// Why: the id passed in by pty.ts's daemon path is a sessionId shaped like -// "@@" where worktreeId itself contains "::" and a -// filesystem path (slashes, colons). Earlier the id was a simple numeric -// counter, so rejecting anything with "/" or ":" was a safe guard against -// path traversal. After the daemon-parity refactor (#1148) the sessionId -// shape changed, and the old regex silently rejected every legitimate id, -// leaving OPENCODE_CONFIG_DIR unset and the plugin never loading. -// -// Keep an input-bounds guard (non-empty, bounded length) for defense in -// depth, and derive the on-disk directory name via hash so any caller's id — -// including ones containing path separators — produces a short, stable, -// filesystem-safe name. Hashing also eliminates path-traversal risk at the -// source: the directory name is always 32 hex chars, never a prefix/suffix -// of the caller's input. -// Why: 1024 is a generous sanity cap — daemon-shaped ids embed a worktree -// filesystem path plus "@@", and this bound prevents pathological inputs -// from burning CPU in the SHA-256 step. Since the id is hashed anyway, 1024 -// is decoupled from PATH_MAX. +// Why: bounds-check only — the id is a daemon sessionId with path separators, hashed downstream to a filesystem-safe name (an old regex rejecting "/"/":" broke every such id, #1148); 1024 just caps pathological hash input. function isUsableId(id: string): boolean { return typeof id === 'string' && id.length > 0 && id.length <= 1024 } function toSafeDirName(id: string): string { - // Why: SHA-256 truncated to 32 hex chars (128 bits) is ample for a - // per-session directory name — collisions require ~2^64 concurrent sessions - // to become likely, far beyond any real workload. Hex keeps the name - // portable across all filesystems (no base64 padding, no `/`). + // Why: 32 hex chars (128 bits) makes collisions negligible and stays filesystem-portable (no base64 padding or `/`). return createHash('sha256').update(id).digest('hex').slice(0, 32) } @@ -63,13 +40,7 @@ export function getOpenCodePluginSource(): string { } export function getOpenCodeFamilyPluginSource(hookPathname: string): string { - // Why: the plugin runs inside the OpenCode Node process and POSTs to the - // unified agent-hooks server shared with Claude/Codex/Gemini. It reads the - // same ORCA_PANE_KEY / ORCA_TAB_ID / ORCA_WORKTREE_ID / ORCA_AGENT_HOOK_* - // env vars that Orca injects into every PTY, so OpenCode panes flow into - // agentStatusByPaneKey via the same IPC path as every other agent. Event - // mapping is done plugin-side (SessionBusy / SessionIdle / PermissionRequest) - // so the server-side normalizer can keep its one-event-per-case switch shape. + // Why: plugin runs in OpenCode's Node process and POSTs Orca's ORCA_* PTY env to the shared agent-hooks server; events are mapped plugin-side to fit the server's per-case switch. return [ '// Why: process-lifetime guard so a recurring parse error on a malformed', "// endpoint file does not spam OpenCode's stderr once per hook post.", @@ -404,35 +375,20 @@ export function getOpenCodeFamilyPluginSource(hookPathname: string): string { ].join('\n') } -// Why: OpenCode hooks used to run their own loopback HTTP server + IPC -// channel (pty:opencode-status). That pathway produced a synthetic terminal -// title but never entered agentStatusByPaneKey, so the unified dashboard -// never saw OpenCode sessions. The service now only installs the plugin -// file into OPENCODE_CONFIG_DIR — the plugin POSTs directly to the shared -// agent-hooks server (/hook/opencode), so OpenCode rides the same status -// pipeline as Claude/Codex/Gemini. +// Why: installs the plugin into OPENCODE_CONFIG_DIR so it POSTs to the shared agent-hooks server, unifying OpenCode status with Claude/Codex/Gemini (the old loopback-IPC path never reached agentStatusByPaneKey). export class OpenCodeHookService { clearPty(_ptyId: string): void { - // Why: OpenCode can materialize thousands of plugin runtime files under - // OPENCODE_CONFIG_DIR. This teardown runs on Electron's main process hot - // path, so recursive deletion here can freeze the whole app on Windows - // while Node, antivirus, or indexing still holds file handles. - // - // Current builds use app/source-scoped config dirs, not PTY-scoped dirs, - // so there is no live PTY-owned OpenCode filesystem state to remove. + // Why: no-op — config dirs are app/source-scoped now, and recursive delete on the main-process hot path could freeze on Windows. } buildPtyEnv(ptyId: string, existingConfigDir?: string | undefined): Record { if (!isUsableId(ptyId)) { - // Why: defense-in-depth. If the id fails the bounds guard, a user-set - // OPENCODE_CONFIG_DIR should still be preserved so OpenCode loads the - // user's own config — only the Orca status plugin is forfeited. + // Why: on a bad id, still preserve a user-set OPENCODE_CONFIG_DIR; only the Orca status plugin is forfeited. return existingConfigDir ? { OPENCODE_CONFIG_DIR: existingConfigDir } : {} } if (!existingConfigDir) { - // Why: OpenCode may install plugin dependencies under this root. Sharing - // it prevents per-terminal node_modules churn and teardown freezes. + // Why: share one config root so OpenCode's plugin deps don't churn node_modules per terminal. const configDir = this.writeSharedPluginConfig() if (!configDir) { return {} @@ -440,10 +396,7 @@ export class OpenCodeHookService { return { OPENCODE_CONFIG_DIR: configDir } } - // Why: do NOT `mkdir -p` the user's typoed path — overriding it with an - // Orca-owned dir is the exact config-replacement failure mode documented in - // docs/opencode-config-dir-collision.md. Let OpenCode surface the typo on - // its own; we only forfeit our status plugin for this pane. + // Why: don't mkdir the user's (possibly typoed) path — that's the config-replacement failure mode in docs/opencode-config-dir-collision.md; let OpenCode surface it. if (!existsSync(existingConfigDir)) { return { OPENCODE_CONFIG_DIR: existingConfigDir } } @@ -455,11 +408,7 @@ export class OpenCodeHookService { this.mirrorUserConfig(existingConfigDir, overlayDir) this.writePluginIntoOverlay(overlayDir) } catch { - // Why: overlay creation is best-effort. Symlink-creation can fail on - // Windows without developer mode (EPERM), userData can be read-only on - // locked-down corporate machines, etc. In every case, preserve the - // user's OPENCODE_CONFIG_DIR — a missing status plugin is a vastly - // smaller harm than silently dropping the user's auth/models/keymap. + // Why: best-effort — symlink creation needs Windows developer mode (else EPERM) and userData may be read-only; preserve the user's config over dropping their auth/models/keymap. return { OPENCODE_CONFIG_DIR: existingConfigDir } } @@ -513,17 +462,10 @@ export class OpenCodeHookService { } } - // Why: walks the user's OPENCODE_CONFIG_DIR top-level entries. The - // `plugins/` subdirectory gets created as a real directory in the overlay - // so Orca can drop a sibling file alongside the user's plugins; everything - // else (opencode.json, auth.json, themes/, etc.) is mirrored as a single - // top-level entry via symlink/junction so user edits propagate live on - // POSIX (and on Windows-with-developer-mode) without copying files. + // Why: mirror user config entries as symlinks so edits propagate live; only plugins/ becomes a real overlay dir so Orca can drop a sibling plugin file. private mirrorUserConfig(sourceDir: string, overlayDir: string): void { const previousManifest = this.readOverlayManifest(overlayDir) - // Why: source-scoped overlays persist across terminals. Only remove paths - // Orca previously mirrored, so deleted/replaced user config cannot stay - // stale while OpenCode-owned runtime dirs such as node_modules survive. + // Why: overlays persist across terminals; remove only Orca-mirrored paths so stale user config clears but OpenCode runtime dirs (node_modules) survive. this.clearManifestEntries(overlayDir, previousManifest) const nextManifest: OpenCodeOverlayManifest = { topLevelEntries: [], pluginEntries: [] } @@ -532,43 +474,25 @@ export class OpenCodeHookService { const sourcePath = join(sourceDir, entry.name) if (entry.name === 'plugins') { - // Why: check isSymbolicLink BEFORE isDirectory — a Windows junction - // can report both as true on a Dirent, and we must take the symlink - // branch so the per-entry mirroring (not a single mirrorEntry call - // that would create a symlink at /plugins) handles it. + // Why: check isSymbolicLink before isDirectory — a Windows junction reports both, and the symlink branch must win. const isSymlink = entry.isSymbolicLink() let isLinkPointingToDir = false if (isSymlink) { try { isLinkPointingToDir = statSync(sourcePath).isDirectory() } catch { - // Why: broken symlink (target missing) or permission error — fall - // through to the default mirrorEntry path so the dangling link is - // mirrored verbatim rather than write-through-resolved. + // Why: broken/inaccessible symlink — mirror the dangling link verbatim instead of resolving through it. isLinkPointingToDir = false } } if ((!isSymlink && entry.isDirectory()) || isLinkPointingToDir) { - // Why: when the user's plugins/ is a symlink-to-dir, resolve to the - // real target so readdir returns the actual entries and child paths - // join against the resolved root. mirrorEntry then creates symlinks - // pointing into the resolved real plugins (not back through the - // user's link), and /plugins itself stays a real dir so - // writePluginIntoOverlay can never write through to the user's FS. + // Why: resolve a symlinked plugins/ to its real target so /plugins stays a real dir and writePluginIntoOverlay can't write through the user's link. const resolvedSource = isLinkPointingToDir ? realpathSync(sourcePath) : sourcePath const overlayPluginsDir = join(overlayDir, 'plugins') mkdirSync(overlayPluginsDir, { recursive: true }) for (const pluginEntry of readdirSync(resolvedSource, { withFileTypes: true })) { - // Why: skip a user file with the same filename as Orca's plugin — - // mirroring it here would either resolve a same-named target via - // symlink (writePluginIntoOverlay then clobbers the user's file - // through the link) or collide on Windows with the directory entry - // about to be created by writePluginIntoOverlay. Either way the - // user's plugin would be lost. Skipping yields the desired - // semantics: Orca's status plugin runs and the user's same-named - // plugin is shadowed for this PTY only — their source file on disk - // is untouched. + // Why: skip a user plugin sharing Orca's filename; mirroring it would let writePluginIntoOverlay clobber the user's file. if (pluginEntry.name === ORCA_OPENCODE_PLUGIN_FILE) { continue } @@ -589,13 +513,7 @@ export class OpenCodeHookService { this.writeOverlayManifest(overlayDir, nextManifest) } - // Why: write Orca's status plugin into the overlay's plugins/ dir. The - // pre-write unlink is the load-bearing part — POSIX writeFileSync over a - // symlink writes through to the link target, so without it a user-owned - // plugin with this filename would be clobbered through a mirrored link. - // Skipping the same-named user file in mirrorUserConfig already prevents - // the link from being created, but the unlink keeps this function safe - // even if a stale overlay slips through with the link still in place. + // Why: pre-write unlink guards against POSIX writeFileSync writing through a mirrored symlink and clobbering a same-named user plugin. private writePluginIntoOverlay(overlayDir: string): void { const pluginsDir = join(overlayDir, 'plugins') mkdirSync(pluginsDir, { recursive: true }) @@ -603,8 +521,7 @@ export class OpenCodeHookService { try { unlinkSync(pluginPath) } catch { - // No-op: file may not exist on a fresh overlay. Any persistent failure - // (e.g. permissions) will surface on the writeFileSync below. + // File may not exist on a fresh overlay; a real failure surfaces on writeFileSync below. } writeFileSync(pluginPath, getOpenCodePluginSource()) } @@ -616,9 +533,7 @@ export class OpenCodeHookService { mkdirSync(pluginsDir, { recursive: true }) writeFileSync(join(pluginsDir, ORCA_OPENCODE_PLUGIN_FILE), getOpenCodePluginSource()) } catch { - // Why: on Windows, userData directories can be locked by antivirus or - // indexers (EPERM/EBUSY). Plugin config is non-critical — the PTY should - // still spawn without the OpenCode status plugin. + // Why: userData can be locked on Windows (EPERM/EBUSY); plugin is non-critical, so spawn without it. return null } return configDir diff --git a/src/main/ports/advertised-url-watcher.ts b/src/main/ports/advertised-url-watcher.ts index b5a9876b55d..befc11ab94f 100644 --- a/src/main/ports/advertised-url-watcher.ts +++ b/src/main/ports/advertised-url-watcher.ts @@ -1,42 +1,26 @@ -/* eslint-disable no-control-regex, max-lines -- Why: ANSI/OSC stripping must match raw - * control sequences in PTY output, same as src/main/runtime/orca-runtime.ts; - * URL parsing, host classification, cache lifecycle, and cross-worktree lookup - * are tightly coupled and kept in one file to keep the rules in lockstep. */ -// Watches PTY output for HTTP(S) URLs that dev servers (Vite, Next, etc.) -// print on startup. Maintains a per-{worktreeId, port} cache so the workspace -// ports panel can show the tool-advertised origin instead of the kernel bind -// (e.g. `https://local.getmontecarlo.com:3001/` rather than `127.0.0.1:3001`). -// -// Why a separate stateful buffer per PTY: ANSI escape sequences and URLs can -// both straddle PTY write boundaries, so we cannot strip ANSI and scan one -// chunk at a time. We accumulate raw bytes, finalize only at newline-anchored -// boundaries, then run a stateless strip-and-scan on the finalized prefix. +/* eslint-disable no-control-regex, max-lines -- control-sequence regexes need raw matching; URL parsing, host classification, cache lifecycle, and cross-worktree lookup stay in one file to keep the rules in lockstep. */ +// Watches PTY output for HTTP(S) URLs dev servers print on startup, caching the +// advertised origin per {worktreeId, port} for the ports panel (vs the kernel bind). +// Why a separate stateful buffer per PTY: ANSI sequences and URLs can straddle PTY +// write boundaries, so we accumulate raw bytes and strip-and-scan only at newlines. const PER_PTY_BUFFER_LIMIT = 4096 const PENDING_PRE_BIND_LIMIT = 16 * 1024 -/** Cap on distinct never-bound PTY IDs. Spawn-failure paths can leave a - * pending entry that never gets bindPty'd — without an upper bound, a - * long-running app would slowly leak one entry per failure. */ +/** Cap on distinct never-bound PTY IDs; spawn-failure paths never bindPty, so without a bound they'd leak one entry each. */ const MAX_PENDING_ENTRIES = 32 const MAX_CACHE_ENTRIES = 256 const URL_CANDIDATE_LIMIT = 2048 -// ANSI/OSC strippers mirror the runtime normalizer, with URL-specific cursor -// move handling below to avoid fusing text that a real terminal would skip. +// ANSI/OSC strippers mirror the runtime normalizer in src/main/runtime/orca-runtime.ts, plus URL-specific cursor-move handling to avoid fusing skipped text. const OSC_PATTERN = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g -// Why: cursor moves in differential redraws can skip cells that are already on -// screen. Replacing them with a URL-invalid guard skips the damaged candidate. +// Why: cursor moves in differential redraws skip on-screen cells; a URL-invalid guard drops the damaged candidate. const CURSOR_MOVE_PATTERN = /\x1b\[[0-?]*[ -/]*[CDGHf]/g const CURSOR_MOVE_URL_GUARD = '[' const CSI_PATTERN = /\x1b\[[0-?]*[ -/]*[@-~]/g const SINGLE_ESC_PATTERN = /\x1b[@-_]/g const CONTROL_PATTERN = /[\x00-\x08\x0b-\x1f\x7f]/g -// Why: this is a permissive candidate matcher. Real validation happens via -// `new URL()` below. Stopping at characters that cannot appear in a URL -// (whitespace, quotes, angle brackets, backtick) avoids absorbing terminal -// punctuation. Trailing punctuation like `.,;)` is trimmed before parsing -// because URLs are commonly followed by sentence punctuation in human text. +// Permissive matcher (real validation is `new URL()` below); stops at non-URL chars so terminal punctuation isn't absorbed. const URL_CANDIDATE_PATTERN = /\bhttps?:\/\/[^\s<>"'`]+/gi export type HostKind = 'custom' | 'loopback' | 'private-ip' | 'public-ip' @@ -49,11 +33,8 @@ export type AdvertisedUrl = { port: number ptyId: string lastSeenAt: number - /** PID of the listening process that this advertised URL was validated - * against on a previous scan. Set by lookup APIs when a current PID is - * supplied; a later mismatch evicts the entry. Captured on first scan - * rather than at capture time because the PTY shell PID is not the - * listener PID. */ + /** Listener PID this URL was validated against on a prior scan; a later mismatch evicts the entry. + * Captured on first scan (not at capture time) because the PTY shell PID isn't the listener PID. */ validatedListenerPid?: number } @@ -82,9 +63,7 @@ function worktreeIdFromCacheKey(key: CacheKey, port: number): string { class PtyBuffer { private raw = '' - /** Append a chunk and return the cleaned, finalized text (everything up to - * and including the last newline). Whatever follows the last newline stays - * buffered so that a URL or ANSI sequence split across chunks survives. */ + /** Append a chunk; return cleaned text up to the last newline. The tail stays buffered so a URL or ANSI sequence split across chunks survives. */ ingest(chunk: string): string { const chunkHasLineBreak = chunk.includes('\n') || chunk.includes('\r') this.raw += chunk @@ -105,8 +84,7 @@ class PtyBuffer { } function lastLineBreak(text: string): number { - // Both \n and \r are accepted; \r\n is handled by stripping \r in - // stripTerminalControls — but we still want either one as a finalize point. + // Accept either \n or \r as a finalize point (\r\n is normalized later in stripTerminalControls). for (let i = text.length - 1; i >= 0; i--) { const ch = text.charCodeAt(i) if (ch === 0x0a || ch === 0x0d) { @@ -162,8 +140,7 @@ function parseUrl(candidate: string): URL | null { } export function classifyHost(hostname: string): HostKind { - // Why: Node's URL.hostname returns IPv6 literals with brackets ("[::1]"), - // while the public API for this function should accept either form. + // Why: strip IPv6 brackets so this public API accepts both "[::1]" (Node's form) and bare literals. const lower = hostname.toLowerCase().replace(/^\[|\]$/g, '') if (lower === 'localhost' || lower === '127.0.0.1' || lower === '::1') { return 'loopback' @@ -230,10 +207,7 @@ function isPrivateIpv6(value: string): boolean { } function hostKindScore(kind: HostKind): number { - // Codex's preference: custom DNS > loopback > private IP > public IP. - // A custom-configured host (e.g. `local.getmontecarlo.com`) is almost always - // the one the user wants opened, and loopback beats LAN for cert/cookie - // reasons on a single-machine setup. + // Prefer custom DNS > loopback > private IP > public IP: loopback beats LAN for cert/cookie reasons on one machine. switch (kind) { case 'custom': return 3 @@ -310,8 +284,7 @@ export class AdvertisedUrlWatcher { if (entry.ptyId !== ptyId) { continue } - // Why: SSH forward enrichment has no listener PID to validate against, - // so PTY teardown is the only reliable expiry signal for that cache row. + // Why: SSH forward enrichment has no listener PID, so PTY teardown is the only reliable expiry signal. this.cache.delete(key) this.validationBaselines.delete(key) this.startupAbsentAllowances.delete(key) @@ -324,8 +297,7 @@ export class AdvertisedUrlWatcher { } forgetWorktree(worktreeId: string): void { - // Why: worktree IDs are reused for the same repo/path, so removed - // worktrees must not leave scan baselines for a future workspace. + // Why: worktree IDs are reused, so a removed worktree must not leave scan baselines for a future one. for (const [ptyId, boundWorktreeId] of this.ptyToWorktree) { if (boundWorktreeId !== worktreeId) { continue @@ -357,14 +329,10 @@ export class AdvertisedUrlWatcher { } const worktreeId = this.ptyToWorktree.get(ptyId) if (!worktreeId) { - // Why: data can arrive on daemon-backed PTYs before the spawn handler - // resolves and we learn the worktreeId (see comment at - // src/main/ipc/pty.ts:1318-1323). Buffer until bindPty replays. + // Why: daemon PTY data can arrive before the spawn handler resolves the worktreeId (src/main/ipc/pty.ts:1318-1323); buffer until bindPty replays. const prior = this.pending.get(ptyId) ?? '' const merged = (prior + chunk).slice(-PENDING_PRE_BIND_LIMIT) - // Why: drop+reinsert touches insertion order so this acts as an LRU - // (Map iterates in insertion order). Combined with the eviction below, - // we keep at most MAX_PENDING_ENTRIES distinct unbound PTYs. + // Why: drop+reinsert refreshes Map insertion order (LRU) so the eviction below drops the oldest unbound PTY. this.pending.delete(ptyId) this.pending.set(ptyId, merged) while (this.pending.size > MAX_PENDING_ENTRIES) { @@ -398,18 +366,12 @@ export class AdvertisedUrlWatcher { return } const hostname = url.hostname - // Why: dev servers sometimes print their bind address verbatim - // (`http://0.0.0.0:3000/`). Those wildcard hosts cannot be opened in a - // browser — the OS scanner already normalizes wildcards to `localhost` - // for the connect host, so refuse the advertised value rather than let - // it overwrite that sensible default. + // Why: wildcard bind hosts (0.0.0.0, ::) can't be opened in a browser; keep the scanner's localhost default instead. if (isUnspecifiedHost(hostname)) { return } const hostKind = classifyHost(hostname) - // Why: store origin only — no path, query, fragment, or userinfo. A - // terminal line containing an OAuth callback or token must not get - // surfaced in the port panel. + // Why: store origin only (no path/query/fragment/userinfo) so an OAuth callback or token can't leak to the panel. const origin = `${protocol}://${formatHostForOrigin(url)}${ isDefaultPort(protocol, port) ? '' : `:${port}` }` @@ -430,17 +392,14 @@ export class AdvertisedUrlWatcher { if (baseline) { this.validationBaselines.set(key, baseline) if (baseline.kind === 'absent') { - // Why: the advertised URL can arrive between the process printing its - // banner and the scanner seeing the listener; allow one settling scan. + // Why: the URL can arrive between the banner print and the scanner seeing the listener; allow one settling scan. this.startupAbsentAllowances.add(key) } else { this.startupAbsentAllowances.delete(key) } } else { this.validationBaselines.delete(key) - // Why: PTY output can arrive before the scanner has produced any - // snapshot for the worktree; give that startup race the same one-scan - // absent allowance as a known-absent baseline. + // Why: PTY output can arrive before any scan snapshot exists; grant the same one-scan absent allowance. this.startupAbsentAllowances.add(key) } const changedEvents = this.enforceCacheLimit() @@ -496,9 +455,7 @@ export class AdvertisedUrlWatcher { if (entry.validatedListenerPid === undefined) { entry.validatedListenerPid = currentListenerPid } else if (entry.validatedListenerPid !== currentListenerPid) { - // Why: the process that printed this URL is gone and a different - // process is now listening on the port. Drop the cached URL — the - // new listener may be unrelated to the captured banner. + // Why: a different process now listens on this port, so the captured banner may be unrelated — drop it. this.cache.delete(key) this.validationBaselines.delete(key) this.startupAbsentAllowances.delete(key) @@ -519,9 +476,8 @@ export class AdvertisedUrlWatcher { } } - /** Reconcile the URL cache with a scanner snapshot for a worktree scope. - * Unvalidated URLs are tied to the listener state observed around capture, - * so a later absent port or changed PID cannot be lazily blessed. */ + /** Reconcile the URL cache with a scanner snapshot. Unvalidated URLs stay tied to the + * listener state seen at capture, so a later absent port or changed PID can't lazily bless them. */ reconcileScan( worktreeIds: readonly string[], observations: readonly AdvertisedUrlListenerObservation[] @@ -582,8 +538,7 @@ export class AdvertisedUrlWatcher { } if (baseline?.kind === 'absent' && current.kind === 'present') { this.startupAbsentAllowances.delete(key) - // Why: dev servers can print their URL before the OS listener scan sees - // the port. Let that first present scan validate the startup banner. + // Why: dev servers print their URL before the listener scan sees the port; let this first present scan validate it. return false } return ( @@ -593,17 +548,10 @@ export class AdvertisedUrlWatcher { ) } - /** Find the best advertised URL for `port` across multiple worktrees. SSH - * connections host several worktrees but the remote-side port scanner - * returns ports for the whole connection, so we need to scan all the - * worktrees attached to that connection. Returns the highest-scoring - * entry by hostKind (custom DNS > loopback > private IP > public IP), - * with HTTPS and recency as tie-breakers — same rule as `shouldReplace` - * uses on insert. - * - * When `currentListenerPid` is supplied each candidate entry is run - * through PID filtering first. Already-pinned mismatches are evicted, - * then only the returned winner is pinned to the current listener. */ + /** Find the best advertised URL for `port` across worktrees, scored via `shouldReplace`. + * Scans all worktrees on the connection because an SSH port scanner reports ports for the + * whole connection, not per-worktree. With `currentListenerPid`, mismatched pinned entries + * are evicted and only the winner is pinned. */ lookupBest( worktreeIds: readonly string[], port: number, @@ -663,15 +611,11 @@ function isDefaultPort(protocol: 'http' | 'https', port: number): boolean { return (protocol === 'http' && port === 80) || (protocol === 'https' && port === 443) } -/** Process-wide singleton. The runtime feeds it from `onPtyData`; the scanner - * enrichment reads it. Tests should instantiate their own - * `AdvertisedUrlWatcher` rather than relying on this. */ +/** Process-wide singleton fed by the runtime and read by scanner enrichment. Tests should instantiate their own instead. */ export const advertisedUrlWatcher = new AdvertisedUrlWatcher() function formatHostForOrigin(url: URL): string { - // Why: Node returns IPv6 hostnames pre-bracketed ("[::1]"). Some other JS - // runtimes strip the brackets. Accept both: re-bracket if a bare IPv6 - // literal slips through. + // Why: some JS runtimes strip the IPv6 brackets Node adds; re-bracket a bare IPv6 literal. const h = url.hostname if (h.startsWith('[') && h.endsWith(']')) { return h @@ -691,8 +635,7 @@ function observedListenersByPort( if (!observed.has(observation.port)) { observed.set(observation.port, observation.pid) } else if (existing !== observation.pid) { - // Multiple host-specific listeners on one port make PID attribution - // ambiguous for our port-keyed URL cache; preserve presence only. + // Multiple host-specific listeners on one port make PID attribution ambiguous; keep presence only. observed.set(observation.port, undefined) } } diff --git a/src/main/source-control/hosted-review-creation.ts b/src/main/source-control/hosted-review-creation.ts index a0271d0ea3d..9d95f6ddf02 100644 --- a/src/main/source-control/hosted-review-creation.ts +++ b/src/main/source-control/hosted-review-creation.ts @@ -1,5 +1,4 @@ -/* eslint-disable max-lines -- Why: provider detection, eligibility, and creation - preflight share one boundary so renderer and main-process gating cannot drift. */ +/* eslint-disable max-lines -- Why: detection, eligibility, and creation preflight share one boundary so gating can't drift. */ import type { CreateHostedReviewInput, CreateHostedReviewResult, @@ -43,9 +42,7 @@ import { type HostedReviewCreationEligibilityInput = HostedReviewCreationEligibilityArgs & { connectionId?: string | null - // Why: only the create-time preflight enforces base-on-remote as a hard block; - // the renderer's eligibility probe passes the base as a candidate and relies on - // Change 1 to correct a local-only parent, so it must never set this. + // Why: only the create-time preflight sets this; the renderer's probe leaves it unset to auto-correct a local-only parent. enforceBaseOnRemote?: boolean } & HostedReviewExecutionOptions @@ -65,11 +62,7 @@ async function isGitHubAuthenticated( connectionId?: string | null, options: HostedReviewExecutionOptions = {} ): Promise { - // Why: a GHES remote is only routed to the GitHub provider once detection has - // confirmed gh is authenticated to its enterprise host, so a non-null slug - // already means authenticated — skip a redundant, rate-limited gh probe. - // Reaching the github.com check below therefore means the remote is github.com - // (its own custom host would have resolved above) (#8312). + // Why: a non-null enterprise slug already means gh is authenticated there, so skip a redundant probe (#8312). if (await getEnterpriseGitHubRepoSlug(repoPath, connectionId, options)) { return true } @@ -142,14 +135,7 @@ async function getDefaultBaseRef( * Whether the candidate base resolves to a remote-tracking branch on the * executing host. * - * Why: a stacked worktree's `worktree.baseRef` is typically a bare parent - * branch name with no remote qualifier, so the probe must match the branch - * under *any* configured remote rather than assume `origin` — otherwise fork - * workflows (`upstream/main`) would be missed. Runs through - * `runGitForHostedReview` so it evaluates on the same host that will run the - * provider create (native/WSL/SSH/relay). This reads the local remote-tracking - * snapshot, not the live remote; see the design doc's Open Questions for the - * ls-remote/staleness tradeoff left as a follow-up. + * Why: matches under *any* remote (not just origin) and reads the local tracking snapshot, not the live remote. */ async function baseRefExistsOnRemote( candidate: string, @@ -165,18 +151,13 @@ async function baseRefExistsOnRemote( runGitForHostedReview(repoPath, argv, connectionId, options) const patterns = [`refs/remotes/*/${base}`] - // Non-origin remote-qualified candidate (e.g. `fork/main`): the wildcard glob - // above only matches a branch literally named that because `*` does not cross `/`. - // Include the exact tracking ref directly. + // `*` does not cross `/`, so a remote-qualified candidate (e.g. `fork/main`) needs its exact tracking ref too. if (base.includes('/')) { patterns.push(`refs/remotes/${base}`) } try { - // for-each-ref exits 0 whether or not the pattern matches, so a clean empty - // result is an authoritative "absent" while a thrown error is a transport - // failure. Never conflate the two: on an unreachable host, preserve the - // candidate rather than silently demoting a legitimately-pushed parent base. + // for-each-ref exits 0 on no match: empty means absent, a thrown error means transport failure (preserve the candidate). const { stdout } = await run(['for-each-ref', '--count=1', '--format=%(refname)', ...patterns]) return stdout.trim().length > 0 } catch { @@ -210,15 +191,13 @@ async function hasUncommittedChanges( 'Remote connection dropped. Click Reconnect on the SSH target before retrying.' ) } - // Why: the relay intentionally restricts generic git.exec. Use the - // structured status RPC for SSH dirty checks instead of raw `git status`. + // Why: the relay restricts generic git.exec, so use the structured status RPC for SSH dirty checks. return (await provider.getStatus(repoPath)).entries.length > 0 } const { stdout } = await gitExecFileAsync(['status', '--porcelain'], { cwd: repoPath, ...getHostedReviewLocalGitOptions(options), - // Why: create-PR validation should not take Git's optional index lock while - // the user may be running fetch/pull/rebase from a terminal. + // Why: don't take Git's optional index lock while the user may be running fetch/pull/rebase in a terminal. env: gitOptionalLocksDisabledEnv() }) return stdout.trim().length > 0 @@ -237,8 +216,7 @@ async function getHostedReviewUpstreamStatus( throw new Error('Remote connection dropped. Click Reconnect on the SSH target before retrying.') } try { - // Why: SSH exposes upstream divergence through a dedicated relay RPC; - // generic git.exec intentionally does not allow rev-list/status plumbing. + // Why: the relay blocks generic git.exec, so use its dedicated upstream RPC for SSH divergence. return await provider.getUpstreamStatus(repoPath) } catch (error) { if (isNoUpstreamError(error)) { @@ -431,16 +409,11 @@ async function validateCurrentBranchCanCreateReview( ahead: upstreamStatus.ahead, behind: upstreamStatus.behind, connectionId, - // Why: this is the last gate before the provider create, which targets the - // submitted base verbatim — enforce that the base exists on the remote here. + // Why: last gate before the create, which targets the submitted base verbatim — enforce it exists on the remote. enforceBaseOnRemote: true, ...options }) - // Why: never call the provider create API when the existing-review lookup - // could not prove the branch has no review. An `unavailable` outcome means a - // real MR/PR might exist that a failed lookup could not see; refuse - // inconclusively (design invariant 8 / submission boundary) so we never - // create a duplicate. The composed title/body are preserved by the caller. + // Why: an unavailable lookup might hide a real PR — refuse rather than risk a duplicate (design invariant 8). if (eligibility.reviewLookupOutcome === 'unavailable') { return { ok: false, @@ -448,8 +421,7 @@ async function validateCurrentBranchCanCreateReview( error: `Create ${copy.shortLabel} failed: Orca could not confirm whether this branch already has a ${copy.reviewLabel}. Retry once the ${copy.providerName} lookup succeeds.` } } - // Why: renderer eligibility can be stale by submit time; the main process - // is the last chance to avoid creating a PR from an out-of-date remote head. + // Why: renderer eligibility can be stale by submit time; main process is the last gate before an out-of-date create. return blockedEligibilityToCreateResult(eligibility, submittedBase) } catch (error) { console.warn('Hosted review creation preflight failed:', error) @@ -470,11 +442,7 @@ export async function getHostedReviewCreationEligibility( connectionId: args.connectionId, ...hostedReviewExecutionContext(args) }) - // Why: an incoming base is only a *candidate* for the default merge target. A - // stacked worktree's parent base resolves on the remote only when it was - // actually pushed; a local-only parent must fall back to the repo default so - // the PR targets a ref the remote can resolve. Never regress to "no base" — - // keep the candidate if the repo default itself is unavailable. + // Why: the base is only a candidate; fall back to repo default so a local-only parent targets a remote-resolvable ref. const candidateBase = args.base?.trim() || null const candidateBaseOnRemote = candidateBase != null && @@ -488,9 +456,7 @@ export async function getHostedReviewCreationEligibility( } const baseBranch = defaultBaseRef ? normalizeHostedReviewBaseRef(defaultBaseRef) : null let review: Awaited> = null - // Why: a swallowed lookup failure must not masquerade as authoritative - // no-review evidence. Track it so the renderer can distinguish an accepted - // no-PR result (`not_found`) from a local-blocker fallback (`unavailable`). + // Why: track lookup failure so a swallowed error isn't mistaken for authoritative no-review evidence. let lookupFailed = false try { review = await getHostedReviewForBranch({ @@ -506,12 +472,7 @@ export async function getHostedReviewCreationEligibility( ...hostedReviewExecutionContext(args) }) } catch (error) { - // Why: a failed existing-review lookup can never authorize Create — it might - // be hiding a real PR. Record `unavailable` and fall through so any local - // blocker (dirty/no-upstream/behind) still supplies primary guidance, but the - // clean-branch happy path below is forced to `canCreate: false`. Never rethrow - // here: a thrown lookup must not collapse into a false `not_found` and must - // stay a structured, honest empty state for the renderer. + // Why: a failed lookup might hide a real PR, so record unavailable and fall through rather than rethrow. lookupFailed = true console.warn('Hosted review lookup failed; treating existing-review as unavailable:', error) } @@ -580,12 +541,7 @@ export async function getHostedReviewCreationEligibility( if ((args.ahead ?? 0) > 0) { return { ...baseResult, canCreate: false, blockedReason: 'needs_push', nextAction: 'push' } } - // Why: at create-time, `gh pr create` (and the other providers) target the - // submitted base verbatim — Change 1 only corrects the *default*, not a stale - // renderer's submitted value. Block a local-only submitted base here so it - // fails with actionable copy instead of the provider's opaque error. Only the - // create-time preflight enforces this; the renderer's eligibility probe leaves - // enforceBaseOnRemote unset so a local-only parent is silently auto-corrected. + // Why: providers target the submitted base verbatim; block a local-only base here with actionable copy. if (args.enforceBaseOnRemote && candidateBase && !candidateBaseOnRemote) { return { ...baseResult, @@ -594,9 +550,7 @@ export async function getHostedReviewCreationEligibility( nextAction: null } } - // Why: a lookup that failed leaves review existence unproven, so the clean, - // in-sync happy path must not claim `canCreate` — that would open Create - // against a branch that may already have a review. Stays `unavailable`. + // Why: a failed lookup leaves review existence unproven, so the happy path must not claim canCreate. return { ...baseResult, canCreate: lookupFailed ? false : Boolean(baseBranch), diff --git a/src/main/speech/model-manager.ts b/src/main/speech/model-manager.ts index c8f918f1526..2b8bca8bbca 100644 --- a/src/main/speech/model-manager.ts +++ b/src/main/speech/model-manager.ts @@ -46,24 +46,13 @@ type DownloadTotals = { totalBytes: number } type ContentRange = { start: number; end: number; totalBytes?: number } const DOWNLOAD_IDLE_TIMEOUT_MS = 120_000 -// Why: flaky networks and scanning proxies commonly kill long CDN transfers -// near the end (reported as "stuck at 90%, then fails"); resuming with a Range -// request lets those connections finish instead of failing the same way on -// every full restart. +// Why: flaky networks/proxies often kill long CDN transfers near the end; Range-resume lets them finish. const DOWNLOAD_RETRY_DELAYS_MS = [1_000, 2_000, 4_000] -// Why: abandon a download only after this many CONSECUTIVE attempts make no -// forward progress. A resume that keeps advancing (even across many mid-stream -// drops) is never given up on — that is the whole point of resumable downloads -// for large models over flaky networks, and a fixed all-time failure budget -// would wrongly kill a steadily-progressing large download. +// Why: count only CONSECUTIVE no-progress attempts, so a download still advancing across drops is never abandoned. const MAX_NO_PROGRESS_ATTEMPTS = DOWNLOAD_RETRY_DELAYS_MS.length + 1 -// Why: an absolute backstop so a server handing back pathologically tiny (or -// zero-net) segments cannot loop forever; sized to cover the largest (~1GB) -// model even if a proxy caps each range response near ~256KB, far below any -// real CDN chunk. +// Why: absolute backstop against a tiny-segment server; 4096 covers the ~1GB model even at a proxy's ~256KB min range. const MAX_TOTAL_DOWNLOAD_REQUESTS = 4_096 -// Why: a longer server window should be surfaced for a later manual retry, -// rather than leaving the settings download looking stuck for minutes. +// Why: cap honored Retry-After; a longer server window is surfaced for manual retry, not a multi-minute stall. const MAX_RETRY_AFTER_MS = 120_000 const RETRYABLE_NET_ERROR = /net::ERR_(CONTENT_LENGTH_MISMATCH|INCOMPLETE_CHUNKED_ENCODING|CONNECTION_(RESET|CLOSED|ABORTED|REFUSED|TIMED_OUT)|EMPTY_RESPONSE|NETWORK_CHANGED|TIMED_OUT|INTERNET_DISCONNECTED|ADDRESS_UNREACHABLE|NAME_NOT_RESOLVED|SOCKET_NOT_CONNECTED|HTTP2_PROTOCOL_ERROR|QUIC_PROTOCOL_ERROR)\b/ @@ -172,8 +161,7 @@ export class ModelManager { const prepared = this.prepareModelsDir(requestedModelsDir) this.modelsDir = prepared.modelsDir this.migrationSourceDir = prepared.migrationSourceDir - // Why: migrating a non-ASCII cache copies large model files; run it off the - // main thread and gate model-state reads on it so the UI stays responsive. + // Why: migration copies large model files, so run it async and gate state reads on it to keep the UI responsive. this.migrationReady = migrateSpeechModelCacheIfNeeded( prepared.migrationSourceDir, prepared.modelsDir @@ -181,8 +169,7 @@ export class ModelManager { } setProgressCallback(cb: ProgressCallback): () => void { - // Why: concurrent settings windows can observe the same download; a - // returned unsubscribe prevents one window from replacing another. + // Why: return an unsubscribe so concurrent settings windows don't replace each other's callback. this.progressCallbacks.add(cb) return () => { this.progressCallbacks.delete(cb) @@ -274,10 +261,7 @@ export class ModelManager { } async downloadModel(modelId: string): Promise { - // Why: no migration await here — migration only copies dirs already present - // in the old cache (surfaced as ready via getModelState before download is - // offered), so it never races a download, and awaiting would defer the - // synchronous request setup that cancelDownload relies on. + // Why: no migration await — it never races a download, and awaiting would defer setup cancelDownload relies on. if (this.activeDownloads.has(modelId)) { return } @@ -302,8 +286,7 @@ export class ModelManager { this.updateState(modelId, 'downloading', 0) const archivePath = join(this.modelsDir, `${modelId}.tar.bz2`) - // Why: resume appends to the partial archive from this download run only; - // a leftover file from a crashed run would corrupt the resumed archive. + // Why: resume appends, so a leftover archive from a crashed run would corrupt the download. try { if (existsSync(archivePath)) { rmSync(archivePath) @@ -317,8 +300,7 @@ export class ModelManager { const handle: DownloadHandle = { abort: () => { aborted = true - // Why: a stalled HTTPS request may never deliver another data chunk; - // cancellation must tear down the request immediately. + // Why: a stalled HTTPS request may never deliver another chunk, so tear it down immediately. abortController.abort() } } @@ -355,9 +337,7 @@ export class ModelManager { } if (!this.validateModelFiles(manifest, modelDir)) { - // Why: some archives nest files inside a subdirectory matching the - // archive name. If the expected files aren't at the top-level model - // dir, scan one level down and move them up. + // Why: some archives nest files in a subdir; scan one level down and move them up. await this.flattenNestedDir(modelDir, manifest) } @@ -378,8 +358,7 @@ export class ModelManager { } this.cleanup(modelId, archivePath) if (!aborted) { - // Why: the settings UI awaits this promise to show download failures; - // cancellation stays quiet, but real failures must reach the caller. + // Why: the settings UI awaits this to surface failures; stay quiet on cancellation, rethrow real errors. throw err } } finally { @@ -416,9 +395,7 @@ export class ModelManager { if (existsSync(modelDir)) { await rm(modelDir, { recursive: true, force: true }) } - // Why: also delete the pre-migration copy (awaited above, so the copy has - // finished) — otherwise the next launch re-migrates it and resurrects the - // model the user just deleted. + // Why: also delete the pre-migration copy, or the next launch re-migrates it and resurrects the model. if (this.migrationSourceDir) { const sourceModelDir = this.getSafeModelDir(modelId, this.migrationSourceDir) if (existsSync(sourceModelDir)) { @@ -436,8 +413,7 @@ export class ModelManager { ): void { const state: SpeechModelState = { id: modelId, status, progress, error } this.modelStates.set(modelId, state) - // Why: notify the renderer on every state change (not just download - // progress) so the UI updates for extracting/ready/error transitions. + // Why: notify on every state change (not just progress) so extracting/ready/error transitions reach the UI. const progressValue = progress ?? (status === 'extracting' ? 0.95 : -1) for (const callback of this.progressCallbacks) { callback(modelId, progressValue) @@ -466,13 +442,11 @@ export class ModelManager { for (;;) { requestCount += 1 const offset = this.getPartialArchiveBytes(archivePath) - // Why: the transport can fail after the final byte reached disk; the - // caller's SHA-256 check is the authoritative completion test. + // Why: transport can fail after the last byte hits disk; the SHA-256 check is the real completion test. if (offset === totals.totalBytes) { return } - // Why: absolute backstop against a server that never lets the download - // finish; the stall and abort paths handle the common cases first. + // Why: absolute backstop against a server that never lets the download finish. if (requestCount > MAX_TOTAL_DOWNLOAD_REQUESTS) { throw describeInterruptedDownload( new Error('too many download requests'), @@ -482,8 +456,7 @@ export class ModelManager { ) } try { - // Why: each attempt restarts from the canonical URL rather than the - // last redirect target, because CDN redirect URLs are signed and expire. + // Why: restart from the canonical URL, not the last redirect, because signed CDN redirect URLs expire. await this.downloadFile( url, archivePath, @@ -508,10 +481,7 @@ export class ModelManager { `Model download response ended at ${receivedBytes} of ${totals.totalBytes} bytes` ) if (receivedBytes > offset) { - // Why: some proxies return a valid but bounded range segment; request - // the next segment immediately instead of failing checksum or waiting. - // Forward progress resets the stall counter; the total-request ceiling - // at the loop top bounds a pathological tiny-segment server. + // Why: some proxies cap each range segment; request the next immediately and reset the stall counter. noProgressStreak = 0 continue } @@ -530,8 +500,7 @@ export class ModelManager { if (!isRetryableDownloadError(err)) { throw err } - // Why: give up only on a genuine stall (repeated no forward progress); - // a download that keeps advancing across drops is allowed to continue. + // Why: give up only on a genuine stall; a download still advancing across drops keeps going. if (noProgressStreak >= MAX_NO_PROGRESS_ATTEMPTS) { throw describeInterruptedDownload(err, receivedBytes, totals.totalBytes, requestCount) } @@ -697,8 +666,7 @@ export class ModelManager { (parsedLength <= 0 || parsedLength === contentRange.end - contentRange.start + 1) if (resumeOffset > 0 && response.statusCode === 206 && !resumed) { - // Why: appending an unverified range can silently corrupt the archive; - // discard it so the canonical URL is retried from byte zero. + // Why: appending an unverified range can silently corrupt the archive; discard and retry from byte zero. try { rmSync(dest) } catch { @@ -716,8 +684,7 @@ export class ModelManager { if (response.statusCode !== 200 && !resumed) { if (response.statusCode === 416) { - // Why: the server rejected our resume offset; drop the partial so - // the retry loop can restart this download from scratch. + // Why: 416 means the server rejected our resume offset; drop the partial to restart from scratch. try { rmSync(dest) } catch { @@ -729,17 +696,14 @@ export class ModelManager { statusError.httpStatusCode = response.statusCode statusError.retryAfterMs = parseRetryAfterMs(response.headers['retry-after']) rejectOnce(statusError) - // Why: retrying must not leave a prior error body draining without - // cancellation or idle-timeout ownership. + // Why: abort so a retry doesn't leave the error-response body draining unowned. activeRequest?.abort() return } - // Why: a 200 despite our Range request means the server restarted the - // transfer from byte zero, so the partial file must be overwritten. + // Why: a 200 to our Range request means the server restarted from byte zero, so overwrite the partial. const progressBase = resumed ? resumeOffset : 0 - // Why: Content-Length on a 206 describes only this segment; when - // Content-Range uses '*', retain the known complete archive size. + // Why: Content-Length on a 206 is only this segment; on Content-Range '*' keep the known full size. const totalSize = resumed ? (contentRange?.totalBytes ?? totals?.totalBytes ?? expectedSize) : parsedLength > 0 @@ -789,8 +753,7 @@ export class ModelManager { request.setHeader('Range', `bytes=${resumeOffset}-`) } - // Why: Electron's net stack honors app proxy settings, unlike Node's - // https client, but it does not expose request.setTimeout(). + // Why: Electron net honors app proxy settings (unlike Node https) but exposes no setTimeout, so time out manually. resetIdleTimeout() request.on('error', onRequestError) request.on('response', onResponse) @@ -838,8 +801,7 @@ export class ModelManager { const onEnd = (): void => { const actualSha256 = hash.digest('hex') if (actualSha256 !== expectedSha256.toLowerCase()) { - // Why: these archives feed native model parsers; filename checks do - // not protect against compromised or redirected release assets. + // Why: archives feed native parsers, so verify contents against compromised/redirected release assets. settleReject(new Error('Downloaded model archive failed integrity verification')) return } @@ -862,10 +824,7 @@ export class ModelManager { mkdirSync(modelDir, { recursive: true }) return new Promise((resolve, reject) => { - // Why: spawn instead of exec because exec buffers all stdout/stderr - // (1MB default maxBuffer). bzip2 decompression is slow (~1-5 min for - // 170MB archives) and exec can silently kill the process if stderr - // exceeds the buffer. spawn streams output without buffering. + // Why: spawn (not exec) so slow bzip2 stderr can't overflow exec's 1MB maxBuffer and silently kill the process. const tarExecutable = resolveTarExecutable() const child = spawn( tarExecutable, @@ -929,8 +888,7 @@ export class ModelManager { }, 600_000) abortPoll = setInterval(() => { if (isAborted()) { - // Why: if the extraction child wedges and never emits close/error, - // the abort poller must still clear itself when we reject. + // Why: a wedged child may never emit close/error, so abort must kill it here. fail(new Error('Aborted'), true) } }, 250) diff --git a/src/main/ssh/ssh-connection.ts b/src/main/ssh/ssh-connection.ts index d38b0ab359c..eb2f79cf13a 100644 --- a/src/main/ssh/ssh-connection.ts +++ b/src/main/ssh/ssh-connection.ts @@ -51,15 +51,10 @@ type SshRemoteFileOptions = { hostPlatform?: RemoteHostPlatform } -// Upper bound on waiting, after an abort, for the in-flight open callback or -// for an aborted late-opened channel to finish closing before rejecting -// anyway. Normal opens and closes complete in one network round-trip. +// Upper bound on waiting for an aborted channel's open/close to settle before rejecting anyway. const ABORTED_CHANNEL_CLOSE_GRACE_MS = 5_000 -// Why: on session-limited servers (MaxSessions), a channel open can be refused -// transiently — e.g. our next CHANNEL_OPEN reaching sshd a few microseconds -// before it finishes processing the previous channel's close. A refused open -// never started the command, so retrying is always safe. +// Why: MaxSessions servers can transiently refuse a channel open; a refused open never ran the command, so retry is safe. const SESSION_LIMIT_OPEN_RETRIES = 4 const SESSION_LIMIT_OPEN_RETRY_DELAY_MS = 150 @@ -131,12 +126,7 @@ export class SshConnection { this.callbacks = callbacks } - // Why: exposes whether a passphrase/password is already cached in-memory for - // this connection. Used by ssh:needsPassphrasePrompt so callers can decide - // whether a manual-reconnect will prompt or go through silently. Without this, - // lastRequiredPassphrase stays true across the session even after the user - // has entered the credential once, causing redundant "enter passphrase" - // prompts on disconnect→reconnect cycles within a single app session. + // Why: lets ssh:needsPassphrasePrompt skip redundant passphrase prompts on reconnect when the credential is already cached in-memory. hasCachedCredential(): boolean { return this.cachedPassphrase != null || this.cachedPassword != null } @@ -247,10 +237,7 @@ export class SshConnection { unconfirmedOpenError = Object.assign(error, { sshChannelCloseConfirmed: false }) return unconfirmedOpenError } - // Why: rejecting the instant the signal aborts lets the caller proceed - // while the in-flight channel open completes in the background and holds - // a server-side session slot (MaxSessions). Mark the abort and settle - // from the open callback, after the late resource has been closed. + // Why: an in-flight open holds a MaxSessions slot; reject the caller now, then settle from the open callback once the late channel closes. let abortRequested = false let abortDeadlineTimer: NodeJS.Timeout | undefined const cleanup = (): void => { @@ -260,8 +247,7 @@ export class SshConnection { } const onAbort = (): void => { abortRequested = true - // Why: a hung socket may never invoke the open callback — bound the - // aborted caller's wait instead of pinning it for CONNECT_TIMEOUT_MS. + // Why: a hung socket may never invoke the open callback; bound the aborted caller's wait instead of pinning it for CONNECT_TIMEOUT_MS. abortDeadlineTimer = setTimeout(() => { settled = true cleanup() @@ -288,8 +274,7 @@ export class SshConnection { if (onClose) { emitter.once?.('close', onClose) } - // Why: ssh2 can withhold CHANNEL_CLOSE while discarded exec streams - // remain unread, and teardown errors have no other owner. + // Why: ssh2 withholds CHANNEL_CLOSE while discarded exec streams remain unread, and teardown errors have no other owner. emitter.resume?.() emitter.stderr?.resume?.() try { @@ -320,14 +305,12 @@ export class SshConnection { } done() } - // Why: bounded — a remote that never confirms the close must not hang - // the aborted operation forever. + // Why: bounded so a remote that never confirms the close can't hang the aborted operation forever. const closeGraceTimer = setTimeout(done, ABORTED_CHANNEL_CLOSE_GRACE_MS) if (typeof emitter.once === 'function') { emitter.once('close', confirmAndDone) } - // Why: ssh2 withholds the 'close' event until the channel's streams - // are drained; nobody else will ever read this discarded channel. + // Why: ssh2 withholds 'close' until the channel's streams are drained; nobody else will read this discarded channel. discardLateValue(value) if (typeof emitter.once !== 'function') { done() @@ -335,9 +318,7 @@ export class SshConnection { } const finish = (error: Error | undefined, value?: T): void => { if (settled) { - // Why: ssh2 can invoke the open callback after our timeout has - // rejected. Close that late resource so the remote channel is not - // left open with no owner. + // Why: ssh2 can invoke the open callback after our timeout rejected; close that late channel so it isn't left open with no owner. if (!error && value !== undefined) { discardLateValue(value, () => { if (unconfirmedOpenError) { @@ -372,8 +353,7 @@ export class SshConnection { signal?.addEventListener('abort', onAbort, { once: true }) try { - // Why: higher-level channel timers start only after ssh2 invokes its - // open callback. A stale SSH socket can otherwise keep exec/sftp stuck. + // Why: higher-level channel timers start only after ssh2's open callback; a stale SSH socket can otherwise keep exec/sftp stuck. register(finish) } catch (error) { finish(error instanceof Error ? error : new Error(String(error))) @@ -386,8 +366,7 @@ export class SshConnection { remoteDir: string, options?: SshRemoteFileOptions & { signal?: AbortSignal } ): Promise { - // Why: relay deployment timeout and connection teardown are independent; - // either owner must stop a transfer that can otherwise outlive its lock. + // Why: relay-deploy timeout and connection teardown are independent owners; either must stop a transfer that could outlive its lock. const linkedSignal = createLinkedSshFileTransferSignal( [this.systemOperationAbortController.signal, options?.signal].filter( (signal): signal is AbortSignal => signal !== undefined @@ -463,8 +442,7 @@ export class SshConnection { close: () => sftp.end() } } - // Why: disconnect replaces the connection controller; an existing import - // session must stay bound to the signal and SSH config it opened with. + // Why: disconnect replaces the connection controller, so an existing import session must stay bound to the signal and SSH config it opened with. const signal = this.systemOperationAbortController.signal const buildArgsOptions = this.getSystemSshBuildArgsOptions() return { @@ -590,8 +568,7 @@ export class SshConnection { } catch (err) { lastError = err instanceof Error ? err : new Error(String(err)) - // Why: a concurrent disconnect() already set 'disconnected'; a cancelled - // attempt's late error must not overwrite it with auth-failed/error. + // Why: a concurrent disconnect() already set 'disconnected'; a cancelled attempt's late error must not overwrite it with auth-failed/error. if (this.disposed) { throw lastError } @@ -630,10 +607,7 @@ export class SshConnection { await this.doSystemSshProbeWithControlMasterRetry(connectGeneration, resolved) return } - // Why: ssh2 has no gssapi-with-mic support, so hosts that explicitly - // request GSSAPIAuthentication try Kerberos SSO via the system OpenSSH - // binary first. Restrict the probe to GSSAPI so missing tickets fall through - // to Orca's existing key and credential-prompt path. + // Why: ssh2 lacks gssapi-with-mic; GSSAPIAuthentication hosts try Kerberos SSO via system OpenSSH first, then fall through to key/credential auth. if (this.target.gssapiAuthentication === true) { try { await this.doSystemSshProbeWithControlMasterRetry(connectGeneration, resolved, true) @@ -644,9 +618,7 @@ export class SshConnection { } } } - // Why: a synchronous spawn throw (no system ssh binary) bypasses the probe's - // own catch, so the ssh2 fall-through must clear all system-transport state - // itself — otherwise exec/sftp keep routing through the failed transport. + // Why: a synchronous spawn throw bypasses the probe's catch, so clear system-transport state here or exec/sftp keep routing through the failed transport. this.systemSshResolvedConfig = null this.systemSshControlMasterDisabledForSession = false this.systemSshGssapiOnlyForSession = false @@ -654,8 +626,7 @@ export class SshConnection { const config = buildConnectConfig(this.target, resolved) - // Why: ssh2 doesn't support ProxyCommand/ProxyJump natively. Spawn the - // resolved proxy and pipe its stdin/stdout as config.sock. + // Why: ssh2 doesn't support ProxyCommand/ProxyJump natively; spawn the resolved proxy and pipe its stdin/stdout as config.sock. const effectiveProxy = resolveEffectiveProxy(this.target, resolved) if (effectiveProxy) { const proxy = spawnProxyCommand(effectiveProxy, config.host!, config.port!, config.username!) @@ -683,8 +654,7 @@ export class SshConnection { this.proxyProcess?.kill() this.proxyProcess = null try { - // Why: on macOS, per-app network policy can block Orca's direct - // TCP socket while the system OpenSSH binary is still allowed. + // Why: on macOS, per-app network policy can block Orca's direct TCP socket while the system OpenSSH binary is still allowed. await this.doSystemSshProbeWithControlMasterRetry(connectGeneration, resolved) return } catch { @@ -700,16 +670,13 @@ export class SshConnection { let passphrasePromptHandled = false let credentialRetryConfig = config - // Why: ssh2 parses encrypted privateKey values before it tries agent - // auth. When an agent is available, give it the first attempt and only - // fall back to direct key parsing after agent auth fails. + // Why: ssh2 parses encrypted privateKey before agent auth; when an agent exists, let it try first and fall back to direct key parsing only if it fails. if (isAgentFallbackError(authError) && config.agent && !config.privateKey) { const keyConfig = buildConnectConfig(this.target, resolved, { includeAgent: false, includePrivateKey: true }) - // Why: if the agent path failed, password/passphrase retries should not - // go back through the same agent-only config. + // Why: if the agent path failed, password/passphrase retries must not reuse the same agent-only config. credentialRetryConfig = keyConfig if (this.cachedPassphrase) { keyConfig.passphrase = this.cachedPassphrase @@ -729,10 +696,7 @@ export class SshConnection { throw keyErr } authError = keyErr - // Why: when the effective config enables GSSAPI, let the reactive - // system-ssh probe (below) try a Kerberos ticket before prompting - // for the key passphrase; the general passphrase prompt still runs - // if that probe fails, since passphrasePromptHandled stays false. + // Why: with GSSAPI enabled, let the reactive system-ssh probe try a Kerberos ticket before prompting for the passphrase; the prompt still runs if it fails. if ( isPassphraseError(authError) && !this.cachedPassphrase && @@ -757,8 +721,7 @@ export class SshConnection { } } - // Why: a Kerberos ticket may authenticate where keys did not; try the - // system ssh binary before falling back to interactive prompts. + // Why: a Kerberos ticket may authenticate where keys did not; try the system ssh binary before falling back to interactive prompts. if (isGssapiSystemSshFallbackCandidate(authError, this.target, resolved)) { this.proxyProcess?.kill() this.proxyProcess = null @@ -771,9 +734,7 @@ export class SshConnection { this.systemSshGssapiOnlyForSession = false this.useSystemSshTransport = false } - // Why: if a disconnect/reconnect superseded this attempt mid-probe, throw - // the cancellation error — not the stale ssh2 authError — so connect() - // does not post auth-failed after the target was deliberately disconnected. + // Why: if a disconnect/reconnect superseded this attempt mid-probe, throw the cancellation error (not the stale authError) so connect() doesn't post auth-failed. if (this.disposed || !this.isCurrentConnectAttempt(connectGeneration)) { throw this.createCancelledConnectAttemptError() } @@ -785,8 +746,7 @@ export class SshConnection { throw authError } - // Why: prompt for passphrase on encrypted-key error, then retry with - // a fresh proxy socket (ssh2 may have destroyed the original). + // Why: prompt for passphrase on encrypted-key error, then retry with a fresh proxy socket (ssh2 may have destroyed the original). if (isPassphraseError(authError) && !this.cachedPassphrase && !passphrasePromptHandled) { const detail = this.target.identityFile || resolved?.identityFile?.[0] || '(unknown)' const val = await this.callbacks.onCredentialRequest(this.target.id, 'passphrase', detail) @@ -798,8 +758,7 @@ export class SshConnection { return } } - // Why: an agent socket failure can still be recovered by password auth, - // but the retry must use the no-agent config selected above. + // Why: an agent socket failure can still be recovered by password auth, but the retry must use the no-agent config selected above. if (isAgentFallbackError(authError) && !this.cachedPassword) { const val = await this.callbacks.onCredentialRequest( this.target.id, @@ -828,9 +787,7 @@ export class SshConnection { clearTimeout(this.reconnectTimer) this.reconnectTimer = null } - // Why: OS sleep/wake can leave ssh2 thinking a dead TCP socket is still - // connected. Tear down the local transport and run the normal reconnect - // path so the relay session can reattach remote PTYs after wake. + // Why: OS sleep/wake can leave ssh2 thinking a dead TCP socket is still connected; tear down and reconnect so the relay can reattach remote PTYs. this.closeTransportsForReconnect() this.state.reconnectAttempt = 0 this.setState('reconnecting') @@ -843,8 +800,7 @@ export class SshConnection { this.proxyProcess?.kill() this.proxyProcess = null - // Why: this probe runs before remote platform detection. A raw echo works - // under POSIX shells, cmd.exe, and PowerShell; `/bin/sh` wrapping does not. + // Why: this probe runs before remote platform detection; a raw echo works under POSIX shells, cmd.exe, and PowerShell, but `/bin/sh` wrapping does not. const channel = this.spawnTrackedSystemSshCommand('echo ORCA-SYSTEM-SSH-OK', { wrapCommand: false }) @@ -1006,8 +962,7 @@ export class SshConnection { reject(this.createCancelledConnectAttemptError()) } const onReady = (): void => { - // Why: direct system SSH startup has the same late-ready race as ssh2; - // disconnect/reconnect must own the generation before state can flip. + // Why: direct system SSH has the same late-ready race as ssh2; disconnect/reconnect must own the generation before state flips. if (!this.isCurrentConnectAttempt(connectGeneration)) { settle(cancelStartup) return @@ -1101,8 +1056,7 @@ export class SshConnection { return options } - // Why: ssh2 may destroy the proxy socket on auth failure, so credential - // retries need a fresh proxy process and Duplex stream. + // Why: ssh2 may destroy the proxy socket on auth failure, so credential retries need a fresh proxy process and Duplex stream. private respawnProxy( config: ConnectConfig, proxy: ReturnType | null | undefined @@ -1126,8 +1080,7 @@ export class SshConnection { client.off('error', onStartupError) } const swallowLateStartupError = (): void => { - // Why: ssh2 can emit another socket error while a failed or cancelled - // pre-handshake client is being destroyed, after startup has settled. + // Why: ssh2 can emit another socket error while destroying a settled pre-handshake client. } const guardStartupDestroy = (): void => { client.on('error', swallowLateStartupError) @@ -1137,9 +1090,7 @@ export class SshConnection { if (settled) { return } - // Why: connect() completion races with explicit disconnect(). Once a - // newer connect attempt or disconnect bumps the generation/disposed - // state, this late ready event must not resurrect the torn-down client. + // Why: connect() completion races with disconnect(); a late ready must not resurrect a torn-down client after generation/disposed changes. if (this.disposed || connectGeneration !== this.connectGeneration) { settled = true guardStartupDestroy() @@ -1154,15 +1105,7 @@ export class SshConnection { this.proxyProcess = null this.setupDisconnectHandler(client) cleanupStartupListeners() - // Why: ssh2 leaves Nagle's algorithm on by default. For single-byte - // keystrokes through a remote PTY this stacks with the kernel's - // delayed-ACK timer and adds up to ~40 ms per keystroke. OpenSSH's - // `ssh` sets TCP_NODELAY whenever a PTY is allocated; we mirror that - // because every channel we open over this connection (PTY data, - // JSON-RPC requests, port-scan probes) is latency-sensitive. No-op - // for proxy-command / proxy-jump connections where _sock is a custom - // Duplex; that case relies on the proxy program's own TCP behavior, - // same as native ssh. + // Why: ssh2 leaves Nagle on; enable TCP_NODELAY so keystrokes don't stack with delayed-ACK (~40ms each). No-op for proxy sockets. const sock = (client as unknown as { _sock?: { setNoDelay?: unknown } })._sock if (sock instanceof net.Socket) { console.warn(`[ssh] TCP_NODELAY enabled for ${this.target.label}`) @@ -1191,8 +1134,7 @@ export class SshConnection { }) } - // Why: guard on identity so a late event from the old client doesn't - // null out a successful reconnect. + // Why: guard on identity so a late event from the old client can't null out a successful reconnect. private setupDisconnectHandler(client: SshClient): void { const onDrop = () => { if (this.disposed || this.client !== client) { @@ -1234,8 +1176,7 @@ export class SshConnection { private async runReconnectAttempt(attempt: number): Promise { try { - // Why: reset reconnectAttempt before attemptConnect so setState('connected') - // broadcasts reconnectAttempt=0, which ssh.ts uses to trigger relay re-establishment. + // Why: reset before connecting so the 'connected' broadcast carries reconnectAttempt=0, which ssh.ts uses to trigger relay re-establishment. this.state.reconnectAttempt = 0 await this.attemptConnect() } catch (err) { @@ -1316,9 +1257,7 @@ export class SshConnection { this.systemSsh = proc this.useSystemSshTransport = true this.setState('connected') - // Why: register reconnection handler only after the initial handshake - // succeeds. The onExit registered above guards with `settled` so it - // won't fire a duplicate for exits during the handshake phase. + // Why: register the reconnect handler only after handshake succeeds (the onExit above guards with `settled`). proc.onExit(() => { if (!this.disposed && this.systemSsh === proc) { this.systemSsh = null diff --git a/src/main/ssh/ssh-relay-deploy.ts b/src/main/ssh/ssh-relay-deploy.ts index 56779b87f0f..2d1a1a446fc 100644 --- a/src/main/ssh/ssh-relay-deploy.ts +++ b/src/main/ssh/ssh-relay-deploy.ts @@ -1,8 +1,5 @@ import { join } from 'node:path' -/* eslint-disable max-lines -- Why: the relay-deploy module owns one cohesive - contract — version detection, install-locked deploy, native-deps probe, - relay launch, and GC — and splitting risks drift between the install - sequence and the GC's live-socket invariant. */ +/* eslint-disable max-lines -- Why: one cohesive contract (version detect, install-locked deploy, native-deps probe, launch, GC); splitting risks install/GC drift. */ import { existsSync } from 'node:fs' import { app } from 'electron' import type { SshConnection } from './ssh-connection' @@ -101,15 +98,7 @@ function execHostCommand( } /** - * Deploy the relay to the remote host and launch it. - * - * Steps: - * 1. Detect remote OS/arch via `uname -sm` - * 2. Check if correct relay version is already deployed - * 3. If not, SCP the relay package - * 4. Launch relay via exec channel - * 5. Wait for ORCA-RELAY sentinel on stdout - * 6. Return the transport (relay's stdin/stdout) for multiplexer use + * Deploy the relay to the remote host and launch it, returning the transport (relay's stdin/stdout) for multiplexer use. */ export async function deployAndLaunchRelay( conn: SshConnection, @@ -118,8 +107,7 @@ export async function deployAndLaunchRelay( relayInstanceId?: string ): Promise { let timeoutHandle: ReturnType - // Why: Promise.race does not cancel its loser. Stop a contended install-lock - // waiter so it cannot acquire and mutate the relay after this call times out. + // Why: Promise.race doesn't cancel its loser; abort so a contended install-lock waiter can't mutate the relay after this call times out. const deployAbortController = new AbortController() const timeoutPromise = new Promise((_resolve, reject) => { timeoutHandle = setTimeout(() => { @@ -145,12 +133,9 @@ export async function deployAndLaunchRelay( } /** - * Resolve the remote home, derive the versioned relay directory, and check - * whether the relay is already installed there. + * Resolve the remote home, derive the versioned relay dir, and check whether the relay is installed there. * - * Why: extracted so the deploy can run this chain concurrently with node-path - * resolution (the two are independent). Home and install-check stay sequential - * here because the install-check needs the resolved directory. + * Why: extracted to run concurrently with node-path resolution; home and install-check stay sequential because the check needs the resolved dir. */ async function resolveRemoteInstallState( conn: SshConnection, @@ -158,17 +143,14 @@ async function resolveRemoteInstallState( fullVersion: string, options?: { rethrowSessionLimitErrors?: boolean; signal?: AbortSignal } ): Promise<{ remoteHome: string; remoteRelayDir: string; alreadyInstalled: boolean }> { - // Why: SFTP does not expand `~`, so we must resolve the remote home - // explicitly with the host's native shell and normalize it before use. + // Why: SFTP doesn't expand `~`, so resolve the remote home explicitly via the host's native shell and normalize it. const remoteHome = normalizeRemoteHome( await execHostCommand(conn, hostPlatform, readRemoteHomeCommand(hostPlatform), { signal: options?.signal }), hostPlatform ) - // Why: we only interpolate $HOME into single-quoted shell strings later, so - // this validation only needs to reject obviously unsafe control characters. - // Allow spaces and non-ASCII so valid home directories are not rejected. + // Why: $HOME is only used inside single-quoted shell strings, so validation only rejects control chars — spaces and non-ASCII stay valid. if (!validateRemoteHome(remoteHome, hostPlatform)) { throw new Error(`Remote home is not a valid path: ${remoteHome.slice(0, 100)}`) } @@ -264,8 +246,7 @@ function isAbortError(err: unknown): boolean { } /** - * Detect the remote platform, resolve install state and node path, install the - * relay if it is not already present, then launch it and return the transport. + * Detect platform, resolve install state + node path, install if absent, launch, and return the transport. * Inner implementation wrapped by `deployAndLaunchRelay` with an overall timeout. */ async function deployAndLaunchRelayInner( @@ -289,8 +270,7 @@ async function deployAndLaunchRelayInner( if (!(err instanceof RelayDirectoryGcConflictError)) { throw err } - // Why: GC atomically moves the old install aside. Wait for its stable - // sibling claim to clear, then recompute install state from scratch. + // Why: GC atomically moves the old install aside; wait for its sibling claim to clear, then recompute install state. await waitForRelayGcClaimRelease(conn, err.remoteRelayDir, err.hostPlatform, deploySignal) } } @@ -321,19 +301,11 @@ async function deployAndLaunchRelayAttempt( `This may be a packaging issue — try reinstalling Orca.` ) } - // Why: read the content-hashed full version from the local build's .version - // file. Used as both the remote dir name and the wire-handshake version. - // Throws on missing/empty rather than silently falling back — see - // docs/ssh-relay-versioned-install-dirs.md "Data Flow: Upstream Error". + // Why: content-hashed version doubles as remote dir name and wire-handshake version; throws on missing rather than falling back (see docs/ssh-relay-versioned-install-dirs.md). const fullVersion = readLocalFullVersion(localRelayDir) onProgress?.('Checking existing relay...') - // Why: the remote-home -> install-check chain and node resolution are - // independent (both only need hostPlatform, not each other's results), yet - // each is a separate SSH exec round trip. Run them concurrently so the deploy - // pays one round trip instead of two for this phase. Most failures stay - // fail-fast; remotes that reject overlapping session channels retry the old - // sequential order so restrictive SSH servers keep working. + // Why: install-check and node resolution are independent; run concurrently to save a round trip, with sequential fallback for restrictive SSH servers. const { remoteHome, remoteRelayDir, alreadyInstalled, nodePath } = await resolveRelayBootstrapState(conn, hostPlatform, fullVersion, deploySignal) console.log(`[ssh-relay] Remote dir: ${remoteRelayDir}`) @@ -354,14 +326,11 @@ async function deployAndLaunchRelayAttempt( launchGcClaimToken = launchFence.gcClaimToken deploySignal?.throwIfAborted() } else { - // Why: serialize concurrent first-installs of the same version against - // each other via a host-native exclusive lock. The losing caller polls and either - // re-checks `alreadyInstalled` (now true) or steals a stale lock. + // Why: serialize concurrent first-installs via a host-native exclusive lock; the loser polls to re-check installed or steal a stale lock. await acquireInstallLock(conn, remoteRelayDir, hostPlatform, { signal: deploySignal }) ownsInstallLock = true try { - // Re-probe after acquiring the lock — a sibling installer may have - // finished while we were waiting. + // Re-probe after acquiring the lock — a sibling installer may have finished while we waited. if ( !(await isRelayAlreadyInstalled(conn, remoteRelayDir, hostPlatform, { signal: deploySignal @@ -384,18 +353,15 @@ async function deployAndLaunchRelayAttempt( ) console.log('[ssh-relay] Native deps installed') - // Why: mark complete but retain the lock until launch makes daemon - // liveness observable to cross-version GC. + // Why: mark complete but retain the lock until launch makes daemon liveness observable to cross-version GC. await finalizeInstall(conn, remoteRelayDir, hostPlatform, { signal: deploySignal, releaseLock: false }) } } catch (err) { - // Why: leave a partial install dir in place (no `.install-complete`) - // so the next deploy detects the partial and re-runs upload + install. - // Keep the lock if remote command termination was not confirmed; stale - // recovery is safer than overlapping a still-running npm process. + // Why: leave a partial install dir (no .install-complete) so the next deploy re-runs upload + install. + // Why: keep the lock if remote termination was unconfirmed — stale recovery beats overlapping a still-running npm. if (!isUnconfirmedSshCommandTermination(err)) { await abandonInstall(conn, remoteRelayDir, hostPlatform) ownsInstallLock = false @@ -421,23 +387,18 @@ async function deployAndLaunchRelayAttempt( ) launchLivenessObserved = true } finally { - // Why: older clients understand only the install lock. If launch never - // becomes live, retain it for stale recovery so their GC cannot race a - // concurrent caller that was waiting behind this owner. + // Why: older clients understand only the install lock; if launch never goes live, keep it so their GC can't race a caller waiting behind this owner. if (ownsInstallLock && launchLivenessObserved) { await abandonInstall(conn, remoteRelayDir, hostPlatform) } - // The detached start may outlive a timed-out SSH command. Preserve either - // fence on failed launch until stale recovery can prove the handoff ended. + // The detached start may outlive a timed-out SSH command; keep the fence on failed launch until stale recovery proves the handoff ended. if (launchGcClaimToken && launchLivenessObserved) { await releaseRelayGcClaimWithRetry(conn, remoteRelayDir, launchGcClaimToken, hostPlatform) } } console.log('[ssh-relay] Relay started successfully') - // Why: best-effort cleanup of unreferenced sibling version dirs. Errors - // are logged inside gcOldRelayVersions and never propagate, so a GC failure - // can never block the user from connecting. + // Why: best-effort GC of unreferenced sibling version dirs; errors are swallowed so a GC failure never blocks connecting. void gcOldRelayVersions(conn, remoteHome, remoteRelayDir, hostPlatform, { windowsNodePath: launched.nodePath, windowsSockNames: [relaySocketNameForInstanceId(relayInstanceId)] @@ -470,14 +431,12 @@ async function uploadRelay( ) } - // Create remote directory await execHostCommand(conn, hostPlatform, makeRemoteDirectoryCommand(hostPlatform, remoteDir), { signal }) await uploadDirectoryForConnection(conn, localRelayDir, remoteDir, hostPlatform, signal) - // Make the node binary executable if (!isWindowsRemoteHost(hostPlatform)) { await execHostCommand( conn, @@ -487,9 +446,7 @@ async function uploadRelay( ) } - // Why: write `.version` via SFTP rather than shell to avoid quoting issues - // with content-hashed version strings. The remote daemon reads this same - // file on startup so the wire-handshake validates against it. + // Why: write .version via SFTP not shell to avoid quoting content-hashed versions; the daemon reads it to validate the wire handshake. await writeRemoteFile( conn, hostPlatform, @@ -535,8 +492,7 @@ async function writeRemoteFile( try { await new Promise((resolve, reject) => { const ws = sftp.createWriteStream(remotePath) - // .once: a session 'error' arriving after we've already resolved/rejected - // would otherwise become an unhandled error and crash main. + // .once: a late session 'error' after resolve/reject would otherwise be unhandled and crash main. sftp.once('error', reject) ws.once('close', resolve) ws.once('error', reject) @@ -556,15 +512,13 @@ type RelayNativeDepName = keyof typeof RELAY_NATIVE_DEPS const RELAY_NATIVE_DEP_NAMES = Object.keys(RELAY_NATIVE_DEPS) as RelayNativeDepName[] const NATIVE_DEPS_MISSING_PREFIX = 'ORCA-NATIVE-DEPS-MISSING:' -// Why: npm 12 blocks dependency lifecycle scripts unless each exact package -// version is approved, even when ignore-scripts is explicitly disabled. +// Why: npm 12 blocks dependency lifecycle scripts unless each exact package version is approved, even with ignore-scripts disabled. const RELAY_NATIVE_DEP_SCRIPT_ALLOWLIST = Object.fromEntries( Object.entries(RELAY_NATIVE_DEPS).map(([name, version]) => [`${name}@${version}`, true]) ) function nativeDepsProbeJs(successToken: string): string { - // Why: node-pty's Windows wrapper defers loading conpty.node until first - // spawn, so require("node-pty") alone cannot prove the binding is healthy. + // Why: node-pty's Windows wrapper defers conpty.node until first spawn, so require("node-pty") alone can't prove the binding is healthy. const loadNodePty = 'require("node-pty"); require("node-pty/lib/utils").loadNativeModule(process.platform==="win32"&&Number(require("os").release().split(".")[2])>=18309?"conpty":"pty")' return `(()=>{const missing=[];try{${loadNodePty}}catch{missing.push("node-pty")}try{require("@parcel/watcher")}catch{missing.push("@parcel/watcher")}if(missing.length){console.log("${NATIVE_DEPS_MISSING_PREFIX}"+missing.join(","));process.exitCode=1}else{console.log(${JSON.stringify(successToken)})}})()` @@ -644,8 +598,7 @@ async function repairInstalledNativeDeps( throw err } if (!stillInstalled) { - // Why: GC may finish its rename before our lock attempt recreates the - // original path. Never trust probes made before this locked recheck. + // Why: GC may finish its rename before our lock recreates the path; never trust probes made before this locked recheck. await abandonInstall(conn, remoteDir, hostPlatform) throw new RelayDirectoryGcConflictError(remoteDir, hostPlatform) } @@ -655,16 +608,11 @@ async function repairInstalledNativeDeps( ? await acquireRelayLaunchGcFence(conn, remoteDir, hostPlatform, signal) : undefined if (initialProbe.available) { - // Why: even a healthy reconnect must stay fenced until launch liveness is - // observable; otherwise cross-version GC can rename after this probe. + // Why: even a healthy reconnect stays fenced until launch liveness is observable, or cross-version GC can rename after this probe. return { ownsInstallLock: lockResult === 'acquired', gcClaimToken } } - // Why: an already-installed relay can launch in degraded mode (fs/git/preflight - // still work; native-backed ops fail), so native-deps repair is best-effort: - // lock contention and repair failures must not abort the connection. Pre-repair - // this path used require.resolve (which passed for a present-but-unloadable - // binding), so a fatal repair here would be a straight regression. + // Why: an already-installed relay can launch degraded, so native-deps repair is best-effort — lock contention and failures must not abort the connection. console.warn(`[ssh-relay] Repairing missing native deps at ${remoteDir}`) if (lockResult === 'busy' || lockResult === 'error') { console.warn( @@ -673,8 +621,7 @@ async function repairInstalledNativeDeps( return { ownsInstallLock: false, gcClaimToken } } try { - // Why: older complete relay dirs were created before @parcel/watcher was - // installed. Re-probe under the lock so only one reconnect mutates the dir. + // Why: older complete relay dirs predate @parcel/watcher; re-probe under the lock so only one reconnect mutates the dir. const probe = await probeRequiredNativeDeps(conn, remoteDir, hostPlatform, nodePath, signal) if (!probe.available) { await installNativeDeps( @@ -691,9 +638,8 @@ async function repairInstalledNativeDeps( return { ownsInstallLock: true } } catch (err) { const terminationUnconfirmed = isUnconfirmedSshCommandTermination(err) - // Why: keep a confirmed-failure lock through degraded launch so GC cannot - // move the relay before liveness is visible. Unconfirmed remote mutation - // keeps its stale-recoverable lock beyond this connection. + // Why: hold a confirmed-failure lock through degraded launch so GC can't move the relay before liveness is visible. + // Why: unconfirmed remote mutation keeps its stale-recoverable lock beyond this connection. console.warn( `[ssh-relay] Native deps repair failed at ${remoteDir}; launching degraded: ${ err instanceof Error ? err.message : String(err) @@ -723,8 +669,7 @@ async function acquireRelayLaunchGcFence( if (!stillInstalled) { throw new RelayDirectoryGcConflictError(remoteDir, hostPlatform) } - // Why: a caller that cannot own the install lock still needs its own - // durable fence; never borrow another connection's lock through launch. + // Why: a caller without the install lock still needs its own durable fence; never borrow another connection's lock through launch. return token } catch (err) { await releaseRelayGcClaimWithRetry(conn, remoteDir, token, hostPlatform) @@ -732,14 +677,8 @@ async function acquireRelayLaunchGcFence( } } -// Why: node-pty and @parcel/watcher are native addons that can't be bundled by -// esbuild. They must be installed on the remote host against its Node.js version -// and OS so dynamic imports/require calls resolve from the relay dir. -// -// TODO(#1693): VS Code ships per-platform tarballs with node-pty pre-built -// from CI and skips `npm install` on the remote entirely. That approach -// eliminates the whole class of bugs around npm/compiler/network failures -// on the remote. Worth doing once we're past the immediate fix. +// Why: node-pty and @parcel/watcher are native addons esbuild can't bundle; install them on the remote against its Node/OS. +// TODO(#1693): ship per-platform tarballs with node-pty prebuilt from CI to skip remote npm install. async function installNativeDeps( conn: SshConnection, remoteDir: string, @@ -749,14 +688,9 @@ async function installNativeDeps( signal?: AbortSignal, resetDeps: RelayNativeDepName[] = [] ): Promise { - // Why: commandWithNodePath puts node's bin directory in PATH for npm's child processes. - // npm install runs node-pty's prebuild script (`node scripts/prebuild.js`) - // which spawns `node` as a child — if node isn't in PATH, that child - // fails with exit 127 even though we invoked npm via its full path. - // npm init -y rejects '+' in derived package names (content-hashed dir - // names like relay-0.1.0+abc123). Bypass it with a fixed minimal - // package.json. type:commonjs pins module resolution against Node default - // flips or a remote ~/.npmrc setting type=module. + // Why: node-pty's prebuild spawns `node` as a child, so node must be in PATH (commandWithNodePath) or it fails exit 127. + // Why: npm init -y rejects '+' in content-hashed dir names, so write a fixed minimal package.json instead. + // Why: type:commonjs pins module resolution against Node default flips or a remote ~/.npmrc type=module. const pkgJson = `${JSON.stringify({ name: 'orca-relay', version: '1.0.0', @@ -777,8 +711,7 @@ async function installNativeDeps( const installArgs = Object.entries(RELAY_NATIVE_DEPS) .map(([dep, version]) => shellEscape(`${dep}@${version}`)) .join(' ') - // Why: npm reports a present package as up to date even when one packaged - // native file was deleted. Reset only dependencies the probe found broken. + // Why: npm reports a present package as up to date even if a native file was deleted; reset only deps the probe found broken. const resetCommand = resetNativeDepsCommand(hostPlatform, resetDeps) const resetPrefix = resetCommand ? `${resetCommand}; ` : '' const command = isWindowsRemoteHost(hostPlatform) @@ -807,17 +740,12 @@ async function installNativeDeps( throw err } signal?.throwIfAborted() - // Don't write .install-complete on hard fail; reconnect retries on a - // partial install. Greppable token so user bug reports paste something - // searchable. + // Don't write .install-complete on hard fail so reconnect retries the partial install; greppable token aids bug reports. const msg = (err as Error).message console.warn( `[ssh-relay][NATIVE-DEPS-INSTALL-FAIL] npm install native deps failed at ${remoteDir} (${platform}): ${msg}` ) - // Why: on Linux node-pty has no prebuild and must compile, so a missing - // C/C++ toolchain is the dominant first-connect failure (#1693). Probe the - // remote and replace node-gyp's opaque `not found: make` with an actionable - // install hint instead of leaking the raw npm output to the user. + // Why: on Linux node-pty compiles, so a missing C/C++ toolchain is the dominant first-connect failure (#1693); probe to give an actionable install hint. if (platform.startsWith('linux') && shouldProbeBuildToolchainAfterNativeDepsFailure(msg)) { const toolchain = await probeBuildToolchain(conn, hostPlatform, signal) if (toolchain?.toolchainMissing) { @@ -831,8 +759,7 @@ async function installNativeDeps( let probe = await probeInstalledNativeDeps(conn, remoteDir, hostPlatform, nodePath, signal) if (!probe.available) { - // Why: npm treats an already-present package as up to date, so enabling - // lifecycle scripts on install cannot repair a binding skipped earlier. + // Why: npm treats an already-present package as up to date, so re-enabling lifecycle scripts on install can't repair a skipped binding. console.warn(`[ssh-relay] Rebuilding unloadable native deps at ${remoteDir}`) let rebuilt = false try { @@ -854,8 +781,7 @@ async function installNativeDeps( } } - // MISSING is non-fatal by design: the relay still serves fs/git/preflight; - // only native-backed operations fail on hosts that cannot build the addons. + // MISSING is non-fatal by design: the relay still serves fs/git/preflight; only native-backed ops fail on hosts that can't build the addons. if (!probe.available) { console.warn( `[ssh-relay][NPTY-MISSING] native deps installed but require() failed at ${remoteDir} (${platform}). stdout=${probe.output.trim().slice(-200)} stderr=${probe.stderr.trim().slice(-500)}` @@ -937,8 +863,7 @@ async function makeNodePtySpawnHelperExecutable( if (isWindowsRemoteHost(hostPlatform)) { return } - // SFTP doesn't preserve execute bits; node-pty's spawn-helper prebuild - // must be +x for posix_spawnp. + // SFTP doesn't preserve execute bits; node-pty's spawn-helper must be +x for posix_spawnp. await execHostCommand( conn, hostPlatform, @@ -959,8 +884,7 @@ async function probeInstalledNativeDeps( output: string stderr: string }> { - // require() catches unloadable installs (wrong arch, missing prebuild, or a - // skipped lifecycle script) that require.resolve() and test -d both miss. + // require() catches unloadable installs (wrong arch, missing prebuild, skipped lifecycle script) that require.resolve() and test -d miss. const PROBE_OK = 'ORCA-NPTY-PROBE-OK' const stderrFile = joinRemotePath(hostPlatform, remoteDir, '.npty-probe.stderr') const escapedStderr = shellEscape(stderrFile) @@ -1016,8 +940,7 @@ export function getLocalRelayCandidates(platform: RelayPlatform): string[] { candidates.push(join(process.env.ORCA_RELAY_PATH, platform)) } - // Why: electron-builder copies extraResources next to the app bundle, while - // app.getAppPath() points at app.asar in packaged builds. + // Why: electron-builder copies extraResources next to the app bundle, but app.getAppPath() points at app.asar in packaged builds. if (process.resourcesPath) { candidates.push(join(process.resourcesPath, 'relay', platform)) candidates.push(join(process.resourcesPath, 'app.asar.unpacked', 'out', 'relay', platform)) @@ -1041,13 +964,7 @@ async function launchRelay( relayInstanceId?: string, signal?: AbortSignal ): Promise<{ transport: MultiplexerTransport; nodePath: string; sockPath: string }> { - // Why: Phase 1 of the plan requires Node.js on the remote. We use the - // system `node` rather than bundling a node binary, keeping the relay - // package small (~100KB JS vs ~60MB with embedded node). - // Non-login SSH shells may not have node in PATH, so we source the - // user's profile to pick up nvm/fnm/brew PATH entries. - // Why: graceTimeSeconds originates from user-editable SshTarget config. - // Clamping to integer prevents shell injection if the type ever loosened. + // Why: graceTimeSeconds comes from user-editable SshTarget config; floor+clamp to an integer prevents shell injection if the type ever loosened. const requestedGraceTime = Math.floor(graceTimeSeconds ?? DEFAULT_SSH_RELAY_GRACE_PERIOD_SECONDS) const graceTime = requestedGraceTime === 0 @@ -1058,9 +975,7 @@ async function launchRelay( ) const escapedDir = shellEscape(remoteDir) const escapedNode = shellEscape(nodePath) - // Why: remoteRelayDir is shared by every Orca target for the same remote - // account. Hashing the target ID into the socket name prevents one target - // from attaching to another target's live relay. + // Why: remoteRelayDir is shared across Orca targets for one account; hashing the target ID into the socket name stops cross-target attach. const sockName = relaySocketNameForInstanceId(relayInstanceId) const sockFile = relayEndpointForHost(hostPlatform, remoteDir, sockName) const endpointDir = relayHookEndpointDirForHost(hostPlatform, remoteDir, sockFile) @@ -1094,10 +1009,7 @@ async function launchRelay( ) } - // Why: after an app restart a relay may still be running in its grace - // period with live PTY sessions. We check for its Unix socket and - // launch in --connect mode to bridge the new SSH channel to the - // existing relay process — preserving PTY state and scrollback. + // Why: after a restart the relay may still be alive in its grace period; --connect to its socket preserves PTY state and scrollback. try { const probeOutput = await execCommand( conn, @@ -1121,8 +1033,7 @@ async function launchRelay( '[ssh-relay] Socket reconnect failed, launching fresh relay:', err instanceof Error ? err.message : String(err) ) - // Why: stale socket from a crashed relay — remove it so the - // fresh launch can bind a new socket at the same path. + // Why: stale socket from a crashed relay — remove it so the fresh launch can bind at the same path. await execCommand(conn, `rm -f ${shellEscape(sockFile)}`, { signal }).catch( (cleanupErr) => { if (isUnconfirmedSshCommandTermination(cleanupErr)) { @@ -1141,41 +1052,21 @@ async function launchRelay( // Probe failed — fall through to fresh launch } - // Why: the relay must outlive the SSH connection so PTY sessions survive - // app restarts. nohup prevents SIGHUP death, ${shellEscape(logFile)} 2>&1 {}) launchChannel.on('error', () => {}) launchChannel.stderr.on('data', () => {}) launchChannel.stderr.on('error', () => {}) - // Why: the shell exits quickly (nohup ... &), but the SSH channel stays - // open until all child fds close. Explicitly closing it after the poll - // loop prevents channel accumulation across relay restarts, which would - // eventually hit the server's MaxSessions limit. + // Why: the SSH channel stays open until all child fds close; close it after the poll or channels accumulate and hit the server's MaxSessions limit. launchChannel.on('close', () => {}) - // Why: the backgrounded relay needs time to bind its Unix socket. We - // poll rather than sleep a fixed duration because remote host speed - // varies widely (CI vs. Raspberry Pi). - // Why: checking `test -S` only verifies the inode exists, not that the - // relay is listening. After a stale socket removal + fresh launch, the - // old inode can linger briefly. We probe with a connect-and-close to - // confirm the socket is actually accepting connections. + // Why: poll rather than fixed sleep — remote host speed varies widely (CI vs. Raspberry Pi). + // Why: test -S only proves the inode exists, not that the relay is listening; a connect-and-close confirms it accepts connections. const POLL_INTERVAL_MS = 200 const POLL_TIMEOUT_MS = 10_000 const pollStart = Date.now() @@ -1183,10 +1074,7 @@ async function launchRelay( try { while (Date.now() - pollStart < POLL_TIMEOUT_MS) { try { - // Why: node is guaranteed to exist on the remote (we just deployed - // the relay with it). Using it to probe the socket is more portable - // than python3/socat/perl which may not be installed. The socket - // path is passed as argv[1] to avoid shell quoting issues with -e. + // Why: probe via node (guaranteed present) not python3/socat/perl; pass the socket path as argv[1] to dodge -e quoting issues. const result = await execCommand( conn, `${escapedNode} -e 'var s=require("net").connect(process.argv[1]);s.on("connect",function(){s.destroy();process.stdout.write("READY")});s.on("error",function(){process.stdout.write("WAITING")})' ${shellEscape(sockFile)} 2>/dev/null || (test -S ${shellEscape(sockFile)} && echo READY || echo WAITING)`, @@ -1216,10 +1104,7 @@ async function launchRelay( throw new Error(`Relay failed to start within ${POLL_TIMEOUT_MS / 1000}s. Log:\n${logOutput}`) } - // Why: the backgrounded relay's stdout goes to a log file, not the exec - // channel. We connect via --connect which bridges this new channel's - // stdin/stdout to the relay's Unix socket — same path used for reconnect - // after app restart. + // Why: backgrounded relay's stdout goes to a log file, not the exec channel; --connect bridges this channel to its Unix socket. const channel = await conn.exec( `cd ${escapedDir} && ${escapedNode} relay.js --connect --sock-path ${shellEscape(sockFile)}`, { signal } @@ -1302,8 +1187,7 @@ async function rememberWindowsActiveRelayEndpoint( { signal } ).catch((err) => { signal?.throwIfAborted() - // Why: fallback pipe names are deterministic, so losing this marker does - // not force the next deploy to orphan an undiscoverable relay. + // Why: fallback pipe names are deterministic, so losing this marker won't orphan an undiscoverable relay. console.warn( `[ssh-relay] Failed to persist Windows active relay pipe at ${markerPath}: ${err instanceof Error ? err.message : String(err)}` ) @@ -1353,11 +1237,8 @@ async function launchWindowsRelay( err instanceof Error ? err.message : String(err) ) if (opts.reconnectFallback) { - // Why: an existing Windows named pipe cannot be unlinked like a Unix - // socket; use a deterministic fallback pipe so marker write failures - // remain recoverable on the next deploy. - // Keep activePipeMarkerPath keyed by the original target sock name; - // the marker records the active pipe for that target, fallback or not. + // Why: a Windows named pipe can't be unlinked like a Unix socket; a deterministic fallback pipe keeps the next deploy recoverable. + // Why: spread keeps activePipeMarkerPath at the original target sock name — the marker records that target's active pipe, fallback or not. launchOpts = { ...opts, ...opts.reconnectFallback } } } @@ -1489,8 +1370,7 @@ function windowsRelayLaunchCommand( errFile: string ): string { const relayScript = joinRemotePath(hostPlatform, remoteDir, 'relay.js') - // Why: Windows sshd kills the exec channel's process tree when the channel - // closes. WMI re-parents the detached relay so the named pipe stays alive. + // Why: Windows sshd kills the exec channel's process tree on close; WMI re-parents the detached relay to survive. const quoted = (value: string): string => `"${value.replace(/"/g, '\\"')}"` const relayCommandLine = [ quoted(nodePath), @@ -1502,8 +1382,7 @@ function windowsRelayLaunchCommand( quoted(sockPath), '--endpoint-dir', quoted(endpointDir), - // Why: in-process rotation owns relay.log (the tail-diagnostics target); - // the shell redirects remain for pre-JS boot/crash output. + // Why: --log-file owns rotation; shell redirects still capture pre-JS boot/crash output. '--log-file', quoted(logFile), `1>${quoted(logFile)}`, diff --git a/src/main/ssh/ssh-relay-native-deps-install.test.ts b/src/main/ssh/ssh-relay-native-deps-install.test.ts index f38a2b1065b..fdc7ff3e73e 100644 --- a/src/main/ssh/ssh-relay-native-deps-install.test.ts +++ b/src/main/ssh/ssh-relay-native-deps-install.test.ts @@ -1,8 +1,4 @@ -// Why: regression coverage for the install-probe contract. The original -// "node-pty is not available" bug shipped because every layer that should -// have caught it (chained shell, swallowing catch, resolve-only probe) was -// silent. Tests below pin the parts that, individually, would have caught -// it. +// Why: regression coverage for the install-probe contract — the "node-pty is not available" bug shipped because every guard layer was silent. import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -84,8 +80,7 @@ import type { SshConnection } from './ssh-connection' type SftpWriteCapture = { paths: string[] contents: Record - // Number of execCommand calls observed at the moment ws.end() ran for each - // captured path. Used to pin "package.json was written before npm install". + // execCommand call count observed when ws.end() ran, per path — pins "package.json written before npm install". execCallCountAtWrite: Record } @@ -115,8 +110,7 @@ function makeMockConnection(capture: SftpWriteCapture): SshConnection { } }) } - // Why: production code uses ws.once('close', ...). The 'once' wrapper - // delegates to the same handler-table as 'on' for the test mock. + // Why: production uses ws.once('close'); the mock delegates 'once' to the same handler table as 'on'. return Object.assign(stub, { once: stub.on }) }), end: vi.fn() @@ -141,17 +135,8 @@ function decodePowerShellCommand(command: string): string | null { return match ? Buffer.from(match[1], 'base64').toString('utf16le') : null } -// Exec call order under our mocks (deploy happy path): -// 1: uname 2: $HOME 3: mkdir remoteDir (uploadRelay) -// 4: chmod +x node 5: npm install 6: chmod prebuilds -// 7: probe (cd && node -e require) -// [8: cat stderr — only when probe stdout is MISSING (graceful path)] -// 8 or 9: rm probe-stderr (best-effort cleanup; runs whenever probe resolved) -// [next: npm rebuild → chmod → second probe when the first probe is MISSING] -// next: socket DEAD next: socket READY -// -// When the probe rejects (SSH channel close or cd-failure when the install -// dir vanished), the catch path skips both stderr-capture and the rm. +// Happy-path exec order: uname, $HOME, mkdir, chmod node, npm install, chmod prebuilds, probe, [cat stderr + rm if MISSING], [rebuild → chmod → re-probe if MISSING], DEAD, READY. +// When the probe rejects (SSH channel close or vanished install dir), the catch skips both stderr-capture and the rm. function makeExecResponses(opts: { npmInstall: 'ok' | { reject: string } // 'ok' : probe resolves with the sentinel; rm runs once @@ -159,19 +144,14 @@ function makeExecResponses(opts: { // 'dir-gone': probe rejects (cd-failure), exec rejects directly // { reject }: probe rejects with custom error (e.g. SSH channel) probe: 'ok' | 'missing' | 'dir-gone' | { reject: string } - // Override probe stdout for shell-noise pressure tests. If set, replaces - // the load-test stdout entirely (useful for testing pollution prefixes). + // Override probe stdout entirely for shell-noise/pollution-prefix pressure tests. probeStdoutOverride?: string - // Result after the automatic rebuild. Defaults to missing so legacy tests - // continue to exercise the final degraded-mode warning. + // Result after the automatic rebuild; defaults to missing so legacy tests still exercise the degraded-mode warning. repairProbe?: 'ok' | 'missing' - // Raw stdout for the build-toolchain probe that runs in installNativeDeps' - // catch when `npm install` rejects on Linux. Defaults to a fully-present - // toolchain so the original npm error propagates unchanged. + // Raw stdout for the toolchain probe in installNativeDeps' catch; defaults to a full toolchain so the original npm error propagates unchanged. toolchainProbe?: string }): ExecResponse[] { - // npm install failure aborts the deploy after the catch probes the remote's - // build toolchain — no chmod/probe/launch slots are reached. + // npm install failure aborts after the catch probes the toolchain; no chmod/probe/launch slots are reached. if (opts.npmInstall !== 'ok') { return [ '__ORCA_REMOTE_PLATFORM__ Linux x86_64', @@ -234,10 +214,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { beforeEach(() => { vi.clearAllMocks() - // Tests that throw mid-deploy leave unconsumed `mockResolvedValueOnce` - // entries queued. Without resetting, the next test's first await consumes - // a leaked response. clearAllMocks doesn't drop the queue (it only clears - // .mock.calls), so we explicitly mockReset. + // mockReset because clearAllMocks keeps queued mockResolvedValueOnce entries, so a leaked response would bleed into the next test. vi.mocked(execCommand).mockReset() vi.mocked(uploadDirectory).mockResolvedValue(undefined) sftpCapture.paths.length = 0 @@ -247,9 +224,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { for (const k of Object.keys(sftpCapture.execCallCountAtWrite)) { delete sftpCapture.execCallCountAtWrite[k] } - // Re-prime: factory mockReturnValue / mockResolvedValue survive - // clearAllMocks, so this is just defense-in-depth in case a test does its - // own resetAllMocks. + // Re-prime as defense-in-depth: factory mocks survive clearAllMocks but a test's own resetAllMocks would drop them. vi.mocked(parseUnameToRelayPlatform).mockReturnValue('linux-x64') vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(false) warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) @@ -285,8 +260,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { expect(parsed.name).toBe('orca-relay') expect(parsed.version).toBe('1.0.0') expect(parsed.private).toBe(true) - // Why: pin commonjs so a future Node default flip doesn't silently - // break `require('node-pty')`. + // Why: pin commonjs so a future Node default flip can't break require('node-pty'). expect(parsed.type).toBe('commonjs') expect(parsed.dependencies).toEqual({ '@parcel/watcher': '2.5.6', 'node-pty': '1.1.0' }) expect(parsed.allowScripts).toEqual({ @@ -300,10 +274,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { ) expect(npmInstallIdx).toBeGreaterThanOrEqual(0) expect(execCalls[npmInstallIdx]).toContain('--ignore-scripts=false') - // Pin actual ordering: number of execCommand calls observed at the moment - // ws.end() ran for package.json must be < the index of `npm install`. - // Catches a future refactor that fires SFTP-write and npm install via - // Promise.all (where the final-state assertions above would still pass). + // Pin write-before-install ordering to catch a Promise.all refactor where the final-state assertions above still pass. const writeObservedAt = sftpCapture.execCallCountAtWrite[pkgPath as string] expect(writeObservedAt).toBeLessThanOrEqual(npmInstallIdx) }) @@ -319,8 +290,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { await expect(deployAndLaunchRelay(conn)).rejects.toThrow(/npm ERR/) - // The crucial regression: `.install-complete` must NOT have been written. - // Previously the catch swallowed the throw and finalizeInstall ran anyway. + // Regression: previously the catch swallowed the throw and finalizeInstall ran anyway. expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled() const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? '')) @@ -333,8 +303,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { makeExecResponses({ npmInstall: { reject: 'gyp ERR! stack Error: not found: make' }, probe: 'ok', - // No HAVE lines → make/g++ absent; apk present → tailored hint must - // come from the remote probe rather than a hardcoded apt fallback. + // No HAVE lines + apk present: the tailored hint must come from the remote probe, not a hardcoded apt fallback. toolchainProbe: 'PKG apk' }) ) @@ -362,14 +331,12 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { makeExecResponses({ npmInstall: { reject: 'npm ERR! network ETIMEDOUT' }, probe: 'ok', - // Even if the host also lacks build tools, a network error should stay - // a network error instead of being relabeled as an install-tools fix. + // A network error must stay a network error even if the host also lacks build tools. toolchainProbe: 'PKG apt-get' }) ) - // The npm output is something else (network, registry), so surface the - // real error rather than a misleading "install build tools". + // Non-toolchain npm error (network/registry): surface the real error, not a misleading "install build tools". const error = await deployAndLaunchRelay(conn).catch((e: Error) => e) expect((error as Error).message).toContain('npm ERR! network ETIMEDOUT') expect((error as Error).message).not.toContain('build tools') @@ -405,11 +372,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { await deployAndLaunchRelay(conn) - // Probe failure is non-fatal by design (see docs/ssh-relay-versioned- - // install-dirs.md): relay still serves fs/git/preflight, only pty.spawn - // fails at runtime. Throwing here would loop reconnects forever on - // hosts where node-pty truly cannot build (Alpine without compiler, - // glibc too old). + // Probe failure is non-fatal by design (docs/ssh-relay-versioned-install-dirs.md): throwing would loop reconnects forever where node-pty can't build. const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? '')) expect(warnMessages.some((m) => m.includes('[ssh-relay][NPTY-MISSING]'))).toBe(true) @@ -439,9 +402,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { }) it('propagates an SSH-channel failure from the post-rebuild re-probe', async () => { - // Why: the rebuild itself degrades gracefully, but the verification probe - // after it must still surface transport death — conflating a dead channel - // with "native deps missing" would finalize a half-repaired install. + // Why: rebuild degrades gracefully, but the post-rebuild probe must surface transport death, else a dead channel finalizes a half-repaired install. const conn = makeMockConnection(sftpCapture) feed([ '__ORCA_REMOTE_PLATFORM__ Linux x86_64', // uname @@ -517,23 +478,18 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { await expect(deployAndLaunchRelay(conn)).rejects.toThrow(/SSH channel/) - // Pin that the rejection actually came from the PROBE call (not some - // earlier/later exec). Drift in slot ordering would otherwise let this - // test pass while exercising a different failure path. + // Pin the rejection to the PROBE call so slot-ordering drift can't pass this test via a different failure path. const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c) const probeCallIdx = execCalls.findIndex((c) => c.includes('require("node-pty")')) const npmInstallIdx = execCalls.findIndex( (c) => c.includes('npm install') && c.includes('node-pty') && c.includes('@parcel/watcher') ) expect(probeCallIdx, 'probe must have been invoked').toBeGreaterThanOrEqual(0) - // Probe must come strictly AFTER `npm install` — otherwise we'd be - // probing into an empty install dir and this whole failure mode - // wouldn't represent the real-world race. + // Probe must come strictly after npm install, else it probes an empty dir and doesn't represent the real-world race. expect(probeCallIdx).toBeGreaterThan(npmInstallIdx) const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? '')) - // Channel failure must NOT be conflated with "node-pty missing" or with - // "npm install failed". + // Channel failure must not be conflated with "node-pty missing" or "npm install failed". expect(warnMessages.some((m) => m.includes('[ssh-relay][NPTY-MISSING]'))).toBe(false) expect(warnMessages.some((m) => m.includes('[ssh-relay][NATIVE-DEPS-INSTALL-FAIL]'))).toBe( false @@ -548,17 +504,10 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { const conn = makeMockConnection(sftpCapture) feed(makeExecResponses({ npmInstall: 'ok', probe: 'dir-gone' })) - // The probe shape `cd ${dir} && (node -e ... || echo MISSING)` short- - // circuits on cd-failure (`&&`), so the whole exec rejects rather than - // resolving with the MISSING sentinel. Conflating "dir vanished" with - // "node-pty missing" would mark the version installed and strand the - // user in degraded mode forever. + // Why: cd-failure short-circuits the probe's && so it rejects instead of resolving MISSING; conflating "dir gone" with "node-pty missing" strands the user in degraded mode. await expect(deployAndLaunchRelay(conn)).rejects.toThrow(/cd:/) - // Pin that the rejection came from the probe slot specifically, not - // some earlier exec — otherwise a future refactor could move probe - // before npm install and this test would still pass for the wrong - // reason. + // Pin the rejection to the probe slot so a refactor moving probe before npm install can't pass this test for the wrong reason. const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c) const probeIdx = execCalls.findIndex((c) => c.includes('require("node-pty")')) const npmInstallIdx = execCalls.findIndex( @@ -584,16 +533,11 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { .mock.calls.map(([, c]) => c) .filter((c) => c.includes(`require("node-pty")`)) - // Why: the probe shape must invoke the deployed node binary against - // require('node-pty'). A weaker probe (test -d) could pass even when - // the native binding load is broken. + // Why: probe must require('node-pty') via the node binary; a weaker test -d passes even when the native binding load is broken. expect(probeCmds.length).toBeGreaterThan(0) expect(probeCmds[0]).toMatch(/node['"]?\s+-e/) - // Pin the full installNativeDeps exec sequence: npm install → chmod - // prebuilds → probe. A refactor that moves chmod-prebuilds after the - // probe would silently break spawn-helper bits; one that probes before - // npm install would test an empty dir. + // Pin order npm install → chmod prebuilds → probe: chmod-after-probe breaks spawn-helper bits; probe-before-install tests an empty dir. const all = vi.mocked(execCommand).mock.calls.map(([, c]) => c) const npmIdx = all.findIndex( (c) => c.includes('npm install') && c.includes('node-pty') && c.includes('@parcel/watcher') @@ -613,10 +557,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { it('matches the sentinel even with bashrc/MOTD noise prefixed to probe stdout', async () => { const conn = makeMockConnection(sftpCapture) - // Some remotes have customized .bashrc that prints to stdout on every - // non-interactive shell exec (corporate MOTD, NVM/conda init banners). - // Production uses .includes(PROBE_OK) with stderr redirected to a file, - // so noise on stdout BEFORE the sentinel must still resolve to OK. + // Why: noisy .bashrc/MOTD can prefix probe stdout; production uses .includes(PROBE_OK) so pre-sentinel noise still resolves OK. feed( makeExecResponses({ npmInstall: 'ok', @@ -644,8 +585,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { await deployAndLaunchRelay(conn) - // Absence of PROBE_OK is what triggers the warn, regardless of what - // appears around it. finalize still runs (degraded-mode by design). + // Absence of PROBE_OK triggers the warn regardless of surrounding noise; finalize still runs by design. const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? '')) expect(warnMessages.some((m) => m.includes('[ssh-relay][NPTY-MISSING]'))).toBe(true) expect(vi.mocked(finalizeInstall)).toHaveBeenCalledTimes(1) @@ -708,8 +648,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { }) it('includes the platform tuple in NPTY-MISSING and native install failure logs', async () => { - // Platform tuple lets bug reports be triaged for prebuild availability - // without asking the user to dig out their arch. + // Platform tuple lets bug reports be triaged for prebuild availability without asking the user for their arch. const conn = makeMockConnection(sftpCapture) feed(makeExecResponses({ npmInstall: 'ok', probe: 'missing' })) await deployAndLaunchRelay(conn) @@ -783,10 +722,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { }) it('launches an already-installed relay in degraded mode when repair throws', async () => { - // Why: a repair failure on a completed dir (e.g. offline/proxy-locked npm) - // must not block the connection — the relay still serves fs/git/preflight. - // Pre-fix this rethrew and aborted the whole deploy. The next reconnect - // retries, so a transient failure self-heals. + // Why: a repair failure on a completed dir must not block the connection — relay still serves fs/git/preflight; next reconnect retries. vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true) const conn = makeMockConnection(sftpCapture) feed([ @@ -849,8 +785,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { installSignal?.addEventListener( 'abort', () => { - // Mirrors execCommand's bounded close grace when ssh2 never - // confirms that the remote npm process stopped. + // Mirrors execCommand's bounded close grace when ssh2 never confirms npm stopped. setTimeout( () => reject( @@ -960,8 +895,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => { }) it.each(['busy', 'error'] as const)('launches degraded when lock is %s', async (lockResult) => { - // Why: lock contention/wedge must not block a completed relay from launching - // in degraded mode — repair is best-effort and we hold no lock to release. + // Why: lock contention/wedge must not block a completed relay from launching in degraded mode. vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true) vi.mocked(tryAcquireRelayRepairLock).mockResolvedValueOnce(lockResult) const conn = makeMockConnection(sftpCapture) diff --git a/src/main/ssh/ssh-relay-session.ts b/src/main/ssh/ssh-relay-session.ts index b9c41c84c1d..0428a4d3086 100644 --- a/src/main/ssh/ssh-relay-session.ts +++ b/src/main/ssh/ssh-relay-session.ts @@ -1,12 +1,5 @@ /* oxlint-disable max-lines */ -// Why: consolidates all relay lifecycle state (multiplexer, providers, abort -// controller, initialization flag) into a single class per SSH target. -// Previously this state was scattered across 5 module-level Maps/Sets in -// ssh.ts and ssh-relay-helpers.ts, with 3 separate code paths for initial -// connect, network-blip reconnect, and cleanup — each partially duplicating -// provider registration/teardown logic. This class is the single authority -// for relay session state, eliminating the class of bugs where one path -// forgets a step that another path handles. +// Why: single authority for all relay lifecycle state per SSH target (previously scattered across module Maps/Sets with duplicated paths). import type { BrowserWindow } from 'electron' import { deployAndLaunchRelay } from './ssh-relay-deploy' @@ -132,8 +125,7 @@ async function resolveRemoteGrokHome( ): Promise { const fallback = defaultRemoteGrokHome(remoteHome) try { - // Why: remote PTYs start login shells, so probe the same profile-derived - // environment instead of the relay service or local Electron environment. + // Why: remote PTYs start login shells, so probe that profile-derived env, not the relay service or local Electron env. const shellOutput = await execCommand(connection, "printenv SHELL || printf '/bin/sh\\n'", { timeoutMs: REMOTE_GROK_HOME_PROBE_TIMEOUT_MS }) @@ -141,8 +133,7 @@ async function resolveRemoteGrokHome( if (!shell?.startsWith('/') || hasControlCharacter(shell)) { return fallback } - // Why: this runs inside the user's actual login shell, which may be fish or - // tcsh. External commands avoid shell-specific variable/conditional syntax. + // Why: runs in the user's login shell (maybe fish/tcsh), so use external commands to avoid shell-specific syntax. const probe = `printenv GROK_HOME | head -c ${REMOTE_GROK_HOME_MAX_LENGTH + 1}` const output = await execCommand(connection, loginShellCommand(shell, probe), { wrapCommand: false, @@ -204,20 +195,11 @@ export class SshRelaySession { private mux: SshChannelMultiplexer | null = null private abortController: AbortController | null = null private muxDisposeCleanup: (() => void) | null = null - // Why: store the notification-handler disposer so teardownProviders can - // release it on reconnect/shutdown. Symmetric with muxDisposeCleanup; while - // the old mux's handler array is GC'd along with the mux today, holding the - // disposer is cheap insurance against future code that retains the old mux. + // Why: hold the notification-handler disposer so teardownProviders can release it on reconnect/shutdown (symmetric with muxDisposeCleanup). private muxNotificationCleanup: (() => void) | null = null - // Why: when the relay exec channel closes but the SSH connection stays - // up, the onStateChange reconnect path never fires. This callback lets - // ssh.ts wire up relay-level reconnect from outside the session. + // Why: onStateChange never fires when the relay channel closes but SSH stays up; this callback lets ssh.ts drive relay-level reconnect. private _onRelayLost: ((targetId: string) => void) | null = null - // Why: a wire-handshake mismatch is terminal — the daemon and client are at - // different versions, no amount of backoff retry will reconcile them. This - // separate callback lets ssh.ts surface the failure to the user and skip - // the relay-lost backoff loop entirely. Distinct from _onRelayLost because - // _onRelayLost expects a recoverable transport drop. + // Why: version mismatch is terminal, so it needs a separate callback from _onRelayLost (which expects a recoverable transport drop). private _onTerminalRelayError: | ((targetId: string, err: RelayVersionMismatchError) => void) | null = null @@ -271,11 +253,7 @@ export class SshRelaySession { return this._state } - // Why: TypeScript narrows _state after control-flow checks and then - // rejects comparisons like `this._state === 'disposed'` inside async - // methods where it "knows" the state was e.g. 'deploying'. But dispose() - // can mutate _state from another call stack between await points. This - // helper defeats narrowing so the disposed checks compile correctly. + // Why: dispose() can mutate _state across await points, so defeat TS's control-flow narrowing that would otherwise reject the 'disposed' check. private isDisposed(): boolean { return (this._state as RelaySessionState) === 'disposed' } @@ -320,9 +298,7 @@ export class SshRelaySession { mux.notify(SSH_RELAY_CONFIGURE_GRACE_TIME_METHOD, { graceTimeSeconds: 0 }) } - // Why: single entry point for relay setup — used by both initial connect - // and app-restart reconnect. Having one path eliminates the risk of - // forgetting a registration step. + // Why: single entry point for relay setup (initial connect + app-restart reconnect) so no path forgets a registration step. async establish(conn: SshConnection, graceTimeSeconds?: number): Promise { if (this._state !== 'idle') { throw new Error(`Cannot establish relay session in state: ${this._state}`) @@ -347,10 +323,7 @@ export class SshRelaySession { } : null - // Why: dispose() can fire during the await above (e.g. user clicks - // disconnect while relay is deploying). If so, the session is already - // cleaned up — creating a mux and registering providers would leak - // resources with no owner to dispose them. + // Why: dispose() can fire during the await above; if it did, creating a mux/providers now would leak with no owner to dispose them. if (this.isDisposed()) { const orphanMux = new SshChannelMultiplexer(transport) orphanMux.dispose() @@ -361,12 +334,7 @@ export class SshRelaySession { this.mux = mux const ownsAttempt = (): boolean => this.mux === mux && !this.isDisposed() - // Why: verify the relay is actually responsive before registering - // providers. In --connect mode the bridge may have already closed - // (e.g. the grace-period relay exited because it had no PTYs), and - // registerRelayRoots would silently swallow all mux errors, leaving - // the session in 'ready' state with a dead mux. A round-trip request - // here fails fast so doConnect() can report the real error. + // Why: round-trip the relay before registering providers so a closed --connect bridge fails fast instead of leaving a 'ready' session on a dead mux. await mux.request('session.resolveHome', { path: '~' }) const registered = await this.registerProviders(mux, ownsAttempt) @@ -377,11 +345,7 @@ export class SshRelaySession { throw new Error('Session disposed during establish') } - // Why: the mux's transport can close during registerProviders (e.g. - // the --connect bridge exits). registerRelayRoots swallows mux errors - // (notifications no-op when disposed, git.listWorktrees requests are - // try/caught), so establish would otherwise reach 'ready' with a dead - // mux. Checking isDisposed catches this silent failure. + // Why: registerProviders swallows mux errors, so an isDisposed check catches a transport that closed mid-registration before we reach 'ready'. if (mux.isDisposed()) { throw new Error('Relay connection lost during provider registration') } @@ -391,8 +355,7 @@ export class SshRelaySession { throw new Error('Session disposed during establish') } - // Why: explicit disconnect keeps PTY ownership so a later manual connect - // must reattach those remote PTYs through the fresh relay connection. + // Why: explicit disconnect keeps PTY ownership, so a later manual connect must reattach those remote PTYs. await this.reattachKnownPtys(ownsAttempt) if (!ownsAttempt()) { @@ -405,22 +368,12 @@ export class SshRelaySession { this.startPortScanning() this._onReady?.(this.targetId) } catch (err) { - // Why: if deployAndLaunchRelay succeeded but registerProviders threw - // partway through, the mux is live and some providers may be partially - // registered. teardownProviders cleans up everything so a subsequent - // establish() call starts from a clean slate. + // Why: registerProviders can throw with a live mux and partial registration — tear everything down so a retry starts clean. if (!this.isDisposed()) { this.teardownProviders('connection_lost') this._state = 'idle' } - // Why: a wire-handshake mismatch on the FIRST connect is also terminal - // — the deployed relay binary on disk does not match a still-running - // daemon (typically because a legacy daemon from before the - // versioned-dir change is still alive). Notify the terminal-error - // callback so ssh.ts surfaces an actionable message and the caller's - // catch path doesn't conflate this with a transient deploy failure. - // We still rethrow so doConnect's existing failure path runs (clean up - // the SSH connection); ssh.ts's handler is idempotent. + // Why: a version mismatch on first connect is terminal (deployed binary vs. a still-running legacy daemon); notify the callback but still rethrow. if (isRelayVersionMismatchError(err)) { console.warn( `[ssh-relay-session] Terminal relay version mismatch on initial connect for ${this.targetId}: ${err.message}` @@ -431,14 +384,9 @@ export class SshRelaySession { } } - // Why: network-blip reconnect path. Tears down the old provider stack, - // deploys a fresh relay, and re-attaches any PTYs that survived the - // relay's grace window. Guarded by an AbortController so overlapping - // reconnect attempts (fast connection flaps) cancel the stale one. + // Why: network-blip reconnect; AbortController-guarded so overlapping attempts from fast flaps cancel the stale one. async reconnect(conn: SshConnection, graceTimeSeconds?: number): Promise { - // Why: only allow reconnect from 'ready' or 'reconnecting'. Calling - // reconnect from 'deploying' would tear down a mux that establish() is - // concurrently using. 'idle' means no session was established yet. + // Why: reconnect only from 'ready'/'reconnecting' — from 'deploying' it would tear down a mux establish() is still using; 'idle' has no session yet. if (this._state !== 'ready' && this._state !== 'reconnecting') { return } @@ -451,8 +399,7 @@ export class SshRelaySession { this._state = 'reconnecting' this.currentConnection = conn - // Why: stop scanning before teardownProviders so the polling timer doesn't - // fire against a disposed multiplexer. + // Why: stop scanning before teardownProviders so the poll timer can't fire against a disposed multiplexer. this.stopPortScanning() await this.portForwardManager.removeAllForwards(this.targetId) this.broadcastEmptyLists() @@ -476,9 +423,7 @@ export class SshRelaySession { : null if (abortController.signal.aborted || this.isDisposed()) { - // Why: the relay is already running on the remote. Creating a temporary - // multiplexer and immediately disposing it sends a clean shutdown to the - // relay process so it doesn't linger until its grace timer expires. + // Why: relay is already running remotely — a throwaway mux we immediately dispose sends a clean shutdown so it doesn't linger until grace expires. const orphanMux = new SshChannelMultiplexer(transport) orphanMux.dispose() return @@ -492,9 +437,7 @@ export class SshRelaySession { !abortController.signal.aborted && !this.isDisposed() - // Why: same health check as establish() — verify the relay is - // responsive before registering providers so a dead --connect bridge - // fails fast instead of silently producing a dead mux. + // Why: same health check as establish() — round-trip the relay before registering providers so a dead --connect bridge fails fast. await mux.request('session.resolveHome', { path: '~' }) if (!ownsAttempt()) { if (!mux.isDisposed()) { @@ -515,10 +458,7 @@ export class SshRelaySession { throw new Error('Relay connection lost during provider registration') } - // Why: dispose() can fire during registerProviders or the attach loop - // below. If it did, providers and mux were already cleaned up by - // dispose() — but this.mux was reassigned above, so the new mux - // would leak. Clean it up and bail. + // Why: dispose() during registration/attach already cleaned up, but this.mux was reassigned above — clean up the new mux so it doesn't leak. if (!ownsAttempt()) { if (this.mux === mux) { this.teardownProviders('shutdown') @@ -540,16 +480,11 @@ export class SshRelaySession { this.startPortScanning() this._onReady?.(this.targetId) } catch (err) { - // Why: clean up the mux if it was created but registration failed - // partway through. Without this, the mux's keepalive/timeout timers - // continue running on a half-initialized session. + // Why: tear down a partially-registered mux so its keepalive/timeout timers don't keep running on a half-initialized session. if (this.abortController === abortController && !this.isDisposed()) { this.teardownProviders('connection_lost') } - // Why: a version-mismatch is terminal. Fire the typed callback so - // ssh.ts can surface a "please reconnect manually" notice and skip the - // relay-lost backoff loop entirely. We do NOT keep state at - // 'reconnecting' — there's no transient drop to recover from. + // Why: version-mismatch is terminal — fire the typed callback and drop out of 'reconnecting' since backoff retry can't reconcile it. if (isRelayVersionMismatchError(err)) { console.warn( `[ssh-relay-session] Terminal relay version mismatch for ${this.targetId}: ${err.message}` @@ -560,17 +495,12 @@ export class SshRelaySession { this._onTerminalRelayError?.(this.targetId, err) return } - // Why: stay in 'reconnecting' rather than reverting to 'ready', because - // the provider stack is already torn down. The SSH connection manager - // will fire another onStateChange when it reconnects again. + // Why: stay in 'reconnecting' (not 'ready') since the provider stack is torn down; the SSH manager will fire another onStateChange to retry. console.warn( `[ssh-relay-session] Failed to re-establish relay for ${this.targetId}: ${err instanceof Error ? err.message : String(err)}` ) if (this.abortController === abortController && !this.isDisposed()) { - // Why: non-not-found PTY attach failures are usually transient mux or - // relay transport failures. Treat them like relay loss so ssh.ts's - // bounded backoff retries instead of stranding the session forever in - // reconnecting. + // Why: treat non-not-found attach failures as relay loss so ssh.ts's bounded backoff retries instead of stranding the session in 'reconnecting'. this._onRelayLost?.(this.targetId) } } finally { @@ -586,8 +516,7 @@ export class SshRelaySession { } this.abortController?.abort() this.stopPortScanning() - // Why: fire-and-forget — nothing rebinds after dispose, so we don't - // need to wait for the OS to release ports. + // Why: fire-and-forget — nothing rebinds after dispose, so no need to await port release. void this.portForwardManager.removeAllForwards(this.targetId) this.broadcastEmptyLists() this.teardownProviders('shutdown') @@ -603,9 +532,7 @@ export class SshRelaySession { this.abortController?.abort() this.stopPortScanning() this.broadcastEmptyLists() - // Why: app/window disconnect is non-destructive for remote PTYs. The relay - // owns the grace timer, so Orca must unregister local providers without - // clearing PTY ownership needed for reattach. + // Why: window disconnect is non-destructive — unregister local providers but keep PTY ownership so reattach works (relay owns the grace timer). this.teardownProviders('connection_lost') this.store.markSshRemotePtyLeases(this.targetId, 'detached') this.currentConnection = null @@ -614,11 +541,7 @@ export class SshRelaySession { // ── Private ─────────────────────────────────────────────────────── - // Why: when the relay exec channel closes (e.g. --connect bridge exits, - // relay process crashes) but the SSH connection stays up, there is no - // automatic recovery — onStateChange only fires on SSH-level reconnects. - // This watcher detects relay-level channel loss and fires onRelayLost - // so ssh.ts can trigger session.reconnect() with the still-live SSH conn. + // Why: onStateChange only fires on SSH-level reconnects, so watch for relay-channel loss while SSH stays up and fire onRelayLost. private watchMuxForRelayLoss(mux: SshChannelMultiplexer): void { this.muxDisposeCleanup?.() this.muxDisposeCleanup = mux.onDispose((reason) => { @@ -631,8 +554,7 @@ export class SshRelaySession { }) } - // Why: shared by establish() and reconnect() — the exact same provider - // registration sequence, eliminating the duplication that caused bugs. + // Why: shared by establish() and reconnect() so both use the exact same registration sequence. private async registerProviders( mux: SshChannelMultiplexer, shouldContinue?: () => boolean @@ -655,10 +577,7 @@ export class SshRelaySession { try { await this.installRemoteOrcaCliLauncher() } catch (error) { - // Why: the remote `orca` CLI launcher is a convenience bridge. On session- - // limited remotes (MaxSessions=1) the relay bridge holds the only slot, - // so this raw-connection install can fail — that must not fail the - // whole connection, matching the managed-hook install above. + // Why: on MaxSessions=1 remotes the relay holds the only slot, so this raw-connection install can fail — don't fail the whole connection. console.warn( `[ssh-relay-session] remote orca CLI launcher install failed for ${this.targetId}: ${ error instanceof Error ? error.message : String(error) @@ -679,8 +598,7 @@ export class SshRelaySession { connection.usesSystemSshTransport?.() === true ? undefined : (options?: { signal?: AbortSignal }) => this.requireReadyConnection().sftp(options) - // Why: getHostPlatform() falls back to this.hostPlatform when the full - // remote CLI bridge env is incomplete, so path rules still match the host. + // Why: getHostPlatform() falls back to this.hostPlatform when bridge env is incomplete, so path rules still match the host. const hostPlatform = this.getHostPlatform() ?? undefined const fsProvider = new SshFilesystemProvider( this.targetId, @@ -728,11 +646,7 @@ export class SshRelaySession { }) } - // Why: the relay can inject ORCA_AGENT_HOOK_* env into SSH PTYs, but - // hook-script agents (Claude/Codex/Gemini/etc.) still need their config - // files on the remote host to call Orca's managed script. Install those - // configs before registering the PTY provider so newly spawned agent panes - // report status from their first prompt. + // Why: hook-script agents need remote config files (relay env alone isn't enough); install before the PTY provider so first prompts report status. private async installManagedHooksOnRemote(mux: SshChannelMultiplexer): Promise { if (!isRemoteAgentHooksEnabled() || !this.areAgentStatusHooksEnabled()) { return @@ -741,8 +655,7 @@ export class SshRelaySession { this.remoteCliBridgeEnv?.hostPlatform && isWindowsRemoteHost(this.remoteCliBridgeEnv.hostPlatform) ) { - // Why: managed hook installers currently emit POSIX hook scripts and paths. - // Windows remotes still get relay-injected env plus plugin overlays. + // Why: managed hook installers emit POSIX-only scripts/paths; Windows remotes rely on relay-injected env + plugin overlays instead. return } @@ -848,17 +761,7 @@ export class SshRelaySession { }) } - // Why: ship the OpenCode plugin / Pi extension source bodies to the relay - // so it can materialize overlay dirs and inject OPENCODE_CONFIG_DIR - // / PI_CODING_AGENT_DIR into spawn env. The strings change as we add agent - // events (recent additions: cursor, pi); pinning them to the relay binary - // would force a relay redeploy on every Orca update. See - // docs/design/agent-status-over-ssh.md §4 + §8 (commit #7). - // - // Best-effort: a -32601 from an older relay (no handler installed) is - // swallowed; the user just doesn't get OpenCode/Pi status reporting until - // they upgrade. Hook-script-based agents use a separate explicit remote - // installer flow because that mutates user-owned agent config files. + // Why: ship plugin/extension source from Orca so agent-event changes don't force a relay redeploy (agent-status-over-ssh.md §4/§8). Best-effort. private async installPluginsOnRelay(mux: SshChannelMultiplexer): Promise { if (!isRemoteAgentHooksEnabled() || !this.areAgentStatusHooksEnabled()) { return @@ -870,11 +773,7 @@ export class SshRelaySession { ompExtensionSource: getPiAgentStatusExtensionSource('omp') }) } catch (err) { - // Why: -32601 = older relay without the handler (treat as soft skip). - // CONNECTION_LOST / DISPOSED come from the multiplexer when it tears - // down mid-flight (routine on session shutdown / reconnect race) — not - // a real failure to surface; suppress to avoid log spam on every clean - // disconnect. + // Why: -32601 = older relay without the handler; CONNECTION_LOST/DISPOSED = routine mid-flight teardown — swallow both. const code = (err as { code?: unknown })?.code if (code === -32601 || code === 'CONNECTION_LOST' || code === 'DISPOSED') { return @@ -901,25 +800,12 @@ export class SshRelaySession { }) } - // Why: route the relay's `agent.hook` JSON-RPC notification into Orca's - // shared `agentHookServer` via `ingestRemote`. The wire envelope carries - // `connectionId: null` (the relay does not know Orca's local handle); we - // stamp the real value here from `this.targetId` so the renderer can drop - // in-flight events for connections that have torn down. After wiring is - // in place we kick off a request-driven replay so any cached payload from - // before the channel was up survives the reconnect — see §5 Path 3. - // - // The Orca-side mux's `notificationHandlers` is a flat array — each - // handler must filter by method name itself. + // Why: relay sends connectionId:null, so stamp this.targetId here so the renderer can drop events from torn-down connections. private wireUpAgentHookEvents(mux: SshChannelMultiplexer): void { if (!isRemoteAgentHooksEnabled()) { return } - // Why: capture the disposer so teardownProviders can release the - // notification handler symmetrically with muxDisposeCleanup. Even though - // the disposed mux's handler array is GC'd along with it today, retaining - // the disposer makes "registerProviders called twice on the same mux" - // safe by future-proofing against duplicate handler registration. + // Why: capture the disposer so teardownProviders can release this handler and re-wiring can't double-register it. this.muxNotificationCleanup?.() this.muxNotificationCleanup = mux.onNotification((method, params) => { if (method !== AGENT_HOOK_NOTIFICATION_METHOD) { @@ -946,11 +832,7 @@ export class SshRelaySession { if (typeof envelope.paneKey !== 'string') { return } - // Why: forward env/version verbatim so Orca's warn-once cross-build / - // dev-vs-prod diagnostics fire on remote events the same as on local - // ones — see docs/design/agent-status-over-ssh.md §3 ("Replay / - // version mismatch") and the relay's wire envelope at - // src/shared/agent-hook-relay.ts. + // Why: forward env/version verbatim so cross-build warn-once diagnostics fire on remote events too (agent-status-over-ssh.md §3). agentHookServer.ingestRemote( { paneKey: envelope.paneKey, @@ -979,12 +861,7 @@ export class SshRelaySession { ) }) - // Why: ask the relay to replay every cached paneKey it remembers. Issued - // *after* the handler is wired so the request-driven replay shape - // strictly trails our subscription on the dispatcher's single write - // callback. Best-effort: a relay that does not know the method - // (e.g. older relay binary) returns -32601; CONNECTION_LOST / DISPOSED - // arise from mux teardown mid-flight on routine reconnect/shutdown. + // Why: request replay of cached paneKeys only after the handler is wired, so replayed events can't arrive before we subscribe. Best-effort. void mux.request(AGENT_HOOK_REQUEST_REPLAY_METHOD).catch((err) => { const code = (err as { code?: unknown })?.code if (code === -32601 || code === 'CONNECTION_LOST' || code === 'DISPOSED') { @@ -993,9 +870,7 @@ export class SshRelaySession { if (mux.isDisposed()) { return } - // Why: a normal disconnect/teardown rejects the in-flight request with - // "Multiplexer disposed"; suppress the warn for that path so reconnect - // cycles aren't noisy. + // Why: suppress the warn when a normal teardown rejects the in-flight request, so reconnect cycles aren't noisy. if (mux.isDisposed()) { return } @@ -1020,8 +895,7 @@ export class SshRelaySession { if (reason === 'shutdown') { clearPtyOwnershipForConnection(this.targetId) } else { - // Why: handlers are detached above, so no late event can recreate a - // stamped status between this clear and reconnect replay. + // Why: handlers detached above, so no late event can re-stamp status between this clear and reconnect replay. agentHookServer.clearStatusEntriesForConnection(this.targetId) } @@ -1039,10 +913,7 @@ export class SshRelaySession { unregisterSshGitProvider(this.targetId) } - // Why: kept for back-compat with old relay binaries during the upgrade - // window — those still gate FS ops on registered roots. New relays no-op - // these notifications. Tracked for removal once the relay-version floor - // moves past the cutover (see docs/relay-fs-allowlist-removal.md). + // Why: back-compat for old relays that gate FS ops on registered roots; removable post-cutover (docs/relay-fs-allowlist-removal.md). private async registerRelayRoots(mux: SshChannelMultiplexer): Promise { const remoteRepos = this.store.getRepos().filter((r) => r.connectionId === this.targetId) @@ -1069,9 +940,7 @@ export class SshRelaySession { ) } - // Why: extracted so establish() and reconnect() share exactly the same - // event wiring. Previously forgetting to wire onReplay on one path - // caused silent terminal blanking after reconnect. + // Why: shared by establish()/reconnect() so both paths reset renderer lists the same way. private broadcastEmptyLists(): void { const win = this.getMainWindow() if (!win || win.isDestroyed()) { @@ -1091,17 +960,13 @@ export class SshRelaySession { if (!this.mux || this.isDisposed()) { return } - // Why: each scan walks /proc/*/fd on the remote host, so the scanner skips - // ticks entirely while no window can show the results (hidden to tray or - // minimized overnight) and rescans immediately when the window returns. + // Why: each scan walks /proc/*/fd remotely, so skip ticks while the window is hidden and rescan when it returns. const scanner = new PortScanner({ isWindowVisible: () => isMainWindowVisible(this.getMainWindow()), onWindowBecameVisible: onMainWindowBecameVisible }) this.portScanner = scanner - // Why: capture the scanner instance so that a late ports.detect callback - // from a previous relay session (before reconnect replaced it) is silently - // discarded instead of publishing stale results into the new session. + // Why: guard against a late ports.detect callback from a pre-reconnect scanner publishing stale results into the new session. scanner.startScanning(this.targetId, this.mux, (targetId, ports, platform) => { if (this.portScanner !== scanner) { return @@ -1131,11 +996,7 @@ export class SshRelaySession { if (!win || win.isDestroyed()) { return } - // Why: hidden-delivery gate parity with ipc/pty.ts — runtime ingestion - // above already consumed the chunk; gated renderer delivery is dropped - // and one out-of-band pty:modelRestoreNeeded signal latches - // model-restore-needed for reveal. Never an in-band pty:data sentinel: - // OSC-9999-only chunks legitimately strip to empty in the renderer. + // Why: hidden-delivery gate parity with ipc/pty.ts — latch model-restore out-of-band, never an in-band pty:data sentinel (OSC-9999-only chunks strip to empty). const store = this.store as { getSettings?: Store['getSettings'] } if (shouldDropHiddenRendererPtyData(payload.id, store.getSettings?.())) { const drop = recordHiddenRendererPtyDataDrop(payload.id, rawLength) @@ -1210,9 +1071,7 @@ export class SshRelaySession { .getSshRemotePtyLeases(this.targetId) .filter((lease) => lease.state !== 'terminated' && lease.state !== 'expired') const leasedPtyIds = activeLeases.map((lease) => lease.ptyId) - // Why: carry each lease's pane identity into the attach so the relay can - // reject cross-generation id collisions even between split panes in one tab. - // Older leases may lack leafId, so tabId remains the compatibility fallback. + // Why: pass pane identity so the relay can reject cross-generation id collisions; tabId falls back for pre-leafId leases. const expectedIdentityByPtyId = new Map( activeLeases .map((lease): [string, ExpectedPtyIdentity] | null => { @@ -1221,8 +1080,7 @@ export class SshRelaySession { }) .filter((entry): entry is [string, ExpectedPtyIdentity] => entry !== null) ) - // Why: after app restart, ptyOwnership is empty but durable SSH leases - // still describe remote PTYs that survived in the relay grace window. + // Why: after app restart ptyOwnership is empty, but durable SSH leases still describe grace-window survivors. const ptyIds = Array.from( new Set([ ...getPtyIdsForConnection(this.targetId).map((ptyId) => @@ -1274,9 +1132,7 @@ export class SshRelaySession { deletePtyOwnership(appPtyId) this.forwardedReattachReplayByPty.delete(appPtyId) this.store.markSshRemotePtyLease(this.targetId, ptyId, 'expired') - // Why: if the new relay cannot reattach this id, the remote backing - // process is gone. Tell the renderer so it clears stale pane bindings - // instead of keeping a cursor-only terminal. + // Why: reattach failure means the remote process is gone; tell the renderer to clear the stale pane. const win = this.getMainWindow() if (win && !win.isDestroyed()) { win.webContents.send('pty:exit', { id: appPtyId, code: -1 }) diff --git a/src/main/ssh/ssh-relay-versioned-install.ts b/src/main/ssh/ssh-relay-versioned-install.ts index b211fec4dd0..def8840ca57 100644 --- a/src/main/ssh/ssh-relay-versioned-install.ts +++ b/src/main/ssh/ssh-relay-versioned-install.ts @@ -1,12 +1,6 @@ -// Versioned-install plumbing for the remote relay. -// -// Why this exists: the relay used to install into a single shared directory -// (~/.orca-remote/relay-v0.1.0) which the deploy step would overwrite in place -// on every cross-version push. A daemon already loaded into memory then served -// new clients off rewritten on-disk code, producing protocol drift and a -// reconnect loop. We now install each (RELAY_VERSION + content-hash) bundle -// into its own directory and never mutate it after the install finishes, -// matching VS Code's `~/.vscode-server/bin//` layout. +// Versioned-install plumbing for the remote relay: each (RELAY_VERSION + content-hash) +// bundle installs into its own immutable dir (like VS Code's ~/.vscode-server/bin//) +// so an in-memory daemon never serves new clients off overwritten on-disk code. // // See: docs/ssh-relay-versioned-install-dirs.md @@ -44,17 +38,10 @@ import { windowsRelayPipePathsForSocketName } from './ssh-relay-endpoints' import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command' import { isSshSessionLimitError } from './ssh-session-limit-error' -// Why: the GC pass and the version-dir parser must agree on what counts as a -// relay install dir. Single source of truth for both. The pattern matches the -// new layout `relay-${RELAY_VERSION}+${hash}` and the legacy `relay-v${VERSION}` -// so the GC eventually drains the old layout once its daemons idle out. +// Single source of truth for GC and the version-dir parser; matches both the new and legacy relay-dir layouts. const RELAY_VERSION_DIR_REGEX = /^relay-(v?\d+\.\d+\.\d+(\+[0-9a-f]+)?)$/ -// Why: legacy dirs from before `.install-complete` was introduced (i.e. the -// `relay-v0.1.0` shape with no content-hash suffix). They are missing the -// install-complete sentinel by definition and need a separate liveness-only -// GC check so they actually drain after the legacy daemon dies, instead of -// living on remote disks forever. +// Legacy dirs predate `.install-complete`; they need a liveness-only GC check so they eventually drain. const LEGACY_RELAY_DIR_REGEX = /^relay-v\d+\.\d+\.\d+$/ const INSTALL_COMPLETE_NAME = '.install-complete' @@ -78,11 +65,9 @@ function execHostCommand( } /** - * Read the local relay's content-hashed version (e.g. "0.1.0+0a5fe134d020") - * from `${localRelayDir}/.version`. Throws on missing/empty so the caller - * never silently falls back to a path where a daemon from a different code - * generation may already be running — that fallback is the failure mode the - * versioned-install design exists to prevent. + * Read the local relay's content-hashed version (e.g. "0.1.0+0a5fe134d020") from + * `${localRelayDir}/.version`. Throws on missing/empty so the caller can't + * silently fall back to a path where a stale-generation daemon may be running. */ export function readLocalFullVersion(localRelayDir: string): string { const versionFile = join(localRelayDir, '.version') @@ -119,11 +104,9 @@ export function computeRemoteRelayDir( } /** - * Probe whether a fully-installed relay already exists at remoteRelayDir. - * - * "Fully installed" means: the directory contains relay.js, its isolated - * relay-watcher.js child, and the .install-complete sentinel written at the - * end of a successful install. Missing artifacts force a complete re-deploy. + * Probe whether a fully-installed relay exists at remoteRelayDir — meaning + * relay.js, its relay-watcher.js child, and the .install-complete sentinel are + * all present. Missing artifacts force a complete re-deploy. */ export async function isRelayAlreadyInstalled( conn: SshConnection, @@ -149,9 +132,9 @@ export async function isRelayAlreadyInstalled( } /** - * Mark the install as complete, then normally release the lock. Deploy keeps - * the lock through first launch so cross-version GC cannot move the directory - * between finalization and daemon liveness becoming observable. + * Mark the install complete, then normally release the lock. Deploy keeps the + * lock through first launch so cross-version GC can't move the dir before daemon + * liveness is observable. */ export async function finalizeInstall( conn: SshConnection, @@ -173,9 +156,8 @@ export async function finalizeInstall( } /** - * Release the install lock without writing the completion sentinel. Called - * from the failure path so the dir remains a recoverable partial that the - * next deploy detects (alreadyInstalled=false) and re-runs upload+install. + * Release the install lock without writing the completion sentinel, leaving the + * dir as a recoverable partial the next deploy re-installs. */ export async function abandonInstall( conn: SshConnection, @@ -187,17 +169,9 @@ export async function abandonInstall( } /** - * Garbage-collect old version directories. Removes a sibling dir under - * `${remoteHome}/${RELAY_REMOTE_DIR}/` only if ALL of: - * - * - it matches the relay-version-dir regex (allowlist) - * - it is NOT the current version dir - * - it has no live `relay-*.sock` (pgrep + connectability probe) - * - it contains `.install-complete` (a fully-installed dir, not a partial) - * - it does NOT contain `.install-lock` (no in-progress install) - * - * Best-effort: any error is logged and swallowed; GC must never block the - * user from connecting. + * Garbage-collect old version directories: remove an idle, fully-installed, + * unlocked sibling version dir (never the current one). Best-effort — errors + * are swallowed so GC never blocks the user from connecting. */ export async function gcOldRelayVersions( conn: SshConnection, @@ -242,8 +216,7 @@ export async function gcOldRelayVersions( kept.push(name) continue } - // Why: the claim is a sibling, so it survives moving/deleting the - // candidate and lets installers back out before mutating the old path. + // Why: the claim is a sibling, so it survives moving/deleting the candidate and lets installers back out first. const gcClaimToken = await tryAcquireRelayGcClaim(conn, dir, host) if (!gcClaimToken) { kept.push(name) @@ -252,8 +225,7 @@ export async function gcOldRelayVersions( let preserveGcClaim = false let gcClaimReleaseNeeded = true try { - // Recheck under the stable claim. New installers probe the claim both - // before and after creating their in-tree lock, closing both orders. + // Recheck under the stable claim; installers probe it before and after creating their lock, closing both orders. if (!(await isCandidateSafeToRemove(conn, dir, name, host, options))) { kept.push(name) continue @@ -268,8 +240,7 @@ export async function gcOldRelayVersions( kept.push(name) continue } - // Once renamed, a fresh install at the original path is isolated from - // deletion of the tombstone, so the sibling claim can be released. + // Once renamed, a fresh install at the original path is isolated from the tombstone's deletion, so release the claim. const release = await releaseRelayGcClaimWithRetry(conn, dir, gcClaimToken, host) gcClaimReleaseNeeded = release === 'unknown' await execHostCommand(conn, host, removeRemoteTreeCommand(host, tombstone)) @@ -326,22 +297,14 @@ async function isCandidateSafeToRemove( const locked = lockState === 'LOCKED' if (locked) { - // Why: a locked dir is normally unsafe to remove — but a STALE lock - // (remote age older than INSTALL_LOCK_STALE_MS) means the previous installer - // crashed and is never coming back. If the dir also has the - // .install-complete sentinel (touch succeeded but the rm-lock at the - // end of finalizeInstall failed), removing the dir is safe — no - // installer is racing us, and the daemon (if any) keeps running off - // its already-loaded code regardless of disk state. + // Why: stale lock = crashed installer; finalize can leave a dir .install-complete yet locked (lock-rm failed), so it's reclaimable. if (!(await isRelayInstallLockStale(conn, lockDir, host))) { return false } process.stderr.write?.(`[ssh-relay] GC: lock at ${lockDir} is stale; treating as recoverable\n`) } - // Legacy dirs (relay-v0.1.0) predate .install-complete. Skip the sentinel - // check for them and rely solely on the live-socket probe — that's the - // only signal we have that a legacy daemon is still serving clients. + // Legacy dirs predate .install-complete; skip the sentinel and rely on the live-socket probe alone. if (!isLegacy) { const completePath = joinRemotePath(host, dir, INSTALL_COMPLETE_NAME) const completeProbe = await execHostCommand( @@ -372,10 +335,7 @@ async function hasLiveRelaySocket( } ): Promise { try { - // Why: `ls -1 dir/relay-*.sock 2>/dev/null` lists socket files. For each, - // we test -S to confirm it's a socket inode. We do NOT attempt to open - // the socket here — `test -S` is sufficient for the GC decision and a - // connect-and-close probe would race with a daemon that's about to idle. + // Why: `test -S` only — a connect-and-close probe would race with a daemon about to idle. const windowsOptions = isWindowsRemoteHost(host) && options?.windowsNodePath ? { From 0f7250879ae3791512e3d00095aba91f797c0dec Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:18:32 -0700 Subject: [PATCH 129/137] refactor(comments): slim verbose comments in renderer state (store/hooks/lib) (#9545) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collapse multi-line explanatory comment blocks into single-line "why" statements per AGENTS.md ("Document the Why, Briefly"): drop restatements of the code and mechanism narration; keep the non-obvious reason, external refs, and directives. Comments-only — verified no code changed via a Babel/esbuild comment-strip token-equality gate against origin/main; typecheck and oxlint clean. Area: renderer — store, hooks, lib, runtime. 36 files changed, 1673 insertions(+), 5677 deletions(-). Co-authored-by: Orca --- src/renderer/src/App.tsx | 562 ++++------------ .../src/hooks/useAutoAckViewedAgent.ts | 101 +-- src/renderer/src/hooks/useComposerState.ts | 427 +++--------- .../src/hooks/useEditorExternalWatch.ts | 220 ++----- src/renderer/src/hooks/useIpcEvents.ts | 491 ++++---------- src/renderer/src/lib/agent-status.test.ts | 81 +-- src/renderer/src/lib/agent-status.ts | 59 +- .../src/lib/launch-agent-in-new-tab.ts | 94 +-- .../pane-terminal-output-scheduler.ts | 176 ++--- .../terminal-arabic-shaping-joiner.ts | 92 +-- src/renderer/src/lib/telemetry.ts | 65 +- src/renderer/src/lib/use-tab-agent.ts | 122 +--- src/renderer/src/lib/workspace-session.ts | 88 +-- src/renderer/src/lib/worktree-activation.ts | 132 +--- src/renderer/src/lib/worktree-status.ts | 69 +- .../src/runtime/sync-runtime-graph.ts | 93 +-- .../src/runtime/web-runtime-session.ts | 75 +-- .../src/runtime/web-session-tabs-sync.ts | 164 ++--- src/renderer/src/store/slices/agent-status.ts | 476 +++----------- src/renderer/src/store/slices/browser.ts | 124 +--- src/renderer/src/store/slices/diffComments.ts | 110 +--- src/renderer/src/store/slices/editor.ts | 484 +++----------- src/renderer/src/store/slices/github.ts | 497 ++++---------- src/renderer/src/store/slices/repos.ts | 190 ++---- .../src/store/slices/store-cascades.test.ts | 69 +- .../slices/store-session-cascades.test.ts | 112 +--- src/renderer/src/store/slices/tabs.test.ts | 125 +--- src/renderer/src/store/slices/tabs.ts | 180 ++--- .../store/slices/terminals-hydration.test.ts | 57 +- src/renderer/src/store/slices/terminals.ts | 615 ++++-------------- src/renderer/src/store/slices/ui.ts | 280 ++------ .../src/store/slices/worktree-nav-history.ts | 65 +- .../src/store/slices/worktrees.test.ts | 84 +-- src/renderer/src/store/slices/worktrees.ts | 543 ++++------------ src/renderer/src/web/web-preload-api.ts | 141 ++-- src/renderer/src/web/web-runtime-client.ts | 87 +-- 36 files changed, 1673 insertions(+), 5677 deletions(-) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 977b32d30f6..faf1b7bb9ba 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -195,18 +195,13 @@ import { subscribeBackgroundTerminalWorktreeMountRequests } from './components/terminal/background-terminal-worktree-mount' -// Why: agents alive during a hard kill (crash, forced update install) need a -// reasonably fresh resume record on disk; one minute bounds the lost window -// without measurable per-tick cost (the capture skips unchanged records). +// Why: bound the resume-record loss window on a hard kill to ~1 min; capture skips unchanged records so per-tick cost is negligible. const SLEEPING_AGENT_RESUME_CAPTURE_INTERVAL_MS = 60_000 const isMac = navigator.userAgent.includes('Mac') const isWindows = !isMac && navigator.userAgent.includes('Windows') const shortcutPlatform: NodeJS.Platform = isMac ? 'darwin' : isWindows ? 'win32' : 'linux' -// Why: Windows and Linux both run with the native title bar removed (Windows -// via titleBarStyle: 'hidden', Linux via frame: false), so the renderer draws -// its own logo/menu anchor and min/max/close controls on both. Paired web -// clients run in a browser tab, so they must not render desktop window chrome. +// Why: Windows and Linux remove the native title bar so the renderer draws its own chrome; paired web clients run in a browser tab and must not. const hasCustomTitleBar = shouldRenderDesktopWindowChrome({ platform: shortcutPlatform, isWebClient: isPairedWebClientWindow() @@ -229,9 +224,7 @@ function getKeybindingContext(target: EventTarget | null): KeybindingContext { : 'app' } -// Abstraction over a real KeyboardEvent and a synthetic double-tap gesture so a -// single dispatch path serves both. KeybindingInput-compatible (key/code + -// modifier flags) so it flows straight into keybindingMatchesAction. +// Abstraction over a real KeyboardEvent and a synthetic double-tap gesture so one dispatch path serves both; KeybindingInput-compatible. type ShortcutDispatchInput = { key?: string code?: string @@ -245,16 +238,11 @@ type ShortcutDispatchInput = { preventDefault: () => void } -// Why: Windows ('hidden' titleBarStyle) and Linux (frame: false) both remove -// the native OS title bar, so we render our own minimize/maximize/close -// buttons. These SVG icons match the Fluent/Win11 style: thin 10×10 paths on a -// 40×30 hit area. +// Why: Windows and Linux both remove the native title bar, so we render our own min/max/close buttons (Fluent/Win11-style SVGs). function WindowControls(): React.JSX.Element { const [maximized, setMaximized] = useState(false) useEffect(() => { - // Why: window:maximize-changed only fires on transitions, so a window - // restored to a maximized state at startup would render the wrong icon - // until the user first clicks the button. Seed from main on mount. + // Why: maximize-changed only fires on transitions; seed from main on mount so a startup-maximized window shows the right icon. let cancelled = false void window.api.ui.isMaximized().then((value) => { if (!cancelled) { @@ -302,10 +290,7 @@ function WindowControls(): React.JSX.Element {

    - {/* aria-label says "Dismiss" — the action persists silent opt-in, - not just hides the UI. */} + {/* aria-label says "Dismiss" but this persists silent opt-in, not just hides the UI. */}
    ) : filesUnavailable && files.length === 0 ? ( - // Why: the file fetch failed (rate limit, auth, unresolved - // remote); offer a retry instead of implying the PR is empty. + // Why: fetch failed (rate limit/auth/unresolved remote); offer retry instead of implying the PR is empty.
    {translate( diff --git a/src/renderer/src/components/TaskPage.tsx b/src/renderer/src/components/TaskPage.tsx index 727c0a44970..9d08575e625 100644 --- a/src/renderer/src/components/TaskPage.tsx +++ b/src/renderer/src/components/TaskPage.tsx @@ -1,7 +1,4 @@ -/* eslint-disable max-lines -- Why: the tasks page keeps the repo selector, -task source controls, and GitHub task list co-located so the wiring between the -selected repo, the task filters, and the work-item list stays readable in one -place while this surface is still evolving. */ +/* eslint-disable max-lines -- Why: repo selector, task-source controls, and task list stay co-located so their wiring reads in one place. */ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' @@ -512,10 +509,7 @@ function getTaskPageRepoCacheInput(repo: Repo): { } } -// Why: the sticky header's bg must be opaque (GITHUB_TASK_ROW_SURFACE_CLASS, not -// bg-muted/50) or vertically-scrolled rows bleed through it. These left-sticky -// cells additionally need a ::before gap-cover so horizontally-scrolled header -// columns don't show through the row's px-3 padding strip. +// Why: sticky header bg must be opaque or scrolled rows bleed through; the ::before gap-cover keeps horizontally-scrolled columns off the px-3 padding strip. const GITHUB_TASK_STICKY_ID_HEADER_CLASS = cn( 'sticky left-3 z-30 before:absolute before:-left-3 before:top-0 before:bottom-0 before:w-3 before:bg-inherit', GITHUB_TASK_ROW_SURFACE_CLASS @@ -550,8 +544,7 @@ function isPRFocusedTaskView(preset: TaskViewPresetId | null, query: string): bo } function normalizeGitHubTaskPreset(preset: TaskViewPresetId | null | undefined): TaskViewPresetId { - // Why: the split Issues/PRs tabs no longer have a mixed "All" view, so - // legacy saved defaults should land on the first tab instead of mixing rows. + // Why: the split Issues/PRs tabs dropped the mixed "All" view, so legacy saved defaults land on the first tab instead of mixing rows. return !preset || preset === 'all' ? 'issues' : preset } @@ -576,9 +569,7 @@ function scopeGitHubTaskSearch(query: string, kind: GitHubTaskKind): string { return `${inferredKind === 'prs' ? 'is:pr' : 'is:issue'} ${trimmed}` } -// Why: Intl.RelativeTimeFormat allocation is non-trivial, and previously we -// built a new formatter per work-item row render. Hoisting to module scope -// means all rows share one instance — zero per-row allocation cost. +// Why: Intl.RelativeTimeFormat allocation is non-trivial; hoist to module scope so all rows share one instance instead of allocating per render. const relativeTimeFormatter = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' }) function formatRelativeTime(input: string): string { @@ -1149,8 +1140,7 @@ function GHStatusCell({ const resolvedStatusStateDraft = resolveTaskPageGitHubStatusStateDraft(statusStateDraft, item) if (resolvedStatusStateDraft !== statusStateDraft) { - // Why: item rows can refresh from the GitHub cache while this cell is still - // mounted; reconcile before paint instead of showing one stale status frame. + // Why: item rows can refresh from the cache while this cell is mounted; reconcile before paint to avoid one stale status frame. setStatusStateDraft(resolvedStatusStateDraft) } const localState = resolvedStatusStateDraft.localState @@ -1181,8 +1171,7 @@ function GHStatusCell({ updateLocalState(newState) patchWorkItem(item.id, { state: newState }, item.repoId, { sourceContext }) const target = getActiveRuntimeTarget(sourceSettings) - // Why: issue rows can be sourced by owner/repo URL instead of the local - // repo context; slug-aware writes preserve close reasons and duplicates. + // Why: issue rows can be sourced by owner/repo URL, not local repo context; slug-aware writes preserve close reasons and duplicates. const updatePromise = parsedOwnerRepo ? target.kind === 'environment' ? callRuntimeRpc<{ ok?: boolean; error?: { message?: string } | string }>( @@ -1521,9 +1510,7 @@ function ReviewChipAvatar({ reviewer: GitHubPRPrimaryReviewer | null }): React.JSX.Element { if (reviewer?.login) { - // Why: `gh pr list --json reviewRequests` can return only logins. Prefer the - // API avatar_url so GHE renders; GitHubUserAvatar falls back to the login URL - // and then an initials placeholder when no avatar loads. See #8784. + // Why: gh pr list --json reviewRequests can return only logins; prefer API avatar_url so GHE renders (falls back to login URL, then initials). See #8784. return ( => { - // Close the popover immediately so the UI feels responsive; the GitHub - // request/remove runs in the background and toasts on completion. + // Close the popover immediately for responsiveness; the GitHub request/remove runs in the background and toasts on completion. setOpen(false) setReviewerInput('') await (selectedReviewerLogins.has(reviewer.login.toLowerCase()) @@ -2929,9 +2914,7 @@ function PRMergeCell({ ) } -// Why: builds the page number array with ellipsis gaps, matching GitHub's -// pagination pattern: always show first page, last page, and a window of -// pages around the current page with "..." gaps between distant ranges. +// Builds the page-number array with ellipsis gaps: first, last, and a window around the current page. function getPageNumbers(current: number, total: number): (number | 'ellipsis')[] { if (total <= 9) { return Array.from({ length: total }, (_, i) => i) @@ -3035,20 +3018,14 @@ function PaginationBar({ ) } -// Why: type-guard predicate used to filter `perRepoSourceState` down to rows -// whose issue-source and PR-source slugs differ. Hoisted to module scope so -// the predicate isn't re-allocated on every TaskPage render. +// Why: hoisted to module scope so the type-guard predicate isn't re-allocated on every TaskPage render. const hasDivergentSources = ( s: TaskPageRepoSourceState ): s is TaskPageRepoSourceState & { sources: { issues: GitHubOwnerRepo; prs: GitHubOwnerRepo } } => !!s.sources?.issues && !!s.sources.prs && !sameGitHubOwnerRepo(s.sources.issues, s.sources.prs) -// Why: the selector keeps rendering even after the user picks 'upstream' (which -// makes effective `sources.prs` point at upstream). Raw-candidate divergence is the -// right render gate — a repo that has an `upstream` remote pointing somewhere -// different from origin is always a candidate for the toggle, regardless of -// the current effective preference. +// Why: gate on raw origin/upstream candidate divergence, not effective sources, so the toggle keeps rendering after the user picks 'upstream'. const hasUpstreamCandidateDivergence = ( s: TaskPageRepoSourceState ): s is TaskPageRepoSourceState & { @@ -3081,10 +3058,7 @@ export default function TaskPage(): React.JSX.Element { const fetchPRChecks = useAppStore((s) => s.fetchPRChecks) const getCachedWorkItems = useAppStore((s) => s.getCachedWorkItems) const setIssueSourcePreference = useAppStore((s) => s.setIssueSourcePreference) - // Why: bumped by `setIssueSourcePreference` after cache eviction so the - // fetch effect below re-runs and repopulates work-items against the new - // source. Eviction alone isn't enough because the effect's deps don't - // include `workItemsCache`. + // Why: bumped after cache eviction to re-run the fetch effect — eviction alone won't, since its deps don't include workItemsCache. const workItemsInvalidationNonce = useAppStore((s) => s.workItemsInvalidationNonce) const linearStatus = useAppStore((s) => s.linearStatus) const linearStatusChecked = useAppStore((s) => s.linearStatusChecked) @@ -3135,11 +3109,7 @@ export default function TaskPage(): React.JSX.Element { const submitShortcutLabel = getScreenSubmitShortcutLabel() const eligibleRepos = useMemo(() => repos.filter((repo) => isGitRepoKind(repo)), [repos]) - // Why: initial selection resolution honors (1) an explicit preselection from - // the caller, (2) the persisted defaultRepoSelection (null = sticky-all, - // array = curated subset, empty after filter = fall back to all), (3) fall - // back to "all eligible". An explicit preselection wins so "open tasks for - // this specific repo" entry points still land on a single-repo view. + // Why: initial selection precedence — explicit preselection > persisted defaultRepoSelection > all eligible; preselection wins so "open tasks for this repo" lands single-repo. const resolvedInitialSelection = useMemo>(() => { const preferred = pageData.preselectedRepoId if (preferred && eligibleRepos.some((repo) => repo.id === preferred)) { @@ -3151,9 +3121,7 @@ export default function TaskPage(): React.JSX.Element { if (filtered.length > 0) { return normalizeTaskRepoSelection(eligibleRepos, new Set(filtered)) } - // Why: empty after filtering (e.g. all persisted repos were removed) - // falls through to the automatic default so the page never renders with - // an empty selection — see the multi-combobox invariant. + // Why: empty after filtering (all persisted repos removed) falls through to the automatic default so the page never renders an empty selection. } return getDefaultTaskRepoSelection(eligibleRepos) }, [eligibleRepos, pageData.preselectedRepoId, settings?.defaultRepoSelection]) @@ -3168,10 +3136,7 @@ export default function TaskPage(): React.JSX.Element { [taskPickerGroups] ) - // Why: prune selection when a previously-selected repo is removed, and - // preserve sticky-all (when the selection equaled every logical project - // pre-change, keep it equal to every logical project post-change). Recreating - // the Set every time eligibleRepos changes would churn the fetch effect. + // Why: prune removed repos and preserve sticky-all (selection == all projects stays == all), without recreating the Set each time and churning the fetch effect. const prevTaskPickerCountRef = useRef(taskPickerRepos.length) useEffect(() => { const prevCount = prevTaskPickerCountRef.current @@ -3205,9 +3170,7 @@ export default function TaskPage(): React.JSX.Element { [eligibleRepos, repoSelection] ) - // Why: many affordances (new-issue dialog default, item dialog repo path lookup, - // optimistic stub) need *a* repo. First selected is used as the default; - // cross-repo dialogs still let the user override per-action. + // Why: many affordances need *a* repo; use the first selected as default, while cross-repo dialogs still let the user override per-action. const primaryRepo = selectedRepos[0] ?? null const linearWorkspaces = linearStatus.workspaces ?? [] const selectedLinearWorkspaceId = @@ -3268,8 +3231,7 @@ export default function TaskPage(): React.JSX.Element { const visibleWithoutProvider = preferredVisibleTaskProviders.filter( (visibleProvider) => visibleProvider !== provider ) - // Why: an empty provider list normalizes back to "all providers"; keep - // one other source visible so opting out actually hides this provider. + // Why: an empty provider list normalizes to "all providers", so keep one other source visible or hiding this one has no effect. const nextVisibleTaskProviders: TaskProvider[] = visibleWithoutProvider.length > 0 ? visibleWithoutProvider : ['github'] const nextDefaultTaskSource = resolveVisibleTaskProvider( @@ -3291,11 +3253,7 @@ export default function TaskPage(): React.JSX.Element { [defaultTaskSource, preferredVisibleTaskProviders, updateSettings] ) - // Why: seed the preset + query from the user's saved default synchronously - // so the first fetch effect issues exactly one request keyed to the final - // query. Previously a separate effect "re-seeded" these after mount, which - // caused a throwaway empty-query fetch followed by a second fetch for the - // real default — doubling the time-to-first-paint of the list. + // Why: seed preset + query synchronously so the first fetch issues one request; a prior post-mount re-seed caused a throwaway empty-query fetch, doubling time-to-first-paint. const defaultTaskViewPreset = normalizeGitHubTaskPreset(settings?.defaultTaskViewPreset ?? 'all') const initialTaskQuery = getTaskPresetQuery(defaultTaskViewPreset) @@ -3391,8 +3349,7 @@ export default function TaskPage(): React.JSX.Element { if (parsed?.kind !== 'runtime') { continue } - // Why: task sources can span multiple runtime hosts; each runtime owns - // its own gh/glab installation and auth state. + // Why: task sources can span multiple runtime hosts; each runtime owns its own gh/glab install and auth state. void callRuntimeRpc( { kind: 'environment', environmentId: parsed.environmentId }, 'preflight.check', @@ -3680,9 +3637,7 @@ export default function TaskPage(): React.JSX.Element { const jiraSearchPersistReadyRef = useRef(false) const [taskResumeApplied, setTaskResumeApplied] = useState(false) - // Why: pageData.taskSource changes when the user clicks a specific source - // icon in the sidebar while the task page is already open. useState only - // initializes once, so sync from the store when the value changes. + // Why: useState only inits once, so sync taskSource from the store when a sidebar source-icon click changes pageData.taskSource. useEffect(() => { const pageTaskSourceChanged = lastPageTaskSourceRef.current !== pageData.taskSource lastPageTaskSourceRef.current = pageData.taskSource @@ -3700,9 +3655,7 @@ export default function TaskPage(): React.JSX.Element { if (taskSourceManuallyChangedRef.current) { return } - // Why: GitLab/Linear availability hydrates after mount. If the saved - // default was unavailable during the first render, restore it once the - // relevant check proves the provider can be shown. + // Why: GitLab/Linear availability hydrates after mount; restore the saved default once its provider check proves it can be shown. if (visibleTaskProviders.includes(preferredTaskSource) && taskSource !== preferredTaskSource) { setTaskSource(preferredTaskSource) } @@ -3714,30 +3667,21 @@ export default function TaskPage(): React.JSX.Element { } }, [settings?.defaultTaskSource, taskSource, visibleTaskProviders]) - // Why: Project mode is a sub-tab within the GitHub source. Visible whenever - // the user is on the GitHub task source — actual entry into Project mode is - // gated on a non-null `activeProject` once they pick one. + // Why: Project mode is a GitHub sub-tab — visible on the GitHub source, but actual entry is gated on a non-null activeProject. const projectModeVisible = taskSource === 'github' const [githubMode, setGithubMode] = useState<'items' | 'project'>('items') // ── GitLab task-source state ────────────────────────────────────── - // Why: parallel to Linear's slim per-source state. Skips workItemsCache - // and cross-repo aggregation in v1 — the GitLab list fetches directly - // from `window.api.gl.listMRs` / `listIssues` for the primary repo. + // Why: parallel to Linear's slim per-source state — skips workItemsCache and cross-repo aggregation; fetches directly via window.api.gl for the primary repo. const [gitlabFilter, setGitlabFilter] = useState('opened') const [gitlabItems, setGitlabItems] = useState([]) const [gitlabLoading, setGitlabLoading] = useState(false) const [gitlabError, setGitlabError] = useState(null) const [gitlabRefreshNonce, setGitlabRefreshNonce] = useState(0) - // Why: opens GitLabItemDialog when a row is clicked. Separate state from - // gitlabItems so the dialog target survives a list refresh that might - // remove the item from the visible filter (e.g. closing an MR while - // it's open in the dialog). + // Why: separate from gitlabItems so the dialog target survives a list refresh that removes the item from the visible filter (e.g. closing an MR). const [gitlabDialogItem, setGitlabDialogItem] = useState(null) - // Why: GitLab tab has two sub-views — the project's MR/issue list, - // and the user's cross-project Todos (gitlab.com/dashboard/todos). - // 'project' is default; 'todos' fetches a separate stream. + // Why: GitLab tab has two sub-views — the project MR/issue list and the user's cross-project Todos (a separate stream). const [gitlabView, setGitlabView] = useState<'issues' | 'mrs' | 'todos'>('mrs') const [gitlabTodos, setGitlabTodos] = useState([]) const [gitlabTodosLoading, setGitlabTodosLoading] = useState(false) @@ -3758,8 +3702,7 @@ export default function TaskPage(): React.JSX.Element { ? isGitLabMRFilter(gitlabFilter) : true const activeGitlabFilter = gitlabFilterIsValid ? gitlabFilter : 'opened' - // Why: Issues and MRs expose different filter sets; repair before commit so - // fetch Effects never issue `glab` with a stale filter from the other view. + // Why: Issues and MRs expose different filter sets; repair before commit so fetch effects never run glab with a stale filter from the other view. if (!gitlabFilterIsValid) { setGitlabFilter('opened') } @@ -3784,40 +3727,26 @@ export default function TaskPage(): React.JSX.Element { const [tasksRefreshing, setTasksRefreshing] = useState(false) const [tasksFiltering, setTasksFiltering] = useState(false) const [tasksError, setTasksError] = useState(null) - // Why: per-repo failure count surfaced through the "N of M" banner. IPC-level - // rejections populate tasksError instead — the two are mutually exclusive so - // a successful-with-partial-failure read and a hard-reject don't double-show. + // Why: per-repo failure count for the "N of M" banner; IPC rejections use tasksError instead so partial-failure and hard-reject don't double-show. const [failedCount, setFailedCount] = useState(0) - // Why: when every selected refresh fails because GitHub is unreachable - // (outage / network / rate limit), attribute it to GitHub instead of making - // an empty or stale cached list look current. + // Why: when every refresh fails (GitHub outage/network/rate limit), attribute it to GitHub instead of showing an empty or stale list as current. const [githubUnavailable, setGithubUnavailable] = useState(false) const [taskRefreshNonce, setTaskRefreshNonce] = useState(0) - // Why: the fetch effect uses this to detect when a nonce bump is from the - // user clicking the refresh button (force=true) vs. re-running for any - // other reason — e.g. a repo change while the nonce happens to be > 0. + // Why: lets the fetch effect tell a user refresh-click nonce bump (force=true) from a re-run for another reason (e.g. repo change with nonce > 0). const lastFetchedNonceRef = useRef(-1) - // Why: analogous to `lastFetchedNonceRef` for the invalidation nonce. A - // preference flip should force the dispatch past fetch-dedupe (same repos + - // same query, cache just evicted — without `force: true` the fan-out could - // collapse onto a stale in-flight request that resolved against the - // pre-flip source). + // Why: invalidation-nonce analog of lastFetchedNonceRef; a preference flip must force past fetch-dedupe or the fan-out collapses onto a stale in-flight request from the pre-flip source. const lastFetchedInvalidationNonceRef = useRef(0) const paginationGenerationRef = useRef(0) - // Why: entering Tasks with fresh cache should still verify remote status - // once, but the result is reconciled into existing rows to avoid a full - // table shuffle when only status/key fields changed. + // Why: entering Tasks with fresh cache still verifies remote status once, reconciled into existing rows to avoid a full table shuffle. const landingGitHubRefreshKeysRef = useRef>(new Set()) - // Why: divide the display budget between repos so one provider page maps to - // one UI page without truncating rows that later provider pages cannot return. + // Why: split the display budget across repos so one provider page maps to one UI page without truncating rows later pages can't return. const githubPerRepoPageLimit = getTaskPagePerRepoLimit( selectedRepos.length, PER_REPO_FETCH_LIMIT, CROSS_REPO_DISPLAY_LIMIT ) const githubPageSize = githubPerRepoPageLimit * Math.max(1, selectedRepos.length) - // Why: null entries are pages not fetched yet. Numbered provider pages let a - // high-page click load that page directly without rate-limiting intermediate reads. + // Why: null entries are pages not fetched yet; numbered provider pages let a high-page click load directly without reading intermediate pages. const [pages, setPages] = useState<(GitHubWorkItem[] | null)[]>(() => { const trimmed = initialTaskQuery.trim() const merged: GitHubWorkItem[] = [] @@ -3852,10 +3781,7 @@ export default function TaskPage(): React.JSX.Element { setLoadingTargetPage(null) }, [selectedRepos, appliedTaskSearch, workItemsInvalidationNonce]) - // Why: clicking a GitHub row (or completing the create-issue flow) opens - // this dialog for a read/review surface. The dialog's "Use" button routes - // through the same direct-launch flow as the row-level "Use" CTA so - // behavior is consistent regardless of entry point. + // Why: the dialog's "Use" button routes through the same direct-launch flow as the row-level "Use" CTA so behavior is consistent regardless of entry point. const githubTaskDrawerWorkItem = useAppStore((s) => s.githubTaskDrawerWorkItem) const setGithubTaskDrawerWorkItem = useAppStore((s) => s.setGithubTaskDrawerWorkItem) const [dialogInitialTab, setDialogInitialTab] = useState('conversation') @@ -3878,12 +3804,7 @@ export default function TaskPage(): React.JSX.Element { ) ) - // Why: derive the dialog's work item from the store cache so it reflects - // optimistic patches (e.g. table-cell status toggle). Falls back to the - // snapshot stored at click time for newly-created stubs not yet in the cache. - // Disambiguates by repoId so issues with the same number fetched from - // multiple repos (e.g. fork + non-fork, both routed through the same - // upstream) resolve to the clicked row's repo, not the first one scanned. + // Why: derive the dialog item from the cache for optimistic patches, falling back to the click-time snapshot for new stubs; key by repoId so same-number issues across repos resolve to the clicked row. const cachedDialogWorkItem = useAppStore((s) => findTaskPageDialogWorkItem(s.workItemsCache, dialogWorkItemKey) ) @@ -4024,15 +3945,8 @@ export default function TaskPage(): React.JSX.Element { [patchTaskPageWorkItemRows] ) - // Why: feature 1 — render the "Issues from {owner}/{repo}" indicator per - // selected repo whose issue-source and PR-source slugs differ, and surface - // a per-repo retryable banner when the issue-side fetch failed. Both derive - // from the same `workItemsCache` entry the list already consumes, so no - // extra IPC round-trip is needed. The `TaskPageRepoSourceState` shape lives - // with the cache selectors so the render and guard code share one contract. - // Why: subscribe only to the cache entries this page can render. The selector - // returns entry references so Zustand shallow equality filters unrelated - // cache writes before they re-render the full tasks page. + // Why: the per-repo issue-source indicator and retry banner both derive from the same workItemsCache entry, so no extra IPC. + // Why: subscribe only to entries this page renders; the selector returns entry refs so shallow equality filters unrelated cache writes. const perRepoSourceState = useMemo( () => buildTaskPageRepoSourceState(selectedRepos, selectedWorkItemsCacheEntries), [selectedRepos, selectedWorkItemsCacheEntries] @@ -4042,18 +3956,13 @@ export default function TaskPage(): React.JSX.Element { if (taskSource !== 'github' || githubMode !== 'items') { return } - // Why: inline/dialog edits patch `workItemsCache`; the paged table renders - // from a local snapshot so it needs the patched row objects copied across. + // Why: inline/dialog edits patch workItemsCache; the paged table renders from a local snapshot, so copy patched rows across. setPages((current) => reconcileTaskPagePagesWithWorkItemsCache(current, selectedWorkItemsCacheEntries) ) }, [githubMode, selectedWorkItemsCacheEntries, taskSource]) - // Why: surface a one-time toast per session per repo when the user's - // preferred `'upstream'` is no longer configured and we fell back to - // origin. Gated on a ref-backed set so repeated list refreshes don't - // re-toast. We deliberately do NOT auto-reset the preference — the user - // may re-add `upstream` later and expect it to pick up again. + // Why: one-time toast per repo when the 'upstream' preference fell back to origin (ref-gated); deliberately don't auto-reset the preference so re-adding upstream later still applies. const fellBackToastedRef = useRef>(new Set()) useEffect(() => { if (taskSource !== 'github') { @@ -4081,14 +3990,7 @@ export default function TaskPage(): React.JSX.Element { } }, [selectedRepos, selectedWorkItemsCacheEntries, taskSource]) - // Why: on a partial-failure retry the cache still holds successful-side - // data, so `tasksLoading` (which is gated on `anyUncached`) never flips - // true and the Retry button would otherwise give no feedback. Track - // retry-in-flight per selected source so that clicking Retry - // on one banner only flips that source's button into its "Retrying…" - // state — other still-failing banners stay in their "Retry" state rather - // than misleadingly flipping in lockstep. The fetch effect clears the set - // when the nonce-driven refresh settles. + // Why: partial-failure retry leaves the cache populated so tasksLoading never flips, giving no feedback; track retry-in-flight per source so only the clicked banner shows "Retrying…". const [retryingSourceKeys, setRetryingSourceKeys] = useState>(() => new Set()) const handleRetryIssuesFetch = useCallback( @@ -4097,12 +3999,7 @@ export default function TaskPage(): React.JSX.Element { if (!source) { return } - // Why: bumping the shared refresh nonce reuses the Tasks list's - // single fetch path — nonce changes are treated as force=true so - // retry doesn't silently dedupe onto a still-failing in-flight request. - // The nonce bump refreshes ALL selected repos, but the Retrying… - // state is scoped to the clicked source so other banners stay in their - // "Retry" state rather than misleadingly flipping to "Retrying…". + // Why: nonce bump reuses the fetch path as force=true so retry doesn't dedupe onto a still-failing in-flight request (refreshes all repos; Retrying… stays scoped to the clicked source). setRetryingSourceKeys((prev) => { const next = new Set(prev) next.add(source.sourceKey) @@ -4123,20 +4020,11 @@ export default function TaskPage(): React.JSX.Element { const [newIssueAssignees, setNewIssueAssignees] = useState([]) const [newIssueSubmitting, setNewIssueSubmitting] = useState(false) const [newIssueRepoId, setNewIssueRepoId] = useState(null) - // Why: session-only draft slice backs recovery of an in-progress issue after - // an accidental dismissal (outside click/Escape/Cancel) and across a Tasks - // view unmount. Component `useState` stays the inputs' immediate source; the - // store is the durable-across-remount backing. See task-page-new-issue-draft. - // The draft value is read imperatively at open time (getState) rather than - // subscribed: it's only consumed in the `+` open handler, and the write-through - // rewrites it on every keystroke — subscribing would re-render all of TaskPage - // per keystroke while the modal is open. Actions are stable refs (no churn). + // Why: session-only draft recovers an in-progress issue across dismissal/remount; read imperatively (not subscribed) so per-keystroke writes don't re-render all of TaskPage. const setNewIssueDraft = useAppStore((s) => s.setNewIssueDraft) const clearNewIssueDraft = useAppStore((s) => s.clearNewIssueDraft) - // Why: resolve the target repo from the user's choice, falling back to the - // first selected repo if the chosen id drops out of the selection while the - // dialog is open — keeps submit always landing on a valid repo. + // Why: fall back to the first selected repo if the chosen id drops from the selection mid-dialog, so submit always has a valid target. const newIssueTargetRepo = useMemo( () => selectedRepos.find((r) => r.id === newIssueRepoId) ?? selectedRepos[0] ?? null, [selectedRepos, newIssueRepoId] @@ -4177,13 +4065,7 @@ export default function TaskPage(): React.JSX.Element { { runtimeEnvironmentId: newIssueOpen ? (newIssueRuntimeTarget?.environmentId ?? null) : null } ) - // Why: repo-scoped labels/assignees can't cross repos. A reactive clear keyed - // on the derived target id can't tell a restore apart from a user switch, so - // it would wipe just-restored fields and corrupt the recovery draft via the - // write-through below. Decompose by cause instead: this guard only handles the - // "chosen repo vanished from the selection" case (removed/deselected). A - // genuine user switch clears imperatively in the repo Select's handler; a - // restore always seeds an in-selection repoId, so neither path fires here. + // Why: only handles the "chosen repo vanished" case; a reactive clear keyed on target id can't tell a restore from a user switch and would wipe the recovery draft. useEffect(() => { const reset = resolveVanishedNewIssueRepoReset( newIssueRepoId, @@ -4197,10 +4079,7 @@ export default function TaskPage(): React.JSX.Element { setNewIssueRepoId(reset.repoId) }, [newIssueRepoId, selectedRepos]) - // Why: mirror the live fields into the session draft while the modal is open - // so an accidental dismissal doesn't lose input. Content-gate the write so an - // untouched open never pins a meaningless draft (repoId alone is not content), - // and clear any stale draft once the form is emptied back out. + // Why: content-gated mirror of live fields into the session draft while the modal is open, so dismissal doesn't lose input. useEffect(() => { if (!newIssueOpen) { return @@ -4239,9 +4118,7 @@ export default function TaskPage(): React.JSX.Element { useState(null) const [selectedLinearIssueCanFloat, setSelectedLinearIssueCanFloat] = useState(false) - // Why: the Linear list keeps its own fetched array, while cell edits patch - // the shared caches. Subscribing to just the Linear caches lets the list and - // inline detail reflect optimistic mutations without a second durable cache. + // Why: subscribe to just the Linear caches so list and inline detail reflect optimistic cell edits without a second cache. const linearCacheSnapshot = useAppStore( useShallow((s) => ({ issueCache: s.linearIssueCache, @@ -4717,8 +4594,7 @@ export default function TaskPage(): React.JSX.Element { setJiraSearchInput(jiraQuery) setAppliedJiraSearch(jiraQuery) - // Why: settings and persisted UI hydrate asynchronously. Apply the restored - // Tasks context exactly once so later source/filter clicks remain local. + // Why: settings/UI hydrate async; apply the restored Tasks context exactly once so later source/filter clicks stay local. taskResumeAppliedRef.current = true setTaskResumeApplied(true) }, [ @@ -4825,9 +4701,7 @@ export default function TaskPage(): React.JSX.Element { taskSource ]) - // Why: fetch the full team list from the Linear API so the selector shows - // all teams the user belongs to, not just teams with issues in the current - // fetch window. Fetched once when the Linear tab is active and connected. + // Why: fetch the full Linear team list so the selector shows all teams, not just those with issues in the fetch window. const [availableTeams, setAvailableTeams] = useState([]) const [linearTeamRefreshNonce, setLinearTeamRefreshNonce] = useState(0) @@ -4843,9 +4717,7 @@ export default function TaskPage(): React.JSX.Element { const cachedTeams = getCachedLinearTeams(selectedLinearWorkspaceId, { sourceContext: linearTaskSourceContext }) - // Why: workspace switches must not leave the prior workspace's teams - // available for new-issue creation while the replacement fetch is pending, - // but a workspace-scoped cache can keep the selector usable immediately. + // Why: on a workspace switch, drop the prior workspace's teams during the pending fetch but seed from the workspace-scoped cache. setAvailableTeams(cachedTeams ?? []) void listLinearTeams(selectedLinearWorkspaceId, { sourceContext: linearTaskSourceContext }) .then((teams) => { @@ -4916,10 +4788,7 @@ export default function TaskPage(): React.JSX.Element { jiraTaskSourceContext ]) - // Why: stable key for `selectedRepos` so the GitLab fetch effect below - // doesn't re-run on every parent re-render just because the array - // reference changed. The memoized string keys off id + path + - // connectionId — the only fields the effect actually reads. + // Why: stable string key for selectedRepos so the GitLab effect doesn't re-run on every parent render from a new array ref. const selectedReposKey = useMemo( () => selectedRepos @@ -4928,10 +4797,7 @@ export default function TaskPage(): React.JSX.Element { [selectedRepos] ) - // Why: GitLab task-source data fetch. Issues and MRs are fetched - // separately (mirrors GitHub's separate Issues / PRs endpoints) so - // errors are isolated per tab and the backend doesn't need a combined - // merge+sort that can hide failures. + // Why: fetch GitLab Issues and MRs separately so errors stay isolated per tab (mirrors GitHub's split endpoints). useEffect(() => { if (taskSource !== 'gitlab') { return @@ -4949,8 +4815,7 @@ export default function TaskPage(): React.JSX.Element { ) { return } - // Why: folder-mode repos have no remotes to derive a GitLab project from; - // SSH-backed Git repos go through the same provider-aware IPC path. + // Why: folder-mode repos lack remotes to derive a GitLab project from; SSH-backed repos use the same provider-aware IPC path. const eligibleRepos = selectedRepos if (eligibleRepos.length === 0) { setGitlabItems([]) @@ -4980,9 +4845,7 @@ export default function TaskPage(): React.JSX.Element { items: GitLabWorkItem[] error?: { type?: string; message: string } } - // Why: not_found just means "this repo isn't a GitLab project" - // (e.g. a GitHub-only repo in a mixed selection). Drop it - // silently so the GitLab list doesn't show false errors. + // Why: not_found just means the repo isn't a GitLab project (mixed selection); drop it so the list shows no false errors. const error = typed.error?.type === 'not_found' ? undefined : typed.error return { repoId: repo.id, items: typed.items, error } }) @@ -5027,9 +4890,7 @@ export default function TaskPage(): React.JSX.Element { } merged.sort((a, b) => (b.updatedAt ?? '').localeCompare(a.updatedAt ?? '')) setGitlabItems(merged) - // Why: only surface an error banner when EVERY eligible repo failed. - // Mixed selections often include non-GitLab repos, and a partial - // banner would hide the working rows. + // Why: only banner when every eligible repo failed; a partial one would hide working rows in a mixed (non-GitLab) selection. if (errs.length > 0 && merged.length === 0) { setGitlabError(errs[0]) } @@ -5045,9 +4906,7 @@ export default function TaskPage(): React.JSX.Element { // eslint-disable-next-line react-hooks/exhaustive-deps -- selectedReposKey encodes the only selectedRepos fields read above; keying off the array ref would re-run on every parent render. }, [taskSource, gitlabView, activeGitlabFilter, gitlabRefreshNonce, selectedReposKey]) - // Why: Todos fetch lives in its own effect — different trigger - // condition from the project view (no chip filter dependence) and a - // different data path (`gl.todos` is user-scoped, not repo-scoped). + // Why: Todos fetch has its own effect — different trigger (no chip filter) and data path (gl.todos is user-scoped, not repo-scoped). useEffect(() => { if (taskSource !== 'gitlab' || gitlabView !== 'todos') { return @@ -5208,8 +5067,7 @@ export default function TaskPage(): React.JSX.Element { return teams.sort((a, b) => a.name.localeCompare(b.name)) }, [displayedLinearIssues]) - // Why: the full Linear team fetch is async and can temporarily be empty. - // Keep the selector usable from issue metadata until the complete list lands. + // Why: the full team fetch is async and briefly empty; keep the selector usable from issue metadata until the list lands. const linearTeamOptions = useMemo(() => { if (availableTeams.length === 0) { return linearIssueTeams @@ -5226,8 +5084,7 @@ export default function TaskPage(): React.JSX.Element { }) }, [availableTeams, linearIssueTeams]) - // Why: team IDs belong to one Linear workspace. Switching workspaces while a - // saved subset exists must not leave the task list filtered by stale team IDs. + // Why: team IDs belong to one workspace, so a workspace switch must not leave the list filtered by stale team IDs. useEffect(() => { if (linearTeamOptions.length === 0) { return @@ -5247,8 +5104,7 @@ export default function TaskPage(): React.JSX.Element { ) const applyLinearAttributeFilter = useCallback((next: LinearIssueAttributeFilter) => { - // Why: batch filter + limit/page reset in one transition so the fetch - // effect never issues an old expanded-limit request for the new filter. + // Why: batch filter + limit/page reset so the fetch effect never issues an old expanded-limit request for the new filter. setLinearAttributeFilter(next) setLinearIssueLimit(LINEAR_ITEM_LIMIT) setLinearIssuePage(0) @@ -5272,8 +5128,7 @@ export default function TaskPage(): React.JSX.Element { if (previousId === null || previousId === nextId) { return } - // Why: status/assignee/labels are team-scoped; clearing them is a filter change - // and must reset limit/page via applyLinearAttributeFilter (R6), not a bare set. + // Why: team-scoped facets; clearing them is a filter change, so reset limit/page via applyLinearAttributeFilter (R6), not a bare set. const next = teamDerivedFacetsForPrimaryTeamChange(linearAttributeFilter) if ( linearIssueAttributeFilterSignature(linearAttributeFilter) === @@ -5292,8 +5147,7 @@ export default function TaskPage(): React.JSX.Element { if (activeLinearIssueContextLabel) { return displayedLinearIssues } - // Why: team options can be derived after issue rows render. Treat an - // empty selection as "all" until reconciliation has a concrete team set. + // Why: team options can arrive after issue rows render; treat an empty selection as "all" until reconciliation sets teams. if (displayedLinearIssues.length > 0 && linearTeamSelection.size === 0) { return displayedLinearIssues } @@ -5374,9 +5228,7 @@ export default function TaskPage(): React.JSX.Element { return } - // Why: unlike GitHub's cursor pages, Linear reads are cached as an - // expanded prefix. Jumping to a new page first expands the prefix, then - // commits the page when the fetch returns enough rows. + // Why: Linear reads are cached as an expanded prefix; a page jump expands it and commits once enough rows arrive. setActiveLinearIssueLoadingTargetPage(page) ensureActiveLinearIssueLimit((page + 1) * LINEAR_ITEM_LIMIT) }, @@ -5410,9 +5262,7 @@ export default function TaskPage(): React.JSX.Element { return } - // Why: Linear can return more backend rows without immediately filling the - // next visible page after local team filtering. Keep expanding the prefix - // until the requested page exists or Linear reports exhaustion. + // Why: local filtering can leave the next page short, so keep expanding the prefix until the page exists or Linear is exhausted. ensureActiveLinearIssueLimit(activeLinearIssueLimit + LINEAR_ITEM_LIMIT) }, [ activeLinearIssueCanRequestMore, @@ -5461,8 +5311,7 @@ export default function TaskPage(): React.JSX.Element { next.delete(groupedProperty) } - // Why: a Team column repeats the same value when one team is selected. - // Keep it hidden until the user explicitly opts back into that property. + // Why: a Team column repeats the same value when one team is selected; keep it hidden until the user opts back in. if (linearTeamSelection.size <= 1 && !linearTeamPropertyTouched) { next.delete('team') } else if (linearTeamSelection.size > 1 && !linearTeamPropertyTouched) { @@ -5795,8 +5644,7 @@ export default function TaskPage(): React.JSX.Element { } }) return () => { - // Why: project lists are workspace-scoped; stale responses must not - // populate the composer after a team/workspace switch. + // Why: project lists are workspace-scoped; stale responses must not populate the composer after a team/workspace switch. cancelled = true } }, [ @@ -5809,8 +5657,7 @@ export default function TaskPage(): React.JSX.Element { ]) useEffect(() => { - // Why: the selected team can change indirectly when the available Linear - // teams/workspace list refreshes, even if the explicit picker value did not. + // Why: the selected team can change indirectly when the Linear teams/workspace list refreshes, even if the picker value didn't. setNewLinearIssueStateId(null) setNewLinearIssueAssigneeId(null) setNewLinearIssuePriority(0) @@ -6132,8 +5979,7 @@ export default function TaskPage(): React.JSX.Element { } }) return () => { - // Why: create fields are scoped to project + issue type; ignore late - // responses after the user switches either selector. + // Why: create fields are scoped to project + issue type; ignore late responses after switching either selector. cancelled = true } }, [ @@ -6145,9 +5991,7 @@ export default function TaskPage(): React.JSX.Element { jiraTaskSourceContext ]) - // Why: defense-in-depth safety net applied to the current page's items. - // The active tab scopes requests to issues or PRs, and this keeps stale - // cache rows from leaking across the split tabs. + // Why: defense-in-depth — keep stale cache rows from leaking across the issue/PR split tabs. const applyTypeFilter = useCallback( (items: GitHubWorkItem[]) => { return items.filter((item) => { @@ -6250,8 +6094,7 @@ export default function TaskPage(): React.JSX.Element { lastLoadedPageIndex = index } } - // Why: when counts fail, a full loaded page is enough evidence to expose one - // more page without pretending unfetched sparse entries are empty results. + // Why: when counts fail, a full loaded page is enough evidence to expose one more page without faking empty results. const lastLoadedPageFull = (pages[lastLoadedPageIndex]?.length ?? 0) >= Math.max(1, githubPageSize) const fallbackTotalPages = lastLoadedPageFull @@ -6262,8 +6105,7 @@ export default function TaskPage(): React.JSX.Element { ? Math.max(pages.length, countedTotalPages) : fallbackTotalPages - // Why: numbered provider pages support random access. Load only the clicked - // page so a high-page jump does not exhaust GitHub's Search API rate bucket. + // Why: load only the clicked page so a high-page jump doesn't exhaust GitHub's Search API rate bucket. const handleLoadNextPage = useCallback( async (targetPage?: number) => { if (paginationLoading || selectedRepos.length === 0) { @@ -6346,12 +6188,7 @@ export default function TaskPage(): React.JSX.Element { githubSearchPersistReadyRef.current = true return } - // Why: persist the debounced applied query regardless of the active - // preset. The preset-click handler writes the canonical query for that - // preset, so persisting again here is at worst idempotent. When the - // user types into the search box `handleTaskSearchChange` clears the - // preset, but persisting unconditionally also covers paths that change - // appliedTaskSearch without going through that handler. + // Why: persist the applied query unconditionally to cover paths that change appliedTaskSearch outside the preset handler. setTaskResumeState({ githubItemsPreset: activeTaskPreset, githubItemsQuery: appliedTaskSearch.trim() @@ -6362,11 +6199,7 @@ export default function TaskPage(): React.JSX.Element { if (!taskResumeApplied) { return } - // Why: both early-return branches must clear `retryingSourceKeys` — if the - // user clicks Retry and then switches `taskSource` away from 'github' (or - // somehow ends up with zero repos selected) before the fetch dispatches, - // neither the `.then` nor the `.catch` below will fire, and the Retry - // button would stay stuck in its disabled/Retrying state indefinitely. + // Why: both early-return branches must clear retryingSourceKeys — if they fire, neither .then nor .catch runs and Retry stays stuck. if (taskSource !== 'github' || githubMode !== 'items') { setRetryingSourceKeys(new Set()) setTasksRefreshing(false) @@ -6380,16 +6213,11 @@ export default function TaskPage(): React.JSX.Element { return } // unreachable — multi-combobox forbids empty - // Why: `repo:owner/name` qualifiers are silently dropped before fan-out - // because in cross-repo mode they would pin every per-repo fetch to a - // single repo and zero out the rest. See stripRepoQualifiers. + // Why: strip repo:owner/name qualifiers before fan-out — cross-repo they'd pin every fetch to one repo. See stripRepoQualifiers. const q = stripRepoQualifiers(appliedTaskSearch.trim()) let cancelled = false - // Why: paint cached rows synchronously before awaiting the fan-out so - // selection changes don't leave the previous selection's rows on screen - // for a frame. Any repo without a cache entry simply contributes nothing - // to this pre-paint; the fetch will fill it in. + // Why: paint cached rows synchronously before the fan-out so a selection change doesn't leave the prior rows on screen for a frame. const preMerged: GitHubWorkItem[] = [] let anyUncached = false let anyRepoCached = false @@ -6408,9 +6236,7 @@ export default function TaskPage(): React.JSX.Element { preMerged.push(...cached) } } - // Why: always replace — if preMerged is empty (e.g. query just changed and - // no repo has a cache entry for it), we clear the previous query's rows - // rather than leaving them on screen under the spinner. + // Why: always replace — an empty preMerged clears the previous query's rows instead of leaving them under the spinner. const page0 = preMerged.length > 0 ? sortWorkItemsByNumber(preMerged).slice(0, githubPageSize) : [] setPages([page0]) @@ -6424,10 +6250,7 @@ export default function TaskPage(): React.JSX.Element { // Preserve the existing nonce-gated force behavior. const forceRefresh = taskRefreshNonce !== lastFetchedNonceRef.current lastFetchedNonceRef.current = taskRefreshNonce - // Why: a preference flip bumps `workItemsInvalidationNonce`. Treat that - // bump as a forced refresh so the fan-out bypasses the in-flight dedupe - // map — otherwise an overlapping request started before the flip could - // resolve the new fetch and repopulate the cache with pre-flip data. + // Why: treat a preference-flip nonce bump as a forced refresh so it bypasses the dedupe map and can't reuse pre-flip data. const preferenceInvalidated = workItemsInvalidationNonce !== lastFetchedInvalidationNonceRef.current lastFetchedInvalidationNonceRef.current = workItemsInvalidationNonce @@ -6447,27 +6270,16 @@ export default function TaskPage(): React.JSX.Element { landingRefreshKey ]) } - // Why: manual refresh keeps cached rows visible, so the normal - // `tasksLoading` flag may stay false. Track the forced fetch separately - // so the toolbar still shows a refresh-in-progress affordance. + // Why: manual refresh keeps cached rows (tasksLoading stays false), so track forced fetch separately for the toolbar spinner. setTasksRefreshing(forcedFetch) - // Why: snapshot the retrying source keys at effect-dispatch so overlapping - // retries don't clear each other's pending state. An earlier cancelled - // effect settling after a newer retry starts would otherwise wipe the - // newer retry's source from the set. Clearing only the keys captured - // when this effect dispatched preserves later additions. + // Why: snapshot retrying keys at dispatch so an earlier settling effect doesn't wipe a newer retry's pending source. const dispatchedRetrySourceKeys = retryingSourceKeys void fetchWorkItemsAcrossRepos(repoArgs, githubPerRepoPageLimit, githubPageSize, q, { ...deriveTaskPageGitHubWorkItemsFetchOptions(forcedFetch, shouldProbeOnLanding) }) .then(({ items, failedCount: failed, githubUnavailable: unavailable }) => { - // Why: clear only the sources this effect was responsible for - // retrying (the snapshot captured at dispatch time). Overlapping - // retries — a second click while a prior fetch is still in flight - // — must not clear the newer source from the set, so we can't just - // reset the whole set here. The early-return branches above reset - // the whole set because those branches won't dispatch a fetch. + // Why: clear only the dispatch-time snapshot keys so an overlapping retry's newer source isn't wiped. setRetryingSourceKeys((prev) => { if (dispatchedRetrySourceKeys.size === 0) { return prev @@ -6499,14 +6311,8 @@ export default function TaskPage(): React.JSX.Element { setTasksFiltering(false) }) .catch((err) => { - // Why: fetchWorkItemsAcrossRepos swallows per-repo failures, so a - // reject here means an IPC-level or programmer error — surface it. - // Clear only the sources this effect was responsible for retrying - // (the snapshot captured at dispatch time). Overlapping retries — - // a second click while a prior fetch is still in flight — must - // not clear the newer source from the set, so we can't just reset - // the whole set here. The early-return branches above reset the - // whole set because those branches won't dispatch a fetch. + // Why: fetchWorkItemsAcrossRepos swallows per-repo failures, so a reject here is IPC/programmer error — surface it. + // Why: clear only the dispatch-time snapshot keys so an overlapping retry's newer source isn't wiped. setRetryingSourceKeys((prev) => { if (dispatchedRetrySourceKeys.size === 0) { return prev @@ -6528,9 +6334,7 @@ export default function TaskPage(): React.JSX.Element { setTasksFiltering(false) }) - // Why: fire-and-forget count query in parallel with the items fetch. - // The search API is cached 120s server-side so this doesn't add - // meaningful latency or rate-limit pressure. + // Why: fire-and-forget count query alongside the items fetch; the search API is cached 120s server-side so it adds little cost. void countWorkItemsAcrossRepos( selectedRepos.map((r) => ({ repoId: r.id, @@ -6549,10 +6353,7 @@ export default function TaskPage(): React.JSX.Element { return () => { cancelled = true } - // Why: getCachedWorkItems and fetchWorkItemsAcrossRepos are stable zustand - // selectors; depending on them would re-run the effect on unrelated store - // updates. `workItemsInvalidationNonce` is explicitly included so a - // preference flip (which only evicts cache) re-dispatches this effect. + // Why: store selectors are stable (omit from deps); workItemsInvalidationNonce included so a preference flip re-dispatches. // eslint-disable-next-line react-hooks/exhaustive-deps }, [ selectedRepos, @@ -6567,9 +6368,7 @@ export default function TaskPage(): React.JSX.Element { const applyPRFilterChange = useCallback( (change: PRFilterChange): void => { let next = scopeGitHubTaskSearch(taskSearchInput, activeGithubTaskKind) - // Why: each filter dropdown contributes an independent qualifier patch. - // `withQualifier` round-trips through parseTaskQuery so prior filters and - // free-text are preserved. + // Why: withQualifier round-trips through parseTaskQuery so each dropdown's patch preserves prior filters and free-text. if ('author' in change) { next = withQualifier(next, 'author', change.author ?? null) } @@ -6589,9 +6388,7 @@ export default function TaskPage(): React.JSX.Element { next = withQualifier(next, 'draft', change.draft ? 'true' : 'false') } if ('reviewer' in change) { - // Why: the two reviewer qualifiers (review-requested vs reviewed-by) are - // mutually exclusive in the PR-list UX — clear the other axis whenever - // one is set so the chip's visible state matches the actual query. + // Why: the two reviewer qualifiers are mutually exclusive — clear the other whenever one is set so the chip matches the query. const reviewer = change.reviewer ?? null if (reviewer === null) { next = withQualifier(next, 'reviewRequested', null) @@ -6608,9 +6405,7 @@ export default function TaskPage(): React.JSX.Element { setAppliedTaskSearch(next) setActiveTaskPreset(null) setTaskResumeState({ githubItemsPreset: null, githubItemsQuery: next }) - // Why: filter changes replace the meaning of every row. Showing stale - // rows while the filtered query resolves reads like the filter did - // nothing, so render the same skeleton used for an uncached initial load. + // Why: a filter change replaces every row's meaning; show the load skeleton so stale rows don't read as if the filter did nothing. setTasksFiltering(true) setTaskRefreshNonce((current) => current + 1) }, @@ -6633,8 +6428,7 @@ export default function TaskPage(): React.JSX.Element { const scoped = scopeGitHubTaskSearch(next, activeGithubTaskKind) setTaskSearchInput(next) setActiveTaskPreset(null) - // Why: the visible rows are keyed by appliedTaskSearch, not the draft - // input. Hide stale rows as soon as the draft would change the query. + // Why: visible rows are keyed by appliedTaskSearch, not the draft input; hide stale rows once the draft changes the query. setTasksFiltering(scoped !== appliedTaskSearch) }, [activeGithubTaskKind, appliedTaskSearch] @@ -6642,9 +6436,7 @@ export default function TaskPage(): React.JSX.Element { const handleSetDefaultTaskPreset = useCallback( (presetId: TaskViewPresetId): void => { - // Why: the default task view is a durable preference, so right-clicking a - // preset updates the persisted settings instead of only changing the - // current page state. + // Why: the default task view is a durable preference, so persist it instead of only changing page state. void updateSettings({ defaultTaskViewPreset: presetId }).catch(() => { toast.error( translate('auto.components.TaskPage.fe380f306c', 'Failed to save default task view.') @@ -6870,8 +6662,7 @@ export default function TaskPage(): React.JSX.Element { labels: newIssueLabels, assignees: newIssueAssignees.map((assignee) => assignee.login) }, - // Why: oversized-body recovery can require two 30-second writes - // after GitHub rejects the initial create request. + // Why: oversized-body recovery can need two 30s writes after GitHub rejects the initial create. { timeoutMs: 65_000 } ) : await window.api.gh.createIssue({ @@ -6913,8 +6704,7 @@ export default function TaskPage(): React.JSX.Element { } setNewIssueOpen(false) if (result.bodySaveWarning) { - // Why: retain the unsaved body for recovery, but clear the title so - // reopening the composer cannot repeat the create with one click. + // Why: keep the unsaved body for recovery but clear the title so reopening can't one-click repeat the create. setNewIssueTitle('') setNewIssueDraft({ title: '' }) } else { @@ -6922,16 +6712,13 @@ export default function TaskPage(): React.JSX.Element { setNewIssueBody('') setNewIssueLabels([]) setNewIssueAssignees([]) - // Why: only a complete success discards the recovery draft; a partial - // body save keeps the original text available without another create. + // Why: only a complete success discards the recovery draft; a partial body save keeps the text for recovery. clearNewIssueDraft() } // Why: bump the nonce so the list refetches and shows the new issue. setTaskRefreshNonce((current) => current + 1) - // Why: auto-open the new issue in the dialog so the user sees - // exactly what was filed. Use an optimistic stub first so the dialog - // has immediate content, then refine with the full `workItem` fetch. + // Why: auto-open the new issue with an optimistic stub for immediate content, then refine with the full workItem fetch. const stub: GitHubWorkItem = { id: `issue:${String(result.number)}`, repoId: newIssueTargetRepo.id, @@ -6971,10 +6758,7 @@ export default function TaskPage(): React.JSX.Element { void fullIssuePromise .then((full) => { if (full) { - // Why: `full` is `Omit` (IPC shape). - // Cast through unknown: spreading a discriminated union loses the - // discriminant, so `{ ...full, repoId }` doesn't typecheck as - // GitHubWorkItem. The runtime shape is correct by construction. + // Why: cast through unknown — spreading the discriminated union loses the discriminant, so { ...full, repoId } won't typecheck. const withRepoId = { ...full, repoId: stubRepoId } as unknown as GitHubWorkItem setDialogWorkItem(withRepoId) } @@ -7155,8 +6939,7 @@ export default function TaskPage(): React.JSX.Element { setLinearRefreshNonce((n) => n + 1) useAppStore.getState().recordFeatureInteraction('linear-tasks') - // Why: auto-select the new issue in the inline workspace so the user - // sees exactly what was filed, mirroring the GitHub create-issue flow. + // Why: auto-select the new issue so the user sees exactly what was filed (mirrors the GitHub create-issue flow). void linearGetIssue( linearTaskSourceContext ?? settings, result.id, @@ -7255,8 +7038,7 @@ export default function TaskPage(): React.JSX.Element { return } if (full) { - // Why: the list cache may still be fresh after create; insert the - // new row locally before selecting it so the inspector stays open. + // Why: list cache may still be fresh after create; insert the new row locally before selecting so the inspector stays open. setJiraIssues((prev) => [full, ...prev.filter((issue) => issue.key !== full.key)]) setSelectedJiraIssue(full) } @@ -7308,9 +7090,7 @@ export default function TaskPage(): React.JSX.Element { return } - // Why: Esc should first dismiss the focused control so users can back - // out of text entry without accidentally closing the whole page. - // Once focus is already outside an input, Esc closes the tasks page. + // Why: Esc first blurs a focused input so it doesn't accidentally close the whole page; only closes once focus is outside an input. if ( target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || @@ -7363,8 +7143,7 @@ export default function TaskPage(): React.JSX.Element { refreshPreflightStatus ]) - // Why: debounce the Linear search input so we don't fire a request on every - // keystroke — matches the 300ms cadence used for GitHub search. + // Why: debounce the Linear search input so we don't fire a request per keystroke (300ms, matching GitHub search). useEffect(() => { if (!taskResumeApplied) { return @@ -7399,8 +7178,7 @@ export default function TaskPage(): React.JSX.Element { taskSource ]) - // Why: fetch Linear issues when the tab is active and connected. Empty search - // uses the plain `all` list with optional server-side attribute filters. + // Why: fetch Linear issues when the tab is active and connected; empty search uses the `all` list with server-side filters. useEffect(() => { if (!taskResumeApplied) { return @@ -7480,8 +7258,7 @@ export default function TaskPage(): React.JSX.Element { ]) } - // Why: cached rows should remain visible on navigation. Only an explicit - // refresh or a true cache miss needs the blocking loading state. + // Why: keep cached rows visible on navigation; only explicit refresh or a true cache miss shows the blocking loading state. setLinearLoading(forceRefresh || cachedResult === null) const request = @@ -7542,8 +7319,7 @@ export default function TaskPage(): React.JSX.Element { return () => { cancelled = true } - // Why: searchLinearIssues and listLinearIssues are stable zustand selectors; - // depending on them would re-run the effect on unrelated store updates. + // Why: searchLinearIssues/listLinearIssues are stable selectors; adding them would re-run the effect on unrelated store updates. // eslint-disable-next-line react-hooks/exhaustive-deps }, [ taskSource, @@ -7729,8 +7505,7 @@ export default function TaskPage(): React.JSX.Element { const force = linearRefreshNonce > 0 setLinearCustomViewsLoading(force || !allCached) setLinearCustomViewsError(null) - // Why: the Views tab already has a Model column, so listing both view - // models avoids a second, redundant Issues/Projects switch. + // Why: the Views tab already has a Model column, so list both models rather than add a redundant Issues/Projects switch. void Promise.all( LINEAR_CUSTOM_VIEW_MODELS.map((model) => listLinearCustomViews(model, LINEAR_ITEM_LIMIT, undefined, { @@ -7843,10 +7618,7 @@ export default function TaskPage(): React.JSX.Element { return } - // Why: the corrected Linear surface is list-first. Keep an open inspector - // only while its issue remains in the current filter instead of auto-opening - // the first row and turning the list back into navigation chrome. Related - // sub-issue navigation is allowed to stay open because it is user-directed. + // Why: list-first — keep an open inspector only while its issue stays in the filter, not auto-open row 1; user-directed sub-issue nav stays. if ( selectedLinearIssueId && !selectedLinearIssueCanFloat && @@ -7992,10 +7764,7 @@ export default function TaskPage(): React.JSX.Element { taskSource ]) - // Why: for Linear issues the "Use" flow opens the composer with the issue - // info adapted to the LinkedWorkItemSummary shape. Linear identifiers are - // strings (e.g. "ENG-123") so we use 0 as a placeholder number since the - // provider-generic work item shape still expects numeric issue metadata. + // Why: Linear ids are strings (e.g. "ENG-123") but the provider-generic shape needs a numeric number, so the adapter uses 0 as placeholder. const openComposerForLinearItem = useCallback( (issue: LinearIssue): void => { const linkedWorkItem = buildLinearIssueLinkedWorkItem(issue) @@ -8011,10 +7780,7 @@ export default function TaskPage(): React.JSX.Element { const handleUseLinearItem = useCallback( (issue: LinearIssue): void => { - // Why: same rationale as handleUseWorkItem — open the New Workspace - // dialog pre-filled rather than yolo-creating the worktree, so the - // user can confirm name / agent / setup before the worktree lands in - // the sidebar. Telemetry attribution flows via openComposerForLinearItem. + // Why: like handleUseWorkItem — open the pre-filled dialog instead of creating the worktree directly, so the user confirms name/agent/setup. useAppStore.getState().recordFeatureInteraction('linear-tasks') openComposerForLinearItem(issue) }, @@ -8129,12 +7895,7 @@ export default function TaskPage(): React.JSX.Element { return (
    - {/* Why: pt-1.5 vertically centers this row's 32px icon cluster (X + - source toggles) with the sidebar's "Tasks" nav row. Sidebar Tasks - center sits 22px below the titlebar (pt-2 + py-1.5 + half size-4 - icon). Matching that here needs 6px top padding above the 32px - cluster (6 + 16 = 22). The previous pt-3 placed the cluster 6px - too low, breaking the visual band across the top chrome. */} + {/* Why: pt-1.5 (6px) aligns this 32px icon cluster's center with the sidebar Tasks row, 22px below the titlebar. */}
    - {/* Why: Close is anchored left in the same row as the - source icons so the top chrome is one compact band. - Left-aligned keeps it clear of the app sidebar on the - right edge. */} + {/* Why: Close is anchored left with the source icons for one compact band, clear of the app sidebar on the right. */}