---
description: Download all design screenshots, animations, and flow metadata from Mobbin for an app (iOS + Web). Navigates, injects resource tracker, scrolls, extracts screens/animations/flows, and bulk-downloads watermark-free images.
category: personal
---
flowchart TD
_HEADER_["
Mobbin Harvest
Download all design screenshots, animations, and flow metadata from Mobbin for an app (iOS + Web). Navigates, injects resource tracker, scrolls, extracts screens/animations/flows, and bulk-downloads watermark-free images.
"]:::headerStyle
classDef headerStyle fill:none,stroke:none
subgraph _MAIN_[" "]
%% Phase 0: Setup
subgraph Setup["Phase 0: Setup"]
INPUT[App Name
from arguments] --> SEARCH[Search Mobbin
for App URLs]
SEARCH --> SET_VARS[Set Variables
APP_SLUG, IOS_URL,
WEB_URL, dirs]
SET_VARS --> MKDIR[Create Output
Directories]
end
%% Phase 1: Browser Setup
subgraph BrowserSetup["Phase 1: Browser Setup"]
MKDIR --> GET_TABS[Get Current
Tab Context]
GET_TABS --> CREATE_TAB[Create New Tab
per Platform]
CREATE_TAB --> NAV[Navigate to
Screens URL]
NAV --> AUTH_CHECK{Logged In?}
AUTH_CHECK -->|Yes| READY[Tab Ready]
AUTH_CHECK -->|No| LOGIN[Email Login
joe at schlesinger.io]
LOGIN --> FETCH_OTP[Fetch OTP
via gog-cli Gmail]
FETCH_OTP --> ENTER_OTP[Enter OTP
in Browser]
ENTER_OTP --> READY
end
%% Phase 2: Inject Tracker
subgraph InjectTracker["Phase 2: Inject Tracker"]
READY --> INJECT[Inject JS via
javascript_tool]
INJECT --> PERF_OBS[PerformanceObserver
Watches Resources]
INJECT --> FETCH_HOOK[Fetch Interceptor
Fallback]
PERF_OBS --> TRACKING[Tracking Active
screens + animations]
FETCH_HOOK --> TRACKING
end
%% Phase 3: Scroll to Bottom
subgraph ScrollPhase["Phase 3: Scroll to Bottom"]
TRACKING --> SCROLL_LOOP[Scroll Down
80-120 times]
SCROLL_LOOP --> CHECK_PROGRESS{Every 20 Scrolls:
Count Stable?}
CHECK_PROGRESS -->|Count increasing| SCROLL_LOOP
CHECK_PROGRESS -->|Stable 3x or
footer visible| SCROLL_DONE[Scrolling
Complete]
end
%% Phase 4: Freeze and Extract
subgraph ExtractPhase["Phase 4: Freeze and Extract Data"]
SCROLL_DONE --> FREEZE[Freeze Data
into window.name]
FREEZE --> BATCH_EXTRACT[Extract in
Batches of 12]
BATCH_EXTRACT --> WRITE_PAIRS[Write Pairs File
screenId imgId]
FREEZE --> EXTRACT_ANIMS[Extract Animation
IDs]
EXTRACT_ANIMS --> WRITE_ANIMS[Write Anims File]
end
%% Phase 5: Download
subgraph DownloadPhase["Phase 5: Download Screens and Animations"]
WRITE_PAIRS --> DL_SCREENS[Download Screens
15 parallel curl]
WRITE_ANIMS --> DL_ANIMS[Download Animations
via /raw/ path]
DL_SCREENS --> DL_DONE[Downloads
Complete]
DL_ANIMS --> DL_DONE
end
%% Phase 6: Flow Metadata
subgraph FlowPhase["Phase 6: Flow Metadata"]
DL_DONE --> NAV_FLOWS[Navigate to
/flows Page]
NAV_FLOWS --> REEXEC[Re-execute Inline
Script with Interceptor]
REEXEC --> PARSE_FLOWS[Parse Flow JSON
Bracket Matching]
PARSE_FLOWS --> SLIM[Slim to id, name,
parentId, screens]
SLIM --> EXPORT_FLOWS{Payload Size?}
EXPORT_FLOWS -->|Small| BATCH_SLICE[Batch Slice
Extraction]
EXPORT_FLOWS -->|Large >50K| BLOB_DL[Blob Download
to ~/Downloads]
BATCH_SLICE --> WRITE_FLOWS[Write flows.json]
BLOB_DL --> WRITE_FLOWS
end
%% Phase 7: Verify
subgraph VerifyPhase["Phase 7: Verify"]
WRITE_FLOWS --> VERIFY_FILES[Count Files
on Disk]
DL_DONE --> VERIFY_FILES
VERIFY_FILES --> DIFF_CHECK{All Tracked
Screens Present?}
DIFF_CHECK -->|Missing| REPORT_MISSING[Report Missing
Screen IDs]
DIFF_CHECK -->|All present| DONE([Platform Done])
end
%% Multi-app queue
subgraph MultiApp["Multi-App Queue"]
DONE --> NEXT_APP{More Apps
in Queue?}
NEXT_APP -->|Yes| UPDATE_GOAL[Update GOAL.md
Mark Done]
UPDATE_GOAL --> INPUT
NEXT_APP -->|No| ALL_DONE([All Apps
Harvested])
end
click INPUT "#" "**App Name**\nThe app to harvest, passed as arguments.\nExamples: `linear`, `notion`, `figma`\n\nIf not provided, ask the user for the Mobbin URL(s)."
click SEARCH "#" "**Search Mobbin**\nNavigate to https://mobbin.com and search for the app.\n- Find the iOS screens URL (ends in `/screens`)\n- Find the Web screens URL (ends in `/screens`)\n- Some apps only have one platform."
click SET_VARS "#" "**Set Variables**\nInfer from the app name:\n- `APP_SLUG` = lowercase, no spaces\n- `IOS_URL` = iOS screens URL (or empty)\n- `WEB_URL` = Web screens URL (or empty)\n- `IOS_DIR` = `~/gitea/mobin/
/ios`\n- `WEB_DIR` = `~/gitea/mobin//web`"
click MKDIR "#" "**Create Output Directories**\n`mkdir -p ~/gitea/mobin//{ios,web}`\n\nAlso creates animation subdirs:\n`~/gitea/mobin//ios/animations`\n`~/gitea/mobin//web/animations`"
click GET_TABS "#" "**Get Current Tab Context**\nUse `mcp__claude-in-chrome__tabs_context_mcp`\nto discover the current browser state before\ncreating new tabs."
click CREATE_TAB "#" "**Create New Tab**\nUse `mcp__claude-in-chrome__tabs_create_mcp`\nto open a fresh tab for each platform (iOS, Web)."
click NAV "#" "**Navigate to Screens URL**\nGo to the platform-specific screens page.\nURL ends in `/screens`."
click AUTH_CHECK "#" "**Logged In?**\nCheck if the page shows a login wall or redirect.\nMobbin sessions expire periodically."
click LOGIN "#" "**Email Login**\nClick login button, enter `joe@schlesinger.io`.\nMobbin uses passwordless OTP login."
click FETCH_OTP "#" "**Fetch OTP via gog-cli**\nUse gog-cli to search Gmail for the Mobbin OTP:\n`gog gmail search 'from:mobbin subject:sign in newer_than:5m'`\nExtract the 6-digit code from the email body."
click ENTER_OTP "#" "**Enter OTP**\nType the 6-digit OTP code into the browser\nlogin form to complete authentication."
click READY "#" "**Tab Ready**\nBrowser tab is authenticated and showing\nthe screens page. Ready for tracker injection."
click INJECT "#" "**Inject JavaScript Tracker**\nInject via `javascript_tool` BEFORE scrolling.\nSets up two capture mechanisms:\n- PerformanceObserver (primary)\n- Fetch interceptor (fallback)\n\nStores captured URLs in `window._mobbinScreens` Map."
click PERF_OBS "#" "**PerformanceObserver**\nPrimary capture mechanism.\nMobbin loads images via `
` tags (browser-level\nHTTP requests that bypass `window.fetch`).\n`PerformanceObserver({ entryTypes: ['resource'] })`\ncatches ALL browser resource loads."
click FETCH_HOOK "#" "**Fetch Interceptor**\nFallback capture. Wraps `window.fetch` to track\nany programmatic image/animation requests.\nSecondary to PerformanceObserver."
click TRACKING "#" "**Tracking Active**\nTracker is capturing:\n- Screen images from `app_screens/` URLs\n- Animation MP4s from `/content/animations/`\n\nStored in `window._allEntries` and `window._animEntries`."
click SCROLL_LOOP "#" "**Scroll Down**\nUse `computer` tool: scroll action, direction down,\nscroll_amount 10. Repeat 80-120 times with 0.3s\npauses between scrolls.\n\nMobbin uses virtual scrolling -- only ~19 screens\nvisible at once. Must scroll to load all."
click CHECK_PROGRESS "#" "**Check Progress**\nEvery 20 scrolls, check tracker count:\n`'screens=' + window._allEntries.length`\n\nStop conditions:\n- Count stable for 3 consecutive checks\n- 'All screens' footer text visible\n- 'Other apps' section visible"
click SCROLL_DONE "#" "**Scrolling Complete**\nAll screens have been loaded and tracked.\nThe tracker map contains every screen ID\nand its corresponding image ID."
click FREEZE "#" "**Freeze Data**\nStore all entries in `window.name`:\n`window.name = window._allEntries.map(e=>e[0]+' '+e[1]).join('\\n')`\n\n`window.name` survives JS output truncation\nand can be extracted in batches."
click BATCH_EXTRACT "#" "**Extract in Batches of 12**\nExtract pairs via `window.name.split('\\n').slice(I, I+12)`\nincrementing by 12 each call.\n\nEach batch of 12 UUID pairs is ~888 chars\n(safely under the ~950 char JS output limit)."
click WRITE_PAIRS "#" "**Write Pairs File**\nWrite to `/tmp/__pairs.txt`\nOne `screenId imgId` pair per line.\nThis drives the download phase."
click EXTRACT_ANIMS "#" "**Extract Animation IDs**\nGet animation entries from `window._animEntries`.\nThese are MP4 files from `/content/animations/`."
click WRITE_ANIMS "#" "**Write Anims File**\nWrite to `/tmp/__anims.txt`\nOne animation ID per line."
click DL_SCREENS "#" "**Download Screens (15 parallel)**\nBulk download watermark-free images:\n`curl -s -o .png /.png?f=webp...`\n\nBase URL: `bytescale.mobbin.com/FW25bBB/image/...`\nParams: `f=webp, w=1920, q=85, fit=shrink-cover`\n15 concurrent downloads, wait every 15."
click DL_ANIMS "#" "**Download Animations**\nDownload MP4 files using `/raw/` path.\n\nIMPORTANT: Always use `/raw/` NOT `/video/`.\n`/video/` triggers Bytescale async processing\nand returns a 335-byte JSON job response\ninstead of the actual MP4 file."
click DL_DONE "#" "**Downloads Complete**\nAll screen PNGs and animation MP4s\nhave been downloaded to the output directory."
click NAV_FLOWS "#" "**Navigate to /flows**\nChange the URL from `/screens` to `/flows`\nto access the flow metadata page."
click REEXEC "#" "**Re-execute Inline Script**\nThe `self.__next_f` array is cleared after React hydration,\nbut the `