Stabilize quote lifecycle dashboard
All checks were successful
deploy / deploy (push) Successful in 1m0s
All checks were successful
deploy / deploy (push) Successful in 1m0s
Proof: Quote lifecycle now renders from a timed display snapshot with fixed row slots and clamped variable cells, so live row churn and missing or long fields do not resize the table; static UI coverage, full npm test, and the operator dashboard build validate the cleanup. Assumptions: this is a UI-only operator cleanup for the active maker timing and competitiveness turn; it does not change strategy decisions, execution, DB config, persistence, live funds, or response policy. Still fake: venue-native terminal fill ids and fee-complete realized PnL remain unavailable.
This commit is contained in:
parent
49187379ef
commit
cf4160245f
3 changed files with 184 additions and 85 deletions
|
|
@ -12,6 +12,8 @@ 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) {
|
||||
if (!value || !navigator?.clipboard?.writeText) return;
|
||||
|
|
@ -22,17 +24,6 @@ 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`;
|
||||
|
|
@ -570,18 +561,26 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
|
|||
function QuoteLifecycleTable({ items }) {
|
||||
const [expanded, setExpanded] = useState(() => new Set());
|
||||
const [showStrategyRejected, setShowStrategyRejected] = useState(true);
|
||||
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(() => {
|
||||
if (!quoteDisplayPaused) setDisplayItems(items || []);
|
||||
}, [items, quoteDisplayPaused]);
|
||||
latestItemsRef.current = items || [];
|
||||
}, [items]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!quoteDisplayPaused) setDisplayNow(liveNow);
|
||||
}, [liveNow, quoteDisplayPaused]);
|
||||
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]);
|
||||
|
||||
const rejectedCount = useMemo(
|
||||
() => displayItems.filter((item) => isStrategyRejected(item)).length,
|
||||
|
|
@ -591,6 +590,12 @@ function QuoteLifecycleTable({ items }) {
|
|||
() => (showStrategyRejected ? displayItems : displayItems.filter((item) => !isStrategyRejected(item))),
|
||||
[displayItems, showStrategyRejected],
|
||||
);
|
||||
const visibleRows = fixedRows(visibleItems, QUOTE_LIFECYCLE_ROW_COUNT);
|
||||
const emptyRowsMessage = !displayItems.length
|
||||
? 'No quote lifecycle evidence has been observed yet.'
|
||||
: !visibleItems.length
|
||||
? 'No quote lifecycle rows match the current filters.'
|
||||
: '';
|
||||
|
||||
function toggle(rowKey) {
|
||||
setExpanded((current) => {
|
||||
|
|
@ -601,6 +606,18 @@ function QuoteLifecycleTable({ items }) {
|
|||
});
|
||||
}
|
||||
|
||||
function applyLatestLifecycleDisplay() {
|
||||
const now = Date.now();
|
||||
setDisplayItems(latestItemsRef.current || []);
|
||||
setDisplayNow(now);
|
||||
setDisplayUpdatedAt(new Date(now).toISOString());
|
||||
}
|
||||
|
||||
function toggleQuoteDisplayPaused() {
|
||||
if (quoteDisplayPaused) applyLatestLifecycleDisplay();
|
||||
setQuoteDisplayPaused((paused) => !paused);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="quote-lifecycle-controls">
|
||||
|
|
@ -615,82 +632,95 @@ function QuoteLifecycleTable({ items }) {
|
|||
<button
|
||||
aria-pressed={quoteDisplayPaused}
|
||||
className="button secondary"
|
||||
onClick={() => setQuoteDisplayPaused((paused) => !paused)}
|
||||
onClick={toggleQuoteDisplayPaused}
|
||||
type="button"
|
||||
>
|
||||
{quoteDisplayPaused ? 'Resume display' : 'Pause display'}
|
||||
</button>
|
||||
{quoteDisplayPaused ? <Pill label="Display paused" stateLabel="warning" /> : null}
|
||||
<Pill label={quoteDisplayPaused ? 'Display paused' : 'Timed snapshot'} stateLabel={quoteDisplayPaused ? 'warning' : 'healthy'} />
|
||||
</div>
|
||||
<div className="status-subtle quote-lifecycle-snapshot-note">
|
||||
Display snapshot {formatTimestamp(displayUpdatedAt)}. Live rows are applied every {QUOTE_LIFECYCLE_REFRESH_MS / 1000}s to keep table height stable.
|
||||
</div>
|
||||
|
||||
{!displayItems.length ? (
|
||||
<EmptyState>No quote lifecycle evidence has been observed yet.</EmptyState>
|
||||
) : !visibleItems.length ? (
|
||||
<EmptyState>No quote lifecycle rows match the current filters.</EmptyState>
|
||||
) : (
|
||||
<TableFrame>
|
||||
<table className="quote-lifecycle-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Quote time</th>
|
||||
<th>Quote id</th>
|
||||
<th>Request</th>
|
||||
<th>Responded?</th>
|
||||
<th>Result</th>
|
||||
<th>Reason</th>
|
||||
<th>Gross edge / notional</th>
|
||||
<th>Lifecycle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{visibleItems.map((item, index) => {
|
||||
const rowKey = item.quote_id || item.decision_id || item.command_id || item.latest_stage_at || String(index);
|
||||
const isExpanded = expanded.has(rowKey);
|
||||
const quoteTime = item.quote_activity_at || item.latest_stage_at;
|
||||
<TableFrame className="quote-lifecycle-frame">
|
||||
<table className="quote-lifecycle-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Quote time</th>
|
||||
<th>Quote id</th>
|
||||
<th>Request</th>
|
||||
<th>Responded?</th>
|
||||
<th>Result</th>
|
||||
<th>Reason</th>
|
||||
<th>Gross edge / notional</th>
|
||||
<th>Lifecycle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{visibleRows.map((item, index) => {
|
||||
if (!item) {
|
||||
return (
|
||||
<Fragment key={rowKey}>
|
||||
<tr className={item.live_flash_at ? 'quote-row-flash' : undefined} key={`${rowKey}:row`}>
|
||||
<td>
|
||||
<div>{formatTimestamp(quoteTime)}</div>
|
||||
<div className="status-subtle quote-age">{formatRelativeAge(quoteTime, displayNow)}</div>
|
||||
{item.latest_stage_at && item.latest_stage_at !== item.quote_activity_at ? (
|
||||
<div className="status-subtle">Updated {formatTimestamp(item.latest_stage_at)} · {formatRelativeAge(item.latest_stage_at, displayNow)}</div>
|
||||
) : null}
|
||||
</td>
|
||||
<td><IdentifierRow label="Quote" value={item.quote_id} /></td>
|
||||
<td>
|
||||
<div>{formatTerms(item.request_terms || item.submitted_terms)}</div>
|
||||
<div className="status-subtle mono">{truncateMiddle(item.pair || '', 34)}</div>
|
||||
</td>
|
||||
<td>{responseLabel(item)}</td>
|
||||
<td><Pill label={item.lifecycle_label} stateLabel={item.lifecycle_tone} /></td>
|
||||
<td>
|
||||
<div>{item.reason_text}</div>
|
||||
<div className="status-subtle mono">{item.reason_code || 'reason_unknown'}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className={Number(item.gross_edge_pct) > 0 ? 'value-positive' : Number(item.gross_edge_pct) < 0 ? 'value-negative' : ''}>{formatGrossEdgePct(item.gross_edge_pct)}</div>
|
||||
<div className="status-subtle">{formatConfiguredEdgeBps(item.edge_bps)}</div>
|
||||
<div className="status-subtle">{notionalLabel(item)}</div>
|
||||
</td>
|
||||
<td>
|
||||
<button className="button secondary" onClick={() => toggle(rowKey)} type="button">
|
||||
{isExpanded ? 'Hide lifecycle' : 'Show lifecycle'}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{isExpanded ? (
|
||||
<tr className="lifecycle-expanded-row" key={`${rowKey}:details`}>
|
||||
<td colSpan={8}><LifecycleDetails item={item} /></td>
|
||||
</tr>
|
||||
) : null}
|
||||
</Fragment>
|
||||
<tr className="quote-lifecycle-placeholder-row" key={`quote-lifecycle-placeholder:${index}`}>
|
||||
<td colSpan={8}>{index === 0 ? emptyRowsMessage : ''}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</TableFrame>
|
||||
)}
|
||||
}
|
||||
const rowKey = item.quote_id || item.decision_id || item.command_id || item.latest_stage_at || String(index);
|
||||
const isExpanded = expanded.has(rowKey);
|
||||
const quoteTime = item.quote_activity_at || item.latest_stage_at;
|
||||
const updatedText = item.latest_stage_at && item.latest_stage_at !== item.quote_activity_at
|
||||
? `Updated ${formatTimestamp(item.latest_stage_at)} - ${formatRelativeAge(item.latest_stage_at, displayNow)}`
|
||||
: '';
|
||||
return (
|
||||
<Fragment key={rowKey}>
|
||||
<tr className={`quote-lifecycle-row${item.live_flash_at ? ' quote-row-flash' : ''}`} key={`${rowKey}:row`}>
|
||||
<td>
|
||||
<div className="quote-lifecycle-cell">
|
||||
<div className="lifecycle-line lifecycle-clamp-one">{formatTimestamp(quoteTime)}</div>
|
||||
<div className="status-subtle quote-age lifecycle-line lifecycle-clamp-one">{formatRelativeAge(quoteTime, displayNow)}</div>
|
||||
<div className="status-subtle lifecycle-line lifecycle-clamp-two">{updatedText}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><IdentifierRow label="Quote" value={item.quote_id} /></td>
|
||||
<td>
|
||||
<div className="quote-lifecycle-cell">
|
||||
<div className="lifecycle-line lifecycle-clamp-two">{formatTerms(item.request_terms || item.submitted_terms)}</div>
|
||||
<div className="status-subtle mono lifecycle-line lifecycle-clamp-one">{truncateMiddle(item.pair || '', 34)}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{responseLabel(item)}</td>
|
||||
<td><Pill label={item.lifecycle_label} stateLabel={item.lifecycle_tone} /></td>
|
||||
<td>
|
||||
<div className="quote-lifecycle-cell">
|
||||
<div className="lifecycle-line lifecycle-clamp-two">{item.reason_text}</div>
|
||||
<div className="status-subtle mono lifecycle-line lifecycle-clamp-one">{item.reason_code || 'reason_unknown'}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="quote-lifecycle-cell">
|
||||
<div className={`lifecycle-line lifecycle-clamp-one ${Number(item.gross_edge_pct) > 0 ? 'value-positive' : Number(item.gross_edge_pct) < 0 ? 'value-negative' : ''}`}>{formatGrossEdgePct(item.gross_edge_pct)}</div>
|
||||
<div className="status-subtle lifecycle-line lifecycle-clamp-one">{formatConfiguredEdgeBps(item.edge_bps)}</div>
|
||||
<div className="status-subtle lifecycle-line lifecycle-clamp-one">{notionalLabel(item)}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button className="button secondary" onClick={() => toggle(rowKey)} type="button">
|
||||
{isExpanded ? 'Hide lifecycle' : 'Show lifecycle'}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{isExpanded ? (
|
||||
<tr className="lifecycle-expanded-row" key={`${rowKey}:details`}>
|
||||
<td colSpan={8}><LifecycleDetails item={item} /></td>
|
||||
</tr>
|
||||
) : null}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</TableFrame>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -616,6 +616,10 @@ table.lifecycle-table th:nth-child(5) {
|
|||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.quote-lifecycle-snapshot-note {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.toggle-field {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -640,6 +644,63 @@ table.lifecycle-table th:nth-child(5) {
|
|||
width: 150px;
|
||||
}
|
||||
|
||||
.quote-lifecycle-table tbody tr.quote-lifecycle-row,
|
||||
.quote-lifecycle-table tbody tr.quote-lifecycle-placeholder-row {
|
||||
height: 112px;
|
||||
}
|
||||
|
||||
.quote-lifecycle-table td {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quote-lifecycle-table .trace-row {
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.quote-lifecycle-table .trace-id {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.quote-lifecycle-table .pill {
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.quote-lifecycle-placeholder-row td {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.quote-lifecycle-cell {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.lifecycle-line {
|
||||
min-height: 1.35em;
|
||||
}
|
||||
|
||||
.lifecycle-clamp-one {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lifecycle-clamp-two {
|
||||
display: -webkit-box;
|
||||
max-height: 2.7em;
|
||||
overflow: hidden;
|
||||
overflow-wrap: anywhere;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.quote-age {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,15 @@ 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, /setDisplayItems\(items \|\| \[\]\)/);
|
||||
assert.match(strategySource, /QUOTE_LIFECYCLE_REFRESH_MS/);
|
||||
assert.match(strategySource, /QUOTE_LIFECYCLE_ROW_COUNT/);
|
||||
assert.match(strategySource, /latestItemsRef/);
|
||||
assert.match(strategySource, /visibleRows/);
|
||||
assert.match(strategySource, /quote-lifecycle-placeholder-row/);
|
||||
assert.match(stylesSource, /\.quote-lifecycle-table tbody tr\.quote-lifecycle-row/);
|
||||
assert.match(stylesSource, /\.quote-lifecycle-placeholder-row td/);
|
||||
assert.match(stylesSource, /\.lifecycle-clamp-one/);
|
||||
assert.match(stylesSource, /\.lifecycle-clamp-two/);
|
||||
assert.match(strategySource, /item\.execution\?\.error_message/);
|
||||
assert.match(strategySource, /item\.execution\?\.note/);
|
||||
assert.match(strategySource, /formatExecutionTiming/);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue