shots: Mac App Store screenshots (mac size, APP_DESKTOP to the macOS version), a landscape layout (Mac window or bare; Mac, Play Chromebook, Play tablets), safe margins in every template checked by shots check from the render's layout boxes, a contact sheet timeout that scales with images, sizes and load; pitfall 80
This commit is contained in:
parent
30488c512d
commit
e59539fec8
26 changed files with 532 additions and 67 deletions
|
|
@ -29,6 +29,8 @@ export interface ImageSpec {
|
|||
/** Max bytes per file (null = the store does not publish one). */
|
||||
maxBytes: number | null;
|
||||
alpha: false;
|
||||
/** Only the listed sizes as given (Mac: no portrait). */
|
||||
landscapeOnly?: true;
|
||||
}
|
||||
|
||||
const MB = 1024 * 1024;
|
||||
|
|
@ -48,10 +50,14 @@ export const IMAGE_SPECS: Record<string, ImageSpec> = {
|
|||
// optional (apple.iphoneDuo). `type` is not an ASC display type: see IPHONE_DUO_DISPLAY. No app preview sizes yet.
|
||||
'iphone-duo-outer': { store: 'apple', type: 'IPHONE_DUO_OUTER', label: 'iPhone Duo (outer display)', sizes: [[1398, 2034]], min: 1, max: 10, maxBytes: null, alpha: false },
|
||||
'iphone-duo-inner': { store: 'apple', type: 'IPHONE_DUO_INNER', label: 'iPhone Duo (inner display)', sizes: [[2007, 2853]], min: 1, max: 10, maxBytes: null, alpha: false },
|
||||
// Mac (Screenshot specifications, checked 2026-10-20): 16:10 only, one of these four, landscape, 1-10, no alpha.
|
||||
mac: { store: 'apple', type: 'APP_DESKTOP', label: 'Mac App Store', sizes: [[2880, 1800], [2560, 1600], [1440, 900], [1280, 800]], min: 1, max: 10, maxBytes: null, alpha: false, landscapeOnly: true },
|
||||
play: { store: 'play', type: 'phoneScreenshots', label: 'Play phone', sizes: [[1080, 1920]], min: 2, max: 8, maxBytes: 8 * MB, alpha: false },
|
||||
tab7: { store: 'play', type: 'sevenInchScreenshots', label: 'Play 7" tablet', sizes: [[1080, 1920]], min: 0, max: 8, maxBytes: 8 * MB, alpha: false },
|
||||
tab10: { store: 'play', type: 'tenInchScreenshots', label: 'Play 10" tablet', sizes: [[1440, 2560]], min: 0, max: 8, maxBytes: 8 * MB, alpha: false },
|
||||
// Play Console states 1024x500, JPEG or 24-bit PNG (no alpha); the upload dialog caps it at 15 MB.
|
||||
// Chromebook (Play Console listing): 16:9 or 9:16, 1080-7680 px per side; no API image type (console only)
|
||||
chromebook: { store: 'play', type: 'chromebookScreenshots', label: 'Play Chromebook', sizes: [[1920, 1080]], min: 0, max: 8, maxBytes: 8 * MB, alpha: false },
|
||||
feature: { store: 'play', type: 'featureGraphic', label: 'Play feature graphic', sizes: [[1024, 500]], min: 1, max: 1, maxBytes: 15 * MB, alpha: false },
|
||||
};
|
||||
|
||||
|
|
@ -59,7 +65,7 @@ export const IMAGE_SPECS: Record<string, ImageSpec> = {
|
|||
* Connect API 4.5.1 (spec re-checked 2026-10-20: the published zip is still the 2026-10-06 one, same sha256) has no Duo value (only AppAssetLibraryDisplayClass lists IPHONE_DUO).
|
||||
* When Apple adds it: regenerate (bun tools/gen-api.ts) and put the exact enum names here; uploads then include them. */
|
||||
export const IPHONE_DUO_DISPLAY: Record<string, string | null> = { 'iphone-duo-outer': null, 'iphone-duo-inner': null };
|
||||
export const IPHONE_DUO_NOT_IN_API = 'the App Store Connect console takes iPhone Duo screenshots, the API does not yet; storebound keeps them in store/renders';
|
||||
export const IPHONE_DUO_NOT_IN_API = 'not uploaded: the App Store Connect API has no iPhone Duo display type yet (the console takes them); the renders stay in store/renders';
|
||||
/** Reportedly required from this date for apps built with the iOS 27.1 SDK or later. */
|
||||
export const IPHONE_DUO_REQUIRED_FROM = '2027-04-01';
|
||||
const PLAY_FOLDABLE = 'Google Play has no foldable slot: foldables show the 7" / 10" tablet screenshots (tab7, tab10, play.tablets)';
|
||||
|
|
@ -72,6 +78,11 @@ export function iphoneDuoNote(o: { apple: boolean; duo: boolean; play?: boolean;
|
|||
return { level: late ? 'warn' : 'info', msg: `no iPhone Duo screenshots (outer 1398x2034, inner 2007x2853; apple.iphoneDuo): optional now, required from April 2027 for iOS 27.1 SDK builds; the App Store Connect console takes Duo screenshots and previews, the API does not yet (apple screenshots skips them with the console link)${o.play ? `. ${PLAY_FOLDABLE}` : ''}` };
|
||||
}
|
||||
|
||||
/** validate / check note for an app with a macOS version but no Mac screenshots (apple.mac off or no mac renders). */
|
||||
export function macNote(o: { macVersion: boolean; mac: boolean }): string | null {
|
||||
return o.macVersion && !o.mac ? 'the app has a macOS version but no Mac screenshots: set apple.mac: true, add landscape frames in <frames>/<lang>/landscape/, storebound shots render, then apple screenshots --platform macos (APP_DESKTOP, 2880x1800 / 2560x1600 / 1440x900 / 1280x800)' : null;
|
||||
}
|
||||
|
||||
/** Play screenshots in general (any size the store accepts): 320..3840 px per side, long side at most 2x the short. */
|
||||
export function playScreenshotOk(w: number, h: number): boolean {
|
||||
const lo = Math.min(w, h), hi = Math.max(w, h);
|
||||
|
|
@ -111,7 +122,7 @@ export function imageInfo(b: Uint8Array): ImageInfo {
|
|||
}
|
||||
|
||||
export type IssueLevel = 'error' | 'warn';
|
||||
export interface Issue { level: IssueLevel; code: 'size' | 'alpha' | 'format' | 'bytes' | 'count' | 'overflow' | 'video' | 'font-fallback' | 'glyph-missing' | 'device-crop' | 'fonts'; msg: string }
|
||||
export interface Issue { level: IssueLevel; code: 'size' | 'alpha' | 'format' | 'bytes' | 'count' | 'overflow' | 'video' | 'font-fallback' | 'glyph-missing' | 'device-crop' | 'fonts' | 'margin'; msg: string }
|
||||
|
||||
/** Problems with one image for a size key. */
|
||||
export function checkImage(info: ImageInfo, sizeKey: string): Issue[] {
|
||||
|
|
@ -121,11 +132,11 @@ export function checkImage(info: ImageInfo, sizeKey: string): Issue[] {
|
|||
if (info.format === 'unknown') return [{ level: 'error', code: 'format', msg: 'not a PNG or JPEG' }];
|
||||
// Apple: one of the listed sizes, portrait or landscape. Play screenshots: any size Play accepts (the listed one is
|
||||
// what `shots render` writes). Feature graphic: exactly 1024 x 500.
|
||||
const exact = s.sizes.some(([w, h]) => (w === info.w && h === info.h) || (s.store === 'apple' && w === info.h && h === info.w));
|
||||
const exact = s.sizes.some(([w, h]) => (w === info.w && h === info.h) || (s.store === 'apple' && !s.landscapeOnly && w === info.h && h === info.w));
|
||||
const playShot = s.store === 'play' && s.type !== 'featureGraphic';
|
||||
if (!exact && !(playShot && playScreenshotOk(info.w, info.h))) {
|
||||
const want = playShot ? `${s.sizes.map(([w, h]) => `${w}x${h}`).join(' or ')} (any 320-3840 px per side, long side at most 2x the short)`
|
||||
: s.sizes.map(([w, h]) => `${w}x${h}`).join(' or ') + (s.store === 'apple' ? ' (or landscape)' : '');
|
||||
: s.sizes.map(([w, h]) => `${w}x${h}`).join(' or ') + (s.store === 'apple' && !s.landscapeOnly ? ' (or landscape)' : '');
|
||||
out.push({ level: 'error', code: 'size', msg: `${info.w}x${info.h}, ${s.label} wants ${want}` });
|
||||
}
|
||||
if (info.alpha) out.push({ level: 'error', code: 'alpha', msg: 'has an alpha channel / transparency (both stores reject it): re-render or flatten to RGB' });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue