Skip to main content

Not every state value belongs in Bitfield. Put state in Bitfield when it coordinates packages. Keep hover, menu, drag, focus, and draft UI details private.

A sidebar has hover state, an editor has unsaved text, a file tree has a selected file, and an assistant panel needs the selected file for context. These are not the same kind of state.The split is simple: Bitfield carries product facts that multiple packages need, while components keep visual-only details close to where they happen.

The rule

Only this component cares -> keep it private
Many packages need to react -> put it in Bitfield

Traditional mistake

await writeBitfieldState('hovered-sidebar-item', itemId);
await writeBitfieldState('dropdown-open', true);
await writeBitfieldState('drag-position', { x, y });
This turns visual noise into product state. It creates unnecessary churn and makes the product harder to reason about.

Bitfield shape

function SidebarItem() {
  const [isHovered, setHovered] = useState(false);
  return <button onMouseEnter={() => setHovered(true)} />;
}
Private UI state stays private.Shared product state becomes Bitfield state:
selected-file
current-project
active-conversation
selected-agent

The decision table

StateWhere it belongsWhy
Hovered buttonPrivate UI stateOnly the button cares.
Dropdown openPrivate UI stateUsually only one component cares.
Current drag positionPrivate UI stateIt changes too often and is visual.
Unsaved textarea draftUsually private UI stateIt is not real product state until submitted.
Selected file used by editor and AI contextBitfield stateMultiple packages coordinate around it.
Current project used by many panelsBitfield stateIt changes what many packages show.
Active conversation used by chat, timeline, and notificationsBitfield stateIt is a shared product fact.

Four decisions in real product code

Hovered row

Traditional over-globalized answer

await writeBitfieldState('hovered-row', rowId);

Keep hover local

const [hoveredRow, setHoveredRow] = useState<string | null>(null);
The hover is not a product fact. It is only a visual detail for the current surface.

Open command menu

Traditional over-globalized answer

globalUiStore.commandMenuOpen = true;

Keep menu state local

setCommandMenuOpen(true);
Keep it private unless another package has a real product reason to observe it.

Selected file

Traditional under-shared answer

const [selectedFile, setSelectedFile] = useState<string | null>(null);
Better answer when editor, assistant panel, breadcrumbs, and command palette need it:
const selectedFile = readInput('selected-file');
That value coordinates packages, so it should not be trapped inside one component.

Draft text

Traditional over-shared answer

await writeBitfieldState('draft-message-text', text);

Keep draft text local

const [draftText, setDraftText] = useState('');
Only promote it when the draft becomes product data, such as a saved message, note, command, or package record.

Promotion rules

Ask thisIf yesIf no
Does another package need this value?Consider Bitfield state or data name.Keep it private.
Does changing it run real product work?Send an action request.Keep it private or write product state through the right boundary.
Is it only animation, hover, focus, drag, scroll, or draft typing?Keep it private.Continue classifying.
Would losing it on refresh lose customer-visible data or progress?It may be product state.It is probably UI state.
Would two packages disagree if they each kept a copy?Put one public fact in Bitfield.Keep package-local state.

What this prevents

Bad shapeWhy it breaksBetter shape
Every UI event writes Bitfield state.Shared state becomes noisy and expensive to understand.Keep visual-only state private.
Shared product facts stay in React state.Other packages cannot reliably react.Put coordination state in Bitfield.
The shell owns product state because it sits above children.The shell becomes the product brain.Packages read shared facts from Bitfield.
AI stores everything globally.The app has too many sources of truth.Classify state before writing code.
React is one adapter example. Swift view state, Kotlin state, terminal prompt state, and future shell state follow the same rule: visual-only state stays near the UI that uses it.

Review checklist

CheckGood answer
Is the value hover, focus, scroll, drag, or draft typing?Keep it private.
Does another package need it to make product decisions?Promote it to a shared fact or data name.
Does changing it run work?Use an action request.
Is React context being used as product coordination?Replace it with Runtime Kit reads and requests.

Full before and after

Traditional feature request

“When the user clicks a file row, highlight it, open it in the editor, and show the assistant panel context.”

Bad implementation

const [hoveredRow, setHoveredRow] = useGlobalStore('hoveredRow');
const [selectedFile, setSelectedFile] = useState<string | null>(null);

function onClick(filePath: string) {
  setSelectedFile(filePath);
}
This gets both decisions backwards. Hover is visual-only but became global. Selected file coordinates packages but stayed trapped in one component.

Put each value where its readers are

const [hoveredRow, setHoveredRow] = useState<string | null>(null);
const selectedFile = useBitfieldData<string>('selected-file');
Then the click path updates the public selected-file fact through the package’s public files and names. The editor and assistant panel read that fact. The hover remains private.The rule is not “local state bad” or “Bitfield state good.” The rule is: put each value where its readers are.

Review check

Before adding state, classify it:
Is this only visual?
  keep it private

Does another package need to read it?
  make it Bitfield state

Does changing it require work to run?
  send an action request

Next

Last modified on May 10, 2026