# 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 `bootstrap` 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 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. 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 site built on **Bootstrap 5.3.8**, vendored under `site/vendor/bootstrap/` (stock build, nothing customised, MIT — see `vendor/bootstrap/LICENSE.txt`). Keep that directory, and never `dev_read_file` anything under it: a minified framework file is hundreds of KiB of context that tells you nothing. The welcome page, `site/index.html`, is a worked example of the framework in use. ## 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 ``. No stylesheet of your own is needed; add one only for what Bootstrap's utilities cannot express. ```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. ## Bootstrap here Prefer the components the framework already styles: - Layout: `.container`, `.row` / `.col-*`, the spacing utilities (`py-5`, `mb-3`, `g-4`). - Navigation: `.navbar` with `.navbar-brand`; a `.btn.btn-primary` for the main action. - Content: `.card` / `.card-body` grids for products and features; `.display-5` and `.lead` for a hero; `.badge` for tags. - Forms: `.form-control`, `.form-label`, `.form-select`, `.btn`. - Feedback: `.alert`. `.modal` and `.collapse` work from `data-bs-toggle` attributes because the bundle is loaded; a `.toast` is shown from script with `bootstrap.Toast.getOrCreateInstance(el).show()`. - Theme: add `data-bs-theme="dark"` on `` for a dark site. Bootstrap Icons are **not** vendored; use text, Unicode or an inline SVG. ## 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. Render the list into a `.card` grid with a small script (`esc` keeps a product's text from being read as markup): ```html
``` - `` 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"`; it can be bought once it has a published offer (`shop_create_offer`, then `shop_publish_offer`). ## 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` (for its hash and as the example), 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.