Add catalog search on timeline header and fix Back to Timeline navigation.

Public GET /api/search over artists, movements, and paintings with a debounced header bar on the timeline; Back to Timeline resets zoom and gallery session.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-07-15 10:34:29 +03:00
co-authored by Cursor
parent 50fc253ab2
commit acc4a91a08
17 changed files with 827 additions and 12 deletions
+71
View File
@@ -171,6 +171,76 @@ Movements are filtered to those with at least one artist active in the requested
---
## `GET /api/search`
Public catalog search over **artists**, **paintings**, and **art movements**. Used by the timeline header search bar (`CatalogSearchBar.tsx`).
**Query**
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `q` | string | — | Search text (min **2** characters after trim; shorter returns `{ q, results: [] }`) |
| `limit` | int | 20 | Max results total (capped at **50**) |
| `types` | string | all | Optional comma list: `artist`, `painting`, `movement` |
**Matching (case-insensitive `ILIKE`):**
| Entity | Fields |
|--------|--------|
| Artist | `name`, `wikipedia_title`, movement name |
| Movement | movement `name`, era name |
| Painting | `title`, `wikipedia_title`, `year` (as text), artist name, movement name |
Prefix matches on primary labels (`name` / `title`) rank before substring matches.
**Response**
```json
{
"q": "monet",
"results": [
{
"type": "artist",
"id": 19,
"name": "Claude Monet",
"birth_year": 1840,
"death_year": 1926,
"movement_name": "Impressionism",
"portrait_path": "portraits/Claude_Monet.jpg",
"portrait_thumb_path": "portraits/thumbs/Claude_Monet_thumb.jpg"
},
{
"type": "movement",
"id": 12,
"name": "Impressionism",
"color": "#87CEEB",
"start_year": 1860,
"end_year": 1890,
"era_name": "Modern"
},
{
"type": "painting",
"id": 241,
"title": "Water Lilies",
"year": 1919,
"artist_id": 19,
"artist_name": "Claude Monet",
"movement_name": "Impressionism",
"thumbnail_path": "paintings/thumbs/Claude_Monet_Water_Lilies_thumb.jpg",
"image_path": "paintings/Claude_Monet_Water_Lilies.jpg"
}
]
}
```
**Indexes:** applied by `npm run dev:migrate` (`db/migrate-search.sql`) or standalone `npm run dev:migrate:search`.
**Client:** `api.search(q, { limit?, types? })`.
**Navigation from search:** choosing a **painting** opens detail with `returnTo: timeline`; the client shows **← Back to Timeline** and calls `goToTimelineHome()` (clears gallery session, resets timeline zoom). Choosing an **artist** or **movement** uses the normal gallery entry handlers.
---
## `GET /api/artists`
Artists for timeline portraits and the movement flow diagram.
@@ -720,6 +790,7 @@ The React client wraps these endpoints in `client/src/api/client.ts`. All reques
| `logoutCurator()` | `POST /api/auth/logout` |
| `api.getBounds()` | `GET /api/bounds` |
| `api.getTimeline(start, end)` | `GET /api/timeline` |
| `api.search(q, options?)` | `GET /api/search` |
| `api.getArtists(...)` | `GET /api/artists` |
| `api.getTimelineArtists()` | `GET /api/artists?timeline=1` |
| `api.getArtist(id)` | `GET /api/artists/:id` |
+2
View File
@@ -5,4 +5,6 @@ this file contains draft for future releases and features
3. tool to monitor/manage (plan actions) of curator actions, markers to check painting/text ?
4. tool to sync prod /env resources (both ways), db structure, db data, images, users etc
5. curator_audit_log should contain log of actions like fixit, checked, upload etc with details for which entity it was made and details what was the action and outcome
6. ~~create search by entity (painting, artist, movement)~~ — done: timeline header + `GET /api/search`
7. create guided tours (with text/extra infor, set of entities)
+43 -6
View File
@@ -12,6 +12,8 @@ The app is organised as a **drill-down hierarchy**:
4. **Painting detail** — full work in the centre, *Influenced By* on the left, *Influenced* on the right (paintings, artists, or movements), optional **art-history notes** with image markers, 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`). With debug mode on, the same image-audit panel as painting detail (portrait search, **Checked** / **Fix it** / **More** / **Clear** / **Upload**).
**Catalog search** — on the timeline home page, the header search bar (`CatalogSearchBar.tsx`) finds artists, paintings, and movements by name and metadata (year, movement, Wikipedia title). Type at least **2 characters** (300 ms debounce); results group into **Artists**, **Movements**, and **Paintings** with thumbnails. Keyboard: `↑`/`↓` to move, `Enter` to open, `Escape` to close. Choosing a result opens the artist gallery, movement gallery, or painting detail. Paintings opened from search show **← Back to Timeline** and return to the home timeline (full year range), not the previous view.
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).
## Stack
@@ -45,6 +47,7 @@ Gallery/
│ │ ├── components/Timeline.tsx # Era bar, year ticks, event markers
│ │ ├── components/TimelineEventGuides.tsx # Event vertical guides into movement flow
│ │ ├── components/MovementBands.tsx # Movement flow (SVG streams + branches)
│ │ ├── components/CatalogSearchBar.tsx # Timeline header catalog search
│ │ ├── components/PaintingAnnotations.tsx # Art-history notes on painting detail
│ │ ├── pages/CheckupPage.tsx # Image audit table
│ │ ├── data/historical-events.ts # Timeline event markers (UI)
@@ -117,9 +120,13 @@ Use for fast frontend iteration without Keenetic. Legacy nginx config in [`deplo
```mermaid
flowchart TD
A[Home — timeline + movement flow] -->|scroll / drag / zoom| A
A -->|catalog search| S[Search results dropdown]
S -->|artist| C
S -->|movement| G
S -->|painting| D
A -->|click movement name| G[Movement gallery — 3D wings]
G -->|click painting| D
D -->|Back| G
D -->|Back to Gallery| G
G -->|back door / Wings / Exit| H[Wing navigator]
H -->|pick wing| G
H -->|Exit to Timeline| A
@@ -131,16 +138,28 @@ flowchart TD
D -->|prev / next| D
D -->|click centre image| F[Fullscreen lightbox]
F -->|close| D
D -->|Back| C
D -->|Back to Gallery| C
C -->|exit doorway / E key| E[Path picker]
E -->|predecessors| C
E -->|successors| C
D -->|influence thumbnail| D
D -->|artist link| B
B -->|Back| A
C -->|Back| A
B -->|Back| C
C -->|Back to Timeline| A
D -->|Back to Timeline when opened from search| A
```
### Back navigation
| Control | Behaviour |
|---------|-----------|
| **← Back to Timeline** (3D gallery header, movement **Exit to Timeline**) | Always returns to the **home timeline**: unmounts the hall, clears the gallery session, resets timeline zoom to the full catalog year range |
| **← Back to Gallery** (painting detail from a hall) | Returns to the **same hall session** — camera position and wing are preserved |
| **← Back to Timeline** (painting detail opened from catalog search) | Returns to the home timeline (same as gallery **Back to Timeline**) |
| **← Back** (artist bio) | Returns to wherever you opened bio from (usually the artist hall) |
Implementation: `goToTimelineHome()` in `HomePage.tsx` — do not use the browser **Back** button; it is not wired to app navigation.
## Timeline and movement flow
The home page shows two linked views over the **same year window** (`viewStart` / `viewEnd` in `HomePage.tsx`):
@@ -152,6 +171,22 @@ The home page shows two linked views over the **same year window** (`viewStart`
Both views share zoom/pan behaviour via `client/src/utils/timelineView.ts` (`zoomTimelineView`, `panTimelineView`, `chooseTimelineTickInterval`, `createViewChangeScheduler`). The home page uses a **fixed viewport** (`100vh`): timeline + movement flow sit in a shared `home-timeline-stack` so event guide lines can extend from the era bar down through the movement canvas. The movement flow compresses vertically so all movements in the visible year range fit without page scrolling.
### Catalog search (timeline header)
`CatalogSearchBar.tsx` calls `GET /api/search?q=…` (public, no login). The dropdown is stacked above the timeline (`z-index` on `.site-header`) so results are not hidden by movement bands.
| UX | Detail |
|----|--------|
| Minimum query | 2 characters after trim |
| Debounce | 300 ms |
| Result groups | Artists, Movements, Paintings (with thumb or movement colour swatch) |
| Open artist | Preload images → artist 3D hall |
| Open movement | Movement gallery (wing 1) |
| Open painting | Painting detail with `returnTo: timeline`**Back to Timeline** |
| Keyboard | `↑`/`↓` highlight, `Enter` open, `Escape` close |
Run `npm run dev:migrate:search` once on existing databases before first use, or rely on `npm run dev:migrate` (includes `migrate-search.sql`). See [API.md — GET /api/search](API.md#get-apsearch).
### Timeline data loading
On first visit, `HomePage.tsx` fetches the full catalog once:
@@ -299,7 +334,7 @@ Enter from the home page by clicking a **movement name** on the movement flow (`
| Front wall | Open **“Next wing →”** archway when a later wing exists; walk through or press `E` when near |
| Influence lamps | Same golden lamps as artist halls when `has_influence_links` is true |
| Missing images | Draped canvas cover in frame |
| Detail return | Hall stays mounted; camera preserved on **Back to Timeline** / **Back to Gallery** |
| Detail return | **Back to Gallery** from painting detail returns to the same wing with camera preserved; **Back to Timeline** exits the hall entirely |
**Controls (movement gallery):**
@@ -337,7 +372,8 @@ Opened from the 3D hall (artist or movement wing — click a frame) or from infl
| 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** / **← Back to Timeline** | Return to the hall or movement wing you entered from — **3D camera position is preserved** |
| **← Back to Gallery** | Return to the hall or movement wing you entered from — **3D camera position is preserved** |
| **← Back to Timeline** | Return to the home timeline (from search result, or from the 3D gallery header / movement **Exit to Timeline**) — hall unmounts, timeline zoom resets |
| **About {artist}** | Open artist biography |
**Navigation rules:**
@@ -388,6 +424,7 @@ Curator-only workflow for reviewing and fixing local image files — not part of
| Feature | Where | Purpose |
|---------|--------|---------|
| **Catalog search** | Timeline header (all visitors) | Find artists, paintings, movements; `GET /api/search`; navigate to gallery or detail |
| **Curator login** | Home header (guests) | Username + password modal; unlocks debug tools |
| **Debug mode** | Home header toggle (curators only) | Persists in `localStorage`; enables debug panel on painting detail and artist bio |
| **Show more** | Home header checkbox (curators, when debug on) | Auto-opens the **More** modal on each painting / bio page load |
+5
View File
@@ -88,6 +88,7 @@ npm run dev:expand-catalog # famous works for artists below MIN_PAIN
npm run dev:update-influences # painting influence graph for detail view + hall exits
npm run dev:migrate:checkup-flags # optional: review/fixed flags for Checkup page (paintings)
npm run dev:migrate:artist-checkup-flags # optional: same flags for artist portraits (bio debug)
npm run dev:migrate:search # optional on very old DBs — also applied by dev:migrate / prod Step 4
npm run dev:migrate:painting-annotations # optional: art-history notes table
npm run dev:update-painting-annotations # optional: load curated notes (+ --wikipedia for Wikipedia intros)
npm run dev:fetch-images -- --limit=50 # random sample; 10s max per painting (default)
@@ -177,6 +178,7 @@ Node on the dev PC at `:3520` with nginx → Vite `:5173` is superseded by TrueN
| `npm run dev:migrate:influence-sources` | `scripts/migrate-influence-sources.js` | Create `painting_influence_sources` + backfill legacy edges |
| `npm run dev:migrate:checkup-flags` | `scripts/migrate-checkup-flags.js` | Add `checkup_checked` / `checkup_fixed` on `paintings` |
| `npm run dev:migrate:artist-checkup-flags` | `scripts/migrate-artist-checkup-flags.js` | Add `checkup_checked` / `checkup_fixed` on `artists` (bio debug) |
| `npm run dev:migrate:search` | `scripts/migrate-search.js` | Same indexes as `migrate-search.sql` (also run by `dev:migrate`) |
| `npm run dev:migrate:painting-annotations` | `scripts/migrate-painting-annotations.js` | Create `painting_annotations` table |
| `npm run dev:migrate:artist-palette` | `scripts/migrate-artist-palette.js` | Add `palette_metadata` JSONB on `artists` |
| `npm run dev:import-painter-palette` | `scripts/import-painter-palette.js` | Enrich artists + influence links from `Inputs/PainterPalette.csv` |
@@ -263,6 +265,9 @@ After clone: copy `.env.example` → `.env`, install dependencies, run [one-time
| No art-history notes on painting detail | Annotations not migrated or loaded | `npm run dev:migrate:painting-annotations` then `npm run dev:update-painting-annotations` |
| **Fix it** fails with `read ECONNRESET` | Remote host dropped connection | Restart server; client sends `searchUrl` / `source`; retry or use Commons URL in overrides |
| Fixed image not shown in 3D gallery | Stale gallery session or cached texture | Rebuild client; fix/upload updates session + `?v=` from `image_cache_key` — use **Back to Gallery** (not browser back) |
| **Back to Timeline** returns to gallery / previous wing | Stale client build | Pull latest client — `goToTimelineHome()` unmounts the hall and resets timeline zoom |
| Catalog search dropdown hidden under timeline | Stale client CSS | Rebuild client — `.site-header` uses `z-index: 110` above the sticky timeline bar |
| Catalog search returns empty / 500 | Search indexes missing | Run `npm run dev:migrate` (includes `migrate-search.sql`) or `npm run dev:migrate:search`; restart API |
| Frame still black after **Checked** | Gallery session not synced | Re-enter hall or toggle debug **Checked** from detail with gallery open behind overlay |
| Duplicate works in gallery / timeline | Double import or variant Wikipedia titles | `npm run dev:find-duplicates`; merge or delete spare rows manually |
| **Failed to load movement gallery** / `Cannot GET /api/movements/:id/gallery` | Stale server process missing route | Restart `npm run dev:web` or `npm run dev:server` after pulling API changes |