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[]
/** Circle diameter in pixels, from how many neurons this one is wired to. */
size: number
/** Why this neuron cannot run, if validation found something. */
issue?: string | null
[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) {
const { label, kind, members, flows, size } = data as BrainNodeData
const { label, kind, members, flows, size, issue } = data as BrainNodeData
const emits = useGroupEmits(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
// holding: the floor is the resting style, so letting go is the whole decay.
const [firing, setFiring] = useState(false)
@@ -34,14 +49,16 @@ function BrainNodeComponent({ data }: NodeProps) {
return (
<span
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.
emits === 0 && "brain-idle",
firing && "brain-hot",
failed ? "border-destructive" : "border-border",
problem ? "border-destructive" : "border-foreground",
)}
style={{ width: size, height: size }}
title={`${label} · ${kind} · ${members.join(", ")}`}
// Border-box, so the outer rim stays at `size` and the edges keep landing
// 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. */}
{emits > 0 ? <span key={emits} className="node-pulse" /> : null}
@@ -63,15 +80,23 @@ function BrainNodeComponent({ data }: NodeProps) {
</span>
{/*
* 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
* failing one, since colour is never the only carrier of a status.
* the neuron under the pointer or the keyboard says what it is — except
* 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">
{label}
</span>
{failed ? (
<span className="text-xs font-medium text-destructive">failed</span>
{/* The label box is only as wide as the circle it hangs under, so two
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}
</span>
</span>
@@ -129,6 +129,7 @@ function build(graph: BrainGraph): { nodes: Node[]; edges: Edge[] } {
members: source.members ?? [],
flows: source.flows ?? [],
size: node.size,
issue: source.issue,
} satisfies BrainNodeData,
}
}),
+20 -24
View File
@@ -140,41 +140,31 @@
}
/*
* A neuron is a filled disc rather than an outline: a `--card` circle on
* `--background` is a hairline in light and all but nothing in dark.
* A neuron is the node of the brand mark: a disc of the identity blue inside a
* 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
* border and the word under it. What its lightness carries is how recently this
* page saw the neuron publish: brightest just after it fires, decaying to a
* floor it never drops below, so a graph left open sorts itself into what is
* busy and what is not. A fresh page has seen nothing either way, so everything
* starts at `.brain-idle`, halfway, claiming no history it does not have.
* The fill is identity and carries no status — that is the ring's job, and the
* word under it. What the fill's lightness carries is how recently this page
* saw the neuron publish: brightest just after it fires, decaying to a floor it
* never drops below, so a graph left open sorts itself into what is busy and
* what is not. The floor stays blue enough to still read as the mark. A fresh
* page has seen nothing either way, so everything starts at `.brain-idle`,
* halfway, claiming no history it does not have.
*/
.brain-cell {
cursor: pointer;
background-color: color-mix(
in srgb,
var(--muted-foreground) 28%,
transparent
);
background-color: color-mix(in srgb, var(--primary) 50%, transparent);
transition: border-color var(--duration-base) var(--ease-standard);
}
.brain-cell.brain-idle {
background-color: color-mix(
in srgb,
var(--muted-foreground) 42%,
transparent
);
background-color: color-mix(in srgb, var(--primary) 65%, transparent);
}
/* Snapped into, decayed out of — never the other way round. */
.brain-cell.brain-hot {
background-color: color-mix(
in srgb,
var(--muted-foreground) 65%,
transparent
);
background-color: color-mix(in srgb, var(--primary) 90%, transparent);
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,
.react-flow__node:focus-visible .brain-cell {
border-color: var(--primary);
outline: 2px solid var(--ring);
outline-offset: 2px;
}
/*
@@ -117,9 +117,13 @@ export function HealthOverview({
<Tile
label="Flows running"
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={
summary?.flows.quarantined
? `${summary.flows.quarantined} quarantined`
: summary?.flows.invalid
? `${summary.flows.invalid} cannot run`
: summary?.flows.paused
? `${summary.flows.paused} paused`
: "none paused"