Files
Brandon Thomas f64ae3bf3c Craft Anything
A native, pure-Rust app that shows anyone how a piece of software works, with
evidence-backed answers, then helps them rebuild it. It ships a desktop app, a
CLI and an MCP server, all driven by one engine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:52:14 -04:00

90 lines
4.0 KiB
Markdown

# Hosting Craft Anything for the web
`craft-anything-web-<version>.zip` (from the GitHub release, or `packaging/web/package.sh`) holds a
static site in `craft-anything-web-<version>/`:
| File | What it is |
|---|---|
| `index.html` | The page. It loads everything through relative URLs. |
| `craft-anything-web-<hash>.js` | wasm-bindgen glue (generated, ES module) |
| `craft-anything-web-<hash>_bg.wasm` | The app |
| `_headers`, `.htaccess` | Sample header rules for Netlify/Cloudflare Pages and Apache |
There is no server-side code and no hand-written JavaScript: the glue is generated by
wasm-bindgen from the Rust sources (`apps/craft-anything-web`). Upload the folder's contents anywhere
that serves static files.
The web viewer runs entirely on the visitor's device: a dropped or picked file is read in the
browser and never uploaded. It uses the built-in, pure-Rust analyzers only (formats, disassembly,
JavaScript bundles); running programs, external engines and AI backends need the desktop app. A
browser with WebGPU or WebGL2 is required.
## Size
`packaging/web/package.sh` fails the build if the wasm grows past 24 MiB: Cloudflare Pages and
Workers static assets reject any file over **25 MiB**, so a release can't ship a file a common host
refuses.
## Required server settings
- **MIME type:** serve `.wasm` as `application/wasm`. Browsers refuse to stream-compile it under
any other type, and the app then loads slowly or not at all. Serve `.js` as `text/javascript`.
Most hosts already do both. For nginx, check that `mime.types` has `application/wasm wasm;`.
- **Compression:** turn on gzip or Brotli for `.wasm`, `.js` and `.html`. Brotli usually
shrinks the wasm to a quarter of its size. You can also
precompress (`brotli -k *.wasm`) and let the server send `Content-Encoding: br`.
- **Caching:** `Cache-Control: public, max-age=31536000, immutable` on the hashed `.wasm` and
`.js` files, and `no-cache` on `index.html`.
- **HTTPS:** WebGPU (and the clipboard) only work in a secure context, which means `https://`
or `http://localhost`. Over plain HTTP elsewhere, the app falls back to WebGL2.
- **No special isolation headers:** Craft Anything doesn't use `SharedArrayBuffer`, so it doesn't
need `Cross-Origin-Opener-Policy` or `Cross-Origin-Embedder-Policy`. If your site already sends
COEP `require-corp`, also send `Cross-Origin-Resource-Policy: same-origin` (or `cross-origin`
when the files live on a CDN) on the app's files.
nginx example:
```nginx
location /craft-anything/ {
types { application/wasm wasm; text/javascript js; text/html html; }
gzip on;
gzip_types application/wasm text/javascript text/html;
location ~* \.(wasm|js)$ { add_header Cache-Control "public, max-age=31536000, immutable"; }
location ~* index\.html$ { add_header Cache-Control "no-cache"; }
}
```
Local test: `python3 -m http.server 8767` inside the folder, then open http://localhost:8767/.
## Embedding in a page (iframe)
```html
<iframe
src="https://example.com/craft-anything/"
title="Craft Anything"
style="width: 100%; height: 720px; border: 0;"
allow="fullscreen; clipboard-read; clipboard-write"
allowfullscreen>
</iframe>
```
- The app fills the iframe and follows its size, so size the iframe and not the app.
- Keyboard shortcuts go to the iframe after the user clicks into it, as with any embedded app.
- **Sandboxed iframes** need at least `sandbox="allow-scripts allow-same-origin allow-popups"`
(`allow-popups` lets Help links open in a new tab).
- Don't send `X-Frame-Options: DENY` or a `frame-ancestors` CSP that excludes the embedding page.
## Renderer
Craft Anything renders with wgpu: **WebGPU** when the browser has it, otherwise **WebGL2**.
## Smoke test (headless Chrome)
Headless Chrome has no WebGPU adapter, so it runs on WebGL2 through SwiftShader:
```sh
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless=new \
--use-angle=swiftshader --enable-unsafe-swiftshader --window-size=1440,900 \
--timeout=30000 --screenshot=craft-anything-web.png "http://127.0.0.1:8767/"
```