feat: warn on the main window when Best-FaceID cannot do anything

Best-FaceID reads identity from an identity_pin-tagged guide, and only the AUTO
continuity builder ever applies that tag (timeline._identity_pin_guide). Every
way of losing it is silent: the run succeeds and simply carries no identity.

Three such states, all verified against _run_sampler_inputs (tagged guides in
the emitted funpack_scene_guides payload: 2 when healthy, 0 in each bad case):
  - no identity pin set at all
  - custom guide stack on -- manual guides REPLACE auto guides (`manual or auto`),
    and manual GuideEntry has no identity_pin field, so the pin is dropped
  - auto continuity off -- same outcome by the other route

Shown on the main window of both frontends, not inside the settings pane that
turns it on: you would only see it there if you already went looking.
  - shared predicate: PipelineCaps.identityTransferIssue -> {short, detail}
  - Editor:   amber chip beside Generate in the timeline header, click opens
              Engine settings; full text in the tooltip
  - Easy Gen: full-width amber strip between the preview and the Generate row

fpActionbar had to grow the identity fields -- it hashed only pid/sel/gen, so
setting the pin would have left a stale warning sitting next to Generate.

Verified live in both UIs across all five transitions (no pin -> pin -> stack on
-> stack off -> auto off -> feature off); appears and clears correctly in each.
Silent when the Chain Sampler is not in the pipeline, where the knob is inert.

