A phone-only project aborted with 'no sevenInchScreenshots'. Play requires the 512 icon, the 1024x500 feature graphic and at least 2 screenshots (Play Console Help answer 9866151). play images uploads what exists and prints an info line for missing optional sets; play.tablets true makes missing tablet sets an error in shots render/check/gallery, validate and play images. Pitfall 19.
9.6 KiB
9.6 KiB
Pitfalls: lessons from real use
A changelog of mistakes found while shipping real apps and games with storebound, newest first. Each entry says what
went wrong, why, and what now guards it. The agent-facing short version is skills/store-pitfalls/SKILL.md; keep the
two in step.
Rule (CONTRIBUTING.md): every bug found in real use adds an entry here and, where possible, a check or a test that fails without the fix.
| # | date | lesson | guard |
|---|---|---|---|
| 19 | 2026-10 | play images --dry aborted with "en-US: no sevenInchScreenshots ... (render first)" on a phone-only project: Play's 7" and 10" tablet screenshots are optional (required: 512 icon, 1024x500 feature graphic, at least 2 screenshots; Play Console Help answer 9866151), but every image type was treated as required. |
play images uploads what exists (icon, feature graphic, phone screenshots, tablet / TV / Wear only when rendered), prints an info line for missing optional sets, never deletes what Play has for them without --prune; play.tablets (default: tablet renders exist) switches tablets for shots render / shots check / shots gallery / validate / play images, and true with missing renders is an error (cli/play/images.ts langImages, resolvedTablets in cli/shots/report.ts, tests/play-post-reads.test.ts). |
| 18 | 2026-10 | console fill play-app-record with a copy of the owner's Chrome cookies reached the Play account chooser once, then Google answered /v3/signin/rejected and the chooser: Google session cookies are device-bound (Chrome's Device Bound Session Credentials) and Google signs out sessions whose cookies look stolen, which can also sign the owner out of their real Chrome. |
Never copy Google cookies by default: storebound's own profile is the default (the owner signs in once by hand), started by storebound as a normal Chrome (no --enable-automation, navigator.webdriver false) and attached over CDP; --use-chrome-session google|apple is an explicit opt-in that warns; /signin/rejected is said plainly (cli/console/session.ts, tests/console-session.test.ts). |
| 17 | 2026-10 | After the data types were saved, the real App Privacy page shows each type's "Set Up " as a plain <p> in a div with a click handler (no button role, no link) and puts the types in a box after the Data Types heading's box: role-based locators and the "nearest heading" check found nothing, so no type was set up. A "?" (More information) button sits next to each type. |
setUpAllowed / setUpTexts (cli/lib/console-forms.ts: exact text "Set Up " of a type storebound answers, in the Data Types section, outside dialogs) and setUpClick / setUpType / flowWhere (last heading before the control) in cli/console/fill.ts; the per-type dialog steps are page sections, so an unknown step dumps and pauses instead of ending the run; tests/fixtures/console/apple-app-privacy-setup.html (structure of the real page) and tests/console-flow.test.ts. |
| 16 | 2026-10 | console fill play-app-record with the copied Chrome cookies printed only "still waiting for the sign-in" for 5+ minutes on a signed-in Play Console: Google pages embed accounts.google.com frames (the account widget, cookie rotation), and the detector counted any frame on a sign-in host. |
signInReason: a sign-in page only by its top-level URL, a visible password / email-entry form in the main frame, or a visible Apple sign-in frame (idmsa / appleid); other frames never count. Each detection logs "sign-in detected because: at "; heartbeats name the page; a copied session that lands on a sign-in URL is said plainly ("did not carry over"); the watchdog dumps the sign-in wait once after --step-timeout, --debug-dump up to 3 times (tests/console-fill.test.ts, chooser / home with the widget frames, a top-level accounts.google.com page). |
| 15 | 2026-10 | The real App Privacy "Data Collection" dialog names each data type checkbox by its label run together with its help text ("NameIncluding first or last name"), and labels some types differently from Apple's help page ("Purchases", "Other Data"): exact-name matching found none. | labelNames (exact name, then the label as a prefix that ends where the help text starts) and TYPE_ALIASES in cli/lib/console-forms.ts; the flow fixture mirrors the real dialog's structure (tests/console-flow.test.ts). |
| 14 | 2026-10 | console fill apple-app-privacy clicked Next onto a real console page it did not recognise and then waited 8 hours without a single log line. |
Every wait of the console engine is bounded: a step watchdog (--step-timeout, 30 s) writes a sanitized debug dump to .storebound/console-debug/ and prints the page's buttons and the manual steps; a line a minute while waiting; no progress for 20 min stops the run with exit 1; --debug-dump dumps every step (cli/console/debug.ts, tests/console-flow.test.ts, which also scans the engine for waits without a deadline). |
| 13 | 2026-10 | App Privacy purposes were over-mapped: Play "advertising" became both Third-Party Advertising and Developer's Advertising or Marketing (Device ID, Coarse Location, Product Interaction, Performance Data). | applePurpose maps one Play purpose to one Apple purpose (advertising: third-party only); Developer's Advertising only from appPrivacy.types; check compares appPrivacy.types with Info.plist / PrivacyInfo.xcprivacy (tests/app-privacy.test.ts). |
| 12 | 2026-10 | A new Play app has no API: Play Console's "Create app" is the only way, its page-level button carries the same words as the form's final submit, and Free cannot become Paid later. | console fill play-app-record: createAppAllowed lets the opener through only on the app list, never in a form or dialog (EN + TR); declarations never ticked; Free / Paid outlined as an owner check (tests/console-fill.test.ts). |
| 11 | 2026-10 | Google Play has no public search API, so there is no Play keyword rank or Play competitor list to read without scraping. | aso reads App Store search only (iTunes Search API); aso audit covers Play through the listing text (short description, density); the aso-basics skill says so. |
| 10 | 2026-10 | The iTunes Search API order is close to, not equal to, App Store search (no personalisation, no search ads, no in-app event rows) and it has no subtitle field. | aso rank / aso competitors / aso audit label ranks as a trend and show no competitor subtitles; popularity only from Apple Ads, never estimated (cli/lib/aso-intel.ts). |
| 9 | 2026-10 | A retried or repeated write can duplicate on a live store: a POST create retried after a timeout or 5xx, a re-run during the list lag right after a create, two runs at once, a Play edit redone after an unclear commit, a crash in the middle of a screenshot set. | Every write is an upsert (cli/lib/upsert.ts: findOrCreate with a fresh re-check and a journal of recent creates, createOnce treating 409 as found, no blind retry of a create); API clients retry only requests that are safe twice; Play edits are all or nothing; asset sets keep the matching prefix; one lock per app and store (cli/lib/lock.ts, --force-unlock). Table: docs/idempotency.md; tests/idempotency.test.ts runs every write twice and asserts zero writes on the second run. |
| 8 | 2026-10 | Capacitor plugins in a monorepo live in apps/native/node_modules, so scan missed them. |
scan / privacy-manifest resolve workspace packages (cli/lib/scan/project.ts, cli/lib/node-packages.ts). |
| 7 | 2026-10 | Console UIs: the App Store Connect sign-in runs in an idmsa.apple.com iframe; "User Access" is a plain heading, not a labeled group; Chrome drops session cookies without --restore-last-session. |
console session and fill (cli/console/), tests/console-*.test.ts. |
| 6 | 2026-10 | Screenshot fonts: only part of the glyphs a language needs were available, so Turkish / Polish / Vietnamese characters fell back to another font silently. | shots fonts / shots check check every language's characters against the fonts (tests/glyphs.test.ts). |
| 5 | 2026-10 | Account features do not imply email collection; Data safety said "email collected" without evidence. | scan marks email only with code evidence, else "ask owner" (cli/lib/scan/declare.ts). |
| 4 | 2026-10 | The App Store keyword limit (100) counts UTF-8 bytes, not characters. | check counts bytes (cli/lib/copy.ts). |
| 3 | 2026-10 | App Store app names are unique per storefront: "already being used" although the developer owns no such app. | apple.name (App Store name with a suffix the owner picks); app.name stays the brand. |
| 2 | 2026-10 | Dry runs that print no plan ("listing: 20 locales") break "show the dry run to the owner". | cli/lib/dry.ts dryPlan(title, rows); tests/pitfalls.test.ts scans cli/** for --dry branches that print only a count (allowlist with TODOs for commands being fixed). |
| 1 | 2026-10 | App Store Connect's /inAppPurchasesV2 list lags right after apple iap creates a product; apple iap-review then skipped it silently. |
apple iap-review names config products missing from the store's list and exits 1; apple iap and play products re-read the list after creating and warn (cli/lib/store-lag.ts, tests/pitfalls.test.ts). |
Adding an entry
- A row above (next number, month, one-sentence lesson, the guard with file or test names).
- A section in
skills/store-pitfalls/SKILL.md: symptom → cause → what to do → which command guards it. Thenbun tools/gen-adapters.ts. - Where the code can know it: a check (
check,validate,scan, a warning in the command) and a test with recorded store answers that fails without the fix.