Skip to content

Commit f79e6c5

Browse files
committed
[IMP] documentation: update documentation about plugin differences
Task: 6428159
1 parent 1b67c15 commit f79e6c5

8 files changed

Lines changed: 74 additions & 49 deletions

File tree

CLAUDE.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ Two layers: **Model** (headless data + logic) and **UI** (Owl components + canva
2525
### Model: CQS via commands, getters, plugins
2626

2727
- `Model` (`src/model.ts`) is the entry point. Mutate via `model.dispatch("CMD_NAME", payload)`; read via `model.getters.someGetter(...)`. All command types are in `src/types/commands.ts`. Commands run through a chain (`allowDispatch``beforeHandle``handle``finalize`) on every registered plugin.
28-
- **Plugins** (`src/plugins/`) own slices of state and expose getters. Categories:
29-
- `core/` — persistent business data (cells, sheets, ranges, charts, pivots, conditional formats, tables, figures, named ranges…). One plugin per data structure. Participate in import/export.
30-
- `evaluation/` — derived state computed from core (cell evaluation, computed style, chart runtime, dynamic tables, custom colors…).
31-
- `ui_stateful/` — UI-only state (active sheet, selection, viewport, edition…).
32-
- `ui_feature/` — high-level features expressible as lower-level command sequences (sort, autofill, find/replace, clipboard handlers, etc.).
33-
Registries in `src/plugins/plugin_registries.ts` declare which plugins are loaded.
28+
- **Plugins** (`src/plugins/`) own slices of state and expose getters. Three base classes, four categories:
29+
- `CorePlugin``core/` — persistent business data (cells, sheets, ranges, charts, pivots, conditional formats, tables, figures, named ranges…). One plugin per data structure. Participate in import/export.
30+
- `EvaluationPlugin``evaluation/` — derived state computed from core (cell evaluation, computed style, chart runtime, dynamic tables, filter/subtotal evaluation, custom colors…).
31+
- `UIPlugin``ui_stateful/` — UI-only state (selection, clipboard, header positions, figures…).
32+
- `UIPlugin``ui_feature/` — high-level features expressible as lower-level command sequences (sort, format, insert pivot, local history…).
33+
Registries in `src/plugins/plugin_registries.ts` declare which plugins are loaded. They are `PluginRegistry` instances that check at `add()`/`replace()` time that the plugin extends the expected base class, so a plugin cannot be registered in the wrong category.
34+
- **Getter scopes** (`src/types/getters.ts`): `CoreGetters``EvaluationGetters``RenderingGetters``Getters`. Each plugin only receives the scope of its category — a core plugin cannot read evaluation getters, and an evaluation plugin cannot read UI getters.
35+
- **Evaluation commands** (`EVALUATE_CELLS`, `EVALUATE_CHARTS`, see `evaluationCommandTypes`/`isEvaluationCommand` in `src/types/commands.ts`) are a distinct dispatch path: they skip `allowDispatch`, an `EvaluationPlugin` may dispatch them (and nothing else), and no non-evaluation command may be dispatched while a top-level evaluation command is being handled. Both violations throw.
3436
- **Range / cell coordinates** flow through `range.ts` plugin; never store `A1` strings directly in plugin state.
3537
- **History**: `src/history/` provides undo/redo by recording inverse commands; plugins use `this.history.update(...)` to make mutations trackable.
3638
- **Collaborative**: `src/collaborative/` synchronizes commands across clients; `state_observer.ts` and command transforms keep concurrent edits consistent.
@@ -46,7 +48,7 @@ Two layers: **Model** (headless data + logic) and **UI** (Owl components + canva
4648
### UI
4749

4850
- `src/components/` — Owl components (sidepanels, top bar, bottom bar, composer, popovers, figures, charts wrappers, etc.).
49-
- `src/components/grid/` and renderer plugins draw the grid on `<canvas>` (`renderer` family in `evaluation`). The DOM only hosts overlays, composer, figures, popovers.
51+
- `src/components/grid/` draws the grid on `<canvas>`. Draw layers come from `UIPlugin.drawLayer` (`ui_stateful/selection`, `ui_stateful/clipboard`, `ui_feature/collaborative`) and from stores extending `SpreadsheetStore`; `EvaluationPlugin` cannot draw. The DOM only hosts overlays, composer, figures, popovers.
5052
- `src/stores/` + `src/store_engine/` — Owl-store-style reactive stores for UI state that doesn't belong in the Model (notifications, sidepanels, hovered link, etc.). Components access them via the store engine, not directly.
5153
- `src/registries/` — extension points (menus, side panels, autofill rules, clipboard handlers, topbar components, cell popovers…). Adding a feature usually means registering in one of these plus a plugin.
5254
- `src/selection_stream/` — keyboard/mouse selection state machine, observed by composer, find-and-replace, etc.

doc/add_function.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -454,13 +454,13 @@ The `compute` function inside the function definition can use external dependenc
454454
455455
To adhere to the o-spreadsheet's architecture, we'll use a dedicated [plugin](./extending/architecture.md#plugins) for this purpose. The `compute` function can access relevant data using its getters.
456456
457-
First, let's create the `CurrencyPlugin` class that extends `UIPlugin` and registers the necessary getters:
457+
First, let's create the `CurrencyPlugin` class that extends `EvaluationPlugin` and registers the necessary getters. It has to be an evaluation plugin (or a core plugin): the `compute` function only receives `EvaluationGetters`, so a getter defined on a `UIPlugin` would not be reachable from a spreadsheet function.
458458
459459
```ts
460-
const { uiPluginRegistry } = o_spreadsheet.registries;
461-
const { UIPlugin } = o_spreadsheet;
460+
const { evaluationPluginRegistry } = o_spreadsheet.registries;
461+
const { EvaluationPlugin } = o_spreadsheet;
462462

463-
class CurrencyPlugin extends UIPlugin {
463+
class CurrencyPlugin extends EvaluationPlugin {
464464
static getters = ["getCurrencyRate"];
465465

466466
constructor(config) {
@@ -472,7 +472,7 @@ class CurrencyPlugin extends UIPlugin {
472472
}
473473
}
474474

475-
uiPluginRegistry.add("currencyPlugin", CurrencyPlugin);
475+
evaluationPluginRegistry.add("currencyPlugin", CurrencyPlugin);
476476
```
477477
478478
Next, we need to update the `compute` function to use the `getCurrencyRate` getter:
@@ -496,7 +496,7 @@ To handle this requirement and enable caching of API results, we'll introduce a
496496
The `getCurrencyRate` function reads from the cache and returns the status. If the status is `"missing"`, the `fetch` method handles data fetching and updates the cache. The `getFromCache` and `fetch` methods are described below:
497497
498498
```ts
499-
class CurrencyPlugin extends UIPlugin {
499+
class CurrencyPlugin extends EvaluationPlugin {
500500
static getters = ["getCurrencyRate"];
501501

502502
constructor(config) {
@@ -526,7 +526,7 @@ class CurrencyPlugin extends UIPlugin {
526526
Let's explore a possible implementation of the `getFromCache` and `fetch` methods:
527527
528528
```ts
529-
class CurrencyPlugin extends UIPlugin {
529+
class CurrencyPlugin extends EvaluationPlugin {
530530
// ...
531531

532532
private getFromCache(from: string, to: string) {
@@ -570,7 +570,7 @@ class CurrencyPlugin extends UIPlugin {
570570
Instead of using the native `fetch` method, you can inject your own service through the configuration:
571571
572572
```ts
573-
class CurrencyPlugin extends UIPlugin {
573+
class CurrencyPlugin extends EvaluationPlugin {
574574
constructor(config) {
575575
super(config);
576576
/**

doc/extending/architecture.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,21 @@ A plugin can:
5555
- introduce new getters to make parts of its state available for other plugins or the user interface.
5656
- react to any dispatched command
5757

58-
Plugins are decomposed in two parts: core and UI.
58+
Plugins are decomposed in three parts, each with its own base class: core (`CorePlugin`), evaluation (`EvaluationPlugin`) and UI (`UIPlugin`).
5959

60-
Core plugins are responsible to manage the data persistence and all associated business rules (cell content, user-defined style, chart definitions, ...). Each plugin is responsible of one data structure.
60+
Core plugins are responsible to manage the data persistence and all associated business rules (cell content, user-defined style, chart definitions, ...)`src/plugins/core/`. Each plugin is responsible of one data structure.
6161

62-
UI plugins are separated in three different categories, with the following responsibility:
62+
Evaluation plugins manage the state derived from the core part (cell evaluation, computed style, chart runtime, ...) — `src/plugins/evaluation/`. They never own a source of truth: their state is entirely recomputed from core data, which is why it is never persisted nor transmitted to other collaborators.
6363

64-
- Manage the derived state from the core part (cell evaluation, computed style, ...) — `src/plugins/evaluation/`
65-
- Manage the ui state (active sheet, current selection, ...) — `src/plugins/ui_stateful/`
64+
UI plugins are separated in two different categories, with the following responsibility:
65+
66+
- Manage the ui state (current selection, clipboard, ...) — `src/plugins/ui_stateful/`
6667
- Handle high-level features that could be described with lower-level features (Sort a zone can be described with different cell updates) — `src/plugins/ui_feature/`
6768

6869
Each UI plugin is responsible of one feature.
6970

71+
Each category also defines what a plugin is allowed to read: core plugins receive `CoreGetters`, evaluation plugins receive `EvaluationGetters` (core + evaluation) and UI plugins receive the full `Getters` (see `src/types/getters.ts`).
72+
7073
More details about plugins here: [Adding a new feature](./business_feature.md)
7174

7275
Not sure whether your new code belongs in a core plugin, a UI plugin, or a store? See [Where should this code live?](./where_to_put_code.md).

doc/extending/business_feature.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ in all cells which contains the content `party`.
99

1010
## Plugin creation
1111

12-
A plugin should extend either `CorePlugin` or `UIPlugin` depending on its role.
13-
The plugin should also be register in the registry of plugins, in order to load
14-
it at the model startup. (`corePluginRegistry` or `uiPluginRegistry`). Mode details
15-
about plugins can be found in the [plugin section]("plugin.md)
12+
A plugin should extend `CorePlugin`, `EvaluationPlugin` or `UIPlugin` depending on its role.
13+
The plugin should also be registered in the registry matching its base class, in order to load
14+
it at the model startup (`corePluginRegistry`, `evaluationPluginRegistry`, `statefulUIPluginRegistry`
15+
or `featurePluginRegistry` — registering a plugin in a registry expecting another base class throws).
16+
More details about plugins can be found in the [plugin section](plugin.md)
1617

1718
In our example, we will create two plugins, a new `CorePlugin` which will manage
1819
wether the party mode is active, and a new `UIPlugin` that will be responsible
@@ -25,9 +26,9 @@ class PartyPlugin extends CorePlugin {}
2526

2627
class PartyDrawerPlugin extends UIPlugin {}
2728

28-
// Register the plugins in order to load it at the model startup
29+
// Register the plugins in order to load them at the model startup
2930
corePluginRegistry.add("party_plugin", PartyPlugin);
30-
uiPluginRegistry.add("party_drawer_plugin", PartyDrawerPlugin);
31+
statefulUIPluginRegistry.add("party_drawer_plugin", PartyDrawerPlugin);
3132
```
3233

3334
## Adding an internal state

doc/extending/command.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Commands
22

33
Commands are essential for modifying the spreadsheet state. They are dispatched to the model, which in turn relays them to each plugin.
4-
There are two types of commands: `CoreCommand` and `LocalCommand`.
4+
There are two types of commands: `CoreCommand` and `LocalCommand`. Among the local commands, the _evaluation commands_ form a special subset with its own dispatch rules (see below).
55

66
## Types of Commands
77

@@ -19,10 +19,19 @@ There are two types of commands: `CoreCommand` and `LocalCommand`.
1919
- **Sub-Commands**: Can dispatch sub-commands, which can be either core or local commands.
2020
- **Collaboration**: Not broadcast to other connected users (but sub-core commands are).
2121

22+
### EvaluationCommand
23+
24+
- **Purpose**: Trigger the (re)computation of a derived state. Currently `EVALUATE_CELLS` and `EVALUATE_CHARTS`, listed in `evaluationCommandTypes` (`src/types/commands.ts`).
25+
- **Handling**: Handled by evaluation plugins and UI plugins. Core plugins never handle them (`CorePlugin.handle` only accepts a `CoreCommand`).
26+
- **Dispatch**: They bypass `allowDispatch` entirely — an evaluation command can never be refused. When dispatched by an evaluation plugin, they are relayed to evaluation and UI plugins only.
27+
- **Sub-Commands**: This is the only kind of command an `EvaluationPlugin` may dispatch. Dispatching anything else from an evaluation plugin throws (`An evaluation plugin cannot dispatch non-evaluation commands`), and so does dispatching a non-evaluation command from any plugin while a top-level evaluation command is being handled (`A top level evaluation command cannot dispatch non-evaluation commands`).
28+
- **Collaboration**: Not broadcast — each client re-evaluates on its own.
29+
2230
### Example
2331

2432
- `RESIZE_COLUMNS_ROWS`: A `CoreCommand` handled by a core plugin to adjust the size of rows or columns.
2533
- `AUTORESIZE_COLUMNS`: A `LocalCommand` handled by a UI plugin, which dispatches the sub-command `RESIZE_COLUMNS_ROWS` based on the current cell content.
34+
- `EVALUATE_CELLS`: An `EvaluationCommand` handled by `CellEvaluationPlugin` to evaluate the cells that were invalidated.
2635

2736
### Device Agnosticism
2837

doc/extending/plugin.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,38 @@ A plugin can :
1919
functions
2020
- react to any existing command
2121

22-
Plugins are divided into two main categories: CorePlugin and UIPlugin, with each category featuring two specific types.
22+
Plugins are divided into three base classes: `CorePlugin`, `EvaluationPlugin` and `UIPlugin`. Each base class is tied to a registry in `src/plugins/plugin_registries.ts`, and these registries refuse a plugin that does not extend the expected base class:
23+
24+
| Base class | Registry | Folder | Getters it receives |
25+
| ------------------ | -------------------------- | -------------------------- | --------------------------------------- |
26+
| `CorePlugin` | `corePluginRegistry` | `src/plugins/core/` | `CoreGetters` |
27+
| `EvaluationPlugin` | `evaluationPluginRegistry` | `src/plugins/evaluation/` | `EvaluationGetters` (core + evaluation) |
28+
| `UIPlugin` | `statefulUIPluginRegistry` | `src/plugins/ui_stateful/` | `Getters` (everything) |
29+
| `UIPlugin` | `featurePluginRegistry` | `src/plugins/ui_feature/` | `Getters` (everything) |
2330

2431
### I. CorePlugin
2532

2633
- manages data that is persistent
2734
- can make changes to its state using the history interface (allowing `undo` and `redo`)
2835
- import and export its state to be stored in the o-spreadsheet file
36+
- can only dispatch core commands
2937

30-
Core plugins include:
38+
### II. EvaluationPlugin
3139

32-
1. Core Plugins: manage data persistence
33-
2. Evaluation Plugins: have a derived state from core data
40+
- has a state entirely derived from core data (cell evaluation, computed styles, chart runtime, ...)
41+
- never persisted, never transmitted to other collaborators: every client recomputes it from the same replayed core commands
42+
- cannot change the model data: it can only dispatch _evaluation_ commands (see [Commands](./command.md))
3443

35-
### II. UIPlugin
44+
### III. UIPlugin
3645

37-
- manages transient state, user specific state and everything that is needed to display the spreadsheet without changing the persistent data (like evaluation)
46+
- manages transient state, user specific state and everything that is needed to display the spreadsheet without changing the persistent data
3847

3948
UI plugins include:
4049

4150
1. Stateful Plugins: have a state, but which should not be shared in collaborative
4251
2. Feature Plugins: handle a specific feature, without handling any core commands
4352

44-
Unsure which of the four plugin types (or which store) your feature belongs in? See [Where should this code live?](./where_to_put_code.md).
53+
Unsure which of the four plugin categories (or which store) your feature belongs in? See [Where should this code live?](./where_to_put_code.md).
4554

4655
## Plugin skeleton
4756

@@ -92,10 +101,11 @@ class MyPlugin extends CorePlugin {
92101
// makes the function getSomething accessible from anywhere that has a reference to model.getters
93102
MyPlugin.getters = ["getSomething"];
94103

95-
// add the new "MyPlugin" to the plugin registry.
104+
// add the new "MyPlugin" to the registry matching its base class
105+
// (corePluginRegistry, evaluationPluginRegistry, statefulUIPluginRegistry or featurePluginRegistry).
96106
// It will be automatically instantiated by o-spreadsheet when you mount the spreadsheet component or when you create a new Model()
97-
const pluginRegistry = spreadsheet.registries.pluginRegistry;
98-
pluginRegistry.add("MyPlugin", MyPlugin);
107+
const { corePluginRegistry } = o_spreadsheet.registries;
108+
corePluginRegistry.add("MyPlugin", MyPlugin);
99109
```
100110

101111
## Dispatch lifecycle and methods

0 commit comments

Comments
 (0)