Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/host-hide-brand.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"sideshow": minor
---

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.
55 changes: 55 additions & 0 deletions e2e/embed-hide-brand.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// End-to-end proof of the `hideBrand` host flag: an embedder that supplies its own
// branding can suppress the engine's "sideshow" wordmark. With the flag off
// (self-hosted default) the wordmark renders as before, so parity holds.
//
// Same harness as embed-main-slot.spec.ts.
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { expect, publish, test } from "./fixtures.ts";

const embedDir = fileURLToPath(new URL("../viewer/dist-embed", import.meta.url));

function contentType(path: string): string {
if (path.endsWith(".js") || path.endsWith(".mjs")) return "text/javascript";
if (path.endsWith(".wasm")) return "application/wasm";
if (path.endsWith(".css")) return "text/css";
return "application/octet-stream";
}

const embedHtml = (hideBrand: boolean) => `<!doctype html>
<html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%}#m{position:fixed;inset:0}</style></head>
<body><div id="m"></div>
<script type="module">
import { mountViewer } from "/__embed/engine.js";
mountViewer(document.getElementById("m"), {
basePath: "",
hideBrand: ${hideBrand ? "true" : "false"},
router: { get: () => ({ sessionId: null }), navigate() {}, subscribe() { return () => {}; } },
});
</script></body></html>`;

async function mount(page: import("@playwright/test").Page, serverUrl: string, hideBrand: boolean) {
page.on("pageerror", (e) => console.error("[pageerror]", e.message));
const path = `/__embedtest-brand-${hideBrand ? "off" : "on"}`;
await page.route(`**${path}`, (route) =>
route.fulfill({ contentType: "text/html", body: embedHtml(hideBrand) }),
);
await page.route("**/__embed/**", (route) => {
const name = new URL(route.request().url()).pathname.replace("/__embed/", "");
route.fulfill({ contentType: contentType(name), body: readFileSync(`${embedDir}/${name}`) });
});
await page.goto(`${serverUrl}${path}`);
}

test("hideBrand: true suppresses the engine wordmark", async ({ page, server }) => {
await publish(server.url, { html: "<p>card</p>", title: "Seeded", agent: "e2e" }, "");
await mount(page, server.url, true);
await expect(page.locator("aside")).toBeVisible();
await expect(page.locator(".brand")).toHaveCount(0);
});

test("hideBrand off (self-hosted default): the wordmark renders", async ({ page, server }) => {
await publish(server.url, { html: "<p>card</p>", title: "Seeded", agent: "e2e" }, "");
await mount(page, server.url, false);
await expect(page.locator("aside .brand")).toBeVisible();
});
7 changes: 7 additions & 0 deletions viewer/embed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ export interface SideshowHost {
* true; self-hosted drives the same flag via a window global. Defaults to off.
*/
screenshots?: boolean;
/**
* Omit the engine's own "sideshow" wordmark (the sidebar/header home-link brand)
* when the host provides its own branding/header — e.g. a cloud with a workspace
* picker atop the sidebar and its own wordmark in the footer. Self-hosted leaves
* this unset and shows the wordmark. Defaults to off.
*/
hideBrand?: boolean;
/**
* The engine calls this with the fully-resolved palette on initial mount, on
* every live theme switch, and on an OS light/dark flip — symmetric with
Expand Down
10 changes: 8 additions & 2 deletions viewer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,17 @@ export default function App() {
☰<span class="dot" id="menuDot" classList={{ show: unread().size > 0 }}></span>
</button>
</Show>
<Brand />
{/* A host that supplies its own branding (e.g. cloud) hides the
engine wordmark via host.hideBrand. */}
<Show when={!host().hideBrand}>
<Brand />
</Show>
</header>
<Show when={!streamMode()}>
<aside>
<Brand />
<Show when={!host().hideBrand}>
<Brand />
</Show>
<UpdateBanner />
<div id="sessionList">
<For each={sessionGroups()}>
Expand Down
6 changes: 6 additions & 0 deletions viewer/src/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ export interface SideshowHost {
// tooltip when this is false. Self-hosted drives the same flag via
// window.__SIDESHOW_SCREENSHOTS__. Optional — defaults to off.
screenshots?: boolean;
// Omit the engine's own "sideshow" wordmark (the sidebar/header home-link brand)
// when the host provides its own branding/header — e.g. a cloud that puts a
// workspace picker at the top of the sidebar and its own wordmark in the footer.
// Self-hosted leaves this unset and shows the wordmark as before. Optional —
// defaults to off.
hideBrand?: boolean;
// The engine calls this with the fully-resolved palette on initial mount, on
// every live theme switch, and on an OS light/dark flip. Symmetric with
// router.navigate: the engine owns the themes and TELLS the host its colors,
Expand Down
Loading