# How a page works inside

This chapter is the technical model behind the examples: the addresses a page answers, what each one stores, and the commands an agent runs. The **ccc-pages** skill carries every detail; its `references/data-and-endpoints.md` and `references/publishing.md` are the two to read next. Every command below runs from a UCC seat, with `BIN="$CCC_SKILL_DIR/ccc-pages/bin"`.

## One slug, seven addresses

A page is named by its slug, a short stem plus 16 random hex characters (`order-sheet-5063bca214b7f486`). The random half is what keeps an unlisted page unfindable. Everything the page owns hangs off that one name on `https://page.sui.pics`:

| Address | What it is | Who writes it |
|---|---|---|
| `/a/<slug>/` | the page itself: one HTML file or a file set, in immutable versions | the owner, with `page-publish` |
| `/d/<slug>/<key>` | the store: one record per key | the owner with `page-data`; editors through a share link |
| `/d/<slug>/<key>?raw=1` | a record's body alone, in its own content type | read-only |
| `/d/<slug>/~<name>` | a computed view: a record the page's `handler.js` computes on each read | read-only |
| `/f/<slug>/<id>` | files: pictures, PDFs, exports | the owner, with `page-file` |
| `/i/<slug>` | the inbox: events posted by machines with the page's secret | anyone holding the secret |
| `/h/<slug>/…` | the page's own API, answered by `handler.js` | whoever the handler lets in |

## The store

`page-publish … --data on` turns the store on. A record is an envelope `{"schema":"<name>@<n>","data":…}` of at most 256 KiB, under a key of letters, digits, `.`, `_` and `-`. The schema is your own name for the shape (`metrics@1`, `table@1`); the service checks its grammar, not its content.

```bash
"$BIN/page-data" put "$SLUG" metrics --schema metrics@1 --data-file metrics.json --notify none
"$BIN/page-data" get "$SLUG" metrics --data-only
"$BIN/page-data" list "$SLUG" --prefix inbox. --values --limit 100
"$BIN/page-data" delete "$SLUG" metrics --notify none
```

Every write carries a version: a plain `put` retries at the newest version and reports that it overwrote someone else's write; `--if-version <n>` makes one attempt at the version you read and exits 4 when someone wrote first, for you to re-read and merge. A browser lists a prefix with `GET /d/<slug>?prefix=inbox.&values=1`.

## Raw reads

`?raw=1` serves a record without its envelope. A `raw@1` record, `{content_type, body, stale_after?}` with `content_type` `text/plain` or `application/json` and a body up to 64 KiB, is served as its body in its own type; that is how every section of this handbook is stored. A `raw@1` record past its `stale_after` answers `503 stale` instead of old content, which is how a quote expires on its own. Any other schema is served as its `data` in JSON.

## Files

`page-file upload <slug> <path>` stores the bytes and a `file@1` record at `f.<id>`; the file is served at `/f/<slug>/<id>` for as long as the page lives, and a large raster image also gets a 512 px thumbnail at `…/thumb`. Raster pictures, PDFs, plain text and JSON serve inline; other types, SVG included, download.

## Computed views

A page published as a file set can carry `handler.js` and, beside it, `handler.json`. A view is a function `view(name, params, records)` that returns a record; `reads(ask)` names the records it is handed. `GET /d/<slug>/~summary?customer=…` runs it on every read, with no network, no secrets and 50 ms of CPU. It reads the page's records under the reader's own access, so a view never shows a reader what they could not fetch themselves. `handler.json` declares which names are served: `{"views":["summary"]}`.

New handler code goes live in three moves, and never on the first:

```bash
"$BIN/page-publish" "$SITE" --slug "$SLUG" --data on    # handler.js + handler.json beside index.html
"$BIN/page-inbox" handler replay "$SLUG"                # runs the candidate over past events, prints its sha8
"$BIN/page-inbox" handler activate "$SLUG" --sha <sha8> # refuses a sha with no replay receipt
```

The same file can carry `fold(event, state, records)`, which shapes each inbox event before it is stored: keep it, drop it, or merge it into an earlier one.

## The inbox

`POST /i/<slug>` stores an event as the record `inbox.<seq>` when it carries the page's secret, in the `X-Inbox-Token` header (or GitHub's signature, or GitLab's token). The event name comes from `X-Inbox-Event`. A delivery id in `X-Inbox-Delivery` makes a retry safe: the same id twice is stored once.

```bash
umask 077; "$BIN/page-inbox" secret "$SLUG" | jq -r .secret > inbox.secret   # once per page; --rotate replaces it
"$BIN/page-inbox" send "$SLUG" --event machine.stop --delivery line2-0917 \
  --data '{"machine":"welder 3"}' --secret-file inbox.secret
"$BIN/page-inbox" list "$SLUG" --values --limit 100
```

The secret is shown once, when it is minted. It belongs in a file with mode 600 on the machine that posts, never on a page or in a record.

## The page's own API

When `handler.json` declares `request`, the handler's `request(req, ctx)` answers `GET`, `POST`, `PUT`, `PATCH` and `DELETE` on `/h/<slug>/…` and returns `{status, body}` as JSON. `ctx.identity` says who is calling, as the service found it: the owner, a share-link holder, or nobody. The handler alone decides what each may do — page visibility is not a gate on `/h/` — so an API that checks nobody is open to everyone with the link, on a private page too.

- `{"request":{"private":true}}` gives the handler `this.env.STORE`, a private range of the page's store that only the handler can read or write; `/d/` refuses it to every caller, the owner included.
- `{"request":{"tokens":["mint","revoke"]}}` lets the handler mint and revoke viewer share links for its own page, the way an invitation form would.
- A browser that holds a share-link cookie adds `x-ccc-auth: cookie` to every write.
- Limits: 100 requests per 10 seconds per page and caller address, request bodies up to 64 KiB, 5 seconds per call.

## Private pages and share links

`page-publish … --private` makes a page that answers `401 page_locked` to anyone without a grant, and a browser shows an unlock form. Make a page private at its first publish: what was public once may already be copied, and a page cannot be turned private later by editing its settings.

```bash
"$BIN/page-token" mint "$SLUG" --role viewer --label "Brightline Warehousing" --expires 30d
"$BIN/page-token" list --slug "$SLUG"
"$BIN/page-token" revoke <id>
```

`mint` prints a share link once. Its secret sits after the `#`, which a browser never sends to a server; opening it exchanges the secret for a cookie in that browser. Use one labelled token per person, so one revoke shuts out one person. A revoke takes effect within the hour. Roles are cumulative: a **viewer** reads; an **editor** also writes the store and the page's source; a **publisher** also publishes new versions and manages the page's share links.

## Versions and rollback

Every content publish is a new immutable version under the same slug; the address does not change. A metadata change (a new description) makes no version.

```bash
"$BIN/page-publish" --slug "$SLUG" --versions         # every version, which one is live
"$BIN/page-publish" --slug "$SLUG" --switch 3         # serve version 3 again: the rollback
"$BIN/page-publish" --slug "$SLUG" --delete-version 1 # free an old version's storage
```

Each publish or switch writes the page's `release` record, `{version, label, at}`, so a page that is already open can notice a new version and offer a reload. Records and files are not versioned with the page: a rollback changes the page's code and layout, never its data.

## Beyond this chapter

The skill covers more than the handbook shows, including page templates for reports, explainers and data tables; ask your agent which templates the skill offers.
