Files
Art-gallery/Documentation/ui-interaction-and-component-standards.md

528 lines
31 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UI Interaction and Component Standards
**Subject:** Virtual Art Gallery — screen and interaction guidance
**Applies to:** Public visitor UI, 3D halls, curator tools
**Companion rule:** `.cursor/rules/ui-interaction-standards.mdc`
---
## 0. Introduction
The Gallery is a single React SPA (`HomePage.tsx` view union — no URL router except `?layout=`). Public visitors browse a museum-dark timeline and 3D halls; curators use the same shell for catalog tools (Checkup, Translations, Influences, Tours, Users, Activity). Work lands in the same header, palette, and back-stack, so a new screen that invents its own chrome, confirmations, or loading pattern fragments the experience.
This document is the shared contract for **how the UI behaves**. Architecture, APIs, and 3D hall construction live in [basics.md](basics.md), [API.md](API.md), and [data-and-images.md](data-and-images.md). Tree geometry lives in [movement-tree.md](movement-tree.md). Locale strings live in [i18n-russian.md](i18n-russian.md).
### 0.1 Purpose and audience
- **Purpose:** Define common UI interaction, navigation, and component standards for the Gallery client.
- **Primary audience:** Developers (and agents) adding or changing `client/src` UI; anyone writing curator-tool screens.
- **Secondary audience:** QA, product, copy/i18n.
### 0.2 Scope
- **In scope:**
- Navigation and back-stack behaviour
- Timeline / search / 3D / detail / curator-tool interaction patterns
- Loading, empty, error, and confirmation behaviour
- Permission-based UI (`can()` / RBAC)
- Shared component and visual-language rules
- **Out of scope:**
- REST contracts, retries, and image pipeline internals
- Three.js hall architecture, textures, and lighting (see [basics.md](basics.md))
- Pixel-perfect branding kit (no separate design-system package; follow existing CSS)
### 0.3 Requirement levels
- **MUST:** Mandatory for new work and for fixes that touch the same screen.
- **SHOULD:** Recommended; deviate only with a short note in the PR or screen section.
- **MAY:** Optional pattern when the screen specification calls for it.
### 0.4 Surface map
| Surface | Typical components | Visitors | Curators |
|---------|--------------------|----------|----------|
| Timeline home | `Timeline`, `VerticalTimeline`, `MovementBands`, `VerticalMovementBands`, `MovementTree`, `CatalogSearchBar` | yes | yes |
| 3D hall | `VirtualGallery`, wing navigator, exit overlays | yes | yes |
| Painting / bio | `PaintingDetail`, `PaintingLightbox`, `ArtistBio`, `PaintingAnnotations` | yes | + debug panel when `can('images')` |
| Overlays | `CuratorLoginModal`, `ArtistFilterModal`, `ToursPopup`, `DebugSearchResultsModal` | some | all |
| Curator tools | `CheckupPage`, `TranslationsPage`, `InfluencesPage`, `ToursPage`, `UsersPage`, `AuditPage` | no | permission-gated |
---
## 1. Design Goals and Principles
### 1.1 Problem statement
- Statement: Visitors and curators share one shell. Inconsistent back labels, ad-hoc modals, English-only captions, and one-off loading/error treatment make the museum feel like several apps glued together.
- Scope: All `client/src` screens and overlays.
- Rationale: The product is a gallery, not an admin console with a visitor skin. Predictable chrome is part of the exhibit.
- Verification: Compare a new screens header, back control, loading marker, and locale keys against this document.
### 1.2 Functional design principles
- Principle 1: One museum, two roles
- Statement: Public browse chrome MUST stay museum-dark (navy / gold / Georgia). Curator tools MAY be denser but MUST reuse the same header back pattern, gold accent, and permission hiding — they MUST NOT look like a separate product.
- Rationale: Curators enter from the same timeline; a visual cliff breaks trust.
- Verification: Side-by-side with timeline header and Checkup / Tours editor.
- Principle 2: Predictable drill-down and return
- Statement: Navigation MUST follow Timeline → (movement picker) → hall → painting/bio, with an explicit in-app Back that restores the intended session — not the browser history stack.
- Rationale: `HomePage` owns view state; the browser Back button is not wired.
- Verification: Walk the [basics.md navigation flow](basics.md#user-navigation-flow) and the Back table in §2.4.
- Principle 3: Clarity of actions and feedback
- Statement: Every user-initiated load, save, delete, or failed request MUST show a loading, success, or error state the user can see without opening the console.
- Rationale: 3D and image work is slow; silent failure looks like a broken hall.
- Verification: Trigger catalog load, hall open, form save, and a failed API call.
- Principle 4: Minimize cognitive load
- Statement: Timeline charts MUST keep zoom/pan/click hints visible. Curator tables MUST put filter/search above the grid. Destructive actions MUST be confirmed.
- Rationale: Dense history data and catalog tables are easy to mis-click.
- Verification: Hint captions present; filters above tables; delete paths show a confirm.
- Principle 5: Locale and permission are first-class
- Statement: New visitor-facing copy MUST go through `react-i18next` (`locales/{en,ru}`). Actions the user cannot perform MUST be hidden, not disabled-without-explanation.
- Rationale: EN/RU is a product requirement; exposing forbidden tools invites errors.
- Verification: Toggle EN|RU; log in as a curator without the relevant `can()` and confirm the control is absent.
---
## 2. Navigation Principles
### 2.1 Application chrome (no left module rail)
The Gallery has **no persistent left navigation menu**. Primary wayfinding is the **timeline header** on home, and an explicit **Back** control on every nested view.
- Statement: The timeline home MUST keep layout switch, catalog search, locale switcher, and role-appropriate tools in the site header.
- Scope: `HomePage` when `view` is `timeline` / `timeline-vertical` / `timeline-tree`.
- Rationale: Visitors need search and layout without hunting; curators need tools without leaving the museum frame.
- Verification: Header remains usable at 100vh; search dropdown stacks above movement bands (`z-index` on `.site-header`).
- Statement: Layout switch links MUST stay in the top-left (`.site-layout-switch`). The Tree of Art control SHOULD use `.site-layout-link-feature`.
- Scope: Timeline home.
- Rationale: Three layouts must stay discoverable; Tree is the featured alternative start page.
- Verification: Classic / Vertical / Tree links match [basics.md](basics.md#timeline-and-movement-flow).
- Statement: Nested views (hall, painting, bio, curator pages) MUST NOT reintroduce a second global nav. They MUST show a single primary Back control in the page header.
- Scope: All non-home views.
- Rationale: Avoid competing menus; the drill-down is the nav.
- Verification: No duplicate “home” plus “modules” rails on curator pages.
### 2.2 Page hierarchy
- Statement: Screens MUST stay within this hierarchy (max four levels):
1. Timeline home (classic / vertical / tree)
2. Overlay or picker (artist filter, tours popup, login) **or** curator tool page
3. 3D hall (artist / movement / tour)
4. Painting detail or artist bio (optional lightbox on top of detail)
- Scope: All visitor and curator flows.
- Rationale: Matches the existing drill-down; deeper stacks become unrecoverable without a router.
- Verification: New views are added to the `View` union in `HomePage.tsx` with a defined parent and Back handler.
- Statement: Curator tools MUST open as siblings of the timeline (replace the home canvas), not as a fifth level under a hall.
- Scope: Checkup, Translations, Influences, Tours editor, Users, Activity.
- Rationale: Tools operate on the catalog, not on a hall session.
- Verification: Opening Checkup from a hall is not required; from timeline header, Back returns to timeline home.
### 2.3 Location awareness (no breadcrumbs)
- Statement: The app MUST NOT add a breadcrumb trail unless a future router lands. Until then, the Back label MUST name the destination (`← Back to Timeline`, `← Back to Gallery`, `← Back`).
- Scope: All nested views.
- Rationale: There is no URL path to reflect; a fake breadcrumb would lie.
- Verification: Labels match §2.4; they are i18n keys (`backToTimeline`, `backToGallery`, …).
- Statement: Timeline layout MUST be deep-linkable via `?layout=classic|vertical|tree` (omit param for classic). Other views MUST NOT pretend to be bookmarkable until a router exists.
- Scope: Timeline home.
- Rationale: Layout is the one shareable start-page choice; halls and tools are session state.
- Verification: Load `?layout=tree`, switch layouts, confirm `history.replaceState` updates the query.
### 2.4 Back navigation
| Control | MUST return to |
|---------|----------------|
| **← Back to Timeline** (hall header, movement Exit to Timeline, painting opened from search) | Home timeline via `goToTimelineHome()` — unmount hall, clear session, reset year window to full catalog bounds |
| **← Back to Gallery** (painting from a hall) | Same hall session (camera / wing preserved) |
| **← Back** (artist bio) | `returnTo` view (usually the hall that opened bio) |
| Curator page Back | Timeline home |
- Statement: Back MUST be an in-app control. The browser Back button MUST NOT be relied on (it is not wired to `View` state).
- Scope: All nested views.
- Rationale: `setView` is the router.
- Verification: From painting detail, in-app Back restores the hall; browser Back does not need to.
- Statement: `goToTimelineHome()` MUST be the single implementation for “leave everything and show the timeline.” New exits MUST call it rather than duplicating reset logic.
- Scope: Halls, search-opened paintings, curator Back.
- Rationale: Year-range reset and session clear must stay consistent.
- Verification: After Exit to Timeline, `viewStart`/`viewEnd` equal catalog bounds.
### 2.5 State preservation
- Statement: Timeline pan/zoom (`viewStart` / `viewEnd`) MUST persist while the user stays on a timeline layout. Switching classic ↔ vertical ↔ tree MUST keep the same year window.
- Scope: Timeline home.
- Rationale: Layout is a lens, not a new dataset.
- Verification: Zoom, switch to Tree, confirm the year rail range is unchanged.
- Statement: Returning to timeline via `goToTimelineHome()` MUST reset the year window to full catalog bounds.
- Scope: Hall / search / curator exits that call `goToTimelineHome()`.
- Rationale: Documented in [basics.md](basics.md#back-navigation); visitors expect a fresh overview, not a leftover zoom.
- Verification: Zoom in, enter a hall, Back to Timeline → full span.
- Statement: Hall camera and wing MUST be preserved across **Back to Gallery** from painting detail. They MUST be discarded on **Back to Timeline**.
- Scope: Artist, movement, and tour halls.
- Rationale: Inspecting a painting is a detour; leaving the museum is not.
- Verification: Move in the hall, open a painting, Back to Gallery → same viewpoint.
- Statement: Catalog search input MAY clear when the dropdown closes. It MUST NOT change timeline zoom by itself.
- Scope: `CatalogSearchBar`.
- Rationale: Search is a jump, not a filter on the chart.
- Verification: Type a query, Escape; year window unchanged.
---
## 3. Common UI Interaction Patterns
### 3.1 Catalog search (visitor)
- Statement: Timeline search MUST live in the header, require **2** trimmed characters, debounce **300 ms**, and group results into Artists, Movements, Paintings.
- Scope: `CatalogSearchBar.tsx`.
- Rationale: Documented product behaviour; keeps `/api/search` load reasonable.
- Verification: 1 character shows no fetch; 2+ after debounce shows groups.
- Statement: Keyboard MUST support `↑`/`↓` highlight, `Enter` to open, `Escape` to close.
- Scope: Catalog search dropdown.
- Rationale: Timeline is pointer-heavy; search should still be keyboardable.
- Verification: Keyboard-only open of an artist, movement, and painting.
- Statement: Opening a painting from search MUST set `returnTo` timeline so Back is **← Back to Timeline**, not Gallery.
- Scope: Search → painting detail.
- Rationale: There is no hall session.
- Verification: Search a title, open, Back → home timeline.
### 3.2 Timeline charts (zoom, pan, click)
- Statement: Scroll MUST zoom, drag MUST pan, click on a stream/limb/label MUST open the movement (artist filter → hall). All three layouts MUST use `zoomTimelineView` / `panTimelineView`.
- Scope: `MovementBands`, `VerticalMovementBands`, `MovementTree`, era rails.
- Rationale: Shared year window; one mental model.
- Verification: Same wheel/drag behaviour on classic, vertical, and tree.
- Statement: Each chart MUST show a localised hint caption (`captionClassicTimeline`, `captionClassicFlow`, `captionVerticalTimeline`, `captionVerticalFlow`, `captionTreeFlow`).
- Scope: Timeline home.
- Rationale: First-time visitors cannot discover zoom/pan otherwise.
- Verification: EN and RU captions change with `LocaleSwitcher`.
- Statement: Timeline layout shifts (lane packing, tree fit scale) SHOULD animate rather than snap.
- Scope: Movement charts.
- Rationale: Unexplained jumps look like bugs.
- Verification: Zoom/pan does not teleport streams.
### 3.3 Filters and search (curator tables)
- Statement: Filter/search controls MUST sit in a toolbar **above** the table, not in a column header hack or a page footer.
- Scope: Checkup, Translations, Influences, Tours editor, Users, Activity.
- Rationale: Matches Checkup (`checkup-toolbar`) and keeps the grid scannable.
- Verification: Filters remain visible while the table scrolls.
- Statement: Simple text filters MAY apply as the user types. Expensive operations (image search, import, refetch) MUST require an explicit button (e.g. Checkup **Search visible**).
- Scope: Curator list screens.
- Rationale: Checkup search is rate-limited and slow; typing must not fire it.
- Verification: Typing in Checkup filter does not start image search.
- Statement: When a filter hides rows, the toolbar SHOULD show how many rows are visible (e.g. `N shown`).
- Scope: Filtered tables.
- Rationale: Empty-looking tables need an explanation.
- Verification: Filter to zero rows → empty state plus count.
### 3.4 Date selection
- Statement: Year fields in the catalog and timeline MUST use numeric years (negative = BCE). They MUST NOT switch to locale-specific calendar widgets for historical BCE dates.
- Scope: Timeline bounds, artist lifespan, painting years, curator year filters.
- Rationale: The catalog spans 800 to the present; HTML date inputs cannot represent BCE.
- Verification: Ancient era still filters correctly.
- Statement: If a future screen needs a civil date (e.g. audit log day), it SHOULD use ISO `YYYY-MM-DD` and validate start ≤ end for ranges.
- Scope: Activity / audit and any new timestamp filters.
- Rationale: Consistent with API timestamps; avoids DD/MM ambiguity.
- Verification: Invalid range shows a field-level message.
### 3.5 Tables and lists (curator)
- Statement: Structured curator datasets MUST use a labeled HTML table (or existing page table classes), one logical record per row.
- Scope: Checkup, Users, Influences worklists, Tours list, Translations worklist, Activity.
- Rationale: Comparison and row actions need columns, not cards.
- Verification: Column headers present; row click/action affects one record.
- Statement: Visitor-facing catalog MUST NOT be presented as a spreadsheet. Timeline streams, tree limbs, and 3D hangs are the list metaphor.
- Scope: Public home and halls.
- Rationale: The product is a gallery, not a DAM table.
- Verification: No “all paintings” data grid on the public home.
- Statement: Tables MAY omit pagination while the dataset is curator-sized and client-filtered. If a list grows past comfortable scrolling, it SHOULD paginate or virtualise rather than rendering thousands of DOM rows.
- Scope: Curator tools.
- Rationale: Checkup is already filter-then-scroll; unbounded paint is a future foot-gun.
- Verification: New tools with large lists have a documented paging or virtualisation plan.
- Statement: Row actions MUST sit in a dedicated column or overflow control, not as random icons in every cell.
- Scope: Interactive curator tables.
- Rationale: Scanability.
- Verification: Action column or consistent button set per row.
### 3.6 Multi-row and per-record actions
- Statement: Bulk actions MUST use a leading checkbox column, Select All for **visible** rows only, and a confirmation that includes the affected count for destructive work.
- Scope: Any new bulk-enabled table. (Today: ArtistFilterModal multi-select is a picker, not a bulk delete.)
- Rationale: Same as the Logistics template; prevent silent mass edits.
- Verification: Select All does not imply “all matching in the database” unless explicitly labelled.
- Statement: Artist filter before a movement hall MUST be a modal checklist with explicit proceed/cancel, not a bulk-edit of the catalog.
- Scope: `ArtistFilterModal`.
- Rationale: It only chooses who appears in the hall.
- Verification: Cancel leaves the user on the timeline; proceed opens the hall.
- Statement: Primary row/object action SHOULD be the name/title (open painting, open user, open tour). Secondary actions SHOULD stay in the rows action controls.
- Scope: Curator tables and search results.
- Rationale: Matches search-result click-to-open.
- Verification: Clicking a Checkup title opens the painting when that handler exists.
- Statement: Unavailable-by-permission actions MUST be hidden. Unavailable-by-record-state SHOULD be disabled with a `title`/tooltip explaining why.
- Scope: All tools.
- Rationale: RBAC vs workflow are different signals.
- Verification: Non-admin does not see Users; a disabled Fix button states why.
### 3.7 Forms
- Statement: Short auth and picker flows MUST use a modal. Multi-section catalog editors (Users create/edit, Tours editor, Influences wizard, Translations worklist) MUST be full-page (or the existing page layout), not nested modals.
- Scope: All forms.
- Rationale: Halls and timeline need to stay the “place”; heavy edit needs space.
- Verification: Login is modal; Users is a page.
- Statement: Forms SHOULD be a single column. Related fields MAY group under a heading when there are more than five inputs.
- Scope: Curator forms.
- Rationale: Scanning beats dense multi-column on museum-width pages.
- Verification: Users create form remains vertically grouped.
- Statement: Required fields MUST use the native `required` attribute and/or a visible marker; validation errors MUST appear next to the field or as a form-level error the submit control does not obscure.
- Scope: Login, Users, Tours, Influences, Translations.
- Rationale: Silent submit-disable is not enough.
- Verification: Submit empty login → field or form error, not a blank modal.
- Statement: After successful save, the system MUST show an inline success message (or equivalent) and keep the user on the tool unless the spec says to return to timeline.
- Scope: Curator mutations.
- Rationale: Users page already uses `message` / `error` banners.
- Verification: Save permissions → success text; failed save → error text.
- Statement: Unsaved-change guards SHOULD be added when a form is long enough that accidental Back would lose work (Tours editor, Translations). Login and tiny pickers MAY skip this.
- Scope: Heavy editors.
- Rationale: `window.confirm` on delete already exists; abandon-edit is the remaining hole.
- Verification: Dirty Tours editor + Back prompts or discards explicitly.
### 3.8 Edit interaction pattern selection
| Pattern | Use when |
|---------|----------|
| **Modal** | Login, artist filter, tours list popup, debug image picker, lightbox, hall exit/wing overlays |
| **Inline** | Checkup flags, debug **Checked** / **Fix it** on painting detail — small, reversible |
| **Full page** | Curator tools, painting detail, artist bio, 3D hall |
| **Drawer** | MUST NOT be introduced unless a spec adds a shared drawer component |
- Statement: Nested modals MUST NOT be used (no modal opened from another modal). The debug “More” picker MAY stack on painting detail because detail is a full page, not a modal.
- Scope: All overlays.
- Rationale: Focus traps and Back labels break.
- Verification: Login does not open another dialog.
### 3.9 Modals and overlays
- Statement: Modals MUST use `role="dialog"` and `aria-modal="true"`, a visible close/cancel, and **Escape** to dismiss unless a submit is in flight.
- Scope: `CuratorLoginModal`, `ArtistFilterModal`, `ToursPopup`, `DebugSearchResultsModal`, `PaintingLightbox`, hall exit overlays.
- Rationale: Accessibility and parity with search.
- Verification: Esc closes lightbox and debug picker; backdrop click matches existing login behaviour.
- Statement: Backdrop click MAY close pickers and login. It MUST NOT close a modal that is applying a destructive or long-running action.
- Scope: Overlays.
- Rationale: Accidental dismiss during Fix/upload is costly.
- Verification: Click outside login closes; do not dismiss mid-upload.
### 3.10 Loading, empty, and error states
- Statement: Catalog, portrait, and hall loads MUST use `GalleryLoadingMarker` (overlay or banner), not an ad-hoc spinner per screen unless the marker cannot cover the region.
- Scope: Home, halls, painting/bio image work.
- Rationale: One recognisable “the museum is fetching” treatment.
- Verification: First visit shows “Loading art history…”; hall open uses the same marker family.
- Statement: Loading SHOULD be scoped to the affected region. Full-viewport overlay MUST be used only when the user cannot usefully interact (first catalog load, hall WebGL init).
- Scope: All loads.
- Rationale: Portrait banner vs full-page overlay already follows this.
- Verification: Timeline remains visible while “Loading portraits…” banners.
- Statement: Empty datasets MUST explain themselves (e.g. vertical flow: no movements in range; search: no matches; Checkup: no rows for filter).
- Scope: Charts, search, tables.
- Rationale: Blank gold-on-navy reads as a crash.
- Verification: Zoom to a year with no movements; search a nonsense string.
- Statement: Failed loads MUST set a visible error string (`error-banner`, form error, or page error) — never `console.error` alone.
- Scope: All data-fetching views.
- Rationale: Visitors have no console.
- Verification: Stop the API and confirm home shows a load failure message.
### 3.11 3D hall interaction
- Statement: Halls MUST keep **← Back to Timeline**, pointer-lock / click-to-move as already implemented, and **E** (or documented key) for exit/wing navigation. New hall UI MUST not steal those keys without updating this section.
- Scope: `VirtualGallery`.
- Rationale: Muscle memory across artist, movement, and tour halls.
- Verification: Same Back label and exit overlay pattern in all three hall kinds.
- Statement: Clicking a framed painting MUST open painting detail with `returnTo` the current hall. Missing images MUST show the draped-canvas placeholder, not a broken `<img>`.
- Scope: Halls.
- Rationale: Documented in [basics.md](basics.md).
- Verification: Work without a file still shows a frame cover.
---
## 4. Behavioral Standards (Screen UX)
### 4.1 Permission-based rendering (RBAC)
- Statement: Header tools and debug controls MUST render only when `can('<permission>')` (and login for curator). Anonymous visitors MUST see browse + Tours popup + locale, not Checkup/Users/etc.
- Scope: `HomePage` header, painting/bio debug panel.
- Rationale: Security UX: hide, dont tease.
- Verification: Logged-out home; curator without `users` cannot open Users.
- Statement: Route-like views that require a role MUST show the existing “Curator access required” panel with login and back-to-gallery actions — they MUST NOT render an empty privileged page.
- Scope: Checkup, Translations, Influences, Tours editor, Users, Activity.
- Rationale: Deep view state can still be set; the gate must hold.
- Verification: Set view to Users while logged out → curator required copy.
- Statement: The same permission MUST hide the same action on every surface (header, painting debug, API). Do not leave a visible button that 403s.
- Scope: All mutations.
- Rationale: Predictable roles.
- Verification: `can('images')` off → no debug panel and no Fix buttons.
### 4.2 Notifications and user feedback
- Statement: Curator mutations MUST show success or error text in the pages existing banner/message area. The app has no global toast system; new screens MUST NOT invent a third notification widget without replacing this standard.
- Scope: Curator pages.
- Rationale: Users/Influences already use inline `message` / `error`.
- Verification: One visual treatment per page, consistent placement under the header.
- Statement: Visitor-facing destructive actions (painting **Remove entry** in debug) MUST confirm and then show failure inline if the API rejects.
- Scope: Debug painting tools.
- Rationale: Catalog deletes are irreversible.
- Verification: Cancel confirm → no delete.
- Statement: Copy MUST be concise and, for errors, actionable (“Is the server running?”, “Sign in as a curator…”).
- Scope: All user-visible strings.
- Rationale: Support load.
- Verification: Home catalog failure string remains understandable.
### 4.3 Confirmation and cancellation
- Statement: Destructive curator actions (delete influence, delete tour, remove painting, deactivate user if offered) MUST confirm before the request. `window.confirm` with an i18n string is the current standard (`confirmDelete`); a shared modal MAY replace it later but MUST stay one pattern.
- Scope: Influences, Tours, painting remove, Users.
- Rationale: Accidental clicks on dense tables.
- Verification: Delete tour → confirm; Cancel → no API call.
- Statement: Cancel on a confirm MUST leave filters, selection, and unsaved fields unchanged.
- Scope: All confirms.
- Rationale: Context preservation.
- Verification: Filtered Checkup, cancel a destructive action → filter still applied.
---
## 5. Component Consistency Rules
### 5.1 Shared interaction pattern usage
- Statement: New UI MUST reuse existing components before creating parallels: `GalleryLoadingMarker`, `CatalogSearchBar`, `LocaleSwitcher`, `CuratorLoginModal`, `ArtistFilterModal`, `PaintingLightbox`, `DebugSearchResultsModal`, `DebugUploadButton`.
- Scope: `client/src`.
- Rationale: Duplicate spinners and dialogs already caused drift.
- Verification: PR does not add a second login modal or loading overlay.
- Statement: Movement colours MUST go through `utils/movementColor.ts` (`vividMovementColor`, `shadeMovementColor`). Hex parsing MUST tolerate `#rgb` / `#rrggbb` and keep the first six digits of longer values.
- Scope: Timeline charts and any movement swatch.
- Rationale: Classic, vertical, and tree already share this util.
- Verification: No local `parseHexColor` copies in components.
- Statement: Timeline zoom/pan MUST use `utils/timelineView.ts`. Tree horizontal layout MUST use `utils/movementTree.ts` (view-independent structure).
- Scope: Timeline surfaces.
- Rationale: Layouts share one year window.
- Verification: No one-off wheel handlers that bypass the util.
### 5.2 Component and style selection
- Statement: There is **no Ant Design / MUI**. UI MUST be React + colocated CSS (`ComponentName.tsx` + `ComponentName.css`). Global museum tokens SHOULD reuse existing values:
| Token | Typical value | Use |
|-------|----------------|-----|
| Gold | `#c9a96e` / `rgba(201, 169, 110, …)` | Accents, borders, links |
| Ink / navy | `#0f0f1a`, `#1a1a2e`, `#16213e` | Page background |
| Type | Georgia, serif | Titles, captions, layout links |
| Viewport | `100vh`, `overflow: hidden` on home | No document scroll for the museum shell |
- Scope: All client UI.
- Rationale: The look *is* the design system.
- Verification: New CSS does not introduce a bright Bootstrap theme.
- Statement: Prefer semantic `<button type="button">` for actions and native `<form>` for submits. Do not use `<div onClick>` for primary actions.
- Scope: All interactive chrome.
- Rationale: Keyboard and a11y.
- Verification: Header tools are buttons.
- Statement: Colocate styles; do not add a CSS-in-JS runtime. Shared layout classes live in `HomePage.css` / page CSS, not inline theme objects.
- Scope: Client.
- Rationale: Matches the repo.
- Verification: New component ships a `.css` file or uses an existing one.
### 5.3 i18n
- Statement: New user-visible chrome MUST add keys to `client/src/locales/en/*.json` and `ru/*.json` (namespace that matches the screen: `home`, `common`, `debug`, `users`, …). Hardcoded English is allowed only for curator-only screens that are not yet migrated, and those SHOULD be migrated when the screen is touched.
- Scope: All new copy.
- Rationale: [i18n-russian.md](i18n-russian.md).
- Verification: Locale toggle changes the new string.
- Statement: Catalog entities (names, titles, bios) MUST use API locale resolution, not a second client-side dictionary.
- Scope: Timeline labels, search, halls, detail.
- Rationale: `entity_translations` is canonical for RU catalog text.
- Verification: RU locale shows published translations.
### 5.4 Exception process
- Statement: Deviations (new overlay type, toast system, left nav, router, design library) MUST be documented in this file or in the features `Documentation/*.md` with rationale before they spread to a second screen.
- Scope: Client-wide patterns.
- Rationale: One exception is an experiment; two without a write-up is fragmentation.
- Verification: PR description links the exception note.
- Statement: Known current exceptions (do not cargo-cult; fix when touching the file):
- Browser history is not a router.
- Only `?layout=` is deep-linked.
- Some curator pages (e.g. Checkup) still have English chrome.
- Some modals may still omit Escape (login); new modals MUST include it.
- Destructive confirms use `window.confirm` rather than a shared dialog component.
### 5.5 Versioning of behaviour
- Statement: Behaviour changes to shared patterns (Back, search debounce, layout query, loading marker) MUST update this document in the same change.
- Scope: Shared components and `HomePage` navigation.
- Rationale: Agents and humans use this as the spec.
- Verification: Doc diff accompanies the code diff.
---
## 6. Screen-Level Checklist
Use this before submitting a new view, overlay, or curator tool. Unchecked items need a note.
- [ ] Surface type is identified (timeline / hall / detail / overlay / curator page).
- [ ] Parent view and Back label/destination are defined (`returnTo` or `goToTimelineHome`).
- [ ] Permissions: controls hidden unless `can(…)` / signed in as required.
- [ ] Loading uses `GalleryLoadingMarker` or a justified scoped indicator.
- [ ] Empty and error states are visible copy, not a blank canvas.
- [ ] Destructive actions confirm; cancel leaves state unchanged.
- [ ] Visitor chrome strings are in `locales/{en,ru}`; catalog text uses API locale.
- [ ] No new CSS framework; gold/navy/Georgia; colocated CSS.
- [ ] Search/filter (if any) sits above the list; expensive work is explicit-button.
- [ ] Modals: `role="dialog"`, close control, Escape.
- [ ] Timeline work uses `timelineView` / `movementColor` / `movementTree` as applicable.
- [ ] Hall work preserves camera on Back to Gallery and resets on Back to Timeline.
- [ ] This document updated if a shared pattern changed.