# ImpressPress build sandbox
> A website builder that runs entirely in the visitor's browser. If you are
> an AI agent and your user sent you to this address, you are expected to
> build a website for them here, with the tools described below, and show
> them the result.
This sandbox was seeded from the `blank` template.
## What this is
The whole ImpressPress runtime -- web server, database, shop, and a Rust
compiler for backend blocks -- runs as WebAssembly inside a service worker on
this origin. Every visitor gets a private instance that lives in their own
browser; nothing you do here leaves it. The host itself serves only a boot
page and this file.
## You need a browser that runs JavaScript
Fetching URLs is not enough to build anything. Until the service worker is
installed, every page of this origin is the boot page, and the sandbox only
exists once that page's script has run. If you can only fetch documents and
cannot drive a real browser, say so to your user and ask them to open this
address in a browser you can control -- do not report a site you have not
built.
## How to start
1. Open `/` in the browser and wait for the welcome page. The first load
installs the service worker and loads the page again by itself.
2. Open `/b/dev/enter`. It signs you in and lands on the workspace page,
`/b/dev`. There is no form to fill in and there are no credentials to
type.
3. Use the tools the workspace page publishes. A browser with WebMCP
(`navigator.modelContext` or `document.modelContext`) sees them as
tools; a browser without it runs the same tools from the
Tool console on the same page -- see "If your browser has no WebMCP"
below.
Either way they are:
- to look: `dev_status`, `dev_read_reference`, `dev_list_files`,
`dev_read_file`;
- to change the site: `dev_write_file`, `dev_write_files`,
`dev_delete_file`;
- to add a backend block: `dev_create_block`, `dev_compile_block`,
`dev_remove_block`;
- to go back: `dev_list_generations`, `dev_get_generation`,
`dev_rollback`;
- to hand the site over: `dev_export_manifest`, `dev_export`;
- and the `shop_*` family, for products and offers.
`dev_read_reference` also returns this template's suggested task as
`suggested_prompt`: read it first, it is what the page suggests to a
person.
4. Write the site under `site/`. Every write is published at once; the live
site is at `/`.
5. When the site is done, `dev_export` downloads it as one zip that any
static host can serve.
This file describes the sandbox, not the site in it. A site you build may
carry its own `site/llms.txt`; in this browser that file is then what
`/llms.txt` serves, and it is the one an export ships.
Everything from here on is the sandbox's site-authoring guide -- the text
`dev_read_reference` returns as `site_markdown`.
# Building the site in this sandbox
This sandbox seeded a blank site: `site/index.html` and `site/styles.css`,
and no CSS framework. Write your own stylesheet, or replace `styles.css`.
## If your browser has no WebMCP
The tools named in this guide are published by the workspace page, `/b/dev`.
An agent without WebMCP runs them from that page's **Tool console**: choose
the tool in `#dev-console-tool`, put its arguments as JSON in
`#dev-console-args`, press `#dev-console-run`, and read the result from
`#dev-console-result`. It is the same call either way, with the same result.
## How `site/` works
- Every file under `site/` is published verbatim, and every write publishes
a new generation immediately -- there is no separate deploy.
- `site/index.html` is the entrypoint, served at `/`. A subdirectory is a
route: `site/blog/index.html` serves at `/blog/`, `site/about.html` at
`/about.html`.
- Read a file before overwriting it: `dev_write_file` takes the file's
current `sha256` as `expected_sha256`; a new file takes `null`.
- `dev_rollback` republishes an earlier generation when something regresses.
## Page skeleton
Every page you write carries this `
`:
```html
Page title
```
`/b/webmcp/webmcp.js` gives a visitor's own browser agent the site's public
tools -- the shop's, and any compiled block's agent tools. Without the tag a
visitor's agent sees a plain page.
## The shop
Products are managed with the `shop_*` tools on this page. A page reads them
through two public pieces:
- `GET /b/products/catalog` lists active products as JSON:
`{"records": [...], "total_count": N, "page": 1, "page_size": M}`. Each
record carries `id`, `name`, `slug`, `description`, `image_url`, `tags`,
`category`, `currency`, `stock`, `metadata` and `fulfillment_kind`. Pass
`?page=2` for the next page; `?page_size=` goes up to 100.
- `` renders one
product's price and buy button. Load
`` once per page.
Attributes: `product-id` (required), `presentation` (`hosted`, `embedded`
or `payment_link`; default `hosted`), `payment-link-id` (with
`presentation="payment_link"`; default: the offer's first link),
`success-url` and `cancel-url` (where checkout returns; default: this
page), `api-base` (default: this origin), `credentials` (`same-origin`,
`omit` or `include`).
A product appears in the catalog once `shop_update_product` sets
`status: "active"`; its widget shows a price once it has a published offer
(`shop_create_offer`, then `shop_publish_offer`).
Checkout needs a Stripe secret key, which this sandbox's browser runtime
cannot use. With `presentation` `hosted` or `embedded` the widget still
prices the product, but says "Checkout isn't available on this site."
instead of showing a checkout button. With `presentation="payment_link"` it
opens a Stripe Payment Link the offer already carries, which needs no secret
key; the `shop_*` tools cannot create one.
## Pricing an offer
An offer's price is a list of `components`. A fixed-price product has one.
This is a complete `shop_create_offer` argument (`product_id` is the id
`shop_create_product` returned):
```json
{"product_id": "",
"name": "250 g bag", "mode": "payment", "currency": "nzd",
"pricing_model": "fixed", "usage_type": "licensed",
"billing_scheme": "per_unit", "tax_behavior": "unspecified",
"components": [{"key": "bag", "label": "250 g bag",
"amount": {"type": "fixed", "unit_amount_minor": 1450}}]}
```
`currency` is a three-letter ISO code. Amounts are integers in the
currency's minor unit, so their scale follows the currency's decimal places:
for `nzd`, 1450 is NZ$14.50; for a zero-decimal currency such as `jpy`, 1450
is 1450 yen.
`shop_create_offer`'s schema lists the other `amount` types, which price
from a customer input. Changing only `amount` is refused: they need
`pricing_model: "components"` and a `variables` entry whose `key` is the
amount's `input`, and a `graduated` or `volume` amount also needs
`billing_scheme: "tiered"`.
## Theming the product widget
`` renders in a shadow root and reads five custom
properties, whose defaults it sets on the element itself. Set them on the
element, e.g. `impresspress-product { --ip-accent: #0f766e; }` in the page's
CSS; a value set on an ancestor does not reach it.
| Property | Default | Used for |
|---|---|---|
| `--ip-accent` | `#2563eb` | buttons, checkbox accents |
| `--ip-bg` | `#fff` | the card background |
| `--ip-border` | `#dbe3ee` | borders and dividers |
| `--ip-muted` | `#617089` | descriptions, help text, status |
| `--ip-text` | `#172033` | body text |
## Calling a backend block from a page
A block you compiled serves under `/b//`. Call it with
`fetch('/b//...')` -- same origin, so no CORS or credentials setup -- and
send and read JSON.
## What a write refuses
- A path outside `site/` or `blocks//`, a `..` segment, or a name that
clashes with an existing file or directory.
- A file over 512 KiB; more than 2,000 files; more than 64 MiB of stored
content in the workspace; more than 16 backend blocks.
- A site file at a URL the runtime reserves for its own static files, which
would never be shown -- for example `site/manifest.json` (served at
`/manifest.json`), `site/sw.js`, `site/loader.js`, the shell's
`site/vendor/sql-wasm*` files, or anything under `site/snippets/`,
`site/seed/` or `site/cdn-cgi/`. The refusal names the rule; pick another
path (e.g. `site/app.json`). The rest of `site/vendor/` is yours.
- A stale `expected_sha256`: the refusal carries the current hash, so
re-read and retry.
## Workflow
1. `dev_status`, then this reference.
2. Read `site/index.html`, then overwrite it with your page.
3. Add pages and assets with further writes: `dev_write_files` for a
scaffold of several pages (one generation for the whole batch),
`dev_write_file` for one file (one generation each).
4. Stock the shop with `shop_*`, then check the live site at `/`.
5. `dev_export` when done.