DraftThis article is currently in draft mode

Entity Component UI

October 17, 2025

Entity Component UI#

Inspired by the composability of the Entity-Component-System (ECS) pattern, popularized in game development, and the testability of the Model-View-View-Model (MVVM) pattern, popularized in Swift, Flutter, and Xamarin. Analogies: Entity ID = UID; System = WorldState.Plugin Primary goals: (unit) testability, composability, observability. Characteristics:

  • Plugins can be tested in complete isolation of each other (e.g. SpatialNav).
  • Source code clearly shows what components each plugin contributes and depends on.
  • Central World Store is highly inspectable by being a simple lookup from UIDs to component values.
  • Reactivity is managed entirely through jotai atoms

Context#

Our WorldState architecture is inspired by Entity Component System (ECS) patterns from game development, adapted for dense UI implementations. It decouples behavior, data, and UI rendering across multiple layers. This is especially useful in large-scale or collaborative applications that manage complex data flows–like CRDT-based documents, hierarchical data, or multiple concurrent user sessions.

Key Concepts#

Entities#

  • What: Unique identifiers representing distinct objects in the UI or application (e.g., modules, variables, workspace info, user accounts, etc.).
  • Why: Entities let us compose data and behavior in a flexible, modular way.

Components#

  • What: Reusable pieces of functionality (with their own states/atoms) that can be attached to entities.
  • Why: Components let you attach specialized behavior (e.g., focusable, draggable, collapsable) to any entity without duplicating logic.

Uniques#

  • What: Singleton objects that exist independently of entities (e.g., global settings, user session state).
  • Why: Uniques allow for truly global or application-wide states that any part of the system can reference.

Entity Map / WorldState#

  • What: A central registry that manages entities, components, and uniques.
  • Why: The WorldState orchestrates creation, updates, and disposal, ensuring a single source of truth.

Plugins#

  • What: Extension modules that respond to entity creation, provide derived components, add global uniques, or enforce constraints.
  • Why: Plugins allow building feature layers (like a tree, a grid, or specialized editing behaviors) without entangling the core system logic.

Benefits#

Composability#

  • You can mix and match components to form new behaviors without duplicating code.
  • Example: Attaching CLabel and CFocusable to an entity to make it both labeled and keyboard-focusable.

Type Safety#

  • The system's use of TypeScript generics ensures correct composition of entities and components.
  • You get compile-time verification that the right components exist on an entity.

UI Framework Agnostic#

  • The "ECS"-like logic (entities, components, plugins) is decoupled from rendering (e.g. React).

Centralized State Management with Jotai#

  • Components store data in Jotai atoms, which are known for their straightforward and granular reactivity.
  • This fosters predictable state updates and fine-grained performance optimizations.

Easy Testing#

  • Components can be tested in isolation (like microservices).
  • You can directly test reactivity, plugin interactions, and more, all without hooking up any UI.

Example Usage#

Defining a Module Entity#

// Components
export class CEditability extends World.Component("editability")<
  CEditability,
  { canBeEditedAtom: Atom<boolean>; disabledReasonAtom: Atom<null | string> }
>() {}

export class CLabel extends World.Component("label")<CLabel, { textEditor: MyTextEditorType }>() {}

// Use tags (zero-value components) to help plugins match on entities of a specific type
export class CModuleTag extends World.Component("moduleTag")<CLabel, {}>() {}

// Entity
export class ModuleEntity extends World.Entity("ModuleEntity", {
  // Required initial components
  components: [CModuleTag, CEditability, CLabel],
  // Components expected to be provided by plugins
  componentsProvidedByPlugins: [],
})<ModuleEntity>() {}

Creating and Using the Entity in the WorldState#

// 1. Create a world with or without plugins
const world = new WorldStateImpl({ store, pool, plugins: [] });

// 2. Add a new module entity
const moduleUID = world.addEntity(World.uid(null, null, "module-1"), ModuleEntity, {
  moduleTag: CModuleTag.of({}),
  editability: CEditability.of({
    canBeEditedAtom: atom(true),
    disabledReasonAtom: atom(null),
  }),
  label: CLabel.of({
    textEditor: {
      /* ...some editor instance... */
    },
  }),
});

// 3. Retrieve and update
const labelAtom = world.getComponentAtom(moduleUID, CLabel);
store.set(labelAtom, {
  textEditor: {
    /* updated editor data */
  },
});

Derived Components via a Plugin#

// For any entity with CEditability, automatically add a CDisableable
class CDisableable extends World.Component("disableable")<CDisableable, { isDisabledAtom: Atom<boolean> }>() {}

const disablePlugin = {
  name: "disable-plugin",
  setup(build: World.WorldStateBuild) {
    build.onEntityCreated(
      {
        // The plugin can match _any_ entity with CEditability, as opposed to only ModuleEntities
        requires: [CEditability],
        provides: [CDisableable],
      },
      (uid, { editability }) => ({
        disableable: CDisableable.of({
          isDisabledAtom: atom((get) => !get(editability.canBeEditedAtom)),
        }),
      }),
    );
  },
};

Best Practices#

Keep Components Small and Focused#

Each component should do one job. For example, CFocusable for keyboard focus logic, CTreeMovable for drag/move logic.

Prefer Composition Over Inheritance#

ECS fosters horizontal composition. Attach more components to get new behavior, rather than a deep inheritance hierarchy.

Use Jotai's <AtomValue /> or Minimal Hooks#

If you use React, <AtomValue /> or minimal hooks reduce extraneous re-renders. For non-React frameworks, the concept is similar: subscribe only to the atoms you need.

const labelAtom = world.getComponentAtom(moduleUID, CLabel);
// if the atom is a primitive, you can render it directly as text
return <AtomValue atom={labelAtom} />;
// or use it with a render function
return <AtomValue atom={labelAtom}>{(label) => <span >{label?.text}</span>}</AtomValue>;
// depends on the component...
return <AtomValue atom={labelAtom}>{(label) => <div  ref={(elt) => label.mount(elt)} />}</AtomValue>;

Test in Isolation#

Since WorldState logic is framework-agnostic, you can test the business logic of each component or plugin thoroughly without rendering UI.

Leverage the ECS for Concurrency#

CRDT merges, multiple user sessions, or real-time data streams can be managed more cleanly by hooking them into WorldState components or uniques, instead of scattering logic throughout the code.

Conclusion#

Our WorldState approach (powered by ECS) provides a robust, modular, and testable foundation for building complex UI and application logic. By separating data (via Jotai atoms), behavior (via components), and global singletons (via uniques), we can scale our application functionality while keeping each layer maintainable and comprehensible.