| author | |
| committer | |
| log | 9b43123ba2a90ecaaf2eac1f75b6aebcf181f253 |
| tree | 40fd2cc58603d6ca9504bf3f36136e8bebb53e39 |
| parent | 17fb0e4a9a068f3ba6c55871dd44ed3c5d70aefe |
Co-authored-by: codex <codex@users.noreply.github.com>30 files changed, 381 insertions(+), 163 deletions(-)
apps/desktop/src/ipc/methods/preview.ts+42-14| ... | ... | @@ -1,7 +1,7 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | |
| 2 | import * as Data from "effect/Data"; | |
| 1 | 3 | import * as Effect from "effect/Effect"; |
| 2 | 4 | import { BrowserWindow } from "electron"; |
| 3 | import * as NodeFileSystem from "node:fs"; | |
| 4 | import * as NodePath from "node:path"; | |
| 5 | 5 | import { pathToFileURL } from "node:url"; |
| 6 | 6 | |
| 7 | 7 | import { previewViewManager } from "../../preview-view-manager.ts"; |
| ... | ... | @@ -29,18 +29,38 @@ const tabIdFrom = (raw: unknown): string => { |
| 29 | 29 | return tabId; |
| 30 | 30 | }; |
| 31 | 31 | |
| 32 | const method = (channel: string, handler: (raw: unknown) => unknown | Promise<unknown>): DesktopIpcMethod<unknown, never> => ({ | |
| 32 | class PreviewIpcError extends Data.TaggedError("PreviewIpcError")<{ | |
| 33 | readonly cause: unknown; | |
| 34 | }> {} | |
| 35 | ||
| 36 | const method = ( | |
| 37 | channel: string, | |
| 38 | handler: (raw: unknown) => unknown | Promise<unknown>, | |
| 39 | ): DesktopIpcMethod<PreviewIpcError, never> => ({ | |
| 33 | 40 | channel, |
| 34 | handler: (raw) => Effect.tryPromise({ try: () => Promise.resolve(handler(raw)), catch: (error) => error }), | |
| 41 | handler: (raw) => | |
| 42 | Effect.tryPromise({ | |
| 43 | try: () => Promise.resolve(handler(raw)), | |
| 44 | catch: (cause) => new PreviewIpcError({ cause }), | |
| 45 | }), | |
| 35 | 46 | }); |
| 36 | 47 | |
| 37 | 48 | export const previewMethods = [ |
| 38 | method(IpcChannels.PREVIEW_CREATE_TAB_CHANNEL, (raw) => previewViewManager.createTab(tabIdFrom(raw))), | |
| 39 | method(IpcChannels.PREVIEW_CLOSE_TAB_CHANNEL, (raw) => previewViewManager.closeTab(tabIdFrom(raw))), | |
| 49 | method(IpcChannels.PREVIEW_CREATE_TAB_CHANNEL, (raw) => | |
| 50 | previewViewManager.createTab(tabIdFrom(raw)), | |
| 51 | ), | |
| 52 | method(IpcChannels.PREVIEW_CLOSE_TAB_CHANNEL, (raw) => | |
| 53 | previewViewManager.closeTab(tabIdFrom(raw)), | |
| 54 | ), | |
| 40 | 55 | method(IpcChannels.PREVIEW_REGISTER_WEBVIEW_CHANNEL, (raw) => { |
| 41 | 56 | const tabId = tabIdFrom(raw); |
| 42 | const webContentsId = typeof raw === "object" && raw !== null && "webContentsId" in raw ? raw.webContentsId : null; | |
| 43 | if (typeof webContentsId !== "number" || !Number.isInteger(webContentsId) || webContentsId <= 0) { | |
| 57 | const webContentsId = | |
| 58 | typeof raw === "object" && raw !== null && "webContentsId" in raw ? raw.webContentsId : null; | |
| 59 | if ( | |
| 60 | typeof webContentsId !== "number" || | |
| 61 | !Number.isInteger(webContentsId) || | |
| 62 | webContentsId <= 0 | |
| 63 | ) { | |
| 44 | 64 | throw new Error("preview webContentsId must be a positive integer"); |
| 45 | 65 | } |
| 46 | 66 | return previewViewManager.registerWebview(tabId, webContentsId); |
| ... | ... | @@ -52,21 +72,29 @@ export const previewMethods = [ |
| 52 | 72 | return previewViewManager.navigate(tabId, url); |
| 53 | 73 | }), |
| 54 | 74 | method(IpcChannels.PREVIEW_GO_BACK_CHANNEL, (raw) => previewViewManager.goBack(tabIdFrom(raw))), |
| 55 | method(IpcChannels.PREVIEW_GO_FORWARD_CHANNEL, (raw) => previewViewManager.goForward(tabIdFrom(raw))), | |
| 75 | method(IpcChannels.PREVIEW_GO_FORWARD_CHANNEL, (raw) => | |
| 76 | previewViewManager.goForward(tabIdFrom(raw)), | |
| 77 | ), | |
| 56 | 78 | method(IpcChannels.PREVIEW_REFRESH_CHANNEL, (raw) => previewViewManager.refresh(tabIdFrom(raw))), |
| 57 | 79 | method(IpcChannels.PREVIEW_ZOOM_IN_CHANNEL, (raw) => previewViewManager.zoomIn(tabIdFrom(raw))), |
| 58 | 80 | method(IpcChannels.PREVIEW_ZOOM_OUT_CHANNEL, (raw) => previewViewManager.zoomOut(tabIdFrom(raw))), |
| 59 | method(IpcChannels.PREVIEW_RESET_ZOOM_CHANNEL, (raw) => previewViewManager.resetZoom(tabIdFrom(raw))), | |
| 60 | method(IpcChannels.PREVIEW_HARD_RELOAD_CHANNEL, (raw) => previewViewManager.hardReload(tabIdFrom(raw))), | |
| 61 | method(IpcChannels.PREVIEW_OPEN_DEVTOOLS_CHANNEL, (raw) => previewViewManager.openDevTools(tabIdFrom(raw))), | |
| 81 | method(IpcChannels.PREVIEW_RESET_ZOOM_CHANNEL, (raw) => | |
| 82 | previewViewManager.resetZoom(tabIdFrom(raw)), | |
| 83 | ), | |
| 84 | method(IpcChannels.PREVIEW_HARD_RELOAD_CHANNEL, (raw) => | |
| 85 | previewViewManager.hardReload(tabIdFrom(raw)), | |
| 86 | ), | |
| 87 | method(IpcChannels.PREVIEW_OPEN_DEVTOOLS_CHANNEL, (raw) => | |
| 88 | previewViewManager.openDevTools(tabIdFrom(raw)), | |
| 89 | ), | |
| 62 | 90 | method(IpcChannels.PREVIEW_CLEAR_COOKIES_CHANNEL, () => previewViewManager.clearCookies()), |
| 63 | 91 | method(IpcChannels.PREVIEW_CLEAR_CACHE_CHANNEL, () => previewViewManager.clearCache()), |
| 64 | 92 | method(IpcChannels.PREVIEW_GET_CONFIG_CHANNEL, () => { |
| 65 | const preloadPath = NodePath.join(__dirname, "preview-pick-preload.cjs"); | |
| 93 | const preloadPath = `${__dirname}/preview-pick-preload.cjs`; | |
| 66 | 94 | return { |
| 67 | 95 | partition: previewViewManager.getBrowserPartition(), |
| 68 | 96 | webPreferences: PREVIEW_WEBVIEW_PREFERENCES, |
| 69 | preloadUrl: NodeFileSystem.existsSync(preloadPath) ? pathToFileURL(preloadPath).href : null, | |
| 97 | preloadUrl: pathToFileURL(preloadPath).href, | |
| 70 | 98 | }; |
| 71 | 99 | }), |
| 72 | 100 | method(IpcChannels.PREVIEW_PICK_ELEMENT_CHANNEL, (raw) => |
apps/desktop/src/main.ts-2| ... | ... | @@ -28,7 +28,6 @@ import * as DesktopApp from "./app/DesktopApp.ts"; |
| 28 | 28 | import * as DesktopAppIdentity from "./app/DesktopAppIdentity.ts"; |
| 29 | 29 | import * as DesktopCloudAuth from "./app/DesktopCloudAuth.ts"; |
| 30 | 30 | import * as DesktopCloudAuthTokenStore from "./app/DesktopCloudAuthTokenStore.ts"; |
| 31 | import * as DesktopConnectionCatalogStore from "./app/DesktopConnectionCatalogStore.ts"; | |
| 32 | 31 | import * as DesktopApplicationMenu from "./window/DesktopApplicationMenu.ts"; |
| 33 | 32 | import * as DesktopAssets from "./app/DesktopAssets.ts"; |
| 34 | 33 | import * as DesktopBackendConfiguration from "./backend/DesktopBackendConfiguration.ts"; |
| ... | ... | @@ -115,7 +114,6 @@ const desktopFoundationLayer = Layer.mergeAll( |
| 115 | 114 | DesktopClientSettings.layer, |
| 116 | 115 | DesktopSavedEnvironments.layer, |
| 117 | 116 | DesktopCloudAuthTokenStore.layer, |
| 118 | DesktopConnectionCatalogStore.layer, | |
| 119 | 117 | DesktopAssets.layer, |
| 120 | 118 | DesktopObservability.layer, |
| 121 | 119 | ).pipe(Layer.provideMerge(desktopEnvironmentLayer)); |
apps/desktop/src/picked-element-payload.test.ts+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | import { describe, expect, it } from "vitest"; | |
| 1 | import { describe, expect, it } from "vite-plus/test"; | |
| 2 | 2 | |
| 3 | 3 | import { isPickedElementPayload } from "./picked-element-payload.ts"; |
| 4 | 4 |
apps/desktop/src/preload.ts+3-7| ... | ... | @@ -47,10 +47,6 @@ contextBridge.exposeInMainWorld("desktopBridge", { |
| 47 | 47 | ipcRenderer.invoke(IpcChannels.SET_SAVED_ENVIRONMENT_SECRET_CHANNEL, { environmentId, secret }), |
| 48 | 48 | removeSavedEnvironmentSecret: (environmentId) => |
| 49 | 49 | ipcRenderer.invoke(IpcChannels.REMOVE_SAVED_ENVIRONMENT_SECRET_CHANNEL, environmentId), |
| 50 | getConnectionCatalog: () => ipcRenderer.invoke(IpcChannels.GET_CONNECTION_CATALOG_CHANNEL), | |
| 51 | setConnectionCatalog: (catalog) => | |
| 52 | ipcRenderer.invoke(IpcChannels.SET_CONNECTION_CATALOG_CHANNEL, catalog), | |
| 53 | clearConnectionCatalog: () => ipcRenderer.invoke(IpcChannels.CLEAR_CONNECTION_CATALOG_CHANNEL), | |
| 54 | 50 | discoverSshHosts: () => ipcRenderer.invoke(IpcChannels.DISCOVER_SSH_HOSTS_CHANNEL), |
| 55 | 51 | ensureSshEnvironment: async (target, options) => |
| 56 | 52 | unwrapEnsureSshEnvironmentResult( |
| ... | ... | @@ -164,8 +160,7 @@ contextBridge.exposeInMainWorld("desktopBridge", { |
| 164 | 160 | clearCookies: () => ipcRenderer.invoke(IpcChannels.PREVIEW_CLEAR_COOKIES_CHANNEL), |
| 165 | 161 | clearCache: () => ipcRenderer.invoke(IpcChannels.PREVIEW_CLEAR_CACHE_CHANNEL), |
| 166 | 162 | getPreviewConfig: () => ipcRenderer.invoke(IpcChannels.PREVIEW_GET_CONFIG_CHANNEL), |
| 167 | pickElement: (tabId) => | |
| 168 | ipcRenderer.invoke(IpcChannels.PREVIEW_PICK_ELEMENT_CHANNEL, { tabId }), | |
| 163 | pickElement: (tabId) => ipcRenderer.invoke(IpcChannels.PREVIEW_PICK_ELEMENT_CHANNEL, { tabId }), | |
| 169 | 164 | cancelPickElement: (tabId) => |
| 170 | 165 | ipcRenderer.invoke(IpcChannels.PREVIEW_CANCEL_PICK_ELEMENT_CHANNEL, { tabId }), |
| 171 | 166 | onStateChange: (listener) => { |
| ... | ... | @@ -178,7 +173,8 @@ contextBridge.exposeInMainWorld("desktopBridge", { |
| 178 | 173 | listener(tabId, state as DesktopPreviewTabState); |
| 179 | 174 | }; |
| 180 | 175 | ipcRenderer.on(IpcChannels.PREVIEW_STATE_CHANGE_CHANNEL, wrappedListener); |
| 181 | return () => ipcRenderer.removeListener(IpcChannels.PREVIEW_STATE_CHANGE_CHANNEL, wrappedListener); | |
| 176 | return () => | |
| 177 | ipcRenderer.removeListener(IpcChannels.PREVIEW_STATE_CHANGE_CHANNEL, wrappedListener); | |
| 182 | 178 | }, |
| 183 | 179 | }, |
| 184 | 180 | } satisfies DesktopBridge); |
apps/desktop/src/preview-pick-preload.test.ts+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | import { describe, expect, it } from "vitest"; | |
| 1 | import { describe, expect, it } from "vite-plus/test"; | |
| 2 | 2 | |
| 3 | 3 | import { computeLabelPosition } from "./preview-pick-label-position.ts"; |
| 4 | 4 |
apps/desktop/src/preview-pick-preload.ts+1| ... | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics globalDate:off | |
| 1 | 2 | /** |
| 2 | 3 | * Preview pick preload — runs in the isolated world of the Chromium |
| 3 | 4 | * `<webview>` that hosts the in-app browser. Loaded via the |
apps/desktop/src/preview-view-manager.ts+1| ... | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics globalDate:off | |
| 1 | 2 | /** |
| 2 | 3 | * PreviewViewManager — desktop side of the in-app browser preview. |
| 3 | 4 | * |
apps/desktop/src/preview-webview-preferences.test.ts+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | import { describe, expect, it } from "vitest"; | |
| 1 | import { describe, expect, it } from "vite-plus/test"; | |
| 2 | 2 | |
| 3 | 3 | import { PREVIEW_WEBVIEW_PREFERENCES } from "./preview-webview-preferences.ts"; |
| 4 | 4 |
apps/server/src/preview/Layers/Manager.test.ts+1-1| ... | ... | @@ -1,7 +1,7 @@ |
| 1 | 1 | import { it } from "@effect/vitest"; |
| 2 | 2 | import { type PreviewEvent, ThreadId } from "@t3tools/contracts"; |
| 3 | 3 | import { Effect, PubSub } from "effect"; |
| 4 | import { expect } from "vitest"; | |
| 4 | import { expect } from "vite-plus/test"; | |
| 5 | 5 | |
| 6 | 6 | import { PreviewManager } from "../Services/Manager.ts"; |
| 7 | 7 | import { PreviewManagerLive } from "./Manager.ts"; |
apps/server/src/preview/Layers/Manager.ts+27-19| ... | ... | @@ -21,7 +21,7 @@ import { |
| 21 | 21 | normalizePreviewUrl, |
| 22 | 22 | PreviewUrlNormalizationError, |
| 23 | 23 | } from "@t3tools/shared/preview"; |
| 24 | import { Effect, Layer, PubSub, Stream, SynchronizedRef } from "effect"; | |
| 24 | import { DateTime, Effect, Layer, PubSub, Stream, SynchronizedRef } from "effect"; | |
| 25 | 25 | |
| 26 | 26 | import { PreviewManager, type PreviewManagerShape } from "../Services/Manager.ts"; |
| 27 | 27 | |
| ... | ... | @@ -66,30 +66,34 @@ const normalizeUrl = (rawUrl: string): Effect.Effect<string, PreviewInvalidUrlEr |
| 66 | 66 | }), |
| 67 | 67 | }); |
| 68 | 68 | |
| 69 | const currentIsoTimestamp = DateTime.now.pipe(Effect.map(DateTime.formatIso)); | |
| 70 | ||
| 69 | 71 | const buildLoadingSnapshot = (input: { |
| 70 | 72 | readonly threadId: string; |
| 71 | 73 | readonly tabId: string; |
| 72 | 74 | readonly url: string; |
| 73 | 75 | readonly title: string; |
| 76 | readonly updatedAt: string; | |
| 74 | 77 | }): PreviewSessionSnapshot => ({ |
| 75 | 78 | threadId: input.threadId, |
| 76 | 79 | tabId: input.tabId, |
| 77 | 80 | navStatus: { _tag: "Loading", url: input.url, title: input.title }, |
| 78 | 81 | canGoBack: false, |
| 79 | 82 | canGoForward: false, |
| 80 | updatedAt: new Date().toISOString(), | |
| 83 | updatedAt: input.updatedAt, | |
| 81 | 84 | }); |
| 82 | 85 | |
| 83 | 86 | const buildIdleSnapshot = (input: { |
| 84 | 87 | readonly threadId: string; |
| 85 | 88 | readonly tabId: string; |
| 89 | readonly updatedAt: string; | |
| 86 | 90 | }): PreviewSessionSnapshot => ({ |
| 87 | 91 | threadId: input.threadId, |
| 88 | 92 | tabId: input.tabId, |
| 89 | 93 | navStatus: { _tag: "Idle" }, |
| 90 | 94 | canGoBack: false, |
| 91 | 95 | canGoForward: false, |
| 92 | updatedAt: new Date().toISOString(), | |
| 96 | updatedAt: input.updatedAt, | |
| 93 | 97 | }); |
| 94 | 98 | |
| 95 | 99 | export const makePreviewManager = Effect.gen(function* () { |
| ... | ... | @@ -152,14 +156,16 @@ export const makePreviewManager = Effect.gen(function* () { |
| 152 | 156 | const open: PreviewManagerShape["open"] = (input) => |
| 153 | 157 | Effect.gen(function* () { |
| 154 | 158 | const tabId = newPreviewTabId(); |
| 159 | const updatedAt = yield* currentIsoTimestamp; | |
| 155 | 160 | const snapshot = input.url |
| 156 | 161 | ? buildLoadingSnapshot({ |
| 157 | 162 | threadId: input.threadId, |
| 158 | 163 | tabId, |
| 159 | 164 | url: yield* normalizeUrl(input.url), |
| 160 | 165 | title: "", |
| 166 | updatedAt, | |
| 161 | 167 | }) |
| 162 | : buildIdleSnapshot({ threadId: input.threadId, tabId }); | |
| 168 | : buildIdleSnapshot({ threadId: input.threadId, tabId, updatedAt }); | |
| 163 | 169 | yield* SynchronizedRef.update(stateRef, (state) => { |
| 164 | 170 | const sessions = new Map(state.sessions); |
| 165 | 171 | sessions.set(compositeKey(input.threadId, tabId), { |
| ... | ... | @@ -183,7 +189,8 @@ export const makePreviewManager = Effect.gen(function* () { |
| 183 | 189 | Effect.gen(function* () { |
| 184 | 190 | const url = yield* normalizeUrl(input.url); |
| 185 | 191 | return yield* mutateExistingSession(input.threadId, input.tabId, (session) => |
| 186 | Effect.sync(() => { | |
| 192 | Effect.gen(function* () { | |
| 193 | const updatedAt = yield* currentIsoTimestamp; | |
| 187 | 194 | const previousTitle = |
| 188 | 195 | session.snapshot.navStatus._tag === "Idle" ? "" : session.snapshot.navStatus.title; |
| 189 | 196 | const resolvedTitle = input.resolvedTitle ?? previousTitle; |
| ... | ... | @@ -193,7 +200,7 @@ export const makePreviewManager = Effect.gen(function* () { |
| 193 | 200 | navStatus: { _tag: "Success", url, title: resolvedTitle }, |
| 194 | 201 | canGoBack: session.snapshot.canGoBack, |
| 195 | 202 | canGoForward: session.snapshot.canGoForward, |
| 196 | updatedAt: new Date().toISOString(), | |
| 203 | updatedAt, | |
| 197 | 204 | }; |
| 198 | 205 | return { |
| 199 | 206 | next: { ...session, snapshot }, |
| ... | ... | @@ -212,14 +219,15 @@ export const makePreviewManager = Effect.gen(function* () { |
| 212 | 219 | |
| 213 | 220 | const reportStatus: PreviewManagerShape["reportStatus"] = (input) => |
| 214 | 221 | mutateExistingSession(input.threadId, input.tabId, (session) => |
| 215 | Effect.sync(() => { | |
| 222 | Effect.gen(function* () { | |
| 223 | const updatedAt = yield* currentIsoTimestamp; | |
| 216 | 224 | const snapshot: PreviewSessionSnapshot = { |
| 217 | 225 | threadId: session.threadId, |
| 218 | 226 | tabId: session.tabId, |
| 219 | 227 | navStatus: input.navStatus, |
| 220 | 228 | canGoBack: input.canGoBack, |
| 221 | 229 | canGoForward: input.canGoForward, |
| 222 | updatedAt: new Date().toISOString(), | |
| 230 | updatedAt, | |
| 223 | 231 | }; |
| 224 | 232 | const emit: PreviewEvent = |
| 225 | 233 | input.navStatus._tag === "LoadFailed" |
| ... | ... | @@ -256,8 +264,9 @@ export const makePreviewManager = Effect.gen(function* () { |
| 256 | 264 | ); |
| 257 | 265 | |
| 258 | 266 | const close: PreviewManagerShape["close"] = (input) => |
| 259 | Effect.flatMap( | |
| 260 | SynchronizedRef.modify(stateRef, (state) => { | |
| 267 | Effect.gen(function* () { | |
| 268 | const createdAt = yield* currentIsoTimestamp; | |
| 269 | const events = yield* SynchronizedRef.modify(stateRef, (state) => { | |
| 261 | 270 | const eventsToEmit: PreviewEvent[] = []; |
| 262 | 271 | const sessions = new Map(state.sessions); |
| 263 | 272 | const targets = input.tabId |
| ... | ... | @@ -271,21 +280,20 @@ export const makePreviewManager = Effect.gen(function* () { |
| 271 | 280 | type: "closed", |
| 272 | 281 | threadId: target.threadId, |
| 273 | 282 | tabId: target.tabId, |
| 274 | createdAt: new Date().toISOString(), | |
| 283 | createdAt, | |
| 275 | 284 | }); |
| 276 | 285 | } |
| 277 | 286 | if (eventsToEmit.length === 0) { |
| 278 | 287 | return [eventsToEmit, state] as const; |
| 279 | 288 | } |
| 280 | 289 | return [eventsToEmit, { sessions }] as const; |
| 281 | }), | |
| 282 | (events) => | |
| 283 | events.length === 0 | |
| 284 | ? Effect.void | |
| 285 | : Effect.forEach(events, (event) => PubSub.publish(eventsPubSub, event), { | |
| 286 | discard: true, | |
| 287 | }), | |
| 288 | ); | |
| 290 | }); | |
| 291 | if (events.length > 0) { | |
| 292 | yield* Effect.forEach(events, (event) => PubSub.publish(eventsPubSub, event), { | |
| 293 | discard: true, | |
| 294 | }); | |
| 295 | } | |
| 296 | }); | |
| 289 | 297 | |
| 290 | 298 | const list: PreviewManagerShape["list"] = (input) => |
| 291 | 299 | SynchronizedRef.get(stateRef).pipe( |
apps/server/src/preview/Layers/PortScanner.test.ts+46-42| ... | ... | @@ -1,12 +1,18 @@ |
| 1 | 1 | import * as net from "node:net"; |
| 2 | 2 | |
| 3 | import { Effect } from "effect"; | |
| 4 | import { afterEach, beforeEach, describe, expect, it } from "vitest"; | |
| 3 | import { it as effectIt } from "@effect/vitest"; | |
| 4 | import { Effect, Layer } from "effect"; | |
| 5 | import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test"; | |
| 5 | 6 | |
| 6 | import { PreviewPortScanner } from "../Services/PortScanner.ts"; | |
| 7 | import { COMMON_DEV_PORTS, PreviewPortScanner } from "../Services/PortScanner.ts"; | |
| 8 | import { ProcessRunner } from "../../processRunner.ts"; | |
| 7 | 9 | import { __testing, PreviewPortScannerLive } from "./PortScanner.ts"; |
| 8 | 10 | |
| 9 | 11 | const { parseLsofOutput, parsePortFromLsofName, serversEqual } = __testing; |
| 12 | const TestProcessRunner = Layer.succeed(ProcessRunner, { | |
| 13 | run: () => Effect.die("ProcessRunner should not be used by Windows TCP probe tests"), | |
| 14 | }); | |
| 15 | const TestPreviewPortScannerLive = PreviewPortScannerLive.pipe(Layer.provide(TestProcessRunner)); | |
| 10 | 16 | |
| 11 | 17 | describe("parsePortFromLsofName", () => { |
| 12 | 18 | it("parses *:port", () => { |
| ... | ... | @@ -121,16 +127,25 @@ describe("serversEqual", () => { |
| 121 | 127 | describe("PreviewPortScanner integration (TCP probe path)", () => { |
| 122 | 128 | let originalPlatform: NodeJS.Platform; |
| 123 | 129 | let server: net.Server; |
| 130 | let port: number; | |
| 124 | 131 | |
| 125 | 132 | beforeEach(async () => { |
| 126 | 133 | originalPlatform = process.platform; |
| 127 | 134 | Object.defineProperty(process, "platform", { value: "win32", configurable: true }); |
| 128 | // 3001 is in COMMON_DEV_PORTS so the TCP-probe pass will check it. | |
| 129 | server = net.createServer(); | |
| 130 | await new Promise<void>((resolve, reject) => { | |
| 131 | server.once("error", reject); | |
| 132 | server.listen(3001, "127.0.0.1", () => resolve()); | |
| 133 | }); | |
| 135 | for (const candidate of COMMON_DEV_PORTS) { | |
| 136 | const candidateServer = net.createServer(); | |
| 137 | const listening = await new Promise<boolean>((resolve) => { | |
| 138 | candidateServer.once("error", () => resolve(false)); | |
| 139 | candidateServer.listen(candidate, "127.0.0.1", () => resolve(true)); | |
| 140 | }); | |
| 141 | if (listening) { | |
| 142 | server = candidateServer; | |
| 143 | port = candidate; | |
| 144 | return; | |
| 145 | } | |
| 146 | candidateServer.close(); | |
| 147 | } | |
| 148 | throw new Error("No common development port was available for the preview scanner test"); | |
| 134 | 149 | }); |
| 135 | 150 | |
| 136 | 151 | afterEach(async () => { |
| ... | ... | @@ -141,40 +156,29 @@ describe("PreviewPortScanner integration (TCP probe path)", () => { |
| 141 | 156 | await new Promise<void>((resolve) => server.close(() => resolve())); |
| 142 | 157 | }); |
| 143 | 158 | |
| 144 | it("scan() returns a server we just opened on a curated dev port", async () => { | |
| 145 | const result = await Effect.runPromise( | |
| 146 | Effect.scoped( | |
| 147 | Effect.gen(function* () { | |
| 148 | const scanner = yield* PreviewPortScanner; | |
| 149 | return yield* scanner.scan(); | |
| 150 | }).pipe(Effect.provide(PreviewPortScannerLive)), | |
| 151 | ), | |
| 152 | ); | |
| 153 | const found = result.find((s) => s.port === 3001); | |
| 154 | expect(found).toBeDefined(); | |
| 155 | expect(found?.host).toBe("localhost"); | |
| 156 | }); | |
| 159 | effectIt.effect("scan() returns a server we just opened on a curated dev port", () => | |
| 160 | Effect.gen(function* () { | |
| 161 | const scanner = yield* PreviewPortScanner; | |
| 162 | const result = yield* scanner.scan(); | |
| 163 | const found = result.find((server) => server.port === port); | |
| 164 | expect(found).toBeDefined(); | |
| 165 | expect(found?.host).toBe("localhost"); | |
| 166 | }).pipe(Effect.provide(TestPreviewPortScannerLive)), | |
| 167 | ); | |
| 157 | 168 | |
| 158 | it("retain() drives an immediate broadcast to subscribers", async () => { | |
| 169 | effectIt.effect("retain() drives an immediate broadcast to subscribers", () => { | |
| 159 | 170 | const received: number[] = []; |
| 160 | await Effect.runPromise( | |
| 161 | Effect.scoped( | |
| 162 | Effect.gen(function* () { | |
| 163 | const scanner = yield* PreviewPortScanner; | |
| 164 | const unsubscribe = yield* scanner.subscribe((servers) => | |
| 165 | Effect.sync(() => { | |
| 166 | for (const server of servers) received.push(server.port); | |
| 167 | }), | |
| 168 | ); | |
| 169 | const release = yield* scanner.retain(); | |
| 170 | // The retain() pollTick is fire-and-forget within Effect, so wait | |
| 171 | // a frame for the broadcast to land. | |
| 172 | yield* Effect.sleep("100 millis"); | |
| 173 | unsubscribe(); | |
| 174 | release(); | |
| 175 | }).pipe(Effect.provide(PreviewPortScannerLive)), | |
| 176 | ), | |
| 177 | ); | |
| 178 | expect(received).toContain(3001); | |
| 171 | return Effect.gen(function* () { | |
| 172 | const scanner = yield* PreviewPortScanner; | |
| 173 | const unsubscribe = yield* scanner.subscribe((servers) => | |
| 174 | Effect.sync(() => { | |
| 175 | for (const server of servers) received.push(server.port); | |
| 176 | }), | |
| 177 | ); | |
| 178 | const release = yield* scanner.retain(); | |
| 179 | unsubscribe(); | |
| 180 | release(); | |
| 181 | expect(received).toContain(port); | |
| 182 | }).pipe(Effect.provide(TestPreviewPortScannerLive)); | |
| 179 | 183 | }); |
| 180 | 184 | }); |
apps/server/src/preview/Layers/PortScanner.ts+19-16| ... | ... | @@ -17,7 +17,7 @@ import type { DiscoveredLocalServer } from "@t3tools/contracts"; |
| 17 | 17 | import { LSOF_LOCAL_HOST_TOKENS } from "@t3tools/shared/preview"; |
| 18 | 18 | import { Cause, Data, Duration, Effect, Layer, Ref, Schedule } from "effect"; |
| 19 | 19 | |
| 20 | import { runProcess } from "../../processRunner.ts"; | |
| 20 | import { ProcessRunner, type ProcessRunnerShape } from "../../processRunner.ts"; | |
| 21 | 21 | import { |
| 22 | 22 | COMMON_DEV_PORTS, |
| 23 | 23 | PreviewPortScanner, |
| ... | ... | @@ -91,20 +91,22 @@ const parsePortFromLsofName = (name: string): number | null => { |
| 91 | 91 | return port; |
| 92 | 92 | }; |
| 93 | 93 | |
| 94 | const probeLsof = (): Effect.Effect<ReadonlyArray<DiscoveredLocalServer> | null> => | |
| 95 | Effect.tryPromise({ | |
| 96 | try: () => | |
| 97 | runProcess("lsof", ["-iTCP", "-sTCP:LISTEN", "-P", "-n", "-F", "pcn"], { | |
| 98 | timeoutMs: LSOF_TIMEOUT_MS, | |
| 99 | allowNonZeroExit: true, | |
| 100 | maxBufferBytes: 1024 * 1024, | |
| 101 | outputMode: "truncate", | |
| 102 | }), | |
| 103 | catch: (cause) => new LsofProbeError({ cause }), | |
| 104 | }).pipe( | |
| 105 | Effect.map((result) => parseLsofOutput(result.stdout)), | |
| 106 | Effect.catch(() => Effect.succeed(null)), | |
| 107 | ); | |
| 94 | const probeLsof = ( | |
| 95 | processRunner: ProcessRunnerShape, | |
| 96 | ): Effect.Effect<ReadonlyArray<DiscoveredLocalServer> | null> => | |
| 97 | processRunner | |
| 98 | .run({ | |
| 99 | command: "lsof", | |
| 100 | args: ["-iTCP", "-sTCP:LISTEN", "-P", "-n", "-F", "pcn"], | |
| 101 | timeout: Duration.millis(LSOF_TIMEOUT_MS), | |
| 102 | maxOutputBytes: 1024 * 1024, | |
| 103 | outputMode: "truncate", | |
| 104 | }) | |
| 105 | .pipe( | |
| 106 | Effect.mapError((cause) => new LsofProbeError({ cause })), | |
| 107 | Effect.map((result) => parseLsofOutput(result.stdout)), | |
| 108 | Effect.orElseSucceed(() => null), | |
| 109 | ); | |
| 108 | 110 | |
| 109 | 111 | const probeTcpPort = (port: number): Promise<boolean> => |
| 110 | 112 | new Promise((resolve) => { |
| ... | ... | @@ -162,6 +164,7 @@ const serversEqual = ( |
| 162 | 164 | }; |
| 163 | 165 | |
| 164 | 166 | export const makePreviewPortScanner = Effect.gen(function* () { |
| 167 | const processRunner = yield* ProcessRunner; | |
| 165 | 168 | const stateRef = yield* Ref.make<ScannerState>({ |
| 166 | 169 | lastSnapshot: [], |
| 167 | 170 | }); |
| ... | ... | @@ -176,7 +179,7 @@ export const makePreviewPortScanner = Effect.gen(function* () { |
| 176 | 179 | if (process.platform === "win32") { |
| 177 | 180 | return yield* probeCommonPorts(); |
| 178 | 181 | } |
| 179 | const lsof = yield* probeLsof(); | |
| 182 | const lsof = yield* probeLsof(processRunner); | |
| 180 | 183 | if (lsof !== null) return lsof; |
| 181 | 184 | return yield* probeCommonPorts(); |
| 182 | 185 | }); |
apps/server/src/preview/Services/PortScanner.ts+1-1| ... | ... | @@ -28,7 +28,7 @@ export interface PreviewPortScannerShape { |
| 28 | 28 | export class PreviewPortScanner extends Context.Service< |
| 29 | 29 | PreviewPortScanner, |
| 30 | 30 | PreviewPortScannerShape |
| 31 | >()("t3/preview/Services/PortScanner") {} | |
| 31 | >()("t3/preview/Services/PortScanner/PreviewPortScanner") {} | |
| 32 | 32 | |
| 33 | 33 | /** Curated list of common dev-server ports for the Windows TCP-probe fallback. */ |
| 34 | 34 | export const COMMON_DEV_PORTS: ReadonlyArray<number> = Object.freeze([ |
apps/server/src/server.test.ts+19-19| ... | ... | @@ -668,25 +668,25 @@ const buildAppUnderTest = (options?: { |
| 668 | 668 | }), |
| 669 | 669 | ), |
| 670 | 670 | Layer.provide( |
| 671 | Layer.mock(PreviewManager)({ | |
| 672 | open: () => Effect.die("PreviewManager not stubbed in this test"), | |
| 673 | navigate: () => Effect.die("PreviewManager not stubbed in this test"), | |
| 674 | reportStatus: () => Effect.void, | |
| 675 | refresh: () => Effect.void, | |
| 676 | close: () => Effect.void, | |
| 677 | list: () => Effect.succeed({ sessions: [] }), | |
| 678 | events: Stream.empty, | |
| 679 | subscribeEvents: Effect.flatMap(PubSub.unbounded<PreviewEvent>(), (pubsub) => | |
| 680 | PubSub.subscribe(pubsub), | |
| 681 | ), | |
| 682 | }), | |
| 683 | ), | |
| 684 | Layer.provide( | |
| 685 | Layer.mock(PreviewPortScanner)({ | |
| 686 | scan: () => Effect.succeed([]), | |
| 687 | subscribe: () => Effect.succeed(() => {}), | |
| 688 | retain: () => Effect.succeed(() => {}), | |
| 689 | }), | |
| 671 | Layer.mergeAll( | |
| 672 | Layer.mock(PreviewManager)({ | |
| 673 | open: () => Effect.die("PreviewManager not stubbed in this test"), | |
| 674 | navigate: () => Effect.die("PreviewManager not stubbed in this test"), | |
| 675 | reportStatus: () => Effect.void, | |
| 676 | refresh: () => Effect.void, | |
| 677 | close: () => Effect.void, | |
| 678 | list: () => Effect.succeed({ sessions: [] }), | |
| 679 | events: Stream.empty, | |
| 680 | subscribeEvents: Effect.flatMap(PubSub.unbounded<PreviewEvent>(), (pubsub) => | |
| 681 | PubSub.subscribe(pubsub), | |
| 682 | ), | |
| 683 | }), | |
| 684 | Layer.mock(PreviewPortScanner)({ | |
| 685 | scan: () => Effect.succeed([]), | |
| 686 | subscribe: () => Effect.succeed(() => {}), | |
| 687 | retain: () => Effect.succeed(() => {}), | |
| 688 | }), | |
| 689 | ), | |
| 690 | 690 | ), |
| 691 | 691 | Layer.provide( |
| 692 | 692 | Layer.mock(OrchestrationEngineService)({ |
apps/server/src/server.ts+6-3| ... | ... | @@ -37,6 +37,7 @@ import { ProviderInstanceRegistryHydrationLive } from "./provider/Layers/Provide |
| 37 | 37 | import { TerminalManagerLive } from "./terminal/Layers/Manager.ts"; |
| 38 | 38 | import { PreviewManagerLive } from "./preview/Layers/Manager.ts"; |
| 39 | 39 | import { PreviewPortScannerLive } from "./preview/Layers/PortScanner.ts"; |
| 40 | import * as ProcessRunner from "./processRunner.ts"; | |
| 40 | 41 | import * as GitManager from "./git/GitManager.ts"; |
| 41 | 42 | import { KeybindingsLive } from "./keybindings.ts"; |
| 42 | 43 | import { ServerRuntimeStartup, ServerRuntimeStartupLive } from "./serverRuntimeStartup.ts"; |
| ... | ... | @@ -235,7 +236,10 @@ const CheckpointingLayerLive = Layer.empty.pipe( |
| 235 | 236 | |
| 236 | 237 | const TerminalLayerLive = TerminalManagerLive.pipe(Layer.provide(PtyAdapterLive)); |
| 237 | 238 | |
| 238 | const PreviewLayerLive = Layer.mergeAll(PreviewManagerLive, PreviewPortScannerLive); | |
| 239 | const PreviewLayerLive = Layer.mergeAll( | |
| 240 | PreviewManagerLive, | |
| 241 | PreviewPortScannerLive.pipe(Layer.provide(ProcessRunner.layer)), | |
| 242 | ); | |
| 239 | 243 | |
| 240 | 244 | const WorkspaceEntriesLayerLive = WorkspaceEntriesLive.pipe( |
| 241 | 245 | Layer.provide(WorkspacePathsLive), |
| ... | ... | @@ -278,8 +282,7 @@ const RuntimeCoreDependenciesLive = ReactorLayerLive.pipe( |
| 278 | 282 | Layer.provideMerge(GitLayerLive), |
| 279 | 283 | Layer.provideMerge(VcsLayerLive), |
| 280 | 284 | Layer.provideMerge(ProviderRuntimeLayerLive), |
| 281 | Layer.provideMerge(TerminalLayerLive), | |
| 282 | Layer.provideMerge(PreviewLayerLive), | |
| 285 | Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)), | |
| 283 | 286 | Layer.provideMerge(PersistenceLayerLive), |
| 284 | 287 | Layer.provideMerge(KeybindingsLive), |
| 285 | 288 | Layer.provideMerge(ProviderRegistryLive), |
apps/server/src/ws.ts+5-4| ... | ... | @@ -1391,14 +1391,15 @@ const makeWsRpcLayer = (currentSession: AuthenticatedSession) => |
| 1391 | 1391 | Effect.gen(function* () { |
| 1392 | 1392 | const release = yield* previewPortScanner.retain(); |
| 1393 | 1393 | const initial = yield* previewPortScanner.scan(); |
| 1394 | const initialScannedAt = DateTime.formatIso(yield* DateTime.now); | |
| 1394 | 1395 | yield* Queue.offer(queue, { |
| 1395 | 1396 | servers: initial, |
| 1396 | scannedAt: new Date().toISOString(), | |
| 1397 | scannedAt: initialScannedAt, | |
| 1397 | 1398 | }); |
| 1398 | 1399 | const unsubscribe = yield* previewPortScanner.subscribe((servers) => |
| 1399 | Queue.offer(queue, { | |
| 1400 | servers, | |
| 1401 | scannedAt: new Date().toISOString(), | |
| 1400 | Effect.gen(function* () { | |
| 1401 | const scannedAt = DateTime.formatIso(yield* DateTime.now); | |
| 1402 | yield* Queue.offer(queue, { servers, scannedAt }); | |
| 1402 | 1403 | }), |
| 1403 | 1404 | ); |
| 1404 | 1405 | return { unsubscribe, release }; |
apps/web/src/components/chat/ChatComposer.tsx+6-8| ... | ... | @@ -17,10 +17,6 @@ import { |
| 17 | 17 | PROVIDER_SEND_TURN_MAX_ATTACHMENTS, |
| 18 | 18 | PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, |
| 19 | 19 | } from "@t3tools/contracts"; |
| 20 | import { | |
| 21 | connectionStatusText, | |
| 22 | type EnvironmentConnectionPresentation, | |
| 23 | } from "@t3tools/client-runtime/connection"; | |
| 24 | 20 | import { serializeComposerFileLink } from "@t3tools/shared/composerTrigger"; |
| 25 | 21 | import { createModelSelection, normalizeModelSlug } from "@t3tools/shared/model"; |
| 26 | 22 | import { |
| ... | ... | @@ -441,7 +437,7 @@ export interface ChatComposerProps { |
| 441 | 437 | isPreparingWorktree: boolean; |
| 442 | 438 | environmentUnavailable: { |
| 443 | 439 | readonly label: string; |
| 444 | readonly connection: EnvironmentConnectionPresentation; | |
| 440 | readonly connectionState: "connecting" | "disconnected" | "error"; | |
| 445 | 441 | } | null; |
| 446 | 442 | |
| 447 | 443 | // Pending approvals / inputs |
| ... | ... | @@ -2354,9 +2350,11 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 2354 | 2350 | : showPlanFollowUpPrompt && activeProposedPlan |
| 2355 | 2351 | ? "Add feedback to refine the plan, or leave this blank to implement it" |
| 2356 | 2352 | : environmentUnavailable |
| 2357 | ? `${environmentUnavailable.label}: ${connectionStatusText( | |
| 2358 | environmentUnavailable.connection, | |
| 2359 | )}` | |
| 2353 | ? `${environmentUnavailable.label} is ${ | |
| 2354 | environmentUnavailable.connectionState === "connecting" | |
| 2355 | ? "connecting" | |
| 2356 | : "disconnected" | |
| 2357 | }` | |
| 2360 | 2358 | : phase === "disconnected" |
| 2361 | 2359 | ? "Ask for follow-up changes or attach images" |
| 2362 | 2360 | : "Ask anything, @tag files/folders, $use skills, or / for commands" |
apps/web/src/components/chat/ChatHeader.tsx-1| ... | ... | @@ -10,7 +10,6 @@ import { memo } from "react"; |
| 10 | 10 | import GitActionsControl from "../GitActionsControl"; |
| 11 | 11 | import { type DraftId } from "~/composerDraftStore"; |
| 12 | 12 | import { DiffIcon, Globe, TerminalSquareIcon } from "lucide-react"; |
| 13 | import { Badge } from "../ui/badge"; | |
| 14 | 13 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 15 | 14 | import ProjectScriptsControl, { type NewProjectScriptInput } from "../ProjectScriptsControl"; |
| 16 | 15 | import { Toggle } from "../ui/toggle"; |
apps/web/src/components/chat/MessagesTimeline.tsx+10-4| ... | ... | @@ -470,9 +470,9 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message" |
| 470 | 470 | )} |
| 471 | 471 | {elementContextState.contexts.length > 0 ? ( |
| 472 | 472 | <div className="mb-2 flex flex-wrap gap-1.5"> |
| 473 | {elementContextState.contexts.map((context, index) => ( | |
| 473 | {elementContextState.contexts.map((context) => ( | |
| 474 | 474 | <UserMessageElementContextChip |
| 475 | key={`${context.header}:${index}`} | |
| 475 | key={`${context.header}:${context.body}`} | |
| 476 | 476 | context={context} |
| 477 | 477 | /> |
| 478 | 478 | ))} |
| ... | ... | @@ -579,10 +579,16 @@ function AssistantTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "mess |
| 579 | 579 | <TooltipTrigger |
| 580 | 580 | render={<p className="text-muted-foreground text-xs tabular-nums" />} |
| 581 | 581 | > |
| 582 | {formatShortTimestamp(row.message.updatedAt, ctx.timestampFormat)} | |
| 582 | {formatShortTimestamp( | |
| 583 | row.message.completedAt ?? row.message.createdAt, | |
| 584 | ctx.timestampFormat, | |
| 585 | )} | |
| 583 | 586 | </TooltipTrigger> |
| 584 | 587 | <TooltipPopup> |
| 585 | {formatChatTimestampTooltip(row.message.updatedAt, ctx.timestampFormat)} | |
| 588 | {formatChatTimestampTooltip( | |
| 589 | row.message.completedAt ?? row.message.createdAt, | |
| 590 | ctx.timestampFormat, | |
| 591 | )} | |
| 586 | 592 | </TooltipPopup> |
| 587 | 593 | </Tooltip> |
| 588 | 594 | )} |
apps/web/src/components/preview/useDiscoveredLocalServers.test.ts+1-1| ... | ... | @@ -1,5 +1,5 @@ |
| 1 | 1 | import type { DiscoveredLocalServer } from "@t3tools/contracts"; |
| 2 | import { describe, expect, it } from "vitest"; | |
| 2 | import { describe, expect, it } from "vite-plus/test"; | |
| 3 | 3 | |
| 4 | 4 | import { mergeServers, type PreviewableServer } from "./useDiscoveredLocalServers"; |
| 5 | 5 |
apps/web/src/environments/runtime/service.threadSubscriptions.test.ts+10| ... | ... | @@ -104,6 +104,16 @@ vi.mock("@t3tools/client-runtime", async (importOriginal) => { |
| 104 | 104 | onEvent: vi.fn(() => () => undefined), |
| 105 | 105 | onMetadata: vi.fn(() => () => undefined), |
| 106 | 106 | }, |
| 107 | preview: { | |
| 108 | open: vi.fn(), | |
| 109 | navigate: vi.fn(), | |
| 110 | refresh: vi.fn(), | |
| 111 | close: vi.fn(), | |
| 112 | list: vi.fn(), | |
| 113 | reportStatus: vi.fn(), | |
| 114 | onEvent: vi.fn(() => () => undefined), | |
| 115 | subscribePorts: vi.fn(() => () => undefined), | |
| 116 | }, | |
| 107 | 117 | projects: { |
| 108 | 118 | searchEntries: vi.fn(), |
| 109 | 119 | writeFile: vi.fn(), |
apps/web/src/lib/elementContext.test.ts+1-1| ... | ... | @@ -1,5 +1,5 @@ |
| 1 | 1 | import type { PickedElementPayload } from "@t3tools/contracts"; |
| 2 | import { describe, expect, it } from "vitest"; | |
| 2 | import { describe, expect, it } from "vite-plus/test"; | |
| 3 | 3 | |
| 4 | 4 | import { |
| 5 | 5 | appendElementContextsToPrompt, |
apps/web/src/lib/elementContext.ts+3-4| ... | ... | @@ -198,12 +198,11 @@ export function appendElementContextsToPrompt( |
| 198 | 198 | } |
| 199 | 199 | |
| 200 | 200 | const ELEMENT_CONTEXT_ID_PREFIX = "el_"; |
| 201 | let nextElementContextSequence = 0; | |
| 201 | 202 | |
| 202 | 203 | export function newElementContextId(): string { |
| 203 | if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") { | |
| 204 | return `${ELEMENT_CONTEXT_ID_PREFIX}${crypto.randomUUID()}`; | |
| 205 | } | |
| 206 | return `${ELEMENT_CONTEXT_ID_PREFIX}${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`; | |
| 204 | nextElementContextSequence += 1; | |
| 205 | return `${ELEMENT_CONTEXT_ID_PREFIX}${nextElementContextSequence.toString(36)}`; | |
| 207 | 206 | } |
| 208 | 207 | |
| 209 | 208 | /** |
apps/web/src/previewStateStore.test.ts+1-1| ... | ... | @@ -1,6 +1,6 @@ |
| 1 | 1 | import { scopeThreadRef } from "@t3tools/client-runtime"; |
| 2 | 2 | import { type EnvironmentId, type PreviewSessionSnapshot, ThreadId } from "@t3tools/contracts"; |
| 3 | import { beforeEach, describe, expect, it } from "vitest"; | |
| 3 | import { beforeEach, describe, expect, it } from "vite-plus/test"; | |
| 4 | 4 | |
| 5 | 5 | import { __testing, selectThreadPreviewState, usePreviewStateStore } from "./previewStateStore"; |
| 6 | 6 |
apps/web/src/rightPanelStore.test.ts+1-1| ... | ... | @@ -1,6 +1,6 @@ |
| 1 | 1 | import { scopeThreadRef } from "@t3tools/client-runtime"; |
| 2 | 2 | import { type EnvironmentId, ThreadId } from "@t3tools/contracts"; |
| 3 | import { beforeEach, describe, expect, it } from "vitest"; | |
| 3 | import { beforeEach, describe, expect, it } from "vite-plus/test"; | |
| 4 | 4 | |
| 5 | 5 | import { |
| 6 | 6 | selectActiveRightPanel, |
packages/contracts/src/preview.test.ts+1-1| ... | ... | @@ -1,5 +1,5 @@ |
| 1 | 1 | import { Schema } from "effect"; |
| 2 | import { describe, expect, it } from "vitest"; | |
| 2 | import { describe, expect, it } from "vite-plus/test"; | |
| 3 | 3 | |
| 4 | 4 | import { |
| 5 | 5 | DiscoveredLocalServer, |
packages/contracts/src/rpc.ts+8-5| ... | ... | @@ -480,38 +480,40 @@ export const WsTerminalCloseRpc = Rpc.make(WS_METHODS.terminalClose, { |
| 480 | 480 | export const WsPreviewOpenRpc = Rpc.make(WS_METHODS.previewOpen, { |
| 481 | 481 | payload: PreviewOpenInput, |
| 482 | 482 | success: PreviewSessionSnapshot, |
| 483 | error: PreviewError, | |
| 483 | error: Schema.Union([PreviewError, EnvironmentAuthorizationError]), | |
| 484 | 484 | }); |
| 485 | 485 | |
| 486 | 486 | export const WsPreviewNavigateRpc = Rpc.make(WS_METHODS.previewNavigate, { |
| 487 | 487 | payload: PreviewNavigateInput, |
| 488 | 488 | success: PreviewSessionSnapshot, |
| 489 | error: PreviewError, | |
| 489 | error: Schema.Union([PreviewError, EnvironmentAuthorizationError]), | |
| 490 | 490 | }); |
| 491 | 491 | |
| 492 | 492 | export const WsPreviewRefreshRpc = Rpc.make(WS_METHODS.previewRefresh, { |
| 493 | 493 | payload: PreviewRefreshInput, |
| 494 | error: PreviewError, | |
| 494 | error: Schema.Union([PreviewError, EnvironmentAuthorizationError]), | |
| 495 | 495 | }); |
| 496 | 496 | |
| 497 | 497 | export const WsPreviewCloseRpc = Rpc.make(WS_METHODS.previewClose, { |
| 498 | 498 | payload: PreviewCloseInput, |
| 499 | error: PreviewError, | |
| 499 | error: Schema.Union([PreviewError, EnvironmentAuthorizationError]), | |
| 500 | 500 | }); |
| 501 | 501 | |
| 502 | 502 | export const WsPreviewListRpc = Rpc.make(WS_METHODS.previewList, { |
| 503 | 503 | payload: PreviewListInput, |
| 504 | 504 | success: PreviewListResult, |
| 505 | error: EnvironmentAuthorizationError, | |
| 505 | 506 | }); |
| 506 | 507 | |
| 507 | 508 | export const WsPreviewReportStatusRpc = Rpc.make(WS_METHODS.previewReportStatus, { |
| 508 | 509 | payload: PreviewReportStatusInput, |
| 509 | error: PreviewError, | |
| 510 | error: Schema.Union([PreviewError, EnvironmentAuthorizationError]), | |
| 510 | 511 | }); |
| 511 | 512 | |
| 512 | 513 | export const WsSubscribePreviewEventsRpc = Rpc.make(WS_METHODS.subscribePreviewEvents, { |
| 513 | 514 | payload: Schema.Struct({}), |
| 514 | 515 | success: PreviewEvent, |
| 516 | error: EnvironmentAuthorizationError, | |
| 515 | 517 | stream: true, |
| 516 | 518 | }); |
| 517 | 519 | |
| ... | ... | @@ -520,6 +522,7 @@ export const WsSubscribeDiscoveredLocalServersRpc = Rpc.make( |
| 520 | 522 | { |
| 521 | 523 | payload: Schema.Struct({}), |
| 522 | 524 | success: DiscoveredLocalServerList, |
| 525 | error: EnvironmentAuthorizationError, | |
| 523 | 526 | stream: true, |
| 524 | 527 | }, |
| 525 | 528 | ); |
packages/shared/src/preview.test.ts+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | import { describe, expect, it } from "vitest"; | |
| 1 | import { describe, expect, it } from "vite-plus/test"; | |
| 2 | 2 | |
| 3 | 3 | import { |
| 4 | 4 | isLoopbackHost, |
packages/shared/src/preview.ts+3-4| ... | ... | @@ -5,16 +5,15 @@ |
| 5 | 5 | */ |
| 6 | 6 | |
| 7 | 7 | const TAB_ID_PREFIX = "tab_"; |
| 8 | let nextPreviewTabSequence = 0; | |
| 8 | 9 | |
| 9 | 10 | /** |
| 10 | 11 | * Generate a fresh preview tab id. Lives in shared (not contracts) because |
| 11 | 12 | * the contracts package is schema-only — runtime helpers belong here. |
| 12 | 13 | */ |
| 13 | 14 | export function newPreviewTabId(): string { |
| 14 | if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") { | |
| 15 | return `${TAB_ID_PREFIX}${crypto.randomUUID()}`; | |
| 16 | } | |
| 17 | return `${TAB_ID_PREFIX}${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`; | |
| 15 | nextPreviewTabSequence += 1; | |
| 16 | return `${TAB_ID_PREFIX}${nextPreviewTabSequence.toString(36)}`; | |
| 18 | 17 | } |
| 19 | 18 | |
| 20 | 19 | const LOOPBACK_HOSTS: ReadonlySet<string> = new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1"]); |
pnpm-lock.yaml+161| ... | ... | @@ -128,6 +128,9 @@ importers: |
| 128 | 128 | electron-updater: |
| 129 | 129 | specifier: ^6.6.2 |
| 130 | 130 | version: 6.8.3 |
| 131 | react-grab: | |
| 132 | specifier: ^0.1.32 | |
| 133 | version: 0.1.44(react@19.2.6) | |
| 131 | 134 | devDependencies: |
| 132 | 135 | '@effect/vitest': |
| 133 | 136 | specifier: 4.0.0-beta.78 |
| ... | ... | @@ -580,6 +583,9 @@ importers: |
| 580 | 583 | playwright: |
| 581 | 584 | specifier: ^1.58.2 |
| 582 | 585 | version: 1.60.0 |
| 586 | react-grab: | |
| 587 | specifier: ^0.1.32 | |
| 588 | version: 0.1.44(react@19.2.6) | |
| 583 | 589 | tailwindcss: |
| 584 | 590 | specifier: ^4.0.0 |
| 585 | 591 | version: 4.3.0 |
| ... | ... | @@ -2395,6 +2401,9 @@ packages: |
| 2395 | 2401 | peerDependencies: |
| 2396 | 2402 | hono: ^4 |
| 2397 | 2403 | |
| 2404 | '@iarna/toml@2.2.5': | |
| 2405 | resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} | |
| 2406 | ||
| 2398 | 2407 | '@img/colour@1.1.0': |
| 2399 | 2408 | resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} |
| 2400 | 2409 | engines: {node: '>=18'} |
| ... | ... | @@ -3432,6 +3441,10 @@ packages: |
| 3432 | 3441 | '@types/react': |
| 3433 | 3442 | optional: true |
| 3434 | 3443 | |
| 3444 | '@react-grab/cli@0.1.44': | |
| 3445 | resolution: {integrity: sha512-gMDYY2rw6OWajCcDlXSIgs2LC432YJXSb3Lm5yM187uhRgBYddoEVULi36h+IolX3r7jSb3ew7vn9FfI8NSo0A==} | |
| 3446 | hasBin: true | |
| 3447 | ||
| 3435 | 3448 | '@react-native-masked-view/masked-view@0.3.2': |
| 3436 | 3449 | resolution: {integrity: sha512-XwuQoW7/GEgWRMovOQtX3A4PrXhyaZm0lVUiY8qJDvdngjLms9Cpdck6SmGAUNqQwcj2EadHC1HwL0bEyoa/SQ==} |
| 3437 | 3450 | peerDependencies: |
| ... | ... | @@ -4787,6 +4800,10 @@ packages: |
| 4787 | 4800 | resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} |
| 4788 | 4801 | engines: {node: '>= 14'} |
| 4789 | 4802 | |
| 4803 | agent-install@0.0.5: | |
| 4804 | resolution: {integrity: sha512-nHlms9BkP8ZiY79HrwCGiA2DcNaXrAaJrCM/BEqQ7MEsSKyCk+2A76xPGylIfASZSZE0SaU3T0bNSg4rBPIJAQ==} | |
| 4805 | hasBin: true | |
| 4806 | ||
| 4790 | 4807 | ajv-draft-04@1.0.0: |
| 4791 | 4808 | resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} |
| 4792 | 4809 | peerDependencies: |
| ... | ... | @@ -5054,6 +5071,11 @@ packages: |
| 5054 | 5071 | resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} |
| 5055 | 5072 | engines: {node: '>=0.6'} |
| 5056 | 5073 | |
| 5074 | bippy@0.5.41: | |
| 5075 | resolution: {integrity: sha512-jCP2pXXLhXqPrAN+iSEFZmLI4uUM4fjSqajh0K+TmM062VehfDT3ZJNkrTGyN701Z5XMejs9qAudSqkMGhSMKg==} | |
| 5076 | peerDependencies: | |
| 5077 | react: '>=17.0.1' | |
| 5078 | ||
| 5057 | 5079 | body-parser@2.2.2: |
| 5058 | 5080 | resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} |
| 5059 | 5081 | engines: {node: '>=18'} |
| ... | ... | @@ -5244,10 +5266,18 @@ packages: |
| 5244 | 5266 | resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} |
| 5245 | 5267 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} |
| 5246 | 5268 | |
| 5269 | cli-cursor@5.0.0: | |
| 5270 | resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} | |
| 5271 | engines: {node: '>=18'} | |
| 5272 | ||
| 5247 | 5273 | cli-spinners@2.9.2: |
| 5248 | 5274 | resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} |
| 5249 | 5275 | engines: {node: '>=6'} |
| 5250 | 5276 | |
| 5277 | cli-spinners@3.4.0: | |
| 5278 | resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} | |
| 5279 | engines: {node: '>=18.20'} | |
| 5280 | ||
| 5251 | 5281 | cli-truncate@2.1.0: |
| 5252 | 5282 | resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} |
| 5253 | 5283 | engines: {node: '>=8'} |
| ... | ... | @@ -5321,6 +5351,10 @@ packages: |
| 5321 | 5351 | resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} |
| 5322 | 5352 | engines: {node: '>=18'} |
| 5323 | 5353 | |
| 5354 | commander@14.0.3: | |
| 5355 | resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} | |
| 5356 | engines: {node: '>=20'} | |
| 5357 | ||
| 5324 | 5358 | commander@2.20.3: |
| 5325 | 5359 | resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} |
| 5326 | 5360 | |
| ... | ... | @@ -6604,6 +6638,10 @@ packages: |
| 6604 | 6638 | resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} |
| 6605 | 6639 | engines: {node: '>= 4'} |
| 6606 | 6640 | |
| 6641 | ignore@7.0.5: | |
| 6642 | resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} | |
| 6643 | engines: {node: '>= 4'} | |
| 6644 | ||
| 6607 | 6645 | image-size@1.2.1: |
| 6608 | 6646 | resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} |
| 6609 | 6647 | engines: {node: '>=16.x'} |
| ... | ... | @@ -6725,6 +6763,10 @@ packages: |
| 6725 | 6763 | engines: {node: '>=14.16'} |
| 6726 | 6764 | hasBin: true |
| 6727 | 6765 | |
| 6766 | is-interactive@2.0.0: | |
| 6767 | resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} | |
| 6768 | engines: {node: '>=12'} | |
| 6769 | ||
| 6728 | 6770 | is-node-process@1.2.0: |
| 6729 | 6771 | resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} |
| 6730 | 6772 | |
| ... | ... | @@ -6742,6 +6784,10 @@ packages: |
| 6742 | 6784 | is-property@1.0.2: |
| 6743 | 6785 | resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} |
| 6744 | 6786 | |
| 6787 | is-unicode-supported@2.1.0: | |
| 6788 | resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} | |
| 6789 | engines: {node: '>=18'} | |
| 6790 | ||
| 6745 | 6791 | is-wsl@2.2.0: |
| 6746 | 6792 | resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} |
| 6747 | 6793 | engines: {node: '>=8'} |
| ... | ... | @@ -7154,6 +7200,10 @@ packages: |
| 7154 | 7200 | resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==} |
| 7155 | 7201 | engines: {node: '>=4'} |
| 7156 | 7202 | |
| 7203 | log-symbols@7.0.1: | |
| 7204 | resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} | |
| 7205 | engines: {node: '>=18'} | |
| 7206 | ||
| 7157 | 7207 | long@5.3.2: |
| 7158 | 7208 | resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} |
| 7159 | 7209 | |
| ... | ... | @@ -7484,6 +7534,10 @@ packages: |
| 7484 | 7534 | resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} |
| 7485 | 7535 | engines: {node: '>=6'} |
| 7486 | 7536 | |
| 7537 | mimic-function@5.0.1: | |
| 7538 | resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} | |
| 7539 | engines: {node: '>=18'} | |
| 7540 | ||
| 7487 | 7541 | mimic-response@1.0.1: |
| 7488 | 7542 | resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} |
| 7489 | 7543 | engines: {node: '>=4'} |
| ... | ... | @@ -7729,6 +7783,10 @@ packages: |
| 7729 | 7783 | resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} |
| 7730 | 7784 | engines: {node: '>=6'} |
| 7731 | 7785 | |
| 7786 | onetime@7.0.0: | |
| 7787 | resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} | |
| 7788 | engines: {node: '>=18'} | |
| 7789 | ||
| 7732 | 7790 | oniguruma-parser@0.12.2: |
| 7733 | 7791 | resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} |
| 7734 | 7792 | |
| ... | ... | @@ -7743,6 +7801,10 @@ packages: |
| 7743 | 7801 | resolution: {integrity: sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==} |
| 7744 | 7802 | engines: {node: '>=6'} |
| 7745 | 7803 | |
| 7804 | ora@9.4.0: | |
| 7805 | resolution: {integrity: sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ==} | |
| 7806 | engines: {node: '>=20'} | |
| 7807 | ||
| 7746 | 7808 | os-paths@7.4.0: |
| 7747 | 7809 | resolution: {integrity: sha512-Ux1J4NUqC6tZayBqLN1kUlDAEvLiQlli/53sSddU4IN+h+3xxnv2HmRSMpVSvr1hvJzotfMs3ERvETGK+f4OwA==} |
| 7748 | 7810 | engines: {node: '>= 4.0'} |
| ... | ... | @@ -8136,6 +8198,15 @@ packages: |
| 8136 | 8198 | peerDependencies: |
| 8137 | 8199 | react: '>=17.0.0' |
| 8138 | 8200 | |
| 8201 | react-grab@0.1.44: | |
| 8202 | resolution: {integrity: sha512-bDEwBdI90ljq2lhUtPqmWis/HwYB/CvfT0m5i+P9F83Pt0Ot8o9XL8v00s9jcWzdQUlsFDzmq2FO2CHUe8JY8A==} | |
| 8203 | hasBin: true | |
| 8204 | peerDependencies: | |
| 8205 | react: '>=17.0.0' | |
| 8206 | peerDependenciesMeta: | |
| 8207 | react: | |
| 8208 | optional: true | |
| 8209 | ||
| 8139 | 8210 | react-is@16.13.1: |
| 8140 | 8211 | resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} |
| 8141 | 8212 | |
| ... | ... | @@ -8454,6 +8525,10 @@ packages: |
| 8454 | 8525 | resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} |
| 8455 | 8526 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} |
| 8456 | 8527 | |
| 8528 | restore-cursor@5.1.0: | |
| 8529 | resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} | |
| 8530 | engines: {node: '>=18'} | |
| 8531 | ||
| 8457 | 8532 | retext-latin@4.0.0: |
| 8458 | 8533 | resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} |
| 8459 | 8534 | |
| ... | ... | @@ -8757,6 +8832,10 @@ packages: |
| 8757 | 8832 | std-env@4.1.0: |
| 8758 | 8833 | resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} |
| 8759 | 8834 | |
| 8835 | stdin-discarder@0.3.2: | |
| 8836 | resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} | |
| 8837 | engines: {node: '>=18'} | |
| 8838 | ||
| 8760 | 8839 | stream-buffers@2.2.0: |
| 8761 | 8840 | resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} |
| 8762 | 8841 | engines: {node: '>= 0.10.0'} |
| ... | ... | @@ -9631,6 +9710,10 @@ packages: |
| 9631 | 9710 | resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} |
| 9632 | 9711 | engines: {node: '>=18'} |
| 9633 | 9712 | |
| 9713 | yoctocolors@2.1.2: | |
| 9714 | resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} | |
| 9715 | engines: {node: '>=18'} | |
| 9716 | ||
| 9634 | 9717 | yoga-layout@3.2.1: |
| 9635 | 9718 | resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} |
| 9636 | 9719 | |
| ... | ... | @@ -11621,6 +11704,8 @@ snapshots: |
| 11621 | 11704 | dependencies: |
| 11622 | 11705 | hono: 4.12.25 |
| 11623 | 11706 | |
| 11707 | '@iarna/toml@2.2.5': {} | |
| 11708 | ||
| 11624 | 11709 | '@img/colour@1.1.0': |
| 11625 | 11710 | optional: true |
| 11626 | 11711 | |
| ... | ... | @@ -12577,6 +12662,17 @@ snapshots: |
| 12577 | 12662 | optionalDependencies: |
| 12578 | 12663 | '@types/react': 19.2.16 |
| 12579 | 12664 | |
| 12665 | '@react-grab/cli@0.1.44': | |
| 12666 | dependencies: | |
| 12667 | agent-install: 0.0.5 | |
| 12668 | commander: 14.0.3 | |
| 12669 | ignore: 7.0.5 | |
| 12670 | ora: 9.4.0 | |
| 12671 | package-manager-detector: 1.6.0 | |
| 12672 | picocolors: 1.1.1 | |
| 12673 | prompts: 2.4.2 | |
| 12674 | tinyexec: 1.2.4 | |
| 12675 | ||
| 12580 | 12676 | '@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)': |
| 12581 | 12677 | dependencies: |
| 12582 | 12678 | react: 19.2.3 |
| ... | ... | @@ -13796,6 +13892,15 @@ snapshots: |
| 13796 | 13892 | |
| 13797 | 13893 | agent-base@7.1.4: {} |
| 13798 | 13894 | |
| 13895 | agent-install@0.0.5: | |
| 13896 | dependencies: | |
| 13897 | '@iarna/toml': 2.2.5 | |
| 13898 | commander: 14.0.3 | |
| 13899 | jsonc-parser: 3.3.1 | |
| 13900 | picocolors: 1.1.1 | |
| 13901 | prompts: 2.4.2 | |
| 13902 | yaml: 2.9.0 | |
| 13903 | ||
| 13799 | 13904 | ajv-draft-04@1.0.0(ajv@8.20.0): |
| 13800 | 13905 | optionalDependencies: |
| 13801 | 13906 | ajv: 8.20.0 |
| ... | ... | @@ -14229,6 +14334,10 @@ snapshots: |
| 14229 | 14334 | |
| 14230 | 14335 | big-integer@1.6.52: {} |
| 14231 | 14336 | |
| 14337 | bippy@0.5.41(react@19.2.6): | |
| 14338 | dependencies: | |
| 14339 | react: 19.2.6 | |
| 14340 | ||
| 14232 | 14341 | body-parser@2.2.2: |
| 14233 | 14342 | dependencies: |
| 14234 | 14343 | bytes: 3.1.2 |
| ... | ... | @@ -14451,8 +14560,14 @@ snapshots: |
| 14451 | 14560 | dependencies: |
| 14452 | 14561 | restore-cursor: 4.0.0 |
| 14453 | 14562 | |
| 14563 | cli-cursor@5.0.0: | |
| 14564 | dependencies: | |
| 14565 | restore-cursor: 5.1.0 | |
| 14566 | ||
| 14454 | 14567 | cli-spinners@2.9.2: {} |
| 14455 | 14568 | |
| 14569 | cli-spinners@3.4.0: {} | |
| 14570 | ||
| 14456 | 14571 | cli-truncate@2.1.0: |
| 14457 | 14572 | dependencies: |
| 14458 | 14573 | slice-ansi: 3.0.0 |
| ... | ... | @@ -14520,6 +14635,8 @@ snapshots: |
| 14520 | 14635 | |
| 14521 | 14636 | commander@12.1.0: {} |
| 14522 | 14637 | |
| 14638 | commander@14.0.3: {} | |
| 14639 | ||
| 14523 | 14640 | commander@2.20.3: {} |
| 14524 | 14641 | |
| 14525 | 14642 | commander@5.1.0: {} |
| ... | ... | @@ -15980,6 +16097,8 @@ snapshots: |
| 15980 | 16097 | |
| 15981 | 16098 | ignore@5.3.2: {} |
| 15982 | 16099 | |
| 16100 | ignore@7.0.5: {} | |
| 16101 | ||
| 15983 | 16102 | image-size@1.2.1: |
| 15984 | 16103 | dependencies: |
| 15985 | 16104 | queue: 6.0.2 |
| ... | ... | @@ -16099,6 +16218,8 @@ snapshots: |
| 16099 | 16218 | dependencies: |
| 16100 | 16219 | is-docker: 3.0.0 |
| 16101 | 16220 | |
| 16221 | is-interactive@2.0.0: {} | |
| 16222 | ||
| 16102 | 16223 | is-node-process@1.2.0: {} |
| 16103 | 16224 | |
| 16104 | 16225 | is-number@7.0.0: {} |
| ... | ... | @@ -16109,6 +16230,8 @@ snapshots: |
| 16109 | 16230 | |
| 16110 | 16231 | is-property@1.0.2: {} |
| 16111 | 16232 | |
| 16233 | is-unicode-supported@2.1.0: {} | |
| 16234 | ||
| 16112 | 16235 | is-wsl@2.2.0: |
| 16113 | 16236 | dependencies: |
| 16114 | 16237 | is-docker: 2.2.1 |
| ... | ... | @@ -16442,6 +16565,11 @@ snapshots: |
| 16442 | 16565 | dependencies: |
| 16443 | 16566 | chalk: 2.4.2 |
| 16444 | 16567 | |
| 16568 | log-symbols@7.0.1: | |
| 16569 | dependencies: | |
| 16570 | is-unicode-supported: 2.1.0 | |
| 16571 | yoctocolors: 2.1.2 | |
| 16572 | ||
| 16445 | 16573 | long@5.3.2: {} |
| 16446 | 16574 | |
| 16447 | 16575 | longest-streak@3.1.0: {} |
| ... | ... | @@ -17071,6 +17199,8 @@ snapshots: |
| 17071 | 17199 | |
| 17072 | 17200 | mimic-fn@2.1.0: {} |
| 17073 | 17201 | |
| 17202 | mimic-function@5.0.1: {} | |
| 17203 | ||
| 17074 | 17204 | mimic-response@1.0.1: {} |
| 17075 | 17205 | |
| 17076 | 17206 | mimic-response@3.1.0: {} |
| ... | ... | @@ -17308,6 +17438,10 @@ snapshots: |
| 17308 | 17438 | dependencies: |
| 17309 | 17439 | mimic-fn: 2.1.0 |
| 17310 | 17440 | |
| 17441 | onetime@7.0.0: | |
| 17442 | dependencies: | |
| 17443 | mimic-function: 5.0.1 | |
| 17444 | ||
| 17311 | 17445 | oniguruma-parser@0.12.2: {} |
| 17312 | 17446 | |
| 17313 | 17447 | oniguruma-to-es@4.3.6: |
| ... | ... | @@ -17330,6 +17464,17 @@ snapshots: |
| 17330 | 17464 | strip-ansi: 5.2.0 |
| 17331 | 17465 | wcwidth: 1.0.1 |
| 17332 | 17466 | |
| 17467 | ora@9.4.0: | |
| 17468 | dependencies: | |
| 17469 | chalk: 5.6.2 | |
| 17470 | cli-cursor: 5.0.0 | |
| 17471 | cli-spinners: 3.4.0 | |
| 17472 | is-interactive: 2.0.0 | |
| 17473 | is-unicode-supported: 2.1.0 | |
| 17474 | log-symbols: 7.0.1 | |
| 17475 | stdin-discarder: 0.3.2 | |
| 17476 | string-width: 8.2.1 | |
| 17477 | ||
| 17333 | 17478 | os-paths@7.4.0: |
| 17334 | 17479 | optionalDependencies: |
| 17335 | 17480 | fsevents: 2.3.3 |
| ... | ... | @@ -17721,6 +17866,13 @@ snapshots: |
| 17721 | 17866 | dependencies: |
| 17722 | 17867 | react: 19.2.3 |
| 17723 | 17868 | |
| 17869 | react-grab@0.1.44(react@19.2.6): | |
| 17870 | dependencies: | |
| 17871 | '@react-grab/cli': 0.1.44 | |
| 17872 | bippy: 0.5.41(react@19.2.6) | |
| 17873 | optionalDependencies: | |
| 17874 | react: 19.2.6 | |
| 17875 | ||
| 17724 | 17876 | react-is@16.13.1: {} |
| 17725 | 17877 | |
| 17726 | 17878 | react-is@17.0.2: {} |
| ... | ... | @@ -18122,6 +18274,11 @@ snapshots: |
| 18122 | 18274 | onetime: 5.1.2 |
| 18123 | 18275 | signal-exit: 3.0.7 |
| 18124 | 18276 | |
| 18277 | restore-cursor@5.1.0: | |
| 18278 | dependencies: | |
| 18279 | onetime: 7.0.0 | |
| 18280 | signal-exit: 4.1.0 | |
| 18281 | ||
| 18125 | 18282 | retext-latin@4.0.0: |
| 18126 | 18283 | dependencies: |
| 18127 | 18284 | '@types/nlcst': 2.0.3 |
| ... | ... | @@ -18563,6 +18720,8 @@ snapshots: |
| 18563 | 18720 | |
| 18564 | 18721 | std-env@4.1.0: {} |
| 18565 | 18722 | |
| 18723 | stdin-discarder@0.3.2: {} | |
| 18724 | ||
| 18566 | 18725 | stream-buffers@2.2.0: {} |
| 18567 | 18726 | |
| 18568 | 18727 | strict-event-emitter@0.5.1: {} |
| ... | ... | @@ -19377,6 +19536,8 @@ snapshots: |
| 19377 | 19536 | |
| 19378 | 19537 | yoctocolors-cjs@2.1.3: {} |
| 19379 | 19538 | |
| 19539 | yoctocolors@2.1.2: {} | |
| 19540 | ||
| 19380 | 19541 | yoga-layout@3.2.1: {} |
| 19381 | 19542 | |
| 19382 | 19543 | zod-to-json-schema@3.25.2(zod@4.4.3): |