Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
fc019f3
Harden preview automation timeouts and degraded snapshots
Quicksaver Jul 25, 2026
7ffae42
Acknowledge newly created preview tabs before readiness waits
Quicksaver Jul 25, 2026
af71805
Add background preview screenshot capture
Quicksaver Jul 25, 2026
49b8840
Harden preview automation across background tabs and startup flows
Quicksaver Jul 25, 2026
76c8bd6
Bound preview automation staging deadlines
Quicksaver Jul 25, 2026
c0d9f67
Fix preview automation deadline races
Quicksaver Jul 25, 2026
e9472ba
Harden preview capture and deadline fallbacks
Quicksaver Jul 25, 2026
e6cfcc6
Keep preview automation within request deadlines
Quicksaver Jul 25, 2026
a6134bf
Document preview automation branch behavior
Quicksaver Jul 25, 2026
01debc6
Avoid focusing background guests during preview snapshots
Quicksaver Jul 26, 2026
27d0818
Harden preview automation deadline handling
Quicksaver Jul 26, 2026
bfe907c
Fix preview background presentation races
Quicksaver Jul 26, 2026
9a1e03b
Merge upstream/main into preview automation reliability
Quicksaver Jul 27, 2026
5ead518
Clarify preview worktree port selection
Quicksaver Jul 27, 2026
b2a4e0b
Refresh preview branch documentation
Quicksaver Jul 27, 2026
d6ec942
Clarify preview branch dev guidance
Quicksaver Jul 27, 2026
80f4cc3
Fix preview snapshot capture and browser surface rendering
Quicksaver Jul 27, 2026
df3fb09
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Quicksaver Jul 27, 2026
a87a922
Clarify preview presentation timeout diagnostics
Quicksaver Jul 27, 2026
f5d3cae
Refresh preview reliability branch documentation
Quicksaver Jul 27, 2026
538aeba
Stabilize preview presentation diagnostics
Quicksaver Jul 27, 2026
7959b69
Merge commit '96398e3775fc6c74525e4a211e75e580c6cf4225' into fix/prev…
Quicksaver Jul 27, 2026
dd1ce3e
Merge commit '32843c2551bd5832d436ef46bff5ab1198cdc39c' into fix/prev…
Quicksaver Jul 27, 2026
41db260
Document upstream preview capture ownership boundary
Quicksaver Jul 27, 2026
59795c1
Isolate desktop user data in dev runner
Quicksaver Jul 28, 2026
5078067
Merge commit '887dd6e455bb969c1a0c9659a6bdf2baceac030d' into fix/prev…
Jul 28, 2026
0ccb200
Update preview presentation tests for runtime tab ids
Jul 28, 2026
5777b0f
Require runtime ids for preview presentation
Jul 28, 2026
5bc0a7a
Refresh preview reliability branch details
Jul 28, 2026
facd17f
Abort preview visibility waits on runtime changes
Jul 28, 2026
168990f
Harden preview runtime replacement handling
Jul 28, 2026
75f2414
Harden preview deadline budgeting
Jul 28, 2026
b40f6f9
Preserve sessions when screenshot capture is skipped
Jul 28, 2026
17720b2
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 28, 2026
fcf2e2c
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 28, 2026
d608c13
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 29, 2026
d38eaae
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 29, 2026
b10478d
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 29, 2026
864d9a2
Serialize pairing and scope browser surface renders
Jul 29, 2026
35aa798
Serialize control session timeout recovery
Jul 29, 2026
7a576c2
Bind timeout cleanup to control sessions
Jul 29, 2026
fc2cfc2
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 29, 2026
204de04
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 29, 2026
517abc6
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 29, 2026
c0cb55e
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 29, 2026
1ff5aed
Keep background capture selection-aware
Jul 29, 2026
b58af85
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 30, 2026
8ef4721
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 30, 2026
54c9799
fix(worktree): restore upstream legend list patch
Jul 30, 2026
9a514a6
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 30, 2026
17c66cf
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 30, 2026
ed0e5dc
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 31, 2026
1a56d38
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 31, 2026
073c0b7
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 31, 2026
efbb037
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Jul 31, 2026
c2f5df8
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 2, 2026
926cf4b
Clamp preview input timeouts to host budget
Aug 3, 2026
56aacb3
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 3, 2026
b3f7847
Clamp preview readiness waits to operation deadlines
Aug 3, 2026
12776c7
Bound preview overlay readiness status calls
Aug 3, 2026
1ef517a
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 6, 2026
be8b7eb
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 6, 2026
8f16143
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 7, 2026
4f2467a
Merge remote-tracking branch 'refs/remotes/upstream/main' into fix/pr…
Aug 7, 2026
67346e6
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 7, 2026
ac45511
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 8, 2026
0c82f33
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 8, 2026
062a1f7
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 8, 2026
fb5ad25
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 9, 2026
020d851
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 9, 2026
71f9877
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 9, 2026
1210d15
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 10, 2026
35ee586
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 10, 2026
356b35d
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 10, 2026
5cfd1e5
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 10, 2026
20038cc
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 10, 2026
d07f571
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Aug 10, 2026
bf8cece
merge upstream/main into preview automation reliability
Quicksaver Aug 16, 2026
b8fef9b
Merge remote-tracking branch 'upstream/main' into fix/preview-automat…
Quicksaver Aug 17, 2026
8cd9ac3
fix(web): keep preview open decisions consistent
Quicksaver Aug 17, 2026
c649f2d
fix(web): pin preview state after settings hydration
Quicksaver Aug 17, 2026
fb2d11f
fix(web): bound preview mutations and viewport waits
Quicksaver Aug 17, 2026
d09aa77
fix(preview): bound post-readiness mutations
Quicksaver Aug 17, 2026
c87599e
fix(preview): bound recording finalization
Quicksaver Aug 18, 2026
6716ed7
fix(preview): preserve recording retries
Quicksaver Aug 18, 2026
b584620
fix(preview): make deadline retries idempotent
Quicksaver Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions BRANCH_DETAILS.md

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions apps/desktop/src/app/DesktopAppIdentity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,31 @@ const withIdentity = <A, E, R>(
};

