desktop: Releases > Send for review (App Store submission state, builds by number, Submit / Resubmit / in-app purchases; Play versionCodes by number, Send to production, managed publishing, console plans for countries and Send changes for review), plan panel before every owner dialog with streamed progress; App info console steps for every console plan form with verify; Catalog price-change owner actions and the declined state; Data safety deletion answer; TestFlight create missing groups; Android SDK in Dependencies; upload progress in job toasts; boot check send-for-review cases on D1 and D3 state

This commit is contained in:
CRTkafa 2026-10-12 12:07:59 +08:00
parent 33eb24a3dc
commit 2b101f0fd8
22 changed files with 1527 additions and 57 deletions

View file

@ -12,6 +12,7 @@ import { isError, type HealthSnapshot, type Review } from './lib/model';
import { crashCrossed, newLowReviews, releaseChanges } from './lib/notify';
import { setCrashContext } from './lib/crash';
import { normalizeAccounts, normalizeProjects, normalizeWorkspace } from './lib/normalize';
import { keepLines, progressOf } from './lib/progress';
import { fixHint } from './lib/views';
export type ToastKind = 'job' | 'ok' | 'bad' | 'info';
@ -24,6 +25,8 @@ export interface Toast {
progress?: { done: number; total: number } | null;
/** Cancellable job id. */
job?: string;
/** The job's last progress lines (the toast's log), newest last. */
lines?: string[];
}
export interface AccountHealth { check?: AccountCheck; error?: string; at: string }
@ -268,7 +271,11 @@ export function DataProvider({ children }: { children: ReactNode }) {
setToasts(ts => ts.map(x => {
if (x.job !== p.job) return x;
if (p.total) return { ...x, progress: { done: p.n ?? 0, total: p.total } };
if (p.line) return { ...x, detail: lastLine(p.line) || x.detail };
if (p.line) {
// upload and render lines carry their own progress ("312/890 MB (35%), part 4/12"): the bar follows them
const lp = progressOf(p.line);
return { ...x, detail: lastLine(p.line) || x.detail, lines: keepLines(x.lines, lastLine(p.line) || p.line), ...(lp ? { progress: lp } : {}) };
}
return x;
}));
});
@ -332,6 +339,9 @@ export function useWrite(projectId: string | null) {
if (o.stage === 'dry-run') d.updateToast(tid, { kind: 'bad', title: t('write.dryFailed'), detail: withHint(lastLine(o.dryRun.output) || undefined), progress: undefined, job: undefined });
else if (o.stage === 'dialog') d.updateToast(tid, { kind: 'info', title: t('write.notApplied'), detail: t('write.notAppliedDetail'), progress: undefined, job: undefined });
else if (o.result?.ok) { d.updateToast(tid, { kind: 'ok', title: w.done, detail: lastLine(o.result.output) || undefined, progress: undefined, job: undefined }); w.after?.(); }
// exit 3: the command stopped for an owner decision it did not get (a price change of a live product: the
// Catalog tab's owner action applies it); everything else it could do was done
else if (o.result?.exitCode === 3) { d.updateToast(tid, { kind: 'info', title: t('write.declined'), detail: lastLine(o.result.output) || undefined, progress: undefined, job: undefined }); w.after?.(); }
else d.updateToast(tid, { kind: 'bad', title: t('write.failed'), detail: withHint(lastLine(o.result?.output ?? '') || undefined), progress: undefined, job: undefined });
return o;
} catch (e) {