Pages
The room’s Read more: briefings and architecture, from the CLI or the app.
On this page
Pages are what a viewer reads beside the demo: the narrative, a briefing, the architecture document. They are listed in the room's rail under Read more, open in a reader over the room, and the app shows you which ones each viewer opened and for how long.
This page covers the two kinds of page and what each may contain, how a page
gets its title, the limits, and the four ways to add, order, replace and
remove pages: pitch.json, the CLI, the app and Claude.
Markdown and HTML
A page is one file.
- Markdown (
.md), set in the room's own typography. GitHub-flavoured: headings, lists, tables, code blocks, quotes, links. Links open in a new tab. - HTML (
.html), shown as you wrote it, in a sandbox: it runs no script, loads nothing from the network, and takes its styles from inline<style>and its images fromdata:URIs. Make it self-contained.
Either way a page is a single file. Images, stylesheets or other files beside it on disk are not uploaded with it.
The sandbox
Every page, Markdown or HTML, is served from the room with a strict Content-Security-Policy and shown in a sandboxed frame, so it has no script, no network and no origin of its own. The room holds what viewers do and say, and a page is content, not code: anything a page carries is inert.
| A page can | A page cannot |
|---|---|
Use <style> blocks and style attributes |
Load a stylesheet with <link> |
Show images embedded as data: URIs |
Load an image, font or file from any address |
| Use system fonts | Load a web font, even as a data: URI |
Open a link in a new tab (target="_blank") |
Run JavaScript, inline or loaded |
Submit a form, or set a <base> address |
|
| Be framed by anything but its own room |
In full, the policy a page is served with is:
sandbox allow-popups allow-popups-to-escape-sandbox; default-src 'none'; style-src 'unsafe-inline'; img-src data:; frame-ancestors 'self'; base-uri 'none'; form-action 'none'Give links in an HTML page target="_blank". A link without it tries to open
inside the reader, which the room does not allow. Markdown pages get this for
you.
Raw HTML inside a Markdown page is kept, and is as inert as an HTML page.
Titles
A page's title is what the rail lists and the reader's bar shows, up to 80 characters.
- From
pitch.jsonandpitch pages add, it is the page's first# heading(Markdown) or its<title>(HTML), else its file name without the extension.pitch pages add --titlesets one of your own. - From the app, it is the title you type, else the file's name without its extension. The app does not read the heading.
- From Claude, it is the title Claude sends.
The title is also how a page is recognised again: adding a page with the title of one the room already has replaces that page (Replacing and removing).
Limits
| What | Limit |
|---|---|
| One page | 2,000,000 characters |
| A title | 1 to 80 characters |
Pages listed in pitch.json |
30 |
| File types | .md and .html; the app also takes .markdown and .htm |
Declared in pitch.json
List them under pages, relative to pitch.json:
"pages": ["docs/why-corner.md", "docs/corner-architecture.html"]pitch deploy --commit uploads every one before the build goes live, so a new
build never goes live without its pages: a page that fails to upload leaves
the previous build live. A page with the same title as one the room already
has replaces it, so deploying again updates the pages rather than listing each
one twice. pitch validate checks each file is there, inside the project, and
no longer than a page can be. pitch init lists what it finds in docs/ and
documentation/.
Deploying does not remove pages. A page you take out of pitch.json stays in
the room until you remove it.
From the CLI
Pages can be added and removed without deploying:
pitch pages list # the room's pages, in order, with their ids
pitch pages add docs/briefing.html # add a page
pitch pages add notes.md --title "Why now" # add one under a title of your choice
pitch pages add docs/briefing.html --replace # replace the page of the same title
pitch pages remove 3f1c9a52-8d0e-4b8e-9a51-2c7d0e6b1f4a # take a page out of the room
pitch pages push # upload the pages pitch.json listsUnlike pitch deploy, these act at once and are not rehearsed: a page is one
file, and each command prints what it did and the id that undoes it. The one
change that would lose something, a page's content replaced by another file
with the same title, is refused unless you pass --replace. Removing a page
listed in pitch.json says so, because the next deploy uploads it again.
Each takes --project <slug>; without it, the project is the one named in
pitch.json here. The project must exist: pitch deploy --commit creates it.
corner-for-bullring: 2 pages, in the room’s order
1 3f1c9a52-8d0e-4b8e-9a51-2c7d0e6b1f4a markdown Why Corner
2 a07b5e19-2f64-4c1d-8e3a-9b6d2c4f8e10 html Corner architecture Added "Why now" (markdown) to corner-for-bullring as page 6d2e4b80-1c3a-4f9e-b7d5-0a8c9e1f2b34.
Viewers of https://corner-for-bullring.getroomi.app see it now. `pitch pages remove 6d2e4b80-1c3a-4f9e-b7d5-0a8c9e1f2b34` takes it out. corner-for-bullring already has a page called "Why now" (6d2e4b80-1c3a-4f9e-b7d5-0a8c9e1f2b34). Nothing was changed.
`--replace` replaces its content with this file; `--title <title>` adds this one under another name.From the app, or from Claude
In the app, the project's Room tab has a Pages panel: the room's
pages in order, each with Up, Down and Remove, and Upload a
page, which takes a .md, .markdown, .html or .htm file and an
optional title. A page with the same title is replaced, as it is from a
deploy; the app does not ask first.
In Claude, the connector's add_page tool adds one. It takes the
project's id, a title, a kind (markdown or html) and the content,
with the same limits, and it is the same route pitch pages add uses. Claude
asks before calling it, since the page is visible to every viewer at once.
Through the API, a page is POST /api/projects/:projectId/pages:
{
"title": "Why now",
"kind": "markdown",
"content": "# Why now\n\nThree of your sites open before 06:00…"
}Ordering
The rail lists pages in the order the room holds them. A new page goes last,
and a replaced page keeps its place. Reorder them in the app's Room tab, with
Up and Down, or through the API, which takes every page's id once, in
the new order (POST /api/projects/:projectId/room/pages). A list that leaves
a page out, or names one of another project's, is refused rather than half
applied.
Replacing and removing
- Replacing keeps the page's id and its place in the rail, and changes its
content (and its kind, if the new file is the other kind). Viewers see the
new content the next time they open it. From a deploy, the app and Claude, a
page with a title the room already has is replaced without asking;
pitch pages addasks for--replace. - Removing takes the page out of the room and deletes its content. Do it
from the app's Remove, or with
pitch pages remove <id>. A page listed inpitch.jsoncomes back with the next deploy unless you take it out ofpagestoo.
What a viewer sees
A room with pages has Read more at the foot of its rail, one row per page
with its title and Read. Opening one shows it in a reader over the room,
with the title in its bar and Close (or Esc) to go back to the demo,
which stays as the viewer left it.
A page is the room's, not the build's: it stays when you deploy a new build or roll back to an earlier one, and it is there for every viewer of every link.
Who read what
Each time a viewer opens a page the room records it, and it counts the seconds the page is open and the tab visible, as it does for each app. On plans with analytics in full (Plans and limits):
- Activity shows each viewer's visits in order, including "Read Why Corner" at the moment they opened it, and where each visit ended, which can be a page.
- Analytics has Pages opened: opens per page across a date range.
- The Pipeline card's signals count the pages each viewer read.
On Free the platform sends total views only. Analytics and the pipeline covers the rest.
Examples
A Markdown briefing: the first heading becomes its title.
# Why Corner
Bullring's coaches see thirty members a day and remember each one. Corner
makes sure the app does too.
## What changes on Monday
| Today | With Corner |
|---|---|
| Check-ins on paper | Check-ins on the member's phone |
| Injuries in a notebook | Injuries flagged to HQ within the session |
Read the [architecture](https://example.com/corner) for how it fits together.A self-contained HTML page: its styles inline, its one image a data: URI,
its links opening a new tab.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Corner architecture</title>
<style>
body { font: 16px/1.5 system-ui, sans-serif; max-width: 42rem; margin: 2rem auto; padding: 0 1rem; color: #1b1b1f; }
h1 { font-size: 1.75rem; }
.box { border: 1px solid #d6d6de; border-radius: 8px; padding: 1rem; }
</style>
</head>
<body>
<h1>Corner architecture</h1>
<p class="box">One server, four apps under their own paths, and a mock API seeded fresh for every viewer.</p>
<img alt="The four apps around one API" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iNDAiPjxyZWN0IHdpZHRoPSIxMjAiIGhlaWdodD0iNDAiIGZpbGw9IiNkNmQ2ZGUiLz48L3N2Zz4=">
<p><a href="https://example.com/corner" target="_blank">The longer write-up</a></p>
</body>
</html>