From 602da1234ff0ac08c60c2a9f166efbfe8382c51e Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Sun, 12 Apr 2026 07:28:36 +0100 Subject: [PATCH 1/2] [#858] Show live current price on chart + simplify header token box MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chart changes: - Append live current price as rightmost point via usePlotUsdPrice hook - Dashed line from last historical trade to live price (visually distinct) - Price label below chart now reads "Current price · $X.XX" instead of showing the last historical trade price - Footnote clarifies dashed line = live price Header token price box: - Simplified to 2 lines: USD price + "Token Price" label - Removed secondary PLOT-denominated line Co-Authored-By: Claude Opus 4.6 (1M context) --- package.json | 2 +- src/components/PriceChart.tsx | 42 ++++++++++++++++++-------------- src/components/TokenPriceBox.tsx | 3 --- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index b7aac94a..41932f40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plotlink", - "version": "0.1.22", + "version": "0.1.23", "private": true, "workspaces": [ "packages/*" diff --git a/src/components/PriceChart.tsx b/src/components/PriceChart.tsx index 9d14bed9..660a379d 100644 --- a/src/components/PriceChart.tsx +++ b/src/components/PriceChart.tsx @@ -5,6 +5,7 @@ import { useQuery } from "@tanstack/react-query"; import { type Address, formatUnits } from "viem"; import { supabase } from "../../lib/supabase"; import { RESERVE_LABEL } from "../../lib/contracts/constants"; +import { usePlotUsdPrice } from "../hooks/usePlotUsdPrice"; const CHART_W = 320; const CHART_H = 140; @@ -74,6 +75,7 @@ function formatUsdPrice(v: number): string { export function PriceChart({ tokenAddress, currentPriceRaw }: PriceChartProps) { const [mode, setMode] = useState("usd"); const currentPrice = Number(formatUnits(currentPriceRaw, 18)); + const { data: plotUsd } = usePlotUsdPrice(); const { data: tradePoints } = useQuery({ queryKey: ["price-history", tokenAddress], @@ -144,15 +146,26 @@ export function PriceChart({ tokenAddress, currentPriceRaw }: PriceChartProps) { price: effectiveMode === "usd" && usdPrice !== null ? usdPrice : reservePrice, hasUsd: usdPrice !== null, isApprox: t.rate_source === "backfill_approx", + isLive: false as const, }; }); // For USD mode, filter to only points with USD data - const chartPoints = effectiveMode === "usd" + const historicalPoints = effectiveMode === "usd" ? points.filter((p) => p.hasUsd) : points; - if (chartPoints.length === 0) { + // Append live current price as the rightmost chart point + const currentUsdPrice = plotUsd ? currentPrice * plotUsd : null; + const livePrice = effectiveMode === "usd" && currentUsdPrice !== null + ? currentUsdPrice + : currentPrice; + const chartPoints = [ + ...historicalPoints, + { time: new Date().toISOString(), price: livePrice, hasUsd: currentUsdPrice !== null, isApprox: false, isLive: true as const }, + ]; + + if (historicalPoints.length === 0) { // All points filtered out — shouldn't happen, but fallback return (
@@ -181,7 +194,7 @@ export function PriceChart({ tokenAddress, currentPriceRaw }: PriceChartProps) { let currentSegment: { indices: number[]; isApprox: boolean } | null = null; for (let i = 0; i < chartPoints.length; i++) { - const isApprox = chartPoints[i].isApprox; + const isApprox = chartPoints[i].isApprox || chartPoints[i].isLive; if (!currentSegment || currentSegment.isApprox !== isApprox) { // Overlap with previous segment's last point for continuity if (currentSegment && currentSegment.indices.length > 0) { @@ -234,12 +247,6 @@ export function PriceChart({ tokenAddress, currentPriceRaw }: PriceChartProps) { const priceLabel = effectiveMode === "usd" ? "USD" : RESERVE_LABEL; - // In USD mode, check if the last charted point is actually the most recent trade. - // If newer trades exist without USD data, label accordingly. - const lastChartTime = new Date(chartPoints[lastIdx].time).getTime(); - const lastTradeTime = new Date(tradePoints[tradePoints.length - 1].block_timestamp).getTime(); - const isLatest = effectiveMode === "reserve" || lastChartTime >= lastTradeTime; - return (
@@ -340,8 +347,8 @@ export function PriceChart({ tokenAddress, currentPriceRaw }: PriceChartProps) { stroke="var(--accent)" strokeWidth={1.5} strokeLinejoin="round" - strokeDasharray={seg.isApprox && effectiveMode === "usd" ? "4 2" : undefined} - opacity={seg.isApprox && effectiveMode === "usd" ? 0.6 : 1} + strokeDasharray={seg.isApprox ? "4 2" : undefined} + opacity={seg.isApprox ? 0.6 : 1} /> ))} @@ -361,16 +368,15 @@ export function PriceChart({ tokenAddress, currentPriceRaw }: PriceChartProps) {

- Price per token ({priceLabel}) + Current price - {" "}· {isLatest ? "latest" : "last USD"}: {formatPrice(chartPoints[lastIdx].price)} {priceLabel} + {" "}· {formatPrice(livePrice)} {priceLabel}

- {effectiveMode === "usd" && hasApproxData && ( -

- Dashed segments use approximate USD conversion -

- )} +

+ Dashed line = current live price + {effectiveMode === "usd" && hasApproxData && " · approx. USD segments also dashed"} +

); } diff --git a/src/components/TokenPriceBox.tsx b/src/components/TokenPriceBox.tsx index c0f82249..f775d816 100644 --- a/src/components/TokenPriceBox.tsx +++ b/src/components/TokenPriceBox.tsx @@ -18,9 +18,6 @@ export function TokenPriceBox({ pricePerToken }: { pricePerToken: number }) {
{usdPrice !== null ? formatUsdValue(usdPrice) : `${formatPrice(pricePerToken)} ${RESERVE_LABEL}`}
- {usdPrice !== null && ( -
{formatPrice(pricePerToken)} {RESERVE_LABEL}
- )}
Token Price
); From ed71aafe085a3af9b7db713c8fa9460f27fd55bb Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Sun, 12 Apr 2026 07:30:27 +0100 Subject: [PATCH 2/2] [#858] Fix unit mixing: skip live point in USD mode when rate unavailable In USD mode, only append the live current price point when plotUsd is resolved. Prevents plotting a PLOT-denominated value on a USD chart while the rate hook is still loading. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/PriceChart.tsx | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/components/PriceChart.tsx b/src/components/PriceChart.tsx index 660a379d..883c217b 100644 --- a/src/components/PriceChart.tsx +++ b/src/components/PriceChart.tsx @@ -155,15 +155,16 @@ export function PriceChart({ tokenAddress, currentPriceRaw }: PriceChartProps) { ? points.filter((p) => p.hasUsd) : points; - // Append live current price as the rightmost chart point + // Append live current price as the rightmost chart point. + // In USD mode, only append when the live USD rate is available to avoid unit mixing. const currentUsdPrice = plotUsd ? currentPrice * plotUsd : null; - const livePrice = effectiveMode === "usd" && currentUsdPrice !== null - ? currentUsdPrice - : currentPrice; - const chartPoints = [ - ...historicalPoints, - { time: new Date().toISOString(), price: livePrice, hasUsd: currentUsdPrice !== null, isApprox: false, isLive: true as const }, - ]; + const livePrice = effectiveMode === "usd" ? currentUsdPrice : currentPrice; + const chartPoints = livePrice !== null + ? [ + ...historicalPoints, + { time: new Date().toISOString(), price: livePrice, hasUsd: currentUsdPrice !== null, isApprox: false, isLive: true as const }, + ] + : historicalPoints.map((p) => ({ ...p, isLive: false as const })); if (historicalPoints.length === 0) { // All points filtered out — shouldn't happen, but fallback @@ -370,13 +371,15 @@ export function PriceChart({ tokenAddress, currentPriceRaw }: PriceChartProps) {

Current price - {" "}· {formatPrice(livePrice)} {priceLabel} + {" "}· {livePrice !== null ? `${formatPrice(livePrice)} ${priceLabel}` : "loading…"}

-

- Dashed line = current live price - {effectiveMode === "usd" && hasApproxData && " · approx. USD segments also dashed"} -

+ {livePrice !== null && ( +

+ Dashed line = current live price + {effectiveMode === "usd" && hasApproxData && " · approx. USD segments also dashed"} +

+ )}
); }