Scorecard
Renders = a human sees the page, not its source. Origin = who serves the bytes. CSP = whether this project's Content-Security-Policy applies. Permanent = survives branch deletion and playground pruning.
| Target | Renders | Origin | Our CSP | Permanent | Verdict |
|---|---|---|---|---|---|
htmlpreview.github.io/?… |
yes | third party | no | yes | Rejected. Serves our HTML from an origin we do not control with none of our CSP. Was recommended in four places. |
blob/<branch>/docs/… |
source only | GitHub | n/a | no | Rejected. Dies when the head branch is deleted on merge. |
raw.githubusercontent… |
never | GitHub | n/a | yes | Cannot work. GitHub forces text/plain deliberately, as an anti-XSS measure. Safe and therefore unusable. |
blob/<SHA>/docs/… |
source only | GitHub | n/a | yes | Default for PR bodies. A SHA stays reachable via refs/pull/<N>/head forever. Trades rendering for not handing a third party our content. |
orchestkit.yonyon.ai/lab/… |
yes | ours | yes | yes | The real answer, for playgrounds worth keeping. Requires publishing to the Lab. |
Why there are two winners, not one
Tier 1 — ephemeral
docs/<branch-slug>/*.html
Attached to a PR to explain that PR. scripts/prune-playgrounds.sh archives these to the orphan playgrounds-archive branch and removes them from main once the branch is gone. They are meant to disappear.
→ link the SHA-pinned blob.
Tier 2 — curated
docs/site/public/lab/<slug>.html
Promoted deliberately, listed at /docs/showcase/lab, often with a case-study page. Served from our origin under the /lab CSP.
→ link the Lab URL.
The mistake this PR corrects is not "playgrounds are unpublished". It is that the guidance pointed at a proxy for tier 1, and never mentioned tier 2 existed.
Promoting to tier 2
1. add an entry to docs/site/lab-manifest.json
{ "slug": "...", "source": "docs/<dir>/<file>.html",
"title": "...", "description": "...",
"tags": ["..."], "date": "YYYY-MM-DD" }
2. node docs/site/scripts/generate-lab-data.mjs
→ copies the html into docs/site/public/lab/<slug>.html
→ regenerates lib/generated/lab-data.ts
The generator does the copy, so this is two steps rather than three. It also tolerates a pruned source by falling back to the committed public/lab copy, which is what makes tier 2 survive tier 1's deletion.
Response headers on /lab/*
Content-Security-Policy:
default-src 'self'
script-src 'self' 'unsafe-inline'
style-src 'self' 'unsafe-inline'
img-src 'self' data:
frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
X-Robots-Tag: noindex
noindex because the gallery page is the indexed front door, not the raw file. frame-ancestors 'self' so docs pages can embed it in a sandboxed iframe, which the global default of DENY would block.