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[]
|
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,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user