World: shared HUD/legend — what you're looking at + live counts
A small bottom-left overlay on all three views: the view title + a one-line key (Live = Gource tree, Flat = 2D graph, Hierarchy = brain mesh) plus live counts — agents, currently-working, tokens/min and pending doors (from the telemetry feed). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
05cf132957
commit
c5daf5925c
@@ -16,7 +16,7 @@ import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
|
|||||||
import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass.js";
|
import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass.js";
|
||||||
|
|
||||||
import type { TaxonomyEvents } from "@/lib/live/taxonomy";
|
import type { TaxonomyEvents } from "@/lib/live/taxonomy";
|
||||||
import { useClawmatesLive } from "@/lib/live/useClawmatesLive";
|
import { useClawmatesLive, useLiveState } from "@/lib/live/useClawmatesLive";
|
||||||
|
|
||||||
import { WorldFlow, type WorldItem } from "../dashboard/flow/WorldFlow";
|
import { WorldFlow, type WorldItem } from "../dashboard/flow/WorldFlow";
|
||||||
|
|
||||||
@@ -107,6 +107,17 @@ const FORMATIONS: { id: Formation; label: string }[] = [
|
|||||||
|
|
||||||
const mono = "'Geist Mono', ui-monospace, monospace";
|
const mono = "'Geist Mono', ui-monospace, monospace";
|
||||||
|
|
||||||
|
const VIEW_TITLE: Record<Formation, string> = {
|
||||||
|
live: "LIVE — GOURCE",
|
||||||
|
flat: "FLAT — 2D GRAPH",
|
||||||
|
hierarchy: "HIERARCHY — BRAIN",
|
||||||
|
};
|
||||||
|
const VIEW_LEGEND: Record<Formation, string> = {
|
||||||
|
live: "org▸team tree · agents converge on the tool/file they touch and beam it",
|
||||||
|
flat: "the 2D graph of org ▸ company ▸ team ▸ agents",
|
||||||
|
hierarchy: "agents are neurons in a mesh; pathways fire as they work",
|
||||||
|
};
|
||||||
|
|
||||||
export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExpand, onOpenRuns }: WorldCanvasProps) {
|
export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExpand, onOpenRuns }: WorldCanvasProps) {
|
||||||
const mountRef = useRef<HTMLDivElement>(null);
|
const mountRef = useRef<HTMLDivElement>(null);
|
||||||
const labelLayerRef = useRef<HTMLDivElement>(null);
|
const labelLayerRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -117,6 +128,12 @@ export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExp
|
|||||||
const [speedMult, setSpeedMult] = useState(1);
|
const [speedMult, setSpeedMult] = useState(1);
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
const replayHours = 24;
|
const replayHours = 24;
|
||||||
|
const [hud, setHud] = useState({ agents: 0, active: 0 });
|
||||||
|
const telemetry = useLiveState<"telemetry", { tokensPerMin?: number; doorsPending?: number; loops?: number }>(
|
||||||
|
"telemetry",
|
||||||
|
(d) => d,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
const onSelectRef = useRef(onSelect);
|
const onSelectRef = useRef(onSelect);
|
||||||
const selectedRef = useRef(selectedId);
|
const selectedRef = useRef(selectedId);
|
||||||
@@ -143,6 +160,18 @@ export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExp
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// poll the engine for the HUD counts (agents + currently working)
|
||||||
|
useEffect(() => {
|
||||||
|
const id = setInterval(() => {
|
||||||
|
const e = engineRef.current;
|
||||||
|
if (!e) return;
|
||||||
|
let active = 0;
|
||||||
|
for (const p of e.pawns.values()) if (p.status === "working") active += 1;
|
||||||
|
setHud({ agents: e.pawns.size, active });
|
||||||
|
}, 600);
|
||||||
|
return () => clearInterval(id);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// wire the live feed into the engine (only in live mode — replay drives it instead)
|
// wire the live feed into the engine (only in live mode — replay drives it instead)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const e = engineRef.current;
|
const e = engineRef.current;
|
||||||
@@ -869,6 +898,37 @@ export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExp
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
{/* HUD — what this view is + live counts */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 14,
|
||||||
|
left: 16,
|
||||||
|
zIndex: 30,
|
||||||
|
maxWidth: 240,
|
||||||
|
padding: "9px 12px",
|
||||||
|
borderRadius: 10,
|
||||||
|
background: "rgba(10,10,14,.72)",
|
||||||
|
border: "1px solid rgba(255,255,255,.08)",
|
||||||
|
backdropFilter: "blur(8px)",
|
||||||
|
fontFamily: mono,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: 11, letterSpacing: ".08em", color: "#ff8a7a", fontWeight: 700 }}>{VIEW_TITLE[formation]}</div>
|
||||||
|
<div style={{ fontSize: 10, color: "#8a8a92", marginTop: 3, lineHeight: 1.5 }}>{VIEW_LEGEND[formation]}</div>
|
||||||
|
<div style={{ fontSize: 10.5, color: "#cfcfd5", marginTop: 6, display: "flex", gap: 10, flexWrap: "wrap" }}>
|
||||||
|
<span>
|
||||||
|
{hud.agents} agent{hud.agents === 1 ? "" : "s"}
|
||||||
|
</span>
|
||||||
|
<span style={{ color: "#5ec8d8" }}>{hud.active} active</span>
|
||||||
|
{telemetry.tokensPerMin ? <span>{Math.round(telemetry.tokensPerMin / 1000)}k tok/m</span> : null}
|
||||||
|
{telemetry.doorsPending ? (
|
||||||
|
<span style={{ color: "#e8b465" }}>
|
||||||
|
{telemetry.doorsPending} door{telemetry.doorsPending === 1 ? "" : "s"}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{/* WorldClock — live ⇄ replay (Gource-style playback of run history) */}
|
{/* WorldClock — live ⇄ replay (Gource-style playback of run history) */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
Reference in New Issue
Block a user