Skip to content
Serelane

Embedding

A self-contained bundle with no framework runtime, rendering into a shadow root. Drop it into any page:

<div id="mount"></div>
<script
	src="https://roadmap.example.com/widget.js"
	data-project="demo"
	data-variant="latest"
	data-target="#mount"
	defer
></script>
Attribute What it does
data-project The project slug, as it appears in /p/<slug>. Required.
data-variant latest for recent updates, top to lead with the vote count, or suggest for a prompt that links to the board.
data-target A selector for the element to render into.

There is deliberately no integrity hash on that tag. Subresource Integrity is the right tool when you load a script from someone else’s CDN, and the wrong one here: widget.js comes from your own instance, and its contents change with every Serelane upgrade, so a pinned hash would break every embed the moment you pulled a new image. The protection SRI would give you is the protection you already have from serving it yourself over TLS.

It renders into a shadow root, so your page’s selectors do not reach inside it. That is only half the problem, and the widget handles the other half too: a shadow boundary stops selectors but not inheritance, so a host page’s * { font-family: cursive !important } would otherwise inherit straight through it. Typography is declared inside the shadow root where nothing you write can reach it, and the host element’s own display is pinned with an inline declaration so an all: unset !important cannot flatten it.

In other words, it survives a hostile stylesheet. That is asserted by a test that embeds it in a page built to wreck it.

For hosts that will not run third-party script:

<iframe
	src="https://roadmap.example.com/embed/demo"
	style="width: 100%; border: 0"
	height="400"
></iframe>

It reports its own height to the parent, so you can size it to its content:

<script>
	window.addEventListener('message', function (event) {
		if (event.data && event.data.type === 'serelane:height') {
			document.getElementById('frame').height = String(event.data.height);
		}
	});
</script>

Framing is refused unless the host is one the project’s operator listed, so add your site in the project’s settings before embedding.

Every board publishes three, all from the same projection:

Format URL
RSS 2.0 /p/<slug>/feed.xml
Atom 1.0 /p/<slug>/atom.xml
JSON Feed 1.1 /p/<slug>/feed.json

They are advertised from the board’s own <head>, so a reader extension finds them without anybody being told a URL.

A feed is a good way to mirror your roadmap into a changelog page, a chat channel or a mailing list without writing against the API.

Public board data is readable cross-origin by any site, with no credentials. Acting as a visitor — voting, commenting — is confined to the origins the project’s operator listed, because that requires their session.

The two are never combined. A browser refuses that pairing anyway, and echoing back whatever Origin arrives while allowing credentials would make every page on the internet a trusted caller.

Private boards can issue bearer read tokens, which widen what may be read and nothing else.

See API for the contract.