Console pages as absolute deep links: cli/lib/console-links.ts builds the URL of every App Store Connect / Play Console page from apple.appId, play.developerId and play.appId (app list / developer chooser when unknown); every plain 'App Store Connect > ...' / 'Play Console > ...' direction in cli/ and templates/ now carries it, validate / scan / declarations / release plan JSON carry url, the desktop shows an Open button; tests/console-links.test.ts guards it; AGENTS rule 11, pitfall 71

This commit is contained in:
CRTkafa 2026-10-15 19:55:37 +08:00
parent f898d642d0
commit e500e88e40
50 changed files with 381 additions and 92 deletions

View file

@ -4,6 +4,7 @@
// (no API: the answers to enter in App Store Connect), app info, export compliance and the App Review details.
// The forms without an API (App Privacy, age rating, the Play questionnaires, a missing App Store app record) have a
// "Fill in console" button (ui/ConsoleFill.tsx): storebound fills them in a visible browser, the owner saves.
import { OpenLink } from '../../ui/OpenLink';
import { useState } from 'react';
import { api, type ConsoleForm, type DeclarationsResult, type DeclAsk, type Project } from '../../api';
import { type Key, t } from '../../i18n';
@ -38,6 +39,7 @@ function AskCard({ ask }: { ask: DeclAsk[] }) {
<div className="li" key={a.id}>
<span className="li-icon tone-bg-warn"><Icon name="question" /></span>
<div className="li-main"><span className="li-title">{a.text}</span><span className="fix"><Icon name="wand" />{a.fix}</span></div>
<OpenLink url={a.url} />
<Chip tone="outline">{t(AREA[a.area])}</Chip>
</div>
))}

View file

@ -10,6 +10,7 @@ import { AiFix, aiFinding } from '../../ui/AiFix';
import { api } from '../../api';
import { useEffect, useState } from 'react';
import { Icon } from '../../ui/icons';
import { OpenLink } from '../../ui/OpenLink';
import { Card, Chip, Empty, ErrorNotice, ReadControl, Skeleton } from '../../ui/kit';
import { exportName, validateMd } from '../../lib/exports';
import { useExports } from '../../ui/Exports';
@ -70,6 +71,7 @@ function ScanCard({ project }: { project: Project }) {
{ev.slice(0, 2).map((e, j) => <span className="li-sub selectable" key={j}><code>{e.file}:{e.line}</code>{e.text ? ` ${e.text}` : ''}</span>)}
{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>}
{!f.accepted && <OpenLink url={f.url} />}
{!f.accepted && f.aiFix && !fixable(f.action) && <AiFix projectId={project.id} f={f} claude={claude} busy={aiBusy} setBusy={setAiBusy} rescan={() => { s.refresh(); }}
stillThere={findings.some(x => same(x, f))} onActive={on => setAiActive(on ? f : null)} />}
</div>
@ -131,6 +133,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>
<OpenLink url={f.url} />
{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>