API layers and ownership
Start a Flutter integration with DrawingSession, DrawingConfiguration and DrawingCanvas from vyuh_diagram_kit. The other public packages expose lower-level contracts for headless agents, custom tools and custom rendering. An exported type is not necessarily an object that every host must create.
The application-facing model
| Object | What your application uses it for |
|---|---|
DrawingSession | Create, connect, select, transform, lock, delete, undo and redo. |
session.document | Read immutable content and save it with encode(). |
DrawingCanvas | Mount the interactive surface. |
DrawingConfiguration | Configure fonts, grids, canvas behavior, extensions and widget builders. |
ShapeRegistry / ShapeDefinition | Register durable geometry, capabilities, text slots, values and portable content. |
ShapeWidgetRegistry | Supply Flutter builders for the widget keys declared by those definitions. |
Shapes / DiagramTools | Convenient catalogs of standard registrations; custom keys remain open-ended. |
Applications do not construct command-group objects or manage a store to embed a drawing. Session listeners take no state argument: read document, selection, activeTool or configuration after notification. snapshot pairs the immutable document with its resolved scene and excludes editor bookkeeping.
DiagramStore, internal editor state and DiagramJsonCodec are not convenience exports of the kit. The sections below describe explicit lower-level integrations, which import their owning packages directly.
Advanced integration layers
| Layer | Main objects | Use it for | Mutation authority |
|---|---|---|---|
| Flutter host | DrawingSession, DrawingConfiguration, DrawingCanvas | Mounting an editor, commands, configuration and subscriptions | The session's DiagramStore |
| Canonical engine | DiagramStore, commands, DiagramTransaction, DiagramInteractionLease | Headless agents, custom commands and continuous previews | The same store; admission precedes publication |
| Schema and grammar | DiagramDocument, element/story values, ShapeRegistry, definitions and capabilities | Describing content and the operations each type supports | Immutable inputs; submit changes through commands |
| Native widget presentation | ShapeWidgetRegistry, ShapeWidgetBuilder | Supply Flutter controls for registered widget slots through DrawingConfiguration.widgetRegistry | Builders read canonical values; edits use the same session/store commands |
| Resolved geometry | DiagramResolver, ResolvedScene, outlines, routes and text lines | Deriving geometry for a document and its registry | Derived output; never edit a resolved scene to change content |
| Flutter projection | DrawingCanvasSurface, DrawingCanvasController, DiagramSceneCoordinator, painters and handle projections | Building a deliberate lower-level mounted integration | The supplied store; adapters do not own another document |
| Output adapters | JSON codecs, SVG scene/resources, PNG/SVG export functions | Persistence and export | Capture canonical content or an admitted publication |
The kit exposes grammar, transactions, tools, extensions and output types so hosts can compose them without private imports. It hides the mounted editor, controller, coordinator, painter, paint cache and DiagramEditorSettings names. Use the owning package explicitly when implementing that lower-level integration.
The kit also excludes the mounted DiagramCameraController, content-band and animation/preview painters, freehand/move previews, control-handle types, selection/text-caret projections and transform-cursor projections. These remain public from vyuh_diagram_renderer_flutter for custom mounted composition. Ordinary hosts use session navigation and registered tools; they do not construct these adapters. This is an import-boundary change: advanced consumers must add the explicit editor-package import rather than importing its private src files. There is no compatibility layer or deprecation promise implied by these names.
Inspector, extension and diagnostic imports
| Name | Import layer | Why it belongs there |
|---|---|---|
DrawingInspector | Kit host API | Takes a DrawingSession; uses its configuration, history and admission. |
DiagramPropertyInspector | Explicit editor import | Takes a DiagramStore and optional mounted controller for advanced composition. Shape capabilities come from that store's configured registry, with no inspector-specific override. It is excluded from the kit. |
DiagramInspectorTitle, DiagramInspectorSectionLabel | Kit host API | Stateless presentation helpers for host-supplied property panels; no store or controller arguments. |
DiagramPropertyStepper, DiagramVisualChoiceField<T> | Kit host API | Value/callback controls for host-supplied fields; hosts submit changes through session commands. |
DiagramEditorExtension, DiagramEditorExtensionController | Kit extension API | Custom overlays receive read-only scene/camera projections and bounded camera commands. |
DiagramFrameClock, DiagramFrameClockLease | Kit extension API | The extension controller exposes the shared clock. Acquire demand only while animating; release the lease and listeners when the overlay stops or unmounts. |
DiagramBandRendering | Explicit editor import | Paint operations on DiagramScenePainter, itself an advanced mounted adapter. |
DiagramFrameBudget | Explicit editor import | A diagnostic 120 Hz target, not a canvas configuration or a promise of measured frame delivery. |
Tests inspecting renderer surfaces import vyuh_diagram_renderer_flutter. Tests inspecting Pro controls such as the inspector use package:vyuh_diagram_kit/canvas_adapter.dart. Production host composition continues to use the session-based kit objects.
Engine services versus session commands
Application code uses DrawingSession: import vyuh_diagram_kit for Flutter or vyuh_diagram_engine for headless Dart. The engine owns the headless DrawingSession; the kit adds Flutter integration.
Store-bound command services belong to the explicit package:vyuh_diagram_engine/vyuh_diagram_engine.dart import. Flutter hosts use the session methods below, which retain the same admission, history and lifetime checks. These services and their extensions are no longer named exports of the kit.
| Advanced engine service | Session entry point |
|---|---|
DiagramCreationCommands, DiagramConnectedCreationCommands | createShape, createText, insertConnectedElement |
DiagramObjectCommands | addElement, removeElements, deleteSelection, setImageSource, updateValues |
DiagramSelectionCommands | selectElements, selectAll, clearSelection |
DiagramLockCommands | setElementsLocked |
DiagramGroupingCommands | groupSelection, ungroupSelection |
DiagramClipboardCommands | copySelection, paste, duplicateSelection |
DiagramArrangementCommands, DiagramArrangementOrder, DiagramArrangementResize, DiagramArrangementTransforms | align, distribute, reorder, moveElements, resizeElements, rotateElements |
DiagramConnectionCommands, DiagramConnectionEditingCommands | createConnector, reconnect |
DiagramConnectorLabelCommands, DiagramConnectorLabelResizeCommands | Connector label methods on the session; advanced resize machinery stays in the engine |
DiagramStoryCommands | session.text (TextEditing) |
The kit hides stores, editor state, publications and store-bound command groups. An infrastructure integration using integration.borrowDrawingStore imports the engine package and the kit's adapter.dart explicitly. Store borrowing is absent from the consumer session constructors. Pure-Dart infrastructure uses the session package's adapter.dart; ordinary pure-Dart applications use vyuh_diagram_engine.dart. Transaction, policy and gesture contracts remain available for custom tools. Custom tools continue to receive DiagramToolContext and dispatch canonical transactions; they do not need to construct a store-bound command service. No service is removed from its owning package, and no compatibility alias is introduced.
DiagramToolContext.dispatch(transaction) is a void command: it returns normally when admitted and throws DiagramChangeDenied when a host policy denies the transaction. A custom tool can catch that exception and inspect decision.reason for feedback. Denial leaves the prior publication and history unchanged. The callback-scoped context still rejects use after the tool callback returns.
Resource lifetimes
The kit exposes registry definitions and resolved geometry for custom hosts, but does not expose DiagramOwnershipIndex, the rebuildable lock-topology cache. Use session.setElementsLocked for lock changes and the admitted resolved scene for effective lock state. Advanced engine integrations can import the index from vyuh_diagram_engine; applications do not need to maintain or invalidate it.
Editor-provided extension controllers belong to one camera/scene owner. Their reads and navigation commands throw StateError after store replacement or editor unmount. Refresh overlay subscriptions when buildOverlay receives a replacement controller. Ordinary rebuilds retain the owner's lifetime. The shared frame clock instead follows the editor lifetime; it survives store replacement, rejects new demand after unmount, and permits release of old leases.
| Object | Who owns it? | Cleanup and failure contract |
|---|---|---|
DrawingSession(...) | Flutter host | Unmount its canvas before dispose(). The session disposes the store it created. Disposal is idempotent; commands after disposal reject. |
integration.borrowDrawingStore(store) | Host owns the facade; caller retains the store | Dispose the facade after unmount, then dispose the externally owned store when finished. A borrowed facade cannot replace the owner's admission hook. |
DrawingCanvas | Flutter widget tree | One mounted canvas per session. Unmounting releases presentation ownership while retaining session content and history. |
DiagramStore | Session or headless owner | dispose() clears subscriptions, history, callbacks and gesture ownership. Disposal during evaluation or notification rejects. Retained immutable publications remain readable. |
DiagramInteractionLease | Command or gesture owner | Preview from its initial state; commit once or cancel. A stale lease cannot mutate a successor interaction. It is not a second store. |
DiagramSceneCoordinator | Advanced Flutter adapter | dispose() removes its store listener; it does not dispose the store. |
DiagramEditor | Advanced Flutter adapter, or session internally | Attach through the owning editor; release the mounted editor before discarding the controller. Do not create one alongside a session just to call content commands. |
| Native Pango text backend | Native host that creates it | Dispose dependent stores first, then the backend. Supplying it to a resolver does not transfer its lifetime. See native text. |
| Canonical and resolved values | Caller retaining the snapshot | No disposal; retain immutable snapshots as needed. A snapshot does not keep a disposed editor actionable. |
Session lifetime in a host
Create an owning session once for the lifetime of the editor host, rather than inside build(). Removing DrawingCanvas from the widget tree releases its presentation attachment; the session retains the document and undo history for a later remount. After the canvas has unmounted, call session.dispose(). Disposing a session while its canvas is still attached throws instead of leaving a mounted editor with dead resources.
When another component owns the canonical store, borrow it explicitly:
dart
import 'package:vyuh_diagram_kit/adapter.dart' as integration;
final session = integration.borrowDrawingStore(store);
// Mount DrawingCanvas(session: session), then unmount it when finished.
final savedPublication = session.snapshot;
session.dispose();
// The externally owned store is still usable. A later host can adopt it.
final nextSession = integration.borrowDrawingStore(store);
// savedPublication remains an immutable snapshot, not a live editing handle.
nextSession.dispose();
store.dispose(); // Only the store's owner performs this final cleanup.Disposal cancels the facade's active preview and restores its pre-edit state. It does not cancel an interaction subsequently started by another owner after that preview ended. Commands retained from the disposed session, including session.text, reject further work. Dispose is idempotent, but must happen outside synchronous document publication callbacks; schedule host teardown after the callback returns.
Borrowed facades share selection, active tools, history and interaction admission. Use this pattern to manage ownership, not to represent independent agent or user participants. See sessions and collaboration for that boundary.
Commands versus presentation
| Operation | Canvas required? | Contract |
|---|---|---|
| Create, mutate, connect, lock, select, undo/redo | No | Use session commands or the same store's command groups. Host authorization belongs in beforeChange, including for custom transactions. |
| Read document or resolved scene | No | Observe the admitted store publication; do not pair a document with geometry from another revision. |
Read session.cameraState | No | Returns null while detached. |
| Navigate, convert coordinates, animate viewport or paths | Yes | Session presentation commands throw StateError while detached. |
| Subscribe to content | No | addListener observes state; document-change hooks describe completed content operations. Remove subscriptions or dispose their owner. |
| Subscribe to presentation | No | addPresentationListener observes camera/animation changes separately from content history. |
The advanced mounted controller has some detached arrangement methods that are no-ops. That behavior is not the session contract and is not a reason to route headless content operations through a controller.
Extending the API
Register shape definitions, tools, native widget builders and hooks through their public registries. Custom tools dispatch the same transactions as built-in tools. Use semantic session operations for ordinary editing. Advanced transactions use session.dispatch or session.beginEdit, without exposing the mutable store. Pure-Dart agents may own a DiagramStore from the engine package directly.
The service-map and storyboard starter compositions demonstrate contrasting registries, tools and policies using public imports. They are examples of these layers, not proof of every possible extension or production frame budget.
Flutter hosts and server agents
vyuh_diagram_engine owns the pure Dart semantic session. The Flutter kit's DrawingSession extends it with mounted configuration and presentation adapters. Content commands share one implementation and one canonical store.
| Host | Entry point | Text layout |
|---|---|---|
| Flutter desktop or web | DrawingSession(document: ..., shapeRegistry: ..., configuration: ...) | Defaults to FlutterDiagramTextLayoutEngine. Mount DrawingCanvas for input, camera and native widgets. |
| Pure Dart agent or server | DrawingSession(document: ..., shapeRegistry: ..., textLayout: ...) from vyuh_diagram_engine | Defaults to approximate metrics. Supply a DiagramTextLayoutEngine when actual font metrics are required. |
| Flutter view of an existing store | integration.borrowDrawingStore(store) | Retains the store's existing resolver and layout backend; wrapping it does not replace approximate text metrics with Flutter metrics. |
Both entry points use the same store commands, admission, document publication and undo history. The session forwards those commands; it does not maintain a second document. A server imports vyuh_diagram_engine and calls session.dispose() when finished. Advanced engine integration remains available to platform adapters and domain-kit authors. Native Flutter widgets require a mounted canvas; their declared fallback content remains available to headless resolution and SVG output.
Attaching a Flutter session to an existing engine
When a Flutter canvas borrows a store, install its text backend when creating that store, before the first document is resolved:
dart
final store = DiagramStore(
DiagramEditorState.initial(document),
resolver: DiagramResolver(
registry: registry,
textLayout: FlutterDiagramTextLayoutEngine(),
),
);
final session = integration.borrowDrawingStore(store);For the workflow kit, pass textLayout: FlutterDiagramTextLayoutEngine() to registry.createSession(...), then use DrawingSession.fromSession(...) for its Flutter adapter. The lower-level createStore adapter remains available for framework integration. The Flutter canvas rejects text lines or list markers without shaped geometry. It does not remeasure fixed-metric text while painting or editing. Existing headless stores must be created with the appropriate backend for their host.
dart
integration.borrowDrawingStore(
DiagramStore store, {
DrawingConfiguration? configuration,
})borrowDrawingStore borrows the store. Without explicit configuration it projects the store settings into a default configuration. With configuration it applies those settings, but retains the owning store admission hook. The owner remains responsible for disposing the store.