diff --git a/client/src/components/fableloom/LoomNodeEditor.jsx b/client/src/components/fableloom/LoomNodeEditor.jsx
index 37fdd2d293..9cc406a58a 100644
--- a/client/src/components/fableloom/LoomNodeEditor.jsx
+++ b/client/src/components/fableloom/LoomNodeEditor.jsx
@@ -44,7 +44,7 @@ const rowToPatch = ({ targetNodeId, intent, triggersText, description }) => ({
});
export default function LoomNodeEditor({
- loom, episode, node, onLoomUpdate, onClearSelection, onMakeStart,
+ loom, episode, node, universe, onLoomUpdate, onClearSelection, onMakeStart,
mediaJobs = {}, onGenerateImage, onGenerateVideo,
generationDisabled = false, generationDisabledReason = '',
}) {
@@ -70,6 +70,11 @@ export default function LoomNodeEditor({
imagePrompt: node.imagePrompt || '',
videoPrompt: node.videoPrompt || '',
cameraMovement: node.cameraMovement || '',
+ visualCanon: node.visualCanon ? {
+ ...node.visualCanon,
+ characterAppearances: [...(node.visualCanon.characterAppearances || [])],
+ objectIds: [...(node.visualCanon.objectIds || [])],
+ } : null,
playbackMode: node.playbackMode || 'decision',
audienceConnection: node.audienceConnection || 'disconnected',
playbackAssets: node.playbackAssets || null,
@@ -107,6 +112,13 @@ export default function LoomNodeEditor({
return updated;
};
+ const saveVisualCanon = (next) => {
+ setForm((current) => ({ ...current, visualCanon: next }));
+ patchNode({ visualCanon: next });
+ };
+
+ const updateVisualCanon = (patch) => saveVisualCanon({ ...form.visualCanon, ...patch });
+
// Blur-save helper: skip the round-trip when the value matches the record
// (tabbing through the panel shouldn't rewrite the loom).
const saveField = (key, value) => {
@@ -474,6 +486,187 @@ export default function LoomNodeEditor({
/>
+
+
+ {form.visualCanon && (
+
+
+
+
+
+
+
+
+
+ {(universe?.objects || []).length > 0 && (
+
+ )}
+
+
+
+
+
+
+
+
+ {node.image && (
+
+ )}
+
+ )}
+
+
Scene image prompt