Keep stale-render prompt hint on timeline clips only.

Remove duplicate Generated-with overlays from the player and inspector so
generation progress is not covered. Add single-place functionality rule.
This commit is contained in:
digital-garbage
2026-06-11 14:47:45 +03:00
parent d246fe7933
commit 1eb5fcd34b
4 changed files with 21 additions and 48 deletions
@@ -0,0 +1,20 @@
---
description: No duplicated functionality — implement each behavior in one place only
alwaysApply: true
---
# Single-place functionality
Where something can be done in one place and functionality remains absolutely the same, it must be done in that single place.
Duplicated functionality is prohibited unless strictly required for the process.
## Examples
- UI state that belongs on a timeline clip (e.g. "Generated with" stale-prompt hint) lives on the clip only — not repeated on the player overlay, inspector, or toolbar.
- Shared logic belongs in one module/function; views call it instead of re-implementing.
- If two call sites need the same behavior, extract once and reuse — do not copy-paste with drift.
## When duplication is allowed
Only when the process genuinely requires it (e.g. server validation mirroring client hints for security, or ComfyUI node vs Movie Editor entry points that cannot share a runtime).
-7
View File
@@ -164,13 +164,6 @@
const ta = el("textarea"); ta.rows = 4; ta.value = root.text || ""; ta.placeholder = "Describe this scene…"; ta.dataset.k = "sc-text";
ta.oninput = () => S.patchSceneQuiet(scene.id, { text: ta.value });
body.append(field("Prompt", ta));
const promptMismatch = S.renderPromptMismatch ? S.renderPromptMismatch(scene.id) : null;
if (promptMismatch) {
const warn = el("div", "render-prompt-warn");
warn.append(el("div", "render-prompt-warn-title", "Preview was generated with a different prompt"));
warn.append(el("div", "render-prompt-text", promptMismatch.rendered || "(empty)"));
body.append(warn);
}
const src = el("select");
SRC.forEach(([v, label]) => { const o = el("option", null, label); o.value = v; if ((root.source?.type) === v) o.selected = true; src.append(o); });
src.onchange = () => {
-18
View File
@@ -102,7 +102,6 @@
let _slateEl = null;
let _badgeEl = null;
let _promptEl = null;
function _showSlate(title, sub) {
if (!_slateEl) return;
_slateEl.style.display = title ? "" : "none";
@@ -117,18 +116,6 @@
_badgeEl.textContent = text || "";
_badgeEl.style.display = text ? "" : "none";
}
function _showRenderPrompt(sceneId) {
if (!_promptEl) return;
const mismatch = sceneId && S.renderPromptMismatch ? S.renderPromptMismatch(sceneId) : null;
if (!mismatch) {
_promptEl.style.display = "none";
_promptEl.textContent = "";
return;
}
_promptEl.style.display = "";
_promptEl.title = "Timeline prompt was edited after generation - rate against this text";
_promptEl.textContent = `Generated with: ${mismatch.rendered}`;
}
// ── video pool management ─────────────────────────────────────────────────────
function _ensureVideo(url) {
@@ -210,7 +197,6 @@
_currentClip = clip;
_setActive(null);
_showBadge("");
_showRenderPrompt(null);
_showSlate(clip.slate || "Not generated yet", clip.slateSub);
if (play) _startTick();
return;
@@ -221,7 +207,6 @@
_showSlate("");
const badge = clip.ghost ? (clip.slate || "Removed — preview only") : _segmentSourceBadge(clip.sceneId);
_showBadge(badge);
_showRenderPrompt(clip.ghost ? null : clip.sceneId);
if (_badgeEl && badge.startsWith("Mixed")) _badgeEl.classList.add("mixed-mode");
else if (_badgeEl) _badgeEl.classList.remove("mixed-mode");
const target = (clip.inSec || 0) + Math.max(0, offset);
@@ -512,14 +497,11 @@
_slateEl.append(el("div", "pm-slate-sub"));
_badgeEl = el("div", "pm-segment-badge");
_badgeEl.style.display = "none";
_promptEl = el("div", "pm-render-prompt");
_promptEl.style.display = "none";
if (_clips.length) {
canvas.append(stage); // pooled videos live here; only the active one is visible
canvas.append(_slateEl);
canvas.append(_badgeEl);
canvas.append(_promptEl);
} else if (previewAsset?.kind === "image") {
canvas.append(stage);
} else if (["queuing", "running", "pending"].includes(gen.state)) {
+1 -23
View File
@@ -343,7 +343,7 @@ input[type=number] { font-family: var(--font-mono); }
.pm-canvas.media-previewing .pm-stage,
.pm-canvas.media-previewing .pm-slate,
.pm-canvas.media-previewing .pm-segment-badge,
.pm-canvas.media-previewing .pm-render-prompt { visibility: hidden; }
.pm-canvas.media-previewing .pm-segment-badge { visibility: hidden; }
.pm-media-preview { position: absolute; inset: 0; z-index: 5; display: flex; align-items: center; justify-content: center; cursor: default; }
.pm-media-preview-img { max-width: 100%; max-height: 100%; object-fit: contain; position: relative; z-index: 1; cursor: default; }
.pm-media-preview-label { position: absolute; top: 10px; left: 10px; z-index: 2; font-size: 10.5px; font-family: var(--font-mono); color: var(--teal); background: #000a; border: 1px solid color-mix(in srgb, var(--teal) 35%, var(--line)); border-radius: 12px; padding: 3px 9px; pointer-events: none; }
@@ -380,13 +380,6 @@ input[type=number] { font-family: var(--font-mono); }
.pm-slate-title { font-size: 14px; color: var(--amber-2); font-family: var(--font-mono); }
.pm-slate-sub { font-size: 11.5px; color: var(--faint); max-width: 280px; }
.pm-segment-badge { position: absolute; top: 10px; left: 10px; z-index: 3; font-size: 10.5px; font-family: var(--font-mono); color: var(--amber-2); background: #000a; border: 1px solid color-mix(in srgb, var(--amber) 35%, var(--line)); border-radius: 12px; padding: 3px 9px; pointer-events: none; }
.pm-render-prompt {
position: absolute; left: 10px; right: 10px; bottom: 10px; z-index: 3;
font-size: 10.5px; font-family: var(--font-mono); line-height: 1.35; color: #ffd9a0;
background: #000c; border: 1px solid color-mix(in srgb, var(--amber) 40%, var(--line));
border-radius: 10px; padding: 6px 10px; pointer-events: none;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pm-needle { position: absolute; top: 0; bottom: 0; width: 2px; background: #fff; opacity: .85; transform: translateX(-50%); pointer-events: none; z-index: 2; }
/* transport bar */
@@ -563,21 +556,6 @@ input[type=number] { font-family: var(--font-mono); }
.me-rating-heart:hover { background: rgba(255,120,180,0.12); border-color: rgba(255,120,180,0.6); color: #ffb3d4; }
.me-rating-heart.active { background: rgba(255,120,180,0.20); border-color: rgba(255,120,180,0.8); color: #ffb3d4; }
.me-rating-nuclear { padding: 8px 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
.tl-render-prompt {
font-family: var(--font-mono); font-size: 10px; color: var(--amber-2); opacity: .9;
max-width: 220px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
border: 1px solid color-mix(in srgb, var(--amber) 30%, var(--line)); border-radius: 6px; padding: 3px 7px;
background: color-mix(in srgb, var(--amber-soft) 35%, var(--ink-2));
}
.render-prompt-warn {
border: 1px solid color-mix(in srgb, var(--amber) 35%, var(--line));
background: color-mix(in srgb, var(--amber-soft) 25%, var(--ink-1));
border-radius: 8px; padding: 8px 10px; margin: -4px 0 12px;
}
.render-prompt-warn-title { font-size: 11.5px; color: var(--amber-2); margin-bottom: 6px; }
.render-prompt-prev { display: flex; flex-direction: column; gap: 3px; margin-bottom: 6px; }
.render-prompt-label { font-family: var(--font-mono); font-size: 10px; letter-spacing: .5px; text-transform: uppercase; color: var(--faint); }
.render-prompt-text { font-size: 12px; color: var(--text); line-height: 1.4; white-space: pre-wrap; word-break: break-word; }
.tl-scroll { flex: 1; overflow-x: auto; overflow-y: auto; position: relative; }
.tl-stage { display: flex; flex-direction: row; height: 100%; min-height: 100%; }