The Screenshot Tour Step
Business view
Whenever someone opens a pull request that changes what a clinic user sees — the web dashboard or the mobile app — reviewers currently have to trust a description, read a diff and imagine the UI, or spin up the sandbox themselves and click around. This PR adds an automatic step: after the sandbox for that PR is deployed, an AI reviewer-in-a-box looks at what changed, visits the relevant screens itself, and drops screenshots into a comment on the PR. It runs on every commit, so the screenshots stay current as the PR evolves, and it costs nothing extra on PRs that don't touch the UI at all.
Nothing about this changes what clinic staff or patients experience in the product — this is purely a tool for the engineering team reviewing changes to the product.
Technical view
What's verified in this repository
The entire change is one new step added to the deploy job in .github/workflows/sandbox.yml:198-208:
- name: UI screenshot tour (detached, AI-gated on UI changes)
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
# Every deploy (open + new commit): an AI agent captures screenshots of
# the user-facing screens THIS diff changes and posts them as one
# updating "📸 UI changes" comment. sandbox-shots gates cheaply first —
# it exits without any AI spend unless the diff touches
# packages/clinic-web/ or packages/clinic-mobile/. Detached so the
# single runner slot frees up; the comment appears when it finishes.
/opt/homelab/sandbox/bin/sandbox-shots dentolize "${{ github.head_ref }}" --detach || true
A few things follow directly from this wiring and are worth being precise about:
- It runs unconditionally on every deploy, not just
opened— unlike
the sibling step right above it, Generate branch docs site (PR open only, detached) at .github/workflows/sandbox.yml:188-196, which is gated with if: github.event.action == 'opened'. This new step has no such if: — it fires on opened, synchronize, and reopened alike (the deploy job itself only runs for those three actions plus edited, per the job condition at .github/workflows/sandbox.yml:88). That matches the PR's stated intent ("automatically, on PR open and on every new commit") but means the AI-spend gating is entirely delegated to sandbox-shots itself — there's nothing in the workflow YAML that skips this step on non-UI PRs; the cost control described in the PR body is a runtime behavior of code this repo doesn't contain.
|| true: like every other host-tool invocation in this workflow
(sandbox, sandbox-docs), a failure in sandbox-shots can't fail the CI job. If the agent run errors out, the PR simply doesn't get a screenshot comment — silently, from the workflow's point of view.
--detach: the same pattern as the docs-generation step one block
above. The step invokes the binary and returns; the actual work (agent run, publish, comment) happens outside the job's lifetime, which is why the job doesn't wait for or report on the result.
- Positioning: it's the last new work inserted before the `Write job
summary step (.github/workflows/sandbox.yml:210`), so it doesn't affect anything the job summary or the sandbox-URL PR comment displays — those two steps are unchanged by this PR.
What's described only in the PR body (not verifiable here)
The PR description states that the referenced binary, /opt/homelab/sandbox/bin/sandbox-shots, along with qa-agent/shots-task.md (the agent prompt) and qa-agent/shots-comment.mjs (the comment builder/updater), live outside this repository under /opt/homelab/sandbox — explicitly out of scope for this PR, per what the author calls the "homelab-scope rule." None of those three files exist in /work/repo, so the following claims from the PR body are reported as claims, not verified facts:
- The pre-agent gate is a cheap, deterministic diff check (no AI spend)
that only proceeds if the diff touches packages/clinic-web/ or packages/clinic-mobile/.
- The agent is "a headless Claude Code agent (Playwright MCP, in the
existing sandbox-qa-agent image)" that reads the diff and decides which screens are worth a shot.
- Screenshots are published to
https://<slug>-shots.docs.anastawfik.com/,
served by the same docs-host infrastructure as the branch-docs feature, under a wildcard cert that already covers it (no new infra).
- The PR comment is titled "📸 UI changes", is created on first run and
updated in place on subsequent runs (the same edit-not-duplicate pattern the existing sandbox-URL comment already uses at .github/workflows/sandbox.yml:460-475, keyed off an HTML marker comment).
The mobile constraint — verified
The PR body says mobile (expo-web) support is best-effort, gated behind a SHOTS_MOBILE flag that defaults off, "pending a clean EXPO_PUBLIC_API_URL-style override" — and this part is verifiable in this repo. packages/clinic-mobile/src/utils/variables.js:13-18 defines BACKEND_URL as a hardcoded three-way branch on NODE_ENV / FOR_DEVELOPMENT:
export const BACKEND_URL =
process.env.NODE_ENV === 'development'
? `http://${getLocalIP()}:4000`
: FOR_DEVELOPMENT
? 'https://api.dentope.com'
: 'https://api.dentolize.com'
There is no branch that reads an arbitrary environment variable for the API host — every non-dev build points at one of exactly two fixed production API hosts (api.dentope.com or api.dentolize.com), never at a given PR's sandbox API (https://api.<slug>.sandbox.anastawfik.com, per sandbox-info.env). So an Expo-web build of clinic-mobile served from a PR sandbox would, today, talk to a live production API rather than that PR's sandbox data — which is exactly the gap the PR cites for leaving SHOTS_MOBILE off by default. This is a genuine, currently-unresolved constraint in clinic-mobile, not just caution on the author's part.