Modernize App Store listing: product creation screenshots and copy - #358
Conversation
Greptile SummaryThe PR modernizes the App Store listing around mobile product creation and replaces the existing screenshot set.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains within the eligible follow-up-review scope. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| store-listing/marketing-seed.rb | Adds repeatable marketing accounts, products, sales, and library purchases for screenshot capture. |
| store-listing/screenshots/frame.html | Defines the five branded screenshot layouts, headlines, colors, and source-capture mappings. |
| store-listing/screenshots/render.sh | Renders and resizes all five App Store screenshot frames on macOS. |
| store-listing/screenshots/CAPTURE.md | Documents local backend setup, seed execution, Simulator capture, and frame rendering. |
| store-listing/en-US/description.txt | Repositions the listing around product creation, publishing, seller analytics, and purchase playback. |
Reviews (2): Last reviewed commit: "Redesign the App Store frames and recapt..." | Re-trigger Greptile
Sahil asked for screenshots that highlight creation and edit (antiwork/gumroad-private#2320). Slot 2 is now the edit form; publish/sales/library shift down; the old library-player watch frame is gone. Co-authored-by: Sahil Lavingia <sahil@gumroad.com>
HTML mock phones are gone. Each 6.7" frame embeds a real iPhone 17 Pro screenshot of the store build (Products, Dashboard, Library). The sim account is empty, so Dashboard is $0 and Library has no purchases. Recapture from a live creator account before App Store Connect upload. Co-authored-by: Sahil Lavingia <sahil@gumroad.com>
|
Stays draft: the 6.7" frames are live Simulator captures, but the sim account has no catalogue (Dashboard $0 / empty Library) and the create-product WebView signed out, so frames 1–2 share the Products empty-state. Recapture from a live creator account before store upload / ready. |
Can you use a seeded dev account? |
Empty simulator captures of a $0 account read as "the app has nothing." Replace them with marketing frames of a mock creator (covers, sales, library).
Products, Dashboard ($10 / 2 sales), and Library now show the mobile_seller1 catalogue instead of the empty sim account.
|
Yes. Recaptured from the local seeded seller (2 products, $10 today). New 6.7" frames are on the PR; create/edit WebView is still the Products catalogue. |
Replace the five 6.7" frames. Each frame now shows a different screen, uses the app's own ABC Favorit typeface, and carries one Gumroad brand color. The previous set reused one Products capture for frames 1 and 2 and one Dashboard capture for frames 3 and 4. It also showed e2e seed data: "Mobile Test Product 1" and mobile_buyer_do_not_edit@gumroad.com. Drop the logo and the eyebrow chip. A single large headline reads faster at App Store thumbnail size. Headlines end in a period to match the voice on gumroad.com. Capture from a purpose-built marketing seller instead of the e2e accounts: five products priced $12 to $49 with real cover art, 56 sales today totalling $1,392, and three Library purchases from a second creator. Add CAPTURE.md with the full procedure, marketing-seed.rb with the seed data, and CAPTURE-BACKEND-NOTES.md describing two antiwork/gumroad bugs the captures hit. render.sh now loads fonts from assets/fonts/ rather than a second copy.
…PS protocol (#7422) ## What Two fixes, both found while capturing App Store screenshots for the mobile app ([antiwork/gumroad-mobile#358](antiwork/gumroad-mobile#358)). 1. **Product thumbnails were missing from the mobile Products tab.** `thumbnail_url` came back `nil` for every product, so each row rendered a placeholder. 2. **Local WebViews rendered blank over HTTPS.** `PROTOCOL` had no override, so pointing the mobile app at a local backend broke every embedded page. ## Why ### The thumbnail was read with the wrong key type `Api::Mobile::ProductsController#product_json` read `props.dig("thumbnail", "url")`. Every other key in that hash is a string, so this reads naturally — but `Thumbnail#as_json` returns **symbol** keys: ```ruby def as_json(*) { url:, guid: } end ``` The outer lookup succeeds, the inner one misses, and the result is always `nil`. This is not environment-specific. The web dashboard reads the same hash, but only after Inertia serializes it to JSON, where symbol keys become strings — so only the mobile API, which dereferences it in Ruby, is affected. No test caught it. The controller spec asserted `name`, `permalink`, `status`, `can_edit` and `can_destroy` but never `thumbnail_url`, and the product schema declares the field as `["string", "null"]`, so `nil` validated. ### PROTOCOL could not be overridden `CUSTOM_DOMAIN` already exists to point a local backend at a registrable hostname, and the mobile app's dev config expects `https://gumroad.dev`. But `CUSTOM_DOMAIN` only changes the host — development stays on `http`, so Rails emits asset URLs like `http://app.localhost:3000/vite-dev/entrypoints/base.ts`. Inside the app's HTTPS WebView those are unreachable. Every embedded page — sign-in, create product, settings — renders blank, with **no error in the app and none in the Rails log**. The page returns 200; only its assets fail. This adds a `CUSTOM_PROTOCOL` override next to the existing `CUSTOM_DOMAIN` and `ASSET_DOMAIN` ones. Unset, `PROTOCOL` resolves exactly as before. ### A third bug, surfaced by review Review suggested `ENV["CUSTOM_PROTOCOL"].presence`. That cannot be used here: `bin/vite` requires `config/domain.rb` before Rails boots, so ActiveSupport is not loaded and `presence` raises `NoMethodError`. Line 108 already called `ENV["BRANCH_DEPLOYMENT"].present?`, which means `bin/vite` has been crashing whenever `CUSTOM_DOMAIN` is set — the exact configuration this override pairs with: ``` $ CUSTOM_DOMAIN=gumroad.dev ruby -e '... require_relative "config/domain"' NoMethodError: undefined method 'present?' for nil ``` Both checks are now plain Ruby, with a comment recording the constraint so it is not reintroduced. ## Before / After Not user-visible on the web. The mobile-side effect is in [antiwork/gumroad-mobile#358](antiwork/gumroad-mobile#358): its frame 01 shows the Products tab with cover art, which required this fix — before it, every row showed a placeholder box. ## Test Results - `bundle exec rspec spec/controllers/api/mobile/products_controller_spec.rb spec/config/domain_spec.rb` — 18 examples, 0 failures - `bundle exec rubocop` on all changed files — no offenses - Every new example was checked against its bug by reverting the fix and confirming the failure: - reverting the key type fails "returns the thumbnail url" - reverting the blank guard fails the blank-`CUSTOM_PROTOCOL` example - restoring `present?` fails the pre-Rails example with `NoMethodError` - reverting `strip` fails the whitespace example New coverage in `spec/config/domain_spec.rb` loads the file the way `bin/vite` does — bundler and vite_ruby, nothing else — because a `bin/rails runner` boots Rails and would hide the ActiveSupport constraint. These assert the resolved constant, not the URLs built from it. Confirming a WebView loads assets over HTTPS end to end needs a running backend, so that stays manual. ## QA steps 1. `bundle exec rspec spec/controllers/api/mobile/products_controller_spec.rb spec/config/domain_spec.rb` 2. Unset `CUSTOM_PROTOCOL` and confirm development still resolves `http` 3. For the WebView fix end to end: run this backend with `CUSTOM_DOMAIN=gumroad.dev CUSTOM_PROTOCOL=https ASSET_DOMAIN=gumroad.dev`, front it with an HTTPS proxy on 443, then open Products → New in the mobile app. The create-product page renders instead of coming up blank. ## Note on CI `Compute relevant specs` escalates here: `bin/branch-specs` cannot attribute specs to `config/domain.rb`, so it asks for the full suite. The `run-all-specs` label is applied for that reason. I tried mapping `config/domain.rb` into `CONFIG_SPEC_MAP` to avoid the label, then reverted it. That mapping would drop the full-suite guard for every future change to the file, and its constants fan out widely — `PROTOCOL` alone is referenced in 37 files — while `domain_spec.rb` exercises no consumer specs (CSP, CORS, WebAuthn origins, URL generation). Escalating is correct here; the label is the intended mechanism. --- 🤖 Written with **Claude Fable 5** (Claude Code). The model investigated both bugs, wrote the fixes and specs, and verified each new example fails without its fix. Two of the three bugs were found by it while setting up a local backend to capture App Store screenshots; the whitespace and blank-value guards came from Greptile review feedback, which it evaluated before accepting. Its proposed `.presence` fix was rejected after testing showed it breaks `bin/vite`, and a self-inflicted change that would have weakened CI coverage was caught in review and reverted. All test results above were produced by real runs, not asserted. --------- Co-authored-by: Gianfranco Piana <gianfrancopiana@users.noreply.github.com>
What
Replaces the 2015 App Store listing (Utilities, “watch what you buy”) with copy and real iOS Simulator screenshots that lead with product creation.
Addresses antiwork/gumroad-private#2320.
Create and sell on mobile(26 chars)The App Store holds one screenshot set per device size per locale, so this replaces the live set rather than adding to it.
Does not upload to App Store Connect or Play — assets only. Receipt / “open in the app” mailer copy is an
antiwork/gumroadchange, not this repo.The frames
Each frame is a flat Gumroad brand color, one headline in ABC Favorit Bold (the app's own typeface, loaded from
assets/fonts/), and a Simulator capture in a black bezel bleeding off the bottom edge.01-create-product.png#FF90E8pink02-name-price.png#FFC900yellow03-today.png#23A094green04-sales.png#90A8EDpurple05-library.png#F4F4F0creamHeadlines end in a period, matching the voice on gumroad.com (“Place small bets.”, “Share your work.”).
Why the frames changed
The earlier set had three problems, all fixed here.
Every frame now shows a different screen. Frames 1 and 2 previously shared one Products capture, and frames 3 and 4 shared one Dashboard capture — so frame 2 sold the create flow while showing a product list.
The data is presentable. The old captures showed e2e seed data: “Mobile Test Product 1”,
mobile_buyer_do_not_edit@gumroad.com, and $10 from 2 sales. Captures now come from a purpose-built marketing seller,marketing_capture_seller@gumroad.com(“Sable Studio”): five products priced $12–$49 with real cover art, 56 sales today totalling $1,392, 119 sales across the past week, and three Library purchases from a second creator. Buyer emails and product names are invented. Themobile_*_do_not_editaccounts are deliberately untouched.The frame design carries less chrome. The logo and eyebrow chip are gone; one large headline reads faster at App Store thumbnail size. Type is the app's real ABC Favorit rather than a substitute.
Reproducing
screenshots/CAPTURE.mdhas the full procedure: local backend setup, seeding, Release-configuration build, per-screen capture, and rendering.marketing-seed.rbis the seed script. Every headline and color lives inscreenshots/frame.html;screenshots/render.shdrives headless Chrome and writes the five PNGs.Two gotchas are written down because they cost real time: the create-product screen needs its “create with AI” promo banner dismissed and the screen reopened before the Name validation clears, and
expo run:iosneedsLANG=en_US.UTF-8or CocoaPods crashes.Backend bugs found on the way
Capturing needed the app pointed at a local
antiwork/gumroadbackend. Two real problems surfaced, both confirmed onantiwork/gumroadmain. They are fixed in antiwork/gumroad#7422, not this repo. Details instore-listing/CAPTURE-BACKEND-NOTES.md.thumbnail_urlis alwaysnilin the mobile products API.Thumbnail#as_jsonreturns symbol keys, butApi::Mobile::ProductsController#product_jsonreadsprops.dig("thumbnail", "url")with a string key. Every row in the app's Products tab renders the placeholder icon instead of the product cover. The test suite cannot catch it — the controller spec never asserts the field and the JSON schema permitsnull.PROTOCOLcannot be overridden, so local WebViews render blank.config/domain.rbhardcodeshttpin development, soCUSTOM_DOMAIN=gumroad.devstill emitshttp://app.localhost:3000asset URLs. Inside the app's HTTPS WebView every embedded page renders blank, with no error in the app and none in the Rails log.Test Results
render.shverified end to end from the checked-insimulator-raw/captures andassets/fonts/QA steps
store-listing/screenshots/render.shand confirm it reproduces themStatus
Draft. Assets only — not uploaded to stores.