# Seeing your work (https://lazyfrog.dev/docs/guide/seeing-your-work)

{/* Generated by scripts/generate-docs.ts from services/gateway/src/guide.md. Do not edit. */}

- **Press it, don't only look at it.** `preview_try` opens the preview in a clean browser and runs
  your steps: click, type, select, press, scroll, wait, `expect` (is this on the page now?), `media`
  (is each audio and video's time really moving, and at what volume?) and `screenshot`. Name things
  as a visitor sees them (`text`), or use `selector`. Anything people press gets tried on a phone
  (`viewport: "mobile"`, the default) before you say it works; a screenshot of a button isn't proof
  it does anything. Each step says what happened; `media` with no sound moving means the player
  doesn't play, whatever its button shows. It changes the preview's data as a visitor would, and
  never the live site. It is a browser: it hears nothing and isn't a real phone, so say what you
  tried and ask the user to try the rest.
- Every project has a **preview address**, `https://p-<id>.lazyfrog.app` (`project get` shows it;
  `project create` returns it). It always shows your current work, live, so give it to the user
  and they can watch the app in their browser as you build. It is unguessable and marked
  `noindex`. If the user wants it private, `project settings` with `preview_password` asks
  visitors for a password; `rotate_preview_url` gives it a new address. Never put it in the site's
  own files (a share image, a canonical link, a sitemap): use the live address or a path;
  publishing names the files that still carry it. On the live site, a `robots.txt` you write is
  served after a few comment lines Cloudflare adds about AI use; without one, only those show.
- A sleeping preview wakes when someone visits it (a short "waking up" page) and sleeps after 15
  minutes without visits or tool calls. What a page does by itself doesn't keep it awake or wake
  it: an open tab's connections (the dev server's hot reload, a WebSocket, an event
  stream) count only when they open, a reconnect to a sleeping preview gets 503 with Retry-After,
  and a page that reloads itself shows "Preview asleep" and wakes it once the tab is looked at.
  Reading a project (`project_read` `get`) never wakes it either: a sleeping preview is reported
  asleep.
- Check the app yourself with `http_get` (any path, against the running app; `http_request` for
  other methods and a body) and `preview_screenshot` (an image of a page, desktop or mobile).
- **Browser errors.** `preview_screenshot` and `design_check` open the page in a clean browser
  (no extensions, a fresh profile) and list what went wrong there under "Browser errors in a
  clean browser": uncaught errors, console errors and the app's own files that failed to load
  (`include_warnings: true` adds console warnings), or "none". Fix what it lists like any other
  bug. The preview runs the app's development server, so it shows the framework's developer
  overlay (the error box over the page) and development-only warnings; the live site doesn't
  show the overlay to visitors.
- **When the person reports an error from their own browser**, check it with a screenshot of
  that page first. If the clean browser shows nothing and the error mentions things the code
  never wrote (attributes such as `data-lastpass-*`, `data-gr-*`, `data-1p-*`, `cz-shortcut-listen`,
  or elements the code doesn't render), tell them in plain words: it's a browser extension on
  their side (a password manager, a spell checker) adding to the page; visitors on the live site
  don't see the overlay; a private window with extensions off shows the page without it. Change
  nothing in the code. Never sprinkle hydration-warning suppression (`suppressHydrationWarning`
  and the like) to hide it. A real hydration mismatch, one the clean browser shows too, comes
  from the code: a time or date, a random value, the locale, or invalid nesting (a `<div>`
  inside a `<p>`, a link inside a link). Fix it in the code so the server and the first render
  in the browser agree (render the changing value after the page has loaded, fix the nesting).
- Publishing puts the preview live at `https://<slug>.lazyfrog.app` (see Publishing below).
- `exec` runs commands as the project's user in `/workspace`. Default timeout 60 s (max 300);
  use `background: true` for longer work (up to `timeout`, default an hour) and follow it with
  the `job` tool. Long output keeps its end: send it to a file and `fs_read` it for all of it.
  ImageMagick (`magick`) and `ffmpeg` are installed, e.g. for photos the user brings:
  `magick in.jpg -auto-orient -strip -resize '1600x1600>' -quality 72 out.webp`. For text on an
  image use an installed font (`magick -list font`: the URW set, such as `NimbusSans-Regular`); a
  missing one only warns and the image still comes out, in a fallback. Anything a command
  leaves running is stopped when it finishes: servers belong in services.json. Commands share the
  sandbox's memory with the database and services (768 MB on Free): a command that would use more
  than what is left is stopped ("Stopped: the command used more than N MB"), and they keep
  running; work in smaller batches or stream instead of loading everything at once. A commit (its
  secret scan) or a publish stopped the same way fails with `workspace_out_of_memory` (see When
  something fails): change course, don't repeat it.
  Free workspaces are small, but on Free and Hobby a command gets extra room while it runs: a
  foreground `exec` (not `background`), `git` and `project_scan` have 512 MB more than the plan's
  memory for their run, taken back after, so a commit, an install, a type check or a build
  usually fits beside the dev server: don't stop the dev server first. When the node is busy a
  command runs without it, and the result says so ("the node is busy, so this command had no
  extra room"): try again in a few minutes. Run one heavy thing at a time (not `project_scan`,
  `design_check` or `deploy preflight` while a build, a test run or a second dev server runs),
  and prefer one page at a time for `design_check`. These checks take their turn, and when one
  fails the way a starved app does it is tried again by itself (after a big service restarts, when
  the workspace is nearly full). If it still fails it says why: the workspace short of memory
  (on Free that is its size, not a fault in the app), a page that didn't load (look at it with
  `http_get` and `logs_tail`), or a page that kept LazyFrog's checking browser busy or crashed it
  (it draws without a graphics card; try one page at a time, `preview_screenshot` meanwhile). Tell
  the user what was skipped and go on. A build on Free (it has no `deploy
  preflight`) that still runs out of memory with the extra room: stop the dev server for the build
  (`service` `stop` name "web"), run the build with `exec` again, and `service` `restart` it when
  the build is done; meanwhile the preview says the site has stopped.