Tests: 22 failed / 465 passed, unchanged from baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-08-02 02:01:09 +03:00
co-authored by Claude Opus 5
parent ffe920474c
commit 858e7d9cb6
7 changed files with 78 additions and 0 deletions
+7
View File
@@ -15,6 +15,7 @@
const uploadInput = document.getElementById("easy-upload-input");
const galleryBtn = document.getElementById("easy-gallery-btn");
const generateBtn = document.getElementById("easy-generate-btn");
const warnStrip = document.getElementById("easy-warn-strip");
const advancedBtn = document.getElementById("easy-advanced-btn");
const projectsBtn = document.getElementById("easy-projects-btn");
const saveBtn = document.getElementById("easy-save-btn");
@@ -72,6 +73,12 @@
uploadBtn.textContent = attached ? "⬆ Uploaded ✕" : "⬆ Upload";
preview.setUpload(attached ? API.mediaUrl(source.media_ref) : null, source?.type === "v2v" ? "video" : "image");
// A setting that is switched on but cannot do anything belongs on the main screen,
// above Generate — not only inside the pane that switched it on.
const issue = window.PipelineCaps?.identityTransferIssue(st);
warnStrip.textContent = issue ? issue.detail : "";
warnStrip.hidden = !issue;
const pid = p ? p.id : null;
if (pid !== lastProjectId) {
lastProjectId = pid;
+9
View File
@@ -63,6 +63,15 @@ a.brand:hover .brand-sub { color: var(--amber-2); }
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.easy-progress-stop:disabled { opacity: .6; cursor: default; }
/* Full-width notice between the preview and the Generate row: a setting that is on
but cannot do anything should be readable without opening any panel. */
.easy-warn-strip {
flex: none; padding: 9px 16px; font-size: 12.5px; line-height: 1.45;
color: var(--amber); border-top: 1px solid color-mix(in srgb, var(--amber) 45%, var(--line));
background: color-mix(in srgb, var(--amber) 9%, transparent);
}
.easy-warn-strip[hidden] { display: none; }
.easy-bar {
flex: none; display: flex; align-items: flex-end; gap: 12px;
padding: 14px 16px; border-top: 1px solid var(--line); background: var(--ink-1);
+2
View File
@@ -64,6 +64,8 @@
<main class="easy-preview" id="easy-preview"></main>
<div class="easy-warn-strip" id="easy-warn-strip" hidden></div>
<footer class="easy-bar">
<button class="btn ghost easy-upload-btn" id="easy-upload-btn" type="button"
title="Upload an image or video to guide generation">⬆ Upload</button>
+11
View File
@@ -40,6 +40,17 @@
montageBtn.disabled = !hasProject() || busy(st);
montageBtn.onclick = () => window.MontageDialog?.open();
mount.append(montageBtn);
// Settings that are switched on but cannot do anything belong next to Generate,
// not buried in the pane that switched them on — you would only see it there if
// you already went looking.
const issue = window.PipelineCaps?.identityTransferIssue(st);
if (issue) {
const chip = el("button", "btn ghost compact action-warn", "⚠ " + issue.short);
chip.title = issue.detail + "\n\nClick to open Engine settings.";
chip.onclick = () => window.SettingsWindow?.open("engine");
mount.append(chip);
}
}
if (window.ViewBus) window.ViewBus.subscribeActionbar(render);
+38
View File
@@ -60,6 +60,43 @@
return sourceNeedsAnchorMedia(scene, st);
}
// Best-FaceID reads identity from an identity_pin-tagged guide, and ONLY the auto
// continuity builder ever applies that tag (backend timeline._identity_pin_guide).
// Every way of losing it is silent — the run succeeds carrying no identity at all —
// so the main window says so before you spend a generation finding out.
// Returns {short, detail} for a compact chip and a full-width strip respectively,
// or null when the setting will actually do something.
function identityTransferIssue(st) {
const p = st && st.project;
if (!p || !(p.sampler_inputs || {}).identity_transfer_enabled) return null;
if (!usesChainSampler(st)) return null; // knob is inert without the Chain Sampler
const cs = p.continuity_settings || {};
const gs = p.guide_settings || {};
if (!cs.identity_pin_ref) {
return {
short: "Best-FaceID: no identity pin",
detail: "You have Best-FaceID enabled but no identity pin is set — it has no face to transfer. "
+ "Set the identity pin in Settings → Engine → Continuity, or press 📌 on an image in the Media bin.",
};
}
if (gs.stack_enabled) {
return {
short: "Best-FaceID: pin not reaching sampler",
detail: "You have Best-FaceID enabled and an identity pin set, but the custom guide stack replaces "
+ "the auto-continuity guides that carry the pin — so the pin never reaches the sampler. "
+ "Turn the custom guide stack off in Settings → Engine → Continuity.",
};
}
if (cs.auto_enabled === false) {
return {
short: "Best-FaceID: pin not reaching sampler",
detail: "You have Best-FaceID enabled and an identity pin set, but auto continuity is off — the pin "
+ "only travels with auto-continuity guides. Turn auto continuity on in Settings → Engine → Continuity.",
};
}
return null;
}
function sourceLabel(type) {
const map = {
empty: "Empty · text-to-video",
@@ -82,6 +119,7 @@
defaultSceneSourceType,
sourceNeedsAnchorMedia,
isMissingAnchorMedia,
identityTransferIssue,
sourceLabel,
};
})();
+3
View File
@@ -197,6 +197,9 @@ button, input, select, textarea { font-family: inherit; color: var(--text); }
.btn.ghost { background: transparent; border-color: transparent; color: var(--muted); padding: 4px 8px; font-size: 12px; }
.btn.ghost:hover { color: var(--amber-2); background: var(--ink-3); }
.btn.ghost.on { color: var(--amber); border-color: color-mix(in srgb, var(--amber) 55%, var(--line)); }
/* "This is switched on but cannot do anything" chip, shown beside Generate. */
.btn.action-warn { color: var(--amber); border-color: color-mix(in srgb, var(--amber) 45%, var(--line)); background: color-mix(in srgb, var(--amber) 10%, transparent); }
.btn.action-warn:hover { color: var(--amber-2); border-color: var(--amber); }
.btn.danger:hover { border-color: var(--danger); color: var(--danger); }
.btn.tiny { padding: 2px 7px; font-size: 12px; }
+8
View File
@@ -120,6 +120,14 @@
pid: st.project?.id,
sel: st.selectedSceneIds?.length,
gen: st.gen?.state,
// The Best-FaceID warning chip lives in this zone, so everything its message
// depends on has to be part of the fingerprint — otherwise setting the pin
// leaves a stale warning sitting next to Generate.
idOn: st.project?.sampler_inputs?.identity_transfer_enabled,
idPin: st.project?.continuity_settings?.identity_pin_ref,
idAuto: st.project?.continuity_settings?.auto_enabled,
idStack: st.project?.guide_settings?.stack_enabled,
sampler: st.project?.sampler_slot,
});
}