Checks: findings with a skill get Open guide (context menu and a link on the card) to the shipped skills/<name>/SKILL.md; known-skill list kept equal to skills/ by a test

This commit is contained in:
CRTkafa 2026-10-23 09:22:22 +08:00
parent 4a74989a95
commit a656240bfb
7 changed files with 53 additions and 1 deletions

View file

@ -2045,6 +2045,8 @@ export const en = {
'acct.noDevId': 'no Play developer id',
'acct.devIdLabel': 'Play developer account id',
'acct.devIdHint': "The digits in play.google.com/console/u/0/developers/<id>. With it, Play Console links open each app's own page; the CLI uses it too.",
'checks.openGuide': 'Open guide',
'checks.openGuideHint': 'Open the {skill} guide (SKILL.md)',
'cm.more': "More actions",
'cm.showIgnored': "Show ignored apps",
'cm.copyVersion': "Copy version",

View file

@ -2047,6 +2047,8 @@ export const tr: Record<keyof typeof en, string> = {
'acct.noDevId': 'Play geliştirici kimliği yok',
'acct.devIdLabel': 'Play geliştirici hesabı kimliği',
'acct.devIdHint': 'play.google.com/console/u/0/developers/<id> içindeki rakamlar. Girilince Play Console bağlantıları her uygulamanın kendi sayfasını açar; CLI de kullanır.',
'checks.openGuide': 'Kılavuzu aç',
'checks.openGuideHint': '{skill} kılavuzunu aç (SKILL.md)',
'cm.more': "Diğer işlemler",
'cm.showIgnored': "Yok sayılanları göster",
'cm.copyVersion': "Sürümü kopyala",

View file

@ -18,6 +18,7 @@ import { OpenLink } from '../../ui/OpenLink';
import { Bar, ErrorNotice, ReadControl, Skeleton } from '../../ui/kit';
import { EmptyState, Section, StatusPill } from '../../ui/ds';
import '../../styles/checks.css';
import { skillGuideUrl } from '../../../../../cli/lib/skill-guide';
import { exportName, validateMd } from '../../lib/exports';
import { useExports } from '../../ui/Exports';
@ -90,11 +91,13 @@ function ScanCard({ project }: { project: Project }) {
const card = ({ f, k }: { f: ScanFinding; k: string }) => {
const [tone, icon] = f.accepted ? ['ok', 'check'] as const : SEV[f.severity] ?? SEV.low;
const ev = Array.isArray(f.evidence) ? f.evidence : [];
const guide = skillGuideUrl(f.skill);
return (
<CheckCard key={k} tone={tone} icon={icon} title={<><span className="ck-gl">{f.guideline}</span>{f.title}</>}
menu={() => [
!f.accepted && fixable(f.action) && fixItem(w, f.action, f.title),
!f.accepted && f.url && /^https:\/\//.test(f.url) && { label: t('link.open'), icon: 'external', onSelect: () => openUrl(d, f.url!) },
guide && { label: t('checks.openGuide'), icon: 'sparkle', shortcut: f.skill, onSelect: () => openUrl(d, guide) },
ev[0] && { label: t('cm.revealEvidence'), icon: 'folder', shortcut: `${ev[0].file.split('/').pop()}:${ev[0].line}`, onSelect: () => { api.reveal(project.id, ev[0]!.file).catch(() => {}); } },
'sep',
{ label: t('cm.copy'), icon: 'copy', submenu: [
@ -108,7 +111,7 @@ function ScanCard({ project }: { project: Project }) {
actions={<>{!f.accepted && <OpenLink url={f.url} />}{!f.accepted && fixable(f.action) && <FixButton action={f.action} what={f.title} w={w} />}</>}>
{ev.slice(0, 2).map((e, j) => <span className="ck-sub selectable" key={j}><code>{e.file}:{e.line}</code>{e.text ? ` ${e.text}` : ''}</span>)}
{f.accepted ? <span className="ck-sub">{t('checks.accepted', { reason: f.accepted })}</span>
: <span className="ck-fix"><Icon name="wand" /><span className="selectable">{f.fix} <code>{f.skill}</code></span></span>}
: <span className="ck-fix"><Icon name="wand" /><span className="selectable">{f.fix} {guide ? <button type="button" className="ck-guide" onClick={() => openUrl(d, guide)} title={t('checks.openGuideHint', { skill: f.skill })}><code>{f.skill}</code><Icon name="external" /></button> : f.skill && <code>{f.skill}</code>}</span></span>}
{!f.accepted && f.aiFix && !fixable(f.action) && <div className="ck-ai"><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>}
</CheckCard>

View file

@ -74,3 +74,7 @@
.scr-checks .ck-report { display: flex; align-items: center; gap: 16px; padding: 14px 18px; border-radius: var(--ds-r-lg); background: var(--ds-surface); border: 1px solid var(--ds-stroke); }
.scr-checks .ck-report-t { display: grid; gap: 2px; flex: 1; min-width: 0; font-size: var(--ds-t-meta); color: var(--muted); }
.scr-checks .ck-report-t b { color: var(--ink); font-size: var(--ds-t-body); }
.scr-checks .ck-guide { all: unset; display: inline-flex; align-items: center; gap: 4px; cursor: pointer; border-radius: var(--ds-r-xs); }
.scr-checks .ck-guide:hover code { text-decoration: underline; }
.scr-checks .ck-guide:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.scr-checks .ck-guide svg { width: 12px; height: 12px; margin: 0; }