Skip to content

Whitebox Architecture

Whitebox is built on a modular, event-driven architecture designed for extensibility and real-time operations.

System Overview

  • Plugin System: Allows for easy extension of functionality through self-contained modules.
  • Event System: Facilitates communication between components and enables reactive architecture.
  • Django Channels Server: Provides the foundation for real-time communication via WebSockets.
  • Database Integration: Offers persistent storage for flight data with revision history.
  • Task Queue & State Synchronization: Ensures reliable task execution and state management across distributed components.

Key Components

Plugin System

  • Supports dynamic loading and unloading of plugins at runtime from the environment
  • Keeps plugin packages and frontend build dependencies separate
  • Offers standardized APIs for data persistence, event handling and event emission

Plugins share the backend process and browser application. They are trusted application code, not security sandboxes.

Check out the plugin guide to make your first plugin, plugin API reference for a deeper dive, or plugin codebase (and here) to learn more about the plugin implementation system implementation.

Frontend build and runtime

A frontend plugin adds React components or shared state to the Whitebox browser application. For example, a Map plugin can provide a map display, while another plugin places markers on it. Python registration tells Whitebox which components and stores are available. A slot in the UI chooses a component for a role such as map.display; a direct lookup chooses a named component. Registration alone does not put anything on screen: an application component must use that slot or lookup.

The host is Whitebox's core browser application: it starts the router and loads plugins. Each frontend plugin is built into a separate remote, a group of JavaScript modules the host loads through Module Federation. They cooperate within one application, not separate embedded pages.

From a Python package to a browser component

The plugin's wheel or source archive contains Python code, JSX sources and assets, not a precompiled remote. If the plugin uses its own JavaScript libraries, their dependency declarations and lockfile travel inside its Python package too. Building or installing that Python package does not run JavaScript.

Before production startup, core assembles the installed application. It uses one prepared set of build tools to compile each plugin separately, with that plugin's own dependencies. The host is built separately with the same core-locked tools. The maintained backend image build performs assembly after Python installation; Django startup only checks and serves the completed result.

Installed plugin A sources ── core assembly ── remote A ──┐
Installed plugin B sources ── core assembly ── remote B ──┤
                                                       │
Core frontend sources ─────── host build ───── host ─────┤
                                                       ▼
                                      One browser application
                                      host React + plugin UI/state

Separate builds let plugins maintain their non-shared libraries without combining every plugin's dependency constraints into the host. Common tooling stays in core so plugin authors need not maintain copies of Vite and the federation setup. Explicit assembly also makes missing or mismatched output a visible build or startup error. There is no startup compiler fallback: silently rebuilding would hide an inconsistent installation and require build dependencies at runtime. A backend-only plugin needs no frontend build.

What is shared?

The host supplies one React/React DOM runtime, including JSX and client entry points. A plugin cannot fall back to its own copy. This lets hooks and context work across plugin boundaries. Other libraries stay with the plugin that uses them; arbitrary library sharing is not supported.

Sometimes the important thing to share is an instance, not a library. Importing a provider's exported Zustand store gives consumers the same state and subscriptions. Installing the same Zustand version and constructing a second store would not. Similarly, Map consumers use Map's exported components so they participate in the provider's native map context. See the component and store APIs.

Failure and update boundaries

At startup, Django checks that installed packages, build tools, registration maps and compiled files belong to the same application build. One invalid frontend plugin blocks the whole browser bootstrap, because a partial registry could omit a provider other plugins need. Backend registrations may still be loaded; a working API is not proof that the UI can start. Plugins are trusted application code, not security sandboxes, and separate builds do not prevent a plugin from breaking the application. Startup validation checks consistency, not every React API or plugin interaction.

An update therefore needs a matched host/plugin installation, tool preparation when tools change, explicit assembly, backend worker restarts and browser reloads. Assembly publishes a complete generation (one set of plugin outputs) at once; that does not make installing several Python packages atomic. A failed assembly leaves the old generation selected, but new packages cannot use old mismatched output. Rollback restores the complete previously working package/tool/build set; database compatibility needs a separate check. Hot upgrades and rolling mixed-version operation are unsupported.

Event System

  • Implements an event-driven architecture for decoupled communication
  • Allows plugins to register their own events, as well as add callbacks for other plugins' events
  • Implements asynchronous event handling for improved responsiveness

Explore the event system API to learn more how to use it.

Django Channels Server

  • Handles asynchronous requests for improved performance
  • Manages WebSocket consumers for real-time, bidirectional communication
  • Utilizes channel layers for inter-instance communication

Explore the whitebox codebase to learn more about the server implementation.

Database Integration

  • Supports PostgreSQL
  • Uses Django's ORM for database interactions
  • Provides migration support for managing plugins' schema changes

Task Queue & State Synchronization

This component is a work-in-progress, with the proposed implementation available here: WIP-0005.

  • Utilizes RQ and Redis for background task processing
  • Ensures state updates & consistency across multi-process workers and distributed components

Real-Time Communication Flow

  1. Clients connect to the server via WebSockets
  2. Server accepts event connections at /ws/squawk/ and frontend logs at /ws/logging/. Squawk clients subscribe via command.session.event.subscribe; kernel event emissions auto-broadcast to bare event-name groups so clients receive the events they subscribed to
  3. Clients send commands or requests to the server
  4. Server processes commands, potentially triggering plugin actions
  5. Plugins emit events in response to actions or internal processes
  6. Server broadcasts relevant events to connected clients and other plugins that have subscribed to those events
  7. Clients update their state based on received events

Next Steps