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:
CRTkafa 2026-10-14 00:53:38 +08:00
parent 3cea2af4e1
commit 7ac028421a
33 changed files with 852 additions and 157 deletions

View file

@ -0,0 +1,33 @@
// docs/console-parity.md is the parity checklist of the desktop against App Store Connect and Play Console: every row
// is edited in the window ("Edit" yes) or names its reason, no row is left as an open gap, and every desktop method a
// row names exists in the sidecar (so a renamed or removed method cannot leave the table claiming a write).
import { describe, expect, test } from 'bun:test';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { describeMethods } from '../cli/serve';
const DOC = readFileSync(join(import.meta.dir, '..', 'docs', 'console-parity.md'), 'utf8');
const rows = DOC.split('\n').filter(l => /^\| /.test(l) && !/^\| (Console screen|What) \|/.test(l) && !/^\| ---/.test(l))
.map(l => l.slice(2, -2).split(' | ').map(c => c.trim()));
describe('console parity checklist', () => {
test('every row: edited in the window, or the reason why not', () => {
expect(rows.length).toBeGreaterThan(60);
for (const r of rows) {
expect(r, r[0]).toHaveLength(6);
const [screen, , , edit, write, gap] = r as [string, string, string, string, string, string];
if (gap === 'none' || gap.startsWith('none ')) {
// nothing missing: edited (or nothing to edit) and written where the store has an API
expect(edit, screen).not.toBe('no');
expect(write, screen).not.toMatch(/^no\b/);
} else expect(gap, screen).toMatch(/^reason: \S/);
expect(gap, screen).not.toMatch(/no editor|not in the window$|not shown/);
}
});
test('the desktop methods the table names exist', () => {
const methods = new Set(describeMethods().map(m => m.method));
const named = [...DOC.matchAll(/`((?:apple|play|owner|project|listing|shots|reviews|prices|console)\.[a-z_]+)`/g)].map(m => m[1]!);
expect(named.length).toBeGreaterThan(20);
for (const m of new Set(named)) expect(methods.has(m), m).toBe(true);
});
});