Skip to content

Commit 929f380

Browse files
benvinegarclaude
andcommitted
feat(viewer): hideBrand host flag — omit the engine wordmark for branded hosts
A host that supplies its own branding (e.g. cloud's workspace picker atop the sidebar + its own wordmark in the footer) can set host.hideBrand to suppress the engine's "sideshow" wordmark and avoid doubling up. Self-hosted leaves it unset and shows the wordmark as before. Covered by e2e/embed-hide-brand.spec.ts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ab5f91a commit 929f380

5 files changed

Lines changed: 81 additions & 2 deletions

File tree

.changeset/host-hide-brand.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"sideshow": minor
3+
---
4+
5+
Add a `hideBrand` flag to the embed host contract. When set, the engine omits its own "sideshow" wordmark (the sidebar/header home-link brand) so a host that supplies its own branding — e.g. a workspace picker atop the sidebar and a wordmark in the footer — isn't doubled up. Self-hosted leaves it unset and shows the wordmark as before.

e2e/embed-hide-brand.spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// End-to-end proof of the `hideBrand` host flag: an embedder that supplies its own
2+
// branding can suppress the engine's "sideshow" wordmark. With the flag off
3+
// (self-hosted default) the wordmark renders as before, so parity holds.
4+
//
5+
// Same harness as embed-main-slot.spec.ts.
6+
import { readFileSync } from "node:fs";
7+
import { fileURLToPath } from "node:url";
8+
import { expect, publish, test } from "./fixtures.ts";
9+
10+
const embedDir = fileURLToPath(new URL("../viewer/dist-embed", import.meta.url));
11+
12+
function contentType(path: string): string {
13+
if (path.endsWith(".js") || path.endsWith(".mjs")) return "text/javascript";
14+
if (path.endsWith(".wasm")) return "application/wasm";
15+
if (path.endsWith(".css")) return "text/css";
16+
return "application/octet-stream";
17+
}
18+
19+
const embedHtml = (hideBrand: boolean) => `<!doctype html>
20+
<html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%}#m{position:fixed;inset:0}</style></head>
21+
<body><div id="m"></div>
22+
<script type="module">
23+
import { mountViewer } from "/__embed/engine.js";
24+
mountViewer(document.getElementById("m"), {
25+
basePath: "",
26+
hideBrand: ${hideBrand ? "true" : "false"},
27+
router: { get: () => ({ sessionId: null }), navigate() {}, subscribe() { return () => {}; } },
28+
});
29+
</script></body></html>`;
30+
31+
async function mount(page: import("@playwright/test").Page, serverUrl: string, hideBrand: boolean) {
32+
page.on("pageerror", (e) => console.error("[pageerror]", e.message));
33+
const path = `/__embedtest-brand-${hideBrand ? "off" : "on"}`;
34+
await page.route(`**${path}`, (route) =>
35+
route.fulfill({ contentType: "text/html", body: embedHtml(hideBrand) }),
36+
);
37+
await page.route("**/__embed/**", (route) => {
38+
const name = new URL(route.request().url()).pathname.replace("/__embed/", "");
39+
route.fulfill({ contentType: contentType(name), body: readFileSync(`${embedDir}/${name}`) });
40+
});
41+
await page.goto(`${serverUrl}${path}`);
42+
}
43+
44+
test("hideBrand: true suppresses the engine wordmark", async ({ page, server }) => {
45+
await publish(server.url, { html: "<p>card</p>", title: "Seeded", agent: "e2e" }, "");
46+
await mount(page, server.url, true);
47+
await expect(page.locator("aside")).toBeVisible();
48+
await expect(page.locator(".brand")).toHaveCount(0);
49+
});
50+
51+
test("hideBrand off (self-hosted default): the wordmark renders", async ({ page, server }) => {
52+
await publish(server.url, { html: "<p>card</p>", title: "Seeded", agent: "e2e" }, "");
53+
await mount(page, server.url, false);
54+
await expect(page.locator("aside .brand")).toBeVisible();
55+
});

viewer/embed.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ export interface SideshowHost {
3838
* true; self-hosted drives the same flag via a window global. Defaults to off.
3939
*/
4040
screenshots?: boolean;
41+
/**
42+
* Omit the engine's own "sideshow" wordmark (the sidebar/header home-link brand)
43+
* when the host provides its own branding/header — e.g. a cloud with a workspace
44+
* picker atop the sidebar and its own wordmark in the footer. Self-hosted leaves
45+
* this unset and shows the wordmark. Defaults to off.
46+
*/
47+
hideBrand?: boolean;
4148
/**
4249
* The engine calls this with the fully-resolved palette on initial mount, on
4350
* every live theme switch, and on an OS light/dark flip — symmetric with

viewer/src/App.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,17 @@ export default function App() {
189189
<span class="dot" id="menuDot" classList={{ show: unread().size > 0 }}></span>
190190
</button>
191191
</Show>
192-
<Brand />
192+
{/* A host that supplies its own branding (e.g. cloud) hides the
193+
engine wordmark via host.hideBrand. */}
194+
<Show when={!host().hideBrand}>
195+
<Brand />
196+
</Show>
193197
</header>
194198
<Show when={!streamMode()}>
195199
<aside>
196-
<Brand />
200+
<Show when={!host().hideBrand}>
201+
<Brand />
202+
</Show>
197203
<UpdateBanner />
198204
<div id="sessionList">
199205
<For each={sessionGroups()}>

viewer/src/host.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ export interface SideshowHost {
4747
// tooltip when this is false. Self-hosted drives the same flag via
4848
// window.__SIDESHOW_SCREENSHOTS__. Optional — defaults to off.
4949
screenshots?: boolean;
50+
// Omit the engine's own "sideshow" wordmark (the sidebar/header home-link brand)
51+
// when the host provides its own branding/header — e.g. a cloud that puts a
52+
// workspace picker at the top of the sidebar and its own wordmark in the footer.
53+
// Self-hosted leaves this unset and shows the wordmark as before. Optional —
54+
// defaults to off.
55+
hideBrand?: boolean;
5056
// The engine calls this with the fully-resolved palette on initial mount, on
5157
// every live theme switch, and on an OS light/dark flip. Symmetric with
5258
// router.navigate: the engine owns the themes and TELLS the host its colors,

0 commit comments

Comments
 (0)