Browse Source

fix: prevent node height accumulation with inline parameters on reload

Store measured settings panel height in node data (_settingsPanelHeight).
On workflow load, subtract it from persisted height so BaseNode's expand
effect re-adds the real panel height from a clean baseline instead of
double-counting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
0fd65682ce
  1. 10
      src/components/nodes/BaseNode.tsx
  2. 53
      src/store/workflowStore.ts
  3. 5
      src/types/nodes.ts

10
src/components/nodes/BaseNode.tsx

@ -141,7 +141,10 @@ export function BaseNode({
nodes.map((node) => { nodes.map((node) => {
if (node.id !== id) return node; if (node.id !== id) return node;
const currentHeight = getNodeDimension(node, "height"); const currentHeight = getNodeDimension(node, "height");
return applyNodeDimensions(node, getNodeDimension(node, "width"), currentHeight + finalHeight); return {
...applyNodeDimensions(node, getNodeDimension(node, "width"), currentHeight + finalHeight),
data: { ...node.data, _settingsPanelHeight: finalHeight },
};
}) })
); );
} }
@ -188,7 +191,10 @@ export function BaseNode({
if (node.id !== id) return node; if (node.id !== id) return node;
const currentHeight = getNodeDimension(node, "height"); const currentHeight = getNodeDimension(node, "height");
const newHeight = Math.max(minHeight, currentHeight + delta); const newHeight = Math.max(minHeight, currentHeight + delta);
return applyNodeDimensions(node, getNodeDimension(node, "width"), newHeight); return {
...applyNodeDimensions(node, getNodeDimension(node, "width"), newHeight),
data: { ...node.data, _settingsPanelHeight: newPanelHeight },
};
}) })
); );

53
src/store/workflowStore.ts

