feat: improve workflow tab click detection and stable graph signing

- Add isLikelyWorkflowTabClickTarget to distinguish workflow tab clicks
  from generic document clicks, preventing false 'workflow-change'
  triggers and unnecessary refresh cycles.
- Add getWorkflowSignatureData to produce a stable, normalized signature
  for workflow data (sorted nodes/links, top-level signature included).
- Update document-click handler to use a new 'workflow-tab-click' reason
  via the click-target heuristic.
- Simplify refreshForActiveWorkflowChange retry logic by removing the
  pending-graph-switch special case, since click origin is now explicit.
This commit is contained in:
Dariusz L
2026-06-06 02:11:15 +02:00
parent 145b061c78
commit 1ed4941b6b
2 changed files with 74 additions and 9 deletions
+24 -1
View File
@@ -246,8 +246,9 @@ export class ModelResolver {
const target = event.target instanceof Element ? event.target : null;
if (target?.closest('#model-resolver-modal, .model-resolver-backdrop')) return;
if (!owner.isLikelyWorkflowTabClickTarget(target)) return;
owner.handleActiveWorkflowRouteChange('document-click');
owner.handleActiveWorkflowRouteChange('workflow-tab-click');
};
const focusHandler = () => {
window.__ModelResolverWorkflowChangeOwner?.handleActiveWorkflowRouteChange('window-focus');
@@ -277,6 +278,28 @@ export class ModelResolver {
];
}
isLikelyWorkflowTabClickTarget(target) {
if (!(target instanceof Element)) return false;
const tab = target.closest([
'[data-workflow-id]',
'[data-workflow-name]',
'[data-tab-id*="workflow" i]',
'[aria-controls*="workflow" i]',
'[class*="workflow"][class*="tab" i]',
'[class*="tab"][class*="workflow" i]',
'[class*="workflow"][class*="item" i]'
].join(','));
if (tab) return true;
const roleTab = target.closest('[role="tab"]');
if (!roleTab) return false;
const text = roleTab.textContent?.trim() || '';
const aria = roleTab.getAttribute('aria-label') || roleTab.getAttribute('title') || '';
return /workflow|unsaved|untitled/i.test(`${text} ${aria}`);
}
handleActiveWorkflowRouteChange(reason = 'workflow-change') {
if (!this.dialog?.isVisible()) return;
this.dialog.scheduleActiveWorkflowRefresh(reason);
+50 -8
View File
@@ -6,13 +6,61 @@ export const workflowStateMethods = {
getWorkflowSignature(workflow) {
if (!workflow) return null;
try {
return JSON.stringify(workflow);
return JSON.stringify(this.getWorkflowSignatureData(workflow));
} catch (error) {
console.warn('Model Resolver: workflow signature generation failed', error);
return null;
}
},
getWorkflowSignatureData(workflow) {
const normalizeNode = (node = {}) => ({
id: node.id,
type: node.type,
mode: node.mode,
flags: node.flags,
inputs: Array.isArray(node.inputs)
? node.inputs.map(input => ({
name: input?.name,
type: input?.type,
link: input?.link
}))
: [],
outputs: Array.isArray(node.outputs)
? node.outputs.map(output => ({
name: output?.name,
type: output?.type,
links: output?.links || []
}))
: [],
widgets_values: node.widgets_values || [],
properties: node.properties || {}
});
const normalizeDefinition = (definition = {}) => ({
nodes: Array.isArray(definition.nodes)
? definition.nodes.map(normalizeNode).sort((a, b) => String(a.id).localeCompare(String(b.id)))
: [],
links: definition.links || []
});
const definitions = workflow.definitions && typeof workflow.definitions === 'object'
? Object.fromEntries(
Object.entries(workflow.definitions)
.sort(([a], [b]) => String(a).localeCompare(String(b)))
.map(([key, definition]) => [key, normalizeDefinition(definition)])
)
: {};
return {
nodes: Array.isArray(workflow.nodes)
? workflow.nodes.map(normalizeNode).sort((a, b) => String(a.id).localeCompare(String(b.id)))
: [],
links: workflow.links || [],
definitions
};
},
getActiveWorkflowRouteKey() {
try {
return window.location?.hash || '';
@@ -217,14 +265,8 @@ export const workflowStateMethods = {
const routeChanged = currentRoute !== this.activeWorkflowRouteKey;
const signatureChanged = signature && signature !== this.activeWorkflowSignature;
const graphStillLooksOld = routeChanged && previousSignature && signature === previousSignature;
const possiblePendingGraphSwitch = (
reason === 'document-click' &&
previousSignature &&
signature === previousSignature
);
const maxAttempts = possiblePendingGraphSwitch ? 3 : 8;
if ((!signature || graphStillLooksOld || possiblePendingGraphSwitch) && attempt < maxAttempts) {
if ((!signature || graphStillLooksOld) && attempt < 8) {
setTimeout(() => {
this.refreshForActiveWorkflowChange({
reason,