diff --git a/docs/NEXT_STEPS.md b/docs/NEXT_STEPS.md
index f852758..4a805c2 100644
--- a/docs/NEXT_STEPS.md
+++ b/docs/NEXT_STEPS.md
@@ -8,6 +8,24 @@ decisions behind them in `docs/plan.md`.
## Fix next
+### Revisit the trendline live-edge bend with painted-pixel evidence
+
+Production still appears to show a bend where 30m manual lines cross from the
+last real candle into future whitespace. The deployed code is verified to use
+one canvas `LineSeries`, retained transparent time-scale points, and canonical
+source-space prices. Diagnostic values can report unequal historical/future
+screen slopes when the final candles are sparse, but synthetic browser coverage
+passes after gap slots are inserted. The transparent-point production fix did
+not visibly settle the report, so do not add another geometry patch from the
+current theory.
+
+Next investigation should compare the painted canvas pixels around the join
+against the chart API's expected coordinates in the user's production viewport.
+This is the same class of problem where self-consistent API coordinates once
+missed a real overlay offset. Capture the asset commit, final three bar times,
+their logical/x coordinates, the first two future coordinates, canonical line
+prices, and page-pixel samples from the rendered line before changing code.
+
### Deepen freshness telemetry
The status bar now reports when the browser received its latest snapshot or bar.
diff --git a/static/app.js b/static/app.js
index fbcf9bb..13d8120 100644
--- a/static/app.js
+++ b/static/app.js
@@ -233,6 +233,16 @@ createApp({
hour: 'numeric', minute: '2-digit', second: '2-digit',
}).format(new Date(dataReceivedAt.value));
});
+ const build = ref({ commit: '', startedAt: '' });
+ const buildStamp = computed(() => {
+ const commit = (build.value.commit || 'dev').slice(0, 7);
+ if (!build.value.startedAt) return commit;
+ const at = new Date(build.value.startedAt);
+ if (Number.isNaN(at.getTime())) return commit;
+ return `${commit} ${new Intl.DateTimeFormat(undefined, {
+ day: 'numeric', month: 'short', hour: 'numeric', minute: '2-digit',
+ }).format(at)}`;
+ });
const quoteChange = computed(() => {
if (price.value == null || !(sessionOpen.value > 0)) return null;
const points = price.value - sessionOpen.value;
@@ -1164,6 +1174,8 @@ createApp({
await migrateLegacyToken();
await refreshStatus();
await refreshComments();
+ const version = await fetch('/api/version').then(response => response.json()).catch(() => ({}));
+ build.value = { commit: version.commit || 'dev', startedAt: version.started_at || '' };
connect();
timer = setInterval(() => { now.value = Date.now(); refreshStatus(); }, 5000);
});
@@ -1174,6 +1186,6 @@ createApp({
window.removeEventListener('keydown', handleKeydown);
});
- return { status, price, sessionOpen, quoteChange, animateCurrentPrice, autoScrollLivePrice, barAge, dataUpdatedAt, timeframe, timeframes, drawingColors, drawingColorRows, drawingColorName, colorRowLabels, symbolChoices, selectedSymbol, symbolColor, symbolScale, symbolScales, symbolPanelOpen, prefs, clusters, clustersByPrice, events, diagnosticMode, captureBusy, captureDiagnostic, armedTool, drawName, drawColor, drawWidth, drawSide, snap, selectedDrawing, selectedDrawings, manualLines, hasDrawingSelection, allShownSelected, selectedAreHidden, alertPrice, alertNote, alertEarlyPoints, levelColor, levelWidth, addPriceAlert, armTool, selectTimeframe, allEnabled, toggleGroup, deleteSelected, deleteLine, toggleDrawingSelection, toggleSelectAll, toggleSelectedVisibility, renameLine, updateLineStyle, updateLevelNumber, setArmed, commentText, commentFloat, comments, drawings, filteredDrawings, drawingFilter, drawingKind, deleteDrawing, toggleComment, togglePinned, chooseSymbol, toggleSymbolPanel, startSymbolDrag, dropSymbol, optionExpirations, optionExpiryId, optionSide, optionMode, optionMin, optionMax, optionContracts, optionUnderlying, optionBusy, optionError, optionSearched, optionCopied, onOptionsToggle, searchOptions, copyOption };
+ return { status, price, sessionOpen, quoteChange, animateCurrentPrice, autoScrollLivePrice, barAge, dataUpdatedAt, buildStamp, timeframe, timeframes, drawingColors, drawingColorRows, drawingColorName, colorRowLabels, symbolChoices, selectedSymbol, symbolColor, symbolScale, symbolScales, symbolPanelOpen, prefs, clusters, clustersByPrice, events, diagnosticMode, captureBusy, captureDiagnostic, armedTool, drawName, drawColor, drawWidth, drawSide, snap, selectedDrawing, selectedDrawings, manualLines, hasDrawingSelection, allShownSelected, selectedAreHidden, alertPrice, alertNote, alertEarlyPoints, levelColor, levelWidth, addPriceAlert, armTool, selectTimeframe, allEnabled, toggleGroup, deleteSelected, deleteLine, toggleDrawingSelection, toggleSelectAll, toggleSelectedVisibility, renameLine, updateLineStyle, updateLevelNumber, setArmed, commentText, commentFloat, comments, drawings, filteredDrawings, drawingFilter, drawingKind, deleteDrawing, toggleComment, togglePinned, chooseSymbol, toggleSymbolPanel, startSymbolDrag, dropSymbol, optionExpirations, optionExpiryId, optionSide, optionMode, optionMin, optionMax, optionContracts, optionUnderlying, optionBusy, optionError, optionSearched, optionCopied, onOptionsToggle, searchOptions, copyOption };
},
}).mount('#app');
diff --git a/static/index.html b/static/index.html
index cd20dae..d1c358e 100644
--- a/static/index.html
+++ b/static/index.html
@@ -35,7 +35,7 @@
- UPDATED {{ dataUpdatedAt }}
+ BUILD {{ buildStamp }}UPDATED {{ dataUpdatedAt }}