13 KiB
13 KiB
Console parity: App Store Connect and Play Console in the desktop app
The desktop app (apps/desktop, desktop.md) replaces App Store Connect and Play Console for everything
storebound can read or write. This page lists every owner-editable thing in the two consoles that the CLI covers, and
what the desktop does with it. tests/console-parity.test.ts keeps it honest: every row is edited in the window or
names the reason it is not.
Columns:
- storebound: the command that reads or writes it, and where the value lives (the source of truth in the app
folder:
storebound.config.ts,store/listing.md,store/appstore.md, the shots copy file,.storebound/). - View: the desktop shows the current value.
- Edit: the desktop edits it in place (live counter against the store limit where there is one, the checks of
check/scanwhile typing, unsaved-changes mark, undo / redo) and saves it into the source file throughproject.edit(storebound edit: dry run with the diff and the checks in the window, then the native dialog). - Store write: the desktop writes it to the store (the dry run in the window, the native confirm dialog, then the real run; owner-only steps through the owner-action dialog).
- Gap: "none", or why a cell stays "no" on purpose.
Status: after the editors (2026-10-14). The audit before them (Phase 1) had 70 rows, 62 of them with a gap. Now: 73 rows, 55 with nothing missing, 18 with the reason a cell stays "no" (no store API, an owner-only public step, a secret, or personal data).
App Store Connect
| Console screen | storebound | View | Edit | Store write | Gap |
|---|---|---|---|---|---|
| App Information > Name (per locale) | apple listing (### name, apple.name, app.name) |
yes | yes | yes (apple.listing) |
none |
| App Information > Subtitle | apple listing (### subtitle) |
yes | yes | yes | none |
| Version > Promotional Text | apple listing (### promotionalText) |
yes | yes | yes | none |
| Version > Description | apple listing (### description) |
yes | yes | yes | none |
| Version > Keywords | apple listing (### keywords), check --aso |
yes | yes (keyword rules while typing) | yes | none |
| Version > What's New | apple listing, whats-new apply (### whatsNew) |
yes | yes | yes | none |
| Version > Support URL | apple listing (app.supportUrl) |
yes | yes | yes | none |
| Version > Marketing URL | apple listing (app.marketingUrl) |
yes | yes | yes | none |
| App Privacy > Privacy Policy URL | apple listing (app.privacyPolicyUrl) |
yes | yes | yes | none |
| Version > Copyright | apple listing (app.copyright) |
yes | yes | yes | none |
| Version > version string, build | apple listing --version --build |
yes | yes | yes | none |
| Version > Screenshots (per device, locale) | shots render, apple screenshots (shots.scenes, the shots copy file) |
yes | yes (scene order, the frame of each scene, the captions per language) | yes (apple.screenshots) |
none |
| Version > App Previews | shots record, shots video, apple previews |
yes | n/a | yes (apple.previews) |
reason: a preview is cut from recorded clips, not a field; replacing one is a new recording (shots.video render job in the window) |
| App Information > Category, subcategories | apple appinfo (apple.info.categories) |
yes | yes | yes (apple.appinfo) |
none |
| App Information > Age Rating | apple appinfo (apple.info.ageRating), console fill apple-age-rating |
yes | yes (every question) | yes | none |
| App Information > Content Rights | apple appinfo (apple.info.contentRights) |
yes | yes | yes | none |
| Pricing and Availability > Free, territories | apple appinfo (apple.info.free) |
yes | yes | yes | none |
| Pricing and Availability > Pre-orders | apple preorder status / enable / end |
no | no | no | reason: putting an app on pre-order or ending it is public at once; it stays the owner's terminal step (apple preorder enable --release-date, asks first), there is no config value to edit |
| App Privacy (data types, purposes) | appPrivacy.types, console fill apple-app-privacy |
yes | yes (checked JSON, validated with the config) | no | reason: App Privacy has no API; Fill in console types the saved answers, the owner presses Publish |
| App Review Information > Contact | apple review-details (.storebound/credentials.json reviewContact) |
masked | yes (write-only, masked) | yes (apple.review_details) |
none |
| App Review Information > Notes | apple review-details (apple.info.reviewNotes, reviewAccess.instructions) |
yes | yes | yes | none |
| App Review Information > Sign-in required | reviewAccess.signInRequired | yes | yes | yes | none |
| App Review Information > Demo account | credentials.json reviewDemoAccount | masked | no | yes (sent by apple.review_details) |
reason: a password; the window never reads or takes it (AGENTS.md rule 4), the owner puts it in .storebound/credentials.json |
| Export compliance (encryption) | apple encryption (apple.usesNonExemptEncryption) |
yes | yes | yes (apple.encryption) |
none |
| In-App Purchases > reference name, display name, description per locale | apple iap (products[]) |
yes | yes | yes (apple.iap) |
none |
| In-App Purchases > price | products[].usd, overrides, prices import |
yes | yes | yes, owner gate for live products | none |
| In-App Purchases > review note, screenshot | products[].reviewNote / reviewScreenshot, apple iap-review |
yes | yes | yes (apple.iap_review) |
none |
| Subscriptions > group name per locale | apple subscriptions (subscriptions[].listings) |
yes | yes | yes (apple.subscriptions) |
none |
| Subscriptions > name, description per locale | apple subscriptions |
yes | yes | yes | none |
| Subscriptions > level, period, price | apple subscriptions |
yes | yes | yes, owner gate for live prices | none |
| Subscriptions > introductory and promotional offers | subscriptions[].subscriptions[].intro / offers | yes | yes (checked JSON) | yes (apple.subscriptions) |
none |
| Subscriptions > offer codes | offerCodes, apple subscriptions, apple offer-codes --codes |
yes | yes (checked JSON) | yes for the offers; the codes: no | reason: generating codes makes vouchers anyone can redeem; it stays the owner's terminal step (apple offer-codes --codes, asks first, codes saved to .storebound/) |
| Subscriptions > win-back offers | winBack, apple win-back |
yes | yes (checked JSON) | no | reason: a win-back offer goes live by itself on its start date; creating one is the owner's terminal step (apple win-back, asks first) |
| In-App Purchases > promoted purchases | apple.promoted, apple promoted |
yes | yes (checked JSON) | no | reason: promoting shows the purchase on the product page; apple promoted [--enable] asks first and stays the owner's terminal step |
| Custom product pages | apple cpp create / upload (apple.customProductPages) |
yes | yes (checked JSON) | yes (apple.cpp_create, apple.cpp_upload) |
none |
| In-app events | apple events create / upload (apple.events) |
yes | yes (checked JSON) | yes (apple.events_create, apple.events_upload) |
none |
| Product Page Optimization | apple ppo |
yes | yes | yes (owner start / stop) | none |
| TestFlight > What to Test (per build, per locale) | apple testflight what-to-test (### whatToTest, else ### whatsNew) |
yes | yes (per locale) | yes (apple.testflight_what_to_test) |
none |
| TestFlight > groups | apple testflight groups sync (apple.testflight.groups) |
yes | yes (checked JSON) | yes (creates the missing groups) | none |
| TestFlight > Test Information (beta description, notes) | apple testflight info (apple.testflight.description / reviewNotes) |
yes | yes | yes (apple.testflight_info) |
none |
| TestFlight > testers | owner.testflight_testers (.storebound/testers.csv) |
count | no | yes (owner) | reason: tester emails are personal data kept in the gitignored CSV; adding them is the owner action, removing them stays a terminal step |
| Ratings and Reviews > reply | reviews draft / reply / approve / send |
yes | yes | yes (owner send) | none |
| Version > phased release | owner.ios_phased |
yes | n/a | yes (owner) | none |
| Version > Submit for review | owner.apple_submit / owner.apple_resubmit |
yes | n/a | yes (owner) | none |
Google Play Console
| Console screen | storebound | View | Edit | Store write | Gap |
|---|---|---|---|---|---|
| Main store listing > App name (per language) | play listing (### title, app.name) |
yes | yes | yes (play.listing) |
none |
| Main store listing > Short description | play listing (### short) |
yes | yes | yes | none |
| Main store listing > Full description | play listing (### full) |
yes | yes (stuffing and claims checks while typing) | yes | none |
| Release > Release notes (per language) | play release, whats-new apply (### What's new) |
yes | yes | yes (with the release: owner.play_production) |
none |
| Main store listing > Graphics (icon, feature graphic, screenshots) | icons, shots render, play images |
yes | yes (scene order, frames, captions) | yes (play.images) |
none |
| Main store listing > Video (YouTube URL) | play listing (play.video) |
yes | yes | yes | none |
| Store settings > Contact email | play listing app details (app.contactEmail) |
yes | yes | yes | none |
| Store settings > Website | play listing app details (app.website) |
yes | yes | yes | none |
| Store settings > App category | play.category, console plan | yes | yes | no | reason: no API; the console plan step (App info > Console steps) uses the saved value |
| App content > Privacy policy | app.privacyPolicyUrl, console plan | yes | yes | no | reason: no API; the console plan step uses the saved value |
| App content > Data safety | play data-safety (dataSafety) |
yes | yes | yes (play.data_safety) |
none |
| App content > Content rating (IARC) | console.contentRating, console fill play-content-rating |
yes | yes (checked JSON) | no | reason: no API; Fill in console types the saved answers, the owner presses Submit |
| App content > Target audience | play.targetAges, console.targetAudience, console fill play-target-audience |
yes | yes | no | reason: no API; Fill in console types the saved answers, the owner saves |
| App content > other declarations (ads ID, government, finance, health) | console.appContent, console plan | yes | yes (checked JSON) | no | reason: no API; the console plan step uses the saved answers |
| Monetize > In-app products: title, description per language | play products (products[].listings) |
yes | yes | yes (play.products) |
none |
| Monetize > In-app products: price | products[].usd / overrides, prices import |
yes | yes | yes, owner gate for live products | none |
| Monetize > Subscriptions: title, benefits, description per language | play subscriptions |
yes | yes | yes (play.subscriptions) |
none |
| Monetize > Subscriptions: base plans, offers, price | play subscriptions |
yes | yes | yes, owner gate for live prices | none |
| Pricing > regional prices (price sheet) | prices export / import, overrides |
yes | yes (per-product overrides in the Catalog editor) | yes (owner) | none |
| Production > Countries / regions | play.countries, console plan | yes | yes | no | reason: no API for the production countries; the console plan step uses the saved list |
| Publishing overview > Managed publishing | play.managedPublishing | yes | yes | no | reason: no API; only Play Console sets it (the console plan names the switch) |
| Testing tracks (internal, closed, open) | play release --track |
yes | n/a | no | reason: sends a build to testers; the owner's terminal step by design (desktop.md safety model) |
| Production > staged rollout | owner.play_rollout |
yes | n/a | yes (owner) | none |
| Production > Send to production | owner.play_production |
yes | n/a | yes (owner) | none |
| Ratings and reviews > reply | reviews draft / reply / approve / send |
yes | yes | yes (owner send) | none |
| Internal app sharing | play share |
no | n/a | no | reason: uploads a build file for testers, not a console field; a terminal or agent step (play share) |
Both stores
| What | storebound | View | Edit | Store write | Gap |
|---|---|---|---|---|---|
| Pull the live texts into the files | apple listing pull, play listing pull |
yes | n/a | n/a (writes the listing files after the diff) | none |
| Store-only app (no folder): listing text per language | listing live, listing set |
yes | yes | yes (listing.set, one language per apply) |
none |
| Store-only app (no folder): URLs, contact, catalog, art | (the config of an app folder) | yes (reads) | no | no | reason: those writes send what an app folder's config holds; "Link a folder" or "Start an app folder" turns the app into a full one |
| Uncommitted changes in the app folder | git status of the edited files | yes | n/a | n/a | none (storebound never commits) |