--- 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 `