store-only apps edit their listing on the store (storebound listing live / set: old -> new per field with the checks, a PATCH of only the edited fields; serve listing.set is the one store-only write, its token bound to the store ids; policy.rs STORE_ONLY_WRITES, write_with_confirm for store targets); screenshot captions per language and scene edited as a text edit of the shots copy JSON (JSON files keep their layout); Play content rating, target audience and App content answers in App info; docs/console-parity.md after the editors (73 rows: 55 complete, 18 with the reason) and tests/console-parity.test.ts keeping it honest
This commit is contained in:
parent
3cea2af4e1
commit
7ac028421a
33 changed files with 852 additions and 157 deletions
|
|
@ -1,66 +1,71 @@
|
|||
# Console parity: App Store Connect and Play Console in the desktop app
|
||||
|
||||
The desktop app (apps/desktop, [desktop.md](desktop.md)) is meant to replace 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.
|
||||
The desktop app (apps/desktop, [desktop.md](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`, `.storebound/`).
|
||||
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 (validation, unsaved-changes guard, undo) and saves it into the source file.
|
||||
- **Store write**: the desktop writes it to the store (dry run in the window, native confirm dialog, then the real
|
||||
run; owner-only steps through the owner-action dialog).
|
||||
- **Gap**: what is missing, or why a cell stays "no" on purpose.
|
||||
- **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: **audit before the editors (2026-10-14)**. Every row whose CLI can write the value but the desktop cannot edit
|
||||
it is a gap. Phase 2 closes them; this table is updated with the result.
|
||||
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) | counter only | no | yes (`apple.listing`) | no text editor |
|
||||
| App Information > Subtitle | `apple listing` (`### subtitle`) | counter only | no | yes | no text editor |
|
||||
| Version > Promotional Text | `apple listing` (`### promotionalText`) | counter only | no | yes | no text editor |
|
||||
| Version > Description | `apple listing` (`### description`) | counter only | no | yes | no text editor |
|
||||
| Version > Keywords | `apple listing` (`### keywords`), `check --aso` | counter only | no | yes | no editor, no keyword checks while typing |
|
||||
| Version > What's New | `apple listing`, `whats-new apply` (`### whatsNew`) | no | no | yes | no editor |
|
||||
| Version > Support URL | `apple listing` (app.supportUrl) | no | no | yes | not shown, no editor |
|
||||
| Version > Marketing URL | `apple listing` (app.marketingUrl) | no | no | yes | not shown, no editor |
|
||||
| App Privacy > Privacy Policy URL | `apple listing` (app.privacyPolicyUrl) | no | no | yes | not shown, no editor |
|
||||
| Version > Copyright | `apple listing` (app.copyright) | no | no | yes | not shown, no editor |
|
||||
| Version > version string, build | `apple listing --version --build` | yes | version field | yes | none |
|
||||
| Version > Screenshots (per device, locale) | `shots render`, `apple screenshots` | renders | no | yes (`apple.screenshots`) | no ordering or replacement of scenes |
|
||||
| Version > App Previews | `shots video`, `apple previews` | renders | no | yes | no replacement |
|
||||
| App Information > Category, subcategories | `apple appinfo` (apple.info.categories) | yes | no | yes (`apple.appinfo`) | no editor |
|
||||
| App Information > Age Rating | `apple appinfo` (apple.info.ageRating), console plan | yes | no | yes | no editor of the answers |
|
||||
| App Information > Content Rights | `apple appinfo` (apple.info.contentRights) | yes | no | yes | no editor |
|
||||
| Pricing and Availability > Free, territories | `apple appinfo` (apple.info.free) | yes | no | yes | no editor |
|
||||
| Pricing and Availability > Pre-orders | `apple preorder` | no | no | terminal (owner) | public step; terminal only |
|
||||
| App Privacy (data types, purposes) | appPrivacy.types, `console fill apple-app-privacy` | yes | no | no API (console fill) | no editor of the answers |
|
||||
| App Review Information > Contact | `apple review-details` (.storebound/credentials.json reviewContact) | masked | no | yes (`apple.review_details`) | no editor |
|
||||
| App Review Information > Notes | `apple review-details` (apple.info.reviewNotes, reviewAccess.instructions) | yes | no | yes | no editor |
|
||||
| App Review Information > Sign-in required, demo account | reviewAccess.signInRequired, credentials.json reviewDemoAccount | masked | no | yes | no editor of sign-in required |
|
||||
| Export compliance (encryption) | `apple encryption` (apple.usesNonExemptEncryption) | yes | no | yes | no editor |
|
||||
| In-App Purchases > reference name, display name, description per locale | `apple iap` (products[]) | parity only | no | yes (`apple.iap`) | no editor |
|
||||
| In-App Purchases > price | products[].usd, overrides, `prices import` | yes (Prices) | no | yes, owner gate | no editor of the base price |
|
||||
| In-App Purchases > review note, screenshot | products[].reviewNote / reviewScreenshot, `apple iap-review` | no | no | yes (`apple.iap_review`) | not shown, no editor |
|
||||
| Subscriptions > group name per locale | `apple subscriptions` (subscriptions[].listings) | no | no | yes | no editor |
|
||||
| Subscriptions > name, description per locale | `apple subscriptions` (subscriptions[].subscriptions[].listings) | parity only | no | yes | no editor |
|
||||
| Subscriptions > level, period, price | `apple subscriptions` | parity only | no | yes, owner gate for live prices | no editor |
|
||||
| Subscriptions > introductory and promotional offers | subscriptions[].subscriptions[].intro / offers | no | no | yes (sync) | no editor |
|
||||
| Subscriptions > offer codes | `apple subscriptions` (offerCodes), `apple offer-codes` | no | no | terminal (owner) | no editor |
|
||||
| Subscriptions > win-back offers | `apple win-back` (winBack) | no | no | terminal (owner) | no editor |
|
||||
| In-App Purchases > promoted purchases | `apple promoted` (apple.promoted) | no | no | terminal (owner) | no editor |
|
||||
| Custom product pages | `apple cpp list / create / upload` (apple.customProductPages) | no | no | CLI only | not in the window |
|
||||
| In-app events | `apple events list / create / upload` (apple.events) | no | no | CLI only | not in the window |
|
||||
| Product Page Optimization | `apple ppo` | yes | create / start / stop | yes (owner start / stop) | none |
|
||||
| TestFlight > What to Test (per build) | `apple testflight what-to-test` | yes | yes (text) | yes | per-locale text not editable |
|
||||
| TestFlight > groups | `apple testflight groups sync` (apple.testflight.groups) | yes | no | yes (create missing) | no group editor |
|
||||
| TestFlight > Test Information (beta description, notes) | `apple testflight info` (apple.testflight.description / reviewNotes) | no | no | CLI only | not in the window |
|
||||
| TestFlight > testers | `owner.testflight_testers` (.storebound/testers.csv) | count | no | yes (owner) | CSV not editable |
|
||||
| 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 |
|
||||
|
|
@ -69,36 +74,38 @@ it is a gap. Phase 2 closes them; this table is updated with the result.
|
|||
|
||||
| Console screen | storebound | View | Edit | Store write | Gap |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Main store listing > App name (per language) | `play listing` (`### title`, app.name) | counter only | no | yes (`play.listing`) | no text editor |
|
||||
| Main store listing > Short description | `play listing` (`### short`) | counter only | no | yes | no text editor |
|
||||
| Main store listing > Full description | `play listing` (`### full`) | counter only | no | yes | no text editor, no stuffing check while typing |
|
||||
| Release > Release notes (per language) | `play release`, `whats-new apply` (`### What's new`) | counter only | no | with the release (owner) | no editor |
|
||||
| Main store listing > Graphics (icon, feature graphic, screenshots) | `icons`, `shots render`, `play images` | renders | no | yes (`play.images`) | no ordering or replacement of scenes |
|
||||
| Main store listing > Video (YouTube URL) | `play listing` (play.video) | no | no | yes | not shown, no editor |
|
||||
| Store settings > Contact email | `play listing` app details (app.contactEmail) | no | no | yes | not shown, no editor |
|
||||
| Store settings > Website | `play listing` app details (app.website) | no | no | yes | not shown, no editor |
|
||||
| Store settings > App category | play.category, console plan (no API) | yes | no | no API | no editor |
|
||||
| App content > Privacy policy | app.privacyPolicyUrl, console plan | no | no | no API | not shown, no editor |
|
||||
| App content > Data safety | `play data-safety` (dataSafety) | yes | no | yes (`play.data_safety`) | no editor of the answers |
|
||||
| App content > Content rating (IARC) | social, products[].randomRewards, `console fill play-content-rating` | yes | no | no API (console fill) | no editor of the answers |
|
||||
| App content > Target audience | play.targetAges, `console fill play-target-audience` | plan | no | no API (console fill) | no editor |
|
||||
| Monetize > In-app products: title, description per language | `play products` (products[].listings) | parity only | no | yes (`play.products`) | no editor |
|
||||
| Monetize > In-app products: price | products[].usd / overrides, `prices import` | yes (Prices) | no | yes, owner gate | no editor of the base price |
|
||||
| Monetize > Subscriptions: title, benefits, description per language | `play subscriptions` | parity only | no | yes | no editor |
|
||||
| Monetize > Subscriptions: base plans, offers, price | `play subscriptions` | parity only | no | yes, owner gate | no editor |
|
||||
| Pricing > regional prices (price sheet) | `prices export / import` | yes | no | yes (owner) | sheet not editable in place |
|
||||
| Production > Countries / regions | play.countries, console plan | count | no | no API | no editor |
|
||||
| Publishing overview > Managed publishing | play.managedPublishing | yes | no | no API | no editor |
|
||||
| Testing tracks (internal, closed, open) | `play release --track` | status | n/a | terminal (owner) | sends builds to testers: not in the window by design |
|
||||
| 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 | CLI only | not in the window |
|
||||
| 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`, `catalog pull` | no | n/a | n/a (writes local files) | not in the window |
|
||||
| Store-only app (no folder): listing text | (none) | yes (status) | no | no | store-only apps are read-only in the window |
|
||||
| Uncommitted changes in the app folder | git | no | n/a | n/a | not shown |
|
||||
| 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) |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue