Skip to content

Commit d0fba2c

Browse files
committed
fix(server): report browser override errors during HTML publishing
1 parent 9fc8a2e commit d0fba2c

5 files changed

Lines changed: 54 additions & 24 deletions

File tree

‎apps/server/src/htmlRender/HtmlRender.test.ts‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const TEST_BROWSER_ENV = "T3CODE_TEST_HEADLESS_SHELL";
2727

2828
const layerHtmlRender = (
2929
executable?: string,
30-
installed: Effect.Effect<Option.Option<string>> = Effect.succeed(
30+
installed: PreviewBrowser.PreviewBrowser["Service"]["installed"] = Effect.succeed(
3131
Option.fromUndefinedOr(executable),
3232
),
3333
) =>
@@ -258,6 +258,27 @@ describe("HtmlRender", () => {
258258
}).pipe(Effect.provide(layerTest)),
259259
);
260260

261+
it.effect("rejects an invalid browser configuration without retaining the published page", () =>
262+
Effect.gen(function* () {
263+
const error = new PreviewBrowser.PreviewBrowserInstallError({
264+
detail: "T3CODE_SERVER_BROWSER_EXECUTABLE must be an absolute path to an executable file.",
265+
});
266+
yield* Effect.gen(function* () {
267+
const fileSystem = yield* FileSystem.FileSystem;
268+
const config = yield* ServerConfig.ServerConfig;
269+
const htmlRender = yield* HtmlRender.HtmlRender;
270+
const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
271+
const threadId = ThreadId.make("thread-html-invalid-browser");
272+
const page = { html: "<p>x</p>", title: "X", height: 200 };
273+
274+
expect(yield* htmlRender.publish({ threadId, ...page }).pipe(Effect.flip)).toEqual(error);
275+
const stored = yield* fileSystem.readDirectory(config.attachmentsDir, { recursive: true });
276+
expect(stored.filter((name) => name.endsWith(".html"))).toEqual([]);
277+
expect(yield* mcpSessions.takeHtmlRender(threadId, page)).toBe(undefined);
278+
}).pipe(Effect.provide(layerHtmlRender(undefined, Effect.fail(error))));
279+
}),
280+
);
281+
261282
it.effect("removes the page when publishing is interrupted", () =>
262283
Effect.gen(function* () {
263284
const threadId = ThreadId.make("thread-html-cancel");

‎apps/server/src/htmlRender/HtmlRender.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@ export class HtmlRender extends Context.Service<
126126
readonly html: string;
127127
readonly title: string;
128128
readonly height: number;
129-
}) => Effect.Effect<HtmlRenderReference, HtmlRenderPrepareError | HtmlRenderStoreError>;
129+
}) => Effect.Effect<
130+
HtmlRenderReference,
131+
HtmlRenderPrepareError | HtmlRenderStoreError | PreviewBrowser.PreviewBrowserInstallError
132+
>;
130133
readonly preview: (input: {
131134
readonly html: string;
132135
readonly width?: number | undefined;
@@ -390,9 +393,11 @@ const make = Effect.gen(function* () {
390393
return heights.toSorted(([left], [right]) => left - right);
391394
},
392395
Effect.catch((cause) =>
393-
Effect.logWarning("Could not measure an HTML render; publishing it without heights.", {
394-
cause,
395-
}).pipe(Effect.as(undefined)),
396+
cause._tag === "PreviewBrowserInstallError"
397+
? Effect.fail(cause)
398+
: Effect.logWarning("Could not measure an HTML render; publishing it without heights.", {
399+
cause,
400+
}).pipe(Effect.as(undefined)),
396401
),
397402
);
398403

‎apps/server/src/preview/PreviewBrowser.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,9 @@ it.layer(NodeServices.layer)("PreviewBrowser", (it) => {
165165
env: { T3CODE_SERVER_BROWSER_EXECUTABLE: configured },
166166
});
167167

168-
expect(yield* browser.installed).toEqual(Option.none());
168+
const installedError = yield* browser.installed.pipe(Effect.flip);
169+
expect(installedError._tag).toBe("PreviewBrowserInstallError");
170+
expect(installedError.message).toContain("T3CODE_SERVER_BROWSER_EXECUTABLE");
169171
const error = yield* browser.executable.pipe(Effect.flip);
170172
expect(error._tag).toBe("PreviewBrowserInstallError");
171173
expect(error.message).toContain("T3CODE_SERVER_BROWSER_EXECUTABLE");

‎apps/server/src/preview/PreviewBrowser.ts‎

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class PreviewBrowser extends Context.Service<
131131
string,
132132
PreviewBrowserInstallError | PreviewBrowserInstallingError | PreviewBrowserUnsupportedError
133133
>;
134-
readonly installed: Effect.Effect<Option.Option<string>>;
134+
readonly installed: Effect.Effect<Option.Option<string>, PreviewBrowserInstallError>;
135135
}
136136
>()("t3/preview/PreviewBrowser") {}
137137

@@ -175,23 +175,24 @@ export const makePreviewBrowser = Effect.fn("PreviewBrowser.make")(function* (
175175
Effect.orElseSucceed(() => Option.none<string>()),
176176
)
177177
: Effect.succeed(Option.none<string>());
178-
return PreviewBrowser.of({
179-
installed,
180-
executable: installed.pipe(
181-
Effect.flatMap(
182-
Option.match({
183-
onSome: Effect.succeed,
184-
onNone: () =>
185-
Effect.fail(
186-
new PreviewBrowserInstallError({
187-
detail:
188-
"T3CODE_SERVER_BROWSER_EXECUTABLE must be an absolute path to an executable file.",
189-
}),
190-
),
191-
}),
192-
),
193-
Effect.withSpan("PreviewBrowser.executable"),
178+
const executable = installed.pipe(
179+
Effect.flatMap(
180+
Option.match({
181+
onSome: Effect.succeed,
182+
onNone: () =>
183+
Effect.fail(
184+
new PreviewBrowserInstallError({
185+
detail:
186+
"T3CODE_SERVER_BROWSER_EXECUTABLE must be an absolute path to an executable file.",
187+
}),
188+
),
189+
}),
194190
),
191+
Effect.withSpan("PreviewBrowser.executable"),
192+
);
193+
return PreviewBrowser.of({
194+
installed: executable.pipe(Effect.map(Option.some)),
195+
executable,
195196
});
196197
}
197198
const http = yield* HttpClient.HttpClient;

‎docs/user/remote-access.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,8 @@ For a background service, put the variable in `service.env` inside the server's
238238
T3 home, normally `~/.t3/service.env`, then restart the service. Set the path on
239239
the machine running the server, including when you connect from another device.
240240
An unset or blank variable keeps the bundled browser. A relative, missing,
241-
non-file or non-executable path produces an error rather than downloading a fallback.
241+
non-file or non-executable path produces an error for browser tabs and HTML
242+
publishing rather than downloading a fallback.
242243

243244
Profiles stay in T3's data folder. You maintain the selected browser and its
244245
required libraries. This option does not change sandbox settings; on Linux,

0 commit comments

Comments
 (0)