diff --git a/tour-comfy/car.html b/tour-comfy/car.html
index d040a02..d4fe42d 100644
--- a/tour-comfy/car.html
+++ b/tour-comfy/car.html
@@ -2246,8 +2246,9 @@
const prefBtn = document.getElementById('lbPreferredBtn');
if (prefBtn) prefBtn.style.display = multi && lbIdx > 0 ? '' : 'none';
- // Film strip
+ // Film strip — always visible (override old display:none CSS)
const strip = document.getElementById('lbVariantStrip');
+ strip.style.display = 'flex';
strip.innerHTML = lbUrls.map((url, i) => {
const n = lbNames[i];
const pose = filePoses[n] || '';
@@ -2735,6 +2736,21 @@
updateCardSelection();
document.getElementById('count').textContent = `${currentGroups.length} group${currentGroups.length !== 1 ? 's' : ''} · ${fileObjects.length} images`;
if (newFileNames.size > 0) showToast(`${newFileNames.size} new image${newFileNames.size !== 1 ? 's' : ''} detected`, 'success');
+
+ // If studio is open, sync lbUrls/lbNames from freshly-loaded groupData
+ const studioEl = document.getElementById('studio');
+ if (studioEl?.classList.contains('open') && lbCurrentGid) {
+ const fresh = groupData.get(lbCurrentGid);
+ if (fresh) {
+ const curName = lbNames[lbIdx];
+ const newIdx = fresh.names.indexOf(curName);
+ lbUrls = fresh.urls;
+ lbNames = fresh.names;
+ lbIdx = newIdx >= 0 ? newIdx : Math.min(lbIdx, fresh.names.length - 1);
+ updateStudio();
+ }
+ }
+
statusDot.classList.remove('updating');
}
@@ -3361,9 +3377,9 @@
function renderSidebarGenerate() {
const panel = document.getElementById('sbPanelGenerate');
if (!panel) return;
+ // Use lbNames (current open group) to find already-generated poses
const donePoses = new Set();
- const gData = groupData.get(lbCurrentGid);
- if (gData) gData.names.forEach(n => { if (filePoses[n]) donePoses.add(filePoses[n]); });
+ lbNames.forEach(n => { if (filePoses[n]) donePoses.add(filePoses[n]); });
let html = '
Poses
';
if (!availablePoses || Object.keys(availablePoses).length === 0) {
@@ -3780,10 +3796,11 @@
html += '
';
}
html += `
-
-
![]()
+
![]()
+
Loading…
_sceneShowFrame(t), 300);
}
- async function sceneExtractFrame() {
- const scrubber = document.getElementById('sceneScrubber');
- const t = parseFloat(scrubber?.value || '0');
- const btn = document.getElementById('sceneExtractBtn');
- if (btn) btn.textContent = 'Extracting…';
+ async function _sceneShowFrame(t) {
+ if (!_sceneVideo) return;
try {
const r = await fetch(`${API}/wireframe/frame`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ video_name: _sceneVideo, time: t }),
});
- if (r.ok) {
+ if (!r.ok) return;
+ const d = await r.json();
+ const vid = document.getElementById('sceneVideoEl');
+ const img = document.getElementById('sceneFrameImg');
+ if (!img) return;
+ if (vid) vid.style.display = 'none';
+ img.src = 'data:image/png;base64,' + d.frame_b64;
+ img.style.display = '';
+ img.dataset.pendingFrame = d.frame_b64; // ready to capture without re-fetch
+ } catch (_) { /* video stays visible on error */ }
+ }
+
+ async function sceneExtractFrame() {
+ const scrubber = document.getElementById('sceneScrubber');
+ const t = parseFloat(scrubber?.value || '0');
+ const btn = document.getElementById('sceneExtractBtn');
+ const img = document.getElementById('sceneFrameImg');
+ // If the debounced preview already shows this frame, just lock it — no re-fetch
+ if (img?.dataset.pendingFrame) {
+ _sceneFrameBytes = img.dataset.pendingFrame;
+ delete img.dataset.pendingFrame;
+ if (btn) btn.textContent = '✓ Frame captured';
+ const genBtn = document.getElementById('sceneGenBtn');
+ if (genBtn) genBtn.disabled = false;
+ return;
+ }
+ if (btn) { btn.disabled = true; btn.textContent = 'Extracting…'; }
+ try {
+ const r = await fetch(`${API}/wireframe/frame`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ video_name: _sceneVideo, time: t }),
+ });
+ if (!r.ok) {
+ showToast('Frame extract failed: ' + (await r.text()), 'error');
+ } else {
const d = await r.json();
_sceneFrameBytes = d.frame_b64;
- const img = document.getElementById('sceneFrameImg');
- if (img) { img.src = 'data:image/png;base64,' + _sceneFrameBytes; img.style.display = ''; }
+ const vid = document.getElementById('sceneVideoEl');
+ const imgEl = document.getElementById('sceneFrameImg');
+ if (vid) vid.style.display = 'none';
+ if (imgEl) { imgEl.src = 'data:image/png;base64,' + _sceneFrameBytes; imgEl.style.display = ''; }
const genBtn = document.getElementById('sceneGenBtn');
if (genBtn) genBtn.disabled = false;
+ if (btn) btn.textContent = '✓ Frame captured';
}
- } catch (_) {}
- if (btn) btn.textContent = 'Extract frame as reference';
+ } catch (e) {
+ showToast('Frame extract error: ' + e, 'error');
+ if (btn) btn.textContent = 'Extract frame as reference';
+ }
+ if (btn) btn.disabled = false;
}
function sceneHandleUpload(event) {
@@ -3920,10 +3992,12 @@
if (btn) btn.disabled = false;
refreshNow();
} else {
- if (textEl) textEl.textContent = 'Error: ' + (job.error || 'unknown');
+ const errMsg = job.error || 'unknown error';
+ if (textEl) textEl.textContent = 'Error: ' + errMsg;
if (btn) btn.disabled = false;
+ showToast('Scenery failed: ' + errMsg, 'error');
}
- } catch (_) { pollScene(); }
+ } catch (e) { pollScene(); }
}, 2500);
};
pollScene();
diff --git a/tour-comfy/watcher.log b/tour-comfy/watcher.log
index a4a2a65..d336670 100644
--- a/tour-comfy/watcher.log
+++ b/tour-comfy/watcher.log
@@ -2094,3 +2094,6 @@ SyntaxError: broken PNG file (chunk b'END\xae')
2026-06-21 11:04:28,350 - INFO - Watcher started. Monitoring /mnt/zim/tour-comfy/stage...
2026-06-21 11:04:28,350 - INFO - Output directory: /mnt/zim/tour-comfy/output
2026-06-21 11:04:28,350 - INFO - API URL: http://127.0.0.1:8500/edit
+2026-06-21 22:09:31,709 - INFO - Watcher started. Monitoring /mnt/zim/tour-comfy/stage...
+2026-06-21 22:09:31,709 - INFO - Output directory: /mnt/zim/tour-comfy/output
+2026-06-21 22:09:31,709 - INFO - API URL: http://127.0.0.1:8500/edit