diff --git a/src/mol-plugin/component.ts b/src/mol-plugin/component.ts
index fab609d81a056aa2604fc2252da0f604c34fe72e..3d15fcb180e8967f2379ce6e5566ef9d1f53a5b1 100644
--- a/src/mol-plugin/component.ts
+++ b/src/mol-plugin/component.ts
@@ -4,41 +4,38 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { BehaviorSubject, Observable, Subject } from 'rxjs';
 import { PluginContext } from './context';
 import { shallowMergeArray } from 'mol-util/object';
+import { RxEventHelper } from 'mol-util/rx-event-helper';
 
 export class PluginComponent<State> {
-    private _state: BehaviorSubject<State>;
-    private _updated = new Subject();
+    private _ev: RxEventHelper;
 
-    updateState(...states: Partial<State>[]): boolean {
-        const latest = this.latestState;
+    protected get ev() {
+        return this._ev || (this._ev = RxEventHelper.create());
+    }
+
+    private _state: State;
+
+    protected updateState(...states: Partial<State>[]): boolean {
+        const latest = this.state;
         const s = shallowMergeArray(latest, states);
         if (s !== latest) {
-            this._state.next(s);
+            this._state = s;
             return true;
         }
         return false;
     }
 
-    get states() {
-        return <Observable<State>>this._state;
-    }
-
-    get latestState() {
-        return this._state.value;
-    }
-
-    get updated() {
-        return <Observable<{}>>this._updated;
+    get state() {
+        return this._state;
     }
 
-    triggerUpdate() {
-        this._updated.next({});
+    dispose() {
+        if (this._ev) this._ev.dispose();
     }
 
     constructor(public context: PluginContext, initialState: State) {
-        this._state = new BehaviorSubject<State>(initialState);
+        this._state = initialState;
     }
 }
\ No newline at end of file
diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts
index c766545aaded8c723164c400fd16ff57ebc6487d..307b00eabe43af4e1a938095de229dbd59151286 100644
--- a/src/mol-plugin/context.ts
+++ b/src/mol-plugin/context.ts
@@ -93,7 +93,7 @@ export class PluginContext {
     initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement) {
         try {
             this.layout.setRoot(container);
-            if (this.spec.initialLayout) this.layout.updateState(this.spec.initialLayout);
+            if (this.spec.initialLayout) this.layout.setProps(this.spec.initialLayout);
             (this.canvas3d as Canvas3D) = Canvas3D.create(canvas, container);
             PluginCommands.Canvas3D.SetSettings.dispatch(this, { settings: { backgroundColor: Color(0xFCFBF9) } });
             this.canvas3d.animate();
@@ -135,6 +135,7 @@ export class PluginContext {
         this.ev.dispose();
         this.state.dispose();
         this.tasks.dispose();
+        this.layout.dispose();
         this.disposed = true;
     }
 
diff --git a/src/mol-plugin/layout.ts b/src/mol-plugin/layout.ts
index bca2e2cd1da0b3570afb58a0b9f620c2e7530ca4..f41fd2c3a5274897173470929aec9a1b6bd9e8c3 100644
--- a/src/mol-plugin/layout.ts
+++ b/src/mol-plugin/layout.ts
@@ -42,21 +42,30 @@ interface RootState {
 }
 
 export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
+
+    readonly events = {
+        updated: this.ev()
+    }
+
     private updateProps(state: Partial<PluginLayoutStateProps>) {
-        let prevExpanded = !!this.latestState.isExpanded;
+        let prevExpanded = !!this.state.isExpanded;
         this.updateState(state);
         if (this.root && typeof state.isExpanded === 'boolean' && state.isExpanded !== prevExpanded) this.handleExpand();
 
-        this.triggerUpdate();
+        this.events.updated.next();
     }
 
     private root: HTMLElement;
     private rootState: RootState | undefined = void 0;
     private expandedViewport: HTMLMetaElement;
 
+    setProps(props: PluginLayoutStateProps) {
+        this.updateState(props);
+    }
+
     setRoot(root: HTMLElement) {
         this.root = root;
-        if (this.latestState.isExpanded) this.handleExpand();
+        if (this.state.isExpanded) this.handleExpand();
     }
 
     private getScrollElement() {
@@ -72,7 +81,7 @@ export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
 
             if (!body || !head) return;
 
-            if (this.latestState.isExpanded) {
+            if (this.state.isExpanded) {
                 let children = head.children;
                 let hasExp = false;
                 let viewports: HTMLElement[] = [];
diff --git a/src/mol-plugin/state.ts b/src/mol-plugin/state.ts
index 5e3a349c88082113ea666c92133b15ed44591c7d..e583ebe6d378e158e285962fc446ede22c77b699 100644
--- a/src/mol-plugin/state.ts
+++ b/src/mol-plugin/state.ts
@@ -73,6 +73,7 @@ class PluginState {
         this.dataState.dispose();
         this.behaviorState.dispose();
         this.cameraSnapshots.dispose();
+        this.animation.dispose();
     }
 
     constructor(private plugin: import('./context').PluginContext) {
diff --git a/src/mol-plugin/state/animation/manager.ts b/src/mol-plugin/state/animation/manager.ts
index 9ef6136f45d5a8ec448f30c513767199177c9d90..734ddf49a427a22ec8200153e735b19e7ec24fa0 100644
--- a/src/mol-plugin/state/animation/manager.ts
+++ b/src/mol-plugin/state/animation/manager.ts
@@ -21,9 +21,17 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
     private _current: PluginAnimationManager.Current;
     private _params?: PD.For<PluginAnimationManager.State['params']> = void 0;
 
+    readonly events = {
+        updated: this.ev()
+    };
+
     get isEmpty() { return this.animations.length === 0; }
     get current() { return this._current!; }
 
+    private triggerUpdate() {
+        this.events.updated.next();
+    }
+
     getParams(): PD.Params {
         if (!this._params) {
             this._params = {
@@ -36,8 +44,8 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
     }
 
     updateParams(newParams: Partial<PluginAnimationManager.State['params']>) {
-        this.updateState({ params: { ...this.latestState.params, ...newParams } });
-        const anim = this.map.get(this.latestState.params.current)!;
+        this.updateState({ params: { ...this.state.params, ...newParams } });
+        const anim = this.map.get(this.state.params.current)!;
         const params = anim.params(this.context) as PD.Params;
         this._current = {
             anim,
@@ -98,7 +106,7 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
     }
 
     get isAnimating() {
-        return this.latestState.animationState === 'playing';
+        return this.state.animationState === 'playing';
     }
 
     private _frame: number | undefined = void 0;
@@ -116,17 +124,17 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
         } else if (newState.kind === 'next') {
             this._current.state = newState.state;
             this._current.lastTime = t - this._current.startedTime;
-            if (this.latestState.animationState === 'playing') this._frame = requestAnimationFrame(this.animate);
+            if (this.state.animationState === 'playing') this._frame = requestAnimationFrame(this.animate);
         } else if (newState.kind === 'skip') {
-            if (this.latestState.animationState === 'playing') this._frame = requestAnimationFrame(this.animate);
+            if (this.state.animationState === 'playing') this._frame = requestAnimationFrame(this.animate);
         }
     }
 
     getSnapshot(): PluginAnimationManager.Snapshot {
-        if (!this.current) return { state: this.latestState };
+        if (!this.current) return { state: this.state };
 
         return {
-            state: this.latestState,
+            state: this.state,
             current: {
                 paramValues: this._current.paramValues,
                 state: this._current.anim.stateSerialization ? this._current.anim.stateSerialization.toJSON(this._current.state) : this._current.state
@@ -144,7 +152,7 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
                 ? this._current.anim.stateSerialization.fromJSON(snapshot.current.state)
                 : snapshot.current.state;
             this.triggerUpdate();
-            if (this.latestState.animationState === 'playing') this.resume();
+            if (this.state.animationState === 'playing') this.resume();
         }
     }
 
diff --git a/src/mol-plugin/ui/base.tsx b/src/mol-plugin/ui/base.tsx
index db43db77315ab37271bdd5b436115d5176272961..d4ae0171d84882a5f0c9fb6e2152b4fbfd5e2d83 100644
--- a/src/mol-plugin/ui/base.tsx
+++ b/src/mol-plugin/ui/base.tsx
@@ -10,7 +10,7 @@ import { PluginContext } from '../context';
 
 export const PluginReactContext = React.createContext(void 0 as any as PluginContext);
 
-export abstract class PluginComponent<P = {}, S = {}, SS = {}> extends React.Component<P, S, SS> {
+export abstract class PluginUIComponent<P = {}, S = {}, SS = {}> extends React.Component<P, S, SS> {
     static contextType = PluginReactContext;
     readonly plugin: PluginContext;
 
@@ -35,7 +35,7 @@ export abstract class PluginComponent<P = {}, S = {}, SS = {}> extends React.Com
     }
 }
 
-export abstract class PurePluginComponent<P = {}, S = {}, SS = {}> extends React.PureComponent<P, S, SS> {
+export abstract class PurePluginUIComponent<P = {}, S = {}, SS = {}> extends React.PureComponent<P, S, SS> {
     static contextType = PluginReactContext;
     readonly plugin: PluginContext;
 
diff --git a/src/mol-plugin/ui/camera.tsx b/src/mol-plugin/ui/camera.tsx
index ddcf5995d5fff6c62de49ea2e04d0f150da79a69..c818c3068a14acc6a0435a59c79f3c4448c6a056 100644
--- a/src/mol-plugin/ui/camera.tsx
+++ b/src/mol-plugin/ui/camera.tsx
@@ -6,11 +6,11 @@
 
 import { PluginCommands } from 'mol-plugin/command';
 import * as React from 'react';
-import { PluginComponent } from './base';
+import { PluginUIComponent } from './base';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { ParameterControls } from './controls/parameters';
 
-export class CameraSnapshots extends PluginComponent<{ }, { }> {
+export class CameraSnapshots extends PluginUIComponent<{ }, { }> {
     render() {
         return <div>
             <div className='msp-section-header'>Camera Snapshots</div>
@@ -20,7 +20,7 @@ export class CameraSnapshots extends PluginComponent<{ }, { }> {
     }
 }
 
-class CameraSnapshotControls extends PluginComponent<{ }, { name: string, description: string }> {
+class CameraSnapshotControls extends PluginUIComponent<{ }, { name: string, description: string }> {
     static Params = {
         name: PD.Text(),
         description: PD.Text()
@@ -48,7 +48,7 @@ class CameraSnapshotControls extends PluginComponent<{ }, { name: string, descri
     }
 }
 
-class CameraSnapshotList extends PluginComponent<{ }, { }> {
+class CameraSnapshotList extends PluginUIComponent<{ }, { }> {
     componentDidMount() {
         this.subscribe(this.plugin.events.state.cameraSnapshots.changed, () => this.forceUpdate());
     }
diff --git a/src/mol-plugin/ui/controls.tsx b/src/mol-plugin/ui/controls.tsx
index 2a29958116c3495a7dc844ea0dd2e10f4643761a..db69594a3c13abea794a75df957abdd9dad58e19 100644
--- a/src/mol-plugin/ui/controls.tsx
+++ b/src/mol-plugin/ui/controls.tsx
@@ -7,10 +7,10 @@
 import * as React from 'react';
 import { PluginCommands } from 'mol-plugin/command';
 import { UpdateTrajectory } from 'mol-plugin/state/actions/basic';
-import { PluginComponent } from './base';
+import { PluginUIComponent } from './base';
 import { LociLabelEntry } from 'mol-plugin/util/loci-label-manager';
 
-export class Controls extends PluginComponent<{ }, { }> {
+export class Controls extends PluginUIComponent<{ }, { }> {
     render() {
         return <>
 
@@ -18,7 +18,7 @@ export class Controls extends PluginComponent<{ }, { }> {
     }
 }
 
-export class TrajectoryControls extends PluginComponent {
+export class TrajectoryControls extends PluginUIComponent {
     render() {
         return <div>
             <button className='msp-btn msp-btn-link' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
@@ -37,7 +37,7 @@ export class TrajectoryControls extends PluginComponent {
     }
 }
 
-export class LociLabelControl extends PluginComponent<{}, { entries: ReadonlyArray<LociLabelEntry> }> {
+export class LociLabelControl extends PluginUIComponent<{}, { entries: ReadonlyArray<LociLabelEntry> }> {
     state = { entries: [] }
 
     componentDidMount() {
diff --git a/src/mol-plugin/ui/plugin.tsx b/src/mol-plugin/ui/plugin.tsx
index f4c812c8889e1afc94cb1b4d4f10be33c58a33c2..e0ee8590fa071f3d79fbd924b49c8bb4d3931282 100644
--- a/src/mol-plugin/ui/plugin.tsx
+++ b/src/mol-plugin/ui/plugin.tsx
@@ -9,7 +9,7 @@ import { PluginContext } from '../context';
 import { StateTree } from './state-tree';
 import { Viewport, ViewportControls } from './viewport';
 import { Controls, TrajectoryControls, LociLabelControl } from './controls';
-import { PluginComponent, PluginReactContext } from './base';
+import { PluginUIComponent, PluginReactContext } from './base';
 import { CameraSnapshots } from './camera';
 import { StateSnapshots } from './state';
 import { List } from 'immutable';
@@ -39,9 +39,9 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
     }
 }
 
-class Layout extends PluginComponent {
+class Layout extends PluginUIComponent {
     componentDidMount() {
-        this.subscribe(this.plugin.layout.updated, () => this.forceUpdate());
+        this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
     }
 
     region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) {
@@ -53,7 +53,7 @@ class Layout extends PluginComponent {
     }
 
     render() {
-        const layout = this.plugin.layout.latestState;
+        const layout = this.plugin.layout.state;
         return <div className='msp-plugin'>
             <div className={`msp-plugin-content ${layout.isExpanded ? 'msp-layout-expanded' : 'msp-layout-standard msp-layout-standard-outside'}`}>
                 <div className={layout.showControls ? 'msp-layout-hide-top' : 'msp-layout-hide-top msp-layout-hide-right msp-layout-hide-bottom msp-layout-hide-left'}>
@@ -73,7 +73,7 @@ class Layout extends PluginComponent {
     }
 }
 
-export class ViewportWrapper extends PluginComponent {
+export class ViewportWrapper extends PluginUIComponent {
     render() {
         return <>
             <Viewport />
@@ -91,7 +91,7 @@ export class ViewportWrapper extends PluginComponent {
     }
 }
 
-export class State extends PluginComponent {
+export class State extends PluginUIComponent {
     componentDidMount() {
         this.subscribe(this.plugin.state.behavior.kind, () => this.forceUpdate());
     }
@@ -113,7 +113,7 @@ export class State extends PluginComponent {
     }
 }
 
-export class Log extends PluginComponent<{}, { entries: List<LogEntry> }> {
+export class Log extends PluginUIComponent<{}, { entries: List<LogEntry> }> {
     private wrapper = React.createRef<HTMLDivElement>();
 
     componentDidMount() {
@@ -151,7 +151,7 @@ export class Log extends PluginComponent<{}, { entries: List<LogEntry> }> {
     }
 }
 
-export class CurrentObject extends PluginComponent {
+export class CurrentObject extends PluginUIComponent {
     get current() {
         return this.plugin.state.behavior.currentObject.value;
     }
diff --git a/src/mol-plugin/ui/state-tree.tsx b/src/mol-plugin/ui/state-tree.tsx
index 59a5fc8c260476607d79dfe8a2bac39fd5ff1596..d91238056a0714691a169533c87f10c86155aa65 100644
--- a/src/mol-plugin/ui/state-tree.tsx
+++ b/src/mol-plugin/ui/state-tree.tsx
@@ -8,16 +8,16 @@ import * as React from 'react';
 import { PluginStateObject } from 'mol-plugin/state/objects';
 import { State, StateObject } from 'mol-state'
 import { PluginCommands } from 'mol-plugin/command';
-import { PluginComponent } from './base';
+import { PluginUIComponent } from './base';
 
-export class StateTree extends PluginComponent<{ state: State }> {
+export class StateTree extends PluginUIComponent<{ state: State }> {
     render() {
         const n = this.props.state.tree.root.ref;
         return <StateTreeNode state={this.props.state} nodeRef={n} />;
     }
 }
 
-class StateTreeNode extends PluginComponent<{ nodeRef: string, state: State }, { state: State, isCollapsed: boolean }> {
+class StateTreeNode extends PluginUIComponent<{ nodeRef: string, state: State }, { state: State, isCollapsed: boolean }> {
     is(e: State.ObjectEvent) {
         return e.ref === this.props.nodeRef && e.state === this.props.state;
     }
@@ -77,7 +77,7 @@ class StateTreeNode extends PluginComponent<{ nodeRef: string, state: State }, {
     }
 }
 
-class StateTreeNodeLabel extends PluginComponent<{ nodeRef: string, state: State }, { state: State, isCurrent: boolean, isCollapsed: boolean }> {
+class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: State }, { state: State, isCurrent: boolean, isCollapsed: boolean }> {
     is(e: State.ObjectEvent) {
         return e.ref === this.props.nodeRef && e.state === this.props.state;
     }
diff --git a/src/mol-plugin/ui/state.tsx b/src/mol-plugin/ui/state.tsx
index b9a16ad129c0e3dd4fc737170b9675fe757ba850..ed8c45d00816052ee185a6a24a062ec02aef3fae 100644
--- a/src/mol-plugin/ui/state.tsx
+++ b/src/mol-plugin/ui/state.tsx
@@ -6,14 +6,14 @@
 
 import { PluginCommands } from 'mol-plugin/command';
 import * as React from 'react';
-import { PluginComponent } from './base';
+import { PluginUIComponent } from './base';
 import { shallowEqual } from 'mol-util';
 import { List } from 'immutable';
 import { ParameterControls } from './controls/parameters';
 import { ParamDefinition as PD} from 'mol-util/param-definition';
 import { Subject } from 'rxjs';
 
-export class StateSnapshots extends PluginComponent<{ }, { serverUrl: string }> {
+export class StateSnapshots extends PluginUIComponent<{ }, { serverUrl: string }> {
     state = { serverUrl: 'https://webchem.ncbr.muni.cz/molstar-state' }
 
     updateServerUrl = (serverUrl: string) => { this.setState({ serverUrl }) };
@@ -31,7 +31,7 @@ export class StateSnapshots extends PluginComponent<{ }, { serverUrl: string }>
 // TODO: this is not nice: device some custom event system.
 const UploadedEvent = new Subject();
 
-class StateSnapshotControls extends PluginComponent<{ serverUrl: string, serverChanged: (url: string) => void }, { name: string, description: string, serverUrl: string, isUploading: boolean }> {
+class StateSnapshotControls extends PluginUIComponent<{ serverUrl: string, serverChanged: (url: string) => void }, { name: string, description: string, serverUrl: string, isUploading: boolean }> {
     state = { name: '', description: '', serverUrl: this.props.serverUrl, isUploading: false };
 
     static Params = {
@@ -93,7 +93,7 @@ class StateSnapshotControls extends PluginComponent<{ serverUrl: string, serverC
     }
 }
 
-class LocalStateSnapshotList extends PluginComponent<{ }, { }> {
+class LocalStateSnapshotList extends PluginUIComponent<{ }, { }> {
     componentDidMount() {
         this.subscribe(this.plugin.events.state.snapshots.changed, () => this.forceUpdate());
     }
@@ -121,7 +121,7 @@ class LocalStateSnapshotList extends PluginComponent<{ }, { }> {
 }
 
 type RemoteEntry = { url: string, removeUrl: string, timestamp: number, id: string, name: string, description: string }
-class RemoteStateSnapshotList extends PluginComponent<{ serverUrl: string }, { entries: List<RemoteEntry>, isFetching: boolean }> {
+class RemoteStateSnapshotList extends PluginUIComponent<{ serverUrl: string }, { entries: List<RemoteEntry>, isFetching: boolean }> {
     state = { entries: List<RemoteEntry>(), isFetching: false };
 
     componentDidMount() {
diff --git a/src/mol-plugin/ui/state/animation.tsx b/src/mol-plugin/ui/state/animation.tsx
index c25fc4edaa18045c164b7eb0395a23912a000b8d..64ebcc6c01f8474456d893097042057ca951cc17 100644
--- a/src/mol-plugin/ui/state/animation.tsx
+++ b/src/mol-plugin/ui/state/animation.tsx
@@ -5,12 +5,12 @@
  */
 
 import * as React from 'react';
-import { PluginComponent } from '../base';
+import { PluginUIComponent } from '../base';
 import { ParameterControls, ParamOnChange } from '../controls/parameters';
 
-export class AnimationControls extends PluginComponent<{ }> {
+export class AnimationControls extends PluginUIComponent<{ }> {
     componentDidMount() {
-        this.subscribe(this.plugin.state.animation.updated, () => this.forceUpdate());
+        this.subscribe(this.plugin.state.animation.events.updated, () => this.forceUpdate());
     }
 
     updateParams: ParamOnChange = p => {
@@ -23,7 +23,7 @@ export class AnimationControls extends PluginComponent<{ }> {
 
     startOrStop = () => {
         const anim = this.plugin.state.animation;
-        if (anim.latestState.animationState === 'playing') anim.stop();
+        if (anim.state.animationState === 'playing') anim.stop();
         else anim.start();
     }
 
@@ -31,17 +31,17 @@ export class AnimationControls extends PluginComponent<{ }> {
         const anim = this.plugin.state.animation;
         if (anim.isEmpty) return null;
 
-        const isDisabled = anim.latestState.animationState === 'playing';
+        const isDisabled = anim.state.animationState === 'playing';
 
         return <div className='msp-animation-section'>
             <div className='msp-section-header'>Animations</div>
 
-            <ParameterControls params={anim.getParams()} values={anim.latestState.params} onChange={this.updateParams} isDisabled={isDisabled} />
+            <ParameterControls params={anim.getParams()} values={anim.state.params} onChange={this.updateParams} isDisabled={isDisabled} />
             <ParameterControls params={anim.current.params} values={anim.current.paramValues} onChange={this.updateCurrentParams} isDisabled={isDisabled} />
 
             <div className='msp-btn-row-group'>
                 <button className='msp-btn msp-btn-block msp-form-control' onClick={this.startOrStop}>
-                    {anim.latestState.animationState === 'playing' ? 'Stop' : 'Start'}
+                    {anim.state.animationState === 'playing' ? 'Stop' : 'Start'}
                 </button>
             </div>
         </div>
diff --git a/src/mol-plugin/ui/state/common.tsx b/src/mol-plugin/ui/state/common.tsx
index daf5f2568aeca146e4cbf638d614a67ddc2906cf..b6570cbafc1cff2fbbe78c3fc2b7a3b9003a5e90 100644
--- a/src/mol-plugin/ui/state/common.tsx
+++ b/src/mol-plugin/ui/state/common.tsx
@@ -6,7 +6,7 @@
 
 import { State, Transform, Transformer } from 'mol-state';
 import * as React from 'react';
-import { PurePluginComponent } from '../base';
+import { PurePluginUIComponent } from '../base';
 import { ParameterControls, ParamOnChange } from '../controls/parameters';
 import { StateAction } from 'mol-state/action';
 import { PluginContext } from 'mol-plugin/context';
@@ -15,7 +15,7 @@ import { Subject } from 'rxjs';
 
 export { StateTransformParameters, TransformContolBase };
 
-class StateTransformParameters extends PurePluginComponent<StateTransformParameters.Props> {
+class StateTransformParameters extends PurePluginUIComponent<StateTransformParameters.Props> {
     validate(params: any) {
         // TODO
         return void 0;
@@ -97,7 +97,7 @@ namespace TransformContolBase {
     }
 }
 
-abstract class TransformContolBase<P, S extends TransformContolBase.ControlState> extends PurePluginComponent<P, S> {
+abstract class TransformContolBase<P, S extends TransformContolBase.ControlState> extends PurePluginUIComponent<P, S> {
     abstract applyAction(): Promise<void>;
     abstract getInfo(): StateTransformParameters.Props['info'];
     abstract getHeader(): Transformer.Definition['display'];
diff --git a/src/mol-plugin/ui/task.tsx b/src/mol-plugin/ui/task.tsx
index c45b783ea05824070df202b5e24712b37cf61c09..20b1c7f14a072a0276ef709d4893c9f0785e8f8f 100644
--- a/src/mol-plugin/ui/task.tsx
+++ b/src/mol-plugin/ui/task.tsx
@@ -5,13 +5,13 @@
  */
 
 import * as React from 'react';
-import { PluginComponent } from './base';
+import { PluginUIComponent } from './base';
 import { OrderedMap } from 'immutable';
 import { TaskManager } from 'mol-plugin/util/task-manager';
 import { filter } from 'rxjs/operators';
 import { Progress } from 'mol-task';
 
-export class BackgroundTaskProgress extends PluginComponent<{ }, { tracked: OrderedMap<number, TaskManager.ProgressEvent> }> {
+export class BackgroundTaskProgress extends PluginUIComponent<{ }, { tracked: OrderedMap<number, TaskManager.ProgressEvent> }> {
     componentDidMount() {
         this.subscribe(this.plugin.events.task.progress.pipe(filter(e => e.level !== 'none')), e => {
             this.setState({ tracked: this.state.tracked.set(e.id, e) })
@@ -30,7 +30,7 @@ export class BackgroundTaskProgress extends PluginComponent<{ }, { tracked: Orde
     }
 }
 
-class ProgressEntry extends PluginComponent<{ event: TaskManager.ProgressEvent }> {
+class ProgressEntry extends PluginUIComponent<{ event: TaskManager.ProgressEvent }> {
     render() {
         const root = this.props.event.progress.root;
         const subtaskCount = countSubtasks(this.props.event.progress.root) - 1;
diff --git a/src/mol-plugin/ui/viewport.tsx b/src/mol-plugin/ui/viewport.tsx
index b23bf8977b3a8aa7e33481ba23ae61b17863d00b..ce917a04dbbaa269c33d1c9115bdec8f818f5baf 100644
--- a/src/mol-plugin/ui/viewport.tsx
+++ b/src/mol-plugin/ui/viewport.tsx
@@ -8,7 +8,7 @@
 import * as React from 'react';
 import { ButtonsType } from 'mol-util/input/input-observer';
 import { Canvas3dIdentifyHelper } from 'mol-plugin/util/canvas3d-identify';
-import { PluginComponent } from './base';
+import { PluginUIComponent } from './base';
 import { PluginCommands } from 'mol-plugin/command';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { ParameterControls } from './controls/parameters';
@@ -20,7 +20,7 @@ interface ViewportState {
     noWebGl: boolean
 }
 
-export class ViewportControls extends PluginComponent {
+export class ViewportControls extends PluginUIComponent {
     state = {
         isSettingsExpanded: false
     }
@@ -35,11 +35,11 @@ export class ViewportControls extends PluginComponent {
     }
 
     toggleControls = () => {
-        PluginCommands.Layout.Update.dispatch(this.plugin, { state: { showControls: !this.plugin.layout.latestState.showControls } });
+        PluginCommands.Layout.Update.dispatch(this.plugin, { state: { showControls: !this.plugin.layout.state.showControls } });
     }
 
     toggleExpanded = () => {
-        PluginCommands.Layout.Update.dispatch(this.plugin, { state: { isExpanded: !this.plugin.layout.latestState.isExpanded } });
+        PluginCommands.Layout.Update.dispatch(this.plugin, { state: { isExpanded: !this.plugin.layout.state.isExpanded } });
     }
 
     setSettings = (p: { param: PD.Base<any>, name: string, value: any }) => {
@@ -55,7 +55,7 @@ export class ViewportControls extends PluginComponent {
             this.forceUpdate();
         });
 
-        this.subscribe(this.plugin.layout.updated, () => {
+        this.subscribe(this.plugin.layout.events.updated, () => {
             this.forceUpdate();
         });
     }
@@ -73,15 +73,15 @@ export class ViewportControls extends PluginComponent {
         // TODO: show some icons dimmed etc..
         return <div className={'msp-viewport-controls'}>
             <div className='msp-viewport-controls-buttons'>
-                {this.icon('tools', this.toggleControls, 'Toggle Controls', this.plugin.layout.latestState.showControls)}
-                {this.icon('expand-layout', this.toggleExpanded, 'Toggle Expanded', this.plugin.layout.latestState.isExpanded)}
+                {this.icon('tools', this.toggleControls, 'Toggle Controls', this.plugin.layout.state.showControls)}
+                {this.icon('expand-layout', this.toggleExpanded, 'Toggle Expanded', this.plugin.layout.state.isExpanded)}
                 {this.icon('settings', this.toggleSettingsExpanded, 'Settings', this.state.isSettingsExpanded)}
                 {this.icon('reset-scene', this.resetCamera, 'Reset Camera')}
             </div>
             {this.state.isSettingsExpanded &&
             <div className='msp-viewport-controls-scene-options'>
                 <ControlGroup header='Layout' initialExpanded={true}>
-                    <ParameterControls params={PluginLayoutStateParams} values={this.plugin.layout.latestState} onChange={this.setLayout} />
+                    <ParameterControls params={PluginLayoutStateParams} values={this.plugin.layout.state} onChange={this.setLayout} />
                 </ControlGroup>
                 <ControlGroup header='Viewport' initialExpanded={true}>
                     <ParameterControls params={Canvas3DParams} values={this.plugin.canvas3d.props} onChange={this.setSettings} />
@@ -91,7 +91,7 @@ export class ViewportControls extends PluginComponent {
     }
 }
 
-export class Viewport extends PluginComponent<{ }, ViewportState> {
+export class Viewport extends PluginUIComponent<{ }, ViewportState> {
     private container = React.createRef<HTMLDivElement>();
     private canvas = React.createRef<HTMLCanvasElement>();
 
@@ -128,7 +128,7 @@ export class Viewport extends PluginComponent<{ }, ViewportState> {
             idHelper.select(x, y);
         });
 
-        this.subscribe(this.plugin.layout.updated, () => {
+        this.subscribe(this.plugin.layout.events.updated, () => {
             setTimeout(this.handleResize, 50);
         });
     }