describe("DesktopAppIdentity", () => {
it.effect("uses an explicit desktop user-data directory without probing legacy state", () =>
withIdentity(
Effect.gen(function* () {
const identity = yield* DesktopAppIdentity.DesktopAppIdentity;
const userDataPath = yield* identity.resolveUserDataPath;

assert.equal(userDataPath, "/tmp/t3-desktop-profile");
}),
{
environment: {
env: {
T3CODE_DESKTOP_USER_DATA_DIR: " /tmp/t3-desktop-profile ",
},
},
legacyPathProbeError: PlatformError.systemError({
_tag: "PermissionDenied",
module: "FileSystem",
method: "exists",
description: "legacy path must not be probed",
pathOrDescriptor: "/Users/alice/Library/Application Support/T3 Code (Alpha)",
}),
},
),
);

it.effect("keeps using the legacy userData path when it already exists", () =>
withIdentity(
Effect.gen(function* () {
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/app/DesktopAppIdentity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ const normalizeCommitHash = (value: string): Option.Option<string> => {
export const resolveUserDataPath = Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const fileSystem = yield* FileSystem.FileSystem;
if (Option.isSome(environment.desktopUserDataDir)) {
return environment.desktopUserDataDir.value;
}

const legacyPath = environment.path.join(
environment.appDataDirectory,
environment.legacyUserDataDirName,
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/app/DesktopConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const DesktopConfig = Config.all({
xdgDataHome: trimmedString("XDG_DATA_HOME"),
t3Home: trimmedString("T3CODE_HOME"),
devServerUrl: Config.url("VITE_DEV_SERVER_URL").pipe(Config.option),
desktopUserDataDir: trimmedString("T3CODE_DESKTOP_USER_DATA_DIR"),
appUserModelIdOverride: trimmedString("T3CODE_DESKTOP_APP_USER_MODEL_ID"),
devRemoteT3ServerEntryPath: trimmedString("T3CODE_DEV_REMOTE_T3_SERVER_ENTRY_PATH"),
configuredBackendPort: Config.port("T3CODE_PORT").pipe(Config.option),
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/app/DesktopEnvironment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ describe("DesktopEnvironment", () => {
{},
{
T3CODE_HOME: " /tmp/t3 ",
T3CODE_DESKTOP_USER_DATA_DIR: " /tmp/t3/userdata/electron ",
T3CODE_COMMIT_HASH: " 0123456789abcdef ",
T3CODE_PORT: "4949",
VITE_DEV_SERVER_URL: "http://localhost:5173",
Expand Down Expand Up @@ -74,6 +75,7 @@ describe("DesktopEnvironment", () => {
Option.map(environment.devServerUrl, (url) => url.href),
Option.some("http://localhost:5173/"),
);
assert.deepEqual(environment.desktopUserDataDir, Option.some("/tmp/t3/userdata/electron"));
assert.deepEqual(environment.devRemoteT3ServerEntryPath, Option.some("/remote/server.mjs"));
assert.deepEqual(environment.configuredBackendPort, Option.some(4949));
assert.deepEqual(environment.commitHashOverride, Option.some("0123456789abcdef"));
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/app/DesktopEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export class DesktopEnvironment extends Context.Service<
readonly preloadPath: string;
readonly appUpdateYmlPath: string;
readonly devServerUrl: Option.Option<URL>;
readonly desktopUserDataDir: Option.Option<string>;
readonly devRemoteT3ServerEntryPath: Option.Option<string>;
readonly configuredBackendPort: Option.Option<number>;
readonly commitHashOverride: Option.Option<string>;
Expand Down Expand Up @@ -216,6 +217,7 @@ const make = Effect.fn("desktop.environment.make")(function* (
? path.join(resourcesPath, "app-update.yml")
: path.join(input.appPath, "dev-app-update.yml"),
devServerUrl,
desktopUserDataDir: Option.map(config.desktopUserDataDir, path.resolve),
devRemoteT3ServerEntryPath: config.devRemoteT3ServerEntryPath,
configuredBackendPort: config.configuredBackendPort,
commitHashOverride: config.commitHashOverride,
Expand Down
59 changes: 42 additions & 17 deletions apps/desktop/src/ipc/methods/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import {
DesktopPreviewAutomationEvaluateInputSchema,
DesktopPreviewAutomationPressInputSchema,
DesktopPreviewAutomationScrollInputSchema,
DesktopPreviewAutomationSnapshotInputSchema,
DesktopPreviewAutomationTypeInputSchema,
DesktopPreviewAutomationWaitForInputSchema,
DesktopPreviewConfigInputSchema,
DesktopPreviewNavigateInputSchema,
DesktopPreviewRecordingArtifactSchema,
DesktopPreviewRecordingStartInputSchema,
DesktopPreviewRecordingStopInputSchema,
DesktopPreviewRecordingSaveInputSchema,
DesktopPreviewRegisterWebviewInputSchema,
DesktopPreviewScreenshotArtifactSchema,
Expand Down Expand Up @@ -148,9 +151,13 @@ export const setColorScheme = DesktopIpc.makeIpcMethod({
channel: IpcChannels.PREVIEW_SET_COLOR_SCHEME_CHANNEL,
payload: DesktopPreviewSetColorSchemeInputSchema,
result: Schema.Void,
handler: Effect.fn("desktop.ipc.preview.setColorScheme")(function* ({ tabId, colorScheme }) {
handler: Effect.fn("desktop.ipc.preview.setColorScheme")(function* ({
tabId,
colorScheme,
timeoutMs,
}) {
const manager = yield* PreviewManager.PreviewManager;
yield* manager.setColorScheme(tabId, colorScheme);
yield* manager.setColorScheme(tabId, colorScheme, timeoutMs);
}),
});
export const openDevTools = tabMethod(
Expand All @@ -163,16 +170,24 @@ export const cancelPickElement = tabMethod(
"desktop.ipc.preview.cancelPickElement",
(manager, tabId) => manager.cancelPickElement(tabId),
);
export const startRecording = tabMethod(
IpcChannels.PREVIEW_RECORDING_START_CHANNEL,
"desktop.ipc.preview.startRecording",
(manager, tabId) => manager.startRecording(tabId),
);
export const stopRecording = tabMethod(
IpcChannels.PREVIEW_RECORDING_STOP_CHANNEL,
"desktop.ipc.preview.stopRecording",
(manager, tabId) => manager.stopRecording(tabId),
);
export const startRecording = DesktopIpc.makeIpcMethod({
channel: IpcChannels.PREVIEW_RECORDING_START_CHANNEL,
payload: DesktopPreviewRecordingStartInputSchema,
result: Schema.Void,
handler: Effect.fn("desktop.ipc.preview.startRecording")(function* ({ tabId, timeoutMs }) {
const manager = yield* PreviewManager.PreviewManager;
yield* manager.startRecording(tabId, timeoutMs);
}),
});
export const stopRecording = DesktopIpc.makeIpcMethod({
channel: IpcChannels.PREVIEW_RECORDING_STOP_CHANNEL,
payload: DesktopPreviewRecordingStopInputSchema,
result: Schema.Void,
handler: Effect.fn("desktop.ipc.preview.stopRecording")(function* ({ tabId, timeoutMs }) {
const manager = yield* PreviewManager.PreviewManager;
yield* manager.stopRecording(tabId, timeoutMs);
}),
});
export const openPictureInPicture = tabMethod(
IpcChannels.PREVIEW_PICTURE_IN_PICTURE_OPEN_CHANNEL,
"desktop.ipc.preview.openPictureInPicture",
Expand Down Expand Up @@ -281,11 +296,15 @@ export const automationStatus = DesktopIpc.makeIpcMethod({

export const automationSnapshot = DesktopIpc.makeIpcMethod({
channel: IpcChannels.PREVIEW_AUTOMATION_SNAPSHOT_CHANNEL,
payload: DesktopPreviewTabInputSchema,
payload: DesktopPreviewAutomationSnapshotInputSchema,
result: PreviewAutomationSnapshot,
handler: Effect.fn("desktop.ipc.preview.automationSnapshot")(function* ({ tabId }) {
handler: Effect.fn("desktop.ipc.preview.automationSnapshot")(function* ({
tabId,
background,
timeoutMs,
}) {
const manager = yield* PreviewManager.PreviewManager;
return yield* manager.automationSnapshot(tabId);
return yield* manager.automationSnapshot(tabId, background, timeoutMs);
}),
});

Expand Down Expand Up @@ -353,9 +372,15 @@ export const saveRecording = DesktopIpc.makeIpcMethod({
channel: IpcChannels.PREVIEW_RECORDING_SAVE_CHANNEL,
payload: DesktopPreviewRecordingSaveInputSchema,
result: DesktopPreviewRecordingArtifactSchema,
handler: Effect.fn("desktop.ipc.preview.saveRecording")(function* ({ tabId, mimeType, data }) {
handler: Effect.fn("desktop.ipc.preview.saveRecording")(function* ({
tabId,
mimeType,
data,
idempotencyKey,
timeoutMs,
}) {
const manager = yield* PreviewManager.PreviewManager;
return yield* manager.saveRecording(tabId, mimeType, data);
return yield* manager.saveRecording(tabId, mimeType, data, idempotencyKey, timeoutMs);
}),
});

Expand Down
28 changes: 19 additions & 9 deletions apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,12 @@ contextBridge.exposeInMainWorld("desktopBridge", {
zoomOut: (tabId) => ipcRenderer.invoke(IpcChannels.PREVIEW_ZOOM_OUT_CHANNEL, { tabId }),
resetZoom: (tabId) => ipcRenderer.invoke(IpcChannels.PREVIEW_RESET_ZOOM_CHANNEL, { tabId }),
hardReload: (tabId) => ipcRenderer.invoke(IpcChannels.PREVIEW_HARD_RELOAD_CHANNEL, { tabId }),
setColorScheme: (tabId, colorScheme) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_SET_COLOR_SCHEME_CHANNEL, { tabId, colorScheme }),
setColorScheme: (tabId, colorScheme, timeoutMs) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_SET_COLOR_SCHEME_CHANNEL, {
tabId,
colorScheme,
timeoutMs,
}),
openDevTools: (tabId) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_OPEN_DEVTOOLS_CHANNEL, { tabId }),
clearCookies: () => ipcRenderer.invoke(IpcChannels.PREVIEW_CLEAR_COOKIES_CHANNEL),
Expand All @@ -207,15 +211,17 @@ contextBridge.exposeInMainWorld("desktopBridge", {
ipcRenderer.invoke(IpcChannels.PREVIEW_PICTURE_IN_PICTURE_CLOSE_CHANNEL, { tabId }),
},
recording: {
startScreencast: (tabId) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_RECORDING_START_CHANNEL, { tabId }),
stopScreencast: (tabId) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_RECORDING_STOP_CHANNEL, { tabId }),
save: (tabId, mimeType, data) =>
startScreencast: (tabId, timeoutMs) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_RECORDING_START_CHANNEL, { tabId, timeoutMs }),
stopScreencast: (tabId, timeoutMs) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_RECORDING_STOP_CHANNEL, { tabId, timeoutMs }),
save: (tabId, mimeType, data, idempotencyKey, timeoutMs) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_RECORDING_SAVE_CHANNEL, {
tabId,
mimeType,
data,
idempotencyKey,
timeoutMs,
}),
onFrame: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, frame: unknown) => {
Expand All @@ -230,8 +236,12 @@ contextBridge.exposeInMainWorld("desktopBridge", {
automation: {
status: (tabId) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_AUTOMATION_STATUS_CHANNEL, { tabId }),
snapshot: (tabId) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_AUTOMATION_SNAPSHOT_CHANNEL, { tabId }),
snapshot: (tabId, background = false, timeoutMs) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_AUTOMATION_SNAPSHOT_CHANNEL, {
tabId,
background,
timeoutMs,
}),
click: (tabId, input) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_AUTOMATION_CLICK_CHANNEL, { tabId, input }),
type: (tabId, input) =>
Expand Down
Loading
Loading