diff --git a/src/operator-dashboard/static/pages/StrategyPage.jsx b/src/operator-dashboard/static/pages/StrategyPage.jsx
index c06bb35..4db4ddf 100644
--- a/src/operator-dashboard/static/pages/StrategyPage.jsx
+++ b/src/operator-dashboard/static/pages/StrategyPage.jsx
@@ -8,11 +8,9 @@ import { formatAgeFromTimestamp, formatBoolean, formatEur, formatTimestamp, trun
const RESPONDED_STATES = new Set(['submitted', 'awaiting_outcome', 'not_filled', 'completed']);
const TRADING_PAIR_MODES = new Set(['maker', 'taker', 'both']);
-const COMPETITIVENESS_REFRESH_MS = 5_000;
const COMPETITIVENESS_GROUP_ROW_COUNT = 12;
const COMPETITIVENESS_DETAIL_ROW_COUNT = 8;
const COMPETITIVENESS_LATENCY_ROW_COUNT = 6;
-const QUOTE_LIFECYCLE_REFRESH_MS = 5_000;
const QUOTE_LIFECYCLE_ROW_COUNT = 20;
async function copyIdentifier(value) {
@@ -24,6 +22,17 @@ async function copyIdentifier(value) {
}
}
+function useNow(intervalMs = 1000) {
+ const [now, setNow] = useState(() => Date.now());
+
+ useEffect(() => {
+ const timer = window.setInterval(() => setNow(Date.now()), intervalMs);
+ return () => window.clearInterval(timer);
+ }, [intervalMs]);
+
+ return now;
+}
+
function formatRelativeAge(value, now) {
const age = formatAgeFromTimestamp(value, now);
return age === 'Unavailable' ? 'Age unavailable' : `${age} ago`;
@@ -274,7 +283,6 @@ function fixedRows(items, count) {
function MakerCompetitivenessSection({ summary, pairConfig }) {
const latestSummaryRef = useRef(summary || {});
const [displaySummary, setDisplaySummary] = useState(() => summary || {});
- const [displayUpdatedAt, setDisplayUpdatedAt] = useState(() => new Date().toISOString());
const [updatesPaused, setUpdatesPaused] = useState(false);
const total = displaySummary?.total || {};
const groups = displaySummary?.groups || [];
@@ -290,23 +298,14 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
useEffect(() => {
latestSummaryRef.current = summary || {};
- }, [summary]);
-
- useEffect(() => {
- if (updatesPaused) return undefined;
- const timer = window.setInterval(() => {
- setDisplaySummary(latestSummaryRef.current || {});
- setDisplayUpdatedAt(new Date().toISOString());
- }, COMPETITIVENESS_REFRESH_MS);
- return () => window.clearInterval(timer);
- }, [updatesPaused]);
+ if (!updatesPaused) setDisplaySummary(summary || {});
+ }, [summary, updatesPaused]);
function toggleUpdatesPaused() {
setUpdatesPaused((current) => {
const nextPaused = !current;
if (current) {
setDisplaySummary(latestSummaryRef.current || {});
- setDisplayUpdatedAt(new Date().toISOString());
}
return nextPaused;
});
@@ -325,7 +324,7 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
0 ? 'warning' : 'unknown'} />
-
+
- Display snapshot {formatTimestamp(displayUpdatedAt)}. Live updates are applied every {COMPETITIVENESS_REFRESH_MS / 1000}s to keep table height stable.
+ Live rows update as they arrive. Fixed row slots and clamped cells keep the table height stable.
@@ -564,23 +563,17 @@ function QuoteLifecycleTable({ items }) {
const latestItemsRef = useRef(items || []);
const [quoteDisplayPaused, setQuoteDisplayPaused] = useState(false);
const [displayItems, setDisplayItems] = useState(() => items || []);
+ const liveNow = useNow();
const [displayNow, setDisplayNow] = useState(() => Date.now());
- const [displayUpdatedAt, setDisplayUpdatedAt] = useState(() => new Date().toISOString());
useEffect(() => {
latestItemsRef.current = items || [];
- }, [items]);
+ if (!quoteDisplayPaused) setDisplayItems(items || []);
+ }, [items, quoteDisplayPaused]);
useEffect(() => {
- if (quoteDisplayPaused) return undefined;
- const timer = window.setInterval(() => {
- const now = Date.now();
- setDisplayItems(latestItemsRef.current || []);
- setDisplayNow(now);
- setDisplayUpdatedAt(new Date(now).toISOString());
- }, QUOTE_LIFECYCLE_REFRESH_MS);
- return () => window.clearInterval(timer);
- }, [quoteDisplayPaused]);
+ if (!quoteDisplayPaused) setDisplayNow(liveNow);
+ }, [liveNow, quoteDisplayPaused]);
const rejectedCount = useMemo(
() => displayItems.filter((item) => isStrategyRejected(item)).length,
@@ -607,10 +600,8 @@ function QuoteLifecycleTable({ items }) {
}
function applyLatestLifecycleDisplay() {
- const now = Date.now();
setDisplayItems(latestItemsRef.current || []);
- setDisplayNow(now);
- setDisplayUpdatedAt(new Date(now).toISOString());
+ setDisplayNow(Date.now());
}
function toggleQuoteDisplayPaused() {
@@ -637,10 +628,10 @@ function QuoteLifecycleTable({ items }) {
>
{quoteDisplayPaused ? 'Resume display' : 'Pause display'}
-
+
- Display snapshot {formatTimestamp(displayUpdatedAt)}. Live rows are applied every {QUOTE_LIFECYCLE_REFRESH_MS / 1000}s to keep table height stable.
+ Live rows update as they arrive. Fixed row slots and clamped cells keep the table height stable.
diff --git a/test/operator-dashboard-ui-static.test.mjs b/test/operator-dashboard-ui-static.test.mjs
index bccc565..57a1510 100644
--- a/test/operator-dashboard-ui-static.test.mjs
+++ b/test/operator-dashboard-ui-static.test.mjs
@@ -27,9 +27,12 @@ test('strategy page owns consolidated quote lifecycle and successful trade table
assert.match(strategySource, /quoteDisplayPaused/);
assert.match(strategySource, /Pause display/);
assert.match(strategySource, /Resume display/);
- assert.match(strategySource, /QUOTE_LIFECYCLE_REFRESH_MS/);
assert.match(strategySource, /QUOTE_LIFECYCLE_ROW_COUNT/);
assert.match(strategySource, /latestItemsRef/);
+ assert.match(strategySource, /setDisplayItems\(items \|\| \[\]\)/);
+ assert.match(strategySource, /useNow\(\)/);
+ assert.match(strategySource, /Live updates/);
+ assert.match(strategySource, /Live rows update as they arrive/);
assert.match(strategySource, /visibleRows/);
assert.match(strategySource, /quote-lifecycle-placeholder-row/);
assert.match(stylesSource, /\.quote-lifecycle-table tbody tr\.quote-lifecycle-row/);
@@ -144,7 +147,8 @@ test('strategy page exposes maker timing waterfall and competitiveness summaries
assert.match(strategySource, /Resume updates/);
assert.match(strategySource, /displaySummary/);
assert.match(strategySource, /fixedRows/);
- assert.match(strategySource, /COMPETITIVENESS_REFRESH_MS/);
+ assert.match(strategySource, /latestSummaryRef/);
+ assert.match(strategySource, /Live updates/);
assert.match(strategySource, /latencyStageRows/);
assert.match(stylesSource, /\.metric-stack/);
assert.match(stylesSource, /\.competitiveness-table-stack/);