You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+9-7Lines changed: 9 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -25,12 +25,14 @@ Two layers: **Model** (headless data + logic) and **UI** (Owl components + canva
25
25
### Model: CQS via commands, getters, plugins
26
26
27
27
-`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.
-`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.
34
36
-**Range / cell coordinates** flow through `range.ts` plugin; never store `A1` strings directly in plugin state.
35
37
-**History**: `src/history/` provides undo/redo by recording inverse commands; plugins use `this.history.update(...)` to make mutations trackable.
36
38
-**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
46
48
### UI
47
49
48
50
-`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.
50
52
-`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.
51
53
-`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.
52
54
-`src/selection_stream/` — keyboard/mouse selection state machine, observed by composer, find-and-replace, etc.
Copy file name to clipboardExpand all lines: doc/add_function.md
+8-8Lines changed: 8 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -454,13 +454,13 @@ The `compute` function inside the function definition can use external dependenc
454
454
455
455
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.
456
456
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.
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
496
496
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:
497
497
498
498
```ts
499
-
classCurrencyPluginextendsUIPlugin {
499
+
classCurrencyPluginextendsEvaluationPlugin {
500
500
static getters = ["getCurrencyRate"];
501
501
502
502
constructor(config) {
@@ -526,7 +526,7 @@ class CurrencyPlugin extends UIPlugin {
526
526
Let's explore a possible implementation of the `getFromCache` and `fetch` methods:
527
527
528
528
```ts
529
-
classCurrencyPluginextendsUIPlugin {
529
+
classCurrencyPluginextendsEvaluationPlugin {
530
530
// ...
531
531
532
532
privategetFromCache(from:string, to:string) {
@@ -570,7 +570,7 @@ class CurrencyPlugin extends UIPlugin {
570
570
Instead of using the native `fetch` method, you can inject your own service through the configuration:
Copy file name to clipboardExpand all lines: doc/extending/architecture.md
+8-5Lines changed: 8 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -55,18 +55,21 @@ A plugin can:
55
55
- introduce new getters to make parts of its state available for other plugins or the user interface.
56
56
- react to any dispatched command
57
57
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`).
59
59
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.
61
61
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.
63
63
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/`
66
67
- 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/`
67
68
68
69
Each UI plugin is responsible of one feature.
69
70
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
+
70
73
More details about plugins here: [Adding a new feature](./business_feature.md)
71
74
72
75
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).
Copy file name to clipboardExpand all lines: doc/extending/command.md
+10-1Lines changed: 10 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
# Commands
2
2
3
3
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).
5
5
6
6
## Types of Commands
7
7
@@ -19,10 +19,19 @@ There are two types of commands: `CoreCommand` and `LocalCommand`.
19
19
-**Sub-Commands**: Can dispatch sub-commands, which can be either core or local commands.
20
20
-**Collaboration**: Not broadcast to other connected users (but sub-core commands are).
21
21
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
+
22
30
### Example
23
31
24
32
-`RESIZE_COLUMNS_ROWS`: A `CoreCommand` handled by a core plugin to adjust the size of rows or columns.
25
33
-`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.
Copy file name to clipboardExpand all lines: doc/extending/plugin.md
+20-10Lines changed: 20 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,29 +19,38 @@ A plugin can :
19
19
functions
20
20
- react to any existing command
21
21
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 |
- can make changes to its state using the history interface (allowing `undo` and `redo`)
28
35
- import and export its state to be stored in the o-spreadsheet file
36
+
- can only dispatch core commands
29
37
30
-
Core plugins include:
38
+
### II. EvaluationPlugin
31
39
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))
34
43
35
-
### II. UIPlugin
44
+
### III. UIPlugin
36
45
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
38
47
39
48
UI plugins include:
40
49
41
50
1. Stateful Plugins: have a state, but which should not be shared in collaborative
42
51
2. Feature Plugins: handle a specific feature, without handling any core commands
43
52
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).
45
54
46
55
## Plugin skeleton
47
56
@@ -92,10 +101,11 @@ class MyPlugin extends CorePlugin {
92
101
// makes the function getSomething accessible from anywhere that has a reference to model.getters
93
102
MyPlugin.getters= ["getSomething"];
94
103
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).
96
106
// It will be automatically instantiated by o-spreadsheet when you mount the spreadsheet component or when you create a new Model()
0 commit comments