terminal: fill Orchestra status footer with live summaries; chart-first order
All checks were successful
deploy / deploy (push) Successful in 7s

Orchestra's bottom legend was just labels — now each status category shows a
brief live readout (live = price·24hΔ, computed = RSI·MACD·ATR, the call =
decision·conf, paper = paper size), still doubling as the colour legend.

Also moved the workspace (chart + Market/Indicators + Committee) to the top so
the chart is the first block; VERDICT + the Orchestra‖Agent-flow row follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zefiroff 2026-06-09 19:31:25 +04:00
parent dbc1b9f705
commit 6ab20d3a72
2 changed files with 66 additions and 27 deletions

View file

@ -1,6 +1,10 @@
import type { CSSProperties } from 'react'
import { cn } from '@/lib/utils'
import type { Direction, Vote } from '@/lib/signals'
/** Brief live summary shown per status category in the footer readout. */
export type OrchestraStatus = { live?: string; computed?: string; call?: string; callTone?: string; paper?: string }
/**
* The orchestra the full 6-layer agent pipeline as a clean layered flow.
* Data Analysis Signal Decision Execution Comms. Each node carries an
@ -136,6 +140,27 @@ function Chip({
)
}
/** One status-category readout in the footer: dot + label + a brief live value. */
function StatusCell({ dotStyle, label, value, tone, dim, loading }: {
dotStyle: CSSProperties; label: string; value?: string; tone?: string; dim?: boolean; loading?: boolean
}) {
return (
<div className={cn('flex items-center gap-2 bg-inset px-3 py-2', dim ? 'opacity-80' : '')}>
<span className="inline-block h-1.5 w-1.5 shrink-0 rounded-full" style={dotStyle} aria-hidden />
<div className="min-w-0">
<div className="label-sm !text-[0.5625rem] !text-faint">{label}</div>
{loading ? (
<div className="skeleton mt-1 h-2.5 w-20" />
) : (
<div className="tnum mt-0.5 truncate font-mono text-[0.625rem] font-medium" style={{ color: tone ?? 'var(--color-bone)' }}>
{value ?? '—'}
</div>
)}
</div>
</div>
)
}
export function Orchestra({
votes,
decision,
@ -144,6 +169,7 @@ export function Orchestra({
onHover,
onInspect,
loading,
status,
gridClass = 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-6',
}: {
votes: Vote[]
@ -153,6 +179,8 @@ export function Orchestra({
onHover?: (a: string | null) => void
onInspect?: (s: string | null) => void
loading?: boolean
/** Brief live summary per status category, shown in the footer readout. */
status?: OrchestraStatus
/** Tailwind grid-cols classes for the layer columns — override when placed side-by-side. */
gridClass?: string
}) {
@ -230,12 +258,12 @@ export function Orchestra({
))}
</div>
{/* legend (status is never colour-only) */}
<div className="flex flex-wrap items-center gap-x-4 gap-y-1.5 border-t border-line-soft px-3 py-2 font-mono text-[0.5625rem] uppercase tracking-[0.06em] text-faint">
<span className="flex items-center gap-1.5"><span className="h-1.5 w-1.5 rounded-full" style={{ background: 'var(--color-bone)' }} /> live</span>
<span className="flex items-center gap-1.5"><span className="h-1.5 w-1.5 rounded-full" style={{ background: 'var(--color-muted)' }} /> computed</span>
<span className="flex items-center gap-1.5"><span className="h-1.5 w-1.5 rounded-full" style={{ background: 'var(--color-accent)' }} /> the call</span>
<span className="flex items-center gap-1.5"><span className="h-1.5 w-1.5 rounded-full opacity-60" style={{ border: '1px solid var(--color-faint)' }} /> paper / planned</span>
{/* status readout — a brief live summary per category (doubles as the legend; never colour-only) */}
<div className="grid grid-cols-2 gap-px border-t border-line-soft bg-line-soft">
<StatusCell dotStyle={{ background: 'var(--color-bone)' }} label="live" value={status?.live} tone="var(--color-bone)" loading={loading} />
<StatusCell dotStyle={{ background: 'var(--color-muted)' }} label="computed" value={status?.computed} tone="var(--color-bone-dim)" loading={loading} />
<StatusCell dotStyle={{ background: 'var(--color-accent)' }} label="the call" value={status?.call} tone={status?.callTone} loading={loading} />
<StatusCell dotStyle={{ border: '1px solid var(--color-faint)' }} label="paper / planned" value={status?.paper} tone="var(--color-faint)" dim loading={loading} />
</div>
</section>
)

View file

@ -416,6 +416,17 @@ export function Terminal() {
const decision = (v?.decision ?? 'SKIP') as Direction
const marketMeta = live ? 'LIVE · HL' : updatedAt > 0 ? 'STALE' : 'CONNECTING…'
// brief live summary per Orchestra status category (live / computed / the call / paper)
const orchestraStatus = {
live: live
? `$${fmtPrice(stat?.mark ?? ta?.price ?? 0)} · ${sign(changePct)}${changePct.toFixed(2)}%`
: updatedAt > 0 ? 'stale feed' : 'connecting…',
computed: ta ? `RSI ${rsi.toFixed(0)} · MACD ${macdHist >= 0 ? '▲' : '▼'} · ATR ${(ta.atrPct ?? 0).toFixed(2)}%` : '—',
call: `${decision} · conf ${Math.round((v?.confidence ?? 0.5) * 100)}%`,
callTone: dirColor(decision),
paper: decision === 'SKIP' ? 'no position · dry-run' : `paper ${decision} ${(v?.sizePct ?? 0).toFixed(1)}%`,
}
const onAgentHover = (a: string | null) => {
setHoverAgent(a)
setInspect(a ? AGENT_RULES[a] ?? null : null)
@ -480,27 +491,7 @@ export function Terminal() {
{guideOpen && <GuideCard onClose={() => setGuideOpen(false)} />}
{/* ── the call (compact) ── */}
<VerdictBar
decision={decision}
confidence={v?.confidence ?? 0.5}
sizePct={v?.sizePct ?? 1}
tpPct={v?.tpPct ?? 2}
slPct={v?.slPct ?? 1.2}
net={net}
votes={votes}
reasoning={v?.reasoning ?? 'Awaiting live market data…'}
live={live}
loading={loading}
/>
{/* ── pipeline row · Orchestra (left) ‖ Agent flow graph (right) ── */}
<div className="grid grid-cols-1 items-stretch gap-2.5 xl:grid-cols-[minmax(0,0.85fr)_minmax(0,1.15fr)]">
<Orchestra votes={votes} decision={decision} activeAgent={activeAgent} onSelect={setSelectedAgent} onHover={onAgentHover} onInspect={setInspect} loading={loading} gridClass="grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 xl:grid-cols-3" />
<AgentWorkflow data={data} decision={decision} activeAgent={activeAgent} onSelect={setSelectedAgent} onHover={onAgentHover} onInspect={setInspect} updatedAt={updatedAt} live={live} loading={loading} />
</div>
{/* ── workspace ── */}
{/* ── workspace · chart with side blocks (moved to top — first) ── */}
<div className="grid items-stretch gap-2.5 lg:grid-cols-[250px_minmax(0,1fr)_330px]">
{/* LEFT — stats + pipeline */}
<div className="space-y-2.5">
@ -539,6 +530,26 @@ export function Terminal() {
</div>
</div>
{/* ── the call (compact) ── */}
<VerdictBar
decision={decision}
confidence={v?.confidence ?? 0.5}
sizePct={v?.sizePct ?? 1}
tpPct={v?.tpPct ?? 2}
slPct={v?.slPct ?? 1.2}
net={net}
votes={votes}
reasoning={v?.reasoning ?? 'Awaiting live market data…'}
live={live}
loading={loading}
/>
{/* ── pipeline row · Orchestra (left) ‖ Agent flow graph (right) ── */}
<div className="grid grid-cols-1 items-stretch gap-2.5 xl:grid-cols-[minmax(0,0.85fr)_minmax(0,1.15fr)]">
<Orchestra votes={votes} decision={decision} status={orchestraStatus} activeAgent={activeAgent} onSelect={setSelectedAgent} onHover={onAgentHover} onInspect={setInspect} loading={loading} gridClass="grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 xl:grid-cols-3" />
<AgentWorkflow data={data} decision={decision} activeAgent={activeAgent} onSelect={setSelectedAgent} onHover={onAgentHover} onInspect={setInspect} updatedAt={updatedAt} live={live} loading={loading} />
</div>
{/* ── live feed · the agent chain streaming in real time ── */}
<LiveFeed data={data} coin={coin} tf={tf} updatedAt={updatedAt} live={live} loading={loading} onInspect={setInspect} />