Skip to content

Commit 5876fed

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 1b7a28c commit 5876fed

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
@@ -47,6 +47,13 @@ export interface SideshowHost {
4747
* the host's landing. Self-hosted leaves this unset and is unchanged. Defaults to off.
4848
*/
4949
homeView?: boolean;
50+
/**
51+
* Omit the engine's own "sideshow" wordmark (the sidebar/header home-link brand)
52+
* when the host provides its own branding/header — e.g. a cloud with a workspace
53+
* picker atop the sidebar and its own wordmark in the footer. Self-hosted leaves
54+
* this unset and shows the wordmark. Defaults to off.
55+
*/
56+
hideBrand?: boolean;
5057
/**
5158
* The engine calls this with the fully-resolved palette on initial mount, on
5259
* 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
@@ -197,11 +197,17 @@ export default function App() {
197197
<span class="dot" id="menuDot" classList={{ show: unread().size > 0 }}></span>
198198
</button>
199199
</Show>
200-
<Brand />
200+
{/* A host that supplies its own branding (e.g. cloud) hides the
201+
engine wordmark via host.hideBrand. */}
202+
<Show when={!host().hideBrand}>
203+
<Brand />
204+
</Show>
201205
</header>
202206
<Show when={!streamMode()}>
203207
<aside>
204-
<Brand />
208+
<Show when={!host().hideBrand}>
209+
<Brand />
210+
</Show>
205211
<UpdateBanner />
206212
{/* Host-overridable region (SLOTS.asideHead): the sidebar header, above the
207213
session list. Empty by default (self-hosted shows nothing here); an embedder

viewer/src/host.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ export interface SideshowHost {
5656
// auto-selects the latest session on boot and deselects explicitly via the
5757
// wordmark goHome() instead. Optional — defaults to off.
5858
homeView?: boolean;
59+
// Omit the engine's own "sideshow" wordmark (the sidebar/header home-link brand)
60+
// when the host provides its own branding/header — e.g. a cloud that puts a
61+
// workspace picker at the top of the sidebar and its own wordmark in the footer.
62+
// Self-hosted leaves this unset and shows the wordmark as before. Optional —
63+
// defaults to off.
64+
hideBrand?: boolean;
5965
// The engine calls this with the fully-resolved palette on initial mount, on
6066
// every live theme switch, and on an OS light/dark flip. Symmetric with
6167
// router.navigate: the engine owns the themes and TELLS the host its colors,

0 commit comments

Comments
 (0)