Agent skill

Screenmap

by aleqsio in aleqsio/screenmap

Generate a visual navigation map of an Expo / React Native or NativeScript app.

MITAuto-check passedMobile

Install Screenmap

skills CLI
$ npx skills add aleqsio/screenmap --skill screenmap -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install aleqsio/screenmap screenmap --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/aleqsio/screenmap.git skills-src && mkdir -p .claude/skills && cp -r skills-src/plugins/screenmap/skills/screenmap .claude/skills/screenmap && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
screenmap
GitHub stars
239
Token cost
~7.2k tokens
SKILL.md length
3,673 words
Files
22 (incl. scripts, references)
Skills in repo
1
Repo updated
First seen
Licence
MIT

At a glance

Generate a visual navigation map of an Expo / React Native or NativeScript app.

  • Works in 6 steps: static parse → plan params and auth → boot the app → …
  • The user asks to map an Expo/React Native/NativeScript apps navigation
  • SKILL.md covers Flow recording (do this…, Phase 1 — static parse, Phase 2 — plan params and auth and Phase 3 — boot the app, plus 7 more sections
  • Runs JavaScript scripts from its folder; calls adb, node and xcrun

What it does

Screenmap is an agent skill from aleqsio/screenmap. Generate a visual navigation map of an Expo / React Native or NativeScript app. Statically parses routes and links (expo-router, react-navigation, NativeScript Angular Router or Core XML pages, or your own parser) for full coverage, then deep-links through every screen in the iOS simulator or Android emulator capturing screenshots — including runtime states like bottom sheet snap points and modals — and renders a self-contained HTML map. Both platforms can go into one map with a platform switcher. Also diffs two…

Its SKILL.md is about 7.2k tokens, which your agent loads only when the skill is triggered. The skill folder holds 27 other files, including scripts and reference files (for example `references/nativescript.md`).

It sits in Mobile, covering Cross-platform mobile apps and Mobile testing and debugging. It works with Expo, Android, iOS and React Native. The repository describes itself as: Visual navigation maps for Expo/React Native apps: agent-explored screenshots, flows, and an interactive .scrmap visualiser. The licence is MIT.

When your agent uses it

  • The user asks to map an Expo/React Native/NativeScript apps navigation
  • Wants a visual sitemap of their app
  • Wants to preview/review what a PR changes on-screen

Example prompts

  • “/screenmap”

Requirements

  • Node.js

Workflow steps

6 steps, taken from the step headings in SKILL.md.

  1. static parse
  2. plan params and auth
  3. boot the app
  4. route sweep
  5. runtime states (the part static analysis can't see)
  6. pack, render, deliver

What it can do on your machine

Read from SKILL.md and the folder at commit febe3f7. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships 13 files in scripts/ (JavaScript, from the files we listed), which the agent can run.

    Shell commands in SKILL.md call:

    • adb
    • node
    • xcrun
    • npx
    • git
    • gh
    • npm

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use npx, git, gh and npm, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Screenmap loads about 7.2k tokens when it runs, and up to ~8.5k if it reads all its reference files. Until then it costs about 207 tokens; SKILL.md has 3,673 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~207
When it runs · the whole SKILL.md, loaded when a task matches
~7.2k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~8.5k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.

SKILL.md

The full file from aleqsio/screenmap at commit febe3f7, republished under its MIT licence (© aleqsio). 3,673 words, ~7,244 tokens.

Download SKILL.mdSave it as .claude/skills/screenmap/SKILL.md (or your agent's skills folder). This skill also uses 21 other files; get the full folder from GitHub.
name
screenmap
description
Generate a visual navigation map of an Expo / React Native or NativeScript app. Statically parses routes and links (expo-router, react-navigation, NativeScript Angular Router or Core XML pages, or your own parser) for full coverage, then deep-links through every screen in the iOS simulator or Android emulator capturing screenshots — including runtime states like bottom sheet snap points and modals — and renders a self-contained HTML map. Both platforms can go into one map with a platform switcher. Also diffs two revisions into a PR preview (.diff.scrmap) showing which screens/edges were added, removed, or changed. Use when the user asks to map an Expo/React Native/NativeScript app's navigation, screens, or routes, wants a visual sitemap of their app, or wants to preview/review what a PR changes on-screen.

screenmap

Produce a visual map of an Expo / React Native or NativeScript app's navigation: every route as a card with a screenshot, runtime state variants (bottom sheets at each snap point, modals), and navigation edges between screens.

Arguments: optional path to the app project (default: current working directory). --static = skip the device phases and render a screenshot-less map. --platform ios|android|both (default ios) = which device(s) to capture on. pr <number> or diff <base>..<head> = PR diff mode (see bottom).

Working directory contract: all outputs go to <project>/.screenmap/out/ — graph.json, screens/*.png, flows/*.yaml + flows/*.meta.json, map.html. Suggest adding .screenmap/out/ to the project's .gitignore at the end.

Platform contract: with one platform, screenshots go to screens/<slug>.png as they always have. With --platform both, they go to screens/ios/<slug>.png and screens/android/<slug>.png, capture-status.json is keyed by platform first ({"android": {"<routeId>": …}}), and the bundle carries both so the viewer gets an iOS / Android switcher. Run the device phases once per platform, all the way through, before starting the next — never interleave them.

Device command table

Everything below that touches a device has a form per platform. <dev> is the iOS UDID or the Android serial (adb devices); with one device attached you can use booted on iOS and omit -s <dev> on Android.

iOSAndroid
list devicesxcrun simctl list devices bootedadb devices -l
deep linkxcrun simctl openurl <dev> "<url>"adb -s <dev> shell am start -a android.intent.action.VIEW -d '<url>'
screenshot to diskxcrun simctl io <dev> screenshot <path>adb -s <dev> exec-out screencap -p > <path>
relaunchxcrun simctl terminate <dev> <bundleId> then launchadb -s <dev> shell am force-stop <pkg> then monkey -p <pkg> -c android.intent.category.LAUNCHER 1
freeze status barxcrun simctl status_bar <dev> override …SystemUI demo mode (see D3)
reach host Metroworks as-isadb -s <dev> reverse tcp:8081 tcp:8081 first, or nothing loads
taps / swipesargent (--udid <dev>)argent (--udid <dev> — it takes an Android serial too)

Quote the Android deep link in single quotes: adb shell runs the string on the device, so an unquoted & in a query string backgrounds the command instead of passing it.

Flow recording (do this throughout Phases 4–5)

Every interaction sequence you perform is recorded as a replayable flow, written at the moment you perform it — not reconstructed afterwards. Flows use the argent flow format (argent.swmansion.com — Software Mansion's agentic mobile toolkit): a <name>.yaml argent flow plus a <name>.meta.json cartography sidecar, both in <project>/.screenmap/out/flows/. Anyone replays a flow headlessly, no LLM in the loop: npx @swmansion/argent flow run .screenmap/out/flows/<name>.yaml. Full pair schema: docs/scrmap-format.md in the skill repo.

yaml
# Open item details and expand the sheet to 50%
steps:
  - tool: open-url
    args:
      url: "myapp://details/42"
  - wait: 1500
  # Open sheet button
  - tap: { x: 0.4975, y: 0.4691 }
  - tool: gesture-swipe
    args: { fromX: 0.4975, fromY: 0.8009, toX: 0.4975, toY: 0.4828 }
json
{ "formatVersion": 2, "name": "details-sheet-50", "route": "details/[id]",
  "title": "Open item details and expand the sheet to 50%", "device": "iPhone 17 Pro",
  "steps": { "2": { "target": "Open sheet button" },
             "3": { "capture": "details_id--sheet-50.png", "note": "sheet at 50% snap" } } }

Rules:

  • If the argent MCP is connected, record through it (flow-start-recording / flow-add-step): every step executes live, only successful steps are recorded, and taps get durable selectors instead of coordinates. Write the sidecar yourself alongside. Without argent, write the YAML directly using normalized 0–1 coordinates (device points ÷ device point-size).
  • Sidecar steps is keyed by 0-based YAML step index. Every coordinate tap/swipe needs a target label (visible text or accessibility description). When a tap/swipe NAVIGATES to a different screen, set "screen": "<route id it landed on>" — this is how multi-screen flows stay traceable and observed edges get pinned. Screenshots are sidecar capture entries on the step they follow — never YAML steps.
  • Add top-level "landmarks": ["Explore", "Trending"] — 2–5 words that are visible on the arrival screen and identify it (a title, a section header, a fixed button label; not live content). Headless replays in CI OCR the end screen and check these words to detect flow drift — without landmarks a drifted flow can only be caught heuristically.
  • Simple deep-link visits from the sweep are flows too (generate them mechanically). Record dead ends you resolved with a note.
  • Never record credentials; argent supports {{secret:NAME}} placeholders if input is unavoidable.

Phase 1 — static parse

bash
node <this skill's dir>/scripts/parse-routes.mjs <project>

The parser picks a route provider for the project — expo-router, react-navigation, nativescript (Angular Router, Core XML pages, or the components an Octane / React / Vue / Svelte app mounts, pushes and presents), or a custom command — and prints which one it chose and why. See docs/route-providers.md for the full contract.

For a NativeScript project (mode is nativescript), read references/nativescript.md in this skill's directory now: deep links come from routes.links, there is no Metro, and it has its own state hints and PR-diff steps.

Read the produced <project>/.screenmap/out/graph.json and report the summary to the user: the provider (mode), route count, layouts (with navigator types), edges (flag unresolved ones), routes with state hints, routes needing params, and routes with no deep link (navigationOnlyRoutes).

If the parser exits saying no provider recognised the project, or that two fit equally well, do not guess. Show the user the detection table it printed and ask which to use, then re-run with --provider <id>:

bash
node <this skill's dir>/scripts/parse-routes.mjs <project> --detect          # scores only
node <this skill's dir>/scripts/parse-routes.mjs <project> --provider react-navigation

A project whose screens are registered somewhere none of the providers read — a generated route table, a home-grown router — is not a dead end: write a small script that emits the graph fragment documented in docs/route-providers.md and point .screenmap/config.json at it with {"routes":{"provider":"custom","command":"…"}}.

If --static was requested, jump to Phase 6.

Phase 2 — plan params and auth

  • For each route with params, pick a sample value: prefer concrete values found in resolved edges' raw hrefs (e.g. /details/42 → id=42), then seed/fixture data in the repo, else 1. Record the substitution you'll use in deep links.
  • Skim the graph for routes that are likely auth-gated (segments like (auth), login, sign-in, or a root layout with a redirect). Expect those to redirect during the sweep; that's fine — capture what actually renders and mark it in your report.

Phase 3 — boot the app

Run this phase once per platform.

iOS

  1. xcrun simctl list devices booted — check for a booted simulator.
  2. Call the iOS simulator MCP attach action FIRST so the user can watch (harmless error if nothing is booted yet — boot/build, then retry attach).
  3. Get the app running, preferring what already exists:
    • If Metro is already running and the app is open in the simulator, use it as-is.
    • Else start Metro in the background (npx expo start via background Bash from the project dir).
    • If a dev build of the app is installed on the simulator, launch it. If only Expo Go is available, open the project in it. If neither, npx expo run:ios (warn the user this builds and takes minutes), or fall back to web (see bottom).
  4. Verify deep linking before sweeping. Open the root route with the scheme from graph.json:
    • dev build: xcrun simctl openurl booted "<scheme>://"
    • Expo Go: xcrun simctl openurl booted "exp://127.0.0.1:8081/--/" Take an MCP screenshot to confirm the app rendered (not a crash/error screen). Use whichever URL form worked for the rest of the run.

Android

There is no Android equivalent of the iOS simulator MCP, so the user watches the emulator window itself — say so rather than promising a live panel. The android-debugging skill, if available, covers adb troubleshooting in more depth.

  1. adb devices -l — check for an attached device or a running emulator. If none, list AVDs with emulator -list-avds and start one in the background: emulator -avd <name> -no-snapshot -no-boot-anim &. If adb/emulator are not on PATH, they are under $ANDROID_HOME/platform-tools and $ANDROID_HOME/emulator.
  2. Wait for the boot to finish — adb wait-for-device only waits for adb to see it, so poll until adb shell getprop sys.boot_completed returns 1, then dismiss the lock screen with adb shell input keyevent 82. Installing before that fails in ways that read as a broken APK.
  3. adb reverse tcp:8081 tcp:8081. The emulator's localhost is the emulator; without the tunnel the app cannot reach Metro on the host and nothing will load. Redo it after any emulator restart.
  4. Get the app running: start Metro in the background as above, then launch the installed dev build (adb shell monkey -p <pkg> -c android.intent.category.LAUNCHER 1). If no dev build is installed, npx expo run:android (warn the user this builds and takes minutes).
  5. Verify deep linking before sweeping, same as iOS: adb shell am start -a android.intent.action.VIEW -d '<scheme>://', then screenshot to disk and look at it.

Phase 4 — route sweep

Routes with "reach": "navigation-only" have no deep link at all — normal for react-navigation screens that are absent from the linking config. Do not deep-link them and do not visit the app root in their place: that captures the home screen under the wrong route's name, which is exactly the kind of silent bad capture Phase 4b exists to catch. Skip them in this phase, record {"needsNavigation": true} for them in capture-status.json, and reach them by tapping in Phase 5b — their nav flow is their capture.

For each route with a URL (substituting params from Phase 2; for +not-found, deep-link a garbage path like /definitely-not-a-route):

  1. Open the route's deep link (see the device command table).
  2. Wait ~1–1.5s for the transition (MCP wait, or just sleep). Content screens that fetch over the network need 3–4s — a capture showing a spinner or loading skeleton means the wait was too short, not that the route is broken; the Phase 4b review catches these, and you re-capture with a longer wait.
  3. Capture to disk at <project>/.screenmap/out/screens/<slug>.png (or screens/<platform>/<slug>.png when capturing both) — use the exact slug from graph.json; the renderer depends on this naming. (The iOS MCP screenshot is for your own eyes only; it doesn't save a file.)
  4. Every few routes, sanity-check that you're capturing real screens — read a capture back with the Read tool, or on iOS take an MCP screenshot. If a route shows a red error screen, an error boundary, or redirected somewhere else, still keep the capture but note it for the final report.
Phase 4b — review and recover (do not skip)

Error boundaries stick: once a bad deep link crashes a screen, subsequent deep links may render into the same error boundary, silently poisoning every capture after it. So before delivering:

  1. Render a draft map (Phase 6 commands) and look at it — it doubles as a contact sheet of all captures.

  2. Classify every capture that isn't clearly the real screen, and write the verdicts to <project>/.screenmap/out/capture-status.json (the renderer badges them on the map):

    json
    { "<routeId>": { "status": "not-found", "needsNavigation": true, "note": "why + what a future agent should do instead" } }

    Statuses: ok · empty-state · not-found · error-boundary · loading · auth-wall. Decide per capture:

    • Loading spinner/skeleton — wait was too short OR the param is synthetic. Re-capture with 3–4s; if still loading, the data doesn't exist → treat as not-found.
    • Not-found / empty "Oops" screen — your sample param doesn't resolve to real data. First try to find real params (public APIs, seed data, values seen in other captures — e.g. a real starter-pack rkey from the account's public profile). Only if no real value is obtainable, keep the capture, set needsNavigation: true, and say in the note how the screen is actually reached (opened from a shared link, requires user-owned data, etc.).
    • Genuine empty state — the screen rendered correctly but the account has no content (e.g. "Nothing saved yet"). That IS the screen; status empty-state, no needsNavigation. Don't confuse this with not-found.
    • Error boundary — the route can't render from a bare deep link at all (missing runtime params). Keep as finding, needsNavigation: true. Beware the poisoned tail: error boundaries stick, so captures taken after a crash may show the same stuck error — relaunch the app (xcrun simctl terminate booted <bundleId>, relaunch, wait for the bundle) and re-capture those.
  3. Re-render after recovery.

Phase 5 — runtime states (the part static analysis can't see)

For each route whose stateHints is non-empty, deep-link to it again and:

bottom-sheet — Read the route's source file (file in graph.json) to find what opens the sheet (a button whose onPress calls ref.expand() / present(); some sheets are open by default — check the index prop). Take an MCP screenshot, locate the trigger, tap it. Then for each snap point in the hint (e.g. 25%, 50%, 90%):

  • drag the sheet to that height: swipe from the sheet handle's current position to y ≈ screen_height × (1 − snap). Start the swipe well inside the screen — a start point within 4pt of an edge triggers an OS edge gesture instead.
  • capture screens/<slug>--sheet-<value>.png (e.g. details_id--sheet-50.png — strip the %).

rn-modal — find and tap the trigger, capture screens/<slug>--modal.png, then dismiss (close button, tap outside, or just deep-link away).

router-modal — already captured as its own route in Phase 4; nothing extra needed.

Rules for this phase:

  • Re-deep-link between routes to reset state; don't let one screen's leftover state bleed into the next capture.
  • Never tap destructive or irreversible controls: delete, remove, sign out, purchase, send, submit. If a sheet can only be opened via such a control, skip it and note why.
  • If you type into inputs to reach a state, use obviously fake data.
  • A hint is advisory — if you can't find the trigger in 2–3 attempts, skip it and note it. Also: sheets defined in shared components aren't in the hints; if you see an obvious sheet/modal trigger while on a screen, capture it as a bonus state.
  • The hint list is a work queue, not a suggestion box. Phase 5 is not done until every hinted route is either captured or explicitly skipped with a reason. End the phase with a coverage line — state hints: N routes · captured M · skipped K (list + why) — and carry it into the Phase 6 delivery report. A state pass that quietly processes 3 of 17 hints looks complete on the map and isn't; that silence has bitten before.
Show full SKILL.md (1,479 more words)Show less

Phase 5b — navigation flows (the tap path to every screen)

Deep links are shorthands; the map's primary flow for each screen is the path a human takes. For every reachable route, record flows/nav-<slug>.yaml + sidecar (name nav-<slug>, title "Navigate to <title>") that reaches it from app launch using real taps:

  • Step 1 is always open_url to the app root (scheme://) — that's the app entry, not a shortcut. Everything after is taps/swipes.
  • Every navigating tap records three things: coordinate (device points), target (durable label), and screen (the route id it landed on). Verify the landing with an MCP screenshot BEFORE writing the step — a wrong screen poisons the graph.
  • Walk the app as a tree, depth-first, reusing prefixes: e.g. record the drawer → Settings hop once, then each settings row extends it. Each flow file is still self-contained (repeats its prefix steps).
  • Transient states get captures too. When a tap changes the UI without changing route (opens a drawer, menu, sheet), capture that state ONCE as a state variant of the source screen (<sourceSlug>--<state>.png, e.g. Home--drawer.png) and insert a screenshot step referencing it right after the opening tap in every flow that passes through it. Viewers then render the ripple for the next tap on the drawer capture instead of the closed-drawer base screen.
  • End each flow with a screenshot step of the target (<slug>.png).
  • Screens with no discoverable in-app path (deep-link-only, or requiring data the account lacks) — record that in capture-status notes instead of forcing it.

These flows are what make edges pinnable: a nav flow tapping through a transition tells the map viewer exactly where the trigger sits on the source screen.

Phase 6 — pack, render, deliver

bash
# downscale (sips is macOS; on Linux use `mogrify -resize '800x800>' <files>`)
for f in <project>/.screenmap/out/screens/*.png; do sips -Z 800 "$f" >/dev/null; done
node <this skill's dir>/scripts/pack-map.mjs <project>        # → .screenmap/out/<app>-<date>.scrmap bundle
node <this skill's dir>/scripts/render-map.mjs <project>/.screenmap/out/graph.json   # static HTML fallback

Capturing both platforms: downscale each screens/<platform>/ directory, then pass the platforms to the packer — node .../pack-map.mjs <project> --platforms ios,android. The bundle then carries both and the viewer shows an iOS / Android switcher; report coverage per platform, since a screen can be fine on one and broken on the other.

The .scrmap bundle (zip: manifest.json + map.json + screens/) is the primary deliverable — see docs/scrmap-format.md in the skill repo. Open it in the map viewer (apps/visualiser in the skill repo, npm run dev, drag the bundle in): interactive graph, flow playback, click-to-copy replay commands. Send the bundle with SendUserFile; send map.html too as the no-tooling fallback (display: render). Report: routes captured / total, state variants captured, anything skipped (error screens, auth redirects, un-triggerable sheets), unresolved edges. Offer to publish as an Artifact (if so, load the artifact-design skill first and rebuild the page body-only per Artifact rules — don't publish the full-document HTML as-is). Suggest adding .screenmap/out/ to .gitignore.

Replay mode — /screenmap replay <flow-name>

Flows are argent YAML, so the primary replay is headless:

bash
npx @swmansion/argent flow run <project>/.screenmap/out/flows/<flow-name>.yaml

Run that first (it needs no LLM and reports pass/fail per step; add --device <id> to pick a device, and argent takes an Android serial wherever it takes an iOS UDID). Fall back to manual replay only when argent isn't installed and can't be (npx unavailable) or when the flow fails and the user wants a diagnosis: execute the YAML steps yourself — open-url/wait via the device command table, taps/swipes via the simulator MCP (iOS) or adb shell input tap <x> <y> in device pixels (Android), using the sidecar's target labels as the source of truth (recorded coordinates are hints that may have drifted).

A flow recorded on one platform is not guaranteed to replay on the other: coordinates are normalized, but layouts, system chrome heights and back-navigation differ. Record per platform when you capture both, and name the platform in the sidecar's device field. Verify each step with an MCP screenshot; if a target can't be found in 3 attempts, stop and report which step failed and what the screen showed instead. Same safety rules as Phase 5: never trigger destructive or submitting controls.

PR diff mode — /screenmap pr <number> or /screenmap diff <base>..<head>

Preview what a change does to the app's navigation surface: which screens were added, removed, or changed, which edges appeared or vanished, with before/after screenshots. The deliverable is a .diff.scrmap bundle (format: docs/diff-scrmap-format.md in the skill repo) — the map viewer renders it with green/amber/red highlights, a Changes panel, and base-vs-head comparison per screen.

Working directory: <project>/.screenmap/out/diff/<slug>/ where slug is pr-<number> or <base>..<head>. Verdicts are static-only (a screen is "changed" iff the change set touches its file or import closure); screenshots are evidence for the reviewer, not input to the classification.

D1 — resolve the two revisions
  • PR form: gh pr view <n> --json number,title,url,baseRefName,headRefName,mergeCommit,files. For a merged PR, head = the merge commit, base = its first parent (<merge>^). For an open PR, gh pr view --json headRefOid,baseRefOid. Write pr.json ({number,title,url,baseSha,headSha,baseRef,headRef}) and changed-files.txt (gh pr diff <n> --name-only, or git diff --name-only <base> <head>).
  • Ref form: resolve both refs with git rev-parse; same files, no number.
  • Shallow clones: git fetch --depth 1 origin <sha> for any SHA the repo doesn't have.
  • Native guard: if changed files touch ios/, android/, patches/, or change native deps in package.json, warn the user that the installed dev build may not match both sides — JS-only diffs are the supported case. Proceed only if they accept. A change under ios/ only affects the iOS side and one under android/ only the Android side, so say which platform's captures to distrust rather than both.
  • The project must have a clean tree (or the user agrees to git stash). Remember the original ref; restore it at the end, always — even after failures.
D2 — parse both sides, pick suspects
bash
git -C <project> checkout --detach <baseSha>
node <skill>/scripts/parse-routes.mjs <project>   # writes .screenmap/out/graph.json
cp <project>/.screenmap/out/graph.json <diffDir>/base/graph.json
git -C <project> checkout --detach <headSha>
node <skill>/scripts/parse-routes.mjs <project>
cp <project>/.screenmap/out/graph.json <diffDir>/head/graph.json
node <skill>/scripts/diff-map.mjs suspects <diffDir> --project <project>

Read suspects.json and report the work-list to the user before capturing: N added, N removed, N modified (with reasons and via-files), plus any broad files excluded from expansion. That report alone is already a useful static preview — if the user asked for --static, skip to D4.

Then read the PR's actual diff for each suspect's via-files and write <diffDir>/notes.json — { "<nodeId>": "what visibly changes on this screen" }, one plain sentence per suspect (e.g. "Trending topic pills become full-width ranked rows"). Viewers show this note on the screen's diff card; without it the card only says "file-touched", which tells a reviewer nothing.

If reading the diff convinces you a statically-flagged suspect has no visible change (shared import only, pure refactor, code path that doesn't render there), use { "note": "why it's unaffected", "verdict": "unaffected" } instead — pack drops it from the changed list into diff.json's dismissed section (omitted from the viewer's Changes list, kept in the bundle for audit). You can also skip capturing that screen. Dismiss only on positive evidence from the diff, not on a hunch — when unsure, keep it and let the captures decide.

Status is tracked per capture state, not just per screen. When a screen's change lives in one state (below the fold, inside a sheet), say so with per-state notes — "" is the bare screen, other keys are variant names:

json
"Settings": { "note": "A 'Beta features' row is added between Languages and Help.",
  "states": {
    "": { "note": "Top of the list is identical — the row is below the fold.", "verdict": "unaffected" },
    "bottom": "New 'Beta features' row appears between Languages and Help." } }

The viewer marks each state in the node's dropdown (± bottom etc.), so capture the state variants that make the change visible (Phase 5 style) — a diff whose change is below the fold and has no scrolled state variant shows two identical screenshots.

D3 — targeted capture, base then head

Boot the app (Phase 3), then freeze the status bar so both sides capture identically (clock noise otherwise pollutes every pixel comparison):

bash
# iOS
xcrun simctl status_bar booted override --time "9:41" --dataNetwork wifi --wifiMode active --wifiBars 3 --cellularMode active --cellularBars 4 --batteryState charged --batteryLevel 100

# Android — SystemUI demo mode is the equivalent
adb shell settings put global sysui_demo_allowed 1
adb shell am broadcast -a com.android.systemui.demo -e command enter
adb shell am broadcast -a com.android.systemui.demo -e command clock -e hhmm 0941
adb shell am broadcast -a com.android.systemui.demo -e command battery -e level 100 -e plugged false
adb shell am broadcast -a com.android.systemui.demo -e command network -e wifi show -e level 4
adb shell am broadcast -a com.android.systemui.demo -e command notifications -e visible false

Then for each side in order base → head:

  1. git checkout --detach <sha>, restart Metro (kill the background process, start again), and relaunch the app; verify a deep link renders the right revision.
  2. Capture only the suspect list for that side (side: both|base for base, both|head for head), Phase 4 style, into <diffDir>/<side>/screens/<slug>.png. Same waits, same 4b review discipline; verdicts go to <diffDir>/<side>/capture-status.json.
  3. For suspects with stateHints — and any state the diff added (states with reason hint) — do a scoped Phase 5 pass so state variants land as <slug>--<state>.png on the right side.

Keep both sides comparable: same device, same account, same waits.

D4 — pack and deliver
bash
for f in <diffDir>/{base,head}/screens/*.png; do sips -Z 800 "$f" >/dev/null; done
node <skill>/scripts/diff-map.mjs pack <diffDir> --device "<device name>"

# both platforms: screens live at <diffDir>/<side>/screens/<platform>/, and
# --device takes one name per platform in the same order
node <skill>/scripts/diff-map.mjs pack <diffDir> --platforms ios,android --device "iPhone 17 Pro,Pixel 7"

Restore the original ref. Send the .diff.scrmap with SendUserFile; report the diff table (added/modified/removed screens, edge changes, state changes, broad-file blind spots, anything uncapturable). The bundle opens in the same map viewer as .scrmap files (drag it in). If a full .scrmap of the app exists, tell the user to load both — the map viewer overlays the diff on the full map, so unchanged screens keep their real screenshots (dimmed) and changed screens flip base⇄head in place (hover for a red changed-pixels render).

Web fallback (no simulator or emulator available, or user asks for web)

  • Start npx expo start --web, confirm http://localhost:8081/_sitemap lists the same routes as the parse (good cross-check).
  • Capture each route that has a urlPath with npx playwright screenshot --viewport-size=390,844 "http://localhost:8081<urlPath>" <project>/.screenmap/out/screens/<slug>.png (needs npx playwright install chromium once; ask before installing).
  • State pass on web: drive the browser pane manually (tap triggers), but note playwright captures are the ones saved to disk — for sheet states, prefer npx playwright screenshot --full-page after using its --wait-for-timeout or skip and note the limitation.

© aleqsio, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 21 other files (scripts, references) in plugins/screenmap/skills/screenmap of aleqsio/screenmap.

  • SKILL.md
  • references/nativescript.md
  • scripts/convert-flows.mjs
  • scripts/diff-map.mjs
  • scripts/pack-map.mjs
  • scripts/parse-routes.mjs
  • scripts/render-map.mjs
  • scripts/routes/lib/graph.mjs
  • scripts/routes/lib/hints.mjs
  • scripts/routes/lib/link-sources.mjs
  • scripts/routes/lib/literals.mjs
  • scripts/routes/lib/project.mjs
  • scripts/routes/providers/custom.mjs
  • scripts/routes/providers/expo-router.mjs
  • scripts/routes/providers/nativescript/angular.mjs
  • … and 7 more

Open the folder on GitHubat commit febe3f7

Compare with similar skills

Screenmap next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Screenmap compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Screenmap this skillaleqsio/screenmap239—~7.2kAutomated safety check: PassMIT
ThemingCode-with-Beto/skills141—~2.5kAutomated safety check: PassNone
Expo Development on Limrunsuperset-sh/superset15k—~3.6kAutomated safety check: PassCustom licence
Fishjam React Native Clientsoftware-mansion-labs/skills291—~1.9kAutomated safety check: PassMIT
Expo Tailwind SetupCherryHQ/cherry-studio-app4k8 repos~3kAutomated safety check: PassMIT
Expo Brownfield Integrationmweinbach/agent-coworker1562 repos~900Automated safety check: NotesCustom licence

Similar skills

  • Theming

    Code-with-Beto/skills

    Scaffold a unified, cross-platform color theme system into an Expo Router app.

    141 GitHub stars~2.5k tokensUpdated 2 mo ago
    MobileAuto-check passed
  • Expo Development on Limrun

    superset-sh/superset

    Sets up and runs Expo and React Native apps on Limrun remote iOS simulators and Android emulators, using dev-client builds for fast JS and TS iteration.

    15k GitHub stars~3.6k tokensUpdated today
    MobileAuto-check passed
  • Fishjam React Native Client

    software-mansion-labs/skills

    React Native / Expo SDK for Fishjam — video/audio streaming on iOS and Android.

    291 GitHub stars~1.9k tokensUpdated 9 days ago
    MobileAuto-check passed
  • Expo Tailwind Setup

    CherryHQ/cherry-studio-app

    Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling

    4k GitHub starsUsed in 8 repos~3k tokens
    MobileAuto-check passed
  • Expo Brownfield Integration

    mweinbach/agent-coworker

    Helps add Expo and React Native to an existing native iOS or Android app, and choose between a prebuilt AAR or XCFramework and a fully integrated build.

    156 GitHub starsUsed in 2 repos~900 tokens
    MobileAuto-check: notes
  • Simulator Audio E2E

    hyochan/react-native-nitro-sound

    Build and run repeatable react-native-nitro-sound recorder/player regression tests on an iOS Simulator or Android emulator, with explicit virtual-device selection, microphone permission, Maestro…

    961 GitHub stars~1.1k tokensUpdated 7 days ago
    MobileAuto-check passed

Categories

Questions about Screenmap

What does Screenmap do?

Generate a visual navigation map of an Expo / React Native or NativeScript app. Screenmap is an agent skill from aleqsio/screenmap. Generate a visual navigation map of an Expo / React Native or NativeScript app.

When should I use Screenmap?

Screenmap fits situations like: the user asks to map an Expo/React Native/NativeScript apps navigation; wants a visual sitemap of their app; wants to preview/review what a PR changes on-screen.

How do I install Screenmap in Claude Code?

Run `npx skills add aleqsio/screenmap --skill screenmap -a claude-code`. Or copy the skill folder (plugins/screenmap/skills/screenmap in aleqsio/screenmap) into .claude/skills/screenmap in your project. Claude Code loads it when a task matches its description.

How do I install Screenmap in Codex?

Run `npx skills add aleqsio/screenmap --skill screenmap -a codex`. Or copy the skill folder (plugins/screenmap/skills/screenmap in aleqsio/screenmap) into .agents/skills/screenmap in your project. Codex loads it when a task matches its description.

Can I use Screenmap in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add aleqsio/screenmap --skill screenmap -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/screenmap, .gemini/skills/screenmap, .github/skills/screenmap and .opencode/skills/screenmap in your project.

What does Screenmap need to run?

Going by SKILL.md and its folder, Screenmap needs JavaScript for the scripts in its folder and the command-line tools its instructions call (adb, node, xcrun, npx, git and gh). Our summary lists: Node.js.

Does Screenmap access the network?

SKILL.md contains no URLs. Its commands use npx, git, gh and npm, which can reach the network depending on how they are called. This is read from the text; nothing was executed.

Is Screenmap safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.

What licence does Screenmap use?

Screenmap is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Screenmap use?

About 7.2k tokens (SKILL.md is roughly 29k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 1.3k tokens, read only when the agent opens those files.

What are the alternatives to Screenmap?

Skills that share tags, products or a category with Screenmap: Theming (Code-with-Beto/skills, 141 stars), Expo Development on Limrun (superset-sh/superset, 15k stars), Fishjam React Native Client (software-mansion-labs/skills, 291 stars) and Expo Tailwind Setup (CherryHQ/cherry-studio-app, 4k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Screenmap?

aleqsio (a GitHub user) maintains it in aleqsio/screenmap, which has 239 GitHub stars. The repository was last updated on October 4, 2026.

Source: aleqsio/screenmap on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.