Fix timeline losing active ring, add latest marker, cache repeated calls
- Active ring now falls back to lastCapturedIdMap when activeSnapshotId is null (cleared on auto-capture), so the ring persists - Latest snapshot gets a yellow glow ring for quick identification - Cache getWorkflowKey() in restore/swap/refresh/populatePicker - Inline getEffectiveWorkflowKey() to avoid redundant getWorkflowKey() - Replace double filter() with single loop for record counting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1583,8 +1583,9 @@ async function restoreSnapshot(record) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await app.loadGraphData(record.graphData, true, true);
|
await app.loadGraphData(record.graphData, true, true);
|
||||||
lastCapturedHashMap.set(getWorkflowKey(), quickHash(JSON.stringify(record.graphData)));
|
const wfKey = getWorkflowKey();
|
||||||
lastGraphDataMap.set(getWorkflowKey(), record.graphData);
|
lastCapturedHashMap.set(wfKey, quickHash(JSON.stringify(record.graphData)));
|
||||||
|
lastGraphDataMap.set(wfKey, record.graphData);
|
||||||
showToast("Snapshot restored", "success");
|
showToast("Snapshot restored", "success");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`[${EXTENSION_NAME}] Restore failed:`, err);
|
console.warn(`[${EXTENSION_NAME}] Restore failed:`, err);
|
||||||
@@ -1625,8 +1626,9 @@ async function swapSnapshot(record) {
|
|||||||
try {
|
try {
|
||||||
const workflow = app.extensionManager?.workflow?.activeWorkflow;
|
const workflow = app.extensionManager?.workflow?.activeWorkflow;
|
||||||
await app.loadGraphData(record.graphData, true, true, workflow);
|
await app.loadGraphData(record.graphData, true, true, workflow);
|
||||||
lastCapturedHashMap.set(getWorkflowKey(), quickHash(JSON.stringify(record.graphData)));
|
const wfKey = getWorkflowKey();
|
||||||
lastGraphDataMap.set(getWorkflowKey(), record.graphData);
|
lastCapturedHashMap.set(wfKey, quickHash(JSON.stringify(record.graphData)));
|
||||||
|
lastGraphDataMap.set(wfKey, record.graphData);
|
||||||
activeSnapshotId = record.id;
|
activeSnapshotId = record.id;
|
||||||
showToast("Snapshot swapped", "success");
|
showToast("Snapshot swapped", "success");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -2103,6 +2105,12 @@ const CSS = `
|
|||||||
.snap-timeline-marker-current:hover {
|
.snap-timeline-marker-current:hover {
|
||||||
box-shadow: 0 0 6px rgba(16, 185, 129, 0.6);
|
box-shadow: 0 0 6px rgba(16, 185, 129, 0.6);
|
||||||
}
|
}
|
||||||
|
.snap-timeline-marker-latest {
|
||||||
|
box-shadow: 0 0 0 2px rgba(250, 204, 21, 0.5);
|
||||||
|
}
|
||||||
|
.snap-timeline-marker-latest:hover {
|
||||||
|
box-shadow: 0 0 6px rgba(250, 204, 21, 0.6);
|
||||||
|
}
|
||||||
.snap-timeline-snap-btn {
|
.snap-timeline-snap-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: 1px solid var(--descrip-text, #64748b);
|
border: 1px solid var(--descrip-text, #64748b);
|
||||||
@@ -2802,8 +2810,8 @@ async function buildSidebar(el) {
|
|||||||
async function populatePicker() {
|
async function populatePicker() {
|
||||||
pickerList.innerHTML = "";
|
pickerList.innerHTML = "";
|
||||||
const keys = await db_getAllWorkflowKeys();
|
const keys = await db_getAllWorkflowKeys();
|
||||||
const effectiveKey = getEffectiveWorkflowKey();
|
|
||||||
const currentKey = getWorkflowKey();
|
const currentKey = getWorkflowKey();
|
||||||
|
const effectiveKey = viewingWorkflowKey ?? currentKey;
|
||||||
|
|
||||||
if (keys.length === 0) {
|
if (keys.length === 0) {
|
||||||
const empty = document.createElement("div");
|
const empty = document.createElement("div");
|
||||||
@@ -3113,13 +3121,14 @@ async function buildSidebar(el) {
|
|||||||
if (tooltipTimer) { clearTimeout(tooltipTimer); tooltipTimer = null; }
|
if (tooltipTimer) { clearTimeout(tooltipTimer); tooltipTimer = null; }
|
||||||
tooltip.classList.remove("visible");
|
tooltip.classList.remove("visible");
|
||||||
const currentKey = getWorkflowKey();
|
const currentKey = getWorkflowKey();
|
||||||
const effKey = getEffectiveWorkflowKey();
|
const effKey = viewingWorkflowKey ?? currentKey;
|
||||||
const isViewingOther = viewingWorkflowKey != null && viewingWorkflowKey !== currentKey;
|
const isViewingOther = viewingWorkflowKey != null && viewingWorkflowKey !== currentKey;
|
||||||
|
|
||||||
const allRecords = await db_getAllForWorkflow(effKey);
|
const allRecords = await db_getAllForWorkflow(effKey);
|
||||||
|
|
||||||
const regularCount = allRecords.filter(r => r.source !== "node").length;
|
let nodeCount = 0;
|
||||||
const nodeCount = allRecords.filter(r => r.source === "node").length;
|
for (const r of allRecords) if (r.source === "node") nodeCount++;
|
||||||
|
const regularCount = allRecords.length - nodeCount;
|
||||||
countSpan.textContent = nodeCount > 0
|
countSpan.textContent = nodeCount > 0
|
||||||
? `${regularCount}/${maxSnapshots} + ${nodeCount}/${maxNodeSnapshots} node`
|
? `${regularCount}/${maxSnapshots} + ${nodeCount}/${maxNodeSnapshots} node`
|
||||||
: `${regularCount} / ${maxSnapshots}`;
|
: `${regularCount} / ${maxSnapshots}`;
|
||||||
@@ -3550,7 +3559,7 @@ function buildTimeline() {
|
|||||||
canvasParent.appendChild(bar);
|
canvasParent.appendChild(bar);
|
||||||
timelineEl = bar;
|
timelineEl = bar;
|
||||||
|
|
||||||
function buildMarker(rec, { onClickBranch = null } = {}) {
|
function buildMarker(rec, { onClickBranch = null, isLatest = false, activeId = null } = {}) {
|
||||||
const marker = document.createElement("div");
|
const marker = document.createElement("div");
|
||||||
marker.className = "snap-timeline-marker";
|
marker.className = "snap-timeline-marker";
|
||||||
|
|
||||||
@@ -3563,11 +3572,12 @@ function buildTimeline() {
|
|||||||
marker.style.setProperty("--snap-marker-color", "#6d28d9");
|
marker.style.setProperty("--snap-marker-color", "#6d28d9");
|
||||||
}
|
}
|
||||||
if (rec.locked) marker.classList.add("snap-timeline-marker-locked");
|
if (rec.locked) marker.classList.add("snap-timeline-marker-locked");
|
||||||
if (rec.id === activeSnapshotId) marker.classList.add("snap-timeline-marker-active");
|
if (rec.id === activeId) marker.classList.add("snap-timeline-marker-active");
|
||||||
if (rec.id === currentSnapshotId) {
|
if (rec.id === currentSnapshotId) {
|
||||||
marker.classList.add("snap-timeline-marker-current");
|
marker.classList.add("snap-timeline-marker-current");
|
||||||
marker.style.setProperty("--snap-marker-color", "#10b981");
|
marker.style.setProperty("--snap-marker-color", "#10b981");
|
||||||
}
|
}
|
||||||
|
if (isLatest) marker.classList.add("snap-timeline-marker-latest");
|
||||||
|
|
||||||
let tip = `${rec.label} — ${formatTime(rec.timestamp)}\n${iconInfo.label}`;
|
let tip = `${rec.label} — ${formatTime(rec.timestamp)}\n${iconInfo.label}`;
|
||||||
if (rec.notes) tip += `\n${rec.notes}`;
|
if (rec.notes) tip += `\n${rec.notes}`;
|
||||||
@@ -3592,7 +3602,8 @@ function buildTimeline() {
|
|||||||
expandBtn.textContent = "\u25B4";
|
expandBtn.textContent = "\u25B4";
|
||||||
}
|
}
|
||||||
|
|
||||||
const allRecords = await db_getAllForWorkflow(getWorkflowKey());
|
const wfKey = getWorkflowKey();
|
||||||
|
const allRecords = await db_getAllForWorkflow(wfKey);
|
||||||
|
|
||||||
track.innerHTML = "";
|
track.innerHTML = "";
|
||||||
|
|
||||||
@@ -3604,6 +3615,10 @@ function buildTimeline() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute once for all markers
|
||||||
|
const effectiveActiveId = activeSnapshotId ?? lastCapturedIdMap.get(wfKey) ?? null;
|
||||||
|
const latestId = allRecords.reduce((best, r) => (!best || r.timestamp > best.timestamp) ? r : best, null)?.id ?? null;
|
||||||
|
|
||||||
let tree = null;
|
let tree = null;
|
||||||
if (branchingEnabled) {
|
if (branchingEnabled) {
|
||||||
tree = buildSnapshotTree(allRecords);
|
tree = buildSnapshotTree(allRecords);
|
||||||
@@ -3636,6 +3651,8 @@ function buildTimeline() {
|
|||||||
onClickBranch: isActiveBranch ? null : () => {
|
onClickBranch: isActiveBranch ? null : () => {
|
||||||
selectBranchContaining(branchLeafId, tree);
|
selectBranchContaining(branchLeafId, tree);
|
||||||
},
|
},
|
||||||
|
isLatest: rec.id === latestId,
|
||||||
|
activeId: effectiveActiveId,
|
||||||
});
|
});
|
||||||
row.appendChild(marker);
|
row.appendChild(marker);
|
||||||
}
|
}
|
||||||
@@ -3663,7 +3680,7 @@ function buildTimeline() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const rec of records) {
|
for (const rec of records) {
|
||||||
const marker = buildMarker(rec);
|
const marker = buildMarker(rec, { isLatest: rec.id === latestId, activeId: effectiveActiveId });
|
||||||
|
|
||||||
// Fork point: vertical stack — up arrow, marker, down arrow
|
// Fork point: vertical stack — up arrow, marker, down arrow
|
||||||
if (branchingEnabled && forkPointSet.has(rec.id)) {
|
if (branchingEnabled && forkPointSet.has(rec.id)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user