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:
2026-02-27 00:01:55 +01:00
parent 74e1e35e6d
commit 4bfc1912f2

View File

@@ -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)) {