storebound/docs/pitfalls.md
CRT c9a7f095c4 console: agent-first mode (console plan / console verify), verified App Privacy and Play Create app facts
- `storebound console plan <form> [--json] [--lookup]`: read-only plan for an AI agent that drives the browser
  itself: start URL (deep links), ordered steps, answer sheet with the console's exact labels (certain / ask owner,
  curly quotes noted), never-press list, verified UI facts, driving guidance (in-page async loop + window.__log),
  verify instructions. All six forms.
- `storebound console verify <form> [--from-text <file|->]`: App Privacy per-type summaries from page text, Play
  Create app from input states, content rating / target audience summaries, App Store record and age rating from
  the API; exit 1 on a mismatch.
- MCP: console_plan, console_verify (read-only); console fill stays off MCP.
- App Privacy (verified 2026-10-12): two information steps, unlabeled purpose checkboxes (nearest parent text),
  "Definitions and Examples" never pressed; fixture and engine follow.
- Play Create app (verified 2026-10-12): /developers/<id>/create-new-app, App package name + read-only Check
  availability, material-radio inner [role=radio] click, app dashboard URL and play.appId, service account
  "Add app" as an owner step.
- console-autofill skill rewritten agent-first; agents-core rule 10; README; pitfalls 12, 18 (confirmed sign-out),
  19; CHANGELOG; play-first-release.
2026-10-12 08:39:12 +08:00

39 lines
11 KiB
Markdown

# 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 |
|---|---|---|---|
| 20 | 2026-10 | Hours of selector-fix cycles of the scripted console engine (`console fill`): the real console kept differing from the model (App Privacy "Set Up" as plain text, purpose checkboxes without an accessible name, two information steps in a type dialog, Play's material radios that ignore the native input). | Agent-first console mode: `console plan <form>` gives an AI agent the start URL, steps, answer sheet with the console's labels, never-press list and verified UI facts; the agent drives the page with its own browser tool and stops before the final button; `console verify <form>` checks the result (App Privacy summaries from the page text, Play Create app from input states, API for the App Store record and age rating). The scripted fill stays a fast path (and now handles the verified facts). MCP: `console_plan`, `console_verify` (read-only). `tests/console-plan.test.ts`, `tests/console-flow.test.ts`. |
| 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`). Confirmed 2026-10-12: the earlier default copy DID sign the owner out of Play Console in their real Chrome. |
| 17 | 2026-10 | After the data types were saved, the real App Privacy page shows each type's "Set Up <type>" 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 <type>" 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: <rule> at <url>"; 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`). Verified 2026-10-12: the form is at `/developers/<id>/create-new-app` and has an App package name field with a read-only "Check availability" (filled from `play.packageName`; the package name no longer comes from the first AAB upload); App or game / Free or paid are `<material-radio>`s whose model only updates on a click of the inner `[role=radio]` (the engine clicks it; `console verify play-app-record` checks aria-checked and the "Select …" hints). After Create app the owner adds the new app to the service account (app-level permissions do not cover later apps). |
| 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
1. A row above (next number, month, one-sentence lesson, the guard with file or test names).
2. A section in `skills/store-pitfalls/SKILL.md`: symptom → cause → what to do → which command guards it. Then
`bun tools/gen-adapters.ts`.
3. 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.