Fix harmonize prod DB targeting and large-hall loading hangs.

Prod env files now win over root .env so harmonize:db can open gallery_prod after gallery_dev; tour_stops gains updated_at; halls no longer block the overlay on every painting texture. Docs cover the fixes and one-artist image pull from prod; Duccio painting assets synced from TrueNAS.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-07-26 01:26:51 +03:00
co-authored by Cursor
parent f6c73c1792
commit 8e444fa461
115 changed files with 124 additions and 37 deletions
+26 -13
View File
@@ -98,6 +98,9 @@ const REVIEWED_MAT_BORDER = FRAME_MAT_BORDER * 2;
const REVIEWED_RAIL = FRAME_RAIL * 2;
const EYE_HEIGHT = 1.65;
const FRAME_GAP = 0.32;
const SHADER_WARM_TIMEOUT_MS = 4000;
/** Per-image fetch/decode deadline so a stuck request cannot hold the counter forever. */
const TEXTURE_LOAD_TIMEOUT_MS = 10000;
const MIN_FRAME_W = 0.45;
const MAX_FRAME_W = 1.05;
const MAX_FRAME_H = 1.35;
@@ -602,9 +605,16 @@ function usePaintingTexture(url: string | null) {
};
textureLoad?.begin();
const loadTimeout = window.setTimeout(() => {
if (settled || disposed) return;
setFailed(true);
finish();
}, TEXTURE_LOAD_TIMEOUT_MS);
loader.load(
url,
(tex) => {
window.clearTimeout(loadTimeout);
if (disposed) {
tex.dispose();
finish();
@@ -620,17 +630,19 @@ function usePaintingTexture(url: string | null) {
loaded = tex;
tex.colorSpace = THREE.SRGBColorSpace;
tex.anisotropy = 4;
// Upload under "Loading paintings…" so first turn into culled walls does not hitch.
try {
gl.initTexture(tex);
} catch {
// Still release the load counter if GPU upload fails.
}
// Release the hall overlay counter before GPU upload — large halls
// (50+ works) otherwise stay on "Loading paintings…" for a long time.
finish();
if (!disposed) setTexture(tex);
try {
if (!disposed) gl.initTexture(tex);
} catch {
// Upload can fail after context loss; texture still usable later.
}
},
undefined,
() => {
window.clearTimeout(loadTimeout);
finish();
if (!disposed) setFailed(true);
}
@@ -638,6 +650,7 @@ function usePaintingTexture(url: string | null) {
return () => {
disposed = true;
window.clearTimeout(loadTimeout);
finish();
loaded?.dispose();
setTexture(null);
@@ -1628,8 +1641,6 @@ function EnvironmentGate({
return <>{children}</>;
}
const SHADER_WARM_TIMEOUT_MS = 4000;
/** Compile all scene materials (incl. culled doors) before dismissing the loading overlay. */
function WarmHallGpu({
enabled,
@@ -1959,6 +1970,7 @@ export default function VirtualGallery(props: Props) {
useEffect(() => {
setEnvReady(false);
setShadersWarmed(false);
setTexturesPending(0);
}, [hallKey, glEpoch]);
useEffect(() => {
@@ -1974,10 +1986,10 @@ export default function VirtualGallery(props: Props) {
// If shader warm-up never settles, dismiss the overlay anyway.
useEffect(() => {
if (shadersWarmed || !canvasReady || !envReady || texturesPending > 0) return;
if (shadersWarmed || !canvasReady || !envReady) return;
const t = window.setTimeout(() => setShadersWarmed(true), SHADER_WARM_TIMEOUT_MS + 1000);
return () => window.clearTimeout(t);
}, [shadersWarmed, canvasReady, envReady, texturesPending, hallKey, glEpoch, hallIndex]);
}, [shadersWarmed, canvasReady, envReady, hallKey, glEpoch, hallIndex]);
const layout = useMemo(() => {
if (isWingedHall && movementHalls.length > 0) {
@@ -1986,16 +1998,17 @@ export default function VirtualGallery(props: Props) {
return buildHallLayout(paintings, periods);
}, [isWingedHall, movementHalls, hallIndex, paintings, periods]);
// Do not block the hall on every painting texture — large artists (e.g. Duccio, 50+)
// otherwise sit on "Loading paintings…" for a long time. Textures keep loading after.
const gallerySceneLoading =
active &&
!glLost &&
(!canvasReady || !envReady || texturesPending > 0 || !shadersWarmed);
(!canvasReady || !envReady || !shadersWarmed);
const galleryLoadingMessage =
canvasReady && texturesPending > 0 ? 'Loading paintings…' : 'Loading gallery…';
const warmGpuEnabled =
canvasReady && envReady && texturesPending === 0 && !shadersWarmed;
const warmGpuEnabled = canvasReady && envReady && !shadersWarmed;
const computedWindows = useMemo(() => {
if (!isMovement || !interiorStyle || !('hallIndex' in layout)) return undefined;