Hi everyone!
Like many of you, I’ve been struggling with the new Bubble Workflow Editor.
To help navigate this transition, I developed a workaround: a visual logs panel that simulates the original workflow logic.
Main features:
– Logs are automatically created when you click mapped workflow buttons.
– Logs are clickable and trigger the same behavior as the original workflow buttons.
– Mapped buttons are highlighted in yellow.
– The currently clicked log or button is highlighted in blue.
– You can reorder the logs by dragging and dropping them to simulate the workflow flow.
Important notes:
– It currently works only inside the Backend Workflows page or Frontend Workflows page. If you change pages, the script needs to be refreshed.
– Some bugs are expected — this is a workaround, not a final solution!
Future ideas:
– We hope to turn it into a Chrome Extension soon.
– Collaboration is 100% welcome! Feel free to improve it, fork it, or help evolve it into a better tool.
(function() {
if (window.workflowTrackerInitialized) return;
window.workflowTrackerInitialized = true;
let savedLogs = [];
let currentLogSelected = null;
let currentSelectedButton = null;
let draggedEntry = null;
let workflowResizeArea = null;
let mouseOverLogArea = false;
const logsContainer = document.createElement('div');
logsContainer.id = 'logs-container';
logsContainer.style.cssText = `
display: flex;
flex-direction: column;
gap: 10px;
overflow-y: auto;
height: 100%;
padding: 10px;
`;
const workflowLog = document.createElement('div');
workflowLog.id = 'workflow-log';
workflowLog.style.cssText = `
display: flex;
flex-direction: column;
width: 280px;
background: #fff;
border-left: 1px solid #ccc;
border-right: 1px solid #B0B0B0;
height: 100%;
box-sizing: border-box;
font-family: 'Helvetica Neue', sans-serif;
font-size: 13px;
color: #333;
pointer-events: auto;
`;
const logHeader = document.createElement('div');
logHeader.style.cssText = `
font-weight: bold;
font-size: 13px;
padding: 15px 12px;
color: #000;
text-align: left;
border-bottom: 1px solid #ccc;
height: 50px;
display: flex;
align-items: center;
justify-content: space-between;
`;
logHeader.innerHTML = `
<span>Workflow Click Logs</span>
<button id="reset-logs" style="
background: red;
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 11px;
">Reset</button>
`;
workflowLog.appendChild(logHeader);
workflowLog.appendChild(logsContainer);
function resetHighlights() {
document.querySelectorAll('.log-entry').forEach(entry => {
entry.style.backgroundColor = '#f9f9f9';
entry.classList.remove('active-log');
});
document.querySelectorAll('[role="treeitem"]').forEach(btn => {
if (btn._hasLog) {
btn.style.backgroundColor = 'rgba(255, 255, 0, 0.3)';
} else {
btn.style.backgroundColor = '';
}
});
}
function addLogEntry(label, targetButton, selectImmediately = false) {
const entry = document.createElement('div');
entry.className = 'log-entry';
entry.draggable = true;
entry.style.cssText = `
cursor: pointer;
padding: 8px 40px 8px 10px;
background: #f9f9f9;
border-radius: 5px;
line-height: 1.8;
overflow-wrap: break-word;
position: relative;
user-select: none;
`;
entry.textContent = label;
entry._targetButton = targetButton;
targetButton._hasLog = true;
targetButton.style.backgroundColor = 'rgba(255, 255, 0, 0.3)';
const deleteBtn = document.createElement('div');
deleteBtn.textContent = 'Delete';
deleteBtn.style.cssText = `
position: absolute;
top: 6px;
right: 8px;
font-size: 11px;
color: red;
cursor: pointer;
`;
deleteBtn.addEventListener('click', function(e) {
e.stopPropagation();
const index = savedLogs.indexOf(label);
if (index !== -1) savedLogs.splice(index, 1);
logsContainer.removeChild(entry);
if (targetButton) {
targetButton._hasLog = false;
targetButton.style.backgroundColor = '';
}
});
entry.addEventListener('click', function(e) {
e.stopPropagation();
if ((e.metaKey || e.ctrlKey) && entry._targetButton) {
const itemId = entry._targetButton.getAttribute('data-item-id');
if (itemId) {
const url = `https://bubble.io/page?id=startcollection&tab=BackendWorkflows&type=api&wf_item=${itemId}`;
window.open(url, '_blank');
return;
}
}
resetHighlights();
currentLogSelected = entry;
currentLogSelected.classList.add('active-log');
entry.style.backgroundColor = 'rgba(13, 41, 171, 0.15)';
if (entry._targetButton) {
currentSelectedButton = entry._targetButton;
currentSelectedButton.style.backgroundColor = 'rgba(13, 41, 171, 0.15)';
currentSelectedButton.scrollIntoView({ behavior: 'smooth', block: 'center' });
currentSelectedButton.click();
}
});
entry.addEventListener('dragstart', function() {
draggedEntry = entry;
setTimeout(() => {
entry.style.opacity = '0.5';
}, 0);
});
entry.addEventListener('dragover', function(e) {
e.preventDefault();
const bounding = this.getBoundingClientRect();
const offset = bounding.y + (bounding.height / 2);
if (e.clientY - offset > 0) {
this.style.borderBottom = '2px solid #0D29AB';
this.style.borderTop = '';
} else {
this.style.borderTop = '2px solid #0D29AB';
this.style.borderBottom = '';
}
});
entry.addEventListener('dragleave', function() {
this.style.borderBottom = '';
this.style.borderTop = '';
});
entry.addEventListener('drop', function(e) {
e.preventDefault();
if (draggedEntry !== this) {
if (this.style.borderTop) {
logsContainer.insertBefore(draggedEntry, this);
} else {
logsContainer.insertBefore(draggedEntry, this.nextSibling);
}
}
this.style.borderBottom = '';
this.style.borderTop = '';
draggedEntry.style.opacity = '1';
draggedEntry = null;
});
entry.addEventListener('dragend', function() {
if (draggedEntry) {
draggedEntry.style.opacity = '1';
draggedEntry = null;
}
});
entry.addEventListener('mouseenter', () => { mouseOverLogArea = true; });
entry.addEventListener('mouseleave', () => { mouseOverLogArea = false; });
entry.appendChild(deleteBtn);
logsContainer.appendChild(entry);
if (selectImmediately) {
resetHighlights();
entry.classList.add('active-log');
entry.style.backgroundColor = 'rgba(13, 41, 171, 0.15)';
if (targetButton) {
targetButton.style.backgroundColor = 'rgba(13, 41, 171, 0.15)';
}
}
}
function monitorWorkflowButtons() {
const parent = document.querySelector('div[data-name="workflowList"]') || document.querySelector('._1ql74v3k');
if (!parent) return;
parent.addEventListener('click', (e) => {
const btn = e.target.closest('div[role="treeitem"]');
if (btn && parent.contains(btn)) {
const label = btn.innerText.trim();
const entries = [...logsContainer.querySelectorAll('.log-entry')];
const matchingEntry = entries.find(entry => entry.textContent.trim() === label);
if (!savedLogs.includes(label)) {
savedLogs.push(label);
addLogEntry(label, btn, true);
} else if (matchingEntry) {
matchingEntry.click();
}
}
}, true);
}
function insertWorkflowLogPanel() {
const rightDiv = document.querySelector('._101sfx31');
if (rightDiv) {
rightDiv.style.width = '280px';
rightDiv.style.flexShrink = '0';
rightDiv.style.pointerEvents = 'none';
workflowLog.style.pointerEvents = 'auto';
rightDiv.appendChild(workflowLog);
const resizeArea = rightDiv.parentElement.querySelector('._16zpnji2');
if (resizeArea) {
workflowResizeArea = resizeArea;
resizeArea.addEventListener('mousedown', (e) => {
if (mouseOverLogArea) {
e.stopPropagation();
e.preventDefault();
}
}, true);
}
}
}
function observePageReady() {
const observer = new MutationObserver(() => {
const workflowArea = document.querySelector('div[data-name="workflowList"]') || document.querySelector('._1ql74v3k');
if (workflowArea) {
observer.disconnect();
insertWorkflowLogPanel();
monitorWorkflowButtons();
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
document.addEventListener('click', (e) => {
if (e.target.id === 'reset-logs') {
savedLogs = [];
logsContainer.innerHTML = '';
document.querySelectorAll('[role="treeitem"]').forEach(btn => {
btn._hasLog = false;
btn.style.backgroundColor = '';
});
}
});
observePageReady();
})();
Ideally, Bubble will fix these problems soon, and we won’t even need this anymore.
The project is fully open for the community!
Let’s help each other during this difficult transition.
Thanks!