diag delay so we can solve trendline slope problem
This commit is contained in:
parent
01acdce045
commit
693d0873f8
5 changed files with 34 additions and 8 deletions
|
|
@ -102,6 +102,10 @@ metadata remain authenticated. Inspect only a URL the user explicitly shares,
|
||||||
then immediately `DELETE /api/debug/captures/{id}`. The server also expires
|
then immediately `DELETE /api/debug/captures/{id}`. The server also expires
|
||||||
captures after 24 hours and caps the directory at 50 files.
|
captures after 24 hours and caps the directory at 50 files.
|
||||||
|
|
||||||
|
After the browser's required share picker closes, capture waits five seconds so
|
||||||
|
the user can restore a hover tooltip. `Alt+Shift+C` starts the same delayed flow
|
||||||
|
without clicking the status-bar button.
|
||||||
|
|
||||||
Diagnostic mode also shows a compact projection readout for visible manual
|
Diagnostic mode also shows a compact projection readout for visible manual
|
||||||
trendlines: historical/future canonical price changes, their screen slopes, and
|
trendlines: historical/future canonical price changes, their screen slopes, and
|
||||||
whether the future canvas point exists. Include it in a capture when a line
|
whether the future canvas point exists. Include it in a capture when a line
|
||||||
|
|
|
||||||
|
|
@ -697,6 +697,12 @@ inspect the pixels. `DELETE /api/debug/captures/{id}` is public too, so an agent
|
||||||
can remove an inspected screenshot immediately. The existing 24-hour expiry and
|
can remove an inspected screenshot immediately. The existing 24-hour expiry and
|
||||||
50-capture cap remain the backstop.
|
50-capture cap remain the backstop.
|
||||||
|
|
||||||
|
The screen-share picker steals focus, so an immediate frame cannot contain a
|
||||||
|
hover tooltip. Capture now waits five seconds after the picker closes and shows
|
||||||
|
the countdown in the status bar; the user can move back over the target before
|
||||||
|
zero. `Alt+Shift+C` starts the same flow from the keyboard, though the picker is
|
||||||
|
still mandatory browser security.
|
||||||
|
|
||||||
The public image and cleanup handlers live in `meta.py`, the deliberately
|
The public image and cleanup handlers live in `meta.py`, the deliberately
|
||||||
unauthenticated router. Capture creation and metadata remain in `routes.py`,
|
unauthenticated router. Capture creation and metadata remain in `routes.py`,
|
||||||
behind the normal chart authentication.
|
behind the normal chart authentication.
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,7 @@ createApp({
|
||||||
const drawingTf = ref('all');
|
const drawingTf = ref('all');
|
||||||
const diagnosticMode = ref(ConfluenceChart.diagnosticMode());
|
const diagnosticMode = ref(ConfluenceChart.diagnosticMode());
|
||||||
const captureBusy = ref(false);
|
const captureBusy = ref(false);
|
||||||
|
const captureCountdown = ref(0);
|
||||||
const snap = ref(true);
|
const snap = ref(true);
|
||||||
const selectedDrawing = ref(null);
|
const selectedDrawing = ref(null);
|
||||||
const selectedDrawings = ref([]);
|
const selectedDrawings = ref([]);
|
||||||
|
|
@ -630,6 +631,12 @@ createApp({
|
||||||
if (video.requestVideoFrameCallback) video.requestVideoFrameCallback(() => resolve());
|
if (video.requestVideoFrameCallback) video.requestVideoFrameCallback(() => resolve());
|
||||||
else requestAnimationFrame(() => resolve());
|
else requestAnimationFrame(() => resolve());
|
||||||
});
|
});
|
||||||
|
for (let seconds = 5; seconds > 0; seconds -= 1) {
|
||||||
|
captureCountdown.value = seconds;
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
}
|
||||||
|
captureCountdown.value = 0;
|
||||||
|
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = video.videoWidth;
|
canvas.width = video.videoWidth;
|
||||||
canvas.height = video.videoHeight;
|
canvas.height = video.videoHeight;
|
||||||
|
|
@ -671,6 +678,7 @@ createApp({
|
||||||
addEvent(cancelled ? 'CAPTURE CANCELLED' : `CAPTURE FAILED · ${error.message}`, 'capture-error');
|
addEvent(cancelled ? 'CAPTURE CANCELLED' : `CAPTURE FAILED · ${error.message}`, 'capture-error');
|
||||||
} finally {
|
} finally {
|
||||||
stream?.getTracks().forEach(track => track.stop());
|
stream?.getTracks().forEach(track => track.stop());
|
||||||
|
captureCountdown.value = 0;
|
||||||
captureBusy.value = false;
|
captureBusy.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1306,6 +1314,12 @@ createApp({
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeydown(event) {
|
function handleKeydown(event) {
|
||||||
|
if (diagnosticMode.value && event.altKey && event.shiftKey
|
||||||
|
&& event.code === 'KeyC' && !captureBusy.value) {
|
||||||
|
event.preventDefault();
|
||||||
|
captureDiagnostic();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
const palettes = [...document.querySelectorAll('.color-picker[open]')];
|
const palettes = [...document.querySelectorAll('.color-picker[open]')];
|
||||||
palettes.forEach(palette => { palette.open = false; });
|
palettes.forEach(palette => { palette.open = false; });
|
||||||
|
|
@ -1542,6 +1556,6 @@ createApp({
|
||||||
window.removeEventListener('keydown', handleKeydown);
|
window.removeEventListener('keydown', handleKeydown);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { status, price, sessionOpen, quoteChange, animateCurrentPrice, autoScrollLivePrice, extraDetail, hideLowerTfDrawings, barAge, dataUpdatedAt, buildStamp, timeframe, timeframes, drawingColors, drawingColorRows, drawingColorName, colorRowLabels, symbolChoices, selectedSymbol, symbolColor, symbolScale, symbolScales, symbolPanelOpen, prefs, clusters, clustersByPrice, events, eventsMore, loadOlderEvents, diagnosticMode, captureBusy, 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, 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 };
|
||||||
},
|
},
|
||||||
}).mount('#app');
|
}).mount('#app');
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
<span v-else-if="armedTool === 'fibonacci'" class="arm-hint">Drag from one swing to the other</span>
|
<span v-else-if="armedTool === 'fibonacci'" class="arm-hint">Drag from one swing to the other</span>
|
||||||
<span v-else-if="selectedDrawing" class="arm-hint">Drawing selected — Delete removes it</span>
|
<span v-else-if="selectedDrawing" class="arm-hint">Drawing selected — Delete removes it</span>
|
||||||
<button v-if="diagnosticMode" class="diag-capture" :disabled="captureBusy" @click="captureDiagnostic">
|
<button v-if="diagnosticMode" class="diag-capture" :disabled="captureBusy" @click="captureDiagnostic">
|
||||||
{{ captureBusy ? 'CAPTURING…' : 'CAPTURE DIAGNOSTIC' }}
|
{{ captureCountdown ? `CAPTURE IN ${captureCountdown}…` : captureBusy ? 'CAPTURING…' : 'CAPTURE DIAGNOSTIC' }}
|
||||||
</button>
|
</button>
|
||||||
<span class="data-freshness"><span>BUILD <b>{{ buildStamp }}</b></span><span>UPDATED <b>{{ dataUpdatedAt }}</b></span></span>
|
<span class="data-freshness"><span>BUILD <b>{{ buildStamp }}</b></span><span>UPDATED <b>{{ dataUpdatedAt }}</b></span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -72,10 +72,7 @@ test('an existing browser token is migrated once and removed from local storage'
|
||||||
});
|
});
|
||||||
|
|
||||||
test('diagnostic capture uploads a PNG and adds its capability ID to Events',
|
test('diagnostic capture uploads a PNG and adds its capability ID to Events',
|
||||||
{
|
{ timeout: 180000 }, async () => {
|
||||||
timeout: 180000,
|
|
||||||
skip: 'quarantined: display-media mocks and async upload completion are not yet reliable',
|
|
||||||
}, async () => {
|
|
||||||
const { browser, page } = await launch();
|
const { browser, page } = await launch();
|
||||||
let uploaded = null;
|
let uploaded = null;
|
||||||
try {
|
try {
|
||||||
|
|
@ -113,7 +110,10 @@ test('diagnostic capture uploads a PNG and adds its capability ID to Events',
|
||||||
});
|
});
|
||||||
HTMLMediaElement.prototype.play = async function play() {};
|
HTMLMediaElement.prototype.play = async function play() {};
|
||||||
HTMLVideoElement.prototype.requestVideoFrameCallback = function callback(done) { done(); };
|
HTMLVideoElement.prototype.requestVideoFrameCallback = function callback(done) { done(); };
|
||||||
HTMLCanvasElement.prototype.getContext = () => ({ drawImage() {} });
|
const realGetContext = HTMLCanvasElement.prototype.getContext;
|
||||||
|
HTMLCanvasElement.prototype.getContext = function getContext(...args) {
|
||||||
|
return this.closest('#chart') ? realGetContext.apply(this, args) : { drawImage() {} };
|
||||||
|
};
|
||||||
HTMLCanvasElement.prototype.toBlob = function toBlob(done) {
|
HTMLCanvasElement.prototype.toBlob = function toBlob(done) {
|
||||||
done(new Blob([new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10])], {
|
done(new Blob([new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10])], {
|
||||||
type: 'image/png',
|
type: 'image/png',
|
||||||
|
|
@ -121,7 +121,9 @@ test('diagnostic capture uploads a PNG and adds its capability ID to Events',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.locator('.diag-capture').click();
|
await page.keyboard.press('Alt+Shift+C');
|
||||||
|
await page.waitForFunction(() =>
|
||||||
|
document.querySelector('.diag-capture')?.textContent.includes('CAPTURE IN'));
|
||||||
await page.waitForFunction(() =>
|
await page.waitForFunction(() =>
|
||||||
[...document.querySelectorAll('.alert-entry')]
|
[...document.querySelectorAll('.alert-entry')]
|
||||||
.some(entry => entry.textContent.includes('c-TESTCAPTURE1')));
|
.some(entry => entry.textContent.includes('c-TESTCAPTURE1')));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue