Tell React Flow which theme it is drawing in

React Flow stamps `colorMode` on its wrapper as a class and defaults it to
`light`. The app's own token scopes are named `.light` / `.dark` — the classes
that let a dashboard be forced to one theme inside a shell on the other — so
every canvas was silently redeclaring the light palette on its own subtree:
white node cards and a white canvas in dark mode, with the inherited
`--foreground` text still near-white and therefore invisible. Home's neurons
had the same fault, drawn with white ring gaps and the light `--primary`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZeGnqVsf5VHQqvz4HdUhN
This commit is contained in:
2026-08-23 09:29:30 +02:00
co-authored by Claude Opus 5
parent 0b2cc81d6a
commit e4cd12dc79
2 changed files with 12 additions and 0 deletions
@@ -23,6 +23,7 @@ import { motion } from "motion/react"
import { useEffect, useMemo, useState } from "react"
import type { BrainGraph } from "@/client"
import { useTheme } from "@/components/theme-provider"
import { useIsMobile } from "@/hooks/useMobile"
import { scaleIn } from "@/lib/motion"
import { BrainEdge, type BrainEdgeData } from "./BrainEdge"
@@ -154,6 +155,7 @@ function build(graph: BrainGraph): { nodes: Node[]; edges: Edge[] } {
function BrainCanvas() {
const navigate = useNavigate()
const { fitView } = useReactFlow()
const { resolvedTheme } = useTheme()
useFlowSocket()
// The socket invalidates every "flows" key on a rebuild, so publishing
// anywhere re-fetches this and the layout runs again.
@@ -213,6 +215,10 @@ function BrainCanvas() {
nodesConnectable={false}
elementsSelectable={false}
proOptions={{ hideAttribution: true }}
// React Flow stamps this on the wrapper as a class, and the app's own
// token scopes are named `.light` / `.dark` (index.css) — so leaving it
// unset re-themes the whole canvas to light inside a dark shell.
colorMode={resolvedTheme}
fitView
fitViewOptions={FIT}
minZoom={0.1}
@@ -31,6 +31,7 @@ import {
type MessageSpec,
type NodeDef_Input,
} from "@/client"
import { useTheme } from "@/components/theme-provider"
import { Button } from "@/components/ui/button"
import {
Dialog,
@@ -205,6 +206,7 @@ function FlowEditorInner({
const { showErrorToast, showSuccessToast } = useCustomToast()
const { fitView } = useReactFlow()
const updateNodeInternals = useUpdateNodeInternals()
const { resolvedTheme } = useTheme()
const { data: flows } = useSuspenseQuery(flowsQueryOptions())
const { data: detail } = useSuspenseQuery(flowQueryOptions(flowName))
@@ -1076,6 +1078,10 @@ function FlowEditorInner({
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
proOptions={{ hideAttribution: true }}
// React Flow stamps this on the wrapper as a class, and the app's own
// token scopes are named `.light` / `.dark` (index.css) — so leaving it
// unset re-themes the whole canvas to light inside a dark shell.
colorMode={resolvedTheme}
fitView
fitViewOptions={FIT_VIEW}
// Low enough that the fit can always show the whole graph. A phone is