desktop: a Fix button on scan and validate findings storebound can fix itself. Findings carry action { method, args } (cli/lib/fix-actions.ts); privacy-labels runs scan.write (scan --write-only, the dialog shows the config diff), Play listing gaps play.listing, a missing App Store localization or copyright apple.listing (editable version only), the App Review contact apple.review_details; each a dry run, the native dialog, then the confirmed run. --write-only is forbidden for MCP tools; tests pin the mapping, the round trip on a fixture and that no fix reaches a submit, release or publish
This commit is contained in:
parent
bd83d036ba
commit
992f5bcc0e
18 changed files with 257 additions and 37 deletions
|
|
@ -3,8 +3,9 @@
|
|||
import type { Project } from '../../api';
|
||||
import { t } from '../../i18n';
|
||||
import { ago } from '../../lib/format';
|
||||
import type { Finding, ScanResult, ScanSeverity, ValidateResult } from '../../lib/model';
|
||||
import { useData, useRead } from '../../store';
|
||||
import type { Finding, FixAction, ScanResult, ScanSeverity, ValidateResult } from '../../lib/model';
|
||||
import { useData, useRead, useWrite } from '../../store';
|
||||
import { WriteButton } from '../../ui/WriteButton';
|
||||
import { Icon } from '../../ui/icons';
|
||||
import { Card, Chip, Empty, ErrorNotice, ReadControl, Skeleton } from '../../ui/kit';
|
||||
import { exportName, validateMd } from '../../lib/exports';
|
||||
|
|
@ -12,11 +13,25 @@ import { useExports } from '../../ui/Exports';
|
|||
|
||||
const LEVEL: Record<Finding['level'], ['bad' | 'warn' | 'ok', 'alertCircle' | 'alert' | 'check']> = { blocker: ['bad', 'alertCircle'], warn: ['warn', 'alert'], ok: ['ok', 'check'] };
|
||||
|
||||
/** Fix methods the window runs from a finding (cli/lib/fix-actions.ts FIX_METHODS): local config writes and store
|
||||
* text pushes, each a dry run, the native dialog showing it, then the real run. Anything else gets no button. */
|
||||
export const FIX_METHODS = new Set(['scan.write', 'play.listing', 'apple.listing', 'apple.review_details']);
|
||||
export const fixable = (a: FixAction | undefined): a is FixAction => !!a && FIX_METHODS.has(a.method) && !!a.args && typeof a.args === 'object';
|
||||
|
||||
/** The Fix button of one finding: its action as a confirm-gated write. */
|
||||
function FixButton({ action, what, w }: { action: FixAction; what: string; w: ReturnType<typeof useWrite> }) {
|
||||
return (
|
||||
<WriteButton run={w} icon="wand" hint={t(`checks.fixHint.${action.method}` as Parameters<typeof t>[0])}
|
||||
w={{ method: action.method, args: action.args, title: t('checks.fixing', { what }), done: t('checks.fixed') }}>{t('checks.fix')}</WriteButton>
|
||||
);
|
||||
}
|
||||
|
||||
const SEV: Record<ScanSeverity, ['bad' | 'warn' | 'info', 'alertCircle' | 'alert' | 'info']> = { blocker: ['bad', 'alertCircle'], high: ['bad', 'alert'], medium: ['warn', 'alert'], low: ['info', 'info'] };
|
||||
|
||||
/** The `storebound scan` card: rejection risks with guideline, evidence file:line, fix and skill. */
|
||||
function ScanCard({ project }: { project: Project }) {
|
||||
const s = useRead<ScanResult>(project.id, 'scan', { network: false, title: t('checks.scan') });
|
||||
const w = useWrite(project.id);
|
||||
const findings = Array.isArray(s.data?.findings) ? s.data!.findings : [];
|
||||
const sdks = Array.isArray(s.data?.sdks) ? s.data!.sdks : [];
|
||||
const asks = (Array.isArray(s.data?.declarations?.items) ? s.data!.declarations.items : []).filter(x => x.status === 'ask').length;
|
||||
|
|
@ -39,6 +54,7 @@ function ScanCard({ project }: { project: Project }) {
|
|||
{f.accepted ? <span className="li-sub">{t('checks.accepted', { reason: f.accepted })}</span>
|
||||
: <span className="fix"><Icon name="wand" /><span className="selectable">{f.fix} <code>{f.skill}</code></span></span>}
|
||||
</div>
|
||||
{!f.accepted && fixable(f.action) && <FixButton action={f.action} what={f.title} w={w} />}
|
||||
<Chip tone={f.accepted ? 'ok' : tone}>{t(f.accepted ? 'checks.sev.accepted' : `checks.sev.${f.severity}` as Parameters<typeof t>[0])}</Chip>
|
||||
<Chip tone="outline">{f.store === 'apple' ? 'App Store' : 'Google Play'}</Chip>
|
||||
</div>
|
||||
|
|
@ -60,6 +76,7 @@ export function ChecksTab({ project }: { project: Project }) {
|
|||
const d = useData();
|
||||
const e = d.statusOf(project);
|
||||
const v = useRead<ValidateResult>(project.id, 'validate', { network: true, title: t('checks.validate') });
|
||||
const w = useWrite(project.id);
|
||||
const name = e?.summary?.name ?? project.name;
|
||||
useExports('checks', [{ id: 'validate-md', label: t('export.validate'), icon: 'shield', kind: 'md', file: exportName(name, 'store readiness'), disabled: v.data ? false : t('export.readFirst'), build: () => validateMd(name, v.data!, v.at) }]);
|
||||
const problems = [...(e?.errors ?? []), ...(e?.check?.problems ?? [])];
|
||||
|
|
@ -95,6 +112,7 @@ export function ChecksTab({ project }: { project: Project }) {
|
|||
<span className="li-title">{f.msg}</span>
|
||||
{f.fix && <span className="fix"><Icon name="wand" /><code className="selectable">{f.fix}</code></span>}
|
||||
</div>
|
||||
{f.level !== 'ok' && fixable(f.action) && <FixButton action={f.action} what={f.msg} w={w} />}
|
||||
<Chip tone="outline">{f.store === 'apple' ? 'App Store' : 'Google Play'}</Chip>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue