storebound/docs/console-parity.md

14 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 / scan while typing, unsaved-changes mark, undo / redo) and saves it into the source file through project.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 and the live store art (2026-10-14). The audit before them (Phase 1) had 70 rows, 62 of them with a gap. Now: 76 rows, 57 with nothing missing, 19 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) art pull (the live sets), shots render, apple screenshots (shots.scenes, the shots copy file) yes (the live sets: art.live thumbnails per locale and display type; the local files) yes (scene order, the frame of each scene, the captions per language; one image replaced with a picked file: art.replace) yes (apple.screenshots; pulled files count as the store's own and upload nothing) 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) art pull (the live graphics), icons, shots render, play images yes (the live graphics: art.live per language and type; the local files) yes (scene order, frames, captions; one image replaced with a picked file: art.replace) yes (play.images; pulled files count as the store's own and upload nothing) 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
Pull the live store art into the app folder (screenshots, previews, feature graphic, icons) art pull (also run by onboard), store/renders/pulled.json yes (art.live: live thumbnails, same / differs / not pulled per image) n/a n/a (art.pull: local files only after the dry run, read-only on the stores; differing local files kept) 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): store art art live yes (art.live: live thumbnails per language and set, read-only) no no reason: replacing store art uploads files from an app folder; "Link a folder" or "Start an app folder" turns the app into a full one, whose Pull from stores (art.pull) brings the live art into it
Store-only app (no folder): URLs, contact, catalog (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)