The captures and rendered images moved to ~/Code/Projects/folo-store-media, and checkouts link them as store-assets/captures and store-assets/output. The release promo skill now checks that folder and finds linked captures, and the ignore rules match the links as well as directories. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Store assets
Screenshots, artwork and listing copy for the App Store (iPhone, iPad, Mac), Google Play and the Microsoft Store. Slides are HTML templates rendered with Playwright from real app captures, so every locale shares one design that follows the folo.is landing page (warm paper, orange gradient).
listing/<locale>/ Store copy per listing locale (names, subtitles, descriptions,
keywords, release notes, screenshot headlines)
demo-account/ Curated subscriptions per UI locale, capture settings, avatar
promo/ Release promo video specs (see Release promo video)
src/ Brand tokens, device frames, slide layouts, automation helpers
scripts/ Entry points (see below)
captures/ Raw app captures, captures/<ui locale>/<device>/<scene>.png (ignored)
output/ Rendered store images, output/<store>/<device>/<store locale>/ (ignored)
Locales
src/locales.ts maps each listing locale to store locale codes. The five app UI languages (en, zh-Hans, zh-Hant, ja, fr) get screenshots of a localized app. The other listing locales (de, es, es-MX, pt-BR, ko, it, ru) have translated copy and headlines over the English captures.
Workflow
Run everything from the repository root with pnpm exec tsx. The scripts' dependencies are declared in store-assets/package.json (a private workspace package); Playwright's Chromium needs a one-time pnpm exec playwright install chromium.
- Check the copy.
store-assets/scripts/check-listing.ts [locale...]validates store length limits and policy rules (no competitor names, no ranking or price claims on Google Play, no URLs in Microsoft Store text). - Switch the demo account's subscriptions to one UI locale:
store-assets/scripts/demo-account.ts <locale> [--dry-run] [--reset] [--park-videos] [--skip-translation]. It talks to the signed-in desktop app over CDP (port 9555).--skip-translationleaves out the translation demo feed while timelines are captured (for English it is a Chinese feed). - Capture the apps with the demo account signed in:
- Desktop:
store-assets/scripts/capture-desktop.ts <locale> [scene,...]drives an isolated production Folo.app (FOLO_E2E_USER_DATA_DIR) over CDP and saves a Mac and a Windows variant of each scene. - iOS:
store-assets/scripts/sim-locale.ts <locale> <udid...>sets the simulator language and a clean status bar, thenstore-assets/scripts/capture-ios.ts <locale> <iphone|ipad> <udid> [scene,...]. Run thelanguagescene first and relaunch the app afterwards, since native tab titles only pick up the new language on launch. - Android:
store-assets/scripts/capture-android.ts <locale> [serial] [scene,...]on an emulator (demo mode status bar). SetFOLO_ANDROID_PACKAGEto capture a side-by-side build such asis.follow.playerfix. store-assets/scripts/check-captures.tslists the captures each UI locale still needs.
- Desktop:
- Render with
store-assets/scripts/render.ts [--deck app-store/iphone,...] [--locale en,ja,...] [--allow-missing].--allow-missingskips slides whose captures are not taken yet, for previews. Each output folder is emptied before it is written. - Check the images with
store-assets/scripts/check-output.ts: one file per slide for every store locale, at the store size, without alpha. - Update the repository README images with
store-assets/scripts/export-readme.ts: it writes the banner and the English Mac and iPhone posters the README shows as JPEGs tooutput/github/readme. The README links them as GitHub attachments, kept in a comment on DIYgodLab/debug#1 rather than in the repository; an attachment only becomes public once the comment holding it is posted. - Update the App Store creative assets (the product page header and search results artwork that iOS and iPadOS 27 show):
store-assets/scripts/app-store-creative.ts uploadputs each locale'soutput/app-store/creative/<store locale>/universal.pnginto the app's Asset Library (identical renders share one upload). Submit them in App Store Connect: Asset Library → Edit → select them → Add for Review → Submit for Review; this needs an Account Holder, Admin or App Manager and has no API. Review takes up to 48 hours. Thenapp-store-creative.ts assign [--dry-run]sets the approved images as header and search results asset on every localization of the live iOS version, which applies without a new app version. - Update the App Store custom product pages in
src/custom-pages.ts(name and screenshot order) andlisting/<locale>/app-store.json→customPages(promotional text and the keywords each page takes over;check-listing.tsmakes sure they come from that locale'skeywords.iosand that no keyword is on two pages).store-assets/scripts/app-store-custom-pages.ts sync [--page ai,...] [--skip-screenshots]creates or updates every page localization with its text, keywords and the iPhone and iPad screenshots in the page's order (about a minute per screenshot set), andapp-store-custom-pages.ts submitsends the editable page versions to App Review. Searches for a page's keywords show that page instead of the default product page once it is approved.
Outputs
| Store | Deck | Size |
|---|---|---|
| App Store | app-store/iphone |
1320 × 2868 (6.9") |
| App Store | app-store/ipad |
2064 × 2752 (13") |
| App Store | app-store/mac |
2880 × 1800 |
| App Store | app-store/creative |
5244 × 2950 universal creative asset per locale |
| Google Play | google-play/phone |
1440 × 2560 |
| Google Play | google-play/feature-graphic |
1024 × 500 |
| Microsoft Store | microsoft-store/desktop |
3840 × 2160, the Mac posters; captions repeat them |
| Microsoft Store | microsoft-store/extras |
3840 × 2160 super hero art, 300 × 300 store logo |
| GitHub | github/readme |
2400 × 1200 README banner, English only |
Release promo video
store-assets/scripts/release-video.ts <spec.json> (--stills <dir> | --video <out.mp4>) [--captures <dir>] renders the announcement video of a release, 1920 × 1080 at 30 fps, from the captures in the slides' style: an intro with the versions, one scene per highlight (a Mac window, a Mac and a phone, or phones with a callout) and an outro with folo.is. The spec format is documented in the script; promo/example.json is the desktop 1.15.0 / mobile 0.5.11 video and uses every layout. --stills writes one frame per scene and a contact sheet; both modes print each shot's capture time and report text that wraps or leaves its area, and --video (which needs ffmpeg) renders nothing until that is fixed. The release flow around it, with the post and the quote line, is .agents/skills/release-promo.
Capture notes
- AI scenes (summary, listen, translate, chat, digest, tasks) need a plan with AI quota on the demo account.
demo-account/scenes.jsonholds per-locale capture settings: the AI chat prompt, words that keep entries and AI answers out of the shots, the folder the hero, digest and mobile summary open, the translation demo feed, the example AI tasks, and localized names and prompts for the built-in AI shortcuts.- The desktop script syncs the account's
languageandactionLanguageto the capture locale (AI answers, summaries and translations followactionLanguage; settings sync is last-writer-wins, so local patches bumpupdated), localizes the AI shortcuts, creates the example AI tasks for the tasks scene and deletes them right after, and retries chat and digest answers that mention an avoided topic. - The desktop script seeds the app's locale cache (
follow:locale-<lang>in localStorage) from the repository's locale files before capturing: the production Electron build leaves some namespaces (the AI settings and chat panel in Traditional Chinese) in English otherwise. - Hero, digest and the mobile summary open one folder rather than the all-articles timeline, which can take a minute to load for this account. The desktop hero and translate scenes skip entries whose stored AI summary is not in the capture language (checked for CJK locales).
- The listen slides need a build after 0.5.10 on iPad and Android: before
7d4b23bb4the player bar lived in the JS tab bar, which is hidden on the article screen. For JS-only changes, swap a new Hermes bundle into the existing app instead of rebuilding: on iOS replacemain.jsbundlein a copy of the simulator.app, ad-hoc sign it andsimctl installit; on Android replaceassets/index.android.bundlein the installed APK,zipalignand sign it with the Expo templatedebug.keystore(the key the local build used) andadb install -rit. Both keep the app data and sign-in. - Mobile translate scenes: on iOS the per-entry "Show Translation" toggle does not fetch every language, so the script switches on the app-wide AI translation setting for the shot and off again, and waits for the entry's translation row in the app's SQLite database; on Android it uses the entry menu and compares pixels to see the translated title (uiautomator keeps reporting the old one).
- The mobile
languagescenes also write the capture language to the account (src/account.ts), so builds after 0.5.10, which sync it, produce AI summaries in that language. - Callouts: the summary and listen scenes save a
<shot>.jsonnext to the screenshot with the region the slide enlarges (the AI summary card, found by its localized title, and the player bar, found by its testID and its localized stop button).render.tsprefers it over the deck's default rectangle, since those elements sit differently per device and entry. - On iPad, the entry "more" menu opens on a physical tap and its items need a held press (
axe touch --delay 0.2); on Android, the entry "more" button is found by its localized "More Actions" label, and switching the app language in place offsets the screen until the next launch, so the script relaunches after it. Builds after 0.5.10 expose testIDs (AXUniqueIdon iOS,resource-idon Android), e.g.timeline-entry-first,entry-item-<id>,floating-player-bar. - Native YouTube feeds (
youtube.com/feeds/videos.xml?playlist_id=UULF…, the long-form uploads playlist) keep original-language titles and exclude Shorts. The subscriptiontitleinfeeds.jsonreplaces their generic "Videos" feed title. A newly added YouTube feed has no thumbnails until its first scheduled refresh;--park-videoskeeps other locales' YouTube feeds followed (hidden from timelines) until then. demo-account.tsretries until the account matches the catalog (new feeds can time out while they import); check that its last line reports nothing left to change.