neuron-explorer
Qwen3.5‑4B 전용 조향형 뉴런 탐색기, 모든 MLP 뉴런을 밀어 결과를 보관·탐색
Expo·React Native 앱용 시각적 네비게이션 지도, 에이전트가 탐색한 스크린샷·플로우·인터랙티브 .scrmap 뷰어
Visual navigation maps for Expo/React Native apps: agent-explored screenshots, flows, and an interactive .scrmap visualiser
개발 도구JavaScript · TypeScriptGitHub @aleqsio직접 설치운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
설치형 프로젝트는 배포 도메인 없이도 확인할 수 있습니다. 개인 저장소는 GitHub 로그인으로, 조직 저장소는 위의 저장소 확인 파일로 직접 확인해 주세요.
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
screenmap shows you every screen in an Expo / React Native or NativeScript app, and shows your reviewers exactly which screens a pull request changed, without anyone writing a test.
Web pull requests get a preview URL. Mobile pull requests get a QR code, a build to install, and a screen to find on your own, so most reviewers skip the visual half of the change. screenmap closes that gap: it runs the app in CI, screenshots the screens your diff can reach, and posts them into the review.README 전문 보기 ↗screenmap shows you every screen in an Expo / React Native or NativeScript app, and shows your reviewers exactly which screens a pull request changed, without anyone writing a test.
Web pull requests get a preview URL. Mobile pull requests get a QR code, a build to install, and a screen to find on your own, so most reviewers skip the visual half of the change. screenmap closes that gap: it runs the app in CI, screenshots the screens your diff can reach, and posts them into the review.
It is MIT licensed, runs in your own GitHub Actions, and keeps every screenshot on a branch in your own repository.
| Use | What you run | Section |
|---|---|---|
| Review what a pull request changes on screen | the aleqsio/screenmap@v1 GitHub Action | Review pull requests in CI |
| Learn an app you have never seen, or map yours locally | the /screenmap Claude Code plugin | Map an app on your machine |
| Look at a map, replay a flow, share it with a designer | the map viewer at app.screenmap.dev | The map viewer |
All three read and write the same .scrmap bundle, so a map recorded locally works in CI and the other way round. If you want the pipeline behind them before the setup steps, jump to How it works under the hood.
Want to see it first? Open app.screenmap.dev/?template=bluesky for a full map of the Bluesky client: 70 screens, 126 recorded flows, captured in one run.
.scrmap) is one bundle holding every screen of the app: screenshots, routes, the edges between screens, and the recorded flows. It is a zip, and the viewer opens it..diff.scrmap) is a second, smaller bundle describing what one pull request changed against a map. It is meaningless on its own and is always loaded on top of a map..meta.json sidecar. Committed to your repo, it replays headlessly with argent flow run, no LLM involved.screenmaps branch in your own repo. Pull request runs diff against it instead of re-mapping the app.The aleqsio/screenmap@v1 Action reviews the screens a pull request touches without re-mapping the whole app. When a PR opens, it works out which routes the diff can reach, captures them on a simulator, and posts a sticky comment with the changed region of each affected screen and a link into the viewer for the rest.
It reports, it does not gate. There is no pass/fail check by default, the agent can mark a suspected change as unaffected, and a reviewer looking at two images decides for themselves.
.screenmap/flows/*.yaml (plus their .meta.json sidecars) live in the app repo and get reviewed like code. A screen with a committed flow is captured by a headless argent flow run, which is deterministic, needs no LLM, and takes seconds..scrmap of main is built once and refreshed incrementally on every push. A PR run captures only the head side of its suspect screens, because the base-side screenshots already exist in the baseline..screenmap/SKILL.md and capped by a per-run budget. On a PR its output is ephemeral: captures go into the bundle, flows go into the artifact. After the feature merges, the baseline job records flows for the new screens and opens a flows PR against main. Once that PR is merged, those screens replay for free.PR opened ──▶ restore baseline (screenmaps branch) ──▶ static parse + suspects ──▶ per suspect:
committed flow? ──yes──▶ argent replay (teal)
──no───▶ agent explores (violet, budgeted)
base-side screenshots reused ──────────────────────────────────▶ head captures + notes ──▶ pack .diff.scrmap
──▶ publish (screenmaps branch + artifact)
──▶ sticky PR comment → ?map=…&changes=…
You need all four of these:
expo-dev-client installed and deep-linkable routes. Or a NativeScript app (Angular Router, or Core XML pages): no dev client and no EAS lane, so you pass a prebuilt .app / .apk as app_path and name the routes your URL handler opens under routes.links — see NativeScript apps."android": { "buildType": "apk" }, since an .aab cannot be installed on an emulator), or a build of your own.ubuntu-latest. A JavaScript-only pull request takes about 12 minutes end to end.Copy the two workflow files from action/templates/ into your app repo's .github/workflows/:
screenmap-pr.yml runs on every pull request and posts the comment.screenmap-baseline.yml keeps the map of main fresh, and opens the flows PR after a merge.Decide where the dev client comes from. The Action owns no build pipeline, so pick one:
EAS (the default). Link the project with npx eas init, add a simulator profile to eas.json, and pass an EXPO_TOKEN secret as the expo_token input. The profile looks like this:
"development-simulator": {
"developmentClient": true,
"distribution": "internal",
"ios": { "simulator": true }
}
The Action reuses the newest finished EAS build whose @expo/fingerprint matches the checkout, so a JS-only PR never waits on a rebuild. When nothing matches, it runs eas build on Expo's infrastructure and waits.
Get your EXPO_TOKEN from expo.dev under Account settings → Access tokens, and add it in your repo under Settings → Secrets and variables → Actions.
Bring your own build. Pass app_path pointing at a simulator .app an earlier step produced, from your own pipeline, a shared artifact, or anywhere else. EAS is skipped entirely, and no EXPO_TOKEN is needed.
Optionally add an agent key and pass it as agent_api_key. Without a key the Action stays deterministic: deep links and committed flows only, with no exploration and no per-screen notes. Claude Code is the default, and the alternatives are under AI providers.
In a monorepo, point the Action at the app with project: apps/mobile. Dependencies are installed from the nearest directory, up to the repo root, that has a lockfile: pnpm-lock.yaml, yarn.lock, package-lock.json, or bun.lock / bun.lockb. For bun, the Action uses a bun already on PATH, and otherwise sets one up with oven-sh/setup-bun at the version packageManager or engines.bun pins in that directory's package.json (the latest when neither does). On a private repo, the comment's screenshots still render, because they load from github.com with the reader's own session. The viewer link does not: the viewer fetches the bundles from raw GitHub URLs, which are not anonymously readable there. The comment also links the workflow artifact, which you download and drop into the viewer yourself.
| Step | Lane | Notes |
|---|---|---|
Restore the baseline for the PR base SHA (or latest) | deterministic | read from the screenmaps branch |
parse-routes on head, then diff-map suspects against the baseline graph | deterministic | the same suspect logic as /screenmap pr |
| Copy base-side screenshots of the suspects out of the baseline | deterministic | nothing is captured twice |
Replay committed flows for the suspects (argent flow run, fragments around capture points) | deterministic | landing-checked, see below |
| Deep-link the remaining screens | deterministic | xcrun simctl openurl plus a screenshot |
| Explore screens that have no flow | agent (budgeted) | writes captures, flows and notes.json with per-screen "what changed" and unaffected verdicts |
diff-map pack into a .diff.scrmap | deterministic | per-state statuses and dismissed suspects |
| Publish and post the sticky comment | deterministic | the link is ?map=<baseline-url>&changes=<diff-url>, which opens the viewer preloaded |
The landing check. Coordinate taps drift silently, because argent reports success wherever a tap lands. So every replay is verified: the end screen is OCR'd with Apple Vision, any system alert is dismissed, and the text is checked against the landmarks the recorder saved in the flow's sidecar (two to five words that identify the arrival screen). A flow with no landmarks falls back to comparing OCR text against a fresh deep-link capture of the same route. When the check fails, the flow is marked drifted, the deep-link capture is used instead, the app is relaunched, and the flow is queued for re-recording in the next baseline run.
| Input | Default | What it does |
|---|---|---|
mode | required | pr or baseline |
project | . | Path to the app project (Expo or NativeScript), relative to the repo root |
agent_provider | claude | claude, codex, gemini or opencode. See AI providers |
agent_cli_version | empty | Version of the agent_provider CLI to install. Empty installs the version this release pins |
agent_api_key | empty | Key for the chosen provider. Leave empty for deterministic-only runs |
effort | balanced | fast, balanced or thorough — tokens and wall-clock against accuracy. See Install step 3 |
agent_max_screens | empty | How many screens the agent may explore in one run. Empty uses the effort preset (6 / 8 / 24) |
auto_baseline | "true" | (pr) Start the baseline workflow when no map exists yet, instead of asking for it. Needs actions: write |
baseline_workflow | screenmap-baseline.yml | (pr) Which workflow to start when no baseline exists |
screenmaps_branch | screenmaps | Orphan branch holding baseline maps and per-PR change bundles |
publish | "true" | Publish bundles to the screenmaps branch so the comment can deep-link the viewer. Needs contents: write |
viewer_url | https://app.screenmap.dev |
Outputs: bundle (path of the produced .scrmap or .diff.scrmap), summary (path of the run summary JSON), and viewer_link (the preloaded viewer link, when the run published).
The agent lane is provider-agnostic. Its contract is file-based: the agent is told which screens to handle and exactly where to write captures, flows, notes.json and summary.json, and the CLI validates those files afterwards. Any headless agentic CLI that can run shell commands fits the slot.
agent_provider | CLI invoked | Key env var |
|---|---|---|
claude (default) | claude -p … --dangerously-skip-permissions | ANTHROPIC_API_KEY |
codex | codex exec --dangerously-bypass-approvals-and-sandbox | OPENAI_API_KEY |
gemini | gemini --yolo -p … | GEMINI_API_KEY |
opencode | opencode run … | whichever its configured provider needs, named in agent.keyEnv |
Pass the key as the agent_api_key input and the CLI maps it onto the env var the provider expects (an env var you set explicitly wins). The Action installs the chosen CLI on demand, at the version it pins unless agent_cli_version says otherwise. Locally, AGENT_PROVIDER plus the provider's own env var work the same way.
For any other CLI, set this in .screenmap/config.json:
"agent": { "command": "myagent --prompt-file {promptFile}", "keyEnv": "MYAGENT_API_KEY" }
{promptFile} (also available as $SCREENMAP_PROMPT_FILE) is a markdown file holding the full task. The command runs through bash in the project directory, and your workflow has to install it beforehand. Setting agent.provider in the same file picks a preset without touching the workflow YAML.
The prompt is identical across providers, so quality depends on the model driving it. Claude Code is the only one dogfooded end to end.
The Action is a thin wrapper around action/cli/screenmap-ci.mjs, and that CLI runs against your own simulator:
cd action/cli && npm install
# full baseline of the current checkout (the agent runs if its provider's key is set)
node screenmap-ci.mjs baseline --project ~/app --out /tmp/main.scrmap
# a PR diff: check out the head, point at the baseline, name the base commit
node screenmap-ci.mjs pr --project ~/app --baseline /tmp/main.scrmap --base <sha> --pr 42 --title "…" --out /tmp/pr42.diff.scrmap
# preview the comment
node screenmap-ci.mjs comment --summary ~/app/.screenmap/out/ci/pr/summary.json --map-url … --changes-url …
While iterating, --only id,id, --limit N, --no-agent and --no-sim (static only) save a lot of waiting.
A local run writes flows to .screenmap/out/flows, which is generated output and gitignored. CI replays from .screenmap/flows, which is committed and reviewed. flows-adopt moves them across:
node screenmap-ci.mjs flows-adopt --project ~/app
It copies the YAML and its .meta.json sidecar, and refuses to overwrite a committed flow whose contents differ — pass --force when you mean to replace one. Review the result and commit it; from then on those screens replay deterministically, with no agent and no tokens.
This is also the repair path when a flow drifts. A drifted flow is captured by deep link for that run and reported in the comment, but nothing re-records it unless an agent key is set, so re-record locally with /screenmap, adopt, and commit.
| Path | Who uses it | What it holds |
|---|---|---|
.screenmap/SKILL.md | agent | App-specific guidance, templated in action/templates/SCREENMAP_SKILL.md |
.screenmap/config.json | deterministic lane | Scheme, device, waits, suspect depth, agent budget, sample params |
.screenmap/flows/ | both lanes | Committed argent flows. The flows PR adds to this directory |
screenmaps branch | the Action | main/<sha>.scrmap, main/latest.scrmap, pr-<n>/<sha>.diff.scrmap, all on SHA-pinned raw URLs |
eas.json | EAS lane | The simulator dev-client profile named by eas_profile |
| EAS build history | EAS lane | The dev-client cache, keyed by @expo/fingerprint, so no actions/cache is involved |
| workflow artifact | the Action | The bundle plus summary.json, kept for 90 days |
app_path you pass, supplies the dev client. An afternoon of CI archaeology (CocoaPods sync, Swift-tools minimums, clang strictness per Xcode point release) is Expo's problem now rather than this Action's.main after a merge, which leaves feature branches alone and keeps the flows PR reviewable on its own.main and on a weekday cron. Refreshes are incremental: only suspect screens, and screens with no usable previous capture, get re-captured.simctl status_bar override before every screenshot, so base and head pixels differ only where the app differs. Simulator privacy is pre-granted with simctl privacy grant all, so a mis-tap cannot raise a system dialog over later captures.The agent reads untrusted text: the PR's source, and whatever the app puts on screen. Treat it as something that can be talked into running a command, and keep what it can reach small.
pull_request, never pull_request_target. pull_request_target hands a fork's PR the write token and your secrets while running its code: the install scripts, the app, and the agent.expo_token the run stops at its first step. With app_path it captures and uploads the artifact, then fails at publishing and commenting, both of which need write access.publish: "false" when the app's screens show data you would not put on a public URL. Published files can be read by anyone who can read the repo.PATH, HOME, locale and terminal variables, the Android and Xcode tooling paths, and the one key variable for its provider (or agent.keyEnv). GH_TOKEN, GITHUB_TOKEN, EXPO_TOKEN, the Actions runtime tokens and AGENT_API_KEY itself are never passed. An opencode or custom agent that reads a different variable needs it named in agent.keyEnv. This is not a sandbox: the agent runs as the runner user, which can still read the job token actions/checkout persists in .git/config, the one publishing pushes with.eas-cli and argent are installed at pinned versions, so a new release cannot change what runs on your runner without a change to the Action.The same pipeline runs as a Claude Code plugin, which is the fastest way to answer "what is even in this app" on your first day in an unfamiliar codebase, and the way to try screenmap before wiring up CI.
claude plugin marketplace add aleqsio/screenmap
claude plugin install screenmap@screenmap
Then, in any Expo, React Native or NativeScript project, run one of these in Claude Code:
/screenmap # full run: parse + simulator exploration + pack
/screenmap --static # parse + render only, no simulator
/screenmap replay <flow-name> # replay a recorded flow on the simulator
/screenmap pr <number> # PR diff locally: which screens, states and edges changed
What you get:
Output lands in <project>/.screenmap/out/, so add that to your .gitignore. You need a macOS host with the iOS simulator, or an Android emulator (--platform android); --platform both captures each screen on both and puts them in one map behind a platform switcher.
The same pipeline reads a NativeScript app, with three differences that follow from how NativeScript apps are built.
Routes come from whichever framework drives the views. For Angular, the nativescript provider follows provideNativeScriptRouter(routes) (or NativeScriptRouterModule.forRoot) into every loadChildren module, reads named outlets, redirectTo aliases, enum-valued paths and barrel re-exports, and takes edges from navigate([...]), navigateByUrl() and nsRouterLink. A route with children is a layout, read as tabs when its children sit in several named outlets. Modals and material bottom sheets opened from a screen become state hints naming the component. Core apps (no framework) are read from their <Page> XML files and Frame.navigate({ moduleName }) calls. For Octane, React NativeScript, Vue, Svelte and Solid, a screen is a component the framework mounts as a root (renderNativeScriptApp, ReactNativeScript.start, createApp, svelteNative, startSolidApp), pushes ($navigateTo, navigate({ page }), a solid-navigation navigate('Name') against its <Route> table), presents ($showModal, showModal({ page }), a second root rendered into a view handed to showModal) or hosts in a <frame> tab; the entry's mount is the app root, a <Route> table's initialRouteName is the root behind a router shell, and a <drawer> or a native menu= prop becomes a state hint.
Deep links are declared, not inferred. The app registers its URL scheme in App_Resources (read automatically, ${BUNDLE_IDENTIFIER}-style variables included), but which screen a link opens is the app's own code. So every route starts navigation-only, and .screenmap/config.json says which ones a link reaches:
{ "routes": { "links": { "talk/today": "today", "chatbot/chat": "ask-mae", "settings": true, "*": false } } }
Keys are route ids, values the path after the scheme, true for the route's own URL, "*" as the default. Everything not listed goes to the agent lane to be reached by tapping. An app that registers no URL scheme at all still maps: the root screen is captured by launching the app, and the rest through committed flows or the agent.
There is no Metro and no dev client. The JS bundle ships inside the app, so a session installs and launches the build and that is the whole boot. Locally, puts the simulator app under and the APK under ; both the plugin and find them there. It has to be : under , and produce a stub that imports every module from the Vite dev server, which is not there in a headless run, and refuses such a build by name rather than capturing the home screen. Debug and release builds can sit side by side; the newest one is used. In CI the Action never builds, so a build step produces the or and passes it as ; the EAS lane and do not apply.
Angular, Octane, Vue and Solid are validated against real apps (a 54-screen NativeScript Angular app with lazy route modules and named-outlet tabs, an Octane chat app with a drawer and a native settings sheet, and three Apple Music clones in Angular, Vue and Solid with tab-hosted screens and shared-transition modals); Core, React and Svelte against fixtures. Flow replay, PR diffs and the agent lane run unchanged, with one caveat: a PR that changes native code needs a rebuild per side, since there is no Metro to restart.
The hosted viewer lives at app.screenmap.dev. Drop a .scrmap file (the Map) and, if you have one, a .diff.scrmap file (the Changes overlay for a pull request). Parsing happens in your browser, so nothing is uploaded. Opening the site with no parameters gives you the drop screen.
In the Map view you get code-declared edges as solid lines and agent-observed edges as dashed ones, flow playback with a follow camera and tap/swipe markers drawn on the exact screen state they happened on, per-screen state pickers, a one-action neighbours mode, minimap click-to-jump, and a copy-paste replay command for every flow.
In the Changes view, added, changed and removed screens and edges light up over the dimmed map, changed screens flip between base and head in place, and hovering renders the region-aware visual diff. The format behind it is specified in docs/diff-scrmap-format.md.
URL parameters:
?map=<url>&changes=<url> loads both bundles from any CORS-readable URL (a raw GitHub URL works) and opens on Changes. This is what the PR comment links to.?template=bluesky loads the bundled demo map and diff, opening on the Map with Changes behind the toggle./diffs loads the same demo pair and opens on Changes.To run the viewer yourself:
cd apps/visualiser
npm install
npm run dev
Drop a .scrmap bundle on the landing page. The demo bundle ships in public/demo.scrmap, and the hosted instance loads the same file behind the "load the demo bundle" button.
┌─────────────────┐ ┌───────────────────────┐ ┌────────────────┐ ┌──────────────┐
│ 1. static parse │ ──▶ │ 2. agent exploration │ ──▶ │ 3. pack │ ──▶ │ 4. visualise │
│ parse-routes.mjs│ │ (simulator + flows) │ │ pack-map.mjs │ │ (web app) │
└─────────────────┘ └───────────────────────┘ └────────────────┘ └──────────────┘
routes, edges, screenshots per screen <app>.scrmap zip graph map with
state hints + state variants (manifest, map.json, flow playback,
+ nav/interaction flows screens/*.png) tap overlays
src/routes.ts), and NativeScript apps — Angular Router Routes arrays or Core XML pages — so the screen list is complete rather than whatever a crawler happened to find. It produces the route list, navigation edges from Link, navigate() and nsRouterLink calls, and state hints saying which screens use a bottom-sheet or dialog system.argent flow run. Runtime states get captured too: open drawers, bottom-sheet snap points, dialogs. If a sticky error boundary blocks the app, the agent recovers and carries on..scrmap zip. The format contract is in docs/scrmap-format.md, which is what you need if you want to write your own producer..diff.scrmap, and it overlays what a pull request changed.The expensive part is step 2, and you only pay it once. Recorded flows get committed to your repo, and every later run replays them headlessly instead of exploring again. The agent only wakes up for screens that have no flow yet.
plugins/screenmap/skills/screenmap/SKILL.md is the agent orchestration: phases, safety rails, and the flow-recording contract.plugins/screenmap/skills/screenmap/scripts/ holds parse-routes.mjs, pack-map.mjs, diff-map.mjs (PR diff: suspects and pack) and render-map.mjs (static HTML fallback). All plain Node, no dependencies.plugins/screenmap/skills/screenmap/scripts/routes/ is the route-provider layer: parse-routes.mjs only drives it, and each framework (expo-router, react-navigation, nativescript, or a command you supply) is one module under providers/. See docs/route-providers.md to add one.apps/visualiser/ is the Map / Changes viewer, built with Vite, React, Tailwind v4, shadcn/ui, React Flow and elkjs, plus pixelmatch and OpenCV.js for the visual diff.action.yml is the composite GitHub Action. The metadata sits at the repo root so the repo is publishable to the Marketplace. Its screenmap-ci CLI lives in action/cli, and the workflow and .scrmap templates live in action/templates.docs/scrmap-format.md is the versioned bundle format contract, for writing your own producer.docs/diff-scrmap-format.md is the format of the PR diff bundle behind the Changes view.docs/ci.md is a stub pointing at the CI section above, which is where that guide lives now.fixtures/demo-app/ is a minimal expo-router app that exercises the parser; fixtures/rn-demo-app/, fixtures/ns-angular-demo-app/ and fixtures/ns-core-demo-app/ do the same for the other providers, and fixtures/run-tests.mjs pins every graph.screenmap-ci merge step; the workflow templates show the shape. A local run does both in one pass.simctl spawn defaults; Android has to reach the app's SharedPreferences through run-as, which only works for a debuggable build. When it fails, the dev-menu floating button stays in the captures — cosmetic, and the run continues.routes.links names them; Octane, React, Vue and Svelte apps are read from their mount, push and modal calls, so a screen wired any other way is invisible until an agent finds it.MIT © Aleksander Mikucki
The key is also what makes a bad capture recoverable. Nothing verifies that a deep link landed on the screen you meant, so a route that starts needing a param will quietly capture its own not-found state; the agent is told to check the link arrived and to find real params when it did not. How many screens it re-checks is the effort input:
effort | The agent sees | Cost |
|---|---|---|
fast | only screens no committed flow can reach | cheapest, quickest |
balanced (default) | also routes whose deep link guesses a param | a few screens more |
thorough | every screen in the run | most accurate, most tokens |
effort also sets how far suspect marking follows imports (1, 2 and 3 hops). Anything set explicitly in .screenmap/config.json — agent.scan, agent.maxScreens, suspects.depth — overrides the preset.
Optionally commit two files in the app repo.
.screenmap/config.json holds the mechanical knobs. Every key is optional; start from action/templates/config.json.
{
"effort": "balanced",
"scheme": "myapp",
"device": "iPhone 17 Pro",
"params": { "id": "v60" },
"suspects": { "depth": 2, "broadCap": 8 },
"waits": { "transition": 2500, "network": 6000, "boot": 15000, "relaunch": 4000 },
"agent": { "enabled": true, "provider": "claude", "scan": "params", "maxScreens": 8 }
}
| Key | Default | What it sets |
|---|---|---|
effort | balanced | The preset from step 3. The effort input and SCREENMAP_EFFORT set the same thing |
scheme | from the parsed app config | URL scheme the deep links use |
platforms | ["ios"] | Which platforms a run captures — ["ios"], ["android"], or both. In the Action each platform is its own job (the platform input), folded together afterwards by screenmap-ci merge |
ios.device | iPhone 16 Pro | Simulator to boot. In the Action, the simulator input boots the device |
ios.appPath | discovered under ios/build | A prebuilt simulator .app. The app_path input wins over it |
ios.appId | read from the .app | Bundle id. Override when discovery picks the wrong one |
android.device | any attached device, else the first AVD | AVD name, or the model of an attached device. In the Action, the avd input |
android.appPath | discovered under android/app/build/outputs/apk | A prebuilt .apk. The app_path input wins over it |
android.appId | read from the APK with aapt2 | Package name. Also accepted as android.packageName |
appName | the project directory name | Name recorded in the bundle |
device, bundleId, appPath | — | Pre-multi-platform spellings of the ios.* keys above; still honoured |
metroPort | 8081 | Port Metro starts on (Expo only) |
runtime | from the route provider | expo or nativescript: nativescript when the nativescript provider read the app, else expo. Set it when a custom provider reads a NativeScript app. A NativeScript session launches the installed app instead of steering a dev client onto Metro |
routes.provider | auto | Pin the route provider (expo-router, react-navigation, nativescript, custom) when detection is wrong. See docs/route-providers.md |
routes.links | {} | Which routes a deep link opens, needed for NativeScript and usable with any provider: route id → path after the scheme (true for the route's own URL, false for none, "*" as the default). Without it every NativeScript screen is reached by tapping |
params | {} | Real values for route parameters, see below |
suspects.depth | from effort | Import hops followed out from a changed file |
suspects.broadCap | 8 | Cap on screens marked by a change to a widely imported file |
waits | 2500 / 6000 / 15000 / 4000 ms | transition, network, boot and relaunch settle times |
agent.enabled | true | Set false for a deterministic-only run |
agent.provider | claude | A preset CLI, or agent.command plus agent.keyEnv for any other |
agent.scan | from effort | unflowed, params or all |
agent.maxScreens | from effort | Screen budget for one run |
flowsDir | .screenmap/flows | Where committed flows live |
skillFile | .screenmap/SKILL.md | Where the project guidance lives |
effort only fills in agent.scan, agent.maxScreens and suspects.depth, so any of the three set here wins over the preset.
params supplies real values for route parameters. Without it a route takes a made-up one, so /brew/[id] opens as /brew/1, and if 1 is not a real id the not-found screen is captured and reported as that screen. "id": "v60" covers every route with an id, and prefixing the route id scopes it to one route: "brew/[id].id" under expo-router, "Profile.name" for a react-navigation route map. With an agent key, balanced and thorough also send those routes back to the agent to find values that work.
.screenmap/SKILL.md is the app-specific guidance: how to log in, which ids are real, which controls to leave alone, how long screens take to settle. The agent reads it before it explores. Start from action/templates/SCREENMAP_SKILL.md.
Open a pull request. Pull requests are diffed against a baseline map of the default branch, published as main/<sha>.scrmap and main/latest.scrmap on an orphan screenmaps branch. You do not have to build that first map: when a PR run finds none, it starts the baseline workflow itself and re-runs the PR once that finishes.
The Action dispatches rather than mapping inline, because a full map takes roughly twenty minutes and waiting for it inside the PR job would bill that at the macOS rate for nothing. This needs actions: write on both workflows and a workflow_dispatch trigger on the baseline one, which the templates have. Set auto_baseline: "false" to have a PR report the missing baseline instead, and run it yourself through workflow_dispatch.
| Viewer origin used in comment links |
simulator | iPhone 17 Pro | Device to boot, falling back to any available iPhone |
app_path | empty | A prebuilt simulator .app (or .apk). When set, EAS is skipped. The only option for a NativeScript app |
expo_token | empty | EXPO_TOKEN for the EAS lane. Required unless app_path is set |
eas_profile | development-simulator | The eas.json profile used for the dev client |
flows_pr | "true" | Baseline runs open a PR with the flows the agent recorded |
github_token | ${{ github.token }} | Used for comments, screenmaps pushes and flows PRs |
ns build iosplatforms/ios/build/Debug-iphonesimulator/ns build androidplatforms/android/app/build/outputs/apk/debug/screenmap-cins build@nativescript/vitens debugns runscreenmap-ci.app.apkapp_pathexpo_token