desktop reads everything by itself: one background read scheduler (4 per store, starts spaced per account, App Store Connect X-Rate-Limit slows it below 20 % and pauses at 100 left, backoff with jitter on 429 / RESOURCE_EXHAUSTED, visible app first, one read per key), per-method TTLs over the saved reads, Settings auto-read (on) and interval (15 min, paused while hidden), Refresh all, a refresh icon and 'read 3 min ago' / 'couldn't refresh' per card, writes invalidate, status line with progress and quota; quota reaches the window through ToolResult.quota (cli/lib/quota.ts); Health top issues open with versions, sample stack (health --issue / --signature, read health_issue), Open in Play Console, Copy for AI and likely-cause hints (cli/lib/crash-hints.ts); tests/scheduler.test.ts, tests/quota-crash-hints.test.ts, boot check auto-read case
This commit is contained in:
parent
89256f1e80
commit
0fb4ce0001
46 changed files with 1531 additions and 141 deletions
|
|
@ -225,6 +225,7 @@ export function Portfolio() {
|
|||
);
|
||||
};
|
||||
|
||||
const notRead = d.portfolioBusy === 'live' ? t('read.waiting') : t('kpi.notRead');
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="ph">
|
||||
|
|
@ -240,13 +241,13 @@ export function Portfolio() {
|
|||
{d.portfolioError && <Notice tone="bad">{d.portfolioError}</Notice>}
|
||||
<BlockedProjectsNotice blocked={blocked} onRetry={recheck} busy={rechecking} />
|
||||
{neverImported > 0 && <Notice tone="info" icon="download" action={<button className="btn btn-sm" onClick={() => d.importApps()} disabled={d.importBusy}>{t('action.importApps')}</button>}>{t('portfolio.notImported', { count: neverImported })}</Notice>}
|
||||
{!known && d.ready && <Notice tone="info" action={<button className="btn btn-sm" onClick={() => d.refreshPortfolio(true)}>{t('action.readStores')}</button>}>{t('portfolio.notReadNotice')}</Notice>}
|
||||
{!known && d.ready && d.portfolioBusy !== 'live' && <Notice tone="info" action={<button className="btn btn-sm" onClick={() => d.refreshPortfolio(true)}>{t('action.readStores')}</button>}>{t('portfolio.notReadNotice')}</Notice>}
|
||||
|
||||
<div className="kpis">
|
||||
<Kpi icon="layers" label={t('kpi.apps')} value={rows.length} sub={t('kpi.appsSubStore', { ios: iosApps, android: androidApps, store: storeOnly })} />
|
||||
<Kpi icon="check" label={t('kpi.live')} value={known ? liveCount : '–'} sub={known ? t('kpi.liveSub', { count: stores }) : t('kpi.notRead')} />
|
||||
<Kpi icon="rocket" label={t('kpi.rolling')} value={known ? rolling.length : '–'} sub={!known ? t('kpi.notRead') : rolling.length ? [...new Set(rolling.map(r => r.version))].map(v => `${v}: ${rolling.filter(r => r.version === v).map(r => `${r.store === 'ios' ? 'iOS' : 'Android'}${r.fraction !== null ? ` ${share(r.fraction)}` : ''}`).join(', ')}`).join(' · ') : t('kpi.none')} />
|
||||
<Kpi icon="clock" label={t('kpi.review')} value={known ? review.length : '–'} sub={!known ? t('kpi.notRead') : review.length ? review.map(r => `${r.store === 'ios' ? 'iOS' : 'Android'} ${r.version}`).join(' · ') : t('kpi.none')} tone={review.length ? 'warn' : undefined} />
|
||||
<Kpi icon="check" label={t('kpi.live')} value={known ? liveCount : '–'} sub={known ? t('kpi.liveSub', { count: stores }) : notRead} />
|
||||
<Kpi icon="rocket" label={t('kpi.rolling')} value={known ? rolling.length : '–'} sub={!known ? notRead : rolling.length ? [...new Set(rolling.map(r => r.version))].map(v => `${v}: ${rolling.filter(r => r.version === v).map(r => `${r.store === 'ios' ? 'iOS' : 'Android'}${r.fraction !== null ? ` ${share(r.fraction)}` : ''}`).join(', ')}`).join(' · ') : t('kpi.none')} />
|
||||
<Kpi icon="clock" label={t('kpi.review')} value={known ? review.length : '–'} sub={!known ? notRead : review.length ? review.map(r => `${r.store === 'ios' ? 'iOS' : 'Android'} ${r.version}`).join(' · ') : t('kpi.none')} tone={review.length ? 'warn' : undefined} />
|
||||
<Kpi icon="alert" label={t('kpi.attention')} value={att.length} sub={blockers ? t('kpi.blockers', { count: blockers }) : t('kpi.noBlockers')} tone={blockers ? 'bad' : att.length ? 'warn' : undefined} />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue