Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/live-history-gap-tip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ftw": patch
---

Pulse only the latest known sample in the Live chart. Earlier points before a missing measurement stay still, and an unknown latest sample has no live marker.
6 changes: 3 additions & 3 deletions web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1349,11 +1349,11 @@
ctx.stroke();
ctx.setLineDash([]);

// Cache the right-most point for un-clipped pulse drawing
if (sr.width >= 2) {
});
// Only the current sample may pulse. Earlier segments remain still.
if (sr.width >= 2 && pts.length && sr.data.length === chartHistory.timestamps.length) {
liveTips.push({ x: pts[pts.length - 1].x, y: pts[pts.length - 1].y, color: sr.color });
Comment on lines +1354 to 1355

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate the live marker change in a browser

This changes visible canvas behavior, but the added VM tests only inspect the cached coordinates and never render the marker; the commit also explicitly defers human UI inspection. A human needs to inspect the gap and latest-null states in a browser before accepting this change, since clipping, layering, and marker appearance are not covered by these assertions.

AGENTS.md reference: AGENTS.md:L129-L131

Useful? React with 👍 / 👎.

}
});
});

// ---- Forward-looking forecast (dashed lines from plan) ----
Expand Down
22 changes: 22 additions & 0 deletions web/history-quality.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,25 @@ test('daily view renders unknown home use and a measured grid total',()=>{
assert.match(els['energy-history-insight'].innerHTML,/Incomplete measurements/);
assert.doesNotMatch(els['energy-history-insight'].innerHTML,/%/);
});

function liveTipsFor(data, timestamps=data.map((_,i)=>i)) {
const noop=()=>{};
const ctx={createLinearGradient:()=>({addColorStop:noop}),beginPath:noop,moveTo:noop,lineTo:noop,closePath:noop,fill:noop,stroke:noop,setLineDash:noop};
const c={ctx,series:[{data,color:'#ef4444',width:2}],chartHistory:{timestamps},tsToX:x=>x,valToY:y=>y,pad:{top:0},plotH:100,hexAlpha:()=>''};
vm.createContext(c);
const start=app.indexOf(' var liveTips = [];');
const end=app.indexOf(' // ---- Forward-looking forecast',start);
assert.ok(start>=0 && end>start);
vm.runInContext(app.slice(start,end),c);
return Array.from(c.liveTips,tip=>({x:tip.x,y:tip.y}));
}

test('live chart pulses only the current segment after a measurement gap',()=>{
assert.deepEqual(liveTipsFor([100,200,null,300,400]),[{x:4,y:400}]);
assert.deepEqual(liveTipsFor([100,200,null,300]),[{x:3,y:300}]);
});

test('live chart leaves old endpoints quiet when the latest measurement is unknown',()=>{
assert.deepEqual(liveTipsFor([100,200,null,300,400,null]),[]);
assert.deepEqual(liveTipsFor([100,200],[0,1,2]),[]);
});
Loading