import { MOBILE_DEFAULT_THEME_ID, MOBILE_THEME_IDS, type MobileThemeId,} from "@t3tools/shared/themePalettes"; import { SHOWCASE_SCENES, type ShowcaseScene } from "./mobile-showcase-environment.ts"; export { SHOWCASE_SCENES };export type { ShowcaseScene }; export type ShowcaseAppearance = "light" | "dark"; /** Every palette the mobile appearance settings can select. */export const SHOWCASE_THEMES = MOBILE_THEME_IDS;export const DEFAULT_SHOWCASE_THEME = MOBILE_DEFAULT_THEME_ID;export type ShowcaseTheme = MobileThemeId; export interface ShowcaseStoreAssetSpec { readonly store: "apple" | "google-play"; /** Device directory relative to ShowcaseConfig.outputDirectory. */ readonly directory: string; readonly width: number; readonly height: number; readonly minimumUploadCount: number; readonly maximumUploadCount: number; readonly maximumFileSizeBytes?: number;} export interface ShowcaseIosDevice { readonly id: string; readonly platform: "ios"; /** Exact name from `xcrun simctl list devices available`. */ readonly simulator: string; /** Device type used to create a disposable simulator when the named one is absent. */ readonly simulatorDeviceType?: string; /** Appearance used when the CLI does not pass --appearance. */ readonly appearance: ShowcaseAppearance; /** Palette used when the CLI does not pass --theme. */ readonly theme: ShowcaseTheme; /** Orientation applied by the capture harness. Defaults to portrait. */ readonly orientation?: "portrait" | "landscape"; readonly scenes: ReadonlyArray<ShowcaseScene>; readonly storeAsset: ShowcaseStoreAssetSpec;} export interface ShowcaseAndroidDevice { readonly id: string; readonly platform: "android"; /** Exact name from `emulator -list-avds`. */ readonly avd: string; /** Appearance used when the CLI does not pass --appearance. */ readonly appearance: ShowcaseAppearance; /** Palette used when the CLI does not pass --theme. */ readonly theme: ShowcaseTheme; /** Native ABI used by the AVD, from its config.ini `abi.type`. */ readonly abi?: "arm64-v8a" | "x86_64" | "x86" | "armeabi-v7a"; readonly scenes: ReadonlyArray<ShowcaseScene>; /** Optional capture viewport. Omit to use the AVD's native size and density. */ readonly viewport?: { readonly width: number; readonly height: number; readonly density?: number; }; readonly storeAsset: ShowcaseStoreAssetSpec;} export type ShowcaseDevice = ShowcaseIosDevice | ShowcaseAndroidDevice; export interface ShowcaseConfig { readonly outputDirectory: string; readonly metroPort: number; readonly settleDelayMs: number; readonly devices: ReadonlyArray<ShowcaseDevice>;} const ANDROID_ABIS = ["arm64-v8a", "x86_64", "x86", "armeabi-v7a"] as const; export function resolveShowcaseAndroidAbi( value: string | undefined,): NonNullable<ShowcaseAndroidDevice["abi"]> { if (!value) return "arm64-v8a"; if (ANDROID_ABIS.some((abi) => abi === value)) { return value as NonNullable<ShowcaseAndroidDevice["abi"]>; } throw new Error( `Unsupported T3_SHOWCASE_ANDROID_ABI '${value}'. Use ${ANDROID_ABIS.join(", ")}.`, );} /** * The defaults cover every App Store Connect and Google Play upload slot used * by the mobile app. Edit this matrix (or pass --device / --scene) without * changing the runner. Every target declares and validates its exact upload * dimensions so SDK or emulator changes cannot silently produce invalid files. */const config: ShowcaseConfig = { outputDirectory: "artifacts/app-store/screenshots", // Dedicated port so the harness cannot attach to a normal mobile dev server // (or a second worktree) and capture the wrong bundle. metroPort: 8199, settleDelayMs: 2_500, devices: [ { id: "iphone-6.9", platform: "ios", simulator: "iPhone 17 Pro Max", simulatorDeviceType: "com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro-Max", appearance: "dark", theme: DEFAULT_SHOWCASE_THEME, scenes: ["thread", "terminal", "review", "threads", "environments"], storeAsset: { store: "apple", directory: "apple/iphone-6.9", width: 1320, height: 2868, minimumUploadCount: 1, maximumUploadCount: 10, }, }, { id: "iphone-6.5", platform: "ios", simulator: "T3 Showcase iPhone 14 Plus", simulatorDeviceType: "com.apple.CoreSimulator.SimDeviceType.iPhone-14-Plus", appearance: "dark", theme: DEFAULT_SHOWCASE_THEME, scenes: ["thread", "terminal", "review", "threads", "environments"], storeAsset: { store: "apple", directory: "apple/iphone-6.5", width: 1284, height: 2778, minimumUploadCount: 1, maximumUploadCount: 10, }, }, { id: "ipad-13", platform: "ios", simulator: "iPad Pro 13-inch (M5)", simulatorDeviceType: "com.apple.CoreSimulator.SimDeviceType.iPad-Pro-13-inch-M5-16GB", appearance: "dark", theme: DEFAULT_SHOWCASE_THEME, orientation: "landscape", scenes: ["thread", "terminal", "review", "threads", "environments"], storeAsset: { store: "apple", directory: "apple/ipad-13", width: 2752, height: 2064, minimumUploadCount: 1, maximumUploadCount: 10, }, }, { id: "pixel", platform: "android", avd: "Pixel_10_Pro", // Apple Silicon uses ARM64 locally; CI overrides this with x86_64 so its // Blacksmith Linux runner can use KVM acceleration. abi: resolveShowcaseAndroidAbi(process.env.T3_SHOWCASE_ANDROID_ABI), appearance: "dark", theme: DEFAULT_SHOWCASE_THEME, viewport: { width: 1080, height: 1920, density: 420, }, scenes: ["thread", "terminal", "review", "threads", "environments"], storeAsset: { store: "google-play", directory: "google-play/phone", width: 1080, height: 1920, minimumUploadCount: 2, maximumUploadCount: 8, maximumFileSizeBytes: 8 * 1024 * 1024, }, }, { id: "android-tablet-7", platform: "android", avd: "Pixel_10_Pro", abi: resolveShowcaseAndroidAbi(process.env.T3_SHOWCASE_ANDROID_ABI), appearance: "dark", theme: DEFAULT_SHOWCASE_THEME, viewport: { width: 1080, height: 1920, density: 288, }, scenes: ["thread", "terminal", "review", "threads", "environments"], storeAsset: { store: "google-play", directory: "google-play/tablet-7", width: 1080, height: 1920, minimumUploadCount: 4, maximumUploadCount: 8, maximumFileSizeBytes: 8 * 1024 * 1024, }, }, { id: "android-tablet-10", platform: "android", avd: "Pixel_10_Pro", abi: resolveShowcaseAndroidAbi(process.env.T3_SHOWCASE_ANDROID_ABI), appearance: "dark", theme: DEFAULT_SHOWCASE_THEME, viewport: { width: 1440, height: 2560, density: 288, }, scenes: ["thread", "terminal", "review", "threads", "environments"], storeAsset: { store: "google-play", directory: "google-play/tablet-10", width: 1440, height: 2560, minimumUploadCount: 4, maximumUploadCount: 8, maximumFileSizeBytes: 8 * 1024 * 1024, }, }, ],}; export default config;
import {
MOBILE_DEFAULT_THEME_ID,
MOBILE_THEME_IDS,
type MobileThemeId,
} from "@t3tools/shared/themePalettes";
import { SHOWCASE_SCENES, type ShowcaseScene } from "./mobile-showcase-environment.ts";
export { SHOWCASE_SCENES };
export type { ShowcaseScene };
export type ShowcaseAppearance = "light" | "dark";
/** Every palette the mobile appearance settings can select. */
export const SHOWCASE_THEMES = MOBILE_THEME_IDS;
export const DEFAULT_SHOWCASE_THEME = MOBILE_DEFAULT_THEME_ID;
export type ShowcaseTheme = MobileThemeId;
export interface ShowcaseStoreAssetSpec {
readonly store: "apple" | "google-play";
/** Device directory relative to ShowcaseConfig.outputDirectory. */
readonly directory: string;
readonly width: number;
readonly height: number;
readonly minimumUploadCount: number;
readonly maximumUploadCount: number;
readonly maximumFileSizeBytes?: number;
}
export interface ShowcaseIosDevice {
readonly id: string;
readonly platform: "ios";
/** Exact name from `xcrun simctl list devices available`. */
readonly simulator: string;
/** Device type used to create a disposable simulator when the named one is absent. */
readonly simulatorDeviceType?: string;
/** Appearance used when the CLI does not pass --appearance. */
readonly appearance: ShowcaseAppearance;
/** Palette used when the CLI does not pass --theme. */
readonly theme: ShowcaseTheme;
/** Orientation applied by the capture harness. Defaults to portrait. */
readonly orientation?: "portrait" | "landscape";
readonly scenes: ReadonlyArray<ShowcaseScene>;
readonly storeAsset: ShowcaseStoreAssetSpec;
}
export interface ShowcaseAndroidDevice {
readonly id: string;
readonly platform: "android";
/** Exact name from `emulator -list-avds`. */
readonly avd: string;
/** Appearance used when the CLI does not pass --appearance. */
readonly appearance: ShowcaseAppearance;
/** Palette used when the CLI does not pass --theme. */
readonly theme: ShowcaseTheme;
/** Native ABI used by the AVD, from its config.ini `abi.type`. */
readonly abi?: "arm64-v8a" | "x86_64" | "x86" | "armeabi-v7a";
readonly scenes: ReadonlyArray<ShowcaseScene>;
/** Optional capture viewport. Omit to use the AVD's native size and density. */
readonly viewport?: {
readonly width: number;
readonly height: number;
readonly density?: number;
};
readonly storeAsset: ShowcaseStoreAssetSpec;
}
export type ShowcaseDevice = ShowcaseIosDevice | ShowcaseAndroidDevice;
export interface ShowcaseConfig {
readonly outputDirectory: string;
readonly metroPort: number;
readonly settleDelayMs: number;
readonly devices: ReadonlyArray<ShowcaseDevice>;
}
const ANDROID_ABIS = ["arm64-v8a", "x86_64", "x86", "armeabi-v7a"] as const;
export function resolveShowcaseAndroidAbi(
value: string | undefined,
): NonNullable<ShowcaseAndroidDevice["abi"]> {
if (!value) return "arm64-v8a";
if (ANDROID_ABIS.some((abi) => abi === value)) {
return value as NonNullable<ShowcaseAndroidDevice["abi"]>;
}
throw new Error(
`Unsupported T3_SHOWCASE_ANDROID_ABI '${value}'. Use ${ANDROID_ABIS.join(", ")}.`,
);
}
/**
* The defaults cover every App Store Connect and Google Play upload slot used
* by the mobile app. Edit this matrix (or pass --device / --scene) without
* changing the runner. Every target declares and validates its exact upload
* dimensions so SDK or emulator changes cannot silently produce invalid files.
*/
const config: ShowcaseConfig = {
outputDirectory: "artifacts/app-store/screenshots",
// Dedicated port so the harness cannot attach to a normal mobile dev server
// (or a second worktree) and capture the wrong bundle.
metroPort: 8199,
settleDelayMs: 2_500,
devices: [
{
id: "iphone-6.9",
platform: "ios",
simulator: "iPhone 17 Pro Max",
simulatorDeviceType: "com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro-Max",
appearance: "dark",
theme: DEFAULT_SHOWCASE_THEME,
scenes: ["thread", "terminal", "review", "threads", "environments"],
storeAsset: {
store: "apple",
directory: "apple/iphone-6.9",
width: 1320,
height: 2868,
minimumUploadCount: 1,
maximumUploadCount: 10,
},
},
{
id: "iphone-6.5",
platform: "ios",
simulator: "T3 Showcase iPhone 14 Plus",
simulatorDeviceType: "com.apple.CoreSimulator.SimDeviceType.iPhone-14-Plus",
appearance: "dark",
theme: DEFAULT_SHOWCASE_THEME,
scenes: ["thread", "terminal", "review", "threads", "environments"],
storeAsset: {
store: "apple",
directory: "apple/iphone-6.5",
width: 1284,
height: 2778,
minimumUploadCount: 1,
maximumUploadCount: 10,
},
},
{
id: "ipad-13",
platform: "ios",
simulator: "iPad Pro 13-inch (M5)",
simulatorDeviceType: "com.apple.CoreSimulator.SimDeviceType.iPad-Pro-13-inch-M5-16GB",
appearance: "dark",
theme: DEFAULT_SHOWCASE_THEME,
orientation: "landscape",
scenes: ["thread", "terminal", "review", "threads", "environments"],
storeAsset: {
store: "apple",
directory: "apple/ipad-13",
width: 2752,
height: 2064,
minimumUploadCount: 1,
maximumUploadCount: 10,
},
},
{
id: "pixel",
platform: "android",
avd: "Pixel_10_Pro",
// Apple Silicon uses ARM64 locally; CI overrides this with x86_64 so its
// Blacksmith Linux runner can use KVM acceleration.
abi: resolveShowcaseAndroidAbi(process.env.T3_SHOWCASE_ANDROID_ABI),
appearance: "dark",
theme: DEFAULT_SHOWCASE_THEME,
viewport: {
width: 1080,
height: 1920,
density: 420,
},
scenes: ["thread", "terminal", "review", "threads", "environments"],
storeAsset: {
store: "google-play",
directory: "google-play/phone",
width: 1080,
height: 1920,
minimumUploadCount: 2,
maximumUploadCount: 8,
maximumFileSizeBytes: 8 * 1024 * 1024,
},
},
{
id: "android-tablet-7",
platform: "android",
avd: "Pixel_10_Pro",
abi: resolveShowcaseAndroidAbi(process.env.T3_SHOWCASE_ANDROID_ABI),
appearance: "dark",
theme: DEFAULT_SHOWCASE_THEME,
viewport: {
width: 1080,
height: 1920,
density: 288,
},
scenes: ["thread", "terminal", "review", "threads", "environments"],
storeAsset: {
store: "google-play",
directory: "google-play/tablet-7",
width: 1080,
height: 1920,
minimumUploadCount: 4,
maximumUploadCount: 8,
maximumFileSizeBytes: 8 * 1024 * 1024,
},
},
{
id: "android-tablet-10",
platform: "android",
avd: "Pixel_10_Pro",
abi: resolveShowcaseAndroidAbi(process.env.T3_SHOWCASE_ANDROID_ABI),
appearance: "dark",
theme: DEFAULT_SHOWCASE_THEME,
viewport: {
width: 1440,
height: 2560,
density: 288,
},
scenes: ["thread", "terminal", "review", "threads", "environments"],
storeAsset: {
store: "google-play",
directory: "google-play/tablet-10",
width: 1440,
height: 2560,
minimumUploadCount: 4,
maximumUploadCount: 8,
maximumFileSizeBytes: 8 * 1024 * 1024,
},
},
],
};
export default config;