- 8822dea: Add a minimal control to collapse the desktop session sidebar into a narrow rail.
-
4161897: Coalesce the session-list refreshes triggered by a burst of live post events. Each post still streams into the open session independently, while sidebar metadata waits for a short quiet window and shares one request instead of fetching the full session list once per post.
-
120705e: Stop live viewer updates and session hydration from downloading a post's complete revision history. Both paths now use a compact viewer representation with current render metadata, native surface data, and a retained-version count, while the existing post detail endpoints keep returning full history.
-
6f81fa1: Refresh runtime and build dependencies to patched releases so the security audit passes with no known vulnerabilities. This also moves the Cloudflare type configuration to the current package entrypoint required by the latest Wrangler release.
-
287bb3d: Make the session-list endpoint count posts through a narrow store aggregate. SQLite no longer selects or decodes post surfaces and history, while the JSON store avoids cloning and sorting posts after loading the workspace. Custom stores without the optional capability keep the existing
listPostsfallback. -
f15dc13: Speed up SQLite-backed workspaces by indexing session posts, recent posts, comment lookups, and session assets.
-
66c5f74: Align the shipped Pi extension with the current post contract: support JSON and code surfaces, require a real surface target for browser-thread replies, and remove experimental trace publishing from agent-facing schemas and guidance.
-
2b98bbf: Cut session-opening hydrate responses by omitting sandboxed surface bodies the viewer never reads. Sandboxed surfaces render through their own versioned iframe URLs, so duplicating those bodies in the session payload only delayed the stream. Across the six largest sessions in a real store, this reduced the response by 95% raw and 91% gzipped. The compact viewer contract above now also replaces retained history with a version count, while full post-detail APIs remain unchanged.
-
9bbcb5e: Fix surface iframes staying stuck at their seed height when a host mounts two viewer instances into one JS realm at once (e.g. an embedding host that cross-fades an outgoing viewer into an incoming one during an in-place navigation). Because the engine loads as a single shared module, both instances shared the module-level surface-frame registry (
cardEls) and the singlemessagebridge listener. Two instances routing to the same URL rendered aCardfor the same post, so keyingcardElsby post id let them clobber each other's entry — and the outgoing instance's teardown deleted the entry the still-visible instance needed, so its surfaceresizemessages were dropped and the frames never grew past their seed height. The shared listener had the same hazard:removeEventListeneron the first teardown tore it out from under the survivor.cardElsis now keyed by a per-Cardtoken (resolved bycontentWindow, with a newcardForPostfor the scroll-to-card pill), and the bridge listener is reference-counted so it lives while any viewer is mounted. Self-hosted single- instance behavior is unchanged.
- a11db09: Add fullscreen Mermaid diagrams and guidance for vertical, card-friendly flowcharts.
- 9e9eb90: Add
send_test_post— a built-in welcome/test post for newly connected agents. One no-arg call publishes a fixed card (shipped with sideshow, themed for light/dark) that confirms the connection works and shows the user example prompts to try. Available on all three tiers: thesend_test_postMCP tool (HTTP and stdio),POST /api/test-post, andsideshow test-post. Idempotent — if the welcome card is already on the board it is returned (alreadySent: true), never duplicated. The MCP initialize instructions now nudge freshly connected agents toward it.
- d6e56e0: Port the cleaner connect-an-agent onboarding screen from sideshow-cloud to the self-hosted viewer.
- ef445fd: Show a pulsing sidecar dot next to the version pill when a post revision was updated recently.
- b4d6a42: Harden the trusted viewer against clickjacking and referrer leaks. The viewer
HTML (the app origin, shared with the authenticated API and the comment→agent
channel) now sends
Content-Security-Policy: frame-ancestors 'self', refusing cross-origin framing; the sandboxed/s/:idsurface documents are unaffected and keep their ownsandboxCSP. External links the viewer opens — theopenLinkbridge'swindow.open, the release-notes markdown links, and the image/trace/footer anchors — now userel="noopener noreferrer"(and thenoreferrerwindow feature), so the current URL (which can carry the?key=deploy token) never rides an outboundReferer. - bf0dd67: Fix blank page when opening a post permalink (
/p/:id) on apublicRead="session"workspace without authentication. The SSE connection fired before the viewer discovered the post's session ID, hitting/api/eventswithout the required?session=param and getting a 401. The connection is now deferred until after the initial post fetch resolves. - 32ed841: Add reusable skeleton loading states for viewer streams and lists.
- 5d2bd91: Reduce MCP instruction and tool-schema context overhead while preserving every canonical and deprecated tool.
-
af09e7a: Emit canonical
/p/<id>post links everywhere a link is produced, finishing the post/surface vocabulary migration. The CLI (sideshow publish/updateoutput), the viewer's copy-link and open-as-image actions, the<link rel="canonical">/ OpenGraph preview tags, the viewer's History API URL shapes (/p/:idand/session/:id/p/:postId), and every MCP tool response — including the deprecatedpublish_surface/publish_snippetaliases — now return/p/URLs.Nothing inbound changes:
/s/:id,/session/:id/s/:postId, and/s/:id.pngremain accepted as legacy aliases (the screenshot Worker now matches both/p/:id.pngand/s/:id.png), and old/s/links keep resolving to the same post page.
- 2bc9db6: Return per-surface ids, kinds, and indexes in write responses without echoing surface payload bodies, and remove the redundant top-level
kindsarray. Read kinds fromsurfaces.map((surface) => surface.kind)instead. - 28a3a6d: Add a host-overridable
ss:aside-headslot at the top of the sidebar, above the session list (mirrorsss:aside-foot). Self-hosted rendering is unchanged; an embedder can project a sidebar header — e.g. a workspace picker and a pinned Home link — above the session list. - 71bf606: Add a
hideBrandflag 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. - 1b7a28c: Add a
homeViewflag to the embed host contract. When a host owns its own session-less landing (e.g. a "home" feed), the engine no longer auto-selects a session on boot — it honors a deep-linked route session but otherwise stays session-less so nothing is highlighted behind the host's landing, and it clears the selection when the route later becomes session-less. Self-hosted leaves the flag unset and is unchanged (auto-selects the latest session on boot). - 5eba65a: Add a server
onEventfeed tap for hosts and aliveTransport: "ws"viewer-embed option so hosted wrappers can provide hibernation-friendly WebSocket live updates while self-hosted sideshow keeps using SSE by default. - ffe7099: Embed host contract:
onThemeChangenow receives a secondmetaargument —{ theme, mode }— naming the resolved theme id and light/dark scheme behind the tokens it already reports. Hosts that re-render surfaces out-of-band (e.g. server-side preview frames they can't theme from the token values alone) can pass those identifiers to/s/:id?theme=&mode=to reproduce the exact look; hosts that only paint from the token values ignore it. Additive — the tokens argument is unchanged and the default self-hosted host is unaffected.
- ed8b987: Validate
sideshow surface addhas at least one surface flag before resolving or creating a session, and resolve long-poll waits promptly when clients disconnect. - 7698b1f: Hydrate session post streams from the session list endpoint to avoid N+1 post detail requests on open.
- 23dad90: Lazy-load sandboxed surface iframes to reduce initial viewer work on long sessions.
- 46cf918: Fix markdown/code/diff/mermaid surfaces rendering on a white canvas (washed-out
text) on a dark board. These rich-part frames are sandboxed opaque-origin
iframes, which default to
color-scheme: normal(light), so in dark mode the UA painted a white backdrop behind the transparent body. They now pincolor-schemeto the resolved scheme — like html surfaces already do — so the frame's canvas tracks the card in both light and dark. - 2bc9db6: List session posts with canonical
surfacesentries that include surface ids and omit elided html bodies. - cc6504c: Slides kit now grid-stacks its deck so it cross-fades in normal flow. Previously
it swapped slides with
display:none, which can't fade — so decks were hand-rolled withposition:absoluteslides over amin-heightstage, an out-of-flow layout the surface-page height bridge can't measure (the overlay growsscrollHeightbut not the box its ResizeObserver watches), leaving the frame clipped/frozen. The kit now stacks slides in one grid cell (in flow, sized to the tallest slide) and fades with opacity/visibility, so the frame follows it. DESIGN_GUIDE documents the out-of-flow trap and the grid-stack recipe alongside the existingposition: fixedban. - 2bc9db6: Expose derived 0-based surface indexes on post detail, session post list, and post history read responses.
- 0a094ac: Let direct rich-surface renders without an explicit mode follow the browser's system color scheme.
- 565c85f: Fix intermittent clipping in surface iframes when async content settles after the initial resize pass.
-
132699b:
sideshow publishandsideshow surface addnow accept repeated surface flags to add several surfaces of the same kind. Previously a repeated non-multiple flag (--diff a --diff b) was silently dropped to the last value with no error.sideshow publish <html> --diff a.patch --code c.ts --diff b.patchnow produces[html, diff, code, diff]— each repeat adds a surface, in command-line flag order.sideshow surface add <id> --md a.md --md b.mdappends two markdown surfaces (one append call per surface, so--before/--afterpositioning still applies per surface).- The seven surface flags (
--md,--mermaid,--diff,--terminal,--json,--code,--image) are nowrepeatablein both commands.
This closes the remaining gap from #151 (multiple surfaces of the same kind on the CLI); surface order control was already fixed in 0.9.x via the token-walk for #158.
-
d58264a: Per-surface operations across CLI, HTTP API, and MCP. Surfaces now carry stable server-assigned ids for targeted operations.
CLI
sideshow publishnow honors flag order: surfaces appear in the order their--md/--code/--diff/ etc. flags appear on the command line, not a fixed sequence (fixes #158).sideshow update <id> <file|-> --surface Ntargets a specific surface in a multi-surface post (by id or 0-based index) for content-only edits.- New
sideshow surfacesubcommand:surface add <postId> [--md f] [--code f] ...— append surfaces to an existing post (flag order honored).surface remove <postId> <N|id>— remove a single surface.surface edit <postId> <N|id> <file|->— replace a surface's content.surface move <postId> <N|id> --to M— reorder a surface.
HTTP API
POST /api/posts/:id/surfaces— append a surface (optionalbefore/afterfor insert position).PATCH /api/posts/:id/surfaces/:target— replace a surface (full or content-only).:targetis a surface id or 0-based index.DELETE /api/posts/:id/surfaces/:target— remove a surface (400 if last).PATCH /api/posts/:id/surfaces— reorder surfaces. Body:{order: [id, ...]}or{order: [2, 0, 1]}.PATCH /api/posts/:idextended: optionalsurfaceparam targets a specific surface in multi-surface posts (previously rejected with 400).
MCP
- New tools:
add_surface,edit_surface,remove_surface,reorder_surfaces— all additive;update_postfull-replace stays for back-compat. Available on both stdio and HTTP MCP transports.
Data model
- Every surface now carries an optional
id: string, assigned server-side on create/update. Existing data is migrated automatically (one-time migration on first boot, gated on a settings sentinel for SqlStore; in-memory normalization on load for JsonFileStore).
Viewer
- Surfaces are keyed by stable
id(Solid<For>withreconcile({ key: "id" })) instead of array position, so reordering moves DOM nodes instead of re-creating them.
-
f0e0337: Drop the dead
/u/-prefix URL fallback from the default viewer host. The embeddable engine'screateDefaultHost()now derives its base path solely fromwindow.__SIDESHOW_BASE_PATH__(empty at root) instead of also sniffing a/u/:accountprefix out oflocation.pathname. That fallback was specific to an old hosted-wrapper URL shape; self-hosted sideshow already runs at the root or sets the global explicitly, so behavior is unchanged for every supported host. -
132699b: Fixed a latent bug where post, surface, and session ids could start with
-or_(URL-safe base64 maps+→-,/→_, so ~1/64 of ids began with a separator). Any id starting with-broke CLI commands that take an id as a positional —node:utilparseArgstreated it as an unknown option (Unknown option '-6'for an id like-6K4AJsKD4M), affectingsideshow update,show, andsurface add/remove/edit/move. Two fixes:newIdnow swaps a leading separator for an alphanumeric, so new ids are always CLI-safe.- The CLI's
parse()wrapper swaps id-shaped-/_-prefixed tokens for a sentinel beforeparseArgssees them, then restores them in the result (positionals, tokens, option values). This rescues already-stored ids that start with a separator.
-
ab5f91a: Fix the mobile sessions drawer in embedded WebKit viewers so the menu button opens the session list reliably.
-
d26f7cc: Respect configured base paths for uploaded asset URLs and native image/trace surface loads.
-
9e61484: Add a recent posts feed endpoint at
GET /api/surfaces/recent, returning the newest updated posts across sessions with capped surface previews. -
392a134: Add a light/dark/system color-mode switcher next to the theme picker.
-
35f75c8: Add a proof-of-concept for anchored comments on surfaces. Comments can now carry sanitized surface anchor metadata, the viewer can place host-owned pins over rendered surfaces, and agent feedback includes anchor context.
-
c498e7d: Use post and session titles for browser tab and share-page titles.
-
3672369:
sideshow updatenow preserves the surface kind instead of always treating content as HTML. A markdown post updated withsideshow update <id> file.mdstays markdown; a code post stays code with its language preserved; diffs keep their layout, terminals keep their cols — every kind-specific field is carried forward. The same fix applies to all text-content surface kinds (html, markdown, code, diff, terminal, mermaid, json).Implemented as a new
PATCH /api/posts/:idendpoint that accepts rawcontent(plus optionaltitleandkits) and slots it into the existing surface's kind, rather than requiring the caller to construct the full typed surface object. Multi-surface posts return a 400 for now — surface-level targeting is a future addition. The existingPUTfull-replacement API is unchanged. -
b04251f: Stop the viewer engine from pinning the default (topmost) surface in the URL when a session auto-opens. Landing at the top of a session feed now keeps the URL at
/session/:id; only an explicit surface open (a deep link, or scrolling into a surface) writes/session/:id/s/:id. Deep links loaded from the URL are still honored and preserved.
-
3046bc9: Add a host-overridable
ss:aside-emptyslot for the sidebar's empty state. When the session list is empty (post-load), it now shows a lightweight native "Connect an agent" row — the first item of an otherwise-empty list — with a plug icon and a one-line helper, instead of a blank list area. Clicking it scrolls to the empty-board pane (ss:empty) that holds the connect instructions. An embedder can project aslot="ss:aside-empty"child to replace the fallback with its own empty-list nudge; once a session exists, neither renders. Self-hosted gets the affordance via the fallback; the slot lets embedders override it. -
fdc5b3e: The engine now uses the canonical
/api/postswire andpost-*SSE events; legacy/api/surfacesroutes,surface-*aliases, andpublish_surfaceMCP tools remain as deprecated aliases. -
14c48dd: Bring the new post / surface vocabulary to the HTTP and MCP wire layers, additively. The canonical hierarchy is now workspace ▸ session ▸ post ▸ surface (a post is an ordered list of surfaces); the older spellings keep working as deprecated aliases — nothing is removed.
New HTTP routes mirror the existing surface routes, sharing the same handlers:
GET/POST/PUT/DELETE /api/posts(/:id),GET /p/:id(with?surface=N),GET /session/:id/p/:postId, andGET /api/sessions/:id/posts. The publish and revise handlers now accept asurfacesbody (falling back to the legacyparts), so both/api/postsand/api/surfacestake either field;/p/:idand/s/:idaccept?surface=Nas well as?part=N.New MCP tools
publish_post,update_post, andlist_postsare advertised on both transports, advertising asurfacesargument and emitting/p/<id>view URLs. The legacypublish_surface/update_surface/list_surfacestools remain (now described as deprecated aliases) and still acceptparts.reply_to_useradditionally accepts apostIdargument. Tool prose and schemas are rewritten in the new vocabulary (surface→post, part→surface, board→workspace). -
13e6a14: Each surface footer gains an open-as-image action that opens the surface rendered as a PNG (
/s/:id.png). The image is captured by Cloudflare Browser Rendering, so the action is live only on a Workers deployment; on a plain Node server it is shown but disabled, with a tooltip pointing at the README. The embeddable engine learns the capability through a new host field (SideshowHost.screenshots);createApp({ screenshots })surfaces it to the self-hosted viewer viawindow.__SIDESHOW_SCREENSHOTS__, and the Workers entry sets it (the Node entry leaves it off). -
80cc684: Adopt the post / surface vocabulary throughout the viewer engine and the host contract. The canonical hierarchy is workspace ▸ session ▸ post ▸ surface: a post is the published artifact (an ordered list of surfaces), and a surface is one block inside a post.
This is an internal rename of the viewer's local identifiers, component names, props, CSS classes, and user-visible strings — behavior is unchanged and all wire paths, query keys (
?part=), SSE event types, and server-provided JSON field names are kept byte-identical for compatibility. The block component files were renamed (ImagePart→ImageSurface,JsonPart→JsonSurface,TracePart→TraceSurface), and the server helpersurfaceParts.tsis nowpostSurfaces.ts(coerceSurfaceParts→coerceSurfaces,validateSurfaceParts→validateSurfaces).Host-contract change (embedders must update): the host identity key
identity.accountSlugis renamed toidentity.workspaceSlug. Any embedder passingaccountSlugon the injected host'sidentitymust rename it toworkspaceSlug.
-
138dafe: Add
sideshow --version,-V, andversionsubcommand. Prints the installed version and checks the npm registry for updates (best-effort, 3 s timeout, 24 h disk cache). -
5dfcb82: Held SSE (
/api/events) and long-poll (/api/comments?wait=N) connections are now bounded per workspace. Both are GETs that pin a socket open and, on apublicReadboard, are reachable unauthenticated — without a ceiling a flood could exhaust connections (cleanup was already correct, there was just no limit). Once overmaxHoldConnections(default 32, configurable viaAppOptions), new held connections return503; an instant?wait=0read still succeeds since it doesn't hold a slot. Slots release exactly once on stream/request abort or normal return. The default is sized for the real concurrency of a single-user workspace — a few viewer tabs (one SSE each) plus active agent long-polls, including a multi-agent session with several agents connected at once — since one workspace is one user; a real flood is orders of magnitude bigger, so the cap rejects it regardless of the exact default.Also indexes the referenced-asset set used by
/a/:id's optimistic-read wait and asset eviction: it was re-parsing every post'ssurfaces+historyJSON on each call (a full-table scan on every/a/:idmiss), and is now built lazily and maintained incrementally on post create/update and invalidated on remove. History is append-only, so an asset id once referenced stays referenced until its whole post is deleted — the cache stays correct without re-scanning. -
faa1322: The
jsonandcodesurface kinds (publishable over the CLI and REST since they were added) are now advertised by the MCP tools too. Both the streamable HTTP (/mcp) and stdio MCP transports listjsonandcodein theirpublish_post/update_post(and the deprecatedpublish_surface/update_surfacealiases)kindenums and document their fields (datafor json;code/language/title/lineStartfor code), so an MCP agent can publish a collapsible JSON tree or a syntax-highlighted code block — not just CLI/REST callers.To stop the surface-kind list from drifting between tiers again, all three surfaces now derive from one canonical
SURFACE_KINDSlist inserver/types.ts: theSurfaceKindtype, both MCPkindenums, and a newtest/mcpSpec.test.tsguard that fails if any kind is missing from the MCP schemas or the runtime validator. -
3177e5b: Improve the mobile viewer layout for phone-sized screens, including sidebar ergonomics, native surface primitives, and timeline trace readability.
-
67a9681: Adopt the post / surface vocabulary across all human-readable text: the design/how-to guides (
guide/*.md,AGENTS.md), the CLI help, usage, and user-facing messages (bin/sideshow.js), and the comments and non-wire strings inserver/*and the residual viewer comments. The canonical hierarchy is workspace ▸ session ▸ post ▸ surface: a post is the published artifact (an ordered list of surfaces), a surface is one block inside a post, and the tenant is a workspace. This is prose and CLI-help only — no behavior, API, route, query-key, SSE-event, MCP-tool-name, or identifier changes. All wire-bound strings (/api/surfaces, thepartsbody key,?part=,surface-created/updated/deleted, the deprecated MCP tool aliases, thestatus boardkit,--surface) are kept byte-identical, and the CLI keeps every endpoint and subcommand it has today (a new--postflag onsideshow commentis added alongside the existing--surface/--snippetaliases). -
76a9976: Move the default data directory from the package-relative
<package-root>/data/to a user-owned~/.sideshow/. The package-relative default was read-only undersudo npm install -g(crashing withEACCESeven after the #157 mkdir guard) and was wiped on everynpm install -gupgrade — silent data loss.~/.sideshow/is always writable and survives reinstalls. A one-time migration copies any existingsideshow.{db,db-wal,db-shm,json}from the old location to the new one on first boot (only when using default paths;SIDESHOW_DATA/SIDESHOW_DBoverrides are unchanged and skip the migration). -
61b57f2: Fix first-run crash when the SQLite db path's parent directory does not exist.
node:sqlitedoes not create missing parent directories, so the default<package-root>/data/sideshow.dbpath (nodata/shipped in the package) failed withERR_SQLITE_ERROR: unable to open database fileon a freshnpx sideshow serve.createSqliteStoragenowmkdirSync(dirname(path), { recursive: true })before opening, guarded by the existing:memory:check so the in-memory contract suite is untouched. A user-suppliedSIDESHOW_DBpointing into a not-yet-created directory now works too. -
da74857: Submit-time validation now rejects diffs and mermaid diagrams that won't render, not just ones with the wrong shape. A
diffpart whosepatchparses to zero files (e.g. a hunk without---/+++headers) returns a400fromPOST /api/surfacesandPUT /api/surfaces/:idwith the parse error. Amermaidpart whose source fails to parse also returns a400; the parser (@mermaid-js/parser, the official mermaid-js extraction) covers the 15 Langium-migrated diagram types (pie, gitGraph, architecture, radar, treemap, wardley, …) — types still on Jison (flowchart, sequence, class, state, er, gantt) skip validation and fall back to the viewer's existing graceful render-failure UI. MCP tool calls (loose mode) drop the invalid part instead of publishing a broken card.
-
f8fb7b3: Viewer: the sidebar wordmark is now a home link. Clicking "sideshow" (in the aside, or the mobile topbar) clears the current session and returns to the session-less base route — a guaranteed way back to the board from anywhere. It's a real
<button>, so it's keyboard- and screen-reader-reachable. The newgoHome()always asks the host to navigate (it never short-circuits on the engine's own selection), so an embedding host that layers its own view over the board — e.g. sideshow cloud's full-page Settings, which has no session rows to click out of on an empty board — gets a reliable exit through the same click; the host dedupes a no-op move. Self-hosted behaviour is otherwise unchanged. -
4822f77: Embeddable engine: add
onReady?()to theSideshowHostcontract and stop flashing the empty-board onboarding before sessions load. On mount the board has no sessions yet, so it rendered#onboard(the "setup" pane) until/api/sessionsresolved, then swapped to a session — a visible flash. The onboarding pane is now gated behind a first-load signal so neither pane is decided before that fetch returns, and the engine callshost.onReady()once it resolves and the board is decided. An embedder (e.g. sideshow cloud) holds its loading overlay until then so its users never see the pre-load flash; it fires even if the fetch failed (the board falls back to onboarding), so an overlay can't get stuck. Optional: the trivial self-hosted host omits it — self-hosted simply no longer flashes onboard. -
760320f: Embeddable engine: add
onThemeChange?(tokens)to the Host contract. The engine now PUSHES its fully-resolved palette to the host on initial mount, on every live theme switch, and on an OS light/dark flip — symmetric withrouter.navigate. An embedder (e.g. sideshow cloud) mirrors those tokens onto its own chrome instead of scraping computed styles across the shadow boundary. Optional: the trivial self-hosted host omits it, so self-hosted behaviour is unchanged. -
38992d7: Embeddable engine: expose
layoutandreadonlyon theSideshowHostcontract. A host can now request the stream-only layout (layout: "stream"— no sidebar/session list, just the current session's stream) and hide write affordances (readonly: true) without relying on the self-hostedwindow.__SIDESHOW_*globals. Self-hosted public-read "session" links keep mapping to the stream layout, so that flow is unchanged. -
23be3a1: Link unfurl / inline preview support. Bare
/s/:idURLs now serve the viewer shell with Open Graph and Twitter Card metadata, so pasting a surface link into Slack, Twitter/X, Discord, or iMessage renders an inline preview card. Theog:imagepoints to/s/:id.png?card=1, which captures a fixed 1200×630 social-card screenshot. Metadata uses only the surface title and a static description — no tokens or session context are leaked. -
12bb6b4: Embeddable engine: add a
ss:mainhost-overridable slot (SLOTS.main) wrapping the whole main content pane (onboarding + session stream). Its fallback is the engine's normal board, so a plain embed and self-hosted sideshow are unchanged. Unlike the always-on footer/empty/session-action overrides, this one is meant to be projected conditionally: an embedder (e.g. sideshow cloud) projects aslot="ss:main"child only while its own full-pane view is active — taking over the main area while the sidebar (session list, account footer) stays — and the engine falls back to the board when the child is gone. -
bd8df08: Screenshot surfaces as PNG by appending
.pngto any surface URL (e.g./s/:id.png). Uses Cloudflare Browser Rendering to capture the rendered page. Supports?mode=dark|light,?theme=,?w=(width), and?nocacheparams. The viewer persists the user's OS color-scheme in a cookie so screenshots automatically match their light/dark preference. -
e924954: Rename the data model: the published artifact
Surface→Post, and its blocks (SurfacePartand the*Partvariants) →Surface. The block fieldparts→surfaces, and comment linkssurfaceId/surfaceTitle→postId/postTitle. Exported types,Storemethods (listSurfaces→listPosts, …), and helpers (htmlPart→htmlSurface,MAX_BOARD_ASSET_BYTES→MAX_WORKSPACE_ASSET_BYTES,BoardSnapshot→WorkspaceSnapshot) are renamed to match — a breaking change for library consumers importing these names.SQLite boards migrate in place via a new idempotent
migrateToPosts()(tablesurfaces→posts, columnparts→surfaces, comment columns renamed, history blob re-keyed), mirroring the JSON store's read-time shims. Existing data is preserved.Wire: full-object reads
GET /api/surfaces/:idandGET/POST/api/commentsnow emit the renamed fields (surfaces,postId/postTitle). Route paths and MCP tool names are unchanged in this release. -
eb2001d: Embeddable engine: add a
ss:session-actionshost-overridable slot (SLOTS.sessionActions) in the session header, beside the stream/timeline toggle. It is empty by default — self-hosted renders nothing there — so an embedder (e.g. sideshow cloud) can project session-scoped controls such as a "Share" button into the engine's own chrome without forking the viewer. -
9da948d: The local Node server now stores data in SQLite (via the built-in
node:sqlite) by default — the sameSqlStorethe Cloudflare Durable Object deploy runs, so local development mirrors production over one storage code path instead of a separate JSON file. On first SQLite boot an existingsideshow.jsonboard is migrated in once automatically (sessions, surfaces, version history, comment ordering, and assets preserved); the JSON file is left untouched as a backup, and the import never runs again or overwrites a non-empty database.This also fixes the JSON store's scaling cliff — it rewrote the entire file (assets base64-inlined) on every write — since assets are now per-row BLOBs.
Both stores now strip embedded NUL bytes from stored text (titles, comments, trace labels, settings) so they behave identically — SQLite would otherwise truncate a value at the first NUL while the JSON file preserved it.
Configuration:
SIDESHOW_STORE=jsonkeeps the legacy single-file JSON store;SIDESHOW_DBsets the SQLite file path (defaultdata/sideshow.db);SIDESHOW_DATAstill names the JSON file and doubles as the migration source. Thesideshow/serverpackage now also exportsSqlStoreandcreateSqliteStoragealongsideJsonFileStore. -
f5e89d7: Direct links to a surface open a full-page standalone view again. Visiting a bare
/s/:idURL now shows just that one surface — its title and parts, no sidebar, session feed, or comment thread — with a small "made with sideshow" watermark beneath it, instead of resolving the link into its session's stream. The parts still render in the same sandboxed iframes the board uses (sized by the same resize bridge), and the link keeps its canonical/s/:idURL. Link-preview metadata from the bare route is unchanged. -
5436598: Embeddable engine: publish the theme-token contract as data via a new lightweight
sideshow/theme-tokensentry (also re-exported fromsideshow/viewer-embed). It exportsTHEME_TOKEN_NAMES(the coarse subset of palette vars a host mirrors), theThemeTokenstype, andTHEME_DEFAULTS(the default theme's built-in light/dark values, derived from the theme registry — never hand-copied). A host (e.g. sideshow cloud) can now consume the token names and no-flash fallback colors as typed data instead of copying hex by hand, so the two design systems can't silently drift. The/theme-tokensentry is engine-free and Node-safe, so build scripts can read it without pulling in the viewer runtime.
-
b60c9a2: Cap the asset-upload body while streaming so a chunked request can't OOM the server.
POST /api/assetsrejected oversize uploads by theirContent-Lengthheader, then read the rest witharrayBuffer()— but a chunked upload sends noContent-Length, so the header check was skipped and the entire body was buffered into memory before any size check. On a board reachable beyond localhost (and the local default has no auth token), that's an unauthenticated out-of-memory vector. The body is now read through a capped reader that stops at the same limit, so an over-cap stream is refused with a 413 without being buffered first. The post-decode cap inuploadAssetis unchanged. -
eb269b5: Cap every request body so an oversize JSON or MCP payload can't OOM the server. The previous fix bounded
/api/assets, but every other write endpoint (/api/surfaces,/api/comments,/api/sessions, the trace ingest,/api/theme) and/mcpstill read their body with an unboundedc.req.json()— so the same unauthenticated out-of-memory vector was reachable by POSTing a giant JSON body instead (the local default has no auth token). A globalbodyLimitnow rejects any request body over a generous ceiling with a 413, short-circuiting on an oversizeContent-Lengthand otherwise aborting the stream at the cap so a chunked body can't slip past. It runs after auth (unauthenticated requests on a token board are refused before their body is read) and exempts/api/assets, which streams its own stricter cap. -
ff217bf: The pi extension's tool schema now accepts the
mermaidsurface part kind. It was omitted from the extension'skindenum when mermaid landed, so a pi agent publishing{kind:"mermaid", mermaid:"..."}hit a validation error (parts.0.kind: must be equal to one of the allowed values) even though the server, MCP spec, and CLI already accepted it. The extension schema now mirrorsmcpSpec.ts. -
c8f7c68: Tighten input validation at the edges:
- Malformed base64 in an asset upload (REST
/api/assetsand theupload_assetMCP tool) now returns a clean 400 instead of surfacing a raw decode error as a 500. - Comment text and surface/session titles are capped (8 KB / 500 chars) before they ride the feedback channel back to the agent, so one oversize value can't bloat the agent's context on every poll.
- The CLI's
--afterflag (wait,watch) now fails fast on a non-numeric value instead of silently ignoring it.
- Malformed base64 in an asset upload (REST
-
c04a9ac: Mermaid diagrams now fully re-theme on a light/dark flip. The renderer drove mermaid's
basetheme from the design tokens but left mermaid to derive the rest, so colors it computes itself stayed stuck in light mode — most visibly arrowheads (derived from a hardcoded light canvas) kept their dark fill while the edges they cap flipped. The renderer now passesdarkModeandbackgroundand pins the previously-derived arrow/text colors to the viewer's tokens, so every element tracks the active scheme. -
58c515f: Validate the
openLinkscheme host-side so a surface can't ask the viewer to open a non-http(s) URL. The in-frame click handler only forwardshttp(s)hrefs, but a surface script can callopenLink()directly — or post the bridge message raw — with any scheme (javascript:,data:,file:), and the host opened it after a confirm without re-checking.noopeneralready kept those from reaching the board, but the host now refuses anything that isn'thttp(s)://outright, matching the documented "external link" contract. -
6e3c1b6: Refresh two README surface-gallery examples so each shows off what its part is for: the
htmlexample is now a shadcn/ui-style ecommerce products data table (filters, status badges, row selection, pagination) instead of a node-flow diagram, and theimageexample is a designed SaaS billboard ad instead of a before/after bar chart. Updatesscripts/surface-examples/*and regeneratesdocs/surfaces/{01-html,06-image}.png; no runtime or API changes. -
134a926: Reserve the
usercomment author so surface content can't impersonate the user to the agent.author:"user"was a forgeable label trusted as a security signal: a surface's script could callsendPrompt()(or post the raw bridge message) with no user interaction, and the result became anauthor:"user"comment indistinguishable from one the user typed — laundering untrusted content rendered in a surface into instructions delivered to the agent through the feedback loop. Nowuseris minted only by the viewer's composer (genuine keystrokes in the trusted origin): surfacesendPromptposts anauthor:"surface"thread message that is never delivered through the feedback channel, and the HTTP MCPreply_to_usertool coercesauthor:"user"to"agent"so the agent can't claim it either. The impersonation is now structurally impossible rather than gated. -
bd3ea88: Fix rich parts (markdown/code/diff/terminal) that intermittently rendered blank or clipped on reload under a Chrome 149 field trial, by rendering them server-side and serving each from
/s/:id?part=Nby real URL — the same opaque-origin, real-navigation load path html parts already use, which the field trial doesn't break (it defers layout only for in-memorysrcdoc/blob:documents). Rich documents render with shiki, @pierre/diffs, markdown-it, and ansi_up on the server (no DOM/WASM, so they run on the Worker too) under a tightsandboxCSP response header with noconnect-srcand no CDN script source. Mermaid, which needs a DOM, instead emits a self-rendering document that loads mermaid from the CDN inside the sandbox. Versioned, themed/s/:idresponses are immutable, so they now carry a long-livedCache-Controland an in-memory render cache. Removes the viewer→serverPOST /api/frames→/f/:idround-trip and transient frame store the previous workaround added, and drops mermaid and shiki from the viewer bundle. -
3752061: Sandbox the
/s/:idsurface document with a CSP response header, so agent script can never run in the board origin even on a top-level load. The viewer embeds surfaces in asandbox="allow-scripts"iframe (opaque origin), but the document is served from the board's own origin — so opening/s/:iddirectly (a user choosing "open frame in new tab", an agent-shared link) ran the agent's script in the board origin, where it could reach same-origin storage orwindow.open()the real viewer. Asandboxdirective can only be set as a response header (not the page's meta-tag CSP), and now forces the same opaque-origin sandbox however the document is loaded:allow-scriptsso the bridge still runs, neverallow-same-origin. Mirrors the iframe's own flags. -
57829c0: Fix an auto-resize feedback loop that could pin a CPU core. A sandboxed surface reports its content height to the host, which sizes the iframe to match; when the content's height inverts with the frame height (a scrollbar that toggles at a threshold, a 100vh/percentage layout), sizing the frame changes the content height back, so reports alternate A, B, A, B… once per frame. The old
h !== lastHguard couldn't catch a 2-cycle, and on a heavy syntax-highlighted surface each relayout was expensive enough to sit at 100% CPU until the surface unmounted. The height reporter now remembers the previous height and drops a rapid return to it (< 250ms), breaking the loop while still honoring genuine changes (a<details>toggle, a textarea drag) that recur on a human timescale. -
7f86b13: Harden share-link secrecy: session and surface ids are now 11 url-safe base64 characters — 8 random bytes, ~64 bits, YouTube-video-id sized — instead of a UUID's first 32-bit segment. In
publicReadmode these ids double as bearer capabilities (/s/:idand/api/{sessions,surfaces}/:idare reachable without the board token), so a 32-bit id (~4e9) was enumerable; 64 bits (~1.8e19) is far past sweepable. Existing ids keep working — nothing validates id shape — so only newly minted ones change. (Asset ids are a separate content hash and were already unguessable.)
-
ee75c8d: Add an embeddable viewer engine.
mountViewer(el, host)(the newsideshow/viewer-embedexport) renders the viewer into a shadow root with its own runtime, reading its base path, route, and theme from an injected host instead ofwindow/location— so a host application can own the page shell and URL while embedding the viewer. The self-hosted page is unchanged: it now uses a trivial default History-API host and behaves identically. -
a4033cf: Add host-overridable slots to the embeddable viewer engine. Two layout regions that carry deployment-specific guidance — the empty-board onboarding and the sidebar footer's instructional links — are now wrapped in named
<slot>s whose fallback content is the existing self-hosted markup. An embedder projects light-DOM children with a matchingslot=attribute into the mount element to replace a whole region; with nothing projected (and self-hosted, outside a shadow root) the fallback renders unchanged, so self-hosted parity is preserved. The newSLOTSregistry andSlotNametype are exported from the embed entry andembed.d.tsso embedders share one typed source of truth. -
91db9a3: Add hosted wrapper seams, including injectable public base-path support for deployments mounted below an origin root while preserving default self-hosted routes.
-
3f45e76: Add
jsonandcodepart kinds for surfaces.-
json— a pre-parsed JSON value rendered natively by the trusted viewer as a collapsible tree. Objects and arrays expand/collapse on click; primitives show inline with type-colored values (strings, numbers, booleans, null). Reach for it for API responses, config files, test results — any structured data where a tree beats a fenced code block. Like image/trace it is data, not markup: the viewer renders it with escaped text nodes, so no sandbox is needed. -
code— source code highlighted with shiki (the same highlighter as markdown fenced code blocks), rendered in a sandboxed iframe with line numbers, an optional filename header, and a copy button.languageis a shiki lang id;titleis a filename shown in the header;lineStartshows original line numbers for excerpts ("lines 80-150 of x.ts"). CLI:sideshow code app.ts --title "app.ts" --line-start 80.
Also extracts the shared shiki highlighter into
viewer/src/highlight.tsso MarkdownPart and CodePart share one lazy-loaded highlighter. -
-
ddbfab2: Add
SIDESHOW_PUBLIC_READenv var for public read-only access to deployed boards. Set tosessionfor unlisted-link style sharing orfullto expose the entire board read-only.
- 5e3f292: Fix invisible markdown/mermaid/diff/terminal surfaces caused by a Chrome field trial that breaks layout measurement in opaque-origin srcdoc iframes. The viewer now retries the srcdoc parse after 2 seconds if the iframe is still stuck at minimum height.
- c2e4443: fix(viewer): honor
lineStartin code-part gutter numbers. The code part's range label already reflectedlineStart, but the gutter still counted from 1 — shiki emits<pre class="shiki …" style="…">, which the counter-reset injection didn't match. The starting line number now applies, so excerpts render at their original line numbers. - 3c56cc1: Deep links to
/session/:id/s/:surfaceIdnow scroll to the target surface card instead of showing the session from the top. - 84e7057: Reject oversize asset uploads before buffering the body into memory. The /api/assets handler previously read the entire request body before checking the 5 MB cap, so a multi-GB upload could exhaust Node's heap on
sideshow servebefore the 413 fired. - 6962019: Fix cursor lag in
waitForComments. ThelastSeqreturned to the caller and theagentSeqcursor were both derived from the filtered (author-matched) comment list, not the full list. When an agent reply landed after the last user comment, the cursor stayed behind the agent's seq, so every subsequentauthor=usercall re-read the agent's own comment, filtered it out, and advanced in a wasted round-trip. BothlastSeqandmarkAgentSeennow use the last seq from the unfiltered list, mirroringcollectFeedback. - a18f210: Fix surface iframes rendering in the wrong color scheme when it diverges from
the chrome (e.g. dark chrome with a white, light-inked html part). Light/dark
was resolved independently in every layer purely from the OS
prefers-color-scheme, but a surface part is a separate iframe document whose scheme resolution can diverge from its embedder across the frame boundary. The viewer now resolves the scheme once and pins each sandboxed frame to it — html parts via amodequery param on/s/:id(with a forcedcolor-scheme), and markdown/code/comment frames viarenderSandboxedPart— so a frame always matches the chrome instead of re-deriving the scheme on its own. The theme tokens, the kit's teal/coral SVG accents, and shiki's dark flip are all pinned together. With no mode passed the OS media query is kept, so self-hosted parity is preserved. - f0c6cd4: Fix a lost-update race in
SqlStore.updateSurface. Two concurrentPUT /api/surfaces/:idcalls could both read the same version, push a duplicate history entry, and write the same version number — silently losing one caller's parts. The fix uses compare-and-set (WHERE id=? AND version=?) withSELECT changes()to detect whether the update landed, retrying with the current version if it lost the race. - db463ce: Improve intro readme.
Sideshow 0.6 focuses on richer surfaces, safer rendering, and better agent setup.
- Timeline traces. Sessions can show the prompts, reasoning, and commands behind a surface. Claude Code users can install the Stop hook with
sideshow install-hook, or runsideshow trace-syncmanually. - Themes. The board now has seven light/dark theme presets — GitHub, Gruvbox, One, Solarized, Catppuccin, Rosé Pine, and Everforest — applied across viewer chrome, html tokens, markdown/diff highlighting, mermaid, and terminal parts.
- Mermaid parts. Agents can publish Mermaid diagrams directly with
sideshow mermaid,--mermaid, MCP, or HTTP. - HTML kits. Opt-in
issuesandslideskits give agents ready-made, theme-aware building blocks for issue trees, status boards, and decks. Discover them withsideshow kitsorGET /api/kits. - Deep links. Viewer URLs now track the current session and surface (
/session/:id,/session/:id/s/:surfaceId), including back/forward navigation. - Pi extension. Installing the package in Pi adds native
sideshow_*tools for publishing, updating, uploading assets, waiting for feedback, replying, listing surfaces, and fetching the guide.
- Added
/agent-howtoandsideshow agent-howto; the bundled skill/setup block is now a small bootstrap that asks the running server for current Sideshow guidance. - Changed the default local server from
http://localhost:4242tohttp://localhost:8228. - Sidebar sessions now show agent logos, a surface count, and cleaner metadata.
- The card comment footer is flatter and quieter.
- Comments now always attach to a surface;
sideshow commentrequires--surface. - The agent design guide is shorter and frames kits/theme tokens as optional scaffolding, not a required house style.
- Markdown, Mermaid, diff, terminal parts, and comment text now render inside opaque-origin sandboxed iframes, matching html parts and reducing the impact of sanitizer regressions.
- The viewer only accepts host-affecting
postMessageevents from frames it embedded. - Added focused unit and e2e coverage for sandbox isolation, themes, kits, trace ingest, and cross-channel feedback delivery.
- Surfaces: a published card is now an ordered list of parts, not a single HTML
blob. New part kinds render natively in the viewer alongside sandboxed
html:diff(a syntax-highlighted code review from a patch),markdown(prose with highlighted fenced code),terminal(monospace output with ANSI colors), plusimageandtrace. Publish any of them across all three tiers — MCP (publish_surface/update_surface),POST /api/surfaces, and the CLI. - Uploads: push images, traces, and files across all three tiers (
POST /api/assets, theupload_assetMCP tool,sideshow upload/image/trace). Assets are content-addressed by SHA-256, identical uploads dedupe, and an asset lives as long as any surface references it. Capped at 5 MB each. - A Claude Code plugin (
plugin/, via a repo-hosted marketplace) bundles the MCP server, the skill, and a background monitor — browser comments arrive in the agent as notifications without pasting or re-arming a watcher. Install with/plugin marketplace add modem-dev/sideshowthen/plugin install sideshow@sideshow. sideshow watchstreams user comments to stdout, re-arming the long-poll forever (exactly-once across watch, wait, and piggyback).- A "connect Claude Code" link in the viewer opens an integrations panel with the plugin install commands and caveats.
- A copy button on each comment puts an agent-ready paste block (surface title + id + comment) on the clipboard.
- The npm package exposes a stable
sideshow/serverentrypoint so integrations can reusecreateApp/JsonFileStorewithout importing private internals.
- Snippets are now "surfaces" throughout the API (
/api/surfaces,surface-*SSE events, comments keyed bysurfaceId). The old snippet endpoints andpublish_snippet/update_snippettools remain as back-compat aliases; stored boards migrate in place on load. - The session sidebar groups sessions by recency (Today / Yesterday / Earlier), and sessions with no surfaces yet are dimmed and sunk to the bottom.
- The viewer is framed around leaving comments rather than messaging an agent — composers read "Leave a comment…", with no delivery receipts or "listening" indicators.
- A surface card's open and delete actions are now minimal Lucide icons.
sideshow-termauto-starts a local server when needed (baresideshow-termopens the watcher, default port 4243), supports mouse input, and gainsclear/clear --all.
sideshow-termhardens STML parsing/rendering for untrusted markup (tested entity decoding, bounded size/depth, neutralized control characters, render failures degrade to an in-view error).- Local JSON storage shares a single cold-load promise across concurrent first requests, fixing a race that could overwrite persisted board data.
- The viewer shows a neutral "refresh sideshow to update the viewer" hint for an unrecognized part kind instead of a broken-diff error.
- Malformed
POST/PUT /api/surfacespart payloads are rejected before they reach storage. sideshow-termcan be packaged and installed standalone (declared server dependencies, reuses thesideshowserver core, runs built JavaScript).
- Cmd+Option+Up/Down switches between sessions in the viewer without reaching for the sidebar — Down moves to the next session in the list, Up the previous, wrapping at the ends.
- The viewer notices new releases: a dismissable banner in the sidebar names
the latest version with a copyable upgrade command (npm install locally,
redeploy for workers), and the release notes render as a card at the top of
the stream. Dismissing either hides both until the next release. The check
lives server-side at
/api/version(npm registry + GitHub release notes), is cached for six hours, and fails silently — offline costs nothing but the absence of the notice. - The CLI now runs on Windows: session detection walks the process tree with a
single PowerShell call instead of
ps, andsideshow serve --openlaunches the browser viacmd /c start. macOS and Linux are unchanged.
- A session thread at the bottom of each session in the viewer: a composer for messaging the agent without picking a snippet.
- Feedback now reaches agents without polling: publish/update/reply responses
carry a
userFeedbackarray with any comments the user left since the agent's last call (delivered once; a consumedwaitalso counts as seen). - The design guide, setup block, and Claude Code skill teach the background
watch pattern: arm
sideshow waitas a background process after publishing and react when it exits, instead of blocking or polling. - Agents can name their session at creation:
sessionTitleon the publish body and both MCPpublish_snippettools,--session-titleonsideshow publish. Applied only when the publish creates the session — it never overwrites a title, including renames made in the viewer. - A snippet kit baked into every snippet doc, so agents publish compact
markup instead of hand-written inline CSS: bare
button/input/select/textareapre-styled to match the viewer, SVG utility classes (t/ts/thtext presets,box,arr,leader,node,c-*color ramps with dark-mode-aware text), and a shared#arrowmarker injected into every doc. The design guide documents it as a compact reference table.
- New snippets no longer steal the scroll position: the viewer only follows them when already at the bottom of the stream, and shows a "new snippet ↓" pill otherwise.
- Activity the user isn't looking at — another session, or any session while the tab is hidden — badges the tab title with an unread count.
- The Claude Code skill now documents the repo-local CLI fallback and a checkpoint-drain feedback pattern for harnesses that cannot surface background watcher output.
- Feedback was re-delivered when channels were mixed: a fresh
sideshow waitprocess (or restarted stdio MCP server) started from seq 0 and replayed comments the agent had already received via piggyback or another channel.author=usersession reads with no explicitafternow resume from the server-side agent cursor, and the CLI and stdio MCP keep no cursor of their own — delivery is exactly-once across CLI, MCP, and piggyback. Pass--after <seq>(CLI) orafterSeq(MCP at/mcp) to deliberately re-read. - A comment that failed to send was silently lost (input cleared, no error). The viewer now echoes comments immediately (pending until confirmed) and on failure restores the text to the input with an error toast.
- After an SSE reconnect the viewer refetches the selected session, so snippets and comments that arrived during the gap can no longer be silently missing from a live-looking board.
- The viewer layout no longer breaks at phone widths: below 700px the sidebar collapses into a drawer behind a slim top bar (hamburger toggle, unread dot), the stream takes the full width, and hover-only actions (card open/delete, session delete) stay visible on narrow or touch screens.
- Comments not attached to a snippet (e.g.
sideshow commentwithout--snippet) were stored and delivered to agents but never shown in the viewer; they now render in the session thread. - The viewer is now usable by keyboard and assistive tech: session rows are focusable and activate with Enter/Space (focus survives live re-renders), hover-only actions (session delete, card open/delete) are reachable and shown on focus, the editable session title is labeled and Escape cancels an edit, snippet iframes carry the snippet title, and toasts are announced via a polite live region.
--help/-hon CLI subcommands (sideshow publish --help, …) printed a raw parseArgs stack trace; it now prints the usage text and exits 0. An unknown option or missing option value likewise fails with a one-line error and asideshow helphint instead of a stack trace.- Following the README quick start from a git clone failed:
npx sideshow serveexited withviewer build missingbecause nothing built the viewer.npm installin the repo now builds it (the published npm package was unaffected —prepackalready ships a built viewer).
sideshow demoseeds two example sessions (a sequence diagram with a comment thread, an interactive explainer, a metrics card) so the viewer can be explored without an agent.
First release.
- Initial release: live preview surface (Hono server + single-file viewer) with sessions, versioned snippets, and comment threads.
- Zero-dependency
sideshowCLI:serve,publish,update,wait,comment,list,sessions,guide,setup. Sessions resolve automatically per agent conversation. - Stdio MCP server with
publish_snippet,update_snippet,wait_for_feedback,reply_to_user,list_snippets,get_design_guide. - Long-poll feedback endpoint (
GET /api/comments?wait=N) so terminal agents can block on user comments without extra infrastructure. - Agent design contract served at
/guide; paste-able AGENTS.md integration block at/setup. - Sandboxed snippet rendering (
sandbox="allow-scripts", CSP CDN allowlist) with light/dark theme CSS variables and asendPrompt/openLinkbridge. - Cloudflare Workers deployment (
npm run deploy): the whole app runs in a Durable Object with SQLite storage; local and cloud are the same product behindSIDESHOW_URL+SIDESHOW_TOKEN. - Built-in MCP over streamable HTTP at
/mcpon every server (local and deployed) — agents can connect without any local process. - Token auth for deployed instances: bearer header for APIs,
/?key=<token>cookie flow for the viewer;/guideand/setupstay public. - Claude Code skill at
skills/sideshow/teaching agents the publish → feedback → iterate workflow.