# Capture media on the creator's computer Build and render the app outside Hackemist. Upload the finished thumbnail, screenshots and demo through MCP. The platform stores and validates the finished files; it does not need to run Chromium or encode a demo for this workflow. `project_capture_media` defaults to `execution: "client"`. Its result is a **plan**, with `accepted: false`, `rendered: false` and `status: "client_capture_required"`. No browser job, launch, account save, upload ticket or draft fork is created. Never describe this response as completed capture or uploaded media. Rendering runs wherever the coding agent/browser runs. A desktop coding agent can use the creator's computer. A cloud-only connector cannot start a browser on someone else's PC without a local agent/recorder; use its own approved browser environment or finished media supplied by the creator. Never ask for a connector's bearer or copy it to a shell, recorder, browser page or external service. ## Agent workflow 1. Save the static build locally. Read the current project, listing revision, honest device targets and `hosting_info.publishing.local_media`. Preserve existing media unless the creator specifically authorizes replacement. 2. Call `project_capture_media` with the saved `version_id`, current `base_listing_revision`, `idempotency_key`, selected `media_kinds`, meaningful inputs and scene descriptions. Save only the returned `capture_plan` as `capture-plan.json`. Its omitted descriptive text stays blank; fill those fields from the real inspected scenes, using short useful captions rather than technical capture boilerplate. 3. Use a local browser/recorder, or the helper below, to finish **all** selected files before starting uploads. Inspect the actual frames, text, controls and video. Include five to eight genuinely different scenes, across advertised devices; desktop/phone/tablet defaults use 3+3+2. A complete demo is 30–60 seconds. Scene labels and changed pixels do not prove a feature worked. 4. Re-read `listing_get`. For each finished file create a `media_upload_create` ticket with its exact SHA-256, `byte_length`, kind, accurate caption, alt text, description and **current** `base_revision`. Transfer bounded base64 chunks with `media_upload_chunk`, then `media_upload_finalize`. Use its returned listing revision for the next file. Inspect `media_upload_status` after an uncertain response; never blindly create another ticket. 5. For an authorized gallery replacement, detach only the selected old **draft** screenshot references with `listing_asset_remove` and current revisions before adding the new set. Live media and retained originals remain intact. Do not delete retained files unless their creator explicitly requests permanent deletion. Re-read the checklist and publish explicitly with `project_release`. Keep unselected thumbnail, screenshots and demo unchanged. Media needs consent and `media:write` when replacing published assets. The normal ownership, current token, plan, quota, byte/hash, decoder, revision, moderation and release guards remain enforced. Local manifests are authored evidence and never impersonate trusted server capture receipts or authenticated save tests. ## Local helper The helper requires **Node 22+, Playwright and FFmpeg** on the recorder computer. It creates a fresh sandboxed Chromium session without the person's cookies/profile. It fulfils browser requests from checked files in the selected static build, opens no HTTP listener and blocks external connections, uses an ephemeral in-memory SDK bridge, and never connects to an account database or uploads media. Root-relative bundled assets are supported. External-service flows need your own approved local recorder and honest finished media. Download the four `helper_files` URLs returned by `hosting_info.publishing.local_media` into one tools directory. They are `creator_capture.mjs`, `capture-engine.mjs`, `capture-bridge.mjs` and `sdk.js`. Use the URLs from the connected server. Install dependencies once in that directory: ```sh npm init -y npm install --save-dev playwright@1.62.0 npx playwright install chromium ``` FFmpeg must be on PATH, or supply its absolute path with `--ffmpeg`. Run from the tools directory, using your own build/plan paths and a **new** output directory: ```sh node creator_capture.mjs --build /path/to/dist --plan /path/to/capture-plan.json --output /path/to/new-media ``` An installed Chrome executable can be selected with `--browser /absolute/path/to/chrome`. Keep the output directory outside the build. The helper never overwrites an earlier capture, runs build scripts, accepts API keys or uploads files. A failed run has no completed `manifest.json`; repair the source/plan and use a new output directory. The result contains `thumbnail.png` (or a locally compressed `thumbnail.webp` for a detailed cover), selected `screenshot-01.png` through `screenshot-08.png`, selected `demo.mp4`, and a final `manifest.json` with exact file sizes, SHA-256 and descriptive text. Only selected media files are written. The server still determines actual dimensions/duration and sanitized bytes, so stored hashes can differ from the upload manifest. Do not treat a local manifest's source version as server-verified capture provenance. The cover uses **1280×800 CSS pixels at device scale 2**, producing **2560×1600 pixels**, exactly **16:10**. Design the full composition for this frame: readable app/game title, a useful representative scene, safe margins and no browser/player chrome. Screenshots retain their actual planned desktop, phone or tablet viewport. Do not enlarge a tiny image or crop a phone screenshot into a pretend desktop cover. Existing low-resolution media needs a creator-authorized recapture to gain real detail. Uploaded stills retain up to 2560 pixels per edge without upscaling. Static UI artwork is normalized once to lossless WebP, with a high-quality fallback only if the encoded bytes exceed the existing quota. Compatible finished H.264/yuv420p demos are fully decoded for validation, then remuxed without another video encode; MCP demos that still need encoding are rejected with an instruction to finish encoding locally. Manual uploads retain the bounded compatibility normalization path. The final MP4 is silent and metadata is removed. These safeguards run once at upload, never for every viewer. ## Optional server recorder Admin → Settings → Publishing checks → **Allow optional server media rendering** is off by default. Even when enabled, MCP still defaults to client capture. `execution: "server"` explicitly requests the existing bounded private recorder and its durable receipt. Disabling the switch prevents new server jobs and prevents an outstanding job from attaching media at its next authorization guard. Publishing safety checks remain independent and are not disabled by moving media capture off the server. Implementation references: [Playwright browser contexts](https://playwright.dev/docs/api/class-browsercontext) and [FFmpeg stream copy](https://ffmpeg.org/ffmpeg.html#Streamcopy). ## Maintainer acceptance check After building the application and checker test images, run `python tests/run_client_media_acceptance.py --app-image hackemist:test --checker-image hackemist-checker:test`. Docker and host Python are required. The runner uses only its own labeled containers/private network, explicit source/test mounts and a new fixture directory. It records a real 30-second demo and eight mixed-device scenes locally, checks detailed-cover compression, then uploads through scoped MCP tools and publishes with the independent browser check. It creates no server media-capture job. Reports stay under `.local/`; production data, environment files and ports are not used. The same check runs in CI. The older optional-server browser acceptance requires `HACKEMIST_SERVER_CAPTURE_TESTS=1` alongside its existing isolated-deployment/mutation flags. It temporarily enables the server switch through its test administrator and restores the previous value afterward. Do not point mutation fixtures at the live site.