Add debug More/Clear/Upload tools for paintings and artist portraits.

Extends the debug panel on painting detail and artist bio with a 20-result search picker, local image upload, and clear-to-empty-frame workflow, plus API routes, artist checkup migration, and documentation.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-06-21 13:14:06 +03:00
co-authored by Cursor
parent b4425445bb
commit 0972b5df99
56 changed files with 1980 additions and 92 deletions
+84 -4
View File
@@ -5,7 +5,7 @@ import VirtualGallery from '../components/VirtualGallery';
import PaintingDetailView from '../components/PaintingDetail';
import ArtistBio from '../components/ArtistBio';
import CheckupPage from '../pages/CheckupPage';
import { api, type FixPaintingImageResult } from '../api/client';
import { api, type FixPaintingImageResult, type FixArtistPortraitResult } from '../api/client';
import type { TimelineData, Artist, ArtistDetail, Painting, PaintingDetail } from '../types';
import { sortArtistPaintingsChronological } from '../utils/paintingUtils';
import { readDebugMode, writeDebugMode } from '../utils/debugMode';
@@ -29,6 +29,13 @@ function patchPaintingInArtistDetail(
};
}
function patchArtistInArtistDetail(detail: ArtistDetail, patch: Partial<Artist>): ArtistDetail {
return {
...detail,
artist: { ...detail.artist, ...patch },
};
}
export default function HomePage() {
const [view, setView] = useState<View>({ type: 'timeline' });
const [gallerySession, setGallerySession] = useState<{ artistId: number; data: ArtistDetail } | null>(
@@ -43,6 +50,7 @@ export default function HomePage() {
const [error, setError] = useState<string | null>(null);
const [detailArtistPaintings, setDetailArtistPaintings] = useState<Painting[]>([]);
const [imageRevisions, setImageRevisions] = useState<Record<number, number>>({});
const [portraitRevisions, setPortraitRevisions] = useState<Record<number, number>>({});
const [debugMode, setDebugMode] = useState(readDebugMode);
const detailReturnToRef = useRef<View>({ type: 'timeline' });
@@ -103,8 +111,8 @@ export default function HomePage() {
const handlePaintingImageFixed = useCallback(async (paintingId: number, fixResult: FixPaintingImageResult) => {
const data = await api.getPainting(paintingId);
const patch: Partial<Painting> = {
image_path: fixResult.imagePath ?? data.painting.image_path,
thumbnail_path: fixResult.thumbnailPath ?? data.painting.thumbnail_path,
image_path: fixResult.imagePath,
thumbnail_path: fixResult.thumbnailPath,
checkup_checked: fixResult.checked ?? true,
checkup_fixed: fixResult.fixed ?? true,
};
@@ -175,6 +183,73 @@ export default function HomePage() {
[]
);
const applyArtistPatch = useCallback((artistId: number, patch: Partial<Artist>) => {
setArtists((list) => list.map((a) => (a.id === artistId ? { ...a, ...patch } : a)));
setView((current) => {
if (current.type === 'bio' && current.artistId === artistId) {
return { ...current, data: patchArtistInArtistDetail(current.data, patch) };
}
if (current.type === 'gallery' && current.artistId === artistId) {
return { ...current, data: patchArtistInArtistDetail(current.data, patch) };
}
if (current.type === 'painting' && current.data.painting.artist_id === artistId) {
return {
...current,
data: {
...current.data,
painting: {
...current.data.painting,
artist_portrait: patch.portrait_path ?? current.data.painting.artist_portrait,
},
},
};
}
return current;
});
setGallerySession((session) =>
session && session.artistId === artistId
? { ...session, data: patchArtistInArtistDetail(session.data, patch) }
: session
);
}, []);
const handleArtistPortraitFixed = useCallback(
async (artistId: number, fixResult: FixArtistPortraitResult) => {
const data = await api.getArtist(artistId);
const patch: Partial<Artist> = {
portrait_path: fixResult.portraitPath,
checkup_checked: fixResult.checked ?? true,
checkup_fixed: fixResult.fixed ?? true,
};
setPortraitRevisions((prev) => ({ ...prev, [artistId]: (prev[artistId] ?? 0) + 1 }));
applyArtistPatch(artistId, patch);
setView((current) =>
current.type === 'bio' && current.artistId === artistId
? { ...current, data: { ...data, artist: { ...data.artist, ...patch } } }
: current
);
},
[applyArtistPatch]
);
const handleArtistCheckupFlagsUpdated = useCallback(
async (artistId: number, flags: { checked: boolean; fixed: boolean }) => {
const patch: Partial<Artist> = {
checkup_checked: flags.checked,
checkup_fixed: flags.fixed,
};
applyArtistPatch(artistId, patch);
setView((current) =>
current.type === 'bio' && current.artistId === artistId
? { ...current, data: patchArtistInArtistDetail(current.data, patch) }
: current
);
},
[applyArtistPatch]
);
const handleArtistClick = async (artistId: number) => {
try {
const data = await api.getArtist(artistId);
@@ -305,10 +380,14 @@ export default function HomePage() {
<div className="home-overlay">
<ArtistBio
artist={view.data.artist}
debugMode={debugMode}
portraitRevision={portraitRevisions[view.data.artist.id]}
onBack={() => setView(view.returnTo)}
onEnterGallery={() =>
setView({ type: 'gallery', artistId: view.artistId, data: view.data })
}
onArtistPortraitFixed={handleArtistPortraitFixed}
onArtistCheckupFlagsUpdated={handleArtistCheckupFlagsUpdated}
/>
</div>
)}
@@ -328,7 +407,7 @@ export default function HomePage() {
type="button"
className={`debug-mode-toggle${debugMode ? ' debug-mode-toggle-active' : ''}`}
onClick={toggleDebugMode}
title="Toggle developer image audit mode on painting details"
title="Toggle developer image audit mode on painting details and artist bios"
>
Debug mode{debugMode ? ': ON' : ''}
</button>
@@ -363,6 +442,7 @@ export default function HomePage() {
<MovementBands
movements={timelineData.movements}
artists={artists}
portraitRevisions={portraitRevisions}
viewStart={viewStart}
viewEnd={viewEnd}
absoluteMin={bounds.min}