Brain: neurons in the mark's blue, ringed in destructive when they cannot run

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XC2jX6Hdj7pxGGKzBTrbqB
This commit is contained in:
2026-08-17 14:50:39 +02:00
co-authored by Claude Opus 5
parent a1265450df
commit 18837e8880
4 changed files with 63 additions and 37 deletions
+35 -10
View File
@@ -13,13 +13,28 @@ export type BrainNodeData = {
flows: string[] flows: string[]
/** Circle diameter in pixels, from how many neurons this one is wired to. */ /** Circle diameter in pixels, from how many neurons this one is wired to. */
size: number size: number
/** Why this neuron cannot run, if validation found something. */
issue?: string | null
[key: string]: unknown [key: string]: unknown
} }
/**
* Ring thickness as a share of the diameter, measured off the brand mark: a
* 1.4545 stroke on a 6.3 outer diameter in `fluksio-light.svg`.
*
* A share rather than a fixed width because the neurons run from 32px to 96px:
* the same ring in pixels would swallow the smallest and read as a hairline on
* the largest, and it is the proportion that makes a circle look like the mark.
*/
const RING = 0.23
function BrainNodeComponent({ data }: NodeProps) { function BrainNodeComponent({ data }: NodeProps) {
const { label, kind, members, flows, size } = data as BrainNodeData const { label, kind, members, flows, size, issue } = data as BrainNodeData
const emits = useGroupEmits(members) const emits = useGroupEmits(members)
const failed = useGroupError(members) const failed = useGroupError(members)
// One ring for both kinds of trouble: a run that broke and wiring that can
// never run are the same answer to "can I trust this neuron right now".
const problem = failed || !!issue
// Brightest right after a publish, then left to decay. Only the peak needs // Brightest right after a publish, then left to decay. Only the peak needs
// holding: the floor is the resting style, so letting go is the whole decay. // holding: the floor is the resting style, so letting go is the whole decay.
const [firing, setFiring] = useState(false) const [firing, setFiring] = useState(false)
@@ -34,14 +49,16 @@ function BrainNodeComponent({ data }: NodeProps) {
return ( return (
<span <span
className={cn( className={cn(
"brain-cell relative flex items-center justify-center rounded-full border", "brain-cell relative flex items-center justify-center rounded-full",
// Nothing seen yet this session is not the same as seen and gone quiet. // Nothing seen yet this session is not the same as seen and gone quiet.
emits === 0 && "brain-idle", emits === 0 && "brain-idle",
firing && "brain-hot", firing && "brain-hot",
failed ? "border-destructive" : "border-border", problem ? "border-destructive" : "border-foreground",
)} )}
style={{ width: size, height: size }} // Border-box, so the outer rim stays at `size` and the edges keep landing
title={`${label} · ${kind} · ${members.join(", ")}`} // on it however thick the ring gets.
style={{ width: size, height: size, borderWidth: size * RING }}
title={`${label} · ${kind} · ${members.join(", ")}${issue ? ` · ${issue}` : ""}`}
> >
{/* Remounting on each emit is what restarts the animation. */} {/* Remounting on each emit is what restarts the animation. */}
{emits > 0 ? <span key={emits} className="node-pulse" /> : null} {emits > 0 ? <span key={emits} className="node-pulse" /> : null}
@@ -63,15 +80,23 @@ function BrainNodeComponent({ data }: NodeProps) {
</span> </span>
{/* {/*
* A name under every circle is what makes the graph unreadable, so only * A name under every circle is what makes the graph unreadable, so only
* the neuron under the pointer or the keyboard says what it is — except a * the neuron under the pointer or the keyboard says what it is — except
* failing one, since colour is never the only carrier of a status. * one with something wrong, since colour is never the only carrier of a
* status. Two words rather than the reason itself: a cycle names every
* node in it, which is a sentence no label under a 32px circle can hold,
* so the phrase matches what Home says and the full text stays in the
* tooltip.
*/} */}
<span className={cn("brain-label", failed && "brain-label-on")}> <span className={cn("brain-label", problem && "brain-label-on")}>
<span className="max-w-[140px] truncate text-xs font-medium"> <span className="max-w-[140px] truncate text-xs font-medium">
{label} {label}
</span> </span>
{failed ? ( {/* The label box is only as wide as the circle it hangs under, so two
<span className="text-xs font-medium text-destructive">failed</span> words break onto two lines unless told not to. */}
{problem ? (
<span className="whitespace-nowrap text-xs font-medium text-destructive">
{failed ? "failed" : "cannot run"}
</span>
) : null} ) : null}
</span> </span>
</span> </span>
@@ -129,6 +129,7 @@ function build(graph: BrainGraph): { nodes: Node[]; edges: Edge[] } {
members: source.members ?? [], members: source.members ?? [],
flows: source.flows ?? [], flows: source.flows ?? [],
size: node.size, size: node.size,
issue: source.issue,
} satisfies BrainNodeData, } satisfies BrainNodeData,
} }
}), }),
+20 -24
View File
@@ -140,41 +140,31 @@
} }
/* /*
* A neuron is a filled disc rather than an outline: a `--card` circle on * A neuron is the node of the brand mark: a disc of the identity blue inside a
* `--background` is a hairline in light and all but nothing in dark. * thick ring, wired to its neighbours. The ring thickness is a share of the
* diameter and lives in `BrainNode`, which is where the diameter is known.
* *
* The fill is neutral, and stays neutral — status still speaks only through the * The fill is identity and carries no status — that is the ring's job, and the
* border and the word under it. What its lightness carries is how recently this * word under it. What the fill's lightness carries is how recently this page
* page saw the neuron publish: brightest just after it fires, decaying to a * saw the neuron publish: brightest just after it fires, decaying to a floor it
* floor it never drops below, so a graph left open sorts itself into what is * never drops below, so a graph left open sorts itself into what is busy and
* busy and what is not. A fresh page has seen nothing either way, so everything * what is not. The floor stays blue enough to still read as the mark. A fresh
* starts at `.brain-idle`, halfway, claiming no history it does not have. * page has seen nothing either way, so everything starts at `.brain-idle`,
* halfway, claiming no history it does not have.
*/ */
.brain-cell { .brain-cell {
cursor: pointer; cursor: pointer;
background-color: color-mix( background-color: color-mix(in srgb, var(--primary) 50%, transparent);
in srgb,
var(--muted-foreground) 28%,
transparent
);
transition: border-color var(--duration-base) var(--ease-standard); transition: border-color var(--duration-base) var(--ease-standard);
} }
.brain-cell.brain-idle { .brain-cell.brain-idle {
background-color: color-mix( background-color: color-mix(in srgb, var(--primary) 65%, transparent);
in srgb,
var(--muted-foreground) 42%,
transparent
);
} }
/* Snapped into, decayed out of — never the other way round. */ /* Snapped into, decayed out of — never the other way round. */
.brain-cell.brain-hot { .brain-cell.brain-hot {
background-color: color-mix( background-color: color-mix(in srgb, var(--primary) 90%, transparent);
in srgb,
var(--muted-foreground) 65%,
transparent
);
transition: none; transition: none;
} }
@@ -186,9 +176,15 @@
} }
} }
/*
* Outside the ring rather than in it: the ring is what says whether this neuron
* can run, and the disc is already `--primary`, so recolouring either to mark
* the pointer would either overwrite a status or vanish into the fill.
*/
.brain-cell:hover, .brain-cell:hover,
.react-flow__node:focus-visible .brain-cell { .react-flow__node:focus-visible .brain-cell {
border-color: var(--primary); outline: 2px solid var(--ring);
outline-offset: 2px;
} }
/* /*
@@ -117,12 +117,16 @@ export function HealthOverview({
<Tile <Tile
label="Flows running" label="Flows running"
value={`${summary?.flows.running ?? 0}/${summary?.flows.total ?? 0}`} value={`${summary?.flows.running ?? 0}/${summary?.flows.total ?? 0}`}
// Worst first: the engine gave up on a quarantined flow, a flow
// validation blocks never started, and a paused one is deliberate.
note={ note={
summary?.flows.quarantined summary?.flows.quarantined
? `${summary.flows.quarantined} quarantined` ? `${summary.flows.quarantined} quarantined`
: summary?.flows.paused : summary?.flows.invalid
? `${summary.flows.paused} paused` ? `${summary.flows.invalid} cannot run`
: "none paused" : summary?.flows.paused
? `${summary.flows.paused} paused`
: "none paused"
} }
/> />
<Tile <Tile