From a057341bbf6666dce8bf9d15a6adfed4230e2dea Mon Sep 17 00:00:00 2001 From: Chris Amow Date: Fri, 28 Aug 2026 15:17:19 -0500 Subject: [PATCH] Put Duplicate on the drawings list; do not require a time under the click. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The handler never ran — no event, no POST. Right-click in empty time also hid the menu. List button uses the selected trendline. --- static/app.js | 28 +++++++++++++++++++++++++++- static/chart.js | 7 ++++--- static/index.html | 1 + 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/static/app.js b/static/app.js index 2eac973..b407c47 100644 --- a/static/app.js +++ b/static/app.js @@ -1112,6 +1112,23 @@ createApp({ return `${base} ${number}`; } + const selectedTrendline = computed(() => { + const id = selectedDrawing.value; + if (!id) return null; + const line = levels.value.find(level => level.id === id); + if (!line || line.kind !== 'manual' || !line.slope) return null; + return line; + }); + + async function duplicateSelected() { + const line = selectedTrendline.value; + if (!line) { + addEvent('DUPLICATE FAILED · select one trendline', 'error'); + return; + } + await duplicateLine(line); + } + async function duplicateLine(line) { const fail = reason => { addEvent(`DUPLICATE #${line.number} FAILED · ${reason}`, 'error'); @@ -1125,6 +1142,15 @@ createApp({ }), }).catch(() => {}); }; + fetch('/api/debug/snap', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + kind: 'duplicate', + tf: line.tf, + note: `start #${line.number} last_t ${line.last_t} cutoff ${line.cutoff_t}`, + }), + }).catch(() => {}); if (line.geometry_resolved === false) { fail('no source geometry'); return; @@ -1656,6 +1682,6 @@ createApp({ window.removeEventListener('keydown', handleKeydown); }); - return { status, price, sessionOpen, quoteChange, animateCurrentPrice, autoScrollLivePrice, extraDetail, hideLowerTfDrawings, confluenceAlerts, barAge, dataUpdatedAt, buildStamp, timeframe, timeframes, drawingColors, drawingColorRows, drawingColorName, colorRowLabels, symbolChoices, selectedSymbol, symbolColor, symbolScale, symbolScales, symbolPanelOpen, prefs, clusters, clustersByPrice, events, eventsMore, loadOlderEvents, diagnosticMode, captureBusy, captureCountdown, captureDiagnostic, armedTool, drawName, drawColor, drawWidth, drawSide, snap, selectedDrawing, selectedDrawings, drawingList, startDrawingListResize, manualLines, hasDrawingSelection, allShownSelected, selectedAreHidden, alertPrice, alertNote, alertEarlyPoints, levelColor, levelWidth, addPriceAlert, armTool, selectTimeframe, allEnabled, toggleGroup, maValue, maAlertOn, toggleMaAlert, deleteSelected, deleteLine, toggleDrawingSelection, activateDrawing, toggleSelectAll, toggleSelectedVisibility, renameLine, updateLineStyle, updateLevelNumber, setArmed, commentText, commentFloat, comments, drawings, filteredDrawings, drawingFilter, drawingKind, drawingTf, deleteDrawing, toggleComment, togglePinned, chooseSymbol, toggleSymbolPanel, startSymbolDrag, dropSymbol, optionExpirations, optionExpiryId, optionSide, optionMode, optionMin, optionMax, optionContracts, optionUnderlying, optionBusy, optionError, optionSearched, optionCopied, onOptionsToggle, searchOptions, copyOption, canUndo, undoTitle, undo }; + return { status, price, sessionOpen, quoteChange, animateCurrentPrice, autoScrollLivePrice, extraDetail, hideLowerTfDrawings, confluenceAlerts, barAge, dataUpdatedAt, buildStamp, timeframe, timeframes, drawingColors, drawingColorRows, drawingColorName, colorRowLabels, symbolChoices, selectedSymbol, symbolColor, symbolScale, symbolScales, symbolPanelOpen, prefs, clusters, clustersByPrice, events, eventsMore, loadOlderEvents, diagnosticMode, captureBusy, captureCountdown, captureDiagnostic, armedTool, drawName, drawColor, drawWidth, drawSide, snap, selectedDrawing, selectedDrawings, drawingList, startDrawingListResize, manualLines, hasDrawingSelection, allShownSelected, selectedAreHidden, selectedTrendline, duplicateSelected, alertPrice, alertNote, alertEarlyPoints, levelColor, levelWidth, addPriceAlert, armTool, selectTimeframe, allEnabled, toggleGroup, maValue, maAlertOn, toggleMaAlert, deleteSelected, deleteLine, toggleDrawingSelection, activateDrawing, toggleSelectAll, toggleSelectedVisibility, renameLine, updateLineStyle, updateLevelNumber, setArmed, commentText, commentFloat, comments, drawings, filteredDrawings, drawingFilter, drawingKind, drawingTf, deleteDrawing, toggleComment, togglePinned, chooseSymbol, toggleSymbolPanel, startSymbolDrag, dropSymbol, optionExpirations, optionExpiryId, optionSide, optionMode, optionMin, optionMax, optionContracts, optionUnderlying, optionBusy, optionError, optionSearched, optionCopied, onOptionsToggle, searchOptions, copyOption, canUndo, undoTitle, undo }; }, }).mount('#app'); diff --git a/static/chart.js b/static/chart.js index dc87c2e..0913190 100644 --- a/static/chart.js +++ b/static/chart.js @@ -569,7 +569,8 @@ class ConfluenceChart { duplicate.textContent = 'Duplicate'; duplicate.addEventListener('click', event => { event.stopPropagation(); - const level = this.levels.find(value => value.id === this.contextLineId); + const id = this.contextLineId || this.selectedLineId; + const level = this.levels.find(value => value.id === id); this.hideContextMenu(); if (level && this.onLineDuplicate) this.onLineDuplicate({ ...level }); }); @@ -3167,9 +3168,9 @@ class ConfluenceChart { if (!level || !this.bars.length) return; event.preventDefault(); const point = this.eventPoint(event); - if (!point || !this.withinPlot(point) || point.t == null) return; + if (!point || !this.withinPlot(point)) return; const lastBar = this.bars[this.bars.length - 1]; - const cutoff = point.t > lastBar.t + const cutoff = point.t == null ? null : point.t > lastBar.t ? Math.round(point.t) : this.bars.reduce((nearest, bar) => Math.abs(bar.t - point.t) < Math.abs(nearest.t - point.t) ? bar : nearest diff --git a/static/index.html b/static/index.html index becc6bb..b21ba99 100644 --- a/static/index.html +++ b/static/index.html @@ -282,6 +282,7 @@ {{ selectedAreHidden ? 'Show' : 'Hide' }} +