feat(ai): show per-category breakdown in pre-check status line
Why: when the pre-validation pass auto-fixes issues, the chat panel
just says "Pre-checks: fixed 5 issues" — generic and uninformative.
The user can't tell whether 5 invisible-container fixes happened
(structural, mostly safe), 5 unexpected-rotation fixes (aesthetic,
worth reviewing), or 5 mixed-sibling-padding fixes (consistency, worth
reviewing). With 10 detector categories now (4 original + 6 aesthetic
added in 53435bf7 / 7aef1b14 / cd1e4325 / ad025c95), the per-category
visibility starts to matter.
What:
- runPreValidationFixesDetailed() returns { total, byCategory } where
byCategory is a per-category count of APPLIED fixes (excludes the
info-severity skips and the protected-status-bar skip).
- runPreValidationFixes() kept as a thin wrapper returning .total so
no caller needs to change.
- design-validation.ts now uses the detailed result and formats the
breakdown as e.g. "fixed 5 (3 text-effect, 2 unexpected-rotation)"
in the chat panel — sorted by count descending so the dominant
category surfaces first. Both the no-vision-validation path and the
size-gated skip path show the breakdown when it exists.
Falls back to the legacy "fixed N issues" format when byCategory is
empty (defensive — should never happen if total > 0). 1080 / 1080 AI
tests still pass — the new return shape is additive and the wrapper
preserves the integer contract.
This commit is contained in:
parent
e8a07e9b08
commit
8475797c7f
|
|
@ -21,19 +21,33 @@ import type { PenNode } from '@/types/pen';
|
|||
* Detected issues that are skipped — `info` severity (detect-only) and
|
||||
* protected status-bar removals — are not counted, so callers can rely on
|
||||
* the return value as a faithful "did anything change" signal.
|
||||
*
|
||||
* Use {@link runPreValidationFixesDetailed} when the caller needs the
|
||||
* per-category breakdown for surfacing in the UI.
|
||||
*/
|
||||
export function runPreValidationFixes(): number {
|
||||
return runPreValidationFixesDetailed().total;
|
||||
}
|
||||
|
||||
export interface PreValidationResult {
|
||||
total: number;
|
||||
/** Number of fixes APPLIED per detector category (not detected). */
|
||||
byCategory: Record<string, number>;
|
||||
}
|
||||
|
||||
export function runPreValidationFixesDetailed(): PreValidationResult {
|
||||
const store = useDocumentStore.getState();
|
||||
const root = store.getNodeById(DEFAULT_FRAME_ID);
|
||||
if (!root) return 0;
|
||||
if (!root) return { total: 0, byCategory: {} };
|
||||
|
||||
const issues = detectAllIssues(root, store.document);
|
||||
return applyFixes(issues);
|
||||
}
|
||||
|
||||
function applyFixes(issues: Issue[]): number {
|
||||
function applyFixes(issues: Issue[]): PreValidationResult {
|
||||
const store = useDocumentStore.getState();
|
||||
let applied = 0;
|
||||
const byCategory: Record<string, number> = {};
|
||||
for (const issue of issues) {
|
||||
// 'info' severity is detect-only — the detector emits the issue for
|
||||
// debug visibility but is not confident enough that an auto-fix would
|
||||
|
|
@ -51,16 +65,18 @@ function applyFixes(issues: Issue[]): number {
|
|||
}
|
||||
store.removeNode(issue.nodeId);
|
||||
applied++;
|
||||
byCategory[issue.category] = (byCategory[issue.category] ?? 0) + 1;
|
||||
console.log(`[Pre-validation] ${issue.nodeId}: removed (${issue.reason})`);
|
||||
} else {
|
||||
store.updateNode(issue.nodeId, {
|
||||
[issue.property]: issue.suggestedValue,
|
||||
} as Partial<PenNode>);
|
||||
applied++;
|
||||
byCategory[issue.category] = (byCategory[issue.category] ?? 0) + 1;
|
||||
console.log(
|
||||
`[Pre-validation] ${issue.nodeId}: ${issue.property} → ${JSON.stringify(issue.suggestedValue)} (${issue.reason})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return applied;
|
||||
return { total: applied, byCategory };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,15 @@ import type { PenNode } from '@/types/pen';
|
|||
import type { AIProviderType } from '@/types/agent-settings';
|
||||
import { getCurrentVisualReference, clearVisualReference } from './visual-ref-orchestrator';
|
||||
import { resolveSkills } from '@zseven-w/pen-ai-skills';
|
||||
import { runPreValidationFixes } from './design-pre-validation';
|
||||
import { runPreValidationFixesDetailed } from './design-pre-validation';
|
||||
|
||||
/** Stringify per-category counts as "3 text-effect, 2 unexpected-rotation". */
|
||||
function formatCategoryBreakdown(byCategory: Record<string, number>): string {
|
||||
return Object.entries(byCategory)
|
||||
.sort(([, a], [, b]) => b - a)
|
||||
.map(([cat, n]) => `${n} ${cat}`)
|
||||
.join(', ');
|
||||
}
|
||||
import { captureRootFrameScreenshot } from './design-screenshot';
|
||||
import {
|
||||
applyValidationFixes,
|
||||
|
|
@ -287,11 +295,14 @@ export async function runPostGenerationValidation(options?: {
|
|||
|
||||
// Pre-validation: pure code checks (no LLM needed)
|
||||
emit('streaming', '[pending] Running pre-checks...');
|
||||
const preFixCount = runPreValidationFixes();
|
||||
const preFix = runPreValidationFixesDetailed();
|
||||
const preFixCount = preFix.total;
|
||||
if (preFixCount > 0) {
|
||||
totalApplied += preFixCount;
|
||||
log[log.length - 1] =
|
||||
`[done] Pre-checks: fixed ${preFixCount} issue${preFixCount > 1 ? 's' : ''}`;
|
||||
const breakdown = formatCategoryBreakdown(preFix.byCategory);
|
||||
log[log.length - 1] = breakdown
|
||||
? `[done] Pre-checks: fixed ${preFixCount} (${breakdown})`
|
||||
: `[done] Pre-checks: fixed ${preFixCount} issue${preFixCount > 1 ? 's' : ''}`;
|
||||
} else {
|
||||
log[log.length - 1] = '[done] Pre-checks: OK';
|
||||
}
|
||||
|
|
@ -300,10 +311,13 @@ export async function runPostGenerationValidation(options?: {
|
|||
// If LLM validation is disabled, stop after pre-checks
|
||||
if (!VALIDATION_ENABLED) {
|
||||
clearVisualReference();
|
||||
const breakdown = formatCategoryBreakdown(preFix.byCategory);
|
||||
emit(
|
||||
'done',
|
||||
preFixCount > 0
|
||||
? `[done] Pre-checks: fixed ${preFixCount} issue${preFixCount > 1 ? 's' : ''}`
|
||||
? breakdown
|
||||
? `[done] Pre-checks: fixed ${preFixCount} (${breakdown})`
|
||||
: `[done] Pre-checks: fixed ${preFixCount} issue${preFixCount > 1 ? 's' : ''}`
|
||||
: '[done] Pre-checks complete',
|
||||
);
|
||||
return { applied: totalApplied, skipped: false };
|
||||
|
|
@ -315,10 +329,13 @@ export async function runPostGenerationValidation(options?: {
|
|||
const nodeCount = countNodesInActivePage();
|
||||
if (nodeCount < VALIDATION_NODE_COUNT_THRESHOLD) {
|
||||
clearVisualReference();
|
||||
const breakdown = formatCategoryBreakdown(preFix.byCategory);
|
||||
emit(
|
||||
'done',
|
||||
preFixCount > 0
|
||||
? `[done] Pre-checks: fixed ${preFixCount} issue${preFixCount > 1 ? 's' : ''} (vision skipped: ${nodeCount} nodes < ${VALIDATION_NODE_COUNT_THRESHOLD})`
|
||||
? breakdown
|
||||
? `[done] Pre-checks: fixed ${preFixCount} (${breakdown}) (vision skipped: ${nodeCount} nodes < ${VALIDATION_NODE_COUNT_THRESHOLD})`
|
||||
: `[done] Pre-checks: fixed ${preFixCount} issue${preFixCount > 1 ? 's' : ''} (vision skipped: ${nodeCount} nodes < ${VALIDATION_NODE_COUNT_THRESHOLD})`
|
||||
: `[done] Pre-checks complete (vision skipped: ${nodeCount} nodes < ${VALIDATION_NODE_COUNT_THRESHOLD})`,
|
||||
);
|
||||
return { applied: totalApplied, skipped: false };
|
||||
|
|
|
|||
Loading…
Reference in a new issue