Skip to content
Closed
1 change: 1 addition & 0 deletions apps/server/src/preview/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export const make = Effect.gen(function* PreviewManagerMake() {
canGoForward: false,
viewport: input.viewport ?? FILL_PREVIEW_VIEWPORT,
...(input.profileId === undefined ? {} : { profileId: input.profileId }),
...(input.colorScheme === undefined ? {} : { colorScheme: input.colorScheme }),
...(runtime === undefined ? {} : { runtime }),
...(runtime === "server" && input.automationOwner !== undefined
? { automationOwner: input.automationOwner }
Expand Down
24 changes: 24 additions & 0 deletions apps/server/src/preview/ServerBrowser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1395,6 +1395,30 @@ it.live("drives the desktop's own page for a tab the desktop renders", () =>
).pipe(Effect.provide(layer)),
);

it.live("a tab opens at its requested appearance, and system undoes Playwright's light", () =>
Effect.scoped(
Effect.gen(function* () {
const browser = yield* ServerBrowser.ServerBrowser;
const manager = yield* Manager.PreviewManager;
yield* Effect.yieldNow;
const dark = yield* manager.open({
threadId: scope.thread.threadId,
runtime: "server",
colorScheme: "dark",
});
expect(dark.colorScheme).toBe("dark");
yield* browser.attachViewer(viewerInput(dark.tabId, true));
expect(contexts[0]!.page.emulateMedia).toHaveBeenCalledWith({ colorScheme: "dark" });
desktopRendersNext = true;
const system = yield* manager.open({ threadId: scope.thread.threadId, runtime: "server" });
yield* browser.attachViewer(viewerInput(system.tabId, true));
expect(desktopConnections[0]!.context.page.emulateMedia).toHaveBeenCalledWith({
colorScheme: null,
});
}),
).pipe(Effect.provide(layer)),
);

it.live("a desktop page the desktop takes back reconnects instead of closing", () =>
Effect.scoped(
Effect.gen(function* () {
Expand Down
6 changes: 4 additions & 2 deletions apps/server/src/preview/ServerBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ interface ServerTab {
} | null;
dialog: Dialog | null;
setting: PreviewViewportSetting;
colorScheme: PreviewAppearancePreference;
/** Null until first applied, so a `system` tab still clears Playwright's default light. */
colorScheme: PreviewAppearancePreference | null;
zoomFactor: number;
loading: boolean;
closing: boolean;
Expand Down Expand Up @@ -751,7 +752,7 @@ const make = Effect.gen(function* () {
fileChooser: null,
dialog: null,
setting: snapshot.viewport ?? FILL_PREVIEW_VIEWPORT,
colorScheme: "system",
colorScheme: null,
zoomFactor: 1,
loading: false,
closing: false,
Expand Down Expand Up @@ -1165,6 +1166,7 @@ const make = Effect.gen(function* () {
...(/^https?:/i.test(url) ? { url } : {}),
runtime: "server",
...(opener.profileId === undefined ? {} : { profileId: opener.profileId }),
...(opener.colorScheme === null ? {} : { colorScheme: opener.colorScheme }),
...(opener.control.agentId === null
? {}
: { automationOwner: opener.control.agentId, reveal: false }),
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/browser/openFileInPreview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export async function openUrlInPreview<E>(input: {
url: input.url,
viewport: browserDefaultOpenViewport(defaults),
profileId: input.profileId ?? browserDefaultOpenProfileId(defaults),
...(runtime === undefined ? {} : { runtime }),
...(runtime === undefined ? {} : { runtime, colorScheme: defaults.appearance }),
},
});
return mapAtomCommandResult(result, (snapshot) => {
Expand Down
36 changes: 36 additions & 0 deletions apps/web/src/components/preview/openPreviewSession.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,42 @@ describe("openPreviewSession", () => {
expect(readThreadPreviewState(threadRef).recentlySeenUrls).toEqual(["https://t3.chat/"]);
});

it("opens a server tab at the default appearance", async () => {
__setClientSettingsForTests({ ...DEFAULT_CLIENT_SETTINGS, browserDefaultAppearance: "dark" });
const open = vi.fn(async (_input: PreviewOpenInput) => AsyncResult.success(snapshot));

await openPreviewSession({
openPreview: ({ input }) => open(input),
threadRef,
runtime: "server",
});

expect(open).toHaveBeenCalledWith({
threadId: "thread-1",
viewport: FILL_PREVIEW_VIEWPORT,
profileId: DEFAULT_BROWSER_PROFILE_ID,
runtime: "server",
colorScheme: "dark",
});
});

it("leaves a desktop tab's appearance to the desktop", async () => {
__setClientSettingsForTests({ ...DEFAULT_CLIENT_SETTINGS, browserDefaultAppearance: "dark" });
const open = vi.fn(async (_input: PreviewOpenInput) => AsyncResult.success(snapshot));

await openPreviewSession({
openPreview: ({ input }) => open(input),
threadRef,
runtime: "desktop",
});

expect(open).toHaveBeenCalledWith({
threadId: "thread-1",
viewport: FILL_PREVIEW_VIEWPORT,
profileId: DEFAULT_BROWSER_PROFILE_ID,
});
});

it("returns failures without mutating preview state", async () => {
const failure = new Error("preview unavailable");

Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/components/preview/openPreviewSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ export async function openPreviewSession<E>(
...(input.url === undefined ? {} : { url: input.url }),
viewport: input.viewport ?? browserDefaultOpenViewport(defaults),
profileId: input.profileId ?? browserDefaultOpenProfileId(defaults),
...(runtime === undefined || runtime === "desktop" ? {} : { runtime }),
...(runtime === undefined || runtime === "desktop"
? {}
: { runtime, colorScheme: defaults.appearance }),
},
});
if (result._tag === "Failure") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export async function openTerminalLinkInPreview<E>(
url: input.url,
viewport: browserDefaultOpenViewport(defaults),
profileId: browserDefaultOpenProfileId(defaults),
...(runtime === undefined ? {} : { runtime }),
...(runtime === undefined ? {} : { runtime, colorScheme: defaults.appearance }),
},
});
if (result._tag === "Failure") {
Expand Down
2 changes: 2 additions & 0 deletions packages/contracts/src/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ export const PreviewOpenInput = Schema.Struct({
viewport: Schema.optional(PreviewViewportSetting),
profileId: Schema.optional(BrowserProfileId),
runtime: Schema.optional(PreviewRuntime),
/** Server tabs only; the desktop seeds its own tabs at creation. */
colorScheme: Schema.optional(PreviewAppearancePreference),
reveal: Schema.optional(Schema.Boolean),
});
export type PreviewOpenInput = typeof PreviewOpenInput.Type;
Expand Down
Loading