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; }