Introduction
SearSuite provides tools and concepts for building spreadsheet and rich-document editors. It includes structured document models, commands, rendering contracts, and optional editing features. Your application decides which capabilities to include, how to present them, and how the editor fits into an existing product.
The central principle is that the application retains control over its documents and their changes. Sheet, Workbook, and Doc content is explicit structured state, not an opaque value owned by a UI component. Each feature owns the state it adds. Changes enter through typed commands, and the editor stages and commits their operations through one transaction boundary. Applications can observe committed changes without mutating internal state directly.
SearSuite is a toolkit rather than a ready-made editor to drop into a page. Its assemblies create working editor runtimes, but you select features, supply platform adapters, and build the surrounding interface. This takes more integration work than a fixed component, in exchange for control over behavior and the ability to assemble the same contracts in different hosts.
The reusable foundation and domain packages provide shared contracts and document operations. Assemblies connect them to selected plugins; visual editors also use rendering and input services supplied by a host. Built-in features such as formulas, fill, and history follow the same extension boundaries as third-party plugins. You can omit an optional feature or provide another implementation of its contract. The next chapter maps these modules and their relationships; the Reference Manual lists their public APIs.
Modules and their relationships
The main module families and the application host each have a distinct role:
sear-foundation-*supplies dependency injection, platform access, geometry, errors, and shared primitives used by other packages.sear-domain-*defines structured Sheet and Doc data, sparse matrices, operations, and protocols independently of any interface.sear-render-*provides scene, input, and drawing contracts for visual editors and their host adapters.sear-assembly-*creates Sheet, Doc, and Workbook runtimes, connects selected plugins, and owns command dispatch and lifecycle.sear-plugin-*adds optional features such as formulas, fill, and history through feature-owned commands, state, and extension points.- Host (your application) supplies the interface, concrete platform adapters, and transport, then chooses which modules to assemble.
Design principles
SearSuite keeps document state explicit and gives each feature one owner. The host chooses the modules and platform adapters it needs; an assembly connects them without making any one feature a requirement of the core editor.
One mutation path
Every change to a running editor's state must be triggered by a command. Pointer and keyboard handlers turn user actions into commands, and application code uses the same entry point. Neither the host nor a feature edits committed state directly. The command bus finds the handler registered by the feature that owns the behavior; that handler builds the operations, and the Sheet core stages their results before committing them. Observers and rendering see the committed selection rather than an intermediate step.
Selecting cell D3 (row 2, column 3 in zero-based coordinates) follows the same path:
import { GetSelection, SelectRangeCommand } from '@searsuite/sear-plugin-selection';
editor.commands.run(SelectRangeCommand, { anchor: [2, 3], focus: [2, 3] });
const selection = editor.queries.run(GetSelection, undefined);
SheetSelectionPlugin registers the command. It expands the range when the cell belongs to a merge, creates Rect OT
operations for the active cell and selected range, and records a selection-change interaction. The core commits
those changes, then runs navigation updates and refreshes the highlight. GetSelection reads the resulting range;
the host never writes the plugin's selection state directly.
One owner per state
Values, fill, alignment, formulas, and selection have different owners. A feature defines the commands and operations for its own state; the runtime coordinates their commits without folding them into a central cell object. This lets optional and third-party plugins use the same extension boundaries as built-in features.
Adapt through contracts
Reusable modules express the capabilities they need through interfaces and abstract contracts. The assembly and host supply concrete implementations at the boundary, so a change in rendering or platform technology does not require a change to document state, commands, or feature ownership. A host may choose a Canvas render context today; a DOM-based renderer could fill the same role when it satisfies the required rendering contract. Platform services, text shaping, storage, and transport follow the same principle: adapt the environment to the editor's contracts rather than embedding one environment throughout the modules.
Reads stay separate from writes
Reads use distinct boundaries:
- Queries answer current, synchronous, side-effect-free questions.
- Observations publish values or events that evolve over time.
- Capabilities expose editor-owned services without leaking the dependency-injection container.
await editor.commands.run(commandDefinition, commandInput);
const value = editor.queries.run(queryDefinition, queryInput);
const dispose = editor.observations.subscribe(observationDefinition, listener);
Foundation
sear-foundation-* supplies dependency injection, platform access, geometry, errors, and shared primitives used by
other packages. These packages provide small contracts that domain, render, assembly, and plugin code can share
without taking a dependency on a specific editor or browser.
sear-foundation-di gives each assembled editor its own service scope. sear-foundation-platform defines access to
timers, events, identifiers, and other host services through an injected Platform. Geometry and range packages
provide portable coordinates and bounds; error and utility packages give callers stable shared contracts.
The host provides concrete platform implementations at composition time. A reusable foundation package never selects the application's UI, transport, or persistence policy.
Domain
sear-domain-* defines structured Sheet and Doc data, sparse matrices, operations, and protocols independently of
any interface. Domain packages describe what an editor can store and how a change is represented; they do not choose
a renderer, host adapter, or optional feature set.
State has one owner
This rule is especially important in a spreadsheet engine, where values, formats, fills, alignment, merged ranges, dimensions, selection, and formula results change independently. Combining them in one central cell object would force unrelated features to coordinate every schema and transaction change.
A state-owning plugin registers its value, defines commands that mutate it, and may publish field projections, queries, observations, visual readers, export codecs, or OT definitions. DataEngine owns data and projections; it does not infer collaboration or persistence policy.
SearSuite distinguishes four kinds of state:
- Document state is content owned by an exported editor or Workbook.
- Session state is contributor-keyed local remount state such as a viewport or transient selection.
- Derived state includes indexes, formula results, layout caches, and render projections.
- Host state includes transport, credentials, dialogs, chrome, and product policy.
Sparse defaults remain absent. For example, a missing alignment or row size means "use current configuration"; it is not repeated in every cell or persisted merely because a renderer needs a value.
The Sheet model
The base Sheet matrix owns cell content. Separate owners hold dimensions, alignment, fill, number formats, fixed rows, merged ranges, formulas, and selection. The assembly combines the selected owners inside one editor-local runtime. Structural commands coordinate those owners through typed operations without transferring ownership to the core.
Cell behavior is catalog-driven. Persisted cell types contribute commit strategies, display text, render registration, and clipboard codecs through the cell catalog. Formula source, calculated values, images, and links use provider registries, allowing the base matrix to remain unaware of every possible projection.
Visual properties are composed through SheetCellVisualReaderRegistry. Each feature contributes only the property
readers it owns, and ownership conflicts fail at registration. Grid and merged-cell renderers consume one
projection without importing or enumerating feature matrices.
Portable operations and protocols
Matrix OT and Rect OT describe changes without binding them to a view. The collaboration domain owns revision, pending-operation, acknowledgement, catch-up, ordering, and rebase behavior. Unknown operation families fail clearly instead of falling through a central type switch.
Render
sear-render-* provides scene, input, and drawing contracts for visual editors and their host adapters. Render
packages turn editor state into visual behavior while keeping platform-specific drawing behind replaceable adapters.
A render context exposes immediate drawing operations without leaking browser-native or headless-native values. Concrete Web Canvas, SVG, and WebGL adapters implement the same contract. Retained scene nodes, cell render registries, dirty-region strategies, hit testing, cursor policy, input routing, and scrolling remain separate packages so a host can replace one boundary without forking editor behavior.
Text shaping follows the same rule. The neutral TextShaper returns glyph, cluster, caret-stop, and advance data using
UTF-16 offsets. Browser-native, HarfBuzz, Skia, and other platform types stay inside their adapters. Each editor
composition root registers one shaper.
Platform access is injected through Platform: timers, event registration, resize observation, host-view offsets,
identifiers, and warnings. Reusable packages do not reach into browser globals. This is what leaves room for Web,
headless, desktop, native, and future hosts to share the same transaction and feature layers.
Assembly
sear-assembly-* creates Sheet, Doc, and Workbook runtimes, connects selected plugins, and owns command dispatch and
lifecycle. An assembly is the composition root: it chooses compatible packages and gives each editor an isolated
runtime, while feature state remains with its owning plugin.
Editor facades
createSheetEditor() composes one grid document, its selected plugins, viewport session, render services, and an
optional embedded rich-text editor. createDocEditor() composes a standalone rich-text document runtime.
createWorkbookEditor() owns workbook structure and mounts a Sheet editor for the active Sheet.
The Sheet and Workbook facades expose typed commands, long-lived observations, runtime capabilities, and idempotent
destruction. Sheet also exposes typed queries. Workbook keeps structural reads under document and mounted-Sheet
access under sheets,
making ownership visible instead of growing one universal editor object.
import { createHeadlessEditor } from '@searsuite/sear-assembly-headless';
import { createEmptySheetDocumentState, ExportSheetStateCommand } from '@searsuite/sear-assembly-setup';
const editor = createHeadlessEditor({ sheetState: createEmptySheetDocumentState(4, 4) });
try {
const snapshot = editor.commands.run(ExportSheetStateCommand, undefined).sheetState;
} finally {
editor.destroy();
}
The browser host example supplies render
and input adapters to createSheetEditor() and shows that boundary in a working editor.
Each editor owns an isolated dependency-injection container. Plugins and adapters resolve editor-local services, and child editors borrow parent-owned shared services explicitly so child teardown cannot dispose parent state.
Workbook composition
WorkbookDocumentState contains stable Sheet identities, names, order, visibility, and per-Sheet document state.
Active Sheet identity, detached editor sessions, and local plugin values belong to WorkbookSessionState. Internal
references use stable sheetId values; names remain display labels that can change.
The Workbook mounts only the active Sheet editor. Before switching, it captures the current Sheet document and local session through contributor registries, destroys that editor, and mounts the next Sheet with shared workbook services. Switching back restores the captured session without making viewport state part of the shared document.
Workbook plugins and Sheet plugins are selected separately. The Workbook owns services that must survive Sheet remounts, including the formula-function registry and collaboration channels keyed by Sheet identity. Structural add, remove, move, rename, hide, and unhide operations use staged Workbook transactions and have independent history from content edits inside a mounted Sheet.
Plugins
sear-plugin-* adds optional features such as formulas, fill, and history through feature-owned commands, state,
and extension points. The same lifecycle applies to built-in and third-party features, so assemblies select what
belongs in an editor without moving feature behavior into a central core.
Registration and ownership
Registration happens in explicit lifecycle phases. A plugin can register services, state, commands, queries, observations, runtime capabilities, session adapters, visual readers, and document-state codecs. The composition root controls ordering and releases acquired resources in reverse order on failure or teardown.
State registration establishes ownership; command registration establishes mutation; queries and observations expose typed reads; capabilities expose editor-owned services to hosts; session adapters capture local remount state. A collaborative transaction owner also contributes versioned OT definitions and a durable export codec.
New behavior belongs in the lowest layer that can own it without reversing dependencies:
- Put portable values and protocols in a domain package.
- Put optional stateful behavior in a plugin package.
- Put concrete platform conversion in a host adapter.
- Put only composition and lifecycle wiring in an assembly package.
Finite feature values have one canonical enum-like contract. Consumers import that contract rather than repeating literal unions, supported-value arrays, or string comparisons.
History, collaboration, and export
History records only successful commits. Sheet features use their owning transaction adapters; Workbook history records structural transactions separately. Undo and redo move stack entries only after the inverse or replay pipeline succeeds.
Collaboration eligibility is explicit. A transaction owner contributes a stable owner identifier, versioned encode/decode/invert functions, and every required pairwise transform relation. Editor plugins bridge committed transactions to host transport; they do not own the collaboration domain's revision or rebase algorithms.
Durable export follows the same ownership. Each state-owning plugin contributes its own codec to the export registry. Runtime snapshot commands are for in-memory remounting, not implicitly versioned file formats. This keeps new feature state from expanding a central serializer and lets third-party plugins participate on equal terms.
Host application
Host (your application) supplies the interface, concrete platform adapters, and transport, then chooses which modules to assemble. The host is not a required SearSuite package: a browser UI, headless server, desktop shell, or another product can use the same domain and editor contracts.
A visual host provides its render context, text shaper, input and viewport adapters, and an implementation of
Platform for timers, events, and browser or native services. The host also owns dialogs, credentials, persistence,
file downloads, and collaboration transport. Reusable packages receive these through contracts instead of reaching
into browser globals or taking over product policy.
The browser Canvas example shows a concrete host. A headless host can omit visual adapters and assemble only the capabilities it needs.
Where to go next
| I want to... | Start here |
|---|---|
| Create a Sheet and change its content or appearance | Assembly, then the Sheet editor API |
| Read current cell data without exporting a snapshot | ReadSheetCellDataQuery |
| Display and edit a Sheet in a browser | Host application, then Render |
| Add an optional feature | Plugins and the complete demo plugin |
| Manage several Sheets | Workbook lifecycle and sear-assembly-workbook |
| Save data or collaborate | History, collaboration, and export |
| Run without a browser | Assembly and sear-assembly-headless |
The Reference Manual is organized by package and public symbol. Package summaries explain ownership; symbol entries contain signatures, parameter documentation, remarks, examples, return values, errors, and source links whenever the owning declaration supplies them.