import type { ScopedThreadRef } from "@t3tools/contracts"; import { isAtomCommandInterrupted } from "@t3tools/client-runtime/state/runtime"; import * as Schema from "effect/Schema"; import { browserDefaultOpenProfileId, browserDefaultOpenViewport, resolveBrowserDefaults, } from "~/browser/browserDefaults"; import { isWebUrl, resolveBrowserLinkTargetPreference } from "~/browser/browserLinkTarget"; import type { OpenPreviewMutation } from "~/browser/openFileInPreview"; import { recordVisitForThread } from "~/browserHistoryStore"; import { applyPreviewServerSnapshot, isPreviewSupportedInRuntime } from "~/previewStateStore"; import { useRightPanelStore } from "~/rightPanelStore"; const terminalLinkErrorContext = { environmentId: Schema.String, threadId: Schema.String, targetOrigin: Schema.String, cause: Schema.Defect(), }; export class TerminalLinkPreviewOpenError extends Schema.TaggedErrorClass()( "TerminalLinkPreviewOpenError", terminalLinkErrorContext, ) { override get message(): string { return `Failed to open terminal link ${this.targetOrigin} in preview for thread ${this.threadId}.`; } } interface OpenTerminalLinkInPreviewInput { readonly url: string; readonly threadRef: ScopedThreadRef; readonly openPreview: OpenPreviewMutation; readonly fallbackToBrowser: () => void; } /** * Opens a terminal hyperlink where the "Open links in" setting says. Terminal * links are activated with the platform modifier already held, so unlike chat * links the modifier cannot double as the system-browser override; the setting * alone decides, and the system browser is the fallback whenever the in-app * one cannot take the URL. */ export async function openTerminalLinkInPreview( input: OpenTerminalLinkInPreviewInput, ): Promise { const supportsPreview = isWebUrl(input.url) && isPreviewSupportedInRuntime() && input.threadRef.threadId.length > 0 && (await resolveBrowserLinkTargetPreference()) === "app"; if (!supportsPreview) { input.fallbackToBrowser(); return; } const errorContext = { environmentId: input.threadRef.environmentId, threadId: input.threadRef.threadId, targetOrigin: new URL(input.url).origin, }; const defaults = await resolveBrowserDefaults(); const result = await input.openPreview({ environmentId: input.threadRef.environmentId, input: { threadId: input.threadRef.threadId, url: input.url, // Same reason as `openUrlInPreview`: this path handles its own result // mapping, so the configured defaults are applied explicitly. viewport: browserDefaultOpenViewport(defaults), profileId: browserDefaultOpenProfileId(defaults), }, }); if (result._tag === "Failure") { if (isAtomCommandInterrupted(result)) { return; } console.error( new TerminalLinkPreviewOpenError({ ...errorContext, cause: result.cause, }), ); input.fallbackToBrowser(); return; } recordVisitForThread(input.threadRef, input.url); applyPreviewServerSnapshot(input.threadRef, result.value); useRightPanelStore.getState().openBrowser(input.threadRef, result.value.tabId); }