diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index e140a4e37..517cb10ac 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -37,13 +37,29 @@ interface Window { switchToEditor: () => Promise; switchToHud: () => Promise; startNewRecording: () => Promise<{ success: boolean; error?: string }>; - openSourceSelector: () => Promise<{ + openSourceSelector: (options?: { + /** + * The renderer has finished waiting on macOS' Screen Recording prompt, so the + * "permission is required" dialog may open. Held back until then only because + * System Settings opening over that prompt is the bug this path exists to fix. + */ + screenPromptWaitElapsed?: boolean; + }) => Promise<{ opened: boolean; reason?: string; access?: { success: boolean; granted: boolean; + /** What the OS actually said. Never bent to steer the caller. */ status: string; + /** + * macOS' own prompt was raised by this very call and may still be unanswered. This, and + * not `status`, is what tells the renderer to keep polling: macOS reports the + * permission as absent for the whole time its prompt is on screen. + */ + promptRaised: boolean; + /** Granted, but this process cannot see it until the app is relaunched. */ + requiresRelaunch?: boolean; error?: string; }; }>; @@ -75,7 +91,16 @@ interface Window { requestScreenAccess: () => Promise<{ success: boolean; granted: boolean; + /** What the OS actually said. Never bent to steer the caller. */ status: string; + /** + * macOS' own prompt was raised by this very call and may still be unanswered. This, and + * not `status`, is what tells the renderer to keep polling: macOS reports the + * permission as absent for the whole time its prompt is on screen. + */ + promptRaised: boolean; + /** Granted, but this process cannot see it until the app is relaunched. */ + requiresRelaunch?: boolean; error?: string; }>; requestNativeMacCursorAccess: () => Promise<{ diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index aa2014670..72499d604 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -76,6 +76,10 @@ import { import { findPipeWireCursorHelperPath } from "../native-bridge/cursor/recording/pipeWireCursorRecordingSession"; import type { CursorRecordingSession } from "../native-bridge/cursor/recording/session"; import { toHelperRect } from "../native-bridge/helperCoordinates"; +import { + isMacScreenProbeUnavailable, + readMacScreenCaptureAccess, +} from "../native-bridge/screen/macScreenAccess"; import { scoreDeviceNameMatch } from "../recording/deviceNameMatching"; import { isSalvageableFragmentedCapture, @@ -90,6 +94,11 @@ import { patchWebmDurationOnDisk } from "../recording/webm-duration"; import { reindexRecordingOnDisk } from "../recording/webm-seek-index"; import { registerNativeBridgeHandlers } from "./nativeBridge"; import { RecordingStreamRegistry, registerRecordingStreamHandlers } from "./recordingStream"; +import { + resolveScreenAccessStatus, + ScreenPromptMarker, + shouldRaiseScreenPrompt, +} from "./screenAccessPrompt"; const PROJECT_FILE_EXTENSION = "openscreen"; export const SHORTCUTS_FILE = path.join(app.getPath("userData"), "shortcuts.json"); @@ -491,6 +500,68 @@ type AttachNativeMacWebcamRecordingInput = { let selectedSource: SelectedSource | null = null; let selectedDesktopSource: DesktopCapturerSource | null = null; +/** Whether this launch has already raised macOS' Screen Recording prompt. */ +let screenPromptRaisedThisLaunch = false; + +/** + * Settles once the startup TCC ask (the microphone prompt in main.ts) has been + * answered. macOS will not stack a second permission alert: a Screen Recording + * prompt raised while the mic alert is still up is silently dropped, and + * `app.focus({ steal: true })` yanks activation from the alert the user is + * reading. First launch hits exactly this — mic prompt at startup, Record + * clicked moments later — and it is the one launch whose prompt the marker + * would then write off for good. + */ +let startupMediaPromptGate: Promise = Promise.resolve(); + +export function setStartupMediaPromptGate(gate: Promise) { + startupMediaPromptGate = gate.catch(() => { + // A failed ask still settles the gate; the screen prompt proceeds. + }); +} +/** Lazily opened so the userData path is only touched once the app needs it. */ +let screenPromptMarkerInstance: ScreenPromptMarker | null = null; + +/** Same flag windows.ts gates every show() on: no window server in the e2e runs. */ +const IS_HEADLESS = process.env.HEADLESS === "true"; + +function getScreenPromptMarker(): ScreenPromptMarker { + screenPromptMarkerInstance ??= new ScreenPromptMarker(app.getPath("userData")); + return screenPromptMarkerInstance; +} + +/** Options the renderer passes to `open-source-selector`. */ +export interface SourceSelectorOptions { + /** + * The renderer has finished waiting on macOS' prompt, so the "permission is required" + * dialog may open. Held back until then only because System Settings opening over that + * prompt is the bug this path exists to fix -- and the renderer, which owns the retry + * budget, is the only side that knows when the wait is over. + */ + screenPromptWaitElapsed?: boolean; +} + +export interface ScreenAccessResult { + success: boolean; + granted: boolean; + /** What the OS actually said. Never bent to steer the caller. */ + status: string; + /** + * Whether THIS call raised macOS' own prompt, which may still be unanswered. + * This, and not `status`, is what tells a caller to keep polling: macOS keeps + * reporting the permission as absent for the whole time its prompt is on screen. + * Scoped to the raising call so every other request — the second click of a + * launch included — reaches the Settings dialog without sitting out the wait. + */ + promptRaised: boolean; + /** + * The permission is held, but this process cannot see it -- its cached read predates + * the grant, so capture in this process would still fail. Only ever true on macOS. + */ + requiresRelaunch?: boolean; + error?: string; +} + let lastEnumeratedSources = new Map(); let currentProjectPath: string | null = null; let currentRecordingSession: RecordingSession | null = null; @@ -1739,40 +1810,132 @@ export function registerIpcHandlers( onRecordingStateChange?: (recording: boolean, sourceName: string) => void, _switchToHud?: () => void, ) { - async function requestScreenAccess() { + /** + * Raises macOS' Screen Recording prompt, and records that it was raised. + * + * The recording path warms the same `getSources` call before starting the native + * capture helper, and deliberately still does: routing it through here would steal + * window focus at the instant a recording begins. It can therefore raise the prompt + * without leaving a mark, which costs at most one wait on a prompt macOS declines to + * redraw -- and it is only reachable after the selector, so this function has almost + * always run first. + * + * Raised from THIS process rather than from the capture helper. `desktopCapturer` is + * the app's own call into Chromium, which calls `CGRequestScreenCaptureAccess()` under + * it, so TCC records the grant against the app bundle's designated requirement. A bare + * child binary asking on its own behalf is what pins a row to a raw cdhash, which + * survives reinstalls and reads to the user as a permission that is on and does not work. + */ + function raiseScreenRecordingPrompt() { + const mainWin = getMainWindow(); + if (mainWin && !mainWin.isDestroyed()) { + if (mainWin.isMinimized()) { + mainWin.restore(); + } + // Same HEADLESS guard every other show() in the app respects: the macOS + // Playwright specs run with no window server to steal activation from. + if (!IS_HEADLESS) { + if (!mainWin.isVisible()) { + mainWin.show(); + } + mainWin.focus(); + } + } + if (!IS_HEADLESS) { + app.focus({ steal: true }); + } + + // The only call that raises the prompt. It settles in milliseconds whether or not + // the prompt is still on screen, and rejects outright where the permission is + // missing, so nothing can be learned from awaiting it -- the answer is read back + // from a fresh process instead. + desktopCapturer + .getSources({ types: ["screen"], thumbnailSize: { width: 1, height: 1 } }) + .catch(() => { + // Permission probing failure is reported by the explicit status read. + }); + + screenPromptRaisedThisLaunch = true; + getScreenPromptMarker().recordRaised(new Date().toISOString()); + } + + async function requestScreenAccess(): Promise { if (process.platform !== "darwin") { - return { success: true, granted: true, status: "granted" }; + return { success: true, granted: true, status: "granted", promptRaised: false }; } try { - const status = systemPreferences.getMediaAccessStatus("screen"); + // Two reads, and the difference between them is load bearing. + // + // `getMediaAccessStatus("screen")` is Chromium's `CGPreflightScreenCaptureAccess()`, + // which caches its answer for the life of this process: once it has said no it + // says no until the app is relaunched, whatever the user does in System Settings. + // The probe spawns a fresh process for every read, so it reports the permission as + // it stands now. See screenAccessPrompt.ts for why this is the whole bug. + const probe = await readMacScreenCaptureAccess(); + const appStatus = systemPreferences.getMediaAccessStatus("screen"); + const status = resolveScreenAccessStatus(probe, appStatus); + + if (isMacScreenProbeUnavailable(probe.status)) { + console.warn( + `[screen-access] permission probe unavailable (status=${probe.status}${ + probe.error ? `, error=${probe.error}` : "" + }); falling back to the app's own status=${appStatus}.`, + ); + } + if (status === "granted") { - return { success: true, granted: true, status }; + // Held, but this process may not be able to use it: a grant made after the app + // started is invisible to the cached read the capture stack goes through. The + // disagreement between the two reads is exactly that situation, and it is the + // only reliable signal macOS leaves us that a relaunch is needed. + return { + success: true, + granted: true, + status, + promptRaised: false, + requiresRelaunch: appStatus !== "granted", + }; } - // Screen recording has no askForMediaAccess equivalent, so trigger the - // TCC prompt without opening OpenScreen's source selector above it. - if (status === "not-determined") { - const mainWin = getMainWindow(); - if (mainWin && !mainWin.isDestroyed()) { - if (!mainWin.isVisible()) { - mainWin.show(); - } - mainWin.focus(); - } - app.focus({ steal: true }); - desktopCapturer - .getSources({ types: ["screen"], thumbnailSize: { width: 1, height: 1 } }) - .catch(() => { - // Permission probing failure is reported by the explicit status check below. - }); - return { success: true, granted: false, status: "not-determined" }; + const raiseNow = shouldRaiseScreenPrompt({ + status, + raisedThisLaunch: screenPromptRaisedThisLaunch, + raisedBefore: getScreenPromptMarker().hasRaisedBefore(), + }); + if (raiseNow) { + // Claim the launch's single raise before awaiting anything, so a + // concurrent request cannot start a second one while this waits. + screenPromptRaisedThisLaunch = true; + // Queue behind the startup mic alert (see startupMediaPromptGate). + // The marker is recorded inside the raise, on the other side of this + // await — recording it here would burn the machine's one prompt on + // an ask macOS never drew. + await startupMediaPromptGate; + raiseScreenRecordingPrompt(); } - return { success: true, granted: false, status }; + // `status` is what the OS actually said, always. Whether the caller should keep + // polling rides on `promptRaised` instead -- a separate field for a separate + // question, so no branch here has to misreport the permission to keep the + // renderer's retry loop alive. Scoped to the call that raised the prompt: a + // sticky per-launch value would arm the wait on every later click too, making + // each one sit out the full retry budget before the dialog it was owed at once. + return { + success: true, + granted: false, + status, + promptRaised: raiseNow, + }; } catch (error) { console.error("Failed to request screen access:", error); - return { success: false, granted: false, status: "unknown", error: String(error) }; + return { + success: false, + granted: false, + status: "unknown", + promptRaised: false, + error: String(error), + }; } } @@ -1959,7 +2122,7 @@ export function registerIpcHandlers( return access; }); - ipcMain.handle("open-source-selector", async () => { + ipcMain.handle("open-source-selector", async (_event, options?: SourceSelectorOptions) => { // Nothing to open on Linux WHEN THE NATIVE HELPER IS THERE. The selector's // own `desktopCapturer.getSources()` raises a portal dialog — a SECOND // one, for a session that is thrown away — and whatever it returns cannot @@ -1974,8 +2137,43 @@ export function registerIpcHandlers( } const access = await requestScreenAccess(); + + // Held, but not by this process. Chromium's capture stack reads the permission + // through a value cached before the grant existed, so opening the picker here + // produces a source list it cannot fill and no way out of it. Apple's guidance -- + // and the warning System Settings prints beside the toggle -- is to relaunch, so + // offer exactly that instead of a picker that cannot work. + if (access.granted && access.requiresRelaunch) { + const mainWin = getMainWindow(); + const messageOptions = { + type: "info", + buttons: ["Restart OpenScreen", "Later"], + defaultId: 0, + cancelId: 1, + message: "Screen Recording permission granted", + detail: + "macOS applies this permission when OpenScreen restarts. Restart now to choose a screen or window.", + } satisfies Electron.MessageBoxOptions; + const result = + mainWin && !mainWin.isDestroyed() + ? await dialog.showMessageBox(mainWin, messageOptions) + : await dialog.showMessageBox(messageOptions); + if (result.response === 0) { + app.relaunch(); + app.quit(); + } + return { opened: false, reason: "screen-access-relaunch-required", access }; + } + if (!access.granted) { - if (process.platform === "darwin" && access.status !== "not-determined") { + // Withheld only while macOS' own prompt from this launch may still be on screen: + // opening System Settings over it is the bug this whole path exists to fix. Every + // other refusal gets the dialog immediately, including the second click of a + // launch and every launch after the first. + if ( + process.platform === "darwin" && + (!access.promptRaised || options?.screenPromptWaitElapsed === true) + ) { const mainWin = getMainWindow(); const messageOptions = { type: "warning", diff --git a/electron/ipc/screenAccessPrompt.test.ts b/electron/ipc/screenAccessPrompt.test.ts new file mode 100644 index 000000000..77fe8bc32 --- /dev/null +++ b/electron/ipc/screenAccessPrompt.test.ts @@ -0,0 +1,129 @@ +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + resolveScreenAccessStatus, + ScreenPromptMarker, + shouldRaiseScreenPrompt, +} from "./screenAccessPrompt"; + +describe("resolveScreenAccessStatus", () => { + it("reports what the fresh probe read, not the app's cached status", () => { + // The regression this guards: the app's own read is frozen at whatever it saw + // first, so a grant made while the app is running is invisible to it. + expect(resolveScreenAccessStatus({ granted: true, status: "granted" }, "denied")).toBe( + "granted", + ); + expect(resolveScreenAccessStatus({ granted: false, status: "denied" }, "granted")).toBe( + "denied", + ); + }); + + it("falls back to the app's status when the probe could not answer", () => { + // A build without the helper, or a helper that crashed, must land on the old + // behaviour rather than accusing the user of refusing the permission. + for (const status of ["missing-helper", "error", "exited", "timeout"] as const) { + expect(resolveScreenAccessStatus({ granted: false, status }, "restricted")).toBe( + "restricted", + ); + } + }); +}); + +describe("shouldRaiseScreenPrompt", () => { + it("raises the prompt on the first ask, even though macOS reports denied", () => { + // The bug: macOS collapses "never asked" into "denied", so a first run skipped + // the prompt entirely and offered only a manual toggle in System Settings. + expect( + shouldRaiseScreenPrompt({ status: "denied", raisedThisLaunch: false, raisedBefore: false }), + ).toBe(true); + }); + + it("never prompts once the permission is held", () => { + expect( + shouldRaiseScreenPrompt({ status: "granted", raisedThisLaunch: false, raisedBefore: false }), + ).toBe(false); + }); + + it("does not re-prompt within a launch", () => { + expect( + shouldRaiseScreenPrompt({ status: "denied", raisedThisLaunch: true, raisedBefore: false }), + ).toBe(false); + }); + + it("does not prompt a user who has already been asked on this machine", () => { + // macOS shows the prompt once per TCC decision and ignores every later request. + // Asking again would cost a refusing user the Settings dialog -- the one message + // they can act on -- in exchange for a prompt that never appears. + expect( + shouldRaiseScreenPrompt({ status: "denied", raisedThisLaunch: false, raisedBefore: true }), + ).toBe(false); + }); + + it("leaves a policy-restricted Mac on its actionable message", () => { + // The cell that a status-blind "anything but granted" rule gets wrong: an MDM + // machine can never grant the permission, so a prompt cannot appear and the wait + // for one costs the user the only message that explains the refusal. + expect( + shouldRaiseScreenPrompt({ + status: "restricted", + raisedThisLaunch: false, + raisedBefore: false, + }), + ).toBe(false); + expect( + shouldRaiseScreenPrompt({ status: "unknown", raisedThisLaunch: false, raisedBefore: false }), + ).toBe(false); + }); + + it("still prompts on not-determined, for a platform that can report it", () => { + expect( + shouldRaiseScreenPrompt({ + status: "not-determined", + raisedThisLaunch: false, + raisedBefore: false, + }), + ).toBe(true); + }); +}); + +describe("ScreenPromptMarker", () => { + let userData: string; + + beforeEach(() => { + userData = mkdtempSync(path.join(tmpdir(), "openscreen-screen-access-")); + }); + + afterEach(() => { + rmSync(userData, { recursive: true, force: true }); + vi.restoreAllMocks(); + }); + + it("starts unmarked on a machine that has never been asked", () => { + expect(new ScreenPromptMarker(userData).hasRaisedBefore()).toBe(false); + }); + + it("remembers across launches that the prompt was raised", () => { + new ScreenPromptMarker(userData).recordRaised("2026-08-31T00:00:00.000Z"); + + expect(new ScreenPromptMarker(userData).hasRaisedBefore()).toBe(true); + }); + + it("treats an unreadable or malformed marker as never asked", () => { + writeFileSync(path.join(userData, "screen-access.json"), "{ not json", "utf8"); + + expect(new ScreenPromptMarker(userData).hasRaisedBefore()).toBe(false); + }); + + it("still gets the rest of the launch right when the marker cannot be written", () => { + // An unwritable userData directory must not stop the prompt the user is waiting on. + const marker = new ScreenPromptMarker(path.join(userData, "does", "not", "exist")); + vi.spyOn(console, "warn").mockImplementation(() => { + // The failed write logs; keep the test output readable. + }); + + expect(() => marker.recordRaised("2026-08-31T00:00:00.000Z")).not.toThrow(); + expect(marker.hasRaisedBefore()).toBe(true); + }); +}); diff --git a/electron/ipc/screenAccessPrompt.ts b/electron/ipc/screenAccessPrompt.ts new file mode 100644 index 000000000..37f0958dc --- /dev/null +++ b/electron/ipc/screenAccessPrompt.ts @@ -0,0 +1,159 @@ +import { readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; + +/** + * Deciding when to raise macOS' own Screen Recording prompt, and when to stop. + * + * Two macOS facts drive everything here, and both are the opposite of what the old + * code assumed: + * + * 1. `getMediaAccessStatus("screen")` cannot report "not-determined". Chromium resolves + * it through `CGPreflightScreenCaptureAccess()`, a bool, so a machine that has never + * been asked is reported exactly like an explicit refusal. Gating the prompt on + * "not-determined" therefore never fired, and a fresh install fell straight through + * to the "open System Settings" dialog with macOS never given the chance to ask. + * + * 2. That same preflight caches its answer for the life of the calling process. The app + * is long-lived, so once it has read false it reads false until relaunch, whatever the + * user does in System Settings. This is why polling the app's own status after raising + * the prompt can never observe a grant -- no delay tunes into correctness -- and why + * the real answer is read from a fresh child process instead + * (`native-bridge/screen/macScreenAccess.ts`). + * + * What is left is one thing macOS genuinely will not tell us: whether a TCC decision + * already exists. "Never asked" and "refused" are the same bool. So the app records for + * itself whether it has ever raised the prompt on this machine, which is the only honest + * way to tell a first run from a user who said no -- and it is what keeps a refusing user + * on the immediate, actionable Settings dialog instead of a wait for a prompt that macOS + * will never show them again. + */ + +/** Statuses the fresh-process probe can report; mirrors MacScreenAccessStatus. */ +export type ScreenAccessProbeStatus = + | "granted" + | "denied" + | "missing-helper" + | "error" + | "exited" + | "timeout"; + +export interface ScreenAccessProbe { + granted: boolean; + status: ScreenAccessProbeStatus; +} + +/** True when the probe never got far enough to answer the permission question. */ +function probeAnswered(status: ScreenAccessProbeStatus) { + return status === "granted" || status === "denied"; +} + +/** + * The status to report, preferring the uncached probe over the app's own stale read. + * + * Falls back to whatever `getMediaAccessStatus` said when the probe could not answer -- + * no helper in the build, a loader failure, a crash, a hang. That fallback is the old + * behaviour exactly, so a build without the helper is no worse off than before, and a + * broken helper never becomes a permission refusal in the user's face. + */ +export function resolveScreenAccessStatus( + probe: ScreenAccessProbe, + fallbackStatus: string, +): string { + return probeAnswered(probe.status) ? probe.status : fallbackStatus; +} + +export interface ScreenPromptDecisionInput { + /** The resolved permission status, preferring the fresh probe over the app's own read. */ + status: string; + /** Whether this launch has already raised the prompt. */ + raisedThisLaunch: boolean; + /** Whether this app has ever raised the prompt on this machine. */ + raisedBefore: boolean; +} + +/** + * Whether to raise macOS' prompt now. + * + * Only on the first ask ever, and only where a grant is actually reachable. macOS shows + * the prompt once per TCC decision and silently ignores every later request, so asking + * again buys nothing -- while the window-focus steal that goes with it, and the wait for + * an answer that cannot come, cost a user who has already refused the one message they + * can act on. + */ +export function shouldRaiseScreenPrompt({ + status, + raisedThisLaunch, + raisedBefore, +}: ScreenPromptDecisionInput): boolean { + if (status === "granted") { + return false; + } + + // An allowlist, not "anything that is not granted". `restricted` means policy + // forbids the grant -- an MDM-managed Mac -- so no prompt can appear and the user + // cannot act on one. Sending those machines down the prompt path would swap their + // only actionable message for a wait on an answer that is never coming. + if (status !== "denied" && status !== "not-determined") { + return false; + } + + return !raisedThisLaunch && !raisedBefore; +} + +const MARKER_FILE = "screen-access.json"; + +interface ScreenPromptMarkerFile { + /** ISO timestamp of the first time this app raised the macOS prompt here. */ + promptRaisedAt?: string; +} + +/** + * Remembers, across launches, that the macOS prompt has been raised on this machine. + * + * Deliberately a plain file rather than anything derived from TCC: the TCC database is + * unreadable without disabling SIP, and its "is there a decision" bit is exactly what + * the OS refuses to expose. This is the app's own note to itself, and it is only ever + * used to choose between "raise the prompt" and "show the dialog" -- never as an answer + * to whether the permission is held, which always comes from a live read. + * + * A stale marker (the user reset TCC with `tccutil`) costs the prompt on the next run and + * leaves the Settings dialog, which still works. A missing one costs a duplicate prompt + * that macOS discards. Both fail towards a message the user can act on. + */ +export class ScreenPromptMarker { + private readonly markerPath: string; + private raised: boolean; + + constructor(userDataPath: string) { + this.markerPath = path.join(userDataPath, MARKER_FILE); + this.raised = this.loadSync(); + } + + private loadSync(): boolean { + try { + const parsed = JSON.parse(readFileSync(this.markerPath, "utf8")) as ScreenPromptMarkerFile; + return typeof parsed.promptRaisedAt === "string"; + } catch { + return false; + } + } + + hasRaisedBefore(): boolean { + return this.raised; + } + + /** + * Records that the prompt has been raised. Best-effort on disk: an unwritable + * userData directory must not stop the prompt the user is waiting on, and the + * in-memory flag still gets the rest of this launch right. + */ + recordRaised(nowIso: string): void { + this.raised = true; + try { + const payload: ScreenPromptMarkerFile = { promptRaisedAt: nowIso }; + writeFileSync(this.markerPath, JSON.stringify(payload), "utf8"); + } catch (error) { + console.warn("Failed to persist the screen prompt marker:", error); + } + } +} diff --git a/electron/main.ts b/electron/main.ts index a85629bf3..56f0d2c12 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -45,6 +45,7 @@ import { exportDiagnosticFile, getSelectedDesktopSource, registerIpcHandlers, + setStartupMediaPromptGate, } from "./ipc/handlers"; import { installMainProcessErrorGuards } from "./main-process-errors"; import { registerSttIpc, shutdownStt } from "./stt"; @@ -1065,10 +1066,15 @@ appReady?.then(async () => { if (process.platform === "darwin") { const micStatus = systemPreferences.getMediaAccessStatus("microphone"); if (micStatus !== "granted") { - systemPreferences - .askForMediaAccess("microphone") - .then((granted) => console.info(`[permissions] microphone granted=${granted}`)) - .catch((error) => console.warn("[permissions] microphone request failed:", error)); + // The Screen Recording prompt queues behind this ask — macOS will not + // stack a second permission alert, so raising it mid-mic-prompt shows + // nothing and (worse) records a prompt that never appeared. + setStartupMediaPromptGate( + systemPreferences + .askForMediaAccess("microphone") + .then((granted) => console.info(`[permissions] microphone granted=${granted}`)) + .catch((error) => console.warn("[permissions] microphone request failed:", error)), + ); } } diff --git a/electron/native-bridge/screen/macScreenAccess.test.ts b/electron/native-bridge/screen/macScreenAccess.test.ts new file mode 100644 index 000000000..c3441c9af --- /dev/null +++ b/electron/native-bridge/screen/macScreenAccess.test.ts @@ -0,0 +1,174 @@ +import { EventEmitter } from "node:events"; +import { PassThrough } from "node:stream"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +/** + * The cast on `actual` is written out in each factory rather than shared in a helper: + * `vi.mock` calls are HOISTED above every top-level statement, so a module-scope helper + * is still in its temporal dead zone when the factory runs. + */ +type WithDefault = { default?: Record }; + +vi.mock("node:child_process", async (importOriginal) => { + const actual = await importOriginal(); + const spawn = vi.fn(); + return { ...actual, spawn, default: { ...((actual as WithDefault).default ?? {}), spawn } }; +}); + +const mocks = vi.hoisted(() => ({ accessSync: vi.fn() })); + +vi.mock("node:fs", async (importOriginal) => { + const actual = await importOriginal(); + // No helper binary exists in a test checkout; by default pretend the first candidate + // path is executable so path resolution is not what is under test. + return { + ...actual, + accessSync: mocks.accessSync, + default: { ...((actual as WithDefault).default ?? {}), accessSync: mocks.accessSync }, + }; +}); + +import { spawn } from "node:child_process"; +import { isMacScreenProbeUnavailable, readMacScreenCaptureAccess } from "./macScreenAccess"; + +/** Minimal stand-in for the helper: stdio pipes plus kill bookkeeping. */ +class FakeHelper extends EventEmitter { + stdout = new PassThrough(); + stderr = new PassThrough(); + killed = false; + + kill() { + this.killed = true; + return true; + } + + /** Feeds one NDJSON line, the way the real helper emits them. */ + emitEvent(event: Record) { + this.stdout.write(`${JSON.stringify(event)}\n`); + } +} + +const spawnMock = vi.mocked(spawn); +let helper: FakeHelper; +let originalPlatform: PropertyDescriptor | undefined; + +beforeEach(() => { + originalPlatform = Object.getOwnPropertyDescriptor(process, "platform"); + Object.defineProperty(process, "platform", { value: "darwin", configurable: true }); + helper = new FakeHelper(); + spawnMock.mockReset(); + spawnMock.mockReturnValue(helper as unknown as ReturnType); + mocks.accessSync.mockReset(); +}); + +afterEach(() => { + if (originalPlatform) { + Object.defineProperty(process, "platform", originalPlatform); + } + vi.restoreAllMocks(); +}); + +/** Lets the spawn listeners attach before the fake helper speaks. */ +async function settle(pending: Promise, act: () => void): Promise { + await Promise.resolve(); + act(); + return pending; +} + +describe("readMacScreenCaptureAccess", () => { + it("grants when the helper reports the permission", async () => { + const access = await settle(readMacScreenCaptureAccess(), () => + helper.emitEvent({ event: "screen-access", granted: true }), + ); + + expect(access).toMatchObject({ success: true, granted: true, status: "granted" }); + }); + + it("denies when the helper reports the permission is absent", async () => { + const access = await settle(readMacScreenCaptureAccess(), () => + helper.emitEvent({ event: "screen-access", granted: false }), + ); + + expect(access).toMatchObject({ success: true, granted: false, status: "denied" }); + }); + + it("spawns the probe flag and never a recording request", async () => { + await settle(readMacScreenCaptureAccess(), () => + helper.emitEvent({ event: "screen-access", granted: true }), + ); + + expect(spawnMock).toHaveBeenCalledTimes(1); + expect(spawnMock.mock.calls[0]?.[1]).toEqual(["--screen-access-status"]); + }); + + it("reads a fresh answer per call, which is the point of the child process", async () => { + // The main process cannot do this: CGPreflightScreenCaptureAccess caches its + // result for the life of the caller, so a grant made while the app runs is + // invisible to it. Every call here is a new process, so the second read sees + // the grant the first one missed. + const first = await settle(readMacScreenCaptureAccess(), () => + helper.emitEvent({ event: "screen-access", granted: false }), + ); + helper = new FakeHelper(); + spawnMock.mockReturnValue(helper as unknown as ReturnType); + const second = await settle(readMacScreenCaptureAccess(), () => + helper.emitEvent({ event: "screen-access", granted: true }), + ); + + expect(first.granted).toBe(false); + expect(second.granted).toBe(true); + expect(spawnMock).toHaveBeenCalledTimes(2); + }); + + it("reports missing-helper rather than a refusal when no binary is installed", async () => { + mocks.accessSync.mockImplementation(() => { + throw new Error("ENOENT"); + }); + + const access = await readMacScreenCaptureAccess(); + + expect(access).toMatchObject({ granted: false, status: "missing-helper" }); + expect(isMacScreenProbeUnavailable(access.status)).toBe(true); + expect(spawnMock).not.toHaveBeenCalled(); + }); + + it("reads the answer even when the helper dies in the same tick", async () => { + // The helper prints one line and exits immediately, so the process-death event can + // land before stdout has been drained. Racing it away would report a good answer + // as a dead helper on a machine that is merely fast. + const access = await settle(readMacScreenCaptureAccess(), () => { + helper.emitEvent({ event: "screen-access", granted: true }); + helper.emit("close", 0, null); + }); + + expect(access).toMatchObject({ granted: true, status: "granted" }); + }); + + it("reports exited rather than a refusal when an older helper rejects the flag", async () => { + // A build predating the probe mode answers `invalidArguments` and exits 1. + // Calling that a denial would tell a user with a working grant to go and + // re-grant it. + const access = await settle(readMacScreenCaptureAccess(), () => helper.emit("close", 1, null)); + + expect(access).toMatchObject({ granted: false, status: "exited" }); + expect(isMacScreenProbeUnavailable(access.status)).toBe(true); + }); + + it("reports error when the helper cannot be launched at all", async () => { + const access = await settle(readMacScreenCaptureAccess(), () => + helper.emit("error", new Error("EACCES")), + ); + + expect(access).toMatchObject({ granted: false, status: "error", error: "EACCES" }); + expect(isMacScreenProbeUnavailable(access.status)).toBe(true); + }); + + it("answers granted off-darwin without spawning anything", async () => { + Object.defineProperty(process, "platform", { value: "win32", configurable: true }); + + const access = await readMacScreenCaptureAccess(); + + expect(access).toMatchObject({ granted: true, status: "granted" }); + expect(spawnMock).not.toHaveBeenCalled(); + }); +}); diff --git a/electron/native-bridge/screen/macScreenAccess.ts b/electron/native-bridge/screen/macScreenAccess.ts new file mode 100644 index 000000000..f167048a3 --- /dev/null +++ b/electron/native-bridge/screen/macScreenAccess.ts @@ -0,0 +1,187 @@ +import { spawn } from "node:child_process"; +import { accessSync, constants as fsConstants } from "node:fs"; +import path from "node:path"; + +/** + * Reading macOS' Screen Recording grant from a short-lived child process. + * + * `CGPreflightScreenCaptureAccess()` caches its answer for the life of the process + * that calls it. Once it has answered false it answers false forever, whatever the + * user does in System Settings afterwards -- Apple's own guidance is to relaunch. + * Electron's `systemPreferences.getMediaAccessStatus("screen")` is that same + * function (Chromium's `IsScreenCaptureAllowed()` in `ui/base/cocoa/permissions_utils.mm`), + * so the app's main process holds one stale bool for its entire run. + * + * That is the whole reason this module exists. The helper is spawned fresh for every + * read, so every read is the current answer, and a grant the user makes while the app + * is running becomes observable without a restart. + * + * The prompt is NOT raised here. It stays in the main process, where Chromium raises + * it through the app bundle, so TCC records the grant against the app's designated + * requirement rather than against a bare child binary. + */ + +const HELPER_NAME = "openscreen-screencapturekit-helper"; + +/** Kept in step with `screenAccessStatusFlag` in ScreenCaptureRecorder.swift. */ +const SCREEN_ACCESS_STATUS_FLAG = "--screen-access-status"; + +/** + * The helper prints one line and exits, so this bounds a hung spawn rather than a + * slow answer. Shorter than the cursor helper's budget because nothing here waits + * on a window server handshake. + */ +const PROBE_TIMEOUT_MS = 3_000; + +/** + * Why `denied` is the only status that means "the user said no". + * + * The other four mean the helper never got to answer -- absent from the build, killed + * by the loader, crashed, or hung. Treating those as a refusal is what would put the + * "grant Screen Recording" dialog in front of a user whose permission is fine, which is + * the same failure the cursor helper's `missing-helper` split exists to prevent (#515). + */ +export type MacScreenAccessStatus = + | "granted" + | "denied" + | "missing-helper" + | "error" + | "exited" + | "timeout"; + +export interface MacScreenAccessResult { + success: boolean; + granted: boolean; + status: MacScreenAccessStatus; + error?: string; +} + +/** True when the probe never got far enough to answer the permission question. */ +export function isMacScreenProbeUnavailable(status: MacScreenAccessStatus) { + return ( + status === "missing-helper" || status === "error" || status === "exited" || status === "timeout" + ); +} + +function helperCandidates() { + const envPath = process.env.OPENSCREEN_SCK_CAPTURE_EXE?.trim(); + const appRoot = process.env.APP_ROOT ? path.resolve(process.env.APP_ROOT) : process.cwd(); + const archTag = process.arch === "arm64" ? "darwin-arm64" : "darwin-x64"; + const resourceRoot = + typeof process.resourcesPath === "string" + ? process.resourcesPath + : path.join(appRoot, "resources"); + + return [ + envPath, + path.join(appRoot, "electron", "native", "screencapturekit", "build", HELPER_NAME), + path.join(appRoot, "electron", "native", "bin", archTag, HELPER_NAME), + path.join(resourceRoot, "electron", "native", "bin", archTag, HELPER_NAME), + ].filter((candidate): candidate is string => Boolean(candidate)); +} + +export function findMacScreenAccessHelperPath() { + for (const candidate of helperCandidates()) { + try { + accessSync(candidate, fsConstants.X_OK); + return candidate; + } catch { + // Try the next helper location. + } + } + + return null; +} + +/** + * Reads the current Screen Recording grant, uncached. + * + * Never prompts and never blocks on the user: the helper calls the preflight function + * only, so this is safe to poll while macOS' own prompt is on screen. + */ +export async function readMacScreenCaptureAccess(): Promise { + if (process.platform !== "darwin") { + return { success: true, granted: true, status: "granted" }; + } + + const helperPath = findMacScreenAccessHelperPath(); + if (!helperPath) { + return { success: true, granted: false, status: "missing-helper" }; + } + + return new Promise((resolve) => { + const child = spawn(helperPath, [SCREEN_ACCESS_STATUS_FLAG], { + stdio: ["ignore", "pipe", "pipe"], + }); + let settled = false; + let lineBuffer = ""; + + const finish = (result: MacScreenAccessResult) => { + if (settled) { + return; + } + settled = true; + clearTimeout(timer); + if (!child.killed) { + child.kill("SIGTERM"); + } + resolve(result); + }; + + const timer = setTimeout(() => { + finish({ + success: false, + granted: false, + status: "timeout", + error: "Timed out reading the macOS screen recording permission", + }); + }, PROBE_TIMEOUT_MS); + + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (chunk: string) => { + lineBuffer += chunk; + const lines = lineBuffer.split(/\r?\n/); + lineBuffer = lines.pop() ?? ""; + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) { + continue; + } + try { + const event = JSON.parse(trimmed) as { event?: string; granted?: boolean }; + if (event.event === "screen-access") { + finish({ + success: true, + granted: event.granted === true, + status: event.granted === true ? "granted" : "denied", + }); + return; + } + } catch { + // Ignore non-JSON helper output. + } + } + }); + + child.once("error", (error) => { + finish({ success: false, granted: false, status: "error", error: error.message }); + }); + + // `close`, not `exit`. This helper prints one line and dies, and `exit` can fire + // before stdout has been drained to the listener above -- which would report a + // perfectly good answer as a dead helper. `close` waits for the stdio streams. + // + // Reaching it at all means the helper ran and said nothing: an older build without + // the flag, which answers `invalidArguments` and exits 1. Reported as `exited` + // rather than a refusal, so the caller falls back to the app's own status instead + // of accusing the user of denying a permission they may well hold. + child.once("close", (code, signal) => { + finish({ + success: false, + granted: false, + status: "exited", + error: `macOS screen access probe exited (code=${code}, signal=${signal})`, + }); + }); + }); +} diff --git a/electron/native/README.md b/electron/native/README.md index 9bd69ae81..39058964e 100644 --- a/electron/native/README.md +++ b/electron/native/README.md @@ -9,6 +9,8 @@ macOS native recording will use a ScreenCaptureKit helper with the same process 3. The helper owns ScreenCaptureKit/AVFoundation capture, timing, encoding, and muxing. 4. Electron persists the resulting media/session manifest and reports helper errors explicitly. +The helper has one non-recording mode: `openscreen-screencapturekit-helper --screen-access-status` prints `{"event":"screen-access","granted":}` and exits. `CGPreflightScreenCaptureAccess()` caches its answer for the life of the calling process, so Electron's own read can never observe a Screen Recording grant made after launch — a helper spawned per read has no cache to be stale (`electron/native-bridge/screen/macScreenAccess.ts`). + Helper locations: 1. `OPENSCREEN_SCK_CAPTURE_EXE`, for local development and diagnostics. @@ -89,8 +91,8 @@ Encoder selection: by default the helper keeps the existing sink-writer path fir Frame input path: the helper feeds the encoder from the GPU when it can. On that path it copies the WGC frame across a keyed-mutex bridge to a second D3D11 device, converts BGRA to NV12 with the D3D11 video processor, and submits an allocator-owned DXGI sample to the hardware H.264 encoder, so no frame ever passes through system memory. The alternative is the original path: a staging texture, `Map(D3D11_MAP_READ)`, and a row-by-row copy into an `IMFMediaBuffer` — which is where a driver stall costs a recording (issue #252). The GPU path is a preference, never a requirement: it is skipped outright for `preferSoftwareEncoder` and for inline webcam PiP (both need the frame in system memory), and it degrades to the CPU path on its own if the encoding device, the NV12 video processor, the shared bridge texture, the DXGI sample allocator, or the hardware sink writer is unavailable. The GPU path is OFF by default: it fixed #252 on the machine that reproduces it and broke recording outright in #336, and its fallbacks only cover failures during `initialize()`, not one that appears once frames are flowing. Set `OPENSCREEN_WGC_ENABLE_DXGI_INPUT=1` to turn it on. Because the two paths land on different encoders and hardware MFTs default to constant bitrate, the GPU path asks for VBR through `ICodecAPI`; without it a static screen spends the full configured budget (measured 16.9 Mbps against 1.95 for the same desktop). -The helper reports the outcome through the `encoder-selection` stdout event (`video` is `default`, `software-preferred`, or `software-fallback`; `videoInput` is `dxgi-nv12` or `cpu-rgb32`; `container` is `fragmented-mp4` or `mp4`; all three report what the encoder settled on rather than what was asked for). On the GPU path the helper also prints one `[frame-drops] gpu_bridge_contended=` line to stderr at stop: a frame the bridge was too busy to take is skipped rather than failing the recording, and a large count there is the first thing to look at in a report about missing frames. When the app sees `software-fallback` — the default encoder failed and the helper switched on its own — it shows a small dismissible notice in the recording HUD with a "Don't show again" option, because software encoding can raise CPU usage. An explicit `software-preferred` selection shows no notice, and the event stays available for diagnostics either way. - +The helper reports the outcome through the `encoder-selection` stdout event (`video` is `default`, `software-preferred`, or `software-fallback`; `videoInput` is `dxgi-nv12` or `cpu-rgb32`; `container` is `fragmented-mp4` or `mp4`; all three report what the encoder settled on rather than what was asked for). On the GPU path the helper also prints one `[frame-drops] gpu_bridge_contended=` line to stderr at stop: a frame the bridge was too busy to take is skipped rather than failing the recording, and a large count there is the first thing to look at in a report about missing frames. When the app sees `software-fallback` — the default encoder failed and the helper switched on its own — it shows a small dismissible notice in the recording HUD with a "Don't show again" option, because software encoding can raise CPU usage. An explicit `software-preferred` selection shows no notice, and the event stays available for diagnostics either way. + At startup the helper also emits `capture-adapter`, naming the GPU its D3D device landed on and the one actually driving the captured display, each with its LUID, plus one `[adapters]` line per enumerated adapter on stderr. `createD3DDevice` asks for the *default* adapter and nothing checks that it is the one driving the display; when they differ every frame crosses an adapter boundary before the caller touches it. The LUIDs are there because the descriptions are not enough to tell: an IddCx virtual display driver renders through the physical GPU and inherits its description string while being a separate DXGI adapter, so the configuration this diagnostic exists to catch is precisely the one where both names are identical and only the LUIDs differ (measured: `NVIDIA Quadro RTX 4000` at LUID `0:24084` driving the display, the same string at `0:12889146` for the virtual adapter). `monitorLookup` says which of three things happened: `ok`, `no-output-claims-it` (the enumeration finished and nothing owns the captured monitor, which is what an active virtual display looks like), or `unavailable` (`EnumOutputs` refused, as it does in session 0 — the outputs were never inspected, so the absence means nothing about the hardware). Encoder diagnostic on final sink-writer failure: when the final sink-writer attempt fails (`MFCreateSinkWriterFromMediaSink` on the fragmented container, `MFCreateSinkWriterFromURL` on the plain one; the message names which), the helper logs the registered H.264 video encoder MFT count (via `MFTEnumEx`), the registered AAC encoder count when audio was requested, and the hex HRESULT. If no H.264 encoder is registered, it additionally emits the four-bullet actionable error (missing Media Feature Pack / GPU driver registration / empty `HKLM:\SOFTWARE\Microsoft\Windows Media Foundation\Transforms` / reboot). If an H.264 encoder IS registered but the sink writer still failed, it logs a hint pointing at invalid output path, missing MP4 mux, or GPU driver incompatibility. There is still no fail-fast pre-flight gate because `MFTEnumEx` and the sink writer can disagree about which H.264 encoders are available in non-interactive / Session 0 contexts. diff --git a/electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/ScreenCaptureRecorder.swift b/electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/ScreenCaptureRecorder.swift index 5add8074d..9f3cdf8ca 100644 --- a/electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/ScreenCaptureRecorder.swift +++ b/electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/ScreenCaptureRecorder.swift @@ -814,10 +814,35 @@ struct OpenScreenScreenCaptureKitHelper { _ = CGMainDisplayID() } + /// The flag that turns this helper into a one-shot answer to "may we record the + /// screen", printed as the usual single JSON line and nothing else. + private static let screenAccessStatusFlag = "--screen-access-status" + static func main() async { do { initializeCoreGraphicsWindowServerConnection() + // Answered from a process that exists for one read and then dies, because a FRESH + // PROCESS is the only place the answer can be trusted. + // `CGPreflightScreenCaptureAccess()` caches its result for the life of the calling + // process: once it has answered false it answers false forever, whatever the user + // does in System Settings afterwards. The app is long-lived, and Chromium's + // `getMediaAccessStatus("screen")` goes through that same function, so from the + // first miss until the next relaunch the app cannot observe its own permission + // being granted. That staleness -- not the missing prompt alone -- is what left + // the permission unreachable without a restart. + // + // Deliberately BEFORE the macOS 13 guard and the request decode below: the question + // is asked on every macOS the app supports, and answering it needs neither + // ScreenCaptureKit nor a recording request. + if CommandLine.arguments.count == 2, CommandLine.arguments[1] == screenAccessStatusFlag { + emit([ + "event": "screen-access", + "granted": CGPreflightScreenCaptureAccess(), + ]) + exit(0) + } + guard CommandLine.arguments.count == 2 else { throw HelperError.invalidArguments } diff --git a/electron/preload.ts b/electron/preload.ts index 6aff16407..bab24a150 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -109,8 +109,8 @@ contextBridge.exposeInMainWorld("electronAPI", { startNewRecording: () => { return ipcRenderer.invoke("start-new-recording"); }, - openSourceSelector: () => { - return ipcRenderer.invoke("open-source-selector"); + openSourceSelector: (options?: { screenPromptWaitElapsed?: boolean }) => { + return ipcRenderer.invoke("open-source-selector", options); }, openNotes: () => { return ipcRenderer.invoke("open-notes"); diff --git a/src/components/launch/LaunchWindow.test.tsx b/src/components/launch/LaunchWindow.test.tsx index 60894f9e0..2438e15c5 100644 --- a/src/components/launch/LaunchWindow.test.tsx +++ b/src/components/launch/LaunchWindow.test.tsx @@ -211,6 +211,7 @@ function stubElectronAPI(getSelectedSource: Window["electronAPI"]["getSelectedSo success: true, granted: true, status: "granted", + promptRaised: false, })), // Follows the platform under test. Pinned to "darwin" before, which was // invisible while only `nativeBridgeClient` was consulted for it — and diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index 8a2427fc1..da4db3689 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -39,7 +39,10 @@ import { HUD_STACK_GAP, } from "./hudGeometry"; import styles from "./LaunchWindow.module.css"; -import { openSourceSelectorWithPermissionRetry } from "./openSourceSelectorFlow"; +import { + type OpenSourceSelectorResult, + openSourceSelectorWithPermissionRetry, +} from "./openSourceSelectorFlow"; // Locale list is computed once at module load; keeping the reference stable lets // the language menu sit behind a memo boundary. @@ -586,15 +589,28 @@ export function LaunchWindow() { }; }, [applySelectedSource, recording, toggleRecording]); + // One flow at a time, shared by the Record button and the source chip. The + // permission wait keeps this promise pending for seconds while the button + // stays live, and every extra click used to start a concurrent retry loop — + // all of them fighting over recordAfterSourceSelectionRef. + const sourceSelectorFlowRef = useRef | null>(null); const openSourceSelector = useCallback(async () => { - if (window.electronAPI) { - return await openSourceSelectorWithPermissionRetry({ - openSourceSelector: () => window.electronAPI.openSourceSelector(), - requestScreenAccess: () => window.electronAPI.requestScreenAccess(), - }); + if (!window.electronAPI) { + return { opened: false, reason: "electron-api-unavailable" }; } - return { opened: false, reason: "electron-api-unavailable" }; + if (sourceSelectorFlowRef.current) { + return sourceSelectorFlowRef.current; + } + + const flow = openSourceSelectorWithPermissionRetry({ + openSourceSelector: (options) => window.electronAPI.openSourceSelector(options), + requestScreenAccess: () => window.electronAPI.requestScreenAccess(), + }).finally(() => { + sourceSelectorFlowRef.current = null; + }); + sourceSelectorFlowRef.current = flow; + return flow; }, []); const handleRecordButtonClick = useCallback( diff --git a/src/components/launch/openSourceSelectorFlow.test.ts b/src/components/launch/openSourceSelectorFlow.test.ts index ac80f1adc..63ca04543 100644 --- a/src/components/launch/openSourceSelectorFlow.test.ts +++ b/src/components/launch/openSourceSelectorFlow.test.ts @@ -1,6 +1,13 @@ import { describe, expect, it, vi } from "vitest"; import { openSourceSelectorWithPermissionRetry } from "./openSourceSelectorFlow"; +/** What the main process returns when it has just raised macOS' prompt. */ +const promptRaised = { + opened: false, + reason: "screen-access-required", + access: { success: true, granted: false, status: "denied", promptRaised: true }, +} as const; + describe("openSourceSelectorWithPermissionRetry", () => { it("returns immediately when the source selector opens on the first attempt", async () => { const openSourceSelector = vi.fn().mockResolvedValue({ opened: true }); @@ -17,19 +24,49 @@ describe("openSourceSelectorWithPermissionRetry", () => { expect(requestScreenAccess).not.toHaveBeenCalled(); }); - it("retries opening after macOS screen permission becomes granted", async () => { + it("does not wait when no prompt was raised, so the dialog is not delayed", async () => { + // A user who refused on an earlier launch: macOS will not show them the prompt + // again, so there is nothing to wait for and the Settings dialog has already been + // shown by the main process. + const openSourceSelector = vi.fn().mockResolvedValue({ + opened: false, + reason: "screen-access-required", + access: { success: true, granted: false, status: "denied", promptRaised: false }, + }); + const requestScreenAccess = vi.fn(); + + const result = await openSourceSelectorWithPermissionRetry({ + openSourceSelector, + requestScreenAccess, + wait: vi.fn(), + }); + + expect(result.opened).toBe(false); + expect(requestScreenAccess).not.toHaveBeenCalled(); + expect(openSourceSelector).toHaveBeenCalledTimes(1); + }); + + it("reopens the selector once the permission is granted while waiting", async () => { + // Reachable only because the main process reads the permission from a fresh + // process. Through its own cached status it could never observe the grant. const openSourceSelector = vi .fn() - .mockResolvedValueOnce({ - opened: false, - reason: "screen-access-required", - access: { success: true, granted: false, status: "not-determined" }, - }) + .mockResolvedValueOnce(promptRaised) .mockResolvedValueOnce({ opened: true }); const requestScreenAccess = vi .fn() - .mockResolvedValueOnce({ success: true, granted: false, status: "not-determined" }) - .mockResolvedValueOnce({ success: true, granted: true, status: "granted" }); + .mockResolvedValueOnce({ + success: true, + granted: false, + status: "denied", + promptRaised: true, + }) + .mockResolvedValueOnce({ + success: true, + granted: true, + status: "granted", + promptRaised: true, + }); const wait = vi.fn().mockResolvedValue(undefined); const result = await openSourceSelectorWithPermissionRetry({ @@ -41,33 +78,48 @@ describe("openSourceSelectorWithPermissionRetry", () => { expect(result).toEqual({ opened: true }); expect(wait).toHaveBeenCalledTimes(2); - expect(requestScreenAccess).toHaveBeenCalledTimes(2); + expect(openSourceSelector).toHaveBeenLastCalledWith(); + }); + + it("asks the main process for its dialog once the wait is spent", async () => { + // The regression this guards: running the budget out used to return a synthesized + // result that LaunchWindow discards, so a user who refused got six seconds of + // nothing followed by nothing at all. + const denied = { success: true, granted: false, status: "denied", promptRaised: true }; + const openSourceSelector = vi.fn().mockResolvedValue(promptRaised); + const requestScreenAccess = vi.fn().mockResolvedValue(denied); + + await openSourceSelectorWithPermissionRetry({ + openSourceSelector, + requestScreenAccess, + wait: vi.fn().mockResolvedValue(undefined), + maxAttempts: 3, + }); + + expect(requestScreenAccess).toHaveBeenCalledTimes(3); expect(openSourceSelector).toHaveBeenCalledTimes(2); + expect(openSourceSelector).toHaveBeenLastCalledWith({ screenPromptWaitElapsed: true }); }); - it("stops retrying once macOS permission is explicitly denied", async () => { - const openSourceSelector = vi.fn().mockResolvedValue({ - opened: false, - reason: "screen-access-required", - access: { success: true, granted: false, status: "not-determined" }, + it("stops waiting immediately on a status no prompt can resolve", async () => { + // An MDM-managed Mac cannot grant the permission at all. Waiting it out would cost + // the user the one message that explains why. + const openSourceSelector = vi.fn().mockResolvedValue(promptRaised); + const requestScreenAccess = vi.fn().mockResolvedValue({ + success: true, + granted: false, + status: "restricted", + promptRaised: true, }); - const requestScreenAccess = vi - .fn() - .mockResolvedValueOnce({ success: true, granted: false, status: "denied" }); - const result = await openSourceSelectorWithPermissionRetry({ + await openSourceSelectorWithPermissionRetry({ openSourceSelector, requestScreenAccess, - wait: vi.fn(), - maxAttempts: 4, + wait: vi.fn().mockResolvedValue(undefined), + maxAttempts: 8, }); - expect(result).toEqual({ - opened: false, - reason: "screen-access-required", - access: { success: true, granted: false, status: "denied" }, - }); expect(requestScreenAccess).toHaveBeenCalledTimes(1); - expect(openSourceSelector).toHaveBeenCalledTimes(1); + expect(openSourceSelector).toHaveBeenLastCalledWith({ screenPromptWaitElapsed: true }); }); }); diff --git a/src/components/launch/openSourceSelectorFlow.ts b/src/components/launch/openSourceSelectorFlow.ts index 540f1fcef..e8fe0c1f9 100644 --- a/src/components/launch/openSourceSelectorFlow.ts +++ b/src/components/launch/openSourceSelectorFlow.ts @@ -1,7 +1,18 @@ export type ScreenAccessResult = { success: boolean; granted: boolean; + /** What the OS actually said. Never bent to steer this loop. */ status: string; + /** + * macOS' own prompt was raised by this very call and may still be unanswered. + * + * This, and not `status`, is what arms the wait below. macOS reports the permission + * as absent for the whole time its prompt is on screen -- it only flips once the user + * accepts -- so a loop keyed on the status would abort on its first poll, every time. + */ + promptRaised?: boolean; + /** Granted, but the main process cannot use it until the app is relaunched. */ + requiresRelaunch?: boolean; error?: string; }; @@ -11,8 +22,13 @@ export type OpenSourceSelectorResult = { access?: ScreenAccessResult; }; +export type OpenSourceSelectorOptions = { + /** Tells the main process this loop has stopped waiting on macOS' prompt. */ + screenPromptWaitElapsed?: boolean; +}; + type OpenSourceSelectorFlowOptions = { - openSourceSelector: () => Promise; + openSourceSelector: (options?: OpenSourceSelectorOptions) => Promise; requestScreenAccess: () => Promise; wait?: (ms: number) => Promise; retryDelayMs?: number; @@ -21,14 +37,31 @@ type OpenSourceSelectorFlowOptions = { const defaultWait = (ms: number) => new Promise((resolve) => window.setTimeout(resolve, ms)); -function shouldRetryAfterPermissionPrompt(result: OpenSourceSelectorResult): boolean { +/** + * Whether macOS' Screen Recording prompt is up and worth waiting on. + * + * The main process holds its "permission is required" dialog back for exactly as long as + * this is true, because opening System Settings over the native prompt is the bug the + * whole path exists to fix. Which makes this loop the owner of that wait -- and the only + * side that can say when it is over. + */ +function shouldWaitForPermissionPrompt(result: OpenSourceSelectorResult): boolean { return ( result.opened === false && result.reason === "screen-access-required" && - result.access?.status === "not-determined" + result.access?.promptRaised === true ); } +/** + * True for a status no prompt can resolve -- policy-restricted by an MDM profile, or a + * permission read that failed outright. Waiting those out costs the user the message that + * explains the refusal and buys nothing: no answer is coming. + */ +function isUnanswerableStatus(status: string): boolean { + return status !== "denied" && status !== "not-determined"; +} + export async function openSourceSelectorWithPermissionRetry({ openSourceSelector, requestScreenAccess, @@ -37,7 +70,7 @@ export async function openSourceSelectorWithPermissionRetry({ maxAttempts = 8, }: OpenSourceSelectorFlowOptions): Promise { const initialResult = await openSourceSelector(); - if (!shouldRetryAfterPermissionPrompt(initialResult)) { + if (!shouldWaitForPermissionPrompt(initialResult)) { return initialResult; } @@ -45,18 +78,21 @@ export async function openSourceSelectorWithPermissionRetry({ await wait(retryDelayMs); const access = await requestScreenAccess(); + // Reachable now: the main process reads the permission from a fresh process, so a + // grant made while the app is running is visible to it. Read through the app's own + // cached status -- as this loop used to be -- this branch could never be taken. if (access.granted) { return openSourceSelector(); } - if (access.status !== "not-determined") { - return { - opened: false, - reason: "screen-access-required", - access, - }; + if (isUnanswerableStatus(access.status)) { + return openSourceSelector({ screenPromptWaitElapsed: true }); } } - return initialResult; + // The budget is spent, so the prompt has been answered, dismissed, or was never shown. + // Going back through the main process -- rather than synthesizing a result here, which + // LaunchWindow discards -- is what puts the "permission is required" dialog in front of + // a user who refused. Without this, running the wait out ended in silence. + return openSourceSelector({ screenPromptWaitElapsed: true }); } diff --git a/technical-documentation/architecture/decisions.md b/technical-documentation/architecture/decisions.md index ac249fe6c..581356c9d 100644 --- a/technical-documentation/architecture/decisions.md +++ b/technical-documentation/architecture/decisions.md @@ -25,6 +25,7 @@ A decision leaves this list only when the code stops honouring it. | **The project file extension is `.openscreen`.** Builds that wrote `.axcut` are read and renamed forward on first open. | Users already recognise the extension; `electron/ai-edition/document-service.ts:23` holds both. | | **Migrations are forward-only.** A document is migrated up to the current `schemaVersion` on open and never written back down. | Round-tripping through an older schema loses fields silently. | | **Captions are derived from the transcript, not injected as annotations.** | The earlier design generated annotation objects from captions, which then drifted from the transcript the moment either was edited. The transcript is the SSOT for spoken words. | +| **macOS' Screen Recording permission is read from a fresh child process, never from the app's own status.** The prompt is still raised in-process, through `desktopCapturer`, so TCC attributes the grant to the app bundle. `electron/native-bridge/screen/macScreenAccess.ts`. | `CGPreflightScreenCaptureAccess()` caches its answer for the life of the calling process, and Electron's `getMediaAccessStatus("screen")` is that same function. A long-lived app therefore cannot observe its own permission being granted, which is why polling it after the prompt could never succeed. A process spawned per read has no cache to be stale. See [`screenAccessPrompt.ts`](../../electron/ipc/screenAccessPrompt.ts). | | **One package, one repository.** No sidecar process, no local HTTP server, no monorepo. | The editing engine was adopted from a project that had a Python worker and a Fastify server; both were replaced by in-process TypeScript and Electron IPC. Adding a second runtime back is a large, permanent cost. | ## Rejected, with the reason @@ -41,6 +42,8 @@ contradicts the reason given. | **Proxy MP4 files for scrubbing** | Dropped in favour of streaming decode. If long-recording scrub latency becomes the top complaint again, the revival path is a per-asset "generate proxy" action, not a background pass over every import. | | **Server-sent events for project changes** | Meaningless in a single-user desktop app; the document store already notifies every subscriber. | | **Auto-generating annotations from captions** | See "captions are derived" above. | +| **An in-flight flag around `desktopCapturer.getSources()`** to hold the Settings dialog back while macOS' prompt is up | Measured on macOS 26.2 / Electron 41.2.1 ([PR #302](https://github.com/getopenscreen/openscreen/pull/302)): the call settles in 4ms whether or not the prompt is still on screen, so the flag would cover four milliseconds of a wait that lasts as long as the user takes. Note the measurement was taken on a machine that had already refused, where the call short-circuits. | +| **Reporting `not-determined` for a grace window after prompting**, to keep the renderer's retry loop alive | Built, then rejected: it cannot be tuned into correctness. The loop it kept alive polled the app's own cached status, which never flips, so no window length would have let it succeed. It also cost a user who had genuinely refused their only actionable message, and made `status` mean two things at once. Replaced by an honest `promptRaised` field plus a fresh-process read. | | **React Query for the agent layer** | Planned during the merge, never adopted; the dependency is not in `package.json`. Plain IPC plus the document store covers it. | ## Surfaces that were removed diff --git a/tsconfig.node.tsbuildinfo b/tsconfig.node.tsbuildinfo new file mode 100644 index 000000000..100624c00 --- /dev/null +++ b/tsconfig.node.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["../../../node_modules/typescript/lib/lib.d.ts","../../../node_modules/typescript/lib/lib.es5.d.ts","../../../node_modules/typescript/lib/lib.es2015.d.ts","../../../node_modules/typescript/lib/lib.es2016.d.ts","../../../node_modules/typescript/lib/lib.es2017.d.ts","../../../node_modules/typescript/lib/lib.es2018.d.ts","../../../node_modules/typescript/lib/lib.es2019.d.ts","../../../node_modules/typescript/lib/lib.es2020.d.ts","../../../node_modules/typescript/lib/lib.dom.d.ts","../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../node_modules/typescript/lib/lib.scripthost.d.ts","../../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../../node_modules/typescript/lib/lib.decorators.d.ts","../../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../../node_modules/@types/node/compatibility/disposable.d.ts","../../../node_modules/@types/node/compatibility/indexable.d.ts","../../../node_modules/@types/node/compatibility/iterators.d.ts","../../../node_modules/@types/node/compatibility/index.d.ts","../../../node_modules/@types/node/globals.typedarray.d.ts","../../../node_modules/@types/node/buffer.buffer.d.ts","../../../node_modules/@types/node/globals.d.ts","../../../node_modules/@types/node/web-globals/abortcontroller.d.ts","../../../node_modules/@types/node/web-globals/domexception.d.ts","../../../node_modules/@types/node/web-globals/events.d.ts","../../../node_modules/undici-types/header.d.ts","../../../node_modules/undici-types/readable.d.ts","../../../node_modules/undici-types/file.d.ts","../../../node_modules/undici-types/fetch.d.ts","../../../node_modules/undici-types/formdata.d.ts","../../../node_modules/undici-types/connector.d.ts","../../../node_modules/undici-types/client.d.ts","../../../node_modules/undici-types/errors.d.ts","../../../node_modules/undici-types/dispatcher.d.ts","../../../node_modules/undici-types/global-dispatcher.d.ts","../../../node_modules/undici-types/global-origin.d.ts","../../../node_modules/undici-types/pool-stats.d.ts","../../../node_modules/undici-types/pool.d.ts","../../../node_modules/undici-types/handlers.d.ts","../../../node_modules/undici-types/balanced-pool.d.ts","../../../node_modules/undici-types/agent.d.ts","../../../node_modules/undici-types/mock-interceptor.d.ts","../../../node_modules/undici-types/mock-agent.d.ts","../../../node_modules/undici-types/mock-client.d.ts","../../../node_modules/undici-types/mock-pool.d.ts","../../../node_modules/undici-types/mock-errors.d.ts","../../../node_modules/undici-types/proxy-agent.d.ts","../../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../../node_modules/undici-types/retry-handler.d.ts","../../../node_modules/undici-types/retry-agent.d.ts","../../../node_modules/undici-types/api.d.ts","../../../node_modules/undici-types/interceptors.d.ts","../../../node_modules/undici-types/util.d.ts","../../../node_modules/undici-types/cookies.d.ts","../../../node_modules/undici-types/patch.d.ts","../../../node_modules/undici-types/websocket.d.ts","../../../node_modules/undici-types/eventsource.d.ts","../../../node_modules/undici-types/filereader.d.ts","../../../node_modules/undici-types/diagnostics-channel.d.ts","../../../node_modules/undici-types/content-type.d.ts","../../../node_modules/undici-types/cache.d.ts","../../../node_modules/undici-types/index.d.ts","../../../node_modules/@types/node/web-globals/fetch.d.ts","../../../node_modules/@types/node/web-globals/navigator.d.ts","../../../node_modules/@types/node/web-globals/storage.d.ts","../../../node_modules/@types/node/assert.d.ts","../../../node_modules/@types/node/assert/strict.d.ts","../../../node_modules/@types/node/async_hooks.d.ts","../../../node_modules/@types/node/buffer.d.ts","../../../node_modules/@types/node/child_process.d.ts","../../../node_modules/@types/node/cluster.d.ts","../../../node_modules/@types/node/console.d.ts","../../../node_modules/@types/node/constants.d.ts","../../../node_modules/@types/node/crypto.d.ts","../../../node_modules/@types/node/dgram.d.ts","../../../node_modules/@types/node/diagnostics_channel.d.ts","../../../node_modules/@types/node/dns.d.ts","../../../node_modules/@types/node/dns/promises.d.ts","../../../node_modules/@types/node/domain.d.ts","../../../node_modules/@types/node/events.d.ts","../../../node_modules/@types/node/fs.d.ts","../../../node_modules/@types/node/fs/promises.d.ts","../../../node_modules/@types/node/http.d.ts","../../../node_modules/@types/node/http2.d.ts","../../../node_modules/@types/node/https.d.ts","../../../node_modules/@types/node/inspector.d.ts","../../../node_modules/@types/node/inspector.generated.d.ts","../../../node_modules/@types/node/module.d.ts","../../../node_modules/@types/node/net.d.ts","../../../node_modules/@types/node/os.d.ts","../../../node_modules/@types/node/path.d.ts","../../../node_modules/@types/node/perf_hooks.d.ts","../../../node_modules/@types/node/process.d.ts","../../../node_modules/@types/node/punycode.d.ts","../../../node_modules/@types/node/querystring.d.ts","../../../node_modules/@types/node/readline.d.ts","../../../node_modules/@types/node/readline/promises.d.ts","../../../node_modules/@types/node/repl.d.ts","../../../node_modules/@types/node/sea.d.ts","../../../node_modules/@types/node/sqlite.d.ts","../../../node_modules/@types/node/stream.d.ts","../../../node_modules/@types/node/stream/promises.d.ts","../../../node_modules/@types/node/stream/consumers.d.ts","../../../node_modules/@types/node/stream/web.d.ts","../../../node_modules/@types/node/string_decoder.d.ts","../../../node_modules/@types/node/test.d.ts","../../../node_modules/@types/node/timers.d.ts","../../../node_modules/@types/node/timers/promises.d.ts","../../../node_modules/@types/node/tls.d.ts","../../../node_modules/@types/node/trace_events.d.ts","../../../node_modules/@types/node/tty.d.ts","../../../node_modules/@types/node/url.d.ts","../../../node_modules/@types/node/util.d.ts","../../../node_modules/@types/node/v8.d.ts","../../../node_modules/@types/node/vm.d.ts","../../../node_modules/@types/node/wasi.d.ts","../../../node_modules/@types/node/worker_threads.d.ts","../../../node_modules/@types/node/zlib.d.ts","../../../node_modules/@types/node/index.d.ts","../../../node_modules/vite/types/hmrpayload.d.ts","../../../node_modules/vite/dist/node/chunks/modulerunnertransport.d.ts","../../../node_modules/vite/types/customevent.d.ts","../../../node_modules/@types/estree/index.d.ts","../../../node_modules/rollup/dist/rollup.d.ts","../../../node_modules/rollup/dist/parseast.d.ts","../../../node_modules/vite/types/hot.d.ts","../../../node_modules/vite/dist/node/module-runner.d.ts","../../../node_modules/esbuild/lib/main.d.ts","../../../node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.mts","../../../node_modules/@jridgewell/trace-mapping/types/types.d.mts","../../../node_modules/@jridgewell/trace-mapping/types/flatten-map.d.mts","../../../node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.mts","../../../node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.mts","../../../node_modules/@jridgewell/gen-mapping/types/types.d.mts","../../../node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.mts","../../../node_modules/@jridgewell/source-map/types/source-map.d.mts","../../../node_modules/terser/tools/terser.d.ts","../../../node_modules/vite/types/internal/terseroptions.d.ts","../../../node_modules/source-map-js/source-map.d.ts","../../../node_modules/postcss/lib/previous-map.d.ts","../../../node_modules/postcss/lib/input.d.ts","../../../node_modules/postcss/lib/css-syntax-error.d.ts","../../../node_modules/postcss/lib/declaration.d.ts","../../../node_modules/postcss/lib/root.d.ts","../../../node_modules/postcss/lib/warning.d.ts","../../../node_modules/postcss/lib/lazy-result.d.ts","../../../node_modules/postcss/lib/no-work-result.d.ts","../../../node_modules/postcss/lib/processor.d.ts","../../../node_modules/postcss/lib/result.d.ts","../../../node_modules/postcss/lib/document.d.ts","../../../node_modules/postcss/lib/rule.d.ts","../../../node_modules/postcss/lib/node.d.ts","../../../node_modules/postcss/lib/comment.d.ts","../../../node_modules/postcss/lib/container.d.ts","../../../node_modules/postcss/lib/at-rule.d.ts","../../../node_modules/postcss/lib/list.d.ts","../../../node_modules/postcss/lib/postcss.d.ts","../../../node_modules/postcss/lib/postcss.d.mts","../../../node_modules/vite/types/internal/csspreprocessoroptions.d.ts","../../../node_modules/vite/types/internal/lightningcssoptions.d.ts","../../../node_modules/vite/types/importglob.d.ts","../../../node_modules/vite/types/metadata.d.ts","../../../node_modules/vite/dist/node/index.d.ts","../../../node_modules/@babel/types/lib/index.d.ts","../../../node_modules/@types/babel__generator/index.d.ts","../../../node_modules/@babel/parser/typings/babel-parser.d.ts","../../../node_modules/@types/babel__template/index.d.ts","../../../node_modules/@types/babel__traverse/index.d.ts","../../../node_modules/@types/babel__core/index.d.ts","../../../node_modules/@vitejs/plugin-react/dist/index.d.ts","../../../node_modules/vite-plugin-electron/dist/utils.d.ts","../../../node_modules/vite-plugin-electron/dist/index.d.ts","../../../node_modules/vite-plugin-electron-renderer/dist/index.d.ts","../../../node_modules/vite-plugin-electron/dist/simple.d.ts","./vite.config.ts","../../../node_modules/playwright-core/types/protocol.d.ts","../../../node_modules/playwright-core/types/structs.d.ts","../../../node_modules/zod/v4/core/json-schema.d.cts","../../../node_modules/zod/v4/core/standard-schema.d.cts","../../../node_modules/zod/v4/core/registries.d.cts","../../../node_modules/zod/v4/core/to-json-schema.d.cts","../../../node_modules/zod/v4/core/util.d.cts","../../../node_modules/zod/v4/core/versions.d.cts","../../../node_modules/zod/v4/core/schemas.d.cts","../../../node_modules/zod/v4/core/checks.d.cts","../../../node_modules/zod/v4/core/errors.d.cts","../../../node_modules/zod/v4/core/core.d.cts","../../../node_modules/zod/v4/core/parse.d.cts","../../../node_modules/zod/v4/core/regexes.d.cts","../../../node_modules/zod/v4/locales/ar.d.cts","../../../node_modules/zod/v4/locales/az.d.cts","../../../node_modules/zod/v4/locales/be.d.cts","../../../node_modules/zod/v4/locales/bg.d.cts","../../../node_modules/zod/v4/locales/ca.d.cts","../../../node_modules/zod/v4/locales/cs.d.cts","../../../node_modules/zod/v4/locales/da.d.cts","../../../node_modules/zod/v4/locales/de.d.cts","../../../node_modules/zod/v4/locales/el.d.cts","../../../node_modules/zod/v4/locales/en.d.cts","../../../node_modules/zod/v4/locales/eo.d.cts","../../../node_modules/zod/v4/locales/es.d.cts","../../../node_modules/zod/v4/locales/fa.d.cts","../../../node_modules/zod/v4/locales/fi.d.cts","../../../node_modules/zod/v4/locales/fr.d.cts","../../../node_modules/zod/v4/locales/fr-ca.d.cts","../../../node_modules/zod/v4/locales/he.d.cts","../../../node_modules/zod/v4/locales/hr.d.cts","../../../node_modules/zod/v4/locales/hu.d.cts","../../../node_modules/zod/v4/locales/hy.d.cts","../../../node_modules/zod/v4/locales/id.d.cts","../../../node_modules/zod/v4/locales/is.d.cts","../../../node_modules/zod/v4/locales/it.d.cts","../../../node_modules/zod/v4/locales/ja.d.cts","../../../node_modules/zod/v4/locales/ka.d.cts","../../../node_modules/zod/v4/locales/kh.d.cts","../../../node_modules/zod/v4/locales/km.d.cts","../../../node_modules/zod/v4/locales/ko.d.cts","../../../node_modules/zod/v4/locales/lt.d.cts","../../../node_modules/zod/v4/locales/mk.d.cts","../../../node_modules/zod/v4/locales/ms.d.cts","../../../node_modules/zod/v4/locales/nl.d.cts","../../../node_modules/zod/v4/locales/no.d.cts","../../../node_modules/zod/v4/locales/ota.d.cts","../../../node_modules/zod/v4/locales/ps.d.cts","../../../node_modules/zod/v4/locales/pl.d.cts","../../../node_modules/zod/v4/locales/pt.d.cts","../../../node_modules/zod/v4/locales/ro.d.cts","../../../node_modules/zod/v4/locales/ru.d.cts","../../../node_modules/zod/v4/locales/sl.d.cts","../../../node_modules/zod/v4/locales/sv.d.cts","../../../node_modules/zod/v4/locales/ta.d.cts","../../../node_modules/zod/v4/locales/th.d.cts","../../../node_modules/zod/v4/locales/tr.d.cts","../../../node_modules/zod/v4/locales/ua.d.cts","../../../node_modules/zod/v4/locales/uk.d.cts","../../../node_modules/zod/v4/locales/ur.d.cts","../../../node_modules/zod/v4/locales/uz.d.cts","../../../node_modules/zod/v4/locales/vi.d.cts","../../../node_modules/zod/v4/locales/zh-cn.d.cts","../../../node_modules/zod/v4/locales/zh-tw.d.cts","../../../node_modules/zod/v4/locales/yo.d.cts","../../../node_modules/zod/v4/locales/index.d.cts","../../../node_modules/zod/v4/core/doc.d.cts","../../../node_modules/zod/v4/core/api.d.cts","../../../node_modules/zod/v4/core/json-schema-processors.d.cts","../../../node_modules/zod/v4/core/json-schema-generator.d.cts","../../../node_modules/zod/v4/core/index.d.cts","../../../node_modules/zod/v4/classic/errors.d.cts","../../../node_modules/zod/v4/classic/parse.d.cts","../../../node_modules/zod/v4/classic/schemas.d.cts","../../../node_modules/zod/v4/classic/checks.d.cts","../../../node_modules/zod/v4/classic/compat.d.cts","../../../node_modules/zod/v4/classic/from-json-schema.d.cts","../../../node_modules/zod/v4/classic/iso.d.cts","../../../node_modules/zod/v4/classic/coerce.d.cts","../../../node_modules/zod/v4/classic/external.d.cts","../../../node_modules/zod/index.d.cts","../../../node_modules/zod/v3/helpers/typealiases.d.cts","../../../node_modules/zod/v3/helpers/util.d.cts","../../../node_modules/zod/v3/zoderror.d.cts","../../../node_modules/zod/v3/locales/en.d.cts","../../../node_modules/zod/v3/errors.d.cts","../../../node_modules/zod/v3/helpers/parseutil.d.cts","../../../node_modules/zod/v3/helpers/enumutil.d.cts","../../../node_modules/zod/v3/helpers/errorutil.d.cts","../../../node_modules/zod/v3/helpers/partialutil.d.cts","../../../node_modules/zod/v3/standard-schema.d.cts","../../../node_modules/zod/v3/types.d.cts","../../../node_modules/zod/v3/external.d.cts","../../../node_modules/zod/v3/index.d.cts","../../../node_modules/electron/electron.d.ts","../../../node_modules/playwright-core/types/types.d.ts","../../../node_modules/playwright-core/index.d.ts","../../../node_modules/playwright/types/test.d.ts","../../../node_modules/playwright/test.d.ts","../../../node_modules/@playwright/test/index.d.ts","./playwright.config.ts","../../../node_modules/@vitest/spy/optional-types.d.ts","../../../node_modules/@vitest/spy/dist/index.d.ts","../../../node_modules/tinyrainbow/dist/index.d.ts","../../../node_modules/@standard-schema/spec/dist/index.d.ts","../../../node_modules/@vitest/pretty-format/dist/index.d.ts","../../../node_modules/@vitest/utils/dist/types.d-bcelap-c.d.ts","../../../node_modules/@vitest/utils/dist/diff.d.ts","../../../node_modules/@vitest/utils/dist/display.d.ts","../../../node_modules/@types/deep-eql/index.d.ts","../../../node_modules/assertion-error/index.d.ts","../../../node_modules/@types/chai/index.d.ts","../../../node_modules/@vitest/expect/dist/index.d.ts","../../../node_modules/@vitest/utils/dist/types.d.ts","../../../node_modules/@vitest/utils/dist/helpers.d.ts","../../../node_modules/@vitest/utils/dist/timers.d.ts","../../../node_modules/@vitest/utils/dist/index.d.ts","../../../node_modules/@vitest/runner/dist/tasks.d-deyaimiu.d.ts","../../../node_modules/@vitest/runner/dist/index.d.ts","../../../node_modules/vitest/dist/chunks/traces.d.d2t_r8rx.d.ts","../../../node_modules/@vitest/snapshot/dist/environment.d-dojxxzv9.d.ts","../../../node_modules/@vitest/snapshot/dist/rawsnapshot.d-d_x3-62x.d.ts","../../../node_modules/@vitest/snapshot/dist/index.d.ts","../../../node_modules/vitest/dist/chunks/config.d.a1h_y6jt.d.ts","../../../node_modules/vitest/dist/chunks/environment.d.crsxczp1.d.ts","../../../node_modules/vitest/dist/chunks/rpc.d.b_8spu0w.d.ts","../../../node_modules/vitest/dist/chunks/worker.d.zphpo4yb.d.ts","../../../node_modules/vitest/dist/chunks/browser.d.bcoexmfg.d.ts","../../../node_modules/vitest/optional-types.d.ts","../../../node_modules/@vitest/runner/dist/utils.d.ts","../../../node_modules/tinybench/dist/index.d.ts","../../../node_modules/vitest/dist/chunks/benchmark.d.daahlpsq.d.ts","../../../node_modules/@vitest/mocker/dist/types.d-bji5eawu.d.ts","../../../node_modules/@vitest/mocker/dist/index.d-b41z0auw.d.ts","../../../node_modules/@vitest/mocker/dist/index.d.ts","../../../node_modules/@vitest/utils/dist/source-map.d.ts","../../../node_modules/vitest/dist/chunks/coverage.d.bztk59wp.d.ts","../../../node_modules/@vitest/utils/dist/serialize.d.ts","../../../node_modules/@vitest/utils/dist/error.d.ts","../../../node_modules/vitest/dist/browser.d.ts","../../../node_modules/vitest/browser/context.d.ts","../../../node_modules/@vitest/snapshot/dist/manager.d.ts","../../../node_modules/vitest/dist/chunks/reporters.d.dtokvv2s.d.ts","../../../node_modules/vitest/dist/chunks/plugin.d.dwfiij7i.d.ts","../../../node_modules/vitest/dist/config.d.ts","../../../node_modules/vitest/config.d.ts","./vitest.config.ts","../../../node_modules/@types/aria-query/index.d.ts","../../../node_modules/keyv/src/index.d.ts","../../../node_modules/@types/http-cache-semantics/index.d.ts","../../../node_modules/@types/responselike/index.d.ts","../../../node_modules/@types/cacheable-request/index.d.ts","../../../node_modules/@types/ms/index.d.ts","../../../node_modules/@types/debug/index.d.ts","../../../node_modules/@types/dom-webcodecs/webcodecs.generated.d.ts","../../../node_modules/@types/dom-webcodecs/index.d.ts","../../../node_modules/@types/dom-mediacapture-transform/index.d.ts","../../../node_modules/@types/fs-extra/index.d.ts","../../../node_modules/@types/json-schema/index.d.ts","../../../node_modules/@types/keyv/index.d.ts","../../../node_modules/@types/prop-types/index.d.ts","../../../node_modules/@types/react/global.d.ts","../../../node_modules/csstype/index.d.ts","../../../node_modules/@types/react/index.d.ts","../../../node_modules/@types/react-dom/index.d.ts","../../../node_modules/@types/use-sync-external-store/index.d.ts","../../../node_modules/@types/yauzl/index.d.ts"],"fileIdsList":[[55,103,120,121,310],[55,103,120,121,125,197,204,208,355],[55,103,120,121,125,356],[55,103,120,121,198],[55,103,120,121],[55,103,120,121,166,168],[55,103,120,121,167],[55,103,120,121,166,169],[55,103,120,121,164,166],[55,103,120,121,163,164,165],[55,103,120,121,163,166],[55,103,120,121,309],[55,103,120,121,198,199,200,201,202],[55,103,120,121,198,200],[55,103,114,117,120,121,146,153,359,360,361],[55,103,120,121,320,321],[55,103,120,121,363],[55,103,120,121,366],[55,103,120,121,365],[55,103,115,120,121,153],[55,103,114,120,121,153],[55,100,101,103,120,121],[55,102,103,120,121],[103,120,121],[55,103,108,120,121,138],[55,103,104,109,114,120,121,123,135,146],[55,103,104,105,114,120,121,123],[50,51,52,55,103,120,121],[55,103,106,120,121,147],[55,103,107,108,115,120,121,124],[55,103,108,120,121,135,143],[55,103,109,111,114,120,121,123],[55,102,103,110,120,121],[55,103,111,112,120,121],[55,103,113,114,120,121],[55,102,103,114,120,121],[55,103,114,115,116,120,121,135,146],[55,103,114,115,116,120,121,130,135,138],[55,96,103,111,114,117,120,121,123,135,146],[55,103,114,115,117,118,120,121,123,135,143,146],[55,103,117,119,120,121,135,143,146],[53,54,55,56,57,58,59,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152],[55,103,114,120,121],[55,103,120,121,122,146],[55,103,111,114,120,121,123,135],[55,103,120,121,124],[55,103,120,121,125],[55,102,103,120,121,126],[55,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152],[55,103,120,121,128],[55,103,120,121,129],[55,103,114,120,121,130,131],[55,103,120,121,130,132,147,149],[55,103,115,120,121],[55,103,114,120,121,135,136,138],[55,103,120,121,137,138],[55,103,120,121,135,136],[55,103,120,121,138],[55,103,120,121,139],[55,100,103,120,121,135,140,146],[55,103,114,120,121,141,142],[55,103,120,121,141,142],[55,103,108,120,121,123,135,143],[55,103,120,121,144],[55,103,120,121,123,145],[55,103,117,120,121,129,146],[55,103,108,120,121,147],[55,103,120,121,135,148],[55,103,120,121,122,149],[55,103,120,121,150],[55,96,103,120,121],[55,96,103,114,116,120,121,126,135,138,146,148,149,151],[55,103,120,121,135,152],[55,103,120,121,374],[55,103,120,121,371,372,373],[55,103,117,120,121,135,153],[55,103,114,120,121,135,153],[55,103,120,121,197,203,355],[55,103,120,121,313,314,315,318,319,322],[55,103,120,121,343],[55,103,120,121,343,344],[55,103,120,121,318,327,328],[55,103,120,121,318,327],[55,103,120,121,327],[55,103,120,121,316,327,331,332],[55,103,120,121,316,327,331],[55,103,120,121,312],[55,103,120,121,316,317],[55,103,120,121,316],[55,103,120,121,316,317,324,348],[55,103,120,121,324],[55,103,120,121,316,319,324,325,326],[55,103,114,115,120,121,153],[55,103,120,121,306],[55,103,104,115,120,121,135,210,211,291,304,305],[55,103,120,121,308],[55,103,120,121,307],[55,103,120,121,188],[55,103,120,121,186,188],[55,103,120,121,177,185,186,187,189,191],[55,103,120,121,175],[55,103,120,121,178,183,188,191],[55,103,120,121,174,191],[55,103,120,121,178,179,182,183,184,191],[55,103,120,121,178,179,180,182,183,191],[55,103,120,121,175,176,177,178,179,183,184,185,187,188,189,191],[55,103,120,121,191],[55,103,120,121,173,175,176,177,178,179,180,182,183,184,185,186,187,188,189,190],[55,103,120,121,173,191],[55,103,120,121,178,180,181,183,184,191],[55,103,120,121,182,191],[55,103,120,121,183,184,188,191],[55,103,120,121,176,186],[55,103,120,121,158,196,197],[55,103,120,121,157,158],[55,103,120,121,170],[55,68,72,103,120,121,146],[55,68,103,120,121,135,146],[55,63,103,120,121],[55,65,68,103,120,121,143,146],[55,103,120,121,123,143],[55,103,120,121,153],[55,63,103,120,121,153],[55,65,68,103,120,121,123,146],[55,60,61,64,67,103,114,120,121,135,146],[55,68,75,103,120,121],[55,60,66,103,120,121],[55,68,89,90,103,120,121],[55,64,68,103,120,121,138,146,153],[55,89,103,120,121,153],[55,62,63,103,120,121,153],[55,68,103,120,121],[55,62,63,64,65,66,67,68,69,70,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,90,91,92,93,94,95,103,120,121],[55,68,83,103,120,121],[55,68,75,76,103,120,121],[55,66,68,76,77,103,120,121],[55,67,103,120,121],[55,60,63,68,103,120,121],[55,68,72,76,77,103,120,121],[55,72,103,120,121],[55,66,68,71,103,120,121,146],[55,60,65,68,75,103,120,121],[55,103,120,121,135],[55,63,68,89,103,120,121,151,153],[55,103,120,121,162,197,355],[55,103,104,120,121,158,196,197,205,355],[55,103,120,121,158,196,197,206,207,355],[55,103,120,121,158,196,197,206,355],[55,103,120,121,154],[55,103,114,115,117,118,119,120,121,123,135,143,146,152,153,154,155,156,158,159,161,162,172,192,193,194,195,196,197],[55,103,120,121,154,155,156,160],[55,103,120,121,156],[55,103,120,121,171],[55,103,120,121,158,197],[55,103,120,121,350],[55,103,120,121,323,355],[55,103,120,121,313,316,318,319,325,326,327,329,330,333,334,346,347,349],[55,103,120,121,329,340,341],[55,103,120,121,329,330,337],[55,103,120,121,316,318,329,330,333],[55,103,120,121,197,353,355],[55,103,106,115,120,121,135,197,316,318,323,327,329,330,333,334,337,338,339,342,345,346,347,351,352,355],[55,103,120,121,161,329,330,333],[55,103,120,121,161,329,334,335,336],[55,103,106,115,120,121,135,161,197,316,318,323,327,329,330,333,334,335,336,337,338,339,340,341,342,345,346,347,351,352,353,354,355],[55,103,120,121,290],[55,103,120,121,294,295],[55,103,120,121,292,293,294,296,297,302],[55,103,120,121,293,294],[55,103,120,121,302],[55,103,120,121,303],[55,103,120,121,294],[55,103,120,121,292,293,294,297,298,299,300,301],[55,103,120,121,292,293,304],[55,103,120,121,281],[55,103,120,121,281,284],[55,103,120,121,216,276,279,281,282,283,284,285,286,287,288,289],[55,103,120,121,212,214,284],[55,103,120,121,281,282],[55,103,120,121,213,281,283],[55,103,120,121,214,216,218,219,220,221],[55,103,120,121,216,218,220,221],[55,103,120,121,216,218,220],[55,103,120,121,213,216,218,219,221],[55,103,120,121,212,214,215,216,217,218,219,220,221,222,223,276,277,278,279,280],[55,103,120,121,212,214,215,218],[55,103,120,121,214,215,218],[55,103,120,121,218,221],[55,103,120,121,212,213,215,216,217,219,220,221],[55,103,120,121,212,213,214,218,281],[55,103,120,121,218,219,220,221],[55,103,120,121,220],[55,103,120,121,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275]],"fileInfos":[{"version":"a7297ff837fcdf174a9524925966429eb8e5feecc2cc55cc06574e6b092c1eaa","impliedFormat":1},{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fa06ada475b910e2106c98c68b10483dc8811d0c14a8a8dd36efb2672485b29","impliedFormat":1},{"version":"33e5e9aba62c3193d10d1d33ae1fa75c46a1171cf76fef750777377d53b0303f","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"9855e02d837744303391e5623a531734443a5f8e6e8755e018c41d63ad797db2","impliedFormat":1},{"version":"4d631b81fa2f07a0e63a9a143d6a82c25c5f051298651a9b69176ba28930756d","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"c906fb15bd2aabc9ed1e3f44eb6a8661199d6c320b3aa196b826121552cb3695","impliedFormat":1},{"version":"22295e8103f1d6d8ea4b5d6211e43421fe4564e34d0dd8e09e520e452d89e659","impliedFormat":1},{"version":"58647d85d0f722a1ce9de50955df60a7489f0593bf1a7015521efe901c06d770","impliedFormat":1},{"version":"6b4e081d55ac24fc8a4631d5dd77fe249fa25900abd7d046abb87d90e3b45645","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"e208f73ef6a980104304b0d2ca5f6bf1b85de6009d2c7e404028b875020fa8f2","impliedFormat":1},{"version":"d163b6bc2372b4f07260747cbc6c0a6405ab3fbcea3852305e98ac43ca59f5bc","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6f137d651076822d4fe884287e68fd61785a0d3d1fdb250a5059b691fa897db","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"8b479a130ccb62e98f11f136d3ac80f2984fdc07616516d29881f3061f2dd472","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"bceb58df66ab8fb00170df20cd813978c5ab84be1d285710c4eb005d8e9d8efb","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"a3fc63c0d7b031693f665f5494412ba4b551fe644ededccc0ab5922401079c95","impliedFormat":1},{"version":"80523c00b8544a2000ae0143e4a90a00b47f99823eb7926c1e03c494216fc363","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"746911b62b329587939560deb5c036aca48aece03147b021fa680223255d5183","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"c8d3e5a18ba35629954e48c4cc8f11dc88224650067a172685c736b27a34a4dc","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"2b55d426ff2b9087485e52ac4bc7cfafe1dc420fc76dad926cd46526567c501a","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"5b7aa3c4c1a5d81b411e8cb302b45507fea9358d3569196b27eb1a27ae3a90ef","affectsGlobalScope":true,"impliedFormat":1},{"version":"5987a903da92c7462e0b35704ce7da94d7fdc4b89a984871c0e2b87a8aae9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"47613031a5a31510831304405af561b0ffaedb734437c595256bb61a90f9311b","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"8a1a0d0a4a06a8d278947fcb66bf684f117bf147f89b06e50662d79a53be3e9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"358765d5ea8afd285d4fd1532e78b88273f18cb3f87403a9b16fef61ac9fdcfe","impliedFormat":1},{"version":"9f55299850d4f0921e79b6bf344b47c420ce0f507b9dcf593e532b09ea7eeea1","impliedFormat":1},{"version":"a7ca8df4f2931bef2aa4118078584d84a0b16539598eaadf7dce9104dfaa381c","impliedFormat":1},{"version":"10073cdcf56982064c5337787cc59b79586131e1b28c106ede5bff362f912b70","impliedFormat":99},{"version":"72950913f4900b680f44d8cab6dd1ea0311698fc1eefb014eb9cdfc37ac4a734","impliedFormat":1},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","impliedFormat":1},{"version":"ee70b8037ecdf0de6c04f35277f253663a536d7e38f1539d270e4e916d225a3f","affectsGlobalScope":true,"impliedFormat":1},{"version":"a660aa95476042d3fdcc1343cf6bb8fdf24772d31712b1db321c5a4dcc325434","impliedFormat":1},{"version":"36977c14a7f7bfc8c0426ae4343875689949fb699f3f84ecbe5b300ebf9a2c55","impliedFormat":1},{"version":"ff0a83c9a0489a627e264ffcb63f2264b935b20a502afa3a018848139e3d8575","impliedFormat":99},{"version":"161c8e0690c46021506e32fda85956d785b70f309ae97011fd27374c065cac9b","affectsGlobalScope":true,"impliedFormat":1},{"version":"971f12a5fc236419ced0b7b9f23a53c1758233713f565635bbf4b85e2b23f55a","impliedFormat":99},{"version":"76de3321ce519928f1ff7d7a30391c0dc7374af20f81d9167919f038895b5cb0","impliedFormat":99},{"version":"094b9210da23b8711709b0535c59841186267bf6b83c1609aa9b515f830ab274","impliedFormat":99},{"version":"fbfbb4e99c6259ff5ccc4a5a62b3b63c0c8cae6e84737786c4a4c761c9a9de91","impliedFormat":99},{"version":"604887bbd5b0a93234ce882543a465f008636185c52e0f0353330e2bc38b03b6","impliedFormat":99},{"version":"32bf912173e8a9533631f9e9d8dc90a2ac7b52c2355611ddd886beab24dfd182","impliedFormat":99},{"version":"82695324abf7f3278b6d9f0582f4a544e8f7055c8cbe1065ab5cbacde1719c4c","impliedFormat":99},{"version":"43bba542e50e19241ec64bc13cfc0d9273e6198f36563cecad1f4e4b78ad47f3","impliedFormat":99},{"version":"b8cb3b69c0e8114f758bb8ef8efeef1cc80f8911bfd21126def73d2174ce479e","impliedFormat":99},{"version":"f582b0fcbf1eea9b318ab92fb89ea9ab2ebb84f9b60af89328a91155e1afce72","impliedFormat":1},{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","impliedFormat":1},{"version":"52dcc257df5119fb66d864625112ce5033ac51a4c2afe376a0b299d2f7f76e4a","impliedFormat":1},{"version":"e5bab5f871ef708d52d47b3e5d0aa72a08ee7a152f33931d9a60809711a2a9a3","impliedFormat":1},{"version":"e16dc2a81595736024a206c7d5c8a39bfe2e6039208ef29981d0d95434ba8fcf","impliedFormat":1},{"version":"cc4a4903fb698ca1d961d4c10dce658aa3a479faf40509d526f122b044eaf6a4","impliedFormat":1},{"version":"19ee8416e6473ed6c7adb868fa796b5653cf0fa2a337658e677eaa0d134388c3","impliedFormat":1},{"version":"1328ab4e442614b28cdb3d4b414cf68325c0da0dca07287a338d0654b7a00261","impliedFormat":1},{"version":"a039dc21f045919f3cbee2ec13812cc6cc3eebc99dae4be00973230f468d19a6","impliedFormat":1},{"version":"3fbe57af01460e49dcd29df55d6931e1672bc6f1be0fb073d11410bc16f9037d","impliedFormat":1},{"version":"f760be449e8562ec5c09bb5187e8e1eabf3c113c0c58cddda53ef8c69f3e2131","impliedFormat":1},{"version":"44325ed13294fce6ab825b82947bbeed2611db7dad9d9135260192f375e5a189","impliedFormat":1},{"version":"e392e8fb5b514eafc585601c1d781485aa6dd6a320e75daf1064a4c6918a1b45","impliedFormat":1},{"version":"46e4a36e8ddbdfb4e7330e11c81c970dc8b218611df9183d39c41c5f8c653b55","impliedFormat":1},{"version":"3cc8a3d123b6b232d48d34b51b785f9da8d193f5b5817fa521fcd2f3b9315c55","impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","impliedFormat":1},{"version":"354a7f8e1287d9d6b7561bc97fdd8cbc2f7c1dd79e4cb37b942e8a5cfaff1085","impliedFormat":1},{"version":"8d369483f0c2b9ee388129cfdb6a43bc8112b377e86a41884bd06e19ce04f4c1","impliedFormat":99},{"version":"960bd764c62ac43edc24eaa2af958a4b4f1fa5d27df5237e176d0143b36a39c6","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ec16d7a4e366c06a4573d299e15fe6207fc080f41beac5da06f4af33ea9761e","impliedFormat":1},{"version":"59f8dc89b9e724a6a667f52cdf4b90b6816ae6c9842ce176d38fcc973669009e","affectsGlobalScope":true,"impliedFormat":1},{"version":"e4af494f7a14b226bbe732e9c130d8811f8c7025911d7c58dd97121a85519715","impliedFormat":1},{"version":"cbb1c5ba5dbabe42c19ca31b83e48fec95895484fe1d1a8fb649b69ea224c5b8","impliedFormat":99},{"version":"556ccd493ec36c7d7cb130d51be66e147b91cc1415be383d71da0f1e49f742a9","impliedFormat":1},{"version":"b6d03c9cfe2cf0ba4c673c209fcd7c46c815b2619fd2aad59fc4229aaef2ed43","impliedFormat":1},{"version":"95aba78013d782537cc5e23868e736bec5d377b918990e28ed56110e3ae8b958","impliedFormat":1},{"version":"670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","impliedFormat":1},{"version":"13b77ab19ef7aadd86a1e54f2f08ea23a6d74e102909e3c00d31f231ed040f62","impliedFormat":1},{"version":"069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","impliedFormat":1},{"version":"ef94438f848be3a3b0033013bf64753f771f983c1e205e4a06675eb253ca7cd2","impliedFormat":99},{"version":"d920c349abc38c151f86499ae2c16d07862cc103e8dd3c781710aabbdb667e61","impliedFormat":1},{"version":"9fe2a0b69d0d9cf90e3d0e7dffc9fa080194a8fedbe191dcece5cf8449452f4d","impliedFormat":1},{"version":"c400678110f688feba4d6d3f93269fca02834bb3d6a1ecd99b28b3f69f7d23f4","impliedFormat":1},{"version":"0531ff3bd78d643b584e02909c5fdb86938169d818afbbef1bc3a2a768ab32ec","impliedFormat":1},"233c18ea181b6edf1129b693329a5763449a931bd8ce24d41135f4c6d52834fb",{"version":"e08660f21d0e8b367414e78706ae69a19b078fb67b0fe8c818ccaeeeedc00272","impliedFormat":1},{"version":"32727845ab5bd8a9ef3e4844c567c09f6d418fcf0f90d381c00652a6f23e7f6e","impliedFormat":1},{"version":"c1a2e05eb6d7ca8d7e4a7f4c93ccf0c2857e842a64c98eaee4d85841ee9855e6","impliedFormat":1},{"version":"835fb2909ce458740fb4a49fc61709896c6864f5ce3db7f0a88f06c720d74d02","impliedFormat":1},{"version":"6e5857f38aa297a859cab4ec891408659218a5a2610cd317b6dcbef9979459cc","impliedFormat":1},{"version":"ead8e39c2e11891f286b06ae2aa71f208b1802661fcdb2425cffa4f494a68854","impliedFormat":1},{"version":"40ba6c32eb732a09e4446ade5cb6ad0c147f186f9c9dc6878b90b4418ad9f6ea","impliedFormat":1},{"version":"fdd814741843f85c98281522c58f5a646590ba9019fad2efaa95987655e0611b","impliedFormat":1},{"version":"c78aff4fb58b28b8f642d5095fc7eeb79f00e652a67caa19693af1adabb833c9","impliedFormat":1},{"version":"f80a08ced8818dc99359c0acd5b3f12762e1ce53758007759b0d4e503cbf4a5e","impliedFormat":1},{"version":"37935fa7564bcc6e0bc845b766a24391098d26f7c8245d6e8ab37bc016816e94","impliedFormat":1},{"version":"68add36d9632bc096d7245d24d6b0b8ad5f125183016102a3dad4c9c2438ccb0","impliedFormat":1},{"version":"3a819c2928ee06bbcc84e2797fd3558ae2ebb7e0ed8d87f71732fb2e2acc87b4","impliedFormat":1},{"version":"0f8a263f4c8595c8a07de52e3f3927640c44386c1aa2984de9eae50d75e613b2","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"346fffde7c32da87c2196eb7494422449dc2ca82d3b4e6bf55be1d1a33ffc2b0","impliedFormat":1},{"version":"add0ce7b77ba5b308492fa68f77f24d1ed1d9148534bdf05ac17c30763fc1a79","impliedFormat":1},{"version":"8b5875e4958528042103fdd775e106a7f76bafc29709f0690df9a7d2241d52a7","impliedFormat":1},{"version":"2f67911e4bf4e0717dc2ded248ce2d5e4398d945ee13889a6852c1233ea41508","impliedFormat":1},{"version":"d8430c275b0f59417ea8e173cfb888a4477b430ec35b595bf734f3ec7a7d729f","impliedFormat":1},{"version":"69364df1c776372d7df1fb46a6cb3a6bf7f55e700f533a104e3f9d70a32bec18","impliedFormat":1},{"version":"6042774c61ece4ba77b3bf375f15942eb054675b7957882a00c22c0e4fe5865c","impliedFormat":1},{"version":"5a3bd57ed7a9d9afef74c75f77fce79ba3c786401af9810cdf45907c4e93f30e","impliedFormat":1},{"version":"aef26cf95593c8ace1c62c4724f9afac77bdfa756fb8a00613cd152117cb2f43","impliedFormat":1},{"version":"30db853bb2e60170ba11e39ab48bacecb32d06d4def89eedf17e58ebab762a65","impliedFormat":1},{"version":"e27451b24234dfed45f6cf22112a04955183a99c42a2691fb4936d63cfe42761","impliedFormat":1},{"version":"2316301dd223d31962d917999acf8e543e0119c5d24ec984c9f22cb23247160c","impliedFormat":1},{"version":"58d65a2803c3b6629b0e18c8bf1bc883a686fcf0333230dd0151ab6e85b74307","impliedFormat":1},{"version":"e818471014c77c103330aee11f00a7a00b37b35500b53ea6f337aefacd6174c9","impliedFormat":1},{"version":"268fd6d9f2e807a39a6c5aa654b00f949feb63d3faa7dd0f9bba7dde9172159c","impliedFormat":1},{"version":"29f823cbe0166e10e7176a94afe609a24b9e5af3858628c541ff8ce1727023cd","impliedFormat":1},{"version":"d3cfde44f8089768ebb08098c96d01ca260b88bccf238d55eee93f1c620ff5a5","impliedFormat":1},{"version":"293eadad9dead44c6fd1db6de552663c33f215c55a1bfa2802a1bceed88ff0ec","impliedFormat":1},{"version":"36eb5babc665b890786550d4a8cb20ef7105673a6d5551fbdd7012877bb26942","impliedFormat":1},{"version":"fec412ded391a7239ef58f455278154b62939370309c1fed322293d98c8796a6","impliedFormat":1},{"version":"e3498cf5e428e6c6b9e97bd88736f26d6cf147dedbfa5a8ad3ed8e05e059af8a","impliedFormat":1},{"version":"dba3f34531fd9b1b6e072928b6f885aa4d28dd6789cbd0e93563d43f4b62da53","impliedFormat":1},{"version":"f672c876c1a04a223cf2023b3d91e8a52bb1544c576b81bf64a8fec82be9969c","impliedFormat":1},{"version":"e4b03ddcf8563b1c0aee782a185286ed85a255ce8a30df8453aade2188bbc904","impliedFormat":1},{"version":"2329d90062487e1eaca87b5e06abcbbeeecf80a82f65f949fd332cfcf824b87b","impliedFormat":1},{"version":"25b3f581e12ede11e5739f57a86e8668fbc0124f6649506def306cad2c59d262","impliedFormat":1},{"version":"93c3e73824ad57f98fd23b39335dbdae2db0bd98199b0dc0b9ccc60bf3c5134a","impliedFormat":1},{"version":"a9ebb67d6bbead6044b43714b50dcb77b8f7541ffe803046fdec1714c1eba206","impliedFormat":1},{"version":"833e92c058d033cde3f29a6c7603f517001d1ddd8020bc94d2067a3bc69b2a8e","impliedFormat":1},{"version":"5d22f19e0756e9fc11fde5bb67eee993c289fc371b8ad3fc63629bfaadff79a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"8324f3861a7a8db0f9d294f6a189182b2d231840cebb7f3ea5f4635773cdaf41","impliedFormat":1},{"version":"7a8ec10b0834eb7183e4bfcd929838ac77583828e343211bb73676d1e47f6f01","impliedFormat":1},{"version":"be57def447f85b42c8f509a8ce4125c1af5f26597c4a93ef617aae5e0b81fa02","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f00324f263189b385c3a9383b1f4dae6237697bcf0801f96aa35c340512d79c","impliedFormat":1},{"version":"ec8997c2e5cea26befc76e7bf990750e96babb16977673a9ff3b5c0575d01e48","impliedFormat":1},"6cf13b54411f9888a1aa5e200b871e56f0406f3f9a84d16f7b0b861fe231ddb4",{"version":"c76c02846ba7d40b9b3488f0e8d75d02cbdee2f0bc5fcd55dd3bd2e1457646ea","impliedFormat":99},{"version":"4ead13a482c539b77394b2a97e3b877b809eac596390371cea490286f53b996a","impliedFormat":99},{"version":"12405c748be62bbefb6df7515956b0921f84d7b444d1f6c3e00af1980cc3af17","impliedFormat":99},{"version":"bdd14f07b4eca0b4b5203b85b8dbc4d084c749fa590bee5ea613e1641dcd3b29","impliedFormat":99},{"version":"3a582c6e8906f5b094ccf0de6cc6f4f8a54b05a34f52517aba5c9c7f704f6b28","impliedFormat":99},{"version":"ef13c73d6157a32933c612d476c1524dd674cf5b9a88571d7d6a0d147544d529","impliedFormat":99},{"version":"49ab4f1d153a252779958fc87b700743d32b5ffa42addd70ae23ad3f429daa5c","impliedFormat":99},{"version":"0528f6d21f7a02d4092895090d2dd86104bd5a3e79eced96d5a1a7dd90943d17","impliedFormat":99},{"version":"427fe2004642504828c1476d0af4270e6ad4db6de78c0b5da3e4c5ca95052a99","impliedFormat":1},{"version":"2eeffcee5c1661ddca53353929558037b8cf305ffb86a803512982f99bcab50d","impliedFormat":99},{"version":"9afb4cb864d297e4092a79ee2871b5d3143ea14153f62ef0bb04ede25f432030","affectsGlobalScope":true,"impliedFormat":99},{"version":"5c935b7fc4ddc1410ea1cd7cd4e35ed106a6e4920dd27a9480a40fd224359dc3","affectsGlobalScope":true,"impliedFormat":99},{"version":"b5ce343886d23392be9c8280e9f24a87f1d7d3667f6672c2fe4aa61fa4ece7d4","impliedFormat":99},{"version":"72ce5b734c05da85c85a6f6dc05823b051d6aa41acaedeeb1d17c72f3b4efa72","impliedFormat":99},{"version":"b0857bb28fd5236ace84280f79a25093f919fd0eff13e47cc26ea03de60a7294","impliedFormat":99},{"version":"5e43e0824f10cd8c48e7a8c5c673638488925a12c31f0f9e0957965c290eb14c","impliedFormat":99},{"version":"53cf4076f42b29b8d411259d168d51b3a0274c42c8814e5b44dfa8803a35d4fc","impliedFormat":99},{"version":"a39461ee1f27cf3e6cfd63d21045713d26d521da55ea4d8efccb705f689e6dbb","impliedFormat":99},{"version":"61bb64660ee150f3ab618340e15cca0a81664801bede7c966ca0eca3a952fe63","impliedFormat":99},{"version":"42a12f2faa483c9b48195ed794d22698162274e755f6e07219c2351c4f08d732","impliedFormat":99},{"version":"ec0c42bb0f465e4993f2bc68a6ce9df9a2dcbc7b83e21748f82f1b69561938e3","impliedFormat":99},{"version":"f50ff37a9cbbe74475f426474d9827083c7c2c138a954d28f1690df338f69291","impliedFormat":99},{"version":"61fd6c17235d530c40f543dd7c40afab091d91c1ef890baeed30db6d82b04b28","impliedFormat":99},{"version":"bcbd3becd08b4515225880abea0dbfbbf0d1181ce3af8f18f72f61edbe4febfb","impliedFormat":99},{"version":"091767bc841f937654ed597d49e023ed59850355e746ae1a6f20ab31076ee1fb","impliedFormat":99},{"version":"19c6d6135af59693698d384050b45a8a049493500add442f58e4bd7c8a255ab6","impliedFormat":99},{"version":"6a0dba12d55314638a8c51108b20fe2f68f1364a619d098918bda91c22dec154","impliedFormat":99},{"version":"8124828a11be7db984fcdab052fd4ff756b18edcfa8d71118b55388176210923","impliedFormat":99},{"version":"2b39c6cf59088713babbfc3e20ee85f1375d40e66953156fa658346b8346f24f","impliedFormat":99},{"version":"69bf2422313487956e4dacf049f30cb91b34968912058d244cb19e4baa24da97","impliedFormat":99},{"version":"6987dfb4b0c4e02112cc4e548e7a77b3d9ddfeffa8c8a2db13ceac361a4567d9","impliedFormat":99},{"version":"5e2ba3d18d78aebbde1f34bde356e41e9c76eeaeaeee56a37036596a9eff4211","impliedFormat":99},{"version":"8280ae8ccc0493b32d1742d585357ab9f0a508ea050af25a5a20d64010d0a5cf","impliedFormat":99},{"version":"7adfd9f9056ecd4ae6c65fde2a98654960c662714c73f048478959d04c09e144","impliedFormat":99},{"version":"437b7613a30a2fcde463f7b707c6d5567a8823fbc51de50b8641bf5b1d126fad","impliedFormat":99},{"version":"63ea959e28c110923f495576e614fb8b36c09b6828b467b2c7cd7f03b03ccf9f","impliedFormat":99},{"version":"1601a95dbb33059fc3d12638ed2a9aecff899e339c5c0f3a0b28768866d385b4","impliedFormat":99},{"version":"56fc978580577d30f4c2cdb5b1eb9217b66ed66537dd27141256f426e4b8dd68","impliedFormat":99},{"version":"2c5413050a2580becf9d82dd7e3006b95623e96f145356bf73230cd635352f70","impliedFormat":99},{"version":"860bedc71ead192ea4a0ea5ef4686e65724d14b391ebd1a6671a7044e6bd8e15","impliedFormat":99},{"version":"7c0a845bee4a084cbb8654709f48e5f13e2f6d45e5e2dde7c57cadf79fd9e3d5","impliedFormat":99},{"version":"7fa6fdc6eeeb3850e6b3166836f8c9199594c4f4bed3cee02cac0ca33d71894d","impliedFormat":99},{"version":"e53757f3e6688e8be16b36ccbeedc85637e607353fc67ff7b6665d6005aeaa12","impliedFormat":99},{"version":"88ba2660f5b024c7afd02ea700bc0c677687e17e34f0a94c3256ddb55e0f1c5d","impliedFormat":99},{"version":"7bbff6783e96c691a41a7cf12dd5486b8166a01b0c57d071dbcfca55c9525ec4","impliedFormat":99},"9c00dd8f0d59eb05b152984d3d393295a9eafc76c37272092581253396a21b7b",{"version":"ae77d81a5541a8abb938a0efedf9ac4bea36fb3a24cc28cfa11c598863aba571","impliedFormat":1},{"version":"42baf4ca38c38deaf411ea73f37bc39ff56c6e5c761a968b64ac1b25c92b5cd8","impliedFormat":1},{"version":"4f6ae308c5f2901f2988c817e1511520619e9025b9b12cc7cce2ab2e6ffed78a","impliedFormat":1},{"version":"8718fa41d7cf4aa91de4e8f164c90f88e0bf343aa92a1b9b725a9c675c64e16b","impliedFormat":1},{"version":"f992cd6cc0bcbaa4e6c810468c90f2d8595f8c6c3cf050c806397d3de8585562","impliedFormat":1},{"version":"fb893a0dfc3c9fb0f9ca93d0648694dd95f33cbad2c0f2c629f842981dfd4e2e","impliedFormat":1},{"version":"89e326922cadcc2331d7e851011cf9f0456a681aaf3c95b48b81f8d80e8cdfba","impliedFormat":1},{"version":"d44c53a5185ae285987268f3f30cdadf3dc7bff19d230bfcb5c6505d4f268299","affectsGlobalScope":true,"impliedFormat":1},{"version":"b7f161bf747d17d98f49d7c2a9e67da87031da2c2cbcf356698fbfc184788b20","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d9ff2131e0cd0d00541853f45cf91d93570f4657717daee949596e828dfecd5","affectsGlobalScope":true,"impliedFormat":1},{"version":"ed19da84b7dbf00952ad0b98ce5c194f1903bcf7c94d8103e8e0d63b271543ae","impliedFormat":1},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"fec943fdb3275eb6e006b35e04a8e2e99e9adf3f4b969ddf15315ac7575a93e4","impliedFormat":1},{"version":"87d9d29dbc745f182683f63187bf3d53fd8673e5fca38ad5eaab69798ed29fbc","impliedFormat":1},{"version":"eb5b19b86227ace1d29ea4cf81387279d04bb34051e944bc53df69f58914b788","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"035312d4945d13efa134ae482f6dc56a1a9346f7ac3be7ccbad5741058ce87f3","affectsGlobalScope":true,"impliedFormat":1},{"version":"17ed71200119e86ccef2d96b73b02ce8854b76ad6bd21b5021d4269bec527b5f","impliedFormat":1},{"version":"7fa8d75d229eeaee235a801758d9c694e94405013fe77d5d1dd8e3201fc414f1","impliedFormat":1},{"version":"74d5a87c3616cd5d8691059d531504403aa857e09cbaecb1c64dfb9ace0db185","impliedFormat":1}],"root":[209,311,357],"options":{"allowSyntheticDefaultImports":true,"composite":true,"module":99,"skipLibCheck":true,"strict":true},"referencedMap":[[311,1],[209,2],[357,3],[200,4],[198,5],[169,6],[167,5],[168,7],[170,8],[165,9],[163,5],[166,10],[164,11],[310,12],[315,5],[358,5],[203,13],[199,4],[201,14],[202,4],[362,15],[322,16],[364,17],[320,5],[367,18],[366,19],[365,5],[157,5],[368,20],[360,5],[369,5],[370,21],[363,5],[100,22],[101,22],[102,23],[55,24],[103,25],[104,26],[105,27],[50,5],[53,28],[51,5],[52,5],[106,29],[107,30],[108,31],[109,32],[110,33],[111,34],[112,34],[113,35],[114,36],[115,37],[116,38],[56,5],[54,5],[117,39],[118,40],[119,41],[153,42],[120,43],[121,5],[122,44],[123,45],[124,46],[125,47],[126,48],[127,49],[128,50],[129,51],[130,52],[131,52],[132,53],[133,5],[134,54],[135,55],[137,56],[136,57],[138,58],[139,59],[140,60],[141,61],[142,62],[143,63],[144,64],[145,65],[146,66],[147,67],[148,68],[149,69],[150,70],[57,5],[58,5],[59,5],[97,71],[98,5],[99,5],[151,72],[152,73],[371,5],[375,74],[372,5],[374,75],[361,76],[376,5],[377,77],[204,78],[323,79],[344,80],[345,81],[343,5],[316,5],[329,82],[328,83],[340,82],[331,84],[333,85],[352,85],[332,86],[313,87],[312,5],[318,88],[319,89],[349,90],[325,91],[327,92],[348,5],[346,91],[326,5],[317,89],[324,5],[321,5],[373,5],[305,93],[162,5],[359,43],[307,94],[210,5],[211,94],[306,95],[309,96],[308,97],[189,98],[187,99],[188,100],[176,101],[177,99],[184,102],[175,103],[180,104],[190,5],[181,105],[186,106],[192,107],[191,108],[174,109],[182,110],[183,111],[178,112],[185,98],[179,113],[159,114],[158,115],[173,5],[171,116],[341,5],[314,5],[1,5],[48,5],[49,5],[9,5],[13,5],[12,5],[3,5],[14,5],[15,5],[16,5],[17,5],[18,5],[19,5],[20,5],[21,5],[4,5],[22,5],[23,5],[5,5],[24,5],[28,5],[25,5],[26,5],[27,5],[29,5],[30,5],[31,5],[6,5],[32,5],[33,5],[34,5],[35,5],[7,5],[39,5],[36,5],[37,5],[38,5],[40,5],[8,5],[41,5],[46,5],[47,5],[42,5],[43,5],[44,5],[45,5],[2,5],[11,5],[10,5],[75,117],[85,118],[74,117],[95,119],[66,120],[65,121],[94,122],[88,123],[93,124],[68,125],[82,126],[67,127],[91,128],[63,129],[62,122],[92,130],[64,131],[69,132],[70,5],[73,132],[60,5],[96,133],[86,134],[77,135],[78,136],[80,137],[76,138],[79,139],[89,122],[71,140],[72,141],[81,142],[61,143],[84,134],[83,132],[87,5],[90,144],[207,145],[206,146],[208,147],[205,148],[155,149],[197,150],[161,151],[156,149],[154,5],[160,152],[195,5],[193,5],[194,5],[172,153],[196,154],[351,155],[356,156],[350,157],[342,158],[338,159],[334,160],[347,5],[335,84],[354,161],[353,162],[336,163],[330,5],[337,164],[355,165],[339,5],[291,166],[296,167],[303,168],[298,5],[299,5],[297,169],[300,170],[292,5],[293,5],[304,171],[295,172],[301,5],[302,173],[294,174],[285,175],[289,176],[286,176],[282,175],[290,177],[287,178],[288,176],[283,179],[284,180],[278,181],[219,182],[221,183],[277,5],[220,184],[281,185],[280,186],[279,187],[212,5],[222,182],[223,5],[214,188],[218,189],[213,5],[215,190],[216,191],[217,5],[224,192],[225,192],[226,192],[227,192],[228,192],[229,192],[230,192],[231,192],[232,192],[233,192],[234,192],[235,192],[236,192],[237,192],[239,192],[238,192],[240,192],[241,192],[242,192],[243,192],[244,192],[276,193],[245,192],[246,192],[247,192],[248,192],[249,192],[250,192],[251,192],[252,192],[253,192],[254,192],[255,192],[256,192],[257,192],[259,192],[258,192],[260,192],[261,192],[262,192],[263,192],[264,192],[265,192],[266,192],[267,192],[268,192],[269,192],[270,192],[271,192],[272,192],[275,192],[273,192],[274,192]],"affectedFilesPendingEmit":[[311,17],[209,17],[357,17]],"emitSignatures":[209,311,357],"version":"5.9.3"} \ No newline at end of file