diff --git a/common/constants.js b/common/constants.js
index e9693fcf1c8a21e001c6bd539597e8cf7746110f..d9d3e6c4ff1bf4558e08aeb977d988beaed47a54 100644
--- a/common/constants.js
+++ b/common/constants.js
@@ -61,14 +61,13 @@
 
     // Annotations
     USER_ANNOTATION_VIEWER: 'user annotations viewer',
-    USER_ANNOTATION_HEADER: 'user annotations header',
     USER_ANNOTATION_LIST: 'user annotations footer',
     USER_ANNOTATION_IMPORT: 'user annotations import',
     USER_ANNOTATION_EXPORT: 'user annotations export',
     USER_ANNOTATION_EXPORT_SINGLE: 'user annotations export single',
     USER_ANNOTATION_HIDE: 'user annotations hide',
     USER_ANNOTATION_DELETE: 'user annotations delete',
-
+    GOTO_ANNOTATION_ROI: 'Navigate to annotation location of interest.'
 
   }
 
diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts
index 23c6557a043660af2298a50a31792900d462224b..141c36903dd66dcdfcef381542fdfe2a4c537446 100644
--- a/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts
+++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.component.ts
@@ -3,18 +3,35 @@ import {AnnotationService} from "src/atlasComponents/userAnnotations/annotationS
 import {viewerStateChangeNavigation} from "src/services/state/viewerState/actions";
 import {Store} from "@ngrx/store";
 import {ARIA_LABELS} from "common/constants";
+import { ModularUserAnnotationToolService } from "../tools/service";
+import { EXPORT_FORMAT_INJ_TOKEN, TExportFormats } from "../tools/type";
+
 
 @Component({
   selector: 'annotation-list',
   templateUrl: './annotationList.template.html',
-  styleUrls: ['./annotationList.style.css']
+  styleUrls: ['./annotationList.style.css'],
+  providers: [{
+    provide: EXPORT_FORMAT_INJ_TOKEN,
+    useFactory: (svc: ModularUserAnnotationToolService) => svc.exportFormat$,
+    deps: [
+      ModularUserAnnotationToolService
+    ]
+  }]
 })
 export class AnnotationList {
 
   public ARIA_LABELS = ARIA_LABELS
   public identifier = (index: number, item: any) => item.id
 
-  constructor(private store$: Store<any>, public ans: AnnotationService) {}
+  public availableFormat: TExportFormats[] = ['json', 'sands', 'string']
+  public exportFromat$ = this.annotSvc.exportFormat$
+  public selectExportFormat(format: TExportFormats) {
+    this.exportFromat$.next(format)
+  }
+
+  public managedAnnotations$ = this.annotSvc.managedAnnotations$
+  constructor(private store$: Store<any>, public ans: AnnotationService, private annotSvc: ModularUserAnnotationToolService) {}
 
   toggleAnnotationVisibility(annotation) {
     if (annotation.type === 'polygon') {
@@ -134,4 +151,8 @@ export class AnnotationList {
     })
   }
 
+  public hiddenAnnotations$ = this.annotSvc.hiddenAnnotations$
+  toggleManagedAnnotationVisibility(id: string) {
+    this.annotSvc.toggleAnnotationVisibilityById(id)
+  }
 }
diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html b/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html
index e977ff16b072e3b88f143b259b64a65e366e2e1c..5433e233e676d3177b710741a7c7c2eb4e2923fd 100644
--- a/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html
+++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html
@@ -1,55 +1,73 @@
 <div [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_VIEWER" class="w-100">
 
-    <div class="overflow-hidden mt-3 mr-2 ml-2">
-        <div [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_HEADER" class="d-flex justify-content-between">
-            <div>
-                <button class="mr-1 ml-1" mat-icon-button
-                        [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_IMPORT"
-                        matTooltip="Import JSON"
-                        [matMenuTriggerFor]="importMenu">
-                    <i class="fas fa-file-import"></i>
+    <!-- header section -->
+    <div class="overflow-hidden mt-3 mr-2 ml-2 d-flex justify-content-between">
+        <div>
+            <button class="mr-1 ml-1" mat-icon-button
+                    [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_IMPORT"
+                    matTooltip="Import JSON"
+                    [matMenuTriggerFor]="importMenu">
+                <i class="fas fa-file-import"></i>
+            </button>
+            <input type="file" #importInput [import-annotations]="{sands: false}" hidden/>
+            <input type="file" #importInputSands [import-annotations]="{sands: true}" hidden/>
+            <mat-menu #importMenu="matMenu">
+                <button mat-menu-item (click)="importInputSands.click()">
+                    SANDS format
                 </button>
-                <input type="file" #importInput [import-annotations]="{sands: false}" hidden/>
-                <input type="file" #importInputSands [import-annotations]="{sands: true}" hidden/>
-                <mat-menu #importMenu="matMenu">
-                    <button mat-menu-item (click)="importInputSands.click()">
-                        SANDS format
-                    </button>
-                    <button mat-menu-item (click)="importInput.click()">
-                        Siibra explorer format
-                    </button>
-                </mat-menu>
-
-                <button class="mr-1 ml-1" mat-icon-button
-                        [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_EXPORT"
-                        matTooltip="Export"
-                        [matMenuTriggerFor]="exportAllMenu">
-                    <i class="fas fa-file-export"></i>
+                <button mat-menu-item (click)="importInput.click()">
+                    Siibra explorer format
                 </button>
-                <mat-menu #exportAllMenu="matMenu">
-                    <button mat-menu-item [export-annotations]="{annotations: ans.finalAnnotationList, sands: true}">
-                        SANDS format
-                    </button>
-                    <button mat-menu-item [export-annotations]="{annotations: ans.finalAnnotationList, sands: false}">
-                        Siibra explorer format
-                    </button>
-                </mat-menu>
+            </mat-menu>
+
+            <button class="mr-1 ml-1" mat-icon-button
+                    [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_EXPORT"
+                    matTooltip="Export"
+                    [matMenuTriggerFor]="exportAllMenu">
+                <i class="fas fa-file-export"></i>
+            </button>
+            <mat-menu #exportAllMenu="matMenu">
+                <button mat-menu-item [export-annotations]="{annotations: ans.finalAnnotationList, sands: true}">
+                    SANDS format
+                </button>
+                <button mat-menu-item [export-annotations]="{annotations: ans.finalAnnotationList, sands: false}">
+                    Siibra explorer format
+                </button>
+            </mat-menu>
 
-            </div>
-            <div class="d-flex flex-column">
-                <small [ngClass]="[ans.annotationFilter !== 'all'? 'text-muted' : '']"
-                       class="cursor-pointer" (click)="ans.refreshFinalAnnotationList('all')">
-                    All landmarks
-                </small>
-                <small [ngClass]="[ans.annotationFilter !== 'current'? 'text-muted' : '']"
-                       class="cursor-pointer" (click)="ans.refreshFinalAnnotationList('current')">
-                    Current template
-                </small>
-            </div>
+        </div>
+
+        <!-- select export format -->
+        <button mat-icon-button [matMenuTriggerFor]="exportFormatMenu">
+            <i class="fas fa-code"></i>
+        </button>
+
+        <mat-menu #exportFormatMenu="matMenu">
+            <button *ngFor="let format of availableFormat"
+                mat-menu-item
+                (click)="selectExportFormat(format)">
+                <span class="iv-custom-comp text"
+                    [ngClass]="{'primary': (exportFromat$ | async) === format}">
+                    {{ format }}
+                </span>
+            </button>
+        </mat-menu>
+
+        <div class="d-flex flex-column">
+            <small [ngClass]="[ans.annotationFilter !== 'all'? 'text-muted' : '']"
+                   class="cursor-pointer" (click)="ans.refreshFinalAnnotationList('all')">
+                All landmarks
+            </small>
+            <small [ngClass]="[ans.annotationFilter !== 'current'? 'text-muted' : '']"
+                   class="cursor-pointer" (click)="ans.refreshFinalAnnotationList('current')">
+                Current template
+            </small>
         </div>
     </div>
-    <mat-divider class="mt-2 mb-2"></mat-divider>
 
+    <mat-divider class="m-2"></mat-divider>
+
+    <!-- list of annotations -->
     <mat-accordion [attr.aria-label]="ARIA_LABELS.USER_ANNOTATION_LIST"
                    class="h-100 d-flex flex-column overflow-auto">
         <mat-expansion-panel hideToggle
@@ -183,6 +201,34 @@
 
         </mat-expansion-panel>
 
+        <!-- expansion panel -->
+        <mat-expansion-panel *ngFor="let managedAnnotation of managedAnnotations$ | async"
+            hideToggle>
+
+            <mat-expansion-panel-header>
+                <mat-panel-title class="d-flex align-items-center">
+
+                    <!-- toggle visibility -->
+                    <button
+                        mat-icon-button
+                        iav-stop="click"
+                        (click)="toggleManagedAnnotationVisibility(managedAnnotation.id)">
+                        <i [ngClass]="(hiddenAnnotations$ | async | annotationVisiblePipe : managedAnnotation) ?  'fa-eye' : 'fa-eye-slash'" class="fas"></i>
+                    </button>
+                    <span class="flex-shrink-1 flex-grow-1" [ngClass]="{ 'text-muted': !managedAnnotation.name }">
+                        {{ managedAnnotation | singleAnnotationNamePipe : managedAnnotation.name }}
+                    </span>    
+                    <i class="flex-shrink-0 flex-grow-0" [ngClass]="managedAnnotation | singleannotationClsIconPipe"></i>
+                </mat-panel-title>
+            </mat-expansion-panel-header>
+
+            <!-- single annotation edit body -->
+            <ng-template matExpansionPanelContent>
+                <single-annotation-unit
+                    [single-annotation-unit-annotation]="managedAnnotation">
+                </single-annotation-unit>
+            </ng-template>
+        </mat-expansion-panel>
     </mat-accordion>
 
 </div>
diff --git a/src/atlasComponents/userAnnotations/annotationVisible.pipe.ts b/src/atlasComponents/userAnnotations/annotationVisible.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..99f7c04de5deabc44875026082b1339773106523
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/annotationVisible.pipe.ts
@@ -0,0 +1,13 @@
+import { Pipe, PipeTransform } from "@angular/core";
+import { IAnnotationGeometry } from "./tools/type";
+
+@Pipe({
+  name: 'annotationVisiblePipe',
+  pure: true
+})
+
+export class AnnotationVisiblePipe implements PipeTransform{
+  public transform(hiddenAnns: IAnnotationGeometry[], thisAnn: IAnnotationGeometry): boolean {
+    return hiddenAnns.findIndex(a => a.id === thisAnn.id) < 0
+  }
+}
diff --git a/src/atlasComponents/userAnnotations/module.ts b/src/atlasComponents/userAnnotations/module.ts
index d0df24dc7ec2b80ef2e374a477e26b1b4b5b454d..f79e3e2f9c657dcc3859fffeda99219d930cff84 100644
--- a/src/atlasComponents/userAnnotations/module.ts
+++ b/src/atlasComponents/userAnnotations/module.ts
@@ -15,6 +15,8 @@ import {ImportAnnotation} from "src/atlasComponents/userAnnotations/directives/i
 import {KeyListener} from "src/atlasComponents/userAnnotations/directives/keyListener.directive";
 import {CoordinateInputTextPipe} from "src/atlasComponents/userAnnotations/annotationList/coordinateInputText.pipe";
 import { UtilModule } from "src/util";
+import { SingleAnnotationClsIconPipe, SingleAnnotationNamePipe, SingleAnnotationUnit } from "./singleAnnotationUnit/singleAnnotationUnit.component";
+import { AnnotationVisiblePipe } from "./annotationVisible.pipe";
 
 @NgModule({
   imports: [
@@ -35,7 +37,11 @@ import { UtilModule } from "src/util";
     ImportAnnotation,
     ExportAnnotation,
     KeyListener,
-    CoordinateInputTextPipe
+    CoordinateInputTextPipe,
+    SingleAnnotationUnit,
+    SingleAnnotationNamePipe,
+    SingleAnnotationClsIconPipe,
+    AnnotationVisiblePipe,
   ],
   providers: [
     AnnotationService
diff --git a/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.component.ts b/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e69df14b0f8bf8106cd266a3289b6511d6aee265
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.component.ts
@@ -0,0 +1,132 @@
+import { AfterViewInit, Component, ComponentFactoryResolver, Inject, Injector, Input, OnDestroy, Optional, Pipe, PipeTransform, ViewChild, ViewContainerRef } from "@angular/core";
+import { EXPORT_FORMAT_INJ_TOKEN, IAnnotationGeometry, TExportFormats, UDPATE_ANNOTATION_TOKEN } from "../tools/type";
+import { Point } from '../tools/point'
+import { Polygon } from '../tools/poly'
+import { FormControl, FormGroup } from "@angular/forms";
+import { Observable, Subscription } from "rxjs";
+import { select, Store } from "@ngrx/store";
+import { viewerStateFetchedAtlasesSelector } from "src/services/state/viewerState/selectors";
+import { ModularUserAnnotationToolService } from "../tools/service";
+import { MatSnackBar } from "@angular/material/snack-bar";
+
+@Component({
+  selector: 'single-annotation-unit',
+  templateUrl: './singleAnnotationUnit.template.html',
+  styleUrls: [
+    './singleAnnotationUnit.style.css',
+  ]
+})
+
+export class SingleAnnotationUnit implements OnDestroy, AfterViewInit{
+  @Input('single-annotation-unit-annotation')
+  public managedAnnotation: IAnnotationGeometry
+
+  public formGrp: FormGroup
+
+  @ViewChild('editAnnotationVCRef', { read: ViewContainerRef })
+  editAnnotationVCR: ViewContainerRef
+
+  private chSubs: Subscription[] = []
+  private subs: Subscription[] = []
+  public templateSpaces: {
+    ['@id']: string
+    name: string
+  }[] = []
+  ngOnChanges(){
+    while(this.chSubs.length > 0) this.chSubs.pop().unsubscribe()
+    
+    this.formGrp = new FormGroup({
+      name: new FormControl(this.managedAnnotation.name),
+      spaceId: new FormControl({
+        value: this.managedAnnotation.space["@id"],
+        disabled: true
+      }),
+      desc: new FormControl(this.managedAnnotation.desc),
+    })
+
+    this.chSubs.push(
+      this.formGrp.valueChanges.subscribe(value => {
+        const { name, desc, spaceId } = value
+        this.managedAnnotation.setName(name)
+        this.managedAnnotation.setDesc(desc)
+      })
+    )
+
+  }
+
+  constructor(
+    store: Store<any>,
+    private snackbar: MatSnackBar,
+    private svc: ModularUserAnnotationToolService,
+    private cfr: ComponentFactoryResolver,
+    @Optional() @Inject(EXPORT_FORMAT_INJ_TOKEN) private useFormat$: Observable<TExportFormats>,
+  ){
+    this.subs.push(
+      store.pipe(
+        select(viewerStateFetchedAtlasesSelector),
+      ).subscribe(atlases => {
+        for (const atlas of atlases) {
+          for (const tmpl of atlas.templateSpaces) {
+            this.templateSpaces.push({
+              '@id': tmpl['@id'],
+              name: tmpl.name
+            })
+          }
+        }
+      })
+    )
+  }
+
+  ngAfterViewInit(){
+    if (this.managedAnnotation && this.editAnnotationVCR) {
+      const editCmp = this.svc.getEditAnnotationCmp(this.managedAnnotation)
+      if (!editCmp) {
+        this.snackbar.open(`Update component not found!`)
+        throw new Error(`Edit component not found!`)
+      }
+      const cf = this.cfr.resolveComponentFactory(editCmp)
+      const injector = Injector.create({
+        providers: [{
+          provide: UDPATE_ANNOTATION_TOKEN,
+          useValue: this.managedAnnotation
+        }, {
+          provide: EXPORT_FORMAT_INJ_TOKEN,
+          useValue: this.useFormat$
+        }]
+      })
+      this.editAnnotationVCR.createComponent(cf, null, injector)
+    }
+  }
+
+  ngOnDestroy(){
+    while (this.subs.length > 0) this.subs.pop().unsubscribe()
+  }
+
+}
+
+@Pipe({
+  name: 'singleAnnotationNamePipe',
+  pure: true
+})
+
+export class SingleAnnotationNamePipe implements PipeTransform{
+  public transform(ann: IAnnotationGeometry, name?: string): string{
+    if (name) return name
+    if (ann instanceof Polygon) return `Unnamed Polygon`
+    if (ann instanceof Point) return `Unname Point`
+    return `Unnamed geometry`
+  }
+}
+
+@Pipe({
+  name: 'singleannotationClsIconPipe',
+  pure: true
+})
+
+export class SingleAnnotationClsIconPipe implements PipeTransform{
+  public transform(ann: IAnnotationGeometry): string{
+    if (ann instanceof Polygon) return `fas fa-draw-polygon`
+    if (ann instanceof Point) return `fas fa-circle`
+    return `fas fa-mouse-pointer`
+  }
+}
\ No newline at end of file
diff --git a/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.style.css b/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.template.html b/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..9dfe60e8dcf6249b1ac16053e87c5c9bbaea8632
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.template.html
@@ -0,0 +1,38 @@
+<form [formGroup]="formGrp" class="d-flex flex-column" autocomplete="off">
+  <mat-form-field>
+    <mat-label>
+      Space
+    </mat-label>
+    <mat-select formControlName="spaceId">
+      <mat-option *ngFor="let tmpl of templateSpaces" [value]="tmpl['@id']">
+        {{ tmpl.name }}
+      </mat-option>
+    </mat-select>
+  </mat-form-field>
+
+  <mat-form-field>
+    <mat-label>
+      Name
+    </mat-label>
+    <input
+      type="text"
+      matInput
+      formControlName="name"
+      [placeholder]="managedAnnotation | singleAnnotationNamePipe">
+  </mat-form-field>
+
+  <mat-form-field>
+    <mat-label>
+      Description
+    </mat-label>
+    <textarea
+      matInput
+      formControlName="desc">
+    </textarea>
+  </mat-form-field>
+</form>
+
+<mat-divider class="m-2"></mat-divider>
+
+<ng-template #editAnnotationVCRef>
+</ng-template>
diff --git a/src/atlasComponents/userAnnotations/tools/module.ts b/src/atlasComponents/userAnnotations/tools/module.ts
index a56d582d4dd917c103bc1e202cc5178d606e257a..d68658dbe2de34be90eae56127baa6dec4f21ed8 100644
--- a/src/atlasComponents/userAnnotations/tools/module.ts
+++ b/src/atlasComponents/userAnnotations/tools/module.ts
@@ -1,9 +1,29 @@
+import { CommonModule } from "@angular/common";
 import { NgModule } from "@angular/core";
 import { Subject } from "rxjs";
+import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module";
+import { UtilModule } from "src/util";
+import { PointUpdateCmp } from "./point/point.component";
+import { PolyUpdateCmp } from "./poly/poly.component";
 import { ModularUserAnnotationToolService } from "./service";
+import { ToFormattedStringPipe } from "./toFormattedString.pipe";
 import { ANNOTATION_EVENT_INJ_TOKEN } from "./type";
 
 @NgModule({
+  imports: [
+    CommonModule,
+    AngularMaterialModule,
+    UtilModule,
+  ],
+  declarations: [
+    PolyUpdateCmp,
+    PointUpdateCmp,
+    ToFormattedStringPipe,
+  ],
+  exports: [
+    PolyUpdateCmp,
+    PointUpdateCmp,
+  ],
   providers: [
     {
       provide: ANNOTATION_EVENT_INJ_TOKEN,
diff --git a/src/atlasComponents/userAnnotations/tools/point.ts b/src/atlasComponents/userAnnotations/tools/point.ts
index 8a235861875ff2c971a5cd0809e606dd6a166906..23649b38e1be13f4d4fc0d0cfcbb6fcf3a804322 100644
--- a/src/atlasComponents/userAnnotations/tools/point.ts
+++ b/src/atlasComponents/userAnnotations/tools/point.ts
@@ -1,5 +1,14 @@
-import { AbsToolClass, IAnnotationEvents, IAnnotationGeometry, IAnnotationTools, INgAnnotationTypes, TAnnotationEvent, TNgAnnotationEv, TToolType } from "./type";
-import { Observable, Subject } from "rxjs";
+import { AbsToolClass, getCoord, IAnnotationEvents, IAnnotationGeometry, IAnnotationTools, INgAnnotationTypes, TAnnotationEvent, TBaseAnnotationGeomtrySpec, TNgAnnotationEv, TSandsPoint, TToolType } from "./type";
+import { merge, Observable, Subject, Subscription } from "rxjs";
+import { OnDestroy } from "@angular/core";
+import { filter, switchMapTo, takeUntil } from "rxjs/operators";
+
+export type TPointJsonSpec = {
+  x: number
+  y: number
+  z: number
+  '@type': 'siibra-ex/annotatoin/point'
+} & TBaseAnnotationGeomtrySpec
 
 export class Point extends IAnnotationGeometry {
   id: string
@@ -13,16 +22,20 @@ export class Point extends IAnnotationGeometry {
       && Math.abs(p1.y - p2.y) < Point.threshold
       && Math.abs(p1.z - p2.z) < Point.threshold
   }
-  constructor(arr: number[] = [], id?: string){
-    super({id})
-    if (arr.length !== 3) throw new Error(`constructor of points must be length 3`)
-    this.x = arr[0]
-    this.y = arr[1]
-    this.z = arr[2]
+  constructor(spec?: TPointJsonSpec){
+    super(spec)
+
+    this.x = spec.x || 0
+    this.y = spec.y || 0
+    this.z = spec.z || 0
+  }
+  toJSON(): TPointJsonSpec{
+    const { id, x, y, z, space, name, desc } = this
+    return { id, x, y, z, space, name, desc, '@type': 'siibra-ex/annotatoin/point' }
   }
-  toJSON(){
-    const { id, x, y, z } = this
-    return { id, x, y, z }
+
+  getNgAnnotationIds(){
+    return [this.id]
   }
   toNgAnnotation(): INgAnnotationTypes['point'][]{
     return [{
@@ -31,29 +44,121 @@ export class Point extends IAnnotationGeometry {
       type: 'point',
     }]
   }
-  static fromJSON(json: any) {
-    const { x, y, z, id } = json
-    return new Point([x, y, z], id)
+  static fromJSON(json: TPointJsonSpec) {
+    return new Point(json)
+  }
+
+  toString(){
+    return `${(this.x / 1e6).toFixed(2)}mm, ${(this.y / 1e6).toFixed(2)}mm, ${(this.z / 1e6).toFixed(2)}mm`
+  }
+
+  toSands(): TSandsPoint{
+    const {x, y, z} = this
+    return {
+      '@id': this.id,
+      '@type': 'https://openminds.ebrains.eu/sands/CoordinatePoint',
+      coordinateSpace: {
+        '@id': this.space["@id"]
+      },
+      coordinates:[ getCoord(x/1e6), getCoord(y/1e6), getCoord(z/1e6) ]
+    }
   }
 
-  public translate(x: number, y: number, z: number) {
+  public translate(x: number, y: number, z: number): void {
     this.x += x
     this.y += y
     this.z += z
+    this.updateSignal$.next(this.toString())
   }
 }
 
-export class ToolPoint extends AbsToolClass implements IAnnotationTools {
+export const POINT_ICON_CLASS='fas fa-circle'
+
+export class ToolPoint extends AbsToolClass implements IAnnotationTools, OnDestroy {
   static PREVIEW_ID='tool_point_preview'
   public name = 'Point'
   public toolType: TToolType = 'drawing'
-  public iconClass = 'fas fa-circle'
+  public iconClass = POINT_ICON_CLASS
+
+  private space: TBaseAnnotationGeomtrySpec['space']
+  
+  private subs: Subscription[] = []
   private managedAnnotations: Point[] = []
+  public managedAnnotations$ = new Subject<Point[]>()
   public allNgAnnotations$ = new Subject<INgAnnotationTypes[keyof INgAnnotationTypes][]>()
+  private forceRefresh$ = new Subject()
+
   constructor(
     annotationEv$: Observable<TAnnotationEvent<keyof IAnnotationEvents>>
   ){
     super(annotationEv$)
+
+    const toolDeselect$ = this.toolSelected$.pipe(
+      filter(flag => !flag)
+    )
+
+    const toolSelThenClick$ = this.toolSelected$.pipe(
+      filter(flag => !!flag),
+      switchMapTo(this.mouseClick$.pipe(
+        takeUntil(toolDeselect$)
+      ))
+    )
+
+    this.subs.push(
+      /**
+       * subscribe to space event space info
+       */
+      this.metadataEv$.subscribe(ev => {
+        this.space = ev.detail.space
+      }),
+      /**
+       * listen to click ev, add point when it occurrs
+       */
+      toolSelThenClick$.subscribe(ev => {
+        const {x, y, z} = ev.detail.ngMouseEvent
+        const { space } = this
+        this.managedAnnotations.push(
+          new Point({
+            x, y, z,
+            space,
+            '@type': 'siibra-ex/annotatoin/point'
+          })
+        )
+        this.managedAnnotations$.next(this.managedAnnotations)
+      }),
+
+      /**
+       * translate point
+       */
+      this.dragHoveredAnnotationsDelta$.subscribe(ev => {
+        const { ann, deltaX, deltaY, deltaZ } = ev
+        const { pickedAnnotationId, pickedOffset } = ann.detail
+        const foundAnn = this.managedAnnotations.find(ann => ann.id === pickedAnnotationId)
+        if (foundAnn) {
+          foundAnn.translate(deltaX, deltaY, deltaZ)
+          this.forceRefresh$.next(null)
+        }
+      }),
+      /**
+       * evts which forces redraw of ng annotations
+       */
+      merge(
+        toolSelThenClick$,
+        this.forceRefresh$,
+      ).subscribe(() => {
+        let out: INgAnnotationTypes['point'][] = []
+        for (const managedAnn of this.managedAnnotations) {
+          if (managedAnn.space['@id'] === this.space['@id']) {
+            out = out.concat(...managedAnn.toNgAnnotation())
+          }
+        }
+        this.allNgAnnotations$.next(out)
+      })
+    )
+  }
+
+  removeAnnotation(id: string) {
+
   }
 
   onMouseMoveRenderPreview(pos: [number, number, number]) {
@@ -68,4 +173,8 @@ export class ToolPoint extends AbsToolClass implements IAnnotationTools {
   ngAnnotationIsRelevant(annotation: TNgAnnotationEv){
     return this.managedAnnotations.some(p => p.id === annotation.pickedAnnotationId)
   }
+
+  ngOnDestroy(){
+    while (this.subs.length > 0) this.subs.pop().unsubscribe()
+  }
 }
diff --git a/src/atlasComponents/userAnnotations/tools/point/point.component.ts b/src/atlasComponents/userAnnotations/tools/point/point.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..869a2c497bdfe169cf0f39589df4abea04fb546b
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/point/point.component.ts
@@ -0,0 +1,86 @@
+import { Component, ElementRef, Inject, Input, OnDestroy, Optional, ViewChild } from "@angular/core";
+import { MatSnackBar } from "@angular/material/snack-bar";
+import { Point, POINT_ICON_CLASS } from "../point";
+import { EXPORT_FORMAT_INJ_TOKEN, IAnnotationGeometry, TExportFormats, UDPATE_ANNOTATION_TOKEN } from "../type";
+import { Clipboard } from "@angular/cdk/clipboard";
+import { ToolCmpBase } from "../toolCmp.base";
+import { Store } from "@ngrx/store";
+import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions";
+import { Observable, Subscription } from "rxjs";
+
+@Component({
+  selector: 'point-update-cmp',
+  templateUrl: './point.template.html',
+  styleUrls: [
+    './point.style.css',
+  ]
+})
+
+export class PointUpdateCmp extends ToolCmpBase implements OnDestroy{
+
+  public POINT_ICON_CLASS = POINT_ICON_CLASS
+
+  @Input('update-annotation')
+  updateAnnotation: Point
+
+  @Input('annotation-label')
+  annotationLabel = 'Point'
+
+  @Input('show-copy-button')
+  showCopyBtn = true
+
+  private sub: Subscription[] = []
+  public useFormat: TExportFormats = 'string'
+
+  @ViewChild('copyTarget', { read: ElementRef, static: false })
+  copyTarget: ElementRef
+
+  constructor(
+    private store: Store<any>,
+    snackbar: MatSnackBar,
+    clipboard: Clipboard,
+    @Optional() @Inject(EXPORT_FORMAT_INJ_TOKEN) useFormat$: Observable<TExportFormats>,
+    @Optional() @Inject(UDPATE_ANNOTATION_TOKEN) updateAnnotation: IAnnotationGeometry,
+  ){
+    super(clipboard, snackbar)
+    
+    if (useFormat$) {
+      this.sub.push(
+        useFormat$.subscribe(val => {
+          this.useFormat = val
+        })
+      )
+    }
+    if (updateAnnotation) {
+      if (updateAnnotation instanceof Point) {
+        this.updateAnnotation = updateAnnotation
+      }
+    }
+  }
+
+  ngOnDestroy(){
+    while (this.sub.length > 0) this.sub.pop().unsubscribe()
+  }
+
+  get copyValue(){
+    return this.copyTarget && this.copyTarget.nativeElement.value
+  }
+
+  gotoRoi(){
+
+    if (!this.updateAnnotation) {
+      throw new Error(`updateAnnotation undefined`)
+    }
+    const { x, y, z } = this.updateAnnotation
+    this.store.dispatch(
+      viewerStateChangeNavigation({
+        navigation: {
+          position: [x, y, z],
+          positionReal: true,
+          animation: {}
+        }
+      })
+    )
+  }
+
+}
diff --git a/src/atlasComponents/userAnnotations/tools/point/point.style.css b/src/atlasComponents/userAnnotations/tools/point/point.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/atlasComponents/userAnnotations/tools/point/point.template.html b/src/atlasComponents/userAnnotations/tools/point/point.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..2fd1334d2f77af8dc16ed69754528891f4ed157b
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/point/point.template.html
@@ -0,0 +1,32 @@
+<div class="d-flex align-items center">
+  <button mat-icon-button
+    class="flex-grow-0 flex-shrink-0"
+    [attr.aria-label]="ARIA_LABELS.GOTO_ANNOTATION_ROI"
+    (click)="gotoRoi()">
+    <i [class]="POINT_ICON_CLASS"></i>
+  </button>
+
+  <form class="flex-grow-1 flex-shrink-1">
+    <mat-form-field class="w-100">
+      <mat-label>
+        {{ annotationLabel }}
+      </mat-label>
+
+      <textarea matInput
+        disabled="true"
+        #copyTarget>{{ updateAnnotation.updateSignal$ | async | toFormattedStringPipe : updateAnnotation : useFormat }}</textarea>
+
+      <!-- copy to clipboard button -->
+      <button mat-icon-button
+        matSuffix
+        iav-stop="click"
+        aria-label="Copy to clipboard"
+        matTooltip="Copy to clipboard."
+        (click)="copyToClipboard(copyValue)"
+        *ngIf="showCopyBtn && !!copyTarget"
+        color="basic">
+        <i class="fas fa-copy"></i>
+      </button>
+    </mat-form-field>
+  </form>
+</div>
diff --git a/src/atlasComponents/userAnnotations/tools/poly.ts b/src/atlasComponents/userAnnotations/tools/poly.ts
index c26a7dde428c751a92512f1810da33c4aa410789..bbf19d6cdfed0ded3bdfe94fd7cc636ea814a8b1 100644
--- a/src/atlasComponents/userAnnotations/tools/poly.ts
+++ b/src/atlasComponents/userAnnotations/tools/poly.ts
@@ -1,31 +1,69 @@
-import { IAnnotationTools, IAnnotationGeometry, TAnnotationEvent, IAnnotationEvents, AbsToolClass, INgAnnotationTypes, TNgAnnotationEv, TToolType } from "./type";
-import { Point } from './point'
+import { IAnnotationTools, IAnnotationGeometry, TAnnotationEvent, IAnnotationEvents, AbsToolClass, INgAnnotationTypes, TNgAnnotationEv, TToolType, TBaseAnnotationGeomtrySpec, TSandsPolyLine, getCoord } from "./type";
+import { Point, TPointJsonSpec } from './point'
 import { OnDestroy } from "@angular/core";
-import { merge, Observable, of, Subject, Subscription } from "rxjs";
-import { filter, map, pairwise, switchMapTo, takeUntil, withLatestFrom } from "rxjs/operators";
+import { merge, Observable, Subject, Subscription } from "rxjs";
+import { filter, switchMapTo, takeUntil, withLatestFrom } from "rxjs/operators";
+import { getUuid } from "src/util/fn";
 
-class Polygon extends IAnnotationGeometry{
+type TPolyJsonSpec = {
+  points: TPointJsonSpec[]
+  edges: [number, number][]
+  '@type': 'siibra-ex/annotation/polyline'
+} & TBaseAnnotationGeomtrySpec
+
+export class Polygon extends IAnnotationGeometry{
   public id: string
 
-  private points: Point[] = []
-  private idCounter = 0
-  private edges: [number, number][] = []
+  public points: Point[] = []
+  public edges: [number, number][] = []
 
-  public hasPoint(p: Point) {
+  public hasPoint(p: Point): boolean {
     return this.points.indexOf(p) >= 0
   }
 
+  private ptWkMp = new WeakMap<Point, {
+    onremove: Function
+  }>()
+
+  public removePoint(p: Point) {
+    if (!this.hasPoint(p)) throw new Error(`polygon does not have this point`)
+    const returnObj = this.ptWkMp.get(p)
+    if (returnObj && returnObj.onremove) returnObj.onremove()
+
+    /**
+     * remove all edges associated with this point
+     */
+    const ptIdx = this.points.indexOf(p)
+    this.edges = this.edges.filter(([ idx1, idx2 ]) => idx1 !== ptIdx && idx2 !== ptIdx)
+    this.points.splice(ptIdx, 1)
+
+    this.sendUpdateSignal()
+  }
+
   public addPoint(p: Point | {x: number, y: number, z: number}, linkTo?: Point): Point {
     if (linkTo && !this.hasPoint(linkTo)) {
       throw new Error(`linkTo point does not exist for polygon!`)
     }
-    
     const pointToBeAdded = p instanceof Point
       ? p
-      : new Point([p.x, p.y, p.z], `${this.id}_${this.idCounter}`)
-    this.idCounter += 1
+      : new Point({
+          id: `${this.id}_${getUuid()}`,
+          space: this.space,
+          '@type': 'siibra-ex/annotatoin/point',
+          ...p
+        })
     
-    if (!this.hasPoint(pointToBeAdded)) this.points.push(pointToBeAdded)
+    if (!this.hasPoint(pointToBeAdded)) {
+      this.points.push(pointToBeAdded)
+      const sub = pointToBeAdded.updateSignal$.subscribe(
+        () => this.sendUpdateSignal()
+      )
+      this.ptWkMp.set(pointToBeAdded, {
+        onremove: () => {
+          sub.unsubscribe()
+        }
+      })
+    }
     if (linkTo) {
       const newEdge = [
         this.points.indexOf(linkTo),
@@ -33,19 +71,57 @@ class Polygon extends IAnnotationGeometry{
       ] as [number, number]
       this.edges.push(newEdge)
     }
+    this.sendUpdateSignal()
     return pointToBeAdded
   }
 
-  toJSON(){
-    const { id, points, edges } = this
-    return { id, points, edges }
+  toJSON(): TPolyJsonSpec{
+    const { id, points, edges, space, name, desc } = this
+    return {
+      id,
+      points: points.map(p => p.toJSON()),
+      edges,
+      space,
+      name,
+      desc,
+      '@type': 'siibra-ex/annotation/polyline'
+    }
+  }
+
+  toString() {
+    return `Points: ${JSON.stringify(this.points.map(p => p.toString()))}, edges: ${JSON.stringify(this.edges)}.`
+  }
+
+  toSands(): TSandsPolyLine{
+    return {
+      "@id": this.id,
+      "@type": 'tmp/poly',
+      coordinateSpace: {
+        '@id': this.space["@id"],
+      },
+      coordinatesPairs: this.edges.map(([ idx1, idx2 ]) => {
+        const { x: x1, y: y1, z: z1 } = this.points[idx1]
+        const { x: x2, y: y2, z: z2 } = this.points[idx2]
+        return [
+          [getCoord(x1), getCoord(y1), getCoord(z1)],
+          [getCoord(x2), getCoord(y2), getCoord(z2)]
+        ]
+      })
+    }
+  }
+
+  private getNgAnnotationId(edgeIdx: number){
+    return `${this.id}_${edgeIdx}_0`
+  }
+  getNgAnnotationIds(){
+    return this.edges.map((_, edgeIdx) => this.getNgAnnotationId(edgeIdx))
   }
   toNgAnnotation(): INgAnnotationTypes['line'][]{
     return this.edges.map((indices, edgeIdx) => {
       const pt1 = this.points[indices[0]]
       const pt2 = this.points[indices[1]]
       return {
-        id: `${this.id}_${edgeIdx}_0`,
+        id: this.getNgAnnotationId(edgeIdx),
         pointA: [pt1.x, pt1.y, pt1.z],
         pointB: [pt2.x, pt2.y, pt2.z],
         type: 'line',
@@ -81,37 +157,46 @@ class Polygon extends IAnnotationGeometry{
     }
   }
 
-  static fromJSON(json: any){
-    const { id, points, edges } = json
-    const p = new Polygon()
-    p.points = points.map(Point.fromJSON)
-    p.edges = edges
-    p.id = id
-    return p
+  static fromJSON(json: TPolyJsonSpec){
+    return new Polygon(json)
+  }
+
+  constructor(spec?: TPolyJsonSpec){
+    super(spec)
+    const { points = [], edges = [] } = spec || {}
+    this.points = points.map(Point.fromJSON)
+    this.edges = edges
   }
 
-  constructor(){
-    super()
+  private sendUpdateSignal(){
+    this.updateSignal$.next(this.toString())
   }
 
   public translate(x: number, y: number, z: number) {
     for (const p of this.points){
       p.translate(x, y, z)
     }
+    this.sendUpdateSignal()
   }
 }
 
+export const POLY_ICON_CLASS = 'fas fa-draw-polygon'
+
 export class ToolPolygon extends AbsToolClass implements IAnnotationTools, OnDestroy {
   static PREVIEW_ID='tool_poly_preview'
 
   public name = 'polygon'
-  public iconClass = 'fas fa-draw-polygon'
+  public iconClass = POLY_ICON_CLASS
   public toolType: TToolType = 'drawing'
 
+  private space: TBaseAnnotationGeomtrySpec['space']
+
   private selectedPoly: Polygon
   private lastAddedPoint: Point
 
   private managedAnnotations: Polygon[] = []
+  public managedAnnotations$ = new Subject<Polygon[]>()
+
   private subs: Subscription[] = []
   private forceRefreshAnnotations$ = new Subject()
   public allNgAnnotations$ = new Subject<INgAnnotationTypes[keyof INgAnnotationTypes][]>()
@@ -151,6 +236,10 @@ export class ToolPolygon extends AbsToolClass implements IAnnotationTools, OnDes
     )
 
     this.subs.push(
+      this.metadataEv$.subscribe(ev => {
+        this.space = ev.detail.space
+      }),
+
       /**
        * on end tool select
        */
@@ -167,8 +256,14 @@ export class ToolPolygon extends AbsToolClass implements IAnnotationTools, OnDes
         withLatestFrom(this.hoverAnnotation$)
       ).subscribe(([mouseev, ann]) => {
         if (!this.selectedPoly) {
-          this.selectedPoly = new Polygon()
+          this.selectedPoly = new Polygon({
+            edges: [],
+            points: [],
+            space: this.space,
+            '@type': 'siibra-ex/annotation/polyline'
+          })
           this.managedAnnotations.push(this.selectedPoly)
+          this.managedAnnotations$.next(this.managedAnnotations)
         }
 
         let existingPoint: Point
@@ -197,37 +292,21 @@ export class ToolPolygon extends AbsToolClass implements IAnnotationTools, OnDes
       ).subscribe(() => {
         let out: INgAnnotationTypes['line'][] = []
         for (const managedAnn of this.managedAnnotations) {
-          out = out.concat(...managedAnn.toNgAnnotation())
+          /**
+           * only emit annotations in matching space
+           */
+          if (managedAnn.space["@id"] === this.space["@id"]) {
+            out = out.concat(...managedAnn.toNgAnnotation())
+          }
         }
         this.allNgAnnotations$.next(out)
       }),
 
       /**
-       * emit on init, and reset on mouseup$
-       * otherwise, pairwise confuses last drag event and first drag event
+       * translate point when on hover a point
+       * translate entire annotation when hover edge
        */
-      merge(
-        of(null),
-        this.mouseUp$
-      ).pipe(
-        switchMapTo(this.dragHoveredAnnotation$.pipe(
-          pairwise(),
-          map(([ prev, curr ]) => {
-            const { currNgX, currNgY, currNgZ } = curr
-            const {
-              currNgX: prevNgX,
-              currNgY: prevNgY,
-              currNgZ: prevNgZ
-            } = prev
-            return {
-              ann: curr.ann,
-              deltaX: currNgX - prevNgX,
-              deltaY: currNgY - prevNgY,
-              deltaZ: currNgZ - prevNgZ,
-            }
-          }),
-        ))
-      ).subscribe(val => {
+      this.dragHoveredAnnotationsDelta$.subscribe(val => {
         const { ann, deltaX, deltaY, deltaZ } = val
         const { pickedAnnotationId, pickedOffset } = ann.detail
         const annotation = this.managedAnnotations.find(poly => poly.parseNgAnnotationObj(pickedAnnotationId, pickedOffset))
@@ -253,6 +332,16 @@ export class ToolPolygon extends AbsToolClass implements IAnnotationTools, OnDes
     )
   }
 
+  removeAnnotation(id: string) {
+    const idx = this.managedAnnotations.findIndex(ann => ann.id === id)
+    if (idx < 0) {
+      return
+    }
+    this.managedAnnotations.splice(idx, 1)
+    this.managedAnnotations$.next(this.managedAnnotations)
+    this.forceRefreshAnnotations$.next(null)
+  }
+
   ngAnnotationIsRelevant(annotation: TNgAnnotationEv): boolean {
     // perhaps use more advanced way to track if annotation is a part of polygon?
     return this.managedAnnotations.some(poly => poly.id.indexOf(annotation.pickedAnnotationId) >= 0)
diff --git a/src/atlasComponents/userAnnotations/tools/poly/poly.component.ts b/src/atlasComponents/userAnnotations/tools/poly/poly.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e6c7347301d8dbf82c4ade5f00f38374655e3c39
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/poly/poly.component.ts
@@ -0,0 +1,85 @@
+import { Component, ElementRef, Inject, Input, OnDestroy, Optional, ViewChild } from "@angular/core";
+import { MatSnackBar } from "@angular/material/snack-bar";
+import { Polygon, POLY_ICON_CLASS } from "../poly";
+import { ToolCmpBase } from "../toolCmp.base";
+import { EXPORT_FORMAT_INJ_TOKEN, IAnnotationGeometry, TExportFormats, UDPATE_ANNOTATION_TOKEN } from "../type";
+import { Clipboard } from "@angular/cdk/clipboard";
+import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions";
+import { Store } from "@ngrx/store";
+import { Observable, Subscription } from "rxjs";
+
+@Component({
+  selector: 'poly-update-cmp',
+  templateUrl: './poly.template.html',
+  styleUrls: [
+    './poly.style.css',
+  ]
+})
+
+export class PolyUpdateCmp extends ToolCmpBase implements OnDestroy{
+  @Input('update-annotation')
+  public updateAnnotation: Polygon
+
+  public annotationLabel = 'Polygon'
+  public POLY_ICON_CLASS = POLY_ICON_CLASS
+
+  @ViewChild('copyTarget', { read: ElementRef, static: false })
+  copyTarget: ElementRef
+
+  public useFormat: TExportFormats = 'string'
+  private sub: Subscription[] = []
+
+  constructor(
+    private store: Store<any>,
+    snackbar: MatSnackBar,
+    clipboard: Clipboard,
+    @Optional() @Inject(EXPORT_FORMAT_INJ_TOKEN) useFormat$: Observable<TExportFormats>,
+    @Optional() @Inject(UDPATE_ANNOTATION_TOKEN) updateAnnotation: IAnnotationGeometry,
+  ){
+    super(clipboard, snackbar)
+    if (useFormat$) {
+      this.sub.push(
+        useFormat$.subscribe(val => {
+          this.useFormat = val
+        })
+      )
+    }
+
+    if (updateAnnotation) {
+      if (updateAnnotation instanceof Polygon) {
+        this.updateAnnotation = updateAnnotation
+      }
+    }
+  }
+
+  ngOnDestroy(){
+    while (this.sub.length > 0) this.sub.pop().unsubscribe()
+  }
+
+  get copyValue(){
+    return this.copyTarget && this.copyTarget.nativeElement.value
+  }
+
+  gotoRoi(){
+    if (!this.updateAnnotation) {
+      throw new Error(`updateAnnotation undefined`)
+    }
+    if (this.updateAnnotation.points.length < 1) {
+      this.snackbar.open('No points added to polygon yet.', 'Dismiss', {
+        duration: 3000
+      })
+      return
+    }
+    const { x, y, z } = this.updateAnnotation.points[0]
+    
+    this.store.dispatch(
+      viewerStateChangeNavigation({
+        navigation: {
+          position: [x, y, z],
+          positionReal: true,
+          animation: {}
+        }
+      })
+    )
+  }
+}
diff --git a/src/atlasComponents/userAnnotations/tools/poly/poly.style.css b/src/atlasComponents/userAnnotations/tools/poly/poly.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..06677c5fea38a957e76e0f0e816aa9bd43bc3328
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/poly/poly.style.css
@@ -0,0 +1,5 @@
+point-update-cmp
+{
+  width: 100%;
+  display: block;
+}
diff --git a/src/atlasComponents/userAnnotations/tools/poly/poly.template.html b/src/atlasComponents/userAnnotations/tools/poly/poly.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..a26ca35b991873e871be7a9663672a64f012f399
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/poly/poly.template.html
@@ -0,0 +1,37 @@
+<div class="d-flex align-items-center">
+  <button mat-icon-button
+    class="flex-grow-0 flex-shrink-0"
+    [attr.aria-label]="ARIA_LABELS.GOTO_ANNOTATION_ROI"
+    (click)="gotoRoi()">
+    <i [class]="POLY_ICON_CLASS"></i>
+  </button>
+
+  <form class="flex-grow-1 flex-shrink-1">
+    <mat-form-field class="w-100">
+      <mat-label>
+        {{ annotationLabel }}
+      </mat-label>
+  
+      <textarea matInput
+        disabled="true"
+        #copyTarget>{{ updateAnnotation.updateSignal$ | async | toFormattedStringPipe : updateAnnotation : useFormat }}</textarea>
+
+      <button mat-icon-button
+        matSuffix
+        iav-stop="click"
+        aria-label="Copy to clipboard"
+        matTooltip="Copy to clipboard."
+        (click)="copyToClipboard(copyValue)"
+        color="basic">
+        <i class="fas fa-copy"></i>
+      </button>
+    </mat-form-field>
+  </form>
+</div>
+
+<point-update-cmp
+  *ngFor="let point of (updateAnnotation?.points || []) ; let i = index"
+  class="pl-4"
+  [annotation-label]="'Point ' + ( i + 1 )"
+  [update-annotation]="point">
+</point-update-cmp>
diff --git a/src/atlasComponents/userAnnotations/tools/service.ts b/src/atlasComponents/userAnnotations/tools/service.ts
index 084499e30fc4953851a864567a5fa2c13e90fca1..b4f302ad8e4a97b6ce7fa75a956690e78ab1a388 100644
--- a/src/atlasComponents/userAnnotations/tools/service.ts
+++ b/src/atlasComponents/userAnnotations/tools/service.ts
@@ -2,14 +2,17 @@ import { Injectable, OnDestroy } from "@angular/core";
 import { ARIA_LABELS } from 'common/constants'
 import { Inject, Optional } from "@angular/core";
 import { select, Store } from "@ngrx/store";
-import { fromEvent, merge, Observable, of, Subject, Subscription } from "rxjs";
-import { map, scan, switchMap, filter } from "rxjs/operators";
+import { BehaviorSubject, combineLatest, fromEvent, merge, Observable, of, Subject, Subscription } from "rxjs";
+import { map, switchMap, filter } from "rxjs/operators";
 import { viewerStateSelectedTemplatePureSelector, viewerStateViewerModeSelector } from "src/services/state/viewerState/selectors";
 import { NehubaViewerUnit } from "src/viewerModule/nehuba";
 import { NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba/util";
-import { ToolPolygon } from "./poly";
-import { AbsToolClass, ANNOTATION_EVENT_INJ_TOKEN, IAnnotationEvents, INgAnnotationTypes, INJ_ANNOT_TARGET, TAnnotationEvent } from "./type";
+import { Polygon, ToolPolygon } from "./poly";
+import { AbsToolClass, ANNOTATION_EVENT_INJ_TOKEN, IAnnotationEvents, IAnnotationGeometry, INgAnnotationTypes, INJ_ANNOT_TARGET, TAnnotationEvent, ClassInterface, TExportFormats } from "./type";
 import { switchMapWaitFor } from "src/util/fn";
+import { PolyUpdateCmp } from './poly/poly.component'
+import { Point, ToolPoint } from "./point";
+import { PointUpdateCmp } from "./point/point.component";
 
 const IAV_VOXEL_SIZES_NM = {
   'minds/core/referencespace/v1.0.0/265d32a0-3d84-40a5-926f-bf89f68212b9': [25000, 25000, 25000],
@@ -19,6 +22,30 @@ const IAV_VOXEL_SIZES_NM = {
   'minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2': [1000000, 1000000, 1000000]
 }
 
+function scanCollapse<T>(){
+  return (src: Observable<{
+    tool: string
+    annotations: T[]
+  }>) => new Observable<T[]>(obs => {
+    const cache: {
+      [key: string]: T[]
+    } = {}
+    src.subscribe({
+      next: val => {
+        const { annotations, tool } = val
+        cache[tool] = annotations
+        const out: T[] = []
+        for (const key in cache) {
+          out.push(...cache[key])
+        }
+        obs.next(out)
+      },
+      complete: obs.complete,
+      error: obs.error
+    })
+  })
+}
+
 @Injectable({
   providedIn: 'root'
 })
@@ -41,34 +68,55 @@ export class ModularUserAnnotationToolService implements OnDestroy{
 
   private previewNgAnnIds: string[] = []
 
-  private selectedTmpl: { fullId: string, name: string }
   private ngAnnotationLayer: any
   private activeToolName: string
+  private forcedAnnotationRefresh$ = new BehaviorSubject(null)
   private ngAnnotations$ = new Subject<{
     tool: string
     annotations: INgAnnotationTypes[keyof INgAnnotationTypes][]
   }>()
 
+
+  private selectedTmpl: any
   public moduleAnnotationTypes: {instance: {name: string, iconClass: string, toolSelected$: Observable<boolean>}, onClick: Function}[] = []
+  private managedAnnotationsStream$ = new Subject<{
+    tool: string
+    annotations: IAnnotationGeometry[]
+  }>()
+
+  private managedAnnotations: IAnnotationGeometry[] = []
+  public managedAnnotations$ = this.managedAnnotationsStream$.pipe(
+    scanCollapse(),
+  )
 
   private registeredTools: {
     name: string
-    iconClass: string
+    iconClass: string,
+    target?: ClassInterface<IAnnotationGeometry>,
+    editCmp?: ClassInterface<any>,
     onMouseMoveRenderPreview: (pos: [number, number, number]) => INgAnnotationTypes[keyof INgAnnotationTypes][]
-    ngOnDestroy?: Function
+    onDestoryCallBack: () => void
   }[] = []
   private mousePosReal: [number, number, number]
 
   /**
    * @description register new annotation tool
-   * @param {AbsToolClass} Cls 
+   * Some tools (deletion / dragging) may not have target and editCmp 
+   * 
+   * @param {{
+   *   toolCls: ClassInterface<AbsToolClass>
+   *   target?: ClassInterface<IAnnotationGeometry>
+   *   editCmp?: ClassInterface<any>
+   * }} arg 
    */
-  private registerTool(Cls: new (
-    svc: Subject<TAnnotationEvent<keyof IAnnotationEvents>>
-  ) => AbsToolClass){
-
+  private registerTool<T extends AbsToolClass>(arg: {
+    toolCls: ClassInterface<T>,
+    target?: ClassInterface<IAnnotationGeometry>,
+    editCmp?: ClassInterface<any>
+  }){
+    const { toolCls: Cls, target, editCmp } = arg
     const newTool = new Cls(this.annotnEvSubj) as AbsToolClass & { ngOnDestroy?: Function }
-    const { name, iconClass, onMouseMoveRenderPreview, ngOnDestroy } = newTool
+    const { name, iconClass, onMouseMoveRenderPreview } = newTool
     
     this.moduleAnnotationTypes.push({
       instance: newTool,
@@ -84,18 +132,37 @@ export class ModularUserAnnotationToolService implements OnDestroy{
       }
     })
 
-    newTool.allNgAnnotations$.subscribe(ann => {
-      this.ngAnnotations$.next({
-        tool: name,
-        annotations: ann
+    const toolSubscriptions: Subscription[] = []
+
+    toolSubscriptions.push(
+      newTool.allNgAnnotations$.subscribe(ann => {
+        this.ngAnnotations$.next({
+          tool: name,
+          annotations: ann
+        })
+      }),
+      newTool.managedAnnotations$.subscribe(ann => {
+        this.managedAnnotationsStream$.next({
+          annotations: ann,
+          tool: name
+        })
       })
-    })
+    )
 
     this.registeredTools.push({
       name,
       iconClass,
+      target,
+      editCmp,
       onMouseMoveRenderPreview: onMouseMoveRenderPreview.bind(newTool),
-      ngOnDestroy: ngOnDestroy.bind(newTool)
+      onDestoryCallBack: () => {
+        newTool.ngOnDestroy && newTool.ngOnDestroy()
+        this.managedAnnotationsStream$.next({
+          annotations: [],
+          tool: name
+        })
+        while(toolSubscriptions.length > 0) toolSubscriptions.pop().unsubscribe()
+      }
     })
   }
 
@@ -110,19 +177,30 @@ export class ModularUserAnnotationToolService implements OnDestroy{
     const foundIdx = this.registeredTools.findIndex(spec => spec.name === name)
     if (foundIdx >= 0) {
       const tool = this.registeredTools.splice(foundIdx, 1)[0]
-      const { ngOnDestroy } = tool
-      if (ngOnDestroy) ngOnDestroy.call(tool)
+      tool.onDestoryCallBack()
     }
   }
 
+  public exportFormat$ = new BehaviorSubject<TExportFormats>('string')
+
   constructor(
     store: Store<any>,
     @Inject(INJ_ANNOT_TARGET) annotTarget$: Observable<HTMLElement>,
     @Inject(ANNOTATION_EVENT_INJ_TOKEN) private annotnEvSubj: Subject<TAnnotationEvent<keyof IAnnotationEvents>>,
     @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Observable<NehubaViewerUnit>,
   ){
-    this.registerTool(ToolPolygon)
 
+    this.registerTool({
+      toolCls: ToolPoint,
+      target: Point,
+      editCmp: PointUpdateCmp,
+    })
+
+    this.registerTool({
+      toolCls: ToolPolygon,
+      target: Polygon,
+      editCmp: PolyUpdateCmp,
+    })
 
     /**
      * listen to mouse event on nehubaViewer, and emit as TAnnotationEvent
@@ -281,31 +359,33 @@ export class ModularUserAnnotationToolService implements OnDestroy{
     )
 
     /**
-     * on annotation update, update annotations
+     * on tool managed annotations update, update annotations
      */
     this.subscription.push(
-      this.ngAnnotations$.pipe(
-        switchMap(switchMapWaitFor({
-          condition: () => !!this.ngAnnotationLayer,
-          leading: true
-        })),
-        scan((acc, curr) => {
-          return {
-            ...acc,
-            [curr.tool]: curr.annotations
-          }
-        }, {} as {
-          [key: string]: INgAnnotationTypes[keyof INgAnnotationTypes][] 
-        }),
-        map(acc => {
-          const out: INgAnnotationTypes[keyof INgAnnotationTypes][] = []
-          for (const key in acc) {
-            out.push(...acc[key])
-          }
-          return out
-        })
+      combineLatest([
+        this.forcedAnnotationRefresh$,
+        this.ngAnnotations$.pipe(
+          switchMap(switchMapWaitFor({
+            condition: () => !!this.ngAnnotationLayer,
+            leading: true
+          })),
+          scanCollapse(),
+        )
+      ]).pipe(
+        map(([_, ngAnnos]) => ngAnnos),
       ).subscribe(arr => {
+        const ignoreNgAnnIdsSet = new Set<string>()
+        for (const hiddenAnnot of this.hiddenAnnotations) {
+          const ids = hiddenAnnot.getNgAnnotationIds()
+          for (const id of ids) {
+            ignoreNgAnnIdsSet.add(id)
+          }
+        }
         for (const annotation of arr) {
+          if (ignoreNgAnnIdsSet.has(annotation.id)) {
+            this.deleteNgAnnotationById(annotation.id)
+            continue
+          }
           const localAnnotations = this.ngAnnotationLayer.layer.localAnnotations
           const annRef = localAnnotations.references.get(annotation.id)
           const annSpec = parseNgAnnotation(annotation)
@@ -365,15 +445,46 @@ export class ModularUserAnnotationToolService implements OnDestroy{
         select(viewerStateSelectedTemplatePureSelector)
       ).subscribe(tmpl => {
         this.selectedTmpl = tmpl
-      })
+        this.annotnEvSubj.next({
+          type: 'metadataEv',
+          detail: {
+            space: tmpl && { ['@id']: tmpl['@id'] }
+          }
+        })
+      }),
+      this.managedAnnotations$.subscribe(ann => this.managedAnnotations = ann),
     )
   }
 
+  private hiddenAnnotationIds = new Set<string>()
+
+  public hiddenAnnotations$ = new BehaviorSubject<IAnnotationGeometry[]>([])
+  private hiddenAnnotations: IAnnotationGeometry[] = []
+  public toggleAnnotationVisibilityById(id: string){
+    if (this.hiddenAnnotationIds.has(id)) this.hiddenAnnotationIds.delete(id)
+    else this.hiddenAnnotationIds.add(id)
+
+    this.hiddenAnnotations = []
+    for (const id of Array.from(this.hiddenAnnotationIds)) {
+      const found = this.managedAnnotations.find(managedAnn => managedAnn.id === id)
+      if (found) {
+        this.hiddenAnnotations.push(found)
+      }
+    }
+    this.hiddenAnnotations$.next(this.hiddenAnnotations)
+    this.forcedAnnotationRefresh$.next(null)
+  }
+
+  public getEditAnnotationCmp(annotation: IAnnotationGeometry): ClassInterface<any>{
+    const foundTool = this.registeredTools.find(t => annotation instanceof t.target)
+    return foundTool && foundTool.editCmp
+  }
+
   private clearAllPreviewAnnotations(){
-    while (this.previewNgAnnIds.length > 0) this.deleteAnnotationById(this.previewNgAnnIds.pop())
+    while (this.previewNgAnnIds.length > 0) this.deleteNgAnnotationById(this.previewNgAnnIds.pop())
   }
 
-  private deleteAnnotationById(annId: string) {
+  private deleteNgAnnotationById(annId: string) {
     const localAnnotations = this.ngAnnotationLayer.layer.localAnnotations
     const annRef = localAnnotations.references.get(annId)
     if (annRef) {
diff --git a/src/atlasComponents/userAnnotations/tools/toFormattedString.pipe.ts b/src/atlasComponents/userAnnotations/tools/toFormattedString.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3fcdf62a3c645d27ea693132e9ef95e0051530c6
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/toFormattedString.pipe.ts
@@ -0,0 +1,22 @@
+import { Pipe, PipeTransform } from "@angular/core";
+import { IAnnotationGeometry, TExportFormats } from "./type";
+
+@Pipe({
+  name: 'toFormattedStringPipe',
+  pure: true
+})
+
+export class ToFormattedStringPipe implements PipeTransform{
+
+  public transform(_: any, annotation: IAnnotationGeometry, format: TExportFormats){
+    if (format === 'json') {
+      return JSON.stringify(annotation.toJSON(), null, 2)
+    }
+
+    if (format === 'sands') {
+      return JSON.stringify(annotation.toSands(), null, 2)
+    }
+
+    return annotation.toString()
+  }
+}
diff --git a/src/atlasComponents/userAnnotations/tools/toolCmp.base.ts b/src/atlasComponents/userAnnotations/tools/toolCmp.base.ts
new file mode 100644
index 0000000000000000000000000000000000000000..102128f26fc77e7f552d77f917a7d759c9168ac3
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/toolCmp.base.ts
@@ -0,0 +1,26 @@
+import { MatSnackBar } from "@angular/material/snack-bar"
+import { Clipboard } from "@angular/cdk/clipboard";
+import { ARIA_LABELS } from 'common/constants'
+
+export abstract class ToolCmpBase {
+  public ARIA_LABELS = ARIA_LABELS
+  constructor(
+    protected clipboard: Clipboard,
+    protected snackbar: MatSnackBar,  
+  ){
+
+  }
+  copyToClipboard(value: string){
+    const success = this.clipboard.copy(`${value}`)
+    this.snackbar.open(
+      success ? `Copied to clipboard!` : `Failed to copy URL to clipboard!`,
+      null,
+      { duration: 1000 }
+    )
+  }
+
+  /**
+   * Intention of navigating to ROI
+   */
+  abstract gotoRoi(): void
+}
diff --git a/src/atlasComponents/userAnnotations/tools/type.ts b/src/atlasComponents/userAnnotations/tools/type.ts
index 878107b1e6c84e4a582fa750a55ad50529892838..d90d40ea836b1b5ee3593cac2224aa65fe8844fa 100644
--- a/src/atlasComponents/userAnnotations/tools/type.ts
+++ b/src/atlasComponents/userAnnotations/tools/type.ts
@@ -1,44 +1,8 @@
 import { InjectionToken } from "@angular/core"
-import { Observable, of } from "rxjs"
-import { filter, map, mapTo, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators'
+import { merge, Observable, of, Subject } from "rxjs"
+import { filter, map, mapTo, pairwise, switchMap, switchMapTo, takeUntil, withLatestFrom } from 'rxjs/operators'
 import { getUuid } from "src/util/fn"
 
-export type TToolType = 'translation' | 'drawing' | 'deletion'
-
-type THasId = {
-  id?: string
-}
-export abstract class IAnnotationGeometry {
-  public id: string
-  
-  abstract toNgAnnotation(): INgAnnotationTypes[keyof INgAnnotationTypes][]
-  abstract toJSON(): object
-
-  constructor(spec?: THasId){
-    this.id = spec && spec.id || getUuid()
-  }
-}
-
-export interface IAnnotationTools {
-  name: string
-  iconClass: string
-  toolType: TToolType
-}
-
-export type TNgAnnotationEv = {
-  pickedAnnotationId: string
-  pickedOffset: number
-}
-
-export type TNgMouseEvent = {
-  event: MouseEvent
-  ngMouseEvent: {
-    x: number
-    y: number
-    z: number
-  }
-}
-
 /**
  * base class to be extended by all annotation tools
  */
@@ -47,6 +11,9 @@ export abstract class AbsToolClass {
   public abstract name: string
   public abstract iconClass: string
 
+  public abstract removeAnnotation(id: string): void
+  public abstract managedAnnotations$: Observable<IAnnotationGeometry[]>
+
   /**
    * @description to be overwritten by subclass. Check if a given annotation is relevant to the tool. Used for filtering annotations.
    * @param {TNgAnnotationEv} annotation
@@ -77,6 +44,10 @@ export abstract class AbsToolClass {
     map(ev => (ev as TAnnotationEvent<'toolSelect'>).detail.name === this.name)
   )
 
+  protected metadataEv$ = this.annotationEv$.pipe(
+    filter(ev => ev.type === 'metadataEv'),
+  ) as Observable<TAnnotationEvent<'metadataEv'>>
+
   protected mouseDown$ = this.annotationEv$.pipe(
     filter(ev => ev.type === 'mousedown')
   ) as Observable<TAnnotationEvent<'mousedown'>>
@@ -142,6 +113,163 @@ export abstract class AbsToolClass {
     }),
     filter(v => !!v)
   )
+
+
+  /**
+   * emit on init, and reset on mouseup$
+   * otherwise, pairwise confuses last drag event and first drag event
+   */
+  protected dragHoveredAnnotationsDelta$: Observable<{
+    ann: TAnnotationEvent<"hoverAnnotation">,
+    deltaX: number,
+    deltaY: number,
+    deltaZ: number
+  }> = merge(
+    of(null),
+    this.mouseUp$
+  ).pipe(
+    switchMapTo(this.dragHoveredAnnotation$.pipe(
+      pairwise(),
+      map(([ prev, curr ]) => {
+        const { currNgX, currNgY, currNgZ } = curr
+        const {
+          currNgX: prevNgX,
+          currNgY: prevNgY,
+          currNgZ: prevNgZ
+        } = prev
+        return {
+          ann: curr.ann,
+          deltaX: currNgX - prevNgX,
+          deltaY: currNgY - prevNgY,
+          deltaZ: currNgZ - prevNgZ,
+        }
+      }),
+    ))
+  )
+}
+
+export type TToolType = 'translation' | 'drawing' | 'deletion'
+
+export type TBaseAnnotationGeomtrySpec = {
+  id?: string
+  space?: {
+    ['@id']: string
+  }
+  name?: string
+  desc?: string
+}
+
+export function getCoord(value: number): TSandsQValue {
+  return {
+    '@id': getUuid(),
+    '@type': "https://openminds.ebrains.eu/core/QuantitativeValue",
+    value,
+    unit: {
+      "@id": 'id.link/mm'
+    }
+  }
+}
+
+type TSandsQValue = {
+  '@id': string
+  '@type': 'https://openminds.ebrains.eu/core/QuantitativeValue'
+  uncertainty?: [number, number]
+  value: number
+  unit: {
+    '@id': 'id.link/mm'
+  }
+}
+type TSandsCoord = [TSandsQValue, TSandsQValue] | [TSandsQValue, TSandsQValue, TSandsQValue]
+
+export type TSandsPolyLine = {
+  coordinatesPairs: [TSandsCoord, TSandsCoord][]
+  coordinateSpace: {
+    '@id': string
+  }
+  '@type': 'tmp/poly'
+  '@id': string
+}
+
+export type TSandsLine = {
+  coordinatesFrom: TSandsCoord
+  coordinatesTo: TSandsCoord
+  coordinateSpace: {
+    '@id': string
+  }
+  '@type': 'tmp/line'
+  '@id': string
+}
+
+export type TSandsPoint = {
+  coordinates: TSandsCoord
+  coordinateSpace: {
+    '@id': string
+  }
+  '@type': 'https://openminds.ebrains.eu/sands/CoordinatePoint'
+  '@id': string
+}
+
+export interface ISandsAnnotation {
+  point: TSandsPoint
+  line: TSandsLine
+  polyline: TSandsPolyLine
+}
+
+export abstract class IAnnotationGeometry {
+  public id: string
+  
+  public name: string
+  public desc: string
+
+  public space: TBaseAnnotationGeomtrySpec['space']
+
+  abstract getNgAnnotationIds(): string[]
+  abstract toNgAnnotation(): INgAnnotationTypes[keyof INgAnnotationTypes][]
+  abstract toJSON(): object
+  abstract toString(): string
+  abstract toSands(): ISandsAnnotation[keyof ISandsAnnotation]
+
+  public updateSignal$ = new Subject()
+
+  constructor(spec?: TBaseAnnotationGeomtrySpec){
+    this.id = spec && spec.id || getUuid()
+    this.space = spec?.space
+    this.name = spec?.name
+    this.desc = spec?.desc
+  }
+
+  setName(name: string) {
+    this.name = name
+    this.updateSignal$.next(this.toString())
+  }
+  setDesc(desc: string) {
+    this.desc = desc
+    this.updateSignal$.next(this.toString())
+  }
+}
+
+export interface IAnnotationTools {
+  name: string
+  iconClass: string
+  toolType: TToolType
+}
+
+export type TNgAnnotationEv = {
+  pickedAnnotationId: string
+  pickedOffset: number
+}
+
+export type TNgMouseEvent = {
+  event: MouseEvent
+  ngMouseEvent: {
+    x: number
+    y: number
+    z: number
+  }
+}
+
+export type TMetaEvent = {
+  space: { ['@id']: string }
 }
 
 export interface IAnnotationEvents {
@@ -152,6 +280,8 @@ export interface IAnnotationEvents {
   mousedown: TNgMouseEvent
   mouseup: TNgMouseEvent
   hoverAnnotation: TNgAnnotationEv
+
+  metadataEv: TMetaEvent
 }
 
 export type TAnnotationEvent<T extends keyof IAnnotationEvents> = {
@@ -185,3 +315,14 @@ export interface INgAnnotationTypes {
 }
 
 export const INJ_ANNOT_TARGET = new InjectionToken<Observable<HTMLElement>>('INJ_ANNOT_TARGET')
+export const UDPATE_ANNOTATION_TOKEN = new InjectionToken<IAnnotationGeometry>('UDPATE_ANNOTATION_TOKEN')
+
+export interface ClassInterface<T> {
+  new (...arg: any[]): T
+}
+
+export type TExportFormats = 'sands' | 'json' | 'string'
+
+export const EXPORT_FORMAT_INJ_TOKEN = new InjectionToken<
+  Observable<TExportFormats>
+>('EXPORT_FORMAT_INJ_TOKEN')
diff --git a/src/viewerModule/nehuba/navigation.service/navigation.service.ts b/src/viewerModule/nehuba/navigation.service/navigation.service.ts
index 9344da35e6024e63a7c50d23bfd13a76a9eba203..ed751e8b85149abb089fbd5cf5adf0ec888abdee 100644
--- a/src/viewerModule/nehuba/navigation.service/navigation.service.ts
+++ b/src/viewerModule/nehuba/navigation.service/navigation.service.ts
@@ -75,9 +75,13 @@ export class NehubaNavigationService implements OnDestroy{
     if (animation && this.globalAnimationFlag) {
 
       const gen = timedValues()
-      const dest = navigation
       const src = this.viewerNav
 
+      const dest = {
+        ...src,
+        ...navigation
+      }
+
       const delta = navAdd(dest, navMul(src, -1))
 
       const animate = () => {