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:
@@ -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,
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -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,12 +117,16 @@ 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.paused
|
||||
? `${summary.flows.paused} paused`
|
||||
: "none paused"
|
||||
: summary?.flows.invalid
|
||||
? `${summary.flows.invalid} cannot run`
|
||||
: summary?.flows.paused
|
||||
? `${summary.flows.paused} paused`
|
||||
: "none paused"
|
||||
}
|
||||
/>
|
||||
<Tile
|
||||
|
||||
Reference in New Issue
Block a user