# 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.