Add influence rework, image checkup, debug mode, and fetched paintings.

Support artist and movement influence links with web discovery, a developer checkup table with gallery/detail thumbnails, and debug image search with fix-it workflow.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-06-20 10:29:43 +03:00
co-authored by Cursor
parent 0ece1195fa
commit bf7db9b25e
246 changed files with 2429 additions and 301 deletions
+19 -2
View File
@@ -164,8 +164,25 @@ Painting detail with influence graph neighbours.
```json
{
"painting": { "id": 10, "title": "...", "artist_name": "...", "image_path": "...", "has_influence_links": true, ... },
"influencedBy": [ { "id": 3, "title": "...", "artist_name": "...", "notes": "...", "aspects": "...", "quote": "...", "source_author": "...", ... } ],
"influenced": [ { "id": 20, "title": "...", ... } ]
"influencedBy": [
{
"source_type": "painting",
"id": 3, "title": "...", "artist_name": "...", "year": 1885,
"notes": "...", "aspects": "...", "quote": "...", "source_author": "...", "source_url": "..."
},
{
"source_type": "artist",
"source_artist_id": 12, "source_artist_name": "Paul Cézanne",
"artist_portrait": "...", "period_note": "during creation (1907)",
"period_start_year": 1907, "period_end_year": 1907
},
{
"source_type": "movement",
"movement_id": 8, "movement_name": "Fauvism", "movement_color": "#c45c26",
"period_start_year": 1905, "period_end_year": 1907
}
],
"influenced": [ { "source_type": "painting", "id": 20, "title": "...", ... } ]
}
```
+33 -1
View File
@@ -24,6 +24,9 @@ erDiagram
artist_periods ||--o{ paintings : groups
paintings ||--o{ painting_influences : influenced_by
paintings ||--o{ painting_influences : influences
paintings ||--o{ painting_influence_sources : influenced_by
artists ||--o{ painting_influence_sources : artist_source
art_movements ||--o{ painting_influence_sources : movement_source
```
## Tables
@@ -114,7 +117,35 @@ Directed edges: *this painting* was influenced by *that painting*.
Unique constraint on `(painting_id, influenced_by_painting_id)`.
Used by the 3D gallery exit panel: predecessors are artists of `influenced_by_painting_id` works; successors are artists of paintings influenced by this artists works (see [API.md](API.md#get-apiartistsidnavigation)).
Used by the 3D gallery exit panel: predecessors are artists of `influenced_by_painting_id` works; successors are artists of paintings influenced by this artists works (see [API.md](API.md#get-apiartistsidnavigation)). Legacy table; new polymorphic links live in `painting_influence_sources`.
### `painting_influence_sources`
Polymorphic influence links: *this painting* was influenced by a **painting**, **artist**, or **movement**.
| Column | Type | Notes |
|--------|------|-------|
| `id` | SERIAL PK | |
| `painting_id` | FK → `paintings` | The work being explained |
| `source_type` | VARCHAR(20) | `painting`, `artist`, or `movement` |
| `source_painting_id` | FK → `paintings` | When `source_type = painting` |
| `source_artist_id` | FK → `artists` | When `source_type = artist` |
| `source_movement_id` | FK → `art_movements` | When `source_type = movement` |
| `period_note` | VARCHAR(240) | Human-readable period label |
| `period_start_year` | INTEGER | Optional span start |
| `period_end_year` | INTEGER | Optional span end |
| `notes` | TEXT | Curator summary |
| `source` | VARCHAR(500) | General attribution |
| `aspects` | TEXT | What was borrowed |
| `quote` | TEXT | Short citation |
| `source_author` | VARCHAR(200) | e.g. Gombrich, Janson |
| `source_url` | VARCHAR(500) | Reference link |
| `discovered_via` | VARCHAR(120) | e.g. `wikipedia`, `wikidata`, `web:metmuseum.org` |
| `confidence` | VARCHAR(20) | `curated` (default) or `discovered` |
Unique index on `(painting_id, source_type, source_painting_id, source_artist_id, source_movement_id)` with COALESCE for null FKs.
Used by painting detail API (`influencedBy`). Painting-type rows also feed `has_influence_links` and hall navigation (with legacy `painting_influences`).
## Indexes
@@ -122,6 +153,7 @@ Used by the 3D gallery exit panel: predecessors are artists of `influenced_by_pa
- `paintings(artist_id)`, `paintings(period_id)`
- `art_movements(era_id)`, `art_movements(start_year, end_year)`
- `painting_influences(painting_id)`, `painting_influences(influenced_by_painting_id)`
- `painting_influence_sources(painting_id)`, `painting_influence_sources(source_artist_id)`, `painting_influence_sources(source_movement_id)`
## First-time setup
+6 -5
View File
@@ -9,7 +9,7 @@ The app is organised as a **drill-down hierarchy**:
1. **Timeline** — historical eras (Ancient → Contemporary) with definite or fuzzy date boundaries.
2. **Movement flow** — art movements as curved SVG streams on the same year axis; documented predecessor→successor branches; portrait thumbnails placed along each stream.
3. **3D gallery** — one personal hall per artist; paintings on the walls, open centre, single exit for influence-based navigation.
4. **Painting detail** — full work in the centre, *Influenced By* on the left, *Influenced* on the right, prev/next catalog browsing, optional fullscreen, link to artist biography.
4. **Painting detail** — full work in the centre, *Influenced By* on the left, *Influenced* on the right (paintings, artists, or movements), prev/next catalog browsing, optional fullscreen, link to artist biography.
5. **Artist biography** — portrait, lifespan, movement, and Wikipedia-sourced intro text (`bio_short` / `bio_full`).
All artwork images are stored locally under `data/images/` — the UI never hot-links to Wikipedia or Commons at runtime (except optional on-demand fetch when a file is missing).
@@ -149,7 +149,7 @@ Each artist appears as a **portrait circle** on their movements stream row:
Artist portraits stop wheel/drag propagation so zooming over a face does not fight portrait clicks.
**Note:** Movement lineage is **frontend curation** for layout and labels — it is not stored in PostgreSQL. Painting-to-painting influence edges (`painting_influences`) are separate and drive the 3D exit picker and detail panels.
**Note:** Movement lineage is **frontend curation** for layout and labels — it is not stored in PostgreSQL. Painting influence links (`painting_influence_sources`, plus legacy `painting_influences` for hall navigation) are separate and drive the 3D exit picker and detail panels.
## Virtual gallery (3D halls)
@@ -191,7 +191,7 @@ Opened from the 3D hall (click a frame) or from influence thumbnails on another
| Layer | What you see |
|-------|----------------|
| **Detail** | Centre image, *Influenced By* (left) and *Influenced* (right), position in catalog (e.g. `3 of 12`) |
| **Detail** | Centre image, *Influenced By* (left) and *Influenced* (right) — painting thumbnails, artist portraits, or movement swatches — position in catalog (e.g. `3 of 12`) |
| **Fullscreen** | Click the centre image; `Escape` or click anywhere to return to detail only |
**Controls:**
@@ -202,6 +202,7 @@ Opened from the 3D hall (click a frame) or from influence thumbnails on another
| `←` / `→` | Same as prev / next (disabled while fullscreen is open) |
| Click centre image | Open fullscreen lightbox |
| Click influence thumbnail | Open that works detail (different artist allowed) |
| Click influence artist portrait | Open that artists 3D gallery hall |
| **← Back to Gallery** | Return to the hall you entered from — **3D camera position is preserved** |
| **About {artist}** | Open artist biography |
@@ -217,9 +218,9 @@ Opened from the 3D hall (click a frame) or from influence thumbnails on another
- **Movement filtering** on zoom only shows movements that have at least one artist active in the visible year range.
- **Movement lineage** (`movement-lineage.ts`) documents art-historical predecessor→successor links for the flow diagram; extend that file to add or correct branches.
- **One hall per artist** keeps navigation predictable: enter from the timeline or bio, leave via the single exit or back button.
- **Influence-based hall links** connect artists through documented painting-to-painting relationships, grouped by movement at the exit.
- **Influence-based hall links** connect artists through documented painting relationships, grouped by movement at the exit.
- **3D gallery images** use locally cached files only; slow remote fetches would break realtime rendering.
- **Influence data** is stored as directed edges between paintings, with optional citation fields (source author, quote, URL) for art-historical references.
- **Influence data** is stored as directed links from paintings to sources (another painting, an artist, or a movement), with optional period fields and citation metadata (source author, quote, URL).
## Related docs
+54 -8
View File
@@ -113,22 +113,68 @@ Renaissance and medieval masters with large museum catalog dumps (e.g. Raphael,
## Painting influence graph
Directed edges in `painting_influences` drive:
Directed influence links are stored in **`painting_influence_sources`**. Each row connects a painting to a **source** of type `painting`, `artist`, or `movement`, with optional period context (e.g. influence during the works creation year).
- **Painting detail** — *Influenced By* (left) and *Influenced* (right) panels with notes, aspects, and citations
- **3D hall exit** — predecessor and successor artists grouped by movement
- **3D gallery lamps** — a golden picture light above any frame whose work has an influence edge (`has_influence_links` on painting API responses)
The legacy **`painting_influences`** table (painting-to-painting only) is still written for hall navigation compatibility and is backfilled into `painting_influence_sources` on migration.
Influence data drives:
- **Painting detail** — *Influenced By* (left) and *Influenced* (right) panels: painting thumbnails, artist portraits, or movement colour swatches, plus notes, aspects, period labels, and citations
- **3D hall exit** — predecessor and successor artists grouped by movement (painting edges only)
- **3D gallery lamps** — golden picture light above frames with any influence edge (`has_influence_links` on painting API responses)
### One-time migration
```bash
npm run migrate:influence-sources # create table + backfill legacy painting edges
```
### Curated updates
`npm run update-influences` runs `scripts/update-influences.js` against `scripts/art-influences-data.js`:
```bash
npm run update-influences # insert edges; create missing artists/paintings
npm run update-influences -- --fetch-images # also download images for newly created works
npm run update-influences # insert curated edges
npm run update-influences -- --fetch-images # also download images for newly created works
npm run update-influences -- --discover # curated + web discovery pass
npm run discover-influences # discovery only (no curated file pass)
npm run update-influences -- --discover --limit=20 # cap discovery to N works
```
Each entry defines a later `work` influenced by an earlier `influencedBy` painting, plus optional curator fields (`notes`, `aspects`, `source_author`, `source`). When `artistMeta` is included, missing artists are created with movement and lifespan. Missing paintings are inserted with `wikipedia_title` for image fetch.
Each entry defines a later `work` and one or more `influencedBy` sources. Legacy single-object form is still supported:
Extend the data file to add lineage chains (e.g. Giotto → Masaccio → Michelangelo → Manet → Picasso → Warhol). New bridge artists may include Géricault, Friedrich, Constable, Giorgione, Poussin, de Chirico, and Böcklin.
```javascript
{ work: { artist: '...', title: '...', year: 1907 },
influencedBy: { artist: 'Giotto', title: 'Lamentation' } }
```
Multi-source form (painting, artist, movement):
```javascript
{
work: { artist: 'Pablo Picasso', title: "Les Demoiselles d'Avignon", year: 1907 },
influencedBy: [
{ type: 'painting', artist: 'Paul Cézanne', title: 'The Bathers' },
{ type: 'artist', artist: 'Paul Cézanne', period: { duringCreation: true } },
{ type: 'movement', movement: 'Fauvism', period: { start: 1905, end: 1907, note: '...' } },
],
notes, aspects, source_author, source, source_url,
}
```
When `artistMeta` is included, missing artists are created with movement and lifespan. Missing paintings are inserted with `wikipedia_title` for image fetch.
### Web discovery
`scripts/influence-discovery.js` searches art-history sources when `--discover` or `--discover-only` is passed:
- Wikipedia summaries and Wikidata **P737** (influenced by)
- Met Museum collection API
- DuckDuckGo site-restricted search across TheArtStory, Met, Google Arts & Culture, NGA, Art Institute of Chicago, MoMA, Britannica, JSTOR, Oxford Art Online, WikiArt, and Wikipedia
Discovered rows are stored with `confidence: discovered` and `discovered_via` (e.g. `wikipedia`, `wikidata`, `met`, `web:theartstory.org`). Period hints are inferred when the source text mentions influence during creation or a date range overlapping the works year.
Extend `art-influences-data.js` for high-quality curated chains; use discovery to suggest additional artist and movement links for manual review.
## Movement lineage (frontend flow diagram)
+7 -3
View File
@@ -111,8 +111,11 @@ Open http://localhost:3001 (or your configured `PORT`).
| `npm run migrate:thumbnails` | `db/migrate-thumbnails.sql` | Add thumbnail columns |
| `npm run fetch-artist-images` | `scripts/fetch-artist-images.js` | Backfill portrait files *(if present)* |
| `npm run sync-image-paths` | `scripts/sync-image-paths.js` | Align DB paths with disk *(if present)* |
| `npm run update-influences` | `scripts/update-influences.js` | Insert influence edges from `art-influences-data.js` |
| `npm run migrate:influence-sources` | `scripts/migrate-influence-sources.js` | Create `painting_influence_sources` + backfill legacy edges |
| `npm run update-influences` | `scripts/update-influences.js` | Insert influence links (painting / artist / movement) from `art-influences-data.js` |
| `npm run update-influences -- --fetch-images` | ↑ | Also download images for newly created works |
| `npm run update-influences -- --discover` | ↑ | Curated pass + web discovery (Wikipedia, Wikidata, Met, art-history sites) |
| `npm run discover-influences` | `update-influences.js --discover-only` | Discovery pass only |
### Scripts in the repository
@@ -121,7 +124,8 @@ These are checked in and maintained:
- `image-fetcher.js` — Wikimedia / museum image resolution
- `fetch-artist-bios.js` — artist biographies
- `expand-paintings.js` + `famous-paintings-data.js` — catalog expansion
- `update-influences.js` + `art-influences-data.js` painting influence graph
- `update-influences.js` + `art-influences-data.js` — influence graph (paintings, artists, movements)
- `influence-discovery.js` + `influence-resolver.js` — web discovery and polymorphic source resolution
- `fetch-missing-images.js` — batch image backfill
- `regenerate-thumbnails.js`, `audit-painting-images.js`
@@ -153,7 +157,7 @@ After clone: copy `.env.example` → `.env`, install dependencies, run `npm run
| Wrong painting in 3D gallery frame | Stale or mismatched thumbnail file | `npm run regenerate-thumbnails`; gallery prefers full `image_path` |
| Wrong painting image (fetch) | Bad museum / search match on first download | Add entry to `DIRECT_IMAGE_OVERRIDES` in `scripts/image-fetcher.js`, re-fetch file |
| Empty exit navigation lists | No `painting_influences` edges for artist | `npm run update-influences` |
| Empty *Influenced By* / *Influenced* on painting detail | No edges for that work | `npm run update-influences`; extend `art-influences-data.js` |
| Empty *Influenced By* / *Influenced* on painting detail | No edges for that work | `npm run migrate:influence-sources` then `npm run update-influences`; extend `art-influences-data.js` or run `--discover` |
| No golden lamps above frames in 3D hall | Stale API process or no influence edges | Restart server after API changes; run `npm run update-influences` |
| Default Vite page instead of gallery | `client/dist` missing or stale | `cd client && npm run build` |
| Permission denied creating tables | `gallery` user lacks CREATE | Run admin grants, then migrate |