@ -1712,17 +1712,54 @@ const workflowStoreImpl: StateCreator<WorkflowStore> = (set, get) => ({
// Load cost data for this workflow // Load cost data for this workflow
const costData = workflow.id ? loadWorkflowCostData(workflow.id) : null; const costData = workflow.id ? loadWorkflowCostData(workflow.id) : null;
// Subtract saved settings panel height so BaseNode's expand effect
// re-adds the real panel height from a clean baseline (prevents
// height accumulation on each save/reload cycle).
const INLINE_PARAM_NODE_TYPES = new Set([
"nanoBanana", "generateVideo", "generate3d", "generateAudio", "llmGenerate",
]);
const inlineParamsEnabled = (() => {
try { return localStorage.getItem("node-banana-inline-parameters") === "true"; }
catch { return false; }
})();
set({ set({
// Clear selected state - selection should not be persisted across sessions // Clear selected state - selection should not be persisted across sessions
// Also validate position to ensure coordinates are finite numbers // Also validate position to ensure coordinates are finite numbers
nodes: hydratedWorkflow.nodes.map(node => ({ nodes: hydratedWorkflow.nodes.map(node => {
...node, let adjustedNode = {
selected: false, ...node,
position: { selected: false,
x: isFinite(node.position?.x) ? node.position.x : 0, position: {
y: isFinite(node.position?.y) ? node.position.y : 0, x: isFinite(node.position?.x) ? node.position.x : 0,
}, y: isFinite(node.position?.y) ? node.position.y : 0,
})), },
};
// Subtract panel height for inline-param nodes so the expand effect
// can re-add the real measured height without double-counting
const data = node.data as Record<string, unknown>;
const panelHeight = typeof data?._settingsPanelHeight === "number" ? data._settingsPanelHeight : 0;
if (
INLINE_PARAM_NODE_TYPES.has(node.type ?? "") &&
inlineParamsEnabled &&
(data?.parametersExpanded ?? true) === true &&
panelHeight > 0
) {
const currentHeight = (node.style?.height as number) ?? (defaultNodeDimensions[node.type as NodeType]?.height ?? 300);
const minHeight = defaultNodeDimensions[node.type as NodeType]?.height ?? 300;
const newHeight = Math.max(minHeight, currentHeight - panelHeight);
adjustedNode = {
...adjustedNode,
width: undefined,
height: undefined,
measured: undefined,
style: { ...adjustedNode.style, height: newHeight },
} as typeof adjustedNode;
}
return adjustedNode;
}),
edges: hydratedWorkflow.edges, edges: hydratedWorkflow.edges,
edgeStyle: hydratedWorkflow.edgeStyle || "angular", edgeStyle: hydratedWorkflow.edgeStyle || "angular",
groups: hydratedWorkflow.groups || {}, groups: hydratedWorkflow.groups || {},

5
src/types/nodes.ts

@ -178,6 +178,7 @@ export interface NanoBananaNodeData extends BaseNodeData {
parameters?: Record<string, unknown>; // Model-specific parameters for external providers parameters?: Record<string, unknown>; // Model-specific parameters for external providers
inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles
parametersExpanded?: boolean; // Collapse state for inline parameter display parametersExpanded?: boolean; // Collapse state for inline parameter display
_settingsPanelHeight?: number; // Measured settings panel height for reload correction
status: NodeStatus; status: NodeStatus;
error: string | null; error: string | null;
imageHistory: CarouselImageItem[]; // Carousel history (IDs only) imageHistory: CarouselImageItem[]; // Carousel history (IDs only)
@ -197,6 +198,7 @@ export interface GenerateVideoNodeData extends BaseNodeData {
parameters?: Record<string, unknown>; // Model-specific parameters parameters?: Record<string, unknown>; // Model-specific parameters
inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles
parametersExpanded?: boolean; // Collapse state for inline parameter display parametersExpanded?: boolean; // Collapse state for inline parameter display
_settingsPanelHeight?: number; // Measured settings panel height for reload correction
status: NodeStatus; status: NodeStatus;
error: string | null; error: string | null;
videoHistory: CarouselVideoItem[]; // Carousel history (IDs only) videoHistory: CarouselVideoItem[]; // Carousel history (IDs only)
@ -217,6 +219,7 @@ export interface Generate3DNodeData extends BaseNodeData {
parameters?: Record<string, unknown>; parameters?: Record<string, unknown>;
inputSchema?: ModelInputDef[]; inputSchema?: ModelInputDef[];
parametersExpanded?: boolean; // Collapse state for inline parameter display parametersExpanded?: boolean; // Collapse state for inline parameter display
_settingsPanelHeight?: number; // Measured settings panel height for reload correction
status: NodeStatus; status: NodeStatus;
error: string | null; error: string | null;
} }
@ -242,6 +245,7 @@ export interface GenerateAudioNodeData extends BaseNodeData {
parameters?: Record<string, unknown>; // Model-specific parameters (voice, speed, etc.) parameters?: Record<string, unknown>; // Model-specific parameters (voice, speed, etc.)
inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles
parametersExpanded?: boolean; // Collapse state for inline parameter display parametersExpanded?: boolean; // Collapse state for inline parameter display
_settingsPanelHeight?: number; // Measured settings panel height for reload correction
status: NodeStatus; status: NodeStatus;
error: string | null; error: string | null;
audioHistory: CarouselAudioItem[]; // Carousel history (IDs only) audioHistory: CarouselAudioItem[]; // Carousel history (IDs only)
@ -263,6 +267,7 @@ export interface LLMGenerateNodeData extends BaseNodeData {
temperature: number; temperature: number;
maxTokens: number; maxTokens: number;
parametersExpanded?: boolean; // Collapse state for inline parameter display parametersExpanded?: boolean; // Collapse state for inline parameter display
_settingsPanelHeight?: number; // Measured settings panel height for reload correction
status: NodeStatus; status: NodeStatus;
error: string | null; error: string | null;
} }

Loading…
Cancel
Save