From 7ce6d33bb5749cbd5bd704b490f0efdd65490ba0 Mon Sep 17 00:00:00 2001 From: Chris Amow Date: Mon, 10 Aug 2026 22:35:40 -0500 Subject: [PATCH] Show the price the anchor will use, and record where things run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The snap dot said where an anchor would land but not what it was, which made "the dot is in the wrong place" and "the dot is on the wrong bar" impossible to tell apart from a screenshot. It now carries a label with the price, whether it is a high or a low, and the bar's time. AGENTS.md gains the environment fact this session kept rediscovering: the agent runs on a remote machine over SSH while the user's browser runs on another, so headless runs here render on different hardware at a different size and pixel ratio, and "it passes in my headless run" is not evidence the user's problem is fixed. Three consecutive reproductions passed server-side while the chart was unusable on the user's screen. When a visual bug will not reproduce, match their viewport and deviceScaleFactor explicitly — and prefer putting the numbers on screen over asking for another console paste. Device pixel ratio is ruled out for the current trendline complaint: the bottom-sweep check lands 115 of 115 at 1600x1000 and at 1900x1400 with ratios of 1, 1.25, 1.5 and 2. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 19 +++++++++++++++++++ static/chart.js | 21 +++++++++++++++++++++ static/style.css | 1 + 3 files changed, 41 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index aab8eef..a49d61b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,6 +21,25 @@ level, a tick must never overwrite a settled bar, volume must be counted once. Name the test after the failure, not the function: `test_a_tick_cannot_overwrite _a_settled_bar` beats `test_put`. +## Where things run + +**The agent works on a remote machine over SSH. The user's browser runs on a +different machine.** Consequences, all learned the hard way: + +- You cannot see the user's screen, console, or cursor. Screenshots and pasted + console output are the only window into it. Browser extensions that drive + "your" Chrome do not help — they attach to the machine the browser is on. +- Headless Chromium here renders on server hardware: different screen, window + size and device pixel ratio from the user's. "Works in my headless run" is not + evidence that it works for them. When a UI bug will not reproduce, match their + viewport and `deviceScaleFactor` explicitly before concluding anything. +- The dev stack is served to them over the network (e.g. `hera.local:8010`), + which is the same app the headless browser reaches as `http://api:8000`. + +When a visual bug resists reproduction, prefer putting the numbers **on screen** +in the app over asking for another console paste — one screenshot then carries +the whole diagnosis. + ## Verify UI in a real browser Chart bugs are invisible from the outside — the API, the socket and the diff --git a/static/chart.js b/static/chart.js index 1d6dcc4..a389fd2 100644 --- a/static/chart.js +++ b/static/chart.js @@ -34,6 +34,7 @@ class ConfluenceChart { this.toolUpListener = null; this.pendingView = null; this.snapDot = null; + this.snapLabel = null; this.comments = []; this.commentNodes = new Map(); this.commentLayer = null; @@ -180,6 +181,13 @@ class ConfluenceChart { this.snapDot.className = 'chart-snap-dot'; this.snapDot.hidden = true; el.appendChild(this.snapDot); + // States the price the anchor will use. Useful in itself, and it means a + // single screenshot answers "is the dot in the wrong place, or on the + // wrong bar?" without anyone pasting console output. + this.snapLabel = document.createElement('div'); + this.snapLabel.className = 'chart-snap-label'; + this.snapLabel.hidden = true; + el.appendChild(this.snapLabel); this.commentLayer = document.createElement('div'); this.commentLayer.className = 'chart-comments'; el.appendChild(this.commentLayer); @@ -593,10 +601,23 @@ class ConfluenceChart { this.snapDot.style.top = `${Math.round(y)}px`; // Says which it is before you commit, since the extreme decides the side. this.snapDot.dataset.side = snapped.snappedSide || ''; + + const side = snapped.snappedSide === 'resistance' ? 'H' : snapped.snappedSide === 'support' ? 'L' : ''; + const clock = new Date(snapped.t * 1000) + .toLocaleTimeString(undefined, { hour12: false, hour: '2-digit', minute: '2-digit' }); + this.snapLabel.hidden = false; + this.snapLabel.dataset.side = snapped.snappedSide || ''; + this.snapLabel.textContent = `${snapped.p.toFixed(2)}${side ? ' ' + side : ''} · ${clock}`; + // Flipped to the left near the right-hand edge so it never leaves the pane. + const flip = x > this.chartEl.clientWidth - 120; + this.snapLabel.style.left = `${Math.round(flip ? x - 12 : x + 12)}px`; + this.snapLabel.style.top = `${Math.round(y)}px`; + this.snapLabel.style.transform = flip ? 'translate(-100%, -50%)' : 'translate(0, -50%)'; } hideSnapDot() { if (this.snapDot) this.snapDot.hidden = true; + if (this.snapLabel) this.snapLabel.hidden = true; } renderPending(a, b) { diff --git a/static/style.css b/static/style.css index d781f15..b4651b5 100644 --- a/static/style.css +++ b/static/style.css @@ -78,3 +78,4 @@ aside { padding:16px; }h2 { margin:0 0 12px; color:var(--muted); font-size:11px; border:1px solid var(--line); border-radius:5px; background:transparent; color:var(--fg); } .side-auto { align-self:end; padding-bottom:6px; font-size:10px; color:var(--muted); }.side-auto b { color:var(--fg); font-weight:600; } .chart-snap-dot { position:absolute; width:9px; height:9px; margin:-5px 0 0 -5px; border-radius:50%; border:2px solid var(--accent); background:var(--chart-bg); pointer-events:none; z-index:5; }.chart-snap-dot[data-side=resistance] { border-color:#bd4545; }.chart-snap-dot[data-side=support] { border-color:#27825c; } +.chart-snap-label { position:absolute; padding:2px 5px; border-radius:3px; pointer-events:none; z-index:6; font-size:10px; white-space:nowrap; background:var(--chart-bg); border:1px solid var(--muted); color:var(--fg); }.chart-snap-label[data-side=resistance] { border-color:#bd4545; }.chart-snap-label[data-side=support] { border-color:#27825c; }